Здравствуйте, уважаемые жители и гости Датагора! Представляю проект обмена данными между микроконтроллером ATmega328P и GPS-модулем NEO-6M.
Четкий прокомментированный исходный код на Си прилагается.
Содержание статьи / Table Of Contents
↑ Видео-демонстрация работы проекта
↑ Краткое описание работы модуля
Примерно раз в секунду модуль по протоколу UART со скоростью 9600 bps выдаёт на пин TX пакет символьных данных из представленных на Рисунке 1 шести предложений (sentence), подробнее о которых можно узнать здесь.Рисунок 1. Пакет предложений модуля NEO-6M
Каждое предложение начинается символом «$» и завершается символом «*», после которого следуют контрольная сумма (КС), а также символы возврата каретки «CR» и новой строки «LF», не отображённые на рисунке.
Нас будут интересовать данные трёх предложений: GPRMC (время, дата, широта и долгота), GPVTG (скорость) и GPGGA (высота над уровнем моря). До соединения со спутником данные, кроме времени и даты, — не валидные, что подтверждается символом «V», следующим за значением времени в GPRMC. После соединения, сопровождаемого блинком имеющегося на борту модуля синего светодиода, начинают поступать валидные данные, о чём свидетельствует смена упомянутого символа на «А»
↑ Код проекта
Программа написана для ATmega328P на Си в Visual Studio Code. Скачать архив с исходниками можно внизу, в разделе файлов.↑ Управление GPS-модулем
Процесс заключается в:• настройке протокола UART,
• считыванию и сортировке интересующих данных,
• подсчёте КС предложения (являющейся результатом последовательной операции ИСКЛЮЧАЮЩЕЕ ИЛИ для символов между «$» и «*») и её сверкой с контрольной суммой, полученной от модуля.
При этом, необходимо учитывать следующее:
1. Время между битами, а так же предложениями – чуть более 100 мкс.
2. Из не валидных пакетов следует хранить только значения времени и даты.
3. Желательно обойтись без буферизации.
Учитывая вышеизложенное, структура библиотеки NEO6 включает следующие функции:
а) gps_init():
• устанавливает скорость 9600 bps,
• настраивает пины RX и TX микроконтроллера,
• глобально и локально разрешает прерывание по приёму байта данных.
б) gps_parse_data() в зависимости от имени текущего предложения и порядкового номера принятого символа сохраняет последний в соответствующем поле структуры gps.
в) Обработчик прерывания:
• определяет имя текущего предложения и порядковый номер полученного символа, а затем вызывает функцию gps_parse_data(),
• подсчитывает контрольную сумму предложения, сравнивает её с принятой и сохраняет результат сравнения (false или true) в поле check_sum структуры.
• по завершению пакета устанавливает в 1 флаг packet_end_flag.
#ifndef NEO6_H_
#define NEO6_H_
#include <avr/io.h>
#include <stdint.h>
#include <stdbool.h>
#include <avr/interrupt.h>
#include "display.h"
#define UART_BAUDRATE 9600
#define FREQUENCY 16000000UL
#define UBRR_VALUE FREQUENCY / 16 / UART_BAUDRATE - 1
#define SENTENCE_IS_GPRMC gps.header[0] == 'M' && gps.header[1] == 'C'
#define SENTENCE_IS_GPVTG gps.header[0] == 'T' && gps.header[1] == 'G'
#define SENTENCE_IS_GPGGA gps.header[0] == 'G' && gps.header[1] == 'A'
#define SENTENCE_IS_GPGSA gps.header[0] == 'S' && gps.header[1] == 'A'
#define SENTENCE_IS_GPGSV gps.header[0] == 'S' && gps.header[1] == 'V'
#define SENTENCE_IS_GPGLL gps.header[0] == 'L' && gps.header[1] == 'L'
typedef struct
{
uint8_t counter;
bool do_check_sum;
volatile uint8_t sentence :4;
bool packet_end_flag;
uint8_t status :2;
uint8_t header[2];
uint8_t hours_tens;
uint8_t hours_units;
uint8_t minutes_tens;
uint8_t minutes_units;
uint8_t seconds_tens;
uint8_t seconds_units;
uint8_t date_tens;
uint8_t date_units;
uint8_t month_tens;
uint8_t month_units;
uint8_t year_tens;
uint8_t year_units;
uint8_t latitude_degree_1;
uint8_t latitude_degree_2;
uint8_t latitude_minutes_1;
uint8_t latitude_minutes_2;
uint8_t latitude_minutes_3;
uint8_t latitude_minutes_4;
uint8_t latitude_minutes_5;
uint8_t latitude_minutes_6;
uint8_t latitude_minutes_7;
uint8_t longitude_degree_1;
uint8_t longitude_degree_2;
uint8_t longitude_minutes_1;
uint8_t longitude_minutes_2;
uint8_t longitude_minutes_3;
uint8_t longitude_minutes_4;
uint8_t longitude_minutes_5;
uint8_t longitude_minutes_6;
uint8_t longitude_minutes_7;
uint8_t altitude_hundreds;
uint8_t altitude_tens;
uint8_t altitude_units;
uint8_t altitude_fraction;
uint8_t altitude_unit;
uint8_t check_sum_high_received;
uint8_t check_sum_low_received;
uint8_t speed_integer;
uint8_t speed_fraction_1;
uint8_t speed_fraction_2;
uint8_t speed_fraction_3;
uint8_t speed_indicator;
bool check_sum[7];
} NEO6_t;
extern NEO6_t gps;
enum {NO_STATUS, STATUS_INVALID, STATUS_VALID};
enum {NO_SENTENCE, GPRMC, GPVTG, GPGGA, GPGSA, GPGSV, GPGLL};
enum {CHECK_SUM_RECEIVED, CHECK_SUM_CALCULATED};
/* gprmc */
enum
{
GPRMC_HOURS_TENS = 8, GPRMC_HOURS_UNITS, GPRMC_MINUTES_TENS, GPRMC_MINUTES_UNITS, GPRMC_SECONDS_TENS, GPRMC_SECONDS_UNITS, GPRMC_STATUS = 18, \
GPRMC_LATITUDE_DEGREE_1 = 20, GPRMC_LATITUDE_DEGREE_2, GPRMC_LATITUDE_MINUTES_1, GPRMC_LATITUDE_MINUTES_2, GPRMC_LATITUDE_MINUTES_3 = 25, GPRMC_LATITUDE_MINUTES_4, GPRMC_LATITUDE_MINUTES_5, GPRMC_LATITUDE_MINUTES_6, GPRMC_LATITUDE_MINUTES_7, \
GPRMC_LONGITUDE_DEGREE_1 = 34, GPRMC_LONGITUDE_DEGREE_2, GPRMC_LONGITUDE_MINUTES_1, GPRMC_LONGITUDE_MINUTES_2, GPRMC_LONGITUDE_MINUTES_3 = 39, GPRMC_LONGITUDE_MINUTES_4, GPRMC_LONGITUDE_MINUTES_5, GPRMC_LONGITUDE_MINUTES_6, GPRMC_LONGITUDE_MINUTES_7, \
GPRMC_DATE_TENS = 54, GPRMC_DATE_UNITS, GPRMC_MONTH_TENS, GPRMC_MONTH_UNITS, GPRMC_YEAR_TENS, GPRMC_YEAR_UNITS, \
GPRMC_CHECK_SUM_HIGH = 65, GPRMC_CHECK_SUM_LOW
};
enum {GPRMC_DATE_TENS_INVALID = 26, GPRMC_DATE_UNITS_INVALID, GPRMC_MONTH_TENS_INVALID, GPRMC_MONTH_UNITS_INVALID, GPRMC_YEAR_TENS_INVALID, GPRMC_YEAR_UNITS_INVALID, GPRMC_CHECK_SUM_HIGH_INVALID = 37, GPRMC_CHECK_SUM_LOW_INVALID};
/* gpvtg */
enum {GPVTG_SPEED_INTEGER = 22, GPVTG_SPEED_FRACTION_1 = 24, GPVTG_SPEED_FRACTION_2, GPVTG_SPEED_FRACTION_3, GPVTG_SPEED_INDICATOR = 28, GPVTG_CHECK_SUM_HIGH = 32, GPVTG_CHECK_SUM_LOW};
enum {GPVTG_CHECK_SUM_HIGH_INVALID = 18, GPVTG_CHECK_SUM_LOW_INVALID};
/* gpgga */
enum {GPGGA_ALTITUDE_HUNDREDS = 55, GPGGA_ALTITUDE_TENS, GPGGA_ALTITUDE_UNITS, GPGGA_ALTITUDE_FRACTION = 59, GPGGA_ALTITUDE_UNIT = 61, GPGGA_CHECK_SUM_HIGH = 73, GPGGA_CHECK_SUM_LOW};
enum {GPGGA_CHECK_SUM_HIGH_INVALID = 39, GPGGA_CHECK_SUM_LOW_INVALID};
/* gpgsa */
enum {GPGSA_CHECK_SUM_HIGH = 47, GPGSA_CHECK_SUM_LOW};
enum {GPGSA_CHECK_SUM_HIGH_INVALID = 42, GPGSA_CHECK_SUM_LOW_INVALID};
/* gpgsv */
enum {GPGSV_CHECK_SUM_HIGH = 67, GPGSV_CHECK_SUM_LOW};
enum {GPGSV_CHECK_SUM_HIGH_INVALID = 31, GPGSV_CHECK_SUM_LOW_INVALID};
/* gpgll */
enum {GPGLL_CHECK_SUM_HIGH = 49, GPGLL_CHECK_SUM_LOW};
enum {GPGLL_CHECK_SUM_HIGH_INVALID = 26, GPGLL_CHECK_SUM_LOW_INVALID};
void gps_init();
void gps_parse_data(uint8_t data);
void serialSendByte(char symbol);
void serialPrintln(const char* buff);
void serialPrint(const char* buff);
#endif /* NEO6_H_ */
#include "NEO6.h"
NEO6_t gps = {.do_check_sum = false, .status = STATUS_INVALID, .sentence = NO_SENTENCE, .packet_end_flag = false};
ISR(USART_RX_vect)
{
static uint8_t check_sum = 0;
uint8_t data = UDR0;
gps.counter++;
if(gps.counter == 5)
gps.header[0] = data;
else if(gps.counter == 6)
{
gps.header[1] = data;
if(SENTENCE_IS_GPRMC)
gps.sentence = GPRMC;
else if(SENTENCE_IS_GPVTG)
gps.sentence = GPVTG;
else if(SENTENCE_IS_GPGGA)
gps.sentence = GPGGA;
else if(SENTENCE_IS_GPGSA)
gps.sentence = GPGSA;
else if(SENTENCE_IS_GPGSV)
gps.sentence = GPGSV;
else if(SENTENCE_IS_GPGLL)
gps.sentence = GPGLL;
else
gps.sentence = NO_SENTENCE;
}
else if(gps.counter > 6 && gps.sentence != NO_SENTENCE)
gps_parse_data(data);
switch(data)
{
case '$':
gps.do_check_sum = true;
break;
case '*':
case 13:
gps.do_check_sum = false;
break;
case 10:
{
uint8_t check_sum_high_calculated = check_sum >> 4;
uint8_t check_sum_low_calculated = check_sum & 0x0F;
if(gps.check_sum_high_received >= '0' && gps.check_sum_high_received <= '9')
gps.check_sum_high_received -= 48;
else if(gps.check_sum_high_received >= 'A' && gps.check_sum_high_received <= 'F')
gps.check_sum_high_received -= 55;
if(gps.check_sum_low_received >= '0' && gps.check_sum_low_received <= '9')
gps.check_sum_low_received -= 48;
else if(gps.check_sum_low_received >= 'A' && gps.check_sum_low_received <= 'F')
gps.check_sum_low_received -= 55;
if(check_sum_high_calculated == gps.check_sum_high_received && check_sum_low_calculated == gps.check_sum_low_received)
gps.check_sum[gps.sentence] = true;
else
gps.check_sum[gps.sentence] = false;
gps.do_check_sum = false;
gps.counter = 0;
check_sum = 0;
if(gps.sentence == GPGLL)
gps.packet_end_flag = true;
}
break;
default:
if(gps.do_check_sum == true)
check_sum ^= (uint8_t)data;
break;
}
}
void gps_init()
{
unsigned int ubrr_value = UBRR_VALUE;
UBRR0H = (unsigned char)(ubrr_value >> 8);
UBRR0L = (unsigned char)(ubrr_value);
UCSR0B |= (1 << RXCIE0) | (1 << TXEN0) | (1 << RXEN0);
UCSR0C |= (3 << UCSZ00);
sei();
}
void gps_parse_data(uint8_t data)
{
if(gps.sentence == GPRMC)
{
if(gps.counter == GPRMC_STATUS && data == 'V')
gps.status = STATUS_INVALID;
if(gps.counter == GPRMC_STATUS && data == 'A')
gps.status = STATUS_VALID;
switch(gps.status)
{
case STATUS_INVALID:
if(gps.counter == GPRMC_HOURS_TENS)
gps.hours_tens = data;
else if(gps.counter == GPRMC_HOURS_UNITS)
gps.hours_units = data;
else if(gps.counter == GPRMC_MINUTES_TENS)
gps.minutes_tens = data;
else if(gps.counter == GPRMC_MINUTES_UNITS)
gps.minutes_units = data;
else if(gps.counter == GPRMC_SECONDS_TENS)
gps.seconds_tens = data;
else if(gps.counter == GPRMC_SECONDS_UNITS)
gps.seconds_units = data;
else if(gps.counter == GPRMC_DATE_TENS_INVALID)
gps.date_tens = data;
else if(gps.counter == GPRMC_DATE_UNITS_INVALID)
gps.date_units = data;
else if(gps.counter == GPRMC_MONTH_TENS_INVALID)
gps.month_tens = data;
else if(gps.counter == GPRMC_MONTH_UNITS_INVALID)
gps.month_units = data;
else if(gps.counter == GPRMC_YEAR_TENS_INVALID)
gps.year_tens = data;
else if(gps.counter == GPRMC_YEAR_UNITS_INVALID)
gps.year_units = data;
else if(gps.counter == GPRMC_YEAR_UNITS_INVALID)
gps.year_units = data;
else if(gps.counter == GPRMC_CHECK_SUM_HIGH_INVALID)
gps.check_sum_high_received = data;
else if(gps.counter == GPRMC_CHECK_SUM_LOW_INVALID)
gps.check_sum_low_received = data;
break;
case STATUS_VALID:
/* time */
if(gps.counter == GPRMC_HOURS_TENS)
gps.hours_tens = data;
else if(gps.counter == GPRMC_HOURS_UNITS)
gps.hours_units = data;
else if(gps.counter == GPRMC_MINUTES_TENS)
gps.minutes_tens = data;
else if(gps.counter == GPRMC_MINUTES_UNITS)
gps.minutes_units = data;
else if(gps.counter == GPRMC_SECONDS_TENS)
gps.seconds_tens = data;
else if(gps.counter == GPRMC_SECONDS_UNITS)
gps.seconds_units = data;
/* latitude */
if(gps.counter == GPRMC_LATITUDE_DEGREE_1)
gps.latitude_degree_1= data;
else if(gps.counter == GPRMC_LATITUDE_DEGREE_2)
gps.latitude_degree_2 = data;
else if(gps.counter == GPRMC_LATITUDE_MINUTES_1)
gps.latitude_minutes_1 = data;
else if(gps.counter == GPRMC_LATITUDE_MINUTES_2)
gps.latitude_minutes_2 = data;
else if(gps.counter == GPRMC_LATITUDE_MINUTES_3)
gps.latitude_minutes_3 = data;
else if(gps.counter == GPRMC_LATITUDE_MINUTES_4)
gps.latitude_minutes_4 = data;
else if(gps.counter == GPRMC_LATITUDE_MINUTES_5)
gps.latitude_minutes_5 = data;
else if(gps.counter == GPRMC_LATITUDE_MINUTES_6)
gps.latitude_minutes_6 = data;
else if(gps.counter == GPRMC_LATITUDE_MINUTES_7)
gps.latitude_minutes_7 = data;
/* longitude */
else if(gps.counter == GPRMC_LONGITUDE_DEGREE_1)
gps.longitude_degree_1 = data;
else if(gps.counter == GPRMC_LONGITUDE_DEGREE_2)
gps.longitude_degree_2 = data;
else if(gps.counter == GPRMC_LONGITUDE_MINUTES_1)
gps.longitude_minutes_1 = data;
else if(gps.counter == GPRMC_LONGITUDE_MINUTES_2)
gps.longitude_minutes_2 = data;
else if(gps.counter == GPRMC_LONGITUDE_MINUTES_3)
gps.longitude_minutes_3 = data;
else if(gps.counter == GPRMC_LONGITUDE_MINUTES_4)
gps.longitude_minutes_4 = data;
else if(gps.counter == GPRMC_LONGITUDE_MINUTES_5)
gps.longitude_minutes_5 = data;
else if(gps.counter == GPRMC_LONGITUDE_MINUTES_6)
gps.longitude_minutes_6 = data;
else if(gps.counter == GPRMC_LONGITUDE_MINUTES_7)
gps.longitude_minutes_7 = data;
/* date */
else if(gps.counter == GPRMC_DATE_TENS)
gps.date_tens = data;
else if(gps.counter == GPRMC_DATE_UNITS)
gps.date_units = data;
else if(gps.counter == GPRMC_MONTH_TENS)
gps.month_tens = data;
else if(gps.counter == GPRMC_MONTH_UNITS)
gps.month_units = data;
else if(gps.counter == GPRMC_YEAR_TENS)
gps.year_tens = data;
else if(gps.counter == GPRMC_YEAR_UNITS)
gps.year_units = data;
/* check sum */
else if(gps.counter == GPRMC_CHECK_SUM_HIGH)
gps.check_sum_high_received = data;
else if(gps.counter == GPRMC_CHECK_SUM_LOW)
gps.check_sum_low_received = data;
break;
default:
break;
}
}
else if(gps.sentence == GPVTG)
{
if(gps.status == STATUS_INVALID)
{
if(gps.counter == GPVTG_CHECK_SUM_HIGH_INVALID)
gps.check_sum_high_received = data;
else if(gps.counter == GPVTG_CHECK_SUM_LOW_INVALID)
gps.check_sum_low_received = data;
}
else if(gps.status == STATUS_VALID)
{
if(gps.counter == GPVTG_SPEED_INTEGER)
gps.speed_integer = data;
else if(gps.counter == GPVTG_SPEED_FRACTION_1)
gps.speed_fraction_1 = data;
else if(gps.counter == GPVTG_SPEED_FRACTION_2)
gps.speed_fraction_2 = data;
else if(gps.counter == GPVTG_SPEED_FRACTION_3)
gps.speed_fraction_3 = data;
else if(gps.counter == GPVTG_SPEED_INDICATOR)
gps.speed_indicator = data;
else if(gps.counter == GPVTG_CHECK_SUM_HIGH)
gps.check_sum_high_received = data;
else if(gps.counter == GPVTG_CHECK_SUM_LOW)
gps.check_sum_low_received = data;
}
}
else if(gps.sentence == GPGGA)
{
if(gps.status == STATUS_INVALID)
{
if(gps.counter == GPGGA_CHECK_SUM_HIGH_INVALID)
gps.check_sum_high_received = data;
else if(gps.counter == GPGGA_CHECK_SUM_LOW_INVALID)
gps.check_sum_low_received = data;
}
else if(gps.status == STATUS_VALID)
{
if(gps.counter == GPGGA_ALTITUDE_HUNDREDS)
gps.altitude_hundreds = data;
else if(gps.counter == GPGGA_ALTITUDE_TENS)
gps.altitude_tens = data;
else if(gps.counter == GPGGA_ALTITUDE_UNITS)
gps.altitude_units = data;
else if(gps.counter == GPGGA_ALTITUDE_FRACTION)
gps.altitude_fraction = data;
else if(gps.counter == GPGGA_ALTITUDE_UNIT)
gps.altitude_unit = data;
else if(gps.counter == GPGGA_CHECK_SUM_HIGH)
gps.check_sum_high_received = data;
else if(gps.counter == GPGGA_CHECK_SUM_LOW)
gps.check_sum_low_received = data;
}
}
else if(gps.sentence == GPGSA)
{
if(gps.status == STATUS_INVALID)
{
if(gps.counter == GPGSA_CHECK_SUM_HIGH_INVALID)
gps.check_sum_high_received = data;
else if(gps.counter == GPGSA_CHECK_SUM_LOW_INVALID)
gps.check_sum_low_received = data;
}
else if(gps.status == STATUS_VALID)
{
if(gps.counter == GPGSA_CHECK_SUM_HIGH)
gps.check_sum_high_received = data;
else if(gps.counter == GPGSA_CHECK_SUM_LOW)
gps.check_sum_low_received = data;
}
}
else if(gps.sentence == GPGSV)
{
if(gps.status == STATUS_INVALID)
{
if(gps.counter == GPGSV_CHECK_SUM_HIGH_INVALID)
gps.check_sum_high_received = data;
else if(gps.counter == GPGSV_CHECK_SUM_LOW_INVALID)
gps.check_sum_low_received = data;
}
else if(gps.status == STATUS_VALID)
{
if(gps.counter == GPGSV_CHECK_SUM_HIGH)
gps.check_sum_high_received = data;
else if(gps.counter == GPGSV_CHECK_SUM_LOW)
gps.check_sum_low_received = data;
}
}
else if(gps.sentence == GPGLL)
{
if(gps.status == STATUS_INVALID)
{
if(gps.counter == GPGLL_CHECK_SUM_HIGH_INVALID)
gps.check_sum_high_received = data;
else if(gps.counter == GPGLL_CHECK_SUM_LOW_INVALID)
gps.check_sum_low_received = data;
}
else if(gps.status == STATUS_VALID)
{
if(gps.counter == GPGLL_CHECK_SUM_HIGH)
gps.check_sum_high_received = data;
else if(gps.counter == GPGLL_CHECK_SUM_LOW)
gps.check_sum_low_received = data;
}
}
}
void serialSendByte(char data)
{
while (!(UCSR0A & (1 << UDRE0)));
UDR0 = data;
}
void serialPrintln(const char* buff)
{
while(*buff != '\0')
{
serialSendByte(*buff);
buff++;
}
serialSendByte('\n');
}
void serialPrint(const char* buff)
{
while(*buff != '\0')
{
serialSendByte(*buff);
buff++;
}
}
↑ Вывод данных на дисплей
Для визуализации принимаемых GPS-данных выбран дисплей SSD1283A, за работу которого отвечают пять библиотек:SPI содержит код одноимённого программного протокола.
#ifndef SPI_H_
#define SPI_H_
#define F_CPU 16000000UL
#include <util/delay.h>
#include <avr/io.h>
#include <stdint.h>
#define SCK PD2
#define SCK_DDR DDRD
#define SCK_PORT PORTD
#define SCK_HIGH SCK_PORT |= (1 << SCK)
#define SCK_LOW SCK_PORT &= ~(1 << SCK)
#define MOSI PD3
#define MOSI_DDR DDRD
#define MOSI_PORT PORTD
#define MOSI_HIGH MOSI_PORT |= (1 << MOSI)
#define MOSI_LOW MOSI_PORT &= ~(1 << MOSI)
#define MISO PC0
#define MISO_DDR DDRC
#define MISO_PORT PORTC
#define MISO_PIN PINC
#define MISO_IS_HIGH (MISO_PIN & (1 << MISO))
#define SPI_PINS_INIT SCK_DDR |= (1 << SCK); \
MOSI_DDR |= (1 << MOSI); \
MISO_DDR &= ~(1 << MISO)
#define MSBit 0x80
#define LSBit 0x01
void SPI_init();
uint8_t SPI_byte(uint8_t _byte);
#endif /* SPI_H_ */
#include "SPI.h"
void SPI_init()
{
SPI_PINS_INIT;
}
uint8_t SPI_byte(uint8_t _byte)
{
for(uint8_t bitNum = 8; bitNum; bitNum--)
{
if (_byte & MSBit)
MOSI_HIGH;
else
MOSI_LOW;
_byte <<= 1;
SCK_HIGH;
if(MISO_IS_HIGH)
_byte |= LSBit;
SCK_LOW;
}
return _byte;
}
SSD1283A обеспечивает инициализацию дисплея, очистку экрана и прорисовку пикселя с заданными координатами и цветом.
#ifndef SSD1283A_H_
#define SSD1283A_H_
#include "SPI.h"
#define SSD1283A_DC PD4
#define SSD1283A_DC_DDR DDRD
#define SSD1283A_DC_PORT PORTD
#define SSD1283A_DC_COMMAND SSD1283A_DC_PORT &= ~(1 << SSD1283A_DC)
#define SSD1283A_DC_DATA SSD1283A_DC_PORT |= (1 << SSD1283A_DC)
#define SSD1283A_CS PD5
#define SSD1283A_CS_DDR DDRD
#define SSD1283A_CS_PORT PORTD
#define SSD1283A_CS_HIGH SSD1283A_CS_PORT |= (1 << SSD1283A_CS)
#define SSD1283A_CS_LOW SSD1283A_CS_PORT &= ~(1 << SSD1283A_CS)
#define SSD1283A_RESET PD6
#define SSD1283A_RESET_DDR DDRD
#define SSD1283A_RESET_PORT PORTD
#define SSD1283A_RESET_HIGH SSD1283A_RESET_PORT |= (1 << SSD1283A_RESET)
#define SSD1283A_RESET_LOW SSD1283A_RESET_PORT &= ~(1 << SSD1283A_RESET)
#define SSD1283_PINS_INIT SSD1283A_DC_DDR |= 1 << SSD1283A_DC; \
SSD1283A_CS_DDR |= 1 << SSD1283A_CS; \
SSD1283A_RESET_DDR |= 1 << SSD1283A_RESET; \
SSD1283A_CS_HIGH; SSD1283A_RESET_HIGH
#define SSD1283A_OSCILLATION_START 0x00
#define SSD1283A_OSCILLATION_START_VALUE 1 << SSD1283A_OSCEN
#define SSD1283A_OSCEN 0
#define SSD1283A_DRIVER_OUTPUT_CONTROL 0x01
#define SSD1283A_DRIVER_OUTPUT_CONTROL_VALUE (1 << SSD1283A_REV) | (1 << SSD1283A_MUX_7) | (1 << SSD1283A_MUX_1) | (1 << SSD1283A_MUX_0)
#define SSD1283A_MUX_0 0
#define SSD1283A_MUX_1 1
#define SSD1283A_MUX_7 7
#define SSD1283A_REV 13
#define SSD1283A_LCD_DRIVE_AC_CONTROL 0x02
#define SSD1283A_LCD_DRIVE_AC_CONTROL_VALUE (1 << SSD1283A_BC) | (1 << SSD1283A_EOR)
#define SSD1283A_EOR 8
#define SSD1283A_BC 9
#define SSD1283A_ENTRY_MODE 0x03
#define SSD1283A_ENTRY_MODE_VALUE (1 << SSD1283A_DFM1) | (1 << SSD1283A_DFM0) | (1 << SSD1283A_OEDEF) | (1 << SSD1283A_ID1) | (1 << SSD1283A_ID0)
#define SSD1283A_ID0 4
#define SSD1283A_ID1 5
#define SSD1283A_OEDEF 11
#define SSD1283A_DFM0 13
#define SSD1283A_DFM1 14
#define SSD1283A_DISPLAY_CONTROL 0x07
#define SSD1283A_DISPLAY_CONTROL_VALUE (1 << SSD1283A_GON) | (1 << SSD1283A_DTE) | (1 << SSD1283A_D1) | (1 << SSD1283A_D0)
#define SSD1283A_D0 0
#define SSD1283A_D1 1
#define SSD1283A_DTE 4
#define SSD1283A_GON 5
#define SSD1283A_FRAME_CYCLE_CONTROL 0x0B
#define SSD1283A_FRAME_CYCLE_CONTROL_VALUE (1 << SSD1283A_NO0) | (1 << SSD1283A_SDT0) | (1 << SSD1283A_EQ1) | (1 << SSD1283A_RTN3) | (1 << SSD1283A_RTN2)
#define SSD1283A_RTN2 2
#define SSD1283A_RTN3 3
#define SSD1283A_EQ1 11
#define SSD1283A_SDT0 12
#define SSD1283A_NO0 14
#define SSD1283A_POWER_CONTROL1 0x10
#define SSD1283A_POWER_CONTROL1_VALUE (1 << SSD1283A_DCY1) | (1 << SSD1283A_BTH2) | (1 << SSD1283A_BTH1) | (1 << SSD1283A_BTH0) | (1 << 8) | (1 << 7) | (1 << 6) | (1 << SSD1283A_AP2) | (1 << SSD1283A_AP1) | (1 << SSD1283A_AP0)
#define SSD1283A_AP0 1
#define SSD1283A_AP1 2
#define SSD1283A_AP2 3
#define SSD1283A_BTH0 9
#define SSD1283A_BTH1 10
#define SSD1283A_BTH2 11
#define SSD1283A_DCY1 13
#define SSD1283A_POWER_CONTROL2 0x11
#define SSD1283A_POWER_CONTROL2_VALUE (1 << SSD1283A_PU0) | (1 << 2)
#define SSD1283A_PU0 3
#define SSD1283A_POWER_CONTROL3 0x12
#define SSD1283A_POWER_CONTROL3_VALUE (1 << SSD1283A_VRH3) | (1 << SSD1283A_VRH0)
#define SSD1283A_VRH0 0
#define SSD1283A_VRH3 3
#define SSD1283A_POWER_CONTROL4 0x13
#define SSD1283A_POWER_CONTROL4_VALUE (1 << SSD1283A_VCOMG) | (1 << SSD1283A_VDV4) | (1 << SSD1283A_VDV0)
#define SSD1283A_VDV0 8
#define SSD1283A_VDV4 12
#define SSD1283A_VCOMG 13
#define SSD1283A_OSCILLATOR_FREQUENCY 0x2C
#define SSD1283A_OSCILLATOR_FREQUENCY_VALUE (1 << SSD1283A_OSCR3)
#define SSD1283A_OSCR3 15
#define BLACK 0x0000
#define BLUE 0x001F
#define RED 0xF800
#define GREEN 0x07E0
#define CYAN 0x07FF
#define MAGENTA 0xF81F
#define YELLOW 0xFFE0
#define WHITE 0xFFFF
#define BACKGROUND_COLOR BLACK
#define SSD1283A_WIDTH 128
#define SSD1283A_HIGHT 160
#define PIXEL_NUMBER 130
#define SSD1283A_DELAY(value) _delay_ms(value)
void SSD1283A_command(uint8_t command);
void SSD1283A_data(uint8_t data);
void SSD1283A_data_16(uint16_t data);
void SSD1283A_write_reg(uint16_t command, uint16_t data);
void SSD1283A_init();
void SSD1283A_set_rotation(uint8_t rotation);
void SSD1283A_set_window_address(int16_t x1, int16_t y1, int16_t x2, int16_t y2);
void SSD1283A_clear_display(int16_t color);
void drawPixel(uint16_t x, uint16_t y, uint16_t color);
extern uint8_t rotation;
#endif /* SSD1283A_H_ */
#include "SSD1283A.h"
uint8_t rotation;
void SSD1283A_command(uint8_t command)
{
SSD1283A_DC_COMMAND;
SSD1283A_CS_LOW;
SPI_byte(command);
SSD1283A_CS_HIGH;
}
void SSD1283A_data(uint8_t data)
{
SSD1283A_DC_DATA;
SSD1283A_CS_LOW;
SPI_byte(data);
SSD1283A_CS_HIGH;
}
void SSD1283A_data_16(uint16_t data)
{
SSD1283A_DC_DATA;
SSD1283A_CS_LOW;
SPI_byte(data >> 8);
SPI_byte(data);
SSD1283A_CS_HIGH;
}
void SSD1283A_write_reg(uint16_t command, uint16_t data)
{
SSD1283A_DC_COMMAND;
SSD1283A_CS_LOW;
SPI_byte(command);
SSD1283A_DC_DATA;
SPI_byte(data >> 8);
SPI_byte(data);
SSD1283A_CS_HIGH;
}
void SSD1283A_init()
{
SPI_init();
SSD1283_PINS_INIT;
SSD1283A_RESET_LOW;
SSD1283A_DELAY(10);
SSD1283A_RESET_HIGH;
SSD1283A_DELAY(120);
SSD1283A_write_reg(SSD1283A_OSCILLATION_START, SSD1283A_OSCILLATION_START_VALUE);
SSD1283A_write_reg(SSD1283A_DRIVER_OUTPUT_CONTROL, SSD1283A_DRIVER_OUTPUT_CONTROL_VALUE);
SSD1283A_write_reg(SSD1283A_LCD_DRIVE_AC_CONTROL, SSD1283A_LCD_DRIVE_AC_CONTROL_VALUE);
SSD1283A_write_reg(SSD1283A_ENTRY_MODE, SSD1283A_ENTRY_MODE_VALUE);
SSD1283A_write_reg(SSD1283A_DISPLAY_CONTROL, SSD1283A_DISPLAY_CONTROL_VALUE);
SSD1283A_write_reg(SSD1283A_FRAME_CYCLE_CONTROL, SSD1283A_FRAME_CYCLE_CONTROL_VALUE);
SSD1283A_write_reg(SSD1283A_POWER_CONTROL1, SSD1283A_POWER_CONTROL1_VALUE);
SSD1283A_write_reg(SSD1283A_POWER_CONTROL2, SSD1283A_POWER_CONTROL2_VALUE);
SSD1283A_write_reg(SSD1283A_POWER_CONTROL3, SSD1283A_POWER_CONTROL3_VALUE);
SSD1283A_write_reg(SSD1283A_POWER_CONTROL4, SSD1283A_POWER_CONTROL4_VALUE);
SSD1283A_write_reg(SSD1283A_OSCILLATOR_FREQUENCY, SSD1283A_OSCILLATOR_FREQUENCY_VALUE);
SSD1283A_set_rotation(0);
SSD1283A_clear_display(BACKGROUND_COLOR);
}
void SSD1283A_set_rotation(uint8_t _rotation)
{
rotation = _rotation & 0x03;
switch(rotation)
{
case 0:
SSD1283A_write_reg(0x01, 0x2183);
SSD1283A_write_reg(0x03, 0x6830);
break;
case 1:
SSD1283A_write_reg(0x01, 0x2283);
SSD1283A_write_reg(0x03, 0x6808);
break;
case 2:
SSD1283A_write_reg(0x01, 0x2183);
SSD1283A_write_reg(0x03, 0x6800);
break;
case 3:
SSD1283A_write_reg(0x01, 0x2283);
SSD1283A_write_reg(0x03, 0x6838);
break;
}
SSD1283A_set_window_address(0, 0, PIXEL_NUMBER - 1, PIXEL_NUMBER - 1);
}
void SSD1283A_set_window_address(int16_t x1, int16_t y1, int16_t x2, int16_t y2)
{
switch(rotation)
{
case 0:
SSD1283A_command(0x44); SSD1283A_data(x2 + 2); SSD1283A_data(x1 + 2);
SSD1283A_command(0x45); SSD1283A_data(y2 + 2); SSD1283A_data(y1 + 2);
SSD1283A_command(0x21); SSD1283A_data(y1 + 2); SSD1283A_data(x1 + 2);
break;
case 1:
SSD1283A_command(0x44); SSD1283A_data(PIXEL_NUMBER - y1 + 1); SSD1283A_data(PIXEL_NUMBER - y2 + 1);
SSD1283A_command(0x45); SSD1283A_data(PIXEL_NUMBER - x1 - 1); SSD1283A_data(PIXEL_NUMBER - x2 - 1);
SSD1283A_command(0x21); SSD1283A_data(PIXEL_NUMBER - x1 - 1); SSD1283A_data(PIXEL_NUMBER - y1 + 1);
break;
case 2:
SSD1283A_command(0x44); SSD1283A_data(PIXEL_NUMBER - x1 + 1); SSD1283A_data(PIXEL_NUMBER - x2 + 1);
SSD1283A_command(0x45); SSD1283A_data(PIXEL_NUMBER - y1 + 1); SSD1283A_data(PIXEL_NUMBER - y2 + 1);
SSD1283A_command(0x21); SSD1283A_data(PIXEL_NUMBER - y1 + 1); SSD1283A_data(PIXEL_NUMBER - x1 + 1);
break;
case 3:
SSD1283A_command(0x44); SSD1283A_data(y2 + 2); SSD1283A_data(y1 + 2);
SSD1283A_command(0x45); SSD1283A_data(x2); SSD1283A_data(x1);
SSD1283A_command(0x21); SSD1283A_data(x1); SSD1283A_data(y1 + 2);
break;
}
}
void SSD1283A_clear_display(int16_t color)
{
SSD1283A_set_window_address(0, 0, PIXEL_NUMBER - 1, PIXEL_NUMBER - 1);
SSD1283A_command(0x22);
for(unsigned int coordinateX = 0; coordinateX < PIXEL_NUMBER; coordinateX++)
for(unsigned int coordinateY = 0; coordinateY < PIXEL_NUMBER; coordinateY++)
SSD1283A_data_16(color);
}
void drawPixel(uint16_t x, uint16_t y, uint16_t color)
{
SSD1283A_set_window_address(x, y, x, y);
SSD1283A_command(0x22);
SSD1283A_data_16(color);
}
GFX предназначена для вывода на экран горизонтальных и вертикальных линий, рамок, символьных и строковых переменных.
#ifndef GFX_H_
#define GFX_H_
#include "images.h"
#define CHAR_CODE_SHIFT 0x20
#define MSBit 0x80
void setColor(uint16_t color);
void setCursor(uint16_t x, uint16_t y);
void drawVerticalLine(uint16_t height);
void drawHorizontalLine(uint16_t length);
void drawRectangle(uint16_t width, uint16_t height);
void fillRectangle(uint16_t width, uint16_t height);
void drawByte(uint8_t data);
void setFont(font_t* fontPtr);
void printChar(const char data);
void printString(char* _string);
extern void drawPixel(uint16_t x, uint16_t y, uint16_t color);
extern uint16_t currentX, currentY, currentColor;
#endif /* GFX_H_ */
#include "GFX.h"
uint16_t currentX, currentY, currentColor;
void setColor(uint16_t color)
{
currentColor = color;
}
void setCursor(uint16_t x, uint16_t y)
{
currentX = x;
currentY = y;
}
void drawVerticalLine(uint16_t height)
{
while(height--)
{
drawPixel(currentX, currentY, currentColor);
currentY++;
}
}
void drawHorizontalLine(uint16_t length)
{
while(length--)
{
drawPixel(currentX, currentY, currentColor);
currentX++;
}
}
void drawRectangle(uint16_t width, uint16_t height)
{
uint16_t startX = currentX, startY = currentY;
drawVerticalLine(height);
currentY--;
drawHorizontalLine(width);
currentX = startX; currentY = startY;
drawHorizontalLine(width);
currentX--;
drawVerticalLine(height);
}
void fillRectangle(uint16_t width, uint16_t height)
{
uint16_t startX = currentX;
for(uint16_t coordinateY = 0; coordinateY < height; coordinateY++)
{
drawHorizontalLine(width);
currentY++;
currentX = startX;
}
}
void drawByte(uint8_t data)
{
for(int bitNum = 0; bitNum < 8; bitNum++)
{
if(data & MSBit)
drawPixel(currentX, currentY, currentColor);
data <<= 1;
currentY++;
}
}
void setFont(font_t* fontPtr)
{
currentFont = fontPtr;
}
void printChar(const char data)
{
uint16_t startY = currentY;
uint16_t startX = currentX;
uint8_t currentByteNum = 0;
uint8_t charNum = data - CHAR_CODE_SHIFT;
uint8_t height = pgm_read_byte(currentFont->height);
uint8_t width = pgm_read_byte(currentFont->width + charNum);
uint8_t strips_per_height = height / 8;
if(strips_per_height * 8 != height)
strips_per_height++;
for(uint8_t stripNum = 0; stripNum < strips_per_height; stripNum++)
{
for(uint8_t byteNum = 0; byteNum < width; byteNum++)
{
drawByte(pgm_read_byte(pgm_read_word(currentFont->array + charNum) + currentByteNum));
currentByteNum++;
currentX++;
currentY = startY;
}
startY += 8;
currentY = startY;
currentX = startX;
}
}
void printString(char* _string)
{
uint16_t stringStartY = currentY;
while (*_string != '\0')
{
uint8_t charNum = *_string - CHAR_CODE_SHIFT;
uint16_t width = pgm_read_byte(currentFont->width + charNum);
printChar(*_string);
_string++;
currentY = stringStartY;
currentX += width;
}
}
images хранит шрифт Arial 14, сгенерированный программой lcd-image-converter.
#ifndef IMAGES_H_
#define IMAGES_H_
#include <stdint.h>
#include <avr/pgmspace.h>
#include <stddef.h>
typedef struct
{
const char* const* array;
const char* width;
const char* height;
} font_t;
extern font_t* currentFont;
/* FONTS */
/* arial_14 */
extern font_t arial_14;
#endif /* IMAGES_H_ */
#include "images.h"
/* FONTS */
font_t* currentFont = NULL;
/* arial_14 */
const char arial_14_0x20[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,};
const char arial_14_0x21[] PROGMEM = {0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0xe8, 0x00, 0x00,};
const char arial_14_0x22[] PROGMEM = {0x00, 0x1e, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,};
const char arial_14_0x23[] PROGMEM = {0x02, 0x02, 0x07, 0x1a, 0x02, 0x07, 0x1a, 0x02, 0x40, 0x78, 0xc0, 0x40, 0x78, 0xc0, 0x40, 0x40,};
const char arial_14_0x24[] PROGMEM = {0x06, 0x09, 0x11, 0x3f, 0x10, 0x10, 0x08, 0x00, 0x10, 0x08, 0x08, 0xfc, 0x88, 0x90, 0x60, 0x00,};
const char arial_14_0x25[] PROGMEM = {0x00, 0x0e, 0x11, 0x11, 0x0e, 0x00, 0x03, 0x0c, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x30, 0xc0, 0x00, 0x70, 0x88, 0x88, 0x70, 0x00,};
const char arial_14_0x26[] PROGMEM = {0x00, 0x00, 0x0e, 0x11, 0x11, 0x12, 0x0c, 0x00, 0x00, 0x00, 0x70, 0x88, 0x08, 0x88, 0x48, 0x30, 0x50, 0x08,};
const char arial_14_0x27[] PROGMEM = {0x00, 0x1e, 0x00, 0x00, 0x00, 0x00,};
const char arial_14_0x28[] PROGMEM = {0x00, 0x03, 0x0c, 0x10, 0x00, 0x00, 0xf8, 0x06, 0x01, 0x00,};
const char arial_14_0x29[] PROGMEM = {0x00, 0x10, 0x0c, 0x03, 0x00, 0x00, 0x01, 0x06, 0xf8, 0x00,};
const char arial_14_0x2a[] PROGMEM = {0x08, 0x0a, 0x1c, 0x0a, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,};
const char arial_14_0x2b[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0xf0, 0x80, 0x80, 0x80,};
const char arial_14_0x2c[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00,};
const char arial_14_0x2d[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x40, 0x40, 0x00,};
const char arial_14_0x2e[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00,};
const char arial_14_0x2f[] PROGMEM = {0x00, 0x00, 0x07, 0x18, 0x18, 0xe0, 0x00, 0x00,};
const char arial_14_0x30[] PROGMEM = {0x00, 0x0f, 0x10, 0x10, 0x10, 0x10, 0x0f, 0x00, 0x00, 0xf0, 0x08, 0x08, 0x08, 0x08, 0xf0, 0x00,};
const char arial_14_0x31[] PROGMEM = {0x00, 0x00, 0x04, 0x08, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00,};
const char arial_14_0x32[] PROGMEM = {0x00, 0x0c, 0x10, 0x10, 0x10, 0x10, 0x0f, 0x00, 0x00, 0x08, 0x18, 0x28, 0x48, 0x88, 0x08, 0x00,};
const char arial_14_0x33[] PROGMEM = {0x00, 0x0c, 0x10, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, 0x30, 0x08, 0x08, 0x08, 0x08, 0xf0, 0x00,};
const char arial_14_0x34[] PROGMEM = {0x00, 0x00, 0x03, 0x04, 0x08, 0x1f, 0x00, 0x00, 0x60, 0xa0, 0x20, 0x20, 0x20, 0xf8, 0x20, 0x00,};
const char arial_14_0x35[] PROGMEM = {0x00, 0x07, 0x1a, 0x12, 0x12, 0x12, 0x11, 0x00, 0x00, 0x30, 0x08, 0x08, 0x08, 0x18, 0xe0, 0x00,};
const char arial_14_0x36[] PROGMEM = {0x00, 0x07, 0x08, 0x11, 0x11, 0x11, 0x08, 0x00, 0x00, 0xf0, 0x88, 0x08, 0x08, 0x08, 0xf0, 0x00,};
const char arial_14_0x37[] PROGMEM = {0x00, 0x10, 0x10, 0x10, 0x11, 0x16, 0x18, 0x00, 0x00, 0x00, 0x00, 0x38, 0xc0, 0x00, 0x00, 0x00,};
const char arial_14_0x38[] PROGMEM = {0x00, 0x0e, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, 0xf0, 0x08, 0x08, 0x08, 0x08, 0xf0, 0x00,};
const char arial_14_0x39[] PROGMEM = {0x00, 0x0f, 0x10, 0x10, 0x10, 0x11, 0x0f, 0x00, 0x00, 0x10, 0x88, 0x88, 0x88, 0x10, 0xe0, 0x00,};
const char arial_14_0x3a[] PROGMEM = {0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00,};
const char arial_14_0x3b[] PROGMEM = {0x00, 0x04, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00,};
const char arial_14_0x3c[] PROGMEM = {0x00, 0x00, 0x01, 0x01, 0x02, 0x02, 0x04, 0x00, 0x00, 0x80, 0x40, 0x40, 0x20, 0x20, 0x10, 0x00,};
const char arial_14_0x3d[] PROGMEM = {0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x00,};
const char arial_14_0x3e[] PROGMEM = {0x00, 0x04, 0x02, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x10, 0x20, 0x20, 0x40, 0x40, 0x80, 0x00,};
const char arial_14_0x3f[] PROGMEM = {0x00, 0x0c, 0x10, 0x10, 0x10, 0x11, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x68, 0x80, 0x00, 0x00, 0x00,};
const char arial_14_0x40[] PROGMEM = {0x01, 0x06, 0x08, 0x08, 0x11, 0x12, 0x12, 0x12, 0x11, 0x13, 0x08, 0x04, 0x03, 0x00, 0xf0, 0x0c, 0x02, 0xf2, 0x09, 0x09, 0x09, 0x11, 0x79, 0x89, 0x0a, 0x32, 0xc4, 0x00,};
const char arial_14_0x41[] PROGMEM = {0x00, 0x00, 0x01, 0x0e, 0x10, 0x0e, 0x01, 0x00, 0x00, 0x18, 0x60, 0xc0, 0x40, 0x40, 0x40, 0xc0, 0x60, 0x18,};
const char arial_14_0x42[] PROGMEM = {0x00, 0x1f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, 0xf8, 0x08, 0x08, 0x08, 0x08, 0x08, 0xf0, 0x00,};
const char arial_14_0x43[] PROGMEM = {0x00, 0x07, 0x08, 0x10, 0x10, 0x10, 0x10, 0x08, 0x04, 0x00, 0x00, 0xe0, 0x10, 0x08, 0x08, 0x08, 0x08, 0x10, 0x20, 0x00,};
const char arial_14_0x44[] PROGMEM = {0x00, 0x1f, 0x10, 0x10, 0x10, 0x10, 0x10, 0x08, 0x07, 0x00, 0x00, 0xf8, 0x08, 0x08, 0x08, 0x08, 0x08, 0x10, 0xe0, 0x00,};
const char arial_14_0x45[] PROGMEM = {0x00, 0x1f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, 0xf8, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00,};
const char arial_14_0x46[] PROGMEM = {0x00, 0x1f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x10, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,};
const char arial_14_0x47[] PROGMEM = {0x00, 0x07, 0x08, 0x10, 0x10, 0x10, 0x10, 0x10, 0x08, 0x04, 0x00, 0x00, 0xe0, 0x10, 0x08, 0x08, 0x08, 0x88, 0x88, 0x90, 0xe0, 0x00,};
const char arial_14_0x48[] PROGMEM = {0x00, 0x1f, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1f, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00,};
const char arial_14_0x49[] PROGMEM = {0x00, 0x1f, 0x00, 0x00, 0xf8, 0x00,};
const char arial_14_0x4a[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x30, 0x08, 0x08, 0x08, 0xf0, 0x00,};
const char arial_14_0x4b[] PROGMEM = {0x00, 0x1f, 0x00, 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x00, 0xf8, 0x40, 0x80, 0x00, 0x80, 0x60, 0x10, 0x08,};
const char arial_14_0x4c[] PROGMEM = {0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,};
const char arial_14_0x4d[] PROGMEM = {0x00, 0x1f, 0x0c, 0x03, 0x00, 0x00, 0x00, 0x03, 0x0c, 0x1f, 0x00, 0x00, 0xf8, 0x00, 0x00, 0xe0, 0x18, 0xe0, 0x00, 0x00, 0xf8, 0x00,};
const char arial_14_0x4e[] PROGMEM = {0x00, 0x1f, 0x08, 0x06, 0x01, 0x00, 0x00, 0x1f, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x80, 0x60, 0x10, 0xf8, 0x00,};
const char arial_14_0x4f[] PROGMEM = {0x00, 0x07, 0x08, 0x10, 0x10, 0x10, 0x10, 0x10, 0x08, 0x07, 0x00, 0x00, 0xe0, 0x10, 0x08, 0x08, 0x08, 0x08, 0x08, 0x10, 0xe0, 0x00,};
const char arial_14_0x50[] PROGMEM = {0x00, 0x1f, 0x10, 0x10, 0x10, 0x10, 0x10, 0x0f, 0x00, 0x00, 0xf8, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00,};
const char arial_14_0x51[] PROGMEM = {0x00, 0x07, 0x08, 0x10, 0x10, 0x10, 0x10, 0x10, 0x08, 0x07, 0x00, 0x00, 0xe0, 0x10, 0x08, 0x08, 0x08, 0x28, 0x10, 0x38, 0xc8, 0x00,};
const char arial_14_0x52[] PROGMEM = {0x00, 0x1f, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x0f, 0x00, 0x00, 0xf8, 0x80, 0x80, 0x80, 0xc0, 0xa0, 0x90, 0x08, 0x00,};
const char arial_14_0x53[] PROGMEM = {0x00, 0x0e, 0x11, 0x11, 0x11, 0x10, 0x10, 0x0c, 0x00, 0x00, 0x30, 0x08, 0x08, 0x08, 0x88, 0x88, 0xf0, 0x00,};
const char arial_14_0x54[] PROGMEM = {0x00, 0x10, 0x10, 0x10, 0x1f, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x00,};
const char arial_14_0x55[] PROGMEM = {0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0xe0, 0x10, 0x08, 0x08, 0x08, 0x10, 0xe0, 0x00,};
const char arial_14_0x56[] PROGMEM = {0x18, 0x06, 0x01, 0x00, 0x00, 0x00, 0x01, 0x06, 0x18, 0x00, 0x00, 0x80, 0x60, 0x18, 0x60, 0x80, 0x00, 0x00,};
const char arial_14_0x57[] PROGMEM = {0x18, 0x07, 0x00, 0x00, 0x00, 0x0f, 0x10, 0x0f, 0x00, 0x00, 0x00, 0x07, 0x18, 0x00, 0x00, 0xe0, 0x18, 0xe0, 0x00, 0x00, 0x00, 0xe0, 0x18, 0xe0, 0x00, 0x00,};
const char arial_14_0x58[] PROGMEM = {0x10, 0x0c, 0x02, 0x01, 0x01, 0x02, 0x0c, 0x10, 0x08, 0x30, 0x40, 0x80, 0x80, 0x40, 0x30, 0x08,};
const char arial_14_0x59[] PROGMEM = {0x10, 0x08, 0x06, 0x01, 0x00, 0x01, 0x06, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x00,};
const char arial_14_0x5a[] PROGMEM = {0x00, 0x10, 0x10, 0x10, 0x11, 0x16, 0x18, 0x10, 0x08, 0x18, 0x68, 0x88, 0x08, 0x08, 0x08, 0x08,};
const char arial_14_0x5b[] PROGMEM = {0x00, 0x1f, 0x10, 0x00, 0x00, 0xff, 0x01, 0x00,};
const char arial_14_0x5c[] PROGMEM = {0x18, 0x07, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x18,};
const char arial_14_0x5d[] PROGMEM = {0x00, 0x10, 0x1f, 0x00, 0x00, 0x01, 0xff, 0x00,};
const char arial_14_0x5e[] PROGMEM = {0x01, 0x0e, 0x10, 0x0e, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,};
const char arial_14_0x5f[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,};
const char arial_14_0x60[] PROGMEM = {0x00, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,};
const char arial_14_0x61[] PROGMEM = {0x00, 0x02, 0x04, 0x04, 0x04, 0x04, 0x03, 0x00, 0x00, 0x30, 0x48, 0x88, 0x88, 0x90, 0xf8, 0x00,};
const char arial_14_0x62[] PROGMEM = {0x00, 0x1f, 0x02, 0x04, 0x04, 0x04, 0x03, 0x00, 0x00, 0xf8, 0x10, 0x08, 0x08, 0x08, 0xf0, 0x00,};
const char arial_14_0x63[] PROGMEM = {0x00, 0x03, 0x04, 0x04, 0x04, 0x02, 0x00, 0x00, 0xf0, 0x08, 0x08, 0x08, 0x10, 0x00,};
const char arial_14_0x64[] PROGMEM = {0x00, 0x03, 0x04, 0x04, 0x04, 0x02, 0x1f, 0x00, 0x00, 0xf0, 0x08, 0x08, 0x08, 0x10, 0xf8, 0x00,};
const char arial_14_0x65[] PROGMEM = {0x00, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x00, 0x00, 0xf0, 0x88, 0x88, 0x88, 0x88, 0x90, 0x00,};
const char arial_14_0x66[] PROGMEM = {0x04, 0x0f, 0x14, 0x14, 0x00, 0xf8, 0x00, 0x00,};
const char arial_14_0x67[] PROGMEM = {0x00, 0x03, 0x04, 0x04, 0x04, 0x02, 0x07, 0x00, 0x00, 0xf2, 0x09, 0x09, 0x09, 0x11, 0xfe, 0x00,};
const char arial_14_0x68[] PROGMEM = {0x00, 0x1f, 0x02, 0x04, 0x04, 0x04, 0x03, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00,};
const char arial_14_0x69[] PROGMEM = {0x00, 0x17, 0x00, 0x00, 0xf8, 0x00,};
const char arial_14_0x6a[] PROGMEM = {0x00, 0x17, 0x00, 0x01, 0xfe, 0x00,};
const char arial_14_0x6b[] PROGMEM = {0x00, 0x1f, 0x00, 0x00, 0x01, 0x02, 0x04, 0x00, 0xf8, 0x40, 0x80, 0x40, 0x30, 0x08,};
const char arial_14_0x6c[] PROGMEM = {0x00, 0x1f, 0x00, 0x00, 0xf8, 0x00,};
const char arial_14_0x6d[] PROGMEM = {0x00, 0x07, 0x02, 0x04, 0x04, 0x03, 0x06, 0x04, 0x04, 0x03, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0xf8, 0x00,};
const char arial_14_0x6e[] PROGMEM = {0x00, 0x07, 0x02, 0x04, 0x04, 0x04, 0x03, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00,};
const char arial_14_0x6f[] PROGMEM = {0x00, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x00, 0x00, 0xf0, 0x08, 0x08, 0x08, 0x08, 0xf0, 0x00,};
const char arial_14_0x70[] PROGMEM = {0x00, 0x07, 0x02, 0x04, 0x04, 0x04, 0x03, 0x00, 0x00, 0xff, 0x10, 0x08, 0x08, 0x08, 0xf0, 0x00,};
const char arial_14_0x71[] PROGMEM = {0x00, 0x03, 0x04, 0x04, 0x04, 0x02, 0x07, 0x00, 0x00, 0xf0, 0x08, 0x08, 0x08, 0x10, 0xff, 0x00,};
const char arial_14_0x72[] PROGMEM = {0x00, 0x07, 0x02, 0x04, 0x04, 0x00, 0xf8, 0x00, 0x00, 0x00,};
const char arial_14_0x73[] PROGMEM = {0x00, 0x03, 0x04, 0x04, 0x04, 0x02, 0x00, 0x00, 0x10, 0x88, 0x88, 0x88, 0x70, 0x00,};
const char arial_14_0x74[] PROGMEM = {0x04, 0x1f, 0x04, 0x04, 0x00, 0xf8, 0x08, 0x08,};
const char arial_14_0x75[] PROGMEM = {0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0xf0, 0x08, 0x08, 0x08, 0x10, 0xf8, 0x00,};
const char arial_14_0x76[] PROGMEM = {0x06, 0x01, 0x00, 0x00, 0x00, 0x01, 0x06, 0x00, 0x80, 0x60, 0x18, 0x60, 0x80, 0x00,};
const char arial_14_0x77[] PROGMEM = {0x06, 0x01, 0x00, 0x01, 0x06, 0x01, 0x00, 0x01, 0x06, 0x00, 0xe0, 0x18, 0xe0, 0x00, 0xe0, 0x18, 0xe0, 0x00,};
const char arial_14_0x78[] PROGMEM = {0x04, 0x03, 0x00, 0x00, 0x03, 0x04, 0x08, 0x30, 0xc0, 0xc0, 0x30, 0x08,};
const char arial_14_0x79[] PROGMEM = {0x06, 0x01, 0x00, 0x00, 0x00, 0x01, 0x06, 0x00, 0x81, 0x71, 0x0e, 0x70, 0x80, 0x00,};
const char arial_14_0x7a[] PROGMEM = {0x04, 0x04, 0x04, 0x05, 0x06, 0x04, 0x08, 0x18, 0x68, 0x88, 0x08, 0x08,};
const char arial_14_0x7b[] PROGMEM = {0x00, 0x00, 0x0f, 0x10, 0x00, 0x00, 0x40, 0xbe, 0x01, 0x00,};
const char arial_14_0x7c[] PROGMEM = {0x00, 0x1f, 0x00, 0x00, 0xff, 0x00,};
const char arial_14_0x7d[] PROGMEM = {0x00, 0x10, 0x0f, 0x00, 0x00, 0x00, 0x01, 0xbe, 0x40, 0x00,};
const char arial_14_0x7e[] PROGMEM = {0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x80, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00,};
const char arial_14_0x7f[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
const char* const arial_14_array[] PROGMEM = {arial_14_0x20,arial_14_0x21,arial_14_0x22,arial_14_0x23,arial_14_0x24,arial_14_0x25,arial_14_0x26,arial_14_0x27,arial_14_0x28,arial_14_0x29,arial_14_0x2a,arial_14_0x2b,arial_14_0x2c,arial_14_0x2d,arial_14_0x2e,arial_14_0x2f,arial_14_0x30,arial_14_0x31,arial_14_0x32,arial_14_0x33,arial_14_0x34,arial_14_0x35,arial_14_0x36,arial_14_0x37,arial_14_0x38,arial_14_0x39,arial_14_0x3a,arial_14_0x3b,arial_14_0x3c,arial_14_0x3d,arial_14_0x3e,arial_14_0x3f,arial_14_0x40,arial_14_0x41,arial_14_0x42,arial_14_0x43,arial_14_0x44,arial_14_0x45,arial_14_0x46,arial_14_0x47,arial_14_0x48,arial_14_0x49,arial_14_0x4a,arial_14_0x4b,arial_14_0x4c,arial_14_0x4d,arial_14_0x4e,arial_14_0x4f,arial_14_0x50,arial_14_0x51,arial_14_0x52,arial_14_0x53,arial_14_0x54,arial_14_0x55,arial_14_0x56,arial_14_0x57,arial_14_0x58,arial_14_0x59,arial_14_0x5a,arial_14_0x5b,arial_14_0x5c,arial_14_0x5d,arial_14_0x5e,arial_14_0x5f,arial_14_0x60,arial_14_0x61,arial_14_0x62,arial_14_0x63,arial_14_0x64,arial_14_0x65,arial_14_0x66,arial_14_0x67,arial_14_0x68,arial_14_0x69,arial_14_0x6a,arial_14_0x6b,arial_14_0x6c,arial_14_0x6d,arial_14_0x6e,arial_14_0x6f,arial_14_0x70,arial_14_0x71,arial_14_0x72,arial_14_0x73,arial_14_0x74,arial_14_0x75,arial_14_0x76,arial_14_0x77,arial_14_0x78,arial_14_0x79,arial_14_0x7a,arial_14_0x7b,arial_14_0x7c,arial_14_0x7d,arial_14_0x7e,arial_14_0x7f};
const char arial_14_width[] PROGMEM = {4,5,5,8,8,12,9,3,5,5,5,8,4,5,4,4,8,8,8,8,8,8,8,8,8,8,4,4,8,8,8,8,14,9,9,10,10,9,9,11,9,3,6,9,8,11,9,11,9,11,10,9,9,9,9,13,8,9,8,4,4,4,5,8,5,8,8,7,8,8,4,8,8,3,3,7,3,11,8,8,8,8,5,7,4,8,7,9,6,7,6,5,3,5,8,7};
const char arial_14_height PROGMEM = 16;
font_t arial_14 = {.array = arial_14_array, .width = arial_14_width, .height = &arial_14_height};
display ответственна за прорисовку времени, даты, широты/долготы, высоты над уровнем моря и скорости.
#ifndef DISPLAY_H_
#define DISPLAY_H_
#include "GFX.h"
#include "SSD1283A.h"
#include "NEO6.h"
#define ARIAL_14_RECTANGLE_WIDHT 8
#define ARIAL_14_RECTANGLE_HEIGHT 16
/* time */
#define TIME_COLOR YELLOW
#define TIME_X 5
#define TIME_Y 5
#define HOURS_TENS_X 40
#define HOURS_UNITS_X HOURS_TENS_X + 8
#define TIME_DOT_1_X HOURS_UNITS_X + 10
#define MINUTES_TENS_X TIME_DOT_1_X + 5
#define MINUTES_UNITS_X MINUTES_TENS_X + 8
#define TIME_DOT_2_X MINUTES_UNITS_X + 10
#define SECONDS_TENS_X TIME_DOT_2_X + 5
#define SECONDS_UNITS_X SECONDS_TENS_X + 8
/* date */
#define DATE_COLOR RED
#define DATE_X 5
#define DATE_Y 25
#define DAY_TENS_X 40
#define DAY_UNITS_X DAY_TENS_X + 8
#define DATE_SLASH_1_X DAY_UNITS_X + 8
#define MONTH_TENS_X DATE_SLASH_1_X + 5
#define MONTH_UNITS_X MONTH_TENS_X + 8
#define DATE_SLASH_2_X MONTH_UNITS_X + 8
#define YEAR_TENS_X DATE_SLASH_2_X + 5
#define YEAR_UNITS_X YEAR_TENS_X + 8
/* latitude */
#define LATITUDE_COLOR WHITE
#define LATITUDE_X 5
#define LATITUDE_Y 45
#define LATITUDE_DEGREE_1_X 30
#define LATITUDE_DEGREE_2_X LATITUDE_DEGREE_1_X + 8
#define LATITUDE_DOT_X LATITUDE_DEGREE_2_X + 8
#define LATITUDE_MINUTES_1_X LATITUDE_DOT_X + 5
#define LATITUDE_MINUTES_2_X LATITUDE_MINUTES_1_X + 8
#define LATITUDE_MINUTES_3_X LATITUDE_MINUTES_2_X + 8
#define LATITUDE_MINUTES_4_X LATITUDE_MINUTES_3_X + 8
#define LATITUDE_MINUTES_5_X LATITUDE_MINUTES_4_X + 8
#define LATITUDE_MINUTES_6_X LATITUDE_MINUTES_5_X + 8
#define LATITUDE_MINUTES_7_X LATITUDE_MINUTES_6_X + 8
/* longitude */
#define LONGITUDE_COLOR WHITE
#define LONGITUDE_X 5
#define LONGITUDE_Y 65
#define LONGITUDE_DEGREE_1_X 30
#define LONGITUDE_DEGREE_2_X LONGITUDE_DEGREE_1_X + 8
#define LONGITUDE_DOT_X LONGITUDE_DEGREE_2_X + 8
#define LONGITUDE_MINUTES_1_X LONGITUDE_DOT_X + 5
#define LONGITUDE_MINUTES_2_X LONGITUDE_MINUTES_1_X + 8
#define LONGITUDE_MINUTES_3_X LONGITUDE_MINUTES_2_X + 8
#define LONGITUDE_MINUTES_4_X LONGITUDE_MINUTES_3_X + 8
#define LONGITUDE_MINUTES_5_X LONGITUDE_MINUTES_4_X + 8
#define LONGITUDE_MINUTES_6_X LONGITUDE_MINUTES_5_X + 8
#define LONGITUDE_MINUTES_7_X LONGITUDE_MINUTES_6_X + 8
/* altitude */
#define ALTITUDE_COLOR WHITE
#define ALTITUDE_X 5
#define ALTITUDE_Y 85
#define ALTITUDE_HUNDREDS_X 30
#define ALTITUDE_TENS_X ALTITUDE_HUNDREDS_X + 8
#define ALTITUDE_UNITS_X ALTITUDE_TENS_X + 8
#define ALTITUDE_DOT_X ALTITUDE_UNITS_X + 8
#define ALTITUDE_FRACTION_X ALTITUDE_DOT_X + 5
#define ALTITUDE_UNIT_X ALTITUDE_FRACTION_X + 10
/* speed */
#define SPEED_COLOR GREEN
#define SPEED_X 5
#define SPEED_Y 105
#define SPEED_INTEGER_X 50
#define SPEED_DOT_X SPEED_INTEGER_X + 8
#define SPEED_FRACTION_1_X SPEED_DOT_X + 5
#define SPEED_FRACTION_2_X SPEED_FRACTION_1_X + 8
#define SPEED_FRACTION_3_X SPEED_FRACTION_2_X + 8
#define SPEED_UNIT_X SPEED_FRACTION_3_X + 10
void display_init();
void draw_time();
void draw_date();
void draw_location();
void draw_altitude();
void draw_speed();
#endif /* DISPLAY_H_ */
#include "display.h"
#include <stdio.h>
void display_init()
{
SSD1283A_init();
setFont(&arial_14);
/* time */
setColor(TIME_COLOR);
setCursor(TIME_X, TIME_Y);
printString("time:");
setCursor(HOURS_TENS_X, TIME_Y);
printChar('0');
setCursor(HOURS_UNITS_X, TIME_Y);
printChar('0');
setCursor(TIME_DOT_1_X, TIME_Y);
printChar(':');
setCursor(MINUTES_TENS_X, TIME_Y);
printChar('0');
setCursor(MINUTES_UNITS_X, TIME_Y);
printChar('0');
setCursor(TIME_DOT_2_X, TIME_Y);
printChar(':');
setCursor(SECONDS_TENS_X, TIME_Y);
printChar('0');
setCursor(SECONDS_UNITS_X, TIME_Y);
printChar('0');
/* date */
setColor(DATE_COLOR);
setCursor(DATE_X, DATE_Y);
printString("date:");
setCursor(DAY_TENS_X, DATE_Y);
printChar('0');
setCursor(DAY_UNITS_X, DATE_Y);
printChar('0');
setCursor(DATE_SLASH_1_X, DATE_Y);
printChar('/');
setCursor(MONTH_TENS_X, DATE_Y);
printChar('0');
setCursor(MONTH_UNITS_X, DATE_Y);
printChar('0');
setCursor(DATE_SLASH_2_X, DATE_Y);
printChar('/');
setCursor(YEAR_TENS_X, DATE_Y);
printChar('0');
setCursor(YEAR_UNITS_X, DATE_Y);
printChar('0');
/* latitude */
setColor(LATITUDE_COLOR);
setCursor(LATITUDE_X, LATITUDE_Y);
printString("lat:");
setCursor(LATITUDE_DEGREE_1_X, LATITUDE_Y);
printChar('0');
setCursor(LATITUDE_DEGREE_2_X, LATITUDE_Y);
printChar('0');
setCursor(LATITUDE_DOT_X, LATITUDE_Y);
printChar('.');
setCursor(LATITUDE_MINUTES_1_X, LATITUDE_Y);
printChar('0');
setCursor(LATITUDE_MINUTES_2_X, LATITUDE_Y);
printChar('0');
setCursor(LATITUDE_MINUTES_3_X, LATITUDE_Y);
printChar('0');
setCursor(LATITUDE_MINUTES_4_X, LATITUDE_Y);
printChar('0');
setCursor(LATITUDE_MINUTES_5_X, LATITUDE_Y);
printChar('0');
setCursor(LATITUDE_MINUTES_6_X, LATITUDE_Y);
printChar('0');
setCursor(LATITUDE_MINUTES_7_X, LATITUDE_Y);
printChar('0');
/* longitude */
setColor(LONGITUDE_COLOR);
setCursor(LONGITUDE_X, LONGITUDE_Y);
printString("lng:");
setCursor(LONGITUDE_DEGREE_1_X, LONGITUDE_Y);
printChar('0');
setCursor(LONGITUDE_DEGREE_2_X, LONGITUDE_Y);
printChar('0');
setCursor(LONGITUDE_DOT_X, LONGITUDE_Y);
printChar('.');
setCursor(LONGITUDE_MINUTES_1_X, LONGITUDE_Y);
printChar('0');
setCursor(LONGITUDE_MINUTES_2_X, LONGITUDE_Y);
printChar('0');
setCursor(LONGITUDE_MINUTES_3_X, LONGITUDE_Y);
printChar('0');
setCursor(LONGITUDE_MINUTES_4_X, LONGITUDE_Y);
printChar('0');
setCursor(LONGITUDE_MINUTES_5_X, LONGITUDE_Y);
printChar('0');
setCursor(LONGITUDE_MINUTES_6_X, LONGITUDE_Y);
printChar('0');
setCursor(LONGITUDE_MINUTES_7_X, LONGITUDE_Y);
printChar('0');
/* altitude */
setColor(ALTITUDE_COLOR);
setCursor(ALTITUDE_X, ALTITUDE_Y);
printString("alt:");
setCursor(ALTITUDE_HUNDREDS_X, ALTITUDE_Y);
printChar('0');
setCursor(ALTITUDE_TENS_X, ALTITUDE_Y);
printChar('0');
setCursor(ALTITUDE_UNITS_X, ALTITUDE_Y);
printChar('0');
setCursor(ALTITUDE_DOT_X, ALTITUDE_Y);
printChar('.');
setCursor(ALTITUDE_FRACTION_X, ALTITUDE_Y);
printChar('0');
setCursor(ALTITUDE_UNIT_X, ALTITUDE_Y);
printChar('m');
/* speed */
setColor(SPEED_COLOR);
setCursor(SPEED_X, SPEED_Y);
printString("speed:");
setCursor(SPEED_INTEGER_X, SPEED_Y);
printChar('0');
setCursor(SPEED_DOT_X, SPEED_Y);
printChar('.');
setCursor(SPEED_FRACTION_1_X, SPEED_Y);
printChar('0');
setCursor(SPEED_FRACTION_2_X, SPEED_Y);
printChar('0');
setCursor(SPEED_FRACTION_3_X, SPEED_Y);
printChar('0');
setCursor(SPEED_UNIT_X, SPEED_Y);
printString("km/h");
}
void draw_time()
{
static uint8_t previous_hours_tens = '0';
static uint8_t previous_hours_units = '0';
static uint8_t previous_minutes_tens = '0';
static uint8_t previous_minutes_units = '0';
static uint8_t previous_seconds_tens = '0';
static uint8_t previous_seconds_units = '0';
if(previous_hours_tens != gps.hours_tens)
{
previous_hours_tens = gps.hours_tens;
setCursor(HOURS_TENS_X, TIME_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(HOURS_TENS_X, TIME_Y);
setColor(TIME_COLOR);
printChar(gps.hours_tens);
}
if(previous_hours_units != gps.hours_units)
{
previous_hours_units = gps.hours_units;
setCursor(HOURS_UNITS_X, TIME_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(HOURS_UNITS_X, TIME_Y);
setColor(TIME_COLOR);
printChar(gps.hours_units);
}
if(previous_minutes_tens != gps.minutes_tens)
{
previous_minutes_tens = gps.minutes_tens;
setCursor(MINUTES_TENS_X , TIME_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(MINUTES_TENS_X , TIME_Y);
setColor(TIME_COLOR);
printChar(gps.minutes_tens);
}
if(previous_minutes_units != gps.minutes_units)
{
previous_minutes_units = gps.minutes_units;
setCursor(MINUTES_UNITS_X , TIME_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(MINUTES_UNITS_X , TIME_Y);
setColor(TIME_COLOR);
printChar(gps.minutes_units);
}
if(previous_seconds_tens != gps.seconds_tens)
{
previous_seconds_tens = gps.seconds_tens;
setCursor(SECONDS_TENS_X, TIME_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(SECONDS_TENS_X, TIME_Y);
setColor(TIME_COLOR);
printChar(gps.seconds_tens);
}
if(previous_seconds_units != gps.seconds_units)
{
previous_seconds_units = gps.seconds_units;
setCursor(SECONDS_UNITS_X, TIME_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(SECONDS_UNITS_X, TIME_Y);
setColor(TIME_COLOR);
printChar(gps.seconds_units);
}
}
void draw_date()
{
static uint8_t previous_day_tens = '0';
static uint8_t previous_day_units = '0';
static uint8_t previous_month_tens = '0';
static uint8_t previous_month_units = '0';
static uint8_t previous_year_tens = '0';
static uint8_t previous_year_units = '0';
if(previous_day_tens != gps.date_tens)
{
previous_day_tens = gps.date_tens;
setCursor(DAY_TENS_X, DATE_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(DAY_TENS_X, DATE_Y);
setColor(RED);
printChar(gps.date_tens);
}
if(previous_day_units != gps.date_units)
{
previous_day_units = gps.date_units;
setCursor(DAY_UNITS_X, DATE_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(DAY_UNITS_X, DATE_Y);
setColor(RED);
printChar(gps.date_units);
}
if(previous_month_tens != gps.month_tens)
{
previous_month_tens = gps.month_tens;
setCursor(MONTH_TENS_X, DATE_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(MONTH_TENS_X, DATE_Y);
setColor(RED);
printChar(gps.month_tens);
}
if(previous_month_units != gps.month_units)
{
previous_month_units = gps.month_units;
setCursor(MONTH_UNITS_X, DATE_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(MONTH_UNITS_X, DATE_Y);
setColor(RED);
printChar(gps.month_units);
}
if(previous_year_tens != gps.year_tens)
{
previous_year_tens = gps.year_tens;
setCursor(YEAR_TENS_X, DATE_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(YEAR_TENS_X, DATE_Y);
setColor(RED);
printChar(gps.year_tens);
}
if(previous_year_units != gps.year_units)
{
previous_year_units = gps.year_units;
setCursor(YEAR_UNITS_X, DATE_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(YEAR_UNITS_X, DATE_Y);
setColor(RED);
printChar(gps.year_units);
}
}
void draw_location()
{
static uint8_t previous_latitude_degree_1 = '0';
static uint8_t previous_latitude_degree_2 = '0';
static uint8_t previous_latitude_minutes_1 = '0';
static uint8_t previous_latitude_minutes_2 = '0';
static uint8_t previous_latitude_minutes_3 = '0';
static uint8_t previous_latitude_minutes_4 = '0';
static uint8_t previous_latitude_minutes_5 = '0';
static uint8_t previous_latitude_minutes_6 = '0';
static uint8_t previous_latitude_minutes_7 = '0';
static uint8_t previous_longitude_degree_1 = '0';
static uint8_t previous_longitude_degree_2 = '0';
static uint8_t previous_longitude_minutes_1 = '0';
static uint8_t previous_longitude_minutes_2 = '0';
static uint8_t previous_longitude_minutes_3 = '0';
static uint8_t previous_longitude_minutes_4 = '0';
static uint8_t previous_longitude_minutes_5 = '0';
static uint8_t previous_longitude_minutes_6 = '0';
static uint8_t previous_longitude_minutes_7 = '0';
if(previous_latitude_degree_1 != gps.latitude_degree_1)
{
previous_latitude_degree_1 = gps.latitude_degree_1;
setCursor(LATITUDE_DEGREE_1_X, LATITUDE_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(LATITUDE_DEGREE_1_X, LATITUDE_Y);
setColor(LATITUDE_COLOR);
printChar(gps.latitude_degree_1);
}
if(previous_latitude_degree_2 != gps.latitude_degree_2)
{
previous_latitude_degree_2 = gps.latitude_degree_2;
setCursor(LATITUDE_DEGREE_2_X, LATITUDE_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(LATITUDE_DEGREE_2_X, LATITUDE_Y);
setColor(LATITUDE_COLOR);
printChar(gps.latitude_degree_2);
}
if(previous_latitude_minutes_1 != gps.latitude_minutes_1)
{
previous_latitude_minutes_1 = gps.latitude_minutes_1;
setCursor(LATITUDE_MINUTES_1_X, LATITUDE_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(LATITUDE_MINUTES_1_X, LATITUDE_Y);
setColor(LATITUDE_COLOR);
printChar(gps.latitude_minutes_1);
}
if(previous_latitude_minutes_2 != gps.latitude_minutes_2)
{
previous_latitude_minutes_2 = gps.latitude_minutes_2;
setCursor(LATITUDE_MINUTES_2_X, LATITUDE_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(LATITUDE_MINUTES_2_X, LATITUDE_Y);
setColor(LATITUDE_COLOR);
printChar(gps.latitude_minutes_2);
}
if(previous_latitude_minutes_3 != gps.latitude_minutes_3)
{
previous_latitude_minutes_3 = gps.latitude_minutes_3;
setCursor(LATITUDE_MINUTES_3_X, LATITUDE_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(LATITUDE_MINUTES_3_X, LATITUDE_Y);
setColor(LATITUDE_COLOR);
printChar(gps.latitude_minutes_3);
}
if(previous_latitude_minutes_4 != gps.latitude_minutes_4)
{
previous_latitude_minutes_4 = gps.latitude_minutes_4;
setCursor(LATITUDE_MINUTES_4_X, LATITUDE_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(LATITUDE_MINUTES_4_X, LATITUDE_Y);
setColor(LATITUDE_COLOR);
printChar(gps.latitude_minutes_4);
}
if(previous_latitude_minutes_5 != gps.latitude_minutes_5)
{
previous_latitude_minutes_5 = gps.latitude_minutes_5;
setCursor(LATITUDE_MINUTES_5_X, LATITUDE_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(LATITUDE_MINUTES_5_X, LATITUDE_Y);
setColor(LATITUDE_COLOR);
printChar(gps.latitude_minutes_5);
}
if(previous_latitude_minutes_6 != gps.latitude_minutes_6)
{
previous_latitude_minutes_6 = gps.latitude_minutes_6;
setCursor(LATITUDE_MINUTES_6_X, LATITUDE_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(LATITUDE_MINUTES_6_X, LATITUDE_Y);
setColor(LATITUDE_COLOR);
printChar(gps.latitude_minutes_6);
}
if(previous_latitude_minutes_7 != gps.latitude_minutes_7)
{
previous_latitude_minutes_7 = gps.latitude_minutes_7;
setCursor(LATITUDE_MINUTES_7_X, LATITUDE_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(LATITUDE_MINUTES_7_X, LATITUDE_Y);
setColor(LATITUDE_COLOR);
printChar(gps.latitude_minutes_7);
}
if(previous_longitude_degree_1 != gps.longitude_degree_1)
{
previous_longitude_degree_1 = gps.longitude_degree_1;
setCursor(LONGITUDE_DEGREE_1_X, LONGITUDE_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(LONGITUDE_DEGREE_1_X, LONGITUDE_Y);
setColor(LONGITUDE_COLOR);
printChar(gps.longitude_degree_1);
}
if(previous_longitude_degree_2 != gps.longitude_degree_2)
{
previous_longitude_degree_2 = gps.longitude_degree_2;
setCursor(LONGITUDE_DEGREE_2_X, LONGITUDE_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(LONGITUDE_DEGREE_2_X, LONGITUDE_Y);
setColor(LONGITUDE_COLOR);
printChar(gps.longitude_degree_2);
}
if(previous_longitude_minutes_1 != gps.longitude_minutes_1)
{
previous_longitude_minutes_1 = gps.longitude_minutes_1;
setCursor(LONGITUDE_MINUTES_1_X, LONGITUDE_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(LONGITUDE_MINUTES_1_X, LONGITUDE_Y);
setColor(LONGITUDE_COLOR);
printChar(gps.longitude_minutes_1);
}
if(previous_longitude_minutes_2 != gps.longitude_minutes_2)
{
previous_longitude_minutes_2 = gps.longitude_minutes_2;
setCursor(LONGITUDE_MINUTES_2_X, LONGITUDE_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(LONGITUDE_MINUTES_2_X, LONGITUDE_Y);
setColor(LONGITUDE_COLOR);
printChar(gps.longitude_minutes_2);
}
if(previous_longitude_minutes_3 != gps.longitude_minutes_3)
{
previous_longitude_minutes_3 = gps.longitude_minutes_3;
setCursor(LONGITUDE_MINUTES_3_X, LONGITUDE_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(LONGITUDE_MINUTES_3_X, LONGITUDE_Y);
setColor(LONGITUDE_COLOR);
printChar(gps.longitude_minutes_3);
}
if(previous_longitude_minutes_4 != gps.longitude_minutes_4)
{
previous_longitude_minutes_4 = gps.longitude_minutes_4;
setCursor(LONGITUDE_MINUTES_4_X, LONGITUDE_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(LONGITUDE_MINUTES_4_X, LONGITUDE_Y);
setColor(LONGITUDE_COLOR);
printChar(gps.longitude_minutes_4);
}
if(previous_longitude_minutes_5 != gps.longitude_minutes_5)
{
previous_longitude_minutes_5 = gps.longitude_minutes_5;
setCursor(LONGITUDE_MINUTES_5_X, LONGITUDE_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(LONGITUDE_MINUTES_5_X, LONGITUDE_Y);
setColor(LONGITUDE_COLOR);
printChar(gps.longitude_minutes_5);
}
if(previous_longitude_minutes_6 != gps.longitude_minutes_6)
{
previous_longitude_minutes_6 = gps.longitude_minutes_6;
setCursor(LONGITUDE_MINUTES_6_X, LONGITUDE_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(LONGITUDE_MINUTES_6_X, LONGITUDE_Y);
setColor(LONGITUDE_COLOR);
printChar(gps.longitude_minutes_6);
}
if(previous_longitude_minutes_7 != gps.longitude_minutes_7)
{
previous_longitude_minutes_7 = gps.longitude_minutes_7;
setCursor(LONGITUDE_MINUTES_7_X, LONGITUDE_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(LONGITUDE_MINUTES_7_X, LONGITUDE_Y);
setColor(LONGITUDE_COLOR);
printChar(gps.longitude_minutes_7);
}
}
void draw_altitude()
{
static uint8_t previous_altitude_hundreds = '0';
static uint8_t previous_altitude_tens = '0';
static uint8_t previous_altitude_units = '0';
static uint8_t previous_altitude_fraction = '0';
if(previous_altitude_hundreds != gps.altitude_hundreds)
{
previous_altitude_hundreds = gps.altitude_hundreds;
setCursor(ALTITUDE_HUNDREDS_X, ALTITUDE_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(ALTITUDE_HUNDREDS_X, ALTITUDE_Y);
setColor(ALTITUDE_COLOR);
printChar(gps.altitude_hundreds);
}
if(previous_altitude_tens != gps.altitude_tens)
{
previous_altitude_tens = gps.altitude_tens;
setCursor(ALTITUDE_TENS_X, ALTITUDE_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(ALTITUDE_TENS_X, ALTITUDE_Y);
setColor(ALTITUDE_COLOR);
printChar(gps.altitude_tens);
}
if(previous_altitude_units != gps.altitude_unit)
{
previous_altitude_units = gps.altitude_unit;
setCursor(ALTITUDE_UNITS_X, ALTITUDE_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(ALTITUDE_UNITS_X, ALTITUDE_Y);
setColor(ALTITUDE_COLOR);
printChar(gps.altitude_units);
}
if(previous_altitude_fraction != gps.altitude_fraction)
{
previous_altitude_fraction = gps.altitude_fraction;
setCursor(ALTITUDE_FRACTION_X, ALTITUDE_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(ALTITUDE_FRACTION_X, ALTITUDE_Y);
setColor(ALTITUDE_COLOR);
printChar(gps.altitude_fraction);
}
setCursor(ALTITUDE_UNIT_X, ALTITUDE_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(ALTITUDE_UNIT_X, ALTITUDE_Y);
setColor(ALTITUDE_COLOR);
printChar(gps.altitude_unit);
}
void draw_speed()
{
static uint8_t previous_speed_integer = '0';
static uint8_t previous_speed_fraction_1 = '0';
static uint8_t previous_speed_fraction_2 = '0';
static uint8_t previous_speed_fraction_3 = '0';
if(previous_speed_integer != gps.speed_integer)
{
previous_speed_integer = gps.speed_integer;
setCursor(SPEED_INTEGER_X, SPEED_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(SPEED_INTEGER_X, SPEED_Y);
setColor(SPEED_COLOR);
printChar(gps.speed_integer);
}
if(previous_speed_fraction_1 != gps.speed_fraction_1)
{
previous_speed_fraction_1 = gps.speed_fraction_1;
setCursor(SPEED_FRACTION_1_X, SPEED_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(SPEED_FRACTION_1_X, SPEED_Y);
setColor(SPEED_COLOR);
printChar(gps.speed_fraction_1);
}
if(previous_speed_fraction_2 != gps.speed_fraction_2)
{
previous_speed_fraction_2 = gps.speed_fraction_2;
setCursor(SPEED_FRACTION_2_X, SPEED_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(SPEED_FRACTION_2_X, SPEED_Y);
setColor(SPEED_COLOR);
printChar(gps.speed_fraction_2);
}
if(previous_speed_fraction_3 != gps.speed_fraction_3)
{
previous_speed_fraction_3 = gps.speed_fraction_3;
setCursor(SPEED_FRACTION_3_X, SPEED_Y);
setColor(BACKGROUND_COLOR);
fillRectangle(ARIAL_14_RECTANGLE_WIDHT, ARIAL_14_RECTANGLE_HEIGHT);
setCursor(SPEED_FRACTION_3_X, SPEED_Y);
setColor(SPEED_COLOR);
printChar(gps.speed_fraction_3);
}
}
↑ Основная функция
В main() по завершению пакета на дисплей выводятся:1. Время и дата.
2. Широта, долгота, высота и скорость, если соответствующие данные — валидные.
↑ Файлы
Программа выложена в архив. Кроме того, там же размещён код эмулятора, который передаёт в последовательный порт такие же данные, что и реальный модуль.🎁kod.zip 71.2 Kb ⇣ 2
Спасибо за внимание!
Камрад, рассмотри датагорские рекомендации
🌼 Полезные и проверенные железяки, можно брать
Опробовано в лаборатории редакции или читателями.