Скидки, ограниченное предложение! Полный список акционных товаров

Урок 41. Самописец

Необходимые детали
Видео уроки

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

Самописцы с выводом данных на бумажные носители могут применяться не только для записи данных, но и в образовательных целях. Если к аналоговому входу подключить потенциометр или ползунок, то можно наглядно объяснить юным техникам и радиолюбителям, что такое график.

Нам понадобится:

Для реализации проекта нам необходимо установить библиотеки:

  • 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.

Самописец на Arduino

Алгоритм работы:

В начале кода подключаются библиотеки 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

Ссылки:

Обсуждение

Присоединяйся

Другие уроки

На главную