OpenVINOTM,给你看得见的未来!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » 夏季在下季的自平衡小车进程帖

共20条 1/2 1 2 跳转至

夏季在下季的自平衡小车进程帖

助工
2015-08-28 20:02:57    评分

从今天开始就要开启平衡小车的进程之路了,由于自己第一次接触STM32,在帖子中肯定会出现错误的地方,还请大家多多指正。



1.平衡小车的开箱、组装、测试...............................................2楼

2.电机驱动实验......................................................................3楼

3.编码器数据采集实验............................................................4楼

4.无线通讯实验(Nrf24l01).................................................5楼

5.传感器驱动实验(mpu6050).............................................6楼

6.多传感器数据融合................................................................7楼

7.PID调试..............................................................................8楼

8.实现平衡小车直立时处于静止状态........................................9楼

9.实现平衡小车绕八字行走.....................................................10楼

10.实现小车自主避障.............................................................11楼

11.实现体感小车....................................................................12楼

12.实现PS2遥杆控制小车.......................................................13楼

13.实现按键控制小车.............................................................14楼

14.实现变速小车....................................................................15楼

15.简易遥控DIY.....................................................................16楼



助工
2015-08-28 23:40:08    评分
2楼

1.平衡小车的开箱、组装、测试

(1)开箱

在取快递回来的路上,突然下起雨,很是不幸,还好雨量不大,保护好快件,回到家,开箱,发现各个模块包裹的非常好,可见店主真是良苦用心。

取出各个模块,很好奇里面都是什么东西。

(2)小车组装

组装之前,一定要看《【EEPW专版】平衡小车使用说明(必看)》这个PDF文件,里面有很多主要事项,非常重要。

下面的模块为:小车主板、航模锂聚合物电池、充电器、车轮、OLED、亚克力板、带编码器的电机和3d打印的一体支架

安装轮胎:直接将车轮插上即可,力气不能太大。



安装双通铜柱:拧紧就可以了。

安装电池:用力往下压,不知道电池的安装方向,感觉怎么安装都挺紧的,暂且先这样吧

安装主板:注意主板上开关的方向和电池供电口是一致的。


安装铜柱:目的是固定电池、主板和后面要安装的亚克力板。接上电机线和电池线:电机线是店主别好了的。接线的时候,发现电机线真的不够长,正如《平衡小车使用说明》中提到的一样,这种情况下,是电机装反了,感觉一开始就有点别扭。然后拆下来,把电机水平旋转180度,再重新安装就好了。


安装OLED:显示屏和排针需要自己焊接,焊好插上即可。


安装亚克力板:主要是为了保护电路板。安装的时候,发现左下角的螺丝拧不上去,不过也无所谓,少一个也没事,很稳固。


接上电池接头:电池线很硬,不太好接,接插头的时候要用力。


组装终于完毕,最后上靓照一张


(3)测试小车

小车上电测试:先拨动左边的开关,再按小车左上方的按键,小车就可以平衡了。


视频地址:http://player.youku.com/player.php/sid/XMTMyMTg2OTA4MA==/v.swf

小车左右晃动来保持平衡,平衡能力还是不错的,推一下小车,还能恢复到原来的地方。在亚克力板上放重物,小车晃动速度明显加快,可能和重心升高有关。


手机蓝牙连接小车测试:把Mini Balance_v1.2.apk安装到手机上,打开并连接,待小车上的红灯由闪烁变为常亮时,连接成功,就可以控制小车了。


视频地址:http://player.youku.com/player.php/sid/XMTMyMTg3NDkzNg==/v.swf

小车能够前进,后退,左转,右转,电机停止和启动旋转。操作的时候,左转和右转不太好控制,一直转就可以转得很好。


谢谢观看!



助工
2015-08-30 22:45:29    评分
3楼

2.电机驱动实验

实验目的:验证STM32是如何通过TB6612FNG(电机驱动芯片)控制电机转速和方向的。

电机转速的控制脉冲宽度调制(PWM)这项技术来实现的,脉宽调制方式产生占空比变化的PWM信号,PWM占空比的大小决定输出电压平均值,进而决定电机的转速。STM32的定时器可以产生PWM输出,可以通过其内部相关寄存器来控制PWM,可以参考《STM32参考手册》14.3.9节。

STM32是通过TB6612FNG控制电机转速和方向的,先来看看TB6612FNG逻辑真值表


从表中可以看出,STBY为H,IN1为H,IN2为L,PWM为H,电机正转;STBY为H,IN1为L,IN2为H,PWM为H,电机反转。

下面再看看STM32与TB6612FNG的硬件连接图


从真值表和驱动电路可以看出,PB1、PB0分别控制两电机的转速,PB15、PB14和PB13、PB12分别控制两电机的方向。STM32只需要控制这六个引脚就可以了。

保留源码中MiniBalance_PWM_Init(u16 arr,u16 psc);Set_Pwm(int moto1,int moto2);Xianfu_Pwm(void)等主要程序,注释掉不需要的程序。在主程序中修改为

