自建极简Ethercat主站-底层驱动编写

1、简介

MECM(Mini Ethercat Master),名字随便起的。已经学习了一段时间的Ethercat总线了,目前的想法就是自己简单实现一个Ethercat主站,没有太多的冗余功能,暂时不考虑太多的容错机制,仅实现目前用到的FOE、COE、过程数据通信这三个功能,仅用于学习和加深理解。主站的硬件是GD32F450Z_EVAL开发板,板载的以太网芯片是DP83848VV。

2、底层编写

从根本来讲,Ethercat主站就是用来读写从站的。因此驱动的最核心功能有三个:网卡初始化、写入据到从站、读取从站数据。还有一个非必要功能,就是系统时钟,用来超时计数,也算是个简单的容错。当然,如果你不做超时也可以,选择发送和接收时一直死等也可以。

1、网卡初始化

关于GD32的以太网理解和初始化流程,请看文章Ethercat学习-GD32以太网学习 下面是部分代码,代码的初始化流程也是按照文章中的流程来的。

/* 初始化代码片段 */
int enet_system_init(void)
{
    ErrStatus reval_state = ERROR;
    /* 初始化以太网模块时钟 */
    enet_clock_init();
    /* 初始化以太网引脚 RMII接口*/
    enet_gpio_init();
    /* 初始化以太网MAC和DMA */
    reval_state = enet_mac_dma_config();
    return reval_state;
}
/* MAC 和 DMA 的初始化 */
static int enet_mac_dma_config(void)
{
    int i;
    ErrStatus reval_state = ERROR;
    /* 复位,并等待完成 */
    enet_deinit();
    reval_state = enet_software_reset();
    if(ERROR == reval_state) {
        return ERROR;
    }
    /* 以太网初始化 配置网卡自协商模式、使能接收端校验和检测功能、接收所有的广播帧*/
    reval_state = enet_init(ENET_AUTO_NEGOTIATION, ENET_AUTOCHECKSUM_DROP_FAILFRAMES, ENET_BROADCAST_FRAMES_PASS);
    if(ERROR == reval_state) {
        return ERROR;
    }
    /* 设置MAC地址 */
    enet_mac_address_set(ENET_MAC_ADDRESS0, mac);
    /* 初始化收发的描述符,常规描述符,链结构*/
    enet_descriptors_chain_init(ENET_DMA_TX);
    enet_descriptors_chain_init(ENET_DMA_RX); 
    /* 设置接收描述符 RDES1的31位为0,接收完成后会立马产生中断*/
    for(i=0; i<ENET_RXBUF_NUM; i++){ 
        enet_rx_desc_immediate_receive_complete_interrupt(&rxdesc_tab[i]);
    }
    /* 使能硬件IP包头和数据域的校验和计算和插入 */
    for(i=0; i < ENET_TXBUF_NUM; i++){
        enet_transmit_checksum_config(&txdesc_tab[i], ENET_CHECKSUM_TCPUDPICMP_FULL);
    }
//    /* 正常中断汇总使能 接收中断属于正常中断 */
//    enet_interrupt_enable(ENET_DMA_INT_NIE);
//    /* 使能接收中断 */
//    enet_interrupt_enable(ENET_DMA_INT_RIE);
    /* 使能以太网 */
    enet_enable();  
    return reval_state;
}

2、数据读写

数据的读写很简单,读数据采用的时轮询的方式,不是中断的方式。

/**
 * @******************************************************************************: 
 * @func: [enet_buf_send]
 * @description: 以太网数据发送
 * @note: 
 * @author: 
 * @param [uint8_t] *buf  待发送数据的地址
 * @param [uint32_t] length 待发送数据的长度
 * @return [*]
 * @==============================================================================: 
 */
int enet_buf_send(uint8_t *buf,uint32_t length)
{
    ErrStatus reval_state = ERROR; 
    reval_state = enet_frame_transmit(buf,length);
    return  reval_state;
}
/**
 * @******************************************************************************: 
 * @func: [enet_buf_send]
 * @description: 以太网数据接收
 * @note: 
 * @author: 
 * @param [uint8_t] *buf 接收数据的地址
 * @param [uint32_t] length 接收数据缓存的最大长度
 * @return [*]
 * @==============================================================================: 
 */
int enet_buf_recv(uint8_t *buf,uint32_t length)
{
    ErrStatus reval_state;
    if(enet_rxframe_size_get()){
        reval_state = enet_frame_receive(buf,length);
    }else{
        reval_state = 0;
    }    
    return reval_state;
}

3、系统时钟

