找回密码
 立即注册
楼主: 王昱顺

【AI助手】@AiCube-ISP-V6.96Z中,【自动生成程序】+【查错高手】

 火.. [复制链接]
  • 打卡等级:常住居民II
  • 打卡总天数:84
  • 最近打卡:2026-07-21 08:39:48
已绑定手机

19

主题

210

回帖

2152

积分

超级版主

积分
2152
发表于 昨天 08:40 | 显示全部楼层

好的,我已经查阅了STC8H4K64TL的完整手册资料。现在我来为您编写一个完整的健身器材演示程序。这个程序将充分利用STC8H4K64TL的特色外设:触摸按键、LED数码管自动刷新显示、ADC、串口通信和定时器。


STC8H4K64TL 健身器材演示程序

以下是一个完整的、可直接编译的演示程序,模拟健身器材(如跑步机/椭圆机)的控制面板功能。

文件1:main.c

/******************************************************************************
 * 文件名:main.c
 * 芯片型号:STC8H4K64TL
 * 功能描述:健身器材控制面板演示程序
 *           1. 触摸按键(TK0~TK3)控制:启动/停止、速度+、速度-、模式切换
 *           2. LED数码管自动刷新显示:速度、时间、距离
 *           3. ADC采集:模拟心率传感器输入
 *           4. 串口1通信:向上位机发送运行数据
 *           5. 定时器0:系统心跳计时(1ms基准)
 *           6. 定时器2:运行计时(1秒中断)
 *
 * 硬件连接说明(以LQFP48封装为例):
 *   - P1.0(TK0) : 触摸按键 - 启动/停止
 *   - P1.1(TK1) : 触摸按键 - 速度+
 *   - P1.3(TK2) : 触摸按键 - 速度-
 *   - P5.4(TK2) : 触摸按键 - 模式切换(注意:TK2与P5.4复用,此处用TK0~TK3)
 *     实际TK0~TK3对应:TK0=P1.0, TK1=P1.1, TK2=P5.4, TK3=P1.3
 *   - P1.4(ADC4): 模拟心率传感器输入
 *   - P3.0(RxD) : 串口1接收
 *   - P3.1(TxD) : 串口1发送
 *   - P2.0~P2.3(COM0~COM3): LED数码管位选(共阴/共阳模式)
 *   - P0.0~P0.7(SEG8~SEG15): LED数码管段选(高8位SEG)
 *   - P1.6(SEG6), P1.7(SEG7): 补充段选
 *     (实际LED引脚分配请根据硬件原理图调整)
 *
 * 使用说明:
 *   1. 使用STC-ISP软件下载程序,选择内部IRC时钟 11.0592MHz
 *   2. 触摸按键对应关系:
 *      - TK0触摸 -> 启动/停止切换
 *      - TK1触摸 -> 速度增加(0.1km/h步进,范围0.0~20.0km/h)
 *      - TK2触摸 -> 速度减少
 *      - TK3触摸 -> 模式切换(手动/间歇/减脂/自定义)
 *   3. 串口1波特率115200,8N1,每秒发送一次运行状态
 *   4. LED数码管显示格式:速度 XX.X km/h
 *
 * 编译环境:Keil C51
 * 编译选项:Memory Model - Large, Code Rom Size - Large
 ******************************************************************************/

#include "stc8h.h"
#include "intrins.h"

/*===========================================================================
 * 宏定义
 *===========================================================================*/
#define FOSC            11059200UL     // 系统主时钟频率 11.0592MHz
#define BAUD            115200UL       // 串口波特率

/* LED数码管段码表(共阴,低电平有效时取反) */
/* 共阴模式:SEG输出高电平点亮,COM输出低电平选中 */
#define SEG_A   0x01    // a段
#define SEG_B   0x02    // b段
#define SEG_C   0x04    // c段
#define SEG_D   0x08    // d段
#define SEG_E   0x10    // e段
#define SEG_F   0x20    // f段
#define SEG_G   0x40    // g段
#define SEG_DP  0x80    // 小数点

/* 触摸按键阈值(需根据实际调试确定) */
#define TK_THRESHOLD    200     // 触摸判定阈值(无触摸时读数约20000,触摸后下降约5%)

/* 系统状态 */
#define MODE_MANUAL     0       // 手动模式
#define MODE_INTERVAL   1       // 间歇模式
#define MODE_FAT_BURN   2       // 减脂模式
#define MODE_CUSTOM     3       // 自定义模式

/*===========================================================================
 * 全局变量
 *===========================================================================*/
/* LED显示缓冲区(共阴模式数据寄存器) */
xdata unsigned char LED_Buffer[8];   // COM0~COM7的显示数据

/* 系统运行参数 */
volatile unsigned char  sys_tick_1ms;        // 1ms心跳计数
volatile unsigned int   sys_tick_1s;         // 1秒计数
volatile unsigned char  sys_1s_flag;         // 1秒标志

volatile unsigned char  run_flag;            // 运行标志:0=停止,1=运行
volatile unsigned int   speed_x10;           // 速度值*10(如12.5km/h存为125)
volatile unsigned int   run_time_sec;        // 运行时间(秒)
volatile unsigned int   distance_x10;        // 距离*10(单位:0.1km)
volatile unsigned char  work_mode;           // 工作模式
volatile unsigned int   heart_rate;          // 心率值(BPM)

/* 触摸按键状态 */
volatile unsigned char  tk0_press;           // TK0触摸标志
volatile unsigned char  tk1_press;           // TK1触摸标志
volatile unsigned char  tk2_press;           // TK2触摸标志
volatile unsigned char  tk3_press;           // TK3触摸标志

/* 串口发送缓冲区 */
xdata unsigned char  uart1_tx_buf[64];
volatile unsigned char uart1_tx_len;

/*===========================================================================
 * 函数声明
 *===========================================================================*/
void System_Init(void);
void GPIO_Init(void);
void Timer0_Init(void);
void Timer2_Init(void);
void UART1_Init(void);
void ADC_Init(void);
void TouchKey_Init(void);
void LED_Init(void);
void LED_Display_Number(unsigned int num, unsigned char dot_pos);
void LED_Display_String(unsigned char *str);
void UART1_SendString(unsigned char *str);
void UART1_SendByte(unsigned char dat);
void Process_TouchKey(void);
void Update_Display(void);
void Update_RunData(void);
void Delay1ms(void);
void Delay_ms(unsigned int ms);

/*===========================================================================
 * 数码管段码表(共阴模式,0~9带小数点)
 * 位:DP G F E D C B A
 *===========================================================================*/
unsigned char code SEG_CODE[] = {
    0x3F,  // 0: 0b00111111
    0x06,  // 1: 0b00000110
    0x5B,  // 2: 0b01011011
    0x4F,  // 3: 0b01001111
    0x66,  // 4: 0b01100110
    0x6D,  // 5: 0b01101101
    0x7D,  // 6: 0b01111101
    0x07,  // 7: 0b00000111
    0x7F,  // 8: 0b01111111
    0x6F,  // 9: 0b01101111
    0x77,  // A
    0x7C,  // b
    0x39,  // C
    0x5E,  // d
    0x79,  // E
    0x71   // F
};

/* 带小数点的段码 */
unsigned char code SEG_CODE_DP[] = {
    0xBF,  // 0.
    0x86,  // 1.
    0xDB,  // 2.
    0xCF,  // 3.
    0xE6,  // 4.
    0xED,  // 5.
    0xFD,  // 6.
    0x87,  // 7.
    0xFF,  // 8.
    0xEF   // 9.
};

/*===========================================================================
 * 主函数
 *===========================================================================*/
void main(void)
{
    unsigned char i;
  
    /* 系统初始化 */
    System_Init();
  
    /* 主循环 */
    while(1)
    {
        /* 处理触摸按键 */
        Process_TouchKey();
      
        /* 更新运行数据(每秒更新一次) */
        if(sys_1s_flag)
        {
            sys_1s_flag = 0;
            Update_RunData();
          
            /* 串口发送运行状态 */
            sprintf(uart1_tx_buf, "SPD:%u, TIM:%u, DIS:%u, HR:%u, MOD:%u\r\n",
                    speed_x10, run_time_sec, distance_x10, heart_rate, work_mode);
            UART1_SendString(uart1_tx_buf);
        }
      
        /* 更新LED显示 */
        Update_Display();
      
        /* 简单延时,降低CPU占用率 */
        for(i = 0; i < 10; i++)
        {
            Delay1ms();
        }
    }
}

/*===========================================================================
 * 系统初始化
 *===========================================================================*/
void System_Init(void)
{
    /* 使能访问XFR(扩展特殊功能寄存器) */
    P_SW2 |= 0x80;
  
    /* 初始化各模块 */
    GPIO_Init();
    Timer0_Init();
    Timer2_Init();
    UART1_Init();
    ADC_Init();
    TouchKey_Init();
    LED_Init();
  
    /* 初始化全局变量 */
    sys_tick_1ms = 0;
    sys_tick_1s = 0;
    sys_1s_flag = 0;
    run_flag = 0;
    speed_x10 = 0;          // 初始速度0.0km/h
    run_time_sec = 0;
    distance_x10 = 0;
    work_mode = MODE_MANUAL;
    heart_rate = 72;        // 初始心率72BPM
  
    tk0_press = 0;
    tk1_press = 0;
    tk2_press = 0;
    tk3_press = 0;
  
    uart1_tx_len = 0;
  
    /* 清空LED缓冲区 */
    for(speed_x10 = 0; speed_x10 < 8; speed_x10++)
    {
        LED_Buffer[speed_x10] = 0x00;
    }
    speed_x10 = 0;
  
    /* 打开总中断 */
    EA = 1;
}

/*===========================================================================
 * GPIO初始化
 *===========================================================================*/
void GPIO_Init(void)
{
    /* P1.0(TK0), P1.1(TK1), P1.3(TK3) - 触摸按键,设置为高阻输入 */
    P1M0 &= ~0x0B;  // 清除P1.0, P1.1, P1.3的M0位
    P1M1 |= 0x0B;   // 设置P1.0, P1.1, P1.3为高阻输入
  
    /* P5.4(TK2) - 触摸按键,设置为高阻输入 */
    P5M0 &= ~0x10;
    P5M1 |= 0x10;
  
    /* P1.4(ADC4) - 心率传感器输入,设置为高阻输入 */
    P1M0 &= ~0x10;
    P1M1 |= 0x10;
  
    /* P3.0(RxD), P3.1(TxD) - 串口1,上电默认准双向,无需额外配置 */
  
    /* 注意:LED驱动引脚(COM和SEG)由LED硬件自动控制,无需手动配置GPIO模式 */
    /* 但需要确保这些引脚没有被其他外设占用 */
}

/*===========================================================================
 * 定时器0初始化 - 1ms系统心跳
 * 使用16位自动重装载模式
 * 定时时间 = (65536 - 重装载值) * 12 / FOSC
 * 重装载值 = 65536 - FOSC / 12 / 1000
 *           = 65536 - 11059200 / 12 / 1000
 *           = 65536 - 921
 *           = 64615 = 0xFC67
 *===========================================================================*/
void Timer0_Init(void)
{
    AUXR |= 0x80;       // 定时器0为1T模式
    TMOD &= 0xF0;       // 设置定时器0为模式0(16位自动重装载)
    TMOD |= 0x00;       // 模式0
    TL0 = 0x67;         // 设置定时初始值低字节
    TH0 = 0xFC;         // 设置定时初始值高字节
    TF0 = 0;            // 清除TF0标志
    TR0 = 1;            // 定时器0开始计时
    ET0 = 1;            // 使能定时器0中断
}

/*===========================================================================
 * 定时器2初始化 - 1秒定时
 * 使用24位自动重装载模式(16位+8位预分频)
 * 定时时间 = (65536 - 重装载值) * (预分频+1) * 12 / FOSC
 * 设置预分频为11(12分频),重装载值计算:
 * 1秒 = (65536 - RL) * 12 * 12 / 11059200
 * RL = 65536 - 11059200 / 12 / 12 = 65536 - 76800
 * 但76800 > 65536,需要调整预分频
 * 
 * 使用预分频=249(250分频),则:
 * 1秒 = (65536 - RL) * 250 * 12 / 11059200
 * RL = 65536 - 11059200 / 250 / 12 = 65536 - 3686 = 61850 = 0xF19A
 *===========================================================================*/
void Timer2_Init(void)
{
    T2L = 0x9A;         // 设置定时初始值低字节
    T2H = 0xF1;         // 设置定时初始值高字节
  
    /* T2PS寄存器在XFR空间,需先使能访问 */
    P_SW2 |= 0x80;
    T2PS = 249;         // 设置预分频为250分频(249+1)
    P_SW2 &= ~0x80;     // 关闭XFR访问(可选)
  
    AUXR |= 0x10;       // 定时器2为1T模式
    AUXR &= ~0x04;      // 定时器2开始计时(T2R=1)
    IE2 |= 0x04;        // 使能定时器2中断
}

/*===========================================================================
 * 串口1初始化 - 115200bps, 8N1
 * 使用定时器1作为波特率发生器
 * 波特率 = FOSC / 4 / (65536 - RL)
 * RL = 65536 - FOSC / 4 / BAUD
 *    = 65536 - 11059200 / 4 / 115200
 *    = 65536 - 24
 *    = 65512 = 0xFFE8
 *===========================================================================*/
void UART1_Init(void)
{
    SCON = 0x50;        // 模式1(8位UART),REN=1使能接收
    AUXR |= 0x40;       // 定时器1为1T模式
    AUXR &= ~0x01;      // 定时器1时钟为FOSC/1
    TMOD &= ~0x30;      // 定时器1为模式0(16位自动重装载)
    TMOD |= 0x00;
    TL1 = 0xE8;         // 设置波特率重装载值低字节
    TH1 = 0xFF;         // 设置波特率重装载值高字节
    TR1 = 1;            // 启动定时器1
    ES = 1;             // 使能串口1中断
}

/*===========================================================================
 * ADC初始化 - 12位ADC,查询方式读取
 * 使用ADC通道4(P1.4)采集心率传感器
 *===========================================================================*/
void ADC_Init(void)
{
    /* 使能访问XFR */
    P_SW2 |= 0x80;
  
    /* 配置ADC时序 */
    /* CSSETUP=0(1个时钟), CSHOLD[1:0]=01(2个时钟), SMPDUTY[4:0]=01010(11个时钟) */
    ADCTIM = 0x2A;      // 默认值:0b00101010
  
    /* 配置ADC时钟分频:SPEED[3:0]=0001 => SYSclk/2/2 = 2.7648MHz */
    ADCCFG = 0x10;      // RESFMT=0(高10位在ADC_RES), SPEED=0001
  
    /* 关闭XFR访问 */
    P_SW2 &= ~0x80;
  
    /* 打开ADC电源 */
    ADC_CONTR |= 0x80;  // ADC_POWER=1
  
    /* 等待ADC稳定 */
    Delay_ms(1);
}

/*===========================================================================
 * 触摸按键初始化
 * 使用TK0~TK3四个触摸按键
 *===========================================================================*/
void TouchKey_Init(void)
{
    /* 使能访问XFR */
    P_SW2 |= 0x80;
  
    /* 选择触摸按键通道:使能TK0, TK1, TK2, TK3 */
    TSCHEN1 = 0x0F;     // 使能TK0~TK3 (bit0~bit3)
    TSCHEN2 = 0x00;     // 不使能TK8~TK15
  
    /* 配置触摸按键参数 */
    /* SCR[2:0]=010 => 开关频率 = FOSC / 2 / (2+1) = 11.0592/6 ≈ 1.84MHz */
    /* DT[2:0]=011 => 放电时间 = 1000个系统时钟 */
    TSCFG1 = 0x43;      // 0b0100_0011
  
    /* TSVR[1:0]=01 => 参考电压 = 1/2 AVcc */
    TSCFG2 = 0x01;
  
    /* TSSAMP[1:0]=11 => 每个通道采样4次取平均 */
    /* SINGLE=0 => 连续扫描模式 */
    /* TSWAIT=0 => 自动连续扫描 */
    TSCTRL = 0x03;      // 0b0000_0011
  
    /* TSRT=0x00 => 不与LED分时复用(独立模式) */
    TSRT = 0x00;
  
    /* 启动触摸按键扫描 */
    TSCTRL |= 0x80;     // TSGO=1
  
    /* 关闭XFR访问 */
    P_SW2 &= ~0x80;
}

/*===========================================================================
 * LED驱动初始化
 * 使用共阴/共阳模式驱动数码管
 *===========================================================================*/
void LED_Init(void)
{
    /* 使能访问XFR */
    P_SW2 |= 0x80;
  
    /* 使能COM0~COM3(4位数码管) */
    COMEN = 0x0F;       // 使能COM0~COM3
  
    /* 使能SEG0~SEG7(8个段选) */
    SEGENL = 0xFF;      // 使能SEG0~SEG7
    SEGENH = 0x00;      // 不使能SEG8~SEG15
  
    /* 设置LED工作模式:共阴/共阳模式(LEDMODE=10) */
    /* LEDDUTY=000 => 8/8占空比(100%亮度) */
    LEDCTRL = 0x20;     // 0b0010_0000
  
    /* 设置LED刷新频率 */
    /* LED工作频率 = SYSclk / 160 / LEDCKS */
    /* 刷新率 = LED工作频率 / 8(COM数) / 16(SEG分时) */
    /* 要求刷新率 >= 75Hz */
    /* LEDCKS = SYSclk / 160 / 8 / 16 / 75 = 11059200/160/8/16/75 ≈ 7.2 */
    LEDCKS = 7;         // 取整为7
  
    /* 关闭XFR访问 */
    P_SW2 &= ~0x80;
  
    /* 启动LED驱动 */
    LEDCTRL |= 0x80;    // LEDON=1
}

/*===========================================================================
 * 触摸按键处理函数
 * 读取触摸按键状态并执行相应操作
 *===========================================================================*/
