feat(rs485-boot): add STM32F103 and G474 ports

This commit is contained in:
2026-08-30 01:37:12 +03:00
parent 94c991380b
commit 217073982f
12 changed files with 334 additions and 0 deletions

View File

@@ -32,6 +32,7 @@ templates/
| [`c/ds18b20`](c/ds18b20) | термометры DS18B20 поверх программной 1-Wire | `stdint.h` | Init, DelayUs, Reset, WriteBit, ReadBit — **порты STM32F103, STM32G431 и STM32G474 в комплекте** |
| [`c/protocan-transport`](c/protocan-transport) | транспорт ProtoCAN: кадр, канал, CRC, общее адресное пространство, каталог GUI | `stdint.h` | запись в поток и запрос свободного места — **порт STM32F4 в комплекте** |
| [`c/protocan-boot`](c/protocan-boot) | адресная прошивка по ProtoCAN: A/B-слоты, сессия, CRC32, verify и rollback-контракт | C99 | CAN TX, erase/write Flash, boot metadata, проверка образа и reboot |
| [`c/rs485-boot`](c/rs485-boot) | прошивка по RS-485 в формате SETGUI v1: потоковый parser, CRC32 и resume | C99 | UART TX/RX, DE, Flash — **порты STM32F103 и STM32G474VET в комплекте** |
| [`c/set-protocol`](c/set-protocol) | единый SET protocol v2: управление, real-time телеметрия и обновление прошивки через UART/CAN/USB/Ethernet | C99 | доставка целого stream/datagram-кадра, часы, backend карты и загрузчика |
| [`c/rtc-service`](c/rtc-service) | RTC с резервированным backup-томом | `stdint.h` | доступ к RTC и backup-памяти — **порт K1921VK028 в комплекте** |

View File

@@ -0,0 +1,9 @@
cmake_minimum_required(VERSION 3.16)
project(rs485_boot C)
add_library(rs485_boot src/rs485_boot.c)
target_include_directories(rs485_boot PUBLIC include)
if(BUILD_TESTING)
add_executable(test_rs485_boot tests/test_rs485_boot.c)
target_link_libraries(test_rs485_boot PRIVATE rs485_boot)
add_test(NAME rs485_boot COMMAND test_rs485_boot)
endif()

24
c/rs485-boot/README.md Normal file
View File

@@ -0,0 +1,24 @@
# RS-485 firmware boot
Переносимое ядро обновления прошивки по полудуплексному RS-485. Wire-протокол
совместим с SETGUI protocol v1 проекта `KONOR_ds18b20`: кадры `A5 5A`, CRC32,
команды `FIRMWARE_BEGIN/DATA/END/ABORT/STATUS` (`0x0B..0x0F`).
Ядро не обращается к регистрам МК. Проект передаёт ему принятые байты через
`rs485_boot_feed()` и реализует callbacks Flash/передачи из `rs485_boot_port_t`.
Готовые bare-metal порты находятся в `ports/stm32f103` и `ports/stm32g474vet`.
Важно: линии A/B должны иметь терминацию и fail-safe bias. Порт держит DE
активным до флага физического завершения передачи TC, затем возвращается в RX.
## Подключение
1. Добавить `src/rs485_boot.c` и порт нужного МК в проект.
2. Скопировать `rs485_boot_port_config.template.h` в include-каталог проекта как
`rs485_boot_port_config.h` и проверить UART, DE, адрес приложения и Flash.
3. Вызвать `rs485_boot_hw_init()`, затем `rs485_boot_init()` с callbacks из
`rs485_boot_hw_make_port()`.
4. В основном цикле читать `rs485_boot_hw_get_byte()` и передавать байты ядру.
Порт STM32F103 рассчитан на стандартную периферию STM32F10x. Порт G474VET
использует CMSIS STM32G4 (`stm32g474xx.h`) и 64-битное программирование Flash.

View File

