CPU load indicator

Что это?

Этот проект может стать хорошим начальным опытом в разработке устройств на микроконтроллере и программировании его, как прошивки мк так и создании управляющей программы.

Собственно это очень простое устройство, плавно показывающее текущую нагрузку процессора меняя яркость светодиода(ну или по желанию можно использовать другую нагрузку). Людям, которые занимаются моддингом особо думаю понравится - нужно только приложить фантазию :) Работает через USB, причём программный, реализованный с помощью прооекта V-USB. В качестве мк выбран Atmega8

Предполагается, что уже есть какие то начальные знания в области программирования и электроники.

Принцип работы

Есть 2 стороны - девайса и компьютера. Компьютер периодически с помощью программы посылает через usb информацию о том, на сколько нужно зажечь ярко светодиод. А девайс слушает комманду и плавно приблежает яркость светодиода от текущего значения к новому.

Хочу отметить, что я посторался сделать как можно более гибкую систему - интервалы опроса текущей загрузки процессора программой на компьютере и интервал с которым приблежается текущая яркость к новой можно задать в коммандной строке программы. Также можно задать верхнюю и нижнюю границу свечения.

Подробнее

Девайс

Схема достаточно простая и вполне собирается легко на макетной плате(у кого она есть) или как у меня - на картонке :) Конечно же, при желании можно сделать печатную плату - будет шикарно - я делать не стал, потому, что CPU load indicator оказался промежуточным этапом к большему проекту который я сейчас делаю. Конечно, гик скажет, что это слишком просто, чтоб уделять этому внимание, но кто-то только учиться, вроде меня =:)

Комментарии по схеме:

  • J1 - USB порт к компьютеру(D- это вторая нога, D+ это третья), J2 - SPI видимо к программатору :)
  • X1, C5, C6 - генератор. Кварц можно поставить, например, на 15МГц, главное, чтоб V-USB поддерживал эту частоту, а кондёры тут от 18 до 22пф можно выбирать. У меня 22пф стоят
  • R1 - подтягивающий резистор, лучше всё-таки 10кОм. Для работы мк ведь требуется reset пассивный, а т.к. он инверсный, нужно подать "1". По идее, там есть внутри 100кОм, но как пишут, нога ловит каждый чих
  • D2, D3 - стабилитроны для того, чтоб не превысить напряжение на линии USB - так должно быть по стандарту, единица - не должна привышать 3.6в. У меня стеклянные - чёрной полоской к сигнальным линиям(D+ & D-)
  • R4, R5 - везде стоят по 68 Ом, у меня нормально работает 100 Ом. Если я правильно понимаю, они нужны чтоб не получилось ситуации, когда можно спалить порт мк
  • R2 - нужно оставить именно таким, это некая индикация USB-хосту что подключённо низкоскоростное устройство. Для эксперемента можно подцепить к usb только этот резюк и ядро будет писать что обнаруженно новое низкоскоростное устройство.
  • C3 - сглаживает помехи по питанию

ШИМ (PWM)

Наверное всё это мутно - интервалы, шаги, яркость.. Так вот, "яркость" - это численное значение от 0..255 - значение, которое подаётся на ШИМ(Широтно - Импульсная Модуляция) выход. Про ШИМ можно много где прочитать, мне понравилось на myRobot. Соответственно максимальной яркости(состоянию, в котором максимальное количество "1") соответствует 255, минимальной - 0(максимальное количество "0"), среднему - 127(примерное равное количесво "1" и "0"). Думаю логика прослеживается.

Когда компьютер послал новое значение для ШИМ, которое нужно установить на выходе девайса, то девайс постепенно приблежает текущее значение к новому, с определённой задержкой. Благодаря этому яркость плавно меняется. Так, например, в темноте плавно повышается яркость после еле различимого свечения и место рядом с девайсом около компьютера краснеет - ну разумеется если стоит красный светодиод.

Как я уже указал, программа посылает уже конкретное значение ШИМ которое нужно установить. Но перед отправкой, нужно его ещё вычислить. Т.е. при загрузки процессора в 100% яркость индикатора должна быть максимальной, а не (если бы послать просто значение ШИМ 100) меньше половины от полной. Для этого нужно пересчитать его по простой формуле: (cpu_load * 255/100), где cpu_load - текущая нагрузка процессора.

