Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add OTA Update capability #28

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,9 @@
[submodule "library/Trinamic-library"]
path = library/Trinamic-library
url = https://github.com/terjeio/Trinamic-library.git
[submodule "library/serial-flash"]
path = library/serial-flash
url = https://github.com/usedbytes/serial-flash
[submodule "library/picowota"]
path = library/picowota
url = https://github.com/dirtbit/picowota
4 changes: 1 addition & 3 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@
"files.associations": {
"neopixel_led.h": "c",
"app.h": "c",
"queue.h": "c",
"limits": "c",
"*.tcc": "c"
"queue.h": "c"
}
}
53 changes: 48 additions & 5 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ set(U8G2_MUI_DIRECTORY "${U8G2_SRC_DIRECYTORY}/csrc")
set(TMC_SRC_DIRECTORY "${CMAKE_SOURCE_DIR}/library/Trinamic-library")
set(SCRIPTS_DIRECTORY "${CMAKE_SOURCE_DIR}/scripts")
set(PICO_BOARD_HEADER_DIRS "${CMAKE_SOURCE_DIR}/targets")
set(PICOWOTA_DIRECTORY "${CMAKE_SOURCE_DIR}/library/picowota")

# Include the Pico SDK
include(${PICO_SDK_PATH}/pico_sdk_init.cmake)
Expand All @@ -39,15 +40,54 @@ SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fdata-sections -ffunction-sections")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdata-sections -ffunction-sections")
# SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections")

# On Windows, here we have a problem: If not full "Windows 10 SDK" from Build Tools for Visual Studio is installed, we'll fail on building PIOASM and ELF2UF2
# Two options:
# #1 Install "Full Build Tools" -> https://vanhunteradams.com/Pico/Setup/PicoSetup.html
# #2 Manually get precompiled PIOASM.exe and ELF2UF2.exe from https://sourceforge.net/projects/rpi-pico-utils/ and place it under library/ELF2UF2_PIOASM
SET(PIOASM_ELF2UF2_OPTION 2) # choose your option: 1 or 2
if(${CMAKE_HOST_SYSTEM_NAME} STREQUAL "Windows")
message("---------------If build fails on elf2uf2, consider looking at CMakeLists.txt around line 50 ;-)---------------")
endif()
if(${CMAKE_HOST_SYSTEM_NAME} STREQUAL "Windows" AND PIOASM_ELF2UF2_OPTION EQUAL 2) # avoid building pioasm and elf2uf2 on windows machines
set(ELF2UF2_PATH "${CMAKE_SOURCE_DIR}/library/ELF2UF2_PIOASM")
set(PIOASM_PATH "${CMAKE_SOURCE_DIR}/library/ELF2UF2_PIOASM")

# .-------------------------------------------------------.
# | Avoid building 'pioasm' |
# | |
add_executable(Pioasm IMPORTED) # |
set_property(TARGET Pioasm PROPERTY IMPORTED_LOCATION # |
${PIOASM_PATH}/pioasm) # |
set(Pioasm_FOUND 1) # |
# | |
# `-------------------------------------------------------'

# .-------------------------------------------------------.
# | Avoid building 'elf2uf2' |
# | |
add_executable(ELF2UF2 IMPORTED) # |
set_property(TARGET ELF2UF2 PROPERTY IMPORTED_LOCATION # |
${ELF2UF2_PATH}/elf2uf2) # |
set(ELF2UF2_FOUND 1) # |
# | |
# `-------------------------------------------------------'
endif()

# Initialise the Pico SDK
pico_sdk_init()

# TODO - would be cool to have any WiFi credentials saved exclusively on the PICO EEPROM, but not being erased on any firmware update...
set(PICOWOTA_WIFI_SSID "OpenTricklerBootloader") # The WiFi network SSID
set(PICOWOTA_WIFI_PASS "opentrickler") # The WiFi network password
set(PICOWOTA_WIFI_AP "1") # Optional; 0 = connect to the network, 1 = create it
add_subdirectory(${PICOWOTA_DIRECTORY})

# Application
add_executable("${TARGET_NAME}")

