STC8H1K08对应芯片管脚图:

- 24MHz系统时钟下的1ms延时函数:
c
void delay_ms(unsigned int ms)//24MHz下的毫秒延时函数
{
unsigned int i;
unsigned int j;
for (i = 0; i < ms; i++)
{
// 内层循环实现 1ms 延时
// 24MHz 下 1ms = 24000 个时钟周期
// DJNZ 指令 = 4 时钟,循环体额外开销约 4 时钟
// 24000 / 8 = 3000 次
j = 3000;
while (j--);
}
}
- 配置1ms定时器中断(以Timer0为例):
c
/*
****************************************************************************
* 函数: Timer0_Init
* 描述: 初始化定时器T0,1ms中断
* 参数: 无
* 返回: 无
****************************************************************************/
void Timer0_Init(void) //1毫秒@24.000MHz
{
// 注意:TM0PS寄存器地址为FEA0H,属于XFR区域,需要先使能访问
// 但此处我们使用默认预分频值(TM0PS复位值为0,即不分频),
// 且使用1T模式,所以不需要修改TM0PS,也就不需要操作XFR。
// 如果后续需要修改TM0PS,则需要:P_SW2 |= 0x80;
// 设置定时器0为模式0(16位自动重装载)
// TMOD: T0_GATE=0, T0_C/T=0, T0_M1=0, T0_M0=0
TMOD &= ~0x0F; // 清除定时器0相关位
// TMOD |= 0x00; // 模式0,默认就是0,无需额外置位
// 设置定时器0为1T模式(AUXR.7 T0x12 = 1)
AUXR |= 0x80; // T0x12 = 1,1T模式
// 装载定时器初值(1ms)
TH0 = 0xa2;
TL0 = 0x40;
// 使能定时器0中断
ET0 = 1; // IE.1 = 1
// 启动定时器0
TR0 = 1; // TCON.4 = 1
EA=1;
}
int num;
/****************************************************************************
* 函数: Timer0_ISR
* 描述: 定时器T0中断服务函数
* 参数: 无
* 返回: 无
* 说明: 中断号1,保持简洁,仅置标志位
****************************************************************************/
void Timer0_ISR(void) interrupt 1
{
// 清除定时器0溢出标志(硬件自动清除,但软件清除更可靠)
TF0 = 0;
//在此处执行要1ms处理一次的程序
num++;
if(num>=1000) num=0;
//在此处执行要1ms处理一次的程序
}
- 看门狗的配置及使用(可配置低功耗模式下是否停止计数):
c
// 看门狗初始化(24MHz主频,超时约4.2秒)
void WDT_Init(void)
{
P_SW2 |= 0x80; // 使能访问XFR
// WDT_CONTR: EN_WDT=1, CLR_WDT=1, IDL_WDT=0, WDT_PS=111
WDT_CONTR = 0xB7; // 启动看门狗,空闲模式停止,分频256
}
// 喂狗
void WDT_Feed(void)
{
P_SW2 |= 0x80;
WDT_CONTR |= 0x10; // 置位CLR_WDT
}
- 硬件PWM的使用:
c
//=============================================================================
// 函数: PWM1_Init
// 描述: 初始化PWMA通道1的互补输出PWM1N,配置为1kHz PWM输出到P1.1
// 参数: 无
// 返回: 无
// 说明: 使用内部24MHz时钟,边沿对齐模式,PWM模式1
// PWM频率 = SYSclk / ((PSCR+1) * (ARR+1))
// = 24MHz / ((23+1) * (999+1))
// = 24MHz / (24 * 1000)
// = 1000 Hz = 1kHz
// 输出引脚: P1.1 (PWM1N)
// 通过设置CC1NP=1使PWM1N与PWM1P同相,占空比数值直观
//=============================================================================
void PWM1_Init(void)//初始化motor控制PWM输出脚
{
// 第一步:使能访问扩展寄存器(XFR)
P_SW2 |= 0x80;
// 第二步:选择PWM1通道输出引脚
// PWMA_PS 地址 FEB2H
// C1PS[1:0] 控制通道1输出脚选择
// 00 -> P1.0 (PWM1P), P1.1 (PWM1N)
// 01 -> P2.0 (PWM1P), P2.1 (PWM1N)
// 10 -> P6.0 (PWM1P), P6.1 (PWM1N)
// 使用默认 C1PS[1:0]=00,PWM1N -> P1.1
PWMA_PS = (PWMA_PS & ~0x03) | 0x00;
// 第三步:配置P1.1为推挽输出模式
// P1.1 推挽输出 (P1M1.1=0, P1M0.1=1)
P1M1 &= ~0x02;
P1M0 |= 0x02;
// 第四步:关闭通道1,以便配置CC1S
PWMA_CCER1 = 0x00; // 全部关闭,包括CC1E和CC1NE
// 第五步:配置捕获/比较模式寄存器1
// CC1S[1:0] = 00 (输出模式)
// OC1M[2:0] = 110 (PWM模式1: CNT<CCR1时OC1REF输出高,否则输出低)
// OC1PE = 1 (使能预装载)
PWMA_CCMR1 = 0x68; // 0110 1000
// 第六步:设置预分频器 PSCR
// PSCR = 23, 即 (PSCR+1) = 24 分频
// 计数器时钟 = 24MHz / 24 = 1MHz
PWMA_PSCRH = 0x00;
PWMA_PSCRL = 23;
// 第七步:设置自动重装载值 ARR
// ARR = 999, 即 (ARR+1) = 1000
// PWM频率 = 1MHz / 1000 = 1kHz
PWMA_ARRH = 0x03; // 999 = 0x03E7
PWMA_ARRL = 0xE7;
// 第八步:设置初始占空比为 50%
// CCR1 = (ARR+1) * 占空比 / 100 = 1000 * 50 / 100 = 500
PWMA_CCR1H = 0x01; // 500 = 0x01F4
PWMA_CCR1L = 0xF4;
// 第九步:配置捕获/比较使能寄存器1
// CC1NP = 1 (PWM1N低电平有效,使输出与PWM1P同相)
// CC1NE = 1 (使能PWM1N输出)
// CC1P = 0 (PWM1P高电平有效,此处不使用)
// CC1E = 0 (关闭PWM1P输出)
PWMA_CCER1 |= 0x44; // 0100 0100
// 位7: CC2NP=0
// 位6: CC2NE=0
// 位5: CC2P=0
// 位4: CC2E=0
// 位3: CC1NP=1 (PWM1N低电平有效)
// 位2: CC1NE=1 (使能PWM1N输出)
// 位1: CC1P=0
// 位0: CC1E=0 (关闭PWM1P)
// 第十步:使能PWM1N输出到引脚
// PWMA_ENO 地址 FEB1H
// ENO1N = 1 (使能PWM1N输出)
// ENO1P = 0 (禁止PWM1P输出)
PWMA_ENO |= 0x02; // 0000 0010, ENO1N=1
// 第十一步:配置刹车寄存器
// MOE = 1 (主输出使能)
// 不使用刹车功能
PWMA_BKR |= 0x80; // MOE = 1
// 第十二步:使能计数器 (CEN = 1)
// ARPE = 1 (自动重装载预装载使能)
// CMS[1:0] = 00 (边沿对齐模式)
// DIR = 0 (向上计数)
PWMA_CR1 |= 0x81; // ARPE=1, CEN=1
}
//=============================================================================
// 函数: PWM1_SetDuty
// 描述: 设置PWM通道1的占空比 (输出到P1.1)
// 参数: duty - 占空比百分比,范围 0~100
// 0 -> 0% (P1.1一直低电平)
// 100 -> 100% (P1.1一直高电平)
// 返回: 无
// 说明: 占空比 = CCR1 / (ARR+1) * 100%
// ARR+1 = 1000, 所以 CCR1 = duty * 10
// 由于CC1NP=1使PWM1N与PWM1P同相,duty越大P1.1高电平时间越长
//=============================================================================
void PWM1_SetDuty(unsigned char duty)
{
unsigned int ccr_value;
if(duty > 100) duty = 100;
// CCR1 = (ARR+1) * duty / 100 = 1000 * duty / 100 = duty * 10
ccr_value = (unsigned int)duty * 10;
// 注意:duty=100时,ccr_value=1000,此时CNT永远<CCR1
// OC1REF一直为高,PWM1N一直为低(因为CC1NP=1取反)
// 但您想要的是高电平,所以需要配合方案一修改极性
// 或者保持CC1NP=1,但将PWM1P输出使能(PWM1P与OC1REF同相)
PWMA_CCR1H = (unsigned char)(ccr_value >> 8);
PWMA_CCR1L = (unsigned char)(ccr_value);
}
- 模数转化ADC的使用(因为ADC的参考电压Vref脚与VCC电压相连,所以这里先通过ADC第15通道内部BandGap反推VCC电压,):
c
#define ADC_SPEED 15 /* ADC速度: 0~15, 对应 SYSclk/2/(n+1) */
/* 此处设为5 => ADC时钟 = 24M/2/6 = 2MHz */
/* ADC采样次数,用于软件均值滤波 */
#define ADC_AVG_CNT 8
unsigned int xdata Vcc_mV,batvol_mV,cur_mA;
/*
/******************************************************************************
* 函数名称 : ADC_Init
* 功能描述 : 初始化ADC模块(STC8H1K08,10位ADC)
* 使用24MHz内部IRC时钟
* 输入参数 : 无
* 返回参数 : 无
******************************************************************************/
void ADC_Init(void)
{
/* 1. 将ADC通道对应的I/O口设为高阻输入模式 */
/* P3.3(ADC11) - 电池分压 */
/* P3.4(ADC12) - 电流采样 */
P3M0 |= 0x18; /* P3.3和P3.4的M0位=1 */
P3M1 |= 0x18; /* P3.3和P3.4的M1位=1 => 高阻输入 */
/* 2. 使能访问XFR,配置ADC时序 */
P_SW2 |= 0x80;
/* ADCTIM: 建议设置为0x3F */
/* CSSETUP=0(1个时钟), CSHOLD=11(4个时钟), SMPDUTY=11111(32个时钟) */
ADCTIM = 0x3F;
/* ADCCFG: 10位右对齐,ADC时钟尽量慢 */
/* SPEED=15 => ADC时钟 = 24M/2/16 = 750KHz (仍略超500KHz,建议用更慢) */
/* 如果实际主频可调,建议用11.0592MHz,SPEED=15 => 11.0592M/2/16=345.6KHz */
ADCCFG = 0x20 | 0x0F; /* RESFMT=1(右对齐), SPEED=15 */
/* 打开ADC电源,选择通道0 */
ADC_CONTR = 0x80; /* ADC_POWER=1 */
/* 清除ADC标志 */
ADC_CONTR &= ~0x20;
/* 等待ADC电源稳定(约1ms) */
delay_ms(2);
P_SW2 &= ~0x80;
}
/******************************************************************************
* 函数名称 : ADC_Start
* 功能描述 : 启动指定通道的ADC转换
* 输入参数 : ch - ADC通道号 (0~15)
* 返回参数 : 无
******************************************************************************/
void ADC_Start(unsigned char ch)
{
ADC_CONTR = (ADC_CONTR & 0xF0) | ch; /* 设置通道 */
ADC_CONTR |= 0x40; /* 启动ADC转换 (ADC_START=1) */
}
/******************************************************************************
* 函数名称 : ADC_ReadAvg
* 功能描述 : 读取指定通道的ADC值,进行多次采样取平均
* 输入参数 : ch - ADC通道号 (0~15)
* 返回参数 : 10位ADC平均值 (0~1023)
******************************************************************************/
unsigned int ADC_ReadAvg(unsigned char ch)
{
unsigned long sum = 0;
unsigned char i;
for(i = 0; i < ADC_AVG_CNT; i++)
{
/* 启动转换 */
ADC_Start(ch);
/* 等待转换完成 */
while(!(ADC_CONTR & 0x20));
/* 清除标志 */
ADC_CONTR &= ~0x20;
/* 读取结果 (10位右对齐) */
sum += ((unsigned int)ADC_RES << 8) | ADC_RESL;
}
/* 返回平均值 */
return (unsigned int)(sum / ADC_AVG_CNT);
}
unsigned int BGV;
/******************************************************************************
* 函数名称 : Get_VCC
* 功能描述 : 通过ADC第15通道(内部BandGap)反推VCC电压
* 从芯片内部读取真实的BGV值
* 输入参数 : 无
* 返回参数 : VCC电压值,单位mV
******************************************************************************/
unsigned int Get_VCC(void)
{
unsigned int adc_bg;
unsigned long vcc_mv;
unsigned char idata *p;
/* 从芯片内部读取真实的BandGap电压值(单位mV) */
/* 存放在 idata 0xEF(高字节) ~ 0xF0(低字节) */
p = (unsigned char idata *)0xEF;
BGV = ((unsigned int)p[0] << 8) | p[1]; /* 高字节在前 */
/* 读取内部BandGap (ADC15) */
adc_bg = ADC_ReadAvg(15);
adc_bg_value=adc_bg;
if(adc_bg == 0)
{
return 5000; /* 防除零 */
}
/* 10位ADC算法:VCC(mV) = 1024 * BGV(mV) / adc_bg */
vcc_mv = (unsigned long)BGV*1024/adc_bg;
return (unsigned int)vcc_mv;
}
然后这里我开启了两个ADC通道,用于读取锂电池电压以及电机工作时的采样电流:
c
/******************************************************************************
* 函数名称 : Get_Battery_Voltage
* 功能描述 : 读取P3.3(ADC11)的电池分压值,换算为实际电池电压
* 分压电路: res_division
* 电池电压 = ADC输入电压 /res_division
* 输入参数 : vcc_mv - 当前VCC电压(mV),由Get_VCC获得
* 返回参数 : 电池电压,单位mV
******************************************************************************/
unsigned int Get_Battery_Voltage(unsigned int vcc_mv)
{
unsigned int adc_val;
unsigned long bat_mv;
/* 读取P3.3 (ADC11) */
adc_val = ADC_ReadAvg(11);
/* ADC输入电压 = VCC × adc_val / 1023 */
/* 电池电压 = ADC输入电压/res_division */
bat_mv = (unsigned long)vcc_mv * adc_val / 1023UL /res_division;
return (unsigned int)bat_mv;
}
unsigned int adc_cur;
/******************************************************************************
* 函数名称 : Get_Current
* 功能描述 : 读取P3.4(ADC12)的电流采样值,换算为实际电流
* 采样电阻: 10mΩ = 0.010Ω
* 电流(A) = 电压(V) / 电阻(Ω)
* 电流(mA) = 电压(mV) / 电阻(mΩ) × 1000
* 电流(mA) = 电压(uV) / 10
* 输入参数 : vcc_mv - 当前VCC电压(mV),由Get_VCC获得
* 返回参数 : 电流值,单位mA (保留整数)
******************************************************************************/
unsigned int Get_Current(unsigned int vcc_mv)
{
unsigned long voltage_mv; /* 采样电阻上的压降,单位uV */
unsigned long current_ma;
/* 读取P3.4 (ADC12) */
adc_cur = ADC_ReadAvg(12);
/* 采样电阻上的压降(uV) = VCC(mV) × 1000 × adc_val / 1023 */
voltage_mv = (unsigned long)vcc_mv * adc_cur / 1023UL;
//采样电阻10mΩ,除以0.01Ω等于乘以100
current_ma = voltage_mv * 100UL;
return (unsigned int)current_ma;
}
调用方法如下,把ADC_time放在ms定时器中累加计时,然后把ADC转换与读取函数放到main的while(1)循环里面,可以实现定时多久执行一次ADC转换的效果:
c
unsigned char ADC_time;
void ADC_work(unsigned int time,unsigned char tx_time)//ADC转换,形参分别为每次转换的间隔时间,以及串口发送数据的时间
{
//ADC_time++;//放在1ms定时器中
if(ADC_time>time)
{
ADC_time=0;
uart_time++;
/* 第1步:通过ADC15内部1.19V反推VCC */
Vcc_mV = Get_VCC();
/* 第2步:读取电池电压 */
batvol_mV = Get_Battery_Voltage(Vcc_mV);
/* 第3步:读取电流 */
cur_mA = Get_Current(Vcc_mV);
}
}
- uart收发通讯及printf串口打印函数的使用(uart使用轮询方式扫描):
c
/*
* 功能: 串口1使用 P1.7(TxD) / P1.6(RxD),定时器2做波特率发生器
* 24MHz 内部IRC,9600bps,纯轮询方式(无中断)
* 芯片: STC8H1K08
*
* 引脚说明:
* P1.7 = TxD_3 → 串口1的第3组切换引脚(TxD)
* P1.6 = RxD_3 → 串口1的第3组切换引脚(RxD)
* 通过 P_SW1 的 S1_S[1:0] = 10 切换
*
* 下载: 使用 STC-ISP 软件,选择内部IRC频率 24MHz
* 注意:P1.6/P1.7 上电后为高阻输入状态,需先设置I/O口模式
*/
#include "STC8H.H"
#include "intrins.h"
#define MAIN_Fosc 24000000UL /* 系统主频 24MHz */
/* 定时器2重装值:9600bps @24MHz,1T模式 */
/* 公式: 重装值 = 65536 - SYSclk / (4 × 波特率) */
/* = 65536 - 24000000 / (4 × 9600) */
/* = 65536 - 625 = 64911 = 0xFD8F */
#define UART1_BRT (65536 - (MAIN_Fosc / 4 / 9600))
/******************************************************************************
* 函数名称 : UART1_Init
* 功能描述 : 初始化串口1,使用定时器2做波特率发生器
* 模式1:8位数据,可变波特率,1位起始位,1位停止位
* 波特率:9600bps @24MHz
* 将串口1切换到 P1.6(RxD) / P1.7(TxD) [S1_S[1:0]=10]
* 纯轮询方式,不使能中断
* 输入参数 : 无
* 返回参数 : 无
******************************************************************************/
void UART1_Init(void)
{
/* ========== 1. 设置I/O口模式 ========== */
/* P1.7(TxD) 设为推挽输出 */
/* P1.6(RxD) 必须设为高阻输入,不能有内部上拉干扰 */
P1M1 |= 0x40; /* P1.6 M1=1 */
P1M0 &= ~0x40; /* P1.6 M0=0 → 高阻输入 */
P1M1 &= ~0x80; /* P1.7 M1=0 */
P1M0 |= 0x80; /* P1.7 M0=1 → 推挽输出 */
/* ========== 2. 切换串口1引脚到 P1.6/P1.7 ========== */
P_SW1 &= ~0xC0; /* 先清零 S1_S[1:0] */
P_SW1 |= 0x80; /* 设置 S1_S[1:0] = 10 */
/* ========== 3. 配置串口1 ========== */
SCON = 0x50; /* 模式1,允许接收 */
/* ========== 4. 定时器2做波特率发生器 ========== */
T2L = (unsigned char)(UART1_BRT);
T2H = (unsigned char)(UART1_BRT >> 8);
AUXR = 0x15; /* T2R=1, T2_C/T=0, T2x12=1, S1ST2=1 */
/* ========== 5. 初始化发送标志 ========== */
TI = 1;
}
/******************************************************************************
* 函数名称 : UART1_SendByte
* 功能描述 : 串口1发送一个字节(轮询方式)
* 输入参数 : dat - 要发送的数据
* 返回参数 : 无
******************************************************************************/
void UART1_SendByte(unsigned char dat)
{
while(TI == 0); /* 等待上次发送完成 */
TI = 0; /* 清除发送完成标志 */
SBUF = dat; /* 写入SBUF启动发送 */
}
/******************************************************************************
* 函数名称 : UART1_SendString
* 功能描述 : 串口1发送字符串
* 输入参数 : p - 字符串指针
* 返回参数 : 无
******************************************************************************/
void UART1_SendString(unsigned char *p)
{
while(*p != '\0')
{
UART1_SendByte(*p++);
}
}
/******************************************************************************
* 函数名称 : UART1_DataReady
* 功能描述 : 检查串口是否收到新数据
* 输入参数 : 无
* 返回参数 : 1 - 有数据可读,0 - 无数据
******************************************************************************/
unsigned char UART1_DataReady(void)
{
if(RI)
{
return 1;
}
return 0;
}
/******************************************************************************
* 函数名称 : UART1_ReceiveByte
* 功能描述 : 读取串口接收到的数据
* 注意:调用前请先通过 UART1_DataReady 确认有数据
* 输入参数 : 无
* 返回参数 : 接收到的数据
******************************************************************************/
unsigned char UART1_ReceiveByte(void)
{
unsigned char dat;
while(RI == 0); /* 等待接收完成 */
dat = SBUF; /* 读取接收数据 */
RI = 0; /* 清除接收完成标志 */
return dat;
}
/******************************************************************************
* 函数名称 : putchar
* 功能描述 : 重定向 putchar 到串口1,使 printf 可用
* 输入参数 : c - 要发送的字符
* 返回参数 : 发送的字符
******************************************************************************/
char putchar(char c)
{
SBUF = c;//SBUF对应串口1
while(TI == 0);
TI = 0;
return c;
}
调用方法的话RX接收是直接在main里面的 while循环判断是否有接收到数据,TX则是放在while循环判断,使用ms计数多久发送一次:
c
void sys_uart_RX(void)//对接受的蓝牙数据进行处理
{
unsigned char dat;
/* 轮询检查是否有数据收到 */
if(UART1_DataReady())
{
/* 读取收到的数据 */
dat = UART1_ReceiveByte();
/* 回显收到的字符 */
//UART1_SendByte(dat);
switch(dat)
{
case RX_bt_link_success: bt_link_flag=1 ;break;
case RX_bt_link_cut: bt_link_flag=0 ;break;
case RX_music_play: auto_shutdown_second=0,auto_shutdown_minute=0 ;break;
case RX_music_stop: ;break;
}
}
}
```c
void sys_uart_TX(void)//下发数据到蓝牙板,除按键操作那些,将不是单次扫描执行的程序放到此处来判断
{
if(display_percentage==100&&sys_mode==charging)//充电满电时
{
if(send_finish_flag_charge_finish==0)//如果还未发送满电数据
{
send_finish_flag_charge_finish=1;
sending_data=TX_charge_finish;//将数据修改为满电数据,发送一次后若不更改sending_data的值,就不会再次发送数据
send_ready_flag=1;//置1发送
}
}
if(display_percentage==5&&sys_mode==work)//工作低电量提醒
{
if(send_finish_flag_lowpower_prompt==0)//如果还未发送低电量提醒数据
{
send_finish_flag_lowpower_prompt=1;
sending_data=TX_lowpower_prompt;//将数据修改为低电量提醒数据,发送一次后若不更改sending_data的值,就不会再次发送数据
send_ready_flag=1;//置1发送
}
}
if(display_percentage==0&&rgb_mode==work&&voltage_percentage==0)//工作低电量关机
{//因为在startup模式下不把电压电量百分比转换给display_percentage,所以可能进入work模式后立马执行该程序,所以这里加入电压百电量百分比判断,避免出现这种情况导致异常低电量关机
if(send_finish_flag_lowpower_stop==0)//如果还未发送低电量关机数据
{
send_finish_flag_lowpower_stop=1;
sending_data=TX_lowpower_stop;//将数据修改为低电量关机数据,发送一次后若不更改sending_data的值,就不会再次发送数据
send_ready_flag=1;//置1发送
sys_mode=stop;
flow_rgb_count=10;//低电量关机时,赋值10颗灯,防止开机时候还没跑完10颗灯,就检测到低电量关机,然后没完整跑完10颗灯
rgb_mode=stoping;
}
}
if(send_ready_flag==1)//有数据等待发送
{
UART1_SendByte(sending_data);
send_ready_flag=0;//发送完成后置0,不再发送
}
}
- 低功耗进入及外部中断唤醒:
当判断到条件要进入STOP时,先将各种需要初始化,以及对外有进行输出的端口全部配置为高阻输入,然后再使能外部中断端口用于进入低功耗后的唤醒,唤醒后会先执行外部中断服务函数里的内容,然后继续从进行低功耗的程序地方往下执行,不会从头开始跑程序:
c
/*
* 进入 STOP 模式函数
* 调用前需确保 P3.6 和 P3.7 的 I/O 口模式已配置好
* 本函数会:
* 1. 使能 P3.6/P3.7 下降沿中断
* 2. 使能 P3.6/P3.7 掉电唤醒功能
* 3. 进入 STOP 模式
* 4. 唤醒后关闭 P3.6/P3.7 中断使能
*/
void EnterStopMode(void)
{
send_finish_flag_motor_run=0;
send_finish_flag_motor_standby=0;
send_finish_flag_charge_finish=0;
send_finish_flag_charge_exit=0;
send_finish_flag_lowpower_prompt=0;
send_finish_flag_lowpower_stop=0;
send_finish_flag_overload1=0;
send_finish_flag_overload2=0;
send_finish_flag_overload3=0;
send_finish_flag_stop_to_charge=0;
send_finish_flag_work_to_charge=0;
charge_led_high_num=0;//捕获高电平次数
charge_led_low_num=0;//捕获低电平次数
charge_error_check_time=0;//LED电平检测时间
key_sum=0;
charge_sum=0;
startup_time=0;
release_flag=0;
rgb_flow_time=0;//放ms定时器中,多少ms更新一次RGB显示
rgb_charge_blink_time=0;//充电时的RGB闪烁时间累积
flow_rgb_count=0;
auto_shutdown_second=0;//清除倒计时关机计数
auto_shutdown_minute=0;
error_flag=none;//清除报错标志位
send_ready_flag=0;
sending_data=0;
motor_error_check_time=0;
motor_error_num=0;
ADC_error_check_time=0;
ADC_error_num=0;
PWM1_SetDuty(0);//0~100
RGBpower_OFF;
lowpower_OFF;
P1M0 &= ~0x0c; P1M1 |= 0x0c; //配置旋转编码器两个旋转检测脚为高阻上拉输入
P1PU &= ~0x0c;
P5M0 &= ~0x10; P5M1 |= 0x10; //配置RGB信号输出脚为高阻下拉输入,实测不配置时进入STOP模式有mA级别电流损耗
P5PD &= ~0x10;
P1M0 &= ~0x80;
P1M1 |= 0x80;
P1PD &= ~0x80;//配置跟蓝牙通讯的uart的TX脚为高阻下拉输入,实测不配置时进入STOP模式有mA级别电流损耗
delay_ms(10);
// ===== 第一步:使能访问 XFR =====
P_SW2 |= 0x80;
// ===== 第二步:设置唤醒后时钟稳定等待时间 =====
IRCDB = 0x10;
// ===== 第三步:使能 INT2 和 INT3 中断 =====
// INT2 和 INT3 是固定下降沿触发,无需配置模式
INTCLKO |= 0x30; // 假设 INT2=bit4, INT3=bit5 使能位
// 具体位定义请查阅手册,也可能是:
// P3.6 作为 INT2 需要设置相应的寄存器
// ===== 第四步:清除可能残留的中断标志 =====
// INT2/INT3 标志位通常在 INTCLK0 或类似寄存器中
// 写1清0
// ===== 第五步:打开总中断 =====
EA = 1;
// ===== 第六步:进入 STOP 模式 =====
PCON = 0x02;
_nop_(); _nop_();
// ===== 第七步:唤醒后等待时钟稳定 =====
_nop_(); _nop_(); _nop_(); _nop_();
_nop_(); _nop_(); _nop_(); _nop_();
_nop_();
RGBpower_ON;
lowpower_ON;
P5M1 &= ~0x10;
P5M0 |= 0x10;
P5 &= ~0x10;//初始化P5.4为推挽输出模式,给RGB发送数据
P1M1 &= ~0x80; /* P1.7 M1=0 */
P1M0 |= 0x80; /* P1.7 M0=1 → 推挽输出 */
//初始化P1.7为推挽输出,蓝牙uart的TX脚
sys_mode=startup;//唤醒后切换为startup模式,去检测是按键唤醒还是充电唤醒还是误唤醒
rgb_mode=startup;//赋一个不判断执行的值,等按键长按被判断为开机后再赋值一个会被执行的值
delay_ms(100);
}
外部中断服务函数中没有需要执行的内容,则空着:
c
// 外部中断 INT2 服务函数(P3.6)
void INT2_Isr(void) interrupt 10 // INT2 中断号通常为 10
{
// INT2 中断标志由硬件自动清除,无需软件操作
// 这里只做唤醒后的标记
}
// 外部中断 INT3 服务函数(P3.7)
void INT3_Isr(void) interrupt 11 // INT3 中断号通常为 11
{
// INT3 中断标志由硬件自动清除,无需软件操作
}
- 24MHz系统时钟下,使用轮询的方式来实现WS2812 RGB灯的驱动:
c
/*
* STC8H1K08 + P5.4 驱动 WS2812 RGB LED
* 阻塞式发送,关中断保护
*
* 时钟:内部24MHz IRC
* 24MHz = 41.67ns/指令周期
*
* WS2812 时序:
* 0码: 高 7T(~292ns) + 低 22T(~917ns) = 29T(~1209ns)
* 1码: 高 22T(~917ns) + 低 7T(~292ns) = 29T(~1209ns)
* 复位: 低电平 ≥ 50μs
*/
#include "STC8H.h"
#define MAIN_Fosc 24000000UL
#define WS2812_GPIO P54
// WS2812 缓冲区:10颗 × 3字节(GRB)
unsigned char ws2812_buffer[30];
#define WS2812_NUM 10
unsigned char xdata flow_LED_Colors[WS2812_NUM][3] = {//流水灯的显示数据
{0x55, 0x55, 0x55}, // 灯0: 灰色
{0x88, 0x00, 0x88}, // 灯1: 紫色
{0x33, 0x00, 0x88}, // 灯2: 蓝紫色
{0x00, 0x00, 0x88}, // 灯3: 蓝色
{0x00, 0x88, 0x33}, // 灯4: 青绿色
{0x00, 0x88, 0x00}, // 灯5: 绿色
{0x33, 0x88, 0x00}, // 灯6: 黄绿色
{0x88, 0x88, 0x00}, // 灯7: 黄色
{0x88, 0x33, 0x00}, // 灯8: 橙色
{0x88, 0x00, 0x00} // 灯9: 红色
};
//==========================================================================
// 发送 WS2812 数据(阻塞式,关中断保护)
//==========================================================================
void WS2812_SendBuffer(void)
{
unsigned char i;
unsigned char j;
unsigned char data_byte;
// 关中断,防止时序被打乱
EA = 0;
// 发送 30 个字节(10颗 × 3字节)
for (i = 0; i < 30; i++)
{
data_byte = ws2812_buffer[i];
// 发送一个字节,MSB first
for (j = 0; j < 8; j++)
{
if (data_byte & 0x80) // 当前位为1
{
// ---- 发送1码:高22T + 低7T ----
WS2812_GPIO = 1; // 高电平开始
// 延时 22T(约917ns)
// 24MHz下,1T = 41.67ns
// WS2812_GPIO=1 本身消耗1T
// 需要再延时 21T
_nop_(); _nop_(); _nop_(); _nop_(); _nop_();
_nop_(); _nop_(); _nop_(); _nop_(); _nop_();
_nop_(); _nop_(); _nop_(); _nop_(); _nop_();
_nop_(); _nop_(); _nop_(); _nop_(); _nop_();
_nop_(); // 共21个_nop_
WS2812_GPIO = 0; // 低电平开始
// 延时 7T(约292ns)
// WS2812_GPIO=0 本身消耗1T
// 需要再延时 6T
_nop_(); _nop_(); _nop_(); _nop_(); _nop_();
_nop_(); // 共6个_nop_
}
else // 当前位为0
{
// ---- 发送0码:高7T + 低22T ----
WS2812_GPIO = 1; // 高电平开始
// 延时 7T
// WS2812_GPIO=1 本身消耗1T
// 需要再延时 6T
_nop_(); _nop_(); _nop_(); _nop_(); _nop_();
_nop_(); // 共6个_nop_
WS2812_GPIO = 0; // 低电平开始
// 延时 22T
// WS2812_GPIO=0 本身消耗1T
// 需要再延时 21T
_nop_(); _nop_(); _nop_(); _nop_(); _nop_();
_nop_(); _nop_(); _nop_(); _nop_(); _nop_();
_nop_(); _nop_(); _nop_(); _nop_(); _nop_();
_nop_(); _nop_(); _nop_(); _nop_(); _nop_();
_nop_(); // 共21个_nop_
}
data_byte <<= 1; // 下一个bit
}
}
// ---- 复位信号:低电平 ≥ 50μs ----
// 24MHz下,50μs ≈ 1200T
// 用循环延时
{
unsigned int reset_delay;
for (reset_delay = 0; reset_delay < 600; reset_delay++);
}
// 恢复中断
EA = 1;
}
//==========================================================================
// 设置某颗 WS2812 的颜色值(GRB 格式)
//==========================================================================
void WS2812_SetLED(unsigned char index, unsigned char r,
unsigned char g, unsigned char b)
{
if (index >= 10) return;
ws2812_buffer[index * 3 + 0] = g; // Green
ws2812_buffer[index * 3 + 1] = r; // Red
ws2812_buffer[index * 3 + 2] = b; // Blue
}
其调用方法如下(放在main函数中循环调用更新RGB显示):
c
void startup_display_RGB(unsigned int time)//开机流水灯
{
unsigned char i;//执行一次函数可以跑完其所有变量值的变量可以定义在函数内部,否则必须用全局变量,不然再次调用该函数,变量值会清0
if(rgb_flow_time>time)
{
rgb_flow_time=0;
if(flow_rgb_count<WS2812_NUM)
{
flow_rgb_count++;
}
// 设置前count颗灯为预设颜色,其余为全灭
for (i = 0; i < WS2812_NUM; i++)
{
if (i >= (WS2812_NUM-flow_rgb_count))
{
// 亮灯: 使用预设颜色
WS2812_SetLED(i,
flow_LED_Colors[i][0], // R
flow_LED_Colors[i][1], // G
flow_LED_Colors[i][2] // B
);
}
else
{
// 灭灯: RGB全0
WS2812_SetLED(i, 0, 0, 0);
}
}
// 刷新显示
WS2812_SendBuffer();
if(flow_rgb_count==WS2812_NUM)
{
//flow_flag=1;
delay_ms(500);//24MHz下的毫秒延时函数
rgb_mode=work;
}
}
}