【原理学习】rt-thread SPI 双向DMA驱动的开发

问题描述

之前开发程序的时候是直接使用的 硬件 SPI 采用 DMA 方式来释放CPU,但是一直没有使用 rt_thread 原来的 DMA 历程,原因是原生的 drv_spi 驱动,实际上没有发挥DMA的真实性能,虽然开启DMA但是还是要等数据发送完成之后才释放CPU:

当发送数据较长,且比较密集的时候如果采用死等的方发送数据就会导致数据发送性能极差,如何将这个死等的时间释放出来且发送和接收数据需要灵活可控
先看电路图:
在这里插入图片描述
说明:此时SPI是通讯的交互的重点,十分密集
IRQ_NRF1: 值做个32的一中断通知管脚,从机有数据上报的时候需要通过此引脚,通知32来读取数据
CE_NRF1: 表示从机设备是否繁忙,若果繁忙在主发送数据无效(暂时还未使用)
其他就是正常的SPI管脚

第一步释放驱动的死等时间

static rt_uint32_t spixfer(struct rt_spi_device *device, struct rt_spi_message *message)
{
    HAL_StatusTypeDef state;
    rt_size_t message_length, already_send_length;
    rt_uint16_t send_length;
    rt_uint8_t *recv_buf;
    const rt_uint8_t *send_buf;

    RT_ASSERT(device != RT_NULL);
    RT_ASSERT(device->bus != RT_NULL);
    RT_ASSERT(device->bus->parent.user_data != RT_NULL);
    RT_ASSERT(message != RT_NULL);

    struct stm32_spi *spi_drv =  rt_container_of(device->bus, struct stm32_spi, spi_bus);
    SPI_HandleTypeDef *spi_handle = &spi_drv->handle;
    struct stm32_hw_spi_cs *cs = device->parent.user_data;

    if (message->cs_take)
    {
        LOG_D("%s transfer nss set 0 ", spi_drv->config->bus_name);
        HAL_GPIO_WritePin(cs->GPIOx, cs->GPIO_Pin, GPIO_PIN_RESET);
    }

    LOG_D("%s transfer prepare and start", spi_drv->config->bus_name);
    LOG_D("%s sendbuf: %X, recvbuf: %X, length: %d",
          spi_drv->config->bus_name,
          (uint32_t)message->send_buf,
          (uint32_t)message->recv_buf, message->length);

    message_length = message->length;
    recv_buf = message->recv_buf;
    send_buf = message->send_buf;
    while (message_length)
    {
        /* the HAL library use uint16 to save the data length */
        if (message_length > 65535)
        {
            send_length = 65535;
            message_length = message_length - 65535;
        }
        else
        {
            send_length = message_length;
            message_length = 0;
        }

        /* calculate the start address */
        already_send_length = message->length - send_length - message_length;
        send_buf = (rt_uint8_t *)message->send_buf + already_send_length;
        recv_buf = (rt_uint8_t *)message->recv_buf + already_send_length;

        /* start once data exchange in DMA mode */
        if (message->send_buf && message->recv_buf)
        {
            if ((spi_drv->spi_dma_flag & SPI_USING_TX_DMA_FLAG) && (spi_drv->spi_dma_flag & SPI_USING_RX_DMA_FLAG))
            {
                state = HAL_SPI_TransmitReceive_DMA(spi_handle, (uint8_t *)send_buf, (uint8_t *)recv_buf, send_length);
            }
            else
            {
                state = HAL_SPI_TransmitReceive(spi_handle, (uint8_t *)send_buf, (uint8_t *)recv_buf, send_length, 1000);
            }
        }
        else if (message->send_buf)
        {
            if (spi_drv->spi_dma_flag & SPI_USING_TX_DMA_FLAG)
            {
                state = HAL_SPI_Transmit_DMA(spi_handle, (uint8_t *)send_buf, send_length);
            }
            else
            {
                state = HAL_SPI_Transmit(spi_handle, (uint8_t *)send_buf, send_length, 1000);
            }
        }
        else
        {
            memset((uint8_t *)recv_buf, 0xff, send_length);
            if (spi_drv->spi_dma_flag & SPI_USING_RX_DMA_FLAG)
            {
                state = HAL_SPI_Receive_DMA(spi_handle, (uint8_t *)recv_buf, send_length);
            }
            else
            {
                state = HAL_SPI_Receive(spi_handle, (uint8_t *)recv_buf, send_length, 1000);
            }
        }

        if (state != HAL_OK)
        {
            LOG_I("spi transfer error : %d", state);
            message->length = 0;
            spi_handle->State = HAL_SPI_STATE_READY;
        }
        else
        {
            LOG_D("%s transfer done", spi_drv->config->bus_name);
        }

        /* For simplicity reasons, this example is just waiting till the end of the
           transfer, but application may perform other tasks while transfer operation
           is ongoing. */
+        if ((spi_drv->spi_dma_flag & SPI_USING_TX_DMA_FLAG) && (spi_drv->spi_dma_flag & SPI_USING_RX_DMA_FLAG))
+        {
+            // 释放 CPU 在发送完成中断中去看数据是否发送完成
+            // HAL_SPI_ErrorCallback 或 HAL_SPI_TxRxCpltCallback 完成 NSS与信号量的释放工作
+         }
+        else
          while (HAL_SPI_GetState(spi_handle) != HAL_SPI_STATE_READY);
    }

+    if ((spi_drv->spi_dma_flag & SPI_USING_TX_DMA_FLAG) && (spi_drv->spi_dma_flag & SPI_USING_RX_DMA_FLAG))
+    {
+        // 释放 CPU 在发送完成中断中去看数据是否发送完成与上来管脚
+        // HAL_SPI_ErrorCallback 或 HAL_SPI_TxRxCpltCallback 完成 NSS与信号量的释放工作
+    }
+    else
+    {
        if (message->cs_release)
        {
            HAL_GPIO_WritePin(cs->GPIOx, cs->GPIO_Pin, GPIO_PIN_SET);
            LOG_D("%s transfer nss set 1 \r\n", spi_drv->config->bus_name);
        }
+    }

    return message->length;
}

