diff --git a/README.md b/README.md index 49e837c..fc6bdbe 100644 --- a/README.md +++ b/README.md @@ -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 в комплекте** | diff --git a/c/rs485-boot/CMakeLists.txt b/c/rs485-boot/CMakeLists.txt new file mode 100644 index 0000000..714d420 --- /dev/null +++ b/c/rs485-boot/CMakeLists.txt @@ -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() diff --git a/c/rs485-boot/README.md b/c/rs485-boot/README.md new file mode 100644 index 0000000..3ddab30 --- /dev/null +++ b/c/rs485-boot/README.md @@ -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. diff --git a/c/rs485-boot/include/rs485_boot.h b/c/rs485-boot/include/rs485_boot.h new file mode 100644 index 0000000..c4072c4 --- /dev/null +++ b/c/rs485-boot/include/rs485_boot.h @@ -0,0 +1,73 @@ +#ifndef RS485_BOOT_H +#define RS485_BOOT_H + +#include +#include +#include + +#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 diff --git a/c/rs485-boot/ports/stm32f103/rs485_boot_port_config.template.h b/c/rs485-boot/ports/stm32f103/rs485_boot_port_config.template.h new file mode 100644 index 0000000..e416160 --- /dev/null +++ b/c/rs485-boot/ports/stm32f103/rs485_boot_port_config.template.h @@ -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 diff --git a/c/rs485-boot/ports/stm32f103/rs485_boot_stm32f103.c b/c/rs485-boot/ports/stm32f103/rs485_boot_stm32f103.c new file mode 100644 index 0000000..942f4f0 --- /dev/null +++ b/c/rs485-boot/ports/stm32f103/rs485_boot_stm32f103.c @@ -0,0 +1,17 @@ +#include "rs485_boot_stm32f103.h" +#include "rs485_boot_port_config.h" +#include "stm32f10x.h" +#include +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<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<SR&USART_SR_TXE)){}USART1->DR=d[i];}while(!(USART1->SR&USART_SR_TC)){}GPIOA->BRR=1UL<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;aCR=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;iCR=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) +static bool fw(void){while(FLASH->SR&FLASH_SR_BSY){}if(FLASH->SR&(FLASH_SR_OPERR|FLASH_SR_PROGERR|FLASH_SR_WRPERR|FLASH_SR_PGAERR|FLASH_SR_SIZERR|FLASH_SR_PGSERR)){FLASH->SR=FLASH_SR_OPERR|FLASH_SR_PROGERR|FLASH_SR_WRPERR|FLASH_SR_PGAERR|FLASH_SR_SIZERR|FLASH_SR_PGSERR;return false;}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){RCC->AHB2ENR|=RCC_AHB2ENR_GPIOAEN;RCC->APB2ENR|=RCC_APB2ENR_USART1EN;GPIOA->MODER=(GPIOA->MODER&~((3UL<<16)|(3UL<<18)|(3UL<<20)))|(1UL<<16)|(2UL<<18)|(2UL<<20);GPIOA->AFR[1]=(GPIOA->AFR[1]&~((0xFUL<<4)|(0xFUL<<8)))|(7UL<<4)|(7UL<<8);GPIOA->BSRR=1UL<<(RS485_BOOT_DE_PIN+16U);USART1->BRR=(RS485_BOOT_UART_CLOCK_HZ+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->ISR;if(s&(USART_ISR_ORE|USART_ISR_FE|USART_ISR_NE)){USART1->ICR=USART_ICR_ORECF|USART_ICR_FECF|USART_ICR_NECF;return false;}if(!(s&USART_ISR_RXNE_RXFNE))return false;*b=(uint8_t)USART1->RDR;return true;} +static bool tx(void*u,const uint8_t*d,uint16_t n){uint16_t i;(void)u;GPIOA->BSRR=1UL<ISR&USART_ISR_TXE_TXFNF)){}USART1->TDR=d[i];}while(!(USART1->ISR&USART_ISR_TC)){}USART1->ICR=USART_ICR_TCCF;GPIOA->BSRR=1UL<<(RS485_BOOT_DE_PIN+16U);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;aCR=(FLASH->CR&~(FLASH_CR_PNB_Msk|FLASH_CR_BKER))|FLASH_CR_PER|(pg<CR|=FLASH_CR_STRT;if(!fw()){lk();return false;}FLASH->CR&=~FLASH_CR_PER;}lk();return true;} +static bool wr(void*u,uint32_t o,const uint8_t*d,uint16_t n){uint16_t z=0;(void)u;if((o&7U)||o+n>RS485_BOOT_FLASH_END-RS485_BOOT_APP_ADDRESS)return false;ul();while(z8)take=8;memcpy(&v,&d[z],take);FLASH->CR|=FLASH_CR_PG;*(volatile uint32_t*)(RS485_BOOT_APP_ADDRESS+o+z)=(uint32_t)v;*(volatile uint32_t*)(RS485_BOOT_APP_ADDRESS+o+z+4)=(uint32_t)(v>>32);if(!fw()){FLASH->CR&=~FLASH_CR_PG;lk();return false;}FLASH->CR&=~FLASH_CR_PG;z=(uint16_t)(z+take);}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<0x20020000UL&&(rv&1U)&&(rv&~1UL)>=RS485_BOOT_APP_ADDRESS&&(rv&~1UL) + +#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; itx[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 (donesizeof(b->scratch)) n=sizeof(b->scratch); + if (!b->port.read(b->port_user, offset+done, b->scratch, n)) return false; + for(i=0U;iscratch[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)||(sizeb->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(offsetnext_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;inputparser_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_lengthparser[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_lengthparser[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); + } + } +} diff --git a/c/rs485-boot/tests/test_rs485_boot.c b/c/rs485-boot/tests/test_rs485_boot.c new file mode 100644 index 0000000..c4ec1bb --- /dev/null +++ b/c/rs485-boot/tests/test_rs485_boot.c @@ -0,0 +1,13 @@ +#include "rs485_boot.h" +#include +#include + +static uint8_t flash_mem[256], reply[600]; static uint16_t reply_len; +static bool tx(void*u,const uint8_t*d,uint16_t n){(void)u;memcpy(reply,d,n);reply_len=n;return true;} +static bool erase(void*u,uint32_t n){(void)u;(void)n;memset(flash_mem,0xff,sizeof(flash_mem));return true;} +static bool write_cb(void*u,uint32_t o,const uint8_t*d,uint16_t n){(void)u;memcpy(&flash_mem[o],d,n);return true;} +static bool read_cb(void*u,uint32_t o,uint8_t*d,uint16_t n){(void)u;memcpy(d,&flash_mem[o],n);return true;} +static bool valid(void*u,uint32_t n,uint32_t crc){(void)u;return rs485_boot_crc32(flash_mem,n)==crc;} +static uint16_t frame(uint8_t type,uint16_t seq,const uint8_t*p,uint16_t n,uint8_t*out){uint32_t c;uint16_t i;out[0]=0xa5;out[1]=0x5a;out[2]=1;out[3]=type;out[4]=(uint8_t)(seq>>8);out[5]=(uint8_t)seq;out[6]=(uint8_t)(n>>8);out[7]=(uint8_t)n;for(i=0;i>8);out[10+n]=(uint8_t)(c>>16);out[11+n]=(uint8_t)(c>>24);return(uint16_t)(12+n);} +static void p32(uint8_t*p,uint32_t v){p[0]=(uint8_t)v;p[1]=(uint8_t)(v>>8);p[2]=(uint8_t)(v>>16);p[3]=(uint8_t)(v>>24);} +int main(void){rs485_boot_t b;rs485_boot_config_t c={0x08003000,256,64};rs485_boot_port_t p={tx,erase,write_cb,read_cb,valid,0};uint8_t f[600],x[32]={0};uint8_t image[4]={1,2,3,4};uint32_t crc=rs485_boot_crc32(image,4);uint16_t n;assert(rs485_boot_init(&b,&c,&p,0));p32(x,4);p32(&x[4],crc);p32(&x[12],c.app_address);n=frame(0x0b,1,x,16,f);rs485_boot_feed(&b,f,n);assert(b.state==RS485_BOOT_RECEIVING&&reply_len==16);p32(x,0);x[4]=4;x[5]=0;x[6]=10;x[7]=0;memcpy(&x[8],image,4);n=frame(0x0c,2,x,12,f);rs485_boot_feed(&b,f,n);assert(b.next_offset==4&&memcmp(flash_mem,image,4)==0);p32(x,4);p32(&x[4],crc);n=frame(0x0d,3,x,8,f);rs485_boot_feed(&b,f,n);assert(b.state==RS485_BOOT_READY);return 0;}