先上图
如图所示:使用12c052AD,晶振使用11.0592M,晶振电容使用20pf,使用USB使串口与电脑连接,LED灯正极接P1,加限流电阻注意:使用12M的晶振会出现大概率的乱码。也不能使用单片机的内部晶振。
实现功能:通过USB转串口与电脑进行串口通讯,实现上位机控制LED的功能。
1.单片机内烧入程序
#include
#include
#define uc unsigned char
#define uint unsigned int
sbit LED=P3^7;
uc sChar[50]={'\0'};
unsigned char sChar_i=0;//写入数据sChar指针
unsigned char sendFlag = 0; //未发送数据时
unsigned char receFlag =0; //未接受到数据时
void sendChar(unsigned char sendValue); //发送一字节数据
void sendAll(unsigned char *pValue); //发送一组数据
void initSer()//初始化
{
TMOD=0x20; //定时器工作方式,选择了定时器1,工作方式2 八位初值自动重装的8位定时器。
TH1=0xfd; //定时器1初值 ,设置波特率为9600 配合为晶振11.0529MHZ,晶振的电容是20uf?
TL1=0xfd;
TR1=1; //开启定时器1
SM0=0; //属于SCON寄存器
SM1=1; //串口工作方式1,10位异步接收,(8位数据)波特率可变
REN=1; //允许串行口接收位
EA=1; //允许中断(总闸)
ES=1; //允许串口中断
}
void main()
{
initSer();
while(1)
{
if(receFlag)
{
LED=~LED;
sChar_i=0;//接受数组指针归0,以便以下次接受
sendAll(sChar);
if(strlen(sChar)==4&&sChar[0]=='L'&&sChar[1]=='E'&&sChar[2]=='D')
{
P1=sChar[3];
}
receFlag=0;
}
if(sendFlag) //发送完毕之后,在电脑端输出。
{
TI=1; //printf之前必须将T1置为1才行。
while(!TI);
sendFlag=0;
}
}
}
void sendChar(unsigned char Value) //发送一个字节数据
{
SBUF = Value;
sendFlag = 1; //设置发送标志位,发一字节就置位
while(sendFlag); //直到发完数据,将sendFlag清零后,才退出sendChar函数
}
void sendAll(unsigned char *pValue) //发送一组数据
{
while((*pValue) != '\0') //如果没有发送完毕就继续发
{
sendChar(*pValue); //发送1字节数据
pValue++; //指向下1个字节
}
}
void serInt() interrupt 4 //中断函数
{
//TI——发送中断bai标志位,可寻址标志位。方式0时,发送完第8位数据后,由硬件置位,其它方式下,在发送或停止位之前由硬件置位,因此,TI=1表示帧发送结束,TI可由软件清“0”。
//RI——接收中断标志位.可寻址标志位。接收完第8位数据后,该位由硬件置位,在其他工作方式下,该位由硬件置位,RI=1表示帧接收完成。
//在串口中断处理时,TI,RI都需要软件清"0",硬件置位后不可能自动清0,此外,在进行缓冲区操作时,需要ES=0,以防止中断出现。
if(RI) //接收数据,手动将RI清0
{
RI=0;
if(SBUF=='\0'||SBUF=='#')
{
sChar[sChar_i++]='\0';
receFlag=1; //修改接受标志,便于主函数进入while中发数据
}
else
{
sChar[sChar_i++]=SBUF; //每次接受8位,存在SBUF里,转存到数组中
}
}
if(TI) //发送数据
{
TI = 0; //发送完一个数据
sendFlag = 0; //清标志位
}
}
2.使用Visual Studio制作上位机程序:
软件界面如下:
双击标题栏进入程序编辑界面。代码如下:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace 串口通讯
{
public partial class Form1 : Form
{
string PortNameCopy;//记录打开哪个端口
byte[] UsartReadBuff=new byte[2048];//串口接受的数据
int UsartReadCnt = 0;//串口接收到的数据个数
public Form1()
{
InitializeComponent();
}
/// <字节数组转16进制字符串>
///
/// String 16进制显示形式
public static string byteToHexStr(byte[] bytes)
{
string returnStr = "";
try
{
if (bytes != null)
{
for (int i = 0; i < bytes.Length; i++)
{
returnStr += bytes[i].ToString("X2");
returnStr += " ";//两个16进制用空格隔开,方便看数据
}
}
return returnStr;
}
catch (Exception)
{
return returnStr;
}
}
private void Form1_Load(object sender, EventArgs e)
{
string[] ports = System.IO.Ports.SerialPort.GetPortNames();//获取电脑上可用的串口号
comboBox_ckxz.Items.AddRange(ports);//给combox_ckxz添加可用串口号
comboBox_ckxz.SelectedIndex = comboBox_ckxz.Items.Count > 0 ? 0 : -1;//如果有可用数据,显示第一个
PortNameCopy = comboBox_ckxz.SelectedValue==null?"": comboBox_ckxz.SelectedValue.ToString();
}
private void button_ljzt_Click(object sender, EventArgs e)
{
if (button_ljzt.Text == "连接")//如果按钮显示的是打开
{
try//防止意外错误
{
serialPort_ckxz.PortName = comboBox_ckxz.Text;//得到comboBox_ckxz显示的串口内容
serialPort_ckxz.BaudRate = Convert.ToInt32(comboBox_btl.Text);//得到comboBox2显示的波特率内容
serialPort_ckxz.Open();//打开串口
serialPort_ckxz.DataReceived += serialPort_ckxz_DataReceived;//注册传回事件
button_ljzt.Text = "断开";//按钮显示断开
}
catch (Exception ex)
{
MessageBox.Show("打开失败, 提示!"+ ex.Message);//对话框显示打开失败
}
}
else//要关闭串口
{
try//预防串口有问题了,实际上已经关了
{
serialPort_ckxz.Close();//关闭串口
}
catch (Exception ex)
{
MessageBox.Show("关闭串口时发生错误" + ex.Message);//对话框显示打开失败
}
button_ljzt.Text = "连接";//按钮显示连接
}
}
///
/// 支持热插拔
///
///
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x0219)//设备改变
{
if (m.WParam.ToInt32() == 0x8004)//usb串口拔出
{
string[] ports = System.IO.Ports.SerialPort.GetPortNames();//重新获取串口
comboBox_ckxz.Items.Clear();
comboBox_ckxz.Items.AddRange(ports);
if (button_ljzt.Text == "断开")//咱打开过一个串口
{
if (!serialPort_ckxz.IsOpen)//咱打开的那个关闭了,说明拔插的是咱打开的
{
button_ljzt.Text = "连接";
serialPort_ckxz.Dispose();//释放掉原先的串口资源
comboBox_ckxz.SelectedIndex = comboBox_ckxz.Items.Count > 0 ? 0 : -1;//显示获取的第一个串口号
}
else//热插拔不是咱打开的那个
{
comboBox_ckxz.Text = PortNameCopy;//默认显示的是咱打开的那个串口号
}
}
else//没有打开过
{
comboBox_ckxz.SelectedIndex = comboBox_ckxz.Items.Count > 0 ? 0 : -1;//显示获取的第一个串口号
}
}
else if (m.WParam.ToInt32() == 0x8000)//usb串口连接上
{
string[] ports = System.IO.Ports.SerialPort.GetPortNames();//重新获取串口
comboBox_ckxz.Items.Clear();
comboBox_ckxz.Items.AddRange(ports);
if (button_ljzt.Text == "断开")//咱打开过一个串口
{
comboBox_ckxz.Text = PortNameCopy;//默认显示的是咱打开的那个串口号
}
else
{
comboBox_ckxz.SelectedIndex = comboBox_ckxz.Items.Count > 0 ? 0 : -1;//显示获取的第一个串口号
}
}
}
base.WndProc(ref m);
}
///
/// LED控制
///
///
///
private void button_dd_Click(object sender, EventArgs e)
{
try
{
if(button_dd.Text=="亮灯")
{
byte[] sendbyte = new byte[3];
sendbyte[0] = 0xaa;
sendbyte[1] = 0x55;
sendbyte[2] = 0x01;
serialPort_ckxz.Write(sendbyte, 0, sendbyte.Length);
}
else if(button_dd.Text=="熄灯")
{
byte[] sendbyte = new byte[3];
sendbyte[0] = 0xaa;
sendbyte[1] = 0x55;
sendbyte[2] = 0x00;
serialPort_ckxz.Write(sendbyte, 0, sendbyte.Length);
}
}
catch (Exception ex)
{
MessageBox.Show("控制LED时发生错误" + ex.Message);//对话框显示打开失败
}
}
///
/// 串口接收到数据就会进入,需要在激活串口时,注册回传事件 serialPort_ckxz.DataReceived += serialPort_ckxz_DataReceived;,serialPort的DtrEnable,RtsEnable设置为true
/// 数据接收可能会中断,主要原因是没bai有接受到一个完整du数据周期,例如你下位机是一个单bai片机 单片机发送 01 02 03 04 05 06 07 08 09 是一个完整du数据周期,但是DataReceived事件可能在一次数据接收中只接受到了 01 02 03 04 05 06 因此造成数据不完整,正确的做法是在数据头部以及尾部设置校验,例如我完整的数据包可以封装为 AA 01 02 03 04 05 06 07 08 09 BB,当接收到AA我认为数据开始接收 只有当接收到BB 我才认为数据接收完毕
///
///
///
private void serialPort_ckxz_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
int len = serialPort_ckxz.BytesToRead;//获取可以读取的字节数
if (len > 0)
{
byte[] recvBytes = new byte[len];//创建接收的数组
serialPort_ckxz.Read(recvBytes, 0, len);//接收数据
Invoke((new Action(() =>//显示字符串
{
textBox_js.AppendText("字符串:" + Encoding.Default.GetString(recvBytes)); //显示在文本框里面
})));
Invoke((new Action(() =>//显示16进制
{
textBox_js.AppendText("\r\n16进制:" + byteToHexStr(recvBytes) + "\r\n"); //显示在文本框里面
})));
//for (int i = 0; i < len; i++)//拷贝数据到UsartReadBuff
//{
// UsartReadBuff[i + UsartReadCnt] = recvBytes[i];//从上次的地方接着填入数据
//}
//UsartReadCnt = UsartReadCnt + len;//记录上次的数据个数
//if (UsartReadCnt >= 3)//接收到可以处理的数据个数
//{
// UsartReadCnt = 0;
// if (UsartReadBuff[0] == 0xaa && UsartReadBuff[1] == 0x55)//判断数据
// {
// if (UsartReadBuff[2] == 0x01)//
// {
// Invoke((new Action(() =>
// {
// button_dd.Text = "熄灭";
// label_zt.Text = "点亮";
// })));
// }
// else if (UsartReadBuff[2] == 0x00)
// {
// Invoke((new Action(() =>
// {
// button_dd.Text = "点亮";
// label_zt.Text = "熄灭";
// })));
// }
// }
//}
}
}
///
/// 发送字符串到串口
///
///
///
private void button_fs_Click(object sender, EventArgs e)
{
try
{
if(textBox_fs.Text.ToString().Length>0)
{
serialPort_ckxz.Write(textBox_fs.Text.ToString());
}
}
catch(Exception ex)
{
MessageBox.Show("发送失败,请检查串口是否连接"+ex.Message);
button_ljzt.Text = "连接";
}
}
private void button1_Click(object sender, EventArgs e)
{
try
{
byte[] sendbyte = new byte[5];
sendbyte[0] = 0x4C;
sendbyte[1] = 0x45;
sendbyte[2] = 0x44;
sendbyte[3] = 0x01;
sendbyte[4] = 0x00;
for (int i = 0; i < 100; i++)
{
serialPort_ckxz.Write(sendbyte, 0, sendbyte.Length);
System.Threading.Thread.Sleep(100);//延时100毫秒
//sendbyte[3] = (byte)(sendbyte[3] >> (8 - (1 % 8)));
unchecked
{
sendbyte[3] = (byte)((sendbyte[3] << 1)| (sendbyte[3] >>7));
}
}
}
catch (Exception ex)
{
MessageBox.Show("发送失败,请检查串口是否连接" + ex.Message);
button_ljzt.Text = "连接";
}
}
}
}