”WinForm上位机+OV7670摄像头+STM32+蓝牙“图像采集系统(二)PC-MCU蓝牙通信及WinForm上位机开发

时间:2021-04-24 16:49:50

上篇Blog谈了一下stm32驱动ov7670进行图像采集,这一篇谈一下后续的几个步骤:


1、图像处理

因为对图像质量要求不高,而且串口蓝牙通信速度局限于波特率。所以决定只传输灰度图像,简单地用了RGB565三个分量取高四位的均值。将两个像素拼接在一起,放在一个unsigned char变量里,前一像素的4位灰度值放在高四位,后一像素放在低四位。 这样就只需要传输320 * 240 / 2 = 38400个byte就可以了。


2、图像传输

用的经典蓝牙模块(hc05或hc06),很简单的串口程序,不再赘述。


3、图像显示


先来一张效果图!


”WinForm上位机+OV7670摄像头+STM32+蓝牙“图像采集系统(二)PC-MCU蓝牙通信及WinForm上位机开发


如上图:是通过C#开发的WinForm程序,功能就是接受串口送来的像素灰度值,刷新出图片显示在右侧,并保存.bmp格式的图片到电脑


1)首先,将电脑蓝牙打开,连接MCU侧的经典蓝牙模块,在控制面板中打开蓝牙,并添加设备。在设备管理器中确保Bluetooth驱动已安装(上方红框)。

其实PC内置的蓝牙,对PC而言也就是一个串口设备,跟一般的232串口并无区别。所以可以在端口里可以看到两个COM口(下方红框),在上位机中打开COM26(因机而异)即可。

注:有的PC蓝牙打不开,可以百度一下如何打开,记得要去官网下载蓝牙驱动程序并安装

”WinForm上位机+OV7670摄像头+STM32+蓝牙“图像采集系统(二)PC-MCU蓝牙通信及WinForm上位机开发”WinForm上位机+OV7670摄像头+STM32+蓝牙“图像采集系统(二)PC-MCU蓝牙通信及WinForm上位机开发


2)Winform程序


打开高逼格的VS2015

”WinForm上位机+OV7670摄像头+STM32+蓝牙“图像采集系统(二)PC-MCU蓝牙通信及WinForm上位机开发


新建Windows窗体程序


”WinForm上位机+OV7670摄像头+STM32+蓝牙“图像采集系统(二)PC-MCU蓝牙通信及WinForm上位机开发


通过左侧的工具箱,添加各种空间。通过右侧的属性窗口,更改属性值,最终的布局如下图:


”WinForm上位机+OV7670摄像头+STM32+蓝牙“图像采集系统(二)PC-MCU蓝牙通信及WinForm上位机开发


网上类似的上位机程序应该有很多,下面附上我的程序,一部分也是参照网友的程序修改的,得于网络,馈于网络:

用到的东西应该也就  串口类SerialPort 、 线程Thread 、 Bitmap类 三样东西,相对简单。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;


namespace MyCom
{
    public partial class Form1 : Form
    {
        SerialPort sp = null;   //声明一个串口类
        bool isOpen = false;    //打开串口标志位
        bool isSetProperty = false; //属性设置标志位
        bool isHex = false;     //十六进制显示标志位


        Bitmap OvImage = new Bitmap(240, 320);
        
        public Form1()
        {
            InitializeComponent();  //窗口初始化,net自动生成
        }
        private void Form1_Load(object sender, EventArgs e)
        {
            this.MaximumSize = this.Size;
            this.MinimumSize = this.Size;
            this.MaximizeBox = false;
            for (int i = 0; i < 30; i++)//最大支持到串口10,可根据自己需求增加
            {
                cbxCOMPort.Items.Add("COM" + (i + 1).ToString());
            }
            cbxCOMPort.SelectedIndex = 0;
            //列出常用的波特率
            cbxBaudRate.Items.Add("1200");
            cbxBaudRate.Items.Add("2400");
            cbxBaudRate.Items.Add("4800");
            cbxBaudRate.Items.Add("9600");
            cbxBaudRate.Items.Add("19200");
            cbxBaudRate.Items.Add("38400");
            cbxBaudRate.Items.Add("43000");
            cbxBaudRate.Items.Add("56000");
            cbxBaudRate.Items.Add("57600");
            cbxBaudRate.Items.Add("115200");
            cbxBaudRate.SelectedIndex = 9;
            //列出停止位
            cbxStopBits.Items.Add("0");
            cbxStopBits.Items.Add("1");
            cbxStopBits.Items.Add("1.5");
            cbxStopBits.Items.Add("2");
            cbxStopBits.SelectedIndex = 1;
            //列出数据位
            cbxDataBits.Items.Add("8");
            cbxDataBits.Items.Add("7");
            cbxDataBits.Items.Add("6");
            cbxDataBits.Items.Add("5");
            cbxDataBits.SelectedIndex = 0;
            //列出奇偶校验位
            cbxParity.Items.Add("无");
            cbxParity.Items.Add("奇校验");
            cbxParity.Items.Add("偶校验");
            cbxParity.SelectedIndex = 0;
            //默认为Hex显示
            rbnHex.Checked = true;


            //初始接收字符数目为0
            tbxRecvLength.Text = "0";
        }


