037【GD32F470】TSL1401阵线性CCD模块

2.44 TSL1401阵线性CCD模块

2.44.1 模块来源

采购链接:
阵线性CCD模块搭配小车巡线循迹传感器动态阈值算法TSL1401线
资料下载:
【完整代码:https://link3.cc/sgzy,输入本文标题,在CSDN博客文章源码资料搜索】

2.44.2 规格参数

在这里插入图片描述

2.44.3 移植过程

我们的目标是在梁山派GD32F470上能够判断粉尘浓度的功能。首先要获取资料,查看数据手册应如何实现,再移植至我们的工程。

2.44.3.1 查看资料

TSL1401 线性传感器由一个 1x128 的光电二极管阵列、相关的电荷放大电路以及一个内部像素数据保功能组成。内部像素数据保功能可以为所有像素点提供同时积分的开始和停止时间。该阵列由 128 个像素组成,每个像素的感光面积为 3,524.3 平方微米。 像素之间的间隔为 8μm。内部控制逻辑简化了操作,该模块需要串行输入(SI)信号和时钟信号(CLK)。CCD 传感器是光学传感器,会受到环境光线的影响;程序中已经运用了动态阈值算法,尽量减小环境光线的影响,但是太暗或者太亮的环境下是不能正常工作的(一般室内正常光线可以运行)。该模块可以用作小车寻线,寻线原理是通过 CCD 线性摄像头扫描黑线,摄像头扫描到 128 的像素点,中值为 64,扫描到黑线会得到一个二值化数据,用这个二值化数据减去中值64再除以 2,就得到小车偏离黑线的值(有正有负,如果为正,小车左转,如果为负,小车右转)。
原文链接:https://blog.csdn.net/Gxust_Veneno/article/details/119797411

2.44.3.2 引脚选择

128 个像素是怎么进行采集并输出的呢,这就用到了 SI 和 CLK 信号。
在 128 个像素之外,还有一个开关逻辑控制和移位寄存器电路。SI 通过该电路,控制每一个像素的积分和复位操作;CLK 通过该电路控制每一个像素电压的依次输出。
在这里插入图片描述
该模块对传感器输出的电压进行增益调整,因此从 AO 引脚输出的电压无需再接其他运放,直接接入单片机的 ADC 输入引脚即可。
原文链接:https://blog.csdn.net/ReCclay/article/details/84141358

在这里插入图片描述

2.44.3.3 移植至工程

移植步骤中的导入.c和.h文件与上一节相同,只是将.c和.h文件更改为ccd.c与ccd.h。见2.2.3.3 移植至工程。这里不再过多讲述。移植完成后面修改相关代码。
在文件ccd.c中,编写如下代码。

 /********************************************************************************
   * 测试硬件:立创·梁山派开发板GD32F470ZGT6    使用主频200Mhz    晶振25Mhz
   * 版 本 号: V1.0
   * 修改作者: LCKFB
   * 修改日期: 2023年06月20日
   * 功能介绍:      
   ******************************************************************************
 
 *********************************************************************************/
#include "ccd.h"

uint16_t adv[128] = {0};         // 存储ADC转换后的值
uint8_t scibuf[200];                         // 存储上传到上位机的信息
uint8_t CCD_Zhongzhi, CCD_Yuzhi; // 线性CCD的 中值 和 阈值 
uint16_t adc_value;                                 //ADC转换后的值
/******************************************************************
 * 函 数 名 :adc_config()
 * 函 数 说 明:配置ADC
 * 函 数 形 参:
 * 函 数 返 回:
 * 作       者:罗小黑
 * 备       注:adc时钟配置需要注意,建议在14M左右
 ******************************************************************/
void adc_config(void)
{
        /* enable GPIOA clock */
        rcu_periph_clock_enable(CCD_AO_RCU);

        /* enable ADC0 clock */
        rcu_periph_clock_enable(RCU_ADC0);

        /* config ADC clock */
        adc_clock_config(ADC_SYNC_DELAY_10CYCLE);

        /* config ADC mode */
        gpio_mode_set(CCD_AO_PORT, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, CCD_AO_PIN);

        adc_deinit();

        adc_regular_channel_config(ADC0, 0, ADC_CHANNEL_5, ADC_SAMPLETIME_56); // 通道配置

        adc_special_function_config(ADC0, ADC_CONTINUOUS_MODE, ENABLE); // ADC连续模式

        adc_data_alignment_config(ADC0, ADC_DATAALIGN_RIGHT); // LSB模式

        adc_channel_length_config(ADC0, ADC_REGULAR_CHANNEL, 1U);

        adc_resolution_config(ADC0, ADC_RESOLUTION_12B); // ADC分辨率配置

        adc_sync_mode_config(ADC_SYNC_MODE_INDEPENDENT);

        adc_sync_delay_config(ADC_SYNC_DELAY_8CYCLE); // 采样周期

        delay_1ms(1);

        adc_enable(ADC0);

        adc_calibration_enable(ADC0);
}
/******************************************************************
 * 函 数 名 :adc_read()
 * 函 数 说 明:adc通道数据转换与读取
 * 函 数 形 参:
 * 函 数 返 回:adc_value adc通道转换后的值
 * 作       者:罗小黑
 * 备       注:无
 ******************************************************************/
