ESP32移植Openharmony外设篇(3)OLED屏

模块简介

产品介绍

OLED (Organic Light-Emitting Diode):有机发光二极管又称为有机电激光显示,OLED显示技术具有自发光的特性,采用薄的有机材料涂层和玻璃基板,当有电流通过时,这些有机材料就会发光,而且OLED显示屏幕可视角度大,功耗低。OLED由于同时具备自发光、不需背光源(只上电是不会亮的,驱动程序和接线正确才会点亮)、对比度高、厚度薄、视角广、反应速度快、可用于挠曲面板、使用温度范围广、结构及制程简单等优异之特性,被认为是下一代的平面显示器新兴应用技术。

电气参数

|------|---------------|
| 模块尺寸 | 23.7 *23.8mm |
| 电源电压 | 3.3-5.5V |
| 驱动芯片 | SSD1306 |

OLED屏幕原理

OLED本身是没有显存的,他的现存是依赖SSD1306提供的,而SSD1306提供一块显存。

SSD1306显存总共为128*64bit大小,SSD1306将这些显存分成了8页。每页包含了128个字节。

单片机内部建立一个缓存(共128*8个字节),每次修改的时候,只是修改单片机上的缓存(实际上就是SRAM),修改完后一次性把STM32上的缓存数据写入到OLED的GRAM。这个方法也有坏处,对于SRAM很小的单片机(51系列)就比较麻烦。

原理图

取字模

因为OLED屏显示的逻辑是根据一个十六进制数 一个点一个点的点亮对应值为1的像素点,网上有很多取字模的网站,这里推荐一个自己使用的取字模的网址:

汉字字模在线: https://www.23bei.com/tool-223.html

参考代码

BUILD.gn
# Copyright (c) 2022 Hunan OpenValley Digital Industry Development Co., Ltd.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import("//kernel/liteos_m/liteos.gni")
module_name = get_path_info(rebase_path("."), "name")
kernel_module(module_name){
    include_dirs = [
        "//drivers/hdf_core/framework/include/platform/",
        "//drivers/hdf_core/framework/include/utils/",
        "//drivers/hdf_core/framework/support/platform/include/gpio",
        "//drivers/hdf_core/adapter/khdf/liteos_m/osal/include/",
        "//drivers/hdf_core/framework/include/core/",
        "//drivers/hdf_core/framework/include/osal/",
        "//device/soc/esp/esp32/components/driver/include",
        "//device/soc/esp/esp32/components/esp_adc_cal/include",
        "//device/soc/esp/esp32/components/driver/esp32/include",
    ]

    sources = [
        "ssd1306.c",
        "ssd1306_fonts.c",
        "ssd1306_demo.c",
        "ssd1306_tests.c",
    ]
}
ssd1306.c
#include "ssd1306.h"
#include <math.h>
#include <stdlib.h>
#include <string.h>  // For memcpy

#include <stdio.h>
#include <unistd.h>
#include "cmsis_os2.h"
#include <stdio.h>
#include "cmsis_os2.h"
#include "ohos_run.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "esp_log.h"
#include "driver/gpio.h"
#include "driver/i2c.h"

#if defined(SSD1306_USE_I2C)

#define SSD1306_I2C_IDX     0

#define SSD1306_CTRL_CMD 0x00
#define SSD1306_CTRL_DATA 0x40
#define SSD1306_MASK_CONT (0x1<<7)

void ssd1306_Reset(void) {
    /* for I2C - do nothing */
}

void HAL_Delay(uint32_t ms)
{
    usleep(ms * 1000);
}


static uint32_t ssd1306_SendData(uint8_t* data, size_t size)
{
    int i;
    i2c_cmd_handle_t cmd_handle = i2c_cmd_link_create();
 
    i2c_master_start(cmd_handle);
    i2c_master_write_byte(cmd_handle, SSD1306_I2C_ADDR, true);

    for(i = 0; i < size; i ++)
    {
        i2c_master_write_byte(cmd_handle, data[i], true);
    }

    i2c_master_stop(cmd_handle);
    esp_err_t error = i2c_master_cmd_begin(I2C_NUM_0,cmd_handle,100/portTICK_PERIOD_MS);
 
    i2c_cmd_link_delete(cmd_handle);

    return error;
}

static uint32_t ssd1306_WiteByte(uint8_t regAddr, uint8_t byte)
{
    uint8_t buffer[] = {regAddr, byte};
    return ssd1306_SendData(buffer, sizeof(buffer));
}

// Send a byte to the command register
void ssd1306_WriteCommand(uint8_t byte) {
    ssd1306_WiteByte(SSD1306_CTRL_CMD, byte);
}

// Send data
void ssd1306_WriteData(uint8_t* buffer, size_t buff_size) {
    uint8_t data[SSD1306_WIDTH * 2] = {0};
    for (size_t i = 0; i < buff_size; i++) {
        data[i*2] = SSD1306_CTRL_DATA | SSD1306_MASK_CONT;
        data[i*2+1] = buffer[i];
    }
    data[(buff_size - 1) * 2] = SSD1306_CTRL_DATA;
    ssd1306_SendData(data, sizeof(data));
}

#elif defined(SSD1306_USE_SPI)

void ssd1306_Reset(void) {
    // CS = High (not selected)
    HAL_GPIO_WritePin(SSD1306_CS_Port, SSD1306_CS_Pin, GPIO_PIN_SET);

    // Reset the OLED
    HAL_GPIO_WritePin(SSD1306_Reset_Port, SSD1306_Reset_Pin, GPIO_PIN_RESET);
    HAL_Delay(10);
    HAL_GPIO_WritePin(SSD1306_Reset_Port, SSD1306_Reset_Pin, GPIO_PIN_SET);
    HAL_Delay(10);
}

// Send a byte to the command register
void ssd1306_WriteCommand(uint8_t byte) {
    HAL_GPIO_WritePin(SSD1306_CS_Port, SSD1306_CS_Pin, GPIO_PIN_RESET); // select OLED
    HAL_GPIO_WritePin(SSD1306_DC_Port, SSD1306_DC_Pin, GPIO_PIN_RESET); // command
    HAL_SPI_Transmit(&SSD1306_SPI_PORT, (uint8_t *) &byte, 1, HAL_MAX_DELAY);
    HAL_GPIO_WritePin(SSD1306_CS_Port, SSD1306_CS_Pin, GPIO_PIN_SET); // un-select OLED
}

// Send data
void ssd1306_WriteData(uint8_t* buffer, size_t buff_size) {
    HAL_GPIO_WritePin(SSD1306_CS_Port, SSD1306_CS_Pin, GPIO_PIN_RESET); // select OLED
    HAL_GPIO_WritePin(SSD1306_DC_Port, SSD1306_DC_Pin, GPIO_PIN_SET); // data
    HAL_SPI_Transmit(&SSD1306_SPI_PORT, buffer, buff_size, HAL_MAX_DELAY);
    HAL_GPIO_WritePin(SSD1306_CS_Port, SSD1306_CS_Pin, GPIO_PIN_SET); // un-select OLED
}

