STM32中的中断机制与应用

目录

一.中断是什么

在 STM32F103(Cortex-M3)里,"中断 "可以理解为:当外设/内核发生某个事件时,硬件打断CPU当前执行流程,强制CPU去执行一段预先登记好的处理函数(ISR/IRQHandler) ,处理完再返回继续原来的程序。它的目的不是"更快",而是让系统对外部事件更及时、更可预测,同时避免主循环一直轮询浪费CPU。

二.中断由哪些部分组成


(1)事件源(谁触发)

外设事件:如 GPIO 边沿(EXTI)、定时器溢出/比较(TIM)、串口收到字节(USART RXNE)、DMA传输完成等

内核异常:如 SysTick、HardFault 等(这些也属于异常/中断体系的一部分)
(2)标志位与使能位(触发条件 = Flag + Enable)

几乎所有外设中断都满足这个规律:1.外设先置位 状态标志 Flag(例如:TIM_SR.UIF、USART_SR.RXNE、EXTI_PR某位);2.同时要求对应 中断使能位 Enable/IE 开启(例如:TIM_DIER.UIE、USART_CR1.RXNEIE、EXTI_IMR)。

只有 Flag=1 且 IE=1,才会向 NVIC 发出中断请求。
(3)NVIC(中断控制器:仲裁/屏蔽/优先级)

NVIC 负责:1.这个中断是否 Enable(允许响应);2.是否处于 Pending(挂起等待服务);3.优先级:谁能抢占谁、同级谁先处理
(4)向量表

CPU 进入中断时,会根据中断号去向量表里取出对应的 ISR 地址,然后跳转执行,如下图所示。

三.一次中断从发生到执行的完整流程

1. 外设事件发生(如串口收到字节)。

2.外设置位 Flag(RXNE=1)。

3.若外设 IE=1,则请求 NVIC → NVIC 把该 IRQ 标成 Pending。

4.NVIC 判断:是否使能?是否被屏蔽?优先级是否允许抢占?

5.CPU 进入异常:自动压栈保存现场(部分寄存器),从向量表取 ISR 地址。

6.执行 ISR:判因 + 清标志 + 快速处理(通常只放入缓冲/置事件)。

7.ISR 返回:硬件自动出栈恢复现场,程序继续从被打断处运行。

四."中断"和"轮询"

方式 你在做什么 优点 缺点
轮询(Polling) 主循环不断读标志位 写法直观 浪费CPU、响应不稳定、容易漏瞬时事件
中断(Interrupt) 事件发生时硬件通知CPU 响应更及时、CPU利用率高 需要管理优先级/嵌套/临界区;ISR写不好会乱

五. STM32F103 常见中断源举例

(1) EXTI(外部中断) :GPIO 上升沿/下降沿触发 → EXTI_PR 置位 → 进 EXTIx_IRQHandler

典型用途:按键、外部告警引脚、传感器中断脚
(2) TIM(定时器) :计数溢出/比较匹配 → TIM_SR.UIF/CCxIF 置位 → 进 TIMx_IRQHandler

典型用途:1ms节拍、PWM更新、输入捕获测频
(3) USART(串口) :收到数据 RXNE=1 → 进 USARTx_IRQHandler,读 DR 清 RXNE

典型用途:协议接收、日志、命令解析。

六. 中断工程模板(C程序)

1.EXTI0 按键(上升沿 + 消抖)

2.TIM2 1ms 中断(系统节拍 + 软定时器框架)

3.USART1 接收(RXNE 环形缓冲)

c 复制代码
/***************************************************************
 * File : bsp_irq_templates.c
 * MCU  : STM32F103CBT6 (Cortex-M3)
 * Goal : EXTI0 button + debounce
 *        TIM2 1ms tick + soft timers
 *        USART1 RX (ring buffer) OR (DMA+IDLE recommended)
 ***************************************************************/

#include "stm32f10x.h"                 // CMSIS/StdPeriph device header

