☰
TSL1401线性CCD入门:128点采集、曝光与循迹调试指南
2026/10/31 9:03:54 网站建设 项目流程

简介:《快速上手TSL1401线性CCD》是一份面向智能车竞赛光电组、嵌入式图像采集初学者及需要快速调试线阵CCD的读者的技术文档,重点解决英文手册阅读门槛高、时序与曝光参数不易理解的问题。压缩包仅含1个PDF文件,约342KB,内容围绕TSL1401的128像素线阵结构展开,梳理了与面阵CCD的差异、引脚功能、SI与CLK时序、积分时间即曝光时间的关系,并比较单次曝光法和连续曝光法在采集周期与CPU占用上的取舍。文中还结合亮度反馈思路讨论阈值选择与常见采集问题,便于读者把原理落到单片机AD采样和程序框架中,也可用于实验排错与竞赛备查。目前已有426人学习,适合作为快速入门该线性CCD的轻量资料。

1. 先搞懂它到底在看什么:一条线,128 个点

很多新手在 TSL1401 线性 CCD 快速上手这件事上卡住,原因往往不是代码,而是心里有个疑问:这玩意儿长得像摄像头,接上电却不给画面,只吐一串电压,它到底在干嘛?把它想成超市收银台扫条码的那道红光就通了——它不看整张照片,只沿一条线看过去,把这条线上 128 个点的明暗,变成 128 个电压值送出来。这份文档讲的就是怎么让这条线“看见”东西:引脚怎么接、曝光时间怎么调、128 个点怎么读进数组、镜头拧到哪才算清楚。它适合刚上手循迹小车、想用一条线判断黑线偏左还是偏右的新手,也适合任何想弄明白“一维图像”是什么的人。

2. 认识这块小板子:6 个引脚怎么接、128 个像素怎么排队

2.1 为什么新手从线性 CCD 上手反而更快

面阵 CCD 和线性 CCD 的差别,说到底是“拍一张照片”和“扫一条线”的差别。面阵芯片一次给你一整个矩形画面,分辨率为 m×n,你看得见形状、看得见轮廓,但代价是数据量大、传输时序复杂、对单片机的内存和算力都有要求。线性 CCD 一次只给你一行,TSL1401 这一行就是 128 个点,输出的是 128 个电压值,你拿到的是一条“明暗曲线”,不是一张图。

对刚入门的人来说,这个“窄”反而是好事。下面这张表把两者放在一起,你就明白为什么很多人建议新手先玩线性 CCD:

对比项面阵 CCD(常见摄像头模块)线性 CCD(TSL1401)
输出图像二维,m×n一维,128×1
单帧数据量几万到几十万个点128 个点
接线数据线多、时序复杂5 根有效线就能跑起来
典型用途识别形状、数字、路标找一条黑线的位置

128 个点意味着什么?意味着一帧数据在内存里只占 128 个uint16_t,也就是 256 字节;意味着你用串口把整帧数据打印出来,一眼就能看完;意味着“处理图像”这件事,退化成“在一串数字里找最暗的那一段”。循迹小车里的光电组之所以指定用线性 CCD,道理也在这里——它不需要知道前面是圆是方,它只需要知道黑线在 128 个点里的第几个位置。

我见过不少新手一上来就抱着一块面阵摄像头模块啃,啃了两周连数据都没读稳,最后放弃。换个思路,先用 128 个点把“采集—观察—判断”这个闭环跑通,再回头碰二维图像,心里会踏实得多。

2.2 六个引脚怎么接:对着表一根根插

TSL1401 的封装不大,但引脚功能划分得很清楚:两个电源脚、一个时钟脚、一个启动脚、一个模拟输出脚,剩下的空着。先把下面这张表对着模块丝印看一遍,心里有数了再动手接线。

引脚名编号干什么用的接到哪里
VDD4供电电源3V 至 5V 正极
GND6、7地电源负极,两个脚都要接
SI1串行输入,标记一帧数据的开始单片机普通 GPIO 输出
CLK2时钟输入,控制电荷转移、像素输出和复位单片机普通 GPIO 输出
AO3模拟电压输出单片机的 AD 输入脚
NC5、8空脚,不用连接悬空

