本次我们使用到了STM32F103C8T6最小系统板作为我们的主控板,通过超声波模块感应接收距离来控制舵机的旋转,可用于超声波感应来控制垃圾桶开盖。
这次我们需要用到的包括了超声波模块HC-SR04,舵机SG90以及我们的STM32的最小系统板。
一.首先是超声波模块的使用,如图所示超声波模块共有4个引脚分别是VCC,Trig,Echo,GND。在接线上,VCC,GND分别对应接在板子的电源和地上,Trig接在PB11,Echo接在PB10上。
上面为超声波工作的时序图。超声波工作时超声波模块的触发角(Trig)输入至少10us以上的高电位,即可发送超声波,在触发角(Trig)发送超声波到“响应”角(Echo)接收到传回的超声波这段时间内,“响应”角(Echo)位始终呈现出一个高电平的状态,因为如此,可以从“响应”角(Echo)持续高电平脉冲的时间来换算出被测物的距离。
超声波测距步骤为
1.配置GPIO引脚结构体(Trig,Echo)。
2.配置定时器结构体
3.配置定时器NVIC中断结构体(这是用到了一个TIM4的中断来测定“响应”角(Echo)持续高电平脉冲的时间)。
4.开启时钟(定时器,GPIO)
5.Trig引脚输出高电平(至少10us),然后关闭。
6.等待“响应”角(Echo)输入高电平开始,定时器打开-开始定时器计数
7.等待“响应”角(Echo)输入高电平结束(即为低电平),定时器关闭-关闭定时器计数
最后我们就可以通过公式换算得到超声波感应的距离了。
即距离 = 高电平持续时间 * 声速(340m/s)/2;(这里除以2是因为超声波的传输是发送后接触到物体后反弹,故实际距离应是超声波的传输距离除以2)。
接下来是超声波函数的代码编写,首先我们需要在HC_SR04.h中宏定义一个Trig发送函数和一个Echo接收函数。方便我们调用。我们还用到了系统定时器SysTick.h。
SysTick.h
#include "SysTick.h"
#include "stm32f10x.h"
void ms_delay(uint32_t ms)
{
uint32_t i;
SysTick_Config(72000);
for(i=0;iCTRL)&(1<<16)) );
}
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
}
void us_delay(uint32_t us)
{
uint32_t i;
SysTick_Config(72);
for(i=0;iCTRL)&(1<<16)) );
}
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
}
HC_SR04.h
#ifndef __HC_SR04_H //条件编译,防止重复编译
#define __HC_SR04_H
#include "stm32f10x.h"
void HC_SR04_config(void);
void Open_TIM4(void);
void Close_TIM4(void);
int GetEcho_time(void);
float Getlength(void);
//Echo读取引脚电平状态
#define ECHO_Receive GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_10)
//Trig发送设置高低电平,这里每一句末尾的“\”起到行与行之间的连接作用
#define TRIG_Send(a) if(a)\
GPIO_SetBits(GPIOB, GPIO_Pin_11);\
else\
GPIO_ResetBits(GPIOB, GPIO_Pin_11)
#endif
HC_SR04.c
#include "HC_SR04.h"
#include "stm32f10x.h"
#include "SysTick.h"
uint16_t mscount;//记录中断次数
void HC_SR04_config(void)
{
GPIO_InitTypeDef GPIO_hcsr04init;
TIM_TimeBaseInitTypeDef TIM_hcsr04init;
NVIC_InitTypeDef NVIC_hcsr04init;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);
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;//不分频
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_IT_Update为允许溢出,更新标志位
TIM_Cmd( TIM4,DISABLE);//先关闭定时器,Trig触发后打开
//NVIC中断控制器(TIM4)
NVIC_hcsr04init.NVIC_IRQChannel = TIM4_IRQn;//TIM4通道,设置中断源
NVIC_hcsr04init.NVIC_IRQChannelPreemptionPriority = 0;//抢占优先级
NVIC_hcsr04init.NVIC_IRQChannelSubPriority = 0;//子优先级
NVIC_hcsr04init.NVIC_IRQChannelCmd = ENABLE;//使能
NVIC_Init(&NVIC_hcsr04init);
}
//打开定时器4
void Open_TIM4(void)
{
TIM_SetCounter(TIM4,0);//开启定时器计数,从0开始
mscount = 0;//设置中断次数初始为0
TIM_Cmd( TIM4,ENABLE);//使能,打开定时器4
}
//关闭定时器4
void Close_TIM4(void)
{
TIM_Cmd( TIM4,DISABLE);//失能,关闭定时器
}
void TIM4_IRQHandler(void)
{
if( TIM_GetITStatus( TIM4, TIM_IT_Update ) != RESET)//假如发生中断
{
TIM_ClearITPendingBit( TIM4, TIM_IT_Update);//清除中断标志位
mscount++;//每发生一次中断加1,定时器中断一次为1ms
}
}
//获取Echo角的高电平脉冲持续时间
int GetEcho_time(void)
{
uint32_t t=0;
t = mscount*1000;//将ms*1000换算为us
t += TIM_GetCounter(TIM4);//获取不足一次中断的时间
TIM4->CNT = 0;
ms_delay(50);
return t;//返回时间t
}
//获得超声波测距距离
float Getlength()
{
int i =0;
uint32_t t =0;
float length =0;
float sum =0;
for(i=0;i<5;i++)//计算5次提高运算精度
{
TRIG_Send(1);//拉高触发角电平
us_delay(20);//延时至少10us
TRIG_Send(0);//拉低触发角电平
while(ECHO_Receive ==0); //注意这里要加分号,这里是一个跳电平的操作,只有满足条件才能向下运行
Open_TIM4();//打开定时器4,开始计数
while(ECHO_Receive ==1); //同上
Close_TIM4();//关闭定时器4,结束计数
t = GetEcho_time();//获取“响应”角(Echo)持续高电平脉冲的时间t
length = ((float)t/58.0);//固定计算超生波距离公式:时间(us)/58.0
sum=sum+length;//获取5次超声波测距的总和
}
length = sum/5.0;//sum/5获取超声波所测距离长度
return length;//返回长度
2.接下来配置Usart.c使得通过超声波测距显示的距离可以打印在串口助手上。如图所示
Usart.c
#include "stm32f10x.h"
#include "usart.h"
void usart_init(void)
{
GPIO_InitTypeDef gpioinitStructure;
USART_InitTypeDef usart_initStructure;
NVIC_InitTypeDef NVICinitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
//1.配置PA9 TX
gpioinitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
gpioinitStructure.GPIO_Pin=GPIO_Pin_9;
gpioinitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&gpioinitStructure );
//2.PA10 RX
gpioinitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
gpioinitStructure.GPIO_Pin=GPIO_Pin_10;
GPIO_Init(GPIOA,&gpioinitStructure );
usart_initStructure.USART_BaudRate=115200;
usart_initStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
usart_initStructure.USART_Mode=USART_Mode_Rx | USART_Mode_Tx;
usart_initStructure.USART_Parity=USART_Parity_No;
usart_initStructure.USART_StopBits=USART_StopBits_1;
usart_initStructure.USART_WordLength=USART_WordLength_8b;
USART_Init(USART1, &usart_initStructure);
USART_ITConfig(USART1, USART_IT_RXNE , ENABLE );
USART_Cmd(USART1, ENABLE);
NVICinitStructure.NVIC_IRQChannel = USART1_IRQn;
NVICinitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVICinitStructure.NVIC_IRQChannelSubPriority = 1;
NVICinitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVICinitStructure);
}
//发送字符
void USART_SendByte(USART_TypeDef* USARTx, uint16_t Data)
{
USART_SendData(USARTx,Data);
while( USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET);
}
//发送字符串
void USART_SendStr(USART_TypeDef* USARTx, char *str)
{
uint16_t i = 0;
do{
USART_SendByte(USARTx,*(str+i));
i++;
}while(*(str+i) !='\0');
while( USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET);
}
int fputc(int ch, FILE *f)
{
USART_SendData(USART1,(uint8_t)ch);
while( USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
return (ch);
}
int fgetc(FILE *f)
{
while( USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);
return (int) USART_ReceiveData(USART1);
}
3.后面是舵机的使用,其中涉及到PWM波的使用。我们通过配置motor.c来实现舵机的使用,如图棕色为负极线,红色为正极线,橙色为信号线,分别接在地,电源以及我们设置的PB5上。
motor.c
#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);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);//部分重映射时钟 TIM3
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_Motorinit.TIM_Period = 200-1; //设置在下一个更新事件装入活动的自动重装载值
TIM_Motorinit.TIM_Prescaler = 7200-1; //时钟频率预分频值
TIM_TimeBaseInit(TIM3,&TIM_Motorinit);
TIMPWM_Motorinit.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式1
TIMPWM_Motorinit.TIM_OutputState = TIM_OutputState_Enable; //比较输出极性
TIMPWM_Motorinit.TIM_OCPolarity = TIM_OCPolarity_Low; //选择有效输出极性,设置低为有效,即CNT < CCR比较值时为有效
TIM_OC2Init(TIM3, &TIMPWM_Motorinit);
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);
TIM_Cmd( TIM3, ENABLE);
}
3,最后就是main函数,其中pwmval的取值,分别对应着舵机旋转角度的大小。
#include "stm32f10x.h"
#include "main.h"
#include "usart.h"
#include "motor.h"
#include "Systick.h"
#include "HC_SR04.h"
void delay(uint16_t time)
{
uint16_t i=0;
while(time--)
{
i=12000;
while(i--);
}
}
int main()
{
int pwmval = 195;//设置比较值初始为195
float Length = 0;
HC_SR04_config();
usart_init();
motor_config();
while(1)
{
Length = Getlength();
printf("%.3f\r\n",Length);//将超声波距离打印到串口上
ms_delay(500);
if(Length < 5)
{
for(pwmval = 195;pwmval >= 155;pwmval-=15)//周期为200,其中pwmval = 175 180 185 190 195时分别对应舵机旋转角度180° 135° 90° 45° 0°
{
TIM_SetCompare2(TIM3, pwmval);//设置pwmval为比较值
delay(1000);
}
}
else if(Length > 5)
{
TIM_SetCompare2(TIM3, pwmval -20);
}
}
}
最后,我们就可以实现通过超声波测算距离来控制舵机旋转啦。