Очередной CI светофор. На этот раз attiny2313 и Node.js

в 14:53, , рубрики: attiny2313, diy или сделай сам, Jenkins, node.js, светофор, электроника, метки: , , , , , ,

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

Очередной CI светофор. На этот раз attiny2313 и Node.js

Под катом светофор из цветомузыки и пластиковых бутылок, USB модуль управления светофором на attiny2313 за доллар, а так же софт для опроса Jenkins и управления USB модулем на Node.js.

Проект был разделен на 3 части

  1. Светофор. Снимать светофор с перекрестка или пытаться купить его мы не собирались. Мы не ставили перед собой цель повесить в офисе настоящий светофор, нам вполне по силам сделать его из подручных материалов.
  2. Электроника. Я сразу отбросил Rasbery Pi и Arduino как избыточные и дорогие решения. Мне нужен был доступ к устройству по USB, я планироал использовать напряжение питания USB в качестве источника питания ламп светофора, то есть избавить схему от дополнительного питания.
  3. Софт для работы с USB на стороне компьютера. Задача: опрос Jenkins текущего статуса определенного билда и отправка соответствующего сообщения по USB.

Светофор

Светофор решили делать из старой цветомузыки, ровный корпус и хорошие стекла — это то, что нужно. Козырьки были вырезаны из пластиковых бутылок и покрашены вместе с корпусом в чёрный цвет. От внутренней начинки избавились, ведь он работал от сетевого напряжения, а нам нужно переделать на 5в от USB.

Вместо ламп я использовал светодиоды, которые предварительно выпаял из светодиодной ленты. Для большей яркости решил сделать сборки по 4 светодиода, соединенных параллельно и ограничил протекающий через них ток резисторами по 100ом, порты attiny2313 нормально справляются с нагрузкой около 20мА.

Картинка

Очередной CI светофор. На этот раз attiny2313 и Node.js

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

Картинка

Очередной CI светофор. На этот раз attiny2313 и Node.js

Светодиодные сборки я прикрепил к крышкам от бутылок при помощи болтов М3 и трубок с внутренним диаметром 2мм, сделал это для того, чтобы была возможность отрегулировать расстояние для большей яркости.

Картинка

Очередной CI светофор. На этот раз attiny2313 и Node.js

Еще картинки

Очередной CI светофор. На этот раз attiny2313 и Node.js
Очередной CI светофор. На этот раз attiny2313 и Node.js

Электроника

В наличии у меня были два AVR микроконтроллера: более дешевый attiny13 с 1kB памяти и чуть дороже attiny2313 c 2kB памяти. Для того, чтобы устройство могло работать с USB протоколом, я не собирался усложнять схему дополнительными USB модулями так как это можно сделать прямо на MK при помощи прекрасной библиотеки V-USB.

Минимальный размер памяти, необходимый этой библиотеке, равен около 1.3kB — поэтому выбор пал на attiny2313. Цена этого микроконтроллера 1-2 доллара, купить его тоже не составит никакого труда, так как это очень популярная модель.

Схема электроники

Схема устройства очень простая, для наглядности покажу свою, но в качестве руководства советую изучить возможные варианты, так как здесь предложены 3 варианта схем, описаны их достоинства и недостатки:

Очередной CI светофор. На этот раз attiny2313 и Node.js

Основная часть схемы — это обвязка, необходимая для корректного определения компьютером устройства как USB. Она необходима для изменения напряжения на контактах D+ и D- USB. Дело в том, что напряжение на контактах питания USB и питание моей схемы равно 5в, но для сигналов на D+ и D- напряжение должно быть 3.3в, для этого на них стоят стабилитроны. Подключение светодиодов у меня расположено так только потому, что я паял без предварительной трассировки, по этой же причине я не выкладываю чертежи платы. Так же на схеме присутствует динамик, он необходим для воспроизведения мелодии из этой статьи, когда падает билд.

Готовый USB модуль

Очередной CI светофор. На этот раз attiny2313 и Node.js
Очередной CI светофор. На этот раз attiny2313 и Node.js

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

USB модуль умеет совсем чуть-чуть. Либо светить одним светодиодом, либо моргать одним из них. Звучание мелодии на упавший билд не было задумано изначально, а было добавлено в уже работающее устройство модификацией прошивки.

После подключения USB библиотеки памяти для реализации необходимого функционала оставалось очень немного, поэтому я решил максимально упростить логику на стороне МК и возложить всю ответственность за правильную работу устройства на плечи компьютерного софта. Так я пришел к следующей форме протокола: устройство будет принимать сообщения, состоящие из двухзначного числа, где первая цифра — это пин порта, а вторая — это флаг, указывающий, должен ли светодиод гореть или моргать.

