Mã nguồn mở tải điện tử thông minh dựa trên CH32V307, sản phẩm cuộc thi nhúng mở mã nguồn

Tải trọng điện tử thông minh dựa trên CH32V307VCT6 mã nguồn mở, tác phẩm cuộc thi nhúng mã nguồn mở, bao gồm sơ đồ nguyên lý, PCB, mã nguồn chương trình, báo cáo tác phẩm.

Cuộc thi Thiết kế Chip và Hệ thống Nhúng năm 2023, hạng hai quốc gia.

Được sửa đổi trong nửa tháng dựa trên tải trọng điện tử phiên bản Holtek được thực hiện trước đó, chuyển chương trình sang vi điều khiển CH32, sử dụng hệ thống RT-Thread, tối ưu hóa mã, hoàn thành trong thời gian gấp rút, được thực hiện rất tầm thường, xin đừng chỉ trích.

Video trình diễn tác phẩm: https://www.bilibili.com/video/BV1Zu4y1m7Zd/

Tải trọng điện tử thông minh mã nguồn mở dựa trên HT32F52352, tác phẩm Holtek Cup mã nguồn mở: https://blog.zeruns.com/archives/784.html

Tác phẩm mã nguồn mở này chỉ dành cho tham khảo và học tập, không khuyến khích sao chép, có nhiều tác phẩm tải trọng điện tử mã nguồn mở tốt hơn và hoàn thiện hơn trên nền tảng mã nguồn mở Lichee!

Liên kết mã nguồn mở của dự án trên nền tảng mã nguồn mở Lichee: https://url.zeruns.com/Et4x4

Nhóm trao đổi kỹ thuật điện tử/vi điều khiển: 2169025065

Trang web chính thức Qinheng có thể yêu cầu miễn phí mẫu bảng phát triển: https://url.zeruns.com/h9a99

Tải trọng điện tử là gì

Tải trọng điện tử là một thiết bị điện tử được sử dụng để mô phỏng môi trường tải trọng thực tế nhằm kiểm tra hiệu suất của nguồn điện hoặc mạch điện tử. So với việc sử dụng điện trở có thể điều chỉnh công suất lớn hoặc sợi điện trở truyền thống, tải trọng điện tử có nhiều ưu điểm như tham số có thể điều chỉnh, dễ sử dụng, v.v. Cho dù là phát triển dự án điện tử chuyên nghiệp hay những người yêu thích điện tử nghiệp dư, máy tải trọng điện tử là một trong những thiết bị không thể thiếu.

Tải trọng điện tử có thể được chia thành tải trọng điện tử xoay chiều và tải trọng điện tử một chiều dựa trên loại nguồn điện được kiểm tra. Từ chức năng, các loại phổ biến bao gồm bốn loại: dòng điện không đổi, điện áp không đổi, điện trở không đổi và công suất không đổi. Vì hầu hết các nguồn điện phổ biến mà chúng ta thấy đều là nguồn điện một chiều có điện áp không đổi. Khi kiểm tra loại nguồn điện này, điều chính được kiểm tra là khả năng xuất dòng điện của nó. Do đó, trong hầu hết các tình huống ứng dụng, tải trọng điện tử dòng điện không đổi là loại phổ biến nhất. Tải trọng điện tử có thể được chia thành hai loại: điều khiển số và tương tự dựa trên phương pháp điều khiển. So với tải trọng điện tử được điều khiển bằng mạch tương tự thuần túy, tải trọng điện tử được điều khiển số sử dụng điều khiển kỹ thuật số, trực quan hơn trong điều chỉnh tham số, có nhiều chức năng, mở rộng đơn giản, và có thể dễ dàng thực hiện tự động hóa kiểm tra.

Giới thiệu dự án

Tác phẩm này sử dụng vi điều khiển Qinheng CH32V307VCT6 làm chip điều khiển chính để thiết kế tải trọng điện tử, được cấp nguồn bằng pin lithium 18650, dễ dàng mang theo.

Phương pháp điều khiển là thông qua vi điều khiển xuất ra một điện áp một chiều thông qua DAC làm điện áp tham chiếu được so sánh với điện áp được khuếch đại bằng bộ khuếch đại hoạt động và lấy mẫu dòng điện/điện áp, đầu ra của bộ khuếch đại hoạt động điều khiển ống MOS, từ đó thực hiện điện áp không đổi/dòng điện không đổi.

Màn hình cảm ứng sử dụng màn hình cổng nối tiếp 2,8 inch của Taojingchi, mô hình: TJC3224T028_011R.

Tản nhiệt sử dụng tản nhiệt máy chủ 2U với chân 1356/1366, thổi bên.

Chương trình dự án được phát triển bằng RT-Thread Studio. Thiết kế mạch sử dụng phần mềm EDA Lichee.

Điện áp và dòng điện đầu vào tối đa là 100V/10A, công suất tối đa 200W.

Hình ảnh thực tế

Lúc đó không chụp nhiều ảnh, chỉ tìm được vài cái, bạn có thể xem video trình diễn.

Địa chỉ tải tài liệu

Các tài liệu trong liên kết dưới đây bao gồm: sơ đồ nguyên lý mạch, tệp dự án EDA Lichee, tệp PCB để sản xuất bảng, mã nguồn chương trình, tệp dự án màn hình cổng nối tiếp, hướng dẫn chip.

Địa chỉ tải xuống không giới hạn tốc độ 123 Cloud Disk: https://www.123pan.com/ps/2Y9Djv-6NevH.html

Địa chỉ tải xuống Baidu Netdisk: https://pan.baidu.com/s/17YSlBZ6F1M18k7JGa7FlVA?pwd=buxx Mã trích xuất: buxx

Địa chỉ mua linh kiện

Khuyến nghị mua linh kiện tại Lichee Mall: https://activity.szlcsc.com/invite/D03E5B9CEAAE70A4.html

Sơ đồ nguyên lý

Bảng công suất



Bảng nguồn điện

Bảng điều khiển

PCB

Bảng công suất

Lớp trên

Lớp dưới

Bảng nguồn điện

Lớp trên

Lớp dưới

Bảng điều khiển

Lớp trên

Lớp dưới

Các dự án mã nguồn mở khác được khuyến nghị- Đã tạo bộ thu thập dữ liệu điện ba pha mã nguồn mở, có thể dễ dàng giám sát tình hình sử dụng điện ở nhà: https://blog.zeruns.com/archives/771.html

Mã chính

Tệp 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
``````c
#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"

/* Định nghĩa kiểu toàn cục */

