蓝桥杯单片机学习笔记(九)——超声波测距与NE555频率测量
蓝桥杯单片机学习笔记(九)——超声波测距与NE555频率测量
温馨提示:内容源自米醋电子工作室培训课,由本人总结
一、超声波测距模块:单片机的"电子眼"
超声波测距就像蝙蝠的回声定位系统——发出声波,等待回声,通过时间差计算距离。在蓝桥杯竞赛中,官方资源包并未提供超声波底层驱动,需要我们自己编写。
1.1 硬件连接与引脚定义
查阅官方原理图后,定义超声波模块的两个关键引脚:
sbit Tx = P1^0; // 发射端(Transmitter)
sbit Rx = P1^1; // 接收端(Receiver)
引脚功能说明
- Tx(发射端):发送超声波信号,像是"喇叭"
- Rx(接收端):接收回波信号,像是"麦克风"
1.2 测距原理:声波的往返旅程
超声波测距的核心原理是时间差测距法,就像你在山谷中喊话,通过回声返回的时间判断距离。
1.2.1 物理公式推导
基本公式
距离 = 速度 × 时间 ÷ 2
为什么要除以2?
- 超声波需要"去"和"回"两段路程
- 测量的时间是往返总时间
- 实际距离只是单程
代入声速计算
- 声速:v = 340 m/s = 34000 cm/s
- 距离公式:d = v × t ÷ 2
- 简化结果:d = 34000 × t ÷ 2 = 17000 × t (cm)
- 最终公式:d = 0.017 × t (cm)
其中t的单位是微秒(μs)
1.2.2 测距流程图解
[发射8个40kHz方波] → [等待回波] → [计算时间差] → [转换为距离]
↓ ↓ ↓ ↓
Tx引脚输出 Rx引脚检测 PCA定时器 d=0.017×t
1.3 超声波信号发射
1.3.1 为什么是40kHz?
超声波模块的谐振频率是40kHz,就像音叉有固定的振动频率,只有这个频率才能让模块高效工作。
40kHz方波参数计算
- 频率:f = 40kHz = 40000Hz
- 周期:T = 1/f = 1/40000 = 25μs
- 半周期:T/2 = 12.5μs ≈ 12μs
方波时序图
Tx引脚电平:
___ ___ ___ ___
___| |___| |___| |___| |___
12μs 12μs (重复8次)
1.3.2 延时函数生成
使用STC-ISP软件生成精确的12μs延时函数:
配置参数
- 系统频率:12MHz
- 指令集:Y1/STC89
- 延时时间:12μs
生成代码
void Delay12us(void) // @12.000MHz
{
unsigned char data i;
_nop_();
i = 3;
while (--i);
}
代码说明
_nop_():空操作指令,精确延时i = 3:循环3次,配合空操作达到12μsdata关键字:将变量存储在data区,提高访问速度
1.3.3 超声波发射函数
// 超声波初始化函数:发射8个40kHz方波
void Ut_Wave_Init()
{
unsigned char i;
EA = 0; // 关闭全局中断,防止时序被打断
// 发射8个完整的方波周期
for(i = 0; i < 8; i++)
{
Tx = 1; // 拉高电平
Delay12us(); // 保持12μs
Tx = 0; // 拉低电平
Delay12us(); // 保持12μs
}
EA = 1; // 恢复全局中断c
}
为什么发射8个方波?
- 1个方波能量太弱,接收端可能检测不到
- 8个方波形成"脉冲串",增强信号强度
- 就像敲门要敲几下才能让人听见
为什么要关闭中断?
- 40kHz方波时序要求极其精确
- 中断会打断延时,导致频率偏移
- 就像演奏乐器时不能被打断,否则节奏会乱
1.4 超声波接收与距离计算
1.4.1 为什么使用PCA定时器?
在蓝桥杯竞赛中,定时器资源非常紧张:
定时器占用情况
- 定时器0:被NE555强制占用(频率测量)
- 定时器1:串口通信占用(如果考到串口)
- 定时器2:函数轮询和系统时基
- PCA定时器:独立的可编程计数器阵列,可用于超声波
PCA定时器优势
- 独立于传统定时器,不冲突
- 16位计数范围,精度高
- 可以测量脉冲宽度
1.4.2 PCA定时器寄存器说明
CMOD = 0x00; // PCA计数器模式寄存器
// 0x00:使用系统时钟/12作为计数时钟
CH = 0; // PCA计数器高8位
CL = 0; // PCA计数器低8位
CR = 1; // PCA计数器运行控制位
// 1:启动计数 0:停止计数
CF = 0; // PCA计数器溢出标志
// 1:计数溢出 0:未溢出
计数时钟计算
- 系统时钟:12MHz
- PCA时钟:12MHz ÷ 12 = 1MHz
- 计数周期:1/1MHz = 1μs
- 即:每计数1次 = 1μs
1.4.3 距离测量函数详解
// 超声波数据获取函数:返回测量距离(单位:cm)
unsigned char Ut_Wave_Data()
{
unsigned int time; // 存储往返时间(μs)
// 步骤1:初始化PCA定时器
CMOD = 0x00; // 设置PCA工作模式
CH = CL = 0; // 清零计数器
// 步骤2:发射超声波
Ut_Wave_Init();
// 步骤3:启动计时
CR = 1;
// 步骤4:等待回波或超时
// Rx == 1:未接收到回波(接收端空闲为高电平)
// CF == 0:计数器未溢出(未超时)
while(Rx == 1 && CF == 0);
// 步骤5:停止计时
CR = 0;
// 步骤6:判断是否成功接收
if(CF == 0) // 未溢出,说明接收到回波
{
// 读取计数值(往返时间)
time = (CH << 8) | CL; // 合并高低8位
// 转换为距离:d = 0.017 × t
return (time * 0.017);
}
else // 溢出,说明超出测量范围
{
CF = 0; // 清除溢出标志
return 0; // 返回0表示测量失败
}
}
代码关键点解析
1. 时间合并操作
time = (CH << 8) | CL;
CH << 8:将高8位左移8位| CL:与低8位进行或运算- 例如:CH=0x12, CL=0x34 → time=0x1234
2. 等待条件判断
while(Rx == 1 && CF == 0);
- 两个条件同时满足才继续等待
- 任一条件不满足就跳出循环
- 就像等人:人没来且没超时就继续等
3. 距离计算
return (time * 0.017);
- time单位是μs
- 0.017是声速换算系数
- 结果单位是cm
1.4.4 测量范围与精度
理论测量范围
- PCA计数器:16位 = 65535
- 最大时间:65535μs ≈ 65.5ms
- 最大距离:65535 × 0.017 ≈ 1114cm ≈ 11m
实际测量范围
- 有效范围:2cm ~ 400cm
- 小于2cm:盲区,回波与发射重叠
- 大于400cm:信号衰减严重,难以接收
测量精度
- 时间精度:1μs
- 距离精度:0.017cm ≈ 0.17mm
- 实际精度:约±1cm(受环境影响)
1.5 完整驱动代码
1.5.1 ultrasound.h 头文件
#ifndef __ULTRASOUND_H__
#define __ULTRASOUND_H__
#include <STC15F2K60S2.H>
// 对外接口函数
unsigned char Ut_Wave_Data(void); // 获取超声波测距数据(单位:cm)
#endif
1.5.2 ultrasound.c 源文件
/* 超声波测距驱动代码
适用于蓝桥杯单片机竞赛
测量范围:2cm ~ 400cm
测量精度:±1cm
*/
#include "ultrasound.h"
#include <intrins.h>
// 引脚定义
sbit Tx = P1^0; // 发射端
sbit Rx = P1^1; // 接收端
// 12μs精确延时函数
void Delay12us(void) // @12.000MHz
{
unsigned char data i;
_nop_();
i = 3;
while (--i);
}
// 超声波发射函数:发射8个40kHz方波
void Ut_Wave_Init(void)
{
unsigned char i;
EA = 0; // 关闭全局中断
for(i = 0; i < 8; i++)
{
Tx = 1;
Delay12us();
Tx = 0;
Delay12us();
}
EA = 1; // 恢复全局中断
}
// 超声波数据获取函数:返回测量距离(单位:cm)
unsigned char Ut_Wave_Data(void)
{
unsigned int time;
// 初始化PCA定时器
CMOD = 0x00;
CH = CL = 0;
// 发射超声波
Ut_Wave_Init();
// 启动计时
CR = 1;
// 等待回波或超时
while(Rx == 1 && CF == 0);
// 停止计时
CR = 0;
// 判断是否接收到回波
if(CF == 0)
{
time = (CH << 8) | CL;
return (time * 0.017);
}
else
{
CF = 0;
return 0;
}
}
1.6 使用示例
#include "ultrasound.h"
void main(void)
{
unsigned char distance;
while(1)
{
// 获取距离数据
distance = Ut_Wave_Data();
// 判断测量是否有效
if(distance > 0)
{
// 显示距离
Display_Distance(distance);
}
else
{
// 显示错误信息
Display_Error();
}
Delay_ms(100); // 每100ms测量一次
}
}
使用注意事项
- 测量频率不宜过高,建议间隔100ms以上
- 测量时避免障碍物倾斜角度过大
- 软质材料(如海绵)会吸收超声波,影响测量
- 测量距离为0时表示测量失败或超出范围
二、NE555频率测量:单片机的"频率计"
NE555是一款经典的定时器芯片,在蓝桥杯中用于产生可调频率的方波信号。我们需要用单片机测量这个频率,就像用频率计测量信号频率一样。
2.1 硬件连接与引脚冲突
2.1.1 引脚共用问题
重要提示:NE555的输出信号连接到P3.4引脚,这个引脚与独立按键S4共用!
引脚冲突示意图
P3.4 ─┬─ NE555信号输入(SIGNAL)
│
└─ 独立按键S4
解决方案
- 如果题目考到NE555频率测量,必须屏蔽S4按键功能
- 在按键扫描函数中,跳过P3.4的检测
- 或者直接不使用S4按键
2.1.2 硬件跳线连接
关键操作:在开发板上,必须用跳线帽连接P34和SIGNAL两个引脚!
[P34] ←→ [SIGNAL] (用跳线帽短接)
忘记连接的后果
- 无法测量到频率
- 测量值始终为0
- 这是最常见的硬件错误
2.2 频率测量原理
2.2.1 测频原理:数脉冲
频率测量的本质是在固定时间内计数脉冲个数,就像数1秒钟内心跳了多少次。
测频公式
频率 = 脉冲个数 / 时间
实现方法
- 使用定时器0的计数模式
- 每来一个脉冲,计数器加1
- 每隔1秒读取一次计数值
- 读取后清零,重新计数
2.2.2 定时器0的两种模式
定时模式(Timer Mode)
- 计数内部时钟脉冲
- 用于产生精确延时
- 我们之前一直在用
计数模式(Counter Mode)
- 计数外部引脚(T0,即P3.4)的脉冲
- 用于频率测量
- NE555测量需要用这个模式
2.3 定时器0配置
2.3.1 TMOD寄存器配置
TMOD |= 0x05;
TMOD寄存器位定义
TMOD = 0x05 = 0000 0101
││││ ││││
││││ │││└─ M0 = 1 ┐
││││ ││└── M1 = 0 ┴─ 模式1(16位)
││││ │└─── C/T = 1 ─── 计数模式
││││ └──── GATE = 0 ── 不使用门控
││││
│││└────── M0 = 0 ┐
││└─────── M1 = 0 ┴─ 定时器1配置
│└──────── C/T = 0
└───────── GATE = 0
关键位说明
- C/T = 1:选择计数模式(Counter)
- M1M0 = 01:16位计数器模式
- GATE = 0:不使用门控功能
2.3.2 完整初始化代码
使用STC-ISP生成定时器1的1ms定时,然后修改定时器0为计数模式:
/* 定时器初始化函数 */
void Timer0_Init(void) // 定时器0:计数模式
{
AUXR &= 0x7F; // 定时器时钟12T模式
TMOD &= 0xF0; // 清除定时器0配置位
TMOD |= 0x05; // 设置为计数模式,16位,不自动重装
TL0 = 0x00; // 计数器清零
TH0 = 0x00; // 计数器清零
TF0 = 0; // 清除溢出标志
TR0 = 1; // 启动定时器0
}
void Timer1_Init(void) // 定时器1:1ms定时
{
AUXR &= 0xBF; // 定时器时钟12T模式
TMOD &= 0x0F; // 清除定时器1配置位
TMOD |= 0x10; // 设置为定时模式,16位
TL1 = 0x18; // 设置定时初值
TH1 = 0xFC; // 设置定时初值
TF1 = 0; // 清除TF1标志
TR1 = 1; // 定时器1开始计时
ET1 = 1; // 使能定时器1中断
EA = 1; // 开启总中断
}
初始化顺序要求
void main(void)
{
Timer0_Init(); // 1. 先初始化定时器0(计数器)
Timer1_Init(); // 2. 再初始化定时器1(时基)
while(1)
{
// 主循环
}
}
为什么要先初始化定时器0?
- 定时器0需要先准备好接收脉冲
- 定时器1启动后立即开始计时
- 顺序错误可能导致第一次测量不准
2.4 频率读取与显示
2.4.1 全局变量定义
unsigned char Freq = 0; // 存储测量到的频率值(单位:Hz)
unsigned int Time_1s = 0; // 1秒计时器
bit Freq_Flag = 0; // 频率有效标志位
变量说明
- Freq:存储频率测量结果
- Time_1s:累计定时器1中断次数,达到1000次=1秒
- Freq_Flag:防止第一次测量误差的标志位
2.4.2 定时器1中断服务函数
/* 定时器1中断服务函数:每1ms执行一次 */
void Timer1_ISR(void) interrupt 3
{
// 1秒计时
if(++Time_1s == 1000) // 1000ms = 1s
{
Time_1s = 0; // 清零计时器
// 读取定时器0的计数值(即频率)
Freq = (TH0 << 8) | TL0;
// 清零定时器0,准备下一次计数
TH0 = 0;
TL0 = 0;
// 拉高频率有效标志
Freq_Flag = 1;
}
}
代码执行流程
每1ms中断一次 → Time_1s++ → 达到1000次?
↓ 是
读取TH0和TL0 → 存入Freq
↓
清零TH0和TL0 → 重新计数
↓
Freq_Flag = 1 → 数据有效
2.4.3 频率显示函数
/* 数码管显示函数 */
void Display_Frequency(void)
{
// 只有标志位为1时才显示频率
if(Freq_Flag == 1)
{
// 显示频率值
Display_Number(Freq);
}
else
{
// 显示等待信息或0
Display_Number(0);
}
}
为什么需要Freq_Flag标志位?
问题分析
- 定时器0在4T模式下,第一次测量可能不准确
- 原因:定时器0启动时刻与定时器1不同步
- 第一次测量的时间可能不足1秒
解决方案
- 使用标志位Freq_Flag
- 第一次1秒到达时,拉高标志位
- 只有标志位为1时才显示频率
- 这样确保显示的都是完整1秒的测量结果
时序示意图
定时器1启动 ─┬─ 0ms
│
定时器0启动 ─┼─ 可能有延迟
│
第一次1秒 ───┼─ 1000ms(数据可能不准)
│ Freq_Flag = 1
│
第二次1秒 ───┼─ 2000ms(数据准确)
│ 开始显示
↓
2.5 完整应用示例
#include <STC15F2K60S2.H>
// 全局变量
unsigned char Freq = 0;
unsigned int Time_1s = 0;
bit Freq_Flag = 0;
// 定时器初始化
void Timer0_Init(void)
{
AUXR &= 0x7F;
TMOD &= 0xF0;
TMOD |= 0x05; // 计数模式
TL0 = 0x00;
TH0 = 0x00;
TF0 = 0;
TR0 = 1;
}
void Timer1_Init(void)
{
AUXR &= 0xBF;
TMOD &= 0x0F;
TMOD |= 0x10; // 定时模式
TL1 = 0x18;
TH1 = 0xFC;
TF1 = 0;
TR1 = 1;
ET1 = 1;
EA = 1;
}
// 定时器1中断服务函数
void Timer1_ISR(void) interrupt 3
{
if(++Time_1s == 1000)
{
Time_1s = 0;
Freq = (TH0 << 8) | TL0;
TH0 = TL0 = 0;
Freq_Flag = 1;
}
}
// 主函数
void main(void)
{
// 初始化(顺序不能错)
Timer0_Init(); // 先初始化计数器
Timer1_Init(); // 再初始化时基
while(1)
{
// 显示频率
if(Freq_Flag)
{
Display_Number(Freq);
}
// 其他功能...
}
}
2.6 常见问题与调试技巧
2.6.1 测量值始终为0
可能原因
- 忘记用跳线帽连接P34和SIGNAL
- 定时器0配置错误,未设置为计数模式
- NE555电路未工作
排查步骤
// 1. 检查硬件连接
// 确认跳线帽已连接
// 2. 检查TMOD配置
if((TMOD & 0x0F) == 0x05)
{
// 配置正确
}
// 3. 检查定时器0是否运行
if(TR0 == 1)
{
// 定时器0已启动
}
2.6.2 第一次测量不准确
原因分析
- 定时器0和定时器1启动时刻不同步
- 第一个1秒周期可能不完整
解决方法
- 使用Freq_Flag标志位
- 第一次测量后才开始显示
- 代码示例见2.4.3节
2.7 测量范围与精度
理论测量范围
- 定时器0:16位 = 65535
- 最大频率:65535 Hz ≈ 65.5 kHz
实际测量范围
- 有效范围:10 Hz ~ 10 kHz
- 低于10Hz:测量时间过长,响应慢
- 高于10kHz:可能超出计数范围
测量精度
- 时间精度:±1ms(定时器1误差)
- 频率精度:±1Hz
- 相对误差:1/频率值
更多推荐



所有评论(0)