void Process_TouchKey(void)
{
    unsigned int tk_data;
    unsigned char tk_channel;
  
    /* 使能访问XFR */
    P_SW2 |= 0x80;
  
    /* 检查触摸按键完成标志 */
    if(TSSTA2 & 0x80)   // TSIF=1,有按键扫描完成
    {
        /* 读取完成通道号 */
        tk_channel = TSSTA2 & 0x0F;  // TSDNCHN[3:0]
      
        /* 读取该通道的触摸数据 */
        tk_data = ((unsigned int)TSDATH << 8) | TSDATL;
      
        /* 根据通道号处理 */
        switch(tk_channel)
        {
            case 0:     // TK0 - 启动/停止
                if(tk_data < TK_THRESHOLD)
                {
                    if(!tk0_press)
                    {
                        tk0_press = 1;
                        run_flag = !run_flag;   // 切换运行状态
                        if(run_flag == 0)
                        {
                            /* 停止时清零计时和距离 */
                            run_time_sec = 0;
                            distance_x10 = 0;
                        }
                    }
                }
                else
                {
                    tk0_press = 0;
                }
                break;
              
            case 1:     // TK1 - 速度+
                if(tk_data < TK_THRESHOLD)
                {
                    if(!tk1_press)
                    {
                        tk1_press = 1;
                        if(speed_x10 < 200)     // 最大速度20.0km/h
                        {
                            speed_x10++;
                        }
                    }
                }
                else
                {
                    tk1_press = 0;
                }
                break;
              
            case 2:     // TK2 - 速度-
                if(tk_data < TK_THRESHOLD)
                {
                    if(!tk2_press)
                    {
                        tk2_press = 1;
                        if(speed_x10 > 0)       // 最小速度0.0km/h
                        {
                            speed_x10--;
                        }
                    }
                }
                else
                {
                    tk2_press = 0;
                }
                break;
              
            case 3:     // TK3 - 模式切换
                if(tk_data < TK_THRESHOLD)
                {
                    if(!tk3_press)
                    {
                        tk3_press = 1;
                        work_mode++;
                        if(work_mode > MODE_CUSTOM)
                        {
                            work_mode = MODE_MANUAL;
                        }
                    }
                }
                else
                {
                    tk3_press = 0;
                }
                break;
              
            default:
                break;
        }
      
        /* 清除TSIF标志(写1清0) */
        TSSTA2 |= 0x80;
    }
  
    /* 关闭XFR访问 */
    P_SW2 &= ~0x80;
}

/*===========================================================================
 * 更新LED显示
 * 显示格式:速度值 XX.X km/h
 * 使用4位数码管显示速度
 * COM0: 十位, COM1: 个位(带小数点), COM2: 十分位, COM3: 单位"km/h"指示
 *===========================================================================*/
void Update_Display(void)
{
    unsigned int spd = speed_x10;
    unsigned char dig1, dig2, dig3;
  
    /* 分解速度值 */
    dig1 = spd / 100;               // 十位
    dig2 = (spd % 100) / 10;        // 个位
    dig3 = spd % 10;                // 十分位
  
    /* 使能访问XFR */
    P_SW2 |= 0x80;
  
    /* 共阴模式数据寄存器 */
    /* COM0 - 十位(如果为0且十位为0则不显示) */
    if(dig1 == 0)
    {
        COM0_DC_L = 0x00;   // 不显示
    }
    else
    {
        COM0_DC_L = SEG_CODE[dig1];
    }
  
    /* COM1 - 个位(带小数点) */
    COM1_DC_L = SEG_CODE_DP[dig2];
  
    /* COM2 - 十分位 */
    COM2_DC_L = SEG_CODE[dig3];
  
    /* COM3 - 显示模式指示 */
    switch(work_mode)
    {
        case MODE_MANUAL:
            COM3_DC_L = 0x76;   // 显示"H"表示手动
            break;
        case MODE_INTERVAL:
            COM3_DC_L = 0x30;   // 显示"n"表示间歇(近似)
            break;
        case MODE_FAT_BURN:
            COM3_DC_L = 0x71;   // 显示"F"表示减脂
            break;
        case MODE_CUSTOM:
            COM3_DC_L = 0x39;   // 显示"C"表示自定义
            break;
        default:
            COM3_DC_L = 0x00;
            break;
    }
  
    /* 关闭XFR访问 */
    P_SW2 &= ~0x80;
}

/*===========================================================================
 * 更新运行数据(每秒调用一次)
 *===========================================================================*/
void Update_RunData(void)
{
    unsigned int adc_value;
  
    if(run_flag)
    {
        /* 运行时间累加 */
        run_time_sec++;
      
        /* 计算距离:距离(km) = 速度(km/h) * 时间(h) */
        /* 速度单位是0.1km/h,时间单位是秒 */
        /* 距离(0.1km) = speed_x10 * run_time_sec / 3600 */
        /* 为避免浮点运算,使用整数运算 */
        if(run_time_sec % 36 == 0)  // 每36秒增加0.01km(速度为1.0km/h时)
        {
            distance_x10 += speed_x10 / 100;
        }
    }
  
    /* 读取ADC心率值(每秒钟采样一次) */
    ADC_CONTR &= ~0x0F;             // 清除通道选择
    ADC_CONTR |= 0x04;              // 选择ADC通道4(P1.4)
    ADC_CONTR |= 0x40;              // 启动ADC转换(ADC_START=1)
  
    /* 等待ADC转换完成 */
    while(!(ADC_CONTR & 0x20));     // 等待ADC_FLAG=1
  
    /* 读取ADC结果(12位,右对齐) */
    adc_value = ((unsigned int)ADC_RES << 8) | ADC_RESL;
  
    /* 清除ADC完成标志 */
    ADC_CONTR &= ~0x20;
  
    /* 将ADC值映射到心率范围(40~200BPM) */
    /* ADC范围0~4095,映射到40~200 */
    heart_rate = 40 + (unsigned int)((unsigned long)adc_value * 160 / 4096);
}

/*===========================================================================
 * 串口1发送一个字节
 *===========================================================================*/
void UART1_SendByte(unsigned char dat)
{
    SBUF = dat;
    while(!TI);         // 等待发送完成
    TI = 0;
}

/*===========================================================================
 * 串口1发送字符串
 *===========================================================================*/
void UART1_SendString(unsigned char *str)
{
    while(*str != '\0')
    {
        UART1_SendByte(*str);
        str++;
    }
}

/*===========================================================================
 * 延时1ms(使用定时器0的1ms心跳)
 *===========================================================================*/
void Delay1ms(void)
{
    unsigned char tick_start;
  
    tick_start = sys_tick_1ms;
    while(sys_tick_1ms == tick_start);  // 等待1ms变化
}

/*===========================================================================
 * 延时N毫秒
 *===========================================================================*/
void Delay_ms(unsigned int ms)
{
    unsigned int i;
    for(i = 0; i < ms; i++)
    {
        Delay1ms();
    }
}

/*===========================================================================
 * 定时器0中断服务函数 - 1ms心跳
 *===========================================================================*/
void Timer0_ISR(void) interrupt 1
{
    sys_tick_1ms++;
  
    /* 每1000个1ms(即1秒)置标志 */
    sys_tick_1s++;
    if(sys_tick_1s >= 1000)
    {
        sys_tick_1s = 0;
        sys_1s_flag = 1;
    }
}

/*===========================================================================
 * 定时器2中断服务函数 - 1秒定时
 *===========================================================================*/
void Timer2_ISR(void) interrupt 12
{
    /* 清除定时器2中断标志 */
    TF2 = 0;
  
    /* 备用1秒标志(与定时器0配合使用) */
    /* 实际1秒计时由定时器0完成,此处保留以备扩展 */
}

/*===========================================================================
 * 串口1中断服务函数
 *===========================================================================*/
void UART1_ISR(void) interrupt 4
{
    unsigned char rx_data;
  
    if(RI)              // 接收中断
    {
        RI = 0;
        rx_data = SBUF;
      
        /* 此处可添加串口命令解析 */
        /* 例如:接收"START"启动,"STOP"停止,"SPD:XX"设置速度等 */
    }
  
    if(TI)              // 发送中断(本程序使用查询方式发送,此处仅清除标志)
    {
        TI = 0;
    }
}

文件2:健身器材演示程序使用说明

===============================================================================
 STC8H4K64TL 健身器材控制面板演示程序 - 使用说明
===============================================================================

一、功能概述
-----------
本程序演示了STC8H4K64TL在健身器材(如跑步机、椭圆机)中的应用,
充分利用了该芯片的特色外设:
  - 触摸按键(TSU):替代传统机械按键,防水防汗、寿命长
  - LED数码管自动刷新显示(LED Driver):硬件自动扫描,不占CPU
  - 高精度12位ADC:采集心率传感器模拟信号
  - 多串口通信:向上位机发送运行数据

二、触摸按键功能
---------------
  TK0 (P1.0) : 启动/停止切换
  TK1 (P1.1) : 速度增加(0.1km/h步进,范围0.0~20.0km/h)
  TK2 (P5.4) : 速度减少
  TK3 (P1.3) : 模式切换(手动→间歇→减脂→自定义→循环)

三、LED数码管显示
----------------
  使用4位LED数码管(共阴/共阳模式)显示:
  第1位:速度十位
  第2位:速度个位(带小数点)
  第3位:速度十分位
  第4位:模式指示(H=手动, n=间歇, F=减脂, C=自定义)

四、串口通信
-----------
  串口1:波特率115200,8N1
  每秒发送一次运行状态数据,格式:
  SPD:速度, TIM:时间(秒), DIS:距离(0.1km), HR:心率(BPM), MOD:模式

五、硬件接线参考(LQFP48封装)
-----------------------------
  触摸按键:
    TK0 → P1.0 → 触摸弹簧/铜箔
    TK1 → P1.1 → 触摸弹簧/铜箔
    TK2 → P5.4 → 触摸弹簧/铜箔
    TK3 → P1.3 → 触摸弹簧/铜箔
    TCAP → 第13脚 → 10nF~47nF电容到GND

  心率传感器(模拟输出):
    ADC4 → P1.4 → 心率传感器输出

  串口通信:
    TxD → P3.1 → USB转串口RX
    RxD → P3.0 → USB转串口TX

  LED数码管(共阴/共阳模式,以共阴为例):
    COM0 → P2.0 → 数码管位选1
    COM1 → P2.1 → 数码管位选2
    COM2 → P2.2 → 数码管位选3
    COM3 → P2.3 → 数码管位选4
    SEG0 → P4.0 → 数码管a段
    SEG1 → P4.1 → 数码管b段
    SEG2 → P4.2 → 数码管c段
    SEG3 → P4.3 → 数码管d段
    SEG4 → P4.4 → 数码管e段
    SEG5 → P4.5 → 数码管f段
    SEG6 → P4.6 → 数码管g段
    SEG7 → P4.7 → 数码管dp段

  注意:LED驱动使用共阴/共阳模式(LEDMODE=10),
        COM和SEG的驱动波形由硬件自动产生。

六、下载设置
-----------
  使用STC-ISP软件:
  1. 选择芯片型号:STC8H4K64TL
  2. 设置内部IRC时钟:11.0592MHz
  3. 打开本程序生成的HEX文件
  4. 点击下载/编程

七、触摸按键调试
--------------
  首次使用时,需要通过串口观察触摸数据,调整阈值:
  1. 在Process_TouchKey函数中,将tk_data通过串口发送
  2. 记录无触摸时的基准值(约20000)
  3. 记录触摸时的数值(约下降5%)
  4. 设置TK_THRESHOLD为基准值的95%左右
===============================================================================

程序说明

设计思路

本程序针对健身器材(跑步机/椭圆机)的控制面板需求,充分利用了STC8H4K64TL的三大特色外设:

  1. 触摸按键(TSU):健身器材经常接触汗水,传统机械按键容易腐蚀损坏。STC8H4K64TL内置的触摸按键控制器支持最多16个触摸通道,防水防汗,寿命极长。本程序使用TK0~TK3四个触摸按键实现启动/停止、速度调节和模式切换。
  2. LED数码管自动刷新显示(LED Driver):传统方案需要CPU不断扫描数码管,占用大量CPU时间。STC8H4K64TL内置的LED驱动电路可硬件自动刷新显示,支持共阴、共阳和共阴/共阳三种模式,最多可驱动256个LED,支持8级灰度调节。本程序使用4位数码管显示速度值。
  3. 高精度12位ADC:用于采集心率传感器的模拟信号,将ADC值映射为心率值(40~200BPM)。

关键寄存器配置说明

模块 寄存器 配置值 说明
触摸按键 TSCHEN1 0x0F 使能TK0~TK3
触摸按键 TSCFG1 0x43 SCR=010(1.84MHz), DT=011(1000clk)
触摸按键 TSCFG2 0x01 TSVR=01(1/2 AVcc)
触摸按键 TSCTRL 0x83 TSGO=1, TSSAMP=11(4次平均)
LED驱动 COMEN 0x0F 使能COM0~COM3
LED驱动 SEGENL 0xFF 使能SEG0~SEG7
LED驱动 LEDCTRL 0xA0 LEDON=1, LEDMODE=10(共阴/共阳), LEDDUTY=000(100%)
LED驱动 LEDCKS 0x07 刷新频率约96Hz
ADC ADCCFG 0x10 SPEED=0001(2.76MHz ADC时钟)
定时器0 TMOD 0x00 16位自动重装载
定时器2 T2PS 249 预分频250,实现1秒定时

注意事项

  1. 触摸按键的参考电容:TCAP引脚需外接10nF~47nF电容到GND,推荐使用22nF(223)的低温漂电容。
  2. 触摸阈值调整:程序中的TK_THRESHOLD(200)为示例值,实际使用时需通过串口观察无触摸和触摸时的数据,调整合适的阈值。
  3. LED驱动模式:本程序使用共阴/共阳模式(LEDMODE=10),该模式下COM和SEG的驱动波形由硬件自动分时切换,适合驱动4位以上数码管。
  4. I/O口初始化:除P3.0/P3.1上电为准双向外,其余I/O口上电均为高阻输入,使用前必须设置模式。
  5. XFR访问:操作扩展特殊功能寄存器(地址0xFE00~0xFFFF)前,必须设置P_SW2 |= 0x80。

如需调整显示位数、增加更多触摸按键功能或修改通信协议,请参考STC8H系列手册中对应的章节进行修改。


🔍 搜索 "STC8H4K64TL 演示程序 健身器材 程序注解"

📌 相关论坛讨论

  1. 【AI助手】@AiCube-ISP-V6.96Z中,【自动生成程序】+【查错高手】

⏱️ 查找用时 18.2s | 总回答用时 85.1s

QQ: 1613405245  手机:19051366862
回复

使用道具 举报 送花

  • 打卡等级:以坛为家I
  • 打卡总天数:274
  • 最近打卡:2026-07-21 08:44:27
已绑定手机

292

主题

1027

回帖

3313

积分

超级版主

积分
3313
QQ
发表于 昨天 08:44 | 显示全部楼层
用STC32CL8K64写一个摩托车外挂炫彩灯的程序,并含详细注解
截图202607210844208886.jpg

热线19952583534
www.STCAI.com
回复

使用道具 举报 送花

  • 打卡等级:以坛为家I
  • 打卡总天数:274
  • 最近打卡:2026-07-21 08:44:27
已绑定手机

292

主题

1027

回帖

3313

积分

超级版主

积分
3313
QQ
发表于 昨天 08:44 | 显示全部楼层

好的,我已经获取了足够的信息。现在我来编写这个完整的摩托车外挂炫彩灯程序。程序将使用STC32CL8K64的PWMA和PWMB两组高级PWM,共8个通道来驱动RGB LED灯带,实现多种炫彩效果。

以下是完整的程序代码:

/******************************************************************************
 * 文件名:    Motorcycle_RGB_Light.c
 * 芯片型号:  STC32CL8K64 (使用Keil C251编译器)
 * 功能描述:  摩托车外挂炫彩灯控制器
 *            - 使用PWMA (通道1~4) 和 PWMB (通道5~8) 共8路PWM
 *            - 支持RGB LED灯带控制 (每路PWM驱动一个MOS管)
 *            - 多种炫彩模式: 呼吸灯、彩虹渐变、流水灯、爆闪、音乐律动
 *            - 通过按键切换模式,通过ADC读取电位器调节速度/亮度
 *          
 * 硬件连接:
 *   PWM1P (P1.0) -> MOS管 -> 红色LED灯组1
 *   PWM2P (P1.2) -> MOS管 -> 绿色LED灯组1
 *   PWM3P (P1.4) -> MOS管 -> 蓝色LED灯组1
 *   PWM4P (P1.6) -> MOS管 -> 白色LED灯组1
 *   PWM5  (P2.0) -> MOS管 -> 红色LED灯组2
 *   PWM6  (P2.1) -> MOS管 -> 绿色LED灯组2
 *   PWM7  (P2.2) -> MOS管 -> 蓝色LED灯组2
 *   PWM8  (P2.3) -> MOS管 -> 白色LED灯组2
 *   
 *   按键: P3.2 (INT0) 模式切换
 *   电位器: P1.1 (ADC1) 速度/亮度调节
 *   
 * 使用说明:
 *   1. 使用STC-ISP软件选择内部IRC时钟 40MHz
 *   2. 编译时选择 "Large" 内存模式
 *   3. 下载时选择 "STC32CL8K64" 型号
 ******************************************************************************/

#include "stc32g.h"        // STC32G系列头文件(STC32CL8K64兼容此头文件)
#include "intrins.h"

//=============================================================================
// 系统时钟定义
//=============================================================================
#define FOSC            40000000UL      // 系统主时钟 40MHz
#define PWM_FREQ        20000UL         // PWM频率 20KHz (人耳听不到)
#define PWM_PERIOD      (FOSC / PWM_FREQ)  // PWM周期计数值 = 2000

//=============================================================================
// 炫彩模式枚举
//=============================================================================
#define MODE_BREATH     0               // 呼吸灯模式
#define MODE_RAINBOW    1               // 彩虹渐变模式
#define MODE_FLOWING    2               // 流水灯模式
#define MODE_STROBE     3               // 爆闪模式
#define MODE_MUSIC      4               // 音乐律动模式
#define MODE_MAX        4               // 最大模式编号