/* Định nghĩa toàn cục */
#define WDT_DEVICE_NAME    "wdt"    /* Tên thiết bị watchdog */
static rt_device_t wdg_dev; /* Xử lý thiết bị watchdog */
/* Điện áp tham chiếu ADC */
#define VREF 3.3
/* Điện áp nguồn */
#define VCC 3.3
/* Dữ liệu bù hiệu chỉnh */
#define V0_COMP 1.000   // Phạm vi điện áp 0.0325 lần
#define V1_COMP 1.000   // Phạm vi điện áp 0.0947 lần
#define V2_COMP 1.000   // Phạm vi điện áp 0.6175 lần
#define YIF1_COMP 1.00  // Bù lấy mẫu dòng điện MOS 1
#define YIF2_COMP 1.00  // Bù lấy mẫu dòng điện MOS 2
#define DAC1_COMP  1.00    // Hệ số bù đầu ra DAC1(VREF)
#define IREF2_COMP  1.00  // Hệ số bù đầu ra IREF2
#define DAC2_COMP   1.00   // Bù đầu ra DAC2(IREF1)
/* Số lần tính giá trị trung bình lấy mẫu ADC */
#define ADC_count 3
/* Hệ số lọc bộ lọc thông thấp bậc nhất */
#define dPower1 0.5
/* Số hiệu chân, được xác định bằng cách xem tệp trình điều khiển 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

/* Biến toàn cục */
u8g2_t u8g2;                    // Biến cấu trúc u8g2
rt_uint16_t AD_Value[4];           // Dữ liệu lấy mẫu ADC

// Định nghĩa biến liệt kê trang chế độ
enum mode_type
{
    menu = 0, // Menu
    CC,       // Dòng điện không đổi
    CV,       // Điện áp không đổi
    CR,       // Điện trở không đổi
    CW        // Công suất không đổi
};

volatile uint8_t Eload_Out = 0;                 // Trạng thái bật/tắt đầu ra tải điện tử
volatile uint8_t mode = menu;                   // Chế độ hiện tại
volatile uint8_t voltage_dw = 0;                // Phạm vi lấy mẫu điện áp, 0 là 0.0325 lần, 2 là 0.6175 lần, 1 là 0.0947 lần
volatile double YVF, YIF1, YIF2, YIF, VBAT;     // Điện áp và dòng điện hiện tại
volatile double ISET, VSET, RSET, PSET;         // Giá trị đặt dòng điện, điện áp, điện trở, công suất
volatile uint32_t YVF_SUM, YIF1_SUM, YIF2_SUM, VBAT_SUM;  // Tổng tính toán để tính giá trị trung bình điện áp và dòng điện
volatile uint8_t AVG_count = 0;                 // Giá trị đếm tích lũy tính giá trị trung bình dòng điện
volatile uint8_t YVF_AVG_count = 0;             // Giá trị đếm tích lũy tính giá trị trung bình điện áp
volatile uint8_t VBAT_count = 0;                      // Giá trị đếm tích lũy tính giá trị trung bình điện áp pin
volatile uint8_t Key_ONOFF = 0;                 // Trạng thái nút bật/tắt tải điện tử có được nhấn hay không

static qpid_t qpid_CC;                          // Con trỏ dữ liệu điều khiển PID
static qpid_t qpid_CV;                          // Con trỏ dữ liệu điều khiển PID
static qpid_t qpid_CR;                          // Con trỏ dữ liệu điều khiển PID
static qpid_t qpid_CW;                          // Con trỏ dữ liệu điều khiển PID
static double I_SET, V_SET, R_SET, P_SET;

/* Khai báo hàm */
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   Chương trình chính.
 *
 * @return  không
 */
int main(void)
{
    rt_kprintf("MCU: CH32V307\n");
    rt_kprintf("SysClk: %dHz\n", SystemCoreClock);
    SYS_Init();

    while (1)
    {
        // Bảo vệ quá dòng và quá công suất
        if (YIF > 10 | YVF * YIF > 300)
        {
            if (Eload_Out == 1)
            {
                Key_ONOFF = 1;
            }
        }
        rt_thread_mdelay(20);
    }
}

/* Khởi tạo hệ thống */
void SYS_Init(void)
{
    IWDG_Init();    // Khởi tạo watchdog
    UART_Init();   // Khởi tạo UART 3
    HMILCD_Send("page 0");   // Chuyển đến trang khởi động
    Dac_Init();     // Khởi tạo DAC
    PWM_Init();     // Khởi tạo PWM

    rt_pin_mode(MCU_G0, PIN_MODE_OUTPUT);   // Đặt chế độ cổng IO là đầu ra
    rt_pin_mode(MCU_G1, PIN_MODE_OUTPUT);
    rt_pin_write(MCU_G0, PIN_LOW);        // Xuất mức thấp
    rt_pin_write(MCU_G1, PIN_LOW);

    Thread_Init();   // Tạo luồng
}

/* Khởi tạo luồng */
void Thread_Init(void)
{
    rt_thread_t tid = NULL; // Định nghĩa con trỏ khối điều khiển luồng
    /* Tạo luồng */
    tid = rt_thread_create("SYS_LED", thread1_sysLED_entry, NULL, 512, 30, 5);
    // Tạo một luồng có tên SYS_LED, hàm nhập là thread1_sysLED_entry, tham số là NULL, kích thước ngăn xếp là 256 byte, ưu tiên là 30, lát thời gian là 5 tick
    if (tid != RT_NULL) // Kiểm tra xem luồng có được tạo thành công không
    {
        if (rt_thread_startup(tid) == RT_EOK) // Khởi động luồng
            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) // Khởi động luồng
     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) // Khởi động luồng
            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) // Khởi động luồng
            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) // Khởi động luồng
            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) // Khởi động luồng
            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) // Khởi động luồng
            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) // Khởi động luồng
            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) // Khởi động luồng
            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) // Khởi động luồng
            LOG_D("thread10 BlueTooth create success");
    }
    else
    {
        LOG_E("thread10 BlueTooth create failed...");
    }

}
``````c
/* Bộ lọc thông thấp bậc một
 * Giá trị trả về: iData giá trị lấy mẫu sau khi lọc bậc một */
double lowV1(double com1)
{
    static double iLastData1;    // Giá trị lần trước
    double iData1;               // Giá trị tính toán lần này
    iData1 = (com1 * dPower1) + (1 - dPower1) * iLastData1; // Tính toán
    iLastData1 = iData1;                                     // Lưu trữ dữ liệu lần này
    return iData1;                                         // Trả về dữ liệu
}
double lowV2(double com1)
{
    static double iLastData2;    // Giá trị lần trước
    double iData1;               // Giá trị tính toán lần này
    iData1 = (com1 * dPower1) + (1 - dPower1) * iLastData2; // Tính toán
    iLastData2 = iData1;                                     // Lưu trữ dữ liệu lần này
    return iData1;                                         // Trả về dữ liệu
}
u16 lowV3(u16 com1)
{
    static u16 iLastData3;    // Giá trị lần trước
    u16 iData1;               // Giá trị tính toán lần này
    iData1 = (com1 * dPower1) + (1 - dPower1) * iLastData3; // Tính toán
    iLastData3 = iData1;                                     // Lưu trữ dữ liệu lần này
    return iData1;                                         // Trả về dữ liệu
}
u16 lowV4(u16 com1)
{
    static u16 iLastData3;    // Giá trị lần trước
    u16 iData1;               // Giá trị tính toán lần này
    iData1 = (com1 * 0.1) + (1 - 0.1) * iLastData3; // Tính toán
    iLastData3 = iData1;                                     // Lưu trữ dữ liệu lần này
    return iData1;                                         // Trả về dữ liệu
}

