基于STM32的线性CCD巡线程序
STM32线性CCD巡线解决方案,包含CCD驱动、图像处理、PID控制、电机舵机控制等功能,适用于智能车竞赛和机器人巡线应用。
一、系统架构
STM32线性CCD巡线系统架构:
├── CCD图像采集模块
│ ├── TSL1401/TSL1410线性CCD驱动
│ ├── SI/CLK时序控制
│ ├── ADC采样(DMA方式)
│ └── 曝光时间动态调整
├── 图像处理模块
│ ├── 中值滤波去噪
│ ├── 二值化处理
│ ├── 边缘检测
│ ├── 中心线提取
│ └── 弯道预测
├── 运动控制模块
│ ├── 舵机转向PID控制
│ ├── 电机速度PID控制
│ ├── 差速转弯控制
│ └、 停车检测
├── 系统保护模块
│ ├── 丢线保护
│ ├── 冲出赛道保护
│ └、 电池电压监测
└── 调试接口
├── OLED显示
├── 串口调试
└、 参数在线调整
二、硬件连接
2.1 TSL1401线性CCD引脚连接
TSL1401引脚 STM32引脚 功能说明
SI PA0 串行输入
CLK PA1 时钟信号
AO PA2 ADC输入(模拟输出)
VDD 3.3V 电源
GND GND 地
2.2 电机舵机控制引脚
舵机PWM PA6 TIM3_CH1
左电机PWM PA8 TIM1_CH1
右电机PWM PA9 TIM1_CH2
编码器A PB6 TIM4_CH1
编码器B PB7 TIM4_CH2
三、核心代码实现
3.1 CCD驱动头文件 (tsl1401.h)
#ifndef TSL1401_H
#define TSL1401_H
#include "stm32f10x.h"
#include <stdint.h>
// TSL1401参数
#define CCD_PIXELS 128 // 像素点数
#define CCD_EXPOSURE_MIN 10 // 最小曝光时间(ms)
#define CCD_EXPOSURE_MAX 500 // 最大曝光时间(ms)
#define CCD_EXPOSURE_DEF 100 // 默认曝光时间(ms)
// 图像阈值
#define IMG_THRESHOLD 100 // 二值化阈值
#define EDGE_THRESHOLD 50 // 边缘检测阈值
// 赛道参数
#define TRACK_WIDTH 80 // 赛道宽度(像素)
#define CENTER_POSITION 64 // 中心位置(像素)
#define LEFT_EDGE_MIN 10 // 左边缘最小位置
#define RIGHT_EDGE_MAX 118 // 右边缘最大位置
// CCD数据结构
typedef struct {
uint16_t pixel[CCD_PIXELS]; // 原始像素值
uint8_t binary[CCD_PIXELS]; // 二值化图像
uint8_t filtered[CCD_PIXELS]; // 滤波后图像
uint16_t left_edge; // 左边缘位置
uint16_t right_edge; // 右边缘位置
uint16_t center; // 中心位置
uint16_t width; // 赛道宽度
uint8_t lost_line; // 丢线标志
uint8_t straight_line; // 直道标志
uint8_t curve_line; // 弯道标志
float curvature; // 曲率
} CCD_Image;
// 控制参数
typedef struct {
uint16_t exposure_time; // 曝光时间
uint8_t auto_exposure; // 自动曝光使能
uint16_t threshold; // 二值化阈值
uint8_t median_filter; // 中值滤波使能
uint8_t edge_detection; // 边缘检测使能
} CCD_Config;
// 函数声明
void TSL1401_Init(void);
void TSL1401_StartCapture(void);
void TSL1401_StopCapture(void);
void TSL1401_ReadPixels(void);
void TSL1401_ProcessImage(CCD_Image *img, CCD_Config *config);
void TSL1401_AutoExposure(CCD_Image *img, CCD_Config *config);
void TSL1401_DisplayImage(CCD_Image *img);
#endif // TSL1401_H
3.2 CCD驱动实现 (tsl1401.c)
#include "tsl1401.h"
#include "stm32f10x_adc.h"
#include "stm32f10x_dma.h"
#include "stm32f10x_tim.h"
#include "delay.h"
// CCD时序控制引脚
#define SI_H() GPIO_SetBits(GPIOA, GPIO_Pin_0)
#define SI_L() GPIO_ResetBits(GPIOA, GPIO_Pin_0)
#define CLK_H() GPIO_SetBits(GPIOA, GPIO_Pin_1)
#define CLK_L() GPIO_ResetBits(GPIOA, GPIO_Pin_1)
// ADC相关
#define ADC_CHANNEL ADC_Channel_2
#define ADC_GPIO_PIN GPIO_Pin_2
static uint16_t adc_buffer[CCD_PIXELS];
static volatile uint8_t capture_complete = 0;
// 初始化GPIO
static void TSL1401_GPIO_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// SI和CLK引脚配置为推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// AO引脚配置为模拟输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
// 初始化ADC
static void TSL1401_ADC_Init(void) {
ADC_InitTypeDef ADC_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
// ADC配置
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &ADC_InitStructure);
// ADC通道配置
ADC_RegularChannelConfig(ADC1, ADC_CHANNEL, 1, ADC_SampleTime_55Cycles5);
ADC_Cmd(ADC1, ENABLE);
// ADC校准
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
// DMA配置
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)adc_buffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = CCD_PIXELS;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel1, ENABLE);
ADC_DMACmd(ADC1, ENABLE);
}
// 初始化TIM定时器
static void TSL1401_TIM_Init(void) {
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_TimeBaseStructure.TIM_Period = 1000 - 1; // 1ms定时
TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1; // 72MHz/72 = 1MHz
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_Cmd(TIM2, ENABLE);
}
// 初始化CCD
void TSL1401_Init(void) {
TSL1401_GPIO_Init();
TSL1401_ADC_Init();
TSL1401_TIM_Init();
SI_L();
CLK_L();
Delay_us(10);
}
// 开始采集
void TSL1401_StartCapture(void) {
uint8_t i;
// 启动ADC DMA传输
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
// 产生SI信号
SI_H();
CLK_H();
Delay_us(1);
CLK_L();
SI_L();
Delay_us(1);
// 读取128个像素
for (i = 0; i < CCD_PIXELS; i++) {
CLK_H();
Delay_us(1);
CLK_L();
Delay_us(1);
}
capture_complete = 1;
}
// 停止采集
void TSL1401_StopCapture(void) {
ADC_SoftwareStartConvCmd(ADC1, DISABLE);
capture_complete = 0;
}
// 读取像素数据
void TSL1401_ReadPixels(void) {
if (capture_complete) {
// 数据已在adc_buffer中,通过DMA传输
capture_complete = 0;
}
}
// 中值滤波
static void MedianFilter(uint16_t *input, uint8_t *output, uint8_t size) {
uint8_t i, j;
uint16_t temp[size];
for (i = 0; i < size; i++) {
temp[i] = input[i];
}
// 冒泡排序
for (i = 0; i < size - 1; i++) {
for (j = 0; j < size - 1 - i; j++) {
if (temp[j] > temp[j + 1]) {
uint16_t t = temp[j];
temp[j] = temp[j + 1];
temp[j + 1] = t;
}
}
}
// 取中值
uint16_t median = temp[size / 2];
// 二值化
for (i = 0; i < size; i++) {
output[i] = (input[i] > median) ? 255 : 0;
}
}
// 边缘检测
static void EdgeDetection(uint8_t *binary, uint8_t *edges, uint8_t size) {
uint8_t i;
for (i = 1; i < size - 1; i++) {
if (binary[i] == 255 && binary[i-1] == 0 && binary[i+1] == 0) {
edges[i] = 255; // 下降沿
} else if (binary[i] == 0 && binary[i-1] == 255 && binary[i+1] == 255) {
edges[i] = 255; // 上升沿
} else {
edges[i] = 0;
}
}
}
// 寻找边缘
static void FindEdges(uint8_t *binary, uint16_t *left_edge, uint16_t *right_edge, uint8_t size) {
uint8_t i;
uint8_t found_left = 0, found_right = 0;
// 从左向右找左边缘
for (i = LEFT_EDGE_MIN; i < size - 10; i++) {
if (binary[i] == 255 && binary[i+1] == 255 && binary[i+2] == 255) {
*left_edge = i;
found_left = 1;
break;
}
}
// 从右向左找右边缘
for (i = RIGHT_EDGE_MAX; i > 10; i--) {
if (binary[i] == 255 && binary[i-1] == 255 && binary[i-2] == 255) {
*right_edge = i;
found_right = 1;
break;
}
}
// 处理丢线情况
if (!found_left) *left_edge = 0;
if (!found_right) *right_edge = size - 1;
}
// 计算中心线
static uint16_t CalculateCenter(uint16_t left_edge, uint16_t right_edge) {
return (left_edge + right_edge) / 2;
}
// 图像处理
void TSL1401_ProcessImage(CCD_Image *img, CCD_Config *config) {
uint8_t i;
// 复制原始数据
for (i = 0; i < CCD_PIXELS; i++) {
img->pixel[i] = adc_buffer[i];
}
// 中值滤波
if (config->median_filter) {
MedianFilter(img->pixel, img->filtered, CCD_PIXELS);
} else {
// 直接二值化
for (i = 0; i < CCD_PIXELS; i++) {
img->filtered[i] = (img->pixel[i] > config->threshold) ? 255 : 0;
}
}
// 边缘检测
if (config->edge_detection) {
EdgeDetection(img->filtered, img->binary, CCD_PIXELS);
} else {
memcpy(img->binary, img->filtered, CCD_PIXELS);
}
// 寻找边缘
FindEdges(img->binary, &img->left_edge, &img->right_edge, CCD_PIXELS);
// 计算中心线
img->center = CalculateCenter(img->left_edge, img->right_edge);
// 计算赛道宽度
img->width = img->right_edge - img->left_edge;
// 判断赛道类型
if (img->width > TRACK_WIDTH * 0.8 && img->width < TRACK_WIDTH * 1.2) {
img->straight_line = 1;
img->curve_line = 0;
img->curvature = 0.0f;
} else if (img->width < TRACK_WIDTH * 0.8) {
img->straight_line = 0;
img->curve_line = 1;
// 计算曲率
img->curvature = (float)(CENTER_POSITION - img->center) / (float)CENTER_POSITION;
} else {
img->straight_line = 0;
img->curve_line = 0;
img->curvature = 0.0f;
}
// 丢线检测
if (img->left_edge == 0 || img->right_edge == CCD_PIXELS - 1 ||
img->width < 10 || img->width > 100) {
img->lost_line = 1;
} else {
img->lost_line = 0;
}
}
// 自动曝光
void TSL1401_AutoExposure(CCD_Image *img, CCD_Config *config) {
if (!config->auto_exposure) return;
uint8_t i;
uint32_t sum = 0;
uint16_t avg;
for (i = 0; i < CCD_PIXELS; i++) {
sum += img->pixel[i];
}
avg = sum / CCD_PIXELS;
// 根据平均亮度调整曝光时间
if (avg < 50) {
config->exposure_time = (config->exposure_time * 120) / 100;
} else if (avg > 200) {
config->exposure_time = (config->exposure_time * 80) / 100;
}
// 限制曝光时间范围
if (config->exposure_time < CCD_EXPOSURE_MIN) {
config->exposure_time = CCD_EXPOSURE_MIN;
} else if (config->exposure_time > CCD_EXPOSURE_MAX) {
config->exposure_time = CCD_EXPOSURE_MAX;
}
}
// 显示图像(调试用)
void TSL1401_DisplayImage(CCD_Image *img) {
uint8_t i;
printf("CCD Image:\r\n");
for (i = 0; i < CCD_PIXELS; i++) {
if (i % 16 == 0) printf("\r\n");
printf("%3d ", img->pixel[i]);
}
printf("\r\nLeft: %d, Right: %d, Center: %d, Width: %d\r\n",
img->left_edge, img->right_edge, img->center, img->width);
}
3.3 PID控制 (pid_control.h)
#ifndef PID_CONTROL_H
#define PID_CONTROL_H
#include <stdint.h>
// PID控制器结构
typedef struct {
float kp; // 比例系数
float ki; // 积分系数
float kd; // 微分系数
float integral; // 积分项
float prev_error; // 上次误差
float max_output; // 最大输出
float min_output; // 最小输出
float integral_limit; // 积分限幅
float dead_zone; // 死区
uint8_t enabled; // 使能标志
} PID_Controller;
// 电机控制参数
typedef struct {
float target_speed; // 目标速度
float current_speed; // 当前速度
float left_duty; // 左电机占空比
float right_duty; // 右电机占空比
uint8_t motor_enable; // 电机使能
} Motor_Control;
// 舵机控制参数
typedef struct {
float target_angle; // 目标角度
float current_angle; // 当前角度
float pwm_duty; // PWM占空比
uint8_t servo_enable; // 舵机使能
} Servo_Control;
// 巡线控制参数
typedef struct {
float steering; // 转向控制量
float throttle; // 油门控制量
uint8_t brake; // 刹车标志
uint8_t stop_line; // 停止线检测
} Line_Follow_Control;
// 函数声明
void PID_Init(PID_Controller *pid, float kp, float ki, float kd);
void PID_SetLimits(PID_Controller *pid, float min, float max);
float PID_Calculate(PID_Controller *pid, float target, float feedback);
void PID_Reset(PID_Controller *pid);
void Motor_Init(void);
void Motor_SetSpeed(Motor_Control *motor, float speed);
void Motor_Stop(Motor_Control *motor);
void Servo_Init(void);
void Servo_SetAngle(Servo_Control *servo, float angle);
void LineFollow_Control(CCD_Image *img, Line_Follow_Control *ctrl,
PID_Controller *steering_pid, PID_Controller *speed_pid);
#endif // PID_CONTROL_H
3.4 PID控制实现 (pid_control.c)
#include "pid_control.h"
#include "stm32f10x_tim.h"
#include "stm32f10x_gpio.h"
#include "delay.h"
// 初始化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->prev_error = 0.0f;
pid->max_output = 100.0f;
pid->min_output = -100.0f;
pid->integral_limit = 50.0f;
pid->dead_zone = 0.5f;
pid->enabled = 1;
}
// 设置输出限制
void PID_SetLimits(PID_Controller *pid, float min, float max) {
pid->min_output = min;
pid->max_output = max;
}
// 计算PID输出
float PID_Calculate(PID_Controller *pid, float target, float feedback) {
if (!pid->enabled) return 0.0f;
float error = target - feedback;
// 死区处理
if (fabs(error) < pid->dead_zone) {
return 0.0f;
}
// 比例项
float proportional = pid->kp * error;
// 积分项
pid->integral += pid->ki * error;
if (pid->integral > pid->integral_limit) {
pid->integral = pid->integral_limit;
} else if (pid->integral < -pid->integral_limit) {
pid->integral = -pid->integral_limit;
}
// 微分项
float derivative = pid->kd * (error - pid->prev_error);
pid->prev_error = error;
// 计算输出
float output = proportional + pid->integral + derivative;
// 输出限幅
if (output > pid->max_output) output = pid->max_output;
if (output < pid->min_output) output = pid->min_output;
return output;
}
// 重置PID
void PID_Reset(PID_Controller *pid) {
pid->integral = 0.0f;
pid->prev_error = 0.0f;
}
// 电机初始化
void Motor_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM1, ENABLE);
// PA8, PA9 作为PWM输出
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; // PWM周期1kHz
TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1; // 72MHz/72 = 1MHz
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
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 = 0;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM1, &TIM_OCInitStructure); // PA8 - 左电机
TIM_OC2Init(TIM1, &TIM_OCInitStructure); // PA9 - 右电机
TIM_Cmd(TIM1, ENABLE);
TIM_CtrlPWMOutputs(TIM1, ENABLE);
}
// 设置电机速度
void Motor_SetSpeed(Motor_Control *motor, float speed) {
if (!motor->motor_enable) return;
motor->target_speed = speed;
// 计算PWM占空比
float duty = fabs(speed) * 10.0f; // 速度转换为占空比
if (duty > 100.0f) duty = 100.0f;
if (speed > 0) {
motor->left_duty = duty;
motor->right_duty = duty;
} else if (speed < 0) {
motor->left_duty = -duty;
motor->right_duty = -duty;
} else {
motor->left_duty = 0;
motor->right_duty = 0;
}
// 设置PWM
TIM_SetCompare1(TIM1, (uint16_t)(motor->left_duty * 10)); // PA8
TIM_SetCompare2(TIM1, (uint16_t)(motor->right_duty * 10)); // PA9
}
// 电机停止
void Motor_Stop(Motor_Control *motor) {
motor->left_duty = 0;
motor->right_duty = 0;
TIM_SetCompare1(TIM1, 0);
TIM_SetCompare2(TIM1, 0);
}
// 舵机初始化
void Servo_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
// PA6作为舵机PWM输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// TIM3配置
TIM_TimeBaseStructure.TIM_Period = 20000 - 1; // 20ms周期
TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1; // 72MHz/72 = 1MHz
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
// PWM模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 1500; // 中位1.5ms
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM3, &TIM_OCInitStructure);
TIM_Cmd(TIM3, ENABLE);
}
// 设置舵机角度
void Servo_SetAngle(Servo_Control *servo, float angle) {
if (!servo->servo_enable) return;
servo->target_angle = angle;
// 角度转换为PWM脉宽 (0.5ms-2.5ms对应-90°到+90°)
float pulse_width = 1500.0f + angle * 10.0f; // 1.5ms + 角度*10us
if (pulse_width < 500) pulse_width = 500;
if (pulse_width > 2500) pulse_width = 2500;
servo->pwm_duty = pulse_width;
TIM_SetCompare1(TIM3, (uint16_t)pulse_width);
}
// 巡线控制
void LineFollow_Control(CCD_Image *img, Line_Follow_Control *ctrl,
PID_Controller *steering_pid, PID_Controller *speed_pid) {
// 计算转向控制量
float steering_error = CENTER_POSITION - img->center;
ctrl->steering = PID_Calculate(steering_pid, 0, steering_error);
// 计算速度控制量
float speed_error = 0.5f - fabs(steering_error) / 64.0f; // 转向越大速度越慢
ctrl->throttle = PID_Calculate(speed_pid, 0.5f, speed_error);
// 弯道减速
if (img->curve_line) {
ctrl->throttle *= 0.8f; // 弯道减速20%
}
// 丢线处理
if (img->lost_line) {
ctrl->throttle = 0.0f; // 停止
ctrl->brake = 1;
} else {
ctrl->brake = 0;
}
// 停止线检测
if (img->width > 100) { // 宽赛道可能是停止线
ctrl->stop_line = 1;
ctrl->throttle = 0.0f;
}
}
3.5 主程序 (main.c)
#include "stm32f10x.h"
#include "tsl1401.h"
#include "pid_control.h"
#include "delay.h"
#include "usart.h"
#include "oled.h"
// 全局变量
CCD_Image ccd_image;
CCD_Config ccd_config;
PID_Controller steering_pid;
PID_Controller speed_pid;
Motor_Control motor_ctrl;
Servo_Control servo_ctrl;
Line_Follow_Control follow_ctrl;
// 系统状态
typedef enum {
SYS_INIT = 0,
SYS_CALIBRATE,
SYS_RUNNING,
SYS_STOPPED,
SYS_ERROR
} System_State;
System_State system_state = SYS_INIT;
// 初始化系统
void System_Init(void) {
// 初始化延时
Delay_Init();
// 初始化串口
USART_Init(115200);
printf("Linear CCD Line Following System Starting...\r\n");
// 初始化OLED
OLED_Init();
OLED_ShowString(0, 0, "CCD Line Follow");
OLED_ShowString(0, 2, "Initializing...");
// 初始化CCD
TSL1401_Init();
ccd_config.exposure_time = CCD_EXPOSURE_DEF;
ccd_config.auto_exposure = 1;
ccd_config.threshold = IMG_THRESHOLD;
ccd_config.median_filter = 1;
ccd_config.edge_detection = 1;
// 初始化PID控制器
PID_Init(&steering_pid, 2.5f, 0.0f, 0.8f); // 转向PID
PID_Init(&speed_pid, 1.0f, 0.05f, 0.2f); // 速度PID
PID_SetLimits(&steering_pid, -45.0f, 45.0f);
PID_SetLimits(&speed_pid, 0.0f, 1.0f);
// 初始化电机和舵机
Motor_Init();
Servo_Init();
motor_ctrl.motor_enable = 1;
servo_ctrl.servo_enable = 1;
printf("System Initialized Successfully!\r\n");
OLED_ShowString(0, 2, "Initialized OK!");
Delay_ms(1000);
}
// 校准CCD
void Calibrate_CCD(void) {
uint8_t i;
printf("Calibrating CCD...\r\n");
OLED_ShowString(0, 2, "Calibrating...");
// 多次采样取平均值
uint32_t sum = 0;
for (i = 0; i < 10; i++) {
TSL1401_StartCapture();
Delay_ms(ccd_config.exposure_time);
TSL1401_ReadPixels();
uint8_t j;
for (j = 0; j < CCD_PIXELS; j++) {
sum += adc_buffer[j];
}
Delay_ms(100);
}
uint16_t avg = sum / (10 * CCD_PIXELS);
ccd_config.threshold = avg;
printf("Calibration Complete. Threshold: %d\r\n", ccd_config.threshold);
OLED_ShowString(0, 2, "Calibration OK!");
Delay_ms(1000);
}
// 主循环
int main(void) {
System_Init();
Calibrate_CCD();
system_state = SYS_RUNNING;
uint32_t last_time = 0;
while (1) {
switch (system_state) {
case SYS_RUNNING:
// 采集CCD图像
TSL1401_StartCapture();
Delay_ms(ccd_config.exposure_time);
TSL1401_ReadPixels();
// 处理图像
TSL1401_ProcessImage(&ccd_image, &ccd_config);
// 自动曝光
if (ccd_config.auto_exposure) {
TSL1401_AutoExposure(&ccd_image, &ccd_config);
}
// 巡线控制
LineFollow_Control(&ccd_image, &follow_ctrl, &steering_pid, &speed_pid);
// 设置舵机角度
Servo_SetAngle(&servo_ctrl, follow_ctrl.steering);
// 设置电机速度
if (follow_ctrl.brake) {
Motor_Stop(&motor_ctrl);
} else if (follow_ctrl.stop_line) {
Motor_Stop(&motor_ctrl);
system_state = SYS_STOPPED;
} else {
Motor_SetSpeed(&motor_ctrl, follow_ctrl.throttle);
}
// 显示信息
if (Delay_GetMs() - last_time > 100) { // 每100ms更新一次显示
OLED_Clear();
OLED_ShowString(0, 0, "CCD Line Follow");
OLED_ShowNum(0, 2, ccd_image.center, 3);
OLED_ShowString(40, 2, "Center");
OLED_ShowNum(0, 4, (uint16_t)follow_ctrl.steering, 3);
OLED_ShowString(40, 4, "Steer");
OLED_ShowNum(0, 6, (uint16_t)(follow_ctrl.throttle * 100), 3);
OLED_ShowString(40, 6, "Speed%");
last_time = Delay_GetMs();
}
// 串口输出调试信息
if (Delay_GetMs() % 1000 == 0) { // 每秒输出一次
printf("Center: %d, Steering: %.1f, Speed: %.1f\r\n",
ccd_image.center, follow_ctrl.steering, follow_ctrl.throttle);
}
break;
case SYS_STOPPED:
OLED_ShowString(0, 0, "System Stopped");
OLED_ShowString(0, 2, "Press Key to Restart");
Motor_Stop(&motor_ctrl);
Servo_SetAngle(&servo_ctrl, 0);
break;
default:
break;
}
Delay_ms(10); // 主循环延时
}
}
四、参数配置与调试
4.1 参数配置表 (config.h)
#ifndef CONFIG_H
#define CONFIG_H
// 系统配置
#define SYSTEM_CLOCK 72000000 // 系统时钟72MHz
#define CONTROL_PERIOD_MS 10 // 控制周期10ms
#define DISPLAY_PERIOD_MS 100 // 显示周期100ms
// CCD配置
#define CCD_EXPOSURE_TIME 100 // 曝光时间(ms)
#define CCD_AUTO_EXPOSURE 1 // 自动曝光
#define CCD_THRESHOLD 100 // 二值化阈值
#define CCD_MEDIAN_FILTER 1 // 中值滤波
#define CCD_EDGE_DETECT 1 // 边缘检测
// PID参数
#define STEERING_KP 2.5f // 转向比例系数
#define STEERING_KI 0.0f // 转向积分系数
#define STEERING_KD 0.8f // 转向微分系数
#define STEERING_MAX 45.0f // 最大转向角
#define SPEED_KP 1.0f // 速度比例系数
#define SPEED_KI 0.05f // 速度积分系数
#define SPEED_KD 0.2f // 速度微分系数
#define SPEED_MAX 1.0f // 最大速度
// 电机参数
#define MOTOR_MAX_SPEED 100.0f // 最大电机速度(%)
#define MOTOR_MIN_SPEED 10.0f // 最小电机速度(%)
// 舵机参数
#define SERVO_CENTER 90.0f // 舵机中位
#define SERVO_LEFT_MAX 135.0f // 左转最大角度
#define SERVO_RIGHT_MAX 45.0f // 右转最大角度
// 赛道参数
#define TRACK_WIDTH_PIXEL 80 // 赛道宽度(像素)
#define CENTER_POSITION 64 // 中心位置(像素)
#define CURVE_THRESHOLD 0.3f // 弯道阈值
#endif // CONFIG_H
4.2 调试工具 (debug_tools.py)
#!/usr/bin/env python3
import serial
import matplotlib.pyplot as plt
import numpy as np
from collections import deque
import threading
class CCDDebugger:
def __init__(self, port='COM3', baudrate=115200):
self.ser = serial.Serial(port, baudrate, timeout=1)
self.data_queue = deque(maxlen=500)
self.center_history = deque(maxlen=500)
self.steering_history = deque(maxlen=500)
self.speed_history = deque(maxlen=500)
self.running = False
def start(self):
self.running = True
thread = threading.Thread(target=self.read_serial)
thread.daemon = True
thread.start()
self.plot_data()
def read_serial(self):
while self.running:
try:
line = self.ser.readline().decode('utf-8').strip()
if 'Center:' in line:
parts = line.split(',')
if len(parts) >= 3:
center = float(parts[0].split(':')[1])
steering = float(parts[1].split(':')[1])
speed = float(parts[2].split(':')[1])
self.center_history.append(center)
self.steering_history.append(steering)
self.speed_history.append(speed)
except:
pass
def plot_data(self):
plt.ion()
fig, axes = plt.subplots(3, 1, figsize=(12, 8))
while self.running:
if len(self.center_history) > 10:
# 清除图表
for ax in axes:
ax.clear()
# 中心线位置
axes[0].plot(list(self.center_history), 'b-', linewidth=2)
axes[0].axhline(y=64, color='r', linestyle='--', alpha=0.5)
axes[0].set_title('CCD Center Position')
axes[0].set_ylabel('Pixel Position')
axes[0].set_ylim(0, 128)
axes[0].grid(True, alpha=0.3)
# 转向角度
axes[1].plot(list(self.steering_history), 'g-', linewidth=2)
axes[1].axhline(y=0, color='r', linestyle='--', alpha=0.5)
axes[1].set_title('Steering Angle')
axes[1].set_ylabel('Angle (degrees)')
axes[1].set_ylim(-50, 50)
axes[1].grid(True, alpha=0.3)
# 速度
axes[2].plot(list(self.speed_history), 'r-', linewidth=2)
axes[2].set_title('Speed Control')
axes[2].set_ylabel('Speed (%)')
axes[2].set_xlabel('Time (samples)')
axes[2].set_ylim(0, 100)
axes[2].grid(True, alpha=0.3)
plt.tight_layout()
plt.pause(0.1)
plt.pause(0.1)
def stop(self):
self.running = False
self.ser.close()
if __name__ == '__main__':
debugger = CCDDebugger()
try:
debugger.start()
except KeyboardInterrupt:
debugger.stop()
参考代码 基于STM32的线性CCD巡线程序 www.youwenfan.com/contentcnv/72188.html
五、编译与部署
5.1 Keil工程配置
-
Target配置:
- Device: STM32F103C8T6
- Clock: 72MHz
- Memory Model: Small
-
C/C++配置:
- Include Paths: 添加所有头文件路径
- Define: USE_STDPERIPH_DRIVER
-
Debug配置:
- Debugger: ST-Link
- Flash Algorithm: STM32F10x Med-density Flash
5.2 烧录步骤
# 使用ST-Link Utility烧录
st-link_cli.exe -c SWD -P program.bin 0x08000000 -V
# 或使用OpenOCD
openocd -f interface/stlink.cfg -f target/stm32f1x.cfg \
-c "program program.bin verify reset exit"
六、故障排除
6.1 常见问题
-
CCD无信号:
- 检查SI/CLK/AO引脚连接
- 检查曝光时间设置
- 检查ADC配置
-
图像不稳定:
- 增加曝光时间
- 启用中值滤波
- 检查电源稳定性
-
巡线不稳定:
- 调整PID参数
- 检查舵机机械安装
- 检查电机差速
-
丢线频繁:
- 降低速度
- 增加CCD分辨率
- 改善光照条件