Initial Demo OS

This commit is contained in:
dark98
2026-09-03 00:46:04 +01:00
commit 1491821e96
45 changed files with 2908 additions and 0 deletions
+26
View File
@@ -0,0 +1,26 @@
idf_component_register(
SRCS
"main.c"
"apps/app_clock.c"
"apps/app_sysmon.c"
"apps/app_settings.c"
"apps/app_game.c"
INCLUDE_DIRS
"."
"apps"
REQUIRES
sys_core
sys_input
sys_gfx
sys_app
sys_cli
driver
esp_driver_gpio
esp_driver_i2c
spi_flash
esp_timer
freertos
log
esp_system
esp_hw_support
)
+88
View File
@@ -0,0 +1,88 @@
#include "apps.h"
#include "os_gfx.h"
#include "os_status.h"
#include <stdio.h>
#include <time.h>
#include "esp_timer.h"
static uint8_t s_view_mode = 0;
static void clock_init(void)
{
s_view_mode = 0;
}
static void clock_enter(void)
{
}
static void clock_update(uint32_t delta_ms)
{
sys_status_t status;
sys_status_get(&status);
sys_gfx_draw_status_bar(&status);
uint32_t sec = status.uptime_sec % 60;
uint32_t min = (status.uptime_sec / 60) % 60;
uint32_t hrs = (status.uptime_sec / 3600);
char time_str[16];
char sub_str[20];
if (s_view_mode == 0) {
snprintf(time_str, sizeof(time_str), "%02lu:%02lu:%02lu", (unsigned long)hrs, (unsigned long)min, (unsigned long)sec);
sys_gfx_draw_str_5x7(12, 11, time_str, COLOR_WHITE);
snprintf(sub_str, sizeof(sub_str), "RAM:%luK", (unsigned long)(status.free_heap / 1024));
sys_gfx_draw_str_3x5(4, 21, sub_str, COLOR_WHITE);
snprintf(sub_str, sizeof(sub_str), "CPU:160M");
sys_gfx_draw_str_3x5(40, 21, sub_str, COLOR_WHITE);
sys_gfx_draw_progress_bar(4, 27, 64, 4, (uint8_t)((sec * 100) / 59));
sys_gfx_draw_action_bar("C:MODE", "H:HOME");
} else if (s_view_mode == 1) {
snprintf(time_str, sizeof(time_str), "%02lu:%02lu", (unsigned long)min, (unsigned long)sec);
sys_gfx_draw_str_5x7(18, 14, time_str, COLOR_WHITE);
sys_gfx_draw_str_5x7(19, 14, time_str, COLOR_WHITE);
snprintf(sub_str, sizeof(sub_str), "UP:%luh %lum", (unsigned long)hrs, (unsigned long)min);
sys_gfx_draw_str_3x5(12, 24, sub_str, COLOR_WHITE);
sys_gfx_draw_action_bar("C:MODE", "H:HOME");
} else {
snprintf(sub_str, sizeof(sub_str), "UP:%02lu:%02lu:%02lu", (unsigned long)hrs, (unsigned long)min, (unsigned long)sec);
sys_gfx_draw_str_3x5(3, 10, sub_str, COLOR_WHITE);
snprintf(sub_str, sizeof(sub_str), "HEAP:%lu B", (unsigned long)status.free_heap);
sys_gfx_draw_str_3x5(3, 17, sub_str, COLOR_WHITE);
snprintf(sub_str, sizeof(sub_str), "MINH:%lu B", (unsigned long)status.min_free_heap);
sys_gfx_draw_str_3x5(3, 24, sub_str, COLOR_WHITE);
sys_gfx_draw_action_bar("C:MODE", "H:HOME");
}
}
static void clock_handle_input(input_action_t action)
{
if (action == INPUT_ACTION_CLICK) {
s_view_mode = (s_view_mode + 1) % 3;
} else if (action == INPUT_ACTION_LONG_PRESS) {
sys_gfx_show_toast("Mode Saved", 600);
}
}
static void clock_exit(void)
{
}
const sys_app_t g_app_clock = {
.id = "clock",
.name = "Clock",
.init = clock_init,
.enter = clock_enter,
.update = clock_update,
.handle_input = clock_handle_input,
.exit = clock_exit,
};
+137
View File
@@ -0,0 +1,137 @@
#include "apps.h"
#include "os_gfx.h"
#include "os_status.h"
#include <stdio.h>
#include <stdlib.h>
#define GROUND_Y 34
typedef enum {
GAME_STATE_READY,
GAME_STATE_PLAYING,
GAME_STATE_OVER
} game_state_t;
static game_state_t s_game_state = GAME_STATE_READY;
static int16_t s_player_y = GROUND_Y;
static int16_t s_player_vy = 0;
static bool s_is_jumping = false;
static int16_t s_obs_x = 72;
static int16_t s_obs_h = 6;
static uint16_t s_score = 0;
static uint16_t s_high_score = 0;
static void game_reset(void)
{
s_player_y = GROUND_Y;
s_player_vy = 0;
s_is_jumping = false;
s_obs_x = 72;
s_obs_h = 5 + (rand() % 4);
s_score = 0;
}
static void game_init(void)
{
s_game_state = GAME_STATE_READY;
s_high_score = 0;
game_reset();
}
static void game_enter(void)
{
s_game_state = GAME_STATE_READY;
game_reset();
}
static void game_update(uint32_t delta_ms)
{
sys_gfx_draw_line(0, GROUND_Y + 1, OLED_WIDTH - 1, GROUND_Y + 1, COLOR_WHITE);
if (s_game_state == GAME_STATE_READY) {
sys_gfx_draw_str_5x7(10, 8, "TINY JUMP", COLOR_WHITE);
sys_gfx_draw_str_3x5(8, 20, "CLICK TO START", COLOR_WHITE);
sys_gfx_draw_action_bar("C:JUMP", "H:HOME");
sys_gfx_fill_rect(8, GROUND_Y - 5, 5, 5, COLOR_WHITE);
return;
}
if (s_game_state == GAME_STATE_PLAYING) {
if (s_is_jumping) {
s_player_y += s_player_vy;
s_player_vy += 1;
if (s_player_y >= GROUND_Y) {
s_player_y = GROUND_Y;
s_player_vy = 0;
s_is_jumping = false;
}
}
s_obs_x -= 3;
if (s_obs_x < -4) {
s_obs_x = 72 + (rand() % 16);
s_obs_h = 5 + (rand() % 5);
s_score++;
if (s_score > s_high_score) s_high_score = s_score;
}
if (s_obs_x <= 13 && (s_obs_x + 3) >= 8) {
if ((s_player_y - 1) >= (GROUND_Y - s_obs_h + 1)) {
s_game_state = GAME_STATE_OVER;
sys_gfx_show_toast("GAME OVER", 800);
}
}
sys_gfx_fill_rect(8, s_player_y - 5, 5, 5, COLOR_WHITE);
sys_gfx_fill_rect(s_obs_x, GROUND_Y - s_obs_h + 1, 3, s_obs_h, COLOR_WHITE);
char score_str[12];
snprintf(score_str, sizeof(score_str), "SC:%u", s_score);
sys_gfx_draw_str_3x5(45, 1, score_str, COLOR_WHITE);
} else if (s_game_state == GAME_STATE_OVER) {
sys_gfx_draw_str_5x7(12, 6, "GAME OVER", COLOR_WHITE);
char buf[16];
snprintf(buf, sizeof(buf), "SCORE: %u", s_score);
sys_gfx_draw_str_3x5(16, 17, buf, COLOR_WHITE);
snprintf(buf, sizeof(buf), "BEST: %u", s_high_score);
sys_gfx_draw_str_3x5(16, 24, buf, COLOR_WHITE);
sys_gfx_draw_action_bar("C:RESTART", "H:HOME");
}
}
static void game_handle_input(input_action_t action)
{
if (action == INPUT_ACTION_CLICK) {
if (s_game_state == GAME_STATE_READY) {
s_game_state = GAME_STATE_PLAYING;
game_reset();
} else if (s_game_state == GAME_STATE_PLAYING) {
if (!s_is_jumping) {
s_is_jumping = true;
s_player_vy = -5;
}
} else if (s_game_state == GAME_STATE_OVER) {
s_game_state = GAME_STATE_PLAYING;
game_reset();
}
}
}
static void game_exit(void)
{
}
const sys_app_t g_app_game = {
.id = "game",
.name = "Tiny Jump",
.init = game_init,
.enter = game_enter,
.update = game_update,
.handle_input = game_handle_input,
.exit = game_exit,
};
+99
View File
@@ -0,0 +1,99 @@
#include "apps.h"
#include "os_gfx.h"
#include "os_status.h"
#include "os_oled.h"
#include <stdio.h>
#include "esp_system.h"
#define SETTINGS_COUNT 4
static const char *s_menu_items[SETTINGS_COUNT] = {
"Contrast",
"Invert",
"Status Bar",
"Reboot"
};
static size_t s_selected_idx = 0;
static uint8_t s_contrast_level = 3;
static bool s_inverted = false;
static void apply_contrast(void)
{
uint8_t val = 255;
if (s_contrast_level == 0) val = 30;
else if (s_contrast_level == 1) val = 100;
else if (s_contrast_level == 2) val = 180;
oled_driver_set_contrast(val);
}
static void settings_init(void)
{
s_selected_idx = 0;
s_contrast_level = 3;
s_inverted = false;
}
static void settings_enter(void)
{
}
static void settings_update(uint32_t delta_ms)
{
sys_status_t status;
sys_status_get(&status);
sys_gfx_draw_status_bar(&status);
sys_gfx_draw_menu(9, s_menu_items, SETTINGS_COUNT, s_selected_idx);
sys_gfx_draw_action_bar("C:NEXT", "H:TOGGLE");
}
static void settings_handle_input(input_action_t action)
{
if (action == INPUT_ACTION_CLICK) {
s_selected_idx = (s_selected_idx + 1) % SETTINGS_COUNT;
} else if (action == INPUT_ACTION_DOUBLE_CLICK) {
if (s_selected_idx == 0) s_selected_idx = SETTINGS_COUNT - 1;
else s_selected_idx--;
} else if (action == INPUT_ACTION_LONG_PRESS) {
switch (s_selected_idx) {
case 0:
s_contrast_level = (s_contrast_level + 1) % 4;
apply_contrast();
sys_gfx_show_toast("Contrast Chg", 600);
break;
case 1:
s_inverted = !s_inverted;
oled_driver_set_invert(s_inverted);
sys_gfx_show_toast(s_inverted ? "Inverted" : "Normal", 600);
break;
case 2:
{
bool vis = sys_status_is_status_bar_visible();
sys_status_set_status_bar_visible(!vis);
sys_gfx_show_toast(!vis ? "Bar: ON" : "Bar: OFF", 600);
}
break;
case 3:
sys_gfx_show_toast("Rebooting...", 1000);
sys_gfx_flush();
esp_restart();
break;
default:
break;
}
}
}
static void settings_exit(void)
{
}
const sys_app_t g_app_settings = {
.id = "settings",
.name = "Settings",
.init = settings_init,
.enter = settings_enter,
.update = settings_update,
.handle_input = settings_handle_input,
.exit = settings_exit,
};
+96
View File
@@ -0,0 +1,96 @@
#include "apps.h"
#include "os_gfx.h"
#include "os_status.h"
#include <stdio.h>
#include <string.h>
#include "esp_system.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#define HISTORY_LEN 40
static uint8_t s_heap_history[HISTORY_LEN] = {0};
static size_t s_history_count = 0;
static uint8_t s_view = 0;
static void sysmon_init(void)
{
s_history_count = 0;
memset(s_heap_history, 50, sizeof(s_heap_history));
}
static void sysmon_enter(void)
{
}
static void sysmon_update(uint32_t delta_ms)
{
sys_status_t status;
sys_status_get(&status);
sys_gfx_draw_status_bar(&status);
static uint32_t sample_timer = 0;
sample_timer += delta_ms;
if (sample_timer >= 200) {
sample_timer = 0;
uint32_t kb = status.free_heap / 1024;
uint8_t norm_val = (kb > 300) ? 100 : (uint8_t)((kb * 100) / 300);
if (s_history_count < HISTORY_LEN) {
s_heap_history[s_history_count++] = norm_val;
} else {
memmove(&s_heap_history[0], &s_heap_history[1], HISTORY_LEN - 1);
s_heap_history[HISTORY_LEN - 1] = norm_val;
}
}
char buf[24];
if (s_view == 0) {
snprintf(buf, sizeof(buf), "RAM:%luK", (unsigned long)(status.free_heap / 1024));
sys_gfx_draw_str_3x5(3, 9, buf, COLOR_WHITE);
snprintf(buf, sizeof(buf), "TASKS:%u", (unsigned int)uxTaskGetNumberOfTasks());
sys_gfx_draw_str_3x5(40, 9, buf, COLOR_WHITE);
sys_gfx_draw_sparkline(3, 16, 66, 14, s_heap_history, s_history_count, 100);
sys_gfx_draw_action_bar("C:VIEW", "H:HOME");
} else {
esp_chip_info_t chip;
esp_chip_info(&chip);
uint32_t flash_size = 0;
esp_flash_get_size(NULL, &flash_size);
sys_gfx_draw_str_3x5(3, 9, "ESP32-C3 RISC-V", COLOR_WHITE);
snprintf(buf, sizeof(buf), "REV:%d CORES:%d", chip.revision, chip.cores);
sys_gfx_draw_str_3x5(3, 16, buf, COLOR_WHITE);
snprintf(buf, sizeof(buf), "FLASH:%lu MB", (unsigned long)(flash_size / (1024 * 1024)));
sys_gfx_draw_str_3x5(3, 23, buf, COLOR_WHITE);
sys_gfx_draw_action_bar("C:VIEW", "H:HOME");
}
}
static void sysmon_handle_input(input_action_t action)
{
if (action == INPUT_ACTION_CLICK) {
s_view = (s_view + 1) % 2;
}
}
static void sysmon_exit(void)
{
}
const sys_app_t g_app_sysmon = {
.id = "sysmon",
.name = "Sys Monitor",
.init = sysmon_init,
.enter = sysmon_enter,
.update = sysmon_update,
.handle_input = sysmon_handle_input,
.exit = sysmon_exit,
};
+19
View File
@@ -0,0 +1,19 @@
#ifndef APPS_H
#define APPS_H
#include "os_app.h"
#ifdef __cplusplus
extern "C" {
#endif
extern const sys_app_t g_app_clock;
extern const sys_app_t g_app_sysmon;
extern const sys_app_t g_app_settings;
extern const sys_app_t g_app_game;
#ifdef __cplusplus
}
#endif
#endif // APPS_H
+110
View File
@@ -0,0 +1,110 @@
#ifndef FONT_5X7_H
#define FONT_5X7_H
#include <stdint.h>
/**
* @brief Standard 5x7 ASCII font table (ASCII characters 32 ' ' to 127 '~')
* Each character is 5 bytes wide, 7 bits high (MSB to LSB or LSB to MSB).
* 1 byte per column.
*/
static const uint8_t font_5x7[][5] = {
{0x00, 0x00, 0x00, 0x00, 0x00}, // ' ' (32)
{0x00, 0x00, 0x5F, 0x00, 0x00}, // '!'
{0x00, 0x07, 0x00, 0x07, 0x00}, // '"'
{0x14, 0x7F, 0x14, 0x7F, 0x14}, // '#'
{0x24, 0x2A, 0x7F, 0x2A, 0x12}, // '$'
{0x23, 0x13, 0x08, 0x64, 0x62}, // '%'
{0x36, 0x49, 0x55, 0x22, 0x50}, // '&'
{0x00, 0x05, 0x03, 0x00, 0x00}, // '''
{0x00, 0x1C, 0x22, 0x41, 0x00}, // '('
{0x00, 0x41, 0x22, 0x1C, 0x00}, // ')'
{0x14, 0x08, 0x3E, 0x08, 0x14}, // '*'
{0x08, 0x08, 0x3E, 0x08, 0x08}, // '+'
{0x00, 0x50, 0x30, 0x00, 0x00}, // ','
{0x08, 0x08, 0x08, 0x08, 0x08}, // '-'
{0x00, 0x60, 0x60, 0x00, 0x00}, // '.'
{0x20, 0x10, 0x08, 0x04, 0x02}, // '/'
{0x3E, 0x51, 0x49, 0x45, 0x3E}, // '0'
{0x00, 0x42, 0x7F, 0x40, 0x00}, // '1'
{0x42, 0x61, 0x51, 0x49, 0x46}, // '2'
{0x21, 0x41, 0x45, 0x4B, 0x31}, // '3'
{0x18, 0x14, 0x12, 0x7F, 0x10}, // '4'
{0x27, 0x45, 0x45, 0x45, 0x39}, // '5'
{0x3C, 0x4A, 0x49, 0x49, 0x30}, // '6'
{0x01, 0x71, 0x09, 0x05, 0x03}, // '7'
{0x36, 0x49, 0x49, 0x49, 0x36}, // '8'
{0x06, 0x49, 0x49, 0x29, 0x1E}, // '9'
{0x00, 0x36, 0x36, 0x00, 0x00}, // ':'
{0x00, 0x56, 0x36, 0x00, 0x00}, // ';'
{0x08, 0x14, 0x22, 0x41, 0x00}, // '<'
{0x14, 0x14, 0x14, 0x14, 0x14}, // '='
{0x00, 0x41, 0x22, 0x14, 0x08}, // '>'
{0x02, 0x01, 0x51, 0x09, 0x06}, // '?'
{0x32, 0x49, 0x79, 0x41, 0x3E}, // '@'
{0x7E, 0x11, 0x11, 0x11, 0x7E}, // 'A'
{0x7F, 0x49, 0x49, 0x49, 0x36}, // 'B'
{0x3E, 0x41, 0x41, 0x41, 0x22}, // 'C'
{0x7F, 0x41, 0x41, 0x22, 0x1C}, // 'D'
{0x7F, 0x49, 0x49, 0x49, 0x41}, // 'E'
{0x7F, 0x09, 0x09, 0x09, 0x01}, // 'F'
{0x3E, 0x41, 0x49, 0x49, 0x7A}, // 'G'
{0x7F, 0x08, 0x08, 0x08, 0x7F}, // 'H'
{0x00, 0x41, 0x7F, 0x41, 0x00}, // 'I'
{0x20, 0x40, 0x41, 0x3F, 0x01}, // 'J'
{0x7F, 0x08, 0x14, 0x22, 0x41}, // 'K'
{0x7F, 0x40, 0x40, 0x40, 0x40}, // 'L'
{0x7F, 0x02, 0x0C, 0x02, 0x7F}, // 'M'
{0x7F, 0x04, 0x08, 0x10, 0x7F}, // 'N'
{0x3E, 0x41, 0x41, 0x41, 0x3E}, // 'O'
{0x7F, 0x09, 0x09, 0x09, 0x06}, // 'P'
{0x3E, 0x41, 0x51, 0x21, 0x5E}, // 'Q'
{0x7F, 0x09, 0x19, 0x29, 0x46}, // 'R'
{0x46, 0x49, 0x49, 0x49, 0x31}, // 'S'
{0x01, 0x01, 0x7F, 0x01, 0x01}, // 'T'
{0x3F, 0x40, 0x40, 0x40, 0x3F}, // 'U'
{0x1F, 0x20, 0x40, 0x20, 0x1F}, // 'V'
{0x3F, 0x40, 0x38, 0x40, 0x3F}, // 'W'
{0x63, 0x14, 0x08, 0x14, 0x63}, // 'X'
{0x07, 0x08, 0x70, 0x08, 0x07}, // 'Y'
{0x61, 0x51, 0x49, 0x45, 0x43}, // 'Z'
{0x00, 0x7F, 0x41, 0x41, 0x00}, // '['
{0x02, 0x04, 0x08, 0x10, 0x20}, // '\'
{0x00, 0x41, 0x41, 0x7F, 0x00}, // ']'
{0x04, 0x02, 0x01, 0x02, 0x04}, // '^'
{0x40, 0x40, 0x40, 0x40, 0x40}, // '_'
{0x00, 0x01, 0x02, 0x04, 0x00}, // '`'
{0x20, 0x54, 0x54, 0x54, 0x78}, // 'a'
{0x7F, 0x48, 0x44, 0x44, 0x38}, // 'b'
{0x38, 0x44, 0x44, 0x44, 0x20}, // 'c'
{0x38, 0x44, 0x44, 0x48, 0x7F}, // 'd'
{0x38, 0x54, 0x54, 0x54, 0x18}, // 'e'
{0x08, 0x7E, 0x09, 0x01, 0x02}, // 'f'
{0x0C, 0x52, 0x52, 0x52, 0x3E}, // 'g'
{0x7F, 0x08, 0x04, 0x04, 0x78}, // 'h'
{0x00, 0x44, 0x7D, 0x40, 0x00}, // 'i'
{0x20, 0x40, 0x44, 0x3D, 0x00}, // 'j'
{0x7F, 0x10, 0x28, 0x44, 0x00}, // 'k'
{0x00, 0x41, 0x7F, 0x40, 0x00}, // 'l'
{0x7C, 0x04, 0x18, 0x04, 0x78}, // 'm'
{0x7C, 0x08, 0x04, 0x04, 0x78}, // 'n'
{0x38, 0x44, 0x44, 0x44, 0x38}, // 'o'
{0x7C, 0x14, 0x14, 0x14, 0x08}, // 'p'
{0x08, 0x14, 0x14, 0x18, 0x7C}, // 'q'
{0x7C, 0x08, 0x04, 0x04, 0x08}, // 'r'
{0x48, 0x54, 0x54, 0x54, 0x20}, // 's'
{0x04, 0x3F, 0x44, 0x40, 0x20}, // 't'
{0x3C, 0x40, 0x40, 0x20, 0x7C}, // 'u'
{0x1C, 0x20, 0x40, 0x20, 0x1C}, // 'v'
{0x3C, 0x40, 0x30, 0x40, 0x3C}, // 'w'
{0x44, 0x28, 0x10, 0x28, 0x44}, // 'x'
{0x0C, 0x50, 0x50, 0x50, 0x3C}, // 'y'
{0x44, 0x64, 0x54, 0x4C, 0x44}, // 'z'
{0x00, 0x08, 0x36, 0x41, 0x00}, // '{'
{0x00, 0x00, 0x7F, 0x00, 0x00}, // '|'
{0x00, 0x41, 0x36, 0x08, 0x00}, // '}'
{0x08, 0x08, 0x2A, 0x1C, 0x08} // '~'
};
#endif // FONT_5X7_H
+117
View File
@@ -0,0 +1,117 @@
#include <stdio.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "driver/gpio.h"
/* OS Subsystems */
#include "os_event.h"
#include "os_status.h"
#include "os_gfx.h"
#include "os_input.h"
#include "os_app.h"
#include "os_cli.h"
#include "apps.h"
static const char *TAG = "MICRO_OS";
#define PIN_LED_USER GPIO_NUM_8
#define PIN_BUTTON_BOOT GPIO_NUM_9
#define UI_REFRESH_HZ 20
#define UI_FRAME_MS (1000 / UI_REFRESH_HZ)
/* Task: Onboard LED Heartbeat */
static void led_heartbeat_task(void *pvParameters)
{
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << PIN_LED_USER),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&io_conf);
while (1) {
gpio_set_level(PIN_LED_USER, 1);
vTaskDelay(pdMS_TO_TICKS(60));
gpio_set_level(PIN_LED_USER, 0);
vTaskDelay(pdMS_TO_TICKS(100));
gpio_set_level(PIN_LED_USER, 1);
vTaskDelay(pdMS_TO_TICKS(60));
gpio_set_level(PIN_LED_USER, 0);
vTaskDelay(pdMS_TO_TICKS(800));
}
}
/* Callback: Forward button gestures to App Manager */
static void on_input_event(input_action_t action, void *user_arg)
{
sys_app_dispatch_input(action);
}
/* Task: OS Display & UI Compositor Loop */
static void os_ui_task(void *pvParameters)
{
ESP_LOGI(TAG, "Starting UI compositor loop @ %d FPS", UI_REFRESH_HZ);
uint32_t last_time_ms = (uint32_t)(esp_timer_get_time() / 1000ULL);
while (1) {
uint32_t now_ms = (uint32_t)(esp_timer_get_time() / 1000ULL);
uint32_t delta_ms = now_ms - last_time_ms;
last_time_ms = now_ms;
sys_status_update();
sys_gfx_clear();
sys_app_update(delta_ms);
sys_gfx_render_toast_if_active();
sys_gfx_flush();
vTaskDelay(pdMS_TO_TICKS(UI_FRAME_MS));
}
}
void app_main(void)
{
ESP_LOGI(TAG, "=============================================");
ESP_LOGI(TAG, " Booting ABrobot ESP32-C3 Micro-OS v1.0.0 ");
ESP_LOGI(TAG, "=============================================");
// 1. Core Services & Telemetry
sys_event_init();
sys_status_init();
// 2. Display & Graphics Subsystem
esp_err_t err = sys_gfx_init();
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize graphics subsystem: %s", esp_err_to_name(err));
}
// 3. Button & Gesture Engine
sys_input_init(PIN_BUTTON_BOOT);
sys_input_register_handler(on_input_event, NULL);
// 4. Application Framework & Registry
sys_app_init();
sys_app_register(&g_app_clock);
sys_app_register(&g_app_sysmon);
sys_app_register(&g_app_game);
sys_app_register(&g_app_settings);
// Launch default startup app
sys_app_launch_by_id("clock");
// 5. Interactive USB Console Shell
sys_cli_init();
// 6. Launch OS Tasks
xTaskCreate(led_heartbeat_task, "led_hb_task", 2048, NULL, 5, NULL);
xTaskCreate(os_ui_task, "ui_task", 4096, NULL, 10, NULL);
ESP_LOGI(TAG, "Micro-OS initialized and running.");
}
+260
View File
@@ -0,0 +1,260 @@
#include "oled_display.h"
#include "font_5x7.h"
#include <string.h>
#include <stdlib.h>
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
static const char *TAG = "OLED_DISPLAY";
/* Internal frame buffer: 72 columns x 5 pages = 360 bytes */
static uint8_t s_frame_buffer[OLED_BUFFER_SIZE];
static uint8_t s_column_offset = OLED_COLUMN_OFFSET;
/* Helper: Send command to OLED */
static esp_err_t oled_send_cmd(uint8_t cmd)
{
i2c_cmd_handle_t link = i2c_cmd_link_create();
i2c_master_start(link);
i2c_master_write_byte(link, (OLED_I2C_ADDR << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(link, 0x00, true); // Control byte: 0x00 = Command
i2c_master_write_byte(link, cmd, true);
i2c_master_stop(link);
esp_err_t ret = i2c_master_cmd_begin(OLED_I2C_PORT, link, pdMS_TO_TICKS(50));
i2c_cmd_link_delete(link);
return ret;
}
/* Helper: Send multiple commands to OLED */
static esp_err_t oled_send_cmds(const uint8_t *cmds, size_t len)
{
i2c_cmd_handle_t link = i2c_cmd_link_create();
i2c_master_start(link);
i2c_master_write_byte(link, (OLED_I2C_ADDR << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(link, 0x00, true); // Control byte: 0x00 = Command
i2c_master_write(link, cmds, len, true);
i2c_master_stop(link);
esp_err_t ret = i2c_master_cmd_begin(OLED_I2C_PORT, link, pdMS_TO_TICKS(100));
i2c_cmd_link_delete(link);
return ret;
}
esp_err_t oled_init(gpio_num_t sda, gpio_num_t scl, uint8_t col_offset)
{
s_column_offset = col_offset;
// 1. Configure I2C bus
i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = sda,
.scl_io_num = scl,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = OLED_I2C_FREQ_HZ,
};
esp_err_t err = i2c_param_config(OLED_I2C_PORT, &conf);
if (err != ESP_OK) {
ESP_LOGE(TAG, "i2c_param_config failed: %s", esp_err_to_name(err));
return err;
}
err = i2c_driver_install(OLED_I2C_PORT, conf.mode, 0, 0, 0);
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
ESP_LOGE(TAG, "i2c_driver_install failed: %s", esp_err_to_name(err));
return err;
}
vTaskDelay(pdMS_TO_TICKS(50)); // Allow display power to stabilize
// 2. Display initialization sequence for 72x40 OLED (SH1106 / SSD1306 compatible)
const uint8_t init_cmds[] = {
0xAE, // Display OFF
0xD5, 0x80, // Set Display Clock Divide Ratio / Osc Frequency
0xA8, 0x27, // Set Multiplex Ratio (40 - 1 = 39 = 0x27)
0xD3, 0x00, // Set Display Offset to 0
0x40, // Set Display Start Line to 0
0x8D, 0x14, // Enable Charge Pump
0x20, 0x02, // Set Memory Addressing Mode to Page mode (compatible with SH1106)
0xA1, // Set Segment Re-map (A0/A1)
0xC8, // Set COM Output Scan Direction (C0/C8)
0xDA, 0x12, // Set COM Pins hardware config
0x81, 0xAF, // Set Contrast Control (0xAF)
0xD9, 0xF1, // Set Pre-charge Period
0xDB, 0x20, // Set VCOMH Deselect Level
0xA4, // Resume to RAM content (Entire Display ON disabled)
0xA6, // Normal display (non-inverted)
0xAF // Display ON
};
err = oled_send_cmds(init_cmds, sizeof(init_cmds));
if (err != ESP_OK) {
ESP_LOGE(TAG, "OLED init commands failed: %s (Check I2C connections)", esp_err_to_name(err));
return err;
}
oled_clear();
oled_update();
ESP_LOGI(TAG, "OLED initialized (72x40, offset: %d, SDA: %d, SCL: %d)",
s_column_offset, (int)sda, (int)scl);
return ESP_OK;
}
void oled_clear(void)
{
memset(s_frame_buffer, 0x00, sizeof(s_frame_buffer));
}
void oled_fill(void)
{
memset(s_frame_buffer, 0xFF, sizeof(s_frame_buffer));
}
esp_err_t oled_update(void)
{
for (uint8_t page = 0; page < OLED_PAGES; page++) {
// Page address + column address setup
uint8_t page_cmds[] = {
(uint8_t)(0xB0 | page),
(uint8_t)(0x00 | (s_column_offset & 0x0F)),
(uint8_t)(0x10 | ((s_column_offset >> 4) & 0x0F))
};
esp_err_t err = oled_send_cmds(page_cmds, sizeof(page_cmds));
if (err != ESP_OK) {
return err;
}
// Push 72 bytes of pixel data for the current page
i2c_cmd_handle_t link = i2c_cmd_link_create();
i2c_master_start(link);
i2c_master_write_byte(link, (OLED_I2C_ADDR << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(link, 0x40, true); // Control byte: 0x40 = Data
i2c_master_write(link, &s_frame_buffer[page * OLED_WIDTH], OLED_WIDTH, true);
i2c_master_stop(link);
err = i2c_master_cmd_begin(OLED_I2C_PORT, link, pdMS_TO_TICKS(50));
i2c_cmd_link_delete(link);
if (err != ESP_OK) {
return err;
}
}
return ESP_OK;
}
esp_err_t oled_set_contrast(uint8_t contrast)
{
uint8_t cmds[] = {0x81, contrast};
return oled_send_cmds(cmds, sizeof(cmds));
}
esp_err_t oled_invert(bool invert)
{
return oled_send_cmd(invert ? 0xA7 : 0xA6);
}
void oled_draw_pixel(int16_t x, int16_t y, uint8_t color)
{
if (x < 0 || x >= OLED_WIDTH || y < 0 || y >= OLED_HEIGHT) {
return;
}
uint16_t index = (y / 8) * OLED_WIDTH + x;
uint8_t bit = y % 8;
switch (color) {
case OLED_COLOR_WHITE:
s_frame_buffer[index] |= (1 << bit);
break;
case OLED_COLOR_BLACK:
s_frame_buffer[index] &= ~(1 << bit);
break;
case OLED_COLOR_INVERT:
s_frame_buffer[index] ^= (1 << bit);
break;
default:
break;
}
}
void oled_draw_line(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint8_t color)
{
int16_t dx = abs(x1 - x0);
int16_t dy = abs(y1 - y0);
int16_t sx = (x0 < x1) ? 1 : -1;
int16_t sy = (y0 < y1) ? 1 : -1;
int16_t err = dx - dy;
while (1) {
oled_draw_pixel(x0, y0, color);
if (x0 == x1 && y0 == y1) break;
int16_t e2 = 2 * err;
if (e2 > -dy) {
err -= dy;
x0 += sx;
}
if (e2 < dx) {
err += dx;
y0 += sy;
}
}
}
void oled_draw_rect(int16_t x, int16_t y, int16_t w, int16_t h, uint8_t color)
{
if (w <= 0 || h <= 0) return;
oled_draw_line(x, y, x + w - 1, y, color);
oled_draw_line(x, y + h - 1, x + w - 1, y + h - 1, color);
oled_draw_line(x, y, x, y + h - 1, color);
oled_draw_line(x + w - 1, y, x + w - 1, y + h - 1, color);
}
void oled_fill_rect(int16_t x, int16_t y, int16_t w, int16_t h, uint8_t color)
{
for (int16_t i = x; i < x + w; i++) {
for (int16_t j = y; j < y + h; j++) {
oled_draw_pixel(i, j, color);
}
}
}
void oled_draw_char(int16_t x, int16_t y, char c, uint8_t color)
{
if (c < 32 || c > 127) {
c = '?';
}
const uint8_t *char_data = font_5x7[c - 32];
for (uint8_t col = 0; col < 5; col++) {
uint8_t line = char_data[col];
for (uint8_t row = 0; row < 7; row++) {
if (line & (1 << row)) {
oled_draw_pixel(x + col, y + row, color);
} else if (color == OLED_COLOR_WHITE) {
oled_draw_pixel(x + col, y + row, OLED_COLOR_BLACK);
}
}
}
}
void oled_draw_string(int16_t x, int16_t y, const char *str, uint8_t color)
{
if (!str) return;
int16_t cur_x = x;
int16_t cur_y = y;
while (*str) {
if (*str == '\n') {
cur_x = x;
cur_y += 8;
} else {
oled_draw_char(cur_x, cur_y, *str, color);
cur_x += 6; // 5 pixels width + 1 pixel inter-character spacing
}
str++;
}
}
+129
View File
@@ -0,0 +1,129 @@
#ifndef OLED_DISPLAY_H
#define OLED_DISPLAY_H
#include <stdint.h>
#include <stdbool.h>
#include "driver/gpio.h"
#include "driver/i2c.h"
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
/* Hardware Specifications for ABrobot ESP32-C3 0.42" OLED */
#define OLED_I2C_PORT I2C_NUM_0
#define OLED_I2C_ADDR 0x3C
#define OLED_PIN_SDA GPIO_NUM_5
#define OLED_PIN_SCL GPIO_NUM_6
#define OLED_I2C_FREQ_HZ 400000
/* Display Resolution */
#define OLED_WIDTH 72
#define OLED_HEIGHT 40
#define OLED_PAGES 5 /* (40 / 8) */
#define OLED_BUFFER_SIZE (OLED_WIDTH * OLED_PAGES)
/* Controller Column Offset (SSD1306/SH1106 internal RAM mapping) */
#ifndef OLED_COLUMN_OFFSET
#define OLED_COLUMN_OFFSET 28 /* Common offset is 28 (or 26-30 depending on batch) */
#endif
/* Color modes */
#define OLED_COLOR_BLACK 0
#define OLED_COLOR_WHITE 1
#define OLED_COLOR_INVERT 2
/**
* @brief Initialize I2C driver and SSD1306/SH1106 OLED display.
*
* @param sda GPIO number for SDA (default: GPIO_NUM_5)
* @param scl GPIO number for SCL (default: GPIO_NUM_6)
* @param col_offset Internal column offset (default: OLED_COLUMN_OFFSET)
* @return esp_err_t ESP_OK on success
*/
esp_err_t oled_init(gpio_num_t sda, gpio_num_t scl, uint8_t col_offset);
/**
* @brief Clear the frame buffer (all black).
*/
void oled_clear(void);
/**
* @brief Fill the entire frame buffer (all white).
*/
void oled_fill(void);
/**
* @brief Push the frame buffer to the OLED display over I2C.
*
* @return esp_err_t ESP_OK on success
*/
esp_err_t oled_update(void);
/**
* @brief Set display contrast.
*
* @param contrast Value 0 to 255
* @return esp_err_t ESP_OK on success
*/
esp_err_t oled_set_contrast(uint8_t contrast);
/**
* @brief Invert the display colors in hardware.
*
* @param invert true for inverted, false for normal
* @return esp_err_t ESP_OK on success
*/
esp_err_t oled_invert(bool invert);
/**
* @brief Draw a single pixel in the frame buffer.
*
* @param x X coordinate (0 to 71)
* @param y Y coordinate (0 to 39)
* @param color OLED_COLOR_BLACK, OLED_COLOR_WHITE, or OLED_COLOR_INVERT
*/
void oled_draw_pixel(int16_t x, int16_t y, uint8_t color);
/**
* @brief Draw a line using Bresenham's algorithm.
*/
void oled_draw_line(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint8_t color);
/**
* @brief Draw an unfilled rectangle.
*/
void oled_draw_rect(int16_t x, int16_t y, int16_t w, int16_t h, uint8_t color);
/**
* @brief Draw a filled rectangle.
*/
void oled_fill_rect(int16_t x, int16_t y, int16_t w, int16_t h, uint8_t color);
/**
* @brief Draw a single character using the 5x7 font.
*
* @param x X position (top-left)
* @param y Y position (top-left)
* @param c ASCII character
* @param color OLED_COLOR_WHITE or OLED_COLOR_BLACK
*/
void oled_draw_char(int16_t x, int16_t y, char c, uint8_t color);
/**
* @brief Draw a text string using the 5x7 font.
*
* @param x X position (top-left)
* @param y Y position (top-left)
* @param str Null-terminated string
* @param color OLED_COLOR_WHITE or OLED_COLOR_BLACK
*/
void oled_draw_string(int16_t x, int16_t y, const char *str, uint8_t color);
#ifdef __cplusplus
}
#endif
#endif // OLED_DISPLAY_H