#else
#error "You should define SSD1306_USE_SPI or SSD1306_USE_I2C macro"
#endif


// Screenbuffer
static uint8_t SSD1306_Buffer[SSD1306_BUFFER_SIZE];

// Screen object
static SSD1306_t SSD1306;

/* Fills the Screenbuffer with values from a given buffer of a fixed length */
SSD1306_Error_t ssd1306_FillBuffer(uint8_t* buf, uint32_t len) {
    SSD1306_Error_t ret = SSD1306_ERR;
    if (len <= SSD1306_BUFFER_SIZE) {
        memcpy(SSD1306_Buffer,buf,len);
        ret = SSD1306_OK;
    }
    return ret;
}

// Initialize the oled screen
void ssd1306_Init(void) {
    // Reset OLED
    ssd1306_Reset();

    // Wait for the screen to boot
    HAL_Delay(100);

    // Init OLED
    ssd1306_SetDisplayOn(0); //display off

    ssd1306_WriteCommand(0x20); //Set Memory Addressing Mode
    ssd1306_WriteCommand(0x00); // 00b,Horizontal Addressing Mode; 01b,Vertical Addressing Mode;
                                // 10b,Page Addressing Mode (RESET); 11b,Invalid

    ssd1306_WriteCommand(0xB0); //Set Page Start Address for Page Addressing Mode,0-7

#ifdef SSD1306_MIRROR_VERT
    ssd1306_WriteCommand(0xC0); // Mirror vertically
#else
    ssd1306_WriteCommand(0xC8); //Set COM Output Scan Direction
#endif

    ssd1306_WriteCommand(0x00); //---set low column address
    ssd1306_WriteCommand(0x10); //---set high column address

    ssd1306_WriteCommand(0x40); //--set start line address - CHECK

    ssd1306_SetContrast(0xFF);

#ifdef SSD1306_MIRROR_HORIZ
    ssd1306_WriteCommand(0xA0); // Mirror horizontally
#else
    ssd1306_WriteCommand(0xA1); //--set segment re-map 0 to 127 - CHECK
#endif

#ifdef SSD1306_INVERSE_COLOR
    ssd1306_WriteCommand(0xA7); //--set inverse color
#else
    ssd1306_WriteCommand(0xA6); //--set normal color
#endif

// Set multiplex ratio.
#if (SSD1306_HEIGHT == 128)
    // Found in the Luma Python lib for SH1106.
    ssd1306_WriteCommand(0xFF);
#else
    ssd1306_WriteCommand(0xA8); //--set multiplex ratio(1 to 64) - CHECK
#endif

#if (SSD1306_HEIGHT == 32)
    ssd1306_WriteCommand(0x1F); //
#elif (SSD1306_HEIGHT == 64)
    ssd1306_WriteCommand(0x3F); //
#elif (SSD1306_HEIGHT == 128)
    ssd1306_WriteCommand(0x3F); // Seems to work for 128px high displays too.
#else
#error "Only 32, 64, or 128 lines of height are supported!"
#endif

    ssd1306_WriteCommand(0xA4); //0xa4,Output follows RAM content;0xa5,Output ignores RAM content

    ssd1306_WriteCommand(0xD3); //-set display offset - CHECK
    ssd1306_WriteCommand(0x00); //-not offset

    ssd1306_WriteCommand(0xD5); //--set display clock divide ratio/oscillator frequency
    ssd1306_WriteCommand(0xF0); //--set divide ratio

    ssd1306_WriteCommand(0xD9); //--set pre-charge period
    ssd1306_WriteCommand(0x11); // 0x22 by default

    ssd1306_WriteCommand(0xDA); //--set com pins hardware configuration - CHECK
#if (SSD1306_HEIGHT == 32)
    ssd1306_WriteCommand(0x02);
#elif (SSD1306_HEIGHT == 64)
    ssd1306_WriteCommand(0x12);
#elif (SSD1306_HEIGHT == 128)
    ssd1306_WriteCommand(0x12);
#else
#error "Only 32, 64, or 128 lines of height are supported!"
#endif

    ssd1306_WriteCommand(0xDB); //--set vcomh
    ssd1306_WriteCommand(0x30); //0x20,0.77xVcc, 0x30,0.83xVcc

    ssd1306_WriteCommand(0x8D); //--set DC-DC enable
    ssd1306_WriteCommand(0x14); //
    ssd1306_SetDisplayOn(1); //--turn on SSD1306 panel

    // Clear screen
    ssd1306_Fill(Black);

    // Flush buffer to screen
    ssd1306_UpdateScreen();

    // Set default values for screen object
    SSD1306.CurrentX = 0;
    SSD1306.CurrentY = 0;

    SSD1306.Initialized = 1;
}

// Fill the whole screen with the given color
void ssd1306_Fill(SSD1306_COLOR color) {
    /* Set memory */
    uint32_t i;

    for(i = 0; i < sizeof(SSD1306_Buffer); i++) {
        SSD1306_Buffer[i] = (color == Black) ? 0x00 : 0xFF;
    }
}

// Write the screenbuffer with changed to the screen
void ssd1306_UpdateScreen(void) {
    // Write data to each page of RAM. Number of pages
    // depends on the screen height:
    //
    //  * 32px   ==  4 pages
    //  * 64px   ==  8 pages
    //  * 128px  ==  16 pages

    uint8_t cmd[] = {
        0X21,   // 设置列起始和结束地址
        0X00,   // 列起始地址 0
        0X7F,   // 列终止地址 127
        0X22,   // 设置页起始和结束地址
        0X00,   // 页起始地址 0
        0X07,   // 页终止地址 7
    };
    uint32_t count = 0;
    uint8_t data[sizeof(cmd)*2 + SSD1306_BUFFER_SIZE + 1] = {};

    // copy cmd
    for (uint32_t i = 0; i < sizeof(cmd)/sizeof(cmd[0]); i++) {
        data[count++] = SSD1306_CTRL_CMD | SSD1306_MASK_CONT;
        data[count++] = cmd[i];
    }

    // copy frame data
    data[count++] = SSD1306_CTRL_DATA;
    memcpy(&data[count], SSD1306_Buffer, sizeof(SSD1306_Buffer));
    count += sizeof(SSD1306_Buffer);

    // send to i2c bus
    uint32_t retval = ssd1306_SendData(data, count);
    if (retval != 0) {
        printf("ssd1306_UpdateScreen send frame data filed: %d!\r\n", retval);
    }
}

//    Draw one pixel in the screenbuffer
//    X => X Coordinate
//    Y => Y Coordinate
//    color => Pixel color
void ssd1306_DrawPixel(uint8_t x, uint8_t y, SSD1306_COLOR color) {
    if(x >= SSD1306_WIDTH || y >= SSD1306_HEIGHT) {
        // Don't write outside the buffer
        return;
    }

    // Check if pixel should be inverted
    if(SSD1306.Inverted) {
        color = (SSD1306_COLOR)!color;
    }

    // Draw in the right color
    if(color == White) {
        SSD1306_Buffer[x + (y / 8) * SSD1306_WIDTH] |= 1 << (y % 8);
    } else {
        SSD1306_Buffer[x + (y / 8) * SSD1306_WIDTH] &= ~(1 << (y % 8));
    }
}

