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

图片上位机

一、思路

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,使能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<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:u16
                LCD_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;             //接收字节计数变量增加nun
       sp.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];
                   //解析RGB565
                   Int32 r, g, b;                        //0-255 , color 511
                   r = (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

本文转载自https://blog.csdn.net/qq_37832932/article/details/104975339

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容