Открытый проект интеллекктронной нагрузки на базе HT32F52352, открытие работы Holtek Cup

Открытый проект интеллектуальной электронной нагрузки на базе HT32F52352, работа на конкурс «合泰杯», включая схему, PCB, исходный код и отчёт.

Третье место на 10-м конкурсе студенческих проектов по применению микроконтроллеров «合泰杯» в Гуандуне, 2023 г.

Сделано в спешке за месяц (при полной загрузке парами), качество посредственное — не ругайте строго.

Демонстрационное видео: https://www.bilibili.com/video/BV1sM4y1b7qu/

Проект выложен в открытый доступ исключительно в образовательных целях; повторять не рекомендуется — на платформе 立创 есть более продвинутые и доработанные решения!

Ссылка на страницу проекта: https://url.zeruns.com/xvvF8

Инструкция по установке среды разработки для HT32: https://blog.zeruns.com/archives/709.html

Технический чат по электронике/МК: 2169025065

Что такое электронная нагрузка

Электронная нагрузка — это устройство, имитирующее реальную нагрузку для испытания источников питания или схем. В отличие от громоздких балластных резисторов или «печёк», она позволяет точно задавать параметры и удобна в эксплуатации. Незаменима как в профессиональной разработке, так и в любительской практике.

По типу питания различают переменную и постоянную нагрузки; по режиму работы — стабилизацию тока, напряжения, сопротивления и мощности. Для типичных стабилизированных БП наиболее востребована постоянная нагрузка в режиме стабильного тока. По способу управления бывают аналоговые и цифровые; последние проще настраивать, легко расширяются и позволяют автоматизировать измерения.

Кратко о проекте

Основа — микроконтроллер Holtek HT32F52352. Питание от аккумулятора 18650, компактность обеспечена.

Управление: МК формирует ШИМ → фильтр нижних частот (получаем стабильное DAC) → ОУ сравнивает опорное напряжение с усиленным сигналом датчиков тока/напряжения → выход ОУ управляет ключом MOS, реализуя стабилизацию тока/напряжения.

Сенсорный экран — 2,8" UART-дисплей TaoJingChi, модель TJC3224T028_011R.

Фото готового устройства

Снимков мало, больше — в видео-обзоре.


Скачать материалы

В архиве: схема, проект в 立创EDA, Gerber-файлы, исходники прошивки, проект экрана, даташиты.

123-диск: https://www.123pan.com/s/2Y9Djv-ZNevH.html

Baidu NetDisk: https://url.zeruns.com/XP241 код: i9bl

Где брать компоненты

Комплектующие удобнее всего покупать в 立创商城: https://activity.szlcsc.com/invite/D03E5B9CEAAE70A4.html

Схемы

Силовая плата



Плата питания

Контроллерная плата

PCB

Силовая плата

Top

Inner 1

Bottom

Слой GND не показан.

Плата питания

Top

Bottom

Контроллерная плата

Top

Bottom

Основной код

Файл main.c

#include "ht32.h"
#include "GPIO.h"
#include "BFTM0.h"
#include "GPTM0.h"
#include "GPTM1.h"
#include "MCTM0.h"
#include "delay.h"
#include "OLED.h"
#include "WDT.h"
#include "ADC.h"
#include "USART.h"
#include "string.h"

#define KEY1 GPIO_ReadInBit(HT_GPIOA, GPIO_PIN_10)

// 定义按键状态的枚举变量类型
typedef enum
{
  KS_RELEASE = 0, // 按键松开
  KS_SHAKE,       // 按键抖动
  KS_PRESS,       // 稳定按下
} KEY_STATUS;

// 当前循环结束的(状态机的)状态
#define g_keyStatus 0
// 当前状态(每次循环后与g_keyStatus保持一致)
#define g_nowKeyStatus 1
// 上次状态(用于记录前一状态以区分状态的来源)
#define g_lastKeyStatus 2

