一、项目概述
该智能小车具有循迹、跟随、避障三种模式,可以通过SU-03语音识别模块实现三种模式的切换,并且在oled显示屏显示当前模式。在循迹模式下,小车可以通过循迹模块跟随赛道中的黑线位置实时改变方向,并沿赛道前行;在跟随模式下,小车可以通过跟随模块实时跟随前方物体而移动;在避障模式下,小车利用超声波模块检测前方距离,并根据距离判断障碍物位置,并及时躲避前方障碍。
二、硬件部分
1.小车底盘套件
2.32开发板
3.电池盒(4节)
4.面包板
5.L9110s电机驱动模块
6.循迹模块(2个)
7.红外跟随模块(2个)
8.SU-03语音模块
9.OLED显示屏
10.超声波
11.sg-90舵机
12.杜邦线若干
详细硬件模块介绍见【51单片机篇】智能小车博文
•硬件接线
•电机驱动模块
B-1A -- PB1 B-2A -- PB0 A-1A -- PB10 A-1B -- PB2
•循迹小车
循迹模块(左) -- PB3 循迹模块(右) -- PB4
•跟随小车
跟随模块(左) -- PA8 跟随模块(右) -- PA9
•避障小车
sg90:PB9 Trig:PA10 Echo:PA11
•OLED模块
SCL -- PB6 SDA -- PB7
•语音模块
A25 -- PA15(循迹) A26 -- PA13(跟随) A27 -- PA14(避障)
三、软件部分
main.c
#define MIDDLE 0
#define LEFT 1
#define RIGHT 2
#define BZ 1
#define XJ 2
#define GS 3
#define ST 4
#define LeftWheel_Value_XJ HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3)
#define RightWheel_Value_XJ HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_4)
#define LeftWheel_Value_GS HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_8)
#define RightWheel_Value_GS HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_9)
#define A25 HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_15)
#define A26 HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_13)
#define A27 HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_14)
char dir;
void xunjiMode()
{
if(LeftWheel_Value_XJ == GPIO_PIN_RESET && RightWheel_Value_XJ == GPIO_PIN_RESET)
goForward();
if(LeftWheel_Value_XJ == GPIO_PIN_SET && RightWheel_Value_XJ == GPIO_PIN_RESET)
goLeft();
if(LeftWheel_Value_XJ == GPIO_PIN_RESET && RightWheel_Value_XJ == GPIO_PIN_SET)
goRight();
if(LeftWheel_Value_XJ == GPIO_PIN_SET && RightWheel_Value_XJ == GPIO_PIN_SET)
stop();
}
void gensuiMode()
{
if(LeftWheel_Value_GS == GPIO_PIN_RESET && RightWheel_Value_GS == GPIO_PIN_RESET)
goForward();
if(LeftWheel_Value_GS == GPIO_PIN_SET && RightWheel_Value_GS == GPIO_PIN_RESET)
goRight();
if(LeftWheel_Value_GS == GPIO_PIN_RESET && RightWheel_Value_GS == GPIO_PIN_SET)
goLeft();
if(LeftWheel_Value_GS == GPIO_PIN_SET && RightWheel_Value_GS == GPIO_PIN_SET)
stop();
}
void bizhangMode()
{
double disMiddle;
double disLeft;
double disRight;
if(dir != MIDDLE){
sgMiddle();
dir = MIDDLE;
HAL_Delay(450);
}
disMiddle = get_distance();
if(disMiddle > 35){
//前进
goForward();
}else if(disMiddle < 10){
goBack();
}else
{
//停止
stop();
//测左边距离
sgLeft();
HAL_Delay(450);
disLeft = get_distance();
sgMiddle();
HAL_Delay(450);
sgRight();
dir = RIGHT;
HAL_Delay(450);
disRight = get_distance();
if(disLeft < disRight){
goRight();
HAL_Delay(150);
stop();
}
if(disRight < disLeft){
goLeft();
HAL_Delay(150);
stop();
}
}
HAL_Delay(50);
}
main函数里:
initSG90();
HAL_Delay(1000);
dir = MIDDLE;
Oled_Init();
Oled_Screen_Clear();
Oled_Show_Str(2,2,"-----Ready----");
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
//满足寻迹模式的条件
if(A25 == 0 && A26 == 1 && A27 == 1){
if(mark != XJ){
Oled_Screen_Clear();
Oled_Show_Str(2,2,"-----XunJi----");
}
mark = XJ;
xunjiMode();
}
//满足跟随模式的条件
if(A25 == 1 && A26 == 0 && A27 == 1){
if(mark != GS){
Oled_Screen_Clear();
Oled_Show_Str(2,2,"-----GenSui----");
}
mark = GS;
gensuiMode();
}
//满足避障模式的条件
if(A25 == 1 && A26 == 1 && A27 == 0){
if(mark != BZ){
Oled_Screen_Clear();
Oled_Show_Str(2,2,"-----BiZhang----");
}
mark = BZ;
bizhangMode();
}
//停下
if(A25 == 0 && A26 == 0 && A27 == 0){
if(mark != ST){
Oled_Screen_Clear();
Oled_Show_Str(2,2,"----Stop----");
}
mark = ST;
stop();
}
}
/* USER CODE END 3 */
}
motor.c
#include "motor.h"
#include "gpio.h"
void goForward(void)
{
//左轮
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_2,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_10,GPIO_PIN_RESET);
//右轮
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_RESET);
}
void goBack(void)
{
//左轮
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_2,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_10,GPIO_PIN_SET);
//右轮
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_SET);
}
void goLeft(void)
{
//左轮
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_2,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_10,GPIO_PIN_RESET);
//右轮
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_RESET);
}
void goRight(void)
{
//左轮
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_2,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_10,GPIO_PIN_RESET);
//右轮
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_RESET);
}
void stop(void)
{
//左轮
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_2,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_10,GPIO_PIN_RESET);
//右轮
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_RESET);
}
sr04.c(超声波)
#include "sr04.h"
#include "gpio.h"
#include "tim.h"
void TIM2_Delay_us(uint16_t n_us)
{
/* 使能定时器2计数 */
__HAL_TIM_ENABLE(&htim2);
__HAL_TIM_SetCounter(&htim2, 0);
while(__HAL_TIM_GetCounter(&htim2) < ((1 * n_us)-1) );
/* 关闭定时器2计数 */
__HAL_TIM_DISABLE(&htim2);
}
double get_distance(void)
{
int cnt=0;
//1. Trig ,给Trig端口至少10us的高电平
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_10, GPIO_PIN_SET);//拉高
TIM2_Delay_us(20);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_10, GPIO_PIN_RESET);//拉低
//2. echo由低电平跳转到高电平,表示开始发送波
//波发出去的那一下,开始启动定时器
while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_11) == GPIO_PIN_RESET);//等待输入电平拉高
HAL_TIM_Base_Start(&htim2);
__HAL_TIM_SetCounter(&htim2,0);
//3. 由高电平跳转回低电平,表示波回来了
while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_11) == GPIO_PIN_SET);//等待输入电平变低
//波回来的那一下,我们开始停止定时器
HAL_TIM_Base_Stop(&htim2);
//4. 计算出中间经过多少时间
cnt = __HAL_TIM_GetCounter(&htim2);
//5. 距离 = 速度 (340m/s)* 时间/2(计数1次表示1us)
return (cnt*340/2*0.000001*100); //单位:cm
}
sg90.c(舵机)
#include "sg90.h"
#include "gpio.h"
#include "tim.h"
void initSG90(void)
{
HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_4); //启动定时器4
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_4, 17); //将舵机置为0度
}
void sgMiddle(void)
{
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_4, 17); //将舵机置为90度
}
void sgRight(void)
{
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_4, 5); //将舵机置为0度
}
void sgLeft(void)
{
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_4, 25); //将舵机置为180度
}
oled.c
#include "oled.h"
#include "i2c.h"
#include "oledfont.h"
void Oled_Write_Cmd(uint8_t dataCmd)
{
HAL_I2C_Mem_Write(&hi2c1, 0x78, 0x00, I2C_MEMADD_SIZE_8BIT,
&dataCmd, 1, 0xff);
}
void Oled_Write_Data(uint8_t dataData)
{
HAL_I2C_Mem_Write(&hi2c1, 0x78, 0x40, I2C_MEMADD_SIZE_8BIT,
&dataData, 1, 0xff);
}
void Oled_Init(void){
Oled_Write_Cmd(0xAE);//--display off
Oled_Write_Cmd(0x00);//---set low column address
Oled_Write_Cmd(0x10);//---set high column address
Oled_Write_Cmd(0x40);//--set start line address
Oled_Write_Cmd(0xB0);//--set page address
Oled_Write_Cmd(0x81); // contract control
Oled_Write_Cmd(0xFF);//--128
Oled_Write_Cmd(0xA1);//set segment remap
Oled_Write_Cmd(0xA6);//--normal / reverse
Oled_Write_Cmd(0xA8);//--set multiplex ratio(1 to 64)
Oled_Write_Cmd(0x3F);//--1/32 duty
Oled_Write_Cmd(0xC8);//Com scan direction
Oled_Write_Cmd(0xD3);//-set display offset
Oled_Write_Cmd(0x00);//
Oled_Write_Cmd(0xD5);//set osc division
Oled_Write_Cmd(0x80);//
Oled_Write_Cmd(0xD8);//set area color mode off
Oled_Write_Cmd(0x05);//
Oled_Write_Cmd(0xD9);//Set Pre-Charge Period
Oled_Write_Cmd(0xF1);//
Oled_Write_Cmd(0xDA);//set com pin configuartion
Oled_Write_Cmd(0x12);//
Oled_Write_Cmd(0xDB);//set Vcomh
Oled_Write_Cmd(0x30);//
Oled_Write_Cmd(0x8D);//set charge pump enable
Oled_Write_Cmd(0x14);//
Oled_Write_Cmd(0xAF);//--turn on oled panel
}
void Oled_Screen_Clear(void){
char i,n;
Oled_Write_Cmd (0x20); //set memory addressing mode
Oled_Write_Cmd (0x02); //page addressing mode
for(i=0;i<8;i++){
Oled_Write_Cmd(0xb0+i); //éè??ò3μ??·£¨0~7£?
Oled_Write_Cmd(0x00); //éè????ê??????aáDμíμ??·
Oled_Write_Cmd(0x10); //éè????ê??????aáD??μ??·
for(n=0;n<128;n++)Oled_Write_Data(0x00);
}
}
void Oled_Show_Char(char row,char col,char oledChar){ //row*2-2
unsigned int i;
Oled_Write_Cmd(0xb0+(row*2-2)); //page 0
Oled_Write_Cmd(0x00+(col&0x0f)); //low
Oled_Write_Cmd(0x10+(col>>4)); //high
for(i=((oledChar-32)*16);i<((oledChar-32)*16+8);i++){
Oled_Write_Data(F8X16[i]); //写数据oledTable1
}
Oled_Write_Cmd(0xb0+(row*2-1)); //page 1
Oled_Write_Cmd(0x00+(col&0x0f)); //low
Oled_Write_Cmd(0x10+(col>>4)); //high
for(i=((oledChar-32)*16+8);i<((oledChar-32)*16+8+8);i++){
Oled_Write_Data(F8X16[i]); //写数据oledTable1
}
}
/******************************************************************************/
// 函数名称:Oled_Show_Char
// 输入参数:oledChar
// 输出参数:无
// 函数功能:OLED显示单个字符
/******************************************************************************/
void Oled_Show_Str(char row,char col,char *str){
while(*str!=0){
Oled_Show_Char(row,col,*str);
str++;
col += 8;
}
}
代码太多,在这不一 一展示,有需要的去看源码资源。
https://gitee.com/GeekerGao/stm32-intelligent-car
四.项目实现
语音控制智能小车