系统时钟的思路就是用滴答定时器,每1us记一次数,这个数就时我们的系统时钟,这个数我们只用来做超时检测。

/**
 * @******************************************************************************: 
 * @func: [osal_current_time]
 * @description: 返回系统时钟计数,单位us
 * @note: 
 * @author: 
 * @return [*]
 * @==============================================================================: 
 */
uint64_t osal_current_time_us(void)
{
  return timercount;
}

3、数据打包

1、Ethercat报文简介

基本接口已经写好,接下来就是按照Ethercat的数据包对数据进行打包,然后将发送出去。

在这里插入图片描述

如图所示,一个标准的ethercat包括了6部分:

名称说明长度
Destination目的MAC地址,这个可以暂时忽略,在简单的连接中,这个是真的无关紧要6byte
Source本机MAC地址,这个可以暂时忽略,在简单的连接中,这个是也无关紧要6byte
EthercatType0x88A42byte
EthercatHeader有11bit的length、1bit的预留、4bit的type组成2byte
Datagrams数据包46~1500byte
FCS帧校验序列,这个不需要我们去填写,自动生成4byte

这里我们关注两个部分EthercatHeader和Datagrams,其他的要么是固定的,要么是自动生成的,只有这两个需要我们字节计算。另外可以看到图中所示,ethercat报文最大长度写的是1522,这是因为它算上了VLAN的4byte,我们用的Ethercat总长度是1518。ethercat报文的最小长度为64,而Datagrams的最小长度就是64-6-6-2-4 = 46。如果我们的Datagrams小于44个长度,MAC子系统会自动将剩余的补齐。如下图所示,图中的pad bytes就是系统补齐的。
在这里插入图片描述

EthercatHeader中有一个length,这个长度就是Datagrams的长度,但是它不包含补齐的长度,也不包含FCS的长度,例如我们自己的数据是10个字节,然后系统又给我们填充了34个自己凑够了64字节,那么这个length的值依然是10。EthercatHeader中的type只能为0x01

2、Datagrams报文简介

Datagrams可以只包含一个Datagram,也可以是好多个Datagram拼接起来的。如下图所示:

在这里插入图片描述

在一包Datagram中又分为了三部分,DatagramHeader、data、WKC。DatagramHeader固定长度为10,说明如下:

名称说明长度
CmdEthercat命令,用来表明该数据包的作用:LWR、BRD、FPRW、APRW等1byte
idx索引,我们字节生成的,不会被从机修改,可以判断数据是否重复或者丢失1byte
Address地址。除了逻辑地址是站四个字节外,其他地址都是一个从机地址(2byte)+内存地址(2byte)4byte
Len数据包中的数据长度,也就是data的长度11bit
R预留,01bit
C帧循环,由从机的处理单元自动置位,用来识别该帧是否已经循环了,例如从机下次又收到了相同的帧,但是帧循环这里已经置1了,说明该帧已经处理过一次了,由于某种原因在重复循环。1bit
M该数据包是否是最后一包,1表示后续还有数据包1bit
IRQ所有从站ethercat事件请求寄存器的OR,主机发送时写0就好了。值是在从站读取出来的。2byte

3、数据组包

int mecm_BRD_test()
{
    int i = 0;
    mecm_fram_t fram;
    mecm_datagram_header_t dg_header;
    /* 填充Destination、Source、EthercatType */
    for(i = 0;i<MAXBUF;i++)
    {
        memcpy( fram.en_header.dst,dstMAC,6);
        memcpy( fram.en_header.src,srcMAC,6);
        fram.en_header.etype = 0x88A4;
    }
    /* 填充Ethercat header */
    fram.ec_header.clehgth = 13;
    fram.ec_header.reserve = 0;
    fram.ec_header.ctype   = 0x01;
    /* 填充数据包的datagram header */
    dg_header.cmd     = CMD_BRD;
    dg_header.idx     = 1;
    dg_header.addr.np.adp = 0x0001;
    dg_header.addr.np.ado = 0x0508;
    /* 数据长度为 1 */
    dg_header.dlength = 1;  
    dg_header.irq     = 0x0000; 
    memcpy((uint8_t *)fram.datagram,(uint8_t *)&dg_header,10);
    /* 填充数据包 data */
    fram.datagram[10] = 0x01;
    /* 填充WCK 发送时置 0*/
    uint16_t wck = 0;
    memcpy((uint8_t *)&fram.datagram[11],(uint8_t *)&wck,2);
    mecm_data_send((uint8_t *)&fram,29);
}

上电测试,开发板于电脑网口连接,用wireshark抓包,结果如下:

在这里插入图片描述

