UAV021(六--更新):STM32F4读取MPU9250数据、USART2通过DMA获取SBUS协议数据

目录

    • 一、读取MPU9250数据
        • 1.1 简单说明
        • 1.2 读取与校准MPU9250数据程序
            • 1.2.1 头文件
            • 1.2.2 源文件
    • 2. UART2 DMA读取与解析Sbus协议
        • 2.1 头文件
        • 2.2 源文件
            • 2.2.1 USART2 DMA配置
            • 2.2.2 Sbus协议解析任务
            • 2.2.3 遥控器校准、测试任务函数、对外接口



之所以有这篇更新,只因时运不济,命途多舛。第一件事是想把之前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


一、读取MPU9250数据


1.1 简单说明

  1. IIC协议在UAV021(二)里说明过,不再赘述。

  2. 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

1.2 读取与校准MPU9250数据程序

1.2.1 头文件

整个头文件如下,首先定义了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
1.2.2 源文件

数据读取采用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];
}

2. UART2 DMA读取与解析Sbus协议


2.1 头文件

从头文件可见,定义了两个结构体,一个是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

2.2 源文件

2.2.1 USART2 DMA配置

配置USART2为DMA模式。STM32DMA 映射表如下图:

UAV021(六--更新):STM32F4读取MPU9250数据、USART2通过DMA获取SBUS协议数据_第1张图片

  1. 此处使用USART2 的接收功能,故DMA1 数据流5 通道4。

  2. USART2 对应的引脚为 PA2(TX)和PA3(RX)。

  3. 根据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协议数据,思想是:

  1. 串口发生中断(UART_FLAG_RXNE)接收到 0x0F(帧头)时,关闭串口中断,开启DMA传输,直接将25个字节传到指定数组中。
  2. 传输完成后,由于两帧Sbus协议间有间隔,会产生线路空闲中断(UART_FLAG_IDLE),此时关闭DMA传输。
  3. 处理完接收的一帧数据后,开启串口中断,使得能够接收新的一帧,见下面的Sbus任务函数。
2.2.2 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);							// 免得异常时,到此处使得低优先级任务无法执行
		}
	}
}
2.2.3 遥控器校准、测试任务函数、对外接口

这部分没什么新的,记得对于遥控器数据,每次校准需要修改 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];
}

— 完 —

你可能感兴趣的:(无人机)