【0.96寸 OLED屏实现1500Fps的帧率】STM32 软件、硬件SPI、I2C驱动总结

11 篇文章 9 订阅

STM32F103VET6
STM32 Cube IDE


SPI版

OLED SPI 端口定义

本节引自STM32驱动0.96寸OLED液晶屏(12864液晶屏) —— 小牧同学
在这里插入图片描述
两种屏幕的引脚数不一样,左边的有7个引脚,而右边的只有6个。其次,端口的标号也不完全一样,第一个分别标为GND,VCC,D0,D1,RES,DC和CS第二个分别标为GND,VCC,SCL,SDA,RST,D/C。

七针OLED引脚定义

GND — 接地端口
VCC — 接3.3V电源端口
D0 — CLK时钟信号(等同于上面的SCL)
D1 — 数据端口(等同于上面的SDA)
RES — 复位端口(等同于上面的RST)
DC — 数据/命令选择引脚(等同于上面的D/C)
CS — 片选引脚(低电平有效,也就是所需要接低电平,我实际试验过不接该引脚也是可以正常使用的)

六针OLED引脚定义

GND — 接地端口
VCC — 接3.3V电源端口
SCL — CLK时钟信号端口
SDA — MOSI数据端口
RST — 复位端口
D/C — 数据/命令选择引脚

软件SPI

       指令解读见【51单片机快速入门指南】4.2: SSD1306 OLED屏(0.96寸、1.3寸)的I2C控制详解

       各引脚初始化如下,均为推挽输出:
在这里插入图片描述
       从【51单片机快速入门指南】5:软件SPI获取软件SPI程序。

修改控制电平的函数:

    GPIOx->BSRR = GPIO_Pin;

    GPIOx->BSRR = (uint32_t)GPIO_Pin << 16u;

由于我们需要的只是单向的、半双工的SPI,只需修改部分函数即可
在这里插入图片描述

       从SPI驱动0.96/1.3寸 OLED屏幕,易修改为DMA控制获取SPI版驱动程序。
将51特色的code改为const
在这里插入图片描述
修改对应引脚
在这里插入图片描述
修改延时函数
在这里插入图片描述
在这里插入图片描述
在主函数中添加测试程序:
在这里插入图片描述
如图,屏幕已轻松点亮。
在这里插入图片描述
帧率为180(见后文)
在这里插入图片描述
使用虚拟显存是时为46帧
在这里插入图片描述

硬件SPI

如图,使用硬件SPI
在这里插入图片描述
移除软件SPI的程序,修改OLED_WR_Byte函数
在这里插入图片描述

extern SPI_HandleTypeDef hspi1;
void OLED_WR_Byte(uint8_t dat, uint8_t cmd)
{
    OLED_CS_L();
    if (cmd)
        OLED_DC_H();
    else
        OLED_DC_L();
//    SOFT_SPI_RW_MODE2(dat);
    HAL_SPI_Transmit(&hspi1, &dat, 1, 10);
    OLED_DC_H();
    OLED_CS_H();
}

再次测试:
在这里插入图片描述
成功点亮
在这里插入图片描述
帧率为410
在这里插入图片描述

在oled.h中启用虚拟显存
在这里插入图片描述
修改OLED_Refresh_Gram函数,实现1024 Byte 显存连续写入
在这里插入图片描述

extern SPI_HandleTypeDef hspi1;
void OLED_Refresh_Gram(void)
{
#if OLED_BUFFER_MODE
//    uint16_t i;
    OLED_Set_Pos(0, 128);
//    for (i = 0; i < Max_Row / 8 * Max_Column; i++)
//    {
//        OLED_WR_Byte(OLED_GRAM[0][i], OLED_DATA);
//    }
    OLED_CS_L();
    OLED_DC_H();
    HAL_SPI_Transmit(&hspi1, OLED_GRAM[0], Max_Row / 8 * Max_Column, 10);
    OLED_DC_H();
    OLED_CS_H();
#endif
}

再次测试,仍能成功点亮,帧率为969。
在这里插入图片描述

启用DMA

在这里插入图片描述

再次修改OLED_Refresh_Gram函数

