基于STM32的CCD智能寻迹小车源码

简介: 基于STM32的CCD智能寻迹小车源码

一、设计

1.1 硬件配置清单

主控芯片:STM32F103C8T6(72MHz,64KB Flash,20KB RAM)
CCD传感器:TSL1401CL(128像素线性CCD)
舵机:SG90(转向控制,PWM 50Hz)
驱动电机:直流电机 × 2(差速转向)
电机驱动:TB6612FNG(双路H桥)
编码器:光电编码器 × 2(测速反馈)
电源:18650锂电池 × 2(7.4V)
显示:0.96寸OLED(SSD1306,I2C)
按键:3个(模式选择、参数设置)
调试:USB转串口(CH340G)

1.2 系统功能框图

┌─────────────────────────────────────────────┐
│            STM32F103C8T6 智能寻迹小车        │
├─────────────────────────────────────────────┤
│  ┌─────────┐  ┌─────────┐  ┌─────────┐      │
│  │ TSL1401 │  │ 编码器   │  │ OLED显示 │      │
│  │ CCD采集  │  │ 速度反馈 │  │ 参数显示 │      │
│  └────┬────┘  └────┬────┘  └────┬────┘      │
│       │            │            │            │
│  ┌────▼────┐  ┌────▼────┐  ┌────▼────┐      │
│  │ 图像预处理│  │ 速度PID │  │ 人机交互 │      │
│  │ 二值化   │  │ 控制    │  │ 菜单系统 │      │
│  └────┬────┘  └────┬────┘  └────┬────┘      │
│       └─────────────┼────────────┘            │
│                     ▼                         │
│            ┌─────────────────┐                │
│            │ 舵机PID控制      │                │
│            │ 位置式PID算法    │                │
│            └────────┬────────┘                │
│                     ▼                         │
│  ┌─────────────────────────────────────┐      │
│  │ 电机控制输出                        │      │
│  │ - 左电机PWM控制                    │      │
│  │ - 右电机PWM控制                    │      │
│  │ - 差速转向控制                     │      │
│  └────────┬────────────────┬────────┘      │
│           ▼                ▼                │
│  ┌────────────┐    ┌────────────┐          │
│  │ 左直流电机 │    │ 右直流电机 │          │
│  │ 带编码器   │    │ 带编码器   │          │
│  └────────────┘    └────────────┘          │
└─────────────────────────────────────────────┘

二、完整工程源码

2.1 主程序(main.c)

/**
  * @file main.c
  * @brief STM32 CCD智能寻迹小车主程序
  * @version 3.0
  */
#include "stm32f10x.h"
#include "tsl1401.h"
#include "oled.h"
#include "pid.h"
#include "motor.h"
#include "encoder.h"
#include "key.h"
#include "menu.h"
#include "uart.h"

/* 系统状态枚举 */
typedef enum {
   
    SYS_INIT = 0,        // 初始化
    SYS_CALIBRATE,       // 传感器校准
    SYS_TRACKING,        // 循迹运行
    SYS_STOP,            // 停止
    SYS_MENU,            // 菜单设置
    SYS_DEBUG            // 调试模式
} SystemState;

/* 全局变量 */
SystemState sys_state = SYS_INIT;
CarParams car_params;
TrackInfo track_info;
ControlData ctrl_data;
DisplayData disp_data;

/* 小车参数结构 */
typedef struct {
   
    uint8_t run_mode;        // 运行模式:0-正常,1-加速,2-减速
    uint8_t speed_level;     // 速度等级:1-5
    uint8_t pid_mode;        // PID模式:0-位置式,1-增量式
    float base_speed;        // 基础速度
    float max_speed;         // 最大速度
    float turn_factor;       // 转向系数
    uint8_t enable_debug;    // 调试使能
} CarParams;

/* 赛道信息结构 */
typedef struct {
   
    uint8_t pixel[128];      // 二值化像素数据
    uint16_t center_pos;     // 黑线中心位置
    uint16_t left_edge;      // 左边缘位置
    uint16_t right_edge;     // 右边缘位置
    uint8_t line_width;      // 黑线宽度
    uint8_t track_type;      // 赛道类型:0-直线,1-左弯,2-右弯,3-十字
    float error;             // 偏差值
    uint8_t is_found;        // 是否找到黑线
} TrackInfo;

/* 控制数据结构 */
typedef struct {
   
    float servo_pid_out;     // 舵机PID输出
    float motor_left_pwm;    // 左电机PWM
    float motor_right_pwm;   // 右电机PWM
    float speed_left;        // 左轮速度
    float speed_right;       // 右轮速度
    uint8_t stop_flag;       // 停止标志
} ControlData;

/* 显示数据结构 */
typedef struct {
   
    uint8_t page;            // 当前页面
    uint8_t refresh_flag;    // 刷新标志
    uint32_t last_refresh;   // 上次刷新时间
} DisplayData;

