Удаленный мониторинг за датчиками на Maple mini

pav2000
Offline
Зарегистрирован: 15.12.2014

Есть 2 устройства по контролю влажности. http://arduino.ru/forum/proekty/kontrol-vlazhnosti-podvala-arduino-pro-mini

Данная ветка описывает устройство которое "принимает" данные с удаленных блоков по радиоканалу и выводит на экран. Планируется подключать несколько устройств (как минимум два осушителя, тепловой насос и 2-3 удаленных термометра).

1. Выбор элементной базы.

В качестве контроллера решил использовать maple mini на stm32f103. По сравнению с "классической" ардуиной одни сплошные плюсы:

- частота 72 мегагерца

- 32 бита

- 120 кбайт для программ (загрузчик перешил на неродной) и 20 кбайт озу

- на любую ножку можно повесить прерывание

- 12 ацп с приличной частотой, 2 spi  и т.д

Минус для меня один нет eeprom и трудность перехода.

2. Среда разработки

Пишу в стандартой ide ардуино 1.6.1 (можно ставить и 1.6.4 НО ОБЯЗАТЕЛЬНО доставить для DUE софт, там компилятор для arm). Дальше доставляем "спец" софт из проекта http://www.stm32duino.com/

качать от сюда https://github.com/rogerclarkmelbourne/Arduino_STM32

читать http://www.stm32duino.com/ и https://github.com/rogerclarkmelbourne/Arduino_STM32/wiki

3. Программа

Выкладываю исходные тексты программы. Это ранняя альфа. Подключено три экрана - информационный и два осушителя. Хотел прикрутить энкодер но с ним переодически зависает эуран (конфликт дисплея и радиомодуля), пока ограничился кнопкой, нажатие на которую меняет экран.

Пока борюсь с косяками, но связь и вывод работает.

4. Конструктив.

Корпус напечатан на 3Д принтере (нижняя часть склеена из двух частей). Очень удобно, все отверстия сразу сделаны, только заусецы счистить и можно использовать. Получилось достаточно компактно. Вся электроника кроме входных цепей распаена на макетке, проводом . В качестве резервного питания использую литевый аккумулятор 18650, с блоком зарядки. Обычно устройство питается от 5 вольт. Автономно дережет часов 10-15.

исходники:

1. stm32_server.ino

#pragma GCC optimize ("-Os")
//#pragma pack(push, 1)     // выравнивание по одному байту ????????

//#define DEMO           // Признак демонстрации - данные с датчиков генерятся рандом другие интервалы
#define VERSION "Version: 0.361 alpha 19/09/15"        // Текущая версия
    #define SPI_16BIT
    #define SPI_MODE_DMA 1
    #define SPEED_UP 1 // Enables extra calculations in the circles routine to use fastVLine and fastHLine, only in DMA mode.
  

#include <SPI.h>       // в зависимости от платформы используется разная библиотека
#include "Ucglib.h"    // адаптирована для Maple mini
#include "rusFont.h"   // русские фонты
#include "nRF24L01.h"  // не требует адаптации
#include "RF24.h"      // адаптирована для Maple mini

   #include <libmaple/iwdg.h>     // Сторожевой таймер
   #include <libmaple/adc.h>      // АЦП
   
   #define DSB_ALL_IRQ asm volatile("cpsid i");  // запретить все прерывания
   #define ENB_ALL_IRQ asm volatile("cpsie i");  // разрешить все прерывания
   // Что куда припаяно
   #define NRF24_CE_PIN   3
   #define NRF24_CSN_PIN  7
   #define NRF24_IRQ_PIN  PB10   // прерывание радио
   #define TFT_CD_PIN     12
   #define TFT_CS_PIN     13
   #define TFT_RST_PIN    14    // сброс дисплея
   #define LED_PIN        PB1   // светодиод на плате
   #define PIN_A          10    // энкодер канал А
   #define PIN_B          9     // энкодер канал B
   #define PIN_SW         8     // энкодер Кнопка
   
// Аппаратный SPI на дисплей ILI9341 и NRF24
 Ucglib_ILI9341_18x240x320_HWSPI ucg(/*cd=*/TFT_CD_PIN , /*cs=*/ TFT_CS_PIN, /*reset=*/ TFT_RST_PIN);  // Дисплей
 RF24 radio(NRF24_CE_PIN, NRF24_CSN_PIN);  // радиомодуль

 #define NRF24_CHANEL 100 
 #define NUM_SCREEN   2+1      // Число экранов + 1 стартовый
 
int  err_count=-1;  // статистика по пропущенным пакетам -1 признак первого старта
  // энкодер
volatile uint8_t enc=0;
volatile int newpos=0;
bool packet_ready=false;     // true  поступили новые данные
bool change_screen=false;    // true  сменился экран

//bool signalStrength;
byte last_error=100;                            // Предыдущая ошибка<packet.error
// Проверка радио по прерываниям от радиомодуля
void check_radio(void);

void setup(){
   pinMode(PB1, OUTPUT);              // Светодиод
   pinMode(NRF24_IRQ_PIN, INPUT);    // Прерывание
   pinMode(PIN_A, INPUT); 
   pinMode(PIN_B, INPUT);
   pinMode(PIN_SW, INPUT);
   digitalWrite(PIN_A, HIGH);       // turn on pullup resistor
   digitalWrite(PIN_B, HIGH);       // turn on pullup resistor
   iwdg_init(IWDG_PRE_256, 1250);   // init an 8 second wd timer

  // Настройка радиомодуля
  radio.begin();
  radio.setDataRate(RF24_250KBPS);         //  выбор скорости RF24_250KBPS RF24_1MBPS RF24_2MBPS
  radio.setPALevel(RF24_PA_MAX);           // выходная мощность передатчика
  radio.setChannel(NRF24_CHANEL);          // тут установка канала
  radio.setCRCLength(RF24_CRC_16);         // использовать контрольную сумму в 16 бит
  radio.setAutoAck(true);                  // включить аппаратное потверждение
//  radio.enableDynamicPayloads();           // разрешить Dynamic Payloads
//  radio.enableAckPayload();                // разрешить AckPayload
  radio.setRetries(50,10);                 // Количество повторов и пауза между повторами
  // Рекомендуют первые 2-4 байта адреса устанавливать в E7 или 18 он проще детектируется чипом
  radio.openWritingPipe(0xE7E7E7E7D2LL);   // передатчик
  radio.openReadingPipe(1,0xE7E7E7E7E1LL); // приемник
  radio.startListening();
  
  reset_ili9341();
  start_screen();

 attachInterrupt(NRF24_IRQ_PIN, check_radio, FALLING); // Прикрепление прерывания радио
// attachInterrupt(PIN_A, updateEncoder, CHANGE);        // Прикрепление прерывания энкодер
// attachInterrupt(PIN_B, updateEncoder, CHANGE);        // Прикрепление прерывания энкодер
attachInterrupt(PIN_SW, scanKey, FALLING);

}
void scanKey()
{ 
int oldpos=newpos;
byte key=digitalRead(PIN_SW);
if (key==0) newpos++;
if (newpos>=NUM_SCREEN)   newpos=0;  
if (oldpos!=newpos)  change_screen=true; // только когда было изменение
}
void updateEncoder() //адаптировано на деление на 2 и диапазон по количеству экранов
{ 
  int oldpos=newpos;
  uint8_t newenc=2*digitalRead(PIN_B)+digitalRead(PIN_A); //берем 2 бита входов и сдвигаем в позицию 0,1
  uint8_t temp=enc ^ newenc; //побитовое исключающее или
//  if (temp == 0b01)
//    if (enc==0b00 || enc==0b11) newpos++; else newpos--;
//  else 
  if(temp == 0b10)
    if (enc==0b00 || enc==0b11) newpos--; else newpos++;
 enc=newenc;
 if (newpos<0)             newpos=NUM_SCREEN-1;
 if (newpos>=NUM_SCREEN)   newpos=0;
 if (oldpos!=newpos)  change_screen=true; // только когда было изменение
}


void loop()
{
 if (change_screen==true)  // Смена экрана
 {  DSB_ALL_IRQ;
    change_screen=false; 
    packet_ready=true;
    ucg.clearScreen(); 
    switch (newpos) { // по положению Энкодера
    case 0:   // стартовый экран
       start_screen();
       break;
    case 1: // осушитель ID 0x21
       dry_static(0);
       dry_data(0);
      break;
    case 2: // осушитель ID 0x22
       dry_static(1);
       dry_data(1);
    break;
    default: newpos=0;
    }
  ENB_ALL_IRQ;  
 } 
  
 if (packet_ready==true)  // Есть не обработанные данные - надо нарисовать 
 {
    packet_ready=false; 
    DSB_ALL_IRQ;
    switch (newpos) { // по положению Энкодера
    case 0:   // стартовый экран
   //      start_screen();
         break;
    case 1: // осушитель ID 0x21
        dry_update(0);
        break;
    case 2: // осушитель ID 0x21
        dry_update(1);
        break;
    default: newpos=0;
       }
   ENB_ALL_IRQ;    
   digitalWrite(PB1,HIGH);
   delay(5);
   digitalWrite(PB1,LOW) ;
 } 
iwdg_feed();  // Сброс сторожевого таймера
delay(100);  // без этого квитанции не приходят
}

 
// Вывод float  с одним десятичным знаком в координаты x y // для экономии места
void print_floatXY(int x,int y, float v)
{
 ucg.setPrintPos(x,y);
 ucg.print(v,2);
 ucg.print("  "); // Стереть хвост от предыдущего числа
} 

// Вывод строки константы в координаты x y // для экономии места
void print_StrXY(int x,int y, const __FlashStringHelper* b)
{
 ucg.setPrintPos(x,y);
 ucg.print(b);
} 

char hex(byte x)  // Функия для вывода в hex
{
   if(x >= 0 && x <= 9 ) return (char)(x + '0');
   else      return (char)('a'+x-10);
}


void check_radio(void) // ПРИЕМ ПАКЕТА Прерывание для проверки радио
{
   uint8_t buf[33];     // Буффер для чтения 32+1
   byte pipe = 0;
  while(radio.available(&pipe) ) // читаем весь буфер до 3 посылок 
 {
   packet_ready=true;   // есть не обработанные данные
   radio.read(&buf, sizeof(buf));  // Читаем в промежуточный буффер, далее анализируем 1 байт id
   if ((buf[0]&0xf0)==0x20)  dry_get_data(buf); // анализ по ID тип устройства ОСУШИТЕЛЬ
 } 
/*
   if ( radio.available(&pipe) ) {
       radio.read(&buf, sizeof(buf));  // Читаем в промежуточный буффер, далее анализируем 1 байт id
     if ((buf[0]&0xf0)==0x20)  dry_get_data(buf); // анализ по ID тип устройства ОСУШИТЕЛЬ
     } 
  */   
}

// Очистка экрана
bool reset_ili9341(void)
{
  pinMode(TFT_RST_PIN, OUTPUT);      // Сброс дисплея сигнал активным является LOW
  digitalWrite(TFT_RST_PIN, LOW);  
  delay(100);
  digitalWrite(TFT_RST_PIN, HIGH);  
  // Дисплей
  ucg.begin(UCG_FONT_MODE_TRANSPARENT);
  ucg.setFont(my14x10rus);  
  ucg.setRotate90();
//  ucg.setRotate270();
  ucg.clearScreen();
}

bool start_screen(void)
{
  // Заголовок 
  ucg.setColor(0, 0, 180);  // 
  ucg.drawBox(0, 0, 320-1, 23);
  ucg.setColor(250, 250, 250);
  ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
 // ucg.setPrintPos(2,19);
 // ucg.print(newpos);
  print_StrXY(2+12,19,F("Удаленный мониторинг"));
 
  ucg.setColor(255, 255, 0); 
  print_StrXY(2,22+18*1,F("1. Температура блока градусы: ")); 
  ucg.print((1750.0-(read_VDDA()))/4.3+25.0);
  
  print_StrXY(2,22+18*2,F("2. Канал NRF24l01+: "));
  ucg.print(NRF24_CHANEL);
  
  print_StrXY(2,22+18*3,F("3. Число внешних устройств:  "));
  ucg.print(NUM_SCREEN-1);
  
  ucg.setColor(250,80,80);
  print_StrXY(2,22+18*11,F(VERSION));
  
}

// Чтение опорного напряжения
uint16 read_VDDA(void)
{
  adc_reg_map *regs = ADC1->regs;
// 3. Set the TSVREFE bit in the ADC control register 2 (ADC_CR2) to wake up the
//    temperature sensor from power down mode.  Do this first 'cause according to
//    the Datasheet section 5.3.21 it takes from 4 to 10 uS to power up the sensor.
  regs->CR2 |= ADC_CR2_TSEREFE;
// 1. Select the ADCx_IN16 input channel.
  regs->SQR1 = 0;	        // set regular channel sequence length to 1
  regs->SQR3 = 0b10000;		// select channel 16
// 2. Select a sample time of 17.1 μs
// per gbulmer: set channel 16 sample time to 239.5 cycles
// 239.5 cycles of the ADC clock (72MHz/6=12MHz) is over 17.1us (about 20us), but no smaller
// sample time exceeds 17.1us.
  regs->SMPR1 = (0b111 << (3*6));
// 4. Start the ADC conversion by setting the ADON bit (or by external trigger).
//  note by virture of bit 11 being zero returns right aligned results.
// Aparently we also need SWSTART - tdc
  regs->CR2 |= (ADC_CR2_SWSTART | ADC_CR2_ADON);
// wait for conversion to complete
    while (!(regs->SR & ADC_SR_EOC))	;
// 5. Read the resulting VSENSE data in the ADC data register
  return (uint16)(regs->DR & ADC_DR_DATA);
}

2. dry_control.ino

// Функции для осушителя подвала 
// Все функции имеют на входе в качестве параметра номер датчика

// Мои макросы
#define MOTOR_BIT                 0            // бит мотора в packet_0x20.flags
#define HEAT_BIT                  1            // бит калорифера в packet_0x20.flags
#define ABS_H_BIT                 2            // бит калорифера в packet_0x20.flags
#define MODE_BIT                  5            // первый бит режима в packet_0x20.flags

#define FLAG_ABS_H_ON             packet_0x20[ID].flags |= (1<<ABS_H_BIT)   // бит ABS_H_BIT установить в 1
#define FLAG_ABS_H_OFF            packet_0x20[ID].flags &= ~(1<<ABS_H_BIT)  // бит ABS_H_BIT установить в 0
#define FLAG_ABS_H_CHECK          packet_0x20[ID].flags & (1<<ABS_H_BIT)    // бит ABS_H_BIT проверить на 1

#define FLAG_MOTOR_ON             packet_0x20[ID].flags |= (1<<MOTOR_BIT)   // бит мотора установить в 1
#define FLAG_MOTOR_OFF            packet_0x20[ID].flags &= ~(1<<MOTOR_BIT)  // бит мотора установить в 0
#define FLAG_MOTOR_CHECK          packet_0x20[ID].flags & (1<<MOTOR_BIT)    // бит мотора проверить на 1

#define FLAG_HEAT_ON              packet_0x20[ID].flags |= (1<<HEAT_BIT)   // бит калорифера установить в 1
#define FLAG_HEAT_OFF             packet_0x20[ID].flags &= ~(1<<HEAT_BIT)  // бит калорифера установить в 0
#define FLAG_HEAT_CHECK           packet_0x20[ID].flags & (1<<HEAT_BIT)    // бит калорифера проверить на 1

#define NUM_SAMPLES      10                  // Число усреднений измерений датчика
#define TIME_SCAN_SENSOR 3000                // Время опроса датчиков мсек
#define TIME_PRINT_CHART 300000              // Время вывода точки графика мсек
#define TIME_HOUR        3600000             // Число мсек в часе

#define NUM_DRY    2                        // число осушителей id 0x21 0x22 
// Пакет передаваемый, используется также для хранения результатов. 
 struct type_packet_0x20_NRF24   // Версия 2.4!! адаптация для stm32 Структура передаваемого пакета 32 байта - 32 максимум
    {
        byte id=0x00;                           // Идентификатор типа устройства - старшие 4 бита, вторые (младшие) 4 бита серийный номер устройства
        byte DHT_error;                         // Ошибка разряды: 0-1 первый датчик (00-ок) 2-3 второй датчик (00-ок) 4 - радиоканал     
        int16_t   tOut=-500,tIn=500;            // Текущие температуры в сотых градуса !!! место экономим
        uint16_t  absHOut=123,absHIn=123;       // Абсолютные влажности в сотых грамма на м*3 !!! место экономим
        uint16_t  relHOut=123,relHIn=123;       // Относительные влажности сотых процента !!! место экономим
        uint8_t   flags=0x00;                   // байт флагов  
                                                // 0 бит - мотор включен/выключен 
                                                // 1 бит - нагреватель включен/выключен
                                                // 2 бит -[1 - dH_min задается в сотых грамма на м*3] [0 - dH_min заадется в ДЕСЯТЫХ процента от absHIn]
                                                // 3 4 - пока пусто
                                                // 5-7 - номер настройки = settingRAM.mode до 8 настроек, надо передавать, что бы на приемнике восстановить
        uint8_t   dH_min;                       // Порог включения вентилятора по РАЗНИЦЕ абсолютной влажности в сотых грамма на м*3 или в ДЕСЯТЫХ % см flags:2
        uint8_t   T_min;                        // Порог выключения вентилятора по температуре в ДЕСЯТЫХ долях градуса, только положительные значения
        uint8_t   count=0;                      // циклический счетчик отправленных пакетов нужен что бы на приемнике проверять качество связи
        char note[14] = "NONE";                 // Примечание не более 13 байт + 0 байт Русские буквы в два раза меньше т.к. UTF-8
    } packet_0x20[NUM_DRY]; 
 
// Cтруктура для графиков
struct type_chart_dry
{
      byte tOutChart[120];
      byte tInChart[120];
      byte absHOutChart[120];
      byte absHInChart[120];
      byte posChart=0;         // Позиция в массиве графиков - начало вывода от 0 до 120-1
      byte TimeChart=0;        // Время до вывода очередной точки на график. 
      bool ChartMotor=false;   // Признак работы мотора во время интервала графика если мотор был включен на любое время то на графике фон меняется в этом месте
      bool ChartHeat=false;    // Признак работы нагревателя во время интервала графика если нагреватель был включен на любое время (даже одно измерение) то на графике фон меняется в этом месте
      bool CoolData=false;     // Признак наличия свежих данных, что бы лишний раз не выводить на экран одно и тоже
} chart_dry[NUM_DRY];

void dry_static(uint8_t ID)  // Печать статической картинки 
{ int i;
  // Заголовок 
  ucg.setColor(0, 0, 180);  // 
  ucg.drawBox(0, 0, 320-1, 23);
  ucg.setColor(250, 250, 250);
  ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
  ucg.setPrintPos(2,19);
  ucg.print(newpos);
  print_StrXY(2+12,19,F(".ОСУШИТЕЛЬ ID: 0x"));
  ucg.print( hex(packet_0x20[ID].id >> 4));
  ucg.print( hex(packet_0x20[ID].id&0x0f));
  
   // Таблица для данных
  ucg.setColor(0, 200, 0);
  ucg.drawHLine(0,25,320-1);
  ucg.drawHLine(0,25+23*1,320-1);
  ucg.drawHLine(0,25+23*2,320-1);
  ucg.drawHLine(0,25+23*3,320-1);
  ucg.drawHLine(0,25+23*4,320-1);
  ucg.drawVLine(200-4,25,24+23*3);
  ucg.drawVLine(260,25,24+23*3);

  // Заголовки таблиц
  ucg.setColor(255, 255, 0);
  // В зависимости от id разные надписи - привязка местоположения блока к ID
  
//  print_StrXY(180+30-9,25+0+18,F(LABEL));
  ucg.setPrintPos(1,25+0+18); 
  ucg.print(F("N/потери")); 
  
  print_StrXY(250+20,25+0+18,F("Улица"));
  print_StrXY(0,25+23*1+18,F("Температура градусы C")); 
  print_StrXY(0,25+23*2+18,F("Относительная влаж. %")); 
  print_StrXY(0,25+23*3+18,F("Абсолют. влаж. г/м*3")); 

  // Графики
  ucg.setColor(210, 210, 210);
  print_StrXY(10,135+0,F("Температура")); 
  print_StrXY(20+154,135+0,F("Абс. влажность")); 
   
  // надписи на графиках
  print_StrXY(128,154,F("+20")); 
  print_StrXY(135,194,F("0")); 
  print_StrXY(128,233,F("-20"));
  
  print_StrXY(296,164,F("15"));
  print_StrXY(296,194,F("10"));
  print_StrXY(296,223,F("5"));
  
  // Горизонтальная шкала по часам
//  ucg.setColor(255, 255, 0);
  for(i=0;i<=120;i=i+12)
    {
      ucg.drawPixel(4+i,239);
      ucg.drawPixel(4+i,238);
      ucg.drawPixel(167+i,239);
      ucg.drawPixel(167+i,238);
     } 
}

void dry_data(uint8_t ID) // Печать панели статуса Значки на статус панели
{
  // Заголовок 
  ucg.setColor(0, 0, 180);  // 
  ucg.drawBox(168, 0, 320-1, 23);
  ucg.setColor(250, 250, 250);
  ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
 // print_StrXY(2,19,F("ОСУШИТЕЛЬ ID: 0x"));
   ucg.setPrintPos(168,19);
  ucg.print( hex(packet_0x20[ID].id >> 4));
  ucg.print( hex(packet_0x20[ID].id&0x0f));

   ucg.setFontMode(UCG_FONT_MODE_SOLID);
  ucg.setPrintPos(100,19+24);
  ucg.print("        ");
  ucg.setPrintPos(100,19+24);
  ucg.print(packet_0x20[ID].count);
  ucg.print("/");
  ucg.print(err_count);
//  ucg.print("  >>");
//  ucg.print(signalStrength);
 
 // 1. печать ошибки чтения датчиков
//  if (packet_0x20.error!=last_error)        // если статус ошибки поменялся то надо вывести если нет то не выводим - экономия время и нет мерцания
  {
      last_error=packet_0x20[ID].DHT_error; 
      ucg.setColor(0, 0, 200);     // Сначала стереть
      ucg.drawBox(290, 0, 30, 18);
      ucg.setPrintPos(290,18); 
      ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
      if (packet_0x20[ID].DHT_error>0) { ucg.setColor(240, 0, 0);   ucg.print(packet_0x20[ID].DHT_error); }
      else                        { ucg.setColor(200, 240, 0); ucg.print(F("ok")); } 
  }
 // 2. Признак включения мотора
  if (FLAG_MOTOR_CHECK)    ucg.setColor(0, 240, 0); 
  else                     ucg.setColor(0, 40, 0);
  ucg.drawBox(290-25, 5, 14, 14);

 // Печать значений для дома
  ucg.setFontMode(UCG_FONT_MODE_SOLID);
  ucg.setColor(255, 255, 0);
  // В зависимости от id разные надписи - привязка местоположения блока к ID
  ucg.setPrintPos(180+30-9,25+0+18); 
  ucg.print(packet_0x20[ID].note); 

  ucg.setColor(250, 0, 100);  // Цвет ДОМА
  print_floatXY(200+0,25+23*1+18,((float)packet_0x20[ID].tIn)/100);
  print_floatXY(200+0,25+23*2+18,((float)packet_0x20[ID].relHIn)/100);
  print_floatXY(200+0,25+23*3+18,((float)packet_0x20[ID].absHIn)/100);
  ucg.setColor(0, 250, 100);  // Цвет УЛИЦЫ
  print_floatXY(260+4,25+23*1+18,((float)packet_0x20[ID].tOut)/100);
  print_floatXY(260+6,25+23*2+18,((float)packet_0x20[ID].relHOut)/100);
  print_floatXY(260+6,25+23*3+18,((float)packet_0x20[ID].absHOut)/100);
  
  // График
  if (chart_dry[ID].TimeChart==0) dry_chart(ID); // когда выводится график на экран
 
}  

// Печать графика на экране, добавляется одна точка и график сдвигается 
void dry_chart(uint8_t ID) 
{
byte i,x=0;
uint8_t tmp;
//   if ((long)((long)chart_dry[ID].TimeChart*TIME_SCAN_SENSOR*NUM_SAMPLES)<(long)TIME_PRINT_CHART) return;  // график еще рано выводить
   for(i=0;i<120;i++)    // График слева на право
     { 
     // Вычислить координаты текущей точки x в кольцевом буфере. Изменяются от 0 до 120-1
     if (chart_dry[ID].posChart<i) x=120+chart_dry[ID].posChart-i; else x=chart_dry[ID].posChart-i;
    
     // нарисовать фон в зависимости от статуса мотора
     if  (chart_dry[ID].tInChart[x]>=0x80)  ucg.setColor(0, 60, 90);        // Мотор был ключен - бледно синий
     else if (chart_dry[ID].tOutChart[x]>=0x80)  ucg.setColor(90, 60, 0);   // Нагреватель был ключен - бледно желтый
     else                          ucg.setColor(0, 0, 0);     // все выключено
      
     ucg.drawVLine(5+120-i,237-100,100); 
     ucg.drawVLine(5+120-i+162,237-100,100);  
     
     ucg.setColor(180, 180, 180);  
     if (i%5==0) // Пунктирные линии графика
     {
       ucg.drawPixel(5+120-i,236-10-1);
       ucg.drawPixel(5+120-i,236-50-1);
       ucg.drawPixel(5+120-i,236-90-1);
       
       ucg.drawPixel(5+120-i+162,236-25-1);
       ucg.drawPixel(5+120-i+162,236-50-1);
       ucg.drawPixel(5+120-i+162,236-75-1);  
     } 
     
     // Вывести новую точку
     tmp=chart_dry[ID].tInChart[x]&0x7f;   // Отбросить старший разряд - признак включения мотора
     if ((tmp==0)||(tmp==100))   ucg.setColor(255, 255, 255); else ucg.setColor(255, 100, 100); 
     ucg.drawPixel(5+120-i,236-tmp);

     tmp=chart_dry[ID].tOutChart[x]&0x7f;   // Отбросить старший разряд - признак включения калорифера   
     if ((tmp==0) || (tmp==100)) ucg.setColor(255, 255, 255); else ucg.setColor(100, 255, 100); 
     ucg.drawPixel(5+120-i,236-tmp);
     
     if (chart_dry[ID].absHInChart[x]==100) ucg.setColor(255, 255, 255); else ucg.setColor(255, 100, 100); 
     ucg.drawPixel(5+120-i+162,236-chart_dry[ID].absHInChart[x]);
 
     if (chart_dry[ID].absHOutChart[x]==100) ucg.setColor(255, 255, 255); else ucg.setColor(100, 255, 100); 
     ucg.drawPixel(5+120-i+162,236-chart_dry[ID].absHOutChart[x]);
       }
   
// if (chart_dry[ID].posChart<120-1) chart_dry[ID].posChart++; else chart_dry[ID].posChart=0;  // Изменили положение в буфере и Замкнули буфер
}

// Получение данных это все в прерывании быстренько надо
bool dry_get_data(uint8_t *buf)
{ 
  uint8_t ID;
  if (buf[0]==0x21) ID=0;
  if (buf[0]==0x22) ID=1;
  
  memcpy(&packet_0x20[ID],buf,sizeof(type_packet_0x20_NRF24));  // скопировать из буфера в структуру
  chart_dry[ID].CoolData=true;  // Получены свежие данные
   // Рисуем графики в памяти, а когда потребуется быстро выводим.
  chart_dry[ID].TimeChart++;
  if ((long)((long)chart_dry[ID].TimeChart*TIME_SCAN_SENSOR*NUM_SAMPLES)>=(long)TIME_PRINT_CHART) // проверка не пора ли добавлять точку на график
     {
     // Работаем через кольцевой буфер
     // Добавить новую точку в кольцевой буфер
     // Температура в доме. диапазон -25 . . . +25 растягиваем на 100 точек
     if (packet_0x20[ID].tIn<=-2500)       chart_dry[ID].tInChart[chart_dry[ID].posChart]=0;       // Если температура меньше -25 то округляем до -25
     else  if (packet_0x20[ID].tIn>=2500)  chart_dry[ID].tInChart[chart_dry[ID].posChart]=100-1;   // Если температура больше 25  то округляем до 25
      else chart_dry[ID].tInChart[chart_dry[ID].posChart]=(packet_0x20[ID].tIn+2500)/50;           // внутри -25...+25 растягиваем в два раза
   
     if (chart_dry[ID].ChartMotor==true) chart_dry[ID].tInChart[chart_dry[ID].posChart]|=0x80; // Признак включения мотора- старший бит в 1 - цвет фона на графике меняется
     chart_dry[ID].ChartMotor=false;

     // Температура на улице. диапазон -25 . . . +25 растягиваем на 100 точек
     if (packet_0x20[ID].tOut<=-2500) chart_dry[ID].tOutChart[chart_dry[ID].posChart]=0;           // Если температура меньше -25 то округляем до -25
     else  if (packet_0x20[ID].tOut>=2500)  chart_dry[ID].tOutChart[chart_dry[ID].posChart]=100-1; // Если температура больше 25  то округляем до 25
      else chart_dry[ID].tOutChart[chart_dry[ID].posChart]=(packet_0x20[ID].tOut+2500)/50;         // внутри -25...+25 растягиваем в два раза

    if (chart_dry[ID].ChartHeat==true) chart_dry[ID].tOutChart[chart_dry[ID].posChart]|=0x80; // Признак включения нагревателя- старший бит в 1 - цвет фона на графике меняется
     chart_dry[ID].ChartHeat=false;

     // Абсолютная влажность в доме диапазон от 0 до 20 грамм на кубометр, растягиваем на 100 точек
     if (packet_0x20[ID].absHIn>=2000) chart_dry[ID].absHInChart[chart_dry[ID].posChart]=100-1;
     else chart_dry[ID].absHInChart[chart_dry[ID].posChart]=packet_0x20[ID].absHIn/20;   // внутри 0...20 растягиваем в пять  раз в сотых % по этому делем не на 100 а на 20

     // Абсолютная влажность на улицу диапазон от 0 до 20 грамм на кубометр, растягиваем на 100 точек
     if (packet_0x20[ID].absHOut>=2000) chart_dry[ID].absHOutChart[chart_dry[ID].posChart]=100-1;
     else chart_dry[ID].absHOutChart[chart_dry[ID].posChart]=packet_0x20[ID].absHOut/20;   // внутри 0...20 растягиваем в пять раз,  в сотых % по этому делем не на 100 а на 20
     
     if (chart_dry[ID].posChart<120-1) chart_dry[ID].posChart++; else chart_dry[ID].posChart=0;  // Изменили положение в буфере и Замкнули буфер   
     chart_dry[ID].TimeChart=0;  // Сдвиг графика и вывод новой точки сброс счетчика  
  //   dry_chart(ID);
   } 
} 

void dry_update(uint8_t ID)
{
  if (chart_dry[ID].CoolData==true) 
  {
   dry_data(ID);
   chart_dry[ID].CoolData=false;
  }
}


3. rusFont.h

