首先在此声明,本人是菜鸟一个,学习stm32也没有多长时间,最近因为做比赛,所以临时学习了一些东西,也遇到了一些坑,在此希望能够记录一下,作为自己的学习笔记,也希望能够跟大家分享,如果有不对的地方,希望各位大佬批评指正。
HC-SR04不再多说,淘宝满天飞的东西,我直接分析代码
首先是头文件,这个都没有问题
#include "hcsr04.h"
#include "stm32f10x_gpio.h"
其次,我在开头宏定义了我的HCSR04的管脚,这样方便以后修改,不用每个都去修改,只需要修改开头的宏定义就可以了,比较方便
// 0# hscr04
#define HCSR04_0_PORT GPIOB
#define HCSR04_0_CLK RCC_APB2Periph_GPIOB
#define HCSR04_0_TRIG GPIO_Pin_5
#define HCSR04_0_ECHO GPIO_Pin_6
其余的也一样,在这里我就不再复制黏贴,大家直接改好就行,例如我定义的
// 1# hscr04
#define HCSR04_1_PORT GPIOB
#define HCSR04_1_CLK RCC_APB2Periph_GPIOB
#define HCSR04_1_TRIG GPIO_Pin_7
#define HCSR04_1_ECHO GPIO_Pin_8
同样的道理,剩下的管脚应该都会定义了。
定义完了管脚,先定义一个整型变量,用于计数
u16 msHcCount = 0;
然后是GPIO的配置,这里以0#传感器为例
//hscr04 0# GPIO管脚初始化
void Hcsr04_0_gpio_Init()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(HCSR04_0_CLK,ENABLE);
// IO初始化
GPIO_InitStructure.GPIO_Pin = HCSR04_0_TRIG; //发送电平引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_Init(HCSR04_0_PORT, &GPIO_InitStructure);
GPIO_ResetBits(HCSR04_0_PORT, HCSR04_0_TRIG);
GPIO_InitStructure.GPIO_Pin = HCSR04_0_ECHO; //返回电平引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Init(HCSR04_0_PORT, &GPIO_InitStructure);
GPIO_ResetBits(HCSR04_0_PORT, HCSR04_0_ECHO);
}
其余的也一样,无非改改名称
配置完GPIO,我们配置TIM定时器
//定时器初始化使用基本定时器TIM2
//所有超声波均使用TIM2计时
void Hcsr04_tim_Init()
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //生成用于定时器设置的结构体
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); //使能对应RCC时钟
//配置定时器基础结构体
TIM_DeInit(TIM2);
TIM_TimeBaseStructure.TIM_Period =(1000-1); //设置在下一个更新事件装入活动的自动重装载寄存器周期的计数到1000为1ms
TIM_TimeBaseStructure.TIM_Prescaler =(72-1); //设置用来作为TIMx时钟频率除数的预分频值1M的计数频率1US计数
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //不分频
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // TIM向上计数模式
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMX的时间基数单位
TIM_ClearFlag(TIM2, TIM_FLAG_Update); //清除更新中断,免得一打开中断立即产生中断
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //打开定时器更新中断
hcsr04_NVIC();
TIM_Cmd(TIM2,ENABLE);
然后定义两个静态的函数,分别负责打开和关闭TIM计数器
注意,静态函数不需要再头文件里面声明
//提示:静态函数的作用域仅限于定义它的源文件内,所以不需要在头文件里声明
static void OpenTimerForHc() //打开定时器
{
TIM_SetCounter(TIM2,0); //清除计数
msHcCount = 0;
TIM_Cmd(TIM2, ENABLE); //使能TIMX外设
}
static void CloseTimerForHc() //关闭定时器
{
TIM_Cmd(TIM2, DISABLE); //使能TIMX外设
}
我只用了一个TIM,分时为各个超声波计数,这里配置NVIC,在中断中处理
配置NVIC
// NVIC配置
//TIM2的NVIC中断配置,所有的超声波均使用TIM2,跳这个NVIC中断
void hcsr04_NVIC()
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //选择串口1个中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //抢占式中断优先级设置为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //响应式中断优先级设置为1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能中断
NVIC_Init(&NVIC_InitStructure);
}
中断处理函数,在中断中计数,用来计时间
//定时器2中断服务程序
void TIM2_IRQHandler(void) // TIM2中断
{
if(TIM_GetITStatus(TIM2, TIM_IT_Update)!= RESET) //检查TIM3更新中断发生与否
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update); //清除TIMX更新中断标志
msHcCount ++;
}
}
这样就得到了时间
时间获取函数
//获取定时器时间
u32 GetEchoTimer(void)
{
u32 t = 0;
t = msHcCount * 1000; //得到MS
t += TIM_GetCounter(TIM2);
TIM2-> CNT = 0; //将TIM2计数寄存器的计数值清零
Delay_Ms(50);
return t;
}
测距采用五次取平均,这样精度高一些
//一次获取超声波测距数据两次测距之间需要相隔一段时间,隔断回响信号
//为了消除余震的影响,取五次数据的平均值进行加权滤波。
//返回0#超声波的距离
float Hcsr04_0_GetLength(void)
{
u32 t = 0;
int i = 0;
float lengthTemp = 0;
float sum = 0;
while (i != 5)
{
GPIO_SetBits(HCSR04_0_PORT, HCSR04_0_TRIG); //发送口高电平输出
Delay_Us(15);
GPIO_ResetBits(HCSR04_0_PORT, HCSR04_0_TRIG);
while(GPIO_ReadInputDataBit(HCSR04_0_PORT, HCSR04_0_ECHO) == 0); //等待接收口高电平输出
OpenTimerForHc(); //打开定时器
i = i + 1;
while((GPIO_ReadInputDataBit(HCSR04_0_PORT, HCSR04_0_ECHO)) == 1);
CloseTimerForHc(); //关闭定时器
t = GetEchoTimer(); //获取时间,分辨率为1US
lengthTemp =((float)t / 58.0); //厘米
sum = lengthTemp + sum;
}
lengthTemp = sum / 5.0;
return lengthTemp;
}
还是以0#为例,其余的改改名字,加个函数就好
下面是粗略的延时函数
//延时函数
void Delay_Ms(uint16_t time)
{
uint16_t i,j;
for(i = 0; i
这样,就可以在主函数中调用了
当然,调用需要完成一系列的初始化,为了方便在主函数中一步调用,我又自己定义了调用的函数
//初始化配置所有超声波传感器
void Hcsr04_ALL_Init()
{
Hcsr04_0_gpio_Init();
Hcsr04_1_gpio_Init();
Hcsr04_2_gpio_Init();
Hcsr04_tim_Init();
hcsr04_NVIC();
}
这样就可以只用这一个函数了,比较方便。
主函数如下:
#include "stm32f10x.h"
#include "usart.h"
#include "pwm.h"
#include "hcsr04.h"
float distance, distance1;
int main()
{
SystemInit(); //初始化系统时钟
systick_init(); //初始化系统滴答
usart_init();
while(1)
{
Hcsr04_ALL_Init();
printf("HCSR04_ALL init successful\n");
printf("USART init successful\n");
distance = Hcsr04_1_GetLength();
distance1 = Hcsr04_0_GetLength();
if(distance > distance1)
{
TIM_SetCompare1(TIM3,900);
TIM_SetCompare2(TIM3,100);
}
else
{
TIM_SetCompare3(TIM3,900);
TIM_SetCompare4(TIM3,100);
}
}
但是在测距的时候我发现,超声波每次在等待的时候,就是
float Hcsr04_0_GetLength(void)
这里面的
while(GPIO_ReadInputDataBit(HCSR04_0_PORT, HCSR04_0_ECHO) == 0);
//等待接收口高电平输出
这一句,如果在小车移动的过程中,出现什么问题,这句就会被卡死,所以这种方法我感觉单独的做超声波还可以,但是如果要用在移动的物体,例如小车定位上,是不靠谱的。
本次发现的问题:
1,超声波定位的小车,超声波速度太慢,反应迟钝
2,超声波采用while()等待高电平,容易出问题,接收不到,卡死,建议加看门狗或者采用输入捕获(如果用输入捕获,需要配置中断)
3,板子有的时候下载之后超声波需要手动按下复位键,才能连续的通过usart串口向电脑发送数据,也就是说,只有手动按下复位键之后,才能开始连续测距,不知道这是什么问题,正在找,也希望大神们指教。我选了下载后执行了啊,用的ST LINK V2, ST ultility下载的。
以上是本次学习笔记,希望各位大神赐教