news 2026/5/21 15:10:04

ET1100+STM32F407的从站开发

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ET1100+STM32F407的从站开发

TWINCAT主站端实现对从站PG8-PG15 → LED 输出的控制,以及读取从站的PC8-PC15 → 拨码开关输入

SSC生成协议栈,除PDO\SDO部分其余均与第一篇文章一致,还有16位要改

为什么 32 位的 STM32F407 却选 16 位?

STM32F407 RAM 缓冲区》SPI1(逐字节传输)》ET1100 ESC DPRAM

通过 SPI 访问 ET1100 时,数据是逐字节串行发送的,STM32 的 32 位总线宽度在这里完全用不上。ET1100 的寄存器和数据区本质上是字节/16位对齐的(EtherCAT 协议本身基于16位对齐设计)。

更重要的历史原因是:_STM32_IO8 = 1这个分支本来是为 PIC24HJ(16位 MCU)写的,SSC 工具将其复用到 STM32 上时,直接继承了 PIC24 的内存组织方式,即 16 位缓冲区布局,而没有必要改成 32 位——因为:

  1. SPI 传输本身是按字节进行的,缓冲区是UINT16[]还是UINT32[]对 SPI 传输没有任何影响
  2. STM32F407 的 Cortex-M4 访问UINT16[]UINT32[]都同样高效,不存在性能损失
  3. 改成CONTROLLER_32BIT = 1反而会让GET_MEM_SIZE变为 4 字节对齐,造成内存浪费(每个字节数据向上对齐到 4 字节)

PDO/SDO

MAX_MBX_SIZE从 0x0080 改为 0x0100。

原因:MAX_MBX_SIZE是主站可以协商的邮箱上限。参考工程设为 0x0100(256字节),允许主站在需要时使用更大的邮箱缓冲区(比如读取较长的 SDO Info 对象名称字符串)。你当前设为 0x0080 与 DEF 相同,意味着邮箱大小被锁死在 128 字节,虽然基本功能能跑,但在 TwinCAT 执行 SDO Info 扫描时可能出现截断。

保存好后,通过 Tool → Application → Create New 弹出 Excel 表格,新生成的表格如下图:
该表格是用来配置对象字典,通过SSC工具导入该表格将自动生成EtherCAT从站代码和XML设备描述文件(ESI)

Excel 对象字典表(Object Dictionary Spreadsheet)。这张表是整个从站开发最核心的配置文件,SSC 工具会根据它自动生成el9800appl.c/h、PDO 映射代码等。下面详细说明如何填写。

列名说明

Index

对象索引,如0x6000

ObjectCode

对象类型:RECORD(结构体)/ARRAY(数组)/VAR(单变量)

SI

SubIndex 编号(0=子索引数量,1起为实际条目)

DataType

数据类型:BOOLUINT8UINT16INT16BIT2BIT8(对齐用)等

Name

TwinCAT 中显示的名称

Default

默认值

R/S

ro(只读)/rw(读写)

Access

留空即可(工具自动处理)

rx/tx

T=可映射到 TxPDO;R=可映射到 RxPDO;留空=不可映射

CoeRead/CoeWrite

自定义读写回调,一般留空

重要提示(截图中的 Usage Notes 说明):

  • PDO 映射对象(0x16010x1A00)和 SyncManager 对象(0x1C120x1C13)不需要填写,SSC 工具会自动生成
  • 0x10000x10010x1008等标准对象不需要填写,同样自动生成
  • 8bit 以下的条目不能跨字节边界(每个对象总位数需为 8 的倍数,用BIT8对齐)

现在我们需要在//0x6nnx(第35行区域)下面填入 0x6000 拨码开关输入,在//0x7nnx(第38行区域)下面填入 0x7010 LED 输出。

对象 0x6000:拨码开关输入(TxPDO,从站→主站)

