乘简 发表于 2022-6-6 21:06

【平头哥RVB2601创意应用开发】+ 平衡小车

<p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Times New Roman&quot;,serif"><b><span style="font-size:16.0pt"><span style="background:white"><span style="font-family:&quot;微软雅黑&quot;,sans-serif"><span style="color:black">一、项目背景</span></span></span></span></b></span></span></p>

<p style="margin-left:28px; text-indent:22.4pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Times New Roman&quot;,serif"><span lang="EN-US" style="background:white"><span style="font-family:宋体"><span style="color:#606975"><span style="letter-spacing:.35pt">RVB2601</span></span></span></span><span style="background:white"><span style="font-family:宋体"><span style="color:#606975"><span style="letter-spacing:.35pt">是基于平头哥生态芯片CH2601设计的一款开发板,板载JTAG调试器、WiFi&amp;BLE芯片W800,音频ADC-ES7210,音频DAC-ES8156,128X64 OLED屏幕,RGB三色指示灯,用户按键,兼容Arduino的扩展接口。非常适合智能家居领域的各种场景应用开发。想来未来是各种机器人的时代,正好可以学一下这款芯片,就拿平衡小车来练练手了。</span></span></span></span></span></span></p>

<p style="text-align:justify"><span style="font-size:20px;"><strong>二、作品简介</strong></span></p>

<p style="margin-left:28px; text-indent:22.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Times New Roman&quot;,serif"><b><span style="background:white"><span style="font-family:宋体"><span style="color:#606975"><span style="letter-spacing:.35pt">本平衡小车采用,两节18650电池串连供电,一个5V的降压模块,给RVB2601供电,开发板上的3.3V用来给MPU6050陀螺仪模块供电,用的IIC与陀螺仪通信,另下还有一DRV8833电机驱动模块,驱动两个带编码器的电机,所以需要4路PWM,每个电机还有2路编码器数据采集,所以一共需要8个IO口,因为开发自带了WIFI,所以遥控部分就用wifi,内嵌一个小的web服务器,这样只要手机打开浏览器即可实现遥控平衡小车的运行。</span></span></span></span></b></span></span></p>

<p style="text-align:justify"><strong><span style="font-size:20px;">三、系统框图</span></strong></p>

<p style="text-align:justify"><strong><span style="font-size:20px;">&nbsp;四、各部分功能说明和解析</span></strong></p>

<p style="text-align:justify"><strong><span style="font-size:20px;">&nbsp; &nbsp; 1、OLED显示部分代码</span></strong></p>

<pre>
<code class="language-cpp">#include "display.h"
#include "ssd1309.h"
#include "wifi_w800.h"

//显示曲线, r取值为-16~+16
void showQx(int r)
{
        static int b=48;
        if(r&lt;0)
        {
                if(r&lt;-16)r=-16;//-16~-1
                r+=16;//0~15
                r+=32;//32~47
        }
        else{
                if(r&gt;15)r=15;//0~15
                r+=48;//48~63
        }
        oled_draw_point(48, 127, 1);//画中线

        oled_draw_point(b, 127, 0);//清除上次的值
        oled_draw_point(r, 127, 1);
       
        b=r;
       
        for(int i=0;i&lt;127;i++){
                g_oled_ram=g_oled_ram;
                g_oled_ram=g_oled_ram;
                g_oled_ram=g_oled_ram;
                g_oled_ram=g_oled_ram;
        }
}

void showIP(int y)
{
        TextOut(0, y,"IP:",12);
        TextOut(24,y,ip,12);
}

//显示3个角度数据,y为y坐标起始地址
void showAngle(int y)
{
        char str;
        sprintf(str,"x%3.2f",angleX);
       
        sprintf(&amp;str,"y%3.2f",angleY);
       
        sprintf(&amp;str,"z%3.2f",angleZ);
        str=0;
        TextOut(0,y,str,12);
}

//显示3项pwm数据
void showPwm(int y)
{
        char str;
        sprintf(str,"p%4.1f",B_Pwm);

        sprintf(&amp;str,"v%4.1f",V_Pwm);
       
        sprintf(&amp;str,"t%4.1f",T_Pwm);
        str=0;
       
        TextOut(0, y,str,12);
}

//显示平衡pid与振动次数
void showPid(int y)
{
        char str;
        sprintf(str,"kp%3.1f",pid.bkp);

        sprintf(&amp;str,"kd%2.2f",pid.bkd);
       
        sprintf(&amp;str,"vp%3.1f",pid.vkp);
        str=0;
       
        TextOut(0, y,str,12);
}

