Выбор иерархии классов для разнотипных данных (дата/время/дни/таймер)

Итоговое устройство

Итоговый код:

Спойлер
/**
 ******************************************************************************
 * @file           : main.c
 * @author         : Auto-generated by STM32CubeIDE
 * @brief          : Main program body
 ******************************************************************************
 * @attention
 *
 * Copyright (c) 2023 STMicroelectronics.
 * All rights reserved.
 *
 * This software is licensed under terms that can be found in the LICENSE file
 * in the root directory of this software component.
 * If no LICENSE file comes with this software, it is provided AS-IS.
 *
 ******************************************************************************
 */

#include <stdint.h>
#include "f103c8t6.h"
#include "stdlib.h"
#include "f103st7735.h"
#include "stm32f10x.h"
#include "pumper.h"
#include "f103rtc.h"

#if !defined(__SOFT_FP__) && defined(__ARM_FP)
  #warning "FPU is not initialized, but the project is compiling for an FPU. Please initialize the FPU before use."
#endif

unsigned char startFontSize = 0;
unsigned char lineCFGtext = 0;

int main(void) {
	if (ClockInit() == 0) SystemCoreClock = 72000000UL;
	f103initPC13led();
	f103initTIM4();
	f103st7735init();
	f1InitPumpGpio();
	f103st7735fillScreen(ST7735_BLACK);
	f103st7735printString((const unsigned char *) "Start!", ST7735_WHITE, ST7735_BLACK, startFontSize, 0, lineCFGtext * fontSizeY[startFontSize]);
	++lineCFGtext;
	f103st7735printUInt(SystemCoreClock, ST7735_RED, ST7735_WHITE, startFontSize, 0, lineCFGtext * fontSizeY[startFontSize]);
	++lineCFGtext;
	f103initRTC();
	// -- manual set date time current
	/*currentDateTime.tm_mday = 18; // 1-31
	currentDateTime.tm_mon = 7; // 0-11
	currentDateTime.tm_year = 2023 - 1900; // since 1900 year
	currentDateTime.tm_hour = 10; // 0-23
	currentDateTime.tm_min = 19; // 0-59
	currentDateTime.tm_sec = 30; // 0-60
	currentDateTime.tm_wday = 4; // 0-6 since Sunday
	setCurrentDateTime(&currentDateTime);*/
	// -- read RTC
	getCurrentDateTime(&currentDateTime);
	f103st7735printString((const unsigned char *) "* ", ST7735_WHITE, ST7735_BLACK, startFontSize, 0, (lineCFGtext+2) * fontSizeY[startFontSize]);
	f103st7735printText((const unsigned char *) asctime((tm*)&currentDateTime));
	// -- begin
	delay_ms(5000UL);
	f103st7735fillScreen(ST7735_BLACK);
	// -- pumper
	initPumper();
	// -- start loop --
	while (1) {
		loopPumper();
		// test led PC13 blink
		static unsigned long timerBlinkC13 = 0;
		static unsigned char ledStat = 0;
		if ((millis() - timerBlinkC13) >= 1000UL) {
			timerBlinkC13 = millis();
			f103setC13led(ledStat = !ledStat);
		}
		// -- end loop --
	}
}


/*
 * f103c8t6.h
 *
 *  Created on: Feb 28, 2023
 *      Author: Andrey
 */

#ifndef F103C8T6_H_
#define F103C8T6_H_

#ifndef count_of
#define count_of(a) (sizeof(a)/sizeof((a)[0]))
#endif

#define bitRead(x, bitPosition) (((x) >> bitPosition) & 1)

extern unsigned long SystemCoreClock; /*!< System Clock Frequency (Core Clock) */

int ClockInit(void);
void f103setC13led(unsigned char AledONOFF);
void f103initPC13led(void);
void f103initTIM4(void);
unsigned long millis(void);
void delay_ms(unsigned long period_ms);

#endif /* F103C8T6_H_ */



/*
 * f103c8t6.c
 *
 *  Created on: Feb 28, 2023
 *      Author: Andrey
 */

#include "stm32f10x.h"
#include "f103c8t6.h"

unsigned long SystemCoreClock = 8000000UL; /*!< System Clock Frequency (Core Clock) */
volatile unsigned long msTicks = 0;

/*http://dimoon.ru/obuchalka/stm32f1/uroki-stm32f103-chast-4-nastroyka-rcc.html?ysclid=leflwcqmz5475683246*/
//Настраиваем тактирование системы от внешнего кварца
//через PLL на саксимально возможных частотах.
//Внешний кварц должен быть на 8МГц
//Возвращает:
//  0 - завершено успешно
//  1 - не запустился кварцевый генератор
//  2 - не запустился PLL
int ClockInit(void) {
  __IO int StartUpCounter;
  ////////////////////////////////////////////////////////////
  //Запускаем кварцевый генератор
  ////////////////////////////////////////////////////////////
  RCC->CR |= RCC_CR_HSEON; //Запускаем генератор HSE
  //Ждем успешного запуска или окончания тайм-аута
  for(StartUpCounter=0; ; StartUpCounter++) {
    //Если успешно запустилось, то
    //выходим из цикла
    if (RCC->CR & RCC_CR_HSERDY) break;
    //Если не запустилось, то
    //отключаем все, что включили
    //и возвращаем ошибку
    if (StartUpCounter > 0x1000) {
      RCC->CR &= ~RCC_CR_HSEON; //Останавливаем HSE
      return 1;
    }
  }
  ////////////////////////////////////////////////////////////
  //Настраиваем и запускаем PLL
  ////////////////////////////////////////////////////////////
  //Настраиваем PLL
  RCC->CFGR |= RCC_CFGR_PLLMULL_0 | RCC_CFGR_PLLMULL_1 | RCC_CFGR_PLLMULL_2;  //PLL множитель равен 9 // 0111: PLL input clock x 9
  RCC->CFGR |= RCC_CFGR_PLLSRC_HSE; //Тактирование PLL от HSE
  RCC->CR |= RCC_CR_PLLON; //Запускаем PLL
  //Ждем успешного запуска или окончания тайм-аута
  for(StartUpCounter=0; ; StartUpCounter++) {
    //Если успешно запустилось, то
    //выходим из цикла
    if (RCC->CR & RCC_CR_PLLRDY) break;
    //Если по каким-то причинам не запустился PLL, то
    //отключаем все, что включили
    //и возвращаем ошибку
    if (StartUpCounter > 0x1000) {
      RCC->CR &= ~RCC_CR_HSEON; //Останавливаем HSE
      RCC->CR &= ~RCC_CR_PLLON; //Останавливаем PLL
      return 2;
    }
  }
  ////////////////////////////////////////////////////////////
  //Настраиваем FLASH и делители
  ////////////////////////////////////////////////////////////
  //Устанавливаем 2 цикла ожидания для Flash
  //так как частота ядра у нас будет 48 MHz < SYSCLK <= 72 MHz
  FLASH->ACR |= FLASH_ACR_LATENCY_1; // 010 Two wait states, if 48 MHz < SYSCLK ≤ 72 MHz
  //Делители
  RCC->CFGR &= ~RCC_CFGR_PPRE2; //Делитель шины APB2 отключен // 0xx: HCLK not divided
  RCC->CFGR |= RCC_CFGR_PPRE1_DIV2; //Делитель нишы APB1 равен 2 // 100: HCLK divided by 2
  RCC->CFGR &= ~RCC_CFGR_HPRE; //Делитель AHB отключен // 0xxx: SYSCLK not divided
  RCC->CFGR &= ~RCC_CFGR_SW; //Переключаемся на работу от PLL // 10: PLL selected as system clock
  RCC->CFGR |= RCC_CFGR_SW_1; //Переключаемся на работу от PLL // 10: PLL selected as system clock
  //Ждем, пока переключимся
  while(!(RCC->CFGR & RCC_CFGR_SWS_1));
  //После того, как переключились на
  //внешний источник такирования
  //отключаем внутренний RC-генератор
  //для экономии энергии
  //RCC->CR &= ~RCC_CR_HSION; // При отключенном HSI Или его частоте выше 24 МГц не работает запись/стирание FLASH пользовательской памяти!!!
  //Настройка и переклбючение сисемы
  //на внешний кварцевый генератор
  //и PLL запершилось успехом.
  //Выходим
  return 0;
}

void f103setC13led(unsigned char AledONOFF) {
	if (AledONOFF) GPIOC->BSRR |= GPIO_BSRR_BR13; // PC13 LOW
	else GPIOC->BSRR |= GPIO_BSRR_BS13; // PC13 HIGH
}

void f103initPC13led(void) {
	RCC->APB2ENR |= RCC_APB2ENR_IOPCEN; // GPIO port C
	GPIOC->CRH &= ~GPIO_CRH_MODE13; // 00: Input mode (reset state)
	GPIOC->CRH |= GPIO_CRH_MODE13_0; // 01: Output mode, max speed 10 MHz.
	GPIOC->CRH &= ~GPIO_CRH_CNF13; // 00: General purpose output push-pull
	f103setC13led(0);
}

extern "C" void TIM4_IRQHandler(void) {
    TIM4->SR &= ~TIM_SR_UIF;
	++msTicks;
}

void f103initTIM4(void) {
	RCC->APB1ENR |= RCC_APB1ENR_TIM4EN; // clock Timer4 ON
	TIM4->SR = 0;
	TIM4->CNT = 0;
	TIM4->PSC = (SystemCoreClock / 2 / 1000000) - 1; // делитель
	TIM4->ARR = 1999; // значение перезагрузки -> 1 msec
	TIM4->DIER |= TIM_DIER_UIE;
	NVIC_EnableIRQ (TIM4_IRQn);
	TIM4->CR1 |= TIM_CR1_CEN; // включаем счётчик
    TIM4->SR &= ~TIM_SR_UIF;
}

unsigned long millis(void) {
	return msTicks;
}

void delay_ms(unsigned long period_ms) {
	unsigned long startTimer = millis();
	while ((millis() - startTimer) < period_ms);
}



Спойлер
/*
 * f103rtc.h
 *
 *  Created on: Aug 15, 2023
 *      Author: seleznev_a
 */

#ifndef F103RTC_H_
#define F103RTC_H_

#include <time.h>

void f103initRTC(void);

extern struct tm currentDateTime;

void getCurrentDateTime(struct tm * outTm);
void setCurrentDateTime(struct tm * outTm);

#endif /* F103RTC_H_ */



/*
 * f103rtc.cpp
 *
 *  Created on: Aug 15, 2023
 *      Author: seleznev_a
 *
 *      +http://uc.org.ru/node/30?ysclid=lldq32k6ys131101849
 *
 */

#include "f103rtc.h"
#include "stm32f10x.h"
#include "f103st7735.h"
#include <time.h>

struct tm currentDateTime;

void f103initRTC(void) {
	  if ((RCC->BDCR & RCC_BDCR_RTCEN) != RCC_BDCR_RTCEN)   {              //Проверка работы часов, если не включены, то инициализировать
		RCC->APB1ENR |= RCC_APB1ENR_PWREN | RCC_APB1ENR_BKPEN;  //Включить тактирование PWR и Backup
		PWR->CR |= PWR_CR_DBP;                                                            //Разрешить доступ к Backup области
		RCC->BDCR |= RCC_BDCR_BDRST;                                               //Сбросить Backup область
		RCC->BDCR &= ~RCC_BDCR_BDRST;
		RCC->BDCR |= RCC_BDCR_RTCSEL_LSE;        //Выбрать LSE источник (кварц 32768)
		RCC->BDCR |= RCC_BDCR_LSEON;      //Включить LSE
		while ((RCC->BDCR & RCC_BDCR_LSEON) != RCC_BDCR_LSEON){} //Дождаться включения
		BKP->RTCCR |= 3;                                                                         //калибровка RTC
		while (!(RTC->CRL & RTC_CRL_RTOFF));                                         //проверить закончены ли изменения регистров RTC
		RTC->CRL  |=  RTC_CRL_CNF;                                                        //Разрешить Запись в регистры RTC
		RTC->PRLL  = 0x7FFF;                                                                    //Настроит делитель на 32768 (32767+1)
		//BKP->RTCCR |= BKP_RTCCR_CCO;                   //Включение вывода Temper
		RTC->CRL  &=  ~RTC_CRL_CNF;                                                     //Запретить запись в регистры RTC
		RCC->BDCR |= RCC_BDCR_RTCEN; // и подать тактирование
		while (!(RTC->CRL & RTC_CRL_RTOFF));                                         //Дождаться окончания записи
		RTC->CRL &= (uint16_t)~RTC_CRL_RSF;                                         //Синхронизировать RTC
		while((RTC->CRL & RTC_CRL_RSF) != RTC_CRL_RSF){}                  //Дождаться синхронизации
		PWR->CR &= ~PWR_CR_DBP;                                                         //запретить доступ к Backup области
	  }
}

unsigned long RTC_GetCounter(void)  {                                                           //Получить значение счетчика
	return  (unsigned long)((RTC->CNTH << 16) | RTC->CNTL);
}

void RTC_SetCounter(unsigned long count)  {                                                  //Записать новое значение счетчика
	RCC->APB1ENR |= RCC_APB1ENR_PWREN | RCC_APB1ENR_BKPEN;  //включить тактирование PWR и Backup
	PWR->CR |= PWR_CR_DBP;                                                            //разрешить доступ к Backup области
	while (!(RTC->CRL & RTC_CRL_RTOFF));                                         //проверить закончены ли изменения регистров RTC
	RTC->CRL |= RTC_CRL_CNF;                                                          //Разрешить Запись в регистры RTC
	RTC->CNTH = count>>16;                                                              //записать новое значение счетного регистра
	RTC->CNTL = count;
	RTC->CRL &= ~RTC_CRL_CNF;                                                       //Запретить запись в регистры RTC
	while (!(RTC->CRL & RTC_CRL_RTOFF));                                         //Дождаться окончания записи
	PWR->CR &= ~PWR_CR_DBP;                                                         //запретить доступ к Backup области
}

void getCurrentDateTime(struct tm * outTm) {
	time_t rawtime = (time_t)RTC_GetCounter();
	localtime_r(&rawtime, outTm);
}

void setCurrentDateTime(struct tm * outTm) {
	RTC_SetCounter((unsigned long)mktime(outTm));
}



