2026-09-30 15:46:21 +02:00
|
|
|
#include <string.h>
|
2026-09-25 16:21:37 +02:00
|
|
|
#include "ygg_sh1106.h"
|
|
|
|
|
#include "lvgl.h"
|
|
|
|
|
#include "i2cdev.h"
|
|
|
|
|
#include "esp_lcd_io_i2c.h"
|
|
|
|
|
#include "esp_lcd_panel_ops.h"
|
|
|
|
|
#include "esp_timer.h"
|
|
|
|
|
#include "esp_lcd_panel_sh1106.h"
|
|
|
|
|
|
|
|
|
|
#define I2C_HOST I2C_NUM_0
|
|
|
|
|
#define EXAMPLE_I2C_MASTER_FREQ_HZ 400000
|
|
|
|
|
#define EXAMPLE_LCD_V_RES 64
|
|
|
|
|
#define EXAMPLE_LCD_H_RES 128
|
|
|
|
|
#define EXAMPLE_LVGL_PALETTE_SIZE 8
|
|
|
|
|
#define SH1106_I2C_SDA_GPIO 19
|
|
|
|
|
#define SH1106_I2C_SCL_GPIO 23
|
|
|
|
|
|
|
|
|
|
static uint8_t oled_buffer[EXAMPLE_LCD_H_RES * EXAMPLE_LCD_V_RES / 8];
|
|
|
|
|
|
|
|
|
|
static void example_lvgl_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map)
|
|
|
|
|
{
|
|
|
|
|
esp_lcd_panel_handle_t panel_handle = lv_display_get_user_data(disp);
|
|
|
|
|
|
|
|
|
|
// This is necessary because LVGL reserves 2 x 4 bytes in the buffer, as these are assumed to be used as a palette. Skip the palette here
|
|
|
|
|
// More information about the monochrome, please refer to https://docs.lvgl.io/9.2/porting/display.html#monochrome-displays
|
|
|
|
|
px_map += EXAMPLE_LVGL_PALETTE_SIZE;
|
|
|
|
|
|
|
|
|
|
uint16_t hor_res = lv_display_get_physical_horizontal_resolution(disp);
|
|
|
|
|
int x1 = area->x1;
|
|
|
|
|
int x2 = area->x2;
|
|
|
|
|
int y1 = area->y1;
|
|
|
|
|
int y2 = area->y2;
|
|
|
|
|
|
|
|
|
|
for (int y = y1; y <= y2; y++) {
|
|
|
|
|
for (int x = x1; x <= x2; x++) {
|
|
|
|
|
/* The order of bits is MSB first
|
|
|
|
|
MSB LSB
|
|
|
|
|
bits 7 6 5 4 3 2 1 0
|
|
|
|
|
pixels 0 1 2 3 4 5 6 7
|
|
|
|
|
Left Right
|
|
|
|
|
*/
|
|
|
|
|
bool chroma_color = (px_map[(hor_res >> 3) * y + (x >> 3)] & 1 << (7 - x % 8));
|
|
|
|
|
|
|
|
|
|
/* Write to the buffer as required for the display.
|
|
|
|
|
* It writes only 1-bit for monochrome displays mapped vertically.*/
|
|
|
|
|
uint8_t *buf = oled_buffer + hor_res * (y >> 3) + (x);
|
|
|
|
|
if (chroma_color) {
|
|
|
|
|
(*buf) &= ~(1 << (y % 8));
|
|
|
|
|
} else {
|
|
|
|
|
(*buf) |= (1 << (y % 8));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// pass the draw buffer to the driver
|
|
|
|
|
esp_lcd_panel_draw_bitmap(panel_handle, x1, y1, x2 + 1, y2 + 1, oled_buffer);
|
2026-09-25 16:33:06 +02:00
|
|
|
lv_display_flush_ready(disp);
|
2026-09-25 16:21:37 +02:00
|
|
|
}
|
|
|
|
|
|
2026-09-30 15:46:21 +02:00
|
|
|
static void lvgl_tick_cb(void *arg) {
|
|
|
|
|
lv_tick_inc(10);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ygg_init_tick_cb() {
|
|
|
|
|
const esp_timer_create_args_t lvgl_tick_args = {
|
|
|
|
|
.callback = lvgl_tick_cb,
|
|
|
|
|
.name = "lvgl_tick",
|
|
|
|
|
};
|
|
|
|
|
esp_timer_handle_t lvgl_tick_handle = NULL;
|
|
|
|
|
ESP_ERROR_CHECK(esp_timer_create(&lvgl_tick_args, &lvgl_tick_handle));
|
|
|
|
|
ESP_ERROR_CHECK(esp_timer_start_periodic(lvgl_tick_handle, 10 * 1000));
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-25 16:21:37 +02:00
|
|
|
lv_display_t* ygg_sh1106_init() {
|
2026-09-30 15:46:21 +02:00
|
|
|
ESP_ERROR_CHECK(i2cdev_init());
|
2026-09-25 16:21:37 +02:00
|
|
|
|
|
|
|
|
static i2c_dev_t oled_i2c_dev;
|
|
|
|
|
memset(&oled_i2c_dev, 0, sizeof(oled_i2c_dev));
|
|
|
|
|
oled_i2c_dev.port = I2C_HOST; // numer portu I2C
|
|
|
|
|
oled_i2c_dev.addr = SH1106_I2C_ADDR; // adres OLED-a (0x3C)
|
|
|
|
|
oled_i2c_dev.cfg.sda_io_num = SH1106_I2C_SDA_GPIO; // SDA z "idf menuconfig"
|
|
|
|
|
oled_i2c_dev.cfg.scl_io_num = SH1106_I2C_SCL_GPIO; // SCL z "idf menuconfig"
|
|
|
|
|
oled_i2c_dev.cfg.master.clk_speed = EXAMPLE_I2C_MASTER_FREQ_HZ;
|
|
|
|
|
oled_i2c_dev.cfg.sda_pullup_en = true; // OLED zwykle nie ma pull-upów
|
|
|
|
|
oled_i2c_dev.cfg.scl_pullup_en = true;
|
|
|
|
|
|
|
|
|
|
// Rejestracja urządzenia + utworzenie mutexu.
|
|
|
|
|
// Przy pierwszym urządzeniu na danym porcie i2cdev tworzy magistralę
|
|
|
|
|
// (i2c_new_master_bus) z podanymi pinami SDA/SCL.
|
|
|
|
|
ESP_ERROR_CHECK(i2c_dev_create_mutex(&oled_i2c_dev));
|
|
|
|
|
|
|
|
|
|
// Opcjonalnie: sprawdź, czy OLED odpowiada na magistrali.
|
|
|
|
|
ESP_ERROR_CHECK(i2c_dev_check_present(&oled_i2c_dev));
|
|
|
|
|
|
|
|
|
|
// Pobierz współdzielony uchwyt magistrali zarządzanej przez i2cdev,
|
|
|
|
|
// żeby przekazać go do natywnego komponentu esp_lcd.
|
|
|
|
|
// Dzięki temu inne sensory na i2cdev i wyświetlacz na esp_lcd
|
|
|
|
|
// bezpiecznie dzielą tę samą magistralę I2C.
|
|
|
|
|
// UWAGA: uchwytem zarządza i2cdev — NIE wołaj na nim i2c_del_master_bus().
|
|
|
|
|
i2c_master_bus_handle_t i2c_bus_handle = NULL;
|
|
|
|
|
ESP_ERROR_CHECK(i2cdev_get_shared_handle(I2C_HOST, (void **)&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));
|
|
|
|
|
|
|
|
|
|
void *buf = NULL;
|
|
|
|
|
size_t draw_buffer_sz = EXAMPLE_LCD_H_RES * EXAMPLE_LCD_V_RES / 8 + EXAMPLE_LVGL_PALETTE_SIZE;
|
|
|
|
|
buf = heap_caps_calloc(1, draw_buffer_sz, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
|
|
|
|
|
|
|
|
|
lv_init();
|
|
|
|
|
lv_display_t *display = lv_display_create(EXAMPLE_LCD_H_RES, EXAMPLE_LCD_V_RES);
|
|
|
|
|
lv_display_set_user_data(display, panel_handle);
|
|
|
|
|
lv_display_set_color_format(display, LV_COLOR_FORMAT_I1);
|
|
|
|
|
lv_display_set_buffers(display, buf, NULL, draw_buffer_sz, LV_DISPLAY_RENDER_MODE_FULL);
|
|
|
|
|
lv_display_set_flush_cb(display, example_lvgl_flush_cb);
|
|
|
|
|
|
2026-09-30 15:46:21 +02:00
|
|
|
ygg_init_tick_cb();
|
2026-09-25 16:21:37 +02:00
|
|
|
|
|
|
|
|
return display;
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-25 16:33:06 +02:00
|
|
|
void ygg_set_display_label(lv_display_t *display, lv_obj_t *label, const char *str) {
|
2026-09-25 16:21:37 +02:00
|
|
|
lv_label_set_text(label, str);
|
|
|
|
|
/* Size of the screen (if you use rotation 90 or 270, please use lv_display_get_vertical_resolution) */
|
|
|
|
|
lv_obj_set_width(label, lv_display_get_horizontal_resolution(display));
|
2026-09-25 16:33:06 +02:00
|
|
|
lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, 0);
|
2026-09-25 16:21:37 +02:00
|
|
|
lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 0);
|
|
|
|
|
}
|