Запуск мелодии происходит только при падении билда, в общем у меня там хардкод, я не планировал конфигураций для него.

Прошивка

Код прошивки я всегда пишу на C в Eclipse, компилятор gcc из набора WinAVR, прошивку заливаю программатором USBasp, купленном на алиекспресс за 4.5$, прошиваю фьюзы программой Khazama.

Начать стоит с прошивки фьюзов. В схеме используется 12MHz кварц и для его использования фьюзы должны быть установлены на кварц с частотой больше 8MHz (CKSEL=1110). Кроме того, нужно не забыть убрать деление частоты на 8 (CKDIV8=1), если я не ошибаюсь, в attiny2313 делитель установлен по дефолту, повторюсь, его нужно отключить (установить единицу). Кварц на 12MHz — не единственный возможный вариант, о возможных вариантах почитайте внизу этой страницы. С таким малым количеством памяти мне стоило выбрать 16MHz, но я решил остановиться на 12MHz.

Первым делом необходимо воспользоваться шаблонным кодом USB библиотеки. Папка usbdrv библиотеки должна быть в папке проекта.

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <avr/wdt.h>
#include <util/delay.h>

#include "usbdrv/usbconfig.h"
#include "usbdrv/usbdrv.h"

PROGMEM const char usbHidReportDescriptor[22] = {
    0x06, 0x00, 0xff,
    0x09, 0x01,
    0xa1, 0x01,
    0x15, 0x00,
    0x26, 0xff, 0x00,
    0x75, 0x08,
    0x95, sizeof(uchar),
    0x09, 0x00,
    0xb2, 0x02, 0x01,
    0xc0
};

int main(void) {
    wdt_enable(WDTO_1S);
    usbInit();
    usbDeviceDisconnect();

    for(uchar i = 0; i<250; i++) { // wait 500 ms
        wdt_reset();
        _delay_ms(2);
    }

    usbDeviceConnect();

    sei();

    while(1) {
        wdt_reset();
        usbPoll();
        _delay_us(100);
    }

    return 0;
}

Этот код необходим для работы USB библиотеки. Кроме этого кода необходимо так же настроить несколько параметров, для этого нужно файл usbconfig-prototype.h в папке usbdrv переименовать в usbconfig.h и подправить необходимые параметры, в моем случае — это:

#define USB_CFG_IOPORTNAME      D
#define USB_CFG_DMINUS_BIT      3
#define USB_CFG_DPLUS_BIT       2
#define USB_CFG_INTERFACE_CLASS     3
#define USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH    22

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

Метод, обрабатывающий пришедшие данные, выглядит так:

#define PORT_D_THRESHOLD 40 // с какого числа начинать использование порта D
#define SERIALIZE_DEVIDER 10 // 51 / 10 = пин 5 и моргание 1
#define PLAY_ON_STATUS 30 // 30 - это горящий красный, начать воспроизведение мелодии

volatile uint8_t *blinkPort = &PORTB;
uint8_t pin;
uint8_t isBlink;
uint8_t play;

USB_PUBLIC uchar usbFunctionSetup(uchar data[8]) {
    usbRequest_t *rq = (void *)data; // cast data to correct type
    uint8_t status = rq->wValue.bytes[0]; //40/41 - 20/21 - 30/31 (возможные значения)
    // пишу 0 во все используемые мною пины очищая предыдущие состояния
    // чтобы избежать одновременного свечения нескольких светодиодов
    PORTD &=~ ((1 << PD5) | (1 << PD4));
    PORTB &=~ ((1 << PD2) | (1 << PD3));

    // если в сообщении пины 4 или 5, то работать будем с портом D
    // иначе с портом B
    if(status < PORT_D_THRESHOLD) {
        blinkPort = &PORTB;
    } else {
         blinkPort = &PORTD;
    }

    // десериализация 
    isBlink = status % SERIALIZE_DEVIDER;
    pin = status / SERIALIZE_DEVIDER;
    play = status % PLAY_ON_STATUS == 0;

    return 0;
}

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

Чтение данных происходит из значения wValue, не самый правильный вариант передачи данных, но я посчитал его вполне уместным в моем случае.

В моей схеме для зеленого сигнала светофора используется порт D, а для желтого и красного порт B, поэтому мне пришлось объявить указатель на используемый порт и добавить условие для использования порта D, если пришел сигнал об успешном билде. Если для всех сигналов использовать разные пины одного порта, то от этого условия можно отказаться.

Метод main():

Кроме инициализации USB библиотеки необходимо настроить порты на output, в моем случае нельзя настраивать весь порт D, т.к. порт D будет использоваться USB библиотекой. Я выставил единички только на пинах, которые буду использовать:

DDRD = 0x30; //пины 5 и 4 порта D
DDRB = 0xC; //пины 2 и 3 порта B
Main loop:

