Arduino:Основы/Интерпретация последовательно передаваемых данных

Материал из Онлайн справочника
Перейти к навигацииПерейти к поиску

Перевод: Максим Кузьмин
Проверка/Оформление/Редактирование: Мякишев Е.А.


Интерпретация последовательно передаваемых данных [1]

Последовательно передаваемые данные – это данные, которые передаются байт за байтом от одного устройства к другому. И то, как оба устройства (компьютер или микроконтроллер) будут интерпретировать эти данные, когда будет начало сообщения, когда будет его конец, и что делать с этими данными в процессе передачи из точки А в точку Б – решать именно вам.

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

Для отображения буквенно-цифровых символов (букв, чисел и знаков препинания) компьютеры используют числа, содержащиеся в байтах. Существует стандартная кодировка под названием ASCII (расшифровывается как American Standard Code for Information Interchange, т.е. «американский стандартный код для обмена информацией»), которая присваивает каждой букве и цифре значение от 0 до 255. Например, заглавной «А» соответствует значение «65». Эту таблицу можно найти во многих мануалах к компьютерам, а также онлайн – например, здесь.

ASCII – это очень распространенная кодировка (хотя и не единственная), поэтому очень многие девайсы используют ее в своем последовательном протоколе. Кроме того, как можно судить из названия, ASCII во многом основана на английском коммуникационном процессе. Она удобна, т.к. любой ASCII-символ можно представить одним байтом, но в то же время имеет и ограничения. Другие буквенно-цифровые системы, которые имеют больше символов, чем в латинице, в ASCII представить нельзя. ASCII можно заменить Unicode, но, к счастью, самая распространенная форма Unicode (т.е. UTF-8) совместима с ASCII. Если хотите почитать о Unicode более подробно, предлагаю взглянуть на этот информативный пост от Умера Мэнсора (Umer Mansoor).

Если вы передаете одно число, значение которого меньше 255, то его можно уместить в байт. Этот вид сообщений довольно прост. Вы можете отсылать один и тот же байт снова и снова, и у компьютера не возникнет никаких проблем, чтобы его получить. Но если отсылать больше (как это обычно и бывает), все становится чуть сложнее – компьютер должен знать, где у получаемого сообщения начало и конец. Разные устройства, выполняя те или иные действия, используют разные коды. К примеру, если устройство вроде кассетного магнитофона, проигрывателя CD-дисков и т.д. управляется при помощи последовательной передачи данных, в руководстве к ним всегда будет раздел, в котором описываются сообщения, которые он ожидает получить, а также скорость получения этих сообщений.

При работе с платой Arduino преобразование исходного значения байта в итоговый ASCII-символ осуществляется при помощи модификатора DEC. Например, так:

Serial.print(myVar, DEC);

То есть, если значением myVar будет 134, функция вернет три байта, содержащие символы «1», «3» и «4».

Допустим, вы должным образом запрограммировали микроконтроллер, подключили его к компьютеру и хотите приступить к передаче каких-нибудь данных. Но для начала нужно протестировать, что именно отсылает микроконтроллер, и для этого подойдет самая простейшая программа. На Mac с этой задачей хорошо справляется ZTerm, а на PC – HyperTerminal. Задайте те же скорость передачи данных и другие настройки, что и на микроконтроллере, и подключайтесь. Если в настройках будет вариант Flow Control (т.е. «управление потоком передачи данных»), поставьте None. В итоге вы должны увидеть все отправленные вами ASCII-символы. Самая большая трудность в последовательной передаче данных – сделать так, чтобы в итоге у вас были все необходимые байты, и чтобы все они были расположены в правильном порядке. Если компьютер (или микроконтроллер) во время приема данных от последовательного порта занят чем-то еще или его буфер переполнен (т.е. если получаете больше данных, чем компьютер способен обработать), то некоторые данные вы рискуете потерять. Нет какого-то одного способа проверить, правильные ли данные вы получаете, но есть несколько фундаментальных вещей, которые стоит иметь в виду:

Сколько байтов я отправляю? А получаю столько же?

Например, если микроконтроллер отсылает три переменные от трех датчиков, компьютер должен получить все три.

Я получаю байты в правильном порядке?

Если отправитель раз за разом отсылает «ABC» (так, что в итоге получается «ABCABCABCABC»), может случиться так, что получатель начнет получать эти сообщения не с самого начала (и получит в итоге что-то вроде «BCABCABCABCA»). Это может стать проблемой, если, к примеру, «A» отвечает за правый переключатель, «B» – за центральный, а «С» – за левый. Чтобы избежать этого, имеет смысл в начале или конце вашей строки данных вписать константу, которая будет отличаться от других значений. Например, если за символ «A» отвечает число от 0 до 100, и за «В» отвечает число от 0 до 100, и также за «С» отвечает число от 0 до 100, то вначале каждой строки полезно будет вписать значение 101. В программе на BASIC это может выглядеть следующим образом:

PICBasic Pro:

 A var byte
 B var Byte
 C var Byte
 headerByte var byte
headerbyte = 101
main:
	  ' Здесь генерируем значения для A, B и C
   serout2 portc.6, 16468, [headerByte, A, B, C]
