Открытый умный электронный нагрузка на базе CH32V307, проект с конкурса встраиваемых систем

Открытый проект интеллектуальной электронной нагрузки на базе CH32V307VCT6, работа-участник конкурса встраиваемых систем, выложена схема, плата, исходники и отчёт.

2023, прикладная ветка конкурса «Встраиваемые микросхемы и системное проектирование», диплом II степени.

За полмесяца переделал ранее собранную версию на HT32, перенёс прошивку на CH32 и добавил RT-Thread, немного оптимизировал код и успел закончить к дедлайну. Сделано на скорую руку, не ругайте.

Демо-видео: https://www.bilibili.com/video/BV1Zu4y1m7Zd/

Проект на HT32F52352 (Holtek Cup) тут: https://blog.zeruns.com/archives/784.html

Материалы выложены только для обучения, самому собирать не рекомендую — на платформе LCSC есть куда более продуманные открытые электронные нагрузки!

Ссылка на проект в LCSC Open Source: https://url.zeruns.com/Et4x4

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

На сайте WCH можно бесплатно заказать образцы плат: https://url.zeruns.com/h9a99

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

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

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

Описание проекта

Головной микроконтроллер — CH32V307VCT6 (WCH). Питание от аккумулятора 18650, компактно и портативно.

Принцип: ЦАП МК выдаёт опорное напряжение; усилитель сравнивает его с напряжением, пропорциональным току/напряжению на входе, и управляет ключом MOS. Так реализуются режимы стабилизации тока и напряжения.

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

Радиатор — боковой 2U-серверный под LGA-1356/1366.

Разработка прошивки — RT-Thread Studio, схема и плата — EasyEDA.

Максимальные параметры: 100 V, 10 A, 200 Вт.

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

Фотографий мало, смотрите демо-видео.





Скачать файлы

В архиве: схема, проект EasyEDA, Gerber, исходники, прошивка экрана, даташиты.

123 Pan (без ограничения скорости): https://www.123pan.com/ps/2Y9Djv-6NevH.html

Baidu Pan: https://pan.baidu.com/s/17YSlBZ6F1M18k7JGa7FlVA?pwd=buxx код: buxx

Где купить компоненты

Компоненты удобнее всего брать в LCSC: https://activity.szlcsc.com/invite/D03E5B9CEAAE70A4.html

Схема

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



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

Контроллер

PCB

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

Top

Bottom

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

Top

Bottom

Контроллер

Top

Bottom

Другие открытые проекты- Сделал открытый трёхфазный измеритель электроэнергии, удобно отслеживать домашнее потребление: https://blog.zeruns.com/archives/771.html

Основной код

Файл main.c

/********************************** (C) COPYRIGHT *******************************
 * File Name          : main.c
 * Author             : WCH
 * Version            : V1.0.0
 * Date               : 2021/06/06
 * Description        : Main program body.
 * Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
 * SPDX-License-Identifier: Apache-2.0
 * https://blog.zeruns.com
 *******************************************************************************/
#include "ch32v30x.h"
#include<rtthread.h>
#include <rtdevice.h>
#include <stdlib.h>
#include <rthw.h>
#include "drivers/pin.h"
#include <board.h>
#include <rtdbg.h>
#include <u8g2_port.h>
#include <qpid.h>

#include "USART.h"
#include "KEY.h"
#include "DAC.h"
#include "PWM.h"

/* Глобальный typedef */

/* Глобальное определение */
#define WDT_DEVICE_NAME    "wdt"    /* Имя устройства сторожевого таймера */
static rt_device_t wdg_dev; /* Дескриптор устройства сторожевого таймера */
/* Опорное напряжение АЦП */
#define VREF 3.3
/* Напряжение питания */
#define VCC 3.3
/* Данные калибровки компенсации */
#define V0_COMP 1.000   // Коэффициент 0.0325 напряжения
#define V1_COMP 1.000   // Коэффициент 0.0947 напряжения
#define V2_COMP 1.000   // Коэффициент 0.6175 напряжения
#define YIF1_COMP 1.00  // Компенсация тока датчика MOS 1
#define YIF2_COMP 1.00  // Компенсация тока датчика MOS 2
#define DAC1_COMP  1.00    // Коэффициент выхода DAC1(VREF)
#define IREF2_COMP  1.00  // Коэффициент выхода IREF2
#define DAC2_COMP   1.00   // Компенсация выхода DAC2(IREF1)
/* Количество усреднений выборок АЦП */
#define ADC_count 3
/* Коэффициент фильтрации ФНЧ первого порядка */
#define dPower1 0.5
/* Номера выводов, определяются через drv_gpio.c */
#define OLED_I2C_PIN_SCL    22  //PB6
#define OLED_I2C_PIN_SDA    23  //PB7
#define LED2                59  //PD11
#define LED1                60  //PD12
#define MCU_G0              62  //PD14
#define MCU_G1              63  //PD14

/* Глобальные переменные */
u8g2_t u8g2;                    // Структура u8g2
rt_uint16_t AD_Value[4];           // Данные выборок АЦП

// Перечисление режимов
enum mode_type
{
    menu = 0, // Меню
    CC,       // Постоянный ток
    CV,       // Постоянное напряжение
    CR,       // Постоянное сопротивление
    CW        // Постоянная мощность
};

volatile uint8_t Eload_Out = 0;                 // Состояние выхода электронной нагрузки
volatile uint8_t mode = menu;                   // Текущий режим
volatile uint8_t voltage_dw = 0;                // Диапазон выборки напряжения: 0=0.0325×, 2=0.6175×, 1=0.0947×
volatile double YVF, YIF1, YIF2, YIF, VBAT;     // Текущие напряжение и ток
volatile double ISET, VSET, RSET, PSET;         // Установки тока, напряжения, сопротивления, мощности
volatile uint32_t YVF_SUM, YIF1_SUM, YIF2_SUM, VBAT_SUM;  // Суммы для усреднения
volatile uint8_t AVG_count = 0;                 // Счётчик усреднения тока
volatile uint8_t YVF_AVG_count = 0;             // Счётчик усреднения напряжения
volatile uint8_t VBAT_count = 0;                      // Счётчик усреднения напряжения батареи
volatile uint8_t Key_ONOFF = 0;                 // Флаг нажатия кнопки вкл/выкл

static qpid_t qpid_CC;                          // Указатель данных ПИД-регулятора
static qpid_t qpid_CV;
static qpid_t qpid_CR;
static qpid_t qpid_CW;
static double I_SET, V_SET, R_SET, P_SET;

/* Прототипы функций */
void OLED_Init(void);
static int IWDG_Init();
static void thread1_sysLED_entry(void *parameter);
static void thread2_OLED_entry(void *parameter);
static void thread3_ADC_entry(void *parameter);
static void thread4_HMI_GetDate_entry(void *parameter);
static void thread5_ONOFF_entry(void *parameter);
static void thread7_HMI_Display_entry(void *parameter);
static void thread8_FAN_entry(void *parameter);
static void thread9_CWCR_entry(void *parameter);
static void thread10_BlueTooth_entry(void *parameter);
void CW_mode(void);
void CR_mode(void);
void key123(void);
void Thread_Init(void);
void SYS_Init(void);
void PID(void);

/*********************************************************************
 * @fn      main
 *
 * @brief   Главная программа.
 *
 * @return  none
 */
int main(void)
{
    rt_kprintf("MCU: CH32V307\n");
    rt_kprintf("SysClk: %dHz\n", SystemCoreClock);
    SYS_Init();

    while (1)
    {
        // Защита по току и мощности
        if (YIF > 10 | YVF * YIF > 300)
        {
            if (Eload_Out == 1)
            {
                Key_ONOFF = 1;
            }
        }
        rt_thread_mdelay(20);
    }
}

