вот код, кому интересно. если хотите проверить то вместо таймера используйте свои пальцы 
// удаленная прошивка через UART для STM32F103
// для ясности из кода у все лишнее
// таймер подключается к средней клемме на джампере BOOT в смыле нажате этой кнопки оначает подачу высокого уровня на сренюю клемму
// таймер https://www.aliexpress.com/item/1005004642280389.html?spm=a2g0o.order_list.order_list_main.107.f7391802xHxNo1
#include "SdFat.h"
#define SD_CS PA4
#define SD_MISO PA6
#define SD_SCK PA5
#define SD_MOSI PA7
#define EX_TIMER PB5
#define SPI_CLOCK SD_SCK_MHZ(50)
#define SD_CONFIG SdSpiConfig(PA4, SHARED_SPI, SPI_CLOCK)
#define PIN_SERIAL1_RX PA10
#define PIN_SERIAL1_TX PA9
//rs-485
HardwareSerial Serial2(PIN_SERIAL1_RX, PIN_SERIAL1_TX);
//SD
SdFs sd;
FsFile file;
//terminal control
int SessoinState = 0;
int SessionCntr = 0;
char serInString[300];
int serInIndx = 0;
//logger control
int LogCntr = 0;
//-----------------------------------------------------
void setup() {
pinMode(EX_TIMER, OUTPUT);
digitalWrite(EX_TIMER, LOW);
Serial2.begin(9600);
Serial2.println("PowerMonitor1#1$2&4"); // "PowerMonitor1#1$2&4" -эта строка выполняет роль индентификатора в файле прошики
// в смысле загрузчик ищет эту строку в файле прошивки и если на находит то ругается что файл не найден
if (!sd.begin(SD_CONFIG)) {
sd.initErrorHalt(&Serial2);
}
}
//-----------------------------------------------------
void loop() {
Serial_Server();
if ( SessoinState == 0) { // SessoinState - смысл этой блокировки в том чтобы логи не мешали вам общатся с сервером
LogCntr=LogCntr+1;
if ( LogCntr >= 30000000*5 ){ // число циклов это значение только для тестирования в реале лог записывается по таймеру каждые 5 минут
WirteLogStr("Test log string");
LogCntr=0;
}
}
}
//-----------------------------------------------------
// эта фукция имеет смыл только для тестирования в реале лог создается из данных АЦП
// выводится на ЛСД пишется в файл и в серийный порт
void WirteLogStr (char * str){
if (!file.open("log_2023.csv", FILE_WRITE)) {
Serial2.println("open failed");
}else{
file.seekEnd();
file.write(str);
file.write("\n\r");
file.close();
Serial2.println(str);
}
}
//-----------------------------------------------------
// для того чтобы небыло тормозов в комуникации не тормозите основной цикл функциями задержки используйте счечики циклов
void Serial_Server(){
int flgno = 0;
char sFilNm[50];
if (Serial2.available()) {
serInString[serInIndx] = Serial2.read();
if (serInString[serInIndx-1] == 0xd && serInString[serInIndx] == 0xa){
serInString[serInIndx+1]=0;
Serial2.print(serInString);
flgno = 0;
//===================
if (strstr(serInString, "version") != 0 ){
Serial2.println("-------------------------------\n"
" MCPwMon v 1.0.0 \n"
" Micro Terminal version 1.0 \n"
"-------------------------------\n");
flgno = 1;
}
if (strstr(serInString, "updat") != 0 ){
delay(100);
digitalWrite(EX_TIMER, HIGH);
NVIC_SystemReset();
__WFI();
}
//===================
if (strstr(serInString, "-h") != 0 ){
Serial2.println(" MAIN MENU \n"
" version { Version}\n"
" mkdir name:xxx { Create a new folder}\n"
" rmdir name:xxx { Remove folder}\n"
" chdir name:xxx { Change current directory}\n"
" fremove name:xxx { Remove any existing file}\n"
" fcreate name:xxx { Create the file}\n"
" ls name:xxx { List directory contents}\n"
" getfile name:xxx { sends file contents to console}\n"
" -h { main menu }");
flgno = 1;
}
//===================
if (strstr(serInString, "mkdir") != 0 ){
GetParam(serInString, "name:", sFilNm);
if (!sd.mkdir(sFilNm)){
Serial2.println("mkdir failed");
}else{
Serial2.println("mkdir is complete.");
}
flgno = 1;
}
//===================
if (strstr(serInString, "rmdir") != 0 ){
GetParam(serInString, "name:", sFilNm);
if (!sd.rmdir(sFilNm)) {
Serial2.println("rmdir failed");
}else{
Serial2.println("rmdir is complete.");
}
flgno = 1;
}
//===================
if (strstr(serInString, "ls") != 0 ){
GetParam(serInString, "name:", sFilNm);
if (!sd.ls(&Serial2, sFilNm, LS_R | LS_SIZE | LS_DATE)) {
Serial2.println("ls failed");
}
flgno = 1;
}
//===================
if (strstr(serInString, "chdir") != 0 ){
GetParam(serInString, "name:", sFilNm);
if (!sd.chdir(sFilNm)) {
Serial2.println("chdir failed");
}else{
Serial2.println("chdir is complete.");
}
flgno = 1;
}
//===================
if (strstr(serInString, "fremove") != 0 ){
GetParam(serInString, "name:", sFilNm);
if (sd.exists(sFilNm)) {
sd.remove(sFilNm);
}else{
Serial2.println("file not exists.");
}
flgno = 1;
}
//===================
if (strstr(serInString, "fcreate") != 0 ){
GetParam(serInString, "name:", sFilNm);
if (!file.open(sFilNm, FILE_WRITE)) {
Serial2.println("create file failed");
}else{
file.close();
Serial2.println("fcreate is complete.");
}
flgno = 1;
}
//===================
if (strstr(serInString, "getfile") != 0 ){
GetParam(serInString, "name:", sFilNm);
if (sd.exists(sFilNm)) {
sReadFile(sFilNm);
}else{
Serial2.println("file not exists.");
}
flgno = 1;
}
//===================
if (flgno == 0 ){
Serial2.println("uncnown command");
}
//===================
serInIndx = -1;
SessoinState = 1;
SessionCntr = 0;
}
serInIndx++;
}
SessionCntr = SessionCntr+1;
if (SessionCntr > 30000000 && SessoinState == 1){ // значение для счетчика нужно устанавливать в зависимости от загруженности контролера
SessoinState = 0; // в реале конролер оборабатывает данные АЦП выводит из на ЛСД и записывает в лог файл
Serial2.println("session end"); // все это занимает время
}
}
//-----------------------------------------------------
void GetParam (char *cmdl, char *tok, char *rstr ){
char sFilNm[50];
char *pnam = strstr(cmdl, tok);
pnam = pnam + strlen(tok);
int cwe = strlen(pnam);
memset(sFilNm,0, 50);
memcpy(sFilNm,(char *) pnam, cwe);
int i = 0;
while(i <= cwe){
if (sFilNm[i] == 0x20 ){
sFilNm[i]=0;
}else if (sFilNm[i] == 0x0d){
sFilNm[i]=0;
}else if (sFilNm[i] == 0x0a){
sFilNm[i]=0;
}
i=i+1;
}
strcpy(rstr,sFilNm);
}
//-----------------------------------------------------
void sReadFile (char *Filnam ){
char sBuj[110];
file.open(Filnam, FILE_READ);
int fsize = file.fileSize();
int del = fsize % 100;
int fipon = fsize / 100;
int pos = 0;
while ( fipon != 0 ){
file.seekSet(pos);
file.read(sBuj, 100);
Serial2.write(sBuj, 100);
pos = pos + 100;
fipon = fipon - 1;
}
if (del != 0){
file.read(sBuj, del);
Serial2.print(sBuj);
}
file.close();
}
все в одном флаконе тут terminal.7z - Google Drive