Repository: CalgaryToSpace/CTS-SAT-1-OBC-Firmware
Pull request #255: Telecommands to Jump to Golden Copy and Switch Active Flash Bank
Author: login: NuclearTea
Labels: name: pr-please-review
description: Indicator for someone to review a PR
Created: 2024-12-16T00:06:50Z | Merged: 2025-03-15T22:47:27Z

#24 


Diff:
diff --git a/docs/Non-Critical_Notes/Deployment_Final_Checks.md b/docs/Non-Critical_Notes/Deployment_Final_Checks.md
index 6c3790036..7133b5d4c 100644
--- a/docs/Non-Critical_Notes/Deployment_Final_Checks.md
+++ b/docs/Non-Critical_Notes/Deployment_Final_Checks.md
@@ -40,7 +40,8 @@ The following checks involve executing code and/or telecommands.
     GOLDEN_COPY (xrw) : ORIGIN = 0x8100000, LENGTH = 1024K
     ```
     This is the linker script. The length can change.
-5. Ensure all tasks are registered in the `FREERTOS_task_info_struct_t FREERTOS_task_handles_array []` array. Consider a unit test for this check, maybe.
+5. Ensure that we have flashed the golden copy of the OS to the GOLDEN_COPY address defined in the linker script ```STM32L4R5ZITx_FLASH.ld```
+6. Ensure all tasks are registered in the `FREERTOS_task_info_struct_t FREERTOS_task_handles_array []` array. Consider a unit test for this check, maybe.
 
 
 ## Management Checks
diff --git a/docs/Testing_Steps_for_Jumping_to_Golden_Copy.md b/docs/Testing_Steps_for_Jumping_to_Golden_Copy.md
new file mode 100644
index 000000000..bedbd485c
--- /dev/null
+++ b/docs/Testing_Steps_for_Jumping_to_Golden_Copy.md
@@ -0,0 +1,45 @@
+# Testing Steps for Jumping to Golden Copy
+
+## Relevant Code
+
+* https://github.com/CalgaryToSpace/CTS-SAT-1-OBC-Firmware/pull/255
+
+## Relvant Telecommands
+
+* `CTS1+stm32_internal_flash_set_active_flash_bank`
+* `CTS1+stm32_internal_flash_get_active_flash_bank`
+* `CTS1+stm32_internal_flash_get_option_bytes`
+
+## Notes
+1. You will need the stm32-for-vscode extension (up-to-date version). **It is important that it is up to date.**
+2. You will need the `STM32CubeProgrammer` which is downloadable from the stm32 website.
+
+## Testing Steps
+1. Open a serial terminal with the appropriate UART configurations.
+3. `Clean Build` the project and `Flash` it to your STM32
+4. Ensure that it is working as normal.
+5. Open the STM32CubeProgrammer and connect it to you STM32 Microcontroller
+	1. ![Cube Programmer Step 1, Connect Button](/docs/assets/Cube_Programmer_Step_1.png)
+	2. This will cause your STM to pause. Your serial test will not be receiving anything new. (You can reset your STM32 manually by pressing the reset button on the microcontroller to resume it working while still being connected).
+	3. Click the `Upload` button on the left toolbar. It is the second from the top.
+		1. ![Cube Programmer Step 2, Location of Flashing Screen](/docs/assets/Cube_Programmer_Step_2.png)
+	4. This will show the following screen:
+	   ![Cube Programmer Step 3, Flashing Screen with Numbers ](/docs/assets/Cube_Programmer_Step_3.png)
+		1. Underneath the `red` 1. drawn, you can select the file path to the firmware .bin file. I would recommend making the following modifications to the firmware.
+			1. Open `rtos_tasks.c` and modify the `TASK_DEBUG_print_heartbeat` function.
+				1. In the while loop at the bottom: 
+				2. ![Modify Heartbeat message](/docs/assets/Modify_Heartbeat.png)
+				3. Edit line 68 in the screenshot above so that it says: `FrontierSat version 2 time: ....`. We are only adding the words: `version 2` infront of `FrontierSat` and not changing anything else.
+		2. Click the `Build` button.
+		3. **DO NOT FLASH**
+		4. As shown in the above CubeProgrammer screenshot, underneath the `red` 1., please enter the path to this .bin file. It should be in the build/debug folder of the firmware project.
+		5. To the left of the `red` 2., ensure the value is: `0x8100000`.
+		6. In the checkboxes below the `red` 2., please ensure **Only** **the**`Verify programming` **is selected**.
+		7. Click the `Start Program` button
+		8. Most likely after completing, your stm will be paused. Press the reset button on your STM32 microcontroller.
+		9. At this point, it should just like before, running the exact same as before. If you followed my recommendation before, you can confirm this as the heartbeat should be saying `FrontierSat time: ...`
+		10. We can now jump to the new program by sending the following telecommand: `CTS1+stm32_internal_flash_set_active_flash_bank(2)!`
+			1. This enables `BFB2 i.e Boot From Bank 2` which boots from Flash Bank 2.
+			2. Following is an example:
+			   ![Telecommand Output](/docs/assets/TCMD_Output.png)
+		11. To go back, we can just send the same telecommand but with 1 as the param: `CTS1+stm32_internal_flash_set_active_flash_bank(1)!`
diff --git a/docs/assets/Cube_Programmer_Step_1.png b/docs/assets/Cube_Programmer_Step_1.png
new file mode 100644
index 000000000..7bf5347f2
Binary files /dev/null and b/docs/assets/Cube_Programmer_Step_1.png differ
diff --git a/docs/assets/Cube_Programmer_Step_2.png b/docs/assets/Cube_Programmer_Step_2.png
new file mode 100644
index 000000000..ae6bdfffd
Binary files /dev/null and b/docs/assets/Cube_Programmer_Step_2.png differ
diff --git a/docs/assets/Cube_Programmer_Step_3.png b/docs/assets/Cube_Programmer_Step_3.png
new file mode 100644
index 000000000..c954db270
Binary files /dev/null and b/docs/assets/Cube_Programmer_Step_3.png differ
diff --git a/docs/assets/Modify_Heartbeat.png b/docs/assets/Modify_Heartbeat.png
new file mode 100644
index 000000000..2df42019d
Binary files /dev/null and b/docs/assets/Modify_Heartbeat.png differ
diff --git a/docs/assets/TCMD_Output.png b/docs/assets/TCMD_Output.png
new file mode 100644
index 000000000..5dcbe5546
Binary files /dev/null and b/docs/assets/TCMD_Output.png differ
diff --git a/firmware/Core/Inc/stm32_internal_flash_drivers/stm32_internal_flash_drivers.h b/firmware/Core/Inc/stm32_internal_flash_drivers/stm32_internal_flash_drivers.h
index 887f2e25e..6e4c859ee 100644
--- a/firmware/Core/Inc/stm32_internal_flash_drivers/stm32_internal_flash_drivers.h
+++ b/firmware/Core/Inc/stm32_internal_flash_drivers/stm32_internal_flash_drivers.h
@@ -1,9 +1,10 @@
-#ifndef INCLUDE_GUARD__STM32_INTERNAL_FLASH_DRIVERS_H__
-#define INCLUDE_GUARD__STM32_INTERNAL_FLASH_DRIVERS_H__
+#ifndef __INCLUDE_GUARD__STM32_INTERNAL_FLASH_DRIVERS_H__
+#define __INCLUDE_GUARD__STM32_INTERNAL_FLASH_DRIVERS_H__
 
 #include <stdint.h>
 
 #include "stm32l4xx_hal.h"
+#include "stm32l4xx_hal_flash.h"
 
 /// @brief  Flash Partitions
 /// @note look in the STM32L4R5ZITx_FLASH.ld file
@@ -14,7 +15,7 @@ typedef enum
     STM32_INTERNAL_FLASH_MEMORY_REGION_RAM_2_ADDRESS = 0x10000000,
     STM32_INTERNAL_FLASH_MEMORY_REGION_RAM_3_ADDRESS = 0x20040000,
     STM32_INTERNAL_FLASH_MEMORY_REGION_FLASH_ADDRESS = 0x8000000, // default boot address
-    STM32_INTERNAL_FLASH_MEMORY_REGION_GOLDEN_COPY_ADDRESS = 0x8100000
+    STM32_INTERNAL_FLASH_MEMORY_REGION_GOLDEN_COPY_ADDRESS = 0x08100000
 } STM32_INTERNAL_FLASH_memory_region_addresses_t;
 
 typedef struct
@@ -30,4 +31,10 @@ uint8_t STM32_internal_flash_read(uint32_t address, uint8_t *buffer, uint32_t le
 
 uint8_t STM32_internal_flash_erase(uint16_t start_page_erase, uint16_t number_of_pages_to_erase, uint32_t *page_error);
 
-#endif // INCLUDE_GUARD__STM32_INTERNAL_FLASH_DRIVERS_H__
\ No newline at end of file
+uint8_t STM32_internal_flash_get_option_bytes(FLASH_OBProgramInitTypeDef *ob_data);
+
+uint8_t STM32_internal_flash_set_active_flash_bank(uint8_t wanted_active_flash_bank);
+
+uint8_t STM32_internal_flash_get_active_flash_bank();
+
+#endif // __INCLUDE_GUARD__STM32_INTERNAL_FLASH_DRIVERS_H__
diff --git a/firmware/Core/Inc/telecommands/stm32_internal_flash_telecommand_defs.h b/firmware/Core/Inc/telecommands/stm32_internal_flash_telecommand_defs.h
index 3099196be..9de74f7b0 100644
--- a/firmware/Core/Inc/telecommands/stm32_internal_flash_telecommand_defs.h
+++ b/firmware/Core/Inc/telecommands/stm32_internal_flash_telecommand_defs.h
@@ -2,6 +2,7 @@
 #define INCLUDE_GUARD_STM32_INTERNAL_FLASH_TELECOMMAND_DEFS_H
 
 #include "telecommands/telecommand_types.h"
+
 uint8_t TCMDEXEC_stm32_internal_flash_write(const char *args_str, TCMD_TelecommandChannel_enum_t tcmd_channel,
                                             char *response_output_buf, uint16_t response_output_buf_len);
 
@@ -11,4 +12,12 @@ uint8_t TCMDEXEC_stm32_internal_flash_read(const char *args_str, TCMD_Telecomman
 uint8_t TCMDEXEC_stm32_internal_flash_erase(const char *args_str, TCMD_TelecommandChannel_enum_t tcmd_channel,
                                             char *response_output_buf, uint16_t response_output_buf_len);
 
-#endif /* INCLUDE_GUARD_STM32_INTERNAL_FLASH_TELECOMMAND_DEFS_H */
\ No newline at end of file
+uint8_t TCMDEXEC_stm32_internal_flash_get_option_bytes(const char *args_str, TCMD_TelecommandChannel_enum_t tcmd_channel,
+                                                       char *response_output_buf, uint16_t response_output_buf_len);
+
+uint8_t TCMDEXEC_stm32_internal_flash_set_active_flash_bank(const char *args_str, TCMD_TelecommandChannel_enum_t tcmd_channel,
+                                                       char *response_output_buf, uint16_t response_output_buf_len);
+
+uint8_t TCMDEXEC_stm32_internal_flash_get_active_flash_bank(const char *args_str, TCMD_TelecommandChannel_enum_t tcmd_channel,
+                                                       char *response_output_buf, uint16_t response_output_buf_len);                                                       
+#endif /* INCLUDE_GUARD_STM32_INTERNAL_FLASH_TELECOMMAND_DEFS_H */
diff --git a/firmware/Core/Src/stm32_internal_flash_drivers/stm32_internal_flash_drivers.c b/firmware/Core/Src/stm32_internal_flash_drivers/stm32_internal_flash_drivers.c
index 9c6da659e..bc6134ae9 100644
--- a/firmware/Core/Src/stm32_internal_flash_drivers/stm32_internal_flash_drivers.c
+++ b/firmware/Core/Src/stm32_internal_flash_drivers/stm32_internal_flash_drivers.c
@@ -1,10 +1,11 @@
+#include <string.h>
+
 #include "stm32_internal_flash_drivers/stm32_internal_flash_drivers.h"
 
 #include "stm32l4r5xx.h"
 #include "stm32l4xx_hal.h" // must include this before stm32l4xx_hal_flash.h
 #include "stm32l4xx_hal_flash.h"
 #include "stm32l4xx_hal_flash_ex.h"
-#include <string.h>
 
 /// @brief Writes data to the flash memory in chunks of 8 bytes.
 /// @param address Address in the flash memory where the data will be written.
@@ -138,4 +139,86 @@ uint8_t STM32_internal_flash_erase(uint16_t start_page_erase, uint16_t number_of
     default:
         return 0;
     }
-}
\ No newline at end of file
+}
+
+/// @brief Gets option bytes configuration from the stm32 internal flash memory
+/// @param ob_data pointer to an FLASH_OBProgramInitTypeDef structure that
+/// contains the configuration information for the programming.
+/// @return 0 always
+uint8_t STM32_internal_flash_get_option_bytes(FLASH_OBProgramInitTypeDef *ob_data)
+{
+    // returns void
+    HAL_FLASHEx_OBGetConfig(ob_data);
+    return 0;
+}
+
+/// @brief Sets the active flash bank to either 1 or 2 (only if it is different than the current active flash bank).
+/// @brief On Success, will load application in desired flash bank
+/// @brief By Default, the active flash bank is 1
+/// @param wanted_active_flash_bank 1 or 2
+/// @return 0 on success, > 0 otherwise
+uint8_t STM32_internal_flash_set_active_flash_bank(uint8_t wanted_active_flash_bank)
+{
+    if (wanted_active_flash_bank != 1 && wanted_active_flash_bank != 2) 
+    {
+        return 1;
+    }
+    const uint8_t current_active_flash_bank = STM32_internal_flash_get_active_flash_bank();
+    if (wanted_active_flash_bank == current_active_flash_bank) 
+    {
+        return 2;
+    }
+
+    __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS);
+
+
+    if (HAL_FLASH_Unlock() != HAL_OK)
+    {
+        return 10;
+    }
+
+    if (HAL_FLASH_OB_Unlock() != HAL_OK)
+    {
+        return 20;
+    }
+
+    // Get current option bytes configuration
+    FLASH_OBProgramInitTypeDef option_byte_init;
+    HAL_FLASHEx_OBGetConfig(&option_byte_init);
+    const uint32_t wanted_bank_mode = wanted_active_flash_bank == 1 ? OB_BFB2_DISABLE : OB_BFB2_ENABLE;
+
+    option_byte_init.OptionType = OPTIONBYTE_USER;
+    option_byte_init.USERType = OB_USER_BFB2;
+    option_byte_init.USERConfig = wanted_bank_mode; 
+
+    // Apply the new option byte configuration
+    if (HAL_FLASHEx_OBProgram(&option_byte_init) != HAL_OK)
+    {
+        return 30;
+    }
+
+    // Launch the option byte loading to apply changes
+    if (HAL_FLASH_OB_Launch() != HAL_OK)
+    {
+        return 40;
+    }
+
+    if (HAL_FLASH_OB_Lock() != HAL_OK)
+    {
+        return 50;
+    }
+
+    if (HAL_FLASH_Lock() != HAL_OK)
+    {
+        return 60;
+    }
+
+    return 0;
+}
+
+/// @brief Returns active flash bank
+/// @return 1 is Flash Bank 1, 2 is Flash Bank 2
+uint8_t STM32_internal_flash_get_active_flash_bank()
+{
+    return (READ_BIT(FLASH->OPTR, FLASH_OPTR_BFB2) >> FLASH_OPTR_BFB2_Pos) + 1;
+}
diff --git a/firmware/Core/Src/system_stm32l4xx.c b/firmware/Core/Src/system_stm32l4xx.c
index be9cfee2b..4e55d72ed 100644
--- a/firmware/Core/Src/system_stm32l4xx.c
+++ b/firmware/Core/Src/system_stm32l4xx.c
@@ -123,7 +123,11 @@
 /*!< Uncomment the following line if you need to relocate the vector table
      anywhere in Flash or Sram, else the vector table is kept at the automatic
      remap of boot address selected */
-/* #define USER_VECT_TAB_ADDRESS */
+// CTS-SAT-1: Defining the USER_VECT_TAB_ADDRESS allows us to change the vector table during the 
+// course of the current program. The main usecase for this is to jump from the current application
+// to another, which requires relocating the vector table (The vector table contains addresses to vital functions needed,
+// you can see the vector table default in the startup_stm32l4r5xx.s file)
+#define USER_VECT_TAB_ADDRESS 
 
 #if defined(USER_VECT_TAB_ADDRESS)
 /*!< Uncomment the following line if you need to relocate your vector Table
diff --git a/firmware/Core/Src/telecommands/stm32_internal_flash_telecommand_defs.c b/firmware/Core/Src/telecommands/stm32_internal_flash_telecommand_defs.c
index e80c0de3f..d78155adc 100644
--- a/firmware/Core/Src/telecommands/stm32_internal_flash_telecommand_defs.c
+++ b/firmware/Core/Src/telecommands/stm32_internal_flash_telecommand_defs.c
@@ -114,4 +114,71 @@ uint8_t TCMDEXEC_stm32_internal_flash_erase(const char *args_str, TCMD_Telecomma
         return 1;
     }
     return 0;
+}
+
+/// @brief Get the option bytes configuration from the stm32 internal flash memory
+/// @param args_str No args
+/// @return 0 on success, > 0 on error
+uint8_t TCMDEXEC_stm32_internal_flash_get_option_bytes(const char *args_str, TCMD_TelecommandChannel_enum_t tcmd_channel, char *response_output_buf, uint16_t response_output_buf_len)
+{
+    FLASH_OBProgramInitTypeDef option_bytes;
+    const uint8_t res = STM32_internal_flash_get_option_bytes(&option_bytes);
+    if (res != 0)
+    {
+        snprintf(response_output_buf, response_output_buf_len, "Error: %u", res);
+        return 1;
+    }
+
+    snprintf(response_output_buf, response_output_buf_len,
+         "{\"OptionType\":%lu,\"WRPArea\":%lu,\"WRPStartOffset\":%lu,"
+         "\"WRPEndOffset\":%lu,\"RDPLevel\":%lu,\"USERType\":%lu,"
+         "\"USERConfig\":%lu,\"PCROPConfig\":%lu,\"PCROPStartAddr\":\"0x%lX\","
+         "\"PCROPEndAddr\":\"0x%lX\"}",
+         option_bytes.OptionType,
+         option_bytes.WRPArea,
+         option_bytes.WRPStartOffset,
+         option_bytes.WRPEndOffset,
+         option_bytes.RDPLevel,
+         option_bytes.USERType,
+         option_bytes.USERConfig,
+         option_bytes.PCROPConfig,
+         option_bytes.PCROPStartAddr,
+         option_bytes.PCROPEndAddr);
+
+    return 0;
+}
+
+/// @brief Given a 1 or 2, switches to the respective flash bank and runs the application stored there if present.
+/// Mostly useful for switching between 2 different version of the firmware (1 will be stored in Flash Bank 1, other will be stored in Flash Bank 2)
+/// @param args_str 
+/// - Arg 0: A 1 or 2. 1 to switch to the application present in Flash Bank 1, 2 to switch to the application present in Flash Bank 2
+/// @param response_output_buf Prints error if it occurs
+/// @return 0 on success, > 0 otherwise
+uint8_t TCMDEXEC_stm32_internal_flash_set_active_flash_bank(const char *args_str, TCMD_TelecommandChannel_enum_t tcmd_channel, char *response_output_buf, uint16_t response_output_buf_len)
+{
+    uint64_t desired_active_flash_bank = 0;
+    const uint8_t arg_res = TCMD_extract_uint64_arg(args_str, strlen(args_str), 0, &desired_active_flash_bank);
+    if (arg_res != 0)
+    {
+        snprintf(response_output_buf, response_output_buf_len, "Error Parsing Arg 0: %u", arg_res);
+        return 1;
+    }
+
+    const uint8_t res = STM32_internal_flash_set_active_flash_bank((uint8_t)desired_active_flash_bank);
+    if (res != 0)
+    {
+        snprintf(response_output_buf, response_output_buf_len, "Error changing flash bank: %u", res);
+        return 2;
+    }
+    return 0;
+}
+
+/// @brief Prints the active flash bank where the firmware boots from
+/// @param response_output_buf Prints the active bank
+uint8_t TCMDEXEC_stm32_internal_flash_get_active_flash_bank(const char *args_str, TCMD_TelecommandChannel_enum_t tcmd_channel, char *response_output_buf, uint16_t response_output_buf_len)
+{
+    const uint8_t stm32_internal_active_flash_bank = STM32_internal_flash_get_active_flash_bank();
+
+    snprintf(response_output_buf, response_output_buf_len, "Active Bank: %u", stm32_internal_active_flash_bank);
+    return 0;
 }
\ No newline at end of file
diff --git a/firmware/Core/Src/telecommands/telecommand_definitions.c b/firmware/Core/Src/telecommands/telecommand_definitions.c
index daf104823..8c32a1613 100644
--- a/firmware/Core/Src/telecommands/telecommand_definitions.c
+++ b/firmware/Core/Src/telecommands/telecommand_definitions.c
@@ -27,7 +27,6 @@
 #include "telecommands/telecommand_crc.h"
 #include "telecommands/gps_telecommand_defs.h"
 
-
 #include "timekeeping/timekeeping.h"
 #include "littlefs/littlefs_helper.h"
 #include "stm32/stm32_reboot_reason.h"
@@ -1017,6 +1016,28 @@ const TCMD_TelecommandDefinition_t TCMD_telecommand_definitions[] = {
         .readiness_level = TCMD_READINESS_LEVEL_GROUND_USAGE_ONLY,
     },
 
+    {
+        .tcmd_name = "stm32_internal_flash_get_option_bytes",
+        .tcmd_func = TCMDEXEC_stm32_internal_flash_get_option_bytes,
+        .number_of_args = 0,
+        .readiness_level = TCMD_READINESS_LEVEL_FOR_OPERATION,
+    },
+
+    {
+        .tcmd_name = "stm32_internal_flash_get_active_flash_bank",
+        .tcmd_func = TCMDEXEC_stm32_internal_flash_get_active_flash_bank,
+        .number_of_args = 0,
+        .readiness_level = TCMD_READINESS_LEVEL_FOR_OPERATION,
+    },
+    
+    {
+        .tcmd_name = "stm32_internal_flash_set_active_flash_bank",
+        .tcmd_func = TCMDEXEC_stm32_internal_flash_set_active_flash_bank,
+        .number_of_args = 1,
+        .readiness_level = TCMD_READINESS_LEVEL_FOR_OPERATION,
+    },
+    
+
     // ****************** END SECTION: stm32_internal_flash_telecommand_defs ******************
 
     // ****************** SECTION: antenna_telecommand_defs ******************


Reviews:
user: login: DeflateAwning
body: All seems fine. 

Does this PR add any features to CTS-SAT-1, or is it all pre-work for upcoming over-the-air upgrades (if implemented)?
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T05:21:02Z
user: login: DeflateAwning
body: All seems reasonable to me! Didn't test it though.

I'd recommend getting @robotoshi to check this next time he's around to independently verify that it works (on an OBC, probably), and also to validate that the documentation/guide is reasonable.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T20:15:10Z

Review comments:
diff_hunk: @@ -114,4 +114,78 @@ uint8_t TCMDEXEC_stm32_internal_flash_erase(const char *args_str, TCMD_Telecomma
         return 1;
     }
     return 0;
+}
+
+/// @brief Get the option bytes configuration from the stm32 internal flash memory
+/// @param args_str No args
+/// @return 0 on success, > 0 on error
+uint8_t TCMDEXEC_stm32_internal_flash_get_option_bytes(const char *args_str, TCMD_TelecommandChannel_enum_t tcmd_channel, char *response_output_buf, uint16_t response_output_buf_len)
+{
+    FLASH_OBProgramInitTypeDef option_bytes;
+    const uint8_t res = STM32_internal_flash_get_option_bytes(&option_bytes);
+    if (res != 0)
+    {
+        snprintf(response_output_buf, response_output_buf_len, "Error: %u", res);
+        return 1;
+    }
+
+    snprintf(response_output_buf, response_output_buf_len,
+             "{\n"
+             "  \"OptionType\": %lu,\n"
+             "  \"WRPArea\": %lu,\n"
+             "  \"WRPStartOffset\": %lu,\n"
+             "  \"WRPEndOffset\": %lu,\n"
+             "  \"RDPLevel\": %lu,\n"
+             "  \"USERType\": %lu,\n"
+             "  \"USERConfig\": %lu,\n"
+             "  \"PCROPConfig\": %lu,\n"
+             "  \"PCROPStartAddr\": \"0x%lX\",\n"
+             "  \"PCROPEndAddr\": \"0x%lX\"\n"
+             "}",
+             option_bytes.OptionType,
+             option_bytes.WRPArea,
+             option_bytes.WRPStartOffset,
+             option_bytes.WRPEndOffset,
+             option_bytes.RDPLevel,
+             option_bytes.USERType,
+             option_bytes.USERConfig,
+             option_bytes.PCROPConfig,
+             option_bytes.PCROPStartAddr,
+             option_bytes.PCROPEndAddr);
+    return 0;
+}
+
+/// @brief Given a 1 or 0, enables or disables dual bank boot option for internal flash
+/// @param args_str 
+/// - Arg 0: A 1 or 0. 1 enables dual bank boot, 0 disables
+/// @param response_output_buf Prints error if it occurs
+/// @return 0 on success, > 0 otherwise
+uint8_t TCMDEXEC_stm32_internal_flash_set_dual_bank_boot(const char *args_str, TCMD_TelecommandChannel_enum_t tcmd_channel, char *response_output_buf, uint16_t response_output_buf_len)
path: firmware/Core/Src/telecommands/stm32_internal_flash_telecommand_defs.c
user: login: DeflateAwning
body: What's the default? Why would anyone want to run this telecommand? 

I'm sure there's a reason, but it should go in this docstring.
created_at: 2025-03-13T05:18:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -114,4 +114,78 @@ uint8_t TCMDEXEC_stm32_internal_flash_erase(const char *args_str, TCMD_Telecomma
         return 1;
     }
     return 0;
+}
+
+/// @brief Get the option bytes configuration from the stm32 internal flash memory
+/// @param args_str No args
+/// @return 0 on success, > 0 on error
+uint8_t TCMDEXEC_stm32_internal_flash_get_option_bytes(const char *args_str, TCMD_TelecommandChannel_enum_t tcmd_channel, char *response_output_buf, uint16_t response_output_buf_len)
+{
+    FLASH_OBProgramInitTypeDef option_bytes;
+    const uint8_t res = STM32_internal_flash_get_option_bytes(&option_bytes);
+    if (res != 0)
+    {
+        snprintf(response_output_buf, response_output_buf_len, "Error: %u", res);
+        return 1;
+    }
+
+    snprintf(response_output_buf, response_output_buf_len,
+             "{\n"
+             "  \"OptionType\": %lu,\n"
path: firmware/Core/Src/telecommands/stm32_internal_flash_telecommand_defs.c
user: login: DeflateAwning
body: Generally we don't put the trailing newline whitespaces and instead rely on the JSON autoformatter in the Ground Support Terminal. Up to you though.
created_at: 2025-03-13T05:18:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -123,7 +123,7 @@
 /*!< Uncomment the following line if you need to relocate the vector table
      anywhere in Flash or Sram, else the vector table is kept at the automatic
      remap of boot address selected */
-/* #define USER_VECT_TAB_ADDRESS */
+#define USER_VECT_TAB_ADDRESS 
path: firmware/Core/Src/system_stm32l4xx.c
user: login: DeflateAwning
body: Can you please leave a comment like:
```
// CTS-SAT-1: The `USER_VECT_TAB_ADDRESS` macro is defined because...
```
created_at: 2025-03-13T05:19:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -138,4 +139,80 @@ uint8_t STM32_internal_flash_erase(uint16_t start_page_erase, uint16_t number_of
     default:
         return 0;
     }
-}
\ No newline at end of file
+}
+
+/// @brief Gets option bytes configuration from the stm32 internal flash memory
+/// @param ob_data pointer to an FLASH_OBProgramInitTypeDef structure that
+/// contains the configuration information for the programming.
+/// @return 0 always
+uint8_t STM32_internal_flash_get_option_bytes(FLASH_OBProgramInitTypeDef *ob_data)
+{
+    // returns void
+    HAL_FLASHEx_OBGetConfig(ob_data);
+    return 0;
+}
+
+/// @brief Sets the dual bank mode on or off depending on param
+/// @param dual_bank_mode 1 or 0
path: firmware/Core/Src/stm32_internal_flash_drivers/stm32_internal_flash_drivers.c
user: login: DeflateAwning
body: Duplicate the comment from the telecommand docstring please
created_at: 2025-03-13T05:20:21Z
author_association: CONTRIBUTOR

PR comments:
user: login: DeflateAwning
created_at: 2024-12-16T00:31:21Z
body: Please ensure pull requests are opened with a title which describes the change they implement.
author_association: CONTRIBUTOR
user: login: NuclearTea
created_at: 2024-12-16T00:39:58Z
body: I will add instructions on how to test in a bit.
author_association: CONTRIBUTOR
user: login: NuclearTea
created_at: 2025-01-23T16:02:23Z
body: @DeflateAwning After discussing with you, you recommended that I look for libraries that can assist with bootloader things. After searching for a while I found:

https://github.com/STMicroelectronics/stm32-mw-openbl

After looking into it, and playing around with it, I found out that for the most part, it does not give us anything really. It has some handy functions to read and write to internal flash memory as well as a jump to application function, however, most of the functions that are required must be implemented ourselves. It merely gives us templates and a couple of helper functions. If we really wanted, I could copy over the files that are for reading and writing internal flash memory however, the rest is really not useful for us.

Additionally, I have already compared my own implementation and their implementation of these functions and they are, for the most part, identical. Actually, I have much more error handling than they do in every function so that is actually an advantage for our source code. There is 1 place where I will update our implementation (which is to do with writing to the internal flash memory and when the data itself is not 8 bytes long) but other than that, its nearly identical.

For some reference: 
| Feature                     | Our Implementation (File)     | Their Implementation (File)                                                                                                                                                                                                 |
|-----------------------------|-------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| JumpToApplication           | Bootloader.c                  | Core/Interfaces/Patterns/Flash/flash_interface.c (https://github.com/STMicroelectronics/stm32-mw-openbl/blob/7689c2de09d3e107923bb2c5971eb09d9299a76e/Interfaces/Patterns/FLASH/flash_interface.c#L170)                     |
| Deinit Peripherals          | Bootloader.c                  | Core/openbl_core.c (https://github.com/STMicroelectronics/stm32-mw-openbl/blob/7689c2de09d3e107923bb2c5971eb09d9299a76e/Core/openbl_core.c#L57) I can’t even find the implementation for the OpenBootloader_DeInit function |
| Flash Lock/Unlock/OB_Unlock | stm32_internal_flash_driver.c | Core/Interfaces/Patterns/Flash/flash_interface.c (https://github.com/STMicroelectronics/stm32-mw-openbl/blob/7689c2de09d3e107923bb2c5971eb09d9299a76e/Interfaces/Patterns/FLASH/flash_interface.c#L79)                      |
| Flash Read/Write            | stm32_internal_flash_driver.c | Core/Interfaces/Patterns/Flash/flash_interface.c (https://github.com/STMicroelectronics/stm32-mw-openbl/blob/7689c2de09d3e107923bb2c5971eb09d9299a76e/Interfaces/Patterns/FLASH/flash_interface.c#L109)                     |



Would love your input on this! 
author_association: CONTRIBUTOR
user: login: NuclearTea
created_at: 2025-02-20T00:04:26Z
body: ## Notes
1. You will need the stm32-for-vscode extension (up-to-date version). **It is important that it is up to date**
2. You will need the `STM32CubeProgrammer` which is downloadable from the stm32 website

# Testing Steps
1. Checkout this branch `ali-restore-golden-copy-i24`
2. Open a serial terminal with the appropriate UART configurations.
3. `Clean Build` the project and `Flash` it to your STM32
4. Ensure that it is working as normal.
5. Open the STM32CubeProgrammer and connect it to you STM32 Microcontroller
	1. ![image](https://github.com/user-attachments/assets/34868303-2f37-41ab-b212-9ccff0b6a16c)
	2. This will cause your STM to pause. Your serial test will not be receiving anything new. (You can reset your STM32 manually by pressing the reset button on the microcontroller to resume it working while still being connected).
	3. Click the `Upload` button on the left toolbar. It is the second from the top.
		1. ![image](https://github.com/user-attachments/assets/f627d18b-9704-4f56-bd9f-bb3baa241252)
	4. This will show the following screen:
	   ![image](https://github.com/user-attachments/assets/b38f06e8-d450-453c-abff-501f71a9e964)
		1. Underneath the `red` 1. drawn, you can select the file path to the firmware .bin file. I would recommend making the following modifications to the firmware.
			1. Open `rtos_tasks.c` and modify the `TASK_DEBUG_print_heartbeat` function.
				1. In the while loop at the bottom: 
				2. ![image](https://github.com/user-attachments/assets/54ba022b-f24d-44e4-a8f5-df54bd1387a8)
				3. Edit line 68 in the screenshot above so that it says: `FrontierSat version 2 time: ....`. We are only adding the words: `version 2` infront of `FrontierSat` and not changing anything else.
		2. Click the `Build` button.
		3. **DO NOT FLASH**
		4. As shown in the above CubeProgrammer screenshot, underneath the `red` 1., please enter the path to this .bin file. It should be in the build/debug folder of the firmware project.
		5. To the left of the `red` 2., ensure the value is: `0x8100000`.
		6. In the checkboxes below the `red` 2., please ensure **Only** **the**`Verify programming` **is selected**.
		7. Click the `Start Program` button
		8. Most likely after completing, your stm will be paused. Press the reset button on your STM32 microcontroller.
		9. At this point, it should just like before, running the exact same as before. If you followed my recommendation before, you can confirm this as the heartbeat should be saying `FrontierSat time: ...`
		10. We can now jump to the new program by sending the following telecommand: `CTS1+stm32_internal_flash_set_dual_bank_boot(1)!`
			1. This enables `dual_bank_mode` which changes the start address to Flash Bank 2.
			2. Following is an example:
![image](https://github.com/user-attachments/assets/9130cb5f-faf6-4ea3-a081-29e550227073)
		11. To go back, we can just send the same telecommand but with 0 as the param: `CTS1+stm32_internal_flash_set_dual_bank_boot(0)!`

**PDF Version:** [Testing Steps for Jumping to Golden Copy.pdf](https://github.com/user-attachments/files/18877245/Testing.Steps.for.Jumping.to.Golden.Copy.pdf)

author_association: CONTRIBUTOR
user: login: NuclearTea
created_at: 2025-02-20T00:06:53Z
body: @DeflateAwning I realized that the entire bootloader section is technically not necessary actually though it may become useful later. Do you want me to remove it or keep it and see if #25 will require it? From what I discovered, it does not seem like it will be needed.
author_association: CONTRIBUTOR
user: login: NuclearTea
created_at: 2025-03-04T23:01:30Z
body: Removed all bootloader stuff, sqashed commits to make it concise

author_association: CONTRIBUTOR
user: login: NuclearTea
created_at: 2025-03-14T01:04:33Z
body: # Updated Testing Instructions
## Notes
1. You will need the stm32-for-vscode extension (up-to-date version). **It is important that it is up to date**
2. You will need the `STM32CubeProgrammer` which is downloadable from the stm32 website

# Testing Steps
1. Checkout this branch `ali-restore-golden-copy-i24`
2. Open a serial terminal with the appropriate UART configurations.
3. `Clean Build` the project and `Flash` it to your STM32
4. Ensure that it is working as normal.
5. Open the STM32CubeProgrammer and connect it to you STM32 Microcontroller
	1. ![image](https://github.com/user-attachments/assets/34868303-2f37-41ab-b212-9ccff0b6a16c)
	2. This will cause your STM to pause. Your serial test will not be receiving anything new. (You can reset your STM32 manually by pressing the reset button on the microcontroller to resume it working while still being connected).
	3. Click the `Upload` button on the left toolbar. It is the second from the top.
		1. ![image](https://github.com/user-attachments/assets/f627d18b-9704-4f56-bd9f-bb3baa241252)
	4. This will show the following screen:
	   ![image](https://github.com/user-attachments/assets/b38f06e8-d450-453c-abff-501f71a9e964)
		1. Underneath the `red` 1. drawn, you can select the file path to the firmware .bin file. I would recommend making the following modifications to the firmware.
			1. Open `rtos_tasks.c` and modify the `TASK_DEBUG_print_heartbeat` function.
				1. In the while loop at the bottom: 
				2. ![image](https://github.com/user-attachments/assets/54ba022b-f24d-44e4-a8f5-df54bd1387a8)
				3. Edit line 68 in the screenshot above so that it says: `FrontierSat version 2 time: ....`. We are only adding the words: `version 2` infront of `FrontierSat` and not changing anything else.
		2. Click the `Build` button.
		3. **DO NOT FLASH**
		4. As shown in the above CubeProgrammer screenshot, underneath the `red` 1., please enter the path to this .bin file. It should be in the build/debug folder of the firmware project.
		5. To the left of the `red` 2., ensure the value is: `0x8100000`.
		6. In the checkboxes below the `red` 2., please ensure **Only** **the**`Verify programming` **is selected**.
		7. Click the `Start Program` button
		8. Most likely after completing, your stm will be paused. Press the reset button on your STM32 microcontroller.
		9. At this point, it should just like before, running the exact same as before. If you followed my recommendation before, you can confirm this as the heartbeat should be saying `FrontierSat time: ...`
		10. We can now jump to the new program by sending the following telecommand: `CTS1+stm32_internal_flash_set_active_flash_bank(2)!`
			1. This enables `dual_bank_mode` which changes the start address to Flash Bank 2.
			2. Following is an example:
![image](https://github.com/user-attachments/assets/56e67ac2-3aef-4305-9ec0-bfa1953745d9)
		11. To go back, we can just send the same telecommand but with 1 as the param: `CTS1+stm32_internal_flash_set_active_flash_bank(1)!`

**PDF Version:**
[Testing Steps for Jumping to Golden Copy.pdf](https://github.com/user-attachments/files/19239006/Testing.Steps.for.Jumping.to.Golden.Copy.pdf)
)

author_association: CONTRIBUTOR
user: login: NuclearTea
created_at: 2025-03-15T18:48:53Z
body: @DeflateAwning Addressed all PR comments. Regarding your question about whether this adds a new feature or is pre-work. Techincally, if we had a "golden copy" flashed on the OBC, then this would be a feature as you could boot from it. It does not contain anything to do with loading a file from the memory module and storing it in our flash memory as that is: #18, #25
author_association: CONTRIBUTOR
user: login: kev9268
created_at: 2025-03-20T23:59:06Z
body: @DeflateAwning 

After setting up the test prerequisites I inputted CTS1+stm32_internal_flash_set_active_flash_bank(2)!

Output of CTS1+stm32_internal_flash_set_active_flash_bank(2)! :
```
CTS1+stm32_internal_flash_set_active_flash_bank(2)!
0000000000000+0000022083_N [TELECOMMAND:DEBUG]: Parsed telecommand (len=52): 'CTS1+stm32_internal_flash_set_active_flash_bank(2)!
'
0000000000000+0000022234_N [TELECOMMAND:NORMAL]: Executing telecommand from agenda slot 0, sent at tssent=0, scheduled for tsexec=0.
0000000000000+0000022246_N [TELECOMMAND:NORMAL]: ðŸš€ Executing telecommand 'stm32_internal_flash_set_active_flash_bank'.