uint8_t KEY_Status[4][3]; // 记录各按键状态
uint8_t key[4];           // 记录各按键是否稳定按下,1表示按键已按下,0表示按键没被按下
char *temp;
```// Определение перечисления для страниц режимов
enum mode_type
{
  menu = 0, // Меню
  CC,       // Постоянный ток
  CV,       // Постоянное напряжение
  CR,       // Постоянное сопротивление
  CW        // Постоянная мощность
};

uint8_t Eload_Out = 0;                // Состояние включения/выключения электронной нагрузки
uint8_t mode = menu;                  // Текущий режим
uint8_t voltage_dw = 0;               // Диапазон выборки напряжения: 0 — 0,0325×, 2 — 0,6175×, 1 — 0,0947×
float YVF, YIF1, YIF2, YIF;           // Текущие напряжение и токи
float ISET, VSET, RSET, PSET;         // Установки тока, напряжения, сопротивления и мощности
float VDD = 3.3;                      // Напряжение питания микроконтроллера
uint32_t YVF_SUM, YIF1_SUM, YIF2_SUM; // Суммы для усреднения напряжения и токов
uint8_t AVG_count = 0;                // Счётчик накопления для усреднения тока
uint8_t YVF_AVG_count = 0;            // Счётчик накопления для усреднения напряжения
uint8_t Key_ONOFF = 0;                // Флаг нажатия кнопки включения/выключения нагрузки

void HMI_GetData(void);
void HMI_Display(void);
void AdcFb(void);
void ONOFF(void);
void FAN(void);
void key_status_check(uint8_t key_num, uint8_t KEY);
void CW_mode(void);
void CR_mode(void);
void OFF(void); // Выключить
void ON(void);  // Включить

int main(void)
{
  GPIO_Configuration();     // Инициализация GPIO
  BFTM0_Configuration();    // Инициализация таймера BFTM0
  GPTM0_Configuration();    // Инициализация таймера GPTM0
  GPTM1_Configuration();    // Инициализация таймера GPTM1
  MCTM0_Configuration();    // Инициализация таймера MCTM0
  WDT_Configuration();      // Инициализация сторожевого таймера
  OLED_Init();              // Инициализация OLED
  ADC_Configuration();      // Инициализация АЦП
  RETARGET_Configuration(); // Перенаправление printf в UART
  USART1_Configuration();   // Инициализация UART1
  USART0_Configuration();   // Инициализация UART0

  uint16_t count1 = 0;

  Serial_SendHMILCD("page 0");      // Переключиться на стартовый экран
  GPTM0_CH0_DisablePWMOutput(1);    // Отключить ШИМ канала CH0 (постоянное напряжение), выдать высокий уровень
  GPTM0_CH2_DisablePWMOutput(0);    // Отключить ШИМ канала CH2 (постоянный ток 1), выдать низкий уровень
  GPTM0_CH3_DisablePWMOutput(0);    // Отключить ШИМ канала CH3 (постоянный ток 2), выдать низкий уровень
  MCTM0_CH0_DisablePWMOutput(0);    // Отключить ШИМ канала MCTM0_CH0 (вентилятор), выдать низкий уровень
  Serial_SendHMILCD("CC.x0.val=0"); // Обнулить отображаемое значение тока на экране
  Serial_SendHMILCD("CV.x0.val=0"); // Обнулить отображаемое значение напряжения на экране
  Serial_SendHMILCD("CR.x0.val=0"); // Обнулить отображаемое значение сопротивления на экране
  Serial_SendHMILCD("CW.x0.val=0"); // Обнулить отображаемое значение мощности на экране

  GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_10, RESET); // MCU_G0, коэффициент выборки напряжения 0,6175×
  GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_11, RESET); // MCU_G1, коэффициент выборки напряжения 0,0947×

  while (1)
  {
    if (HT_CKCU->APBCCR1 & (1 << 4)) // Проверка, включён ли такт сторожевого таймера
      WDT_Restart();                 // Сброс счётчика сторожевого таймера

    GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_14, RESET);

    if (bftm0_ct3 >= 1) // Выполнять каждую 1 мс
    {
      AdcFb(); // Обработка данных АЦП
      bftm0_ct3 = 0;
    }

    HMI_GetData();

    if (bftm0_ct2 >= 40) // Выполнять каждые 40 мс
    {
      HMI_Display(); // Вывод данных на экран
      bftm0_ct2 = 0;
    }
    ONOFF();
    FAN();

    if (bftm0_ct >= 50) // Выполнять каждые 50 мс
    {
      key_status_check(0, KEY1); // Опрос кнопок
      if (mode == CW)
      {
        CW_mode();
      }
      if (mode == CR)
      {
        CR_mode();
      }
      bftm0_ct = 0;
    }

    if (YIF > 10 | YVF * YIF > 100)
    {
      if (Eload_Out == 1)
      {
        Key_ONOFF = 1;
      }
    }

    GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_14, SET);
    /*
        float Voltage0 = (AD_Value[0] & 0x0000FFFF) / 4096.0 * 3.3; // Преобразование значения АЦП в напряжение
        OLED_ShowNum(1, 4, (uint8_t)Voltage0, 1);                   // Вывод целой части
        OLED_ShowString(1, 5, ".");
        OLED_ShowString(1, 9, "V");
        OLED_ShowNum(1, 6, (uint16_t)(Voltage0 * 1000) % 1000, 3); // Вывод дробной части

        float Voltage1 = (AD_Value[1] & 0x0000FFFF) / 4096.0 * 3.3; // Преобразование значения АЦП в напряжение
        OLED_ShowNum(2, 4, (uint8_t)Voltage1, 1);                   // Вывод целой части
        OLED_ShowString(2, 5, ".");
        OLED_ShowString(2, 9, "V");
        OLED_ShowNum(2, 6, (uint16_t)(Voltage1 * 1000) % 1000, 3); // Вывод дробной части

        float Voltage2 = (AD_Value[2] & 0x0000FFFF) / 4096.0 * 3.3; // Преобразование значения АЦП в напряжение
        OLED_ShowNum(3, 4, (uint8_t)Voltage2, 1);                   // Вывод целой части
        OLED_ShowString(3, 5, ".");
        OLED_ShowString(3, 9, "V");
        OLED_ShowNum(3, 6, (uint16_t)(Voltage2 * 1000) % 1000, 3); // Вывод дробной части

        VDD = (AD_Value[3] & 0x0000FFFF) / 4096.0 * 3.3; // Преобразование значения АЦП в напряжение
        OLED_ShowNum(4, 4, (uint8_t)VDD, 1);             // Вывод целой части
        OLED_ShowString(4, 5, ".");
        OLED_ShowString(4, 9, "V");
        OLED_ShowNum(4, 6, (uint16_t)(VDD * 1000) % 1000, 3); // Вывод дробной части

        VDD = (AD_Value[4] & 0x0000FFFF) / 4096.0 * 3.3; // Преобразование значения АЦП в напряжение
        OLED_ShowNum(4, 11, (uint8_t)VDD, 1);            // Вывод целой части
        OLED_ShowString(4, 12, ".");
        OLED_ShowString(4, 16, "V");
        OLED_ShowNum(4, 13, (uint16_t)(VDD * 1000) % 1000, 3); // Вывод дробной части
        count1++;*/

			if(flag_start==1)//Обработка данных UART1
			{
				if(++cishu==7)  {flag_over=1;}
							  Delay_ms(1);
			}
			if(flag_over==1)
		{
					flag_over=0;
			 if (Data[0] == 'A') // Текущая страница — цифровая клавиатура
    {

			char *temp = Data;
      temp++;                      // Увеличить адрес на 1
      uint16_t temp2 = atoi(temp); // Преобразовать строку в целое
//      if (mode == CC)
//      {
//        if (temp2 > 10000)
//          temp2 = 10000;
//        ISET = temp2 / 1000.0;
//        Serial_SendHMILCD("CC.x0.val=%d", temp2);

//        if (ISET <= 2.5)
//        {
//          if (Eload_Out == 1)
//          {
//            GPTM0_CH3_DisablePWMOutput(0);
//            GPTM0_CH2_SetOnduty((uint16_t)(ISET * 0.01 * 50 / VDD * 50000));
//          }
//          else
//          {
//            GPTM0_CH2_SetOnduty((uint16_t)(ISET * 0.01 * 50 / VDD * 50000));
//          }
//        }
//        else
//        {
//          if (Eload_Out == 1)
//          {
//            GPTM0_CH2_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
//            GPTM0_CH3_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
//            GPTM0_CH3_EnablePWMOutput();
//          }
//          else
//          {
//            GPTM0_CH2_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
//            GPTM0_CH3_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
//          }
//        }
//      }
//      else if (mode == CV)
//      {
//        VSET = temp2 / 100.0;
//        Serial_SendHMILCD("CV.x0.val=%d", temp2);
//        if (voltage_dw == 0)
//        {
//          GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0325 / VDD * 50000));
//        }
//        else if (voltage_dw == 1)
//        {
//          GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0947 / VDD * 50000));
//        }
//        else if (voltage_dw == 2)
//        {
//          GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.6175 / VDD * 50000));
//        }
//      }
//      else if (mode == CR)
//      {
//        RSET = temp2 / 100.0;
//        Serial_SendHMILCD("CR.x0.val=%d", temp2);
//        CR_mode();
//      }
       if (mode == CW)
      {
        PSET = temp2/100.0;
        Serial_SendHMILCD("CW.x0.val=%d", temp2);
        CW_mode();
      }

    }
			switch (Data[0]){
				case '6':
				Serial_SendHMILCD("page menu");          // Переключиться на страницу меню
        mode = menu;
				break;
				case '5':
		              Serial_SendHMILCD("page CW"); // Переключиться на страницу постоянной мощности
                  mode = CW;                    // Установить режим постоянной мощности
				break;
				case '4':
		              Serial_SendHMILCD("page CR"); // Переключиться на страницу постоянного сопротивления
                  mode = CR;                    // Установить режим постоянного сопротивления
				break;
				case '3':
		              Serial_SendHMILCD("page CV"); // Переключиться на страницу постоянного напряжения
                  mode = CV;                    // Установить режим постоянного напряжения
				break;
        case '2':
		              Serial_SendHMILCD("page CC"); // Переключиться на страницу постоянного тока
                  mode = CC;                    // Установить режим постоянного тока
					break;
				case '1':
		      ON();//Включить
					break;
				case '0':
          OFF();//Выключить
					break;
				default:
					break;
			}

			  cishu=0;
				len=0;
				flag_start=0;
	 }
  }
}void ON(void)//включить
{
		if (mode == CC) // постоянный ток
    {
      if (Eload_Out == 0) // если выход нагрузки сейчас выключен
      {
        Serial_SendHMILCD("CC.t1.txt=\"ON\"");   // в правом верхнем углу экрана заголовок ON
        Serial_SendHMILCD("CC.b1.txt=\"关闭\""); // в правом нижнем углу кнопка «关闭»
        Eload_Out = 1;                           // выход нагрузки установлен в ON
        if (ISET <= 2.5)                         // если заданный ток ≤ 2,5 А, использовать только один MOS
        {
          GPTM0_CH2_SetOnduty((uint16_t)(ISET * 0.01 * 50 / VDD * 50000));
          GPTM0_CH2_EnablePWMOutput();   // включить ШИМ канала CH2(IREF1)
          GPTM0_CH3_DisablePWMOutput(0); // выключить ШИМ канала CH3(IREF2), низкий уровень
        }
        else
        {
          GPTM0_CH2_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
          GPTM0_CH3_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
          GPTM0_CH2_EnablePWMOutput(); // включить ШИМ CH2
          GPTM0_CH3_EnablePWMOutput(); // включить ШИМ CH3
        }
        GPTM0_CH0_DisablePWMOutput(0); // VREF – низкий уровень
      }
    }

		else if (mode == CV) // постоянное напряжение
    {
      if (Eload_Out == 0) // если выход нагрузки сейчас выключен
      {
        Serial_SendHMILCD("CV.t1.txt=\"ON\"");
        Serial_SendHMILCD("CV.b1.txt=\"关闭\"");
        Eload_Out = 1;
        if (voltage_dw == 0)
        {
          GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0325 / VDD * 50000));
        }
        else if (voltage_dw == 1)
        {
          GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0947 / VDD * 50000));
        }
        else if (voltage_dw == 2)
        {
          GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.6175 / VDD * 50000));
        }
        GPTM0_CH0_EnablePWMOutput();
        GPTM0_CH2_DisablePWMOutput(1);
        GPTM0_CH3_DisablePWMOutput(1);
      }
    }

		else if (mode == CR) // постоянное сопротивление
    {
      if (Eload_Out == 0) // если выход нагрузки сейчас выключен
      {
        Serial_SendHMILCD("CR.t1.txt=\"ON\"");
        Serial_SendHMILCD("CR.b1.txt=\"关闭\"");
        Eload_Out = 1;
        float Rtemp = YVF / RSET;
        if (Rtemp > 10)
          Rtemp = 10;
        GPTM0_CH2_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
        GPTM0_CH3_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
        GPTM0_CH2_EnablePWMOutput(); // включить ШИМ CH2
        GPTM0_CH3_EnablePWMOutput(); // включить ШИМ CH3

        GPTM0_CH0_DisablePWMOutput(0); // VREF – низкий уровень
      }
    }

		else if (mode == CW) // постоянная мощность
    {
      if (Eload_Out == 0) // если выход нагрузки сейчас выключен
      {
        Serial_SendHMILCD("CW.t1.txt=\"ON\"");
        Serial_SendHMILCD("CW.b1.txt=\"关闭\"");
        Eload_Out = 1;
        float Ptemp = PSET / YVF;
        if (Ptemp > 10)
          Ptemp = 10;
        GPTM0_CH2_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
        GPTM0_CH3_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
        GPTM0_CH2_EnablePWMOutput(); // включить ШИМ CH2
        GPTM0_CH3_EnablePWMOutput(); // включить ШИМ CH3

        GPTM0_CH0_DisablePWMOutput(0); // VREF – низкий уровень
      }
    }
}

void OFF(void)//выключить
{

		if (mode == CC) // постоянный ток
		{
					if (Eload_Out == 1) // если выход нагрузки сейчас включён
				{
					Serial_SendHMILCD("CC.t1.txt=\"OFF\"");
					Serial_SendHMILCD("CC.b1.txt=\"开启\"");
					Eload_Out = 0;
					GPTM0_CH0_DisablePWMOutput(1); // VREF – высокий уровень
					GPTM0_CH2_DisablePWMOutput(0); // IREF1 – низкий уровень
					GPTM0_CH3_DisablePWMOutput(0); // IREF2 – низкий уровень
				}
			}

			else if (mode == CV) // постоянное напряжение
     {

		  if (Eload_Out == 1) // если выход нагрузки сейчас включён
      {
        Serial_SendHMILCD("CV.t1.txt=\"OFF\"");
        Serial_SendHMILCD("CV.b1.txt=\"开启\"");
        Eload_Out = 0;
        GPTM0_CH0_DisablePWMOutput(1); // VREF – высокий уровень
        GPTM0_CH2_DisablePWMOutput(0); // IREF1 – низкий уровень
        GPTM0_CH3_DisablePWMOutput(0); // IREF2 – низкий уровень
      }
    }

	 	else if (mode == CR) // постоянное сопротивление
     {
		 if (Eload_Out == 1) // если выход нагрузки сейчас включён
      {
        Serial_SendHMILCD("CR.t1.txt=\"OFF\"");
        Serial_SendHMILCD("CR.b1.txt=\"开启\"");
        Eload_Out = 0;
        GPTM0_CH0_DisablePWMOutput(1); // VREF – высокий уровень
        GPTM0_CH2_DisablePWMOutput(0); // IREF1 – низкий уровень
        GPTM0_CH3_DisablePWMOutput(0); // IREF2 – низкий уровень
      }
		}
		else if (mode == CW) // постоянная мощность
    {
		 if (Eload_Out == 1) // если выход нагрузки сейчас включён
      {
        Serial_SendHMILCD("CW.t1.txt=\"OFF\"");
        Serial_SendHMILCD("CW.b1.txt=\"开启\"");
        Eload_Out = 0;
        GPTM0_CH0_DisablePWMOutput(1); // VREF – высокий уровень
        GPTM0_CH2_DisablePWMOutput(0); // IREF1 – низкий уровень
        GPTM0_CH3_DisablePWMOutput(0); // IREF2 – низкий уровень
      }
		}
}

/*обработка данных, пришедших с HMI-экрана*/
void HMI_GetData(void)
{
  if (Serial_RxFlag == 1)
  {
    if (Serial_RxPacket[0] == 0x01) // текущая страница – главное меню
    {
      if (Serial_RxPacket[1] == 0x10) // нажата кнопка «постоянный ток»
      {
        Serial_SendHMILCD("page CC"); // перейти на страницу CC
        mode = CC;                    // установить режим – постоянный ток
      }
      else if (Serial_RxPacket[1] == 0x11) // нажата кнопка «постоянное напряжение»
      {
        Serial_SendHMILCD("page CV"); // перейти на страницу CV
        mode = CV;                    // установить режим – постоянное напряжение
      }
      else if (Serial_RxPacket[1] == 0x12) // нажата кнопка «постоянное сопротивление»
      {
        Serial_SendHMILCD("page CR"); // перейти на страницу CR
        mode = CR;                    // установить режим – постоянное сопротивление
      }
      else if (Serial_RxPacket[1] == 0x13) // нажата кнопка «постоянная мощность»
      {
        Serial_SendHMILCD("page CW"); // перейти на страницу CW
        mode = CW;                    // установить режим – постоянная мощность
      }
    }
    else if (Serial_RxPacket[0] == 0x02) // текущая страница – постоянный ток
    {
      if (Serial_RxPacket[1] == 0x10) // нажата кнопка «меню»
      {
        Serial_SendHMILCD("CC.t1.txt=\"OFF\"");  // заголовок – OFF
        Serial_SendHMILCD("CC.b1.txt=\"开启\""); // кнопка – «开启»
        Eload_Out = 0;                           // выход нагрузки – OFF
        GPTM0_CH0_DisablePWMOutput(1);           // выключить ШИМ CH0(пост. напряжение), высокий уровень
        GPTM0_CH2_DisablePWMOutput(0);           // выключить ШИМ CH2(пост. ток 1), низкий уровень
        GPTM0_CH3_DisablePWMOutput(0);           // выключить ШИМ CH3(пост. ток 2), низкий уровень
        Serial_SendHMILCD("page menu");          // перейти в меню
        mode = menu;
      }
      else if (Serial_RxPacket[1] == 0x11) // нажата кнопка «включить» и выход выключен
      {
        Key_ONOFF = 1;
      }
    }
    else if (Serial_RxPacket[0] == 0x03) // текущая страница – постоянное напряжение
    {
      if (Serial_RxPacket[1] == 0x10) // нажата кнопка «меню»
      {
        Serial_SendHMILCD("CV.t1.txt=\"OFF\"");
        Serial_SendHMILCD("CV.b1.txt=\"开启\"");
        Eload_Out = 0;
        GPTM0_CH0_DisablePWMOutput(1); // выключить ШИМ CH0(пост. напряжение), высокий уровень
        GPTM0_CH2_DisablePWMOutput(0); // выключить ШИМ CH2(пост. ток 1), низкий уровень
        GPTM0_CH3_DisablePWMOutput(0); // выключить ШИМ CH3(пост. ток 2), низкий уровень
        Serial_SendHMILCD("page menu");
        mode = menu;
      }
      if (Serial_RxPacket[1] == 0x11) // нажата кнопка «включить» и выход выключен
      {
        Key_ONOFF = 1;
      }
    }
    else if (Serial_RxPacket[0] == 0x04) // текущая страница – постоянное сопротивление
    {
      if (Serial_RxPacket[1] == 0x10) // нажата кнопка «меню»
      {
        Serial_SendHMILCD("CR.t1.txt=\"OFF\"");
        Serial_SendHMILCD("CR.b1.txt=\"开启\"");
        Eload_Out = 0;
        GPTM0_CH0_DisablePWMOutput(1); // выключить ШИМ CH0(пост. напряжение), высокий уровень
        GPTM0_CH2_DisablePWMOutput(0); // выключить ШИМ CH2(пост. ток 1), низкий уровень
        GPTM0_CH3_DisablePWMOutput(0); // выключить ШИМ CH3(пост. ток 2), низкий уровень
        Serial_SendHMILCD("page menu");
        mode = menu;
      }
      if (Serial_RxPacket[1] == 0x11) // нажата кнопка «включить» и выход выключен
      {
        Key_ONOFF = 1;
      }
    }
    else if (Serial_RxPacket[0] == 0x05) // текущая страница – постоянная мощность
    {
      if (Serial_RxPacket[1] == 0x10) // нажата кнопка «меню»
      {
        Serial_SendHMILCD("CW.t1.txt=\"OFF\"");
        Serial_SendHMILCD("CW.b1.txt=\"开启\"");
        Eload_Out = 0;
        GPTM0_CH0_DisablePWMOutput(1); // выключить ШИМ CH0(пост. напряжение), высокий уровень
        GPTM0_CH2_DisablePWMOutput(0); // выключить ШИМ CH2(пост. ток 1), низкий уровень
        GPTM0_CH3_DisablePWMOutput(0); // выключить ШИМ CH3(пост. ток 2), низкий уровень
        Serial_SendHMILCD("page menu");
        mode = menu;
      }
      if (Serial_RxPacket[1] == 0x11) // нажата кнопка «включить» и выход выключен
      {
        Key_ONOFF = 1;
      }
    }

   ```c
