电源模块选用的是18650锂电池(充电器及电池底座)、3.3v稳压模块。
买的智能车带有四个电机,选用L298N电机驱动板对电机进行驱动。
巡迹选用四路红外模块实现,避障选用超声波模块HC-SR04实现。
热熔胶枪,焊笔,杜邦线,主控制器STM32f103c8t6核心板,烧录工具(usb转ttl模块、ST-Link或J-Link)等。
将电池组引出线的正极接L298N模块的+12V(Vcc)端,负极接L298N模块的GND端,再将L298N的+5V端和GND端与3.3V稳压模块的5V与GND分别相连。最后把稳压模块的3.3V和GND与STM32核心板上的3.3V与GND相连。
将一侧电机的两极分别与L298N模块的Out1和Out2相连,另一侧的电机与Out3和Out4相连(写程序时测试正转与反转如何控制)。将L298N上的IN1,IN2,IN3,IN4分别与STM32核心板上的四个不同的IO口连接(本人连接的是PA7,PA6,PB1,PB0)。
四个红外模块与主控板上对应的端口相连即可。主控板上的3.3V与GND与STM32核心板上的3.3V和GND相连,IN1,IN2,IN3,IN4分别与STM32核心板上的四个不同的IO口连接(本人连接的是PB4,PB5,PB6,PB7)。
HC-SR04上的5V与GND与STM32核心板上的5V和GND相连,TRIG与ECHO与STM32核心板上的两个不同的IO口连接(本人连接的是PB11,PB10)。
编程思路:控制核心板IO口的电平(即电机驱动模块的IN1,IN2,IN3,IN4)从而控制Out1、Out2、Out3、Out4的电平高低,进而控制电机使其正转、反转、不转。由于单纯地拉高拉低电平只能让电机在转与不转的状态切换,故决定利用TIM3的四个通道分别产生四路PWM信号来控制电机速度。
/*Head File*/
#ifndef _PWM_H
#define _PWM_H
#include
#include
#define PWMA1 TIM3->CCR1
#define PWMA2 TIM3->CCR2
#define PWMB1 TIM3->CCR3
#define PWMB2 TIM3->CCR4
void PWM_Init(uint16_t Arr,uint16_t Psc);
void Set_LeftMotor(int speed);
void Set_RightMotor(int speed);
#endif
#include
void PWM_Init(uint16_t Arr,uint16_t Psc){
GPIO_InitTypeDef GPIO_InitStruct;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_OCInitTypeDef TIM_OCInitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
TIM_TimeBaseStructInit(&TIM_TimeBaseInitStruct);
TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period=Arr;
TIM_TimeBaseInitStruct.TIM_Prescaler=Psc;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStruct);
TIM_OCInitStruct.TIM_OCMode=TIM_OCMode_PWM1;
TIM_OCInitStruct.TIM_OCPolarity=TIM_OCPolarity_High;
TIM_OCInitStruct.TIM_OutputState=TIM_OutputState_Enable;
TIM_OCInitStruct.TIM_Pulse=0;
TIM_OC1Init(TIM3,&TIM_OCInitStruct );
TIM_OC2Init(TIM3,&TIM_OCInitStruct );
TIM_OC3Init(TIM3,&TIM_OCInitStruct );
TIM_OC4Init(TIM3,&TIM_OCInitStruct);
TIM_OC1PreloadConfig(TIM3,TIM_OCPreload_Enable);
TIM_OC2PreloadConfig(TIM3,TIM_OCPreload_Enable);
TIM_OC3PreloadConfig(TIM3,TIM_OCPreload_Enable);
TIM_OC4PreloadConfig(TIM3,TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM3,ENABLE);
TIM_Cmd(TIM3,ENABLE);
}
void Set_RightMotor(int speed){
if(speed<0) PWMA1=2000,PWMA2=2000+speed;
else PWMA2=2000,PWMA1=2000-speed;
}
void Set_LeftMotor(int speed){
if(speed<0) PWMB1=2000,PWMB2=2000+speed;
else PWMB2=2000,PWMB1=2000-speed;
}
编程思路:检测到黑线时红外模块将会把返回高电平,否则返回低电平。
故只需要读取对应的IO端口电平用以控制小车转向从而实现巡线。(由于在自己铺的简易赛道上四路红外巡线效果和两路巡线效果差不多,所以只用了中间两个红外模块进行巡线,外侧两个用来辅助超声波模块实现避障)
#ifndef _SENSOR_H
#define _SENSOR_H
#include
void Sensor_Init(void);
unsigned char Sensor_Detect(void);
#endif
#include
#include
#define STRAIGHT 0
#define LEFTS 1
#define LEFTL 2
#define RIGHTS 3
#define RIGHTL 4
#define STOP 5
#define BACK 6
uint8_t left1;
uint8_t left0;
uint8_t right1;
uint8_t right0;
uint8_t counter=0;
float length;
void Sensor_Init(void){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IN_FLOATING;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_8|GPIO_Pin_9;
GPIO_Init(GPIOB,&GPIO_InitStruct);
}
unsigned char Sensor_Detect(void){
left1=GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_9);
left0=GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_8);
right0=GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7);
right1=GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_6);
if(left0==1&&right0==0)return LEFTS;
else if(left1==0&&right1==1)return LEFTL;
else if(left0==0&&right0==1)return RIGHTS;
else if(left1==1&&right1==0)return RIGHTL;
else if(left1==1&&left0==1&&right0==1&&right1==1)return STOP;
else if(left1==0&&right1==0)return BACK;
else return STRAIGHT;
}
/*Head File*/
#ifndef __HC_H
#define __HC_H
#include "sys.h"
void hcsr04_NVIC(void);
void Hcsr04Init(void);
static void OpenTimerForHc(void);
static void CloseTimerForHc(void);
void TIM4_IRQHandler(void);
u32 GetEchoTimer(void);
float Hcsr04GetLength(void);
#endif
#include "hc.h"
#include "delay.h"
#include "sys.h"
#include "usart.h"
#include "led.h"
#define TRIG_Send PBout(11)
#define ECHO_Reci PBin(10)
#define HCSR04_PORT GPIOB
#define HCSR04_CLK RCC_APB2Periph_GPIOB
#define HCSR04_TRIG GPIO_Pin_11
#define HCSR04_ECHO GPIO_Pin_10
u16 msHcCount = 0;
void hcsr04_NVIC()
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void Hcsr04Init()
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(HCSR04_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin =HCSR04_TRIG;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(HCSR04_PORT, &GPIO_InitStructure);
GPIO_ResetBits(HCSR04_PORT,HCSR04_TRIG);
GPIO_InitStructure.GPIO_Pin = HCSR04_ECHO;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(HCSR04_PORT, &GPIO_InitStructure);
GPIO_ResetBits(HCSR04_PORT,HCSR04_ECHO);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
TIM_DeInit(TIM2);
TIM_TimeBaseStructure.TIM_Period = (1000-1);
TIM_TimeBaseStructure.TIM_Prescaler =(72-1);
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
TIM_ClearFlag(TIM4, TIM_FLAG_Update);
TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE);
hcsr04_NVIC();
TIM_Cmd(TIM4,DISABLE);
}
static void OpenTimerForHc()
{
TIM_SetCounter(TIM4,0);
msHcCount = 0;
TIM_Cmd(TIM4, ENABLE);
}
static void CloseTimerForHc()
{
TIM_Cmd(TIM4, DISABLE);
}
void TIM4_IRQHandler(void)
{
if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM4, TIM_IT_Update );
msHcCount++;
}
}
u32 GetEchoTimer(void)
{
u32 t = 0;
t = msHcCount*1000;
t += TIM_GetCounter(TIM4);
TIM4->CNT = 0;
Delay_ms(50);
return t;
}
float Hcsr04GetLength(void )
{
u32 t = 0;
int i = 0;
float lengthTemp = 0;
float sum = 0;
while(i!=5)
{
TRIG_Send = 1;
Delay_us(20);
TRIG_Send = 0;
while(ECHO_Reci == 0);
OpenTimerForHc();
i = i + 1;
while(ECHO_Reci == 1);
CloseTimerForHc();
t = GetEchoTimer();
lengthTemp = ((float)t/58.0);//cm
sum = lengthTemp + sum ;
}
lengthTemp = sum/5.0;
return lengthTemp;
}
控制小车的代码主要在Speed_Control()函数中编写。如果不使用超声波模块的话,也可以在定时器1的中断服务函数中编写。
/*Head File*/
#ifndef _CONTROL_H
#define _CONTROL_H
void TIM1_UP_IRQHandler(void);
void Speed_Control(unsigned char Flag,float length);
#endif
#include
#include
#include
#include
#include
#include
#define STRAIGHT 0
#define LEFTS 1
#define LEFTL 2
#define RIGHTS 3
#define RIGHTL 4
#define STOP 5
#define BACK 6
extern uint8_t left0;
extern uint8_t left1;
extern uint8_t right0;
extern uint8_t right1;
extern float length;
void TIM1_UP_IRQHandler(void){
if(TIM_GetITStatus(TIM1,TIM_IT_Update)==SET){
}
TIM_ClearITPendingBit(TIM1,TIM_IT_Update);
}
void Speed_Control(unsigned char Flag,float length){
if(Flag==STOP){
Set_LeftMotor(0);
Set_RightMotor(0);
}
if(length<18.0){
if(Flag==RIGHTL){
Set_LeftMotor(-2000);
Set_RightMotor(2000);
}
else if(Flag==LEFTL){
Set_LeftMotor(2000);
Set_RightMotor(-2000);
}
else{
Set_LeftMotor(-2000);
Set_RightMotor(2000);
}
}
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define STRAIGHT 0
#define LEFTS 1
#define LEFTL 2
#define RIGHTS 3
#define RIGHTL 4
#define STOP 5
#define BACK 6
extern float length;
int main(){
Led_Init();
Delay_Init();
Sensor_Init();
Hcsr04Init();
PWM_Init(1999,71);
TIM1_Init(1999,71);
TIM2_Init(1999,71);
uart_init(115200);
OLED_Init();
while(1){
int flag=Sensor_Detect();
Speed_Control(flag,Hcsr04GetLength());
}
}
整个工程参考了许多不同的代码(简称究极缝合怪),故代码风格不统一。由于调试过程中把红外模块撞坏了,所以控制代码没有写得很完善,但是各个模块工作正常。在加入超声波模块之前,对巡迹功能进行了测试,虽然速度比较慢,但还是能完成巡迹工作。加入超声波模块后,也能实现较高精度的障碍检测(可以多用几个超声波模块,这样避障功能更完善)。
工程链接:https://pan.baidu.com/s/1CslspOudO7mCGkAVzEmbaQ?pwd=14j9
提取码:14j9
视频演示:循迹功能展示