int main(void)
{
   
    /* 1. 系统初始化 */
    System_Init();

    /* 2. 外设初始化 */
    Delay_Init();
    OLED_Init();
    TSL1401_Init();
    Motor_Init();
    Encoder_Init();
    KEY_Init();
    Menu_Init();
    UART_Init(115200);
    PID_Init();

    /* 3. 加载默认参数 */
    Load_DefaultParams(&car_params);

    /* 4. 显示启动画面 */
    OLED_ShowString(0, 0, "CCD Smart Car");
    OLED_ShowString(0, 2, "Initializing...");
    OLED_Refresh();
    Delay_Ms(2000);

    /* 5. 传感器校准 */
    OLED_ShowString(0, 4, "Calibrating...");
    OLED_Refresh();
    TSL1401_Calibrate();

    /* 6. 主循环 */
    while(1)
    {
   
        /* 6.1 系统状态机 */
        switch(sys_state)
        {
   
            case SYS_INIT:
                sys_state = SYS_TRACKING;
                break;

            case SYS_TRACKING:
                /* 采集CCD图像 */
                TSL1401_Capture(track_info.pixel);

                /* 图像处理 */
                Image_Process(&track_info);

                /* 舵机PID控制 */
                Servo_Control(&track_info, &ctrl_data);

                /* 电机速度控制 */
                Motor_Control(&track_info, &ctrl_data, &car_params);

                /* 编码器测速 */
                Encoder_GetSpeed(&ctrl_data);
                break;

            case SYS_CALIBRATE:
                TSL1401_Calibrate();
                sys_state = SYS_TRACKING;
                break;

            case SYS_STOP:
                Motor_Stop();
                Servo_SetAngle(90);  // 舵机回中
                break;

            case SYS_MENU:
                Menu_Process(&sys_state, &car_params);
                break;

            case SYS_DEBUG:
                Debug_Process(&track_info, &ctrl_data);
                break;
        }

        /* 6.2 按键处理 */
        Key_Process(&sys_state, &car_params);

        /* 6.3 显示更新 */
        if(Get_Tick() - disp_data.last_refresh > 100)  // 10Hz刷新
        {
   
            Display_Update(sys_state, track_info, ctrl_data, car_params);
            disp_data.last_refresh = Get_Tick();
        }

        /* 6.4 串口数据输出 */
        if(Get_Tick() - uart_data.last_send > 100)  // 10Hz输出
        {
   
            UART_SendTrackData(track_info, ctrl_data);
            uart_data.last_send = Get_Tick();
        }

        /* 6.5 延时 */
        Delay_Ms(10);  // 100Hz主循环
    }
}

/* 系统初始化 */
void System_Init(void)
{
   
    /* 系统时钟配置:72MHz */
    SystemClock_Init();

    /* 中断优先级分组 */
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

    /* 全局变量初始化 */
    memset(&track_info, 0, sizeof(TrackInfo));
    memset(&ctrl_data, 0, sizeof(ControlData));
    memset(&disp_data, 0, sizeof(DisplayData));

    track_info.center_pos = 64;  // 默认中心位置
    car_params.base_speed = 30.0f;
    car_params.max_speed = 60.0f;
    car_params.speed_level = 3;
    car_params.turn_factor = 1.5f;
    car_params.run_mode = 0;
    car_params.pid_mode = 0;
    car_params.enable_debug = 0;

    sys_state = SYS_INIT;
}

2.2 CCD驱动(tsl1401.c)

/**
  * @file tsl1401.c
  * @brief TSL1401线性CCD驱动
  */
#include "tsl1401.h"
#include "delay.h"
#include "adc.h"

/* CCD引脚定义 */
#define CCD_SI_PIN      GPIO_Pin_0
#define CCD_SI_PORT     GPIOA
#define CCD_CLK_PIN     GPIO_Pin_1
#define CCD_CLK_PORT    GPIOA
#define CCD_AO_PIN      GPIO_Pin_0
#define CCD_AO_PORT     GPIOA

/* 曝光时间(微秒) */
#define EXPOSURE_TIME   10000

/* 校准参数 */
static uint16_t ccd_min[128];    // 最小值
static uint16_t ccd_max[128];    // 最大值
static uint16_t ccd_threshold[128];  // 阈值
static uint8_t calib_count = 0;

/* 初始化CCD */
void TSL1401_Init(void)
{
   
    GPIO_InitTypeDef GPIO_InitStructure;

    /* 使能时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    /* 配置SI引脚 */
    GPIO_InitStructure.GPIO_Pin = CCD_SI_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(CCD_SI_PORT, &GPIO_InitStructure);

    /* 配置CLK引脚 */
    GPIO_InitStructure.GPIO_Pin = CCD_CLK_PIN;
    GPIO_Init(CCD_CLK_PORT, &GPIO_InitStructure);

    /* 配置AO引脚(ADC输入) */
    GPIO_InitStructure.GPIO_Pin = CCD_AO_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_Init(CCD_AO_PORT, &GPIO_InitStructure);

    /* 初始化ADC */
    ADC_Init_Config();

    /* 初始状态 */
    GPIO_ResetBits(CCD_SI_PORT, CCD_SI_PIN);
    GPIO_ResetBits(CCD_CLK_PORT, CCD_CLK_PIN);

    /* 初始化校准数据 */
    for(int i = 0; i < 128; i++)
    {
   
        ccd_min[i] = 4095;
        ccd_max[i] = 0;
        ccd_threshold[i] = 2048;
    }
}

/* 采集一行像素数据 */
void TSL1401_Capture(uint8_t* pixel)
{
   
    uint16_t adc_value;
    uint16_t pixel_raw[128];

    /* 1. SI高电平 */
    GPIO_SetBits(CCD_SI_PORT, CCD_SI_PIN);
    Delay_Us(1);

    /* 2. CLK高电平 */
    GPIO_SetBits(CCD_CLK_PORT, CCD_CLK_PIN);
    Delay_Us(1);

    /* 3. SI低电平 */
    GPIO_ResetBits(CCD_SI_PORT, CCD_SI_PIN);
    Delay_Us(1);

    /* 4. CLK低电平 */
    GPIO_ResetBits(CCD_CLK_PORT, CCD_CLK_PIN);
    Delay_Us(1);

    /* 5. 采集128个像素 */
    for(int i = 0; i < 128; i++)
    {
   
        /* CLK高电平 */
        GPIO_SetBits(CCD_CLK_PORT, CCD_CLK_PIN);
        Delay_Us(1);

        /* 读取ADC值 */
        adc_value = ADC_GetValue(ADC_Channel_0);
        pixel_raw[i] = adc_value;

        /* 更新最值(用于自动校准) */
        if(adc_value < ccd_min[i]) ccd_min[i] = adc_value;
        if(adc_value > ccd_max[i]) ccd_max[i] = adc_value;

        /* CLK低电平 */
        GPIO_ResetBits(CCD_CLK_PORT, CCD_CLK_PIN);
        Delay_Us(1);
    }

    /* 6. 二值化处理 */
    Image_Binarization(pixel_raw, pixel);
}