extern SPI_HandleTypeDef hspi1;
void OLED_Refresh_Gram(void)
{
#if OLED_BUFFER_MODE
//    uint16_t i;
    OLED_Set_Pos(0, 128);
//    for (i = 0; i < Max_Row / 8 * Max_Column; i++)
//    {
//        OLED_WR_Byte(OLED_GRAM[0][i], OLED_DATA);
//    }
    OLED_CS_L();
    OLED_DC_H();
//    HAL_SPI_Transmit(&hspi1, OLED_GRAM[0], Max_Row / 8 * Max_Column, 10);
    HAL_SPI_Transmit_DMA(&hspi1, OLED_GRAM[0], Max_Row / 8 * Max_Column);
    while(hspi1.State != HAL_SPI_STATE_READY);
    OLED_DC_H();
    OLED_CS_H();
#endif
}
帧率测试

帧率的显示:
在这里插入图片描述
在1ms中断中统计1s内的帧数
在这里插入图片描述
在每次更新屏幕内容时FPS_Count自加1
在这里插入图片描述
这个帧率已经远远超过屏幕本身所能提供的刷新率了。
在这里插入图片描述

I2C 版

软件I2C

将SCL设为推挽输出,SDA设为开漏上拉输出
在这里插入图片描述

【51单片机快速入门指南】4.2: SSD1306 OLED屏(0.96寸、1.3寸)的I2C控制详解【51单片机快速入门指南】4: 软件 I2C获取控制程序。

修改对应引脚,其他部分同SPI的步骤。
在这里插入图片描述

//SCL拉高 移植时需修改
void I2C_SCL_H(void)
{
	OLED_SCL_GPIO_Port->BSRR = OLED_SCL_Pin;
}

//SCL拉低 移植时需修改
void I2C_SCL_L(void)
{
	OLED_SCL_GPIO_Port->BSRR = (uint32_t)OLED_SCL_Pin << 16u;
}

//SDA拉高 移植时需修改
void I2C_SDA_H(void)
{
	OLED_SDA_GPIO_Port->BSRR = OLED_SDA_Pin;
}

//SDA拉低 移植时需修改
void I2C_SDA_L(void)
{
	OLED_SDA_GPIO_Port->BSRR = (uint32_t)OLED_SDA_Pin << 16u;
}

//读取SDA 移植时需修改
uint8_t I2C_SDA_Read(void)
{
	OLED_SDA_GPIO_Port->BSRR = (uint32_t)OLED_SDA_Pin << 16u;
	i2c_delay();
	return ((OLED_SDA_GPIO_Port->IDR & OLED_SDA_Pin) == 0?0:1);
}

测试:
在这里插入图片描述
帧率约为60帧
在这里插入图片描述

硬件I2C

在这里插入图片描述
修改OLED_WR_Byte函数

extern I2C_HandleTypeDef hi2c1;
void OLED_WR_Byte(uint8_t dat, uint8_t cmd)
{
	if (cmd)
		HAL_I2C_Mem_Write(&hi2c1, OLED_ADDRESS << 1, OLED_WriteData_Addr, I2C_MEMADD_SIZE_8BIT, &dat, 1, 10);
	else
		HAL_I2C_Mem_Write(&hi2c1, OLED_ADDRESS << 1, OLED_WriteCom_Addr, I2C_MEMADD_SIZE_8BIT, &dat, 1, 10);
}

此时帧率为49
在这里插入图片描述
在这里插入图片描述
开虚拟显存模式,修改OLED_Refresh_Gram函数

extern I2C_HandleTypeDef hi2c1;
void OLED_Refresh_Gram(void)
{
#if OLED_BUFFER_MODE
//    uint16_t i;
    OLED_Set_Pos(0, 128);
//    for (i = 0; i < Max_Row / 8 * Max_Column; i++)
//    {
//        OLED_WR_Byte(OLED_GRAM[0][i], OLED_DATA);
//    }
    HAL_I2C_Mem_Write(&hi2c1, OLED_ADDRESS << 1, OLED_WriteData_Addr, I2C_MEMADD_SIZE_8BIT, OLED_GRAM[0], Max_Row / 8 * Max_Column, 1000);
#endif
}

发现会更慢
在这里插入图片描述

DMA

在这里插入图片描述
开中断
在这里插入图片描述

