【STM32调试(一)】串口发送像素,上位机解析显示。 您所在的位置:网站首页 串口发送文件格式是什么 【STM32调试(一)】串口发送像素,上位机解析显示。

【STM32调试(一)】串口发送像素,上位机解析显示。

2024-07-01 22:56| 来源: 网络整理| 查看: 265

图片上位机 一、思路二、STM32采集数据发送2.1、OV7725模组2.2、串口发送 三、上位机接收,解析,显示保存2.1、接收解析2.2、数据格式转换2.3、显示结果及存在问题 四、小结&开源

一、思路

STM32采集OV数据,数据尺寸是QVGA(320*240),RGB565数据格式。采集的FIFO数据是一个像素,占两个字节。每采集一个像素就向串口发送一个像素。上位机是一个串口助手,接收串口数据,将一个RGB565格式像素解析为RGB55格式并显示在上位机。

二、STM32采集数据发送 2.1、OV7725模组

我们使用正点原子的例程进行修改,在接线时注意将数据线绑在一起,其它线绑在一起,以防发生数据干扰。

我买的OV7725摄像头是带FIFO的,因为 OV7725 的像素时钟(PCLK)最高可达 24Mhz,我们用STM32F103的IO口直接抓取,是非常困难的,也十分占耗 CPU,所以我们并不是采取直接抓取来自 OV7725 的数据,而是通过 FIFO 读取,ALIENTEK-OV7725 摄像头模块自带了一个 FIFO 芯片(AL422B),用于暂存图像数据,OV将图像帧存储在FIFO中,CPU就可以自己慢慢读取FIFO中的数据帧,这样就可以很方便的获取图像数据了,而不再需要单片机具有高速 IO,也不会耗费多少 CPU,任意一款MCU都可控制该模块和获取图像。

在这里插入图片描述 1. 串行摄像头控制总线(SCCB)

ATK-OV7725 摄像头模块的所有配置,都是通过 SCCB 总线来实现的。

它由两条数据线组成:一个是用于传输时钟信号的 SIO_C(即 OV_SCL),另一个适用于传输数据信号的 SIO_D(即 OV_SDA)。 SCCB 的传输协议与 IIC 协议极其相似,只不过 IIC 在每传输完一个字节后,接收数据的一方要发送一位的确认数据,而 SCCB 一次要传输 9 位数据,前 8 位为有用数据,而第 9 位数据在写周期中是 don’t care 位(即不必关心位),在读周期中是 NA 位。 SCCB 定义数据传输的基本单元为相( phase),即一个相传输一个字节数据。

2. 驱动程序

中断程序:

PA15位中断输入,接OV7725的VSYNC脚,负责帧同步。 ov_sta是OV的中断标记,状态变量ov_sta初始为0,VSYNC中断到来时,OV开始输出一帧图像,复位FIFO写指针,允许写入FIFO,ov_sta自增至1。

//中断服务函数 u8 ov_sta; void EXTI15_10_IRQHandler(void) { if(EXTI_GetITStatus(EXTI_Line15)==SET) { if(ov_sta OV7670_WRST=0; //复位写指针 OV7670_WRST=1; OV7670_WREN=1; //允许写入FIFO }else OV7670_WREN=0; //禁止写入FIFO ov_sta++; } } EXTI_ClearITPendingBit(EXTI_Line15); //清除LINE15上的中断标志位 } //外部中断初始化程序 //初始化PA15为中断输入. void EXTI15_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; EXTI_InitTypeDef EXTI_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOA,ENABLE);//外部中断,需要使能AFIO和GPIOA时钟 GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE); //关闭JTAG,使能SWD GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;//PA15 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置成上拉输入 GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA15 GPIO_SetBits(GPIOA,GPIO_Pin_15); GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource15); EXTI_InitStructure.EXTI_Line=EXTI_Line15; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;//下降沿触发 EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器 NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; //使能按键所在的外部中断通道 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级2, NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01; //子优先级1 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道 NVIC_Init(&NVIC_InitStructure); }

刷新显示:

camera_refresh()负责读取FIFI图像数据,并送至LCD显示。在这里是连续读取的,读完一帧图像就立即将ov_sta置0。若想只读取一帧图像,可以不把ov_sta置0,不进行下一次FIFO读取。这也是做摄像机和照相机的区别。

2.2、串口发送

有两种发送方式:高低位单独发送;合并一起发送。 发送内容:黑白,二值,彩图

选择黑白发送或二值图像发送就会简单许多,而且数据量至少会小一半。但是为了后续上位机有更高质量的图片做处理,这里还是选择发送彩色图像,将采集的像素点直接发送给串口。

采集一个像素,发送一个像素:

for(i=0;i GPIOB->CRL=0X88888888; OV7725_RCK=0; color=OV7725_DATA; //读数据 --高8位 OV7725_RCK=1; color USART_SendData(USART1,color); while(USART_GetFlagStatus(USART1,USART_FLAG_TC) != SET); }

最后需要注意,把串口其它发送数据部分注释掉,因为上位机把所有接受数据都认为是图像数据。我这里只注释了LCD初始化和TIM3中断里的两个printf()。

至此,STM32这边的程序就完成啦,接下来做上位机部分。

三、上位机接收,解析,显示保存 2.1、接收解析

上位机还是基于串口助手修改的,我直接用我之前写的串口助手修改了,基础功能不多(正是想要的)。需要重点修改的就是串口接收部分。

我们采用采用16进制接收数据。因为都是字节流,而且是不断发送过来的。所以接收到数据包就要解析。

这里需要强调一下,一个像素是16位,2字节,所以我们要拿到两个byte,也就是像素的高位和低位,然后再进行解析。我这样来取两字节:

colorL = received_buf[i * 2]; colorH = received_buf[i * 2 + 1];

还有Invoke最好套在最外面,因为里面会频繁的更新UI显示。 整个C#接收代码是这样的:

private void sp_DataReceived(object sender, SerialDataReceivedEventArgs e) { this.Invoke(new EventHandler(delegate { //---------------------------// int num = sp.BytesToRead; //获取接收缓冲区中的字节数 byte[] received_buf = new byte[num]; //声明一个大小为num的字节数据用于存放读出的byte型数据 receive_count += num; //接收字节计数变量增加nun sp.Read(received_buf, 0, num); //读取接收缓冲区中num个字节到byte数组中 sb.Clear(); //防止出错,首先清空字符串构造器 if (isHex == true) { //u16 = 2bytes //这里是按byte读取,换成Int16也不行,需要一次读两个byte出来 for (int i = 0; i //读取一个像素 colorL = received_buf[i * 2]; colorH = received_buf[i * 2 + 1]; //解析RGB565 Int32 r, g, b; //0-255 , color 511 r = (colorH & 0xf8) >> 3; g = ((colorH & 0x07) 6); b = colorL & 0x1f; //Console.WriteLine("Red: "+r.ToString()+ " Green: " + g.ToString()+ " Blue: " + b.ToString()); //合成并显示像素,提高亮度 newColor = Color.FromArgb(r*5, g*5, b*5); Int32 Row = (receive_count) / 320 / 2; //计算列: 共240列,每列320个像素点 OvImage.SetPixel(Row, y++, newColor); //换列显示 if (y == 320) { y = 0; } } } } else { //选中ASCII模式显示 sb.Append(Encoding.ASCII.GetString(received_buf)); //将整个数组解码为ASCII数组 } //更新UI显示 ptbOv7725.Image = OvImage; //放在外面按每次(一列)接收的来显示了 tbxRecvData.AppendText(sb.ToString()); tbxRecvLength.Text = receive_count.ToString() + "Bytes"; //--------------------------------------// })); } 2.2、数据格式转换

LCD上是RGB565,电脑上BMP是16位RGB555。所以我们需要对接收的数据进行格式转换。

先说一下这两种格式的数据。

RGB565:

每个像素用16比特位表示,占2个字节,RGB分量分别使用5位、6位、5位。 在这里插入图片描述

//获取高字节的5个bit R = color & 0xF800; //获取中间6个bit G = color & 0x07E0; //获取低字节5个bit B = color & 0x001F; RGB555

每个像素用16比特位表示,占2个字节,RGB分量都使用5位(最高位保留)。 在这里插入图片描述

//获取高字节的5个bit R = color & 0x7C00; //获取中间5个bit G = color & 0x03E0; //获取低字节5个bit B = color & 0x001F;

所以我们最终的解析方法是这样的:

对于G分量由6转换成5,直接舍弃最低位(右移一位实现)。

提取R分量:将colorH右移3位,最后将剩余位清零。 提取G分量:将colorH左移2位,做G分量的高3位;将colorL右移6位,舍弃低位,其余做低2位,拼接在一起,最后将剩余位清零。 提取B分量:将colorL剩余位清零。 在这里插入图片描述

//解析RGB565 Int32 r, g, b; //0-255 , color 511 r = (colorH & 0xf8) >> 3; g = ((colorH & 0x07) 6); b = colorL & 0x1f;

C#里面Bitmap类所指定的数据格式不知道为啥不好用,选择RGB555格式后就是黑底,很疑惑。

2.3、显示结果及存在问题

图像在第一列会有偏移: 在这里插入图片描述 接收数据少一字节: 在这里插入图片描述 上位机与LCD花屏部分相反(左上位机保存的图像,有LCD显示): 我猜测是丢失的一字节引起的。 在这里插入图片描述在这里插入图片描述 实验截图: 在这里插入图片描述

四、小结&开源

开发其实遇到了太多问题,数据解析,丢失字节,传输速度慢等等问题。在我的OneNote笔记上大概有10页多,

现在对于RGB565和RGB555数据格式有了了解,也知道该如何解析。OV7725模块的配置使用也比较了解。

该项目还是有一些问题未解决,但是我目前换了更简单的思路(后面会继续发出来),就不再继续深究了。如果有兴趣欢迎指导我一下。后面还要对上位机的启动方式做修改,让它能多次接收。最后能在解决那一字节的问题。传输时间有点长,可以提高波特率。

项目地址(STM32和上位机代码压缩在一处了): CSDN:上位机+STM32 Github:上位机 、 STM32

参考文章: 1、RGB888、RGB555、RGB565之间转换 2、stm32调用OV7670获取图像并通过蓝牙传输至PC



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有