IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> 解决rt-thread上使用SPI+DMA -> 正文阅读

[嵌入式]解决rt-thread上使用SPI+DMA

首先阐述下遇到的问题

使用SPI读取icm20602数据,读取频率为1000hz,使用stm32f407主控,发现CPU占用率达到了70%,且扰乱了线程的时序,将此线程注释掉后CPU占用率掉到25%,线程频率恢复正常,看来这里要着重优化,便萌生了使用DMA的想法。

使用SPI+DMA要进行的配置

RTT部分

1.开启RTT设备驱动。点击自己的工程 ->RT-Thread Setting,开启SPI设备驱动请添加图片描述
2.在board.h中添加开启宏请添加图片描述
开启后设备驱动会自动调用HAL库进行底层硬件的初始化默认配置,并将spi注册到设备容器

int rt_hw_spi_init(void)
{
    stm32_get_dma_info();
    return rt_hw_spi_bus_init();
}
INIT_BOARD_EXPORT(rt_hw_spi_init);
HAL库部分

3.在board.c文件里加入以下函数,此函数受设备框架调用以进行底层硬件初始化

void HAL_SPI_MspInit(SPI_HandleTypeDef* hspi)
{
    GPIO_InitTypeDef GPIO_InitStruct;

    if(hspi->Instance == SPI2)
    {
        /* Peripheral clock enable */
        __HAL_RCC_SPI2_CLK_ENABLE();
        __HAL_RCC_GPIOB_CLK_ENABLE();
        /**SPI2 GPIO Configuration,这里配置自己的
        PB13     ------> SPI3_MISO
        PB14     ------> SPI3_SCK
        PB15     ------> SPI3_MOSI
        */
        GPIO_InitStruct.Pin = GPIO_PIN_13| GPIO_PIN_14 | GPIO_PIN_15;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
        GPIO_InitStruct.Pull = GPIO_PULLUP;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
        GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;
        HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
    }
}

4.使能HAL库-SPI,即可与RTT设备驱动对接请添加图片描述

至此基础配置已完毕,接下来是用户配置,配置片选,并挂载到具体的SPI设备

选择想要的io作为片选引脚,即一个设备对应一个片选

int spi_device_attach(void)
{
    __HAL_RCC_GPIOC_CLK_ENABLE();
    __HAL_RCC_GPIOD_CLK_ENABLE();
    /*******传感器片选引脚*********/
    rt_pin_mode(56, PIN_MODE_OUTPUT); //PD8,配置引脚为输出模式
    rt_pin_mode(57, PIN_MODE_OUTPUT); //PD9
    rt_pin_mode(58, PIN_MODE_OUTPUT); //PD10
    rt_pin_mode(33, PIN_MODE_OUTPUT); //PC1

    rt_pin_write(56, PIN_HIGH);//配置初始化引脚高电平(解除片选)
    rt_pin_write(57, PIN_HIGH);
    rt_pin_write(58, PIN_HIGH);
    rt_pin_write(33, PIN_HIGH);

    /*这里挂载了四个设备spi2-0,spi2-1,spi2-2,spi2-3*/
    rt_hw_spi_device_attach("spi2",ICM20602_SPI_DEVICE_NAME, GPIOD, GPIO_PIN_8);      //片选引脚PD8
    rt_hw_spi_device_attach("spi2",SPL06_SPI_DEVICE_NAME, SPL06_CS_GPIO,SPL06_CS_PIN);//片选引脚PD10
    rt_hw_spi_device_attach("spi2","spi22", GPIOD,GPIO_PIN_9);//片选引脚PD9
    rt_hw_spi_device_attach("spi2","spi23", GPIOC,GPIO_PIN_1);//片选引脚PC1
    
    return RT_EOK;
}
INIT_DEVICE_EXPORT(spi_device_attach);//导出到自动初始化

