大型量产固件的工程实践(九):GPIO 模拟 SPI 驱动设计
大型量产固件的工程实践九GPIO 模拟 SPI 驱动设计本文是《大型量产固件的工程实践》专栏第 9 篇。上一篇第 8 篇《多款同类型芯片驱动的统一抽象》 下一篇第 9 篇实战《GPIO 模拟 SPI 实现源码与使用范例》一、为什么需要 GPIO 模拟 SPISPI 是嵌入式最常用的外设总线之一。但很多场景下硬件 SPI 不够用硬件 SPI 通道被占用或干脆没有多余通道非字节对齐地址某些芯片的寄存器地址是 9-bit、10-bit而硬件 SPI 通常只支持 8-bit 的倍数时序特殊需要自定义 CPOL/CPHA、时钟极慢、或插入特定延时。这时就要用GPIO 位绑bit-bang模拟 SPI——用普通 GPIO 引脚按 SPI 时序手动翻转时钟线、读写数据线。本文给出一个通用、可复用的 bit-bang SPI 驱动设计核心亮点是任意位宽传输 CPOL/CPHA 四套时序 驱动/协议分层。二、设计总览驱动层与协议层分离一个关键设计原则驱动层只做纯 SPI 位绑收发不掺任何具体芯片的协议。协议层每个芯片一套 驱动层通用只此一套 ┌──────────────────────┐ ┌──────────────────────────┐ │ 组装芯片特定命令格式 │ │ 只负责时钟线 数据线翻转 │ │ 如R/W 标志 地址编码│ ──▶ │ 不关心地址是 9-bit 还是 │ │ 决定先发哪些 bit │ │ 16-bit也不关心 R/W 语义 │ └──────────────────────┘ └──────────────────────────┘这样换一颗芯片只需改协议层的命令组装驱动层零改动。三、通用 transfer 接口任意位宽驱动层对外只暴露一个传输接口发送和接收的位宽可以不同/* 发送 txBitLen 位接收 rxBitLen 位可只发不收或只收不发 */voidhwspi_transfer(constuint8_t*tx_buf,uint8_ttx_bit_len,uint8_t*rx_buf,uint8_trx_bit_len);tx_bit_len/rx_bit_len支持1~32 bit 任意值不受字节对齐限制内部把位拆成完整字节 剩余 bit两段处理。/* 发送先发完整字节再发剩余 bit */full_bytestx_bit_len3;for(i0;ifull_bytes;i)for(bit0;bit8;bit)send_one_bit(...);remain_bitstx_bit_len0x07;for(i0;iremain_bits;i)send_one_bit(...);这就是非字节对齐地址能直接传输的关键。四、CPOL / CPHA 四套时序SPI 有 4 种模式CPOL×CPHA它们的时钟相位和采样边沿不同。设计上用4 个static inline函数分别实现传输前用函数指针分派选定避免在每个 bit 里做分支判断/* 输出 bit 的函数指针类型 */typedefvoid(*out_bit_fn)(constuint8_t*buf,uint8_tbyte,uint8_tbit,uint8_tlsb,bool sck_active,bool sck_idle,uint8_tdly);4.1 CPHA0数据在第一个边沿变化在第二个边沿采样/* 输出SCK 变 active 时换 MOSI变 idle 前数据保持 */staticinlinevoidout_bit_cpha0(...){gpio_write(SCK,sck_active);// 时钟翻转gpio_write(MOSI,bit_value(buf,...));// 同时送出数据delay_us(dly);gpio_write(SCK,sck_idle);// 时钟回落delay_us(dly);}/* 输入SCK 变 active 后采样 MISO */staticinlinevoidin_bit_cpha0(...){gpio_write(SCK,sck_active);if(gpio_read(MISO))bit_set(buf,...);// 采样delay_us(dly);gpio_write(SCK,sck_idle);delay_us(dly);}4.2 CPHA1数据在第一个边沿采样在第二个边沿变化/* 输出先换 MOSI再翻转 SCK */staticinlinevoidout_bit_cpha1(...){gpio_write(MOSI,bit_value(buf,...));// 先送数据gpio_write(SCK,sck_active);delay_us(dly);gpio_write(SCK,sck_idle);// 采样发生在回落沿delay_us(dly);}/* 输入SCK 回落沿采样 */staticinlinevoidin_bit_cpha1(...){gpio_write(SCK,sck_active);delay_us(dly);gpio_write(SCK,sck_idle);// 回落沿if(gpio_read(MISO))bit_set(buf,...);// 此时采样delay_us(dly);}4.3 函数指针分派传输开始时根据 CPHA 选一次函数指针之后每个 bit 都走同一个函数out_bit_fn out_bitcpha?out_bit_cpha1:out_bit_cpha0;收益static inline保证零函数调用开销函数指针分派保证循环体里没有 per-bit 的 if 分支——这对 bit-bang 这种每个 bit 都要快的场景很重要。五、关键细节5.1 CPOL 只影响空闲电平CPOL 决定时钟空闲时是高还是低但活动电平始终是空闲电平的取反可以从空闲电平推导sck_activesck_idle^1;// 活动电平 空闲电平取反所以 4 个时序函数只需关心active/idle 两个电平CPOL 通过把哪一档当成空闲来体现。5.2 LSB/MSB 可配置bit 的提取/写入方向可配/* lsb_first1 从最低位开始0 从最高位开始 */uint8_tbit_value(constuint8_t*buf,uint8_tbyte,uint8_tbit,uint8_tlsb_first){return(buf[byte](lsb_first?bit:(7-bit)))0x01;}5.3 传输流程voidhwspi_transfer(constuint8_t*tx_buf,uint8_ttx_bit_len,uint8_t*rx_buf,uint8_trx_bit_len){gpio_write(CS,active);// 拉低片选进入传输gpio_write(SCK,sck_idle);// 确保时钟在空闲电平if(tx_bit_len)tx_bits(tx_buf,tx_bit_len,...);// 发送阶段if(rx_bit_len){// 接收阶段memset(rx_buf,0,(rx_bit_len7)3);rx_bits(rx_buf,rx_bit_len,...);}gpio_write(CS,idle);// 拉高片选结束传输gpio_write(SCK,sck_idle);// 时钟恢复空闲}六、使用范例协议层组装驱动层只管收发 bit协议层负责把芯片命令组装成要发的 bit。以一颗16-bit 地址 8-bit 数据、读操作最高位置 1的芯片为例/* 协议层读寄存器 —— 组装命令然后调通用 transfer */uint16_tsensor_read_reg(uint16_taddr){uint8_ttx[3];uint8_trx[1]{0};/* 16-bit 地址读标志(R/W1)放在最高位 */uint16_tcmdaddr|0x8000;tx[0](uint8_t)(cmd8);// 地址高字节tx[1](uint8_t)(cmd);// 地址低字节/* 发 16-bit 命令收 8-bit 数据 */hwspi_transfer(tx,16,rx,8);returnrx[0];}注意R/W 标志、地址位宽、字节序这些芯片特有的东西全在协议层处理驱动层始终是那个通用的hwspi_transfer。七、小结一个通用、可复用的 bit-bang SPI 驱动四个设计要点要点说明驱动/协议分离驱动只做位绑协议组装命令换芯片零改驱动任意位宽transfer支持 1~32 bit非字节对齐地址直接传四套时序CPOL/CPHA 用 4 个static inline 函数指针分派循环体无分支电平推导CPOL 只定空闲电平活动电平取反推导这套设计最大的价值在于通用性——它不绑定任何具体芯片任何要按位精确控制时序的外设都能复用。下一篇我们把视角从位级驱动上升到状态级设计聊聊嵌入式里无处不在的状态机。本文仅介绍 GPIO 模拟 SPI 的通用驱动设计方法不涉及任何具体项目或产品的私有实现。

相关新闻

基于VScode搭建GD32开发环境

基于VScode搭建GD32开发环境

——————————————————————————————————————————— 声明 首先声明一下我这个教程需要使用到的工具链和硬件组网。 工具链:cmake、ninja、llvm.clang、J-Link烧录环境、arm-none-eabi全家桶; 硬件组网&#xf…

2026/11/16 4:38:53 阅读更多 →
autocad 2020 模型窗口输出布局窗口

autocad 2020 模型窗口输出布局窗口

布局空间是模型空间的观察窗口,便于进行局部查看和打印操作模型窗口和布局窗口数据编辑同步多个图框图纸输出,一个图纸输出到一个布局窗口操作步骤:1. 打开 CAD 图纸,如果没有布局,可以新建布局;2. 点击布局…

2026/11/16 4:38:53 阅读更多 →
大型量产固件的工程实践(十):嵌入式状态机设计——多阶段初始化与断点续传

大型量产固件的工程实践(十):嵌入式状态机设计——多阶段初始化与断点续传

大型量产固件的工程实践(十):嵌入式状态机设计——多阶段初始化与断点续传 本文是《大型量产固件的工程实践》专栏第 10 篇。 上一篇:第 9 篇实战《GPIO 模拟 SPI 实现源码与使用范例》 | 下一篇:第 11 篇《健壮的网络状态机》 一、为什么需要状态机 嵌入式里很多外设的…

2026/10/31 19:33:06 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/11/15 5:13:35 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/11/15 5:13:35 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/11/15 5:13:35 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/11/15 3:37:41 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/11/15 4:18:38 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/11/15 5:13:35 阅读更多 →

日新闻

周新闻

月新闻