//=============================================================================
// 全局变量定义
//=============================================================================
unsigned char g_mode = MODE_BREATH;     // 当前工作模式
unsigned char g_mode_change_flag = 0;   // 模式切换标志
unsigned int  g_speed = 50;             // 速度参数 (0-100)
unsigned int  g_brightness = 80;        // 亮度参数 (0-100)

// 呼吸灯参数
unsigned int  g_breath_step = 0;        // 呼吸步进
bit           g_breath_dir = 0;         // 呼吸方向: 0=变亮, 1=变暗

// 彩虹渐变参数
unsigned int  g_rainbow_index = 0;      // 彩虹索引

// 流水灯参数
unsigned int  g_flow_pos = 0;           // 流水位置

// 爆闪参数
unsigned int  g_strobe_cnt = 0;         // 爆闪计数器

// 定时器0中断计数 (用于非阻塞延时)
volatile unsigned int g_timer0_cnt = 0;

//=============================================================================
// 函数声明
//=============================================================================
void System_Init(void);
void PWM_Init(void);
void Timer0_Init(void);
void ADC_Init(void);
void GPIO_Init(void);
void Delay_ms(unsigned int ms);
void Set_PWM_Duty(unsigned char channel, unsigned int duty);
void Set_RGB1(unsigned char r, unsigned char g, unsigned char b, unsigned char w);
void Set_RGB2(unsigned char r, unsigned char g, unsigned char b, unsigned char w);
void Mode_Breath(void);
void Mode_Rainbow(void);
void Mode_Flowing(void);
void Mode_Strobe(void);
void Mode_Music(void);
void HSV_to_RGB(unsigned int hue, unsigned char *r, unsigned char *g, unsigned char *b);

//=============================================================================
// 函数: System_Init
// 描述: 系统初始化,配置时钟、等待状态等
//=============================================================================
void System_Init(void)
{
    // 使能访问扩展RAM区 (XFR)
    P_SW2 |= 0x80;          // EA=1时使能访问XFR
  
    // 设置外部数据总线速度为最快
    CKCON = 0x00;
  
    // 设置程序读取等待周期为0 (最快)
    WTST = 0x00;
  
    // 选择内部高速IRC时钟作为主时钟 (默认就是,这里再确认一下)
    // CLKSEL 寄存器配置: 默认使用内部IRC
}

//=============================================================================
// 函数: GPIO_Init
// 描述: GPIO初始化,配置PWM输出引脚为推挽输出
//=============================================================================
void GPIO_Init(void)
{
    // PWM1P - P1.0, PWM2P - P1.2, PWM3P - P1.4, PWM4P - P1.6
    // 设置为推挽输出模式 (PxM1=0, PxM0=1)
    P1M0 = 0x55;            // P1.0, P1.2, P1.4, P1.6 推挽输出
    P1M1 = 0x00;
  
    // PWM5 - P2.0, PWM6 - P2.1, PWM7 - P2.2, PWM8 - P2.3
    // 设置为推挽输出模式
    P2M0 = 0x0F;            // P2.0~P2.3 推挽输出
    P2M1 = 0x00;
  
    // 按键输入引脚 P3.2 (INT0) - 设置为高阻输入
    P3M0 &= ~0x04;          // P3.2 清0
    P3M1 |= 0x04;           // P3.2 置1 -> 高阻输入
}

//=============================================================================
// 函数: PWM_Init
// 描述: 初始化PWMA和PWMB两组高级PWM
//       使用高速PWM模式 (异步模式),时钟源选择PLL 144MHz
//       PWM频率 = 20KHz, 周期 = 144MHz / 20KHz = 7200
//=============================================================================
void PWM_Init(void)
{
    unsigned int pwm_period;
  
    // 注意: PWMA_PS 和 PWMB_PS 属于端口控制寄存器,只能用同步模式读写
    // 配置PWMA通道1~4的引脚输出位置
    // C1PS[1:0]=00 -> PWM1P=P1.0, PWM1N=P1.1 (不使用N)
    // C2PS[1:0]=00 -> PWM2P=P1.2, PWM2N=P1.3 (不使用N)
    // C3PS[1:0]=00 -> PWM3P=P1.4, PWM3N=P1.5 (不使用N)
    // C4PS[1:0]=00 -> PWM4P=P1.6, PWM4N=P1.7 (不使用N)
    PWMA_PS = 0x00;         // 所有通道选择默认引脚
  
    // 配置PWMB通道5~8的引脚输出位置
    // C5PS[1:0]=00 -> PWM5=P2.0
    // C6PS[1:0]=00 -> PWM6=P2.1
    // C7PS[1:0]=00 -> PWM7=P2.2
    // C8PS[1:0]=00 -> PWM8=P2.3
    PWMB_PS = 0x00;         // 所有通道选择默认引脚
  
    //---------------------------------------------------------------------
    // 配置高速PWM模式 (异步模式)
    // 使用PLL 144MHz作为HSPWM时钟源
    //---------------------------------------------------------------------
  
    // 选择PLL输出时钟为144MHz
    // CLKSEL.7 (CKMS) = 1 选择PLL 144MHz
    CLKSEL |= 0x80;
  
    // 配置PLL输入时钟分频
    // 系统时钟40MHz,PLL输入需要12MHz,所以分频系数 = 40/12 ≈ 3.33
    // 使用PCKI_D4 (4分频) -> PLL输入 = 40/4 = 10MHz
    // PLL倍频到144MHz
    USBCLK &= ~0x60;        // 清除PCKI_MSK位
    USBCLK |= 0x40;         // PCKI_D4: PLL输入时钟4分频
  
    // 启动PLL
    USBCLK |= 0x80;         // ENCKM = 1, 使能PLL倍频
  
    // 等待PLL锁频 (简单延时)
    {
        unsigned int i;
        for (i = 0; i < 1000; i++);
    }
  
    // 选择HSPWM时钟源为PLL输出时钟
    // CLKSEL.6 (HSIOCK) = 1 选择PLL输出时钟
    CLKSEL |= 0x40;
  
    // HSPWM时钟源不分频
    HSCLKDIV = 0;
  
    //---------------------------------------------------------------------
    // 配置PWMA (通道1~4)
    //---------------------------------------------------------------------
  
    // 使能PWMA异步访问
    HSPWMA_CFG = 0x03;      // 使能异步控制模式和中断
  
    // 通过异步方式配置PWMA寄存器
    // 先关闭所有通道输出
    WritePWMA((char)&PWMA_CCER1, 0x00);
    WritePWMA((char)&PWMA_CCER2, 0x00);
  
    // 配置通道1为PWM输出模式1 (向上计数,CNT<CCR时输出有效)
    WritePWMA((char)&PWMA_CCMR1, 0x60);  // OC1M[2:0]=110, 模式1: 向上计数时CNT<CCR1输出有效
  
    // 配置通道2为PWM输出模式1
    WritePWMA((char)&PWMA_CCMR2, 0x60);
  
    // 配置通道3为PWM输出模式1
    WritePWMA((char)&PWMA_CCMR3, 0x60);
  
    // 配置通道4为PWM输出模式1
    WritePWMA((char)&PWMA_CCMR4, 0x60);
  
    // 使能通道1~4的输出 (只使能P端,不使能N端)
    WritePWMA((char)&PWMA_CCER1, 0x55);   // CC1E=1, CC2E=1, CC3E=1, CC4E=1
  
    // 使能PWM信号输出到端口
    WritePWMA((char)&PWMA_ENO, 0x0F);     // ENO1P=1, ENO2P=1, ENO3P=1, ENO4P=1
  
    // 使能主输出
    WritePWMA((char)&PWMA_BKR, 0x80);     // MOE=1
  
    // 设置PWM周期
    pwm_period = PWM_PERIOD;
    WritePWMA((char)&PWMA_ARRH, (unsigned char)(pwm_period >> 8));
    WritePWMA((char)&PWMA_ARRL, (unsigned char)(pwm_period));
  
    // 初始占空比设为0 (所有通道关闭)
    WritePWMA((char)&PWMA_CCR1H, 0x00);
    WritePWMA((char)&PWMA_CCR1L, 0x00);
    WritePWMA((char)&PWMA_CCR2H, 0x00);
    WritePWMA((char)&PWMA_CCR2L, 0x00);
    WritePWMA((char)&PWMA_CCR3H, 0x00);
    WritePWMA((char)&PWMA_CCR3L, 0x00);
    WritePWMA((char)&PWMA_CCR4H, 0x00);
    WritePWMA((char)&PWMA_CCR4L, 0x00);
  
    // 开始PWM计数
    WritePWMA((char)&PWMA_CR1, 0x01);     // CEN=1, 使能计数器
  
    //---------------------------------------------------------------------
    // 配置PWMB (通道5~8)
    //---------------------------------------------------------------------
  
    // 使能PWMB异步访问
    HSPWMB_CFG = 0x03;
  
    // 通过异步方式配置PWMB寄存器
    WritePWMB((char)&PWMB_CCER1, 0x00);
    WritePWMB((char)&PWMB_CCER2, 0x00);
  
    // 配置通道5~8为PWM输出模式1
    WritePWMB((char)&PWMB_CCMR1, 0x60);
    WritePWMB((char)&PWMB_CCMR2, 0x60);
    WritePWMB((char)&PWMB_CCMR3, 0x60);
    WritePWMB((char)&PWMB_CCMR4, 0x60);
  
    // 使能通道5~8的输出
    WritePWMB((char)&PWMB_CCER1, 0x55);
    WritePWMB((char)&PWMB_CCER2, 0x55);
  
    // 使能PWM信号输出到端口
    WritePWMB((char)&PWMB_ENO, 0x0F);     // ENO5=1, ENO6=1, ENO7=1, ENO8=1
  
    // 使能主输出
    WritePWMB((char)&PWMB_BKR, 0x80);
  
    // 设置PWM周期 (与PWMA相同)
    WritePWMB((char)&PWMB_ARRH, (unsigned char)(pwm_period >> 8));
    WritePWMB((char)&PWMB_ARRL, (unsigned char)(pwm_period));
  
    // 初始占空比设为0
    WritePWMB((char)&PWMB_CCR1H, 0x00);
    WritePWMB((char)&PWMB_CCR1L, 0x00);
    WritePWMB((char)&PWMB_CCR2H, 0x00);
    WritePWMB((char)&PWMB_CCR2L, 0x00);
    WritePWMB((char)&PWMB_CCR3H, 0x00);
    WritePWMB((char)&PWMB_CCR3L, 0x00);
    WritePWMB((char)&PWMB_CCR4H, 0x00);
    WritePWMB((char)&PWMB_CCR4L, 0x00);
  
    // 开始PWM计数
    WritePWMB((char)&PWMB_CR1, 0x01);
}

//=============================================================================
// 函数: Timer0_Init
// 描述: 初始化定时器0,用于产生1ms基准定时中断
//       定时器0: 16位自动重装载模式, 1T模式
//       定时周期 = (65536 - [TH0,TL0]) / (FOSC / (TM0PS+1))
//       设 TM0PS=39, 则定时器时钟 = 40MHz / 40 = 1MHz
//       1ms = (65536 - 64536) / 1MHz = 1000 / 1MHz = 1ms
//       重载值 = 65536 - 1000 = 64536 = 0xFC18
//=============================================================================
void Timer0_Init(void)
{
    // 设置定时器0预分频: TM0PS = 39, 定时器时钟 = 40MHz / 40 = 1MHz
    TM0PS = 39;             // 预分频值 (地址: 7EFEA0H)
  
    // 配置定时器0为16位自动重装载模式 (模式0)
    // TMOD: T0_M1=0, T0_M0=0 -> 模式0 (16位自动重载)
    //       T0_C/T=0 -> 定时器模式
    //       T0_GATE=0 -> 仅TR0控制
    TMOD &= ~0x0F;          // 清空T0相关位
    // TMOD低4位已经是0
  
    // 设置1T模式 (不分频)
    AUXR |= 0x80;           // T0x12 = 1, 1T模式
  
    // 设置重载值: 65536 - 1000 = 64536 = 0xFC18
    // 注意: 在TR0=0时写入TH0/TL0会同时写入隐藏的重载寄存器
    TH0 = 0xFC;             // 高字节
    TL0 = 0x18;             // 低字节
  
    // 清除定时器0中断标志
    TCON &= ~0x20;          // TF0 = 0
  
    // 使能定时器0中断
    IE |= 0x02;             // ET0 = 1
  
    // 启动定时器0
    TCON |= 0x10;           // TR0 = 1
}

//=============================================================================
// 函数: ADC_Init
// 描述: 初始化ADC,用于读取电位器调节速度/亮度
//       使用ADC通道1 (P1.1)
//=============================================================================
void ADC_Init(void)
{
    // 配置P1.1为ADC输入模式 (高阻输入)
    P1M0 &= ~0x02;          // P1.1 清0
    P1M1 |= 0x02;           // P1.1 置1 -> 高阻输入
  
    // 配置ADC控制寄存器
    // ADCCON2: 设置ADC时钟分频
    // ADC时钟 = SYSclk / (ADCCON2 + 1) / 2
    // 设 ADCCON2 = 39, ADC时钟 = 40MHz / 40 / 2 = 500KHz
    ADCCON2 = 39;
  
    // ADCCON1: 设置ADC电源和触发方式
    // ADCPOWER=1 (使能ADC), ADCSTART=0 (不启动)
    // ADCFIFO=0 (普通模式), ADCEXCG=0
    ADCCON1 = 0x80;         // 使能ADC电源
  
    // 等待ADC稳定
    Delay_ms(1);
}

//=============================================================================
// 函数: Read_ADC
// 描述: 读取指定ADC通道的转换结果
// 参数: channel - ADC通道号 (0~15)
// 返回: 12位ADC转换结果 (0~4095)
//=============================================================================
unsigned int Read_ADC(unsigned char channel)
{
    unsigned int result;
  
    // 选择ADC通道
    ADCCON3 = (ADCCON3 & 0xF0) | (channel & 0x0F);
  
    // 启动ADC转换
    ADCCON1 |= 0x40;        // ADCSTART = 1
  
    // 等待转换完成
    while (ADCCON1 & 0x40); // 等待ADCSTART变为0
  
    // 读取转换结果 (12位)
    // ADCRESH: 高8位, ADCRESL: 低4位 (高4位有效)
    result = ((unsigned int)ADCRESH << 4) | (ADCRESL >> 4);
  
    return result;
}

//=============================================================================
// 函数: Delay_ms
// 描述: 毫秒级延时函数 (使用定时器0计数)
// 参数: ms - 延时毫秒数
//=============================================================================
void Delay_ms(unsigned int ms)
{
    unsigned int start_cnt;
  
    // 关闭总中断,防止干扰计数
    EA = 0;
    g_timer0_cnt = 0;
    start_cnt = 0;
    EA = 1;
  
    while (start_cnt < ms)
    {
        // 等待定时器0中断将g_timer0_cnt加1
        if (g_timer0_cnt > start_cnt)
        {
            start_cnt = g_timer0_cnt;
        }
    }
}

//=============================================================================
// 函数: Set_PWM_Duty
// 描述: 设置指定PWM通道的占空比
// 参数: channel - PWM通道号 (1~8)
//       duty    - 占空比计数值 (0 ~ PWM_PERIOD)
//=============================================================================
void Set_PWM_Duty(unsigned char channel, unsigned int duty)
{
    unsigned char h, l;
  
    // 限制占空比不超过周期值
    if (duty > PWM_PERIOD)
    {
        duty = PWM_PERIOD;
    }
  
    h = (unsigned char)(duty >> 8);
    l = (unsigned char)(duty);
  
    switch (channel)
    {
        case 1:  // PWMA 通道1
            WritePWMA((char)&PWMA_CCR1H, h);
            WritePWMA((char)&PWMA_CCR1L, l);
            break;
        case 2:  // PWMA 通道2
            WritePWMA((char)&PWMA_CCR2H, h);
            WritePWMA((char)&PWMA_CCR2L, l);
            break;
        case 3:  // PWMA 通道3
            WritePWMA((char)&PWMA_CCR3H, h);
            WritePWMA((char)&PWMA_CCR3L, l);
            break;
        case 4:  // PWMA 通道4
            WritePWMA((char)&PWMA_CCR4H, h);
            WritePWMA((char)&PWMA_CCR4L, l);
            break;
        case 5:  // PWMB 通道5
            WritePWMB((char)&PWMB_CCR1H, h);
            WritePWMB((char)&PWMB_CCR1L, l);
            break;
        case 6:  // PWMB 通道6
            WritePWMB((char)&PWMB_CCR2H, h);
            WritePWMB((char)&PWMB_CCR2L, l);
            break;
        case 7:  // PWMB 通道7
            WritePWMB((char)&PWMB_CCR3H, h);
            WritePWMB((char)&PWMB_CCR3L, l);
            break;
        case 8:  // PWMB 通道8
            WritePWMB((char)&PWMB_CCR4H, h);
            WritePWMB((char)&PWMB_CCR4L, l);
            break;
        default:
            break;
    }
}

//=============================================================================
// 函数: Set_RGB1
// 描述: 设置第一组RGBW灯的颜色 (PWMA通道1~4)
// 参数: r, g, b, w - 各通道亮度值 (0~100)
//=============================================================================
void Set_RGB1(unsigned char r, unsigned char g, unsigned char b, unsigned char w)
{
    unsigned int duty_r, duty_g, duty_b, duty_w;
  
    // 将百分比亮度转换为PWM占空比计数值
    // 并应用全局亮度系数
    duty_r = (unsigned int)r * g_brightness * PWM_PERIOD / 10000;
    duty_g = (unsigned int)g * g_brightness * PWM_PERIOD / 10000;
    duty_b = (unsigned int)b * g_brightness * PWM_PERIOD / 10000;
    duty_w = (unsigned int)w * g_brightness * PWM_PERIOD / 10000;
  
    Set_PWM_Duty(1, duty_r);    // 红色
    Set_PWM_Duty(2, duty_g);    // 绿色
    Set_PWM_Duty(3, duty_b);    // 蓝色
    Set_PWM_Duty(4, duty_w);    // 白色
}