初始化spi
struct rt_spi_device *spi_dev_icm20602 = RT_NULL;     /*spi设备句柄*/ 
int icm20602_spi_device_init(void)
{
    struct rt_spi_configuration spi_cfg;

    spi_dev_icm20602 = (struct rt_spi_device *)rt_device_find(ICM20602_SPI_DEVICE_NAME);/* 查找 spi2 设备获取设备句柄 */

    spi_cfg.data_width = 8;
    spi_cfg.mode       = RT_SPI_MASTER | RT_SPI_MODE_0 | RT_SPI_MSB;
    spi_cfg.max_hz     = 10000000;  /*10M*/
    rt_spi_configure(spi_dev_icm20602,&spi_cfg);

    return RT_EOK;
}
/* 导出到自动初始化 */
INIT_COMPONENT_EXPORT(icm20602_spi_device_init);

配置完毕,开始使用

spi底层读写函数实现

/****************************************************************************************
*@brief   读取数据
*@param[in]
*****************************************************************************************/
void icm20602_reg_read(rt_uint8_t addr,rt_uint8_t *rev_buf,rt_uint32_t len)
{
    struct rt_spi_message msg1,msg2;

    rt_uint8_t reg =  addr|0x80;
    msg1.send_buf    = ®
    msg1.recv_buf    = RT_NULL;
    msg1.length      = 1;
    msg1.cs_take     = 1;
    msg1.cs_release  = 0;
    msg1.next        = &msg2;

    msg2.send_buf    = RT_NULL;
    msg2.recv_buf    = rev_buf;
    msg2.length      = len;
    msg2.cs_take     = 0;
    msg2.cs_release  = 1;
    msg2.next        = RT_NULL;

    /*给icm20602设备读取和发送消息*/
    rt_spi_transfer_message(spi_dev_icm20602, &msg1);//发送消息
}

/****************************************************************************************
*@brief   写数据
*@param[in]
*****************************************************************************************/
void icm20602_reg_write(rt_uint8_t addr,rt_uint8_t value)
{
    struct rt_spi_message msg1;
    rt_uint8_t send_buf[2];
    send_buf[0] = addr;
    send_buf[1] = value;

    msg1.send_buf   = send_buf;
    msg1.recv_buf   = RT_NULL;
    msg1.length     = 2;
    msg1.cs_take    = 1;
    msg1.cs_release = 1;
    msg1.next       = RT_NULL;

    rt_spi_transfer_message(spi_dev_icm20602, &msg1);//发送消息
}

解决RTT的SPI设备驱动(drv_spi.c)缺陷(使用DMA)

rt_spi_transfer_message(spi_dev_icm20602, &msg1);//发送消息
(这个函数在spi_core.c后面会提到)
函数会调用这个函数进行数据传输请添加图片描述
就是这个

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)
    {
        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. */
        while (HAL_SPI_GetState(spi_handle) != HAL_SPI_STATE_READY);
    }

    if (message->cs_release)
    {
        HAL_GPIO_WritePin(cs->GPIOx, cs->GPIO_Pin, GPIO_PIN_SET);
    }

    return message->length;
}

使用DMA时也是在这里死等,这就失去了我们使用DMA的初衷,现在优化这个函数,在DMA传输数据时释放CPU去干其它事


        /* 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. */
        while (HAL_SPI_GetState(spi_handle) != HAL_SPI_STATE_READY);

添加如下函数,这里的方法是用信号量释放CPU

static rt_uint32_t spixfer_my(struct rt_spi_device *device, struct rt_spi_message *message, rt_sem_t sem)
{
    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)
    {
        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. */
        while (HAL_SPI_GetState(spi_handle) != HAL_SPI_STATE_READY)
            {
                rt_sem_take(sem, RT_WAITING_FOREVER);
            }
    }

    if (message->cs_release)
    {
        HAL_GPIO_WritePin(cs->GPIOx, cs->GPIO_Pin, GPIO_PIN_SET);
    }

    return message->length;
}

替换本文件里的方法结构体

static const struct rt_spi_ops stm_spi_ops =
{
    .configure = spi_configure,
    .xfer = spixfer,
    .xfer_my = spixfer_my,
};

OK,drv_spi.c更改完成

下面更改spi.h文件

同理,替换这个结构体

/**
 * SPI operators
 */
struct rt_spi_ops
{
    rt_err_t (*configure)(struct rt_spi_device *device, struct rt_spi_configuration *configuration);
    rt_uint32_t (*xfer)(struct rt_spi_device *device, struct rt_spi_message *message);
    rt_uint32_t (*xfer_my)(struct rt_spi_device *device, struct rt_spi_message *message, rt_sem_t sem);
};