uint16_t adc_read(void)
{
        adc_regular_channel_config(ADC0, 0U, ADC_CHANNEL_5, ADC_SAMPLETIME_15);
        adc_software_trigger_enable(ADC0, ADC_REGULAR_CHANNEL);
        // /* 等待 ADC0 采样完成 */
        while (!adc_flag_get(ADC0, ADC_FLAG_EOC)) 
        {
                ;
        }

        /* 返回采样值 */
        adc_value = adc_regular_data_read(ADC0);

        return adc_value;
}
/******************************************************************
 * 函 数 名 :ccd_init()
 * 函 数 说 明:ccd模块的初始化
 * 函 数 形 参:
 * 函 数 返 回:
 * 作       者:罗小黑
 * 备       注:无
 ******************************************************************/
void ccd_init()
{
        rcu_periph_clock_enable(CCD_CLK_RCU);
        rcu_periph_clock_enable(CCD_SI_RCU);

        gpio_mode_set(CCD_CLK_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, CCD_CLK_PIN);
        gpio_mode_set(CCD_SI_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, CCD_SI_PIN);

        gpio_output_options_set(CCD_CLK_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_25MHZ, CCD_CLK_PIN);
        gpio_output_options_set(CCD_SI_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_25MHZ, CCD_SI_PIN);
}
/******************************************************************
 * 函 数 名 ccd_read
 * 函 数 说 明:读取ccd模块AO引脚的值,并存入adv[128]数组
 * 函 数 形 参:
 * 函 数 返 回:
 * 作       者:罗小黑
 * 备       注:无
 ******************************************************************/
void ccd_read(void)
{
        uint8_t i = 0, tslp = 0;
        //        static uint8_t j, Left, Right, Last_CCD_Zhongzhi;
        //        static uint16_t value1_max, value1_min;

        CCD_CLK_SET; // CLK引脚设为高电平
        CCD_SI_RESET;
        delay_us(2);

        CCD_SI_SET;
        CCD_CLK_RESET;
        delay_us(2);

        CCD_CLK_SET;
        CCD_SI_RESET;
        delay_us(2);
        for (i = 0; i < 128; i++)
        {
                CCD_CLK_RESET;
                delay_us(5);// 调节曝光时间                                                  

                // 将读取到的电压值存入数组adv中,"adc_read() >> 3"是对数值进行归一化
                //2.1可以更改别的数值,值越大显示的波形幅度越高
                adv[tslp] = 2.1f * (adc_read() >> 3); 

                ++tslp;                                                                  
                CCD_CLK_SET;
                delay_us(1);
        }
        // 只是想测试模块的话,下面的代码可以不要,加不加无所谓
        //  value1_max = adv[0];          // 动态阈值算法,读取最大和最小值
        //  for (i = 15; i < 123; i++) // 两边各去掉15个点
        //  {
        //          if (value1_max <= adv[i])
        //                  value1_max = adv[i];
        //  }
        //  value1_min = adv[0]; // 最小值
        //  for (i = 5; i < 123; i++)
        //  {
        //          if (value1_min >= adv[i])
        //          {
        //                  value1_min = adv[i];
        //          }
        //  }
        //  CCD_Yuzhi = (value1_max + value1_min) / 2; // 计算出本次中线提取的阈值
        //  for (i = 5; i < 118; i++)                                   // 寻找左边跳变沿
        //  {
        //          if (adv[i] > CCD_Yuzhi && adv[i + 1] > CCD_Yuzhi && adv[i + 2] > CCD_Yuzhi && adv[i + 3] < CCD_Yuzhi && adv[i + 4] < CCD_Yuzhi && adv[i + 5] < CCD_Yuzhi)
        //          {
        //                  Left = i;
        //                  break;
        //          }
        //  }
        //  for (j = 118; j > 5; j--) // 寻找右边跳变沿
        //  {
        //          if (adv[j] < CCD_Yuzhi && adv[j + 1] < CCD_Yuzhi && adv[j + 2] < CCD_Yuzhi && adv[j + 3] > CCD_Yuzhi && adv[j + 4] > CCD_Yuzhi && adv[j + 5] > CCD_Yuzhi)
        //          {
        //                  Right = j;
        //                  break;
        //          }
        //  }
        //  CCD_Zhongzhi = (Right + Left) / 2;                                // 计算中线位置
        //  if (abs(CCD_Zhongzhi - Last_CCD_Zhongzhi) > 70) // 计算中线的偏差,如果太大
        //          CCD_Zhongzhi = Last_CCD_Zhongzhi;                        // 则取上一次的值
        //  Last_CCD_Zhongzhi = CCD_Zhongzhi;                                // 保存上一次的偏差
}
/******************************************************************
 * 函 数 名 binToHex_high()
 * 函 数 说 明:将二进制高8位转换16进制
 * 函 数 形 参:
 * 函 数 返 回:转换后得到的16进制码,高8位
 * 作       者:XJU
 * 备       注:无
 ******************************************************************/
