线性CCD智能小车循迹:STM32F103与PID控制实战
2026/9/18 1:03:47 网站建设 项目流程

简介:围绕线性CCD循迹的智能小车控制系统设计文档,面向参加NXP杯智能汽车竞赛的高校队伍、嵌入式与控制算法初学者,以及需要借鉴循迹方案的系统开发者。内容以MC9S12X S128单片机为控制核心,讲解利用蓝宙TSL1401线性CCD识别两侧黑色引导线白色道路的完整实现思路。硬件部分涵盖7.2V电池供电与5V、6V降压设计、CCD图像采集、$3010舵机转向、欧姆龙编码器测速,以及加装74LVC245隔离保护的MOS管双电机驱动;软件部分重点说明多个固定阈值滤波、跳变沿黑线边缘提取、分段非线性位置式PD转向控制与增量式PID速度闭环调速,并给出直道、起跑线、十字等赛道元素的识别与抗干扰处理经验。压缩包内为1个PDF文件,约139KB,篇幅精炼、便于打印研读。目前已有122人学习,适合希望快速掌握线性CCD智能车硬件架构与控制算法、用于课程设计或竞赛备赛的读者参考。

1. 线性CCD智能小车为什么比红外循迹更适合做控制系统

赛道上有反光接缝、环境灯管频闪、黑线边缘毛刺,红外对管只返回“有”或“无”,一旦车身偏出半厘米,控制系统拿到的仍是同一个开关量,转向舵机只能在最后一刻猛打。线性CCD把一维视场里的灰度分布连续采回来,哪怕黑线只有几个像素宽,也能算出中心位置、线宽和左右边缘斜率。对智能小车控制系统来说,控制量从离散开关变成了连续偏差,位置式PID才有发挥空间。这套方案适合电子设计竞赛、工创赛智能物流小车、智能搬运小车这类需要在分岔口和急弯提前判断的场合,也适合从51单片机智能小车升级到STM32智能小车的课程设计。

2. 线性CCD成像原理与主控选型:从像素输出到STM32F103采样

2.1 线性CCD的电荷积分与曝光时间怎么定

线性CCD内部是一排光敏像素,每个像素在积分时间内把光生电荷攒起来,再由转移栅依次搬到模拟输出脚。常见器件如TCD1206、TCD1304,输出是一串随光强变化的电压脉冲。曝光时间就是积分时间,它直接决定输出电压幅度:时间太短,白底和黑线都接近0,阈值没法分;时间太长,白底饱和,黑线也被周围散射光抬起来,边缘变糊。

实际调试时,先把CCD对准白纸,用示波器看模拟输出峰峰值。调整积分时间,让白底输出落在ADC量程的70%到85%,黑线落在地板附近。曝光时间通常在1ms到5ms之间,再长就会在高速过弯时产生运动模糊。帧率近似等于1/(曝光时间+读出时间),读出时间由像素数和时钟频率决定。

曝光时间理论帧率适用车速现象
0.5ms约800Hz高速直道光线暗时输出太小
1ms约500Hz中高速多数室内赛道可用
2ms约300Hz中速抗环境光稍好
5ms约150Hz低速调试过弯拖影明显

提示:曝光时间不要只在静态下调好就固定,电池电压下降后红外补光或环境灯变化,白底输出会漂,需要留动态阈值余量。

2.2 主控选型:STM32F103、51单片机、Arduino谁更适合线性CCD智能小车

线性CCD的像素多、帧率高,主控要同时做ADC采样、二值化、中线提取、PID和电机PWM。51单片机智能小车常见的是STC89C52一类,机器周期长,ADC外设弱,跑128像素、200Hz以上时中断会挤占控制周期,适合低速电磁三轮智能小车循迹,不太适合线性CCD。Arduino智能小车开发快,analogRead加中断也能跑,但analogRead阻塞时间长,多路采样时抖动大,适合验证算法,不适合追求高帧率。

STM32F103系列更均衡,72MHz主频、12位ADC、定时器触发和DMA搬运可以把CPU从采样里解放出来。若选STM32F103ZET6这类大容量型号,定时器、串口和IO都宽裕,方便接OLED和编码器。选型时重点看三点:ADC是否支持外部触发和DMA;定时器能否输出CCD所需的转移时钟;串口是否够用,因为调CCD波形时串口比调试器直观。

