SSD1306 OLED驱动

SSD1306 OLED驱动

参考《HAL快速入门与项目实战(基于DshanMCU-F407)_V1.0》

参考《ssd1306_datasheet.pdf》

参考 SSD1306 OLED驱动原理

参考 libDriver

介绍

SSD1306 是一款带控制器的单芯片 CMOS OLED/PLED 驱动器,适用于有机/聚合物发光二极管点阵图形显示系统。它由 128 个列和 64 个行像素点组成。该IC专为共阴极型OLED面板而设计。

SSD1306内嵌对比度控制、显示RAM和振荡器,减少了外部元件的数量和功耗。它具有256级亮度控制。数据/命令从通用 MCU 通过硬件可选的 6800/8000 系列兼容并行接口、I2C 接口或串行外设接口发送。它适用于许多紧凑型便携式应用,例如手机副显示屏、MP3 播放器和计算器等。

1

瑞士军刀开发板这里使用的是I2C接口,这里假设读者已有 I2C 通信协议的相关的概念。

2

I2C-bus Write data

I2C 总线接口允许将数据和命令写入设备。I2C 总线的写模式时序图参考如下:

3

在发送完1位的起始信号后紧跟着7位的设备地址,和1位的读写控制位。等待设备应答后,发送一个控制字节,这个控制字节的第二位会决定接下发送的数据字节是表示命令(command)还是数据(data)。等待设备响应后,紧跟着发送数据字节。博主测试过,在发送完一个控制字节后可以发送多个数据字节,如果要发送连续的数据,例如,显存缓冲区中的数据。

Write mode for I2C

1. 起始信号 (Start Condition)

主设备开始与从设备通信时,会发送一个 起始条件(Start Condition)

2. 从设备地址 (Slave Address)

主设备发送从设备地址,让从设备知道这条信息是主设备发给它的。

  • 对于 SSD1306,它的从设备地址可以是两种:
    • 0111100 (当 SA0 引脚接地,LOW)。
    • 0111101 (当 SA0 引脚接高电平,HIGH)。
  • 这是为了支持不同设备的区分。

3. 写模式 (Write Mode)

接下来,主设备需要告诉从设备它是想写入数据还是读取数据

  • 设置 R/W# 位(读/写位):
    • 写模式 = 0
    • 读模式 = 1(手册中提到的是写模式)。

驱动程序中已实现读写操作。

4. 应答信号 (Acknowledge Signal)

当从设备收到 一字节数据(如从设备地址和 R/W 位)时,它会生成应答信号,表示数据已成功接收。

5. 发送控制字节或数据字节 (Control Byte or Data Byte)

主设备发送从设备地址后,可以开始发送数据。发送的数据可以是:

  1. 控制字节 (Control Byte):用于传输命令或配置信息。

    • 包含:
      • Co 位:决定是否连续传输(0 表示仅发送数据字节)。
      • D/C# 位:决定接下来的数据字节是命令还是数据
        • 0 = 命令
        • 1 = 数据(显示用)
  2. 数据字节 (Data Byte)

    • 如果是数据字节,数据会存储在 SSD1306 的 GDDRAM(显示缓冲区)中。
    • 自动递增:每次写入一个数据字节,SSD1306 的列地址指针会自动增加 1。

6. 应答信号 (Acknowledge Signal)

  • 无论发送的是 控制字节还是数据字节,从设备每次接收数据后都会发出 应答信号

7. 停止信号 (Stop Condition)

主设备完成数据传输后,发送 停止信号 表示结束通信:

Command Decoder

该模块确定输入的字节数据将被解释为数据或命令。数据根据 D/C# 位的输入进行解释。

  • 如果 D/C# 引脚为高电平,则 D[7:0] 被解释为写入图形显示数据 RAM (GDDRAM) 的显示数据。
  • 如果为低电平,则 D[7:0] 处的输入被解释为命令。然后输入的数据将被解码并写入相应的命令寄存器。

Graphic Display Data RAM (GDDRAM)

GDDRAM 是位映射静态 RAM,保存要显示的位模式。 RAM 的大小为 128 x 64 位,RAM 分为 8 个页,从 PAGE0 到 PAGE7,用于单色 128x64 点阵显示,如下图所示。

4

当一个数据字节写入GDDRAM时,当前列同一页的所有行图像数据都被填充(即填充列地址指针指向的整列(8位))。数据位 D0 写入顶行,数据位 D7 写入底行,如下图所示。

5

COMMAND DESCRIPTIONS

重点参考手册中的命令描述章节,不过博主已经写好了驱动,直接拿来用就好,不要重复造轮子!!!

博主后面还会介绍如何移植 U8G2 图形库,提供更多的图形显示、位图显示,有能力的朋友可以尝试将动图通过转换成连续多个位图的方式在屏幕上显示,实现动画的效果。

驱动程序

driver_ssd1306.h

#ifndef DRIVER_SSD1306_H
#define DRIVER_SSD1306_H

#include "main.h"