再次修改OLED_Refresh_Gram函数

extern I2C_HandleTypeDef hi2c1;
void OLED_Refresh_Gram(void)
{
#if OLED_BUFFER_MODE
//    uint16_t i;
    OLED_Set_Pos(0, 128);
//    for (i = 0; i < Max_Row / 8 * Max_Column; i++)
//    {
//        OLED_WR_Byte(OLED_GRAM[0][i], OLED_DATA);
//    }
//    HAL_I2C_Mem_Write(&hi2c1, OLED_ADDRESS << 1, OLED_WriteData_Addr, I2C_MEMADD_SIZE_8BIT, OLED_GRAM[0], Max_Row / 8 * Max_Column, 1000);
    while(hi2c1.State != HAL_I2C_STATE_READY);
    HAL_I2C_Mem_Write_DMA(&hi2c1, OLED_ADDRESS << 1, OLED_WriteData_Addr, I2C_MEMADD_SIZE_8BIT, OLED_GRAM[0], Max_Row / 8 * Max_Column);
#endif
}

如图,瓶颈应为400kHz的I2C速度
在这里插入图片描述

  • 13
    点赞
  • 107
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
以下是一个使用 I2C 总线控制 0.96 OLED 屏幕进行局部清零的程序示例,基于 STM32 平台: ``` #include <Wire.h> #define OLED_ADDR 0x3C // OLED屏幕I2C地址 #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 void OLED_Init(); void OLED_Clear(); void OLED_PartialClear(int x, int y, int w, int h); void setup() { Wire.begin(); OLED_Init(); } void loop() { // 指定需要清空的区域坐标 int x = 0; int y = 0; int w = 64; int h = 32; // 清空指定区域的像素点 OLED_PartialClear(x, y, w, h); delay(1000); // 等待一段时间后再进行下一次局部清空 } void OLED_Init() { Wire.beginTransmission(OLED_ADDR); Wire.write(0x00); Wire.write(0xAE); Wire.write(0xD5); Wire.write(0x80); Wire.write(0xA8); Wire.write(0x3F); Wire.write(0xD3); Wire.write(0x00); Wire.write(0x40); Wire.write(0x8D); Wire.write(0x14); Wire.write(0x20); Wire.write(0x00); Wire.write(0xA1); Wire.write(0xC8); Wire.write(0xDA); Wire.write(0x12); Wire.write(0x81); Wire.write(0xCF); Wire.write(0xD9); Wire.write(0xF1); Wire.write(0xDB); Wire.write(0x40); Wire.write(0xA4); Wire.write(0xA6); Wire.write(0xAF); Wire.endTransmission(); } void OLED_Clear() { for (int i = 0; i < SCREEN_HEIGHT / 8; i++) { Wire.beginTransmission(OLED_ADDR); Wire.write(0x40); Wire.write(0xB0 + i); Wire.write(0x00); Wire.endTransmission(); } } void OLED_PartialClear(int x, int y, int w, int h) { int x_end = x + w; int y_end = y + h; for (int i = x; i < x_end; i++) { for (int j = y; j < y_end; j++) { int page = j / 8; int bit = j % 8; int data = ~(1 << bit); Wire.beginTransmission(OLED_ADDR); Wire.write(0x40); Wire.write(0xB0 + page); Wire.write(i & 0x0F); Wire.write(0x10 | (i >> 4)); Wire.write(data); Wire.endTransmission(); } } } ``` 在该程序中,首先使用 `Wire` 库初始化 I2C 总线,然后在 `OLED_Init()` 函数中初始化 OLED 显示的参数。在 `OLED_Clear()` 函数中实现了全局清的方法,而在 `OLED_PartialClear()` 函数中实现了局部清的方法。 在 `OLED_PartialClear()` 函数中,首先根据传入的坐标和大小计算出需要清空的区域,然后使用 `Wire.write()` 函数将每个像素点的数据写入 OLED 显示,从而实现局部清空的效果。 需要注意的是,在使用 `Wire.write()` 函数时,需要确保写入的数据格式正确,否则会导致程序出错或程序崩溃。同时,在进行局部清空时,也需要确保指定的区域不超出 OLED 显示的有效范围。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

乙酸氧铍

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值