一、设计
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 性能优化
- 自适应曝光:根据环境光线自动调整CCD曝光时间
- 动态PID参数:根据赛道类型自动调整PID参数
- 速度规划:弯道提前减速,出弯加速
- 图像滤波:使用中值滤波去除噪点
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控制
}