可以看到,虽然数据发出来了,但是wireshark识别的并不时Ethercat包,0x88A4 变成了0xA488。这里要把上面的0x88A4改为0xA488,主要是因为本地是小端存储,而网络字节序中在前的被认为是高字节,改完之后可以看到,ethercat包被正确识别出来了,结果如下:

在这里插入图片描述

4、其他

关于网络字节序的理解。我们用的单片机时小端存储,低字节的放在低地址处,数据在传输的时候是由低到高。因此我们的0x88A4,首先传输的时0xA4。但是到了对方网卡那里,它是按照网络字节序来识别的,而网络字节序统一是高字节在前,因此网卡在识别协议的时候先到达的0xA4被认为是高字节,所以就被识别为了0xA488。因此我们在传输前可以将网络协议部分的报文转换为网络字节序,例如MAC地址,以太网类型等报文,这样网卡在识别网络协议的时候可以识别出来。至于数据部分,我们可以不需要管,因为主机和从机都是小端存储,不会出错。当然,如果接收方是大端的话,数据部分需要转换为大端再传输,或者让接收方自己收到数据后自己转换。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/26058.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

性能测试监控平台:InfluxDB+Grafana+Jmeter

前言 性能测试工具jmeter自带的监视器对性能测试结果的实时展示&#xff0c;在Windows系统下的GUI模式运行&#xff0c;渲染和效果不是太好&#xff0c;在linux环境下又无法实时可视化。 2023年最新出炉性能测试教程&#xff0c;真实企业性能压测全流程项目实战训练大合集&am…

整型在内存中的存储

目录 一、为什么内存中存储补码&#xff1f; 二、大小端概念 百度笔试试题&#xff1a; 几道小题&#xff1a; 一、为什么内存中存储补码&#xff1f; 上一节我们了解了原码&#xff0c;反码&#xff0c;补码的概念&#xff08;http://t.csdn.cn/N0grg&#xff09;&#xff…

1-1 统计数字问题

题目&#xff1a; 我的答案&#xff1a; 一、信息 二、分析 1.如何选择数据结构&#xff1f; 2.如何选择算法有很多思路&#xff1f; 3.如何用文件实现输入输出&#xff1f; 三、思考 疑问1 我选择了一开始数组选择使用数组是一个不错的选择&#xff0c;尤其在这个问题中…

vulnhub dc-8

1.信息搜集 端口 22,80,31337 存活ip 192.168.85.136 2.访问网站&#xff0c;进行信息搜集 在欢迎页面发现sql注入 sqlmap进行跑数据 python sqlmap.py -u "http://192.168.85.136/?nid1" --batch -D d7db -T users -C name,pass --dump尝试robots.txt,发现后他登…

生成程序片段(程序依赖图PDG)

生成程序片段(程序依赖图PDG) 生成程序片段 标准方法是&#xff1a; 基于依赖性分析的切片。 使用程序依赖图表示依赖。 从中生成切片。 我们将专注于这种方法。但是&#xff0c;还有其他选择。 程序依赖图 The Program Dependence Graph (PDG) 表示数据和控制依赖项&#xf…

《深入理解计算机系统(CSAPP)》第7章 链接 - 学习笔记

写在前面的话&#xff1a;此系列文章为笔者学习CSAPP时的个人笔记&#xff0c;分享出来与大家学习交流&#xff0c;目录大体与《深入理解计算机系统》书本一致。因是初次预习时写的笔记&#xff0c;在复习回看时发现部分内容存在一些小问题&#xff0c;因时间紧张来不及再次整理…

java单元测试( Hamcrest 断言)

java单元测试( Hamcrest 断言) 单元测试特征: 1 范围狭窄 2 限于单一类或方法 3 体积小 为什么要编写单元测试&#xff1f; 为了防止错误&#xff08;很明显&#xff01;&#xff09; 而且还可以提高开发人员的生产力&#xff0c;因为单元测试&#xff1a; (1) 帮助实施——在…

力扣sql中等篇练习(二十八)

力扣sql中等篇练习(二十八) 1 每个城市最高气温的第一天 1.1 题目内容 1.1.1 基本题目信息 1.1.2 示例输入输出 1.2 示例sql语句 # Write your MySQL query statement below SELECT w.city_id,MIN(w.day) day,w.degree FROM Weather w INNER JOIN (SELECT city_id,MAX(degr…

【算法学习系列】07 - 无序数组中的局部最小值问题

文章目录 说明约束条件简单说下思路解决方案随机无序数组样本生成器算法实现验证代码进行大样本随机测试验证算法正确性 说明 在算法中&#xff0c;局部最小值是指一个函数在一个局部范围内的最小值。 具体而言&#xff0c;如果一个函数在一个小区间内的取值都比该区间内的其他…