平台ADC与DMA控制周期适合场景
STM32F10312位,支持定时器触发+DMA1ms以内线性CCD智能小车主控
51单片机多数无ADC,需外扩5ms以上低速循迹、教学
Arduino10位,阻塞式读取2ms到5ms算法验证、快速原型

2.3 线性CCD驱动与ADC+DMA采样最小代码

下面以STM32 HAL库为例,思路是用定时器更新事件触发ADC,再让DMA把一帧像素搬到数组。CCD的转移时钟由另一个定时器PWM输出,这里不展开寄存器配置,只保留采样主干。

// STM32F103 + 线性CCD:定时器触发ADC,DMA搬运一帧像素 #define CCD_PIXELS 128 uint16_t ccd_raw[CCD_PIXELS]; void CCD_Sample_Init(void) { // ADC1 外部触发选择 TIM2_TRGO,上升沿启动 // DMA1_Channel1 循环模式,搬 CCD_PIXELS 个半字 HAL_ADC_Start_DMA(&hadc1, (uint32_t *)ccd_raw, CCD_PIXELS); } // 每次DMA传输完成中断里置位,主循环处理 volatile uint8_t ccd_frame_ready = 0; void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { if (hadc->Instance == ADC1) { ccd_frame_ready = 1; // 一帧采集完成 } }

逻辑说明:CCD每输出一个像素,ADC就采一次,DMA自动递增目标地址,不需要在中断里逐个读寄存器。参数说明:CCD_PIXELS要和实际使用的像素数一致,多采会浪费时间,少采会丢边缘;ADC采样时间要小于像素时钟周期的一半,否则相邻像素串扰;DMA用循环模式,主循环来不及处理时新帧会覆盖旧帧,调试阶段可以改成正常模式并在回调里重启。

3. 从一维灰度到黑线偏差:线性CCD二值化、边缘检测与中线提取

3.1 固定阈值二值化为什么在赛道翻车

固定阈值在实验室白纸上好用,一到赛场就出问题:灯管照到赛道一侧,白底从3000跳到3600,黑线从400升到900,同一个阈值会把半边白底判成黑线。常见做法是用一帧内的最亮和最暗做动态阈值,或者对每帧做归一化后再比较。动态阈值不能简单取中值,因为黑线只占少数像素,中值容易落在白底上。更稳的是取最大最小值的加权,再配合边缘梯度。

// 一帧内动态阈值二值化,white_is_high表示白底ADC值大 #define CCD_N 128 uint8_t ccd_bin[CCD_N]; void ccd_binarize(uint16_t *raw, uint8_t *bin, uint16_t n, uint8_t white_is_high) { uint16_t vmin = 4095, vmax = 0; for (uint16_t i = 0; i < n; i++) { if (raw[i] < vmin) vmin = raw[i]; if (raw[i] > vmax) vmax = raw[i]; } // 阈值偏向黑线一侧,避免白底噪声被误判 uint16_t th = vmin + (uint16_t)((vmax - vmin) * 0.35f); for (uint16_t i = 0; i < n; i++) { uint8_t is_white = white_is_high ? (raw[i] > th) : (raw[i] < th); bin[i] = is_white ? 1 : 0; // 1为白,0为黑 } }

逻辑说明:先统计一帧的极值,再按比例取阈值,比固定值更能适应光照变化。参数说明:white_is_high根据CCD输出极性设置,TCD1206常见白底输出高;0.35f是经验系数,光线均匀时可取0.5,反光严重时降到0.25。如果一帧内全是白底,vmax-vmin很小,阈值会接近vmin,此时应当用上一帧阈值或直接判丢线。

3.2 边缘检测与中线计算:跳变沿、加权质心、宽度有效性

二值化后要从中线偏差。最简单的是找左右跳变沿:从左往右第一个白到黑的下降沿是左边缘,从右往左第一个白到黑的下降沿是右边缘,取平均就是黑线中心。这种方法在十字路口会找到多个沿,需要加宽度过滤。更抗噪的是对黑点做加权质心,把每个黑像素的坐标乘权重累加,权重可以用灰度值或到上一帧中心的距离。