/*
 * f103flash.h
 *
 *  Created on: 28 февр. 2023 г.
 *      Author: Andrey
 */

#ifndef F103FLASH_H_
#define F103FLASH_H_

#define NVIC_VectTab_FLASH		((unsigned long)0x08000000) // начало флэша
#define FLASH_PAGE_SIZE			((unsigned long)128) // CHF32F103C8T6 -> 128 // размер одной страницы // размер страницы памяти для большинства МК серии STM32F103 составляет 1Kb, за исключением микроконтроллеров линейки HD и CL (Connectivity Line), в которых она равна двум килобайтам
#define NUM_PAGE_EEPROM_BEGIN	((unsigned long)256) // с какой страницы памяти будем писать свои данные // 256*128=0x8000->0x8008000
#define FIRMWARE_PAGE_OFFSET 	((unsigned long)NUM_PAGE_EEPROM_BEGIN*FLASH_PAGE_SIZE) // смещение в байтах, с которого будем писать свои данные

void FLASH_fill_page(uint32_t Value);
void FLASH_write_param(unsigned short numParam, uint32_t paramValue);
unsigned long FLASH_read_param(unsigned short numParam);

#endif /* F103FLASH_H_ */



/*
 * f103flash.c
 *
 *  Created on: 28 февр. 2023 г.
 *      Author: Andrey
 */

#include "stm32f10x.h"
#include "f103flash.h"

/*https://smartmode.info/stm32/13-stm32-flash*/
/*https://easystm32.ru/for-beginners/38-flash-stm32/*/
/*http://we.easyelectronics.ru/STM32/programmirovanie-flash.html?ysclid=leo3iva1x048263658*/

unsigned long FLASH_read(uint32_t address) {
	return (*(__IO uint32_t*) address);
}

#define FLASH_KEY1 ((uint32_t)0x45670123)
#define FLASH_KEY2 ((uint32_t)0xCDEF89AB)

void FLASH_Unlock(void) {
	  FLASH->KEYR = FLASH_KEY1;
	  FLASH->KEYR = FLASH_KEY2;
}

void FLASH_Lock() {
	FLASH->CR |= FLASH_CR_LOCK;
}

void FLASH_ErasePage(unsigned long inAdr) {
	  FLASH->CR |= FLASH_CR_PER; //Устанавливаем бит стирания одной страницы
	  FLASH->AR = inAdr; // Задаем её адрес
	  FLASH->CR |= FLASH_CR_STRT; // Запускаем стирание
	  while ((FLASH->SR & FLASH_SR_BSY) != 0 ); // Wait end of eraze
	  FLASH->CR &= ~FLASH_CR_PER; //Сбрасываем бит обратно
}

void FLASH_fill_page(uint32_t Value) {
	uint32_t pageAdr = NVIC_VectTab_FLASH + FIRMWARE_PAGE_OFFSET;                    // Адрес страницы памяти
	FLASH_Unlock();                                                         // Разблокируем память для записи
	FLASH_ErasePage(pageAdr);                                               // Очистим страницу памяти
	FLASH->CR |= FLASH_CR_PG; //Разрешаем программирование флеша
	for(unsigned short i = 0; i < (FLASH_PAGE_SIZE/4); ++i) {
		unsigned long inValue = Value;
		  while ((FLASH->SR & FLASH_SR_BSY) != 0 );
		  *(__IO uint16_t*)pageAdr = (uint16_t)inValue; //Пишем младшие 2 бата
		  while ((FLASH->SR & FLASH_SR_BSY) != 0 );
		  pageAdr += 2;
		  inValue>>=16;
		  *(__IO uint16_t*)pageAdr = (uint16_t)inValue; //Пишем старшие 2 байта
		  while ((FLASH->SR & FLASH_SR_BSY) != 0 );
		  pageAdr += 2;
	}
	FLASH->CR &= ~FLASH_CR_PG; //Запрещаем программирование флеша
	FLASH_Lock();
}

void FLASH_write_param(unsigned short numParam, uint32_t paramValue) {
	uint32_t pageAdr = NVIC_VectTab_FLASH + FIRMWARE_PAGE_OFFSET; // Адрес страницы памяти
	numParam %= FLASH_PAGE_SIZE/4; // номер параметра не может превысить число 4х байтовых слов в странице
	unsigned long dataPage[FLASH_PAGE_SIZE/4]; // место куда сохраним текущую страницу
	for(unsigned short i = 0; i < (FLASH_PAGE_SIZE/4); ++i) dataPage[i] = FLASH_read(pageAdr + i * 4);
	dataPage[numParam] = paramValue;
	FLASH_Unlock();                                                         // Разблокируем память для записи
	FLASH_ErasePage(pageAdr);                                               // Очистим страницу памяти
	FLASH->CR |= FLASH_CR_PG; //Разрешаем программирование флеша
	for(unsigned short i = 0; i < (FLASH_PAGE_SIZE/4); ++i) {
		unsigned long inValue = dataPage[i];
		  while ((FLASH->SR & FLASH_SR_BSY) != 0 );
		  *(__IO uint16_t*)pageAdr = (uint16_t)inValue; //Пишем младшие 2 бата
		  while ((FLASH->SR & FLASH_SR_BSY) != 0 );
		  pageAdr += 2;
		  inValue>>=16;
		  *(__IO uint16_t*)pageAdr = (uint16_t)inValue; //Пишем старшие 2 байта
		  while ((FLASH->SR & FLASH_SR_BSY) != 0 );
		  pageAdr += 2;
	}
	FLASH->CR &= ~FLASH_CR_PG; //Запрещаем программирование флеша
	FLASH_Lock();
}

unsigned long FLASH_read_param(unsigned short numParam) {
	uint32_t pageAdr = NVIC_VectTab_FLASH + FIRMWARE_PAGE_OFFSET; // Адрес страницы памяти
	numParam %= FLASH_PAGE_SIZE/4; // номер параметра не может превысить число 4х байтовых слов в странице
	return FLASH_read(pageAdr + numParam * 4);
}


Спойлер
 /*
 * f103st7735.h
 *
 *  Created on: 6 июн. 2023 г.
 *      Author: seleznev_a
 *
 *      ST7735 pins ->  STM32F103C8T6 pins
 *      1 GND -> GND
 *      2 VCC -> 3.3 V
 *      3 SCK -> PA5 SCK SPI1
 *      4 SDA -> PA7 MOSI SPI1
 *      5 RES -> PB1
 *      6 RS(A0) -> PB10
 *      7 CS -> PB11
 *      8 LEDA -> 3.3 V
 *
 */

#ifndef F103ST7735_H_
#define F103ST7735_H_

/*
 * https://count-zero.ru/2022/display/#10
 */

#define st7735sizeX 128
#define st7735sizeY 160

// some flags for initR() :(
#define INITR_GREENTAB 0x00
#define INITR_REDTAB 0x01
#define INITR_BLACKTAB 0x02
#define INITR_18GREENTAB INITR_GREENTAB
#define INITR_18REDTAB INITR_REDTAB
#define INITR_18BLACKTAB INITR_BLACKTAB
#define INITR_144GREENTAB 0x01
#define INITR_MINI160x80 0x04
#define INITR_HALLOWING 0x05

// Some register settings
#define ST7735_MADCTL_BGR 0x08
#define ST7735_MADCTL_MH 0x04

#define ST7735_FRMCTR1 0xB1
#define ST7735_FRMCTR2 0xB2
#define ST7735_FRMCTR3 0xB3
#define ST7735_INVCTR 0xB4
#define ST7735_DISSET5 0xB6

#define ST7735_PWCTR1 0xC0
#define ST7735_PWCTR2 0xC1
#define ST7735_PWCTR3 0xC2
#define ST7735_PWCTR4 0xC3
#define ST7735_PWCTR5 0xC4
#define ST7735_VMCTR1 0xC5

#define ST7735_PWCTR6 0xFC

#define ST7735_GMCTRP1 0xE0
#define ST7735_GMCTRN1 0xE1

// Some ready-made 16-bit ('565') color settings:
#define ST7735_BLACK ST77XX_BLACK
#define ST7735_WHITE ST77XX_WHITE
#define ST7735_RED ST77XX_RED
#define ST7735_GREEN ST77XX_GREEN
#define ST7735_BLUE ST77XX_BLUE
#define ST7735_CYAN ST77XX_CYAN
#define ST7735_MAGENTA ST77XX_MAGENTA
#define ST7735_YELLOW ST77XX_YELLOW
#define ST7735_ORANGE ST77XX_ORANGE

#define ST_CMD_DELAY 0x80 // special signifier for command lists

#define ST77XX_NOP 0x00
#define ST77XX_SWRESET 0x01
#define ST77XX_RDDID 0x04
#define ST77XX_RDDST 0x09

#define ST77XX_SLPIN 0x10
#define ST77XX_SLPOUT 0x11
#define ST77XX_PTLON 0x12
#define ST77XX_NORON 0x13

#define ST77XX_INVOFF 0x20
#define ST77XX_INVON 0x21
#define ST77XX_DISPOFF 0x28
#define ST77XX_DISPON 0x29
#define ST77XX_CASET 0x2A
#define ST77XX_RASET 0x2B
#define ST77XX_RAMWR 0x2C
#define ST77XX_RAMRD 0x2E

#define ST77XX_PTLAR 0x30
#define ST77XX_TEOFF 0x34
#define ST77XX_TEON 0x35
#define ST77XX_MADCTL 0x36
#define ST77XX_COLMOD 0x3A

#define ST77XX_MADCTL_MY 0x80
#define ST77XX_MADCTL_MX 0x40
#define ST77XX_MADCTL_MV 0x20
#define ST77XX_MADCTL_ML 0x10
#define ST77XX_MADCTL_RGB 0x00

#define ST77XX_RDID1 0xDA
#define ST77XX_RDID2 0xDB
#define ST77XX_RDID3 0xDC
#define ST77XX_RDID4 0xDD

// Some ready-made 16-bit ('565') color settings:
#define ST77XX_BLACK 0x0000
#define ST77XX_WHITE 0xFFFF
#define ST77XX_RED 0xF800
#define ST77XX_GREEN 0x07E0
#define ST77XX_BLUE 0x001F
#define ST77XX_CYAN 0x07FF
#define ST77XX_MAGENTA 0xF81F
#define ST77XX_YELLOW 0xFFE0
#define ST77XX_ORANGE 0xFC00

void f103st7735init(void);
void f103st7735fillScreen(uint16_t value);
void f103st7735fillRect(uint16_t value, unsigned short x0, unsigned short y0, unsigned short x1, unsigned short y1);
void f103st7735showRect(unsigned short * dataPoint, unsigned short x0, unsigned short y0, unsigned short x1, unsigned short y1);

void f103st7735printOneChar(unsigned char inChar, unsigned short colChar, unsigned short colBackground, unsigned char sizeChar, unsigned char xChar, unsigned char yChar);
void f103st7735printString(const unsigned char * inStr, unsigned short colChar, unsigned short colBackground, unsigned char sizeChar, unsigned char xChar, unsigned char yChar);
void f103st7735printUInt(unsigned long inVal, unsigned short colChar, unsigned short colBackground, unsigned char sizeChar, unsigned char xChar, unsigned char yChar);
void f103st7735printHEX(unsigned long inVal, unsigned short colChar, unsigned short colBackground, unsigned char sizeChar, unsigned char xChar, unsigned char yChar);
void f103st7735printText(const unsigned char * inText);

#define fontWsize0 8
#define fontHsize0 8

#define fontWsize1 (fontWsize0+fontWsize0/2) // 12
#define fontHsize1 (fontHsize0*2) // 16

#define fontWsize2 (fontWsize1+fontWsize1/2) // 18
#define fontHsize2 (fontHsize1+fontHsize0) // 24

#define fontWsize3 (fontWsize2+fontWsize1/2) //24
#define fontHsize3 (fontHsize2+fontHsize0) // 32

extern unsigned char fontSizeX[4];
extern unsigned char fontSizeY[4];

#endif /* F103ST7735_H_ */



/*
 * f103st7735.c
 *
 *  Created on: 6 июн. 2023 г.
 *      Author: seleznev_a
 *
 *      ST7735 pins ->  STM32F103C8T6 pins
 *      1 GND -> GND
 *      2 VCC -> 3.3 V
 *      3 SCK -> PA5 SCK SPI1
 *      4 SDA -> PA7 MOSI SPI1
 *      5 RES -> PB1
 *      6 RS(A0) -> PB10
 *      7 CS -> PB11
 *      8 LEDA -> 3.3 V
 *
 */

#include "stm32f10x.h"
#include "f103c8t6.h"
#include "f103st7735.h"
#include <stdlib.h>
#include <string.h>

/*
 * FR08x08.c
 *
 *  Created on: 25 нояб. 2021 г.
 *      Author: http://forum.easyelectronics.ru/viewtopic.php?f=9&t=18271
 */