else if (Serial_RxPacket[0] == 0xAA) // текущая страница — цифровая клавиатура
    {
      char *temp = Serial_RxPacket;
      temp++;                      // адрес увеличен на 1
      uint16_t temp2 = atoi(temp); // преобразование строки в целое
      if (mode == CC)
      {
        if (temp2 > 10000)
          temp2 = 10000;
        ISET = temp2 / 1000.0;
        Serial_SendHMILCD("CC.x0.val=%d", temp2);

        if (ISET <= 2.5)
        {
          if (Eload_Out == 1)
          {
            GPTM0_CH3_DisablePWMOutput(0);
            GPTM0_CH2_SetOnduty((uint16_t)(ISET * 0.01 * 50 / VDD * 50000));
          }
          else
          {
            GPTM0_CH2_SetOnduty((uint16_t)(ISET * 0.01 * 50 / VDD * 50000));
          }
        }
        else
        {
          if (Eload_Out == 1)
          {
            GPTM0_CH2_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
            GPTM0_CH3_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
            GPTM0_CH3_EnablePWMOutput();
          }
          else
          {
            GPTM0_CH2_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
            GPTM0_CH3_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
          }
        }
      }
      else if (mode == CV)
      {
        VSET = temp2 / 100.0;
        Serial_SendHMILCD("CV.x0.val=%d", temp2);
        if (voltage_dw == 0)
        {
          GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0325 / VDD * 50000));
        }
        else if (voltage_dw == 1)
        {
          GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0947 / VDD * 50000));
        }
        else if (voltage_dw == 2)
        {
          GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.6175 / VDD * 50000));
        }
      }
      else if (mode == CR)
      {
        RSET = temp2 / 100.0;
        Serial_SendHMILCD("CR.x0.val=%d", temp2);
        CR_mode();
      }
      else if (mode == CW)
      {
        PSET = temp2 / 100.0;
        Serial_SendHMILCD("CW.x0.val=%d", temp2);
        CW_mode();
      }
    }
    Serial_RxFlag = 0;
  }
}