SubIndex 9 的BIT8是对齐填充:8个 BOOL(各1bit)合计8bit,加上 SI0 是 UINT8(8bit),共16bit = 2字节,刚好对齐。

SSC 工具自动为每个 RECORD 对象添加 SubIndex0,你在 Excel 里再写一行 SI=0 就重复了。

修改方法:删除 0x6000 和 0x7010 中的 SubIndex0 行:

对象 0x7010:LED 输出(RxPDO,主站→从站)

表格填写完成后,回到 SSC Tool 主界面:

  1. File → Save保存 Excel 表格,改名myapp
  2. 切换到 SSC Tool 窗口,点击Build → Create New Slave Files

工具会自动生成:

  • el9800appl.h:包含TOBJ6000(开关结构体)和TOBJ7010(LED结构体)
  • el9800appl.c:包含对象字典数组ApplicationObjDic[]和 PDO 映射初始值
  • 同时自动生成0x1A00(TxPDO映射,映射到0x6000)、0x1601(RxPDO映射,映射到0x7010)

导入有报错,SSC中重复定义了SubIndex0,删掉重新导入

生成文件说明

myapp.c

应用主文件,含main()APPL_Application()等回调

myapp.h

PDO 数据结构定义、对象字典(由 Excel 自动生成)

el9800hw.c

硬件抽象层(名字不变,固定为此名)

el9800hw.h

硬件宏接口(名字不变)

ecatslv.c/h

协议栈核心(不变)

mailbox.c/h

邮箱(不变)

ecatcoe.c/h

CoE(不变)

ecatappl.c/h

应用调度(不变)

ecat_def.h

配置宏(不变)

参考工程的el9800appl.c/h替换为你生成的myapp.c/h

Myappl.c中找到APPL_Application()函数,添加 IO 读写逻辑:

myappObjects.h中可以看到,SSC 为你的对象生成了以下变量名:

对象变量名成员名

0x6000 DI Inputs

DIInputs0x6000

.Switch1~.Switch8

0x7010 DO Outputs

DOOutputs0x7010

.LED1~.LED8

需要修改myapp.c的三个函数

原来三个函数体内的内容是编译警告占位符,不是实际逻辑,必须删掉再填入你的代码:

函数一:APPL_OutputMapping()— 接收主站下发的 LED 控制数据

void APPL_OutputMapping(UINT16* pData) { /* pData 指向 RxPDO 数据区(主站→从站) * 低字节 bit0~bit7 依次对应 LED1~LED8 */ UINT8 led_byte = (UINT8)(*pData & 0x00FF); DOOutputs0x7010.LED1 = (led_byte >> 0) & 0x01; DOOutputs0x7010.LED2 = (led_byte >> 1) & 0x01; DOOutputs0x7010.LED3 = (led_byte >> 2) & 0x01; DOOutputs0x7010.LED4 = (led_byte >> 3) & 0x01; DOOutputs0x7010.LED5 = (led_byte >> 4) & 0x01; DOOutputs0x7010.LED6 = (led_byte >> 5) & 0x01; DOOutputs0x7010.LED7 = (led_byte >> 6) & 0x01; DOOutputs0x7010.LED8 = (led_byte >> 7) & 0x01; }

函数二:APPL_Application()— 读 GPIO 开关 + 驱动 LED