在驱动中我们不需要死等,那么数据传输完成的时候就需要,在HAL的DMA传输完成中断的回调函数中实现数据通知给SPI发送数据线程,保证数据不再在发送的时候被打断 (注意:此处我是将SPI1和SPI2 双向都是开启了DMA收发功能)

此处我们使用信号量的方式管理
核心管理变量

static struct rt_spi_device esb_rx;    // 驱动设备
static struct stm32_gpio_pin rx_nss;   // 硬件片选管脚
struct rt_messagequeue esb_rx_mq;      // 发送数据邮箱,接收其他线程需要发送的数据
static rt_uint8_t esb_rx_mq_pool[ESB_RX_PAC_LEN*ESB_RX_BUF_SUM]; // 发送缓存
rt_sem_t esb_rx_hw_sem;   // 管理信号量
uint8_t resb_rx_evt = 0;  // 数据发送状态: 0 发送 1:中断接收开始 2:中断接收完成
uint8_t resb_hw_idle = 0; // 硬件空闲状态,启动DMA发发送为1, DMA传输完成为0

驱动初始化

#define ESB_RX_NSS_PIN GET_PIN(A, 4)
#define ESB_RX_IRQ_PIN GET_PIN(C, 5)
#define ESB_TX_NSS_PIN GET_PIN(C, 11)
#define ESB_TX_IRQ_PIN GET_PIN(D, 2)

#define ESB_RX_PAC_LEN (256)
#define ESB_RX_BUF_SUM (4)

/*******************************************************************************
  * Function Name  : drv_esb_rx_init
  * Description    : esb 模块初始化部分
  * Input          : None
  * Output         : None
  * Return         : None
*******************************************************************************/
void drv_esb_rx_init(void)
{
  int ret = 0;
  struct  rt_spi_configuration cfg;
  rt_device_t esb_rx_dev;
  rt_device_t bus;

  /* 挂载 rx 天线端 */
  bus = rt_device_find("spi1");
  if (bus != RT_NULL)
  {
    /* 初始化 esb_rx DMA 同步管理信号量 */
    esb_rx_hw_sem = rt_sem_create("esb_rx_sem", 1, RT_IPC_FLAG_FIFO);
    /* 初始化发送缓冲区 */
    rt_mq_init(&esb_rx_mq, "esb_rmq", esb_rx_mq_pool, ESB_RX_PAC_LEN,
                            sizeof(esb_rx_mq_pool), RT_IPC_FLAG_FIFO);
    /* 注意: 此处的NSS管脚需要单独初始化,否则无效,或者使用:
     *  rt_hw_spi_device_attach("spi1", "esb_rx", GPIOA, GPIO_PIN_4)
     */
    rx_nss.GPIOx    = GPIOA;
    rx_nss.GPIO_Pin = GPIO_PIN_4;
    rt_pin_mode(ESB_RX_NSS_PIN, PIN_MODE_OUTPUT);
    rt_pin_write(ESB_RX_NSS_PIN, PIN_HIGH);
    if (rt_spi_bus_attach_device(&esb_rx, "esb_rx", "spi1", (void*)&rx_nss) != RT_EOK)
      ret = 1;
    else
      LOG_I("hw_ver:%x, esb_rx find spi bus ok", sys_tcb_val.hw_ver);

    /* 初始化SPI */
    cfg.data_width = 8;
    cfg.mode = RT_SPI_MASTER | RT_SPI_MODE_0 | RT_SPI_MSB;
    cfg.max_hz = 4 * 1000 *1000;
    rt_spi_configure(&esb_rx, &cfg);

    /* 初始化中断接收管脚:PC5 */
    rt_pin_attach_irq(ESB_RX_IRQ_PIN, PIN_IRQ_MODE_RISING,
                      esb_rx_isr, RT_NULL);
    rt_pin_irq_enable(ESB_RX_IRQ_PIN, PIN_IRQ_ENABLE);
  }
}

