STM32与SIT2515/MCP2515 CAN模块深度实战指南1. 硬件连接与SPI接口配置在开始编写代码之前确保硬件连接正确至关重要。SIT2515/MCP2515模块与STM32的SPI接口连接需要特别注意电平匹配和信号完整性。典型连接方式SIT2515引脚STM32引脚备注VCC3.3V注意模块工作电压GNDGND共地连接SCKPA5SPI时钟线SI(MOSI)PA7主出从入SO(MISO)PA6主入从出CSPA4片选信号INTPB0中断信号(可选)提示如果使用3.3V供电的STM32与5V模块通信建议使用电平转换芯片或选择兼容3.3V逻辑的CAN模块版本。SPI初始化代码示例基于STM32 HAL库void SPI_Init(void) { hspi1.Instance SPI1; hspi1.Init.Mode SPI_MODE_MASTER; hspi1.Init.Direction SPI_DIRECTION_2LINES; hspi1.Init.DataSize SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity SPI_POLARITY_LOW; hspi1.Init.CLKPhase SPI_PHASE_1EDGE; hspi1.Init.NSS SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler SPI_BAUDRATEPRESCALER_8; hspi1.Init.FirstBit SPI_FIRSTBIT_MSB; hspi1.Init.TIMode SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation SPI_CRCCALCULATION_DISABLE; hspi1.Init.CRCPolynomial 10; if (HAL_SPI_Init(hspi1) ! HAL_OK) { Error_Handler(); } }常见硬件问题排查通信失败首先检查CS信号是否正常拉低SPI时钟是否有输出数据错误确认SPI相位和极性设置与模块要求一致模块不响应测量供电电压检查复位电路是否正常2. CAN控制器初始化与配置SIT2515/MCP2515的初始化流程需要严格遵循芯片的配置顺序这是保证CAN通信稳定的关键。标准初始化步骤硬件复位拉低复位引脚至少2μs进入配置模式设置CANCTRL寄存器配置波特率参数CNF1/2/3寄存器设置接收过滤器和掩码切换回正常模式波特率配置示例代码bool CAN_SetBaudRate(uint32_t baudrate) { uint8_t cnf1, cnf2, cnf3; switch(baudrate) { case 1000000: // 1Mbps cnf1 0x00; // BRP0, SJW1TQ cnf2 0xD0; // PRSEG5TQ, PHSEG16TQ cnf3 0x82; // PHSEG23TQ break; case 500000: // 500kbps cnf1 0x00; // BRP0, SJW1TQ cnf2 0x90; // PRSEG1TQ, PHSEG13TQ cnf3 0x82; // PHSEG23TQ break; case 250000: // 250kbps cnf1 0x01; // BRP1, SJW1TQ cnf2 0xB5; // PRSEG4TQ, PHSEG18TQ cnf3 0x82; // PHSEG23TQ break; default: return false; } CAN_WriteRegister(CNF1, cnf1); CAN_WriteRegister(CNF2, cnf2); CAN_WriteRegister(CNF3, cnf3); return true; }注意配置波特率时必须在配置模式下进行修改后需要切换回正常或监听模式才能通信。波特率计算要点CAN总线波特率由以下参数决定BRP (Baud Rate Prescaler)时钟预分频Sync Seg固定1TQProp Seg传播时间段Phase Seg1相位缓冲段1Phase Seg2相位缓冲段2计算公式波特率 Fosc / (BRP × (1 Prop Seg Phase Seg1 Phase Seg2))3. 消息发送与接收实现CAN消息的收发是驱动最核心的功能需要处理好缓冲区管理和时序控制。3.1 消息发送实现发送消息的基本流程检查可用发送缓冲区配置消息标识符和类型标准/扩展帧写入数据内容触发发送请求发送函数代码示例bool CAN_SendMessage(CAN_Message_t *msg) { uint8_t txb_ctrl, txb_sidh, txb_sidl, txb_eid8, txb_eid0, txb_dlc; uint8_t txb_d0 0; // 查找空闲发送缓冲区 uint8_t txb_status CAN_ReadRegister(TXB0CTRL); if((txb_status 0x08) 0) { txb_ctrl TXB0CTRL; txb_sidh TXB0SIDH; txb_sidl TXB0SIDL; txb_eid8 TXB0EID8; txb_eid0 TXB0EID0; txb_dlc TXB0DLC; txb_d0 TXB0D0; } else if((CAN_ReadRegister(TXB1CTRL) 0x08) 0) { txb_ctrl TXB1CTRL; txb_sidh TXB1SIDH; txb_sidl TXB1SIDL; txb_eid8 TXB1EID8; txb_eid0 TXB1EID0; txb_dlc TXB1DLC; txb_d0 TXB1D0; } else if((CAN_ReadRegister(TXB2CTRL) 0x08) 0) { txb_ctrl TXB2CTRL; txb_sidh TXB2SIDH; txb_sidl TXB2SIDL; txb_eid8 TXB2EID8; txb_eid0 TXB2EID0; txb_dlc TXB2DLC; txb_d0 TXB2D0; } else { return false; // 所有发送缓冲区都忙 } // 配置消息标识符 if(msg-ide CAN_ID_STD) { // 标准帧 CAN_WriteRegister(txb_sidh, (msg-id 3) 0xFF); CAN_WriteRegister(txb_sidl, (msg-id 5) 0xE0); } else { // 扩展帧 CAN_WriteRegister(txb_sidh, (msg-id 21) 0xFF); CAN_WriteRegister(txb_sidl, ((msg-id 13) 0xE0) | 0x08 | ((msg-id 16) 0x03)); CAN_WriteRegister(txb_eid8, (msg-id 8) 0xFF); CAN_WriteRegister(txb_eid0, msg-id 0xFF); } // 写入数据 for(uint8_t i 0; i msg-dlc; i) { CAN_WriteRegister(txb_d0 i, msg-data[i]); } // 设置DLC和帧类型 uint8_t dlc_reg msg-dlc; if(msg-rtr) { dlc_reg | 0x40; // 远程帧 } CAN_WriteRegister(txb_dlc, dlc_reg); // 触发发送 CAN_WriteRegister(txb_ctrl, 0x08); return true; }3.2 消息接收处理接收消息可以通过轮询或中断方式实现。中断方式更为高效适合实时性要求高的应用。中断接收配置步骤配置INT引脚为外部中断输入使能CAN控制器接收中断在中断服务程序中读取消息中断初始化代码void CAN_Interrupt_Init(void) { // 配置INT引脚为外部中断 GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin GPIO_PIN_0; GPIO_InitStruct.Mode GPIO_MODE_IT_FALLING; GPIO_InitStruct.Pull GPIO_PULLUP; HAL_GPIO_Init(GPIOB, GPIO_InitStruct); // 设置NVIC HAL_NVIC_SetPriority(EXTI0_IRQn, 5, 0); HAL_NVIC_EnableIRQ(EXTI0_IRQn); // 使能CAN接收中断 CAN_WriteRegister(CANINTE, RX0IE_ENABLED | RX1IE_ENABLED); }中断服务程序示例void EXTI0_IRQHandler(void) { if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_0) ! RESET) { CAN_Message_t rx_msg; if(CAN_ReceiveMessage(rx_msg)) { // 处理接收到的消息 Process_CAN_Message(rx_msg); } __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0); } }4. 高级功能与性能优化4.1 过滤器配置技巧SIT2515/MCP2515提供强大的过滤功能合理配置可以大幅减轻MCU负担。过滤器配置策略标准帧过滤器适用于11位标识符扩展帧过滤器适用于29位标识符掩码设置决定哪些位需要严格匹配标准帧过滤器配置示例void CAN_SetStandardFilter(uint16_t filter_id, uint16_t mask) { // 设置过滤器0 CAN_WriteRegister(RXF0SIDH, filter_id 3); CAN_WriteRegister(RXF0SIDL, (filter_id 5) 0xE0); // 设置掩码0 CAN_WriteRegister(RXM0SIDH, mask 3); CAN_WriteRegister(RXM0SIDL, (mask 5) 0xE0); }4.2 错误处理与状态监测良好的错误处理机制可以提高系统可靠性。关键错误检测包括总线关闭状态检测错误被动状态检测接收错误计数器监测发送错误计数器监测错误状态监测代码void CAN_CheckErrorStatus(void) { uint8_t eflg CAN_ReadRegister(EFLG); if(eflg 0x20) { // 总线关闭状态 Handle_BusOff(); } else if(eflg 0x10) { // 错误被动状态 Handle_ErrorPassive(); } uint8_t tec CAN_ReadRegister(TEC); uint8_t rec CAN_ReadRegister(REC); if(tec 96 || rec 96) { // 接近错误被动状态 Handle_NearErrorPassive(); } }4.3 低功耗模式实现对于电池供电设备低功耗模式尤为重要。SIT2515/MCP2515支持睡眠模式。睡眠模式切换代码void CAN_EnterSleepMode(void) { // 请求进入睡眠模式 CAN_WriteRegister(CANCTRL, REQOP_SLEEP); // 等待确认 uint8_t timeout 100; while((CAN_ReadRegister(CANSTAT) 0xE0) ! REQOP_SLEEP timeout--); if(timeout 0) { // 进入睡眠模式失败 Handle_SleepModeError(); } } void CAN_WakeUp(void) { // 通过SPI访问唤醒芯片 CS_LOW(); SPI_TransferByte(0x00); // 发送任意命令 CS_HIGH(); // 等待回到正常模式 uint8_t timeout 100; while((CAN_ReadRegister(CANSTAT) 0xE0) ! REQOP_NORMAL timeout--); if(timeout 0) { // 唤醒失败 Handle_WakeUpError(); } }5. 实战调试技巧与常见问题5.1 调试工具推荐逻辑分析仪用于观察SPI通信时序CAN总线分析仪如PCAN-USB, ZLG CAN分析仪示波器检查信号质量和总线电平5.2 常见问题排查指南问题现象可能原因解决方案无法进入配置模式SPI通信失败检查CS信号和SPI时序发送消息失败波特率不匹配确认两端波特率设置一致接收不到消息过滤器配置错误检查过滤器和掩码设置通信不稳定终端电阻缺失在总线两端添加120Ω终端电阻频繁错误帧总线负载过高降低发送频率或提高波特率5.3 性能优化建议SPI时钟优化在保证稳定的前提下尽可能提高SPI时钟频率中断优化合理设置中断优先级避免丢失消息缓冲区管理实现双缓冲或多缓冲机制提高吞吐量DMA传输对于支持DMA的STM32型号使用DMA传输SPI数据SPI DMA配置示例void SPI_DMA_Init(void) { // 配置TX DMA hdma_tx.Instance DMA1_Channel3; hdma_tx.Init.Direction DMA_MEMORY_TO_PERIPH; hdma_tx.Init.PeriphInc DMA_PINC_DISABLE; hdma_tx.Init.MemInc DMA_MINC_ENABLE; hdma_tx.Init.PeriphDataAlignment DMA_PDATAALIGN_BYTE; hdma_tx.Init.MemDataAlignment DMA_MDATAALIGN_BYTE; hdma_tx.Init.Mode DMA_NORMAL; hdma_tx.Init.Priority DMA_PRIORITY_HIGH; HAL_DMA_Init(hdma_tx); __HAL_LINKDMA(hspi1, hdmatx, hdma_tx); // 配置RX DMA hdma_rx.Instance DMA1_Channel2; hdma_rx.Init.Direction DMA_PERIPH_TO_MEMORY; hdma_rx.Init.PeriphInc DMA_PINC_DISABLE; hdma_rx.Init.MemInc DMA_MINC_ENABLE; hdma_rx.Init.PeriphDataAlignment DMA_PDATAALIGN_BYTE; hdma_rx.Init.MemDataAlignment DMA_MDATAALIGN_BYTE; hdma_rx.Init.Mode DMA_NORMAL; hdma_rx.Init.Priority DMA_PRIORITY_HIGH; HAL_DMA_Init(hdma_rx); __HAL_LINKDMA(hspi1, hdmarx, hdma_rx); // 使能SPI DMA SET_BIT(hspi1.Instance-CR2, SPI_CR2_TXDMAEN | SPI_CR2_RXDMAEN); }
手把手教你用STM32的SPI驱动SIT2515/MCP2515 CAN模块(附完整代码)
STM32与SIT2515/MCP2515 CAN模块深度实战指南1. 硬件连接与SPI接口配置在开始编写代码之前确保硬件连接正确至关重要。SIT2515/MCP2515模块与STM32的SPI接口连接需要特别注意电平匹配和信号完整性。典型连接方式SIT2515引脚STM32引脚备注VCC3.3V注意模块工作电压GNDGND共地连接SCKPA5SPI时钟线SI(MOSI)PA7主出从入SO(MISO)PA6主入从出CSPA4片选信号INTPB0中断信号(可选)提示如果使用3.3V供电的STM32与5V模块通信建议使用电平转换芯片或选择兼容3.3V逻辑的CAN模块版本。SPI初始化代码示例基于STM32 HAL库void SPI_Init(void) { hspi1.Instance SPI1; hspi1.Init.Mode SPI_MODE_MASTER; hspi1.Init.Direction SPI_DIRECTION_2LINES; hspi1.Init.DataSize SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity SPI_POLARITY_LOW; hspi1.Init.CLKPhase SPI_PHASE_1EDGE; hspi1.Init.NSS SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler SPI_BAUDRATEPRESCALER_8; hspi1.Init.FirstBit SPI_FIRSTBIT_MSB; hspi1.Init.TIMode SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation SPI_CRCCALCULATION_DISABLE; hspi1.Init.CRCPolynomial 10; if (HAL_SPI_Init(hspi1) ! HAL_OK) { Error_Handler(); } }常见硬件问题排查通信失败首先检查CS信号是否正常拉低SPI时钟是否有输出数据错误确认SPI相位和极性设置与模块要求一致模块不响应测量供电电压检查复位电路是否正常2. CAN控制器初始化与配置SIT2515/MCP2515的初始化流程需要严格遵循芯片的配置顺序这是保证CAN通信稳定的关键。标准初始化步骤硬件复位拉低复位引脚至少2μs进入配置模式设置CANCTRL寄存器配置波特率参数CNF1/2/3寄存器设置接收过滤器和掩码切换回正常模式波特率配置示例代码bool CAN_SetBaudRate(uint32_t baudrate) { uint8_t cnf1, cnf2, cnf3; switch(baudrate) { case 1000000: // 1Mbps cnf1 0x00; // BRP0, SJW1TQ cnf2 0xD0; // PRSEG5TQ, PHSEG16TQ cnf3 0x82; // PHSEG23TQ break; case 500000: // 500kbps cnf1 0x00; // BRP0, SJW1TQ cnf2 0x90; // PRSEG1TQ, PHSEG13TQ cnf3 0x82; // PHSEG23TQ break; case 250000: // 250kbps cnf1 0x01; // BRP1, SJW1TQ cnf2 0xB5; // PRSEG4TQ, PHSEG18TQ cnf3 0x82; // PHSEG23TQ break; default: return false; } CAN_WriteRegister(CNF1, cnf1); CAN_WriteRegister(CNF2, cnf2); CAN_WriteRegister(CNF3, cnf3); return true; }注意配置波特率时必须在配置模式下进行修改后需要切换回正常或监听模式才能通信。波特率计算要点CAN总线波特率由以下参数决定BRP (Baud Rate Prescaler)时钟预分频Sync Seg固定1TQProp Seg传播时间段Phase Seg1相位缓冲段1Phase Seg2相位缓冲段2计算公式波特率 Fosc / (BRP × (1 Prop Seg Phase Seg1 Phase Seg2))3. 消息发送与接收实现CAN消息的收发是驱动最核心的功能需要处理好缓冲区管理和时序控制。3.1 消息发送实现发送消息的基本流程检查可用发送缓冲区配置消息标识符和类型标准/扩展帧写入数据内容触发发送请求发送函数代码示例bool CAN_SendMessage(CAN_Message_t *msg) { uint8_t txb_ctrl, txb_sidh, txb_sidl, txb_eid8, txb_eid0, txb_dlc; uint8_t txb_d0 0; // 查找空闲发送缓冲区 uint8_t txb_status CAN_ReadRegister(TXB0CTRL); if((txb_status 0x08) 0) { txb_ctrl TXB0CTRL; txb_sidh TXB0SIDH; txb_sidl TXB0SIDL; txb_eid8 TXB0EID8; txb_eid0 TXB0EID0; txb_dlc TXB0DLC; txb_d0 TXB0D0; } else if((CAN_ReadRegister(TXB1CTRL) 0x08) 0) { txb_ctrl TXB1CTRL; txb_sidh TXB1SIDH; txb_sidl TXB1SIDL; txb_eid8 TXB1EID8; txb_eid0 TXB1EID0; txb_dlc TXB1DLC; txb_d0 TXB1D0; } else if((CAN_ReadRegister(TXB2CTRL) 0x08) 0) { txb_ctrl TXB2CTRL; txb_sidh TXB2SIDH; txb_sidl TXB2SIDL; txb_eid8 TXB2EID8; txb_eid0 TXB2EID0; txb_dlc TXB2DLC; txb_d0 TXB2D0; } else { return false; // 所有发送缓冲区都忙 } // 配置消息标识符 if(msg-ide CAN_ID_STD) { // 标准帧 CAN_WriteRegister(txb_sidh, (msg-id 3) 0xFF); CAN_WriteRegister(txb_sidl, (msg-id 5) 0xE0); } else { // 扩展帧 CAN_WriteRegister(txb_sidh, (msg-id 21) 0xFF); CAN_WriteRegister(txb_sidl, ((msg-id 13) 0xE0) | 0x08 | ((msg-id 16) 0x03)); CAN_WriteRegister(txb_eid8, (msg-id 8) 0xFF); CAN_WriteRegister(txb_eid0, msg-id 0xFF); } // 写入数据 for(uint8_t i 0; i msg-dlc; i) { CAN_WriteRegister(txb_d0 i, msg-data[i]); } // 设置DLC和帧类型 uint8_t dlc_reg msg-dlc; if(msg-rtr) { dlc_reg | 0x40; // 远程帧 } CAN_WriteRegister(txb_dlc, dlc_reg); // 触发发送 CAN_WriteRegister(txb_ctrl, 0x08); return true; }3.2 消息接收处理接收消息可以通过轮询或中断方式实现。中断方式更为高效适合实时性要求高的应用。中断接收配置步骤配置INT引脚为外部中断输入使能CAN控制器接收中断在中断服务程序中读取消息中断初始化代码void CAN_Interrupt_Init(void) { // 配置INT引脚为外部中断 GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin GPIO_PIN_0; GPIO_InitStruct.Mode GPIO_MODE_IT_FALLING; GPIO_InitStruct.Pull GPIO_PULLUP; HAL_GPIO_Init(GPIOB, GPIO_InitStruct); // 设置NVIC HAL_NVIC_SetPriority(EXTI0_IRQn, 5, 0); HAL_NVIC_EnableIRQ(EXTI0_IRQn); // 使能CAN接收中断 CAN_WriteRegister(CANINTE, RX0IE_ENABLED | RX1IE_ENABLED); }中断服务程序示例void EXTI0_IRQHandler(void) { if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_0) ! RESET) { CAN_Message_t rx_msg; if(CAN_ReceiveMessage(rx_msg)) { // 处理接收到的消息 Process_CAN_Message(rx_msg); } __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0); } }4. 高级功能与性能优化4.1 过滤器配置技巧SIT2515/MCP2515提供强大的过滤功能合理配置可以大幅减轻MCU负担。过滤器配置策略标准帧过滤器适用于11位标识符扩展帧过滤器适用于29位标识符掩码设置决定哪些位需要严格匹配标准帧过滤器配置示例void CAN_SetStandardFilter(uint16_t filter_id, uint16_t mask) { // 设置过滤器0 CAN_WriteRegister(RXF0SIDH, filter_id 3); CAN_WriteRegister(RXF0SIDL, (filter_id 5) 0xE0); // 设置掩码0 CAN_WriteRegister(RXM0SIDH, mask 3); CAN_WriteRegister(RXM0SIDL, (mask 5) 0xE0); }4.2 错误处理与状态监测良好的错误处理机制可以提高系统可靠性。关键错误检测包括总线关闭状态检测错误被动状态检测接收错误计数器监测发送错误计数器监测错误状态监测代码void CAN_CheckErrorStatus(void) { uint8_t eflg CAN_ReadRegister(EFLG); if(eflg 0x20) { // 总线关闭状态 Handle_BusOff(); } else if(eflg 0x10) { // 错误被动状态 Handle_ErrorPassive(); } uint8_t tec CAN_ReadRegister(TEC); uint8_t rec CAN_ReadRegister(REC); if(tec 96 || rec 96) { // 接近错误被动状态 Handle_NearErrorPassive(); } }4.3 低功耗模式实现对于电池供电设备低功耗模式尤为重要。SIT2515/MCP2515支持睡眠模式。睡眠模式切换代码void CAN_EnterSleepMode(void) { // 请求进入睡眠模式 CAN_WriteRegister(CANCTRL, REQOP_SLEEP); // 等待确认 uint8_t timeout 100; while((CAN_ReadRegister(CANSTAT) 0xE0) ! REQOP_SLEEP timeout--); if(timeout 0) { // 进入睡眠模式失败 Handle_SleepModeError(); } } void CAN_WakeUp(void) { // 通过SPI访问唤醒芯片 CS_LOW(); SPI_TransferByte(0x00); // 发送任意命令 CS_HIGH(); // 等待回到正常模式 uint8_t timeout 100; while((CAN_ReadRegister(CANSTAT) 0xE0) ! REQOP_NORMAL timeout--); if(timeout 0) { // 唤醒失败 Handle_WakeUpError(); } }5. 实战调试技巧与常见问题5.1 调试工具推荐逻辑分析仪用于观察SPI通信时序CAN总线分析仪如PCAN-USB, ZLG CAN分析仪示波器检查信号质量和总线电平5.2 常见问题排查指南问题现象可能原因解决方案无法进入配置模式SPI通信失败检查CS信号和SPI时序发送消息失败波特率不匹配确认两端波特率设置一致接收不到消息过滤器配置错误检查过滤器和掩码设置通信不稳定终端电阻缺失在总线两端添加120Ω终端电阻频繁错误帧总线负载过高降低发送频率或提高波特率5.3 性能优化建议SPI时钟优化在保证稳定的前提下尽可能提高SPI时钟频率中断优化合理设置中断优先级避免丢失消息缓冲区管理实现双缓冲或多缓冲机制提高吞吐量DMA传输对于支持DMA的STM32型号使用DMA传输SPI数据SPI DMA配置示例void SPI_DMA_Init(void) { // 配置TX DMA hdma_tx.Instance DMA1_Channel3; hdma_tx.Init.Direction DMA_MEMORY_TO_PERIPH; hdma_tx.Init.PeriphInc DMA_PINC_DISABLE; hdma_tx.Init.MemInc DMA_MINC_ENABLE; hdma_tx.Init.PeriphDataAlignment DMA_PDATAALIGN_BYTE; hdma_tx.Init.MemDataAlignment DMA_MDATAALIGN_BYTE; hdma_tx.Init.Mode DMA_NORMAL; hdma_tx.Init.Priority DMA_PRIORITY_HIGH; HAL_DMA_Init(hdma_tx); __HAL_LINKDMA(hspi1, hdmatx, hdma_tx); // 配置RX DMA hdma_rx.Instance DMA1_Channel2; hdma_rx.Init.Direction DMA_PERIPH_TO_MEMORY; hdma_rx.Init.PeriphInc DMA_PINC_DISABLE; hdma_rx.Init.MemInc DMA_MINC_ENABLE; hdma_rx.Init.PeriphDataAlignment DMA_PDATAALIGN_BYTE; hdma_rx.Init.MemDataAlignment DMA_MDATAALIGN_BYTE; hdma_rx.Init.Mode DMA_NORMAL; hdma_rx.Init.Priority DMA_PRIORITY_HIGH; HAL_DMA_Init(hdma_rx); __HAL_LINKDMA(hspi1, hdmarx, hdma_rx); // 使能SPI DMA SET_BIT(hspi1.Instance-CR2, SPI_CR2_TXDMAEN | SPI_CR2_RXDMAEN); }