Freescale实纪——关于线性CCD TSL1401CL与曝光的一些心得

时间:2021-08-23 14:32:01

        Freescale安徽赛结束,感触颇多。

        这次就线性CCD,特别是曝光的问题想做些总结。

        Freescale光电组指定的传感器是线性CCD——TSL1401CL。

        下面是官方说明中对此线性CCD的简单描述:

        Freescale实纪——关于线性CCD TSL1401CL与曝光的一些心得

        所以,TSL1401CL的核心是128个光电二极管组成的感光阵列,阵列后面有一排积分电容,光电二极管在光能量冲击下产生光电流,构成有源积分电路,那么积分电容就是用来存储光能转化后的电荷。积分电容存储的电荷越多,说明前方对应的那个感光二极管采集的光强越大。反映在像素点上就是,像素灰度低。光强接近饱和,像素点灰度趋近于全白,则呈白电平。

        TSL1401CL的功能框图与引脚配置如下:

        Freescale实纪——关于线性CCD TSL1401CL与曝光的一些心得

        Freescale实纪——关于线性CCD TSL1401CL与曝光的一些心得

         我个人认为, 对于做车来说,不需要懂太多关于线性CCD的硬性知识,因为太底层的架构与原理也比较复杂,对小车所要涉及的图像处理也没有太紧密的联系。但是,对于线性CCD的工作基本原理,特别是时序,要有清晰的概念。

         在代码编写与CCD运用中,我的实际经验告诉我,积分时间与曝光时间最最重要。

         先就时序谈谈我自己的见解。现附上官方文档的时序图:

       Freescale实纪——关于线性CCD TSL1401CL与曝光的一些心得

       Freescale实纪——关于线性CCD TSL1401CL与曝光的一些心得

         对于线性CCD的时序操作,我觉得基本要知道的首先是TSL1401CL的关键引脚(见上表):除了必备的VCC,GND以外,还有与单片机IO口连接的CLK和SI脚,与单片机AD采集联通的AO脚。首先,对于CCD来讲,前18个时钟周期是像素复位时间,不进行积分与曝光。而且,第一个逻辑时钟SI必须出现在下一个时钟信号CLK上升沿之前。从时序图可清晰的看出CCD的操作过程,SI信号相当于一个标志,当它变为高电平后,我们就可以在每个CLK信号高电平到来后进行数据的AD采样。对于这样的整个时钟的时序操作,包括对CLK和SI的操作,就可以写成曝光函数。具体如下(代码具体参照了蓝宙的资料):

         //======曝光函数======//

         void StartIntegration(void) 

         {

                unsigned char i;

 

                TSL1401_SI = 1;         /* SI  = 1 */

                SamplingDelay();           /*合理延时100ns*/

                TSL1401_CLK = 1;        /* CLK = 1 */

                SamplingDelay();

                TSL1401_SI = 0;         /* SI  = 0 */

                SamplingDelay();

                TSL1401_CLK = 0;        /* CLK = 0 */

                for(i=0; i<127; i++) 

                {

                       SamplingDelay();

                       SamplingDelay();

                       TSL1401_CLK = 1;    /* CLK = 1 */

                       SamplingDelay();

                       SamplingDelay();

                       TSL1401_CLK = 0;    /* CLK = 0 */

               }

        }

        这仅仅是对CCD做的时序操作,通过对与CLK于SI脚连接的端口做电平变换,使TSL1401CL进入正常的工作状态。完了以后我们才能对CCD上AO口采集到的数据进行AD采样。采样函数如下:

void ImageCapture(unsigned char * ImageData,unsigned char * ImageData2) 

