0、此篇文章内容为使用STM32和openmv(k210)制作一辆智能小车,其功能为小车实现全向移动加自主避障,机器视觉检测户外情况通过wifi模块实时给电脑发送数据(是否有人类出现,有则发送单词“person”,和位置----经纬度),从而实现野外自由巡逻的目的。文章末尾附全部代码和效果视频。
一、小车底盘控制
1、麦克纳姆理轮运动动学解算
机器人上使用麦克纳姆轮底盘以往我们都会使用底盘逆运动学解算,但是由于本人选型时为了减少成本使用直流减速电机来驱动麦克纳姆轮,电机没有实时的数据反馈,使用传统的解算较复杂,于是我使用了较为简单的解算方式。
(1)麦克纳姆轮的安装
如图所示,麦克纳姆轮安装需要两对不同旋向的轮子,图中B轮我们称之为左旋麦克纳姆轮,共两个,反之,A轮为右旋麦克纳姆轮,两对轮子需要对称安装,成对使用。安装完之后,我们把右上角轮子命名为1号轮,逆时针方向依次命名序号累加,右下角A轮为4号轮。
(2)麦克纳姆轮的移动控制
底盘运动方向,需要建立一个坐标系进行讲解,如图所示,底盘前进方向为X轴,左移方向为Y轴,Z轴竖直向上,底盘逆时针旋转为旋转正方向。
一个刚体在平面内具有三个自由度运动,眼前后方向的运动Vx,沿左右方向的运动Vy,绕Z轴的旋转运动Wz。根据底盘三个自由度计算四个麦克纳姆轮的运动的过程,称为底盘的正运动过程。根据底盘速度计算麦克纳姆轮的速度如下表所示。
可见,当底盘四个轮子的速度确定时,小车的速度也就确定了,但是麦克纳姆轮的结构会造成速度损耗,所以在实际移动时真实速度需要进行速度分解,上图仅给出小车的不同运动组合。
为了便于读者使用更精确的运动解算,现给出解算后的底盘运动公式:
刚体平面运动速度分解图:
底盘运动速度计算公式:
其中a代表底盘中心到轮子轴心的X方向距离,b代表底盘中心到轮子轴心的Y方向距离,Vtx, Vty分别代表整车的速度,用底盘中心点的速度(质心速度)计算。
将上面三个式子相加即可得到底盘最终的速度解算式:
此处只给出底盘运动解算结果,底盘运动解算详细过程请参考(链接中的博客):
https://www.cnblogs.com/FangLai-you/p/10867791.html
2、麦克纳姆理轮底盘运动的代码实现(基于STM32标准库,实际效果见文章末尾)
首先,现笼统地讲一下电机驱动,我们知道单片机一般只支持3.3v电压输出,且其电流也是很小的,并不能驱动电机旋转,所以我们需要使用电机驱动来驱动电机,常见的电机驱动有L298N,tb6612,drv8701等,此处使用的是操作最简单的L298N。
如图,输出A和输出B分别接电机的两个角,逻辑输入接单片机的两个方向控制引脚(自己定义的),通道A、B使能分别接两路PWM对电机进行速度调节。
(1)定时器输出1000hz 的PWM
源文件:
#include "stm32f10x.h" // Device header
#include "PWM.h"
/**
* @brief 定时器用于输出PWM,PWM频率为 72000000/(arr+1)/(psc+1) = 1000HZ
* @param 无
* @param 无
* @retval 无
*/
void PWM_Init(void)
{
RCC_APB1PeriphClockCmd( RCC_APB1Periph_TIM4, ENABLE); //打开定时器4时钟
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB , ENABLE); //打开GOIO时钟
TIM_InternalClockConfig(TIM4);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //引脚复用功能
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7 |GPIO_Pin_8 |GPIO_Pin_9 ; //PWM
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 100 - 1; //ARR 自动重装值
TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1; //PSC 预分频值,调节PWM频率
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseInitStructure);
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 50; //CCR,调节占空比,Duty= CCR/ARR+1
TIM_OC1Init(TIM4, &TIM_OCInitStructure); //初始化输出比较通道
TIM_OC2Init(TIM4, &TIM_OCInitStructure);
TIM_OC3Init(TIM4, &TIM_OCInitStructure);
TIM_OC4Init(TIM4, &TIM_OCInitStructure);
TIM_Cmd(TIM4, ENABLE); //打开定时器
}
/**
* @brief 设置输出比较通道1占空比
* @param 需要设置的占空比值 0 -100
* @param 无
* @retval 无
*/
void PWM_SetCompare1(uint16_t Compare) //定时器通道1占空比设置函数
{
TIM_SetCompare1(TIM4, Compare);
}
void PWM_SetCompare2(uint16_t Compare)
{
TIM_SetCompare2(TIM4, Compare);
}
void PWM_SetCompare3(uint16_t Compare)
{
TIM_SetCompare3(TIM4, Compare);
}
void PWM_SetCompare4(uint16_t Compare)
{
TIM_SetCompare4(TIM4, Compare);
}
/**
* @brief PWM限幅,因为(ARR+=1) = 100 , CCR需要大于0小于100
* @param[in] 设定的PWM
* @param[out] 无
* @retval 限幅后的PWM
*/
int16_t pwm_limit(int16_t in_value)
{
if (in_value>100) in_value = 100;
else if(in_value<-100) in_value = -100;
else in_value=in_value;
return in_value;
}
头文件:
#ifndef __PWM_H
#define __PWM_H
void PWM_Init(void);
void PWM_SetCompare3(uint16_t Compare);
void PWM_SetCompare2(uint16_t Compare);
void PWM_SetCompare1(uint16_t Compare);
void PWM_SetCompare4(uint16_t Compare);
int16_t pwm_limit(int16_t in_value);
#endf
(2)使用两个L298N驱动四个直流电机
电机驱动源文件:
#include "stm32f10x.h" // Device header
#include "PWM.h"
#include "motor.h"
/**
* @brief 使用L298N电机驱动模块,每个电机使用两个引脚进行电机方向控制,初始化每个引脚
* @param 无
* @param 无
* @retval 无
*/
void Motor_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = MCKNUM_NEGTIVE_3 | MCKNUM_POSITIVE_3 ; //电机正反引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = MCKNUM_POSITIVE_1 | MCKNUM_NEGTIVE_1 | MCKNUM_NEGTIVE_2 | \
MCKNUM_POSITIVE_2 | MCKNUM_POSITIVE_4 | MCKNUM_NEGTIVE_4 ; //电机正反引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
PWM_Init();
}
/*设置麦克纳姆轮1 的速度*/
/**
* @brief 设置麦克纳姆轮1 的速度
* @param[in] int8_t Mcknum1_Speed 需要设置的速度值
* @param[out] 无
* @retval 无
*/
//这个函数可以四个轮子共用一个,只是需要传入不同的参数,为了写代码简单,我就直接复制了
void Motor_SetMcknum1_Speed(int8_t Mcknum1_Speed)
{
if(Mcknum1_Speed > 0)
{
GPIO_SetBits(MCKNUM_POSITIVE1_Port, MCKNUM_POSITIVE_1);
GPIO_ResetBits(MCKNUM_NEGTIVE1_Port, MCKNUM_NEGTIVE_1);
Mcknum1_Speed =((Mcknum1_Speed>100)?100:Mcknum1_Speed);
PWM_SetCompare1(Mcknum1_Speed);
}
else if(Mcknum1_Speed == 0)
{
GPIO_SetBits(MCKNUM_POSITIVE1_Port, MCKNUM_POSITIVE_1);
GPIO_SetBits(MCKNUM_NEGTIVE1_Port, MCKNUM_NEGTIVE_1);
PWM_SetCompare1(Mcknum1_Speed);
}
else if(Mcknum1_Speed<0)
{
GPIO_ResetBits(MCKNUM_POSITIVE1_Port, MCKNUM_POSITIVE_1);
GPIO_SetBits(MCKNUM_NEGTIVE1_Port, MCKNUM_NEGTIVE_1);
Mcknum1_Speed =((Mcknum1_Speed<-100)?-100:Mcknum1_Speed);
PWM_SetCompare1(-Mcknum1_Speed);
}
}
/**
* @brief 设置麦克纳姆轮2 的速度
* @param[in] int8_t Mcknum1_Speed 需要设置的速度值
* @param[out] 无
* @retval 无
*/
void Motor_SetMcknum2_Speed(int8_t Mcknum2_Speed)
{
if(Mcknum2_Speed > 0)
{
GPIO_SetBits(MCKNUM_POSITIVE2_Port, MCKNUM_POSITIVE_2);
GPIO_ResetBits(MCKNUM_NEGTIVE2_Port, MCKNUM_NEGTIVE_2);
Mcknum2_Speed =((Mcknum2_Speed>100)?100:Mcknum2_Speed);
PWM_SetCompare2(Mcknum2_Speed);
}
else if(Mcknum2_Speed == 0)
{
GPIO_SetBits(MCKNUM_POSITIVE2_Port, MCKNUM_POSITIVE_2);
GPIO_SetBits(MCKNUM_NEGTIVE2_Port, MCKNUM_NEGTIVE_2);
PWM_SetCompare2(Mcknum2_Speed);
}
else if(Mcknum2_Speed<0)
{
GPIO_ResetBits(MCKNUM_POSITIVE2_Port, MCKNUM_POSITIVE_2);
GPIO_SetBits(MCKNUM_NEGTIVE2_Port, MCKNUM_NEGTIVE_2);
Mcknum2_Speed =((Mcknum2_Speed<-100)?-100:Mcknum2_Speed);
PWM_SetCompare2(-Mcknum2_Speed);
}
}
/**
* @brief 设置麦克纳姆轮3 的速度
* @param[in] int8_t Mcknum1_Speed 需要设置的速度值
* @param[out] 无
* @retval 无
*/
void Motor_SetMcknum3_Speed(int8_t Mcknum3_Speed)
{
if(Mcknum3_Speed > 0)
{
GPIO_SetBits(MCKNUM_POSITIVE3_Port, MCKNUM_POSITIVE_3);
GPIO_ResetBits(MCKNUM_NEGTIVE3_Port, MCKNUM_NEGTIVE_3);
Mcknum3_Speed =((Mcknum3_Speed>100)?100:Mcknum3_Speed);
PWM_SetCompare3(Mcknum3_Speed);
}
else if(Mcknum3_Speed == 0)
{
GPIO_SetBits(MCKNUM_POSITIVE3_Port, MCKNUM_POSITIVE_3);
GPIO_SetBits(MCKNUM_NEGTIVE3_Port, MCKNUM_NEGTIVE_3);
PWM_SetCompare3(Mcknum3_Speed);
}
else if(Mcknum3_Speed<0)
{
GPIO_ResetBits(MCKNUM_POSITIVE3_Port, MCKNUM_POSITIVE_3);
GPIO_SetBits(MCKNUM_NEGTIVE3_Port, MCKNUM_NEGTIVE_3);
Mcknum3_Speed =((Mcknum3_Speed<-100)?-100:Mcknum3_Speed);
PWM_SetCompare3(-Mcknum3_Speed);
}
}
/**
* @brief 设置麦克纳姆轮4 的速度
* @param[in] int8_t Mcknum1_Speed 需要设置的速度值
* @param[out] 无
* @retval 无
*/
void Motor_SetMcknum4_Speed(int8_t Mcknum4_Speed)
{
if(Mcknum4_Speed > 0)
{
GPIO_SetBits(MCKNUM_POSITIVE4_Port, MCKNUM_POSITIVE_4);
GPIO_ResetBits(MCKNUM_NEGTIVE4_Port, MCKNUM_NEGTIVE_4);
Mcknum4_Speed =((Mcknum4_Speed>100)?100:Mcknum4_Speed);
PWM_SetCompare4(Mcknum4_Speed);
}
else if(Mcknum4_Speed == 0)
{
GPIO_SetBits(MCKNUM_POSITIVE4_Port, MCKNUM_POSITIVE_4);
GPIO_SetBits(MCKNUM_NEGTIVE4_Port, MCKNUM_NEGTIVE_4);
PWM_SetCompare4(Mcknum4_Speed);
}
else if(Mcknum4_Speed<0)
{
GPIO_ResetBits(MCKNUM_POSITIVE4_Port, MCKNUM_POSITIVE_4);
GPIO_SetBits(MCKNUM_NEGTIVE4_Port, MCKNUM_NEGTIVE_4);
Mcknum4_Speed =((Mcknum4_Speed<-100)?-100:Mcknum4_Speed);
PWM_SetCompare4(-Mcknum4_Speed);
}
}
头文件:
#ifndef __MOTOR_H
#define __MOTOR_H
/*对使用到的引脚宏定义,每一组都一样*/
#define MCKNUM_POSITIVE_1 GPIO_Pin_12 //轮1方向控制角1
#define MCKNUM_NEGTIVE_1 GPIO_Pin_13 //轮1方向控制角2
#define MCKNUM_POSITIVE1_Port GPIOB //轮1时钟
#define MCKNUM_NEGTIVE1_Port GPIOB
#define MCKNUM_POSITIVE_2 GPIO_Pin_0
#define MCKNUM_NEGTIVE_2 GPIO_Pin_1
#define MCKNUM_POSITIVE2_Port GPIOB
#define MCKNUM_NEGTIVE2_Port GPIOB
#define MCKNUM_POSITIVE_3 GPIO_Pin_11
#define MCKNUM_NEGTIVE_3 GPIO_Pin_12
#define MCKNUM_POSITIVE3_Port GPIOA
#define MCKNUM_NEGTIVE3_Port GPIOA //改 2024.3.2
#define MCKNUM_POSITIVE_4 GPIO_Pin_14
#define MCKNUM_NEGTIVE_4 GPIO_Pin_15
#define MCKNUM_POSITIVE4_Port GPIOB
#define MCKNUM_NEGTIVE4_Port GPIOB
void Motor_Init(void);
void Motor_SetMcknum1_Speed(int8_t Mcknum1_Speed);
void Motor_SetMcknum2_Speed(int8_t Mcknum2_Speed);
void Motor_SetMcknum3_Speed(int8_t Mcknum3_Speed);
void Motor_SetMcknum4_Speed(int8_t Mcknum4_Speed);
#endif
(3)小车行为控制(前面函数的封装,便于调用和增强代码可读性)
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "Motor.h"
void Car_Init()
{
Motor_Init();
}
/*************************************************
实现小车各行为的函数
函数可以使用一个,此为为了调用方便理解每一行为出一个函数(其实是懒)
************************************************/
/*直走,速度为 轮1:+ 轮2:+ 轮3:+ 轮4:+ */
void Go_Ahead(int8_t Mcknum1_speed,uint8_t Mcknum2_speed,uint8_t Mcknum3_speed,uint8_t Mcknum4_speed)
{
Motor_SetMcknum1_Speed(Mcknum1_speed);
Motor_SetMcknum2_Speed(Mcknum2_speed);
Motor_SetMcknum3_Speed(Mcknum3_speed);
Motor_SetMcknum4_Speed(Mcknum4_speed);
}
/*静止,速度为 轮1:0 轮2:0 轮3:0 轮4:0 */
void Car_Stop(int8_t Mcknum1_speed,uint8_t Mcknum2_speed,uint8_t Mcknum3_speed,uint8_t Mcknum4_speed)
{
Motor_SetMcknum1_Speed(Mcknum1_speed);
Motor_SetMcknum2_Speed(Mcknum2_speed);
Motor_SetMcknum3_Speed(Mcknum3_speed);
Motor_SetMcknum4_Speed(Mcknum4_speed);
}
/*后退,速度为 轮1:- 轮2:- 轮3:- 轮4:- */
void Go_Back(int8_t Mcknum1_speed,uint8_t Mcknum2_speed,uint8_t Mcknum3_speed,uint8_t Mcknum4_speed)
{
Motor_SetMcknum1_Speed(Mcknum1_speed);
Motor_SetMcknum2_Speed(Mcknum2_speed);
Motor_SetMcknum3_Speed(Mcknum3_speed);
Motor_SetMcknum4_Speed(Mcknum4_speed);
}
/*向左移动,速度为 轮1:+ 轮2:- 轮3:+ 轮4:- */
void GoStraight_Left(int8_t Mcknum1_speed,uint8_t Mcknum2_speed,uint8_t Mcknum3_speed,uint8_t Mcknum4_speed)
{
Motor_SetMcknum1_Speed(Mcknum1_speed);
Motor_SetMcknum2_Speed(Mcknum2_speed);
Motor_SetMcknum3_Speed(Mcknum3_speed);
Motor_SetMcknum4_Speed(Mcknum4_speed);
}
/*向右移动,速度为 轮1:- 轮2:+ 轮3:- 轮4:+ */
void GoStraight_Right(int8_t Mcknum1_speed,uint8_t Mcknum2_speed,uint8_t Mcknum3_speed,uint8_t Mcknum4_speed)
{
Motor_SetMcknum1_Speed(Mcknum1_speed);
Motor_SetMcknum2_Speed(Mcknum2_speed);
Motor_SetMcknum3_Speed(Mcknum3_speed);
Motor_SetMcknum4_Speed(Mcknum4_speed);
}
/*逆时针转动,速度为 轮1:+ 轮2:- 轮3:- 轮4:+ */
void Anticlockwise_Rotate(int8_t Mcknum1_speed,uint8_t Mcknum2_speed,uint8_t Mcknum3_speed,uint8_t Mcknum4_speed)
{
Motor_SetMcknum1_Speed(Mcknum1_speed);
Motor_SetMcknum2_Speed(Mcknum2_speed);
Motor_SetMcknum3_Speed(Mcknum3_speed);
Motor_SetMcknum4_Speed(Mcknum4_speed);
}
/*顺时针转动,速度为 轮1:- 轮2:+ 轮3:+ 轮4:- */
void Clockwise_Rotate(int8_t Mcknum1_speed,uint8_t Mcknum2_speed,uint8_t Mcknum3_speed,uint8_t Mcknum4_speed)
{
Motor_SetMcknum1_Speed(Mcknum1_speed);
Motor_SetMcknum2_Speed(Mcknum2_speed);
Motor_SetMcknum3_Speed(Mcknum3_speed);
Motor_SetMcknum4_Speed(Mcknum4_speed);
}
按照这个代码的注释接好线,至此,底盘就可以按照你希望的方向实现全向移动了。
二、使用超声波进行避障
1、超声波的使用方式
简单来说就是将超声波供电,TRIG引脚给一个持续至少10微秒的高电平,开始触发测距,在测距触发后连续扫描ECHO引脚的高电平持续时间,并记下,由公式 距离 = 持续时间x声速/2 ,计算距离。
2、超声波测距代码实现
(1)定时器做一个10us的定时用于记录返回信号时高电平的持续时间
源文件:
#include "stm32f10x.h" // Device header
#include "Timer.h"
#include "car.h"
#include "IC.h"
extern uint16_t Time_M;
extern uint16_t dis_M;
/**
* @brief 定时1初始化,定时周期10Us
* @param 无
* @param 无
* @retval 无
*/
void Timer1_Init()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); //选择APB2总线下的定时器Timer1
Time_M = 0;
TIM_InternalClockConfig(TIM1); //TIM1使用内部时钟
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数模式,此处为向上计数
TIM_TimeBaseInitStructure.TIM_Period = 7199; //ARR 1 = 0.0001S
TIM_TimeBaseInitStructure.TIM_Prescaler = 0; //PSC
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //高级计时器特有,重复计数
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStructure);
TIM_ClearFlag(TIM1, TIM_FLAG_Update);
TIM_ITConfig(TIM1, TIM_IT_Update, ENABLE); //使能中断
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM1_UP_IRQn; //中断通道选择
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //优先级,同上
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM1, ENABLE); //打开定时器
}
extern uint16_t distance;
/**
* @brief 定时1中断函数,计算经历的时间
* @param 无
* @param 无
* @retval 无
*/
void TIM1_UP_IRQHandler(void) //定时器2的中断函数
{
if(TIM_GetITStatus(TIM1, TIM_IT_Update) == SET)
{
TIM_ClearITPendingBit(TIM1, TIM_IT_Update); //清空标志位
if (GPIO_ReadInputDataBit(Trig_PortA, Echo_Pin_M) == 1) //接收引脚有高电平
{
Time_M ++;
}
}
}
/**
* @brief 两次距离差判断是否在一定范围内
* @param 无
* @param 无
* @retval 1:两次距离差大于5, 0:两次距离只差小于5
*/
uint8_t Overtime_Judge(uint16_t now_distance,uint16_t last_distance)
{
int8_t error;
error = now_distance - last_distance;
if(error>5 || error<-5)
{
return 1;
}
return 0;
}
头文件:
#ifndef __TIMER_H
#define __TIMER_H
#include "HCSR04.h"
#define AUTO_GO_APEEED 70 //小车前进最大速度
#define AUTO_LR_APEEED 100 //左移最大速度
#define AUTO_TURN_APEEED 90 //旋转最大速度
void Timer1_Init();
void Timer2_Init();
uint8_t Overtime_Judge(uint16_t now_distance,uint16_t last_distance);
#endif
(2)超声波测距及初始化
源文件:
#include "stm32f10x.h" // Device header
#include "HCSR04.h"
#include "Timer.h"
#include "Delay.h"
uint16_t Time_M,Time_R;
/**
* @brief 超声波引脚初始化 ,发射 ,接收引脚
* @param 无
* @param 无
* @retval 无
*/
void HCSR04_Init()
{
RCC_APB2PeriphClockCmd(Trig_RCCA , ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Pin = Trig_Pin_M ;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(Trig_PortA, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPD;
GPIO_InitStruct.GPIO_Pin = Echo_Pin_M ;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(Trig_PortA, &GPIO_InitStruct);
GPIO_ResetBits(Trig_PortA, Echo_Pin_M);
}
/**
* @brief 中间超声波开始测距
* @param 无
* @param 无
* @retval 无
*/
void HCSR04_Start_M()
{
GPIO_SetBits(Trig_PortA, Trig_Pin_M);
Delay_us(45);
GPIO_ResetBits(Trig_PortA, Trig_Pin_M);
Timer1_Init();
}
/**
* @brief 右边超声波计算距离
* @param 无
* @param 无
* @retval 算出的实际距离
*/
uint16_t HCSR04_GetValue_M()
{
HCSR04_Start_M();
Delay_ms(50);
return ((Time_M * 0.0001) * 34000) / 2; //0.0001S即100Us 340m/s=34000cm/s 经历的时间 * 声速/2 = 距离
}
头文件:
#ifndef __HCSR04_H
#define __HCSR04_H
//超声波引脚即时钟定义
#define Trig_PortA GPIOA
#define Trig_Pin_M GPIO_Pin_3
#define Trig_RCCA RCC_APB2Periph_GPIOA
#define Echo_PortA GPIOA
#define Echo_Pin_M GPIO_Pin_2
#define Echo_RCCA RCC_APB2Periph_GPIOA
void HCSR04_Init();
//void HCSR04_GetValue(uint16_t *dis_M,uint16_t *dis_R,uint16_t *dis_L);
void HCSR04_Start_M();
uint16_t HCSR04_GetValue_M();
#endif
主函数简易避障代码
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "Motor.h"
#include "Serial.h"
#include "Car.h"
#include "Delay.h"
#include "IC.h"
#include "HCSR04.h"
#include "Timer.h"
uint16_t CH1_leftright,CH2_updown,distance;
uint8_t Ahead_value,Left_value,Rotate_value;
uint16_t dis_M;
Car_mode_t car_mode;
uint8_t last_carmode;
void main(void)
{
Car_Init();
IC_Init();
HCSR04_Init();
uint8_t Control_flag = 0;
Delay_ms(6000);
while (1)
{
Delay_ms(10);
dis_M = HCSR04_GetValue_M();
CH1_leftright=IC2_Get_Control_Value();
CH2_updown=IC1_Get_Control_Value();
if(dis_M>50) Go_Ahead(-AUTO_GO_APEEED,-AUTO_GO_APEEED,-AUTO_GO_APEEED,-AUTO_GO_APEEED);
else if(dis_M<=50)
{
GoStraight_Right(AUTO_LR_APEEED,-AUTO_LR_APEEED,AUTO_LR_APEEED,-AUTO_LR_APEEED);
Delay_ms(600);
dis_M = HCSR04_GetValue_M();
if(dis_M<=50)
{
Anticlockwise_Rotate(-AUTO_TURN_APEEED,AUTO_TURN_APEEED,AUTO_TURN_APEEED,-AUTO_TURN_APEEED);
Delay_ms(550);
}
else continue;
}
}
}
自此,只需要在主循环读取距离并进行相关处理,小车就可实现简易避障了。(效果文章末尾展示)
三、GPS模块位置读取
1、认识模块
可见,只需要给模块接上电,并使用串口协议就可以读取GPS模块采集的位置信息了。由于底层代码讲解比较麻烦并且我也是移植商家的例程,此处不再讲解,并将相关资料放在文章末尾。
四、openmv物体检测、云台巡航、WIFI通信
简单了解K210:
k210是国内仿照openmv做的一个嵌入式机器视觉平台,其集成了传统的单片机的基本外设功能,亮点就是它具备机器视觉功能,能帮助嵌入式学习者快速做出自己的产品。
这是一个k210核心板引脚示意图,在官网可进行更深入学习,如果有python基础的话,还是上手很快的,代码中使用的开发环境为Maixpy IDE。
k210官网:MaixPy 文档简介 - Sipeed Wiki
1、openmv物体检测
(1)功能
对已经训练好的20中模型进行识别,并在LCD屏幕上进行显示,框选识别到的物体,并打印物体的英文单词字符串(主要检测人类)。
(2)实际代码:
自此,摄像头已经能实现检测20种模型并在LCD屏幕上显示(效果放在文章末尾)
2、云台自动巡航找目标
(1)功能
两轴云台连续在限定的范围内周期性旋转,YAW轴和PITCH轴在主循环中每隔3毫秒转动一度。
(2)代码实现
自此,云台已经能实现连续巡航找寻目标了(效果文章末尾展示)
3、WIFI通信
(1)功能
当在巡逻过程中发现人类时,STM32向K210发送当前位置信息,k210的WIFI模块向电脑发送字符串“person”和当前位置信息,实现巡逻的目的。(单片机需要单独开一路串口给K210发数据,STM32的串口1,对应k210的串口1接收单片机发送的数据,stm32串口2用于和GPS模块通信)
(2)实际代码
自此,已经实现了,底盘的任意运动,小车的避障,GPS位置数据的采集,物体(主要人类)识别,云台两轴分别大范围巡航,小车远程和电脑通信。
五、功能展示(视频)
效果演示
以上功能实现中用到的所有代码我已经整理成单独的文件和整合版本放在这里,需要的小伙伴可以自取哦。
链接:https://pan.baidu.com/s/1_DfKcqWOHGILM7jKOGVkig?pwd=3814
提取码:3814