ET1100+STM32F407的从站开发

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这个分支本来是为 PIC24HJ16位 MCU写的SSC 工具将其复用到 STM32 上时直接继承了 PIC24 的内存组织方式即 16 位缓冲区布局而没有必要改成 32 位——因为SPI 传输本身是按字节进行的缓冲区是UINT16[]还是UINT32[]对 SPI 传输没有任何影响STM32F407 的 Cortex-M4 访问UINT16[]和UINT32[]都同样高效不存在性能损失改成CONTROLLER_32BIT 1反而会让GET_MEM_SIZE变为 4 字节对齐造成内存浪费每个字节数据向上对齐到 4 字节PDO/SDO将MAX_MBX_SIZE从 0x0080 改为 0x0100。原因MAX_MBX_SIZE是主站可以协商的邮箱上限。参考工程设为 0x0100256字节允许主站在需要时使用更大的邮箱缓冲区比如读取较长的 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对象索引如0x6000ObjectCode对象类型RECORD结构体/ARRAY数组/VAR单变量SISubIndex 编号0子索引数量1起为实际条目DataType数据类型BOOL、UINT8、UINT16、INT16、BIT2、BIT8对齐用等NameTwinCAT 中显示的名称Default默认值R/Sro只读/rw读写Access留空即可工具自动处理rx/txT可映射到 TxPDOR可映射到 RxPDO留空不可映射CoeRead/CoeWrite自定义读写回调一般留空重要提示截图中的 Usage Notes 说明PDO 映射对象0x1601、0x1A00和 SyncManager 对象0x1C12、0x1C13不需要填写SSC 工具会自动生成0x1000、0x1001、0x1008等标准对象不需要填写同样自动生成8bit 以下的条目不能跨字节边界每个对象总位数需为 8 的倍数用BIT8对齐现在我们需要在//0x6nnx第35行区域下面填入 0x6000 拨码开关输入在//0x7nnx第38行区域下面填入 0x7010 LED 输出。对象 0x6000拨码开关输入TxPDO从站→主站SubIndex 9 的BIT8是对齐填充8个 BOOL各1bit合计8bit加上 SI0 是 UINT88bit共16bit 2字节刚好对齐。SSC 工具自动为每个 RECORD 对象添加 SubIndex0你在 Excel 里再写一行 SI0 就重复了。修改方法删除 0x6000 和 0x7010 中的 SubIndex0 行对象 0x7010LED 输出RxPDO主站→从站表格填写完成后回到 SSC Tool 主界面File → Save保存 Excel 表格改名myapp切换到 SSC Tool 窗口点击Build → Create New Slave Files工具会自动生成el9800appl.h包含TOBJ6000开关结构体和TOBJ7010LED结构体el9800appl.c包含对象字典数组ApplicationObjDic[]和 PDO 映射初始值同时自动生成0x1A00TxPDO映射映射到0x6000、0x1601RxPDO映射映射到0x7010导入有报错SSC中重复定义了SubIndex0删掉重新导入生成文件说明myapp.c应用主文件含main()、APPL_Application()等回调myapp.hPDO 数据结构定义、对象字典由 Excel 自动生成el9800hw.c硬件抽象层名字不变固定为此名el9800hw.h硬件宏接口名字不变ecatslv.c/h协议栈核心不变mailbox.c/h邮箱不变ecatcoe.c/hCoE不变ecatappl.c/h应用调度不变ecat_def.h配置宏不变参考工程的el9800appl.c/h替换为你生成的myapp.c/h在Myappl.c中找到APPL_Application()函数添加 IO 读写逻辑从myappObjects.h中可以看到SSC 为你的对象生成了以下变量名对象变量名成员名0x6000 DI InputsDIInputs0x6000.Switch1~.Switch80x7010 DO OutputsDOOutputs0x7010.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 开关 驱动 LEDvoid 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高字节为 Align0 */ 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 时关闭所有 LEDUINT16 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 /** } */数据流全貌主站写 RxPDOLED控制字节 ↓ APPL_OutputMapping() ── 解包bit → DOOutputs0x7010.LED1~LED8 ↓ APPL_Application() ── DOOutputs0x7010 → 驱动 PG8~PG15 ── 读 PC8~PC15 → DIInputs0x6000.Switch1~8 ↓ APPL_InputMapping() ── 打包bit ← DIInputs0x6000.Switch1~8 ↓ 主站读 TxPDO开关状态字节问题 2el9800hw.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 → SPI1Mode 页面参数值ModeFull-Duplex MasterHardware NSS SignalDisable软件控制CSConfiguration → Parameter Settings 页面参数值说明Frame FormatMotorolaData Size8 BitsFirst BitMSB FirstPrescaler (for Baud Rate)884MHz/8 10.5 MHzClock Polarity (CPOL)HighSPI Mode 3Clock Phase (CPHA)2 EdgeSPI Mode 3CRC CalculationDisabledNSS Signal TypeSoftware引脚确认Pinout 自动分配或手动点击引脚信号引脚SPI1_SCKPB3SPI1_MISOPB4SPI1_MOSIPB5GPIO 配置逐一点击 Pinout 图中的引脚PE2 — ET1100 片选SPI CS点击PE2→ 选GPIO_Output然后在GPIO页配置参数值GPIO output levelHigh默认未选中GPIO modeOutput Push PullGPIO Pull-up/Pull-downNo pull-up, no pull-downSpeedHighUser LabelSPI1_CSPC8 ~ PC15 — 拨码开关输入8个逐一配置点击每个引脚 →GPIO_Input参数值GPIO modeInput modeGPIO Pull-up/Pull-downPull-up拨码ON接地低电平User LabelSW1~SW8PG8 ~ PG15 — LED 输出8个逐一配置点击每个引脚 →GPIO_Output参数值GPIO output levelLow默认熄灭GPIO modeOutput Push PullGPIO Pull-up/Pull-downNo pullSpeedLowUser LabelLED1~LED8EXTI 中断配置3路PE3 — AL Event 中断最重要点击PE3→ 选GPIO_EXTI3参数值GPIO modeExternal Interrupt Mode with Falling edge trigger detectionGPIO Pull-up/Pull-downNo pull-up, no pull-downUser LabelECAT_AL_INTPC1 — DC Sync0 中断点击PC1→ 选GPIO_EXTI1参数值GPIO modeExternal Interrupt Mode with Falling edge triggerPullNo pullUser LabelSYNC0PC2 — DC Sync1 中断点击PC2→ 选GPIO_EXTI2同上。TIM9 配置EtherCAT 心跳定时器左侧Timers → TIM9Mode参数值Clock SourceInternal ClockParameter SettingsAPB2 Timer Clock 168MHzAPB284MHz×2168MHz参数值说明Prescaler83168MHz/(831)2MHzCounter ModeUpCounter Period (ARR)19992MHz/20001kHz即1ms中断auto-reload preloadEnableNVICTIM1_BRK_TIM9开启Preemption Priority 2CubeMX 里 ARR 填 1999对应 StdPeriph 的TIM_Period2000两者含义相同都是计数 0~1999 共 2000 个 tick。el9800hw.h定时器宏对应关系确认#define ECAT_TIMER_INC_P_MS 2000 /* 2MHz计数1ms2000tick */ #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_11位抢占3位子。CubeMX NVIC 设置System Core → NVIC → Priority Group选择1 bit for pre-emption priority, 3 bits for subpriority然后各中断设置中断名称Preemption PrioritySub PriorityEXTI line3 interrupt00EXTI line1 interrupt11EXTI line2 interrupt11TIM1 break interrupt and TIM9 global interrupt1注意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 wireProject Manager 设置接下来是移植仅看个大概-具体操作参考下一篇文章操作清单按顺序执行1. 将 SSC 文件复制到Ethercat/目录并加入 Keil 工程2. 添加Ethercat/Inc到 include path3.ecat_def.h中设置USE_DEFAULT_MAIN 04.el9800hw.h添加上述 STM32 HAL 宏5.el9800hw.c将 SPI 收发替换为HAL_SPI_TransmitReceive6.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 中右键Project→Manage 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: APB2168MHz, Prescaler83 → timer clock2MHz, 1ms2000 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) #endifel9800hw.h修改完毕内容完全正确。解释一下每处改动的原因第34行#include stm32f4xx_hal.hNVIC_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依赖这个值正确。数值推导APB2168MHzTIM9 Prescaler83 → 时钟 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 中断防止重入对应 PE3EXTI3。el9800hw.h完成后接下来按上次分析的顺序还需要操作stm32f4xx_it.c— 添加HAL_GPIO_EXTI_Callback和HAL_TIM_PeriodElapsedCallbackmain.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 中断EXTI3PE3 */ #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 SyncEXTI1PC1, EXTI2PC2 */ #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.c的APPL_Application()中直接用HAL_GPIO_WritePin()不必依赖宏。第四步改造el9800hw.c中的 SPI 读写函数找到HW_EscRead和HW_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 寄存器的地方SPI1BUF、WriteSPI()等全部替换为SPITransfer(byte)。第五步main.c接入 SSC第六步stm32f4xx_it.c挂接 SSC 中断