前言:
使用的硬件:STM32F103C8T6,HC-SR04,ST-Link(其他烧录器也可以),0.96寸OLED屏幕(非必须,仅供显示测距结果,可以使用串口助手代替),若干杜邦线。
涉及操作stm32的GPIO口,外部中断,定时器,本文中不会详细解释,仅提供代码思路。
HC-SR04:
HC-SR04超声波测距模块提供2cm~400cm的测距功能,精度达3mm。
以下图片截取自深圳市捷深科技有限公司的《HC-SR04超声波测距模块说明书》
通过时序图我们可以知道,我们给HC-SR04发送长达10us的TTL脉冲,然后模块就会进行测距,测距的结果通过回响信号传达,回响的TTL电平信号时间即是超声波从HC-SR04模块发出,触碰到障碍物后返回到HC-SR04模块的时间总和。
TTL是逻辑电平标准,当电压达到2.4V~5V之间,那么为逻辑1(高电平),电压在0V~0.4V之间,那么为逻辑0(低电平)。所以我们可以直接通过GPIO口来输出以及输入时序所需的电平信号。
总所周知,声音的速度为340m/s,因此我们将回响电平的时间除340再除2之后得到的就是单位为米的测距结果。
编写思路:
结合说明书我们可以知道,我们仅需提供10us的高电平给Trig口即可。然后HC-SR04在测量完毕之后会将结果通过Echo回响回来。
所以我们只需要将Trig口拉高,等待10us(最好再延长一些,代码中用的是15us)后再拉低即可。
接着就只需要等待Echo将数据传输回来,通过时序图我们可以得知回响信号是拉高Echo口,再拉低,中间持续的时间就是测距的结果。
所以我们给Echo口配置一个中断事件,设置为上跳变下跳变都触发,另外再用一个变量记录Echo口到底是拉高还是拉低即可。
如果是拉高,那么我们需要记录下持续的时间,这时候我们需要用定时器计时,所以需要在一开始的时候就配置好定时器的初始化。唯一的问题就是该如何配置定时器的预分频器和自动重装器了。
根据说明书我们可以知道HC-SR04的精度为3mm,而测距的公式为 us/58-cm,稍加计算可知,如果我们需要测量3mm,那么得到的时间为17.4us,以此为一个刻度,那么定时器的频率应该为57471Hz。然而这样太麻烦了,而且也不好用,因此我们可以随意一些,我在代码中使用的是预分频器为72,自动重装器为100,那么得到的频率为72MHz/72/100=1000Hz,也就是一次定时器中断的时间为100us,而自动重装器里的每一个值就是1us,所以每次外部中断的下降沿触发之后只需要将定时器触发的次数*100再加上自动重装器里的值就可以得到回响信号的持续时间了,单位是us。
接线:
HC-SR04需要单独提供5V的供电,因此不能与stm32共用一个VCC(3.3V),而ST-Link有5V的供电接口,因此我将5V电压单独拉出来给HC-SR04供电,GND与stm32用同一个。
HC-SR04的Trig接GPIOA的6号口,Echo接GPIOA的7号口。
接线端口没有硬性要求,只需要修改对应代码即可。需要注意的是修改GPIO口的同时还需要修改为对应的中断通道。
OLED的SCK接GPIOB的8号口,SDA接GPIOB的9号口。
精度还是可以的。
完整代码:
1 #include "stm32f10x.h" // Device header 2 #include "Delay.h" //使用的是b站江科大的延时函数,可以自己用别的替代延时功能 3 #include "OLED.h" //使用的是b站江科大的OLED驱动代码,用于展示测距结果,可以在相应的地方更换为串口通信展示到电脑的串口助手上 4 5 uint8_t flag=0; //用于记录中断信号是上升沿还是下降沿 6 uint32_t number=0; //记录定时器中断的次数 7 uint32_t times=0; //记录回响信号的持续时间 8 9 int main(void){ 10 OLED_Init(); 11 OLED_ShowString(1,1,"Hello World!!!"); 12 13 //初始化GPIO口,Trig使用推挽输出,Echo使用浮空输入 14 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //使能GPIOA的外设时钟 15 GPIO_InitTypeDef itd; 16 itd.GPIO_Mode=GPIO_Mode_Out_PP; //选择推挽输出模式 17 itd.GPIO_Pin=GPIO_Pin_6; //选择GPIO_Pin_6 18 itd.GPIO_Speed=GPIO_Speed_50MHz; //默认选择50MHz 19 GPIO_Init(GPIOA,&itd); 20 21 itd.GPIO_Mode=GPIO_Mode_IN_FLOATING; //选择浮空输入模式 22 itd.GPIO_Pin=GPIO_Pin_7; //选择GPIO_Pin_7 23 GPIO_Init(GPIOA,&itd); 24 25 //AFIO映射中断引脚 26 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //使能AFIO的外设时针 27 GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource1); //选择外部中断源和中断通道 28 29 //EXTI中断配置 30 EXTI_InitTypeDef itd1; 31 itd1.EXTI_Line=EXTI_Line7; //echo使用的端口7,因此选择7号中断线 32 itd1.EXTI_LineCmd=ENABLE; 33 itd1.EXTI_Mode=EXTI_Mode_Interrupt; 34 itd1.EXTI_Trigger=EXTI_Trigger_Rising_Falling; //上升沿和下降沿都触发中断 35 EXTI_Init(&itd1); 36 37 //NVIC分配外部中断的中断优先级 38 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //指定中断分组 39 NVIC_InitTypeDef itd2; 40 itd2.NVIC_IRQChannel=EXTI9_5_IRQn; //使用的端口7,因此选择这个参数 41 itd2.NVIC_IRQChannelCmd=ENABLE; 42 itd2.NVIC_IRQChannelPreemptionPriority=2; //抢占优先级 43 itd2.NVIC_IRQChannelSubPriority=2; //响应优先级 44 NVIC_Init(&itd2); 45 46 //配置定时器 47 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); 48 TIM_TimeBaseInitTypeDef itd3; 49 itd3.TIM_ClockDivision=TIM_CKD_DIV1; //使用时钟分频1 50 itd3.TIM_CounterMode=TIM_CounterMode_Up; //向上计数 51 //72MHz/72/100=1000,每秒定时器计数1000个,因此每个计数为100us 52 itd3.TIM_Period=72-1; //预分频系数 53 itd3.TIM_Prescaler=100-1; //自动重装器 54 itd3.TIM_RepetitionCounter=0; //该参数仅给高级定时器使用 55 TIM_TimeBaseInit(TIM2,&itd3); 56 TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //使能中断输出信号 57 TIM_InternalClockConfig(TIM2); //选择内部时钟 58 59 //NVIC分配定时器的中断优先级 60 NVIC_InitTypeDef itd4; 61 itd4.NVIC_IRQChannel=TIM2_IRQn; //指定Tim2的中断通道 62 itd4.NVIC_IRQChannelCmd=ENABLE; 63 itd4.NVIC_IRQChannelPreemptionPriority=1; //抢占优先级 64 itd4.NVIC_IRQChannelSubPriority=1; //响应优先级 65 NVIC_Init(&itd4); 66 67 uint32_t distance; 68 while(1){ 69 distance=0; 70 for(int i=0;i<10;++i){ //每次取10次测距数据,取平均值减少误差 71 GPIO_SetBits(GPIOA,GPIO_Pin_6); 72 Delay_us(15); //根据说明书,需要提供至少10us的高电平 73 GPIO_ResetBits(GPIOA,GPIO_Pin_6); 74 Delay_ms(65); //根据说明书,每个周期至少需要等待60ms 75 distance+=(times/5.8); //根据说明书提供的公式,获取单位为mm的距离 76 } 77 distance/=10; 78 OLED_ShowNum(2,1,distance,4); 79 } 80 } 81 82 //定时器中断函数 83 void TIM2_IRQHandler(void){ 84 if(SET==TIM_GetITStatus(TIM2,TIM_FLAG_Update)){ 85 number++; //每次中断将次数++ 86 TIM_ClearITPendingBit(TIM2,TIM_FLAG_Update); 87 } 88 } 89 90 //外部中断函数 91 void EXTI9_5_IRQHandler(void){ 92 if(SET==EXTI_GetITStatus(EXTI_Line7)){ 93 if(flag==0){ 94 //上升沿即回响电平开始,打开计数器 95 number=0;flag=1; 96 TIM_SetCounter(TIM2,0); 97 TIM_Cmd(TIM2,ENABLE); 98 99 }else{ 100 //下降沿即回响电平结束,统计高电平持续时长 101 TIM_Cmd(TIM2,DISABLE); 102 flag=0; 103 times=number*100+TIM_GetCounter(TIM2); //得到回响的高电平持续的us 104 } 105 EXTI_ClearITPendingBit(EXTI_Line7); 106 } 107 }
参考:
《HC-SR04超声波测距模块说明书》—— 深圳市捷深科技有限公司
b站江科大
STM32驱动HC-SR04超声波测距模块测距_哔哩哔哩_bilibili
完整的工程文件在这边:
链接:https://pan.baidu.com/s/1k4oXI_XNgQwquPyXXRIafQ
提取码:yvz6
————————————————
版权声明:本文转载CSDN博主「折途」的原创文章,遵循CC 4.0 BY-SA版权协议。
原文链接:https://blog.csdn.net/m0_63235356/article/details/134635107