/*********************** Compile-time options *******************/
// 0: USART1 RXNE interrupt + ring buffer
// 1: USART1 DMA RX + IDLE interrupt (recommended)
#define USART1_USE_DMA_IDLE   (1U)     // <-- 按需切换

#define SYS_TICK_HZ           (1000U)  // TIM2 1ms tick => 1000 Hz
#define DEBOUNCE_MS           (20U)    // 按键消抖时间窗(20ms 常用)

#define UART_BAUDRATE         (115200U)

#define UART_RING_SIZE        (256U)   // 环形缓冲大小(2^n 不是必须,但方便)
#define UART_DMA_BUF_SIZE     (256U)   // DMA staging buffer(建议 >= 256)

/*********************** Global time base ************************/
static volatile uint32_t g_ms = 0;     // 1ms 递增系统时间戳(TIM2 中断更新)

/*********************** Soft timer framework ********************/
// 软定时器:ISR里只做计数递减 + 置位 fired;回调在主循环执行(避免ISR耗时)
typedef void (*soft_cb_t)(void);

typedef struct
{
    volatile uint32_t remain;          // 剩余 ms
    uint32_t          period;          // 周期 ms(周期定时器使用)
    soft_cb_t         cb;              // 回调函数(在 main/poll 执行)
    uint8_t           periodic;        // 1=周期;0=一次
    volatile uint8_t  fired;           // 1=到期(由ISR置位)
} soft_timer_t;

#define SOFT_TIMER_NUM (8U)
static soft_timer_t g_tmr[SOFT_TIMER_NUM];  // 软定时器数组

/*********************** EXTI0 button state **********************/
static volatile uint32_t g_btn_last_ms = 0; // 上次有效按键时间
static volatile uint8_t  g_btn_event   = 0; // 1=产生一次"按下事件"(消抖后)

/*********************** USART1 ring buffer **********************/
static volatile uint8_t  g_rb[UART_RING_SIZE];   // 环形缓冲区
static volatile uint16_t g_rb_w = 0;             // 写指针
static volatile uint16_t g_rb_r = 0;             // 读指针
static volatile uint32_t g_uart_ore_cnt = 0;     // ORE 计数(用于诊断)

/*********************** USART1 DMA staging **********************/
#if (USART1_USE_DMA_IDLE == 1U)
static uint8_t           g_dma_buf[UART_DMA_BUF_SIZE]; // DMA 接收缓冲(循环)
static volatile uint16_t g_dma_old_pos = 0;            // 上次处理到的位置
#endif

/*********************** Utility: NVIC priority grouping *********/
// 使用 StdPeriph 的"PriorityGroup_4"等价:4 bits 抢占优先级 / 0 bits 子优先级:contentReference[oaicite:4]{index=4}
static void NVIC_Grouping_PriorityGroup4(void)
{
    uint32_t reg = 0;                                  // 临时变量
    reg  = SCB->AIRCR;                                 // 读 AIRCR
    reg &= ~SCB_AIRCR_PRIGROUP_Msk;                     // 清 PRIGROUP 位域
    reg  = (0x5FAUL << SCB_AIRCR_VECTKEY_Pos) |         // 写 VECTKEY 解锁
           (reg) |
           (3UL << SCB_AIRCR_PRIGROUP_Pos);             // PRIGROUP=3 => PriorityGroup_4
    SCB->AIRCR = reg;                                   // 写回 AIRCR
}

/*********************** Utility: clock helpers ******************/
// 注:计时器时钟:当 APB 预分频 != 1 时,TIMxCLK = 2 * PCLKx(STM32 典型规则):contentReference[oaicite:5]{index=5}
static uint32_t decode_ahb_presc(uint32_t hpre_bits)
{
    static const uint16_t tbl[16] = {1,1,1,1,1,1,1,1,2,4,8,16,64,128,256,512};
    return (uint32_t)tbl[(hpre_bits >> 4) & 0x0F];
}

static uint32_t decode_apb_presc(uint32_t ppre_bits)
{
    static const uint8_t tbl[8] = {1,1,1,1,2,4,8,16};
    return (uint32_t)tbl[(ppre_bits >> 8) & 0x07];
}