static void idle_hook(void)
{
    /* Cho chó ăn trong hàm gọi lại của luồng nhàn rỗi */
    rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_KEEPALIVE, NULL);
    // rt_kprintf("cho chó ăn!\n ");
}

static int IWDG_Init()
{
    rt_err_t ret = RT_EOK;
    rt_uint32_t timeout = 1; /* Thời gian tràn, đơn vị: giây */
    /* Tìm thiết bị watchdog theo tên thiết bị, lấy handle thiết bị */
    wdg_dev = rt_device_find(WDT_DEVICE_NAME);
    if (!wdg_dev)
    {
        rt_kprintf("tìm %s thất bại!\n", WDT_DEVICE_NAME);
        return RT_ERROR;
    }
    /* Khởi tạo thiết bị */
    rt_device_init(wdg_dev);
    /* Đặt thời gian tràn watchdog */
    ret = rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_SET_TIMEOUT, &timeout);
    if (ret != RT_EOK)
    {
        rt_kprintf("đặt %s timeout thất bại!\n", WDT_DEVICE_NAME);
        return RT_ERROR;
    }
    /* Khởi động watchdog */
    ret = rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_START, RT_NULL);
    if (ret != RT_EOK)
    {
        rt_kprintf("khởi động %s thất bại!\n", WDT_DEVICE_NAME);
        return -RT_ERROR;
    }
    /* Đặt hàm gọi lại của luồng nhàn rỗi */
    rt_thread_idle_sethook(idle_hook);

    return ret;
}

/* Hàm điểm vào của luồng 1, đèn LED trạng thái hệ thống nhấp nháy */
static void thread1_sysLED_entry(void *parameter)
{
    /* Chân LED1 ở chế độ đầu ra */
    rt_pin_mode(LED1, PIN_MODE_OUTPUT);
    /* Mặc định mức thấp */
    rt_pin_write(LED1, PIN_LOW);
    while (1)
    {
        /* Luồng 1 chạy ở mức ưu tiên thấp, liên tục nhấp nháy LED1 */
        rt_pin_write(LED1, !rt_pin_read(LED1));
        rt_thread_mdelay(500);
    }
}

/* Hàm điểm vào của luồng 2, màn hình OLED hiển thị thông tin */
static void thread2_OLED_entry(void *parameter)
{
    // Khởi tạo
    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); // Đặt bộ ký tự tiếng Trung

        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); // Định dạng chuỗi ký tự đầu ra vào biến chuỗi
        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); // Định dạng chuỗi ký tự đầu ra vào biến chuỗi
        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); // Định dạng chuỗi ký tự đầu ra vào biến chuỗi
        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); // Định dạng chuỗi ký tự đầu ra vào biến chuỗi
        u8g2_DrawStr(&u8g2, 0, 63, String);
        u8g2_SendBuffer(&u8g2); // Gửi dữ liệu vùng đệm
        rt_thread_mdelay(100);  // Trễ 100 mili giây
    }
}

/* Hàm điểm vào của luồng 3, xử lý dữ liệu ADC */
static void thread3_ADC_entry(void *parameter)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE); // Bật xung nhịp GPIOA và ADC
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);      // Bật xung nhịp DMA1

    RCC_ADCCLKConfig(RCC_PCLK2_Div6);       // Cấu hình phân chia xung nhịp ADC, chia 6 (72Mhz/6=12Mhz), tần số xung nhịp ADC không được vượt quá 14Mhz

    GPIO_InitTypeDef GPIO_InitStructure = { 0 };                // Định nghĩa cấu trúc để cấu hình GPIO
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;    // Đặt chân GPIO
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; // Chế độ GPIO là đầu vào tương tự
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5); // Cấu hình nhóm quy tắc ADC, viết kênh 0 vào chuỗi 1 của nhóm quy tắc, thời gian lấy mẫu 55.5 chu kỳ
    ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_239Cycles5); // Cấu hình nhóm quy tắc ADC, viết kênh 1 vào chuỗi 2 của nhóm quy tắc, thời gian lấy mẫu 55.5 chu kỳ
    ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_239Cycles5); // Cấu hình nhóm quy tắc ADC, viết kênh 2 vào chuỗi 3 của nhóm quy tắc, thời gian lấy mẫu 55.5 chu kỳ
    ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_239Cycles5); // Cấu hình nhóm quy tắc ADC, viết kênh 3 vào chuỗi 4 của nhóm quy tắc, thời gian lấy mẫu 55.5 chu kỳ

    ADC_InitTypeDef ADC_InitStructure = { 0 };
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;  // Cấu hình ADC ở chế độ độc lập
    ADC_InitStructure.ADC_ScanConvMode = ENABLE;        // Bật chế độ quét trong chế độ đa kênh
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;  // Đặt bật chế độ chuyển đổi liên tục
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // Đặt chuyển đổi không được khởi động bởi kích hoạt bên ngoài, khởi động bằng phần mềm
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; // Đặt dữ liệu ADC căn phải
    ADC_InitStructure.ADC_NbrOfChannel = 4;                // Số lượng kênh ADC của chuyển đổi quy tắc
    ADC_Init(ADC1, &ADC_InitStructure);

    ADC_Cmd(ADC1, ENABLE);      // Bật ADC1

    ADC_ResetCalibration(ADC1); // Đặt lại thanh ghi hiệu chuẩn ADC1.

    while (ADC_GetResetCalibrationStatus(ADC1))
        ; // Chờ kết thúc đặt lại hiệu chuẩn

    ADC_StartCalibration(ADC1); // Bắt đầu hiệu chuẩn AD

    while (ADC_GetCalibrationStatus(ADC1))
        ;      // Chờ kết thúc hiệu chuẩn

    DMA_DeInit(DMA1_Channel1); // Đặt lại bộ điều khiển DMA
    DMA_InitTypeDef DMA_InitStructure;                      // Định nghĩa cấu trúc để cấu hình DMA
    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32) &ADC1->RDATAR; // Cấu hình địa chỉ ngoại vi là địa chỉ thanh ghi dữ liệu ADC
    DMA_InitStructure.DMA_MemoryBaseAddr = (u32) AD_Value;          // Cấu hình địa chỉ bộ nhớ là địa chỉ đọc giá trị ADC
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;              // Cấu hình nguồn dữ liệu là ngoại vi, tức là chế độ truyền DMA từ ngoại vi đến bộ nhớ
    DMA_InitStructure.DMA_BufferSize = 4;                           // Đặt kích thước vùng đệm dữ liệu DMA
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;                           // Đặt chế độ tăng ngoại vi DMA tắt
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;         // Đặt chế độ tăng bộ nhớ DMA bật
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; // Đặt kích thước dữ liệu ngoại vi là nửa từ, tức là hai byte
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;         // Đặt kích thước dữ liệu bộ nhớ là nửa từ, tức là hai byte
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;     // Đặt chế độ DMA là chế độ truyền vòng
    DMA_InitStructure.DMA_Priority = DMA_Priority_High; // Đặt mức ưu tiên kênh truyền DMA cao, khi sử dụng một kênh DMA, cài đặt mức ưu tiên không ảnh hưởng
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;        // Vì chế độ truyền DMA này là từ ngoại vi đến bộ nhớ, do đó vô hiệu hóa chế độ truyền từ bộ nhớ đến bộ nhớ
    DMA_Init(DMA1_Channel1, &DMA_InitStructure);        // Khởi tạo kênh 1 của DMA1, kích hoạt phần cứng ADC1 được kết nối trên kênh 1 của DMA1, vì vậy phải sử dụng kênh 1 DMA1

    DMA_Cmd(DMA1_Channel1, ENABLE); // Khởi động kênh 1 DMA1
    ADC_DMACmd(ADC1, ENABLE);       // Bật yêu cầu DMA ADC
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);       // Vì không sử dụng kích hoạt bên ngoài, nên sử dụng kích hoạt phần mềm chuyển đổi ADC

    qpid_init(&qpid_CC);            // Khởi tạo dữ liệu điều khiển PID
    qpid_set_lmt(&qpid_CC, 0, 10);  // Đặt giới hạn PID
    qpid_set_ratio(&qpid_CC, 1, 0.001, 0.1);    // Đặt hệ số tỷ lệ điều khiển

    qpid_init(&qpid_CV);                // Khởi tạo dữ liệu điều khiển PID
    qpid_set_lmt(&qpid_CV, 31.0, 100);  // Đặt giới hạn PID
    qpid_set_ratio(&qpid_CV, 0.35, 0.005, 0.001);    // Đặt hệ số tỷ lệ điều khiển