OK,spi.h文件更改完成

下面更改spi_core.c文件

使这个函数支持我们自定义的函数

struct rt_spi_message *rt_spi_transfer_message(struct rt_spi_device  *device,
                                               struct rt_spi_message *message)

添加如下函数

struct rt_spi_message *rt_spi_transfer_message_my(struct rt_spi_device  *device,
                                               struct rt_spi_message *message, rt_sem_t sem)
{
    rt_err_t result;
    struct rt_spi_message *index;

    RT_ASSERT(device != RT_NULL);

    /* get first message */
    index = message;
    if (index == RT_NULL)
        return index;

    result = rt_mutex_take(&(device->bus->lock), RT_WAITING_FOREVER);
    if (result != RT_EOK)
    {
        rt_set_errno(-RT_EBUSY);

        return index;
    }

    /* reset errno */
    rt_set_errno(RT_EOK);

    /* configure SPI bus */
    if (device->bus->owner != device)
    {
        /* not the same owner as current, re-configure SPI bus */
        result = device->bus->ops->configure(device, &device->config);
        if (result == RT_EOK)
        {
            /* set SPI bus owner */
            device->bus->owner = device;
        }
        else
        {
            /* configure SPI bus failed */
            rt_set_errno(-RT_EIO);
            goto __exit;
        }
    }

    /* transmit each SPI message */
    while (index != RT_NULL)
    {
        /* transmit SPI message */
        result = device->bus->ops->xfer_my(device, index, sem);
        if (result == 0)
        {
            rt_set_errno(-RT_EIO);
            break;
        }

        index = index->next;
    }

__exit:
    /* release bus lock */
    rt_mutex_release(&(device->bus->lock));

    return index;
}

在spi.h里新加入此函数的声明,供外部调用

struct rt_spi_message *rt_spi_transfer_message_my(struct rt_spi_device  *device,
                                               struct rt_spi_message *message, rt_sem_t sem);

OK,全部搞定

回过头来

spi底层读写函数实现 小改一下

static rt_sem_t spidma_sem = RT_NULL;//定义信号量,供我们的自定义函数使用

void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
{
    rt_sem_release(spidma_sem);//读完成回调,释放信号量
}
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
    rt_sem_release(spidma_sem);//写完成回调,释放信号量
}
/****************************************************************************************
*@brief   读取数据
*@param[in]
*****************************************************************************************/
void icm20602_reg_read(rt_uint8_t addr,rt_uint8_t *rev_buf,rt_uint32_t len)
{
    struct rt_spi_message msg1,msg2;

    rt_uint8_t reg =  addr|0x80;
    msg1.send_buf    = ®
    msg1.recv_buf    = RT_NULL;
    msg1.length      = 1;
    msg1.cs_take     = 1;
    msg1.cs_release  = 0;
    msg1.next        = &msg2;

    msg2.send_buf    = RT_NULL;
    msg2.recv_buf    = rev_buf;
    msg2.length      = len;
    msg2.cs_take     = 0;
    msg2.cs_release  = 1;
    msg2.next        = RT_NULL;

    /*给icm20602设备读取和发送消息*/
    rt_spi_transfer_message_my(spi_dev_icm20602, &msg1, spidma_sem);//发送消息
}

/****************************************************************************************
*@brief   写数据
*@param[in]
*****************************************************************************************/
void icm20602_reg_write(rt_uint8_t addr,rt_uint8_t value)
{
    struct rt_spi_message msg1;
    rt_uint8_t send_buf[2];
    send_buf[0] = addr;
    send_buf[1] = value;

    msg1.send_buf   = send_buf;
    msg1.recv_buf   = RT_NULL;
    msg1.length     = 2;
    msg1.cs_take    = 1;
    msg1.cs_release = 1;
    msg1.next       = RT_NULL;

    rt_spi_transfer_message_my(spi_dev_icm20602, &msg1, spidma_sem);//发送消息
}

全部完成,可以愉快的使用spi+dma干活了
CPU,DMA搭配干活不累^ _ ^

最后附上一张导图请添加图片描述

第一次写这么长的文章,有不当之处还请批评指正~

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-12-08 13:58:41  更:2021-12-08 14:00:12 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 -2025/1/9 1:23:57-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码