add some code
This commit is contained in:
@@ -0,0 +1 @@
|
||||
7be190d7c58cd635adf8b74b3a841f8245f2f82f36d135a64d8e53cfab39124d
|
||||
@@ -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"}]}
|
||||
@@ -0,0 +1,5 @@
|
||||
idf_component_register(
|
||||
SRCS "esp_lcd_panel_sh1106.c"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES "driver esp_lcd"
|
||||
)
|
||||
5
managed_components/tny-robotics__sh1106-esp-idf/Kconfig
Normal file
5
managed_components/tny-robotics__sh1106-esp-idf/Kconfig
Normal file
@@ -0,0 +1,5 @@
|
||||
menu "SH1106 ESP-IDF Driver"
|
||||
|
||||
|
||||
|
||||
endmenu
|
||||
21
managed_components/tny-robotics__sh1106-esp-idf/LICENSE
Normal file
21
managed_components/tny-robotics__sh1106-esp-idf/LICENSE
Normal 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.
|
||||
134
managed_components/tny-robotics__sh1106-esp-idf/README.md
Normal file
134
managed_components/tny-robotics__sh1106-esp-idf/README.md
Normal 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:
|
||||
|
||||

|
||||
|
||||
- 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 |
@@ -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;
|
||||
}
|
||||
@@ -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)
|
||||
@@ -0,0 +1,3 @@
|
||||
# Example code
|
||||
|
||||
Example code for creating a SH1106 lcd screen object in ESP-IDF.
|
||||
@@ -0,0 +1,4 @@
|
||||
idf_component_register(
|
||||
SRCS "sh1106_lcd_example.c"
|
||||
INCLUDE_DIRS "."
|
||||
)
|
||||
@@ -0,0 +1,4 @@
|
||||
dependencies:
|
||||
pedrominatel/shtc3:
|
||||
version: "*"
|
||||
override_path: '../../../'
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user