```qpid_init(&qpid_CW);                // Khởi tạo dữ liệu điều khiển PID
    qpid_set_lmt(&qpid_CW, 0.1, 200);  // Đặt giới hạn PID
    qpid_set_ratio(&qpid_CW, 0.5, 0.003, 0.001);    // Đặt hệ số tỷ lệ điều khiển

    qpid_init(&qpid_CR);                // Khởi tạo dữ liệu điều khiển PID
    qpid_set_lmt(&qpid_CR, 0.1, 1000);  // Đặt giới hạn PID
    qpid_set_ratio(&qpid_CR, 0.35, 0.003, 0.001);    // Đặt hệ số tỷ lệ điều khiển

    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) // Khi mức lấy mẫu điện áp là 0.0325 lần
        {
            if (YVF_AVG_count < ADC_count) // Khi nhỏ hơn 15, cộng dồn giá trị lấy mẫu
            {
                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;    // Tính giá trị điện áp
                YVF_AVG_count = 0;
                YVF_SUM = 0;
            }
            if (YVF <= 31.0) // Khi điện áp nhỏ hơn 31V, chuyển đổi mức
            {
                rt_pin_write(MCU_G0, PIN_LOW);
                rt_pin_write(MCU_G1, PIN_HIGH); // Mức lấy mẫu điện áp 0.0947 lần
                if (Eload_Out == 1)
                {
                    DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(VSET * 0.0947 * 4096 / VREF * DAC1_COMP + 0.5)); // Đặt giá trị đầu ra DAC1, điều khiển điện áp không đổi
                }
                voltage_dw = 1;
                YVF_AVG_count = 0;
                YVF_SUM = 0;
                qpid_set_lmt(&qpid_CV, 4.6, 33.5); // Đặt giới hạn PID
                qpid_set_ratio(&qpid_CV, 0.38, 0.0035, 0.0005);    // Đặt hệ số tỷ lệ điều khiển
            }
        }
        else if (voltage_dw == 1) // Khi mức điện áp là 0.0947 lần
        {
            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)  // Khi điện áp lớn hơn 33.5V chuyển sang mức 0.0325 lần
            {
                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)); // Đặt giá trị đầu ra DAC1, điều khiển điện áp không đổi
                }
                voltage_dw = 0;
                YVF_AVG_count = 0;
                YVF_SUM = 0;
                qpid_set_lmt(&qpid_CV, 31.0, 100); // Đặt giới hạn PID
                qpid_set_ratio(&qpid_CV, 0.5, 0.005, 0.0005);    // Đặt hệ số tỷ lệ điều khiển
            }
            else if (YVF <= 4.6) // Khi điện áp nhỏ hơn 4.6V chuyển đổi mức
            {
                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); // Đặt giá trị đầu ra DAC1, điều khiển điện áp không đổi
                }
                voltage_dw = 2;
                YVF_AVG_count = 0;
                YVF_SUM = 0;
                qpid_set_lmt(&qpid_CV, 0.01, 5.1); // Đặt giới hạn PID
                qpid_set_ratio(&qpid_CV, 0.26, 0.0025, 0.0005);    // Đặt hệ số tỷ lệ điều khiển
            }
        }
        else if (voltage_dw == 2) // Khi mức điện áp là 0.6175 lần
        {
            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) // Khi điện áp lớn hơn 5.1V, chuyển đổi mức
            {
                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)); // Đặt giá trị đầu ra DAC1, điều khiển điện áp không đổi
                }
                voltage_dw = 1;
                YVF_AVG_count = 0;
                YVF_SUM = 0;
                qpid_set_lmt(&qpid_CV, 4.6, 33.5); // Đặt giới hạn PID
                qpid_set_ratio(&qpid_CV, 0.38, 0.0035, 0.0005);    // Đặt hệ số tỷ lệ điều khiển
            }
        }

        if (AVG_count < ADC_count)
        {
            YIF1_SUM += AD_Value[1]; // Cộng dồn dòng điện của MOS 1
            YIF2_SUM += AD_Value[2]; // Cộng dồn dòng điện của MOS 2
            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]); // Cộng dồn giá trị lấy mẫu điện áp pin
            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```c
