简介《一种新型线阵CCD驱动电路的设计方法》是一份面向数码相机与CCD硬件设计学习者的技术文档聚焦线阵CCD驱动电路中光积分时间难以随环境光强动态调整的问题。文档针对直接数字电路驱动实现复杂、单片机驱动速度慢、计数分频法灵活性差等传统方案提出以可编程逻辑器件EPM7032C44控制光转移脉冲周期、在不改变系统主频的前提下动态调节光积分时间的设计思路并结合TCD1206UD的2236个光电二极管结构与驱动波形展开说明。压缩包内为1个doc文件约120KB篇幅紧凑适合作为硬件设计参考或课程资料查阅。内容还延伸到DSP带钢纵切机组自动对中系统的应用实例读者可从中获取驱动时序分析、饱和失真规避与复杂光照下信号质量保障的具体方法。已有271人学习。1. 强光下发白的线阵 CCD问题常常出在驱动时序上产线上那台线阵 CCD 相机白天调好的二值化阈值一到夜班整幅画面发白目标边缘和背景糊成一片换回白天又整体偏灰轮廓断断续续。这种现场我碰过不止一次追到最后发现既不是镜头脏了也不是算法参数没调好而是线阵 CCD 驱动电路把光积分时间写死了光强翻倍CCD 单元攒的电荷就翻倍输出直接顶到饱和光弱下来电荷太少信噪比不够物体和背景自然分不开。原稿给出的思路很短系统主频一点都不动靠可编程逻辑器件动态拉长空驱动脉冲数让光积分时间跟着光源强度走。这套做法适合已经用 CPLD 或 FPGA 产生时序、想把静态驱动改成自适应驱动的开发者尤其是做非接触尺寸测量、光电检测这类工况光变比较大的场景。2. 方案定位与技术选型为什么把积分时间交给 CPLD 去调2.1 三条传统路线各自的翻车点原稿先盘了三类常见的线阵 CCD 驱动电路做法这三条的毛病在今天的项目里依然能对上号。第一类是直接数字电路驱动法拿 74 系列计数器、分频器一级一级搭出来。它的优点是逻辑透明缺点是器件多、走线长、板子面积大改一个时序参数就得重新飞线割铜调试基本靠示波器一路一路往回追一旦某级竞争冒险出了毛刺排查成本高得离谱。第二类是单片机驱动法。用 MCU 的 IO 口翻转产生 φ1、φ2、φSH问题在于翻转速率和抖动都压不住中断响应还有不确定性做到几百 kHz 以上波形就开始变形高速数据采集场合根本撑不住。就算用定时器硬件输出多路信号的相位对齐也很难做准。第三类是计数分频法靠改系统主频或者改计数器初值来改积分时间。改主频会连带把模拟通道的滤波器截止频率、A/D 采样率一起带偏等于为了曝光去动整条信号链改计数器初值又会把驱动波形的相位关系搞乱SH 和 φ1 的对齐关系一变输出数据就整体错位。这三条路有一个共同点都是静态驱动CCD 的光积分时间在运行过程中是一个常数光源变了它不变。而 CCD 的输出电压幅度正比于光强乘积分时间光源一强就饱和失真一弱就景物与背景分不开这是物理层面的事后期算法补不回来。提示判断一个线阵 CCD 项目要不要做动态积分时间先看现场光源是不是会漂。光源恒定、只做暗电流和固定增益校正的场合静态驱动够用光源随环境、随工件反光变化大的场合静态驱动一定会撞墙。2.2 器件与工具链32 个宏单元的 CPLD 就够用原稿选的是 EPM7032C44 这一档的 CPLD32 个宏单元、44 脚封装属于典型的「小逻辑够用、成本可控、掉电不丢配置」的器件。选它而不是选 FPGA理由很实际这个设计里全部逻辑就是两个 12 位计数器加几个状态位加起来的触发器数量三十个上下正好卡在这颗器件的容量边缘用 FPGA 是杀鸡用牛刀还多出配置芯片和上电加载时序的麻烦选它而不是选单片机理由在前面说过微秒级的相位对齐和 MHz 级的计数软件翻转做不到确定性。开发流程就是 Verilog HDL 写逻辑综合、适配、引脚分配然后下载到片子里。整条链路上真正需要人工盯的只有两件事一是引脚分配要把四路输出放到同一组 IO 上避免走线延迟差把相位关系带歪二是综合报告里的宏单元占用率超过 90% 之后适配器会开始做逻辑重组本来对齐的信号可能被拆到不同宏单元时序就不再是想当然的那个样子。环节具体内容需要注意的地方逻辑描述Verilog HDL 行为级描述全部时序逻辑统一用非阻塞赋值综合适配生成熔丝映射到宏单元关注宏单元占用率和关键路径延迟引脚约束四路输出 时钟 复位 12 位数据口时钟走专用输入脚输出尽量同组板级验证示波器看四路波形相位先看 SH 周期再看 φ1/φR 对齐2.3 上板之前先把时序参数算清楚这个设计里所有的时间都是从系统主频 T0 派生出来的所以上板前必须把这张表算明白否则后面调积分时间就是在黑匣子里瞎试。原稿给的约束是一轮完整读出至少要 2236 个 T0因为 φ1 是 T0 二分频所以一个 SH 周期里至少要有 1118 个 φ1 脉冲即 TSH 1118T0SH 周期短于这个下限上一帧数据还没读完下一帧就开始输出信号直接失真。空驱动数的可调范围是 1 到 4095对应光积分时间从 2236T0 到 2236T0 4096T0。参数符号典型取值说明系统主频T01 μs1 MHz全系统统一时基模拟通道也按它设计一轮最小读出时钟NPIX2236有效像元 暗像元 虚设单元φ1/φ2 周期2T02 μs由 T0 二分频得到空驱动数NOSI1 ~ 4095微处理器按光强下发光积分时间Tint2.236 ~ 6.331 msNPIX·T0 ~ (NPIXNOSI)·T0行频fL447 ~ 158 Hz随积分时间反向变化这张表里最容易被忽略的是最后一行。积分时间拉长能救弱光代价是行频跟着往下掉后级 DSP 的取数节奏、缓存深度、控制环周期全都得跟着重新算。我一般会先按最坏工况最长积分时间算出最低行频拿这个数去校后级处理链路能不能跟上能跟上再往下做。3. 驱动时序拆解一个 SH 周期里四路信号怎么排队3.1 TCD1206UD 的像元排布与一轮读出原稿用的是 TCD1206UD 这款线阵 CCD结构上可以分成三段感光区、存储栅、模拟移位寄存器。感光区是 2236 个 PN 结光电二极管排成的阵列最前面 64 个和最后面 12 个被遮光层盖住专门用来做暗电流检测记为 Dn中间 2160 个是真正曝光的像敏单元每个单元 14 μm × 14 μm中心距也是 14 μm总感光长度大约 30.24 mm。感光区两侧是转移栅电极 SH再往外是两列并行的模拟移位寄存器奇偶像元分两路传输最后汇到输出单元 OS 和补偿输出单元 DOS。一轮完整的读出顺序是这样的OS 端先吐 13 个虚设单元信号再吐 51 个暗电流信号接着连续输出 S1 到 S2160 共 2160 个有效像素信号然后又是 9 个暗信号、2 个奇偶检测信号之后进入空驱动段。空驱动段不输出有效数据但计数器还在跑光积分还在继续累积这正是动态调积分时间的抓手——想延长积分时间往空驱动段里多塞时钟就行完全不用碰主频。注意不同厂家手册对 SH 的有效电平定义略有差别有的写高电平积分、下降沿转移有的写低电平积分。调试时别只信文档先上电用示波器看一眼 SH 和第一路有效像素输出的相对位置以实测波形为准这一步能省掉后面半天的返工。3.2 四路信号的相位关系与产生方式四路驱动信号里φ1 和 φ2 是模拟移位寄存器的转移时钟相位差半个周期由主频 T0 二分频再加一个反相得到φR 是复位脉冲每输出一个像素复位一次周期也是 2T0φSH 是光转移脉冲它的上升沿把感光区攒的电荷整体推进移位寄存器两个 SH 脉冲之间的间隔就是光积分时间。四路信号必须严格同步所以全部由同一个 T0 派生不允许任何一路走独立时钟源。原稿提到用统一主频保证同步这一条是整套电路的根基一旦引入第二个时钟域SH 和 φ1 的相位关系就不再确定输出数据会出现偶发的错位而且这种错位是概率性的非常难查。按时序推导一个工作周期是这样走的SH 有效感光区电荷转移到移位寄存器随后 1118 个 φ1 周期把 2236 个单元的数据依次推出数据推完之后进入空驱动段此时 SH 保持无效感光区重新开始积分空驱动计数到预置值再发一个 SH 脉冲进入下一轮。3.3 积分时间的上下界与空驱动数最小光积分时间对应空驱动数为零或取最小值的情况也就是 2236T0此时 CCD 刚读完上一帧就立刻开始下一帧没有任何额外等待。最大光积分时间对应空驱动数取满 4095即 2236T0 4095T0。这里有个工程上的取舍空驱动数不是越大越好。4095 这个上限是 12 位计数器给的实际项目里往往用不到这么大因为积分时间拉得越长行频越低运动工件的拖影越明显。做对中、测宽这类动态测量时我通常把上限压在 1024 左右宁可多给一点光源功率也不让拖影把边缘糊掉。另外下限也不能设成 0。因为 SH 脉冲本身要占几个 T0 的宽度如果空驱动数取 0SH 脉宽和计数回卷会撞在一起波形上表现为 SH 变窄或者直接被吃掉。稳妥的做法是给空驱动数留一个最小保护值比如 16专门用来装 SH 脉宽。4. Verilog 核心模块拆解两个计数器如何决定积分时间原稿贴出的 Verilog 程序是这套方法的核心但直接照抄是编译不过的——里面用希腊字母 φ 当标识符还有把组合逻辑当时钟用的写法。下面按可综合的版本重新拆一遍逻辑和原稿一致写法做了工程化修正。4.1 端口定义与命名修正先把端口和参数理清楚。φ 系列标识符统一改成 ASCII 的 phi 前缀计数器位宽按 12 位给足因为空驱动数上限是 4095正好卡在 12 位边界上。// ccd_drive.v —— 线阵 CCD 动态积分时间驱动 // 主频 T0 1 MHz全部输出由 T0 同步产生 module ccd_drive ( input wire clk, // 系统主频 T0 input wire rst_n, // 异步复位低有效 input wire [11:0] osi_in, // 微处理器下发的空驱动数1~4095 output reg phi_sh, // 光转移脉冲 output reg phi1, // 模拟寄存器转移时钟 output reg phi2, // 与 phi1 反相 output reg phi_r // 复位脉冲 ); localparam [11:0] NPIX 12d2236; // 一轮最小读出时钟数 localparam [11:0] OSI_MIN 12d16; // 空驱动下限保证 SH 有脉宽 localparam [11:0] OSI_MAX 12d4095; // 空驱动上限12 位封顶 reg [11:0] base_cnt; // 基本计数器0 ~ NPIX reg [11:0] osi_cnt; // 空驱动计数器 reg [11:0] osi_lat; // 本轮锁存的空驱动数防止运行中被改写参数说明NPIX是硬约束对应一轮读出的最小时钟数小于它就会丢数据OSI_MIN是保护值实际是给 SH 脉宽留的余量取值和 SH 保持时间有关OSI_MAX由计数器位宽决定想调大就得把位宽往上加但这个上限在多数测量场景里够用了。端口这边osi_in是 12 位并行口由微处理器的 IO 或者总线直接挂上来。4.2 φ1、φ2 与 φR 的产生这三路时钟都是在同一个 always 块里做分频保证它们永远同步不会因为综合工具各自优化而错开半个周期。always (posedge clk or negedge rst_n) begin if (!rst_n) begin phi1 1b0; phi2 1b1; // 上电时与 phi1 反相避免同相丢数 phi_r 1b0; end else begin phi1 ~phi1; // 二分频周期 2T0 phi2 phi1; // 比 phi1 晚一拍等效反相 phi_r ~phi_r; // 复位脉冲周期 2T0 end end这段的关键在phi2 phi1这一行。它没有写成phi2 ~phi1因为非阻塞赋值里右边的 phi1 取的是上一拍的值phi2 phi1得到的结果天然就是 phi1 延迟一个 T0而 phi1 本身是 T0 的二分频延迟一个 T0 正好等于反相逻辑上等价于反相器但在综合后不会产生额外的组合逻辑延迟。复位初值把 phi2 置 1是为了保证上电第一个周期两路就是反相关系如果都置 0第一个周期会同时为高移位寄存器会丢一拍数据。phi_r的处理比较粗放直接用二分频实际项目里复位脉冲的占空比往往要更窄通常做法是让它在 phi1 的上升沿之后延迟几个纳秒再拉高这需要细调先按二分频跑通再改。4.3 基本计数器、空驱动计数器与 SH 脉冲这一块是整个设计的主体两个计数器串联第一个数满 2236 就交给第二个继续数第二个数满就回卷并发 SH。always (posedge clk or negedge rst_n) begin if (!rst_n) begin base_cnt 12d0; osi_cnt 12d0; osi_lat OSI_MIN; phi_sh 1b0; end else if (base_cnt NPIX) begin base_cnt base_cnt 12d1; // 有效像素 暗像元读出段 phi_sh 1b0; end else if (osi_cnt osi_lat) begin osi_cnt osi_cnt 12d1; // 空驱动段光积分继续累积 phi_sh 1b0; end else if (osi_cnt osi_lat) begin phi_sh 1b1; // SH 上升沿开始电荷转移 osi_cnt osi_cnt 12d1; end else if (osi_cnt osi_lat 12d3) begin osi_cnt osi_cnt 12d1; // SH 保持 3T0 作为脉宽 phi_sh 1b1; end else begin base_cnt 12d0; // 一轮结束回卷 osi_cnt 12d0; phi_sh 1b0; osi_lat (osi_in OSI_MIN) ? OSI_MIN : (osi_in OSI_MAX) ? OSI_MAX : osi_in; end end endmodule逻辑说明前 2236 个 T0 全部用来把移位寄存器里的电荷推出去这段里 SH 保持低电平感光区在积累电荷推完之后进入空驱动段此时数据已经读完但积分还在继续空驱动数决定了这段要拖多长空驱动数到点之后拉高 SH保持 3 个 T0 的脉宽把感光区积累的电荷整体转移进移位寄存器然后计数器清零开始下一轮。osi_lat这一级寄存器是原稿里没写、但实际必须加的一环。如果直接用osi_in参与比较微处理器在计数过程中改了值比较条件会当场变化轻则这一轮 SH 周期抖动一下重则计数器提前回卷或者永远不回卷直接卡死。锁存到本轮结束后再更新能保证每一轮的积分时间是一个定值。4.4 微处理器侧的闭环调节代码驱动电路只负责执行真正决定空驱动数取值的是微处理器。它读一帧数据算出峰值和均值再回算下一次的积分时间。原稿提到用采集到的电荷信号检测光的强弱并自动设置预置值具体算法没展开工程上最常用的就是比例调节加限幅。/* 微处理器侧按一帧数据的峰值调整空驱动数 */ #define OSI_MIN 16 #define OSI_MAX 4095 #define PEAK_TARGET 3000 /* 12 位 A/D 目标峰值留约 20% 余量防饱和 */ static int32_t osi_cur 800; /* 上电默认值对应中等光强 */ uint16_t osi_next(uint16_t peak, uint16_t mean) { int32_t err (int32_t)PEAK_TARGET - (int32_t)peak; int32_t step (osi_cur * err) / 1250; /* 比例项误差 100 LSB 约调 8% */ /* 整体太暗时强行拉长积分时间避免在弱光下收敛太慢 */ if (mean 200 peak PEAK_TARGET) step osi_cur / 4; osi_cur step; if (osi_cur OSI_MIN) osi_cur OSI_MIN; if (osi_cur OSI_MAX) osi_cur OSI_MAX; return (uint16_t)osi_cur; }参数说明PEAK_TARGET不能贴满量程一定要留余量因为调节是滞后的等到峰值顶到 4095 再往回拉已经饱和了好几帧除数是比例系数1250 大约对应误差量程的三分之一调小响应快但容易振荡调大稳但收敛慢mean的判断是兜底逻辑专治画面整体发黑但峰值还有点数的异常工况比如镜头被遮了一半。这套调节每帧跑一次就够跑到行频级别反而会把自己绕进去。5. 避坑与排查这版驱动电路我踩过的五个坑5.1 现象微处理器改了预置值画面纹丝不动现象很直观调试软件里把空驱动数从 200 改成 2000串口回读确认写进去了但示波器上 SH 周期一点没变画面亮度也没变化。原因是预置值直接参与了运行中的比较而写入时刻正好落在计数器计数中途。如果新值比当前计数值小比较条件当拍就成立计数器立刻回卷SH 提前发出如果新值比当前值大很多本轮就永远等不到相等SH 一直不发出直到看门狗复位。两种情况在示波器上表现为周期乱跳或者干脆没有波形。解决办法就是前面代码里的osi_lat。把外部写入值先在端口上稳住本轮计数结束、计数器清零的那一拍再锁存进osi_lat运行中的比较只用锁存值。改成这样之后改参数只会影响下一帧波形永远干净。这个改动只多花 12 个触发器但在 32 宏单元的器件上要提前算好占用率别把适配器逼到做逻辑重组。5.2 现象SH 脉冲时有时无偶尔还冒出纳秒级毛刺现象是示波器上用正常触发抓不到 SH换成毛刺触发偶尔能抓到一个几纳秒的尖峰接到 CCD 上表现为整帧数据错位或者全黑。原因是把组合逻辑的输出当成了时钟用。原稿程序里有always(T0) phiR T0;和always(Clr) phiSH 1;这样的写法Clr是由计数器比较产生的一个组合信号把它当敏感列表里的时钟等于在片内造了一条组合逻辑驱动的时钟网络延迟不确定、容易出毛刺而且综合工具对这种写法会报警告很多人直接忽略了。解决办法只有一条全部改成同步设计所有输出都在posedge clk的 always 块里用非阻塞赋值产生SH 的脉宽不靠组合逻辑延迟靠计数器多计几个拍来实现。改完之后 SH 的边沿完全对齐 T0毛刺消失。这条经验对任何 CPLD 项目都成立片内时钟只留系统主频一个其余全部当数据信号处理。5.3 现象复位之后第一帧数据总是错的现象是每次上电或者手动复位第一帧读出来整体偏移几十个像素第二帧开始正常。原因是上电时四路信号的初值没有对齐。φ1 和 φ2 如果都复位成 0第一个周期两路同时为高移位寄存器会把同一批电荷推两次输出的像素序列整体错位SH 如果复位成高电平上电瞬间就多发了一个转移脉冲感光区里本来是空的推出来一堆噪声。解决办法是把所有寄存器的复位初值显式写死φ1 置 0、φ2 置 1、φSH 置 0、两个计数器置 0并且在复位释放之后先空跑一整轮再开放数据有效标志让后级丢掉第一帧。多半天的调试时间就耗在这上面后来我养成习惯任何时序模块的复位初值都一个一个写清楚绝不靠综合工具的默认值。5.4 现象强光下照样饱和弱光下照样发灰现象是闭环加上了参数也在动但极端工况下该饱和还是饱和该发灰还是发灰调节范围明显不够用。原因有两个。一是空驱动数的可调范围被卡死了代码里OSI_MAX设成了 4095但 12 位计数器本身已经占满再往上加会溢出回卷实际有效范围可能只有几百弱光下根本拖不够二是强光侧的下限设得太低OSI_MIN只留了 16SH 脉宽和回卷撞在一起强光下积分时间缩不下去输出顶死。解决办法是分两步先把计数器位宽从 12 位加到 13 位OSI_MAX提到 8191弱光侧的余量立刻宽出来再按实际光源动态范围重新标定上下限强光侧下限根据实测 SH 最小可分辨脉宽来定弱光侧上限根据允许的最大行周期来定。标定完画一条「光强—空驱动数」的曲线斜率不对就调比例系数别硬改限幅值去凑。5.5 现象积分时间拉满后帧率塌方后级开始丢帧现象是系统在弱光下调到最大积分时间画面亮度确实上来了但整机节拍掉了一半DSP 那边频繁报缓冲区溢出。原因是积分时间和行频是反比关系这一点在设计阶段最容易被忽略。空驱动数从 200 拉到 4095行周期从 2436T0 变成 6331T0帧率直接掉到原来的 38%。后级的 A/D 采样节奏、DMA 传输块大小、控制环周期全是按默认行频算的行频一变全都对不上。解决办法是把行频当成一个需要全链路通告的参数驱动器在每轮结束时输出一个帧同步脉冲后级用它做时间基准而不是用固定定时器同时按最坏工况最长积分时间重新算缓冲区深度和控制环周期宁可平时浪费一点余量也别在弱光时掉链子。如果节拍实在压不住就只能回到前面那条路——加光源功率而不是无限拉长积分时间。6. 二次开发把静态驱动改成自适应闭环的三条路径拿到这套电路之后真正花时间的不是把它跑通而是把它改成能适应自己工况的样子。我按改造成本从低到高排了三条路可以按项目阶段挑。第一条是改标定表不动逻辑。把「背景光强—空驱动数」做成一张 64 点的查找表烧进微处理器运行时读一次环境光传感器的值直接查表省掉闭环调节的收敛过程。这条路适合光源变化有规律、且能提前实测的产线改造成本几乎为零缺点是换一种工件就得重新标一次。改造路径改动位置适用场景主要代价查找表微处理器侧光源变化有规律换工况需重标峰值闭环微处理器侧通用场景需处理收敛振荡硬件自适应CPLD 逻辑内高实时性要求需重新综合适配第二条是把闭环做扎实也就是第 4.4 节那段比例调节。要补的是抗饱和逻辑连续 N 帧峰值顶到量程就强制缩小积分时间不看误差符号直接砍一半等峰值落回安全区再交还给比例项。这套逻辑我在几个项目里都用过专治反光工件突然进视野导致的连续饱和。第三条最激进把闭环的一部分下沉到 CPLD 里。做法是在片内加一个峰值检测比较器A/D 转换完的每个像素值和当前峰值寄存器比一下一帧结束把峰值直接送给积分时间调节状态机微处理器只负责下发目标值。好处是省掉一次跨芯片通信响应能快一到两帧代价是 32 宏单元的器件基本装不下得换更大容量的片子综合适配要重来一遍引脚也得重新分配。验证方法我一般固定成三步先用 testbench 跑纯仿真看 SH 周期是不是严格等于(2236 osi_lat) × T0再上板用示波器量 SH 和第一路有效像素的相位确认没有毛刺和丢拍最后拿手电筒和遮光板做人工光照扰动看闭环能不能在十帧之内收敛回目标峰值。三步里最容易被跳过的是第二步但恰恰是它抓到的问题最多。血泪经验是每次改完时序逻辑我都会强制把仿真和示波器这两步走一遍哪怕只改了一个比较条件。有一次为了赶进度跳过示波器直接烧片子联调结果 SH 脉宽被综合工具优化掉了一个时钟整机跑了一下午都是错的最后回到波形上五分钟就定位了。希望这些能帮到你少走几段弯路。本文还有配套的精品资源点击获取