ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

GD32F30x SPI单线双向模式实战:释放MISO引脚节省GPIO资源

GD32F30x SPI单线双向模式实战:释放MISO引脚节省GPIO资源 简介GD32F30x SPI 单线双向模式开发资源包重点面向嵌入式开发者、电子竞赛备赛者以及正在学习GD32/STM32体系SPI通信的工程师解决在单线双向通信中模式配置、时序同步与数据收发等实际问题。资源共132个文件以61个h头文件和50个c源文件为主体附带hex/bin固件、汇编文件及工程配置文件整体仅303KB结构清晰便于查阅定位。目前已有1308人学习下载。包内提供完整可编译的GD32F30x SPI单线双向模式例程覆盖SPI初始化、模式切换、数据发送接收、片选管理以及中断和DMA传输实现同时包含Utilities与Firmware支持库可帮助理解寄存器配置和底层驱动调用。通过研读例程可以快速掌握在GD32F30x上启用单线双向SPI的关键步骤与调试方法为后续在传感器、存储、显示等外设通信中灵活复用打下基础。 前阵子调一块板子主控是GD32F30x资源表排到最后发现PA6实在没地方安排了。SPI0的MISO挂在那边可整个项目里没有一个外设需要真正从SPI读数据反而是PA6边上缺一个按键输入。换引脚吧布线已经投出去了软件模拟SPI吧又要被同事评审吐槽。后来翻SDK头文件看到SPI_CTL0的BDOEN和BDOE两位才想起来GD32F30x的SPI外设本来就支持单线双向模式把MOSI当一根半双工数据线用MISO整个释放出去。这篇博文把这个模式从硬件机制、寄存器配置到实测踩坑完整讲一遍适合正被引脚资源逼疯的嵌入式工程师也适合想把SPI协议底层机制搞清楚的初学者。1. 一个引脚救急的场景MISO真的非留不可吗1.1 项目背景SPI外设占着MISO实际却用不上很多人看到SPI第一反应就是SCK、MOSI、MISO、CS四根线但实际项目里并不是所有SPI外设都要走全双工。比如我这次对接的从设备协议很简单主机写一个命令字节再从数据线上读结果从设备的数据引脚和命令引脚物理上就是同一根线并不存在标准SPI那种主机出从机入和从机出主机入分开走线的需求。这种情况下标准四线SPI接法里MISO这根线从头到尾是闲置的。但因为是硬件外设的默认映射PA6作为SPI0_MISO被占着。想把它当普通GPIO用就得关掉SPI外设或者改引脚重映射而改重映射又会影响SCK和MOSI的现有分配牵一发动全身。GD32F30x的单线双向模式解决的就是这个问题把MOSI变成一根收发共用的数据线MISO从SPI控制逻辑里整个摘出去随便你怎么用。1.2 单线双向模式能省什么不能省什么先说清楚边界。单线双向模式省的是MISO这根线数据线只有一根也就是MOSI引脚更准确地说手册里叫SIMO主出从入脚。这意味着通信方向从全双工变成了半双工同一时刻要么发送要么接收不能像标准SPI那样一边发一边收。SCK和CS还在时钟仍然由主机产生片选逻辑也完全保留。所以如果你的设备需要全双工通信比如同时读写W25Q64这种SPI Flash那就老老实实用标准四线模式单线双向模式替代不了。但如果你只是写命令、读结果这种时序上交替进行的半双工通信那么单线双向模式完全够用而且能帮你省下一根宝贵的GPIO。各类模式的对比关系大致如下对比项标准四线SPISPI单线双向模式IIC数据线数量MOSI MISO 两根一根MOSI一根SDA 时钟SCL通信方向全双工半双工半双工时钟线SCK 由主机产生SCK 由主机产生SCL 由主机产生片选机制CS 每设备一根CS 每设备一根无片选靠从机地址速度上限可达外设时钟/2可达外设时钟/2标准 100k/400k/1M 级别多从机扩展需要多个CS需要多个CS靠地址扩展CS更省这个表格拿来做方案选型很直观后面第5章还会结合实测数据再展开。2. 硬件机制BDOEN和BDOE到底怎么控制一根线2.1 一个位决定方向BDOEN打开半双工BDOE选择收发GD32F30x的SPI控制寄存器SPI_CTL0里有两个关键位BDOEN和BDOE。BDOEN置1后SPI会从标准全双工模式切换为单线双向模式此时数据引脚收归到MOSI一根线上MISO引脚从SPI逻辑中断开退化成普通IO前提是AFIO重映射配置允许。BDOE则控制当前数据线的方向置1表示输出使能SPI把数据写到MOSI上清零表示输入使能MOSI对外呈现高阻状态由对端设备驱动。你可以把这两个位理解成一根水管的两个阀门。BDOEN是总阀打开才有水BDOE是换向阀决定水往哪边流。实际配置的时候用标准库就是一行spi_bidirectional_transfer_config(SPI0, SPI_BIDIRECTIONAL_TRANSMIT)或者SPI_BIDIRECTIONAL_RECEIVE库函数内部帮你把BDOEN置1、把BDOE按方向置位或清零。如果直接操作寄存器就是下面这样/* 切到发送方向 */ SPI0-CTL0 | SPI_CTL0_BDOEN; SPI0-CTL0 | SPI_CTL0_BDOE; /* 切到接收方向 */ SPI0-CTL0 | SPI_CTL0_BDOEN; SPI0-CTL0 ~SPI_CTL0_BDOE;要注意的是这两个位的名称在GD32和STM32里叫法略有不同ST那边习惯叫BIDIMODE和BIDIOE功能是一回事。移植代码或查手册时别被名字绕晕。2.2 发送、接收两条路径分别由谁驱动数据线标准四线SPI好理解主机发数据从MOSI输出从机发数据从MISO输出两个方向互不干扰。单线双向模式下只有一根线所以必须搞清楚每个时刻数据线由谁驱动。主机发送阶段BDOE1MOSI由SPI外设的移位寄存器驱动SCK每个时钟沿向外输出一个bit从机在对应边沿采样。主机接收阶段BDOE0MOSI引脚对外呈高阻外部从机获得数据线控制权从机在SCK边沿把数据一位一位送到MOSI上主机采样。这里有个容易搞错的点从机模式下的数据引脚也是MOSI。也就是说两块GD32F30x做单线双向通信时主机的MOSI要和从机的MOSI直连而不是像标准SPI那样主MOSI接从MISO。我见过有人按惯性思维接了主MOSI到从MISO结果两边都收发不到数据。原因就是这个模式里MISO已经退出了。2.3 接收时也要写哑数据SPI主机的时钟产生机制标准SPI模式下主机接收数据时必须往发送数据寄存器写一个哑数据目的不是发送而是触发SCK时钟的产生否则时钟不跑谁也采不到数据。单线双向模式的接收阶段同样遵循这个规则。有个细节值得注意接收方向时BDOE0这时往数据寄存器写哑数据数据不会真正出现在MOSI引脚上引脚依然保持高阻等待从机驱动。所以哑数据写0x00还是0xFF都无所谓它唯一的作用就是让SPI移位器转起来、把SCK带起来。很多第一次用单线接收的人会卡在等不到RBNE置位排查半天发现是忘了写这个哑数据。主机SPI的接收并不是什么都不用做就能收到数据的。3. 能直接抄走的工程代码标准库和寄存器两种写法3.1 时钟和GPIO别把数据引脚配置错先看初始化部分GD32F30x的标准库写法如下void spi_single_wire_init(void) { rcu_periph_clock_enable(RCU_GPIOA); rcu_periph_clock_enable(RCU_AF); rcu_periph_clock_enable(RCU_SPI0); /* SCK和MOSI都配置为复用推挽输出 */ gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_5); gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_7); /* SPI0初始化主机、8bit、模式0 */ spi_parameter_struct spi_init_struct; spi_init_struct.trans_mode SPI_TRANSMODE_BDRECEIVE; /* 初始为单线双向接收 */ spi_init_struct.device_mode SPI_MASTER; spi_init_struct.frame_size SPI_FRAMESIZE_8BIT; spi_init_struct.clock_polarity SPI_CK_PL_LOW_PH_1EDGE; spi_init_struct.nss SPI_NSS_SOFT; spi_init_struct.prescale SPI_PSC_32; spi_init_struct.endian SPI_ENDIAN_MSB; spi_init(SPI0, spi_init_struct); spi_enable(SPI0); }这里有一个关键点MOSI引脚即使当前处于接收方向也保持复用推挽配置。因为引脚的实际方向由SPI外设内部根据BDOE位控制而不是由GPIO模块控制。GPIO只需要把引脚所有权交给SPI外设就行不需要也不应该手动改GPIO模式。这一点到第4章踩坑部分还会细说。3.2 发送一帧数据的完整流程单线模式下发送数据的代码和标准SPI差不多但多了方向切换这一道工序void spi_single_wire_transmit(uint8_t data) { /* 1. 切到发送方向 */ spi_bidirectional_transfer_config(SPI0, SPI_BIDIRECTIONAL_TRANSMIT); /* 2. 等待发送缓冲为空 */ while (spi_i2s_flag_get(SPI0, SPI_FLAG_TBE) RESET); /* 3. 写入数据 */ spi_i2s_data_transmit(SPI0, data); /* 4. 等待物理发送完成 */ while (spi_i2s_flag_get(SPI0, SPI_FLAG_BSY) SET); }第4步的BSY等待别省略。TBE置位只是表示数据从软件缓冲搬进了移位寄存器此时SCK可能还在跑最后一个bit还在引脚上往外送。如果不等BSY清零就切到接收方向有可能切断最后一个bit的发送导致从机收到错误帧。这一点在半双工模式下特别致命因为方向切换本身就是在时序敏感区做的操作。3.3 接收一帧数据的完整流程接收的代码比发送多一个产生时钟的动作uint8_t spi_single_wire_receive(void) { uint8_t data; /* 1. 切到接收方向 */ spi_bidirectional_transfer_config(SPI0, SPI_BIDIRECTIONAL_RECEIVE); /* 2. 写哑数据产生SCK */ while (spi_i2s_flag_get(SPI0, SPI_FLAG_TBE) RESET); spi_i2s_data_transmit(SPI0, 0xFF); /* 3. 等待接收缓冲非空 */ while (spi_i2s_flag_get(SPI0, SPI_FLAG_RBNE) RESET); /* 4. 读取数据 */ data spi_i2s_data_receive(SPI0); return data; }我在实际调试时会把这个接收流程再固化一层等待RBNE之后也顺手等一下BSY清零再返回因为调用方可能立刻切换方向或拉高CS如果返回时机太早接收的最后一拍可能还没稳定落进移位寄存器下次操作就会冲掉数据。宁可多等几十个循环也不要在这种细节上省时间。3.4 方向切换一次完整写读交互的实战模板实际项目中很少只发不收更多是写命令、读结果这种组合动作。下面这段代码是一块半双工从设备完整的交互流程包含软件CS控制和换向延时#define SPI0_CS_LOW() gpio_bit_reset(GPIOA, GPIO_PIN_4) #define SPI0_CS_HIGH() gpio_bit_set(GPIOA, GPIO_PIN_4) void spi_single_wire_write_read(uint8_t tx_data, uint8_t *rx_data) { SPI0_CS_LOW(); /* 发送阶段 */ spi_bidirectional_transfer_config(SPI0, SPI_BIDIRECTIONAL_TRANSMIT); while (spi_i2s_flag_get(SPI0, SPI_FLAG_TBE) RESET); spi_i2s_data_transmit(SPI0, tx_data); while (spi_i2s_flag_get(SPI0, SPI_FLAG_BSY) SET); /* 换向稳定时间实测必不可少 */ for (volatile int i 0; i 10; i); /* 接收阶段 */ spi_bidirectional_transfer_config(SPI0, SPI_BIDIRECTIONAL_RECEIVE); while (spi_i2s_flag_get(SPI0, SPI_FLAG_TBE) RESET); spi_i2s_data_transmit(SPI0, 0xFF); /* 产生SCK */ while (spi_i2s_flag_get(SPI0, SPI_FLAG_RBNE) RESET); *rx_data spi_i2s_data_receive(SPI0); while (spi_i2s_flag_get(SPI0, SPI_FLAG_BSY) SET); SPI0_CS_HIGH(); }这段代码我在多个GD32F30x型号包括GD32F303系列上跑过时序稳定。换向延时用了一个空循环具体循环次数跟主频有关我的经验是宁可多一点。后面第4章会解释为什么这个延时躲不掉。4. 实测中踩过的坑换向、电气冲突、DMA和CS时序4.1 方向切换后第一字节丢失等BSY清零还不够第一次调通单线双向模式时发送阶段一切正常接收阶段总是少第一个字节。逻辑分析仪抓出来的波形很诡异CS拉低后MOSI上确实出现了8个bit的命令字节然后数据线空了一小段接着从机的响应才出现。问题出在从机响应太快而我切到接收模式的动作太慢。从机在命令字节结束后立刻想把数据推上来但此时我这边BDOE还是1MOSI还被主机占着两边对拉从机推不上来错过第一拍。解决思路有两个方向一是从机侧看能不能在命令结束后等一下再驱动数据线这取决于协议是否允许二是主机侧把换向动作做得更利索比如在BSY清零前就提前把BDOE配置好。实测下来在while(BSY)循环里提前切换BDOE并不安全因为BSY还置位时最后一个bit可能还在引脚上。最终我采用的方案是BSY清零后先切BDOE再补一段空循环延时给从机一点准备时间。从机准备数据需要时间主机换向也需要时间两边都留出余量丢字节问题就消失了。4.2 接收模式下的电气冲突别手贱改GPIO配置这个坑我印象最深。最初我认为接收数据时MOSI引脚应该配置成输入模式于是在切换BDOE的同时把PA7的GPIO模式从复用推挽改成了浮空输入。结果是从设备确实能驱动数据线了但SPI外设的数据接收通道也跟着出了问题——RBNE倒是会置位可读出来的数据是乱的偶尔还会多出一些毛刺bit。原因是SPI外设在单线双向模式下对引脚方向的管理是内部完成的GPIO模块只需要保持复用功能即可。一旦手动把GPIO改成输入模式相当于绕过了SPI的引脚驱动逻辑SPI输出的使能控制失效甚至可能影响采样窗口。正确做法是初始化时配置一次复用推挽之后在代码里根本不用再碰GPIO寄存器方向切换完全交给BDOE位。4.3 DMA配合的注意点半双工模式下不能同时开收发通道单线双向模式可以配合DMA使用但比全双工模式麻烦不少。全双工时SPI的DMA发送请求和DMA接收请求各自独立可以同时开启单线双向模式下只有一根数据线收发通道绝不能同时使能否则硬件上就是两根DMA通道往同一个引脚上踩轻则数据错乱重则总线冲突。我遇到的具体问题是DMA发送完成后在DMA传输完成中断里立刻调用DMA接收逻辑结果第一帧接收数据丢了。原因和4.1类似——DMA的传输完成标志表示的是DMA搬运完成不是SPI物理发送完成。DMA把最后一个字节丢进SPI数据寄存器时SPI的移位器可能还在逐位输出此时切换方向和DMA通道等于把没有发送完的尾巴掐断了。正确顺序是DMA传输完成中断里先等SPI的BSY清零再失能DMA发送通道再使能DMA接收通道之后再往SPI数据寄存器写哑数据触发时钟。4.4 软件CS而不是硬件CS切换方向时片选不能抬GD32F30x的SPI支持硬件NSS模式标准SPI通信时很好用帧开始硬件自动拉低CS帧结束自动拉高。但单线双向模式下硬件NSS会按一个个数据帧自动控制CS电平也就是说发送完一个字节、接收完一个字节CS都会经历一次完整的拉低拉高。问题在于单线双向模式的一次事务往往跨越多个数据帧比如发命令、收结果就是两个帧。硬件NSS在命令帧结束后立刻拉高CS从设备会认为这次通信结束了等主机切换方向后试图接收数据时从设备已经退出通信状态数据线上一片寂然。如果把CS当成一个时序上的总开关那么整个事务期间CS必须持续拉低直到所有数据收发完毕才能抬起硬件NSS做不到这点。解决方案就是软CSNSS配置为SPI_NSS_SOFTCS引脚用普通GPIO控制在整个写读交互期间保持低电平全部结束后再拉高。第3章的代码模板就是这个思路实际跑起来从设备的状态机完全正常。5. 和IIC、软件模拟SPI放一起怎么选5.1 三类方案对照接线、速度、资源消耗搞嵌入式的大概率都纠结过SPI和IIC最近还冒出一个软件模拟SPI的选项。拿GD32F30x来说我把三个方案放一起对比维度硬件SPI单线双向硬件IIC软件模拟SPI接线SCK MOSI CS三根SCL SDA两根可任意引脚配对全双工支持不支持不支持可支持两根线速度高可达数十MHz低一般400k/1M低取决于CPU翻转频率CPU占用低可配DMA中需处理应答/总线状态高每个bit都要CPU参与多从机扩展每从机一个CS靠地址适合挂多设备每从机一个CS时序稳定性硬件保证稳定硬件保证稳定受中断和编译优化影响单线双向模式最尴尬的是无法全双工如果外设是标准四线SPI比如W25Q64、常见LCD屏那么它永远不在考虑范围内。剩下两类场景适合它一是外设协议本身就是半双工二是板级通信只需要一问一答。5.2 我现在的选型判断标准踩过这些坑之后我的判断逻辑基本固定了。第一条协议要求全双工没有任何讨论余地用标准四线SPIMISO必须保留。第二条协议是半双工但速度要求比较高或者想留出扩展空间优先考虑硬件单线双向模式省下的MISO做GPIO或复用其他外设功能。第三条速度要求不高、需要挂多个从设备且不想拉一堆CS线用IIC更合适它的从机地址机制天然为此设计。第四条临时调试、飞线验证、引脚实在没得配才用软件模拟SPI兜底。GD32F30x的单线双向模式确实是个容易被忽略的功能。固件库的trans_mode枚举里SPI_TRANSMODE_BDRECEIVE和SPI_TRANSMODE_BDTRANSMIT就安静地躺在那里但绝大多数教程都在讲全双工导致它在需要的时候根本想不起来。如果你手头正好有类似的项目引脚资源又紧张不妨按这篇文章的代码模板试一下。实际调试中把第4章那几个坑记在心里尤其是换向延时和软件CS这两点基本就能少熬几个小时的抓波形的夜。本文还有配套的精品资源点击获取
返回列表