Skip to content

Commit 9be2ae4

Browse files
committed
Implement SUP (Simple UART Protocol)
1 parent 4cc4513 commit 9be2ae4

File tree

12 files changed

+773
-42
lines changed

12 files changed

+773
-42
lines changed

.clang-format

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
1-
# This .clang-format file is tailored for C projects following widely accepted best practices.
2-
# It enforces readability, consistency, and compatibility with most open source C codebases.
31
BasedOnStyle: LLVM
4-
Language: C
52
IndentWidth: 4
63
TabWidth: 4
74
UseTab: Never
8-
ColumnLimit: 80
5+
ColumnLimit: 120
96
AllowShortIfStatementsOnASingleLine: false
107
AllowShortLoopsOnASingleLine: false
118
AllowShortFunctionsOnASingleLine: InlineOnly

CMakeLists.txt

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
cmake_minimum_required(VERSION 3.10)
1+
cmake_minimum_required(VERSION 3.13)
22

33
# Use the AVR toolchain file (set before project())
44
set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_SOURCE_DIR}/toolchain.cmake CACHE STRING "AVR toolchain file")
5-
project("AVR Bootloader" C)
5+
6+
# Dynamically set project name from directory name
7+
get_filename_component(PROJECT_NAME ${CMAKE_CURRENT_SOURCE_DIR} NAME)
8+
project(${PROJECT_NAME} C)
69

710
# ---------------------------------------------------------------------------
811
# Common configuration
@@ -14,6 +17,9 @@ set(MCU atmega328p)
1417
# This definition is essential for libraries like <util/delay.h>
1518
set(F_CPU 16000000UL)
1619

20+
# UART Baud rate settings
21+
set(BAUD 9600)
22+
1723
# Programmer for avrdude
1824
set(AVRDUDE_PROGRAMMER usbasp)
1925

@@ -23,11 +29,12 @@ set(CMAKE_C_STANDARD_REQUIRED ON)
2329
set(CMAKE_C_EXTENSIONS OFF)
2430

2531
# Common compiler flags for AVR targets
26-
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Os -mmcu=${MCU} -DF_CPU=${F_CPU}")
32+
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Os -mmcu=${MCU} -DF_CPU=${F_CPU} -DBAUD=${BAUD}")
2733

2834
# Add subprojects
2935
add_subdirectory(blinky)
3036
add_subdirectory(bootloader_hardcoded)
37+
add_subdirectory(simple_uart_protocol)
3138