/* отображение параметров на экране */
void HMI_Display(void)
{
  if (mode != menu)
  {
    Serial_SendHMILCD("x1.val=%d", (uint16_t)(YVF * 100));       // напряжение
    Serial_SendHMILCD("x2.val=%d", (uint16_t)(YIF * 1000));      // ток
    Serial_SendHMILCD("x3.val=%d", (uint32_t)(YIF * YVF * 100)); // мощность
    Serial_SendHMILCD("x4.val=%d", (uint32_t)(YVF / YIF * 100)); // сопротивление
  }
}

/* обработка данных АЦП */
void AdcFb(void)
{
  VDD = (AD_Value[4] & 0x0000FFFF) * 1.2482 / (AD_Value[3] & 0x0000FFFF); // преобразование отсчёта АЦП в напряжение
                                                                          // VDD = (AD_Value[4] & 0x0000FFFF)  / 4096.0*3.3; // преобразование отсчёта АЦП в напряжение
  // VDD = 3.3;

  if (voltage_dw == 0) // коэффициент деления напряжения 0.0325
  {
    if (YVF_AVG_count < 15)
    {
      YVF_SUM += (AD_Value[0] & 0x0000FFFF);
      YVF_AVG_count++;
    }
    if (YVF_AVG_count == 15)
    {
      YVF = YVF_SUM / YVF_AVG_count / 4096.0 * VDD / 0.0325;
      YVF_AVG_count = 0;
      YVF_SUM = 0;
    }
    // YVF = (AD_Value[0] & 0x0000FFFF) / 4096.0 * VDD / 0.0325;
    if (YVF <= 32) // при напряжении ниже 32 В переключить делитель
    {
      GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_10, RESET);
      GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_11, SET);
      GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0947 / VDD * 50000));
      voltage_dw = 1;
      YVF_AVG_count = 0;
      YVF_SUM = 0;
      // YVF = (AD_Value[0] & 0x0000FFFF) / 4096.0 * VDD / 0.0947;
    }
  }
  else if (voltage_dw == 1) // коэффициент деления напряжения 0.0947
  {
    if (YVF_AVG_count < 15)
    {
      YVF_SUM += (AD_Value[0] & 0x0000FFFF);
      YVF_AVG_count++;
    }
    if (YVF_AVG_count == 15)
    {
      YVF = YVF_SUM / YVF_AVG_count / 4096.0 * VDD / 0.0947;
      YVF_AVG_count = 0;
      YVF_SUM = 0;
    }
    // YVF = (AD_Value[0] & 0x0000FFFF) / 4096.0 * VDD / 0.0947;

    if (YVF >= 34)
    {
      GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_10, RESET);
      GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_11, RESET);
      GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0325 / VDD * 50000));
      voltage_dw = 0;
      YVF_AVG_count = 0;
      YVF_SUM = 0;
      // YVF = (AD_Value[0] & 0x0000FFFF) / 4096.0 * VDD / 0.0325;
    }
    else if (YVF <= 5.0) // при напряжении ниже 5 В переключить делитель
    {
      GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_10, SET);
      GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_11, RESET);
      GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.6175 / VDD * 50000));
      voltage_dw = 2;
      YVF_AVG_count = 0;
      YVF_SUM = 0;
      // YVF = (AD_Value[0] & 0x0000FFFF) / 4096.0 * VDD / 0.6175;
    }
  }
  else if (voltage_dw == 2) // коэффициент деления напряжения 0.6175
  {
    if (YVF_AVG_count < 15)
    {
      YVF_SUM += (AD_Value[0] & 0x0000FFFF);
      YVF_AVG_count++;
    }
    if (YVF_AVG_count == 15)
    {
      YVF = YVF_SUM / YVF_AVG_count / 4096.0 * VDD / 0.6175;
      if (YVF < 0.2)
      {
        YVF = 0;
      }
      YVF_AVG_count = 0;
      YVF_SUM = 0;
    }
    // YVF = (AD_Value[0] & 0x0000FFFF) / 4096.0 * VDD / 0.6175;

    if (YVF > 5.1) // при напряжении выше 5.1 В переключить делитель
    {
      GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_10, RESET);
      GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_11, SET);
      GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0947 / VDD * 50000));
      voltage_dw = 1;
      YVF_AVG_count = 0;
      YVF_SUM = 0;
      // YVF = (AD_Value[0] & 0x0000FFFF) / 4096.0 * VDD / 0.0947;
    }
  }

  if (AVG_count < 15)
  {
    YIF1_SUM += (AD_Value[1] & 0x0000FFFF); // накопление тока MOSFET 1
    YIF2_SUM += (AD_Value[2] & 0x0000FFFF); // накопление тока MOSFET 2
    AVG_count++;
  }
  if (AVG_count == 15)
  {
    YIF1 = YIF1_SUM / AVG_count / 4096.0 * VDD / 50 / 0.01;
    YIF2 = YIF2_SUM / AVG_count / 4096.0 * VDD / 50 / 0.01;
    YIF = YIF1 + YIF2;
    AVG_count = 0;
    YIF1_SUM = 0;
    YIF2_SUM = 0;
  }
  // YIF1 = (AD_Value[1] & 0x0000FFFF) / 4096.0 * VDD / 50 / 0.01;
  // YIF2 = (AD_Value[2] & 0x0000FFFF) / 4096.0 * VDD / 50 / 0.01;
  // YIF = YIF1 + YIF2;
}
```/*Кнопка включения/выключения электронной нагрузки*/
void ONOFF(void)
{
  if (Key_ONOFF == 1 | key[0] == 1) // Нажата кнопка включения
  {
    if (mode == CC) // Постоянный ток
    {
      if (Eload_Out == 0) // Текущее состояние выхода нагрузки — выключено
      {
        Serial_SendHMILCD("CC.t1.txt=\"ON\"");   // В правом верхнем углу экрана отображается ON
        Serial_SendHMILCD("CC.b1.txt=\"关闭\""); // В правом нижнем углу экрана кнопка отображает «关闭»
        Eload_Out = 1;                           // Установить состояние выхода нагрузки в «включено»
        if (ISET <= 2.5)                         // Если установленное значение тока ≤ 2,5 А, использовать только один MOS-транзистор
        {
          GPTM0_CH2_SetOnduty((uint16_t)(ISET * 0.01 * 50 / VDD * 50000));
          GPTM0_CH2_EnablePWMOutput();   // Включить ШИМ канала CH2 (IREF1)
          GPTM0_CH3_DisablePWMOutput(0); // Выключить ШИМ канала CH3 (IREF2) и установить низкий уровень
        }
        else
        {
          GPTM0_CH2_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
          GPTM0_CH3_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
          GPTM0_CH2_EnablePWMOutput(); // Включить ШИМ канала CH2
          GPTM0_CH3_EnablePWMOutput(); // Включить ШИМ канала CH3
        }
        GPTM0_CH0_DisablePWMOutput(0); // VREF — низкий уровень
      }
      else if (Eload_Out == 1) // Текущее состояние выхода нагрузки — включено
      {
        Serial_SendHMILCD("CC.t1.txt=\"OFF\"");
        Serial_SendHMILCD("CC.b1.txt=\"开启\"");
        Eload_Out = 0;
        GPTM0_CH0_DisablePWMOutput(1); // VREF — высокий уровень
        GPTM0_CH2_DisablePWMOutput(0); // IREF1 — низкий уровень
        GPTM0_CH3_DisablePWMOutput(0); // IREF2 — низкий уровень
      }
    }
    else if (mode == CV) // Постоянное напряжение
    {
      if (Eload_Out == 0) // Текущее состояние выхода нагрузки — выключено
      {
        Serial_SendHMILCD("CV.t1.txt=\"ON\"");
        Serial_SendHMILCD("CV.b1.txt=\"关闭\"");
        Eload_Out = 1;
        if (voltage_dw == 0)
        {
          GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0325 / VDD * 50000));
        }
        else if (voltage_dw == 1)
        {
          GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0947 / VDD * 50000));
        }
        else if (voltage_dw == 2)
        {
          GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.6175 / VDD * 50000));
        }
        GPTM0_CH0_EnablePWMOutput();
        GPTM0_CH2_DisablePWMOutput(1);
        GPTM0_CH3_DisablePWMOutput(1);
      }
      else if (Eload_Out == 1) // Текущее состояние выхода нагрузки — включено
      {
        Serial_SendHMILCD("CV.t1.txt=\"OFF\"");
        Serial_SendHMILCD("CV.b1.txt=\"开启\"");
        Eload_Out = 0;
        GPTM0_CH0_DisablePWMOutput(1); // VREF — высокий уровень
        GPTM0_CH2_DisablePWMOutput(0); // IREF1 — низкий уровень
        GPTM0_CH3_DisablePWMOutput(0); // IREF2 — низкий уровень
      }
    }
    else if (mode == CR) // Постоянное сопротивление
    {
      if (Eload_Out == 0) // Текущее состояние выхода нагрузки — выключено
      {
        Serial_SendHMILCD("CR.t1.txt=\"ON\"");
        Serial_SendHMILCD("CR.b1.txt=\"关闭\"");
        Eload_Out = 1;
        float Rtemp = YVF / RSET;
        if (Rtemp > 10)
          Rtemp = 10;
        GPTM0_CH2_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
        GPTM0_CH3_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
        GPTM0_CH2_EnablePWMOutput(); // Включить ШИМ канала CH2
        GPTM0_CH3_EnablePWMOutput(); // Включить ШИМ канала CH3

        GPTM0_CH0_DisablePWMOutput(0); // VREF — низкий уровень
      }
      else if (Eload_Out == 1) // Текущее состояние выхода нагрузки — включено
      {
        Serial_SendHMILCD("CR.t1.txt=\"OFF\"");
        Serial_SendHMILCD("CR.b1.txt=\"开启\"");
        Eload_Out = 0;
        GPTM0_CH0_DisablePWMOutput(1); // VREF — высокий уровень
        GPTM0_CH2_DisablePWMOutput(0); // IREF1 — низкий уровень
        GPTM0_CH3_DisablePWMOutput(0); // IREF2 — низкий уровень
      }
    }
    else if (mode == CW) // Постоянная мощность
    {
      if (Eload_Out == 0) // Текущее состояние выхода нагрузки — выключено
      {
        Serial_SendHMILCD("CW.t1.txt=\"ON\"");
        Serial_SendHMILCD("CW.b1.txt=\"关闭\"");
        Eload_Out = 1;
        float Ptemp = PSET / YVF;
        if (Ptemp > 10)
          Ptemp = 10;
        GPTM0_CH2_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
        GPTM0_CH3_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
        GPTM0_CH2_EnablePWMOutput(); // Включить ШИМ канала CH2
        GPTM0_CH3_EnablePWMOutput(); // Включить ШИМ канала CH3

        GPTM0_CH0_DisablePWMOutput(0); // VREF — низкий уровень
      }
      else if (Eload_Out == 1) // Текущее состояние выхода нагрузки — включено
      {
        Serial_SendHMILCD("CW.t1.txt=\"OFF\"");
        Serial_SendHMILCD("CW.b1.txt=\"开启\"");
        Eload_Out = 0;
        GPTM0_CH0_DisablePWMOutput(1); // VREF — высокий уровень
        GPTM0_CH2_DisablePWMOutput(0); // IREF1 — низкий уровень
        GPTM0_CH3_DisablePWMOutput(0); // IREF2 — низкий уровень
      }
    }
		
    Key_ONOFF = 0;
    key[0] = 0;
  }
// https://blog.zeruns.com
  if (Eload_Out == 0)
  {
    GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_15, SET);
  }
  else if (Eload_Out == 1)
  {
    GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_15, RESET);
  }
}

/*Управление вентилятором охлаждения*/
void FAN(void)
{
  uint16_t P = (uint16_t)(YIF * YVF);
  if (P >= 6) // Вентилятор запускается при мощности ≥ 6 Вт
  {
    MCTM0_CH0_EnablePWMOutput(); // Включить вентилятор
    if (P < 15)
    {
      MCTM0_CH0_SetOnduty(50); // Коэффициент заполнения ШИМ 50 %
    }
    else if (P >= 15 && P < 20)
    {
      MCTM0_CH0_SetOnduty(60);
    }
    else if (P >= 20 && P < 25)
    {
      MCTM0_CH0_SetOnduty(70);
    }
    else if (P >= 25 && P < 30)
    {
      MCTM0_CH0_SetOnduty(80);
    }
    else if (P >= 30 && P < 35)
    {
      MCTM0_CH0_SetOnduty(90);
    }
    else if (P >= 35)
    {
      MCTM0_CH0_SetOnduty(100);
    }
  }
  else if (P <= 5)
  {
    MCTM0_CH0_DisablePWMOutput(0); // Выключить ШИМ канала MCTM0_CH0 (вентилятор) и установить низкий уровень
  }
}

/*Режим постоянной мощности*/
void CW_mode(void)
{
  float Ptemp = PSET / YVF;
  if (Ptemp > 10)
    Ptemp = 10;
  if (Eload_Out == 1)
  {
    GPTM0_CH2_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
    GPTM0_CH3_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
  }
  else
  {
    GPTM0_CH2_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
    GPTM0_CH3_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
  }
}

/*Режим постоянного сопротивления*/
void CR_mode(void)
{
  float Rtemp = YVF / RSET;
  if (Rtemp > 10)
    Rtemp = 10;
  if (Eload_Out == 1)
  {
    GPTM0_CH2_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
    GPTM0_CH3_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
  }
  else
  {
    GPTM0_CH2_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
    GPTM0_CH3_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
  }
}

// Программа конечного автомата кнопок
void key_status_check(uint8_t key_num, uint8_t KEY)
{
  switch (KEY_Status[key_num][g_keyStatus])
  {
  // Кнопка отпущена (исходное состояние)
  case KS_RELEASE:
  {
    // Обнаружен низкий уровень — начать подавление дребезга
    if (KEY == 0)
    {
      KEY_Status[key_num][g_keyStatus] = KS_SHAKE;
    }
  }
  break;

  // Дребезг
  case KS_SHAKE:
  {
    if (KEY == 1)
    {
      KEY_Status[key_num][g_keyStatus] = KS_RELEASE;
    }
    else
    {
      KEY_Status[key_num][g_keyStatus] = KS_PRESS;
    }
  }
  break;

  // Стабильное короткое нажатие
  case KS_PRESS:
  {
    // Обнаружен высокий уровень — начать подавление дребезга
    if (KEY == 1)
    {
      KEY_Status[key_num][g_keyStatus] = KS_SHAKE;
    }
  }
  break;

  default:
    break;
  }

  if (KEY_Status[key_num][g_keyStatus] != KEY_Status[key_num][g_nowKeyStatus])
  {
    // Текущее состояние — отпущено, предыдущее — нажато
    if ((KEY_Status[key_num][g_keyStatus] == KS_RELEASE) && (KEY_Status[key_num][g_lastKeyStatus] == KS_PRESS))
    {
      key[key_num] = 1;
    }
    KEY_Status[key_num][g_lastKeyStatus] = KEY_Status[key_num][g_nowKeyStatus];
    KEY_Status[key_num][g_nowKeyStatus] = KEY_Status[key_num][g_keyStatus];
  }
}

Другие рекомендуемые проекты с открытым исходным кодом

Рекомендуем к прочтению- Рекомендации по VPS/облачным серверам с высокой производительностью и низкой ценой: https://blog.zeruns.com/archives/383.html