摄像头实验 ## 摘要: 本课程报告完成了一个基于STM32的摄像头控制项目。软件上,项目并未使用HAL库,而是使用寄存器库,直接对开发板、屏幕、摄像头等硬件的寄存器进行操作。项目涉及了多种头文件,例如stm32f10x.h(定义了STM32F10x芯片的寄存器定义和外设结构体,可以方便地访问和配置STM32F10x芯片的外设)、system_stm32f10x.h(STM32F10x芯片的系统配置函数,初始化时钟系统,设置系统时钟源和时钟频率)等,这些都是来自MCD Application团队提供的头文件。在个人开发中,结合开发板、摄像头、屏幕对应的开发文档与提供的接口代码,设计编程了按键初始化、屏幕输出字符/刷新、摄像头画面风格等函数,并在main函数中进行硬件操作的结合。最终,使用STM32 F103ZET6开发板,结合MCU屏幕与摄像头,可以实现两个功能:(1)在屏幕中可以看到摄像头的画面;(2)使用按键,可以调节摄像头显示画面的风格。
0x00 实验设备&软件
设备名称
设备型号
开发板
STM32 F103ZET6
屏幕
MCU电阻屏,IC驱动:ILI9341/ST7789
摄像头
OV7725
软件
Keil uVision5
0x01 头文件介绍
文件名
作用
stm32f10x.h
STM32F10x芯片的寄存器定义和外设结构体,可以方便地访问和配置STM32F10x芯片的外设
core_cm3.h
Cortex-M3处理器的寄存器映射以及内联函数,是CMSIS(Cortex Microcontroller Software Interface Standard)标准的一部分,用来提供对Cortex-M3的访问和控制
system_stm32f10x.h
STM32F10x芯片的系统配置函数,初始化时钟系统,设置系统时钟源和时钟频率
stdint.h
定义了int8_t、uint32_t等
stm32f10x_conf.h
包含所有外设的头文件,例如GPIO、TIM、USART等,使用标准外设库时必须包含此文件
stm32f10x_exti.h
与外部中断相关的寄存器和函数定义,使用标准外设库时必须包含此文件
stm32f10x_fsmc.h
连接并管理外部存储,例如NOR、NAND、SRAM等
stm32f10x_gpio.h
定义GPIO(通用输入输出)的数据结构和函数,使用GPIO可以控制LED灯、输出音频等
stm32f10x_rcc.h
定义时钟控制与复位相关的数据结构和函数
stm32f10x_tim.h
定义测量时间间隔的数据结构和函数
stm32f10x_usart.h
定义USART的数据结构和函数;USART(通用同步异步收发器)是一种用于串行数据传输的外设
misc.h
定义操作内核组件的函数与结构体
补充:
1 2 3 NOR:可像随机存取存储器一样进行随机访问,存储程序代码,写入和擦除速度较慢。 NAND:不能随机访问,存储数据,写入和擦除速度较快。 SRAM:静态随机存取存储器,适用于高速缓存。
0x02 初始化操作 延时函数初始化 方便调用延时函数。首先设置SysTick定时器,它可以用来实现延时,当计数到0时,会从重装载寄存器中自动重装载定时初值,并触发中断;之后,设置fac_us与fac_ms,设置fac_us为系统时钟的1/8,STM32 F103xxxx的频率为72MHz。
函数代码:
1 2 3 4 5 6 7 void delay_init () { SysTick_CLKSourceConfig (SysTick_CLKSource_HCLK_Div8); fac_us=SystemCoreClock/8000000 ; fac_ms=(u16)fac_us*1000 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 void SysTick_CLKSourceConfig (uint32_t SysTick_CLKSource) { ... if (SysTick_CLKSource == SysTick_CLKSource_HCLK) { SysTick->CTRL |= SysTick_CLKSource_HCLK; } else { SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8; } } #define SysTick_CLKSource_HCLK_Div8 ((uint32_t)0xFFFFFFFB)
1 2 uint32_t SystemCoreClock = SYSCLK_FREQ_72MHz;
串口初始化 初始化USART1串口。其作用是实现与外部设备的全双工通用同步/异步串行通信。USART1可以支持多种数据帧格式、波特率、校验方式、流控制和中断方式,可以用于发送和接收数据,USART1可以通过GPIOA的9和10引脚分别作为TX和RX与外部设备连接。
函数逻辑:(1)使能USART1与GPIOA的时钟;(2)配置GPIOA的9和10引脚分别作为USART1的TX和RX,使用复用推挽输出和浮空输入;(3)之后,配置NVIC,设置中断优先级为抢占优先级3和子优先级3,并使能中断;(4)配置USART1的波特率、数据位、停止位、奇偶校验等参数;(5)使能USART1的中断和串口。
补充:
1 2 3 4 5 6 1. GPIOA是STM32的一组通用输入/输出端口,共有16个引脚,可以通过软件配置成输入/输出模式 2. 复用推挽输出:GPIO引脚可以输出来自其他外设的数据,比如定时器、DAC等,而不是输出数据寄存器中的数据。复用推挽输出既可以输出高电平,又可以输出低电平,速度快,损耗小。 3. 浮空输入:GPIO引脚可以接收数字信号,但是没有上拉或下拉电阻,所以当没有信号输入时,电压不确定,容易受到干扰。浮空输入可以检测到微弱的信号。 4. NVIC:中断控制器,用于控制和管理中断的优先级、使能、挂起。 5. 抢占优先级:中断能够打断其他中断的属性,抢占优先级高(数值小)的中断可以打断抢占优先级低(数值大)的中断。 6. 子优先级:指在抢占优先级相同的情况下,哪个中断先被响应的属性,子优先级高(数值小)的中断先被响应。
函数定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 void uart_init (u32 bound) { ... RCC_APB2PeriphClockCmd (RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init (GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init (GPIOA, &GPIO_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3 ; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init (&NVIC_InitStructure); USART_InitStructure.USART_BaudRate = bound; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init (USART1, &USART_InitStructure); USART_ITConfig (USART1, USART_IT_RXNE, ENABLE); USART_Cmd (USART1, ENABLE); }
波特率(每秒传输的位数)设置为115200。115200是一种常用的波特率,可以实现较高的传输效率和较低的误码率。
按键与屏幕初始化 对于按键而言,我们要初始化KEY0与KEY1,其中KEY0负责转换画面风格,KEY1负责进入摄像头页面:(1)使能GPIOA和GPIOE的时钟,这样才能对端口进行配置;(2)设置GPIO_InitStructure.GPIO_Pin为GPIO_Pin_4|GPIO_Pin_3,表示要配置的是GPIOE的第4和第3引脚,也就是KEY0和KEY1;(3)设置GPIO_InitStructure.GPIO_Mode为GPIO_Mode_IPU,表示:没有按键按下时,引脚的电平为高,当按键抬起时,电平为低,并进行输入。(4)调用GPIO_Init函数,将GPIO_InitStructure的参数传递给GPIOE,完成对KEY0和KEY1的初始化。
1 2 3 4 5 6 7 8 9 void KEY_Init (void ) { ... RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOE,ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init (GPIOE, &GPIO_InitStructure); }
对于LCD屏幕的初始化,其逻辑如下:(1)使能FSMC时钟,因为LCD屏幕要通过FSMC接口与STM32通信;(2)使能GPIOB、GPIOD、GPIOE、GPIOG的时钟,因为LCD屏幕的数据线和控制线要用到这些端口;(3)配置PB0为推挽输出,用于控制LCD屏幕的背光;(4)配置PD0、PD1、PD4、PD5、PD8、PD9、PD10、PD14、PD15为复用推挽输出,用于连接FSMC的数据线和地址线;配置PE7~PE15为复用推挽输出,用于连接FSMC的数据线;配置PG0和PG12为复用推挽输出,用于连接FSMC的RS和NE4信号;(5)配置FSMC的各种参数,例如读写时序、存储器类型等。(6)初始化FSMC。
之后,使用FSMC接口向LCD屏幕发送初始化指令和数据。例如,0x11是退出睡眠模式的指令,0x36是设置内存访问控制的指令,0x3A是设置像素格式的指令等。初始化代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 LCD_WR_REG(0x11); delay_ms(120); LCD_WR_REG(0x36); LCD_WR_DATA(0x00); LCD_WR_REG(0x3A); LCD_WR_DATA(0X05); LCD_WR_REG(0xB2); LCD_WR_DATA(0x0C); LCD_WR_DATA(0x0C); LCD_WR_DATA(0x00); LCD_WR_DATA(0x33); LCD_WR_DATA(0x33); LCD_WR_REG(0xB7); LCD_WR_DATA(0x35); LCD_WR_REG(0xBB); //vcom LCD_WR_DATA(0x32); //30 LCD_WR_REG(0xC0); LCD_WR_DATA(0x0C); LCD_WR_REG(0xC2); LCD_WR_DATA(0x01); LCD_WR_REG(0xC3); //vrh LCD_WR_DATA(0x10); //17 0D LCD_WR_REG(0xC4); //vdv LCD_WR_DATA(0x20); //20 LCD_WR_REG(0xC6); LCD_WR_DATA(0x0f); LCD_WR_REG(0xD0); LCD_WR_DATA(0xA4); LCD_WR_DATA(0xA1); LCD_WR_REG(0xE0); //Set Gamma LCD_WR_DATA(0xd0); LCD_WR_DATA(0x00); LCD_WR_DATA(0x02); LCD_WR_DATA(0x07); LCD_WR_DATA(0x0a); LCD_WR_DATA(0x28); LCD_WR_DATA(0x32); LCD_WR_DATA(0X44); LCD_WR_DATA(0x42); LCD_WR_DATA(0x06); LCD_WR_DATA(0x0e); LCD_WR_DATA(0x12); LCD_WR_DATA(0x14); LCD_WR_DATA(0x17); LCD_WR_REG(0XE1); //Set Gamma LCD_WR_DATA(0xd0); LCD_WR_DATA(0x00); LCD_WR_DATA(0x02); LCD_WR_DATA(0x07); LCD_WR_DATA(0x0a); LCD_WR_DATA(0x28); LCD_WR_DATA(0x31); LCD_WR_DATA(0x54); LCD_WR_DATA(0x47); LCD_WR_DATA(0x0e); LCD_WR_DATA(0x1c); LCD_WR_DATA(0x17); LCD_WR_DATA(0x1b); LCD_WR_DATA(0x1e); LCD_WR_REG(0x2A); LCD_WR_DATA(0x00); LCD_WR_DATA(0x00); LCD_WR_DATA(0x00); LCD_WR_DATA(0xef); LCD_WR_REG(0x2B); LCD_WR_DATA(0x00); LCD_WR_DATA(0x00); LCD_WR_DATA(0x01); LCD_WR_DATA(0x3f); LCD_WR_REG(0x29); //display on
补充:
1 2 3 1. FSMC(灵活静态存储器控制器:Flexible Static Memory Controller)是STM32的一个外设,可以让STM32通过FSMC与SRAM、ROM等存储器相连,从而进行数据的交换。 2. PB0是STM32的一个GPIO引脚,可以用于控制LCD屏幕的背光。LCD屏幕的背光是指LCD屏幕后面的一层发光材料,可以提高LCD屏幕的亮度和对比度。 3. RS是一个控制信号,用于选择命令或数据。NE4是一个片选信号,用于选择第4个NOR/PSRAM设备。这样,就可以通过不同的地址来控制LCD的读写操作。NOR/PSRAM设备是指使用FSMC接口的NOR Flash或PSRAM存储器。NOR Flash是一种非易失性的闪存存储器,可以随机访问任意地址的数据,适合用于存储程序代码。PSRAM是一种伪静态随机存取器,可以实现较大的存储容量和较低的功耗。
摄像头初始化 初始化步骤如下:(1)配置GPIO的时钟、模式等相关的端口时钟,设置PA8为输入上拉,PB3和PB4为推挽输出,PC0~7为输入上拉,PD6、PG14和PG15为推挽输出,禁用JTAG功能;(2)调用SCCB_Init函数初始化SCCB的IO口;(3)调用SCCB_Init函数初始化SCCB的IO口,SCCB是一种串行通信协议,用于控制摄像头的寄存器;(4)判断摄像头ID;(5)遍历camera_init_reg_tb1数组,向摄像头写入一系列的初始化指令和数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 u8 CAMERA_Init (void ) { ... RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOA|...|RCC_APB2Periph_AFIO, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init (GPIOA, &GPIO_InitStructure); GPIO_SetBits (GPIOA,GPIO_Pin_8); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init (GPIOB, &GPIO_InitStructure); GPIO_SetBits (GPIOB,GPIO_Pin_3|GPIO_Pin_4); GPIO_InitStructure.GPIO_Pin = 0xff ; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init (GPIOC, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init (GPIOD, &GPIO_InitStructure); GPIO_SetBits (GPIOD,GPIO_Pin_6); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14|GPIO_Pin_15; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init (GPIOG, &GPIO_InitStructure); GPIO_SetBits (GPIOG,GPIO_Pin_14|GPIO_Pin_15); GPIO_PinRemapConfig (GPIO_Remap_SWJ_JTAGDisable,ENABLE); SCCB_Init (); if (SCCB_WR_Reg (0x12 ,0x80 )) return 1 ; delay_ms (50 ); reg=SCCB_RD_Reg (0X1c ); reg<<=8 ; reg|=SCCB_RD_Reg (0X1d ); if (reg!=CAMERA_MID) { printf ("MID:%d\r\n" ,reg); return 1 ; } reg=SCCB_RD_Reg (0X0a ); reg<<=8 ; reg|=SCCB_RD_Reg (0X0b ); if (reg!=CAMERA_PID) { printf ("HID:%d\r\n" ,reg); return 2 ; } for (i=0 ;i<sizeof (camera_init_reg_tb1)/sizeof (camera_init_reg_tb1[0 ]);i++) { SCCB_WR_Reg (camera_init_reg_tb1[i][0 ],camera_init_reg_tb1[i][1 ]); } return 0x00 ; }
补充:
1 2 3 4 5 6 7 1. JTAG|SWD是调试嵌入式系统的接口协议: a. JTAG(Joint Test Action Group)需要至少四根信号线:TMS(模式选择)、TCK(时钟)、TDI(数据输入)和TDO(数据输出)。JTAG接口的优点是兼容性好,支持多种设备和协议,缺点是占用的引脚较多,速度较慢。 b. SWD(Serial Wire Debugging)是为了简化和优化JTAG接口而设计的,只需要两根信号线:SWDIO(数据输入输出)和SWCLK(时钟)。 2. SCCB是一种串行通信协议,用于控制摄像头的寄存器。 3. 寄存器版本与HAL库版本: a. 直接操作STM32的寄存器,需要对寄存器的地址、位域、功能等有较深入的了解,编程难度较高,但是性能较好,代码量较小,可移植性较差。 b. HAL库版本是使用ST提供的硬件抽象层库,对寄存器进行了封装,提供了统一的API接口,编程难度较低,但是性能较差,代码量较大,可移植性较好。
0x03 摄像头相关函数 设置Camera,以VGA模式输出图像: 设置摄像头的窗口大小和位置,并以VGA模式(640x480)输出图像。函数逻辑为:(1)设置摄像头为VGA模式,并设置水平和垂直的起始位置和尺寸为640x480;(2)根据width和height的值,计算窗口的水平和垂直的偏移量sx和sy;(3)修改摄像头的寄存器HSTART、HSIZE、VSTRT、VSIZE、HREF、HOutSize、VOutSize、EXHCH,将窗口的位置和尺寸写入寄存器中。相关代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 void camera_Window_Set (u16 width,u16 height) { ... sx=(640 -width)/2 ; sy=(480 -height)/2 ; SCCB_WR_Reg (COM7,0x06 ); SCCB_WR_Reg (HSTART,0x23 ); SCCB_WR_Reg (HSIZE,0xA0 ); SCCB_WR_Reg (VSTRT,0x07 ); SCCB_WR_Reg (VSIZE,0xF0 ); SCCB_WR_Reg (HREF,0x00 ); SCCB_WR_Reg (HOutSize,0xA0 ); SCCB_WR_Reg (VOutSize,0xF0 ); raw=SCCB_RD_Reg (HSTART); temp=raw+(sx>>2 ); SCCB_WR_Reg (HSTART,temp); SCCB_WR_Reg (HSIZE,width>>2 ); raw=SCCB_RD_Reg (VSTRT); temp=raw+(sy>>1 ); SCCB_WR_Reg (VSTRT,temp); SCCB_WR_Reg (VSIZE,height>>1 ); raw=SCCB_RD_Reg (HREF); temp=((sy&0x01 )<<6 )|((sx&0x03 )<<4 )|((height&0x01 )<<2 )|(width&0x03 )|raw; SCCB_WR_Reg (HREF,temp); SCCB_WR_Reg (HOutSize,width>>2 ); SCCB_WR_Reg (VOutSize,height>>1 ); SCCB_RD_Reg (EXHCH); temp = (raw|(width&0x03 )|((height&0x01 )<<2 )); SCCB_WR_Reg (EXHCH,temp); } u8 SCCB_WR_Reg (u8 reg,u8 data) { u8 res=0 ; SCCB_Start (); if (SCCB_WR_Byte (SCCB_ID))res=1 ; delay_us (100 ); if (SCCB_WR_Byte (reg))res=1 ; delay_us (100 ); if (SCCB_WR_Byte (data))res=1 ; SCCB_Stop (); return res; } u8 SCCB_RD_Reg (u8 reg) { u8 val=0 ; SCCB_Start (); SCCB_WR_Byte (SCCB_ID); delay_us (100 ); SCCB_WR_Byte (reg); delay_us (100 ); SCCB_Stop (); delay_us (100 ); SCCB_Start (); SCCB_WR_Byte (SCCB_ID|0X01 ); delay_us (100 ); val=SCCB_RD_Byte (); SCCB_No_Ack (); SCCB_Stop (); return val; }
设置Camera画面风格 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 void camera_Light_Mode (u8 mode) { switch (mode) { case 0 : SCCB_WR_Reg (0x13 , 0xff ); SCCB_WR_Reg (0x0e , 0x65 ); SCCB_WR_Reg (0x2d , 0x00 ); SCCB_WR_Reg (0x2e , 0x00 ); break ; case 1 : SCCB_WR_Reg (0x13 , 0xfd ); SCCB_WR_Reg (0x01 , 0x5a ); SCCB_WR_Reg (0x02 , 0x5c ); SCCB_WR_Reg (0x0e , 0x65 ); SCCB_WR_Reg (0x2d , 0x00 ); SCCB_WR_Reg (0x2e , 0x00 ); break ; case 2 : SCCB_WR_Reg (0x13 , 0xfd ); SCCB_WR_Reg (0x01 , 0x58 ); SCCB_WR_Reg (0x02 , 0x60 ); SCCB_WR_Reg (0x0e , 0x65 ); SCCB_WR_Reg (0x2d , 0x00 ); SCCB_WR_Reg (0x2e , 0x00 ); break ; case 3 : SCCB_WR_Reg (0x13 , 0xfd ); SCCB_WR_Reg (0x01 , 0x84 ); SCCB_WR_Reg (0x02 , 0x4c ); SCCB_WR_Reg (0x0e , 0x65 ); SCCB_WR_Reg (0x2d , 0x00 ); SCCB_WR_Reg (0x2e , 0x00 ); break ; case 4 : SCCB_WR_Reg (0x13 , 0xfd ); SCCB_WR_Reg (0x01 , 0x96 ); SCCB_WR_Reg (0x02 , 0x40 ); SCCB_WR_Reg (0x0e , 0x65 ); SCCB_WR_Reg (0x2d , 0x00 ); SCCB_WR_Reg (0x2e , 0x00 ); break ; case 5 : SCCB_WR_Reg (0x13 , 0xff ); SCCB_WR_Reg (0x0e , 0xe5 ); break ; } }
0x04 LCD相关函数 LCD输出字符串: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 void LCD_ShowString (u16 x, u16 y, u16 width, u16 height, u8 size, u8 *p) { u8 x0 = x; width += x; height += y; while True: { if (x >= width) { x = x0; y += size; } if (y >= height) break ; LCD_ShowChar (x, y, *p, size, 0 ); x += size / 2 ; p++; } } void LCD_ShowChar (u16 x, u16 y, u8 num, u8 size, u8 mode) { u8 temp, t1, t; u16 y0 = y; u8 csize = (size / 8 + ((size % 8 ) ? 1 : 0 )) * (size / 2 ); num = num - ' ' ; for (t = 0 ; t < csize; t++) { if (size == 16 ) temp = asc2_1608[num][t]; else return ; for (t1 = 0 ; t1 < 8 ; t1++) { if (temp & 0x80 ) LCD_Fast_DrawPoint (x, y, POINT_COLOR); else if (mode == 0 ) LCD_Fast_DrawPoint (x, y, BACK_COLOR); temp <<= 1 ; y++; if ((y - y0) == size) { y = y0; x++; break ; } } } } void LCD_Fast_DrawPoint (u16 x, u16 y, u16 color) { LCD_WR_REG (lcddev.setxcmd); LCD_WR_DATA (x >> 8 ); LCD_WR_DATA (x & 0XFF ); LCD_WR_REG (lcddev.setycmd); LCD_WR_DATA (y >> 8 ); LCD_WR_DATA (y & 0XFF ); LCD->LCD_REG=lcddev.wramcmd; LCD->LCD_RAM=color; }
LCD屏幕填充颜色: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 void LCD_Fill (u16 sx, u16 sy, u16 ex, u16 ey, u16 color) { u16 i, j; u16 xlen = 0 ; xlen = ex - sx + 1 ; for (i = sy; i <= ey; i++) { LCD_SetCursor (sx, i); LCD_WriteRAM_Prepare (); for (j = 0 ; j < xlen; j++) { LCD->LCD_RAM=color; } } }
0x05 按键相关函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 u8 KEY_Scan (u8 mode) { static u8 key_up=1 ; if (mode) key_up=1 ; if (key_up&&(KEY0==0 ||KEY1==0 )){ key_up=0 ; if (KEY0==0 ) return KEY0_PRES; else if (KEY1==0 ) return KEY1_PRES; }else if (KEY0==1 &&KEY1==1 ) key_up=1 ; return 0 ; }
0x06 main函数 其中,camera_refresh的逻辑为:(1)判断ov_sta变量是否为真,如果为真,表示有帧中断发生,即摄像头采集到了一帧图像;(2)设置LCD的扫描方向为从上到下,从左到右;(3)设置LCD的显示窗口为屏幕中央的一块区域,大小为CAMARA_WINDOW_WIDTH x CAMARA_WINDOW_HEIGHT;(4)准备写入LCD的GRAM(图形存储器);(5)复位摄像头的读指针,使其指向第一个像素数据;(6)用双层循环遍历摄像头的每个像素,每次读取两个字节(16位)的数据,即一个像素的颜色值;(7)将读取到的颜色值写入LCD的GRAM中,显示在对应的位置上;(8)清零ov_sta变量,表示帧中断已经处理完毕;(9)恢复LCD的默认扫描方向。
最终实验的展示效果为:
(1)屏幕首先输出”wd2711’s camera exp”。
(2)如果摄像头正常进行初始化,那么输出”camera init success”,并闪烁输出”press key1 to start”。
(3)按下key1按键,之后进入摄像头画面,画面为VGA格式。
(4)按下key0,可以改变画面风格,一共有6种风格。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 #include "delay.h" #include "key.h" #include "sys.h" #include "lcd.h" #include "usart.h" #include "string.h" #include "camera.h" #include "timer.h" #include "exti.h" #include "usmart.h" #define CAMERA 1 #define CAMARA_WINDOW_WIDTH 320 #define CAMARA_WINDOW_HEIGHT 240 u8 *LMODE_TBL[6 ]={"Auto" ,"Sunny" ,"Cloudy" ,"Office" ,"Home" ,"Night" }; extern u8 ov_sta; void camera_refresh (void ) { u32 i,j; u16 color; if (ov_sta) { LCD_Scan_Dir (U2D_L2R); LCD_Set_Window ((lcddev.width-CAMARA_WINDOW_WIDTH)/2 ,(lcddev.height-CAMARA_WINDOW_HEIGHT)/2 ,CAMARA_WINDOW_WIDTH,CAMARA_WINDOW_HEIGHT); LCD_WriteRAM_Prepare (); CAMERA_RRST=0 ; CAMERA_RCK_L; CAMERA_RCK_H; CAMERA_RCK_L; CAMERA_RRST=1 ; CAMERA_RCK_H; for (i=0 ;i<CAMARA_WINDOW_HEIGHT;i++) { for (j=0 ;j<CAMARA_WINDOW_WIDTH;j++) { CAMERA_RCK_L; color=GPIOC->IDR&0XFF ; CAMERA_RCK_H; color<<=8 ; CAMERA_RCK_L; color|=GPIOC->IDR&0XFF ; CAMERA_RCK_H; LCD->LCD_RAM=color; } } ov_sta=0 ; LCD_Scan_Dir (DFT_SCAN_DIR); } } int main (void ) { u8 key; u8 i=0 ; u8 lightmode=0 ; delay_init (); uart_init (115200 ); KEY_Init (); LCD_Init (); LCD_ShowString (30 ,50 ,200 ,16 ,16 ,"wd2711's camera exp" ); while (1 ) { if (CAMERA_Init ()==0 ) { LCD_ShowString (30 ,210 ,200 ,16 ,16 ,"camera init success" ); while (1 ) { key=KEY_Scan (0 ); if (key==KEY1_PRES) { CAMERA_Window_Set (OV7725_WINDOW_WIDTH,OV7725_WINDOW_HEIGHT,1 ); break ; } i++; if (i==100 ) LCD_ShowString (30 ,230 ,210 ,16 ,16 ,"press key1 to start" ); if (i==200 ) { LCD_Fill (30 ,230 ,210 ,250 +16 ,WHITE); i=0 ; } delay_ms (5 ); } CAMERA_CS=0 ; break ; } } EXTI8_Init (); while (1 ) { LCD_ShowString (30 ,250 ,210 ,16 ,16 ,"press key0 to change mode" ); LCD_ShowString (30 ,270 ,210 ,16 ,16 ,"now mode is " ); LCD_ShowString (30 ,290 ,210 ,16 ,16 ,LMODE_TBL[lightmode]); if (key) { switch (key) { case KEY0_PRES: lightmode++; if (lightmode>5 ) lightmode=0 ; camera_Light_Mode (lightmode); break ; } } camera_refresh (); } }
0x07 最终展示效果 初始界面:
进入摄像头画面:
更改画面风格:
代码地址: https://github.com/WD-2711/stm32-IOT