基于S9KEA128 + MPU6050 + 直流减速电机的通用直立车程序

“KEA”通常指的是 NXP 的 S9KEA 系列单片机(如 S9KEA128),这款芯片在国内的智能车竞赛和平衡车项目中非常流行。

一个完整的“KEA 直立程序”通常包含四大核心部分:传感器数据读取(MPU6050)、姿态角融合(滤波)、直立 PID 控制环、以及电机 PWM 输出

基于 S9KEA128 + MPU6050 + 直流减速电机 的通用直立车程序


1. 核心控制逻辑与宏定义 (main.c)

这部分是程序的骨架,包含了初始化流程和主循环中的控制逻辑。

#include "common.h"      // KEA128 官方或第三方库头文件
#include "MKKEA128.h"    // 芯片寄存器头文件
#include "MPU6050.h"     // MPU6050 驱动
#include "motor.h"       // 电机PWM驱动

// ==================== 全局变量定义 ====================
float Pitch = 0;         // 最终融合后的俯仰角 (小车前倾/后仰)
float Gyro_Y = 0;        // Y轴陀螺仪原始数据 (角速度)
float Accel_X = 0;       // X轴加速度计原始数据

// PID 相关变量
float Balance_PWM = 0;  // 直立环输出的PWM
float Motor_Left = 0;   // 左电机最终PWM
float Motor_Right = 0;  // 右电机最终PWM

// ==================== 宏定义 (根据你的机械结构修改) ====================
#define MIDDLE_ANGLE  0.0f   // 机械中值角度 (小车完全直立时为0度)
#define BALANCE_KP    42.0f  // 直立环 P 系数 (正数)
#define BALANCE_KD    26.0f  // 直立环 D 系数 (正数)

/* 简要说明:
   Balance_PWM = KP * (Pitch - MIDDLE_ANGLE) + KD * Gyro_Y;
   注意: 如果你的小车往哪边倒,电机就往哪边转,说明极性反了,把 KP/KD 改为负数即可。
*/

// ==================== 直立 PD 控制函数 ====================
float Balance_Control(float Angle, float Gyro) {
    float Bias;
    float Pwm;
    
    Bias = Angle - MIDDLE_ANGLE; // 计算角度偏差
    Pwm = BALANCE_KP * Bias + BALANCE_KD * Gyro; // 经典 PD 公式
    
    return Pwm;
}

// ==================== 主函数 ====================
int main(void) {
    // 1. 系统初始化
    SystemCoreClockUpdate();    // 更新系统核心时钟
    Delay_Init();               // 延时初始化
    LED_Init();                 // LED 初始化 (用于状态指示)
    
    // 2. 外设初始化
    I2C_Init();                 // 初始化 I2C 总线 (用于通信 MPU6050)
    MPU6050_Init();            // 初始化 MPU6050 (设置量程、滤波器等)
    Motor_PWM_Init();          // 初始化电机 PWM (FTM模块)
    
    // 3. 编码器初始化 (如果需要速度环,在这里初始化 FTM 输入捕获)
    // Encoder_Init();
    
    while (1) {
        
        // --- 步骤 1: 读取传感器数据 ---
        MPU6050_ReadData(&Pitch, &Gyro_Y, &Accel_X); 
        
        // --- 步骤 2: 直立环 PID 计算 ---
        Balance_PWM = Balance_Control(Pitch, Gyro_Y);
        
        // --- 步骤 3: 电机输出 ---
        // 将直立环的结果分配给左右电机
        // 如果加上转向环或速度环,就在后面加减相应的PWM值
        Motor_Left  = Balance_PWM; 
        Motor_Right = Balance_PWM;
        
        // 限幅保护 (防止 PWM 超出 100%)
        if (Motor_Left > 100) Motor_Left = 100;
        if (Motor_Left < -100) Motor_Left = -100;
        if (Motor_Right > 100) Motor_Right = 100;
        if (Motor_Right < -100) Motor_Right = -100;
        
        // 真正将 PWM 写入寄存器,驱动电机
        Motor_Set_PWM(Motor_Left, Motor_Right);
        
        // --- 步骤 4: 循环延时控制 (控制周期建议 5ms - 10ms) ---
        Delay_ms(5); 
    }
}

2. MPU6050 驱动与滤波处理 (MPU6050.c)

MPU6050 返回的是原始 ADC 值,我们需要将其转化为物理量,并进行简单的互补滤波(一阶低通+高通)来消除加速度计的噪声和陀螺仪的漂移。

#include "MPU6050.h"

// MPU6050 内部寄存器地址
#define MPU6050_ADDR 0xD0    // 器件写地址 (AD0 接地)
#define PWR_MGMT_1   0x6B
#define SMPLRT_DIV   0x19
#define CONFIG       0x1A
#define GYRO_CONFIG  0x1B
#define ACCEL_CONFIG 0x1C
#define ACCEL_XOUT_H 0x3B
#define GYRO_YOUT_H  0x45    // 读取 Y 轴陀螺仪的高字节