// Draw 1 char to the screen buffer
// ch       => char om weg te schrijven
// Font     => Font waarmee we gaan schrijven
// color    => Black or White
char ssd1306_DrawChar(char ch, FontDef Font, SSD1306_COLOR color) {
    uint32_t i, b, j;

    // Check if character is valid
    if (ch < 32 || ch > 126)
        return 0;

    // Check remaining space on current line
    if (SSD1306_WIDTH < (SSD1306.CurrentX + Font.FontWidth) ||
        SSD1306_HEIGHT < (SSD1306.CurrentY + Font.FontHeight))
    {
        // Not enough space on current line
        return 0;
    }

    // Use the font to write
    for(i = 0; i < Font.FontHeight; i++) {
        b = Font.data[(ch - 32) * Font.FontHeight + i];
        for(j = 0; j < Font.FontWidth; j++) {
            if((b << j) & 0x8000)  {
                ssd1306_DrawPixel(SSD1306.CurrentX + j, (SSD1306.CurrentY + i), (SSD1306_COLOR) color);
            } else {
                ssd1306_DrawPixel(SSD1306.CurrentX + j, (SSD1306.CurrentY + i), (SSD1306_COLOR)!color);
            }
        }
    }

    // The current space is now taken
    SSD1306.CurrentX += Font.FontWidth;

    // Return written char for validation
    return ch;
}

// Write full string to screenbuffer
char ssd1306_DrawString(char* str, FontDef Font, SSD1306_COLOR color) {
    // Write until null-byte
    while (*str) {
        if (ssd1306_DrawChar(*str, Font, color) != *str) {
            // Char could not be written
            return *str;
        }

        // Next char
        str++;
    }

    // Everything ok
    return *str;
}

// Position the cursor
void ssd1306_SetCursor(uint8_t x, uint8_t y) {
    SSD1306.CurrentX = x;
    SSD1306.CurrentY = y;
}

// Draw line by Bresenhem's algorithm
void ssd1306_DrawLine(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, SSD1306_COLOR color) {
  int32_t deltaX = abs(x2 - x1);
  int32_t deltaY = abs(y2 - y1);
  int32_t signX = ((x1 < x2) ? 1 : -1);
  int32_t signY = ((y1 < y2) ? 1 : -1);
  int32_t error = deltaX - deltaY;
  int32_t error2;

  ssd1306_DrawPixel(x2, y2, color);
    while((x1 != x2) || (y1 != y2))
    {
    ssd1306_DrawPixel(x1, y1, color);
    error2 = error * 2;
    if(error2 > -deltaY)
    {
      error -= deltaY;
      x1 += signX;
    }
    else
    {
    /*nothing to do*/
    }

    if(error2 < deltaX)
    {
      error += deltaX;
      y1 += signY;
    }
    else
    {
    /*nothing to do*/
    }
  }
  return;
}
//Draw polyline
void ssd1306_DrawPolyline(const SSD1306_VERTEX *par_vertex, uint16_t par_size, SSD1306_COLOR color) {
  uint16_t i;
  if(par_vertex != 0){
    for(i = 1; i < par_size; i++){
      ssd1306_DrawLine(par_vertex[i - 1].x, par_vertex[i - 1].y, par_vertex[i].x, par_vertex[i].y, color);
    }
  }
  else
  {
    /*nothing to do*/
  }
  return;
}
/*Convert Degrees to Radians*/
static float ssd1306_DegToRad(float par_deg) {
    return par_deg * 3.14 / 180.0;
}
/*Normalize degree to [0;360]*/
static uint16_t ssd1306_NormalizeTo0_360(uint16_t par_deg) {
  uint16_t loc_angle;
  if(par_deg <= 360)
  {
    loc_angle = par_deg;
  }
  else
  {
    loc_angle = par_deg % 360;
    loc_angle = ((par_deg != 0)?par_deg:360);
  }
  return loc_angle;
}
/*DrawArc. Draw angle is beginning from 4 quart of trigonometric circle (3pi/2)
 * start_angle in degree
 * sweep in degree
 */
void ssd1306_DrawArc(uint8_t x, uint8_t y, uint8_t radius, uint16_t start_angle, uint16_t sweep, SSD1306_COLOR color) {
    #define CIRCLE_APPROXIMATION_SEGMENTS 36
    float approx_degree;
    uint32_t approx_segments;
    uint8_t xp1,xp2;
    uint8_t yp1,yp2;
    uint32_t count = 0;
    uint32_t loc_sweep = 0;
    float rad;

    loc_sweep = ssd1306_NormalizeTo0_360(sweep);

    count = (ssd1306_NormalizeTo0_360(start_angle) * CIRCLE_APPROXIMATION_SEGMENTS) / 360;
    approx_segments = (loc_sweep * CIRCLE_APPROXIMATION_SEGMENTS) / 360;
    approx_degree = loc_sweep / (float)approx_segments;
    while(count < approx_segments)
    {
        rad = ssd1306_DegToRad(count*approx_degree);
        xp1 = x + (int8_t)(sin(rad)*radius);
        yp1 = y + (int8_t)(cos(rad)*radius);
        count++;
        if(count != approx_segments)
        {
            rad = ssd1306_DegToRad(count*approx_degree);
        }
        else
        {
            rad = ssd1306_DegToRad(loc_sweep);
        }
        xp2 = x + (int8_t)(sin(rad)*radius);
        yp2 = y + (int8_t)(cos(rad)*radius);
        ssd1306_DrawLine(xp1,yp1,xp2,yp2,color);
    }

    return;
}
//Draw circle by Bresenhem's algorithm
void ssd1306_DrawCircle(uint8_t par_x,uint8_t par_y,uint8_t par_r,SSD1306_COLOR par_color) {
  int32_t x = -par_r;
  int32_t y = 0;
  int32_t err = 2 - 2 * par_r;
  int32_t e2;

  if (par_x >= SSD1306_WIDTH || par_y >= SSD1306_HEIGHT) {
    return;
  }

    do {
      ssd1306_DrawPixel(par_x - x, par_y + y, par_color);
      ssd1306_DrawPixel(par_x + x, par_y + y, par_color);
      ssd1306_DrawPixel(par_x + x, par_y - y, par_color);
      ssd1306_DrawPixel(par_x - x, par_y - y, par_color);
        e2 = err;
        if (e2 <= y) {
            y++;
            err = err + (y * 2 + 1);
            if(-x == y && e2 <= x) {
              e2 = 0;
            }
            else
            {
              /*nothing to do*/
            }
        }
        else
        {
          /*nothing to do*/
        }
        if(e2 > x) {
          x++;
          err = err + (x * 2 + 1);
        }
        else
        {
          /*nothing to do*/
        }
    } while(x <= 0);

    return;
}

//Draw rectangle
void ssd1306_DrawRectangle(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, SSD1306_COLOR color) {
  ssd1306_DrawLine(x1,y1,x2,y1,color);
  ssd1306_DrawLine(x2,y1,x2,y2,color);
  ssd1306_DrawLine(x2,y2,x1,y2,color);
  ssd1306_DrawLine(x1,y2,x1,y1,color);

  return;
}

