SPI 接口与 OLED 显示屏
1. SPI 概述
- SPI(Serial Peripheral Interface)串行外设接口是一种高速、全双工、同步串行通信总线,由摩托罗拉公司提出。
- SPI 接口具有简单易用、传输速率高、占用引脚资源少等特点,广泛应用于高速数据传输,如显示屏、存储器、传感器等。
SPI 通信特点
- 全双工通信:SPI 支持同时发送和接收数据。
- 主从模式:通信中有一个主设备(Master)和一个或多个从设备(Slave)。
- 同步通信:通信时钟由主设备提供,从设备根据时钟同步接收数据。
- 连接方式:通常需要 4 条线:
- MOSI(Master Out Slave In):主设备输出,从设备输入。
- MISO(Master In Slave Out):主设备输入,从设备输出。
- SCK(Serial Clock):串行时钟信号,由主设备提供。
- SS/CS(Slave Select/Chip Select):从设备选择信号,低电平有效。
2. SPI 配置步骤
要在 STM32F103 微控制器中使用 SPI 接口,需要按以下步骤进行配置:
-
使能 SPI 和 GPIO 时钟
在操作外设之前,必须使能对应的外设时钟。
// 使能 SPI1 和 GPIOA 的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1 | RCC_APB2Periph_GPIOA, ENABLE);
-
配置 GPIO 引脚为复用功能
根据 SPI 通信所需的引脚功能,配置相应的 GPIO 引脚为复用推挽输出或输入模式。
GPIO_InitTypeDef GPIO_InitStructure; // 配置 SPI1 SCK(PA5)和 MOSI(PA7)为复用推挽输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出模式 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); // 配置 SPI1 MISO(PA6)为输入模式(若未使用可不配置) GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入模式 GPIO_Init(GPIOA, &GPIO_InitStructure);
-
初始化 SPI 外设
设置 SPI 的通信参数,如数据传输方向、主从模式、数据位数、时钟极性和相位、波特率等。
SPI_InitTypeDef SPI_InitStructure; SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; // 全双工模式 SPI_InitStructure.SPI_Mode = SPI_Mode_Master; // 主机模式 SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // 8 位数据帧格式 SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; // 时钟空闲时为低电平 SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; // 第一个时钟沿采样数据 SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; // 软件控制 NSS 信号 SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8; // 波特率预分频,SPI 时钟频率为 PCLK/8 SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; // 高位在前 SPI_InitStructure.SPI_CRCPolynomial = 7; // CRC 多项式(如不使用可忽略) SPI_Init(SPI1, &SPI_InitStructure);
-
使能 SPI 外设
在完成配置后,启用 SPI 外设。
SPI_Cmd(SPI1, ENABLE);
3. 示例:使用 SPI 控制 OLED 显示屏
以常见的 128x64 OLED 显示屏为例,演示如何通过 SPI 接口进行控制。OLED 显示屏通常使用 SSD1306 或类似的显示驱动芯片。
3.1 初始化 SPI1
void SPI1_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
// 1. 使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1 | RCC_APB2Periph_GPIOA, ENABLE);
// 2. 配置 GPIO 引脚
// SPI1 SCK (PA5), MOSI (PA7)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// SPI1 MISO (PA6) 未用到,可配置为输入或用于其他功能
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 3. 初始化 SPI
SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx; // 单线发送
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; // 主机模式
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // 8 位数据长度
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; // 时钟空闲时为低电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; // 第一个时钟沿采样数据
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; // 软件控制 NSS
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2; // 波特率预分频为2,SPI 时钟频率高
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; // 高位在前
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(SPI1, &SPI_InitStructure);
// 4. 使能 SPI
SPI_Cmd(SPI1, ENABLE);
}
3.2 SPI 发送数据函数
// 发送一个字节数据
void SPI1_SendByte(uint8_t byte) {
// 等待发送缓冲区空
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
// 发送数据
SPI_I2S_SendData(SPI1, byte);
// 等待传输完成
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) == SET);
}
3.3 OLED 显示屏控制
OLED 显示屏通常需要通过命令和数据来控制,其接口通常包括:
- DC(Data/Command)引脚:用于区分发送的是命令还是数据,通常高电平为数据,低电平为命令。
- RES(Reset)引脚:用于复位显示屏。
- CS(Chip Select)引脚:片选信号,低电平有效。
GPIO 引脚配置
// 配置 DC(PB0)、RES(PB1)、CS(PB2)引脚
void OLED_GPIO_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// 初始化引脚状态
GPIO_SetBits(GPIOB, GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2);
}
OLED 发送命令和数据
// 选择 OLED(拉低 CS 引脚)
#define OLED_CS_LOW() GPIO_ResetBits(GPIOB, GPIO_Pin_2)
// 取消选择 OLED(拉高 CS 引脚)
#define OLED_CS_HIGH() GPIO_SetBits(GPIOB, GPIO_Pin_2)
// 设置为命令模式(拉低 DC 引脚)
#define OLED_DC_LOW() GPIO_ResetBits(GPIOB, GPIO_Pin_0)
// 设置为数据模式(拉高 DC 引脚)
#define OLED_DC_HIGH() GPIO_SetBits(GPIOB, GPIO_Pin_0)
// 复位 OLED(拉低 RES 引脚)
#define OLED_RES_LOW() GPIO_ResetBits(GPIOB, GPIO_Pin_1)
// 取消复位(拉高 RES 引脚)
#define OLED_RES_HIGH() GPIO_SetBits(GPIOB, GPIO_Pin_1)
// 发送命令
void OLED_WriteCommand(uint8_t cmd) {
OLED_DC_LOW(); // 命令模式
OLED_CS_LOW(); // 选择 OLED
SPI1_SendByte(cmd);
OLED_CS_HIGH(); // 取消选择
}
// 发送数据
void OLED_WriteData(uint8_t data) {
OLED_DC_HIGH(); // 数据模式
OLED_CS_LOW(); // 选择 OLED
SPI1_SendByte(data);
OLED_CS_HIGH(); // 取消选择
}
3.4 OLED 初始化代码
根据具体的 OLED 显示屏驱动芯片,初始化代码会有所不同。以下以常见的 SSD1306 为例:
void OLED_Init(void) {
OLED_GPIO_Init();
SPI1_Init();
// 复位 OLED
OLED_RES_LOW();
Delay_ms(100); // 自定义延时函数
OLED_RES_HIGH();
// 初始化命令序列(根据具体的驱动芯片手册编写)
OLED_WriteCommand(0xAE); // 关闭显示
OLED_WriteCommand(0x00); // 设置低列地址
OLED_WriteCommand(0x10); // 设置高列地址
OLED_WriteCommand(0x40); // 设置起始行地址
// ...(其他初始化命令)
OLED_WriteCommand(0xAF); // 打开显示
}
3.5 显示示例
在完成初始化后,可以编写函数来控制 OLED 显示,如清屏、显示字符、绘制图形等。
// 清屏函数
void OLED_Clear(void) {
for (uint8_t page = 0; page < 8; page++) {
OLED_WriteCommand(0xB0 + page); // 设置页地址
OLED_WriteCommand(0x00); // 设置列低地址
OLED_WriteCommand(0x10); // 设置列高地址
for (uint8_t col = 0; col < 128; col++) {
OLED_WriteData(0x00); // 清空数据
}
}
}
// 在指定位置显示一个字符(需要字库支持)
void OLED_ShowChar(uint8_t x, uint8_t y, char chr) {
// 根据字库获取字符的点阵数据,然后调用 OLED_WriteData 写入显示
}
// 显示字符串
void OLED_ShowString(uint8_t x, uint8_t y, char *str) {
while (*str) {
OLED_ShowChar(x, y, *str);
x += 8; // 字符宽度,根据字体大小调整
if (x > 120) { // 换行处理
x = 0;
y += 2;
}
str++;
}
}
3.6 主函数示例
int main(void) {
OLED_Init(); // 初始化 OLED
OLED_Clear(); // 清屏
OLED_ShowString(0, 0, "Hello, OLED!");
while (1) {
// 主循环,可以添加其他功能
}
}
4. 注意事项
- SPI 时钟参数:根据从设备的通信要求,合理设置 SPI 的时钟极性(CPOL)和相位(CPHA),以确保数据正确传输。
- 引脚连接:确保 GPIO 引脚与 OLED 显示屏连接正确,尤其是 CS、DC、RES 等控制引脚。
- 软件 NSS 管脚:若使用软件控制 NSS,引脚需要手动拉低和拉高,以实现从设备的选择。
- 数据延时:某些从设备在数据传输之间需要一定的延时,可根据需要添加延时函数。
Comments NOTHING