接线的顺序建议按“地—电源—信号—输出”来走,别一上来就把线全插上再检查:

  1. 先把 GND 两个脚都接到电源负极,这一步最容易漏,漏了后面所有现象都像玄学。
  2. 再接 VDD,注意模块供电范围是 3V 至 5V,和你单片机的电平保持一致,别接反。
  3. 把 SI 和 CLK 分别接到两个普通 GPIO 上,优先挑同一组端口、相邻的引脚,后面写代码时好统一操作。
  4. 最后把 AO 接到单片机的 AD 输入通道上,记下通道号,代码里要用。
  5. NC 的两个脚保持悬空,别顺手接个地或者接个电源上去。

接完之后先别急着写采集程序,用万用表量一下 VDD 和 GND 之间是不是你期望的电压,确认没接反再接单片机。这一步花不了三十秒,但能省掉后面一晚上的排查。

这里有个新手常问的问题:AO 要不要加运放?答案是不用。TSL1401 的 AO 输出引脚内部已经带了增益调整器,输出的电压幅度直接就能进单片机的 AD,不需要外面再搭一级放大电路。后面第 5 章会专门讲这个坑,先记住结论就行。

2.3 内部发生了什么:128 个小水桶排队倒水

想真正用好这块芯片,得知道它内部在忙什么,但不需要背英文手册。可以把它想象成下着雨的院子里,摆了一排 128 个小水桶,每个桶底下都有个小阀门。

每个小水桶就是一个“像素”,也就是一个光电二极管加它自己的积分电路。下雨的时候,桶在接水;雨越大,也就是光越强,同样时间接到的水越多;接的时间越长,水也越多。桶里的水量,对应到芯片上就是输出的灰度值,也就是 AO 脚上的电压。所以原文里那句话“像素的输出值与光强和积分时间成正比”,翻译过来就是:水 = 雨量 × 接水时间。

那 128 个桶怎么排队倒水?靠的就是 SI 和 CLK 两个信号,背后是一套开关逻辑控制和移位寄存器电路。SI 相当于吹一声哨:“全体注意,新一轮开始。”CLK 相当于传送带:“下一个,把桶里的水倒出来量一量。”每来一个 CLK 的下降沿,就有一个桶的电压被送到 AO 脚上,你在这一刻读 AD,读到的就是这个像素的值。

芯片里还有一个叫 Gain Trim 的模块,出厂时就把增益调好了,所以从 AO 出来的电压不用再经过任何外部放大,直接进单片机 AD 引脚。理解了这一层,你就明白为什么整块板子只有 5 根有效线——它把所有麻烦事都做在芯片里了。

3. 曝光时间:为什么拍快了发黑、拍慢了发糊

3.1 积分时间就是曝光时间:跟相机胶片一个道理

英文手册里反复出现一个词——integration,翻译过来叫“积分”。这个词劝退过不少人,但其实它就是相机里说的“曝光时间”。上一节的水桶类比已经埋了伏笔:桶里接多少水,取决于雨量和接水时长,接水时长就是曝光时间。

相机上的道理是一样的。胶片曝光时间越长,底片越亮;越短,底片越暗。线性 CCD 的像素也一样,曝光时间越长,输出的电压值越大,图像越亮;曝光时间越短,输出电压越小,图像越暗。所以当你发现采集到的 128 个值全都贴着 0 或者全都贴着最大值,第一个该怀疑的就是曝光时间给错了,不是芯片坏了。

这里要记住一个硬指标:TSL1401 的最大曝光时间是 100ms。也就是说,你把曝光时间拉到 200ms、500ms,图像效果和 100ms 是一样的,多出来的时间纯属浪费。这个数字在后面的调参里会反复用到。

3.2 一个 SI 周期里到底发生了什么