/* Инициализация системы */
void SYS_Init(void)
{
    IWDG_Init();    // Инициализировать сторожевой таймер
    UART_Init();   // Инициализировать UART3
    HMILCD_Send("page 0");   // Перейти на стартовую страницу
    Dac_Init();     // Инициализировать ЦАП
    PWM_Init();     // Инициализировать ШИМ

    rt_pin_mode(MCU_G0, PIN_MODE_OUTPUT);   // Настроить пин на выход
    rt_pin_mode(MCU_G1, PIN_MODE_OUTPUT);
    rt_pin_write(MCU_G0, PIN_LOW);        // Установить низкий уровень
    rt_pin_write(MCU_G1, PIN_LOW);

    Thread_Init();   // Создать потоки
}

/* Инициализация потоков */
void Thread_Init(void)
{
    rt_thread_t tid = NULL; //Указатель блока управления потоком
    /* Создать поток */
    tid = rt_thread_create("SYS_LED", thread1_sysLED_entry, NULL, 512, 30, 5);
    //Создать поток SYS_LED, функция входа thread1_sysLED_entry, параметр NULL, стек 256 байт, приоритет 30, квант 5 тиков
    if (tid != RT_NULL) // Проверка успешности создания
    {
        if (rt_thread_startup(tid) == RT_EOK) // Запустить поток
            LOG_D("thread sysLED create success");
    }
    else
    {
        LOG_E("thread1 sysLED create failed...");
    }
    /*
     tid = rt_thread_create("OLED_Display", thread2_OLED_entry, NULL, 2048, 25, 30);
     if (tid != RT_NULL)
     {
     if (rt_thread_startup(tid) == RT_EOK) // 启动线程
     LOG_D("thread2 OLED create success");
     }
     else
     {
     LOG_E("thread2_OLED create failed...");
     }*/

    tid = rt_thread_create("ADC", thread3_ADC_entry, NULL, 1536, 18, 30);
    if (tid != RT_NULL)
    {
        if (rt_thread_startup(tid) == RT_EOK) // Запустить поток
            LOG_D("thread3 ADC create success");
    }
    else
    {
        LOG_E("thread3 ADC create failed...");
    }

    tid = rt_thread_create("HMI_GetDate", thread4_HMI_GetDate_entry, NULL, 2048, 23, 30);
    if (tid != RT_NULL)
    {
        if (rt_thread_startup(tid) == RT_EOK) // Запустить поток
            LOG_D("thread4 HMI_GetDate create success");
    }
    else
    {
        LOG_E("thread4 HMI_GetDate create failed...");
    }

    tid = rt_thread_create("ONOFF", thread5_ONOFF_entry, NULL, 1024, 15, 25);
    if (tid != RT_NULL)
    {
        if (rt_thread_startup(tid) == RT_EOK) // Запустить поток
            LOG_D("thread5 ONOFF create success");
    }
    else
    {
        LOG_E("thread5 ONOFF create failed...");
    }
    tid = rt_thread_create("KEY", thread6_KEY_entry, NULL, 512, 20, 20);
    if (tid != RT_NULL)
    {
        if (rt_thread_startup(tid) == RT_EOK) // Запустить поток
            LOG_D("thread6 KEY create success");
    }
    else
    {
        LOG_E("thread6 KEY create failed...");
    }

    tid = rt_thread_create("HMI_Display", thread7_HMI_Display_entry, NULL, 1024, 25, 30);
    if (tid != RT_NULL)
    {
        if (rt_thread_startup(tid) == RT_EOK) // Запустить поток
            LOG_D("thread7 HMI_Display create success");
    }
    else
    {
        LOG_E("thread7 HMI_Display create failed...");
    }

    tid = rt_thread_create("FAN", thread8_FAN_entry, NULL, 512, 26, 15);
    if (tid != RT_NULL)
    {
        if (rt_thread_startup(tid) == RT_EOK) // Запустить поток
            LOG_D("thread8 FAN create success");
    }
    else
    {
        LOG_E("thread8 FAN create failed...");
    }

    tid = rt_thread_create("CWCR", thread9_CWCR_entry, NULL, 512, 19, 15);
    if (tid != RT_NULL)
    {
        if (rt_thread_startup(tid) == RT_EOK) // Запустить поток
            LOG_D("thread9 CWCR create success");
    }
    else
    {
        LOG_E("thread9 CWCR create failed...");
    }

    tid = rt_thread_create("BlueTooth", thread10_BlueTooth_entry, NULL, 2048, 23, 30);
    if (tid != RT_NULL)
    {
        if (rt_thread_startup(tid) == RT_EOK) // Запустить поток
            LOG_D("thread10 BlueTooth create success");
    }
    else
    {
        LOG_E("thread10 BlueTooth create failed...");
    }

}/* Первичный фильтр нижних частот
 * Возвращаемое значение: iData — отфильтрованное по первому порядку значение выборки */
double lowV1(double com1)
{
    static double iLastData1;    // предыдущее значение
    double iData1;               // текущее вычисленное значение
    iData1 = (com1 * dPower1) + (1 - dPower1) * iLastData1; // вычисление
    iLastData1 = iData1;                                     // сохранение текущих данных
    return iData1;                                         // возврат данных
}
double lowV2(double com1)
{
    static double iLastData2;    // предыдущее значение
    double iData1;               // текущее вычисленное значение
    iData1 = (com1 * dPower1) + (1 - dPower1) * iLastData2; // вычисление
    iLastData2 = iData1;                                     // сохранение текущих данных
    return iData1;                                         // возврат данных
}
u16 lowV3(u16 com1)
{
    static u16 iLastData3;    // предыдущее значение
    u16 iData1;               // текущее вычисленное значение
    iData1 = (com1 * dPower1) + (1 - dPower1) * iLastData3; // вычисление
    iLastData3 = iData1;                                     // сохранение текущих данных
    return iData1;                                         // возврат данных
}
u16 lowV4(u16 com1)
{
    static u16 iLastData3;    // предыдущее значение
    u16 iData1;               // текущее вычисленное значение
    iData1 = (com1 * 0.1) + (1 - 0.1) * iLastData3; // вычисление
    iLastData3 = iData1;                                     // сохранение текущих данных
    return iData1;                                         // возврат данных
}

static void idle_hook(void)
{
    /* в callback-функции потока простоя «подкармливаем» сторожевой таймер */
    rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_KEEPALIVE, NULL);
    //rt_kprintf("feed the dog!\n ");
}

static int IWDG_Init()
{
    rt_err_t ret = RT_EOK;
    rt_uint32_t timeout = 1; /* время переполнения, секунды */
    /* ищем сторожевой таймер по имени устройства, получаем дескриптор */
    wdg_dev = rt_device_find(WDT_DEVICE_NAME);
    if (!wdg_dev)
    {
        rt_kprintf("find %s failed!\n", WDT_DEVICE_NAME);
        return RT_ERROR;
    }
    /* инициализируем устройство */
    rt_device_init(wdg_dev);
    /* устанавливаем время переполнения сторожевого таймера */
    ret = rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_SET_TIMEOUT, &timeout);
    if (ret != RT_EOK)
    {
        rt_kprintf("set %s timeout failed!\n", WDT_DEVICE_NAME);
        return RT_ERROR;
    }
    /* запускаем сторожевой таймер */
    ret = rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_START, RT_NULL);
    if (ret != RT_EOK)
    {
        rt_kprintf("start %s failed!\n", WDT_DEVICE_NAME);
        return -RT_ERROR;
    }
    /* устанавливаем callback-функцию потока простоя */
    rt_thread_idle_sethook(idle_hook);

    return ret;
}

/* функция потока 1: мигание системного светодиода */
static void thread1_sysLED_entry(void *parameter)
{
    /* пин LED1 в режиме выхода */
    rt_pin_mode(LED1, PIN_MODE_OUTPUT);
    /* по умолчанию низкий уровень */
    rt_pin_write(LED1, PIN_LOW);
    while (1)
    {
        /* поток 1 работает с низким приоритетом, постоянно мигает LED1 */
        rt_pin_write(LED1, !rt_pin_read(LED1));
        rt_thread_mdelay(500);
    }
}

