实现的效果





上面是用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);
}
}
}
}
View Code



#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
View Code



#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++;
}
}
View Code



#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
View Code



#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;
}
}
}
View Code



#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
View Code
算了剩下的不贴了,反正后面有源码.......
说几个地方吧
程序风格呢,还是自己习惯的风格.....
串口接收和上位机一样的道理
在定时器里面做的判断是否接收到一个完整的数据

串口的配置呢加入了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的实验板......哎,,,,,,,感觉太懒了