// 陀螺仪和加速度计量程缩放因子
#define GYRO_SCALER  16.4f   // ±2000 dps 量程下的缩放因子 (32768 / 2000)
#define ACCEL_SCALER 16384.0f// ±2g 量程下的缩放因子

static float GyroOffset_Y = 0;
static float AccelOffset_X = 0;

// 一阶互补滤波参数 (α 越大,陀螺仪信任度越高,响应越快但对加速度计噪声抑制变差)
#define ALPHA 0.98f 

// ==================== 向 MPU6050 写一个字节 ====================
void MPU6050_WriteReg(uint8_t reg, uint8_t data) {
    I2C_Start();
    I2C_SendByte(MPU6050_ADDR); // 发送写地址
    I2C_WaitAck();
    I2C_SendByte(reg);         // 发送寄存器地址
    I2C_WaitAck();
    I2C_SendByte(data);       // 发送要写入的数据
    I2C_WaitAck();
    I2C_Stop();
}

// ==================== 从 MPU6050 读一个字节 ====================
uint8_t MPU6050_ReadReg(uint8_t reg) {
    uint8_t data;
    I2C_Start();
    I2C_SendByte(MPU6050_ADDR);
    I2C_WaitAck();
    I2C_SendByte(reg);
    I2C_WaitAck();
    
    I2C_Start();              // 重启总线,准备读取
    I2C_SendByte(MPU6050_ADDR | 0x01); // 发送读地址
    I2C_WaitAck();
    data = I2C_ReadByte();
    I2C_NoAck();             // 读最后一个字节发送 NAck
    I2C_Stop();
    return data;
}

// ==================== MPU6050 初始化 ====================
void MPU6050_Init(void) {
    MPU6050_WriteReg(PWR_MGMT_1, 0x00); // 唤醒设备
    MPU6050_WriteReg(SMPLRT_DIV, 0x07); // 采样率分频,1kHz / (1+7) = 125Hz
    MPU6050_WriteReg(CONFIG, 0x00);     // 配置低通滤波器
    MPU6050_WriteReg(GYRO_CONFIG, 0x18); // +-2000度/秒 量程
    MPU6050_WriteReg(ACCEL_CONFIG, 0x00); // +-2g 量程
    
    // 注意:实际项目中,这里应该加入“上电静态校准”代码,
    // 通过连续读取几百次数据求平均,计算出 GyroOffset 和 AccelOffset。
}

// ==================== 读取并处理传感器数据 ====================
void MPU6050_ReadData(float *pitch, float *gyro_y, float *accel_x) {
    int16_t raw_ax, raw_ay, raw_az;
    int16_t raw_gy, dt = 5; // dt 为每次计算的间隔时间(ms),这里与main中的Delay_ms(5)对应
    
    // 读取加速度计原始值
    raw_ax = (int16_t)((MPU6050_ReadReg(ACCEL_XOUT_H) << 8) | MPU6050_ReadReg(ACCEL_XOUT_H + 1));
    raw_ay = (int16_t)((MPU6050_ReadReg(ACCEL_XOUT_H + 2) << 8) | MPU6050_ReadReg(ACCEL_XOUT_H + 3));
    raw_az = (int16_t)((MPU6050_ReadReg(ACCEL_XOUT_H + 4) << 8) | MPU6050_ReadReg(ACCEL_XOUT_H + 5));
    
    // 读取陀螺仪原始值
    raw_gy = (int16_t)((MPU6050_ReadReg(GYRO_YOUT_H) << 8) | MPU6050_ReadReg(GYRO_YOUT_H + 1));
    
    // 减去零点偏移量
    raw_gy -= GyroOffset_Y;
    raw_ax -= AccelOffset_X;
    
    // 转换为物理量
    float f_gy = (float)raw_gy / GYRO_SCALER;       // 单位:度/秒
    float f_ax = (float)raw_ax / ACCEL_SCALER;      // 单位:g
    float f_ay = (float)raw_ay / ACCEL_SCALER;
    float f_az = (float)raw_az / ACCEL_SCALER;
    
    // --- 互补滤波计算俯仰角 (Pitch) ---
    // 1. 通过加速度计计算角度 (arctan2 得到的单位是弧度,需转为度)
    float angle_acc = atan2(f_ax, sqrt(f_ay*f_ay + f_az*f_az)) * 180.0f / 3.1415926f;
    
    // 2. 通过陀螺仪积分计算角度
    float angle_gy = *pitch + f_gy * dt / 1000.0f; // 度/秒 * 秒 = 度
    
    // 3. 互补滤波融合
    *pitch = ALPHA * angle_gy + (1 - ALPHA) * angle_acc;
    
    // 传出数据
    *gyro_y = f_gy;
    *accel_x = f_ax;
}

3. 电机 PWM 驱动 (motor.c)

KEA128 通常使用 FTM (FlexTimer Module) 来产生 PWM 波。