void showSd(int y)
{
        char str;
        sprintf(&amp;str,"sd%-3d",Velocity);
        sprintf(&amp;str,"%3.2f",sd);
        sprintf(&amp;str,"L%4d   ",(int)Pluse_L);
        sprintf(&amp;str,"R%4d   ",(int)Pluse_R);
        str=0;
        TextOut(0, y,str,12);
}

void showOp(int x,int y)
{
        if(gb==0)CharOut(x,y,24,12,CUSTOM12,12);
        if(gb==1)CharOut(x,y,24,12,CUSTOM12,12);
        if(gb==2)CharOut(x,y,24,12,CUSTOM12,12);
        if(gb==3)CharOut(x,y,24,12,CUSTOM12,12);
        if(gb==4)CharOut(x,y,24,12,CUSTOM12,12);
        if(gb==5)CharOut(x,y,24,12,CUSTOM12,12);
}

void showEsd(int y)
{
        char str;
        sprintf(&amp;str,"e%5d    ",(int)sum_e_Velocity);
        sprintf(&amp;str,"z%4d    ",(int)sum_e_AngleY);
        sprintf(&amp;str,"zd%3d   ",(int)zd);
        str=0;
        TextOut(0, y,str,12);
}

void show()
{
        static int p=0;
       
        if(page==0){
                if(p!=page){//切换页,先清屏
                        p=page;
                        oled_clear();
                }
                showIP(0);//显示IP
                showQx(angleY);//+-90度,就是180,要显示在32高的位置上,180/32=5.625);
                showAngle(12);//显示角度
        }
        else if(page==1){
                if(p!=page){//切换页,先清屏
                        p=page;
                        oled_clear();
                }

                showSd(0);
                showEsd(12);
                showQx(sd*2);
        }
        else if(page==2){
                if(p!=page){//切换页,先清屏
                        p=page;
                        oled_clear();
                }

                showAngle(0);//显示角度
                showSd(12);//
                showQx(Pluse_L/18.75);//显示振动曲线
        }
        else{
                if(p!=page){//切换页,先清屏
                        p=page;
                        oled_clear();
                }
               
                showPwm(0);//显示3项pwm数据
                showAngle(12);
                showSd(24);
                showEsd(36);
                showOp(52,48);
        }
       
        oled_reflesh();//刷新屏幕
}
</code></pre>

<p>2、陀螺仪代码</p>

<pre>
<code class="language-cpp">#include "mpu6050.h"
#include &lt;drv/iic.h&gt;
#include &lt;aos/aos.h&gt;
#include &lt;drv/pin.h&gt;
#include &lt;math.h&gt;
#include "pidcar.h"
#include "wifi_w800.h"

#define IIC_IDX0
static csi_iic_t master_iic;
static aos_timer_t mpu_timer;//定时器

int16_t rawAccX=0,rawAccY=0,rawAccZ=0,rawGyroX=0,rawGyroY=0,rawGyroZ=0;//6项原始数据

float angleX=0, angleY=0, angleZ=0;
float angleAccX=0,angleAccY=0; //由mpu6050_tockn加计算的加速度角度
float gyroX, gyroY, gyroZ;

//float gyroXoffset=0,gyroYoffset=0,gyroZoffset=0;

int iic_init(void)
{
csi_error_t ret;

csi_pin_set_mux(PA8, PA8_IIC0_SCL);
csi_pin_set_mux(PA9, PA9_IIC0_SDA);

ret = csi_iic_init(&amp;master_iic, IIC_IDX);
if (ret != CSI_OK) {
    printf("csi_iic_initialize error\n");
    return -1;
}
/* config iic master mode */
ret = csi_iic_mode(&amp;master_iic, IIC_MODE_MASTER);
if (ret != CSI_OK) {
      printf("csi_iic_set_mode error\n");
      return -1;
}
/* config iic 7bit address mode */
ret = csi_iic_addr_mode(&amp;master_iic, IIC_ADDRESS_7BIT);
if (ret != CSI_OK) {
      printf("csi_iic_set_addr_mode error\n");
      return -1;
}
/* config iic standard/fast speed*/
ret = csi_iic_speed(&amp;master_iic, IIC_BUS_SPEED_FAST);//IIC_BUS_SPEED_STANDARD);
if (ret != CSI_OK) {
      printf("csi_iic_set_speed error\n");
      return -1;
}
return 0;
}

//写寄存器
void MPU6050_WriteReg(uint8_t REG_Address, uint8_t REG_data)
{
        uint8_t buf;
    buf=REG_Address;
        buf=REG_data;
        csi_iic_master_send(&amp;master_iic, SLAVE_ADDR, buf, 2, 100);
}