@@ -0,0 +1,73 @@
#ifndef RS485_BOOT_H
#define RS485_BOOT_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define RS485_BOOT_VERSION 1U
#define RS485_BOOT_MAX_PAYLOAD 512U
#define RS485_BOOT_MAX_FRAME (8U + RS485_BOOT_MAX_PAYLOAD + 4U)
#define RS485_BOOT_DATA_HEADER_SIZE 8U
#define RS485_BOOT_MAX_BLOCK (RS485_BOOT_MAX_PAYLOAD - RS485_BOOT_DATA_HEADER_SIZE)
typedef enum {
RS485_BOOT_IDLE = 0U,
RS485_BOOT_RECEIVING = 1U,
RS485_BOOT_READY = 2U,
RS485_BOOT_FAILED = 3U
} rs485_boot_state_t;
typedef enum {
RS485_BOOT_OK = 0U,
RS485_BOOT_INVALID_ARGUMENT = 1U,
RS485_BOOT_INVALID_LENGTH = 2U,
RS485_BOOT_BUSY = 5U,
RS485_BOOT_INTERNAL = 7U,
RS485_BOOT_UNSUPPORTED = 0x11U
} rs485_boot_result_t;
typedef struct {
uint32_t app_address;
uint32_t app_size;
uint16_t max_block_size;
} rs485_boot_config_t;
typedef struct {
bool (*transmit)(void *user, const uint8_t *data, uint16_t length);
bool (*erase)(void *user, uint32_t image_size);
bool (*write)(void *user, uint32_t offset, const uint8_t *data, uint16_t length);
bool (*read)(void *user, uint32_t offset, uint8_t *data, uint16_t length);
bool (*image_valid)(void *user, uint32_t image_size, uint32_t image_crc32);
void (*reboot)(void *user);
} rs485_boot_port_t;
typedef struct {
rs485_boot_config_t config;
rs485_boot_port_t port;
void *port_user;
uint8_t parser[RS485_BOOT_MAX_FRAME];
uint16_t parser_length;
uint8_t tx[RS485_BOOT_MAX_FRAME];
uint8_t scratch[32];
uint32_t image_size;
uint32_t image_crc32;
uint32_t next_offset;
uint32_t crc_errors;
rs485_boot_state_t state;
} rs485_boot_t;
bool rs485_boot_init(rs485_boot_t *boot, const rs485_boot_config_t *config,
const rs485_boot_port_t *port, void *port_user);
void rs485_boot_feed(rs485_boot_t *boot, const uint8_t *data, size_t length);
void rs485_boot_abort(rs485_boot_t *boot);
uint32_t rs485_boot_crc32(const uint8_t *data, size_t length);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,8 @@
#ifndef RS485_BOOT_PORT_CONFIG_H
#define RS485_BOOT_PORT_CONFIG_H
#define RS485_BOOT_BAUDRATE 512000UL
#define RS485_BOOT_APP_ADDRESS 0x08003000UL
#define RS485_BOOT_FLASH_END 0x08010000UL
#define RS485_BOOT_FLASH_PAGE 1024UL
#define RS485_BOOT_DE_PIN 8U /* USART1 PA9/PA10; DE PA8, active high. */
#endif

View File