/* 图像二值化 */
void Image_Binarization(uint16_t* raw, uint8_t* binary)
{
   
    uint16_t threshold;

    for(int i = 0; i < 128; i++)
    {
   
        /* 动态阈值计算 */
        threshold = (ccd_min[i] + ccd_max[i]) / 2;
        ccd_threshold[i] = threshold;

        /* 二值化 */
        if(raw[i] < threshold)
            binary[i] = 0;   // 黑线
        else
            binary[i] = 1;   // 白色背景
    }
}

/* CCD校准 */
void TSL1401_Calibrate(void)
{
   
    uint8_t pixel[128];
    uint16_t raw_data[128];

    OLED_ShowString(0, 6, "Calibrating...");
    OLED_Refresh();

    /* 多次采样求平均 */
    for(int count = 0; count < 10; count++)
    {
   
        TSL1401_Capture(pixel);
        Delay_Ms(100);
    }

    calib_count++;
    if(calib_count >= 5)
    {
   
        /* 校准完成,锁定阈值 */
        OLED_ShowString(0, 6, "Calibration Done!");
        OLED_Refresh();
        Delay_Ms(1000);
    }
}

/* 获取像素值 */
uint16_t TSL1401_GetPixel(uint8_t index)
{
   
    uint16_t adc_value;

    if(index >= 128) return 0;

    /* 重新采集单个像素 */
    GPIO_SetBits(CCD_SI_PORT, CCD_SI_PIN);
    Delay_Us(1);
    GPIO_SetBits(CCD_CLK_PORT, CCD_CLK_PIN);
    Delay_Us(1);
    GPIO_ResetBits(CCD_SI_PORT, CCD_SI_PIN);
    Delay_Us(1);
    GPIO_ResetBits(CCD_CLK_PORT, CCD_CLK_PIN);
    Delay_Us(1);

    for(int i = 0; i < index; i++)
    {
   
        GPIO_SetBits(CCD_CLK_PORT, CCD_CLK_PIN);
        Delay_Us(1);
        GPIO_ResetBits(CCD_CLK_PORT, CCD_CLK_PIN);
        Delay_Us(1);
    }

    GPIO_SetBits(CCD_CLK_PORT, CCD_CLK_PIN);
    Delay_Us(1);
    adc_value = ADC_GetValue(ADC_Channel_0);
    GPIO_ResetBits(CCD_CLK_PORT, CCD_CLK_PIN);
    Delay_Us(1);

    return adc_value;
}

2.3 图像处理(image_process.c)

/**
  * @file image_process.c
  * @brief CCD图像处理算法
  */
#include "image_process.h"
#include "math.h"

/* 图像处理参数 */
#define LEFT_START      10      // 左边界起始
#define RIGHT_END       117     // 右边界结束
#define MIN_LINE_WIDTH  5       // 最小线宽
#define MAX_LINE_WIDTH  30      // 最大线宽
#define EDGE_THRESHOLD  3       // 边缘检测阈值

/* 图像预处理 */
void Image_Process(TrackInfo* track)
{
   
    uint8_t* pixel = track->pixel;
    uint16_t left_edge = 0, right_edge = 0;
    uint8_t found_left = 0, found_right = 0;
    uint8_t line_count = 0;
    uint16_t center_sum = 0;
    uint16_t center_count = 0;

    /* 1. 边缘检测 */
    for(int i = LEFT_START; i <= RIGHT_END; i++)
    {
   
        /* 检测左边缘:1->0跳变 */
        if(!found_left && i > 0 && pixel[i-1] == 1 && pixel[i] == 0)
        {
   
            /* 验证边缘有效性 */
            if(i < RIGHT_END && pixel[i+1] == 0 && pixel[i+2] == 0)
            {
   
                left_edge = i;
                found_left = 1;
            }
        }

        /* 检测右边缘:0->1跳变 */
        if(!found_right && i > 0 && pixel[i-1] == 0 && pixel[i] == 1)
        {
   
            /* 验证边缘有效性 */
            if(i > LEFT_START && pixel[i-1] == 0 && pixel[i-2] == 0)
            {
   
                right_edge = i;
                found_right = 1;
            }
        }
    }

    /* 2. 计算黑线宽度 */
    if(found_left && found_right)
    {
   
        track->line_width = right_edge - left_edge;
        if(track->line_width >= MIN_LINE_WIDTH && track->line_width <= MAX_LINE_WIDTH)
        {
   
            track->left_edge = left_edge;
            track->right_edge = right_edge;
            track->is_found = 1;

            /* 3. 计算中心位置 */
            track->center_pos = (left_edge + right_edge) / 2;
        }
        else
        {
   
            track->is_found = 0;
        }
    }
    else
    {
   
        /* 未找到完整黑线,尝试找单个边缘 */
        if(found_left && !found_right)
        {
   
            track->left_edge = left_edge;
            track->center_pos = left_edge + 20;  // 估计中心
            track->is_found = 1;
        }
        else if(!found_left && found_right)
        {
   
            track->right_edge = right_edge;
            track->center_pos = right_edge - 20;  // 估计中心
            track->is_found = 1;
        }
        else
        {
   
            track->is_found = 0;
        }
    }

    /* 4. 赛道类型判断 */
    if(track->is_found)
    {
   
        /* 计算偏差 */
        track->error = (float)track->center_pos - 64.0f;  // 64为中心位置

        /* 判断赛道类型 */
        if(fabs(track->error) < 5.0f)
        {
   
            track->track_type = TRACK_STRAIGHT;  // 直线
        }
        else if(track->error < -5.0f)
        {
   
            track->track_type = TRACK_LEFT_CURVE;  // 左弯
        }
        else
        {
   
            track->track_type = TRACK_RIGHT_CURVE; // 右弯
        }

        /* 十字路口检测 */
        if(track->line_width > 40)
        {
   
            track->track_type = TRACK_CROSS;
        }
    }
    else
    {
   
        /* 未找到黑线,保持上一状态 */
        track->error = 0;
        track->track_type = TRACK_STRAIGHT;
    }
}