//读寄存器
void MPU6050_ReadReg(uint8_t REG_Address)
{
        uint8_t buf;
    buf=REG_Address;
        csi_iic_master_send(&amp;master_iic, SLAVE_ADDR, &amp;buf, 1, 100);
}


//读数据
int16_t MPU6050_ReadData(uint8_t REG_Address)
{
        uint8_t H,L;
        uint8_t buf={0,0};
       
        MPU6050_ReadReg(REG_Address);
        csi_iic_master_receive(&amp;master_iic, SLAVE_ADDR, buf, 2, 100);
        H=buf;
        L=buf;
       
        return (H&lt;&lt;8)|L;
}

void MPU6050_Get_Data()
{
        float accX, accY, accZ;
//        float homeAccX,homeAccY;

        //6项原始数据
        rawAccX = MPU6050_ReadData(ACCEL_XOUT_H);
        rawAccY = MPU6050_ReadData(ACCEL_YOUT_H);
        rawAccZ = MPU6050_ReadData(ACCEL_ZOUT_H);
        rawGyroX = MPU6050_ReadData(GYRO_XOUT_H);
        rawGyroY = MPU6050_ReadData(GYRO_YOUT_H);
        rawGyroZ = MPU6050_ReadData(GYRO_ZOUT_H);
       
                //计算重力加速度的g数
        accX = ((float)rawAccX) / 16384.0;//+-2g,65536/4=16384
        accY = ((float)rawAccY) / 16384.0;
        accZ = ((float)rawAccZ) / 16384.0;

        //角速度
        gyroX = ((float)rawGyroX) / 65.536;//+-500
        gyroY = ((float)rawGyroY) / 65.536;
        gyroZ = ((float)rawGyroZ) / 65.536;

        //由重力加速度计算X、Y轴的角度,tockn库
        angleAccX = atan2(accY, accZ + abs(accX)) * RAD_TO_DEG;//360 / 2.0 / PI;
        angleAccY = -atan2(accX, accZ + abs(accY)) * RAD_TO_DEG;
        //angleAccY += MPU_OFFSET_Y;
       
        angleAccY += 1.15;//此参数为陀镙仪固定在小车上的角度物理误差,根据经验调整
       
//        gyroX -= gyroXoffset;
//        gyroY -= gyroYoffset;
//        gyroZ -= gyroZoffset;

//        //平衡小车之家的融合算法
//        homeAccX = 0;
//        homeAccY = -atan2((float)rawAccX, (float)rawAccZ) * RAD_TO_DEG;
//        homeAccY+=MPU_OFFSET_Y;
//        homeX = 0.02 * homeAccX + 0.98 * (homeX + gyroX * 0.01);
//        homeY = 0.02 * homeAccY + 0.98 * (homeY + gyroY * 0.01);

        //tockn库角度融合算法,上次的角度+10ms的角速度占98%,本次的角度占2%
        angleX = (0.98f * (angleX + gyroX * 0.01)) + (0.02f * angleAccX);
        angleY = (0.98f * (angleY + gyroY * 0.01)) + (0.02f * angleAccY);
        angleZ = gyroZ*0.01;

}


//定时器回调函数
static void timer_mpu6050(void *arg1, void* arg2)
{
       
    MPU6050_Get_Data();
       
        //pid_car();

        static int t=0;
        t++;
        t%=10;
        if(t==0){
                printf("%12f,%12f,%12f\r\n",angleX,angleY,angleZ);
        }
}

void mpu6050_init()
{
        iic_init();//初始化iic总线
        MPU6050_WriteReg(SMPLRT_DIV, 0x00);//陀螺仪125hz
        MPU6050_WriteReg(CONFIG, 0x00);      //21HZ滤波 延时A8.5ms G8.3ms此处取值应相当注意,延时与系统周期相近为宜
        MPU6050_WriteReg(GYRO_CONFIG, 0x08); //陀螺仪 0x00为+-2500x08为+-5000x10为+-10000x18为+-2000度
        MPU6050_WriteReg(ACCEL_CONFIG, 0x00);//加速度 0x00为+-2g0x08为+-4g0x10为+-8g0x18为+-16g
        MPU6050_WriteReg(PWR_MGMT_1, 0x01);        //解除休眠状态
       
        aos_timer_new(&amp;mpu_timer, timer_mpu6050, NULL, 10, 1);//10ms定时器
}
</code></pre>

<p>3、平衡部分代码</p>

<pre>
<code class="language-cpp">#include "pidcar.h"
#include "pwm_mada.h"
#include "mpu6050.h"
#include "wifi_w800.h"
#include "counter.h"
#include &lt;math.h&gt;

