實現的效果
上面是用Proteus仿真的,,對了如果自己想用proteus仿真需要安裝下面這個軟件
再看一下實物顯示效果
先做上位機部分...........
為了程序一啟動就把電腦上能用的串口號顯示在下拉框中
private void Form1_Load(object sender, EventArgs e) { string[] ComName = SerialPort.GetPortNames();//把可用的串口號存入comname comboBoxCom.Items.AddRange(ComName);//添加到下拉框 comboBoxCom.SelectedIndex = comboBoxCom.Items.Count > 0 ? 0 : -1;//顯示第一個 }
還有就是串口呢可能會隨時改變,所以在用戶點擊下拉框的時候重新更新一下下拉框中的內容
private void comboBoxCom_DropDown(object sender, EventArgs e) { string[] ComName = SerialPort.GetPortNames();//把可用的串口號存入comname comboBoxCom.Items.Clear();//先清除一下,防止重復添加 comboBoxCom.Items.AddRange(ComName);//添加到下拉框 comboBoxCom.SelectedIndex = comboBoxCom.Items.Count > 0 ? 0 : -1;//顯示第一個 }
現在在波特率框中添加常用的波特率
現在的效果
然后放一個按鈕用來打開和關閉串口
現在就做打開和關閉串口部分,,,
/// <打開按鈕事件> /// /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void buttonOpen_Click(object sender, EventArgs e) { if (OpenFlage == false)//打開串口 { try { serialPort1.PortName = comboBoxCom.Text;//端口號 serialPort1.BaudRate = Convert.ToInt32(comboBoxBaud.Text);//波特率 serialPort1.Open();//打開串口 OpenFlage = true; } catch (Exception)//其余意外情況執行這里 { OpenFlage = false; MessageBox.Show("端口錯誤,請檢查串口", "提示"); } } else//關閉串口 { try { OpenFlage = false; if (serialPort1.IsOpen)//判斷串口是否打開,如果打開執行下一步操作 { serialPort1.Close(); } serialPort1.Close();//強制關閉 } catch (Exception) { } } }
對了按鈕點擊了打開串口,讓它顯示"關閉串口"
就用回調來顯示
現在按鈕事件就這樣了
/// <打開按鈕事件> /// /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void buttonOpen_Click(object sender, EventArgs e) { if (OpenFlage == false)//打開串口 { try { serialPort1.PortName = comboBoxCom.Text; serialPort1.BaudRate = Convert.ToInt32(comboBoxBaud.Text); serialPort1.Open(); OpenFlage = true; buttonOpen.Invoke(buttonConnectDelegate,"關閉串口"); } catch (Exception)//其余意外情況執行這里 { OpenFlage = false; buttonOpen.Invoke(buttonConnectDelegate, "打開串口"); MessageBox.Show("端口錯誤,請檢查串口", "提示"); } } else//關閉串口 { try { OpenFlage = false; buttonOpen.Invoke(buttonConnectDelegate, "打開串口"); if (serialPort1.IsOpen)//判斷串口是否打開,如果打開執行下一步操作 { serialPort1.Close(); } serialPort1.Close();//強制關閉 } catch (Exception) { } } }
現在在多優化一下,我們在打開了串口的時候,我接着用去選擇別的串口了,那么為了不去重復重新打開的按鈕動作,我們就多加一點程序,,,,這個一會再說吧!現在看不出效果
現在寫接收程序部分
放一個textbox
接收的文本框設置只讀
接收的數據肯定會很多,,所以讓他有上下的滾動條
然后界面又加了幾個按鈕和選擇
現在接收數據
為了接收到一條完整的數據之后再去做處理,我就用個定時器用於檢測接收是否空閑了一段時間,只要出現空閑說明接收到了一條完整的數據
設置的是10ms檢測一次
看程序里面怎么做,,,其實和我的單片機檢測空閑是一樣的道理
定義一個鏈表用於存儲數據,還有兩個計數變量
List<byte> SerialBuffer = new List<byte>(1024);//串口接收數據緩存 int UsartReadCnt = 0;//串口接收到的數據個數 int UsartIdleCnt = 0;//空閑檢測用
串口接收函數里面這樣寫
private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e) { byte[] SerialBuff = new byte[serialPort1.BytesToRead];//串口接收數據臨時緩存 if (serialPort1.BytesToRead != 0) { try { UsartReadCnt = serialPort1.Read(SerialBuff, 0, serialPort1.BytesToRead); SerialBuffer.AddRange(SerialBuff); } catch (Exception ex) { MessageBox.Show(ex.ToString()); } } }
然后定時器里面
/// <串口空閑檢測定時器> /// /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void timer1_Tick(object sender, EventArgs e) { if (UsartReadCnt != 0)//如果接收到數據了 { if (UsartIdleCnt == UsartReadCnt)//10ms時間數據沒了變化 { UsartReadCnt = 0;//清零數據個數 UsartIdleCnt = 0;//清零 byte[] ReadData = new byte[SerialBuffer.Count]; for (int i = 0; i < SerialBuffer.Count; i++) { ReadData[i] = SerialBuffer[i]; } SerialBuffer.RemoveRange(0, SerialBuffer.Count); } else { UsartIdleCnt = UsartReadCnt; } } }
現在定義個回調把數據顯示出來
/// <顯示串口接收到的信息> /// /// </summary> private void ShowReMsgMethod(byte[] by) { }
private void ShowReMsgMethod(byte[] by) { string getMsg = " "; if (checkBoxHexShow.Checked)//16進制顯示 { getMsg = byteToHexStr(by); //用到函數byteToHexStr--字節數組轉16進制字符串 } else { getMsg = new ASCIIEncoding().GetString(by); } textBoxDataRes.AppendText(getMsg); }
/// <字節數組轉16進制字符串> /// /// </summary> /// <param name="bytes"></param> /// <returns></returns> public static string byteToHexStr(byte[] bytes) { string returnStr = string.Empty; try { if (bytes != null) { for (int i = 0; i < bytes.Length; i++) { returnStr += bytes[i].ToString("X2"); } } return returnStr; } catch (Exception) { return returnStr; } }
現在啟動試一下
我電腦上安裝了虛擬串口軟件,方便調試
還有就是
當我們選擇這個的時候希望接收框里面的內容也跟着改變
就像是這樣
選擇上
然后再取消選擇
這樣感覺更好一些
寫上以下代碼
private void checkBoxHexShow_CheckedChanged(object sender, EventArgs e) { if (checkBoxHexShow.Checked) { try { byte[] by = StringToByte(textBoxDataRes.Text); textBoxDataRes.Clear(); textBoxDataRes.BeginInvoke(showReMsgSerialDelegate, by); } catch (Exception ex) { //MessageBox.Show(ex.ToString()); } } else { try { byte[] by = strToToHexByte(textBoxDataRes.Text); textBoxDataRes.Clear(); textBoxDataRes.BeginInvoke(showReMsgSerialDelegate, by); } catch (Exception ex) { //MessageBox.Show(ex.ToString()); } }
其實就一句話..........................
/// <字符串轉換成字節數組> /// /// </summary> /// <param name="stringToConvert"></param> /// <returns></returns> public static byte[] StringToByte(string stringToConvert) { return (new ASCIIEncoding()).GetBytes(stringToConvert); }
/// <字符串轉16進制格式,不夠自動前面補零(每兩位組成一個16進制數)> /// /// </summary> /// <param name="hexString"></param> /// <returns></returns> private static byte[] strToToHexByte(String hexString) { int i; bool Flag = false; hexString = hexString.Replace(" ", "");//清除空格 if ((hexString.Length % 2) != 0) { Flag = true; } if (Flag == true) { byte[] returnBytes = new byte[(hexString.Length + 1) / 2]; try { for (i = 0; i < (hexString.Length - 1) / 2; i++) { returnBytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16); } returnBytes[returnBytes.Length - 1] = Convert.ToByte(hexString.Substring(hexString.Length - 1, 1).PadLeft(2, '0'), 16); } catch { for (i = 0; i < returnBytes.Length; i++) { returnBytes[i] = 0; } MessageBox.Show("超過16進制范圍A-F,已初始化為0", "提示"); } return returnBytes; } else { byte[] returnBytes = new byte[(hexString.Length) / 2]; try { for (i = 0; i < returnBytes.Length; i++) { returnBytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16); } } catch { for (i = 0; i < returnBytes.Length; i++) { returnBytes[i] = 0; } MessageBox.Show("超過16進制范圍A-F,已初始化為0", "提示"); } return returnBytes; } }
看效果
加一個功能,,,我已經電機打開一個串口了,然后呢想換一個
然而如果和第一次選擇的一樣就不切換了
寫上以下代碼
private void comboBoxCom_DropDownClosed(object sender, EventArgs e) { try { if (CopyPortName != comboBoxCom.SelectedItem.ToString())//與當前的不同才切換 { if (serialPort1.IsOpen) { serialPort1.Close(); serialPort1.PortName = comboBoxCom.SelectedItem.ToString(); serialPort1.BaudRate = Convert.ToInt32(comboBoxBaud.Text); serialPort1.Open();
CopyPortName = serialPort1.PortName; } } } catch (Exception)//切換出現錯誤執行這里 { OpenFlage = false; buttonOpen.Invoke(buttonConnectDelegate, "打開串口"); MessageBox.Show("端口錯誤,請檢查串口", "提示"); } }
然后呢波特率也是如此
不過呢有點不同
不用關閉串口....
private void comboBoxBaud_DropDownClosed(object sender, EventArgs e) { try { if (CopyBaud != Convert.ToInt32(comboBoxBaud.SelectedItem.ToString()))//與當前的不同才切換 { serialPort1.BaudRate = Convert.ToInt32(comboBoxBaud.SelectedItem.ToString()); CopyBaud = serialPort1.BaudRate; } } catch (Exception)//切換出現錯誤執行這里 { OpenFlage = false; buttonOpen.Invoke(buttonConnectDelegate, "打開串口"); MessageBox.Show("端口錯誤,請檢查串口", "提示"); } }
干脆再便捷點....一啟動軟件就自動連接第一個串口號
private void InitConnect() { string[] ComName = SerialPort.GetPortNames();//把可用的串口號存入comname comboBoxCom.Items.AddRange(ComName);//添加到下拉框 comboBoxCom.SelectedIndex = comboBoxCom.Items.Count > 0 ? 0 : -1;//顯示第一個 if (comboBoxCom.SelectedIndex != -1) { try { serialPort1.PortName = comboBoxCom.Text; serialPort1.BaudRate = Convert.ToInt32(comboBoxBaud.Text); serialPort1.Open(); OpenFlage = true; CopyPortName = serialPort1.PortName;//記錄COM口號 CopyBaud = serialPort1.BaudRate;//記錄波特率 buttonOpen.Invoke(buttonConnectDelegate, "關閉串口"); } catch (Exception)//其余意外情況執行這里 { OpenFlage = false; buttonOpen.Invoke(buttonConnectDelegate, "打開串口"); MessageBox.Show("端口錯誤,請檢查串口", "提示"); } } }
private void Form1_Load(object sender, EventArgs e) { buttonConnectDelegate = new ButtonConnectDelegate(buttonConnectMethod);//實例化 showReMsgSerialDelegate = new ShowReMsgSerialDelegate(ShowReMsgMethod);//實例化 InitConnect(); }
再便捷一點,讓軟件打開一個能用的串口號
private void InitConnect() { string[] ComName = SerialPort.GetPortNames();//把可用的串口號存入comname comboBoxCom.Items.AddRange(ComName);//添加到下拉框 comboBoxCom.SelectedIndex = comboBoxCom.Items.Count > 0 ? 0 : -1;//顯示第一個 if (comboBoxCom.SelectedIndex != -1) { for (int i = 0; i < comboBoxCom.Items.Count; i++) { try { serialPort1.PortName = comboBoxCom.SelectedIndex.ToString(); serialPort1.PortName = comboBoxCom.Text; serialPort1.BaudRate = Convert.ToInt32(comboBoxBaud.Text); serialPort1.Open(); OpenFlage = true; CopyPortName = serialPort1.PortName;//記錄COM口號 CopyBaud = serialPort1.BaudRate;//記錄波特率 buttonOpen.Invoke(buttonConnectDelegate, "關閉串口"); break; } catch (Exception)//其余意外情況執行這里 { OpenFlage = false; buttonOpen.Invoke(buttonConnectDelegate, "打開串口"); if (comboBoxCom.SelectedIndex < comboBoxCom.Items.Count - 1) { comboBoxCom.SelectedIndex++; } //MessageBox.Show("端口錯誤,請檢查串口", "提示"); } } } }
再優化點,,就是軟件關閉的時候釋放用到的資源
private void Form1_FormClosed(object sender, FormClosedEventArgs e) { try { serialPort1.Dispose(); } catch (Exception) { } }
好,現在做發送部分
/// <發送數據按鈕事件> /// /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void buttonSend_Click(object sender, EventArgs e) { if (!checkBoxHexSend.Checked)//字符發送 { byte[] sendbyte = Encoding.Default.GetBytes(textBoxSend.Text); try { serialPort1.Write(sendbyte, 0, sendbyte.Length); } catch (Exception) { MessageBox.Show("請檢查串口", "提示!"); } } else//16形式進制發送 { byte[] sendbyte = strToToHexByte(textBoxSend.Text); try { serialPort1.Write(sendbyte, 0, sendbyte.Length); } catch (Exception) { MessageBox.Show("請檢查串口", "提示!"); } } }
/// <顯示串口發送的信息> /// /// </summary> /// <param name="by"></param> private void ShowSeMsgMethod(byte[] by) { string getMsg = string.Empty; if (checkBoxHexSend.Checked)//16進制發送 { getMsg = byteToHexStr(by); //用到函數byteToHexStr } else { getMsg = new ASCIIEncoding().GetString(by); } textBoxSend.AppendText(getMsg); }
其實和接收數據的文本框一樣的處理
private void checkBoxHexSend_CheckedChanged(object sender, EventArgs e) { if (checkBoxHexSend.Checked) { try { byte[] by = StringToByte(textBoxSend.Text); textBoxSend.Clear(); textBoxSend.BeginInvoke(showSeMsgSerialDelegate, by); } catch (Exception) { //MessageBox.Show(ex.ToString()); } } else { try { byte[] by = strToToHexByte(textBoxSend.Text); textBoxSend.Clear(); textBoxSend.BeginInvoke(showSeMsgSerialDelegate, by); } catch (Exception) { //MessageBox.Show(ex.ToString()); } } }
再加一項功能,,就是說在串口意外斷開的時候能夠檢測出來
加入下面這個函數
/// <檢測串口是否斷開> /// /// </summary> /// <param name="m"></param> protected override void WndProc(ref Message m) { if (m.Msg == 0x0219) { if (m.WParam.ToInt32() == 0x8004) { if (OpenFlage == true)//確定串口一開始是打開的 { if (!serialPort1.IsOpen)//是當前串口意外關閉 { OpenFlage = false; try { buttonOpen.Invoke(buttonConnectDelegate, "打開串口"); /*重新添加一下串口號*/ string[] ComName = SerialPort.GetPortNames();//把可用的串口號存入comname comboBoxCom.Items.Clear();//先清除一下,防止重復添加 comboBoxCom.Items.AddRange(ComName);//添加到下拉框 comboBoxCom.SelectedIndex = comboBoxCom.Items.Count > 0 ? 0 : -1;//顯示第一個 serialPort1.Dispose();//釋放資源 } catch (Exception) { } } } } } base.WndProc(ref m); }
到這里只是做了一個串口助手
其余的呢就簡單了
看現在的界面
對了我規定了協議,,第一個字節代表命令,01代表后面是漢字數據,02代表正弦波數據,03矩形波數據,,04三角波數據
數據的最后兩位是CRC16校驗
顯示漢字部分
/// <發送顯示的漢字> /// /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void buttonSendChinese_Click(object sender, EventArgs e) { byte[] sendby = Encoding.Default.GetBytes(textBoxChinese.Text.ToString()); byte[] sendbyte = new byte[sendby.Length + 1]; sendbyte[0] = 0x01; for (int i = 0; i < sendby.Length; i++) { sendbyte[i+1] = sendby[i]; } SerialSend(sendbyte); }
/// <串口發送數據函數> /// /// </summary> /// <param name="sendbyte"></param> private void SerialSend(byte[] byt) { int crc = 0; byte[] sendbyte = new byte[byt.Length + 2]; for (int i = 0; i < byt.Length;i++ ) { sendbyte[i] = byt[i]; } crc = crc16_modbus(byt, byt.Length);//計算CRC byte[] Crcbyte = System.BitConverter.GetBytes(crc);//得到CRC sendbyte[sendbyte.Length - 2] = Crcbyte[0]; sendbyte[sendbyte.Length - 1] = Crcbyte[1]; try { serialPort1.Write(sendbyte, 0, sendbyte.Length); } catch (Exception) { MessageBox.Show("請檢查串口", "提示!"); } }
正弦波以及其余波形的方案
byte[] sendbyte = new byte[3]; if(trackBarSinFlage == 1)//正弦波 { trackBarSinCnt++; if (trackBarSinCnt>=5) { trackBarSinFlage = 0; trackBarSinCnt = 0; sendbyte[0] = 0x02; sendbyte[1] = Convert.ToByte(trackBarSinF.Value);//正弦波F sendbyte[2] = Convert.ToByte(trackBarSinH.Value);//正弦波H SerialSend(sendbyte); } }
這段代碼放在了定時器2里面,,,我這樣做的,只要拖動滑塊后500Ms沒在改變滑塊的值,那么就把當前滑塊的值發給單片機,讓單片機顯示出來
我沒有做成一直發給單片機的,,因為12864本身刷新整個界面就慢,,一直發也沒什么用.............
其余的親們看源碼吧!
現在做做下位機--單片機程序
由於單片機程序太多了,所以就事先做好了底層的了,,,就先看一看
直接貼上來把

#define _12864_C_ #include "include.h" #include "12864.h" /** * @brief 延時us函數 * @param Time 延時微秒數 * @param None * @param None * @retval None * @example **/ void DelayUs(int Time) //誤差 -0.173611111111us { while(Time --) { _nop_(); } } void Init12864() { WriteCom(0x30);// 基本指令(DL=1) WriteCom(0x30);// 基本指令(RE=0) WriteCom(0x0C);// 打開整體顯示(不顯示光標) WriteCom(0x01);// RAM地址歸零 DelayUs(2000); WriteCom(0x06);// 游標自動加一 } void CRAM_OFF()//關閉顯示 { WriteCom(0x30); //DL=1:8-BIT interface WriteCom(0x30); //RE=0:basic instruction WriteCom(0x08); //Display OFF,Cursor OFF,Cursor position blink OFF WriteCom(0x01); //CLEAR DelayUs(2000); } void CRAM_ON()//打開顯示 { WriteCom(0x30);// 基本指令(DL=1) WriteCom(0x30);// 基本指令(RE=0) WriteCom(0x0C);// 打開整體顯示(不顯示光標) } /** * @brief 向12864內寫入數據 * @param Data 數據 * @param None * @param None * @retval None * @example **/ void WriteData(char Data) { RS = 1;//數據 RW = 0;//寫入 E = 0;//使能拉低 DelayUs(1); Port = Data; DelayUs(1); E = 1; DelayUs(1); E = 0; DelayUs(80); } /** * @brief 向12864內寫入命令 * @param Com 命令 * @param None * @param None * @retval None * @example **/ void WriteCom(char Com) { E = 0;//使能拉低 RS = 0;//命令 RW = 0;//寫入 DelayUs(1); Port = Com; DelayUs(1); E = 1; DelayUs(1); E = 0; DelayUs(80); } /** * @brief 從12864內讀出數據 * @param None * @param None * @param None * @retval None * @example 讀出的數據 **/ char ReadData() { char Data; Port = 0xff; RS = 1; //數據 RW = 1; //讀取 E = 1; Data=Port;//讀取數據 E = 0; DelayUs(80); return(Data); } /** * @brief 顯示圖片 * @param char *img * @param None * @param None * @retval None * @example **/ void DisplayImage(char *img)//橫向取膜 { char i,j; WriteCom(0x36); //圖形方式 for(i=0;i<32;i++) { WriteCom(0x80+i); WriteCom(0x80); for(j=0;j<16;j++) { WriteData(*img++); } } for(i=0;i<32;i++) { WriteCom(0x80+i); WriteCom(0x88); for(j=0;j<16;j++) { WriteData(*img++); } } } /** * @brief 在指定位置畫一個點 * @param char x,char y, char Flage * @param None * @param None * @retval None * @example **/ void DrawPoint(char x,char y, char Flage) { char x_dyte,x_byte; //定義列地址的字節位,及在字節中的哪1位 char y_dyte,y_byte; //定義為上下兩個屏(取值為0,1),行地址(取值為0~31) char GDRAM_hbit,GDRAM_lbit; WriteCom(0x36); //繪圖模式命令 /***X,Y坐標互換,即普通的X,Y坐標***/ x_dyte=y/16; //計算在16個字節中的哪一個 x_byte=y&0x0f; //計算在該字節中的哪一位 y_dyte=x/32; //0為上半屏,1為下半屏 y_byte=x&0x1f; //計算在0~31當中的哪一行 WriteCom(0x80+y_byte); //設定行地址(y坐標) WriteCom(0x80+x_dyte+8*y_dyte); //設定列地址(x坐標),並通過8*y_Dyte選定上下屏 DelayUs(1); ReadData(); GDRAM_hbit=ReadData();//讀取當前顯示高8位數據 GDRAM_lbit=ReadData();//讀取當前顯示低8位數據 if(Flage == 1) { WriteCom(0x80+y_byte); //設定行地址(y坐標) WriteCom(0x80+x_dyte+8*y_dyte); //設定列地址(x坐標),並通過8*y_Dyte選定上下屏 DelayUs(1); if(x_byte<8) //判斷其在高8位,還是在低8位 { WriteData(GDRAM_hbit|(0X01<<(7-x_byte))); //顯示GDRAM區高8位數據 WriteData(GDRAM_lbit); //顯示GDRAM區低8位數據 } else { WriteData(GDRAM_hbit); WriteData(GDRAM_lbit|(0x01<<(15-x_byte))); } } else { WriteData((0x00)); //清除GDRAM區高8位數據 WriteData((0x00)); //清除GDRAM區低8位數據 } } /** * @brief 八點畫圓 * @param char x,char y,char xc,char yc * @param None * @param None * @retval None * @example **/ void plotC(char x,char y,char xc,char yc) { DrawPoint(xc+x,yc+y,1); DrawPoint(xc+x,yc-y,1); DrawPoint(xc-x,yc+y,1); DrawPoint(xc-x,yc-y,1); DrawPoint(xc+y,yc+x,1); DrawPoint(xc+y,yc-x,1); DrawPoint(xc-y,yc+x,1); DrawPoint(xc-y,yc-x,1); } /** * @brief 在指定位置畫一個半徑為R的圓 * @param char x0,char y0, char r * @param None * @param None * @retval None * @example **/ void DrawCircle(char xc,char yc, char r) { char x,y,d; y=r; d=3-(r<<1); x=0; while(x<=y) { plotC(x,y,xc,yc); if(d < 0) { d+=(x<<2)+6; } else { d+=((x-y)<<2)+10; y=y-1; } x=x+1; } } /** * @brief 顯示漢字 * @param x:行號, y:列號, k:個數, *p:數據 * @param None * @param None * @retval None * @example **/ void Chinese(char x,char y,char k,char *p) { char hang=0,out=0,i=0; y=y-1; switch(x) { case 1:hang=0x80;break; case 2:hang=0x90;break; case 3:hang=0x88;break; case 4:hang=0x98;break; } out=hang+y; WriteCom(out); for(i=0;i<k*2;i++) { switch(i) { case 16:WriteCom(0x90);break; case 32:WriteCom(0x88);break; case 48:WriteCom(0x98);break; } WriteData(*p); p++; } } /** * @brief 清除液晶GDRAM中的隨機數據 * @param None * @param None * @param None * @retval None * @example **/ void ClearGDRAM(void) { char i,j,k; WriteCom(0x34); //打開擴展指令集 i=0x80; for(j=0;j<32;j++) { WriteCom(i++); WriteCom(0x80); for(k=0;k<16;k++) { WriteData(0x00); } } i=0x80; for(j=0;j<32;j++) { WriteCom(i++); WriteCom(0x88); for(k=0;k<16;k++) { WriteData(0x00); } } WriteCom(0x30); //回到基本指令集 } /** * @brief * @param None * @param None * @param None * @retval None * @example **/ void ClearDDRAM() { WriteCom(0x30); //DL=1:8-BIT interface WriteCom(0x30); //RE=0:basic instruction WriteCom(0x0C); //Display ON,Cursor OFF,Cursor position blink OFF WriteCom(0x01); //CLEAR DelayUs(5000); } /** * @brief 正弦波 * @param None * @param None * @param None * @retval None * @example **/ void fsin(char f,char h) { char i,j; for(i=0;i<127;i++) { j=32-h*sin(i*3.14/f); DrawPoint(j,i,1); } } /** * @brief 矩形波 * @param None * @param None * @param None * @retval None * @example **/ void RecWave(char f,char h) { char i,j,flag=0; for(i=0;i<127;i++) { if(f <= 0) break; if(h >= 32) break; if(i%f==0) { for(j=32-h;j<=32+h;j++) DrawPoint(j,i,1); if(flag==0) flag=1; else flag=0; } else { if(flag==0) j=32-h; else j=32+h; DrawPoint(j,i,1); } } } /** * @brief 畫一條線 * @param int x0, int y0,起點 * @param int x1, int y1,終點 * @param None * @retval None * @example **/ void DrawLine(char x0, char y0, char x1, char y1) { char x,y; char dx;// = abs(x1 - x0); char dy;// = abs(y1 - y0); if(y0==y1) { if(x0<=x1) { x=x0; } else { x=x1; x1=x0; } while(x <= x1) { DrawPoint(x,y0,1); x++; } return; } else if(y0>y1) { dy=y0-y1; } else { dy=y1-y0; } if(x0==x1) { if(y0<=y1) { y=y0; } else { y=y1; y1=y0; } while(y <= y1) { DrawPoint(x0,y,1); y++; } return; } else if(x0 > x1) { dx=x0-x1; x = x1; x1 = x0; y = y1; y1 = y0; } else { dx=x1-x0; x = x0; y = y0; } if(dx == dy) { while(x < x1) { x++; if(y>y1) { y--; } else { y++; } DrawPoint(x,y,1); } } else { DrawPoint(x, y,1); if(y < y1) { if(dx > dy) { char p = dy * 2 - dx; char twoDy = 2 * dy; char twoDyMinusDx = 2 * (dy - dx); while(x < x1) { x++; if(p < 0) { p += twoDy; } else { y++; p += twoDyMinusDx; } DrawPoint(x, y,1); } } else { char p = dx * 2 - dy; char twoDx = 2 * dx; char twoDxMinusDy = 2 * (dx - dy); while(y < y1) { y++; if(p < 0) { p += twoDx; } else { x++; p+= twoDxMinusDy; } DrawPoint(x, y,1); } } } else { if(dx > dy) { char p = dy * 2 - dx; char twoDy = 2 * dy; char twoDyMinusDx = 2 * (dy - dx); while(x < x1) { x++; if(p < 0) { p += twoDy; } else { y--; p += twoDyMinusDx; } DrawPoint(x, y,1); } } else { char p = dx * 2 - dy; char twoDx = 2 * dx; char twoDxMinusDy = 2 * (dx - dy); while(y1 < y) { y--; if(p < 0) { p += twoDx; } else { x++; p+= twoDxMinusDy; } DrawPoint(x, y,1); } } } } } /** * @brief 顯示三角波 * @param char f,char h,頻率,幅值 * @param None * @param None * @retval None * @example **/ void TriWave(char f,char h)//顯示三角波 { char i,j=0,flag=0; char x1,x2; for(i=0;i<127;i++) { if(i%f==0) { if(flag==0) { x1 = i; flag=1; j++; } else { x2 = i; flag=0; } if(flag == 1) { if(j>=2) { DrawLine(32+h,x2,32-h,x1); } } else { DrawLine(32-h,x1,32+h,x2); } } } }

#ifndef __12864_H_ #define __12864_H_ #include <REGX52.H> #ifndef _12864_C_ #define _12864_C_ extern #else #define _12864_C_ #endif sbit RS = P3^7;//數據\命令選擇 sbit RW = P3^6;//讀\寫 sbit E = P3^5;//使能 sfr Port = 0xA0; void DelayUs(int Time); void Init12864(); void WriteData(char Data); void WriteCom(char Com); char ReadData(); void DisplayImage(char *img); void CRAM_OFF(); void CRAM_ON(); void DrawPoint(char x,char y, char Flage); void DrawCircle(char x0,char y0, char r); void Chinese(char x,char y,char k,char *p); void ClearGDRAM(void); void ClearDDRAM(); void fsin(char f,char h); void RecWave(char f,char h);//顯示矩形波 void DrawLine(char x0, char y0, char x1, char y1); void TriWave(char f,char h);//顯示三角波 #endif

#define _USART_C_ #include "include.h" #include "usart.h" bit UsartFlage = 0; char UsartReadCnt = 0; char UsartReadCntCopy = 0; char UsartReceive[50] = {0}; void InitUART(long Baud) { if(Baud == 115200) { SCON=0x50; //串口工作方式1,8位UART,波特率可變 TH2=0xFF; TL2=0xFD; //波特率:115200 晶振=11.0592MHz RCAP2H=0xFF; RCAP2L=0xFD; //16位自動再裝入值 /*****************/ TCLK=1; RCLK=1; C_T2=0; EXEN2=0; //波特率發生器工作方式 /*****************/ TR2=1 ; //定時器2開始 } else { TMOD |= 0x20; SCON = 0x50; switch(Baud) { case 2400 :TH1 = 0xF4;TL1 = TH1;PCON = 0x00;break; case 4800 :TH1 = 0xFA;TL1 = TH1;PCON = 0x00;break; case 9600 :TH1 = 0xFD;TL1 = TH1;PCON = 0x00;break; case 14400 :TH1 = 0xFE;TL1 = TH1;PCON = 0x00;break; case 19200 :TH1 = 0xFD;TL1 = TH1;PCON = 0x80;break; default : TH1 = 0xFD;TL1 = TH1;PCON = 0x00;break; } EA = 1; ES = 1; TR1 = 1; } } void UartSend(unsigned char value) { ES=0; //關閉串口中斷 TI=0; //清發送完畢中斷請求標志位 SBUF=value; //發送 while(TI==0); //等待發送完畢 TI=0; //清發送完畢中斷請求標志位 ES=1; //允許串口中斷 } void UARTInterrupt(void) interrupt 4 { if(RI) { RI=0; UsartReceive[UsartReadCnt]=SBUF;//接收串口數據 UsartReadCnt++; } }

#ifndef __USART_H_ #define __USART_H_ #ifndef _USART_C_ #define _USART_C_ extern #else #define _USART_C_ #endif _USART_C_ bit UsartFlage; _USART_C_ char UsartReadCnt; _USART_C_ char UsartReceive[50]; _USART_C_ char UsartReadCntCopy; void InitUART(long Baud); void UartSend(unsigned char value); #endif

#define _TIME_C_ #include "include.h" #include "time.h" int UsartIdleCnt =0 ; int TimeCnt = 0; int TimeDelay = 0; void DelayS(int s) { TimeCnt = 0; TimeDelay = s; while(TimeDelay>0); } //定時器初始化 void InitTimer0(void) { TMOD |= 0x01; TH0 = (65536 - 5000)/256; TL0 = (65536 - 5000)%256; EA = 1; ET0 = 1; TR0 = 1; } void Timer0Interrupt(void) interrupt 1 { TH0 = (65536 - 5000)/256; TL0 = (65536 - 5000)%256; TimeCnt ++; if(TimeCnt >= 200) { TimeCnt = 0; TimeDelay --; } if (UsartReadCnt != 0)//如果接收到數據了 { if (UsartIdleCnt == UsartReadCnt)//1ms時間數據沒了變化 { UsartReadCntCopy = UsartReadCnt; UsartReadCnt = 0;//清零數據個數 UsartIdleCnt = 0;//清零 UsartFlage = 1; } else { UsartIdleCnt = UsartReadCnt; } } }

#ifndef __TIME_H_ #define __TIME_H_ #ifndef _TIME_C_ #define _TIME_C_ extern #else #define _TIME_C_ #endif void InitTimer0(void); void DelayS(int s); #endif
算了剩下的不貼了,反正后面有源碼.......
說幾個地方吧
程序風格呢,還是自己習慣的風格.....
串口接收和上位機一樣的道理
在定時器里面做的判斷是否接收到一個完整的數據
串口的配置呢加入了115200的,因為印象深刻......
void InitUART(long Baud) { if(Baud == 115200) { SCON=0x50; //串口工作方式1,8位UART,波特率可變 TH2=0xFF; TL2=0xFD; //波特率:115200 晶振=11.0592MHz RCAP2H=0xFF; RCAP2L=0xFD; //16位自動再裝入值 /*****************/ TCLK=1; RCLK=1; C_T2=0; EXEN2=0; //波特率發生器工作方式 /*****************/ TR2=1 ; //定時器2開始 } else { TMOD |= 0x20; SCON = 0x50; switch(Baud) { case 2400 :TH1 = 0xF4;TL1 = TH1;PCON = 0x00;break; case 4800 :TH1 = 0xFA;TL1 = TH1;PCON = 0x00;break; case 9600 :TH1 = 0xFD;TL1 = TH1;PCON = 0x00;break; case 14400 :TH1 = 0xFE;TL1 = TH1;PCON = 0x00;break; case 19200 :TH1 = 0xFD;TL1 = TH1;PCON = 0x80;break; default : TH1 = 0xFD;TL1 = TH1;PCON = 0x00;break; } EA = 1; ES = 1; TR1 = 1; } }
這個控制顯示正弦波的函數 h呢很容易看出來是控制這個波形的高度,,,,,那個3.14和f共同決定了周期(其實就是點數),,f越大這個函數的圖像越拉伸,,,,,
void TriWave(char f,char h)//顯示三角波 { char i,j=0,flag=0; char x1,x2; for(i=0;i<127;i++) { if(i%f==0) { if(flag==0) { x1 = i; flag=1; j++; } else { x2 = i; flag=0; } if(flag == 1) { if(j>=2) { DrawLine(32+h,x2,32-h,x1); } } else { DrawLine(32-h,x1,32+h,x2); } } } }
這個三角波函數是當初自己造的......其實就是畫線.....
上面的 f 很容易看出來就是控制拐點的,,每隔 f 個點拐一下,
x1 和 x2是記錄當前的 i 的值,關於那個 j 是由於 i 是從 0 開始的 如果不限制一下,那么第一根先就會是這樣
最后看一下主函數
#define _MAIN_C_ #include "include.h" #include "main.h" void main() { unsigned int CRC=0; InitTimer0();//初始化定時器 InitUART(9600);//初始化串口 Init12864();//初始化12864 CRAM_OFF();//關閉顯示 DisplayImage(Image);//顯示圖片 CRAM_ON();//打開顯示 DelayS(1); ClearGDRAM();//清除界面 Init12864();//初始化12864 for(CRC = 17;CRC<127;CRC+=23) { DrawCircle(32,CRC, 16);//畫5個圓 } while(1) { if(UsartFlage == 1) { UsartFlage = 0; if(crc16_flage(UsartReceive,UsartReadCntCopy-2))//判斷CRC正確與否 { ClearGDRAM();//清除界面 Init12864();//初始化12864 switch(UsartReceive[0]) { case 1 : Chinese(1,1,(UsartReadCntCopy-3)/2,&UsartReceive[1]); break;//顯示漢字 case 2 : fsin(UsartReceive[1],UsartReceive[2]); break;//顯示正弦波 case 3 : RecWave(UsartReceive[1],UsartReceive[2]); break;//顯示鋸齒波 case 4 : TriWave(UsartReceive[1],UsartReceive[2]); break;//顯示三角波 default : break; } } } } }
主函數呢,沒什么說的....
源碼地址
鏈接:http://pan.baidu.com/s/1miiLiGC%20密碼:ix66
實物鏈接
https://item.taobao.com/item.htm?id=556782600668
關於為什么要有實物了,,因為確實有人用到實物,,,,能滿足的就一定要滿足,而且好多元器件放着就浪費了.....
記得當初一個朋友學8266,竟然用了1個多月才能正常通信,,,那時候其實就想着應該做一個實物供朋友使用,這樣的話就不能耽擱這么長時間了...
想想這都過去5個多月了,,我還沒有去做8266的實驗板......哎,,,,,,,感覺太懶了