基于STM32的手势检测遥控车

一、实现效果

基于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发送和接收。懂了就可以做飞行器啦。

  • 30
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值