From b8cb172c08db59620b01753074cdbf07eeae8e94 Mon Sep 17 00:00:00 2001 From: Marcel Kaufmann Date: Thu, 19 Oct 2023 17:42:39 +0200 Subject: [PATCH 1/6] make reboot.h compatible with C++ --- picowota_reboot/include/picowota/reboot.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/picowota_reboot/include/picowota/reboot.h b/picowota_reboot/include/picowota/reboot.h index f8610bb..0dc09f1 100644 --- a/picowota_reboot/include/picowota/reboot.h +++ b/picowota_reboot/include/picowota/reboot.h @@ -11,6 +11,10 @@ #define PICOWOTA_BOOTLOADER_ENTRY_MAGIC 0xb105f00d +#ifdef __cplusplus +extern "C" +#endif + void picowota_reboot(bool to_bootloader); #endif /* __PICOWOTA_REBOOT_H__ */ From ca43cb4e0e2cec76df8b7b4413b70bd690ab1ee5 Mon Sep 17 00:00:00 2001 From: Marcel Kaufmann Date: Sat, 21 Oct 2023 17:57:48 +0200 Subject: [PATCH 2/6] Add Memory Usage at link time --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index eb3fd0a..488154e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,7 +48,7 @@ target_cl_options("-Wall") target_cl_options("-Os") target_cl_options("-ffunction-sections") target_cl_options("-fdata-sections") -target_link_options(picowota PRIVATE "LINKER:--gc-sections") +target_link_options(picowota PRIVATE "LINKER:--gc-sections,--print-memory-usage") pico_add_extra_outputs(picowota) From 9bc5cc27a48240b66c5ddd161d9af8fa0046207f Mon Sep 17 00:00:00 2001 From: Marcel Kaufmann Date: Sat, 21 Oct 2023 17:58:22 +0200 Subject: [PATCH 3/6] change BOOTLOADER_ENTRY_PIN to a not yet used pin --- main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.c b/main.c index 80b892e..d336e95 100644 --- a/main.c +++ b/main.c @@ -83,7 +83,7 @@ struct event { }; }; -#define BOOTLOADER_ENTRY_PIN 15 +#define BOOTLOADER_ENTRY_PIN 28 //GPIO28_ADC2 #define TCP_PORT 4242 From bbcdba09e38afe2e72085d1e9706413338c5cfa3 Mon Sep 17 00:00:00 2001 From: Marcel Kaufmann Date: Sun, 22 Oct 2023 17:31:59 +0200 Subject: [PATCH 4/6] add minimal display support to picowota bootloader --- CMakeLists.txt | 8 +- display_minimal.c | 70 +++++++++++++ display_minimal.h | 23 +++++ main.c | 9 ++ uc1701_display_minimal.c | 209 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 318 insertions(+), 1 deletion(-) create mode 100644 display_minimal.c create mode 100644 display_minimal.h create mode 100644 uc1701_display_minimal.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 488154e..39f483c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,8 @@ add_executable(picowota main.c tcp_comm.c dhcpserver/dhcpserver.c + display_minimal.c + uc1701_display_minimal.c ) function(target_cl_options option) @@ -54,7 +56,9 @@ pico_add_extra_outputs(picowota) target_include_directories(picowota PRIVATE ${CMAKE_CURRENT_LIST_DIR} # Needed so that lwip can find lwipopts.h - ${CMAKE_CURRENT_LIST_DIR}/dhcpserver) + ${CMAKE_CURRENT_LIST_DIR}/dhcpserver + ${CMAKE_SOURCE_DIR}/targets +) pico_enable_stdio_usb(picowota 1) @@ -68,9 +72,11 @@ target_link_libraries(picowota hardware_structs pico_cyw43_arch_lwip_poll pico_stdlib + hardware_spi pico_sync pico_util picowota_reboot + u8g2 ) # Retrieves build variables from the environment if present diff --git a/display_minimal.c b/display_minimal.c new file mode 100644 index 0000000..a803e5a --- /dev/null +++ b/display_minimal.c @@ -0,0 +1,70 @@ +#include +#include +#include +#include + + +#include "display_minimal.h" + + +// Local variables +u8g2_t display_handler; + +u8g2_t * get_display_handler(void) { + return &display_handler; +} + + +void write_bl_info_AP(const char *wifi_ssid, const char *wifi_pass){ + u8g2_SetMaxClipWindow(&display_handler); + u8g2_SetFont(&display_handler, u8g2_font_5x8_tr); + u8g2_DrawStr(&display_handler, 3, 15, "Bootloader in AP Mode"); + u8g2_DrawLine(&display_handler, 3, 19, 125, 19); + u8g2_DrawStr(&display_handler, 3, 30, wifi_ssid); + u8g2_DrawStr(&display_handler, 3, 40, wifi_pass); + u8g2_DrawStr(&display_handler, 3, 50, "192.168.4.1:4242"); + u8g2_UpdateDisplay(&display_handler); +} + +void write_bl_info_STA(const char *wifi_ssid, bool status){ + u8g2_SetMaxClipWindow(&display_handler); + u8g2_SetFont(&display_handler, u8g2_font_5x8_tr); + u8g2_DrawStr(&display_handler, 3, 15, "Bootloader in STA Mode"); + u8g2_DrawLine(&display_handler, 3, 19, 125, 19); + + if (status) { + u8g2_DrawStr(&display_handler, 3, 30, "Successfully connected to:"); + u8g2_DrawStr(&display_handler, 3, 40, wifi_ssid); + } + else { + u8g2_DrawStr(&display_handler, 3, 30, "FAILED connecting to:"); + u8g2_DrawStr(&display_handler, 3, 40, wifi_ssid); + } + + u8g2_UpdateDisplay(&display_handler); +} + +/* u8g2 buffer structure can be decoded according to the description here: + https://github.com/olikraus/u8g2/wiki/u8g2reference#memory-structure-for-controller-with-u8x8-support + + Here is the Python script helping to explain how u8g2 buffer are arranged. + + with open(f, 'rb') as fp: + display_buffer = fp.read() + + tile_width = 0x10 # 16 tiles per tile row + + for tile_row_idx in range(8): + for bit in range(8): + # Each tile row includes 16 * 8 bytes + for byte_idx in range(tile_width * 8): + data_offset = byte_idx + tile_row_idx * tile_width * 8 + data = display_buffer[data_offset] + if (1 << bit) & data: + print(' * ', end='') + else: + print(' ', end='') + + print() + +*/ \ No newline at end of file diff --git a/display_minimal.h b/display_minimal.h new file mode 100644 index 0000000..95d194f --- /dev/null +++ b/display_minimal.h @@ -0,0 +1,23 @@ +#ifndef DISPLAY_MINIMAL_H_ +#define DISPLAY_MINIMAL_H_ + +#include +#include "pico/cyw43_arch.h" + +#ifdef __cplusplus +extern "C" { +#endif + +u8g2_t *get_display_handler(void); + +void display_minimal_init(void); + +void write_bl_info_AP(const char *wifi_ssid, const char *wifi_pass); +void write_bl_info_STA(const char *wifi_ssid, bool status); + +#ifdef __cplusplus +} +#endif + + +#endif // DISPLAY_H_ \ No newline at end of file diff --git a/main.c b/main.c index d336e95..5894b13 100644 --- a/main.c +++ b/main.c @@ -30,6 +30,8 @@ #include "picowota/reboot.h" +#include "display_minimal.h" + #ifdef DEBUG #include #include "pico/stdio_usb.h" @@ -580,6 +582,9 @@ int main() sleep_ms(10); + // Configure others + display_minimal_init(); + struct image_header *hdr = (struct image_header *)(XIP_BASE + IMAGE_HEADER_OFFSET); if (!should_stay_in_bootloader() && image_header_ok(hdr)) { @@ -609,15 +614,19 @@ int main() dhcp_server_t dhcp_server; dhcp_server_init(&dhcp_server, &gw, &mask); DBG_PRINTF("Started the DHCP server.\n"); + write_bl_info_AP(wifi_ssid, wifi_pass); + #else cyw43_arch_enable_sta_mode(); DBG_PRINTF("Connecting to WiFi...\n"); if (cyw43_arch_wifi_connect_timeout_ms(wifi_ssid, wifi_pass, CYW43_AUTH_WPA2_AES_PSK, 30000)) { DBG_PRINTF("failed to connect.\n"); + write_bl_info_STA(wifi_ssid, false); return 1; } else { DBG_PRINTF("Connected.\n"); + write_bl_info_STA(wifi_ssid, true); } #endif diff --git a/uc1701_display_minimal.c b/uc1701_display_minimal.c new file mode 100644 index 0000000..fef9c6f --- /dev/null +++ b/uc1701_display_minimal.c @@ -0,0 +1,209 @@ +// +// ----- +// 5V |10 9 | GND +// -- | 8 7 | -- +// (DIN) | 6 5#| (RESET) +// (LCD_A0) | 4 3 | (LCD_CS) +// (BTN_ENC) | 2 1 | -- +// ------ +// EXP1 +// ----- +// 5V |10 9 | GND +// (RESET) | 8 7 | -- +// (MOSI) | 6 5#| (EN2) +// -- | 4 3 | (EN1) +// (LCD_SCK)| 2 1 | -- +// ------ +// EXP2 +// +// For Pico W +// EXP1_3 (CS) <-> PIN22 (SPI0 CS) +// EXP1_4 (A0) <-> PIN26 (GP20) +// EXP2_6 (MOSI) <-> PIN19 (SPI0 TX) +// EXP2_2 (SCK) <-> PIN24 (SPI0 SCK) +// EXP1_5 (RST) <-> PIN27 (GP21) +// +// For Fly ERCF +// EXP1_3 (CS) <-> P13 +// EXP1_4 (A0) <-> P24 +// EXP2_6 (MOSI) <-> P11 +// EXP2_2 (SCK) <-> P10 +// EXP1_5 (RST) <-> P25 + +#include +#include +#include "pico/stdlib.h" +#include "u8g2.h" + +#include "display_minimal.h" + +#include "hardware/spi.h" + +#ifdef RASPBERRYPI_PICO +#include "raspberrypi_pico_config.h" +#endif + +#ifdef RASPBERRYPI_PICO_W +#include "raspberrypi_pico_w_config.h" +#endif + +// Local variables +extern u8g2_t display_handler; + +uint8_t u8x8_gpio_and_delay(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) +{ + switch(msg) + { + case U8X8_MSG_GPIO_AND_DELAY_INIT: // called once during init phase of u8g2/u8x8 + // We don't initialize here + break; // can be used to setup pins + case U8X8_MSG_DELAY_NANO: // delay arg_int * 1 nano second + break; + case U8X8_MSG_DELAY_100NANO: // delay arg_int * 100 nano seconds + __asm volatile ("NOP\n"); + break; + case U8X8_MSG_DELAY_10MICRO: // delay arg_int * 10 micro seconds + busy_wait_us(arg_int * 10ULL); + break; + case U8X8_MSG_DELAY_MILLI: // delay arg_int * 1 milli second + { + sleep_ms(arg_int); + break; + } + case U8X8_MSG_DELAY_I2C: // arg_int is the I2C speed in 100KHz, e.g. 4 = 400 KHz + break; // arg_int=1: delay by 5us, arg_int = 4: delay by 1.25us + case U8X8_MSG_GPIO_D0: // D0 or SPI clock pin: Output level in arg_int + //case U8X8_MSG_GPIO_SPI_CLOCK: + break; + case U8X8_MSG_GPIO_D1: // D1 or SPI data pin: Output level in arg_int + //case U8X8_MSG_GPIO_SPI_DATA: + break; + case U8X8_MSG_GPIO_D2: // D2 pin: Output level in arg_int + break; + case U8X8_MSG_GPIO_D3: // D3 pin: Output level in arg_int + break; + case U8X8_MSG_GPIO_D4: // D4 pin: Output level in arg_int + break; + case U8X8_MSG_GPIO_D5: // D5 pin: Output level in arg_int + break; + case U8X8_MSG_GPIO_D6: // D6 pin: Output level in arg_int + break; + case U8X8_MSG_GPIO_D7: // D7 pin: Output level in arg_int + break; + case U8X8_MSG_GPIO_E: // E/WR pin: Output level in arg_int + break; + case U8X8_MSG_GPIO_CS: // CS (chip select) pin: Output level in arg_int + gpio_put(DISPLAY0_CS_PIN, arg_int); + break; + case U8X8_MSG_GPIO_DC: // DC (data/cmd, A0, register select) pin: Output level in arg_int + gpio_put(DISPLAY0_A0_PIN, arg_int); + break; + case U8X8_MSG_GPIO_RESET: // Reset pin: Output level in arg_int + gpio_put(DISPLAY0_RESET_PIN, arg_int); + break; + case U8X8_MSG_GPIO_CS1: // CS1 (chip select) pin: Output level in arg_int + break; + case U8X8_MSG_GPIO_CS2: // CS2 (chip select) pin: Output level in arg_int + break; + case U8X8_MSG_GPIO_I2C_CLOCK: // arg_int=0: Output low at I2C clock pin + break; // arg_int=1: Input dir with pullup high for I2C clock pin + case U8X8_MSG_GPIO_I2C_DATA: // arg_int=0: Output low at I2C data pin + break; // arg_int=1: Input dir with pullup high for I2C data pin + case U8X8_MSG_GPIO_MENU_SELECT: + u8x8_SetGPIOResult(u8x8, /* get menu select pin state */ 0); + break; + case U8X8_MSG_GPIO_MENU_NEXT: + u8x8_SetGPIOResult(u8x8, /* get menu next pin state */ 0); + break; + case U8X8_MSG_GPIO_MENU_PREV: + u8x8_SetGPIOResult(u8x8, /* get menu prev pin state */ 0); + break; + case U8X8_MSG_GPIO_MENU_HOME: + u8x8_SetGPIOResult(u8x8, /* get menu home pin state */ 0); + break; + default: + u8x8_SetGPIOResult(u8x8, 1); // default return value + break; + } + return 1; +} + +uint8_t u8x8_byte_pico_hw_spi(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) +{ + //uint8_t *data; + //uint8_t internal_spi_mode; + + switch (msg) + { + case U8X8_MSG_BYTE_SEND: + // taskENTER_CRITICAL(); + spi_write_blocking(DISPlAY0_SPI, (uint8_t *) arg_ptr, arg_int); + // taskEXIT_CRITICAL(); + break; + case U8X8_MSG_BYTE_INIT: + u8x8_gpio_SetCS(u8x8, u8x8->display_info->chip_disable_level); + break; + case U8X8_MSG_BYTE_SET_DC: + u8x8_gpio_SetDC(u8x8, arg_int); + break; + case U8X8_MSG_BYTE_START_TRANSFER: + u8x8_gpio_SetCS(u8x8, u8x8->display_info->chip_enable_level); + u8x8->gpio_and_delay_cb(u8x8, U8X8_MSG_DELAY_NANO, u8x8->display_info->post_chip_enable_wait_ns, NULL); + break; + case U8X8_MSG_BYTE_END_TRANSFER: + u8x8->gpio_and_delay_cb(u8x8, U8X8_MSG_DELAY_NANO, u8x8->display_info->pre_chip_disable_wait_ns, NULL); + u8x8_gpio_SetCS(u8x8, u8x8->display_info->chip_disable_level); + break; + default: + return 0; + } + return 1; +} + + +void display_minimal_init() { + // Configure 12864 + printf("Initializing Display -- "); + // Initialize SPI engine + spi_init(DISPlAY0_SPI, 4000 * 1000); + + // Configure port for SPI + // gpio_set_function(DISPLAY0_RX_PIN, GPIO_FUNC_SPI); // Rx + gpio_set_function(DISPLAY0_SCK_PIN, GPIO_FUNC_SPI); // CSn + gpio_set_function(DISPLAY0_SCK_PIN, GPIO_FUNC_SPI); // SCK + gpio_set_function(DISPLAY0_TX_PIN, GPIO_FUNC_SPI); // Tx + + // Configure property for CS + gpio_init(DISPLAY0_CS_PIN); + gpio_set_dir(DISPLAY0_CS_PIN, GPIO_OUT); + gpio_put(DISPLAY0_CS_PIN, 1); + + // Configure property for A0 (D/C) + gpio_init(DISPLAY0_A0_PIN); + gpio_set_dir(DISPLAY0_A0_PIN, GPIO_OUT); + gpio_put(DISPLAY0_A0_PIN, 0); + + // Configure property for RESET + gpio_init(DISPLAY0_RESET_PIN); + gpio_set_dir(DISPLAY0_RESET_PIN, GPIO_OUT); + gpio_put(DISPLAY0_RESET_PIN, 0); + + u8g2_Setup_uc1701_mini12864_f( + &display_handler, + U8G2_R0, + u8x8_byte_pico_hw_spi, + u8x8_gpio_and_delay + ); + + // Display something + // Initialize Screen + u8g2_InitDisplay(&display_handler); + u8g2_SetPowerSave(&display_handler, 0); + u8g2_SetContrast(&display_handler, 255); + + // Clear + u8g2_ClearBuffer(&display_handler); + u8g2_ClearDisplay(&display_handler); + + printf("done\n"); +} From 40142052225b3e64da41fbe4e41f3859c1db764e Mon Sep 17 00:00:00 2001 From: dirtbit Date: Sun, 22 Oct 2023 17:39:26 +0200 Subject: [PATCH 5/6] Update README.md --- README.md | 105 +++--------------------------------------------------- 1 file changed, 4 insertions(+), 101 deletions(-) diff --git a/README.md b/README.md index 559aab9..2f116ba 100644 --- a/README.md +++ b/README.md @@ -2,85 +2,13 @@ > `picowota`, kinda sounds like you're speaking [Belter](https://expanse.fandom.com/wiki/Belter) -This project implements a bootloader for the Raspberry Pi Pico W which allows -upload of program code over WiFi ("Over The Air"). +This project implements a bootloader for the Raspberry Pi Pico W for the OpenTrickler Project -The easiest way to use it is to include this repository as a submodule in the -application which you want to be able to update over WiFi. +credits to: @usedbytes / https://github.com/usedbytes/picowota -There's an example project using picowota at https://github.com/usedbytes/picowota_blink +OpenTrickler: https://github.com/dirtbit/OpenTrickler-RP2040-Controller, developed by @earms: +https://github.com/eamars/OpenTrickler-RP2040-Controller -## Using in your project - -First add `picowota` as a submodule to your project: -``` -git submodule add https://github.com/usedbytes/picowota -git submodule update --init picowota -git commit -m "Add picowota submodule" -``` - -Then modifiy your project's CMakeLists.txt to include the `picowota` directory: - -``` -add_subdirectory(picowota) -``` - -`picowota` either connects to an existing WiFi network (by default) or -creates one, in both cases with the given SSID and password. - -You can either provide the following as environment variables, or set them -as CMake variables: - -``` -PICOWOTA_WIFI_SSID # The WiFi network SSID -PICOWOTA_WIFI_PASS # The WiFi network password -PICOWOTA_WIFI_AP # Optional; 0 = connect to the network, 1 = create it -``` - -Then, you can either build just your standalone app binary (suitable for -updating via `picowota` when it's already on the Pico), or a combined binary -which contains the bootloader and the app (suitable for flashing the first -time): - -``` -picowota_build_standalone(my_executable_name) -picowota_build_combined(my_executable_name) -``` - -Note: The combined target will also build the standalone binary. - -To be able to update your app, you must provide a way to return to the -bootloader. By default, if GPIO15 is pulled low at boot time, then `picowota` -will stay in bootloader mode, ready to receive new app code. - -You can also return to the bootloader from your app code - for example when a -button is pressed, or in response to some network request. The -`picowota_reboot` library provides a `picowota_reboot(bool to_bootloader)` -function, which your app can call to get back in to the bootloader. - -``` -CMakeLists.txt: - -target_link_libraries(my_executable_name picowota_reboot) - -your_c_code.c: - -#include "picowota/reboot.h" - -... - -{ - - ... - - if (should_reboot_to_bootloader) { - picowota_reboot(true); - } - - ... - -} -``` ## Uploading code via `picowota` @@ -102,28 +30,3 @@ serial-flash tcp:192.168.1.123:4242 my_executable_name.elf After uploading the code, if successful, the Pico will jump to the newly uploaded app. - -## How it works - -This is derived from my Pico non-W bootloader, https://github.com/usedbytes/rp2040-serial-bootloader, which I wrote about in a blog post: https://blog.usedbytes.com/2021/12/pico-serial-bootloader/ - -The bootloader code attempts to avoid "bricking" by storing a CRC of the app -code which gets uploaded. If the CRC doesn't match, then the app won't get run -and the Pico will stay in `picowota` bootloader mode. This should make it fairly -robust against errors in transfers etc. - -## Known issues - -### Bootloader/app size and `cyw43` firmware - -The WiFi chip on a Pico W needs firmware, which gets built in to any program -you build with the Pico SDK. This is relatively large - 300-400 kB, which is why -this bootloader is so large. - -This gets duplicated in the `picowota` bootloader binary and _also_ the app -binary, which obviously uses up a significant chunk of the Pico's 2 MB flash. - -It would be nice to be able to avoid this duplication, but the Pico SDK -libraries don't give a mechanism to do so. - -I've raised https://github.com/raspberrypi/pico-sdk/issues/928 for consideration. From ca32bc36a39d8df431a8326c73aad2212b4d4838 Mon Sep 17 00:00:00 2001 From: Marcel Kaufmann Date: Sun, 22 Oct 2023 22:51:23 +0200 Subject: [PATCH 6/6] Use OpenTrickler config via Pico's EEPROM for STA config --- CMakeLists.txt | 3 ++ cat24c256_eeprom.c | 99 ++++++++++++++++++++++++++++++++++++++++++++++ display_minimal.c | 2 + eeprom.c | 58 +++++++++++++++++++++++++++ eeprom.h | 37 +++++++++++++++++ main.c | 68 ++++++++++++++++++++++++++++--- 6 files changed, 262 insertions(+), 5 deletions(-) create mode 100644 cat24c256_eeprom.c create mode 100644 eeprom.c create mode 100644 eeprom.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 39f483c..cead000 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,8 @@ add_executable(picowota dhcpserver/dhcpserver.c display_minimal.c uc1701_display_minimal.c + eeprom.c + cat24c256_eeprom.c ) function(target_cl_options option) @@ -73,6 +75,7 @@ target_link_libraries(picowota pico_cyw43_arch_lwip_poll pico_stdlib hardware_spi + hardware_i2c pico_sync pico_util picowota_reboot diff --git a/cat24c256_eeprom.c b/cat24c256_eeprom.c new file mode 100644 index 0000000..d0c0806 --- /dev/null +++ b/cat24c256_eeprom.c @@ -0,0 +1,99 @@ +#include "pico/stdlib.h" +#include "hardware/i2c.h" +#include "hardware/spi.h" +#include "hardware/uart.h" + +#ifdef RASPBERRYPI_PICO +#include "raspberrypi_pico_config.h" +#endif + +#ifdef RASPBERRYPI_PICO_W +#include "raspberrypi_pico_w_config.h" +#endif +#include "eeprom.h" + +// Include only for PICO board with specific flash chip +#include "pico/unique_id.h" + + +#define PAGE_SIZE 64 // 64 byte page size + + +void cat24c256_eeprom_init() { + // Initialize I2C bus with 400k baud rate + i2c_init(EEPROM_I2C, 400 * 1000); + + // Initialize PINs as I2C function + gpio_set_function(EEPROM_SDA_PIN, GPIO_FUNC_I2C); + gpio_set_function(EEPROM_SCL_PIN, GPIO_FUNC_I2C); + + gpio_pull_up(EEPROM_SDA_PIN); + gpio_pull_up(EEPROM_SCL_PIN); + + // Make the I2C pins available to picotool + // bi_decl(bi_2pins_with_func(EEPROM_SDA_PIN, EEPROM_SCL_PIN, GPIO_FUNC_I2C)); +} + + +bool _cat24c256_write_page(uint16_t data_addr, uint8_t * data, size_t len){ + uint8_t buf[len + 2]; // Include first two bytes for address + buf[0] = (data_addr >> 8) & 0xFF; // High byte of address + buf[1] = data_addr & 0xFF; // Low byte of address + + // Copy data to buffer + memcpy(&buf[2], data, len); + + // Send to the EEPROM + int ret; + ret = i2c_write_blocking(EEPROM_I2C, EEPROM_ADDR, buf, len + 2, false); + return ret != PICO_ERROR_GENERIC; +} + + +bool cat24c256_write(uint16_t base_addr, uint8_t * data, size_t len) { + uint16_t num_pages = len / PAGE_SIZE; + bool is_ok; + + for (uint16_t page = 0; page <= num_pages; page += 1) { + uint16_t offset = page * PAGE_SIZE; + size_t write_size = PAGE_SIZE; + if (page == num_pages) { + write_size = len % PAGE_SIZE; + } + + is_ok = _cat24c256_write_page(base_addr + offset, data + offset, write_size); + busy_wait_us(5 * 1000ULL); + if (!is_ok) { + return false; + } + } + + return true; +} + + +bool cat24c256_read(uint16_t data_addr, uint8_t * data, size_t len) { + uint8_t buf[2]; // Include first two bytes for address + buf[0] = (data_addr >> 8) & 0xFF; // High byte of address + buf[1] = data_addr & 0xFF; // Low byte of address + + i2c_write_blocking(EEPROM_I2C, EEPROM_ADDR, buf, 2, true); + + int bytes_read; + bytes_read = i2c_read_blocking(EEPROM_I2C, EEPROM_ADDR, data, len, false); + + return bytes_read == len; +} + + +bool cat24c256_eeprom_erase() { + uint8_t dummy_buffer[PAGE_SIZE]; + memset(dummy_buffer, 0xff, PAGE_SIZE); + + + for (size_t page=0; page < 512; page++) { + size_t page_offset = page * PAGE_SIZE; + cat24c256_write(page_offset, dummy_buffer, PAGE_SIZE); + } + return true; +} diff --git a/display_minimal.c b/display_minimal.c index a803e5a..1beff31 100644 --- a/display_minimal.c +++ b/display_minimal.c @@ -35,6 +35,8 @@ void write_bl_info_STA(const char *wifi_ssid, bool status){ if (status) { u8g2_DrawStr(&display_handler, 3, 30, "Successfully connected to:"); u8g2_DrawStr(&display_handler, 3, 40, wifi_ssid); + u8g2_DrawStr(&display_handler, 3, 50, "Use serial-flash to"); + u8g2_DrawStr(&display_handler, 3, 60, "update new app.elf."); } else { u8g2_DrawStr(&display_handler, 3, 30, "FAILED connecting to:"); diff --git a/eeprom.c b/eeprom.c new file mode 100644 index 0000000..86f6ced --- /dev/null +++ b/eeprom.c @@ -0,0 +1,58 @@ +#include "stdint.h" +#include "stdio.h" +#include "stdlib.h" +#include "string.h" + +#include "hardware/watchdog.h" +#include "hardware/regs/rosc.h" +#include "hardware/regs/addressmap.h" + +#include "eeprom.h" + + +extern bool cat24c256_eeprom_erase(); +extern void cat24c256_eeprom_init(); +extern bool cat24c256_write(uint16_t data_addr, uint8_t * data, size_t len); +extern bool cat24c256_read(uint16_t data_addr, uint8_t * data, size_t len); + +eeprom_metadata_t metadata; + +uint32_t rnd(void){ + int k, random=0; + volatile uint32_t *rnd_reg=(uint32_t *)(ROSC_BASE + ROSC_RANDOMBIT_OFFSET); + + for(k=0;k<32;k++){ + + random = random << 1; + random=random + (0x00000001 & (*rnd_reg)); + + } + return random; +} + +bool eeprom_init(void) { + bool is_ok = true; + + cat24c256_eeprom_init(); + + // Read data revision, if match then move forward + is_ok = eeprom_read(EEPROM_METADATA_BASE_ADDR, (uint8_t *) &metadata, sizeof(eeprom_metadata_t)); + if (!is_ok) { + printf("Unable to read from EEPROM at address %x\n", EEPROM_METADATA_BASE_ADDR); + return false; + } + + if (metadata.eeprom_metadata_rev != EEPROM_METADATA_REV) { + return false; + } + + return is_ok; +} + +bool eeprom_read(uint16_t data_addr, uint8_t * data, size_t len) { + bool is_ok; + + is_ok = cat24c256_read(data_addr, data, len); + + return is_ok; +} \ No newline at end of file diff --git a/eeprom.h b/eeprom.h new file mode 100644 index 0000000..0bb86b0 --- /dev/null +++ b/eeprom.h @@ -0,0 +1,37 @@ +#ifndef CAT24C256_EEPROM_H_ +#define CAT24C256_EEPROM_H_ + +#include +#include +#include + +#define EEPROM_METADATA_BASE_ADDR 0 * 1024 // 0K +#define EEPROM_SCALE_CONFIG_BASE_ADDR 1 * 1024 // 1K +#define EEPROM_WIRELESS_CONFIG_BASE_ADDR 2 * 1024 // 2K +#define EEPROM_MOTOR_CONFIG_BASE_ADDR 4 * 1024 // 4K +#define EEPROM_CHARGE_MODE_BASE_ADDR 5 * 1024 // 5K +#define EEPROM_APP_CONFIG_BASE_ADDR 6 * 1024 // 6k +#define EEPROM_NEOPIXEL_LED_CONFIG_BASE_ADDR 7 * 1024 // 7k +#define EEPROM_ROTARY_BUTTON_CONFIG_BASE_ADDR 8 * 1024 // 8k + +#define EEPROM_METADATA_REV 2 // 16 byte + + +typedef struct { + uint16_t eeprom_metadata_rev; + char unique_id[8]; +} __attribute__((packed)) eeprom_metadata_t; + + +#ifdef __cplusplus +extern "C" { +#endif + +bool eeprom_init(void); +bool eeprom_read(uint16_t data_addr, uint8_t * data, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif // CAT24C256_EEPROM_H_ \ No newline at end of file diff --git a/main.c b/main.c index 5894b13..f1079c6 100644 --- a/main.c +++ b/main.c @@ -29,8 +29,8 @@ #include "tcp_comm.h" #include "picowota/reboot.h" - #include "display_minimal.h" +#include "eeprom.h" #ifdef DEBUG #include @@ -108,6 +108,21 @@ struct event { #define CMD_GO (('G' << 0) | ('O' << 8) | ('G' << 16) | ('O' << 24)) #define CMD_REBOOT (('B' << 0) | ('O' << 8) | ('O' << 16) | ('T' << 24)) +typedef struct { + uint16_t wireless_data_rev; + char ssid[32]; + char pw[64]; + uint32_t auth; + uint32_t timeout_ms; + bool configured; +} __attribute__((packed)) eeprom_wireless_metadata_t; + +typedef struct { + eeprom_wireless_metadata_t eeprom_wireless_metadata; +} wireless_config_t; + +static wireless_config_t wireless_config; + static uint32_t handle_sync(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out) { return RSP_SYNC; @@ -566,10 +581,17 @@ static bool should_stay_in_bootloader() static void network_deinit() { + if(wireless_config.eeprom_wireless_metadata.configured){ + cyw43_arch_deinit(); + } else { + dhcp_server_deinit(&dhcp_server); + } +/* #if PICOWOTA_WIFI_AP == 1 dhcp_server_deinit(&dhcp_server); #endif cyw43_arch_deinit(); +*/ } int main() @@ -582,9 +604,6 @@ int main() sleep_ms(10); - // Configure others - display_minimal_init(); - struct image_header *hdr = (struct image_header *)(XIP_BASE + IMAGE_HEADER_OFFSET); if (!should_stay_in_bootloader() && image_header_ok(hdr)) { @@ -595,6 +614,12 @@ int main() } DBG_PRINTF_INIT(); + // Configure others + display_minimal_init(); + eeprom_init(); // read OpenTrickler config + + memset(&wireless_config, 0x00, sizeof(wireless_config_t)); + eeprom_read(EEPROM_WIRELESS_CONFIG_BASE_ADDR, (uint8_t *) &wireless_config.eeprom_wireless_metadata, sizeof(eeprom_wireless_metadata_t)); // will result in AP mode queue_init(&event_queue, sizeof(struct event), EVENT_QUEUE_LENGTH); @@ -603,6 +628,39 @@ int main() return 1; } + if (wireless_config.eeprom_wireless_metadata.configured) { // STA + cyw43_arch_enable_sta_mode(); + + DBG_PRINTF("Connecting to WiFi...\n"); + if (cyw43_arch_wifi_connect_timeout_ms( + wireless_config.eeprom_wireless_metadata.ssid, + wireless_config.eeprom_wireless_metadata.pw, + wireless_config.eeprom_wireless_metadata.auth, + wireless_config.eeprom_wireless_metadata.timeout_ms + )) { + DBG_PRINTF("failed to connect.\n"); + write_bl_info_STA(wifi_ssid, false); + return 1; + } else { + DBG_PRINTF("Connected.\n"); + write_bl_info_STA(wireless_config.eeprom_wireless_metadata.ssid, true); + } + } + else{ // AP MODE + cyw43_arch_enable_ap_mode(wifi_ssid, wifi_pass, CYW43_AUTH_WPA2_AES_PSK); + DBG_PRINTF("Enabled the WiFi AP.\n"); + + ip4_addr_t gw, mask; + IP4_ADDR(&gw, 192, 168, 4, 1); + IP4_ADDR(&mask, 255, 255, 255, 0); + + dhcp_server_t dhcp_server; + dhcp_server_init(&dhcp_server, &gw, &mask); + DBG_PRINTF("Started the DHCP server.\n"); + write_bl_info_AP(wifi_ssid, wifi_pass); + } + +/* #if PICOWOTA_WIFI_AP == 1 cyw43_arch_enable_ap_mode(wifi_ssid, wifi_pass, CYW43_AUTH_WPA2_AES_PSK); DBG_PRINTF("Enabled the WiFi AP.\n"); @@ -629,7 +687,7 @@ int main() write_bl_info_STA(wifi_ssid, true); } #endif - +*/ critical_section_init(&critical_section); const struct comm_command *cmds[] = {