/* Entry function for thread 4, processes data from serial port screen */
static void thread4_HMI_GetDate_entry(void *parameter)
{
    HMILCD_Send("CC.x0.val=0"); // Clear current setting value displayed on screen
    HMILCD_Send("CV.x0.val=0"); // Clear voltage setting value displayed on screen
    HMILCD_Send("CR.x0.val=0"); // Clear resistance setting value displayed on screen
    HMILCD_Send("CW.x0.val=0"); // Clear power setting value displayed on screen
    while (1)
    {
        if (Serial3_RxFlag == 1)
        {
            if (Serial3_RxPacket[0] == 0x01) // Currently on main menu page
            {
                if (Serial3_RxPacket[1] == 0x10) // Constant current button pressed
                {
                    HMILCD_Send("page CC"); // Switch to constant current mode page
                    mode = CC;                    // Set current mode to constant current
                }
                else if (Serial3_RxPacket[1] == 0x11) // Constant voltage button pressed
                {
                    HMILCD_Send("page CV"); // Switch to constant voltage page
                    mode = CV;                    // Set current mode to constant voltage
                }
                else if (Serial3_RxPacket[1] == 0x12) // Constant resistance button pressed
                {
                    HMILCD_Send("page CR"); // Switch to constant resistance page
                    mode = CR;                    // Set current mode to constant resistance
                }
                else if (Serial3_RxPacket[1] == 0x13) // Constant power button pressed
                {
                    HMILCD_Send("page CW"); // Switch to constant power page
                    mode = CW;                    // Set current mode to constant power
                }
            }
            else if (Serial3_RxPacket[0] == 0x02) // Currently on constant current mode page
            {
                if (Serial3_RxPacket[1] == 0x10) // Menu button pressed
                {
                    HMILCD_Send("CC.t1.txt=\"OFF\"");  // Display OFF in title box at top right of screen
                    HMILCD_Send("CC.b1.txt=\"开启\""); // Display "Turn On" in button at bottom right of screen
                    Eload_Out = 0;                           // Set load output status to off
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 outputs high level, turn off constant voltage
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 outputs low level, turn off constant current
                    PWM_SetCCR4(0);  // Set IREF2
                    HMILCD_Send("page menu");          // Switch to menu page
                    mode = menu;
                }
                else if (Serial3_RxPacket[1] == 0x11) // Turn on button pressed and current load output status is off
                {
                    Key_ONOFF = 1;
                }
            }
            else if (Serial3_RxPacket[0] == 0x03) // Currently on constant voltage mode page
            {
                if (Serial3_RxPacket[1] == 0x10) // Menu button pressed
                {
                    HMILCD_Send("CV.t1.txt=\"OFF\"");
                    HMILCD_Send("CV.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 outputs high level, turn off constant voltage
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 outputs low level, turn off constant current
                    PWM_SetCCR4(0);  // Set IREF2
                    HMILCD_Send("page menu");
                    mode = menu;
                }
                if (Serial3_RxPacket[1] == 0x11) // Turn on button pressed and current load output status is off
                {
                    Key_ONOFF = 1;
                }
            }
            else if (Serial3_RxPacket[0] == 0x04) // Currently on constant resistance mode page
            {
                if (Serial3_RxPacket[1] == 0x10) // Menu button pressed
                {
                    HMILCD_Send("CR.t1.txt=\"OFF\"");
                    HMILCD_Send("CR.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 outputs high level, turn off constant voltage
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 outputs low level, turn off constant current
                    PWM_SetCCR4(0);  // Set IREF2
                    HMILCD_Send("page menu");
                    mode = menu;
                }
                if (Serial3_RxPacket[1] == 0x11) // Turn on button pressed and current load output status is off
                {
                    Key_ONOFF = 1;
                }
            }
            else if (Serial3_RxPacket[0] == 0x05) // Currently on constant power mode page
            {
                if (Serial3_RxPacket[1] == 0x10) // Menu button pressed
                {
                    HMILCD_Send("CW.t1.txt=\"OFF\"");
                    HMILCD_Send("CW.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 outputs high level, turn off constant voltage
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 outputs low level, turn off constant current
                    PWM_SetCCR4(0);  // Set IREF2
                    HMILCD_Send("page menu");
                    mode = menu;
                }
                if (Serial3_RxPacket[1] == 0x11) // Turn on button pressed and current load output status is off
                {
                    Key_ONOFF = 1;
                }
            }

            else if (Serial3_RxPacket[0] == 0xAA) // Currently on numeric keypad page
            {
                char *temp = Serial3_RxPacket;
                temp++;                      // Address increment by 1
                uint16_t temp2 = atoi(temp); // Convert string to integer
                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)
                        {
                            // Set DAC2 output value to control constant current, +0.5 for rounding
                            DAC_SetChannel2Data(DAC_Align_12b_R,
                                    (uint16_t)(ISET * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
                            PWM_SetCCR4(0);  // Set IREF2
                        }
                        else
                        {
                            // Set DAC2 output value to control constant current, +0.5 for rounding
                            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)); // Set 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); // Set IREF1
                        PWM_SetCCR4(50000);  // Set IREF2
                        if (voltage_dw == 0)
                        {
                            DAC_SetChannel1Data(DAC_Align_12b_R,
                                    (uint16_t)(VSET * 0.0325 * 4096 / VREF * DAC1_COMP + 0.5)); // Set DAC1 output value to control constant voltage
                        }
                        else if (voltage_dw == 1)
                        {
                            DAC_SetChannel1Data(DAC_Align_12b_R,
                                    (uint16_t)(VSET * 0.0947 * 4096 / VREF * DAC1_COMP + 0.5)); // Set DAC1 output value to control constant voltage
                        }
                        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); // Set DAC1 output value to control constant voltage
                        }
                    }
                }
                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);
    }

}
``````c
/* Chế độ công suất không đổi */
void CW_mode(void)
{
    double Ptemp = PSET / YVF;
    if (Ptemp > 10)
        Ptemp = 10;
    if (Eload_Out == 1)
    {
        // Đặt giá trị đầu ra DAC2, điều khiển dòng điện không đổi, +0.5 để làm tròn
        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));  // Đặt IREF2
    }
    else
    {
        DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 xuất mức cao, tắt điều khiển điện áp không đổi
        DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 xuất mức thấp, tắt điều khiển dòng điện không đổi
        PWM_SetCCR4(0);  // Đặt IREF2
    }
}

/* Chế độ điện trở không đổi */
void CR_mode(void)
{
    double Rtemp = YVF / RSET;
    if (Rtemp > 10)
        Rtemp = 10;
    if (Eload_Out == 1)
    {
        // Đặt giá trị đầu ra DAC2, điều khiển dòng điện không đổi, +0.5 để làm tròn
        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));  // Đặt IREF2
    }
    else
    {
        DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 xuất mức cao, tắt điều khiển điện áp không đổi
        DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 xuất mức thấp, tắt điều khiển dòng điện không đổi
        PWM_SetCCR4(0);  // Đặt IREF2
    }
}