        //滚动条ScrollBar自动滚到最底端
        private void tbxRecvData_TextChanged(object sender, EventArgs e)
        {
            tbxRecvData.SelectionStart = tbxRecvData.Text.Length;
            tbxRecvData.ScrollToCaret();
        }


        private void btnSend_Click(object sender, EventArgs e)//发送串口数据
        {
            if (isOpen)
            {
                try
                {
                    sp.WriteLine(tbxSendData.Text);
                }
                catch (Exception)
                {
                    MessageBox.Show("发送数据时发生错误!", "错误提示");
                    return;
                }
            }
            else
            {
                MessageBox.Show("串口未打开!", "错误提示");
                return;
            }
            if (CheckSendData())//检测要发送的数据
            {
               // MessageBox.Show("请输入要发送的数据!", "错误提示");
                return;
            }
        }


        private void btnCheckCOM_Click(object sender, EventArgs e)
        {
            bool comExistence = false;  //有可用串口标志位
            cbxCOMPort.Items.Clear();
            for (int i = 0; i < 30; i++)
            {
                try
                {
                    SerialPort sp = new SerialPort("COM" + (i + 1).ToString());
                    sp.Open();
                    sp.Close();
                    cbxCOMPort.Items.Add("COM" + (i + 1).ToString());
                    comExistence = true;   
                }
                catch (Exception)
                {


                    continue;
                }
            }
            if (comExistence)
            {
                cbxCOMPort.SelectedIndex = 0;//使ListBox显示第一个添加的索引
            }
            else
            {
                MessageBox.Show("没有找到可用串口!","错误提示");
            }
        }
        private bool CheckPortSetting() //检查串口是否设置
        {
            if (cbxCOMPort.Text.Trim() == "") return false;
            if (cbxBaudRate.Text.Trim() == "") return false;
            if (cbxDataBits.Text.Trim() == "") return false;
            if (cbxParity.Text.Trim() == "") return false;
            if (cbxStopBits.Text.Trim() == "") return false;
            return true;
        }
        private bool CheckSendData()
        {
            if (tbxSendData.Text.Trim() == "") return false;
            return true;
        }
        private void SetPortProperty()  //设置串口的属性
        {
            sp = new SerialPort();
            sp.PortName = cbxCOMPort.Text.Trim();//设置串口名


            sp.BaudRate = Convert.ToInt32(cbxBaudRate.Text.Trim());//设置串口波特率


            float f = Convert.ToSingle(cbxStopBits.Text.Trim());   //设置停止位
            if (0 == f)
            {
                sp.StopBits = StopBits.None;
            }
            else if (1.5 == f)
            {
                sp.StopBits = StopBits.OnePointFive;
            }
            else if (1 == f)
            {
                sp.StopBits = StopBits.One;
            }
            else if (2 == f)
            {
                sp.StopBits = StopBits.Two;
            }
            else
            {
                sp.StopBits = StopBits.One;
            }


            sp.DataBits = Convert.ToInt16(cbxDataBits.Text.Trim());//设置数据位


            string s = cbxParity.Text.Trim();//设置奇偶校验位
            if (0 == s.CompareTo("无"))
            {
                sp.Parity = Parity.None;
            }
            else if (0 == s.CompareTo("奇校验"))
            {
                sp.Parity = Parity.Odd;
            }
            else if (0 == s.CompareTo("偶校验"))
            {
                sp.Parity = Parity.Even;
            }
            else
            {
                sp.Parity = Parity.None;
            }


            sp.ReadTimeout = -1;//设置超时读取时间


            sp.RtsEnable = true;


            //定义DataReceived事件,当串口收到数据后触发事件
            sp.DataReceived += new SerialDataReceivedEventHandler(sp_DataReceived);
            if (rbnHex.Checked)
            {
                isHex = true;
            }
            else
            {
                isHex = false;  
            }
        }
        private void btnOpenCOM_Click(object sender, EventArgs e)
        {
            if (false == isOpen)
            {
                if (!CheckPortSetting()) //检查串口设置
                {
                    MessageBox.Show("串口未设置!", "错误提示");
                    return;
                }
                if (!isSetProperty)  //串口未设置则设置串口
                {
                    SetPortProperty();
                    isSetProperty = true;
                }
                try //打开串口
                {
                    sp.Open();
                    isOpen = true;
                    btnOpenCOM.Text = "关闭串口";
                    //串口打开后,相关的串口设置按钮便不可再用  
                    
                    cbxCOMPort.Enabled = false;
                    cbxBaudRate.Enabled = false;
                    cbxDataBits.Enabled = false;
                    cbxParity.Enabled = false;
                    cbxStopBits.Enabled = false;
                    rbnChar.Enabled = false;
                    rbnHex.Enabled = false;
                    
                }
                catch (Exception)
                {
                    //打开串口失败后,相应标志位取消
                    isSetProperty = false;
                    isOpen = false;
                    MessageBox.Show("串口无效或已被占用!", "错误提示");
                }
            }
            else
            {
                try //关闭串口
                {
                    sp.Close();
                    isOpen = false;
                    isSetProperty = false;
                    btnOpenCOM.Text = "打开串口";
                    //关闭串口后,串口设置选项便可以继续使用
                    cbxCOMPort.Enabled = true;
                    cbxBaudRate.Enabled = true;
                    cbxDataBits.Enabled = true;
                    cbxParity.Enabled = true;
                    cbxStopBits.Enabled = true;
                    rbnChar.Enabled = true;
                    rbnHex.Enabled = true;
                }
                catch (Exception)
                {


                    MessageBox.Show("关闭串口时发生错误!", "错误提示");
                }
            }
        }
        private void sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            System.Threading.Thread.Sleep(100);//延时100ms等待接收完数据
            //this.Invoke就是跨线程访问ui的方法,也是本文的范例
            this.Invoke(new EventHandler(delegate
            {
            Byte[] ReceivedData = new byte[sp.BytesToRead]; //创建接收字节数组
            sp.Read(ReceivedData, 0, ReceivedData.Length);  //读取所接收到的数据
            string RecvDataText = null;
            if (false == isHex)
            {
                for (int i = 0; i < ReceivedData.Length; i++)
                {
                    RecvDataText += ReceivedData[i];
                }
                //byte类型转成string类型
                RecvDataText = System.Text.Encoding.Default.GetString(ReceivedData);
                tbxRecvData.Text += RecvDataText;//更新接收框数据
                tbxRecvLength.Text = tbxRecvData.TextLength.ToString();//更新接收框数据长度
            }
            else
            {
                for (int i = 0; i < ReceivedData.Length; i++)
                {
                        Int32 Row = tbxRecvData.TextLength / 3 /160;
                        Int32 DataH = (ReceivedData[i] >> 4) * 17;
                        Int32 DataL = (ReceivedData[i] & 0x0f) * 17;
                        RecvDataText += (ReceivedData[i].ToString("X2") + " ");//长度变成了3倍!
                        


                        //高4位是一个像素
                        Color newColorH = Color.FromArgb(DataH, DataH, DataH);
                        OvImage.SetPixel(Row, i * 2, newColorH);
                    
                        //低4位是下一个像素
                        Color newColorL = Color.FromArgb(DataL, DataL, DataL);
                        OvImage.SetPixel(Row, i * 2 + 1, newColorL);
                    
                 }
                    ptbOv7670.Image = OvImage;
                    tbxRecvData.Text += RecvDataText;//更新接收框数据
                    tbxRecvLength.Text = (tbxRecvData.TextLength/3).ToString();//更新接收框数据长度
              }
                sp.DiscardInBuffer();   //丢弃接收缓冲区数据
            }));
        }


        private void btnCleanData_Click(object sender, EventArgs e)
        {
            tbxRecvData.Text = "";
            //tbxSendData.Text = "";
            tbxRecvLength.Text = "0";//更新接收框数据长度
            //ptbOv7670.Image = OvImage;
            ptbOv7670.Image = null;
        }


        private void label6_Click(object sender, EventArgs e)
        {


        }


        private void label7_Click(object sender, EventArgs e)
        {


        }


        private void tbxRecvLength_TextChanged(object sender, EventArgs e)
        {


        }


        private void button1_Click(object sender, EventArgs e)
        {
            ptbOv7670.Image.Save("Ov7670.bmp");
            MessageBox.Show("保存图片成功!", "信息");
        }
    }
}