@@ -0,0 +1,17 @@
#include "rs485_boot_stm32f103.h"
#include "rs485_boot_port_config.h"
#include "stm32f10x.h"
#include <string.h>
static bool fw(void){while(FLASH->SR&FLASH_SR_BSY){}if(FLASH->SR&(FLASH_SR_PGERR|FLASH_SR_WRPRTERR)){FLASH->SR=FLASH_SR_PGERR|FLASH_SR_WRPRTERR|FLASH_SR_EOP;return false;}FLASH->SR=FLASH_SR_EOP;return true;}
static void ul(void){if(FLASH->CR&FLASH_CR_LOCK){FLASH->KEYR=0x45670123UL;FLASH->KEYR=0xCDEF89ABUL;}}
static void lk(void){FLASH->CR|=FLASH_CR_LOCK;}
void rs485_boot_hw_init(void){uint32_t c;RCC->APB2ENR|=RCC_APB2ENR_IOPAEN|RCC_APB2ENR_USART1EN;c=GPIOA->CRH;c&=~((0xFUL<<0)|(0xFUL<<4)|(0xFUL<<8));c|=(0x3UL<<0)|(0xBUL<<4)|(0x4UL<<8);GPIOA->CRH=c;GPIOA->BRR=1UL<<RS485_BOOT_DE_PIN;USART1->BRR=(SystemCoreClock+RS485_BOOT_BAUDRATE/2U)/RS485_BOOT_BAUDRATE;USART1->CR1=USART_CR1_TE|USART_CR1_RE|USART_CR1_UE;}
bool rs485_boot_hw_get_byte(uint8_t*b){uint32_t s=USART1->SR;if(s&(USART_SR_ORE|USART_SR_FE|USART_SR_NE)){(void)USART1->DR;return false;}if(!(s&USART_SR_RXNE))return false;*b=(uint8_t)USART1->DR;return true;}
static bool tx(void*u,const uint8_t*d,uint16_t n){uint16_t i;(void)u;GPIOA->BSRR=1UL<<RS485_BOOT_DE_PIN;for(i=0;i<n;i++){while(!(USART1->SR&USART_SR_TXE)){}USART1->DR=d[i];}while(!(USART1->SR&USART_SR_TC)){}GPIOA->BRR=1UL<<RS485_BOOT_DE_PIN;return true;}
static bool er(void*u,uint32_t n){uint32_t a,e;(void)u;if(!n||n>RS485_BOOT_FLASH_END-RS485_BOOT_APP_ADDRESS)return false;e=RS485_BOOT_APP_ADDRESS+((n+RS485_BOOT_FLASH_PAGE-1U)&~(RS485_BOOT_FLASH_PAGE-1U));ul();for(a=RS485_BOOT_APP_ADDRESS;a<e;a+=RS485_BOOT_FLASH_PAGE){FLASH->CR=FLASH_CR_PER;FLASH->AR=a;FLASH->CR|=FLASH_CR_STRT;if(!fw()){FLASH->CR=0;lk();return false;}}FLASH->CR=0;lk();return true;}
static bool wr(void*u,uint32_t o,const uint8_t*d,uint16_t n){uint16_t i;(void)u;if((o&1U)||o+n>RS485_BOOT_FLASH_END-RS485_BOOT_APP_ADDRESS)return false;ul();for(i=0;i<n;i+=2){uint16_t v=(uint16_t)d[i]|(uint16_t)(i+1<n?((uint16_t)d[i+1]<<8):0xFF00U);volatile uint16_t*q=(volatile uint16_t*)(RS485_BOOT_APP_ADDRESS+o+i);FLASH->CR=FLASH_CR_PG;*q=v;if(!fw()||*q!=v){FLASH->CR=0;lk();return false;}}FLASH->CR=0;lk();return true;}
static bool rd(void*u,uint32_t o,uint8_t*d,uint16_t n){(void)u;if(o+n>RS485_BOOT_FLASH_END-RS485_BOOT_APP_ADDRESS)return false;memcpy(d,(const void*)(RS485_BOOT_APP_ADDRESS+o),n);return true;}
static bool ok(void*u,uint32_t n,uint32_t c){uint32_t sp=*(const uint32_t*)RS485_BOOT_APP_ADDRESS,rv=*(const uint32_t*)(RS485_BOOT_APP_ADDRESS+4);(void)u;return sp>=0x20000000UL&&sp<=0x20005000UL&&(rv&1U)&&(rv&~1UL)>=RS485_BOOT_APP_ADDRESS&&(rv&~1UL)<RS485_BOOT_FLASH_END&&rs485_boot_crc32((const uint8_t*)RS485_BOOT_APP_ADDRESS,n)==c;}
static void reset(void*u){(void)u;NVIC_SystemReset();}
rs485_boot_config_t rs485_boot_hw_config(void){rs485_boot_config_t c={RS485_BOOT_APP_ADDRESS,RS485_BOOT_FLASH_END-RS485_BOOT_APP_ADDRESS,RS485_BOOT_MAX_BLOCK};return c;}
rs485_boot_port_t rs485_boot_hw_make_port(void){rs485_boot_port_t p={tx,er,wr,rd,ok,reset};return p;}