void APPL_Application(void) { /* ---- 读拨码开关 PC8~PC15 → 更新 DIInputs0x6000 ---- */ /* PC 上拉输入:拨码ON接地=低电平,逻辑取反得1 */ DIInputs0x6000.Switch1 = GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_8) ? 0 : 1; DIInputs0x6000.Switch2 = GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_9) ? 0 : 1; DIInputs0x6000.Switch3 = GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_10) ? 0 : 1; DIInputs0x6000.Switch4 = GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_11) ? 0 : 1; DIInputs0x6000.Switch5 = GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_12) ? 0 : 1; DIInputs0x6000.Switch6 = GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_13) ? 0 : 1; DIInputs0x6000.Switch7 = GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_14) ? 0 : 1; DIInputs0x6000.Switch8 = GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_15) ? 0 : 1; /* ---- 从 DOOutputs0x7010 驱动 LED PG8~PG15 ---- */ GPIO_WriteBit(GPIOG, GPIO_Pin_8, DOOutputs0x7010.LED1 ? Bit_SET : Bit_RESET); GPIO_WriteBit(GPIOG, GPIO_Pin_9, DOOutputs0x7010.LED2 ? Bit_SET : Bit_RESET); GPIO_WriteBit(GPIOG, GPIO_Pin_10, DOOutputs0x7010.LED3 ? Bit_SET : Bit_RESET); GPIO_WriteBit(GPIOG, GPIO_Pin_11, DOOutputs0x7010.LED4 ? Bit_SET : Bit_RESET); GPIO_WriteBit(GPIOG, GPIO_Pin_12, DOOutputs0x7010.LED5 ? Bit_SET : Bit_RESET); GPIO_WriteBit(GPIOG, GPIO_Pin_13, DOOutputs0x7010.LED6 ? Bit_SET : Bit_RESET); GPIO_WriteBit(GPIOG, GPIO_Pin_14, DOOutputs0x7010.LED7 ? Bit_SET : Bit_RESET); GPIO_WriteBit(GPIOG, GPIO_Pin_15, DOOutputs0x7010.LED8 ? Bit_SET : Bit_RESET); }

函数三:APPL_InputMapping()— 打包开关状态发给主站

void APPL_InputMapping(UINT16* pData) { /* 将 DIInputs0x6000 各 bit 打包进 TxPDO 数据区(从站→主站) * 低字节 bit0~bit7 依次对应 Switch1~Switch8,高字节为 Align=0 */ UINT8 sw_byte = 0; sw_byte |= (DIInputs0x6000.Switch1 & 0x01) << 0; sw_byte |= (DIInputs0x6000.Switch2 & 0x01) << 1; sw_byte |= (DIInputs0x6000.Switch3 & 0x01) << 2; sw_byte |= (DIInputs0x6000.Switch4 & 0x01) << 3; sw_byte |= (DIInputs0x6000.Switch5 & 0x01) << 4; sw_byte |= (DIInputs0x6000.Switch6 & 0x01) << 5; sw_byte |= (DIInputs0x6000.Switch7 & 0x01) << 6; sw_byte |= (DIInputs0x6000.Switch8 & 0x01) << 7; *pData = (UINT16)sw_byte; /* 高字节 Align 自动为 0 */ }

补充:APPL_StopOutputHandler()— 离开 OP 时关闭所有 LED

UINT16 APPL_StopOutputHandler(void) { GPIO_ResetBits(GPIOG, GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15); return ALSTATUSCODE_NOERROR; }

一处额外修改:main()返回类型

注意myapp.c底部的main()用的是_PIC24判断,而不是_STM32_IO8,在 Keil 中会警告:

#if USE_DEFAULT_MAIN #if _PIC24 int main(void) #else void main(void) // ← Keil 会警告 #endif

将这段改为:

#if USE_DEFAULT_MAIN #if _PIC24 || _STM32_IO8 int main(void) #else void main(void) #endif

有问题main()int返回值但缺少return 0;

myapp.c第 366 行你已改为:

#if _PIC24 || _STM32_IO8 int main(void)

但是 SSC 5.12 的ecat_def.h里没有_STM32_IO8这个宏(SSC 5.12 已删除此宏)。而结尾处return 0;仍在#if _PIC24下,导致:若你在 Keil 全局定义了_STM32_IO8,函数签名是int main(void)但函数体没有return 0;,Keil 会报警告/错误。

修复方法:直接把main()这段改为:

#if USE_DEFAULT_MAIN ///////////////////////////////////////////////////////////////////////////////////////// /** \brief This is the main function *//////////////////////////////////////////////////////////////////////////////////////// int main(void) { HW_Init(); MainInit(); bRunApplication = TRUE; do { MainLoop(); } while (bRunApplication == TRUE); HW_Release(); return 0; } #endif //#if USE_DEFAULT_MAIN /** @} */

数据流全貌

主站写 RxPDO(LED控制字节) ↓ APPL_OutputMapping() ── 解包bit → DOOutputs0x7010.LED1~LED8 ↓ APPL_Application() ── DOOutputs0x7010 → 驱动 PG8~PG15 ── 读 PC8~PC15 → DIInputs0x6000.Switch1~8 ↓ APPL_InputMapping() ── 打包bit ← DIInputs0x6000.Switch1~8 ↓ 主站读 TxPDO(开关状态字节)

问题 2:el9800hw.h缺少所有 STM32 硬件宏——这是移植的核心工作(先去创建32工程再返回这一步)

查看你生成的el9800hw.h,与参考工程相比,以下内容完全缺失,必须手动补充

/* 需要在 el9800hw.h 的 #include 部分添加 */ #include "stm32f4xx.h" #include "SPI1.h" /* 提供 SELECT_SPI / DESELECT_SPI / WR_CMD */
/* 需要添加的硬件宏定义 */ /* 定时器 */ #define ECAT_TIMER_INC_P_MS 2000 #define HW_GetTimer() (TIM9->CNT) #define HW_ClearTimer() ((TIM9->CNT) = 0) /* AL Event 中断控制 */ #define DISABLE_ESC_INT() NVIC_DisableIRQ(EXTI3_IRQn) #define ENABLE_ESC_INT() NVIC_EnableIRQ(EXTI3_IRQn) /* 定时器中断控制 */ #define INIT_ECAT_TIMER TIM_Configuration(10) #define START_ECAT_TIMER TIM_Cmd(TIM9, ENABLE) #define STOP_ECAT_TIMER TIM_Cmd(TIM9, DISABLE) /* ESC 中断初始化 */ #define INIT_ESC_INT EXTI3_Configuration() /* DC Sync 中断初始化 */ #define INIT_SYNC0_INT EXTI1_Configuration() #define INIT_SYNC1_INT EXTI2_Configuration() /* 拨码开关输入 PC8~PC15 */ #define SWITCH_1 GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_8) #define SWITCH_2 GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_9) #define SWITCH_3 GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_10) #define SWITCH_4 GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_11) #define SWITCH_5 GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_12) #define SWITCH_6 GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_13) #define SWITCH_7 GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_14) #define SWITCH_8 GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_15) /* LED 输出 PG8~PG15 */ #define LED_1 PGout(8) #define LED_2 PGout(9) #define LED_3 PGout(10) #define LED_4 PGout(11) #define LED_5 PGout(12) #define LED_6 PGout(13) #define LED_7 PGout(14) #define LED_8 PGout(15)

这些宏添加在el9800hw.h#ifndef _EL9800HW_H_#define _EL9800HW_H_之后、现有宏定义区域内。

STM32基础工程的创建

首先是时钟,选好之后先配置时钟树

然后SPI配置

左侧Connectivity → SPI1

Mode 页面:

参数

Mode

Full-Duplex Master

Hardware NSS Signal

Disable(软件控制CS)

Configuration → Parameter Settings 页面:

参数说明

Frame Format

Motorola

Data Size

8 Bits

First Bit

MSB First

Prescaler (for Baud Rate)

8

84MHz/8 = 10.5 MHz

Clock Polarity (CPOL)

High

SPI Mode 3

Clock Phase (CPHA)

2 Edge

SPI Mode 3

CRC Calculation

Disabled

NSS Signal Type

Software

引脚确认(Pinout 自动分配或手动点击引脚):

信号引脚

SPI1_SCK

PB3

SPI1_MISO

PB4

SPI1_MOSI

