STC8H1K08单片机在Keil环境下各外设的配置及使用方法

STC8H1K08对应芯片管脚图:

  1. 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--);
    }
}
  1. 配置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处理一次的程序		
}
  1. 看门狗的配置及使用(可配置低功耗模式下是否停止计数):
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
}
  1. 硬件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);
}
  1. 模数转化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);
	}
}
  1. 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,不再发送
	}
}
  1. 低功耗进入及外部中断唤醒:
    当判断到条件要进入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 中断标志由硬件自动清除,无需软件操作
}
  1. 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;
		}
	}
}
相关推荐
hongmai6668883 小时前
聊聊ESP32-C3-WROOM-02-N8这颗8MB Flash的RISC-V模组
笔记·嵌入式硬件·物联网·智能路由器·risc-v
VALENIAN瓦伦尼安教学设备13 小时前
激光对中仪采购要点
数据库·嵌入式硬件·算法
芯岭技术13 小时前
OM6625A芯片使用PY32 link烧录程序流程操作介绍
嵌入式硬件·物联网
zlinear数据采集卡15 小时前
ZLinear DABM-D223 通信协议与软件开发全解析:从 USB CDC 到 DDS 波形输出
arm开发·嵌入式硬件·fpga开发·开源
小比特-combat16 小时前
多模式调光台灯
单片机·嵌入式硬件
2CM_Embed16 小时前
Altium Designer 中为什么一颗芯片会分成多个 Part?——以 C2000 MCU 为例
单片机·嵌入式硬件·altium designer·c2000
大爱编程♡18 小时前
STM32-USART串口
stm32·单片机·嵌入式硬件
hongmai66688818 小时前
耐高温小钢炮:ESP32-C3-MINI-1U-H4模组选型分享
笔记·单片机·嵌入式硬件·物联网·risc-v
qq_5739678219 小时前
STM32问题
stm32·单片机·嵌入式硬件