关于HC-SR04的那些事(想看HC-SR04教程?请后翻)
当时手里拿的是HC-SR04模块,于是就去查了它的资料,通过CSDN上的一些博客得知它的工作原理之后,我便开始自行操作了。
错误示范
首先,我满怀信心的打开CubeMX,配置了Trig引脚作为输出引脚(下拉),Echo引脚作为输入引脚(下拉)。
接下来,打开一个定时器,纠结了一番后,我打开了定时器中断,别骂了别骂了,在改了在改了将PSC设置为83,将ARR设置为0,于是美滋滋的得到了一个1us触发一次的中断用于计时。只要在中断回调函数里面每次让一个计时变量加一,就可以做到计时了(gepi)。
以下内容虽然整体是错误的,但是还是有一点有用的地方的
再接下来,我需要发出一个超过10us的信号来触发模块工作,于是就产生了一个有趣的问题:微秒级延时。在网上找了好多资料都由于我太菜看不懂无果,于是我开始向我们亲爱的学长求助,了解到有一个计数频率等于单片机主频的寄存器,我们可以通过读取其中的值来获得微秒级别的延时。
代码如下:
void Delay_us(int n)
{ i
nt a;
a=SysTick->VAL;//读取寄存器的当前值
while(SysTick->VAL-a<84*n);//等待n us
}
好了好了,接下来就是敲一段简单的主函数代码之后享受胜利的喜悦了。
今天学会了超声波模块,真是开心的一天!
屁啦!Debug一下发现距离的值根本不会动啊!
询问了学长,得知是定时器中断的频率太快,这谁受得了啊建议用输入捕获来计时。
于是,我又回去卑微的看了一遍参考手册,加上学长的指导,自己有了一些体会:串口通信真香!
关于US-100的那些事
其实会有以上奇怪的体会还是因为学长满脸嫌弃细心认真的指导:干嘛用那种东西,US-100它不香嘛?
换了US-100之后我发现,串口模式的确很香。
模块拿到手,首先是万年不变的查资料过程。(PS:请注意观察模块的引脚连线部分内容,一般的串口通信都是Tx连接Rx,Rx连接Tx,而这个模块刚好相反。)
了解了模块的使用方法之后,我便开始操作(梅开二度)了:
首先,打开CubeMX进行引脚的配置,此处需要打开一组串口并使能串口中断以确保收发数据时不会被打断,我用的是USART1。
定义两个变量,一个用来发送触发信号,一个用来接收两个字节的反馈信息:
uint8_t receive[2];//存储接收的数据
double dis;//存储计算得到的距离
char buffer[256];//显示变量所需要的缓冲变量
uint8_t sign=0x55;//触发信号
在正确配置屏幕之后,在循环语句前后敲上以下代码:
/* USER CODE BEGIN WHILE */
u8g2_Setup_ssd1306_128x64_noname_1(&u8g2, U8G2_R0, u8x8_byte_4wire_hw_spi,
u8x8_gpio_and_delay);
u8g2_InitDisplay(&u8g2);
u8g2_SetPowerSave(&u8g2, 0); //u8g2屏幕的初始化
while (1)
{
HAL_UART_Transmit_IT(&huart1,&sign,1);
HAL_UART_Receive_IT(&huart1,receive,2);
u8g2_FirstPage(&u8g2);
do
{
u8g2_SetFont(&u8g2, u8g2_font_courR18_tf);
sprintf(buffer,"%.2lf",dis);
u8g2_DrawStr(&u8g2, 0, 15, buffer);
u8g2_DrawStr(&u8g2, 80, 31, "cm" );
} while (u8g2_NextPage(&u8g2));
HAL_Delay(10);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
最后定义中断回调函数并在其中计算距离:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
dis=((double)receive[0]<<8+(double)receive[1])/10;
}
至此,我们就可以在我们的屏幕上面显示模块测量的距离值了。
什么你问HC-SR04?你就是为了它才来的?没门!
我这就去学输入捕获.........
A FEW THOUSEND YEARS LATER........
关于HC-SR04
经过不断的请教之后,我终于弄懂了我心心念念的输入捕获。
是这样的,在F4参考手册里面有这样的一个图:
这张图对于我们弄懂输入捕获至关重要,在尝试期间我出现过了许多玄学错误由于不懂原理而出现的错误,下面一一与大家分享。(观前提示:以下错误均是在TIM引脚的配置上出现的,我使用的是TIM2。)
一大波错误示范如期而至
(对于其中原理不感兴趣的童鞋请直接移步下一部分,会提供完整的代码供移植)
首先屏幕和IO口的配置就不赘述了 :
看了参考手册里面关于输入捕获的介绍之后,我便开始了操作:
此时对于输入捕获,我的理解是这样的:
首先我看到了这张图
哦!检测到跳变沿后CCRx会获取CNT寄存器的值并且锁存,因此我们只需要读取CCRx的值就可以得到从CNT为0一直到跳变沿发生经过的时间,那么问题来了,CNT什么时候为0呢?于是我继续看到了这张图:
好嘛!这不是写的明明白白的嘛!第一个上升沿时IC1和IC2共同捕获,并且将计数器(CNT寄存器)复位,到达下降沿时IC2再次捕获,将CNT的值锁存到CCR2寄存器里面,下次上升沿来的时候重复以上操作.
被快乐冲昏了头脑我配置了TIM引脚如下:
生成代码后完成一番配置,接着在循环前后敲下如下代码:
HAL_TIM_IC_Start(&htim2,TIM_CHANNEL_1);//使能输入捕获
while (1)
{
Trig();//发出触发信号
HAL_Delay(100);//延时是为了给模块完成回响信号的发送的时间
a=TIM2->CCR2;//读取CCR2的值
dis=a/84000*17;//根据声速公式进行计算(PS:CNT寄存器计时的频率等于总线频率经过预分频
后的结果)
u8g2_FirstPage(&u8g2);
do
{
u8g2_SetFont(&u8g2, u8g2_font_courR18_tf);
sprintf(buffer,"%.2lf",dis);
u8g2_DrawStr(&u8g2, 0, 15, buffer);
} while (u8g2_NextPage(&u8g2));
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
变量定义:
char buffer[256];
void Trig();
double a,dis;
触发函数Trig():
void Trig()
{
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_SET);
int a;
a=SysTick->VAL;
while(SysTick->VAL-a<840);//延时10us
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_RESET);
}
(以上代码可以放心移植,因为问题不出在代码上,不过后面会有点小调整.)
烧完了程序,玄学现象出现了:屏幕上的数字一直是0.00.......
作为一个菜狗
我想了好久才想到是怎么回事,我看到了之前的那张图:
意识到频道一的值似乎应该用CCR1啊,又注意到PWM输入时序图上面写的是PWM输入不是输入捕获,我瞬间懂了,看来我想的是对的,就是应该用CCR1的值,于是就改成了读取CCR1的值.(一个错误的结论)
再次运行.........
屏幕上的数字不再是0.00了我十分兴奋,今天搞懂了HC-SR04 ,真是开心!不过我很快就意识到了问题:
数字在不断的上涨,没有一点停止的迹象.
不知所措的我再次向手册求助,问题还是出在了这张图上面:
注意到了TI1FP1和IC1以及CCR1的关系:
我意识到自己根本没有进行TI1FP1的配置,于是回到MX里面进行配置,很快就又发现:
这唤起了我的记忆,好像是要配置一个Reset Mode来着?
点下Reset Mode的一瞬间,我悟了,马上去手册里面翻看,果然发现了:
复位模式是需要进行配置的,而这个Reset Mode显然就是对此进行了配置,而之前数值不断上升的现象显然是CNT寄存器没有重置导致的.
好嘞!生成代码烧程序收工!
这次数字果然没有一直增加,然而却会一直乱跳,即使我完全没有碰模块,也会一直乱跳.
转念一想,PWM时序图里面测量脉冲长度的是啥来着?CCR2吧,可是我读CCR2是0,读取CCR1又出现这么奇怪的读数,果然还是串口香,HC-SR04算个屁,教程结束!问题应该还是出在那张我看不懂的乱七八糟关系图上,于是我硬着头皮开始硬看,好在看懂了,不然就劝退了.
以下为正确内容
其实我们只需要关注这么一小部分就可以:
首先,最上面一行告诉我们了CNT计数器的频率是主频经过PSC分频之后得到的频率,这为我们计算脉冲宽度(时间)提供了时间基准.
接下来,想想我们之前说的话:
捕获脉冲宽度的是IC2而不是IC1,所以的确是应该读取CCR2的值的,可是为什么之前的实验得到的值是0呢?我们再来看图:
显然IC2的输入大部分是从CH2里面过来的,我们用的是CH1啊,根本就没有输入怎么会有值呢?可是总不能再分一路到CH2去吧?
别慌,仔细看图就会发现IC2的输入里面有一个卧底,那就是TI1FP2:
可以看出它不是直接由CH2输入到IC2的,换句话说就是从CH1间接输入到CH2的.
好的,相信大家都想到了我们之前讲的CH2间接模式(其实大家应该很早就想到了,只有我这样的菜狗才会这么晚才意识到问题所在orz):
到了这里,我们已经离成功不远了,需要注意的是(别问我怎么注意到的):CH2有一个单独的边沿设置选项,默认为上升沿捕获,
而我们希望IC2捕获下降沿,所以记得更改捕获边沿为下降沿.
接下来敲代码,完成.
仅供参考的例程
1.屏幕的配置略
2.CubeMX配置:
打开TIM2_CH1连接ECHO引脚以便捕获回响信号;
将A1设置为GPIO输出模式并下拉,用作触发引脚.
定时器配置如图:(箭头指向为非默认选项)
3.代码内容:
变量定义和函数声明:
char buffer[256];
void Trig();
double a,dis;
触发函数Trig():
void Trig()
{
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_SET);
int a;
a=SysTick->VAL;
while(SysTick->VAL-a<840);//延时10us
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_RESET);
}
主函数循环前后:
HAL_TIM_IC_Start(&htim2,TIM_CHANNEL_1);//使能输入捕获
while (1)
{
Trig();//发出触发信号
HAL_Delay(100);//延时是为了给模块完成回响信号的发送的时间
a=TIM2->CCR2;//读取CCR2的值
dis=a/84000*17;//根据声速公式进行计算(PS:CNT寄存器计时的频率等于总线频率经过预分频后的结果)
u8g2_FirstPage(&u8g2);
do
{
u8g2_SetFont(&u8g2, u8g2_font_courR18_tf);
sprintf(buffer,"%.2lf",dis);
u8g2_DrawStr(&u8g2, 0, 15, buffer);
} while (u8g2_NextPage(&u8g2));
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
结束语
谨以此文记录自己学习超声波模块的曲折过程,分享给大家希望能给正在研究超声波模块的人以帮助.