//=============================================================================
// 函数: Set_RGB2
// 描述: 设置第二组RGBW灯的颜色 (PWMB通道5~8)
// 参数: r, g, b, w - 各通道亮度值 (0~100)
//=============================================================================
void Set_RGB2(unsigned char r, unsigned char g, unsigned char b, unsigned char w)
{
    unsigned int duty_r, duty_g, duty_b, duty_w;
  
    duty_r = (unsigned int)r * g_brightness * PWM_PERIOD / 10000;
    duty_g = (unsigned int)g * g_brightness * PWM_PERIOD / 10000;
    duty_b = (unsigned int)b * g_brightness * PWM_PERIOD / 10000;
    duty_w = (unsigned int)w * g_brightness * PWM_PERIOD / 10000;
  
    Set_PWM_Duty(5, duty_r);    // 红色
    Set_PWM_Duty(6, duty_g);    // 绿色
    Set_PWM_Duty(7, duty_b);    // 蓝色
    Set_PWM_Duty(8, duty_w);    // 白色
}

//=============================================================================
// 函数: HSV_to_RGB
// 描述: 将HSV颜色空间转换为RGB颜色空间
//       用于彩虹渐变效果
// 参数: hue   - 色相 (0~360)
//       *r, *g, *b - 输出RGB值 (0~100)
//=============================================================================
void HSV_to_RGB(unsigned int hue, unsigned char *r, unsigned char *g, unsigned char *b)
{
    unsigned char region;
    unsigned char remainder;
    unsigned char p, q, t;
  
    // 将色相映射到0~360范围
    hue = hue % 360;
  
    region = (unsigned char)(hue / 60);         // 色相区域 (0~5)
    remainder = (unsigned char)((hue % 60) * 100 / 60);  // 区域内位置 (0~100)
  
    p = 0;                                      // 最小值
    q = 100 - (unsigned int)remainder * 100 / 100;  // 下降段
    t = remainder;                              // 上升段
  
    switch (region)
    {
        case 0: *r = 100; *g = t;   *b = p; break;   // 红->黄
        case 1: *r = q;   *g = 100; *b = p; break;   // 黄->绿
        case 2: *r = p;   *g = 100; *b = t; break;   // 绿->青
        case 3: *r = p;   *g = q;   *b = 100; break; // 青->蓝
        case 4: *r = t;   *g = p;   *b = 100; break; // 蓝->紫
        case 5: *r = 100; *g = p;   *b = q; break;   // 紫->红
        default: *r = 0;  *g = 0;   *b = 0; break;
    }
}

//=============================================================================
// 模式函数: Mode_Breath
// 描述: 呼吸灯模式 - 所有LED缓慢呼吸
//       两组灯交替呼吸,产生立体感
//=============================================================================
void Mode_Breath(void)
{
    unsigned char brightness;
    unsigned int step_max;
  
    // 根据速度参数计算步进最大值
    step_max = 200 - g_speed * 2;   // 速度越快,步进越小 (50~200)
    if (step_max < 10) step_max = 10;
  
    // 计算当前亮度 (0~100)
    if (g_breath_dir == 0)
    {
        // 变亮阶段
        brightness = (unsigned char)(g_breath_step * 100 / step_max);
        if (brightness >= 100)
        {
            brightness = 100;
            g_breath_dir = 1;       // 切换为变暗
        }
    }
    else
    {
        // 变暗阶段
        brightness = (unsigned char)(100 - g_breath_step * 100 / step_max);
        if (g_breath_step >= step_max)
        {
            brightness = 0;
            g_breath_dir = 0;       // 切换为变亮
            g_breath_step = 0;
        }
    }
  
    // 第一组: 暖色呼吸 (红+绿=黄)
    Set_RGB1(brightness, brightness >> 1, 0, 0);
  
    // 第二组: 冷色呼吸 (蓝+白)
    Set_RGB2(0, brightness >> 2, brightness, brightness >> 1);
  
    g_breath_step++;
}

//=============================================================================
// 模式函数: Mode_Rainbow
// 描述: 彩虹渐变模式 - 两组灯呈现彩虹色渐变
//=============================================================================
void Mode_Rainbow(void)
{
    unsigned char r1, g1, b1;
    unsigned char r2, g2, b2;
    unsigned int hue1, hue2;
    unsigned int step;
  
    // 根据速度计算步进
    step = 1 + (g_speed / 10);      // 1~11
  
    // 第一组: 当前色相
    hue1 = g_rainbow_index % 360;
    HSV_to_RGB(hue1, &r1, &g1, &b1);
    Set_RGB1(r1, g1, b1, 0);
  
    // 第二组: 色相偏移180度 (互补色)
    hue2 = (g_rainbow_index + 180) % 360;
    HSV_to_RGB(hue2, &r2, &g2, &b2);
    Set_RGB2(r2, g2, b2, 0);
  
    g_rainbow_index += step;
    if (g_rainbow_index >= 360)
    {
        g_rainbow_index -= 360;
    }
}

//=============================================================================
// 模式函数: Mode_Flowing
// 描述: 流水灯模式 - LED依次点亮,形成流水效果
//=============================================================================
void Mode_Flowing(void)
{
    unsigned char i;
    unsigned char r_val[8] = {0};
    unsigned char g_val[8] = {0};
    unsigned char b_val[8] = {0};
    unsigned char w_val[8] = {0};
    unsigned int step;
  
    // 根据速度计算步进
    step = 1 + (g_speed / 20);      // 1~6
  
    // 流水位置 (0~7)
    g_flow_pos = (g_flow_pos + step) % 8;
  
    // 点亮当前位置的LED (红色)
    // 每个位置对应一个通道
    for (i = 0; i < 8; i++)
    {
        if (i == g_flow_pos)
        {
            r_val[i] = 100;         // 主灯全亮
        }
        else if ((i + 1) % 8 == g_flow_pos || (i + 7) % 8 == g_flow_pos)
        {
            r_val[i] = 30;          // 相邻灯半亮 (拖尾效果)
        }
        else
        {
            r_val[i] = 0;
        }
    }
  
    // 设置第一组 (通道1~4)
    Set_RGB1(r_val[0], g_val[0], b_val[0], w_val[0]);
  
    // 设置第二组 (通道5~8)
    // 注意: Set_RGB2 设置的是通道5~8
    Set_PWM_Duty(5, (unsigned int)r_val[4] * g_brightness * PWM_PERIOD / 10000);
    Set_PWM_Duty(6, (unsigned int)r_val[5] * g_brightness * PWM_PERIOD / 10000);
    Set_PWM_Duty(7, (unsigned int)r_val[6] * g_brightness * PWM_PERIOD / 10000);
    Set_PWM_Duty(8, (unsigned int)r_val[7] * g_brightness * PWM_PERIOD / 10000);
}

//=============================================================================
// 模式函数: Mode_Strobe
// 描述: 爆闪模式 - 快速闪烁,模拟警灯效果
//=============================================================================
void Mode_Strobe(void)
{
    unsigned int flash_rate;
  
    // 根据速度计算闪烁频率
    flash_rate = 5 + (g_speed / 5);     // 5~25
  
    g_strobe_cnt++;
  
    if (g_strobe_cnt < flash_rate)
    {
        // 第一组红色全亮,第二组蓝色全亮
        Set_RGB1(100, 0, 0, 0);         // 红
        Set_RGB2(0, 0, 100, 0);         // 蓝
    }
    else if (g_strobe_cnt < flash_rate * 2)
    {
        // 第一组蓝色全亮,第二组红色全亮 (交替)
        Set_RGB1(0, 0, 100, 0);         // 蓝
        Set_RGB2(100, 0, 0, 0);         // 红
    }
    else
    {
        g_strobe_cnt = 0;
    }
}

//=============================================================================
// 模式函数: Mode_Music
// 描述: 音乐律动模式 - 模拟音乐节奏效果
//       使用ADC读取外部音频信号 (连接到P1.1)
//=============================================================================
void Mode_Music(void)
{
    unsigned int adc_val;
    unsigned char level;
    unsigned char r, g, b;
  
    // 读取ADC值 (模拟音频信号)
    adc_val = Read_ADC(1);              // 读取ADC通道1
  
    // 将ADC值映射到0~100
    level = (unsigned char)(adc_val * 100 / 4095);
  
    // 根据音频电平设置颜色
    if (level < 10)
    {
        // 低电平: 微弱蓝光
        Set_RGB1(0, 0, 20, 0);
        Set_RGB2(0, 0, 20, 0);
    }
    else if (level < 30)
    {
        // 中低电平: 绿色
        r = (unsigned char)(level * 2);
        Set_RGB1(0, level, 0, 0);
        Set_RGB2(0, level, 0, 0);
    }
    else if (level < 60)
    {
        // 中电平: 黄色
        Set_RGB1(level, level >> 1, 0, 0);
        Set_RGB2(level, level >> 1, 0, 0);
    }
    else if (level < 80)
    {
        // 中高电平: 红色
        Set_RGB1(level, 0, 0, 0);
        Set_RGB2(level, 0, 0, 0);
    }
    else
    {
        // 高电平: 白色爆闪
        Set_RGB1(100, 100, 100, 100);
        Set_RGB2(100, 100, 100, 100);
    }
}

//=============================================================================
// 函数: Check_Key
// 描述: 检测按键并切换模式
//       使用P3.2 (INT0) 检测下降沿
//=============================================================================
void Check_Key(void)
{
    // 检测P3.2是否被拉低 (按键按下)
    if ((P3 & 0x04) == 0)
    {
        // 延时消抖
        Delay_ms(20);
      
        // 再次确认按键按下
        if ((P3 & 0x04) == 0)
        {
            // 切换模式
            g_mode++;
            if (g_mode > MODE_MAX)
            {
                g_mode = MODE_BREATH;
            }
          
            // 设置模式切换标志
            g_mode_change_flag = 1;
          
            // 等待按键释放
            while ((P3 & 0x04) == 0);
          
            // 释放后延时消抖
            Delay_ms(20);
        }
    }
}

//=============================================================================
// 函数: Update_Speed
// 描述: 通过ADC读取电位器,更新速度和亮度参数
//=============================================================================
void Update_Speed(void)
{
    unsigned int adc_val;
  
    // 读取ADC通道1 (P1.1)
    adc_val = Read_ADC(1);
  
    // 高8位作为速度 (0~100)
    g_speed = (unsigned int)adc_val * 100 / 4095;
  
    // 如果速度值小于5,设为5 (防止太慢)
    if (g_speed < 5) g_speed = 5;
}

//=============================================================================
// 函数: main
// 描述: 主函数
//=============================================================================
void main(void)
{
    unsigned int main_cnt = 0;
  
    // 系统初始化
    System_Init();
  
    // GPIO初始化
    GPIO_Init();
  
    // PWM初始化
    PWM_Init();
  
    // 定时器0初始化 (1ms中断)
    Timer0_Init();
  
    // ADC初始化
    ADC_Init();
  
    // 打开总中断
    EA = 1;
  
    // 初始状态: 关闭所有LED
    Set_RGB1(0, 0, 0, 0);
    Set_RGB2(0, 0, 0, 0);
  
    // 主循环
    while (1)
    {
        // 检测按键切换模式
        Check_Key();
      
        // 如果模式切换,重置相关参数
        if (g_mode_change_flag)
        {
            g_mode_change_flag = 0;
            g_breath_step = 0;
            g_breath_dir = 0;
            g_rainbow_index = 0;
            g_flow_pos = 0;
            g_strobe_cnt = 0;
        }
      
        // 每10ms更新一次速度和模式执行
        if (main_cnt >= 10)     // 10ms
        {
            main_cnt = 0;
          
            // 更新速度参数 (每100ms更新一次,减少ADC读取频率)
            if ((g_timer0_cnt % 100) == 0)
            {
                Update_Speed();
            }
          
            // 根据当前模式执行对应的灯光效果
            switch (g_mode)
            {
                case MODE_BREATH:
                    Mode_Breath();
                    break;
                  
                case MODE_RAINBOW:
                    Mode_Rainbow();
                    break;
                  
                case MODE_FLOWING:
                    Mode_Flowing();
                    break;
                  
                case MODE_STROBE:
                    Mode_Strobe();
                    break;
                  
                case MODE_MUSIC:
                    Mode_Music();
                    break;
                  
                default:
                    g_mode = MODE_BREATH;
                    break;
            }
        }
      
        // 简单延时,控制主循环频率
        {
            unsigned int i;
            for (i = 0; i < 1000; i++);
        }
      
        main_cnt++;
    }
}

//=============================================================================
// 中断服务函数: Timer0_ISR
// 描述: 定时器0中断服务程序,每1ms触发一次
//       用于提供系统时基
//=============================================================================
void Timer0_ISR(void) interrupt 1
{
    // 定时器0中断标志TF0由硬件自动清除
  
    // 更新系统时基计数器
    g_timer0_cnt++;
  
    // 如果计数器溢出,重新从0开始
    if (g_timer0_cnt >= 60000)
    {
        g_timer0_cnt = 0;
    }
}

程序说明

硬件设计思路

这个程序针对摩托车外挂炫彩灯应用,使用 STC32CL8K64两组高级PWM (PWMA + PWMB)8个通道 来驱动RGBW LED灯带:

通道 引脚 功能 说明
PWMA-CH1 P1.0 红色灯组1 第一组RGBW
PWMA-CH2 P1.2 绿色灯组1 第一组RGBW
PWMA-CH3 P1.4 蓝色灯组1 第一组RGBW
PWMA-CH4 P1.6 白色灯组1 第一组RGBW
PWMB-CH5 P2.0 红色灯组2 第二组RGBW
PWMB-CH6 P2.1 绿色灯组2 第二组RGBW
PWMB-CH7 P2.2 蓝色灯组2 第二组RGBW
PWMB-CH8 P2.3 白色灯组2 第二组RGBW

5种炫彩模式

  1. 呼吸灯模式 — 两组灯以不同色调缓慢呼吸,第一组暖色(黄),第二组冷色(蓝白)
  2. 彩虹渐变模式 — 两组灯呈现互补色渐变,色相连续变化
  3. 流水灯模式 — 8个通道依次点亮,带拖尾效果
  4. 爆闪模式 — 红蓝交替闪烁,模拟警灯效果
  5. 音乐律动模式 — 通过ADC读取外部音频信号,随音乐节奏变换颜色

关键设计要点

  • 高速PWM模式:使用PLL 144MHz作为PWM时钟源,PWM频率20KHz,避免人耳可闻噪声
  • 异步寄存器访问:通过 HSPWMA_CFG / HSPWMB_CFG 使能异步模式,通过 WritePWMA/WritePWMB 函数操作PWM寄存器
  • 1ms定时基准:定时器0配置为1ms中断,提供系统时基
  • ADC电位器调节:通过ADC读取外部电位器,实时调节速度和亮度

使用步骤

  1. STC-ISP 下载软件中选择芯片型号 STC32CL8K64
  2. 选择内部IRC时钟 40MHz
  3. 使用 Keil C251 编译器,选择 Large 内存模式编译
  4. 通过 STC-USB Link1DUSB转串口 下载程序
  5. 硬件连接:PWM输出引脚通过 MOS管 (如IRLZ44N) 驱动12V LED灯带

注意:实际驱动大功率LED灯带时,PWM引脚不能直接驱动,必须通过 N沟道MOS管 进行功率放大,并在MOS管栅极加 10KΩ下拉电阻 防止上电瞬间误亮。


🔍 搜索 "用 STC32CL8K64写一个摩托车外挂炫彩灯的程序 并含详细注解"

📌 相关论坛讨论

  1. 【AI助手】@AiCube-ISP-V6.96Z中,【自动生成程序】+【查错高手】

⏱️ 查找用时 15.5s | 总回答用时 91.7s

热线19952583534
www.STCAI.com
回复

使用道具 举报 送花

  • 打卡等级:常住居民III
  • 打卡总天数:104
  • 最近打卡:2026-07-21 08:50:20

50

主题

353

回帖

2848

积分

超级版主

积分
2848
发表于 昨天 08:50 | 显示全部楼层
STC32CL8K64-33A-TSSOP20 用CAN 读电池的状态,如何设计 和出程序 详细注解

截图202607210848555319.jpg

QQ:3398500488
微信号:18106296592(小刘)
回复

使用道具 举报 送花

  • 打卡等级:常住居民III
  • 打卡总天数:104
  • 最近打卡:2026-07-21 08:50:20

50

主题

353

回帖

2848

积分

超级版主

积分
2848
发表于 昨天 08:50 | 显示全部楼层

现在我已经掌握了所有必要的信息。让我来整理完整的回答。


STC32CL8K64-33A-TSSOP20 用 CAN 读取电池状态 — 完整设计方案与程序

一、硬件设计

1.1 引脚分配

根据手册 「不同封装形式管脚对应功能说明」 章节,TSSOP20 封装中:

引脚编号 引脚名称 CAN 功能 说明
19 P0.0 CAN_RX CAN 总线接收脚(输入)
20 P0.1 CAN_TX CAN 总线发送脚(输出)

注意:TSSOP20 封装中,CAN1 的 RX/TX 默认映射在 P0.0 / P0.1(对应 P_SW1CAN_S[1:0] = 00),无需额外切换。同时 P0.2/P0.3 还可作为 CAN2_RX/CAN2_TX(第二组 CAN)。

1.2 外部 CAN 收发器电路

STC32CL8K64 内部是 CAN 控制器,必须外接 CAN 收发器(如 TJA1050 / SN65HVD230 / MCP2551)才能连接到 CAN 总线。

                    +-------------------+
                    |  STC32CL8K64      |
                    |  TSSOP20          |
                    |                   |
   Pin19 (P0.0) --->| CAN_RX            |
   Pin20 (P0.1) --->| CAN_TX            |
                    +-------------------+
                           |        |
                           |        |
                    +------+--------+------+
                    |      |               |
                    |  +---+---+           |
                    |  |  CAN  |           |
                    |  | Trans|           |
                    |  | ceiver           |
                    |  |(TJA1050)         |
                    |  +---+---+          |
                    |      |              |
                    +------+--------------+
                           |
                    CAN_H  |  CAN_L
                           |
                    +------+------+
                    |  CAN Bus   |
                    | (电池管理   |
                    |  BMS节点)  |
                    +-----------+