方法抗噪计算量适用
左右跳变沿极小单线、无十字
加权质心弯道、虚线
多段聚类十字、分岔
边缘梯度拟合高速、模糊赛道
typedef struct { float center; // 黑线中心像素坐标 int left; // 左边缘 int right; // 右边缘 uint8_t valid; // 是否有效 } LineInfo; LineInfo ccd_get_line(uint8_t *bin, int n) { LineInfo info = {0, -1, -1, 0}; float sum = 0, cnt = 0; for (int i = 1; i < n - 1; i++) { if (bin[i] == 0) { // 黑点 sum += i; cnt += 1.0f; } // 记录第一个左沿和最后一个右沿,用于线宽判断 if (bin[i-1] == 1 && bin[i] == 0 && info.left < 0) info.left = i; if (bin[i] == 0 && bin[i+1] == 1) info.right = i; } if (cnt > 3) { // 黑点太少视为丢线 info.center = sum / cnt; info.valid = 1; } return info; }

逻辑说明:先累加所有黑点坐标求质心,同时记录左右沿用于线宽判断。参数说明:cnt > 3是有效性门槛,像素少时质心会跳;如果赛道有虚线,门槛要降到2并配合上一帧预测。实际控制时偏差用info.center - CCD_N/2,再除以CCD_N/2归一化到-1到1,PID参数才好整定。

3.3 偏差映射到舵机与差速:位置式PID的C语言实现

线性CCD给出的偏差有正负,位置式PID输出给舵机或左右轮差速。舵机控制常见做法是输出PWM脉宽,中值1500us,左右限幅到1000us和2000us。差速控制则把PID输出加到左右轮PWM上,同时用速度环维持总速度。下面是一个带积分限幅和死区的PID。

typedef struct { float kp, ki, kd; float err, last_err, integral; float out_max; float dead_zone; } PID; float pid_calc(PID *p, float target, float actual) { p->err = target - actual; if (p->err > -p->dead_zone && p->err < p->dead_zone) { p->err = 0; // 小偏差不动作,防止舵机抖 } p->integral += p->err; if (p->integral > p->out_max) p->integral = p->out_max; if (p->integral < -p->out_max) p->integral = -p->out_max; float d = p->err - p->last_err; p->last_err = p->err; return p->kp * p->err + p->ki * p->integral + p->kd * d; }

逻辑说明:误差是目标中线减当前中线,输出正负对应左右转。参数说明:kp先给1.0左右,能回中线但不过冲;ki通常给0到0.05,大了会在弯道积分饱和;kd给0.05到0.3,抑制过冲但会放大CCD噪声;dead_zone按像素定,常见1到2个像素;out_max对应舵机最大偏转或差速上限。调参顺序是先kp,再加kd,最后少量ki

4. 线性CCD智能小车控制系统闭环调参:速度环、转向环与丢线保护

4.1 双环控制结构:转向环和速度环如何解耦

线性CCD智能小车如果只做转向PID,直道能跑,进弯会冲出去,因为速度没降。常见做法是串级:外环用中线偏差算目标速度,内环用编码器算实际速度。转向环单独跑,输出舵机角度或左右差速。速度环的目标值可以由偏差绝对值决定:偏差大就降速,偏差小就加速。这样直道全速,弯道自动刹车。

// 双环控制:偏差大则降速,转向环独立输出 float steer_pid(PID *sp, float err_norm) { return pid_calc(sp, 0.0f, err_norm); // 目标偏差为0 } float speed_target(float base_speed, float err_norm) { float abs_err = err_norm > 0 ? err_norm : -err_norm; float reduce = abs_err * 40.0f; // 偏差1.0时降40 float target = base_speed - reduce; return target < 15.0f ? 15.0f : target; }

逻辑说明:转向环目标始终是0偏差,速度环目标随偏差收缩。参数说明:base_speed是直道编码器脉冲目标值,按电机减速比实测;40.0f是降速系数,赛道急弯多就加大;15.0f是最低速度,太低会在弯中失速。两个环的周期可以不同,转向环1ms到2ms,速度环5ms到10ms。

4.2 曝光时间、帧率与车速的匹配表

CCD帧率决定了控制系统能多快看到偏差。假设车重和轮胎抓地力固定,过弯时从看到线到打舵的延迟超过控制周期,PID再快也救不回来。下面给一个经验匹配表,实际以跑车测试为准。

车速建议帧率曝光时间控制周期备注
0.5m/s100Hz3ms5ms调试阶段
1.0m/s200Hz2ms3ms稳定循迹
1.5m/s300Hz1ms2ms需要补光
2.0m/s500Hz0.5ms1ms直道多、弯道少

注意:帧率不是越高越好,曝光时间缩短后信号变小,ADC噪声占比变大,二值化会抖。先保证白底输出稳定,再提帧率。

4.3 丢线、十字、急弯的工况状态机

线性CCD在十字路口会看到左右两条黑线,在急弯会丢掉一侧边缘,在虚线赛道会间歇丢线。如果只用一帧中线,控制器会来回甩。常见做法是加状态机:正常循迹、丢线预测、十字、急弯。丢线时用上一帧偏差和陀螺仪或编码器差速做短时预测,超过时间阈值就停车。

typedef enum { LINE_NORMAL, LINE_LOST, LINE_CROSS, LINE_CURVE } LineState; LineState line_state = LINE_NORMAL; float last_err_norm = 0; int lost_cnt = 0; void line_state_update(LineInfo *info, float err_norm) { if (!info->valid) { lost_cnt++; if (lost_cnt < 10) { line_state = LINE_LOST; // 短时丢线,用上次偏差 } else { line_state = LINE_NORMAL; // 长时间丢线由上层停车 } } else { lost_cnt = 0; line_state = LINE_NORMAL; last_err_norm = err_norm; } // 线宽超过阈值且左右沿都在,可判十字 if (info->valid && (info->right - info->left) > CCD_N * 0.6f) { line_state = LINE_CROSS; } }

逻辑说明:丢线计数器区分短时遮挡和真正冲出赛道,十字用线宽判断。参数说明:lost_cnt < 10按帧率调整,200Hz下约50ms;线宽阈值0.6f要按赛道黑线宽度标定。急弯状态可以用偏差绝对值大于0.7并持续几帧来触发,触发后强制降速。

4.4 用串口波形调CCD:把一维像素发到上位机

调CCD最有效的手段不是盯着车跑,而是把一帧像素发到串口上位机看波形。波特率至少115200,一帧128像素用十六进制发送,帧头加0xAA 0x55,帧尾加校验。上位机画出来的波形能直接看出曝光是否合适、阈值是否切偏、边缘是否被反光干扰。

// 串口发送一帧CCD原始数据,帧头0xAA55,像素低字节在前 void ccd_send_frame(uint16_t *raw, int n) { uint8_t head[2] = {0xAA, 0x55}; HAL_UART_Transmit(&huart1, head, 2, 10); for (int i = 0; i < n; i++) { uint8_t buf[2]; buf[0] = raw[i] & 0xFF; buf[1] = (raw[i] >> 8) & 0xFF; HAL_UART_Transmit(&huart1, buf, 2, 10); } }

逻辑说明:帧头用于上位机对齐,两个字节表示一个12位ADC值。参数说明:HAL_UART_Transmit的超时10ms在115200下够用,但会阻塞控制循环,实际可以改成DMA发送。发送频率不要等于控制频率,每5帧发一次即可,避免占用CPU。

5. 线性CCD智能小车的进阶技巧:标定、抗光干扰与故障定位

5.1 全白全黑标定与归一化

线性CCD个体差异和镜头安装角度会让同一块白底在不同像素上输出不同。跑车前做一次全白和全黑标定,把每个像素的white[i]black[i]存下来,运行时用(raw[i]-black[i])/(white[i]-black[i])归一化到0到1,再统一二值化。这样即使左侧像素偏暗,也不会被误判成黑线。标定数据可以存在Flash里,每次上电读取。

5.2 抗光干扰与机械安装

环境光干扰主要来自顶部灯管和赛道反光。机械上给CCD加遮光罩,镜头离地高度控制在8cm到15cm,视场覆盖黑线两侧各两倍线宽。镜头前加红外滤光片配合红外补光,能压掉可见光频闪,但要注意补光均匀,否则中间亮两边暗。程序上可以在二值化前做相邻像素滑动平均,窗口3到5,代价是边缘变钝,高速时慎用。

5.3 线性CCD智能小车控制系统故障排查表

现象可能原因检查方法处理
波形全高曝光过长或没接转移时钟示波器看CCD输出脚缩短积分时间,查时钟
波形全低曝光过短或镜头盖未开对准白纸看峰峰值加曝光,查补光
中线跳动阈值切到噪声串口看二值化结果改动态阈值系数
过弯冲出帧率低或降速不够看串口帧周期提帧率,加降速系数
十字误判线宽阈值太宽看左右沿距离收紧线宽阈值
舵机抖PID的kd过大看偏差波形降kd,加死区

最后一条实用技巧:把CCD固定在车头后,先用手推车走完整赛道,同时用串口记录每帧的中线和偏差,回到电脑上离线跑PID参数,比直接上路撞车快得多。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询