0.96oled移植u8g2库,采用stm32f103硬件SPI或DMA通信,全网唯一,最详细教程

该教程介绍了如何使用STM32通过硬件SPI驱动SSD1306OLED显示屏,包括下载源码、解压、配置Keil工程、保留必要的函数、编写回调函数以及初始化和绘制图形的步骤。重点在于理解和配置SPI通信的回调函数及u8g2库的相关设置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

0. 视频教程

我的B站

1.下载源码

官网下载源码
在这里插入图片描述

也可以点击我的链接下载。

2.解压

解压出来其中的csrc文件夹,放到工程目录。
把自己的屏幕驱动保留下来,比如我用的128x64的ssd1306,就把里面的 u8x8_d_ssd1306_128x64_noname.c 保留,其他类似 u8x8_d_···.c 的都删了。

3. 打开keil工程

3.1 添加文件到工程

新建个组,把精简后的csrc的文件都添加都组里来,把csrc路径添加到项目包含目录里来,先编译,有问题没关系。

3.2 打开 u8g2_d_setup.c 文件,只保留下面6个函数

/* ssd1306*/
/* ssd1306 1 */
void u8g2_Setup_ssd1306_128x64_noname_1(u8g2_t *u8g2, const u8g2_cb_t *rotation, u8x8_msg_cb byte_cb, u8x8_msg_cb gpio_and_delay_cb)
{
  uint8_t tile_buf_height;
  uint8_t *buf;
  u8g2_SetupDisplay(u8g2, u8x8_d_ssd1306_128x64_noname, u8x8_cad_001, byte_cb, gpio_and_delay_cb);
  buf = u8g2_m_16_8_1(&tile_buf_height);
  u8g2_SetupBuffer(u8g2, buf, tile_buf_height, u8g2_ll_hvline_vertical_top_lsb, rotation);
}
/* ssd1306 2 */
void u8g2_Setup_ssd1306_128x64_noname_2(u8g2_t *u8g2, const u8g2_cb_t *rotation, u8x8_msg_cb byte_cb, u8x8_msg_cb gpio_and_delay_cb)
{
  uint8_t tile_buf_height;
  uint8_t *buf;
  u8g2_SetupDisplay(u8g2, u8x8_d_ssd1306_128x64_noname, u8x8_cad_001, byte_cb, gpio_and_delay_cb);
  buf = u8g2_m_16_8_2(&tile_buf_height);
  u8g2_SetupBuffer(u8g2, buf, tile_buf_height, u8g2_ll_hvline_vertical_top_lsb, rotation);
}
/* ssd1306 f */
void u8g2_Setup_ssd1306_128x64_noname_f(u8g2_t *u8g2, const u8g2_cb_t *rotation, u8x8_msg_cb byte_cb, u8x8_msg_cb gpio_and_delay_cb)
{
  uint8_t tile_buf_height;
  uint8_t *buf;
  u8g2_SetupDisplay(u8g2, u8x8_d_ssd1306_128x64_noname, u8x8_cad_001, byte_cb, gpio_and_delay_cb);
  buf = u8g2_m_16_8_f(&tile_buf_height);
  u8g2_SetupBuffer(u8g2, buf, tile_buf_height, u8g2_ll_hvline_vertical_top_lsb, rotation);
}

/* ssd1306 IIC*/
/* ssd1306 1 */
//void u8g2_Setup_ssd1306_i2c_128x64_noname_1(u8g2_t *u8g2, const u8g2_cb_t *rotation, u8x8_msg_cb byte_cb, u8x8_msg_cb gpio_and_delay_cb)
//{
//  uint8_t tile_buf_height;
//  uint8_t *buf;
//  u8g2_SetupDisplay(u8g2, u8x8_d_ssd1306_128x64_noname, u8x8_cad_ssd13xx_fast_i2c, byte_cb, gpio_and_delay_cb);
//  buf = u8g2_m_16_8_1(&tile_buf_height);
//  u8g2_SetupBuffer(u8g2, buf, tile_buf_height, u8g2_ll_hvline_vertical_top_lsb, rotation);
//}