char binToHex_high(uint8_t num)
{
        uint8_t high_four;
        high_four = (num >> 4) & 0x0f;
        if (high_four == 0)
                return ('0');
        else if (high_four == 1)
                return ('1');
        else if (high_four == 2)
                return ('2');
        else if (high_four == 3)
                return ('3');
        else if (high_four == 4)
                return ('4');
        else if (high_four == 5)
                return ('5');
        else if (high_four == 6)
                return ('6');
        else if (high_four == 7)
                return ('7');
        else if (high_four == 8)
                return ('8');
        else if (high_four == 9)
                return ('9');
        else if (high_four == 10)
                return ('A');
        else if (high_four == 11)
                return ('B');
        else if (high_four == 12)
                return ('C');
        else if (high_four == 13)
                return ('D');
        else if (high_four == 14)
                return ('E');
        else if (high_four == 15)
                return ('F');
}
/******************************************************************
 * 函 数 名  binToHex_low()
 * 函 数 说 将二进制低8位转换16进制
 * 函 数 形 参:
 * 函 数 返 回:转换后得到的16进制码,低8位
 * 作       者:XJU
 * 备       注:无
 ******************************************************************/
char binToHex_low(uint8_t num)
{
        uint8_t low_four;
        low_four = num & 0x0f;
        if (low_four == 0)
                return ('0');
        else if (low_four == 1)
                return ('1');
        else if (low_four == 2)
                return ('2');
        else if (low_four == 3)
                return ('3');
        else if (low_four == 4)
                return ('4');
        else if (low_four == 5)
                return ('5');
        else if (low_four == 6)
                return ('6');
        else if (low_four == 7)
                return ('7');
        else if (low_four == 8)
                return ('8');
        else if (low_four == 9)
                return ('9');
        else if (low_four == 10)
                return ('A');
        else if (low_four == 11)
                return ('B');
        else if (low_four == 12)
                return ('C');
        else if (low_four == 13)
                return ('D');
        else if (low_four == 14)
                return ('E');
        else if (low_four == 15)
                return ('F');
}
/******************************************************************
 * 函 数 名 :slove_data()
 * 函 数 说 明:ccd模块的数据处理
 * 函 数 形 参:
 * 函 数 返 回:
 * 作       者:XJU
 * 备       注:无
 ******************************************************************/
void slove_data(void)
{
        int i;
        ccd_read();
        scibuf[0] = 0;
        scibuf[1] = 132;
        scibuf[2] = 0;
        scibuf[3] = 0;
        scibuf[4] = 0;
        scibuf[5] = 0;
        for (i = 0; i < 128; i++)
                scibuf[6 + i] = adv[i];
}
/******************************************************************
 * 函 数 名 称:sendToPc()
 * 函 数 说 明:向上位机发送数据,并显示
 * 函 数 形 参:无
 * 函 数 返 回:无
 * 作       者:XJU
 * 备       注:无
 ******************************************************************/
void sendToPc(void)
{
        int i;
        slove_data();
        printf("*");
        printf("LD");
        for (i = 2; i < 134; i++)
        {
                printf("%c", binToHex_high(scibuf[i])); // 以字符形式发送高4位对应的16进制
                printf("%c", binToHex_low(scibuf[i]));        // 以字符形式发送低?位对应的16进制
        }
        printf("00"); // 通信协议要求
        printf("#");  // 通信协议要求
}
/******************************************************************
 * 函 数 名 称:ccd()
 * 函 数 说 明:CCD数据采集
 * 函 数 形 参:
 * 函 数 返 回:
 * 作       者:XJU
 * 备       注:无
 ******************************************************************/
