基于STM32的线性CCD巡线程序

基于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工程配置

  1. Target配置:

    • Device: STM32F103C8T6
    • Clock: 72MHz
    • Memory Model: Small
  2. C/C++配置:

    • Include Paths: 添加所有头文件路径
    • Define: USE_STDPERIPH_DRIVER
  3. 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 常见问题

  1. CCD无信号:

    • 检查SI/CLK/AO引脚连接
    • 检查曝光时间设置
    • 检查ADC配置
  2. 图像不稳定:

    • 增加曝光时间
    • 启用中值滤波
    • 检查电源稳定性
  3. 巡线不稳定:

    • 调整PID参数
    • 检查舵机机械安装
    • 检查电机差速
  4. 丢线频繁:

    • 降低速度
    • 增加CCD分辨率
    • 改善光照条件

 

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