电赛ti板例程10+8学习
2026/7/21 18:30:18 网站建设 项目流程

2026-07-19 电赛TI板例程10+8学习:从GPIO输入到定时器中断

一、今日目标

  • 跑通例程10(keyscan),理解GPIO输入、按键消抖、轮询检测
  • 跑通例程8(systick),理解定时器中断、非阻塞延时、软件分频
  • 建立"GPIO输出→输入→中断→PWM"的闭环认知
  • 记录从"会看代码"到"会改代码"的完整过程

二、环境确认

项目状态备注
Keil MDK✅ 正常已安装TI MSPM0支持包
J-Link驱动✅ 正常SWD模式
核心板连接✅ 正常四线连接
例程10编译✅ 0 Error10_keyscan.uvprojx
例程8编译✅ 0 Error8_systick.uvprojx

硬件:无名创新24年电赛H题小车套件,MSPM0G3507核心板
屏幕:SPI接口OLED(7针),但例程8代码是I2C驱动,暂时跳过显示


三、例程10(keyscan)核心代码分析

3.1 main.c 完整结构

#include"ti_msp_dl_config.h"#defineDELAY(16000000)// 0.5s(当前代码未使用)voiddelay_ms(uint16_tms){while(ms--)delay_cycles(CPUCLK_FREQ/1000);// 32MHz/1000 = 32000周期/ms}intmain(void){SYSCFG_DL_init();// 一键初始化while(1){// S1按键检测(高电平有效)if(DL_GPIO_readPins(KEY_PORT,KEY_S1_PIN)){delay_ms(10);// 消抖if(DL_GPIO_readPins(KEY_PORT,KEY_S1_PIN))// 再次确认{while(DL_GPIO_readPins(KEY_PORT,KEY_S1_PIN));// 等待释放DL_GPIO_togglePins(PORTB_PORT,PORTB_RGB_R_PIN);// 翻转红灯}}// S2按键检测(低电平有效,注意取反!)if(!DL_GPIO_readPins(PORTB_PORT,PORTB_S2_PIN)){delay_ms(10);if(!DL_GPIO_readPins(PORTB_PORT,PORTB_S2_PIN)){while(!DL_GPIO_readPins(PORTB_PORT,PORTB_S2_PIN));DL_GPIO_togglePins(PORTB_PORT,PORTB_RGB_G_PIN);// 翻转绿灯}}delay_ms(100);DL_GPIO_togglePins(GPIO_LEDS_PORT,GPIO_LEDS_USER_TEST_PIN);// 测试灯闪烁}}

3.2 代码元素逐行拆解

代码元素作用对应STM32标准库关键理解
DL_GPIO_readPins(port, pin)读取引脚电平GPIO_ReadInputDataBit()返回值是掩码,不是0/1!
DL_GPIO_initDigitalInputFeatures()初始化输入+上拉/下拉GPIO_Init()配置输入模式需要配置上拉电阻
delay_ms(10)软件消抖自定义或HAL_Delay机械按键抖动5-20ms
while(readPins(...))等待按键释放无(手动实现)防止按键按住时重复触发
!DL_GPIO_readPins(...)低电平检测!GPIO_ReadInputDataBit()S2硬件是上拉输入

3.3 返回值陷阱(核心认知)

// ❌ 错误!返回值不一定是1if(DL_GPIO_readPins(KEY_PORT,KEY_S1_PIN)==1)// ✅ 正确:判断大于0(高电平)if(DL_GPIO_readPins(KEY_PORT,KEY_S1_PIN)>0)// ✅ 正确:判断等于0(低电平)if(DL_GPIO_readPins(KEY_PORT,KEY_S1_PIN)==0)// ✅ 最精确:与掩码比较if(DL_GPIO_readPins(KEY_PORT,KEY_S1_PIN)==KEY_S1_PIN)

原因DL_GPIO_readPins返回的是对应位的掩码值,如0x00200000(第21位为1),而不是简单的1

3.4 三层检测模型

第一层:if(检测到电平) ← 初步判断 ↓ 第二层:delay_ms(10) ← 消抖等待 if(再次确认电平) ← 确认是真的按下 ↓ 第三层:while(等待释放) ← 等待按键松开 执行动作(翻转LED)

四、例程10拓展练习

拓展1:统一按键检测逻辑(失败→成功)

目标:把S1也改成低电平触发,和S2统一风格。

修改