///* ssd1306 2 */
//void u8g2_Setup_ssd1306_i2c_128x64_noname_2(u8g2_t *u8g2, const u8g2_cb_t *rotation, u8x8_msg_cb byte_cb, u8x8_msg_cb gpio_and_delay_cb)
//{
//  uint8_t tile_buf_height;
//  uint8_t *buf;
//  u8g2_SetupDisplay(u8g2, u8x8_d_ssd1306_128x64_noname, u8x8_cad_ssd13xx_fast_i2c, byte_cb, gpio_and_delay_cb);
//  buf = u8g2_m_16_8_2(&tile_buf_height);
//  u8g2_SetupBuffer(u8g2, buf, tile_buf_height, u8g2_ll_hvline_vertical_top_lsb, rotation);
//}

///* ssd1306 f */
//void u8g2_Setup_ssd1306_i2c_128x64_noname_f(u8g2_t *u8g2, const u8g2_cb_t *rotation, u8x8_msg_cb byte_cb, u8x8_msg_cb gpio_and_delay_cb)
//{
//  uint8_t tile_buf_height;
//  uint8_t *buf;
//  u8g2_SetupDisplay(u8g2, u8x8_d_ssd1306_128x64_noname, u8x8_cad_ssd13xx_fast_i2c, byte_cb, gpio_and_delay_cb);
//  buf = u8g2_m_16_8_f(&tile_buf_height);
//  u8g2_SetupBuffer(u8g2, buf, tile_buf_height, u8g2_ll_hvline_vertical_top_lsb, rotation);
//}

其中,前3个是SPI的,后三个是IIC的,因为我用的是SPI,所以后面三个注释了。
这6个函数名的最后一位(1,2,f)表示不同的显示buf,一般用带 f 的,具体意义如下:

  • 1:128字节
  • 2:256字节
  • f:1024字节

3.3 打开 u8g2_d_memory.c 文件,只保留下面3个函数

uint8_t *u8g2_m_16_8_1(uint8_t *page_cnt)
{
  #ifdef U8G2_USE_DYNAMIC_ALLOC
  *page_cnt = 1;
  return 0;
  #else
  static uint8_t buf[128];
  *page_cnt = 1;
  return buf;
  #endif
}
uint8_t *u8g2_m_16_8_2(uint8_t *page_cnt)
{
  #ifdef U8G2_USE_DYNAMIC_ALLOC
  *page_cnt = 2;
  return 0;
  #else
  static uint8_t buf[256];
  *page_cnt = 2;
  return buf;
  #endif
}
uint8_t *u8g2_m_16_8_f(uint8_t *page_cnt)
{
  #ifdef U8G2_USE_DYNAMIC_ALLOC
  *page_cnt = 8;
  return 0;
  #else
  static uint8_t buf[1024];
  *page_cnt = 8;
  return buf;
  #endif
}

这3函数会被上一步的6个函数调用。

3.4 编写回调函数

打开 main.c 文件,添加下面四个函数

uint8_t u8x8_byte_4wire_hw_spi(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
    switch (msg)
    {
    case U8X8_MSG_BYTE_SEND: /*通过SPI发送arg_int个字节数据*/
        //HAL_SPI_Transmit_DMA(&hspi1, (uint8_t *)arg_ptr, arg_int);while(hspi1.TxXferCount);//DMA
        HAL_SPI_Transmit(&hspi1,(uint8_t *)arg_ptr,arg_int,1000);//SPI
        break;
    case U8X8_MSG_BYTE_INIT: /*初始化函数*/
        break;
    case U8X8_MSG_BYTE_SET_DC: /*设置DC引脚,表明发送的是数据还是命令*/
        HAL_GPIO_WritePin(SPI1_DC_GPIO_Port, SPI1_DC_Pin, arg_int);
        break;
    case U8X8_MSG_BYTE_START_TRANSFER:
        u8x8_gpio_SetCS(u8x8, u8x8->display_info->chip_enable_level);
        u8x8->gpio_and_delay_cb(u8x8, U8X8_MSG_DELAY_NANO, u8x8->display_info->post_chip_enable_wait_ns, NULL);
        break;
    case U8X8_MSG_BYTE_END_TRANSFER:
        u8x8->gpio_and_delay_cb(u8x8, U8X8_MSG_DELAY_NANO, u8x8->display_info->pre_chip_disable_wait_ns, NULL);
        u8x8_gpio_SetCS(u8x8, u8x8->display_info->chip_disable_level);
        break;
    default:
        return 0;
    }
    return 1;
}


