参考
zynq以太网透传到串口
为了方便学习以太网的帧格式
有很多好用的串口助手, 但很难找到一个好用的以太网调试助手
所以有必要做这个工具
网口数据太多了, 用 1 对 1 的 方式接线
目标
向zynq板的串口发送1个字节流(超时封包),
zynq板通过网口发出( )
zynq板从网口接收一个字节流,从它的串口输出
可过滤非ecat的帧
例子
json
{
"remark": "ecat_translate.j2b.json",
"读取AL状态寄存器0x0130": {
"Call": "FF FF FF FF FF FF 02 11 22 33 44 55 88 A4 0E 10 01 00 00 00 30 01 02 00 00 00 00 00 00 00",
"Reply": "FF FF FF FF FF FF 02 11 22 33 44 55 88 A4 0E 10 01 00 01 00 30 01 02 00 00 00 01 00 01 00"
},
"写LED寄存器_点亮LED1": {
"Call": "FF FF FF FF FF FF 02 11 22 33 44 55 88 A4 0E 10 01 00 00 00 00 10 02 00 01 00 00 00 00",
"Reply": "FF FF FF FF FF FF 02 11 22 33 44 55 88 A4 0E 10 01 00 01 00 00 10 02 00 00 00 00 00 00 00"
},
"读取AL事件屏蔽寄存器0x0204": {
"Call": "FF FF FF FF FF FF 02 11 22 33 44 55 88 A4 0E 10 01 00 00 00 04 02 04 00 00 00 00 00 00 00",
"Reply": "FF FF FF FF FF FF 02 11 22 33 44 55 88 A4 0E 10 01 00 01 00 04 02 04 00 XX XX XX XX 01 00"
}
}
连接
bash
/dev/ttyPS1 和 eth1 相互透传
eth1 直连 ecat LAN9252
eth0 连交换机联网
/dev/ttyPS0 串口终端
eth0 配置
basjh
# 清空 eth0 上的所有 IPv4/IPv6 地址
ip addr flush dev eth0
# 清空默认路由(有多个默认路由就多执行几次,或用 replace 见下)
ip route del default 2>/dev/null
# 重新配置
ip addr add 192.168.3.50/24 dev eth0
ip link set eth0 up
# 默认路由建议用 replace(可重复执行)
ip route replace default via 192.168.3.1 dev eth0
eth1配置
bash
ip link set eth1 up
ip link set eth1 promisc on
ttyPS1测试
bash
stty -F /dev/ttyPS1 115200 raw -echo
cat /dev/ttyPS1
源码
main.cpp
c
#include <iostream>
#include <iomanip>
#include <string>
#include <vector>
#include "utils/raw_socket.h"
#include "utils/serial_port.h"
#include <chrono>
#include <errno.h>
#include <poll.h>
#include <signal.h>
#include <string.h>
#include <sys/socket.h>
#include <linux/if_packet.h>
struct BridgeOptions
{
std::string ifname = "eth1";
std::string serial = "/dev/ttyPS1";
int baud = 115200;
bool ethercatOnly = true; // filter ethertype 0x88A4
bool padTo60 = true; // pad short frames (without FCS) to 60 bytes
bool ignoreOutgoing = true; // avoid serial echo from our own TX
bool trimEthercatPadding = true;
bool debug = false;
int maxFrame = 2048;
int serialTimeoutMs = 100; // serial idle timeout means end of one ethernet frame
};
static void PrintHelp(const char* argv0)
{
std::cout
<< "zynq eth<->serial bridge (for EtherCAT learning)\n\n"
<< "Usage:\n"
<< " " << argv0 << " [--if eth1] [--serial /dev/ttyPS1] [--baud 115200]\n"
<< " [--all] [--no-pad] [--no-trim] [--no-ignore-outgoing] [--max 2048]\n"
<< " [--timeout-ms 100] [--debug]\n\n"
<< "Serial framing:\n"
<< " Send raw ethernet frame bytes directly. 100ms serial idle means frame end.\n\n"
<< "Defaults:\n"
<< " - Only forwards EtherCAT (ethertype 0x88A4). Use --all to forward all Ethernet frames.\n"
<< " - Pads short TX frames to 60 bytes (without FCS).\n"
<< " - Needs root privileges on Linux (raw socket).\n";
}
static bool ParseArgs(int argc, char** argv, BridgeOptions& opt)
{
for (int i = 1; i < argc; ++i)
{
std::string a = argv[i];
if (a == "--help" || a == "-h")
{
PrintHelp(argv[0]);
return false;
}
if (a == "--if" && i + 1 < argc)
{
opt.ifname = argv[++i];
continue;
}
if (a == "--serial" && i + 1 < argc)
{
opt.serial = argv[++i];
continue;
}
if (a == "--baud" && i + 1 < argc)
{
opt.baud = std::stoi(argv[++i]);
continue;
}
if (a == "--all")
{
opt.ethercatOnly = false;
continue;
}
if (a == "--no-pad")
{
opt.padTo60 = false;
continue;
}
if (a == "--no-trim")
{
opt.trimEthercatPadding = false;
continue;
}
if (a == "--no-ignore-outgoing")
{
opt.ignoreOutgoing = false;
continue;
}
if (a == "--debug")
{
opt.debug = true;
continue;
}
if (a == "--max" && i + 1 < argc)
{
opt.maxFrame = std::stoi(argv[++i]);
continue;
}
if (a == "--timeout-ms" && i + 1 < argc)
{
opt.serialTimeoutMs = std::stoi(argv[++i]);
continue;
}
std::cout << "Unknown arg: " << a << "\n";
PrintHelp(argv[0]);
return false;
}
return true;
}
static volatile sig_atomic_t g_stop = 0;
static void OnSignal(int) { g_stop = 1; }
static uint16_t GetEtherType(const uint8_t* frame, int len)
{
if (len < 14)
return 0;
return ((uint16_t)frame[12] << 8) | (uint16_t)frame[13];
}
static uint16_t GetEtherType(const std::vector<uint8_t>& frame)
{
if (frame.size() < 14)
return 0;
return ((uint16_t)frame[12] << 8) | (uint16_t)frame[13];
}
static void PrintHexPrefix(const uint8_t* data, int len, int maxLen = 64)
{
int n = len < maxLen ? len : maxLen;
for (int i = 0; i < n; ++i)
{
std::cout << std::hex << std::uppercase << std::setw(2) << std::setfill('0')
<< (int)data[i] << " ";
}
if (len > maxLen)
std::cout << "...";
std::cout << std::dec << std::nouppercase << std::setfill(' ') << "\n";
}
static int GetEthercatPayloadLength(const uint8_t* frame, int len)
{
if (len < 16 || GetEtherType(frame, len) != 0x88A4)
return -1;
uint16_t header = (uint16_t)frame[14] | ((uint16_t)frame[15] << 8);
return header & 0x07FF;
}
static int GetEthernetUsefulLength(const uint8_t* frame, int len, const BridgeOptions& opt)
{
if (!opt.trimEthercatPadding)
return len;
int ethercatPayloadLen = GetEthercatPayloadLength(frame, len);
if (ethercatPayloadLen < 0)
return len;
int usefulLen = 14 + 2 + ethercatPayloadLen;
if (usefulLen < 16 || usefulLen > len)
return len;
return usefulLen;
}
static bool IsAllowedEtherType(const std::vector<uint8_t>& frame, bool ethercatOnly)
{
if (frame.size() < 14)
return false;
if (!ethercatOnly)
return true;
return GetEtherType(frame) == 0x88A4;
}
static bool IsAllowedEtherType(const uint8_t* frame, int len, bool ethercatOnly)
{
if (len < 14)
return false;
if (!ethercatOnly)
return true;
return GetEtherType(frame, len) == 0x88A4;
}
static void SendSerialFrameToEth(RawSocket& eth, std::vector<uint8_t>& frame, const BridgeOptions& opt)
{
if (frame.empty())
return;
if (!IsAllowedEtherType(frame, opt.ethercatOnly))
{
if (opt.debug)
{
std::cout << "SER->ETH drop len=" << frame.size();
if (frame.size() >= 14)
std::cout << " ethertype=0x" << std::hex << std::uppercase << GetEtherType(frame) << std::dec << std::nouppercase;
std::cout << "\n";
}
frame.clear();
return;
}
int originalLen = (int)frame.size();
if (opt.padTo60 && frame.size() < 60)
frame.resize(60, 0x00);
bool ok = eth.Send(frame);
if (opt.debug)
{
std::cout << "SER->ETH " << (ok ? "send" : "send_failed")
<< " len=" << originalLen
<< " tx_len=" << frame.size()
<< " ethertype=0x" << std::hex << std::uppercase << GetEtherType(frame)
<< std::dec << std::nouppercase << "\n";
PrintHexPrefix(frame.data(), (int)frame.size());
}
frame.clear();
}
int main(int argc, char** argv)
{
BridgeOptions opt;
if (!ParseArgs(argc, argv, opt))
return 0;
signal(SIGINT, OnSignal);
signal(SIGTERM, OnSignal);
RawSocket eth;
if (!eth.Open(opt.ifname))
{
std::cout << "Open ethernet failed: " << opt.ifname << "\n";
return 1;
}
if (opt.ignoreOutgoing)
{
int one = 1;
setsockopt(eth.GetFd(), SOL_PACKET, PACKET_IGNORE_OUTGOING, &one, sizeof(one));
}
SerialPort ser;
if (!ser.Open(opt.serial, opt.baud))
{
std::cout << "Open serial failed: " << opt.serial << " baud=" << opt.baud << "\n";
return 1;
}
std::cout
<< "Bridge running:\n"
<< " serial: " << opt.serial << " @" << opt.baud << "\n"
<< " eth : " << opt.ifname << (opt.ethercatOnly ? " (EtherCAT only)" : " (ALL)") << "\n"
<< " frame : raw ethernet bytes, " << opt.serialTimeoutMs << "ms idle timeout\n"
<< " max : " << opt.maxFrame << " bytes, pad_to_60=" << (opt.padTo60 ? "yes" : "no")
<< ", trim_ecat_padding=" << (opt.trimEthercatPadding ? "yes" : "no") << "\n"
<< " debug : " << (opt.debug ? "on" : "off") << "\n";
std::vector<uint8_t> serialFrame;
uint8_t serialIn[4096];
uint8_t ethIn[4096];
auto lastSerialRx = std::chrono::steady_clock::now();
while (!g_stop)
{
int pollTimeoutMs = 200;
if (!serialFrame.empty())
{
auto now = std::chrono::steady_clock::now();
int idleMs = (int)std::chrono::duration_cast<std::chrono::milliseconds>(now - lastSerialRx).count();
pollTimeoutMs = idleMs >= opt.serialTimeoutMs ? 0 : opt.serialTimeoutMs - idleMs;
}
pollfd fds[2];
memset(fds, 0, sizeof(fds));
fds[0].fd = ser.GetFd();
fds[0].events = POLLIN;
fds[1].fd = eth.GetFd();
fds[1].events = POLLIN;
int pr = poll(fds, 2, pollTimeoutMs);
if (pr < 0)
{
if (errno == EINTR)
continue;
std::cout << "poll error: " << strerror(errno) << "\n";
break;
}
// serial -> eth
if (fds[0].revents & POLLIN)
{
int n = ser.Read(serialIn, (int)sizeof(serialIn));
if (n > 0)
{
serialFrame.insert(serialFrame.end(), serialIn, serialIn + n);
lastSerialRx = std::chrono::steady_clock::now();
if (opt.debug)
std::cout << "SER rx chunk len=" << n << " buffered=" << serialFrame.size() << "\n";
if ((int)serialFrame.size() > opt.maxFrame)
{
std::cout << "serial frame too large, dropped: " << serialFrame.size() << "\n";
serialFrame.clear();
}
}
}
if (!serialFrame.empty())
{
auto now = std::chrono::steady_clock::now();
int idleMs = (int)std::chrono::duration_cast<std::chrono::milliseconds>(now - lastSerialRx).count();
if (idleMs >= opt.serialTimeoutMs)
SendSerialFrameToEth(eth, serialFrame, opt);
}
// eth -> serial
if (fds[1].revents & POLLIN)
{
int len = eth.Receive(ethIn, (int)sizeof(ethIn));
if (len > 0)
{
if (!IsAllowedEtherType(ethIn, len, opt.ethercatOnly))
{
if (opt.debug)
{
std::cout << "ETH->SER drop len=" << len;
if (len >= 14)
std::cout << " ethertype=0x" << std::hex << std::uppercase << GetEtherType(ethIn, len) << std::dec << std::nouppercase;
std::cout << "\n";
}
continue;
}
int usefulLen = GetEthernetUsefulLength(ethIn, len, opt);
ser.WriteAll(ethIn, usefulLen);
if (opt.debug)
{
std::cout << "ETH->SER recv len=" << len
<< " serial_len=" << usefulLen
<< " ethertype=0x" << std::hex << std::uppercase << GetEtherType(ethIn, len)
<< std::dec << std::nouppercase << "\n";
PrintHexPrefix(ethIn, usefulLen);
}
}
}
}
std::cout << "Bridge stopped.\n";
return 0;
}
utils/raw_socket.h
c
#ifndef RAW_SOCKET_H
#define RAW_SOCKET_H
#include <stdint.h>
#include <string>
#include <vector>
class RawSocket
{
public:
RawSocket();
~RawSocket();
bool Open(const std::string& ifname);
bool Send(const std::vector<uint8_t>& frame);
int Receive(uint8_t* buffer,int maxLen);
void Close();
int GetFd() const { return m_fd; }
private:
//套接字句柄
int m_fd;
};
#endif
utils/raw_socket.cpp
c
#include "raw_socket.h"
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netpacket/packet.h>
#include <net/if.h>
#include <netinet/ether.h>
RawSocket::RawSocket()
{
m_fd = -1;
}
RawSocket::~RawSocket()
{
if (m_fd >= 0)
{
close(m_fd);
}
}
bool RawSocket::Open(
const std::string& ifname)
{
m_fd =
socket(
AF_PACKET,
SOCK_RAW,
htons(ETH_P_ALL));
if (m_fd < 0)
{
return false;
}
struct ifreq ifr;
memset(&ifr,0,sizeof(ifr));
strncpy(
ifr.ifr_name,
ifname.c_str(),
IFNAMSIZ - 1);
if (ioctl(
m_fd,
SIOCGIFINDEX,
&ifr) < 0)
{
return false;
}
sockaddr_ll addr;
memset(&addr,0,sizeof(addr));
addr.sll_family = AF_PACKET;
addr.sll_protocol = htons(ETH_P_ALL);
addr.sll_ifindex = ifr.ifr_ifindex;
if (bind(
m_fd,
(sockaddr*)&addr,
sizeof(addr)) < 0)
{
return false;
}
return true;
}
bool RawSocket::Send(
const std::vector<uint8_t>& frame)
{
return send(
m_fd,
frame.data(),
frame.size(),
0) > 0;
}
int RawSocket::Receive(
uint8_t* buffer,
int maxLen)
{
return recv(
m_fd,
buffer,
maxLen,
0);
}
void RawSocket::Close()
{
if (m_fd >= 0)
{
close(m_fd);// 关闭socket
m_fd = -1; //防止重复close
}
}
utils/serial_port.h
c
#ifndef SERIAL_PORT_H
#define SERIAL_PORT_H
#include <stdint.h>
#include <string>
#include <vector>
class SerialPort
{
public:
SerialPort();
~SerialPort();
bool Open(const std::string& path, int baud);
void Close();
int GetFd() const { return m_fd; }
bool IsOpen() const { return m_fd >= 0; }
// Non-blocking read; returns bytes read, or -1 on EAGAIN/error.
int Read(uint8_t* buffer, int maxLen);
// Write all bytes (busy-waits on EAGAIN).
bool WriteAll(const uint8_t* data, int len);
bool WriteAll(const std::vector<uint8_t>& data);
private:
int m_fd;
};
#endif
utils/serial_port.cpp
c
#include "serial_port.h"
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
static speed_t BaudToSpeed(int baud)
{
switch (baud)
{
case 9600: return B9600;
case 19200: return B19200;
case 38400: return B38400;
case 57600: return B57600;
case 115200: return B115200;
case 230400: return B230400;
case 460800: return B460800;
case 921600: return B921600;
default: return 0;
}
}
SerialPort::SerialPort() : m_fd(-1) {}
SerialPort::~SerialPort()
{
Close();
}
bool SerialPort::Open(const std::string& path, int baud)
{
Close();
speed_t speed = BaudToSpeed(baud);
if (speed == 0)
return false;
m_fd = open(path.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK);
if (m_fd < 0)
return false;
termios tio;
if (tcgetattr(m_fd, &tio) != 0)
{
Close();
return false;
}
cfmakeraw(&tio);
tio.c_cflag &= ~PARENB;
tio.c_cflag &= ~CSTOPB;
tio.c_cflag &= ~CSIZE;
tio.c_cflag |= CS8;
tio.c_cflag &= ~CRTSCTS;
tio.c_iflag &= ~(IXON | IXOFF | IXANY);
tio.c_cflag |= (CLOCAL | CREAD);
tio.c_cc[VMIN] = 0;
tio.c_cc[VTIME] = 0;
if (cfsetispeed(&tio, speed) != 0 || cfsetospeed(&tio, speed) != 0)
{
Close();
return false;
}
if (tcsetattr(m_fd, TCSANOW, &tio) != 0)
{
Close();
return false;
}
tcflush(m_fd, TCIOFLUSH);
return true;
}
void SerialPort::Close()
{
if (m_fd >= 0)
{
close(m_fd);
m_fd = -1;
}
}
int SerialPort::Read(uint8_t* buffer, int maxLen)
{
if (m_fd < 0)
return -1;
int n = (int)read(m_fd, buffer, (size_t)maxLen);
if (n < 0)
{
if (errno == EAGAIN || errno == EWOULDBLOCK)
return -1;
return -1;
}
return n;
}
bool SerialPort::WriteAll(const uint8_t* data, int len)
{
if (m_fd < 0)
return false;
int written = 0;
while (written < len)
{
int n = (int)write(m_fd, data + written, (size_t)(len - written));
if (n < 0)
{
if (errno == EAGAIN || errno == EWOULDBLOCK)
continue;
return false;
}
if (n == 0)
continue;
written += n;
}
return true;
}
bool SerialPort::WriteAll(const std::vector<uint8_t>& data)
{
if (data.empty())
return true;
return WriteAll(data.data(), (int)data.size());
}
启动
网口 eth1 和 串口 /dev/ttyPS1 相互透传
bash
./ming_ethercat_petalinux --if eth1 --serial /dev/ttyPS1 --baud 115200 --debug