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博客文章源码资料搜索】