#ifdef __cplusplus
extern "C"{
#endif

/**
 * @brief ssd1306 interface enumeration definition
 */
typedef enum
{
    SSD1306_INTERFACE_IIC = 0x00,        /**< interface iic */
    SSD1306_INTERFACE_SPI = 0x01,        /**< interface spi */
} ssd1306_interface_t;

/**
 * @brief ssd1306 address pin enumeration definition
 */
typedef enum  
{
    SSD1306_ADDR_SA0_0 = 0x78,        /**< address pin GND */
    SSD1306_ADDR_SA0_1 = 0x7A,        /**< address pin VCC */
} ssd1306_address_t;

/**
 * @brief ssd1306 memory addressing mode enumeration definition
 */
typedef enum  
{
    SSD1306_MEMORY_ADDRESSING_MODE_HORIZONTAL = 0x00,        /**< horizontal addressing mode */
    SSD1306_MEMORY_ADDRESSING_MODE_VERTICAL   = 0x01,        /**< vertical addressing mode */
    SSD1306_MEMORY_ADDRESSING_MODE_PAGE       = 0x02,        /**< page addressing mode */
} ssd1306_memory_addressing_mode_t;

/**
 * @brief ssd1306 fade blinking mode enumeration definition
 */
typedef enum  
{
    SSD1306_FADE_BLINKING_MODE_DISABLE  = 0x00,        /**< disable fade blinking mode */
    SSD1306_FADE_BLINKING_MODE_FADE_OUT = 0x02,        /**< fade out fade blinking mode */
    SSD1306_FADE_BLINKING_MODE_BLINKING = 0x03,        /**< blinking fade blinking mode */
} ssd1306_fade_blinking_mode_t;

/**
 * @brief ssd1306 scroll frame enumeration definition
 */
typedef enum  
{
    SSD1306_SCROLL_FRAME_2    = 0x07,        /**< scroll frame 2 */
    SSD1306_SCROLL_FRAME_3    = 0x04,        /**< scroll frame 3 */
    SSD1306_SCROLL_FRAME_4    = 0x05,        /**< scroll frame 4 */
    SSD1306_SCROLL_FRAME_5    = 0x00,        /**< scroll frame 5 */
    SSD1306_SCROLL_FRAME_25   = 0x06,        /**< scroll frame 25 */
    SSD1306_SCROLL_FRAME_64   = 0x01,        /**< scroll frame 64 */
    SSD1306_SCROLL_FRAME_128  = 0x02,        /**< scroll frame 128 */
    SSD1306_SCROLL_FRAME_256  = 0x03,        /**< scroll frame 256 */
} ssd1306_scroll_frame_t;

/**
 * @brief ssd1306 charge pump enumeration definition
 */
typedef enum  
{
    SSD1306_CHARGE_PUMP_DISABLE = 0x00,        /**< charge pump disable */
    SSD1306_CHARGE_PUMP_ENABLE  = 0x01,        /**< charge pump enable */
} ssd1306_charge_pump_t;

/**
 * @brief ssd1306 segment column remap enumeration definition
 */
typedef enum  
{
    SSD1306_SEGMENT_COLUMN_ADDRESS_0   = 0x00,        /**< segment column remap address 0 */
    SSD1306_SEGMENT_COLUMN_ADDRESS_127 = 0x01,        /**< segment column remap address 127 */
} ssd1306_segment_column_remap_t;

/**
 * @brief ssd1306 entire display enumeration definition
 */
typedef enum  
{
    SSD1306_ENTIRE_DISPLAY_OFF = 0x00,        /**< entire display off */
    SSD1306_ENTIRE_DISPLAY_ON  = 0x01,        /**< entire display on */
} ssd1306_entire_display_t;

/**
 * @brief ssd1306 display mode enumeration definition
 */
typedef enum  
{
    SSD1306_DISPLAY_MODE_NORMAL  = 0x00,        /**< display mode normal */
    SSD1306_DISPLAY_MODE_INVERSE = 0x01,        /**< display mode inverse */
} ssd1306_display_mode_t;

/**
 * @brief ssd1306 display enumeration definition
 */
typedef enum  
{
    SSD1306_DISPLAY_OFF = 0x00,        /**< close display */
    SSD1306_DISPLAY_ON  = 0x01,        /**< open display */
} ssd1306_display_t;

/**
 * @brief ssd1306 scan direction enumeration definition
 */
typedef enum  
{
    SSD1306_SCAN_DIRECTION_COM0_START   = 0x00,        /**< scan direction com 0 start */
    SSD1306_SCAN_DIRECTION_COMN_1_START = 0x01,        /**< scan direction com N-1 start */
} ssd1306_scan_direction_t;

/**
 * @brief ssd1306 zoom in enumeration definition
 */
typedef enum  
{
    SSD1306_ZOOM_IN_DISABLE = 0x00,        /**< disable zoom in */
    SSD1306_ZOOM_IN_ENABLE  = 0x01,        /**< enable zoom in */
} ssd1306_zoom_in_t;

/**
 * @brief ssd1306 pin conf enumeration definition
 */
typedef enum  
{
    SSD1306_PIN_CONF_SEQUENTIAL  = 0x00,        /**< pin conf sequential */
    SSD1306_PIN_CONF_ALTERNATIVE = 0x01,        /**< pin conf alternative */
} ssd1306_pin_conf_t;

/**
 * @brief ssd1306 left right remap enumeration definition
 */
typedef enum  
{
    SSD1306_LEFT_RIGHT_REMAP_DISABLE = 0x00,        /**< disable left right remap */
    SSD1306_LEFT_RIGHT_REMAP_ENABLE  = 0x01,        /**< enable left right remap */
} ssd1306_left_right_remap_t;

/**
 * @brief ssd1306 deselect level enumeration definition
 */
typedef enum  
{
    SSD1306_DESELECT_LEVEL_0P65 = 0x00,        /**< deselect level 0.65 */
    SSD1306_DESELECT_LEVEL_0P77 = 0x02,        /**< deselect level 0.77 */
    SSD1306_DESELECT_LEVEL_0P83 = 0x03,        /**< deselect level 0.83 */
} ssd1306_deselect_level_t;

/**
 * @brief ssd1306 font enumeration definition
 */
typedef enum  
{
    SSD1306_FONT_12 = 0x0C,        /**< font 12 */
    SSD1306_FONT_16 = 0x10,        /**< font 16 */
    SSD1306_FONT_24 = 0x18,        /**< font 24 */
} ssd1306_font_t;

/**
 * @brief ssd1306 handle structure definition
 */
typedef struct ssd1306_handle_s
{
    uint8_t (*iic_init)(void);                                                          /**< point to an iic_init function address */
    uint8_t (*iic_deinit)(void);                                                        /**< point to an iic_deinit function address */
    uint8_t (*iic_write)(uint8_t addr, uint8_t reg, uint8_t *buf, uint16_t len);        /**< point to an iic_write function address */
    uint8_t (*spi_init)(void);                                                          /**< point to a spi_init function address */
    uint8_t (*spi_deinit)(void);                                                        /**< point to a spi_deinit function address */
    uint8_t (*spi_write_cmd)(uint8_t *buf, uint16_t len);                               /**< point to a spi_write_cmd function address */
    uint8_t (*spi_cmd_data_gpio_init)(void);                                            /**< point to a spi_cmd_data_gpio_init function address */
    uint8_t (*spi_cmd_data_gpio_deinit)(void);                                          /**< point to a spi_cmd_data_gpio_deinit function address */
    uint8_t (*spi_cmd_data_gpio_write)(uint8_t value);                                  /**< point to a spi_cmd_data_gpio_write function address */
    uint8_t (*reset_gpio_init)(void);                                                   /**< point to a reset_gpio_init function address */
    uint8_t (*reset_gpio_deinit)(void);                                                 /**< point to a reset_gpio_deinit function address */
    uint8_t (*reset_gpio_write)(uint8_t value);                                         /**< point to a reset_gpio_write function address */
    void (*debug_print)(const char *const fmt, ...);                                    /**< point to a debug_print function address */
    void (*delay_ms)(uint32_t ms);                                                      /**< point to a delay_ms function address */
    uint8_t inited;                                                                     /**< inited flag */
    uint8_t iic_addr;                                                                   /**< iic address */
    uint8_t iic_spi;                                                                    /**< iic spi type */
    uint8_t gram[128][8];                                                               /**< gram buffer */
} ssd1306_handle_t;

/**
 * @brief ssd1306 information structure definition
 */
typedef struct ssd1306_info_s
{
    char chip_name[32];                /**< chip name */
    char manufacturer_name[32];        /**< manufacturer name */
    char interface[8];                 /**< chip interface name */
    float supply_voltage_min_v;        /**< chip min supply voltage */
    float supply_voltage_max_v;        /**< chip max supply voltage */
    float max_current_ma;              /**< chip max current */
    float temperature_min;             /**< chip min operating temperature */
    float temperature_max;             /**< chip max operating temperature */
    uint32_t driver_version;           /**< driver version */
} ssd1306_info_t;

/**
 * @brief     initialize ssd1306_handle_t structure
 * @param[in] HANDLE points to an ssd1306 handle structure
 * @param[in] STRUCTURE is ssd1306_handle_t
 * @note      none
 */
#define DRIVER_SSD1306_LINK_INIT(HANDLE, STRUCTURE)                     memset(HANDLE, 0, sizeof(STRUCTURE))

/**
 * @brief     link iic_init function
 * @param[in] HANDLE points to an ssd1306 handle structure
 * @param[in] FUC points to an iic_init function address
 * @note      none
 */
#define DRIVER_SSD1306_LINK_IIC_INIT(HANDLE, FUC)                      (HANDLE)->iic_init = FUC

/**
 * @brief     link iic_deinit function
 * @param[in] HANDLE points to an ssd1306 handle structure
 * @param[in] FUC points to an iic_deinit function address
 * @note      none
 */
#define DRIVER_SSD1306_LINK_IIC_DEINIT(HANDLE, FUC)                    (HANDLE)->iic_deinit = FUC

/**
 * @brief     link iic_write function
 * @param[in] HANDLE points to an ssd1306 handle structure
 * @param[in] FUC points to an iic_write function address
 * @note      none
 */
#define DRIVER_SSD1306_LINK_IIC_WRITE(HANDLE, FUC)                     (HANDLE)->iic_write = FUC

/**
 * @brief     link spi_init function
 * @param[in] HANDLE points to an ssd1306 handle structure
 * @param[in] FUC points to a spi_init function address
 * @note      none
 */
#define DRIVER_SSD1306_LINK_SPI_INIT(HANDLE, FUC)                      (HANDLE)->spi_init = FUC

/**
 * @brief     link spi_deinit function
 * @param[in] HANDLE points to an ssd1306 handle structure
 * @param[in] FUC points to a spi_deinit function address
 * @note      none
 */
#define DRIVER_SSD1306_LINK_SPI_DEINIT(HANDLE, FUC)                    (HANDLE)->spi_deinit = FUC

/**
 * @brief     link spi_write_cmd function
 * @param[in] HANDLE points to an ssd1306 handle structure
 * @param[in] FUC points to a spi_write_cmd function address
 * @note      none
 */
#define DRIVER_SSD1306_LINK_SPI_WRITE_COMMAND(HANDLE, FUC)             (HANDLE)->spi_write_cmd = FUC

/**
 * @brief     link spi_cmd_data_gpio_init function
 * @param[in] HANDLE points to an ssd1306 handle structure
 * @param[in] FUC points to a spi_cmd_data_gpio_init function address
 * @note      none
 */
#define DRIVER_SSD1306_LINK_SPI_COMMAND_DATA_GPIO_INIT(HANDLE, FUC)    (HANDLE)->spi_cmd_data_gpio_init = FUC

/**
 * @brief     link spi_cmd_data_gpio_deinit function
 * @param[in] HANDLE points to an ssd1306 handle structure
 * @param[in] FUC points to a spi_cmd_data_gpio_deinit function address
 * @note      none
 */
#define DRIVER_SSD1306_LINK_SPI_COMMAND_DATA_GPIO_DEINIT(HANDLE, FUC)  (HANDLE)->spi_cmd_data_gpio_deinit = FUC

/**
 * @brief     link spi_cmd_data_gpio_write function
 * @param[in] HANDLE points to an ssd1306 handle structure
 * @param[in] FUC points to a spi_cmd_data_gpio_write function address
 * @note      none
 */
#define DRIVER_SSD1306_LINK_SPI_COMMAND_DATA_GPIO_WRITE(HANDLE, FUC)   (HANDLE)->spi_cmd_data_gpio_write = FUC

/**
 * @brief     link reset_gpio_init function
 * @param[in] HANDLE points to an ssd1306 handle structure
 * @param[in] FUC points to a reset_gpio_init function address
 * @note      none
 */
#define DRIVER_SSD1306_LINK_RESET_GPIO_INIT(HANDLE, FUC)               (HANDLE)->reset_gpio_init = FUC

/**
 * @brief     link reset_gpio_deinit function
 * @param[in] HANDLE points to an ssd1306 handle structure
 * @param[in] FUC points to a reset_gpio_deinit function address
 * @note      none
 */
#define DRIVER_SSD1306_LINK_RESET_GPIO_DEINIT(HANDLE, FUC)             (HANDLE)->reset_gpio_deinit = FUC

/**
 * @brief     link reset_gpio_write function
 * @param[in] HANDLE points to an ssd1306 handle structure
 * @param[in] FUC points to a reset_gpio_write function address
 * @note      none
 */
#define DRIVER_SSD1306_LINK_RESET_GPIO_WRITE(HANDLE, FUC)              (HANDLE)->reset_gpio_write = FUC

/**
 * @brief     link delay_ms function
 * @param[in] HANDLE points to an ssd1306 handle structure
 * @param[in] FUC points to a delay_ms function address
 * @note      none
 */
#define DRIVER_SSD1306_LINK_DELAY_MS(HANDLE, FUC)                      (HANDLE)->delay_ms = FUC

/**
 * @brief     link debug_print function
 * @param[in] HANDLE points to an ssd1306 handle structure
 * @param[in] FUC points to a debug_print function address
 * @note      none
 */
#define DRIVER_SSD1306_LINK_DEBUG_PRINT(HANDLE, FUC)                   (HANDLE)->debug_print = FUC

/**
 * @brief      get chip's information
 * @param[out] *info points to an ssd1306 info structure
 * @return     status code
 *             - 0 success
 *             - 2 handle is NULL
 * @note       none
 */
uint8_t ssd1306_info(ssd1306_info_t *info);

/**
 * @brief     set the chip interface
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] interface is the chip interface
 * @return    status code
 *            - 0 success
 *            - 2 handle is NULL
 * @note      none
 */
uint8_t ssd1306_set_interface(ssd1306_handle_t *handle, ssd1306_interface_t interface);

/**
 * @brief      get the chip interface
 * @param[in]  *handle points to an ssd1306 handle structure
 * @param[out] *interface points to a chip interface buffer
 * @return     status code
 *             - 0 success
 *             - 2 handle is NULL
 * @note       none
 */
uint8_t ssd1306_get_interface(ssd1306_handle_t *handle, ssd1306_interface_t *interface);

/**
 * @brief     set the chip iic address
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] addr_pin is the iic address
 * @return    status code
 *            - 0 success
 *            - 2 handle is NULL
 * @note      none
 */
uint8_t ssd1306_set_addr_pin(ssd1306_handle_t *handle, ssd1306_address_t addr_pin);

/**
 * @brief      get the chip iic address
 * @param[in]  *handle points to an ssd1306 handle structure
 * @param[out] *addr_pin points to an iic address buffer
 * @return     status code
 *             - 0 success
 *             - 2 handle is NULL
 * @note       none
 */
uint8_t ssd1306_get_addr_pin(ssd1306_handle_t *handle, ssd1306_address_t *addr_pin);

/**
 * @brief     initialize the chip
 * @param[in] *handle points to an ssd1306 handle structure
 * @return    status code
 *            - 0 success
 *            - 1 iic or spi initialization failed
 *            - 2 handle is NULL
 *            - 3 linked functions is NULL
 *            - 4 reset failed
 *            - 5 command && data init failed
 *            - 6 interface param is invalid
 * @note      none
 */
uint8_t ssd1306_init(ssd1306_handle_t *handle);

/**
 * @brief     close the chip
 * @param[in] *handle points to an ssd1306 handle structure
 * @return    status code
 *            - 0 success
 *            - 1 iic or spi deinit failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 *            - 4 power down failed
 *            - 5 reset gpio deinit failed
 *            - 6 command && data deinit failed
 *            - 7 interface param is invalid
 * @note      none
 */
uint8_t ssd1306_deinit(ssd1306_handle_t *handle);

/**
 * @brief     clear the screen
 * @param[in] *handle points to an ssd1306 handle structure
 * @return    status code
 *            - 0 success
 *            - 1 clear failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 * @note      none
 */
uint8_t ssd1306_clear(ssd1306_handle_t *handle);

/**
 * @brief     update the gram data
 * @param[in] *handle points to an ssd1306 handle structure
 * @return    status code
 *            - 0 success
 *            - 1 gram update failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 * @note      none
 */
uint8_t ssd1306_gram_update(ssd1306_handle_t *handle);

/**
 * @brief     write a point
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] x is the coordinate x
 * @param[in] y is the coordinate y
 * @param[in] data is the write data
 * @return    status code
 *            - 0 success
 *            - 1 write point failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 *            - 4 x or y is invalid
 * @note      none
 */
uint8_t ssd1306_write_point(ssd1306_handle_t *handle, uint8_t x, uint8_t y, uint8_t data);

/**
 * @brief      read a point
 * @param[in]  *handle points to an ssd1306 handle structure
 * @param[in]  x is the coordinate x
 * @param[in]  y is the coordinate y
 * @param[out] *data points to a data buffer
 * @return     status code
 *             - 0 success
 *             - 1 read point failed
 *             - 2 handle is NULL
 *             - 3 handle is not initialized
 *             - 4 x or y is invalid
 * @note       none
 */
uint8_t ssd1306_read_point(ssd1306_handle_t *handle, uint8_t x, uint8_t y, uint8_t *data);

/**
 * @brief     write a point in the gram
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] x is the coordinate x
 * @param[in] y is the coordinate y
 * @param[in] data is the write data
 * @return    status code
 *            - 0 success
 *            - 1 gram write point failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 *            - 4 x or y is invalid
 * @note      none
 */
uint8_t ssd1306_gram_write_point(ssd1306_handle_t *handle, uint8_t x, uint8_t y, uint8_t data);

/**
 * @brief      read a point from the gram
 * @param[in]  *handle points to an ssd1306 handle structure
 * @param[in]  x is the coordinate x
 * @param[in]  y is the coordinate y
 * @param[out] *data points to a data buffer
 * @return     status code
 *             - 0 success
 *             - 1 gram read point failed
 *             - 2 handle is NULL
 *             - 3 handle is not initialized
 *             - 4 x or y is invalid
 * @note       none
 */
uint8_t ssd1306_gram_read_point(ssd1306_handle_t *handle, uint8_t x, uint8_t y, uint8_t *data);

/**
 * @brief     draw a string in the gram
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] x is the coordinate x
 * @param[in] y is the coordinate y
 * @param[in] *str points to a write string address
 * @param[in] len is the length of the string
 * @param[in] color is the display color
 * @param[in] font is the display font size
 * @return    status code
 *            - 0 success
 *            - 1 gram write string failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 *            - 4 x or y is invalid
 * @note      none
 */
uint8_t ssd1306_gram_write_string(ssd1306_handle_t *handle, uint8_t x, uint8_t y, char *str, uint16_t len, uint8_t color, ssd1306_font_t font);

/**
 * @brief     fill a rectangle in the gram
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] left is the left coordinate x
 * @param[in] top is the top coordinate y
 * @param[in] right is the right coordinate x
 * @param[in] bottom is the bottom coordinate y
 * @param[in] color is the display color
 * @return    status code
 *            - 0 success
 *            - 1 gram fill rect failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 *            - 4 left or top is invalid
 *            - 5 right or bottom is invalid
 *            - 6 left > right or top > bottom
 * @note      none
 */
uint8_t ssd1306_gram_fill_rect(ssd1306_handle_t *handle, uint8_t left, uint8_t top, uint8_t right, uint8_t bottom, uint8_t color);

/**
 * @brief     draw a picture in the gram
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] left is the left coordinate x
 * @param[in] top is the top coordinate y
 * @param[in] right is the right coordinate x
 * @param[in] bottom is the bottom coordinate y
 * @param[in] *img points to an image buffer
 * @return    status code
 *            - 0 success
 *            - 1 gram draw picture failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 *            - 4 left or top is invalid
 *            - 5 right or bottom is invalid
 *            - 6 left > right or top > bottom
 * @note      none
 */
uint8_t ssd1306_gram_draw_picture(ssd1306_handle_t *handle, uint8_t left, uint8_t top, uint8_t right, uint8_t bottom, uint8_t *img);

/**
 * @brief     set the low column start address
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] addr is the low column start address
 * @return    status code
 *            - 0 success
 *            - 1 set low column start address failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 *            - 4 addr is invalid
 * @note      addr <= 0xF
 */
uint8_t ssd1306_set_low_column_start_address(ssd1306_handle_t *handle, uint8_t addr);

/**
 * @brief     set the high column start address
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] addr is the high column start address
 * @return    status code
 *            - 0 success
 *            - 1 set high column start address failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 *            - 4 addr is invalid
 * @note      addr <= 0xF
 */
uint8_t ssd1306_set_high_column_start_address(ssd1306_handle_t *handle, uint8_t addr);

/**
 * @brief     set the memory addressing mode
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] mode is the memory addressing mode
 * @return    status code
 *            - 0 success
 *            - 1 set memory addressing mode failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 * @note      none
 */
uint8_t ssd1306_set_memory_addressing_mode(ssd1306_handle_t *handle, ssd1306_memory_addressing_mode_t mode);

/**
 * @brief     set the column address range
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] start_addr is the column start address
 * @param[in] end_addr is the column end address
 * @return    status code
 *            - 0 success
 *            - 1 set column address range failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 *            - 4 start addr is invalid
 *            - 5 end addr is invalid
 * @note      start addr and end addr can't be over 0x7F
 */
uint8_t ssd1306_set_column_address_range(ssd1306_handle_t *handle, uint8_t start_addr, uint8_t end_addr);

/**
 * @brief     set the page address range
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] start_addr is the page start address
 * @param[in] end_addr is the page end address
 * @return    status code
 *            - 0 success
 *            - 1 set page address range failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 *            - 4 start addr is invalid
 *            - 5 end addr is invalid
 * @note      start addr and end addr can't be over 0x07
 */
uint8_t ssd1306_set_page_address_range(ssd1306_handle_t *handle, uint8_t start_addr, uint8_t end_addr);

/**
 * @brief     set the fade blinking mode
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] mode is the fade blinking mode
 * @param[in] frames is the fade or blinking frames
 * @return    status code
 *            - 0 success
 *            - 1 set fade blinking mode failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 *            - 4 frames is invalid
 * @note      frames max is 0x0F and div is (frames + 1) * 8
 */
uint8_t ssd1306_set_fade_blinking_mode(ssd1306_handle_t *handle, ssd1306_fade_blinking_mode_t mode, uint8_t frames);

/**
 * @brief     set the right horizontal scroll
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] start_page_addr is the start page address
 * @param[in] end_page_addr is the end page address
 * @param[in] frames is the scroll frames
 * @return    status code
 *            - 0 success
 *            - 1 set right horizontal scroll failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 *            - 4 start page addr is invalid
 *            - 5 end page addr is invalid
 * @note       start_page_addr <= 0x07, end_page_addr <= 0x07
 */
uint8_t ssd1306_set_right_horizontal_scroll(ssd1306_handle_t *handle, uint8_t start_page_addr, uint8_t end_page_addr, 
                                            ssd1306_scroll_frame_t frames);

/**
 * @brief     set the left horizontal scroll
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] start_page_addr is the start page address
 * @param[in] end_page_addr is the end page address
 * @param[in] frames is the scroll frames
 * @return    status code
 *            - 0 success
 *            - 1 set left horizontal scroll failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 *            - 4 start_page_addr is invalid
 *            - 5 end_page_addr is invalid
 * @note      start_page_addr <= 0x07, end_page_addr <= 0x07
 */
uint8_t ssd1306_set_left_horizontal_scroll(ssd1306_handle_t *handle, uint8_t start_page_addr, uint8_t end_page_addr, 
                                           ssd1306_scroll_frame_t frames);

/**
 * @brief     set the vertical right horizontal scroll
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] start_page_addr is the start page address
 * @param[in] end_page_addr is the end page address
 * @param[in] rows is the row address
 * @param[in] frames is the scroll frames
 * @return    status code
 *            - 0 success
 *            - 1 set vertical right horizontal scroll failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 *            - 4 start_page_addr is invalid
 *            - 5 end_page_addr is invalid
 *            - 6 rows is invalid
 * @note      start_page_addr <= 0x07, end_page_addr <= 0x07, rows <= 0x3F
 */
uint8_t ssd1306_set_vertical_right_horizontal_scroll(ssd1306_handle_t *handle, uint8_t start_page_addr, uint8_t end_page_addr, 
                                                     uint8_t rows, ssd1306_scroll_frame_t frames);

/**
 * @brief     set the vertical left horizontal scroll
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] start_page_addr is the start page address
 * @param[in] end_page_addr is the end page address
 * @param[in] rows is the row address
 * @param[in] frames is the scroll frames
 * @return    status code
 *            - 0 success
 *            - 1 set vertical left horizontal scroll failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 *            - 4 start_page_addr is invalid
 *            - 5 end_page_addr is invalid
 *            - 6 rows is invalid
 * @note      start_page_addr <= 0x07, end_page_addr <= 0x07, rows <= 0x3F
 */
uint8_t ssd1306_set_vertical_left_horizontal_scroll(ssd1306_handle_t *handle, uint8_t start_page_addr, uint8_t end_page_addr, 
                                                    uint8_t rows, ssd1306_scroll_frame_t frames);

/**
 * @brief     deactivate the scroll
 * @param[in] *handle points to an ssd1306 handle structure
 * @return    status code
 *            - 0 success
 *            - 1 deactivate scroll failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 * @note      none
 */
uint8_t ssd1306_deactivate_scroll(ssd1306_handle_t *handle);

/**
 * @brief     activate the scroll
 * @param[in] *handle points to an ssd1306 handle structure
 * @return    status code
 *            - 0 success
 *            - 1 activate scroll failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 * @note      none
 */
uint8_t ssd1306_activate_scroll(ssd1306_handle_t *handle);

/**
 * @brief     set the display start line
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] l is the start line
 * @return    status code
 *            - 0 success
 *            - 1 set display start line failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 *            - 4 line is invalid
 * @note      line <= 0x3F
 */
uint8_t ssd1306_set_display_start_line(ssd1306_handle_t *handle, uint8_t l);

/**
 * @brief     set the display contrast
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] contrast is the display contrast
 * @return    status code
 *            - 0 success
 *            - 1 set contrast failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 * @note      none
 */
uint8_t ssd1306_set_contrast(ssd1306_handle_t *handle, uint8_t contrast);

/**
 * @brief     enable or disable the charge pump
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] enable is a bool value
 * @return    status code
 *            - 0 success
 *            - 1 set charge pump failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 * @note      none
 */
uint8_t ssd1306_set_charge_pump(ssd1306_handle_t *handle, ssd1306_charge_pump_t enable);

/**
 * @brief     set the segment remap
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] remap is the segment remap param
 * @return    status code
 *            - 0 success
 *            - 1 set segment remap failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 * @note      none
 */
uint8_t ssd1306_set_segment_remap(ssd1306_handle_t *handle, ssd1306_segment_column_remap_t remap);

/**
 * @brief     set the vertical scroll area
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] start_row is the start row
 * @param[in] end_row is the end row
 * @return    status code
 *            - 0 success
 *            - 1 set vertical scroll area failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 *            - 4 start_row is invalid
 *            - 5 end_row is invalid
 *            - 6 end_row > start_row
 * @note      start_row <= 0x3F, end_row <= 0x7F, start_row >= end_row
 */
uint8_t ssd1306_set_vertical_scroll_area(ssd1306_handle_t *handle, uint8_t start_row, uint8_t end_row);

/**
 * @brief     enable or disable the entire display
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] enable is a bool value
 * @return    status code
 *            - 0 success
 *            - 1 set entire display failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 * @note      none
 */
uint8_t ssd1306_set_entire_display(ssd1306_handle_t *handle, ssd1306_entire_display_t enable);

/**
 * @brief     set the display mode
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] mode is the display mode
 * @return    status code
 *            - 0 success
 *            - 1 set display mode failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 * @note      none
 */
uint8_t ssd1306_set_display_mode(ssd1306_handle_t *handle, ssd1306_display_mode_t mode);

/**
 * @brief     set the multiplex ratio
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] multiplex is the multiplex ratio
 * @return    status code
 *            - 0 success
 *            - 1 set multiplex ratio failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 *            - 4 multiplex is too small
 *            - 5 multiplex is too large
 * @note      multiplex must be over 0x0E and less than 0x40
 */
uint8_t ssd1306_set_multiplex_ratio(ssd1306_handle_t *handle, uint8_t multiplex);

/**
 * @brief     enable or disable the display
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] on_off is a bool value
 * @return    status code
 *            - 0 success
 *            - 1 set display failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 * @note      none
 */
uint8_t ssd1306_set_display(ssd1306_handle_t *handle, ssd1306_display_t on_off);

/**
 * @brief     set the page address
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] addr is the page address
 * @return    status code
 *            - 0 success
 *            - 1 set page address failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 *            - 4 addr is invalid
 * @note      addr <= 0x07
 */
uint8_t ssd1306_set_page_address(ssd1306_handle_t *handle, uint8_t addr);

/**
 * @brief     set the scan direction
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] dir is the scan direction
 * @return    status code
 *            - 0 success
 *            - 1 set scan direction failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 * @note      none
 */
uint8_t ssd1306_set_scan_direction(ssd1306_handle_t *handle, ssd1306_scan_direction_t dir);

/**
 * @brief     set the display offset
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] offset is the display offset
 * @return    status code
 *            - 0 success
 *            - 1 set display offset failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 *            - 4 offset is invalid
 * @note      offset <= 0x3F
 */
uint8_t ssd1306_set_display_offset(ssd1306_handle_t *handle, uint8_t offset);

/**
 * @brief     set the display clock
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] oscillator_frequency is the oscillator frequency
 * @param[in] clock_divide is the clock divide
 * @return    status code
 *            - 0 success
 *            - 1 set display clock failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 *            - 4 oscillator frequency is invalid
 *            - 5 clock divide is invalid
 * @note      oscillator_frequency <= 0x0F, clock_divide <= 0x0F
 */
uint8_t ssd1306_set_display_clock(ssd1306_handle_t *handle, uint8_t oscillator_frequency, uint8_t clock_divide);

/**
 * @brief     set the display zoom in
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] zoom is the display zoom in
 * @return    status code
 *            - 0 success
 *            - 1 set zoom in failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 * @note      none
 */
uint8_t ssd1306_set_zoom_in(ssd1306_handle_t *handle, ssd1306_zoom_in_t zoom);

/**
 * @brief     set the pre charge period
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] phase1_period is the phase1 period
 * @param[in] phase2_period is the phase2 period
 * @return    status code
 *            - 0 success
 *            - 1 set pre charge period failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 *            - 4 phase1 period is invalid
 *            - 5 phase2 period is invalid
 * @note      phase1_period <= 0x0F, phase2_period <= 0x0F
 */
uint8_t ssd1306_set_precharge_period(ssd1306_handle_t *handle, uint8_t phase1_period, uint8_t phase2_period);

/**
 * @brief     set the hardware com pins
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] conf is the pin conf
 * @param[in] remap is the left right remap
 * @return    status code
 *            - 0 success
 *            - 1 set com pins hardware conf failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 * @note      none
 */
uint8_t ssd1306_set_com_pins_hardware_conf(ssd1306_handle_t *handle, ssd1306_pin_conf_t conf, ssd1306_left_right_remap_t remap);

/**
 * @brief     set the deselect level
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] level is the deselect level
 * @return    status code
 *            - 0 success
 *            - 1 set deselect level failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 * @note      none
 */
uint8_t ssd1306_set_deselect_level(ssd1306_handle_t *handle, ssd1306_deselect_level_t level);

/**
 * @}
 */

/**
 * @defgroup ssd1306_extend_driver ssd1306 extend driver function
 * @brief    ssd1306 extend driver modules
 * @ingroup  ssd1306_driver
 * @{
 */

/**
 * @brief     write the register command
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] *buf points to a data buffer
 * @param[in] len is the data length
 * @return    status code
 *            - 0 success
 *            - 1 write failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 * @note      none
 */
uint8_t ssd1306_write_cmd(ssd1306_handle_t *handle, uint8_t *buf, uint8_t len);

/**
 * @brief     write the register data
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] *buf points to a data buffer
 * @param[in] len is the data length
 * @return    status code
 *            - 0 success
 *            - 1 write failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 * @note      none
 */
uint8_t ssd1306_write_data(ssd1306_handle_t *handle, uint8_t *buf, uint8_t len);

/**
 * @}
 */

/**
 * @}
 */

#ifdef __cplusplus
}
#endif