PB5

GPIO 配置(逐一点击 Pinout 图中的引脚)

PE2 — ET1100 片选(SPI CS)

点击PE2→ 选GPIO_Output,然后在GPIO页配置:

参数

GPIO output level

High(默认未选中)

GPIO mode

Output Push Pull

GPIO Pull-up/Pull-down

No pull-up, no pull-down

Speed

High

User Label

SPI1_CS

PC8 ~ PC15 — 拨码开关输入(8个,逐一配置)

点击每个引脚 →GPIO_Input

参数

GPIO mode

Input mode

GPIO Pull-up/Pull-down

Pull-up(拨码ON=接地=低电平)

User Label

SW1~SW8

PG8 ~ PG15 — LED 输出(8个,逐一配置)

点击每个引脚 →GPIO_Output

参数

GPIO output level

Low(默认熄灭)

GPIO mode

Output Push Pull

GPIO Pull-up/Pull-down

No pull

Speed

Low

User Label

LED1~LED8

EXTI 中断配置(3路)

PE3 — AL Event 中断(最重要)

点击PE3→ 选GPIO_EXTI3

参数

GPIO mode

External Interrupt Mode with Falling edge trigger detection

GPIO Pull-up/Pull-down

No pull-up, no pull-down

User Label

ECAT_AL_INT

PC1 — DC Sync0 中断

点击PC1→ 选GPIO_EXTI1

参数

GPIO mode

External Interrupt Mode with Falling edge trigger

Pull

No pull

User Label

SYNC0

PC2 — DC Sync1 中断

点击PC2→ 选GPIO_EXTI2,同上。

TIM9 配置(EtherCAT 心跳定时器)

左侧Timers → TIM9

Mode:

参数

Clock Source

Internal Clock

Parameter Settings:

APB2 Timer Clock = 168MHz(APB2=84MHz,×2=168MHz)

参数说明

Prescaler

83

168MHz/(83+1)=2MHz

Counter Mode

Up

Counter Period (ARR)

1999

2MHz/2000=1kHz,即1ms中断

auto-reload preload

Enable

NVIC:TIM1_BRK_TIM9

开启

Preemption Priority = 2

CubeMX 里 ARR 填 1999(对应 StdPeriph 的TIM_Period=2000,两者含义相同,都是计数 0~1999 共 2000 个 tick)。


el9800hw.h定时器宏对应关系确认

#define ECAT_TIMER_INC_P_MS 2000 /* 2MHz计数,1ms=2000tick */ #define HW_GetTimer() (__HAL_TIM_GET_COUNTER(&htim9)) #define HW_ClearTimer() (__HAL_TIM_SET_COUNTER(&htim9, 0))

这三个值与参考工程完全一致,不需要改。

NVIC 优先级配置

左侧System Core → NVIC,设置各中断优先级:

CubeMX 中优先级组选4 bits for pre-emption / 0 bits for subpriority不合适,因为参考工程用的是 Group_1(1位抢占+3位子)。

CubeMX NVIC 设置:

System Core → NVIC → Priority Group选择:1 bit for pre-emption priority, 3 bits for subpriority

然后各中断设置:

中断名称Preemption PrioritySub Priority

EXTI line3 interrupt

0

0

EXTI line1 interrupt

1

1

EXTI line2 interrupt

1

1

TIM1 break interrupt and TIM9 global interrupt

1(注意)

1

注意:参考工程 TIM9 的抢占优先级写的是2,但在NVIC_PriorityGroup_1下抢占优先级只有 0 和 1 两个值(1位),写2实际被截断为0,等效于抢占优先级 0,子优先级 1。这是参考工程的一个小问题。CubeMX 中建议直接填写:

| TIM9 | Preemption: 1 | Sub: 1 |

与 Sync0/Sync1 相同,确保 AL Event(抢占0)仍是最高优先级,三者都低于它即可。

点击SYS→DEBUG选择serial wire

