目录
前言
这篇文章主要是对自己学习STM32做的一个总结,利用超声波模块加舵机模块进行的小项目,为了方便知识的回顾,有错误还望指正谢谢。
1、项目简介
项目是利用超声波模块(HC_SR04)来进行测距,由此来进行操纵舵机(SG90)进行运动反馈。这也是可以做成一个小小的开盖垃圾桶。
项目相关内容
本项实验的硬件组成有STM32F103C8T6开发板、USB转TTL串口模块(CH340)、超声波模块(HC-SR04)、舵机模块(SG90 180度),系统定时器SysTick,由此来进行一个各个模块的编写。
2、模块
1、系统定时器SysTick
系统定时器的延时函数参考我的上一篇文章,有详细解释。
STM32的SysTick系统定时器_努力学习的多云的博客-CSDN博客
void ms_delay(uint32_t ms)
{
uint32_t i;
SysTick_Config( 72000);
for(i=0;i<ms;i++)//一个循环相当于1ms
{
while( !((SysTick->CTRL)&(1<<16)) );//一次计时结束
}
SysTick->CTRL &=~ SysTick_CTRL_ENABLE_Msk;//使之使能,置O
}
void us_delay(uint32_t us)
{
uint32_t i;
SysTick_Config(72);
for(i=0;i<us;i++)
{
while( !((SysTick->CTRL)&(1<<16)) );
}
SysTick->CTRL &=~ SysTick_CTRL_ENABLE_Msk;
}
2、舵机模块SG90
我是选择图中这款的SG90舵机支持0到180度,可以看到有三根线,红色线(VCC)、棕色线(GND)、还有一根黄橙色的信号线。舵机的控制一般需要一个20ms的时基脉冲,。
SG90舵机的控制需要一个20ms的时基脉冲,定时器的时钟周期计算公式是, T = (arr+1) * (psc+1) / 72M。对TIM_TimeBaseInit结构体的成员TIM_Period 与TIM_Prescaler进行赋值即可。 当设置arr= 199,psc = 7199,T = 200*7200/72000000 = 0.02s = 20ms。
脉冲的高电平部分一般为0.5ms~2.5ms范围内的角度控制脉冲部分,所以是利用一个占空比来控制舵机转动的角度。PWM占空比是指在一个周期内,信号处于高电平的时间占据整个信号周期的百分比。占空比 = t / T 相关参数如下:
t = 0.5ms——————-舵机会转动 0 °
t = 1.0ms——————-舵机会转动 45°
t = 1.5ms——————-舵机会转动 90°
t = 2.0ms——————-舵机会转动 135°
t = 2.5ms——————-舵机会转动180°
当你需要转动135°时,他的占空比就是2.0ms/20ms=10%,所以 TIMx 捕获比较寄存器值就为200-200*10% = 180。
#include "motor.h"
#include "stm32f10x.h"
void motor_config(void)
{
GPIO_InitTypeDef GPIO_Motorinit;
TIM_TimeBaseInitTypeDef TIM_Motorinit;
TIM_OCInitTypeDef TIMPWM_Motorinit;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//根据参考手册可知道他们各自挂在APB2还是APB1
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3 , ENABLE);//部分重映射
GPIO_Motorinit.GPIO_Mode = GPIO_Mode_AF_PP;//根据参考手册查找,可以看图选择复用推挽
GPIO_Motorinit.GPIO_Pin = GPIO_Pin_5;
GPIO_Motorinit.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init( GPIOB, &GPIO_Motorinit);
TIM_Motorinit.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割
TIM_Motorinit.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_Motorinit.TIM_Period = 200-1; //设置在下一个更新事件装入活动的自动重装载值
TIM_Motorinit.TIM_Prescaler = 7200-1; //TIMx时钟频率预分频值
TIM_TimeBaseInit( TIM3, &TIM_Motorinit);
TIMPWM_Motorinit.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式,低于比较值为有效电平
TIMPWM_Motorinit.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIMPWM_Motorinit.TIM_OCPolarity = TIM_OCPolarity_Low; //选择有效输出极性,为低电平
TIM_OC2Init( TIM3, &TIMPWM_Motorinit);//使能通道的的TIM3
TIM_OC2PreloadConfig( TIM3, TIM_OCPreload_Enable );
TIM_Cmd( TIM3, ENABLE);
}
在选择向上计数模式,讲述PWM原理:
①在PWM输出模式下除了CNT(计数器当前值) ARR(自动重装载值),CCRx(捕获/比较寄存器值)。
②当CNT小于CCRx时,TIMxCHx通道输出低电平,根据定时器模式和选择有效输出极性来做判断
③当CNT等于或大于CCRx时,TIMxCHx通道输出高电平
由此在上面的占空比图中就可以看到调整占空比就可以控制舵机转的角度了。
其中的引脚配置由参考手册可以查找到
STM32F1xx中文参考手册-->8.1.11 外设的GPIO配置-->表20 通用定时器TIM2/3/4/5
STM32F1xx中文参考手册-->8.3.7 定时器复用功能重映射-->表42 TIM3复用功能重映像
3、 超声波模块 HC-SR04
HC-SR04是一款尺寸完全兼容老版本增加UART和IIC功能的开放式超声波测距模块,可以通过电阻设置成UART或IIC模式。2CM盲区,4.5M典型最远测距,2.2mA作电流。工作电压宽(3-5.5V)。
外部MCU给模块Trig脚一个大于10uS的高电平脉冲;模块会给出一个与距离等比的高电平脉冲信号,可根据脉宽时间“T”算出:
距离=T*C/2 (C为声速)
图中时VCC为供电电源,Trig触发端口配置了PB11,Echo为接受端口配置了PB10,GND为地。
在此次程序中做了IO配置,定时器配置,和中断配置,因为要计数高电平的持续时间,在高电平的时候开始计数,下降沿的时候关闭定时器。
其中输入输出模式可以从参考手册找到
#include"HC_SR04.h"
#include"stm32f10x.h"
#include"SysTick.h"
extern uint16_t mscount = 0;//超声波计数,加了extern后,在Main里也可以使用了
void HC_SR04Config(void)
{
GPIO_InitTypeDef GPIO_HCSR04init;//io配置
TIM_TimeBaseInitTypeDef TIM_HCSR04init;//定时器配置
NVIC_InitTypeDef NVIC_HCSR04init;//中断配置
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB , ENABLE );
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4 , ENABLE);//在rcc.h寻找,出TIM1外的定时器都是挂在APB1总线上的
/*所以要使能定时器时钟就要使能APB1外设时钟寄存器*/
NVIC_PriorityGroupConfig( NVIC_PriorityGroup_1);//优先级分组
//Trig触发 PB11
GPIO_HCSR04init.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_HCSR04init.GPIO_Pin = GPIO_Pin_11;
GPIO_HCSR04init.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init( GPIOB, &GPIO_HCSR04init);//初始化
//Echo接受引脚 PB10 接收电平不需要配置速度
GPIO_HCSR04init.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_HCSR04init.GPIO_Pin = GPIO_Pin_10;
GPIO_Init( GPIOB, &GPIO_HCSR04init);
TIM_HCSR04init.TIM_ClockDivision= TIM_CKD_DIV1;//时钟分割 在内核misc.h寻找
TIM_HCSR04init.TIM_CounterMode = TIM_CounterMode_Up;//计数模式
TIM_HCSR04init.TIM_Period = 1000-1;//自动重装载数值
TIM_HCSR04init.TIM_Prescaler = 72-1;//分频系数
TIM_TimeBaseInit( TIM4, &TIM_HCSR04init);
TIM_ITConfig( TIM4, TIM_IT_Update, ENABLE );//定时器中断
TIM_Cmd( TIM4, DISABLE );//使能定时器,因为要计算高电平时间,所以先关闭,等检测时再打开
NVIC_HCSR04init.NVIC_IRQChannel = TIM4_IRQn;//中断通道 在stm32f10x.h寻找
NVIC_HCSR04init.NVIC_IRQChannelPreemptionPriority =0;//抢占优先级,最高级
NVIC_HCSR04init.NVIC_IRQChannelSubPriority =0;//响应优先级
NVIC_HCSR04init.NVIC_IRQChannelCmd =ENABLE;//通道使能
NVIC_Init( &NVIC_HCSR04init);
}
超声波测距步骤
1.配置GPIO引脚结构体(Trig,Echo)。
2.配置定时器结构体
3.配置定时器中断结构体
4.开启时钟(定时器,GPIO)
5.Trig引脚输出高电平(10us以上),然后关闭
6.等待Echo引脚输入高电平开始,定时器打开--->开启计数器计数
7.等待Echo引脚输入高电平结束,定时器关闭-..>停止计数器计数
8.通过计数器的值计算得出超声波测量距离
程序中配置了周期为1us的TIM4,再编写中断服务函数TIM4_IRQHandler(void),进入一次中断 mscount加1,
//打开定时器4
void Open_TIM4(void)
{
TIM_SetCounter( TIM4, 0);//设置寄存器的计数值
mscount =0 ;
TIM_Cmd( TIM4, ENABLE );
}
//关闭定时器4
void Close_TIM4(void)
{
TIM_Cmd( TIM4, DISABLE );
}
//中断服务函数
void TIM4_IRQHandler(void)
{
if (TIM_GetITStatus( TIM4, TIM_IT_Update ) != RESET)//判断是否发生中断,意思是不等于0时
{
TIM_ClearITPendingBit( TIM4, TIM_IT_Update );
mscount++;
}
}
/*获取定时器计数的值,计算高电平的时间*/
int GetEcho_time(void)
{
uint32_t t = 0;
t = mscount*1000;//换算成以us做单位
t += TIM_GetCounter( TIM4);//获取定时器的值
TIM4->CNT = 0;//清零从头开始计数
us_delay(50);
return t;
}
距离公式:高电平持续时间*声速(340m/s)/2。
34300 除于 1000 00 换算成cm/us,也就是0.0343cm/us,在换算一个角度,1/(0.0343)cm/us,即29.15 us/cm,意味着 291.5us表示10cm的距离,1cm就是29.15us,发送后接收,声音走过两倍距离,实际距离就是1cm,对应58.3us,程序中的 length 实际上是 us,换算成距离,除以 58.358.3 会更具体一点,但是我们除以58就可以了。
代码中运用5次计算求出平均,使得结果会更准确。其中TRIG_Send(), ECHO_Reci(),是我在.h文件中宏定义了出来,方便查看。
#define ECHO_Reci GPIO_ReadInputDataBit( GPIOB, GPIO_Pin_10)
/* 注意 这里 第二行 不要分号结尾 */
#define TRIG_Send(a) if(a) \
GPIO_SetBits( GPIOB, GPIO_Pin_11);\
else \
GPIO_ResetBits( GPIOB, GPIO_Pin_11) // 就是这里
//通过定时器4计数器值推算距离
float GetLength(void)
{
int i = 0;
uint32_t t = 0;
float length = 0;
float sum = 0;
while(i !=5)
{
TRIG_Send(1);
us_delay(20);//TRIG引脚发送20us高电平
TRIG_Send(0);
while (ECHO_Reci == 0);//while (ECHO_Reci == 0); 相当于是 while(0); 就是一直在0那里循环
//到了这里相当于跳出0了 到了1
Open_TIM4(); //也就是高电平的时候 开启 TIM4
i = i+1;
while (ECHO_Reci == 1);//这里的意思 和上面的相反
Close_TIM4();
t = GetEcho_time();
length = ((float)t /58.0);
sum = sum + length;
}
length = sum/5.0;
return length;
}
4、Main.c
其中作为一个超声波距离判断来进行一个舵机的转动,距离可以通过串口打印出来。在讲到PWM波控制占空比大小来进行一个舵机的转动角度,要让舵机转180度,那么就让pwmval的值为200*(1-(2.5/20)) = 175。我选择的是选择135度,所以pwmval设置为180。
int main()
{
int pwmval ;
float Length = 0;
HC_SR04Config();
usart_init();
motor_config();
while(1)
{
Length = GetLength();
printf("%.3f\r\n",Length);
ms_delay(500);
if (Length < 10)
{
pwmval = 180;
TIM_SetCompare2( TIM3, pwmval);
ms_delay(500);
}
else if (Length >20)
{
pwmval =195;
TIM_SetCompare2( TIM3, pwmval );
}
}
}
5、总结
在这个学习过程中,也是从不明白到渐渐进入状态,也看了很多小伙伴的文章,终于把第一篇搞了出来,在这里有错误的还望大家指正,这次的就是一个简单的小项目,内容就是要学会查资料,看参考手册,整个弄懂之后会发现一个新的感觉,内容相对简单,但对于新手的我就是极大的一个收获,后续可以通过更多的模块来实现更多好玩的东西。
参考链接: