1. 程式人生 > >”WinForm上位機+OV7670攝像頭+STM32+藍芽“影象採集系統(二)PC-MCU藍芽通訊及WinForm上位機開發

”WinForm上位機+OV7670攝像頭+STM32+藍芽“影象採集系統(二)PC-MCU藍芽通訊及WinForm上位機開發

上篇Blog談了一下stm32驅動ov7670進行影象採集,這一篇談一下後續的幾個步驟:

1、影象處理

因為對影象質量要求不高,而且串列埠藍芽通訊速度侷限於波特率。所以決定只傳輸灰度影象,簡單地用了RGB565三個分量取高四位的均值。將兩個畫素拼接在一起,放在一個unsigned char變數裡,前一畫素的4位灰度值放在高四位,後一畫素放在低四位。 這樣就只需要傳輸320 * 240 / 2 = 38400個byte就可以了。

2、影象傳輸

用的經典藍芽模組(hc05或hc06),很簡單的串列埠程式,不再贅述。

3、影象顯示

先來一張效果圖!


如上圖:是通過C#開發的WinForm程式,功能就是接受串列埠送來的畫素灰度值,刷新出圖片顯示在右側,並儲存.bmp格式的圖片到電腦

1)首先,將電腦藍芽開啟,連線MCU側的經典藍芽模組,在控制面板中開啟藍芽,並新增裝置。在裝置管理器中確保Bluetooth驅動已安裝(上方紅框)。

其實PC內建的藍芽,對PC而言也就是一個串列埠裝置,跟一般的232串列埠並無區別。所以可以在端口裡可以看到兩個COM口(下方紅框),在上位機中開啟COM26(因機而異)即可。

注:有的PC藍芽打不開,可以百度一下如何開啟,記得要去官網下載藍芽驅動程式並安裝


2)Winform程式

開啟高逼格的VS2015


新建Windows窗體程式


通過左側的工具箱,新增各種空間。通過右側的屬性視窗,更改屬性值,最終的佈局如下圖:


網上類似的上位機程式應該有很多,下面附上我的程式,一部分也是參照網友的程式修改的,得於網路,饋於網路:

用到的東西應該也就  串列埠類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("儲存圖片成功!", "資訊");
        }
    }
}