#define constrain(p) {if(p&lt;-pwm_width)p=-pwm_width;else if(p&gt;pwm_width)p=pwm_width;}

int Velocity=0;
float sd=0;
int zd=0;//振断次数
float sum_e_Velocity=0, sum_e_AngleY=0;
float Angle_Turn_Setting=0, V_Setting;
float B_Pwm, V_Pwm, T_Pwm, Pluse_L, Pluse_R;
//bkp为100时,高频振动,bkd为2.8时高频振动,所以*0.6=60和1.68
//再调vkp,调到60的时候,发现基本能定在一个位置了
PID pid={60, 1.6, 60, 25, 1};
float Blance_JD=0;//平衡时的角度

//平衡PD
void Blance_PD()                                        //blance pid
{
        //float Kp = 18;//在关Kd时,调36会前后摇摆往一边驶去,18会越来越快往一边驶去,直到倒掉
        //float Kd = 0.3;//1.4时,高频抖.1.4*0.6=0.8
        float p = pid.bkp * angleY + pid.bkd * gyroY;
       
        B_Pwm = B_Pwm * 0.5 + p * 0.5;
               
        constrain(B_Pwm);//限制B_Pwm值超过pwm_width
}


//检测每秒振动次数
void CheckZd()
{
        static float angle_Last=0;
        static int i=0;
        if(angle_Last&gt;0 &amp;&amp; angleY&lt;0 || angle_Last&lt;0 &amp;&amp; angleY&gt;0){//与上次
                zd++;
        }
        if(++i&gt;=100){//每秒钟清零
                i=zd=0;
        }
        angle_Last=angleY;
}

//速度
void Velocity_PI()//velocity pid
{
        float e_Velocity = Velocity - V_Setting;
        float p;
       
        sd=0.7 * sd + 0.3 * e_Velocity;
       
        sum_e_Velocity += sd;
        constrain(sum_e_Velocity);

        p = pid.vkp * sd + pid.vkp * sum_e_Velocity / 200;
        V_Pwm = V_Pwm*0.5 + p*0.5;
       
        constrain(V_Pwm);
        //V_Pwm=0;
}

//旋转
void Turn_PID_Angle()//angle of Z axis pid
{       
        float e_AngleZ = (Count_Left - Count_Right) - Angle_Turn_Setting;
       
        sum_e_AngleY += e_AngleZ;
       
        T_Pwm = pid.tkp * e_AngleZ + pid.tki * sum_e_AngleY;

        //T_Pwm=0;
}

void reset()
{
        gb=0;//停止运行,要再次按键
        sum_e_Velocity = 0;
        sum_e_AngleY = 0;
        B_Pwm = 0;
        V_Pwm = 0;
        T_Pwm = 0;
        sd=0;
        sd_zq=sd_zh=sd_yq=sd_yh=0;
}

void pid_car()
{       
        float l,r;
       
        Velocity = (Count_Left + Count_Right);//记录10ms的电机转动速度
       
        if (gb==2){//前进
                V_Setting = 2;
        }
        else if(gb==4){//后退
                V_Setting = -2;
        }
        else{//停住
                V_Setting = 0;
        }
       
        if(gb==1){//左转
                Angle_Turn_Setting = -2;
        }
        else if(gb==3){
                Angle_Turn_Setting = 2;
        }
        else Angle_Turn_Setting = 0;
       
        Blance_PD();
        CheckZd();
        Velocity_PI();
        Turn_PID_Angle();

       
        l = B_Pwm + V_Pwm - T_Pwm;
        Pluse_L = Pluse_L*0.5 + l*0.5;
       
        r = B_Pwm + V_Pwm + T_Pwm;
        Pluse_R = Pluse_R*0.5 + r*0.5;
       
        constrain(Pluse_L);
        constrain(Pluse_R);


        if (gb &amp;&amp; angleY&gt;-30 &amp;&amp; angleY &lt; 30)                //if angleY is between -65 and 65 ,run it
        {

                pwm_start();
                if (Pluse_L &gt; 0)
                {
                        sd_zq=abs((int)Pluse_L);
                        pwm_zq();
                }
                else
                {
                        sd_zh=abs((int)Pluse_L);
                        pwm_zh();
                }

                if (Pluse_R &gt; 0)
                {
                        sd_yq=abs((int)Pluse_R);
                        pwm_yq();
                }
                else
                {
                        sd_yh=abs((int)Pluse_R);
                        pwm_yh();
                }
        }
        else
        {
                reset();
                pwm_stop();
        }
       
        Count_Left = Count_Right = 0;//统计完后清零
}
</code></pre>

<p><span style="font-size:20px;"><strong>五、作品源码</strong></span></p>