C++程序员的待遇怎么样?我来谈谈学好C++的五个关键点

有个学弟跟我谈到这样一个问题&#xff1a;现在我看到网上很多人都在讲&#xff0c;说这个做C程序员&#xff0c;尤其是本科毕业计算机专业&#xff0c;然后步入社会之后就能拿到月入过万。但是为什么自己找的这个工作啊&#xff0c;普遍在月薪六七千块钱左右&#xff0c;也就是…

利用OpenCV处理图像

OpenCV是非常流行的图像处理库&#xff0c;下面介绍一下其对图像的基本操作。 1. 安装与环境 安装还有点儿复杂的&#xff0c;但百度几篇博客基本能解决&#xff0c;这里就不多说了。 安装好后&#xff0c;要在工程中使用OpenCV的头文件和库&#xff0c;需要在CMakeLists.tx…

码住!IC设计常用工具合集!

芯片设计过程中&#xff0c;选择和使用适合的工具是非常重要的。芯片设计工具通常分为三类&#xff1a;EDA工具、模拟仿真工具和布局工具。 一、EDA工具 EDA工具是芯片设计的核心&#xff0c;它包括原理图绘制、逻辑综合、门级仿真工具和物理版图编辑等&#xff0c;可以帮助设计…

基于springboot+Redis的前后端分离项目(一)-【黑马点评】

&#x1f381;&#x1f381;资源文件分享 链接&#xff1a;https://pan.baidu.com/s/1189u6u4icQYHg_9_7ovWmA?pwdeh11 提取码&#xff1a;eh11 基于session和redis实现登录 &#xff08;一&#xff09;前言&#xff08;二&#xff09;导入资源&#xff08;三&#xff09;短信…

每日学术速递5.26

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV 1.Text2NeRF: Text-Driven 3D Scene Generation with Neural Radiance Fields 标题&#xff1a;Text2NeRF&#xff1a;具有神经辐射场的文本驱动 3D 场景生成 作者&#xff1a;Jingb…

Python程序设计基础:标识符、变量与赋值、输入输出

文章目录 一、标识符二、变量与赋值三、输入输出 一、标识符 Python对每个标识符的命名存在要求&#xff1a; 1、每个标识符必须以字母或下划线“_”开头&#xff0c;后跟字母、数字或下划线的任意序列。根据这个规则&#xff0c;以下都是Python中的合法名称&#xff1a;a&…

史上最全测试开发工具推荐(含自动化、性能、稳定性、抓包)

目录 一、UI自动化测试工具 1. uiautomator2 2. Appium 3. ATX-Test 4. Airtest 5. ATXServer2 6. STF 7. Appetizer 二、APP稳定性测试工具 8. UICrawler 9. Maxim 10. AppCrawler 三、APP性能测试工具 11. SoloPi 12. GT 四、抓包工具 13. AnyProxy 14. mi…

【滤波】设计卡尔曼滤波器

本文主要翻译自rlabbe/Kalman-and-Bayesian-Filters-in-Python的第8章节08-Designing-Kalman-Filters&#xff08;设计卡尔曼滤波器&#xff09;。 %matplotlib inline#format the book import book_format book_format.set_style()简介 在上一章节中&#xff0c;我们讨论了教…

【自然语言处理】【大模型】ChatGLM-6B模型结构代码解析(单机版)

ChatGLM-6B模型结构代码解析(单机版) ​ 本文介绍ChatGLM-6B的模型结构&#xff0c;代码来自https://huggingface.co/THUDM/chatglm-6b/blob/main/modeling_chatglm.py。 相关博客 【自然语言处理】【大模型】ChatGLM-6B模型结构代码解析(单机版) 【自然语言处理】【大模型】BL…

枚举_源码_分析

枚举源码分析 前言 这是所有Java语言枚举类型的公共基类。关于枚举的更多信息&#xff0c;包括编译器合成的隐式声明方法的描述&#xff0c;可以在Java的第8.9节中找到™ 语言规范。 请注意&#xff0c;当使用枚举类型作为集合的类型或映射中键的类型时&#xff0c;可以使用专…

斩获阿里offer,这份258页面试宝典也太顶了....

测试三年有余&#xff0c;很多新学到的技术不能再项目中得到实践&#xff0c;同时薪资的涨幅很低&#xff0c;于是萌生了跳槽大厂的想法 但大厂不是那么容易进的&#xff0c;前面惨败字节&#xff0c;为此我辛苦准备了两个月&#xff0c;又从小公司开始面试了半个月有余&#…