const unsigned char frus08x08[256][8] = {
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
  {0x7E, 0x81, 0xA5, 0x81, 0xBD, 0x99, 0x81, 0x7E},
  {0x7E, 0xFF, 0xDB, 0xFF, 0xC3, 0xE7, 0xFF, 0x7E},
  {0x6C, 0xFE, 0xFE, 0xFE, 0x7C, 0x38, 0x10, 0x00},
  {0x10, 0x38, 0x7C, 0xFE, 0x7C, 0x38, 0x10, 0x00},
  {0x38, 0x7C, 0x38, 0xFE, 0xFE, 0x7C, 0x38, 0x7C},
  {0x10, 0x10, 0x38, 0x7C, 0xFE, 0x7C, 0x38, 0x7C},
  {0x00, 0x00, 0x18, 0x3C, 0x3C, 0x18, 0x00, 0x00},
  {0xFF, 0xFF, 0xE7, 0xC3, 0xC3, 0xE7, 0xFF, 0xFF},
  {0x00, 0x3C, 0x66, 0x42, 0x42, 0x66, 0x3C, 0x00},
  {0xFF, 0xC3, 0x99, 0xBD, 0xBD, 0x99, 0xC3, 0xFF},
  {0x0F, 0x07, 0x0F, 0x7D, 0xCC, 0xCC, 0xCC, 0x78},
  {0x3C, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x7E, 0x18},
  {0x3F, 0x33, 0x3F, 0x30, 0x30, 0x70, 0xF0, 0xE0},
  {0x7F, 0x63, 0x7F, 0x63, 0x63, 0x67, 0xE6, 0xC0},
  {0x99, 0x5A, 0x3C, 0xE7, 0xE7, 0x3C, 0x5A, 0x99},
  {0x80, 0xE0, 0xF8, 0xFE, 0xF8, 0xE0, 0x80, 0x00},
  {0x02, 0x0E, 0x3E, 0xFE, 0x3E, 0x0E, 0x02, 0x00},
  {0x18, 0x3C, 0x7E, 0x18, 0x18, 0x7E, 0x3C, 0x18},
  {0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x66, 0x00},
  {0x7F, 0xDB, 0xDB, 0x7B, 0x1B, 0x1B, 0x1B, 0x00},
  {0x3E, 0x63, 0x38, 0x6C, 0x6C, 0x38, 0xCC, 0x78},
  {0x00, 0x00, 0x00, 0x00, 0x7E, 0x7E, 0x7E, 0x00},
  {0x18, 0x3C, 0x7E, 0x18, 0x7E, 0x3C, 0x18, 0xFF},
  {0x18, 0x3C, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x00},
  {0x18, 0x18, 0x18, 0x18, 0x7E, 0x3C, 0x18, 0x00},
  {0x00, 0x18, 0x0C, 0xFE, 0x0C, 0x18, 0x00, 0x00},
  {0x00, 0x30, 0x60, 0xFE, 0x60, 0x30, 0x00, 0x00},
  {0x00, 0x00, 0xC0, 0xC0, 0xC0, 0xFE, 0x00, 0x00},
  {0x00, 0x24, 0x66, 0xFF, 0x66, 0x24, 0x00, 0x00},
  {0x00, 0x18, 0x3C, 0x7E, 0xFF, 0xFF, 0x00, 0x00},
  {0x00, 0xFF, 0xFF, 0x7E, 0x3C, 0x18, 0x00, 0x00},
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
  {0x60, 0xF0, 0xF0, 0x60, 0x60, 0x00, 0x60, 0x00},
  {0xD8, 0xD8, 0xD8, 0x00, 0x00, 0x00, 0x00, 0x00},
  {0x6C, 0xFE, 0x6C, 0x6C, 0xFE, 0x6C, 0x00, 0x00},
  {0x18, 0x7E, 0xC0, 0x7C, 0x06, 0xFC, 0x18, 0x00},
  {0x00, 0xC6, 0xCC, 0x18, 0x30, 0x66, 0xC6, 0x00},
  {0x38, 0x6C, 0x38, 0x6E, 0xDC, 0xCC, 0x76, 0x00},
  {0x30, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00},
  {0x30, 0x60, 0xC0, 0xC0, 0xC0, 0x60, 0x30, 0x00},
  {0x60, 0x30, 0x18, 0x18, 0x18, 0x30, 0x60, 0x00},
  {0x00, 0xCC, 0x78, 0xFC, 0x78, 0xCC, 0x00, 0x00},
  {0x00, 0x30, 0x30, 0xFC, 0x30, 0x30, 0x00, 0x00},
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x60},
  {0x00, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 0x00},
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x00},
  {0x06, 0x0C, 0x18, 0x30, 0x60, 0xC0, 0x80, 0x00},
  {0x7C, 0xC6, 0xCE, 0xDE, 0xF6, 0xE6, 0x7C, 0x00},
  {0x30, 0x70, 0x30, 0x30, 0x30, 0x30, 0xFC, 0x00},
  {0x78, 0xCC, 0x0C, 0x38, 0x60, 0xCC, 0xFC, 0x00},
  {0x78, 0xCC, 0x0C, 0x38, 0x0C, 0xCC, 0x78, 0x00},
  {0x1C, 0x3C, 0x6C, 0xCC, 0xFE, 0x0C, 0x1E, 0x00},
  {0xFC, 0xC0, 0xF8, 0x0C, 0x0C, 0xCC, 0x78, 0x00},
  {0x38, 0x60, 0xC0, 0xF8, 0xCC, 0xCC, 0x78, 0x00},
  {0xFC, 0xCC, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x00},
  {0x78, 0xCC, 0xCC, 0x78, 0xCC, 0xCC, 0x78, 0x00},
  {0x78, 0xCC, 0xCC, 0x7C, 0x0C, 0x18, 0x70, 0x00},
  {0x00, 0x30, 0x30, 0x00, 0x00, 0x30, 0x30, 0x00},
  {0x00, 0x30, 0x30, 0x00, 0x00, 0x30, 0x30, 0x60},
  {0x18, 0x30, 0x60, 0xC0, 0x60, 0x30, 0x18, 0x00},
  {0x00, 0x00, 0xFC, 0x00, 0x00, 0xFC, 0x00, 0x00},
  {0x60, 0x30, 0x18, 0x0C, 0x18, 0x30, 0x60, 0x00},
  {0x78, 0xCC, 0x0C, 0x18, 0x30, 0x00, 0x30, 0x00},
  {0x7C, 0xC6, 0xDE, 0xDE, 0xDE, 0xC0, 0x78, 0x00},
  {0x30, 0x78, 0xCC, 0xCC, 0xFC, 0xCC, 0xCC, 0x00},
  {0xFC, 0x66, 0x66, 0x7C, 0x66, 0x66, 0xFC, 0x00},
  {0x3C, 0x66, 0xC0, 0xC0, 0xC0, 0x66, 0x3C, 0x00},
  {0xF8, 0x6C, 0x66, 0x66, 0x66, 0x6C, 0xF8, 0x00},
  {0xFE, 0x62, 0x68, 0x78, 0x68, 0x62, 0xFE, 0x00},
  {0xFE, 0x62, 0x68, 0x78, 0x68, 0x60, 0xF0, 0x00},
  {0x3C, 0x66, 0xC0, 0xC0, 0xCE, 0x66, 0x3E, 0x00},
  {0xCC, 0xCC, 0xCC, 0xFC, 0xCC, 0xCC, 0xCC, 0x00},
  {0x78, 0x30, 0x30, 0x30, 0x30, 0x30, 0x78, 0x00},
  {0x1E, 0x0C, 0x0C, 0x0C, 0xCC, 0xCC, 0x78, 0x00},
  {0xE6, 0x66, 0x6C, 0x78, 0x6C, 0x66, 0xE6, 0x00},
  {0xF0, 0x60, 0x60, 0x60, 0x62, 0x66, 0xFE, 0x00},
  {0xC6, 0xEE, 0xFE, 0xFE, 0xD6, 0xC6, 0xC6, 0x00},
  {0xC6, 0xE6, 0xF6, 0xDE, 0xCE, 0xC6, 0xC6, 0x00},
  {0x38, 0x6C, 0xC6, 0xC6, 0xC6, 0x6C, 0x38, 0x00},
  {0xFC, 0x66, 0x66, 0x7C, 0x60, 0x60, 0xF0, 0x00},
  {0x78, 0xCC, 0xCC, 0xCC, 0xDC, 0x78, 0x1C, 0x00},
  {0xFC, 0x66, 0x66, 0x7C, 0x6C, 0x66, 0xE6, 0x00},
  {0x78, 0xCC, 0xE0, 0x70, 0x1C, 0xCC, 0x78, 0x00},
  {0xFC, 0xB4, 0x30, 0x30, 0x30, 0x30, 0x78, 0x00},
  {0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xFC, 0x00},
  {0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x78, 0x30, 0x00},
  {0xC6, 0xC6, 0xC6, 0xD6, 0xFE, 0xEE, 0xC6, 0x00},
  {0xC6, 0xC6, 0x6C, 0x38, 0x38, 0x6C, 0xC6, 0x00},
  {0xCC, 0xCC, 0xCC, 0x78, 0x30, 0x30, 0x78, 0x00},
  {0xFE, 0xC6, 0x8C, 0x18, 0x32, 0x66, 0xFE, 0x00},
  {0x78, 0x60, 0x60, 0x60, 0x60, 0x60, 0x78, 0x00},
  {0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x02, 0x00},
  {0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x78, 0x00},
  {0x10, 0x38, 0x6C, 0xC6, 0x00, 0x00, 0x00, 0x00},
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF},
  {0x30, 0x30, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00},
  {0x00, 0x00, 0x78, 0x0C, 0x7C, 0xCC, 0x76, 0x00},
  {0xE0, 0x60, 0x60, 0x7C, 0x66, 0x66, 0xDC, 0x00},
  {0x00, 0x00, 0x78, 0xCC, 0xC0, 0xCC, 0x78, 0x00},
  {0x1C, 0x0C, 0x0C, 0x7C, 0xCC, 0xCC, 0x76, 0x00},
  {0x00, 0x00, 0x78, 0xCC, 0xFC, 0xC0, 0x78, 0x00},
  {0x38, 0x6C, 0x60, 0xF0, 0x60, 0x60, 0xF0, 0x00},
  {0x00, 0x00, 0x76, 0xCC, 0xCC, 0x7C, 0x0C, 0xF8},
  {0xE0, 0x60, 0x6C, 0x76, 0x66, 0x66, 0xE6, 0x00},
  {0x30, 0x00, 0x70, 0x30, 0x30, 0x30, 0x78, 0x00},
  {0x0C, 0x00, 0x0C, 0x0C, 0x0C, 0xCC, 0xCC, 0x78},
  {0xE0, 0x60, 0x66, 0x6C, 0x78, 0x6C, 0xE6, 0x00},
  {0x70, 0x30, 0x30, 0x30, 0x30, 0x30, 0x78, 0x00},
  {0x00, 0x00, 0xCC, 0xFE, 0xFE, 0xD6, 0xC6, 0x00},
  {0x00, 0x00, 0xF8, 0xCC, 0xCC, 0xCC, 0xCC, 0x00},
  {0x00, 0x00, 0x78, 0xCC, 0xCC, 0xCC, 0x78, 0x00},
  {0x00, 0x00, 0xDC, 0x66, 0x66, 0x7C, 0x60, 0xF0},
  {0x00, 0x00, 0x76, 0xCC, 0xCC, 0x7C, 0x0C, 0x1E},
  {0x00, 0x00, 0xDC, 0x76, 0x66, 0x60, 0xF0, 0x00},
  {0x00, 0x00, 0x7C, 0xC0, 0x78, 0x0C, 0xF8, 0x00},
  {0x10, 0x30, 0x7C, 0x30, 0x30, 0x34, 0x18, 0x00},
  {0x00, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0x76, 0x00},
  {0x00, 0x00, 0xCC, 0xCC, 0xCC, 0x78, 0x30, 0x00},
  {0x00, 0x00, 0xC6, 0xD6, 0xFE, 0xFE, 0x6C, 0x00},
  {0x00, 0x00, 0xC6, 0x6C, 0x38, 0x6C, 0xC6, 0x00},
  {0x00, 0x00, 0xCC, 0xCC, 0xCC, 0x7C, 0x0C, 0xF8},
  {0x00, 0x00, 0xFC, 0x98, 0x30, 0x64, 0xFC, 0x00},
  {0x1C, 0x30, 0x30, 0xE0, 0x30, 0x30, 0x1C, 0x00},
  {0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00},
  {0xE0, 0x30, 0x30, 0x1C, 0x30, 0x30, 0xE0, 0x00},
  {0x76, 0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
  {0x00, 0x10, 0x38, 0x6C, 0xC6, 0xC6, 0xFE, 0x00},
  {0x1E, 0x36, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x00},
  {0x7C, 0x60, 0x60, 0x7C, 0x66, 0x66, 0x7C, 0x00},
  {0x7C, 0x66, 0x66, 0x7C, 0x66, 0x66, 0x7C, 0x00},
  {0x7E, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x00},
  {0x38, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0xFE, 0xC6},
  {0x7E, 0x60, 0x60, 0x7C, 0x60, 0x60, 0x7E, 0x00},
  {0xDB, 0xDB, 0x7E, 0x3C, 0x7E, 0xDB, 0xDB, 0x00},
  {0xFE, 0x00, 0x00, 0xFE, 0x00, 0x00, 0xFE, 0x00},
  {0x66, 0x66, 0x6E, 0x7E, 0x76, 0x66, 0x66, 0x00},
  {0x3C, 0x66, 0x6E, 0x7E, 0x76, 0x66, 0x66, 0x00},
  {0x66, 0x6C, 0x78, 0x70, 0x78, 0x6C, 0x66, 0x00},
  {0x1E, 0x36, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00},
  {0xC6, 0xEE, 0xFE, 0xFE, 0xD6, 0xC6, 0xC6, 0x00},
  {0x66, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x00},
  {0x3C, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00},
  {0x7E, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00},
  {0x7E, 0x81, 0xA5, 0x81, 0xA5, 0x99, 0x81, 0x7E},
  {0x3C, 0x66, 0x60, 0x60, 0x60, 0x66, 0x3C, 0x00},
  {0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00},
  {0x66, 0x66, 0x66, 0x3E, 0x06, 0x66, 0x3C, 0x00},
  {0x7E, 0xDB, 0xDB, 0xDB, 0x7E, 0x18, 0x18, 0x00},
  {0x00, 0x38, 0x7C, 0x7C, 0x7C, 0x38, 0x00, 0x00},
  {0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7F, 0x03},
  {0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00},
  {0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xFF, 0x00},
  {0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xFF, 0x03},
  {0xE0, 0x60, 0x60, 0x7C, 0x66, 0x66, 0x7C, 0x00},
  {0xC6, 0xC6, 0xC6, 0xF6, 0xDE, 0xDE, 0xF6, 0x00},
  {0x60, 0x60, 0x60, 0x7C, 0x66, 0x66, 0x7C, 0x00},
  {0x78, 0x8C, 0x06, 0x3E, 0x06, 0x8C, 0x78, 0x00},
  {0xCE, 0xDB, 0xDB, 0xFB, 0xDB, 0xDB, 0xCE, 0x00},
  {0x3E, 0x66, 0x66, 0x66, 0x3E, 0x36, 0x66, 0x00},
  {0x00, 0x00, 0x78, 0x0C, 0x7C, 0xCC, 0x76, 0x00},
  {0x00, 0x3C, 0x60, 0x3C, 0x66, 0x66, 0x3C, 0x00},
  {0x00, 0x00, 0x7C, 0x66, 0x7C, 0x66, 0x7C, 0x00},
  {0x00, 0x00, 0x7E, 0x60, 0x60, 0x60, 0x60, 0x00},
  {0x00, 0x00, 0x3C, 0x6C, 0x6C, 0x6C, 0xFE, 0xC6},
  {0x00, 0x00, 0x3C, 0x66, 0x7E, 0x60, 0x3C, 0x00},
  {0x00, 0x00, 0xDB, 0x7E, 0x3C, 0x7E, 0xDB, 0x00},
  {0x00, 0x00, 0x3C, 0x66, 0x0C, 0x66, 0x3C, 0x00},
  {0x7E, 0x60, 0x60, 0x7E, 0x60, 0x60, 0x7E, 0x00},
  {0x00, 0x18, 0x66, 0x6E, 0x7E, 0x76, 0x66, 0x00},
  {0x00, 0x00, 0x66, 0x6C, 0x78, 0x6C, 0x66, 0x00},
  {0x00, 0x00, 0x1E, 0x36, 0x66, 0x66, 0x66, 0x00},
  {0x00, 0x00, 0xC6, 0xFE, 0xFE, 0xD6, 0xC6, 0x00},
  {0x00, 0x00, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x00},
  {0x00, 0x00, 0x3C, 0x66, 0x66, 0x66, 0x3C, 0x00},
  {0x00, 0x00, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x00},
  {0x11, 0x44, 0x11, 0x44, 0x11, 0x44, 0x11, 0x44},
  {0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA},
  {0xDD, 0x77, 0xDD, 0x77, 0xDD, 0x77, 0xDD, 0x77},
  {0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18},
  {0x18, 0x18, 0x18, 0xF8, 0x18, 0x18, 0x18, 0x18},
  {0x18, 0xF8, 0x18, 0xF8, 0x18, 0x18, 0x18, 0x18},
  {0x36, 0x36, 0x36, 0xF6, 0x36, 0x36, 0x36, 0x36},
  {0x00, 0x00, 0x00, 0xFE, 0x36, 0x36, 0x36, 0x36},
  {0x24, 0x00, 0x3C, 0x66, 0x7E, 0x60, 0x3C, 0x00},
  {0x8E, 0x88, 0xC8, 0xA8, 0x98, 0x88, 0x88, 0x00},
  {0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36},
  {0x00, 0xFE, 0x06, 0xF6, 0x36, 0x36, 0x36, 0x36},
  {0x36, 0xF6, 0x06, 0xFE, 0x00, 0x00, 0x00, 0x00},
  {0x36, 0x36, 0x36, 0xFE, 0x00, 0x00, 0x00, 0x00},
  {0x18, 0xF8, 0x18, 0xF8, 0x00, 0x00, 0x00, 0x00},
  {0x00, 0x00, 0x00, 0xF8, 0x18, 0x18, 0x18, 0x18},
  {0x1E, 0x36, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x00},
  {0x7C, 0x60, 0x60, 0x7C, 0x66, 0x66, 0x7C, 0x00},
  {0x7C, 0x66, 0x66, 0x7C, 0x66, 0x66, 0x7C, 0x00},
  {0x7E, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x00},
  {0x38, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0xFE, 0xC6},
  {0x7E, 0x60, 0x60, 0x7C, 0x60, 0x60, 0x7E, 0x00},
  {0xDB, 0xDB, 0x7E, 0x3C, 0x7E, 0xDB, 0xDB, 0x00},
  {0x3C, 0x66, 0x06, 0x1C, 0x06, 0x66, 0x3C, 0x00},
  {0x66, 0x66, 0x6E, 0x7E, 0x76, 0x66, 0x66, 0x00},
  {0x3C, 0x66, 0x6E, 0x7E, 0x76, 0x66, 0x66, 0x00},
  {0x66, 0x6C, 0x78, 0x70, 0x78, 0x6C, 0x66, 0x00},
  {0x1E, 0x36, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00},
  {0xC6, 0xEE, 0xFE, 0xFE, 0xD6, 0xC6, 0xC6, 0x00},
  {0x66, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x00},
  {0x3C, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00},
  {0x7E, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00},
  {0x7C, 0x66, 0x66, 0x66, 0x7C, 0x60, 0x60, 0x00},
  {0x3C, 0x66, 0x60, 0x60, 0x60, 0x66, 0x3C, 0x00},
  {0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00},
  {0x66, 0x66, 0x66, 0x3E, 0x06, 0x66, 0x3C, 0x00},
  {0x7E, 0xDB, 0xDB, 0xDB, 0x7E, 0x18, 0x18, 0x00},
  {0x66, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0x66, 0x00},
  {0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7F, 0x03},
  {0x66, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x06, 0x00},
  {0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xFF, 0x00},
  {0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xFF, 0x03},
  {0xE0, 0x60, 0x60, 0x7C, 0x66, 0x66, 0x7C, 0x00},
  {0xC6, 0xC6, 0xC6, 0xF6, 0xDE, 0xDE, 0xF6, 0x00},
  {0x60, 0x60, 0x60, 0x7C, 0x66, 0x66, 0x7C, 0x00},
  {0x78, 0x8C, 0x06, 0x3E, 0x06, 0x8C, 0x78, 0x00},
  {0xCE, 0xDB, 0xDB, 0xFB, 0xDB, 0xDB, 0xCE, 0x00},
  {0x3E, 0x66, 0x66, 0x66, 0x3E, 0x36, 0x66, 0x00},
  {0x00, 0x00, 0x78, 0x0C, 0x7C, 0xCC, 0x76, 0x00},
  {0x00, 0x3C, 0x60, 0x3C, 0x66, 0x66, 0x3C, 0x00},
  {0x00, 0x00, 0x7C, 0x66, 0x7C, 0x66, 0x7C, 0x00},
  {0x00, 0x00, 0x7E, 0x60, 0x60, 0x60, 0x60, 0x00},
  {0x00, 0x00, 0x3C, 0x6C, 0x6C, 0x6C, 0xFE, 0xC6},
  {0x00, 0x00, 0x3C, 0x66, 0x7E, 0x60, 0x3C, 0x00},
  {0x00, 0x00, 0xDB, 0x7E, 0x3C, 0x7E, 0xDB, 0x00},
  {0x00, 0x00, 0x3C, 0x66, 0x0C, 0x66, 0x3C, 0x00},
  {0x00, 0x00, 0x66, 0x6E, 0x7E, 0x76, 0x66, 0x00},
  {0x00, 0x18, 0x66, 0x6E, 0x7E, 0x76, 0x66, 0x00},
  {0x00, 0x00, 0x66, 0x6C, 0x78, 0x6C, 0x66, 0x00},
  {0x00, 0x00, 0x1E, 0x36, 0x66, 0x66, 0x66, 0x00},
  {0x00, 0x00, 0xC6, 0xFE, 0xFE, 0xD6, 0xC6, 0x00},
  {0x00, 0x00, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x00},
  {0x00, 0x00, 0x3C, 0x66, 0x66, 0x66, 0x3C, 0x00},
  {0x00, 0x00, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x00},
  {0x00, 0x00, 0x7C, 0x66, 0x66, 0x7C, 0x60, 0x00},
  {0x00, 0x00, 0x3C, 0x66, 0x60, 0x66, 0x3C, 0x00},
  {0x00, 0x00, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x00},
  {0x00, 0x00, 0x66, 0x66, 0x3E, 0x06, 0x3C, 0x00},
  {0x00, 0x00, 0x7E, 0xDB, 0xDB, 0x7E, 0x18, 0x00},
  {0x00, 0x00, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0x00},
  {0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x7F, 0x03},
  {0x00, 0x00, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x00},
  {0x00, 0x00, 0xDB, 0xDB, 0xDB, 0xDB, 0xFF, 0x00},
  {0x00, 0x00, 0xDB, 0xDB, 0xDB, 0xDB, 0xFF, 0x03},
  {0x00, 0x00, 0xE0, 0x60, 0x7C, 0x66, 0x7C, 0x00},
  {0x00, 0x00, 0xC6, 0xC6, 0xF6, 0xDE, 0xF6, 0x00},
  {0x00, 0x00, 0x60, 0x60, 0x7C, 0x66, 0x7C, 0x00},
  {0x00, 0x00, 0x7C, 0x06, 0x3E, 0x06, 0x7C, 0x00},
  {0x00, 0x00, 0xCE, 0xDB, 0xFB, 0xDB, 0xCE, 0x00},
  {0x00, 0x00, 0x3E, 0x66, 0x3E, 0x36, 0x66, 0x00}};

