STM8开发实战:HC-SR04超声波测距仪

前言

  • 在经过前期理论的学习,懂得如何使用单片机的各项外设之后,我们便可以想做什么就做什么了,本文以制作一个基于HC-SRO4超声波测距模块的测距仪为例子,演示怎样通过单片机的各项外设来控制各种功能的外围电路,从而实现我们想要的各种功能
  • 本程序已开源,提供了STM8S105C6与STM8S003F3两个版本的代码,开源地址:https://github.com/artless-artist/STM8_UltrasonicRangefinder

准备工作

硬件

  • HC-SR04超声波测距模块
  • 0.96寸OLED屏幕(集成了SSD1306显示驱动芯片)
  • STM8S,推荐使用STM8S105C6这样的高级型号,若使用内存较小的型号会遇到开发困难

软件

  • 使用IAR开发

HR-SR04超声波测距模块

工作原理

  • 超声波测距原理
    超声波模块会在触发测距后发射8个40khz的方波(这是为了让声波独特,避免与环境中的其他信号混淆),从声波接触到被测物体时就会被反射回来,被模块接收
    那么,从发射声波到接收到反射声波的用时除以2就是声音从模块传播到被测物体所用的时间,再将这个时间乘以声速就是距离了
  • HC-SR04工作时序
    1.在TRIG引脚上给至少10us的高电平信号后触发测距;
    2.触发测距后模块自动发送8个40khz的方波,自动检测是否有信号返回;
    3.若有信号返回,通过ECHO引脚输出一个高电平,高电平持续的时间就是超声波从发射到返回的时间。

STM8控制原理

  • 设计思路
    • 从工作时序来看,我们需要让STM8的一个引脚作为TRIG,输出高电平触发测距;另外需要一个ECHO引脚被配置为输入模式,用以测量超声波模块的ECHO引脚返回的高电平持续时间
    • 测量ECHO引脚高电平持续时间是整个功能实现的核心,我们很容易想到通过STM8的定时器外设来实现这一点,具体方法如下:
  • 使用基本型定时器
    • 使用任意定时器的计时功能,在返回高电平信号后开始计时,当返回信号变回低电平后结束计时,并计算
    • 由于适用轮询的数据传输方式,将单片机主频设置为16MHz以提高运行速度,而又由于计数值有限,为了扩展测量范围,设定TIM4预分频系数为128,让计数值每8us加一
    //使用TIM4进行计时
    void TIM4_Init(void)
    {
    	TIM4_PSCR = 0x07;   // 预分频器128,16MHz/128=125kHz(8us)
    	TIM4_ARR = 255;     // 自动重装载值
    	TIM4_CNTR = 0;      // 计数器清零
    	TIM4_CR1 |= 0x01;   // 使能计数器
    }
    //详细的宏定义、初始化代码受限篇幅省略,需要完整驱动请到开源地址中查看
    float HC_SR04_GetDistance(void)
    {
    	float time = 0;
    	float distance = 0;
    
    	// 发送10us的高电平触发信号
    	TRIG = 1;
    	Delay_us(15);       // 延时一段时间保证触发测距
    	TRIG = 0;
    
    	// 等待回波信号变高
    	while(ECHO == 0);
    	// 开始计时
    	TIM4_CNTR = 0;
    	// 等待回波信号变低
    	while(ECHO == 1);
    
    	// 获取计时值
    	time = TIM4_CNTR * 8;
    	distance = time * (0.0331 + 0.00006*temperature) /2;
    	//声速加入温度补偿系数后的计算公式,单位为厘米
    	return distance;
    }
    
  • 使用高级计时器的输入捕获功能
    • 我们的需求是在对信号的高电平持续时间进行测量,也就是在低电平变为高电平时从零开始计时,而高电平变回低电平时停止计时读数,这显然与我们曾学习过的定时器输入捕获与复位触发功能不谋而合,我们曾用这个功能进行信号占空比的测量
    //使用TIM1输入捕获的复位触发模式对echo引脚返回的高电平持续时间进行测量
    void TIM1_Init(void)
    {
    	TIM1_PSCRH = 0x00;
    	TIM1_PSCRL = 0x02;
    	TIM1_ARRH = 0xFF;
    	TIM1_ARRL = 0xFF;
    
    	//利用输入时TIx产生的两路相同信号TIxFP1和TIxFP2,对上边沿与下边沿进行检测
    	TIM1_CCMR1 = 0x01;  //CC1S=01 TI1FP1连接到IC1
    	TIM1_CCMR2 = 0x02;  //CC2S=10 TI1FP2连接到IC2
    	TIM1_CCER1_CC1P = 0;        //检测TI1FP1上升沿
    	TIM1_CCER1_CC2P = 1;        //检测TI1FP2下降沿
    	TIM1_SMCR = 0x54;   //TS=101(TI1FP1为触发信号), SMS=100(复位触发模式)
    	//从TI1的上升沿触发复位,在捕获到TI2的下降沿时
    	TIM1_CCER1 |= 0x11;    //使能两个捕获
    
    	TIM1_CR1 = 0x01;    // 使能计数器
    }
    
    float HC_SR04_GetDistance(void)
    {
    	TIM1_SR1 &= 0xF9; //清除CC1IF和CC2IF标志位
    	TIM1_CCER1 |= 0x11; //开启捕获功能
    
    	float time = 0;
    	float distance = 0;
    	u16 pulse_width = 0;
    
    	// 发送高电平触发信号
    	TRIG = 1;
    	Delay_us(15);
    	TRIG = 0;
    
    	while((TIM1_SR1&0x02)==0); //等待捕获比较标志位CC1IF变为1(上升沿)
    	while((TIM1_SR1&0x04)==0); //等待捕获比较标志位CC2IF变为1(下降沿)
    
    	//IC1检测到上升沿时复位计时,到IC2检测到下降沿时即高电平脉宽
    	pulse_width = (u16)TIM1_CCR2H<<8; //取高电平脉宽的高8位
    	pulse_width |= TIM1_CCR2L; //取高电平脉宽的低8位
    
    	time=(float)(pulse_width)*0.1875;//主频=16M,TIM1进行三分频
    	distance = time * (0.0331 + 0.00006*temperature) /2;
    
    	return distance;
    }
    

