add some code

This commit is contained in:
2025-09-05 13:25:11 +08:00
parent 9ff0a99e7a
commit 3cf1229a85
8911 changed files with 2535396 additions and 0 deletions

View File

@@ -0,0 +1 @@
7be190d7c58cd635adf8b74b3a841f8245f2f82f36d135a64d8e53cfab39124d

View File

@@ -0,0 +1 @@
{"version": "1.0", "algorithm": "sha256", "created_at": "2025-05-22T00:03:19.799898+00:00", "files": [{"path": "CMakeLists.txt", "size": 117, "hash": "d5ca704d05fdfc999a69deb092e53cbea31f0276fe8ae7f958c10956306f3937"}, {"path": "LICENSE", "size": 1090, "hash": "f1bb2941143d51a83144bddb837f1ba9fc7f2ed6d219f7ef65e05916f32f3a79"}, {"path": "esp_lcd_panel_sh1106.c", "size": 12237, "hash": "5234ac474c40ee28faa11bd0ebf1a48e53f660c3b8b86ad3282a7d7140e460ab"}, {"path": "idf_component.yml", "size": 355, "hash": "999533d8f6a6678f9fd54ae27452a3f7b6f101e9c9ff490ac6cbc4a0cdfcd246"}, {"path": "Kconfig", "size": 57, "hash": "0232c897aba7a1954acbfc5516a0909ec29799eb72ca40c72c51911cc979764a"}, {"path": "README.md", "size": 5773, "hash": "e8aeaba316efa41ebeca11e98b441012ad55e76182290913a6f845516ce84f7f"}, {"path": "include/esp_lcd_panel_sh1106.h", "size": 2376, "hash": "07a96fb06e6b26a1d820fe0b9476e3643d3f4a2cdb6b5ddc1832353b78569b18"}, {"path": "doc/sh1106 schema.png", "size": 39105, "hash": "2fd09ac4ea203020e2e843ef8996c013b158fc014e0dea12a284670766590aae"}, {"path": "examples/sh1106_lcd_example/CMakeLists.txt", "size": 248, "hash": "fc06feea88d9c5193d307b7b9cfb6ca3293cf7030194ddb1e389d90da36ecd87"}, {"path": "examples/sh1106_lcd_example/README.md", "size": 81, "hash": "d7a9b1459c833beb710804ff90626fdbac39e377825a3a6c0eae21c6ff173f0b"}, {"path": "examples/sh1106_lcd_example/main/sh1106_lcd_example.c", "size": 3894, "hash": "4954872cc5333b4e289d2a7c8bbe42a886ac0718e57a64575ef58e37adf36f00"}, {"path": "examples/sh1106_lcd_example/main/CMakeLists.txt", "size": 79, "hash": "f508d656b07edc110036bb248de0487f343e91524cf1ad30fed5424da26301dd"}, {"path": "examples/sh1106_lcd_example/main/idf_component.yml", "size": 83, "hash": "0dd78b9f5987ec444aa017b29260479ba90cd75da61ec8bfc733710b7efc22f8"}]}

View File

@@ -0,0 +1,5 @@
idf_component_register(
SRCS "esp_lcd_panel_sh1106.c"
INCLUDE_DIRS "include"
REQUIRES "driver esp_lcd"
)

View File

