1.开发平台:
软件:Keil5 STM32CubeMX
硬件:STM32F103C8T6 HC-SR04
2.实现思路列举
类比51单片机的方法直接读取定时器数值中高电平时长
优点:实现起来很方便,有51单片机开发经验看起来很容易理解
缺点:个人感觉实时性一般,而且一旦超声波传感器Echo引脚出现问题,会导致程序卡死
自己在定时器中通过引脚状态进行计时,用结束时间-开始时间
优点:实现起来比前者还要方便,并且理论上更容易扩展,看起来也非常简洁
缺点:误差很大,精确度很低,实时性差
在定时器输入中断捕获回调函数中通过判断引脚上升沿和下降沿的变化的时间来读取高电平时长
优点: 实时性好,可以放在其它定时器中断中进行,比如放到系统滴答定时器10Hz的中断中执行,个人认为是对STM32定时器强大功能在计算高电平时间上最好的开发
缺点:代码实现难度大,需要对STM32定时器寄存器功能的深层理解(在这里发自内心感谢硬石科技提供的例程)
3.个人实现方法
按照最新的STM32CubeMX,进行编程即可(这里对于基础的调试配置,晶振配置,时钟树的配置不做赘述):
这里只需要选定:按照方向输入捕获 和 预分频 71 以及计数周期65535即可
这边的参数是默认的可以不用管
之后打开定时器中断
至于选择那个IO口作为Trig引脚,随意即可,然后上代码:
hc_sr04.c
#include "hc_sr04.h"
HC_SR04_define hc_Sr04;
// 超声波传感器初始化
void HC_SR04_Init(void)
{
hc_Sr04.timPeriod = SR04TIM_PERIOD; //初始化计数周期
//获得Trig引脚
hc_Sr04.TRIG = SR04_TRIG;
hc_Sr04.TRIG_PIN = SR04_TRIG_PIN;//
hc_Sr04.tim = &SR04TIM; //获取定时器
hc_Sr04.channel = SR04TIM_CHANNEL; //定时器通道1
hc_Sr04.timITcc1 = SR04TIM_IT_CC; //设定中断通道
hc_Sr04.timSTRAT_ICPolarity = SR04TIM_STRAT_ICPolarity; //中断捕获开始
hc_Sr04.timEND_ICPolarity = SR04TIM_END_ICPolarity; //中断捕获结束
//获取锁相环时钟频率/预分频 = 定时器的工作频率
hc_Sr04.ulTmrClk = HAL_RCC_GetHCLKFreq()/SR04TIM_PRESCALER;
/* 启动定时器 */
HAL_TIM_Base_Start_IT(&htim3);
/* 启动定时器3通道1输入捕获中断*/
HAL_TIM_IC_Start_IT(&htim3,SR04TIM_CHANNEL);
}
unsigned int i = 0;
//定时器中断捕获回调函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
HCSR04_CaptureHighLevel(&hc_Sr04);
}
/* 获取超声波测量的距离 */
void getDistance(HC_SR04_define *sr04)
{
//Trig引脚用来触发超声波测量
HAL_GPIO_WritePin(sr04->TRIG,sr04->TRIG_PIN,GPIO_PIN_SET);
delay_us(10);//触发信号延时10us
HAL_GPIO_WritePin(sr04->TRIG,sr04->TRIG_PIN,GPIO_PIN_RESET);
/* 完成测量高电平脉宽 */
if(sr04->ucFinishFlag == 1)
{
/* 计算高电平计数值 = 捕获到的电平的周期*定时器计数溢出值上限+捕获到高电平的数值*/
sr04->ulTime = sr04->usCtr;
/*distance是一次测量通过一次计算出的距离的值*/
/*距离distance=捕获的高电平的数值对于时钟取余然后得到的超声波反馈的时间*/
/*根据声速是每秒钟340m,因为声音发出后遇到物体返回回来,这是一个来回 所以具体计算的时候要除以2,因为捕获的时间是us,所以要除以1000*/
/*得到的距离是mm*/
sr04->distance = ((sr04->ulTime%sr04->ulTmrClk)*340)/(1000*2);
sr04->cmdis = sr04->distance/10.0f; //计算cm的距离
sr04->ucFinishFlag = 0;
}
}
//超声波捕获高电平
void HCSR04_CaptureHighLevel(HC_SR04_define *sr04)
{
TIM_IC_InitTypeDef sConfigIC;
if(sr04->ucStartFlag == 0)
{
__HAL_TIM_SET_COUNTER(sr04->tim,0); // 清零定时器计数
sr04->usPeriod = 0;
sr04->usCtr = 0;
// 配置输入捕获参数,主要是修改触发电平
sConfigIC.ICPolarity = sr04->timEND_ICPolarity;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
sConfigIC.ICFilter = 0;
HAL_TIM_IC_ConfigChannel(sr04->tim, &sConfigIC,sr04->channel);
// 清除中断标志位
__HAL_TIM_CLEAR_IT(sr04->tim,sr04->timITcc1);
// 启动输入捕获并开启中断
__HAL_TIM_ENABLE_IT(sr04->tim,sr04->timITcc1);
TIM_CCxChannelCmd(sr04->tim->Instance,sr04->channel, TIM_CCx_ENABLE);
__HAL_TIM_ENABLE(sr04->tim);
sr04->ucStartFlag = 1;
i = 14;
}
else
{
i = 15;
// 获取定时器计数值
sr04->usCtr = HAL_TIM_ReadCapturedValue(sr04->tim,sr04->channel);
// 配置输入捕获参数,主要是修改触发电平
sConfigIC.ICPolarity = sr04->timSTRAT_ICPolarity;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
sConfigIC.ICFilter = 0;
HAL_TIM_IC_ConfigChannel(sr04->tim, &sConfigIC, sr04->channel);
// 清除中断标志位
__HAL_TIM_CLEAR_IT(sr04->tim, sr04->timITcc1);
// 启动输入捕获并开启中断
__HAL_TIM_ENABLE_IT(sr04->tim,sr04->timITcc1);
TIM_CCxChannelCmd(sr04->tim->Instance,sr04->channel, TIM_CCx_ENABLE);
__HAL_TIM_ENABLE(sr04->tim);
sr04->ucStartFlag = 0;
sr04->ucFinishFlag = 1;
}
}
hc_sr04.h
#ifndef __HC_SR04_H
#define __HC_SR04_H
#include "header.h"
#define SR04_TRIG GPIOA
#define SR04_TRIG_PIN GPIO_PIN_5
#define SR04TIM_PRESCALER 71 //预分频71
#define SR04TIM_PERIOD 0xFFFF //定时器技术周期
#define SR04TIM htim3
#define SR04TIM_CHANNEL TIM_CHANNEL_1 //通道1
#define SR04TIM_IT_CC TIM_IT_CC1 //中断1
#define SR04TIM_STRAT_ICPolarity TIM_INPUTCHANNELPOLARITY_RISING //测量的起始边沿
#define SR04TIM_END_ICPolarity TIM_INPUTCHANNELPOLARITY_FALLING //测量的结束边沿
typedef struct HC_SR04
{
uint8_t ucFinishFlag; //捕获结束标志位
uint8_t ucStartFlag; //捕获开始标志位
uint16_t usCtr; //捕获时间
uint16_t usPeriod; //捕获周期
uint32_t ulTmrClk; //定时器工作频率
uint32_t ulTime;
uint32_t distance; //最终捕获距离
float cmdis; //以厘米为单位的距离
uint16_t TRIG_PIN; //方向控制引脚1
GPIO_TypeDef *TRIG; //方向控制GPIO
TIM_HandleTypeDef *tim; //需要用的定时器
uint16_t timPeriod; //定时器计数周期
uint32_t channel; //定时器通道
uint32_t timITcc1; //定时器中断计数1
uint32_t timSTRAT_ICPolarity;
uint32_t timEND_ICPolarity;
}HC_SR04_define;
extern HC_SR04_define hc_Sr04;
// 超声波传感器初始化
void HC_SR04_Init(void);
//超声波捕获高电平 只能放到定时器回调函数中使用
void HCSR04_CaptureHighLevel(HC_SR04_define *sr04);
/* 获取超声波测量的距离 */
void getDistance(HC_SR04_define *sr04);
#endif //
使用非常简单,如果增加超声波传感器只需要再定义一个HC_SR04_define 数据结构型变量,然后按照初始化中的顺序对端口和初值进行赋值传递,之后放到回调函数中即可,获取距离
getDistance(&hc_Sr04);
4.对于HAL库更新的吐槽
其实以上代码的核心部分是我在20年的时候参照硬石电子的资料写的,但是如果使用的是写这篇文章时候的HAL库还按照硬石的资料去写,是不能实现的,主要原因如下:大家可以来对比一下,
这是19年的HAL_TIM_IC_Start_IT函数接下来看一下现在的
/**
* @brief Starts the TIM Input Capture measurement in interrupt mode.
* @param htim : TIM Input Capture handle
* @param Channel : TIM Channels to be enabled
* This parameter can be one of the following values:
* @arg TIM_CHANNEL_1: TIM Channel 1 selected
* @arg TIM_CHANNEL_2: TIM Channel 2 selected
* @arg TIM_CHANNEL_3: TIM Channel 3 selected
* @arg TIM_CHANNEL_4: TIM Channel 4 selected
* @retval HAL status
*/
HAL_StatusTypeDef HAL_TIM_IC_Start_IT (TIM_HandleTypeDef *htim, uint32_t Channel)
{
/* Check the parameters */
assert_param(IS_TIM_CCX_INSTANCE(htim->Instance, Channel));
switch (Channel)
{
case TIM_CHANNEL_1:
{
/* Enable the TIM Capture/Compare 1 interrupt */
__HAL_TIM_ENABLE_IT(htim, TIM_IT_CC1);
}
break;
case TIM_CHANNEL_2:
{
/* Enable the TIM Capture/Compare 2 interrupt */
__HAL_TIM_ENABLE_IT(htim, TIM_IT_CC2);
}
break;
case TIM_CHANNEL_3:
{
/* Enable the TIM Capture/Compare 3 interrupt */
__HAL_TIM_ENABLE_IT(htim, TIM_IT_CC3);
}
break;
case TIM_CHANNEL_4:
{
/* Enable the TIM Capture/Compare 4 interrupt */
__HAL_TIM_ENABLE_IT(htim, TIM_IT_CC4);
}
break;
default:
break;
}
/* Enable the Input Capture channel */
TIM_CCxChannelCmd(htim->Instance, Channel, TIM_CCx_ENABLE);
/* Enable the Peripheral */
__HAL_TIM_ENABLE(htim);
/* Return function status */
return HAL_OK;
}
23年现在的
/**
* @brief Starts the TIM Input Capture measurement in interrupt mode.
* @param htim TIM Input Capture handle
* @param Channel TIM Channels to be enabled
* This parameter can be one of the following values:
* @arg TIM_CHANNEL_1: TIM Channel 1 selected
* @arg TIM_CHANNEL_2: TIM Channel 2 selected
* @arg TIM_CHANNEL_3: TIM Channel 3 selected
* @arg TIM_CHANNEL_4: TIM Channel 4 selected
* @retval HAL status
*/
HAL_StatusTypeDef HAL_TIM_IC_Start_IT(TIM_HandleTypeDef *htim, uint32_t Channel)
{
uint32_t tmpsmcr;
HAL_TIM_ChannelStateTypeDef channel_state = TIM_CHANNEL_STATE_GET(htim, Channel);
HAL_TIM_ChannelStateTypeDef complementary_channel_state = TIM_CHANNEL_N_STATE_GET(htim, Channel);
/* Check the parameters */
assert_param(IS_TIM_CCX_INSTANCE(htim->Instance, Channel));
/* Check the TIM channel state */
if ((channel_state != HAL_TIM_CHANNEL_STATE_READY)
|| (complementary_channel_state != HAL_TIM_CHANNEL_STATE_READY))
{
return HAL_ERROR;
}
/* Set the TIM channel state */
TIM_CHANNEL_STATE_SET(htim, Channel, HAL_TIM_CHANNEL_STATE_BUSY);
TIM_CHANNEL_N_STATE_SET(htim, Channel, HAL_TIM_CHANNEL_STATE_BUSY);
switch (Channel)
{
case TIM_CHANNEL_1:
{
/* Enable the TIM Capture/Compare 1 interrupt */
__HAL_TIM_ENABLE_IT(htim, TIM_IT_CC1);
break;
}
case TIM_CHANNEL_2:
{
/* Enable the TIM Capture/Compare 2 interrupt */
__HAL_TIM_ENABLE_IT(htim, TIM_IT_CC2);
break;
}
case TIM_CHANNEL_3:
{
/* Enable the TIM Capture/Compare 3 interrupt */
__HAL_TIM_ENABLE_IT(htim, TIM_IT_CC3);
break;
}
case TIM_CHANNEL_4:
{
/* Enable the TIM Capture/Compare 4 interrupt */
__HAL_TIM_ENABLE_IT(htim, TIM_IT_CC4);
break;
}
default:
break;
}
/* Enable the Input Capture channel */
TIM_CCxChannelCmd(htim->Instance, Channel, TIM_CCx_ENABLE);
/* Enable the Peripheral, except in trigger mode where enable is automatically done with trigger */
if (IS_TIM_SLAVE_INSTANCE(htim->Instance))
{
tmpsmcr = htim->Instance->SMCR & TIM_SMCR_SMS;
if (!IS_TIM_SLAVEMODE_TRIGGER_ENABLED(tmpsmcr))
{
__HAL_TIM_ENABLE(htim);
}
}
else
{
__HAL_TIM_ENABLE(htim);
}
/* Return function status */
return HAL_OK;
}
我知道HAL库在向着更加高效更加方便的方向进行发展,我也是HAL库的忠实粉丝,但是我真的真的希望HAL库在更新的时候进行一下自测,因为我已经不是第一次遇上这种问题了,这几年来的嵌入式开发中,HAL库在我这出现了至少三次之前代码能用,之后代码不能用,结果发现是HAL库底层代码的变化导致的。对于以上更改的代码我的解决方法是,将该函数替换为:
// 启动输入捕获并开启中断
__HAL_TIM_ENABLE_IT(sr04->tim,sr04->timITcc1);
TIM_CCxChannelCmd(sr04->tim->Instance,sr04->channel, TIM_CCx_ENABLE);
__HAL_TIM_ENABLE(sr04->tim);
还是希望STM32官方能对HAL库进行完善,总这样真的要逼得小编将来自己用寄存器写库了啊啊啊啊!
转载:https://blog.csdn.net/weixin_44080304/article/details/128773267