ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

嵌入式实验报告2:定时器、串口与中断优先级的量化验证

嵌入式实验报告2:定时器、串口与中断优先级的量化验证 简介天津理工大学嵌入式系统课程的实验报告完整记录基于树莓派与Linux环境使用GCC编译器控制I2C总线读取温度与湿度的实现过程面向嵌入式初学者及高校嵌入式课程学生参考。整份资源为单个docx格式文件约955KB内含实验考核表、实验目的与内容、详细操作步骤、带注释的C语言源代码、运行结果截图以及实验过程中遇到的问题与排错记录。已有544人学习下载适合准备嵌入式实验或希望动手实践树莓派I2C通信的读者。报告侧重嵌入式硬件接口控制与Linux下程序编译运行清晰展示温度、湿度读取函数的模块化拆分与主函数调用方式同时延伸LPS25H气压温度传感器选做内容。读者可借鉴实验报告撰写规范、程序结构设计方法以及因gedit命令缺失而改用图形界面编辑的排错经验对独立完成同类实验和编写实验报告有直接帮助。1. 嵌入式实验报告2从“会亮”到“会证明”的第二次实验第一份嵌入式实验报告大家都会写初始化GPIO写高写低LED亮了拍两张照片贴上去完事。到了第二份画风突变——串口要往电脑发数据定时器要给你一个“准的”时间中断一来你还得想清楚谁先执行。要是继续按“现象截图”的套路写评审大概率会问一句你怎么证明这个100ms周期是100ms而不是碰巧看起来像天津理工大学这类工科院校的嵌入式实验报告2绝大多数就是把门槛放在这里不再要求“跑起来”而是要求“证明它按预期工作”。这篇不按教材目录走只讲我调实验板、写课程实验报告、复核学生数据时总结出的一套做法——把时基计算、串口帧、中断优先级、数据验证四件事拆开每件给到能直接复用的命令、参数和脚本。正在写报告的学生、带实验的助教、刚接触裸机实时设计的工程师都能按这套流程把实验做厚。2. 实验报告2的技术底座定时器时基计算与串口中断优先级GPIO实验为什么简单因为整个程序是main函数从第一行顺序走到最后一行所有行为可预期。串口定时器把它变成了另一种形态定时器在后台自己数数数到顶了就发一个“更新事件”串口数据随时可能到达。程序不再是从头到尾跑一遍而是“主循环跑着跑着突然被硬件打断”的异步模型。第二份报告要克服的第一个障碍就是这个心智模型不是代码不会写而是不知道该记录什么。2.1 为什么第二份报告普遍卡在串口与中断如果把第一份实验比作“写开关”第二份实验就是在学“写调度”。定时器一旦启动计数器和比较寄存器一直在硬件里跑和CPU执行到哪一行无关中断请求到达后CPU保存现场、进入向量表、执行回调、再恢复现场这整个流程在数据手册里只有几页但落到实验报告上要写清楚的就变成了三件事事件什么时候发生、发生后多久才响应、响应后留下了什么记录。常见做法是把这三件事分开处理——事件时间由定时器更新中断决定响应时间用DWT或逻辑分析仪去量记录则通过串口帧带出来。很多实验做到一半失败不是逻辑错误而是把这三件事混在了同一个回调里比如在中断里直接调用HAL_UART_Transmit做阻塞发送串口忙的时候整个中断响应被拖长时间记录立刻失真。这部分在实验报告里是最值得写的内容也是评审最愿意追问的段落。把这个模型理清之后后面再接触嵌入式linux里的jiffies、hrtimer、软中断思路也是同一套——先有事件再谈优先级最后再看谁在什么上下文里消费数据。2.2 定时器时基计算PSC、ARR和16位计数器的天花板时基计算是第二份报告绕不开的公式也是面试题的高频考点。以F103系列为例HSE为8MHz、SYSCLK为72MHz、APB1预分频为2时挂在APB1上的TIM3实际时钟是72MHz——APB1外设时钟是36MHz但定时器会在APB1预分频不为1时获得两倍补偿很多学生栽在这个补偿上把计数频率当成36MHz去算周期。# timer_calc.py —— 通过目标周期反推 PSC / ARR # f_tim 为定时器实际时钟period_us 为目标周期微秒 def calc_timer(f_tim, period_us, psc): tick_hz f_tim // (psc 1) # 计数频率 arr period_us * tick_hz // 1_000_000 - 1 return psc, arr # 72MHz 定时器时钟预分频 7199 后计数频率为 10kHz psc, arr calc_timer(72_000_000, 100_000, 7199) print(psc, arr) # (7199, 999) - 100msPSC决定计数频率ARR决定溢出周期。选PSC时先看精度需求计数频率越高周期分辨得越细但ARR很容易顶到上限计数频率越低ARR余量越大但周期只能按毫秒级粒度调。F103上的TIM3是16位计数器PSC和ARR都不能超过65535如果目标周期超过“当前计数频率下的65535个计数”就不得不往下调PSC。常用的几组组合如下。目标周期PSCARR计数频率说明1ms719991MHz适合高频采样10ms7199991MHz周期粒度1us100ms719999910kHz本篇实验用1s7199999910kHz适合做长稳观测注意表里ARR只是示例实际值要按“目标周期×计数频率−1”反推。手动改CubeMX里的PSC和ARR时务必回头确认定时器时钟源不是APB1外设时钟而是APB1定时器时钟否则整套参数都会偏一倍。2.3 中断优先级怎么配NVIC分组、抢占与子优先级实验里一般只开两个中断定时器更新中断和串口接收中断。F1的NVIC通过SCB-AIRCR的优先级分组决定抢占位和子优先级的位数分配CubeMX的NVIC配置页里可以直接选Group 22位抢占、2位子优先级这类组合这是多数实验项目最常用的配置。分组抢占优先级位数子优先级位数使用场景Group 222中断源少区分“时基优先”Group 440只论抢占不分先后Group 004不推荐做时基这套配置里最重要的原则是定时器中断的抢占优先级要高于串口。原因很简单时基丢了这一拍就补不回来串口晚几十微秒处理不影响数据完整性。另一个常见的坑是把所有中断优先级调成相同数值表面上能编译能运行但两个中断同时挂起时NVIC按向量表顺序裁决行为完全不可预期。第二份报告里只要把优先级分组、两个中断的抢占级、为什么定时器更高这三行写清楚整个实验的设计感就出来了。3. 跑通最小可复验实验TIM3周期100ms上报与三路验证3.1 接线与CubeMX配置PA9/PA10、TIM3和中断向量先解决物理链路。F103最常见的实验板把USART1放在PA9/PA10上如果板载了USB转串口直接插USB线即可如果是外部模块接线如下。实验板引脚接到说明PA9 (USART1_TX)外部串口模块 RX发送帧到PCPA10 (USART1_RX)外部串口模块 TX预留接收通道GND模块 GND必须共地否则首帧乱码CubeMX里按下面顺序点一遍RCC启用HSE并选Crystal/Ceramic ResonatorSYS里把Debug串行线打开USART1设为异步模式波特率115200、8位数据、无校验、1位停止位TIM3时钟源选Internal ClockPSC填7199、ARR填999NVIC里把TIM3 global interrupt和USART1 global interrupt两个勾都打上Clock Configuration里确认SYSCLK是72MHz。生成工程之后定时器还需要一行启动代码才能进入工作状态。3.2 主循环上报最小代码中断只置位主循环发帧这里给出一个可以直接替换进CubeMX工程的最小实现。关键设计是中断回调里只打标志位不碰串口串口发送全部放到主循环从根上避开重入问题。/* main.c —— TIM3 每 100ms 更新一次主循环把计数值通过 USART1 发出 */ /* 前提CubeMX 已配置 USART1 115200/8N1TIM3 PSC7199 ARR999 */ static volatile uint16_t tick_cnt 0; /* 溢出次数计数 */ static volatile uint8_t need_report 0; /* 中断置位主循环消费 */ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM3) { tick_cnt; /* 只更新计数和标志 */ need_report 1; /* 由主循环负责发送 */ } } int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); MX_TIM3_Init(); HAL_TIM_Base_Start_IT(htim3); /* 启动计数器并打开中断 */ uint8_t frame[6]; while (1) { if (need_report) { need_report 0; frame[0] 0xAA; /* 帧头1 */ frame[1] 0x55; /* 帧头2 */ frame[2] tick_cnt 0xFF; /* 计数值低字节 */ frame[3] (tick_cnt 8) 0xFF; /* 计数值高字节 */ frame[4] 0x00; /* 预留字节 */ frame[5] frame[0] ^ frame[1] ^ frame[2] ^ frame[3]; /* XOR校验 */ HAL_UART_Transmit(huart1, frame, 6, 100); } } }代码里有三处必须解释清楚。第一tick_cnt和need_report都加了volatile因为它们由中断修改、主循环读取不加volatile可能被优化掉这是嵌入式面试里最常见的送分题。第二帧头用0xAA 0x55作用是让PC端能快速找到帧边界XOR校验占一字节接收端重新算一遍就知道这一帧有没有被干扰。第三阻塞式发送的超时时间是100ms这个值要大于“一帧发完所需时间”——115200波特率下6字节约0.52ms100ms远超余量够用。提示frame[5]的XOR校验只覆盖前4个数据字节以后扩展帧结构时记得把新字节加进校验表达式。参数表如下。参数值说明波特率1152001字节约86.8us6字节帧约0.52msPSC7199定时器时钟从72MHz分频到10kHzARR999满计数1000次溢出周期100ms中断回调动作仅置位避免串口阻塞拖垮时基3.3 三种验证手段串口终端、逻辑分析仪和PC脚本先做最简单的验证用串口终端直接看帧。Linux下用screenWindows下用PuTTY或其它串口工具参数都是115200/8/N/1打开后应能看到一帧以0xAA 0x55开头、长度为6字节的数据定时出现。sudo screen /dev/ttyUSB0 115200逻辑分析仪是测量MCU端真实行为的最可靠工具。把通道接到PA9采样率设置为1MHz以上抓大约10帧测量相邻两帧起始位的间隔。实测值应该是100ms左右如果看到200ms或50ms问题几乎都出在PSC/ARR算错或定时器时钟源没核对。PC脚本适合做长时间验证比如连续跑1000帧看丢帧率和平均周期。# verify_tick.py —— 连续接收1000帧统计间隔、丢帧并落盘CSV import serial, struct, time, csv ser serial.Serial(/dev/ttyUSB0, 115200, timeout0.5) last_tick, last_t None, None loss 0 rows [] for _ in range(1000): frame ser.read(6) if len(frame) ! 6 or frame[0] ! 0xAA or frame[1] ! 0x55: continue tick struct.unpack(H, frame[2:4])[0] now time.perf_counter() if last_tick is not None: dt (tick - last_tick) 0xFFFF # 相邻帧tick差值 if dt ! 1: # 不是步进1说明丢帧 loss dt - 1 rows.append({tick: tick, interval_ms: (now - last_t) * 1000}) last_tick, last_t tick, now print(f接收完成丢帧计数: {loss}) with open(period_log.csv, w, newline) as f: writer csv.DictWriter(f, fieldnames[tick, interval_ms]) writer.writeheader() writer.writerows(rows) print(已写入 period_log.csv)这个脚本测量的间隔包括PC端USB调度和串口驱动延迟所以它验证的是“通路完整性”不是MCU的微秒级精度。两层结论要分开写进报告逻辑分析仪证明MCU发得准PC脚本证明这条路不丢数据。很多学生拿PC毫秒级的时间戳直接断言MCU精度这是报告里最容易被挑刺的推论错误。3.4 必踩的4个坑Start_IT、回调重入、NVIC和打印阻塞这4个坑在实验报告2里出现的频率非常高。第一CubeMX生成代码后只初始化定时器不工作必须手动调用HAL_TIM_Base_Start_IT(htim3)计数器才开始走、更新中断才使能。第二中断回调里不要做阻塞型调用尤其是HAL_UART_Transmit串口忙时整个时基都会抖。第三NVIC勾选和Start_IT是两件事前者在CubeMX生成的MX_TIM3_Init中完成后者需要自己写漏了多半程序一直不按预期进中断。第四如果用了printf重定向到串口浮点格式化会明显拉大堆栈占用建议在实验板上直接发二进制帧PC端再解析既快又稳。4. 把实验数据写进报告时间戳采集、误差分析与图表4.1 时间戳方案选型SysTick、DWT与RTC串口终端只解决“能不能收到”报告要解决“数据长什么样、偏差有多少”。采集时间戳有几种选择关键不是哪个更高级而是量程和分辨率匹配实验目标。RTC这类外设通常只做到秒级分辨率适合小时级稳定度参考不适合测单帧周期要在代码里微秒级打点还是得用内核里的计数器。方案分辨率特点SysTick1/72MHz约13.9ns与HAL共用别随意改动重装值DWT-CYCCNT1/72MHz约13.9ns内核外设测量最干净外部RTC1s只适合小时级稳定度参考逻辑分析仪取决于采样率离线测量最接近硬件真实行为需要在代码里对一小段路径做时间戳时DWT比SysTick好用因为不占用系统节拍。使能方式固定在系统初始化阶段执行一次即可。/* dwt_us.c —— 使用内核调试单元做微秒级计时 */ CoreDebug-DEMCR | CoreDebug_DEMCR_TRCENA_Msk; /* 打开跟踪时钟 */ DWT-CYCCNT 0; DWT-CTRL | DWT_CTRL_CYCCNTENA_Msk; /* 使能周期计数器 */ uint32_t t0 DWT-CYCCNT; /* 被测量的代码段 */ uint32_t us (DWT-CYCCNT - t0) / 72; /* 72MHz 下转 us */这里要注意DWT-CYCCNT是32位72MHz下大约59.6秒回绕一次实验里测量短代码段完全够用。用它记录中断回调的进出时刻就能判断回调本身占了多少执行时间——这个数写进报告比“耗时很短”这种话有力得多。4.2 误差来源与量级晶振、中断入口和上位机调度数据采集完成后下一步是解释误差。把误差按固定偏差、随机抖动和测量污染三类拆开报告会清晰很多。很多学生一看到上位机测量抖动有零点几毫秒就急着改代码其实那部分根本不在MCU里。误差来源量级表现晶振频率偏差±20~50ppm平均周期整体偏移读数固定偏大或偏小中断入口延迟固定12个时钟周期悬挂等待单次抖动微秒级以下主循环取标志时机取决于主循环代码长度周期上限被主循环耗时拉高串口发送占用115200下6字节约0.52ms只在阻塞发送时影响主循环PC端USB调度0.125~1ms上位机测得抖动远大于MCU真实值ppm换算有个好记的规则50ppm的晶振在1s的测量窗口里最多偏50us。所以报告里写“实测平均周期100.023ms比目标值长0.023%”远比“基本准确”有信息量。如果还发现固定偏移下一步就去查外部晶振的实际频率和负载电容那是硬件层面的归因。4.3 用matplotlib画周期离散度直方图上一章生成的period_log.csv在这里直接派上用场。把采集结果先落成CSV再画图报告里引用的每张图都能从同一份原始数据复现这一点评审非常喜欢。# plot_period.py —— 从CSV读取间隔数据画直方图并标注均值 import csv import matplotlib.pyplot as plt deltas [] with open(period_log.csv, newline) as f: for row in csv.DictReader(f): deltas.append(float(row[interval_ms])) mean sum(deltas) / len(deltas) plt.figure(figsize(6, 4)) plt.hist(deltas, bins40, color#4C72B0, edgecolorwhite) plt.axvline(mean, colorred, linewidth1.5, labelfmean{mean:.4f} ms) plt.xlabel(interval / ms) plt.ylabel(counts) plt.legend() plt.savefig(period_dist.png, dpi200, bbox_inchestight)这段脚本做两件报告需要的事分布形状说明抖动是离散还是集中均值线说明偏差方向。如果直方图只有一根细柱子说明100ms周期很稳定如果出现拖尾优先去查主循环里是否有不定期执行的代码段。4.4 报告结论别写成嵌入式八股实验报告里最常见的写法是“本次实验让我了解了xx原理掌握了xx方法”这种句子在评审眼里约等于没写。更值得写的是一个能复验的三段式结论观测值、对比期望、归因。比如“连续采集1000个周期均值100.023ms极差0.9ms较标称值偏差0.023%偏差方向与外部晶振标称精度一致判定时基配置正确。”三十来个字含义比三行套话多得多。原理部分也不要抄数据手册写“为什么选10kHz作为计数频率而不是1MHz或100kHz”一句话就能让别人看出你理解过这个设计。实验2真正的分水岭就在这里能不能用数据把自己的设计决策说圆。5. 报送前5分钟自检复位重跑、时钟确认与记录留存5.1 提交前自检清单第二份报告的评审通常比第一份更较真会自己拉程序跑一遍。交之前花5分钟按下面这张表过一遍能挡掉八成被打回的原因。自检项操作判定标准上电即跑断开电源再重新上电无需手动复位就能持续上报时钟复核打开CubeMX的Clock Configuration确认HCLK72MHzTIM3时钟显示72MHz串口配置留证截图串口终端设置页115200/8/N/1必须可见数据文件落盘用verify脚本导出CSV报告里的图表能从同一份CSV复现最小化工程清理生成的中间目录保留.ioc、源码和hex别人打开工程能直接重建这套自检里时钟复核最容易漏。很多人会把HCLK的72MHz当作TIM3的时钟但APB1预分频为2时两者数值刚好一样属于“巧合成立”一旦改成别的外频配置就错位。更稳妥的判断方式是查RCC-CFGR里的PPRE1位段和定时器时钟的补偿规则。5.2 留档对下次实验的作用把工具链固定下来实验3会轻松很多。串口查看可以用开源串口工具也可以在VSCode的嵌入式开发插件里直接看串口输出还有人习惯用VB6写个简单的接收上位机只是验证数据完整性的话用什么语言不重要重要的是把原始数据留成CSV而不是只截图。测量脚本放进工程的tools/目录命名带上实验参数比如tim3_100ms_verify.py下次实验3把周期改成1s甚至换一个定时器脚本逻辑一行都不用动。报告正文和截图分离保存正文用Markdown写图和CSV放在同级的data/和figures/目录。截图命名不要用“111”“新建位图”这类名字改成带用途和时间的形式比如uart_115200_settings_run01.png逻辑分析仪导出的波形文件保留原始格式评审真的追问时能直接打开看。做固件实验证据链越完整结论越牢靠。把脚本放在工程tools/目录里下次实验3直接把周期参数从100ms改成1s连测量方法都不用换。本文还有配套的精品资源点击获取
返回列表