@@ -0,0 +1,5 @@
menu "SH1106 ESP-IDF Driver"
endmenu

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 TNY Robotics
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,134 @@
# SH1106 ESP-IDF Driver
Source code for the SH1106 I2C OLED screen driver for ESP-IDF.
## Introduction
This driver is a simple implementation of the SH1106 OLED screen driver for the ESP-IDF framework.
It is used to communicate with the SH1106 OLED screen using the standard LCD interface of the ESP-IDF framework.
## Installation
To install the driver, you can clone the repository and place it in the `components` folder of your project.
```bash
git clone https://github.com/TNY-Robotics/sh1106-esp-idf.git components/sh1106
```
## Usage
The driver exposes a simple header file named `esp_lcd_panel_sh1106.h`, that you can include using :
```c
#include "esp_lcd_panel_sh1106.h"
```
You can now create an lcd panel with the SH1106 driver using the following code :
```c
esp_err_t err = esp_lcd_new_panel_sh1106(panel_io_handle, &panel_dev_config, &panel_handle);
```
For more information about LCDs with the ESP-IDF framework, visit the [official documentation](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/peripherals/lcd/index.html).
## Examples
An example of how ESP-IDF lcd creation using the SH1106 driver can be found in the `main` folder.
## Screen buffer format
Here's some information about the screen buffer format used by the SH1106 driver, and the SH1106 to better understand how to use the driver and display pixels on the screen.
### Pixels and pages
The SH1106 buffer format works with lines and pages.
Each page is `8` pixels high and `128` pixels wide. The screen has `8` pages for a total of `64` pixels high.
If we represent the screen buffer as an array of bytes *(8 bits)*, the screen will read each byte as a `page pixel` *(a vertical strip of 8 pixels high)*, and at the end of the page, it will move to the next page *(8 pixels down)*.
Each bit in the byte represents a pixel state, where `0` is off and `1` is on.
### Schematic representation
The way the screen reads the buffer is as follows:
![SH1106 Schema](./doc/sh1106%20schema.png)
- Each **RED Arrrow** represents one byte of data, corresponding to a `page pixel` *(vertical line of 8 pixels)*.
- Each **GREEN Arrow** represents one 128 byte of data, corresponding to a `page` *(horizontal line of 128 `page pixels`)*.
- Each **BLUE Arrow** represents the page shift *(8 pixels down, back to left side)* after reading one `page` of data.
### Adressing pixels in the buffer
Turning on and off one pixel in the buffer is a little bit tricky, as we need to only change the state of one bit in the byte buffer.
Here's a simple example of how to turn on and off a pixel in the buffer:
```c
// Creating the screen buffer
uint8_t buffer[128 * 8]; // 128 page pixels * 8 pages, with 8 pixels per page pixel
// Optional : Clear the buffer
memset(buffer, 0, sizeof(buffer)); // All pixels will be turned off if we fill the buffer with 0
// ==> Only turning on a pixel at [0, 0] (the top-left corner)
// the page index is 0, the page pixel index is 0
// so we just need to set the first bit of the first byte of the buffer to 1
buffer[0] = 0b00000001;
// note : if you don't want to affect the other pixels in the byte, you can use the bitwise OR operator
buffer[0] |= 0b00000001; // setting at 0 the pixels we don't want to change, and at 1 the pixel we want to turn on
// note : now if you want to turn off the pixel, you can use the bitwise AND operator
buffer[0] &= 0b11111110; // setting at 1 the pixels we don't want to change, and at 0 the pixel we want to turn off
// note: writing the entire binary value is a little bit tricky, so you can use shift operators instead
buffer[0] |= 1 << 0; // 1 << 0 is equivalent to 0b00000001, we are shifting the 1 by 0 positions to the left
// note: writing the entire binary value is a little bit tricky, so you can use shift operators instead
buffer[0] &= ~(1 << 0); // ~(1 << 0) is equivalent to 0b11111110, we are shifting the 1 by 0 positions to the left and inverting the bits
// ==> Turning on a pixel at [0, 1] (the pixel right below the top-left corner)
// the page index is 0, the page pixel index is 0
// so we just need to set the second bit of the first byte of the buffer to 1
buffer[0] |= 1 << 1; // 1 << 1 is equivalent to 0b00000010, we are shifting the 1 by 1 position to the left
// ==> Turning on a pixel at [127, 0] (the top-right corner)
// the page index is 0, the page pixel index is 127
// so we just need to set the first bit of the 127th byte of the buffer to 1
buffer[127] |= 1 << 0;
// ==> Turning on a pixel at [127, 63] (the bottom-right corner)
// the page index is 7, the page pixel index is 127
// so we just need to set the last bit of the byte at position 7*128 + 127 of the buffer to 1
buffer[7*128 + 127] |= 1 << 7; // shifting by 7 positions to the left is equivalent to 0b10000000
```
### General formula
To turn on a pixel at position `(x, y)` in the buffer, you can use the following formula:
```c
uint8_t x = 0; // the x position of the pixel
uint8_t y = 0; // the y position of the pixel
// Getting the page index
uint8_t page_index = y / 8; // integer division, we get the page index of the pixel
uint8_t page_pixel_index = x; // the page pixel index is the x position of the pixel
uint8_t page_pixel_shift = y % 8; // remainder of the division, we get the bit shift of the pixel in the page pixel
// Turning on the pixel
buffer[page_index * 128 + page_pixel_index] |= 1 << page_pixel_shift;
// Turning off the pixel
buffer[page_index * 128 + page_pixel_index] &= ~(1 << page_pixel_shift);
```
## Licence
This driver is under the MIT Licence.
## Author
This driver was created by the [TNY Robotics](https://tny-robotics.com) team, for any questions or suggestions, please contact us at [contact@furwaz.com](mailto:contact@furwaz.com).

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -0,0 +1,290 @@
/*
* SH1106 ESP-IDF Driver by TNY Robotics
*
* SPDX-FileCopyrightText: 2025 TNY Robotics
* SPDX-License-Identifier: MIT
*
*
* Copyright (C) 2025 TNY Robotics
*
* This file is part of the SH1106 ESP-IDF Driver.
*
* License: MIT
* Repository: https://github.com/tny-robotics/sh1106-esp-idf
*
* Author: TNY Robotics
* Date: 13/02/2025
* Version: 1.0
*/
#include <stdlib.h>
#include <stdint.h>
#include <sys/cdefs.h>
#include "sdkconfig.h"
#if CONFIG_LCD_ENABLE_DEBUG_LOG
// The local log level must be defined before including esp_log.h
// Set the maximum log level for this source file
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
#endif
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <esp_lcd_panel_interface.h>
#include <esp_lcd_panel_io.h>
#include "esp_lcd_panel_sh1106.h"
#include <esp_lcd_panel_ops.h>
#include <driver/gpio.h>
#include <esp_log.h>
#include <esp_check.h>
static const char *TAG = "lcd_panel.sh1106";
// SH1106 commands
#define SH1106_CMD_SET_CHARGE_PUMP_CTRL 0xAD
#define SH1106_CMD_SET_CHARGE_PUMP_ON 0x8B
#define SH1106_CMD_SET_CHARGE_PUMP_OFF 0x8A
#define SH1106_CMD_SET_DISPLAY_NORMAL 0xA6
#define SH1106_CMD_SET_DISPLAY_REVERSE 0xA7
#define SH1106_CMD_SET_ENTIRE_DISPLAY_OFF 0xA4
#define SH1106_CMD_SET_ENTIRE_DISPLAY_ON 0xA5
#define SH1106_CMD_SET_DISPLAY_OFF 0xAE
#define SH1106_CMD_SET_DISPLAY_ON 0xAF
#define SH1106_CMD_SET_PAGE_ADDR 0xB0
#define SH1106_CMD_SET_COLUMN_ADDR_LOW 0x00
#define SH1106_CMD_SET_COLUMN_ADDR_HIGH 0x10
#define SH1106_CMD_SET_DISPLAY_START_LINE 0x40
#define SH1106_CMD_SET_DISPLAY_OFFSET 0xD3
#define SH1106_CMD_SET_CONTRAST 0x81
#define SH1106_CMD_SET_COM_SCAN_MODE_NORMAL 0xC0
#define SH1106_CMD_SET_COM_SCAN_MODE_REVERSE 0xC8
#define SH1106_CMD_SET_SEGMENT_REMAP_INVERSE 0xA1
#define SH1106_CMD_SET_SEGMENT_REMAP_NORMAL 0xA0
#define SH1106_CMD_SET_PADS_HW_CONFIG 0xDA
#define SH1106_CMD_SET_PADS_HW_SEQUENTIAL 0x02
#define SH1106_CMD_SET_PADS_HW_ALTERNATIVE 0x12
#define SH1106_CMD_SET_MULTIPLEX_RATIO 0xA8
static esp_err_t panel_sh1106_del(esp_lcd_panel_t *panel);
static esp_err_t panel_sh1106_reset(esp_lcd_panel_t *panel);
static esp_err_t panel_sh1106_init(esp_lcd_panel_t *panel);
static esp_err_t panel_sh1106_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data);
static esp_err_t panel_sh1106_invert_color(esp_lcd_panel_t *panel, bool invert_color_data);
static esp_err_t panel_sh1106_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y);
static esp_err_t panel_sh1106_swap_xy(esp_lcd_panel_t *panel, bool swap_axes);
static esp_err_t panel_sh1106_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap);
static esp_err_t panel_sh1106_disp_on_off(esp_lcd_panel_t *panel, bool off);
typedef struct {
esp_lcd_panel_t base;
esp_lcd_panel_io_handle_t io;
int reset_gpio_num;
int x_gap;
int y_gap;
unsigned int bits_per_pixel;
bool reset_level;
bool swap_axes;
} sh1106_panel_t;
esp_err_t esp_lcd_new_panel_sh1106(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel)
{
#if CONFIG_LCD_ENABLE_DEBUG_LOG
esp_log_level_set(TAG, ESP_LOG_DEBUG);
#endif
esp_err_t ret = ESP_OK;
sh1106_panel_t *sh1106 = NULL;
ESP_GOTO_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
ESP_GOTO_ON_FALSE(panel_dev_config->bits_per_pixel == 1, ESP_ERR_INVALID_ARG, err, TAG, "bpp must be 1");
// esp_lcd_panel_sh1106_config_t *sh1106_spec_config = (esp_lcd_panel_sh1106_config_t *)panel_dev_config->vendor_config;
// leak detection of sh1106 because saving sh1106->base address
ESP_COMPILER_DIAGNOSTIC_PUSH_IGNORE("-Wanalyzer-malloc-leak")
sh1106 = calloc(1, sizeof(sh1106_panel_t));
ESP_GOTO_ON_FALSE(sh1106, ESP_ERR_NO_MEM, err, TAG, "no mem for sh1106 panel");
if (panel_dev_config->reset_gpio_num >= 0) {
gpio_config_t io_conf = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num,
};
ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed");
}
sh1106->io = io;
sh1106->bits_per_pixel = panel_dev_config->bits_per_pixel;
sh1106->reset_gpio_num = panel_dev_config->reset_gpio_num;
sh1106->reset_level = panel_dev_config->flags.reset_active_high;
sh1106->base.del = panel_sh1106_del;
sh1106->base.reset = panel_sh1106_reset;
sh1106->base.init = panel_sh1106_init;
sh1106->base.draw_bitmap = panel_sh1106_draw_bitmap;
sh1106->x_gap = 0;
sh1106->y_gap = 0;
sh1106->base.invert_color = panel_sh1106_invert_color;
sh1106->base.set_gap = panel_sh1106_set_gap;
sh1106->base.mirror = panel_sh1106_mirror;
sh1106->base.swap_xy = panel_sh1106_swap_xy;
sh1106->base.disp_on_off = panel_sh1106_disp_on_off;
*ret_panel = &(sh1106->base);
ESP_LOGD(TAG, "new sh1106 panel @%p", sh1106);
return ESP_OK;
err:
if (sh1106) {
if (panel_dev_config->reset_gpio_num >= 0) {
gpio_reset_pin(panel_dev_config->reset_gpio_num);
}
free(sh1106);
}
return ret;
ESP_COMPILER_DIAGNOSTIC_POP("-Wanalyzer-malloc-leak")
}
static esp_err_t panel_sh1106_del(esp_lcd_panel_t *panel)
{
sh1106_panel_t *sh1106 = __containerof(panel, sh1106_panel_t, base);
if (sh1106->reset_gpio_num >= 0) {
gpio_reset_pin(sh1106->reset_gpio_num);
}
ESP_LOGD(TAG, "del sh1106 panel @%p", sh1106);
free(sh1106);
return ESP_OK;
}
static esp_err_t panel_sh1106_reset(esp_lcd_panel_t *panel)
{
sh1106_panel_t *sh1106 = __containerof(panel, sh1106_panel_t, base);
// perform hardware reset
if (sh1106->reset_gpio_num >= 0) {
gpio_set_level(sh1106->reset_gpio_num, sh1106->reset_level);
vTaskDelay(pdMS_TO_TICKS(10));
gpio_set_level(sh1106->reset_gpio_num, !sh1106->reset_level);
vTaskDelay(pdMS_TO_TICKS(10));
}
return ESP_OK;
}
static esp_err_t panel_sh1106_init(esp_lcd_panel_t *panel)
{
sh1106_panel_t *sh1106 = __containerof(panel, sh1106_panel_t, base);
esp_lcd_panel_io_handle_t io = sh1106->io;
// Enable the charge pump (DC voltage converter for OLED panel)
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, SH1106_CMD_SET_CHARGE_PUMP_CTRL, (uint8_t[1]) {
SH1106_CMD_SET_CHARGE_PUMP_ON
}, 1), TAG, "io tx param SH1106_CMD_SET_CHARGE_PUMP_CTRL failed");
// Set direction of SEG and COM
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, SH1106_CMD_SET_SEGMENT_REMAP_INVERSE, NULL, 0), TAG, "io tx param SH1106_CMD_SET_SEGMENT_REMAP_INVERSE failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, SH1106_CMD_SET_COM_SCAN_MODE_REVERSE, NULL, 0), TAG, "io tx param SH1106_CMD_SET_COM_SCAN_MODE_REVERSE failed");
// Set display start line and 0 offset
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, SH1106_CMD_SET_DISPLAY_START_LINE | 0x00, NULL, 0), TAG, "io tx param SH1106_CMD_SET_DISPLAY_START_LINE failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, SH1106_CMD_SET_DISPLAY_OFFSET, (uint8_t[1]) {
0x00
}, 1), TAG, "io tx param SH1106_CMD_SET_DISPLAY_OFFSET failed");
// Set display padding to 0
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, SH1106_CMD_SET_PADS_HW_CONFIG, (uint8_t[1]) {
SH1106_CMD_SET_PADS_HW_ALTERNATIVE
}, 1), TAG, "io tx param SH1106_CMD_SET_PADS_HW_CONFIG failed");
// Set multiplex ratio
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, SH1106_CMD_SET_MULTIPLEX_RATIO, (uint8_t[1]) {
0x3F
}, 1), TAG, "io tx param SH1106_CMD_SET_MULTIPLEX_RATIO failed");
// Set cursor at (0, 0)
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, SH1106_CMD_SET_PAGE_ADDR | 0x00, NULL, 0), TAG, "io tx param SH1106_CMD_SET_PAGE_ADDR failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, SH1106_CMD_SET_COLUMN_ADDR_LOW | 0x00, NULL, 0), TAG, "io tx param SH1106_CMD_SET_COLUMN_ADDR_LOW failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, SH1106_CMD_SET_COLUMN_ADDR_HIGH | 0x00, NULL, 0), TAG, "io tx param SH1106_CMD_SET_COLUMN_ADDR_HIGH failed");
// Set entire display mode OFF (using ram to display)
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, SH1106_CMD_SET_ENTIRE_DISPLAY_OFF, NULL, 0), TAG, "io tx param SH1106_CMD_SET_ENTIRE_DISPLAY_OFF failed");
return ESP_OK;
}
static esp_err_t panel_sh1106_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data)
{
sh1106_panel_t *sh1106 = __containerof(panel, sh1106_panel_t, base);
esp_lcd_panel_io_handle_t io = sh1106->io;
// For each line, shift at the line and send the bitmap line data
for (int y = 0; y < 8; y++) {
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, SH1106_CMD_SET_COLUMN_ADDR_LOW | 0x02, NULL, 0), TAG, "io tx param SH1106_CMD_SET_COLUMN_ADDR_LOW failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, SH1106_CMD_SET_COLUMN_ADDR_HIGH | 0x00, NULL, 0), TAG, "io tx param SH1106_CMD_SET_COLUMN_ADDR_HIGH failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, SH1106_CMD_SET_PAGE_ADDR | y, NULL, 0), TAG, "io tx param SH1106_CMD_SET_PAGE_ADDR failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_color(io, -1, color_data + y * SH1106_WIDTH, SH1106_WIDTH), TAG, "io tx color failed");
}
return ESP_OK;
}
static esp_err_t panel_sh1106_invert_color(esp_lcd_panel_t *panel, bool invert_color_data)
{
// NOTE : Cannot invert colors on the SH1106
// sh1106_panel_t *sh1106 = __containerof(panel, sh1106_panel_t, base);
// esp_lcd_panel_io_handle_t io = sh1106->io;
// int command = 0;
// if (invert_color_data) {
// command = SH1106_CMD_INVERT_ON;
// } else {
// command = SH1106_CMD_INVERT_OFF;
// }
// ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "io tx param SH1106_CMD_INVERT_ON/OFF failed");
return ESP_OK;
}
static esp_err_t panel_sh1106_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y)
{
sh1106_panel_t *sh1106 = __containerof(panel, sh1106_panel_t, base);
esp_lcd_panel_io_handle_t io = sh1106->io;
int command = 0;
if (mirror_x) {
command = SH1106_CMD_SET_DISPLAY_REVERSE;
} else {
command = SH1106_CMD_SET_DISPLAY_NORMAL;
}
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "io tx param SH1106_CMD_MIRROR_X_ON/OFF failed");
if (mirror_y) {
command = SH1106_CMD_SET_COM_SCAN_MODE_REVERSE;
} else {
command = SH1106_CMD_SET_COM_SCAN_MODE_NORMAL;
}
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "io tx param SH1106_CMD_MIRROR_Y_ON/OFF failed");
return ESP_OK;
}
static esp_err_t panel_sh1106_swap_xy(esp_lcd_panel_t *panel, bool swap_axes)
{
// sh1106_panel_t *sh1106 = __containerof(panel, sh1106_panel_t, base);
// sh1106->swap_axes = swap_axes;
return ESP_OK;
}
static esp_err_t panel_sh1106_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap)
{
// sh1106_panel_t *sh1106 = __containerof(panel, sh1106_panel_t, base);
// sh1106->x_gap = x_gap;
// sh1106->y_gap = y_gap;
return ESP_OK;
}
static esp_err_t panel_sh1106_disp_on_off(esp_lcd_panel_t *panel, bool on_off)
{
sh1106_panel_t *sh1106 = __containerof(panel, sh1106_panel_t, base);
esp_lcd_panel_io_handle_t io = sh1106->io;
int command = 0;
if (on_off) {
command = SH1106_CMD_SET_DISPLAY_ON;
} else {
command = SH1106_CMD_SET_DISPLAY_OFF;
}
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "io tx param SH1106_CMD_DISP_ON/OFF failed");
// SEG/COM will be ON/OFF after 100ms after sending DISP_ON/OFF command
vTaskDelay(pdMS_TO_TICKS(100));
return ESP_OK;
}

