下午好🌅🌅🌅
本答案参考通义千问
根据你的描述,你希望在 STM32F103C8 上实现以下功能:
- 串口每 100ms 发送一次 AD 电压值;
- 使用两个引脚(比如
PB0 和 PB1)控制 PA0 的 LED 状态:
- 任意一个引脚为低电平时,LED 常亮;
- 两个引脚都为高电平时,LED 闪烁;
- 要求 无阻塞串口发送,即不能用
delay() 延迟,否则会阻塞其他任务; - 已经尝试使用定时器和状态机,但无法实现“另一个引脚检测低电平”时的 LED 闪烁。
✅ 解决方案概述
要实现上述功能,需要做到以下几点:
- 使用 定时器中断 来触发串口发送和 LED 闪烁;
- 使用 外部中断或轮询方式 检测 PB0 和 PB1 的状态;
- 在主循环中使用 状态机 控制 LED 的行为;
- 避免使用
delay(),而是用 定时器标志位 或 时间戳 来判断时间间隔;
🧠 核心逻辑分析
1. 定时器配置
- 配置一个定时器(例如
TIM2)用于每隔 100ms 触发一次事件; - 在定时器中断中,进行以下操作:
- 串口发送 AD 数据;
- 更新 LED 状态(根据 PB0/PB1 的状态);
- 切换 LED 闪烁状态(如果处于闪烁模式);
2. 引脚状态检测
- 在主函数中不断读取
PB0 和 PB1 的状态; - 如果任意一个为低电平,则设置标志位
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、或者如何优化性能,欢迎继续提问!