OLED(SSD1306驱动)屏幕

工作原理

  • 为了简化开发,我们直接选用已经集成了显示驱动的OLED屏幕,商家已经提供了驱动库文件,调用其中函数即可进行显示,我们只需要对其进行修改使其适用于我们所用的单片机型号
  • 为了节省引脚,我选用了使用IIC通信的版本,只需要占用两个引脚

STM8控制原理

  • STM8具体控制原理本文不再阐述,在之前的文章中对IIC通信已有具体介绍
  • 模拟IIC与硬件IIC
    尽管在我的博文中使用了硬件IIC,但在实际开发时我使用了GPIO软件模拟IIC的工作时序,一来STM8的硬件IIC代码资料不多不易于开发,二来方便移植到更多没有硬件IIC的平台上
    但是,这样就带来一个问题:如果配置了其他中断,中断会打断软件模拟的IIC时序,导致OLED屏幕无法正常显示,这也就是为什么在前文HC-SR04的驱动不使用定时器捕获中断进行更精确的计时;使用硬件IIC并且合理配置中断优先级能够解决这个问题
  • 内存占用问题
    本项目直接使用了硬件生产商提供的字库文件,但在诸如STM8S003这类入门级的单片机中存储空间不足,过大的字库会导致烧录失败,为此,我对原本的字库文件进行简化(只能显示6×8的符号与数字),并在头文件中将宏定义SIZE的值修改为6以使用更小(所需要的像素更少)的字体,可在开源地址中下载

成品展示

  • 使用开发板与杜邦线搭建的测试平台
    image

  • 成品运行效果展示(OLED屏幕有屏闪,在肉眼观察时显示正常)
    image

posted on 2025-04-30 19:06  无术师  阅读(173)  评论(0)    收藏  举报