从发出 SI 到下一个 SI 之间,芯片内部的时间被切成了几段,每段干的事不一样。下面这张表是按原文时序整理的,照着看一遍,你就知道代码里那些循环为什么是 18 和 128:

阶段CLK 个数作用
SI 脉冲之后前 18 个积分器复位,这段时间读到的值没用,要丢弃
第 19 个开始128 个128 个像素依次从 AO 输出,下降沿采样
收尾第 129 个结束本次采集,之后进入下一轮曝光

关键的一句话是:在每个 SI 信号之后采集到的像素,都是这个 SI 之前曝光得到的图像。也就是说,你发 SI 的那一刻,上一轮的曝光就结束了,接下来读出来的 128 个值,反映的是“发 SI 之前那段时间”的光照情况。

还有一条容易忽略的细节:SI 拉高之后,要至少保持 20ns 才能产生第一个 CLK。20ns 对单片机来说太短了,你随便写个delay_us(1)都远远超过,所以实际编程时不用抠这个数,但心里要知道有这么个下限。

如果 CLK 频率足够快,比如跑到最大 8MHz,那么前 18 个 CLK 的时间可以忽略不计,曝光时间就近似等于两个 SI 信号之间的间隔。这条结论很有用,因为它意味着:你控制曝光时间,本质上就是控制发 SI 的间隔。后面两种编程方法,区别就在于这个间隔由谁来定。

3.3 曝光时间选多长:100ms 上限和“别把图拍糊”

曝光时间没有标准答案,因为它取决于你面前的光线和你采集的对象。但有两个方向是确定的。

光线暗的时候,要加长曝光时间,让像素攒够电荷,否则整条线都是黑的,什么都分不出来。光线强的时候,要缩短曝光时间,否则像素饱和,128 个值全部顶到最大值,黑线白线糊成一片。这就是为什么同一套代码,白天在窗边跑得好好的,晚上挪到墙角就失效了——不是代码有 bug,是曝光时间没跟着环境走。

但如果你的采集对象在快速变化,比如小车在赛道上跑,情况就反过来:要尽量避免加长曝光时间。原因有两个,原文说得很直白。第一,曝光时间变长意味着采集周期变长,系统反应变慢,等你想明白黑线在哪,车已经冲出去了。第二,曝光时间长会让图像变模糊,这跟相机拍运动物体一个道理,快门越慢,运动中的物体拖影越重,图像处理就越难做。

所以真正合理的做法是:先确定一个“够用就好”的曝光时间,再根据环境亮度做小幅调整,并且给它设一个上限,别让它无限增长。如果采集的是静止画面或者变化很慢的场景,那曝光时间长一点短一点都无所谓,不用纠结。

4. 跑通第一个例子:把 128 个点的电压读进数组

4.1 单次曝光法:最短的一段能跑通的采集代码

原文给了两种编程方法,先看简单的这种,叫单次曝光法。它的思路是:先用一个 SI 周期专心曝光,等一段时间,再在下一个 SI 周期把 128 个点读出来。中间那段等待,就是曝光时间。

下面这段是最小可跑的采集流程。它是伪代码风格,CLK_HIGH()、adc_read()这些换成你自己单片机的库函数就行,逻辑不用改。

/* TSL1401 最小采集流程 */ #define PIXEL_NUM 128 uint16_t pixel[PIXEL_NUM]; /* 12 位 AD 时每个点取值范围 0~4095 */ static void clk_pulse(void) /* 一个完整时钟:拉高再拉低 */ { CLK_HIGH(); delay_us(1); CLK_LOW(); delay_us(1); } void ccd_exposure_start(void) /* 第一步:发 SI,开始曝光 */ { SI_HIGH(); delay_us(1); /* 手册要求 SI 高电平至少 20ns,这里给足 1us */ SI_LOW(); } void ccd_read_frame(void) /* 第二步:再发 SI,把上一轮曝光的 128 个点读出来 */ { SI_HIGH(); delay_us(1); SI_LOW(); for (int i = 0; i < 18; i++) { /* 前 18 个 CLK 是复位段,数据没用,丢掉 */ clk_pulse(); } for (int i = 0; i < PIXEL_NUM; i++) { CLK_HIGH(); delay_us(1); CLK_LOW(); /* 关键:下降沿之后 AO 上才是这一点的电压 */ delay_us(1); pixel[i] = adc_read(AO_CH); /* 把模拟电压变成数字 */ } clk_pulse(); /* 第 129 个 CLK 收尾,结束本次采集 */ }

