使用线性CCD循迹

一,什么是线性CCD?为什么要使用线性CCD?

他的样子先看看

他的原理先了解了解

使用线性CCD循迹_第1张图片

来自某宝界面的介绍哈

下面说说我的理解,就是一排128个的灰度模块,主要用来循迹。

128个采样点 返回的高低电压来确定读到的是黑色还是白色 进而来循迹。

在遇到岔路口的情况,灰度或者红外模块可能无法准确判别属于哪种情况(由程序设定),这个时候采用线性ccd,读取高低电平的坐标点数,进而来判断是否遇到了岔路口还是停车线。

CCD是光学传感器,跟灰度一样受光的影响比较大,太暗(离地面太近)和太亮(离地面太远)是没办法正常工作(不正常就会”乱发“数据)的。

二,CCD咋用?

首先,他有五个口:AO,CLK,GND,SI,VDD

可以通过查看数据手册来看管脚介绍

使用线性CCD循迹_第2张图片

还可以参考这张

使用线性CCD循迹_第3张图片

 

 又通过手册看到他的工作说明

然后附着了这一段话:

通过在 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;
 }

小白一个,感谢各位大佬指点!

对你有帮助,请点赞支持。

你可能感兴趣的:(嵌入式学习,stm32,嵌入式硬件)