/* 加权平均法计算中心 */
uint16_t Calculate_WeightedCenter(uint8_t* pixel)
{
   
    uint32_t weighted_sum = 0;
    uint32_t weight_count = 0;

    for(int i = 0; i < 128; i++)
    {
   
        if(pixel[i] == 0)  // 黑线
        {
   
            weighted_sum += i * 100;  // 权重100
            weight_count += 100;
        }
    }

    if(weight_count > 0)
        return weighted_sum / weight_count;
    else
        return 64;  // 默认中心
}

/* 形态学滤波 */
void Morphology_Filter(uint8_t* pixel)
{
   
    uint8_t temp[128];

    /* 腐蚀操作 */
    for(int i = 1; i < 127; i++)
    {
   
        if(pixel[i-1] == 0 && pixel[i] == 0 && pixel[i+1] == 0)
            temp[i] = 0;
        else
            temp[i] = 1;
    }

    /* 膨胀操作 */
    for(int i = 1; i < 127; i++)
    {
   
        if(temp[i-1] == 1 || temp[i] == 1 || temp[i+1] == 1)
            pixel[i] = 1;
        else
            pixel[i] = 0;
    }
}

2.4 PID控制(pid.c)

/**
  * @file pid.c
  * @brief PID控制算法(舵机和电机)
  */
#include "pid.h"

/* PID结构体 */
static PID_TypeDef servo_pid;
static PID_TypeDef motor_pid_left;
static PID_TypeDef motor_pid_right;

/* 舵机PID初始化 */
void PID_Init(void)
{
   
    /* 舵机PID参数 */
    servo_pid.kp = 2.5f;
    servo_pid.ki = 0.1f;
    servo_pid.kd = 0.8f;
    servo_pid.integral = 0;
    servo_pid.prev_error = 0;
    servo_pid.output = 0;
    servo_pid.max_output = 45.0f;   // 最大±45度
    servo_pid.min_output = -45.0f;

    /* 左电机PID参数 */
    motor_pid_left.kp = 1.2f;
    motor_pid_left.ki = 0.05f;
    motor_pid_left.kd = 0.3f;
    motor_pid_left.integral = 0;
    motor_pid_left.prev_error = 0;
    motor_pid_left.output = 0;
    motor_pid_left.max_output = 100.0f;
    motor_pid_left.min_output = 0.0f;

    /* 右电机PID参数 */
    motor_pid_right.kp = 1.2f;
    motor_pid_right.ki = 0.05f;
    motor_pid_right.kd = 0.3f;
    motor_pid_right.integral = 0;
    motor_pid_right.prev_error = 0;
    motor_pid_right.output = 0;
    motor_pid_right.max_output = 100.0f;
    motor_pid_right.min_output = 0.0f;
}

/* 舵机PID计算 */
float PID_ServoCalculate(float target, float feedback)
{
   
    float error;
    float derivative;
    float output;

    error = target - feedback;  // 目标为64(中心),feedback为实际中心位置

    /* 比例项 */
    output = servo_pid.kp * error;

    /* 积分项(抗积分饱和) */
    if(fabs(output) < servo_pid.max_output)
    {
   
        servo_pid.integral += error;
        if(servo_pid.integral > 50) servo_pid.integral = 50;
        if(servo_pid.integral < -50) servo_pid.integral = -50;
    }
    output += servo_pid.ki * servo_pid.integral;

    /* 微分项 */
    derivative = error - servo_pid.prev_error;
    output += servo_pid.kd * derivative;
    servo_pid.prev_error = error;

    /* 输出限幅 */
    if(output > servo_pid.max_output) output = servo_pid.max_output;
    if(output < servo_pid.min_output) output = servo_pid.min_output;

    servo_pid.output = output;
    return output;
}

/* 增量式PID(用于电机) */
float PID_MotorCalculate(PID_TypeDef* pid, float target, float feedback)
{
   
    float error;
    float delta_output;

    error = target - feedback;

    /* 增量计算 */
    delta_output = pid->kp * (error - pid->prev_error)
                 + pid->ki * error
                 + pid->kd * (error - 2*pid->prev_error + pid->prev_prev_error);

    pid->output += delta_output;

    /* 限幅 */
    if(pid->output > pid->max_output) pid->output = pid->max_output;
    if(pid->output < pid->min_output) pid->output = pid->min_output;

    /* 保存历史误差 */
    pid->prev_prev_error = pid->prev_error;
    pid->prev_error = error;

    return pid->output;
}

/* 舵机控制 */
void Servo_Control(TrackInfo* track, ControlData* ctrl)
{
   
    float servo_angle;

    if(track->is_found)
    {
   
        /* 使用PID计算舵机角度 */
        servo_angle = PID_ServoCalculate(64.0f, (float)track->center_pos);

        /* 转换为舵机PWM值(90度为中心) */
        uint16_t pwm = (uint16_t)(1500 + servo_angle * 10);  // 1ms=1000, 1.5ms=1500, 2ms=2000

        /* 设置舵机PWM */
        Servo_SetPWM(pwm);
        ctrl->servo_pid_out = servo_angle;
    }
    else
    {
   
        /* 未找到黑线,舵机回中 */
        Servo_SetAngle(90);
        ctrl->servo_pid_out = 0;
    }
}