void ssd1306_DrawBitmap(const uint8_t* bitmap, uint32_t size)
{
    uint8_t rows = size * 8 / SSD1306_WIDTH;
    if (rows > SSD1306_HEIGHT) {
        rows = SSD1306_HEIGHT;
    }
    for (uint8_t y = 0; y < rows; y++) {
        for (uint8_t x = 0; x < SSD1306_WIDTH; x++) {
            uint8_t byte = bitmap[(y * SSD1306_WIDTH / 8) + (x / 8)];
            uint8_t bit = byte & (0x80 >> (x % 8));
            ssd1306_DrawPixel(x, y, bit ? White : Black);
        }
    }
}

void ssd1306_DrawRegion(uint8_t x, uint8_t y, uint8_t w, uint8_t h, const uint8_t* data, uint32_t size, uint32_t stride)
{
    if (x + w > SSD1306_WIDTH || y + h > SSD1306_HEIGHT || w * h == 0) {
        printf("%dx%d @ %d,%d out of range or invalid!\r\n", w, h, x, y);
        return;
    }

    w = (w <= SSD1306_WIDTH ? w : SSD1306_WIDTH);
    h = (h <= SSD1306_HEIGHT ? h : SSD1306_HEIGHT);
    stride = (stride == 0 ? w : stride);

    uint8_t rows = size * 8 / stride;
    for (uint8_t i = 0; i < rows; i++) {
        uint32_t base = i * stride / 8;
        for (uint8_t j = 0; j < w; j++) {
            uint32_t idx = base + (j / 8);
            uint8_t byte = idx < size ? data[idx] : 0;
            uint8_t bit  = byte & (0x80 >> (j % 8));
            ssd1306_DrawPixel(x + j, y + i, bit ? White : Black);
        }
    }
}

void ssd1306_SetContrast(const uint8_t value) {
    const uint8_t kSetContrastControlRegister = 0x81;
    ssd1306_WriteCommand(kSetContrastControlRegister);
    ssd1306_WriteCommand(value);
}

void ssd1306_SetDisplayOn(const uint8_t on) {
    uint8_t value;
    if (on) {
        value = 0xAF;   // Display on
        SSD1306.DisplayOn = 1;
    } else {
        value = 0xAE;   // Display off
        SSD1306.DisplayOn = 0;
    }
    ssd1306_WriteCommand(value);
}

uint8_t ssd1306_GetDisplayOn() {
    return SSD1306.DisplayOn;
}
ssd1306.h
#include "ssd1306.h"
#include <math.h>
#include <stdlib.h>
#include <string.h>  // For memcpy

#include <stdio.h>
#include <unistd.h>
#include "cmsis_os2.h"
#include <stdio.h>
#include "cmsis_os2.h"
#include "ohos_run.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "esp_log.h"
#include "driver/gpio.h"
#include "driver/i2c.h"

#if defined(SSD1306_USE_I2C)

#define SSD1306_I2C_IDX     0

#define SSD1306_CTRL_CMD 0x00
#define SSD1306_CTRL_DATA 0x40
#define SSD1306_MASK_CONT (0x1<<7)

void ssd1306_Reset(void) {
    /* for I2C - do nothing */
}

void HAL_Delay(uint32_t ms)
{
    usleep(ms * 1000);
}


static uint32_t ssd1306_SendData(uint8_t* data, size_t size)
{
    int i;
    i2c_cmd_handle_t cmd_handle = i2c_cmd_link_create();
 
    i2c_master_start(cmd_handle);
    i2c_master_write_byte(cmd_handle, SSD1306_I2C_ADDR, true);

    for(i = 0; i < size; i ++)
    {
        i2c_master_write_byte(cmd_handle, data[i], true);
    }

    i2c_master_stop(cmd_handle);
    esp_err_t error = i2c_master_cmd_begin(I2C_NUM_0,cmd_handle,100/portTICK_PERIOD_MS);
 
    i2c_cmd_link_delete(cmd_handle);

    return error;
}

static uint32_t ssd1306_WiteByte(uint8_t regAddr, uint8_t byte)
{
    uint8_t buffer[] = {regAddr, byte};
    return ssd1306_SendData(buffer, sizeof(buffer));
}

// Send a byte to the command register
void ssd1306_WriteCommand(uint8_t byte) {
    ssd1306_WiteByte(SSD1306_CTRL_CMD, byte);
}

// Send data
void ssd1306_WriteData(uint8_t* buffer, size_t buff_size) {
    uint8_t data[SSD1306_WIDTH * 2] = {0};
    for (size_t i = 0; i < buff_size; i++) {
        data[i*2] = SSD1306_CTRL_DATA | SSD1306_MASK_CONT;
        data[i*2+1] = buffer[i];
    }
    data[(buff_size - 1) * 2] = SSD1306_CTRL_DATA;
    ssd1306_SendData(data, sizeof(data));
}

#elif defined(SSD1306_USE_SPI)

void ssd1306_Reset(void) {
    // CS = High (not selected)
    HAL_GPIO_WritePin(SSD1306_CS_Port, SSD1306_CS_Pin, GPIO_PIN_SET);

    // Reset the OLED
    HAL_GPIO_WritePin(SSD1306_Reset_Port, SSD1306_Reset_Pin, GPIO_PIN_RESET);
    HAL_Delay(10);
    HAL_GPIO_WritePin(SSD1306_Reset_Port, SSD1306_Reset_Pin, GPIO_PIN_SET);
    HAL_Delay(10);
}

// Send a byte to the command register
void ssd1306_WriteCommand(uint8_t byte) {
    HAL_GPIO_WritePin(SSD1306_CS_Port, SSD1306_CS_Pin, GPIO_PIN_RESET); // select OLED
    HAL_GPIO_WritePin(SSD1306_DC_Port, SSD1306_DC_Pin, GPIO_PIN_RESET); // command
    HAL_SPI_Transmit(&SSD1306_SPI_PORT, (uint8_t *) &byte, 1, HAL_MAX_DELAY);
    HAL_GPIO_WritePin(SSD1306_CS_Port, SSD1306_CS_Pin, GPIO_PIN_SET); // un-select OLED
}

// Send data
void ssd1306_WriteData(uint8_t* buffer, size_t buff_size) {
    HAL_GPIO_WritePin(SSD1306_CS_Port, SSD1306_CS_Pin, GPIO_PIN_RESET); // select OLED
    HAL_GPIO_WritePin(SSD1306_DC_Port, SSD1306_DC_Pin, GPIO_PIN_SET); // data
    HAL_SPI_Transmit(&SSD1306_SPI_PORT, buffer, buff_size, HAL_MAX_DELAY);
    HAL_GPIO_WritePin(SSD1306_CS_Port, SSD1306_CS_Pin, GPIO_PIN_SET); // un-select OLED
}

#else
#error "You should define SSD1306_USE_SPI or SSD1306_USE_I2C macro"
#endif


// Screenbuffer
static uint8_t SSD1306_Buffer[SSD1306_BUFFER_SIZE];

