воскресенье, 9 февраля 2014 г.

Управление светодиодами с компьютера

В этой статье рассматриваются основы организации взаимодействия компьютера и ардуино. Возможность управления объектами реального мира с компьютера очень интересна. Это позволяет делать невероятные вещи. Ограниченность ресурсов микроконтроллера больше не проблема. Сложную и ресурсоемкую часть задачи теперь можно переложить на ПК. Микроконтроллеру останется лишь управлять исполнительными механизмами.


Для изучения сформулированного в самом начале вопроса достаточно одной arduino uno. Но это не правильно :-) Чтобы было интереснее стоит добавить светодиодиков. Я подключил шесть светодиодов на цифровые выводы с  8 по 13.


Сразу приведу скетч для arduino uno

////////////////////////
//
// Arduino UNO
//
////////////////////////
//
// Sketch: PC-duino-led
//

const unsigned char LED1 = 8;
const unsigned char LED2 = 9;
const unsigned char LED3 = 10;
const unsigned char LED4 = 11;
const unsigned char LED5 = 12;
const unsigned char LED6 = 13;

int data = 0;

void setup()
{
    pinMode(LED1, OUTPUT);
    pinMode(LED2, OUTPUT);
    pinMode(LED3, OUTPUT);
    pinMode(LED4, OUTPUT);
    pinMode(LED5, OUTPUT);
    pinMode(LED6, OUTPUT);

    Serial.begin(9600);
}

void loop()
{
    while (Serial.available() == 0) ;
    data = Serial.read();
    Serial.write(data);

    if ( data == 'a' ) digitalWrite(LED1, HIGH);
    if ( data == 'b' ) digitalWrite(LED1, LOW);

    if ( data == 'c' ) digitalWrite(LED2, HIGH);
    if ( data == 'd' ) digitalWrite(LED2, LOW);

    if ( data == 'e' ) digitalWrite(LED3, HIGH);
    if ( data == 'f' ) digitalWrite(LED3, LOW);

    if ( data == 'g' ) digitalWrite(LED4, HIGH);
    if ( data == 'h' ) digitalWrite(LED4, LOW);

    if ( data == 'i' ) digitalWrite(LED5, HIGH);
    if ( data == 'j' ) digitalWrite(LED5, LOW);

    if ( data == 'k' ) digitalWrite(LED6, HIGH);
    if ( data == 'l' ) digitalWrite(LED6, LOW);
}

//
// End
//
////////////////////////

После загрузки скетча в ардуино, необходимо открыть монитор порта (Ctrl + Shift + M).

Управление светодиодами осуществляется посредством передачи маленьких латинских букв в ардуино. Отправив 'a' можно зажечь LED1. А отправив 'b' можно погасить LED1. В расположенной ниже табличке все написано.

a LED1 ON
b LED1 OFF
c LED2 ON
d LED2 OFF
e LED3 ON
f LED3 OFF
g LED4 ON
h LED4 OFF
i LED5 ON
j LED5 OFF
k LED6 ON
l LED6 OFF

Если не работает, то следует проверить скорость передачи данных. Должно быть 9600 bps.

Это было самое интересное в статье :-) Оставшаяся часть статьи будет посвящена подробному рассказу об использованных в программе функциях Serial.

Исходный код всех этих функций можно посмотреть в файле HardwareSerial.cpp

Начну с описания функции begin. Эта функция выполняет настройку USART (Universal Synchronous and Asynchronous serial Receiver and Transmitter).

Функции begin могут быть переданы два параметра. Первым параметром должна быть скорость передачи данных. Единицей измерения этой величины является bps (bits per second). По русски это означает число бит передаваемых за секунду. Наиболее часто используется 2400, 4800, 9600, 14400, 19200, 28800, 38400, 57600, 115200 bps. Однако, при необходимости можно передавать информацию с иной скоростью. Главное, чтобы устройства обменивающиеся данными, делали это с одинаковой скоростью.

Из за особенностей микроконтроллеров AVR, при передаче данных через USART могут возникать ошибки. Процент ошибок зависит от тактовой частоты микроконтроллера, настроек USART, скорости передачи данных. В datasheet конкретного микроконтроллера есть таблица ошибок. Ниже приводится фрагмент такой таблички для ATmega328 работающей на частоте 16MHz (Arduino Uno).

Baud Rate (bps) Error (U2Xn=0) Error (U2Xn=1)
2400 -0.1 % 0.0 %
4800 0.2 % -0.1 %
9600 0.2 % 0.2 %
14400 0.6 % -0.1 %
19200 0.2 % 0.2 %
28800 -0.8 % 0.6 %
38400 0.2 % 0.2 %
57600 2.1 % -0.8 %
76800 0.2 % 0.2 %
115200 -3.5 % 2.1 %
230400 8.5 % -3.5 %
250000 0.0 % 0.0 %
500000 0.0 % 0.0 %
1000000 0.0 % 0.0 %

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

Второй параметр функции begin не является обязательным. Это значит, что функцию можно вызвать без него.

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