调用的时候是这样:

ccd_exposure_start(); delay_ms(5); /* 这 5ms 就是曝光时间,改它就能改亮度 */ ccd_read_frame(); /* 读回来的才是刚才那 5ms 曝光的结果 */

几个参数需要说清楚。PIXEL_NUM固定是 128,这是 TSL1401 的像素数,改小了后面一半数据就丢了。delay_us(1)让 CLK 的半周期是 1us,对应大约 500kHz 的时钟频率,远低于芯片 8MHz 的上限,也照顾了单片机 AD 的转换速度,是个稳妥的起点。adc_read返回的值,12 位 AD 是 0 到 4095,8 位 AD 是 0 到 255,后面的阈值计算要按这个量程来。中间那个delay_ms(5)就是曝光时间,先从 5ms 试起,太亮就减,太暗就加。

单次曝光法有一个明显的缺点:曝光期间 CPU 只能干等。delay_ms(5)这 5 毫秒里,你的单片机什么也做不了,只能守着。采集周期不固定的场景用它可以,但如果小车还要同时干别的事,就得换第二种方法。

4.2 连续曝光法:把曝光交给定时器,CPU 好干别的

连续曝光法的采集原理和单次法一模一样,唯一的区别是:两个 SI 之间的间隔不再靠delay函数,而是靠单片机的周期定时器中断来定。定时器每隔固定时间进一次中断,中断里干两件事——发 SI、读 128 个点,然后再等下一次中断。这个固定的中断周期,就是你设定好的曝光时间。

volatile uint8_t frame_ready = 0; /* 定时器周期中断,中断周期 = 曝光时间,比如设成 5ms */ void timer_period_isr(void) { SI_HIGH(); delay_us(1); SI_LOW(); for (int i = 0; i < 18; i++) { clk_pulse(); } for (int i = 0; i < PIXEL_NUM; i++) { CLK_HIGH(); delay_us(1); CLK_LOW(); delay_us(1); pixel[i] = adc_read(AO_CH); } clk_pulse(); frame_ready = 1; /* 告诉主循环:新一帧好了 */ } void main_loop(void) { while (1) { if (frame_ready) { frame_ready = 0; ccd_process(pixel); /* 主循环里慢慢处理,不占用曝光时间 */ } } }

两种方法摆在一起对比,选择就清楚了:

对比项单次曝光法连续曝光法
曝光时间由谁决定delay函数的时长定时器中断周期
CPU 占用曝光期间只能等待曝光由硬件定时器掌控
适合场景采集周期不固定、调试阶段周期固定、要同时干别的事
改动曝光时间改delay_ms参数改定时器重装值

刚上手时我建议先用单次曝光法,因为它直观,你能亲眼看到“这次曝光多久”和“图像多亮”的对应关系。等这条链路跑顺了,再迁到定时器版本。要注意的是,如果你在中断里跑完整套 128 次循环,中断服务函数会有点长,嫌它占用时间的话,可以只在中断里发 SI,把读 128 个点的活留给主循环,效果一样。

4.3 数据读回来怎么判断对不对:先看最大最小值

数据读进数组只是第一步,接下来必须能“看见”它,否则调参全靠猜。最土也最有效的办法是串口打印,把 128 个值当成一条曲线或者一行字符打出来。

