В этом уроке мы создадим самописец с выводом информации на чековую ленту. Данные с любых аналоговых датчиков поступающие на вход A0 Arduino будут обработаны скетчем и переданы на печать в термопринтер. Нажатие на кнопку приведёт к остановке/возобновлению печати данных. При желании Вы можете изменить номер аналогового входа датчика, номер входа кнопки, чувствительность графика к данным датчика, скорость печати, ширину кривой графика или записывать в переменную varData любые значения, в т.ч. и с цифровых датчиков, с последующим выводом этих данных на печать.
Самописцы с выводом данных на бумажные носители могут применяться не только для записи данных, но и в образовательных целях. Если к аналоговому входу подключить потенциометр или ползунок, то можно наглядно объяснить юным техникам и радиолюбителям, что такое график.
Нам понадобится:
- Arduino Uno х 1шт.
- Любой аналоговый датчик x 1шт.
- Термопринтер x 1шт.
- Источник питания от 5 до 9 В постоянного тока не менее 1,5 A x 1шт.
- Trema кнопка x 1шт.
- Trema Shield x 1шт.
Для реализации проекта нам необходимо установить библиотеки:
- Adafruit_Thermal с описанием функций которой можно ознакомиться на странице Wiki - termoprinter.
- SoftwareSerial для работы с программной шиной UART - библиотека входит в стандартный набор Arduino IDE.
О том как устанавливать библиотеки, Вы можете ознакомиться на странице Wiki - Установка библиотек в Arduino IDE.
Видео:
Схема подключения:
- синий провод термопринтера RX UART (вход данных в термопринтер от Arduino) - подключается к выводу TX Arduino, либо аппаратному UATR (указанному), либо программному UART (назначаемому). В уроке назначен вывод D3 Arduino.
- зелёный провод термопринтера TX UART (выход данных из термопринтера в Arduino) - подключается к выводу RX Arduino, либо аппаратному UART (указанному), либо программному UART (назначаемому). В уроке назначен вывод D4 Arduino.
- черный провод термопринтера (GND) - подключается в выводу GND Arduino и «-» источника питания.
- красный провод термопринтера (Vin) - подключается к «+» источника питания.
- желтый провод термопринтера DTR UART (готовность) - не используется. О том как использовать данный провод написано в разделе Wiki - termoprinter.
- Trema кнопка - подключается к любом выводу Arduino. В уроке используется вывод D7.
- Аналоговый датчик - подключается к любому аналоговому входу Arduino. В уроке используется вывод A0.
Алгоритм работы:
В начале кода подключаются библиотеки SoftwareSerial и Adafruit_Thermal. После чего объявляются объекты этих библиотек mySerial и printer. При объявлении объекта mySerial указываются выводы Arduino к которым подключены зелёный и синий провода термопринтера, а при объявлении объекта printer указывается ссылка на объект mySerial.
Следующим шагом кода является объявление переменных: varData, graph[48], width[5] и flag. Далее объявляются константы: pinKey - содержит цифру 7 (номер вывода Arduino к которому подключена кнопка) и pinSensor - содержит значение A0 (номер вывода Arduino к которому подключён аналоговый датчик).
В коде setup инициируется работа с программной шиной UATR и принтером, чистится массив width, конфигурируется вывод pinKey как вход и выводятся три строки текста в термопринтер.
В 3 строке кода loop, данные которые требуется вывести на кассовую ленту, читаются из аналогового входа pinSensor и записываются в переменную varData преобразовавшись функцией map() от диапазона 0...1023 к диапазону 0...383. Запись новых значений осуществляется при каждом проходе цикла loop. Если уменьшить первый диапазон (например, указать не 0...1023, а 100...1000), то можно увеличить чувствительность выводимого графика. Вы можете записывать в переменную varData любые данные с любых датчиков, главное чтобы записываемое число лежало в диапазоне от 0 до 383.
График выводится построчно. Каждая выводимая вертикальная строка набирается из 384 точек (пикселов) от 0 до 383. Эти точки формируются битами массива graph, который состоит из 48 байт => содержит 384 бита.
Толщину кривой графика можно задавать меняя количество элементов массива width при его объявлении в 7 строке скетча. Если код оставить без изменений, то кривая графика будет выводиться с толщиной в 5 пикселов.
Переменная flag может принимать только два значения true или false. Она разрешает или запрещает вывод данных, а её значение меняется при каждом нажатии на кнопку.
- Весь код loop разбит на две части:
- в первой части if(flag){...} формируется массив graph, биты которого выводятся на печать в виде точек на вертикальной линии
- во второй части if(digitalRead(pinKey)){...} проверяется нажата ли кнопка и если да, то меняется значение flag.
В конце первой части кода loop имеется закомментированная строка delay(60000) - это задержка на 60 секунд. Если её раскомментировать, то устанавливая различные задержки Вы сможете корректировать плотность графика (скорость продвижения ленты).
Код программы:
#include <SoftwareSerial.h> // Подключаем библиотеку для работы по программной шине UART #include <Adafruit_Thermal.h> // Подключаем библиотеку для работы с принтером SoftwareSerial mySerial(4, 3); // Объявляем объект библиотеки SoftwareSerial, указывая задействованные выводы Arduino (RX=4-зелёный, TX=3-синий). Можно назначить другие выводы Arduino Adafruit_Thermal printer(&mySerial); // Объявляем объект библиотеки Adafruit_Thermal, указывая ссылку на созданный ранее объект mySerial библиотеки SoftwareSerial uint16_t varData; // Переменная из которой берутся значения для вывода графика (значение переменной должны быть приведены к диапазону 0 ... 383) uint8_t graph[48]; // Массив, каждый бит данных которого является точкой на линии для вывода на печать (48 байт = 384 бит = ширина печати принтера в пикселях) uint16_t width[5]; // Массив толщины линии графика (количество элементов массива (5) = толщине графика, а значения элементов массива являются стеком для предыдущих точек графика) bool flag = true; // Флаг разрешающий работу самописца const uint8_t pinKey = 7; // Определяем вывод к которому подключена кнопка «Стоп» const uint8_t pinSensor = A0; // Определяем вывод к которому подключён аналоговый датчик, с него снимаются показания // void setup(){ // Код функции setup выполняется только 1 раз, при старте скетча // Подготовка: // mySerial.begin(9600); // Инициируем передачу данных по программной шине UART на скорости 9600. Функцию begin объекта mySerial нужно вызвать до вызова функции begin объекта printer! printer.begin(); // Инициируем работу с термопринтером. В качестве параметра можно указать время нагрева пикселей от 3 (0,03 мс) до 255 (2,55 мс), чем выше тем темнее пикселы. Значение по умолчанию = 120 (1,20 мс) memset(width, 0, sizeof(width)); // Обнуляем массив width, заполняя его значениями 0 от начала до последнего байта sizeof(width). pinMode(pinKey, INPUT); // Конфигурируем вывод pinKey как вход // Выводим текст: // printer.feed(1); // Прокручиваем кассовую ленту на 1 строку printer.println("Chart recorder:"); // Выводим текст "Chart recorder:" printer.println("www.iarduino.ru"); // Выводим текст "www.iarduino.ru" printer.println("0% 50% 100%"); // Выводим текст "0% 50% 100%" } // // void loop(){ // if(flag){ // Печатаем график только если установлен флаг flag // Получаем значение с аналогового входа pinSensor: // varData = map(analogRead(pinSensor), 0,1023, 0,383); // Читаем данные с аналогового входа pinSensor и преобразуем их от диапазона 0...1023 к диапазону 0...383 // Получаем нижнюю и верхнюю точку выводимой линии: // Уменьшая первый диапазон 0...1023 (например до 200 ... 900), Вы увеличиваете чувствительность int16_t PointBot = varData; // Считаем что нижняя точка выводимой линии графика равна точке на графике считанной с аналогового входа int16_t PointTop = varData; // Считаем что верхняя точка выводимой линии графика равна точке на графике считанной с аналогового входа for(int8_t i=0; i<sizeof(width)/2; i++){ // Проходим по всем имеющимся ранее считанным точкам с аналогового входа (количество хранимых точек равно толщине графика ) PointBot = min(PointBot, width[i]); // Определяем наименьшую из хранимых точек PointTop = max(PointTop, width[i]); // Определяем наивысшую из хранимых точек } // // Добавляем к полученным точкам по половине толщины линии графика: // Толщина графика равна количеству элементов в массиве width PointBot -= sizeof(width)/4; if(PointBot<0 ){PointBot=0; } // Вычитаем из PointBot количество байт массива width / 2 (т.к. каждый элемент занимает 2 байта) / 2 (т.к. вычитаем половину толщины) PointTop += sizeof(width)/4; if(PointTop>383){PointTop=383;} // Добавляем к PointTop количество байт массива width / 2 (т.к. каждый элемент занимает 2 байта) / 2 (т.к. добавляем половину толщины) // Сдвигаем стек хранящий последние значения переменной varData: // for(int8_t i=sizeof(width)/2; i>1; i--){ // Проходимся по всем элементам массива width width[i-1]=width[i-2]; // Присваиваем каждому старшему элементу значение из предыдущего (младшего) элемента } width[0]=varData; // А в самый младший элемент массива записываем значение из переменной varData // Формируем точки выводимой линии: // memset(graph,0,48); // Обнуляем массив graph, заполняя его значениями 0 от начала до последнего 48 байта for(uint16_t i=0; i<384; i++){ // Проходим по всем 384 битам массива graph (от 0 до 383) if(i>=PointBot && i<=PointTop){ // Если сквозная нумерация бита лежит в диапазоне от PointBot до PointTop, то ... bitSet(graph[i/8],7-i%8); // устанавливаем этот бит в 1 } // } // // Добавляем пунктирные линии разметки: // static uint8_t grid_line = 0; // Определяем переменную grid_line так, что её значение не сотрётся при следующем проходе цикла loop if(grid_line==0){memset(graph,0xC3,48);} // Если значение переменной grid_line обнулилось, то устанавливаем все байты массива graph в значение 0xC3 (это будет вертикальная линия разметки графика) if(grid_line>=48){grid_line=0;}else{grid_line++;} // Увеличиваем счётчик grid_line на 1 и обнуляем его через каждые 48 проходов (через каждые 48 выведенных на чеке строк) if(grid_line%8<4){ // Если остаток от целочисленного деления переменной grid_line на 8 стал меньше 4, то ... for(uint16_t i=0; i<384; i++){ // Проходим по всем 384 битам массива graph (от 0 до 383) for(int8_t j=47; j>0; j-=6){graph[j]|=1;} // Проходим в обратном порядке по байтам массива graph от 47 с шагом 6 и устанавливаем в 1 последний бит этого байта (это горизонтальные линии разметки графика) graph[0]|=0x80; // Устанавливаем старший бит младшего байта массива graph в 1 (это нижняя горизонтальная пунктирная линия разметки графика) } // } // // Выводим график: // Выводим очередную строку (линию) на кассовом чеке как изображение с высотой 1 пиксель printer.printBitmap(384, 1, graph, false); // Ширина 384 пикселя (полная ширина печати), высота 1 пиксель, изображение брать из массива graph, массив находится не в области памяти программ // Выполняем задержку: // // delay(60000); // Выводим 1 линию за 60000 мс = 60 сек = 1 мин => 1440 линий в день ≈ 18 см кассовой ленты в день } // // Читаем состояние кнопки для остановки/запуска самописца: // Если не используется задержка if(digitalRead(pinKey)){ // Если кнопка нажата, то ... if(flag){printer.println("Stop");} // Если самописец был запущен, то выводим текст "Stop" else {printer.println("0% 50% 100%");} // Если самописец был остановлен, то выводим текст "0% 50% 100%" while(digitalRead(pinKey)){delay(100);} // Избавляемся от дребезга кнопки при её нажатии и ждём отпускания кнопки delay(100); flag=!flag; // Избавляемся от дребезга кнопки при её отпускании и меняем значение флага } }
Вывод данных с цифровых датчиков:
Если Вы хотите вывести данные из цифровых датчиков, то измените третью строку кода loop,
вместо «varData = map(analogRead(pinSensor), 0,1023, 0,383);»
напишите: «varData = map( ДАННЫЕ , ОТ , ДО , 0 , 383);»
- ДАННЫЕ - это переменная хранящая значения цифрового датчика, которые требуется вывести.
- ОТ - это минимально возможное значение цифровых данных.
- ДО - это максимально возможное значение цифровых данных.
Пример:
sensor.read(); // Читаем данные с датчика DHT22 (не забыв ранее подключить библиотеку и инициировать работу с датчиком) varData = map( sensor.tem , -20 , 50 , 0 , 383); // Выводим температуру значение которой берётся из переменной sensor.tem и лежит в диапазоне от -20 до 50 °C
Обсуждение