#endif

driver_ssd1306.c

#include "driver_ssd1306.h"
#include "driver_ssd1306_font.h"

/**
 * @brief chip information definition
 */
#define CHIP_NAME                 "Solomon Systech SSD1306"        /**< chip name */
#define MANUFACTURER_NAME         "Solomon Systech"                /**< manufacturer name */
#define SUPPLY_VOLTAGE_MIN        1.65f                            /**< chip min supply voltage */
#define SUPPLY_VOLTAGE_MAX        3.3f                             /**< chip max supply voltage */
#define MAX_CURRENT               0.78f                            /**< chip max current */
#define TEMPERATURE_MIN           -40.0f                           /**< chip min operating temperature */
#define TEMPERATURE_MAX           85.0f                            /**< chip max operating temperature */
#define DRIVER_VERSION            2000                             /**< driver version */

/**
 * @brief chip command data definition
 */
#define SSD1306_CMD          0        /**< command */
#define SSD1306_DATA         1        /**< data */

/**
 * @brief chip command definition
 */
#define SSD1306_CMD_LOWER_COLUMN_START_ADDRESS              0x00        /**< command lower column start address */
#define SSD1306_CMD_HIGHER_COLUMN_START_ADDRESS             0x10        /**< command higher column start address */
#define SSD1306_CMD_MEMORY_ADDRESSING_MODE                  0x20        /**< command memory addressing mode */
#define SSD1306_CMD_SET_COLUMN_ADDRESS                      0x21        /**< command set column address */
#define SSD1306_CMD_SET_PAGE_ADDRESS                        0x22        /**< command set page address */
#define SSD1306_CMD_SET_FADE_OUT_AND_BLINKING               0x23        /**< command set fade out and blinking */
#define SSD1306_CMD_RIGHT_HORIZONTAL_SCROLL                 0x26        /**< command right horizontal scroll */
#define SSD1306_CMD_LEFT_HORIZONTAL_SCROLL                  0x27        /**< command left horizontal scroll */
#define SSD1306_CMD_VERTICAL_RIGHT_HORIZONTAL_SCROLL        0x29        /**< command vertical right horizontal scroll */
#define SSD1306_CMD_VERTICAL_LEFT_HORIZONTAL_SCROLL         0x2A        /**< command vertical left horizontal scroll */
#define SSD1306_CMD_DEACTIVATE_SCROLL                       0x2E        /**< command deactivate scroll */
#define SSD1306_CMD_ACTIVATE_SCROLL                         0x2F        /**< command activate scroll */
#define SSD1306_CMD_DISPLAY_START_LINE                      0x40        /**< command display start line */
#define SSD1306_CMD_CONTRAST_CONTROL                        0x81        /**< command contrast control */
#define SSD1306_CMD_CHARGE_PUMP_SETTING                     0x8D        /**< command charge pump setting */
#define SSD1306_CMD_COLUMN_0_MAPPED_TO_SEG0                 0xA0        /**< command column 0 mapped to seg 0 */
#define SSD1306_CMD_COLUMN_127_MAPPED_TO_SEG0               0xA1        /**< command column 127 mapped to seg 0 */
#define SSD1306_CMD_VERTICAL_SCROLL_AREA                    0xA3        /**< command vertical scroll area */
#define SSD1306_CMD_ENTIRE_DISPLAY_OFF                      0xA4        /**< command entire display off */ 
#define SSD1306_CMD_ENTIRE_DISPLAY_ON                       0xA5        /**< command entire display on */ 
#define SSD1306_CMD_NORMAL_DISPLAY                          0xA6        /**< command normal display */ 
#define SSD1306_CMD_INVERSE_DISPLAY                         0xA7        /**< command inverse display */ 
#define SSD1306_CMD_MULTIPLEX_RATIO                         0xA8        /**< command multiplex ratio */ 
#define SSD1306_CMD_DISPLAY_OFF                             0xAE        /**< command display off */ 
#define SSD1306_CMD_DISPLAY_ON                              0xAF        /**< command display on */ 
#define SSD1306_CMD_PAGE_ADDR                               0xB0        /**< command page address */ 
#define SSD1306_CMD_SCAN_DIRECTION_COM0_START               0xC0        /**< command scan direction com 0 start */ 
#define SSD1306_CMD_SCAN_DIRECTION_COMN_1_START             0xC8        /**< command scan direction com n-1 start */ 
#define SSD1306_CMD_DISPLAY_OFFSET                          0xD3        /**< command display offset */ 
#define SSD1306_CMD_DISPLAY_CLOCK_DIVIDE                    0xD5        /**< command display clock divide */ 
#define SSD1306_CMD_SET_ZOOM_IN                             0xD6        /**< command set zoom in */ 
#define SSD1306_CMD_PRE_CHARGE_PERIOD                       0xD9        /**< command pre charge period */ 
#define SSD1306_CMD_COM_PINS_CONF                           0xDA        /**< command com pins conf */ 
#define SSD1306_CMD_COMH_DESLECT_LEVEL                      0xDB        /**< command comh deslect level */ 
#define SSD1306_CMD_NOP                                     0xE3        /**< command nop */ 

