mirror of
https://github.com/BotChain-Robots/firmware.git
synced 2026-07-08 17:47:21 +02:00
159 lines
5.4 KiB
C++
159 lines
5.4 KiB
C++
#include <string>
|
|
|
|
#include "driver/gpio.h"
|
|
#include <driver/i2c_master.h>
|
|
#include "esp_err.h"
|
|
#include "esp_log.h"
|
|
#include "Oled.h"
|
|
#include "font8x8_basic.h"
|
|
#include "ssd1306.h"
|
|
|
|
#define TAG "OLED"
|
|
|
|
// 128x64 pixels
|
|
// 8 pixels per byte
|
|
// 128 bytes per page -> ((128*64)/8)/128 = 8 pages.
|
|
#define DISPLAY_WIDTH 128
|
|
#define DISPLAY_HEIGHT 64
|
|
#define PAGE_SIZE 128
|
|
#define PAGE_COUNT 8
|
|
#define I2C_TIMEOUT_MS 100
|
|
|
|
void Oled::init(uint8_t sda_pin, uint8_t scl_pin) {
|
|
i2c_master_bus_config_t bus_config = {
|
|
.i2c_port = I2C_NUM_0,
|
|
.sda_io_num = (gpio_num_t)sda_pin,
|
|
.scl_io_num = (gpio_num_t)scl_pin,
|
|
.clk_source = I2C_CLK_SRC_DEFAULT,
|
|
.glitch_ignore_cnt = 7,
|
|
.intr_priority = 0,
|
|
.trans_queue_depth = 0,
|
|
.flags = {
|
|
.enable_internal_pullup = true,
|
|
},
|
|
};
|
|
ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &bus_handle));
|
|
|
|
i2c_device_config_t dev_config = {
|
|
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
|
|
.device_address = OLED_I2C_ADDRESS,
|
|
.scl_speed_hz = 400000,
|
|
.scl_wait_us = 0,
|
|
.flags = {
|
|
.disable_ack_check = false,
|
|
},
|
|
};
|
|
ESP_ERROR_CHECK(i2c_master_bus_add_device(bus_handle, &dev_config, &dev_handle));
|
|
|
|
// Full SSD1306 initialisation sequence
|
|
uint8_t init_cmd[] = {
|
|
OLED_CONTROL_BYTE_CMD_STREAM,
|
|
OLED_CMD_DISPLAY_OFF, // display off during init
|
|
OLED_CMD_SET_DISPLAY_CLK_DIV, 0x80, // clock divide ratio / oscillator frequency
|
|
OLED_CMD_SET_MUX_RATIO, 0x3F, // 1/64 duty (64 rows)
|
|
OLED_CMD_SET_DISPLAY_OFFSET, 0x00, // no vertical shift
|
|
OLED_CMD_SET_DISPLAY_START_LINE, // start line = 0
|
|
OLED_CMD_SET_CHARGE_PUMP, 0x14, // enable charge pump
|
|
OLED_CMD_SET_MEMORY_ADDR_MODE, 0x02,// page addressing mode
|
|
OLED_CMD_SET_SEGMENT_REMAP, // mirror horizontally (0xA1)
|
|
OLED_CMD_SET_COM_SCAN_MODE, // mirror vertically (0xC8)
|
|
OLED_CMD_SET_COM_PIN_MAP, 0x12, // alt COM pin config for 128x64
|
|
OLED_CMD_SET_CONTRAST, 0x7F, // mid contrast
|
|
OLED_CMD_SET_PRECHARGE, 0xF1, // pre-charge period
|
|
OLED_CMD_SET_VCOMH_DESELCT, 0x30, // VCOMH deselect level
|
|
OLED_CMD_DISPLAY_RAM, // display follows RAM
|
|
OLED_CMD_DISPLAY_NORMAL, // non-inverted
|
|
OLED_CMD_DISPLAY_ON, // turn display on
|
|
};
|
|
|
|
if (ESP_OK != i2c_master_transmit(dev_handle, init_cmd, sizeof(init_cmd), I2C_TIMEOUT_MS)) {
|
|
ESP_LOGE(TAG, "configuration failed");
|
|
}
|
|
|
|
clear_display();
|
|
}
|
|
|
|
void Oled::clear_display() {
|
|
|
|
// Data packet: control byte + PAGE_SIZE zero bytes
|
|
uint8_t data_buf[1 + PAGE_SIZE] = { OLED_CONTROL_BYTE_DATA_STREAM };
|
|
// rest is already zero-initialised
|
|
|
|
for (uint8_t i = 0; i < PAGE_COUNT; i++) {
|
|
// Set page address and reset column to 0
|
|
uint8_t page_cmd[] = {
|
|
OLED_CONTROL_BYTE_CMD_STREAM,
|
|
(uint8_t)(0xB0 | i), // page address
|
|
0x00, // column low nibble = 0
|
|
0x10, // column high nibble = 0
|
|
};
|
|
i2c_master_transmit(dev_handle, page_cmd, sizeof(page_cmd), I2C_TIMEOUT_MS);
|
|
i2c_master_transmit(dev_handle, data_buf, sizeof(data_buf), I2C_TIMEOUT_MS);
|
|
}
|
|
}
|
|
|
|
void Oled::set_contrast(uint8_t contrast) {
|
|
uint8_t cmd[] = {
|
|
OLED_CONTROL_BYTE_CMD_STREAM,
|
|
OLED_CMD_SET_CONTRAST,
|
|
contrast,
|
|
};
|
|
i2c_master_transmit(dev_handle, cmd, sizeof(cmd), I2C_TIMEOUT_MS);
|
|
}
|
|
|
|
void Oled::display_text(const std::string& text) {
|
|
|
|
uint8_t cur_page = 0;
|
|
uint8_t col = 0;
|
|
auto col_width = DISPLAY_WIDTH / 8; // using an 8x8 font
|
|
auto num_rows = DISPLAY_HEIGHT / 8; // using an 8x8 font
|
|
auto max_size = col_width * num_rows;
|
|
|
|
// Reset cursor to top-left
|
|
uint8_t reset_cmd[] = {
|
|
OLED_CONTROL_BYTE_CMD_STREAM,
|
|
0x00, // reset column low nibble
|
|
0x10, // reset column high nibble
|
|
(uint8_t)(0xB0 | cur_page), // reset page
|
|
};
|
|
i2c_master_transmit(dev_handle, reset_cmd, sizeof(reset_cmd), I2C_TIMEOUT_MS);
|
|
|
|
for (uint8_t i = 0; i < text.size() && i < max_size; i++) {
|
|
if (text[i] == '\n' || col >= col_width) {
|
|
// new line
|
|
cur_page++;
|
|
col = 0;
|
|
uint8_t newline_cmd[] = {
|
|
OLED_CONTROL_BYTE_CMD_STREAM,
|
|
0x00,
|
|
0x10,
|
|
(uint8_t)(0xB0 | cur_page),
|
|
};
|
|
i2c_master_transmit(dev_handle, newline_cmd, sizeof(newline_cmd), I2C_TIMEOUT_MS);
|
|
} else {
|
|
uint8_t rotated[8];
|
|
rotate8x8((uint8_t*)font8x8_basic[(uint8_t)text[i]], rotated);
|
|
|
|
// Prepend data-stream control byte
|
|
uint8_t data_buf[9];
|
|
data_buf[0] = OLED_CONTROL_BYTE_DATA_STREAM;
|
|
for (int b = 0; b < 8; b++) {
|
|
data_buf[1 + b] = rotated[b];
|
|
}
|
|
i2c_master_transmit(dev_handle, data_buf, sizeof(data_buf), I2C_TIMEOUT_MS);
|
|
col++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Oled::rotate8x8(uint8_t in[8], uint8_t out[8]) {
|
|
for (int x = 0; x < 8; x++) {
|
|
out[x] = 0;
|
|
for (int y = 0; y < 8; y++) {
|
|
if (in[7 - y] & (1 << x)) {
|
|
out[x] |= (1 << y); // bit 0 = top row, iterate rows bottom-to-top to correct orientation
|
|
}
|
|
}
|
|
}
|
|
}
|