// 修改前:S1高电平触发,S2低电平触发if(DL_GPIO_readPins(KEY_PORT,KEY_S1_PIN))// S1if(!DL_GPIO_readPins(PORTB_PORT,PORTB_S2_PIN))// S2// 修改后:统一低电平触发if(!DL_GPIO_readPins(KEY_PORT,KEY_S1_PIN)){delay_ms(10);if(!DL_GPIO_readPins(KEY_PORT,KEY_S1_PIN)){while(!DL_GPIO_readPins(KEY_PORT,KEY_S1_PIN));DL_GPIO_togglePins(PORTB_PORT,PORTB_RGB_R_PIN);}}

结果:S2失效,程序卡在S1的while循环里。

原因分析

  • S1硬件是下拉输入(未按下时低电平),按下后变高电平
  • 改成低电平检测后,!0永远为真,while死循环
  • 主循环被卡死,执行不到下面的S2检测

教训:两个按键硬件连接方式不同,代码逻辑本来就该不同。强行统一会导致逻辑错误。

恢复:S1保持高电平检测,S2保持低电平检测。


拓展2:状态机切换LED模式(成功)

目标:短按S1切换LED模式(灭→常亮→闪烁→灭)。

代码

#defineMODE_OFF0#defineMODE_ON1#defineMODE_BLINK2uint8_tled_mode=MODE_OFF;// 按键处理led_mode=(led_mode+1)%3;// 主循环根据模式执行switch(led_mode){caseMODE_OFF:DL_GPIO_clearPins(PORTB_PORT,PORTB_RGB_R_PIN);break;caseMODE_ON:DL_GPIO_setPins(PORTB_PORT,PORTB_RGB_R_PIN);break;caseMODE_BLINK:DL_GPIO_togglePins(PORTB_PORT,PORTB_RGB_R_PIN);delay_ms(300);// 300ms周期,肉眼可见闪烁break;}

遇到的问题:直接复制代码,红灯常亮而不是闪烁。

原因delay_ms(300)在switch内部,但while循环顶部还有delay_ms(100),循环周期约400ms。blink_cnt需要50次循环才翻转,周期太长。

解决:直接用delay_ms(300)控制闪烁周期,不用计数器。

验证:每按一次S1,红灯切换一种模式(灭→亮→闪烁→灭)。


五、例程8(systick)核心代码分析

5.1 main.c 完整结构

#include"ti_msp_dl_config.h"#include"systick.h"#include"drv_oled.h"#include"stdio.h"#defineDELAY(3200000)// 未使用systimemain_t,timer_t;intmain(void){SYSCFG_DL_init();oled_init();// OLED初始化(SPI屏,代码是I2C,跳过)NVIC_EnableIRQ(TIMER_0_INST_INT_IRQN);// 使能定时器中断DL_TimerG_startCounter(TIMER_0_INST);// 启动定时器while(1){// OLED显示(跳过)delay_ms(200);DL_GPIO_togglePins(USER_GPIO_PORT,USER_GPIO_LED_PA0_PIN);// PA0闪烁get_systime(&main_t);}}voidTIMER_0_INST_IRQHandler(void){switch(DL_TimerG_getPendingInterrupt(TIMER_0_INST)){caseDL_TIMERG_IIDX_ZERO:get_systime(&timer_t);DL_GPIO_togglePins(USER_GPIO_PORT,USER_GPIO_RED_PIN);// 红灯闪烁break;default:break;}}

5.2 关键配置(SysConfig截图)

参数说明
Timer Clock Prescaler10032MHz / 100 = 320kHz
Timer Clock Frequency320.00 kHz定时器实际时钟
Desired Timer Period100ms中断周期
Actual Timer Period100.00 ms实际中断周期
Enable InterruptsZero event计数到零触发中断
Start Timer未勾选代码中手动启动

5.3 中断 vs 轮询对比

维度轮询(例程10按键)中断(例程8定时器)
CPU行为主动查询,忙等被动响应,做其他事
效率低,CPU空转高,CPU解放
响应延迟取决于查询间隔硬件级,微秒级
适用场景简单、低频事件定时、高频事件
代码复杂度简单需要中断服务函数

5.4 遇到的问题:红灯不亮

排查过程

  1. PA0闪烁正常 → 程序在跑
  2. 检查中断函数名 → 正确(TIMER_0_INST_IRQHandler)
  3. 检查SysConfig → 中断已使能(Zero event)
  4. 检查引脚定义 → 发现USER_GPIO_RED_PINGPIO_GRP_0_PIN_R_PIN不是同一个引脚!