Главный цикл начинается с необходимых для работы USB:

wdt_reset();
usbPoll();

Дальше идет логика, отвечающая за свечение/моргание одного из светодиодов:

blinkDelay++;
if(blinkDelay > BLINK_DELAY) {
    blinkDelay = -BLINK_DELAY;
}

if(isBlink == 0) {
    *blinkPort |= (1 << pin);
} else {
    if(blinkDelay == 0) {
        *blinkPort ^= (1 << pin);
    }
}

Если флаг моргания не установлен, то просто пишется единица в необходимый бит

*blinkPort |= (1 << pin)

. Иначе необходимый бит инвертируется

*blinkPort ^= (1 << pin)

, таким образом получается моргание светодиодом. Задержка в цикле всего 100us, поэтому для более редкого моргания добавлена искусственная задержка в виде переменной-счетчика blinkDelay.

Дальше идет блок отвечающий, за воспроизведение мелодии:

if(play == 1) {
    if(soundDelay < duration) {
        if(soundDelay % frequency <= FREQUECY_EDGE) {
            PORTD ^= (1 << PD5);
        }
    } else {
        soundDelay = 0;
        if(note == NOTES_COUNT) {
            note = 0;
            play = 0;
        }

        duration = pgm_read_word_near(durations + note) * DURATION_MULTIPLIER;
        frequency = FREQUENCY_DEV / pgm_read_word_near(frequences + note);

        note++;
    }

    soundDelay++;
}

Из публикации «Музыкальный дверной звонок в стиле Star Wars на Arduino» были взяты только массивы частот и длина нот, как я уже упоминал, памяти оставалось очень мало по этому процесс воспроизведения мелодии интегрирован в общий цикл, чтобы не нарушить работу USB библиотеки. Я решил не делать кастомных задержек для задания нужной частоты и длинны ноты.

Попытаюсь описать процесс как можно детальнее. Для того, чтобы подать на динамик звук нужной тональности и длительности, необходимо переключать состояние пина с частотой заданной ноты и делать это на протяжении времени длительности заданной ноты. У меня этот процесс происходит в основном цикле и выполняется параллельно остальным задачам. Для этого я считаю длительность текущей ноты и ее частоту (константы здесь зависят от задержки в конце главного цикла и были подобраны вручную):

duration = pgm_read_word_near(durations + note) * DURATION_MULTIPLIER;
frequency = FREQUENCY_DEV / pgm_read_word_near(frequences + note);

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

Софт

После того, как схема собрана, а микроконтроллер прошит (как и чем прошивать ищите в интернетах вашего города), можно перейти к компьютерному софту.

Вариантов здесь предостаточно. Для работы с DIY USB большой популярностью пользуется виртуальный COM порт, та же Arduino создает виртуальный COM порт, работать с которым довольно просто, с ним можно работать даже обычным bat файлом. Но мне очень хотелось попробовать работу именно с USB, поэтому моя прошивка настроена как HID устройство, и софт должен уметь работать именно с USB.

Пишем в USB

После небольшого расследования оказалось, что один из самых простых вариантов — это пакет USB для Node.js. Для того, чтобы отправить сигнал об упавшем билде, достаточно написать 4 строчки:

var usb = require('usb');
var device = usb.findByIds(5824, 1500); // VID и PID нашего устройства (эти стоят по умолчанию в V-USB конфиге)
device.open();
device.controlTransfer(usb.LIBUSB_REQUEST_TYPE_RESERVED, usb.LIBUSB_TRANSFER_TYPE_CONTROL, 30, 0, new Buffer(0), function(error, data){});

Здесь число 30 отсылается параметром wValue на устройство, а то в свою очередь подаст напряжение на третий пин порта B и начнет проигрывание мелодии.

Узнаем статус джобы у Jenkins

С опросом Jenkins у Node.js тоже никаких проблем, подключаем пакет jenkins-api и пишем:

var jenkinsapi = require('jenkins-api');
var JOB_NAME = 'JOB';
var JENKINS_URL = 'URL';
var jenkins = jenkinsapi.init(JENKINS_URL);

jenkins.last_build_info(JOB_NAME, function(err, data){
    console.log('Статус билда', data.result);
});

В консоли увидим: SUCCESS, FAILURE или null (в процессе сборки).

Драйвер для Windows

Для работы с USB модулем необходимо установить драйвер libUSB, это можно сделать либо вручную, либо воспользоваться программой zadig, взятой со страницы пакета USB из пункта Installation; за подробностями — туда, там же инструкции для Linux и OSX.

Всё вместе

Короткое видео:

Полные исходники здесь.

Спасибо за внимание.

Автор: Galiaf47

Источник

* - обязательные к заполнению поля