典型电路(以 TJA1050 为例):

  • TJA1050 的 TXD → STC32CL8K64 的 P0.1 (CAN_TX)
  • TJA1050 的 RXD → STC32CL8K64 的 P0.0 (CAN_RX)
  • TJA1050 的 VCC → 5V(或 3.3V,视模块规格)
  • TJA1050 的 GND → GND
  • TJA1050 的 CANH/CANL → 总线终端各接一个 120Ω 终端电阻

1.3 电源与去耦

  • Vcc (Pin8):接 3.3V 或 5V,靠近引脚放置 0.1μF + 10μF 去耦电容
  • Gnd (Pin10):接系统地
  • ADC_VRef+ (Pin9):若使用 ADC 监控电池电压,接基准电压(如 2.5V 或 VCC)

二、软件设计

2.1 通信协议约定

假设电池管理系统(BMS)通过 CAN 总线以 标准数据帧 发送电池状态,协议约定如下:

参数
CAN 波特率 250 kbps(常用 BMS 速率)
帧格式 标准帧(11位ID)
BMS 发送 ID 0x100(电池状态上报)
本机发送 ID 0x101(查询命令,可选)
数据长度 8 字节
数据格式 Byte0: 总电压高字节Byte1: 总电压低字节(单位: 0.01V)Byte2: 电流高字节Byte3: 电流低字节(单位: 0.01A,有符号)Byte4: 剩余电量百分比(%)Byte5: 温度(℃, 补码)Byte6: 单体最高电压(0.01V)Byte7: 单体最低电压(0.01V)

2.2 程序架构

采用 中断接收 + 主循环轮询处理 的方式:

  • CAN 接收中断将数据存入缓冲区
  • 主循环解析缓冲区数据,更新电池状态变量
  • 可通过串口输出或数码管显示

三、完整程序代码(Keil C251 工程)

/******************************************************************************
 * 文件名: main.c
 * 芯片  : STC32CL8K64-33A (TSSOP20)
 * 功能  : 通过 CAN 总线读取电池状态(电压、电流、电量、温度)
 * 编译  : Keil C251
 * 时钟  : 24MHz 内部 IRC
 * CAN   : 250kbps, 标准帧, 验收滤波只接收 ID=0x100
 *
 * 硬件连接:
 *   P0.0 (Pin19) - CAN_RX → TJA1050 RXD
 *   P0.1 (Pin20) - CAN_TX → TJA1050 TXD
 *   P3.0 (Pin11) - UART1_RX (调试用, 可选)
 *   P3.1 (Pin12) - UART1_TX (调试用, 可选)
 *
 * 使用说明:
 *   1. 用 STC-ISP 下载软件选择 STC32CL8K64 系列
 *   2. 设置内部 IRC 频率为 24MHz
 *   3. 编译下载后, 上电自动初始化 CAN
 *   4. 收到 ID=0x100 的 CAN 帧后自动解析并更新全局变量
 *   5. 可通过串口1输出电池状态信息
 ******************************************************************************/

#include "stc32g.h"         // STC32G 系列头文件
#include "intrins.h"

/* ======================== 用户配置宏 ======================== */

#define MAIN_Fosc    24000000UL      // 系统主频 24MHz

// CAN 波特率计算 (250kbps)
// 公式: 波特率 = Fclk / ((1 + (TSG1+1) + (TSG2+1)) * (BRP+1) * 2)
// 24MHz / ((1 + 4 + 2) * 4 * 2) = 24MHz / 56 ≈ 428.57kbps
// 调整为: TSG1=3, TSG2=1, BRP=5
// 24MHz / ((1 + 4 + 2) * 6 * 2) = 24MHz / 84 ≈ 285.7kbps
// 再调整: TSG1=4, TSG2=2, BRP=3
// 24MHz / ((1 + 5 + 3) * 4 * 2) = 24MHz / 72 ≈ 333kbps
// 精确计算 250kbps:
// 24MHz / 250kbps = 96 个 Tq
// 96 = (1 + (TSG1+1) + (TSG2+1)) * (BRP+1) * 2
// 设 BRP=3 → (BRP+1)*2 = 8 → 96/8 = 12 → 1+(TSG1+1)+(TSG2+1)=12
// 设 TSG1=8, TSG2=2 → 1+9+3=13 → 13*8=104 → 24M/104=230.7k ❌
// 设 BRP=2 → (BRP+1)*2 = 6 → 96/6=16 → 1+(TSG1+1)+(TSG2+1)=16
// 设 TSG1=12, TSG2=2 → 1+13+3=17 → 17*6=102 → 24M/102=235k ❌
// 设 BRP=1 → (BRP+1)*2 = 4 → 96/4=24 → 1+(TSG1+1)+(TSG2+1)=24
// 设 TSG1=19, TSG2=3 → 1+20+4=25 → 25*4=100 → 24M/100=240k ❌
// 使用手册推荐的 500kbps 配置更容易:
// TSG1=2, TSG2=1, BRP=3 → 24M/((1+3+2)*4*2)=24M/48=500kbps
// 对于 250kbps, 将 BRP 加倍: BRP=7
// 24M/((1+3+2)*8*2)=24M/96=250kbps ✓

#define CAN_TSG1    2               // 时间段1 (0~15)
#define CAN_TSG2    1               // 时间段2 (0~7)
#define CAN_BRP     7               // 波特率预分频 (0~63)
#define CAN_SJW     1               // 同步跳转宽度

// 验收滤波配置 - 只接收 ID=0x100 的标准帧
// 标准帧 ID 共 11 位, 在帧格式中:
// 字节1: FF(1bit)+RTR(1bit)+DLC(4bit)
// 字节2: ID[10:3] (8bit)
// 字节3: ID[2:0] (3bit) + 5bit 填充
// 单滤波模式下, ACR0~ACR3 与 AMR0~AMR3 共 32 位参与滤波
// 对于标准帧, 参与滤波的位为:
//   ACR0[7:0] = ID[10:3]
//   ACR1[7:5] = ID[2:0], ACR1[4:0] = 无关位
//   ACR2[7:0] = 数据字节0
//   ACR3[7:0] = 数据字节1
// 我们只过滤 ID, 不过滤数据, 所以 ACR2/ACR3 全部屏蔽

#define CAN_ACR0    0x20            // ID=0x100 的高8位: 0x100>>3 = 0x20
#define CAN_ACR1    0x00            // ID=0x100 的低3位: (0x100&0x07)<<5 = 0x00
#define CAN_ACR2    0x00            // 数据字节0 不过滤
#define CAN_ACR3    0x00            // 数据字节1 不过滤

#define CAN_AMR0    0x00            // ID 高8位必须匹配 (AMR=0 表示相关)
#define CAN_AMR1    0x1F            // ID 低3位必须匹配, 低5位无关
#define CAN_AMR2    0xFF            // 数据字节0 不关心
#define CAN_AMR3    0xFF            // 数据字节1 不关心

/* ======================== 电池状态结构体 ======================== */

typedef struct {
    unsigned int    voltage;        // 总电压 (0.01V), 如 4800 = 48.00V
    int             current;        // 电流 (0.01A), 有符号, 如 -500 = -5.00A
    unsigned char   soc;            // 剩余电量百分比 (0~100)
    signed char     temperature;    // 温度 (℃), 如 25
    unsigned char   cell_max;       // 单体最高电压 (0.01V)
    unsigned char   cell_min;       // 单体最低电压 (0.01V)
    unsigned char   frame_ok;       // 收到有效帧标志, 主循环清零
} BatteryStatus;

/* ======================== 全局变量 ======================== */

BatteryStatus g_battery;            // 电池状态全局变量
unsigned char g_rx_buf[8];          // CAN 接收数据缓冲区

/* ======================== 函数声明 ======================== */

void System_Init(void);
void CAN_Init(void);
void CAN_SetBaudrate(void);
void UART1_Init(void);
void UART1_SendString(unsigned char *str);
void UART1_SendHex(unsigned char dat);
void PrintBatteryStatus(void);

// CAN 内部寄存器读写函数
unsigned char CAN_ReadReg(unsigned char addr);
void CAN_WriteReg(unsigned char addr, unsigned char dat);
void CAN_ReadFifo(unsigned char *pdat);
unsigned int  CAN_ReadMsg(unsigned char *pdat);
void CAN_SendMsg(unsigned int canid, unsigned char *pdat);

/* ======================== 主函数 ======================== */

void main(void)
{
    unsigned char i;

    System_Init();                  // 系统初始化
    UART1_Init();                   // 串口1初始化 (调试输出)
    CAN_Init();                     // CAN 初始化

    EA = 1;                         // 打开总中断

    // 发送一条 UART 提示信息
    UART1_SendString("STC32CL8K64 CAN Battery Monitor\r\n");
    UART1_SendString("Waiting for CAN frame ID=0x100...\r\n");

    while (1)
    {
        // 检查是否收到新帧
        if (g_battery.frame_ok)
        {
            g_battery.frame_ok = 0;     // 清除标志

            // 打印电池状态
            PrintBatteryStatus();

            // 此处可添加其他处理逻辑, 如:
            // - 电压过低时报警
            // - 通过 PWM 控制风扇
            // - 通过 I/O 口控制继电器
            // - 通过串口上传到上位机
        }

        // 可添加其他轮询任务
    }
}

/* ======================== 系统初始化 ======================== */

void System_Init(void)
{
    // 使能访问 XFR (扩展特殊功能寄存器)
    // 注意: 操作 XFR 前必须设置此位, 手册 "特殊功能寄存器" 章节说明
    P_SW2 |= 0x80;                  // EAXFR = 1

    // 设置程序等待参数, 最快速度
    WTST = 0x00;

    // 设置外部数据总线速度为最快
    CKCON = 0x00;

    // 将所有 I/O 口设置为准双向口 (默认模式)
    P0M1 = 0x00; P0M0 = 0x00;
    P1M1 = 0x00; P1M0 = 0x00;
    P2M1 = 0x00; P2M0 = 0x00;
    P3M1 = 0x00; P3M0 = 0x00;
    P4M1 = 0x00; P4M0 = 0x00;
    P5M1 = 0x00; P5M0 = 0x00;

    // 初始化电池状态
    g_battery.voltage     = 0;
    g_battery.current     = 0;
    g_battery.soc         = 0;
    g_battery.temperature = 0;
    g_battery.cell_max    = 0;
    g_battery.cell_min    = 0;
    g_battery.frame_ok    = 0;
}

/* ======================== UART1 初始化 (调试用) ======================== */

void UART1_Init(void)
{
    // 使用定时器2做波特率发生器, 115200bps @ 24MHz
    // 公式: 波特率 = Fclk / (65536 - RL) / 4
    // RL = 65536 - Fclk / 4 / 波特率
    // RL = 65536 - 24000000 / 4 / 115200 = 65536 - 52 = 65484 = 0xFFCC
    SCON = 0x50;                    // 模式1, 8位UART, 允许接收
    T2L  = 0xCC;                    // 定时器2低字节
    T2H  = 0xFF;                    // 定时器2高字节
    AUXR = 0x14;                    // T2R=1 (启动定时器2), T2_C/T=0 (定时), T2x12=1 (1T模式)
    // AUXR 地址 0x8E, 不可位寻址, 直接赋值
    // 注意: AUXR 的 B4 是 T2R, B2 是 T2_C/T, B0 是 T2x12
    // 0x14 = 0001 0100 → T2R=1, T2x12=1
}

/* ======================== UART1 发送字符串 ======================== */

void UART1_SendString(unsigned char *str)
{
    while (*str)
    {
        SBUF = *str++;
        while (TI == 0);            // 等待发送完成
        TI = 0;
    }
}

/* ======================== UART1 发送十六进制数 ======================== */

void UART1_SendHex(unsigned char dat)
{
    unsigned char nibble;

    // 高4位
    nibble = (dat >> 4) & 0x0F;
    if (nibble < 10)
        SBUF = '0' + nibble;
    else
        SBUF = 'A' + (nibble - 10);
    while (TI == 0);
    TI = 0;

    // 低4位
    nibble = dat & 0x0F;
    if (nibble < 10)
        SBUF = '0' + nibble;
    else
        SBUF = 'A' + (nibble - 10);
    while (TI == 0);
    TI = 0;
}

/* ======================== 打印电池状态 ======================== */

void PrintBatteryStatus(void)
{
    unsigned char buf[64];
    unsigned int  volt_int, volt_dec;
    int           curr_int, curr_dec;
    unsigned char temp_str[4];

    // 解析电压: g_battery.voltage 单位 0.01V
    // 如 4800 → 48.00V
    volt_int = g_battery.voltage / 100;
    volt_dec = g_battery.voltage % 100;

    // 解析电流: g_battery.current 单位 0.01A
    // 如 -500 → -5.00A
    curr_int = g_battery.current / 100;
    curr_dec = g_battery.current % 100;
    if (curr_dec < 0) curr_dec = -curr_dec;

    // 温度处理
    if (g_battery.temperature < 0)
    {
        temp_str[0] = '-';
        temp_str[1] = '0' + ((-g_battery.temperature) / 10);
        temp_str[2] = '0' + ((-g_battery.temperature) % 10);
        temp_str[3] = '\0';
    }
    else
    {
        temp_str[0] = ' ';
        temp_str[1] = '0' + (g_battery.temperature / 10);
        temp_str[2] = '0' + (g_battery.temperature % 10);
        temp_str[3] = '\0';
    }

    // 格式化输出
    // 使用 sprintf 需要包含 stdio.h, 这里手动组装
    UART1_SendString("=== Battery Status ===\r\n");

    UART1_SendString("Voltage: ");
    SBUF = '0' + (volt_int / 10);
    while (TI == 0); TI = 0;
    SBUF = '0' + (volt_int % 10);
    while (TI == 0); TI = 0;
    SBUF = '.';
    while (TI == 0); TI = 0;
    SBUF = '0' + (volt_dec / 10);
    while (TI == 0); TI = 0;
    SBUF = '0' + (volt_dec % 10);
    while (TI == 0); TI = 0;
    UART1_SendString("V\r\n");

    UART1_SendString("Current: ");
    if (g_battery.current < 0)
    {
        SBUF = '-';
        while (TI == 0); TI = 0;
    }
    if (curr_int < 0) curr_int = -curr_int;
    SBUF = '0' + (curr_int / 10);
    while (TI == 0); TI = 0;
    SBUF = '0' + (curr_int % 10);
    while (TI == 0); TI = 0;
    SBUF = '.';
    while (TI == 0); TI = 0;
    SBUF = '0' + (curr_dec / 10);
    while (TI == 0); TI = 0;
    SBUF = '0' + (curr_dec % 10);
    while (TI == 0); TI = 0;
    UART1_SendString("A\r\n");

    UART1_SendString("SOC: ");
    SBUF = '0' + (g_battery.soc / 10);
    while (TI == 0); TI = 0;
    SBUF = '0' + (g_battery.soc % 10);
    while (TI == 0); TI = 0;
    UART1_SendString("%\r\n");

    UART1_SendString("Temp: ");
    UART1_SendString(temp_str);
    UART1_SendString("C\r\n");

    UART1_SendString("Cell Max: ");
    SBUF = '0' + (g_battery.cell_max / 10);
    while (TI == 0); TI = 0;
    SBUF = '.';
    while (TI == 0); TI = 0;
    SBUF = '0' + (g_battery.cell_max % 10);
    while (TI == 0); TI = 0;
    UART1_SendString("V, Min: ");
    SBUF = '0' + (g_battery.cell_min / 10);
    while (TI == 0); TI = 0;
    SBUF = '.';
    while (TI == 0); TI = 0;
    SBUF = '0' + (g_battery.cell_min % 10);
    while (TI == 0); TI = 0;
    UART1_SendString("V\r\n\r\n");
}

/* ======================== CAN 内部寄存器读写 ======================== */

// 读 CAN 内部功能寄存器
// 方法: 将地址写入 CANAR, 然后读取 CANDR
unsigned char CAN_ReadReg(unsigned char addr)
{
    unsigned char dat;
    CANAR = addr;                   // 写入内部寄存器地址
    dat = CANDR;                    // 读取数据
    return dat;
}

// 写 CAN 内部功能寄存器
// 方法: 将地址写入 CANAR, 然后将数据写入 CANDR
void CAN_WriteReg(unsigned char addr, unsigned char dat)
{
    CANAR = addr;                   // 写入内部寄存器地址
    CANDR = dat;                    // 写入数据
}

/* ======================== CAN 接收 FIFO 读取 ======================== */

// 从 CAN 接收 FIFO 读取 16 字节原始数据
// CAN 的 RXFIFO 是 64 字节 FIFO, 通过 RXBUF0~RXBUF3 循环读取
// 每读一次 RXBUF3, 指针自动+1, 重新装载下一组 4 字节
// 一帧标准帧最长 11 字节 (3 字节帧头 + 8 字节数据)
// 一帧扩展帧最长 13 字节 (5 字节帧头 + 8 字节数据)
// 这里读取 16 字节确保覆盖所有情况
void CAN_ReadFifo(unsigned char *pdat)
{
    // 第1组 4 字节
    pdat[0]  = CAN_ReadReg(0x0C);   // RXBUF0
    pdat[1]  = CAN_ReadReg(0x0D);   // RXBUF1
    pdat[2]  = CAN_ReadReg(0x0E);   // RXBUF2
    pdat[3]  = CAN_ReadReg(0x0F);   // RXBUF3 (读后指针+1)

    // 第2组 4 字节
    pdat[4]  = CAN_ReadReg(0x0C);   // RXBUF0
    pdat[5]  = CAN_ReadReg(0x0D);   // RXBUF1
    pdat[6]  = CAN_ReadReg(0x0E);   // RXBUF2
    pdat[7]  = CAN_ReadReg(0x0F);   // RXBUF3 (读后指针+1)

    // 第3组 4 字节
    pdat[8]  = CAN_ReadReg(0x0C);   // RXBUF0
    pdat[9]  = CAN_ReadReg(0x0D);   // RXBUF1
    pdat[10] = CAN_ReadReg(0x0E);   // RXBUF2
    pdat[11] = CAN_ReadReg(0x0F);   // RXBUF3 (读后指针+1)

    // 第4组 4 字节
    pdat[12] = CAN_ReadReg(0x0C);   // RXBUF0
    pdat[13] = CAN_ReadReg(0x0D);   // RXBUF1
    pdat[14] = CAN_ReadReg(0x0E);   // RXBUF2
    pdat[15] = CAN_ReadReg(0x0F);   // RXBUF3 (读后指针+1)
}