// Screen object
static SSD1306_t SSD1306;

/* Fills the Screenbuffer with values from a given buffer of a fixed length */
SSD1306_Error_t ssd1306_FillBuffer(uint8_t* buf, uint32_t len) {
    SSD1306_Error_t ret = SSD1306_ERR;
    if (len <= SSD1306_BUFFER_SIZE) {
        memcpy(SSD1306_Buffer,buf,len);
        ret = SSD1306_OK;
    }
    return ret;
}

// Initialize the oled screen
void ssd1306_Init(void) {
    // Reset OLED
    ssd1306_Reset();

    // Wait for the screen to boot
    HAL_Delay(100);

    // Init OLED
    ssd1306_SetDisplayOn(0); //display off

    ssd1306_WriteCommand(0x20); //Set Memory Addressing Mode
    ssd1306_WriteCommand(0x00); // 00b,Horizontal Addressing Mode; 01b,Vertical Addressing Mode;
                                // 10b,Page Addressing Mode (RESET); 11b,Invalid

    ssd1306_WriteCommand(0xB0); //Set Page Start Address for Page Addressing Mode,0-7

#ifdef SSD1306_MIRROR_VERT
    ssd1306_WriteCommand(0xC0); // Mirror vertically
#else
    ssd1306_WriteCommand(0xC8); //Set COM Output Scan Direction
#endif

    ssd1306_WriteCommand(0x00); //---set low column address
    ssd1306_WriteCommand(0x10); //---set high column address

    ssd1306_WriteCommand(0x40); //--set start line address - CHECK

    ssd1306_SetContrast(0xFF);

#ifdef SSD1306_MIRROR_HORIZ
    ssd1306_WriteCommand(0xA0); // Mirror horizontally
#else
    ssd1306_WriteCommand(0xA1); //--set segment re-map 0 to 127 - CHECK
#endif

#ifdef SSD1306_INVERSE_COLOR
    ssd1306_WriteCommand(0xA7); //--set inverse color
#else
    ssd1306_WriteCommand(0xA6); //--set normal color
#endif

// Set multiplex ratio.
#if (SSD1306_HEIGHT == 128)
    // Found in the Luma Python lib for SH1106.
    ssd1306_WriteCommand(0xFF);
#else
    ssd1306_WriteCommand(0xA8); //--set multiplex ratio(1 to 64) - CHECK
#endif

#if (SSD1306_HEIGHT == 32)
    ssd1306_WriteCommand(0x1F); //
#elif (SSD1306_HEIGHT == 64)
    ssd1306_WriteCommand(0x3F); //
#elif (SSD1306_HEIGHT == 128)
    ssd1306_WriteCommand(0x3F); // Seems to work for 128px high displays too.
#else
#error "Only 32, 64, or 128 lines of height are supported!"
#endif

    ssd1306_WriteCommand(0xA4); //0xa4,Output follows RAM content;0xa5,Output ignores RAM content

    ssd1306_WriteCommand(0xD3); //-set display offset - CHECK
    ssd1306_WriteCommand(0x00); //-not offset

    ssd1306_WriteCommand(0xD5); //--set display clock divide ratio/oscillator frequency
    ssd1306_WriteCommand(0xF0); //--set divide ratio

    ssd1306_WriteCommand(0xD9); //--set pre-charge period
    ssd1306_WriteCommand(0x11); // 0x22 by default

    ssd1306_WriteCommand(0xDA); //--set com pins hardware configuration - CHECK
#if (SSD1306_HEIGHT == 32)
    ssd1306_WriteCommand(0x02);
#elif (SSD1306_HEIGHT == 64)
    ssd1306_WriteCommand(0x12);
#elif (SSD1306_HEIGHT == 128)
    ssd1306_WriteCommand(0x12);
#else
#error "Only 32, 64, or 128 lines of height are supported!"
#endif

    ssd1306_WriteCommand(0xDB); //--set vcomh
    ssd1306_WriteCommand(0x30); //0x20,0.77xVcc, 0x30,0.83xVcc

    ssd1306_WriteCommand(0x8D); //--set DC-DC enable
    ssd1306_WriteCommand(0x14); //
    ssd1306_SetDisplayOn(1); //--turn on SSD1306 panel

    // Clear screen
    ssd1306_Fill(Black);

    // Flush buffer to screen
    ssd1306_UpdateScreen();

    // Set default values for screen object
    SSD1306.CurrentX = 0;
    SSD1306.CurrentY = 0;

    SSD1306.Initialized = 1;
}

// Fill the whole screen with the given color
void ssd1306_Fill(SSD1306_COLOR color) {
    /* Set memory */
    uint32_t i;

    for(i = 0; i < sizeof(SSD1306_Buffer); i++) {
        SSD1306_Buffer[i] = (color == Black) ? 0x00 : 0xFF;
    }
}

// Write the screenbuffer with changed to the screen
void ssd1306_UpdateScreen(void) {
    // Write data to each page of RAM. Number of pages
    // depends on the screen height:
    //
    //  * 32px   ==  4 pages
    //  * 64px   ==  8 pages
    //  * 128px  ==  16 pages

    uint8_t cmd[] = {
        0X21,   // 设置列起始和结束地址
        0X00,   // 列起始地址 0
        0X7F,   // 列终止地址 127
        0X22,   // 设置页起始和结束地址
        0X00,   // 页起始地址 0
        0X07,   // 页终止地址 7
    };
    uint32_t count = 0;
    uint8_t data[sizeof(cmd)*2 + SSD1306_BUFFER_SIZE + 1] = {};

    // copy cmd
    for (uint32_t i = 0; i < sizeof(cmd)/sizeof(cmd[0]); i++) {
        data[count++] = SSD1306_CTRL_CMD | SSD1306_MASK_CONT;
        data[count++] = cmd[i];
    }

    // copy frame data
    data[count++] = SSD1306_CTRL_DATA;
    memcpy(&data[count], SSD1306_Buffer, sizeof(SSD1306_Buffer));
    count += sizeof(SSD1306_Buffer);

    // send to i2c bus
    uint32_t retval = ssd1306_SendData(data, count);
    if (retval != 0) {
        printf("ssd1306_UpdateScreen send frame data filed: %d!\r\n", retval);
    }
}

//    Draw one pixel in the screenbuffer
//    X => X Coordinate
//    Y => Y Coordinate
//    color => Pixel color
void ssd1306_DrawPixel(uint8_t x, uint8_t y, SSD1306_COLOR color) {
    if(x >= SSD1306_WIDTH || y >= SSD1306_HEIGHT) {
        // Don't write outside the buffer
        return;
    }

    // Check if pixel should be inverted
    if(SSD1306.Inverted) {
        color = (SSD1306_COLOR)!color;
    }

    // Draw in the right color
    if(color == White) {
        SSD1306_Buffer[x + (y / 8) * SSD1306_WIDTH] |= 1 << (y % 8);
    } else {
        SSD1306_Buffer[x + (y / 8) * SSD1306_WIDTH] &= ~(1 << (y % 8));
    }
}

