精华内容
下载资源
问答
  • 虽然摄像头容易受光干扰、信息处理起来稍显复杂,不如电磁那样简单和稳定,但是因为摄像头前瞻相对较远并且可调,因此速度上限更高。下面简单介绍摄像头ov7725的配置 摄像头 ...2.另外我参考野火关于ov7725的介绍来梳理...

    虽然摄像头容易受光干扰、信息处理起来稍显复杂,不如电磁那样简单和稳定,但是因为摄像头前瞻相对较远并且可调,因此速度上限更高。下面简单介绍摄像头ov7725的配置

    一、摄像头与DMA

    1.先简单了解一下ov7725,采用的是SCCB协议,关于此的介绍参考这篇进行了解:https://blog.csdn.net/weixin_43529046/article/details/90453815

    2.另外我参考野火关于ov7725的介绍来梳理一下内容。用的是野火鹰眼摄像头,是硬件二值化摄像头即只有黑白两色,场中断是一帧图像的开始,场中断出现后既会触发自身的场中断函数(主要负责交换缓冲区数组以及设置一些标志位),也会打开DMA传输(因为DMA的中断请求源设为了场中断引脚),然后DMA会在PCLK下降沿采集和传输数据,当采集完一帧图像后就会触发DMA的中断函数(这个中断函数的作用大致也是交换图像缓冲数组)
    在这里插入图片描述这一页介绍了数字摄像头的像素采集顺序即从左到右,从上到下。至于奇偶场,奇数场只采集奇数行的像素值,偶数场只采集偶数行的像素值,因此一帧图的采集需要两场,而ov7725不分奇偶场
    在这里插入图片描述
    在这里插入图片描述提供的几种采集图像的思路
    在这里插入图片描述
    一次传输8个像素,一个位就表示一个像素(因为通过了硬件二值化),这样一来,一个字节就表示8
    个像素点,一次采集就相当于采了8个像素点。而对于非硬件二值化的,一次采集8位构成了一个字节,这一个字节表示一个像素点,对比之下,硬件二值化的采集速度是它的8倍。硬件二值化由此也带来了一个问题,对于每一个像素点的访问比较麻烦,因为像素是和位对应起来的,因此就需要解压函数对其格式进行修改,把每一位都单独取出来,重新放到数组中以方便访问,这也就是后面解压函数的作用

    3.摄像头配置

    原理图
    在这里插入图片描述在这里插入图片描述对照着看,我们需要配置的有数据线、时钟信号线、场中断信号线(没有奇偶场,一场就是一帧)、SCCB控制信号线

    3.1 GPIO口的配置

    //数据口的配置
    static GPIO_InitTypeDef data_init;
    data_init.GPIO_PTx = PTC;//引脚组
    data_init.GPIO_Dir = DIR_INPUT;//方向
    data_init.GPIO_Pins = GPIO_Pin8_15;//引脚号
    data_init.GPIO_PinControl = IRQC_DIS | INPUT_PULL_DIS;
    LPLD_GPIO_Init(data_init);
    
    //场信号口的配置
    static GPIO_InitTypeDef vsyn_init;
    vsyn_init.GPIO_PTx = PTC;
    vsyn_init.GPIO_Dir = DIR_INPUT;
    vsyn_init.GPIO_Pins = GPIO_Pin19;
    vsyn_init.GPIO_PinControl = IRQC_RI|INPUT_PULL_DOWN|INPUT_PF_EN ;
    vsyn_init.GPIO_Isr = vsyn_isr;//触发场中断后调用场中断函数
    LPLD_GPIO_Init(vsyn_init);
    LPLD_GPIO_EnableIrq(vsyn_init);
    
    //PCLK信号口配置
    static GPIO_InitTypeDef pclk_init;
    pclk_init.GPIO_PTx = PTC;
    pclk_init.GPIO_Pins = GPIO_Pin18;
    pclk_init.GPIO_Dir = DIR_INPUT;
    pclk_init.GPIO_PinControl = IRQC_DMAFA | INPUT_PULL_DIS;
    LPLD_GPIO_Init(pclk_init);
    

    3.2 SCCB接口的配置
    在底层库DEV_SCCB.h文件中可以找到SCCB的引脚配置,只需对照原理图修改SCL、SDA对应的引脚即可

    //定义引脚
    #define SCCB_SCL  		PTC16_O
    #define SCCB_SDA_O		PTC17_O
    #define SCCB_SDA_I		PTC17_I
    
    //输入输出
    #define SCCB_SDA_OUT()	DDRC17=1
    #define SCCB_SDA_IN()	DDRC17=0
    

    此外,DEV_SCCB.c文件中的LPLD_SCCB_Init()函数也需要修改引脚

    void LPLD_SCCB_Init(void)
    {
    	GPIO_InitTypedef gpio_init;
    	gpio_init.GPIO_Ptx=PTC;//引脚组
    	gpio_init.GPIO_Pins=GPIO_Pin16|GPIO_Pin17;//引脚号
    	gpio_init.GPIO_Dir=DIR_OUTPUT;
    	gpio_init.GPIO_Output=OUTPUT_H;
    	gpio_init.GPIO_PinControl=NULL;
    	LPLD_GPIO_Init(gpio_init);
    }
    

    4 . DMA的配置
    参考这篇了解DMA的知识:https://blog.csdn.net/zhejfl/article/details/82555634
    在这里插入图片描述在这里插入图片描述
    展示了部分请求源

    #define ROW		120
    #define COLUMN	160
    
    //图像缓存区,都是8位的,恰好对应
    uint8	Image_Buf[ROW+10][COLUMN/8];//[130][20]
    uint8	Image_Buf2[ROW+10][COLUMN/8];
    
    static DMA_InitTypeDef dma_init_struct;
    dma_init_struct.DMA_CHx = DMA_CH0;//CH0通道
    dma_init_struct.DMA_Req = PORTC_DMAREQ;//PORTC为请求源
    dma_init_struct.DMA_MajorLoopCnt = ROW *COLUMN/8;//主循环计数值:字节数
    dma_init_struct.DMA_MinorByteCnt = 1;//次循环字节计数:每次读入1字节(8位)
    dma_init_struct.DMA_SourceAddr = (uint32)&PTC->PDIR+1;// 数据的源地址:PTC0~7
    dma_init_struct.DMA_DestAddr = (uint32)Image_Buf;//配置目的数据地址目的地址:存放图像的数组
    dma_init_struct.DMA_DestAddrOffset = 1;//目的地址偏移:每次读入增加1
    dma_init_struct.DMA_MajorCompleteIntEnable = TRUE;//完成中断请求
    dma_init_struct.DMA_AutoDisableReq =TRUE;
    dma_init_struct.DMA_Isr =DMA_complete_isr;//中断函数
    LPLD_DMA_Init(dma_init_struct);
    LPLD_DMA_EnableIrq(dma_init_struct); 
    

    以上DMA的配置完成了:
    可以从PORTC接收传输请求,然后去读取PRTC0 - 7端口的数据值,次循环每次读一个字节恰好对应8个端口的8位数据即1个字节,按照这样的读法读取 (120 x 160 / 8)次,就算完成了一次的传输,触发DMA中断。另外,传输的数据存放在数组Image_Buf[ ][ ]中,其数组元素为8位的一个字节,地址每次偏移1字节就相当于移动到下一个数组元素。

    5 . 摄像头寄存器的初始化

    //宏定义标志寄存器地址
    #define OV7725_ID        0x21
    #define OV7725_GAIN      0x00
    #define OV7725_BLUE      0x01
    #define OV7725_COM7      0x12
    #define OV7725_CNST      0x9C
    ......//把所有寄存器地址进行宏定义
    
    
    //自定义结构体类型
    typedef struct{
    	uint8 addr;//寄存器地址
    	uint8 val;//寄存器的值
    }reg_s;
    //定义一个此类型的结构体数组变量,即每个成员都是一个结构体,构成了一个数组
    reg_s ov7725_eagle_reg[]=
    {
    	{OV7725_COM4,0xC1},
    	{OV7725_CLKRC,0x00},
    	{OV7725_COM2,0x03},
    	{OV7725_COM3,0xD0},
    	{OV7725_COM7,0x40},
    	......
    	//把摄像头所需的寄存器地址以及值都放在这里,方便使用
    	//OV7725_COM4表示的是寄存器地址,用的是宏定义
    }
    
    uint8 Camera_CNST=60;
    uint8 ov7725init()
    {
    	int16 i=0,j=0,device_id=0;
    	LPLD_SCCB_Init();//DEV_SCCB.c文件中定义的初始化函数
    	camera_delay();//延时函数
    	while(0==LPLD_SCCB_WriteReg(OV7725_COM7,0x80))
    	{//复位:把值0x80写入到寄存器OV7725_COM7中,把所有寄存器恢复到默认值
    		i++;
    		if(i==500)
    			return 0;//长时间等待时则退出
    	}
    	//初始化数组
    	for(i=0;i<120;i++)
    	{
    		for(j=0;j<160;j++)
    		{
    			Image[i][j]=0;
    		}
    	}
    	//初始化缓冲数组
    	for(i=0;i<120;i++)
    	{
    		for(j=0;j<20;j++)
    		{
    			Image_Buf[i][j]=255;
    			Image_Buf2[i][j]=255;
    		}
    	}
    	camera_delay();
    	//读取设备的ID号,确定设备存在
    	if(0==LPLD_SCCB_ReadReg(OV7725_VER,&devive_id,1))
    	//参数:寄存器地址,读出来后存到这个地址,读取长度
    	{
    		return 0;//读取失败则退出
    	}
    	camera_delay();
    	if(device_id==OV7725_ID)//唯一的地址对应上
    	{
    		for(i=0;i<ov7725_eagle_cfgnum;i++)
    		{
    			if(0==LPLD_SCCB_WriteReg(ov7725_eagle_reg[i].addr,ov7725_eagle_reg[i].val))
    			//把寄存器值按照寄存器地址逐个写入到寄存器中,从而实现对摄像头的配置
    			{
    				return 0;//出现写入失败就退出
    			}
    		}
    		LPLD_SCCB_WriteReg(OV7725_CNST,Camera_CNST);
    	}
    	else
    	{
    		return 0;
    	}
    	return 1;
    	
    }
    

    关于ov7725寄存器的设置,会影响到其帧率、大小等,参考这篇了解:https://www.cnblogs.com/raymon-tec/p/5090185.html

    通过把摄像头各个寄存器配置成想要的值从而实现不同的帧率、亮度、对比度等以满足不同的需求,这也就是摄像头寄存器初始化的主要内容。需要修改的寄存器有,PCLK速率,帧率、图像亮度、对比度、色饱和度等

    6 . 场中断函数
    每一场结束即每一帧结束就触发场中断函数

    //主要是在修改DMA的目的地址,因为有两个缓冲区交替使用
    void vsyn_isr()
    {
    	static int8 i =0;
    	static uint8 *p;
    	if(LPLD_GPIO_IsPinxExt(PORTC,GPIO_Pin19))
    	//检测引脚是否产生了外部中断即
    	{
    		if(i<5) i++;
    		if(i>=3)
    		{
    		//Image_complete_flag和WhichBuffer的值由是否产生了DMA中断来控制
    			if(Image_complete_flag==1)
    			{
    				Image_complete_flag=0;
    				i=0;
    				LPLD_GPIO_ClearIntFlag(PORTC);
    				//两个缓冲区,交替使用
    				if(WhichBuffer==1)
    				{
    					p=(uint8*)Image_Buf2;
    					LPLD_DMA_LoadDstAddr(DMA_CH0,p);//加载地址到DMA
    				}
    				else
    				{
    					p=(uint8*)Image_Buf;
    					LPLD_DMA_LoadDstAddr(DMA_CH0,p);//加载地址到DMA
    				}
    				LPLD_DMA_EnableReq(DMA_CH0);//使能DMA中断
    			}
    		}
    	}
    	LPLD_GPIO_ClearIntFlag(PORTC);
    }
    

    7 . DMA中断函数
    完成一帧图像的传输就会触发一次DMA中断,从而通知CPU可以进行读取图像数据了,并且使用另一个图像缓冲数组来存图像数据。因为图像不能边传边读,只能先把一帧图完全采集完毕并传过来,CPU才能进行读取这帧图像,因此有了两个图像缓冲区,当Buffer1用于接收DMA传输的图像,CPU可以去解压读取Buffer2中已经是完整的图像数据了。当Buffer1存满图像数据,则CPU可以去解压读取Buffer1中的数据,而把已经解压读取过的Buffer2用作接收DMA的图像数据。实现交替使用,一边在传输,一边在解压读取图像

    int8 WhichBuffer=0,Sample_over=0;
    int8 Image_complete_flag=1;//标志位
    
    void DMA_complete_isr()
    {
    	Sample_over=1;//采样结束
    	Image_complete_flag=1;
    	if(WhichBuffer==1)
    	{
    		WhichBuffer=0;
    	}
    	else
    		WhichBuffer=1;
    
    }
    

    8 . 解压图像

    像素介绍:
    单色:一个像素只占1位,因此只能存储黑白信息;
    16色位图:一个像素占4位,有16种颜色可选
    256色位图:一个像素占8位即1个字节,有256种颜色可选
    24位位图:RGB图像,一个像素占24位即3个字节,每个分量占8位,有2^24种颜色可选

    uint8 Image_Buf[ROW + 10][COLUMN / 8];//每个元素是8位一个字节
    uint8 Image_Buf2[ROW + 10][COLUMN / 8];//每个元素是8位一个字节
    unsigned char Image[ROW][COLUMN];//缓冲区数组拆分出来的位存到这里,一个位表示一个像素
    //解压图像,进行格式转化
    void image_exact()
    {
    	uint8 color[2]={255,0};//二值化,只有黑白两色
    	uint8 Which;
    	Which=WhichBuffer;
    	if(Which==1)
    	{
    		for(i=0;i<120;i++)
    		{
    			for(j=0;j<COLUMN/8;j++)// COLUMN/8 = 20
    			{
    				Image[i][j*8+0]=color[(Image_Buf[i][j]>>7) & 0x01];//取出最高位,对应此字节的第7个像素点
    				Image[i][j*8+1]=color[(Image_Buf[i][j]>>6) & 0x01];//取出次高位,对应此字节的第6个像素点
    				Image[i][j*8+2]=color[(Image_Buf[i][j]>>5) & 0x01];//取出第5位,对应此字节的第5个像素点
    				Image[i][j*8+3]=color[(Image_Buf[i][j]>>4) & 0x01];//取出第4位,对应此字节的第4个像素点
    				Image[i][j*8+4]=color[(Image_Buf[i][j]>>3) & 0x01];//取出第3位,对应此字节的第3个像素点
    				Image[i][j*8+5]=color[(Image_Buf[i][j]>>2) & 0x01];//取出第2位,对应此字节的第2个像素点
    				Image[i][j*8+6]=color[(Image_Buf[i][j]>>1) & 0x01];//取出第1位,对应此字节的第1个像素点
    				Image[i][j*8+7]=color[(Image_Buf[i][j]>>0) & 0x01];//取出第0位,对应此字节的第0个像素点
    			}//实现了把每一位都取出来重新存放到Image[120][160]数组中
    		}
    	}
    	else
    	{
    		for(i=0;i<120;i++)
    		{
    			for(j=0;j<COLUMN/8;j++)
    			{
    				Image[i][j*8+0]=color[(Image_Buf2[i][j]>>7) & 0x01];
    				Image[i][j*8+1]=color[(Image_Buf2[i][j]>>6) & 0x01];
    				Image[i][j*8+2]=color[(Image_Buf2[i][j]>>5) & 0x01];
    				Image[i][j*8+3]=color[(Image_Buf2[i][j]>>4) & 0x01];
    				Image[i][j*8+4]=color[(Image_Buf2[i][j]>>3) & 0x01];
    				Image[i][j*8+5]=color[(Image_Buf2[i][j]>>2) & 0x01];
    				Image[i][j*8+6]=color[(Image_Buf2[i][j]>>1) & 0x01];
    				Image[i][j*8+7]=color[(Image_Buf2[i][j]>>0) & 0x01];
    			}
    		}
    	}
    }
    

    需要明白解压的目的是什么:
    DMA传过来的数据是Image_Buf数组或者Image_Buf2数组,即120行,20列的数据(因为1位就表示了一个像素点,因此所占的字节数没那么多,就只有20列,对应160位,160个像素),而为了便于访问,需要把这些位都展开即变成160列的。解压只是把数据存储形式改变,方便访问,点数还是那么多。比如,如果第0行的字节为0x01,0x02,0x01,0x02,0x01,0x02,0x01,0x02 … 共20个字节,刚好160位,对应160个像素,填满了一行,但如果具体想要访问每个像素点,需要对位进行访问,十分麻烦,因此,期望展开成:00000001,00000010,00000001,00000010,00000001,00000010,00000001,00000010 …
    填充到具体的数组中则是:Imag[0][0]=0, Imag[0][1]=0, Imag[0][2]=0, Imag[0][3]=0, Imag[0][4]=0, Imag[0][5]=0, Imag[0][6]=0, Imag[0][7]=1(这是第1个字节对应的黑点),Imag[0][0]=0, Imag[0][1]=0,
    Imag[0][2]=0, Imag[0][3]=0, Imag[0][4]=0, Imag[0][5]=0, Imag[0][6]=1(这是第2个字节对应的黑点),Imag[0][7]=0 … 这一黑色点的值是1还是255,不影响颜色的判断

    解压的作用就是把每一位都取出来重新存放以方便访问

    二、蓝牙传输到上位机

    主要是通过UART通信的方式传到上位机,主要介绍一下UART。UART需要事先约定一个波特率即传输速率:每秒传递的位数,以及是否有校验位等。然后,接收方也就可以按照这个约定的速率去读取传来的数据从而进行正确的解读,并且每一字节的传输都有起始位和停止位,方便识别。参考这篇了解UART:https://wenku.baidu.com/view/a3b76f4ae43a580216fc700abb68a98270feac27.html
    在这里插入图片描述UART的配置(像电机那样,没有GPIO的配置初始化,可以尝试一下对其进行GPIO初始化会不会有影响,进而判断GPIO的配置对哪些外设是必要的,哪些不是必要的)

    static UART_InitTypeDef UART_InitStructure;
    UART_InitStructure.UART_Uartx = UART0;//模块号选择
    UART_InitStructure.UART_BaudRate = 115200//波特率
    UART_InitStructure.UART_RxPin = PTA1;  //接收引脚
    UART_InitStructure.UART_TxPin = PTA2;  //发送引脚
    UART_InitStructure.UART_RxIntEnable = TRUE; //使能接收中断
    UART_InitStructure.UART_RxIsr = UART_recive_isr; //接收中断回调函数,一旦接收到数据就会触发此中断
    LPLD_UART_Init(UART_InitStructure);
    LPLD_UART_EnableIrq(UART_InitStructure);
    

    发送的时候不必触发中断,因此只需调用库函数发送数据即可,一次发送一个8位的一字节。由于要发送到上位机,因此需要带有帧头帧尾。可以在中断中调用这个发送函数从而实现定时发送数据

    void uart_image()
    {
    	int i,j;
    	uint8 temp;
    	LPLD_UART_PutChar(UART0, 0x01);
    	LPLD_UART_PutChar(UART0, 0xFE);//这两句发送了帧头
    	for (i = 0; i < ROW; i+=2)//跳着发?
      	{
        	for (j = 0; j < COLUMN; j+=2)
        	{
          		temp = 0;
         		temp = Image[i][j]; //
          
          		LPLD_UART_PutChar(UART0,temp);//库函数,一次发送一个8位一字节
        	}
      	}
      
      	LPLD_UART_PutChar(UART0, 0xFE);
      	LPLD_UART_PutChar(UART0, 0x01);//这两句发送了帧尾
    	
    }
    

    如果一个MCU通过蓝牙接收另一个设备发送来的数据,需要通过UART的接收中断函数来实现接收

    //自定义结构体类型
    typedef struct
    {
    	int Stack;
    	uint8 Data;
    	uint8 Buffer[4];//4个字符构成的字符串
    } SerialPortType;
    //声明一个这样结构的结构体变量
    SerialPortType SerialPortRx;//串口接收端
    
    void UART_recive_isr()
    {
     	//UART_S1_RDRF_MASK是接收寄存器对应的掩码
    	//if(UART0->S1 & UART_S1_RDRF_MASK)  表示接收数据寄存器满
    	//if(UART0->C2 & UART_C2_RIE_MASK ) 表示发送数据寄存器空
    	if ((UART0->S1 & UART_S1_RDRF_MASK) && (UART0->C2 & UART_C2_RIE_MASK))
    	{
    		SerialPortRx.Data = LPLD_UART_GetChar(UART0);//从UART0读取一个数据
    		SerialPortRx.Buffer[SerialPortRx.Stack] = SerialPortRx.Data;//存储到数组中,方便后续连成字符串
    	}
    }
    

    三、在OLED屏幕上显示

    1 . 在OLED屏幕上显示

    显示原理:
    对于128 x 64像素的OLED显示屏,也就是说显示屏横向有128个像素点,纵向有64个像素点,共128 x 64=8192个像素点,该像素点有亮(1)和灭(0)两种状态,由此构成不同的图像。每个点是否亮由寄存器中的值决定,我们的任务就是告诉它哪个点该亮,哪个该灭。每一纵行的64个像素点被分成8个页面,共8 x 128个页面,每个页面有8个像素点,其亮灭状态以8个位即1字节的形式存储在一个寄存器中,数据中的每一位对应一个像素点,相应的就有8 x 128个8位寄存器。实际使用过程中,把要显示的图片通过取模软件就能转成数组,把这个数组的数据写入到OLED对应的寄存器中就能达到显示图像的目的

    显示字符:
    字符的显示原理和图片是一样的,只不过为了美观和显示方便,设置每个字符的宽和高是一样的,宽高为6 x 8像素,也就是6个字节;每个汉字的宽高为14 x 16像素,即28个字节

    1.2 此OLED屏幕的接口,是SPI

    SPI双向传输至少需要4根线,单向传输只需3根线:
    SDI(主设备输入,从设备输出),SDO(主设备输出,从设备输入),SCLK(时钟信号,由主设备产生),CS(从设备使能信号,由主设备控制),其中CS控制芯片是否被选中,也即是说只有片选信号为预先规定的使能信号(高电位或低电位),对此芯片的操作才有效,这就使得在同一总线上连接多个SPI设备成为了可能

    由SCLK提供时钟脉冲,SDI、SDO则基于此脉冲完成数据的传输,数据输出通过SDO线,数据在时钟上升沿或下降沿时改变,在紧接着的下降沿或上升沿被读取,完成1位的数据传输。因此至少需要8次时钟信号的改变(上升沿和下降沿为1次)才能完成8位数据的传输。但缺少应答机制


    在这里插入图片描述
    在这里插入图片描述VCC:5V电源输入;GND:接地端;

    DC:数据、命令选择器,HIGH表示输入的是数据,LOW表示输入的是命令;

    RES:复位引脚,当RES为LOW时进行初始化,在使用OLED的过程应保持RES为HIGH

    SCL:时钟信号输入;

    SDA:数据或命令输入

    //宏定义其引脚和简单函数功能
    #define OLED_GPIO_SCLK 	GPIO_Pin15//时钟
    #define OLED_GPIO_SDA 	GPIO_Pin16//数据
    #define OLED_GPIO_RST 	GPIO_Pin13//复位
    #define OLED_GPIO_DC	GPIO_Pin17
    
    //控制引脚的输出,方便实现模拟协议
    #define OLED_SCLK(x) LPLD_GPIO_Output_b(PTA, 15, x)
    #define OLED_SDA(x)  LPLD_GPIO_Output_b(PTA, 16, x)
    #define OLED_RST(x)  LPLD_GPIO_Output_b(PTA, 13, x)
    #define OLED_DC(x)   LPLD_GPIO_Output_b(PTA, 17, x)
    

    1.3 OLED首先需要配置GPIO口

    //对应的四线引脚
    static GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_PTx = PTA;
    GPIO_InitStructure.GPIO_Pins =  OLED_GPIO_SCLK|OLED_GPIO_SDA | OLED_GPIO_RST | OLED_GPIO_DC;//引脚
    GPIO_InitStructure.GPIO_Dir = DIR_OUTPUT;
    GPIO_InitStructure.GPIO_Output = OUTPUT_H;
    LPLD_GPIO_Init(GPIO_InitStructure);
    
    

    1.4 然后进行接口初始化的配置,需要写很多子函数,不过应该都有现成的吧

    	OLED_DC(0);//拉低后表示输入的是命令
    	OLED_SDA(0);
    	OLED_SCLK(0);
    	OLED_RST(0);//RES处于低,表示在初始化状态
    	OLED_DelayMs(50);
            
    	OLED_SCLK(1);//SCL拉高
    	OLED_RST(0);//RES处于低,表示在初始化状态
    	OLED_DelayMs(50);//等待初始化
    	OLED_RST(1);//OLED使用过程中需保持RES为高
    	
    	//常规操作配置(子函数)
    	SetDisplay_On_Off(0x00);	// Display Off (0x00/0x01)
    	SetDisplayClock(0x80);		// Set Clock as 100 Frames/Sec
    	SetMultiplexRatio(0x3F);	// 1/64 Duty (0x0F~0x3F)
    	SetDisplayOffset(0x00);		// Shift Mapping RAM Counter (0x00~0x3F)
    	SetStartLine(0x00);			// Set Mapping RAM Display Start Line (0x00~0x3F)
    	SetChargePump(0x04);		// Enable Embedded DC/DC Converter (0x00/0x04)
    	SetAddressingMode(0x02);	// Set Page Addressing Mode (0x00/0x01/0x02)
    	SetSegmentRemap(0x01);		// Set SEG/Column Mapping     0x00左右反置 0x01正常
    	SetCommonRemap(0x08);		// Set COM/Row Scan Direction 0x00上下反置 0x08正常
    	SetCommonConfig(0x10);		// Set Sequential Configuration (0x00/0x10)
    	SetContrastControl(0xCF);	// Set SEG Output Current
    	SetPrechargePeriod(0xF1);	// Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
    	SetVCOMH(0x40);				// Set VCOM Deselect Level
    	SetEntireDisplay(0x00);		// Disable Entire Display On (0x00/0x01)
    	SetInverseDisplay(0x00);	// Disable Inverse Display On (0x00/0x01)
    	SetDisplay_On_Off(0x01);	// Display On (0x00/0x01)
    	OLED_Fill(0x00);
    	OLED_SetPosition(0,0);
    
    
    

    1.5 如何把图像显示到OLED屏幕上

    显示图像,输入的是数据而不是命令

    //向OLED写入命令的子函数功能
    void OLED_WriteCmd(unsigned char cmd)
    {
    	unsigned char i = 8;
    
    	OLED_DC(0);//处于LOW表示写入的是命令
    	OLED_SCLK(0);
    
    	while(i--)
    	{
    		if(cmd & 0x80)
    			OLED_SDA(1);
    		else
    			OLED_SDA(0);
    
    		OLED_SCLK(1);
    		asm("nop");
    		OLED_SCLK(0);//模拟SPI的时序图
    
    		cmd <<= 1;
    	}
    }
    
    //向OLED写入数据字符
    void OLED_WriteData(unsigned char data)
    {
    	unsigned char i = 8;
    
    	OLED_DC(1);//HIGH表示写入的是数据
    	OLED_SCLK(0);
    
    	while(i--)
    	{
    		if(data & 0x80)
    			OLED_SDA(1);
    		else
    			OLED_SDA(0);
    
    		OLED_SCLK(1);
    		asm("nop");
    		OLED_SCLK(0);
    
    		data <<= 1;
    	}
    }
    
    //把图像写入到OLED屏幕上
    void Showimage()
    {
      int16 i,j,cnt,temp1,data;
      for (j = 0; j < 8; j++)
      {
        OLED_WriteCmd(0xb0 + j);
        OLED_WriteCmd(0x01);
        OLED_WriteCmd(0x10);//写入命令
        for (i = 0; i < 80; i++)
        {
          temp1 = 0;
          for ( cnt = 7; cnt >= 0; cnt--)
          {
            temp1 |= Image[(j * 8 + cnt) * 2][i * 2] / 253;
            temp1 = temp1 << 1;
          }
          temp1 |= Image[(j * 8) * 2][i * 2] / 253;
          data = temp1;
          OLED_WriteData(data);//写入数据
        }
      }
    }
    
    

    总结一下:

    1.摄像头:ov7725,SCCB协议,硬件二值化使得采集速度更快,需要GPIO口(数据口、场中断、PCLK口)的配置,SCCB接口的配置,另外还需要摄像头寄存器的配置从而实现不同的对比度、亮度等,摄像头与DMA的配合,解压图像以方便访问每一个像素点

    2.DMA配置:选好模块号、请求源、主次循环、源地址和目的地址,还有DMA中断,注意有两个图像缓冲数组

    3.蓝牙通信:注意蓝牙接收中断的使用,用于接收其他设备通过蓝牙传过来的数据

    4.OLED屏幕:采用SPI接口,显示原理和显示方式

    在复习总结的过程中,发现自己还是有不少地方理解的不清晰,还需要后续继续学习和应用。做车的时候对这方面关注的不够多,多集中在控制程序和算法的编写,但这些外设和模块的配置是这个智能车比赛能学到的重要的一部分,不能因为前人对外设配置的比较好了而忽略这方面的学习。

    对于刚接触智能车的,如果有例程,可能会急于让车动起来而忽略这些外设的配置,顺利的话车能动起来,要是不顺利的话可能会很打击人的自信,因为我自己零基础入门这个智能车的时候经历了太多的波折,如果对例程的结构和配置没有基本的掌握会很容易陷入问题的漩涡之中。我建议,最好先找一些先关方面的资料,对k60的相关模块有一个大致的了解,然后结合例程和LPLD底层库文档去研究例程,理清结构,尽量明白每一步配置所起到的作用,确保你们的硬件配置不要出现太多的问题,然后才能尽可能专心的研究控制程序和算法。这个比赛无论你有没有走到国赛的赛场,它都是一个很好的学习k60的机会。拿起板子,编写自己的程序,理解其中的原理。

    另外,因为个人水平有限,上面总结的地方可能有些地方不清晰甚至有错误,还请大家能够指出来。写总结一方面是加深自己的理解,另一方面是希望能够帮助做车的人入门,不要像我一样走太多的弯路而消磨意志。

    参考资料:

    1. 清华大学工程物理系学生科协技术部智能车大赛技术文档
    2. ov7725数字摄像头编程基本知识笔记
    展开全文
  • 智能车K60主板

    2015-03-29 16:32:26
    飞思卡尔智能车K60驱动主板,有SPI、UART、I2C总线接口,可以控制舵机,双电机,读取摄像头、加速度计、电池状态、电机状态,有按键,方便更改参数
  • 基于K60芯片智能车控制代码 开机后,对所有硬件进行初始化,完成之后,PIT 定时中断,对电感采回来的数值进行分析,根据比例关系算出赛道电流大小并自动设置对应该电流时 最佳参数。正式起跑后定时的采集感应电压。...
  • 飞思卡尔智能车 K60 程序 ,开发环境 IAR
  • 第十二届全国大学生智能汽车竞赛有一个分项是光电四轮的竞速(任务A),Seven她们组采购到的配件使用了freescale Crotex-M4内核的CPU,TSL1401 CCD摄像头进行道路识别,从网上搜索了一下,应当是K60平台的的一个...

    timg?image&quality=80&size=b9999_10000&sec=1513083187762&di=91f1f47b50bbd41bf6875e9453fa5c14&imgtype=0&src=http%3A%2F%2Fxfjy.chd.edu.cn%2F_mediafile%2Fzerui24%2F2017%2F04%2F12%2F2jawp8ae2v.jpg
    (图片仅为示例,并不一定固定为这种造型)

    第十二届全国大学生智能汽车竞赛有一个分项是光电四轮车的竞速(任务A),Seven她们组采购到的配件使用了freescale Crotex-M4内核的CPU,TSL1401 CCD摄像头进行道路识别,从网上搜索了一下,应当是K60平台的的一个变种方案。
    这个方案基本平台使用IAR系统开发编译、调试及烧录。IAR其实是一个很昂贵的系统,还好这次真的是纯粹的教学需求,经由《计算机软件保护条例》第十七条的豁免说明,这次用一下破解版。
    原厂提供的DEMO程序没有考察所使用的编译版本,但是在网上查找一些资料,IAR6的系统可能是有一些兼容性的小问题,需要补丁,所以干脆下载了当前比较新的8.1版本。软件的破解是需要一些技巧的,建议仔细看一下附带的视频了解操作流程。
    还收集了一些相关的资料,包含上面说的IAR共有:

    • IAR8.1安装包、破解包、破解说明视频
    • IAR for ARM系列教程,可以当做手册看,IAR系统十分庞大,短时间不可能全部熟悉,一般只要了解自己常用的功能就好。
    • 附件3-第十二届全国大学生“恩智浦”杯智能汽车竞赛规则.pdf,作为命题作文,这个文档是要烂熟于心的。
    • NEWB_K60_OV7725_ZL_.rar,厂方提供的原始demo代码,对于所有新上手的同学,实际练习都应当从这里开始起步。
    • 【野火】K60中文资料整合版.pdf 这个是比较完整的资料。
    • 野火Kinetics开发板教程:三天入门Cortex-M4.pdf建议从这篇文章开始入门,写的非常浅显易懂。
    • 野火K60开发板资料集锦(飞思卡尔智能车).rar 这是一组资料,有些跟上面是重复的,根据自己的时间情况酌情了解。
    • 智能车PID算法.pdf
    • 智能车总结.doc 这篇跟上篇是智能车的基本理论知识,包括调优的一些公式、算法的基本推演,理论上说应当先看懂这些再下手开发,当然很多天生的实践派就当我没说,不过相信我,早晚你会回到这里。

    上述所有资料下载链接:https://pan.baidu.com/s/1nv5QH1R 密码:c6t9

    厂商demo程序,IAR编译上手记录

    • 解压到自己的工作目录,不要破坏原有的目录结构。
    • 顶层目录中的fire_Kinetis_demo.eww文件就是总工程文件,双击就可以用IAR打开。
    • 打开后左上角是项目文件列表,首先右键点击工程文件(默认是fire_demo-Debug)字样。在左侧列表中选择Linker,右侧的路径中有原开发者的路径,一般情况下这同你的项目文件是不一致的,点选你项目文件夹下面/iar_config_files/LPLD_BOOT_K60DN512.icf文件。然后OK退出设置。
    • 选择Project菜单中Make,开始编译项目,也可以直接按F7快捷键。
    • 编译过程中会有很多警告信息,这些信息中,有因为版本不同造成的,有原有开发者不重视造成的。一般企业开发的要求是不允许存在这些警告信息,都需要改正。但在学习项目中,根据自己情况来定吧,建议有能力的情况下都要修正。当然经验上说,一般情况下警告信息不影响最终的编译结果。

    • 编译完成后,左上角列表窗口中最下面一行Output之后应当有输出内容,本项目中应当是:fire_gpio_demo.out,右键点击这个文件,菜单中Open Containing Folder可以打开编译结果所在文件夹。其中有3个文件,out文件是编译结果,一般是本地调试及其它一些特殊用途,hex文件是可以烧制到智能车中运行的文件。一般烧制使用J-Link,使用不同的烧制工具操作有不同,需要看具体工具的说明书,IAR平台也支持大多常见的工具,在菜单中有J-link菜单,可以参考。
    • 按照一般开发经验,Project菜单中的Download and Debug也是指的烧录到设备进行调试,手头没有测试环境没有尝试。
    • 左上角项目窗上面,Debug可以点击选择成Release,最终正式发布的版本,应当选择为Release方式然后编译、烧录到设备。

    代码粗解

    项目文件列表中,刚才说到了Output是指编译的输出结果,从下向上看还有:

    • uCOS-II:这是一个极简的嵌入式操作系统,比常见的Windows/Linux都要小很多,虽然功能弱,但也具备了基本的功能,可以在网上搜索查找更全面资料。
    • LIB:各种库文件,所谓的开发,一般情况下都是基于这些库和操作系统的。
    • Header:对应上面库的c语言头文件,通过这些头文件的描述,开发才能使用上面的库。
    • drivers:驱动程序,理论上说除了cpu之外所有的设备都是需要驱动的,不同设备有不同驱动,一般就归类在这个目录。
    • cpu:实际是uCOS-II跟具体硬件之间的一个接口层,不同的配置,比如FLASH大小、RAM大小、型号不同,这部分及驱动部分都会有响应的设置及内容不同,从而保证系统正常启动、加载必要的驱动比如内存、FLASH的驱动,然后才把控制权交给uCOS-II操作系统。
    • common:其它一些必须的、共用的、也是基础的功能,类似基本的i/o,输入输出、内存测试等,这些既是系统必须的,又严重依赖当前的设备从而无法归类到通用的库、驱动、操作系统中。
    • app:终于切入了正题,这里面是我们真正自己要开发的部分,也就是我们应用的主程序。在其中的main.c则又是我们程序的入口。

    拉普兰德开源Kinetis固件库使用

    拉普兰德开源Kinetis固件库地址:https://github.com/LPLDTeam/LPLD_OSKinetis
    项目README.md已经有比较清楚的说明,所有的演示代码及自己的项目,应当放在project中,github上project中还提供了一个windows可执行的exe文件来帮助用户建立基于拉普兰德开源库的项目,自动生成可供iar系统使用的工程文件。
    前面介绍的fire_Kinetis_demo.eww这个工程,实际是用官方库+野火K60开发库所建立的工程,和这里介绍的拉普兰德开源库,实际相当于我们企业及应用中所说的软件框架。目前看,官方的标准库肯定是兼容性最好的,其它框架中都可以使用官方库的功能。但官方库也只提供了最基本的功能,已经很少看到有人在官方库的基础上从头开发。而其它的框架之间目前看几乎没有办法直接互相调用,选择一个,基本代表放弃另外一个。所以选择一个适合自己的开发库框架开始来建立自己的项目就比较重要。当然接手一个别人的项目,往往大多只能延续原有的框架。
    从网上的评价看,拉普兰德开源项目的完整性比较好,上手容易。而野火库有写的很完善的文档,似乎更适合新手。

    转载于:https://www.cnblogs.com/andrewwang/p/8536280.html

    展开全文
  • 基于K60智能汽车防盗系统的设计方案,王存跃,郭伟,近几年,汽车大量的涌入人们的生活,用户对汽车的防盗要求也越来越高,并且计算机技术、移动通信技术和传感器技术不断的发展,使
  • 飞思卡尔智能车大赛K60工程模板,摄像头组专用,底层模板。
  • 飞思卡尔智能车竞赛K60光电组程序
  • 智能车K60学习笔记

    2020-04-04 09:10:19
    文章目录K60学习笔记(开个坑,慢慢学)基础知识一系列定义或名称简单位运算中断(Interrupt)8051 定时器中断Kinetis K60介绍K60模块PORT模块GPIO模块 K60学习笔记(开个坑,慢慢学) 基础知识 一系列定义或名称 ...

    K60学习笔记(开个坑,慢慢学)

    基础知识

    一系列定义或名称

    • MCU微控制单元(Microcontroller Unit) ,又称单片微型计算机(Single Chip Microcomputer )或者单片机,是把中央处理器(Central Process Unit,CPU)的频率与规格做适当缩减,并将内存(memory)、计数器(Timer)、USB、A/D转换、UART、PLC、DMA等周边接口,甚至LCD驱动电路都整合在单一芯片上,形成芯片级的计算机,为不同的应用场合做不同组合控制。

    • 内存是计算机中重要的部件之一,它是外存与CPU进行沟通的桥梁。计算机中所有程序的运行都是在内存中进行的,因此内存的性能对计算机的影响非常大。内存(Memory)也被称为内存储器和主存储器,其作用是用于暂时存放CPU中的运算数据,以及与硬盘等外部存储器交换的数据。只要计算机在运行中,操作系统就会把需要运算的数据从内存调到CPU中进行运算,当运算完成后CPU再将结果传送出来,内存的运行也决定了计算机的稳定运行。

    • UART通用异步收发传输器(Universal Asynchronous Receiver/Transmitter)。它将要传输的资料在串行通信与并行通信之间加以转换。作为把并行输入信号转成串行输出信号的芯片,UART通常被集成于其他通讯接口的连结上。

    • PLC可编程逻辑控制器(Programmable Logic Controller,PLC),一种具有微处理器的用于自动化控制的数字运算控制器,可以将控制指令随时载入内存进行储存与执行。

    • DMA直接存储器访问(Direct Memory Access)是所有现代电脑的重要特色,它允许不同速度的硬件装置来沟通,而不需要依赖于 CPU 的大量中断负载。否则,CPU 需要从来源把每一片段的资料复制到暂存器,然后把它们再次写回到新的地方。在这个时间中,CPU 对于其他的工作来说就无法使用。

    • GPIO通用型输入输出(General-purpose input/output),其接脚可以供使用者由程控自由使用

    • I2CInter-Integrated Circuit。I2C总线是由Philips公司开发的一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。

    • SPI串行外设接口(Serial Peripheral Interface),是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便。是一种高速、高效率的串行接口技术。

    • ADC模数转换器(analog to digital converter)作用是将时间连续、幅值也连续的模拟量转换为时间离散、幅值也离散的数字信号,因此,A/D转换一般要经过取样、保持、量化及编码4个过程。在实际电路中,这些过程有的是合并进行的。

    • DAC数模转换器(digital to analog converter)它是把数字量转变成模拟的器件。DAC主要由数字寄存器、模拟电子开关、位权网络、求和运算放大器和基准电压源(或恒流源)组成。用存于数字寄存器的数字量的各位数码,分别控制对应位的模拟电子开关,使数码为1的位在位权网络上产生与其位权成正比的电流值,再由运算放大器对各电流值求和,并转换成电压值。

    • PIT可编程间隔定时器(programmable interval timer)

    简单位运算

    • ‘>>’,二进制右移。x>>5相当于除25^5并向下取整。

    • ‘<<’,二进制左移,效果参照’>>’。

    • ‘&’,按位与。x&0x1f相当于只取二进制最后五位。

    中断(Interrupt)

    • 定义:指处理机处理程序运行中出现的紧急事件的整个过程。程序运行过程中,系统外部、系统内部或者现行程序本身若出现紧急事件,处理机立即中止现行程序的运行,自动转入相应的处理程序(中断服务程序),待处理完后,再返回原来的程序运行,这整个过程称为程序中断。
    • 优先级:在某一时刻有几个中断源同时发出中断请求时,处理器只响应其中优先权最高的中断源。当处理机正在运行某个中断服务程序期间出现另一个中断源的请求时,如果后者的优先权低于前者,处理机不予理睬,反之,处理机立即响应后者,进入所谓的“嵌套中断”。中断优先权的排序按其性质、重要性以及处理的方便性决定,由硬件的优先权仲裁逻辑或软件的顺序询问程序来实现。
    • 中断向量:对应每个中断源设置一个向量。这些向量顺序存在主存储器的特定存储区。向量的内容是相应中断服务程序的起始地址和处理机状态字。在响应中断时,由中断系统硬件提供向量地址,处理机根据该地址取得向量,并转入相应的中断服务程序。
    • “紧急事件”须向处理器提出申请(发一个电脉冲信号),要求“中断”,即要求处理器先停下“自己手头的工作”先去处理“我的急件”,这一“申请”过程,称中断请求(Interrupt ReQuest,IRQ)。

    8051 定时器中断

    8051的单片机有5个中断源,2个优先级

    中断源:INT0(外部中断0),INT1(外部中断1),T0(定时器0),T1(定时器1),RXD和TXD(同属串口中断)

    中断相关的特殊寄存器:

    • 中断允许控制寄存器(IE)--------控制各中断的开放和屏蔽

    • 定时器/计数器控制寄存器(TCON)-------定时器和外部中断的控制

    • 串行口控制寄存器(SCON)-------串行中断的控制

    • 中断优先级控制寄存器(IP)-------设置各中断的优先级

    void funcname(void) interrupt x using y
    {
    	your code;
    }
    

    x 范围为0~4,分别代表5个中断源,例如外部中断INT0就是0,T0就是1,INT1就是2,T1就是3,串行中断就是4

    y 的范围为0~3,分别表示4组工作寄存器,不写就用0,不写也可以

    TLx和THx寄存器,x=0,1。高八位和低八位寄存器,用于计数,一共16位。TCON和TMOD,其中TCON用于开启定时器/计数器中断,TMOD用于设置定时器/计数器的工作方式。有以下四种例如TMOD= 0x01对应方式1。

    C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20200331213056219.png

    TLx和THx的计算

    例:定时10ms。晶振频率是12M,工作模式在方式1。

    f = 12M/12 =1M(机械周期为时钟周期的112\frac{1}{12})即1秒有10610^6个机械周期,则10ms有10410^4个机器周期。从而65536-10000=55536,意思是计数器从65536 一直减到 55536 所用的时间就是10ms。55536的十六进制是0xD8F0,所以设置 TH0 = 0XD8,TL0=0XF0。

    #include<reg52.h>
    sbit LED0=P1^0;
    unsigned char i;
     
    void main()
    {
    	TMOD=0X01;  //设置使用定时器0,16位的定时/计数寄存器
    	TL0=0xD8;    //低八位赋初值
    	TH0=0XF0;    //高八位赋初值
    	ET0=1;          //开启定时器0中断
    	TR0=1;         //运行定时器0
    	EA=1;          //开启中断总开关
    	while(1);
    }
     
    void Delay(void) interrupt 1 using 0
    {
    	i++;
    	//  TR0=0;//进入中断函数时,关闭定时器  似乎不是必要的语句
    	TL0=0XD8;//重新赋初值
    	TH0=0XF0;
    	if(i==20)//因为10ms一次处罚看不出明显结果,所以让20次触发才让灯变一次
    	{
            LED0=~LED0;
            i=0;
    	}
    	//   TR0=1;//重新开启定时器   似乎不是必要的语句
    }
    

    Kinetis K60介绍

    简单的说, K60 是一块 MCU 芯片,其内部还可以细分多个模块: Cortex-M4 内 核、 GPIO、 UART、 I2C、 SPI、 ADC、 DAC、 DMA、 PIT、 FTM 等。除 Cortex-M4 内核 模块外,其他的 GPIO、 UART 等模块就是 K60 的外设。

    K60模块

    PORT模块

    PORT 模块,是 K60/KL26 的管脚管理模块,控制每个管脚复用到各个不同的内部模块(GPIO、UART、 I2C 等),还可以配置每个管脚的各种属性(上拉下拉电阻、无源滤波等)。 似乎是用来触发一系列中断而使用的。以下给出相应的函数,作为做软件的菜鸡虽然写不来但起码也得看吧。中断的逻辑应该为:

    • 中断初始化port_init
    • 设置中断向量set_vector_handler
    • 使能中断enable_irq直接调用库函数即可
    //中断初始化函数,ptxn是对应端口号,cfg是端口属性配置
    //example: port_init (PTA8, IRQ_RISING | PF | ALT1 | PULLUP ); 初始化 PTA8 管脚,上升沿触发中断,带无源滤波器,复用功能为GPIO(对应ALT1) ,上拉电阻
    void  port_init(PTXn_e ptxn, uint32 cfg )
    {
        SIM_SCGC5 |= (SIM_SCGC5_PORTA_MASK << PTX(ptxn));//开启PORTx端口
        PORT_ISFR_REG(PORTX_BASE(ptxn)) = (1<<PTn(ptxn));// 清空标志位
        PORT_PCR_REG(PORTX_BASE(ptxn), PTn(ptxn)) = cfg;// 复用功能 , 确定触发模式 ,开启上拉或下拉电阻
    }
    
    //中断处理函数,PORT_FUNC函数前两个参数对应端口号,当对应引脚触发中断时会执行最后输入的函数,在这里是key
    void PORTD_IRQHandler(void)
    {
    	PORT_FUNC(A,6,key);
    }
    
    //设置中断向量表里的中断服务函数,只有中断向量表位于icf指定的RAM区域时,此函数才有效
    //example:set_vector_handler(PIT0_VECTORn ,PIT0_IRQHandler)
    void set_vector_handler(VECTORn_t vector , void pfunc_handler(void))
    {
        extern uint32 __VECTOR_RAM[];
        ASSERT(SCB->VTOR == (uint32)__VECTOR_RAM);//断言,检测中断向量表是否在 RAM 里
        __VECTOR_RAM[vector] = (uint32)pfunc_handler;
    }
    

    GPIO模块

    GPIO模块可以设置某一管脚的输入输出状态。一天攻破K60内列举的许多宏定义似乎并没有找到

    展开全文
  • 用于最新恩智浦比赛的k60的flash文件,帮助新手解决因flash文件错误引起的问题
  • 这是一个简单的K60的一个模板,和几个简单的流水灯实验代码,可以让初学者简单的了解到K60的一些知识。
  • 第十四届恩智浦智能车资料,基于KEIL编译环境逐飞库,K60单片机。工程文件在MDK文件夹中,可跑完全程赛道,可以入环,速度在2.0m/s。

空空如也

空空如也

1 2 3 4 5 ... 7
收藏数 131
精华内容 52
关键字:

k60智能车