/* ======================== CAN 接收消息解析 ======================== */

// 从 FIFO 中解析一帧标准帧, 返回 CAN ID, 数据存入 pdat
// 标准帧帧格式 (手册 "CAN总线帧格式" 章节):
//   字节0: FF(1) + RTR(1) + DLC(4)
//   字节1: ID[10:3]
//   字节2: ID[2:0] + 5位填充
//   字节3~10: 数据 (最多8字节)
unsigned int CAN_ReadMsg(unsigned char *pdat)
{
    unsigned char i;
    unsigned int  can_id;
    unsigned char buffer[16];

    CAN_ReadFifo(buffer);           // 读取原始 FIFO 数据

    // 解析标准帧 ID
    // ID = (buffer[1] << 3) | (buffer[2] >> 5)
    can_id = ((unsigned int)buffer[1] << 3) | (buffer[2] >> 5);

    // 提取数据字节 (从 buffer[3] 开始, 最多 8 字节)
    for (i = 0; i < 8; i++)
    {
        pdat[i] = buffer[i + 3];
    }

    return can_id;
}

/* ======================== CAN 发送消息 ======================== */

// 发送一帧标准数据帧
// canid: 11 位 CAN ID
// pdat : 指向 8 字节数据缓冲区
void CAN_SendMsg(unsigned int canid, unsigned char *pdat)
{
    unsigned int temp_id;

    // 将 ID 左移 5 位, 放入帧格式的对应位置
    temp_id = canid << 5;

    // 写入发送缓冲区
    // 字节0: 帧信息 = 0x08 (标准帧+数据帧+DLC=8)
    CAN_WriteReg(0x08, 0x08);       // TXBUF0 = 标准帧, 数据帧, 8字节

    // 字节1: ID[10:3]
    CAN_WriteReg(0x09, (unsigned char)(temp_id >> 8));  // TXBUF1

    // 字节2: ID[2:0] + 填充
    CAN_WriteReg(0x0A, (unsigned char)temp_id);         // TXBUF2

    // 写入 8 字节数据 (通过循环写入 TXBUF0~TXBUF3)
    // 注意: 每写一次 TXBUF3, 内部指针+1
    CAN_WriteReg(0x0B, pdat[0]);    // TXBUF3 = 数据0
    CAN_WriteReg(0x08, pdat[1]);    // TXBUF0 = 数据1
    CAN_WriteReg(0x09, pdat[2]);    // TXBUF1 = 数据2
    CAN_WriteReg(0x0A, pdat[3]);    // TXBUF2 = 数据3
    CAN_WriteReg(0x0B, pdat[4]);    // TXBUF3 = 数据4
    CAN_WriteReg(0x08, pdat[5]);    // TXBUF0 = 数据5
    CAN_WriteReg(0x09, pdat[6]);    // TXBUF1 = 数据6
    CAN_WriteReg(0x0A, pdat[7]);    // TXBUF2 = 数据7
    CAN_WriteReg(0x0B, 0x00);       // TXBUF3 = 填充 (最后一组)

    // 发起发送请求
    // CMR 寄存器 (地址 0x01), B2=TR 位, 写 1 发起传输
    CAN_WriteReg(0x01, 0x04);       // CMR = 0x04, TR=1
}

/* ======================== CAN 波特率设置 ======================== */

void CAN_SetBaudrate(void)
{
    // BTR0 (地址 0x06):
    //   B7~B6: SJW[1:0]
    //   B5~B0: BRP[5:0]
    // BRP 实际分频系数 = BRP[5:0] + 1
    CAN_WriteReg(0x06, (CAN_SJW << 6) | CAN_BRP);

    // BTR1 (地址 0x07):
    //   B7: SAM (采样次数, 0=1次, 1=3次)
    //   B6~B4: TSG2[2:0]
    //   B3~B0: TSG1[3:0]
    // 采样点 = (1 + (TSG1+1)) / (1 + (TSG1+1) + (TSG2+1))
    CAN_WriteReg(0x07, (CAN_TSG2 << 4) | CAN_TSG1);
}

/* ======================== CAN 初始化 ======================== */

void CAN_Init(void)
{
    // 步骤1: 设置波特率
    CAN_SetBaudrate();

    // 步骤2: 设置验收代码寄存器 (ACR0~ACR3)
    // 单滤波模式下, 与接收帧的 ID 和数据字节进行比较
    CAN_WriteReg(0x10, CAN_ACR0);   // ACR0
    CAN_WriteReg(0x11, CAN_ACR1);   // ACR1
    CAN_WriteReg(0x12, CAN_ACR2);   // ACR2
    CAN_WriteReg(0x13, CAN_ACR3);   // ACR3

    // 步骤3: 设置验收屏蔽寄存器 (AMR0~AMR3)
    // AMR=0: 对应位必须匹配; AMR=1: 对应位不关心
    CAN_WriteReg(0x14, CAN_AMR0);   // AMR0
    CAN_WriteReg(0x15, CAN_AMR1);   // AMR1
    CAN_WriteReg(0x16, CAN_AMR2);   // AMR2
    CAN_WriteReg(0x17, CAN_AMR3);   // AMR3

    // 步骤4: 设置中断使能寄存器 (IMR)
    // 使能接收中断 (RIM=1) 和发送中断 (TIM=1)
    // IMR 地址 0x04:
    //   B6: ALIM (仲裁丢失)
    //   B5: EWIM (错误警告)
    //   B4: EPIM (被动错误)
    //   B3: RIM  (接收中断)
    //   B2: TIM  (发送中断)
    //   B1: BEIM (总线错误)
    //   B0: DOIM (溢出)
    CAN_WriteReg(0x04, 0x0C);       // IMR = 0x0C, 使能 RIM + TIM

    // 步骤5: 设置模式寄存器 (MR)
    // MR 地址 0x00:
    //   B2: RM (复位模式, 1=复位, 0=正常)
    //   B1: LOM (只听模式)
    //   B0: AFM (滤波模式, 1=单滤波, 0=双滤波)
    // 先进入复位模式配置, 再退出
    CAN_WriteReg(0x00, 0x05);       // MR = 0x05, RM=1, AFM=1 (单滤波)
    CAN_WriteReg(0x00, 0x01);       // MR = 0x01, RM=0, AFM=1 (正常模式, 单滤波)

    // 步骤6: 设置 CAN 引脚切换
    // P_SW1 地址 0xA2
    //   B5~B4: CAN_S[1:0]
    //   00 = P0.0(RX)/P0.1(TX) - 默认, TSSOP20 的 Pin19/Pin20
    // 默认就是 00, 无需修改
    // P_SW1 = (P_SW1 & ~0x30) | 0x00;

    // 步骤7: 使能 CAN 中断 (CANICR 寄存器)
    // CANICR 地址 0xF1:
    //   B2: CANIF (中断标志)
    //   B1: CANIE (中断使能)
    //   B0: PCANL (优先级低)
    CANICR = 0x02;                  // CANIE=1, 使能 CAN 中断

    // 步骤8: 使能 CAN 模块 (AUXR2 寄存器)
    // AUXR2 地址 0x97:
    //   B4: CANFD (CAN FD 模式)
    //   B3: CANSEL (CAN 选择, 0=CAN1, 1=CAN2)
    //   B2: CAN2EN (CAN2 使能)
    //   B1: CANEN (CAN1 使能)
    //   B0: LINEN (LIN 使能)
    AUXR2 |= 0x02;                  // CANEN=1, 使能 CAN1 模块
}

/* ======================== CAN 中断服务函数 ======================== */

// CAN 总线中断向量号为 28 (手册 "中断系统" 章节)
void CANBUS_Interrupt(void) interrupt 28
{
    unsigned char isr;
    unsigned int  rx_id;
    unsigned char i;

    // 读取中断状态寄存器 ISR (地址 0x03)
    // ISR 同时也是 IACK (中断应答寄存器)
    // 读 ISR 获取中断源, 写对应位清零
    isr = CAN_ReadReg(0x03);        // 读取 ISR

    // 检查接收中断 (RI, ISR.3)
    if (isr & 0x08)
    {
        // 清除接收中断标志: 对 ISR 的 RI 位写 1
        // 注意: 手册说明 "用户需要对 RI 写 1, 以减少接收信息计数器(RMC)值"
        CAN_WriteReg(0x03, 0x08);   // 写 ISR, RI=1 清零

        // 读取接收到的 CAN 帧
        rx_id = CAN_ReadMsg(g_rx_buf);

        // 检查是否是电池状态帧 (ID=0x100)
        if (rx_id == 0x100)
        {
            // 解析电池状态数据
            // 根据协议约定:
            // g_rx_buf[0~1]: 总电压 (高字节在前)
            g_battery.voltage = ((unsigned int)g_rx_buf[0] << 8) | g_rx_buf[1];

            // g_rx_buf[2~3]: 电流 (有符号, 高字节在前)
            g_battery.current = ((int)((signed char)g_rx_buf[2] << 8)) | g_rx_buf[3];

            // g_rx_buf[4]: SOC
            g_battery.soc = g_rx_buf[4];

            // g_rx_buf[5]: 温度 (有符号)
            g_battery.temperature = (signed char)g_rx_buf[5];

            // g_rx_buf[6]: 单体最高电压
            g_battery.cell_max = g_rx_buf[6];

            // g_rx_buf[7]: 单体最低电压
            g_battery.cell_min = g_rx_buf[7];

            // 设置帧接收标志, 通知主循环处理
            g_battery.frame_ok = 1;
        }
    }

    // 检查发送中断 (TI, ISR.2)
    if (isr & 0x04)
    {
        // 清除发送中断标志
        CAN_WriteReg(0x03, 0x04);   // 写 ISR, TI=1 清零
    }

    // 检查总线错误中断 (BEI, ISR.1)
    if (isr & 0x02)
    {
        // 清除总线错误中断标志
        CAN_WriteReg(0x03, 0x02);   // 写 ISR, BEI=1 清零

        // 可在此处读取 ECC 寄存器 (地址 0x18) 获取错误类型
        // unsigned char ecc = CAN_ReadReg(0x18);
    }

    // 检查溢出中断 (DOI, ISR.0)
    if (isr & 0x01)
    {
        // 清除溢出中断标志
        CAN_WriteReg(0x03, 0x01);   // 写 ISR, DOI=1 清零
    }
}

四、关键设计说明

4.1 CAN 波特率计算

根据手册 「CAN总线时钟寄存器0(BTR0)」「CAN总线时钟寄存器1(BTR1)」 章节:

CAN 波特率 = Fclk / ((1 + (TSG1+1) + (TSG2+1)) * (BRP+1) * 2)

本程序配置(24MHz → 250kbps):

  • BRP = 7 → 分频系数 = 7+1 = 8
  • TSG1 = 2 → 时间段1 = 2+1 = 3 Tq
  • TSG2 = 1 → 时间段2 = 1+1 = 2 Tq
  • 同步段 = 1 Tq
  • 总 Tq 数 = 1 + 3 + 2 = 6
  • 最终波特率 = 24,000,000 / (6 × 8 × 2) = 250,000 bps ✓

采样点位置 = (1 + 3) / 6 = 66.7%(在 CAN 推荐范围 60%~70% 内)

4.2 验收滤波配置

根据手册 「CAN总线验收过滤器」 章节,本程序使用 单滤波模式MR.0 = AFM = 1):

  • ACR0 = 0x20 → ID[10:3] = 0x20,即 ID 高 8 位为 0010 0000
  • ACR1 = 0x00 → ID[2:0] = 000,低 5 位屏蔽
  • AMR0 = 0x00 → ID 高 8 位必须完全匹配
  • AMR1 = 0x1F → ID 低 3 位必须匹配,低 5 位不关心

这样配置后,只有 ID = 0x100(二进制 001 0000 0000)的标准帧才能通过滤波进入接收 FIFO,大大减轻 CPU 负担。

4.3 中断处理

根据手册 「CAN中断/应答寄存器(ISR/IACK)」 章节:

  • 接收中断(RI)触发后,读取 ISR 获取中断源
  • 对 RI 位写 1 清零,同时减少 RMC(接收消息计数器)
  • 中断中只做数据拷贝和标志设置,具体解析在主循环中完成,避免中断过长

4.4 TSSOP20 引脚注意事项