/************************ (C) COPYRIGHT andycat2013@yandex.ru *****END OF FILE****/

unsigned char fontSizeX[4] = {fontWsize0, fontWsize1, fontWsize2, fontWsize3};
unsigned char fontSizeY[4] = {fontHsize0, fontHsize1, fontHsize2, fontHsize3};

void tftCSenable(void) {GPIOB->BSRR |= GPIO_BSRR_BR11;} // PB11 LOW // говорим slave устройству что начинаем работать
void tftCSdisable(void) {GPIOB->BSRR |= GPIO_BSRR_BS11;} // PB11 HIGH // закончили передачу SPI устройству

unsigned char nextXchar = 0;
unsigned char nextYchar = 0;
unsigned short nextColor = ST77XX_WHITE;
unsigned short nextBack = ST77XX_BLACK;
unsigned char nextSize = 0;

/*
 * http://dimoon.ru/obuchalka/stm32f1/programmirovanie-stm32-chast-6-spi.html?ysclid=lik3u6uz6b459087878
 */
void initSPI(void) {
	//Включаем тактирование SPI1 и GPIOA
	RCC->APB2ENR |= RCC_APB2ENR_SPI1EN | RCC_APB2ENR_IOPAEN;
	//Для начала сбрасываем все конфигурационные биты в нули
	GPIOA->CRL &= ~(GPIO_CRL_CNF5 | GPIO_CRL_MODE5 | GPIO_CRL_CNF6 | GPIO_CRL_MODE6 | GPIO_CRL_CNF7 | GPIO_CRL_MODE7);
	//Настроаиваем
	//SCK: MODE5 = 0x03 (11b); CNF5 = 0x02 (10b)
	GPIOA->CRL |= GPIO_CRL_CNF5_1 | GPIO_CRL_MODE5;
	//MISO: MODE6 = 0x00 (00b); CNF6 = 0x01 (01b)
	GPIOA->CRL |= GPIO_CRL_CNF6_0;
	//MOSI: MODE7 = 0x03 (11b); CNF7 = 0x02 (10b)
	GPIOA->CRL |= GPIO_CRL_CNF7_1 | GPIO_CRL_MODE7;
	// Вывод NSS не трогаем, так как не будем его использовать. Далее, настройка SPI:
	SPI1->CR1 &= ~SPI_CR1_DFF; //Размер кадра 8 бит
	SPI1->CR1 &= ~SPI_CR1_LSBFIRST; //MSB first
	SPI1->CR1 |= SPI_CR1_SSM; //Программное управление SS
	SPI1->CR1 |= SPI_CR1_SSI; //SS в высоком состоянии
	SPI1->CR1 &= ~SPI_CR1_BR; // reset // 0x00 Скорость передачи: F_PCLK/2
	//SPI1->CR1 |= SPI_CR1_BR_2; // 0x04 Скорость передачи: F_PCLK/32
	SPI1->CR1 |= SPI_CR1_MSTR; //Режим Master (ведущий)
	SPI1->CR1 &= ~SPI_CR1_CPOL; //Режим работы SPI: 0
	SPI1->CR1 &= ~SPI_CR1_CPHA; //Режим работы SPI: 0
	SPI1->CR1 |= SPI_CR1_SPE; //Включаем SPI
}

void f1tftSendCommand(unsigned char cmd) {
	GPIOB->BSRR |= GPIO_BSRR_BR10; // PB10 LOW
	SPI1->DR = cmd;
	while (!(SPI1->SR & SPI_SR_TXE) || (SPI1->SR & SPI_SR_BSY));
}

void f1tftWriteData(unsigned char * buff, unsigned short buff_size) {
	GPIOB->BSRR |= GPIO_BSRR_BS10; // PB10 HIGH
	for(unsigned short i = 0; i < buff_size; ++i) {
		while (!(SPI1->SR & SPI_SR_TXE));
		SPI1->DR = *(buff+i);
	}
	while (!(SPI1->SR & SPI_SR_TXE) || (SPI1->SR & SPI_SR_BSY));
}

void f1tftSetAddrWindow(unsigned short x0, unsigned short y0, unsigned short x1, unsigned short y1) {
	static unsigned char pos_data[4];
	// column address set
	f1tftSendCommand(ST77XX_CASET); // CASET
	pos_data[0] = (x0 & 0xFF00) >> 8;
	pos_data[1] = x0 & 0x00FF;
	pos_data[2] = (x1 & 0xFF00) >> 8;
	pos_data[3] = x1 & 0x00FF;
	f1tftWriteData((unsigned char *)&pos_data, 4);
	// row address set
	f1tftSendCommand(ST77XX_RASET); // RASET
	pos_data[0] = (y0 & 0xFF00) >> 8;
	pos_data[1] = y0 & 0x00FF;
	pos_data[2] = (y1 & 0xFF00) >> 8;
	pos_data[3] = y1 & 0x00FF;
	f1tftWriteData((unsigned char *)&pos_data, 4);
	// write to RAM
	f1tftSendCommand(ST77XX_RAMWR); // RAMWR
}

void f103st7735fillRect(uint16_t value, unsigned short x0, unsigned short y0, unsigned short x1, unsigned short y1) {
	tftCSenable(); // chip select on
	f1tftSetAddrWindow(x0, y0, x1, y1);
    GPIOB->BSRR |= GPIO_BSRR_BS10; // PB10 HIGH
    // 8 bit transfer mode
    /*uint8_t c1=value>>8;
    uint8_t c2=(uint8_t)value;
    for (uint16_t i=0; i < (uint16_t)((x1-x0+1)*(y1-y0+1)); i++) {
		while (!(SPI1->SR & SPI_SR_TXE));
		SPI1->DR = c1;
		while (!(SPI1->SR & SPI_SR_TXE));
		SPI1->DR = c2;
    }*/
    // 16 bit transfer mode
	SPI1->CR1 &= ~SPI_CR1_SPE;
	SPI1->CR1 |= SPI_CR1_DFF;
	SPI1->CR1 |= SPI_CR1_SPE;
    for (uint16_t i=0; i < (uint16_t)((x1-x0+1)*(y1-y0+1)); i++) {
		while (!(SPI1->SR & SPI_SR_TXE));
		SPI1->DR = value;
    }
    // wait end transfer
	while (!(SPI1->SR & SPI_SR_TXE) || (SPI1->SR & SPI_SR_BSY));
    // return 8bit transfer mode
	SPI1->CR1 &= ~SPI_CR1_SPE;
	SPI1->CR1 &= ~SPI_CR1_DFF;
	SPI1->CR1 |= SPI_CR1_SPE;
	tftCSdisable(); // chip select off
}