{

    unsigned char i;

    unsigned int  temp_int;

    unsigned int  temp2_int;

 

    TSL1401_SI = 1;         //SI  = 1 

    SamplingDelay();

    TSL1401_CLK = 1;        // CLK = 1 

    SamplingDelay();

    TSL1401_SI = 0;         // SI  = 0 

    SamplingDelay();

 

    //Delay 20us for sample the first pixel

    

    for(i = 0; i < 20; i++)    //更改25,让CCD的图像看上去比较平滑。

    {                

        Cpu_Delay1us();                      //把该值改大或者改小达到自己满意的结果。

    }

      

    temp_int = AD_Measure12(0);    //读取AD0口采集到的数据。

   

    *ImageData++ =(byte)(temp_int>>4);         

    

    TSL1401_CLK = 0;        // CLK = 0 

 

    for(i=0; i<127; i++) 

    {

        SamplingDelay();

        SamplingDelay();

        TSL1401_CLK = 1;    //CLK = 1 

        SamplingDelay();

        SamplingDelay();

        

        temp_int = AD_Measure12(0);

        

        *ImageData++ =(byte)(temp_int>>4);      

        TSL1401_CLK = 0;    // CLK = 0 

    }

    SamplingDelay();

    SamplingDelay();

    TSL1401_CLK = 1;        // CLK = 1  

    SamplingDelay();

    SamplingDelay();

    TSL1401_CLK = 0;        // CLK = 0

}

         从上述代码中可看出我们的代码完全按照时序进行,只是在先前的曝光函数中的适当位置插入了AD采样的语句。其中:

    for(i = 0; i < 20; i++)    //更改25,让CCD的图像看上去比较平滑。

    {                

        Cpu_Delay1us();                      //把该值改大或者改小达到自己满意的结果。

    } 

        这段代码做了一个合理延时,大约20us,当然i值可调,延时时间可调。针对其作用,蓝宙的说法是,在于控制AD采集的时间点,让采集数据更稳定,出来的图像更平滑。我也尝试过删除这个延时,在我的上位机上看到的图像采集也没有受到什么影响,也很稳定。小车跑出来的效果也正常,所以我个人认为这个延时或许必要性没那么强,只是相当于一个补充与完善,大家可以自己试试看。

        解决了时序与采集,需要将ImageCapture()函数写在StartIntegration()函数后。

        下面一个关键问题就是对积分时间与曝光时间的掌控。何时进行曝光,曝光的周期设置是整个图像处理部分的先头和关键。积分其实是对像素进行放电的时间。其中,积分时间的计算公式是:

        tint(min)=(128-18)*clock period+20us

        其中,128是线性CCD一次采集的像素数;18是所需的逻辑设置时钟,也就是之前提到的复位时间;20us是像素转移时间,顾名思义是将积分电容储存的电荷经过运算电路转移到AO数据输出的时间。

        其实我感觉此公式大概理解一下就行,更加清晰的体现了一下工作原理。公式本身对于我们的代码与对CCD的操作提供不了什么太直接地帮助。

        个人的经验总结是,光线越强,积分时间就越长,我们进行曝光的周期就该越短。这样采集的周期相应缩短,采到的数据更加稳定。

        所以自然引出曝光的另一个重要内容:选择自适应曝光或是固定曝光。自适应曝光的写法蓝宙电子的附送资料很详细,大家可以参考。但本人觉得自适应更适合初学者,比较好上手。但是自适应的做法本身很浪费,也没必要。它适合光线很不均匀的环境,但实际上一般的实验室与实际比赛场地不存在这样的问题,而且对于自适应算法,二值化后在小车过急弯CCD可能看到全黑图像时,二值化的阈值不好处理,容易误判,我后面会提到。所以,我认为固定曝光才是真正的出路。自适应的特点是曝光周期可变,所以他能适应多种复杂的光线;而固定曝光的优点就是曝光周期固定,在光线正常情况下采集更加稳定,图像抖动减小且更加清晰,更有利于黑线的提取与二值化。

       当然,平常在实验室,光线均匀,我用10ms的曝光周期正好。但是实际到了省赛的大体育馆,那种光线强到和太阳直射几乎无差,建议大家将曝光缩短到5ms,而且务必加上偏振片(旋到合适角度),这样出来的图像才会明暗差距清晰,凹槽明显。

        至于固定曝光与采集的代码位置,大家可以用PIT定时器中断,将CCD基础处理部分写在中断里,设置标志位什么的,以下是本人写的中断(10ms固定曝光),包含了速度采集于二值化等内容,不吝赐教:

 #pragma CODE_SEG __NEAR_SEG NON_BANKED

__interrupt void PITCh0IntISR(void) 

{

    static unsigned char TimerCnt10ms = 0;

    static unsigned char TimerCnt2s = 0;

    static unsigned char u=0;

    static unsigned char b=0;

    

    PITTF_PTF0 = 1;

    TimerCnt10ms++;

    TimerCnt2s++;

    

    if(TimerCnt10ms >= 50)

     { 

        TimerCnt10ms = 0;

        TimerFlag10ms = 1;

 

        LED1 = ~LED1;

        speed_back = PACNT;//返回速度值

        PACNT = 0;

        ImageCapture(Pixel,Pixel2);//CCD采样

                                                                             

        //SendImageData(Pixel); 

        Get_Dyn_Th();                    //获得动态阈值

        Bi_conversion();                 //二值化处理

        Filter_Pixel_Two();              //数据滤波(均值)     

        //SendImageData(Pixel); 

        

        Get_Flag();

        Get_Flag2();

        CCD1_mid_point();

        CCD2_mid_point();

     } else if(TimerCnt10ms==10)  

       {

            StartIntegration();   //开始曝光     

       }

}

#pragma CODE_SEG DEFAULT

        解决了曝光与采集,这时相应的写出SCI串口发送程序,就可在上位机上观察到线性CCD采集到的原始图像了。关于图像的进一步处理,包括二值化与动态阈值我会在后面的文章谈到。

        谢谢大家!