根据手册 「不同封装形式管脚对应功能说明」 章节:

  • TSSOP20 的 Pin19 (P0.0)Pin20 (P0.1) 默认就是 CAN_RX/CAN_TX
  • 无需通过 P_SW1 切换引脚映射(默认 CAN_S[1:0] = 00
  • 如果使用第二组 CAN(CAN2),则对应 P0.2 (CAN2_RX)P0.3 (CAN2_TX),需设置 AUXR2 |= 0x04 使能 CAN2EN

五、调试与验证

5.1 使用 STC-ISP 的 CAN 助手

  1. 打开 AiCube-ISP 下载软件 → 选择 "CAN助手" 功能页
  2. 设置波特率为 250kbps
  3. 连接 USB 转 CAN 工具(如 STC-USB Link1 或 USB-CAN 适配器)
  4. 发送 ID=0x100 的标准数据帧,数据内容按协议填充
  5. 观察串口输出的电池状态信息

5.2 使用 CAN 波特率计算器

在 AiCube-ISP 中选择 "CAN波特率计算器",输入系统频率 24MHz 和目标波特率 250kbps,软件会自动推荐最优的 BRP/TSG1/TSG2 组合,并生成初始化代码。


六、扩展建议

  1. 增加发送查询帧:可定时发送 ID=0x101 的远程帧或数据帧,主动向 BMS 请求数据
  2. 增加错误处理:监控 SR 状态寄存器的 ES(错误状态)和 BS(Bus-Off)位,异常时自动恢复
  3. 增加 ADC 本地采样:利用 STC32CL8K64 的 12 位 ADC 采样本地电压/电流,与 CAN 收到的数据做对比
  4. 增加低功耗模式:电池系统通常需要低功耗,可在空闲时进入掉电模式,用 CAN 唤醒(需配置 I/O 中断)

如有任何具体问题,欢迎继续提问!


🔍 搜索 "STC32 CAN 电池状态 设计 程序"

📌 相关论坛讨论

  1. 求助,如何启用 STC32CL8K64-33A-TSSOP20的串口4 | 已知
  2. STC32CL8K64-33A-TSSOP20, 2组 CAN, 1组独立 Lin + 1组 USART-Lin, 串口 3组
  3. 【AI助手】@AiCube-ISP-V6.96Z中,【自动生成程序】+【查错高手】

⏱️ 查找用时 15.8s | 总回答用时 94.0s

QQ:3398500488
微信号:18106296592(小刘)
回复

使用道具 举报 送花

  • 打卡等级:常住居民III
  • 打卡总天数:149
  • 最近打卡:2026-07-21 08:54:57

39

主题

543

回帖

3327

积分

超级版主

积分
3327
发表于 昨天 08:54 | 显示全部楼层

STC8H驱动单颗WS2812(PWM模式)完整演示程序,并含详细程序注解
截图202607210854347228.jpg
电话:0513-55012946 QQ:2195591714  微信:  19952583740
回复

使用道具 举报 送花

  • 打卡等级:常住居民III
  • 打卡总天数:149
  • 最近打卡:2026-07-21 08:54:57

39

主题

543

回帖

3327

积分

超级版主

积分
3327
发表于 昨天 08:55 | 显示全部楼层

现在我已经掌握了足够的信息来编写完整的STC8H驱动WS2812的PWM模式演示程序。让我来编写这个程序。

根据手册和论坛资料,使用高级PWM(PWMA)的PWM模式1来产生WS2812所需的时序信号。WS2812的时序要求:

  • 逻辑"0":高电平约0.25μs ±150ns,低电平约1.0μs
  • 逻辑"1":高电平约1.0μs ±150ns,低电平约0.25μs
  • 码元周期:1.25μs ±150ns(约800KHz)
  • RESET:低电平 > 50μs

使用PWM模式驱动WS2812的核心思路:将PWM频率设置为800KHz(周期1.25μs),通过改变占空比来区分"0"和"1"。

下面给出完整程序:

/******************************************************************************
 * 文件名: STC8H_WS2812_PWM_Demo.c
 * 芯片  : STC8H系列 (如STC8H8K64U, STC8H3K64S4等)
 * 功能  : 使用高级PWM(PWMA)驱动单颗WS2812全彩LED
 * 硬件  : 
 *         - PWM输出引脚: P1.0 (PWMA通道1正极, 即PWM1P)
 *         - WS2812 DI 接 P1.0
 *         - VCC: 5V, GND: GND
 * 说明  : 
 *         本程序使用高级PWM的PWM模式1,将PWM频率设置为800KHz
 *         (周期1.25μs),通过改变占空比来模拟WS2812的"0"和"1"码。
 *       
 *         WS2812时序要求:
 *         逻辑"0": 高电平 0.25μs ±150ns, 低电平 1.0μs
 *         逻辑"1": 高电平 1.0μs ±150ns,  低电平 0.25μs
 *         码元周期: 1.25μs ±150ns (约800KHz)
 *         RESET: 低电平 > 50μs
 *       
 *         本程序使用系统主频24MHz,PWM预分频器设为0(不分频),
 *         自动重装载值ARR = 30 - 1 = 29 (30个时钟周期 = 1.25μs @24MHz)
 *         逻辑"0": CCR = 6  (高电平6个时钟 = 0.25μs)
 *         逻辑"1": CCR = 24 (高电平24个时钟 = 1.0μs)
 *       
 *         注意: 由于PWM输出在空闲时为低电平,发送完所有数据后,
 *               需要停止PWM并拉低引脚超过50μs以产生RESET信号。
 *               本程序采用发送完数据后关闭PWM主输出(MOE=0),
 *               此时引脚自动变为低电平(空闲状态),满足RESET条件。
 *             
 *         使用步骤:
 *         1. 将本程序编译后下载到STC8H单片机
 *         2. 将P1.0连接到WS2812的DI引脚
 *         3. 上电后WS2812会循环显示红、绿、蓝三色
 ******************************************************************************/

#include "stc8h.h"
#include "intrins.h"

//==============================================================================
// 用户配置宏
//==============================================================================
#define MAIN_Fosc    24000000UL    // 系统主频 24MHz

// WS2812时序参数 (基于24MHz主频,每个时钟周期约41.67ns)
#define WS_PERIOD    30            // PWM周期 = 30个时钟 = 1.25μs (800KHz)
#define WS_T0H       6             // 逻辑"0"高电平宽度 = 6个时钟 ≈ 0.25μs
#define WS_T1H       24            // 逻辑"1"高电平宽度 = 24个时钟 ≈ 1.0μs

//==============================================================================
// 颜色定义 (GRB格式 - WS2812数据格式为 Green-Red-Blue)
//==============================================================================
#define COLOR_RED      0x00, 0xFF, 0x00    // 红色 (G=0, R=255, B=0)
#define COLOR_GREEN    0xFF, 0x00, 0x00    // 绿色 (G=255, R=0, B=0)
#define COLOR_BLUE     0x00, 0x00, 0xFF    // 蓝色 (G=0, R=0, B=255)
#define COLOR_OFF      0x00, 0x00, 0x00    // 关闭

//==============================================================================
// 全局变量
//==============================================================================
// WS2812颜色数据缓冲区 (单颗灯珠 = 3字节 = 24位)
// 每个位用1个字节表示: 0x00=逻辑0, 0x01=逻辑1
unsigned char code g_WS2812_Data[24];  // 24位数据缓冲区

//==============================================================================
// 函数声明
//==============================================================================
void delay_ms(unsigned int ms);
void WS2812_SetColor(unsigned char green, unsigned char red, unsigned char blue);
void WS2812_Send(void);
void PWM_Init(void);
void PWM_SetDuty(unsigned int duty);

//==============================================================================
// 主函数
//==============================================================================
void main(void)
{
    unsigned char i;
  
    // 关闭所有中断
    EA = 0;
  
    // 使能访问XFR扩展寄存器
    P_SW2 |= 0x80;
  
    // 设置所有I/O口为准双向口
    P0M0 = 0x00; P0M1 = 0x00;
    P1M0 = 0x00; P1M1 = 0x00;
    P2M0 = 0x00; P2M1 = 0x00;
    P3M0 = 0x00; P3M1 = 0x00;
    P4M0 = 0x00; P4M1 = 0x00;
    P5M0 = 0x00; P5M1 = 0x00;
    P6M0 = 0x00; P6M1 = 0x00;
    P7M0 = 0x00; P7M1 = 0x00;
  
    // 初始化PWM
    PWM_Init();
  
    // 打开总中断
    EA = 1;
  
    while(1)
    {
        // 显示红色 (GRB格式: G=0, R=255, B=0)
        WS2812_SetColor(COLOR_RED);
        WS2812_Send();
        delay_ms(500);
      
        // 显示绿色 (GRB格式: G=255, R=0, B=0)
        WS2812_SetColor(COLOR_GREEN);
        WS2812_Send();
        delay_ms(500);
      
        // 显示蓝色 (GRB格式: G=0, R=0, B=255)
        WS2812_SetColor(COLOR_BLUE);
        WS2812_Send();
        delay_ms(500);
      
        // 关闭
        WS2812_SetColor(COLOR_OFF);
        WS2812_Send();
        delay_ms(500);
    }
}

//==============================================================================
// 函数: delay_ms
// 描述: 毫秒延时函数 (基于软件循环)
// 参数: ms - 延时毫秒数
// 返回: 无
//==============================================================================
void delay_ms(unsigned int ms)
{
    unsigned int i, j;
    for(i = 0; i < ms; i++)
    {
        // 24MHz主频下,约循环6000次为1ms
        for(j = 0; j < 6000; j++);
    }
}

//==============================================================================
// 函数: PWM_Init
// 描述: 初始化高级PWM (PWMA) 用于WS2812驱动
//       配置PWMA通道1 (PWM1P) 输出800KHz PWM信号
//       引脚: P1.0 (C1PS[1:0] = 00)
// 参数: 无
// 返回: 无
//==============================================================================
void PWM_Init(void)
{
    // 注意: P_SW2 |= 0x80; 已在main中执行
  
    // 步骤1: 关闭所有PWM通道输出 (先清CCER1)
    PWMA_CCER1 = 0x00;      // 关闭CC1E/CC1NE/CC2E/CC2NE
    PWMA_CCER2 = 0x00;      // 关闭CC3E/CC3NE/CC4E/CC4NE
  
    // 步骤2: 配置通道1为PWM输出模式
    // CCMR1: CC1S[1:0]=00 (输出模式), OC1M[2:0]=110 (PWM模式1)
    // PWM模式1: 向上计数时, CNT<CCR时输出高, 否则输出低
    PWMA_CCMR1 = 0x60;      // OC1M=110 (PWM模式1), OC1PE=0 (禁止预装载)
                            // CC1S=00 (输出模式)
  
    // 步骤3: 配置通道1输出使能和极性
    // CCER1: CC1E=1 (使能OC1输出), CC1P=0 (高电平有效)
    PWMA_CCER1 = 0x01;      // 仅使能CC1E, 不使能CC1NE(互补输出)
  
    // 步骤4: 配置PWM输出引脚
    // PWMA_PS: C1PS[1:0]=00 -> PWM1P输出到P1.0
    PWMA_PS = 0x00;         // 通道1选择P1.0作为输出
  
    // 步骤5: 使能PWM信号输出到端口
    // PWMA_ENO: ENO1P=1 (使能PWM1P输出)
    PWMA_ENO = 0x01;        // 仅使能PWM1P
  
    // 步骤6: 设置PWM周期 (ARR)
    // PWM频率 = SYSclk / ((PSCR+1) * (ARR+1))
    // 800KHz = 24MHz / (1 * 30) -> ARR = 29
    PWMA_ARRH = (unsigned char)((WS_PERIOD - 1) >> 8);
    PWMA_ARRL = (unsigned char)(WS_PERIOD - 1);
  
    // 步骤7: 设置预分频器 (不分频)
    PWMA_PSCRH = 0x00;
    PWMA_PSCRL = 0x00;
  
    // 步骤8: 设置初始占空比为0 (输出低电平)
    PWMA_CCR1H = 0x00;
    PWMA_CCR1L = 0x00;
  
    // 步骤9: 使能主输出 (MOE=1)
    // BKR: MOE=1 (主输出使能), 其他位默认
    PWMA_BKR = 0x80;        // MOE=1, 使能PWM输出
  
    // 步骤10: 启动PWM计数器
    // CR1: CEN=1 (使能计数器), ARPE=0 (ARR不预装载)
    PWMA_CR1 = 0x01;        // CEN=1, 边沿对齐模式, 向上计数
}

//==============================================================================
// 函数: PWM_SetDuty
// 描述: 设置PWMA通道1的占空比
// 参数: duty - 占空比值 (0 ~ ARR)
// 返回: 无
//==============================================================================
void PWM_SetDuty(unsigned int duty)
{
    PWMA_CCR1H = (unsigned char)(duty >> 8);
    PWMA_CCR1L = (unsigned char)(duty);
}

//==============================================================================
// 函数: WS2812_SetColor
// 描述: 设置WS2812的颜色数据到缓冲区 (GRB格式)
//       将3字节的GRB颜色数据转换为24位的位数据
//       每个位用1个字节表示: 0=逻辑0, 1=逻辑1
// 参数: green - 绿色分量 (0~255)
//       red   - 红色分量 (0~255)
//       blue  - 蓝色分量 (0~255)
// 返回: 无
// 说明: WS2812的数据格式为 GRB (先发Green, 再发Red, 最后发Blue)
//       高位先发 (MSB first)
//==============================================================================
void WS2812_SetColor(unsigned char green, unsigned char red, unsigned char blue)
{
    unsigned char i;
    unsigned char byteVal;
    unsigned char bitMask;
  
    // 处理Green字节 (第1个字节)
    byteVal = green;
    for(i = 0; i < 8; i++)
    {
        bitMask = 0x80 >> i;            // 从高位到低位
        if(byteVal & bitMask)
            g_WS2812_Data[i] = 1;       // 逻辑"1"
        else
            g_WS2812_Data[i] = 0;       // 逻辑"0"
    }
  
    // 处理Red字节 (第2个字节)
    byteVal = red;
    for(i = 0; i < 8; i++)
    {
        bitMask = 0x80 >> i;
        if(byteVal & bitMask)
            g_WS2812_Data[8 + i] = 1;
        else
            g_WS2812_Data[8 + i] = 0;
    }
  
    // 处理Blue字节 (第3个字节)
    byteVal = blue;
    for(i = 0; i < 8; i++)
    {
        bitMask = 0x80 >> i;
        if(byteVal & bitMask)
            g_WS2812_Data[16 + i] = 1;
        else
            g_WS2812_Data[16 + i] = 0;
    }
}

//==============================================================================
// 函数: WS2812_Send
// 描述: 通过PWM发送WS2812颜色数据
//       逐位发送24位数据,每位对应一个PWM周期
//       发送完毕后关闭PWM输出,产生RESET信号(>50μs低电平)
// 参数: 无
// 返回: 无
// 说明: 本函数采用"逐位发送"方式,每发送一位就更新一次PWM占空比。
//       由于PWM频率为800KHz(1.25μs/位),发送24位需要30μs,
//       加上RESET时间(>50μs),总耗时约80μs。
//       在此期间应关闭中断以防止干扰时序。
//==============================================================================
void WS2812_Send(void)
{
    unsigned char i;
    unsigned int delay_cnt;
  
    // 关闭总中断,防止中断干扰PWM占空比更新
    EA = 0;
  
    // 确保PWM正在运行且主输出已使能
    PWMA_CR1 |= 0x01;           // CEN=1, 启动计数器
    PWMA_BKR |= 0x80;           // MOE=1, 使能主输出
  
    // 逐位发送24位数据
    for(i = 0; i < 24; i++)
    {
        if(g_WS2812_Data[i])
        {
            // 逻辑"1": 高电平约1.0μs
            PWM_SetDuty(WS_T1H);
        }
        else
        {
            // 逻辑"0": 高电平约0.25μs
            PWM_SetDuty(WS_T0H);
        }
      
        // 等待当前PWM周期完成
        // 由于PWM周期为1.25μs,等待一个周期即可
        // 通过查询更新事件标志位来判断周期是否完成
        while(!(PWMA_SR1 & 0x01));   // 等待UIF=1 (更新事件)
        PWMA_SR1 &= ~0x01;           // 清除UIF标志
    }
  
    // 发送完毕,关闭PWM主输出,引脚变为低电平
    PWMA_BKR &= ~0x80;          // MOE=0, 禁止主输出
    // 此时P1.0引脚变为低电平(空闲状态)
  
    // 等待RESET时间 (>50μs)
    // 24MHz下,约1200个时钟周期 = 50μs
    // 这里延时约60μs以确保满足要求
    for(delay_cnt = 0; delay_cnt < 1500; delay_cnt++);
  
    // 重新打开总中断
    EA = 1;
}

程序说明

1. 工作原理

WS2812采用单线归零码通信,每个码元周期固定为1.25μs(800KHz),通过高电平宽度来区分"0"和"1":

  • 逻辑"0":高电平约0.25μs,低电平约1.0μs
  • 逻辑"1":高电平约1.0μs,低电平约0.25μs

本程序利用STC8H的高级PWM(PWMA)的PWM模式1来产生上述时序。将PWM频率设置为800KHz(周期1.25μs),通过改变比较值CCR来调整占空比:

  • 设置CCR=6时,高电平约0.25μs → 逻辑"0"
  • 设置CCR=24时,高电平约1.0μs → 逻辑"1"

2. 硬件连接

STC8H引脚 WS2812引脚
P1.0 (PWM1P) DI (数据输入)
VCC (5V) VCC
GND GND

注意:WS2812供电电压为5V,STC8H的I/O口可承受5V电平,P1.0设置为推挽输出即可直接驱动。如果使用3.3V供电的STC8H,建议在P1.0与WS2812的DI之间加一个电平转换电路(如74LVC1G125或简单的三极管电平转换)。

3. 关键参数计算(24MHz主频)

参数 计算
时钟周期 1/24MHz ≈41.67ns
PWM周期(ARR+1) 30个时钟 1.25μs
PWM频率 24MHz/30 800KHz
逻辑"0"高电平(CCR) 6个时钟 0.25μs
逻辑"1"高电平(CCR) 24个时钟 1.0μs

4. 数据发送流程

  1. 将24位颜色数据(GRB格式)存入缓冲区
  2. 逐位发送:每发送一位,更新一次PWM占空比
  3. 等待当前PWM周期完成(查询UIF标志)
  4. 发送完24位后,关闭PWM主输出(MOE=0),引脚自动变为低电平
  5. 延时>50μs产生RESET信号,WS2812锁存数据

5. 注意事项

  • 中断处理:发送数据期间关闭了总中断(EA=0),防止中断干扰PWM占空比更新时序
  • 引脚模式:PWM输出引脚由硬件强制设置为强推挽输出模式,无需软件配置
  • 数据格式:WS2812数据格式为GRB(先绿后红再蓝),高位先发
  • RESET信号:发送完24位数据后,必须产生>50μs的低电平信号,WS2812才会锁存数据

如果需要驱动多颗WS2812,只需将24位数据扩展为 N×24 位,依次发送即可(级联的WS2812会自动转发数据)。


🔍 搜索 "STC8H WS2812 PWM 驱动 程序"

📌 相关论坛讨论

  1. 【AI助手】@AiCube-ISP-V6.96Z中,【自动生成程序】+【查错高手】
  2. STC8H1K08T, DMA-SPI驱动一线制 WS2812的演示程序

⏱️ 查找用时 24.4s | 总回答用时 57.0s

电话:0513-55012946 QQ:2195591714  微信:  19952583740
回复

使用道具 举报 送花

  • 打卡等级:以坛为家I
  • 打卡总天数:343
  • 最近打卡:2026-07-21 09:29:54

16

主题

864

回帖

3994

积分

超级版主

积分
3994
发表于 昨天 09:29 | 显示全部楼层
基于STC8H2K08U自动写一个串口1、串口2超时中断与电脑收发测试的演示程序,并含详细程序注解
截图202607210934116988.jpg
回复

使用道具 举报 送花

  • 打卡等级:以坛为家I
  • 打卡总天数:343
  • 最近打卡:2026-07-21 09:29:54

16

主题

864

回帖

3994

积分

超级版主

积分
3994
发表于 昨天 09:30 | 显示全部楼层

好的,我已经获取了所有需要的信息。现在编写完整的演示程序。

根据手册信息:

  • STC8H2K08U 有2个串口(串口1和串口2),均支持接收超时中断
  • 串口1默认引脚:RxD=P3.0, TxD=P3.1(同时也是USB的D-/D+)
  • 串口2默认引脚:RxD2=P1.0, TxD2=P1.1
  • 串口1中断号4,串口2中断号8
  • 串口1超时中断入口地址与原串口1中断相同(中断号4),串口2同理(中断号8)
  • 超时中断标志位TOIF通过向CTOIF写1清除

以下是完整的演示程序:

/******************************************************************************
 * 文件名    : UART_Timeout_Test_STC8H2K08U.c
 * 编译器    : Keil C51
 * 实验平台  : STC8H2K08U 系列单片机
 * 系统时钟  : 11.0592MHz (内部IRC)
 * 串口1     : 波特率115200, 8N1, 引脚 P3.0(RxD) / P3.1(TxD)
 * 串口2     : 波特率115200, 8N1, 引脚 P1.0(RxD2) / P1.1(TxD2)
 * 功能说明  :
 *   1. 串口1和串口2均使能接收超时中断
 *   2. 超时时间设置为 10 个波特率位周期
 *   3. 电脑通过串口助手分别向串口1和串口2发送数据
 *   4. 每收到一帧数据后,若后续无新数据到达(超时触发),
 *      则将收到的数据原样回显(回环测试)
 *   5. 同时通过串口1打印调试信息到电脑
 *
 * 接线说明  :
 *   串口1: P3.0(RxD) -- USB转串口TXD
 *          P3.1(TxD) -- USB转串口RXD
 *   串口2: P1.0(RxD2) -- 另一路USB转串口TXD
 *          P1.1(TxD2) -- 另一路USB转串口RXD
 *
 * 注意事项  :
 *   1. 使用 STC-ISP 下载时,选择内部IRC时钟 11.0592MHz
 *   2. 串口1的P3.0/P3.1同时也是USB口,下载时注意不要冲突
 *   3. 超时中断与普通接收中断共用同一个中断入口
 ******************************************************************************/

#include "stc8h.h"          /* STC8H系列头文件 */
#include "intrins.h"        /* 内部函数库,如_nop_() */

/*============================================================================
 * 系统时钟与波特率参数定义
 *============================================================================*/
#define FOSC        11059200UL      /* 系统时钟频率 11.0592MHz */
#define BAUD_RATE   115200          /* 串口波特率 */

/* 定时器2重装值计算(1T模式,作为串口1和串口2的波特率发生器)
 * 公式: 重装值 = 65536 - FOSC / (4 * 波特率)
 * 加2是为了让Keil自动实现四舍五入 */
#define T2_RELOAD   (65536 - (FOSC / BAUD_RATE + 2) / 4)

/* 超时时间设置:以波特率位周期为单位
 * 例如设置为10,表示连续10个位周期无新数据即触发超时中断
 * 注意:TM不可设置为0,否则超时功能无效 */
#define TOUT_BITS   10              /* 超时位周期数 */

/*============================================================================
 * 全局变量定义
 *============================================================================*/

/* ---- 串口1 环形接收缓冲区 ---- */
#define U1_BUF_SIZE     64          /* 缓冲区大小(必须是2的幂) */
static unsigned char   u1_rx_buf[U1_BUF_SIZE];   /* 接收数据缓冲区 */
static unsigned char   u1_wptr;                   /* 写入指针 */
static unsigned char   u1_rptr;                   /* 读取指针 */
static bit            u1_busy;                    /* 发送忙标志 */
static bit            u1_timeout_flag;            /* 超时标志,主循环检测 */

/* ---- 串口2 环形接收缓冲区 ---- */
#define U2_BUF_SIZE     64          /* 缓冲区大小(必须是2的幂) */
static unsigned char   u2_rx_buf[U2_BUF_SIZE];   /* 接收数据缓冲区 */
static unsigned char   u2_wptr;                   /* 写入指针 */
static unsigned char   u2_rptr;                   /* 读取指针 */
static bit            u2_busy;                    /* 发送忙标志 */
static bit            u2_timeout_flag;            /* 超时标志,主循环检测 */

/*============================================================================
 * 函数声明
 *============================================================================*/
void System_Init(void);
void UART1_Init(void);
void UART2_Init(void);
void UART1_SendByte(unsigned char dat);
void UART2_SendByte(unsigned char dat);
void UART1_SendString(unsigned char *p);
void UART2_SendString(unsigned char *p);

/*============================================================================
 * 串口1中断服务函数
 * 中断号 4,入口地址 0023H
 * 该中断同时处理:发送完成(TI)、接收完成(RI)、接收超时(TOIF)
 *============================================================================*/
void UART1_Isr(void) interrupt 4
{
    unsigned char scon_val;
  
    /* 读取SCON寄存器,保存到局部变量,避免多次读取 */
    scon_val = SCON;
  
    /* ---- 处理发送完成中断 ---- */
    if (scon_val & 0x02)            /* TI = 1? */
    {
        SCON &= ~0x02;              /* 软件清零TI */
        u1_busy = 0;                /* 清除发送忙标志 */
    }
  
    /* ---- 处理接收完成中断 ---- */
    if (scon_val & 0x01)            /* RI = 1? */
    {
        SCON &= ~0x01;              /* 软件清零RI */
        u1_rx_buf[u1_wptr++] = SBUF;   /* 读取接收数据并存入缓冲区 */
        u1_wptr &= (U1_BUF_SIZE - 1);  /* 环形指针回绕 */
    }
  
    /* ---- 处理接收超时中断 ---- */
    /* 超时中断标志TOIF在UR1TOSR寄存器的bit0,
     * 该中断与RI/TI共用同一个中断入口(中断号4) */
    if (UR1TOSR & 0x01)             /* TOIF = 1? */
    {
        UR1TOSR |= 0x80;            /* 向CTOIF位(bit7)写1清除TOIF标志 */
        u1_timeout_flag = 1;        /* 设置超时标志,通知主循环处理 */
    }
}

/*============================================================================
 * 串口2中断服务函数
 * 中断号 8,入口地址 0043H
 *============================================================================*/
void UART2_Isr(void) interrupt 8
{
    unsigned char s2con_val;
  
    /* 读取S2CON寄存器 */
    s2con_val = S2CON;
  
    /* ---- 处理发送完成中断 ---- */
    if (s2con_val & 0x02)           /* S2TI = 1? */
    {
        S2CON &= ~0x02;             /* 软件清零S2TI */
        u2_busy = 0;                /* 清除发送忙标志 */
    }
  
    /* ---- 处理接收完成中断 ---- */
    if (s2con_val & 0x01)           /* S2RI = 1? */
    {
        S2CON &= ~0x01;             /* 软件清零S2RI */
        u2_rx_buf[u2_wptr++] = S2BUF;   /* 读取接收数据并存入缓冲区 */
        u2_wptr &= (U2_BUF_SIZE - 1);   /* 环形指针回绕 */
    }
  
    /* ---- 处理接收超时中断 ---- */
    if (UR2TOSR & 0x01)             /* TOIF = 1? */
    {
        UR2TOSR |= 0x80;            /* 向CTOIF位(bit7)写1清除TOIF标志 */
        u2_timeout_flag = 1;        /* 设置超时标志,通知主循环处理 */
    }
}

/*============================================================================
 * 系统初始化
 * 设置所有I/O口为准双向口模式
 *============================================================================*/
void System_Init(void)
{
    /* 使能访问XFR(扩展特殊功能寄存器)
     * 串口超时控制寄存器(UR1TOCR等)位于XFR区域,必须使能访问 */
    P_SW2 |= 0x80;
  
    /* 将所有I/O口设置为准双向口(弱上拉)模式
     * STC8H系列除P3.0/P3.1外,其余I/O口上电后为高阻输入状态
     * 使用前必须设置I/O口模式 */
    P0M0 = 0x00;    P0M1 = 0x00;
    P1M0 = 0x00;    P1M1 = 0x00;
    P2M0 = 0x00;    P2M1 = 0x00;
    P3M0 = 0x00;    P3M1 = 0x00;
    P4M0 = 0x00;    P4M1 = 0x00;
    P5M0 = 0x00;    P5M1 = 0x00;
}

/*============================================================================
 * 串口1初始化
 * 模式1(8位UART,可变波特率)
 * 使用定时器2作为波特率发生器(1T模式)
 * 使能接收超时中断
 *============================================================================*/
void UART1_Init(void)
{
    unsigned char t2l_val, t2h_val;
  
    /* ---- 配置串口1 ---- */
    /* SCON = 0x50:
     *   SM0/SM1 = 01  --> 模式1(8位UART,可变波特率)
     *   SM2 = 0        --> 非多机通信模式
     *   REN = 1        --> 允许接收
     *   TB8 = 0        --> 第9位数据(模式1不用)
     *   TI = 0, RI = 0 --> 清除中断标志 */
    SCON = 0x50;
  
    /* ---- 配置定时器2作为波特率发生器 ---- */
    /* 计算定时器2重装值 */
    t2l_val = (unsigned char)(T2_RELOAD);           /* 低8位 */
    t2h_val = (unsigned char)(T2_RELOAD >> 8);      /* 高8位 */
  
    T2L = t2l_val;          /* 设置定时器2低8位重装值 */
    T2H = t2h_val;          /* 设置定时器2高8位重装值 */
  
    /* AUXR = 0x15:
     *   T0x12 = 0  --> 定时器0 12T模式
     *   T1x12 = 0  --> 定时器1 12T模式
     *   UART_M0x6 = 0 --> 串口1模式0速度不加倍
     *   T2R = 1    --> 启动定时器2
     *   T2_C/T = 0 --> 定时器2为定时器模式(对系统时钟计数)
     *   T2x12 = 1  --> 定时器2 1T模式(高速模式)
     *   EXTRAM = 0 --> 禁止外部RAM访问
     *   S1ST2 = 1  --> 串口1选择定时器2作为波特率发生器 */
    AUXR = 0x15;
  
    /* ---- 配置串口1接收超时中断 ---- */
    /* 超时时间 = TM值 × 时钟源周期
     * SCALE = 0 时,时钟源为串口数据位率(波特率)
     * 即超时时间 = TOUT_BITS / 波特率(秒)
     * 例如:TOUT_BITS=10, 波特率115200, 超时≈86.8μs */
  
    /* 必须先设置UR1TOTL和UR1TOTH,最后设置UR1TOTE */
    UR1TOTL = (unsigned char)(TOUT_BITS);           /* TM[7:0] */
    UR1TOTH = (unsigned char)(TOUT_BITS >> 8);      /* TM[15:8] */
    UR1TOTE = (unsigned char)(TOUT_BITS >> 16);     /* TM[23:16] */
  
    /* UR1TOCR:
     *   ENTO = 1  --> 使能串口1接收超时功能
     *   ENTOI = 1 --> 使能串口1接收超时中断
     *   SCALE = 0 --> 超时计数时钟源为串口数据位率(波特率) */
    UR1TOCR = 0xC0;             /* ENTO=1, ENTOI=1, SCALE=0 */
  
    /* ---- 初始化全局变量 ---- */
    u1_wptr = 0;
    u1_rptr = 0;
    u1_busy = 0;
    u1_timeout_flag = 0;
  
    /* ---- 使能串口1中断 ---- */
    ES = 1;                     /* 使能串口1中断 */
}

/*============================================================================
 * 串口2初始化
 * 模式0(8位UART,可变波特率)
 * 使用定时器2作为波特率发生器(与串口1共用)
 * 使能接收超时中断
 *============================================================================*/
void UART2_Init(void)
{
    /* ---- 配置串口2 ---- */
    /* S2CON = 0x10:
     *   S2SM0 = 0   --> 模式0(8位UART,可变波特率)
     *   S2SM2 = 0   --> 非多机通信模式
     *   S2REN = 1   --> 允许接收
     *   S2TB8 = 0   --> 第9位数据(模式0不用)
     *   S2TI = 0, S2RI = 0 --> 清除中断标志 */
    S2CON = 0x10;
  
    /* 串口2与串口1共用定时器2作为波特率发生器
     * 定时器2已在UART1_Init()中配置完成,此处不再重复配置 */
  
    /* ---- 配置串口2接收超时中断 ---- */
    /* 超时时间与串口1相同 */
    UR2TOTL = (unsigned char)(TOUT_BITS);           /* TM[7:0] */
    UR2TOTH = (unsigned char)(TOUT_BITS >> 8);      /* TM[15:8] */
    UR2TOTE = (unsigned char)(TOUT_BITS >> 16);     /* TM[23:16] */
  
    /* UR2TOCR:
     *   ENTO = 1  --> 使能串口2接收超时功能
     *   ENTOI = 1 --> 使能串口2接收超时中断
     *   SCALE = 0 --> 超时计数时钟源为串口数据位率(波特率) */
    UR2TOCR = 0xC0;             /* ENTO=1, ENTOI=1, SCALE=0 */
  
    /* ---- 初始化全局变量 ---- */
    u2_wptr = 0;
    u2_rptr = 0;
    u2_busy = 0;
    u2_timeout_flag = 0;
  
    /* ---- 使能串口2中断 ---- */
    /* IE2寄存器:
     *   bit0 = 1 --> 使能串口2中断 (ES2) */
    IE2 |= 0x01;                /* 使能串口2中断 */
}

/*============================================================================
 * 串口1发送一个字节
 * 采用查询方式等待上一字节发送完成
 *============================================================================*/
void UART1_SendByte(unsigned char dat)
{
    while (u1_busy);            /* 等待上一字节发送完成 */
    u1_busy = 1;                /* 设置发送忙标志 */
    SBUF = dat;                 /* 写入SBUF,启动发送 */
}

/*============================================================================
 * 串口2发送一个字节
 *============================================================================*/
void UART2_SendByte(unsigned char dat)
{
    while (u2_busy);            /* 等待上一字节发送完成 */
    u2_busy = 1;                /* 设置发送忙标志 */
    S2BUF = dat;                /* 写入S2BUF,启动发送 */
}

/*============================================================================
 * 串口1发送字符串
 *============================================================================*/
void UART1_SendString(unsigned char *p)
{
    while (*p)                  /* 遍历字符串直到结束符'\0' */
    {
        UART1_SendByte(*p++);   /* 逐字节发送 */
    }
}

/*============================================================================
 * 串口2发送字符串
 *============================================================================*/
void UART2_SendString(unsigned char *p)
{
    while (*p)
    {
        UART2_SendByte(*p++);
    }
}

/*============================================================================
 * 主函数
 *============================================================================*/
void main(void)
{
    unsigned char dat;
  
    /* ---- 系统初始化 ---- */
    System_Init();              /* 初始化I/O口和XFR访问 */
  
    /* ---- 串口初始化 ---- */
    UART1_Init();               /* 初始化串口1(含超时中断) */
    UART2_Init();               /* 初始化串口2(含超时中断) */
  
    /* ---- 打开总中断 ---- */
    EA = 1;                     /* 使能全局中断 */
  
    /* ---- 发送启动提示信息(通过串口1输出到电脑) ---- */
    UART1_SendString("\r\n");
    UART1_SendString("========================================\r\n");
    UART1_SendString(" STC8H2K08U 串口1+串口2 超时中断回环测试\r\n");
    UART1_SendString(" 系统时钟: 11.0592MHz\r\n");
    UART1_SendString(" 波特率: 115200\r\n");
    UART1_SendString(" 超时位周期数: ");
    UART1_SendByte(TOUT_BITS / 10 + '0');       /* 十位 */
    UART1_SendByte(TOUT_BITS % 10 + '0');       /* 个位 */
    UART1_SendString("\r\n");
    UART1_SendString(" 串口1引脚: P3.0(RxD) / P3.1(TxD)\r\n");
    UART1_SendString(" 串口2引脚: P1.0(RxD2) / P1.1(TxD2)\r\n");
    UART1_SendString("========================================\r\n");
    UART1_SendString(" 使用方法:\r\n");
    UART1_SendString("  向串口1发送数据 -> 超时后自动回显\r\n");
    UART1_SendString("  向串口2发送数据 -> 超时后自动回显\r\n");
    UART1_SendString("  串口1收到的数据也会通过串口1打印\r\n");
    UART1_SendString("========================================\r\n");
    UART1_SendString("\r\n");
  
    /* ---- 主循环 ---- */
    while (1)
    {
        /* ======== 处理串口1接收数据 ======== */
        /* 检查是否有超时标志(表示一帧数据接收完成) */
        if (u1_timeout_flag)
        {
            u1_timeout_flag = 0;    /* 清除超时标志 */
          
            /* 检查缓冲区中是否有数据待处理 */
            if (u1_rptr != u1_wptr)
            {
                /* 通过串口1打印接收到的数据帧信息 */
                UART1_SendString("[UART1] 收到数据帧: ");
              
                /* 将缓冲区中的所有数据通过串口1回显 */
                while (u1_rptr != u1_wptr)
                {
                    dat = u1_rx_buf[u1_rptr++];         /* 从缓冲区读取一个字节 */
                    u1_rptr &= (U1_BUF_SIZE - 1);       /* 环形指针回绕 */
                  
                    UART1_SendByte(dat);                /* 串口1回显(到电脑) */
                    UART2_SendByte(dat);                /* 串口2也发一份(可选) */
                }
              
                UART1_SendString(" [OK]\r\n");
            }
        }
      
        /* ======== 处理串口2接收数据 ======== */
        if (u2_timeout_flag)
        {
            u2_timeout_flag = 0;    /* 清除超时标志 */
          
            if (u2_rptr != u2_wptr)
            {
                /* 通过串口1打印串口2的接收信息 */
                UART1_SendString("[UART2] 收到数据帧: ");
              
                /* 将串口2收到的数据回显到串口2 */
                while (u2_rptr != u2_wptr)
                {
                    dat = u2_rx_buf[u2_rptr++];         /* 从缓冲区读取一个字节 */
                    u2_rptr &= (U2_BUF_SIZE - 1);       /* 环形指针回绕 */
                  
                    UART2_SendByte(dat);                /* 串口2回显 */
                }
              
                UART1_SendString(" [OK]\r\n");
            }
        }
    }
}

程序说明

1. 硬件连接

串口 引脚 连接
串口1 P3.0 (RxD) / P3.1 (TxD) 接 USB转串口工具(如CH340)
串口2 P1.0 (RxD2) / P1.1 (TxD2) 接另一路 USB转串口工具

2. 超时中断工作原理

根据 STC8H手册"串口通信" 章节:

  • 超时计数器:串口每接收完一个字节,内部超时计数器自动复位并重新开始计数
  • 时钟源选择SCALE=0 时以波特率位周期为时钟;SCALE=1 时以系统时钟为时钟
  • 超时触发条件:当接收空闲且计数达到 TM[23:0] 设定值时,TOIF 置1
  • 中断入口:超时中断与普通串口中断共用同一个入口(串口1中断号4,串口2中断号8)
  • 标志清除:向 CTOIF 位(bit7)写1来清除 TOIF 标志

3. 测试方法

  1. STC-ISP 下载软件 将程序下载到 STC8H2K08U,选择内部IRC时钟 11.0592MHz
  2. 打开两个串口助手(如 SSCOM),分别连接串口1和串口2,波特率均设为 115200
  3. 向串口1发送任意数据(如 "Hello UART1!"),程序会在超时后自动回显
  4. 向串口2发送任意数据(如 "Hello UART2!"),同样会超时后自动回显
  5. 串口1上会同时打印接收状态信息,方便观察

4. 关键寄存器配置

寄存器 地址 说明
UR1TOCR FD70H 0xC0 ENTO=1, ENTOI=1, SCALE=0
UR1TOTL FD73H TOUT_BITS 超时时间低8位
UR1TOTH FD72H TOUT_BITS>>8 超时时间中8位
UR1TOTE FD88H TOUT_BITS>>16 超时时间高8位
UR2TOCR FD74H 0xC0 同上,串口2
UR2TOTL FD77H TOUT_BITS
UR2TOTH FD76H TOUT_BITS>>8
UR2TOTE FD89H TOUT_BITS>>16

注意:设置超时长度寄存器时,必须先设置 URxTOTL 和 URxTOTH,最后设置 URxTOTE,这是手册明确要求的顺序。


🔍 搜索 "基于 STC8H2K08U自动写一个串口1、串口2超时中断与电脑收发测试的演示程序 并含详细程序注解"

📌 相关论坛讨论

  1. 【AI助手】@AiCube-ISP-V6.96Z中,【自动生成程序】+【查错高手】
  2. 使用DMA接收串口数据时如何判断接收超时
  3. 10-串口1中断模式与电脑收发测试,STC8H8K64U实验箱演示程序, STC8H2K32U, STC8H2K12U

⏱️ 查找用时 15.4s | 总回答用时 54.5s

回复

使用道具 举报 送花

  • 打卡等级:常住居民III
  • 打卡总天数:102
  • 最近打卡:2026-07-21 09:30:57
已绑定手机

20

主题

48

回帖

594

积分

版主

积分
594
发表于 昨天 09:30 | 显示全部楼层

帮STC32G8K64-33A-LQFP48自动写一个完整的演示程序,
用于摩托车车灯和转向灯,并含详细程序注解
3313653e-7f5c-48a6-be1e-6f11c2ab778f.png
Tel: 0513-55012977     18051419563(微信同号)
回复

使用道具 举报 送花

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

GMT+8, 2026-7-22 09:19 , Processed in 0.103212 second(s), 98 queries .

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表