void f103st7735showRect(unsigned short * dataPoint, unsigned short x0, unsigned short y0, unsigned short x1, unsigned short y1) {
	tftCSenable(); // chip select on
	f1tftSetAddrWindow(x0, y0, x1, y1);
    GPIOB->BSRR |= GPIO_BSRR_BS10; // PB10 HIGH
    // 16 bit transfer mode
	SPI1->CR1 &= ~SPI_CR1_SPE;
	SPI1->CR1 |= SPI_CR1_DFF;
	SPI1->CR1 |= SPI_CR1_SPE;
    for (uint16_t i=0; i < (uint16_t)((x1-x0+1)*(y1-y0+1)); i++) {
		while (!(SPI1->SR & SPI_SR_TXE));
		SPI1->DR = *(dataPoint+i);
    }
    // wait end transfer
	while (!(SPI1->SR & SPI_SR_TXE) || (SPI1->SR & SPI_SR_BSY));
    // return 8bit transfer mode
	SPI1->CR1 &= ~SPI_CR1_SPE;
	SPI1->CR1 &= ~SPI_CR1_DFF;
	SPI1->CR1 |= SPI_CR1_SPE;
	tftCSdisable(); // chip select off
}

void f103st7735fillScreen(uint16_t value) {
	f103st7735fillRect(value, 0, 0, st7735sizeX-1, st7735sizeY-1);
}

void initGpio(void) {
	// PB1 - RESET ST7735
	RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; // GPIO port B
	GPIOB->CRL &= ~GPIO_CRL_MODE1; // 00: Input mode (reset state)
	GPIOB->CRL |= GPIO_CRL_MODE1_0; // 01: Output mode, max speed 10 MHz.
	GPIOB->CRL &= ~GPIO_CRL_CNF1; // 00: General purpose output push-pull
	// PB10 - RS(A0) ST7735 cmd/data select
	GPIOB->CRH &= ~GPIO_CRH_MODE10; // 00: Input mode (reset state)
	GPIOB->CRH |= GPIO_CRH_MODE10_0; // 01: Output mode, max speed 10 MHz.
	GPIOB->CRH &= ~GPIO_CRH_CNF10; // 00: General purpose output push-pull
	// PB11 - CS ST7735
	GPIOB->CRH &= ~GPIO_CRH_MODE11; // 00: Input mode (reset state)
	GPIOB->CRH |= GPIO_CRH_MODE11_0; // 01: Output mode, max speed 10 MHz.
	GPIOB->CRH &= ~GPIO_CRH_CNF11; // 00: General purpose output push-pull
}

void f103st7735init(void) {
	initGpio();
	tftCSdisable(); // chip select off
	initSPI();
	// hardware reset
	GPIOB->BSRR |= GPIO_BSRR_BR1; // PB1 LOW
	delay_ms(10);
	GPIOB->BSRR |= GPIO_BSRR_BS1; // PB1 HIGH
	delay_ms(10);
	// init routine
	tftCSenable();
	unsigned char dataSend[16];
	f1tftSendCommand(ST77XX_SWRESET); // 0x01
	delay_ms(150);
	f1tftSendCommand(ST77XX_SLPOUT); // 0x11
	delay_ms(150);
	f1tftSendCommand(ST7735_FRMCTR1); // 0xB1
	dataSend[0] = 0x01;
	dataSend[1] = 0x2C;
	dataSend[2] = 0x2D;
	f1tftWriteData((unsigned char *)&dataSend, 3);
	f1tftSendCommand(ST7735_FRMCTR2);
	dataSend[0] = 0x01;
	dataSend[1] = 0x2C;
	dataSend[2] = 0x2D;
	f1tftWriteData((unsigned char *)&dataSend, 3);
    f1tftSendCommand(ST7735_FRMCTR3);
	dataSend[0] = 0x01;
	dataSend[1] = 0x2C;
	dataSend[2] = 0x2D;
	dataSend[3] = 0x01;
	dataSend[4] = 0x2C;
	dataSend[5] = 0x2D;
	f1tftWriteData((unsigned char *)&dataSend, 6);
    f1tftSendCommand(ST7735_INVCTR);
	dataSend[0] = 0x07;
	f1tftWriteData((unsigned char *)&dataSend, 1);
    f1tftSendCommand(ST7735_PWCTR1);
	dataSend[0] = 0xA2;
	dataSend[1] = 0x02;
	dataSend[2] = 0x84;
	f1tftWriteData((unsigned char *)&dataSend, 3);
    f1tftSendCommand(ST7735_PWCTR2);
	dataSend[0] = 0xC5;
	f1tftWriteData((unsigned char *)&dataSend, 1);
	f1tftSendCommand(ST7735_PWCTR3);
	dataSend[0] = 0x0A;
	dataSend[1] = 0x00;
	f1tftWriteData((unsigned char *)&dataSend, 2);
    f1tftSendCommand(ST7735_PWCTR4);
	dataSend[0] = 0x8A;
	dataSend[1] = 0x2A;
	f1tftWriteData((unsigned char *)&dataSend, 2);
    f1tftSendCommand(ST7735_PWCTR5);
	dataSend[0] = 0x8A;
	dataSend[1] = 0xEE;
	f1tftWriteData((unsigned char *)&dataSend, 2);
    f1tftSendCommand(ST7735_VMCTR1);
	dataSend[0] = 0x0E;
	f1tftWriteData((unsigned char *)&dataSend, 1);
	f1tftSendCommand(ST77XX_INVOFF);
	f1tftSendCommand(ST77XX_MADCTL);
	dataSend[0] = 0x00; // rotation 180
	//dataSend[0] = 0xC0; // original string
	f1tftWriteData((unsigned char *)&dataSend, 1);
    f1tftSendCommand(ST77XX_COLMOD);
	dataSend[0] = 0x05;
	f1tftWriteData((unsigned char *)&dataSend, 1);
    f1tftSendCommand(ST7735_GMCTRP1);
    dataSend[0] = 0x02;
    dataSend[1] = 0x1C;
    dataSend[2] = 0x07;
    dataSend[3] = 0x12;
    dataSend[4] = 0x37;
    dataSend[5] = 0x32;
    dataSend[6] = 0x29;
    dataSend[7] = 0x2D;
    dataSend[8] = 0x29;
    dataSend[9] = 0x25;
    dataSend[10] = 0x2B;
    dataSend[11] = 0x39;
    dataSend[12] = 0x00;
    dataSend[13] = 0x01;
    dataSend[14] = 0x03;
    dataSend[15] = 0x10;
	f1tftWriteData((unsigned char *)&dataSend, 16);
	f1tftSendCommand(ST7735_GMCTRN1);
	dataSend[0] = 0x03;
	dataSend[1] = 0x1D;
	dataSend[2] = 0x07;
	dataSend[3] = 0x06;
	dataSend[4] = 0x2E;
	dataSend[5] = 0x2C;
	dataSend[6] = 0x29;
	dataSend[7] = 0x2D;
	dataSend[8] = 0x2E;
	dataSend[9] = 0x2E;
	dataSend[10] = 0x37;
	dataSend[11] = 0x3F;
	dataSend[12] = 0x00;
	dataSend[13] = 0x00;
	dataSend[14] = 0x02;
	dataSend[15] = 0x10;
	f1tftWriteData((unsigned char *)&dataSend, 16);
	f1tftSendCommand(ST77XX_NORON);
	delay_ms(10);
    f1tftSendCommand(ST77XX_DISPON);
    delay_ms(100);
	tftCSdisable();
}

void f103st7735printOneChar(unsigned char inChar, unsigned short colChar, unsigned short colBackground, unsigned char sizeChar, unsigned char xChar, unsigned char yChar) {
	unsigned char inSize = sizeChar & 0x03; // size 0...3
	unsigned short oneCharBuf[fontWsize3 * fontHsize3];
	unsigned short locPosInBuf = 0; // абсолютная позиция в буфере где мы формируем картинку
	unsigned short outColor;
	for(unsigned char lineY = 0; lineY < fontSizeY[0]; ++lineY) { // цикл по строкам шрифта
		for(unsigned char bitY = fontSizeX[0]; bitY > 0; --bitY) { // обратный цикл по горизонтальным битам строки шрифта
			if (bitRead(frus08x08[inChar][lineY], (bitY-1))) outColor = colChar; else outColor = colBackground;
			if (inSize == 0) { // самый маленький шрифт 8 на 8
				oneCharBuf[locPosInBuf] = outColor;
				++locPosInBuf;
			} else { // необходимо масштабирование
				unsigned char countHpoints; // количество точек по горизонтали
				switch (inSize) {
					case 1: {
						if (bitRead(bitY, 0)) countHpoints = 1; else countHpoints = 2;
						break;
					}
					case 2: {
						if ((bitY == 7) || (bitY == 3))  countHpoints = 3; else countHpoints = 2;
						break;
					}
					default: { // 3 size
						countHpoints = 3;
					}
				}
				for (unsigned char v = 0; v < countHpoints; ++v) { // сколько точек надо нарисовать в строке
					for (unsigned char h = 0; h < (inSize+1); ++h) { // дублируем вниз точку на нужное количество увеличения шрифта
						oneCharBuf[locPosInBuf + h * fontSizeX[inSize]] = outColor;
					}
					++locPosInBuf;
				}
			}
		}
		locPosInBuf += fontSizeX[inSize] * inSize;
	}
	f103st7735showRect((unsigned short *)&oneCharBuf, xChar, yChar, xChar+fontSizeX[inSize]-1, yChar+fontSizeY[inSize]-1);
	nextSize = inSize;
	nextColor = colChar;
	nextBack = colBackground;
	nextXchar = xChar+fontSizeX[inSize];
	nextYchar = yChar;
	if (nextXchar >= st7735sizeX) {
		nextXchar = 0;
		nextYchar += fontSizeY[inSize];
		if (nextYchar >= st7735sizeY) nextYchar = 0;
	}
}

void f103st7735printString(const unsigned char * inStr, unsigned short colChar, unsigned short colBackground, unsigned char sizeChar, unsigned char xChar, unsigned char yChar) {
	unsigned char inSize = sizeChar & 0x03; // size 0...3
	unsigned char posStr = 0;
	unsigned char inChar;
	while ((inChar = *(inStr + posStr)) > 0) {
		f103st7735printOneChar(inChar, colChar, colBackground, inSize, xChar + fontSizeX[inSize] * posStr, yChar);
		++posStr;
	}
}

void f103st7735printUInt(unsigned long inVal, unsigned short colChar, unsigned short colBackground, unsigned char sizeChar, unsigned char xChar, unsigned char yChar) {
	unsigned char outStr[32];
	utoa(inVal, (char *)outStr, 10);
	f103st7735printString((unsigned char *)&outStr, colChar, colBackground, sizeChar, xChar, yChar);
}

void f103st7735printHEX(unsigned long inVal, unsigned short colChar, unsigned short colBackground, unsigned char sizeChar, unsigned char xChar, unsigned char yChar) {
	unsigned char outStr[32];
	utoa(inVal, (char *)outStr, 16);
	f103st7735printString((unsigned char *)&outStr, colChar, colBackground, sizeChar, xChar, yChar);
}

void f103st7735printText(const unsigned char * inText) {
	unsigned char posStr = 0;
	unsigned char inChar;
	while ((inChar = *(inText + posStr)) > 0) {
		f103st7735printOneChar(inChar, nextColor, nextBack, nextSize, nextXchar, nextYchar);
		++posStr;
	}
}


Спойлер
/*
 * pumper.h
 *
 *  Created on: 13 июн. 2023 г.
 *      Author: seleznev_a
 *
 *      PB14 - key 1
 *      PB15 - key 2
 *      PA11 - relay1
 *      PA12 - relay2
 *
 */

#ifndef PUMPER_H_
#define PUMPER_H_

class ifaceItem { // интерфейс элемента внутри группы, например текущий день недели, или часы полива
private:
protected:
	ifaceItem * parentItem;
	ifaceItem * childItems[32];
	unsigned char noEditItem = 0;
	unsigned short currentValue = 0;
	unsigned short minValue = 0;
	unsigned short maxValue = 1;
	unsigned short countChildren = 0;
	unsigned long blinkTimer;
	unsigned char modeShow = 0; // режим отображения 0-показывается 1+2-моргает
public:
	unsigned short posX, posY, sizeX, sizeY, backgroundColor = 0x0000;
    void addChild(ifaceItem * newChild) {this->childItems[this->countChildren] = newChild; ++this->countChildren;}
    ifaceItem * getChild(unsigned char posChild) {return this->childItems[posChild];}
    unsigned char getCountChildren(void) {return this->countChildren;}
    void setNoEditedItem(void) {this->noEditItem = 1;}
    unsigned char getNoEditedItem(void) {return this->noEditItem;}
    unsigned char getValue(void) {return this->currentValue;}
    virtual void showItem(void){}; // отобразить элемент
    virtual void changeValue() {++this->currentValue; if (this->currentValue > this->maxValue) this->currentValue = this->minValue;} // изменить значение элемента
    virtual void blinkItem(); // мигает позиция
    virtual void clearItem() {}; // очищается позиция
    virtual void setValue(unsigned short inValue) {this->currentValue = inValue;}
};

class baseUnit:public ifaceItem { // группа
private:
protected:
	unsigned short iX, iY, itemColor = 0x0000;
	unsigned char fontSize;
public:
    baseUnit(ifaceItem * itemParent = nullptr, unsigned short pX = 0, unsigned short pY = 0, unsigned short sX = 32, unsigned short sY = 64, unsigned short bColor =  0x00);
    virtual void showItem(void) override;
    virtual void clearItem(void) override;
};