/* Điều khiển PID */
void PID(void)
{
    double Ptemp, Rtemp;

    if (mode == CC && Eload_Out == 1)
    {
        qpid_set_dst(&qpid_CC, ISET);   // Đặt giá trị mục tiêu PID
        I_SET = qpid_cal_pos(&qpid_CC, YIF);    // Tính toán PID
        if (ISET <= 2.5)
        {
            // Đặt giá trị đầu ra DAC2, điều khiển dòng điện không đổi, +0.5 để làm tròn
            DAC_SetChannel2Data(DAC_Align_12b_R, (uint16_t)(I_SET * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
        }
        else
        {
            // Đặt giá trị đầu ra DAC2, điều khiển dòng điện không đổi, +0.5 để làm tròn
            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));  // Đặt IREF2
        }
    }
    if (mode == CV && Eload_Out == 1)
    {
        qpid_set_dst(&qpid_CV, VSET);           // Đặt giá trị mục tiêu PID
        V_SET = qpid_cal_pos(&qpid_CV, YVF);    // Tính toán PID
        if (voltage_dw == 0)
        {
            DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(V_SET * 0.0325 * 4096 / VREF * DAC1_COMP + 0.5));
            // Đặt giá trị đầu ra DAC1, điều khiển điện áp không đổi
        }
        else if (voltage_dw == 1)
        {
            DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(V_SET * 0.0947 * 4096 / VREF * DAC1_COMP + 0.5));
            // Đặt giá trị đầu ra DAC1, điều khiển điện áp không đổi
        }
        else if (voltage_dw == 2)
        {
            DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(V_SET * 0.6175 * 4096 / VREF * DAC1_COMP + 0.5));
            // Đặt giá trị đầu ra DAC1, điều khiển điện áp không đổi
        }
    }
    if (mode == CW && Eload_Out == 1)
    {
        qpid_set_dst(&qpid_CW, PSET);   // Đặt giá trị mục tiêu PID
        P_SET = qpid_cal_pos(&qpid_CW, YIF * YVF);    // Tính toán PID
        Ptemp = P_SET / YVF;
        if (Ptemp > 10)
            Ptemp = 10;
        // Đặt giá trị đầu ra DAC2, điều khiển dòng điện không đổi, +0.5 để làm tròn
        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));  // Đặt IREF2
    }
    if (mode == CR && Eload_Out == 1)
    {
        qpid_set_dst(&qpid_CR, RSET);   // Đặt giá trị mục tiêu PID
        R_SET = qpid_cal_pos(&qpid_CR, YVF / YIF);    // Tính toán PID
        Rtemp = YVF / R_SET;
        if (Rtemp > 10)
            Rtemp = 10;
        // Đặt giá trị đầu ra DAC2, điều khiển dòng điện không đổi, +0.5 để làm tròn
        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));  // Đặt IREF2
    }

}

/* Hàm xử lý phím */
void key123(void)
{
    if (key[2] == 1)    // Phím 2, phím chuyển đổi
    {
        if (mode == menu)
        {
            HMILCD_Send("page CC"); // Chuyển đến trang chế độ dòng điện không đổi
            mode = CC;              // Đặt chế độ hiện tại là chế độ dòng điện không đổi
        }
        else if (mode == CC)
        {
            if (Eload_Out == 1)
            {
                HMILCD_Send("CC.t1.txt=\"OFF\"");
                HMILCD_Send("CC.b1.txt=\"Bật\"");
                Eload_Out = 0;
                DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 xuất mức cao, tắt điều khiển điện áp không đổi
                DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 xuất mức thấp, tắt điều khiển dòng điện không đổi
                PWM_SetCCR4(0);  // Đặt IREF2
            }
            HMILCD_Send("page CV"); // Chuyển đến trang chế độ điện áp không đổi
            mode = CV;              // Đặt chế độ hiện tại là chế độ điện áp không đổi
        }
        else if (mode == CV)
        {
            if (Eload_Out == 1)
            {
                HMILCD_Send("CV.t1.txt=\"OFF\"");
                HMILCD_Send("CV.b1.txt=\"Bật\"");
                Eload_Out = 0;
                DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 xuất mức cao, tắt điều khiển điện áp không đổi
                DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 xuất mức thấp, tắt điều khiển dòng điện không đổi
                PWM_SetCCR4(0);  // Đặt IREF2
            }
            HMILCD_Send("page CR"); // Chuyển đến trang chế độ điện trở không đổi
            mode = CR;              // Đặt chế độ hiện tại là chế độ điện trở không đổi
        }
        else if (mode == CR)
        {
            if (Eload_Out == 1)
            {
                HMILCD_Send("CR.t1.txt=\"OFF\"");
                HMILCD_Send("CR.b1.txt=\"Bật\"");
                Eload_Out = 0;
                DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 xuất mức cao, tắt điều khiển điện áp không đổi
                DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 xuất mức thấp, tắt điều khiển dòng điện không đổi
                PWM_SetCCR4(0);  // Đặt IREF2
            }
            HMILCD_Send("page CW"); // Chuyển đến trang chế độ công suất không đổi
            mode = CW;              // Đặt chế độ hiện tại là chế độ công suất không đổi
        }
        else if (mode == CW)
        {
            if (Eload_Out == 1)
            {
                HMILCD_Send("CW.t1.txt=\"OFF\"");
                HMILCD_Send("CW.b1.txt=\"Bật\"");
                Eload_Out = 0;
                DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 xuất mức cao, tắt điều khiển điện áp không đổi
                DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 xuất mức thấp, tắt điều khiển dòng điện không đổi
                PWM_SetCCR4(0);  // Đặt IREF2
            }
            HMILCD_Send("page menu"); // Chuyển đến trang menu
            mode = menu;              // Đặt chế độ hiện tại là chế độ menu
        }
        key[2] = 0;
    }
    if (key[3] == 1)    // Phím 3, phím menu
    {
        if (Eload_Out == 1)
            Key_ONOFF = 1;
        rt_thread_mdelay(15);
        HMILCD_Send("page menu"); // Chuyển đến trang menu
        mode = menu;              // Đặt chế độ hiện tại là chế độ menu
        key[3] = 0;
    }
}
``````c
/* Hàm entry của luồng 5, xử lý phím bật/tắt tải điện tử */
static void thread5_ONOFF_entry(void *parameter)
{
    /* Chân LED2 ở chế độ đầu ra */
    rt_pin_mode(LED2, PIN_MODE_OUTPUT);
    /* Mặc định mức cao */
    rt_pin_write(LED2, PIN_HIGH);
    while (1)
    {
        if (Key_ONOFF == 1 | key[1] == 1) // Nút bật được nhấn
        {
            if (mode == CC) // Chế độ dòng điện không đổi
            {
                if (Eload_Out == 0) // Khi trạng thái đầu ra tải hiện tại là tắt
                {
                    HMILCD_Send("CC.t1.txt=\"ON\"");   // Khung tiêu đề góc trên bên phải màn hình hiển thị ON
                    HMILCD_Send("CC.b1.txt=\"关闭\"");    // Nút góc dưới bên phải màn hình hiển thị tắt
                    Eload_Out = 1;                           // Đặt trạng thái đầu ra tải thành bật
                    DAC_SetChannel1Data(DAC_Align_12b_R, 0);
                    if (ISET <= 2.5)
                    {
                        // Đặt giá trị đầu ra DAC2, điều khiển dòng điện không đổi, +0.5 là để làm tròn
                        DAC_SetChannel2Data(DAC_Align_12b_R,
                                (uint16_t)(ISET * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
                        PWM_SetCCR4(0);  // Đặt IREF2
                    }
                    else
                    {
                        // Đặt giá trị đầu ra DAC2, điều khiển dòng điện không đổi, +0.5 là để làm tròn
                        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));  // Đặt IREF2
                    }
                }
                else if (Eload_Out == 1) // Khi trạng thái đầu ra tải hiện tại là bật
                {
                    HMILCD_Send("CC.t1.txt=\"OFF\"");
                    HMILCD_Send("CC.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 xuất mức cao, tắt điện áp không đổi
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 xuất mức thấp, tắt dòng điện không đổi
                    PWM_SetCCR4(0);  // Đặt IREF2
                }
            }
            else if (mode == CV) // Chế độ điện áp không đổi
            {
                if (Eload_Out == 0) // Khi trạng thái đầu ra tải hiện tại là tắt
                {
                    HMILCD_Send("CV.t1.txt=\"ON\"");
                    HMILCD_Send("CV.b1.txt=\"关闭\"");
                    Eload_Out = 1;
                    DAC_SetChannel2Data(DAC_Align_12b_R, 4095); // Đặt IREF1
                    PWM_SetCCR4(50000);  // Đặt IREF2
                    if (voltage_dw == 0)
                    {
                        DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(VSET * 0.0325 * 4096 / VREF * DAC1_COMP + 0.5));
                        // Đặt giá trị đầu ra DAC1, điều khiển điện áp không đổi
                    }
                    else if (voltage_dw == 1)
                    {
                        DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(VSET * 0.0947 * 4096 / VREF * DAC1_COMP + 0.5));
                        // Đặt giá trị đầu ra DAC1, điều khiển điện áp không đổi
                    }
                    else if (voltage_dw == 2)
                    {
                        DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(VSET * 0.6175 * 4096 / VREF * DAC1_COMP + 0.5));
                        // Đặt giá trị đầu ra DAC1, điều khiển điện áp không đổi
                    }
                }
                else if (Eload_Out == 1) // Khi trạng thái đầu ra tải hiện tại là bật
                {
                    HMILCD_Send("CV.t1.txt=\"OFF\"");
                    HMILCD_Send("CV.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 xuất mức cao, tắt điện áp không đổi
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 xuất mức thấp, tắt dòng điện không đổi
                    PWM_SetCCR4(0);  // Đặt IREF2
                }
            }
            else if (mode == CR) // Chế độ điện trở không đổi
            {
                if (Eload_Out == 0) // Khi trạng thái đầu ra tải hiện tại là tắt
                {
                    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) // Khi trạng thái đầu ra tải hiện tại là bật
                {
                    HMILCD_Send("CR.t1.txt=\"OFF\"");
                    HMILCD_Send("CR.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 xuất mức cao, tắt điện áp không đổi
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 xuất mức thấp, tắt dòng điện không đổi
                    PWM_SetCCR4(0);  // Đặt IREF2
                }
            }
            else if (mode == CW) // Chế độ công suất không đổi
            {
                if (Eload_Out == 0) // Khi trạng thái đầu ra tải hiện tại là tắt
                {
                    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) // Khi trạng thái đầu ra tải hiện tại là bật
                {
                    HMILCD_Send("CW.t1.txt=\"OFF\"");
                    HMILCD_Send("CW.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 xuất mức cao, tắt điện áp không đổi
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 xuất mức thấp, tắt dòng điện không đổi
                    PWM_SetCCR4(0);  // Đặt 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);
    }

}

/* Hàm entry của luồng 7, hiển thị tham số trên màn hình nối tiếp */
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));       // Hiển thị điện áp
            HMILCD_Send("x2.val=%d", (uint16_t)(I * 1000));      // Hiển thị dòng điện
            HMILCD_Send("x3.val=%d", (uint32_t)(I * V * 100)); // Hiển thị công suất
            HMILCD_Send("x4.val=%d", (uint32_t)(V / I * 100)); // Hiển thị điện trở
            HMILCD_Send("x5.val=%d", (uint32_t)(VBAT * 100));     // Hiển thị điện trở
        }
        rt_thread_mdelay(50);
    }
}