static uint32_t get_hclk_hz(void)
{
    uint32_t hpre = (RCC->CFGR & RCC_CFGR_HPRE);         // 取 HPRE
    uint32_t div  = decode_ahb_presc(hpre);             // 解码分频
    return SystemCoreClock / div;                       // HCLK = SYSCLK / AHB_div
}

static uint32_t get_pclk1_hz(void)
{
    uint32_t hclk = get_hclk_hz();                      // 获取 HCLK
    uint32_t ppre1 = (RCC->CFGR & RCC_CFGR_PPRE1);       // 取 PPRE1
    uint32_t div   = decode_apb_presc(ppre1);           // 解码分频
    return hclk / div;                                  // PCLK1 = HCLK / APB1_div
}

static uint32_t get_pclk2_hz(void)
{
    uint32_t hclk = get_hclk_hz();                      // 获取 HCLK
    uint32_t ppre2 = (RCC->CFGR & RCC_CFGR_PPRE2);       // 取 PPRE2
    uint32_t div   = decode_apb_presc(ppre2);           // 解码分频
    return hclk / div;                                  // PCLK2 = HCLK / APB2_div
}

static uint32_t get_tim2_clk_hz(void)
{
    uint32_t pclk1 = get_pclk1_hz();                    // TIM2 在 APB1
    uint32_t ppre1_bits = (RCC->CFGR & RCC_CFGR_PPRE1);  // 取 APB1 分频配置
    uint32_t apb1_div = decode_apb_presc(ppre1_bits);    // 计算 APB1 分频
    if (apb1_div == 1U)                                 // APB1 不分频
    {
        return pclk1;                                   // TIM2CLK = PCLK1
    }
    else
    {
        return (2U * pclk1);                            // TIM2CLK = 2 * PCLK1(APB分频≠1):contentReference[oaicite:6]{index=6}
    }
}

/*********************** Soft timer APIs **************************/
static void soft_timer_init(void)
{
    for (uint32_t i = 0; i < SOFT_TIMER_NUM; i++)        // 遍历所有定时器槽
    {
        g_tmr[i].remain   = 0;                           // 初始不计时
        g_tmr[i].period   = 0;                           // 周期=0
        g_tmr[i].cb       = 0;                           // 回调为空
        g_tmr[i].periodic = 0;                           // 非周期
        g_tmr[i].fired    = 0;                           // 未触发
    }
}

static void soft_timer_start(uint32_t id, uint32_t delay_ms, uint8_t periodic, soft_cb_t cb)
{
    if (id >= SOFT_TIMER_NUM) return;                    // 参数检查
    g_tmr[id].remain   = delay_ms;                       // 设置剩余时间
    g_tmr[id].period   = delay_ms;                       // 周期=delay(周期定时器用)
    g_tmr[id].cb       = cb;                             // 绑定回调
    g_tmr[id].periodic = periodic ? 1U : 0U;             // 记录是否周期
    g_tmr[id].fired    = 0;                              // 清触发标志
}

static void soft_timer_stop(uint32_t id)
{
    if (id >= SOFT_TIMER_NUM) return;                    // 参数检查
    g_tmr[id].remain = 0;                                // 停止计时
    g_tmr[id].fired  = 0;                                // 清触发标志
}

static void soft_timer_tick_isr(void)
{
    for (uint32_t i = 0; i < SOFT_TIMER_NUM; i++)         // ISR 内遍历(数量少时可接受)
    {
        if (g_tmr[i].remain > 0U)                         // 仍在计时
        {
            g_tmr[i].remain--;                            // 1ms 递减
            if (g_tmr[i].remain == 0U)                    // 到期
            {
                g_tmr[i].fired = 1U;                      // 置位触发(ISR只置位,不调用回调)
                if (g_tmr[i].periodic)                    // 周期定时器
                {
                    g_tmr[i].remain = g_tmr[i].period;    // 重新装载周期
                }
            }
        }
    }
}

