前言:本文为手把手教学基于STM32的四足机器人项目——JDY-31蓝牙控制,特别地,本次项目采用的是STM32作为MCU。四足机器人的支架为3D打印件,SG90舵机驱动机器人实现姿态运动。借助PCA9685舵机驱动板实现12路PWM波控制,更多的舵机可以实现机器人更多运动姿态。本文将以STM32作为核心控制板搭配JDY-31蓝牙模块制作一个远程可控的蓝牙四足机器人,采用三角法去解析机器人的步态运动,实现较高自由度的平稳运动。(文末代码与资料开源!)
实验硬件:STM32F103C8T6;PCA9685舵机驱动板;SG90舵机;JDY-31蓝牙模块;0.96寸OLED;四足机器人3D打印件
硬件实物图:
效果图:
引脚连接:
PCA9685驱动板引脚:
VCC --> 5V
GND --> GND
SCL --> PB6
SDA --> PB7
JDY-31蓝牙模块引脚:
VCC --> 3.3V
GND --> GND
RX--> PA9
TX --> PA10
0.96寸OLED引脚:
VCC --> 3.3V
GND --> GND
SCL --> PB10
SDA --> PB11
一、机器人介绍
1.1 机器人简介
机器人(Robot)是一种能够半自主或全自主工作的智能机器。机器人能够通过编程和自动控制来执行诸如作业或移动等任务。机器人具有感知、决策、执行等基本特征,可以辅助甚至替代人类完成危险、繁重、复杂的工作,提高工作效率与质量,服务人类生活,扩大或延伸人的活动及能力范围。
近些年来,随着深度学习以及芯片计算能力的快速发展,全球出现了各式各样的优秀机器人。例如,波士顿动力(Boston Dynamics)的Atlas与Spot,国内小米公司的铁蛋1号等。这些机器人都或多或少代表了人类目前顶尖的机器人制造技术,当然随着科学技术的进一步发展,相信不久之后科幻电影中的高性能、高智商和高自由度的机器人将出现在世人眼中。
1.2 机器人项目概述
机器人的制作无论在工程应用还是学术研究方面都存在着许多高深知识点,其涉及到运动控制算法,机械结构设计与数据通讯等诸多方面。每一个环节的设计与研发都对最终的机器人成品有着决定性作用,一个优秀的产品肯定是需要精雕细琢的!
当然,本项目的四足机器人借鉴了国外众多优秀开源的四足机器人机械结构,项目主体框架采用PLA材质的3D打印件,具有很高的性价比与机械硬度。12路SG90舵机的设计带给机器人更多的运动姿态,更稳定的步态。项目中四足机器人的控制则采用JDY-31蓝牙模块与手机上位机蓝牙助手APP进行通讯,方便开发简单易上手。总之,本项目设计的四足机器人基础框架具有很高的上限值,感兴趣的读者朋友可以尝试复现后再优化。
二、PCA9685舵机驱动板
PCA9685驱动板是一款基于IIC总线通信的12位精度16通道PWM波输出的芯片,该芯片最初由NXP(恩智浦)推出时主要面向LED开关调光,16路12位PWM信号发生器,可用于控制舵机、LED、电机等设备。利用IIC通信读写关键寄存器内的数据来控制多路PWM信号发送,节省主机引脚资源。灵活使用PCA9685驱动板,就可以真正实现舵机自由(理论上最高可以控制64路舵机)。
PCA9685驱动板厉害之处在于成功通过IIC通讯去解决了MCU的PWM波引脚较少的窘境,通过对PCA9685对应的寄存器写入相关的数据进行配置PCA9685的时钟初始化后,再对现有的16路引脚对应的寄存器写入需要的寄存器数值实现16路PWM的控制输出。详情的资料可以参考恩智浦公司提供的技术文档,如下:
技术文档网址:PCA9685 | NXP Semiconductors
特别注意:PCA9685模块有2个电源引脚,其中绿色接线柱处为16路舵机的电源供应引脚,切忌此处接入的移动电源电压不得超过舵机额定电压。(考虑到多路舵机同时工作,建议使用大电流电源)
三、JDY-31蓝牙模块
JDY-31蓝牙模块基于蓝牙3.0 SPP设计,这样可以支持 Windows、Linux、android数据透传,工作频段 2.4GHZ,调制方式GFSK,最大发射功率8db,最大发射距离30米,支持用户通过AT命令修改设备名、波特率等指令,方便快捷使用灵活。
其通常使用时接入串口引脚(UART)即可,成功与蓝牙模块建立通信后会发送CONNECTED。简单的说,JDY-31蓝牙模块本质上就是一个蓝牙转串口的设备,使用的时候直接当串口通讯进行使用即可。
而上位机作者则选用现成开源的蓝牙调试器APP,直接创建一个针对本项目机器人的控制上位机工程。利用该APP极大的缩短了上位机开发的时间与工作量,也可以快速检验出机器人的运动控制是否满足要求(需要蓝牙调试APP的可以私信作者提供)。
JDY-31蓝牙模块的AT指令集:
序号 | 目的 | 指令 | 参数 | 响应 | 备注 |
1 | 查询版本号 | AT+VERSION | NONE | +VERSION=… | |
2 | 复位 | AT+RESET | NONE | +OK | |
3 | 断开连接 | AT+DISC | NONE | +OK | 蓝牙连接后有效 |
4 | BLE蓝牙MAC地址 | AT+LADDR | NONE | +LADDR=… | |
5 | 波特率设置/查询 | AT+BAUD[参数] | 4-9600;5-19200;6-38400;7-57600;8-115200;9-128000 | +OK | 128000也不丢包 |
6 | SPP蓝牙密码配对/查询 | AT+PIN[参数] | 4位密码 | +OK | 默认1234 |
7 | 广播名设置/查询 | AT+NAME[参数] | 18字节以下 | OK | |
8 | 恢复出厂设置 | AT+DEFAULT | NONE | OK | 默认JDY-31-SPP |
9 | 串口输出状态使能查询 | AT+ENLOG | 1:打开 0:关闭 | OK | 默认为1 |
四、SG90舵机与OLED
4.1 SG90舵机模块
SG90舵机作为机器人的力学驱动装置,其工作原理为时基脉冲,该脉冲的高电平部分一般为0.5ms-2.5ms范围内的角度控制脉冲部分,总间隔为2ms。
角度与脉冲时间关系
##############################################################
0.5MS 1.0MS 1.5MS 2.0MS 2.5MS
0° 45° 90° 135° 180°
##############################################################
编程思路:读者朋友控制舵机的时候,只需要使用定时器去产生PWM调节。用PWM调节出对应ms数的脉冲即可实现对舵机的固定角度控制。 SG90舵机参考博客:基于STM32的SG90舵机实验含代码(HAL库)_混分巨兽龙某某的博客-CSDN博客_stm32舵机代码
4.2 0.96寸OLED模块
关于OLED屏幕显示机器人表情仅为锦上添花行为,并不对机器人制作产生影响,所选的0.96寸OLED屏幕为4引脚的IIC通讯方式。,关于OLED的使用与原理不熟悉的笔者欢迎去笔者另一篇文章学习。【强烈推荐】基于stm32的OLED各种显示实现(含动态图)_混分巨兽龙某某的博客-CSDN博客_stm32 oled
五、CubeMX配置
1、RCC配置外部高速晶振(精度更高)——HSE;
2、SYS配置:Debug设置成Serial Wire(否则可能导致芯片自锁);
3、UART1配置:JDY-31模块与STM32的蓝牙串口通讯(开启串口中断);
4、 I2C1和I2C2配置:I2C1为MCU与PCA9685驱动板进行通讯,I2C2为MCU与OLED的通讯方式;
5、时钟树配置:
6、工程配置
六、代码与解析
6.1 PCA9685舵机驱动模块
项目的核心控制板为STM32F103C8T6受限于可输出PWM波引脚太少,故采用IIC通讯协议与PCA9685舵机驱动板进行通讯交互,通过IIC通讯对固定好的寄存器地址写入相应的数据来初始化PCA9685驱动板,以及输出板载可控的16路PWM波。
考虑到PCA9685舵机驱动板作为控制四足机器人的核心驱动装置,其需要接入的引脚资源过多。初次接触可能有点茫然,这里给出硬件上接线的具体情况如图所示。特别需要注意的是接入的锂电池一定不能超过舵机电压承受的上限,否者会烧坏舵机。其次,由于机器人运动时可能一次接入的负载较大,建议选择5V3A的大电流锂电池。
关于代码编程的逻辑,首先需要使用IIC通讯去给PCA9685芯片设置寄存器模式,之后在设定芯片频率(这一点与后续的PWM波输出相关)。初始化完成后就可以对控制16路PWM波产生的寄存器地址写入需要的数值,实现利用IIC通讯去控制16路舵机。这部分后续关键使用到的API函数为PCA_MG90(),后续使用其实时控制我们的舵机运动角度。
pca9685.c代码:
#include "pca9685.h"
#include "i2c.h"
#include "math.h"
uint8_t PCA_Read(uint8_t startAddress)
{
//设置开始读取数据的寄存器地址
uint8_t tx[1];
uint8_t buffer[1];
tx[0]=startAddress;
HAL_I2C_Master_Transmit(&hi2c1,PCA9685_adrr,tx,1,10000);
HAL_I2C_Master_Receive(&hi2c1,PCA9685_adrr,buffer,1,10000);
return buffer[0];
}
void PCA_Write(uint8_t startAddress, uint8_t buffer)
{
//设置开始读取数据的寄存器地址
uint8_t tx[2];
tx[0]=startAddress;
tx[1]=buffer;
HAL_I2C_Master_Transmit(&hi2c1,PCA9685_adrr, tx,2,10000); //利用HAL库的I2C通讯函数对寄存器地址写值
}
void PCA_Setfreq(float freq)//设置PWM频率
{
uint8_t prescale,oldmode,newmode;
double prescaleval;
freq *= 0.83; //实际使用过程中存在偏差需要×矫正系数=0.83
prescaleval = 25000000;
prescaleval /= 4096;
prescaleval /= freq;
prescaleval -= 1;
prescale =floor(prescaleval + 0.5f); //floor向下取整函数
oldmode = PCA_Read(PCA9685_MODE1);
newmode = (oldmode&0x7F) | 0x10; // sleep睡眠
PCA_Write(PCA9685_MODE1, newmode); // go to sleep(需要进入随眠状态才能设置频率)
PCA_Write(PCA9685_PRESCALE, prescale); // 设置预分频系数
PCA_Write(PCA9685_MODE1, oldmode);
HAL_Delay(2);
PCA_Write(PCA9685_MODE1, oldmode | 0xA1);
}
/*num:舵机PWM输出引脚0~15,on:PWM上升计数值0~4096,off:PWM下降计数值0~4096*/
void PCA_Setpwm(uint8_t num, uint32_t on, uint32_t off)
{
PCA_Write(LED0_ON_L+4*num,on);
PCA_Write(LED0_ON_H+4*num,on>>8);
PCA_Write(LED0_OFF_L+4*num,off);
PCA_Write(LED0_OFF_H+4*num,off>>8);
}
/*函数作用:初始化舵机驱动板参数:1.PWM频率2.初始化舵机角度*/
void PCA_MG90_Init(float hz,uint8_t angle)
{
uint32_t off=0;
PCA_Write(PCA9685_MODE1,0x0);
PCA_Setfreq(hz);//设置PWM频率
off=(uint32_t)(145+angle*2.4);
PCA_Setpwm(0,0,off);PCA_Setpwm(1,0,off);PCA_Setpwm(2,0,off);PCA_Setpwm(3,0,off);
PCA_Setpwm(4,0,off);PCA_Setpwm(5,0,off);PCA_Setpwm(6,0,off);PCA_Setpwm(7,0,off);
PCA_Setpwm(8,0,off);PCA_Setpwm(9,0,off);PCA_Setpwm(10,0,off);PCA_Setpwm(11,0,off);
PCA_Setpwm(12,0,off);PCA_Setpwm(13,0,off);PCA_Setpwm(14,0,off);PCA_Setpwm(15,0,off);
HAL_Delay(500);
}
/*函数作用:控制舵机转动;参数:1.输出端口,可选0~15; 2.结束角度,可选0~180;*/
void PCA_MG90(uint8_t num,uint8_t end_angle)
{
uint32_t off=0;
off=(uint32_t)(158+end_angle*2.2);
PCA_Setpwm(num,0,off);
}
6.2 JDY-31蓝牙模块控制机器人代码
本项目的四足机器人采用手机蓝牙APP进行控制,而且控制所用的蓝牙APP为开源的蓝牙调试助手,具体界面如图所示。设置6个控制组件为byte类型,按下时分别对应数字1-6,松开时为数字0。
bluetooth.c代码:
#include "bluetooth.h"
uint8_t USART1_RX_BUF[USART1_REC_LEN]; //数据接受数组
uint16_t USART1_RX_STA=0;
uint8_t USART1_NewData;
extern int flag; //全局变量
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart ==&huart1)
{
USART1_RX_BUF[USART1_RX_STA&0X7FFF]=USART1_NewData;
USART1_RX_STA++;
if(USART1_RX_STA>(USART1_REC_LEN-1))USART1_RX_STA=0;
if(USART1_RX_BUF[USART1_RX_STA-7] == 0x01)
{
flag = 1;
}
if(USART1_RX_BUF[USART1_RX_STA-7] == 0x02)
{
flag = 2;
}
if(USART1_RX_BUF[USART1_RX_STA-7] == 0x03)
{
flag = 3;
}
if(USART1_RX_BUF[USART1_RX_STA-7] == 0x04)
{
flag = 4;
}
if(USART1_RX_BUF[USART1_RX_STA-7] == 0x05)
{
flag = 5;
}
if(USART1_RX_BUF[USART1_RX_STA-7] == 0x06)
{
flag = 6;
}
HAL_UART_Receive_IT(&huart1,(uint8_t *)&USART1_NewData,1);//HAL库串口接受中断打开
}
}
6.3 四足机器人运动学代码
严格的机器人运动学控制需要根据设计的机器人机械结构去建立数学模型,然后根据数学模型与运动学方程去解析得到控制方程。这一套逻辑与算法是非常复杂的,适合批量化或者要求较高的机器人设计。
本项目则采用运动学中的三角形重心法,这里从积极稳定与消极稳定入手。四足机器人在四脚着地时是消极稳定的状态,走路时则不一定。如果每次只动一只脚,让其他三只脚仍稳稳地踩在地上,即是消极稳定状态,然而四足机器人也可以放弃此状态,进入积极稳定状态,这样可以动得比较快。这两种步态分别被称作爬行(Creep)与快步(Trot)。
这里我们使用爬行作为四足机器人运动的步态,运动过程中必须保证每次移动的时候都有三只脚踩在地面上,且重心放在这三只脚形成的三角形内,如果重心离开这个三角形太久,机器人就会跌倒。如下图所示:
在不借助机器人运动控制学算法实现四足机器人运动的前提下,重复利用PCA_MG90()函数模仿生物的运动步骤,同时需要保证机器人处于积极稳定状态。读者朋友在调试的时候可以提取去记录下各个SG90舵机的活区角度和位置信息,方便后续快速实现爬行步态向前等动作。这里提供自己设计出的步态稳定运动解析,具体如图所示。
它的分解动作:1、这是起始位置,其中一侧两只脚向外,另一侧两只脚向内。2、右上方的脚离地,向机器人前方踏出。3、所有脚都往回收,让身体向前移动。4、左后方的脚沿着身体侧边往前踏步,现在这个状态与起始位置互为镜像。5、左上方的脚离地,向机器人前方踏出。6、重复动作,将所有脚都往回收,身体向前移动。7、右后方的脚离地,沿着身体侧边往前踏部,这样就回到起始位置了。
control.c代码:
#include "control.h"
#include "pca9685.h"
#include "oled.h"
#include "bmp.h"
//机器人站立
void stand()
{
OLED_DrawBMP(0,0,128,8,gImage_1);
PCA_MG90(0,113); //1-1角度变大,腿部向前
PCA_MG90(1,80); //1-2角度变小,腿部向上
PCA_MG90(2,50); //1-3角度变小,腿部收起
PCA_MG90(4,55); //2-1角度变小,腿部向后
PCA_MG90(5,140); //2-2角度变大,腿部向上
PCA_MG90(6,130); //2-3角度变大,腿部收起
PCA_MG90(8,100); //3-1角度变小,腿部向前
PCA_MG90(9,130); //3-2角度变大,腿部向上
PCA_MG90(10,160); //3-3角度变大,腿部收起
PCA_MG90(12,124); //4-1角度变大,腿部向后
PCA_MG90(13,125); //4-2角度变小,腿部向上
PCA_MG90(14,100); //4-3角度变小,腿部收起
}
void forward()
{
uint8_t a=30; //a,b,c变量确定运动幅度大小
uint8_t b=10;
uint8_t c=20;
OLED_DrawBMP(0,0,128,8,gImage_1);//运动表情
PCA_MG90(4,55+a); //向前
PCA_MG90(5,140+a); //向上
PCA_MG90(6,130);
HAL_Delay(200);
PCA_MG90(4,55+a); //向前
PCA_MG90(5,140);
PCA_MG90(6,130);
HAL_Delay(200);
PCA_MG90(0,113+a); //向前
PCA_MG90(1,80-a); //向上
HAL_Delay(200);
PCA_MG90(0,113+a); //向前
PCA_MG90(1,80-b);
PCA_MG90(2,50+c);
HAL_Delay(200);
PCA_MG90(0,113);
PCA_MG90(4,55);
PCA_MG90(8,100+a);
PCA_MG90(12,134+a-10);
PCA_MG90(1,80);
PCA_MG90(2,50);
PCA_MG90(13,125+b);
PCA_MG90(14,100+c);
HAL_Delay(200);
PCA_MG90(12,134-a-10); //向前
PCA_MG90(13,125-a); //向上
PCA_MG90(14,100);
HAL_Delay(200);
PCA_MG90(12,134-a-10); //向前
PCA_MG90(13,125);
HAL_Delay(200);
PCA_MG90(8,100-a); //向前
PCA_MG90(9,130+a); //向上
HAL_Delay(200);
PCA_MG90(8,100-a); //向前
PCA_MG90(9,130-b);
PCA_MG90(10,160-c);
HAL_Delay(200);
PCA_MG90(0,113-a);
PCA_MG90(4,55-a);
PCA_MG90(8,100);
PCA_MG90(12,134-10);
PCA_MG90(9,130);
PCA_MG90(10,160);
PCA_MG90(5,140-b);
PCA_MG90(6,130-c);
HAL_Delay(200);
PCA_MG90(5,140); //大数向上
PCA_MG90(6,130);
}
void left_moving()
{
uint8_t a=30;
uint8_t b=10;
uint8_t c=20;
uint8_t d=10;//运动幅度控制变量
OLED_DrawBMP(0,0,128,8,gImage_1);
PCA_MG90(4,55+a); //向前
PCA_MG90(5,140+a); //向上
PCA_MG90(6,130);
PCA_MG90(0,113-d);//整体左转
PCA_MG90(8,100-d);
PCA_MG90(12,134-d);
HAL_Delay(200);
PCA_MG90(4,55+a); //向前
PCA_MG90(5,140);
PCA_MG90(6,130);
HAL_Delay(200);
PCA_MG90(0,113+a); //向前
PCA_MG90(1,80-a); //向上
PCA_MG90(4,55+a-d);//整体左转
PCA_MG90(8,100-2*d);
PCA_MG90(12,134-2*d);
HAL_Delay(200);
PCA_MG90(0,113+a); //向前
PCA_MG90(1,80);
PCA_MG90(2,50);
HAL_Delay(200);
PCA_MG90(8,100+a); //向后
PCA_MG90(9,130+a); //向上
PCA_MG90(0,113+a-d);//整体左转
PCA_MG90(4,55+a-2*d);
PCA_MG90(8,100+a);
PCA_MG90(12,134-3*d);
HAL_Delay(200);
PCA_MG90(8,100+a); //向后
PCA_MG90(9,130); //向上
PCA_MG90(10,160);
HAL_Delay(200);
PCA_MG90(12,134+a); //向后
PCA_MG90(13,125-a);
PCA_MG90(14,100);
PCA_MG90(0,113+a-2*d);//整体左转
PCA_MG90(4,55+a-3*d);
PCA_MG90(8,100+a-d);
PCA_MG90(12,134+a);
HAL_Delay(200);
PCA_MG90(12,134+a); //向后
PCA_MG90(13,125);
HAL_Delay(200);
}
void beckon()
{
uint8_t i=113;
OLED_DrawBMP(0,0,128,8,gImage_3);
PCA_MG90(4,55+30);
PCA_MG90(8,100-30);
HAL_Delay(300);
PCA_MG90(1,30);
PCA_MG90(2,30);
for(i;i<163;i++) //利用延迟循环控制舵机缓慢移动
{
PCA_MG90(0,i);
HAL_Delay(30);
}
PCA_MG90(2,60);
HAL_Delay(300);
PCA_MG90(2,30);
HAL_Delay(300);
PCA_MG90(2,60);
for(i;i>113;i--)
{
PCA_MG90(0,i);
HAL_Delay(30);
}
PCA_MG90(0,113);
PCA_MG90(1,80);
PCA_MG90(2,50);
}
6.4 OLED机器人表情代码
关于OLED屏幕显示机器人表情仅为锦上添花行为,并不对机器人制作产生影响,所选的0.96寸OLED屏幕为4引脚的IIC通讯方式。OLED屏幕显示的表情由作者自行设计,大家也可以自行DIY一下可爱的表情为机器人添色。
作者使用PCtoLCD2002软件对设计自己的机器人表情进行取模,具体如图所示:
表情显示API函数代码:
void OLED_DrawBMP(unsigned char x0,unsigned char y0,unsigned char x1,unsigned char y1,unsigned char BMP[])
{
unsigned int j=0;
unsigned char x,y;
if(y1%8==0)
y = y1/8;
else
y = y1/8 + 1;
for(y=y0;y<y1;y++)
{
OLED_SetPos(x0,y); /设点函数
for(x=x0;x<x1;x++)
{
WriteDat(BMP[j++]);
}
}
}
6.5 main函数代码
主函数中我们仅需要初始化OLED屏幕显示以及使四足机器人位姿初始化即可,之后便是利用switch()函数与蓝牙控制代码中定义的全局变量flag进行组合使用,在while()循环函数中实现串口1中断后全局变量flag控制机器人运动状态切换。
main.c:
#include "pca9685.h"
#include "oled.h"
#include "control.h"
#include "bluetooth.h"
int flag;
PCA_MG90_Init(60,90); //初始化PCA9685
OLED_Init(); //初始化OLED
OLED_CLS(); //OLED屏幕清空
HAL_UART_Receive_IT(&huart1,(uint8_t *)&USART1_RX_BUF,1);
stand();
int main()
{
//HAL库初始化配置的代码
While(1)
{
switch(flag) //全局变量flag控制运动状态
{
case(1):forward();break;
case(2):backward();break;
case(3):left_moving();break;
case(4):right_moving();break;
case(5):beckon();break;
case(6):stand();break;
default:break;
}
}
}
七、项目效果
四足机器人演示视频
八、项目代码
3D打印文件与代码地址: 基于STM32与与PCA9685的四足机器人项目资料.rar-嵌入式文档类资源-CSDN文库
如果积分不够的朋友,点波关注,评论区留下邮箱,作者无偿提供源码和后续问题解答。求求啦关注一波吧 !!!