原因

  • USER_GPIO_RED_PIN= GPIOA.26(例程8的独立红灯)
  • GPIO_GRP_0_PIN_R_PIN= GPIOB.26(例程10的RGB红灯)
  • 硬件上不是同一个LED

解决:把中断函数里的红灯改为GPIO_GRP_0_PIN_R_PIN

验证:PA0每200ms闪烁(主循环),RGB红灯每100ms闪烁(中断),频率不同,证明中断独立运行。


六、例程8拓展练习

拓展1:改变定时器周期(成功)

操作:修改SysConfig中Desired Timer Period:

原值新值效果
100ms10ms红灯快闪(50Hz)
100ms500ms红灯慢闪(1Hz)
100ms1000ms每秒闪一次

理解:Period越大,中断越慢,LED闪得越慢。


拓展2:非阻塞延时(失败→成功)

目标:用定时器中断做时间基准,替代delay_ms()的阻塞等待。

第一次尝试(失败)

volatileuint32_ttick_ms=0;// 中断中每100ms+1// 主循环if(tick_ms-last_tick>=200)// 以为是200ms{last_tick=tick_ms;DL_GPIO_togglePins(...);}

结果:LED不闪烁。

原因:中断周期是100ms,tick_ms每100ms才+1。tick_ms - last_tick >= 200实际是200 × 100ms =20秒

解决:统一时间单位,代码中按中断次数计算:

// 中断周期100ms,tick_cnt每100ms+1volatileuint32_ttick_cnt=0;// 每2个中断周期(200ms)翻转if(tick_cnt-last_tick>=2){last_tick=tick_cnt;DL_GPIO_togglePins(...);}

验证:PA0每200ms闪烁,主循环中没有delay_ms,CPU一直在运行。


拓展3:多个定时任务(成功)

volatileuint32_ttick_cnt=0;// 任务1:每100ms(1个中断)翻转PA0if(tick_cnt-last_tick_1>=1){last_tick_1=tick_cnt;DL_GPIO_togglePins(USER_GPIO_PORT,USER_GPIO_LED_PA0_PIN);}// 任务2:每500ms(5个中断)翻转红灯if(tick_cnt-last_tick_5>=5){last_tick_5=tick_cnt;DL_GPIO_togglePins(GPIO_GRP_0_PORT,GPIO_GRP_0_PIN_R_PIN);}

验证:PA0每100ms闪(快),红灯每500ms闪(慢),互不干扰。


拓展4:按键控制定时器启停(成功)

if(timer_running)DL_TimerG_startCounter(TIMER_0_INST);// 启动elseDL_TimerG_stopCounter(TIMER_0_INST);// 停止

验证:上电红灯不闪,按S1开始闪,再按S1停止。


拓展5:按键动态改变周期(失败→成功)

目标:按S1切换红灯闪烁频率(快/中/慢)。

第一次尝试(失败)

DL_TimerG_setPeriod(TIMER_0_INST,3200);// 报错:没有这个函数

原因:无名科技提供的库版本没有DL_TimerG_setPeriod

解决:不用硬件修改,用软件计数实现调速:

uint8_tspeed_mode=1;// 0=快(100ms) 1=中(500ms) 2=慢(1000ms)// 根据速度模式,决定计数目标uint8_ttarget_cnt;switch(speed_mode){case0:target_cnt=1;break;// 100ms = 1个中断case1:target_cnt=5;break;// 500ms = 5个中断case2:target_cnt=10;break;// 1000ms = 10个中断}if(tick_cnt-last_tick>=target_cnt){last_tick=tick_cnt;DL_GPIO_togglePins(GPIO_GRP_0_PORT,GPIO_GRP_0_PIN_R_PIN);}

验证:按S1切换红灯闪烁速度(100ms→500ms→1000ms→100ms)。


拓展6:状态机+中断(成功)

#defineSTATE_IDLE0#defineSTATE_RUN1#defineSTATE_FAST2#defineSTATE_SLOW3uint8_tsystem_state=STATE_IDLE;// 按键切换状态system_state=(system_state+1)%4;// 根据状态执行不同动作switch(system_state){caseSTATE_IDLE:/* 所有灯灭 */break;caseSTATE_RUN:/* 红灯500ms闪烁 */break;caseSTATE_FAST:/* 红灯100ms+绿灯500ms */break;caseSTATE_SLOW:/* 红灯1000ms闪烁 */break;}

