Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
SPI
SPI从机
使用STM32的SPI实现双机通讯
发布于 2024-07-08 11:07:44 浏览:866
订阅该版
[tocm] # 0、写在前面 接触RT-Thread两三个月了,看到了很多人在系统上的贡献和付出。系统功能的强大性和稳定性是经过市场验证的,开发的便捷性也是有目共睹。使用一段时间后感觉爱上此操作系统。因为项目中有需要扩展资源的情况,而方案选择的是SPI双机通信的方式。网上相关的资料都写的不是很详细,故出此篇文章,供大家一起指正和完善开发内容,使实现方式更加合理,代码更加坚固。 # 1、开发环境 笔者使用的是STM32CubeMX+Keil的开发环境。主机使用的是STM32F407,从机是STM32F103。 # 2、电路设计 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20240708/09f943e3daaca96a92cce11a166b5d22.png.webp) SPI0_NSS接口是主SPI的使能接口,而SPI0_EN是从机通知主机接收的接口。因为在常规使用中,从机都是被动接收数据的,而双机通信中从机可能需要主动发送数据,此时需要一个接口来通知主机接收从机发送的数据。 备注:SPI0_EN最好设计为下拉,因为在启动过程中有可能会有上升沿脉冲,产生误判消息。 # 3、工程配置 配置原则,主机配置成Master模式,从机配置成Slave模式。参数配置要相同,如通信速率等。因为会使用DMA,所以也要把DMA配置上。 ## 3.1 主机的配置 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20240708/4640fb3a70fc1180b8b0b7a2322f0f9d.png.webp) ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20240708/5d7fdbf38b33d8c7ac5e96e80891da99.png.webp) ## 3.2 从机的配置 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20240708/09d5ea5234f1d16b72f8697a8eb5aef3.png.webp) ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20240708/fba4c9016b8de6c08f90d337da672d7b.png.webp) # 4、menuconfig配置 写相应的Kconfig以及使能SPI外设,跟官网的使用说明一致,不再说明。 # 5、主要代码 ## 5.1 主机代码 备注:有些代码是有额外功能的。比如消息队列,不属于SPI通信内容。因为是从项目中拷贝的,未整理,请大家自行分辨。 ```c //////////////////////////////////////华丽分割线///////////////////////////////////// #include
#include
#include
#include
#include "app_spi1.h" #define SPI_BUS_NAME "spi1" #define MASTER_SPI_DEVICE_NAME "spi10" struct rt_spi_device *spiMaster; static uint8_t g_spiIrqFlag = 0; // 用作中断通知标志 /************************************************ * 函数:spi_exirq_callback * 功能:spi中断回调函数 ************************************************/ void spi_exirq_callback(void *args)// 回调函数 { g_spiIrqFlag++; // rt_kprintf("g_spiIrqFlag=%d\n", g_spiIrqFlag); if (g_spiIrqFlag >= 3) { /* 规避主/从上电启动时可能有检测到突变的上升沿 */ g_spiIrqFlag = 0; } } /************************************************ * 函数:SpiMasterHardwareConfiguration * 功能:主SPI硬件信息配置 ************************************************/ static void SpiMasterHardwareConfiguration(struct rt_spi_device *spiDev) { struct rt_spi_configuration cfg; cfg.data_width = 8; // 8数据位 cfg.mode = RT_SPI_MASTER | RT_SPI_MODE_0 | RT_SPI_MSB; // 主模式0, MSB cfg.max_hz = 10.5 * 1000 *1000; /* 10.5MBits/s速率 */ if (rt_spi_configure(spiDev, &cfg) != RT_EOK) { rt_kprintf("master spi configure fail.\n"); } } /************************************************ * 函数:spi1_tx_thread * 功能:SPI1发送任务 ************************************************/ static void spi1_tx_thread(void* parameter) { uint8_t canBuf[12] = {0}; // 数据接收缓冲区接收12字节的数据 uint8_t spiBuf[13] = {0}; // spi发送的13个字节数据缓冲区 while(1) { /* 从消息队列中接收消息 */ if (rt_mq_recv(&g_can2MsgQueue, &canBuf, sizeof(canBuf), RT_WAITING_FOREVER) == RT_EOK) { rt_kprintf("Spi1 Rcv QueMsg From can2: "); for (int i = 0; i < sizeof(canBuf) / sizeof(uint8_t); ++i) { rt_kprintf(" %2x", canBuf[i]); spiBuf[i] = canBuf[i]; // copy data } rt_kprintf("\n"); spiBuf[12] = GetDataChecksum(canBuf, 12); // 0~12,最后一个数据是校验和 SendQueMsgBySpi(spiBuf, sizeof(spiBuf) / sizeof(uint8_t)); } else { rt_kprintf("master spi recv msg queue fail.\n"); } rt_thread_mdelay(10); } } /************************************************ * 函数:SpiSendTaskCreate * 功能:发送任务创建 ************************************************/ static rt_err_t SpiSendTaskCreate(void) { rt_err_t ret = RT_EOK; rt_thread_t thread_ptr; /* 动态创建线程 */ /* 线程入口函数是SpiSendTask, 参数是RT_NULL * 栈空间是1024, 优先级25, 时间片10个OS Tick */ thread_ptr = rt_thread_create("spi1_tx", spi1_tx_thread, RT_NULL, 512, 25, 10); /* 启动线程 */ if (thread_ptr != RT_NULL) { rt_thread_startup(thread_ptr); } else { ret = RT_ERROR; } return ret; } /************************************************ * 函数:spi1_rx_thread * 功能:SPI接收任务 ************************************************/ static void spi1_rx_thread(void* parameter) { uint8_t spiRcvBuf[13] = {0}; while(1) { uint8_t spiRcvLen = 0; if (g_spiIrqFlag != 0) { spiRcvLen = rt_spi_recv(spiMaster, spiRcvBuf, sizeof(spiRcvBuf) / sizeof(uint8_t)); if ((spiRcvLen != 0) &&(spiRcvBuf[0] != 0) && (spiRcvBuf[0] != 0xFF)) { /* 接收到数据 */ if (spiRcvBuf[12] == GetDataChecksum(spiRcvBuf, 12)) { // 校验和 /* 写消息队列发送给CAN */ BmsSendQueMsgToNorthward(spiRcvBuf, 12); g_spiIrqFlag = 0; } } } rt_thread_mdelay(10); } } /************************************************ * 函数:SpiReceiveTaskCreate * 功能:SPI总线接收任务初始化 ************************************************/ static rt_err_t SpiReceiveTaskCreate(void) { rt_err_t ret = RT_EOK; rt_thread_t thread_ptr; /* 动态创建线程 */ /* 线程入口函数是SpiSendTask, 参数是RT_NULL * 栈空间是512, 优先级25, 时间片10个OS Tick */ thread_ptr = rt_thread_create("spi1_rx", spi1_rx_thread, RT_NULL, 512, 25, 10); /* 启动线程 */ if (thread_ptr != RT_NULL) { rt_thread_startup(thread_ptr); } else { ret = RT_ERROR; } /* SPI1消息队列初始化 */ ret = Spi1MsgQueInit(); RT_ASSERT(ret == RT_EOK); return ret; } /************************************************ * 函数:SpiExtInterruptIint * 功能:SPI外部中断输入引脚初始化,通知Master SPI接收数据 ************************************************/ static rt_err_t SpiExtInterruptIint(void) { rt_err_t ret = RT_EOK; rt_pin_mode(SPI_EXT_INTERRUPT_PIN, PIN_MODE_INPUT_PULLDOWN); /* 下拉输入 */ /* 下降和上升都检测 */ rt_pin_attach_irq(SPI_EXT_INTERRUPT_PIN, PIN_IRQ_MODE_RISING_FALLING, spi_exirq_callback, RT_NULL); rt_pin_irq_enable(SPI_EXT_INTERRUPT_PIN, PIN_IRQ_ENABLE); /* 使能中断 */ return ret; } /************************************************ * 函数:APP_SpiMasterInit * 功能:初始化SPI总线配置以及收发任务初始化 ************************************************/ rt_err_t APP_SpiMasterInit(void) { rt_err_t ret = RT_EOK; char name[RT_NAME_MAX]; rt_strncpy(name, MASTER_SPI_DEVICE_NAME, RT_NAME_MAX); /* 查找设备前挂接设备,CS=PA15 */ rt_hw_spi_device_attach("spi1", "spi10", GET_PIN(A, 15)); /* 查找spi设备获取设备句柄 */ spiMaster = (struct rt_spi_device *)rt_device_find(name); if (!spiMaster) { rt_kprintf("spi can't find %s device!\n", name); } else { SpiMasterHardwareConfiguration(spiMaster); /* find之后要配置SPI */ } /* 创建收发任务 */ ret = SpiSendTaskCreate(); if (ret != RT_EOK) { rt_kprintf("SpiTxTask fail.\n"); return ret; } ret = SpiReceiveTaskCreate(); if (ret != RT_EOK) { rt_kprintf("SpiRxTask fail.\n"); return ret; } rt_kprintf("SpiInit ok.\n"); /* 配置SPI外部中断 */ if (SpiExtInterruptIint() != RT_EOK) { rt_kprintf("Spi master exirq inti fail.\n"); } return ret; } MSH_CMD_EXPORT(APP_SpiMasterInit, spi sample); ## 5.1 从机主要代码 #include
#include
#include
#include
#include "app_spi2.h" #define SPI_BUS_NAME "spi2" #define SPI_DEVICE_NAME "spi20" static struct rt_spi_device *spiSlaveDev; /************************************************ * 函数:SpiSlaveHardwareConfiguration * 功能:从SPI硬件信息配置 ************************************************/ static rt_err_t SpiSlaveHardwareConfiguration(struct rt_spi_device *spiDev) { rt_err_t result = RT_EOK; struct rt_spi_configuration cfg; cfg.data_width = 8; // 数据位宽 /* 从模式, Mode 0:CPOL=0&CPHA=0. MSB: */ cfg.mode = RT_SPI_SLAVE | RT_SPI_MODE_0 | RT_SPI_MSB; // SPI从模式 cfg.max_hz = 10.5 * 1000 *1000; /* 10.5MBits/s */ if (rt_spi_configure(spiDev, &cfg) != RT_EOK) { return RT_ERROR; } return result; } /************************************************ * 函数:AuxMcuSendQueMsgBySpi * 功能:辅助MCU通过SPI将数据发送给主MCU ************************************************/ static rt_err_t AuxMcuSendQueMsgBySpi(const uint8_t *data, const uint8_t len) { rt_err_t ret = RT_EOK; struct rt_spi_message msg1; msg1.send_buf = data; msg1.recv_buf = RT_NULL; msg1.length = len; // len字节数据 msg1.cs_take = 1; /* 选中片选 */ msg1.cs_release = 1; /* 函数返回时释放片选 */ msg1.next = RT_NULL; rt_pin_write(SPI_INFORM_MASTER_PIN, PIN_HIGH); /* 给主MCU发送数据接收通知信号 */ if (rt_spi_transfer_message(spiSlaveDev, &msg1) == RT_NULL){ rt_spi_release(spiSlaveDev); /* 传输完成后释放片选 */ rt_spi_release_bus(spiSlaveDev); /* 尽快释放总线 */ rt_thread_delay(2); // 2ms rt_pin_write(SPI_INFORM_MASTER_PIN, PIN_LOW); /* 给主MCU发送数据发送完信号 */ } else { rt_thread_delay(2); // 2ms /* 传输失败也要拉低信号,以便spi总线恢复的时候能恢复正常的数据传输 */ rt_pin_write(SPI_INFORM_MASTER_PIN, PIN_LOW); return RT_ERROR; } return ret; } /************************************************ * 函数:spi_tx_thread * 功能:SPI数据发送任务函数 ************************************************/ static void spi_tx_thread(void *parameter) { uint8_t buf[12] = {0}; // 数据接收缓冲区接收12字节的数据 uint8_t spiBuf[13] = {0}; while(1) { #ifdef USING_SPI_LOOP_SEND SpiLoopSendData(); #else /* 从消息队列中接收消息 */ if (rt_mq_recv(&g_canMsgQueue, &buf, sizeof(buf), RT_WAITING_FOREVER) == RT_EOK) { rt_kprintf("Aux Spi Rcv can QueMsg: "); for (int i = 0; i < sizeof(buf) / sizeof(uint8_t); ++i) { rt_kprintf(" %2x", buf[i]); spiBuf[i] = buf[i]; // copy data } rt_kprintf("\n"); spiBuf[12] = GetDataChecksum(buf, 12); AuxMcuSendQueMsgBySpi(spiBuf, sizeof(spiBuf) / sizeof(uint8_t)); rt_kprintf("Aux Spi send Msg.\n"); } else { rt_kprintf("SPI recv msg queue fail(timeout).\n"); } #endif rt_thread_delay(5); // 5ms } } /************************************************ * 函数:APP_SpiSendTaskCreate * 功能:SPI发送任务创建 ************************************************/ rt_err_t APP_SpiSendTaskCreate(void) { rt_err_t res = 0; rt_thread_t thread; /* 创建数据发送线程 */ thread = rt_thread_create("spi_tx", spi_tx_thread, RT_NULL, 1024, 28, 10); // 栈空间1024,28优先级,10ms时间片 if (thread != RT_NULL) { rt_thread_startup(thread); } else { rt_kprintf("create spi_tx thread failed!\n"); } return res; } /************************************************ * 函数:spi_rx_thread * 功能:SPI数据接收任务函数 ************************************************/ static void spi_rx_thread(void *parameter) { uint8_t spiRcvBuf[13] = {0}; uint8_t spiRcvLen = 0; while(1) { rt_err_t ret = RT_EOK; spiRcvLen = rt_spi_recv(spiSlaveDev, spiRcvBuf, sizeof(spiRcvBuf) / sizeof(uint8_t)); if ((spiRcvLen != 0) && ((spiRcvBuf[0] != 0) && (spiRcvBuf[0] != 0xFF))) { // 接收到数据 if (spiRcvBuf[12] == GetDataChecksum(spiRcvBuf, 12)) { // 校验和 /* 写消息队列发送给CAN */ ret = rt_mq_send(&g_spiMsgQueue, spiRcvBuf, 12); // 只需要发送前12个数据,第十三个是校验和 if (ret != RT_EOK) { rt_kprintf("Spi send queMsg fail.\n"); ret = RT_ERROR; } else { rt_kprintf("Spi->QueMsg->Can.\n"); } } else { rt_kprintf("Slave Spi rcv data checksum fail.\n"); } } rt_thread_delay(10); } } /************************************************ * 函数:APP_SpiReceiveTaskCreate * 功能:SPI接收任务创建 ************************************************/ rt_err_t APP_SpiReceiveTaskCreate(void) { rt_err_t res = 0; rt_thread_t thread; /* 创建数据接收线程 */ thread = rt_thread_create("spi_rx", spi_rx_thread, RT_NULL, 1024, 28, 10); if (thread != RT_NULL) { rt_thread_startup(thread); } else { rt_kprintf("create spi_rx thread failed!\n"); } return res; } /************************************************ * 函数:APP_SpiInformGpioInit * 功能:SPI通知主MCU接收数据接口初始化 ************************************************/ rt_err_t APP_SpiInformGpioInit(void) { /* set pin mode to output */ rt_pin_mode(SPI_INFORM_MASTER_PIN, PIN_MODE_OUTPUT); /* output low*/ rt_pin_write(SPI_INFORM_MASTER_PIN, PIN_LOW); return RT_EOK; } /************************************************ * 函数:APP_SpiDeviceInit * 功能:SPI设备初始化 ************************************************/ rt_err_t APP_SpiDeviceInit(void) { rt_err_t ret = RT_EOK; char name[RT_NAME_MAX]; rt_strncpy(name, SPI_DEVICE_NAME, RT_NAME_MAX); /* 查找之前要挂接设备,CS = PB12 */ rt_hw_spi_device_attach("spi2", "spi20", GET_PIN(B, 12)); /* 查找spi设备获取设备句柄 */ spiSlaveDev = (struct rt_spi_device *)rt_device_find(name); if (!spiSlaveDev){ rt_kprintf("Aux spi can't find %s device!\n", name); } else { // find之后要配置SPI if (SpiSlaveHardwareConfiguration(spiSlaveDev) != RT_EOK) { rt_kprintf("SpiConfigure fail.\n"); return ret; } } /* SPI消息队列初始化 */ ret = SpiMsgQueInit(); RT_ASSERT(ret == RT_EOK); /* 创建收发任务 */ ret = APP_SpiReceiveTaskCreate(); if (ret != RT_EOK) { rt_kprintf("SpiRxTask fail.\n"); return ret; } ret = APP_SpiSendTaskCreate(); if (ret != RT_EOK) { rt_kprintf("SpiTxTask fail.\n"); return ret; } rt_kprintf("SpiInit ok.\n"); return ret; } MSH_CMD_EXPORT(APP_SpiDeviceInit, spi2 sample); ``` # 6、测试情况 ## 6.1 主机发送从机接收 (1)发送 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20240708/3c9c61652b00a2d8f80c6359a606c2b2.png.webp) ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20240708/4095df6778cd8c98fd0b516c36cf63f1.png) (2)接收 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20240708/2f51870d8909d1af514f0a1460c467ec.png) ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20240708/3f9bf49e37054287a51ad3680004e85d.png.webp) ## 6.2 从机发送主机接收 (1)发送 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20240708/e1e4de20936d4eee9797ac24206502fc.png.webp) ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20240708/c471052d373d72d7b4743ddde0450a4a.png) (2)接收 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20240708/d9b547aa7a027231d6368c6174c1cc7d.png.webp) ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20240708/e4336589032a21ec93399020a6d5b3bd.png) # 7、后话 初步把主要内容整理出来了。有疑问和需要帮助的同学请留言。如有需要后续可将完整工程代码共享出来。
11
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
程序猿Charlie
这家伙很懒,什么也没写!
文章
2
回答
1
被采纳
0
关注TA
发私信
相关文章
1
BBB的SPI驱动
2
求个SPI上挂两个或多个设备的使用例子
3
SPI设备有个bug
4
spi flash 的fatfs使用一段时间后读写文件出现故障
5
SPI驱动
6
请教rt_spi_configure函数理解
7
SPI FLASH挂载的问题
8
SPI-FLASH 文件系统 SPIFFS
9
求助一个完整的 spi flash 驱动
10
关于同时使用文件系统与SPI FLASH的问题
推荐文章
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
I2C_IIC
ESP8266
UART
WIZnet_W5500
ota在线升级
PWM
cubemx
flash
freemodbus
BSP
packages_软件包
潘多拉开发板_Pandora
定时器
ADC
flashDB
GD32
socket
编译报错
中断
Debug
rt_mq_消息队列_msg_queue
SFUD
msh
keil_MDK
ulog
MicroPython
C++_cpp
本月问答贡献
出出啊
1517
个答案
342
次被采纳
小小李sunny
1444
个答案
290
次被采纳
张世争
813
个答案
177
次被采纳
crystal266
547
个答案
161
次被采纳
whj467467222
1222
个答案
149
次被采纳
本月文章贡献
聚散无由
2
篇文章
14
次点赞
catcatbing
2
篇文章
4
次点赞
Wade
2
篇文章
2
次点赞
Ghost_Girls
1
篇文章
4
次点赞
xiaorui
1
篇文章
1
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部