4. 驱动开发环境搭建

SPI,全称 Serial Peripheral Interface,说白了就是串行外设接口。我刚开始接触嵌入式时,总觉得它比I2C复杂,后来才发现——其实SPI的逻辑非常直白,就是「主设备发时钟,从设备收数据」。今天咱们就把它彻底讲透,然后直接上手驱动一块SSD1306 OLED屏。

4.1 SPI协议基础:四根线,一个故事

SPI总线只需要四根线:

  • SCLK(Serial Clock):时钟线,由主设备产生
  • MOSI(Master Out Slave In):主设备输出,从设备输入
  • MISO(Master In Slave Out):主设备输入,从设备输出
  • CS(Chip Select):片选线,低电平有效

为什么说它简单?因为SPI是全双工的。主设备发一个bit的同时,从设备也能回一个bit。我在项目中遇到过一位同事,非要用SPI模拟UART,折腾了两天——其实选对协议比硬凑重要得多。

核心要点:SPI没有像I2C那样的设备地址,全靠CS片选来区分从设备。你拉低哪个CS,哪个设备就跟你通信。

4.2 SPI的四种模式:时钟极性(CPOL)和时钟相位(CPHA)

这是新手最容易踩坑的地方。SPI有四种模式,由两个参数决定:

模式 CPOL CPHA 时钟空闲电平 数据采样边沿
模式0 0 0 低电平 上升沿
模式1 0 1 低电平 下降沿
模式2 1 0 高电平 上升沿
模式3 1 1 高电平 下降沿

你想想看,如果主设备配的是模式0,从设备配的是模式3,数据根本对不上。我曾经因为没仔细看SSD1306的数据手册,默认用了模式0,结果屏幕死活不亮——折腾了半小时才发现手册里写的是模式3。

我的习惯:拿到任何SPI器件,第一件事就是翻数据手册的「SPI Timing Diagram」章节。看清楚了再配寄存器,能省下大量调试时间。

4.3 SPI模式配置实战:以STM32为例

配置SPI其实就几步:

  1. 使能SPI和GPIO时钟
  2. 配置GPIO为复用功能
  3. 设置SPI参数(模式、波特率、数据位宽等)
  4. 使能SPI外设

下面是一个典型的初始化代码:

void SPI_Init(void)
{
    // 1. 使能时钟
    RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;

    // 2. 配置GPIO: PA5(SCK), PA7(MOSI), PA4(CS)
    GPIOA->MODER |= GPIO_MODER_MODER5_1;  // 复用功能
    GPIOA->MODER |= GPIO_MODER_MODER7_1;
    GPIOA->MODER |= GPIO_MODER_MODER4_0;  // 推挽输出

    GPIOA->AFR[0] |= 0x00500000;  // PA5, PA7 复用为 SPI1

    // 3. 配置SPI
    SPI1->CR1 = SPI_CR1_SSM | SPI_CR1_SSI;  // 软件片选
    SPI1->CR1 |= SPI_CR1_BR_2 | SPI_CR1_BR_1;  // 波特率分频
    SPI1->CR1 |= SPI_CR1_MSTR;  // 主模式
    SPI1->CR1 |= SPI_CR1_CPOL | SPI_CR1_CPHA;  // 模式3

    // 4. 使能SPI
    SPI1->CR1 |= SPI_CR1_SPE;
}

注意:配置完SPI后,记得先拉高CS引脚,避免在初始化过程中误触发从设备。我见过有人把CS初始化为低电平,结果一上电从设备就进入了错误状态。

4.4 驱动SSD1306 OLED屏实战

SSD1306是一款非常经典的OLED驱动芯片,支持SPI和I2C两种接口。咱们这里用SPI模式3来驱动它。

4.4.1 硬件连接

SSD1306引脚 MCU引脚 说明
CS PA4 片选,低电平有效
DC PA3 数据/命令选择:0=命令,1=数据
RES PA2 复位,低电平复位
SCLK PA5 SPI时钟
MOSI PA7 SPI数据

嗯,这里要注意:SSD1306的DC引脚不是SPI标准引脚,它是用来区分你发的是命令还是数据的。这是很多SPI器件都会有的额外控制线。

4.4.2 初始化序列

SSD1306上电后需要发送一串初始化命令。我习惯把这些命令放在一个数组里:

static const uint8_t init_cmds[] = {
    0xAE,       // 关闭显示
    0xD5, 0x80, // 设置时钟分频
    0xA8, 0x3F, // 设置多路复用比
    0xD3, 0x00, // 设置显示偏移
    0x40,       // 设置显示起始行
    0x8D, 0x14, // 启用电荷泵
    0x20, 0x00, // 设置内存地址模式为水平
    0xA1,       // 段重映射(左右翻转)
    0xC8,       // COM扫描方向(上下翻转)
    0xDA, 0x12, // 设置COM引脚配置
    0x81, 0xCF, // 设置对比度
    0xD9, 0xF1, // 设置预充电周期
    0xDB, 0x40, // 设置VCOMH电压
    0xA4,       // 全局显示开启
    0xA6,       // 正常显示(非反色)
    0xAF        // 开启显示
};

发送命令的函数也很简单:

void SSD1306_WriteCmd(uint8_t cmd)
{
    GPIO_ResetBits(GPIOA, GPIO_Pin_3);  // DC = 0,表示命令
    GPIO_ResetBits(GPIOA, GPIO_Pin_4);  // CS = 0,选中设备

    while (!(SPI1->SR & SPI_SR_TXE));  // 等待发送缓冲区空
    SPI1->DR = cmd;
    while (!(SPI1->SR & SPI_SR_RXNE));  // 等待接收完成(清标志)

    GPIO_SetBits(GPIOA, GPIO_Pin_4);    // CS = 1,释放设备
}

避坑指南:我曾经在发送命令时忘了拉低CS,结果数据根本没发出去。后来我养成了一个习惯:每次SPI传输前后都检查CS的电平状态。

4.4.3 显示一帧数据

SSD1306的分辨率是128×64,共1024个字节。每个字节控制8个像素点(垂直排列)。显示数据时,先设置页地址和列地址,然后连续发送数据:

void SSD1306_Display(void)
{
    // 设置页地址范围(0~7)
    SSD1306_WriteCmd(0x22);
    SSD1306_WriteCmd(0x00);  // 起始页
    SSD1306_WriteCmd(0x07);  // 结束页

    // 设置列地址范围(0~127)
    SSD1306_WriteCmd(0x21);
    SSD1306_WriteCmd(0x00);  // 起始列
    SSD1306_WriteCmd(0x7F);  // 结束列

    // 发送帧缓冲区数据
    GPIO_ResetBits(GPIOA, GPIO_Pin_3);  // DC = 1,表示数据
    GPIO_ResetBits(GPIOA, GPIO_Pin_4);  // CS = 0

    for (uint16_t i = 0; i < 1024; i++)
    {
        while (!(SPI1->SR & SPI_SR_TXE));
        SPI1->DR = frame_buffer[i];
        while (!(SPI1->SR & SPI_SR_RXNE));
    }

    GPIO_SetBits(GPIOA, GPIO_Pin_4);    // CS = 1
}

4.5 知识体系总览

下面这张图把本章的核心逻辑串起来了:

SPI显示接口驱动知识体系 SPI显示接口驱动知识体系 SPI协议基础 四根信号线 SCLK / MOSI MISO / CS 四种工作模式 CPOL + CPHA 模式0 ~ 模式3 配置流程 时钟 → GPIO → 参数 → 使能 SSD1306 OLED 驱动实战 硬件连接 初始化序列 帧数据发送 核心:SPI模式必须与从设备匹配,CS和DC控制线是驱动成功的关键

4.6 常见问题与调试技巧

  • 屏幕不亮:先检查复位时序。SSD1306上电后需要至少100μs的低电平复位脉冲。我习惯在初始化前先拉低RES,延时1ms后再拉高。
  • 显示花屏:大概率是SPI模式不匹配。用逻辑分析仪抓一下SCLK的波形,看看空闲电平是低还是高,数据是在上升沿还是下降沿采样的。
  • 显示偏移:检查列地址和页地址的设置。SSD1306的列地址范围是0~127,页地址范围是0~7,多写或少写都会导致显示错位。

重要提醒:SPI的时钟频率不是越高越好。SSD1306最高支持10MHz,但实际布线长了、干扰大了,跑5MHz都可能出错。我一般从1MHz开始调试,稳定后再慢慢往上提。

好了,SPI显示接口驱动的内容就到这里。你只要把协议基础、模式配置和SSD1306的初始化流程吃透,后面再遇到其他SPI屏(比如ST7735、ILI9341),思路是完全一样的。