基于梁山派GD32F470的TSL1401线性CCD模块移植与巡线应用实战最近在做一个智能小车项目需要用到巡线功能我选择了TSL1401线性CCD传感器。这个模块价格不贵但效果比普通红外对管好很多能识别更复杂的路线。今天我就把在立创·梁山派GD32F470上驱动这个模块的完整过程分享出来从硬件连接到代码编写再到数据分析和上位机显示手把手带你搞定。1. TSL1401线性CCD模块是什么咱们先来认识一下今天的主角——TSL1401线性CCD模块。你可以把它想象成一个“单行的摄像头”它只有一行128个“眼睛”光电二极管专门用来“看”一条线上的明暗变化。1.1 模块基本工作原理TSL1401内部有128个感光像素点排成一条直线。每个像素点就像一个小光敏电阻光照越强它产生的电信号就越大。模块工作时我们需要通过两个控制信号SI和CLK来告诉它“开始拍照”、“把第一个点的数据给我”、“把第二个点的数据给我”……直到读完128个点。模块内部已经集成了信号放大电路所以从AO引脚输出的就是可以直接给单片机ADC模数转换器读取的电压信号了。电压越高代表那个位置的光线越亮。1.2 它能用来做什么最常见的应用就是小车巡线。把模块装在车头镜头朝下对着地面。当地面上有一条黑线时对应的像素点接收到的反射光就弱输出的电压就低白色的地面反射光强电压就高。这样我们就能得到一条有“凹坑”的电压曲线这个“凹坑”的位置就对应了黑线的位置。通过计算这个位置就能知道小车相对于黑线的偏移量从而控制转向。注意CCD是光学传感器对环境光很敏感。虽然程序里用了动态阈值算法来减少影响但在光线太暗或太亮的环境下比如阳光直射效果还是会打折扣。一般在室内正常光照下使用没问题。2. 硬件连接与引脚配置要把模块用起来第一步就是正确接线。梁山派和TSL1401模块的连接非常简单。2.1 引脚定义与连接根据原始资料我们需要连接三根信号线SI、CLK、AO和电源线。具体连接关系如下表所示TSL1401模块引脚梁山派开发板引脚功能说明SIPC2串行输入信号控制积分开始和像素复位CLKPC1时钟信号控制像素电压的依次输出AOPA5模拟信号输出连接至MCU的ADC输入引脚VCC5V 或 3.3V模块电源两种电压均可GNDGND电源地接线要点SI和CLK是数字信号用来控制CCD我们配置为输出模式。AO是模拟信号输出的是电压值必须连接到MCU具有ADC功能的引脚上我们用的是PA5并配置为模拟输入模式。电源接3.3V或5V都可以模块兼容。2.2 硬件连接实物图思路在实际焊接或插线时建议使用杜邦线。确保连接牢固避免接触不良导致数据跳动。可以将模块通过排针固定在面包板或小车的支架上镜头距离地面大约1-2厘米这个高度需要根据实际测试微调以获取清晰的明暗对比波形。3. 软件驱动代码编写硬件接好了接下来就是重头戏——写代码。咱们按照功能模块来拆分讲解。3.1 头文件定义 (ccd.h)头文件里主要做两件事引脚宏定义和函数声明。这样代码看起来清晰以后换引脚也方便。#ifndef __ccd_H #define __ccd_H #include gd32f4xx.h #include systick.h #include stdlib.h #include usart.h // 1. 定义 SI、CLK、AO 引脚的时钟 #define CCD_SI_RCU RCU_GPIOC #define CCD_CLK_RCU RCU_GPIOC #define CCD_AO_RCU RCU_GPIOA // 2. 定义 SI、CLK、AO 引脚所在的端口 #define CCD_SI_PORT GPIOC #define CCD_CLK_PORT GPIOC #define CCD_AO_PORT GPIOA // 3. 定义 SI、CLK、AO 引脚的编号 #define CCD_SI_PIN GPIO_PIN_2 #define CCD_CLK_PIN GPIO_PIN_1 #define CCD_AO_PIN GPIO_PIN_5 // 4. 定义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) // 5. 函数声明 void adc_config(void); // ADC配置函数 void ccd_init(void); // CCD模块初始化GPIO void ccd_read(void); // 读取CCD 128个像素值 void sendToPc(void); // 向上位机发送数据 #endif /* __ccd_H */3.2 ADC配置与读取 (ccd.c 部分代码)CCD的AO引脚输出模拟电压我们需要用ADC把它转换成数字值。梁山派的ADC功能很强大配置起来也不复杂。// 存储ADC转换后的128个像素值 uint16_t adv[128] {0}; /** * brief 配置ADC以ADC0通道5为例对应PA5 * note ADC时钟建议设置在14MHz左右性能最佳 */ void adc_config(void) { /* 1. 使能GPIOA时钟因为AO接在PA5和ADC0时钟 */ rcu_periph_clock_enable(CCD_AO_RCU); rcu_periph_clock_enable(RCU_ADC0); /* 2. 配置ADC时钟同步延迟 */ adc_clock_config(ADC_SYNC_DELAY_10CYCLE); /* 3. 配置PA5引脚为模拟输入模式 */ gpio_mode_set(CCD_AO_PORT, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, CCD_AO_PIN); /* 4. 复位并初始化ADC0 */ adc_deinit(); /* 5. 配置规则组通道第0个转换通道5采样时间56个周期 */ adc_regular_channel_config(ADC0, 0, ADC_CHANNEL_5, ADC_SAMPLETIME_56); /* 6. 使能连续转换模式 */ adc_special_function_config(ADC0, ADC_CONTINUOUS_MODE, ENABLE); /* 7. 数据右对齐、12位分辨率 */ adc_data_alignment_config(ADC0, ADC_DATAALIGN_RIGHT); adc_resolution_config(ADC0, ADC_RESOLUTION_12B); /* 8. 配置为独立模式因为我们只用一个ADC */ adc_sync_mode_config(ADC_SYNC_MODE_INDEPENDENT); adc_sync_delay_config(ADC_SYNC_DELAY_8CYCLE); delay_1ms(1); // 短暂延时稳定 /* 9. 使能ADC并执行校准非常重要 */ adc_enable(ADC0); adc_calibration_enable(ADC0); } /** * brief 读取一次ADC转换值 * retval 12位的ADC转换结果0-4095 */ uint16_t adc_read(void) { uint16_t adc_value; // 再次配置通道并启动软件触发转换在连续模式下通常只需触发一次 adc_regular_channel_config(ADC0, 0U, ADC_CHANNEL_5, ADC_SAMPLETIME_15); adc_software_trigger_enable(ADC0, ADC_REGULAR_CHANNEL); /* 等待转换结束标志位 */ while (!adc_flag_get(ADC0, ADC_FLAG_EOC)) { ; } /* 读取转换结果 */ adc_value adc_regular_data_read(ADC0); return adc_value; }3.3 CCD初始化与读取时序 (ccd.c 核心代码)这是驱动TSL1401最核心的部分我们需要严格按照它的时序要求通过SI和CLK信号来“指挥”它输出数据。/** * brief CCD模块GPIO初始化 */ void ccd_init() { // 使能GPIOC时钟SI和CLK引脚 rcu_periph_clock_enable(CCD_CLK_RCU); rcu_periph_clock_enable(CCD_SI_RCU); // 配置PC1(CLK)和PC2(SI)为推挽输出模式速度25MHz 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); } /** * brief 读取CCD 128个像素点的值 * note 此函数严格按照TSL1401时序编写是数据采集的关键 */ void ccd_read(void) { uint8_t i 0, tslp 0; // --- 时序开始 --- // 步骤1: CLK高SI低保持至少一个时钟周期 CCD_CLK_SET; CCD_SI_RESET; delay_us(2); // 步骤2: SI产生一个高脉冲启动新的积分周期 CCD_SI_SET; CCD_CLK_RESET; // CLK在SI高期间产生一个下降沿 delay_us(2); // 步骤3: SI拉低CLK拉高为输出第一个像素做准备 CCD_CLK_SET; CCD_SI_RESET; delay_us(2); // --- 时序初始化完成 --- // 步骤4: 循环读取128个像素点 for (i 0; i 128; i) { // CLK下降沿输出第i个像素的电压到AO引脚 CCD_CLK_RESET; delay_us(5); // 这个延时决定了曝光积分时间调这个可以改变感光量。 // 在CLK为低时读取AO引脚的电压值 // adc_read() 3 是将12位ADC值(0-4095)缩放到8位左右(0-511)便于处理和显示 // 乘以2.1f是为了放大波形幅度让上位机显示的曲线更明显这个系数可根据实际情况调整 adv[tslp] 2.1f * (adc_read() 3); tslp; // 存储位置索引加1 // CLK上升沿为下一个像素输出做准备 CCD_CLK_SET; delay_us(1); // CLK高电平保持时间 } }关键点解释时序SI和CLK的配合必须严格按照上述步骤。SI的高电平脉冲启动一次新的采样积分随后的128个CLK脉冲依次读出每个像素的值。曝光时间delay_us(5);这行代码的延时就是每个像素的积分时间。增大这个值传感器收集的光子更多输出信号更强但帧率会下降减小则相反。你需要根据环境亮度调整确保黑线和白底的电压差足够大。数据缩放adc_read() 3相当于除以8把0-4095的范围映射到0-511。这只是为了后续处理和显示方便不是必须的。3.4 数据发送与上位机显示为了直观地看到CCD采集到的波形我们需要把数据发送到电脑用上位机软件显示。原始代码里提供了一个sendToPc()函数它按照特定的协议打包数据通过串口发送。// 存储要发送给上位机的数据包 uint8_t scibuf[200]; /** * brief 将数据打包准备发送 */ void slove_data(void) { int i; ccd_read(); // 先采集一次数据到adv数组 // 构建数据包头根据你的上位机通信协议 scibuf[0] 0; scibuf[1] 132; scibuf[2] 0; scibuf[3] 0; scibuf[4] 0; scibuf[5] 0; // 将128个像素数据填入数据包 for (i 0; i 128; i) scibuf[6 i] adv[i]; // adv里的值已被缩放到0-255左右 } /** * brief 向上位机发送数据 * note 此函数按照特定协议发送需要与上位机软件协议匹配 */ void sendToPc(void) { int i; slove_data(); // 准备数据 // 假设的通信协议格式: * LD [数据] 00 # printf(*); printf(LD); for (i 2; i 134; i) // 从scibuf[2]开始发送134-2个字节的数据 { // 将每个字节拆成两个16进制字符发送 printf(%c, binToHex_high(scibuf[i])); printf(%c, binToHex_low(scibuf[i])); } printf(00); // 协议要求 printf(#); // 协议结束符 }这里用到的binToHex_high和binToHex_low函数作用是把一个字节比如0xAE转换成两个ASCII字符A和E发送这是十六进制传输的常见方式。你需要根据自己使用的上位机软件如匿名上位机、山外上位机、串口绘图助手等的通信协议来修改slove_data()和sendToPc()函数中的数据打包格式。4. 主函数与移植验证最后我们把所有功能在main函数里组织起来。#include gd32f4xx.h #include systick.h #include stdio.h #include ccd.h int main(void) { // 1. 系统初始化 nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 设置中断优先级分组 systick_config(); // 初始化系统滴答定时器用于delay_us usart0_config(115200); // 初始化串口0波特率115200用于打印和发送数据 // 2. 模块初始化 adc_config(); // 初始化ADC ccd_init(); // 初始化CCD控制引脚 // 3. 主循环 while (1) { sendToPc(); // 不断采集CCD数据并发送到上位机 delay_ms(50); // 可以加一个延时控制数据发送频率避免串口堵塞 } }如何验证是否成功硬件连接确保梁山派、TSL1401模块、USB串口线连接正确。编译下载将上述代码编译后下载到梁山派开发板。打开串口助手在电脑上打开串口调试助手或专用的上位机软件选择对应的COM口波特率设为115200。观察数据如果使用普通的串口调试助手你会看到一连串的十六进制字符数据流。如果使用支持波形显示的上位机如匿名四轴上位机正确配置协议后你应该能看到一条波形曲线。当CCD镜头前没有黑线时波形是一条平缓的线当有黑线从镜头下经过时波形对应位置会出现一个明显的“凹坑”。5. 进阶应用从数据到巡线控制拿到波形只是第一步我们的最终目标是让小车能沿着黑线走。这里简单讲一下思路原始代码中有一部分被注释掉的动态阈值算法就是干这个的。基本巡线思路二值化设定一个阈值。假设ADC值大于阈值代表白色地面小于阈值代表黑色赛道线。这个阈值可以是固定的也可以是像示例代码里那样每次扫描都计算最大值和最小值的平均值作为动态阈值这样能适应不同的光照。寻找边沿在二值化后的数据中从左到右找第一个从白变黑的位置左边界从右到左找第一个从黑变白的位置右边界。计算中线中线位置 (左边界 右边界) / 2。我们的CCD有128个点理论中点是64。计算偏差偏差 中线位置 - 64。如果偏差为正说明黑线在中点右边小车应该左转偏差为负说明黑线在中点左边小车应该右转。偏差的绝对值大小可以决定转弯的幅度。你可以把计算出的偏差值输入到小车的PID控制器中最终生成电机PWM信号实现平滑的巡线。调试技巧调整曝光时间如果波形整体幅度太低都趴在地上或太高都顶到天花板请调整ccd_read()函数中的delay_us(5);这个值通常在1-20微秒之间尝试。优化阈值算法示例中的动态阈值算法去掉了两边各15个像素点是为了避免边缘畸变。你可以根据实际图像调整这个忽略范围。滤波ADC采集的数据可能会有毛刺可以在adc_read()后或对adv数组进行简单的中值滤波或均值滤波让波形更平滑。希望这篇教程能帮你顺利在梁山派上驱动TSL1401。实际做小车的时候肯定会遇到各种问题比如光线干扰、响应速度、机械抖动等多调试、多尝试这些坑踩过去你的小车就能稳稳跑起来了。
2.44 基于梁山派GD32F470的TSL1401线性CCD模块移植与巡线应用实战
基于梁山派GD32F470的TSL1401线性CCD模块移植与巡线应用实战最近在做一个智能小车项目需要用到巡线功能我选择了TSL1401线性CCD传感器。这个模块价格不贵但效果比普通红外对管好很多能识别更复杂的路线。今天我就把在立创·梁山派GD32F470上驱动这个模块的完整过程分享出来从硬件连接到代码编写再到数据分析和上位机显示手把手带你搞定。1. TSL1401线性CCD模块是什么咱们先来认识一下今天的主角——TSL1401线性CCD模块。你可以把它想象成一个“单行的摄像头”它只有一行128个“眼睛”光电二极管专门用来“看”一条线上的明暗变化。1.1 模块基本工作原理TSL1401内部有128个感光像素点排成一条直线。每个像素点就像一个小光敏电阻光照越强它产生的电信号就越大。模块工作时我们需要通过两个控制信号SI和CLK来告诉它“开始拍照”、“把第一个点的数据给我”、“把第二个点的数据给我”……直到读完128个点。模块内部已经集成了信号放大电路所以从AO引脚输出的就是可以直接给单片机ADC模数转换器读取的电压信号了。电压越高代表那个位置的光线越亮。1.2 它能用来做什么最常见的应用就是小车巡线。把模块装在车头镜头朝下对着地面。当地面上有一条黑线时对应的像素点接收到的反射光就弱输出的电压就低白色的地面反射光强电压就高。这样我们就能得到一条有“凹坑”的电压曲线这个“凹坑”的位置就对应了黑线的位置。通过计算这个位置就能知道小车相对于黑线的偏移量从而控制转向。注意CCD是光学传感器对环境光很敏感。虽然程序里用了动态阈值算法来减少影响但在光线太暗或太亮的环境下比如阳光直射效果还是会打折扣。一般在室内正常光照下使用没问题。2. 硬件连接与引脚配置要把模块用起来第一步就是正确接线。梁山派和TSL1401模块的连接非常简单。2.1 引脚定义与连接根据原始资料我们需要连接三根信号线SI、CLK、AO和电源线。具体连接关系如下表所示TSL1401模块引脚梁山派开发板引脚功能说明SIPC2串行输入信号控制积分开始和像素复位CLKPC1时钟信号控制像素电压的依次输出AOPA5模拟信号输出连接至MCU的ADC输入引脚VCC5V 或 3.3V模块电源两种电压均可GNDGND电源地接线要点SI和CLK是数字信号用来控制CCD我们配置为输出模式。AO是模拟信号输出的是电压值必须连接到MCU具有ADC功能的引脚上我们用的是PA5并配置为模拟输入模式。电源接3.3V或5V都可以模块兼容。2.2 硬件连接实物图思路在实际焊接或插线时建议使用杜邦线。确保连接牢固避免接触不良导致数据跳动。可以将模块通过排针固定在面包板或小车的支架上镜头距离地面大约1-2厘米这个高度需要根据实际测试微调以获取清晰的明暗对比波形。3. 软件驱动代码编写硬件接好了接下来就是重头戏——写代码。咱们按照功能模块来拆分讲解。3.1 头文件定义 (ccd.h)头文件里主要做两件事引脚宏定义和函数声明。这样代码看起来清晰以后换引脚也方便。#ifndef __ccd_H #define __ccd_H #include gd32f4xx.h #include systick.h #include stdlib.h #include usart.h // 1. 定义 SI、CLK、AO 引脚的时钟 #define CCD_SI_RCU RCU_GPIOC #define CCD_CLK_RCU RCU_GPIOC #define CCD_AO_RCU RCU_GPIOA // 2. 定义 SI、CLK、AO 引脚所在的端口 #define CCD_SI_PORT GPIOC #define CCD_CLK_PORT GPIOC #define CCD_AO_PORT GPIOA // 3. 定义 SI、CLK、AO 引脚的编号 #define CCD_SI_PIN GPIO_PIN_2 #define CCD_CLK_PIN GPIO_PIN_1 #define CCD_AO_PIN GPIO_PIN_5 // 4. 定义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) // 5. 函数声明 void adc_config(void); // ADC配置函数 void ccd_init(void); // CCD模块初始化GPIO void ccd_read(void); // 读取CCD 128个像素值 void sendToPc(void); // 向上位机发送数据 #endif /* __ccd_H */3.2 ADC配置与读取 (ccd.c 部分代码)CCD的AO引脚输出模拟电压我们需要用ADC把它转换成数字值。梁山派的ADC功能很强大配置起来也不复杂。// 存储ADC转换后的128个像素值 uint16_t adv[128] {0}; /** * brief 配置ADC以ADC0通道5为例对应PA5 * note ADC时钟建议设置在14MHz左右性能最佳 */ void adc_config(void) { /* 1. 使能GPIOA时钟因为AO接在PA5和ADC0时钟 */ rcu_periph_clock_enable(CCD_AO_RCU); rcu_periph_clock_enable(RCU_ADC0); /* 2. 配置ADC时钟同步延迟 */ adc_clock_config(ADC_SYNC_DELAY_10CYCLE); /* 3. 配置PA5引脚为模拟输入模式 */ gpio_mode_set(CCD_AO_PORT, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, CCD_AO_PIN); /* 4. 复位并初始化ADC0 */ adc_deinit(); /* 5. 配置规则组通道第0个转换通道5采样时间56个周期 */ adc_regular_channel_config(ADC0, 0, ADC_CHANNEL_5, ADC_SAMPLETIME_56); /* 6. 使能连续转换模式 */ adc_special_function_config(ADC0, ADC_CONTINUOUS_MODE, ENABLE); /* 7. 数据右对齐、12位分辨率 */ adc_data_alignment_config(ADC0, ADC_DATAALIGN_RIGHT); adc_resolution_config(ADC0, ADC_RESOLUTION_12B); /* 8. 配置为独立模式因为我们只用一个ADC */ adc_sync_mode_config(ADC_SYNC_MODE_INDEPENDENT); adc_sync_delay_config(ADC_SYNC_DELAY_8CYCLE); delay_1ms(1); // 短暂延时稳定 /* 9. 使能ADC并执行校准非常重要 */ adc_enable(ADC0); adc_calibration_enable(ADC0); } /** * brief 读取一次ADC转换值 * retval 12位的ADC转换结果0-4095 */ uint16_t adc_read(void) { uint16_t adc_value; // 再次配置通道并启动软件触发转换在连续模式下通常只需触发一次 adc_regular_channel_config(ADC0, 0U, ADC_CHANNEL_5, ADC_SAMPLETIME_15); adc_software_trigger_enable(ADC0, ADC_REGULAR_CHANNEL); /* 等待转换结束标志位 */ while (!adc_flag_get(ADC0, ADC_FLAG_EOC)) { ; } /* 读取转换结果 */ adc_value adc_regular_data_read(ADC0); return adc_value; }3.3 CCD初始化与读取时序 (ccd.c 核心代码)这是驱动TSL1401最核心的部分我们需要严格按照它的时序要求通过SI和CLK信号来“指挥”它输出数据。/** * brief CCD模块GPIO初始化 */ void ccd_init() { // 使能GPIOC时钟SI和CLK引脚 rcu_periph_clock_enable(CCD_CLK_RCU); rcu_periph_clock_enable(CCD_SI_RCU); // 配置PC1(CLK)和PC2(SI)为推挽输出模式速度25MHz 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); } /** * brief 读取CCD 128个像素点的值 * note 此函数严格按照TSL1401时序编写是数据采集的关键 */ void ccd_read(void) { uint8_t i 0, tslp 0; // --- 时序开始 --- // 步骤1: CLK高SI低保持至少一个时钟周期 CCD_CLK_SET; CCD_SI_RESET; delay_us(2); // 步骤2: SI产生一个高脉冲启动新的积分周期 CCD_SI_SET; CCD_CLK_RESET; // CLK在SI高期间产生一个下降沿 delay_us(2); // 步骤3: SI拉低CLK拉高为输出第一个像素做准备 CCD_CLK_SET; CCD_SI_RESET; delay_us(2); // --- 时序初始化完成 --- // 步骤4: 循环读取128个像素点 for (i 0; i 128; i) { // CLK下降沿输出第i个像素的电压到AO引脚 CCD_CLK_RESET; delay_us(5); // 这个延时决定了曝光积分时间调这个可以改变感光量。 // 在CLK为低时读取AO引脚的电压值 // adc_read() 3 是将12位ADC值(0-4095)缩放到8位左右(0-511)便于处理和显示 // 乘以2.1f是为了放大波形幅度让上位机显示的曲线更明显这个系数可根据实际情况调整 adv[tslp] 2.1f * (adc_read() 3); tslp; // 存储位置索引加1 // CLK上升沿为下一个像素输出做准备 CCD_CLK_SET; delay_us(1); // CLK高电平保持时间 } }关键点解释时序SI和CLK的配合必须严格按照上述步骤。SI的高电平脉冲启动一次新的采样积分随后的128个CLK脉冲依次读出每个像素的值。曝光时间delay_us(5);这行代码的延时就是每个像素的积分时间。增大这个值传感器收集的光子更多输出信号更强但帧率会下降减小则相反。你需要根据环境亮度调整确保黑线和白底的电压差足够大。数据缩放adc_read() 3相当于除以8把0-4095的范围映射到0-511。这只是为了后续处理和显示方便不是必须的。3.4 数据发送与上位机显示为了直观地看到CCD采集到的波形我们需要把数据发送到电脑用上位机软件显示。原始代码里提供了一个sendToPc()函数它按照特定的协议打包数据通过串口发送。// 存储要发送给上位机的数据包 uint8_t scibuf[200]; /** * brief 将数据打包准备发送 */ void slove_data(void) { int i; ccd_read(); // 先采集一次数据到adv数组 // 构建数据包头根据你的上位机通信协议 scibuf[0] 0; scibuf[1] 132; scibuf[2] 0; scibuf[3] 0; scibuf[4] 0; scibuf[5] 0; // 将128个像素数据填入数据包 for (i 0; i 128; i) scibuf[6 i] adv[i]; // adv里的值已被缩放到0-255左右 } /** * brief 向上位机发送数据 * note 此函数按照特定协议发送需要与上位机软件协议匹配 */ void sendToPc(void) { int i; slove_data(); // 准备数据 // 假设的通信协议格式: * LD [数据] 00 # printf(*); printf(LD); for (i 2; i 134; i) // 从scibuf[2]开始发送134-2个字节的数据 { // 将每个字节拆成两个16进制字符发送 printf(%c, binToHex_high(scibuf[i])); printf(%c, binToHex_low(scibuf[i])); } printf(00); // 协议要求 printf(#); // 协议结束符 }这里用到的binToHex_high和binToHex_low函数作用是把一个字节比如0xAE转换成两个ASCII字符A和E发送这是十六进制传输的常见方式。你需要根据自己使用的上位机软件如匿名上位机、山外上位机、串口绘图助手等的通信协议来修改slove_data()和sendToPc()函数中的数据打包格式。4. 主函数与移植验证最后我们把所有功能在main函数里组织起来。#include gd32f4xx.h #include systick.h #include stdio.h #include ccd.h int main(void) { // 1. 系统初始化 nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 设置中断优先级分组 systick_config(); // 初始化系统滴答定时器用于delay_us usart0_config(115200); // 初始化串口0波特率115200用于打印和发送数据 // 2. 模块初始化 adc_config(); // 初始化ADC ccd_init(); // 初始化CCD控制引脚 // 3. 主循环 while (1) { sendToPc(); // 不断采集CCD数据并发送到上位机 delay_ms(50); // 可以加一个延时控制数据发送频率避免串口堵塞 } }如何验证是否成功硬件连接确保梁山派、TSL1401模块、USB串口线连接正确。编译下载将上述代码编译后下载到梁山派开发板。打开串口助手在电脑上打开串口调试助手或专用的上位机软件选择对应的COM口波特率设为115200。观察数据如果使用普通的串口调试助手你会看到一连串的十六进制字符数据流。如果使用支持波形显示的上位机如匿名四轴上位机正确配置协议后你应该能看到一条波形曲线。当CCD镜头前没有黑线时波形是一条平缓的线当有黑线从镜头下经过时波形对应位置会出现一个明显的“凹坑”。5. 进阶应用从数据到巡线控制拿到波形只是第一步我们的最终目标是让小车能沿着黑线走。这里简单讲一下思路原始代码中有一部分被注释掉的动态阈值算法就是干这个的。基本巡线思路二值化设定一个阈值。假设ADC值大于阈值代表白色地面小于阈值代表黑色赛道线。这个阈值可以是固定的也可以是像示例代码里那样每次扫描都计算最大值和最小值的平均值作为动态阈值这样能适应不同的光照。寻找边沿在二值化后的数据中从左到右找第一个从白变黑的位置左边界从右到左找第一个从黑变白的位置右边界。计算中线中线位置 (左边界 右边界) / 2。我们的CCD有128个点理论中点是64。计算偏差偏差 中线位置 - 64。如果偏差为正说明黑线在中点右边小车应该左转偏差为负说明黑线在中点左边小车应该右转。偏差的绝对值大小可以决定转弯的幅度。你可以把计算出的偏差值输入到小车的PID控制器中最终生成电机PWM信号实现平滑的巡线。调试技巧调整曝光时间如果波形整体幅度太低都趴在地上或太高都顶到天花板请调整ccd_read()函数中的delay_us(5);这个值通常在1-20微秒之间尝试。优化阈值算法示例中的动态阈值算法去掉了两边各15个像素点是为了避免边缘畸变。你可以根据实际图像调整这个忽略范围。滤波ADC采集的数据可能会有毛刺可以在adc_read()后或对adv数组进行简单的中值滤波或均值滤波让波形更平滑。希望这篇教程能帮你顺利在梁山派上驱动TSL1401。实际做小车的时候肯定会遇到各种问题比如光线干扰、响应速度、机械抖动等多调试、多尝试这些坑踩过去你的小车就能稳稳跑起来了。