MX_Init() done
0000000000000+0000000066_N [EPS:ERROR]: EPS UART Reception Error: 4
0000000000000+0000000170_N [OBC:NORMAL]: TASK_DEBUG_print_heartbeat() -> started (booted).
0000000000000+0000000178_N [OBC:NORMAL]: Reset reason: EXTERNAL_RESET_PIN.
0000000000000+0000005170_N [EPS:NORMAL]: Setting OBC time based on EPS time because last_source == TIM_SOURCE_NONE
0000000000000+0000005233_N [EPS:ERROR]: EPS->OBC: timeout before first byte received
0000000000000+0000005240_N [EPS:ERROR]: EPS_set_obc_time_based_on_eps_time() -> Error
0000000000000+0000010222_N [EPS:ERROR]: EPS->OBC: timeout before first byte received
0000000000000+0000010229_N [EPS:ERROR]: EPS_CMD_watchdog() -> Error: 3
0000000000000+0000010248_N [EPS:NORMAL]: Setting OBC time based on EPS time because last_source == TIM_SOURCE_NONE
0000000000000+0000010311_N [EPS:ERROR]: EPS->OBC: timeout before first byte received
0000000000000+0000010318_N [EPS:ERROR]: EPS_set_obc_time_based_on_eps_time() -> Error
0000000000000+0000012155_N [OBC:DEBUG]: Heartbeat: FrontierSat v2 time: 19700101T00:00:12.155, Uptime: 12155 ms
```

From the output we can see the heartbeat prints out FrontierSat v2 as expected

Now switching back by inputting the tcmd  CTS1+stm32_internal_flash_set_active_flash_bank(1)!

Output of CTS1+stm32_internal_flash_set_active_flash_bank(1)! 
```
0000000000000+0000254169_N [OBC:DEBUG]: Heartbeat: FrontierSat v2 time: 19700101T00:04:14.169, Uptime: 254169 ms