/**
 * @brief     write one byte
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] data is the write data
 * @param[in] cmd is the command or data type
 * @return    status code
 *            - 0 success
 *            - 1 write failed
 * @note      none
 */
static uint8_t a_ssd1306_write_byte(ssd1306_handle_t *handle, uint8_t data, uint8_t cmd)
{
    uint8_t res;
    
    if (handle->iic_spi == SSD1306_INTERFACE_IIC)                              /* if iic */
    {
        if (cmd != 0)                                                          /* if data */
        {
            if (handle->iic_write(handle->iic_addr, 0x40, &data, 1) != 0)      /* write data */
            {
                return 1;                                                      /* return error */
            }
            else
            {
                return 0;                                                      /* success return 0 */
            }
        }
        else
        {
            if (handle->iic_write(handle->iic_addr, 0x00, &data, 1) != 0)      /* write command */
            {
                return 1;                                                      /* return error */
            }
            else
            {
                return 0;                                                      /* success return 0 */
            }
        }
    }
    else if (handle->iic_spi == SSD1306_INTERFACE_SPI)                         /* if spi */
    {
        res = handle->spi_cmd_data_gpio_write(cmd);                            /* write data command */
        if (res != 0)                                                          /* check error */
        {
            return 1;                                                          /* return error */
        }
        
        if (handle->spi_write_cmd(&data, 1) != 0)                              /* write command */
        {
            return 1;                                                          /* return error */
        }
        else
        {
            return 0;                                                          /* success return 0 */
        }
    }
    else
    {
        return 1;                                                              /* return error */
    }
}