static void soft_timer_poll(void)
{
    for (uint32_t i = 0; i < SOFT_TIMER_NUM; i++)         // 主循环轮询触发标志
    {
        if (g_tmr[i].fired)                               // 已到期
        {
            g_tmr[i].fired = 0U;                          // 先清标志(避免回调重入风险)
            if (g_tmr[i].cb)                              // 回调有效
            {
                g_tmr[i].cb();                            // 在主循环执行回调(避免ISR耗时)
            }
        }
    }
}

/*********************** EXTI0 (PA0) init *************************/
// 典型按键接法:PA0 下拉,按下连接到 VCC => 上升沿触发
static void EXTI0_Button_Init(void)
{
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;                    // 使能 GPIOA 时钟
    RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;                    // 使能 AFIO 时钟(EXTI 映射需要)

    // GPIOA PA0: 输入上拉/下拉(CNF=10, MODE=00)
    GPIOA->CRL &= ~(GPIO_CRL_MODE0 | GPIO_CRL_CNF0);       // 清 MODE0/CNF0
    GPIOA->CRL |=  (GPIO_CRL_CNF0_1);                      // CNF0=10 => Pull-up/Pull-down input
    GPIOA->ODR &= ~(GPIO_ODR_ODR0);                        // ODR0=0 => 选择下拉(Pull-down)

    // EXTI0 映射到 PA0:EXTICR1[3:0] = 0000(默认就是PA,但这里显式写一次)
    AFIO->EXTICR[0] &= ~(AFIO_EXTICR1_EXTI0);              // EXTI0 -> PA0

    EXTI->IMR  |= EXTI_IMR_MR0;                            // 取消屏蔽 EXTI0
    EXTI->EMR  &= ~EXTI_EMR_MR0;                           // 事件不需要(可选)
    EXTI->RTSR |= EXTI_RTSR_TR0;                           // 上升沿触发
    EXTI->FTSR &= ~EXTI_FTSR_TR0;                          // 关闭下降沿触发
    EXTI->PR    = EXTI_PR_PR0;                             // 写1清 pending(rc_w1):contentReference[oaicite:7]{index=7}

    NVIC_SetPriority(EXTI0_IRQn, 3U);                      // EXTI0 优先级(数值越小越高)
    NVIC_EnableIRQ(EXTI0_IRQn);                            // 使能 NVIC EXTI0
}

/*********************** TIM2 1ms tick init ************************/
static void TIM2_1ms_Init(void)
{
    uint32_t timclk = get_tim2_clk_hz();                   // 获取 TIM2 输入时钟(考虑APB倍频):contentReference[oaicite:8]{index=8}
    uint32_t psc    = (timclk / 1000000U) - 1U;            // 预分频到 1MHz(1us 计数)
    uint32_t arr    = (1000000U / SYS_TICK_HZ) - 1U;       // 1ms => 1000us => ARR=1000-1=999

    RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;                    // 使能 TIM2 时钟

    TIM2->CR1   = 0U;                                      // 先复位 CR1(关闭计数)
    TIM2->PSC   = (uint16_t)psc;                           // 设置 PSC(分频)
    TIM2->ARR   = (uint16_t)arr;                           // 设置 ARR(自动重装)
    TIM2->EGR   = TIM_EGR_UG;                              // 产生更新事件,把 PSC/ARR 装载生效
    TIM2->SR    = 0U;                                      // 清 SR(避免上电残留 UIF)
    TIM2->DIER |= TIM_DIER_UIE;                            // 开启更新中断 UIE

    NVIC_SetPriority(TIM2_IRQn, 1U);                       // TIM2 优先级最高(示例=1)
    NVIC_EnableIRQ(TIM2_IRQn);                             // 使能 TIM2 中断

    TIM2->CR1  |= TIM_CR1_CEN;                             // 启动计数器
}