/* Hàm entry của luồng 8, điều khiển quạt tản nhiệt */
static void thread8_FAN_entry(void *parameter)
{
    while (1)
    {
        uint16_t P = (uint16_t)(YIF * YVF + 0.5);
        if (P >= 13) // Khởi động quạt khi công suất lớn hơn 13W
        {
            FAN_PWM_ON();
            if (P < 20)
            {
                FAN_PWM_SetCCR(20); // Điều khiển quạt với chu kỳ công việc 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); // Tắt đầu ra PWM của quạt và xuất mức thấp
            FAN_PWM_OFF();
        }
        rt_thread_mdelay(200);
    }
}

/* Hàm entry của luồng 9, điều khiển chế độ công suất không đổi và điện trở không đổi */
static void thread9_CWCR_entry(void *parameter)
{
    while (1)
    {
        if (mode == CW)
        {
            CW_mode();
        }
        if (mode == CR)
        {
            CR_mode();
        }
        rt_thread_mdelay(50);
    }
}
```/* Hàm entry của thread 10, xử lý dữ liệu được gửi từ Bluetooth */
static void thread10_BlueTooth_entry(void *parameter)
{
    HMILCD_Send("CC.x0.val=0"); // Xóa giá trị thiết lập dòng điện hiển thị trên màn hình
    HMILCD_Send("CV.x0.val=0"); // Xóa giá trị thiết lập điện áp hiển thị trên màn hình
    HMILCD_Send("CR.x0.val=0"); // Xóa giá trị thiết lập điện trở hiển thị trên màn hình
    HMILCD_Send("CW.x0.val=0"); // Xóa giá trị thiết lập công suất hiển thị trên màn hình
    while (1)
    {
        if (Serial5_RxFlag == 1)
        {
            if (Serial5_RxPacket[0] == 0x01) // Hiện tại là trang menu chính
            {
                if (Serial5_RxPacket[1] == 0x10) // Nút dòng điện không đổi được nhấn
                {
                    if (mode == CV)
                    {
                        HMILCD_Send("CV.t1.txt=\"OFF\"");
                        HMILCD_Send("CV.b1.txt=\"Bật\"");

                    }
                    else if (mode == CR)
                    {
                        HMILCD_Send("CR.t1.txt=\"OFF\"");
                        HMILCD_Send("CR.b1.txt=\"Bật\"");

                    }
                    else if (mode == CW)
                    {
                        HMILCD_Send("CW.t1.txt=\"OFF\"");
                        HMILCD_Send("CW.b1.txt=\"Bật\"");

                    }
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 xuất mức cao, tắt chế độ điện áp không đổi
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 xuất mức thấp, tắt chế độ dòng điện không đổi
                    PWM_SetCCR4(0);  // Thiết lập IREF2
                    Eload_Out = 0;
                    HMILCD_Send("page CC"); // Chuyển đến trang chế độ dòng điện không đổi
                    mode = CC;                    // Thiết lập chế độ hiện tại là dòng điện không đổi
                }
                else if (Serial5_RxPacket[1] == 0x11) // Nút điện áp không đổi được nhấn
                {
                    if (mode == CC)
                    {
                        HMILCD_Send("CC.t1.txt=\"OFF\"");
                        HMILCD_Send("CC.b1.txt=\"Bật\"");

                    }
                    else if (mode == CR)
                    {
                        HMILCD_Send("CR.t1.txt=\"OFF\"");
                        HMILCD_Send("CR.b1.txt=\"Bật\"");

                    }
                    else if (mode == CW)
                    {
                        HMILCD_Send("CW.t1.txt=\"OFF\"");
                        HMILCD_Send("CW.b1.txt=\"Bật\"");

                    }
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 xuất mức cao, tắt chế độ điện áp không đổi
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 xuất mức thấp, tắt chế độ dòng điện không đổi
                    PWM_SetCCR4(0);  // Thiết lập IREF2
                    Eload_Out = 0;
                    HMILCD_Send("page CV"); // Chuyển đến trang điện áp không đổi
                    mode = CV;                    // Thiết lập chế độ hiện tại là điện áp không đổi
                }
                else if (Serial5_RxPacket[1] == 0x12) // Nút điện trở không đổi được nhấn
                {
                    if (mode == CC)
                    {
                        HMILCD_Send("CC.t1.txt=\"OFF\"");
                        HMILCD_Send("CC.b1.txt=\"Bật\"");

                    }
                    else if (mode == CV)
                    {
                        HMILCD_Send("CV.t1.txt=\"OFF\"");
                        HMILCD_Send("CV.b1.txt=\"Bật\"");

                    }
                    else if (mode == CW)
                    {
                        HMILCD_Send("CW.t1.txt=\"OFF\"");
                        HMILCD_Send("CW.b1.txt=\"Bật\"");

                    }
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 xuất mức cao, tắt chế độ điện áp không đổi
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 xuất mức thấp, tắt chế độ dòng điện không đổi
                    PWM_SetCCR4(0);  // Thiết lập IREF2
                    Eload_Out = 0;
                    HMILCD_Send("page CR"); // Chuyển đến trang điện trở không đổi
                    mode = CR;                    // Thiết lập chế độ hiện tại là điện trở không đổi
                }
                else if (Serial5_RxPacket[1] == 0x13) // Nút công suất không đổi được nhấn
                {
                    if (mode == CV)
                    {
                        HMILCD_Send("CV.t1.txt=\"OFF\"");
                        HMILCD_Send("CV.b1.txt=\"Bật\"");

                    }
                    else if (mode == CR)
                    {
                        HMILCD_Send("CR.t1.txt=\"OFF\"");
                        HMILCD_Send("CR.b1.txt=\"Bật\"");

                    }
                    else if (mode == CC)
                    {
                        HMILCD_Send("CC.t1.txt=\"OFF\"");
                        HMILCD_Send("CC.b1.txt=\"Bật\"");

                    }
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 xuất mức cao, tắt chế độ điện áp không đổi
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 xuất mức thấp, tắt chế độ dòng điện không đổi
                    PWM_SetCCR4(0);  // Thiết lập IREF2
                    Eload_Out = 0;
                    HMILCD_Send("page CW"); // Chuyển đến trang công suất không đổi
                    mode = CW;                    // Thiết lập chế độ hiện tại là công suất không đổi
                }
            }
            else if (Serial5_RxPacket[0] == 0x08)    // Quay lại menu
            {
                if (mode == CC)
                {
                    HMILCD_Send("CC.t1.txt=\"OFF\"");  // Khung tiêu đề góc trên bên phải màn hình hiển thị OFF
                    HMILCD_Send("CC.b1.txt=\"Bật\""); // Nút góc dưới bên phải màn hình hiển thị Bật
                    Eload_Out = 0;                           // Thiết lập trạng thái xuất tải là tắt
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 xuất mức cao, tắt chế độ điện áp không đổi
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 xuất mức thấp, tắt chế độ dòng điện không đổi
                    PWM_SetCCR4(0);  // Thiết lập IREF2
                    HMILCD_Send("page menu");          // Chuyển đến trang menu
                    mode = menu;
                }
                else if (mode == CV)
                {
                    HMILCD_Send("CV.t1.txt=\"OFF\"");
                    HMILCD_Send("CV.b1.txt=\"Bật\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 xuất mức cao, tắt chế độ điện áp không đổi
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 xuất mức thấp, tắt chế độ dòng điện không đổi
                    PWM_SetCCR4(0);  // Thiết lập IREF2
                    HMILCD_Send("page menu");
                    mode = menu;
                }
                else if (mode == CR)
                {
                    HMILCD_Send("CR.t1.txt=\"OFF\"");
                    HMILCD_Send("CR.b1.txt=\"Bật\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 xuất mức cao, tắt chế độ điện áp không đổi
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 xuất mức thấp, tắt chế độ dòng điện không đổi
                    PWM_SetCCR4(0);  // Thiết lập IREF2
                    HMILCD_Send("page menu");
                    mode = menu;
                }
                else if (mode == CW)
                {
                    HMILCD_Send("CW.t1.txt=\"OFF\"");
                    HMILCD_Send("CW.b1.txt=\"Bật\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 xuất mức cao, tắt chế độ điện áp không đổi
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 xuất mức thấp, tắt chế độ dòng điện không đổi
                    PWM_SetCCR4(0);  // Thiết lập IREF2
                    HMILCD_Send("page menu");
                    mode = menu;
                }
            }
            else if (Serial5_RxPacket[0] == 0x09)    // Nút bật
            {
                Key_ONOFF = 1;
            }

            else if (Serial5_RxPacket[0] == 0xAA) // Hiện tại là trang bàn phím số
            {
                char *temp = Serial5_RxPacket;
                temp++;                      // Địa chỉ tự tăng 1
                uint16_t temp2 = atoi(temp); // Chuyển đổi chuỗi thành số nguyên
                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)
                        {
                            // Đặt giá trị đầu ra DAC2, điều khiển dòng điện không đổi, +0.5 là để làm tròn
                            DAC_SetChannel2Data(DAC_Align_12b_R,
                                    (uint16_t)(ISET * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
                            PWM_SetCCR4(0);  // Đặt IREF2
                        }
                        else
                        {
                            // Đặt giá trị đầu ra DAC2, điều khiển dòng điện không đổi, +0.5 là để làm tròn
                            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)); // Đặt 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); // Đặt IREF1
                        PWM_SetCCR4(50000);  // Đặt IREF2
                        if (voltage_dw == 0)
                        {
                            DAC_SetChannel1Data(DAC_Align_12b_R,
                                    (uint16_t)(VSET * 0.0325 * 4096 / VREF * DAC1_COMP + 0.5)); // Đặt giá trị đầu ra DAC1, điều khiển điện áp không đổi
                        }
                        else if (voltage_dw == 1)
                        {
                            DAC_SetChannel1Data(DAC_Align_12b_R,
                                    (uint16_t)(VSET * 0.0947 * 4096 / VREF * DAC1_COMP + 0.5)); // Đặt giá trị đầu ra DAC1, điều khiển điện áp không đổi
                        }
                        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); // Đặt giá trị đầu ra DAC1, điều khiển điện áp không đổi
                        }
                    }
                }
                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);
    }

}

Đọc thêm