Надеюсь пока ещё понятно, я ж предупредил что минимальная подготовка должна быть =).
А ведь вполне может быть такая ситуация, что светодиод начинает светиться только от значния 80 в ШИМ? Вполне может быть - значит при маленькой нагрузке мы вообще не увидим свечения. А что, если после ШИМ 200 индикатор так ярко светит, что кажется, вот-вот сгорит? Можно прийти в выводу, что требуется поставить рамки от и до и высчитывать соответственно величину ШИМ. Вычисляется по формуле(выжимка из кода):
cpu_load = ((valueMax - valueMin) / 100.0 ) * cpu_load + valueMin ;
Названия переменных говорят за себя, пояснения не требуются, как я думаю.

Управляющая программа(сторона компьютера) - драйвер

Основная программа сосредоточенна в software/cpu_indicator.c.
Внимание! Для сборки приложения нужно воспользоваться коммандой make clean all. Для запуска(из папки software) ./cpuload_indicator.
У вас обязательно должен быть установлен пакет libusb-dev и libgtop-dev, иначе ничего не соберётся. Разумеется в зависимости от дистрибутива исходные коды этих библиотек могут отличаться названием.

libusb нужен очевидно для работы с USB, а libgtop для получения информации о текущей нагрузке процессора.

В начале software/cpu_indicator.c подключаются хедеры и указываются дефолтные значения переменных.
О функциях

  1. bool parseOpts(int, char *) - служит для парсинга коммандной строки и проверки на корректность значений.
  2. bool connectUsb() - ищет устройство среди всех и открывает его, если нашла. А если не нашла то в зависимости от наличия аргумента коммандной строки -catch будет пытаться искать снова или просто попросит завершить работу программы с информацией о том, что устройство не найдённо. В противном случае, если указан -catch программа будет ждать пока подключат устройство. После открытия успешного устройства посылается комманда устанавливающая шаг времени, с которым значение ШИМ приблежаться к новому. Этот аргумент можно задать в коммандной строке ключом -step N, где N - значение задержки в мсек.
  3. int main (int, char *) - собвственнно входная точка программы. Вызывает parseOpts() и connectUsb(), высчитывает нагрузку на процессор за время в процентах, переводит в соответствующее значение ШИМ с учётом верхних и нижних смещений границ(о чём речь шла ранее) и посылает значение в USB. При ошибке, в зависимости от -catch, либо завершается работа программы, либо пытается подсоединиться снова, если аргумент указан.
    Особенно сложным оказалось для меня получение информации о нагрузки на процессор. Я воспользовался статьёй Calculating CPU Usage from /proc/stat, где нагрузка вычисляется bash скриптом и берётся информация из /proc/stat.
  4. void usage(char *) - выводит информацию об использовании, лучше показать:

    $ ./cpuload_indicator -help
    Control program for CPU Load indicator (c) ruX 2010
    Usage ./cpuload_indicator [option=[value]] ..
    Options:
    -min NN - set minimal PWM value [0..255]
    -max NN - set maxmimum PWM value [0..255]
    -step NN - set step interval for LED (in msec)
    -update NN - interval for update load about current CPU(in seconds)
    -catch - never quit(always trying to catch device)
    -help - this help promt
    Copyright by ruX, 2010, http://ruX.pp.ru/

Думаю на столько краткого описания достаточно. За подробностями - в исходник. Есть ещё файл obdev_usb.c - там лежат довольно стандартные функции для открытия устройства(по его VID:PID, имени, имени разработчика) - они качуют из различных проектов, иногда немного правясь. Так и я взял за из проекта usb-relay.

Программа прошивки

Разумеется, всё что относится к прошивке девайса лежит в firmware, как ни странно :)

Код программы для мк можно разделить условно на 2 части: работа с USB, реализованная с помощью библиотеки V-USB и основной логики.

Важный этап - настройка V-USB, которая находится в файле firmware/usbdrv.h. То, что там определенно будет решать каким будет устройство. Файл шаблонный, от obdev. Но добавил 2 строчки, которые определяют константы комманд, которые посылает компьютер - для установки нового значения ШИМ и установки интервала шага приближения к новому значению:
#define REQUEST_SET_TARGET 1
#define REQUEST_SET_STEP 2

В основном файле логики firmware/cpuload_indicator.c сначала устанавливается конфигурация перефирии: направление вывода и настройка ШИМ.
// Configure port B pin 1 as output
DDRB = (_BV(1));

// Setup PWM
TCCR1A=0xA1;
TCCR1B=0x09;
TCNT1H=0x00;

Дальше вызывается process() в котором и происходит основная работа - один раз инициализируется USB и начинается бесконечный цикл, в котором происходит опрос usb - usbPoll() и приближение значения ШИМ, выполняется маленькая задержка:

void process(void)
{
unsigned int dsec = 0;

for(startUsb(); ;dsec++){
usbPoll();

if (dsec % (delay * 10) == 0 && OCR1A != target) {
if (OCR1A > target) OCR1A--; else OCR1A++;
}

_delay_ms(0.1);

if (dsec == 10000) dsec = 0;
}
}

У меня в качестве выхода ШИМ используется T/C1 - PORTB, бит 1, нога 15 в PDIP корпусе, соответственно имеем дело с OCR1A. Ещё нюанс: компьютер посылает интервал шага приближения в мсек, а внутри цикла задержка 0.1мсек, поэтому приходиться делать (delay * 10), чтоб привести к одному порядку - к примеру у нас компьютер передал,что шаг - 50мсек(=5 * 10-2, значит нужно сделать 50 * 10 = 500 циклов перед тем, как будет приблежение значения ШИМ к новому.

Если кто то не понял код выше - в цикле сначала происходит опрос, а дальше проверяется, если наступило время, когда нужно сделать шаг(т.е. интервал задержки прошёл) и при этом текущее значение ШИМ это не то, что компьютер "попросил" выставить, то приблежаемся к требуемому значению. Дальше задержка на 10-4сек и попытка избежать переполнения если число в счётчике циклов dsec достаточно большое.

Ещё определенна uchar usbFunctionSetup(uchar data[8]), которая вызывается, когда компьютер послал данные - в нашем случае это либо новое значение ШИМ, либо установка интервала шага приблежения.

uchar usbFunctionSetup(uchar data[8])
{
usbRequest_t *request = (usbRequest_t*) data;
switch (request->bRequest) {
case REQUEST_SET_TARGET:
target = request->wValue.bytes[0];
break;

case REQUEST_SET_STEP:
delay = (short int)(request->wValue.bytes);
break;
}

return 0;
}

Структура usbRequest_t *request содержит информацию о том, что нам пришло, подробнее можно узнать в доках. request->bRequest определяет номер комманды, а request->wValue.bytes переданные от компьютера данные.
target - значение ШИМ, которое нужно установить, фактически используется только 1 байт, поэтому и выбираем только первый байт из того, что пришло. А delay - задержка в милисекундах, поэтому тут одним байтом не обойтись, берём 2 сразу, делая тайпкастинг к short int.

Прошивка прошивки :)

В firmware/ определён мэйкфайл, который можно настроить под себя - в моём случае я использовал мк atmega8, программатор usbasp и частота цварца - 12MГц. По желанию можно изменить:

. . . .
DEVICE = atmega8
PROGRAMMER = usbasp
FREQ = 12000000
. . . .

Чтоб полностью откомпилировать, прошить программу и фьюзы, достаточно выполнить make clean all flash fuse :) Из фьюзов нужно отметить, что установленно тактирование от кварца, свыше 12МГц, кому интересно найдут интерпретацию фьюзов. Кстати о них(для atmega8) : старший байт - 0xC9, младший - 0xEF

Фото

Повторюсь, это сделанно на картонке, из за отсутствия макетной платы, причём буквально на коленке и для другого проекта, поэтому, уведенное может показаться совсем не то, что на схеме было. Но так оно и есть, поверьте :) Итак, показать фото

Финал

На этом всё, буду рад если кому то это помогло. Хотя это уже помогло мне самому :)
Такие простые штуки позволяют сразу много всего пощюпать, хоть и не глубоко. А это помогает разложить по полочкам тонны информации.

Это - всё таки посложнее просто миганием светодиодов. И даже посложнее чем мигание светодиодов через USB(кстати, огромное спасибо проекту usb-Relay, который помог мне разобраться во многом).

Стоимость сборки такого устройства порядка 100-120р(по крайней мере с ценами, которые в мегаэлектронике в СПБ)

У линуксодидов проблемм быть не должно. У *bsd и solaris как я понимаю тоже нет(разве что поправить пути к инклюдам в мейкфайле). С виндой всё хуже. То, что нужно libusb это очевидно, кто чистал описание проекта V-USB сразу это поняли. По идее, как я понимаю, ничего не изменится, кроме места получения текущей нагрузки процессора - тут придётся разбираться самостоятельно. Или, если получится, воспользоваться cygwin(не из за libusb, а из за libgtop!), если умеете, я если честно практически не предствляю что он умеет и как :) Ну и разумеется нужно иметь make & gcc & winAvr

Скачать прошивку, программу и схему(в формате KiCad) в одном файле: cpuload_indicator_v0.1.tar.gz