/*********************** USART1 BRR helper ************************/
// Oversampling by 16: BRR = Mantissa<<4 | Fraction (Fraction on /16)
static uint16_t usart_brr_from_pclk(uint32_t pclk_hz, uint32_t baud)
{
    uint32_t usartdiv_x16 = (pclk_hz + (baud / 2U)) / baud; // 近似:USARTDIV*16 = PCLK/baud(做简单四舍五入)
    uint32_t mantissa     = usartdiv_x16 / 16U;             // 取整数部分
    uint32_t fraction     = usartdiv_x16 % 16U;             // 取小数部分(0..15)
    return (uint16_t)((mantissa << 4) | (fraction & 0x0FU)); // 拼 BRR
}

/*********************** USART1 init (common) *********************/
static void USART1_Common_Init(void)
{
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;                     // GPIOA 时钟
    RCC->APB2ENR |= RCC_APB2ENR_USART1EN;                   // USART1 时钟

    // PA9  (TX): 复用推挽输出 50MHz => MODE=11, CNF=10
    GPIOA->CRH &= ~(GPIO_CRH_MODE9 | GPIO_CRH_CNF9);        // 清 PA9 配置
    GPIOA->CRH |=  (GPIO_CRH_MODE9);                        // MODE9=11 => 50MHz
    GPIOA->CRH |=  (GPIO_CRH_CNF9_1);                       // CNF9=10 => AF push-pull

    // PA10 (RX): 浮空输入 => MODE=00, CNF=01
    GPIOA->CRH &= ~(GPIO_CRH_MODE10 | GPIO_CRH_CNF10);      // 清 PA10 配置
    GPIOA->CRH |=  (GPIO_CRH_CNF10_0);                      // CNF10=01 => floating input

    USART1->CR1 = 0U;                                       // 先关 CR1
    USART1->CR2 = 0U;                                       // 1 stop bit
    USART1->CR3 = 0U;                                       // 默认无硬件流控

    USART1->BRR = usart_brr_from_pclk(get_pclk2_hz(), UART_BAUDRATE); // 配波特率(USART1 在 APB2)

    USART1->CR1 |= USART_CR1_TE;                            // 使能发送
    USART1->CR1 |= USART_CR1_RE;                            // 使能接收
    USART1->CR1 |= USART_CR1_UE;                            // 使能 USART
}

/*********************** USART1 RXNE + ring buffer ****************/
static void USART1_RXNE_Ring_Init(void)
{
    USART1_Common_Init();                                   // 公共初始化

    USART1->CR1 |= USART_CR1_RXNEIE;                        // 开 RXNE 中断(收到字节即进中断)

    NVIC_SetPriority(USART1_IRQn, 2U);                      // USART1 优先级(示例=2)
    NVIC_EnableIRQ(USART1_IRQn);                            // 使能 USART1 中断
}

static inline void rb_put(uint8_t b)
{
    uint16_t next = (uint16_t)((g_rb_w + 1U) % UART_RING_SIZE); // 计算下一个写位置
    if (next != g_rb_r)                                       // 非满(留一个空位区分满/空)
    {
        g_rb[g_rb_w] = b;                                     // 写入数据
        g_rb_w = next;                                        // 前移写指针
    }
    else
    {
        // 缓冲满:可选择丢弃/覆盖/计数,这里直接丢弃(可扩展溢出计数)
    }
}

static int rb_get(uint8_t *out)
{
    if (g_rb_r == g_rb_w) return 0;                            // 空
    *out = g_rb[g_rb_r];                                       // 读出
    g_rb_r = (uint16_t)((g_rb_r + 1U) % UART_RING_SIZE);        // 前移读指针
    return 1;                                                  // 成功
}

/*********************** USART1 DMA+IDLE (recommended) ************/
#if (USART1_USE_DMA_IDLE == 1U)

