RT-Thread论坛
直播中

李明聪

8年用户 1233经验值
私信 关注
[问答]

rtt的SPI作从机怎么编程?

如标题,尝试过使用SPI + DMA 作从机,但是rtt的spi的驱动框架好像没有支持!然后我尝试直接调用HAL库去作接收,但是DMA接收出错。
这部分裸机程序(SPI从机+DMA)我已经单独进行验证过了,没有问题。


回帖(1)

jsqueh

2025-10-21 17:56:03

在 RT-Thread 中使用 SPI 作为从机(Slave)模式并配合 DMA 接收数据时,由于框架原生支持有限,需要结合 HAL 库和 RT-Thread 的中断管理机制实现。以下是完整的解决方案:


1. 驱动框架扩展思路


RT-Thread 的 SPI 框架主要面向主机模式,从机模式需自行扩展:



  • 使用 HAL 库底层配置 SPI 从机

  • 手动管理 DMA 传输

  • 通过 RT-Thread 中断管理和同步机制处理数据


2. 关键代码实现


2.1 初始化 SPI 从机 + DMA


#include 
#include
#include

// 定义接收缓冲区
#define BUFFER_SIZE 128
static uint8_t rx_buffer[BUFFER_SIZE];
static struct rt_semaphore dma_sem;

static SPI_HandleTypeDef hspi_slave;

void SPI_IRQHandler(void)
{
    rt_interrupt_enter();
    HAL_SPI_IRQHandler(&hspi_slave);
    rt_interrupt_leave();
}

void DMA_IRQHandler(void)
{
    rt_interrupt_enter();
    HAL_DMA_IRQHandler(hspi_slave.hdmarx);
    rt_interrupt_leave();
}

void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
{
    if(hspi == &hspi_slave) {
        rt_sem_release(&dma_sem);  // 通知数据接收完成
    }
}

static void spi_slave_init(void)
{
    __HAL_RCC_SPI2_CLK_ENABLE();
    __HAL_RCC_DMA1_CLK_ENABLE();

    // 配置SPI为从机模式
    hspi_slave.Instance = SPI2;
    hspi_slave.Init.Mode = SPI_MODE_SLAVE;
    hspi_slave.Init.Direction = SPI_DIRECTION_2LINES;
    hspi_slave.Init.DataSize = SPI_DATASIZE_8BIT;
    hspi_slave.Init.CLKPolarity = SPI_POLARITY_LOW;
    hspi_slave.Init.CLKPhase = SPI_PHASE_1EDGE;
    hspi_slave.Init.NSS = SPI_NSS_SOFT;
    hspi_slave.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
    hspi_slave.Init.FirstBit = SPI_FIRSTBIT_MSB;
    HAL_SPI_Init(&hspi_slave);

    // 配置DMA接收
    hspi_slave.hdmarx = &hdma_spi2_rx;
    hdma_spi2_rx.Instance = DMA1_Channel3;
    hdma_spi2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_spi2_rx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_spi2_rx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_spi2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_spi2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_spi2_rx.Init.Mode = DMA_CIRCULAR;  // 循环模式避免频繁重启
    HAL_DMA_Init(&hdma_spi2_rx);

    // 绑定DMA中断
    rt_hw_interrupt_install(DMA1_Channel3_IRQn, DMA_IRQHandler, RT_NULL, "spi_dma");
    HAL_NVIC_SetPriority(DMA1_Channel3_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(DMA1_Channel3_IRQn);

    // 启动DMA接收
    HAL_SPI_Receive_DMA(&hspi_slave, rx_buffer, BUFFER_SIZE);

    // 创建信号量用于同步
    rt_sem_init(&dma_sem, "spi_sem", 0, RT_IPC_FLAG_FIFO);
}

2.2 数据接收线程


static void spi_slave_thread_entry(void *param)
{
    while (1) {
        // 等待DMA传输完成
        if (rt_sem_take(&dma_sem, RT_WAITING_FOREVER) == RT_EOK) {
            // 处理接收到的数据
            rt_kprintf("Received %d bytesn", BUFFER_SIZE);
            // 此处添加数据处理代码...

            // 可选:重新启动下一次接收(非循环模式时需要)
            // HAL_SPI_Receive_DMA(&hspi_slave, rx_buffer, BUFFER_SIZE);
        }
    }
}

void spi_slave_thread_init()
{
    rt_thread_t thread = rt_thread_create("spi_slv", spi_slave_thread_entry, RT_NULL, 1024, 20, 10);
    rt_thread_startup(thread);
}

3. 关键注意事项




  1. DMA 配置模式



    • 循环模式DMA_CIRCULAR):推荐使用,避免每次传输后重新启动。

    • 非循环模式:需在回调中重启传输,注意总线竞争问题。




  2. 中断优先级管理


    // 确保SPI/DMA中断优先级高于RT-Thread调度锁
    HAL_NVIC_SetPriority(SPI2_IRQn, 1, 0); // 高于最低中断优先级



  3. 缓冲区管理



    • 使用双缓冲技术避免数据覆盖:
      uint8_t rx_buf1[BUFF_SIZE], rx_buf2[BUFF_SIZE];
      HAL_SPI_Receive_DMA(&hspi, rx_buf1, BUFF_SIZE); // 启动第一次

      在回调中切换缓冲区:


      void HAL_SPI_RxCpltCallback(...)
      {
      static uint8_t toggle = 0;
      rt_sem_release(&dma_sem);
      toggle ^= 1;
      HAL_SPI_Receive_DMA(hspi, toggle ? rx_buf2 : rx_buf1, BUFF_SIZE);
      }




  4. 时钟配置冲突



    • 检查 CubeMX 生成的时钟配置与 board.h 是否一致。

    • 确认 SPI 时钟未超过从机最大速率(尤其 STM32 作从机时通常 ≤ fPCLK/2)。




4. 调试建议



  1. DMA 传输错误检查
    void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi)
    {
       rt_kprintf("SPI Error: %dn", hspi->ErrorCode);
    }

  2. 逻辑分析仪验证

    • 使用示波器/逻辑分析仪检查:

      • SCK/CS 信号是否存在

      • MOSI 数据是否与主机发送一致

      • DMA 传输启动时机(应在主机发起传输前)




5. 替代方案建议


若仍存在问题,可尝试以下方案:


// 使用中断模式替代DMA(适合小数据量)
HAL_SPI_Receive_IT(&hspi_slave, rx_buffer, BUFFER_SIZE);

void HAL_SPI_RxCpltCallback(...)
{
    rt_sem_release(&dma_sem);
    HAL_SPI_Receive_IT(&hspi_slave, rx_buffer, BUFFER_SIZE); // 重启
}

完整集成步骤



  1. 将上述代码放入 drv_spi.c 的自定义驱动部分

  2. rt_hw_spi_init() 中初始化从机配置

  3. 创建线程处理数据

  4. main.c 中调用初始化函数



提示:STM32 HAL 库的 SPI 从机 DMA 接收需要严格满足时序:



  • 必须在主机发起通信启动 DMA

  • 循环模式可避免时序问题

  • 检查 NSS 引脚管理(软件模式需手动控制)



通过以上步骤,可实现在 RT-Thread 中稳定运行的 SPI 从机 DMA 接收。最终效果应与裸机程序一致,同时利用 RTOS 的任务管理优势。

举报

更多回帖

发帖
×
20
完善资料,
赚取积分