<p></p>

<p>&nbsp;</p>

<p><b><span style="font-size:16.0pt"><span style="font-family:&quot;微软雅黑&quot;,sans-serif">六、视频</span></span><span lang="X-NONE" style="font-size:16.0pt"><span style="font-family:&quot;微软雅黑&quot;,sans-serif">演示</span></span></b></p>

<p><iframe allowfullscreen="true" frameborder="0" height="450" src="//player.bilibili.com/player.html?bvid=1pY411g7RM&amp;page=1" style="background:#eee;margin-bottom:10px;" width="700"></iframe><br />
<strong><span style="font-size:20px;">七、</span></strong><b><span style="font-size:16.0pt"><span style="font-family:&quot;微软雅黑&quot;,sans-serif">项目总结</span></span></b></p>

<p>&nbsp;&nbsp;<a href="https://bbs.eeworld.com.cn/thread-1205777-1-1.html" target="_blank">(一)陀螺仪相关</a></p>

<p><a href="https://bbs.eeworld.com.cn/thread-1205776-1-1.html" target="_blank">(二)wifi遥控器相关</a></p>

<p>&nbsp;</p>

<p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Times New Roman&quot;,serif"><b><span style="font-size:16.0pt"><span style="font-family:&quot;微软雅黑&quot;,sans-serif">八、其他</span></span></b></span></span></p>

<p style="text-indent:21.0pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Times New Roman&quot;,serif"><span lang="X-NONE" style="font-family:宋体"><span style="font-weight:normal">第一次用这个开发板,各种不顺手,主要原因是,平头哥的开发环境,必须要内嵌一个OS在里面的,这和我之前学的51大有不同,且IDE也不太友好,各种BUG多,还有驱动中也存在一些BUG,好在有平头哥小助手及工单系统的帮助,总算完成了,期间多次想放弃,退回开发板不想弄了,但最终还是坚持下来了,希望平头哥解决掉所有BUG,使开发者用着舒心,我会持续关注,并学习我还未使用到的功能。。。</span></span></span></span></p>

<p style="text-indent:21.0pt; text-align:justify">&nbsp;</p>

nmg 发表于 2022-6-7 08:42

<p><img height="50" src="https://bbs.eeworld.com.cn/static/editor/plugins/hkemoji/sticker/facebook/wanwan21.gif" width="63" />这个作品竟然做出来了,出乎我意料</p>

乘简 发表于 2022-6-7 09:23

nmg 发表于 2022-6-7 08:42
这个作品竟然做出来了,出乎我意料

<p>其实平衡小车,大部分时间都用在调参数上了,不过我调的参数,还不是很理想,小车运行起来不是很平稳,其实真正的代码是很少。。。</p>

<p>&nbsp;</p>

<p>不过交完作业才发现display.c中少#define了几个.h文件,因为交作业之前那个里面的函数是在main.c中的,后来才移到display.c中,所以就少包函了几个.h文件,但问题不大。。。</p>

<p>&nbsp;</p>

乘简 发表于 2022-6-7 09:31

<p>还有,交完作业后,我把小车拆了,可是发现开发板跑不起来了,后来发现,我用的IIC函数不对,我用的是同步函数,里面有100的超时处理,当没有接上陀螺仪的时候,是会卡住的,应该改用异步的IIC库函数,应该就没有问题了。。。</p>

<p>&nbsp;</p>

nmg 发表于 2022-6-7 09:34

乘简 发表于 2022-6-7 09:31
还有,交完作业后,我把小车拆了,可是发现开发板跑不起来了,后来发现,我用的IIC函数不对,我用的是同步 ...

<p>先别着急拆作品啊,如果平头哥那边想二次包装,估计需要你邮寄作品</p>

lugl4313820 发表于 2022-6-7 15:31

楼主,是最优秀的哥,声音也好有磁性哦,加油,期待更精彩的作品。

吾妻思萌 发表于 2022-6-8 08:34

动手能力拉满,点赞

乘简 发表于 2022-6-8 08:44

nmg 发表于 2022-6-7 09:34
先别着急拆作品啊,如果平头哥那边想二次包装,估计需要你邮寄作品

<p>我好像又发现代码中的一个小BUG,就是</p>

<p>Count_Left = Count_Right = 0;//统计完后清零</p>

<p>这条语句写得太远了,,应该获取到值后立即清零,或者干脆不清零,用别的方法统计,,要不可能会丢失一些编码器数据</p>

<p>&nbsp;</p>

<p>改天再装起来试试</p>
页: [1]
查看完整版本: 【平头哥RVB2601创意应用开发】+ 平衡小车