/* функция потока 2: вывод информации на OLED-дисплей */
static void thread2_OLED_entry(void *parameter)
{
    // инициализация
    u8g2_Setup_ssd1306_i2c_128x64_noname_f(&u8g2, U8G2_R0, u8x8_byte_rtthread_hw_i2c, u8x8_gpio_and_delay_rtthread);
    u8g2_InitDisplay(&u8g2);
    u8g2_SetPowerSave(&u8g2, 0);

    u8g2_InitDisplay(&u8g2);
    u8g2_SetPowerSave(&u8g2, 0);
    while (1)
    {
        char String[26];
        u8g2_ClearBuffer(&u8g2);
        u8g2_SetFont(&u8g2, u8g2_font_wqy15_t_chinese3); // китайский набор символов

        float V0 = AD_Value[0] * VREF / 4096.0;
        sprintf(String, "AD0:%d V:%d.%d%d%d", AD_Value[0], (uint8_t) V0, (uint16_t)(V0 * 10.0) % 10,
                (uint16_t)(V0 * 100.0) % 100 % 10, (uint16_t)(V0 * 1000.0) % 1000 % 100 % 10); // форматирование
        u8g2_DrawStr(&u8g2, 0, 15, String);

        float V1 = AD_Value[1] * VREF / 4096.0;
        sprintf(String, "AD1:%d V:%d.%d%d%d", AD_Value[1], (uint8_t) V1, (uint16_t)(V1 * 10.0) % 10,
                (uint16_t)(V1 * 100.0) % 100 % 10, (uint16_t)(V1 * 1000.0) % 1000 % 100 % 10); // форматирование
        u8g2_DrawStr(&u8g2, 0, 31, String);

        float V2 = AD_Value[2] * VREF / 4096.0;
        sprintf(String, "AD2:%d V:%d.%d%d%d", AD_Value[2], (uint8_t) V2, (uint16_t)(V2 * 10.0) % 10,
                (uint16_t)(V2 * 100.0) % 100 % 10, (uint16_t)(V2 * 1000.0) % 1000 % 100 % 10); // форматирование
        u8g2_DrawStr(&u8g2, 0, 47, String);

        float V3 = AD_Value[3] * VREF / 4096.0;
        sprintf(String, "AD3:%d V:%d.%d%d%d", AD_Value[3], (uint8_t) V3, (uint16_t)(V3 * 10.0) % 10,
                (uint16_t)(V3 * 100.0) % 100 % 10, (uint16_t)(V3 * 1000.0) % 1000 % 100 % 10); // форматирование
        u8g2_DrawStr(&u8g2, 0, 63, String);
        u8g2_SendBuffer(&u8g2); // отправка буфера
        rt_thread_mdelay(100);  // задержка 100 мс
    }
}

/* функция потока 3: обработка данных АЦП */
static void thread3_ADC_entry(void *parameter)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE); // тактирование GPIOA и ADC
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);      // тактирование DMA1

    RCC_ADCCLKConfig(RCC_PCLK2_Div6);       // делитель ADC 6 (72 МГц/6=12 МГц), частота ≤14 МГц

    GPIO_InitTypeDef GPIO_InitStructure = { 0 };                // структура для GPIO
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;    // выводы
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; // аналоговый вход
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5); // канал 0, 239.5 циклов
    ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_239Cycles5); // канал 1
    ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_239Cycles5); // канал 2
    ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_239Cycles5); // канал 3

    ADC_InitTypeDef ADC_InitStructure = { 0 };
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;  // независимый режим
    ADC_InitStructure.ADC_ScanConvMode = ENABLE;        // сканирование включено
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;  // непрерывное преобразование
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // программный запуск
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; // выравнивание вправо
    ADC_InitStructure.ADC_NbrOfChannel = 4;                // 4 канала
    ADC_Init(ADC1, &ADC_InitStructure);

    ADC_Cmd(ADC1, ENABLE);      // включить ADC1

    ADC_ResetCalibration(ADC1); // сброс калибровки

    while (ADC_GetResetCalibrationStatus(ADC1))
        ; // ждём окончания сброса

    ADC_StartCalibration(ADC1); // начать калибровку

    while (ADC_GetCalibrationStatus(ADC1))
        ;      // ждём окончания

    DMA_DeInit(DMA1_Channel1); // сброс DMA
    DMA_InitTypeDef DMA_InitStructure;                      // структура DMA
    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32) &ADC1->RDATAR; // адрес регистра данных ADC
    DMA_InitStructure.DMA_MemoryBaseAddr = (u32) AD_Value;          // адрес памяти
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;              // источник — периферия
    DMA_InitStructure.DMA_BufferSize = 4;                           // размер буфера
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // без инкремента периферии
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;         // инкремент памяти
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; // 16 бит
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;         // 16 бит
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;     // циклический режим
    DMA_InitStructure.DMA_Priority = DMA_Priority_High; // высокий приоритет
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;        // mem-to-mem выключен
    DMA_Init(DMA1_Channel1, &DMA_InitStructure);        // инициализация канала 1 DMA1

    DMA_Cmd(DMA1_Channel1, ENABLE); // запустить DMA1_Channel1
    ADC_DMACmd(ADC1, ENABLE);       // разрешить DMA-запросы ADC
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);       // программный старт преобразования

    qpid_init(&qpid_CC);            // инициализация PID-регулятора
    qpid_set_lmt(&qpid_CC, 0, 10);  // установка ограничений
    qpid_set_ratio(&qpid_CC, 1, 0.001, 0.1);    // коэффициенты

    qpid_init(&qpid_CV);                // инициализация PID-регулятора
    qpid_set_lmt(&qpid_CV, 31.0, 100);  // установка ограничений
    qpid_set_ratio(&qpid_CV, 0.35, 0.005, 0.001);    // коэффициенты

}qpid_init(&qpid_CW);                // инициализация данных ПИД-регулирования
    qpid_set_lmt(&qpid_CW, 0.1, 200);  // установка пределов ПИД
    qpid_set_ratio(&qpid_CW, 0.5, 0.003, 0.001);    // установка коэффициентов управления

    qpid_init(&qpid_CR);                // инициализация данных ПИД-регулирования
    qpid_set_lmt(&qpid_CR, 0.1, 1000);  // установка пределов ПИД
    qpid_set_ratio(&qpid_CR, 0.35, 0.003, 0.001);    // установка коэффициентов управления

    while (1)
    {
        /*rt_kprintf("AD0:%d V:%f\n", AD_Value[0], AD_Value[0] / 4095.0 * VREF);
         rt_kprintf("AD1:%d V:%4.3f\n", AD_Value[1], (float) AD_Value[1] / 4095.0 * VREF);
         rt_kprintf("AD2:%d V:%4.3f\n", AD_Value[2], AD_Value[2] / 4095.0 * VREF);*/

        if (voltage_dw == 0) // коэффициент деления напряжения 0.0325
        {
            if (YVF_AVG_count < ADC_count) // при значении меньше 15 накопление выборок
            {
                YVF_SUM += AD_Value[0];
                YVF_AVG_count++;
            }
            if (YVF_AVG_count == ADC_count)
            {
                YVF = YVF_SUM / YVF_AVG_count * VREF / 4096.0 / 0.0325 * V0_COMP;    // расчёт напряжения
                YVF_AVG_count = 0;
                YVF_SUM = 0;
            }
            if (YVF <= 31.0) // при напряжении ниже 31 В переключение диапазона
            {
                rt_pin_write(MCU_G0, PIN_LOW);
                rt_pin_write(MCU_G1, PIN_HIGH); // делитель 0.0947
                if (Eload_Out == 1)
                {
                    DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(VSET * 0.0947 * 4096 / VREF * DAC1_COMP + 0.5)); // установка выхода DAC1, управление постоянным напряжением
                }
                voltage_dw = 1;
                YVF_AVG_count = 0;
                YVF_SUM = 0;
                qpid_set_lmt(&qpid_CV, 4.6, 33.5); // установка пределов ПИД
                qpid_set_ratio(&qpid_CV, 0.38, 0.0035, 0.0005);    // установка коэффициентов управления
            }
        }
        else if (voltage_dw == 1) // делитель 0.0947
        {
            if (YVF_AVG_count < ADC_count)
            {
                YVF_SUM += AD_Value[0];
                YVF_AVG_count++;
            }
            if (YVF_AVG_count == ADC_count)
            {
                YVF = YVF_SUM / YVF_AVG_count * VREF / 4096.0 / 0.0947 * V1_COMP;
                YVF_AVG_count = 0;
                YVF_SUM = 0;
            }

            if (YVF >= 33.5)  // при напряжении выше 33.5 В переключение на 0.0325
            {
                rt_pin_write(MCU_G0, PIN_LOW);
                rt_pin_write(MCU_G1, PIN_LOW);
                if (Eload_Out == 1)
                {
                    DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(VSET * 0.0325 * 4096 / VREF * DAC1_COMP + 0.5)); // установка выхода DAC1, управление постоянным напряжением
                }
                voltage_dw = 0;
                YVF_AVG_count = 0;
                YVF_SUM = 0;
                qpid_set_lmt(&qpid_CV, 31.0, 100); // установка пределов ПИД
                qpid_set_ratio(&qpid_CV, 0.5, 0.005, 0.0005);    // установка коэффициентов управления
            }
            else if (YVF <= 4.6) // при напряжении ниже 4.6 В переключение диапазона
            {
                rt_pin_write(MCU_G0, PIN_HIGH);
                rt_pin_write(MCU_G1, PIN_LOW);
                if (Eload_Out == 1)
                {
                    uint16_t vset_pwm = (uint16_t)(VSET * 0.6175 * 4096 / VREF * DAC1_COMP + 0.5);
                    if (vset_pwm > 4065)
                        vset_pwm = 4095;
                    DAC_SetChannel1Data(DAC_Align_12b_R, vset_pwm); // установка выхода DAC1, управление постоянным напряжением
                }
                voltage_dw = 2;
                YVF_AVG_count = 0;
                YVF_SUM = 0;
                qpid_set_lmt(&qpid_CV, 0.01, 5.1); // установка пределов ПИД
                qpid_set_ratio(&qpid_CV, 0.26, 0.0025, 0.0005);    // установка коэффициентов управления
            }
        }
        else if (voltage_dw == 2) // делитель 0.6175
        {
            if (YVF_AVG_count < ADC_count)
            {
                YVF_SUM += AD_Value[0];
                YVF_AVG_count++;
            }
            if (YVF_AVG_count == ADC_count)
            {
                YVF = YVF_SUM / YVF_AVG_count * VREF / 4096.0 / 0.6175 * V2_COMP;
                if (YVF < 0.15)
                    YVF = 0;
                YVF_AVG_count = 0;
                YVF_SUM = 0;
            }

            if (YVF >= 5.1) // при напряжении выше 5.1 В переключение диапазона
            {
                rt_pin_write(MCU_G0, PIN_LOW);
                rt_pin_write(MCU_G1, PIN_HIGH);
                if (Eload_Out == 1)
                {
                    DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(VSET * 0.0947 * 4096 / VREF * DAC1_COMP + 0.5)); // установка выхода DAC1, управление постоянным напряжением
                }
                voltage_dw = 1;
                YVF_AVG_count = 0;
                YVF_SUM = 0;
                qpid_set_lmt(&qpid_CV, 4.6, 33.5); // установка пределов ПИД
                qpid_set_ratio(&qpid_CV, 0.38, 0.0035, 0.0005);    // установка коэффициентов управления
            }
        }

        if (AVG_count < ADC_count)
        {
            YIF1_SUM += AD_Value[1]; // накопление тока MOS1
            YIF2_SUM += AD_Value[2]; // накопление тока MOS2
            AVG_count++;
        }
        if (AVG_count == ADC_count)
        {
            YIF1 = YIF1_SUM / AVG_count * VREF / 4096.0 / 50 / 0.01 * YIF1_COMP - 0.007;
            YIF2 = YIF2_SUM / AVG_count * VREF / 4096.0 / 50 / 0.01 * YIF2_COMP - 0.007;
            YIF = YIF1 + YIF2;
            if (YIF < 0.008)
                YIF = 0;
            AVG_count = 0;
            YIF1_SUM = 0;
            YIF2_SUM = 0;
            PID();
        }

        if (VBAT_count < 5)
        {
            VBAT_SUM += lowV4(AD_Value[3]); // накопление выборок напряжения батареи
            VBAT_count++;
        }
        if (VBAT_count == 5)
        {
            VBAT = VBAT_SUM / VBAT_count * VREF / 4096.0 / 0.3535;
            VBAT_count = 0;
            VBAT_SUM = 0;
        }
        rt_thread_mdelay(5);
    }
}