View File

@@ -0,0 +1,6 @@
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(sh1106_lcd_example)

View File

@@ -0,0 +1,3 @@
# Example code
Example code for creating a SH1106 lcd screen object in ESP-IDF.

View File

@@ -0,0 +1,4 @@
idf_component_register(
SRCS "sh1106_lcd_example.c"
INCLUDE_DIRS "."
)

View File

@@ -0,0 +1,4 @@
dependencies:
pedrominatel/shtc3:
version: "*"
override_path: '../../../'

View File

@@ -0,0 +1,101 @@
/*
* SH1106 ESP-IDF Driver by TNY Robotics
*
* SPDX-FileCopyrightText: 2025 TNY Robotics
* SPDX-License-Identifier: MIT
*
*
* Copyright (C) 2025 TNY Robotics
*
* This file is part of the SH1106 ESP-IDF driver.
*
* License: MIT
* Repository: https://github.com/tny-robotics/sh1106-esp-idf
*
* Author: TNY Robotics
* Date: 13/02/2025
* Version: 1.0
*/
#include "freertos/FreeRTOS.h"
#include "esp_lcd_io_i2c.h"
#include "esp_lcd_panel_sh1106.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_ops.h"
#define I2C_SDA_GPIO GPIO_NUM_21
#define I2C_SCL_GPIO GPIO_NUM_22
#define I2C_HOST I2C_NUM_0
#ifdef __cplusplus
extern "C"
#endif
void app_main(void)
{
/* I2C CONFIGURATION */
// i2c bus configuration
i2c_master_bus_config_t bus_config = {
.i2c_port = I2C_HOST, // I2C port number
.sda_io_num = I2C_SDA_GPIO, // GPIO number for I2C sda signal
.scl_io_num = I2C_SCL_GPIO, // GPIO number for I2C scl signal
.clk_source = I2C_CLK_SRC_DEFAULT, // I2C clock source, just use the default
.glitch_ignore_cnt = 7, // glitch filter, again, just use the default
.intr_priority = 0, // interrupt priority, default to 0
.trans_queue_depth = 0, // transaction queue depth, default to 0
.flags = {
.enable_internal_pullup = true, // enable internal pullup resistors (oled screen does not have one)
.allow_pd = false, // just using the default value
},
};
// Create the i2c bus handle
i2c_master_bus_handle_t i2c_bus_handle = NULL;
ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &i2c_bus_handle));
// Create the i2c io handle
esp_lcd_panel_io_handle_t io_handle = NULL;
esp_lcd_panel_io_i2c_config_t io_config = ESP_SH1106_DEFAULT_IO_CONFIG;
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(*i2c_bus_handle, &io_config, &io_handle));
/* SCREEN CONFIGURATION */
// sh1106 panel configuration (most of the values are not used, but must be set to avoid cpp warnings)
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = -1, // sh1106 does not have a reset pin, so set to -1
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, // not even used, but must be set to avoid cpp warnings
.data_endian = LCD_RGB_DATA_ENDIAN_LITTLE, // not even used, but must be set to avoid cpp warnings
.bits_per_pixel = SH1106_PIXELS_PER_BYTE / 8, // bpp = 1 (monochrome, that's important)
.flags = {
.reset_active_high = false, // not even used, but must be set to avoid cpp warnings
},
.vendor_config = NULL, // no need for custom vendor config, not implemented
};
// Create the panel handle from the sh1106 driver
esp_lcd_panel_handle_t panel_handle = NULL;
ESP_ERROR_CHECK(esp_lcd_new_panel_sh1106(io_handle, &panel_config, &panel_handle));
// Reset the screen (no reset pin, so it's a no-op here, optional)
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
// Initialize the screen (this one isn't optional at all!)
ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
// Turn on the screen (Easier to see something, right?)
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
/* SCREEN PIXEL TEST */
// Create a buffer to hold the screen data
uint8_t buffer_data[SH1106_SCREEN_SIZE];
memset(buffer_data, 0, SH1106_SCREEN_SIZE);
// Just turn on the first top-left pixel
// NOTE : Refer to driver README.md file for more information about the screen buffer format
buffer_data[0] = 0b10000000;
// Send the buffer to the screen
ESP_ERROR_CHECK(esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, SH1106_WIDTH, SH1106_HEIGHT, buffer_data));
}

