
基于STM32的四翼无人机设计
飞控(飞行控制系统,Flight Control System)是无人机中至关重要的组成部分,负责控制无人机的飞行姿态、航向、速度和高度等。飞控系统通过传感器收集飞行数据,并根据这些数据进行实时计算和调整,以确保无人机的稳定飞行和安全操作。姿态控制:通过陀螺仪、加速度计等传感器监测无人机的姿态(如俯仰、滚转和偏航),并进行调整以保持稳定。导航:利用GPS等定位系统确定无人机的位置,并根据预设航线进
前言:本博客主要记录了一次设计无人机的流程以及相关知识,由于成本以及本人知识储备,
项目较为粗糙,旨在学习与记录,如有错误与建议欢迎指出!!!
一,飞控理论知识
1,飞控概述
飞控(飞行控制系统,Flight Control System)是无人机中至关重要的组成部分,负责控制无人机的飞行姿态、航向、速度和高度等。飞控系统通过传感器收集飞行数据,并根据这些数据进行实时计算和调整,以确保无人机的稳定飞行和安全操作。
飞控系统的主要功能包括:
-
姿态控制:通过陀螺仪、加速度计等传感器监测无人机的姿态(如俯仰、滚转和偏航),并进行调整以保持稳定。
-
导航:利用GPS等定位系统确定无人机的位置,并根据预设航线进行导航。
-
故障检测与安全:实时监测系统状态,检测潜在故障,并在必要时采取安全措施,如自动返航。
-
通信:与地面控制站进行数据交换,接收指令并反馈飞行状态。
飞控系统的设计和实现通常涉及复杂的算法和控制理论,以确保无人机在各种环境条件下的稳定性和可靠性。
2,飞行姿态
无人机的飞行姿态是指无人机在三维空间中的定向和角度状态,通常用俯仰(Pitch)、滚转(Roll)和偏航(Yaw)三个角度来描述。这些姿态角度决定了无人机的飞行方向和稳定性。
-
俯仰角(Pitch):指无人机前后倾斜的角度。正俯仰角表示无人机机头向上,负俯仰角表示机头向下。俯仰角影响无人机的升降和前进速度。
-
滚转角(Roll):指无人机左右倾斜的角度。正滚转角表示无人机向右倾斜,负滚转角表示向左倾斜。滚转角影响无人机的侧向运动和转弯能力。
-
偏航角(Yaw):指无人机围绕垂直轴的旋转角度。正偏航角表示无人机向右旋转,负偏航角表示向左旋转。偏航角影响无人机的航向和方向控制。
3,飞行原理
四翼无人机结构目前主流设计分为 " x " 型和 " + " 型两种,本设计采用机动性更好的 " x " 型结构。如图:
在四旋翼无人机中,可以将位于机身同一对角线上的旋翼分为一组。前后端的旋翼顺时针旋转,从而产生顺时针方向的扭矩;而左右端的旋翼则逆时针旋转,产生逆时针方向的扭矩。这样,四个旋翼所产生的扭矩能够相互抵消。因此,四旋翼无人机的姿态和位置控制完全依赖于调节四个电机的转速来实现。
3.1,悬停
悬停为无人机的一个重要功能,悬停状态下,四翼无人机的四个旋翼具有相同的转速,扭矩相互抵消,且产生的升力正好可以抵消飞行器本身的重力。使得无人机可以在空中保持静止状态。
3.2,垂直运动
垂直运动,即保持无人机四旋翼转速相同,扭矩相互抵消的状态下,将每一个旋翼增加或者减小相同的转速,使得无人机的升力大于或者小于重力,既可以实现无人机的垂直运动。
3.3,俯仰运动
当将无人机前端两旋翼的转速降低或者将后端两旋翼的转速升高的情况下,四旋翼会产生向前的力,使无人机向前方飞行。反之,如果将前端两旋翼的转速升高或者将后端两旋翼的转速降低时,四旋翼会产生向后的力,使无人机向后飞行。
3.4,翻滚运动
翻滚运动与俯仰运动类似,当左端两旋翼转速增加或者右端两旋翼转速减小时,四旋翼会产生向右上方的力,使飞行器向右飞行。反之,当左端两旋翼转速减小或者右端两旋翼转速增加时,四旋翼会产生向左上方的力,使飞行器向左飞行。
3.5,偏航运动
当F1和F3旋翼转速增加或者F4和F2旋翼转速减小时,由于前者为逆时针旋转,所以四旋翼会产生一个向左的扭矩力,从而使无人机逆时钟运动,即向左偏航。反之,当F1和F3旋翼转速减小或者F4和F2旋翼转速增加时,由于后者为顺时针旋转,且转速大于前者,所以四旋翼会产生一个向右的扭矩力,从而使无人机顺时钟运动,即向右偏航。如图:
4,飞行器控制结构
一般情况下,飞行器主要分为检测模块,控制模块,电源模块以及驱动模块。如图:
4.1,控制模块
控制模块主要负责对无人机当前姿态的解算,优化控制,并对驱动模块产生相应的控制。此项目中的飞控模块即为无人机的MCU ,如今飞控多采用STM32F4系列的芯片MCU作为主控单元,但由于本项目中本人成本等问题,所以考虑采用的为STM32F103CBT6作为主控芯片。
4.2,检测模块
检测模块主要负责对无人机当前姿态进行量测,并且将数据提供给通知模块。检测模块主要包括:惯性检测单元(MPU6050),气压计(SPL06),电子罗盘(GPS)等。
惯性检测单元:通常由姿态传感器+磁力计构成。本设计中使用的为MPU6050六轴姿态传感器作为惯性检测单元,可以得到飞行器的欧拉角。如图:
气压计:气压计主要负责检测飞行器的飞行高度,帮助飞行器实现"定高"功能。本设计中采用SPL06作为气压计,可测出无人机的相对高度以及绝对高度。但绝对高度可能因为各种因素在一天内浮动几十米,而在同一时段所测量相对高度相对稳定。
4.3,驱动模块
由于本设计为四翼无人机的设计,每一个旋翼都需要单独的电机驱动控制。所以需要四个电机,而每一个电机则需要专门的驱动电路来进行控制,本项目使用如下驱动电路,每一个电路可以单独控制停启以及进行PWM占空比调节。
4.4,电源模块
此飞行器使用的供电电源为专用锂电池,供电电压为12V,但是单片机的工作额定电压为3.3V~5V,所以需要降压为不同的元器件提供其所需电压。
注意:大部分相关锂电池不可低于标准电压,否则会导致锂电池损坏,因此需要使用相关IO口的AD读取功能时刻检测电池电压变化,当低于规定值时,发出相关信号提醒。
二,飞控具体设计
1,指示灯
指示灯作为无人机上最直观的信息传达手段,在无人机的四个角分别具有红绿两个led小灯,可以直观的表达出无人机的正常起飞,悬停,电量不足,信号接受成功等等信息。
硬件原理图如图所示:
由原理图可以获取,四个IO口可以分别控制两组红绿灯。而每一组的两个灯都成对角分布在无人机的四角,因此可以通过灯光控制达到多种视觉效果。
LED.H 代码如下:
#ifndef __LED_H
#define __LED_H
void LED_Init(void);
void LED1_ON(void);
void LED1_OFF(void);
void LED1_Turn(void);
void LED2_ON(void);
void LED2_OFF(void);
void LED2_Turn(void);
#endif
LED.C 代码如下:
#include "stm32f10x.h" // Device header
#include "LED.h"
/**
* 函 数:LED初始化
* 参 数:无
* 返 回 值:无
*/
void LED_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); //开启GPIOC的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_WriteBit(GPIOC, GPIO_Pin_14 | GPIO_Pin_15, Bit_SET);
GPIO_WriteBit(GPIOB, GPIO_Pin_3 | GPIO_Pin_4, Bit_SET);
}
/**
* 函 数:RED开启
* 参 数:无
* 返 回 值:无
*/
void LED1_ON(void)
{
GPIO_ResetBits(GPIOB, GPIO_Pin_3 | GPIO_Pin_4);
}
/**
* 函 数:RED关闭
* 参 数:无
* 返 回 值:无
*/
void LED1_OFF(void)
{
GPIO_SetBits(GPIOB, GPIO_Pin_3 | GPIO_Pin_4);
}
/**
* 函 数:RED状态翻转
* 参 数:无
* 返 回 值:无
*/
void LED1_Turn(void)
{
if (GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_3 | GPIO_Pin_4) == 0)
{
GPIO_SetBits(GPIOB, GPIO_Pin_3 | GPIO_Pin_4);
}
else
{
GPIO_ResetBits(GPIOB ,GPIO_Pin_3 | GPIO_Pin_4);
}
}
/**
* 函 数:GREEN开启
* 参 数:无
* 返 回 值:无
*/
void LED2_ON(void)
{
GPIO_ResetBits(GPIOC, GPIO_Pin_14 | GPIO_Pin_15);
}
/**
* 函 数:GREEN关闭
* 参 数:无
* 返 回 值:无
*/
void LED2_OFF(void)
{
GPIO_SetBits(GPIOC, GPIO_Pin_14 | GPIO_Pin_15);
}
/**
* 函 数:GREEN状态翻转
* 参 数:无
* 返 回 值:无
*/
void LED2_Turn(void)
{
if (GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_14 | GPIO_Pin_15) == 0)
{
GPIO_SetBits(GPIOC, GPIO_Pin_14 | GPIO_Pin_15);
}
else
{
GPIO_ResetBits(GPIOC, GPIO_Pin_14 | GPIO_Pin_15);
}
}
此代码中仅写了两组红绿灯的整体亮灭与翻转,如需要,可自行写每一组灯的分别亮灭和翻转。
2,开关及电机
由于此项目中四旋翼在各种情况下需要单独控制,所以对应的每一个电机都配备了一个单独的开关,且出于安全性等因素的考虑,还为四个电机配备了一个总的电源开关,可在某突发情况下实现一键断电。
两个开关的原理图分别如下:
电机总开关代码如下:
E8520.H代码:
#ifndef __E8520_H
#define __E8520_H
void E8520_JP12_Init(void); //开关初始化
void E8520_JP34_Init(void);
//电机转速调节
void PWM_SetCompare1(uint16_t Compare);//JP1 : PB9
void PWM_SetCompare2(uint16_t Compare);//JP2 : PB8
void PWM_SetCompare3(uint16_t Compare);//JP3 : PB1
void PWM_SetCompare4(uint16_t Compare);//JP4 : PB0
void Switch_5V_Init(void); //电源总开关初始化
void Open_5V(void); //打开开关
void Close_5V(void); //关闭开关
#endif
E8520.C代码:
#include "stm32f10x.h"
#include "E8520.h"
void Switch_5V_Init(void) //5V开关初始化
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_WriteBit(GPIOA,GPIO_Pin_11, Bit_RESET);
}
void Open_5V(void) //打开点击总开关
{
GPIO_WriteBit(GPIOA,GPIO_Pin_11, Bit_SET);
}
void Close_5V(void) //关闭电机总开关
{
GPIO_WriteBit(GPIOA,GPIO_Pin_11, Bit_RESET);
}
void E8520_JP12_Init(void) //一二号电机初始化
{
//JP1 : PB9 JP2 : PB8 JP3 : PB1 JP4 : PB0
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_WriteBit(GPIOB,GPIO_Pin_8 | GPIO_Pin_9, Bit_RESET);
TIM_InternalClockConfig(TIM4);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;
TIM_TimeBaseInitStructure.TIM_Prescaler = 36 - 1;
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseInitStructure);
//Êä³ö±È½Ï³õʼ»¯
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OC3Init(TIM4, &TIM_OCInitStructure); //PB8
TIM_OC4Init(TIM4, &TIM_OCInitStructure); //PB9
TIM_Cmd(TIM4, ENABLE);
}
void E8520_JP34_Init(void) //三四号电机初始化
{
//JP1 : PB9 JP2 : PB8 JP3 : PB1 JP4 : PB0
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_WriteBit(GPIOB,GPIO_Pin_0 | GPIO_Pin_1, Bit_RESET);
TIM_InternalClockConfig(TIM3);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;
TIM_TimeBaseInitStructure.TIM_Prescaler = 36 - 1;
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
//Êä³ö±È½Ï³õʼ»¯
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OC3Init(TIM3, &TIM_OCInitStructure); //PB0
TIM_OC4Init(TIM3, &TIM_OCInitStructure); //PB1
TIM_Cmd(TIM3, ENABLE);
}
void PWM_SetCompare1(uint16_t Compare) //一号电机占空比调节
{
TIM_SetCompare4(TIM4, Compare); //JP1 : PB9 :TIM4_CH4
}
void PWM_SetCompare2(uint16_t Compare)
{
TIM_SetCompare3(TIM4, Compare); //JP2 : PB8 :TIM4_CH3
}
void PWM_SetCompare3(uint16_t Compare)
{
TIM_SetCompare4(TIM3, Compare); //JP3 : PB1 :TIM3_CH4
}
void PWM_SetCompare4(uint16_t Compare)
{
TIM_SetCompare3(TIM3, Compare); //JP4 : PB0 :TIM3_CH3
}
四电机百分之八十占空比展示视频如下:
无人机电机测试
3,惯性检测单元
本设计中使用的惯性检测单元为MPU6050六轴姿态传感器,可以读出三轴角速度和三轴角加速度。得到这些数据后便可通过几种方法转化为相应的欧拉角,从而得到飞行器的姿态
3.1,MPU6050简介
MPU6050是InvenSense公司推出的全球首款整合性6轴运动处理组件。
图示向右为 X 轴正方向,向前为 Y 轴正方向,垂直模块向上为 Z 轴正方向。
旋转的方向按右手法则定义,即右手大拇指指向轴向,四指弯曲的方向即为绕该轴旋转的方向。
X 轴角度(滚转角 Roll)即为绕 X 轴旋转方向的角度,
Y 轴角度(俯仰角 Pitch)即为绕 Y 轴旋转方向的角度,
Z 轴角度(偏航角 Yaw)即为绕 Z 轴旋转方向的角度,
三者合称姿态角/欧拉角(Euler angles)。
一般市面上的MPU6050的引脚如图:
VCC引脚是电源的正极,通常连接到3.3V电源,而GND则是电源地。SCL和SDA分别是IIC通信中的时钟线和数据线,XDA和XCL则是外接IIC设备的对应数据线和时钟线。AD0引脚用于控制IIC从属设备的地址,接地时地址为0x68,接VCC时地址为0x69。此外,INT引脚用于中断数字输出。
本项目原理图如下:
此原理图中,AD0引脚接地所以设备地址为0X68,而SDA数据线与SCL时钟线分别与主控芯片的PB7与PB6相连,需要注意的是市面上的MPU6050芯片两根线一般都会接上拉电阻将其稳定在高电平,但是本项目中仅有SDA接了上拉电阻,因此在配置GPIO模式时,需要注意SCL时钟线需要配置为推挽输出模式。
3.2,通信方式
由其引脚可以得知,此设备是通过IIC来与控制模块来进行通信的,所以可以先将IIC软件通信的函数解决。
IIC的通信协议以及软件模拟实现在此文中便不再赘述,直接放出代码。
IIC.c软件如下:
#include "stm32f10x.h" // Device header
#include "Delay.h"
void MyI2C_W_SCL(uint8_t BitValue) //改写SCL时钟线
{
GPIO_WriteBit(GPIOB, GPIO_Pin_6, (BitAction)BitValue);
Delay_us(10);
}
void MyI2C_W_SDA(uint8_t BitValue) //改写SDA数据线
{
GPIO_WriteBit(GPIOB, GPIO_Pin_7, (BitAction)BitValue);
Delay_us(10);
}
uint8_t MyI2C_R_SDA(void) //读取SDA数据线
{
uint8_t BitValue;
BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7);
Delay_us(10);
return BitValue;
}
void MyI2C_Init(void) //IIC初始化
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB, GPIO_Pin_6 | GPIO_Pin_7);
}
void MyI2C_Start(void) //发送起始信号
{
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
MyI2C_W_SDA(0);
MyI2C_W_SCL(0);
}
void MyI2C_Stop(void) //发送结束信号
{
MyI2C_W_SDA(0);
MyI2C_W_SCL(1);
MyI2C_W_SDA(1);
}
void MyI2C_SendByte(uint8_t Byte) //发送信息
{
uint8_t i;
for (i = 0; i < 8; i ++)
{
MyI2C_W_SDA(Byte & (0x80 >> i));
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
}
uint8_t MyI2C_ReceiveByte(void) //读取信息
{
uint8_t i, Byte = 0x00;
MyI2C_W_SDA(1);
for (i = 0; i < 8; i ++)
{
MyI2C_W_SCL(1);
if (MyI2C_R_SDA() == 1){Byte |= (0x80 >> i);}
MyI2C_W_SCL(0);
}
return Byte;
}
void MyI2C_SendAck(uint8_t AckBit) //发送应答位
{
MyI2C_W_SDA(AckBit);
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
uint8_t MyI2C_ReceiveAck(void) //接受应答位
{
uint8_t AckBit;
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
AckBit = MyI2C_R_SDA();
MyI2C_W_SCL(0);
return AckBit;
}
IIC.h代码如下:
#ifndef __MYI2C_H
#define __MYI2C_H
void MyI2C_Init(void);
void MyI2C_Start(void);
void MyI2C_Stop(void);
void MyI2C_SendByte(uint8_t Byte);
uint8_t MyI2C_ReceiveByte(void);
void MyI2C_SendAck(uint8_t AckBit);
uint8_t MyI2C_ReceiveAck(void);
#endif
通过这两个函数,可是实现控制模块与6050的通信,因此只需要向其相关寄存器中发送或改写和读取相关信息则可以获取我们需要的信息。
3.3,MPU6050寄存器
通过数据手册中的MPU6050框图可以得知,MPU6050内部有六轴传感器外加一个温度传感器,可以通过内部的ADC和DMP将数据自动存入相关的数据寄存器,因此,我们需要获得相关数据,只需要将DMA初始化后,访问相关的寄存器,并读取其中的数据便可得到相关的信息。
数据手册中的寄存器很多,这里只介绍用到的相关寄存器,需要更深入了解的小伙伴可以自行观看相关数据手册。
3.3.1,加速度数据寄存器
这里是加速度计的数据寄存器,想要获取数据,只需要直接读取数据寄存器则可。这是一个十六位的有符号数,以二进制补码的形式存储,所以我们只需要读取高八位和低八位,再讲高八位左移八次或上第八位再存入int_16t的变量中,便可得到数据。
温度寄存器与陀螺仪寄存器同理,手册图如下:
3.3.2,ID寄存器
此寄存器为只读寄存器,ID号不可修改, 最高位和最低位都为0,中间六位固定位110 100,所以ID号固定为0X68.实际则就是IIC的地址,虽然MPU6050能通过AD0引脚配置IIC地址,但是此ID号并不会随之变换。
3.3.3,电源管理寄存器1
此寄存器允许用户在仅加速计低功率模式下配置唤醒频率。这个寄存器还允许用户将加速度计和陀螺仪的各个轴进入待机模式。
DEVICE_RESET(设备复位)
当设置为1时,所有寄存器都恢复到默认值。
SLEEP(睡眠模式)
当设置为1时,芯片睡眠,芯片不工作,进入低功耗模式。
CYCLE(循环模式)
当设置为1时,设备进入低功耗,过一段时间启动一次。
TEMP_DIS(温度传感器使能)
当设置为1时,禁用内部温度传感器
CLKSEL(选择系统时钟来源)
3.3.4,注意事项
注意:所有寄存器上电都默认为0X00,除了117号寄存器与107号寄存器,而117号寄存器为ID寄存器, 初始为ID号。而107号寄存器则为电源管理寄存器一,默认0X40则表示次高位默认为SLEEP也就是睡眠模式,所以在操控其他寄存器之前必须先解除睡眠模式。
3.4,具体代码实现
mpu6050_reg.h(寄存器宏定义文件):
#ifndef __MPU6050_REG_H
#define __MPU6050_REG_H
#define MPU6050_SMPLRT_DIV 0x19 //采样频率分频寄存器
#define MPU6050_CONFIG 0x1A //配置寄存器
#define MPU6050_GYRO_CONFIG 0x1B //陀螺仪配置寄存器
#define MPU6050_ACCEL_CONFIG 0x1C //加速度计配置寄存器
#define MPU6050_ACCEL_XOUT_H 0x3B //加速度寄存器X轴高八位
#define MPU6050_ACCEL_XOUT_L 0x3C //低八位
#define MPU6050_ACCEL_YOUT_H 0x3D //加速度寄存器Y轴高八位
#define MPU6050_ACCEL_YOUT_L 0x3E
#define MPU6050_ACCEL_ZOUT_H 0x3F //加速度寄存器Z轴高八位
#define MPU6050_ACCEL_ZOUT_L 0x40
#define MPU6050_TEMP_OUT_H 0x41 //温度寄存器高八位
#define MPU6050_TEMP_OUT_L 0x42
#define MPU6050_GYRO_XOUT_H 0x43 //陀螺仪寄存器X轴高八位
#define MPU6050_GYRO_XOUT_L 0x44
#define MPU6050_GYRO_YOUT_H 0x45 //陀螺仪寄存器Y轴高八位
#define MPU6050_GYRO_YOUT_L 0x46
#define MPU6050_GYRO_ZOUT_H 0x47 //陀螺仪寄存器Z轴高八位
#define MPU6050_GYRO_ZOUT_L 0x48
#define MPU6050_PWR_MGMT_1 0x6B //电源管理器1
#define MPU6050_PWR_MGMT_2 0x6C
#define MPU6050_WHO_AM_I 0x75 //ID寄存器
#endif
MPU6050.C:
#include "stm32f10x.h" // Device header
#include "MyI2C.h"
#include "MPU6050_Reg.h"
#define MPU6050_ADDRESS 0xD0 //从机地址
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data) //指定地址写一个字节
{
MyI2C_Start();
MyI2C_SendByte(MPU6050_ADDRESS);
MyI2C_ReceiveAck();
MyI2C_SendByte(RegAddress);
MyI2C_ReceiveAck();
MyI2C_SendByte(Data);
MyI2C_ReceiveAck();
MyI2C_Stop();
}
uint8_t MPU6050_ReadReg(uint8_t RegAddress) //指定地址读一个字节
{
uint8_t Data;
MyI2C_Start();
MyI2C_SendByte(MPU6050_ADDRESS);
MyI2C_ReceiveAck();
MyI2C_SendByte(RegAddress);
MyI2C_ReceiveAck();
MyI2C_Start();
MyI2C_SendByte(MPU6050_ADDRESS | 0x01);
MyI2C_ReceiveAck();
Data = MyI2C_ReceiveByte();
MyI2C_SendAck(1);
MyI2C_Stop();
return Data;
}
void MPU6050_Init(void) //mpu6050初始化
{
MyI2C_Init();
MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01); //电源管理寄存器配置
MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);
MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09); //采样率分频寄存器
MPU6050_WriteReg(MPU6050_CONFIG, 0x06); //配置寄存器
MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18); // 陀螺仪配置寄存器
MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18); //加速度计配置寄存器 满量程16G
}
uint8_t MPU6050_GetID(void)
{
return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}
//通过指针进行变量地址传递,实现多返回值
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{
uint8_t DataH, DataL;
DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
*AccX = (DataH << 8) | DataL;
DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
*AccY = (DataH << 8) | DataL;
DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
*AccZ = (DataH << 8) | DataL;
DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
*GyroX = (DataH << 8) | DataL;
DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
*GyroY = (DataH << 8) | DataL;
DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
*GyroZ = (DataH << 8) | DataL;
}
//所测数据为16位,而满量程配置为16 则所测数据/32768 = X/16
MPU6050.h:
#ifndef __MPU6050_H
#define __MPU6050_H
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data);
uint8_t MPU6050_ReadReg(uint8_t RegAddress);
void MPU6050_Init(void);
uint8_t MPU6050_GetID(void);
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ);
#endif
3.5,姿态解算
MPU6050并不能直接得出飞行器的欧拉角,而只能获取到三轴角速度和三轴角加速度,因此需要使用其自带的DMP去解算此时飞行器的欧拉角。DMP(Digital Motion Processor)是 MPU6050 中的一个关键特性,它能够处理传感器的数据并进行融合,从而提供更为稳定和准确的姿态信息。
其优点有:
-
数据融合处理:
DMP 可以算法融合来自加速度计和陀螺仪的数据,提供更准确的姿态信息。通过结合多个传感器的数据,可以有效减少噪声和漂移问题。 -
减轻主控 CPU 负担:
由于 DMP 在硬件层面处理数据,主控 CPU 可以释放出来更多的资源进行其他任务,从而提高整个系统的性能和响应速度。 -
实时处理:
DMP 有较快的数据处理能力,可以在几毫秒内处理多帧数据,适合实时应用,如运动控制和姿态跟踪。 -
多种输出格式:
DMP 支持多种输出格式(如角度、四元数等),使开发者可以根据应用需求灵活选择,简化了数据处理过程。
而其缺点则是数据的精度不够高,在某些需要高精度的领域可能会有些吃力。同时十分容易收到外界各种因素的干扰。
具体的移植过程可以参考:MPU6050+DMP获取姿态解析_哔哩哔哩_bilibili
成功移植后所需文件如下:
最后在main函数中调用获取函数后可以的到:Pitch(俯仰角),Roll(翻滚角),Yaw(偏航角)
4,气压计
本设计中使用的是SPL06作为气压计,SPL06是一款高精度的气压传感器,主要用于测量气压和高度。它通常被应用于气象监测、无人机、汽车、便携式设备等领域。以下是一些关于SPL06气压计的主要特点:
-
高精度:SPL06气压计提供高分辨率的气压测量,可以实现精确的高度计算。
-
小型化设计:SPL06通常设计为小型封装,便于集成到各种设备中。
-
低功耗:该传感器在工作时功耗较低,适合电池供电的应用,如可穿戴设备和便携式仪器。
-
数字输出:SPL06通常通过I2C或SPI接口提供数字输出,简化了与微控制器或其他处理器的通信。
-
温度补偿:一些型号的SPL06气压计具备温度补偿功能,从而提高了在不同环境条件下的测量精度。
-
应用场景:广泛用于天气预报、飞行器高度测量、室内环境监测等。
其原理图如下所示:
SPL与MPU6050使用方法类似,都是IIC通信,寄存器操作也相差不大,可对照相关数据手册完成,在此不多赘述,直接展示具体代码。
SPL寄存器表格:
SPL.c:
#include "SPL06.h"
void SPL06_Write_Byte(u8 addr,u8 data)
{
IIC_Start();
IIC_Send_Byte(SPL06_Write);
IIC_Wait_Ack();
IIC_Send_Byte(addr);
IIC_Wait_Ack();
IIC_Send_Byte(data);
IIC_Wait_Ack();
IIC_Stop();
}
u8 SPL06_Read_Byte(u8 addr)
{
u8 SPL06_Data;
IIC_Start();
IIC_Send_Byte(SPL06_Write);
IIC_Wait_Ack();
IIC_Send_Byte(addr);
u8 i = IIC_Wait_Ack();
IIC_Send_Byte(SPL06_Read);
IIC_Wait_Ack();
SPL06_Data = IIC_Read_Byte(0); //Í£Ö¹ÓëÆøѹ¼ÆͨÐÅ
IIC_Stop();
return SPL06_Data;
}
u8 SPL06_Init(void)
{
u8 SPL06_ID;
SPL06_Write_Byte(RESET_Addr,0x89);//Reset
Delay_ms(100);
SPL06_ID = SPL06_Read_Byte(ID_Addr);//Read the SPL06's ID
SPL06_Write_Byte(MEAS_CFG_Addr,0x07);//Set Working mode and state of sensor
SPL06_Write_Byte(PRS_CFG_Addr,0x27);//Set the PM-RATE and PM-PRC
SPL06_Write_Byte(TMP_CFG_Addr,0xA0);//Set the TMPI-RATE and TMP-PRC
SPL06_Write_Byte(CFG_REG_Addr,0x04);//Configuration of abort, measurement data shift and FIFO enable
return SPL06_ID;
}
float Temperature_conversion(u32 Temp_Data,float k)
{
float Temperature;
int Temp;
if(Temp_Data&0x800000)
{
Temp = Temp_Data-Total_Number_24;
}
else
{
Temp = Temp_Data;
}
Temperature = Temp/k;
return Temperature;
}
float Pressure_conversion(u32 Pressure_Data,float k)
{
float Pressure;
int Press;
if(Pressure_Data&0x800000)
{
Press = Pressure_Data-Total_Number_24;
}
else
{
Press = Pressure_Data;
}
Pressure = Press/k;
return Pressure;
}
float Scale_factor(u8 Config_k)
{
float k;
switch(Config_k)
{
case 0: k = k_SPS1;break;
case 1: k = k_SPS2;break;
case 2: k = k_SPS4;break;
case 3: k = k_SPS8;break;
case 4: k = k_SPS16;break;
case 5: k = k_SPS32;break;
case 6: k = k_SPS64;break;
case 7: k = k_SPS128;break;
}
return k;
}
void Parameter_Reading(int *Pressure_Para,int *Temperature_Para)
{
u8 Temp_Config0,Temp_Config1,Temp_Config2;
u8 Press_Config0,Press_Config1,Press_Config2,Press_Config3,Press_Config4;
u8 Press_Config5,Press_Config6,Press_Config7,Press_Config8,Press_Config9;
u8 Press_Config10,Press_Config11,Press_Config12,Press_Config13,Press_Config14;
//Temperature
Temp_Config0 = SPL06_Read_Byte(Temp_c0_Addr);
Temp_Config1 = SPL06_Read_Byte(Temp_c1_Addr);
Temp_Config2 = SPL06_Read_Byte(Temp_c2_Addr);
Temperature_Para[0] = (Temp_Config0<<4)+((Temp_Config1&0xF0)>>4);
if(Temperature_Para[0]&0x0800) Temperature_Para[0] = Temperature_Para[0]-Total_Number_12;
Temperature_Para[1] = ((Temp_Config1&0x0F)<<8)+Temp_Config2;
if(Temperature_Para[1]&0x0800) Temperature_Para[1] = Temperature_Para[1]-Total_Number_12;
//Pressure
Press_Config0 = SPL06_Read_Byte(Press_c0_Addr);
Press_Config1 = SPL06_Read_Byte(Press_c1_Addr);
Press_Config2 = SPL06_Read_Byte(Press_c2_Addr);
Press_Config3 = SPL06_Read_Byte(Press_c3_Addr);
Press_Config4 = SPL06_Read_Byte(Press_c4_Addr);
Press_Config5 = SPL06_Read_Byte(Press_c5_Addr);
Press_Config6 = SPL06_Read_Byte(Press_c6_Addr);
Press_Config7 = SPL06_Read_Byte(Press_c7_Addr);
Press_Config8 = SPL06_Read_Byte(Press_c8_Addr);
Press_Config9 = SPL06_Read_Byte(Press_c9_Addr);
Press_Config10 = SPL06_Read_Byte(Press_c10_Addr);
Press_Config11 = SPL06_Read_Byte(Press_c11_Addr);
Press_Config12 = SPL06_Read_Byte(Press_c12_Addr);
Press_Config13 = SPL06_Read_Byte(Press_c13_Addr);
Press_Config14 = SPL06_Read_Byte(Press_c14_Addr);
Pressure_Para[0] = (Press_Config0<<12)+(Press_Config1<<4)+((Press_Config2&0xF0)>>4);//c00
if(Pressure_Para[0]&0x80000) Pressure_Para[0] = Pressure_Para[0] - Total_Number_20;//c00
Pressure_Para[1] = ((Press_Config2&0x0F)<<16)+ (Press_Config3<<8)+ Press_Config4;//c10
if(Pressure_Para[1]&0x80000) Pressure_Para[1] = Pressure_Para[1] - Total_Number_20;//c10
Pressure_Para[2] = (Press_Config5<<8)+Press_Config6;//c01
if(Pressure_Para[2]&0x8000) Pressure_Para[2] = Pressure_Para[2] - Total_Number_16;//c01
Pressure_Para[3] = (Press_Config7<<8)+Press_Config8;//c11
if(Pressure_Para[3]&0x8000) Pressure_Para[3] = Pressure_Para[3] - Total_Number_16;//c11
Pressure_Para[4] = (Press_Config9<<8)+Press_Config10;//c20
if(Pressure_Para[4]&0x8000) Pressure_Para[4] = Pressure_Para[4] - Total_Number_16;//c20
Pressure_Para[5] = (Press_Config11<<8)+Press_Config12;//c21
if(Pressure_Para[5]&0x8000) Pressure_Para[5] = Pressure_Para[5] - Total_Number_16;//c21
Pressure_Para[6] = (Press_Config13<<8)+Press_Config14;//c30
if(Pressure_Para[6]&0x8000) Pressure_Para[6] = Pressure_Para[6] - Total_Number_16;//c30
}
float Correcting_Pressure(int *Pressure_Para,float Pressure,float Temperature)
{
float Corr_Pressure;
Corr_Pressure = Pressure_Para[0]+ Pressure*(Pressure_Para[1]+Pressure*(Pressure_Para[4]+Pressure*Pressure_Para[6]))+Temperature*Pressure_Para[2]+Temperature*Pressure*(Pressure_Para[3]+Pressure*Pressure_Para[5]);
return Corr_Pressure;
}
float Correcting_Temperature(int *Temperature_Para,float Temperature)
{
float Corr_Temperature;
Corr_Temperature = Temperature_Para[0]*0.5+Temperature_Para[1]*Temperature;
return Corr_Temperature;
}
SPL.h:
#ifndef __SPL06_H
#define __SPL06_H
#include "myiic.h"
#include "sys.h"
#include "Delay.h"
#include "stdio.h"
#define SPL06_Write 0XEE
#define SPL06_Read 0xEF
#define k_SPS1 524288.0
#define k_SPS2 1572864.0
#define k_SPS4 3670016.0
#define k_SPS8 7864320.0
#define k_SPS16 253952.0
#define k_SPS32 516096.0
#define k_SPS64 1040384.0
#define k_SPS128 2088960.0
#define PSR_B2_Addr 0x00
#define PSR_B1_Addr 0x01
#define PSR_B0_Addr 0x02
#define TMP_B2_Addr 0x03
#define TMP_B1_Addr 0x04
#define TMP_B0_Addr 0x05
#define PRS_CFG_Addr 0x06
#define TMP_CFG_Addr 0x07
#define MEAS_CFG_Addr 0x08
#define CFG_REG_Addr 0x09
#define RESET_Addr 0x0C
#define ID_Addr 0x0D
#define Temp_c0_Addr 0x10
#define Temp_c1_Addr 0x11
#define Temp_c2_Addr 0x12
#define Press_c0_Addr 0x13
#define Press_c1_Addr 0x14
#define Press_c2_Addr 0x15
#define Press_c3_Addr 0x16
#define Press_c4_Addr 0x17
#define Press_c5_Addr 0x18
#define Press_c6_Addr 0x19
#define Press_c7_Addr 0x1A
#define Press_c8_Addr 0x1B
#define Press_c9_Addr 0x1C
#define Press_c10_Addr 0x1D
#define Press_c11_Addr 0x1E
#define Press_c12_Addr 0x1F
#define Press_c13_Addr 0x20
#define Press_c14_Addr 0x21
#define Total_Number_24 16777216.0
#define Total_Number_20 1048576.0
#define Total_Number_16 65536.0
#define Total_Number_12 4096.0
u8 SPL06_Init(void);
u8 SPL06_Read_Byte(u8 addr);
void SPL06_Write_Byte(u8 addr,u8 data);
void Parameter_Reading(int *Pressure_Para,int *Temperature_Para);
float Temperature_conversion(u32 Temp_Data,float k);
float Pressure_conversion(u32 Pressure_Data,float k);
float Scale_factor(u8 Config_k);
float Correcting_Pressure(int *Pressure_Para,float Pressure,float Temperature);
float Correcting_Temperature(int *Temperature_Para,float Temperature);
#endif
其IIC函数与MPU6050基本一致,在此不在重复贴出。
5,2.4G通信
2.4G无线通信一般是通过两个设备进行通信,最高速率可达到2Mbps,抗干扰能力强。一般的可以进行1对6的通信,一般是1个接收,6个发送。在本设计中主要用于无人机与手柄端的通信。
封装图如下:
引脚说明:
通过以下六个引脚,便可实现模块的所有功能:
(1)MOSI:主输从入。
(2)MISO:主入从输。
(3)SCLK:时钟信号。
(4) CSN :片选线。
(5)CE:芯片使能,使能器件的发送模式或者接收模式。高电平有效,在发送和接收过程中都要将这个引脚拉高。
(6)IRQ:中断信号线,中断输出。低电平有效,中断时变为低电平。
(7)VCC:电压范围1.9V~3.6V,一般使用电压为3.3V。
其工作模式由CE,PWR_UP,PRIM_RX共同决定,所以其工作模式有收发模式,配置模式,空闲模式,关机模式四种。其中收发模式又有: Enhanced ShockBurstTM收发模式和ShockBurstTM收发模式,只有Enhanced ShockBurstTM收发模式支持自动ACK和自动重发。所以一般都是使用的Enhanced ShockBurstTM收发模式。
发送流程:
Enhanced ShockBurstTM发送流程
1.把地址和要发送的数据按时序送入NRF24L01;
2.配置CONFIG寄存器,使之进入发送模式;
3.微控制器把CE置高(至少10us),激发Enhanced ShockBurstTM发射;
4. 发射完成,NRF24L01进入空闲状态。
初始化NRF24L01到TX模式
1) CE置低
2) 写Tx节点的地址
3) 写Rx节点的地址,使能自动应答
4) 使能通道x的自动应答
5) 使能通道x的接收地址
6) 设置自动重发间隔时间和最大自动重发次数
7) 设置RF通道
8) 配置TX发射参数(低噪放大器增益、发射功率、无线速率)
9) 配置基本工作模式的参数
10)CE拉高,进入发送模式,注意CE要拉高一段时间才进入发送模式
接收流程:
Enhanced ShockBurstTM接收流程
1.配置接收地址和要接收的数据包大小;
2.配置CONFIG寄存器,使之进入接收模式,把CE置高;
3. 130us后,NRF24LO1进入监视状态,等待数据包的到来;
4.当接收到正确的数据包(正确的地址和CRC校验码),NRF2401自动把字头、地址和CRC校验位移去;
5.NRF24LO1通过把STATUS寄存器的RX_DR置位(STATUS一般引起微控制器中断)通知微控制器;
6.微控制器把数据从FIFO读出(0X61指令);
7.所有数据读取完毕后,可以清除STATUS寄存器。进入四种主要的模式之—。
初始化NRF24L01到RX模式
1) CE置低
2)写RX节点地址
3)使能通道x的自动应答
4)使能通道0的接收地址
5)设置RF通信频率
6)选择通道x的有效数据宽度
7)设置TX发射参数
8)配置基本工作模式的参数
9)CE拉高,进入接收模式
发送接收流程参考于:原文链接:https://blog.csdn.net/weixin_52801934/article/details/126136217
初始化IO口代码:
//初始化24L01的IO口
void NRF24L01_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOC, ENABLE );
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_SetBits(GPIOC,GPIO_Pin_4);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU ; //上拉输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA,GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4);
SPI1_Init(); //初始化SPI
SPI_Cmd(SPI1, DISABLE); //
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置SPI工作模式:设置为主SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //设置SPI的数据大小:SPI发送接收8位帧结构
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //选择了串行时钟的稳态:时钟悬空低电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; //数据捕获于第一个时钟沿
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //定义波特率预分频的值:波特率预分频值为256
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值计算的多项式
SPI_Init(SPI1, &SPI_InitStructure); //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
NRF24L01_CE=0; //使能24L01
NRF24L01_CSN=1; //SPI片选取消
}
发送流程 :
//Enhanced ShockBurstTM发送流程
//启动NRF24L01发送一次数据
//txbuf:待发送数据首地址
//返回值:发送完成状况
u8 NRF24L01_TxPacket(u8 *txbuf)
{
u8 sta;
SPI1_SetSpeed(SPI_BaudRatePrescaler_8);//spi速度为9Mhz(24L01的最大SPI时钟为10Mhz)
NRF24L01_CE=0;
NRF24L01_Write_Buf(WR_TX_PLOAD,txbuf,TX_PLOAD_WIDTH);//写数据到TX BUF 32个字节
NRF24L01_CE=1;//启动发送
while(NRF24L01_IRQ!=0);//等待发送完成
sta=NRF24L01_Read_Reg(STATUS); //读取状态寄存器的值
NRF24L01_Write_Reg(NRF_WRITE_REG+STATUS,sta); //清除TX_DS或MAX_RT中断标志
if(sta&MAX_TX)//达到最大重发次数
{
NRF24L01_Write_Reg(FLUSH_TX,0xff);//清除TX FIFO寄存器
return MAX_TX;
}
if(sta&TX_OK)//发送完成
{
return TX_OK;
}
return 0xff;//其他原因发送失败
}
//Enhanced ShockBurstTM发送模式初始化
//该函数初始化NRF24L01到TX模式
//设置TX地址,写TX数据宽度,设置RX自动应答的地址,填充TX发送数据,选择RF频道,波特率和LNA HCURR
//PWR_UP,CRC使能
//当CE变高后,即进入RX模式,并可以接收数据了
//CE为高大于10us,则启动发送.
void NRF24L01_TX_Mode(void)
{
NRF24L01_CE=0;
NRF24L01_Write_Buf(NRF_WRITE_REG+TX_ADDR,(u8*)TX_ADDRESS,TX_ADR_WIDTH);//写TX节点地址
NRF24L01_Write_Buf(NRF_WRITE_REG+RX_ADDR_P0,(u8*)RX_ADDRESS,RX_ADR_WIDTH); //设置TX节点地址,主要为了使能ACK
NRF24L01_Write_Reg(NRF_WRITE_REG+EN_AA,0x01); //使能通道0的自动应答
NRF24L01_Write_Reg(NRF_WRITE_REG+EN_RXADDR,0x01); //使能通道0的接收地址
NRF24L01_Write_Reg(NRF_WRITE_REG+SETUP_RETR,0x1a);//设置自动重发间隔时间:500us + 86us;最大自动重发次数:10次
NRF24L01_Write_Reg(NRF_WRITE_REG+RF_CH,40); //设置RF通道为40
NRF24L01_Write_Reg(NRF_WRITE_REG+RF_SETUP,0x0f); //设置TX发射参数,0db增益,2Mbps,低噪声增益开启
NRF24L01_Write_Reg(NRF_WRITE_REG+CONFIG,0x0e); //配置基本工作模式的参数;PWR_UP,EN_CRC,16BIT_CRC,接收模式,开启所有中断
NRF24L01_CE=1;//CE为高,10us后启动发送
}
接收流程:
//启动NRF24L01发送一次数据
//txbuf:待发送数据首地址
//返回值:0,接收完成;其他,错误代码
u8 NRF24L01_RxPacket(u8 *rxbuf)
{
u8 sta;
SPI1_SetSpeed(SPI_BaudRatePrescaler_8); //spi速度为9Mhz(24L01的最大SPI时钟为10Mhz)
sta=NRF24L01_Read_Reg(STATUS); //读取状态寄存器的值
NRF24L01_Write_Reg(NRF_WRITE_REG+STATUS,sta); //清除TX_DS或MAX_RT中断标志
if(sta&RX_OK)//接收到数据
{
NRF24L01_Read_Buf(RD_RX_PLOAD,rxbuf,RX_PLOAD_WIDTH);//读取数据
NRF24L01_Write_Reg(FLUSH_RX,0xff);//清除RX FIFO寄存器
return 0;
}
return 1;//没收到任何数据
}
//Enhanced ShockBurstTM接收模式初始化
//该函数初始化NRF24L01到RX模式
//设置RX地址,写RX数据宽度,选择RF频道,波特率和LNA HCURR
//当CE变高后,即进入RX模式,并可以接收数据了
void NRF24L01_RX_Mode(void)
{
NRF24L01_CE=0;
NRF24L01_Write_Buf(NRF_WRITE_REG+RX_ADDR_P0,(u8*)RX_ADDRESS,RX_ADR_WIDTH);//写RX节点地址
NRF24L01_Write_Reg(NRF_WRITE_REG+EN_AA,0x01); //使能通道0的自动应答
NRF24L01_Write_Reg(NRF_WRITE_REG+EN_RXADDR,0x01); //使能通道0的接收地址
NRF24L01_Write_Reg(NRF_WRITE_REG+RF_CH,40); //设置RF通信频率
NRF24L01_Write_Reg(NRF_WRITE_REG+RX_PW_P0,RX_PLOAD_WIDTH);//选择通道0的有效数据宽度
NRF24L01_Write_Reg(NRF_WRITE_REG+RF_SETUP,0x0f); //设置TX发射参数,0db增益,2Mbps,低噪声增益开启
NRF24L01_Write_Reg(NRF_WRITE_REG+CONFIG, 0x0f); //配置基本工作模式的参数;PWR_UP,EN_CRC,16BIT_CRC,接收模式
NRF24L01_CE = 1; //CE为高,进入接收模式
}
读写函数:
//读取SPI寄存器值
//reg:要读的寄存器
u8 NRF24L01_Read_Reg(u8 reg)
{
u8 reg_val;
NRF24L01_CSN = 0; //使能SPI传输
SPI1_ReadWriteByte(reg); //发送寄存器号
reg_val=SPI1_ReadWriteByte(0XFF);//读取寄存器内容
NRF24L01_CSN = 1; //禁止SPI传输
return(reg_val); //返回状态值
}
//在指定位置读出指定长度的数据
//reg:寄存器(位置)
//*pBuf:数据指针
//len:数据长度
//返回值,此次读到的状态寄存器值
u8 NRF24L01_Read_Buf(u8 reg,u8 *pBuf,u8 len)
{
u8 status,u8_ctr;
NRF24L01_CSN = 0; //使能SPI传输
status=SPI1_ReadWriteByte(reg);//发送寄存器值(位置),并读取状态值
for(u8_ctr=0;u8_ctr<len;u8_ctr++)pBuf[u8_ctr]=SPI1_ReadWriteByte(0XFF);//读出数据
NRF24L01_CSN=1; //关闭SPI传输
return status; //返回读到的状态值
}
//SPI写寄存器
//reg:指定寄存器地址
//value:写入的值
u8 NRF24L01_Write_Reg(u8 reg,u8 value)
{
u8 status;
NRF24L01_CSN=0; //使能SPI传输
status =SPI1_ReadWriteByte(reg);//发送寄存器号
SPI1_ReadWriteByte(value); //写入寄存器的值
NRF24L01_CSN=1; //禁止SPI传输
return(status); //返回状态值
}
//在指定位置写指定长度的数据
//reg:寄存器(位置)
//*pBuf:数据指针
//len:数据长度
//返回值,此次读到的状态寄存器值
u8 NRF24L01_Write_Buf(u8 reg, u8 *pBuf, u8 len)
{
u8 status,u8_ctr;
NRF24L01_CSN = 0; //使能SPI传输
status = SPI1_ReadWriteByte(reg);//发送寄存器值(位置),并读取状态值
for(u8_ctr=0; u8_ctr<len; u8_ctr++)SPI1_ReadWriteByte(*pBuf++); //写入数据
NRF24L01_CSN = 1; //关闭SPI传输
return status; //返回读到的状态值
}
6,飞行器总设计图
主控板原理图:
主控板PCB:
电源板原理图:
电源板PCB:
三,遥控端
遥控端采用CUBEMX配置,外加FREERTOS操作系统调度控制。有时间单独更新一篇帖子,在此先贴出原理图欢迎大家纠错。
原理图:
PCB图:
实物图:
更多推荐
所有评论(0)