图片上位机

  • 一、思路
  • 二、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<2){if(ov_sta==0){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,使能SWDGPIO_InitStructure.GPIO_Pin  = GPIO_Pin_15;//PA15GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置成上拉输入GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA15GPIO_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;                 //子优先级1NVIC_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<OV7725_WINDOW_HEIGHT;i++)
{for(j=0;j<OV7725_WINDOW_WIDTH;j++){GPIOB->CRL=0X88888888;OV7725_RCK=0;color=OV7725_DATA;   //读数据  --高8位OV7725_RCK=1; color<<=8;  OV7725_RCK=0;color|=OV7725_DATA;    //读数  --低8位     (高低8+8位合并成一个u16发送)                             OV7725_RCK=1;GPIOB->CRL=0X33333333;/*串口发送数据*/Send_Pic_Div(color);     //color:u16LCD_WR_DATA(color);      //显示一个像素点(RGB) 320*240中的一个               }
}

然后RGB565的一个像素是16位,两字节。在测试过程中发现,如果直接发送u16的color,那么图像将会偏绿。调试发现读取高字节R分量值为0。所以我还是选择将u16拆成低8位和高8位分开发送。

但是其实真正的原因在于上位机解析方法错了,发送方式并无影响,这点我们在后文说。

串口发送函数,先发高位,后发低位:

void Send_Pic_Div(u16 color)
{u8 temp;        temp = color&0x00ff;                      //低八位USART_SendData(USART1,temp);while(USART_GetFlagStatus(USART1,USART_FLAG_TC) != SET);temp = color>>8;                               //高八位USART_SendData(USART1,temp);while(USART_GetFlagStatus(USART1,USART_FLAG_TC) != SET);
}

或者直接发u16的color像素:

void Send_Pic_All(u16 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;             //接收字节计数变量增加nunsp.Read(received_buf, 0, num);    //读取接收缓冲区中num个字节到byte数组中sb.Clear();                       //防止出错,首先清空字符串构造器if (isHex == true){//u16 = 2bytes//这里是按byte读取,换成Int16也不行,需要一次读两个byte出来for (int i = 0; i < received_buf.Length; i++){sb.Append(received_buf[i].ToString("X2") + ' ');    //将byte型数据转化为2位16进制文本显示,并用空格隔开if ((i+1) * 2 <= received_buf.Length){//读取一个像素colorL = received_buf[i * 2];colorH = received_buf[i * 2 + 1];//解析RGB565Int32 r, g, b;                        //0-255 , color 511r = (colorH & 0xf8) >> 3;g = ((colorH & 0x07) << 2) | ((colorL & 0xe0) >> 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。所以我们需要对接收的数据进行格式转换。

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

  1. RGB565:

每个像素用16比特位表示,占2个字节,RGB分量分别使用5位、6位、5位。

//获取高字节的5个bit
R = color & 0xF800;
//获取中间6个bit
G = color & 0x07E0;
//获取低字节5个bit
B = color & 0x001F;
  1. 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) << 2) | ((colorL & 0xe0) >> 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

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

  1. 采集温度数据,用串口传输到上位机

    这里写目录标题 一.实验要求 二.I2C总线通信协议 (一)概念 (二)I2C总线特征 (三)I2C总线协议 (四)I2C的两种方式--硬件I2C和软件I2C 三.AHT20采集温度并上传上位机 四. ...

  2. STM32+ESP8266连接电脑Qt网络上位机——QT篇

    本文简单介绍下手写网络调试器并连接ESP8266模块 上篇:  STM32+ESP8266连接电脑Qt网络上位机--准备工作 目录 一.部分Qt代码及实现过程 二.实现过程--使用ESP8266连接上 ...

  3. stm32单片机 北斗GPS 定位 vb上位机显示。 蓝牙主从级通信

    stm32单片机 北斗GPS 定位 vb上位机显示. 蓝牙主从级通信. 主单片机获取GPS北斗模块定位信息后,通过蓝牙发送给从模块. 从蓝牙模块,从模块通过串口讲定位信息发送给vb上位机. 上位机实时 ...

  4. 乐鑫Esp32学习之旅 安信可 ESP32-Cam 摄像头开发板二次开发 C SDK编程,拍照图片通过有线串口传到上位机PC端。(附带设备端+PC端源码)

    本系列博客学习由非官方人员 半颗心脏 潜心所力所写,仅仅做个人技术交流分享,不做任何商业用途.如有不对之处,请留言,本人及时更改. 系列一:ESP32系列模组基础学习系列笔记 1. 爬坑学习新旅程,虚 ...

  5. C#实现串口通信的上位机开发

    目录 上位机 串口通信 C#串口通信:SerialPort类 列出所有的串口 C#串口通信:读写数据 写数据: 读数据: DataReceived事件: 数据发送不同步问题: 界面设计 波形显示(ch ...

  6. 多路双向串口转网口上位机C++源代码带主动连接支持UDP和TCP客户端Socket通信C语言

    多路双向串口转网口上位机C++源代码带主动连接支持UDP和TCP客户端Socket通信C语言 使用说明介绍 1.功能介绍: 完成了多路网口和串口数据转换的功能. 可实现串口接收到的数据,通过网口发送出 ...

  7. C#做一个简单的进行串口通信的上位机

    C#做一个简单的进行串口通信的上位机 乱世中的单纯 发布于 1年前,共有 10 条评论 1.上位机与下位机 上位机相当于一个软件系统,可以用于接收数据.控制数据.即可以对接收到的数据直接发送操控命令来 ...

  8. 详细介绍如何从0开始写一个数据通信,将数据从单片机发送到上位机(或者虚拟示波器)进行数据或图像显示,以及常见问题或注意事项解答,本文主要以匿名上位机为例,适合新手和小白

      本文主要内容:详细介绍如何从0开始写一个数据通信,将数据从单片机发送到上位机(或者虚拟示波器)进行数据或图像显示,帮助我们调节一些参数,比如电机PID的调节.波形融合等,以及在我们写通信协议的时候 ...

  9. 自己用C#写的控制三菱FX5U PLC(三菱任何系列都通用,网口,串口都行)的上位机程序

    自己用C#写的控制三菱FX5U PLC(三菱任何系列都通用,网口,串口都行)的上位机程序,PLC源程序也附上,是学习C#和三菱PLC通信的好例子,有对辅助继电器M,对单字,双子D的读写,IO的监控,报 ...

  10. C#写的控制三菱FX5U PLC(三菱任何系列都通用,网口,串口都行)的上位机程序

    自己用C#写的控制三菱FX5U PLC(三菱任何系列都通用,网口,串口都行)的上位机程序,PLC源程序也附上,是学习C#和三菱PLC通信的好例子,有对辅助继电器M,对单字,双子D的读写,IO的监控,报 ...

最新文章

  1. input输入框为number类型时,去掉上下小箭头
  2. 利用pmap查看进程的地址空间
  3. OpenMP并行化实例----Mandelbrot集合并行化计算
  4. 【Python】青少年蓝桥杯_每日一题_4.15_正方形里面套个实心圆形
  5. C# Windows Phone 8 WP8 开发,取得手机萤幕大小两种方法。
  6. 烟袋斜街-后海,印象已模糊
  7. phalcon无限重定向
  8. Java描述设计模式(15):责任链模式
  9. 学习Vim 全图解释
  10. lisp将图元追加选择_AutoLISP入门7 - 图元资料的取得与活用技巧(二)
  11. 解决docker-compose: command not found
  12. GsonFormat的使用
  13. IBM IT 企业基础架构解决方案
  14. 纯CSS3制作优惠券线性UI效果
  15. vue+element实现手机号验证码注册
  16. 如何更改itunes备份位置_Mac怎么修改iTunes的备份路径 如何在 Mac 中修改iTunes的备份路径...
  17. 终于搞懂python通过twain模块控制扫描仪了
  18. 在MATLAB中实现均值变点法
  19. 劝学篇翻译软件测试,《劝学篇》 全文、注释、翻译和赏析 - 可可诗词网
  20. 利用计算机制作3D动画属于,第一部完全以电脑技术制作而成的3D动画长片

热门文章

  1. 千锋网站完工!品优购更新
  2. 学生健康数据管理软件怎么找?
  3. Linux网络编程之TCP协议(一版)
  4. 5+单细胞+脂质代谢+预后模型+实验
  5. 大学英语计算机开学考试试题,2017大学计算机一级考试试题(含答案)
  6. 迅为工业级iMX6Q开发板全新升级兼容PLUS版本|四核商业级|工业级|双核商业级
  7. 29_static关键字
  8. UE5笔记【】操作细节记录:处理拉伸纹理形变;贴花厚度处理技巧;相框中添加照片;
  9. java获取微信小程序用户信息
  10. 图片在线放大网站||视频素材网站