SERIAL_5N1 5 бит данных, нет бита паритета, 1 стоп бит
SERIAL_6N1 6 бит данных, нет бита паритета, 1 стоп бит
SERIAL_7N1 7 бит данных, нет бита паритета, 1 стоп бит
SERIAL_8N1 8 бит данных, нет бита паритета, 1 стоп бит
SERIAL_5N2 5 бит данных, нет бита паритета, 2 стоп бита
SERIAL_6N2 6 бит данных, нет бита паритета, 2 стоп бита
SERIAL_7N2 7 бит данных, нет бита паритета, 2 стоп бита
SERIAL_8N2 8 бит данных, нет бита паритета, 2 стоп бита
SERIAL_5E1 5 бит данных, бит паритета (контроль четности), 1 стоп бит
SERIAL_6E1 6 бит данных, бит паритета (контроль четности), 1 стоп бит
SERIAL_7E1 7 бит данных, бит паритета (контроль четности), 1 стоп бит
SERIAL_8E1 8 бит данных, бит паритета (контроль четности), 1 стоп бит
SERIAL_5E2 5 бит данных, бит паритета (контроль четности), 2 стоп бита
SERIAL_6E2 6 бит данных, бит паритета (контроль четности), 2 стоп бита
SERIAL_7E2 7 бит данных, бит паритета (контроль четности), 2 стоп бита
SERIAL_8E2 8 бит данных, бит паритета (контроль четности), 2 стоп бита
SERIAL_5O1 5 бит данных, бит паритета (контроль нечетности), 1 стоп бит
SERIAL_6O1 6 бит данных, бит паритета (контроль нечетности), 1 стоп бит
SERIAL_7O1 7 бит данных, бит паритета (контроль нечетности), 1 стоп бит
SERIAL_8O1 8 бит данных, бит паритета (контроль нечетности), 1 стоп бит
SERIAL_5O2 5 бит данных, бит паритета (контроль нечетности), 2 стоп бита
SERIAL_6O2 6 бит данных, бит паритета (контроль нечетности), 2 стоп бита
SERIAL_7O2 7 бит данных, бит паритета (контроль нечетности), 2 стоп бита
SERIAL_8O2 8 бит данных, бит паритета (контроль нечетности), 2 стоп бита

Вызов функции begin без второго параметра эквивалентен вызову этой функции с параметром SERIAL_8N1.

Что конкретно скрывается за этими константами можно узнать из файла HardwareSerial.h

#define SERIAL_5N1 0x00 // 0b00000000
#define SERIAL_6N1 0x02 // 0b00000010
#define SERIAL_7N1 0x04 // 0b00000100
#define SERIAL_8N1 0x06 // 0b00000110
#define SERIAL_5N2 0x08 // 0b00001000
#define SERIAL_6N2 0x0A // 0b00001010
#define SERIAL_7N2 0x0C // 0b00001100
#define SERIAL_8N2 0x0E // 0b00001110
#define SERIAL_5E1 0x20 // 0b00100000
#define SERIAL_6E1 0x22 // 0b00100010
#define SERIAL_7E1 0x24 // 0b00100100
#define SERIAL_8E1 0x26 // 0b00100110
#define SERIAL_5E2 0x28 // 0b00101000
#define SERIAL_6E2 0x2A // 0b00101010
#define SERIAL_7E2 0x2C // 0b00101100
#define SERIAL_8E2 0x2E // 0b00101110
#define SERIAL_5O1 0x30 // 0b00110000
#define SERIAL_6O1 0x32 // 0b00110010
#define SERIAL_7O1 0x34 // 0b00110100
#define SERIAL_8O1 0x36 // 0b00110110
#define SERIAL_5O2 0x38 // 0b00111000
#define SERIAL_6O2 0x3A // 0b00111010
#define SERIAL_7O2 0x3C // 0b00111100
#define SERIAL_8O2 0x3E // 0b00111110

В функции begin истинное значение этих констант записывается в регистр UCSR0C (ATmega328).

Но, лучше я напишу отдельную статью про регистры USART. А сейчас продолжу описание инструментов Arduino IDE предназначенных для обмена данными через USART.

Прием и передача данных, стандартными средствами ардуино, организуется через прерывания. Полученные данные складываются обработчиком прерываний по приему данных USART в буфер приема. Непосредственную передачу данных выполняет обработчик прерываний по завершению передачи данных USART. Он берет данные из буфера передачи.

Буферы представляют собой обычные массивы типа unsigned char. Для Arduino Uno (ATmega328) создаются массивы состоящие из 64 элементов.

Не смотря на значительный размер буфера приема, данные нужно своевременно считывать из него. Если в буфере не будет свободного места, то новые данные будут потеряны.

При передаче данных также нужно учитывать размер буфера. Если объем передаваемых данных будет больше буфера передачи, то может произойти незапланированная задержка. Она будет длиться пока все данные не будут перемещены в буфер.

Функция available позволяет узнать были ли приняты какие либо данные через USART. Параметров у этой функции нет. Передавать ей ничего не нужно. Функция available возвращает 0 если в буфере приема нет непрочитанных данных. Тип возвращаемых функцией данных - signed int.

Функция read читает данные из буфера приема и возвращает их. Если в буфере нет непрочитанных данных, то эта функция вернет -1. Тип возвращаемых данных - signed int. У этой функции тоже нет параметров.

Функция write помещает данные, которые должны быть переданы через USART, в буфер передачи. Эти данные и являются единственным параметром функции. Наиболее подходящим типом для этих данных является unsigned char. Данные иного типа, в конечном счете, приводятся именно к unsigned char. Функция также возвращает количество записанных в буфер байтов. Однако, эту информацию можно и проигнорировать.