# Include source
include_directories(${SRC_DIRECTORY})
include_directories(${SRC_DIRECTORY}
)

# Pull in FreeRTOS
include(${FREERTOS_SRC_DIRECTORY}/portable/ThirdParty/GCC/RP2040/FreeRTOS_Kernel_import.cmake)
Expand All @@ -66,8 +106,6 @@ target_sources(trinamic INTERFACE
)
target_include_directories(trinamic INTERFACE ${TMC_SRC_DIRECTORY})



# Pull in u8g2 mui
add_library(u8g2_mui
${U8G2_MUI_DIRECTORY}/mui_u8g2.c
Expand All @@ -82,7 +120,8 @@ add_subdirectory(${SRC_DIRECTORY})

# Collect all source files
file(GLOB SRC ${SRC_DIRECTORY}/*.c
${SRC_DIRECTORY}/*.cpp)
${SRC_DIRECTORY}/*.cpp
)

# Include application source file
target_sources("${TARGET_NAME}" PUBLIC
Expand All @@ -103,6 +142,7 @@ target_link_libraries("${TARGET_NAME}"
u8g2
u8g2_mui
trinamic
picowota_reboot
)

if (PICO_BOARD STREQUAL "pico_w" )
Expand All @@ -116,6 +156,9 @@ endif()

target_link_options("${TARGET_NAME}" PUBLIC -Wl,--gc-sections -Wl,--print-memory-usage)
# set( CMAKE_VERBOSE_MAKEFILE on )

# Generate extra outputs
pico_add_extra_outputs("${TARGET_NAME}")

picowota_build_combined("${TARGET_NAME}") # build both standalone and combined executable
#picowota_build_standalone("${TARGET_NAME}")

37 changes: 32 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# OpenTrickler RP2040 Controller
# OpenTrickler RP2040 Controller - with OTA update capability
This repo is for the firmware that utilises the Raspberry Pi RP2040 micro controller OpenTrickler RP2040 Controller.

Join our [discord server](https://discord.gg/ZhdThA2vrW) for help and development information.
Expand Down Expand Up @@ -27,7 +27,7 @@ Join our [discord server](https://discord.gg/ZhdThA2vrW) for help and developmen
## Pre-build firmware
[![Auto Build](https://github.com/eamars/OpenTrickler-RP2040-Controller/actions/workflows/cmake.yml/badge.svg)](https://github.com/eamars/OpenTrickler-RP2040-Controller/actions/workflows/cmake.yml)

You can download the pre-built firmware based on the latest release from above link. Similar to flashing other RP2040 firmware, you need to put the Pico W into the bootloader mode by pressing BOOTSEL button and plug in the micro-USB cable. Then you can copy the .uf2 file from the package to the pico. Shortly after the Pico W will be programmed automatically.
You can download the pre-built firmware based on the latest release from above link. Similar to flashing other RP2040 firmware, you need to put the Pico W into the bootloader mode by pressing BOOTSEL button and plug in the micro-USB cable. Then you can copy the .uf2 file from the package to the pico. Shortly after the Pico W will be programmed automatically. Alternatively, use the OTA update feature described below.



Expand All @@ -36,9 +36,9 @@ You can download the pre-built firmware based on the latest release from above l
[Git](https://gitforwindows.org/) and [Pico-SDK](https://github.com/raspberrypi/pico-setup-windows/releases/download/v0.5.1/pico-setup-windows-x64-standalone.exe) are required to build the firmware.

### Setting Up Firmware
Using Git Bash clone the repository
Using Git Bash clone this repository

git clone https://github.com/eamars/OpenTrickler-RP2040-Controller
git clone <URL>

Next change to the cloned directory

Expand Down Expand Up @@ -66,5 +66,32 @@ Then run the following comand
~~~javascript
cmake .. -DPICO_BOARD=pico_w -DCMAKE_BUILD_TYPE=Debug
~~~

### Compiling the Firmware
Open Pico-VisualStudioCode and open the OpenTrickler-RP2040-Controller folder then navigate to the cmake plugin and click Build All Projects.
Open Pico-VisualStudioCode and open the OpenTrickler-RP2040-Controller folder then navigate to the cmake plugin.

First time compilation: Compile "App" only by clicking the Build Icon next to "app [app.elf]".
After that, hit "Build All Projects" to let the whole project bake together. From now on, "Build All Projects" is good to go.


### Flashing the Firmware
The first time you have to flash the bootloader by flashing "picowota_app.uf2" via Pico's USB bootloader (the way you did up to now).

On any further flashing, one can use serial-flash from usedbytes: https://github.com/usedbytes/serial-flash
Read on usedbytes' repository on how to obtain it.

Once serial-flash is working, and OpenTrickler is in Bootloader (via menu, Settings -> Bootloader), one can use serial-flash_app.bat to send app.elf over the air to the OpenTrickler. You may need to configure the correct IP address.

If you configured WiFi via OpenTrickler Web Interface, it will use the same credentials for the bootloader.

If not, OpenTrickler Bootloader will create a WiFi AP with following credentials:

SSID: OpenTricklerBootloader

PW: opentrickler

Credentials for access point can be changed in CMakeLists.txt.


### Known Issues
For Windows users: If necessary, get precompiled PIOASM.exe and ELF2UF2.exe from https://sourceforge.net/projects/rpi-pico-utils/ or configure CMakeLists.txt around line 50 according to your needs. I write this, because full "Windows 10 SDK" is required to build PIOASM and ELF2UF2.
1 change: 1 addition & 0 deletions library/ELF2UF2_PIOASM/readme.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
If necessary, get precompiled PIOASM.exe and ELF2UF2.exe from https://sourceforge.net/projects/rpi-pico-utils/
1 change: 1 addition & 0 deletions library/picowota
Submodule picowota added at ca32bc
1 change: 1 addition & 0 deletions library/serial-flash
Submodule serial-flash added at 51e30a
2 changes: 1 addition & 1 deletion library/u8g2
Submodule u8g2 updated 147 files
12 changes: 0 additions & 12 deletions manuals/firmware_update_via_usb.md

This file was deleted.

Binary file removed resources/firmware_update/bootsel.png
Binary file not shown.
Binary file removed resources/firmware_update/file_manager.png
Binary file not shown.
Binary file removed resources/firmware_update/pico-top-plug.png
Binary file not shown.
Binary file removed resources/firmware_update/plug-in-pico.png
Binary file not shown.
6 changes: 6 additions & 0 deletions serial-flash_app.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
REM https://github.com/usedbytes/serial-flash
REM install go: https://go.dev/doc/install
REM run: "go install github.com/usedbytes/serial-flash@latest" to install serial-flash
REM serial-flash tcp:192.168.4.1:4242 build/app.elf
serial-flash tcp:192.168.2.163:4242 build/app.elf
pause
13 changes: 8 additions & 5 deletions src/app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
#include "neopixel_led.h"
#include "rotary_button.h"
#include "menu.h"
#include "profile.h"

#include "picowota/reboot.h"


uint8_t software_reboot() {
Expand All @@ -35,6 +35,12 @@ uint8_t software_reboot() {
return 0;
}

uint8_t reboot_to_bootloader() {
picowota_reboot(true);

return 0;
}


int main()
{
Expand All @@ -60,16 +66,13 @@ int main()
// Initialize charge mode settings
charge_mode_config_init();

// Initialize profile data
profile_data_init();

#ifdef RASPBERRYPI_PICO_W
// Load wireless settings
wireless_init();
#else
#error "Unpported platform"
#endif // RASPBERRYPI_PICO_W
xTaskCreate(menu_task, "Menu Task", 1024, NULL, 6, NULL);
xTaskCreate(menu_task, "Menu Task", configMINIMAL_STACK_SIZE, NULL, 6, NULL);
// xTaskCreate(motor_task, "Motor Task", configMINIMAL_STACK_SIZE, NULL, 8, NULL);

vTaskStartScheduler();
Expand Down
2 changes: 2 additions & 0 deletions src/app.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ typedef enum {
APP_STATE_ENTER_EEPROM_ERASE = 8,
APP_STATE_ENTER_REBOOT = 9,
APP_STATE_ENTER_WIFI_INFO = 10,
APP_STATE_ENTER_BOOTLOADER = 11,
} AppState_t;


Expand All @@ -36,6 +37,7 @@ extern "C" {

bool app_init();
uint8_t software_reboot();
uint8_t reboot_to_bootloader();
bool http_app_config();

#ifdef __cplusplus
Expand Down
70 changes: 43 additions & 27 deletions src/charge_mode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
#include "charge_mode.h"
#include "eeprom.h"
#include "neopixel_led.h"
#include "profile.h"


uint8_t charge_weight_digits[] = {0, 0, 0, 0, 0};
Expand All @@ -31,6 +30,13 @@ extern scale_config_t scale_config;

const eeprom_charge_mode_data_t default_charge_mode_data = {
.charge_mode_data_rev = EEPROM_CHARGE_MODE_DATA_REV,
.coarse_kp = 0.025f,
.coarse_ki = 0.0f,
.coarse_kd = 0.25f,

.fine_kp = 2.0f,
.fine_ki = 0.0f,
.fine_kd = 10.0f,

.coarse_stop_threshold = 5,
.fine_stop_threshold = 0.03,
Expand Down Expand Up @@ -93,11 +99,6 @@ void scale_measurement_render_task(void *p) {
u8g2_SetFont(display_handler, u8g2_font_profont22_tf);
u8g2_DrawStr(display_handler, 26, 35, current_weight_string);

// Draw current profile
profile_t * current_profile = profile_get_selected();
u8g2_SetFont(display_handler, u8g2_font_helvR08_tr);
u8g2_DrawStr(display_handler, 5, 61, current_profile->name);

u8g2_SendBuffer(display_handler);

vTaskDelayUntil(&last_render_tick, pdMS_TO_TICKS(20));
Expand Down Expand Up @@ -168,18 +169,9 @@ ChargeModeState_t charge_mode_wait_for_complete(ChargeModeState_t prev_state) {
"Target: %.02f",
charge_mode_config.target_charge_weight);

// Read trickling parameter from the current profile
profile_t * current_profile = profile_get_selected();

// Find the minimum of max speed from the motor and the profile
float coarse_trickler_max_speed = fmin(get_motor_max_speed(SELECT_COARSE_TRICKLER_MOTOR),
current_profile->coarse_max_flow_speed_rps);
float coarse_trickler_min_speed = fmax(get_motor_min_speed(SELECT_COARSE_TRICKLER_MOTOR),
current_profile->coarse_min_flow_speed_rps);
float fine_trickler_max_speed = fmin(get_motor_max_speed(SELECT_FINE_TRICKLER_MOTOR),
current_profile->fine_max_flow_speed_rps);
float fine_trickler_min_speed = fmax(get_motor_min_speed(SELECT_FINE_TRICKLER_MOTOR),
current_profile->fine_min_flow_speed_rps);
uint16_t coarse_trickler_max_speed = get_motor_max_speed(SELECT_COARSE_TRICKLER_MOTOR);
uint16_t fine_trickler_max_speed = get_motor_max_speed(SELECT_FINE_TRICKLER_MOTOR);
float fine_trickler_min_speed = get_motor_min_speed(SELECT_FINE_TRICKLER_MOTOR);

float integral = 0.0f;
float last_error = 0.0f;
Expand Down Expand Up @@ -229,20 +221,20 @@ ChargeModeState_t charge_mode_wait_for_complete(ChargeModeState_t prev_state) {
float derivative = (error - last_error) / elapse_time_ms;

// Update fine trickler speed
float new_p = current_profile->fine_kp * error;
float new_i = current_profile->fine_ki * integral;
float new_d = current_profile->fine_kd * derivative;
float new_p = charge_mode_config.eeprom_charge_mode_data.fine_kp * error;
float new_i = charge_mode_config.eeprom_charge_mode_data.fine_ki * integral;
float new_d = charge_mode_config.eeprom_charge_mode_data.fine_kd * derivative;
float new_speed = fmax(fine_trickler_min_speed, fmin(new_p + new_i + new_d, fine_trickler_max_speed));

motor_set_speed(SELECT_FINE_TRICKLER_MOTOR, new_speed);

// Update coarse trickler speed
if (should_coarse_trickler_move) {
new_p = current_profile->coarse_kp * error;
new_i = current_profile->coarse_ki * integral;
new_d = current_profile->coarse_kd * derivative;
new_p = charge_mode_config.eeprom_charge_mode_data.coarse_kp * error;
new_i = charge_mode_config.eeprom_charge_mode_data.coarse_ki * integral;
new_d = charge_mode_config.eeprom_charge_mode_data.coarse_kd * derivative;

new_speed = fmax(coarse_trickler_min_speed, fmin(new_p + new_i + new_d, coarse_trickler_max_speed));
new_speed = fmin(new_p + new_i + new_d, coarse_trickler_max_speed);

motor_set_speed(SELECT_COARSE_TRICKLER_MOTOR, new_speed);
}
Expand Down Expand Up @@ -492,7 +484,25 @@ bool http_rest_charge_mode_config(struct fs_file *file, int num_params, char *pa

// Control
for (int idx = 0; idx < num_params; idx += 1) {
if (strcmp(params[idx], "c_stop") == 0) {
if (strcmp(params[idx], "c_kp") == 0) {
charge_mode_config.eeprom_charge_mode_data.coarse_kp = strtof(values[idx], NULL);
}
else if (strcmp(params[idx], "c_ki") == 0) {
charge_mode_config.eeprom_charge_mode_data.coarse_ki = strtof(values[idx], NULL);
}
else if (strcmp(params[idx], "c_kd") == 0) {
charge_mode_config.eeprom_charge_mode_data.coarse_kd = strtof(values[idx], NULL);
}
else if (strcmp(params[idx], "f_kp") == 0) {
charge_mode_config.eeprom_charge_mode_data.fine_kp = strtof(values[idx], NULL);
}
else if (strcmp(params[idx], "f_ki") == 0) {
charge_mode_config.eeprom_charge_mode_data.fine_ki = strtof(values[idx], NULL);
}
else if (strcmp(params[idx], "f_kd") == 0) {
charge_mode_config.eeprom_charge_mode_data.fine_kd = strtof(values[idx], NULL);
}
else if (strcmp(params[idx], "c_stop") == 0) {
charge_mode_config.eeprom_charge_mode_data.coarse_stop_threshold = strtof(values[idx], NULL);
}
else if (strcmp(params[idx], "f_stop") == 0) {
Expand Down Expand Up @@ -523,8 +533,14 @@ bool http_rest_charge_mode_config(struct fs_file *file, int num_params, char *pa
// Response
snprintf(charge_mode_json_buffer,
sizeof(charge_mode_json_buffer),
"{\"c_stop\":%.3f,\"f_stop\":%.3f,\"sp_sd\":%.3f,\"sp_avg\":%.3f,"
"{\"c_kp\":%.3f,\"c_ki\":%.3f,\"c_kd\":%.3f,\"f_kp\":%.3f,\"f_ki\":%.3f,\"f_kd\":%.3f,\"c_stop\":%.3f,\"f_stop\":%.3f,\"sp_sd\":%.3f,\"sp_avg\":%.3f,"
"\"c1\":\"#%06x\",\"c2\":\"#%06x\",\"c3\":\"#%06x\",\"c4\":\"#%06x\"}",
charge_mode_config.eeprom_charge_mode_data.coarse_kp,
charge_mode_config.eeprom_charge_mode_data.coarse_ki,
charge_mode_config.eeprom_charge_mode_data.coarse_kd,
charge_mode_config.eeprom_charge_mode_data.fine_kp,
charge_mode_config.eeprom_charge_mode_data.fine_ki,
charge_mode_config.eeprom_charge_mode_data.fine_kd,
charge_mode_config.eeprom_charge_mode_data.coarse_stop_threshold,
charge_mode_config.eeprom_charge_mode_data.fine_stop_threshold,
charge_mode_config.eeprom_charge_mode_data.set_point_sd_margin,
Expand Down
Loading