class baseOneCharValue:public baseUnit { // элемент - один символ
private:
protected:
public:
	baseOneCharValue(ifaceItem * itemParent = nullptr, unsigned short pX = 0, unsigned short pY = 0, unsigned char iSize = 0, unsigned short iColor =  0x00, unsigned short mV = 0, unsigned short xV = 1);
    virtual void showItem(void) override;
};

class baseNum2digValue:public baseOneCharValue { // элемент - число из двух цифр
private:
protected:
public:
	baseNum2digValue(ifaceItem * itemParent = nullptr, unsigned short pX = 0, unsigned short pY = 0, unsigned char iSize = 0, unsigned short iColor =  0x00, unsigned short mV = 0, unsigned short xV = 1):
		baseOneCharValue(itemParent, pX, pY, iSize, iColor, mV, xV) {};
    virtual void showItem(void) override;
    virtual void clearItem(void) override;
};

class baseDictWDayValue:public baseOneCharValue { // элемент - техт из справочника день недели
private:
protected:
	unsigned char * textWDay[9] = {(unsigned char *)"Sunday", (unsigned char *)"Monday", (unsigned char *)"Tuesday", (unsigned char *)"Wednesday", (unsigned char *)"Thursday", (unsigned char *)"Friday", (unsigned char *)"Saturday", (unsigned char *)"         ", (unsigned char *)"-no set- "};
public:
	baseDictWDayValue(ifaceItem * itemParent = nullptr, unsigned short pX = 0, unsigned short pY = 0, unsigned char iSize = 0, unsigned short iColor =  0x00, unsigned short mV = 0, unsigned short xV = 1):
		baseOneCharValue(itemParent, pX, pY, iSize, iColor, mV, xV) {};
    virtual void showItem(void) override;
    virtual void clearItem(void) override;
};

class baseDictMonthValue:public baseOneCharValue { // элемент - техт из справочника месяцы
private:
	unsigned char * textMonths[14] = {(unsigned char *)"January  ", (unsigned char *)"February ", (unsigned char *)"March    ", (unsigned char *)"April    ", (unsigned char *)"May      ", (unsigned char *)"June     ", (unsigned char *)"July     ", (unsigned char *)"August   ", (unsigned char *)"September", (unsigned char *)"October  ", (unsigned char *)"November ", (unsigned char *)"December ", (unsigned char *)"         ", (unsigned char *)"-no set- "};
protected:
public:
	baseDictMonthValue(ifaceItem * itemParent = nullptr, unsigned short pX = 0, unsigned short pY = 0, unsigned char iSize = 0, unsigned short iColor =  0x00, unsigned short mV = 0, unsigned short xV = 1):
		baseOneCharValue(itemParent, pX, pY, iSize, iColor, mV, xV) {};
    virtual void showItem(void) override;
    virtual void clearItem(void) override;
};

class baseDictPumpDaysValue:public baseDictWDayValue { // элемент - по два символа из дней недели - дни полива, value подсвеченный выбранный день
private:
	unsigned char weekDay = 0; // день недели 0...6
	unsigned short colorDay = 0x5555; // цвет выбранного дня
	unsigned short colorLabel = 0xAAAA; // цвет фона выбранного дня
protected:
public:
	baseDictPumpDaysValue(ifaceItem * itemParent = nullptr, unsigned short pX = 0, unsigned short pY = 0, unsigned char iSize = 0, unsigned short iColor =  0x00, unsigned char inDay = 0, unsigned short daColor = 0xAAAA, unsigned short lbColor = 0x5555);
    virtual void showItem(void) override;
    virtual void clearItem(void) override;
};

class baseNum2digText:public baseNum2digValue { // элемент - число из двух цифр + текст
private:
	unsigned char * endText;
protected:
public:
	baseNum2digText(ifaceItem * itemParent = nullptr, unsigned short pX = 0, unsigned short pY = 0, unsigned char iSize = 0, unsigned short iColor =  0x00, unsigned char * inText = nullptr, unsigned short mV = 0, unsigned short xV = 1);
    virtual void showItem(void) override;
    virtual void clearItem(void) override;
};

void loopPumper(void);
void f1InitPumpGpio(void);
void initPumper(void);

#define blinkPeriod 300UL

#define keys_short_press 50UL
#define keys_long_press 2000UL

#define numParamPump1Days 1
#define numParamPump2Days 3
#define numParamTimePump 5

union t_PumpTimePart {
    struct {
    	unsigned char pumpTime[2];
    	unsigned char pumpPeriod;
    };
    unsigned long PartPumpTime;
};

union t_PumpDaysPart {
    struct {
    	unsigned char pumpDays[4];
    };
    unsigned long PartPumpDays;
};

#define currentWDayColor ST7735_MAGENTA
#define currentWDayBack ST7735_BLACK
#define currentWDaySize 0

#define currentDateColor ST7735_GREEN
#define currentDateSize 0

#define currentTimeColor ST7735_YELLOW
#define currentTimeSize 2
#define currentTimeStartX 18

#define pumpBackground ST7735_WHITE
#define pumpDaysColor ST7735_BLACK
#define pumpSelectedDay ST7735_YELLOW
#define pumpSelectedBack ST7735_BLUE
#define pumpDaysSize 0

#define pumpTimeColor ST7735_GREEN
#define pumpTimeSize 2

#define pumpPeriodColor ST7735_ORANGE
#define pumpPeriodBack ST7735_BLUE
#define pumpPeriodSize 2

#endif /* PUMPER_H_ */



/*
 * pumper.c
 *
 *  Created on: 13 июн. 2023 г.
 *      Author: seleznev_a
 */

#include "pumper.h"
#include "stm32f10x.h"
#include <time.h>
#include "f103c8t6.h"
#include "f103st7735.h"
#include "stdlib.h"
#include "f103flash.h"
#include "f103rtc.h"

t_PumpDaysPart saveParamDays1partPump = {0, 0, 1, 0};
t_PumpDaysPart saveParamDays2partPump = {1, 0, 0, 0xFF};
t_PumpTimePart saveParamTime3partPump = {20, 18, 5};

unsigned char devModes = 0; // 1-settings mode
unsigned long unusedDevice; // таймер бездействия пользователя

baseUnit iMainDisplay;

//--current date time
baseUnit iCurrentDateTime((ifaceItem *)&iMainDisplay, 0, 0,
		st7735sizeX, fontSizeY[currentWDaySize]+fontSizeY[currentDateSize]+fontSizeY[currentTimeSize], currentWDayBack);
baseDictWDayValue iDayOfWeekCurrentDateTime((ifaceItem *)&iCurrentDateTime, 0, 0,
		currentWDaySize, currentWDayColor, 0, 6);
baseNum2digValue iHourTimeCurrentDateTime((ifaceItem *)&iCurrentDateTime, currentTimeStartX,
		fontSizeY[currentWDaySize], currentTimeSize, currentTimeColor, 0, 23);
baseOneCharValue iPointTimeCurrentDateTime((ifaceItem *)&iCurrentDateTime, currentTimeStartX + fontSizeX[currentTimeSize] * 2, fontSizeY[currentWDaySize],
		currentTimeSize, currentTimeColor);
baseNum2digValue iMinTimeCurrentDateTime((ifaceItem *)&iCurrentDateTime, currentTimeStartX + fontSizeX[currentTimeSize] * 3, fontSizeY[currentWDaySize],
		currentTimeSize, currentTimeColor, 0, 59);
baseNum2digValue iMDayCurrentDateTime((ifaceItem *)&iCurrentDateTime, 0, fontSizeY[currentWDaySize]+fontSizeY[currentTimeSize],
		currentDateSize, currentDateColor, 1, 31);
baseDictMonthValue iMonthCurrentDateTime((ifaceItem *)&iCurrentDateTime, fontSizeX[currentDateSize] * 3, fontSizeY[currentWDaySize]+fontSizeY[currentTimeSize],
		currentDateSize, currentDateColor, 0, 11);
baseNum2digValue iYearCurrentDateTime((ifaceItem *)&iCurrentDateTime, fontSizeX[currentDateSize] * 13, fontSizeY[currentWDaySize]+fontSizeY[currentTimeSize],
		currentDateSize, currentDateColor, 23, 30);

//-- pump data
baseUnit iPumperDaysTime((ifaceItem *)&iMainDisplay, 0, fontSizeY[currentWDaySize]+fontSizeY[currentDateSize]+fontSizeY[currentTimeSize],
		st7735sizeX, fontSizeY[currentWDaySize]+fontSizeY[currentDateSize]+fontSizeY[currentTimeSize]+fontSizeY[pumpDaysSize]*2+fontSizeY[pumpTimeSize]+fontSizeY[0]*2,
		pumpBackground);
baseDictPumpDaysValue i1DayPumper((ifaceItem *)&iPumperDaysTime, fontSizeX[0], fontSizeY[0], pumpDaysSize, pumpDaysColor, 1, pumpSelectedDay, pumpSelectedBack);
baseDictPumpDaysValue i2DayPumper((ifaceItem *)&iPumperDaysTime, fontSizeX[0]+fontSizeX[pumpDaysSize]*4, fontSizeY[0], pumpDaysSize, pumpDaysColor, 2, pumpSelectedDay, pumpSelectedBack);
baseDictPumpDaysValue i3DayPumper((ifaceItem *)&iPumperDaysTime, fontSizeX[0]+fontSizeX[pumpDaysSize]*8, fontSizeY[0], pumpDaysSize, pumpDaysColor, 3, pumpSelectedDay, pumpSelectedBack);
baseDictPumpDaysValue i4DayPumper((ifaceItem *)&iPumperDaysTime, fontSizeX[0]+fontSizeX[pumpDaysSize]*12, fontSizeY[0], pumpDaysSize, pumpDaysColor, 4, pumpSelectedDay, pumpSelectedBack);
baseDictPumpDaysValue i5DayPumper((ifaceItem *)&iPumperDaysTime, fontSizeX[0]+fontSizeX[pumpDaysSize]*2, fontSizeY[0]+fontSizeY[pumpDaysSize], pumpDaysSize, pumpDaysColor, 5, pumpSelectedDay, pumpSelectedBack);
baseDictPumpDaysValue i6DayPumper((ifaceItem *)&iPumperDaysTime, fontSizeX[0]+fontSizeX[pumpDaysSize]*6, fontSizeY[0]+fontSizeY[pumpDaysSize], pumpDaysSize, pumpDaysColor, 6, pumpSelectedDay, pumpSelectedBack);
baseDictPumpDaysValue i0DayPumper((ifaceItem *)&iPumperDaysTime, fontSizeX[0]+fontSizeX[pumpDaysSize]*10, fontSizeY[0]+fontSizeY[pumpDaysSize], pumpDaysSize, pumpDaysColor, 0, pumpSelectedDay, pumpSelectedBack);
baseNum2digValue iHourPumpTime((ifaceItem *)&iPumperDaysTime, currentTimeStartX, fontSizeY[0]+fontSizeY[pumpDaysSize]*2,
		pumpTimeSize, pumpTimeColor, 8, 22);
baseOneCharValue iPointPumpTime((ifaceItem *)&iPumperDaysTime, currentTimeStartX + fontSizeX[pumpTimeSize] * 2, fontSizeY[0]+fontSizeY[pumpDaysSize]*2,
		pumpTimeSize, pumpTimeColor);
baseNum2digValue iMinPumpTime((ifaceItem *)&iPumperDaysTime, currentTimeStartX + fontSizeX[pumpTimeSize] * 3, fontSizeY[0]+fontSizeY[pumpDaysSize]*2,
		pumpTimeSize, pumpTimeColor, 0, 59);

//--period pump
baseUnit iPeriodTime((ifaceItem *)&iMainDisplay, 0,
		fontSizeY[currentWDaySize]+fontSizeY[currentDateSize]+fontSizeY[currentTimeSize]+
			fontSizeY[pumpDaysSize]*2+fontSizeY[pumpTimeSize]+fontSizeY[0]*2,
		st7735sizeX,
		fontSizeY[currentWDaySize]+fontSizeY[currentDateSize]+fontSizeY[currentTimeSize]+
			fontSizeY[pumpDaysSize]*2+fontSizeY[pumpTimeSize]+fontSizeY[0]*2+
				fontSizeY[pumpPeriodSize]+fontSizeY[0]*2,
		pumpPeriodBack);
baseNum2digText iPeriodPump((ifaceItem *)&iPeriodTime, 0, fontSizeY[0],
		pumpPeriodSize, pumpPeriodColor, (unsigned char *)" min", 5, 40);

baseUnit::baseUnit(ifaceItem * itemParent, unsigned short pX, unsigned short pY, unsigned short sX, unsigned short sY, unsigned short bColor) {
	this->countChildren = 0;
	this->parentItem = itemParent;
	if (itemParent != nullptr) {
		this->posX = pX;
		this->posY = pY;
		this->sizeX = sX;
		this->sizeY = sY;
		this->backgroundColor = bColor;
		this->modeShow = 0;
	}
}

void ifaceItem::blinkItem(void) {
	if ((millis() - this->blinkTimer) >= blinkPeriod) {
		this->blinkTimer = millis();
		if ((this->modeShow == 0) || (this->modeShow == 1)) this->clearItem(); else this->showItem();
	}
}

void baseUnit::clearItem(void) {
	this->modeShow = 2;
	f103st7735fillRect(this->backgroundColor, this->posX, this->posY, this->sizeX-1, this->sizeY-1);
}

void baseUnit::showItem(void) {
	this->modeShow = 0;
	f103st7735fillRect(this->backgroundColor, this->posX, this->posY, this->sizeX-1, this->sizeY-1);
	if (this->countChildren) for(unsigned char i=0; i<this->countChildren; ++i) this->childItems[i]->showItem();
}

baseOneCharValue::baseOneCharValue(ifaceItem * itemParent, unsigned short pX, unsigned short pY, unsigned char iSize, unsigned short iColor, unsigned short mV, unsigned short xV) {
	this->countChildren = 0;
	this->parentItem = itemParent;
	this->iX = pX;
	this->iY = pY;
	this->itemColor = iColor;
	this->modeShow = 0;
	this->fontSize = iSize;
	this->minValue = mV;
	this->maxValue = xV;
}

void baseOneCharValue::showItem(void) {
	this->modeShow = 0;
	f103st7735printOneChar(this->currentValue, this->itemColor, this->parentItem->backgroundColor, this->fontSize, this->parentItem->posX+this->iX, this->parentItem->posY+this->iY);
}