// Русский шрифт 10х14 ------------------------------------------------------
const ucg_fntpgm_uint8_t my14x10rus[4157] UCG_SECTION(".progmem.my14x10") = {
  0,11,15,0,255,14,3,35,6,248,32,255,0,15,255,14,
  0,0,0,0,8,0,0,2,14,14,4,1,0,64,192,192,
  192,192,192,192,192,192,128,0,64,192,128,6,5,5,7,0,
  9,68,204,204,204,136,10,14,28,11,0,0,8,128,8,128,
  17,0,17,0,127,192,17,0,17,0,34,0,34,0,255,128,
  34,0,34,0,68,0,68,0,9,14,28,10,0,0,8,0,
  8,0,59,128,123,0,200,0,200,0,232,0,107,0,11,128,
  9,128,9,128,111,0,238,0,8,0,8,14,14,9,0,0,
  97,179,214,102,12,12,24,24,48,48,102,107,205,134,9,14,
  28,10,0,0,56,0,124,0,108,0,108,0,108,0,56,0,
  56,128,109,128,199,0,194,0,199,0,237,128,124,128,56,0,
  2,5,5,3,0,10,128,192,192,192,64,5,14,14,6,0,
  0,24,48,96,96,192,192,192,192,192,192,96,96,48,24,5,
  14,14,6,0,0,192,96,48,48,24,24,24,24,24,24,56,
  48,96,192,7,7,7,8,0,4,146,214,124,16,124,214,146,
  8,10,10,10,0,1,8,24,24,24,127,254,24,24,24,16,
  4,6,6,5,0,0,112,96,96,64,192,192,8,2,2,9,
  0,5,127,254,3,3,3,4,0,0,224,160,224,8,14,14,
  9,0,0,3,3,6,6,12,12,24,24,48,48,96,96,192,
  192,9,14,28,10,0,0,62,0,127,0,227,128,197,128,197,
  128,197,128,201,128,201,128,209,128,209,128,209,128,227,128,127,
  0,62,0,9,14,28,10,0,0,12,0,28,0,60,0,124,
  0,8,0,4,0,12,0,12,0,8,0,4,0,12,0,12,
  0,127,128,255,128,9,14,28,10,0,0,126,0,255,0,195,
  128,193,128,1,128,3,128,63,0,126,0,224,0,192,0,192,
  0,192,0,223,128,191,0,9,14,28,10,0,0,126,0,255,
  0,195,128,1,128,1,128,3,0,58,0,119,0,3,128,1,
  128,1,128,3,128,255,0,126,0,9,14,28,10,0,0,2,
  0,6,0,14,0,30,0,62,0,118,0,230,0,198,0,251,
  128,247,0,6,0,6,0,6,0,4,0,9,14,28,10,0,
  0,127,128,127,128,96,0,96,0,96,0,110,0,111,0,3,
  128,1,128,1,128,1,128,195,0,255,0,124,0,9,14,28,
  10,0,0,31,0,127,0,96,0,192,0,192,0,192,0,222,
  0,223,0,195,128,193,128,193,128,227,0,127,0,60,0,9,
  14,28,10,0,0,127,128,255,128,0,0,3,0,3,0,6,
  0,6,0,12,0,12,0,24,0,24,0,48,0,48,0,32,
  0,9,14,28,10,0,0,58,0,119,0,227,128,193,128,193,
  128,99,0,54,0,111,0,227,128,193,128,193,128,227,128,119,
  0,46,0,9,14,28,10,0,0,60,0,255,0,231,0,195,
  128,193,128,193,128,225,128,253,128,125,128,1,128,3,128,7,
  0,127,0,252,0,3,11,11,4,0,2,224,160,224,0,0,
  0,0,0,224,160,224,4,13,13,5,0,0,112,80,112,0,
  0,0,0,0,112,80,112,96,192,9,11,22,10,0,1,1,
  128,3,128,15,0,28,0,120,0,224,0,120,0,28,0,15,
  0,3,128,1,128,9,6,12,10,0,4,127,128,255,0,0,
  0,0,0,127,128,255,0,9,11,22,10,0,1,192,0,224,
  0,120,0,60,0,15,0,3,128,15,0,60,0,120,0,224,
  0,192,0,8,14,14,9,0,0,116,238,135,3,3,6,14,
  56,48,48,0,0,48,48,9,11,22,10,0,1,62,0,65,
  0,128,128,154,128,166,128,162,128,162,128,166,128,155,0,64,
  0,63,128,9,14,28,10,0,0,252,0,254,0,199,0,195,
  128,193,128,193,128,193,128,253,128,253,128,193,128,193,128,193,
  128,193,128,129,0,9,14,28,10,0,0,94,0,223,0,195,
  128,193,128,193,128,195,128,255,0,255,0,195,128,193,128,193,
  128,195,128,223,0,190,0,9,14,28,10,0,0,14,0,63,
  0,115,128,97,128,192,0,192,0,192,0,192,0,192,0,192,
  0,96,0,112,0,63,128,15,0,9,14,28,10,0,0,238,
  0,111,0,99,128,97,128,97,128,97,128,97,128,97,128,97,
  128,97,128,97,128,99,128,111,0,238,0,9,14,28,10,0,
  0,95,128,223,0,192,0,192,0,192,0,192,0,223,0,222,
  0,192,0,192,0,192,0,192,0,223,128,191,0,9,14,28,
  10,0,0,95,128,223,0,192,0,192,0,192,0,192,0,223,
  0,222,0,192,0,192,0,192,0,192,0,192,0,128,0,9,
  14,28,10,0,0,63,128,127,128,225,128,192,0,192,0,192,
  0,192,0,207,128,223,128,193,128,193,128,225,128,127,128,62,
  0,9,14,28,10,0,0,129,0,193,128,193,128,193,128,193,
  128,193,128,223,128,223,128,193,128,193,128,193,128,193,128,193,
  128,64,128,8,14,14,10,1,0,254,127,24,24,24,24,24,
  24,24,24,24,24,254,127,9,14,28,10,0,0,31,128,63,
  128,1,128,1,128,1,128,1,128,1,128,1,128,1,128,1,
  128,1,128,195,128,255,0,62,0,9,14,28,10,0,0,65,
  128,195,128,199,0,206,0,220,0,216,0,216,0,216,0,216,
  0,220,0,206,0,199,0,195,128,65,128,9,14,28,10,0,
  0,64,0,192,0,192,0,192,0,192,0,192,0,192,0,192,
  0,192,0,192,0,192,0,192,0,255,128,255,0,9,14,28,
  10,0,0,193,128,227,128,247,128,247,128,213,128,193,128,213,
  128,221,128,221,128,201,128,193,128,193,128,193,128,129,0,9,
  14,28,10,0,0,225,0,225,128,241,128,241,128,249,128,217,
  128,221,128,205,128,205,128,197,128,197,128,193,128,193,128,128,
  128,9,14,28,10,0,0,46,0,111,0,227,128,193,128,193,
  128,193,128,193,128,193,128,193,128,193,128,193,128,227,128,123,
  0,58,0,9,14,28,10,0,0,254,0,255,0,195,128,193,
  128,193,128,195,128,223,0,222,0,192,0,192,0,192,0,192,
  0,192,0,128,0,10,15,30,10,0,255,46,0,111,0,227,
  128,193,128,193,128,193,128,193,128,193,128,193,128,193,128,193,
  128,227,0,123,128,58,192,0,192,9,14,28,10,0,0,126,
  0,255,0,195,128,193,128,193,128,195,128,223,0,220,0,206,
  0,199,0,195,128,193,128,193,128,129,0,9,14,28,10,0,
  0,62,0,127,0,224,0,192,0,192,0,224,0,118,0,27,
  0,3,128,1,128,1,128,3,128,255,128,127,0,9,14,28,
  10,0,0,255,0,127,128,0,0,12,0,12,0,12,0,12,
  0,12,0,12,0,12,0,12,0,12,0,12,0,4,0,9,
  14,28,10,0,0,64,128,193,128,193,128,193,128,193,128,193,
  128,193,128,193,128,193,128,193,128,193,128,99,0,127,0,62,
  0,9,14,28,10,0,0,227,128,99,0,99,0,99,0,34,
  0,54,0,54,0,54,0,20,0,28,0,28,0,28,0,8,
  0,8,0,10,14,28,11,0,0,64,64,192,192,192,192,192,
  192,192,192,192,192,204,192,204,192,204,192,222,192,222,192,211,
  192,193,192,128,192,9,14,28,10,0,0,193,128,193,128,193,
  128,99,0,99,0,50,0,56,0,28,0,14,0,103,0,99,
  0,193,128,193,128,193,128,10,14,28,10,0,0,192,192,192,
  192,97,128,97,128,51,0,63,0,30,0,12,0,8,0,4,
  0,12,0,12,0,12,0,8,0,9,14,28,10,0,0,127,
  128,255,128,1,128,3,128,7,0,6,0,4,0,16,0,48,
  0,112,0,224,0,192,0,255,128,255,0,5,14,14,6,0,
  0,248,192,192,192,192,192,192,192,192,192,192,192,192,248,9,
  14,28,10,0,0,192,0,96,0,96,0,48,0,48,0,24,
  0,24,0,12,0,12,0,6,0,6,0,3,0,3,0,1,
  128,5,14,14,6,0,0,248,24,24,24,24,24,24,24,24,
  24,24,24,24,248,9,6,12,10,0,8,8,0,28,0,54,
  0,99,0,193,128,128,128,10,1,2,10,0,255,255,192,4,
  3,3,5,0,12,224,96,48,8,11,11,9,0,0,124,127,
  3,3,59,123,227,195,199,255,123,9,13,26,10,0,0,64,
  0,192,0,192,0,192,0,192,0,222,0,223,0,195,128,193,
  128,193,128,195,0,255,0,222,0,8,11,11,9,0,0,30,
  63,115,224,192,192,192,224,240,127,30,9,13,26,10,0,0,
  0,128,1,128,1,128,1,128,1,128,61,128,125,128,225,128,
  193,128,193,128,227,128,127,128,61,128,8,11,11,9,0,0,
  60,126,231,195,195,223,222,192,227,127,62,7,13,13,8,0,
  0,62,124,96,96,252,248,96,96,96,96,96,96,32,8,13,
  13,9,0,0,63,127,227,195,195,195,227,123,51,3,3,127,
  254,8,13,13,9,0,0,64,192,192,192,222,223,195,195,195,
  195,195,195,130,2,13,13,3,0,0,64,192,128,64,192,192,
  192,192,192,192,192,192,128,5,14,14,6,0,255,16,24,24,
  8,48,120,24,24,24,24,24,24,120,240,8,13,13,9,0,
  0,64,192,192,198,198,204,216,216,216,204,198,199,131,2,14,
  14,3,0,0,64,192,192,192,192,192,192,192,192,192,192,192,
  192,128,9,11,22,10,0,0,91,0,219,128,201,128,201,128,
  201,128,201,128,201,128,201,128,201,128,193,128,129,0,8,11,
  11,9,0,0,94,223,195,195,195,195,195,195,195,195,130,8,
  11,11,9,0,0,52,118,227,195,195,195,195,195,227,118,52,
  8,11,11,9,0,0,252,254,199,195,199,222,220,192,192,192,
  128,9,13,26,9,0,255,63,0,127,0,227,0,195,0,195,
  0,195,0,251,0,123,0,3,0,3,0,3,128,3,128,3,
  128,7,11,11,8,0,0,92,222,224,224,192,192,192,192,192,
  192,128,8,11,11,9,0,0,62,127,192,192,240,102,15,3,
  3,254,124,6,13,13,7,0,0,32,96,96,252,248,96,96,
  96,96,96,96,124,60,8,11,11,9,0,0,65,195,195,195,
  195,195,195,195,227,123,58,8,11,11,9,0,0,129,129,195,
  195,102,102,102,36,60,24,24,9,11,22,10,0,0,128,128,
  193,128,201,128,201,128,201,128,201,128,193,128,221,128,247,128,
  227,128,65,0,8,11,11,9,0,0,195,102,102,52,24,24,
  24,52,102,102,195,8,12,12,9,0,255,193,227,99,102,110,
  44,12,24,24,48,240,224,8,11,11,9,0,0,127,255,7,
  6,12,0,48,96,224,255,254,7,14,14,8,0,0,14,28,
  24,24,24,48,224,224,48,24,24,24,28,14,2,16,16,5,
  1,255,192,192,192,192,192,192,192,192,192,192,192,192,192,192,
  192,192,8,14,14,9,0,0,224,112,24,24,24,12,7,7,
  12,24,24,24,112,224,10,5,10,11,0,4,48,192,120,192,
  204,192,199,128,195,0,5,13,13,6,0,1,248,136,136,136,
  136,136,136,136,136,136,136,136,248,8,11,11,9,0,0,252,
  254,199,195,199,222,220,192,192,192,128,8,11,11,9,0,0,
  30,63,115,224,192,192,192,224,240,127,30,8,11,11,9,0,
  0,127,254,24,24,24,24,24,24,24,24,16,8,12,12,9,
  0,255,193,227,99,102,110,44,12,24,24,48,240,224,8,12,
  12,9,0,255,126,255,219,219,219,219,90,24,24,24,24,16,
  8,11,11,9,0,0,195,102,102,52,24,24,24,52,102,102,
  195,8,12,12,9,0,255,132,198,198,198,198,198,198,198,198,
  254,255,3,8,11,11,9,0,0,65,195,195,195,231,127,63,
  3,3,3,2,8,11,11,9,0,0,130,195,195,211,219,219,
  219,219,219,203,255,8,12,12,9,0,255,130,195,195,211,219,
  219,219,219,218,200,255,3,8,11,11,9,0,0,192,224,96,
  96,108,110,103,99,103,126,124,8,11,11,9,0,0,130,195,
  195,195,219,221,207,199,207,253,251,8,11,11,9,0,0,64,
  192,192,192,220,222,199,195,199,254,252,8,11,11,9,0,0,
  124,254,198,3,27,59,3,3,6,254,120,9,11,22,10,0,
  0,71,0,207,128,205,128,205,128,221,128,221,128,205,128,205,
  128,205,128,207,128,135,0,8,11,11,9,0,0,63,127,227,
  195,227,123,59,51,51,115,226,9,14,28,10,0,0,252,0,
  254,0,199,0,195,128,193,128,193,128,193,128,253,128,253,128,
  193,128,193,128,193,128,193,128,129,0,9,14,28,10,0,0,
  223,128,223,0,192,0,192,0,192,0,192,0,222,0,223,0,
  195,128,193,128,193,128,195,128,255,0,254,0,9,14,28,10,
  0,0,94,0,223,0,195,128,193,128,193,128,195,128,255,0,
  255,0,195,128,193,128,193,128,195,128,223,0,190,0,9,14,
  28,10,0,0,223,0,223,128,192,0,192,0,192,0,192,0,
  192,0,192,0,192,0,192,0,192,0,192,0,192,0,128,0,
  9,14,28,10,0,0,11,0,27,0,59,0,115,0,99,0,
  99,0,99,0,99,0,99,0,99,0,123,0,251,128,193,128,
  193,128,9,14,28,10,0,0,95,128,223,0,192,0,192,0,
  192,0,192,0,223,0,222,0,192,0,192,0,192,0,192,0,
  223,128,191,0,10,14,28,11,0,0,64,64,192,192,196,192,
  204,192,204,192,109,128,109,128,109,128,109,128,204,192,204,192,
  204,192,200,192,128,128,9,14,28,10,0,0,122,0,251,0,
  131,128,1,128,1,128,3,0,58,0,123,0,3,128,1,128,
  1,128,131,128,251,0,120,0,9,14,28,10,0,0,67,128,
  195,128,199,128,199,128,199,128,205,128,205,128,205,128,217,128,
  217,128,217,128,209,128,209,128,193,0,9,14,28,10,0,0,
  91,128,219,128,215,128,199,128,199,128,205,128,205,128,205,128,
  217,128,217,128,217,128,209,128,209,128,193,0,9,14,28,10,
  0,0,65,128,195,128,199,0,206,0,220,0,216,0,216,0,
  216,0,216,0,220,0,206,0,199,0,195,128,65,128,9,14,
  28,10,0,0,220,0,222,0,199,0,195,128,193,128,193,128,
  193,128,193,128,193,128,193,128,193,128,193,128,193,128,193,128,
  9,14,28,10,0,0,193,128,227,128,247,128,247,128,213,128,
  193,128,213,128,221,128,221,128,201,128,193,128,193,128,193,128,
  129,0,9,14,28,10,0,0,129,0,193,128,193,128,193,128,
  193,128,193,128,223,128,223,128,193,128,193,128,193,128,193,128,
  193,128,64,128,9,14,28,10,0,0,46,0,111,0,227,128,
  193,128,193,128,193,128,193,128,193,128,193,128,193,128,193,128,
  227,128,123,0,58,0,9,14,28,10,0,0,223,128,223,128,
  193,128,193,128,193,128,193,128,193,128,193,128,193,128,193,128,
  193,128,193,128,193,128,129,0,9,14,28,10,0,0,254,0,
  255,0,195,128,193,128,193,128,195,128,223,0,222,0,192,0,
  192,0,192,0,192,0,192,0,128,0,9,14,28,10,0,0,
  14,0,63,0,115,128,97,128,192,0,192,0,192,0,192,0,
  192,0,192,0,96,0,112,0,63,128,15,0,9,14,28,10,
  0,0,255,0,127,128,0,0,12,0,12,0,12,0,12,0,
  12,0,12,0,12,0,12,0,12,0,12,0,4,0,9,14,
  28,10,0,0,129,0,193,128,193,128,193,128,193,128,125,128,
  61,128,1,128,1,128,1,128,193,128,225,128,127,0,62,0,
  10,14,28,11,0,0,63,0,127,128,237,192,204,192,204,192,
  237,192,109,128,45,0,12,0,12,0,12,0,12,0,12,0,
  4,0,9,14,28,10,0,0,193,128,193,128,193,128,99,0,
  99,0,50,0,56,0,28,0,14,0,103,0,99,0,193,128,
  193,128,193,128,10,15,30,10,0,255,130,0,195,0,195,0,
  195,0,195,0,195,0,195,0,195,0,195,0,195,0,195,0,
  195,0,223,0,223,128,1,192,9,14,28,10,0,0,129,0,
  193,128,193,128,193,128,193,128,193,128,225,128,125,128,61,128,
  1,128,1,128,1,128,1,128,0,128,10,14,28,11,0,0,
  128,128,192,192,192,192,192,192,200,192,204,192,204,192,204,192,
  204,192,204,192,196,192,192,64,223,128,95,192,11,15,30,11,
  0,255,128,128,192,192,192,192,192,192,200,192,204,192,204,192,
  204,192,204,192,204,192,196,192,192,64,223,128,95,192,0,224,
  9,14,28,10,0,0,224,0,224,0,96,0,96,0,96,0,
  96,0,110,0,111,0,99,128,97,128,97,128,99,128,127,0,
  62,0,10,14,28,11,0,0,128,128,192,192,192,192,192,192,
  192,192,192,192,220,192,222,192,199,64,195,64,195,64,199,64,
  254,192,124,192,9,14,28,10,0,0,128,0,192,0,192,0,
  192,0,192,0,192,0,222,0,223,0,195,128,193,128,193,128,
  195,128,255,0,254,0,9,14,28,10,0,0,62,0,127,0,
  227,128,193,128,193,128,1,128,29,128,29,128,1,128,1,128,
  193,128,227,128,127,0,62,0,10,14,28,11,0,0,71,0,
  207,128,221,192,216,192,216,192,216,192,248,192,248,192,216,192,
  216,192,216,192,221,192,207,128,135,0,9,14,28,10,0,0,
  63,128,127,128,225,128,193,128,193,128,225,128,125,128,61,128,
  29,128,57,128,113,128,225,128,193,128,129,0,8,11,11,9,
  0,0,124,127,3,3,59,123,227,195,199,255,123,8,11,11,
  9,0,0,7,31,56,112,102,207,195,195,231,126,60,8,11,
  11,9,0,0,92,222,198,198,220,222,195,195,199,222,188,8,
  11,11,9,0,0,254,255,192,192,192,192,192,192,192,192,128,
  10,11,22,11,0,0,31,128,31,128,25,128,49,128,49,128,
  49,128,1,128,127,192,255,192,192,192,192,192,8,11,11,9,
  0,0,60,126,231,195,195,223,222,192,227,127,62,9,11,22,
  10,0,0,64,128,201,128,201,128,107,0,54,0,54,0,107,
  0,201,128,201,128,201,128,129,0,8,11,11,9,0,0,116,
  246,195,7,126,62,6,3,7,254,124,8,11,11,9,0,0,
  65,195,195,199,207,223,219,211,195,195,130,8,11,11,9,0,
  0,89,219,211,199,207,223,219,211,195,195,130,8,11,11,9,
  0,0,71,207,204,220,216,216,216,220,206,199,67,8,11,11,
  9,0,0,27,59,115,227,195,195,195,195,195,195,195,9,11,
  22,10,0,0,65,0,227,128,247,128,247,128,213,128,213,128,
  213,128,213,128,193,128,193,128,129,0,8,11,11,9,0,0,
  65,195,195,251,251,195,195,195,195,195,130,8,11,11,9,0,
  0,52,118,227,195,195,195,195,195,227,118,52,8,11,11,9,
  0,0,95,223,195,195,195,195,195,195,195,195,130,255,255,255,
  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
  255,255,255,255,255,255,255,255,255,255,255,255,255};

Изображение устройства и конструктив

 

 

pav2000
Offline
Зарегистрирован: 15.12.2014

Я адапитровал под stm32 две библиотеки радиомодуля и дисплея. Их надо ставить в среду Arduino. Библиотеки идут для arm и  avr. При компиляции для arm надо убедится сто библиотека SPI берется из паки arm  а не avr

К сожалению на форум нельзя выкладывать zip архивы. Поэтому буду описывать что менял или выкладывать измененные файлы.

1. Для радио модуля используется библиотека TMRh20s ссылки:  http://tmrh20.blogspot.ru/  и  https://github.com/TMRh20

Библиотека адаптирована для stm32
1. Исправления в модуле RF24.cpp для работы maple mini Было  в функции void RF24::csn(bool mode) 
   #if !defined (SOFTSPI)   
        _SPI.setBitOrder(MSBFIRST);
        _SPI.setDataMode(SPI_MODE0);
        _SPI.setClockDivider(SPI_CLOCK_DIV2);   // Понизить частоту !!!!!
    #endif    
Заменить 
    #if !defined (SOFTSPI)   
        _SPI.setBitOrder(MSBFIRST);
        _SPI.setDataMode(SPI_MODE0);
        _SPI.setClockDivider(SPI_CLOCK_DIV4);  // Чип не успевал понизил частоту в два раза
      #endif
 
2. В RF24_config.h  вставить строки того чего не хватает в stm32
// --- STM32 ------
#define _BV(x) (1&lt;&lt;(x))
#define printf_P printf
 
Пока работает
 
2. Библиотека ucglib версия НЕ СТАРШЕ 1.02. Не помню что менял выкладываю весь файл целиком который надо заменить.
/*

  Ucglib.cpp

  ucglib = universal color graphics library
  ucglib = micro controller graphics library
  
  Universal uC Color Graphics Library
  
  Copyright (c) 2014, olikraus@gmail.com
  All rights reserved.

  Redistribution and use in source and binary forms, with or without modification, 
  are permitted provided that the following conditions are met:

  * Redistributions of s_t ource code must retain the above copyright notice, this list 
    of conditions and the following disclaimer.
    
  * Redistributions in binary form must reproduce the above copyright notice, this 
    list of conditions and the following disclaimer in the documentation and/or other 
    materials provided with the distribution.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 
  CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 
  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  
  
*/

#include <SPI.h>

#include "Ucglib.h"

/*=========================================================================*/
/* 8 Bit SW SPI */

#if  defined(__SAM3X8E__)
//#elif defined(__SAM3X8E__)

//#define setbit(pio, mask) PIO_Set( (pio), (mask) )
//#define clrbit(pio, mask) PIO_Clear( (pio), (mask) )

#define setbit(pio, mask) ((pio)->PIO_SODR = (mask))
#define clrbit(pio, mask) ((pio)->PIO_CODR = (mask))

static void ucg_nano_delay(void)
{
  volatile uint32_t i;
  for( i = 0; i < 1; i++ )
  {
    __NOP;
  }
  //delayMicroseconds(1);
}

static void ucg_com_arduino_send_generic_SW_SPI(ucg_t *ucg, uint8_t data)
{
  uint32_t sda_pin = ucg->pin_list[UCG_PIN_SDA];
  uint32_t scl_pin = ucg->pin_list[UCG_PIN_SCL];
  Pio *sda_port = g_APinDescription[sda_pin].pPort;
  Pio *scl_port = g_APinDescription[scl_pin].pPort;
  uint8_t i = 8;
  sda_pin = g_APinDescription[sda_pin].ulPin;
  scl_pin = g_APinDescription[scl_pin].ulPin;

  do
  {
    if ( data & 128 )
    {
      setbit( sda_port, sda_pin) ;
    }
    else
    {
      clrbit( sda_port, sda_pin) ;
    }
    //delayMicroseconds(1);
    ucg_nano_delay();
    setbit( scl_port, scl_pin);
    //delayMicroseconds(1);
    ucg_nano_delay();
    i--;
    clrbit( scl_port, scl_pin) ;
    data <<= 1;
  } while( i > 0 );
  
}

#elif defined(__AVR__)

uint8_t u8g_bitData, u8g_bitNotData;
uint8_t u8g_bitClock, u8g_bitNotClock;
volatile uint8_t *u8g_outData;
volatile uint8_t *u8g_outClock;

static void ucg_com_arduino_init_shift_out(uint8_t dataPin, uint8_t clockPin)
{
  u8g_outData = portOutputRegister(digitalPinToPort(dataPin));
  u8g_outClock = portOutputRegister(digitalPinToPort(clockPin));
  u8g_bitData = digitalPinToBitMask(dataPin);
  u8g_bitClock = digitalPinToBitMask(clockPin);

  u8g_bitNotClock = u8g_bitClock;
  u8g_bitNotClock ^= 0x0ff;

  u8g_bitNotData = u8g_bitData;
  u8g_bitNotData ^= 0x0ff;
}


static void ucg_com_arduino_send_generic_SW_SPI(ucg_t *ucg, uint8_t val) UCG_NOINLINE;
static void ucg_com_arduino_send_generic_SW_SPI(ucg_t *ucg, uint8_t val)
{
  uint8_t cnt = 8;
  uint8_t bitData = u8g_bitData;
  uint8_t bitNotData = u8g_bitNotData;
  uint8_t bitClock = u8g_bitClock;
  uint8_t bitNotClock = u8g_bitNotClock;
  volatile uint8_t *outData = u8g_outData;
  volatile uint8_t *outClock = u8g_outClock;
  
  UCG_ATOMIC_START();
  do
  {
    if ( val & 128 )
      *outData |= bitData;
    else
      *outData &= bitNotData;
   
    *outClock |= bitClock;
    val <<= 1;
    cnt--;
    *outClock &= bitNotClock;
  } while( cnt != 0 );
  UCG_ATOMIC_END();
  
}

#else

static void ucg_com_arduino_send_generic_SW_SPI(ucg_t *ucg, uint8_t data)
{
  uint8_t i = 8;
  
  do
  {
    if ( data & 128 )
    {
      digitalWrite(ucg->pin_list[UCG_PIN_SDA], 1 );
    }
    else
    {
      digitalWrite(ucg->pin_list[UCG_PIN_SDA], 0 );
    }
    // no delay required, also Arduino Due is slow enough
    //delayMicroseconds(1);
    digitalWrite(ucg->pin_list[UCG_PIN_SCL], 1 );
    //delayMicroseconds(1);
    i--;
    digitalWrite(ucg->pin_list[UCG_PIN_SCL], 0 );
    //delayMicroseconds(1);
    data <<= 1;
  } while( i > 0 );
  
}

#endif

static int16_t ucg_com_arduino_generic_SW_SPI(ucg_t *ucg, int16_t msg, uint16_t arg, uint8_t *data)
{

  switch(msg)
  {
    case UCG_COM_MSG_POWER_UP:
      /* "data" is a pointer to ucg_com_info_t structure with the following information: */
      /*	((ucg_com_info_t *)data)->serial_clk_speed value in nanoseconds */
      /*	((ucg_com_info_t *)data)->parallel_clk_speed value in nanoseconds */

#ifdef __AVR__
      ucg_com_arduino_init_shift_out(ucg->pin_list[UCG_PIN_SDA], ucg->pin_list[UCG_PIN_SCL]);
#endif
    
      /* setup pins */
      pinMode(ucg->pin_list[UCG_PIN_CD], OUTPUT);
      pinMode(ucg->pin_list[UCG_PIN_SDA], OUTPUT);
      pinMode(ucg->pin_list[UCG_PIN_SCL], OUTPUT);
      if ( ucg->pin_list[UCG_PIN_CS] != UCG_PIN_VAL_NONE )
	pinMode(ucg->pin_list[UCG_PIN_CS], OUTPUT);
      if ( ucg->pin_list[UCG_PIN_RST] != UCG_PIN_VAL_NONE )
	pinMode(ucg->pin_list[UCG_PIN_RST], OUTPUT);

      digitalWrite(ucg->pin_list[UCG_PIN_CD], 1);
      digitalWrite(ucg->pin_list[UCG_PIN_SDA], 1);
      digitalWrite(ucg->pin_list[UCG_PIN_SCL], 0);
      if ( ucg->pin_list[UCG_PIN_CS] != UCG_PIN_VAL_NONE )
	digitalWrite(ucg->pin_list[UCG_PIN_CS], 1);
      if ( ucg->pin_list[UCG_PIN_RST] != UCG_PIN_VAL_NONE )
	digitalWrite(ucg->pin_list[UCG_PIN_RST], 1);

      break;
    case UCG_COM_MSG_POWER_DOWN:
      break;
    case UCG_COM_MSG_DELAY:
      delayMicroseconds(arg);
      break;
    case UCG_COM_MSG_CHANGE_RESET_LINE:
      if ( ucg->pin_list[UCG_PIN_RST] != UCG_PIN_VAL_NONE )
	digitalWrite(ucg->pin_list[UCG_PIN_RST], arg);
      break;
    case UCG_COM_MSG_CHANGE_CS_LINE:
#ifdef __AVR__
      ucg_com_arduino_init_shift_out(ucg->pin_list[UCG_PIN_SDA], ucg->pin_list[UCG_PIN_SCL]);
#endif    
      if ( ucg->pin_list[UCG_PIN_CS] != UCG_PIN_VAL_NONE )
	digitalWrite(ucg->pin_list[UCG_PIN_CS], arg);      
      break;
    case UCG_COM_MSG_CHANGE_CD_LINE:
      digitalWrite(ucg->pin_list[UCG_PIN_CD], arg);
      break;
    case UCG_COM_MSG_SEND_BYTE:
      ucg_com_arduino_send_generic_SW_SPI(ucg, arg);
      break;
    case UCG_COM_MSG_REPEAT_1_BYTE:
      while( arg > 0 ) {
	ucg_com_arduino_send_generic_SW_SPI(ucg, data[0]);
	arg--;
      }
      break;
    case UCG_COM_MSG_REPEAT_2_BYTES:
      while( arg > 0 ) {
	ucg_com_arduino_send_generic_SW_SPI(ucg, data[0]);
	ucg_com_arduino_send_generic_SW_SPI(ucg, data[1]);
	arg--;
      }
      break;
    case UCG_COM_MSG_REPEAT_3_BYTES:
      while( arg > 0 ) {
	ucg_com_arduino_send_generic_SW_SPI(ucg, data[0]);
	ucg_com_arduino_send_generic_SW_SPI(ucg, data[1]);
	ucg_com_arduino_send_generic_SW_SPI(ucg, data[2]);
	arg--;
      }
      break;
    case UCG_COM_MSG_SEND_STR:
      while( arg > 0 ) {
	ucg_com_arduino_send_generic_SW_SPI(ucg, *data++);
	arg--;
      }
      break;
    case UCG_COM_MSG_SEND_CD_DATA_SEQUENCE:
      while(arg > 0)
      {
	if ( *data != 0 )
	{
	  if ( *data == 1 )
	  {
	    digitalWrite(ucg->pin_list[UCG_PIN_CD], 0);
	  }
	  else
	  {
	    digitalWrite(ucg->pin_list[UCG_PIN_CD], 1);
	  }
	}
	data++;
	ucg_com_arduino_send_generic_SW_SPI(ucg, *data);
	data++;
	arg--;
      }
      break;
  }
  return 1;
}

void Ucglib4WireSWSPI::begin(ucg_font_mode_fnptr font_mode)
{ 
  ucg_Init(&ucg, dev_cb, ext_cb, ucg_com_arduino_generic_SW_SPI); 
  ucg_SetFontMode(&ucg, font_mode);
}


/*=========================================================================*/
/* 8 Bit SW SPI for ILI9325 (mode IM3=0, IM2=1, IM1=0, IM0=0 */