// Draw 1 char to the screen buffer
// ch       => char om weg te schrijven
// Font     => Font waarmee we gaan schrijven
// color    => Black or White
char ssd1306_DrawChar(char ch, FontDef Font, SSD1306_COLOR color) {
    uint32_t i, b, j;

    // Check if character is valid
    if (ch < 32 || ch > 126)
        return 0;

    // Check remaining space on current line
    if (SSD1306_WIDTH < (SSD1306.CurrentX + Font.FontWidth) ||
        SSD1306_HEIGHT < (SSD1306.CurrentY + Font.FontHeight))
    {
        // Not enough space on current line
        return 0;
    }

    // Use the font to write
    for(i = 0; i < Font.FontHeight; i++) {
        b = Font.data[(ch - 32) * Font.FontHeight + i];
        for(j = 0; j < Font.FontWidth; j++) {
            if((b << j) & 0x8000)  {
                ssd1306_DrawPixel(SSD1306.CurrentX + j, (SSD1306.CurrentY + i), (SSD1306_COLOR) color);
            } else {
                ssd1306_DrawPixel(SSD1306.CurrentX + j, (SSD1306.CurrentY + i), (SSD1306_COLOR)!color);
            }
        }
    }

    // The current space is now taken
    SSD1306.CurrentX += Font.FontWidth;

    // Return written char for validation
    return ch;
}

// Write full string to screenbuffer
char ssd1306_DrawString(char* str, FontDef Font, SSD1306_COLOR color) {
    // Write until null-byte
    while (*str) {
        if (ssd1306_DrawChar(*str, Font, color) != *str) {
            // Char could not be written
            return *str;
        }

        // Next char
        str++;
    }

    // Everything ok
    return *str;
}

// Position the cursor
void ssd1306_SetCursor(uint8_t x, uint8_t y) {
    SSD1306.CurrentX = x;
    SSD1306.CurrentY = y;
}

// Draw line by Bresenhem's algorithm
void ssd1306_DrawLine(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, SSD1306_COLOR color) {
  int32_t deltaX = abs(x2 - x1);
  int32_t deltaY = abs(y2 - y1);
  int32_t signX = ((x1 < x2) ? 1 : -1);
  int32_t signY = ((y1 < y2) ? 1 : -1);
  int32_t error = deltaX - deltaY;
  int32_t error2;

  ssd1306_DrawPixel(x2, y2, color);
    while((x1 != x2) || (y1 != y2))
    {
    ssd1306_DrawPixel(x1, y1, color);
    error2 = error * 2;
    if(error2 > -deltaY)
    {
      error -= deltaY;
      x1 += signX;
    }
    else
    {
    /*nothing to do*/
    }

    if(error2 < deltaX)
    {
      error += deltaX;
      y1 += signY;
    }
    else
    {
    /*nothing to do*/
    }
  }
  return;
}
//Draw polyline
void ssd1306_DrawPolyline(const SSD1306_VERTEX *par_vertex, uint16_t par_size, SSD1306_COLOR color) {
  uint16_t i;
  if(par_vertex != 0){
    for(i = 1; i < par_size; i++){
      ssd1306_DrawLine(par_vertex[i - 1].x, par_vertex[i - 1].y, par_vertex[i].x, par_vertex[i].y, color);
    }
  }
  else
  {
    /*nothing to do*/
  }
  return;
}
/*Convert Degrees to Radians*/
static float ssd1306_DegToRad(float par_deg) {
    return par_deg * 3.14 / 180.0;
}
/*Normalize degree to [0;360]*/
static uint16_t ssd1306_NormalizeTo0_360(uint16_t par_deg) {
  uint16_t loc_angle;
  if(par_deg <= 360)
  {
    loc_angle = par_deg;
  }
  else
  {
    loc_angle = par_deg % 360;
    loc_angle = ((par_deg != 0)?par_deg:360);
  }
  return loc_angle;
}
/*DrawArc. Draw angle is beginning from 4 quart of trigonometric circle (3pi/2)
 * start_angle in degree
 * sweep in degree
 */
void ssd1306_DrawArc(uint8_t x, uint8_t y, uint8_t radius, uint16_t start_angle, uint16_t sweep, SSD1306_COLOR color) {
    #define CIRCLE_APPROXIMATION_SEGMENTS 36
    float approx_degree;
    uint32_t approx_segments;
    uint8_t xp1,xp2;
    uint8_t yp1,yp2;
    uint32_t count = 0;
    uint32_t loc_sweep = 0;
    float rad;

    loc_sweep = ssd1306_NormalizeTo0_360(sweep);

    count = (ssd1306_NormalizeTo0_360(start_angle) * CIRCLE_APPROXIMATION_SEGMENTS) / 360;
    approx_segments = (loc_sweep * CIRCLE_APPROXIMATION_SEGMENTS) / 360;
    approx_degree = loc_sweep / (float)approx_segments;
    while(count < approx_segments)
    {
        rad = ssd1306_DegToRad(count*approx_degree);
        xp1 = x + (int8_t)(sin(rad)*radius);
        yp1 = y + (int8_t)(cos(rad)*radius);
        count++;
        if(count != approx_segments)
        {
            rad = ssd1306_DegToRad(count*approx_degree);
        }
        else
        {
            rad = ssd1306_DegToRad(loc_sweep);
        }
        xp2 = x + (int8_t)(sin(rad)*radius);
        yp2 = y + (int8_t)(cos(rad)*radius);
        ssd1306_DrawLine(xp1,yp1,xp2,yp2,color);
    }

    return;
}
//Draw circle by Bresenhem's algorithm
void ssd1306_DrawCircle(uint8_t par_x,uint8_t par_y,uint8_t par_r,SSD1306_COLOR par_color) {
  int32_t x = -par_r;
  int32_t y = 0;
  int32_t err = 2 - 2 * par_r;
  int32_t e2;

  if (par_x >= SSD1306_WIDTH || par_y >= SSD1306_HEIGHT) {
    return;
  }

    do {
      ssd1306_DrawPixel(par_x - x, par_y + y, par_color);
      ssd1306_DrawPixel(par_x + x, par_y + y, par_color);
      ssd1306_DrawPixel(par_x + x, par_y - y, par_color);
      ssd1306_DrawPixel(par_x - x, par_y - y, par_color);
        e2 = err;
        if (e2 <= y) {
            y++;
            err = err + (y * 2 + 1);
            if(-x == y && e2 <= x) {
              e2 = 0;
            }
            else
            {
              /*nothing to do*/
            }
        }
        else
        {
          /*nothing to do*/
        }
        if(e2 > x) {
          x++;
          err = err + (x * 2 + 1);
        }
        else
        {
          /*nothing to do*/
        }
    } while(x <= 0);

    return;
}

//Draw rectangle
void ssd1306_DrawRectangle(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, SSD1306_COLOR color) {
  ssd1306_DrawLine(x1,y1,x2,y1,color);
  ssd1306_DrawLine(x2,y1,x2,y2,color);
  ssd1306_DrawLine(x2,y2,x1,y2,color);
  ssd1306_DrawLine(x1,y2,x1,y1,color);

  return;
}

