串口数据帧我们学过,但到RS485是不是就卡壳了?
空闲状态:AB线悬浮在2.3V的样子。GND是0V,+5V是4.75v
工作时,AB线在2.3v上做逻辑01(-2v,+2v)跳变。
这图是不是还不太好理解?我把B线下移3伏,给大家看看。
黄色是A相,绿色是B相。值是0x71
电平逻辑正好相反。永远对称。
RS-485的电气特性:逻辑“1”以两线间的电压差为 正(2~6)V 表示;逻辑“0”以两线间的电压差为 负(2~6)V 表示。
我们来看下:
逻辑正: A(2.3v+2v)约4v B(2.3v-2v)约0v 差值 正4V
逻辑负: A(2.3v-2v)约0v B(2.3v+2v)约4v 差值 负4V
所以 B相 是电平参考线。
//===========================
电压的问题讲完了,就该讲时序了。
我们先把示波器的GND接到B相上。
这2图是同一个波形,只是GND参考线换成了B相。
空闲时:AB都是2.3V相当于没电压,是0伏。逻辑正负切换时,波形振幅放大了。一个格子是2v,逻辑1正3.5v,逻辑0负3.5v
RS-485的电气特性:逻辑“1”以两线间的电压差为 正(2~6)V 表示;逻辑“0”以两线间的电压差为 负(2~6)V 表示。
我们来看下:
逻辑正: A(2.3v+2v)约4v B(2.3v-2v)约0v 差值 正4V
逻辑负: A(2.3v-2v)约0v B(2.3v+2v)约4v 差值 负4V
B相 是电平参考线。这样,逻辑就对上了。起始位要保持一个低电平,0伏怎么保持低电平呢?所以要先拉高4v,再拉低 (负4v)并保持一位长度。
我们来看下数据帧:
n长度高电平,一个起始位低电平,8个数据位电平,一个校验位,一个停止位。
所以,空闲是n长的0v。
一个起始位低电平:为了这个条件,所以至少拉高1个位的高电平再保持一个位的低电平。
8个数据位电平,D0D1D2D3D4D5D6D7 我发的是0xF1,
这个是(96N81)的时序图,你会发现,没有校验位的电平宽度,因为你是无校验,所以校验电平宽度就没有。
为了看校验位,我发的值要改成0x71(0111 0001),串口(96o81)奇校验
为了看校验位,我发的值要改成0x71(0111 0001),串口(96E81)偶校验
0x71(0111 0001)括号里的1数量,加 校验位电平值,是偶数就是偶校验 4+0等于4是偶数
好,下面看个无校验的(96N81)
可以看到,校验位的电平长度没了。
校验位,实际还可以强制:一直为1,或者一直为0;
注意:串口刚打开后,发送的第2帧有延迟,之后会没这个问题。
打开串口,连续发送3次字节,看下时序图
我也不明白,为什么刚打开串口后,发送的第2帧会延迟。之后再连续发送就不会再延迟。可能是C#底层类库的问题。
接收:出现数据帧过短字节,可以用 com.ReceivedBytesThreshold = 1;// 单帧最低字节数
com.ReceivedBytesThreshold = 5;// 单帧最低字节数
设置5后,间歇发送的字节必须达到5字节,才能触发串口接收事件。
发送部分:
虽然是连续发单字节,实际代码有延迟。还是要用示波器,逻辑分析仪,监视
com.Write(by, 0, 1) ;
上位机上也能明显看出第3帧有延迟,我精确到毫秒4位。电脑还是不如单片机那样精准控制时间,实时性太差了。
txshow(by, DateTime.Now.ToString("ss's'ffffff':'"));
void txshow(byte[] bs, string tim)//日志
{
string num = string.Empty;
foreach (var item in bs)
{
num += item.ToString("X2") + " ";
}
listBox2.Items.Insert(0, tim + num);
//if (this.InvokeRequired)
//{
// this.Invoke(new Action(
// () => { listBox2.Items.Insert(0, tim + num); }
// ));
//}
//else
//{
// listBox2.Items.Insert(0, tim + num);
//}
}
用数据包发送才可以实现 连续字节发送
private void button9_Click(object sender, EventArgs e)
{
com.Write(new byte[3] { 0x30, 0x31, 0x32 }, 0, 3);
}
这3字节没出现等待情况。
总之,电脑的时间实时性比较差,频繁读写,会造成卡顿。可能线程的问题。会出现字节帧过短,过长,空字节触发串口接收事件。
要么发送后,等待固定的时间,再去读取接收数据帧。或者判断载波检测,空字节可以再等待一会,再去读字节。
以下是我对串口的简单调试,写的C#,可以参考下
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace portDemo
{
public partial class Form1 : Form
{
SerialPort com;
static int wait=0;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
com = new SerialPort();
com.PortName=comboBox1.Text;
com.BaudRate=int.Parse(comboBox2.Text );// 96
switch (comboBox3.Text)// n
{
case "无": com.Parity = Parity.None; break;
case "奇": com.Parity = Parity.Odd; break;
case "偶": com.Parity = Parity.Even; break;
case "高": com.Parity = Parity.Mark; break;
case "低": com.Parity = Parity.Space; break;
default:
break;
}
com.DataBits=int.Parse(comboBox4.Text);
switch (comboBox5.Text)// n
{
case "1": com.StopBits = StopBits.One; break;
case "1.5": com.StopBits = StopBits.OnePointFive; break;
case "2": com.StopBits = StopBits.Two; break;
default:
break;
}
com.ReceivedBytesThreshold = 1;// 单帧最低字节数
com.ReadTimeout = 5;
com.DataReceived += Com_DataReceived;//接收事件
com.Open();
button1.BackColor = Color.Green;
}
private void Com_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
string tim = DateTime.Now.ToString("ss's'ffffff ':'"); // 毫秒
byte[ ] rxbuff= new byte[com.BytesToRead];
com.Read(rxbuff, 0, rxbuff.Length );
rzshow(rxbuff ,tim);
}
private void button2_Click(object sender, EventArgs e)
{
com?.Close();
com = null;
button1.BackColor = Color.Gray;
}
private void button3_Click(object sender, EventArgs e)
{
string tim = DateTime.Now.ToString("ss's'fffff ':'"); // 毫秒
byte by = Convert.ToByte(textBox1.Text, 16);
com.Write(new byte[1]{ by },0,1);
}
void rzshow(byte[] bs,string tim)//日志
{
string num = string.Empty;
foreach (var item in bs)
{
num += item.ToString("X2")+" ";
}
if(this.InvokeRequired)
{
this.Invoke(new Action(
()=> { listBox1.Items.Insert(0, tim +num); }
));
}
else
{
listBox1.Items.Insert(0, tim + num);
}
}
private void button4_Click(object sender, EventArgs e)
{// ↑
com.ReadTimeout+=1;
label7.Text= com.ReadTimeout.ToString();
}
private void button5_Click(object sender, EventArgs e)
{
com.ReadTimeout -= 1;
label7.Text = com.ReadTimeout.ToString();
}
private async void button6_Click(object sender, EventArgs e)
{//3tx
listBox2.Items.Clear();
label8.Text = wait.ToString();
byte[] by = new byte[1] { Convert.ToByte(textBox1.Text, 16) };
txshow(by, DateTime.Now.ToString("ss's'ffffff':'"));
com.Write(by, 0, 1) ;
await Task.Delay(wait);
txshow(by, DateTime.Now.ToString("ss's'ffffff':'"));
com.Write(by, 0, 1);
await Task.Delay(wait);
txshow(by, DateTime.Now.ToString("ss's'ffffff':'"));
com.Write(by, 0, 1);
await Task.Delay(wait);
}
void txshow(byte[] bs, string tim)//日志
{
string num = string.Empty;
foreach (var item in bs)
{
num += item.ToString("X2") + " ";
}
listBox2.Items.Insert(0, tim + num);
//if (this.InvokeRequired)
//{
// this.Invoke(new Action(
// () => { listBox2.Items.Insert(0, tim + num); }
// ));
//}
//else
//{
// listBox2.Items.Insert(0, tim + num);
//}
}
private void button7_Click(object sender, EventArgs e)
{// ↑
wait += 1;
label8.Text = wait.ToString();
}
private void button8_Click(object sender, EventArgs e)
{
wait -= 1;
label8.Text = wait.ToString();
}
private void button9_Click(object sender, EventArgs e)
{
com.Write(new byte[3] { 0x30, 0x31, 0x32 }, 0, 3);
}
}
}