diff --git a/F0-nolib/Socket_fans/flash.c b/F0-nolib/Socket_fans/flash.c new file mode 100644 index 0000000..554d091 --- /dev/null +++ b/F0-nolib/Socket_fans/flash.c @@ -0,0 +1,196 @@ +/* + * This file is part of the SockFans project. + * Copyright 2020 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include "adc.h" +#include "flash.h" +#include "proto.h" // printout +#include // memcpy + +// max amount of Config records stored (will be recalculate in flashstorage_init() +static uint32_t maxCnum = FLASH_BLOCK_SIZE / sizeof(user_conf); + +#define USERCONF_INITIALIZER { \ + .userconf_sz = sizeof(user_conf) \ + ,.Tturnoff = 20000 \ + ,.Thyst = 30 \ + ,.Tmin = {400, 400, 400} \ + ,.Tmax = {900, 800, 800, 800} \ + } + +static int erase_flash(const void*, const void*); +static int write2flash(const void*, const void*, uint32_t); +// don't write `static` here, or get error: +// 'memcpy' forming offset 8 is out of the bounds [0, 4] of object '__varsstart' with type 'uint32_t' +const user_conf *Flash_Data = (const user_conf *)(&__varsstart); + +user_conf the_conf = USERCONF_INITIALIZER; + +static int currentconfidx = -1; // index of current configuration + +/** + * @brief binarySearch - binary search in flash for last non-empty cell + * any struct searched should have its sizeof() @ the first field!!! + * @param l - left index + * @param r - right index (should be @1 less than last index!) + * @param start - starting address + * @param stor_size - size of structure to search + * @return index of non-empty cell or -1 + */ +static int binarySearch(int r, const uint8_t *start, int stor_size){ + int l = 0; + while(r >= l){ + int mid = l + (r - l) / 2; + const uint8_t *s = start + mid * stor_size; + if(*((const uint16_t*)s) == stor_size){ + if(*((const uint16_t*)(s + stor_size)) == 0xffff){ + return mid; + }else{ // element is to the right + l = mid + 1; + } + }else{ // element is to the left + r = mid - 1; + } + } + return -1; // not found +} + +/** + * @brief flashstorage_init - initialization of user conf storage + * run in once @ start + */ +void flashstorage_init(){ + if(FLASH_SIZE > 0 && FLASH_SIZE < 20000){ + uint32_t flsz = FLASH_SIZE * 1024; // size in bytes + flsz -= (uint32_t)(&__varsstart) - FLASH_BASE; + maxCnum = flsz / sizeof(user_conf); +//SEND("flsz="); printu(flsz); +//SEND("\nmaxCnum="); printu(maxCnum); newline(); sendbuf(); + } + // -1 if there's no data at all & flash is clear; maxnum-1 if flash is full + currentconfidx = binarySearch((int)maxCnum-2, (const uint8_t*)Flash_Data, sizeof(user_conf)); + if(currentconfidx > -1){ + memcpy(&the_conf, &Flash_Data[currentconfidx], sizeof(user_conf)); + } +} + +// store new configuration +// @return 0 if all OK +int store_userconf(){ + // maxnum - 3 means that there always should be at least one empty record after last data + // for binarySearch() checking that there's nothing more after it! + if(currentconfidx > (int)maxCnum - 3){ // there's no more place + currentconfidx = 0; + if(erase_flash(Flash_Data, (&__varsstart))) return 1; + }else ++currentconfidx; // take next data position (0 - within first run after firmware flashing) + return write2flash((const void*)&Flash_Data[currentconfidx], &the_conf, sizeof(the_conf)); +} + +static int write2flash(const void *start, const void *wrdata, uint32_t stor_size){ + int ret = 0; + if (FLASH->CR & FLASH_CR_LOCK){ // unloch flash + FLASH->KEYR = FLASH_KEY1; + FLASH->KEYR = FLASH_KEY2; + } + while (FLASH->SR & FLASH_SR_BSY); + if(FLASH->SR & FLASH_SR_WRPRTERR){ + MSG("Can't remove write protection\n"); + return 1; // write protection + } + FLASH->SR = FLASH_SR_EOP | FLASH_SR_PGERR | FLASH_SR_WRPRTERR; // clear all flags + FLASH->CR |= FLASH_CR_PG; + const uint16_t *data = (const uint16_t*) wrdata; + volatile uint16_t *address = (volatile uint16_t*) start; + uint32_t i, count = (stor_size + 1) / 2; + for (i = 0; i < count; ++i){ + IWDG->KR = IWDG_REFRESH; + *(volatile uint16_t*)(address + i) = data[i]; + while (FLASH->SR & FLASH_SR_BSY); + if(FLASH->SR & FLASH_SR_PGERR){ + ret = 1; // program error - meet not 0xffff + MSG("FLASH_SR_PGERR\n"); + break; + }else while (!(FLASH->SR & FLASH_SR_EOP)); + FLASH->SR = FLASH_SR_EOP | FLASH_SR_PGERR | FLASH_SR_WRPRTERR; + } + FLASH->CR |= FLASH_CR_LOCK; // lock it back + FLASH->CR &= ~(FLASH_CR_PG); + MSG("Flash stored\n"); + return ret; +} + +/** + * @brief erase_flash - erase N pages of flash memory + * @param start - first address + * @param end - last address (or NULL if need to erase all flash remaining) + * @return 0 if succeed + */ +static int erase_flash(const void *start, const void *end){ + int ret = 0; + uint32_t nblocks = 1, flsz = 0; + if(!end){ // erase all remaining + if(FLASH_SIZE > 0 && FLASH_SIZE < 20000){ + flsz = FLASH_SIZE * 1024; // size in bytes + flsz -= (uint32_t)start - FLASH_BASE; + } + }else{ // erase a part + flsz = (uint32_t)end - (uint32_t)start; + } + nblocks = flsz / FLASH_BLOCK_SIZE; + if(nblocks == 0 || nblocks >= FLASH_SIZE) return 1; + for(uint32_t i = 0; i < nblocks; ++i){ +#ifdef EBUG + SEND("Try to erase page #"); printu(i); newline(); sendbuf(); +#endif + IWDG->KR = IWDG_REFRESH; + /* (1) Wait till no operation is on going */ + /* (2) Clear error & EOP bits */ + /* (3) Check that the Flash is unlocked */ + /* (4) Perform unlock sequence */ + while ((FLASH->SR & FLASH_SR_BSY) != 0){} /* (1) */ + FLASH->SR = FLASH_SR_EOP | FLASH_SR_PGERR | FLASH_SR_WRPRTERR; /* (2) */ + /* if (FLASH->SR & FLASH_SR_EOP){ + FLASH->SR |= FLASH_SR_EOP; + }*/ + if ((FLASH->CR & FLASH_CR_LOCK) != 0){ /* (3) */ + FLASH->KEYR = FLASH_KEY1; /* (4) */ + FLASH->KEYR = FLASH_KEY2; + } + /* (1) Set the PER bit in the FLASH_CR register to enable page erasing */ + /* (2) Program the FLASH_AR register to select a page to erase */ + /* (3) Set the STRT bit in the FLASH_CR register to start the erasing */ + /* (4) Wait until the EOP flag in the FLASH_SR register set */ + /* (5) Clear EOP flag by software by writing EOP at 1 */ + /* (6) Reset the PER Bit to disable the page erase */ + FLASH->CR |= FLASH_CR_PER; /* (1) */ + FLASH->AR = (uint32_t)Flash_Data + i*FLASH_BLOCK_SIZE; /* (2) */ + FLASH->CR |= FLASH_CR_STRT; /* (3) */ + while(!(FLASH->SR & FLASH_SR_EOP)); + FLASH->SR |= FLASH_SR_EOP; /* (5)*/ + if(FLASH->SR & FLASH_SR_WRPRTERR){ /* Check Write protection error */ + ret = 1; + MSG("Write protection error!\n"); + FLASH->SR |= FLASH_SR_WRPRTERR; /* Clear the flag by software by writing it at 1*/ + break; + } + FLASH->CR &= ~FLASH_CR_PER; /* (6) */ + } + return ret; +} + + diff --git a/F0-nolib/Socket_fans/flash.h b/F0-nolib/Socket_fans/flash.h new file mode 100644 index 0000000..8e7b9b6 --- /dev/null +++ b/F0-nolib/Socket_fans/flash.h @@ -0,0 +1,51 @@ +/* + * This file is part of the SockFans project. + * Copyright 2020 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#ifndef FLASH_H__ +#define FLASH_H__ + +#include "hardware.h" + +#define FLASH_BLOCK_SIZE (1024) +#define FLASH_SIZE_REG ((uint32_t)0x1FFFF7CC) +#define FLASH_SIZE *((uint16_t*)FLASH_SIZE_REG) + +#define TMINNO (3) +#define TMAXNO (4) + +/* + * struct to save user configurations + */ +typedef struct __attribute__((packed, aligned(4))){ + uint16_t userconf_sz; // "magick number" + uint32_t Tturnoff; // wait for Tturnoff ms before turning power off + uint32_t Thyst; // Thysteresis + int16_t Tmin[TMINNO]; // minT + int16_t Tmax[TMAXNO]; // maxT +} user_conf; + +extern user_conf the_conf; // global user config (read from FLASH to RAM) +// data from ld-file: start address of storage +extern const uint32_t __varsstart; + +void flashstorage_init(); +int store_userconf(); +void dump_userconf(); + +#endif // FLASH_H__ diff --git a/F0-nolib/Socket_fans/main.c b/F0-nolib/Socket_fans/main.c index e7db15a..5838a34 100644 --- a/F0-nolib/Socket_fans/main.c +++ b/F0-nolib/Socket_fans/main.c @@ -19,6 +19,7 @@ * MA 02110-1301, USA. */ +#include "flash.h" #include "hardware.h" #include "monitor.h" #include "proto.h" @@ -80,6 +81,7 @@ int main(void){ char *txt; sysreset(); SysTick_Config(6000, 1); + flashstorage_init(); HW_setup(); USB_setup(); RCC->CSR |= RCC_CSR_RMVF; // remove reset flags diff --git a/F0-nolib/Socket_fans/monitor.c b/F0-nolib/Socket_fans/monitor.c index 100a949..c94e28d 100644 --- a/F0-nolib/Socket_fans/monitor.c +++ b/F0-nolib/Socket_fans/monitor.c @@ -17,32 +17,25 @@ */ #include "adc.h" +#include "flash.h" #include "monitor.h" #include "proto.h" // when critical T reached wait for TturnOff ms and after that turn off system -#define TturnOff (20000) +#define TturnOff the_conf.Tturnoff // don't mind when button 2 pressed again after t<5s #define TbtnPressed (5000) // settings // T0 - CPU, T1 - HDD, T2 - inner T, T3 - power source -static const int16_t Thysteresis = 30; // hysteresis by T=3degC -static const int16_t tmin[3] = {400, 350, 350}; // turn off fans when T[x]tmin+Th -static const int16_t tmax[3] = {900, 800, 600}; // critical T, turn off power after TturnOff milliseconds -static const int16_t t3max = 850; +#define Thysteresis (int16_t)the_conf.Thyst +#define tmin the_conf.Tmin +#define tmax the_conf.Tmax +#define t3max the_conf.Tmax[TMAXNO-1] static uint8_t dontprocess = 0; // don't process monitor static uint32_t TOff = 0; // time to turn off power -// show hardcoded settings -void showSettings(){ - SEND("Thysteresis=30\n"); - SEND("Tmin={400, 350, 350}\n"); - SEND("Tmax={900, 800, 600}\n"); - SEND("T3max=850"); -} - static void chkOffRelay(){ static uint32_t scntr = 0; if(!TOff){ // warning cleared diff --git a/F0-nolib/Socket_fans/monitor.h b/F0-nolib/Socket_fans/monitor.h index cefeb0b..95258fd 100644 --- a/F0-nolib/Socket_fans/monitor.h +++ b/F0-nolib/Socket_fans/monitor.h @@ -25,7 +25,6 @@ #define MONITOR_PERIOD (999) void process_monitor(); -void showSettings(); void SetDontProcess(uint8_t newstate); uint8_t GetDontProcess(); #define MONITOR_H__ diff --git a/F0-nolib/Socket_fans/proto.c b/F0-nolib/Socket_fans/proto.c index 7866598..317701e 100644 --- a/F0-nolib/Socket_fans/proto.c +++ b/F0-nolib/Socket_fans/proto.c @@ -21,6 +21,7 @@ * */ #include "adc.h" +#include "flash.h" #include "hardware.h" #include "monitor.h" #include "proto.h" @@ -306,6 +307,104 @@ void showState(){ NL(); } +static uint8_t userconf_changed = 0; // ==1 if user_conf was changed +static const char *nshould = "N should be from 0 to "; +static const char *changed = "Changed OK\n"; +static const char *notchanged = "Not changed: the same value\n"; +static inline void setArrval(int16_t *arr, uint8_t maxelem, char *params){ + uint32_t N, val; + int16_t sign = 1; + char *next = getnum(params, &N); + if(N > maxelem){ + SEND(nshould); + printu(maxelem); newline(); + return; + } + next = omit_spaces(next); + if(*next == '-'){ + next = omit_spaces(next+1); + sign = -1; + } + char *rest = getnum(next, &val); + if(*rest && *rest != '\n'){ + SEND("Arguments are: N T, where N is channel number, T - temperature\n"); + return; + } + sign *= (int16_t)val; + if(arr[N] != sign){ + arr[N] = sign; + userconf_changed = 1; + SEND(changed); + }else SEND(notchanged); +} +static inline void setval(uint32_t *var, char *params){ + uint32_t val; + char *next = getnum(params, &val); + if(*next && *next != '\n'){ + SEND("Argument is 32-bit number\n"); + return; + } + if(*var != val){ + *var = val; + userconf_changed = 1; + SEND(changed); + }else SEND(notchanged); +} +static inline void showSettings(){ + int i; + SEND("Tturnoff="); printu(the_conf.Tturnoff); newline(); + SEND("Thysteresis="); printu(the_conf.Thyst); newline(); + SEND("Tmin={"); + for(i = 0; i < TMINNO; ++i){ + if(i) SEND(", "); + printu(the_conf.Tmin[i]); + } + SEND("}\n"); + SEND("Tmax={"); + for(i = 0; i < TMAXNO; ++i){ + if(i) SEND(", "); + printu(the_conf.Tmax[i]); + } + SEND("}\n"); +} +static inline void setters(char *txt){ // setters + txt = omit_spaces(txt); + if(!*txt){ + SEND("Setters need more arguments"); + return; + } + char *next = omit_spaces(txt+1); + switch(*txt){ + case '<': // Tmin + setArrval(the_conf.Tmin, TMINNO-1, next); + break; + case '>': // Tmax + setArrval(the_conf.Tmax, TMAXNO-1, next); + break; + case 'H': // Thyst + setval(&the_conf.Thyst, next); + break; + case 'O': // Tturnoff + setval(&the_conf.Tturnoff, next); + break; + case 'S': // save settings + if(userconf_changed){ + if(!store_userconf()){ + userconf_changed = 0; + SEND("Stored!"); + }else SEND("Error when storing!"); + } + break; + default: + SEND("Setters:\n" + "< N T - set nth (0..2) Tmin (T/10degrC)\n" + "> N T - set Tmax\n" + "H T - set temperature hysteresis (T/10degrC)\n" + "O t - time to turn off after emergency (t in ms)\n" + "S - save settings\n"); + } +} + /** * @brief cmd_parser - command parsing * @param txt - buffer with commands & data @@ -341,14 +440,17 @@ void cmd_parser(char *txt){ getPWM(txt[1]); goto eof; break; + case 'P': // set PWM + changePWM(txt+1); + goto eof; + break; case 'r': // relay: set/clear/check onoff(txt[1], RELAY_port, RELAY_pin, "RELAY"); goto eof; break; - case 'S': // set PWM - changePWM(txt+1); + case 'S': // setters + setters(&txt[1]); goto eof; - break; case 't': gett(txt[1]); goto eof; @@ -422,9 +524,10 @@ void cmd_parser(char *txt){ "'Gx' - get cooler x (0..3) PWM settings\n" "'M' - get MCU temperature\n" "'m' - toggle monitoring\n" + "'Px y' - set coolerx PWM to y\n" "'R' - software reset\n" "'rx' - relay on/off (x=1/0) or get status\n" - "'Sx y' - set coolerx PWM to y\n" + "'S' - setters\n" "'s' - show settings\n" "'T' - get time from start (ms)\n" "'tx' - get temperature x (0..3)\n" diff --git a/F0-nolib/Socket_fans/sockfans.bin b/F0-nolib/Socket_fans/sockfans.bin index 71869a7..d1b8b86 100755 Binary files a/F0-nolib/Socket_fans/sockfans.bin and b/F0-nolib/Socket_fans/sockfans.bin differ