最近在工作中,需要使用光栅尺,但是之前的程序是电气工程师通过PLC控制,然后使用需要使用modbus协议去读取结果,使用相当的不智能,优化的话,那位电气工程师已经离职,没人接手。
为了更好的使用这个光栅尺,我决定自己来优化一下。PLC开发不太懂,但没事,我手头有个空闲的ESP32C3,使用MCU去读个脉冲信号,我觉得问题不大。
在拿到光栅尺,我看了下规格:精度1um,TTL信号输出,一个脉冲的计数为4um,接口为DB9,当然接线与传统的RS232串口定义完全不一样。
这里接线就不做过多的描述,毕竟设备不一样,光栅尺的输出信号分为了A/B/Z,然后我们来看下光栅的A/B信号:
这时候有使用arduino玩过编码器,一看就知道,这不就是编码器,于是顺手把USB逻辑分析仪拿出来,运动一下光栅,看下数据波形,一个波形的时间大约在3-400ms。
于是arduino的开发就简单了,两个pin脚中断触发,然后读编码器一般计数就行了。为了让使用过程中能实现自动上报数据,于是我增加了一个pin脚中断,在上升或者下降触发的时候能自动把计数数据发到串口,这样在到达我需要测试数据的时候,ESP32C3能把数据自动上报。
所以arduino的代码我设计为3块:
1、中断计数、中断上报;
2、串口协议生成;
3、串口数据通讯。
既然已经有了单片机程序,那上位机也必不可少,那就直接简单点,使用C#进行数据开发。
那就直接开发,使用arduino开发虽然不能深入了解单片机的开发精髓,但是我们要的是能拿来直接用。
中断定义:
const int A_pin = 1; // 定义A管脚引脚号
const int B_pin = 2; // 定义B管脚引脚号
const int P_pin = 3;
const int interruptPinA = digitalPinToInterrupt(A_pin);
const int interruptPinB = digitalPinToInterrupt(B_pin);
const int interruptPinP = digitalPinToInterrupt(P_pin);
在初始化中增加代码:
// 设置中断回调函数
attachInterrupt(interruptPinA, countPulseA, CHANGE);
attachInterrupt(interruptPinB, countPulseB, CHANGE);
attachInterrupt(interruptPinP, countPulseP, CHANGE);
中断函数关键代码:
A脚中断部分:
// 在A脉冲中寻找上升沿信号,若A编码器引脚为高电平,
// 若B编码器引脚为低电平,Counter计数+1,否则-1
if (digitalRead(A_pin) == HIGH) {
if (digitalRead(B_pin) == LOW) {
count = count + 1;
}
else {
count = count - 1;
}
}
else // 在A脉冲中寻找下降沿信号
{
if (digitalRead(B_pin) == HIGH) {
count = count + 1;
}
else {
count = count - 1;
}
}
B脚中断部分:
// 在B脉冲中寻找上升沿信号
if (digitalRead(B_pin) == HIGH)
{
if (digitalRead(A_pin) == HIGH) {
count = count + 1;
}
else {
count = count - 1;
}
}
else {
if (digitalRead(A_pin) == LOW) {
count = count + 1;
}
else {
count = count - 1;
}
}
P脚就是自动发送串口数据,协议是自己定义的就不具体描述,基本协议思路:
定义好协议的帧头、流水号、数据长度、数据位、数据内容、校验位以及帧尾,这样基本不会有问题。至于校验,简单的就是累加、或者直接使用现成的校验方式,比如CRC16等。
上位机就简单的使用C#进行数据收发,我选择使用winform进行编写。
当前.net的版本较多,既然支持的直接使用推荐的.net8.0,在.net8.0的环境中是没有serialport控件,需要使用nuget来获取system.io.ports控件:
功能就简洁点,直接获取串口,然后直接读数据内容:
至于功能就基于serialport控件:
搜索本地串口:
cb_comname.Items.Clear();
cb_comname.Items.AddRange(SerialPort.GetPortNames());
if (cb_comname.Items.Count > 0)
{
cb_comname.SelectedIndex = 0;
}
打开串口:
try
{
sp.PortName = comname;
sp.BaudRate = bs;
sp.Open();
isok = sp.IsOpen;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
数据收发:
sp.DataReceived += Sp_DataReceived;
处理事件的函数内容:
int len=sp.BytesToRead;
if (len > 0)
{
byte[] buf=new byte[len];
sp.Read(buf, 0, len);
//处理数据
}
顺便使用了closexml来保存记录的光栅数据,使用起来相当完美,有时间再录制一个视频。