当前位置:首页 > EDA > 电子设计自动化
[导读]没有硬件SPI外设、或者SPI总线被LCD/Flash占满时,用普通GPIO模拟SPI是兜底方案。只要按CPOL/CPHA把时钟沿和采样点写对,SAR ADC在几十kHz~几百kHz SCK下能稳定出数;再配合寄存器直写和批量读,可进一步逼近芯片上限。


没有硬件SPI外设、或者SPI总线被LCD/Flash占满时,用普通GPIO模拟SPI是兜底方案。只要按CPOL/CPHA把时钟沿和采样点写对,SAR ADC在几十kHz~几百kHz SCK下能稳定出数;再配合寄存器直写和批量读,可进一步逼近芯片上限。

一、先抽象底层IO,做到无库可移植

不依赖HAL/RTOS,只用“写寄存器/置位宏”。以通用寄存器映射为例:

typedef struct {

   volatile uint32_t *dir;   // 方向寄存器

   volatile uint32_t *set;   // 输出置1

   volatile uint32_t *clr;   // 输出清0

   volatile uint32_t *in;    // 输入电平

   uint8_t sck, mosi, miso, cs;

   uint8_t cpol, cpha;       // 0/0或1/1常用

} soft_spi_t;


static inline void pin_set(volatile uint32_t *s, uint8_t b){ *s |= (1u<<b); }

static inline void pin_clr(volatile uint32_t *s, uint8_t b){ *s &= ~(1u<<b); }

static inline uint8_t pin_get(volatile uint32_t *i, uint8_t b){ return (*i>>b)&1u; }


void sspi_init(soft_spi_t *s){

   *s->dir |= (1u<<s->sck)|(1u<<s->mosi)|(1u<<s->cs);

   *s->dir &= ~(1u<<s->miso);

   pin_set(s->set, s->cs);                       // CS空闲高

   pin_set(s->set, s->sck); if(!s->cpol) pin_clr(s->clr, s->sck);

}

不同MCU把dir/set/clr/in换成对应端口寄存器即可,应用层不动。

二、模式0/模式3单字节交换

SPI四模式由CPOL(空闲时钟)和CPHA(采样边沿)决定:模式0空闲低、上升沿采样;模式3空闲高、上升沿采样。 ADC常用模式0,少数高速片用模式3。统一写一个交换函数:

static inline void delay_ns(uint32_t n){        // 按主频折算空循环

   for(volatile uint32_t i=0;i<n;i++);

}


uint8_t sspi_xchg(soft_spi_t *s, uint8_t tx){

   uint8_t rx=0;

   uint8_t idle = s->cpol ? 1:0;

   uint8_t lead = s->cpha ? 1:0;               // 先翻时钟再采样=CPHA1

   for(uint8_t bit=0; bit<8; bit++){

       if(!lead) {                              // 采样前沿前先放数据

           (tx&0x80)?pin_set(s->set,s->mosi):pin_clr(s->clr,s->mosi);

       }

       idle? pin_clr(s->clr,s->sck) : pin_set(s->set,s->sck); // 产生采样沿

       delay_ns(2);

       rx<<=1; if(pin_get(s->in,s->miso)) rx|=1;

       idle? pin_set(s->set,s->sck) : pin_clr(s->clr,s->sck); // 释放沿

       delay_ns(2);

       tx<<=1;

   }

   return rx;

}

模式0传cpol=0,cpha=0、模式3传cpol=1,cpha=1,其余由lead/idle自动切换。若只跑一种ADC,把if判断展开成专用函数,能少走分支、提速率。

三、驱动SAR ADC:以单/多通道为例

很多SPI SAR(如MCP3201单通道、MCP3204/3208多通道、TLC2543多通道)都是“发命令字节+读12/10/16位”。多通道示例:

uint16_t adc_read_ch(soft_spi_t *s, uint8_t ch, uint8_t bits){

   uint16_t val=0;

   pin_clr(s->clr, s->cs);

   if(bits==12){                                // 类MCP3204/3208帧

       sspi_xchg(s, 0x06|(ch>>2));              // start=1,diff=0,高1位地址

       val  = (sspi_xchg(s, ch<<6) & 0x0F)<<8;  // 取低4位高半

       val |= sspi_xchg(s, 0x00);               // 低8位

   } else {                                     // 类TLC2543帧

       sspi_xchg(s, (ch<<4)|0x0C);              // 16clk,12bit,msb

       sspi_xchg(s, 0x00);

       val  = sspi_xchg(s, 0x00);               // 依芯片调整返回帧

   }

   pin_set(s->set, s->cs);

   return val;

}

只读单通道、无地址的ADC可省命令字节,CS拉低后直接时钟出数据,进一步省时间。

四、提速四招

直写寄存器不用库函数:HAL_GPIO_WritePin类有函数调用和判断开销,BSRR/set-clr寄存器直写更快;关键路径用static inline。

最短合法延时:SCK高低脉宽按ADC手册最小t{SCK,H}/t{SCK,L}取整,空循环用主频标定;不盲目加for(i<100)。

批量连续读:多通道轮询时,全部读完再拉高CS,避免每通道重新片选;换通道只发地址字节,不重初始化。

中断临界:高速采样时在xchg外层关全局中断或提高BASEPRI,防止ISR插入导致时钟拉伸;若ADC有DRDY/IDLE,用GPIO中断触发批量搬数,再交给主循环处理。

五、边界与替代

软件SPI速率受CPU主频、GPIO翻转和延时精度限制,一般适用于几十k~几百k SCK;若ADC要求1MHz以上、或多通道持续百kSPS,软件SPI会吃满CPU且抖动大,应改硬件SPI+DMA,或用FPGA做串并转换。 调试先用逻辑分析仪抓CS/SCK/MOSI/MISO,确认模式、MSB、建立保持时间,再谈“高速”。

把上述soft_spi_t做成全局句柄,ADC驱动只调用xchg和cs控制,换芯片只改adc_read_ch的命令帧,换MCU只改pin宏,基本能做到无依赖、可复用。

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