static int16_t ucg_com_arduino_illi9325_SW_SPI(ucg_t *ucg, int16_t msg, uint16_t arg, uint8_t *data)
{

  switch(msg)
  {
    case UCG_COM_MSG_POWER_UP:
      /* "data" is a pointer to ucg_com_info_t structure with the following information: */
      /*	((ucg_com_info_t *)data)->serial_clk_speed value in nanoseconds */
      /*	((ucg_com_info_t *)data)->parallel_clk_speed value in nanoseconds */

#ifdef __AVR__
      ucg_com_arduino_init_shift_out(ucg->pin_list[UCG_PIN_SDA], ucg->pin_list[UCG_PIN_SCL]);
#endif
    
      /* setup pins */
      pinMode(ucg->pin_list[UCG_PIN_CD], OUTPUT);
      pinMode(ucg->pin_list[UCG_PIN_SDA], OUTPUT);
      pinMode(ucg->pin_list[UCG_PIN_SCL], OUTPUT);
      if ( ucg->pin_list[UCG_PIN_CS] != UCG_PIN_VAL_NONE )
	pinMode(ucg->pin_list[UCG_PIN_CS], OUTPUT);
      if ( ucg->pin_list[UCG_PIN_RST] != UCG_PIN_VAL_NONE )
	pinMode(ucg->pin_list[UCG_PIN_RST], OUTPUT);

      digitalWrite(ucg->pin_list[UCG_PIN_CD], 1);
      digitalWrite(ucg->pin_list[UCG_PIN_SDA], 1);
      digitalWrite(ucg->pin_list[UCG_PIN_SCL], 0);
      if ( ucg->pin_list[UCG_PIN_CS] != UCG_PIN_VAL_NONE )
	digitalWrite(ucg->pin_list[UCG_PIN_CS], 1);
      if ( ucg->pin_list[UCG_PIN_RST] != UCG_PIN_VAL_NONE )
	digitalWrite(ucg->pin_list[UCG_PIN_RST], 1);

      break;
    case UCG_COM_MSG_POWER_DOWN:
      break;
    case UCG_COM_MSG_DELAY:
      delayMicroseconds(arg);
      break;
    case UCG_COM_MSG_CHANGE_RESET_LINE:
      if ( ucg->pin_list[UCG_PIN_RST] != UCG_PIN_VAL_NONE )
	digitalWrite(ucg->pin_list[UCG_PIN_RST], arg);
      break;
      
    case UCG_COM_MSG_CHANGE_CS_LINE:
#ifdef __AVR__
      ucg_com_arduino_init_shift_out(ucg->pin_list[UCG_PIN_SDA], ucg->pin_list[UCG_PIN_SCL]);
#endif    
      if ( ucg->pin_list[UCG_PIN_CS] != UCG_PIN_VAL_NONE )
	digitalWrite(ucg->pin_list[UCG_PIN_CS], arg);      
      break;
      
    case UCG_COM_MSG_CHANGE_CD_LINE:
      if ( ucg->pin_list[UCG_PIN_CS] != UCG_PIN_VAL_NONE )
      {
	digitalWrite(ucg->pin_list[UCG_PIN_CS], 1);      
	digitalWrite(ucg->pin_list[UCG_PIN_CS], 0);      
      }

      if ( ucg->com_status & UCG_COM_STATUS_MASK_CD )
	ucg_com_arduino_send_generic_SW_SPI(ucg, 0x072);
      else
	ucg_com_arduino_send_generic_SW_SPI(ucg, 0x070);      
	
      break;
      
    case UCG_COM_MSG_SEND_BYTE:
      ucg_com_arduino_send_generic_SW_SPI(ucg, arg);
      break;
    case UCG_COM_MSG_REPEAT_1_BYTE:
      while( arg > 0 ) {
	ucg_com_arduino_send_generic_SW_SPI(ucg, data[0]);
	arg--;
      }
      break;
    case UCG_COM_MSG_REPEAT_2_BYTES:
      while( arg > 0 ) {
	ucg_com_arduino_send_generic_SW_SPI(ucg, data[0]);
	ucg_com_arduino_send_generic_SW_SPI(ucg, data[1]);
	arg--;
      }
      break;
    case UCG_COM_MSG_REPEAT_3_BYTES:
      while( arg > 0 ) {
	ucg_com_arduino_send_generic_SW_SPI(ucg, data[0]);
	ucg_com_arduino_send_generic_SW_SPI(ucg, data[1]);
	ucg_com_arduino_send_generic_SW_SPI(ucg, data[2]);
	arg--;
      }
      break;
    case UCG_COM_MSG_SEND_STR:
      while( arg > 0 ) {
	ucg_com_arduino_send_generic_SW_SPI(ucg, *data++);
	arg--;
      }
      break;
    case UCG_COM_MSG_SEND_CD_DATA_SEQUENCE:
      while(arg > 0)
      {
	if ( *data != 0 )
	{
	  if ( *data == 1 )
	  {
	    if ( ucg->pin_list[UCG_PIN_CS] != UCG_PIN_VAL_NONE )
	    {
	      digitalWrite(ucg->pin_list[UCG_PIN_CS], 1);      
	      digitalWrite(ucg->pin_list[UCG_PIN_CS], 0);      
	    }
	    ucg_com_arduino_send_generic_SW_SPI(ucg, 0x070);
	  }
	  else
	  {
	    if ( ucg->pin_list[UCG_PIN_CS] != UCG_PIN_VAL_NONE )
	    {
	      digitalWrite(ucg->pin_list[UCG_PIN_CS], 1);      
	      digitalWrite(ucg->pin_list[UCG_PIN_CS], 0);      
	    }
	    ucg_com_arduino_send_generic_SW_SPI(ucg, 0x072);
	  }
	}
	data++;
	ucg_com_arduino_send_generic_SW_SPI(ucg, *data);
	data++;
	arg--;
      }
      break;
  }
  return 1;
}

void Ucglib3WireILI9325SWSPI::begin(ucg_font_mode_fnptr font_mode)
{ 
  ucg_Init(&ucg, dev_cb, ext_cb, ucg_com_arduino_illi9325_SW_SPI); 
  ucg_SetFontMode(&ucg, font_mode);
}


/*=========================================================================*/
/* 9 Bit SW SPI */

static void ucg_com_arduino_send_3wire_9bit_SW_SPI(ucg_t *ucg, uint8_t first_bit, uint8_t data)
{
  uint8_t i;

  if ( first_bit != 0 )
  {
    digitalWrite(ucg->pin_list[UCG_PIN_SDA], 1 );
  }
  else
  {
    digitalWrite(ucg->pin_list[UCG_PIN_SDA], 0 );
  }
  // no delay required, also Arduino Due is slow enough
  //delayMicroseconds(1);
  digitalWrite(ucg->pin_list[UCG_PIN_SCL], 1 );
  //delayMicroseconds(1);
  digitalWrite(ucg->pin_list[UCG_PIN_SCL], 0 );
  //delayMicroseconds(1);

  i = 8;
  do
  {
    if ( data & 128 )
    {
      digitalWrite(ucg->pin_list[UCG_PIN_SDA], 1 );
    }
    else
    {
      digitalWrite(ucg->pin_list[UCG_PIN_SDA], 0 );
    }
    // no delay required, also Arduino Due is slow enough
    //delayMicroseconds(1);
    digitalWrite(ucg->pin_list[UCG_PIN_SCL], 1 );
    //delayMicroseconds(1);
    i--;
    digitalWrite(ucg->pin_list[UCG_PIN_SCL], 0 );
    //delayMicroseconds(1);
    data <<= 1;
  } while( i > 0 );
  
}

static int16_t ucg_com_arduino_3wire_9bit_SW_SPI(ucg_t *ucg, int16_t msg, uint16_t arg, uint8_t *data)
{

  switch(msg)
  {
    case UCG_COM_MSG_POWER_UP:
      /* "data" is a pointer to ucg_com_info_t structure with the following information: */
      /*	((ucg_com_info_t *)data)->serial_clk_speed value in nanoseconds */
      /*	((ucg_com_info_t *)data)->parallel_clk_speed value in nanoseconds */
      
      /* setup pins */
      pinMode(ucg->pin_list[UCG_PIN_SDA], OUTPUT);
      pinMode(ucg->pin_list[UCG_PIN_SCL], OUTPUT);
      if ( ucg->pin_list[UCG_PIN_CS] != UCG_PIN_VAL_NONE )
	pinMode(ucg->pin_list[UCG_PIN_CS], OUTPUT);
      if ( ucg->pin_list[UCG_PIN_RST] != UCG_PIN_VAL_NONE )
	pinMode(ucg->pin_list[UCG_PIN_RST], OUTPUT);

      digitalWrite(ucg->pin_list[UCG_PIN_SDA], 1);
      digitalWrite(ucg->pin_list[UCG_PIN_SCL], 0);
      if ( ucg->pin_list[UCG_PIN_CS] != UCG_PIN_VAL_NONE )
	digitalWrite(ucg->pin_list[UCG_PIN_CS], 1);
      if ( ucg->pin_list[UCG_PIN_RST] != UCG_PIN_VAL_NONE )
	digitalWrite(ucg->pin_list[UCG_PIN_RST], 1);

      break;
    case UCG_COM_MSG_POWER_DOWN:
      break;
    case UCG_COM_MSG_DELAY:
      delayMicroseconds(arg);
      break;
    case UCG_COM_MSG_CHANGE_RESET_LINE:
      if ( ucg->pin_list[UCG_PIN_RST] != UCG_PIN_VAL_NONE )
	digitalWrite(ucg->pin_list[UCG_PIN_RST], arg);
      break;
    case UCG_COM_MSG_CHANGE_CS_LINE:
      if ( ucg->pin_list[UCG_PIN_CS] != UCG_PIN_VAL_NONE )
	digitalWrite(ucg->pin_list[UCG_PIN_CS], arg);
      break;
    case UCG_COM_MSG_CHANGE_CD_LINE:
      /* ignored, there is not CD line */
      break;
    case UCG_COM_MSG_SEND_BYTE:
      ucg_com_arduino_send_3wire_9bit_SW_SPI(ucg, ucg->com_status &UCG_COM_STATUS_MASK_CD, arg);
      break;
    case UCG_COM_MSG_REPEAT_1_BYTE:
      while( arg > 0 ) {
	ucg_com_arduino_send_3wire_9bit_SW_SPI(ucg, ucg->com_status &UCG_COM_STATUS_MASK_CD, data[0]);
	arg--;
      }
      break;
    case UCG_COM_MSG_REPEAT_2_BYTES:
      while( arg > 0 ) {
	ucg_com_arduino_send_3wire_9bit_SW_SPI(ucg, ucg->com_status &UCG_COM_STATUS_MASK_CD, data[0]);
	ucg_com_arduino_send_3wire_9bit_SW_SPI(ucg, ucg->com_status &UCG_COM_STATUS_MASK_CD, data[1]);
	arg--;
      }
      break;
    case UCG_COM_MSG_REPEAT_3_BYTES:
      while( arg > 0 ) {
	ucg_com_arduino_send_3wire_9bit_SW_SPI(ucg, ucg->com_status &UCG_COM_STATUS_MASK_CD, data[0]);
	ucg_com_arduino_send_3wire_9bit_SW_SPI(ucg, ucg->com_status &UCG_COM_STATUS_MASK_CD, data[1]);
	ucg_com_arduino_send_3wire_9bit_SW_SPI(ucg, ucg->com_status &UCG_COM_STATUS_MASK_CD, data[2]);
	arg--;
      }
      break;
    case UCG_COM_MSG_SEND_STR:
      while( arg > 0 ) {
	ucg_com_arduino_send_3wire_9bit_SW_SPI(ucg, ucg->com_status &UCG_COM_STATUS_MASK_CD, *data++);
	arg--;
      }
      break;
    case UCG_COM_MSG_SEND_CD_DATA_SEQUENCE:
      {
	uint8_t last_cd = ucg->com_status &UCG_COM_STATUS_MASK_CD;
	while(arg > 0)
	{
	  if ( *data != 0 )
	  {
	    if ( *data == 1 )
	    {
	      last_cd = 0;
	    }
	    else
	    {
	      last_cd = 1;
	    }
	  }
	  data++;
	  ucg_com_arduino_send_3wire_9bit_SW_SPI(ucg, last_cd, *data); 
	  data++;
	  arg--;
	}
      }
      break;
  }
  return 1;
}

void Ucglib3Wire9bitSWSPI::begin(ucg_font_mode_fnptr font_mode)
{ 
  ucg_Init(&ucg, dev_cb, ext_cb, ucg_com_arduino_3wire_9bit_SW_SPI); 
  ucg_SetFontMode(&ucg, font_mode);
}

/*=========================================================================*/
/* 9 Bit HW SPI */

#define UCG_COM_ARDUINO_3WIRE_8BIT_BUF_LEN 9
static uint8_t ucg_com_3wire_9bit_buffer[UCG_COM_ARDUINO_3WIRE_8BIT_BUF_LEN];
static uint8_t ucg_com_3wire_9bit_buf_bytepos;
static uint8_t ucg_com_3wire_9bit_buf_bitpos;
static uint8_t ucg_com_3wire_9bit_cd_mask;

static void ucg_com_arduino_init_3wire_9bit_HW_SPI(ucg_t *ucg) UCG_NOINLINE;
static void ucg_com_arduino_init_3wire_9bit_HW_SPI(ucg_t *ucg)
{
  uint8_t i;
  ucg_com_3wire_9bit_buf_bytepos = 0;
  ucg_com_3wire_9bit_buf_bitpos = 7;
  ucg_com_3wire_9bit_cd_mask = 128;
  for( i = 0; i < UCG_COM_ARDUINO_3WIRE_8BIT_BUF_LEN; i++ )
    ucg_com_3wire_9bit_buffer[i] = 0; /* this is also the NOP command for the PCF8833 */
}

static void ucg_com_arduino_flush_3wire_9bit_HW_SPI(ucg_t *ucg) UCG_NOINLINE;
static void ucg_com_arduino_flush_3wire_9bit_HW_SPI(ucg_t *ucg)
{
  uint8_t i;
  if ( ucg_com_3wire_9bit_buf_bytepos == 0 && ucg_com_3wire_9bit_buf_bitpos == 7 )
    return;
  
  for( i = 0; i < UCG_COM_ARDUINO_3WIRE_8BIT_BUF_LEN; i++ )
    SPI.transfer(ucg_com_3wire_9bit_buffer[i] );
  
  ucg_com_arduino_init_3wire_9bit_HW_SPI(ucg);
}

static void ucg_com_arduino_send_3wire_9bit_HW_SPI(ucg_t *ucg, uint8_t first_bit, uint8_t data)
{
  
  if ( first_bit != 0 )
    ucg_com_3wire_9bit_buffer[ucg_com_3wire_9bit_buf_bytepos] |= ucg_com_3wire_9bit_cd_mask;
  
  if ( ucg_com_3wire_9bit_buf_bitpos > 0 )
  {
    ucg_com_3wire_9bit_buf_bitpos--;
    ucg_com_3wire_9bit_cd_mask >>= 1;
  }
  else
  {
    ucg_com_3wire_9bit_buf_bitpos = 7;
    ucg_com_3wire_9bit_buf_bytepos++;
    ucg_com_3wire_9bit_cd_mask = 128;
  }
  
  ucg_com_3wire_9bit_buffer[ucg_com_3wire_9bit_buf_bytepos] |=  data >> (7-ucg_com_3wire_9bit_buf_bitpos);

  if ( ucg_com_3wire_9bit_buf_bitpos == 7 )
  {
    ucg_com_3wire_9bit_buf_bytepos++;
    if ( ucg_com_3wire_9bit_buf_bytepos >= UCG_COM_ARDUINO_3WIRE_8BIT_BUF_LEN )
      ucg_com_arduino_flush_3wire_9bit_HW_SPI(ucg);      
  }
  else
  {
    ucg_com_3wire_9bit_buf_bytepos++;
    ucg_com_3wire_9bit_buffer[ucg_com_3wire_9bit_buf_bytepos] |=  data << (ucg_com_3wire_9bit_buf_bitpos+1);
  }
}

static int16_t ucg_com_arduino_3wire_9bit_HW_SPI(ucg_t *ucg, int16_t msg, uint16_t arg, uint8_t *data)
{

  switch(msg)
  {
    case UCG_COM_MSG_POWER_UP:
      /* "data" is a pointer to ucg_com_info_t structure with the following information: */
      /*	((ucg_com_info_t *)data)->serial_clk_speed value in nanoseconds */
      /*	((ucg_com_info_t *)data)->parallel_clk_speed value in nanoseconds */
      
      ucg_com_arduino_init_3wire_9bit_HW_SPI(ucg);
    
      /* setup pins */
      if ( ucg->pin_list[UCG_PIN_CS] != UCG_PIN_VAL_NONE )
	pinMode(ucg->pin_list[UCG_PIN_CS], OUTPUT);
      if ( ucg->pin_list[UCG_PIN_RST] != UCG_PIN_VAL_NONE )
	pinMode(ucg->pin_list[UCG_PIN_RST], OUTPUT);

      if ( ucg->pin_list[UCG_PIN_CS] != UCG_PIN_VAL_NONE )
	digitalWrite(ucg->pin_list[UCG_PIN_CS], 1);
      if ( ucg->pin_list[UCG_PIN_RST] != UCG_PIN_VAL_NONE )
	digitalWrite(ucg->pin_list[UCG_PIN_RST], 1);

      /* setup Arduino SPI */
      SPI.begin();
#if defined(__AVR__)
      SPI.setClockDivider( SPI_CLOCK_DIV2 );
      //SPI.setClockDivider( SPI_CLOCK_DIV64  );
      //SPI.setDataMode(SPI_MODE0);
    
#endif
#if defined(__SAM3X8E__)
      SPI.setClockDivider( (((ucg_com_info_t *)data)->serial_clk_speed * 84L + 999)/1000L );
#endif
      SPI.setDataMode(SPI_MODE0);
      SPI.setBitOrder(MSBFIRST);

      break;
    case UCG_COM_MSG_POWER_DOWN:
      SPI.end();
      break;
    case UCG_COM_MSG_DELAY:
      /* flush pending data first, then do the delay */
      ucg_com_arduino_flush_3wire_9bit_HW_SPI(ucg);      
      delayMicroseconds(arg);
      break;
    case UCG_COM_MSG_CHANGE_RESET_LINE:
      if ( ucg->pin_list[UCG_PIN_RST] != UCG_PIN_VAL_NONE )
	digitalWrite(ucg->pin_list[UCG_PIN_RST], arg);
      break;
    case UCG_COM_MSG_CHANGE_CS_LINE:
      if ( arg != 0 )
	ucg_com_arduino_flush_3wire_9bit_HW_SPI(ucg);      
      
      if ( ucg->pin_list[UCG_PIN_CS] != UCG_PIN_VAL_NONE )
	digitalWrite(ucg->pin_list[UCG_PIN_CS], arg);
      
      if ( arg == 0 )
	ucg_com_arduino_init_3wire_9bit_HW_SPI(ucg);
      
      break;
    case UCG_COM_MSG_CHANGE_CD_LINE:
      /* not used */
      break;
    case UCG_COM_MSG_SEND_BYTE:
      ucg_com_arduino_send_3wire_9bit_HW_SPI(ucg, ucg->com_status &UCG_COM_STATUS_MASK_CD, arg);
      break;
    case UCG_COM_MSG_REPEAT_1_BYTE:
      while( arg > 0 ) {
	ucg_com_arduino_send_3wire_9bit_HW_SPI(ucg, ucg->com_status &UCG_COM_STATUS_MASK_CD, data[0]);
	arg--;
      }
      break;
    case UCG_COM_MSG_REPEAT_2_BYTES:
      while( arg > 0 ) {
	ucg_com_arduino_send_3wire_9bit_HW_SPI(ucg, ucg->com_status &UCG_COM_STATUS_MASK_CD, data[0]);
	ucg_com_arduino_send_3wire_9bit_HW_SPI(ucg, ucg->com_status &UCG_COM_STATUS_MASK_CD, data[1]);
	arg--;
      }
      break;
    case UCG_COM_MSG_REPEAT_3_BYTES:
      while( arg > 0 ) {
	ucg_com_arduino_send_3wire_9bit_HW_SPI(ucg, ucg->com_status &UCG_COM_STATUS_MASK_CD, data[0]);
	ucg_com_arduino_send_3wire_9bit_HW_SPI(ucg, ucg->com_status &UCG_COM_STATUS_MASK_CD, data[1]);
	ucg_com_arduino_send_3wire_9bit_HW_SPI(ucg, ucg->com_status &UCG_COM_STATUS_MASK_CD, data[2]);
	arg--;
      }
      break;
    case UCG_COM_MSG_SEND_STR:
      while( arg > 0 ) {
	ucg_com_arduino_send_3wire_9bit_HW_SPI(ucg, ucg->com_status &UCG_COM_STATUS_MASK_CD, *data++);
	arg--;
      }
      break;
    case UCG_COM_MSG_SEND_CD_DATA_SEQUENCE:
      {
	uint8_t last_cd = ucg->com_status &UCG_COM_STATUS_MASK_CD;
	while(arg > 0)
	{
	  if ( *data != 0 )
	  {
	    if ( *data == 1 )
	    {
	      last_cd = 0;
	    }
	    else
	    {
	      last_cd = 1;
	    }
	  }
	  data++;
	  ucg_com_arduino_send_3wire_9bit_HW_SPI(ucg, last_cd, *data); 
	  data++;
	  arg--;
	}
      }
      break;
  }
  return 1;
}

void Ucglib3Wire9bitHWSPI::begin(ucg_font_mode_fnptr font_mode)
{ 
  ucg_Init(&ucg, dev_cb, ext_cb, ucg_com_arduino_3wire_9bit_HW_SPI); 
  ucg_SetFontMode(&ucg, font_mode);
}


/*=========================================================================*/
/* 8 Bit Parallel */


#if defined(__PIC32MX) || defined(__arm__)
/* CHIPKIT PIC32 */
static volatile uint32_t *u8g_data_port[9];
static uint32_t u8g_data_mask[9];
#else
static volatile uint8_t *u8g_data_port[9];
static uint8_t u8g_data_mask[9];
#endif

static void ucg_com_arduino_init_8bit(ucg_t *ucg)
{

  u8g_data_port[0] =  portOutputRegister(digitalPinToPort(ucg->pin_list[UCG_PIN_D0]));
  u8g_data_mask[0] =  digitalPinToBitMask(ucg->pin_list[UCG_PIN_D0]);  
  
  u8g_data_port[1] =  portOutputRegister(digitalPinToPort(ucg->pin_list[UCG_PIN_D1]));
  u8g_data_mask[1] =  digitalPinToBitMask(ucg->pin_list[UCG_PIN_D1]);  
  
  u8g_data_port[2] =  portOutputRegister(digitalPinToPort(ucg->pin_list[UCG_PIN_D2]));
  u8g_data_mask[2] =  digitalPinToBitMask(ucg->pin_list[UCG_PIN_D2]);  
  
  u8g_data_port[3] =  portOutputRegister(digitalPinToPort(ucg->pin_list[UCG_PIN_D3]));
  u8g_data_mask[3] =  digitalPinToBitMask(ucg->pin_list[UCG_PIN_D3]);  
  
  u8g_data_port[4] =  portOutputRegister(digitalPinToPort(ucg->pin_list[UCG_PIN_D4]));
  u8g_data_mask[4] =  digitalPinToBitMask(ucg->pin_list[UCG_PIN_D4]);  
  
  u8g_data_port[5] =  portOutputRegister(digitalPinToPort(ucg->pin_list[UCG_PIN_D5]));
  u8g_data_mask[5] =  digitalPinToBitMask(ucg->pin_list[UCG_PIN_D5]);  
  
  if ( ucg->pin_list[UCG_PIN_D6] != UCG_PIN_VAL_NONE )
  {
    u8g_data_port[6] =  portOutputRegister(digitalPinToPort(ucg->pin_list[UCG_PIN_D6]));
    u8g_data_mask[6] =  digitalPinToBitMask(ucg->pin_list[UCG_PIN_D6]);  
  }
  
  if ( ucg->pin_list[UCG_PIN_D7] != UCG_PIN_VAL_NONE )
  {
    u8g_data_port[7] =  portOutputRegister(digitalPinToPort(ucg->pin_list[UCG_PIN_D7]));
    u8g_data_mask[7] =  digitalPinToBitMask(ucg->pin_list[UCG_PIN_D7]);  
  }  

  u8g_data_port[8] =  portOutputRegister(digitalPinToPort(ucg->pin_list[UCG_PIN_WR]));
  u8g_data_mask[8] =  digitalPinToBitMask(ucg->pin_list[UCG_PIN_WR]);  
  
}

static void ucg_com_arduino_send_8bit(ucg_t *ucg, uint8_t data)
{
  int i;
  #if defined(__arm__)
  /*
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  */
#endif
  for( i = 0; i < 8; i++ )
  {
    if ( data & 1 )
      *u8g_data_port[i] |= u8g_data_mask[i]; 
    else
      *u8g_data_port[i] &= ~u8g_data_mask[i]; 
    data >>= 1;
  }

  #if defined(__arm__)
  /*
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  */
  delayMicroseconds(1);
#elif defined(__AVR__)
#else
  delayMicroseconds(1);
#endif
  
  *u8g_data_port[8] &= ~u8g_data_mask[8]; 
  
#if defined(__arm__)
/*
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  __NOP;
  */
  delayMicroseconds(1);
#elif defined(__AVR__)
#else
  delayMicroseconds(1);
#endif
  
  *u8g_data_port[8] |= u8g_data_mask[8]; 
}

/*
static void ucg_com_arduino_send_8bit(ucg_t *ucg, uint8_t data)
{
    digitalWrite(ucg->pin_list[UCG_PIN_D0], (data & 1) == 0 ? 0 : 1 );
    digitalWrite(ucg->pin_list[UCG_PIN_D1], (data & 2) == 0 ? 0 : 1 );
    digitalWrite(ucg->pin_list[UCG_PIN_D2], (data & 4) == 0 ? 0 : 1 );
    digitalWrite(ucg->pin_list[UCG_PIN_D3], (data & 8) == 0 ? 0 : 1 );
    digitalWrite(ucg->pin_list[UCG_PIN_D4], (data & 16) == 0 ? 0 : 1 );
    digitalWrite(ucg->pin_list[UCG_PIN_D5], (data & 32) == 0 ? 0 : 1 );
    if ( ucg->pin_list[UCG_PIN_D6] != UCG_PIN_VAL_NONE )
      digitalWrite(ucg->pin_list[UCG_PIN_D6], (data & 64) == 0 ? 0 : 1 );
    if ( ucg->pin_list[UCG_PIN_D7] != UCG_PIN_VAL_NONE )
      digitalWrite(ucg->pin_list[UCG_PIN_D7], (data & 128) == 0 ? 0 : 1 );  
    delayMicroseconds(1);
    digitalWrite(ucg->pin_list[UCG_PIN_WR], 0);
    delayMicroseconds(1);
    digitalWrite(ucg->pin_list[UCG_PIN_WR], 1);
}
*/

static int16_t ucg_com_arduino_generic_8bit(ucg_t *ucg, int16_t msg, uint16_t arg, uint8_t *data)
{
  switch(msg)
  {
    case UCG_COM_MSG_POWER_UP:
      /* "data" is a pointer to ucg_com_info_t structure with the following information: */
      /*	((ucg_com_info_t *)data)->serial_clk_speed value in nanoseconds */
      /*	((ucg_com_info_t *)data)->parallel_clk_speed value in nanoseconds */
      
      /* setup pins */
      pinMode(ucg->pin_list[UCG_PIN_CD], OUTPUT);
      pinMode(ucg->pin_list[UCG_PIN_WR], OUTPUT);
    
      if ( ucg->pin_list[UCG_PIN_CS] != UCG_PIN_VAL_NONE )
	pinMode(ucg->pin_list[UCG_PIN_CS], OUTPUT);
      if ( ucg->pin_list[UCG_PIN_RST] != UCG_PIN_VAL_NONE )
	pinMode(ucg->pin_list[UCG_PIN_RST], OUTPUT);

      pinMode(ucg->pin_list[UCG_PIN_D0], OUTPUT);
      pinMode(ucg->pin_list[UCG_PIN_D1], OUTPUT);
      pinMode(ucg->pin_list[UCG_PIN_D2], OUTPUT);
      pinMode(ucg->pin_list[UCG_PIN_D3], OUTPUT);
      pinMode(ucg->pin_list[UCG_PIN_D4], OUTPUT);
      pinMode(ucg->pin_list[UCG_PIN_D5], OUTPUT);
      if ( ucg->pin_list[UCG_PIN_D6] != UCG_PIN_VAL_NONE )
	pinMode(ucg->pin_list[UCG_PIN_D6], OUTPUT);
      if ( ucg->pin_list[UCG_PIN_D7] != UCG_PIN_VAL_NONE )
	pinMode(ucg->pin_list[UCG_PIN_D7], OUTPUT);

      digitalWrite(ucg->pin_list[UCG_PIN_CD], 1);
      digitalWrite(ucg->pin_list[UCG_PIN_WR], 1);
      if ( ucg->pin_list[UCG_PIN_CS] != UCG_PIN_VAL_NONE )
	digitalWrite(ucg->pin_list[UCG_PIN_CS], 1);
      if ( ucg->pin_list[UCG_PIN_RST] != UCG_PIN_VAL_NONE )
	digitalWrite(ucg->pin_list[UCG_PIN_RST], 1);

      ucg_com_arduino_init_8bit(ucg);
      
      break;
    case UCG_COM_MSG_POWER_DOWN:
      break;
    case UCG_COM_MSG_DELAY:
      delayMicroseconds(arg);
      break;
    case UCG_COM_MSG_CHANGE_RESET_LINE:
      if ( ucg->pin_list[UCG_PIN_RST] != UCG_PIN_VAL_NONE )
	digitalWrite(ucg->pin_list[UCG_PIN_RST], arg);
      break;
    case UCG_COM_MSG_CHANGE_CS_LINE:
      if ( ucg->pin_list[UCG_PIN_CS] != UCG_PIN_VAL_NONE )
	digitalWrite(ucg->pin_list[UCG_PIN_CS], arg);
      break;
    case UCG_COM_MSG_CHANGE_CD_LINE:
      digitalWrite(ucg->pin_list[UCG_PIN_CD], arg);
      break;
    case UCG_COM_MSG_SEND_BYTE:
      ucg_com_arduino_send_8bit(ucg, arg);
      break;
    case UCG_COM_MSG_REPEAT_1_BYTE:
      while( arg > 0 ) {
	ucg_com_arduino_send_8bit(ucg, data[0]);
	arg--;
      }
      break;
    case UCG_COM_MSG_REPEAT_2_BYTES:
      while( arg > 0 ) {
	ucg_com_arduino_send_8bit(ucg, data[0]);
	ucg_com_arduino_send_8bit(ucg, data[1]);
	arg--;
      }
      break;
    case UCG_COM_MSG_REPEAT_3_BYTES:
      while( arg > 0 ) {
	ucg_com_arduino_send_8bit(ucg, data[0]);
	ucg_com_arduino_send_8bit(ucg, data[1]);
	ucg_com_arduino_send_8bit(ucg, data[2]);
	arg--;
      }
      break;
    case UCG_COM_MSG_SEND_STR:
      while( arg > 0 ) {
	ucg_com_arduino_send_8bit(ucg, *data++);
	arg--;
      }
      break;
    case UCG_COM_MSG_SEND_CD_DATA_SEQUENCE:
      while(arg > 0)
      {
	if ( *data != 0 )
	{
	  if ( *data == 1 )
	  {
	    digitalWrite(ucg->pin_list[UCG_PIN_CD], 0);
	  }
	  else
	  {
	    digitalWrite(ucg->pin_list[UCG_PIN_CD], 1);
	  }
	}
	data++;
	ucg_com_arduino_send_8bit(ucg, *data);
	data++;
	arg--;
      }
      break;
  }
  return 1;
}

void Ucglib8Bit::begin(ucg_font_mode_fnptr font_mode)
{ 
  ucg_Init(&ucg, dev_cb, ext_cb, ucg_com_arduino_generic_8bit); 
  ucg_SetFontMode(&ucg, font_mode);
}


/*=========================================================================*/
/* 8 Bit Parallel on Port D of AVR controller */

#ifdef __AVR__

static void ucg_com_arduino_port_d_send(uint8_t data, volatile uint8_t *port, uint8_t and_mask, uint8_t or_mask)
{
    PORTD = data;
    *port &= and_mask;
    *port |= or_mask;
}

static int16_t ucg_com_arduino_port_d(ucg_t *ucg, int16_t msg, uint16_t arg, uint8_t *data)
{
  switch(msg)
  {
    case UCG_COM_MSG_POWER_UP:
      /* "data" is a pointer to ucg_com_info_t structure with the following information: */
      /*	((ucg_com_info_t *)data)->serial_clk_speed value in nanoseconds */
      /*	((ucg_com_info_t *)data)->parallel_clk_speed value in nanoseconds */
      
      /* setup pins */
      pinMode(ucg->pin_list[UCG_PIN_CD], OUTPUT);
      pinMode(ucg->pin_list[UCG_PIN_WR], OUTPUT);
      
      if ( ucg->pin_list[UCG_PIN_CS] != UCG_PIN_VAL_NONE )
	pinMode(ucg->pin_list[UCG_PIN_CS], OUTPUT);
      if ( ucg->pin_list[UCG_PIN_RST] != UCG_PIN_VAL_NONE )
	pinMode(ucg->pin_list[UCG_PIN_RST], OUTPUT);

      pinMode(0, OUTPUT);
      pinMode(1, OUTPUT);
      pinMode(2, OUTPUT);
      pinMode(3, OUTPUT);
      pinMode(4, OUTPUT);
      pinMode(5, OUTPUT);
      pinMode(6, OUTPUT);
      pinMode(7, OUTPUT);

      digitalWrite(ucg->pin_list[UCG_PIN_CD], 1);
      digitalWrite(ucg->pin_list[UCG_PIN_WR], 1);
      if ( ucg->pin_list[UCG_PIN_CS] != UCG_PIN_VAL_NONE )
	digitalWrite(ucg->pin_list[UCG_PIN_CS], 1);
      if ( ucg->pin_list[UCG_PIN_RST] != UCG_PIN_VAL_NONE )
	digitalWrite(ucg->pin_list[UCG_PIN_RST], 1);

      break;
    case UCG_COM_MSG_POWER_DOWN:
      break;
    case UCG_COM_MSG_DELAY:
      delayMicroseconds(arg);
      break;
    case UCG_COM_MSG_CHANGE_RESET_LINE:
      if ( ucg->pin_list[UCG_PIN_RST] != UCG_PIN_VAL_NONE )
	digitalWrite(ucg->pin_list[UCG_PIN_RST], arg);
      break;
    case UCG_COM_MSG_CHANGE_CS_LINE:
      if ( ucg->pin_list[UCG_PIN_CS] != UCG_PIN_VAL_NONE )
	digitalWrite(ucg->pin_list[UCG_PIN_CS], arg);
      break;
    case UCG_COM_MSG_CHANGE_CD_LINE:
      if ( arg == 0 )
	*ucg->data_port[UCG_PIN_CD] &= ~ucg->data_mask[UCG_PIN_CD];
      else
	*ucg->data_port[UCG_PIN_CD] |= ucg->data_mask[UCG_PIN_CD];
      //digitalWrite(ucg->pin_list[UCG_PIN_CD], arg);
      break;
    case UCG_COM_MSG_SEND_BYTE:
      ucg_com_arduino_port_d_send(arg, ucg->data_port[UCG_PIN_WR], ~ucg->data_mask[UCG_PIN_WR], ucg->data_mask[UCG_PIN_WR]);

      break;
    case UCG_COM_MSG_REPEAT_1_BYTE:
      while( arg > 0 ) {
	ucg_com_arduino_port_d_send(data[0], ucg->data_port[UCG_PIN_WR], ~ucg->data_mask[UCG_PIN_WR], ucg->data_mask[UCG_PIN_WR]);
	arg--;
      }
      break;
    case UCG_COM_MSG_REPEAT_2_BYTES:
      while( arg > 0 ) {
	ucg_com_arduino_port_d_send(data[0], ucg->data_port[UCG_PIN_WR], ~ucg->data_mask[UCG_PIN_WR], ucg->data_mask[UCG_PIN_WR]);
	ucg_com_arduino_port_d_send(data[1], ucg->data_port[UCG_PIN_WR], ~ucg->data_mask[UCG_PIN_WR], ucg->data_mask[UCG_PIN_WR]);
	arg--;
      }
      break;
    case UCG_COM_MSG_REPEAT_3_BYTES:
      while( arg > 0 ) {
	ucg_com_arduino_port_d_send(data[0], ucg->data_port[UCG_PIN_WR], ~ucg->data_mask[UCG_PIN_WR], ucg->data_mask[UCG_PIN_WR]);
	ucg_com_arduino_port_d_send(data[1], ucg->data_port[UCG_PIN_WR], ~ucg->data_mask[UCG_PIN_WR], ucg->data_mask[UCG_PIN_WR]);
	ucg_com_arduino_port_d_send(data[2], ucg->data_port[UCG_PIN_WR], ~ucg->data_mask[UCG_PIN_WR], ucg->data_mask[UCG_PIN_WR]);
	arg--;
      }
      break;
    case UCG_COM_MSG_SEND_STR:
      while( arg > 0 ) {
	ucg_com_arduino_port_d_send(*data++, ucg->data_port[UCG_PIN_WR], ~ucg->data_mask[UCG_PIN_WR], ucg->data_mask[UCG_PIN_WR]);
	arg--;
      }
      break;
    case UCG_COM_MSG_SEND_CD_DATA_SEQUENCE:
      while(arg > 0)
      {
	if ( *data != 0 )
	{
	  if ( *data == 1 )
	  {
	    *ucg->data_port[UCG_PIN_CD] &= ~ucg->data_mask[UCG_PIN_CD];
	  }
	  else
	  {
	    *ucg->data_port[UCG_PIN_CD] |= ucg->data_mask[UCG_PIN_CD];
	  }
	}
	data++;
	ucg_com_arduino_port_d_send(*data, ucg->data_port[UCG_PIN_WR], ~ucg->data_mask[UCG_PIN_WR], ucg->data_mask[UCG_PIN_WR]);	
	data++;
	arg--;
      }
      break;
  }
  return 1;
}