Project Manager 设置

接下来是移植(仅看个大概-具体操作参考下一篇文章)

操作清单(按顺序执行)

  • 1. 将 SSC 文件复制到Ethercat/目录并加入 Keil 工程
  • 2. 添加Ethercat/Inc到 include path
  • 3.ecat_def.h中设置USE_DEFAULT_MAIN 0
  • 4.el9800hw.h添加上述 STM32 HAL 宏
  • 5.el9800hw.c将 SPI 收发替换为HAL_SPI_TransmitReceive
  • 6.main.c加入HW_Init()/MainInit()/MainLoop()
  • 7.stm32f4xx_it.c挂接 4 个中断函数
  • 8. 编译,先排除#warning类警告,再解决 error

第一步:目录结构规划

YourProject/ ├── Core/ │ ├── Src/ │ │ ├── main.c ← CubeMX 生成,需添加调用 │ │ └── stm32f4xx_it.c ← CubeMX 生成,需挂 SSC ISR │ └── Inc/ ├── Ethercat/ ← 新建,放所有 SSC 文件 │ ├── Src/ │ │ ├── myapp.c ← 你修改好的应用层 │ │ ├── ecatslv.c │ │ ├── mailbox.c │ │ ├── ecatcoe.c │ │ ├── ecatappl.c │ │ ├── objdef.c │ │ ├── sdoserv.c │ │ ├── coeappl.c │ │ └── el9800hw.c ← 需要改造为 HAL 版本 │ └── Inc/ │ ├── myapp.h │ ├── myappObjects.h │ ├── ecat_def.h │ ├── ecatslv.h │ ├── mailbox.h │ ├── ecatcoe.h │ ├── ecatappl.h │ ├── applInterface.h │ ├── objdef.h │ ├── sdoserv.h │ ├── coeappl.h │ ├── esc.h │ └── el9800hw.h ← 需要添加 STM32 HAL 宏 ├── Drivers/ ← CubeMX 生成的 HAL 库 └── MDK-ARM/ └── YourProject.uvprojx

第二步:Keil 工程中添加文件和路径

在 Keil 中右键ProjectManage Project Items

  • 新建EtherCAT_Stack组,添加Ethercat/Src/下所有.c文件

Options for Target → C/C++ → Include Paths中追加:

../Ethercat/Inc

Define中追加(CubeMX 已生成了 STM32 的那些,再加):

USE_HAL_DRIVER,STM32F407xx

第三步:改造el9800hw.h——添加 STM32 HAL 宏

文件结构很清晰。需要做两处修改:

第一处:在 Includes 区域补一个 HAL 头文件(NVIC_DisableIRQ等函数需要它)

第二处:在两个空白的注释区域填入4个宏

具体操作,在第33行#include "esc.h"下方、第42行#define ESC_RD上方,插入一行 include:

#include "esc.h" #include "stm32f4xx_hal.h"

然后在两个空白注释区域填入宏定义:

/*--------------------------------------------- - hardware timer settings -----------------------------------------------*/ /* TIM9: APB2=168MHz, Prescaler=83 → timer clock=2MHz, 1ms=2000 ticks */ #define ECAT_TIMER_INC_P_MS 2000 #ifndef HW_GetTimer #define HW_GetTimer() (TIM9->CNT) #endif #ifndef HW_ClearTimer #define HW_ClearTimer() (TIM9->CNT = 0) #endif /*--------------------------------------------- - Interrupt and Timer defines -----------------------------------------------*/ /* AL Event interrupt: PE3 → EXTI3 */ #ifndef DISABLE_ESC_INT #define DISABLE_ESC_INT() NVIC_DisableIRQ(EXTI3_IRQn) #endif #ifndef ENABLE_ESC_INT #define ENABLE_ESC_INT() NVIC_EnableIRQ(EXTI3_IRQn) #endif