View File

@@ -0,0 +1,8 @@
#ifndef RS485_BOOT_STM32F103_H
#define RS485_BOOT_STM32F103_H
#include "rs485_boot.h"
void rs485_boot_hw_init(void);
bool rs485_boot_hw_get_byte(uint8_t *byte);
rs485_boot_config_t rs485_boot_hw_config(void);
rs485_boot_port_t rs485_boot_hw_make_port(void);
#endif

View File

@@ -0,0 +1,9 @@
#ifndef RS485_BOOT_PORT_CONFIG_H
#define RS485_BOOT_PORT_CONFIG_H
#define RS485_BOOT_BAUDRATE 512000UL
#define RS485_BOOT_UART_CLOCK_HZ SystemCoreClock
#define RS485_BOOT_APP_ADDRESS 0x08008000UL
#define RS485_BOOT_FLASH_END 0x08080000UL
#define RS485_BOOT_FLASH_PAGE 2048UL
#define RS485_BOOT_DE_PIN 8U /* USART1 PA9/PA10 AF7; DE PA8. */
#endif

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,8 @@
#ifndef RS485_BOOT_STM32G474VET_H
#define RS485_BOOT_STM32G474VET_H
#include "rs485_boot.h"
void rs485_boot_hw_init(void);
bool rs485_boot_hw_get_byte(uint8_t *byte);
rs485_boot_config_t rs485_boot_hw_config(void);
rs485_boot_port_t rs485_boot_hw_make_port(void);
#endif

View File