void Ucglib8BitPortD::begin(ucg_font_mode_fnptr font_mode)
{ 
  ucg_Init(&ucg, dev_cb, ext_cb, ucg_com_arduino_port_d); 
  ucg_SetFontMode(&ucg, font_mode);
}

#endif /* __AVR__ */

/*=========================================================================*/

static int16_t ucg_com_arduino_4wire_HW_SPI(ucg_t *ucg, int16_t msg, uint16_t arg, uint8_t *data)
{
  switch(msg)
  {
    case UCG_COM_MSG_POWER_UP:
      /* "data" is a pointer to ucg_com_info_t structure with the following information: */
      /*	((ucg_com_info_t *)data)->serial_clk_speed value in nanoseconds */
      /*	((ucg_com_info_t *)data)->parallel_clk_speed value in nanoseconds */
      
      /* setup pins */
    
      if ( ucg->pin_list[UCG_PIN_RST] != UCG_PIN_VAL_NONE )
	pinMode(ucg->pin_list[UCG_PIN_RST], OUTPUT);
      pinMode(ucg->pin_list[UCG_PIN_CD], OUTPUT);
      
      if ( ucg->pin_list[UCG_PIN_CS] != UCG_PIN_VAL_NONE )
	pinMode(ucg->pin_list[UCG_PIN_CS], OUTPUT);
      
      /* setup Arduino SPI */
      SPI.begin();
#if defined(__AVR__)
      SPI.setClockDivider( SPI_CLOCK_DIV2 );
#endif
#if defined(__SAM3X8E__)
      SPI.setClockDivider( (((ucg_com_info_t *)data)->serial_clk_speed * 84L + 999)/1000L );
#endif
      SPI.setDataMode(SPI_MODE0);
      SPI.setBitOrder(MSBFIRST);
      break;
    case UCG_COM_MSG_POWER_DOWN:
      SPI.end(); 
      break;
    case UCG_COM_MSG_DELAY:
      delayMicroseconds(arg);
      break;
    case UCG_COM_MSG_CHANGE_RESET_LINE:
      if ( ucg->pin_list[UCG_PIN_RST] != UCG_PIN_VAL_NONE )
	digitalWrite(ucg->pin_list[UCG_PIN_RST], arg);
      break;
    case UCG_COM_MSG_CHANGE_CS_LINE:
      if ( ucg->pin_list[UCG_PIN_CS] != UCG_PIN_VAL_NONE )
	digitalWrite(ucg->pin_list[UCG_PIN_CS], arg);
      break;
    case UCG_COM_MSG_CHANGE_CD_LINE:
      digitalWrite(ucg->pin_list[UCG_PIN_CD], arg);
      break;
    case UCG_COM_MSG_SEND_BYTE:
      SPI.transfer(arg); 
      break;
    case UCG_COM_MSG_REPEAT_1_BYTE:
      while( arg > 0 ) {
	SPI.transfer(data[0]);
	arg--;
      }
      break;
    case UCG_COM_MSG_REPEAT_2_BYTES:
      while( arg > 0 ) {
	SPI.transfer(data[0]);
	SPI.transfer(data[1]);
	arg--;
      }
      break;
    case UCG_COM_MSG_REPEAT_3_BYTES:
      while( arg > 0 ) {
	SPI.transfer(data[0]);
	SPI.transfer(data[1]);
	SPI.transfer(data[2]);
	arg--;
      }
      break;
    case UCG_COM_MSG_SEND_STR:
      while( arg > 0 ) {
	SPI.transfer(*data++);
	arg--;
      }
      break;
    case UCG_COM_MSG_SEND_CD_DATA_SEQUENCE:
      while(arg > 0)
      {
	if ( *data != 0 )
	{
	  if ( *data == 1 )
	  {
	    digitalWrite(ucg->pin_list[UCG_PIN_CD], 0);
	  }
	  else
	  {
	    digitalWrite(ucg->pin_list[UCG_PIN_CD], 1);
	  }
	}
	data++;
	SPI.transfer(*data);
	data++;
	arg--;
      }
      break;
  }
  return 1;
}

void Ucglib4WireHWSPI::begin(ucg_font_mode_fnptr font_mode)
{ 
  ucg_Init(&ucg, dev_cb, ext_cb, ucg_com_arduino_4wire_HW_SPI); 
  ucg_SetFontMode(&ucg, font_mode);
}


/*=========================================================================*/

void Ucglib::init(void) {
  uint8_t i;
  
  // do a dummy init so that something usefull is part of the ucg structure
  ucg_Init(&ucg, ucg_dev_default_cb, ucg_ext_none, (ucg_com_fnptr)0);

  // reset cursor position
  tx = 0;
  ty = 0;
  tdir = 0;	// default direction for Arduino print() 
  
  for( i = 0; i < UCG_PIN_COUNT; i++ )
    ucg.pin_list[i] = UCG_PIN_VAL_NONE;
  
}

size_t Ucglib::write(uint8_t c) { 
  ucg_int_t delta;
  delta = ucg_DrawGlyph(get_ucg(), get_tx(), get_ty(), get_tdir(), c); 
  switch(get_tdir()) {
    case 0: get_tx() += delta; break;
    case 1: get_ty() += delta; break;
    case 2: get_tx() -= delta; break;
    default: case 3: get_ty() -= delta; break;
  }
  return 1;
}
 

Начинать работу надо с компиляции blink (после установки софта https://github.com/rogerclarkmelbourne/Arduino_STM32)

В исходнике поменять ногу 13 на PB1. Если сетодиод начнет мигать, значит кроскомпиляция на arm настроена и загрука кода в модуль успешна. Только ТОГДА надо начинать компилить мои исходники и доставлять библиотеки, иначе не разберетесь.

 

 
 
Immortal
Offline
Зарегистрирован: 28.12.2013

pav2000, можете загрузить архив на яндекс диск или дропбокс?

jeka_tm
jeka_tm аватар
Offline
Зарегистрирован: 19.05.2013

классно получилось)

но вот по корпусу есть предложения на будущее

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

http://ru.aliexpress.com/item/Carbon-Steel-50Pcs-Bag-M3-8mm-Countersunk-Hexagon-Socket-Inside-Black-Head-Screws/32309356123.html?spm=2114.03020208.3.1.Zo5vtt&ws_ab_test=201526_1,201527_3_10_71_9_72_73_74_75_11_12,201409_2

да и цвет пластика применить другой например

2. ручку применять готовую, стоит копейки

http://ru.aliexpress.com/item/New-1PCS-Adjustable-6mm-Knurled-Shaft-Potentiometer-Volume-Control-Rotary-Knob/2034811612.html?spm=2114.03020208.3.38.y1Jjlo&ws_ab_test=201526_1,201527_3_71_72_73_74_75,201409_2

3. выключатель

http://ru.aliexpress.com/item/High-Quality-Snap-In-Rocker-LED-Indicator-Switch-3-Pin-On-Off-12V-Red/32435791706.html?spm=2114.03020208.3.69.wvIsrJ&ws_ab_test=201526_1,201527_3_71_72_73_74_75,201409_2

http://ru.aliexpress.com/item/2015-Hot-5-x-AC-250V-3A-2-Pin-ON-OFF-I-O-SPST-Snap-in/32374495371.html?spm=2114.03020208.3.2.wvIsrJ&ws_ab_test=201526_1,201527_3_71_72_73_74_75,201409_2

и наверно не стоит слишком много цветов на дисплее применять. даже темно синий на белом фоне прекранно выклядить будет, да и к корпусу больше подходить

ну это мое мнение

 

jeka_tm
jeka_tm аватар
Offline
Зарегистрирован: 19.05.2013
miaua
Offline
Зарегистрирован: 20.08.2015

На каком 3д принтере этот корпус печатался? Есть желание освоить печать...Но всему свое время...

 
miaua
Offline
Зарегистрирован: 20.08.2015

pav2000 пишет:

В качестве резервного питания использую литевый аккумулятор 18650, с блоком зарядки. Обычно устройство питается от 5 вольт. Автономно дережет часов 10-15.

Если запрограммировать отключение подсветки экрана, или поставить кнопку и т.д. То можно значительно увеличить время автономной работы... По даташиту потребление на 1группу светодиодов подсветки идет 15-20мА, а их там от 2х до 4х групп ( ! 80мА), в зависимости от размера дисплея...

 

Logik
Offline
Зарегистрирован: 05.08.2014

А расскажите немного подробней про организацию загрузки в maple.

Есть следующее - maple mini на stm32f103 внешне один в один Ваш, WindowsXP и свежайший arduino-1.6.5-r5. Добавляем в последний поддержку Duo - он выкачивает, ставит без замечаний. В папке, где проекты, делаем новую папку hardware, в неё распаковываем Arduino_STM32-master. Приступаем к драйверам и облом - install_drivers.bat ругается ехе-шники не запускаются, видно требуют Win7 или выше. Ладно, обратимся к первоисточнику, качаем libusb-win32-bin-1.2.6.0 - становится, теперь при подключении контролера в диспетчере устройств есть вкладка libusb, а в ней MapleDFU корректно стоит. Запускаем ардуино, выбираем плату Maple mini и версию Bootloader - Original. Пытаемся собрать и залить пустой проект, собирается, но не заливается, обнаруживается отсутствие драйвера порта - успешно ставим драйвер MapleSerial (ранее его не спрашивало и порта небыло). Теперь картина такая - после подключения (или ребута) контроллера наблюдается кратковременное , быстрое мигание светодиода на нем (ну bootloader работает) , затем медленное мигание (запустился залитый китайцами скетч) затем через гдето секунду все гаснет. Винда во время этих дел меняет драйвера - сразу запускает DFU, затем он исчезает и стартует MapleSerial, на нем и успокаивается. Это нормальное поведение, у Вас так же?

Аналогично ведет себя при попытке собрать и залить пустой проект, в сообщениях имеем

Изменена опция сборки, пересобираем все

 
Sketch uses 12 316 bytes (10%) of program storage space. Maximum is 122 880 bytes.
Global variables use 2 560 bytes of dynamic memory.
maple_loader v0.1
Resetting to bootloader via DTR pulse
Searching for DFU device [1EAF:0003]...
dfu-util - (C) 2007-2008 by OpenMoko Inc.
Couldn't find the DFU device: [1EAF:0003]
This program is Free Software and has ABSOLUTELY NO WARRANTY
 
Чем его стукнуть?
Там чего-то про настройку какого- то фильтра в доках писали, что Вы про это знаете?
 
pav2000
Offline
Зарегистрирован: 15.12.2014

Usb порт maple mini функционирует в двух режимах программирования (dfu) и эмуляции последовательного порта (serial).

Переключение между режимами работает у меня не четко. Есть возможность принудительного перевода в режим программирования. У платы есть две кнопки. Нажимаете на сброс а потом другую кнопку (та которая ближе к узкому краю платы) очень быстро. У меня это получается не с первого раза. Светодиод будет мигать бесконечно (добейтесь этого). Это режим программирования. Скейч должен залится. Светодиод погаснет.

У меня под убунту порт платы чаще переводится в режим программирования и очень трудно перевести в режим последовательного порта. Под вин хр и 7 я очень часто пользуюсь принудительным режимом который описан выше. А режим последовательного порта почти всегда доступен.

 

Logik
Offline
Зарегистрирован: 05.08.2014

Понял, спасибо,  буду бодатся дальше. Всетаки 72МГц по цене 16-и того стоят )))

pav2000
Offline
Зарегистрирован: 15.12.2014

Очередная версия кода.

Основные изменения:

