Rice我叫加饭? · 2021年02月02日

基于RT-THREAD nano的平衡车--下位机软件

首发:Rice 嵌入式开发技术分享
作者:RiceDIY

简要

  • 平衡车文章分为4篇进行说明:
  1. 《平衡车 - 硬件》:讲解平衡车的硬件设计
  2. 《平衡车 - 软件》:讲解平衡车的软件设计,算法。
  3. 《平衡车 - 上位机》:讲解调参上位机的设计
  4. 《平衡车 - 微信小程序》:讲解微信小程序作为遥控器的实现。

软件设计

代码结构

  • 平衡车的代码设计,该平衡车是基于RT-THREAD NANO上进行设计,主要分为3层,driver-device-controler。
  1. driver层:主要对接STM32的HAL层。这部分的代码,模仿了rt-thread完整版的设备驱动框架。
  2. device层:主要实现平衡车各种外设的驱动。
  3. controler层:主要实现平衡车的算法,控制,显示,通信等功能。

image.png

driver层:

  • 主要包含如下驱动:| 驱动 | 功能 | |------|------| | drv\_adc | 测量电压,提供原始数据 | | drv\_flash | 保存平衡车参数,提供操作flash接口 | | drv\_gpio | 通用GPIO的,效仿RTT完整版接口 | | drv\_pulse\_encoder | 脉冲解码器,提供读取编码器数值 | | drv\_pwm | 提供电机驱动底层接口 | | drv\_soft\_i2c | 提供软件I2C接口,可任意扩展 | | drv\_uart | 驱动串口接口,可任意扩展 |
  • 以上的驱动都是根据rt-thread完整版的思想,进行简化,为上层提供统一的接口。
  • 以采集ADC 驱动为例:
  1. 将名字和ADC1实例绑定:
#ifdef RT_USING_ADC1  
#define ADC1_CONFIG                                               \  
    {                                                             \  
        .name = "adc1",                                           \  
        .Instance = ADC1,                                         \  
    }  
#endif  
  
#ifdef RT_USING_ADC2  
#define ADC2_CONFIG                                               \  
    {                                                             \  
        .name = "adc2",                                           \  
        .Instance = ADC2,                                         \  
    }  
#endif  
  1. 为上层提供统一的API,通过rt\_find\_adc()接口找到相对应的实例句柄,然后通过rt\_adc\_enabled()接口使能对应的实例,通过rt\_get\_adc\_value()接口读取对应的通道的ADC值。
struct rt_adc_drv *rt_find_adc(char *name);  
rt_err_t rt_adc_enabled(struct rt_adc_drv *obj, rt_bool_t enabled);  
rt_err_t rt_get_adc_value(struct rt_adc_drv *obj, rt_uint32_t channel, rt_uint32_t *value);  

device层:

  • device层主要提供传感器的原始数据,设备控制接口。设备驱动如下:| 设备 | 功能 | |------|------| | dev\_ble | 提供BLE的发送与接收接口,对接drv\_uart | | dev\_buzzer | 提供控制蜂鸣器接口,对接drv\_gpio | | dev\_encoder | 提供读取编码器数值接口,对接drv\_pulse\_encoder | | dev\_key | 提供读取按键值接口,对接drv\_gpio | | dev\_motor | 通过控制电机接口,对接drv\_pwm | | dev\_mpu6050 | 提供陀螺仪读取数值接口,对接drv\_soft\_i2c | | dev\_oled | 提供陀控制oled接口,对接drv\_soft\_i2c | | dev\_voltage | 通过读取电压接口,对接drv\_adc |
  • 以采集ADC 驱动为例:
  1. 获取对应实例的句柄,以及使能对应实例:
int rt_voltage_dev_init(void)  
{  
    voltage_adc_drv = rt_find_adc("adc1");  
    if(voltage_adc_drv == RT_NULL)  
    {  
        rt_kprintf("find adc1 fail\n");  
        return RT_ERROR;  
    }  
  
    rt_adc_enabled(voltage_adc_drv, RT_TRUE);  
  
    return RT_EOK;  
}  
  1. 获取电压, 上层只需要调用此接口,即可完成电压读取:
rt_uint32_t rt_get_voltage(void)  
{  
    rt_uint32_t vol = 0;  
    rt_uint32_t value = 0;  
    rt_get_adc_value(voltage_adc_drv, VOLTAGE_ADC_CHANNEL, &value);  
  
    vol = (value * REFER_VOLTAGE / CONVERT_BITS) * 11;  
  
    rt_kprintf("the voltage is :%d.%02d \n", vol / 100, vol % 100);  
  
    return value;  
}  

controler层:

  1. ble\_ctrl: 提供BLE与上位机/微信小程序的控制逻辑
  • 串口一个数据发送线程。
  • 注册BLE接受数据回调函数,接受上位机/微信小程序的控制逻辑。因为采用中断以及为了分层,所以采用回调的形式。
  1. show\_menu: oled显示,参数设置,参数显示等控制逻辑
  • 初始化启动了一个线程,用于运行时参数数据实时显示。
  • 每次重新启动都会进入此功能,通过按键和oled,可进行PID,速度等参数整定。
  1. sds:这是一款虚拟示波器,通过串口输入,在我这个平衡车中,我采用BLE转发
  • 方便在整定参数的时候使用。
  1. controler:控制层的总入口,角度计算,平衡PID算法,速度计算,速度PID,方向PID。
  • 创建一个线程,然后每运行一次延时5ms。线程执行内容:
static void ctrl_thread_entry(void *parameter)  
{  
    for(;;)  
    {  
        ctrl_get_angle();   //获取角度  
        ctrl_balance_pid(); //平衡PID计算  
        ctrl_get_speed();   //获取当前速度  
        ctrl_speed_pid();   //速度PID计算  
        ctrl_turn_pid();    //转向PID计算  
        ctrl_set_speed();   //控制速度  
  
        rt_thread_delay(5);  
    }  
}  
  • 角度计算,我直接采用三角函数进行计算,没有采用四元数:
   turn_parm.turn_gyor = gyro.z / 16.384;  
    angle_parm.balance_gyor = gyro.y / 16.384;  
    angle_parm.angle = atan2(accel.x, accel.z) * 180 / PI;  
    angle_parm.angle_increment = angle_parm.out_angle - (gyro.y / 16.384) * 0.005;  
    angle_parm.out_angle = K1 * angle_parm.angle + (1 - K1)*(angle_parm.angle_increment);  
  • 平衡PID算法,采用PD控制算法:
int ctrl_balance_pid(void)  
{  
    float bias_val = 0.0;  
  
    bias_val = (car_parm.blc_angle - 0.5) - angle_parm.out_angle;  
  
    speed_parm.balance_pwm = car_parm.blc_Kp * bias_val + car_parm.blc_Kd * angle_parm.balance_gyor;  
  
    return speed_parm.balance_pwm;  
}  
  • 速度计算,直接获取左右编码器的值
  • 速度PID,采用PI控制算法:
void ctrl_speed_pid(void)  
{  
    float current_bias = 0;  
  
    current_bias = (speed_parm.get_left_speed + speed_parm.get_right_speed) - car_parm.speed;  
  
    current_bias = speed_parm.last_bias * 0.3 + current_bias * 0.7;  
    speed_parm.integral_bias += current_bias;  
  
    speed_parm.speed_pwm = (int)(car_parm.speed_Kp * current_bias +  
                         car_parm.speed_Ki * speed_parm.integral_bias);  
  
    speed_parm.last_bias = current_bias;  
}  
  • 速度PID,我这里是没有太关乎的,只有P值.
  • 速度设置,将平衡PID、速度PID、转向PID计算出来的整合既是最终速度的值。

效果:

image.png

关注微信公众号『Rice嵌入式开发技术分享』,后台回复“微信”添加作者微信,备注”入群“,便可邀请进入技术交流群。

image.png
推荐阅读

更多嵌入式技术干货请关注Rice 嵌入式开发技术分享
推荐阅读
关注数
1761
内容数
51
一个周末很无聊的嵌入式软件工程师,写写经验,写写总结。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息