目录前言原理图cubeMX配置移植步骤Setup 入口函数差异实现 gpio_and_delay_cb实现 byte_cb思路主函数效果工程链接前言参考上篇STM32F407 移植 U8g2 驱动 SSD1306基于 HAL 硬件 I2C 的完整实战I2C接口的移植过程本篇文章介绍SPI接口的0.96寸OLED移植U8g2库的步骤。 MCU为STM32F411CEU6OLED的驱动芯片为SSD1306gcc工具链使用stlink下载调试。原理图使用OLED裸屏外围电路如下所示其中D0连接SPI的时钟线SCKD1连接数据线MOSI。cubeMX配置开启调试口外部晶振SPI总线等。系统时钟配置为 100MHz。工程使用 CMake 工具链。移植步骤Setup 入口函数差异与i2c接口初始化时用的u8g2_Setup_ssd1306_i2c_128x64_noname_f函数不同SPI接口使用。/* 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); }需要我们实现的是byte_cb和gpio_and_delay_cb这两个回调函数。 SPI通信不需要先发送OLED的总线地址实现比较简洁清晰。实现gpio_and_delay_cbuint8_t mydelaycb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) { switch (msg) { case U8X8_MSG_GPIO_RESET: { HAL_GPIO_WritePin(RES_GPIO_Port, RES_Pin, arg_int); break; } case U8X8_MSG_DELAY_MILLI: { HAL_Delay(arg_int); break; } default: break; } return 1; }实现byte_cbuint8_t u8x8_byte_my_spi(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) { switch(msg) { case U8X8_MSG_BYTE_SEND: { HAL_SPI_Transmit(hspi1, (uint8_t *)arg_ptr, arg_int, 0xFFFF); break; } case U8X8_MSG_BYTE_INIT: { break; } case U8X8_MSG_BYTE_SET_DC: { HAL_GPIO_WritePin(DC_GPIO_Port, DC_Pin, arg_int); break; } case U8X8_MSG_BYTE_START_TRANSFER: { HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET); break; } case U8X8_MSG_BYTE_END_TRANSFER: { HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET); break; } default: return 0; } return 1; }思路在u8g2_Setup_ssd1306_128x64_noname_f函数中最关键的是u8g2_SetupDisplay(u8g2, u8x8_d_ssd1306_128x64_noname, u8x8_cad_001, byte_cb, gpio_and_delay_cb);display_cb函数为u8x8_d_ssd1306_128x64_noname与I2C接口调用的一致。cad_cb函数为u8x8_cad_001/* convert to bytes by using dc 0 for commands and args and dc 1 for data */ uint8_t u8x8_cad_001(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) { switch(msg) { case U8X8_MSG_CAD_SEND_CMD: u8x8_byte_SetDC(u8x8, 0); u8x8_byte_SendByte(u8x8, arg_int); break; case U8X8_MSG_CAD_SEND_ARG: u8x8_byte_SetDC(u8x8, 0); u8x8_byte_SendByte(u8x8, arg_int); break; case U8X8_MSG_CAD_SEND_DATA: u8x8_byte_SetDC(u8x8, 1); //u8x8_byte_SendBytes(u8x8, arg_int, arg_ptr); //break; /* fall through */ case U8X8_MSG_CAD_INIT: case U8X8_MSG_CAD_START_TRANSFER: case U8X8_MSG_CAD_END_TRANSFER: return u8x8-byte_cb(u8x8, msg, arg_int, arg_ptr); default: return 0; } return 1; }追踪u8g2_SendBuffer函数最终调用的是uint8_t u8x8_DrawTile(u8x8_t *u8x8, uint8_t x, uint8_t y, uint8_t cnt, uint8_t *tile_ptr) { u8x8_tile_t tile; tile.x_pos x; tile.y_pos y; tile.cnt cnt; tile.tile_ptr tile_ptr; return u8x8-display_cb(u8x8, U8X8_MSG_DISPLAY_DRAW_TILE, 1, (void *)tile); }把U8X8_MSG_DISPLAY_DRAW_TILE信息传入display回调函数继续传入cad_cb函数最后是需要我们实现的byte_cb函数作为最底层。只需要根据不同的信号完成不同的事情当传入U8X8_MSG_BYTE_SET_DC时就根据参数修改DC引脚的高低电平——HAL_GPIO_WritePin(DC_GPIO_Port, DC_Pin, arg_int);在发送过程中U8g2库并没有指定CS电平所以需要自行在发送数据前拉低CS发送完毕拉高CS笔者实测一直保持CS拉低也可以显示在初始化时会控制RES引脚复位OLED因此gpio_and_delay_cb回调函数不能像I2C接口那样简化需要控制RES引脚。void u8x8_d_helper_display_init(u8x8_t *u8x8) { /* 2) apply port directions to the GPIO lines and apply default values for the IO lines*/ u8x8_gpio_Init(u8x8); /* macro, which calls gpio_and_delay_cb with U8X8_MSG_GPIO_AND_DELAY_INIT */ u8x8_cad_Init(u8x8); /* this will also call U8X8_MSG_BYTE_INIT, byte init will NOT call GPIO_INIT */ /* 3) do reset */ u8x8_gpio_SetReset(u8x8, 1); u8x8_gpio_Delay(u8x8, U8X8_MSG_DELAY_MILLI, u8x8-display_info-reset_pulse_width_ms); u8x8_gpio_SetReset(u8x8, 0); u8x8_gpio_Delay(u8x8, U8X8_MSG_DELAY_MILLI, u8x8-display_info-reset_pulse_width_ms); u8x8_gpio_SetReset(u8x8, 1); u8x8_gpio_Delay(u8x8, U8X8_MSG_DELAY_MILLI, u8x8-display_info-post_reset_wait_ms); }当发送U8X8_MSG_GPIO_RESET信号时相应地设置RES高低电平主函数/** * brief The application entry point. * retval int */ int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_SPI1_Init(); /* USER CODE BEGIN 2 */ u8g2_Setup_ssd1306_128x64_noname_f(u8g2, U8G2_R0, u8x8_byte_my_spi, mydelaycb); u8g2_InitDisplay(u8g2); // transfer init sequence to the display u8g2_SetPowerSave(u8g2, 0); u8g2_ClearBuffer(u8g2); u8g2_SendBuffer(u8g2); u8g2_SetFont(u8g2, u8g2_font_10x20_tf); u8g2_DrawStr(u8g2, 8, 24, u8g2); /* Use the built-in streamline logo icon font, glyph range starts at 0. */ u8g2_SetFont(u8g2, u8g2_font_streamline_logo_t); u8g2_DrawGlyph(u8g2, 52, 62, 0); u8g2_SendBuffer(u8g2); HAL_Delay(1000); u8g2_ClearBuffer(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); u8g2_SendBuffer(u8g2); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }效果工程链接STM32F411SSD1306U8g2SPI移植全流程实战含回调函数详解资源-CSDN下载