uint8_t u8x8_stm32_gpio_and_delay(U8X8_UNUSED u8x8_t *u8x8, U8X8_UNUSED uint8_t msg, U8X8_UNUSED uint8_t arg_int, U8X8_UNUSED void *arg_ptr)
{
    switch (msg)
    {
    case U8X8_MSG_GPIO_AND_DELAY_INIT: /*delay和GPIO的初始化,在main中已经初始化完成了*/
        break;
    case U8X8_MSG_DELAY_MILLI: /*延时函数*/
        delay_ms(arg_int);
        break;
    case U8X8_MSG_GPIO_CS: /*片选信号*/
        HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, arg_int);
        break;
    case U8X8_MSG_GPIO_DC:
        break;
    case U8X8_MSG_GPIO_RESET:
        break;
    }
    return 1;
}

/*官方提供的Logo绘制demo*/
void draw(u8g2_t *u8g2)
{
    u8g2_SetFontMode(u8g2, 1); /*字体模式选择*/
    u8g2_SetFontDirection(u8g2, 0); /*字体方向选择*/
    u8g2_SetFont(u8g2, u8g2_font_inb24_mf); /*字库选择*/
    u8g2_DrawStr(u8g2, 0, 20, "U");

    u8g2_SetFontDirection(u8g2, 1);
    u8g2_SetFont(u8g2, u8g2_font_inb30_mn);
    u8g2_DrawStr(u8g2, 21, 8, "8");

    u8g2_SetFontDirection(u8g2, 0);
    u8g2_SetFont(u8g2, u8g2_font_inb24_mf);
    u8g2_DrawStr(u8g2, 51, 30, "g");
    u8g2_DrawStr(u8g2, 67, 30, "\xb2");

    u8g2_DrawHLine(u8g2, 2, 35, 47);
    u8g2_DrawHLine(u8g2, 3, 36, 47);
    u8g2_DrawVLine(u8g2, 45, 32, 12);
    u8g2_DrawVLine(u8g2, 46, 33, 12);

    u8g2_SetFont(u8g2, u8g2_font_4x6_tr);
    u8g2_DrawStr(u8g2, 1, 54, "github.com/olikraus/u8g2");
}

void u8g2Init(u8g2_t *u8g2)
{
    /*
    U8G2_R0: 顺时针旋转0度
    U8G2_R1: 顺时针旋转90度
    U8G2_R2: 顺时针旋转180度
    U8G2_R3: 顺时针旋转270度
    U8G2_MIRROR:U8G2_R0的镜像
    */
    u8g2_Setup_ssd1306_128x64_noname_f(u8g2, U8G2_R0, u8x8_byte_4wire_hw_spi, u8x8_stm32_gpio_and_delay);
    u8g2_InitDisplay(u8g2); // 根据所选的芯片进行初始化工作,初始化完成后,显示器处于关闭状态
    u8g2_SetPowerSave(u8g2, 0); // 打开显示器
    u8g2_ClearBuffer(u8g2);

    HAL_Delay(1000);

    u8g2_DrawLine(u8g2, 0, 0, 127, 63);
    u8g2_DrawLine(u8g2, 127, 0, 0, 63);
    u8g2_SendBuffer(u8g2);
    HAL_Delay(1000);
}

第一个函数 u8x8_byte_4wire_hw_spi 是通信的,用的硬件SPI或者SPI的DMA,具体看注释。
第二个函数 u8x8_stm32_gpio_and_delay 是回调函数,控制片选、延时、初始化引脚的,具体看注释。
第三个函数 draw 是官方提供的Logo绘制demo。
第四个函数 u8g2Init 是初始化U8g2的,这个函数里面的 u8g2_Setup_ssd1306_128x64_noname_f 函数是之前保留的,这个函数会调用前面写的两个回调函数(u8x8_byte_4wire_hw_spi和u8x8_stm32_gpio_and_delay),所以这两个回调函数是 重中之重

注意 : 用硬件通信,无论是SPI、IIC或者其他协议,都需要自己写类似于u8x8_byte_4wire_hw_spi 的回调函数;用软件通信,都不用写,因为这个U8g2库里有,具体在 u8x8_byte.c 这个文件里,里面有 4线软件SPI、3线软件SPI、软件IIC、6800、8080 等通信的回调函数。本篇介绍的是用硬件SPI的,主要是用硬件SPI的网上没有详细教程,所以贡献一下。如果用硬件IIC或者其他通信可以看这里