int main(void)
{
Stm32_Clock_Init(9); //72M
delay_init(72); //延时函数
MiniBalance_PWM_Init(3599,0);   //初始化PWM 20KHZ
while(1)
{
Moto1=1000,Moto2=1000; //Moto1、Moto2分别为左右电机的PWM值
Xianfu_Pwm();                                           //限幅
Set_Pwm(Moto1,Moto2);                              //===赋值给PWM寄存器
}
}

MiniBalance_PWM_Init(3599,0)函数

arr=3599;psc=0

计数器频率fCK_CNT=fCK_PSC/(psc+1)=72000000/1=72Mhz

PWM频率fCK_PWM=fCK_CNT/(arr+1)/72000000/3600=20khz

(TB6612FNG最高可支持频率为100khz的PWM)


程序下载到小车中,下载教程参考

STM32使用MCUISP下载程序教程


观察实验现象(拿起小车,小车轱辘没有挨地,主要看电机运行状态):

(1)Moto1=1000,Moto2=1000时,

左边轮子顺时针旋转(眼睛面对电机轴端面方向

右边轮子逆时针旋转(眼睛面对电机轴端面方向

小车向后运动

(2)修改参数

Moto1=3000,Moto2=3000时,

小车轮子转速加快,方向没有改变

(3)修改参数

Moto1=-1000,Moto2=-1000时,

左边轮子逆时针旋转(眼睛面对电机轴端面方向

右边轮子顺时针旋转(眼睛面对电机轴端面方向

小车向前运动

(4)修改参数

Moto1=-3000,Moto2=-3000时,

小车轮子转速加快,方向没有改变



总结:通过这次试验,我改变了一个误区,我一直以为电机顺时针转为正转,逆时针转为反转。平衡小车的左右两个电机给同极性值时,旋转方向却相反(眼睛面对电机轴端面方向。如下表

PWM 电机(左) 电机(右
小车方向

另外,电机的转速随着PWM绝对值的增大而加快,增大到3500(限幅值)时,不再增加,转速不再变化。








助工
2015-08-31 22:18:22    评分
4楼

3.编码器数据采集实验

实验目的:通过给电机不同PWM值,采集编码器传来的数据,并显示在OLED屏上。

STM32内部定时器可以配置为编码器接口模式,可以参考《STM32参考手册》14.3.12节。

先来看看STM32与电机编码接口的硬件连接图


STM32通过PA0、PA1获取两路方波,计数器在方波边沿计数,计数器的值可以表示左边电机的转速;同样,通过PB6、PB7可以获得右边电机的转速。

修改源码主程序为

#include "main.h"
int Encoder_Left,Encoder_Right;      //左右编码器的脉冲计数
int Moto1=0,Moto2=0; //电机PWM变量 应是Motor的 向Moto致敬
int Voltage;                                //电池电压采样相关的变量
float Show_Data_Mb; //全局显示变量,用于显示需要查看的数据

int main(void)
{
Stm32_Clock_Init(9);            //系统时钟设置
delay_init(72);            //=====延时函数
usart1_init(); //=====串口1初始化 波特率:115200
JTAG_Set(JTAG_SWD_DISABLE);     //=====关闭JTAG接口
JTAG_Set(SWD_ENABLE);         //=====打开SWD接口 可以利用主板的SWD接口调试
led_init();                    //=====LED初始化
Adc_Init();                  //=====初始化ADC模块
MiniBalance_PWM_Init(3599,0);   //=====初始化PWM 20KHZ 高频可以防止电机低频时的尖叫声
OLED_Init();                  //=====初始化OLED 模拟SPI
Encoder_Init();                 //=====初始化编码器1
Encoder_Init2();              //=====初始化编码器2
delay_ms(200);                  //=====延时等待稳定
Timer1_Init(49,7199);           //=====5MS进一次中断服务函数 中断服务函数在minibalance.c里面
while(1)
{
u8 i;

Xianfu_Pwm();                                    //===PWM限幅
if(Turn_Off(Voltage)==0) //===如果不存在异常
Set_Pwm(Moto1,Moto2);
for(i=0;i<3;i++) //延时3s,便于OLED显示
delay_ms(1000);
oled_show(); //===显示屏打开

Moto1=Moto1+200;
Moto2=Moto2-200;
}
}
其中
oled_show()函数改为

static u32 Count;
void oled_show(void)
{
Count=0;
OLED_Display_On();  //显示屏打开
//=============显示PWM1====================//
OLED_ShowString(00,0,"Moto1");
if(Moto1<0) OLED_ShowString(45,0,"-"),
OLED_ShowNumber(65,0, -Moto1,4,12);
else            OLED_ShowString(45,0,"+"),
OLED_ShowNumber(65,0, Moto1,4,12);
//=============显示PWM2====================//
OLED_ShowString(00,10,"Moto2");
if(Moto2<0) OLED_ShowString(45,10,"-"),
OLED_ShowNumber(65,10, -Moto2,4,12);
else            OLED_ShowString(45,10,"+"),
OLED_ShowNumber(65,10, Moto2,4,12);
//=============显示编码器1====================//
OLED_ShowString(00,20,"Enco1");
if( Encoder_Left<0) OLED_ShowString(45,20,"-"),
OLED_ShowNumber(65,20,-Encoder_Left,5,12);
else                 OLED_ShowString(45,20,"+"),
OLED_ShowNumber(65,20, Encoder_Left,5,12);
//=============显示编码器2====================//
OLED_ShowString(00,30,"Enco2");
if(Encoder_Right<0) OLED_ShowString(45,30,"-"),
OLED_ShowNumber(65,30,-Encoder_Right,5,12);
else               OLED_ShowString(45,30,"+"),
OLED_ShowNumber(65,30,Encoder_Right,5,12);
//=============显示电压=====================//
OLED_ShowString(00,40,"Volta");
OLED_ShowString(58,40,".");
OLED_ShowString(80,40,"V");
OLED_ShowNumber(45,40,Voltage/100,2,12);
OLED_ShowNumber(68,40,Voltage%100,2,12);
if(Voltage%100<10) OLED_ShowNumber(62,40,0,2,12);
//=============刷新=======================//
OLED_Refresh_Gram();

}

 给Moto1与Moto2赋初值为0,此后Moto1每3s增加200;Moto2每3s减少200。STM32每5ms读取一次编码器的值,在主函数中大约3s OLED刷新一次。
 

观察现象:


视频地址:http://player.youku.com/player.php/sid/XMTMyNDI1ODcxNg==/v.swf


 小车转速从静止开始转动,两电机都按顺时针转(眼睛面对电机轴端面方向),速度逐渐加快,PWM绝对值为3500时,开始匀速转动,此时速度达到最大。Moto1,Moto2,Enco1,Enco2,Volte显示在OLED屏上。



助工
2015-09-14 17:35:58    评分
5楼

4.无线通讯实验(Nrf24l01

    实验目的:实现两块Nrf24l01之间的无线通信,一个用于发送,一个用于接收,通信结果显示在OLED屏上。

    首先肯定要先介绍一下Nrf24l01了。NRF24L01 是NORDIC 公司生产的一款无线通信通信芯片,工作在2.4GHz世界通用ISM频段,采用GFSK 调制,抗干扰能力强。可以实现点对点或是1 对6 的无线通信。无线通信速度可以达到2M(bps)。只需要为MCU预留5 个GPIO,1 个中断输入引脚,就可以很容易实现无线通信的功能。

    Nrf24l01模块现在用的很多,可以花几块钱就可以买到。


    该模块外形和引脚图如下图


①GND:接地。

②VCC:电压范围为1.9~3.6V,建议不要超过 3.6V ,否则可能烧坏模块,一般用 3.3V电压比较合适 。

③CE:芯片的模式(RX或TX)控制。在CSN为低的情况下,CE协同Nrf24l01的CONFIG 寄存器共同决定Nrf24l01 的状态。

④CSN:SPI片选,CSN为低电平MCU与芯片正常通讯。

⑤SCK:SPI时钟。

⑥MOSI:从SPI数据输入脚。主机(STM32)输出,从机(Nrf24l01输入

⑦MISO:从SPI数据输出脚。主机(STM32)输入,从机(Nrf24l01)输出

⑧IRQ:可屏蔽中断脚无线通信过程中MCU 主要是通过IRQ 判断Nrf24l01 的状态。

    除了 VCC和 GND 脚,其他引都可以和5V 单片机的单片机的IO口直连,正是因为其兼容 5V 单片机的 IO ,故使用上具有很大优势。

    Nrf24l01的工作模式有以下几种

模式

PWR_UP

PRIM_RX

CE

FIFO寄存器状态

接收模式

1

1

1

-

发送模式

1

0

1

数据在TX FIFO寄存器中

发送模式

1

0

1->0

停留在发送模式,直到数据发送完

待机模式II

1

0

1

TX FIFO为空

待机模式I

1

-

0

无数据传输

掉电模式

0

-

-

-

    在掉电模式下,可以配置各寄存器。PWR_UP=1,芯片进入待机模式1 。MCU置高引脚CE,芯片进入待机模式2 。PRIM_RX为1 ,芯片进入接收模式;PRIM_RX为0 ,芯片进入发送模式(PWR_UP和PRIM_RX分别为配置寄存器CONFIG的第1位和第0位,相关寄存器和命令字介绍参考Nrf24l01数据手册)。

    对Nrf24l01 的固件编程的基本思路如下:
1) 置 CSN 为低,使能芯片,配置芯片各个参数。配置参数在 掉电状态中完成。
2) 如果是 Tx 模式,填充Tx FIFO。
3) 配置完成以后,通过 CE 与CONFIG 中的PWR_UP 与PRIM_RX 参数确定Nrf24l01要切换到的状态。
发送模式:PWR_UP=1; PRIM_RX=0; CE=1 (保持超过10us 就可以);
接收模式: PWR_UP=1; PRIM_RX=1; CE=1;
4) IRQ 引脚会在以下三种情况变低:
①Tx FIFO 发完并且收到ACK(使能ACK 情况下)
②Rx FIFO 收到数据
③达到最大重发次数
将 IRQ 接到MCU外部中断输入引脚,通过中断程序进行处理。


    保留源码中24l01.c和24l01.h,spi.c和spi.h等函数,修改主函数程序如下

int main(void)

Stm32_Clock_Init(9);            //系统时钟设置
delay_init(72);                 //延时初始化
JTAG_Set(JTAG_SWD_DISABLE);     //=====关闭JTAG接口
JTAG_Set(SWD_ENABLE);           //=====打开SWD接口 可以利用主板的SWD接口调试
OLED_Init();                    //OLED初始化
NRF24L01_Init();              //=====NRF24L01模块初始化

while(NRF24L01_Check()) //检查NRF24L01是否在位. 
{
OLED_ShowString(00,0,"NRF24L01 Error");
OLED_Refresh_Gram();
delay_ms(200);
}   
 
while(1)
{
u8 mode=1; //发射模式  mode=0接收模式 
u8 tmp_buf[33];
u8 tmp[33]={"Mini Balance"};
u16 t=0,count

 
if(mode==0)//RX模式
{
RX_Mode();  
while(1)
{            
OLED_ShowString(0,0,"NRF24L01 OK");
OLED_ShowString(0,12,"NRF24L01 RX_Mode");
OLED_ShowString(0,24,"Received DATA:");
if(NRF24L01_RxPacket(tmp_buf)==0)//一旦接收到信息,则显示出来.
{
OLED_ShowString(0,36,tmp_buf);
OLED_Refresh_Gram();
}
else delay_us(100);   

}
else//TX模式
{    
TX_Mode();
while(1) 
{  
OLED_ShowString(0,0,"NRF24L01 OK");
OLED_ShowString(0,12,"NRF24L01 TX_Mode");
OLED_ShowString(0,24,"Sended DATA:");
if(++count>100)count=0;
for(t=0;t<32;t++)
{
tmp_buf[t]=tmp[t]; 
}
if(NRF24L01_TxPacket(tmp_buf)==TX_OK)
{
OLED_ShowString(0,36,tmp_buf);
OLED_Refresh_Gram();
}
else delay_ms(10);    
}
}      

}
    下载到小车主板(发送mode=1)和另一块板子(接收mode=0)上,观察结果:

小车主板上OLED显示:

NRF24L01 OK

NRF24L01 TX_Mode

Sended DATA:

Mini Balance


 另一块板子上OLED显示:

NRF24L01 OK

NRF24L01 RX_Mode

Received DATA:

Mini Balance


     两块板子OLED屏上都没有显示出“NRF24L01 Error”,说明NRF24L01自检成功。并且OLED屏上显示数据正常,说明两块NRF24L01通信成功。

    

 

 


助工
2015-09-16 23:04:17    评分
6楼

5.传感器驱动实验(mpu6050)

    实验目的:学习mpu6050传感器原理,利用其自带的DMP实现姿态解算,获取mpu6050的俯仰角(Pitch)、横滚角(Roll)、航向角(Yaw)和温度(Temperature),并显示在OLED屏上。

(1)mpu6050

    MPU6050是InvenSense公司推出的全球首款整合性 6轴运动处理组件,相较于多组件方案;免除了组合陀螺仪与加速器时之轴间差的问题;减少安装空间。 
    MPU6050内部整合了3轴陀螺仪和3轴加速度传感器,并且含有一个第二IIC接口,可用于连接外部磁力传感器,并利用自带的数字运动处理( DMP: Digital Motion Processor)硬件加速引擎,通过主IIC 接口,向应用端输出完整的9轴融合演算数据。有了DMP,我们可以使用 InvenSense公司提供的运动处理资料库,非常方便实现姿态解算,降低了运动处理运算对操作系统的负荷,同时大大降低了开发难度。

    mpu6050对陀螺仪和加速度计分别用了三个16 位的ADC,将其测量的模拟量转化为可输出的数字量。为了精确跟踪快速和慢速的运动,传感器的测量范围都是用户可控的,陀螺仪可测范围为±250,±500,±1000,±2000°/秒(dps),加速度计可测范围为±2,±4,±8,±16g。

    mpu6050传感器的检测轴及其方向如下


    下面看看STM32与mpu6050的硬件连接图


    其中。SCL和SDL是连接STM32的IIC接口,STM32通过这个IIC接口来控制MPU6050和读取数据。VLOGIO是 IO 口电压,该引脚最低可以到1.8V,我们 一般直接 VDD 即可。AD0是从 IIC 接 口(MCU)的地址控制引脚,该引脚控制IIC 地址的最低位。如果接GND,则 MPU6050的IIC地址是: 0X68 ,如果接 ,如果接VDD,则是 0X69,注意: 这里的地址是不包含数据传输最低位(用来表示读写)!原理图上AD0是接地的,所以地址为0X68 。

(2)DMP介绍

    我们可以读出MPU6050 的加速度传感器和角速度传感器的原始数据。不过这些原始数 ,对想搞平横车之类的初学者来说,用处不大,我们期望得到是姿态数据,也就是欧拉角:航向角( yaw)、横滚角( roll)和俯仰角( pitch)。有了这三个角,我们就 可以得到当前平衡车的姿态,这才是我们想要结果。

    版主的帖子对DMP的介绍与代码移植已经介绍的很详细了,大家可以参考一下

MPU6050的DMP移植教程


(3)修改代码

    添加oled.c和oled.h、show.c和show.h文件到移植程序中,同时修改主函数

#include "sys.h"

int main(void)

Stm32_Clock_Init(9);  //系统时钟设置
delay_init(72); //延时初始化
uart_init(72,9600);
IIC_Init();                     //模拟IIC初始化
MPU6050_initialize();           //=====MPU6050初始化
DMP_Init();                     //初始化DMP 
OLED_Init();
while(1)
{
Read_DMP();
Temperature=Read_Temperature(); 
oled_show();

}
其中oled_show()内容为

void oled_show(void)
{
OLED_Display_On();  
//=============显示Pitch=====================//
                          OLED_ShowString(00,0,"Pitch");
if( Pitch<0)       {OLED_ShowString(48,0,"-");
                          OLED_ShowNumber(54,0, -Pitch,4,12);}
  else                 {OLED_ShowString(48,0,"+");
                          OLED_ShowNumber(54,0, Pitch,4,12);}
//=============显示Roll=====================//
                           OLED_ShowString(00,15,"Roll");}
 if( Roll<0)         {OLED_ShowString(48,15,"-");
                           OLED_ShowNumber(54,15, -Roll,4,12);}
    else                {OLED_ShowString(48,15,"+");
                           OLED_ShowNumber(54,15, Roll,4,12);}
//=============显示Yaw====================//
                          OLED_ShowString(00,30,"Yaw");
if( Yaw<0)         {OLED_ShowString(48,30,"-");
                          OLED_ShowNumber(54,30, -Yaw,4,12);}
  else                {OLED_ShowString(48,30,"+");
                          OLED_ShowNumber(54,30, Yaw,4,12);}
//=============显示温度====================//
                     OLED_ShowString(00,45,"Wendu");
                     OLED_ShowNumber(45,45,Temperature/10,2,12);
                     OLED_ShowNumber(68,45,Temperature%10,1,12);
                     OLED_ShowString(58,45,".");
                     OLED_ShowString(80,45,"`C");

//=============刷新=====================//
OLED_Refresh_Gram();
}

4.显示结果

(1)板子平放在桌面上

    可以看到俯仰角(Pitch)、横滚角(Roll)、航向角(Yaw)的值接近于0,温度(Temperature为29.8度。

(2)随意拿起板子


    可以看到俯仰角(Pitch)、横滚角(Roll)、航向角(Yaw)的绝对值都变大,温度(Temperature为29.6度。
    OLED屏上数据显示正常,STM32通过
mpu6050自带的DMP,得到姿态角,获取数据成功。


助工
2015-09-22 15:37:34    评分
7楼

6.多传感器数据融合

    实验目的:STM32获取主板温度(MPU6050获取),左右编码数值(编码器获取),电池电压(ad获取),平衡倾角(MPU6050获取,并卡尔曼滤波)后,通过Nrf24l01无线传输到另一块板子,并显示在OLED屏上。

    这次的知识点都是以前说过的,就是把各个传感器的数据融合在一起,直接改代码。主要修改24l01.c中的NRF24L01()函数。

void NRF24L01(void)
{
u8 mode=0,count;  
u8 tmp_buf[33];
if(mode==0)//RX模式
{
RX_Mode();  
while(1)
{            
if(NRF24L01_RxPacket(tmp_buf)==0)//一旦接收到信息,则显示出来.
{
Temperature=tmp_buf[1]*256+tmp_buf[2];
Encoder_Left=tmp_buf[3]*256+tmp_buf[4]-32768;
Encoder_Right=tmp_buf[5]*256+tmp_buf[6]-32768; 
Voltage=tmp_buf[7]*256+tmp_buf[8]; 
Angle_Balance=tmp_buf[9]*256+tmp_buf[10]-180;
}else delay_us(100);   
oled_show();    
};
}else//TX模式
{    
TX_Mode();
while(1)
{  
     if(++count>100)count=0;
tmp_buf[1]=Temperature/256; 
tmp_buf[2]=Temperature%256;

Encoder_Left=Encoder_Left+32768;
tmp_buf[3]=Encoder_Left/256;
tmp_buf[4]=Encoder_Left%256;

Encoder_Right=Encoder_Right+32768;
tmp_buf[5]=Encoder_Right/256;
tmp_buf[6]=Encoder_Right%256;

tmp_buf[7]=Voltage/256;
tmp_buf[8]=Voltage%256;

Angle_Balance=Angle_Balance+180;
tmp_buf[9]=Angle_Balance/256;
tmp_buf[10]=(int)Angle_Balance%256;

if(NRF24L01_TxPacket(tmp_buf)==TX_OK)
{
 OLED_ShowString(00,0,"NRF24L01 TX OK");
OLED_Refresh_Gram();
}else  
delay_ms(10);    
}
}      
}


观察现象


视频地址:http://player.youku.com/player.php/sid/XMTM0MjE3NTE5Mg==/v.swf

    可以看到,小车在直立时,不断地把数据传到另一块板子,并显示在OLED屏上,数据显示正常。


助工
2015-09-29 14:07:37    评分
8楼

7.PID调试

    实验目的:按键修改PID各个参数值,通过Nrf24l01无线通讯把修改的数据传递给小车,进行调试,参数内容实时显示在OLED屏上。

    PID这个东西还是比较难的。不仅需要理论基础,还需要多年的调试经验,掌握起来比较困难。和我一样的初学者可以看看这个PID通俗易懂.pdf

   源程序中涉及到的PID控制主要有直立PD控制、速度PI控制、转向PD控制。

(1)直立PD控制






    最上面红圈数值为平衡的角度中值,也就是小车重心,注释已经很清楚了。这一数值要先调好,否则会影响后面参数的调试。我的方法是,看小车平衡时OLED屏上的

angle角度值,会在一个范围变化,取最大值与最小值的中间值。例如:我的小车在359度与2度之间徘徊,所以我取0.5。

    下面两个参数分别为直立时P参数和D参数,需要后面调的。我用变量BP和BD代替。

(2)速度PI控制










     如果没有速度PI控制,小车会往重心方向加速运行,并倒下。

     红圈两个参数分别为速度P参数和I参数,我用变量VP和VI代替。

(3)转向PD控制








     如果没有转向PD控制,小车会在原地慢慢的打转。

     红圈两个参数分别为转向P参数和D参数,我用变量TP和TD代替。



来看看另一块板子上的按键排布如下













    只用到前4个按键:


 S1用于BP,BD,VP,VI,TB,TD六个参数的选择键;

 S2用于BP,BD,VP,VI,TB,TD六个参数的选择键(相反方向);

 S3用于BP,BD,VP,VI,TB,TD六个参数数值增加键;

 S4用于BP,BD,VP,VI,TB,TD六个参数数值减少键;


软件修改

(1)修改按键程序key.c和key.h

void KEY_Init(void)
{
RCC->APB2ENR|=1<<3;    //使能PORTB时钟    
GPIOB->CRH&=0X0000FFFF; 
GPIOB->CRH|=0X88880000;//PB12 PB13 PB15 PB15 上拉输入
  GPIOB->ODR|=0X0000F000; //PB12 PB13 PB15 PB15 上拉

void KEY_Scan (void)
{
if(s1==0)
{
delay_ms(10);
if(s1==0)
{
s1num++;
while(!s1);
if(s1num==7) s1num=0;
}
  }
if(s2==0)
{
delay_ms(10);
if(s2==0)
{
s1num--;
while(!s2);
}
  }
if(s1num!=0)
{
if(s3==0)
{
delay_ms(10);
if(s3==0)
{
while(!s3);
if(s1num==1) BP+=1;
if(s1num==2) BD+=0.005;
if(s1num==3) VP+=0.5;
if(s1num==4) VI+=0.001;
if(s1num==5) TP+=0.5;
if(s1num==6) TD+=0.002;
}
}
if(s4==0)
{
delay_ms(10);
if(s4==0)
{
while(!s4);
if(s1num==1) BP-=1;
if(s1num==2) BD-=0.005;
if(s1num==3) VP-=0.5;
if(s1num==4) VI-=0.001;
if(s1num==5) TP-=0.5;
if(s1num==6) TD-=0.002;
}
}
}

(2)修改无线通讯程序24l01.c中的void NRF24L01()函数

void NRF24L01(void)
{
u8 mode=1,count;  
u8 tmp_buf[33];
if(mode==0)//RX模式
{
RX_Mode();  
while(1)
{            
if(NRF24L01_RxPacket(tmp_buf)==0)//一旦接收到信息,则显示出来.
{
key_flag=1;

BP=(tmp_buf[1]*256+tmp_buf[2])/10.0;
BD=(tmp_buf[3]*256+tmp_buf[4])/1000.000;

VP=(tmp_buf[5]*256+tmp_buf[6])/10.0; 
VI=(tmp_buf[7]*256+tmp_buf[8])/1000.000; 

TP=(tmp_buf[9]*256+tmp_buf[10])/10.0;
TD=(tmp_buf[11]*256+tmp_buf[12])/1000.000;
s1num=tmp_buf[13];
oled_show();

}else break;   
  
};
}else//TX模式
{    
TX_Mode();
while(1)
{  
     if(++count>100)count=0;

tmp_buf[1]=BP*10/256; 
tmp_buf[2]=(u16)(BP*10)%256;

tmp_buf[3]=BD*1000/256;
tmp_buf[4]=(u16)(BD*1000)%256;

tmp_buf[5]=VP*10/256;
tmp_buf[6]=(u16)(VP*10)%256;

tmp_buf[7]=VI*1000/256;
tmp_buf[8]=(u16)(VI*1000)%256;

tmp_buf[9]=TP*10/256;
tmp_buf[10]=(u16)(TP*10)%256;

tmp_buf[11]=TD*1000/236;
tmp_buf[12]=(u16)(TD*1000)%256;
tmp_buf[13]=s1num;
        
if(NRF24L01_TxPacket(tmp_buf)==TX_OK)
{
key_flag=1;

}else  
break;
 
}
}      
}
(3)修改显示程序show.c中oled_show()程序

void oled_show(void)
{
OLED_Display_On();  //显示屏打开
//=============显示BP=======================//
                     OLED_ShowString(00,0,"BP");
                     OLED_ShowNumber(30,0, BP,3,12);
                     OLED_ShowString(48,0,".");
                     OLED_ShowNumber(54,0, (u16)(BP*10)%10,1,12);
if(s1num==0) OLED_ShowString(100,0,"  ");
if(s1num==1) OLED_ShowString(100,0,"BP");
//=============显示BD=======================//
                     OLED_ShowString(00,10,"BD");
                     OLED_ShowNumber(30,10, BD,3,12);
                     OLED_ShowString(48,10,".");
                     OLED_ShowNumber(54,10, (u16)(BD*1000)%1000,3,12);
if(s1num==2) OLED_ShowString(100,0,"BD");
//=============显示VP=======================//
                     OLED_ShowString(00,20,"VP");
                     OLED_ShowNumber(30,20, VP,3,12);
                     OLED_ShowString(48,20,".");
                     OLED_ShowNumber(54,20, (u16)(VP*10)%10,1,12);
if(s1num==3) OLED_ShowString(100,0,"VP");
  //=============显示VI=======================//
                     OLED_ShowString(00,30,"VI");
                     OLED_ShowNumber(30,30, VI,3,12);
                     OLED_ShowString(48,30,".");
                     OLED_ShowNumber(54,30, (u16)(VI*1000)%1000,3,12);
if(s1num==4) OLED_ShowString(100,0,"VI");
//=============显示TP=======================//
                     OLED_ShowString(00,40,"TP");
                     OLED_ShowNumber(30,40, TP,3,12);
                     OLED_ShowString(48,40,".");
                     OLED_ShowNumber(54,40, (u16)(TP*10)%10,1,12);
if(s1num==5) OLED_ShowString(100,0,"TP");
//=============显示TD=======================//
                     OLED_ShowString(00,50,"TD");
                     OLED_ShowNumber(30,50, TD,3,12);
                     OLED_ShowString(48,50,".");
                     OLED_ShowNumber(54,50, (u16)(TD*1000)%1000,3,12);
if(s1num==6) OLED_ShowString(100,0,"TD");
if(key_flag==1) OLED_ShowString(100,50,"OK");
//=============刷新=======================//
OLED_Refresh_Gram();
}

观察现象

    先看看OLED屏的显示状态












    一手要按键,另一只手要拍摄,没有多余的手去扶小车,所以上电直接给源程序中的PID初值。

    小车装上测距模块和无线模块,拆下OLED屏(因为只有一个,不够使),装在另一块板子上。


视频地址:http://player.youku.com/player.php/sid/XMTM0NzY3NjQ1Ng==/v.swf

    视频不知道怎么了,在自己电脑上播放器播放正常,传到优酷就倒过来了,不太影响效果,就不更改了。

    一开始上电,传给小车的是源程序的PID参数值,小车左右摆动大约7cm。按键增加BP值,增加到BP=70,小车左右摆动大约4cm。重启板子,小车PID值恢复到初始状态,小车左右摆动恢复到大约7cm。哈哈,接下来可以尽情的调试PID了。


助工
2015-09-29 17:26:31    评分
9楼

8.实现平衡小车直立时处于静止状态

    平衡车直立时处于静止或相对静止状态,那么它的前进、后退、左移、右移可能不会很稳定,所以在调试PID参数时,要全面的调试,得到一组最好的数据,使小车直立和跑起来的时候同样稳定。在这节只是实现平衡小车直立时处于静止状态,那么就舍弃了其他的情况。

    通过上节按键调节PID参数的方法,实现实验目的,效果如下所示:



视频地址:http://player.youku.com/player.php/sid/XMTM0NzcwNDM5Mg==/v.swf

助工
2015-10-03 12:27:53    评分
10楼

9.实现平衡小车绕八字行走

    国庆节到了,趁着假期期间来更新一贴!

    实现小车绕八字行走的原理是让小车顺时针转一圈,再按逆时针转一圈,如此循环下去,轨迹如同8字。所以要控制好每一圈的时间。

源程序中有四个变量Flag_Qian,Flag_Hou,Flag_Left,Flag_Right分别表示小车的前进、后退、左转、右转,可以用这四个标志位实现小车顺、逆时针转。

    另外,还要设置小车顺时针与逆时针转的时间,分别用time1和time2表示。这两个时间用另一块板子的四个按键设置,通过无线通讯Nrf24l01传递给小车,数据显示在OLED屏上。


S1表示time1增加;

S2表示time1减少;

S3表示time2增加;

S4表示time2减少




软件修改

(1)修改MiniBalance.c和MiniBalance.h中的5ms中的函数

    小车转圈时间在5ms中断函数在进行,顺时针时间time1*5ms;逆时针时间time2*5ms


void TIM1_UP_TIM16_IRQHandler(void)
{
if(TIM1->SR&0X0001)//5ms定时中断
{
TIM1->SR&=~(1<<0); //===清除定时器1中断标志位

if(Flag_Stop==0) time++;
if(time0)
{
Flag_Qian=1,Flag_Hou=0,Flag_Left=0,Flag_Right=1;
}
else if(time==time1)
{
Flag_Qian=0,Flag_Hou=0,Flag_Left=0,Flag_Right=0;
}
else if(time {
Flag_Qian=1,Flag_Hou=0,Flag_Left=1,Flag_Right=0;
}
else if(time>=time2)
{
time=0;
Flag_Qian=0,Flag_Hou=0,Flag_Left=0,Flag_Right=0;
}

readEncoder();                                           //===读取编码器的值
Led_Flash(400);                                          //===LED闪烁;
Get_battery_volt();                                      //===获取电池电压
key(100);                                                //===扫描按键状态
Get_Angle(Way_Angle);                                    //===更新姿态
Balance_Pwm =balance(Angle_Balance,Gyro_Balance);     //===平衡PID控制
Velocity_Pwm=velocity(Encoder_Left,Encoder_Right);    //===速度环PID控制
Turn_Pwm    =turn(Encoder_Left,Encoder_Right,Gyro_Turn); //转向PID控制

Moto1=Balance_Pwm+Velocity_Pwm-Turn_Pwm;    //计算左轮电机最终PWM

Moto2=Balance_Pwm+Velocity_Pwm+Turn_Pwm;    //计算右轮电机最终PWM
Xianfu_Pwm();                                            //PWM限幅
if(Turn_Off(Angle_Balance,Voltage)==0)                   //如果不存在异常
Set_Pwm(Moto1,Moto2);                                    //赋值给PWM寄存器

}
}
(2)修改按键程序key.c和key.h

void KEY_Init(void)
{
RCC->APB2ENR|=1<<3; //使能PORTB时钟
GPIOB->CRH&=0X0000FFFF;
GPIOB->CRH|=0X88880000;//PB12 PB13 PB15 PB15 上拉输入
GPIOB->ODR|=0X0000F000; //PB12 PB13 PB15 PB15 上拉
}
void KEY_Scan (void)
{
if(s1==0)
{
delay_ms(10);
if(s1==0)
{
while(!s1);
time1+=1;
}
}
if(s2==0)
{
delay_ms(10);
if(s2==0)
{
while(!s2);
time1-=1;
}
}

{
if(s3==0)
{
delay_ms(10);
if(s3==0)
{
while(!s3);
time2+=1;

}
}
if(s4==0)
{
delay_ms(10);
if(s4==0)
{
while(!s4);
time2-=1;
}
}
}
}
(3)修改无线通讯程序24l01.c中的void NRF24L01()函数

void NRF24L01(void)
{
u8 mode=1,count;
u8 tmp_buf[33];
if(mode==0)//RX模式
{
RX_Mode();
while(1)
{
if(NRF24L01_RxPacket(tmp_buf)==0)//一旦接收到信息,则显示出来.
{
time1=(tmp_buf[1]*256+tmp_buf[2]);
time2=(tmp_buf[3]*256+tmp_buf[4]);

oled_show();
}else break;
};
}else//TX模式
{
TX_Mode();
while(1)
{

if(++count>100)count=0;

tmp_buf[1]=time1/256;
tmp_buf[2]=time1%256;

tmp_buf[3]=time2/256;
tmp_buf[4]=time2%256;

if(NRF24L01_TxPacket(tmp_buf)==TX_OK)
{
}else
break;
}
}
}
(4)修改显示程序show.c中oled_show()程序

void oled_show(void)
{
OLED_Display_On();  //显示屏打开
//=============显示time1====================//
OLED_ShowString(00,0,"time1");
OLED_ShowNumber(54,0, time1,4,12);
//=============显示time2====================//
OLED_ShowString(00,10,"time2");
OLED_ShowNumber(54,10, time2,4,12);
//=============刷新====================//
OLED_Refresh_Gram();
}

5)修改控制程序control.c中函数turn(int encoder_left,int encoder_right,float gyro

    将Turn_Amplitude=1500/Way_Angle+200;

    改为Turn_Amplitude=400/Way_Angle;   //可以降低转弯速度

(6)修改控制程序control.c中函数velocity(int encoder_left,int encoder_right)

    将Movement=-700;

    改为Movement=-600;


观察现象






    按键调整time1为382,time2为764




视频地址:http://player.youku.com/player.php/sid/XMTM2NDY1NDUxMg==/v.swf

    小车可以实现绕八字走,但会有微小的偏移,OLED屏显示正常。


最后祝大家国庆节快乐!!!



共20条 1/2 1 2 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]