goto main

BX-24:

Dim A as byte
Dim B as Byte
Dim C as Byte
dim headerByte as byte
headerbyte = 101
' Здесь пишем код, чтобы задать последовательный порт
do
	' Здесь генерируем значения для A, B и C
call putQueue(OutputBuffer, headerByte, 1)
call putQueue(OutputBuffer, A, 1)
call putQueue(OutputBuffer, B, 1)
call putQueue(OutputBuffer, C, 1)
loop

Wiring/Arduino:

char A;
char B;
char C;
char headerByte = 101;
void setup() {
  Serial.begin(9600);
}
void loop() {
  // Здесь генерируем значения для A, B и C
  Serial.print(A, BYTE);
  Serial.print(B, BYTE);
  Serial.print(C, BYTE);
  Serial.print(headerByte, BYTE);
}

Но эту задачу можно выполнить и по-другому – методом «вызова и ответа». То есть девайс-отправитель ждет запроса на отправку данных, затем дожидается и отправляет, а затем ждет нового запроса. Таким образом, девайс-получатель будет знать, что в его буфере одновременно будет находиться лишь одна строка данных. То есть вам нужно сделать так, чтобы последовательный порт девайса-получателя мог уместить то количество данных, которые девайс-отправитель отсылает в своей строке, а так же так, чтобы девайс-отправитель умел ждать и отправлял данные только после того, как получит байт-запрос. Если в качестве отправителя выступает микроконтроллер, его можно запрограммировать следующим образом (см. ниже). В этом примере получатель, будучи готовым к получению новых данных, шлет отправителю запрос – букву «А», за которую в кодировке ASCII отвечает число «65».

PicBasic Pro:

main:
    ' Ждем входящих данных:
    serin2 portc.7, 16468, [inputVar]
    ' Если мы получили от PC байт со значением «65» (ASCII-значение для «А»),
    ' отсылаем данные:
    if inputVar = 65 then
      serout2 portc.6, 16468, [A, B, C]
    endif
goto main

BX-24:

' Выясняем, есть ли что-нибудь в очереди:
gotaByte = statusQueue(inputBuffer)
' Если в буфере есть данные, берем первый байт:
if (gotaByte = true) then
    call getQueue(inputBuffer, inByte, 1)
        if inByte = 65 then
            ' Высылаем байты:
            call putQueue(OutputBuffer, A, 1)
            call putQueue(OutputBuffer, B, 1)
            call putQueue(OutputBuffer, C, 1)
        end if
else
    inByte = 0
end If

Arduino:

// Выясняем, есть ли что-нибудь в очереди:
if (Serial.available() > 0) {
// Если в буфере есть данные, берем первый байт:
char inByte = Serial.read();
        if (inByte == 'A') {
            // Высылаем байты:
            Serial.print(A, BYTE);
            Serial.print(B, BYTE);
            Serial.print(C, BYTE);
        }
}

Мои данные являются частью какой-то более крупной переменной?

Допустим, вы отсылаете от BX-24 целочисленную переменную. Длина этой переменной – два байта, поэтому компьютер при получении этой переменной как раз получит два этих байта. Чтобы снова преобразовать эти два байта в одно число, воспользуйтесь этой формулой:

integerVar = (byte1Var * 256) + byte2Var

Если непонятно, представьте, что конвертируете шестнадцатеричное значение в десятичное. В данном же случае каждый байт – это число с целочисленным основанием 256. Чтобы убедиться в том, что формула верна, попробуйте отослать константу с числом больше 256.

Мои данные искажаются при передаче?

Последовательная передача данных – штука непростая в том смысле, что оба девайса нужно правильно заземлить, а провода для отправки и передачи данных должны быть правильно изолированы и подключены. Разного рода электрический шум может проникнуть в вашу систему и помешать передаче данных. Чтобы минимизировать время на устранение неполадок, лучше начать с отправки сообщения с константой. К примеру, если вы планируете отослать три байта с данными от датчиков, не стоит начинать с отправки значений, действительно полученных от датчиков. Начните с констант.

PicBasic Pro:

outByte var byte
outByte = 65
serout2 portc.6, 16468, [outByte]
outByte = 66
serout2 portc.6, 16468, [outByte]
outByte = 67
serout2 portc.6, 16468, [outByte]

BX-24:

dim outByte as byte
outByte = 65
call putQueue(OutputBuffer, outByte, 1)
outByte = 66
call putQueue(OutputBuffer, outByte, 1)
outByte = 67
call putQueue(OutputBuffer, outByte, 1)

Arduino:

Serial.print(65, BYTE);
Serial.print(66, BYTE);
Serial.print(67, BYTE);

Если вы видите, что девайсу-получателю приходят именно те данные, которые вы отсылаете, то код можно поменять, чтобы вы могли отсылать уже не константы, а данные, действительно полученные от датчиков. Кроме того, перед тем, как заниматься отладкой «железной» части, которая, как правило, богата на всевозможные неисправности и неполадки, займитесь софтверной. Код – гораздо более контролируемая область, чем «железо».

См.также

Внешние ссылки