void ccd_report(void) { uint16_t vmax = 0, vmin = 4095; for (int i = 0; i < PIXEL_NUM; i++) { if (pixel[i] > vmax) vmax = pixel[i]; if (pixel[i] < vmin) vmin = pixel[i]; } uint16_t vth = (vmax + vmin) / 2; /* 最朴素的动态阈值:最大最小取中 */ printf("max=%u min=%u th=%u\r\n", vmax, vmin, vth); for (int i = 0; i < PIXEL_NUM; i++) { printf("%c", pixel[i] > vth ? '#' : '.'); /* 用字符画一条黑白线 */ } printf("\r\n"); }

这段代码输出的东西非常直观:max和min告诉你动态范围够不够,#和.拼出来的一行字符告诉你黑线大概在哪个位置。拿张白纸挡住镜头,max应该往下掉;拿手电筒照一下,max应该顶到量程上限。如果max和min差不了几个数,说明曝光时间或者镜头有问题,先别往下写算法。

顺带说一句阈值怎么定。固定阈值在实验室里能用,换个光线就废,因为同样的黑线,环境亮的时候整体数值都高。一个常用的做法是用(max + min) / 2做动态阈值,每帧自己算一次。另一种思路来自带感光传感器的模块——板上额外焊了一颗感光元件,供电同样是 3V 至 5V,输出电流随环境亮度线性变化,直接接一路 AD 就能读到环境亮度。有了这个亮度值,阈值可以写成Vth = k * Vout + b的线性形式,环境越暗阈值越低,这比拍脑袋定一个常数靠谱得多。

5. 新手最容易翻车的地方:6 个现象对着排查

5.1 采集类的坑:数据全是 0 或者全是满量程

现象一:128 个值一个不差,全是 0 或者全是 4095。

原因通常不在芯片,而在接线或者时序。全是 0,多半是 AO 没接到 AD 脚上、AD 通道号写错、或者 SI 根本没发出去,芯片压根没开始工作。全是满量程,多半是曝光时间太长,像素饱和了,也可能是 CLK 和 SI 接反了——这两个脚都是输出,接反了现象很像,但波形完全不同。

解决办法是按顺序排查:先用万用表确认 VDD 和 GND 正常;再把 CLK 频率降下来,用 100kHz 甚至 10kHz 试,慢到人眼能看出来都没关系;然后检查 AD 采样是不是放在了 CLK 的下降沿之后。原文写得很明确,每个像素的值要在 CLK 的下降沿时从 AO 采集,如果你在上升沿采,读到的是上一个像素的残留。

现象二:每帧数据都在跳,波形完全对不上。

如果是单次曝光法,问题多半出在两次 SI 的间隔上。你写了delay_ms(5),但这 5ms 里如果进了别的高优先级中断,实际间隔就变了,曝光时间跟着变,图像自然一帧一个样。

这种情况下,要么在曝光期间关掉无关中断,要么直接改用连续曝光法,让定时器来兜底。定时器的周期是硬件保证的,不会因为主循环里干了什么活而漂移,这就是它在实战里更受欢迎的原因。

5.2 图像类的坑:中间亮两边暗、波形不够尖

现象三:采集出来的曲线,中间一段很高,两头明显往下掉。

这不是芯片坏了,也不是接线问题,而是光学系统的固有特性,叫“余弦效应”。图像边缘的光线经过透镜是斜着入射到感光器件上的,中间的光线是垂直入射的,斜着进去的能量自然弱一些,所以几乎所有的成像系统都是中间亮、四角暗。原文说得很坦诚:这个问题要靠对图像的解析和相应算法来根本解决。

入门阶段不用急着做补偿,先确认这个暗是“对称地暗”还是“一边倒地暗”。对称地往下掉,那是正常现象,后面可以按位置乘一个补偿系数,越靠边乘得越大。如果是一边亮一边暗得厉害,那可能是镜头装歪了或者模块本身有遮挡,得回头查硬件。

现象四:曲线很平滑,看不出黑线的边界,找不到尖峰。

