В предыдущей заметке был описан вольтметр постоянного тока. Очень полезно управлять прибором не только с помощью кнопки, но с помощью интерфейса, хотя бы и простейшего текстового. Для этого рассмотрим работу с имеющимся у Arduino последовательным портом.
Следует заметить, что это не полноценный RS232, а конвертер RS232->USB. Полноценный RS232 можно организовать с помощью дополнительных элементов, используя сигналы RXD и TXD, но об этом как-нибудь в другой раз.
Настройка порта очень проста, и заключается в установке скорости передачи и таймаута ожидания приема строки символов. Для этого введем в ранее описанную программу вольтметра функцию void SetSerial().
// Подпрограмма настройки последовательного порта
// ==============================================
void SetSerial() {
Serial.begin(38400); // скорость основного порта
Serial.setTimeout(2); // таймаут ожидания приема строки символов в мс
}
И изменим функцию PrintTitle(), добавив в нее несколько строк для вывода заголовка программы в последовательный порт. Скорость передачи 38400 выбрана только для примера.
// Подпрограмма печати заголовка программы
// =======================================
void PrintTitle() {
lcd.begin(16, 2);
lcd.print(TITLE); // печатаем первую строку на дисплее
lcd.setCursor(0, 1);
sprintf(adc, "Version %s", Ver);
lcd.print(adc); // печатаем вторую строку на дисплее
// печатаем в последовательный порт
Serial.println(); // новая строка
Serial.println(TITLE); // печатаем первую строку на терминал
Serial.println(adc); // печатаем вторую строку на терминал
Serial.print("> "); // приглашение для ввода
SER_status = SER_WAITCOMMAND;
}
Дополнительный оператор вывода Serial.println(); обеспечивает вывод заголовка с новой строки. Как это выглядит на экране Putty показано на рис. 1.
Полный текст программы приведен по ссылке.
Однако необходимо обеспечить не только вывод из контроллера, но и управление режимами работы, индикацией и проч. Первое, что проще всего выполнить — просмотр и изменение некоторых параметров работы вольтметра.
Для работы с портом разработаем функцию SerialWork(), которая будет обрабатывать ввод/вывод. Чтобы можно было применить оператор switch, введем слово состояния клавиатуры SER_status, по содержимому которого и будет производиться переключение.
// Подпрограмма работы с последовательным портом
// =============================================
void SerialWork() {
switch (SER_status) {
// ожидание ввода команды
case (SER_WAITCOMMAND): {
if(Serial.available() > 0) {
if(Serial.readBytes(ser, 3) == 1) {
if(ser[0] >= '0' && ser[0] <= '9') {
Serial.write(ser[0]);
SER_status = SER_GETCOMMAND; }
else SerPrintHelp(); } }
break; }
// обработка команды
case (SER_GETCOMMAND): {
switch (ser[0]) {
// команда вывода настроек
case ('1'): {
SerPrintSetup();
break; }
// команда вывода измеряемых величин
case ('2'): {
SER_status = SER_PRINTDATA;
break; }
default: {
Serial.print("\r\n> ");
SER_status = SER_WAITCOMMAND;
break; } }
break; }
case (SER_PRINTDATA): {
if((SYS_status & SYS_SERPRINT) != 0) {
SerPrintData();
SYS_status &= ~SYS_SERPRINT; }
if(Serial.available() > 0) {
if(Serial.readBytes(ser, 3) == 1) {
if (ser[0] == 0x1b) {
Serial.print("\r\n> ");
SYS_status &= ~SYS_SERTITLE;
SER_status = SER_WAITCOMMAND; } } }
break; }
default: {
break; } }
Для того, чтобы выводимые данные не "мигали", в слове состояния программу SYS_status введен еще один бит SYS_SERPRINT. Этот бит устанавливается прерыванием сторожевого таймера, а сбрасывается после вывода данных в порт. Если введен ошибочный символ, то выводится короткий Help. Полный текст программы приведен отдельно по ссылке, а результат работы программы — на рис. 2.
Ввод с последовательного порта производится функцией Serial.readBytes, а не Serial.read. Связано это с тем, что ряд клавиш (наприм., функциональные, и т. п.) выдает не один, а три кода клавиши. Поэтому и ожидается прием 3 символов, а время ожидания задается функцией Serial.setTimeout в процессе настройки последовательного порта SetSerial().
Теперь попробуем изменять параметры вольтметра.
Введем в Help еще одну строчку и затем добавим в функцию SerialWork() соответствующий раздел. Настроить можно следующие параметры:
1. Изменение времени ожидания окончания дребезга кнопки.
2. Калибровка вольтметра отдельно для Vref 1,1 и 5 вольт.
Сначала проведем настройку таймаута дребезга. Текст измененной функции SerialWork() и функции приема символа SerReadByte() приведен ниже:
// Подпрограмма работы с последовательным портом
// =============================================
void SerialWork() {
switch (SER_status) {
// ожидание ввода команды
case (SER_WAITCOMMAND): {
if(Serial.available() > 0) {
if(Serial.readBytes(ser, 3) == 1) {
if(ser[0] >= '0' && ser[0] <= '9') {
Serial.write(ser[0]);
SER_status = SER_GETCOMMAND; }
else SerPrintHelp(); } }
break; }
// обработка команды
case (SER_GETCOMMAND): {
// команда вывода настроек
switch (ser[0]) {
case ('1'): {
SerPrintSetup();
break; }
case ('2'): {
SER_status = SER_PRINTDATA;
break; }
case ('3'): {
SER_status = SER_CONFIG;
Serial.println();
Serial.println("1 - Key timeout");
Serial.println("2 - Correct Vref = 1.1 V");
Serial.println("3 - Correct Vref = 5 V");
Serial.println("ESC - Return to Command Mode");
Serial.print("CFG>");
break; }
default: {
Serial.print("\r\n> ");
SER_status = SER_WAITCOMMAND;
break; } }
break; }
case (SER_PRINTDATA): {
if((SYS_status & SYS_SERPRINT) != 0) {
SerPrintData();
SYS_status &= ~SYS_SERPRINT; }
if(Serial.available() > 0) {
if(Serial.readBytes(ser, 3) == 1) {
if (ser[0] == 0x1b) {
Serial.print("\r\n> ");
SYS_status &= ~SYS_SERTITLE;
SER_status = SER_WAITCOMMAND; } } }
break; }
case (SER_CONFIG): {
if (SerReadByte() != 0) {
switch (ser[0]) {
case (0x1b): {
Serial.print("\r\n> ");
SER_status = SER_WAITCOMMAND;
break; }
case ('1'): {
SER_data = 0;
Serial.write(ser[0]);
Serial.println("\n\rESC - Return to Old Value");
sprintf(adc, "Kkey [%u] (1-255) = ", KEY_delay);
Serial.print(adc);
SER_status = SER_CONFIGTIMEOUT;
break; }
default: {
break; } } }
break; }
case (SER_CONFIGTIMEOUT): {
if (SerReadByte() != 0) {
switch (ser[0]) {
case (0x1b): {
Serial.print("\r\nCFG> ");
SER_status = SER_CONFIG;
break; }
case (0xd): {
if((SER_data > 255) || (SER_data == 0)) {
Serial.print("\r\nError!"); }
else {
KEY_delay = SER_data;
cli();
SetTimer2(KEY_delay);
sei(); }
Serial.print("\r\nCFG> ");
SER_status = SER_CONFIG;
break; }
default: {
if(ser[0] >= '0' && ser[0] <= '9') {
Serial.write(ser[0]);
SER_data = SER_data * 10 + (ser[0] - 0x30); }
break; } } }
break; }
default: {
break; } } }
// Подпрограмма приема символа
// с последовательного порта
// ===========================
int SerReadByte() {
if(Serial.available() > 0) {
if(Serial.readBytes(ser, 3) == 1) {
return ser[0]; } }
return 0;
}
Полный текст программы приведен отдельно по ссылке, а результат работы — на рис. 3.
Однако есть одна неприятность. Вновь установленное значение пропадет при выключении контроллера, и после включения питания будет вновь восстановлена исходная величина таймаута. Следовательно введенное значение надо сохранить в энергонезависимой памяти и восстанавливать его при включении питания. Поэтому займемся работой с EEPROM контроллера.
Первым делом необходимо включить в программу заголовочный файл для работы с EEPROM:
#include <EEPROM.h>
В программу вводится функция начальной установки EEPROM , в которую при первом включении заносятся значения параметров по умолчанию.
// Подпрограмма обращения с EEPROM
// ===============================
void SetEEPROM() {
// проверка на инициализацию EEPROM
// по нулевому байту
if (EEPROM.read(0) == 0x55) {
// EEPROM уже инициализирована
// считываем данные
EEPROM.get(EEPROM_T2_DELAY, KEY_delay);
EEPROM.get(EEPROM_K11, ADC_K11);
EEPROM.get(EEPROM_K5, ADC_K5); }
// инициализация EEPROM
else {
EEPROM.write(0, 0x55);
EEPROM.put(EEPROM_T2_DELAY, KEY_delay);
EEPROM.put(EEPROM_K11, ADC_K11);
EEPROM.put(EEPROM_K5, ADC_K5); } }
Дело в том, что начальное состоян ие ячеек EEPROM равно "1". Поэтому если содержимое нулевого байта не равно значению 0x55, то считается, что EEPROM требуется инициальзировать, т. е. записать значения параметров по умолчанию, а в нулевой байт записать 0x55. Соответственно, при дальнейших включениях контроллера значения параметров будут считываться при инициальзации и использоваться при настройке и расчетах.
В версии программы 05.023с вводится не время задержки, а значение счетчика таймера OCR2A, который используется для сравнения. Это не совсем удобно, поэтому немного изменим программу, чтобы вводить время ожидания окончания дребезга непосредственно в микросекундах. Введем дополнительную ячейку память KEY_divider, чтобы в будущем иметь возможность менять время задержки.
Текст этого варианта программы приведен по ссылке, а полученный результат — на рис. 4.
Видно, что при повторном обращении к настройке параметра время задержки не совсем соответствует введенному нами значению. Это связано с тем, что 1 единица счетчика соответствует 64 мксек. Следовательно введенное значение округляется до ближайшей возможной комбинации счетчика, каковая дробной быть не может. Однако в нашем случае этим отклонением можно пренебречь.
Последовательный порт ограничивает радиус общения с контроллером допустимой длиной провода. Для работы откуда угодно (точнее — отттуда, где есть сотовая связь и мобильный интернет) можно попробовать использовать GSM-модем.
Возьмем самый простой модем NEOWAY M590. Для его подключения требуется организовать еще один последовательный порт. Схема подключения приведена на рис. 5. Модемы встречаются в разных вариантах исполнения. Тот, который будет использован имеет двухрядный 20-контактный разъем, в котором будем использовать несколько четных контактов. Второй ряд нечетных контактов для простоты не показан.
Для питания рекомендуется использовать источник с напряжением не выше 5 вольт. Потребляемый модемом ток может достигать (как пишут) 2 А, поэтому возможно потребуется несколько более мощный источник, чем USB-порт.