实现的效果
上面是用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本身刷新整个界面就慢,,一直发也没什么用.............
其余的亲们看源码吧!
现在做做下位机--单片机程序
由于单片机程序太多了,所以就事先做好了底层的了,,,就先看一看
直接贴上来把
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#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); } } } }
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#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
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#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++; } }
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#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
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#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; } } }
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#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的实验板......哎,,,,,,,感觉太懒了