基于STM32的平衡车外设控制应用案例**
下面是一个使用STM32控制平衡车的简单应用案例,包含姿态传感器读取、电机控制和串口通信功能。
主要功能
- 使用MPU6050传感器读取姿态数据
- 使用PID控制器调整平衡车姿态
- 通过串口输出调试信息
- 电机速度控制
C++源代码
#include "stm32f10x.h"
#include <math.h>
// 定义常量
#define PWM_MIN 1000
#define PWM_MAX 2000
#define PWM_MID 1500
// 全局变量
volatile float roll = 0.0f; // 姿态角-横滚角
volatile float pitch = 0.0f; // 姿态角-俯仰角
volatile float yaw = 0.0f; // 姿态角-偏航角
volatile float motorPWM[2] = {PWM_MID, PWM_MID}; // 两个电机的PWM信号
// 定义PID控制器参数
typedef struct {
float Kp;
float Ki;
float Kd;
float integral;
float prevError;
} PID_Controller;
PID_Controller pidPitch;
// 初始化定时器
void TIM1_Init(void) {
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
// 使能定时器1和GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1 | RCC_APB2Periph_GPIOA, ENABLE);
// 配置PA8和PA9为复用推挽输出(TIM1_CH1和TIM1_CH2)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置TIM1
TIM_TimeBaseStructure.TIM_Period = 999; // 计数周期
TIM_TimeBaseStructure.TIM_Prescaler = 71; // 预分频器
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
// 配置PWM输出
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = PWM_MID; // 初始占空比
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM1, &TIM_OCInitStructure);
TIM_OC2Init(TIM1, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);
TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable);
// 使能定时器1
TIM_Cmd(TIM1, ENABLE);
}
// 初始化串口
void USART1_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
// 使能GPIOA和USART1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
// 配置PA9为复用推挽输出(USART1_TX)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置PA10为浮空输入(USART1_RX)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置USART1
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStructure);
// 使能USART1
USART_Cmd(USART1, ENABLE);
}
// 串口发送数据
void USART1_SendString(char *str) {
while (*str != '\0') {
USART_SendData(USART1, *str);
while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
str++;
}
}
// 初始化I2C
void I2C1_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
I2C_InitTypeDef I2C_InitStructure;
// 使能GPIOB和I2C1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
// 配置PB6为I2C1_SCL
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// 配置PB7为I2C1_SDA
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// 配置I2C1
I2C_InitStructure.I2C_ClockSpeed = 100000;
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStructure.I2C_OwnAddress1 = 0x00;
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_Init(I2C1, &I2C_InitStructure);
// 使能I2C1
I2C_Cmd(I2C1, ENABLE);
}
// 读取MPU6050数据
void MPU6050_ReadData(void) {
// 发送I2C起始信号
I2C_GenerateSTART(I2C1, ENABLE);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
// 发送MPU6050从机地址和写模式
I2C_Send7bitAddress(I2C1, 0x68, I2C_Direction_Transmitter);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
// 发送寄存器地址
I2C_SendData(I2C1, 0x3B); // 从0x3B寄存器开始读取加速度计和陀螺仪数据
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
// 发送I2C重复起始信号
I2C_GenerateSTART(I2C1, ENABLE);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
// 发送MPU6050从机地址和读模式
I2C_Send7bitAddress(I2C1, 0x68, I2C_Direction_Receiver);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
// 读取14字节数据(加速度计、温度、陀螺仪)
uint8_t data[14];
for (int i = 0; i < 14; i++) {
data[i] = I2C_ReceiveData(I2C1);
if (i < 13) {
I2C_AcknowledgeConfig(I2C1, ENABLE);
} else {
I2C_AcknowledgeConfig(I2C1, DISABLE);
}
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED));
}
// 解析数据
int16_t accelXRaw = (int16_t)((data[0] << 8) | data[1]);
int16_t accelYRaw = (int16_t)((data[2] << 8) | data[3]);
int16_t accelZRaw = (int16_t)((data[4] << 8) | data[5]);
int16_t tempRaw = (int16_t)((data[6] << 8) | data[7]);
int16_t gyroXRaw = (int16_t)((data[8] << 8) | data[9]);
int16_t gyroYRaw = (int16_t)((data[10] << 8) | data[11]);
int16_t gyroZRaw = (int16_t)((data[12] << 8) | data[13]);
// 转换为实际值
float accelX = accelXRaw / 16384.0f; // 加速度计灵敏度为16384 LSB/g
float accelY = accelYRaw / 16384.0f;
float accelZ = accelZRaw / 16384.0f;
float gyroX = gyroXRaw / 131.0f; // 陀螺仪灵敏度为131 LSB/(°/s)
float gyroY = gyroYRaw / 131.0f;
float gyroZ = gyroZRaw / 131.0f;
// 计算姿态角(简化版,实际应用中应使用更复杂的滤波算法)
roll = atan2(accelY, accelZ) * 180.0f / M_PI;
pitch = atan2(-accelX, sqrt(accelY * accelY + accelZ * accelZ)) * 180.0f / M_PI;
yaw = gyroZ * 0.01f; // 假设时间步长为10ms
// 生成停止信号
I2C_GenerateSTOP(I2C1, ENABLE);
}
// 初始化MPU6050
void MPU6050_Init(void) {
// 发送I2C起始信号
I2C_GenerateSTART(I2C1, ENABLE);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
// 发送MPU6050从机地址和写模式
I2C_Send7bitAddress(I2C1, 0x68, I2C_Direction_Transmitter);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
// 发送寄存器地址
I2C_SendData(I2C1, 0x6B); // 配置寄存器
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
// 发送配置数据(清除睡眠模式)
I2C_SendData(I2C1, 0x00);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
// 生成停止信号
I2C_GenerateSTOP(I2C1, ENABLE);
}
// 初始化PID控制器
void PID_Init(PID_Controller *pid, float Kp, float Ki, float Kd) {
pid->Kp = Kp;
pid->Ki = Ki;
pid->Kd = Kd;
pid->integral = 0.0f;
pid->prevError = 0.0f;
}
// PID控制器更新
float PID_Update(PID_Controller *pid, float setPoint, float processVar, float dt) {
float error = setPoint - processVar;
// 比例项
float proportional = pid->Kp * error;
// 积分项
pid->integral += pid->Ki * error * dt;
// 微分项
float derivative = pid->Kd * (error - pid->prevError) / dt;
// 计算输出
float output = proportional + pid->integral + derivative;
// 保存当前误差
pid->prevError = error;
return output;
}
// 更新电机PWM信号
void UpdateMotorPWM(float pwm[2]) {
// 限制PWM信号范围
for (int i = 0; i < 2; i++) {
if (pwm[i] < PWM_MIN) pwm[i] = PWM_MIN;
if (pwm[i] > PWM_MAX) pwm[i] = PWM_MAX;
}
// 更新定时器占空比
TIM_SetCompare1(TIM1, (uint32_t)pwm[0]);
TIM_SetCompare2(TIM1, (uint32_t)pwm[1]);
}
// 主函数
int main(void) {
// 初始化外设
TIM1_Init();
USART1_Init();
I2C1_Init();
MPU6050_Init();
// 初始化PID控制器
PID_Init(&pidPitch, 50.0f, 5.0f, 2.0f);
// 主循环
while (1) {
// 读取传感器数据
MPU6050_ReadData();
// 计算控制信号
float dt = 0.01f; // 假设控制周期为10ms
float pitchError = 0.0f - pitch;
float pitchControl = PID_Update(&pidPitch, 0.0f, pitch, dt);
// 计算电机PWM信号
motorPWM[0] = PWM_MID + pitchControl; // 左电机
motorPWM[1] = PWM_MID - pitchControl; // 右电机
// 更新电机PWM信号
UpdateMotorPWM(motorPWM);
// 通过串口发送调试信息
char debugStr[100];
sprintf(debugStr, "Pitch: %.2f, PWM_L: %.2f, PWM_R: %.2f\r\n", pitch, motorPWM[0], motorPWM[1]);
USART1_SendString(debugStr);
// 延时10ms(100Hz控制频率)
for (int i = 0; i < 10000; i++);
}
}
代码说明
1. 定时器初始化
使用TIM1生成PWM信号控制两个电机。
2. 串口初始化
初始化USART1用于调试输出。
3. I2C初始化
初始化I2C1用于与MPU6050传感器通信。
4. MPU6050初始化
配置MPU6050传感器。
5. PID控制器
实现简单的PID控制器用于姿态控制。
6. 主循环
- 读取传感器数据。
- 计算姿态角。
- 使用PID控制器计算电机控制信号。
- 更新电机PWM信号。
- 发送调试信息。
运行说明
- 该代码基于STM32F10x系列微控制器,使用STM32标准外设库。
- 在实际应用中,需要根据具体的硬件配置和传感器连接进行相应的调整。
- 代码中包含简化版的姿态角计算,在实际项目中可能需要使用更复杂的滤波算法(如互补滤波或卡尔曼滤波)。
- PID参数需要根据实际情况进行调整以获得最佳的控制效果。
扩展建议
- 增加更多传感器:可以添加距离传感器、摄像头等,实现更复杂的平衡车功能。
- 实现无线通信:添加Wi-Fi或蓝牙模块,实现远程控制。
- 优化控制算法:使用更复杂的控制算法,如卡尔曼滤波,提高控制精度。
- 增加安全机制:实现紧急停止、障碍物检测等安全功能。
这个案例展示了如何使用STM32实现平衡车的外设控制,包括电机PWM信号生成、传感器数据读取和简单的姿态控制算法。