之所以有这篇更新,只因时运不济,命途多舛。第一件事是想把之前10DOF模块(MPU6050+HMC5883+MS5611)的弯排针取下来换个直的,废了九牛二虎之力取下来换上,模块却挂了,只能用旁边的MPU9250(MPU6550+AK8963)代替。注意,磁力计是AK,不是M416(^-^),扯远了。所以,写了个mpu9250的驱动。
第二件是UAV021(六)里面提到的,解析出的遥控器数据有问题。其他的不是很确定,但是油门通道的数据会跳变,油门逐渐增大,数据会突然减小。这是令人崩溃的现象,但是其他数据貌似正常(其实也有错误,不明显而已矣)。后面发现是解析SBUS协议时,要把数据位配置位 9 位,STM32把8位数据位和那位校验位都算进去了。然而,这么一配,问题更大了,大于90%的概率不出数据。看其他地方有用DMA,“冥冥中,这是我唯一要走的路啊!”只能转战DMA。然而问题更大了,但是一整天DMA也不出数据。还好天无绝人之路,终于,一个简单的DMA可以了,然后修改以解析Sbus协议可以了,再检查数据,看起来就是很好看的那种,说有问题我都不相信。
唠叨结束,说正事。如果您也使用 mpu9250模块,那么把之前的 mpu6050.c, hmc5883.c 和 ms5611.c 及其对应的头文件移除工程,然后添加 mpu9250.c及其头文件。当然,别忘了在主函数里修改include的头文件及初始化函数。任务函数的名称是一样的,可以不用管。
对于使用 DMA,直接把之前的 subs.c 替换成这次的 sbus.c 即可,所有的配置修改都只涉及此文件,其他地方均可不变。
整个工程的Gitee链接:https://gitee.com/gengstrong/UAV021/raw/master/UAV021_6-Sbus_ControlMotor-Update.zip
IIC协议在UAV021(二)里说明过,不再赘述。
MPU6550和MPU6050一样,7位地址都是 0x68;AK8963的7位地址是 0x0C。程序中还有器件 ID,如下,可能会有稍有不同,可在初始化函数MPU9250_Init() 中查看,适当地更改。
// 器件IIC地址与器件ID定义
#define MPU6500_ADDR 0X68 << 1 //MPU6500的器件IIC地址
#define MPU6500_ID 0X73 //MPU6500的器件ID
//MPU92509250内部封装了一个AK8963磁力计,地址和ID如下:
#define AK8963_ADDR 0X0C << 1 //AK8963的I2C地址
#define AK8963_ID 0X48 //AK8963的器件ID
整个头文件如下,首先定义了MPU9250寄存器地址,其次做用户定义及函数声明。函数包括MPU9250设置及初始化、数据校准、获取MPU9250数据及任务、外部接口四个部分。
// mpu9250.h
#ifndef MPU92509250_H
#define MPU92509250_H
// ==================================================================
// MPU9250 寄存器地址
#define MPU6500_ADDR 0X68 << 1 //MPU6500的器件IIC地址
#define MPU6500_ID 0X73 //MPU6500的器件ID
//MPU92509250内部封装了一个AK8963磁力计,地址和ID如下:
#define AK8963_ADDR 0X0C << 1 //AK8963的I2C地址
#define AK8963_ID 0X48 //AK8963的器件ID
//AK8963的内部寄存器
#define MPU9250_MAG_WIA 0x00 //AK8963的器件ID寄存器地址
#define MPU9250_MAG_CNTL1 0X0A
#define MPU9250_MAG_CNTL2 0X0B
#define MPU9250_MAG_XOUT_L 0X03
#define MPU9250_MAG_XOUT_H 0X04
#define MPU9250_MAG_YOUT_L 0X05
#define MPU9250_MAG_YOUT_H 0X06
#define MPU9250_MAG_ZOUT_L 0X07
#define MPU9250_MAG_ZOUT_H 0X08
//MPU6500的内部寄存器
#define MPU9250_SELF_TESTX_REG 0X0D //自检寄存器X
#define MPU9250_SELF_TESTY_REG 0X0E //自检寄存器Y
#define MPU9250_SELF_TESTZ_REG 0X0F //自检寄存器Z
#define MPU9250_SELF_TESTA_REG 0X10 //自检寄存器A
#define MPU9250_SAMPLE_RATE_REG 0X19 //采样频率分频器
#define MPU9250_CFG_REG 0X1A //配置寄存器
#define MPU9250_GYRO_CFG_REG 0X1B //陀螺仪配置寄存器
#define MPU9250_ACCEL_CFG_REG 0X1C //加速度计配置寄存器
#define MPU9250_MOTION_DET_REG 0X1F //运动检测阀值设置寄存器
#define MPU9250_FIFO_EN_REG 0X23 //FIFO使能寄存器
#define MPU9250_I2CMST_CTRL_REG 0X24 //IIC主机控制寄存器
#define MPU9250_I2CSLV0_ADDR_REG 0X25 //IIC从机0器件地址寄存器
#define MPU9250_I2CSLV0_REG 0X26 //IIC从机0数据地址寄存器
#define MPU9250_I2CSLV0_CTRL_REG 0X27 //IIC从机0控制寄存器
#define MPU9250_I2CSLV1_ADDR_REG 0X28 //IIC从机1器件地址寄存器
#define MPU9250_I2CSLV1_REG 0X29 //IIC从机1数据地址寄存器
#define MPU9250_I2CSLV1_CTRL_REG 0X2A //IIC从机1控制寄存器
#define MPU9250_I2CSLV2_ADDR_REG 0X2B //IIC从机2器件地址寄存器
#define MPU9250_I2CSLV2_REG 0X2C //IIC从机2数据地址寄存器
#define MPU9250_I2CSLV2_CTRL_REG 0X2D //IIC从机2控制寄存器
#define MPU9250_I2CSLV3_ADDR_REG 0X2E //IIC从机3器件地址寄存器
#define MPU9250_I2CSLV3_REG 0X2F //IIC从机3数据地址寄存器
#define MPU9250_I2CSLV3_CTRL_REG 0X30 //IIC从机3控制寄存器
#define MPU9250_I2CSLV4_ADDR_REG 0X31 //IIC从机4器件地址寄存器
#define MPU9250_I2CSLV4_REG 0X32 //IIC从机4数据地址寄存器
#define MPU9250_I2CSLV4_DO_REG 0X33 //IIC从机4写数据寄存器
#define MPU9250_I2CSLV4_CTRL_REG 0X34 //IIC从机4控制寄存器
#define MPU9250_I2CSLV4_DI_REG 0X35 //IIC从机4读数据寄存器
#define MPU9250_I2CMST_STA_REG 0X36 //IIC主机状态寄存器
#define MPU9250_INTBP_CFG_REG 0X37 //中断/旁路设置寄存器
#define MPU9250_INT_EN_REG 0X38 //中断使能寄存器
#define MPU9250_INT_STA_REG 0X3A //中断状态寄存器
#define MPU9250_ACCEL_XOUTH_REG 0X3B //加速度值,X轴高8位寄存器
#define MPU9250_ACCEL_XOUTL_REG 0X3C //加速度值,X轴低8位寄存器
#define MPU9250_ACCEL_YOUTH_REG 0X3D //加速度值,Y轴高8位寄存器
#define MPU9250_ACCEL_YOUTL_REG 0X3E //加速度值,Y轴低8位寄存器
#define MPU9250_ACCEL_ZOUTH_REG 0X3F //加速度值,Z轴高8位寄存器
#define MPU9250_ACCEL_ZOUTL_REG 0X40 //加速度值,Z轴低8位寄存器
#define MPU9250_TEMP_OUTH_REG 0X41 //温度值高八位寄存器
#define MPU9250_TEMP_OUTL_REG 0X42 //温度值低8位寄存器
#define MPU9250_GYRO_XOUTH_REG 0X43 //陀螺仪值,X轴高8位寄存器
#define MPU9250_GYRO_XOUTL_REG 0X44 //陀螺仪值,X轴低8位寄存器
#define MPU9250_GYRO_YOUTH_REG 0X45 //陀螺仪值,Y轴高8位寄存器
#define MPU9250_GYRO_YOUTL_REG 0X46 //陀螺仪值,Y轴低8位寄存器
#define MPU9250_GYRO_ZOUTH_REG 0X47 //陀螺仪值,Z轴高8位寄存器
#define MPU9250_GYRO_ZOUTL_REG 0X48 //陀螺仪值,Z轴低8位寄存器
#define MPU9250_I2CSLV0_DO_REG 0X63 //IIC从机0数据寄存器
#define MPU9250_I2CSLV1_DO_REG 0X64 //IIC从机1数据寄存器
#define MPU9250_I2CSLV2_DO_REG 0X65 //IIC从机2数据寄存器
#define MPU9250_I2CSLV3_DO_REG 0X66 //IIC从机3数据寄存器
#define MPU9250_I2CMST_DELAY_REG 0X67 //IIC主机延时管理寄存器
#define MPU9250_SIGPATH_RST_REG 0X68 //信号通道复位寄存器
#define MPU9250_MDETECT_CTRL_REG 0X69 //运动检测控制寄存器
#define MPU9250_USER_CTRL_REG 0X6A //用户控制寄存器
#define MPU9250_PWR_MGMT1_REG 0X6B //电源管理寄存器1
#define MPU9250_PWR_MGMT2_REG 0X6C //电源管理寄存器2
#define MPU9250_FIFO_CNTH_REG 0X72 //FIFO计数寄存器高八位
#define MPU9250_FIFO_CNTL_REG 0X73 //FIFO计数寄存器低八位
#define MPU9250_FIFO_RW_REG 0X74 //FIFO读写寄存器
#define MPU9250_DEVICE_ID_REG 0X75 //器件ID寄存器
//=======================================================================
// 用户自定义部分
enum MPU9250_GYRO_FSR // Full Scale Range,满量程
{
MPU9250_GYRO_FSR_250 = 0,
MPU9250_GYRO_FSR_500 = 1,
MPU9250_GYRO_FSR_1000 = 2,
MPU9250_GYRO_FSR_2000 = 3
};
enum MPU9250_ACCEL_FSR
{
MPU9250_ACCEL_FSR_2g = 0,
MPU9250_ACCEL_FSR_4g = 1,
MPU9250_ACCEL_FSR_8g = 2,
MPU9250_ACCEL_FSR_16g = 3
};
#define MPU9250_GYRO_FSR MPU9250_GYRO_FSR_2000 // 加速度计量程 ±2000(可选MPU9250_GYRO_FSR枚举)
#define MPU9250_ACCEL_FSR MPU9250_ACCEL_FSR_2g // 加速度计量程 ±2g(可选MPU9250_ACCEL_FSR枚举)
#define MPU9250_RATE 50 // 采样速率 50Hz(可选4-2000Hz)
/*********************************************************/
// PART1: 初始化及设置函数
uint8_t MPU9250_Init(void);
uint8_t MPU9250_SetGyroFsr(uint8_t fsr);
uint8_t MPU9250_SetAccelFsr(uint8_t fsr);
uint8_t MPU9250_SetLPF(uint16_t lpf);
uint8_t MPU9250_SetRate(uint16_t rate);
/*********************************************************/
// PART2: 获取数据函数及任务
float GetAccelTemperature(void);
void GetGyroTask(void *arg);
void GetMagTask(void *arg); // 获取磁力计原始数据
void GetAccelTask(void *arg);
/*********************************************************/
// PART3: 获取数据函数及任务
void CaliAccelData(float acc[3]);
void CaliGyroData(float gyro[3]);
void CaliMagData(short mag[3]);
/*********************************************************/
// PART4: 外部接口函数
void GetAccelDataApi(float acc[3]); /* 获取三轴加速度接口 */
void GetGyroDataApi(float gyro[3]); /* 获取三轴角速度接口 */
void GetMagDataApi(short mag[3]); /* 获取磁力计数据接口 */
#endif
数据读取采用IIC协议,校准方式也没有改变,几乎没有新的内容,四个部分内容如下:
// mpu9250.c
#include "sys.h"
#include "mpu9250.h"
#include "iic.h"
#include "delay.h"
#include "math.h"
float acc_[3];
float gyro_[3];
short mag_[3];
/********************************************************************************************/
/* PART1: MPU9250 初始化配置 */
/********************************************************************************************/
uint8_t MPU9250_Init(void)
{
uint8_t res = 0;
IIC_WriteAddrRegByte(MPU6500_ADDR, MPU9250_PWR_MGMT1_REG, 0x80);
delay_ms(100);
IIC_WriteAddrRegByte(MPU6500_ADDR, MPU9250_PWR_MGMT2_REG, 0x00);
MPU9250_SetGyroFsr(MPU9250_GYRO_FSR);
MPU9250_SetAccelFsr(MPU9250_ACCEL_FSR_2g);
MPU9250_SetRate(MPU9250_RATE);
IIC_WriteAddrRegByte(MPU6500_ADDR, MPU9250_INT_EN_REG, 0x00);
IIC_WriteAddrRegByte(MPU6500_ADDR, MPU9250_USER_CTRL_REG, 0x00);
IIC_WriteAddrRegByte(MPU6500_ADDR, MPU9250_FIFO_EN_REG, 0x00);
IIC_WriteAddrRegByte(MPU6500_ADDR, MPU9250_INTBP_CFG_REG, 0x82);
res = IIC_ReadAddrRegByte(MPU6500_ADDR, MPU9250_DEVICE_ID_REG);
if (res == MPU6500_ID)
{
IIC_WriteAddrRegByte(MPU6500_ADDR, MPU9250_PWR_MGMT1_REG, 0x01);
IIC_WriteAddrRegByte(MPU6500_ADDR, MPU9250_PWR_MGMT2_REG, 0x00);
}
else
{
printf("MPU9250 Init failed 1, res = %X\r\n", res);
return 1;
}
res = IIC_ReadAddrRegByte(AK8963_ADDR, MPU9250_MAG_WIA);
if (res == AK8963_ID)
{
IIC_WriteAddrRegByte(AK8963_ADDR, MPU9250_MAG_CNTL1, 0x11); // 连续测量模式
printf("MPU9250 Init success.\r\n");
}
else
{
printf("MPU9250 Init failed 2, res = %X\r\n", res);
return 1;
}
return 0;
}
/* 设置MPU9250陀螺仪传感器满量程范围 */
/* fsr:0,±250dps;1,±500dps;2,±1000dps;3,±2000dps */
/* 返回值:0,设置成功 */
/* 其他,设置失败 */
uint8_t MPU9250_SetGyroFsr(uint8_t fsr)
{
return IIC_WriteAddrRegByte(MPU6500_ADDR, MPU9250_GYRO_CFG_REG, fsr<<3); //设置陀螺仪满量程范围
}
/* 设置MPU92506050加速度传感器满量程范围 */
/* fsr:0,±2g;1,±4g;2,±8g;3,±16g */
/* 返回值:0,设置成功 */
/* 其他,设置失败 */
uint8_t MPU9250_SetAccelFsr(uint8_t fsr)
{
return IIC_WriteAddrRegByte(MPU6500_ADDR, MPU9250_ACCEL_CFG_REG, fsr<<3); //设置加速度传感器满量程范围
}
/* 设置MPU92506050的数字低通滤波器 */
/* lpf:数字低通滤波频率(Hz) */
/* 返回值:0,设置成功 */
/* 其他,设置失败 */
uint8_t MPU9250_SetLPF(uint16_t lpf)
{
uint8_t data=0;
if(lpf>=188)
data=1;
else if(lpf>=98)
data=2;
else if(lpf>=42)
data=3;
else if(lpf>=20)
data=4;
else if(lpf>=10)
data=5;
else
data=6;
return IIC_WriteAddrRegByte(MPU6500_ADDR, MPU9250_CFG_REG, data); //设置数字低通滤波器
}
/* 设置MPU9250的采样率(假定Fs=1KHz) */
/* rate:4~1000(Hz) */
/* 返回值:0,设置成功 */
/* 其他,设置失败 */
uint8_t MPU9250_SetRate(uint16_t rate)
{
uint8_t data;
if(rate>1000)
rate=1000;
if(rate<4)
rate=4;
data = 1000 / rate - 1;
data = IIC_WriteAddrRegByte(MPU6500_ADDR, MPU9250_SAMPLE_RATE_REG, data); //设置数字低通滤波器
return MPU9250_SetLPF(rate / 2); //自动设置LPF为采样率的一半
}
/********************************************************************************************/
/* PART2: 校准函数 */
/********************************************************************************************/
/* 校准加速度 */
/* 采用简单的六面校准 */
void CaliAccelData(float acc[3])
{
static const float acc_offset[3] = {0.0144, 0.0311, 1.3100}; // 多次测量加速度计后估计的偏移值
float acc_norm; // 加速度模值
const float g = 9.8; // 重力加速度
acc[0] = -acc[0] - acc_offset[0]; // 去除偏移,此处添加负号使得与机体坐标系一致
acc[1] = acc[1] - acc_offset[1];
acc[2] = acc[2] - acc_offset[2];
acc_norm = sqrt(acc[0]*acc[0] + acc[1]*acc[1] + acc[2]*acc[2]);
acc[0] = acc[0] / acc_norm * g; // 归一化到 [-g, g]
acc[1] = acc[1] / acc_norm * g;
acc[2] = acc[2] / acc_norm * g;
}
/* 校准陀螺仪 */
/* 去除零偏 */
void CaliGyroData(float gyro[3])
{
static const float gyro_offset[3] = {0.685492, -0.836520, 0.721489}; // 测量陀螺仪数据后估计的陀螺仪偏移值
gyro[0] = -(gyro[0] - gyro_offset[0]);
gyro[1] = -(gyro[1] - gyro_offset[1]);
gyro[2] = -(gyro[2] - gyro_offset[2]);
}
/* 校准磁力计 */
/* 校准零偏 */
void CaliMagData(short mag[3])
{
static const short mag_offset[3] = {-80, 196, -101}; // 校准磁力计使用的偏移,多次测量计算而得
mag[0] -= mag_offset[0];
mag[1] -= mag_offset[1];
mag[2] -= mag_offset[2];
}
/********************************************************************************************/
/* PART3: IIC读取数据任务 */
/********************************************************************************************/
/* 获取加速度数据,获取加速度 */
void GetAccelTask(void *arg)
{
uint8_t buf[6], res;
short raw_acc[3]; // 原始的加速度数据,此处必须用short, 取值范围 -32767~32736,占2字节。int取值范围也相同,但是占4字节
float fsr; // 满量程
const float g = 9.8;
while (1)
{
res = IIC_ReadAddrRegBytes(MPU6500_ADDR, MPU9250_ACCEL_XOUTH_REG, 6, buf);
if(res==0)
{
raw_acc[0] = ((uint16_t)buf[0]<<8) | buf[1]; // x 方向加速度原始值
raw_acc[1] = ((uint16_t)buf[2]<<8) | buf[3]; // y 方向加速度原始值
raw_acc[2] = ((uint16_t)buf[4]<<8) | buf[5]; // z 方向加速度原始值
}
if (MPU9250_ACCEL_FSR == MPU9250_ACCEL_FSR_2g) // 选择量程
fsr = 2.0 * g;
else if (MPU9250_ACCEL_FSR == MPU9250_ACCEL_FSR_4g)
fsr = 4.0 * g;
else if (MPU9250_ACCEL_FSR == MPU9250_ACCEL_FSR_8g)
fsr = 8.0 * g;
else if (MPU9250_ACCEL_FSR == MPU9250_ACCEL_FSR_16g)
fsr = 16.0 * g;
acc_[0] = (raw_acc[0]) / 100.0 * (fsr / 327.67); // x 方向加速度,dof9 于 attitude.c 定义
acc_[1] = (raw_acc[1]) / 100.0 * (fsr / 327.67); // y 方向加速度
acc_[2] = (raw_acc[2]) / 100.0 * (fsr / 327.67); // z 方向加速度
CaliAccelData(acc_); // 校准
delay_ms(20);
}
}
/* 获取陀螺仪数据,获取角速度 */
/* 逆时针旋转角速度为正,顺时针旋转角速度为负 */
void GetGyroTask(void *arg)
{
uint8_t buf[6], res;
float fsr; // Full Scale Range 满量程
short raw_gyro[3]; // 原始的陀螺仪数据,此处必须用short, 取值范围 -32767~32736,占2字节。int取值范围也相同,但是占4字节
while (1)
{
res = IIC_ReadAddrRegBytes(MPU6500_ADDR, MPU9250_GYRO_XOUTH_REG, 6, buf);
if(res==0)
{
raw_gyro[0] = ((uint16_t)buf[0]<<8) | buf[1]; // 原始 gyro x
raw_gyro[1] = ((uint16_t)buf[2]<<8) | buf[3]; // 原始 gyro y
raw_gyro[2] = ((uint16_t)buf[4]<<8) | buf[5]; // 原始 gyro z
}
if (MPU9250_GYRO_FSR == MPU9250_GYRO_FSR_250)
fsr = 250.0;
else if (MPU9250_GYRO_FSR == MPU9250_GYRO_FSR_500)
fsr = 500.0;
else if (MPU9250_GYRO_FSR == MPU9250_GYRO_FSR_1000)
fsr = 1000.0;
else if (MPU9250_GYRO_FSR == MPU9250_GYRO_FSR_2000)
fsr = 2000.0;
gyro_[0] = (float)raw_gyro[0] * (fsr / 3276.70) / -10.0;// 添加负号,使得绕 x 轴逆时针转动为正
gyro_[1] = (float)raw_gyro[1] * (fsr / 3276.70) / 10.0; // 乘以 fsr 除以 32767,为了乘积太大引起误差,先除以3276.7再除以10.0。
gyro_[2] = (float)raw_gyro[2] * (fsr / 3276.70) / 10.0;
CaliGyroData(gyro_); // 校准
delay_ms(20);
}
}
/* 获取磁力计数据任务 */
/* mag[0]~mag[2] 分别为x,z,y方向16位磁力计数据 */
/* !!! 是 x,z,y方向数据,不是 x,y,z 方向,因为HMC5883读取数据顺序就是 x,z,y !!!*/
/* short为2字节,读取的buf[0],buf[1]也是两字节。int为四字节,(int)65534=65534, (short)65534=-2*/
void GetMagTask(void *arg) // 获取磁力计原始数据
{
uint8_t buf[6] ={0}, res;
while (1)
{
res = IIC_ReadAddrRegBytes(AK8963_ADDR, MPU9250_MAG_XOUT_L, 6, buf); // IIC读取磁力计原始数据
if (res == 0)
{
mag_[0] = ((uint16_t)buf[1]<<8) | buf[0]; // 磁力计 x方向原始数据
mag_[1] = ((uint16_t)buf[3]<<8) | buf[2]; // 磁力计 y方向原始数据
mag_[2] = ((uint16_t)buf[5]<<8) | buf[4]; // 磁力计 z方向原始数据
}
IIC_WriteA-ddrRegByte(AK8963_ADDR, MPU9250_MAG_CNTL1, 0x11); // 单次测量模式
CaliMagData(mag_);
delay_ms(20);
}
}
/* 得到温度值 */
/* 返回值:温度值(°C) */
/* 此处为使用到 */
float GetAccelTemperature(void)
{
uint8_t buf[2];
short raw_tmp; // 温度的原始数据
float tmp; // 温度值
IIC_ReadAddrRegBytes(MPU6500_ADDR, MPU9250_TEMP_OUTH_REG, 2, buf);
raw_tmp = ((uint16_t)buf[0]<<8) | buf[1];
tmp = 36.53 + ((double)raw_tmp) / 340.0;
return tmp;
}
/*********************************************-***********************************************/
/* PART4: 外部接口 */
/********************************************************************************************/
/* 获取磁力计数据接口 */
void GetMagDataApi(short mag[3])
{
mag[0] = mag_[0];
mag[1] = mag_[1];
mag[2] = mag_[2];
}
/* 获取三轴加速度接口 */
void GetAccelDataApi(float acc[3])
{
acc[0] = acc_[0];
acc[1] = acc_[1];
acc[2] = acc_[2];
}
/* 获取三轴角速度接口 */
void GetGyroDataApi(float gyro[3])
{
gyro[0] = gyro_[0];
gyro[1] = gyro_[1];
gyro[2] = gyro_[2];
}
从头文件可见,定义了两个结构体,一个是Sbsu协议帧,一个专门针对六通道的SBUS遥控器设计的结构体。函数声明可见包括初始化、解析Sbus协议、校准遥控器、测试控制电机以及对外提供的API。
// sbus.h
#ifndef SBUS_H
#define SBUS_H
#include "sys.h"
#define USART_BUF_SIZE 16 // HAL库USART接收Buffer大小
#define SBUS_DATA_SIZE 25 // 25字节
/* SBUS协议帧 */
struct SBUS_t
{
uint8_t head; // 1字节首部
uint16_t ch[16]; // 16个字节数据
uint8_t flag; // 1字节标志位
uint8_t end; // 1字节结束
};
/* MC6C遥控器只有六个通道,只使用了SBUS协议帧的部分数据 */
struct MC6C_t
{
float ail; // CH1, aileron, 副翼,调节滚转角
float ele; // CH2, elevator, 升降,调节俯仰角
float thr; // CH3, throttle, 油门
float rud; // CH4, rudder, 方向舵,调节偏航角
uint16_t ch5; // CH5, 最终只三档, 取值 1,2,3
uint16_t ch6; // CH6, 最终值两档, 取值 1,2
};
void SBUS_Init(void); // SBUS硬件初始化
void SbusParseTask(void *arg); // 解析遥控器接收的数据
void CaliMc6cData(struct MC6C_t *mc6c); // 遥控器校准,将遥控器接收数据映射到想要区间
void TestCtrlMotorTask(void *arg); // 测试遥控器控制电机任务
void GetSbusDataApi(struct SBUS_t *sbus); // Sbus数据调用接口
void GetMc6cDataApi(struct MC6C_t *mc6c); // 针对MC6C遥控器,MC6C遥控器数据接口
#endif
配置USART2为DMA模式。STM32DMA 映射表如下图:
此处使用USART2 的接收功能,故DMA1 数据流5 通道4。
USART2 对应的引脚为 PA2(TX)和PA3(RX)。
根据SBUS协议,波特率为 100kbps,数据为9B(在STM32中要选择9位,8位数据+1位校验位),偶校验,2位停止位。
把上面三个按照引脚、DMA和串口的顺序依次配置(不按照此顺序可能不行),每个配置都是先开启时钟,然后配置结构体,再来句初始化,最后有中断的配置并开启中断。
// sbus.c
#include "sbus.h"
#include "delay.h"
#include "define.h"
#include "pwm.h"
#include
struct SBUS_t sbus_; // SBUS接收数据全局变量
uint8_t usart_buf[USART_BUF_SIZE];
uint8_t sbus_rx_buf[SBUS_DATA_SIZE]; // 接收sbus_数据缓冲区
/***********************************************************************************************/
/*********************** PART1:USART2 DMA 获取遥控器原始数据***********************************/
UART_HandleTypeDef huart2;
DMA_HandleTypeDef hdma_usart2_rx;
/* SBUS协议硬件初始化 */
/* 配置GPIO、DMA与串口 */
void SBUS_Init(void)
{
GPIO_InitTypeDef GPIO_Initure;
// GPIO 配置
//PA2 ------> USART2_TX
//PA3 ------> USART2_RX
__HAL_RCC_GPIOA_CLK_ENABLE(); // 时钟要开启
GPIO_Initure.Pin = GPIO_PIN_2 | GPIO_PIN_3; // 引脚 PA2, PA3
GPIO_Initure.Mode = GPIO_MODE_AF_PP; // 复用推挽输出
GPIO_Initure.Pull = GPIO_PULLUP; // 上拉
GPIO_Initure.Speed = GPIO_SPEED_FREQ_VERY_HIGH; // 极高速
GPIO_Initure.Alternate = GPIO_AF7_USART2; // 串口2复用
HAL_GPIO_Init(GPIOA, &GPIO_Initure);
// DMA 配置
// USART2 -- DMA1 Stream5 CH4
__HAL_RCC_DMA1_CLK_ENABLE(); // 时钟不要少
hdma_usart2_rx.Instance = DMA1_Stream5; // DMA1 Stream5
hdma_usart2_rx.Init.Channel = DMA_CHANNEL_4; // CH4
hdma_usart2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; // 外设到内存
hdma_usart2_rx.Init.PeriphInc = DMA_PINC_DISABLE; // 外设地址不变
hdma_usart2_rx.Init.MemInc = DMA_MINC_ENABLE; // 内存地址递增
hdma_usart2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; // 按照字节对齐
hdma_usart2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; // 按照字节对齐
hdma_usart2_rx.Init.Mode = DMA_NORMAL; // 普通模式,每接收完一次都要重新接收
hdma_usart2_rx.Init.Priority = DMA_PRIORITY_VERY_HIGH;// 极高优先级
hdma_usart2_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; // 不使用 FIFO
HAL_DMA_Init(&hdma_usart2_rx);
__HAL_LINKDMA(&huart2,hdmarx,hdma_usart2_rx); // USART2 和 DMA1_Stream5_CH4 本是独立的,这句话将两者连起来
HAL_NVIC_SetPriority(DMA1_Stream1_IRQn, 2, 2); // DMA中断优先级
HAL_NVIC_EnableIRQ(DMA1_Stream5_IRQn); // 使能DMA中断
// USART2 配置
// SBUS协议:100kbps,9B=8B数据+1B校验位,停止位2
__HAL_RCC_USART2_CLK_ENABLE(); // 时钟必须的
huart2.Instance = USART2;
huart2.Init.BaudRate = 100000; // 100kbps
huart2.Init.WordLength = UART_WORDLENGTH_9B; // 注意为9位!!!(8bits数据+1bit校验位)
huart2.Init.StopBits = UART_STOPBITS_2; // 停止位2
huart2.Init.Parity = UART_PARITY_EVEN; // 偶校验
huart2.Init.Mode = UART_MODE_TX_RX; // 收发模式
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE; // 不使用硬件流
huart2.Init.OverSampling = UART_OVERSAMPLING_16; // 过采样16倍,采样频率是波特率的16倍?
HAL_UART_Init(&huart2);
HAL_NVIC_SetPriority(USART2_IRQn, 2, 3); // 中断优先级
HAL_NVIC_EnableIRQ(USART2_IRQn); // 中断使能
}
/* DMA1 Stream5 中断服务函数 */
void DMA1_Stream5_IRQHandler(void)
{
HAL_DMA_IRQHandler(&hdma_usart2_rx);
}
/* 串口2中断服务函数 */
void USART2_IRQHandler(void)
{
uint8_t chr;
if (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_RXNE) != RESET) // 发生接收中断(每来一个字符都会中断一次)
{
HAL_UART_Receive(&huart2, &chr, 1, 1000); // 接收一个字符
if (chr == 0x0F) // 如果是 0x0F(首部)
{
__HAL_UART_DISABLE_IT(&huart2, UART_IT_RXNE); // 关闭接收中断(再来字符也进不了此中断)
HAL_UART_Receive_DMA(&huart2, sbus_rx_buf , SBUS_DATA_SIZE); // 启动DMA,接收一帧数据
}
}
if((__HAL_UART_GET_FLAG(&huart2,UART_FLAG_IDLE) != RESET)) // 如果线路空闲(接收完一帧)
{
__HAL_UART_CLEAR_IDLEFLAG(&huart2); // 清除线路空闲标志
HAL_UART_DMAStop(&huart2); // 关闭DMA(HAL_UART_Receive_DMA会启动)
}
}
对于通过中断服务函数获取Sbus协议数据,思想是:
Sbus解析任务如下,注意解析完之后记得开启串口接收中断。
/***********************************************************************************************/
/*********************** PART2:解析SBUS协议并对遥控器进行校准***********************************/
/* 对SBUS协议数据进行解析 */
/* 实现对S.BUS协议缓存,头部为 0x0F,结尾为 0x00, 中间22Bytes16通道数据,1Byte标志符 */
void SbusParseTask(void *arg)
{
__HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);
__HAL_UART_ENABLE_IT(&huart2, UART_IT_RXNE);
while (1)
{
if (sbus_rx_buf[0]==0x0F && sbus_rx_buf[24]==0x00)
{
sbus_.head = sbus_rx_buf[0]; // 首部
sbus_.flag = sbus_rx_buf[23]; // 标志符
sbus_.end = sbus_rx_buf[24]; // 结尾
sbus_.ch[0] =((uint16_t)(sbus_rx_buf[2]<<8) | (uint16_t)(sbus_rx_buf[1])) & 0x07ff;
sbus_.ch[1] =((uint16_t)(sbus_rx_buf[3]<<5) | (uint16_t)(sbus_rx_buf[2]>>3)) & 0x07ff;
sbus_.ch[2] =((uint16_t)(sbus_rx_buf[5]<<10) | (uint16_t)(sbus_rx_buf[4]<<2) | (sbus_rx_buf[3]>>6)) & 0x07ff;
sbus_.ch[3] =((uint16_t)(sbus_rx_buf[6]<<7) | (uint16_t)(sbus_rx_buf[5]>>1)) & 0x07ff;
sbus_.ch[4] =((uint16_t)(sbus_rx_buf[7]<<4) | (sbus_rx_buf[6]>>4)) & 0x07ff;
sbus_.ch[5] =((uint16_t)(sbus_rx_buf[9]<<9) | (uint16_t)(sbus_rx_buf[8]<<1) | (sbus_rx_buf[7]>>7)) & 0x07ff;
sbus_.ch[6] =((uint16_t)(sbus_rx_buf[10]<<6) | (uint16_t)(sbus_rx_buf[9]>>2)) & 0x07ff;
sbus_.ch[7] =((uint16_t)(sbus_rx_buf[11]<<3) | (uint16_t)(sbus_rx_buf[10]>>5)) & 0x07ff;
sbus_.ch[8] =((uint16_t)(sbus_rx_buf[13]<<8) | (uint16_t)sbus_rx_buf[12]) & 0x07ff;
sbus_.ch[9] =((uint16_t)(sbus_rx_buf[14]<<5) | (uint16_t)(sbus_rx_buf[13]>>3)) & 0x07ff;
sbus_.ch[10]=((uint16_t)(sbus_rx_buf[16]<<10)| (uint16_t)(sbus_rx_buf[15]<<2) | (sbus_rx_buf[14]>>6)) & 0x07ff;
sbus_.ch[11]=((uint16_t)(sbus_rx_buf[17]<<7) | (uint16_t)(sbus_rx_buf[16]>>1)) & 0x07ff;
sbus_.ch[12]=((uint16_t)(sbus_rx_buf[18]<<4) | (uint16_t)(sbus_rx_buf[17]>>4)) & 0x07ff;
sbus_.ch[13]=((uint16_t)(sbus_rx_buf[20]<<9) | (uint16_t)(sbus_rx_buf[19]<<1) | (sbus_rx_buf[18]>>7)) & 0x07ff;
sbus_.ch[14]=((uint16_t)(sbus_rx_buf[21]<<6) | (uint16_t)(sbus_rx_buf[20]>>2)) & 0x07ff;
sbus_.ch[15]=((uint16_t)(sbus_rx_buf[22]<<3) | (uint16_t)(sbus_rx_buf[21]>>5)) & 0x07ff;
printf("======================================\r\n");
printf("正常: head=0x0F, flag=0x00, end=0x00\r\n\r\n");
printf("head: %d\r\n", sbus_.head);
printf(" %d, %d, %d, %d\r\n", sbus_.ch[0], sbus_.ch[1], sbus_.ch[2], sbus_.ch[3]);
printf(" %d, %d, %d, %d\r\n", sbus_.ch[4], sbus_.ch[5], sbus_.ch[6], sbus_.ch[7]);
printf(" %d, %d, %d, %d\r\n", sbus_.ch[8], sbus_.ch[9], sbus_.ch[10], sbus_.ch[11]);
printf(" %d, %d, %d, %d\r\n", sbus_.ch[12], sbus_.ch[13], sbus_.ch[14], sbus_.ch[15]);
printf("flag: %d\r\n", sbus_.flag);
printf("end: %d\r\n", sbus_.end);
printf("======================================\r\n\r\n");
memset(sbus_rx_buf, 1, SBUS_DATA_SIZE); // 用1覆盖原有数据,避免数据为更新导致错误
__HAL_UART_ENABLE_IT(&huart2, UART_IT_RXNE);
delay_ms(50); // 先做完延时再开启中断与下一次捕获,否则延时期间中断到来,没有达到预期效果
}
else
{
delay_ms(100); // 免得异常时,到此处使得低优先级任务无法执行
}
}
}
这部分没什么新的,记得对于遥控器数据,每次校准需要修改 mc6c_min[] 等几个数组的值。
/*
CH1 -- 俯仰角, 归中0°, 最大最小 ±30°
CH2 -- 滚转角, 归中0°, 最大最小 ±30°
CH3 -- 油门, 归中0°, 最大设置占空比 80%, 最小设置占空比 40%, 电调驱动频率为400Hz=2.5ms, 40%=1ms, 80%=2ms
CH4 -- 偏航角角速度, 归中0°/s, 最大最小 ±36°/s
CH5 -- 档位, 上中下分别为 1, 2, 3三档
CH6 -- 档位, 上下分别为 1, 2两档
*/
void CaliMc6cData(struct MC6C_t *mc6c)
{
static const float mc6c_min[6] = {596, 596, 196, 609, 201, 201}; // 转动摇杆,各通道最小值,本为 uint16_t,为方便计算直接为 float
static const float mc6c_max[6] = {1404, 1404, 1796, 1412, 1801, 1801}; // 转动摇杆,各通道最大值
static const float mc6c_mid[6] = {1017, 1009, 1001, 1001, 998, 200}; // CH6 只有两通道
float k;
float b;
// CH1 映射, [mc6c_min[0], mc6c_mid[0]] --> [30, 0]; [mc6c_mid[0], mc6c_max[0]] --> [0, -30], 滚转角变化
if (mc6c->ail < mc6c_mid[0])
{
k = (0 - 30) / (mc6c_mid[0] - mc6c_min[0]);
b = 0 - k * mc6c_mid[0];
mc6c->ail = k * mc6c->ail + b;
}
else
{
k = (-30 - 0) / (mc6c_max[0] - mc6c_mid[0]);
b = 0 - k * mc6c_mid[0];
mc6c->ail = k * mc6c->ail + b;
}
// CH2 映射,俯仰角变化,注意对比俯仰角,保证遥控器CH1前推无人机要向前
if (mc6c->ele < mc6c_mid[1])
{
k = (0 - 30) / (mc6c_mid[1] - mc6c_min[1]);
b = 0 - k * mc6c_mid[1];
mc6c->ele = k * mc6c->ele + b;
}
else
{
k = (-30 - 0) / (mc6c_max[1] - mc6c_mid[1]);
b = 0 - k * mc6c_mid[1];
mc6c->ele = k * mc6c->ele + b;
}
// CH3 映射,不用考虑中间档 [mc6c_min[2], mc6c_max[2]] --> [400, 800]
if (mc6c->thr < mc6c_min[2])
mc6c->thr = mc6c_min[2];
else if (mc6c->thr > mc6c_max[2])
mc6c->thr = mc6c_max[2];
else
{
k = (800 - 400) / (mc6c_max[2] - mc6c_min[2]);
b = 400 - k * mc6c_min[2];
mc6c->thr = k * mc6c->thr + b;
}
// CH4 映射,[mc6c_min[3], mc6c_mid[3]] --> [36, 0]; [mc6c_mid[3], mc6c_max[3]] --> [0, -36]
if (mc6c->rud < mc6c_mid[3])
{
k = (0 - 36) / (mc6c_mid[3] - mc6c_min[3]);
b = 0 - k * mc6c_mid[3];
mc6c->rud = k * mc6c->rud + b;
}
else
{
k = (-36 - 0) / (mc6c_max[3] - mc6c_mid[3]);
b = 0 - k * mc6c_mid[3];
mc6c->rud = k * mc6c->rud + b;
}
// CH5 映射,得到三档分别赋值 1,2,3
if (mc6c->ch5 < (mc6c_min[4] + mc6c_mid[4]) / 2)
mc6c->ch5 = 1;
else if (mc6c->ch5 < (mc6c_mid[4] + mc6c_max[4]) / 2)
mc6c->ch5 = 2;
else
mc6c->ch5 = 3;
// CH6 映射,得到两档分别赋值 1,2
if (mc6c->ch6 < (mc6c_min[5] + mc6c_max[5]) / 2)
mc6c->ch6 = 1;
else
mc6c->ch6 = 2;
}
/* 遥控器控制电机测试 */
/* 遥控器油门将调节电机占空比 */
void TestCtrlMotorTask(void *arg)
{
struct MC6C_t mc6c;
while (1)
{
GetMc6cDataApi(&mc6c);
SetMotorDutyApi(MOTOR1, (uint16_t)mc6c.thr); // MOTOR1, 引脚为TIM3 CH1, PB4
SetMotorDutyApi(MOTOR2, (uint16_t)mc6c.thr); // MOTOR2, 引脚为TIM3 CH2, PB5
SetMotorDutyApi(MOTOR3, (uint16_t)mc6c.thr); // MOTOR3, 引脚为TIM3 CH3, PB0
SetMotorDutyApi(MOTOR4, (uint16_t)mc6c.thr); // MOTOR4, 引脚为TIM3 CH4, PB1
delay_ms(50);
}
}
/* MC6C遥控器数据接口 */
/* 依赖SbusParseTask()任务 */
void GetMc6cDataApi(struct MC6C_t *mc6c)
{
mc6c->ail = (float)sbus_.ch[0];
mc6c->ele = (float)sbus_.ch[1];
mc6c->thr = (float)sbus_.ch[2];
mc6c->rud = (float)sbus_.ch[3];
mc6c->ch5 = sbus_.ch[4];
mc6c->ch6 = sbus_.ch[5];
CaliMc6cData(mc6c);
}
/* 获取遥控器数据接口 */
/* 目前未使用此函数 */
void GetSbusDataApi(struct SBUS_t *sbus)
{
sbus->head = sbus_.head;
sbus->flag = sbus_.flag;
sbus->end = sbus_.end ;
sbus->ch[0] = sbus_.ch[0];
sbus->ch[1] = sbus_.ch[1];
sbus->ch[2] = sbus_.ch[2];
sbus->ch[3] = sbus_.ch[3];
sbus->ch[4] = sbus_.ch[4];
sbus->ch[5] = sbus_.ch[5];
sbus->ch[6] = sbus_.ch[6];
sbus->ch[7] = sbus_.ch[7];
sbus->ch[8] = sbus_.ch[8];
sbus->ch[9] = sbus_.ch[9];
sbus->ch[10] = sbus_.ch[10];
sbus->ch[11] = sbus_.ch[11];
sbus->ch[12] = sbus_.ch[12];
sbus->ch[13] = sbus_.ch[13];
sbus->ch[14] = sbus_.ch[14];
sbus->ch[15] = sbus_.ch[15];
}
— 完 —