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

Tải sáng tạo điện tải thông minh dựa trên HT32F52352, bao gồm sơ đồ nguyên lý, PCB, mã nguồn và báo cáo sản phẩm.

Tác phẩm đạt giải Ba trong Cuộc thi Thiết kế Ứng dụng Vi điều khiển Holtek Cup lần thứ 10 năm 2023 dành cho sinh viên đại học tỉnh Quảng Đông.

Làm trong vòng một tháng (mà lịch học dày nên cũng không có nhiều thời gian), làm khá tạm, đừng ném đá.

Video demo sản phẩm: https://www.bilibili.com/video/BV1sM4y1b7qu/

Tài liệu mở này chỉ nhằm mục đích tham khảo học tập, không khuyến khích sao chép; trên nền tảng Lichuang Open Source có nhiều dự án điện tải mở hoàn thiện và tốt hơn!

Liên kết mở trên nền tảng Lichuang Open Source: https://url.zeruns.com/xvvF8

Hướng dẫn cài đặt và cấu hình môi trường phát triển vi điều khiển Holtek HT32: https://blog.zeruns.com/archives/709.html

Nhóm thảo luận kỹ thuật điện tử/vi điều khiển: 2169025065

Điện tải là gì

Điện tải là thiết bị điện tử dùng để mô phỏng môi trường tải 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 điện trở công suất lớn hoặc dây nichrome truyền thống, điện tải điện tử có ưu điểm như thông số điều chỉnh được, dễ sử dụng. Dù là dự án phát triển chuyên nghiệp hay sở thích cá nhân, điện tải đều là thiết bị cần thiết.

Điện tải chia theo loại nguồn kiểm tra thành điện tải xoay chiều và điện tải một chiều. Theo chức năng thường có bốn loại: dòng không đổi, áp không đổi, trở không đổi, công suất không đổi. Vì hầu hết nguồn thường gặp là nguồn một chiều áp không đổi, khi kiểm tra chủ yếu xét khả năng cung cấp dòng, nên điện tải một chiều dòng không đổi là phổ biến nhất. Theo cách điều khiển chia thành điều khiển số và điều khiển tương tự. So với điện tải tương tự, điện tải số điều chỉnh trực quan, chức năng phong phú, dễ mở rộng và dễ tự động hóa kiểm tra.

Giới thiệu dự án

Sản phẩm dùng vi điều khiển Holtek HT32F52352 làm chip điều khiển chính, cấp nguồn bằng pin 18650 tiện mang theo.

Cách điều khiển: vi điều khiển xuất xung PWM qua mạch lọc thông thấp tạo điện áp một chiều ổn định (PWM+lọc thay DAC) làm điện áp tham chiếu cho opamp so sánh với điện áp lấy mẫu dòng/áp sau khi khuếch đại, đầu ra opamp điều khiển MOSFET, thực hiện chế độ áp/dòng không đổi.

Màn hình cảm ứng dùng màn hình UART 2,8" của TaoJingChi, model TJC3224T028_011R.

Ảnh thực tế

Khi đó chụp ít, chỉ tìm được hai tấm, xem video demo nhé.


Link tải tài liệu

Link bên dưới gồm: sơ đồ nguyên lý, file LichuangEDA, file Gerber PCB, mã nguồn, file màn hình UART, datasheet chip.

123云盘: https://www.123pan.com/s/2Y9Djv-ZNevH.html

百度网盘: https://url.zeruns.com/XP241 Mã rút gọn: i9bl

Link mua linh kiện

Nên mua linh kiện tại Lichuang Mall: https://activity.szlcsc.com/invite/D03E5B9CEAAE70A4.html

Sơ đồ nguyên lý

Board công suất



Board nguồn

Board điều khiển

PCB

Board công suất

Lớp trên

Inner1

Lớp dưới

Còn lớp GND không chụp

Board nguồn

Lớp trên

Lớp dưới

Board điều khiển

Lớp trên

Lớp dưới

Mã chính

File main.c

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

#define KEY1 GPIO_ReadInBit(HT_GPIOA, GPIO_PIN_10)

// Định nghĩa enum trạng thái phím
typedef enum
{
  KS_RELEASE = 0, // phím nhả
  KS_SHAKE,       // rung phím
  KS_PRESS,       // giữ ổn định
} KEY_STATUS;

// Trạng thái máy trạng thái hiện tại
#define g_keyStatus 0
// Trạng thái hiện tại (sau mỗi vòng lặp bằng g_keyStatus)
#define g_nowKeyStatus 1
// Trạng thái trước (để phân biệt nguồn trạng thái)
#define g_lastKeyStatus 2