// https://blog.zeruns.com/* Функция входа потока 4, обрабатывающая данные, присланные с экрана */
static void thread4_HMI_GetDate_entry(void *parameter)
{
    HMILCD_Send("CC.x0.val=0"); // Сброс отображаемого на экране заданного значения тока
    HMILCD_Send("CV.x0.val=0"); // Сброс отображаемого на экране заданного значения напряжения
    HMILCD_Send("CR.x0.val=0"); // Сброс отображаемого на экране заданного значения сопротивления
    HMILCD_Send("CW.x0.val=0"); // Сброс отображаемого на экране заданного значения мощности
    while (1)
    {
        if (Serial3_RxFlag == 1)
        {
            if (Serial3_RxPacket[0] == 0x01) // Текущая страница — главное меню
            {
                if (Serial3_RxPacket[1] == 0x10) // Нажата кнопка постоянного тока
                {
                    HMILCD_Send("page CC"); // Переключиться на страницу режима постоянного тока
                    mode = CC;                    // Установить текущий режим — постоянный ток
                }
                else if (Serial3_RxPacket[1] == 0x11) // Нажата кнопка постоянного напряжения
                {
                    HMILCD_Send("page CV"); // Переключиться на страницу постоянного напряжения
                    mode = CV;                    // Установить текущий режим — постоянное напряжение
                }
                else if (Serial3_RxPacket[1] == 0x12) // Нажата кнопка постоянного сопротивления
                {
                    HMILCD_Send("page CR"); // Переключиться на страницу постоянного сопротивления
                    mode = CR;                    // Установить текущий режим — постоянное сопротивление
                }
                else if (Serial3_RxPacket[1] == 0x13) // Нажата кнопка постоянной мощности
                {
                    HMILCD_Send("page CW"); // Переключиться на страницу постоянной мощности
                    mode = CW;                    // Установить текущий режим — постоянная мощность
                }
            }
            else if (Serial3_RxPacket[0] == 0x02) // Текущая страница — режим постоянного тока
            {
                if (Serial3_RxPacket[1] == 0x10) // Нажата кнопка меню
                {
                    HMILCD_Send("CC.t1.txt=\"OFF\"");  // В правом верхнем углу экрана отобразить OFF
                    HMILCD_Send("CC.b1.txt=\"开启\""); // В правом нижнем углу экрана кнопка покажет «开启»
                    Eload_Out = 0;                           // Состояние выхода нагрузки — выключено
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдаёт высокий уровень, отключает постоянное напряжение
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 выдаёт низкий уровень, отключает постоянный ток
                    PWM_SetCCR4(0);  // Установить IREF2
                    HMILCD_Send("page menu");          // Переключиться на страницу меню
                    mode = menu;
                }
                else if (Serial3_RxPacket[1] == 0x11) // Нажата кнопка включения и текущее состояние выхода — выключено
                {
                    Key_ONOFF = 1;
                }
            }
            else if (Serial3_RxPacket[0] == 0x03) // Текущая страница — режим постоянного напряжения
            {
                if (Serial3_RxPacket[1] == 0x10) // Нажата кнопка меню
                {
                    HMILCD_Send("CV.t1.txt=\"OFF\"");
                    HMILCD_Send("CV.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдаёт высокий уровень, отключает постоянное напряжение
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 выдаёт низкий уровень, отключает постоянный ток
                    PWM_SetCCR4(0);  // Установить IREF2
                    HMILCD_Send("page menu");
                    mode = menu;
                }
                if (Serial3_RxPacket[1] == 0x11) // Нажата кнопка включения и текущее состояние выхода — выключено
                {
                    Key_ONOFF = 1;
                }
            }
            else if (Serial3_RxPacket[0] == 0x04) // Текущая страница — режим постоянного сопротивления
            {
                if (Serial3_RxPacket[1] == 0x10) // Нажата кнопка меню
                {
                    HMILCD_Send("CR.t1.txt=\"OFF\"");
                    HMILCD_Send("CR.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдаёт высокий уровень, отключает постоянное напряжение
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 выдаёт низкий уровень, отключает постоянный ток
                    PWM_SetCCR4(0);  // Установить IREF2
                    HMILCD_Send("page menu");
                    mode = menu;
                }
                if (Serial3_RxPacket[1] == 0x11) // Нажата кнопка включения и текущее состояние выхода — выключено
                {
                    Key_ONOFF = 1;
                }
            }
            else if (Serial3_RxPacket[0] == 0x05) // Текущая страница — режим постоянной мощности
            {
                if (Serial3_RxPacket[1] == 0x10) // Нажата кнопка меню
                {
                    HMILCD_Send("CW.t1.txt=\"OFF\"");
                    HMILCD_Send("CW.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдаёт высокий уровень, отключает постоянное напряжение
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 выдаёт низкий уровень, отключает постоянный ток
                    PWM_SetCCR4(0);  // Установить IREF2
                    HMILCD_Send("page menu");
                    mode = menu;
                }
                if (Serial3_RxPacket[1] == 0x11) // Нажата кнопка включения и текущее состояние выхода — выключено
                {
                    Key_ONOFF = 1;
                }
            }

            else if (Serial3_RxPacket[0] == 0xAA) // Текущая страница — цифровая клавиатура
            {
                char *temp = Serial3_RxPacket;
                temp++;                      // Адрес увеличен на 1
                uint16_t temp2 = atoi(temp); // Преобразование строки в целое
                if (mode == CC)
                {
                    if (temp2 > 1000)
                        temp2 = 1000;
                    ISET = temp2 / 100.0;
                    HMILCD_Send("CC.x0.val=%d", temp2);

                    if (Eload_Out == 1)
                    {
                        DAC_SetChannel1Data(DAC_Align_12b_R, 0);
                        if (ISET <= 2.5)
                        {
                            // Установить значение выхода DAC2 для управления постоянным током, +0.5 — округление
                            DAC_SetChannel2Data(DAC_Align_12b_R,
                                    (uint16_t)(ISET * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
                            PWM_SetCCR4(0);  // Установить IREF2
                        }
                        else
                        {
                            // Установить значение выхода DAC2 для управления постоянным током, +0.5 — округление
                            DAC_SetChannel2Data(DAC_Align_12b_R,
                                    (uint16_t)(ISET / 2.0 * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
                            PWM_SetCCR4((uint16_t)(ISET / 2.0 * 0.01 * 50 / VCC * 50000 * IREF2_COMP + 0.5)); // Установить IREF2
                        }

                    }

                }
                else if (mode == CV)
                {
                    VSET = temp2 / 100.0;
                    HMILCD_Send("CV.x0.val=%d", temp2);
                    if (Eload_Out == 1)
                    {
                        DAC_SetChannel2Data(DAC_Align_12b_R, 4095); // Установить IREF1
                        PWM_SetCCR4(50000);  // Установить IREF2
                        if (voltage_dw == 0)
                        {
                            DAC_SetChannel1Data(DAC_Align_12b_R,
                                    (uint16_t)(VSET * 0.0325 * 4096 / VREF * DAC1_COMP + 0.5)); // Установить значение выхода DAC1 для управления постоянным напряжением
                        }
                        else if (voltage_dw == 1)
                        {
                            DAC_SetChannel1Data(DAC_Align_12b_R,
                                    (uint16_t)(VSET * 0.0947 * 4096 / VREF * DAC1_COMP + 0.5)); // Установить значение выхода DAC1 для управления постоянным напряжением
                        }
                        else if (voltage_dw == 2)
                        {
                            uint16_t vset_pwm = (uint16_t)(VSET * 0.6175 * 4096 / VREF * DAC1_COMP + 0.5);
                            if (vset_pwm > 4065)
                                vset_pwm = 4095;
                            DAC_SetChannel1Data(DAC_Align_12b_R, vset_pwm); // Установить значение выхода DAC1 для управления постоянным напряжением
                        }
                    }
                }
                else if (mode == CR)
                {
                    RSET = temp2 / 100.0;
                    HMILCD_Send("CR.x0.val=%d", temp2);
                    //CR_mode();
                }
                else if (mode == CW)
                {
                    PSET = temp2 / 100.0;
                    HMILCD_Send("CW.x0.val=%d", temp2);
                    //CW_mode();
                }
            }
            Serial3_RxFlag = 0;
        }
        key123();
        rt_thread_mdelay(35);
    }

}/*Постоянная мощность режим*/
void CW_mode(void)
{
    double Ptemp = PSET / YVF;
    if (Ptemp > 10)
        Ptemp = 10;
    if (Eload_Out == 1)
    {
        // Установить выходное значение DAC2, управление постоянным током, +0.5 для округления
        DAC_SetChannel2Data(DAC_Align_12b_R, (uint16_t)(Ptemp / 2.0 * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
        PWM_SetCCR4((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VCC * 50000 * IREF2_COMP + 0.5));  // Установить IREF2
    }
    else
    {
        DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдаёт высокий уровень, отключить постоянное напряжение
        DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 выдаёт низкий уровень, отключить постоянный ток
        PWM_SetCCR4(0);  // Установить IREF2
    }
}

/*Постоянное сопротивление режим*/
void CR_mode(void)
{
    double Rtemp = YVF / RSET;
    if (Rtemp > 10)
        Rtemp = 10;
    if (Eload_Out == 1)
    {
        // Установить выходное значение DAC2, управление постоянным током, +0.5 для округления
        DAC_SetChannel2Data(DAC_Align_12b_R, (uint16_t)(Rtemp / 2.0 * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
        PWM_SetCCR4((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VCC * 50000 * IREF2_COMP + 0.5));  // Установить IREF2
    }
    else
    {
        DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдаёт высокий уровень, отключить постоянное напряжение
        DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 выдаёт низкий уровень, отключить постоянный ток
        PWM_SetCCR4(0);  // Установить IREF2
    }
}

/* PID-регулирование  */
void PID(void)
{
    double Ptemp, Rtemp;

    if (mode == CC && Eload_Out == 1)
    {
        qpid_set_dst(&qpid_CC, ISET);   // Установить целевое значение PID
        I_SET = qpid_cal_pos(&qpid_CC, YIF);    // Расчёт PID
        if (ISET <= 2.5)
        {
            // Установить выходное значение DAC2, управление постоянным током, +0.5 для округления
            DAC_SetChannel2Data(DAC_Align_12b_R, (uint16_t)(I_SET * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
        }
        else
        {
            // Установить выходное значение DAC2, управление постоянным током, +0.5 для округления
            DAC_SetChannel2Data(DAC_Align_12b_R, (uint16_t)(I_SET / 2.0 * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
            PWM_SetCCR4((uint16_t)(I_SET / 2.0 * 0.01 * 50 / VCC * 50000 * IREF2_COMP + 0.5));  // Установить IREF2
        }
    }
    if (mode == CV && Eload_Out == 1)
    {
        qpid_set_dst(&qpid_CV, VSET);           // Установить целевое значение PID
        V_SET = qpid_cal_pos(&qpid_CV, YVF);    // Расчёт PID
        if (voltage_dw == 0)
        {
            DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(V_SET * 0.0325 * 4096 / VREF * DAC1_COMP + 0.5));
            // Установить выходное значение DAC1, управление постоянным напряжением
        }
        else if (voltage_dw == 1)
        {
            DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(V_SET * 0.0947 * 4096 / VREF * DAC1_COMP + 0.5));
            // Установить выходное значение DAC1, управление постоянным напряжением
        }
        else if (voltage_dw == 2)
        {
            DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(V_SET * 0.6175 * 4096 / VREF * DAC1_COMP + 0.5));
            // Установить выходное значение DAC1, управление постоянным напряжением
        }
    }
    if (mode == CW && Eload_Out == 1)
    {
        qpid_set_dst(&qpid_CW, PSET);   // Установить целевое значение PID
        P_SET = qpid_cal_pos(&qpid_CW, YIF * YVF);    // Расчёт PID
        Ptemp = P_SET / YVF;
        if (Ptemp > 10)
            Ptemp = 10;
        // Установить выходное значение DAC2, управление постоянным током, +0.5 для округления
        DAC_SetChannel2Data(DAC_Align_12b_R, (uint16_t)(Ptemp / 2.0 * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
        PWM_SetCCR4((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VCC * 50000 * IREF2_COMP + 0.5));  // Установить IREF2
    }
    if (mode == CR && Eload_Out == 1)
    {
        qpid_set_dst(&qpid_CR, RSET);   // Установить целевое значение PID
        R_SET = qpid_cal_pos(&qpid_CR, YVF / YIF);    // Расчёт PID
        Rtemp = YVF / R_SET;
        if (Rtemp > 10)
            Rtemp = 10;
        // Установить выходное значение DAC2, управление постоянным током, +0.5 для округления
        DAC_SetChannel2Data(DAC_Align_12b_R, (uint16_t)(Rtemp / 2.0 * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
        PWM_SetCCR4((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VCC * 50000 * IREF2_COMP + 0.5));  // Установить IREF2
    }

}

/* Функция обработки кнопок */
void key123(void)
{
    if (key[2] == 1)    // Кнопка 2, переключение
    {
        if (mode == menu)
        {
            HMILCD_Send("page CC"); // Перейти на страницу постоянного тока
            mode = CC;              // Установить текущий режим постоянного тока
        }
        else if (mode == CC)
        {
            if (Eload_Out == 1)
            {
                HMILCD_Send("CC.t1.txt=\"OFF\"");
                HMILCD_Send("CC.b1.txt=\"开启\"");
                Eload_Out = 0;
                DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдаёт высокий уровень, отключить постоянное напряжение
                DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 выдаёт низкий уровень, отключить постоянный ток
                PWM_SetCCR4(0);  // Установить IREF2
            }
            HMILCD_Send("page CV"); // Перейти на страницу постоянного напряжения
            mode = CV;              // Установить текущий режим постоянного напряжения
        }
        else if (mode == CV)
        {
            if (Eload_Out == 1)
            {
                HMILCD_Send("CV.t1.txt=\"OFF\"");
                HMILCD_Send("CV.b1.txt=\"开启\"");
                Eload_Out = 0;
                DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдаёт высокий уровень, отключить постоянное напряжение
                DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 выдаёт низкий уровень, отключить постоянный ток
                PWM_SetCCR4(0);  // Установить IREF2
            }
            HMILCD_Send("page CR"); // Перейти на страницу постоянного сопротивления
            mode = CR;              // Установить текущий режим постоянного сопротивления
        }
        else if (mode == CR)
        {
            if (Eload_Out == 1)
            {
                HMILCD_Send("CR.t1.txt=\"OFF\"");
                HMILCD_Send("CR.b1.txt=\"开启\"");
                Eload_Out = 0;
                DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдаёт высокий уровень, отключить постоянное напряжение
                DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 выдаёт низкий уровень, отключить постоянный ток
                PWM_SetCCR4(0);  // Установить IREF2
            }
            HMILCD_Send("page CW"); // Перейти на страницу постоянной мощности
            mode = CW;              // Установить текущий режим постоянной мощности
        }
        else if (mode == CW)
        {
            if (Eload_Out == 1)
            {
                HMILCD_Send("CW.t1.txt=\"OFF\"");
                HMILCD_Send("CW.b1.txt=\"开启\"");
                Eload_Out = 0;
                DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдаёт высокий уровень, отключить постоянное напряжение
                DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 выдаёт низкий уровень, отключить постоянный ток
                PWM_SetCCR4(0);  // Установить IREF2
            }
            HMILCD_Send("page menu"); // Перейти на страницу меню
            mode = menu;              // Установить текущий режим меню
        }
        key[2] = 0;
    }
    if (key[3] == 1)    // Кнопка 3, меню
    {
        if (Eload_Out == 1)
            Key_ONOFF = 1;
        rt_thread_mdelay(15);
        HMILCD_Send("page menu"); // Перейти на страницу меню
        mode = menu;              // Установить текущий режим меню
        key[3] = 0;
    }
}/* Функция входа потока 5, обработка кнопки включения/выключения электронной нагрузки */
static void thread5_ONOFF_entry(void *parameter)
{
    /* Пин LED2 в режиме выхода */
    rt_pin_mode(LED2, PIN_MODE_OUTPUT);
    /* По умолчанию высокий уровень */
    rt_pin_write(LED2, PIN_HIGH);
    while (1)
    {
        if (Key_ONOFF == 1 | key[1] == 1) // Нажата кнопка включения
        {
            if (mode == CC) // Постоянный ток
            {
                if (Eload_Out == 0) // Текущее состояние выхода нагрузки выключено
                {
                    HMILCD_Send("CC.t1.txt=\"ON\"");   // В правом верхнем углу экрана отображается ON
                    HMILCD_Send("CC.b1.txt=\"关闭\"");    // В правом нижнем углу экрана кнопка отображает "关闭"
                    Eload_Out = 1;                           // Установить состояние выхода нагрузки в включено
                    DAC_SetChannel1Data(DAC_Align_12b_R, 0);
                    if (ISET <= 2.5)
                    {
                        // Установить значение выхода DAC2 для управления постоянным током, +0.5 для округления
                        DAC_SetChannel2Data(DAC_Align_12b_R,
                                (uint16_t)(ISET * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
                        PWM_SetCCR4(0);  // Установить IREF2
                    }
                    else
                    {
                        // Установить значение выхода DAC2 для управления постоянным током, +0.5 для округления
                        DAC_SetChannel2Data(DAC_Align_12b_R,
                                (uint16_t)(ISET / 2.0 * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
                        PWM_SetCCR4((uint16_t)(ISET / 2.0 * 0.01 * 50 / VCC * 50000 * IREF2_COMP + 0.5));  // Установить IREF2
                    }
                }
                else if (Eload_Out == 1) // Текущее состояние выхода нагрузки включено
                {
                    HMILCD_Send("CC.t1.txt=\"OFF\"");
                    HMILCD_Send("CC.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдает высокий уровень, выключить постоянное напряжение
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 выдает низкий уровень, выключить постоянный ток
                    PWM_SetCCR4(0);  // Установить IREF2
                }
            }
            else if (mode == CV) // Постоянное напряжение
            {
                if (Eload_Out == 0) // Текущее состояние выхода нагрузки выключено
                {
                    HMILCD_Send("CV.t1.txt=\"ON\"");
                    HMILCD_Send("CV.b1.txt=\"关闭\"");
                    Eload_Out = 1;
                    DAC_SetChannel2Data(DAC_Align_12b_R, 4095); // Установить IREF1
                    PWM_SetCCR4(50000);  // Установить IREF2
                    if (voltage_dw == 0)
                    {
                        DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(VSET * 0.0325 * 4096 / VREF * DAC1_COMP + 0.5));
                        // Установить значение выхода DAC1 для управления постоянным напряжением
                    }
                    else if (voltage_dw == 1)
                    {
                        DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(VSET * 0.0947 * 4096 / VREF * DAC1_COMP + 0.5));
                        // Установить значение выхода DAC1 для управления постоянным напряжением
                    }
                    else if (voltage_dw == 2)
                    {
                        DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(VSET * 0.6175 * 4096 / VREF * DAC1_COMP + 0.5));
                        // Установить значение выхода DAC1 для управления постоянным напряжением
                    }
                }
                else if (Eload_Out == 1) // Текущее состояние выхода нагрузки включено
                {
                    HMILCD_Send("CV.t1.txt=\"OFF\"");
                    HMILCD_Send("CV.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдает высокий уровень, выключить постоянное напряжение
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 выдает низкий уровень, выключить постоянный ток
                    PWM_SetCCR4(0);  // Установить IREF2
                }
            }
            else if (mode == CR) // Постоянное сопротивление
            {
                if (Eload_Out == 0) // Текущее состояние выхода нагрузки выключено
                {
                    HMILCD_Send("CR.t1.txt=\"ON\"");
                    HMILCD_Send("CR.b1.txt=\"关闭\"");
                    Eload_Out = 1;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 0);
                    CR_mode();
                }
                else if (Eload_Out == 1) // Текущее состояние выхода нагрузки включено
                {
                    HMILCD_Send("CR.t1.txt=\"OFF\"");
                    HMILCD_Send("CR.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдает высокий уровень, выключить постоянное напряжение
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 выдает низкий уровень, выключить постоянный ток
                    PWM_SetCCR4(0);  // Установить IREF2
                }
            }
            else if (mode == CW) // Постоянная мощность
            {
                if (Eload_Out == 0) // Текущее состояние выхода нагрузки выключено
                {
                    HMILCD_Send("CW.t1.txt=\"ON\"");
                    HMILCD_Send("CW.b1.txt=\"关闭\"");
                    Eload_Out = 1;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 0);
                    CW_mode();
                }
                else if (Eload_Out == 1) // Текущее состояние выхода нагрузки включено
                {
                    HMILCD_Send("CW.t1.txt=\"OFF\"");
                    HMILCD_Send("CW.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдает высокий уровень, выключить постоянное напряжение
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 выдает низкий уровень, выключить постоянный ток
                    PWM_SetCCR4(0);  // Установить IREF2
                }
            }
            Key_ONOFF = 0;
            key[1] = 0;
        }

        if (Eload_Out == 0)
        {
            rt_pin_write(LED2, SET);
        }
        else if (Eload_Out == 1)
        {
            rt_pin_write(LED2, RESET);
        }
        rt_thread_mdelay(35);
    }

}

/* Функция входа потока 7, отображение параметров на экране */
static void thread7_HMI_Display_entry(void *parameter)
{
    while (1)
    {
        if (mode != menu)
        {
            double V = lowV1(YVF);
            double I = lowV2(YIF);
            HMILCD_Send("x1.val=%d", (uint16_t)(V * 100));       // Отобразить напряжение
            HMILCD_Send("x2.val=%d", (uint16_t)(I * 1000));      // Отобразить ток
            HMILCD_Send("x3.val=%d", (uint32_t)(I * V * 100)); // Отобразить мощность
            HMILCD_Send("x4.val=%d", (uint32_t)(V / I * 100)); // Отобразить сопротивление
            HMILCD_Send("x5.val=%d", (uint32_t)(VBAT * 100));     // Отобразить сопротивление
        }
        rt_thread_mdelay(50);
    }
}

/* Функция входа потока 8, управление вентилятором охлаждения */
static void thread8_FAN_entry(void *parameter)
{
    while (1)
    {
        uint16_t P = (uint16_t)(YIF * YVF + 0.5);
        if (P >= 13) // Запустить вентилятор при мощности >13 Вт
        {
            FAN_PWM_ON();
            if (P < 20)
            {
                FAN_PWM_SetCCR(20); // Скважность управления вентилятором 20%
            }
            else if (P >= 20 && P < 25)
            {
                FAN_PWM_SetCCR(30);
            }
            else if (P >= 25 && P < 30)
            {
                FAN_PWM_SetCCR(40);
            }
            else if (P >= 30 && P < 35)
            {
                FAN_PWM_SetCCR(50);
            }
            else if (P >= 35 && P < 40)
            {
                FAN_PWM_SetCCR(60);
            }
            else if (P >= 40 && P < 45)
            {
                FAN_PWM_SetCCR(70);
            }
            else if (P >= 45 && P < 50)
            {
                FAN_PWM_SetCCR(80);
            }
            else if (P >= 50 && P < 60)
            {
                FAN_PWM_SetCCR(90);
            }
            else if (P >= 60)
            {
                FAN_PWM_SetCCR(100);
            }
        }
        else if (P <= 8)
        {
            FAN_PWM_SetCCR(0); // Выключить ШИМ вентилятора и выдать низкий уровень
            FAN_PWM_OFF();
        }
        rt_thread_mdelay(200);
    }
}

/* Функция входа потока 9, управление режимами постоянной мощности и сопротивления */
static void thread9_CWCR_entry(void *parameter)
{
    while (1)
    {
        if (mode == CW)
        {
            CW_mode();
        }
        if (mode == CR)
        {
            CR_mode();
        }
        rt_thread_mdelay(50);
    }
}/* Функция входа потока 10, обрабатывающая данные, полученные по Bluetooth */
static void thread10_BlueTooth_entry(void *parameter)
{
    HMILCD_Send("CC.x0.val=0"); // Сброс отображаемого на экране заданного значения тока
    HMILCD_Send("CV.x0.val=0"); // Сброс отображаемого на экране заданного значения напряжения
    HMILCD_Send("CR.x0.val=0"); // Сброс отображаемого на экране заданного значения сопротивления
    HMILCD_Send("CW.x0.val=0"); // Сброс отображаемого на экране заданного значения мощности
    while (1)
    {
        if (Serial5_RxFlag == 1)
        {
            if (Serial5_RxPacket[0] == 0x01) // Текущая страница — главное меню
            {
                if (Serial5_RxPacket[1] == 0x10) // Нажата кнопка постоянного тока
                {
                    if (mode == CV)
                    {
                        HMILCD_Send("CV.t1.txt=\"OFF\"");
                        HMILCD_Send("CV.b1.txt=\"开启\"");

                    }
                    else if (mode == CR)
                    {
                        HMILCD_Send("CR.t1.txt=\"OFF\"");
                        HMILCD_Send("CR.b1.txt=\"开启\"");

                    }
                    else if (mode == CW)
                    {
                        HMILCD_Send("CW.t1.txt=\"OFF\"");
                        HMILCD_Send("CW.b1.txt=\"开启\"");

                    }
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдаёт высокий уровень, отключение постоянного напряжения
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 выдаёт низкий уровень, отключение постоянного тока
                    PWM_SetCCR4(0);  // Установка IREF2
                    Eload_Out = 0;
                    HMILCD_Send("page CC"); // Переключение на страницу режима постоянного тока
                    mode = CC;                    // Установка текущего режима — постоянный ток
                }
                else if (Serial5_RxPacket[1] == 0x11) // Нажата кнопка постоянного напряжения
                {
                    if (mode == CC)
                    {
                        HMILCD_Send("CC.t1.txt=\"OFF\"");
                        HMILCD_Send("CC.b1.txt=\"开启\"");

                    }
                    else if (mode == CR)
                    {
                        HMILCD_Send("CR.t1.txt=\"OFF\"");
                        HMILCD_Send("CR.b1.txt=\"开启\"");

                    }
                    else if (mode == CW)
                    {
                        HMILCD_Send("CW.t1.txt=\"OFF\"");
                        HMILCD_Send("CW.b1.txt=\"开启\"");

                    }
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдаёт высокий уровень, отключение постоянного напряжения
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 выдаёт низкий уровень, отключение постоянного тока
                    PWM_SetCCR4(0);  // Установка IREF2
                    Eload_Out = 0;
                    HMILCD_Send("page CV"); // Переключение на страницу постоянного напряжения
                    mode = CV;                    // Установка текущего режима — постоянное напряжение
                }
                else if (Serial5_RxPacket[1] == 0x12) // Нажата кнопка постоянного сопротивления
                {
                    if (mode == CC)
                    {
                        HMILCD_Send("CC.t1.txt=\"OFF\"");
                        HMILCD_Send("CC.b1.txt=\"开启\"");

                    }
                    else if (mode == CV)
                    {
                        HMILCD_Send("CV.t1.txt=\"OFF\"");
                        HMILCD_Send("CV.b1.txt=\"开启\"");

                    }
                    else if (mode == CW)
                    {
                        HMILCD_Send("CW.t1.txt=\"OFF\"");
                        HMILCD_Send("CW.b1.txt=\"开启\"");

                    }
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдаёт высокий уровень, отключение постоянного напряжения
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 выдаёт низкий уровень, отключение постоянного тока
                    PWM_SetCCR4(0);  // Установка IREF2
                    Eload_Out = 0;
                    HMILCD_Send("page CR"); // Переключение на страницу постоянного сопротивления
                    mode = CR;                    // Установка текущего режима — постоянное сопротивление
                }
                else if (Serial5_RxPacket[1] == 0x13) // Нажата кнопка постоянной мощности
                {
                    if (mode == CV)
                    {
                        HMILCD_Send("CV.t1.txt=\"OFF\"");
                        HMILCD_Send("CV.b1.txt=\"开启\"");

                    }
                    else if (mode == CR)
                    {
                        HMILCD_Send("CR.t1.txt=\"OFF\"");
                        HMILCD_Send("CR.b1.txt=\"开启\"");

                    }
                    else if (mode == CC)
                    {
                        HMILCD_Send("CC.t1.txt=\"OFF\"");
                        HMILCD_Send("CC.b1.txt=\"开启\"");

                    }
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдаёт высокий уровень, отключение постоянного напряжения
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 выдаёт низкий уровень, отключение постоянного тока
                    PWM_SetCCR4(0);  // Установка IREF2
                    Eload_Out = 0;
                    HMILCD_Send("page CW"); // Переключение на страницу постоянной мощности
                    mode = CW;                    // Установка текущего режима — постоянная мощность
                }
            }
            else if (Serial5_RxPacket[0] == 0x08)    // Возврат в меню
            {
                if (mode == CC)
                {
                    HMILCD_Send("CC.t1.txt=\"OFF\"");  // В правом верхнем углу экрана заголовок OFF
                    HMILCD_Send("CC.b1.txt=\"开启\""); // В правом нижнем углу кнопка «开启»
                    Eload_Out = 0;                           // Состояние выхода нагрузки — выключено
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдаёт высокий уровень, отключение постоянного напряжения
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 выдаёт низкий уровень, отключение постоянного тока
                    PWM_SetCCR4(0);  // Установка IREF2
                    HMILCD_Send("page menu");          // Переключение на страницу меню
                    mode = menu;
                }
                else if (mode == CV)
                {
                    HMILCD_Send("CV.t1.txt=\"OFF\"");
                    HMILCD_Send("CV.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдаёт высокий уровень, отключение постоянного напряжения
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 выдаёт низкий уровень, отключение постоянного тока
                    PWM_SetCCR4(0);  // Установка IREF2
                    HMILCD_Send("page menu");
                    mode = menu;
                }
                else if (mode == CR)
                {
                    HMILCD_Send("CR.t1.txt=\"OFF\"");
                    HMILCD_Send("CR.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдаёт высокий уровень, отключение постоянного напряжения
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 выдаёт низкий уровень, отключение постоянного тока
                    PWM_SetCCR4(0);  // Установка IREF2
                    HMILCD_Send("page menu");
                    mode = menu;
                }
                else if (mode == CW)
                {
                    HMILCD_Send("CW.t1.txt=\"OFF\"");
                    HMILCD_Send("CW.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдаёт высокий уровень, отключение постоянного напряжения
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 выдаёт низкий уровень, отключение постоянного тока
                    PWM_SetCCR4(0);  // Установка IREF2
                    HMILCD_Send("page menu");
                    mode = menu;
                }
            }
            else if (Serial5_RxPacket[0] == 0x09)    // Кнопка включения
            {
                Key_ONOFF = 1;
            }

            else if (Serial5_RxPacket[0] == 0xAA) // Текущая страница — цифровая клавиатура
            {
                char *temp = Serial5_RxPacket;
                temp++;                      // Адрес увеличен на 1
                uint16_t temp2 = atoi(temp); // Преобразование строки в целое
                if (mode == CC)
                {
                    if (temp2 > 1000)
                        temp2 = 1000;
                    ISET = temp2 / 100.0;
                    HMILCD_Send("CC.x0.val=%d", temp2);```c
if (Eload_Out == 1)
                    {
                        DAC_SetChannel1Data(DAC_Align_12b_R, 0);
                        if (ISET <= 2.5)
                        {
                            // Установить выходное значение DAC2 для управления стабилизацией тока, +0.5 для округления
                            DAC_SetChannel2Data(DAC_Align_12b_R,
                                    (uint16_t)(ISET * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
                            PWM_SetCCR4(0);  // Установить IREF2
                        }
                        else
                        {
                            // Установить выходное значение DAC2 для управления стабилизацией тока, +0.5 для округления
                            DAC_SetChannel2Data(DAC_Align_12b_R,
                                    (uint16_t)(ISET / 2.0 * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
                            PWM_SetCCR4((uint16_t)(ISET / 2.0 * 0.01 * 50 / VCC * 50000 * IREF2_COMP + 0.5)); // Установить IREF2
                        }

                    }

                }
                else if (mode == CV)
                {
                    VSET = temp2 / 100.0;
                    HMILCD_Send("CV.x0.val=%d", temp2);
                    if (Eload_Out == 1)
                    {
                        DAC_SetChannel2Data(DAC_Align_12b_R, 4095); // Установить IREF1
                        PWM_SetCCR4(50000);  // Установить IREF2
                        if (voltage_dw == 0)
                        {
                            DAC_SetChannel1Data(DAC_Align_12b_R,
                                    (uint16_t)(VSET * 0.0325 * 4096 / VREF * DAC1_COMP + 0.5)); // Установить выходное значение DAC1 для управления стабилизацией напряжения
                        }
                        else if (voltage_dw == 1)
                        {
                            DAC_SetChannel1Data(DAC_Align_12b_R,
                                    (uint16_t)(VSET * 0.0947 * 4096 / VREF * DAC1_COMP + 0.5)); // Установить выходное значение DAC1 для управления стабилизацией напряжения
                        }
                        else if (voltage_dw == 2)
                        {
                            uint16_t vset_pwm = (uint16_t)(VSET * 0.6175 * 4096 / VREF * DAC1_COMP + 0.5);
                            if (vset_pwm > 4065)
                                vset_pwm = 4095;
                            DAC_SetChannel1Data(DAC_Align_12b_R, vset_pwm); // Установить выходное значение DAC1 для управления стабилизацией напряжения
                        }
                    }
                }
                else if (mode == CR)
                {
                    RSET = temp2 / 100.0;
                    HMILCD_Send("CR.x0.val=%d", temp2);
                    CR_mode();
                }
                else if (mode == CW)
                {
                    PSET = temp2 / 100.0;
                    HMILCD_Send("CW.x0.val=%d", temp2);
                    CW_mode();
                }
            }
            Serial5_RxFlag = 0;
        }
        rt_thread_mdelay(40);
    }

}

Рекомендуем к прочтению