/**
 * @brief     write multiple bytes
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] *data points to a data buffer
 * @param[in] len is the data length
 * @param[in] cmd is the command or data type
 * @return    status code
 *            - 0 success
 *            - 1 write failed
 * @note      none
 */
static uint8_t a_ssd1306_multiple_write_byte(ssd1306_handle_t *handle, uint8_t *data, uint8_t len, uint8_t cmd)
{    
    uint8_t res;
    
    if (handle->iic_spi == SSD1306_INTERFACE_IIC)                               /* if iic */
    {
        if (cmd != 0)                                                           /* if data */
        {
            if (handle->iic_write(handle->iic_addr, 0x40, data, len) != 0)      /* write data */
            {
                return 1;                                                       /* return error */
            }
            else
            {
                return 0;                                                       /* success return 0 */
            }
        }
        else
        {
            if (handle->iic_write(handle->iic_addr, 0x00, data, len) != 0)      /* write command */
            {
                return 1;                                                       /* return error */
            }
            else
            {
                return 0;                                                       /* success return 0 */
            }
        }
    }
    else if (handle->iic_spi == SSD1306_INTERFACE_SPI)                          /* if spi */
    {
        res = handle->spi_cmd_data_gpio_write(cmd);                             /* write data command */
        if (res != 0)                                                           /* check error */
        {
            return 1;                                                           /* return error */
        }
        
        if (handle->spi_write_cmd(data, len) != 0)                              /* write command */
        {
            return 1;                                                           /* return error */
        }
        else
        {
            return 0;                                                           /* success return 0 */
        }
    }
    else
    {
        return 1;                                                               /* return error */
    }
}

