基于STM32F103C8T6的CCD线性摄像头寻线寻迹小车
前言
目前大多数的小车寻线寻迹都是用红外对管寻线,这是比较简单也比较成熟的技术方案,且成本也低。本文将介绍使用CCD线性摄像头寻线寻迹。
一、模块介绍
TSL1401 线性传感器由一个 1x128 的光电二极管阵列、相关的电荷放大电路以及一个内部像素数据保功能组成。内部像素数据保功能可以为所有像素点提供同时积分的开始和停止时间。该阵列由 128 个像素组成,每个像素的感光面积为 3,524.3 平方微米。 像素之间的间隔为 8μm。内部控制逻辑简化了操作,该模块需要串行输入(SI)信号和时钟信号(CLK)。
二、使用说明
1.引脚说明
通过查看数据手册模块的管脚介绍如上
- AO: 信号输出(供单片机进行信号采集读取)
- CLK: 时钟信号
- GND: 地线
- SI: 信号输入(单片机向 CCD 发送指令)
- VDD: 电源(模块引脚标识为 VCC,接单片机逻辑电平,支持 3~5V)
2.其他
CCD 传感器是光学传感器,会受到环境光线的影响;程序中已经运用了动态阈值算法,尽量减小环境光线的影响,但是太暗或者太亮的环境下是不能正常工作的(一般室内正常光线可以运行)。
三、模块调试
在CCD调试助手中可以看出,当CCD摄像头扫描到黑线时,会出现一个凹槽,左右移动,凹槽也移动,脱离黑线凹槽消失。从这条线大概可以看出中值为多少。 通过串口助手打印出的中值结果如下:
四、部分代码
1.CCD摄像头相关代码
首先对CCD摄像头初始化所用到的管脚初始化:
void Ccd_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA,GPIO_Pin_2);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA,GPIO_Pin_3);
}
获取中值:
void RD_TSL(void)
{
u8 i=0,tslp=0;
static u8 j,Left,Right,Last_CCD_Zhongzhi;
static u16 value1_max,value1_min;
TSL_CLK=1;
TSL_SI=0;
Dly_us(TIME_us);
TSL_SI=1;
TSL_CLK=0;
Dly_us(TIME_us);
TSL_CLK=1;
TSL_SI=0;
Dly_us(TIME_us);
for(i=0;i<128;i++)
{
TSL_CLK=0;
Dly_us(TIME_us);
ccd_adc[tslp]=(u8)((float)Get_Adc(ADC_Channel_1)/4096*255);
++tslp;
TSL_CLK=1;
Dly_us(TIME_us);
}
value1_max=ccd_adc[0];
for(i=5;i<123;i++)
{
if(value1_max<=ccd_adc[i])
value1_max=ccd_adc[i];
}
value1_min=ccd_adc[0];
for(i=5;i<123;i++)
{
if(value1_min>=ccd_adc[i])
{
value1_min=ccd_adc[i];
}
}
CCD_Yuzhi=(value1_max+value1_min)/2;
for(i = 5;i<118; i++)
{
if(ccd_adc[i]>CCD_Yuzhi&&ccd_adc[i+1]>CCD_Yuzhi&&ccd_adc[i+2]>CCD_Yuzhi&&ccd_adc[i+3]<CCD_Yuzhi&&ccd_adc[i+4]<CCD_Yuzhi&&ccd_adc[i+5]<CCD_Yuzhi)
{
Left=i;
break;
}
}
for(j = 118;j>5; j--)
{
if(ccd_adc[j]<CCD_Yuzhi&&ccd_adc[j+1]<CCD_Yuzhi&&ccd_adc[j+2]<CCD_Yuzhi&&ccd_adc[j+3]>CCD_Yuzhi&&ccd_adc[j+4]>CCD_Yuzhi&&ccd_adc[j+5]>CCD_Yuzhi)
{
Right=j;
break;
}
}
CCD_Zhongzhi=(Right+Left)/2;
if(myabs(CCD_Zhongzhi-Last_CCD_Zhongzhi)>70)
CCD_Zhongzhi=Last_CCD_Zhongzhi;
Last_CCD_Zhongzhi=CCD_Zhongzhi;
}
发送至上位机调试:
void sendToPc(void)
{
int i;
slove_data();
printf("*");
printf("LD");
for(i=2;i<134;i++)
{
printf("%c",binToHex_high(SciBuf[i]));
printf("%c",binToHex_low(SciBuf[i]));
}
printf("00");
printf("#");
}
2.ADC采集相关代码
ADC定义初始化:
void Adc_Init(void)
{
ADC_InitTypeDef ADC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_ADC1 , ENABLE );
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
ADC_DeInit(ADC1);
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
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_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &ADC_InitStructure);
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
}
获取ADC值:
u16 Get_Adc(u8 ch)
{
ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5 );
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));
return ADC_GetConversionValue(ADC1);
}
3.主函数代码
u8 CCD_Zhongzhi=64,CCD_Yuzhi;
int main(void)
{
int piancha=0,jiaodu;
delay_init();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
uart_init(115200);
Adc_Init();
Ccd_Init();
TIME_us=1;
TIM1_PWM_Init(7199,0);
Motor_Init();
SERVO_Init();
while(1)
{
RD_TSL();
printf("%d\n",CCD_Zhongzhi);
piancha = (CCD_Zhongzhi-64)/3;
jiaodu = 75+piancha;
SERVO_Angle_Control(jiaodu);
go();
}
}
四、小车硬件
主控:STM32F103C8T6 摄像头:线性CCD( TSL1401CL) 电机驱动:TB6612FNG 舵机一个 电机两个 LM2596S稳压模块 7.4V航模锂电池
五、演示视频
总结
用CCD线性摄像头寻迹寻线小车跑起来更稳更丝滑,跑黑线的类型也多一点。参考:线性CCD基础学习
|