void ssd1306_DrawBitmap(const uint8_t* bitmap, uint32_t size)
{
    uint8_t rows = size * 8 / SSD1306_WIDTH;
    if (rows > SSD1306_HEIGHT) {
        rows = SSD1306_HEIGHT;
    }
    for (uint8_t y = 0; y < rows; y++) {
        for (uint8_t x = 0; x < SSD1306_WIDTH; x++) {
            uint8_t byte = bitmap[(y * SSD1306_WIDTH / 8) + (x / 8)];
            uint8_t bit = byte & (0x80 >> (x % 8));
            ssd1306_DrawPixel(x, y, bit ? White : Black);
        }
    }
}

void ssd1306_DrawRegion(uint8_t x, uint8_t y, uint8_t w, uint8_t h, const uint8_t* data, uint32_t size, uint32_t stride)
{
    if (x + w > SSD1306_WIDTH || y + h > SSD1306_HEIGHT || w * h == 0) {
        printf("%dx%d @ %d,%d out of range or invalid!\r\n", w, h, x, y);
        return;
    }

    w = (w <= SSD1306_WIDTH ? w : SSD1306_WIDTH);
    h = (h <= SSD1306_HEIGHT ? h : SSD1306_HEIGHT);
    stride = (stride == 0 ? w : stride);

    uint8_t rows = size * 8 / stride;
    for (uint8_t i = 0; i < rows; i++) {
        uint32_t base = i * stride / 8;
        for (uint8_t j = 0; j < w; j++) {
            uint32_t idx = base + (j / 8);
            uint8_t byte = idx < size ? data[idx] : 0;
            uint8_t bit  = byte & (0x80 >> (j % 8));
            ssd1306_DrawPixel(x + j, y + i, bit ? White : Black);
        }
    }
}

void ssd1306_SetContrast(const uint8_t value) {
    const uint8_t kSetContrastControlRegister = 0x81;
    ssd1306_WriteCommand(kSetContrastControlRegister);
    ssd1306_WriteCommand(value);
}

void ssd1306_SetDisplayOn(const uint8_t on) {
    uint8_t value;
    if (on) {
        value = 0xAF;   // Display on
        SSD1306.DisplayOn = 1;
    } else {
        value = 0xAE;   // Display off
        SSD1306.DisplayOn = 0;
    }
    ssd1306_WriteCommand(value);
}