void baseNum2digValue::clearItem(void) {
	unsigned short posX = 0;
	this->modeShow = 2;
	f103st7735printOneChar(' ', this->itemColor, this->parentItem->backgroundColor, this->fontSize, this->parentItem->posX+this->iX, this->parentItem->posY+this->iY);
	posX += fontSizeX[this->fontSize];
	f103st7735printOneChar(' ', this->itemColor, this->parentItem->backgroundColor, this->fontSize, this->parentItem->posX+this->iX+posX, this->parentItem->posY+this->iY);
}

void baseNum2digValue::showItem(void) {
	this->modeShow = 0;
	unsigned short posX = 0;
	f103st7735printOneChar((this->currentValue/10)+'0', this->itemColor, this->parentItem->backgroundColor, this->fontSize, this->parentItem->posX+this->iX+posX, this->parentItem->posY+this->iY);
	posX += fontSizeX[this->fontSize];
	f103st7735printOneChar((this->currentValue%10)+'0', this->itemColor, this->parentItem->backgroundColor, this->fontSize, this->parentItem->posX+this->iX+posX, this->parentItem->posY+this->iY);
}

void baseDictWDayValue::clearItem(void) {
	this->modeShow = 2;
	f103st7735printString((unsigned char *)this->textWDay[7], this->itemColor, this->parentItem->backgroundColor,
		this->fontSize, this->iX+this->parentItem->posX, this->iY+this->parentItem->posY);
}

void baseDictWDayValue::showItem(void) {
	this->modeShow = 0;
	f103st7735printString((unsigned char *)this->textWDay[this->currentValue], this->itemColor, this->parentItem->backgroundColor,
		this->fontSize, this->iX+this->parentItem->posX, this->iY+this->parentItem->posY);
}

void baseDictMonthValue::clearItem(void) {
	this->modeShow = 2;
	f103st7735printString((unsigned char *)this->textMonths[12], this->itemColor, this->parentItem->backgroundColor,
		this->fontSize, this->iX+this->parentItem->posX, this->iY+this->parentItem->posY);
}

void baseDictMonthValue::showItem(void) {
	this->modeShow = 0;
	f103st7735printString((unsigned char *)this->textMonths[this->currentValue], this->itemColor, this->parentItem->backgroundColor,
		this->fontSize, this->iX+this->parentItem->posX, this->iY+this->parentItem->posY);
}

baseDictPumpDaysValue::baseDictPumpDaysValue(ifaceItem * itemParent, unsigned short pX, unsigned short pY, unsigned char iSize, unsigned short iColor, unsigned char inDay, unsigned short daColor, unsigned short lbColor) {
	this->countChildren = 0;
	this->parentItem = itemParent;
	this->iX = pX;
	this->iY = pY;
	this->itemColor = iColor;
	this->modeShow = 0;
	this->fontSize = iSize;
	this->weekDay = inDay;
	this->colorDay = daColor;
	this->colorLabel = lbColor;
}

void baseDictPumpDaysValue::clearItem(void) {
	this->modeShow = 2;
	if (this->currentValue) {
		f103st7735printOneChar(' ', this->colorDay, this->colorLabel, this->fontSize, this->parentItem->posX+this->iX, this->parentItem->posY+this->iY);
		f103st7735printOneChar(' ', this->colorDay, this->colorLabel, this->fontSize, this->parentItem->posX+this->iX+fontSizeY[this->fontSize], this->parentItem->posY+this->iY);
	} else {
		f103st7735printOneChar(' ', this->itemColor, this->parentItem->backgroundColor, this->fontSize, this->parentItem->posX+this->iX, this->parentItem->posY+this->iY);
		f103st7735printOneChar(' ', this->itemColor, this->parentItem->backgroundColor, this->fontSize, this->parentItem->posX+this->iX+fontSizeY[this->fontSize], this->parentItem->posY+this->iY);
	}
}

void baseDictPumpDaysValue::showItem(void) {
	this->modeShow = 0;
	if (this->currentValue) {
		f103st7735printOneChar((unsigned char)this->textWDay[this->weekDay][0], this->colorDay, this->colorLabel, this->fontSize, this->parentItem->posX+this->iX, this->parentItem->posY+this->iY);
		f103st7735printOneChar((unsigned char)this->textWDay[this->weekDay][1], this->colorDay, this->colorLabel, this->fontSize, this->parentItem->posX+this->iX+fontSizeY[this->fontSize], this->parentItem->posY+this->iY);
	} else {
		f103st7735printOneChar((unsigned char)this->textWDay[this->weekDay][0], this->itemColor, this->parentItem->backgroundColor, this->fontSize, this->parentItem->posX+this->iX, this->parentItem->posY+this->iY);
		f103st7735printOneChar((unsigned char)this->textWDay[this->weekDay][1], this->itemColor, this->parentItem->backgroundColor, this->fontSize, this->parentItem->posX+this->iX+fontSizeY[this->fontSize], this->parentItem->posY+this->iY);
	}
}

baseNum2digText::baseNum2digText(ifaceItem * itemParent, unsigned short pX, unsigned short pY, unsigned char iSize, unsigned short iColor, unsigned char * inText, unsigned short mV, unsigned short xV) {
	this->countChildren = 0;
	this->parentItem = itemParent;
	this->iX = pX;
	this->iY = pY;
	this->itemColor = iColor;
	this->modeShow = 0;
	this->fontSize = iSize;
	this->endText = inText;
	this->minValue = mV;
	this->maxValue = xV;
}

void baseNum2digText::clearItem(void) {
	unsigned short posX = 0;
	this->modeShow = 2;
	f103st7735printOneChar(' ', this->itemColor, this->parentItem->backgroundColor, this->fontSize, this->parentItem->posX+this->iX+posX, this->parentItem->posY+this->iY);
	posX += fontSizeX[this->fontSize];
	f103st7735printOneChar(' ', this->itemColor, this->parentItem->backgroundColor, this->fontSize, this->parentItem->posX+this->iX+posX, this->parentItem->posY+this->iY);
	posX += fontSizeX[this->fontSize];
	f103st7735printString((unsigned char *)"     ", this->itemColor, this->parentItem->backgroundColor, this->fontSize, posX, this->parentItem->posY+this->iY);
}

void baseNum2digText::showItem(void) {
	this->modeShow = 0;
	unsigned short posX = 0;
	f103st7735printOneChar((this->currentValue/10)+'0', this->itemColor, this->parentItem->backgroundColor, this->fontSize, this->parentItem->posX+this->iX+posX, this->parentItem->posY+this->iY);
	posX += fontSizeX[this->fontSize];
	f103st7735printOneChar((this->currentValue%10)+'0', this->itemColor, this->parentItem->backgroundColor, this->fontSize, this->parentItem->posX+this->iX+posX, this->parentItem->posY+this->iY);
	posX += fontSizeX[this->fontSize];
	f103st7735printString((unsigned char *)this->endText, this->itemColor, this->parentItem->backgroundColor, this->fontSize, posX, this->parentItem->posY+this->iY);
}

void fillPumpDaysFromData() {
	if (iPumperDaysTime.getCountChildren()) {
		if ((iPumperDaysTime.getCountChildren()>7) && (iPumperDaysTime.getCountChildren()<11)) {
			i0DayPumper.setValue(saveParamDays1partPump.pumpDays[0]);
			i1DayPumper.setValue(saveParamDays1partPump.pumpDays[1]);
			i2DayPumper.setValue(saveParamDays1partPump.pumpDays[2]);
			i3DayPumper.setValue(saveParamDays1partPump.pumpDays[3]);
			i4DayPumper.setValue(saveParamDays2partPump.pumpDays[0]);
			i5DayPumper.setValue(saveParamDays2partPump.pumpDays[1]);
			i6DayPumper.setValue(saveParamDays2partPump.pumpDays[2]);
			iHourPumpTime.setValue(saveParamTime3partPump.pumpTime[0]);
			iMinPumpTime.setValue(saveParamTime3partPump.pumpTime[1]);
		}
	}
}

void initPumper(void) {
	//--current date time
	iMainDisplay.addChild((ifaceItem *)&iCurrentDateTime);
	iCurrentDateTime.addChild((ifaceItem *)&iDayOfWeekCurrentDateTime);
	iCurrentDateTime.addChild((ifaceItem *)&iHourTimeCurrentDateTime);
	iCurrentDateTime.addChild((ifaceItem *)&iPointTimeCurrentDateTime);
	iPointTimeCurrentDateTime.setValue(' ');
	iPointTimeCurrentDateTime.setNoEditedItem();
	iCurrentDateTime.addChild((ifaceItem *)&iMinTimeCurrentDateTime);
	iCurrentDateTime.addChild((ifaceItem *)&iMDayCurrentDateTime);
	iCurrentDateTime.addChild((ifaceItem *)&iMonthCurrentDateTime);
	iCurrentDateTime.addChild((ifaceItem *)&iYearCurrentDateTime);
	//-- pump data
	iMainDisplay.addChild((ifaceItem *)&iPumperDaysTime);
	iPumperDaysTime.addChild((ifaceItem *)&i0DayPumper);
	iPumperDaysTime.addChild((ifaceItem *)&i1DayPumper);
	iPumperDaysTime.addChild((ifaceItem *)&i2DayPumper);
	iPumperDaysTime.addChild((ifaceItem *)&i3DayPumper);
	iPumperDaysTime.addChild((ifaceItem *)&i4DayPumper);
	iPumperDaysTime.addChild((ifaceItem *)&i5DayPumper);
	iPumperDaysTime.addChild((ifaceItem *)&i6DayPumper);
	iPumperDaysTime.addChild((ifaceItem *)&iHourPumpTime);
	iPumperDaysTime.addChild((ifaceItem *)&iPointPumpTime);
	iPointPumpTime.setValue('.');
	iPointPumpTime.setNoEditedItem();
	iPumperDaysTime.addChild((ifaceItem *)&iMinPumpTime);
	//--period pump
	iMainDisplay.addChild((ifaceItem *)&iPeriodTime);
	iPeriodTime.addChild((ifaceItem *)&iPeriodPump);
	// -- read param from flash
	saveParamDays1partPump.PartPumpDays = FLASH_read_param(numParamPump1Days);
	saveParamDays2partPump.PartPumpDays = FLASH_read_param(numParamPump2Days);
	saveParamTime3partPump.PartPumpTime = FLASH_read_param(numParamTimePump);
}

void relayON(void) {
	//GPIOA->BSRR |= GPIO_BSRR_BR12; // PA12 LOW
	GPIOA->BSRR |= GPIO_BSRR_BS12; // PA12 HIGH
	delay_ms(100UL);
	//GPIOA->BSRR |= GPIO_BSRR_BR11; // PA11 LOW
	GPIOA->BSRR |= GPIO_BSRR_BS11; // PA11 HIGH
}

void relayOFF(void) {
	GPIOA->BSRR |= GPIO_BSRR_BR11; // PA11 LOW
	//GPIOA->BSRR |= GPIO_BSRR_BS11; // PA11 HIGH
	delay_ms(100UL);
	GPIOA->BSRR |= GPIO_BSRR_BR12; // PA12 LOW
	//GPIOA->BSRR |= GPIO_BSRR_BS12; // PA12 HIGH
}

void f1InitPumpGpio(void) {
	RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; // GPIO port B
	// PB14 - key 1
	GPIOB->CRH &= ~GPIO_CRH_MODE14; // 00: Input mode (reset state)
	GPIOB->CRH &= ~GPIO_CRH_CNF14; // 10: Input with pull-up / pull-down
	GPIOB->CRH |= GPIO_CRH_CNF14_1; // 10: Input with pull-up / pull-down
	GPIOB->ODR |= GPIO_ODR_ODR14; // Input with pull-up
	// PB15 - key 2
	GPIOB->CRH &= ~GPIO_CRH_MODE15; // 00: Input mode (reset state)
	GPIOB->CRH &= ~GPIO_CRH_CNF15; // 10: Input with pull-up / pull-down
	GPIOB->CRH |= GPIO_CRH_CNF15_1; // 10: Input with pull-up / pull-down
	GPIOB->ODR |= GPIO_ODR_ODR15; // Input with pull-up
	// relay pins OUTPUT
	RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; // GPIO port A
	// PA11 - relay1
	GPIOA->ODR &= ~GPIO_ODR_ODR11; // PA11 LOW
	GPIOA->CRH &= ~GPIO_CRH_MODE11; // 00: Input mode (reset state)
	GPIOA->CRH |= GPIO_CRH_MODE11_0; // 01: Output mode, max speed 10 MHz.
	GPIOA->CRH &= ~GPIO_CRH_CNF11; // 00: General purpose output push-pull
	// PA12 - relay2
	GPIOA->ODR &= ~GPIO_ODR_ODR12; // PA12 LOW
	GPIOA->CRH &= ~GPIO_CRH_MODE12; // 00: Input mode (reset state)
	GPIOA->CRH |= GPIO_CRH_MODE12_0; // 01: Output mode, max speed 10 MHz.
	GPIOA->CRH &= ~GPIO_CRH_CNF12; // 00: General purpose output push-pull
	// --
	relayOFF();
}

void showDateTime(void) {
	static unsigned long timerShowDateTime = 59000UL;
	static unsigned long timerBlinkSec = 0;
	static unsigned char secBlink =0;
	if ((millis() - timerShowDateTime) >= 60000UL) {
		timerShowDateTime = millis();
		getCurrentDateTime((struct tm *)&currentDateTime);
		iDayOfWeekCurrentDateTime.setValue(currentDateTime.tm_wday);
		iHourTimeCurrentDateTime.setValue(currentDateTime.tm_hour);
		iMinTimeCurrentDateTime.setValue(currentDateTime.tm_min);
		iMDayCurrentDateTime.setValue(currentDateTime.tm_mday);
		iMonthCurrentDateTime.setValue(currentDateTime.tm_mon);
		iYearCurrentDateTime.setValue((currentDateTime.tm_year+1900)%100);
		iCurrentDateTime.showItem();
	} else	if ((millis() - timerBlinkSec) >= 1000UL) {
		timerBlinkSec = millis();
		if (secBlink) {
			iPointTimeCurrentDateTime.setValue(':');
			secBlink = 0;
		} else {
			iPointTimeCurrentDateTime.setValue(' ');
			secBlink = 1;
		}
		iPointTimeCurrentDateTime.showItem();
	}
}