/* 电机控制 */
void Motor_Control(TrackInfo* track, ControlData* ctrl, CarParams* params)
{
   
    float base_speed = params->base_speed;
    float turn_factor = params->turn_factor;
    float left_speed, right_speed;

    if(track->is_found)
    {
   
        /* 根据赛道类型调整速度 */
        switch(track->track_type)
        {
   
            case TRACK_STRAIGHT:
                base_speed = params->base_speed * 1.2f;  // 直线加速
                break;
            case TRACK_LEFT_CURVE:
                base_speed = params->base_speed * 0.8f;  // 弯道减速
                turn_factor *= 1.2f;  // 增大转向
                break;
            case TRACK_RIGHT_CURVE:
                base_speed = params->base_speed * 0.8f;
                turn_factor *= 1.2f;
                break;
            case TRACK_CROSS:
                base_speed = params->base_speed * 0.5f;  // 十字路口慢速
                break;
        }

        /* 差速控制 */
        left_speed = base_speed - track->error * turn_factor;
        right_speed = base_speed + track->error * turn_factor;

        /* 限幅 */
        if(left_speed > params->max_speed) left_speed = params->max_speed;
        if(right_speed > params->max_speed) right_speed = params->max_speed;
        if(left_speed < 0) left_speed = 0;
        if(right_speed < 0) right_speed = 0;

        /* 设置电机PWM */
        Motor_SetLeftSpeed(left_speed);
        Motor_SetRightSpeed(right_speed);

        ctrl->motor_left_pwm = left_speed;
        ctrl->motor_right_pwm = right_speed;
    }
    else
    {
   
        /* 未找到黑线,停车 */
        Motor_Stop();
        ctrl->motor_left_pwm = 0;
        ctrl->motor_right_pwm = 0;
    }
}

2.5 电机驱动(motor.c)

/**
  * @file motor.c
  * @brief 直流电机驱动(TB6612FNG)
  */
#include "motor.h"
#include "tim.h"
#include "gpio.h"

/* 电机引脚定义 */
#define MOTOR_AIN1_PIN   GPIO_Pin_0
#define MOTOR_AIN1_PORT  GPIOB
#define MOTOR_AIN2_PIN   GPIO_Pin_1
#define MOTOR_AIN2_PORT  GPIOB
#define MOTOR_BIN1_PIN   GPIO_Pin_2
#define MOTOR_BIN1_PORT  GPIOB
#define MOTOR_BIN2_PIN   GPIO_Pin_3
#define MOTOR_BIN2_PORT  GPIOB

#define MOTOR_PWMA_PIN   GPIO_Pin_6
#define MOTOR_PWMA_PORT  GPIOB
#define MOTOR_PWMB_PIN   GPIO_Pin_7
#define MOTOR_PWMB_PORT  GPIOB

/* 电机控制状态 */
typedef enum {
   
    MOTOR_STOP = 0,
    MOTOR_FORWARD,
    MOTOR_BACKWARD
} MotorState;

/* 初始化电机 */
void Motor_Init(void)
{
   
    GPIO_InitTypeDef GPIO_InitStructure;

    /* 使能时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

    /* 配置方向控制引脚 */
    GPIO_InitStructure.GPIO_Pin = MOTOR_AIN1_PIN | MOTOR_AIN2_PIN | 
                                MOTOR_BIN1_PIN | MOTOR_BIN2_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    /* 配置PWM引脚 */
    GPIO_InitStructure.GPIO_Pin = MOTOR_PWMA_PIN | MOTOR_PWMB_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    /* 初始化PWM(TIM4_CH1和CH2) */
    TIM4_PWM_Init(1000-1, 72-1);  // 1kHz PWM
    TIM_SetCompare1(TIM4, 0);     // 左电机PWM
    TIM_SetCompare2(TIM4, 0);     // 右电机PWM

    /* 初始状态:停止 */
    Motor_Stop();
}

/* 设置左电机速度和方向 */
void Motor_SetLeftSpeed(float speed)
{
   
    uint16_t pwm_value;
    MotorState direction;

    if(speed >= 0)
    {
   
        direction = MOTOR_FORWARD;
        pwm_value = (uint16_t)speed;
    }
    else
    {
   
        direction = MOTOR_BACKWARD;
        pwm_value = (uint16_t)(-speed);
    }

    /* 限幅 */
    if(pwm_value > 1000) pwm_value = 1000;

    /* 设置方向 */
    switch(direction)
    {
   
        case MOTOR_FORWARD:
            GPIO_SetBits(MOTOR_AIN1_PORT, MOTOR_AIN1_PIN);
            GPIO_ResetBits(MOTOR_AIN2_PORT, MOTOR_AIN2_PIN);
            break;
        case MOTOR_BACKWARD:
            GPIO_ResetBits(MOTOR_AIN1_PORT, MOTOR_AIN1_PIN);
            GPIO_SetBits(MOTOR_AIN2_PORT, MOTOR_AIN2_PIN);
            break;
        case MOTOR_STOP:
            GPIO_ResetBits(MOTOR_AIN1_PORT, MOTOR_AIN1_PIN);
            GPIO_ResetBits(MOTOR_AIN2_PORT, MOTOR_AIN2_PIN);
            break;
    }

    /* 设置PWM */
    TIM_SetCompare1(TIM4, pwm_value);
}

/* 设置右电机速度和方向 */
void Motor_SetRightSpeed(float speed)
{
   
    uint16_t pwm_value;
    MotorState direction;

    if(speed >= 0)
    {
   
        direction = MOTOR_FORWARD;
        pwm_value = (uint16_t)speed;
    }
    else
    {
   
        direction = MOTOR_BACKWARD;
        pwm_value = (uint16_t)(-speed);
    }

    /* 限幅 */
    if(pwm_value > 1000) pwm_value = 1000;

    /* 设置方向 */
    switch(direction)
    {
   
        case MOTOR_FORWARD:
            GPIO_SetBits(MOTOR_BIN1_PORT, MOTOR_BIN1_PIN);
            GPIO_ResetBits(MOTOR_BIN2_PORT, MOTOR_BIN2_PIN);
            break;
        case MOTOR_BACKWARD:
            GPIO_ResetBits(MOTOR_BIN1_PORT, MOTOR_BIN1_PIN);
            GPIO_SetBits(MOTOR_BIN2_PORT, MOTOR_BIN2_PIN);
            break;
        case MOTOR_STOP:
            GPIO_ResetBits(MOTOR_BIN1_PORT, MOTOR_BIN1_PIN);
            GPIO_ResetBits(MOTOR_BIN2_PORT, MOTOR_BIN2_PIN);
            break;
    }

    /* 设置PWM */
    TIM_SetCompare2(TIM4, pwm_value);
}

