基于GD32F450智能小车
设计目标
通过蓝牙模块控制小车的运动,同时可以调节速度,和关闭开启车灯,舵机云台实现避障功能,循迹模块实现循迹功能,OLED显示运动信息以及小车电池的实时电压。
总体设计方案和思路

器件准备和焊接
蓝牙模块、5V稳压模块、超声波模块、MG90S、循迹模块、GD32F450,四个电机,0.96寸OLED等。

小车的底板是4mm厚的亚克力板,是用solidworks2021画的,再给打印出来,比较实惠,可以根据自己需求画出喜欢的形状。要定好孔位用于安装电机和主控板。

元器件的焊接比较简单,没有太多引脚密集的芯片,可以轻松焊接好,要注意电机驱动芯片不要焊反。
程序编写
1.0.96寸OLED显示功能
0.96寸OLED分辨率是128*64,采用IIC协议。通过OLED上面可以显示数字,汉字,字符串,图片等内容,移植了中景园的OLED显示代码。
OLED_Init(); //初始化OLED
OLED_Clear();
OLED_ShowCHinese(0,0,0);//智
OLED_ShowCHinese(18,0,1);//能
OLED_ShowCHinese(36,0,2);//小
OLED_ShowCHinese(54,0,3);//车
OLED_ShowCHinese(72,0,4);//梁
OLED_ShowCHinese(90,0,5);//山
OLED_ShowCHinese(108,0,6);//派
OLED_ShowString(88,6,".",16);
OLED_ShowString(112,6,"V",16);
OLED_ShowString(32,4,"Speed=",16); //显示运动速度
OLED_ShowNum(82,4,speed,3,16);
D= SR04_Get_Distance(); //显示距离
OLED_ShowString(54,2,"D:",16);
OLED_ShowNum(72,2,D,4,16);
OLED_ShowString(108,2,"mm",16);
voltage=adc_get_val();
OLED_ShowString(0,6,"Voltage:",16); //显示电压
OLED_ShowNum(72,6,((voltage*100/4096)*3.3*5)/100,2,16);
OLED_ShowNum(96,6,((voltage*100/4096)*3.3*5)/100-1100,2,16);
效果演示:
第一行是汉字智能小车梁山派,其中第二行左边是显示运行状态,0是静止状态,1定距离跟随,2是循迹模式,3是避障模式,4是蓝牙遥控模式。第二行右边是超声波测距的距离,第三行左边是显示小车运行模式,Q表示前进,H表示后退,Z表示左转,Y表示右转,T表示停止等,第三行右边是小车当前速度,第四行是小车的电压。
2.按键,蜂鸣器,LED等代码编写
主要是对各个模块的初始化,蜂鸣器和LED只需要简单的初始化,而按键需要通过一个按键读取函数读取状态。
按键读取函数:
void Key_Init(void)
{
/* 开启时钟 */
rcu_periph_clock_enable(BSP_KEYM_RCU);
rcu_periph_clock_enable(BSP_KEYS_RCU);
/* 配置为输入模式 上拉模式 */
gpio_mode_set(BSP_KEYM_PORT,GPIO_MODE_INPUT,GPIO_PUPD_PULLUP,BSP_KEYM_PIN); // 按键默认状态是高电平,配置为上拉
gpio_mode_set(BSP_KEYS_PORT,GPIO_MODE_INPUT,GPIO_PUPD_PULLUP,BSP_KEYS_PIN); // 按键默认状态是高电平,配置为上拉
}
uint8_t Key_Getnum(void)
{
static uint8_t KeyNum=0;
if(gpio_input_bit_get(BSP_KEYM_PORT,BSP_KEYM_PIN) == RESET)
{
delay_1ms (10);
while(gpio_input_bit_get(BSP_KEYM_PORT,BSP_KEYM_PIN) == RESET);
delay_1ms (10);
KeyNum +=1;
}
if(gpio_input_bit_get(BSP_KEYS_PORT,BSP_KEYS_PIN) == RESET)
{
delay_1ms (10);
while(gpio_input_bit_get(BSP_KEYS_PORT,BSP_KEYS_PIN) == RESET);
delay_1ms (10);
KeyNum =0;
}
if(KeyNum>4)
{
KeyNum=0;
}
return KeyNum;
}
通过按键切换小车运行模式,0是静止模式,默认为静止状态,1是1定距离跟随,2是循迹模式,3是避障模式,4是蓝牙遥控模式。
3.蓝牙模块功能实现
JDY-31 蓝牙基于蓝牙 3.0 SPP 设计,这样可以支持 Windows、 Linux、 android 数据透传,工作频段 2.4GHZ,调制方式 GFSK,最大发射功率 8db,最大发射距离30 米,支持用户通过AT 命令修改设备名、 波特率等指令,方便快捷使用灵活。
可以用蓝牙模块切换不同的运动状态,控制小车运动,控制小车速度,控制车灯的开启与关闭等。
蓝牙软件界面效果:

部分蓝牙代码:
void BSP_UART6_IRQHandler(void)
{
if(usart_interrupt_flag_get(BSP_UART6,USART_INT_FLAG_RBNE) == SET) // 接收缓冲区不为空
{
g_recv_buff = usart_data_receive(BSP_UART6); // 把接收到的数据放到缓冲区中
if(g_recv_buff=='A') Blue=1;
if(g_recv_buff=='B') Blue=2;
if(g_recv_buff=='C') Blue=3;
if(g_recv_buff=='D') Blue=4;
if(g_recv_buff=='E') Blue=0;
if(g_recv_buff=='F') Blue=5;
if(g_recv_buff=='G') Blue=6;
}
if(usart_interrupt_flag_get(BSP_UART6,USART_INT_FLAG_IDLE) == SET) // 检测到帧中断
{
usart_data_receive(BSP_UART6); // 必须要读,读出来的值不能要
g_recv_complete_flag = 1; // 接收完成
}
}
通过软件发送不同的指令控制小车,既可以通过按键控制小车运动模式,也可以通过按键控制小车运行模式。
4.超声波实现定距离跟随
基本工作原理
(1)采用 IO 口 TRIG 触发测距,给最少 10us 的高电平信呈。
(2)模块自动发送 8 个 40khz 的方波,自动检测是否有信号返回;
(3)有信号返回,通过 IO 口 ECHO 输出一个高电平,高电平持续的时间就是超声波从发射到返回的时间。测试距离=(高电平时间*声速(340M/S))/2;
超声波代码:
void SR04_Init(void)
{
/* 使能时钟 */
rcu_periph_clock_enable(HCSR04_RCU);
/* 配置为输出模式 浮空模式 */
gpio_mode_set(PORT_HCSR04,GPIO_MODE_OUTPUT,GPIO_PUPD_NONE,HCSR04_Trig_PIN);
/* 配置为推挽输出 50MHZ */
gpio_output_options_set(PORT_HCSR04,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,HCSR04_Trig_PIN);
/* 配置为输入模式 下拉模式 */
gpio_mode_set(PORT_HCSR04,GPIO_MODE_INPUT,GPIO_PUPD_PULLDOWN,HCSR04_Echo_PIN);
//PB12初始状态为低电平,看时序图
PBout(12)=0; //PB12trig PB10echo
}
int SR04_Get_Distance(void)
{
uint32_t t=0;
int32_t d=0;
PBout(12)=1; //PB12高电平
delay_1us(20);//持续10us以上
PBout(12)=0; //PB12低电平
t=0;
while(PBin(10)==0)//等待PB10出现高电平
{
t++;
delay_1us(1);
if(t >= 1000000)
return -1;
}
t=0;
while(PBin(10))//测量高电平的时间
{
t++;
delay_1us(9); //9us == 3mm
if(t >= 1000000)
return -2;
}
//由于测量的时间,就是超声波从发射到返回的时间
t=t/2;
d = 3*t;
return d;
}
定距离跟随代码:
if(Mode==1) //模式1定距离跟随模式
{
//定距离跟随
if(SR04_Get_Distance()>100)
{
car_front(speed);//前进
delay_1ms(50);
}
if(SR04_Get_Distance()<50)
{
car_back(speed);//后退
delay_1ms(50);
}
car_stop(1);
}
5.循迹模块实现循迹
TCRT5000就是一个红外发射和接收器,不断发射和接收红外线。产品用途: 1、电度表脉冲数据采样2、传真机碎纸机纸张检测3、障碍检测4、黑白线检测
循迹原理非常简单,模块上配有一个输出指示灯,部分模块还有电源指示灯,我们主要关注输出指示灯。红外发射器一直发射红外线,红外线经发射后被接收,此时输出低电平,输出指示灯点亮。黑色是不反射红外线的,也就是说循迹模块遇到黑线,模块输出高电平,输出指示灯熄灭。当然除了遇到黑线熄灭,当距离太远红外线反射后检测不到,此时指示灯也会熄灭。那么如果要循迹,模块离地面要近,在没有遇到黑线时确保指示灯长亮,一旦指示灯熄灭就说明遇到黑线了。
注意引脚要设置为下拉输入模式,输入模式不需要配置速度。
if(Mode==2) //模式2循迹模式
{
Black_Line_Detection();
//红外对管循迹
if(XJ01== 0 && XJ02 == 1 && XJ03 == 1 && XJ04 == 0)
{
car_front(speed);//前进
delay_1ms(300);
car_stop(1);
delay_1ms(5);
}
if(XJ01== 0 && XJ02 == 0 && XJ03 == 0 && XJ04 == 0)
{
car_stop(1);
}
if(XJ01 == 0 && XJ02 == 1 && XJ03 == 0 && XJ04 == 0)
{
car_right(speed);//右转
delay_1ms(150);
}
if(XJ01 == 1 && XJ02 == 0 && XJ03 == 0 && XJ04 == 0)
{
car_right(speed);//右转
delay_1ms(250);
}
if(XJ01 == 1 && XJ02 == 1 && XJ03 == 0 && XJ04 == 0)
{
car_right(speed);//右转
delay_1ms(300);
}
if(XJ01 == 0 && XJ02 == 0 && XJ03 == 1 && XJ04 == 0)
{
car_right(speed);//左转
delay_1ms(150);
}
if(XJ01 == 0 && XJ02 == 0 && XJ03 == 0 && XJ04 == 1)
{
car_right(speed);//左转
delay_1ms(250);
}
if(XJ01 == 0 && XJ02 == 0 && XJ03 == 1 && XJ04 == 1)
{
car_right(speed);//左转
delay_1ms(300);
}
}
6.超声波和舵机组合实现避障
舵机基本原理:
控制舵机实际上只需要使用定时器输出一束PWM就可以了
*舵机的控制一般需要一个20ms左右的时基脉冲,该脉冲的高电平部分一般为0.5ms-2.5ms范围内的角度控制脉冲部分,总间隔为2ms。以180度角度伺服为例,那么对应的控制关系是这样的:0.5ms--------------0度;1.0ms------------45度;1.5ms------------90度;2.0ms-----------135度;2.5ms-----------180度;舵机的追随特性假设现在舵机稳定在A点,这时候CPU发出一个PWM信号,舵机全速由A点转向B点,在这个过程中需要一段时间,舵机才能运B点。
舵机代码:
void Servo_Init(uint16_t pre,uint16_t per)
{
timer_parameter_struct timere_initpara; // 定义定时器结构体
timer_oc_parameter_struct timer_ocintpara; //定时器比较输出结构体
rcu_periph_clock_enable(BSP_Servo_RCU); //开启GPIO时钟
rcu_periph_clock_enable(BSP_Servo_TIMER_RCU); // 开启定时器时钟
rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4); // 配置定时器时钟200MHz
/* 配置GPIO的模式 */ //复用功能模式
gpio_mode_set(BSP_Servo_PORT,GPIO_MODE_AF,GPIO_PUPD_NONE,BSP_Servo_PIN);
/* 配置GPIO的输出 */
gpio_output_options_set(BSP_Servo_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BSP_Servo_PIN);
/* 配置GPIO的复用 */
gpio_af_set(BSP_Servo_PORT,BSP_Servo_AF,BSP_Servo_PIN);
/* 配置定时器参数 */
//timer_parameter_struct timere_initpara; // 定义定时器结构体
timer_deinit(BSP_Servo_TIMER); // 复位定时器
timere_initpara.prescaler = pre-1; // 时钟预分频值 PSC_CLK= 200MHZ / 200 = 1MHZ
timere_initpara.alignedmode = TIMER_COUNTER_EDGE; // 边缘对齐
timere_initpara.counterdirection = TIMER_COUNTER_UP; // 向上计数
timere_initpara.period = per-1; // 周期 T = 10000 * 1MHZ = 10ms f = 100HZ
/* 在输入捕获的时候使用 数字滤波器使用的采样频率之间的分频比例 */
timere_initpara.clockdivision = TIMER_CKDIV_DIV1; // 分频因子
/* 只有高级定时器才有 配置为x,就重复x+1次进入中断 */
timere_initpara.repetitioncounter = 0; // 重复计数器 0-255
timer_init(BSP_Servo_TIMER,&timere_initpara); // 初始化定时器
/* 配置输出结构体 */
//timer_oc_parameter_struct timer_ocintpara; //定时器比较输出结构体
timer_ocintpara.ocpolarity = TIMER_OC_POLARITY_HIGH; // 有效电平的极性
timer_ocintpara.outputstate = TIMER_CCX_ENABLE;
// 配置比较输出模式状态 也就是使能PWM输出到端口
/* 配置定时器输出功能 */
timer_channel_output_config(BSP_Servo_TIMER,BSP_Servo_CHANNEL,&timer_ocintpara);
timer_channel_output_pulse_value_config(BSP_Servo_TIMER,BSP_Servo_CHANNEL,0); //配置占空比 // 配置定时器通道输出脉冲值
timer_channel_output_mode_config(BSP_Servo_TIMER,BSP_Servo_CHANNEL,TIMER_OC_MODE_PWM0); // 配置定时器通道输出比较模式
timer_channel_output_shadow_config(BSP_Servo_TIMER,BSP_Servo_CHANNEL,TIMER_OC_SHADOW_DISABLE);// 配置定时器通道输出影子寄存器
timer_auto_reload_shadow_enable(BSP_Servo_TIMER);
/* 使能定时器 */
timer_enable(BSP_Servo_TIMER);
}
void Angle_Set(int Angle)
{
timer_channel_output_pulse_value_config(TIMER8 ,TIMER_CH_1 ,Angle/180*2000+500) ;
}
避障代码:
if(Mode==3) //模式3避障模式
{
//超声波避障
timer_channel_output_pulse_value_config(TIMER8 ,TIMER_CH_1 ,1500) ; //超声波正前
delay_1ms(200);
if(SR04_Get_Distance()>50)// 前方无障碍物
{
car_front(speed);//前进
delay_1ms(100);
car_stop(1);
delay_1ms(50);
}
if(SR04_Get_Distance()<50) //向前有障碍物
{
timer_channel_output_pulse_value_config(TIMER8 ,TIMER_CH_1 ,500);//向右转90度
if(SR04_Get_Distance()>50)//右侧无障碍物判断
{
car_right(speed);//右转
delay_1ms(400);
car_stop(1);
delay_1ms(5);
//delay_1ms(700);
}
else { //右边有障碍物
timer_channel_output_pulse_value_config(TIMER8 ,TIMER_CH_1 ,2500);//向左转90度
delay_1ms(200);
if(SR04_Get_Distance()>50)//左侧无障碍物
{
car_left(speed);//左转
delay_1ms(400);
car_stop(1);
delay_1ms(5);
}
else{
car_back(speed);//后退
delay_1ms(200);
car_stop(1);
delay_1ms(5);
car_right(speed);//左转
delay_1ms(200);
car_stop(1);
delay_1ms(5);
}
}
}
}
超声波配合舵机使其能够判断左右是否有障碍。由于小车速度太快,可能反应不过来,只能降低速度。
实物演示

思考、反思和总结
原理图有一些没检查好,导致了一些错误,在OLED的移植上耗费了太多时间,由于电源电流不够,导致可能会断电,这些还需要改进。代码的编写移植需要一步一步来,一边检查一边改错,最后再组合起来。
无