验证:按S1循环切换四种模式。


七、关键陷阱汇总

陷阱现象解决
readPins返回值陷阱判断条件永远为假>0==0判断,不要用==1
按键硬件差异统一逻辑后某个按键失效确认上拉/下拉配置,不要强行统一
中断函数名错误程序卡死,LED常亮必须和SysConfig生成的向量名一致
引脚定义混淆LED不亮USER_GPIO_RED_PINGPIO_GRP_0_PIN_R_PIN不是同一个
时间单位错误LED"不闪烁"(实际很慢)确认中断周期,tick单位是中断次数不是ms
setPeriod不存在编译报错用软件计数实现调速,不修改硬件周期
中断里阻塞程序卡死中断里不能用delay_ms
共享变量不加volatile变量不更新中断和主循环共享的变量必须加volatile

八、概念连接

8.1 从例程10到例程8的演进

例程1(GPIO输出) 例程10(GPIO输入) 例程8(定时器中断) ↓ ↓ ↓ LED亮灭控制 按键检测+消抖 定时触发+非阻塞 ↓ ↓ ↓ 单一动作 条件判断 时间基准+多任务 ↓ ↓ ↓ 阻塞延时 轮询检测 中断响应

8.2 软件分频的理解

硬件定时器:100ms中断(固定不变) ↓ 软件计数: tick_cnt % 1 == 0 → 100ms任务(快) tick_cnt % 5 == 0 → 500ms任务(中) tick_cnt % 10 == 0 → 1000ms任务(慢) ↓ 一个硬件定时器 → 多个不同频率任务

这是嵌入式中最常用的"软件分频"方法,比动态修改硬件周期更稳定。

8.3 向PWM衔接

例程8 定时器中断(通知型) ↓ 例程6/7 Timer PWM(自动型) ↓ 区别: - 中断:到点了告诉CPU,CPU自己处理(翻转LED) - PWM:到点了硬件自动改变引脚电平,CPU完全解放(输出方波)

九、今日问题清单

序号问题描述当前状态优先级下一步行动
1SPI OLED与I2C代码不匹配✅ 已解决购买I2C OLED或改写SPI驱动
2readPins返回值陷阱✅ 已理解记住用>0/==0判断
3按键硬件差异导致统一逻辑失败✅ 已理解确认上拉/下拉后再写代码
4引脚定义混淆(USER_GPIO vs GPIO_GRP_0)✅ 已解决ti_msp_dl_config.h确认
5时间单位错误(tick单位是中断次数)✅ 已理解确认中断周期后再写代码
6DL_TimerG_setPeriod不存在✅ 已解决用软件计数替代
7中断函数里不能阻塞✅ 已理解中断里只做简单操作
8共享变量必须加volatile✅ 已理解养成习惯
9状态机从隐式到显式📝 已实践继续练习
10Keil调试(Watch窗口)🔍 待学习下次学习

十、今日感悟与模式记录

学习节奏

“跑通例程→尝试修改→遇到问题→排查→理解原理→再修改→成功”

这个循环比"一次性看完所有代码"效率高得多。每个陷阱都是一次深度理解的机会。

关键认知更新

  1. 返回值陷阱是TI的常态readPins返回掩码不是0/1,必须养成检查返回值类型的习惯
  2. 硬件决定软件逻辑:按键的上拉/下拉配置决定了检测逻辑,不能强行统一
  3. 引脚定义必须确认USER_GPIO_RED_PINGPIO_GRP_0_PIN_R_PIN不是同一个,查ti_msp_dl_config.h
  4. 时间单位必须统一:中断周期是100ms,tick单位是中断次数,不是ms
  5. 软件分频比硬件修改更稳定:不用setPeriod,用计数器实现多频率
  6. 中断是效率的关键:从轮询到中断,CPU从忙等变成被动响应,效率质变

工具链使用

工具今日用途效果
Keil编译、烧录熟悉,遇到delay_ms重复定义问题
SysConfig配置定时器周期、中断理解Prescaler和Period的关系
J-Link程序下载顺畅
Kimi实时答疑、代码分析、概念连接长期陪伴,理解上下文

记录时间:2026-07-19 16:15

下次学习:例程6/7(Timer PWM),让电机转起来

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询