static void USART1_DMA_IDLE_Init(void)
{
    USART1_Common_Init();                                      // 公共初始化

    RCC->AHBENR |= RCC_AHBENR_DMA1EN;                           // 使能 DMA1 时钟

    DMA1_Channel5->CCR &= ~DMA_CCR1_EN;                         // 先关 DMA 通道5(USART1_RX)
    DMA1_Channel5->CPAR  = (uint32_t)&USART1->DR;               // 外设地址 = USART1->DR
    DMA1_Channel5->CMAR  = (uint32_t)g_dma_buf;                 // 内存地址 = staging buffer
    DMA1_Channel5->CNDTR = UART_DMA_BUF_SIZE;                   // 传输计数 = buffer size

    // 配 DMA:外设到内存、8bit、内存自增、循环模式、较高优先级
    DMA1_Channel5->CCR   = 0U;                                  // 清 CCR
    DMA1_Channel5->CCR  |= DMA_CCR1_MINC;                       // 内存地址自增
    DMA1_Channel5->CCR  |= DMA_CCR1_CIRC;                       // 循环模式(持续接收)
    DMA1_Channel5->CCR  |= DMA_CCR1_PL_1;                       // PL=10(高优先级)
    // DIR=0: 外设->内存;PSIZE=00/MSIZE=00: 8bit(默认0)

    USART1->CR3 |= USART_CR3_DMAR;                              // USART1 使能 DMA 接收请求
    USART1->CR1 |= USART_CR1_IDLEIE;                            // 开 IDLE 中断(帧间空闲触发)

    DMA1_Channel5->CCR |= DMA_CCR1_EN;                           // 使能 DMA 通道

    NVIC_SetPriority(USART1_IRQn, 2U);                           // USART1 优先级
    NVIC_EnableIRQ(USART1_IRQn);                                 // 使能 USART1 中断

    g_dma_old_pos = 0U;                                          // 初始化处理指针
}

static void dma_process_new_data(uint16_t new_pos)
{
    // 把 DMA staging buffer 中的新数据段搬到环形缓冲(用于统一上层解析入口)
    while (g_dma_old_pos != new_pos)                              // 逐字节搬运(段通常不大)
    {
        rb_put(g_dma_buf[g_dma_old_pos]);                         // 放入环形缓冲
        g_dma_old_pos++;                                          // 前移
        if (g_dma_old_pos >= UART_DMA_BUF_SIZE)                   // 到末尾回绕
        {
            g_dma_old_pos = 0U;                                   // 回绕
        }
    }
}

#endif

/*********************** Interrupt Handlers ************************/
void TIM2_IRQHandler(void)
{
    if (TIM2->SR & TIM_SR_UIF)                                    // UIF=1 表示更新事件
    {
        TIM2->SR &= ~TIM_SR_UIF;                                  // 清 UIF(避免重复进中断)

        g_ms++;                                                   // 1ms 时间戳递增
        soft_timer_tick_isr();                                    // 软定时器 tick(ISR里只计数/置位)
    }
}

void EXTI0_IRQHandler(void)
{
    if (EXTI->PR & EXTI_PR_PR0)                                   // 判断是否 EXTI0 pending
    {
        uint32_t now = g_ms;                                      // 读取当前毫秒时间戳

        // 消抖:同一按键在 DEBOUNCE_MS 内重复触发则忽略
        if ((now - g_btn_last_ms) >= DEBOUNCE_MS)                 // 超过消抖窗
        {
            g_btn_last_ms = now;                                  // 更新最后触发时间
            g_btn_event   = 1U;                                   // 置位"按键事件"(主循环消费)
        }

        EXTI->PR = EXTI_PR_PR0;                                   // 写1清 pending(rc_w1):contentReference[oaicite:9]{index=9}
    }
}

void USART1_IRQHandler(void)
{
#if (USART1_USE_DMA_IDLE == 0U)
    uint32_t sr = USART1->SR;                                     // 先读 SR(对 ORE 等清除序列有要求)

    if (sr & USART_SR_RXNE)                                       // RXNE=1:收到字节
    {
        uint8_t b = (uint8_t)USART1->DR;                          // 读 DR:取走数据(同时清 RXNE)
        rb_put(b);                                                // 放入环形缓冲
    }
    else if (sr & USART_SR_ORE)                                   // ORE=1:溢出(说明服务不够快)
    {
        (void)USART1->DR;                                         // 读 DR:配合先读 SR 清 ORE
        g_uart_ore_cnt++;                                         // 计数用于诊断
    }

#else
    uint32_t sr = USART1->SR;                                     // 先读 SR
    if (sr & USART_SR_IDLE)                                       // IDLE=1:总线空闲(常用于"帧结束")
    {
        (void)USART1->DR;                                         // 读 DR:配合 SR->DR 清 IDLE

        uint16_t new_pos = (uint16_t)(UART_DMA_BUF_SIZE - DMA1_Channel5->CNDTR); // 计算 DMA 写入位置
        dma_process_new_data(new_pos);                            // 搬运新增数据到环形缓冲
    }

    if (sr & USART_SR_ORE)                                        // DMA 情况下仍可保留 ORE 诊断
    {
        (void)USART1->DR;                                         // SR->DR 清 ORE
        g_uart_ore_cnt++;                                         // 计数
    }
#endif
}