/**
 * @brief     draw a point in gram
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] x is the coordinate x
 * @param[in] y is the coordinate y
 * @param[in] data is the write data
 * @return    status code
 *            - 0 success
 *            - 1 gram draw point failed
 * @note      none
 */
static uint8_t a_ssd1306_gram_draw_point(ssd1306_handle_t *handle, uint8_t x, uint8_t y, uint8_t data)
{
    uint8_t pos;
    uint8_t bx;
    uint8_t temp = 0;
    
    pos = y / 8;                              /* get y page */
    bx = y % 8;                               /* get y point */
    temp = 1 << bx;                           /* set data */
    if (data != 0)                            /* if 1  */
    {
        handle->gram[x][pos] |= temp;         /* set 1 */
    }
    else
    {
        handle->gram[x][pos] &= ~temp;        /* set 0 */
    }
  
    return 0;                                 /* success return 0 */
}

/**
 * @brief     draw a char in gram
 * @param[in] *handle points to an ssd1306 handle structure
 * @param[in] x is the coordinate x
 * @param[in] y is the coordinate y
 * @param[in] chr is the write char
 * @param[in] size is the char size
 * @param[in] mode is the display mode
 * @return    status code
 *            - 0 success
 *            - 1 gram show char failed
 * @note      none
 */