unsigned char getKey1(void) { // выдает 1 если нажата 1 кнопка
	static unsigned char last1Level = 1;
	static unsigned char flag1KeysDown = 0;
	static unsigned long start1TimeDown;
	unsigned char current1Level;
	if (GPIOB->IDR & GPIO_IDR_IDR14) current1Level = 1; else current1Level = 0;
	if (current1Level != last1Level) { // change level
		unusedDevice = millis();
		if (current1Level == 0) { // key down
			if (flag1KeysDown == 0) { // no timer
				flag1KeysDown = 1; // begin timer
				start1TimeDown = millis();
			}
		} else { // key up
			if (flag1KeysDown != 0) { // timer started
				if ((millis() - start1TimeDown) >= 500UL) { // error press
					flag1KeysDown = 0; // stop timer
				} else if ((millis() - start1TimeDown) >= keys_short_press) { // short press
					flag1KeysDown = 0; // stop timer
					last1Level = current1Level; // save current level
					return 1;
				}
			}
		}
	} else { // level not change
		if ((flag1KeysDown != 0) && (current1Level == 0)) { // timer started and key down
			if ((millis() - start1TimeDown) >= keys_long_press) { // long press
				flag1KeysDown = 0; // stop timer
				last1Level = current1Level; // save current level
				return 2;
			}
		} else if (current1Level != 0) { // key up
			flag1KeysDown = 0; // reset timer
		}
	}
	last1Level = current1Level; // save current level
	return 0;
}

unsigned char getKey2(void) { // выдает 1 если нажата 2 кнопка
	static unsigned char last2Level = 1;
	static unsigned char flag2KeysDown = 0;
	static unsigned long start2TimeDown;
	unsigned char current2Level;
	if (GPIOB->IDR & GPIO_IDR_IDR15) current2Level = 1; else current2Level = 0;
	if (current2Level != last2Level) { // change level
		unusedDevice = millis();
		if (current2Level == 0) { // key down
			if (flag2KeysDown == 0) { // no timer
				flag2KeysDown = 1; // begin timer
				start2TimeDown = millis();
			}
		} else { // key up
			if (flag2KeysDown != 0) { // timer started
				if ((millis() - start2TimeDown) >= 500UL) { // error press
					flag2KeysDown = 0; // stop timer
				} else if ((millis() - start2TimeDown) >= keys_short_press) { // short press
					flag2KeysDown = 0; // stop timer
					last2Level = current2Level; // save current level
					return 1;
				}
			}
		}
	} else { // level not change
		if ((flag2KeysDown != 0) && (current2Level == 0)) { // timer started and key down
			if ((millis() - start2TimeDown) >= keys_long_press) { // long press
				flag2KeysDown = 0; // stop timer
				last2Level = current2Level; // save current level
				return 2;
			}
		} else if (current2Level != 0) { // key up
			flag2KeysDown = 0; // reset timer
		}
	}
	last2Level = current2Level; // save current level
	return 0;
}

void saveDeviceOptions(void) {
	// --current date time
	currentDateTime.tm_wday = iDayOfWeekCurrentDateTime.getValue();
	currentDateTime.tm_hour = iHourTimeCurrentDateTime.getValue();
	currentDateTime.tm_min = iMinTimeCurrentDateTime.getValue();
	currentDateTime.tm_mday = iMDayCurrentDateTime.getValue();
	currentDateTime.tm_mon = iMonthCurrentDateTime.getValue();
	currentDateTime.tm_year = iYearCurrentDateTime.getValue() + 100;
	// -- save to DS3231
	setCurrentDateTime((struct tm *)&currentDateTime);
	// -- pump data
	saveParamDays1partPump.pumpDays[0] = i0DayPumper.getValue();
	saveParamDays1partPump.pumpDays[1] = i1DayPumper.getValue();
	saveParamDays1partPump.pumpDays[2] = i2DayPumper.getValue();
	saveParamDays1partPump.pumpDays[3] = i3DayPumper.getValue();
	saveParamDays2partPump.pumpDays[0] = i4DayPumper.getValue();
	saveParamDays2partPump.pumpDays[1] = i5DayPumper.getValue();
	saveParamDays2partPump.pumpDays[2] = i6DayPumper.getValue();
	saveParamTime3partPump.pumpTime[0] = iHourPumpTime.getValue();
	saveParamTime3partPump.pumpTime[1] = iMinPumpTime.getValue();
	saveParamTime3partPump.pumpPeriod = iPeriodPump.getValue();
	// -- save to flash
	FLASH_write_param(numParamPump1Days, saveParamDays1partPump.PartPumpDays);
	FLASH_write_param(numParamPump2Days, saveParamDays2partPump.PartPumpDays);
	FLASH_write_param(numParamTimePump, saveParamTime3partPump.PartPumpTime);
}

unsigned char controlDevice(void) { // выдать 1 если изменились параметры и их все надо обновить на экране
	static unsigned char dataChanged = 0; // 1 если данные менялись
	unsigned char data1key = getKey1();
	unsigned char data2key = getKey2();
	static unsigned char numEditMainUnit = 0; // номер главного модуля который редактируется
	static unsigned char enteredSubUnit = 0; // перешли в редактирование значения в модуле
	static unsigned char numSubItem = 0; // номер элемента который редактируется
	if (!devModes) { // standby mode
		dataChanged = 0; // пока данные не менялись
		if (data1key) { // enter set mode
			devModes = 1;  // enter set mode
			numEditMainUnit = 0; // номер главного модуля который редактируется
			enteredSubUnit = 0; // моргаем пока модулем
		}
	} else { // settings mode
		if ((millis()-unusedDevice) >= 8000UL) { // если кнопки долго не нажимали
			if (dataChanged) saveDeviceOptions(); // сохраняем все данные
			devModes = 0; // возвращаемся в ждущий режим
			return 1; // с обновлением данных
		}
		if (enteredSubUnit) { // в режиме редактирования элемента
			iMainDisplay.getChild(numEditMainUnit)->getChild(numSubItem)->blinkItem(); // моргаем элементом
			if (data2key) { // если продолжается нажиматься вторая кнопка
				iMainDisplay.getChild(numEditMainUnit)->getChild(numSubItem)->showItem(); // предыдущий элемент просто отобразим
				++numSubItem; // будем моргать следующим элементом
				if (numSubItem >= iMainDisplay.getChild(numEditMainUnit)->getCountChildren()) numSubItem = 0;
				if (iMainDisplay.getChild(numEditMainUnit)->getChild(numSubItem)->getNoEditedItem()) { // если элемент не редактируемый
					++numSubItem; // будем моргать следующим элементом
					if (numSubItem >= iMainDisplay.getChild(numEditMainUnit)->getCountChildren()) numSubItem = 0;
				}
			} else {
				if (data1key) { // нажалась кнопка 1 - изменить значение
					dataChanged = 1;
					iMainDisplay.getChild(numEditMainUnit)->getChild(numSubItem)->changeValue();
				}
			}
		} else { // в режиме выбора модуля
			iMainDisplay.getChild(numEditMainUnit)->blinkItem(); // моргаем модулем
			if (data1key) { // если продолжается нажиматься первая кнопка
				iMainDisplay.getChild(numEditMainUnit)->showItem(); // предыдущий модуль просто отобразим
				++numEditMainUnit; // будем моргать следующим модулем
				if (numEditMainUnit >= iMainDisplay.getCountChildren()) numEditMainUnit = 0;
			} else {
				if (data2key) { // если нажалась вторая кнопка
					iMainDisplay.getChild(numEditMainUnit)->showItem(); // текущий модуль просто отобразим
					enteredSubUnit = 1; // перешли в редактирование значения в модуле
					numSubItem = 0; // номер элемента который редактируется
				}
			}
		}
	}
	return 0;
}

unsigned char deviceProcess(void) { // проверка времени запуска и включение реле
	if ((saveParamTime3partPump.pumpTime[0] == currentDateTime.tm_hour) &&
			(saveParamTime3partPump.pumpTime[1] == currentDateTime.tm_min)) { // время пришло
		unsigned char dayON = 0;
		if (currentDateTime.tm_wday<4) {
			if (saveParamDays1partPump.pumpDays[currentDateTime.tm_wday]) dayON = 1;
		} else {
			if (saveParamDays2partPump.pumpDays[currentDateTime.tm_wday-4]) dayON = 1;
		}
		if (dayON) { // и день подошел
			unsigned long pumpPeriod = saveParamTime3partPump.pumpPeriod * 60000UL; //by min
			if (pumpPeriod >= 3360000UL) return 0; // 56 min MAX
			unsigned long timerPumper = millis();
			unsigned long timerSec = 0;
			f103st7735fillScreen(ST7735_BLACK);
			relayON();
			unsigned short posX = fontSizeX[0], posY = fontSizeY[0];
			while ((millis() - timerPumper) < pumpPeriod) {
				if ((millis() - timerSec) >= 1000UL) { // 1 sec
					timerSec = millis();
					unsigned long minutes = (pumpPeriod - (millis() - timerPumper)) / 60000UL;
					unsigned long seconds = ((pumpPeriod - (millis() - timerPumper)) / 1000UL) % 60;
					f103st7735printOneChar((minutes/10)+'0', ST7735_GREEN, ST7735_BLACK, 3, posX, posY);
					f103st7735printOneChar((minutes%10)+'0', ST7735_GREEN, ST7735_BLACK, 3, posX + fontSizeX[3], posY);
					f103st7735printOneChar((seconds/10)+'0', ST7735_GREEN, ST7735_BLACK, 3, posX + fontSizeX[0], posY + fontSizeY[3] + fontSizeY[0]);
					f103st7735printOneChar((seconds%10)+'0', ST7735_GREEN, ST7735_BLACK, 3, posX + fontSizeX[3] + fontSizeX[0], posY + fontSizeY[3] + fontSizeY[0]);
				}
			}
			relayOFF();
			return 1; // обновим всю картинку при выходе
		}
	}
	return 0;
}

void loopPumper(void) {
	static unsigned char needShowPumpDate = 1; // в обычном ждущем режиме один раз показать данные полива при старте железки
	if (!devModes) { // standby mode
		showDateTime();
		if ((needShowPumpDate) || (deviceProcess())) {
			needShowPumpDate = 0;
			fillPumpDaysFromData();
			iPumperDaysTime.showItem();
			iPeriodPump.setValue(saveParamTime3partPump.pumpPeriod);
			iPeriodTime.showItem();
			f103st7735printString((unsigned char *)"Pumper v08.2023", ST7735_WHITE, ST7735_BLACK, 0, 0, st7735sizeY - fontSizeY[0]);
		}
	}
	needShowPumpDate = controlDevice(); // основная работа с кнопками и режимами настройки
}



кшмр. :face_with_spiral_eyes:

У меня такие же чувства, но скорее от того что я не умею программировать… ))

2 лайка

в каком плане ? :slight_smile:

в программном. Для такой примитивной задачи уж больно на…уеверчено.

ну не, не соглашусь, если выкинуть код инициализации, RTC, флэш памяти, дисплея (предположить что используются библиотеки), то останется только классы/объекты работы с меню → настройка параметров. Т е то, с чего я и начинал данную тему.

Может так и стоило сделать перед тем как выкладывать?

Вообще, начиная с определенного уровня сложности, задавать вопросы на форуме становится почти бессмысленно. Слишком глубоко надо объяснять суть проекта, прежде чем кто-то сможет помочь.

1 лайк

Если выкинуть “не нужное”, код работать не будет.

Про " задавать вопросы на форуме" не понял.
Я задал вопрос в начале темы, получил ответ, написал код, он заработал, профит! :slight_smile:

Небольшое уточнение по работе RTC на CH32F103C8T6:
Взял очередрую подопытную плату, запустил ранее работающую программу, оказалось что RTC “тикают” в 60 раз медленнее, возможно виноват данный конкретный экземпляр.
Причем поведение не зависит от подачи напряжения на VBAT, при сбросе/подаче питания все аналогично.
Для исправления, в функции:

void f103initRTC(void) {
	  if ((RCC->BDCR & RCC_BDCR_RTCEN) != RCC_BDCR_RTCEN)   {              //Проверка работы часов, если не включены, то инициализировать
		RCC->APB1ENR |= RCC_APB1ENR_PWREN | RCC_APB1ENR_BKPEN;  //Включить тактирование PWR и Backup
		PWR->CR |= PWR_CR_DBP;                                                            //Разрешить доступ к Backup области
		RCC->BDCR |= RCC_BDCR_BDRST;                                               //Сбросить Backup область
		RCC->BDCR &= ~RCC_BDCR_BDRST;
		RCC->BDCR |= RCC_BDCR_RTCSEL_LSE;        //Выбрать LSE источник (кварц 32768)
		RCC->BDCR |= RCC_BDCR_LSEON;      //Включить LSE
		while ((RCC->BDCR & RCC_BDCR_LSEON) != RCC_BDCR_LSEON){} //Дождаться включения
		BKP->RTCCR |= 3;                                                                         //калибровка RTC
		while (!(RTC->CRL & RTC_CRL_RTOFF));                                         //проверить закончены ли изменения регистров RTC
		RTC->CRL  |=  RTC_CRL_CNF;                                                        //Разрешить Запись в регистры RTC
		RTC->PRLH  = 0;                                                                    //Настроит делитель на 32768 (32767+1)
		RTC->PRLL  = 0x7FFF;                                                                    //Настроит делитель на 32768 (32767+1)
		//BKP->RTCCR |= BKP_RTCCR_CCO;                   //Включение вывода Temper
		RTC->CRL  &=  ~RTC_CRL_CNF;                                                     //Запретить запись в регистры RTC
		RCC->BDCR |= RCC_BDCR_RTCEN; // и подать тактирование
		while (!(RTC->CRL & RTC_CRL_RTOFF));                                         //Дождаться окончания записи
		RTC->CRL &= (uint16_t)~RTC_CRL_RSF;                                         //Синхронизировать RTC
		while((RTC->CRL & RTC_CRL_RSF) != RTC_CRL_RSF){}                  //Дождаться синхронизации
		PWR->CR &= ~PWR_CR_DBP;                                                         //запретить доступ к Backup области
	  }
}

добавил строку

RTC->PRLH  = 0;

т.е. обнулить старшую часть регистра делителя RTC от LSE