el9800hw.h修改完毕,内容完全正确。解释一下每处改动的原因:

第34行#include "stm32f4xx_hal.h"NVIC_DisableIRQ/NVIC_EnableIRQ是 CMSIS 函数,TIM9寄存器结构体也需要 HAL 头带入。不加这个,凡是包含el9800hw.h的 SSC 文件(如ecatslv.c)编译时都会找不到这些符号。

第69行ECAT_TIMER_INC_P_MS 2000ecatappl.c第152行有#warning "Define the timer ticks per ms"检查,ECAT_CheckTimer()内部的 DC 时间戳计算(每次调用加 1000000 ns = 1ms)依赖这个值正确。数值推导:APB2=168MHz,TIM9 Prescaler=83 → 时钟 = 168M/84 = 2MHz → 每毫秒计 2000 个 tick,与参考工程完全一致。

第72~76行HW_GetTimer/HW_ClearTimerecatappl.c中计算总线周期时间时使用这两个宏直接读写TIM9->CNT,用#ifndef保护,将来若需要覆盖也方便。

第83~87行DISABLE_ESC_INT/ENABLE_ESC_INTecatslv.c第255/280行、ecatappl.c第804/814行都用到。作用是在 SM 同步数据处理期间短暂关闭 AL 中断防止重入,对应 PE3(EXTI3)。

el9800hw.h完成后,接下来按上次分析的顺序,还需要操作:

  1. stm32f4xx_it.c— 添加HAL_GPIO_EXTI_CallbackHAL_TIM_PeriodElapsedCallback
  2. main.c— 在初始化后调用HW_Init()/MainInit(),循环里调用MainLoop()

缺口二:stm32f4xx_it.c缺少 HAL 回调函数实现

/* ---- STM32 HAL 平台适配 ---- */ #include "stm32f4xx_hal.h" /* SPI + 片选(PE2) */ extern SPI_HandleTypeDef hspi1; #define SELECT_SPI HAL_GPIO_WritePin(GPIOE, GPIO_PIN_2, GPIO_PIN_RESET) #define DESELECT_SPI HAL_GPIO_WritePin(GPIOE, GPIO_PIN_2, GPIO_PIN_SET) #define SPI_SEND_BYTE(b) ({ \ uint8_t _r; \ HAL_SPI_TransmitReceive(&hspi1, (uint8_t*)&(b), &_r, 1, 10); \ _r; }) /* 定时器(TIM9) */ extern TIM_HandleTypeDef htim9; #define ECAT_TIMER_INC_P_MS 2000 #define HW_GetTimer() __HAL_TIM_GET_COUNTER(&htim9) #define HW_ClearTimer() __HAL_TIM_SET_COUNTER(&htim9, 0) /* AL Event 中断(EXTI3,PE3) */ #define DISABLE_ESC_INT() HAL_NVIC_DisableIRQ(EXTI3_IRQn) #define ENABLE_ESC_INT() HAL_NVIC_EnableIRQ(EXTI3_IRQn) /* 初始化宏(在 HW_Init 中已调用 CubeMX 的 MX_ 函数,这里留空或做二次配置) */ #define INIT_ESC_INT /* 由 CubeMX 的 MX_GPIO_Init() 完成 */ #define INIT_ECAT_TIMER HAL_TIM_Base_Start_IT(&htim9) #define START_ECAT_TIMER HAL_TIM_Base_Start_IT(&htim9) #define STOP_ECAT_TIMER HAL_TIM_Base_Stop_IT(&htim9) /* DC Sync(EXTI1=PC1, EXTI2=PC2) */ #define INIT_SYNC0_INT /* 由 CubeMX 完成 */ #define INIT_SYNC1_INT /* 由 CubeMX 完成 */ #define DISABLE_SYNC0_INT() HAL_NVIC_DisableIRQ(EXTI1_IRQn) #define ENABLE_SYNC0_INT() HAL_NVIC_EnableIRQ(EXTI1_IRQn) #define DISABLE_SYNC1_INT() HAL_NVIC_DisableIRQ(EXTI2_IRQn) #define ENABLE_SYNC1_INT() HAL_NVIC_EnableIRQ(EXTI2_IRQn) /* 拨码开关输入:PC8~PC15 */ #define SWITCH_1 (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_8) == GPIO_PIN_RESET ? 1 : 0) #define SWITCH_2 (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_9) == GPIO_PIN_RESET ? 1 : 0) #define SWITCH_3 (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_10) == GPIO_PIN_RESET ? 1 : 0) #define SWITCH_4 (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_11) == GPIO_PIN_RESET ? 1 : 0) #define SWITCH_5 (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_12) == GPIO_PIN_RESET ? 1 : 0) #define SWITCH_6 (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) == GPIO_PIN_RESET ? 1 : 0) #define SWITCH_7 (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_14) == GPIO_PIN_RESET ? 1 : 0) #define SWITCH_8 (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_15) == GPIO_PIN_RESET ? 1 : 0) /* LED 输出:PG8~PG15 */ #define LED_1 HAL_GPIO_WritePin(GPIOG, GPIO_PIN_8, (GPIO_PinState)(v)) /* 用法特殊,建议改为函数,见 el9800hw.c 中 LED 驱动方式 */