static uint8_t a_ssd1306_gram_show_char(ssd1306_handle_t *handle, uint8_t x, uint8_t y, uint8_t chr, uint8_t size, uint8_t mode)
{
    uint8_t temp, t, t1;
    uint8_t y0 = y;
    uint8_t csize = (size / 8 + ((size % 8) ? 1 : 0)) * (size / 2);                 /* get size */
    
    chr = chr - ' ';                                                                /* get index */
    for (t = 0; t < csize; t++)                                                     /* write size */
    {   
        if (size == 12)                                                             /* if size 12 */
        {
            temp = gsc_ssd1306_ascii_1206[chr][t];                                  /* get ascii 1206 */
        }
        else if (size == 16)                                                        /* if size 16 */
        {
            temp = gsc_ssd1306_ascii_1608[chr][t];                                  /* get ascii 1608 */
        }
        else if(size == 24)                                                         /* if size 24 */
        {
            temp = gsc_ssd1306_ascii_2412[chr][t];                                  /* get ascii 2412 */
        }
        else
        {
            return 1;                                                               /* return error */
        }
        for (t1 = 0; t1 < 8; t1++)                                                  /* write one line */
        {
            if ((temp & 0x80) != 0)                                                 /* if 1 */
            {
                if (a_ssd1306_gram_draw_point(handle, x, y, mode) != 0)             /* draw point */
                {
                    return 1;                                                       /* return error */
                }
            }
            else 
            {
                if (a_ssd1306_gram_draw_point(handle, x, y, !mode) != 0)            /* draw point */
                {
                    return 1;                                                       /* return error */
                }
            }
            temp <<= 1;                                                             /* left shift 1 */
            y++;
            if ((y - y0) == size)                                                   /* reset size */
            {
                y = y0;                                                             /* set y */
                x++;                                                                /* x++ */
                
                break;                                                              /* break */
            }
        }
    }
  
    return 0;                                                                       /* success return 0 */
}