uint8_t KEY_Status[4][3]; // lưu trạng thái từng phím
uint8_t key[4];           // 1=phím đã giữ ổn định, 0=chưa
char *temp;
```// Định nghĩa enum cho các 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
};

uint8_t Eload_Out = 0;                // trạng thái bật/tắt đầu ra tải điện tử
uint8_t mode = menu;                  // chế độ hiện tại
uint8_t voltage_dw = 0;               // tầng 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
float YVF, YIF1, YIF2, YIF;           // điện áp và dòng điện hiện tại
float ISET, VSET, RSET, PSET;         // giá trị đặt dòng, điện áp, điện trở, công suất
float VDD = 3.3;                      // điện áp nguồn vi điều khiển
uint32_t YVF_SUM, YIF1_SUM, YIF2_SUM; // tổng tính toán cho giá trị trung bình điện áp và dòng
uint8_t AVG_count = 0;                // bộ đếm tích lũy tính trung bình dòng
uint8_t YVF_AVG_count = 0;            // bộ đếm tính trung bình điện áp
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

void HMI_GetData(void);
void HMI_Display(void);
void AdcFb(void);
void ONOFF(void);
void FAN(void);
void key_status_check(uint8_t key_num, uint8_t KEY);
void CW_mode(void);
void CR_mode(void);
void OFF(void);//tắt
void ON(void);//bật

int main(void)
{
  GPIO_Configuration();     // khởi tạo GPIO
  BFTM0_Configuration();    // khởi tạo timer BFTM0
  GPTM0_Configuration();    // khởi tạo timer GPTM0
  GPTM1_Configuration();    // khởi tạo timer GPTM1
  MCTM0_Configuration();    // khởi tạo timer MCTM0
  WDT_Configuration();      // khởi tạo watchdog
  OLED_Init();              // khởi tạo OLED
  ADC_Configuration();      // khởi tạo ADC
  RETARGET_Configuration(); // chuyển hướng hàm printf sang UART
  USART1_Configuration();   // khởi tạo UART1
  USART0_Configuration();   // khởi tạo UART0

  uint16_t count1 = 0;

  Serial_SendHMILCD("page 0");      // chuyển sang trang khởi động
  GPTM0_CH0_DisablePWMOutput(1);    // tắt đầu ra PWM kênh CH0 (CV), xuất mức cao
  GPTM0_CH2_DisablePWMOutput(0);    // tắt đầu ra PWM kênh CH2 (CC1), xuất mức thấp
  GPTM0_CH3_DisablePWMOutput(0);    // tắt đầu ra PWM kênh CH3 (CC2), xuất mức thấp
  MCTM0_CH0_DisablePWMOutput(0);    // tắt đầu ra PWM kênh MCTM0_CH0 (quạt), xuất mức thấp
  Serial_SendHMILCD("CC.x0.val=0"); // xóa giá trị dòng đặt trên màn hình
  Serial_SendHMILCD("CV.x0.val=0"); // xóa giá trị điện áp đặt trên màn hình
  Serial_SendHMILCD("CR.x0.val=0"); // xóa giá trị điện trở đặt trên màn hình
  Serial_SendHMILCD("CW.x0.val=0"); // xóa giá trị công suất đặt trên màn hình

  GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_10, RESET); // MCU_G0, tầng lấy mẫu điện áp 0,6175 lần
  GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_11, RESET); // MCU_G1, tầng lấy mẫu điện áp 0,0947 lần

  while (1)
  {
    if (HT_CKCU->APBCCR1 & (1 << 4)) // kiểm tra xem có bật clock watchdog không
      WDT_Restart();                 // làm mới bộ đếm watchdog

    GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_14, RESET);

    if (bftm0_ct3 >= 1) // thực hiện mỗi 1 ms
    {
      AdcFb(); // xử lý dữ liệu ADC
      bftm0_ct3 = 0;
    }

    HMI_GetData();

    if (bftm0_ct2 >= 40) // thực hiện mỗi 40 ms
    {
      HMI_Display(); // hiển thị dữ liệu lên màn hình UART
      bftm0_ct2 = 0;
    }
    ONOFF();
    FAN();

    if (bftm0_ct >= 50) // thực hiện mỗi 50 ms
    {
      key_status_check(0, KEY1); // quét phím
      if (mode == CW)
      {
        CW_mode();
      }
      if (mode == CR)
      {
        CR_mode();
      }
      bftm0_ct = 0;
    }

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

    GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_14, SET);
    /*
        float Voltage0 = (AD_Value[0] & 0x0000FFFF) / 4096.0 * 3.3; // chuyển giá trị ADC sang điện áp
        OLED_ShowNum(1, 4, (uint8_t)Voltage0, 1);                   // hiển thị phần nguyên
        OLED_ShowString(1, 5, ".");
        OLED_ShowString(1, 9, "V");
        OLED_ShowNum(1, 6, (uint16_t)(Voltage0 * 1000) % 1000, 3); // hiển thị phần thập phân

        float Voltage1 = (AD_Value[1] & 0x0000FFFF) / 4096.0 * 3.3; // chuyển giá trị ADC sang điện áp
        OLED_ShowNum(2, 4, (uint8_t)Voltage1, 1);                   // hiển thị phần nguyên
        OLED_ShowString(2, 5, ".");
        OLED_ShowString(2, 9, "V");
        OLED_ShowNum(2, 6, (uint16_t)(Voltage1 * 1000) % 1000, 3); // hiển thị phần thập phân

        float Voltage2 = (AD_Value[2] & 0x0000FFFF) / 4096.0 * 3.3; // chuyển giá trị ADC sang điện áp
        OLED_ShowNum(3, 4, (uint8_t)Voltage2, 1);                   // hiển thị phần nguyên
        OLED_ShowString(3, 5, ".");
        OLED_ShowString(3, 9, "V");
        OLED_ShowNum(3, 6, (uint16_t)(Voltage2 * 1000) % 1000, 3); // hiển thị phần thập phân

        VDD = (AD_Value[3] & 0x0000FFFF) / 4096.0 * 3.3; // chuyển giá trị ADC sang điện áp
        OLED_ShowNum(4, 4, (uint8_t)VDD, 1);             // hiển thị phần nguyên
        OLED_ShowString(4, 5, ".");
        OLED_ShowString(4, 9, "V");
        OLED_ShowNum(4, 6, (uint16_t)(VDD * 1000) % 1000, 3); // hiển thị phần thập phân

        VDD = (AD_Value[4] & 0x0000FFFF) / 4096.0 * 3.3; // chuyển giá trị ADC sang điện áp
        OLED_ShowNum(4, 11, (uint8_t)VDD, 1);            // hiển thị phần nguyên
        OLED_ShowString(4, 12, ".");
        OLED_ShowString(4, 16, "V");
        OLED_ShowNum(4, 13, (uint16_t)(VDD * 1000) % 1000, 3); // hiển thị phần thập phân
        count1++;*/
			
			if(flag_start==1)//xử lý dữ liệu UART1
			{
				if(++cishu==7)  {flag_over=1;}
							  Delay_ms(1);
			}
			if(flag_over==1)
		{
				flag_over=0;
			 if (Data[0] == 'A') // đang ở trang bàn phím số
    {
			
			char *temp = Data;
      temp++;                      // tăng địa chỉ 1
      uint16_t temp2 = atoi(temp); // chuyển chuỗi sang số nguyên
//      if (mode == CC)
//      {
//        if (temp2 > 10000)
//          temp2 = 10000;
//        ISET = temp2 / 1000.0;
//        Serial_SendHMILCD("CC.x0.val=%d", temp2);

//        if (ISET <= 2.5)
//        {
//          if (Eload_Out == 1)
//          {
//            GPTM0_CH3_DisablePWMOutput(0);
//            GPTM0_CH2_SetOnduty((uint16_t)(ISET * 0.01 * 50 / VDD * 50000));
//          }
//          else
//          {
//            GPTM0_CH2_SetOnduty((uint16_t)(ISET * 0.01 * 50 / VDD * 50000));
//          }
//        }
//        else
//        {
//          if (Eload_Out == 1)
//          {
//            GPTM0_CH2_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
//            GPTM0_CH3_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
//            GPTM0_CH3_EnablePWMOutput();
//          }
//          else
//          {
//            GPTM0_CH2_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
//            GPTM0_CH3_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
//          }
//        }
//      }
//      else if (mode == CV)
//      {
//        VSET = temp2 / 100.0;
//        Serial_SendHMILCD("CV.x0.val=%d", temp2);
//        if (voltage_dw == 0)
//        {
//          GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0325 / VDD * 50000));
//        }
//        else if (voltage_dw == 1)
//        {
//          GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0947 / VDD * 50000));
//        }
//        else if (voltage_dw == 2)
//        {
//          GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.6175 / VDD * 50000));
//        }
//      }
//      else if (mode == CR)
//      {
//        RSET = temp2 / 100.0;
//        Serial_SendHMILCD("CR.x0.val=%d", temp2);
//        CR_mode();
//      }
       if (mode == CW)
      {
        PSET = temp2/100.0;
        Serial_SendHMILCD("CW.x0.val=%d", temp2);
        CW_mode();
      }
			
    }
			switch (Data[0]){
				case '6':
				Serial_SendHMILCD("page menu");          // chuyển sang trang menu
        mode = menu;
				break;
				case '5':
		              Serial_SendHMILCD("page CW"); // chuyển sang trang công suất không đổi
                  mode = CW;                    // đặt chế độ hiện tại là công suất không đổi
				break;
				case '4':
		              Serial_SendHMILCD("page CR"); // chuyển sang trang điện trở không đổi
                  mode = CR;                    // đặt chế độ hiện tại là điện trở không đổi
				break;	
				case '3':
		              Serial_SendHMILCD("page CV"); // chuyển sang trang điện áp không đổi
                  mode = CV;                    // đặt chế độ hiện tại là điện áp không đổi
				break;	
        case '2':
		              Serial_SendHMILCD("page CC"); // chuyển sang trang dòng điện không đổi
                  mode = CC;                    // đặt chế độ hiện tại là dòng điện không đổi
					break;		  
				case '1':
		      ON();//bật
					break;
				case '0':
          OFF();//tắt
					break;
				default:
					break;
			}
			
			  cishu=0;
				len=0;
				flag_start=0;
	 }
  }
}void ON(void)//bật
{
		if (mode == CC) // chế độ dòng không đổi
    {
      if (Eload_Out == 0) // khi trạng thái đầu ra tải hiện tại là tắt
      {
        Serial_SendHMILCD("CC.t1.txt=\"ON\"");   // hiển thị ON ở khung tiêu đề góc trên bên phải màn hình
        Serial_SendHMILCD("CC.b1.txt=\"tắt\""); // 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 là bật
        if (ISET <= 2.5)                         // chỉ bật một MOSFET khi giá trị đặt dòng nhỏ hơn 2,5 A
        {
          GPTM0_CH2_SetOnduty((uint16_t)(ISET * 0.01 * 50 / VDD * 50000));
          GPTM0_CH2_EnablePWMOutput();   // bật PWM kênh CH2(IREF1)
          GPTM0_CH3_DisablePWMOutput(0); // tắt PWM kênh CH3(IREF2) và xuất mức thấp
        }
        else
        {
          GPTM0_CH2_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
          GPTM0_CH3_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
          GPTM0_CH2_EnablePWMOutput(); // bật PWM kênh CH2
          GPTM0_CH3_EnablePWMOutput(); // bật PWM kênh CH3
        }
        GPTM0_CH0_DisablePWMOutput(0); // VREF xuất mức thấp
      }
      
    }
		
		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
      {
        Serial_SendHMILCD("CV.t1.txt=\"ON\"");
        Serial_SendHMILCD("CV.b1.txt=\"tắt\"");
        Eload_Out = 1;
        if (voltage_dw == 0)
        {
          GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0325 / VDD * 50000));
        }
        else if (voltage_dw == 1)
        {
          GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0947 / VDD * 50000));
        }
        else if (voltage_dw == 2)
        {
          GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.6175 / VDD * 50000));
        }
        GPTM0_CH0_EnablePWMOutput();
        GPTM0_CH2_DisablePWMOutput(1);
        GPTM0_CH3_DisablePWMOutput(1);
      }
    }
		
		else if (mode == CR) // 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
      {
        Serial_SendHMILCD("CR.t1.txt=\"ON\"");
        Serial_SendHMILCD("CR.b1.txt=\"tắt\"");
        Eload_Out = 1;
        float Rtemp = YVF / RSET;
        if (Rtemp > 10)
          Rtemp = 10;
        GPTM0_CH2_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
        GPTM0_CH3_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
        GPTM0_CH2_EnablePWMOutput(); // bật PWM kênh CH2
        GPTM0_CH3_EnablePWMOutput(); // bật PWM kênh CH3

        GPTM0_CH0_DisablePWMOutput(0); // VREF xuất mức thấp
      }
    }
		
		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
      {
        Serial_SendHMILCD("CW.t1.txt=\"ON\"");
        Serial_SendHMILCD("CW.b1.txt=\"tắt\"");
        Eload_Out = 1;
        float Ptemp = PSET / YVF;
        if (Ptemp > 10)
          Ptemp = 10;
        GPTM0_CH2_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
        GPTM0_CH3_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
        GPTM0_CH2_EnablePWMOutput(); // bật PWM kênh CH2
        GPTM0_CH3_EnablePWMOutput(); // bật PWM kênh CH3

        GPTM0_CH0_DisablePWMOutput(0); // VREF xuất mức thấp
      }
    }
}

void OFF(void)//tắt
{
	      
	if (mode == CC) // chế độ dòng không đổi
				{
						if (Eload_Out == 1) // khi trạng thái đầu ra tải hiện tại là bật
					{
						Serial_SendHMILCD("CC.t1.txt=\"OFF\"");
						Serial_SendHMILCD("CC.b1.txt=\"bật\"");
						Eload_Out = 0;
						GPTM0_CH0_DisablePWMOutput(1); // VREF xuất mức cao
						GPTM0_CH2_DisablePWMOutput(0); // IREF1 xuất mức thấp
						GPTM0_CH3_DisablePWMOutput(0); // IREF2 xuất mức thấp
					}
				}
				
				else if (mode == CV) // chế độ điện áp không đổi
     {
		 
		  if (Eload_Out == 1) // khi trạng thái đầu ra tải hiện tại là bật
      {
        Serial_SendHMILCD("CV.t1.txt=\"OFF\"");
        Serial_SendHMILCD("CV.b1.txt=\"bật\"");
        Eload_Out = 0;
        GPTM0_CH0_DisablePWMOutput(1); // VREF xuất mức cao
        GPTM0_CH2_DisablePWMOutput(0); // IREF1 xuất mức thấp
        GPTM0_CH3_DisablePWMOutput(0); // IREF2 xuất mức thấp
      }
    }
		 
		 else if (mode == CR) // chế độ điện trở không đổi
     {  
		 if (Eload_Out == 1) // khi trạng thái đầu ra tải hiện tại là bật
      {
        Serial_SendHMILCD("CR.t1.txt=\"OFF\"");
        Serial_SendHMILCD("CR.b1.txt=\"bật\"");
        Eload_Out = 0;
        GPTM0_CH0_DisablePWMOutput(1); // VREF xuất mức cao
        GPTM0_CH2_DisablePWMOutput(0); // IREF1 xuất mức thấp
        GPTM0_CH3_DisablePWMOutput(0); // IREF2 xuất mức thấp
      }
		}
		else if (mode == CW) // chế độ công suất không đổi
    {  
	 if (Eload_Out == 1) // khi trạng thái đầu ra tải hiện tại là bật
      {
        Serial_SendHMILCD("CW.t1.txt=\"OFF\"");
        Serial_SendHMILCD("CW.b1.txt=\"bật\"");
        Eload_Out = 0;
        GPTM0_CH0_DisablePWMOutput(1); // VREF xuất mức cao
        GPTM0_CH2_DisablePWMOutput(0); // IREF1 xuất mức thấp
        GPTM0_CH3_DisablePWMOutput(0); // IREF2 xuất mức thấp
      }
		}
}

/*xử lý dữ liệu gửi từ màn hình serial*/
void HMI_GetData(void)
{
  if (Serial_RxFlag == 1)
  {
    if (Serial_RxPacket[0] == 0x01) // hiện đang ở trang menu chính
    {
      if (Serial_RxPacket[1] == 0x10) // nút dòng không đổi được nhấn
      {
        Serial_SendHMILCD("page CC"); // chuyển sang trang chế độ dòng không đổi
        mode = CC;                    // đặt chế độ hiện tại là dòng không đổi
      }
      else if (Serial_RxPacket[1] == 0x11) // nút điện áp không đổi được nhấn
      {
        Serial_SendHMILCD("page CV"); // chuyển sang trang điện áp không đổi
        mode = CV;                    // đặt chế độ hiện tại là điện áp không đổi
      }
      else if (Serial_RxPacket[1] == 0x12) // nút điện trở không đổi được nhấn
      {
        Serial_SendHMILCD("page CR"); // chuyển sang trang điện trở không đổi
        mode = CR;                    // đặt chế độ hiện tại là điện trở không đổi
      }
      else if (Serial_RxPacket[1] == 0x13) // nút công suất không đổi được nhấn
      {
        Serial_SendHMILCD("page CW"); // chuyển sang trang công suất không đổi
        mode = CW;                    // đặt chế độ hiện tại là công suất không đổi
      }
    }
    else if (Serial_RxPacket[0] == 0x02) // hiện đang ở trang chế độ dòng không đổi
    {
      if (Serial_RxPacket[1] == 0x10) // nút menu được nhấn
      {
        Serial_SendHMILCD("CC.t1.txt=\"OFF\"");  // hiển thị OFF ở khung tiêu đề góc trên bên phải màn hình
        Serial_SendHMILCD("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;                           // đặt trạng thái đầu ra tải là tắt
        GPTM0_CH0_DisablePWMOutput(1);           // tắt đầu ra PWM kênh CH0(điện áp không đổi), và xuất mức cao
        GPTM0_CH2_DisablePWMOutput(0);           // tắt đầu ra PWM kênh CH2(dòng không đổi 1), và xuất mức thấp
        GPTM0_CH3_DisablePWMOutput(0);           // tắt đầu ra PWM kênh CH3(dòng không đổi 2), và xuất mức thấp
        Serial_SendHMILCD("page menu");          // chuyển sang trang menu
        mode = menu;
      }
      else if (Serial_RxPacket[1] == 0x11) // nút bật được nhấn và trạng thái đầu ra tải hiện tại là tắt
      {
        Key_ONOFF = 1;
      }
    }
    else if (Serial_RxPacket[0] == 0x03) // hiện đang ở trang chế độ điện áp không đổi
    {
      if (Serial_RxPacket[1] == 0x10) // nút menu được nhấn
      {
        Serial_SendHMILCD("CV.t1.txt=\"OFF\"");
        Serial_SendHMILCD("CV.b1.txt=\"bật\"");
        Eload_Out = 0;
        GPTM0_CH0_DisablePWMOutput(1); // tắt đầu ra PWM kênh CH0(điện áp không đổi), và xuất mức cao
        GPTM0_CH2_DisablePWMOutput(0); // tắt đầu ra PWM kênh CH2(dòng không đổi 1), và xuất mức thấp
        GPTM0_CH3_DisablePWMOutput(0); // tắt đầu ra PWM kênh CH3(dòng không đổi 2), và xuất mức thấp
        Serial_SendHMILCD("page menu");
        mode = menu;
      }
      if (Serial_RxPacket[1] == 0x11) // nút bật được nhấn và trạng thái đầu ra tải hiện tại là tắt
      {
        Key_ONOFF = 1;
      }
    }
    else if (Serial_RxPacket[0] == 0x04) // hiện đang ở trang chế độ điện trở không đổi
    {
      if (Serial_RxPacket[1] == 0x10) // nút menu được nhấn
      {
        Serial_SendHMILCD("CR.t1.txt=\"OFF\"");
        Serial_SendHMILCD("CR.b1.txt=\"bật\"");
        Eload_Out = 0;
        GPTM0_CH0_DisablePWMOutput(1); // tắt đầu ra PWM kênh CH0(điện áp không đổi), và xuất mức cao
        GPTM0_CH2_DisablePWMOutput(0); // tắt đầu ra PWM kênh CH2(dòng không đổi 1), và xuất mức thấp
        GPTM0_CH3_DisablePWMOutput(0); // tắt đầu ra PWM kênh CH3(dòng không đổi 2), và xuất mức thấp
        Serial_SendHMILCD("page menu");
        mode = menu;
      }
      if (Serial_RxPacket[1] == 0x11) // nút bật được nhấn và trạng thái đầu ra tải hiện tại là tắt
      {
        Key_ONOFF = 1;
      }
    }
    else if (Serial_RxPacket[0] == 0x05) // hiện đang ở trang chế độ công suất không đổi
    {
      if (Serial_RxPacket[1] == 0x10) // nút menu được nhấn
      {
        Serial_SendHMILCD("CW.t1.txt=\"OFF\"");
        Serial_SendHMILCD("CW.b1.txt=\"bật\"");
        Eload_Out = 0;
        GPTM0_CH0_DisablePWMOutput(1); // tắt đầu ra PWM kênh CH0(điện áp không đổi), và xuất mức cao
        GPTM0_CH2_DisablePWMOutput(0); // tắt đầu ra PWM kênh CH2(dòng không đổi 1), và xuất mức thấp
        GPTM0_CH3_DisablePWMOutput(0); // tắt đầu ra PWM kênh CH3(dòng không đổi 2), và xuất mức thấp
        Serial_SendHMILCD("page menu");
        mode = menu;
      }
      if (Serial_RxPacket[1] == 0x11) // nút bật được nhấn và trạng thái đầu ra tải hiện tại là tắt
      {
        Key_ONOFF = 1;
      }
    }

    }else if (Serial_RxPacket[0] == 0xAA) // hiện tại là trang bàn phím số
    {
      char *temp = Serial_RxPacket;
      temp++;                      // địa chỉ tăng thêm 1
      uint16_t temp2 = atoi(temp); // chuyển chuỗi sang số nguyên
      if (mode == CC)
      {
        if (temp2 > 10000)
          temp2 = 10000;
        ISET = temp2 / 1000.0;
        Serial_SendHMILCD("CC.x0.val=%d", temp2);

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

/*Hiển thị thông số trên màn hình serial*/
void HMI_Display(void)
{
  if (mode != menu)
  {
    Serial_SendHMILCD("x1.val=%d", (uint16_t)(YVF * 100));       // hiển thị điện áp
    Serial_SendHMILCD("x2.val=%d", (uint16_t)(YIF * 1000));      // hiển thị dòng điện
    Serial_SendHMILCD("x3.val=%d", (uint32_t)(YIF * YVF * 100)); // hiển thị công suất
    Serial_SendHMILCD("x4.val=%d", (uint32_t)(YVF / YIF * 100)); // hiển thị điện trở
  }
}

/*Xử lý dữ liệu ADC*/
void AdcFb(void)
{
  VDD = (AD_Value[4] & 0x0000FFFF) * 1.2482 / (AD_Value[3] & 0x0000FFFF); // chuyển giá trị lấy mẫu ADC thành điện áp
                                                                          // VDD = (AD_Value[4] & 0x0000FFFF)  / 4096.0*3.3; // chuyển giá trị lấy mẫu ADC thành điện áp
  // VDD = 3.3;

  if (voltage_dw == 0) // khi cài đặt thang đo điện áp 0.0325 lần
  {
    if (YVF_AVG_count < 15)
    {
      YVF_SUM += (AD_Value[0] & 0x0000FFFF);
      YVF_AVG_count++;
    }
    if (YVF_AVG_count == 15)
    {
      YVF = YVF_SUM / YVF_AVG_count / 4096.0 * VDD / 0.0325;
      YVF_AVG_count = 0;
      YVF_SUM = 0;
    }
    // YVF = (AD_Value[0] & 0x0000FFFF) / 4096.0 * VDD / 0.0325;
    if (YVF <= 32) // khi điện áp nhỏ hơn 32V, chuyển thang đo
    {
      GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_10, RESET);
      GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_11, SET);
      GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0947 / VDD * 50000));
      voltage_dw = 1;
      YVF_AVG_count = 0;
      YVF_SUM = 0;
      // YVF = (AD_Value[0] & 0x0000FFFF) / 4096.0 * VDD / 0.0947;
    }
  }
  else if (voltage_dw == 1) // khi thang đo điện áp 0.0947 lần
  {
    if (YVF_AVG_count < 15)
    {
      YVF_SUM += (AD_Value[0] & 0x0000FFFF);
      YVF_AVG_count++;
    }
    if (YVF_AVG_count == 15)
    {
      YVF = YVF_SUM / YVF_AVG_count / 4096.0 * VDD / 0.0947;
      YVF_AVG_count = 0;
      YVF_SUM = 0;
    }
    // YVF = (AD_Value[0] & 0x0000FFFF) / 4096.0 * VDD / 0.0947;

    if (YVF >= 34)
    {
      GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_10, RESET);
      GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_11, RESET);
      GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0325 / VDD * 50000));
      voltage_dw = 0;
      YVF_AVG_count = 0;
      YVF_SUM = 0;
      // YVF = (AD_Value[0] & 0x0000FFFF) / 4096.0 * VDD / 0.0325;
    }
    else if (YVF <= 5.0) // khi điện áp nhỏ hơn 5V, chuyển thang đo
    {
      GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_10, SET);
      GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_11, RESET);
      GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.6175 / VDD * 50000));
      voltage_dw = 2;
      YVF_AVG_count = 0;
      YVF_SUM = 0;
      // YVF = (AD_Value[0] & 0x0000FFFF) / 4096.0 * VDD / 0.6175;
    }
  }
  else if (voltage_dw == 2) // khi thang đo điện áp 0.6175 lần
  {
    if (YVF_AVG_count < 15)
    {
      YVF_SUM += (AD_Value[0] & 0x0000FFFF);
      YVF_AVG_count++;
    }
    if (YVF_AVG_count == 15)
    {
      YVF = YVF_SUM / YVF_AVG_count / 4096.0 * VDD / 0.6175;
      if (YVF < 0.2)
      {
        YVF = 0;
      }
      YVF_AVG_count = 0;
      YVF_SUM = 0;
    }
    // YVF = (AD_Value[0] & 0x0000FFFF) / 4096.0 * VDD / 0.6175;

    if (YVF > 5.1) // khi điện áp lớn hơn 5.1V, chuyển thang đo
    {
      GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_10, RESET);
      GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_11, SET);
      GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0947 / VDD * 50000));
      voltage_dw = 1;
      YVF_AVG_count = 0;
      YVF_SUM = 0;
      // YVF = (AD_Value[0] & 0x0000FFFF) / 4096.0 * VDD / 0.0947;
    }
  }

  if (AVG_count < 15)
  {
    YIF1_SUM += (AD_Value[1] & 0x0000FFFF); // cộng dồn dòng MOSFET 1
    YIF2_SUM += (AD_Value[2] & 0x0000FFFF); // cộng dồn dòng MOSFET 2
    AVG_count++;
  }
  if (AVG_count == 15)
  {
    YIF1 = YIF1_SUM / AVG_count / 4096.0 * VDD / 50 / 0.01;
    YIF2 = YIF2_SUM / AVG_count / 4096.0 * VDD / 50 / 0.01;
    YIF = YIF1 + YIF2;
    AVG_count = 0;
    YIF1_SUM = 0;
    YIF2_SUM = 0;
  }
  // YIF1 = (AD_Value[1] & 0x0000FFFF) / 4096.0 * VDD / 50 / 0.01;
  // YIF2 = (AD_Value[2] & 0x0000FFFF) / 4096.0 * VDD / 50 / 0.01;
  // YIF = YIF1 + YIF2;
}/*Nút bật/tắt tải điện tử*/
void ONOFF(void)
{
  if (Key_ONOFF == 1 | key[0] == 1) // Nút bật được nhấn
  {
    if (mode == CC) // Chế độ dòng không đổi
    {
      if (Eload_Out == 0) // Khi trạng thái đầu ra tải hiện tại là tắt
      {
        Serial_SendHMILCD("CC.t1.txt=\"ON\"");   // Hiển thị ON ở ô tiêu đề góc trên bên phải màn hình
        Serial_SendHMILCD("CC.b1.txt=\"Tắt\""); // 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
        if (ISET <= 2.5)                         // Khi giá trị dòng đặt nhỏ hơn 2.5A chỉ bật một MOS
        {
          GPTM0_CH2_SetOnduty((uint16_t)(ISET * 0.01 * 50 / VDD * 50000));
          GPTM0_CH2_EnablePWMOutput();   // Bật PWM kênh CH2(IREF1)
          GPTM0_CH3_DisablePWMOutput(0); // Tắt PWM kênh CH3(IREF2) và xuất ra mức thấp
        }
        else
        {
          GPTM0_CH2_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
          GPTM0_CH3_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
          GPTM0_CH2_EnablePWMOutput(); // Bật PWM kênh CH2
          GPTM0_CH3_EnablePWMOutput(); // Bật PWM kênh CH3
        }
        GPTM0_CH0_DisablePWMOutput(0); // VREF xuất mức thấp
      }
      else if (Eload_Out == 1) // Khi trạng thái đầu ra tải hiện tại là bật
      {
        Serial_SendHMILCD("CC.t1.txt=\"OFF\"");
        Serial_SendHMILCD("CC.b1.txt=\"Bật\"");
        Eload_Out = 0;
        GPTM0_CH0_DisablePWMOutput(1); // VREF xuất mức cao
        GPTM0_CH2_DisablePWMOutput(0); // IREF1 xuất mức thấp
        GPTM0_CH3_DisablePWMOutput(0); // IREF2 xuất mức thấp
      }
    }
    else if (mode == CV) // Chế độ áp không đổi
    {
      if (Eload_Out == 0) // Khi trạng thái đầu ra tải hiện tại là tắt
      {
        Serial_SendHMILCD("CV.t1.txt=\"ON\"");
        Serial_SendHMILCD("CV.b1.txt=\"Tắt\"");
        Eload_Out = 1;
        if (voltage_dw == 0)
        {
          GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0325 / VDD * 50000));
        }
        else if (voltage_dw == 1)
        {
          GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0947 / VDD * 50000));
        }
        else if (voltage_dw == 2)
        {
          GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.6175 / VDD * 50000));
        }
        GPTM0_CH0_EnablePWMOutput();
        GPTM0_CH2_DisablePWMOutput(1);
        GPTM0_CH3_DisablePWMOutput(1);
      }
      else if (Eload_Out == 1) // Khi trạng thái đầu ra tải hiện tại là bật
      {
        Serial_SendHMILCD("CV.t1.txt=\"OFF\"");
        Serial_SendHMILCD("CV.b1.txt=\"Bật\"");
        Eload_Out = 0;
        GPTM0_CH0_DisablePWMOutput(1); // VREF xuất mức cao
        GPTM0_CH2_DisablePWMOutput(0); // IREF1 xuất mức thấp
        GPTM0_CH3_DisablePWMOutput(0); // IREF2 xuất mức thấp
      }
    }
    else if (mode == CR) // Chế độ trở không đổi
    {
      if (Eload_Out == 0) // Khi trạng thái đầu ra tải hiện tại là tắt
      {
        Serial_SendHMILCD("CR.t1.txt=\"ON\"");
        Serial_SendHMILCD("CR.b1.txt=\"Tắt\"");
        Eload_Out = 1;
        float Rtemp = YVF / RSET;
        if (Rtemp > 10)
          Rtemp = 10;
        GPTM0_CH2_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
        GPTM0_CH3_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
        GPTM0_CH2_EnablePWMOutput(); // Bật PWM kênh CH2
        GPTM0_CH3_EnablePWMOutput(); // Bật PWM kênh CH3

        GPTM0_CH0_DisablePWMOutput(0); // VREF xuất mức thấp
      }
      else if (Eload_Out == 1) // Khi trạng thái đầu ra tải hiện tại là bật
      {
        Serial_SendHMILCD("CR.t1.txt=\"OFF\"");
        Serial_SendHMILCD("CR.b1.txt=\"Bật\"");
        Eload_Out = 0;
        GPTM0_CH0_DisablePWMOutput(1); // VREF xuất mức cao
        GPTM0_CH2_DisablePWMOutput(0); // IREF1 xuất mức thấp
        GPTM0_CH3_DisablePWMOutput(0); // IREF2 xuất mức thấp
      }
    }
    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
      {
        Serial_SendHMILCD("CW.t1.txt=\"ON\"");
        Serial_SendHMILCD("CW.b1.txt=\"Tắt\"");
        Eload_Out = 1;
        float Ptemp = PSET / YVF;
        if (Ptemp > 10)
          Ptemp = 10;
        GPTM0_CH2_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
        GPTM0_CH3_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
        GPTM0_CH2_EnablePWMOutput(); // Bật PWM kênh CH2
        GPTM0_CH3_EnablePWMOutput(); // Bật PWM kênh CH3

        GPTM0_CH0_DisablePWMOutput(0); // VREF xuất mức thấp
      }
      else if (Eload_Out == 1) // Khi trạng thái đầu ra tải hiện tại là bật
      {
        Serial_SendHMILCD("CW.t1.txt=\"OFF\"");
        Serial_SendHMILCD("CW.b1.txt=\"Bật\"");
        Eload_Out = 0;
        GPTM0_CH0_DisablePWMOutput(1); // VREF xuất mức cao
        GPTM0_CH2_DisablePWMOutput(0); // IREF1 xuất mức thấp
        GPTM0_CH3_DisablePWMOutput(0); // IREF2 xuất mức thấp
      }
    }
		
    Key_ONOFF = 0;
    key[0] = 0;
  }
// https://blog.zeruns.com
  if (Eload_Out == 0)
  {
    GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_15, SET);
  }
  else if (Eload_Out == 1)
  {
    GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_15, RESET);
  }
}

/*Điều khiển quạt tản nhiệt*/
void FAN(void)
{
  uint16_t P = (uint16_t)(YIF * YVF);
  if (P >= 6) // Khi công suất lớn hơn 6W thì khởi động quạt
  {
    MCTM0_CH0_EnablePWMOutput(); // Bật quạt
    if (P < 15)
    {
      MCTM0_CH0_SetOnduty(50); // Tỷ số xung điều khiển quạt 50%
    }
    else if (P >= 15 && P < 20)
    {
      MCTM0_CH0_SetOnduty(60);
    }
    else if (P >= 20 && P < 25)
    {
      MCTM0_CH0_SetOnduty(70);
    }
    else if (P >= 25 && P < 30)
    {
      MCTM0_CH0_SetOnduty(80);
    }
    else if (P >= 30 && P < 35)
    {
      MCTM0_CH0_SetOnduty(90);
    }
    else if (P >= 35)
    {
      MCTM0_CH0_SetOnduty(100);
    }
  }
  else if (P <= 5)
  {
    MCTM0_CH0_DisablePWMOutput(0); // Tắt đầu ra PWM kênh MCTM0_CH0(quạt) và xuất mức thấp
  }
}

/*Chế độ công suất không đổi*/
void CW_mode(void)
{
  float Ptemp = PSET / YVF;
  if (Ptemp > 10)
    Ptemp = 10;
  if (Eload_Out == 1)
  {
    GPTM0_CH2_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
    GPTM0_CH3_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
  }
  else
  {
    GPTM0_CH2_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
    GPTM0_CH3_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
  }
}

/*Chế độ trở không đổi*/
void CR_mode(void)
{
  float Rtemp = YVF / RSET;
  if (Rtemp > 10)
    Rtemp = 10;
  if (Eload_Out == 1)
  {
    GPTM0_CH2_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
    GPTM0_CH3_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
  }
  else
  {
    GPTM0_CH2_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
    GPTM0_CH3_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
  }
}

// Chương trình trạng thái máy phím
void key_status_check(uint8_t key_num, uint8_t KEY)
{
  switch (KEY_Status[key_num][g_keyStatus])
  {
  // Phím thả (trạng thái ban đầu)
  case KS_RELEASE:
  {
    // Phát hiện mức thấp, tiến hành chống rung
    if (KEY == 0)
    {
      KEY_Status[key_num][g_keyStatus] = KS_SHAKE;
    }
  }
  break;

  // Rung
  case KS_SHAKE:
  {
    if (KEY == 1)
    {
      KEY_Status[key_num][g_keyStatus] = KS_RELEASE;
    }
    else
    {
      KEY_Status[key_num][g_keyStatus] = KS_PRESS;
    }
  }
  break;

  // Nhấn ngắn ổn định
  case KS_PRESS:
  {
    // Phát hiện mức cao, tiến hành chống rung
    if (KEY == 1)
    {
      KEY_Status[key_num][g_keyStatus] = KS_SHAKE;
    }
  }
  break;

  default:
    break;
  }

  if (KEY_Status[key_num][g_keyStatus] != KEY_Status[key_num][g_nowKeyStatus])
  {
    // Trạng thái hiện tại là thả và trạng thái trước là nhấn
    if ((KEY_Status[key_num][g_keyStatus] == KS_RELEASE) && (KEY_Status[key_num][g_lastKeyStatus] == KS_PRESS))
    {
      key[key_num] = 1;
    }
    KEY_Status[key_num][g_lastKeyStatus] = KEY_Status[key_num][g_nowKeyStatus];
    KEY_Status[key_num][g_nowKeyStatus] = KEY_Status[key_num][g_keyStatus];
  }
}

Giới thiệu các dự án mã nguồn mở khác

Đề xuất đọc thêm- Giới thiệu VPS/Máy chủ đám mây giá rẻ và có hiệu suất cao: https://blog.zeruns.com/archives/383.html