LED 宏因为 HAL 写法需要传值,建议在myapp.cAPPL_Application()中直接用HAL_GPIO_WritePin(),不必依赖宏。

第四步:改造el9800hw.c中的 SPI 读写函数

找到HW_EscReadHW_EscWrite,将底层字节收发替换为 HAL:

/* 原来的 PIC SPI 收发字节函数,替换为 HAL 版本 */ static UINT8 SPITransfer(UINT8 txByte) { UINT8 rxByte = 0; HAL_SPI_TransmitReceive(&hspi1, &txByte, &rxByte, 1, 10); return rxByte; }

然后在HW_EscRead/HW_EscWrite中所有调用原来 PIC SPI 寄存器的地方(SPI1BUFWriteSPI()等)全部替换为SPITransfer(byte)


第五步:main.c接入 SSC

第六步:stm32f4xx_it.c挂接 SSC 中断

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/21 15:08:17

OpenClaw 如何快速配置 Taotoken 聚合大模型 API 端点

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 OpenClaw 如何快速配置 Taotoken 聚合大模型 API 端点 基础教程类&#xff0c;面向希望将 OpenClaw 工具接入 Taotoken 平台的开发…

作者头像 李华
网站建设 2026/5/21 15:07:26

地平线6发售日期是什么时候 怎么远程玩地平线6

作为备受期待的竞速大作&#xff0c;地平线6的动态一直牵动着众多爱好者的心&#xff0c;大家最为关心的地平线6发售日期&#xff0c;官方现已正式公布&#xff0c;定在2026年5月19日全球同步上线。不管是上班间隙想摸鱼体验&#xff0c;还是外出旅行时想念竞速快感&#xff0c…

作者头像 李华
网站建设 2026/5/21 15:07:07

6.模块讲解视频怎么找

模块讲解视频怎么找在资料中找到"模块资料"打开后里面是该项目用到的模块资料&#xff0c;先进行解压解压完可以看到里面有&#xff0c;文档资料、参考代码、视频讲解、无水印图片视频讲解里面有一个视频链接、复制到浏览器即可打开

作者头像 李华
网站建设 2026/5/21 15:07:06

完全解决TranslucentTB透明任务栏VCLibs依赖错误的终极指南

完全解决TranslucentTB透明任务栏VCLibs依赖错误的终极指南 【免费下载链接】TranslucentTB A lightweight utility that makes the Windows taskbar translucent/transparent. 项目地址: https://gitcode.com/gh_mirrors/tr/TranslucentTB TranslucentTB是一款广受欢迎…

作者头像 李华