@@ -0,0 +1,147 @@
#include "rs485_boot.h"
#include <string.h>
#define SOF0 0xA5U
#define SOF1 0x5AU
#define HEADER_SIZE 8U
#define CRC_SIZE 4U
#define MSG_BEGIN 0x0BU
#define MSG_DATA 0x0CU
#define MSG_END 0x0DU
#define MSG_ABORT 0x0EU
#define MSG_STATUS 0x0FU
#define MSG_NACK 0x80U
static uint16_t get_u16(const uint8_t *p) { return (uint16_t)(p[0] | ((uint16_t)p[1] << 8U)); }
static uint32_t get_u32(const uint8_t *p) { return (uint32_t)p[0] | ((uint32_t)p[1] << 8U) | ((uint32_t)p[2] << 16U) | ((uint32_t)p[3] << 24U); }
static void put_u32(uint8_t *p, uint32_t v) { p[0]=(uint8_t)v; p[1]=(uint8_t)(v>>8U); p[2]=(uint8_t)(v>>16U); p[3]=(uint8_t)(v>>24U); }
uint32_t rs485_boot_crc32(const uint8_t *data, size_t length)
{
uint32_t crc = 0xFFFFFFFFUL;
size_t i;
for (i = 0U; i < length; ++i) {
uint8_t bit;
crc ^= data[i];
for (bit = 0U; bit < 8U; ++bit) crc = (crc >> 1U) ^ ((crc & 1U) ? 0xEDB88320UL : 0U);
}
return crc ^ 0xFFFFFFFFUL;
}
void rs485_boot_abort(rs485_boot_t *boot)
{
if (boot == NULL) return;
boot->state = RS485_BOOT_IDLE;
boot->image_size = 0U;
boot->image_crc32 = 0U;
boot->next_offset = 0U;
}
bool rs485_boot_init(rs485_boot_t *boot, const rs485_boot_config_t *config,
const rs485_boot_port_t *port, void *port_user)
{
if ((boot == NULL) || (config == NULL) || (port == NULL) ||
(config->app_size == 0U) || (port->transmit == NULL) ||
(port->erase == NULL) || (port->write == NULL) || (port->read == NULL)) return false;
(void)memset(boot, 0, sizeof(*boot));
boot->config = *config;
boot->port = *port;
boot->port_user = port_user;
if ((boot->config.max_block_size == 0U) || (boot->config.max_block_size > RS485_BOOT_MAX_BLOCK))
boot->config.max_block_size = RS485_BOOT_MAX_BLOCK;
return true;
}
static bool send_frame(rs485_boot_t *b, uint8_t type, uint16_t seq, const uint8_t *payload, uint16_t size)
{
uint32_t crc;
uint16_t i;
const uint16_t total = (uint16_t)(HEADER_SIZE + size + CRC_SIZE);
if (size > RS485_BOOT_MAX_PAYLOAD) return false;
b->tx[0]=SOF0; b->tx[1]=SOF1; b->tx[2]=RS485_BOOT_VERSION; b->tx[3]=type;
b->tx[4]=(uint8_t)(seq>>8U); b->tx[5]=(uint8_t)seq; b->tx[6]=(uint8_t)(size>>8U); b->tx[7]=(uint8_t)size;
for (i=0U; i<size; ++i) b->tx[HEADER_SIZE+i]=payload[i];
crc=rs485_boot_crc32(&b->tx[2], (size_t)6U+size);
put_u32(&b->tx[HEADER_SIZE+size], crc);
return b->port.transmit(b->port_user, b->tx, total);
}
static void nack(rs485_boot_t *b, uint8_t request, uint16_t seq, uint16_t result)
{
uint8_t p[3]={(uint8_t)result,(uint8_t)(result>>8U),request};
(void)send_frame(b, MSG_NACK, seq, p, 3U);
}
static bool matches(rs485_boot_t *b, uint32_t offset, const uint8_t *data, uint16_t size)
{
uint16_t done=0U;
while (done<size) {
uint16_t n=(uint16_t)(size-done); uint16_t i;
if (n>sizeof(b->scratch)) n=sizeof(b->scratch);
if (!b->port.read(b->port_user, offset+done, b->scratch, n)) return false;
for(i=0U;i<n;++i) if(b->scratch[i]!=data[done+i]) return false;
done=(uint16_t)(done+n);
}
return true;
}
static void process(rs485_boot_t *b, uint8_t type, uint16_t seq, const uint8_t *p, uint16_t size)
{
if (type==MSG_BEGIN) {
uint32_t base;
if(size!=16U){nack(b,type,seq,RS485_BOOT_INVALID_LENGTH);return;}
b->image_size=get_u32(p); b->image_crc32=get_u32(&p[4]); base=get_u32(&p[12]);
if((b->image_size==0U)||(b->image_size>b->config.app_size)||((base!=0U)&&(base!=b->config.app_address))){nack(b,type,seq,RS485_BOOT_INVALID_ARGUMENT);return;}
b->next_offset=0U; b->state=RS485_BOOT_RECEIVING;
if(!b->port.erase(b->port_user,b->image_size)){b->state=RS485_BOOT_FAILED;nack(b,type,seq,RS485_BOOT_INTERNAL);return;}
put_u32(b->scratch,0U); (void)send_frame(b,type,seq,b->scratch,4U); return;
}
if(type==MSG_DATA){
uint32_t offset; uint16_t n,expected=0U,i;
if((b->state!=RS485_BOOT_RECEIVING)||(size<RS485_BOOT_DATA_HEADER_SIZE)){nack(b,type,seq,RS485_BOOT_BUSY);return;}
offset=get_u32(p); n=get_u16(&p[4]); for(i=0U;i<n && (uint16_t)(RS485_BOOT_DATA_HEADER_SIZE+i)<size;++i) expected=(uint16_t)(expected+p[RS485_BOOT_DATA_HEADER_SIZE+i]);
if((n==0U)||(n>b->config.max_block_size)||(size!=(uint16_t)(RS485_BOOT_DATA_HEADER_SIZE+n))||((offset&1U)!=0U)||(offset>b->image_size)||(n>b->image_size-offset)){nack(b,type,seq,RS485_BOOT_INVALID_LENGTH);return;}
if(expected!=get_u16(&p[6])){nack(b,type,seq,RS485_BOOT_INVALID_ARGUMENT);return;}
if(offset<b->next_offset){if((offset+n>b->next_offset)||!matches(b,offset,&p[8],n)){nack(b,type,seq,RS485_BOOT_INVALID_ARGUMENT);return;}}
else {if(offset!=b->next_offset){nack(b,type,seq,RS485_BOOT_INVALID_ARGUMENT);return;} if(!b->port.write(b->port_user,offset,&p[8],n)){b->state=RS485_BOOT_FAILED;nack(b,type,seq,RS485_BOOT_INTERNAL);return;} b->next_offset+=n;}
put_u32(b->scratch,b->next_offset); (void)send_frame(b,type,seq,b->scratch,4U); return;
}
if(type==MSG_END){
if((b->state!=RS485_BOOT_RECEIVING)||(size!=8U)||(get_u32(p)!=b->image_size)||(get_u32(&p[4])!=b->image_crc32)||(b->next_offset!=b->image_size)||((b->port.image_valid!=NULL)&&!b->port.image_valid(b->port_user,b->image_size,b->image_crc32))){b->state=RS485_BOOT_FAILED;nack(b,type,seq,RS485_BOOT_INVALID_ARGUMENT);return;}
b->state=RS485_BOOT_READY; (void)send_frame(b,type,seq,NULL,0U); if(b->port.reboot!=NULL)b->port.reboot(b->port_user); return;
}
if(type==MSG_ABORT){rs485_boot_abort(b);(void)send_frame(b,type,seq,NULL,0U);return;}
if(type==MSG_STATUS){b->scratch[0]=(uint8_t)b->state;put_u32(&b->scratch[1],b->next_offset);(void)send_frame(b,type,seq,b->scratch,5U);return;}
nack(b,type,seq,RS485_BOOT_UNSUPPORTED);
}
static void discard(rs485_boot_t *b, uint16_t n)
{
if(n>=b->parser_length){b->parser_length=0U;return;}
b->parser_length=(uint16_t)(b->parser_length-n);
(void)memmove(b->parser,&b->parser[n],b->parser_length);
}
void rs485_boot_feed(rs485_boot_t *b, const uint8_t *data, size_t length)
{
size_t input;
if((b==NULL)||((data==NULL)&&(length!=0U)))return;
for(input=0U;input<length;++input){
if(b->parser_length==RS485_BOOT_MAX_FRAME)b->parser_length=0U;
b->parser[b->parser_length++]=data[input];
for(;;){
uint16_t payload,total,i; uint32_t got;
while((b->parser_length>=2U)&&((b->parser[0]!=SOF0)||(b->parser[1]!=SOF1)))discard(b,1U);
if(b->parser_length<HEADER_SIZE)break;
if(b->parser[2]!=RS485_BOOT_VERSION){discard(b,1U);continue;}
payload=(uint16_t)(((uint16_t)b->parser[6]<<8U)|b->parser[7]);
if(payload>RS485_BOOT_MAX_PAYLOAD){discard(b,1U);continue;}
total=(uint16_t)(HEADER_SIZE+payload+CRC_SIZE); if(b->parser_length<total)break;
got=get_u32(&b->parser[HEADER_SIZE+payload]);
if(got!=rs485_boot_crc32(&b->parser[2],(size_t)6U+payload)){++b->crc_errors;discard(b,1U);continue;}
i=(uint16_t)(((uint16_t)b->parser[4]<<8U)|b->parser[5]);
process(b,b->parser[3],i,&b->parser[HEADER_SIZE],payload); discard(b,total);
}
}
}

File diff suppressed because one or more lines are too long