2.44 TSL1401 阵线性 CCD 模块(来自 Deep Dark Fantasies 的贡献)
2.44.1 模块来源
采购链接: 阵线性 CCD 模块搭配小车巡线循迹传感器动态阈值算法 TSL1401 线 资料下载:https://pan.baidu.com/s/1ZmUxwAhhfCguNtxULIUsNw
**图 2.44.1-1 产品实物展示**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
######### TSL1401 | ######### 立创·梁山派 | ######### 接线图 |
---|
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日
* 功能介绍:
******************************************************************************
* 梁山派软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 其余模块移植手册:https://dri8c0qdfb.feishu.cn/docx/EGRVdxunnohkrNxItYTcrwAnnHe
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*********************************************************************************/
#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]);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
在文件 ccd.h 中,编写如下代码。
/********************************************************************************
* 测试硬件:立创·梁山派开发板GD32F470ZGT6 使用主频200Mhz 晶振25Mhz
* 版 本 号: V1.0
* 修改作者: LCKFB
* 修改日期: 2023年06月20日
* 功能介绍:
******************************************************************************
* 梁山派软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 其余模块移植手册:https://dri8c0qdfb.feishu.cn/docx/EGRVdxunnohkrNxItYTcrwAnnHe
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*********************************************************************************/
#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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
2.44.4 移植验证
在自己工程中的 main 主函数中,编写如下。
/********************************************************************************
* 测试硬件:立创·梁山派开发板GD32F470ZGT6 使用主频200Mhz 晶振25Mhz
* 版 本 号: V1.0
* 修改作者: LCKFB
* 修改日期: 2023年06月20日
* 功能介绍:
******************************************************************************
* 梁山派软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 其余模块移植手册:https://dri8c0qdfb.feishu.cn/docx/EGRVdxunnohkrNxItYTcrwAnnHe
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*********************************************************************************/
#include "gd32f4xx.h"
#include "systick.h"
#include "stdio.h"
#include "ccd.h"
int main(void)
{
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 优先级分组
systick_config(); // 滴答定时器初始化 1us
usart0_config(115200);
adc_config();
ccd_init();
while (1)
{
sendToPc();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
移植现象:移植成功后,在串口助手上可以看到指定的数据;在上位机显示的波形中,中间下凹的部分则是识别到了有物体。
移植成功示例,见文件 2.44.4-1 。
文件 2.44.4-1 代码移植成功示例