Add distance sensor, rewrite OLED to use new i2c library

This commit is contained in:
2026-02-19 14:48:31 -05:00
parent 29b79fbbb5
commit 20b047bbfe
31 changed files with 3040 additions and 122 deletions

View File

@@ -1,8 +1,7 @@
#include <string>
#include "driver/gpio.h"
#include "driver/i2c.h"
#include <driver/i2c_master.h>
#include "esp_err.h"
#include "esp_log.h"
#include "Oled.h"
@@ -18,141 +17,141 @@
#define DISPLAY_HEIGHT 64
#define PAGE_SIZE 128
#define PAGE_COUNT 8
#define I2C_PORT I2C_NUM_0
#define I2C_TIMEOUT_MS 100
void Oled::init(uint8_t sda_pin, uint8_t scl_pin) {
i2c_config_t i2c_config = {
.mode = I2C_MODE_MASTER,
.sda_io_num = sda_pin,
.scl_io_num = scl_pin,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master = {
.clk_speed = 1000000
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,
},
.clk_flags = 0,
};
i2c_param_config(I2C_PORT, &i2c_config);
ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &bus_handle));
i2c_driver_install(I2C_PORT, I2C_MODE_MASTER, 0, 0, 0);
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));
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (OLED_I2C_ADDRESS << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, OLED_CONTROL_BYTE_CMD_STREAM, true);
i2c_master_write_byte(cmd, OLED_CMD_SET_CHARGE_PUMP, true);
i2c_master_write_byte(cmd, 0x14, true);
i2c_master_write_byte(cmd, OLED_CMD_SET_SEGMENT_REMAP, true);
i2c_master_write_byte(cmd, OLED_CMD_SET_COM_SCAN_MODE, true);
i2c_master_write_byte(cmd, OLED_CMD_DISPLAY_ON, true);
i2c_master_stop(cmd);
// 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_cmd_begin(I2C_PORT, cmd, 10 / portTICK_PERIOD_MS)) {
ESP_LOGE(TAG, "configuration failed");
}
if (ESP_OK != i2c_master_transmit(dev_handle, init_cmd, sizeof(init_cmd), I2C_TIMEOUT_MS)) {
ESP_LOGE(TAG, "configuration failed");
}
i2c_cmd_link_delete(cmd);
clear_display();
clear_display();
}
void Oled::clear_display() {
ESP_LOGI(TAG, "clear_display");
i2c_cmd_handle_t cmd;
uint8_t page[PAGE_SIZE] = {0};
for (uint8_t i = 0; i < PAGE_COUNT; i++) {
cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (OLED_I2C_ADDRESS << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, OLED_CONTROL_BYTE_CMD_SINGLE, true);
i2c_master_write_byte(cmd, 0xB0 | i, true);
// 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
i2c_master_write_byte(cmd, OLED_CONTROL_BYTE_DATA_STREAM, true);
i2c_master_write(cmd, page, PAGE_SIZE, true);
i2c_master_stop(cmd);
i2c_master_cmd_begin(I2C_PORT, cmd, 10 / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
}
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) {
i2c_cmd_handle_t cmd;
cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (OLED_I2C_ADDRESS << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, OLED_CONTROL_BYTE_CMD_STREAM, true);
i2c_master_write_byte(cmd, OLED_CMD_SET_CONTRAST, true);
i2c_master_write_byte(cmd, contrast, true);
i2c_master_stop(cmd);
i2c_master_cmd_begin(I2C_NUM_0, cmd, 10 / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
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) {
ESP_LOGI(TAG, "display_text %s", text.c_str());
i2c_cmd_handle_t cmd;
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;
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;
cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (OLED_I2C_ADDRESS << 1) | I2C_MASTER_WRITE, true);
// 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);
i2c_master_write_byte(cmd, OLED_CONTROL_BYTE_CMD_STREAM, true);
i2c_master_write_byte(cmd, 0x00, true); // reset column
i2c_master_write_byte(cmd, 0x10, true);
i2c_master_write_byte(cmd, 0xB0 | cur_page, true); // reset page
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);
i2c_master_stop(cmd);
i2c_master_cmd_begin(I2C_NUM_0, cmd, 10 / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
for (uint8_t i = 0; i < text.size() && i < max_size; i++) {
ESP_LOGI(TAG, "%d: %c", i, text[i]);
if (text[i] == '\n' || col >= col_width) {
ESP_LOGI(TAG, "new line");
cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (OLED_I2C_ADDRESS << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, OLED_CONTROL_BYTE_CMD_STREAM, true);
i2c_master_write_byte(cmd, 0x00, true); // reset column
i2c_master_write_byte(cmd, 0x10, true);
i2c_master_write_byte(cmd, 0xB0 | ++cur_page, true); // increment page
i2c_master_stop(cmd);
i2c_master_cmd_begin(I2C_NUM_0, cmd, 10/portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
col = 0;
} else {
cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (OLED_I2C_ADDRESS << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, OLED_CONTROL_BYTE_DATA_STREAM, true);
uint8_t rotated[8];
rotate8x8((uint8_t*)font8x8_basic[(uint8_t)text[i]], rotated);
i2c_master_write(cmd, rotated, 8, true);
i2c_master_stop(cmd);
i2c_master_cmd_begin(I2C_NUM_0, cmd, 10/portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
col++;
}
}
// 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[y] & (1 << x)) {
out[x] |= (1 << (7 - y));
if (in[7 - y] & (1 << x)) {
out[x] |= (1 << y); // bit 0 = top row, iterate rows bottom-to-top to correct orientation
}
}
}

View File

@@ -1,6 +1,7 @@
#include <cstdint>
#include <string>
#include "driver/i2c_master.h"
#ifndef OLED_H
#define OLED_H
@@ -18,6 +19,8 @@ private:
void init(uint8_t sda_pin, uint8_t scl_pin);
void rotate8x8(uint8_t in[8], uint8_t out[8]);
i2c_master_bus_handle_t bus_handle = nullptr;
i2c_master_dev_handle_t dev_handle = nullptr;
};
#endif // OLED_H