对应回调函数

/*******************************************************************************
* Function Name  : esb_rx_isr
* Description    : 外部中断获取函数
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void esb_rx_isr( void *args )
{
  /* 启动SPI开始读取数据 */
  resb_rx_evt = 1;
  LOG_E("esb_rx: irq s0");
  rt_sem_release(esb_rx_hw_sem);
}

/*******************************************************************************
* Function Name  : rt_hw_spi_get_port_num
* Description    : 获取硬件端口号
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
uint8_t rt_hw_spi_get_port_num(SPI_HandleTypeDef *hspi)
{
  uint8_t port = 0xFF;
  if (hspi->Instance == SPI1)
    port = 0;
  if (hspi->Instance == SPI2)
    port = 1;
  return port;
}

void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi)
{
  uint8_t port = rt_hw_spi_get_port_num(hspi);
  if (port == 0)
  {
    HAL_GPIO_WritePin(rx_nss.GPIOx, rx_nss.GPIO_Pin, GPIO_PIN_SET);
    LOG_E("RX: HAL_SPI_ErrorCallback");
    resb_hw_idle = 0;
    rt_sem_release(esb_rx_hw_sem);
  }
  else
  {
    HAL_GPIO_WritePin(tx_nss.GPIOx, tx_nss.GPIO_Pin, GPIO_PIN_SET);
    LOG_E("TX: HAL_SPI_ErrorCallback");
    rt_sem_release(esb_tx_hw_sem);
  }
}

void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
  uint8_t port = rt_hw_spi_get_port_num(hspi);
  if (port == 0)
  {
      HAL_GPIO_WritePin(rx_nss.GPIOx, rx_nss.GPIO_Pin, GPIO_PIN_SET);
      // LOG_E("RX: HAL_SPI_TxCpltCallback");
      if (resb_rx_evt == 1)
      {
        resb_rx_evt = 2;
        // LOG_E("RX: HAL_SPI_RxCpltCallback");
      }
      resb_hw_idle = 0;
      rt_sem_release(esb_rx_hw_sem);
  }
  else
  {
      HAL_GPIO_WritePin(tx_nss.GPIOx, tx_nss.GPIO_Pin, GPIO_PIN_SET);
      // LOG_E("TX: HAL_SPI_TxRxCpltCallback");
      rt_sem_release(esb_tx_hw_sem);
  }
}

主要业务逻辑与接口函数


/*******************************************************************************
  * Function Name  : spi_rx_cmd_decode
  * Description    : 答题器数据接收处理线程
  * Input          : None
  * Output         : None
  * Return         : None
*******************************************************************************/
RT_WEAK void spi_rx_cmd_decode(uint8_t *rbuf)
{
    // 接收数据处理函数, 可以在外部文件实现指令解析,保证驱动文件的不修改逻辑
}

/*******************************************************************************
  * Function Name  : spi_rx_nop_cmd_set
  * Description    : 答题器读取数据的空指令实现
  * Input          : None
  * Output         : None
  * Return         : None
*******************************************************************************/
RT_WEAK void spi_rx_nop_cmd_set(uint8_t *sbuf)
{
  int i = 0;
  // 接收数据处理函数, 可以在外部文件实现指令解析,保证驱动文件的不修改逻辑
  for (i=0; i<256; i++)
    sbuf[i] = i;
}

int8_t drv_esb_rx_tbuf_send(uint8_t *tbuf)
{
  int8_t err = 0;
  err = rt_mq_send(&esb_rx_mq, tbuf, ESB_RX_PAC_LEN);
  if (err == RT_EOK)
    rt_sem_release(esb_rx_hw_sem);
  return err;
}