void ccd(void)
{
        int i, j;
        usart_send_data(0xff);
        for (i = 0; i < 100; i++)
        {
                ccd_read();
                j = 0;
                for (j = 0; j < 128; j++)
                {
                        if (adv[j] == 0XFF)
                                adv[j]--;
                        usart_send_data(adv[j]);
                }
        }
}

在文件ccd.h中,编写如下代码。

 /********************************************************************************
   * 测试硬件:立创·梁山派开发板GD32F470ZGT6    使用主频200Mhz    晶振25Mhz
   * 版 本 号: V1.0
   * 修改作者: LCKFB
   * 修改日期: 2023年06月20日
   * 功能介绍:      
   ******************************************************************************

 *********************************************************************************/

#ifndef __ccd_H
#define __ccd_H
#include "gd32f4xx.h"
#include "systick.h"
#include "stdlib.h"
#include "usart.h"

//定义 SI CLK AO 引脚时钟 
#define CCD_SI_RCU RCU_GPIOC
#define CCD_CLK_RCU RCU_GPIOC
#define CCD_AO_RCU RCU_GPIOA
//定义 SI CLK AO 引脚组
#define CCD_SI_PORT GPIOC
#define CCD_CLK_PORT GPIOC
#define CCD_AO_PORT GPIOA
//定义 SI CLK AO 引脚号
#define CCD_SI_PIN GPIO_PIN_2
#define CCD_CLK_PIN GPIO_PIN_1
#define CCD_AO_PIN GPIO_PIN_5
//定义 SI CLK 引脚输出电平,方便移植
#define CCD_CLK_SET gpio_bit_write(CCD_CLK_PORT, CCD_CLK_PIN, SET)
#define CCD_CLK_RESET gpio_bit_write(CCD_CLK_PORT, CCD_CLK_PIN, RESET)
#define CCD_SI_SET gpio_bit_write(CCD_SI_PORT, CCD_SI_PIN, SET)
#define CCD_SI_RESET gpio_bit_write(CCD_SI_PORT, CCD_SI_PIN, RESET)


void adc_config(void);

void ccd_init(void);

void ccd_read(void);

void sendToPc(void);

void ccd(void);

#endif /* __ccd_H */

2.44.4 移植验证
在自己工程中的main主函数中,编写如下。

【完整代码:https://link3.cc/sgzy,输入本文标题,在CSDN博客文章源码资料搜索】

移植现象:移植成功后,在串口助手上可以看到指定的数据;在上位机显示的波形中,中间下凹的部分则是识别到了有物体。
在这里插入图片描述

在这里插入图片描述
移植成功示例
【完整代码:https://link3.cc/sgzy,输入本文标题,在CSDN博客文章源码资料搜索】

线 CCD 模块是智能车的“眼睛”,本文档旨在向大家阐述线 CCD 的基本原理及其软硬件使用说明。   说到 CCD,想必大家都不陌生。在我们常用的手机、数码相机等电子设备的摄像头中,CCD 得到了广泛的应用。但是细心的各位可能发现了,我们这篇文档从始至终都在强调“线CCD,那么什么是线 CCD,它又与我们常说的 CCD 有什么区别呢?   我们常说的 CCD 指的是面阵 CCD,在你使用手机拍照后,会得到一幅图像,如果你感兴趣的话,打开图像的属可以看到它的尺寸,例如 1920x1080。从此可以发现,手机里面的 CCD 拍下的图像可以被认为是一个 1920x1080 的矩阵(这里只考虑灰度图像)。但是如果你使用线 CCD 拍摄下同样一幅图像,你会发现它的尺寸是 1x128,也就是说线 CCD 拍下的图像是一个仅有一个像素宽的长条,我们可以将其作为一个由 128 个灰度值组成的向量(数组),这也就是两种 CCD 最大的不同之处。两种 CCD 拍到的图片如下图所示。   TSL1401 芯片包含 128 个线排列的光电二极管,同时片内为每个光电二极管集成了独自的积分电路,下面为了便于理解,我们将这些光电二极管及其积分电路统称为像素。对于每个像素来说,其采集到的灰度值均与其感知的光强与积分(曝光)时间成正比,而采集到的灰度值将在 AO 线上以模拟信号(电压)的形式输出,下面将每个像素采集到的灰度值称为其像素值。那么问题来了,我们共有 128 个像素值要传输给单片机,但是 AO 线只有一根,怎么办呢?这时候就轮到 CLK 和 SI 这两个信号上场了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值