1. Перешол на другую графическую библиотеку Adafruit_GFX_AS (идет в поставке с https://github.com/rogerclarkmelbourne/Arduino_STM32)

2. Русифицировал ее (спасибо arduinec  http://arduino.ru/forum/programmirovanie/rusifikatsiya-biblioteki-adafruit-gfx-i-vyvod-russkikh-bukv-na-displei-v-kodi) использовал его функция (кажется доработал) и встроенный шрифт. Адаптировал для Adafruit_GFX_AS. Скорость возрасла на порядок, перерисовка экрана почти не замета - используется spi через DMA. Очень нравится (+ еще 2 кбайта кода уменьшилось)

3. Adafruit_GFX_AS позволяет использовать несколько шрифтов. Разработал шрифт 8*16 и заменил стандартный Font16. 224 символа кодировка 1251 (из UTF8 перевожу через функцию см п.2)

Заменить Font16.c

// Font size 2

#include "Font32.h"
//#include <avr/pgmspace.h>
#ifdef __AVR__
 #include <avr/io.h>
 #include <avr/pgmspace.h>
#else
 #define PROGMEM
#endif 
PROGMEM const unsigned char widtbl_f16[224] =        // character width table
{
        5, 2, 6, 9, 7, 9, 9, 2,             // char 32 - 39
        4, 4, 7, 8, 3, 5, 2, 8,             // char 40 - 47
        8, 8, 8, 8, 8, 8, 8, 8,             // char 48 - 55
        8, 8, 2, 3, 8, 8, 8, 7,             // char 56 - 63
        9, 8, 8, 8, 8, 0, 8, 8,             // char 64 - 71
        8, 6, 8, 8, 8, 8, 8, 8,             // char 72 - 79
        8, 8, 9, 8, 8, 8, 8, 9,             // char 80 - 87
        8, 9, 8, 4, 8, 4, 9, 9,             // char 88 - 95
        4, 8, 8, 8, 8, 0, 7, 8,             // char 96 - 103
        8, 8, 5, 8, 7, 8, 8, 8,             // char 104 - 111
        8, 8, 7, 8, 7, 8, 8, 9,             // char 112 - 119
        8, 8, 8, 6, 2, 6, 8, 0,             // char 120 - 127
        9, 8, 3, 6, 7, 8, 8, 8,             // char 128 - 135
        8, 9, 9, 4, 9, 8, 9, 8,             // char 136 - 143
        9, 3, 3, 7, 7, 6, 9, 9,             // char 144 - 151
        0, 9, 9, 4, 9, 8, 9, 7,             // char 152 - 159
        8, 0, 8, 8, 6, 8, 2, 7,             // char 160 - 167
        8, 9, 8, 8, 8, 5, 9, 6,             // char 168 - 175
        5, 8, 6, 8, 6, 9, 8, 2,             // char 176 - 183
        8, 9, 8, 8, 5, 8, 8, 8,             // char 184 - 191
        8, 8, 8, 8, 9, 8, 9, 8,             // char 192 - 199
        8, 8, 8, 9, 8, 8, 8, 8,             // char 200 - 207
        8, 8, 8, 9, 8, 8, 9, 8,             // char 208 - 215
        8, 9, 9, 9, 8, 7, 9, 8,             // char 216 - 223
        8, 8, 8, 6, 9, 8, 9, 7,             // char 224 - 231
        8, 8, 8, 9, 8, 8, 8, 8,             // char 232 - 239
        8, 8, 6, 8, 8, 8, 9, 8,             // char 240 - 247
        8, 9, 9, 9, 8, 7, 9, 8              // char 248 - 255
};

// Row format, MSB left

PROGMEM const unsigned char chr_f16_20[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,    // row 1 - 11
        0x00, 0x00, 0x00, 0x00, 0x00                                         // row 12 - 16
}; PROGMEM const unsigned char chr_f16_21[16] =         // 1 byte per row
{
        0x00, 0x00, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x00,    // row 1 - 11
        0x00, 0xC0, 0xC0, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_22[16] =         // 1 byte per row
{
        0x00, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0x00, 0x00, 0x00, 0x00, 0x00,    // row 1 - 11
        0x00, 0x00, 0x00, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_23[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0x0D, 0x80, 0x09, 0x80, 0x19, 0x80, 0x7F, 0x80,    // row 1 - 6
        0x7F, 0x80, 0x13, 0x00, 0x33, 0x00, 0xFF, 0x80, 0xFF, 0x80, 0x26, 0x00,    // row 7 - 12
        0x66, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_24[16] =         // 1 byte per row
{
        0x00, 0x00, 0x10, 0x10, 0x7C, 0xFC, 0xD0, 0xF0, 0x7C, 0x1E, 0x16,    // row 1 - 11
        0x96, 0xFE, 0x7C, 0x10, 0x10                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_25[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00,    // row 1 - 6
        0x71, 0x80, 0x06, 0x00, 0x38, 0x00, 0xC7, 0x00, 0x08, 0x80, 0x08, 0x80,    // row 7 - 12
        0x08, 0x80, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_26[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x7E, 0x00, 0x62, 0x00, 0x60, 0x00,    // row 1 - 6
        0x30, 0x00, 0x78, 0x00, 0x5D, 0x80, 0xCD, 0x80, 0xC7, 0x80, 0xE7, 0x00,    // row 7 - 12
        0x7F, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x00, 0x00                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_27[16] =         // 1 byte per row
{
        0x00, 0x00, 0xC0, 0xC0, 0xC0, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00,    // row 1 - 11
        0x00, 0x00, 0x00, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_28[16] =         // 1 byte per row
{
        0x00, 0x00, 0x30, 0x60, 0x60, 0x60, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0,    // row 1 - 11
        0xC0, 0x60, 0x60, 0x60, 0x30                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_29[16] =         // 1 byte per row
{
        0x00, 0x00, 0xC0, 0x60, 0x60, 0x20, 0x30, 0x30, 0x30, 0x30, 0x30,    // row 1 - 11
        0x30, 0x60, 0x60, 0x60, 0xC0                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_2A[16] =         // 1 byte per row
{
        0x00, 0x00, 0x10, 0x10, 0xD6, 0x7C, 0x7C, 0xD6, 0x10, 0x10, 0x00,    // row 1 - 11
        0x00, 0x00, 0x00, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_2B[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0xFF, 0xFF, 0x18,    // row 1 - 11
        0x18, 0x18, 0x00, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_2C[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,    // row 1 - 11
        0x60, 0x60, 0x60, 0x60, 0xC0                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_2D[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xF8,    // row 1 - 11
        0x00, 0x00, 0x00, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_2E[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,    // row 1 - 11
        0xC0, 0xC0, 0xC0, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_2F[16] =         // 1 byte per row
{
        0x00, 0x00, 0x03, 0x06, 0x06, 0x0C, 0x0C, 0x18, 0x18, 0x18, 0x30,    // row 1 - 11
        0x30, 0x60, 0x60, 0xC0, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_30[16] =         // 1 byte per row
{
        0x00, 0x00, 0x3C, 0x7E, 0x67, 0xC3, 0xC3, 0xDB, 0xDB, 0xC3, 0xC3,    // row 1 - 11
        0x66, 0x7E, 0x3C, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_31[16] =         // 1 byte per row
{
        0x00, 0x00, 0x38, 0x78, 0x58, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,    // row 1 - 11
        0x18, 0xFF, 0xFF, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_32[16] =         // 1 byte per row
{
        0x00, 0x00, 0x7C, 0xFE, 0x83, 0x03, 0x03, 0x06, 0x0C, 0x18, 0x30,    // row 1 - 11
        0x60, 0xFF, 0xFF, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_33[16] =         // 1 byte per row
{
        0x00, 0x00, 0x7C, 0xFE, 0x83, 0x03, 0x03, 0x3E, 0x3E, 0x07, 0x03,    // row 1 - 11
        0x83, 0xFF, 0x7C, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_34[16] =         // 1 byte per row
{
        0x00, 0x00, 0x0E, 0x0E, 0x1E, 0x36, 0x26, 0x66, 0xC6, 0xFF, 0xFF,    // row 1 - 11
        0x06, 0x06, 0x06, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_35[16] =         // 1 byte per row
{
        0x00, 0x00, 0xFE, 0xFE, 0xC0, 0xC0, 0xFC, 0xFE, 0x87, 0x03, 0x03,    // row 1 - 11
        0x87, 0xFE, 0x7C, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_36[16] =         // 1 byte per row
{
        0x00, 0x00, 0x3C, 0x7E, 0x62, 0xC0, 0xDC, 0xFE, 0xE7, 0xC3, 0xC3,    // row 1 - 11
        0xE7, 0x7E, 0x3C, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_37[16] =         // 1 byte per row
{
        0x00, 0x00, 0xFF, 0xFF, 0x07, 0x06, 0x0E, 0x0C, 0x0C, 0x18, 0x18,    // row 1 - 11
        0x30, 0x30, 0x60, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_38[16] =         // 1 byte per row
{
        0x00, 0x00, 0x3C, 0x7E, 0xC3, 0xC3, 0xC3, 0x7E, 0x7E, 0xC3, 0xC3,    // row 1 - 11
        0xC3, 0xFF, 0x3C, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_39[16] =         // 1 byte per row
{
        0x00, 0x00, 0x3C, 0x7E, 0xE7, 0xC3, 0xC3, 0xE7, 0x7F, 0x3B, 0x03,    // row 1 - 11
        0x46, 0x7E, 0x3C, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_3A[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xC0, 0xC0, 0x00, 0x00,    // row 1 - 11
        0xC0, 0xC0, 0xC0, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_3B[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x60, 0x60, 0x00, 0x00,    // row 1 - 11
        0x60, 0x60, 0x60, 0x60, 0xC0                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_3C[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0F, 0x3C, 0xE0, 0xE0, 0x3C,    // row 1 - 11
        0x0F, 0x01, 0x00, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_3D[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF,    // row 1 - 11
        0xFF, 0x00, 0x00, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_3E[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xF0, 0x3C, 0x07, 0x07, 0x3C,    // row 1 - 11
        0xF0, 0x80, 0x00, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_3F[16] =         // 1 byte per row
{
        0x00, 0x00, 0x7C, 0xFE, 0x86, 0x06, 0x0C, 0x1C, 0x30, 0x30, 0x30,    // row 1 - 11
        0x00, 0x30, 0x30, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_40[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x3F, 0x00, 0x73, 0x80,    // row 1 - 6
        0xE1, 0x80, 0xCF, 0x80, 0xDF, 0x80, 0xD9, 0x80, 0xDF, 0x80, 0xCF, 0x80,    // row 7 - 12
        0x60, 0x00, 0x71, 0x00, 0x3F, 0x80, 0x1F, 0x00                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_41[16] =         // 1 byte per row
{
        0x00, 0x00, 0x18, 0x3C, 0x3C, 0x3C, 0x3C, 0x66, 0x66, 0x7E, 0x7E,    // row 1 - 11
        0x66, 0xE7, 0xC3, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_42[16] =         // 1 byte per row
{
        0x00, 0x00, 0xFE, 0xFF, 0xC3, 0xC3, 0xC3, 0xFE, 0xFE, 0xC3, 0xC3,    // row 1 - 11
        0xC3, 0xFF, 0xFE, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_43[16] =         // 1 byte per row
{
        0x00, 0x00, 0x1E, 0x7F, 0x61, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0,    // row 1 - 11
        0x61, 0x7F, 0x1E, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_44[16] =         // 1 byte per row
{
        0x00, 0x00, 0xF8, 0xFE, 0xC6, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,    // row 1 - 11
        0xC6, 0xFE, 0xF8, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_45[1] = { 0 };   // empty character
PROGMEM const unsigned char chr_f16_46[16] =         // 1 byte per row
{
        0x00, 0x00, 0xFF, 0xFF, 0xC0, 0xC0, 0xC0, 0xFE, 0xFE, 0xC0, 0xC0,    // row 1 - 11
        0xC0, 0xC0, 0xC0, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_47[16] =         // 1 byte per row
{
        0x00, 0x00, 0x1E, 0x7F, 0x61, 0xC0, 0xC0, 0xC0, 0xCF, 0xCF, 0xC3,    // row 1 - 11
        0x63, 0x7F, 0x1E, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_48[16] =         // 1 byte per row
{
        0x00, 0x00, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xFF, 0xFF, 0xC3, 0xC3,    // row 1 - 11
        0xC3, 0xC3, 0xC3, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_49[16] =         // 1 byte per row
{
        0x00, 0x00, 0xFC, 0xFC, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,    // row 1 - 11
        0x30, 0xFC, 0xFC, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_4A[16] =         // 1 byte per row
{
        0x00, 0x00, 0x1F, 0x1F, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,    // row 1 - 11
        0x83, 0xFF, 0x7C, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_4B[16] =         // 1 byte per row
{
        0x00, 0x00, 0xC7, 0xCE, 0xCC, 0xD8, 0xF8, 0xF8, 0xF8, 0xCC, 0xCE,    // row 1 - 11
        0xC6, 0xC7, 0xC3, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_4C[16] =         // 1 byte per row
{
        0x00, 0x00, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0,    // row 1 - 11
        0xC0, 0xFF, 0xFF, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_4D[16] =         // 1 byte per row
{
        0x00, 0x00, 0xE7, 0xE7, 0xE7, 0xFF, 0xFF, 0xDB, 0xDB, 0xDB, 0xC3,    // row 1 - 11
        0xC3, 0xC3, 0xC3, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_4E[16] =         // 1 byte per row
{
        0x00, 0x00, 0xE3, 0xE3, 0xE3, 0xF3, 0xD3, 0xDB, 0xDB, 0xCB, 0xCF,    // row 1 - 11
        0xC7, 0xC7, 0xC7, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_4F[16] =         // 1 byte per row
{
        0x00, 0x00, 0x3C, 0x7E, 0x66, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,    // row 1 - 11
        0x66, 0x7E, 0x3C, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_50[16] =         // 1 byte per row
{
        0x00, 0x00, 0xFC, 0xFF, 0xC3, 0xC3, 0xC3, 0xFF, 0xFC, 0xC0, 0xC0,    // row 1 - 11
        0xC0, 0xC0, 0xC0, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_51[16] =         // 1 byte per row
{
        0x00, 0x00, 0x3C, 0x7E, 0x66, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,    // row 1 - 11
        0x67, 0x7E, 0x3C, 0x06, 0x02                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_52[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0xFC, 0x00, 0xFF, 0x00, 0xC3, 0x00, 0xC3, 0x00,    // row 1 - 6
        0xC3, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xCE, 0x00, 0xC7, 0x00, 0xC3, 0x00,    // row 7 - 12
        0xC3, 0x00, 0xC3, 0x80, 0x00, 0x00, 0x00, 0x00                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_53[16] =         // 1 byte per row
{
        0x00, 0x00, 0x3C, 0x7E, 0xC2, 0xC0, 0xE0, 0x7C, 0x3E, 0x07, 0x03,    // row 1 - 11
        0x83, 0xFF, 0x7C, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_54[16] =         // 1 byte per row
{
        0x00, 0x00, 0xFF, 0xFF, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,    // row 1 - 11
        0x18, 0x18, 0x18, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_55[16] =         // 1 byte per row
{
        0x00, 0x00, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,    // row 1 - 11
        0xE7, 0x7E, 0x3C, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_56[16] =         // 1 byte per row
{
        0x00, 0x00, 0xC3, 0xE7, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x3C,    // row 1 - 11
        0x3C, 0x3C, 0x3C, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_57[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0xC0, 0x80, 0xC0, 0x80, 0xC0, 0x80, 0xCC, 0x80,    // row 1 - 6
        0x6D, 0x80, 0x6D, 0x80, 0x6D, 0x80, 0x6D, 0x80, 0x73, 0x80, 0x73, 0x80,    // row 7 - 12
        0x73, 0x80, 0x73, 0x80, 0x00, 0x00, 0x00, 0x00                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_58[16] =         // 1 byte per row
{
        0x00, 0x00, 0xE7, 0x66, 0x6E, 0x3C, 0x3C, 0x18, 0x18, 0x3C, 0x3C,    // row 1 - 11
        0x66, 0x66, 0xE7, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_59[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0xE1, 0x80, 0x61, 0x80, 0x73, 0x80, 0x33, 0x00,    // row 1 - 6
        0x3F, 0x00, 0x1E, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0C, 0x00,    // row 7 - 12
        0x0C, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_5A[16] =         // 1 byte per row
{
        0x00, 0x00, 0xFF, 0xFF, 0x07, 0x0E, 0x0C, 0x1C, 0x38, 0x30, 0x70,    // row 1 - 11
        0xE0, 0xFF, 0xFF, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_5B[16] =         // 1 byte per row
{
        0x00, 0x00, 0xF0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0,    // row 1 - 11
        0xC0, 0xC0, 0xC0, 0xC0, 0xF0                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_5C[16] =         // 1 byte per row
{
        0x00, 0x00, 0xC0, 0x40, 0x60, 0x20, 0x30, 0x10, 0x18, 0x08, 0x0C,    // row 1 - 11
        0x04, 0x06, 0x02, 0x03, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_5D[16] =         // 1 byte per row
{
        0x00, 0x00, 0xF0, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,    // row 1 - 11
        0x30, 0x30, 0x30, 0x30, 0xF0                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_5E[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x3E, 0x00, 0x77, 0x00, 0xE3, 0x80,    // row 1 - 6
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,    // row 7 - 12
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_5F[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,    // row 1 - 6
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,    // row 7 - 12
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_60[16] =         // 1 byte per row
{
        0x00, 0xC0, 0x60, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,    // row 1 - 11
        0x00, 0x00, 0x00, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_61[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x7F, 0x43, 0x1F, 0x7F, 0xE3,    // row 1 - 11
        0xC7, 0xFF, 0x7B, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_62[16] =         // 1 byte per row
{
        0x00, 0x00, 0xC0, 0xC0, 0xC0, 0xDC, 0xFE, 0xE7, 0xC3, 0xC3, 0xC3,    // row 1 - 11
        0xE7, 0xFE, 0xDC, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_63[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x7F, 0xE1, 0xC0, 0xC0, 0xC0,    // row 1 - 11
        0xE1, 0x7F, 0x3E, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_64[16] =         // 1 byte per row
{
        0x00, 0x00, 0x03, 0x03, 0x03, 0x3B, 0x7F, 0xE7, 0xC3, 0xC3, 0xC3,    // row 1 - 11
        0xE7, 0x7F, 0x3B, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_65[1] = { 0 };   // empty character
PROGMEM const unsigned char chr_f16_66[16] =         // 1 byte per row
{
        0x00, 0x00, 0x1E, 0x3E, 0x30, 0xFE, 0xFE, 0x30, 0x30, 0x30, 0x30,    // row 1 - 11
        0x30, 0x30, 0x30, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_67[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x3B, 0x7F, 0xE7, 0xC3, 0xC3, 0xC3,    // row 1 - 11
        0xE7, 0x7F, 0x3B, 0x43, 0x7F                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_68[16] =         // 1 byte per row
{
        0x00, 0x00, 0xC0, 0xC0, 0xC0, 0xDE, 0xFF, 0xE3, 0xC3, 0xC3, 0xC3,    // row 1 - 11
        0xC3, 0xC3, 0xC3, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_69[16] =         // 1 byte per row
{
        0x00, 0x18, 0x18, 0x18, 0x00, 0x78, 0x78, 0x18, 0x18, 0x18, 0x18,    // row 1 - 11
        0x18, 0xFF, 0xFF, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_6A[16] =         // 1 byte per row
{
        0x00, 0x18, 0x18, 0x18, 0x00, 0x78, 0x78, 0x18, 0x18, 0x18, 0x18,    // row 1 - 11
        0x18, 0x18, 0x18, 0x18, 0xF8                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_6B[16] =         // 1 byte per row
{
        0x00, 0x00, 0xC0, 0xC0, 0xC0, 0xCE, 0xDC, 0xD8, 0xF0, 0xF8, 0xDC,    // row 1 - 11
        0xCC, 0xC6, 0xC7, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_6C[16] =         // 1 byte per row
{
        0x00, 0x00, 0xF0, 0xF0, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,    // row 1 - 11
        0x30, 0x3E, 0x1E, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_6D[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0xF6, 0xFF, 0xDB, 0xDB, 0xDB, 0xDB,    // row 1 - 11
        0xDB, 0xDB, 0xDB, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_6E[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0xDE, 0xFF, 0xE3, 0xC3, 0xC3, 0xC3,    // row 1 - 11
        0xC3, 0xC3, 0xC3, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_6F[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x7E, 0xE7, 0xC3, 0xC3, 0xC3,    // row 1 - 11
        0xE7, 0x7E, 0x3C, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_70[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0xDC, 0xFE, 0xE7, 0xC3, 0xC3, 0xC3,    // row 1 - 11
        0xE7, 0xFE, 0xDC, 0xC0, 0xC0                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_71[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x3B, 0x7F, 0xE7, 0xC3, 0xC3, 0xC3,    // row 1 - 11
        0xE7, 0x7F, 0x3B, 0x03, 0x03                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_72[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0xDE, 0xFE, 0xE0, 0xC0, 0xC0, 0xC0,    // row 1 - 11
        0xC0, 0xC0, 0xC0, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_73[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0xFF, 0xC1, 0xFC, 0x7E, 0x07,    // row 1 - 11
        0x83, 0xFF, 0x7E, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_74[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x30, 0x30, 0xFE, 0xFE, 0x30, 0x30, 0x30, 0x30,    // row 1 - 11
        0x30, 0x3E, 0x1E, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_75[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,    // row 1 - 11
        0xC7, 0xFF, 0x7B, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_76[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0xC3, 0xE7, 0x66, 0x66, 0x66, 0x3C,    // row 1 - 11
        0x3C, 0x3C, 0x18, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_77[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x80,    // row 1 - 6
        0xC0, 0x80, 0xCC, 0x80, 0x4C, 0x80, 0x6D, 0x80, 0x7F, 0x80, 0x73, 0x80,    // row 7 - 12
        0x33, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_78[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0xE7, 0x66, 0x3C, 0x3C, 0x18, 0x3C,    // row 1 - 11
        0x3C, 0x66, 0xE7, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_79[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0xE7, 0x67, 0x66, 0x76, 0x3E, 0x3C,    // row 1 - 11
        0x1C, 0x1C, 0x18, 0x18, 0x70                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_7A[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x06, 0x0C, 0x18, 0x30,    // row 1 - 11
        0x60, 0xFF, 0xFF, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_7B[16] =         // 1 byte per row
{
        0x00, 0x00, 0x1C, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0xC0, 0x30,    // row 1 - 11
        0x30, 0x30, 0x30, 0x30, 0x30                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_7C[16] =         // 1 byte per row
{
        0x00, 0x00, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0,    // row 1 - 11
        0xC0, 0xC0, 0xC0, 0xC0, 0xC0                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_7D[16] =         // 1 byte per row
{
        0x00, 0x00, 0xE0, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x0C, 0x30,    // row 1 - 11
        0x30, 0x30, 0x30, 0x30, 0x30                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_7E[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71, 0xFF, 0x8E, 0x00,    // row 1 - 11
        0x00, 0x00, 0x00, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_7F[1] = { 0 };   // empty character
PROGMEM const unsigned char chr_f16_80[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00,    // row 1 - 6
        0x77, 0x00, 0x7F, 0x80, 0x73, 0x80, 0x71, 0x80, 0x71, 0x80, 0x71, 0x80,    // row 7 - 12
        0x71, 0x80, 0x71, 0x80, 0x01, 0x80, 0x03, 0x80                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_81[16] =         // 1 byte per row
{
        0x18, 0x00, 0xFF, 0xFF, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0,    // row 1 - 11
        0xC0, 0xC0, 0xC0, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_82[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,    // row 1 - 11
        0x60, 0x60, 0x60, 0x60, 0xC0                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_83[16] =         // 1 byte per row
{
        0x00, 0x0C, 0x18, 0x30, 0x00, 0xFC, 0xFC, 0xC0, 0xC0, 0xC0, 0xC0,    // row 1 - 11
        0xC0, 0xC0, 0xC0, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_84[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,    // row 1 - 11
        0x66, 0x66, 0x66, 0x66, 0xCC                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_85[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,    // row 1 - 11
        0xDB, 0xDB, 0xDB, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_86[16] =         // 1 byte per row
{
        0x00, 0x00, 0x18, 0x18, 0x18, 0xFF, 0xFF, 0x18, 0x18, 0x18, 0x18,    // row 1 - 11
        0x18, 0x18, 0x18, 0x18, 0x18                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_87[16] =         // 1 byte per row
{
        0x00, 0x00, 0x18, 0x18, 0x18, 0xFF, 0xFF, 0x18, 0x18, 0x18, 0x18,    // row 1 - 11
        0xFF, 0xFF, 0x18, 0x18, 0x18                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_88[16] =         // 1 byte per row
{
        0x00, 0x00, 0x0E, 0x3F, 0x31, 0x70, 0x60, 0xFC, 0x60, 0xF8, 0x60,    // row 1 - 11
        0x31, 0x3F, 0x1E, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_89[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x90, 0x00, 0x90, 0x00, 0x63, 0x00,    // row 1 - 6
        0x0C, 0x00, 0x30, 0x00, 0xE0, 0x00, 0x80, 0x00, 0x63, 0x00, 0x94, 0x80,    // row 7 - 12
        0x94, 0x80, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_8A[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x24, 0x00, 0x24, 0x00,    // row 1 - 6
        0x24, 0x00, 0x27, 0x00, 0x27, 0x80, 0x24, 0x80, 0x64, 0x80, 0x65, 0x80,    // row 7 - 12
        0xE7, 0x80, 0xC7, 0x00, 0x00, 0x00, 0x00, 0x00                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_8B[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x30, 0x60, 0xC0, 0x60,    // row 1 - 11
        0x30, 0x10, 0x00, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_8C[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0xC4, 0x00, 0xC4, 0x00, 0xC4, 0x00, 0xC4, 0x00,    // row 1 - 6
        0xC4, 0x00, 0xFF, 0x00, 0xFF, 0x80, 0xC4, 0x80, 0xC4, 0x80, 0xC5, 0x80,    // row 7 - 12
        0xC7, 0x80, 0xC7, 0x00, 0x00, 0x00, 0x00, 0x00                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_8D[16] =         // 1 byte per row
{
        0x18, 0x00, 0xC7, 0xCE, 0xCC, 0xD8, 0xF8, 0xF8, 0xF8, 0xCC, 0xCE,    // row 1 - 11
        0xC6, 0xC7, 0xC3, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_8E[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00,    // row 1 - 6
        0x77, 0x00, 0x7F, 0x80, 0x73, 0x80, 0x71, 0x80, 0x71, 0x80, 0x71, 0x80,    // row 7 - 12
        0x71, 0x80, 0x71, 0x80, 0x00, 0x00, 0x00, 0x00                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_8F[16] =         // 1 byte per row
{
        0x00, 0x00, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7,    // row 1 - 11
        0xC7, 0xFF, 0xFF, 0x18, 0x18                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_90[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0xFE, 0x00,    // row 1 - 6
        0xFE, 0x00, 0x70, 0x00, 0x7F, 0x00, 0x7F, 0x80, 0x71, 0x80, 0x71, 0x80,    // row 7 - 12
        0x71, 0x80, 0x73, 0x80, 0x03, 0x00, 0x07, 0x00                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_91[16] =         // 1 byte per row
{
        0x00, 0x00, 0x60, 0xC0, 0xC0, 0xC0, 0xC0, 0x00, 0x00, 0x00, 0x00,    // row 1 - 11
        0x00, 0x00, 0x00, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_92[16] =         // 1 byte per row
{
        0x00, 0x00, 0x60, 0x60, 0x60, 0x60, 0xC0, 0x00, 0x00, 0x00, 0x00,    // row 1 - 11
        0x00, 0x00, 0x00, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_93[16] =         // 1 byte per row
{
        0x00, 0x00, 0x66, 0xCC, 0xCC, 0xCC, 0xCC, 0x00, 0x00, 0x00, 0x00,    // row 1 - 11
        0x00, 0x00, 0x00, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_94[16] =         // 1 byte per row
{
        0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0xCC, 0x00, 0x00, 0x00, 0x00,    // row 1 - 11
        0x00, 0x00, 0x00, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_95[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0xFC, 0xFC, 0xFC, 0xFC, 0x78,    // row 1 - 11
        0x00, 0x00, 0x00, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_96[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,    // row 1 - 6
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x80, 0xFF, 0x80, 0x00, 0x00,    // row 7 - 12
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_97[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,    // row 1 - 6
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x80, 0xFF, 0x80, 0x00, 0x00,    // row 7 - 12
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_98[1] = { 0 };   // empty character
PROGMEM const unsigned char chr_f16_99[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0xED, 0x80, 0x4D, 0x80, 0x4A, 0x80, 0x4A, 0x80,    // row 1 - 6
        0x48, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,    // row 7 - 12
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_9A[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00,    // row 1 - 6
        0x3C, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x3F, 0x80, 0x2D, 0x80, 0x6C, 0x80,    // row 7 - 12
        0xEF, 0x80, 0xCF, 0x00, 0x00, 0x00, 0x00, 0x00                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_9B[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xC0, 0x60, 0x30, 0x60,    // row 1 - 11
        0xC0, 0x80, 0x00, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_9C[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xCC, 0x00,    // row 1 - 6
        0xCC, 0x00, 0xCC, 0x00, 0xFC, 0x00, 0xFF, 0x80, 0xCD, 0x80, 0xCC, 0x80,    // row 7 - 12
        0xCF, 0x80, 0xCF, 0x00, 0x00, 0x00, 0x00, 0x00                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_9D[16] =         // 1 byte per row
{
        0x00, 0x06, 0x0C, 0x18, 0x00, 0xCE, 0xDC, 0xD8, 0xF0, 0xF8, 0xDC,    // row 1 - 11
        0xCC, 0xC6, 0xC7, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_9E[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0xFE, 0x00,    // row 1 - 6
        0xFE, 0x00, 0x70, 0x00, 0x7F, 0x00, 0x7F, 0x00, 0x73, 0x00, 0x73, 0x80,    // row 7 - 12
        0x73, 0x80, 0x73, 0x80, 0x00, 0x00, 0x00, 0x00                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_9F[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0xE6, 0xE6, 0xE6, 0xE6, 0xE6, 0xE6,    // row 1 - 11
        0xE6, 0xFE, 0xFE, 0x18, 0x18                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_A0[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,    // row 1 - 11
        0x00, 0x00, 0x00, 0x1B, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_A1[1] = { 0 };   // empty character
PROGMEM const unsigned char chr_f16_A2[16] =         // 1 byte per row
{
        0x00, 0x44, 0x38, 0x00, 0x00, 0xE7, 0x67, 0x66, 0x76, 0x3E, 0x3C,    // row 1 - 11
        0x1C, 0x1C, 0x18, 0x18, 0x70                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_A3[16] =         // 1 byte per row
{
        0x00, 0x00, 0x1F, 0x1F, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,    // row 1 - 11
        0x83, 0xFF, 0x7C, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_A4[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x44, 0x44, 0x44, 0x7C,    // row 1 - 11
        0x00, 0x00, 0x00, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_A5[16] =         // 1 byte per row
{
        0x03, 0x03, 0xFF, 0xFF, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0,    // row 1 - 11
        0xC0, 0xC0, 0xC0, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_A6[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x00, 0x00,    // row 1 - 11
        0xC0, 0xC0, 0xC0, 0xC0, 0xC0                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_A7[16] =         // 1 byte per row
{
        0x00, 0x00, 0x3C, 0x7C, 0x60, 0x70, 0x38, 0xFC, 0xC6, 0xE6, 0x76,    // row 1 - 11
        0x3C, 0x0E, 0x06, 0x7E, 0x7C                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_A8[16] =         // 1 byte per row
{
        0x6C, 0x00, 0xFF, 0xFF, 0xC0, 0xC0, 0xC0, 0xFE, 0xFE, 0xC0, 0xC0,    // row 1 - 11
        0xC0, 0xFF, 0xFF, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_A9[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x61, 0x80, 0x5E, 0x80,    // row 1 - 6
        0xB0, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0xB0, 0x00, 0x5E, 0x80, 0x61, 0x80,    // row 7 - 12
        0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_AA[16] =         // 1 byte per row
{
        0x00, 0x00, 0x1E, 0x7F, 0x61, 0xC0, 0xC0, 0xFE, 0xFE, 0xC0, 0xE0,    // row 1 - 11
        0x61, 0x7F, 0x1E, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_AB[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x33, 0x66, 0xCC, 0x66,    // row 1 - 11
        0x33, 0x11, 0x00, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_AC[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x03, 0x03,    // row 1 - 11
        0x00, 0x00, 0x00, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_AD[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xF8,    // row 1 - 11
        0x00, 0x00, 0x00, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_AE[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x61, 0x80, 0x7E, 0x80,    // row 1 - 6
        0xA1, 0x00, 0xA1, 0x00, 0xBE, 0x00, 0xA3, 0x00, 0x61, 0x80, 0x61, 0x80,    // row 7 - 12
        0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_AF[16] =         // 1 byte per row
{
        0xCC, 0x00, 0xFC, 0xFC, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,    // row 1 - 11
        0x30, 0xFC, 0xFC, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_B0[16] =         // 1 byte per row
{
        0x00, 0x00, 0x70, 0x88, 0x88, 0x88, 0x70, 0x00, 0x00, 0x00, 0x00,    // row 1 - 11
        0x00, 0x00, 0x00, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_B1[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0xFF, 0xFF, 0x18, 0x18,    // row 1 - 11
        0x18, 0xFF, 0xFF, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_B2[16] =         // 1 byte per row
{
        0x00, 0x00, 0xFC, 0xFC, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,    // row 1 - 11
        0x30, 0xFC, 0xFC, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_B3[16] =         // 1 byte per row
{
        0x00, 0x18, 0x18, 0x18, 0x00, 0x78, 0x78, 0x18, 0x18, 0x18, 0x18,    // row 1 - 11
        0x18, 0xFF, 0xFF, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_B4[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x0C, 0x0C, 0xFC, 0xFC, 0xC0, 0xC0, 0xC0, 0xC0,    // row 1 - 11
        0xC0, 0xC0, 0xC0, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_B5[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC3, 0x00,    // row 1 - 6
        0xC3, 0x00, 0xC3, 0x00, 0xC3, 0x00, 0xC3, 0x00, 0xC3, 0x00, 0xC3, 0x00,    // row 7 - 12
        0xFF, 0x80, 0xFD, 0x80, 0xC0, 0x00, 0xC0, 0x00                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_B6[16] =         // 1 byte per row
{
        0x00, 0x00, 0x3F, 0x7D, 0xFD, 0xFD, 0xFD, 0x7D, 0x1D, 0x05, 0x05,    // row 1 - 11
        0x05, 0x05, 0x05, 0x05, 0x05                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_B7[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xC0, 0xC0, 0x00,    // row 1 - 11
        0x00, 0x00, 0x00, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_B8[16] =         // 1 byte per row
{
        0x00, 0x00, 0x6C, 0x6C, 0x00, 0x3C, 0x7E, 0xC3, 0xFF, 0xFF, 0xC0,    // row 1 - 11
        0xE1, 0x7F, 0x3E, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_B9[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0x62, 0x00, 0x66, 0x00, 0x7C, 0x00, 0x7C, 0x00,    // row 1 - 6
        0x7C, 0x00, 0x7C, 0x00, 0x7D, 0x80, 0x7D, 0x80, 0x7D, 0x80, 0x6D, 0x80,    // row 7 - 12
        0xEC, 0x00, 0xCD, 0x80, 0x00, 0x00, 0x00, 0x00                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_BA[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x7F, 0xE1, 0xFE, 0xFE, 0xC0,    // row 1 - 11
        0xE1, 0x7F, 0x3E, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_BB[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0xCC, 0x66, 0x33, 0x66,    // row 1 - 11
        0xCC, 0x88, 0x00, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_BC[16] =         // 1 byte per row
{
        0x00, 0x18, 0x18, 0x18, 0x00, 0x78, 0x78, 0x18, 0x18, 0x18, 0x18,    // row 1 - 11
        0x18, 0x18, 0x18, 0x18, 0xF8                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_BD[16] =         // 1 byte per row
{
        0x00, 0x00, 0x3C, 0x7E, 0xC2, 0xC0, 0xE0, 0x7C, 0x3E, 0x07, 0x03,    // row 1 - 11
        0x83, 0xFF, 0x7C, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_BE[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0xFF, 0xC1, 0xFC, 0x7E, 0x07,    // row 1 - 11
        0x83, 0xFF, 0x7E, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_BF[16] =         // 1 byte per row
{
        0x00, 0x00, 0x6C, 0x6C, 0x00, 0x78, 0x78, 0x18, 0x18, 0x18, 0x18,    // row 1 - 11
        0x18, 0xFF, 0xFF, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_C0[16] =         // 1 byte per row
{
        0x00, 0x00, 0x18, 0x3C, 0x3C, 0x3C, 0x3C, 0x66, 0x66, 0x7E, 0x7E,    // row 1 - 11
        0x66, 0xE7, 0xC3, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_C1[16] =         // 1 byte per row
{
        0x00, 0x00, 0xFF, 0xFF, 0xC0, 0xC0, 0xC0, 0xFC, 0xFF, 0xC3, 0xC3,    // row 1 - 11
        0xC3, 0xFF, 0xFC, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_C2[16] =         // 1 byte per row
{
        0x00, 0x00, 0xFE, 0xFF, 0xC3, 0xC3, 0xC3, 0xFE, 0xFE, 0xC3, 0xC3,    // row 1 - 11
        0xC3, 0xFF, 0xFE, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_C3[16] =         // 1 byte per row
{
        0x00, 0x00, 0xFF, 0xFF, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0,    // row 1 - 11
        0xC0, 0xC0, 0xC0, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_C4[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0x7F, 0x00, 0x7F, 0x00, 0x63, 0x00, 0x63, 0x00,    // row 1 - 6
        0x63, 0x00, 0x63, 0x00, 0x63, 0x00, 0x63, 0x00, 0x63, 0x00, 0x63, 0x00,    // row 7 - 12
        0xFF, 0x80, 0xFF, 0x80, 0xC1, 0x80, 0xC1, 0x80                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_C5[16] =         // 1 byte per row
{
        0x00, 0x00, 0xFF, 0xFF, 0xC0, 0xC0, 0xC0, 0xFE, 0xFE, 0xC0, 0xC0,    // row 1 - 11
        0xC0, 0xFF, 0xFF, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_C6[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0xCC, 0x80, 0x6D, 0x80, 0x6D, 0x80, 0x3F, 0x00,    // row 1 - 6
        0x3F, 0x00, 0x3F, 0x00, 0x3F, 0x00, 0x7F, 0x80, 0x6D, 0x80, 0x6D, 0x80,    // row 7 - 12
        0x6D, 0x80, 0xCC, 0x80, 0x00, 0x00, 0x00, 0x00                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_C7[16] =         // 1 byte per row
{
        0x00, 0x00, 0x7C, 0xFE, 0x83, 0x03, 0x03, 0x3E, 0x3E, 0x07, 0x03,    // row 1 - 11
        0x83, 0xFF, 0x7C, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_C8[16] =         // 1 byte per row
{
        0x00, 0x00, 0xC7, 0xC7, 0xC7, 0xCF, 0xCB, 0xDB, 0xDB, 0xD3, 0xF3,    // row 1 - 11
        0xE3, 0xE3, 0xE3, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_C9[16] =         // 1 byte per row
{
        0x38, 0x00, 0xC7, 0xC7, 0xC7, 0xCF, 0xCB, 0xDB, 0xDB, 0xD3, 0xF3,    // row 1 - 11
        0xE3, 0xE3, 0xE3, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_CA[16] =         // 1 byte per row
{
        0x00, 0x00, 0xC7, 0xCE, 0xCC, 0xD8, 0xF8, 0xF8, 0xF8, 0xCC, 0xCE,    // row 1 - 11
        0xC6, 0xC7, 0xC3, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_CB[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x3F, 0x80, 0x31, 0x80, 0x31, 0x80,    // row 1 - 6
        0x31, 0x80, 0x31, 0x80, 0x31, 0x80, 0x31, 0x80, 0x31, 0x80, 0x31, 0x80,    // row 7 - 12
        0xE1, 0x80, 0xE1, 0x80, 0x00, 0x00, 0x00, 0x00                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_CC[16] =         // 1 byte per row
{
        0x00, 0x00, 0xE7, 0xE7, 0xE7, 0xFF, 0xFF, 0xDB, 0xDB, 0xDB, 0xC3,    // row 1 - 11
        0xC3, 0xC3, 0xC3, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_CD[16] =         // 1 byte per row
{
        0x00, 0x00, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xFF, 0xFF, 0xC3, 0xC3,    // row 1 - 11
        0xC3, 0xC3, 0xC3, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_CE[16] =         // 1 byte per row
{
        0x00, 0x00, 0x3C, 0x7E, 0x66, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,    // row 1 - 11
        0x66, 0x7E, 0x3C, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_CF[16] =         // 1 byte per row
{
        0x00, 0x00, 0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,    // row 1 - 11
        0xC3, 0xC3, 0xC3, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_D0[16] =         // 1 byte per row
{
        0x00, 0x00, 0xFC, 0xFF, 0xC3, 0xC3, 0xC3, 0xFF, 0xFC, 0xC0, 0xC0,    // row 1 - 11
        0xC0, 0xC0, 0xC0, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_D1[16] =         // 1 byte per row
{
        0x00, 0x00, 0x1E, 0x7F, 0x61, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0,    // row 1 - 11
        0x61, 0x7F, 0x1E, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_D2[16] =         // 1 byte per row
{
        0x00, 0x00, 0xFF, 0xFF, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,    // row 1 - 11
        0x18, 0x18, 0x18, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_D3[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0xE1, 0x80, 0x61, 0x80, 0x73, 0x80, 0x33, 0x00,    // row 1 - 6
        0x3F, 0x00, 0x1E, 0x00, 0x1E, 0x00, 0x0E, 0x00, 0x0C, 0x00, 0x1C, 0x00,    // row 7 - 12
        0x78, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_D4[16] =         // 1 byte per row
{
        0x00, 0x00, 0x18, 0x3C, 0x7E, 0x7F, 0xDB, 0xDB, 0xDB, 0xDB, 0x7F,    // row 1 - 11
        0x7E, 0x3C, 0x18, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_D5[16] =         // 1 byte per row
{
        0x00, 0x00, 0xE7, 0x66, 0x6E, 0x3C, 0x3C, 0x18, 0x18, 0x3C, 0x3C,    // row 1 - 11
        0x66, 0x66, 0xE7, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_D6[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0xC3, 0x00, 0xC3, 0x00, 0xC3, 0x00, 0xC3, 0x00,    // row 1 - 6
        0xC3, 0x00, 0xC3, 0x00, 0xC3, 0x00, 0xC3, 0x00, 0xC3, 0x00, 0xC3, 0x00,    // row 7 - 12
        0xFF, 0x80, 0xFF, 0x80, 0x01, 0x80, 0x01, 0x80                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_D7[16] =         // 1 byte per row
{
        0x00, 0x00, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xFF, 0x7B, 0x03, 0x03,    // row 1 - 11
        0x03, 0x03, 0x03, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_D8[16] =         // 1 byte per row
{
        0x00, 0x00, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB,    // row 1 - 11
        0xDB, 0xFF, 0xFF, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_D9[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0xDB, 0x00, 0xDB, 0x00, 0xDB, 0x00, 0xDB, 0x00,    // row 1 - 6
        0xDB, 0x00, 0xDB, 0x00, 0xDB, 0x00, 0xDB, 0x00, 0xDB, 0x00, 0xDB, 0x00,    // row 7 - 12
        0xFF, 0x80, 0xFF, 0x80, 0x01, 0x80, 0x01, 0x80                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_DA[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x30, 0x00, 0x30, 0x00,    // row 1 - 6
        0x30, 0x00, 0x3F, 0x00, 0x3F, 0x80, 0x31, 0x80, 0x31, 0x80, 0x31, 0x80,    // row 7 - 12
        0x3F, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_DB[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0xC1, 0x80, 0xC1, 0x80, 0xC1, 0x80, 0xC1, 0x80,    // row 1 - 6
        0xC1, 0x80, 0xF9, 0x80, 0xFD, 0x80, 0xCD, 0x80, 0xCD, 0x80, 0xCD, 0x80,    // row 7 - 12
        0xFD, 0x80, 0xF9, 0x80, 0x00, 0x00, 0x00, 0x00                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_DC[16] =         // 1 byte per row
{
        0x00, 0x00, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xFC, 0xFE, 0xC3, 0xC3,    // row 1 - 11
        0xC3, 0xFE, 0xFC, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_DD[16] =         // 1 byte per row
{
        0x00, 0x00, 0x78, 0xFC, 0x8E, 0x06, 0x06, 0x7E, 0x7E, 0x06, 0x06,    // row 1 - 11
        0x8E, 0xFC, 0xF8, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_DE[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0xC7, 0x00, 0xCF, 0x00, 0xC9, 0x80, 0xD9, 0x80,    // row 1 - 6
        0xD9, 0x80, 0xF9, 0x80, 0xF9, 0x80, 0xD9, 0x80, 0xD9, 0x80, 0xD9, 0x80,    // row 7 - 12
        0xCF, 0x00, 0xC6, 0x00, 0x00, 0x00, 0x00, 0x00                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_DF[16] =         // 1 byte per row
{
        0x00, 0x00, 0x3F, 0xFF, 0xC3, 0xC3, 0xC3, 0x7F, 0x3F, 0x73, 0x63,    // row 1 - 11
        0x63, 0xE3, 0xE3, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_E0[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x7F, 0x43, 0x1F, 0x7F, 0xE3,    // row 1 - 11
        0xC7, 0xFF, 0x7B, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_E1[16] =         // 1 byte per row
{
        0x00, 0x3C, 0x7E, 0xE0, 0xC0, 0xFC, 0xFE, 0xE7, 0xC3, 0xC3, 0xC3,    // row 1 - 11
        0xE7, 0x7E, 0x3C, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_E2[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xC3, 0xC3, 0xFE, 0xFF,    // row 1 - 11
        0xC3, 0xFF, 0xFE, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_E3[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFC, 0xC0, 0xC0, 0xC0, 0xC0,    // row 1 - 11
        0xC0, 0xC0, 0xC0, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_E4[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x00,    // row 1 - 6
        0x7F, 0x00, 0x63, 0x00, 0x63, 0x00, 0x63, 0x00, 0x63, 0x00, 0x63, 0x00,    // row 7 - 12
        0xFF, 0x80, 0xFF, 0x80, 0xC1, 0x80, 0xC1, 0x80                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_E5[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x7E, 0xC3, 0xFF, 0xFF, 0xC0,    // row 1 - 11
        0xE1, 0x7F, 0x3E, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_E6[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6D, 0x80,    // row 1 - 6
        0x6D, 0x80, 0x3F, 0x00, 0x3F, 0x00, 0x3F, 0x00, 0x7F, 0x80, 0x6D, 0x80,    // row 7 - 12
        0x6D, 0x80, 0xCC, 0x80, 0x00, 0x00, 0x00, 0x00                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_E7[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xFE, 0x86, 0x06, 0x3C, 0x3C,    // row 1 - 11
        0x86, 0xFE, 0x7C, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_E8[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0xC3, 0xC7, 0xCF, 0xCF, 0xDB, 0xF3,    // row 1 - 11
        0xF3, 0xE3, 0xC3, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_E9[16] =         // 1 byte per row
{
        0x00, 0x44, 0x38, 0x00, 0x00, 0xC3, 0xC7, 0xCF, 0xCF, 0xDB, 0xF3,    // row 1 - 11
        0xF3, 0xE3, 0xC3, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_EA[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0xCE, 0xDC, 0xD8, 0xF0, 0xF8, 0xDC,    // row 1 - 11
        0xCC, 0xC6, 0xC7, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_EB[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80,    // row 1 - 6
        0x3F, 0x80, 0x31, 0x80, 0x31, 0x80, 0x31, 0x80, 0x31, 0x80, 0x31, 0x80,    // row 7 - 12
        0xE1, 0x80, 0xE1, 0x80, 0x00, 0x00, 0x00, 0x00                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_EC[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0xE7, 0xE7, 0xE7, 0xFF, 0xFF, 0xDB,    // row 1 - 11
        0xDB, 0xDB, 0xC3, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_ED[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0xC3, 0xC3, 0xC3, 0xFF, 0xFF, 0xC3,    // row 1 - 11
        0xC3, 0xC3, 0xC3, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_EE[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x7E, 0xE7, 0xC3, 0xC3, 0xC3,    // row 1 - 11
        0xE7, 0x7E, 0x3C, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_EF[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xC3,    // row 1 - 11
        0xC3, 0xC3, 0xC3, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_F0[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0xDC, 0xFE, 0xE7, 0xC3, 0xC3, 0xC3,    // row 1 - 11
        0xE7, 0xFE, 0xDC, 0xC0, 0xC0                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_F1[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x7F, 0xE1, 0xC0, 0xC0, 0xC0,    // row 1 - 11
        0xE1, 0x7F, 0x3E, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_F2[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFC, 0x30, 0x30, 0x30, 0x30,    // row 1 - 11
        0x30, 0x30, 0x30, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_F3[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0xE7, 0x67, 0x66, 0x76, 0x3E, 0x3C,    // row 1 - 11
        0x1C, 0x1C, 0x18, 0x18, 0x70                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_F4[16] =         // 1 byte per row
{
        0x00, 0x00, 0x18, 0x18, 0x18, 0x3C, 0x7E, 0xDB, 0xDB, 0xDB, 0xDB,    // row 1 - 11
        0xDB, 0x7E, 0x3C, 0x18, 0x18                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_F5[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0xE7, 0x66, 0x3C, 0x3C, 0x18, 0x3C,    // row 1 - 11
        0x3C, 0x66, 0xE7, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_F6[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC3, 0x00,    // row 1 - 6
        0xC3, 0x00, 0xC3, 0x00, 0xC3, 0x00, 0xC3, 0x00, 0xC3, 0x00, 0xC3, 0x00,    // row 7 - 12
        0xFF, 0x80, 0xFF, 0x80, 0x01, 0x80, 0x01, 0x80                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_F7[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0xC3, 0xC3, 0xC3, 0xC3, 0xFF, 0x3F,    // row 1 - 11
        0x03, 0x03, 0x03, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_F8[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB,    // row 1 - 11
        0xDB, 0xFF, 0xFF, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_F9[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDB, 0x00,    // row 1 - 6
        0xDB, 0x00, 0xDB, 0x00, 0xDB, 0x00, 0xDB, 0x00, 0xDB, 0x00, 0xDB, 0x00,    // row 7 - 12
        0xFF, 0x80, 0xFF, 0x80, 0x01, 0x80, 0x01, 0x80                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_FA[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x00,    // row 1 - 6
        0xF8, 0x00, 0x38, 0x00, 0x38, 0x00, 0x3F, 0x00, 0x3B, 0x80, 0x39, 0x80,    // row 7 - 12
        0x3F, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_FB[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE1, 0x80,    // row 1 - 6
        0xE1, 0x80, 0xE1, 0x80, 0xE1, 0x80, 0xFD, 0x80, 0xED, 0x80, 0xED, 0x80,    // row 7 - 12
        0xFD, 0x80, 0xF9, 0x80, 0x00, 0x00, 0x00, 0x00                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_FC[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xC0, 0xC0, 0xC0, 0xFE, 0xFF,    // row 1 - 11
        0xC3, 0xFF, 0xFE, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_FD[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0xFC, 0x8E, 0x06, 0x7E, 0x06,    // row 1 - 11
        0x0E, 0xFE, 0xFC, 0x00, 0x00                                         // row 12 - 16
};
PROGMEM const unsigned char chr_f16_FE[32] =         // 2 bytes per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0x00,    // row 1 - 6
        0xCF, 0x00, 0xD9, 0x80, 0xD9, 0x80, 0xF9, 0x80, 0xF9, 0x80, 0xD9, 0x80,    // row 7 - 12
        0xDF, 0x00, 0xCE, 0x00, 0x00, 0x00, 0x00, 0x00                             // row 13 - 16
};
PROGMEM const unsigned char chr_f16_FF[16] =         // 1 byte per row
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x7F, 0x63, 0x7F, 0x3F, 0x3B,    // row 1 - 11
        0x73, 0x63, 0xE3, 0x00, 0x00                                         // row 12 - 16
};

PROGMEM const unsigned char * chrtbl_f16[224] =      // character pointer table
{
        chr_f16_20, chr_f16_21, chr_f16_22, chr_f16_23, chr_f16_24, chr_f16_25, chr_f16_26, chr_f16_27, 
        chr_f16_28, chr_f16_29, chr_f16_2A, chr_f16_2B, chr_f16_2C, chr_f16_2D, chr_f16_2E, chr_f16_2F, 
        chr_f16_30, chr_f16_31, chr_f16_32, chr_f16_33, chr_f16_34, chr_f16_35, chr_f16_36, chr_f16_37, 
        chr_f16_38, chr_f16_39, chr_f16_3A, chr_f16_3B, chr_f16_3C, chr_f16_3D, chr_f16_3E, chr_f16_3F, 
        chr_f16_40, chr_f16_41, chr_f16_42, chr_f16_43, chr_f16_44, chr_f16_45, chr_f16_46, chr_f16_47, 
        chr_f16_48, chr_f16_49, chr_f16_4A, chr_f16_4B, chr_f16_4C, chr_f16_4D, chr_f16_4E, chr_f16_4F, 
        chr_f16_50, chr_f16_51, chr_f16_52, chr_f16_53, chr_f16_54, chr_f16_55, chr_f16_56, chr_f16_57, 
        chr_f16_58, chr_f16_59, chr_f16_5A, chr_f16_5B, chr_f16_5C, chr_f16_5D, chr_f16_5E, chr_f16_5F, 
        chr_f16_60, chr_f16_61, chr_f16_62, chr_f16_63, chr_f16_64, chr_f16_65, chr_f16_66, chr_f16_67, 
        chr_f16_68, chr_f16_69, chr_f16_6A, chr_f16_6B, chr_f16_6C, chr_f16_6D, chr_f16_6E, chr_f16_6F, 
        chr_f16_70, chr_f16_71, chr_f16_72, chr_f16_73, chr_f16_74, chr_f16_75, chr_f16_76, chr_f16_77, 
        chr_f16_78, chr_f16_79, chr_f16_7A, chr_f16_7B, chr_f16_7C, chr_f16_7D, chr_f16_7E, chr_f16_7F, 
        chr_f16_80, chr_f16_81, chr_f16_82, chr_f16_83, chr_f16_84, chr_f16_85, chr_f16_86, chr_f16_87, 
        chr_f16_88, chr_f16_89, chr_f16_8A, chr_f16_8B, chr_f16_8C, chr_f16_8D, chr_f16_8E, chr_f16_8F, 
        chr_f16_90, chr_f16_91, chr_f16_92, chr_f16_93, chr_f16_94, chr_f16_95, chr_f16_96, chr_f16_97, 
        chr_f16_98, chr_f16_99, chr_f16_9A, chr_f16_9B, chr_f16_9C, chr_f16_9D, chr_f16_9E, chr_f16_9F, 
        chr_f16_A0, chr_f16_A1, chr_f16_A2, chr_f16_A3, chr_f16_A4, chr_f16_A5, chr_f16_A6, chr_f16_A7, 
        chr_f16_A8, chr_f16_A9, chr_f16_AA, chr_f16_AB, chr_f16_AC, chr_f16_AD, chr_f16_AE, chr_f16_AF, 
        chr_f16_B0, chr_f16_B1, chr_f16_B2, chr_f16_B3, chr_f16_B4, chr_f16_B5, chr_f16_B6, chr_f16_B7, 
        chr_f16_B8, chr_f16_B9, chr_f16_BA, chr_f16_BB, chr_f16_BC, chr_f16_BD, chr_f16_BE, chr_f16_BF, 
        chr_f16_C0, chr_f16_C1, chr_f16_C2, chr_f16_C3, chr_f16_C4, chr_f16_C5, chr_f16_C6, chr_f16_C7, 
        chr_f16_C8, chr_f16_C9, chr_f16_CA, chr_f16_CB, chr_f16_CC, chr_f16_CD, chr_f16_CE, chr_f16_CF, 
        chr_f16_D0, chr_f16_D1, chr_f16_D2, chr_f16_D3, chr_f16_D4, chr_f16_D5, chr_f16_D6, chr_f16_D7, 
        chr_f16_D8, chr_f16_D9, chr_f16_DA, chr_f16_DB, chr_f16_DC, chr_f16_DD, chr_f16_DE, chr_f16_DF, 
        chr_f16_E0, chr_f16_E1, chr_f16_E2, chr_f16_E3, chr_f16_E4, chr_f16_E5, chr_f16_E6, chr_f16_E7, 
        chr_f16_E8, chr_f16_E9, chr_f16_EA, chr_f16_EB, chr_f16_EC, chr_f16_ED, chr_f16_EE, chr_f16_EF, 
        chr_f16_F0, chr_f16_F1, chr_f16_F2, chr_f16_F3, chr_f16_F4, chr_f16_F5, chr_f16_F6, chr_f16_F7, 
        chr_f16_F8, chr_f16_F9, chr_f16_FA, chr_f16_FB, chr_f16_FC, chr_f16_FD, chr_f16_FE, chr_f16_FF
};

 

Заменить Font16.h

#define nr_chrs_f16 224
#define chr_hgt_f16 16
#define data_size_f16 8
#define firstchr_f16 32

extern const unsigned char widtbl_f16[224];
extern const unsigned char * chrtbl_f16[224];

 

4. Откопал на чипе stm32 RTC (часы реального времени). Надо только часовой кварц припаять -))). Запустил часики. Теперь блок в кватанции в ответ на полученную информацию отсылает текущее время. На удаленнных блоках часы реализуются легко. Часы правда не с первого раза получились, но в конце концов запустились как надо.

Проект название поменялось  на stm32_server_GFX

Файл stm32_server_GFX.ino

/*
Описание
1. Часы реального времени. Необходимые аппаратные доработки:
1.1 Надо припаять батарейку 3 вольта на ноги bat (M20 pin 3 "+") и gnd (J2 pin 2 "-")
1.2 Припаять часовой кварц 32.768 к цифровым ногам 13 и 12 (М20 pin 4 и 5)  Эти ноги после этого использовать НЕЛЬЗЯ!!!
1.3 Установить библиотеку  Time library - https://github.com/PaulStoffregen/Time  
У нее не удачное имя (винды регистронезависимы и имя совпадает с системным -- среда глючит)
переименовать в stmTime  директорию, файлы и ссылки в них !!!
*/

#pragma GCC optimize ("-Os")
//#pragma pack(push, 1)     // выравнивание по одному байту ????????

//#define DEMO           // Признак демонстрации - данные с датчиков генерятся рандом другие интервалы
#define VERSION "Version: 0.45 alpha 12/10/15"        // Текущая версия
#define SPI_16BIT
#define SPI_MODE_DMA 1
#define SPEED_UP 1 // Enables extra calculations in the circles routine to use fastVLine and fastHLine, only in DMA mode.
  
#include <itoa.h>  
#include <SPI.h>       // в зависимости от платформы используется разная библиотека
#include "Adafruit_GFX_AS.h"
#include "Adafruit_ILI9341_STM.h"

#include "nRF24L01.h"  // не требует адаптации
#include "RF24.h"      // адаптирована для Maple mini

#include <RTClock.h>
RTClock rt (RTCSEL_LSE); // используется внешний часовой кварц
uint32 tt; 

#include   "stmTime.h"     // Time library - https://github.com/PaulStoffregen/Time
#define TZ "UTC+3"      // Часовой пояс

   #include <libmaple/iwdg.h>     // Сторожевой таймер
   #include <libmaple/adc.h>      // АЦП
   
   #define DSB_ALL_IRQ asm volatile("cpsid i");  // запретить все прерывания
   #define ENB_ALL_IRQ asm volatile("cpsie i");  // разрешить все прерывания
   // Что куда припаяно
   #define NRF24_CE_PIN   3
   #define NRF24_CSN_PIN  7
   #define NRF24_IRQ_PIN  PB10   // прерывание радио
   #define TFT_DC_PIN     11     // 12 старая нога
   #define TFT_CS_PIN     10     // 13 старая нога
   #define TFT_RST_PIN    14     // сброс дисплея
   #define LED_PIN        PB1    // светодиод на плате
   #define PIN_A          2      // энкодер канал А
   #define PIN_B          9      // энкодер канал B
   #define PIN_SW         8      // энкодер Кнопка
   
// Аппаратный SPI на дисплей ILI9341 и NRF24
// Use hardware SPI (on Uno, #13, #12, #11) and the above for CS/DC
Adafruit_ILI9341_STM tft = Adafruit_ILI9341_STM(TFT_CS_PIN, TFT_DC_PIN);

 RF24 radio(NRF24_CE_PIN, NRF24_CSN_PIN);  // радиомодуль

 #define NRF24_CHANEL 100 
 #define NUM_SCREEN   2+1      // Число экранов + 1 стартовый

// Цвета дополнительные
#define ILI9341_GRAY   0xBDF7	// Light Gray
#define ILI9341_DGRAY  0x7BEF	// Dark Gray
#define ILI9341_ORANGE 0xFBE0	// Orange
#define ILI9341_BROWN  0x79E0	// Brown
#define ILI9341_PINK   0xF81F	// Pink


int  err_count=-1;  // статистика по пропущенным пакетам -1 признак первого старта
  // энкодер
volatile uint8_t enc=0;
volatile int newpos=0;
bool packet_ready=false;     // true  поступили новые данные
bool change_screen=false;    // true  сменился экран
int last_min=-1;             // для вывода часов
char OutputBuf[100];         // буффер используется для вывода на экран (туда кладут результат различных преобразований перед выводом) 

//bool signalStrength;
byte last_error=100;                            // Предыдущая ошибка<packet.error
// Проверка радио по прерываниям от радиомодуля
void check_radio(void);

void setup(){
   pinMode(PB1, OUTPUT);              // Светодиод
   pinMode(NRF24_IRQ_PIN, INPUT);     // Прерывание
   pinMode(PIN_A, INPUT); 
   pinMode(PIN_B, INPUT);
   pinMode(PIN_SW, INPUT);
   digitalWrite(PIN_A, HIGH);       // turn on pullup resistor
   digitalWrite(PIN_B, HIGH);       // turn on pullup resistor
   iwdg_init(IWDG_PRE_256, 1250);   // init an 8 second wd timer

  // Настройка радиомодуля
  radio.begin();
  radio.setDataRate(RF24_250KBPS);         //  выбор скорости RF24_250KBPS RF24_1MBPS RF24_2MBPS
  radio.setPALevel(RF24_PA_MAX);           // выходная мощность передатчика
  radio.setChannel(NRF24_CHANEL);          // тут установка канала
  radio.setCRCLength(RF24_CRC_16);         // использовать контрольную сумму в 16 бит
  radio.setAutoAck(true);                  // включить аппаратное потверждение
//  radio.enableDynamicPayloads();           // разрешить Dynamic Payloads
  radio.enableAckPayload();                // разрешить AckPayload
  radio.setRetries(50,10);                 // Количество повторов и пауза между повторами
  // Рекомендуют первые 2-4 байта адреса устанавливать в E7 или 18 он проще детектируется чипом
  radio.openWritingPipe(0xE7E7E7E7D2LL);   // передатчик
  radio.openReadingPipe(1,0xE7E7E7E7E1LL); // приемник
  radio.startListening();
  
  reset_ili9341();
  start_screen();

 attachInterrupt(NRF24_IRQ_PIN, check_radio, FALLING); // Прикрепление прерывания радио
// attachInterrupt(PIN_A, updateEncoder, CHANGE);        // Прикрепление прерывания энкодер
// attachInterrupt(PIN_B, updateEncoder, CHANGE);        // Прикрепление прерывания энкодер
 attachInterrupt(PIN_SW, scanKey, FALLING);

rtc_set_prescaler_load(0x7fff);  // установка делителя для часов
// setCurrentTime(); // Установка времени
 rt.attachSecondsInterrupt(show_time);
}

void setCurrentTime() // становка заданного времени один раз загружается а потом отключается
{
setTime(13,11,10,12,10,2015);
time_t t = now();
rt.setTime(t);
}

void scanKey()
{ 
int oldpos=newpos;
byte key=digitalRead(PIN_SW);
if (key==0) newpos++;
if (newpos>=NUM_SCREEN)   newpos=0;  
if (oldpos!=newpos)  change_screen=true; // только когда было изменение
}
void updateEncoder() //адаптировано на деление на 2 и диапазон по количеству экранов
{ 
  int oldpos=newpos;
  uint8_t newenc=2*digitalRead(PIN_B)+digitalRead(PIN_A); //берем 2 бита входов и сдвигаем в позицию 0,1
  uint8_t temp=enc ^ newenc; //побитовое исключающее или
  if (temp == 0b01)
    if (enc==0b00 || enc==0b11) newpos++; else newpos--;
  else 
  if(temp == 0b10)
    if (enc==0b00 || enc==0b11) newpos--; else newpos++;
 enc=newenc;
 if (newpos<0)             newpos=NUM_SCREEN-1;
 if (newpos>=NUM_SCREEN)   newpos=0;
 if (oldpos!=newpos)  change_screen=true; // только когда было изменение
}


void loop()
{
 if (change_screen==true)  // Смена экрана
 {  DSB_ALL_IRQ;
    change_screen=false; 
    packet_ready=true;
  //  ucg.clearScreen(); 
   tft.fillScreen(ILI9341_BLACK);
    switch (newpos) { // по положению Энкодера
    case 0:   // стартовый экран
       start_screen();
       break;
    case 1: // осушитель ID 0x21
       dry_static(0);
       dry_data(0);
      break;
    case 2: // осушитель ID 0x22
       dry_static(1);
       dry_data(1);
    break;
    default: newpos=0;
    }
  ENB_ALL_IRQ;  
 last_min=-1;    
 } 
  
 if (packet_ready==true)  // Есть не обработанные данные - надо нарисовать 
 {
    packet_ready=false; 
    DSB_ALL_IRQ;
    switch (newpos) { // по положению Энкодера
    case 0:   // стартовый экран
   //      start_screen();
         break;
    case 1: // осушитель ID 0x21
        dry_update(0);
        break;
    case 2: // осушитель ID 0x21
        dry_update(1);
        break;
    default: newpos=0;
       }
   ENB_ALL_IRQ;    
   digitalWrite(PB1,HIGH);
   delay(5);
   digitalWrite(PB1,LOW) ;
 }
 
iwdg_feed();  // Сброс сторожевого таймера
delay(100);  // без этого квитанции не приходят

tt = rt.getTime();

}

/* 
// Вывод float  с одним десятичным знаком в координаты x y // для экономии места
void print_floatXY(int x,int y, float v)
{
 //ucg.setPrintPos(x,y);
 //ucg.print(v,2);
 //ucg.print("  "); // Стереть хвост от предыдущего числа
  tft.setCursor(x, y);
//  tft.setTextColor(ILI9341_WHITE); 
  tft.setTextSize(1);
  tft.println(v,2);
 
} 
*/


// Вывод строки константы в координаты x y // для экономии места
void print_StrXY(int x,int y, char* b)
{
  tft.setCursor(x, y);
  tft.println(b);
} 


char hex(byte x)  // Функия для вывода в hex
{
   if(x >= 0 && x <= 9 ) return (char)(x + '0');
   else      return (char)('a'+x-10);
}


void check_radio(void) // ПРИЕМ ПАКЕТА Прерывание для проверки радио
{
   uint8_t buf[33];     // Буффер для чтения 32+1
   byte pipe = 0;
  while(radio.available(&pipe) ) // читаем весь буфер до 3 посылок 
 {
   packet_ready=true;   // есть не обработанные данные
   radio.writeAckPayload( 1, &tt, sizeof(tt) ); // подготавливаем ответ с времением - передаем текущее время в удаленный блок
   radio.read(&buf, sizeof(buf));  // Читаем в промежуточный буффер, далее анализируем 1 байт id
   if ((buf[0]&0xf0)==0x20)  dry_get_data(buf); // анализ по ID тип устройства ОСУШИТЕЛЬ
 } 
/*
   if ( radio.available(&pipe) ) {
       radio.read(&buf, sizeof(buf));  // Читаем в промежуточный буффер, далее анализируем 1 байт id
     if ((buf[0]&0xf0)==0x20)  dry_get_data(buf); // анализ по ID тип устройства ОСУШИТЕЛЬ
     } 
  */   
}

// Очистка экрана
bool reset_ili9341(void)
{
  pinMode(TFT_RST_PIN, OUTPUT);      // Сброс дисплея сигнал активным является LOW
  digitalWrite(TFT_RST_PIN, LOW);  
  delay(100);
  digitalWrite(TFT_RST_PIN, HIGH);  
  // Дисплей
   tft.begin();
   tft.setRotation(1);
   tft.fillScreen(ILI9341_BLACK);
}

bool start_screen(void)
{
  // Заголовок
  tft.fillRect(0, 0, 320-1, 20, ILI9341_BLUE);
  tft.setTextColor(ILI9341_WHITE); 
  tft.drawString(utf8rus("Удаленный мониторинг",OutputBuf),1,1,2); 
 
  tft.setTextColor(ILI9341_YELLOW); 
  tft.drawString(utf8rus("1. Температура блока градусы: ",OutputBuf),2,2+18*1,2);  
  tft.drawString(ftoa(OutputBuf, (1750.0-(read_VDDA()))/4.3+25.0,2),280,2+18*1,2);  
  
  tft.drawString(utf8rus("2. Канал NRF24l01+:",OutputBuf),2,2+18*2,2);  
  tft.drawString(int2str(NRF24_CHANEL),280,2+18*2,2);

  tft.drawString(utf8rus("3. Число внешних устройств:",OutputBuf),2,2+18*3,2);  
  tft.drawString(int2str(NUM_SCREEN-1),280,2+18*3,2);
  
  tft.setTextColor(ILI9341_RED); 
  print_StrXY(2,240-10,VERSION);
  
}

// Чтение опорного напряжения
uint16 read_VDDA(void)
{
  adc_reg_map *regs = ADC1->regs;
// 3. Set the TSVREFE bit in the ADC control register 2 (ADC_CR2) to wake up the
//    temperature sensor from power down mode.  Do this first 'cause according to
//    the Datasheet section 5.3.21 it takes from 4 to 10 uS to power up the sensor.
  regs->CR2 |= ADC_CR2_TSEREFE;
// 1. Select the ADCx_IN16 input channel.
  regs->SQR1 = 0;	        // set regular channel sequence length to 1
  regs->SQR3 = 0b10000;		// select channel 16
// 2. Select a sample time of 17.1 μs
// per gbulmer: set channel 16 sample time to 239.5 cycles
// 239.5 cycles of the ADC clock (72MHz/6=12MHz) is over 17.1us (about 20us), but no smaller
// sample time exceeds 17.1us.
  regs->SMPR1 = (0b111 << (3*6));
// 4. Start the ADC conversion by setting the ADON bit (or by external trigger).
//  note by virture of bit 11 being zero returns right aligned results.
// Aparently we also need SWSTART - tdc
  regs->CR2 |= (ADC_CR2_SWSTART | ADC_CR2_ADON);
// wait for conversion to complete
    while (!(regs->SR & ADC_SR_EOC))	;
// 5. Read the resulting VSENSE data in the ADC data register
  return (uint16)(regs->DR & ADC_DR_DATA);
}

// Показ времени по секундным прерываниям
void show_time()
{ byte temp;
  char t[6];
if (last_min!=minute(tt))  // выводим только изменение - т.е. раз в минуту
{
last_min=minute(tt);
    // Быстро формируем строку со временем а потом выводим
    temp=hour(tt);
    if (temp<10) t[0]=48; else t[0]=temp/10+48;   // код "0" 48
    t[1]=temp%10+48;
    t[2]=58; // ":"
    temp=minute(tt);
    if (temp<10) t[3]=48; else t[3]=temp/10+48;
    t[4]=temp%10+48;
    t[5]=0; // Конец строки
    tft.setTextColor(ILI9341_YELLOW);
    switch (newpos) { // в зависимости от экрана
    case 0:   // стартовый экран
    tft.fillRect(275, 0, 45, 18, ILI9341_BLUE);
    tft.drawString(t,275,1,2);  
       break;
    case 1: // осушитель ID 0x21
    case 2: // осушитель ID 0x22
    tft.fillRect(210, 0, 45, 18, ILI9341_BLUE);
    tft.drawString(t,210,1,2);  
    break;
    }
  }
}
// Перевод кодировки из UTF8 в 1251
char* utf8rus(String source, char* buf)
{
  int i,k,j;
  unsigned char n;
  char m[2] = { '0', '\0' };
  k = source.length(); i = 0; j=0;
  while (i < k) {
    n = source[i]; i++;
    if (n >= 0xC0) {
      switch (n) {
        case 0xD0: {
          n = source[i]; i++;
          if (n == 0x81) { n = 0xA8; break; }
          if (n >= 0x90 && n <= 0xBF) n = n + 0x30;
          break;
        }
        case 0xD1: {
          n = source[i]; i++;
          if (n == 0x91) { n = 0xB8; break; }
          if (n >= 0x80 && n <= 0x8F) n = n + 0x70;
          break;
        }
      }
    }
    buf[j]=n; j++;
  }
 buf[j]=0;  
return buf;
}


// int to str - для уменьшения кода
char _int2str[7];
char* int2str( register int i ) {
  register unsigned char L = 1;
  register char c;
  register boolean m = false;
  register char b;  // lower-byte of i
  // negative
  if ( i < 0 ) {
    _int2str[ 0 ] = '-';
    i = -i;
  }
  else L = 0;
  // ten-thousands
  if( i > 9999 ) {
    c = i < 20000 ? 1
      : i < 30000 ? 2
      : 3;
    _int2str[ L++ ] = c + 48;
    i -= c * 10000;
    m = true;
  }
  // thousands
  if( i > 999 ) {
    c = i < 5000
      ? ( i < 3000
          ? ( i < 2000 ? 1 : 2 )
          :   i < 4000 ? 3 : 4
        )
      : i < 8000
        ? ( i < 6000
            ? 5
            : i < 7000 ? 6 : 7
          )
        : i < 9000 ? 8 : 9;
    _int2str[ L++ ] = c + 48;
    i -= c * 1000;
    m = true;
  }
  else if( m ) _int2str[ L++ ] = '0';
  // hundreds
  if( i > 99 ) {
    c = i < 500
      ? ( i < 300
          ? ( i < 200 ? 1 : 2 )
          :   i < 400 ? 3 : 4
        )
      : i < 800
        ? ( i < 600
            ? 5
            : i < 700 ? 6 : 7
          )
        : i < 900 ? 8 : 9;
    _int2str[ L++ ] = c + 48;
    i -= c * 100;
    m = true;
  }
  else if( m ) _int2str[ L++ ] = '0';
  // decades (check on lower byte to optimize code)
  b = char( i );
  if( b > 9 ) {
    c = b < 50
      ? ( b < 30
          ? ( b < 20 ? 1 : 2 )
          :   b < 40 ? 3 : 4
        )
      : b < 80
        ? ( i < 60
            ? 5
            : i < 70 ? 6 : 7
          )
        : i < 90 ? 8 : 9;
    _int2str[ L++ ] = c + 48;
    b -= c * 10;
    m = true;
  }
  else if( m ) _int2str[ L++ ] = '0';
  // last digit
  _int2str[ L++ ] = b + 48;
  // null terminator
  _int2str[ L ] = 0;  
  return _int2str;
}

// Float to String   - экономим место 
char *ftoa(char *a, double f, int precision)
{
 long p[] = {0,10,100,1000,10000,100000,1000000,10000000,100000000};
 char *ret = a;
 long heiltal = (long)f;
 itoa(heiltal, a, 10);
 while (*a != '\0') a++;
 *a++ = '.';
 long desimal = abs((long)((f - heiltal) * p[precision]));
 itoa(desimal, a, 10);
 return ret;
}

Файл dry_control.ino


// Функции для осушителя подвала 
// Все функции имеют на входе в качестве параметра номер датчика

// Мои макросы
#define MOTOR_BIT                 0            // бит мотора в packet_0x20.flags
#define HEAT_BIT                  1            // бит калорифера в packet_0x20.flags
#define ABS_H_BIT                 2            // бит калорифера в packet_0x20.flags
#define MODE_BIT                  5            // первый бит режима в packet_0x20.flags

#define FLAG_ABS_H_ON             packet_0x20[ID].flags |= (1<<ABS_H_BIT)   // бит ABS_H_BIT установить в 1
#define FLAG_ABS_H_OFF            packet_0x20[ID].flags &= ~(1<<ABS_H_BIT)  // бит ABS_H_BIT установить в 0
#define FLAG_ABS_H_CHECK          packet_0x20[ID].flags & (1<<ABS_H_BIT)    // бит ABS_H_BIT проверить на 1

#define FLAG_MOTOR_ON             packet_0x20[ID].flags |= (1<<MOTOR_BIT)   // бит мотора установить в 1
#define FLAG_MOTOR_OFF            packet_0x20[ID].flags &= ~(1<<MOTOR_BIT)  // бит мотора установить в 0
#define FLAG_MOTOR_CHECK          packet_0x20[ID].flags & (1<<MOTOR_BIT)    // бит мотора проверить на 1

#define FLAG_HEAT_ON              packet_0x20[ID].flags |= (1<<HEAT_BIT)   // бит калорифера установить в 1
#define FLAG_HEAT_OFF             packet_0x20[ID].flags &= ~(1<<HEAT_BIT)  // бит калорифера установить в 0
#define FLAG_HEAT_CHECK           packet_0x20[ID].flags & (1<<HEAT_BIT)    // бит калорифера проверить на 1

#define NUM_SAMPLES      10                  // Число усреднений измерений датчика
#define TIME_SCAN_SENSOR 3000                // Время опроса датчиков мсек
#define TIME_PRINT_CHART 300000              // Время вывода точки графика мсек
#define TIME_HOUR        3600000             // Число мсек в часе

#define NUM_DRY    2                        // число осушителей id 0x21 0x22 
// Пакет передаваемый, используется также для хранения результатов. 
 struct type_packet_0x20_NRF24   // Версия 2.4!! адаптация для stm32 Структура передаваемого пакета 32 байта - 32 максимум
    {
        byte id=0x00;                           // Идентификатор типа устройства - старшие 4 бита, вторые (младшие) 4 бита серийный номер устройства
        byte DHT_error;                         // Ошибка разряды: 0-1 первый датчик (00-ок) 2-3 второй датчик (00-ок) 4 - радиоканал     
        int16_t   tOut=-500,tIn=500;            // Текущие температуры в сотых градуса !!! место экономим
        uint16_t  absHOut=123,absHIn=123;       // Абсолютные влажности в сотых грамма на м*3 !!! место экономим
        uint16_t  relHOut=123,relHIn=123;       // Относительные влажности сотых процента !!! место экономим
        uint8_t   flags=0x00;                   // байт флагов  
                                                // 0 бит - мотор включен/выключен 
                                                // 1 бит - нагреватель включен/выключен
                                                // 2 бит -[1 - dH_min задается в сотых грамма на м*3] [0 - dH_min заадется в ДЕСЯТЫХ процента от absHIn]
                                                // 3 4 - пока пусто
                                                // 5-7 - номер настройки = settingRAM.mode до 8 настроек, надо передавать, что бы на приемнике восстановить
        uint8_t   dH_min;                       // Порог включения вентилятора по РАЗНИЦЕ абсолютной влажности в сотых грамма на м*3 или в ДЕСЯТЫХ % см flags:2
        uint8_t   T_min;                        // Порог выключения вентилятора по температуре в ДЕСЯТЫХ долях градуса, только положительные значения
        uint8_t   count=0;                      // циклический счетчик отправленных пакетов нужен что бы на приемнике проверять качество связи
        char note[14] = "NONE";                 // Примечание не более 13 байт + 0 байт Русские буквы в два раза меньше т.к. UTF-8
    } packet_0x20[NUM_DRY]; 
 
// Cтруктура для графиков
struct type_chart_dry
{
      byte tOutChart[120];
      byte tInChart[120];
      byte absHOutChart[120];
      byte absHInChart[120];
      byte posChart=0;         // Позиция в массиве графиков - начало вывода от 0 до 120-1
      byte TimeChart=0;        // Время до вывода очередной точки на график. 
      bool ChartMotor=false;   // Признак работы мотора во время интервала графика если мотор был включен на любое время то на графике фон меняется в этом месте
      bool ChartHeat=false;    // Признак работы нагревателя во время интервала графика если нагреватель был включен на любое время (даже одно измерение) то на графике фон меняется в этом месте
      bool CoolData=false;     // Признак наличия свежих данных, что бы лишний раз не выводить на экран одно и тоже
} chart_dry[NUM_DRY];

void dry_static(uint8_t ID)  // Печать статической картинки 
{ int i;
  // Заголовок 
  tft.fillRect(0, 0, 320-1, 20, ILI9341_BLUE);
  tft.setTextColor(ILI9341_WHITE); 
  tft.drawString(int2str(newpos),2,2,2);
  tft.drawString(utf8rus(".ОСУШИТЕЛЬ ID: 0x",OutputBuf),13,2,2); 
  tft.drawChar(hex(packet_0x20[ID].id >> 4),149,2,2);
  tft.drawChar(hex(packet_0x20[ID].id&0x0f),159,2,2);
   // Таблица для данных
  tft.drawFastHLine(0,25,320-1,ILI9341_GREEN);
  tft.drawFastHLine(0,25+23*1,320-1,ILI9341_GREEN);
  tft.drawFastHLine(0,25+23*2,320-1,ILI9341_GREEN);
  tft.drawFastHLine(0,25+23*3,320-1,ILI9341_GREEN);
  tft.drawFastHLine(0,25+23*4,320-1,ILI9341_GREEN);
  tft.drawFastVLine(200-4,25,24+23*3, ILI9341_GREEN);
  tft.drawFastVLine(260,25,24+23*3, ILI9341_GREEN);
  
  // Заголовки таблиц
  // В зависимости от id разные надписи - привязка местоположения блока к ID
  tft.setTextColor(ILI9341_RED); 
 // tft.drawString(utf8rus(packet_0x20[ID].note,OutputBuf),180+30-9,28+23*0,2); 
  tft.setTextColor(ILI9341_GREEN); 
  tft.drawString(utf8rus("Улица",OutputBuf),250+20,28+23*0,2); 
  tft.setTextColor(ILI9341_YELLOW); 
  tft.drawString(utf8rus("N/потери",OutputBuf),0,28+23*0,2); 
  tft.drawString(utf8rus("Температура градусы C\xB0",OutputBuf),0,28+23*1,2); 
  tft.drawString(utf8rus("Относительная влаж. %",OutputBuf),0,28+23*2,2);
  tft.drawString(utf8rus("Абсолют. влаж. г/м*3",OutputBuf),0,28+23*3,2);
 
  // Графики
  tft.setTextColor(ILI9341_WHITE); 
  print_StrXY(10,125+0,utf8rus("Температура",OutputBuf)); 
  print_StrXY(20+154,125+0,utf8rus("Абс. влажность",OutputBuf)); 
  // надписи на графиках
  print_StrXY(128,140,"+20"); 
  print_StrXY(135,180,"0"); 
  print_StrXY(128,220,"-20");
  
  print_StrXY(296,150,"15");
  print_StrXY(296,180,"10");
  print_StrXY(296,210,"5");
  
  // Горизонтальная шкала по часам
  for(i=0;i<=120;i=i+12)
    {
    tft.drawPixel(4+i,239,ILI9341_DGRAY);
    tft.drawPixel(4+i,238,ILI9341_DGRAY);
    tft.drawPixel(167+i,239,ILI9341_DGRAY);
    tft.drawPixel(167+i,238,ILI9341_DGRAY);
     }
}

void dry_data(uint8_t ID) // Печать панели статуса Значки на статус панели
{
  
  // Заголовок
  // 1. печать ошибки чтения датчиков
  if (packet_0x20[ID].DHT_error!=last_error)  // если статус ошибки поменялся то надо вывести если нет то не выводим - экономия время и нет мерцания
  {
      last_error=packet_0x20[ID].DHT_error; 
      tft.fillRect(290, 0, 30, 18, ILI9341_BLUE); 
      
   //   if (packet_0x20[ID].DHT_error>0) { tft.setTextColor(ILI9341_RED);   tft.drawNumber(packet_0x20[ID].DHT_error,290,2,2); }
   //   else                             { tft.setTextColor(ILI9341_GREEN); tft.drawString(utf8rus(" ok ",OutputBuf),290,2,2); } 
  if (packet_0x20[ID].DHT_error>0) { tft.setTextColor(ILI9341_RED);   tft.drawString(int2str(packet_0x20[ID].DHT_error),290,2,2); }
      else                             { tft.setTextColor(ILI9341_GREEN); tft.drawString(utf8rus(" ok ",OutputBuf),290,2,2); }    
      
  }
 // 2. Признак включения мотора
  if (FLAG_MOTOR_CHECK)    tft.fillRect(290-25, 3, 12, 12, ILI9341_GREEN); 
  else                     tft.fillRect(290-25, 3, 12, 12, ILI9341_BLACK); 
 

 // Печать значений для дома
  tft.setTextColor(ILI9341_RED);
  tft.fillRect(200+0,28+23*0,50, 18, ILI9341_BLACK);
  tft.drawString(utf8rus(packet_0x20[ID].note,OutputBuf),180+30-9,28+23*0,2); 
  tft.fillRect(200+0,28+23*1,50, 18, ILI9341_BLACK);
  tft.drawString(ftoa(OutputBuf,packet_0x20[ID].tIn/100.0,2),200+0,28+23*1,2);
  tft.fillRect(200+0,28+23*2,50, 18, ILI9341_BLACK);
  tft.drawString(ftoa(OutputBuf,packet_0x20[ID].relHIn/100.0,2),200+0,28+23*2,2);
  tft.fillRect(200+0,28+23*3,50, 18, ILI9341_BLACK);
  tft.drawString(ftoa(OutputBuf,packet_0x20[ID].absHIn/100.0,2),200+0,28+23*3,2);
  
  tft.setTextColor(ILI9341_GREEN);
  tft.fillRect(260+6,28+23*1,50, 18, ILI9341_BLACK);
  tft.drawString(ftoa(OutputBuf,packet_0x20[ID].tOut/100.0,2),260+6,28+23*1,2);  
  tft.fillRect(260+6,28+23*2,50, 18, ILI9341_BLACK);
  tft.drawString(ftoa(OutputBuf,packet_0x20[ID].relHOut/100.0,2),260+6,28+23*2,2);
  tft.fillRect(260+6,28+23*3,50, 18, ILI9341_BLACK);
   tft.drawString(ftoa(OutputBuf,packet_0x20[ID].absHOut/100.0,2),260+6,28+23*3,2);
  // График
  if (chart_dry[ID].TimeChart==0) dry_chart(ID); // когда выводится график на экран
}  

// Печать графика на экране, добавляется одна точка и график сдвигается 
void dry_chart(uint8_t ID) 
{
byte i,x=0;
uint8_t tmp;
//   if ((long)((long)chart_dry[ID].TimeChart*TIME_SCAN_SENSOR*NUM_SAMPLES)<(long)TIME_PRINT_CHART) return;  // график еще рано выводить
   for(i=0;i<120;i++)    // График слева на право
     { 
     // Вычислить координаты текущей точки x в кольцевом буфере. Изменяются от 0 до 120-1
     if (chart_dry[ID].posChart<i) x=120+chart_dry[ID].posChart-i; else x=chart_dry[ID].posChart-i;

     // нарисовать фон в зависимости от статуса мотора
     if  (chart_dry[ID].tInChart[x]>=0x80) 
     { tft.drawFastVLine(5+120-i,237-100,100, tft.color565(0, 60, 9)); tft.drawFastVLine(5+120-i+162,237-100,100, tft.color565(0, 60, 9));} // Мотор был ключен - бледно синий
     else 
        if (chart_dry[ID].tOutChart[x]>=0x80) { tft.drawFastVLine(5+120-i,237-100,100, tft.color565(90, 60, 0)); tft.drawFastVLine(5+120-i+162,237-100,100, tft.color565(90, 60, 0));}// Нагреватель был ключен - бледно желтый
           else   {  tft.drawFastVLine(5+120-i,237-100,100, ILI9341_BLACK); tft.drawFastVLine(5+120-i+162,237-100,100, ILI9341_BLACK); }// ucg.setColor(0, 0, 0);     // все выключено
      
     if (i%5==0) // Пунктирные линии графика
     {
       tft.drawPixel(5+120-i,236-10-1,ILI9341_DGRAY);
       tft.drawPixel(5+120-i,236-50-1,ILI9341_DGRAY);
       tft.drawPixel(5+120-i,236-90-1,ILI9341_DGRAY);
       
       tft.drawPixel(5+120-i+162,236-25-1,ILI9341_DGRAY);
       tft.drawPixel(5+120-i+162,236-50-1,ILI9341_DGRAY);
       tft.drawPixel(5+120-i+162,236-75-1,ILI9341_DGRAY);  
     } 
     
     // Вывести новую точку
     tmp=chart_dry[ID].tInChart[x]&0x7f;   // Отбросить старший разряд - признак включения мотора
     if ((tmp==0)||(tmp==100))   tft.drawPixel(5+120-i,236-tmp,ILI9341_WHITE); 
     else tft.drawPixel(5+120-i,236-tmp,ILI9341_RED);//ucg.setColor(255, 255, 255); else ucg.setColor(255, 100, 100); 
   
     tmp=chart_dry[ID].tOutChart[x]&0x7f;   // Отбросить старший разряд - признак включения калорифера   
     if ((tmp==0) || (tmp==100)) tft.drawPixel(5+120-i,236-tmp,ILI9341_WHITE);
     else tft.drawPixel(5+120-i,236-tmp,ILI9341_GREEN);//ucg.setColor(255, 255, 255); else ucg.setColor(100, 255, 100); 
     
     if (chart_dry[ID].absHInChart[x]==100) tft.drawPixel(5+120-i+162,236-chart_dry[ID].absHInChart[x],ILI9341_WHITE);
     else tft.drawPixel(5+120-i+162,236-chart_dry[ID].absHInChart[x],ILI9341_RED);//ucg.setColor(255, 255, 255); else ucg.setColor(255, 100, 100); 
   
     if (chart_dry[ID].absHOutChart[x]==100) tft.drawPixel(5+120-i+162,236-chart_dry[ID].absHOutChart[x],ILI9341_WHITE);
     else tft.drawPixel(5+120-i+162,236-chart_dry[ID].absHOutChart[x],ILI9341_GREEN);//ucg.setColor(255, 255, 255); else ucg.setColor(100, 255, 100); 
         }
   
// if (chart_dry[ID].posChart<120-1) chart_dry[ID].posChart++; else chart_dry[ID].posChart=0;  // Изменили положение в буфере и Замкнули буфер
}

// Получение данных это все в прерывании быстренько надо
bool dry_get_data(uint8_t *OutputBuf)
{ 
  uint8_t ID;
  if (OutputBuf[0]==0x21) ID=0;
  if (OutputBuf[0]==0x22) ID=1;
  
  memcpy(&packet_0x20[ID],OutputBuf,sizeof(type_packet_0x20_NRF24));  // скопировать из буфера в структуру
  chart_dry[ID].CoolData=true;  // Получены свежие данные
   // Рисуем графики в памяти, а когда потребуется быстро выводим.
  chart_dry[ID].TimeChart++;
  if ((long)((long)chart_dry[ID].TimeChart*TIME_SCAN_SENSOR*NUM_SAMPLES)>=(long)TIME_PRINT_CHART) // проверка не пора ли добавлять точку на график
     {
     // Работаем через кольцевой буфер
     // Добавить новую точку в кольцевой буфер
     // Температура в доме. диапазон -25 . . . +25 растягиваем на 100 точек
     if (packet_0x20[ID].tIn<=-2500)       chart_dry[ID].tInChart[chart_dry[ID].posChart]=0;       // Если температура меньше -25 то округляем до -25
     else  if (packet_0x20[ID].tIn>=2500)  chart_dry[ID].tInChart[chart_dry[ID].posChart]=100-1;   // Если температура больше 25  то округляем до 25
      else chart_dry[ID].tInChart[chart_dry[ID].posChart]=(packet_0x20[ID].tIn+2500)/50;           // внутри -25...+25 растягиваем в два раза
   
     if (chart_dry[ID].ChartMotor==true) chart_dry[ID].tInChart[chart_dry[ID].posChart]|=0x80; // Признак включения мотора- старший бит в 1 - цвет фона на графике меняется
     chart_dry[ID].ChartMotor=false;

     // Температура на улице. диапазон -25 . . . +25 растягиваем на 100 точек
     if (packet_0x20[ID].tOut<=-2500) chart_dry[ID].tOutChart[chart_dry[ID].posChart]=0;           // Если температура меньше -25 то округляем до -25
     else  if (packet_0x20[ID].tOut>=2500)  chart_dry[ID].tOutChart[chart_dry[ID].posChart]=100-1; // Если температура больше 25  то округляем до 25
      else chart_dry[ID].tOutChart[chart_dry[ID].posChart]=(packet_0x20[ID].tOut+2500)/50;         // внутри -25...+25 растягиваем в два раза

    if (chart_dry[ID].ChartHeat==true) chart_dry[ID].tOutChart[chart_dry[ID].posChart]|=0x80; // Признак включения нагревателя- старший бит в 1 - цвет фона на графике меняется
     chart_dry[ID].ChartHeat=false;

     // Абсолютная влажность в доме диапазон от 0 до 20 грамм на кубометр, растягиваем на 100 точек
     if (packet_0x20[ID].absHIn>=2000) chart_dry[ID].absHInChart[chart_dry[ID].posChart]=100-1;
     else chart_dry[ID].absHInChart[chart_dry[ID].posChart]=packet_0x20[ID].absHIn/20;   // внутри 0...20 растягиваем в пять  раз в сотых % по этому делем не на 100 а на 20

     // Абсолютная влажность на улицу диапазон от 0 до 20 грамм на кубометр, растягиваем на 100 точек
     if (packet_0x20[ID].absHOut>=2000) chart_dry[ID].absHOutChart[chart_dry[ID].posChart]=100-1;
     else chart_dry[ID].absHOutChart[chart_dry[ID].posChart]=packet_0x20[ID].absHOut/20;   // внутри 0...20 растягиваем в пять раз,  в сотых % по этому делем не на 100 а на 20
     
     if (chart_dry[ID].posChart<120-1) chart_dry[ID].posChart++; else chart_dry[ID].posChart=0;  // Изменили положение в буфере и Замкнули буфер   
     chart_dry[ID].TimeChart=0;  // Сдвиг графика и вывод новой точки сброс счетчика  
  //   dry_chart(ID);
   } 
} 

void dry_update(uint8_t ID)
{
  if (chart_dry[ID].CoolData==true) 
  {
   dry_data(ID);
   chart_dry[ID].CoolData=false;
  }
}



Проект продолжает развиваться только на stm32,  Больше не будет поддерживаться двух платфор avr и stm32 одновременно.

pav2000
Offline
Зарегистрирован: 15.12.2014

Очередная модификация кода. Основные изменения:

1. Добавлен экран по контролю тепловым насосом. Данные передаются но пока графики не выводятся.

2. В рамках развития проекта сделал автономный датчик температуры и давления (на ВМ180 и pro mini) питание от одной литиевой батереи 1/2 АА 3.6 вольта. Ток потребления в режиме power_down около 40 мкА.  Передача данных раз в 5 минут по nrf24 (будет отдельный пост). Теперь головной блок выводит информацию с этих датчиков. Возможно подключение до 3 шт.

3. Не значительная опитмизация кода.

Код stm32_server_GFX.ino (основной блок без особенностьей переферии)

/*
Описание
1. Часы реального времени. Необходимые аппаратные доработки:
1.1 Надо припаять батарейку 3 вольта на ноги bat (M20 pin 3 "+") и gnd (J2 pin 2 "-")
1.2 Припаять часовой кварц 32.768 к цифровым ногам 13 и 12 (М20 pin 4 и 5)  Эти ноги после этого использовать НЕЛЬЗЯ!!!

*/

#pragma GCC optimize ("-Os")
//#pragma pack(push, 1)     // выравнивание по одному байту ????????

//#define DEMO           // Признак демонстрации - данные с датчиков генерятся рандом другие интервалы
#define VERSION "Version: 0.51 alpha 28/11/15"        // Текущая версия
#define SPI_16BIT
#define SPI_MODE_DMA 1
#define SPEED_UP 1 // Enables extra calculations in the circles routine to use fastVLine and fastHLine, only in DMA mode.
  
#include <itoa.h>  
#include <SPI.h>       // в зависимости от платформы используется разная библиотека
#include "Adafruit_GFX_AS.h"
#include "Adafruit_ILI9341_STM.h"

#include "nRF24L01.h"  // не требует адаптации
#include "RF24.h"      // адаптирована для Maple mini

#include <RTClock.h>
RTClock rt (RTCSEL_LSE); // используется внешний часовой кварц
uint32 tt; 

#include   "stmTime.h"     // Time library - https://github.com/PaulStoffregen/Time
#define TZ "UTC+3"      // Часовой пояс

   #include <libmaple/iwdg.h>     // Сторожевой таймер
   #include <libmaple/adc.h>      // АЦП
   
   #define DSB_ALL_IRQ asm volatile("cpsid i");  // запретить все прерывания
   #define ENB_ALL_IRQ asm volatile("cpsie i");  // разрешить все прерывания
   // Что куда припаяно
   #define NRF24_CE_PIN   3
   #define NRF24_CSN_PIN  7
   #define NRF24_IRQ_PIN  PB10   // прерывание радио
   #define TFT_DC_PIN     11     // 12 старая нога
   #define TFT_CS_PIN     10     // 13 старая нога
   #define TFT_RST_PIN    14     // сброс дисплея
   #define LED_PIN        PB1    // светодиод на плате
   #define PIN_A          2      // энкодер канал А
   #define PIN_B          9      // энкодер канал B
   #define PIN_SW         8      // энкодер Кнопка
   
// Аппаратный SPI на дисплей ILI9341 и NRF24
// Use hardware SPI (on Uno, #13, #12, #11) and the above for CS/DC
Adafruit_ILI9341_STM tft = Adafruit_ILI9341_STM(TFT_CS_PIN, TFT_DC_PIN);

 RF24 radio(NRF24_CE_PIN, NRF24_CSN_PIN);  // радиомодуль

 #define NRF24_CHANEL 100 
 #define NUM_SCREEN   4+1      // Число экранов + 1 стартовый

// Цвета дополнительные
#define ILI9341_GRAY   0xBDF7	// Light Gray
#define ILI9341_DGRAY  0x7BEF	// Dark Gray
#define ILI9341_ORANGE 0xFBE0	// Orange
#define ILI9341_BROWN  0x79E0	// Brown
#define ILI9341_PINK   0xF81F	// Pink


int  err_count=-1;  // статистика по пропущенным пакетам -1 признак первого старта
  // энкодер
volatile uint8_t enc=0;
volatile int newpos=0;
bool packet_ready=false;     // true  поступили новые данные
bool change_screen=false;    // true  сменился экран
int last_min=-1;             // для вывода часов
char OutputBuf[100];         // буффер используется для вывода на экран (туда кладут результат различных преобразований перед выводом) 

//bool signalStrength;
byte last_error=100;                            // Предыдущая ошибка<packet.error
// Проверка радио по прерываниям от радиомодуля
void check_radio(void);

void setup(){
   pinMode(PB1, OUTPUT);              // Светодиод
   pinMode(NRF24_IRQ_PIN, INPUT);     // Прерывание
   pinMode(PIN_A, INPUT); 
   pinMode(PIN_B, INPUT);
   pinMode(PIN_SW, INPUT);
   digitalWrite(PIN_A, HIGH);       // turn on pullup resistor
   digitalWrite(PIN_B, HIGH);       // turn on pullup resistor
   iwdg_init(IWDG_PRE_256, 1250);   // init an 8 second wd timer

  // Настройка радиомодуля
  radio.begin();
  radio.setDataRate(RF24_250KBPS);         //  выбор скорости RF24_250KBPS RF24_1MBPS RF24_2MBPS
  radio.setPALevel(RF24_PA_MAX);           // выходная мощность передатчика
  radio.setChannel(NRF24_CHANEL);          // тут установка канала
  radio.setCRCLength(RF24_CRC_16);         // использовать контрольную сумму в 16 бит
  radio.setAutoAck(true);                  // включить аппаратное потверждение
//  radio.enableDynamicPayloads();           // разрешить Dynamic Payloads
  radio.enableAckPayload();                // разрешить AckPayload - ответ с полезной информацией
  radio.setRetries(50,10);                 // Количество повторов и пауза между повторами
  // Рекомендуют первые 2-4 байта адреса устанавливать в E7 или 18 он проще детектируется чипом
  radio.openWritingPipe(0xE7E7E7E7D2LL);   // передатчик
  radio.openReadingPipe(1,0xE7E7E7E7E1LL); // приемник
  radio.startListening();
  
  reset_ili9341();
  start_screen();

 attachInterrupt(NRF24_IRQ_PIN, check_radio, FALLING); // Прикрепление прерывания радио
// attachInterrupt(PIN_A, updateEncoder, CHANGE);        // Прикрепление прерывания энкодер
// attachInterrupt(PIN_B, updateEncoder, CHANGE);        // Прикрепление прерывания энкодер
 attachInterrupt(PIN_SW, scanKey, FALLING);

rtc_set_prescaler_load(0x7fff);  // установка делителя для часов
// setCurrentTime(); // Установка времени
 rt.attachSecondsInterrupt(show_time);
}

void setCurrentTime() // становка заданного времени один раз загружается а потом отключается
{
setTime(14,46,20,28,11,2015);
time_t t = now();
rt.setTime(t);
}

void scanKey()
{ 
    int oldpos=newpos;
    byte key=digitalRead(PIN_SW);
    if (key==0) newpos++;
    if (newpos>=NUM_SCREEN)   newpos=0;  
    if (oldpos!=newpos)  change_screen=true; // только когда было изменение
}
void updateEncoder() //адаптировано на деление на 2 и диапазон по количеству экранов
{ 
  int oldpos=newpos;
  uint8_t newenc=2*digitalRead(PIN_B)+digitalRead(PIN_A); //берем 2 бита входов и сдвигаем в позицию 0,1
  uint8_t temp=enc ^ newenc; //побитовое исключающее или
  if (temp == 0b01)
    if (enc==0b00 || enc==0b11) newpos++; else newpos--;
  else 
  if(temp == 0b10)
    if (enc==0b00 || enc==0b11) newpos--; else newpos++;
 enc=newenc;
 if (newpos<0)             newpos=NUM_SCREEN-1;
 if (newpos>=NUM_SCREEN)   newpos=0;
 if (oldpos!=newpos)  change_screen=true; // только когда было изменение
}


void loop()
{
 if (change_screen==true)  // Смена экрана
 {  DSB_ALL_IRQ;
    change_screen=false; 
    packet_ready=true;
  //  ucg.clearScreen(); 
   tft.fillScreen(ILI9341_BLACK);
    switch (newpos) { // по положению Энкодера
    case 0:   // стартовый экран
       start_screen();
       break;
    case 1: // осушитель ID 0x21
       dry_static(0);
       dry_data(0);
       dry_chart(0);
      break;
    case 2: // осушитель ID 0x22
       dry_static(1);
       dry_data(1);
       dry_chart(1);
  //      dry_update(1);
       break;
     case 3: // датчики ID 0x30
       temp_static(0);
       temp_data(0);
       temp_chart(0);
       break;
     case 4: // осушитель ID 0x10
       heatpump_static(1);
       
    break;
    default: newpos=0;
    }
  ENB_ALL_IRQ;  
 last_min=-1;    
 } 
  
 if (packet_ready==true)  // Есть не обработанные данные - надо нарисовать 
 {
    packet_ready=false; 
    DSB_ALL_IRQ;
    switch (newpos) { // по положению Энкодера
    case 0:   // стартовый экран
   //      start_screen();
         break;
    case 1: // осушитель ID 0x21
        dry_update(0);
  //      dry_chart(0);
        break;
    case 2: // осушитель ID 0x21
        dry_update(1);
 //       dry_chart(1);
        break;
    case 3: // датчики ID 0x31  32  33 
        temp_update(0);
        temp_update(1);
        temp_update(2);
        break;
    
        
    default: newpos=0;
       }
   ENB_ALL_IRQ;    
   digitalWrite(PB1,HIGH);
   delay(5);
   digitalWrite(PB1,LOW) ;
 }
 
iwdg_feed();  // Сброс сторожевого таймера
delay(100);  // без этого квитанции не приходят
tt = rt.getTime();
}

// Вывод строки константы в координаты x y маленьким шрифтом 
void print_StrXY(int x,int y, char* b)
{
  tft.setCursor(x, y);
  tft.println(b);
} 


char hex(byte x)  // Функия для вывода в hex
{
   if(x >= 0 && x <= 9 ) return (char)(x + '0');
   else                  return (char)('a'+x-10);
}


void check_radio(void) // ПРИЕМ ПАКЕТА Прерывание для проверки радио
{
   uint8_t buf[33];     // Буффер для чтения 32+1
   byte pipe = 0;
  while(radio.available(&pipe) ) // читаем весь буфер до 3 посылок 
 {
   packet_ready=true;   // есть не обработанные данные
   radio.writeAckPayload(1, &tt, sizeof(tt) ); // подготавливаем ответ с времением - передаем текущее время в удаленный блок
   radio.read(&buf, sizeof(buf));              // Читаем в промежуточный буффер, далее анализируем 1 байт id
   if ((buf[0]&0xf0)==0x20)  dry_get_data(buf); // анализ по ID тип устройства ОСУШИТЕЛЬ
   if ((buf[0]&0xf0)==0x30)  temp_get_data(buf); // анализ по ID тип устройства РадиоДатчик
 } 
}

// Очистка экрана
bool reset_ili9341(void)
{
  pinMode(TFT_RST_PIN, OUTPUT);      // Сброс дисплея сигнал активным является LOW
  digitalWrite(TFT_RST_PIN, LOW);  
  delay(100);
  digitalWrite(TFT_RST_PIN, HIGH);  
  // Дисплей
   tft.begin();
   tft.setRotation(1);
   tft.fillScreen(ILI9341_BLACK);
}

bool start_screen(void)
{
  // Заголовок
  tft.fillRect(0, 0, 320-1, 20, ILI9341_BLUE);
  tft.setTextColor(ILI9341_WHITE); 
  tft.drawString(utf8rus("Удаленный мониторинг",OutputBuf),1,1,2); 
 
  tft.setTextColor(ILI9341_YELLOW); 
  tft.drawString(utf8rus("1. Температура блока градусы: ",OutputBuf),2,2+18*1,2);  
  tft.drawString(ftoa(OutputBuf, (1750.0-(read_VDDA()))/4.3+25.0,2),280,2+18*1,2);  
  
  tft.drawString(utf8rus("2. Канал NRF24l01+:",OutputBuf),2,2+18*2,2);  
  tft.drawString(int2str(NRF24_CHANEL),280,2+18*2,2);

  tft.drawString(utf8rus("3. Число внешних устройств:",OutputBuf),2,2+18*3,2);  
  tft.drawString(int2str(NUM_SCREEN-1),280,2+18*3,2);
  
  tft.drawString(utf8rus("4. Напряжение питания В:",OutputBuf),2,2+18*4,2);  
//  tft.drawString(int2str(NUM_SCREEN-1),280,2+18*4,2);
  
  tft.setTextColor(ILI9341_RED); 
  print_StrXY(2,240-10,VERSION);
  
}

// Чтение опорного напряжения
uint16 read_VDDA(void)
{
  adc_reg_map *regs = ADC1->regs;
// 3. Set the TSVREFE bit in the ADC control register 2 (ADC_CR2) to wake up the
//    temperature sensor from power down mode.  Do this first 'cause according to
//    the Datasheet section 5.3.21 it takes from 4 to 10 uS to power up the sensor.
  regs->CR2 |= ADC_CR2_TSEREFE;
// 1. Select the ADCx_IN16 input channel.
  regs->SQR1 = 0;	        // set regular channel sequence length to 1
  regs->SQR3 = 0b10000;		// select channel 16
// 2. Select a sample time of 17.1 μs
// per gbulmer: set channel 16 sample time to 239.5 cycles
// 239.5 cycles of the ADC clock (72MHz/6=12MHz) is over 17.1us (about 20us), but no smaller
// sample time exceeds 17.1us.
  regs->SMPR1 = (0b111 << (3*6));
// 4. Start the ADC conversion by setting the ADON bit (or by external trigger).
//  note by virture of bit 11 being zero returns right aligned results.
// Aparently we also need SWSTART - tdc
  regs->CR2 |= (ADC_CR2_SWSTART | ADC_CR2_ADON);
// wait for conversion to complete
    while (!(regs->SR & ADC_SR_EOC))	;
// 5. Read the resulting VSENSE data in the ADC data register
  return (uint16)(regs->DR & ADC_DR_DATA);
}

// Показ времени по секундным прерываниям
void show_time()
{ byte temp;
  char t[6];
if (last_min!=minute(tt))  // выводим только изменение - т.е. раз в минуту
{
last_min=minute(tt);
    // Быстро формируем строку со временем а потом выводим
    temp=hour(tt);
    if (temp<10) t[0]=48; else t[0]=temp/10+48;   // код "0" 48
    t[1]=temp%10+48;
    t[2]=58; // ":"
    temp=minute(tt);
    if (temp<10) t[3]=48; else t[3]=temp/10+48;
    t[4]=temp%10+48;
    t[5]=0; // Конец строки
    tft.setTextColor(ILI9341_YELLOW);
    switch (newpos) { // в зависимости от экрана
    case 0:   // стартовый экран
       tft.fillRect(275, 0, 45, 18, ILI9341_BLUE);
       tft.drawString(t,275,1,2);  
       break;
    case 1: // осушитель ID 0x21
    case 2: // осушитель ID 0x22
       tft.fillRect(210, 0, 45, 18, ILI9341_BLUE);
       tft.drawString(t,210,1,2);  
       break;
    case 3:  // Датчики   ID 0x30
    case 4:  // Тепловой насос
    tft.fillRect(275, 0, 45, 18, ILI9341_BLUE);
    tft.drawString(t,275,1,2);  
   }
  }
}
// Перевод кодировки из UTF8 в 1251
char* utf8rus(String source, char* buf)
{
  int i,k,j;
  unsigned char n;
  char m[2] = { '0', '\0' };
  k = source.length(); i = 0; j=0;
  while (i < k) {
    n = source[i]; i++;
    if (n >= 0xC0) {
      switch (n) {
        case 0xD0: {
          n = source[i]; i++;
          if (n == 0x81) { n = 0xA8; break; }
          if (n >= 0x90 && n <= 0xBF) n = n + 0x30;
          break;
        }
        case 0xD1: {
          n = source[i]; i++;
          if (n == 0x91) { n = 0xB8; break; }
          if (n >= 0x80 && n <= 0x8F) n = n + 0x70;
          break;
        }
      }
    }
    buf[j]=n; j++;
  }
buf[j]=0;   //  окончание строки 
return buf;
}


// int to str - для уменьшения кода
char _int2str[7];
char* int2str( register int i ) {
  register unsigned char L = 1;
  register char c;
  register boolean m = false;
  register char b;  // lower-byte of i
  // negative
  if ( i < 0 ) {
    _int2str[ 0 ] = '-';
    i = -i;
  }
  else L = 0;
  // ten-thousands
  if( i > 9999 ) {
    c = i < 20000 ? 1
      : i < 30000 ? 2
      : 3;
    _int2str[ L++ ] = c + 48;
    i -= c * 10000;
    m = true;
  }
  // thousands
  if( i > 999 ) {
    c = i < 5000
      ? ( i < 3000
          ? ( i < 2000 ? 1 : 2 )
          :   i < 4000 ? 3 : 4
        )
      : i < 8000
        ? ( i < 6000
            ? 5
            : i < 7000 ? 6 : 7
          )
        : i < 9000 ? 8 : 9;
    _int2str[ L++ ] = c + 48;
    i -= c * 1000;
    m = true;
  }
  else if( m ) _int2str[ L++ ] = '0';
  // hundreds
  if( i > 99 ) {
    c = i < 500
      ? ( i < 300
          ? ( i < 200 ? 1 : 2 )
          :   i < 400 ? 3 : 4
        )
      : i < 800
        ? ( i < 600
            ? 5
            : i < 700 ? 6 : 7
          )
        : i < 900 ? 8 : 9;
    _int2str[ L++ ] = c + 48;
    i -= c * 100;
    m = true;
  }
  else if( m ) _int2str[ L++ ] = '0';
  // decades (check on lower byte to optimize code)
  b = char( i );
  if( b > 9 ) {
    c = b < 50
      ? ( b < 30
          ? ( b < 20 ? 1 : 2 )
          :   b < 40 ? 3 : 4
        )
      : b < 80
        ? ( i < 60
            ? 5
            : i < 70 ? 6 : 7
          )
        : i < 90 ? 8 : 9;
    _int2str[ L++ ] = c + 48;
    b -= c * 10;
    m = true;
  }
  else if( m ) _int2str[ L++ ] = '0';
  // last digit
  _int2str[ L++ ] = b + 48;
  // null terminator
  _int2str[ L ] = 0;  
  return _int2str;
}

// Float to String   - экономим место 
char *ftoa(char *a, double f, int precision)
{
 long p[] = {0,10,100,1000,10000,100000,1000000,10000000,100000000};
 char *ret = a;
 long heiltal = (long)f;
 itoa(heiltal, a, 10);
 while (*a != '\0') a++;
 *a++ = '.';
 long desimal = abs((long)((f - heiltal) * p[precision]));
 itoa(desimal, a, 10);
 return ret;
}

Файл dry_control.ino  (Контроль влажности подвала http://arduino.ru/forum/proekty/kontrol-vlazhnosti-podvala-arduino-pro-mini)

//=======================================================================
//  Осушитель подвала
//=======================================================================
// Все функции имеют на входе в качестве параметра номер датчика

// Мои макросы
#define MOTOR_BIT                 0            // бит мотора в packet_0x20.flags
#define HEAT_BIT                  1            // бит калорифера в packet_0x20.flags
#define ABS_H_BIT                 2            // бит калорифера в packet_0x20.flags
#define MODE_BIT                  5            // первый бит режима в packet_0x20.flags

#define FLAG_ABS_H_ON             packet_0x20[ID].flags |= (1<<ABS_H_BIT)   // бит ABS_H_BIT установить в 1
#define FLAG_ABS_H_OFF            packet_0x20[ID].flags &= ~(1<<ABS_H_BIT)  // бит ABS_H_BIT установить в 0
#define FLAG_ABS_H_CHECK          packet_0x20[ID].flags & (1<<ABS_H_BIT)    // бит ABS_H_BIT проверить на 1

#define FLAG_MOTOR_ON             packet_0x20[ID].flags |= (1<<MOTOR_BIT)   // бит мотора установить в 1
#define FLAG_MOTOR_OFF            packet_0x20[ID].flags &= ~(1<<MOTOR_BIT)  // бит мотора установить в 0
#define FLAG_MOTOR_CHECK          packet_0x20[ID].flags & (1<<MOTOR_BIT)    // бит мотора проверить на 1

#define FLAG_HEAT_ON              packet_0x20[ID].flags |= (1<<HEAT_BIT)   // бит калорифера установить в 1
#define FLAG_HEAT_OFF             packet_0x20[ID].flags &= ~(1<<HEAT_BIT)  // бит калорифера установить в 0
#define FLAG_HEAT_CHECK           packet_0x20[ID].flags & (1<<HEAT_BIT)    // бит калорифера проверить на 1

#define NUM_SAMPLES      10                  // Число усреднений измерений датчика
#define TIME_SCAN_SENSOR 3000                // Время опроса датчиков мсек
#define TIME_PRINT_CHART 300000              // Время вывода точки графика мсек
#define TIME_HOUR        3600000             // Число мсек в часе

#define NUM_DRY    2                        // число осушителей id 0x21 0x22 
// Пакет передаваемый, используется также для хранения результатов. 
 struct type_packet_0x20_NRF24   // Версия 2.4!! адаптация для stm32 Структура передаваемого пакета 32 байта - 32 максимум
    {
        byte id=0x00;                           // Идентификатор типа устройства - старшие 4 бита, вторые (младшие) 4 бита серийный номер устройства
        byte DHT_error;                         // Ошибка разряды: 0-1 первый датчик (00-ок) 2-3 второй датчик (00-ок) 4 - радиоканал     
        int16_t   tOut=-500,tIn=500;            // Текущие температуры в сотых градуса !!! место экономим
        uint16_t  absHOut=123,absHIn=123;       // Абсолютные влажности в сотых грамма на м*3 !!! место экономим
        uint16_t  relHOut=123,relHIn=123;       // Относительные влажности сотых процента !!! место экономим
        uint8_t   flags=0x00;                   // байт флагов  
                                                // 0 бит - мотор включен/выключен 
                                                // 1 бит - нагреватель включен/выключен
                                                // 2 бит -[1 - dH_min задается в сотых грамма на м*3] [0 - dH_min заадется в ДЕСЯТЫХ процента от absHIn]
                                                // 3 4 - пока пусто
                                                // 5-7 - номер настройки = settingRAM.mode до 8 настроек, надо передавать, что бы на приемнике восстановить
        uint8_t   dH_min;                       // Порог включения вентилятора по РАЗНИЦЕ абсолютной влажности в сотых грамма на м*3 или в ДЕСЯТЫХ % см flags:2
        uint8_t   T_min;                        // Порог выключения вентилятора по температуре в ДЕСЯТЫХ долях градуса, только положительные значения
        uint8_t   count=0;                      // циклический счетчик отправленных пакетов нужен что бы на приемнике проверять качество связи
        char note[14] = "NONE";                 // Примечание не более 13 байт + 0 байт Русские буквы в два раза меньше т.к. UTF-8
    } packet_0x20[NUM_DRY]; 
 
// Cтруктура для графиков
struct type_chart_dry
{
      byte tOutChart[120];
      byte tInChart[120];
      byte absHOutChart[120];
      byte absHInChart[120];
      byte posChart=0;         // Позиция в массиве графиков - начало вывода от 0 до 120-1
      byte TimeChart=0;        // Время до вывода очередной точки на график. 
      bool ChartMotor=false;   // Признак работы мотора во время интервала графика если мотор был включен на любое время то на графике фон меняется в этом месте
      bool ChartHeat=false;    // Признак работы нагревателя во время интервала графика если нагреватель был включен на любое время (даже одно измерение) то на графике фон меняется в этом месте
      bool CoolData=false;     // Признак наличия свежих данных, что бы лишний раз не выводить на экран одно и тоже
} chart_dry[NUM_DRY];

void dry_static(uint8_t ID)  // Печать статической картинки 
{ int i;
  // Заголовок 
  tft.fillRect(0, 0, 320-1, 20, ILI9341_BLUE);
  tft.setTextColor(ILI9341_WHITE); 
  tft.drawString(int2str(newpos),2,2,2);
  tft.drawString(utf8rus(".ОСУШИТЕЛЬ ID: 0x",OutputBuf),13,2,2); 
  tft.drawChar(hex(packet_0x20[ID].id >> 4),149,2,2);
  tft.drawChar(hex(packet_0x20[ID].id&0x0f),159,2,2);
   // Таблица для данных
  tft.drawFastHLine(0,25,320-1,ILI9341_GREEN);
  tft.drawFastHLine(0,25+23*1,320-1,ILI9341_GREEN);
  tft.drawFastHLine(0,25+23*2,320-1,ILI9341_GREEN);
  tft.drawFastHLine(0,25+23*3,320-1,ILI9341_GREEN);
  tft.drawFastHLine(0,25+23*4,320-1,ILI9341_GREEN);
  tft.drawFastVLine(200-4,25,24+23*3, ILI9341_GREEN);
  tft.drawFastVLine(260,25,24+23*3, ILI9341_GREEN);
  
  // Заголовки таблиц
  // В зависимости от id разные надписи - привязка местоположения блока к ID
  tft.setTextColor(ILI9341_RED); 
 // tft.drawString(utf8rus(packet_0x20[ID].note,OutputBuf),180+30-9,28+23*0,2); 
  tft.setTextColor(ILI9341_GREEN); 
  tft.drawString(utf8rus("Улица",OutputBuf),250+20,28+23*0,2); 
  tft.setTextColor(ILI9341_YELLOW); 
  tft.drawString(utf8rus("N/потери",OutputBuf),0,28+23*0,2); 
  tft.drawString(utf8rus("Температура градусы C\xB0",OutputBuf),0,28+23*1,2); 
  tft.drawString(utf8rus("Относительная влаж. %",OutputBuf),0,28+23*2,2);
  tft.drawString(utf8rus("Абсолют. влаж. г/м*3",OutputBuf),0,28+23*3,2);
 
  // Графики
  tft.setTextColor(ILI9341_WHITE); 
  print_StrXY(10,125+0,utf8rus("Температура C\xB0",OutputBuf)); 
  print_StrXY(20+154,125+0,utf8rus("Абс. влажность",OutputBuf)); 
  // надписи на графиках
  print_StrXY(128,140,"+20"); 
  print_StrXY(135,180,"0"); 
  print_StrXY(128,220,"-20");
  
  print_StrXY(296,150,"15");
  print_StrXY(296,180,"10");
  print_StrXY(296,210,"5");
  
  // Горизонтальная шкала по часам
  for(i=0;i<=120;i=i+12)
    {
    tft.drawPixel(4+i,239,ILI9341_DGRAY);
    tft.drawPixel(4+i,238,ILI9341_DGRAY);
    tft.drawPixel(167+i,239,ILI9341_DGRAY);
    tft.drawPixel(167+i,238,ILI9341_DGRAY);
     }
}

void dry_data(uint8_t ID) // Печать панели статуса Значки на статус панели
{
  
  // Заголовок
  // 1. печать ошибки чтения датчиков
  if (packet_0x20[ID].DHT_error!=last_error)  // если статус ошибки поменялся то надо вывести если нет то не выводим - экономия время и нет мерцания
  {
      last_error=packet_0x20[ID].DHT_error; 
      tft.fillRect(290, 0, 30, 18, ILI9341_BLUE); 
      
  if (packet_0x20[ID].DHT_error>0) { tft.setTextColor(ILI9341_RED);   tft.drawString(int2str(packet_0x20[ID].DHT_error),290,2,2); }
      else                         { tft.setTextColor(ILI9341_GREEN); tft.drawString(utf8rus(" ok ",OutputBuf),290,2,2); }    
      
  }
 // 2. Признак включения мотора
  if (FLAG_MOTOR_CHECK)    tft.fillRect(290-25, 3, 12, 12, ILI9341_GREEN); 
  else                     tft.fillRect(290-25, 3, 12, 12, ILI9341_BLACK); 
 

 // Печать значений для дома
  tft.setTextColor(ILI9341_RED);
  tft.fillRect(200+0,28+23*0,50, 18, ILI9341_BLACK);
  tft.drawString(utf8rus(packet_0x20[ID].note,OutputBuf),180+30-9,28+23*0,2); 
  tft.fillRect(200+0,28+23*1,50, 18, ILI9341_BLACK);
  tft.drawString(ftoa(OutputBuf,packet_0x20[ID].tIn/100.0,2),200+0,28+23*1,2);
  tft.fillRect(200+0,28+23*2,50, 18, ILI9341_BLACK);
  tft.drawString(ftoa(OutputBuf,packet_0x20[ID].relHIn/100.0,2),200+0,28+23*2,2);
  tft.fillRect(200+0,28+23*3,50, 18, ILI9341_BLACK);
  tft.drawString(ftoa(OutputBuf,packet_0x20[ID].absHIn/100.0,2),200+0,28+23*3,2);
  
  tft.setTextColor(ILI9341_GREEN);
  tft.fillRect(260+6,28+23*1,50, 18, ILI9341_BLACK);
  tft.drawString(ftoa(OutputBuf,packet_0x20[ID].tOut/100.0,2),260+6,28+23*1,2);  
  tft.fillRect(260+6,28+23*2,50, 18, ILI9341_BLACK);
  tft.drawString(ftoa(OutputBuf,packet_0x20[ID].relHOut/100.0,2),260+6,28+23*2,2);
  tft.fillRect(260+6,28+23*3,50, 18, ILI9341_BLACK);
   tft.drawString(ftoa(OutputBuf,packet_0x20[ID].absHOut/100.0,2),260+6,28+23*3,2);
  // График
  if (chart_dry[ID].TimeChart==0) dry_chart(ID); // когда выводится график на экран
}  

// Печать графика на экране, добавляется одна точка и график сдвигается 
void dry_chart(uint8_t ID) 
{
byte i,x=0;
uint8_t tmp;
//   if ((long)((long)chart_dry[ID].TimeChart*TIME_SCAN_SENSOR*NUM_SAMPLES)<(long)TIME_PRINT_CHART) return;  // график еще рано выводить
   for(i=0;i<120;i++)    // График слева на право
     { 
     // Вычислить координаты текущей точки x в кольцевом буфере. Изменяются от 0 до 120-1
     if (chart_dry[ID].posChart<i) x=120+chart_dry[ID].posChart-i; else x=chart_dry[ID].posChart-i;

     // нарисовать фон в зависимости от статуса мотора
     if  (chart_dry[ID].tInChart[x]>=0x80) 
     { tft.drawFastVLine(5+120-i,237-100,100, tft.color565(0, 60, 9)); tft.drawFastVLine(5+120-i+162,237-100,100, tft.color565(0, 60, 9));} // Мотор был ключен - бледно синий
     else 
        if (chart_dry[ID].tOutChart[x]>=0x80) { tft.drawFastVLine(5+120-i,237-100,100, tft.color565(90, 60, 0)); tft.drawFastVLine(5+120-i+162,237-100,100, tft.color565(90, 60, 0));}// Нагреватель был ключен - бледно желтый
           else   {  tft.drawFastVLine(5+120-i,237-100,100, ILI9341_BLACK); tft.drawFastVLine(5+120-i+162,237-100,100, ILI9341_BLACK); }// ucg.setColor(0, 0, 0);     // все выключено
      
     if (i%5==0) // Пунктирные линии графика
     {
       tft.drawPixel(5+120-i,236-10-1,ILI9341_DGRAY);
       tft.drawPixel(5+120-i,236-50-1,ILI9341_DGRAY);
       tft.drawPixel(5+120-i,236-90-1,ILI9341_DGRAY);
       
       tft.drawPixel(5+120-i+162,236-25-1,ILI9341_DGRAY);
       tft.drawPixel(5+120-i+162,236-50-1,ILI9341_DGRAY);
       tft.drawPixel(5+120-i+162,236-75-1,ILI9341_DGRAY);  
     } 
     
     // Вывести новую точку
     tmp=chart_dry[ID].tInChart[x]&0x7f;   // Отбросить старший разряд - признак включения мотора
     if ((tmp==0)||(tmp==100))   tft.drawPixel(5+120-i,236-tmp,ILI9341_WHITE); 
     else tft.drawPixel(5+120-i,236-tmp,ILI9341_RED);//ucg.setColor(255, 255, 255); else ucg.setColor(255, 100, 100); 
   
     tmp=chart_dry[ID].tOutChart[x]&0x7f;   // Отбросить старший разряд - признак включения калорифера   
     if ((tmp==0) || (tmp==100)) tft.drawPixel(5+120-i,236-tmp,ILI9341_WHITE);
     else tft.drawPixel(5+120-i,236-tmp,ILI9341_GREEN);//ucg.setColor(255, 255, 255); else ucg.setColor(100, 255, 100); 
     
     if (chart_dry[ID].absHInChart[x]==100) tft.drawPixel(5+120-i+162,236-chart_dry[ID].absHInChart[x],ILI9341_WHITE);
     else tft.drawPixel(5+120-i+162,236-chart_dry[ID].absHInChart[x],ILI9341_RED);//ucg.setColor(255, 255, 255); else ucg.setColor(255, 100, 100); 
   
     if (chart_dry[ID].absHOutChart[x]==100) tft.drawPixel(5+120-i+162,236-chart_dry[ID].absHOutChart[x],ILI9341_WHITE);
     else tft.drawPixel(5+120-i+162,236-chart_dry[ID].absHOutChart[x],ILI9341_GREEN);//ucg.setColor(255, 255, 255); else ucg.setColor(100, 255, 100); 
         }
   
// if (chart_dry[ID].posChart<120-1) chart_dry[ID].posChart++; else chart_dry[ID].posChart=0;  // Изменили положение в буфере и Замкнули буфер
}

// Получение данных это все в прерывании быстренько надо
bool dry_get_data(uint8_t *OutputBuf)
{ 
  uint8_t ID;
  if (OutputBuf[0]==0x21) ID=0;
  if (OutputBuf[0]==0x22) ID=1;
  
  memcpy(&packet_0x20[ID],OutputBuf,sizeof(type_packet_0x20_NRF24));  // скопировать из буфера в структуру
  chart_dry[ID].CoolData=true;  // Получены свежие данные
   // Рисуем графики в памяти, а когда потребуется быстро выводим.
  chart_dry[ID].TimeChart++;
  if ((long)((long)chart_dry[ID].TimeChart*TIME_SCAN_SENSOR*NUM_SAMPLES)>=(long)TIME_PRINT_CHART) // проверка не пора ли добавлять точку на график
     {
     // Работаем через кольцевой буфер
     // Добавить новую точку в кольцевой буфер
     // Температура в доме. диапазон -25 . . . +25 растягиваем на 100 точек
     if (packet_0x20[ID].tIn<=-2500)       chart_dry[ID].tInChart[chart_dry[ID].posChart]=0;       // Если температура меньше -25 то округляем до -25
     else  if (packet_0x20[ID].tIn>=2500)  chart_dry[ID].tInChart[chart_dry[ID].posChart]=100-1;   // Если температура больше 25  то округляем до 25
      else chart_dry[ID].tInChart[chart_dry[ID].posChart]=(packet_0x20[ID].tIn+2500)/50;           // внутри -25...+25 растягиваем в два раза
   
     if (chart_dry[ID].ChartMotor==true) chart_dry[ID].tInChart[chart_dry[ID].posChart]|=0x80; // Признак включения мотора- старший бит в 1 - цвет фона на графике меняется
     chart_dry[ID].ChartMotor=false;

     // Температура на улице. диапазон -25 . . . +25 растягиваем на 100 точек
     if (packet_0x20[ID].tOut<=-2500) chart_dry[ID].tOutChart[chart_dry[ID].posChart]=0;           // Если температура меньше -25 то округляем до -25
     else  if (packet_0x20[ID].tOut>=2500)  chart_dry[ID].tOutChart[chart_dry[ID].posChart]=100-1; // Если температура больше 25  то округляем до 25
      else chart_dry[ID].tOutChart[chart_dry[ID].posChart]=(packet_0x20[ID].tOut+2500)/50;         // внутри -25...+25 растягиваем в два раза

    if (chart_dry[ID].ChartHeat==true) chart_dry[ID].tOutChart[chart_dry[ID].posChart]|=0x80; // Признак включения нагревателя- старший бит в 1 - цвет фона на графике меняется
     chart_dry[ID].ChartHeat=false;

     // Абсолютная влажность в доме диапазон от 0 до 20 грамм на кубометр, растягиваем на 100 точек
     if (packet_0x20[ID].absHIn>=2000) chart_dry[ID].absHInChart[chart_dry[ID].posChart]=100-1;
     else chart_dry[ID].absHInChart[chart_dry[ID].posChart]=packet_0x20[ID].absHIn/20;   // внутри 0...20 растягиваем в пять  раз в сотых % по этому делем не на 100 а на 20

     // Абсолютная влажность на улицу диапазон от 0 до 20 грамм на кубометр, растягиваем на 100 точек
     if (packet_0x20[ID].absHOut>=2000) chart_dry[ID].absHOutChart[chart_dry[ID].posChart]=100-1;
     else chart_dry[ID].absHOutChart[chart_dry[ID].posChart]=packet_0x20[ID].absHOut/20;   // внутри 0...20 растягиваем в пять раз,  в сотых % по этому делем не на 100 а на 20
     
     if (chart_dry[ID].posChart<120-1) chart_dry[ID].posChart++; else chart_dry[ID].posChart=0;  // Изменили положение в буфере и Замкнули буфер   
     chart_dry[ID].TimeChart=0;  // Сдвиг графика и вывод новой точки сброс счетчика  
  //   dry_chart(ID);
   } 
} 

void dry_update(uint8_t ID)
{
  if (chart_dry[ID].CoolData==true) 
  {
   dry_data(ID);
   chart_dry[ID].CoolData=false;
  }
}



Файл sensor_NRF24.inо (батарейные датчики http://arduino.ru/forum/proekty/batareinyi-radio-datchik-temperatury-na-...)

//=======================================================================
//  Удаленные датчики температуры влажности и давления
//=======================================================================
// Все функции имеют на входе в качестве параметра номер датчика

#define NUM_SENS    3     
// Пакет передаваемый, используется также для хранения УСРЕДНЕННЫХ результатов. 
 struct type_packet_0x30_NRF24                  // Версия 1.1!! адаптация для stm32 Структура передаваемого пакета 32 байта - 32 максимум
    {
        byte id=0x00;                           // Идентификатор типа устройства - старшие 4 бита, вторые (младшие) 4 бита серийный номер устройства
        byte error=0;                           // Ошибка блока, пока резерв
        uint16_t  Vcc;                          // Напряжение батареи  Милливольты!!!!!
        int16_t  Temp;                          // Температура датчика  сотые градуса
        uint16_t  relH;                         // Относительная влажность датчика  сотые %
        uint16_t  absH;                         // Абсолютная влажность датчика сотые грамма на куб
        uint16_t  P;                            // Давление  резерв
        uint16_t count=0;                       // Циклический счетчик пакетов 2 байта хватит на долго - около 3000 часов
        char ver[5] ;                           // Версия прошивки не более 4 байт + "0" символ 
        char note[13] = "none";                 // Примечание не более 12 байт + "0" байт Русские буквы в два раза меньше т.к. UTF-8
    } packet_0x30[NUM_SENS];

// Cтруктура для графиков
struct type_chart_temp
{
      byte Chart1[120];
      byte Chart2[120];
      byte posChart=0;         // Позиция в массиве графиков - начало вывода от 0 до 120-1
      bool CoolData=false;     // Признак наличия свежих данных, что бы лишний раз не выводить на экран одно и тоже
} chart_temp[NUM_SENS];

void temp_static(uint8_t ID)  // Печать статической картинки 
{ int i;
  // Заголовок 
  tft.fillRect(0, 0, 320-1, 20, ILI9341_BLUE);
  tft.setTextColor(ILI9341_WHITE); 
  tft.drawString(int2str(newpos),2,2,2);
  tft.drawString(utf8rus(".УДАЛЕННЫЕ ДАТЧИКИ NRF24 ",OutputBuf),13,2,2); 
//  tft.drawChar(hex(packet_0x30[ID].id >> 4),169,2,2);
//  tft.drawChar(hex(packet_0x30[ID].id&0x0f),179,2,2);
   // Таблица для данных
  tft.drawFastHLine(0,20,320-1,ILI9341_GREEN);
  tft.drawFastHLine(0,20+20*1,320-1,ILI9341_GREEN);
  tft.drawFastHLine(0,20+20*2,320-1,ILI9341_GREEN);
  tft.drawFastHLine(0,20+20*3,320-1,ILI9341_GREEN);
  tft.drawFastHLine(0,20+20*4,320-1,ILI9341_GREEN);
  tft.drawFastHLine(0,20+20*5,320-1,ILI9341_GREEN);
  tft.drawFastVLine(140-4,20,20*5, ILI9341_GREEN);
  tft.drawFastVLine(200-4,20,20*5, ILI9341_GREEN);
  tft.drawFastVLine(260,20,20*5, ILI9341_GREEN);
  
  // Заголовки таблиц
  // В зависимости от id разные надписи - привязка местоположения блока к ID

//  tft.setTextColor(ILI9341_GREEN); 
//  tft.drawString(utf8rus("Улица",OutputBuf),250+20,22+20*0,2); 
  tft.setTextColor(ILI9341_YELLOW); 
  tft.drawString(utf8rus("Параметр",OutputBuf),0,22+20*0,2); 
  tft.drawString(utf8rus("Температура C\xB0",OutputBuf),0,22+20*1,2); 
  tft.drawString(utf8rus("Влажность %",OutputBuf),0,22+20*2,2);
  tft.drawString(utf8rus("Давление mbar",OutputBuf),0,22+20*3,2);
  tft.drawString(utf8rus("Питание В",OutputBuf),0,22+20*4,2);
 
  // Графики
  tft.setTextColor(ILI9341_WHITE); 
  print_StrXY(10,125+0,utf8rus("Температура C\xB0",OutputBuf)); 
  print_StrXY(20+154,125+0,utf8rus("Давление mbar",OutputBuf)); 
  // надписи на графиках
  print_StrXY(128,140,"+20"); 
  print_StrXY(135,180,"0"); 
  print_StrXY(128,220,"-20");
  
  print_StrXY(295,150,"1035");
  print_StrXY(295,180,"1010");
  print_StrXY(295,210,"985");
  
  // Горизонтальная шкала по часам
  for(i=0;i<=120;i=i+12)
    {
    tft.drawPixel(4+i,239,ILI9341_DGRAY);
    tft.drawPixel(4+i,238,ILI9341_DGRAY);
    tft.drawPixel(167+i,239,ILI9341_DGRAY);
    tft.drawPixel(167+i,238,ILI9341_DGRAY);
     }
}
// Получение данных это все в прерывании быстренько надо
bool temp_get_data(uint8_t *OutputBuf)
{ 
  uint8_t ID;
  if (OutputBuf[0]==0x31) ID=0;
  if (OutputBuf[0]==0x32) ID=1;
  if (OutputBuf[0]==0x33) ID=2;
  chart_temp[ID].CoolData=true;  // Получены свежие данные
  memcpy(&packet_0x30[ID],OutputBuf,sizeof(type_packet_0x30_NRF24));  // скопировать из буфера в структуру

     // Работаем через кольцевой буфер
     // Добавить новую точку в кольцевой буфер
     // Температура датчика. диапазон -25 . . . +25 растягиваем на 100 точек
     if (packet_0x30[ID].Temp<=-2500)       chart_temp[ID].Chart1[chart_temp[ID].posChart]=0;       // Если температура меньше -25 то округляем до -25
     else  if (packet_0x30[ID].Temp>=2500)  chart_temp[ID].Chart1[chart_temp[ID].posChart]=100-1;   // Если температура больше 25  то округляем до 25
      else chart_temp[ID].Chart1[chart_temp[ID].posChart]=(packet_0x30[ID].Temp+2500)/50;           // внутри -25...+25 растягиваем в два раза
   
     // Давление. диапазон 960 . . . 1060 растягиваем на 100 точек
     if (OutputBuf,packet_0x30[ID].P<=960) chart_temp[ID].Chart2[chart_temp[ID].posChart]=0;             // Если 960 округляем 960
     else  if (OutputBuf,packet_0x30[ID].P>=1060)  chart_temp[ID].Chart2[chart_temp[ID].posChart]=100-1; // Если температура больше 1060  то округляем до 1060
      else chart_temp[ID].Chart2[chart_temp[ID].posChart]=packet_0x30[ID].P-960;                     // внутри 960-1060 100 точек 
  
     if (chart_temp[ID].posChart<120-1) chart_temp[ID].posChart++; else chart_temp[ID].posChart=0;  // Изменили положение в буфере и Замкнули буфер   
  
} 

void temp_update(uint8_t ID)
{
  if (chart_temp[ID].CoolData==true) 
  {
   temp_data(ID);
   chart_temp[ID].CoolData=false;
  }
}
void temp_data(uint8_t ID) // Печать значений датчика
{
  switch (ID) { 
    case 0:  // ID0x31 
     tft.setTextColor(ILI9341_RED);
     tft.fillRect(140-1,22+20*0,50, 18, ILI9341_BLACK);
     tft.drawString(utf8rus(packet_0x30[ID].note,OutputBuf),140-1,22+20*0,2); 
     tft.fillRect(140-1,22+20*1,50, 18, ILI9341_BLACK);
     tft.drawString(ftoa(OutputBuf,packet_0x30[ID].Temp/100.0,2),140-1,22+20*1,2);
     tft.fillRect(140-1,22+20*2,50, 18, ILI9341_BLACK);
     tft.drawString(" ---",140-1,22+20*2,2);
     tft.fillRect(140-1,22+20*3,50, 18, ILI9341_BLACK);
     tft.drawString(ftoa(OutputBuf,packet_0x30[ID].P,2),140-1,22+20*3,2); 
     tft.fillRect(140-1,22+20*4,50, 18, ILI9341_BLACK);
     tft.drawString(ftoa(OutputBuf,packet_0x30[ID].Vcc/1000.0,2),140-1,22+20*4,2);
    break;
    case 1:
     tft.setTextColor(ILI9341_BLUE);
     tft.fillRect(200-1,22+20*0,50, 18, ILI9341_BLACK);
     tft.drawString(utf8rus(packet_0x30[ID].note,OutputBuf),200-1,22+20*0,2); 
     tft.fillRect(200-1,22+20*1,50, 18, ILI9341_BLACK);
     tft.drawString(ftoa(OutputBuf,packet_0x30[ID].Temp/100.0,2),200-1,22+20*1,2);
     tft.fillRect(200-1,22+20*2,50, 18, ILI9341_BLACK);
     tft.drawString(ftoa(OutputBuf,packet_0x30[ID].relH/100.0,2),200-1,22+20*2,2);
     tft.fillRect(200-1,22+20*3,50, 18, ILI9341_BLACK);
     tft.drawString(ftoa(OutputBuf,packet_0x30[ID].P,2),200-1,22+20*3,2); 
     tft.fillRect(200-1,22+20*4,50, 18, ILI9341_BLACK);
     tft.drawString(ftoa(OutputBuf,packet_0x30[ID].Vcc/1000.0,2),200-1,22+20*4,2);
   break;
    case 2: 
    break;
  }
   // График
temp_chart(ID); // когда выводится график на экран
} 

// Печать графика на экране, добавляется одна точка и график сдвигается 
void temp_chart(uint8_t ID) 
{
byte i,x=0;
uint8_t tmp;
   for(i=0;i<120;i++)    // График слева на право
     { 
     // Вычислить координаты текущей точки x в кольцевом буфере. Изменяются от 0 до 120-1
     if (chart_temp[ID].posChart<i) x=120+chart_temp[ID].posChart-i; else x=chart_temp[ID].posChart-i;
     // Фон
     tft.drawFastVLine(5+120-i,237-100,100, ILI9341_BLACK); tft.drawFastVLine(5+120-i+162,237-100,100, ILI9341_BLACK);
     
    if (i%5==0) // Пунктирные линии графика
     {
       tft.drawPixel(5+120-i,236-10-1,ILI9341_DGRAY);
       tft.drawPixel(5+120-i,236-50-1,ILI9341_DGRAY);
       tft.drawPixel(5+120-i,236-90-1,ILI9341_DGRAY);
       
       tft.drawPixel(5+120-i+162,236-25-1,ILI9341_DGRAY);
       tft.drawPixel(5+120-i+162,236-50-1,ILI9341_DGRAY);
       tft.drawPixel(5+120-i+162,236-75-1,ILI9341_DGRAY);  
     } 
     
     // Вывести новую точку
     if ((chart_temp[ID].Chart1[x]==0)||(chart_temp[ID].Chart1[x]==100))   tft.drawPixel(5+120-i,236-chart_temp[ID].Chart1[x],ILI9341_WHITE); 
     else tft.drawPixel(5+120-i,236-chart_temp[ID].Chart1[x],ILI9341_RED);
 
     if ((chart_temp[ID].Chart2[x]==0)||(chart_temp[ID].Chart2[x]==100)) tft.drawPixel(5+120-i+162,236-chart_temp[ID].Chart2[x],ILI9341_WHITE);
     else tft.drawPixel(5+120-i+162,236-chart_temp[ID].Chart2[x],ILI9341_RED);
   
         }
   
}

Файл heatpump.ino (Контроль за тепловым насосом http://arduino.ru/forum/proekty/kontrol-za-teplovym-nasosom-na-arduino-uno)

//=======================================================================
//  Тепловой насос
//=======================================================================
// Все функции имеют на входе в качестве параметра номер датчика



#define NUM_HEATPUMP    1                       // число тепловых насосов
// Пакет передаваемый, используется также для хранения результатов. 
 struct type_packet_0x10_NRF24                  // Версия 2.0!! адаптация для stm32 Структура передаваемого пакета 32 байта - 32 максимум
    {
        byte id=0x00;                           // Идентификатор типа устройства - старшие 4 бита, вторые (младшие) 4 бита серийный номер устройства
        byte error;                             // Ошибка теплового насоса
        int16_t tOut1=-5000,tIn1=-5000;        // Текущие температуры ТП в сотых градуса !!! место экономим
        int16_t PowerFloor=0;                  // Мощность теплого пола  вт
        int16_t tOut2=-5000,tIn2=-5000;        // Текущие температуры ГК в сотых градуса !!! место экономим
        int16_t PowerLand=0;                   // Мощность геоконтура  вт
        int16_t I,U,P;                         // Ток в сотых ампера, напряжение в сотых вольта, мощность в вт.
//        uint8_t   count=0;                      // циклический счетчик отправленных пакетов нужен что бы на приемнике проверять качество связи
        char note[12] = "Heat pump";           // Примечание не более 11 байт + 0 байт Русские буквы в два раза меньше т.к. UTF-8
    } packet_0x10[NUM_HEATPUMP]; 


 // Cтруктура для графиков
struct type_chart_heatpump
{
      byte tOut2Chart[120];     // Подача теплого пола
      byte tIn2Chart[120];      // Обратка теплого пола
      byte HeatChart[120];      // Мощность теплого пола
      byte Power[120];          // Потребляемая мощность  ТН
      byte COP[120];            // Коэффициент преобразования
      byte posChart=0;          // Позиция в массиве графиков - начало вывода от 0 до 120-1
      byte TimeChart=0;         // Время до вывода очередной точки на график. 
      bool ChartPump=false;     // Признак работы ТН во время интервала графика если ТН был включен на любое время то на графике фон меняется в этом месте
      bool CoolData=false;      // Признак наличия свежих данных, что бы лишний раз не выводить на экран одно и тоже
} chart_heatpump[NUM_DRY];

void heatpump_static(uint8_t ID)  // Печать статической картинки 
{ int i;
  // Заголовок 
  tft.fillRect(0, 0, 320-1, 20*2, ILI9341_BLUE);
  tft.setTextColor(ILI9341_WHITE); 
  tft.drawString(int2str(newpos),2,2,2);
  tft.drawString(utf8rus(".ТЕПЛОВОЙ НАСОС ID: 0x",OutputBuf),13,2,2); 
  tft.drawChar(hex(packet_0x20[ID].id >> 4),189,2,2);
  tft.drawChar(hex(packet_0x20[ID].id&0x0f),199,2,2);
   // Таблица для данных
  /*
  tft.drawFastHLine(0,25,320-1,ILI9341_GREEN);
  tft.drawFastHLine(0,25+23*1,320-1,ILI9341_GREEN);
  tft.drawFastHLine(0,25+23*2,320-1,ILI9341_GREEN);
  tft.drawFastHLine(0,25+23*3,320-1,ILI9341_GREEN);
  tft.drawFastHLine(0,25+23*4,320-1,ILI9341_GREEN);
  */
  tft.drawFastVLine(140+30-11,41,1+16*5, ILI9341_GREEN);
  tft.drawFastVLine(220+18   ,41,1+16*5, ILI9341_GREEN);
  tft.drawFastHLine(0,42+16*5,320-1,ILI9341_GREEN);
  tft.drawString(utf8rus("Энергия P=12.5 кВт, U=220 В, A=12.5 А",OutputBuf),0,21,2);
  // Заголовки таблиц
  tft.setTextColor(ILI9341_RED); 
  tft.drawString(utf8rus("Тепл. пол",OutputBuf),140+30-9,41+16*0,2); 
  tft.setTextColor(ILI9341_BLUE); 
  tft.drawString(utf8rus("Геоконтур",OutputBuf),220+20,41+16*0,2); 
  tft.setTextColor(ILI9341_YELLOW); 
  tft.drawString(utf8rus("СОР: ",OutputBuf),0,41+16*0,2); 
  tft.drawString(utf8rus("Подача градусы C\xB0",OutputBuf),0,41+16*1,2); 
  tft.drawString(utf8rus("Обратка градусы C\xB0",OutputBuf),0,41+16*2,2);
  tft.drawString(utf8rus("Поток. м*3/ч",OutputBuf),0,41+16*3,2);
  tft.drawString(utf8rus("Мощность. кВт",OutputBuf),0,41+16*4,2);

  // Графики
  tft.setTextColor(ILI9341_WHITE); 
  print_StrXY(10,125+0,utf8rus("Температура",OutputBuf)); 
  print_StrXY(20+154,125+0,utf8rus("Мощность",OutputBuf)); 
  // надписи на графиках
  print_StrXY(128,140,"+20"); 
  print_StrXY(135,180,"0"); 
  print_StrXY(128,220,"-20");
  
  print_StrXY(296,150,"15");
  print_StrXY(296,180,"10");
  print_StrXY(296,210,"5");
  
  // Горизонтальная шкала по часам
  for(i=0;i<=120;i=i+12)
    {
    tft.drawPixel(4+i,239,ILI9341_DGRAY);
    tft.drawPixel(4+i,238,ILI9341_DGRAY);
    tft.drawPixel(167+i,239,ILI9341_DGRAY);
    tft.drawPixel(167+i,238,ILI9341_DGRAY);
     }
}

Файлы фонтов не менялись.

Сейчас провожу исследования по изменению питания, что бы увеличить время работы от аккумулятора.

nikantovik
Offline
Зарегистрирован: 19.06.2015

Добрый день! 

Подскажите пожалуйста, может кто сталкивался, maple mini после выключения питания сбрасывает часы реального времени.

Батарейку подключил  плюсом к Vbat минусом на землю....

misir
Offline
Зарегистрирован: 14.05.2018

Добрый день! Подскажите где взять библиотеки для проекта. У меня при  компиляции ругается на неверная библиотека limaple.

enjoyneering
enjoyneering аватар
Offline
Зарегистрирован: 05.09.2016

вот тут подробнее с картинками о том как запустить stm32 в arduino ide - blue pill

misir
Offline
Зарегистрирован: 14.05.2018

C stm32 я разобрался blink прошил работает, а когда проект компилирую то в конце ошибка неверная библиотека.

misir
Offline
Зарегистрирован: 14.05.2018

Подскажите не выводится не один идентификатор устройства все 0х00 ,как будто ничего не подключено . А часы на осушитель передаются.