Smj.小李 2026-03-20 16:10 采纳率: 0%
浏览 1

stm32如何无阻塞串口发送数据同时两个引脚检测低电平led常亮和闪烁的两种状态

大lao们, 目标是串口100ms发送一次ad电压值到电脑,再次期间可以使两个引脚任意一个为低电平来控制PA0led,常亮和闪烁,使用的是stm32f103c8

现在串口检测ad电压可以发送到电脑,引脚也可以使led常亮,但另一个引脚不可以低电平闪烁,我已经用定时器定标志位,状态机在主函数运行,led闪烁也没有用太大Delay

目前还没有学完freertos

void LED_Toggle2(void)
{
    GPIO_WriteBit(GPIOA, GPIO_Pin_0, 
        (BitAction)(1 - GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_0)));
}

想要解决这个问题,该怎么样做呢

  • 写回答

1条回答 默认 最新

  • 檀越@新空间 2026-03-20 16:11
    关注

    下午好🌅🌅🌅
    本答案参考通义千问

    根据你的描述,你希望在 STM32F103C8 上实现以下功能:

    1. 串口每 100ms 发送一次 AD 电压值;
    2. 使用两个引脚(比如 PB0PB1)控制 PA0 的 LED 状态:
      • 任意一个引脚为低电平时,LED 常亮
      • 两个引脚都为高电平时,LED 闪烁
    3. 要求 无阻塞串口发送,即不能用 delay() 延迟,否则会阻塞其他任务;
    4. 已经尝试使用定时器和状态机,但无法实现“另一个引脚检测低电平”时的 LED 闪烁。

    ✅ 解决方案概述

    要实现上述功能,需要做到以下几点:

    • 使用 定时器中断 来触发串口发送和 LED 闪烁;
    • 使用 外部中断或轮询方式 检测 PB0 和 PB1 的状态;
    • 在主循环中使用 状态机 控制 LED 的行为;
    • 避免使用 delay(),而是用 定时器标志位时间戳 来判断时间间隔;

    🧠 核心逻辑分析

    1. 定时器配置

    • 配置一个定时器(例如 TIM2)用于每隔 100ms 触发一次事件;
    • 在定时器中断中,进行以下操作:
      • 串口发送 AD 数据;
      • 更新 LED 状态(根据 PB0/PB1 的状态);
      • 切换 LED 闪烁状态(如果处于闪烁模式);

    2. 引脚状态检测

    • 在主函数中不断读取 PB0PB1 的状态;
    • 如果任意一个为低电平,则设置标志位 LED_MODE = MODE_CONSTANT
    • 如果两个都为高电平,则设置标志位 LED_MODE = MODE_BLINKING

    3. LED 控制逻辑

    • 使用一个全局变量 LED_MODE 来表示当前 LED 模式;
    • 使用一个计数器 blink_counter 来控制 LED 闪烁频率;
    • 在定时器中断中,根据 LED_MODE 切换 LED 状态;

    🔧 实现步骤与代码示例

    1. 定时器配置(以 TIM2 为例)

    // 定义定时器结构体
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    
    void TIM2_Init(void) {
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    
        TIM_TimeBaseStructure.TIM_Prescaler = 7199;     // 72MHz / (7200 + 1) = 10kHz
        TIM_TimeBaseStructure.TIM_Period = 9999;        // 10kHz * 100ms = 1000
        TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
        TIM_TimeBaseStructure.TIM_CLKDivider = TIM_CLKDivider_1;
    
        TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
        TIM_Cmd(TIM2, ENABLE);
    
        TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
        NVIC_EnableIRQ(TIM2_IRQn);
    }
    

    2. 定时器中断服务函数

    volatile uint8_t send_flag = 0;
    volatile uint8_t blink_mode = 0;
    volatile uint16_t blink_counter = 0;
    
    void TIM2_IRQHandler(void) {
        if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
            TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    
            // 串口发送 AD 数据(假设 AD 采集已经完成)
            send_flag = 1;
    
            // 检查 LED 模式
            if (blink_mode) {
                blink_counter++;
                if (blink_counter >= 500) {  // 500 个周期 = 5s(假设 10kHz)
                    blink_counter = 0;
                    LED_Toggle2();  // 切换 LED 状态
                }
            }
        }
    }
    

    3. 主函数中的状态机逻辑

    uint8_t LED_MODE = MODE_CONSTANT;  // 默认常亮
    uint8_t pb0_state = 1;
    uint8_t pb1_state = 1;
    
    int main(void) {
        // 初始化系统、GPIO、串口、定时器等
        GPIO_InitTypeDef GPIO_InitStruct;
        USART_InitTypeDef USART_InitStruct;
    
        // 初始化 PA0 为输出
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
        GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
        GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOA, &GPIO_InitStruct);
    
        // 初始化 PB0 和 PB1 为输入
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
        GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
        GPIO_Init(GPIOB, &GPIO_InitStruct);
    
        // 初始化串口
        USART_Init(USART1, &USART_InitStruct);
        USART_Cmd(USART1, ENABLE);
    
        // 初始化定时器
        TIM2_Init();
    
        while (1) {
            // 检测 PB0 和 PB1 的状态
            pb0_state = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0);
            pb1_state = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1);
    
            if (pb0_state == 0 || pb1_state == 0) {
                LED_MODE = MODE_CONSTANT;
            } else {
                LED_MODE = MODE_BLINKING;
            }
    
            // 处理串口发送
            if (send_flag) {
                send_flag = 0;
                SendADValue();  // 发送 AD 值到串口
            }
    
            // 根据 LED 模式控制 LED
            if (LED_MODE == MODE_CONSTANT) {
                GPIO_SetBits(GPIOA, GPIO_Pin_0);  // 常亮
            } else {
                // 由定时器中断处理闪烁
            }
    
            // 可选:延时(非阻塞)
            // delay_ms(1); // 不建议使用
        }
    }
    

    4. LED 切换函数(修改后)

    void LED_Toggle2(void) {
        GPIO_WriteBit(GPIOA, GPIO_Pin_0, 
            (BitAction)(1 - GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_0)));
    }
    

    ⚠️ 注意事项

    • 避免使用 delay():这会阻塞主循环,导致无法响应引脚变化;
    • 确保定时器中断优先级合理:防止与其他中断冲突;
    • LED 闪烁频率可根据需求调整:通过修改 blink_counter 的阈值;
    • 可以扩展更多引脚控制,只需在主循环中增加检测即可;

    ✅ 总结

    | 问题 | 解决方案 | |------|----------| | 串口阻塞 | 使用定时器中断发送数据 | | LED 闪烁不工作 | 使用定时器控制 LED 闪烁频率 | | 引脚状态检测 | 在主循环中读取引脚状态并更新 LED 模式 | | 无延迟控制 | 使用状态机和定时器标志位 |


    如果你还有其他问题,比如如何获取 AD 值、如何配置 USART、或者如何优化性能,欢迎继续提问!

    评论

报告相同问题?

问题事件

  • 创建了问题 3月20日