uint8_t ssd1306_GetDisplayOn() {
    return SSD1306.DisplayOn;
}
ssd1306_demo.c
/*
 * Copyright (c) 2020, HiHope Community.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdio.h>
#include <unistd.h>

#include "ohos_init.h"
#include "cmsis_os2.h"
#include <stdio.h>
#include "cmsis_os2.h"
#include "ohos_run.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "esp_log.h"
#include "driver/gpio.h"
#include "driver/i2c.h"

#include "ssd1306.h"
#include "ssd1306_tests.h"



#define OLED_I2C_BAUDRATE 400*1000

void TestGetTick(void)
{

}


/**
 * 汉字字模在线: https://www.23bei.com/tool-223.html
 * 数据排列:从左到右从上到下
 * 取模方式:横向8位左高位
**/
void TestDrawChinese1(void)
{
    const uint32_t W = 16, H = 16;
    uint8_t fonts[][32] = {
        /* [字库]:[HZK1616宋体] [数据排列]:从左到右从上到下 [取模方式]:横向8点左高位 [正负反色]:否 [去掉重复后]共6个字符
            [总字符库]:" 鸿蒙实验箱"*/
        {
            /*-- ID:0,字符:" ",ASCII编码:A1A1,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
            0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
            0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        },{
            /*-- ID:1,字符:"鸿",ASCII编码:BAE8,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
            0x40,0x20,0x30,0x48,0x10,0xFC,0x02,0x88,0x9F,0xA8,0x64,0x88,0x24,0xA8,0x04,0x90,
            0x14,0x84,0x14,0xFE,0xE7,0x04,0x3C,0x24,0x29,0xF4,0x20,0x04,0x20,0x14,0x20,0x08,
        },{
            /*-- ID:2,字符:"蒙",ASCII编码:C3C9,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
            0x04,0x48,0x7F,0xFC,0x04,0x40,0x7F,0xFE,0x40,0x02,0x8F,0xE4,0x00,0x00,0x7F,0xFC,
            0x06,0x10,0x3B,0x30,0x05,0xC0,0x1A,0xA0,0x64,0x90,0x18,0x8E,0x62,0x84,0x01,0x00,
        },{
            /*-- ID:3,字符:"实",ASCII编码:CAB5,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
            0x02,0x00,0x01,0x00,0x7F,0xFE,0x48,0x02,0x86,0x84,0x02,0x80,0x10,0x80,0x0C,0x80,
            0x04,0x84,0xFF,0xFE,0x01,0x00,0x01,0x40,0x02,0x20,0x04,0x10,0x18,0x0C,0x60,0x04,
        },{
            /*-- ID:4,字符:"验",ASCII编码:D1E9,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
            0x08,0x40,0xFC,0x40,0x08,0xA0,0x48,0xA0,0x49,0x10,0x4A,0x0E,0x4D,0xF4,0x48,0x00,
            0x7C,0x48,0x06,0x48,0x05,0x48,0x1D,0x50,0xE5,0x10,0x44,0x24,0x17,0xFE,0x08,0x00,
        },{
            /*-- ID:5,字符:"箱",ASCII编码:CFE4,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
            0x12,0x44,0x1F,0x7E,0x28,0xA0,0x45,0x10,0x08,0x04,0x08,0xFE,0xFE,0x84,0x08,0x84,
            0x18,0xFC,0x1C,0x84,0x2A,0x84,0x28,0xFC,0x48,0x84,0x88,0x84,0x08,0xFC,0x08,0x84
        }
    };

    ssd1306_Fill(Black);
    for (size_t i = 0; i < sizeof(fonts)/sizeof(fonts[0]); i++) {
        ssd1306_DrawRegion(i * W, 18, W, H, fonts[i], sizeof(fonts[0]), W);
    }
    ssd1306_UpdateScreen();
    sleep(1);
}

void TestDrawChinese2(void)
{
    const uint32_t W = 12, H = 12, S = 16;
      /* [字库]:[HZK1212宋体] [数据排列]:从左到右从上到下 [取模方式]:横向8点左高位 [正负反色]:否 [去掉重复后]共9个字符
        [总字符库]:" -OLED屏案例"*/
    uint8_t fonts[][24] = {
        {
            /*-- ID:0,字符:" ",ASCII编码:A1A1,对应字:宽x高=12x12,画布:宽W=16 高H=12,共24字节*/
            0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
            0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        },{
            /*-- ID:1,字符:"-",ASCII编码:A3AD,对应字:宽x高=12x12,画布:宽W=16 高H=12,共24字节*/
            0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7F,0x00,0x00,0x00,0x00,0x00,
            0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        },{
            /*-- ID:2,字符:"O",ASCII编码:A3CF,对应字:宽x高=12x12,画布:宽W=16 高H=12,共24字节*/
            0x00,0x00,0x1C,0x00,0x36,0x00,0x63,0x00,0x63,0x00,0x63,0x00,0x63,0x00,0x63,0x00,
            0x36,0x00,0x1C,0x00,0x00,0x00,0x00,0x00,
        },{
            /*-- ID:3,字符:"L",ASCII编码:A3CC,对应字:宽x高=12x12,画布:宽W=16 高H=12,共24字节*/
            0x00,0x00,0x78,0x00,0x30,0x00,0x30,0x00,0x30,0x00,0x30,0x00,0x30,0x00,0x31,0x00,
            0x33,0x00,0x7F,0x00,0x00,0x00,0x00,0x00,
        },{
            /*-- ID:4,字符:"E",ASCII编码:A3C5,对应字:宽x高=12x12,画布:宽W=16 高H=12,共24字节*/
            0x00,0x00,0x7F,0x00,0x33,0x00,0x31,0x00,0x34,0x00,0x3C,0x00,0x34,0x00,0x31,0x00,
            0x33,0x00,0x7F,0x00,0x00,0x00,0x00,0x00,
        },{
            /*-- ID:5,字符:"D",ASCII编码:A3C4,对应字:宽x高=12x12,画布:宽W=16 高H=12,共24字节*/
            0x00,0x00,0x7C,0x00,0x36,0x00,0x33,0x00,0x33,0x00,0x33,0x00,0x33,0x00,0x33,0x00,
            0x36,0x00,0x7C,0x00,0x00,0x00,0x00,0x00,
        },{
            /*-- ID:6,字符:"屏",ASCII编码:C6C1,对应字:宽x高=12x12,画布:宽W=16 高H=12,共24字节*/
            0x7F,0xE0,0x40,0x20,0x7F,0xE0,0x48,0x80,0x45,0x00,0x5F,0xE0,0x44,0x80,0x7F,0xF0,
            0x44,0x80,0x44,0x80,0x48,0x80,0x90,0x80,
        },{
            /*-- ID:7,字符:"案",ASCII编码:B0B8,对应字:宽x高=12x12,画布:宽W=16 高H=12,共24字节*/
            0x04,0x00,0x7F,0xF0,0xC8,0x20,0x7F,0xF0,0x11,0x00,0x0E,0x00,0x71,0xC0,0x04,0x00,
            0xFF,0xF0,0x15,0x00,0x24,0xC0,0xC4,0x30,
        },{
            /*-- ID:8,字符:"例",ASCII编码:C0FD,对应字:宽x高=12x12,画布:宽W=16 高H=12,共24字节*/
            0x10,0x10,0x1F,0x90,0x24,0x50,0x27,0x50,0x69,0x50,0xA9,0x50,0x35,0x50,0x22,0x50,
            0x22,0x10,0x24,0x10,0x28,0x50,0x30,0x20
        }
    };
    ssd1306_Fill(Black);
    for (size_t i = 0; i < sizeof(fonts)/sizeof(fonts[0]); i++) {
        ssd1306_DrawRegion(i * H, 18, W, H, fonts[i], sizeof(fonts[0]), S);         //y= 18 居中显示
    }
    ssd1306_UpdateScreen();
    sleep(1);
}

void Ssd1306TestTask(void* arg)
{
    (void) arg;
    
    i2c_config_t i2c_initer = {
        .clk_flags = 0,                         // 选择默认时钟源
        .master.clk_speed = 50000,             // 指定速率为100Kbit,最大可以为400Kbit
        .mode = I2C_MODE_MASTER,                // 主机模式
        .scl_io_num = 17,                        // 指定SCL的GPIO口
        .scl_pullup_en = true,                  // SCL接上拉电阻
        .sda_io_num = 16,                        // 指定SDA的GPIO口
        .sda_pullup_en = true,                  // SDA接上拉电阻
    };
    if(i2c_param_config(I2C_NUM_0, &i2c_initer) == ESP_OK)
    {
        printf("i2c parm config success\r\n");
    }else{
        printf("config fail\r\n");
    }

    if(i2c_driver_install(I2C_NUM_0,I2C_MODE_MASTER,0,0,0) == ESP_OK )
    {
        printf("i2c driver install success\r\n");
    }else{
        printf("driver fail\r\n");
    }

    //WatchDogDisable();

    usleep(20*1000);
    ssd1306_Init();
    ssd1306_Fill(Black);
    ssd1306_SetCursor(0, 0);

    while (1) {
        TestDrawChinese1();
        ssd1306_UpdateScreen();
        usleep(10000);
        TestDrawChinese2();
        ssd1306_UpdateScreen();
        usleep(10000);
    }
}

void Ssd1306TestDemo(void)
{
    osThreadAttr_t attr;

    attr.name = "Ssd1306Task";
    attr.attr_bits = 0U;
    attr.cb_mem = NULL;
    attr.cb_size = 0U;
    attr.stack_mem = NULL;
    attr.stack_size = 10240;
    attr.priority = osPriorityNormal;

    if (osThreadNew(Ssd1306TestTask, NULL, &attr) == NULL) {
        printf("[Ssd1306TestDemo] Falied to create Ssd1306TestTask!\n");
    }
}
OHOS_APP_RUN(Ssd1306TestDemo);

代码分析:

OLED屏幕分为页寻址模式,水平地址模式,垂直地址模式。

这里用的是页寻址模式,页寻址模式如下图,把整个OLED屏幕分成了八页,说白了就是把OLED的屏幕把宽平均分成了八份。比如想要在第0列第三行的开头显示一个点那就是按位来配置,0000 0100(0x08)。

通过I2C时序与SSD1306显存进行通信,把取好的字模数组中的每一项取出并点亮对应的像素点即可

实验现象

按下ESP32开发板上的EN键,即可观察到实验现象:

OLED屏显示汉字

相关推荐
网易独家音乐人Mike Zhou4 小时前
【卡尔曼滤波】数据预测Prediction观测器的理论推导及应用 C语言、Python实现(Kalman Filter)
c语言·python·单片机·物联网·算法·嵌入式·iot
Random_index6 小时前
#Uniapp篇:支持纯血鸿蒙&发布&适配&UIUI
uni-app·harmonyos
2401_882727578 小时前
BY组态-低代码web可视化组件
前端·后端·物联网·低代码·数学建模·前端框架
鸿蒙自习室9 小时前
鸿蒙多线程开发——线程间数据通信对象02
ui·harmonyos·鸿蒙
SuperHeroWu711 小时前
【HarmonyOS】鸿蒙应用接入微博分享
华为·harmonyos·鸿蒙·微博·微博分享·微博sdk集成·sdk集成
期待未来的男孩11 小时前
华为FusionCube 500-8.2.0SPC100 实施部署文档
华为
畅联云平台11 小时前
美畅物联丨智能分析,安全管控:视频汇聚平台助力智慧工地建设
人工智能·物联网
lantiandianzi12 小时前
基于单片机的多功能跑步机控制系统
单片机·嵌入式硬件
哔哥哔特商务网12 小时前
高集成的MCU方案已成电机应用趋势?
单片机·嵌入式硬件
跟着杰哥学嵌入式12 小时前
单片机进阶硬件部分_day2_项目实践
单片机·嵌入式硬件