之前参加比赛,自己写了一些传感器的驱动,不太确定网上是否有雷同的,给大家参考下。
模块用的是HC-SR06,比较普遍,首先看一下驱动原理,如下图:
- 将T引脚置高超过10us;
- 结束后探头将发送超声波信号并进入检测,同时拉高E引脚;
- 接收到反射波后拉低E引脚;
可知E引脚被拉高的时间是超声波的传输时间,根据声速即可计算距离。
STM32的实现方式还是有许多种的,如延时查询,获取系统节拍等,本文采用的定时器,以后有机会可以写一下其他方式的。
因为当时参加的项目功能较少,所以用资源比较奢侈,这里用了一个定时器的一个通道驱动T引脚,一个通道捕获E引脚。
思路比较简单,介绍一下代码。
配置是参考正点原子例程的, 用标准库配置的,一股原子味。
首先是定时器配置,具体参数定义可以自己看官方文档,修改成自己的:
void TIM3_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
TIM_ICInitTypeDef TIM3_ICInitStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
//外设时钟配置
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能定时器3时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); //使能GPIO外设和AFIO复用功能模块时钟
//映射GPIO
GPIO_PinRemapConfig(GPIO_FullRemap_TIM3, ENABLE);
//E引脚输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_ResetBits(GPIOC,GPIO_Pin_0);
//T引脚输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
//定时器配置
TIM_TimeBaseStructure.TIM_Period = arr;
TIM_TimeBaseStructure.TIM_Prescaler =psc;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
//输入捕获配置
TIM3_ICInitStructure.TIM_Channel = TIM_Channel_3;
TIM3_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM3_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM3_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM3_ICInitStructure.TIM_ICFilter = 0x00;
TIM_ICInit(TIM3, &TIM3_ICInitStructure);
//捕获中断配置
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
TIM_ITConfig(TIM3,TIM_IT_Update|TIM_IT_CC3,ENABLE);
//输出比较配置
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
TIM_OC4Init(TIM3, &TIM_OCInitStructure);
TIM_OC4PreloadConfig(TIM3, TIM_OCPreload_Enable);
TIM_Cmd(TIM3,ENABLE );
}
void DISTANCE_INIT(){
TIM3_Init(50000-1,72-1); //重装载频率20HZ
TIM_SetCompare4(TIM3,20); //
g_target_distance = 0;
}
这里解释一下:
首先定时器的频率计算公式是 72M / ((arr+1)*(psc+1))(72M是我的主频率)
然后设置计数器时钟分频到1M Hz,重装载频率20Hz,又设置了输出比较值是20。
这样的结果是定时器的定时精度为1 / 1M s,每秒自动启动超声波模块20次(输出比较每秒20次置20us的高电平)。
这里没必要设置成PWM模式,显然是多余的,而且不需要中断管理,利用输出比较的特性自动控制管脚高低。
接下来是输入捕获部分
void TIM3_IRQHandler(void)
{
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET){
if(TIM3CH3_CAPTURE_STA&0X40)//已经捕获到高电平了
{
TIM3CH3_CAPTURE_STA = 0;//标记成功捕获了一次
TIM3CH3_CAPTURE_VAL=0;
TIM_OC3PolarityConfig(TIM3,TIM_ICPolarity_Rising);
}
}
if (TIM_GetITStatus(TIM3, TIM_IT_CC3) != RESET)//发生捕获事件
{
if(TIM3CH3_CAPTURE_STA&0X40) //捕获到一个下降沿
{
TIM3CH3_CAPTURE_STA|=0X80; //标记成功捕获到一次上升沿
TIM3CH3_CAPTURE_VAL=TIM_GetCapture3(TIM3);
TIM_OC3PolarityConfig(TIM3,TIM_ICPolarity_Rising); //设置为上升沿捕获
}
else //还未开始,第一次捕获上升沿
{
TIM3CH3_CAPTURE_STA=0; //清空
TIM3CH3_CAPTURE_VAL=0;
TIM_SetCounter(TIM3,0);
TIM3CH3_CAPTURE_STA|=0X40; //标记捕获到了上升沿
TIM_OC3PolarityConfig(TIM3,TIM_ICPolarity_Falling); //CC1P=1 设置为下降沿捕获
}
}
//上面是修改正点原子的
//下面计算并保存数据
if(TIM3CH3_CAPTURE_STA & 0X80){
if(TIM3CH3_CAPTURE_VAL < 1750 && TIM3CH3_CAPTURE_VAL > 50)
{
g_target_distance = TIM3CH3_CAPTURE_VAL * 1.72f;//1us 3.44mm 来回
//printf("target distance :%d mm\r\n",g_target_distance );
}
else
{
g_target_distance = 0;
}
TIM3CH3_CAPTURE_STA = 0;
}
TIM_ClearITPendingBit(TIM3, TIM_IT_CC3|TIM_IT_Update); //清除中断标志位
}
接着对外开放一个接口获取g_target_distance,可以自己加滤波之类的。
这里再插两个个知识
为什么STM32会自动调用这个中断函数呢,是因为标准库内其实已经有这个同名函数了,且在中断向量表内指向了这个函数名。但是它被配置成weak函数,所以当我们自己写了同一名称的函数时,就取代了原本的函数入口。具体这里不展开介绍。
还有超声波模块需要较高电压(相对)驱动,但是我们直接接到5v上就能用,这是为什么呢?其实可以用类似MAX232芯片来驱动,可以参考这篇文章。