十有八九是没对焦。线性 CCD 模块的焦距是固定的,想得到清晰的图像,只能靠拧镜头在镜头座里的进出位置。判断方法很朴素:对准一个有明暗边界的物体,比如显示器上的一条分界线,慢慢拧,同时看串口打印出来的曲线。拧到位置合适,曲线会在明暗交界处出现陡峭的跳变,看起来很“尖”;拧得不对,跳变就是一段缓坡,看起来很“平滑”。原文给的两张对比图说的就是这个。

调焦没有捷径,只能一边拧一边看数据,拧四分之一圈就打印一帧,别凭感觉。

5.3 硬件类的坑:加运放、挑镜头、时钟拉太满

现象五:觉得图像太暗,想给 AO 加一级运放提亮。

这是新手最容易走的弯路。TSL1401 的 AO 引脚内部已经内置了增益调整器,官方文档明确写了不需要再接运放。所谓“加运放能提高画面亮度”,本质上就是把电压整体放大,和你采集完之后在单片机里乘一个系数做数字增益,效果是一样的,但后者不花钱、不占板子、还不会引入额外噪声。真觉得暗,先加曝光时间,再加数字增益,最后才考虑硬件。

现象六:CLK 直接拉到 8MHz,结果数据全乱。

8MHz 是芯片能接受的 CLK 上限,但不是你的系统能接受的上限。原文特意提醒过:即使 CLK 跑到 8MHz,单片机的 AD 转换时间也达不到那么快。CLK 太快,一个像素的电压还没转换完,下一个像素已经出来了,读到的全是错位的数据。

入门阶段的稳妥做法是把 CLK 控制在几百 kHz,等你确认整条链路都正常了,再一点点往上提,每提一次都打印一帧看数据有没有变形。另外,镜头这块也别被宣传语带偏:市面上卖的都是配好镜头的模块,参数固定,可视效果也就固定。焦距决定视角大小,焦距越长视角越小、看远处清楚;光圈越大进光量越多、需要的最短曝光时间越短。所谓“最远能看 XX 米”,实际意思往往只是“适合看 XX 米到 XX 米之间”,买之前想清楚你要看多远。

6. 从读出波形到跑起小车:接下来三个月学什么

能稳定读出 128 个点之后,很多人会卡在第二个坎上:数据有了,然后呢?下面这条路是我带新人时用得最多的顺序,按周推进,每一步都有能验证的产出。

阶段目标具体练什么
第 1 周能看见波形串口打印 128 个值,用手遮挡镜头,看数值跟着变
第 2 周曝光调得稳强光、弱光各调一次,找到不饱和又不发黑的那档
第 3 周二值化靠谱用最大最小值取中做阈值,或接板载感光传感器做亮度反馈
第 4 周起算出偏差找黑线中心位置,和 64 相减,得到方向偏差送控制

第 3 周那个亮度反馈值得多说一句。线性 CCD 本身没有反馈信号,你不知道当前环境有多亮,所以曝光时间和黑白阈值只能靠试。带感光传感器的模块解决了这个问题:把传感器的输出接一路 AD,读到环境亮度 Vout,阈值就可以写成Vth = k * Vout + b,k 和 b 靠实测标定两组数据就能估出来。环境变暗,阈值跟着降,二值化的效果就稳了。这里唯一要注意的是给曝光时间设一个上限,别让它在暗环境里无限增长,100ms 是芯片的硬顶。

第 4 周开始才是真正有意思的部分:从 128 个点里找出黑线。最简单的做法是找连续最暗的一段,取它的中点;讲究一点的做法是算加权质心,把每个点的暗度当权重。算出来的位置减去 64,就是一个带正负号的偏差值,正的代表线偏右,负的代表线偏左,直接丢给转向控制就行。

我第一次调这块板子的时候,自认为接线没问题,代码也没问题,结果串口打出来 128 个 4095,折腾了大半夜才发现是曝光时间给了 50ms,镜头还对着窗户。从那以后我每次上电的第一件事,都是先打印一帧最大最小值,确认动态范围正常,再去动算法。这个习惯帮我省掉了无数次“以为是算法错了其实是硬件没对”的返工。希望帮到你。

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

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

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

立即咨询