/* 停止所有电机 */
void Motor_Stop(void)
{
   
    /* 左电机停止 */
    GPIO_ResetBits(MOTOR_AIN1_PORT, MOTOR_AIN1_PIN);
    GPIO_ResetBits(MOTOR_AIN2_PORT, MOTOR_AIN2_PIN);
    TIM_SetCompare1(TIM4, 0);

    /* 右电机停止 */
    GPIO_ResetBits(MOTOR_BIN1_PORT, MOTOR_BIN1_PIN);
    GPIO_ResetBits(MOTOR_BIN2_PORT, MOTOR_BIN2_PIN);
    TIM_SetCompare2(TIM4, 0);
}

/* 舵机控制 */
void Servo_SetAngle(uint8_t angle)
{
   
    uint16_t pwm;

    /* 角度转PWM:0度=500us, 90度=1500us, 180度=2500us */
    pwm = 500 + angle * 10;  // 简化计算

    /* 设置舵机PWM(TIM3_CH1) */
    TIM_SetCompare1(TIM3, pwm);
}

/* 设置舵机PWM */
void Servo_SetPWM(uint16_t pwm)
{
   
    /* 限制PWM范围 */
    if(pwm < 500) pwm = 500;
    if(pwm > 2500) pwm = 2500;

    TIM_SetCompare1(TIM3, pwm);
}

2.6 编码器测速(encoder.c)

/**
  * @file encoder.c
  * @brief 光电编码器测速
  */
#include "encoder.h"
#include "tim.h"

/* 编码器计数 */
static int32_t encoder_left_count = 0;
static int32_t encoder_right_count = 0;
static float left_speed = 0.0f;
static float right_speed = 0.0f;
static uint32_t last_time = 0;

/* 初始化编码器 */
void Encoder_Init(void)
{
   
    /* 初始化定时器编码器模式 */
    TIM2_Encoder_Init();  // 左编码器:TIM2_CH1和CH2
    TIM3_Encoder_Init();  // 右编码器:TIM3_CH1和CH2

    /* 重置计数器 */
    TIM_SetCounter(TIM2, 0);
    TIM_SetCounter(TIM3, 0);
}

/* 获取编码器计数 */
int32_t Encoder_GetLeftCount(void)
{
   
    return TIM_GetCounter(TIM2);
}

int32_t Encoder_GetRightCount(void)
{
   
    return TIM_GetCounter(TIM3);
}

/* 获取速度(单位:脉冲/秒) */
void Encoder_GetSpeed(ControlData* ctrl)
{
   
    uint32_t current_time = Get_Tick();
    int32_t left_delta, right_delta;
    static int32_t last_left = 0, last_right = 0;

    if(current_time - last_time >= 100)  // 每100ms计算一次
    {
   
        left_delta = Encoder_GetLeftCount() - last_left;
        right_delta = Encoder_GetRightCount() - last_right;

        /* 计算速度(假设编码器每圈1000脉冲) */
        left_speed = (float)left_delta * 10.0f;  // 转换为每秒脉冲数
        right_speed = (float)right_delta * 10.0f;

        last_left = Encoder_GetLeftCount();
        last_right = Encoder_GetRightCount();
        last_time = current_time;

        ctrl->speed_left = left_speed;
        ctrl->speed_right = right_speed;
    }
}

/* 重置编码器计数 */
void Encoder_Reset(void)
{
   
    TIM_SetCounter(TIM2, 0);
    TIM_SetCounter(TIM3, 0);
    encoder_left_count = 0;
    encoder_right_count = 0;
}

2.7 OLED显示(oled.c)

/**
  * @file oled.c
  * @brief OLED显示屏驱动
  */
#include "oled.h"
#include "i2c.h"
#include "font.h"

/* OLED显存 */
static uint8_t OLED_GRAM[128][8];

/* 初始化OLED */
void OLED_Init(void)
{
   
    /* 初始化I2C */
    I2C_Init();

    /* OLED初始化序列 */
    OLED_WriteCmd(0xAE);  // 关闭显示
    OLED_WriteCmd(0xD5);  // 设置时钟分频
    OLED_WriteCmd(0x80);
    OLED_WriteCmd(0xA8);  // 设置多路复用率
    OLED_WriteCmd(0x3F);
    OLED_WriteCmd(0xD3);  // 设置显示偏移
    OLED_WriteCmd(0x00);
    OLED_WriteCmd(0x40);  // 设置起始行
    OLED_WriteCmd(0x8D);  // 电荷泵设置
    OLED_WriteCmd(0x14);
    OLED_WriteCmd(0x20);  // 内存寻址模式
    OLED_WriteCmd(0x00);
    OLED_WriteCmd(0xA1);  // 段重映射
    OLED_WriteCmd(0xC8);  // 扫描方向
    OLED_WriteCmd(0xDA);  // 设置COM引脚硬件配置
    OLED_WriteCmd(0x12);
    OLED_WriteCmd(0x81);  // 对比度设置
    OLED_WriteCmd(0xCF);
    OLED_WriteCmd(0xD9);  // 预充电周期
    OLED_WriteCmd(0xF1);
    OLED_WriteCmd(0xDB);  // VCOMH取消选择级别
    OLED_WriteCmd(0x40);
    OLED_WriteCmd(0xA4);  // 整个显示开启
    OLED_WriteCmd(0xA6);  // 正常显示
    OLED_WriteCmd(0xAF);  // 开启显示

    /* 清屏 */
    OLED_Clear();
    OLED_Refresh();
}

/* 显示赛道信息 */
void OLED_ShowTrackInfo(TrackInfo* track, CarParams* params)
{
   
    char str[20];

    OLED_Clear();

    /* 第一行:系统状态 */
    OLED_ShowString(0, 0, "CCD Smart Car");

    /* 第二行:黑线位置 */
    sprintf(str, "Center:%3d", track->center_pos);
    OLED_ShowString(0, 2, str);

    /* 第三行:偏差和类型 */
    sprintf(str, "Err:%5.1f Type:%d", track->error, track->track_type);
    OLED_ShowString(0, 4, str);

    /* 第四行:速度和模式 */
    sprintf(str, "Spd:%3.0f Mode:%d", params->base_speed, params->run_mode);
    OLED_ShowString(0, 6, str);

    OLED_Refresh();
}