3.5 主函数初始化U8g2,绘制logo

最后,在主函数里,先初始化oled,再调用 u8g2Init ,然后绘制logo,代码如下:

	u8g2_t u8g2;
	
    u8g2Init(&u8g2);
    u8g2_FirstPage(&u8g2);
    do
    {
        draw(&u8g2);
    }
    while (u8g2_NextPage(&u8g2));
    

4. 现象

在这里插入图片描述

5.附录

工程源码

<think>嗯,用户想找STM32F103C8T6和OLED显示模块的使用教程、示例代码和连接方法。首先,我需要回忆一下STM32F103C8T6的基本信息,它是一款基于ARM Cortex-M3内核的微控制器,属于STM32的入门级系列,通常叫做“Blue Pill”开发板。而OLED显示模块常用的应该是SSD1306驱动的0.961.3寸屏幕,I2CSPI接口。 接下来,用户可能需要硬件连接方法。I2C接口比较简单,只需要接四根线:VCC、GND、SCL、SDA。如果是SPI的话,可能需要更多的引脚,但I2C更省IO口,所以可能更常见。然后,软件部分需要配置相应的驱动,比如使用HAL者标准外设者使用现成的第三方u8g2、SSD1306的驱动。 用户可能遇到的问题包括引脚配置错误、通信协议设置不正确、显示内容乱码等。需要提醒检查地址是否正确,I2C地址通常是0x3C0x3D。另外,初始化代码是否正确,是否有正确的延时函数,是否需要上拉电阻等。 示例代码方面,应该提供一个简单的初始化步骤和显示字符串图形的例子。可能还需要提到如何获取字体图形,以及如何优化显示速度。另外,是否需要使用DMA传输者中断来处理显示更新,这可能会影响性能。 还有,用户可能想知道如何移植现有的Arduino代码到STM32者如何在不同开发环境(如Keil、STM32CubeIDE、PlatformIO)中配置项目。需要推荐一些可靠的资源,比如官方文档、GitHub上的开源项目,者社区教程后,常见问题解答部分应该包括硬件连接检查、地址确认、电压匹配(OLED通常是3.3V5V,STM32的IO是3.3V),以及软件配置中的时钟速度和引脚复用功能是否正确设置。可能还需要提到使用逻辑分析仪示波器来检查I2C信号是否正常。 需要确保回答的结构清晰,分步骤说明,让用户能够一步步操作。同时,提供的代码示例要正确,并指出可能出错的点。引用相关的资料教程链接,帮助用户进一步查找资源。</think>### STM32F103C8T6与OLED显示模块使用教程 #### 1. 硬件连接方法 OLED模块通常采用I2CSPI通信协议,以下是I2C接口连接方式: | OLED引脚 | STM32F103C8T6引脚 | |----------|--------------------| | VCC | 3.3V | | GND | GND | | SCL | PB6 | | SDA | PB7 | 对于SPI接口,需额外连接`RES`、`DC`、`CS`等控制线[^1]。 #### 2. 软件配置步骤 1. **STM32CubeMX配置** - 启用I2C1外设 - 配置PB6为I2C1_SCL,PB7为I2C1_SDA - 设置I2C时钟频率为400kHz(快速模式) 2. **OLED驱动选择** 推荐使用`SSD1306`官方驱动第三方如`u8g2`,以下为初始化代码片段: ```c void OLED_Init(void) { HAL_Delay(100); OLED_Write_Cmd(0xAE); // 关闭显示 OLED_Write_Cmd(0xD5); // 设置时钟分频 OLED_Write_Cmd(0x80); OLED_Write_Cmd(0xA8); // 设置复用率 OLED_Write_Cmd(0x3F); // ...其他初始化命令 } ``` #### 3. 示例代码(显示字符串) ```c void OLED_ShowString(uint8_t x, uint8_t y, char *str) { while(*str) { OLED_ShowChar(x, y, *str); x += 8; str++; } } ``` #### 4. 调试技巧 - 使用逻辑分析仪验证I2C时序 - 检查OLED设备地址(通常0x780x7A) - 确认电源电压匹配(3.3V/5V兼容)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值