/*#############################################################################
                             答题器操作相关函数
##############################################################################*/
/*******************************************************************************
  * Function Name  : fun_esb_rx_port
  * Description    : 答题器 扫描数据线程
  * Input          : None
  * Output         : None
  * Return         : None
*******************************************************************************/
void fun_esb_rx_port(void *parameter)
{
  uint32_t count = 0;
  int ret = 0;

  static uint8_t sbuf[256];
  static uint8_t rbuf[256];

  drv_esb_rx_init();
  spi_rx_nop_cmd_set(sbuf);

  while (1)
  {
    /* 等待数据传输完成:释放的信号量,此时完成数据读回, rbuf中有数据需要处理 */
    rt_sem_take(esb_rx_hw_sem, 100);
    /* 信号量只有等数据空闲的时候才能被执行:常见主动发送数据 */
    if (resb_hw_idle == 0)
    {
      switch(resb_rx_evt)
      {
        case 2: /* s0: 处理接收回来的数据 */
          spi_rx_cmd_decode(rbuf);
          resb_rx_evt = 0;
          break;
        case 1: /* 中断有数据需要接收 */
          rbuf[0] = 0xAA; // 表示清空数据
          if(rt_mq_recv(&esb_rx_mq, sbuf, ESB_RX_PAC_LEN, 0) != RT_EOK )
          {
             // 没有数据就请发送读指令
             spi_rx_nop_cmd_set(sbuf);
          }
          break;
        case 0: /* 没有数据发送: 继续等待信号量 */
          if(rt_mq_recv(&esb_rx_mq, sbuf, ESB_RX_PAC_LEN, 0) != RT_EOK )
          {
             // LOG_D("sbuf is empty");
             continue;
          }
          break;
        default:
          break;
      }
      resb_hw_idle = 1;
      rt_spi_transfer(&esb_rx, sbuf, rbuf, 256);
    }
  }
}

/*******************************************************************************
  * Function Name  : th_esb_drv_init
  * Description    : dtq 同步数据实例线程初始化
  * Input          : None
  * Output         : None
  * Return         : None
*******************************************************************************/
int th_esb_drv_init(void)
{
  rt_err_t ret;
  static struct rt_thread thd_esb_rx, thd_esb_tx;
  static uint8_t stk_esb_rx[1300];
  static uint8_t stk_esb_tx[1300];

  /* 创建答题器扫描数据线程 */
  ret = rt_thread_init(&thd_esb_rx, "thd_esb_rx", fun_esb_rx_port,
                       RT_NULL, (uint8_t *)stk_esb_rx, 1300, 5, 1);
  if (ret == RT_EOK)
    rt_thread_startup(&thd_esb_rx);
  LOG_D("s_PORT: thread:thd_esb_rx init %s", ret_s[ret]);

  /* 创建答题器扫描数据线程 */
  ret = rt_thread_init(&thd_esb_tx, "thd_esb_tx", fun_esb_tx_port,
                       RT_NULL, (uint8_t *)stk_esb_tx, 1300, 5, 1);
  if (ret == RT_EOK)
    rt_thread_startup(&thd_esb_tx);
  LOG_D("s_PORT: thread:thd_esb_tx init %s", ret_s[ret]);

  return 0;
}

测试驱动

完成之后的驱动测试代码,接收端的测试函数如下:

int8_t app_esb_rx_ts(void)
{
  uint32_t count = 0;
  int8_t ret = 0;
  int i = 0;
  uint8_t sbuf[256];

  for (i=0; i<256; i++)
    sbuf[i] = i;

  /* 当数据发送间隔超过1ms: 当前速度发送数据耗时 0.91ms */
  sbuf[0] = 3;
  ret = drv_esb_rx_tbuf_send(sbuf);
  rt_thread_mdelay(3);
  sbuf[0] = 7;
  ret = drv_esb_rx_tbuf_send(sbuf);
  rt_thread_mdelay(7);
  sbuf[0] = 5;
  ret = drv_esb_rx_tbuf_send(sbuf);
  rt_thread_mdelay(5);
  sbuf[0] = 11;
  ret = drv_esb_rx_tbuf_send(sbuf);
  rt_thread_mdelay(11);

  /* 阻塞数据发送逻辑验证:快速发送数据逻辑,无缝发送,需保证数据无干扰 */
  sbuf[0] = 20;
  ret = drv_esb_rx_tbuf_send(sbuf);
  if (ret != RT_EOK)
    LOG_D("pac_num:%2x is full", sbuf[0]);
  sbuf[0] = 21;
  ret = drv_esb_rx_tbuf_send(sbuf);
  if (ret != RT_EOK)
    LOG_D("pac_num:%2x is full", sbuf[0]);
  sbuf[0] = 22;
  ret = drv_esb_rx_tbuf_send(sbuf);
  if (ret != RT_EOK)
    LOG_D("pac_num:%2x is full", sbuf[0]);
  sbuf[0] = 23;
  ret = drv_esb_rx_tbuf_send(sbuf);
  if (ret != RT_EOK)
    LOG_D("pac_num:%2x is full", sbuf[0]);
}

MSH_CMD_EXPORT(app_esb_rx_ts, app_esb_rx_ts);

在这里插入图片描述
发送逻辑测试完成,等待ESB端程序完成之后再验证中断接收数据逻辑是否OK。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值