View File

@@ -0,0 +1,15 @@
description: SH1106 ESP-IDF Driver by TNY Robotics
issues: https://github.com/TNY-Robotics/sh1106-esp-idf/issues
maintainers:
- TNY Robotics <contact@furwaz.com>
repository: https://github.com/TNY-Robotics/sh1106-esp-idf.git
tags:
- sh1106
- display
- lcd
- oled
- i2c
- driver
- esp-idf
url: https://github.com/TNY-Robotics/sh1106-esp-idf
version: 1.0.0

View File

@@ -0,0 +1,91 @@
/*
* SH1106 ESP-IDF Driver by TNY Robotics
*
* SPDX-FileCopyrightText: 2025 TNY Robotics
* SPDX-License-Identifier: MIT
*
*
* Copyright (C) 2025 TNY Robotics
*
* This file is part of the SH1106 ESP-IDF driver.
*
* License: MIT
* Repository: https://github.com/tny-robotics/sh1106-esp-idf
*
* Author: TNY Robotics
* Date: 13/02/2025
* Version: 1.0
*/
#pragma once
#include <stdbool.h>
#include "esp_err.h"
#include "esp_lcd_panel_dev.h"
#ifdef __cplusplus
extern "C" {
#endif
#define SH1106_HEIGHT 64
#define SH1106_WIDTH 128
#define SH1106_PIXELS_PER_BYTE 8
#define SH1106_BUFFER_SIZE (SH1106_HEIGHT * SH1106_WIDTH / SH1106_PIXELS_PER_BYTE)
#define SH1106_I2C_ADDR 0x3C
#define ESP_SH1106_DEFAULT_IO_CONFIG {\
.dev_addr = 0x3C,\
.on_color_trans_done = NULL,\
.user_ctx = NULL,\
.control_phase_bytes = 1,\
.dc_bit_offset = 6,\
.lcd_cmd_bits = 8,\
.lcd_param_bits = 8,\
.flags = {\
.dc_low_on_data = false,\
.disable_control_phase = false,\
},\
.scl_speed_hz = 400 * 1000,\
};
/**
* @brief sh1106 configuration structure
*
* To be used as esp_lcd_panel_dev_config_t.vendor_config.
* See esp_lcd_new_panel_sh1106().
*/
typedef struct {
// Nothing to configure
} esp_lcd_panel_sh1106_config_t;
/**
* @brief Create LCD panel for model sh1106
*
* @param[in] io LCD panel IO handle
* @param[in] panel_dev_config general panel device configuration
* @param[out] ret_panel Returned LCD panel handle
* @return
* - ESP_ERR_INVALID_ARG if parameter is invalid
* - ESP_ERR_NO_MEM if out of memory
* - ESP_OK on success
*
* @note The default panel size is 128x64.
* @note Use esp_lcd_panel_sh1106_config_t to set the correct size.
* Example usage:
* @code {c}
*
* esp_lcd_panel_sh1106_config_t sh1106_config;
* esp_lcd_panel_dev_config_t panel_config = {
* <...>
* .vendor_config = &sh1106_config
* };
*
* esp_lcd_panel_handle_t panel_handle = NULL;
* esp_lcd_new_panel_sh1106(io_handle, &panel_config, &panel_handle);
* @endcode
*/
esp_err_t esp_lcd_new_panel_sh1106(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel);
#ifdef __cplusplus
}
#endif