一,什么是线性CCD?为什么要使用线性CCD?
他的样子先看看
他的原理先了解了解
来自某宝界面的介绍哈
下面说说我的理解,就是一排128个的灰度模块,主要用来循迹。
128个采样点 返回的高低电压来确定读到的是黑色还是白色 进而来循迹。
在遇到岔路口的情况,灰度或者红外模块可能无法准确判别属于哪种情况(由程序设定),这个时候采用线性ccd,读取高低电平的坐标点数,进而来判断是否遇到了岔路口还是停车线。
CCD是光学传感器,跟灰度一样受光的影响比较大,太暗(离地面太近)和太亮(离地面太远)是没办法正常工作(不正常就会”乱发“数据)的。
二,CCD咋用?
首先,他有五个口:AO,CLK,GND,SI,VDD
可以通过查看数据手册来看管脚介绍
还可以参考这张
又通过手册看到他的工作说明
然后附着了这一段话:
通过在 SI 上输入逻辑 1 来启动输出周期。为了达到正常操作,在满 足最小保持时间条件后,SI 必须在时钟的下一个上升沿之前变低。内 部信号称为 Hold,由 SI 的上升沿并传输到像素电路中的模拟开关采 样。电容器与其各自的积分器断开连接,在 18 个时钟周期内复位。 当 SI 脉冲通过移位寄存器计时时,存储在采样电容器上的电荷顺序 连接到电荷耦合输出放大器,该放大器在模拟输出 AO 上产生电压。 第 129 个时钟上升沿,SI 脉冲从移位寄存器移出,模拟输出 AO 处于 高阻抗状态。注意,需要第 129 个时钟脉冲来终止第 128 个像素的输 出,并使内部逻辑返回到已知状态。如果期望最小积分时间,则可以 在第 129 个时钟脉冲之后的最小延迟 tqt(像素电荷转移时间)之后 出现下一个 SI 脉冲。
这时候 ,你可能会想:啊,这么多。我不想看啊,这么多我也看不懂啊。
其实就是(程序思路):
1 首先设置周期为 129us 的定时中断。
2 每次进入中断后设置首先 SI为高电平,然后恢复为低电平以激活采集和输出任务。
3 然后在每次 CLK 的下降沿后对 AO进行 ADC 采集, 共 128 次,即采集 AO 输出的灰度值信号,同时 CCD 硬件自动采集 灰度值,为下一次 AO 引脚输出灰度值做准备。
4 以上为中断服务函数的内容,最后我们设置在主函数设置串 口定时发送 128 个灰度值,以便使用串口调试助手查看灰度值变化。
那我们就直接看程序;
我购买的ccd模块附送了例程,我们看例程入手!
三,相关例程学习
1,首先必不可少的初始化
void ccd_Init(void)
{
ADC_InitTypeDef ADC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_ADC1 , ENABLE ); //使能ADC1通道时钟
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M
//设置模拟通道输入引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输入引脚
GPIO_Init(GPIOA, &GPIO_InitStructure);
ADC_DeInit(ADC1); //复位ADC1,将外设 ADC1 的全部寄存器重设为缺省值
//初始化SI,CLK接口
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC工作模式:ADC1和ADC2工作在独立模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE; //模数转换工作在单通道模式
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //模数转换工作在单次转换模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //转换由软件而不是外部触发启动
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC数据右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1; //顺序进行规则转换的ADC通道的数目
ADC_Init(ADC1, &ADC_InitStructure); //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器
ADC_Cmd(ADC1, ENABLE); //使能指定的ADC1
ADC_ResetCalibration(ADC1); //使能复位校准
while(ADC_GetResetCalibrationStatus(ADC1)); //等待复位校准结束
ADC_StartCalibration(ADC1); //开启AD校准
while(ADC_GetCalibrationStatus(ADC1)); //等待校准结束
}
2,开始用CCD
a)CCD数据进行采样
void RD_TSL(void)
{
u8 i=0,tslp=0;
TSL_CLK=1;
TSL_SI=0;
Dly_us();
TSL_SI=1;
TSL_CLK=0;
Dly_us();
TSL_CLK=1;
TSL_SI=0;
Dly_us();
for(i=0;i<128;i++) //读取128个像素点电压值
{
TSL_CLK=0;
Dly_us(); //调节曝光时间
Dly_us();
ADV[tslp]=(Get_Adc(3))>>4;
++tslp;
TSL_CLK=1;
Dly_us();
}
}
这里我们就用到了SI和CLK引脚,分别进行拉高置低的操作,来让CCD进行工作。
b)出现了一个新函数Get_Adc(u8 ch)
用来进行AD采样的
/**************************************************************************
Function: The AD sampling
Input : The ADC channels
Output : AD conversion results
函数功能:AD采样
入口参数:ADC的通道
返回 值:AD转换结果
**************************************************************************/
u16 Get_Adc(u8 ch)
{
//Sets the specified ADC rule group channel, one sequence, and sampling time
//设置指定ADC的规则组通道,一个序列,采样时间
//ADC1,ADC通道,采样时间为480周期
ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5 ); //ADC1,ADC通道,采样时间为239.5周期
//Enable the specified ADC1 software transformation startup function
//使能指定的ADC1的软件转换启动功能
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
//Wait for the conversion to finish
//等待转换结束
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));
//Returns the result of the last ADC1 rule group conversion
//返回最近一次ADC1规则组的转换结果
return ADC_GetConversionValue(ADC1);
}
这里是不是对CCD如何工作的有了更加大致的了解?
初用CCD,需要用配套的上位机进行查看工作状况,所以配套发送给上位机的程序。
/******************************************************************************
***
* FUNCTION NAME: void sendToPc(void) *
* CREATE DATE : 20170707 *
* CREATED BY : XJU *
* FUNCTION : 将待发送的信息通过串口发送至上位机*
* MODIFY DATE : NONE *
* INPUT : void *
* OUTPUT : NONE *
* RETURN : NONE *
*******************************************************************************
**/
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("#"); //通信协议要求
}
void slove_data(void)
{
int i;
RD_TSL();
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];
}
/******************************************************************************
***
* FUNCTION NAME: binToHex_low(u8 num) *
* CREATE DATE : 20170707 *
* CREATED BY : XJU *
* FUNCTION : 将二进制低8位转换16进制*
* MODIFY DATE : NONE *
* INPUT : u8 *
* OUTPUT : NONE *
* RETURN : char *
*******************************************************************************
**/
char binToHex_low(u8 num)
{u8 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');
}
/******************************************************************************
***
* FUNCTION NAME: binToHex_low(u8 num) *
* CREATE DATE : 20170707 *
* CREATED BY : XJU *
* FUNCTION : 将二进制高8位转换16进制*
* MODIFY DATE : NONE *
* INPUT : u8 *
* OUTPUT : NONE *
* RETURN : char *
*******************************************************************************
**/
char binToHex_high(u8 num)
{
u8 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');
}
对应的头文件 就包含
#ifndef __ADC_H
#define __ADC_H
#include "sys.h"
#include "system.h"
#define TSL_SI PAout(7) //SI
#define TSL_CLK PAout(2) //CLK
u16 Get_Adc(u8 ch);
void Dly_us(void);
void RD_TSL(void);
void CCD(void);
void ccd_Init(void);
char binToHex_high(u8 num);
char binToHex_low(u8 num);
void slove_data(void);
void sendToPc(void);
#endif
连接好引脚就可以在上位机读取到数据了。
这时候初步的感觉出来了CCD的作用。
接下来,我们用在循迹中看看。
四,实际操作
打开详细介绍的这个工程,我先看他的main函数有些啥
该初始化初始化,while有个串口接收的函数,发CCD128个数据的
先连上,接上串口,看看你的CCD,是不是能接收数据(是不是好货【狗头】)。
这个例程特别详细,大家可以对应参考学习。
下面我们进行巡线咯,学习代码来自同门师哥的指导(在场感谢)
我们知道CCD返回给我们128个数据,那我咋用这数据来进行循迹?
用一个数值处理函数,读取我们黑线的左跳变,右跳变,中值以及阈值。
当你需要巡内圈的时候,以左跳变为目标值。寻外圈,以右跳变为目标值(PID的期望值)即可。当你只需要寻一根线,那就以中值为目标值。
下面给出处理函数:
/************************************************************************************************************************
*函数名:Find_Middle_CCD()
*功能:读取CCD中值
*形参:无
*返回值:CCD中值位置
*************************************************************************************************************************/
uint8_t Find_Middle_CCD(void)
{
static uint8_t i,j;
//static uint8_t Last_Middle_CCD;
uint8_t Middle_CCD_Value;
static uint16_t value1_max,value1_min;
value1_max=ADV[0];
//读取最大值
for(i=5;i<123;i++){
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_threshold=(value1_max+value1_min)/2;
//printf("%d\r\n",CCD_threshold);
//计算左跳变值
for(i = 5;i<118; i++){
if(ADV[i]>CCD_threshold&&ADV[i+1]>CCD_threshold&&ADV[i+2]>CCD_threshold&&ADV[i+3]5; j--){
if(ADV[j]CCD_threshold&&ADV[j+4]>CCD_threshold&&ADV[j+5]>CCD_threshold){
Right=j;
break;
}
}
//计算中值
Middle_CCD_Value=(Right+Left)/2;
// if(abs(Middle_CCD_Value-Last_Middle_CCD)>70){
// Middle_CCD_Value=Last_Middle_CCD;
// Last_Middle_CCD=Middle_CCD_Value;
// }
return Middle_CCD_Value;
}
/**************************************************************************************************************************
*函数名:Get_CCD_data()
*功能:整理需要发送的CCD数据
*形参:无
*返回值:无
***************************************************************************************************************************/
void Get_CCD_data(void)
{
//CCD中值
CCD_data_package[CCD_MIDDLE]=Find_Middle_CCD();
//CCD阈值
CCD_data_package[CCD_THRESHOLD]=CCD_threshold;
//CCD左跳变值
CCD_data_package[CCD_LEFT]=Left;
//CCD右跳变值
CCD_data_package[CCD_RIGHT]=Right;
}
小白一个,感谢各位大佬指点!
对你有帮助,请点赞支持。