aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorConfuSomu2024-02-04 14:38:58 -0500
committerConfuSomu2024-02-04 14:38:58 -0500
commit9a74ae13182061a239f308011a3fd3ba8121c015 (patch)
tree836f7b6848bd0867338966955c242e5a17d33407
parent6b306c8dcd65016b6e022c648e2477931f9c4b95 (diff)
downloadpico-watch-9a74ae13182061a239f308011a3fd3ba8121c015.tar
pico-watch-9a74ae13182061a239f308011a3fd3ba8121c015.tar.gz
pico-watch-9a74ae13182061a239f308011a3fd3ba8121c015.zip
Implement basic SDL2 port
This allows using pico watch on a desktop computer/host which allows a faster development cycle and easy screenshots.
-rw-r--r--CMakeLists.txt32
-rw-r--r--buttons.cpp42
-rw-r--r--buttons.hpp1
-rw-r--r--globals.hpp5
-rw-r--r--hal/api.cpp25
-rw-r--r--hal/api.hpp9
-rw-r--r--hal/sdl2/display.cpp137
-rw-r--r--hal/sdl2/display.hpp30
-rw-r--r--init.cpp6
-rw-r--r--oled/BitBang_I2C.c4
-rw-r--r--oled/ss_oled.c10
-rw-r--r--pico-watch.cpp24
12 files changed, 309 insertions, 16 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0efeb1f..e5d96e9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,10 +2,20 @@ cmake_minimum_required(VERSION 3.12)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # For Kate's LSP
+
+# Compile option for SDL2 build
+option(SDL2_BUILD "Build an SDL2 version that can be run on a computer." OFF)
+
+if (SDL2_BUILD)
+ set(PICO_PLATFORM host)
+ set(PICO_DEOPTIMIZED_DEBUG 1)
+ add_compile_definitions(SDL2_BUILD PICO_PLATFORM=host PICO_DEOPTIMIZED_DEBUG=1)
+endif(SDL2_BUILD)
# initalize pico_sdk from installed location
# (note this can come from environment, CMake cache etc)
-set(PICO_SDK_PATH "/home/pi/pico/pico-sdk")
+#set(PICO_SDK_PATH "/home/pi/pico/pico-sdk")
# Pull in Pico SDK (must be before project)
include(pico_sdk_import.cmake)
@@ -23,7 +33,8 @@ add_compile_definitions(PICO_DEBUG_MALLOC PICO_DEBUG_MALLOC_LOW_WATER=0 PICO_USE
# We still have to factor in global variables, so this is not precise. The best place for information seems to be the pico-wath.elf.map file.
# Small size used for testing, seems to work fine.
-add_compile_definitions(PICO_HEAP_SIZE=0x200)
+# TODO SDL: reduce back to 0x200 and see what happens (should work fine and change nothing as 60000 B malloc worked)
+add_compile_definitions(PICO_HEAP_SIZE=0x20000)
# Initialise the Pico SDK
pico_sdk_init()
@@ -35,7 +46,7 @@ add_library(Oled
oled/ss_oled.c
oled/ss_oled.h
)
-target_link_libraries(Oled pico_stdlib hardware_i2c)
+target_link_libraries(Oled pico_stdlib)
# Main code
add_executable(pico-watch
@@ -46,6 +57,8 @@ add_executable(pico-watch
buttons.cpp
buttons.hpp
hal/api.cpp
+ hal/sdl2/display.hpp
+ hal/sdl2/display.cpp
apps/home_menu/main.cpp
apps/home_menu/main.hpp
apps/main_clock/main.cpp
@@ -64,9 +77,18 @@ target_link_libraries(pico-watch pico_stdlib)
# Add any user requested libraries
target_link_libraries(pico-watch
- Oled
+ Oled)
+
+if(SDL2_BUILD)
+ find_package(SDL2 REQUIRED)
+ include_directories(${SDL2_INCLUDE_DIRS})
+ target_link_libraries(pico-watch ${SDL2_LIBRARIES})
+else()
+ target_link_libraries(pico-watch
+ hardware_i2c
hardware_rtc
hardware_sync # For use of __wfi()
- )
+)
+endif()
pico_add_extra_outputs(pico-watch)
diff --git a/buttons.cpp b/buttons.cpp
index bc0ba74..589619c 100644
--- a/buttons.cpp
+++ b/buttons.cpp
@@ -1,5 +1,4 @@
#include <stdio.h>
-#include "pico/stdlib.h"
#include "buttons.hpp"
#include "globals.hpp"
@@ -12,6 +11,8 @@ extern Api app_api;
// Rising edge and falling edge are "inverted" as every button is set to be pulled up. This means that the falling edge is done when the button is pressed and the rising edge when the button is released.
+// SDL2 TODO: Implement button support
+#ifndef SDL2_BUILD
void gpio_interrupt_cb(uint gpio, uint32_t events) {
auto delta_since_press = time_since_button_press();
@@ -72,3 +73,42 @@ void init_buttons() {
*/
g_s.button_last_pressed_time = to_ms_since_boot(get_absolute_time());
}
+
+#else // SDL2_BUILD
+// this function will have to be called by SDL2 on keyboard/button press
+// gpio: button pressed
+// events: 0 is when button released and 1 is when button pressed
+void gpio_interrupt_cb(uint gpio, uint32_t events) {
+ auto delta_since_press = time_since_button_press();
+
+ if (delta_since_press > g_s.button_delay_time) {
+
+ if (app_api.m_interpret_button_press) {
+ switch (events) {
+ case 1:
+ if (gpio == BUTTON_HOME && (g_s.foreground_app->app_get_attributes().id != 0)) // Home app
+ app_mgr::app_switch_request(0);
+ else
+ app_mgr::app_btnpressed(g_s.foreground_app, gpio, delta_since_press);
+ break;
+
+ case 0:
+ if (gpio != BUTTON_HOME) // For simplicity's sake
+ app_mgr::app_btnreleased(g_s.foreground_app, gpio, delta_since_press);
+ break;
+ }
+
+ }
+
+ if (events == 1)
+ app_api.button_last_set(gpio);
+ g_s.button_last_pressed_time = to_ms_since_boot(get_absolute_time());
+ }
+}
+unsigned long time_since_button_press() {
+ // FIXME: This does not correct overflows
+ // std::cout << to_ms_since_boot(get_absolute_time());
+ return to_ms_since_boot(get_absolute_time())-g_s.button_last_pressed_time;
+}
+void init_buttons() {}
+#endif
diff --git a/buttons.hpp b/buttons.hpp
index f3ee020..2c4cbff 100644
--- a/buttons.hpp
+++ b/buttons.hpp
@@ -1,5 +1,6 @@
#pragma once
#include "init.hpp"
+#include "pico/stdlib.h"
// Init buttons used in conjuction with interrupts
// All buttons are connected to ground.
diff --git a/globals.hpp b/globals.hpp
index fd24542..87c0ab6 100644
--- a/globals.hpp
+++ b/globals.hpp
@@ -1,8 +1,9 @@
#pragma once
#include "app/base_app.hpp"
+#include <stdio.h>
struct global_status {
- BaseApp* foreground_app = 0;
+ BaseApp* foreground_app = nullptr;
bool is_sleeping = false;
bool app_ready = true;
bool app_switch_requested = false;
@@ -25,4 +26,4 @@ struct user_settings {
};
extern global_status g_s;
-extern user_settings g_user; \ No newline at end of file
+extern user_settings g_user;
diff --git a/hal/api.cpp b/hal/api.cpp
index 92b3cd1..f15c4ec 100644
--- a/hal/api.cpp
+++ b/hal/api.cpp
@@ -1,10 +1,15 @@
#include <stdio.h>
+#ifndef SDL2_BUILD
#include "pico/stdlib.h"
extern "C" {
#include "hardware/rtc.h"
}
+#else
+#include "sdl2/display.hpp"
+#endif
#include "api.hpp"
+#include "../buttons.hpp"
#include "../init.hpp"
#include "../globals.hpp"
@@ -35,6 +40,7 @@ void Api::display_set_contrast(unsigned char contrast) {
int Api::display_write_string(int iScrollX, int x, int y, const char *szMsg, int iSize, int bInvert, int bRender) {
oledWriteString(&m_oled, iScrollX, x, y, szMsg, iSize, bInvert, 0);
m_writebb_needed = true;
+ return 0;
}
void Api::display_fill(unsigned char ucData, int bRender) {
@@ -60,10 +66,15 @@ void Api::display_write_buffer(uint8_t *pBuffer) {
oledDumpBuffer(&m_oled, pBuffer);
}
-void Api::display_write_backbuffer() {
+bool Api::display_write_backbuffer() {
+#ifndef SDL2_BUILD
if (m_writebb_needed)
oledDumpBuffer(&m_oled, m_ucBuffer);
m_writebb_needed = false;
+ return false;
+#else
+ return m_sdl_display.update_display(m_ucBuffer);
+#endif
}
int Api::display_write_pixel(int x, int y, unsigned char ucColor, int bRender) {
@@ -284,7 +295,7 @@ bool Api::gui_footer_text(std::string text, int offset_x, int offset_row, bool i
oledRectangle(&m_oled, 0,56-offset_row*8, 127,64-offset_row*8, invert, 1);
m_writebb_needed = true;
}
- oledWriteString(&m_oled, 0,offset_x,7-offset_row, text.c_str(), font, invert, 0);
+ return oledWriteString(&m_oled, 0,offset_x,7-offset_row, text.c_str(), font, invert, 0);
}
bool Api::gui_header_text(std::string text, int offset_x, int offset_row, bool invert, bool no_bg) {
@@ -305,7 +316,7 @@ bool Api::gui_header_text(std::string text, int offset_x, int offset_row, bool i
oledRectangle(&m_oled, 0,0+offset_row*8, 127,8+offset_row*8, invert, 1);
m_writebb_needed = true;
}
- oledWriteString(&m_oled, 0,offset_x,0+offset_row, text.c_str(), font, invert, 0);
+ return oledWriteString(&m_oled, 0,offset_x,0+offset_row, text.c_str(), font, invert, 0);
}
bool Api::performance_set(int perf) {
@@ -313,11 +324,19 @@ bool Api::performance_set(int perf) {
}
bool Api::datetime_get(datetime_t *t) {
+#ifndef SDL2_BUILD
return rtc_get_datetime(t);
+#else
+ return false;
+#endif
}
bool Api::datetime_set(datetime_t *t) {
+#ifndef SDL2_BUILD
return rtc_set_datetime(t);
+#else
+ return false;
+#endif
}
uint Api::button_last_get() {
diff --git a/hal/api.hpp b/hal/api.hpp
index 5d0c33e..eaba324 100644
--- a/hal/api.hpp
+++ b/hal/api.hpp
@@ -3,7 +3,7 @@
#include <iostream>
#include "pico/util/datetime.h"
#include "../oled/ss_oled.h"
-
+#include "sdl2/display.hpp"
#include "../buttons.hpp"
class Api {
@@ -31,6 +31,10 @@ class Api {
ENTER_SHALLOW_SLEEP,
EXIT_SHALLOW_SLEEP // Restore perf setting
};
+#ifdef SDL2_BUILD
+ SDLDisplay m_sdl_display;
+#endif
+
void init();
// Control the display's power (on or off)
void display_power(bool mode);
@@ -44,7 +48,8 @@ class Api {
void display_draw_ellipse(int iCenterX, int iCenterY, int32_t iRadiusX, int32_t iRadiusY, uint8_t ucColor, uint8_t bFilled);
void display_write_buffer(uint8_t *pBuffer);
// Write the internal backbuffer to the display. Should be called when after all drawing calls. One call is done to avoid flickering of the display.
- void display_write_backbuffer();
+ // Retunrs true if must quit app
+ bool display_write_backbuffer();
int display_write_pixel(int x, int y, unsigned char ucColor, int bRender);
// Display a popup over the current view and wait for select button to be pressed.
// This is a blocking function and should be used only in the app's render method.
diff --git a/hal/sdl2/display.cpp b/hal/sdl2/display.cpp
new file mode 100644
index 0000000..ecb6870
--- /dev/null
+++ b/hal/sdl2/display.cpp
@@ -0,0 +1,137 @@
+#ifdef SDL2_BUILD
+#include <SDL.h>
+#include "display.hpp"
+#include "../../buttons.hpp"
+#include <iostream>
+
+#define SCREEN_HEIGHT 64
+#define SCREEN_WIDTH 128
+
+SDLDisplay::SDLDisplay() {
+ SDL_Init(SDL_INIT_VIDEO);
+ SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
+ /* scale here to make the window larger than the surface itself.
+ Int*eger scalings only as set by function below. */
+ m_window = SDL_CreateWindow("SDL2 pico-watch build", SDL_WINDOWPOS_UNDEFINED,
+ SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, 0);
+ if (m_window == nullptr) {
+ SDL_Log("Unable to create renderer: %s", SDL_GetError());
+ m_should_quit = true;
+ return;
+ }
+
+ m_renderer = SDL_CreateRenderer(m_window, -1, SDL_RENDERER_PRESENTVSYNC);
+ if (m_renderer == nullptr) {
+ SDL_Log("Unable to create renderer: %s", SDL_GetError());
+ m_should_quit = true;
+ return;
+ }
+
+ const int width = SCREEN_WIDTH;
+ const int height = SCREEN_HEIGHT;
+
+ /* Since we are going to display a low resolution buffer,
+ i t i*s best to limit the window size so that it cannot be
+ smaller than our internal buffer size. */
+ SDL_SetWindowMinimumSize(m_window, width, height);
+ SDL_RenderSetLogicalSize(m_renderer, width, height);
+ SDL_RenderSetIntegerScale(m_renderer, SDL_bool(1));
+
+ /* A one-bit-per-pixel Surface, indexed to these colors */
+ m_surface = SDL_CreateRGBSurfaceWithFormat(SDL_SWSURFACE,
+ width, height, 1, SDL_PIXELFORMAT_INDEX1MSB);
+ SDL_Color colors[2] = {{0, 0, 0, 255}, {255, 255, 255, 255}};
+ SDL_SetPaletteColors(m_surface->format->palette, colors, 0, 2);
+}
+
+bool SDLDisplay::update_display(const uint8_t* oled_screen) {
+ if (m_should_quit) return true;
+
+ SDL_Event e;
+ while (SDL_PollEvent(&e) != 0) {
+ switch (e.type) {
+ case SDL_QUIT:
+ m_should_quit = true;
+ break;
+ case SDL_KEYDOWN:
+ case SDL_KEYUP:
+ uint button = get_button(e.key.keysym.sym);
+ if (button)
+ gpio_interrupt_cb(button, e.type == SDL_KEYDOWN);
+ break;
+ }
+ }
+
+ // NOTE: hardcoded display size (might be fine, see todo.md and oledWriteDataBlock in ss_oled.c)
+ // memcpy(m_surface->pixels, oled_screen, (size_t)(128*8));
+ convert_display(oled_screen);
+
+ // render on screen
+ SDL_RenderClear(m_renderer);
+ SDL_Texture* screen_texture = SDL_CreateTextureFromSurface(m_renderer, m_surface);
+ SDL_RenderCopy(m_renderer, screen_texture, NULL, NULL);
+ SDL_RenderPresent(m_renderer);
+ SDL_DestroyTexture(screen_texture);
+ return false;
+}
+
+SDLDisplay::~SDLDisplay() {
+ SDL_DestroyTexture(m_texture);
+ SDL_DestroyRenderer(m_renderer);
+ SDL_DestroyWindow(m_window);
+ SDL_Quit();
+}
+
+unsigned char rorb(unsigned char x, unsigned char n) {
+ return __rorb(x,n);
+}
+
+unsigned char rolb(unsigned char x, unsigned char n) {
+ return __rolb(x,n);
+}
+
+void SDLDisplay::convert_display(const uint8_t* in) {
+ unsigned char* out = (unsigned char*)m_surface->pixels;
+ // clear screen
+ for (int i = 0; i < 8*128; ++i)
+ out[i] = 0x00;
+
+ // draw to screen
+ for (int i = 0; i < 8*128; ++i) {
+ int row = i / 128; // "super-row" consisting of 8 subrows
+ int col = i % 128;
+
+ // create a column of a subrow
+ // int subrow represents which bit of byte i
+ for (int subrow = 0; subrow < 8; ++subrow)
+ // OR instead of XOR seems to do exactly the same thing?
+ out[col/8 + (row*8 + subrow)*16] ^= rorb(
+ (bool)(in[i] & rolb(1, subrow)), (col % 8) + 1);
+ }
+}
+
+uint SDLDisplay::get_button(SDL_Keycode key) {
+ switch (key) {
+ case SDLK_w:
+ case SDLK_UP:
+ return BUTTON_UP;
+ case SDLK_s:
+ case SDLK_DOWN:
+ return BUTTON_DOWN;
+ case SDLK_RETURN:
+ case SDLK_o:
+ return BUTTON_SELECT;
+ case SDLK_TAB:
+ case SDLK_m:
+ return BUTTON_MODE;
+ case SDLK_BACKSPACE:
+ case SDLK_h:
+ return BUTTON_HOME;
+ case SDLK_x:
+ m_testbool = !m_testbool;
+ default:
+ return 0;
+ }
+}
+
+#endif
diff --git a/hal/sdl2/display.hpp b/hal/sdl2/display.hpp
new file mode 100644
index 0000000..55d82b7
--- /dev/null
+++ b/hal/sdl2/display.hpp
@@ -0,0 +1,30 @@
+#pragma once
+#ifdef SDL2_BUILD
+#include <SDL.h>
+
+class SDLDisplay {
+public:
+ SDLDisplay();
+ ~SDLDisplay();
+ // bool init_display();
+ // When returning false, please quit the program (return from main)
+ bool update_display(const uint8_t* oled_screen);
+private:
+ SDL_Window* m_window = nullptr;
+ SDL_Renderer* m_renderer = nullptr;
+ SDL_Surface* m_surface = nullptr;
+ SDL_Texture* m_texture = nullptr;
+
+ bool m_should_quit = false;
+ bool m_testbool = false;
+
+ // Convert the surface's pixels from the internal SSOLED display representation
+ // to a valid representation that can be correctly displayed
+ void convert_display(const uint8_t* oled_screen);
+
+ // Get the gpio button that corresponds to the key that has been pressed
+ uint get_button(SDL_Keycode key);
+};
+
+#endif
+
diff --git a/init.cpp b/init.cpp
index eb33533..7e7056d 100644
--- a/init.cpp
+++ b/init.cpp
@@ -1,12 +1,15 @@
#include <stdio.h>
#include "pico/stdlib.h"
+#ifndef SDL2_BUILD
#include "hardware/i2c.h"
extern "C" {
#include "hardware/rtc.h"
}
+#endif
#include "init.hpp"
+#ifndef SDL2_BUILD
void init_rtc() {
datetime_t init_date = {
.year = INIT_DATETIME_YEAR,
@@ -19,8 +22,11 @@ void init_rtc() {
rtc_init();
rtc_set_datetime(&init_date);
}
+#endif
void init_all() {
+#ifndef SDL2_BUILD
stdio_init_all();
init_rtc();
+#endif
}
diff --git a/oled/BitBang_I2C.c b/oled/BitBang_I2C.c
index e5c34da..faff67a 100644
--- a/oled/BitBang_I2C.c
+++ b/oled/BitBang_I2C.c
@@ -21,7 +21,9 @@
#include "pico/stdlib.h"
#include "hardware/gpio.h"
#include "pico/binary_info.h"
+#ifndef SDL2_BUILD
#include "hardware/i2c.h"
+#endif
#include "BitBang_I2C.h"
#define I2C_PORT i2c1
@@ -60,6 +62,7 @@ static void SDA_LOW(uint8_t iSDA)
// otherwise return 1 for success
//
+#ifndef SDL2_BUILD
static int i2cByteOut(BBI2C *pI2C, uint8_t b)
{
uint8_t i, ack;
@@ -537,3 +540,4 @@ int iDevice = DEVICE_UNKNOWN;
}
return iDevice;
} /* I2CDiscoverDevice() */
+#endif // SDL2_BUILD ndef
diff --git a/oled/ss_oled.c b/oled/ss_oled.c
index c29171f..d2ec30d 100644
--- a/oled/ss_oled.c
+++ b/oled/ss_oled.c
@@ -602,7 +602,9 @@ static int16_t pgm_read_word(uint8_t *ptr)
static void _I2CWrite(SSOLED *pOLED, unsigned char *pData, int iLen)
{
+#ifndef SDL2_BUILD
I2CWrite(&pOLED->bbi2c, pOLED->oled_addr, pData, iLen);
+#endif
} /* _I2CWrite() */
#ifdef FUTURE
@@ -727,7 +729,9 @@ int rc = OLED_NOT_FOUND;
// Disable SPI mode code
iCSPin = iDCPin = -1;
+#ifndef SDL2_BUILD
I2CInit(&pOLED->bbi2c, iSpeed); // on Linux, SDA = bus number, SCL = device address
+#endif
// Reset it
#ifdef FUTURE
@@ -743,6 +747,7 @@ int rc = OLED_NOT_FOUND;
}
#endif
+#ifndef SDL2_BUILD
// find the device address if requested
if (iAddr == -1 || iAddr == 0 || iAddr == 0xff) // find it
{
@@ -765,6 +770,9 @@ int rc = OLED_NOT_FOUND;
uint8_t u = 0;
I2CReadRegister(&pOLED->bbi2c, pOLED->oled_addr, 0x00, &u, 1); // read the status register
u &= 0x0f; // mask off power on/off bit
+#else
+ uint8_t u = 3; // The type of display we are using
+#endif
if (u == 0x7 || u == 0xf) // SH1107
{
pOLED->oled_type = OLED_128x128;
@@ -1318,6 +1326,7 @@ unsigned char uc, ucOld;
if (pOLED->ucScreen)
uc = ucOld = pOLED->ucScreen[i];
+#ifndef SDL2_BUILD
else if (pOLED->oled_type == OLED_132x64 || pOLED->oled_type == OLED_128x128) // SH1106/SH1107 can read data
{
uint8_t ucTemp[3];
@@ -1331,6 +1340,7 @@ unsigned char uc, ucOld;
uc = ucOld = ucTemp[1]; // first byte is garbage
}
else
+#endif
uc = ucOld = 0;
uc &= ~(0x1 << (y & 7));
diff --git a/pico-watch.cpp b/pico-watch.cpp
index 54e78f4..a9e156a 100644
--- a/pico-watch.cpp
+++ b/pico-watch.cpp
@@ -1,9 +1,16 @@
-#include <stdio.h>
#include "pico/stdlib.h"
+#include <cstdlib>
+#ifndef SDL2_BUILD
+#include <stdio.h>
#include "hardware/i2c.h"
-#include "hardware/sync.h"
#include "hardware/rtc.h"
#include "pico/util/datetime.h"
+#include "hardware/sync.h"
+#else
+#include <chrono>
+#include <thread>
+#include <iostream>
+#endif
#include "init.hpp"
#include "hal/api.hpp"
@@ -35,24 +42,35 @@ bool cb_status_check(struct repeating_timer *t) {
}
int main() {
+#ifdef SDL2_BUILD
+ using namespace std::chrono_literals;
+#endif
init_all();
printf("~~~==~~~");
+ std::cerr << std::endl;
init_buttons();
app_api.init();
+#ifndef SDL2_BUILD
struct repeating_timer status_check_timer;
add_repeating_timer_ms(250, cb_status_check, NULL, &status_check_timer); // TODO: Execute on core1
+#endif
g_s.foreground_app = app_mgr::app_init(0);
while (1) {
if (g_s.app_ready && !g_s.is_sleeping) {
app_mgr::app_render(g_s.foreground_app);
- app_api.display_write_backbuffer();
+ bool do_quit = app_api.display_write_backbuffer();
+ if (do_quit) return 0;
}
if (g_s.is_sleeping)
+#ifndef SDL2_BUILD
__wfi();
+#else
+ ;//std::this_thread::sleep_for(10ms);
+#endif
else if (g_s.app_switch_requested) {
app_mgr::app_switch(g_s.foreground_app, g_s.app_switch_to_app);
g_s.app_switch_requested = false;