STM32智能小车(循迹、跟随、避障、测速、蓝牙、wifi、4g、语音识别)总结
前言
有需要帮忙代做51和32小车或者其他单片机项目,课程设计,报告,PCB原理图的小伙伴,可以在文章最下方加我V交流咨询,本篇文章的小车所有功能实现的代码还有硬件清单放在资源包里,有需要的自行下载即可!
目录
1.电机模块开发
1.1 让小车动起来
1.2 串口控制小车方向
1.3 如何进行小车PWM调速
1.4 PWM方式实现小车转向
2.循迹小车
2.1 循迹模块使用
2.2 循迹小车原理
2.3 循迹小车核心代码
2.4 循迹小车解决转弯平滑问题
3.跟随/避障小车
3.1 红外壁障模块分析编辑
3.2 跟随小车的原理
3.3 跟随小车开发和调试代码
3.4 超声波模块介绍
3.5 舵机模块介绍
3.6 摇头避障小车开发和调试代码
4.测速小车
4.1 测速模块
4.2 测试原理和单位换算
4.3 定时器和中断实现测速开发和调试代码
4.4 小车速度显示在OLED屏
5.远程控制小车
5.1 蓝牙控制小车
5.2 蓝牙控制并测速小车
5.3 wifi控制测速小车
5.4 4g控制小车
6.语音控制小车
6.1语音模块配置:
6.2 语音控制小车开发和调试代码
1.电机模块开发
L9110s概述
接通VCC,GND 模块电源指示灯亮, 以下资料来源官方,具体根据实际调试
IA1输入高电平,IA1输入低电平,【OA1 OB1】电机正转;
IA1输入低电平,IA1输入高电平,【OA1 OB1】电机反转;
IA2输入高电平,IA2输入低电平,【OA2 OB2】电机正转;
IA2输入低电平,IA2输入高电平,【OA2 OB2】电机反转;
接线参考:
B-1A -- PA0
B-1B -- PB1
A-1A -- PA1
A-1B -- PB10
1.1 让小车动起来
代码实现:
motor.c
#include "motor.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_SET); 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_RESET); }
motor.h
#ifndef __MOTOR_H__ #define __MOTOR_H__ #include "main.h" void goForward(void); void goBack(void); void goLeft(void); void goRight(void); void stop(void); #endif
main.c
#include "motor.h" //main函数的while循环部分: while (1) { goForward(); HAL_Delay(1000); goBack(); HAL_Delay(1000); goLeft(); HAL_Delay(1000); goRight(); HAL_Delay(1000); stop(); HAL_Delay(1000); }
1.2 串口控制小车方向
- 串口分文件编程进行代码整合——通过现象来改代码
- 接入蓝牙模块,通过蓝牙控制小车
- 添加点动控制,如果APP支持按下一直发数据,松开就停止发数据(蓝牙调试助手的自定义按键不 能实现),就能实现前进按键按下后小车一直往前走的功能
代码实现:
usart.c
#include "usart.h" #include "string.h" #include "stdio.h" #include "motor.h" //串口接收缓存(1字节) uint8_t buf=0; //定义最大接收字节数 200,可根据需求调整 #define UART1_REC_LEN 200 // 接收缓冲, 串口接收到的数据放在这个数组里,最大UART1_REC_LEN个字节 uint8_t UART1_RX_Buffer[UART1_REC_LEN]; // 接收状态 // bit15, 接收完成标志 // bit14, 接收到0x0d // bit13~0, 接收到的有效字节数目 uint16_t UART1_RX_STA=0; #define SIZE 12 char buffer[SIZE]; // 接收完成回调函数,收到一个数据后,在这里处理 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { // 判断中断是由哪个串口触发的 if(huart->Instance == USART1) { // 判断接收是否完成(UART1_RX_STA bit15 位是否为1) if((UART1_RX_STA & 0x8000) == 0) { // 如果已经收到了 0x0d (回车), if(UART1_RX_STA & 0x4000) { // 则接着判断是否收到 0x0a (换行) if(buf == 0x0a) { // 如果 0x0a 和 0x0d 都收到,则将 bit15 位置为1 UART1_RX_STA |= 0x8000; // 灯控指令 if(!strcmp(UART1_RX_Buffer, "M1")) goForward(); else if(!strcmp(UART1_RX_Buffer, "M2")) goBack(); else if(!strcmp(UART1_RX_Buffer, "M3")) goLeft(); else if(!strcmp(UART1_RX_Buffer, "M4")) goRight(); else stop(); memset(UART1_RX_Buffer, 0, UART1_REC_LEN); UART1_RX_STA = 0; } else // 否则认为接收错误,重新开始 UART1_RX_STA = 0; } else // 如果没有收到了 0x0d (回车) { //则先判断收到的这个字符是否是 0x0d (回车) if(buf == 0x0d) { // 是的话则将 bit14 位置为1 UART1_RX_STA |= 0x4000; } else { // 否则将接收到的数据保存在缓存数组里 UART1_RX_Buffer[UART1_RX_STA & 0X3FFF] = buf; UART1_RX_STA++; // 如果接收数据大于UART1_REC_LEN(200字节),则重新开始接收 if(UART1_RX_STA > UART1_REC_LEN - 1) UART1_RX_STA = 0; } } } // 重新开启中断 HAL_UART_Receive_IT(&huart1, &buf, 1); } } int fputc(int ch, FILE *f) { unsigned char temp[1]={ch}; HAL_UART_Transmit(&huart1,temp,1,0xffff); return ch; }
1.3 如何进行小车PWM调速
原理:
全速前进是LeftCon1A = 0; LeftCon1B = 1;
完全停止是LeftCon1A = 0;LeftCon1B = 0;
那么单位时间内,比如20ms, 有15ms是全速前进,5ms是完全停止, 速度就会比5ms全速前进,15ms完全停止获得的功率多,相应的速度更快!
开发:借用PWM的舵机控制代码
将控制车轮的4个 GPIO 口配置修改如下,否则小车动不起来。
原因:L9110每个控制口需要一高一低才可以动起来,如果PWM有效电平为高电平,则另一个 GPIO口则需要输出低电平才可以驱动轮子。
代码实现:
main.c
// main函数里 HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1); HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_2); while (1) { __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, 8); __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, 8); HAL_Delay(1000); __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, 10); __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, 10); HAL_Delay(1000); __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, 15); __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, 15); HAL_Delay(1000); }
1.4 PWM方式实现小车转向
右转原理: 左轮速度大于右轮
左转原理: 右轮速度大于左轮
左右轮各自调速代码实现:
// main函数里 while (1) { __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1,8); __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2,15); HAL_Delay(1000); __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1,15); __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2,8); HAL_Delay(1000); }
2.循迹小车
2.1 循迹模块介绍
- TCRT5000传感器的红外发射二极管不断发射红外线
- 当发射出的红外线没有被反射回来或被反射回来但强度不够大时
- 红外接收管一直处于关断状态,此时模块的输出端为高电平,指示二极管一直处于熄灭状态
- 被检测物体出现在检测范围内时,红外线被反射回来且强度足够大,红外接收管饱和
- 此时模块的输出端为低电平,指示二极管被点亮
- 总结就是一句话,没反射回来,D0输出高电平,灭灯
接线方式
- VCC:接电源正极(3-5V)
- GND:接电源负极 DO:TTL开关信号输出0、1
- AO:模拟信号输出(不同距离输出不同的电压,此脚一般可以不接)
2.2 循迹小车原理
由于黑色具有较强的吸收能力,当循迹模块发射的红外线照射到黑线时,红外线将会被黑线吸收,导致 循迹模块上光敏三极管处于关闭状态,此时模块上一个LED熄灭。在没有检测到黑线时,模块上两个LED常亮
总结就是一句话,有感应到黑线,D0输出高电平 ,灭灯
2.3 循迹小车核心代码
硬件接线
- B-1A -- PA0
- B-1B -- PB1
- A-1A -- PA1
- A-1B -- PB10
- 循迹模块(左)-- PB3
- 循迹模块(右) -- PB4
代码示例:
#define LeftWheel_Value HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3) #define RightWheel_Value HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_4) // main函数里 while (1) { if (LeftWheel_Value == GPIO_PIN_RESET && RightWheel_Value == GPIO_PIN_RESET) goForward(); if (LeftWheel_Value == GPIO_PIN_SET && RightWheel_Value == GPIO_PIN_RESET) goLeft(); if (LeftWheel_Value == GPIO_PIN_RESET && RightWheel_Value == GPIO_PIN_SET) goRight(); if (LeftWheel_Value == GPIO_PIN_SET && RightWheel_Value == GPIO_PIN_SET) stop(); }
2.4 循迹小车解决转弯平滑问题
原理:两轮都有速度且一轮速度大于另一轮
代码实现:
#define LeftWheel_Value HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3) #define RightWheel_Value HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_4) // main函数里 while (1) { if(LeftWheel_Value == GPIO_PIN_RESET && RightWheel_Value == GPIO_PIN_RESET) { __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1,19); __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2,19); } if(LeftWheel_Value == GPIO_PIN_SET && RightWheel_Value == GPIO_PIN_RESET) { __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1,15); __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2,8); } if(LeftWheel_Value == GPIO_PIN_RESET && RightWheel_Value == GPIO_PIN_SET) { __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1,8); __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2,15); } if(LeftWheel_Value == GPIO_PIN_SET && RightWheel_Value == GPIO_PIN_SET) { __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1,0); __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2,0); } }
3.跟随/避障小车
3.1 红外壁障模块分析
原理和循迹是一样的,循迹红外观朝下,跟随朝前
3.2 跟随小车的原理
- 左边跟随模块能返回红外,输出低电平,右边不能返回,输出高电平,说明物体在左边,需要左转
- 右边跟随模块能返回红外,输出低电平,左边不能返回,输出高电平,说明物体在右边,需要右转
3.3 跟随小车开发和调试代码
硬件接线
- B-1A -- PB0
- B-1B -- PB1
- A-1A -- PB2
- A-1B -- PB10
- 跟随模块(左) -- PB5
- 跟随模块(右) -- PB6
代码示例:
#define LeftWheel_Value HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_5) #define RightWheel_Value HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_6) // main函数里 while (1) { if(LeftWheel_Value == GPIO_PIN_RESET && RightWheel_Value == GPIO_PIN_RESET) goForward(); if(LeftWheel_Value == GPIO_PIN_SET && RightWheel_Value == GPIO_PIN_RESET) goRight(); if(LeftWheel_Value == GPIO_PIN_RESET && RightWheel_Value == GPIO_PIN_SET) goLeft(); if(LeftWheel_Value == GPIO_PIN_SET && RightWheel_Value == GPIO_PIN_SET) stop(); }
3.4 超声波模块介绍
使用超声波模块,型号:HC-SR04
- 怎么让它发送波 Trig ,给Trig端口至少10us的高电平
- 怎么知道它开始发了 Echo信号,由低电平跳转到高电平,表示开始发送波
- 怎么知道接收了返回波 Echo,由高电平跳转回低电平,表示波回来了
- 怎么算时间 Echo引脚维持高电平的时间! 波发出去的那一下,开始启动定时器 波回来的拿一下,我们开始停止定时器,计算出中间经过多少时间
- 怎么算距离 距离 = 速度 (340m/s)* 时间/2
时序图:
3.5 舵机模块介绍
1. 什么是舵机
如下图所示,最便宜的舵机sg90,常用三根或者四根接线,黄色为PWM信号控制 用处:垃圾桶项目开盖用、智能小车的全比例转向、摄像头云台、机械臂等 常见的有0-90°、0-180°、0-360°
2. 怎么控制舵机
向黄色信号线“灌入”PWM信号
PWM波的频率不能太高,大约50HZ,即周期=1/频率=1/50=0.02s,20ms左右
确定周期/频率:
如果周期为20ms,则 PSC=7199,ARR=199
角度控制
0.5ms-------------0度; 2.5% 对应函数中CCRx为5
1.0ms------------45度; 5.0% 对应函数中CCRx为10
1.5ms------------90度; 7.5% 对应函数中CCRx为15
2.0ms-----------135度; 10.0% 对应函数中CCRx为20
2.5ms-----------180度; 12.5% 对应函数中CCRx为25
3.6 摇头避障小车开发和调试代码
硬件接线
- sg90 -- PB9
代码实现
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); //将舵机置为90度 } 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度 }
sg90.h
#ifndef __SG90_H__ #define __SG90_H__ void initSG90(void); void sgMiddle(void); void sgRight(void); void sgLeft(void); #endif
main.c
initSG90(); HAL_Delay(1000); while (1) { sgLeft(); HAL_Delay(1000); sgMiddle(); HAL_Delay(1000); sgRight(); HAL_Delay(1000); sgMiddle(); HAL_Delay(1000); }
封装超声波传感器
超声波模块接线:
- Trig -- PB7
- Echo -- PB8
cubeMX配置
代码实现
sr04.c
#include "sr04.h" #include "gpio.h" #include "tim.h" //使用TIM2来做us级延时函数 void TIM2_Delay_us(uint16_t n_us) { /* 使能定时器2计数 */ __HAL_TIM_ENABLE(&htim2); __HAL_TIM_SetCounter(&htim2, 0); while(__HAL_TIM_GetCounter(&htim2)
sr04.h
#ifndef __SR04_H__ #define __SR04_H__ double get_distance(void); #endif
main.c
while (1) { if(dir != MIDDLE){ sgMiddle(); dir = MIDDLE; HAL_Delay(300); } disMiddle = get_distance(); if(disMiddle > 35){ //前进 } else { //停止 //测左边距离 sgLeft(); HAL_Delay(300); disLeft = get_distance(); sgMiddle(); HAL_Delay(300); sgRight(); dir = RIGHT; HAL_Delay(300); disRight = get_distance(); } }
封装电机驱动
代码实现:
while (1) { if(dir != MIDDLE){ sgMiddle(); dir = MIDDLE; HAL_Delay(300); } disMiddle = get_distance(); if(disMiddle > 35){ //前进 goForward(); }else if(disMiddle
4.测速小车
4.1 测速模块
- 用途:广泛用于电机转速检测,脉冲计数,位置限位等。
- 有遮挡,输出高电平;无遮挡,输出低电平
- 接线 :VCC 接电源正极3.3-5V
- GND 接电源负极 DO TTL开关信号输出
- AO 此模块不起作用
4.2 测试原理和单位换算
- 轮子走一圈,经过一个周长,C = 2x3.14x半径= 3.14 x 直径(6.5cm)
- 对应的码盘也转了一圈,码盘有20个格子,每经过一个格子,会遮挡(高电平)和不遮挡(低电平), 那么一个脉冲就是走了 3.14 * 6.5 cm /20 = 1.0205CM
- 定时器可以设计成一秒,统计脉冲数,一个脉冲就是1cm
- 假设一秒有80脉冲,那么就是80cm/s
4.3 定时器和中断实现测速开发和调试代码
测试数据通过串口发送到上位机
硬件接线
测速模块:
- VCC -- 3.3V 不能接5V,否则遮挡一次会触发3次中断
- OUT -- PB14
代码实现:
unsigned int speedCnt; void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin == GPIO_PIN_14) if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_14) == GPIO_PIN_RESET) speedCnt++; } void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { printf("speed: %d\r\n", speedCnt); speedCnt = 0; } main函数里: HAL_TIM_Base_Start_IT(&htim2);
4.4 小车速度显示在OLED屏
OLED模块介绍:STM32 OLED屏幕显示详解
硬件接线
- SCL -- PB6
- SDA -- PB7
代码示例:
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;i4)); //high for(i=((oledChar-32)*16);i>4)); //high for(i=((oledChar-32)*16+8);i 35){ //前进 goForward(); }else if(disMiddle
- sg90 -- PB9