简介:这是一份面向电子信息技术、近代电子学实验等课程设计场景的单片机应用设计报告,围绕AT89C52实现1Hz~10MHz自动量程数字频率计,覆盖频率测量、放大整形、分频、脉宽测量、显示锁存等完整设计流程,适合电气/电子类专业学生做课程设计、毕业设计或实验报告参考。资源共1个文件,为doc格式设计说明,压缩包约1.06MB,内容以设计任务、测量原理、模块电路分析、仿真截图和程序控制思路为主,文件集中便于直接查阅。文档详细展示了74LS90分频、74LS273锁存显示、74LS153量程切换、GATE位脉宽测量以及放大整形电路的计算与调试方法,并配有分频、整形等电路仿真截图,可帮助读者理解单片机测量系统从硬件到控制的完整实现路径。目前已有99人学习浏览,对有同类课程设计或单片机测控项目需求的学习者具有直接参考价值。
1. 单片机数字频率计的关键坎:AT89C52 的计数上限与 74LS90 分频
单片机数字频率计看着只是数脉冲,实际上一旦把 1MHz 方波直接送到 AT89C52 的 T1 脚,一秒内 16 位计数器会溢出十几次,显示值完全不可信。课程设计题面写的是 1Hz 到 1MHz,这套方案却用两片 74LS90 把上限扩到了 10MHz,核心思路不是让单片机硬数高频,而是先把信号分频到它能稳定处理的区间。对电子信息类专业的学生来说,它把信号放大整形、门控定时、自动量程、锁存显示串成一条完整链路;对做信号源校准的工程师来说,里面 74LS153 换挡和回差处理也有可参考的地方。下面按原理、硬件、固件和误差四个方向拆开讲。
2. 数字频率计的测频原理与 AT89C52 的计数上限
频率测量最直接的定义是“单位时间内的脉冲数”。常见的做法是固定 1 秒闸门,对输入信号的上升沿计数,计数值在数值上就等于频率值。这个思路简单,但放到 AT89C52 上会碰到两个限制:第一,引脚能识别的最高信号频率约为晶振频率的 1/24,12MHz 晶振下就是 500kHz;第二,16 位计数器在 1 秒内最多记 65535 个脉冲,不做软件扩展的话,量程连 100kHz 都撑不住。所以必须同时在硬件上加分频,在软件里扩展计数值。
2.1 自动量程三档的计数关系
方案把测量范围分成三档,核心是让进入 T1 引脚的频率始终落在 100kHz 以内。这个值比 500kHz 物理上限低很多,能留出余量,也让 1 秒闸门内的最大计数值稳定在 100000 附近。分频比不同,最终频率换算系数也不同。
| 档位 | P1.1 P1.0 | 分频比例 | 输入频率范围 | 1 秒闸门内 T1 最大计数值 |
|---|---|---|---|---|
| 0 | 00 | 不处理 | 1Hz – 100kHz | 100000 |
| 1 | 01 | 10 分频 | 100kHz – 1MHz | 100000 |
| 2 | 10 | 100 分频 | 1MHz – 10MHz | 100000 |
注意第二档和第三档在分频之后,进入 T1 的引脚频率仍然在 100kHz 以下。第一档虽然允许直接输入到 100kHz,但 16 位计数器最大只有 65535,依然会溢出。所以下一节要做的软件扩展是必选项,不是锦上添花。
2.2 T1 溢出中断扩展:把 16 位计数器当成 24 位用
AT89C52 的两个定时/计数器 T0、T1 都支持外部脉冲计数,但寄存器只有 16 位。要在 1 秒闸门内安全地数到 100000,就得在 T1 每次溢出时进中断,把软件计数器加一。读取最终频率时,用溢出次数乘以 65536,再加上 TH1/TL1 的当前值,就得到完整计数值。
// 频率测量模式:T0定时10ms,T1外部计数 void freq_mode_init(void) { TMOD = 0x51; // T1:方式1外部计数,T0:方式1定时 TH0 = 0xD8; // 10ms定时初值,12MHz晶振 TL0 = 0xF0; // 65536 - 10000 = 0xD8F0 TH1 = 0x00; // T1计数清零 TL1 = 0x00; ET0 = 1; // 开T0中断 ET1 = 1; // 开T1溢出中断 EA = 1; // 开总中断 TR0 = 1; // 启动定时 TR1 = 1; // 启动外部计数 } unsigned int ovf_cnt; void timer1_isr(void) interrupt 3 { ovf_cnt++; // 每次溢出代表又多计了65536个脉冲 }TMOD 的 0x51 换算成二进制是 0101 0001,高四位对应 T1,低四位对应 T0。低四位 0001 表示 T0 工作在方式 1 的 16 位定时器;高四位 0101 表示 T1 工作在方式 1 的 16 位计数器。T1 在计数模式时持续采样外部引脚跳变,每检测到一个上升沿,TH1/TL1 加 1。溢出中断只负责把软件高 16 位补上,最终得到类似 24 位的计数值。实际工程里读取 T1 时要先停 TR1 再读,避免计数器一边跳一边读,这个问题在 4.2 节会专门说。
2.3 GATE 位复用:用同一颗单片机测脉宽
测频之外还要测脉宽,方案利用的是 TMOD 的 GATE 位。GATE 置 1 后,定时器的启动条件从“TRx=1”变成“TRx=1 且 INTx 引脚为高”。这样一来,INT0 引脚持续为高的时间就是定时器的实际开门时间。配置语句很少。
// 脉宽测量模式:T0方式1,GATE=1 void pulse_mode_init(void) { TMOD = 0x09; // 0000 1001,T0方式1且门控打开 TH0 = 0x00; // 从0开始计数 TL0 = 0x00; TR0 = 1; // 允许计数,但需INT0为高才真正计数 }12MHz 晶振下机器周期是 1us,定时器每个计数就是 1us。因此测量结束后读出的 TH0/TL0 直接就是脉冲宽度的微秒数。需要留意的是方式 1 最大只有 65535us,约 65.5ms;如果被测脉宽更长,同样要用溢出中断扩展。这个 GATE 位的用法是很多 51 单片机教材里容易略过的地方,实际做脉宽测量时非常顺手。
提示:频率测量模式和脉宽测量模式共用同一组定时器寄存器,切换模式前先 TR0/TR1 清零,避免中断里读到半个闸门的数据。
3. 放大整形、74LS90 分频与 74LS273 锁存显示硬件链路
被测信号不一定是方波,题目要求里写了输入可以是 10mV 到 3V 有效值的正弦波。单片机不认识正弦波,只认 TTL 电平的上升沿和下降沿,所以前端必须完成两件事:把信号幅度抬到门限以上,再把波形变成方波。之后才能接分频和显示电路。
3.1 反相比例放大和与门整形:参数要按最小输入来定
放大电路由集成运放组成反相比例放大,增益 Au = R4 / R3,默认取 10 倍。后面接一个与门,与门一端拉高,另一端接放大后的信号。输入幅度高于与门阈值时输出高,低于阈值时输出低,于是正弦波被切成方波。
如果输入最大到 3V 有效值,放大 10 倍后会超过运放输出范围,波形顶部会削顶。这不是不能工作,削顶后过零跳变仍然有效,但最好按最小输入幅度调整增益。R3 取 10k、R4 取 100k 时增益 10,适合几十 mV 的小信号;输入到几百 mV 以上时,我会直接把 R4 改成 22k,增益压到 2.2 倍,避免运放进入饱和区。
| 输入幅度 | R3 | R4 | 增益 | 说明 |
|---|---|---|---|---|
| 10mV – 100mV | 10k | 100k | 10 | 小信号输入时用 |
| 100mV – 1V | 10k | 22k | 2.2 | 中幅度常用 |
| 1V – 3V | 10k | 10k | 1 | 接近直接整形 |
有一点必须提醒:与门不是施密特触发器,它只有一个固定阈值电压,输入边沿有噪声时输出会产生抖动。课程设计里用与门是低成本做法,如果后续想提高稳定性,应换成 74HC14 施密特门,整形效果会好很多。
3.2 74LS90 双级分频与 74LS153 选通切换
74LS90 是异步十进制计数器,可以配置成 10 分频。第一片从输入 CLK 进、QD 出就是十分频;第二片再接第一片的 QD,从第二片的 QD 出就是一百分频。分频输出不能同时直接进单片机,否则两路信号会互相影响,所以要用 74LS153 四选一选择器。
AT89C52 的 P1.0 和 P1.1 接到 74LS153 的 A、B 选择端,三个状态分别选通直通、10 分频、100 分频。对应关系如下。
| P1.1 | P1.0 | 74LS153 选择 | 进入 T1 的频率 | 量程上限 |
|---|---|---|---|---|
| 0 | 0 | 输入直通 | 原信号 | 100kHz |
| 0 | 1 | 通道 10 | 输入 / 10 | 1MHz |
| 1 | 0 | 通道 100 | 输入 / 100 | 10MHz |
切换选择端要用代码操作两个 IO,不能靠硬件自动跳。也就是说,量程判断必须在软件里完成,分频器只是执行机构。切挡时注意等当前 1 秒闸门结束再改 P1.0/P1.1,不然闸门中段混入不同分频比,计数值既不是原频率也不是分频后的频率。
#define DIV_BYPASS 0 #define DIV_10 1 #define DIV_100 2 sbit SEL_A = P1^0; sbit SEL_B = P1^1; void set_prescaler(unsigned char div) { SEL_A = div & 0x01; // 74LS153的A选择位 SEL_B = (div >> 1) & 0x01; // 74LS153的B选择位 }这样写的好处是上层逻辑只需要传DIV_BYPASS、DIV_10、DIV_100,不需要关心引脚电平组合。如果后面换成 STC 或其他 51 单片机,只要把 P1.0/P1.1 的 sbit 定义改掉即可。
3.3 74LS273 锁存显示与小数点控制
显示部分用 6 片 74LS273 配 6 个数码管。AT89C52 的 P0 口分时送出段码,P2 口送出锁存信号,让每一片 74LS273 在上升沿锁存当前 P0 数据。锁存之后,即使 P0 去传送下一位,数码管也不会闪,这就是文档里强调的“锁存输出”比动态扫描稳定的原因。
显示刷新可以按位写:先送段码到 P0,再选中对应锁存器并给一个上升沿。工程里常用一个数组保存当前 6 位数字。
unsigned char code seg_tab[10] = { 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F }; void update_display(unsigned char *digits) { unsigned char i; for (i = 0; i < 6; i++) { P0 = seg_tab[digits[i]]; // 数字转段码 P2 = (P2 & 0xF8) | i; // 选中第i个锁存器 P2 = (P2 & 0xF8) | (i | 0x08); // CLK上升沿锁存 P2 = (P2 & 0xF8) | i; // 拉低CLK,准备下一个 } }这里的思路是用 P2 低三位选锁存器编号,P2.3 当 CLK 使用。先给数据,再给一个上升沿,数据就被锁住。段码表对应共阴数码管 a-g-dp,如果换共阳数码管,需要把表中每个值按位取反。小数点的位置由 P1.2、P1.3、P1.4 控制,和分频器选择端的原理一样,不放进刷新函数里处理。如果改用 LCD,锁存逻辑就不需要了,改为把频率值按位拆成字符直接写 LCD 模块。
4. 频率计固件实现:1s 闸门、自动量程与脉宽测量
固件最需要注意的是状态切换。测频和测脉宽用的是同一个定时器模块,如果不先把中断关掉或者把寄存器恢复,很容易出现上一档遗留的初值污染下一档的问题。下面按主循环、闸门计数、自动量程、脉宽测量四段展开。
4.1 主循环的按键入口:freq 和 pwide 怎么分配
两个按键分别对应测频和测脉宽。按键按下时引脚为低,释放时为高。为了保证按键只在按下瞬间触发一次,通常在主循环里做一次电平判断加一次延时确认。
#define KEY_FREQ P3_2 #define KEY_PWIDE P3_3 unsigned char mode; void key_scan(void) { if (KEY_FREQ == 0) { delay_10ms(); if (KEY_FREQ == 0) { mode = MODE_FREQ; // 进入频率测量 } } if (KEY_PWIDE == 0) { delay_10ms(); if (KEY_PWIDE == 0) { mode = MODE_PWIDTH; // 进入脉宽测量 } } }延时确认是简单的软件消抖,10ms 已经能滤掉大部分机械抖动。如果按键接触电阻偏大,可以改成 20ms。进入对应模式后,主循环分别调用freq_measure_once()和pulse_width_measure_once(),两个函数不要同时运行。
4.2 10ms 定时中断拼出 1 秒闸门
文档里写的闸门时间是 1 秒,刷新时间也是 1 秒。一种标准做法是让 T0 每 10ms 中断一次,软件计数到 100 次后认为 1 秒闸门结束。这样比直接定时 1 秒更稳,中断里的固定开销也会被平均掉。
unsigned int t0_cnt; bit gate_done; void timer0_isr(void) interrupt 1 { TH0 = 0xD8; // 重新装入10ms初值 TL0 = 0xF0; if (++t0_cnt >= 100) { t0_cnt = 0; gate_done = 1; // 1秒闸门到 } } unsigned long get_count(void) { unsigned long n; TR1 = 0; // 先停T1,避免读取过程中变化 n = (unsigned long)ovf_cnt * 65536UL + ((unsigned int)TH1 << 8 | TL1); TR1 = 1; return n; }读取 T1 计数时先停TR1,这是很多人容易漏掉的一步。如果不先停,TH1/TL1 可能在读取期间继续加 1,导致高字节和低字节不是同一个时刻的值。更稳的顺序是“停计数器、读 TH1、读 TL1、恢复计数器”,这一点在 51 单片机定时器相关课程里经常被忽略。
4.3 自动量程:先低挡试测,超阈值再升挡
测频的主循环先从旁路挡开始,计数 1 秒。如果raw超过 100000,就升到 10 分频挡,再重新计一个 1 秒闸门。判断阈值用的不是最终频率,而是进入 T1 引脚的原始计数值。
| 当前挡位 | 判断条件 | 动作 |
|---|---|---|
| 旁路 | raw > 100000 | 切 10 分频,重新计数 |
| 10 分频 | raw > 100000 | 切 100 分频,重新计数 |
| 10 分频 | raw < 90000 | 降回旁路,带 10% 回差 |
unsigned long raw, freq; unsigned char range = DIV_BYPASS; while (1) { set_prescaler(range); // start_gate():清ovf_cnt、TH1/TL1,置gate_done=0,开TR0/TR1 start_gate(); wait_gate_done(); raw = get_count(); if (range == DIV_BYPASS && raw > 100000UL) { range = DIV_10; // 输入超过100kHz,升到10分频 continue; // 重新开始一次完整闸门 } if (range == DIV_10 && raw > 100000UL) { range = DIV_100; // 超过1MHz,升到100分频 continue; } break; // 当前挡位计数合适 } switch (range) { case DIV_BYPASS: freq = raw; break; case DIV_10: freq = raw * 10UL; break; case DIV_100: freq = raw * 100UL; break; }这里的升挡逻辑在高频时会占用两个以上的 1 秒周期。比如一个 5MHz 输入信号,第一秒在旁路挡计数,发现超限;第二秒在 10 分频挡计数,可能还是超限;第三秒在 100 分频挡才读到正确值。也就是说,自动量程的刷新时间不总是 1 秒,这是“自动量程”和“1 秒刷新”之间必须做的取舍。如果课程设计要求严格 1 秒刷新,可以把第一秒当成预扫描不显示,第二秒直接显示结果。
4.4 脉宽测量:利用 INT0 的电平门控
脉宽测量用 T0 的门控方式,GATE=1 时,只有 INT0 为高电平,T0 才计数。要测一个高脉冲宽度,流程是等待信号变低,清空计数器,开 TR0,然后等上升沿开始计数,等下降沿停止。
unsigned long measure_pulse_us(void) { unsigned long cnt; TMOD &= 0xF0; // 只动T0,保留T1原来的配置 TMOD |= 0x09; // T0方式1,GATE=1 TH0 = 0x00; // 计数值清零 TL0 = 0x00; while (INT0 == 1); // 如果当前是高电平,先等低 TR0 = 1; // 允许T0计数,但INT0为低时不会动作 while (INT0 == 0); // 等待上升沿,计数自动开始 while (INT0 == 1); // 等待下降沿,计数自动停止 TR0 = 0; cnt = ((unsigned int)TH0 << 8) | TL0; return cnt; // 12MHz晶振下单位是us }这段代码里while (INT0 == 1)的作用很重要。如果测频刚结束,INT0 上还留着高电平,直接开门会导致后半段高电平也被计进去。先等低电平,才能保证测量的起点是上升沿。返回的cnt直接就是微秒数。测完后必须把 TMOD 恢复到0x51,否则下一次测频会用脉宽模式。
注意:INT0 输入不能直接接高压信号。被测电压超过 5V 时,先经过前面 3.1 节的放大整形再进 INT0,否则会损坏单片机引脚。
5. 频率计误差校验和量程切换回差技巧
频率计做完后不能只看信号源上的数字。我一般会把信号源和频率计接到同一个输出端,从低频到高频选几个点,读出显示值并计算差值。文档里最清晰的实测点是 567.890kHz,显示为 567.888kHz,相差 2Hz,相对误差约 0.00000352%。这个误差主要来自 ±1 计数误差,也就是闸门开启时刻和输入脉冲边沿不同步造成的计数差。
| 信号源频率 | 频率计显示 | 绝对误差 | 相对误差 |
|---|---|---|---|
| 567.890kHz | 567.888kHz | 2Hz | 0.00000352% |
| 理论 ±1 计数误差 | - | - | ≤ 1 / 567888 ≈ 0.000176% |
2Hz 是实测误差,理论 ±1 误差反而更大,说明晶振和整形电路在这个频点没有额外拖后腿。频率越高,计数 N 越大,相对误差越小,这也是高频档位误差百分比反而不高的原因。
5.1 加一个回差值,避免量程边界反复跳档
自动量程最容易出问题的位置是 100kHz 和 1MHz 附近。信号源输出正好在边界上下抖动时,频率计会在两个量程之间反复切换,显示值来回跳。解决方法是在判断线上加一个迟滞区间:升挡还是按 100000,但降挡要低于 90000 才执行。
if (range == DIV_BYPASS && raw > 100000UL) { range = DIV_10; continue; } if (range == DIV_10 && raw < 90000UL) { range = DIV_BYPASS; // 不是低于100000立刻降挡 continue; } if (range == DIV_10 && raw > 100000UL) { range = DIV_100; continue; } if (range == DIV_100 && raw < 90000UL) { range = DIV_10; continue; }90000 这个值按当前挡位的原始计数值判断,相当于给量程边界留了 10% 回差。回差大小可以根据稳定度需求调整,一般取量程上限的 5% 到 10%。如果现场信号本身有缓慢漂移,可以把 90000 改到 85000;如果信号很稳,则可以改到 95000,减少换挡次数。
5.2 现场调试时优先检查的四个地方
第一,确认分频后的信号确实进了 T1 而不是 T0。本方案里 T0 用于定时,接错引脚会导致计数永远为零或直接卡死。第二,用示波器看 74LS153 的输出端,分频比必须和 P1.0/P1.1 的电平组合一致。第三,换挡后不要立即读计数值,等一个完整的闸门周期再显示。第四,测脉宽模式下如果 INT0 浮空,GATE 逻辑会被噪声触发,要么接上被测信号,要么先让 P3.2 输出高电平。把这些检查项走完,比反复改软件更容易定位问题。
最后一个技巧:把挡位、分频系数和回差阈值整理成一个结构体,比如{range, divisor, up_th, down_th}。固件升级或换晶振时只改这一组表,不需要在中断服务程序里翻逻辑。这样调完一个点,剩下的点基本都能一次通过。
本文还有配套的精品资源,点击获取