
【立创开发板】基于梁山派的多功能语音播报车
简介
1、蓝牙控制、WIFI控制、自制摇杆控制、自动追光、循迹避障、语音播报等功能; 2、使用0.96寸IIC接口的OLED屏移植u8g2图形库,实现高刷新率和和精美UI显示。
简介:1、蓝牙控制、WIFI控制、自制摇杆控制、自动追光、循迹避障、语音播报等功能; 2、使用0.96寸IIC接口的OLED屏移植u8g2图形库,实现高刷新率和和精美UI显示。开源协议
:GPL 3.0
描述
做为一个电子人,怎么能没有自己的小车?(doge)
本来想做一个平衡小车车,但是看到教程这么丰富,忍不住跟着一起做了一个飞法法的4轮车。(弟弟说不喜欢两轮,哭唧唧)
项目介绍
该项目为立创开发板寒假训练营项目,跟着学习下来,一共掌握了:
对常用电机驱动有一定了解,并自己画电路实现了功能;
掌握循迹电路原理,并实现了小车循迹功能;
掌握SR04超声波模块测距原理,并完成了测距功能,实现避障;
整个小车的电源部分,采用3节18650电池并联,并实现充电功能;
使用便宜又常用的0.96IIC接口的OLED屏进行显示,认识到一个单色GUI库,并移植成功显示比较好看的界面;
使用蓝牙模块HC05做无线控制,实现手机APP通过蓝牙控制小车;
使用WIFI模块ESP8266做局域网控制,实现手机APP通过WIFI控制小车;
使用2.4G模块NRF24L01做摇杆控制,设计了一个遥控杆,摇杆通过NRF控制小车;
使用3个光敏电阻,放在3个方向,实现追光模式(就是那一边比较亮,就往哪一个开)
还加了一个语音播报功能。在开源平台上看到,非常想实现这个功能。试着按照数据手册设计,结果一次就过了,可喜可贺。就是声音太生硬成本也高。
硬件实现
电源部分
为了电池能够稳固的定在小车上,我选择了贴片类型的电池盒,这个有点贵。。
如果可以,我建议是使用下面这个,这个也可以固定在板子上。但是它是插件,如果电路板很紧凑,一个通孔都没有位置放还是不要用了。
因为要用到5V和3.3V和电机的8V,并且是3.7V的电池供电。所以我的供电方式是有三种。
1、电池电压->升压->5V
2、电池电压->升压->8V
我之前购买的电机是6V300转的,所以我为什么用8V供电?因为我算了一下的电阻分配,我24K电阻都没有用过多少,就搞了进去,实测确实是7.8V左右,并且电机也没有发生什么问题。
3、5V -> 3.3V
直接使用升压后的5V再降压到3.3V给一些模块供电。(这个方案我不太喜欢,元件太大了,但是我在学习硬件的时候,买了这个方案的物料很多)
4、电源开关
小车肯定要有开关功能啊,是吧?本来开关有个长按开机电路的,但是想到如果搞长按开机,调试小车估计按键都给整坏,就放弃了长按开机的想法。
5、电池充电
使用的是老生常谈的TC4056,通过TYPE-C充电。
电源部分完结!!
控制部分
控制部分大多是使用的模块,有蓝牙控制,WIFI控制、NRF控制。还要控制屏幕,控制语音播报这些。
1、电机控制
使用的是DRV8833进行驱动,挺好用,就是4个电机需要8个PWM。那个VM引脚是电机供电引脚,建议加个大电容,防止电机偷电太多。
2、WIFI、蓝牙和屏幕控制
这个WIFI模块也是一个吃电大户,被它坑过一次,所以我单独给WiFi模块一个1117供电。屏幕的通信方式是IIC,我使用的是软件IIC方式,所以引脚就随便放啦。我还写了关于GD32F450如何移植0.96屏幕代码,见链接:立创梁山派GD32F450ZGT6--移植4针0.96寸OLED显示屏
WIFI模块使用的是ESP8266,蓝牙模块使用的是HC05(蓝牙模块这里我忘记连接一个手机连接成功指示引脚了,该打)。
3、NRF控制
NRF模块是采购的泽耀科技的NRF24L01模块,他家的模块资料比较丰富,我买的这个只要会SPI就可以移植了。
4、语音播报
它是通过串口进行控制,比如发送一串字符串"嘉立创天长地久",通过特定的帧格式,它就会播报语音"嘉立创天长地久"。
这个画的比较乱,这里在VDD上都加了100nF和10uF,说是能够让音质好点,可能是我PCB布局有问题,声音能听懂,但是很沙哑。
5、其他控制
蜂鸣器我模拟为汽车的喇叭;
左右转灯使用LED代替;
关于语音播报的电源门控,我的想法是,有人比较喜欢安静,所以搞了一个可以通过软件控制语音播报电路的电源,关了电源,它就不能播报啦。
采集部分
采集部分,分别有温湿度采集、电量采集、光照度采集、超声波测距采集、5路循迹采集;
1、电量采集和温湿度采集
电量采集是以前刚学硬件时,自己想的。就欧姆定律计算出两点的电压最高3.3V,对它进行采集换算等到实际电量。
后面发现很多大佬都是使用10K这样的多路分压读取对应比例的电量(我又是一个固步自封的例子,哭唧唧)
不过我这里已经完善了这个电量方面的代码,所以无所谓了。
温湿度方面,本来想使用SHT30这种小型高精度的温湿度模块,但是看到它的价格,选了又选,看了又看,最后买了DHT11(我真的穷疯了doge)
温湿度也吃过亏,我之前以为数据口可以通过内部电阻的上拉就可以,但是实际使用发现外部最好要加一个4.7K的上拉电阻,不然读出的数据不准,其在数据手册上也有说明。
2、超声波与光敏采集
光敏是想用作追光设计的,把3个光敏分别放在3个方向,哪一个方向比较亮就往哪一边跑(其实是想多实践一下PID)。
超声波使用的是常用的SR04模块,原理就不说啦,在百度上都很详细了。
3、循迹电路
循迹是通过电压比较实现的高低电平变化,采集这些变化就知道当前是否走在特定的轨迹上了。
PCB方面我就不说了,谁教谁还不一定呢。。我只能说 我还有很大的进步空间,哈哈
软件实现
1、蓝牙控制
使用的是HC05蓝牙模块,使用之前需要对模块进行一下配置。(在附件有资料)
按住模块上的按键或EN脚拉高,此时灯是慢闪,HC-05进入AT命令模式,默认波特率是38400;此模式我们叫原始模式。原始模式下一直处于AT命令模式状态。记住!!每一条指令都要加上\r\n,不然是识别不到命令的,可以发送: AT 测试一下是否返回OK
最主要的是设置模式为从机控制,即等待手机去连接我们蓝牙模块的蓝牙,主要由手机控制。发送:AT+ROLE0
我这里还修改了波特率,改为了115200, 发送:AT+UART=115200,0,0
还可以修改蓝牙名称, 发送: AT+NAME=智能车
这里如果你发现你的手机APP连接不上你的蓝牙模块的话,应该是配对密码错误的问题。大多APP的蓝牙配对密码是1234,
所以发送:AT+PSWD=1234
也可以发送 AT+PSWD? 查询密码
这样就配置完成了,给蓝牙模块断电再通电,它的名称就是智能车,通信波特率是115200。并且当手机连接成功后,模块上狂闪的灯就长亮,表示连接成功。
看一下我所实现的代码:
我手机上发送特定的指令去控制小车动作
/************************************************************
* 函数名称:WIFI_control
* 函数说明:根据手机发送过来的命令,进行相应的控制
* 型 参:cmd=手机发送过来的命令(具体命令见指令表)
* 返 回 值:无
* 备 注:指令与蓝牙模块通用
* 指令表
【接收的数据】 【功能】
0x00 停止
0x01 前进
0X02 后退
0X03 左转
0X04 右转
0X05 左转灯亮再按灭
0X06 右转灯亮再按灭
0X07 速度加
0X08 速度减
0X09 喇叭响再按灭
*************************************************************/
void WIFI_control(unsigned char cmd)
{
switch( cmd )
{
case 0x00://停止
MOTOR_stop();
break;
case 0x01://前进
MOTOR_forward(CAR_SPEED, CAR_SPEED);
break;
case 0x02://后退
MOTOR_back(CAR_SPEED, CAR_SPEED);
break;
case 0x03://左转
MOTOR_left(CAR_SPEED, CAR_SPEED);
break;
case 0x04://右转
MOTOR_right(CAR_SPEED, CAR_SPEED);
break;
case 0x05://左转灯亮再按灭
Turn_Light_Con(LEFT_LED_FLAG=!LEFT_LED_FLAG, SET);
break;
case 0x06://右转灯亮再按灭
Turn_Light_Con(SET, RIGHT_LED_FLAG=!RIGHT_LED_FLAG);
break;
case 0x07://速度加
CAR_SPEED = CAR_SPEED + 500;
if( CAR_SPEED >= 7999 ) CAR_SPEED = 7999;
break;
case 0x08://速度减
CAR_SPEED = CAR_SPEED - 500;
if( CAR_SPEED <= 500 ) CAR_SPEED = 500;
break;
case 0x09://喇叭控制
BEEP_FLAG = !BEEP_FLAG;
if( BEEP_FLAG == 1 )
{
BEEP_ON();
}
else
{
BEEP_OFF();
}
break;
default:break;
}
}
2、WIFI控制
首先要控制WIFI模块开启WIFI,让我们可以通过连接它的WIFI,去控制它。
/************************************************************
* 函数名称:ESP12_Send_Cmd
* 函数说明:向WIFI模块发送指令,并查看WIFI模块是否返回想要的数据
* 型 参:
* 【cmd=发送的AT指令 ack=想要的应答 waitms=等待应答的时间 cnt=等待应答多少次】
* 返 回 值:1=得到了想要的应答 0=没有得到想要的应答
* 备 注:无
*************************************************************/
char ESP12_Send_Cmd(char *cmd,char *ack,unsigned int waitms,unsigned char cnt)
{
UART4_send_String((unsigned char*)cmd);//向WIFI模块发送AT指令
while(cnt--)
{
delay_1ms(waitms);
//串口中断接收wifi应答
if(U4RX_FLAG)
{
U4RX_FLAG = 0;
U4RX_LEN = 0;
if(strstr((char*)U4RX_BUFF,ack)!=NULL)//接收到想要的数据
{
return 1;
}
memset(U4RX_BUFF,0,sizeof(U4RX_BUFF));//清除接收缓存
}
}
U4RX_FLAG = 0;//清除有串口数据标志
U4RX_LEN = 0;//清除接收缓存数组长度
return 0;
}
/************************************************************
* 函数名称:ESP12_AP_Init
* 函数说明:设置WIFI模块为AP模式,即开启热点让手机进行连接
* 型 参:无
* 返 回 值:无
* 备 注: IP=196.168.4.1 端口=5000 如要修改WIFI名称与密码请修改以下参数
* WIFI_SSID
* WIFI_PASS
*************************************************************/
void ESP12_AP_Init(void)
{
char buff[200];
UART4_Init(115200);
//发送AT 等待它返回OK 等待10ms 没有返回OK继续等待,一共等待3次
ESP12_Send_Cmd("AT\r\n","OK",10,3);
//发送AT+CWMODE=2 等待它返回OK 等待30ms 没有返回OK则继续等待,一共等待3次
ESP12_Send_Cmd("AT+CWMODE=2\r\n","OK",30,3); //配置WIFI AP模式
sprintf(buff, "AT+CWSAP=\"%s\",\"%s\",11,4\r\n",WIFI_SSID, WIFI_PASS);
ESP12_Send_Cmd(buff,"OK",30,3); //设置wifi账号与密码
ESP12_Send_Cmd("AT+RST\r\n","ready",800,3); //重启
ESP12_Send_Cmd("AT+CIPMUX=1\r\n","OK",50,3); //开启多个连接
ESP12_Send_Cmd("AT+CIPSERVER=1,5000\r\n","OK",50,3); //开启服务器设置端口号
printf("ESP12_AP_Init succeed!\r\n");
}
这里还判断了是否有手机连接,当有手机连接时,才能控制,这样大大增加了CPU的工作效率。
//DISCONNECTED AP模式下 手机断开了WIFI连接
//DIST_STA_IP 开启AP模式后,有手机连接
//0,CONNECT 设备0连接成功
//+IPD,0,4:刚刚 接收到设备0发来的4个字节数据:刚刚
char WIFI_Mode(void)
{
char ret = 0;
//没有手机连接的情况下
if( ConnectFlag == 0 )
{
ret = 0;
if( U4RX_FLAG == 1 )//接收到WIFI数据
{
U4RX_FLAG = 0;
//是否有设备连接
if( strstr((char*)U4RX_BUFF, "CONNECT") != NULL )
{
printf("手机已连接\r\n");
ConnectFlag = 1;
}
//清除串口接收缓存
Clear_U4RX_BUFF();
}
}
// 有手机连接的情况下
if( ConnectFlag == 1 )
{
ret = 1;
if( U4RX_FLAG == 1 )//接收到WIFI数据
{
U4RX_FLAG = 0;
//判断手机是否断开WIFI连接
if( strstr((char*)U4RX_BUFF, "DISCONNECTED") != NULL )
{
printf("断开连接\r\n");
BEEP = 0;//蜂鸣器关
Turn_Light_Con(SET, SET);//左右灯灭
ConnectFlag = 0;
}
//确定当前是按下什么键(确定当前手机发送过来什么数据)
WIFI_control( Get_WIFIAPP_Data() );
//清除串口接收缓存 等待下一次控制命令到来
Clear_U4RX_BUFF();
}
}
return ret;
}
然后控制方面是和蓝牙一样的,就不贴出来了。
3、电机控制
电机控制方面,我为了连线方便,都是选择的离电机控制引脚最近的PWM控制引脚,分别使用到了
PA15--TIM1-CH0--PWML11 JTDI 左轮
PB4 --TIM2-CH0--PWML12 NJTRST
PB3 --TIM1-CH1--PWML21 JTDO
PB6 --TIM3-CH0--PWML22
PC7 --TIM7-CH1 --PWMR11 右轮
PC6 --TIM7-CH0 --PWMR12
PA7 --TIM13-CH0--PWMR21
PA6 --TIM12_CH0--PWMR22
浪费的定时器比较多,但是够用了。
大致代码,见我之前写的文章,链接:立创梁山派GD32F450ZGT6--定时器3-PWM-4通道输出
虽然文章只写了4个通道并且是同一个定时器,但是具体初始化的方法是一样的,也可以去附件下载我的源码。
4、语音播报
语音播报控制,只要配置出串口,再根据数据手册要求的命令帧格式发送数据,就能实现播报功能。
具体实现 命令帧封装发送代码
/************************************************************
* 函数名称:SYN6288_Send_Cmd
* 函数说明:向SYN6288发送命令
* 型 参:
* 【CmdType=命令字】 可使用参数有:
* -0x01 语音合成命令
* -0x31 设置波特率(默认9600)
* -0x02 停止合成命令
* -0x03 暂停合成命令
* -0x04 恢复合成命令
* -0x21 芯片状态查询命令
* -0x88 芯片进入低功耗模式
* 【CmdPar=命令参数】 可使用参数有:
* -字节高5位的十进制为0时,表示不加背景音乐
* -字节高5位的十进制为1~15时,表示所选背景音乐的编号
* -字节低3位的十进制为0~3,并且命令字为语音合成命令时,分别代表设置文本为BG2312格式、GBK格式、BIG5格式、UNICODE格式;
* -字节低3位的十进制为0~2,并且命令字为设置波特率时,分别代表设置波特率为9600、19200、38400;
* 【text=播报的文本】
* 返 回 值:0=发送成功
* 备 注:
* 接收到控制命令帧,芯片会向上位机发送1个字节的状态回传,上位机可根据这个回传来判断芯片目前的工作状态
* 初始化成功回传 0X4A
* 收到正确的命令帧回传 0x41
* 收到不能识别命令帧回传 0x45
* 芯片播音状态回传 0x4E
* 芯片空闲状态回传 0x4F
*************************************************************/
unsigned char SYN6288_Send_Cmd(u8 CmdType, u8 CmdPar, u8 *text)
{
unsigned char frame_header = 0XFD; //帧头
unsigned int Text_Len = strlen((const char*)text);//待发送文本的长度
unsigned int Data_Len = Text_Len + 3; //数据区长度;3=帧头、帧尾和异或校验
unsigned char Xor_Check = 0; //异或校验存储
unsigned char Send_Buff[210]; //待发送的命令帧,命令帧最大206个字节
u8 i = 0;
Send_Buff[0] = frame_header; //帧头
Send_Buff[1] = Data_Len>>8; //高位在前
Send_Buff[2] = Data_Len&0x00ff; //低位在前
Send_Buff[3] = CmdType; //命令字
Send_Buff[4] = CmdPar; //命令数据
sprintf((char*)Send_Buff+5, "%s", text );
//发送数据
for( i = 0; i < Text_Len+5; i++ )
{
Xor_Check = Xor_Check ^ Send_Buff[i];//对每一个数据进行异或校验保存
USART1_Send_Bit( Send_Buff[i] );//发送数据
}
USART1_Send_Bit( Xor_Check );//发送最后一位:异或校验数据
return 0;
}
如果发现其播报的内容和你发送的内容不一致,请确保你发送命令的那个文件的编码格式为ANSI编码格式
使用.txt文本打开再另存为ANSI格式
5、追光模式
追光模式使用PID,随便采用了一个大概的参数。
float Kp = 800, Ki=0, Kd =5;//PID参数
float P = 0, I = 0, D = 0, PID_value = 0;
float error = 0, previous_error = 0;
static int initial_motor_speed = 1000;//基础速度
//PID计算
void calc_pid(void)
{
P=error; //当前误差
I=I+error; //误差累加
D=error-previous_error; //当前误差与之前误差的误差
PID_value=(Kp*P)+(Ki*I)+(Kd*D);
previous_error=error;//更新之前误差
}
//电机动作
void motorsWrite(int speedL,int speedR)
{
if(speedR > 0)
{
Right_forward(speedR);//右边两个轮向前
}
else
{
Right_Back(-speedR);//右边两个轮向后
}
if(speedL > 0)
{
Left_Back(speedL);//左边两个轮向前
}
else
{
Left_Back(-speedL);//左边两个轮向后
}
}
//数值限幅并控制
void motor_cortrol(void)
{
//基础速度+PID值
int left_motor_speed = initial_motor_speed+PID_value;
int right_motor_speed = initial_motor_speed-PID_value;
//左轮限幅
if(left_motor_speed <= -8000)
{
left_motor_speed = -8000;
}
if(left_motor_speed >= 8000)
{
left_motor_speed = 8000;
}
//右轮限幅
if(right_motor_speed <= -8000)
{
right_motor_speed = -8000;
}
if(right_motor_speed >= 8000)
{
right_motor_speed = 8000;
}
//如果不是中间的光照度最高
if( error != 0 )
{
//左轮因为调试过多,目前速度已经不一致,需要手动调整
if( left_motor_speed < 0 )
{
left_motor_speed = left_motor_speed - 2000;
}
else
{
left_motor_speed = left_motor_speed + 2000;
}
motorsWrite(left_motor_speed,right_motor_speed);
}
else//中间光照度最高,则停车
{
motorsWrite(0,0);
}
}
//误差获取
void error_estimate(void)
{
unsigned int left = Get_Liium_Val(GM_LEFT);//读取左边光敏滤波后的ADC值
unsigned int middle = Get_Liium_Val(GM_MIDDLE);//读取中间光敏滤波后的ADC值
unsigned int right = Get_Liium_Val(GM_RIGHT);//读取右边光敏滤波后的ADC值
//设置误差
if( (left > middle) && (left > right) )//左边最亮
{
error = -5;
}
if( (middle > left) && (middle > right) )//中间最亮
{
error = 0;
}
if( (right > left) && (right > middle) )////右边最亮
{
error = 5;
}
}
//追光模式
void follow_light(void)
{
//获取误差
error_estimate();
//计算PID值
calc_pid();
//控制电机
motor_cortrol();
}
设计图

BOM


评论