/*********************** Example application hooks ****************/
//按键事件处理(你可替换为自己的逻辑)
static void on_button_pressed(void)
{
    // 这里不要做耗时操作;建议仅置位状态/发消息
}

//软定时器回调(1Hz闪烁等)
static void on_timer0(void)
{
    // 示例:翻转 PC13(若你想用PC13,请自行初始化GPIOC)
}

/*********************** Public init ********************************/
void BSP_IRQ_Templates_Init(void)
{
    NVIC_Grouping_PriorityGroup4();                               // 只用抢占优先级(4bits/0bits)
    soft_timer_init();                                            // 初始化软定时器

    TIM2_1ms_Init();                                              // 启动 1ms 节拍(系统时间基准)
    EXTI0_Button_Init();                                          // 按键 EXTI0 + 消抖

#if (USART1_USE_DMA_IDLE == 0U)
    USART1_RXNE_Ring_Init();                                      // 串口 RXNE + 环形缓冲
#else
    USART1_DMA_IDLE_Init();                                       // 串口 DMA+IDLE(推荐)
#endif

    soft_timer_start(0U, 1000U, 1U, on_timer0);                   // 定时器0:1000ms 周期回调
}

/*********************** Example main loop *************************/
int main(void)
{
    SystemInit();                                                 // 启动文件一般会调用;这里写上更直观
    SystemCoreClockUpdate();                                      // 更新 SystemCoreClock(若工程未做)

    BSP_IRQ_Templates_Init();                                     // 初始化:EXTI0 + TIM2 + USART1

    while (1)
    {
        soft_timer_poll();                                        // 执行到期回调(在主循环,不在 ISR)

        if (g_btn_event)                                          // 有按键事件
        {
            g_btn_event = 0U;                                     // 清事件
            on_button_pressed();                                  // 处理按键
        }

        // 统一的"串口取数据"入口:两种模式最终都把数据放进环形缓冲
        uint8_t ch = 0;
        while (rb_get(&ch))                                       // 读取环形缓冲
        {
            // TODO: 在这里做协议解析(推荐:状态机/帧解析器)
            (void)ch;                                             // 占位避免未使用警告
        }
    }
}
相关推荐
爱潜水的小L2 小时前
自学嵌入式day44,51单片机
单片机·嵌入式硬件
v先v关v住v获v取2 小时前
ZY8600-25-50型掩护式液压支架设计支撑6张cad+设计说明书
科技·单片机·51单片机
宵时待雨2 小时前
STM32笔记归纳1:STM32的基本信息与引脚分布
笔记·stm32·嵌入式硬件
CS Beginner2 小时前
STM32F103ZET6中I2C、SPI和USART
单片机
阿华hhh2 小时前
单片机day1
c语言·单片机·嵌入式硬件
我还可以再学点2 小时前
C语言常见函数
c语言·开发语言
思茂信息2 小时前
CST电动汽车EMC仿真(三)——初探轴电压
运维·服务器·单片机·嵌入式硬件·cst·电磁仿真·天线仿真
Xx香菜2 小时前
单片机—2
单片机·嵌入式硬件
黎雁·泠崖3 小时前
吃透Java操作符高阶:位操作符+赋值操作符 全解析(Java&C区别+实战技巧+面试考点)
java·c语言·面试