0000000000000+0000259070_N [EPS:NORMAL]: Setting OBC time based on EPS time because last_source == TIM_SOURCE_NONE
0000000000000+0000259133_N [EPS:ERROR]: EPS->OBC: timeout before first byte received
0000000000000+0000259140_N [EPS:ERROR]: EPS_set_obc_time_based_on_eps_time() -> Error
CTS1+stm32_internal_flash_set_active_flash_bank(1)!
0000000000000+0000259828_N [TELECOMMAND:DEBUG]: Parsed telecommand (len=52): 'CTS1+stm32_internal_flash_set_active_flash_bank(1)!
'
0000000000000+0000259899_N [TELECOMMAND:NORMAL]: Executing telecommand from agenda slot 0, sent at tssent=0, scheduled for tsexec=0.
0000000000000+0000259912_N [TELECOMMAND:NORMAL]: ðŸš€ Executing telecommand 'stm32_internal_flash_set_active_flash_bank'.


MX_Init() done
0000000000000+0000000066_N [EPS:ERROR]: EPS UART Reception Error: 4
0000000000000+0000000170_N [OBC:NORMAL]: TASK_DEBUG_print_heartbeat() -> started (booted).
0000000000000+0000000178_N [OBC:NORMAL]: Reset reason: EXTERNAL_RESET_PIN.
0000000000000+0000005170_N [EPS:NORMAL]: Setting OBC time based on EPS time because last_source == TIM_SOURCE_NONE
0000000000000+0000005233_N [EPS:ERROR]: EPS->OBC: timeout before first byte received
0000000000000+0000005240_N [EPS:ERROR]: EPS_set_obc_time_based_on_eps_time() -> Error
0000000000000+0000010222_N [EPS:ERROR]: EPS->OBC: timeout before first byte received
0000000000000+0000010229_N [EPS:ERROR]: EPS_CMD_watchdog() -> Error: 3
0000000000000+0000010248_N [EPS:NORMAL]: Setting OBC time based on EPS time because last_source == TIM_SOURCE_NONE
0000000000000+0000010311_N [EPS:ERROR]: EPS->OBC: timeout before first byte received
0000000000000+0000010318_N [EPS:ERROR]: EPS_set_obc_time_based_on_eps_time() -> Error
0000000000000+0000012155_N [OBC:DEBUG]: Heartbeat: FrontierSat time: 19700101T00:00:12.155, Uptime: 12155 ms
```

From the output we see that the heartbeat print has returned back to only FrontierSat as expected in the testing instructions.



author_association: CONTRIBUTOR