newrudeman

Intelligent car based on Liangshan School

 
Overview

基于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的移植上耗费了太多时间,由于电源电流不够,导致可能会断电,这些还需要改进。代码的编写移植需要一步一步来,一边检查一边改错,最后再组合起来。

参考设计图片
×
 
 
Search Datasheet?

Supported by EEWorld Datasheet

Forum More
Update:2025-05-09 06:38:51

EEWorld
subscription
account

EEWorld
service
account

Automotive
development
community

Robot
development
community

About Us Customer Service Contact Information Datasheet Sitemap LatestNews


Room 1530, 15th Floor, Building B, No.18 Zhongguancun Street, Haidian District, Beijing, Postal Code: 100190 China Telephone: 008610 8235 0740

Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号