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
- Bo dev HT32F52352: https://s.click.taobao.com/XmpTGpt
- Chip vi điều khiển HT32F52352: https://s.click.taobao.com/sG9xeot
- Chip INA199A1: https://s.click.taobao.com/XLuweot
- Mẫu điện trồ dán 0805: https://s.click.taobao.com/p8YSGpt
- Mẫu tụ dán 0805: https://u.jd.com/9uvZoBd
- Chip XL6007E1: https://s.click.taobao.com/14Oueot
- Màn hình UART: https://s.click.taobao.com/pyzleot
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
- Làm bộ thu thập điện năng ba pha mã nguồn mở, tiện theo dõi điện năng tiêu thụ trong nhà: https://blog.zeruns.com/archives/771.html
- Mẫu dự án STM32F407 thư viện chuẩn đã port sẵn thư viện đồ họa U8g2: https://blog.zeruns.com/archives/722.html
- Mở mã nguồn bo mạch tối thiểu Qinheng CH32V307VCT6: https://blog.zeruns.com/archives/726.html
- Mô-đun nguồn DCDC tự động tăng áp giảm áp LM25118 điều chỉnh được: https://blog.zeruns.com/archives/727.html
- Mở mã nguồn mô-đun tăng áp đồng bộ hiệu suất cao EG1164, hiệu suất cao nhất 97%: https://blog.zeruns.com/archives/730.html
- Node giám sát môi trường 4G dựa trên Air700E, đẩy dữ liệu nhiệt độ, độ ẩm, áp suất… lên nền tảng Alibaba Cloud qua MQTT: https://blog.zeruns.com/archives/747.html
Đề 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
- Hướng dẫn mở máy chủ Minecraft: https://blog.zeruns.com/tag/mc/
- Xây dựng blog cá nhân mà không cần viết code! Hướng dẫn chi tiết cách tạo blog cá nhân: https://blog.zeruns.com/archives/783.html
- Hướng dẫn cài đặt máy chủ xuyên mạng nội bộ, hướng dẫn cài đặt và sử dụng NPS: https://blog.zeruns.com/archives/741.html
- Hướng dẫn cài đặt SD (Stable Diffusion) trên máy chủ đám mây GPU YuYu, tạo trang web vẽ tranh AI của riêng bạn: https://blog.zeruns.com/archives/768.html
- Đánh giá camera Huawei Pura70Pro+, so sánh với Mate40Pro: https://blog.zeruns.com/archives/782.html













