一、实现效果
基于STM32的手势检测遥控车
二、简单介绍
遥控器:使用stm32f103c8t6作为主控,mpu6050检测当前姿态,并进行姿态解算,然后得到指令,通过nrf24l01无线传输,将命令发送到小车端。
小车端:使用stm32f103rct6作为主控,当nrf24l01接收到前进命令时,通过定时器发出pwm波形,通过L9110四路驱动模块驱动电机;当接收到转弯命令时,发出pwm波形控制SG90舵机转弯。
三、硬件
STM32F103C8T6、STM32F103RCT6、MPU6050、NRF24L01x2、N20直流减速电机x4、SG90舵机、L9110四路电机驱动。
四、原理实现
1、MPU6050
1.1 姿态解算
从MPU6050直接读取的数据是3轴的加速度和角速度,必须把他们转化为欧拉角,才能进行下一步操作。
使用四元数求出欧拉角:
1、初始化单位四元数 q =(1,0,0,0)表示没有旋转。
2、根据读取mpu6050的角速度更新角速度四元数ω=(0,ωx,ωy,ωz)。
3、更新四元数
其中,Δt 是时间步长,ωk 是当前时间步长的角速度四元数,⊗ 是四元数乘法。
四元数运算:q⊗ω=(q0ω0−q1ω1−q2ω2−q3ω3, q0ω1+q1ω0+q2ω3−q3ω2, q0ω2−q1ω3+q2ω0+q3ω1, q0ω3+q1ω2−q2ω1+q3ω0)
4、对获取的四元数进行归一化处理q = q/sqrt(q0^2+q1^2+q2^2+q3^2)
5、转化为欧拉角
6、使用互补滤波融合加速度和角速度,α 是互补滤波的系数,通常取值在 0.95 到 0.98 之间
7、再更新欧拉角
8、使用python模拟计算欧拉角
import numpy as np
def quaternion_multiply(q1, q2):
w1, x1, y1, z1 = q1
w2, x2, y2, z2 = q2
return np.array([
w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2,
w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2,
w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2,
w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2
])
def normalize_quaternion(q):
return q / np.linalg.norm(q)
def update_quaternion(q, omega, dt):
omega_quat = np.array([0, omega[0], omega[1], omega[2]])
dq = 0.5 * quaternion_multiply(q, omega_quat) * dt
q_new = q + dq
return normalize_quaternion(q_new)
def quaternion_to_euler(q):
w, x, y, z = q
roll = np.arctan2(2 * (w * x + y * z), 1 - 2 * (x**2 + y**2))
pitch = np.arcsin(2 * (w * y - z * x))
yaw = np.arctan2(2 * (w * z + x * y), 1 - 2 * (y**2 + z**2))
return np.array([yaw, pitch, roll])
def complementary_filter(accel, gyro, q, dt, alpha=0.98):
# 加速度计估计俯仰和横滚角
pitch_acc = np.arctan2(accel[0], np.sqrt(accel[1]**2 + accel[2]**2))
roll_acc = np.arctan2(accel[1], np.sqrt(accel[0]**2 + accel[2]**2))
# 使用角速度数据更新四元数
q = update_quaternion(q, gyro, dt)
# 从四元数计算欧拉角
euler_angles = quaternion_to_euler(q)
yaw, pitch_gyro, roll_gyro = euler_angles
# 互补滤波
pitch = alpha * (pitch_gyro + gyro[1] * dt) + (1 - alpha) * pitch_acc
roll = alpha * (roll_gyro + gyro[0] * dt) + (1 - alpha) * roll_acc
return np.array([yaw, pitch, roll]), q
# 初始化四元数
q = np.array([1, 0, 0, 0])
# 时间步长
dt = 0.01
# 示例传感器数据
accel = np.array([0, 0, -1]) # 静止状态下的加速度计数据,假设Z轴指向地心
gyro = np.array([0.1, 0.2, 0.3]) # 角速度数据 (rad/s)
# 计算欧拉角
euler_angles, q = complementary_filter(accel, gyro, q, dt)
print("Yaw, Pitch, Roll:", euler_angles)
9、目前keil运用的是DMP库移植,可以省去时间。
1.2 MPU6050通过I2C读取
1.2.1 内部框图
1.2.2 地址码和命令码
// MPU6050 I2C地址
#define MPU6050_ADDR 0x68 // 当 AD0 引脚接地时为 0x68, 接 Vcc 时为 0x69
// MPU6050寄存器地址
#define MPU6050_SELF_TEST_X 0x0D
#define MPU6050_SELF_TEST_Y 0x0E
#define MPU6050_SELF_TEST_Z 0x0F
#define MPU6050_SELF_TEST_A 0x10
#define MPU6050_SMPLRT_DIV 0x19
#define MPU6050_CONFIG 0x1A
#define MPU6050_GYRO_CONFIG 0x1B
#define MPU6050_ACCEL_CONFIG 0x1C
#define MPU6050_FIFO_EN 0x23
#define MPU6050_I2C_MST_CTRL 0x24
#define MPU6050_I2C_SLV0_ADDR 0x25
#define MPU6050_I2C_SLV0_REG 0x26
#define MPU6050_I2C_SLV0_CTRL 0x27
#define MPU6050_I2C_SLV1_ADDR 0x28
#define MPU6050_I2C_SLV1_REG 0x29
#define MPU6050_I2C_SLV1_CTRL 0x2A
#define MPU6050_I2C_SLV2_ADDR 0x2B
#define MPU6050_I2C_SLV2_REG 0x2C
#define MPU6050_I2C_SLV2_CTRL 0x2D
#define MPU6050_I2C_SLV3_ADDR 0x2E
#define MPU6050_I2C_SLV3_REG 0x2F
#define MPU6050_I2C_SLV3_CTRL 0x30
#define MPU6050_I2C_SLV4_ADDR 0x31
#define MPU6050_I2C_SLV4_REG 0x32
#define MPU6050_I2C_SLV4_DO 0x33
#define MPU6050_I2C_SLV4_CTRL 0x34
#define MPU6050_I2C_SLV4_DI 0x35
#define MPU6050_I2C_MST_STATUS 0x36
#define MPU6050_INT_PIN_CFG 0x37
#define MPU6050_INT_ENABLE 0x38
#define MPU6050_INT_STATUS 0x3A
#define MPU6050_ACCEL_XOUT_H 0x3B
#define MPU6050_ACCEL_XOUT_L 0x3C
#define MPU6050_ACCEL_YOUT_H 0x3D
#define MPU6050_ACCEL_YOUT_L 0x3E
#define MPU6050_ACCEL_ZOUT_H 0x3F
#define MPU6050_ACCEL_ZOUT_L 0x40
#define MPU6050_TEMP_OUT_H 0x41
#define MPU6050_TEMP_OUT_L 0x42
#define MPU6050_GYRO_XOUT_H 0x43
#define MPU6050_GYRO_XOUT_L 0x44
#define MPU6050_GYRO_YOUT_H 0x45
#define MPU6050_GYRO_YOUT_L 0x46
#define MPU6050_GYRO_ZOUT_H 0x47
#define MPU6050_GYRO_ZOUT_L 0x48
#define MPU6050_I2C_MST_DELAY_CTRL 0x67
#define MPU6050_SIGNAL_PATH_RESET 0x68
#define MPU6050_USER_CTRL 0x6A
#define MPU6050_PWR_MGMT_1 0x6B
#define MPU6050_PWR_MGMT_2 0x6C
#define MPU6050_FIFO_COUNTH 0x72
#define MPU6050_FIFO_COUNTL 0x73
#define MPU6050_FIFO_R_W 0x74
#define MPU6050_WHO_AM_I 0x75
1.2.3 读取寄存器值
uint8_t mpu6050_write(uint8_t addr, uint8_t reg, uint8_t len, uint8_t* buf)//返回值 0:读成功 -1:读失败
{
unsigned char i;
addr=addr<<1; //注意dma库地址不包含最后一位,需要移位
MPU6050_IIC_Start(); //启动总线
MPU6050_IIC_Send_Byte(addr); //发送器件地址
MPU6050_IIC_Send_Byte(reg); //发送器件子地址
for(i=0;i<len;i++)
MPU6050_IIC_Send_Byte(*buf++); //发送数据
MPU6050_IIC_Stop(); //结束总线
return 0;
}
uint8_t mpu6050_read(uint8_t addr, uint8_t reg, uint8_t len, uint8_t *buf)//返回值 0:读成功 -1:读失败
{
unsigned char i;
addr=addr<<1; //注意dma库地址不包含最后一位,需要移位
MPU6050_IIC_Start(); //启动总线
MPU6050_IIC_Send_Byte(addr); //发送器件地址
MPU6050_IIC_Send_Byte(reg); //发送器件子地址
MPU6050_IIC_Start(); //重新启动总线
MPU6050_IIC_Send_Byte(addr+1);
for(i=0;i<len-1;i++)
*buf++=MPU6050_IIC_Read_Byte(0);//发送数据
*buf=MPU6050_IIC_Read_Byte(1);
MPU6050_IIC_Stop(); //结束总线
return 0;
}
void mpu6050_write_reg(uint8_t reg, uint8_t dat)
{
mpu6050_write(SlaveAddress,reg,1,&dat);
}
uint8_t mpu6050_read_reg (uint8_t reg)
{
uint8_t dat;
mpu6050_read(SlaveAddress,reg,1,&dat);
return dat;
}
void MPU6050_Init(void)
{
MPU6050_IIC_IO_Init();
mpu6050_write_reg(MPU6050_PWR_MGMT_1, 0X00); //唤醒MPU6050
mpu6050_write_reg(MPU6050_SMPLRT_DIV, 0x07); //陀螺仪采样率125Hz
mpu6050_write_reg(MPU6050_CONFIG, 0x06); //低通滤波频率5Hz
mpu6050_write_reg(MPU6050_GYRO_CONFIG, 0x18); //陀螺仪自检及测量范围:不自检,2000deg/s
mpu6050_write_reg(MPU6050_ACCEL_CONFIG,0x01); //加速计自检、测量范围及高通滤波频率:不自检,2G,5Hz
}
2、NRF24L01发送与接收
2.1 地址寄存器以及命令码
//寄存器地址代码
#define CONFIG 0x00
#define EN_AA 0x01
#define EN_RXADDR 0x02
#define SETUP_AW 0x03
#define SETUP_RETR 0x04
#define RF_CH 0x05
#define RF_SETUP 0x06
#define STATUS 0x07
#define OBSERVE_TX 0x08
#define CD 0x09
#define RX_ADDR_P0 0x0A
#define RX_ADDR_P1 0x0B
#define RX_ADDR_P2 0x0C
#define RX_ADDR_P3 0x0D
#define RX_ADDR_P4 0x0E
#define RX_ADDR_P5 0x0F
#define TX_ADDR 0x10
#define RX_PW_P0 0x11
#define RX_PW_P1 0x12
#define RX_PW_P2 0x13
#define RX_PW_P3 0x14
#define RX_PW_P4 0x15
#define RX_PW_P5 0x16
#define FIFO_STATUS 0x17
#define DYNPD 0x1C
#define FEATURE 0x1D
//操作指令代码
#define R_REGISTER 0x00
#define W_REGISTER 0x20
#define R_RX_PAYLOAD 0x61
#define W_TX_PAYLOAD 0xA0
#define FLUSH_TX 0xE1
#define FLUSH_RX 0xE2
#define NOP 0xFF
2.2 发送与接收
uint8_t T_ADDR[5] = {0xF0, 0xF0, 0xF0, 0xF0, 0xF0};
uint8_t R_ADDR[5] = {0xF0, 0xF0, 0xF0, 0xF0, 0xF0};
void NRF24L01_Pin_Init(void){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Pin = MOSI_Pin;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(MOSI_Port, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = SCK_Pin;
GPIO_Init(SCK_Port, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = CSN_Pin;
GPIO_Init(CSN_Port, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = CE_Pin;
GPIO_Init(CE_Port, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStruct.GPIO_Pin = IRQ_Pin;
GPIO_Init(IRQ_Port, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = MISO_Pin;
GPIO_Init(MISO_Port, &GPIO_InitStruct);
}
void NRF24L01_W_MOSI(uint8_t Value){
GPIO_WriteBit(MOSI_Port, MOSI_Pin, (BitAction)Value);
}
void NRF24L01_W_SCK(uint8_t Value){
GPIO_WriteBit(SCK_Port, SCK_Pin, (BitAction)Value);
}
void NRF24L01_W_CSN(uint8_t Value){
GPIO_WriteBit(CSN_Port, CSN_Pin, (BitAction)Value);
}
void NRF24L01_W_CE(uint8_t Value){
GPIO_WriteBit(CE_Port, CE_Pin, (BitAction)Value);
}
uint8_t NRF24L01_R_IRQ(void){
return GPIO_ReadInputDataBit(IRQ_Port, IRQ_Pin);
}
uint8_t NRF24L01_R_MISO(void){
return GPIO_ReadInputDataBit(MISO_Port, MISO_Pin);
}
uint8_t NRF24L01_SPI_SwapByte(uint8_t Byte){
uint8_t i;
uint8_t ByteReceive = 0x00;
for(i = 0; i < 8; i++){
NRF24L01_W_MOSI(Byte&(0x80>>i));
NRF24L01_W_SCK(1);
if(NRF24L01_R_MISO() == 1){
ByteReceive = ByteReceive | (0x80>>i);
}
NRF24L01_W_SCK(0);
}
return ByteReceive;
}
void NRF24L01_W_Reg(uint8_t Reg, uint8_t Value){
NRF24L01_W_CSN(0);
NRF24L01_SPI_SwapByte(Reg);
NRF24L01_SPI_SwapByte(Value);
NRF24L01_W_CSN(1);
}
uint8_t NRF24L01_R_Reg(uint8_t Reg){
uint8_t Value;
NRF24L01_W_CSN(0);
NRF24L01_SPI_SwapByte(Reg);
Value = NRF24L01_SPI_SwapByte(NOP);
NRF24L01_W_CSN(1);
return Value;
}
void NRF24L01_W_Buf(uint8_t Reg, uint8_t* Buf, uint8_t Length){
uint8_t i;
NRF24L01_W_CSN(0);
NRF24L01_SPI_SwapByte(Reg);
for(i = 0; i < Length; i++){
NRF24L01_SPI_SwapByte(Buf[i]);
}
NRF24L01_W_CSN(1);
}
void NRF24L01_R_Buf(uint8_t Reg, uint8_t* Buf, uint8_t Length){
uint8_t i;
NRF24L01_W_CSN(0);
NRF24L01_SPI_SwapByte(Reg);
for(i = 0; i < Length; i++){
Buf[i] = NRF24L01_SPI_SwapByte(NOP);
}
NRF24L01_W_CSN(1);
}
void NRF24L01_Init(void){
NRF24L01_Pin_Init();
NRF24L01_W_CE(0);
NRF24L01_W_Buf(W_REGISTER+TX_ADDR, T_ADDR, 5);
NRF24L01_W_Buf(W_REGISTER+RX_ADDR_P0, R_ADDR, 5);
NRF24L01_W_Reg(W_REGISTER+CONFIG, 0x0F);
NRF24L01_W_Reg(W_REGISTER+EN_AA, 0x01);
NRF24L01_W_Reg(W_REGISTER+RF_CH, 0x00);
NRF24L01_W_Reg(W_REGISTER+RX_PW_P0, 32);
NRF24L01_W_Reg(W_REGISTER+EN_RXADDR, 0x01);
NRF24L01_W_Reg(W_REGISTER+SETUP_RETR, 0x1A);
NRF24L01_W_Reg(FLUSH_RX, NOP);
NRF24L01_W_CE(1);
}
void NRF24L01_Receive(uint8_t *Buf){
uint8_t Status;
Status = NRF24L01_R_Reg(R_REGISTER+STATUS);
if(Status & RX_OK){
NRF24L01_R_Buf(R_RX_PAYLOAD, Buf, 32);
NRF24L01_W_Reg(FLUSH_RX, NOP);
NRF24L01_W_Reg(W_REGISTER+STATUS, Status);
Delay_us(150);
}
}
uint8_t NRF24L01_Send(uint8_t *Buf){
uint8_t Status;
NRF24L01_W_Buf(W_TX_PAYLOAD, Buf, 32);
NRF24L01_W_CE(0);
NRF24L01_W_Reg(W_REGISTER+CONFIG, 0x0E);
NRF24L01_W_CE(1);
while(NRF24L01_R_IRQ() == 1);
Status = NRF24L01_R_Reg(R_REGISTER+STATUS);
if(Status & MAX_TX){
NRF24L01_W_Reg(FLUSH_TX, NOP);
NRF24L01_W_Reg(W_REGISTER+STATUS, Status);
return MAX_TX;
}
if(Status & TX_OK){
NRF24L01_W_Reg(W_REGISTER+STATUS, Status);
return RX_OK;
}
return 0;
}
void NRF24L01_ReceiveString(char* str)
{
uint8_t Status;
Status = NRF24L01_R_Reg(R_REGISTER + STATUS);
if (Status & RX_OK)
{
NRF24L01_R_Buf(R_RX_PAYLOAD, (uint8_t*)str, 32);
NRF24L01_W_Reg(FLUSH_RX, NOP);
NRF24L01_W_Reg(W_REGISTER + STATUS, Status);
Delay_us(150);
}
}
3、小车端直流电机与舵机实现
使用定时器输出PWM来控制速度及舵机
3.1 直流电机
void Motor_Init(void){
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //开启TIM2的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); //开启GPIOA的时钟
//
// /*GPIO重映射*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //开启AFIO的时钟,重映射必须先开启AFIO的时钟
GPIO_PinRemapConfig(GPIO_FullRemap_TIM3, ENABLE); //将TIM2的引脚部分重映射,具体的映射方案需查看参考手册
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); //将JTAG引脚失能,作为普通GPIO引脚使用
//
// /*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9; //GPIO_Pin_15;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure); //将PA0引脚初始化为复用推挽输出
// //受外设控制的引脚,均需要配置为复用模式
//
// /*配置时钟源*/
TIM_InternalClockConfig(TIM3); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
//
// /*时基单元初始化*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
TIM_TimeBaseInitStructure.TIM_Period = 1000 - 1; //计数周期,即ARR的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1; //预分频器,即PSC的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
//
// /*输出比较初始化*/
TIM_OCInitTypeDef TIM_OCInitStructure; //定义结构体变量
TIM_OCStructInit(&TIM_OCInitStructure); //结构体初始化,若结构体没有完整赋值
// //则最好执行此函数,给结构体所有成员都赋一个默认值
// //避免结构体初值不确定的问题
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //输出比较模式,选择PWM模式1
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性,选择为高,若选择极性为低,则输出高低电平取反
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //输出使能
TIM_OCInitStructure.TIM_Pulse = 0; //初始的CCR值
TIM_OC1Init(TIM3, &TIM_OCInitStructure); //将结构体变量交给TIM_OC1Init,配置TIM2的输出比较通道1
TIM_OC2Init(TIM3, &TIM_OCInitStructure);
TIM_OC3Init(TIM3, &TIM_OCInitStructure);
TIM_OC4Init(TIM3, &TIM_OCInitStructure);
//
// /*TIM使能*/
TIM_Cmd(TIM3, ENABLE); //使能TIM2,定时器开始运行
}
void Motor_SetSpeed(int16_t Compare){
if (Compare >= 0){
TIM_SetCompare1(TIM3, Compare);
TIM_SetCompare2(TIM3, 0);
TIM_SetCompare3(TIM3, Compare);
TIM_SetCompare4(TIM3, 0);
}
else{
TIM_SetCompare1(TIM3, 0); //设置CCR1的值
TIM_SetCompare2(TIM3, -Compare);
TIM_SetCompare3(TIM3, 0); //设置CCR1的值
TIM_SetCompare4(TIM3, -Compare);
}
}
3.2 舵机
void Servo_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA1引脚初始化为复用推挽输出
//受外设控制的引脚,均需要配置为复用模式
/*配置时钟源*/
TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
/*时基单元初始化*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
TIM_TimeBaseInitStructure.TIM_Period = 20000 - 1; //计数周期,即ARR的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; //预分频器,即PSC的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
/*输出比较初始化*/
TIM_OCInitTypeDef TIM_OCInitStructure; //定义结构体变量
TIM_OCStructInit(&TIM_OCInitStructure); //结构体初始化,若结构体没有完整赋值
//则最好执行此函数,给结构体所有成员都赋一个默认值
//避免结构体初值不确定的问题
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //输出比较模式,选择PWM模式1
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性,选择为高,若选择极性为低,则输出高低电平取反
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //输出使能
TIM_OCInitStructure.TIM_Pulse = 0; //初始的CCR值
TIM_OC2Init(TIM2, &TIM_OCInitStructure); //将结构体变量交给TIM_OC2Init,配置TIM2的输出比较通道2
/*TIM使能*/
TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
}
void Servo_SetAngle(float Angle)
{
TIM_SetCompare2(TIM2, (Angle+SERVO_COR) / 180 * 2000 + 500); //设置CCR2的值
}
4、主函数逻辑
4.1 遥控器端
检测姿态角,如果roll角度的值在[-8, 8]范围内,则发送停止指令,roll角度大于14,则发送前进,小于14,发送后退,中间设立一段缓冲区,避免在界限是发生抖动现象(就和光敏电阻控制灯类似);pitch控制方向,原理与前进后退一样。然后,设置一个前后变量和方向变量,来存储上一条发送的指令和当前想要发送的指令,如果一样,就不发送,总不能在roll=20时一直发送go吧。
while(1)
{
MPU6050_DMP_Get_Data(&Pitch,&Roll,&Yaw);
OLED_ShowSignedNum(1,1,Pitch,3);
OLED_ShowSignedNum(1,7,Roll,3);
if(Roll < -14){
Car_rn = 1;
OLED_ShowString(2, 1, "SEND:CAR_BACK");
}
else if(Roll > -8 && Roll < 8){
Car_rn = 0;
OLED_ShowString(2, 1, "SEND:CAR_STOP");
}
else if(Roll > 14){
Car_rn = 2;
OLED_ShowString(2, 1, "SEND:CAR_GO ");
}
if(Pitch < -25){
Car_pn = 1;
OLED_ShowString(3, 1, "SEND:CAR_RIGHT");
}
else if(Pitch > -20 && Pitch < 20){
Car_pn = 0;
OLED_ShowString(3, 1, "SEND:CAR_MID ");
}
else if(Pitch > 25){
Car_pn = 2;
OLED_ShowString(3, 1, "SEND:CAR_LEFT ");
}
switch(Car_rn){
case 0:
if(Car_rn != Car_r){ NRF24L01_SendString("CAR_STOP");}
Car_r = Car_rn;
break;
case 1:
if(Car_rn != Car_r){ NRF24L01_SendString("CAR_BACK");}
Car_r = Car_rn;
break;
case 2:
if(Car_rn != Car_r){ NRF24L01_SendString("CAR_GOOO");}
Car_r = Car_rn;
break;
default:
break;
}
switch(Car_pn){
case 0:
if(Car_pn != Car_p){ NRF24L01_SendString("CAR_MIDD");}
Car_p = Car_pn;
break;
case 1:
if(Car_pn != Car_p){ NRF24L01_SendString("CAR_RIGH");}
Car_p = Car_pn;
break;
case 2:
if(Car_pn != Car_p){ NRF24L01_SendString("CAR_LEFT");}
Car_p = Car_pn;
break;
default:
break;
}
}
4.2 小车端
如果接收到数据,标志位置0,将接收到的指令与操作指令对比,和哪一个相同就执行哪个,比较简单。
while (1)
{
if (NRF24L01_R_IRQ() == 0)
{
NRF24L01_ReceiveString(receivedString);
receivedString[8] = '\0';
OLED_ShowString(3, 1, " ");
// 在OLED显示接收到的字符串
OLED_ShowString(3, 1, receivedString);
if (strcmp(receivedString, "CAR_GOOO") == 0){
//Motor_Flag = 0;
Motor_SetSpeed(500);
OLED_ShowString(2, 1, " ");
OLED_ShowString(2, 1, "CAR_GO_OK");
}
else if (strcmp(receivedString, "CAR_BACK") == 0){
//Motor_Flag = 0;
Motor_SetSpeed(-500);
OLED_ShowString(2, 1, " ");
OLED_ShowString(2, 1, "CAR_BACK_OK");
}
else if (strcmp(receivedString, "CAR_STOP") == 0){
//Motor_Flag = 1;
Motor_SetSpeed(0);
OLED_ShowString(2, 1, " ");
OLED_ShowString(2, 1, "CAR_STOP_OK");
}
else if (strcmp(receivedString, "CAR_LEFT") == 0){
Servo_SetAngle(-30);
OLED_ShowString(2, 1, " ");
OLED_ShowString(2, 1, "CAR_LEFT_OK");
}
else if (strcmp(receivedString, "CAR_RIGH") == 0){
Servo_SetAngle(30);
OLED_ShowString(2, 1, " ");
OLED_ShowString(2, 1, "CAR_RIGHT_OK");
}
else if (strcmp(receivedString, "CAR_MIDD") == 0){
Servo_SetAngle(0);
OLED_ShowString(2, 1, " ");
OLED_ShowString(2, 1, "CAR_MID_OK");
}
// 清空接收到的字符串,以便下次接收新的数据
memset(receivedString, 0, sizeof(receivedString));
}
}
五、总结
这个项目的难点在于解析姿态角,第二难的是NRF24L01发送和接收。懂了就可以做飞行器啦。