#include "motor.h"

// 假设使用 FTM2 模块,通道 0 和 1 控制左电机,通道 2 和 3 控制右电机
// 具体引脚需根据你自己的原理图修改
#define MOTOR_FTM   FTM2
#define LEFT_FWD    FTM_Ch0
#define LEFT_REV    FTM_Ch1
#define RIGHT_FWD   FTM_Ch2
#define RIGHT_REV   FTM_Ch3

void Motor_PWM_Init(void) {
    // 使能 FTM2 时钟
    SIM->SCGC |= SIM_SCGC_FTM2_MASK;
    
    // 配置 FTM2 为边沿对齐 PWM 模式
    MOTOR_FTM->MODE = FTM_MODE_WPDIS_MASK; // 禁用写保护
    MOTOR_FTM->CONF = 0xC0;               // 设置BDM为11,允许在调试模式下运行
    MOTOR_FTM->MOD = 10000;               // 设置 PWM 频率 = 总线频率 / MOD值 (例如 10kHz)
    
    // 配置通道为 PWM 模式
    MOTOR_FTM->CONTROLS[LEFT_FWD].CnSC = FTM_CnSC_MSB_MASK | FTM_CnSC_ELSB_MASK;
    MOTOR_FTM->CONTROLS[LEFT_REV].CnSC = FTM_CnSC_MSB_MASK | FTM_CnSC_ELSB_MASK;
    MOTOR_FTM->CONTROLS[RIGHT_FWD].CnSC = FTM_CnSC_MSB_MASK | FTM_CnSC_ELSB_MASK;
    MOTOR_FTM->CONTROLS[RIGHT_REV].CnSC = FTM_CnSC_MSB_MASK | FTM_CnSC_ELSB_MASK;
    
    // 初始化占空比为 0
    MOTOR_FTM->CONTROLS[LEFT_FWD].CnV = 0;
    MOTOR_FTM->CONTROLS[LEFT_REV].CnV = 0;
    MOTOR_FTM->CONTROLS[RIGHT_FWD].CnV = 0;
    MOTOR_FTM->CONTROLS[RIGHT_REV].CnV = 0;
    
    // 启动计数器
    MOTOR_FTM->SC = FTM_SC_CLKS(1) | FTM_SC_PS(0); // 选择系统时钟,不分频
}

void Motor_Set_PWM(float left_pwm, float right_pwm) {
    // 限制 PWM 占空比在 0 - 10000 之间
    if(left_pwm > 0) {
        MOTOR_FTM->CONTROLS[LEFT_FWD].CnV = (uint16_t)left_pwm * 100; // 假设 100% 对应 10000
        MOTOR_FTM->CONTROLS[LEFT_REV].CnV = 0;
    } else {
        MOTOR_FTM->CONTROLS[LEFT_FWD].CnV = 0;
        MOTOR_FTM->CONTROLS[LEFT_REV].CnV = (uint16_t)(-left_pwm) * 100;
    }
    
    if(right_pwm > 0) {
        MOTOR_FTM->CONTROLS[RIGHT_FWD].CnV = (uint16_t)right_pwm * 100;
        MOTOR_FTM->CONTROLS[RIGHT_REV].CnV = 0;
    } else {
        MOTOR_FTM->CONTROLS[RIGHT_FWD].CnV = 0;
        MOTOR_FTM->CONTROLS[RIGHT_REV].CnV = (uint16_t)(-right_pwm) * 100;
    }
}

参考代码 KEA_直立程序 www.youwenfan.com/contentcsu/70191.html

调参指南

  1. 先调直立环(PD)

    • 把小车拿在手里,上电,不要放下。
    • 先设 KD = 0,慢慢增大 KP,直到小车有“反抗”的趋势(你往后倒它往后转,你往前倒它往前转)。
    • 固定 KP,慢慢增大 KD,直到小车能够比较平稳地抵抗你的推拉,并且不会剧烈震荡。
    • 极性判断:如果小车往哪边倒,轮子就往相反方向转(即越倒越快),说明 KPKD正负号反了,直接改成负数即可。
  2. MPU6050 的安装方向

    • 代码中的俯仰角计算是基于 MPU6050 的 X 轴和 Z 轴加速度计。如果你的板子安装方向不同(例如平放或侧放),你需要修改 atan2 函数里的输入参数(比如改用 Y 轴和 Z 轴)。
  3. 控制周期

    • 直立车的控制周期非常关键。上面的代码使用了 Delay_ms(5),即 200Hz。如果你的控制周期变慢(比如 50ms),陀螺仪的积分项 *pitch = angle_gy + f_gy * dt 中的 dt 就必须跟着改,否则角度会疯狂发散。

这个框架实现了最基本的自平衡功能。在实际的智能车比赛中,你还需要在此基础上加入速度环(PI控制,用来让小车保持静止不走动)和转向环(PD控制,用来循迹或保持方向)。

专注于matlab/simulink,电子电路,编程