/* 显示PID参数 */
void OLED_ShowPIDParams(float kp, float ki, float kd)
{
   
    char str[20];

    OLED_Clear();

    OLED_ShowString(0, 0, "PID Parameters");

    sprintf(str, "Kp:%6.2f", kp);
    OLED_ShowString(0, 2, str);

    sprintf(str, "Ki:%6.2f", ki);
    OLED_ShowString(0, 4, str);

    sprintf(str, "Kd:%6.2f", kd);
    OLED_ShowString(0, 6, str);

    OLED_Refresh();
}

/* 显示CCD图像(简化版) */
void OLED_ShowCCDImage(uint8_t* pixel)
{
   
    uint8_t page, col;

    OLED_Clear();

    /* 将128像素压缩到128列,每8行一个page */
    for(col = 0; col < 128; col++)
    {
   
        if(pixel[col] == 0)  // 黑线
        {
   
            /* 在中间行画点 */
            OLED_DrawPoint(col, 32, 1);
        }
    }

    OLED_Refresh();
}

2.8 串口调试(uart.c)

/**
  * @file uart.c
  * @brief 串口调试输出
  */
#include "uart.h"
#include "stdio.h"

/* 串口接收缓冲区 */
static uint8_t rx_buffer[256];
static uint16_t rx_index = 0;

/* 初始化串口 */
void UART_Init(uint32_t baudrate)
{
   
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;

    /* 使能时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);

    /* 配置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);

    /* 配置RX引脚 */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    /* 配置串口参数 */
    USART_InitStructure.USART_BaudRate = baudrate;
    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);

    /* 使能串口 */
    USART_Cmd(USART1, ENABLE);

    /* 使能接收中断 */
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);

    /* 配置NVIC */
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

/* 发送字符串 */
void UART_SendString(char* str)
{
   
    while(*str)
    {
   
        USART_SendData(USART1, *str++);
        while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
    }
}

/* 发送赛道数据 */
void UART_SendTrackData(TrackInfo track, ControlData ctrl)
{
   
    char buffer[128];

    sprintf(buffer, "TRACK: Center=%3d, Error=%5.1f, Type=%d, Found=%d\r\n",
            track.center_pos, track.error, track.track_type, track.is_found);

    UART_SendString(buffer);

    sprintf(buffer, "CTRL: Servo=%5.1f, LeftPWM=%5.1f, RightPWM=%5.1f\r\n",
            ctrl.servo_pid_out, ctrl.motor_left_pwm, ctrl.motor_right_pwm);

    UART_SendString(buffer);
}

/* 串口中断处理函数 */
void USART1_IRQHandler(void)
{
   
    uint8_t data;

    if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
    {
   
        data = USART_ReceiveData(USART1);

        if(rx_index < sizeof(rx_buffer)-1)
        {
   
            rx_buffer[rx_index++] = data;
        }

        /* 处理命令 */
        if(data == '\n' || data == '\r')
        {
   
            rx_buffer[rx_index] = '\0';
            Process_Command((char*)rx_buffer);
            rx_index = 0;
        }
    }
}

/* 处理串口命令 */
void Process_Command(char* cmd)
{
   
    if(strstr(cmd, "GET_TRACK"))
    {
   
        char response[50];
        sprintf(response, "TRACK_DATA\r\n");
        UART_SendString(response);
    }
    else if(strstr(cmd, "SET_SPEED:"))
    {
   
        float new_speed = atof(cmd + 10);
        car_params.base_speed = new_speed;
        UART_SendString("OK\r\n");
    }
    else if(strstr(cmd, "SET_PID:"))
    {
   
        // 格式:SET_PID:kp,ki,kd
        float kp, ki, kd;
        sscanf(cmd + 8, "%f,%f,%f", &kp, &ki, &kd);
        servo_pid.kp = kp;
        servo_pid.ki = ki;
        servo_pid.kd = kd;
        UART_SendString("PID Updated\r\n");
    }
}

三、Keil工程配置

3.1 工程设置

Target:
  - Device: STM32F103C8T6
  - Clock: 8MHz HSE → 72MHz PLL
  - RAM: 20KB
  - Flash: 64KB

C/C++:
  - Define: USE_STDPERIPH_DRIVER, STM32F103C8T6
  - Include Paths:
    .\
    ..\Libraries\CMSIS\Include
    ..\Libraries\STM32F10x_StdPeriph_Driver\inc
    ..\User
    ..\User\drivers
    ..\User\algorithm

Linker:
  - IROM1: 0x08000000, 0x10000  (64KB Flash)
  - IRAM1: 0x20000000, 0x5000   (20KB RAM)

3.2 编译优化

Optimization Level: -O1
One ELF Section per Function: ✓

参考代码 基于STM32的CCD智能寻迹小车源代码 www.youwenfan.com/contentali/56414.html

四、测试与调试

4.1 测试步骤

/* 测试1:CCD图像采集 */
void Test_CCDCapture(void)
{
   
    uint8_t pixel[128];

    TSL1401_Capture(pixel);

    printf("CCD Pixel Data:\r\n");
    for(int i = 0; i < 128; i++)
    {
   
        printf("%d ", pixel[i]);
        if((i+1)%16 == 0) printf("\r\n");
    }
}

/* 测试2:舵机控制 */
void Test_ServoControl(void)
{
   
    printf("Testing Servo...\r\n");

    for(int angle = 0; angle <= 180; angle += 30)
    {
   
        Servo_SetAngle(angle);
        printf("Angle: %d\r\n", angle);
        Delay_Ms(1000);
    }
}

/* 测试3:电机控制 */
void Test_MotorControl(void)
{
   
    printf("Testing Motors...\r\n");

    Motor_SetLeftSpeed(30);
    Motor_SetRightSpeed(30);
    Delay_Ms(2000);

    Motor_SetLeftSpeed(0);
    Motor_SetRightSpeed(0);
    Delay_Ms(1000);

    Motor_SetLeftSpeed(30);
    Motor_SetRightSpeed(0);  // 右转
    Delay_Ms(1000);

    Motor_Stop();
}