3239
# Clean all build files and directories
3340
add_custom_target(

README.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,19 @@ To prepare your build environment first read this tutorial:
55
- [Getting started with AVR programming](https://github.com/m3y54m/start-avr)
66

77
> [!NOTE]
8-
> **A "bootloader" is a small program that is written to a dedicated section of the non-volatile memory of a computer.
8+
> A "bootloader" is a small program that is written to a dedicated section of the non-volatile memory of a computer.
99
> In microcontrollers it is mostly used to facilitate the updating of the main program by utilizing a communication peripheral,
1010
> thereby eliminating the requirement for an external programmer. In more sophisticated computer systems, a bootloader is mostly
11-
> employed to pre-configure the system clock and input/output interfaces.**
11+
> employed to pre-configure the system clock and input/output interfaces.
1212
>
13-
> **With this definition in mind, what follows is not a practical bootloader. Instead, it is a tutorial designed to step-by-step
13+
> With this definition in mind, what follows is not a practical bootloader. Instead, it is a tutorial designed to step-by-step
1414
> illustrate the process of program compilation and configuration to show how a bootloader can self-program the microcontroller.
15-
> This bootloader is literally hardcoding the binary data of the program you want to upload (**[**`blinky`**](blinky)**) in the
15+
> This bootloader is literally hardcoding the binary data of the program you want to upload ([`blinky`](blinky)) in the
1616
> bootloader itself. With some small changes in code you can modify it to receive binary of the program you want to upload through
17-
> UART, I2C or SPI. To learn how to write a more sophisticated and secure bootloader study the** [**resources**](#resources).
17+
> UART, I2C or SPI. To learn how to write a more sophisticated and secure bootloader study the [resources](#resources).
18+
19+
> [!CAUTION]
20+
> Please note that the code and materials provided in this repository are intended for **EDUCATIONAL** purposes only and is **NOT SAFE** to be used in production.
1821
1922
*DONE:*
2023
- Configure fuse bits settings for bootloader section size and reset vector

blinky/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
cmake_minimum_required(VERSION 3.10)
1+
cmake_minimum_required(VERSION 3.13)
22

33
# Dynamically set project name from directory name
44
get_filename_component(PROJECT_NAME ${CMAKE_CURRENT_SOURCE_DIR} NAME)

blinky/src/main.c

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
/**
2-
* @brief Blinky application for the ATmega328P.
2+
* @file main.c
3+
* @brief Blinky application
4+
*
35
* @details This application blinks an LED connected to pin PB5
46
* (Arduino Uno D13) at a 5Hz frequency.
7+
*
8+
* @warning This code is provided for educational purposes only and is not
9+
* intended for production use. Use at your own risk. No warranty is provided.
510
*/
611

7-
#include <stdint.h>
812
#include <avr/io.h>
13+
#include <stdint.h>
914
#include <util/delay.h>
1015

1116
#define LED_PIN PB5
@@ -19,6 +24,7 @@ int main(void)
1924
{
2025
// Toggle the LED
2126
PORTB ^= (1U << LED_PIN);
27+
2228
// Wait for 100 ms
2329
_delay_ms(100U);
2430
}

bootloader_hardcoded/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

2-
cmake_minimum_required(VERSION 3.10)
2+
cmake_minimum_required(VERSION 3.13)
33

44
# Dynamically set project name from directory name
55
get_filename_component(PROJECT_NAME ${CMAKE_CURRENT_SOURCE_DIR} NAME)

bootloader_hardcoded/src/main.c

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
/**
2-
* @brief A hardcoded bootloader for the ATmega328P.
2+
* @file main.c
3+
* @brief Hardcoded bootloader
4+
*
35
* @details This bootloader checks if a user application exists at
4-
* destination_address 0x0000. If not, it flashes a built-in "blinky"
6+
* destination_address 0x0000. If not, it flashes a hardcoded "blinky"
57
* application to that destination_address. It then jumps to the application
68
* code.
9+
*
10+
* @warning This code is provided for educational purposes only and is not
11+
* intended for production use. Use at your own risk. No warranty is provided.
712
*/
813

914
#include <avr/boot.h>
@@ -25,20 +30,15 @@
2530
* Program size: 162 bytes.
2631
*/
2732
static const uint8_t hardcoded_blinky_bin[] PROGMEM = {
28-
0x0C, 0x94, 0x34, 0x00, 0x0C, 0x94, 0x3E, 0x00, 0x0C, 0x94, 0x3E, 0x00,
29-
0x0C, 0x94, 0x3E, 0x00, 0x0C, 0x94, 0x3E, 0x00, 0x0C, 0x94, 0x3E, 0x00,
30-
0x0C, 0x94, 0x3E, 0x00, 0x0C, 0x94, 0x3E, 0x00, 0x0C, 0x94, 0x3E, 0x00,
31-
0x0C, 0x94, 0x3E, 0x00, 0x0C, 0x94, 0x3E, 0x00, 0x0C, 0x94, 0x3E, 0x00,
32-
0x0C, 0x94, 0x3E, 0x00, 0x0C, 0x94, 0x3E, 0x00, 0x0C, 0x94, 0x3E, 0x00,
33-
0x0C, 0x94, 0x3E, 0x00, 0x0C, 0x94, 0x3E, 0x00, 0x0C, 0x94, 0x3E, 0x00,
34-
0x0C, 0x94, 0x3E, 0x00, 0x0C, 0x94, 0x3E, 0x00, 0x0C, 0x94, 0x3E, 0x00,
35-
0x0C, 0x94, 0x3E, 0x00, 0x0C, 0x94, 0x3E, 0x00, 0x0C, 0x94, 0x3E, 0x00,
36-
0x0C, 0x94, 0x3E, 0x00, 0x0C, 0x94, 0x3E, 0x00, 0x11, 0x24, 0x1F, 0xBE,
37-
0xCF, 0xEF, 0xD8, 0xE0, 0xDE, 0xBF, 0xCD, 0xBF, 0x0E, 0x94, 0x40, 0x00,
38-
0x0C, 0x94, 0x4F, 0x00, 0x0C, 0x94, 0x00, 0x00, 0x25, 0x9A, 0x90, 0xE2,
39-
0x85, 0xB1, 0x89, 0x27, 0x85, 0xB9, 0x2F, 0xEF, 0x31, 0xEE, 0x84, 0xE0,
40-
0x21, 0x50, 0x30, 0x40, 0x80, 0x40, 0xE1, 0xF7, 0x00, 0xC0, 0x00, 0x00,
41-
0xF3, 0xCF, 0xF8, 0x94, 0xFF, 0xCF};
33+
0x0C, 0x94, 0x34, 0x00, 0x0C, 0x94, 0x3E, 0x00, 0x0C, 0x94, 0x3E, 0x00, 0x0C, 0x94, 0x3E, 0x00, 0x0C, 0x94,
34+
0x3E, 0x00, 0x0C, 0x94, 0x3E, 0x00, 0x0C, 0x94, 0x3E, 0x00, 0x0C, 0x94, 0x3E, 0x00, 0x0C, 0x94, 0x3E, 0x00,
35+
0x0C, 0x94, 0x3E, 0x00, 0x0C, 0x94, 0x3E, 0x00, 0x0C, 0x94, 0x3E, 0x00, 0x0C, 0x94, 0x3E, 0x00, 0x0C, 0x94,
36+
0x3E, 0x00, 0x0C, 0x94, 0x3E, 0x00, 0x0C, 0x94, 0x3E, 0x00, 0x0C, 0x94, 0x3E, 0x00, 0x0C, 0x94, 0x3E, 0x00,
37+
0x0C, 0x94, 0x3E, 0x00, 0x0C, 0x94, 0x3E, 0x00, 0x0C, 0x94, 0x3E, 0x00, 0x0C, 0x94, 0x3E, 0x00, 0x0C, 0x94,
38+
0x3E, 0x00, 0x0C, 0x94, 0x3E, 0x00, 0x0C, 0x94, 0x3E, 0x00, 0x0C, 0x94, 0x3E, 0x00, 0x11, 0x24, 0x1F, 0xBE,
39+
0xCF, 0xEF, 0xD8, 0xE0, 0xDE, 0xBF, 0xCD, 0xBF, 0x0E, 0x94, 0x40, 0x00, 0x0C, 0x94, 0x4F, 0x00, 0x0C, 0x94,
40+
0x00, 0x00, 0x25, 0x9A, 0x90, 0xE2, 0x85, 0xB1, 0x89, 0x27, 0x85, 0xB9, 0x2F, 0xEF, 0x31, 0xEE, 0x84, 0xE0,
41+
0x21, 0x50, 0x30, 0x40, 0x80, 0x40, 0xE1, 0xF7, 0x00, 0xC0, 0x00, 0x00, 0xF3, 0xCF, 0xF8, 0x94, 0xFF, 0xCF};
4242

4343
// --- Configuration Constants ---
4444

@@ -119,9 +119,8 @@ static bool user_app_exists(void)
119119
* @note This function must be called from the bootloader section.
120120
* @return true on success, false on error
121121
*/
122-
static bool write_to_flash(const uint32_t destination_address,
123-
const uint8_t* source_data,
124-
const size_t source_data_size)
122+
static bool write_to_flash(const uint32_t destination_address, const uint8_t* source_data,
123+
const size_t source_data_size)
125124
{
126125
if ((source_data == NULL) || (source_data_size == 0))
127126
{
@@ -146,8 +145,7 @@ static bool write_to_flash(const uint32_t destination_address,
146145
eeprom_busy_wait();
147146

148147
// Process each page
149-
for (uint32_t page_addr = destination_address;
150-
page_addr < (destination_address + source_data_size);
148+
for (uint32_t page_addr = destination_address; page_addr < (destination_address + source_data_size);
151149
page_addr += SPM_PAGESIZE)
152150
{
153151
// Erase page
@@ -156,8 +154,7 @@ static bool write_to_flash(const uint32_t destination_address,
156154
boot_spm_busy_wait();
157155

158156
// Fill page buffer word by word
159-
for (uint16_t offset = 0; offset < SPM_PAGESIZE;
160-
offset += WORD_SIZE_BYTES)
157+
for (uint16_t offset = 0; offset < SPM_PAGESIZE; offset += WORD_SIZE_BYTES)
161158
{
162159
uint16_t word_data = FLASH_EMPTY_WORD; // Default to erased state
163160

@@ -226,9 +223,9 @@ static void __attribute__((noreturn)) jump_to_user_app(void)
226223

227224
// Jump to application start address
228225
// The user application expects r1 to be zero when it starts
229-
__asm__ __volatile__("clr r1\n\t" // Clear register r1 (zero register)
230-
"jmp %0" // Jump to application
231-
: // Output operands (empty)
226+
__asm__ __volatile__("clr r1\n\t" // Clear register r1 (zero register)
227+
"jmp %0" // Jump to application
228+
: // Output operands (empty)
232229
: "i"(USER_APP_START_ADDR)); // Input operands
233230

234231
// Should never reach here
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
cmake_minimum_required(VERSION 3.13)
2+
3+
# Dynamically set project name from directory name
4+
get_filename_component(PROJECT_NAME ${CMAKE_CURRENT_SOURCE_DIR} NAME)
5+
project(${PROJECT_NAME} C)
6+
7+
add_executable(${PROJECT_NAME}.elf src/main.c src/sup.c)
8+
9+
add_custom_target(
10+
${PROJECT_NAME}_build ALL
11+
COMMAND avr-objcopy -j .text -j .data -O ihex ${PROJECT_NAME}.elf ${PROJECT_NAME}.hex
12+
COMMAND avr-objcopy -j .text -j .data -O binary ${PROJECT_NAME}.elf ${PROJECT_NAME}.bin
13+
COMMAND avr-size --format=avr --mcu=${MCU} ${PROJECT_NAME}.elf
14+
DEPENDS ${PROJECT_NAME}.elf
15+
COMMENT "[[${PROJECT_NAME}]] Building .hex and .bin files for \"${MCU}\""
16+
)
17+
18+
add_custom_target(
19+
${PROJECT_NAME}_flash
20+
COMMAND avrdude -c ${AVRDUDE_PROGRAMMER} -p ${MCU} -U flash:w:${PROJECT_NAME}.hex
21+
DEPENDS ${PROJECT_NAME}_build
22+
COMMENT "[[${PROJECT_NAME}]] Flashing to \"${MCU}\" using \"${AVRDUDE_PROGRAMMER}\""
23+
)

simple_uart_protocol/demo.py

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import serial
2+
import time
3+
4+
# SUP Protocol Constants
5+
SUP_SOF = 0xA1
6+
SUP_EOF = 0xE9
7+
SUP_MAX_PAYLOAD_SIZE = 64
8+
9+
# SUP Frame IDs
10+
SUP_ID_ACK = 0x01 # Acknowledgment for received frame
11+
SUP_ID_NACK = 0x02 # Negative Acknowledgment for received frame
12+
SUP_ID_DATA = 0x03 # Binary data chunk transfer
13+
SUP_ID_CMD_FW_UPDATE = 0x11 # Firmware Update Command
14+
15+
16+
# SUP Protocol Functions
17+
def calculate_checksum(payload_size, id, payload):
18+
"""Calculates the checksum for a SUP frame."""
19+
checksum = payload_size + id
20+
for byte in payload:
21+
checksum += byte
22+
return checksum & 0xFF # Ensure checksum is a single byte
23+
24+
25+
def create_sup_frame(id, payload=b""):
26+
"""Creates a complete SUP frame as a bytearray."""
27+
payload_size = len(payload)
28+
if payload_size > SUP_MAX_PAYLOAD_SIZE:
29+
raise ValueError(
30+
f"Payload size ({payload_size}) exceeds max limit ({SUP_MAX_PAYLOAD_SIZE})"
31+
)
32+
33+
checksum = calculate_checksum(payload_size, id, payload)
34+
35+
frame = bytearray()
36+
frame.append(SUP_SOF)
37+
frame.append(id)
38+
frame.append(payload_size)
39+
frame.extend(payload)
40+
frame.append(checksum)
41+
frame.append(SUP_EOF)
42+
43+
return frame
44+
45+
46+
def parse_sup_frames(buffer: bytearray):
47+
"""Parse a byte buffer and return a list of full SUP frames (each as bytearray).
48+
49+
A SUP frame has the structure:
50+
SOF(1) ID(1) SIZE(1) PAYLOAD(SIZE) CHECKSUM(1) EOF(1)
51+
52+
This function extracts complete frames and skips incomplete or corrupted segments.
53+
"""
54+
frames = []
55+
i = 0
56+
buf_len = len(buffer)
57+
while i < buf_len:
58+
# find next SOF
59+
if buffer[i] != SUP_SOF:
60+
i += 1
61+
continue
62+
63+
# need at least SOF + ID + SIZE + CHECKSUM + EOF => 5 bytes (with 0 payload)
64+
if i + 5 > buf_len:
65+
# incomplete header/short tail -- wait for more bytes
66+
break
67+
68+
# read size
69+
payload_size = buffer[i + 2]
70+
total_len = payload_size + 5 # SOF, ID, SIZE, PAYLOAD, CHK, EOF => payload+5
71+
72+
# check if full frame is present
73+
if i + total_len > buf_len:
74+
# incomplete frame
75+
break
76+
77+
# check EOF marker
78+
if buffer[i + total_len - 1] != SUP_EOF:
79+
# malformed frame: skip this SOF and continue searching
80+
i += 1
81+
continue
82+
83+
# extract full frame
84+
frame = bytearray(buffer[i : i + total_len])
85+
frames.append(frame)
86+
87+
# advance index past this frame
88+
i += total_len
89+
90+
return frames
91+
92+
93+
def main():
94+
"""Main function to demonstrate communication with the AVR."""
95+
# Configure the serial port
96+
ser = serial.Serial(
97+
port="COM9", # Change to your COM port (e.g., '/dev/ttyACM0' on Linux)
98+
baudrate=9600,
99+
parity=serial.PARITY_NONE,
100+
stopbits=serial.STOPBITS_ONE,
101+
bytesize=serial.EIGHTBITS,
102+
timeout=1,
103+
)
104+
105+
print(f"Connected to {ser.name}")
106+
107+
try:
108+
# Example 1: Send a simple message to the AVR
109+
demo_id = SUP_ID_DATA
110+
demo_payload = bytearray([0x44, 0x55, 0x66, 0x77])
111+
112+
tx_frame = create_sup_frame(demo_id, demo_payload)
113+
print(f"Sending frame:\r\n {[f'{b:02X}' for b in tx_frame]}")
114+
ser.write(tx_frame)
115+
116+
# Wait for a response from the AVR
117+
time.sleep(0.5)
118+
119+
rx_buffer = bytearray()
120+
while ser.in_waiting > 0:
121+
rx_buffer.extend(ser.read(ser.in_waiting))
122+
time.sleep(0.1) # Give some time for more data to arrive
123+
124+
if rx_buffer:
125+
# split into SUP frames (there may be multiple concatenated frames)
126+
parsed = parse_sup_frames(rx_buffer)
127+
if parsed:
128+
print("Received frames:")
129+
for f in parsed:
130+
print(f" {[f'{b:02X}' for b in f]}")
131+
else:
132+
# fallback: print raw buffer if no valid frames found
133+
print(f"Received bytes (raw): {[f'{b:02X}' for b in rx_buffer]}")
134+
else:
135+
print("No response from AVR.")
136+
137+
except serial.SerialException as e:
138+
print(f"Serial communication error: {e}")
139+
except Exception as e:
140+
print(f"An error occurred: {e}")
141+
finally:
142+
if ser.is_open:
143+
ser.close()
144+
print("Serial port closed.")
145+
146+
147+
if __name__ == "__main__":
148+
main()

0 commit comments

Comments
 (0)