Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
SPI+DMA
STM32F407
完善rt-thread上使用SPI+DMA
发布于 2022-07-26 13:12:58 浏览:2419
订阅该版
[tocm] ## 首先阐述下遇到的问题 使用SPI读取icm20602数据,读取频率为1000hz,使用stm32f407主控,发现CPU占用率达到了70%,且扰乱了线程的时序,将此线程注释掉后CPU占用率掉到25%,线程频率恢复正常,看来这里要着重优化,便萌生了使用DMA的想法。(后记:最后发现是SPI时钟频率配置错了) ## 使用SPI+DMA要进行的配置 ##### RTT部分 1.开启RTT设备驱动。点击自己的工程 ->RT-Thread Setting,开启SPI设备驱动 ![请添加图片描述](https://oss-club.rt-thread.org/uploads/20220726/f8d9eee2a1418a7c0e8ebc951e8c1cb6a21fb866.png) 2.在board.h中添加开启宏 ![请添加图片描述](https://oss-club.rt-thread.org/uploads/20220726/ec5a462c95b01aa9ca29da9f196e88ac0e95ffa0.jpg) 开启后设备驱动会自动调用HAL库进行底层硬件的初始化默认配置,并将spi注册到设备容器 ```c 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文件里加入以下函数,此函数受设备框架调用以进行底层硬件初始化 ```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设备驱动对接![请添加图片描述](https://oss-club.rt-thread.org/uploads/20220726/1b098ea611c7bb9bb80f9a2cf66be9ea37a4cd91.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5YWJ5LquwqfpgqPmlrk=,size_20,color_FFFFFF,t_70,g_se,x_16) ##### 至此基础配置已完毕,接下来是用户配置,配置片选,并挂载到具体的SPI设备 选择想要的io作为片选引脚,即一个设备对应一个片选 ```c 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 ```c 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底层读写函数实现 ```c /**************************************************************************************** *@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后面会提到) 函数会调用这个函数进行数据传输 ![请添加图片描述](https://oss-club.rt-thread.org/uploads/20220726/0af02395eb75ffd0b76c06674dc2d65c16713ac0.png) 就是这个 ```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去干其它事 ```c /* 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 ```c 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; } ``` 替换本文件里的方法结构体 ```c static const struct rt_spi_ops stm_spi_ops = { .configure = spi_configure, .xfer = spixfer, .xfer_my = spixfer_my, }; ``` OK,drv_spi.c更改完成 ##### 下面更改spi.h文件 同理,替换这个结构体 ```c /** * 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文件 使这个函数支持我们自定义的函数 ```c struct rt_spi_message *rt_spi_transfer_message(struct rt_spi_device *device, struct rt_spi_message *message) ``` 添加如下函数 ```c 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里新加入此函数的声明,供外部调用 ```c 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底层读写函数实现 **小改一下** ```c 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搭配干活不累^ _ ^ ### 最后附上一张导图 ![请添加图片描述](https://oss-club.rt-thread.org/uploads/20220726/75b59dd71fd7fb35a8038fd62bf6c79c38cda43b.png) 第一次写这么长的文章,有不当之处还请批评指正~
18
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
my_dream
这家伙很懒,什么也没写!
文章
1
回答
0
被采纳
0
关注TA
发私信
相关文章
1
正点原子战舰V3+ENC28J60+SPI,开启SPI DMA后,卡死
2
SPI4设置DMA RX 错误
3
spi DMA 发送数据失败
4
spi dma 中断接收的问题
5
关于spi和dma的问题
6
RT Studio中F407芯片SPI的DMA怎么开启?
7
RT Studio中SPI+DMA如何使用?
8
rt-thread 互斥信号量BUG
9
我现在可以使用SPI正常收发数据了,但是怎样使用SPI的DMA模式进行数据的收发
10
SPI DMA 缓冲区
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
WIZnet_W5500
ota在线升级
UART
PWM
cubemx
freemodbus
flash
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
Debug
编译报错
msh
SFUD
keil_MDK
rt_mq_消息队列_msg_queue
at_device
ulog
C++_cpp
本月问答贡献
踩姑娘的小蘑菇
7
个答案
3
次被采纳
张世争
8
个答案
2
次被采纳
rv666
5
个答案
2
次被采纳
用户名由3_15位
11
个答案
1
次被采纳
KunYi
6
个答案
1
次被采纳
本月文章贡献
程序员阿伟
6
篇文章
2
次点赞
hhart
3
篇文章
4
次点赞
大龄码农
1
篇文章
2
次点赞
ThinkCode
1
篇文章
1
次点赞
Betrayer
1
篇文章
1
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部