4.2 调试输出示例

CCD Smart Car Initializing...
Calibrating...
Calibration Done!

TRACK: Center= 64, Error=  0.0, Type=0, Found=1
CTRL: Servo=  0.0, LeftPWM= 30.0, RightPWM= 30.0

TRACK: Center= 58, Error= -6.0, Type=1, Found=1
CTRL: Servo=-15.2, LeftPWM= 24.0, RightPWM= 36.0

TRACK: Center= 70, Error=  6.0, Type=2, Found=1
CTRL: Servo= 18.5, LeftPWM= 36.0, RightPWM= 24.0

五、优化建议

5.1 性能优化

  1. 自适应曝光:根据环境光线自动调整CCD曝光时间
  2. 动态PID参数:根据赛道类型自动调整PID参数
  3. 速度规划:弯道提前减速,出弯加速
  4. 图像滤波:使用中值滤波去除噪点

5.2 功能扩展

/* 赛道记忆功能 */
typedef struct {
   
    uint16_t center_history[100];  // 中心位置历史
    uint8_t type_history[100];    // 赛道类型历史
    uint8_t index;                 // 当前索引
} TrackMemory;

/* 停车入库功能 */
void Auto_Parking(void)
{
   
    // 检测车库位置
    // 精确控制入库
}

/* 无线遥控功能 */
void Wireless_Control(void)
{
   
    // 蓝牙/WiFi遥控
    // 手机APP控制
}
相关文章
|
1月前
|
数据采集 JSON API
小红书笔记详情API实战总结(技术复盘)
本文为小红书笔记详情API实战复盘,涵盖OAuth2.0鉴权、代理与指纹配置避封、限流/风控应对等关键问题。详解note_id、access_token等核心参数及结构化返回字段(内容/媒体/互动/作者),助力竞品分析与内容监测。(239字)
|
3月前
|
传感器 数据采集 数据可视化
基于STM32的智能家居控制系统设计
基于STM32的智能家居控制系统设计
551 10
|
8天前
|
人工智能 缓存 弹性计算
阿里云服务器2核4G5M199元解析:独享型u1实例,性能、适用场景、购买和续费规则介绍
阿里云通用算力型u1实例(ecs.u1-c1m2.large)2核4G、5M带宽、80G ESSD Entry云盘,活动特惠价仅199元/年(官网价3498.36元),企业新老用户同享,续费同价至2027年3月31日,每人限购1台。该实例采用独享型架构,搭载Intel至强可扩展处理器,内网带宽1Gbit/s、收发包30万PPS、云盘IOPS 1万,性能稳定,适合企业官网、中小Web应用、轻量数据库及开发测试等场景。
|
1月前
|
JSON 自然语言处理 API
大模型应用:解锁大模型能力边界:Skill 与 Function Call的底层逻辑与实战应用.117
本文深入解析大模型能力扩展核心机制:Skill(技能)与Function Call(函数调用)的关系。Skill是标准化、可复用的能力单元(如计算器、天气查询),定义“能做什么”;Function Call是执行协议,实现“如何调用”。二者结合突破大模型在实时性、准确性、安全性上的局限,推动其从对话工具进化为可执行复杂任务的智能体。
319 6
|
1月前
|
人工智能 供应链 算法
从“小单困局”到供应链Agent:成本结构、博弈逻辑与人机协同的技术推演
本文剖析C2M服装供应链中“小单困局”的本质——切换成本在极小批量下不可摊销的数学必然。通过Agent集群实现成本透明化、智能拼单与品类感知,推动供应链从零和砍价转向正和协同。人机分工明确:AI做“数字包工头”,人当“关系架构师”。(239字)
|
1月前
|
人工智能 自然语言处理 机器人
[开源框架-实战]用 Hermes Agent 搭一个微信播报机器人
30 分钟,零 Python 代码,搭出一个每天早上 9 点把 GitHub Trending 推送到你微信的机器人。顺带把 Hermes 的 Skill、Gateway、Cron 四个招牌能力全用上。
593 8
|
2月前
|
算法 语音技术 数据安全/隐私保护
语音更改技术:变调与变速的原理及实现
语音更改技术:变调与变速的原理及实现
290 1
|
1月前
|
存储 安全 前端开发
加密货币社区 Google 官方邮件钓鱼威胁机理与防御体系研究
本文剖析2026年利用Google官方邮件通道实施的高级加密钓鱼攻击:通过伪造发件人名称、劫持安全提示、绕过SPF/DKIM/DMARC,使传统防护失效。提出四层闭环防御体系,并提供可落地的Python/JS检测代码,强调零信任校验与资产隔离为关键防线。(239字)
96 2
加密货币社区 Google 官方邮件钓鱼威胁机理与防御体系研究
|
1月前
|
安全 机器人 Python
三个工具,让 agent 在一次对话里完成研究、写码、调试与保存
本文展示了一个真正“具身智能”的多工具Agent:它能自动研究网页、编写/调试Python代码(如列表推导式)、运行验证并保存结果。核心不在工具本身,而在模型自主规划工作流——研究→写→测→修→存,全程无需硬编码逻辑。三工具即成系统,智能涌现于规划。
186 1
三个工具,让 agent 在一次对话里完成研究、写码、调试与保存
|
1月前
|
人工智能 JSON 安全
金融公共服务机构钓鱼邮件威胁治理研究 —— 以 NS&I 安全事件为例
本文基于英国NS&I三年钓鱼攻击数据(激增323%),揭示AI赋能下高仿真、精准化钓鱼新趋势,提出覆盖边界拦截、AI语义检测、行为管控、用户感知与应急响应的五层闭环防御体系,并提供可工程落地的检测规则与代码实现,为金融及公共服务机构提供可复用的智能防护框架。(239字)
84 6

热门文章

最新文章