/**
 * @brief     clear the screen
 * @param[in] *handle points to an ssd1306 handle structure
 * @return    status code
 *            - 0 success
 *            - 1 clear failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 * @note      none
 */
uint8_t ssd1306_clear(ssd1306_handle_t *handle)
{
    uint8_t i;
    uint8_t n;
    
    if (handle == NULL)                                                                               /* check handle */
    {
        return 2;                                                                                     /* return error */
    }
    if (handle->inited != 1)                                                                          /* check handle initialization */
    {
        return 3;                                                                                     /* return error */
    }
    
    for (i = 0; i < 8; i++)                                                                           /* write 8 page */
    {  
        if (a_ssd1306_write_byte(handle, SSD1306_CMD_PAGE_ADDR+i, SSD1306_CMD) != 0)                  /* set page */
        {
            handle->debug_print("ssd1306: write byte failed.\n");                                     /* write byte failed */
            
            return 1;                                                                                 /* return error */
        }
        if (a_ssd1306_write_byte(handle, SSD1306_CMD_LOWER_COLUMN_START_ADDRESS, SSD1306_CMD) != 0)   /* set lower column 0 */
        {
            handle->debug_print("ssd1306: write byte failed.\n");                                     /* write byte failed */
            
            return 1;                                                                                 /* return error */
        }
        if (a_ssd1306_write_byte(handle, SSD1306_CMD_HIGHER_COLUMN_START_ADDRESS, SSD1306_CMD) != 0)  /* set higher column 0 */
        {
            handle->debug_print("ssd1306: write byte failed.\n");                                     /* write byte failed */
            
            return 1;                                                                                 /* return error */
        }
        for (n = 0; n < 128; n++)                                                                     /* write 128 */
        {
            handle->gram[n][i] = 0x00;                                                                /* set black */
            if (a_ssd1306_write_byte(handle, handle->gram[n][i], SSD1306_DATA) != 0)                  /* write data */
            {
                handle->debug_print("ssd1306: write byte failed.\n");                                 /* write byte failed */
                
                return 1;                                                                             /* return error */
            }
        }
    }
    
    return 0;                                                                                         /* success return 0 */
}

/**
 * @brief     update the gram data
 * @param[in] *handle points to an ssd1306 handle structure
 * @return    status code
 *            - 0 success
 *            - 1 gram update failed
 *            - 2 handle is NULL
 *            - 3 handle is not initialized
 * @note      none
 */
uint8_t ssd1306_gram_update(ssd1306_handle_t *handle)
{
    uint8_t i;
    uint8_t n;
    
    if (handle == NULL)                                                                               /* check handle */
    {
        return 2;                                                                                     /* return error */
    }
    if (handle->inited != 1)                                                                          /* check handle initialization */
    {
        return 3;                                                                                     /* return error */
    }
    
    for (i = 0; i < 8; i++)                                                                           /* write 8 page */
    {  
        if (a_ssd1306_write_byte(handle, SSD1306_CMD_PAGE_ADDR+i, SSD1306_CMD) != 0)                  /* set page */
        {
            handle->debug_print("ssd1306: write byte failed.\n");                                     /* write byte failed */
            
            return 1;                                                                                 /* return error */
        }
        if (a_ssd1306_write_byte(handle, SSD1306_CMD_LOWER_COLUMN_START_ADDRESS, SSD1306_CMD) != 0)   /* set lower column 0 */
        {
            handle->debug_print("ssd1306: write byte failed.\n");                                     /* write byte failed */
            
            return 1;                                                                                 /* return error */
        }
        if (a_ssd1306_write_byte(handle, SSD1306_CMD_HIGHER_COLUMN_START_ADDRESS, SSD1306_CMD) != 0)  /* set higher column 0 */
        {
            handle->debug_print("ssd1306: write byte failed.\n"