├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── SConscript ├── _config.yml ├── tfdb_port.c ├── tfdb_port.h ├── tinyflashdb.c └── tinyflashdb.h /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.o 3 | *.crf 4 | *.sct 5 | *.htm 6 | *.hex 7 | *.d 8 | *.uvprojx 9 | *.uvoptx 10 | *.xml 11 | *.xml 12 | *.dep 13 | *.xml 14 | *.lst 15 | *.lnp 16 | *.map 17 | *.mx 18 | *.xml 19 | 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 smartmx - smartmx@qq.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TFDB 2 | 3 | Tiny Flash Database for MCU. 4 | 5 | ## TinyFlashDB设计前言 6 | 7 | 在单片机日常开发中,总会需要存储一些信息,这时就需要使用单片机FLASH存储的方案,目前单片机存储的方案有很多,比如:EASYFLASH、FLASHDB、OSAL_NV等等方案,他们程序都非常大,在存储不多的变量时不值得。而且很少有考虑到flash写入出错的情况。 8 | 在实际产品中,嵌入式产品flash写入可能会受各种因素影响(电池供电、意外断电、气温等)从而并不是很稳定,一旦出现错误,会导致产品一系列问题。 9 | 10 | ## TinyFlashDB设计理念 11 | 12 | 不同于其他很多的KV型数据库,TinyFlashDB每一个需要存储的变量都会分配一个单独的单片机flash扇区,变量长度不可变。 13 | 所以TinyFlashDB仅适用于存储几个关键性变量(例如:IAP跳转标志、系统断电时间等等),不适合大规模数据存储(大规模数据存储可使用EASYFLASH等)。 14 | TinyFlashDB在设计时就考虑了写入错误的影响,追求力所能及的安全保障、资源占用方面尽可能的缩小(不到1kb代码占用)、尽可能的通用性(可以移植到51等8位机,无法逆序写入的stm32L4系列,某些flash加密的单片机和其他普通32位机上)。 15 | 16 | ## TinyFlashDB使用示例 17 | 18 | ```c 19 | const tfdb_index_t test_index = { 20 | .end_byte = 0x00, 21 | .flash_addr = 0x4000, 22 | .flash_size = 256, 23 | .value_length = 2, 24 | };/* c99写法,如果编译器不支持,可自行改为c89写法 */ 25 | tfdb_addr_t addr = 0; /*addr cache*/ 26 | 27 | uint8_t test_buf[TFDB_ALIGNED_RW_BUFFER_SIZE(2,1)]; /*aligned_value_size*/ 28 | 29 | uint16_t test_value; 30 | 31 | void main() 32 | { 33 | TFDB_Err_Code result; 34 | result = tfdb_set(&test_index, test_buf, &addr, &test_value); 35 | if(result == TFDB_NO_ERR) 36 | { 37 | printf("set ok, addr:%x\n", addr); 38 | } 39 | 40 | addr = 0; /* reset addr cache, to see tfdb_get. */ 41 | 42 | result = tfdb_get(&test_index, test_buf, &addr, &test_value); 43 | if(result == TFDB_NO_ERR) 44 | { 45 | printf("get ok, addr:%x, value:%x\n", addr, test_value); 46 | } 47 | } 48 | ``` 49 | 50 | ## TinyFlashDB API介绍 51 | 52 | ```c 53 | typedef struct _tfdb_index_struct{ 54 | tfdb_addr_t flash_addr;/* the start address of the flash block */ 55 | uint16_t flash_size;/* the size of the flash block */ 56 | uint8_t value_length;/* the length of value that saved in this flash block */ 57 | uint8_t end_byte; /* must different to TFDB_VALUE_AFTER_ERASE */ 58 | /* 0x00 is recommended for end_byte, because almost all flash is 0xff after erase. */ 59 | }tfdb_index_t; 60 | ``` 61 | 62 | 结构体功能:在TinyFlashDB中,API的操作都需要指定的参数index,该index结构体中存储了flash的地址,flash的大小,存储的变量的长度,结束标志位。 在读取flash扇区时会去校验此信息。 63 | 64 | ```c 65 | TFDB_Err_Code tfdb_get(const tfdb_index_t *index, uint8_t *rw_buffer, tfdb_addr_t *addr_cache, void* value_to); 66 | ``` 67 | 68 | 函数功能:从`index`指向的扇区中获取一个index中指定变量长度的变量,flash头部数据校验出错不会重新初始化flash。 69 | 70 | 参数 `index`:tfdb操作的index指针。 71 | 72 | 参数 `rw_buffer`:写入和读取的缓存,所有flash的操作最后都会将整理后的数据拷贝到该buffer中,再调用`tfdb_port_write`或者`tfdb_port_read`进行读取写入。当芯片对于写入的数据区缓存有特殊要求(例如4字节对齐,256字节对齐等),可以通过该参数将符合要求的变量指针传递给函数使用。至少为4字节长度。 73 | 74 | 参数 `addr_cache`:可以是`NULL`,或者是地址缓存变量的指针,当`addr_cache`不为`NULL`,并且也不为0时,则认为`addr_cache`已经初始化成功,不再校验flash头部,直接从该`addr_cache`的地址读取数据。 75 | 76 | 参数 `value_to`:要存储数据内容的地址。 77 | 78 | 返回值:`TFDB_NO_ERR`成功,其他失败。 79 | 80 | ```c 81 | TFDB_Err_Code tfdb_set(const tfdb_index_t *index, uint8_t *rw_buffer, tfdb_addr_t *addr_cache, void* value_from); 82 | ``` 83 | 84 | 函数功能:在`index`指向的扇区中写入一个index中指定变量长度的变量,flash头部数据校验出错重新初始化flash。 85 | 86 | 参数 `index`:tfdb操作的index指针。 87 | 88 | 参数 `rw_buffer`:写入和读取的缓存,所有flash的操作最后都会将整理后的数据拷贝到该buffer中,再调用`tfdb_port_write`或者`tfdb_port_read`进行读取写入。当芯片对于写入的数据区缓存有特殊要求(例如4字节对齐,256字节对齐等),可以通过该参数将符合要求的变量指针传递给函数使用。至少为4字节长度。 89 | 90 | 参数 `addr_cache`:可以是`NULL`,或者是地址缓存变量的指针,当`addr_cache`不为`NULL`,并且也不为0时,则认为`addr_cache`已经初始化成功,不再校验flash头部,直接从该`addr_cache`的地址读取数据。 91 | 92 | 参数 `value_from`:要存储的数据内容。 93 | 94 | 返回值:`TFDB_NO_ERR`成功,其他失败。 95 | 96 | ## TinyFlashDB dual使用示例 97 | 98 | tfdb dual api是基于`tfdb_set`和`tfdb_get`封装而成的。`tfdb dual`会调用`tfdb_set`和`tfdb_get`,并且在数据前部添加两个字节的seq,所以在tfdb dual中,最长支持的存储变量长度为253字节。 99 | 同时,tfdb dual api需要提供两个缓冲区,并且需要是增加两字节变量长度再重新计算的`aligned_value_size`。 100 | 101 | ```c 102 | typedef struct _my_test_params_struct 103 | { 104 | uint32_t aa[2]; 105 | uint8_t bb[16]; 106 | } my_test_params_t; 107 | 108 | my_test_params_t my_test_params = { 109 | 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18 110 | }; 111 | 112 | tfdb_dual_index_t my_test_tfdb_dual = { 113 | .indexes[0] = { 114 | .end_byte = 0x00, 115 | .flash_addr = 0x08077000, 116 | .flash_size = 256, 117 | .value_length = TFDB_DUAL_VALUE_LENGTH(sizeof(my_test_params_t)), 118 | }, 119 | .indexes[1] = { 120 | .end_byte = 0x00, 121 | .flash_addr = 0x08077100, 122 | .flash_size = 256, 123 | .value_length = TFDB_DUAL_VALUE_LENGTH(sizeof(my_test_params_t)), 124 | }, 125 | }; 126 | 127 | tfdb_dual_cache_t my_test_tfdb_dual_cache = {0}; 128 | 129 | void my_test_tfdb_dual_func() 130 | { 131 | uint32_t rw_buffer[TFDB_DUAL_ALIGNED_RW_BUFFER_SIZE(TFDB_DUAL_VALUE_LENGTH(sizeof(my_test_params_t)), 4)]; 132 | uint32_t rw_buffer_bak[TFDB_DUAL_ALIGNED_RW_BUFFER_SIZE(TFDB_DUAL_VALUE_LENGTH(sizeof(my_test_params_t)), 4)]; 133 | TFDB_Err_Code err; 134 | for(uint8_t i = 0; i < 36; i++) 135 | { 136 | err = tfdb_dual_get(&my_test_tfdb_dual, (uint8_t *)rw_buffer, (uint8_t *)rw_buffer_bak, &my_test_tfdb_dual_cache, &my_test_params); 137 | if(err == TFDB_NO_ERR) 138 | { 139 | printf("read ok\ncache seq1:0x%04x, seq2:0x%04x\naddr1:0x%08x, addr2:0x%08x\n", my_test_tfdb_dual_cache.seq[0], my_test_tfdb_dual_cache.seq[1], my_test_tfdb_dual_cache.addr_cache[0], my_test_tfdb_dual_cache.addr_cache[1]); 140 | } 141 | else 142 | { 143 | printf("read err:%d\n", err); 144 | } 145 | 146 | my_test_params.aa[0]++; 147 | my_test_params.aa[1]++; 148 | for(uint8_t i = 0; i < 16; i++) 149 | { 150 | my_test_params.bb[i]++; 151 | } 152 | 153 | memset(&my_test_tfdb_dual_cache, 0, sizeof(my_test_tfdb_dual_cache)); /* 测试无地址缓存写入 */ 154 | 155 | err = tfdb_dual_set(&my_test_tfdb_dual, (uint8_t *)rw_buffer, (uint8_t *)rw_buffer_bak, &my_test_tfdb_dual_cache, &my_test_params); 156 | if(err == TFDB_NO_ERR) 157 | { 158 | printf("write ok\ncache seq1:0x%04x, seq2:0x%04x\naddr1:0x%08x, addr2:0x%08x\n", my_test_tfdb_dual_cache.seq[0], my_test_tfdb_dual_cache.seq[1], my_test_tfdb_dual_cache.addr_cache[0], my_test_tfdb_dual_cache.addr_cache[1]); 159 | } 160 | else 161 | { 162 | printf("write err:%d\n", err); 163 | } 164 | 165 | memset(&my_test_tfdb_dual_cache, 0, sizeof(my_test_tfdb_dual_cache)); /* 测试无地址缓存读取 */ 166 | } 167 | } 168 | 169 | ``` 170 | 171 | ## TinyFlashDB dual API介绍 172 | 173 | ```c 174 | typedef struct _tfdb_dual_index_struct 175 | { 176 | tfdb_index_t indexes[2]; 177 | } tfdb_dual_index_t; 178 | 179 | typedef struct _tfdb_dual_cache_struct 180 | { 181 | tfdb_addr_t addr_cache[2]; 182 | uint16_t seq[2]; 183 | } tfdb_dual_cache_t; 184 | ``` 185 | 186 | 结构体功能:在TinyFlashDB dual中,API的操作都需要指定的参数`index`,该`index`结构体中存储了两个`tfdb_index_t`。 187 | 188 | ```c 189 | TFDB_Err_Code tfdb_dual_get(const tfdb_dual_index_t *index, uint8_t *rw_buffer, uint8_t *rw_buffer_bak, tfdb_dual_cache_t *cache, void *value_to); 190 | ``` 191 | 192 | 函数功能:从index指向的扇区中获取一个index中指定变量长度的变量,flash头部数据校验出错不会重新初始化flash。 193 | 194 | 参数 `index`:tfdb操作的index指针。 195 | 196 | 参数 `rw_buffer`:写入和读取的缓存,所有flash的操作最后都会将整理后的数据拷贝到该buffer中,再调用`tfdb_port_write`或者`tfdb_port_read`进行读取写入。当芯片对于写入的数据区缓存有特殊要求(例如4字节对齐,256字节对齐等),可以通过该参数将符合要求的变量指针传递给函数使用。至少为4字节长度。 197 | 198 | 参数 `rw_buffer_bak`:写入和读取的缓存,所有flash的操作最后都会将整理后的数据拷贝到该buffer中,再调用`tfdb_port_write`或者`tfdb_port_read`进行读取写入。当芯片对于写入的数据区缓存有特殊要求(例如4字节对齐,256字节对齐等),可以通过该参数将符合要求的变量指针传递给函数使用。至少为4字节长度。 199 | 200 | 参数 `cache`:不可以是`NULL`,必须是`tfdb_dual_cache_t`定义的缓存的指针,当`cache`中数据合法时,则认为`cache`已经初始化成功,直接从该`cache`的flash块和地址读取数据。 201 | 202 | 参数 `value_to`:要存储数据内容的地址。 203 | 204 | 返回值:`TFDB_NO_ERR`成功,其他失败。 205 | 206 | ```c 207 | TFDB_Err_Code tfdb_dual_set(const tfdb_dual_index_t *index, uint8_t *rw_buffer, uint8_t *rw_buffer_bak, tfdb_dual_cache_t *cache, void *value_from); 208 | ``` 209 | 210 | 函数功能:在index指向的扇区中写入一个index中指定变量长度的变量,flash头部数据校验出错重新初始化flash。 211 | 212 | 参数 `index`:tfdb操作的index指针。 213 | 214 | 参数 `rw_buffer`:写入和读取的缓存,所有flash的操作最后都会将整理后的数据拷贝到该buffer中,再调用`tfdb_port_write`或者`tfdb_port_read`进行读取写入。当芯片对于写入的数据区缓存有特殊要求(例如4字节对齐,256字节对齐等),可以通过该参数将符合要求的变量指针传递给函数使用。至少为4字节长度。 215 | 216 | 参数 `rw_buffer_bak`:写入和读取的缓存,所有flash的操作最后都会将整理后的数据拷贝到该buffer中,再调用`tfdb_port_write`或者`tfdb_port_read`进行读取写入。当芯片对于写入的数据区缓存有特殊要求(例如4字节对齐,256字节对齐等),可以通过该参数将符合要求的变量指针传递给函数使用。至少为4字节长度。 217 | 218 | 参数 `cache`:不可以是`NULL`,必须是`tfdb_dual_cache_t`定义的缓存的指针,当`cache`中数据合法时,则认为`cache`已经初始化成功,直接从该`cache`的flash块和地址读取数据。 219 | 220 | 参数 `value_from`:要存储的数据内容。 221 | 222 | 返回值:`TFDB_NO_ERR`成功,其他失败。 223 | 224 | ## TinyFlashDB设计原理 225 | 226 | 观察上方代码,可以发现TinyFlashDB的操作都需要`tfdb_index_t`定义的`index`参数。 227 | Flash初始化后头部信息为4字节,所以只支持1、2、4、8字节操作的flash: 228 | 头部初始化时会读取头部,所以函数中`rw_buffer`指向的数据第一要求至少为4字节,如果最小写入单位是8字节,则为第一要求最少为8字节。 229 | 230 | |第一字节|第二字节|第三字节|第四字节和其他对齐字节| 231 | -|-|-|- 232 | |flash_size高8位字节|flash_size低8位字节|value_length|end_byte| 233 | 234 | 数据存储时,会根据flash支持的字节操作进行对齐,所以函数中`rw_buffer`指向的数据第二要求至少为下面函数中计算得出的`aligned_value_size`个字节: 235 | 236 | ```c 237 | aligned_value_size = index->value_length + 2;/* data + verify + end_byte */ 238 | 239 | #if (TFDB_WRITE_UNIT_BYTES==2) 240 | /* aligned with TFDB_WRITE_UNIT_BYTES */ 241 | aligned_value_size = ((aligned_value_size + 1) & 0xfe); 242 | #elif (TFDB_WRITE_UNIT_BYTES==4) 243 | /* aligned with TFDB_WRITE_UNIT_BYTES */ 244 | aligned_value_size = ((aligned_value_size + 3) & 0xfc); 245 | #elif (TFDB_WRITE_UNIT_BYTES==8) 246 | /* aligned with TFDB_WRITE_UNIT_BYTES */ 247 | aligned_value_size = ((aligned_value_size + 7) & 0xf8); 248 | #endif 249 | ``` 250 | 251 | |前value_length个字节|第value_length+1字节|第value_length+2字节|其他对齐字节| 252 | -|-|-|- 253 | |value_from数据内容|value_from的和校验|end_byte|end_byte| 254 | 255 | 每次写入后都会再读取出来进行校验,如果校验不通过,就会继续在下一个地址继续尝试写入。直到达到最大写入次数(TFDB_WRITE_MAX_RETRY)或者头部校验错误。 256 | 257 | 读取数据时也会计算和校验,不通过的话继续读取,直到返回校验通过的最新数据,或者读取失败。 258 | 259 | ## TinyFlashDB dual设计原理 260 | 261 | 数据前部两字节seq只有3种合法值,0x00ff->0x0ff0->0xff00。 262 | 如此循环往复,通过读取两个block中最新变量的seq来判断哪个flash扇区中存储的是最新值。 263 | 当最新值存储在第一扇区时,下次写入则会在第二扇区写入,反之亦然。 264 | 265 | ## TinyFlashDB移植和配置 266 | 267 | ### 移植使用只需要在tfdb_port.c中,编写完成三个接口函数,也要在tfdb_port.h中添加相应的头文件和根据不同芯片修改宏定义 268 | 269 | ```c 270 | TFDB_Err_Code tfdb_port_read(tfdb_addr_t addr, uint8_t *buf, size_t size); 271 | 272 | TFDB_Err_Code tfdb_port_erase(tfdb_addr_t addr, size_t size); 273 | 274 | TFDB_Err_Code tfdb_port_write(tfdb_addr_t addr, const uint8_t *buf, size_t size); 275 | ``` 276 | 277 | ### 所有的配置项都在tfdb_port.h中 278 | 279 | ```c 280 | /* use string.h or self functions */ 281 | #define TFDB_USE_STRING_H 1 282 | 283 | #if TFDB_USE_STRING_H 284 | #include "string.h" 285 | #define tfdb_memcpy memcpy 286 | #define tfdb_memcmp memcmp 287 | #define TFDB_MEMCMP_SAME 0 288 | #else 289 | #define tfdb_memcpy 290 | #define tfdb_memcmp 291 | #define TFDB_MEMCMP_SAME 292 | #endif 293 | 294 | #define TFDB_DEBUG printf 295 | 296 | /* The data value in flash after erased, most are 0xff, some flash maybe different. 297 | * if it's over 1 byte, please be care of little endian or big endian. */ 298 | #define TFDB_VALUE_AFTER_ERASE 0xff 299 | 300 | /* The size of TFDB_VALUE_AFTER_ERASE, only support 1 / 2 / 4. 301 | * This value must not bigger than TFDB_WRITE_UNIT_BYTES. */ 302 | #define TFDB_VALUE_AFTER_ERASE_SIZE 1 303 | 304 | /* the flash write granularity, unit: byte 305 | * only support 1(stm32f4)/ 2(CH559)/ 4(stm32f1)/ 8(stm32L4) */ 306 | #define TFDB_WRITE_UNIT_BYTES 8 /* @note you must define it for a value */ 307 | 308 | /* @note the max retry times when flash is error ,set 0 will disable retry count */ 309 | #define TFDB_WRITE_MAX_RETRY 32 310 | 311 | /* must not use pointer type. Please use uint32_t, uint16_t or uint8_t. */ 312 | typedef uint32_t tfdb_addr_t; 313 | ``` 314 | 315 | ## TFDB资源占用 316 | 317 | 在去除DEBUG打印信息后,资源占用如下: 318 | 319 | ### Cortex M4平台 320 | 321 | keil -o2编译优化选项 322 | 323 | ```c 324 | Code (inc. data) RO Data RW Data ZI Data Debug Object Name 325 | 326 | 154 0 0 0 0 2621 tfdb_port.o 327 | 682 0 0 0 0 4595 tinyflashdb.o 328 | ``` 329 | 330 | ### RISC-V平台 331 | 332 | gcc -os编译优化选项 333 | 334 | ```c 335 | .text.tfdb_port_read 336 | 0x00000000000039b4 0x1a ./Drivers/TFDB/tfdb_port.o 337 | 0x00000000000039b4 tfdb_port_read 338 | .text.tfdb_port_erase 339 | 0x00000000000039ce 0x46 ./Drivers/TFDB/tfdb_port.o 340 | 0x00000000000039ce tfdb_port_erase 341 | .text.tfdb_port_write 342 | 0x0000000000003a14 0x5c ./Drivers/TFDB/tfdb_port.o 343 | 0x0000000000003a14 tfdb_port_write 344 | .text.tfdb_check 345 | 0x0000000000003a70 0x56 ./Drivers/TFDB/tinyflashdb.o 346 | 0x0000000000003a70 tfdb_check 347 | .text.tfdb_init 348 | 0x0000000000003ac6 0x56 ./Drivers/TFDB/tinyflashdb.o 349 | 0x0000000000003ac6 tfdb_init 350 | .text.tfdb_set 351 | 0x0000000000003b1c 0x186 ./Drivers/TFDB/tinyflashdb.o 352 | 0x0000000000003b1c tfdb_set 353 | .text.tfdb_get 354 | 0x0000000000003ca2 0x11c ./Drivers/TFDB/tinyflashdb.o 355 | 0x0000000000003ca2 tfdb_get 356 | ``` 357 | 358 | ## Demo 359 | 360 | 裸机移植例程,RT-Thread可以参考使用: 361 | [STM32F429IGT6](https://github.com/smartmx/TFDB/tree/raw/Templates/STM32F429IGT6_TFDB) 362 | [CH583](https://github.com/smartmx/TFDB/tree/raw/Templates/CH583_TFDB) 363 | 364 | ## [博客主页](https://blog.maxiang.vip/) 365 | 366 | QQ交流群:562090553 367 | -------------------------------------------------------------------------------- /SConscript: -------------------------------------------------------------------------------- 1 | from building import * 2 | import rtconfig 3 | 4 | cwd = GetCurrentDir() 5 | 6 | # init src 7 | src = [] 8 | src += Glob('*.c') 9 | 10 | CPPPATH = [cwd] 11 | 12 | group = DefineGroup('tfdb', src, depend = ['PKG_USING_TFDB'], CPPPATH = CPPPATH) 13 | 14 | Return('group') 15 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | title: TFDB 2 | description: Tiny Flash Database for MCU. 3 | show_downloads: true 4 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /tfdb_port.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022-2023, smartmx - smartmx@qq.com 3 | * 4 | * SPDX-License-Identifier: MIT 5 | * 6 | * Change Logs: 7 | * Date Author Notes 8 | * 2022-02-03 smartmx the first version 9 | * 2022-02-08 smartmx fix bugs 10 | * 2022-02-12 smartmx fix bugs, add support for 2 byte write flash 11 | * 2022-03-15 smartmx fix bugs, add support for stm32l4 flash 12 | * 2022-08-02 smartmx add TFDB_VALUE_AFTER_ERASE_SIZE option 13 | * 2023-02-22 smartmx add dual flash index function 14 | * 15 | */ 16 | #include "tfdb_port.h" 17 | 18 | /** 19 | * Read data from flash. 20 | * @note This operation's units is refer to TFDB_WRITE_UNIT_BYTES. 21 | * 22 | * @param addr flash address. 23 | * @param buf buffer to store read data. 24 | * @param size read bytes size. 25 | * 26 | * @return TFDB_Err_Code 27 | */ 28 | TFDB_Err_Code tfdb_port_read(tfdb_addr_t addr, uint8_t *buf, size_t size) 29 | { 30 | TFDB_Err_Code result = TFDB_NO_ERR; 31 | /* You can add your code under here. */ 32 | 33 | return result; 34 | } 35 | 36 | /** 37 | * Erase flash. 38 | * @param addr flash address. 39 | * @param size erase bytes size. 40 | * 41 | * @return TFDB_Err_Code 42 | */ 43 | TFDB_Err_Code tfdb_port_erase(tfdb_addr_t addr, size_t size) 44 | { 45 | TFDB_Err_Code result = TFDB_NO_ERR; 46 | /* You can add your code under here. */ 47 | 48 | return result; 49 | } 50 | 51 | /** 52 | * Write data to flash. 53 | * @note This operation's units is refer to TFDB_WRITE_UNIT_BYTES. 54 | * if you're using some flash like stm32L4xx, please add flash check 55 | * operations before write flash to ensure the write area is erased. 56 | * if the write area is not erased, please just return TFDB_NO_ERR. 57 | * TFDB will check data and retry at next address. 58 | * 59 | * @param addr flash address. 60 | * @param buf the write data buffer. 61 | * @param size write bytes size. 62 | * 63 | * @return result 64 | */ 65 | TFDB_Err_Code tfdb_port_write(tfdb_addr_t addr, const uint8_t *buf, size_t size) 66 | { 67 | TFDB_Err_Code result = TFDB_NO_ERR; 68 | /* You can add your code under here. */ 69 | 70 | return result; 71 | } 72 | 73 | 74 | -------------------------------------------------------------------------------- /tfdb_port.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022-2023, smartmx - smartmx@qq.com 3 | * 4 | * SPDX-License-Identifier: MIT 5 | * 6 | * Change Logs: 7 | * Date Author Notes 8 | * 2022-02-03 smartmx the first version 9 | * 2022-02-08 smartmx fix bugs 10 | * 2022-02-12 smartmx fix bugs, add support for 2 byte write flash 11 | * 2022-03-15 smartmx fix bugs, add support for stm32l4 flash 12 | * 2022-08-02 smartmx add TFDB_VALUE_AFTER_ERASE_SIZE option 13 | * 2023-02-22 smartmx add dual flash index function 14 | * 15 | */ 16 | #ifndef _TFDB_PORT_H_ 17 | #define _TFDB_PORT_H_ 18 | 19 | /* add headers of your chips */ 20 | #include "stdint.h" 21 | 22 | /* tinyflashdb error code */ 23 | typedef enum 24 | { 25 | TFDB_NO_ERR = 0, 26 | TFDB_ERASE_ERR, 27 | TFDB_READ_ERR, 28 | TFDB_WRITE_ERR, 29 | TFDB_HDR_ERR, 30 | TFDB_FLASH_ERR, 31 | TFDB_CACHE_ERR, 32 | TFDB_SEQ_ERR, 33 | TFDB_FLASH1_ERR, 34 | TFDB_FLASH2_ERR, 35 | TFDB_NO_DATA, 36 | TFDB_NO_PRE_DATA, 37 | TFDB_ERR_MAX, 38 | } TFDB_Err_Code; 39 | 40 | /* use string.h or self functions */ 41 | #define TFDB_USE_STRING_H 1 42 | 43 | #if TFDB_USE_STRING_H 44 | #include "string.h" 45 | #define tfdb_memcpy memcpy 46 | #define tfdb_memcmp memcmp 47 | #define TFDB_MEMCMP_SAME 0 48 | #else 49 | #define tfdb_memcpy 50 | #define tfdb_memcmp 51 | #define TFDB_MEMCMP_SAME 52 | #endif 53 | 54 | #define TFDB_DEBUG printf 55 | 56 | #define TFDB_LOG printf 57 | 58 | /* The data value in flash after erased, most are 0xff, some flash maybe different. 59 | * if it's over 1 byte, please be care of little endian or big endian. */ 60 | #define TFDB_VALUE_AFTER_ERASE 0xff 61 | 62 | /* The size of value in flash after erased, only support 1/2/4. 63 | * This value must not bigger than TFDB_WRITE_UNIT_BYTES. */ 64 | #define TFDB_VALUE_AFTER_ERASE_SIZE 1 65 | 66 | /* the flash write granularity, unit: byte 67 | * only support 1(stm32f4)/ 2(CH559)/ 4(stm32f1)/ 8(stm32L4) */ 68 | #define TFDB_WRITE_UNIT_BYTES 4 /* @note you must define it for a value */ 69 | 70 | #if TFDB_VALUE_AFTER_ERASE_SIZE > TFDB_WRITE_UNIT_BYTES 71 | #error "TFDB_VALUE_AFTER_ERASE_SIZE must not bigger than TFDB_WRITE_UNIT_BYTES." 72 | #endif 73 | 74 | /* @note the max retry times when flash is error ,set 0 will disable retry count */ 75 | #define TFDB_WRITE_MAX_RETRY 32 76 | 77 | /* must not use pointer type. Please use uint32_t, uint16_t or uint8_t. */ 78 | typedef uint32_t tfdb_addr_t; 79 | 80 | extern TFDB_Err_Code tfdb_port_read(tfdb_addr_t addr, uint8_t *buf, size_t size); 81 | 82 | extern TFDB_Err_Code tfdb_port_erase(tfdb_addr_t addr, size_t size); 83 | 84 | extern TFDB_Err_Code tfdb_port_write(tfdb_addr_t addr, const uint8_t *buf, size_t size); 85 | 86 | #endif 87 | 88 | -------------------------------------------------------------------------------- /tinyflashdb.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022-2023, smartmx - smartmx@qq.com 3 | * 4 | * SPDX-License-Identifier: MIT 5 | * 6 | * Change Logs: 7 | * Date Author Notes 8 | * 2022-02-03 smartmx the first version 9 | * 2022-02-08 smartmx fix bugs 10 | * 2022-02-12 smartmx fix bugs, add support for 2 byte write flash 11 | * 2022-03-15 smartmx fix bugs, add support for stm32l4 flash 12 | * 2022-08-02 smartmx add TFDB_VALUE_AFTER_ERASE_SIZE option 13 | * 2023-02-22 smartmx add dual flash index function 14 | * 2023-11-07 smartmx fix bugs, tfdb_get error when flash write in tfdb_set not success. 15 | * 16 | */ 17 | #include "tinyflashdb.h" 18 | 19 | /** 20 | * check header in flash. 21 | * 22 | * @param index the data manage index. 23 | * @param rw_buffer buffer to store prepared read data or write data. 24 | * 25 | * @return TFDB_Err_Code 26 | */ 27 | TFDB_Err_Code tfdb_check(const tfdb_index_t *index, uint8_t *rw_buffer) 28 | { 29 | TFDB_Err_Code result; 30 | 31 | TFDB_DEBUG("tfdb_check >\n"); 32 | #if (TFDB_WRITE_UNIT_BYTES==8) 33 | /* flash_size / value_len / end_byte */ 34 | result = tfdb_port_read(index->flash_addr, rw_buffer, 8); 35 | #else 36 | /* flash_size / value_len / end_byte */ 37 | result = tfdb_port_read(index->flash_addr, rw_buffer, 4); 38 | #endif 39 | if (result != TFDB_NO_ERR) 40 | { 41 | //read err 42 | TFDB_DEBUG(" read err\n"); 43 | goto end; 44 | } 45 | result = TFDB_HDR_ERR; 46 | /* compare flash_size */ 47 | if ((rw_buffer[0] == ((index->flash_size >> 8) & 0xff)) && (rw_buffer[1] == ((index->flash_size) & 0xff))) 48 | { 49 | /* compare value_length and end_byte */ 50 | if ((rw_buffer[2] == index->value_length) && (rw_buffer[3] == index->end_byte)) 51 | { 52 | /* check hdr success */ 53 | result = TFDB_NO_ERR; 54 | goto end; 55 | } 56 | } 57 | end: 58 | TFDB_DEBUG("tfdb_check:%d\n", result); 59 | return result; 60 | } 61 | 62 | /** 63 | * erase the flash block and init header in flash. 64 | * 65 | * @param index the data manage index. 66 | * @param rw_buffer buffer to store prepared read data or write data. 67 | * 68 | * @return TFDB_Err_Code 69 | */ 70 | TFDB_Err_Code tfdb_init(const tfdb_index_t *index, uint8_t *rw_buffer) 71 | { 72 | TFDB_Err_Code result = TFDB_NO_ERR; 73 | 74 | TFDB_DEBUG("tfdb_init >\n"); 75 | 76 | result = tfdb_port_erase(index->flash_addr, index->flash_size); 77 | if (result != TFDB_NO_ERR) 78 | { 79 | //erase err 80 | TFDB_DEBUG(" erase err\n"); 81 | goto end; 82 | } 83 | rw_buffer[0] = ((index->flash_size >> 8) & 0xff); 84 | rw_buffer[1] = ((index->flash_size) & 0xff); 85 | rw_buffer[2] = index->value_length; 86 | rw_buffer[3] = index->end_byte; 87 | #if (TFDB_WRITE_UNIT_BYTES==8) 88 | rw_buffer[4] = index->end_byte; 89 | rw_buffer[5] = index->end_byte; 90 | rw_buffer[6] = index->end_byte; 91 | rw_buffer[7] = index->end_byte; 92 | /* flash_size / value_len / end_byte */ 93 | result = tfdb_port_write(index->flash_addr, rw_buffer, 8); 94 | #else 95 | /* flash_size / value_len / end_byte */ 96 | result = tfdb_port_write(index->flash_addr, rw_buffer, 4); 97 | #endif 98 | if (result != TFDB_NO_ERR) 99 | { 100 | //write err 101 | TFDB_DEBUG(" write err\n"); 102 | goto end; 103 | } 104 | result = tfdb_check(index, rw_buffer); 105 | if (result != TFDB_NO_ERR) 106 | { 107 | TFDB_DEBUG(" flash ERR\n"); 108 | result = TFDB_FLASH_ERR; 109 | goto end; 110 | } 111 | end: 112 | TFDB_DEBUG("tfdb_init:%d\n", result); 113 | return result; 114 | } 115 | 116 | /** 117 | * set data in flash and save the addr to addr_cache. 118 | * 119 | * @param index the data manage index. 120 | * @param rw_buffer buffer to store prepared read data or write data. 121 | * @param addr_cache the pointer to addr which is user offered, which will save read addr. 122 | * @param value_from the pointer to buffer which is user offered that need to save. 123 | * 124 | * @return TFDB_Err_Code 125 | */ 126 | TFDB_Err_Code tfdb_set(const tfdb_index_t *index, uint8_t *rw_buffer, tfdb_addr_t *addr_cache, void *value_from) 127 | { 128 | TFDB_Err_Code result; 129 | tfdb_addr_t find_addr; 130 | uint8_t aligned_value_size; 131 | uint8_t sum_verify_byte; 132 | uint8_t i; 133 | #if TFDB_WRITE_MAX_RETRY 134 | uint32_t max_retry = 0; 135 | #endif 136 | 137 | TFDB_DEBUG("tfdb_set >\n"); 138 | 139 | aligned_value_size = index->value_length + 2;/* data + verify + end_byte */ 140 | 141 | #if (TFDB_WRITE_UNIT_BYTES==2) 142 | /* aligned with TFDB_WRITE_UNIT_BYTES */ 143 | aligned_value_size = ((aligned_value_size + 1) & 0xfe); 144 | #elif (TFDB_WRITE_UNIT_BYTES==4) 145 | /* aligned with TFDB_WRITE_UNIT_BYTES */ 146 | aligned_value_size = ((aligned_value_size + 3) & 0xfc); 147 | #elif (TFDB_WRITE_UNIT_BYTES==8) 148 | /* aligned with TFDB_WRITE_UNIT_BYTES */ 149 | aligned_value_size = ((aligned_value_size + 7) & 0xf8); 150 | #endif 151 | TFDB_LOG("aigned size:%d\n", aligned_value_size); 152 | 153 | if (addr_cache == NULL) 154 | { 155 | start: 156 | /* addr_cache is not init. so check header first. */ 157 | find_addr = 0; 158 | result = tfdb_get(index, rw_buffer, &find_addr, NULL); 159 | if(result == TFDB_NO_ERR) 160 | { 161 | find_addr = find_addr + aligned_value_size; 162 | #if (TFDB_WRITE_UNIT_BYTES==8) 163 | if(find_addr > (index->flash_addr + index->flash_size - ((index->flash_size - 8) % aligned_value_size) - aligned_value_size)) 164 | #else 165 | if(find_addr > (index->flash_addr + index->flash_size - ((index->flash_size - 4) % aligned_value_size) - aligned_value_size)) 166 | #endif 167 | { 168 | /* the flash block is fill */ 169 | TFDB_DEBUG(" the flash is fill\n"); 170 | goto init; 171 | } 172 | 173 | /* find the addr success */ 174 | TFDB_LOG(" find success\n"); 175 | set: 176 | /* calculate sum verify */ 177 | sum_verify_byte = 0xff; 178 | for (i = 0; i < index->value_length; i++) 179 | { 180 | sum_verify_byte = ((sum_verify_byte + ((uint8_t *)(value_from))[i]) & 0xff); 181 | } 182 | write: 183 | #if TFDB_WRITE_MAX_RETRY 184 | max_retry++; 185 | if (max_retry > TFDB_WRITE_MAX_RETRY) 186 | { 187 | result = TFDB_FLASH_ERR; 188 | goto end; 189 | } 190 | #endif 191 | tfdb_memcpy(rw_buffer, value_from, index->value_length); 192 | rw_buffer[index->value_length] = sum_verify_byte; 193 | for (i = index->value_length + 1; i < aligned_value_size; i++) 194 | { 195 | /* fill aligned data with end_byte */ 196 | rw_buffer[i] = index->end_byte; 197 | } 198 | result = tfdb_port_write(find_addr, rw_buffer, aligned_value_size); 199 | if (result != TFDB_NO_ERR) 200 | { 201 | TFDB_DEBUG(" write err\n"); 202 | goto end; 203 | } 204 | result = tfdb_port_read(find_addr, rw_buffer, aligned_value_size); 205 | if (result != TFDB_NO_ERR) 206 | { 207 | TFDB_DEBUG(" read err\n"); 208 | goto end; 209 | } 210 | if ((tfdb_memcmp(rw_buffer, value_from, index->value_length) != TFDB_MEMCMP_SAME) \ 211 | || (rw_buffer[index->value_length] != sum_verify_byte)\ 212 | || (rw_buffer[aligned_value_size - 1] != index->end_byte)) 213 | { 214 | /* write verify failed, maybe the flash is error, try next address. */ 215 | TFDB_DEBUG(" Write verify failed, try next address.\n"); 216 | find_addr += aligned_value_size; 217 | 218 | #if (TFDB_WRITE_UNIT_BYTES==8) 219 | if(find_addr > (index->flash_addr + index->flash_size - ((index->flash_size - 8) % aligned_value_size) - aligned_value_size)) 220 | #else 221 | if(find_addr > (index->flash_addr + index->flash_size - ((index->flash_size - 4) % aligned_value_size) - aligned_value_size)) 222 | #endif 223 | { 224 | /* the flash is fill */ 225 | TFDB_DEBUG(" the flash is fill\n"); 226 | goto init; 227 | } 228 | else 229 | { 230 | goto write; 231 | } 232 | } 233 | else 234 | { 235 | /* write data to flash success */ 236 | /* save addr to addr_cache */ 237 | if (addr_cache != NULL) 238 | { 239 | *addr_cache = find_addr; 240 | } 241 | } 242 | } 243 | else if (result == TFDB_HDR_ERR) 244 | { 245 | TFDB_DEBUG(" header err\n"); 246 | init: 247 | result = tfdb_init(index, rw_buffer); 248 | if (result == TFDB_NO_ERR) 249 | { 250 | after_init: 251 | #if (TFDB_WRITE_UNIT_BYTES==8) 252 | find_addr = index->flash_addr + 8; 253 | #else 254 | find_addr = index->flash_addr + 4; 255 | #endif 256 | goto set; 257 | } 258 | goto end; 259 | } 260 | else if (result == TFDB_NO_DATA) 261 | { 262 | goto after_init; 263 | } 264 | } 265 | else 266 | { 267 | if (*addr_cache == 0) 268 | { 269 | /* addr_cache is not set */ 270 | goto start; 271 | } 272 | else 273 | { 274 | /* addr_cache is set */ 275 | TFDB_DEBUG(" addr_cache is set\n"); 276 | find_addr = *addr_cache + aligned_value_size; 277 | if (find_addr > (index->flash_addr + index->flash_size - aligned_value_size)) 278 | { 279 | /* the flash is fill */ 280 | TFDB_DEBUG(" the flash is fill\n"); 281 | goto init; 282 | } 283 | else 284 | { 285 | goto set; 286 | } 287 | } 288 | } 289 | end: 290 | TFDB_LOG("tfdb_set:%d\n", result); 291 | return result; 292 | } 293 | 294 | /** 295 | * get the data in flash and save the addr of data to addr_cache. 296 | * 297 | * @param index the data manage index. 298 | * @param rw_buffer buffer to store prepared read data or write data. 299 | * @param addr_cache the pointer to addr which is user offered. 300 | * @param value_to the pointer to buffer which is user offered to save data. 301 | * 302 | * @return TFDB_Err_Code 303 | */ 304 | TFDB_Err_Code tfdb_get(const tfdb_index_t *index, uint8_t *rw_buffer, tfdb_addr_t *addr_cache, void *value_to) 305 | { 306 | TFDB_Err_Code result; 307 | tfdb_addr_t find_addr; 308 | uint8_t aligned_value_size; 309 | uint8_t sum_verify_byte; 310 | uint8_t i; 311 | TFDB_LOG("tfdb_get >\n"); 312 | 313 | aligned_value_size = index->value_length + 2;/* data + verify + end_byte */ 314 | 315 | #if (TFDB_WRITE_UNIT_BYTES==2) 316 | /* aligned with TFDB_WRITE_UNIT_BYTES */ 317 | aligned_value_size = ((aligned_value_size + 1) & 0xfe); 318 | #elif (TFDB_WRITE_UNIT_BYTES==4) 319 | /* aligned with TFDB_WRITE_UNIT_BYTES */ 320 | aligned_value_size = ((aligned_value_size + 3) & 0xfc); 321 | #elif (TFDB_WRITE_UNIT_BYTES==8) 322 | /* aligned with TFDB_WRITE_UNIT_BYTES */ 323 | aligned_value_size = ((aligned_value_size + 7) & 0xf8); 324 | #endif 325 | TFDB_LOG("aigned size:%d\n", aligned_value_size); 326 | 327 | if (addr_cache == NULL) 328 | { 329 | start: 330 | /* addr_cache is not init. so check header first. */ 331 | result = tfdb_check(index, rw_buffer); 332 | if (result == TFDB_NO_ERR) 333 | { 334 | /* the header is right. so start to find data location address in flash. */ 335 | #if (TFDB_WRITE_UNIT_BYTES==8) 336 | find_addr = index->flash_addr + index->flash_size - ((index->flash_size - 8) % aligned_value_size) - aligned_value_size; 337 | while ((find_addr) >= (index->flash_addr + 8)) 338 | #else 339 | find_addr = index->flash_addr + index->flash_size - ((index->flash_size - 4) % aligned_value_size) - aligned_value_size; 340 | while ((find_addr) >= (index->flash_addr + 4)) 341 | #endif 342 | { 343 | /* start to find value */ 344 | result = tfdb_port_read(find_addr, rw_buffer, aligned_value_size); 345 | if (result != TFDB_NO_ERR) 346 | { 347 | TFDB_DEBUG(" read err\n"); 348 | goto end; 349 | } 350 | 351 | if (rw_buffer[aligned_value_size - 1] == index->end_byte) 352 | { 353 | /* find value addr success */ 354 | break; 355 | } 356 | 357 | if(find_addr >= aligned_value_size) 358 | { 359 | find_addr -= aligned_value_size; 360 | } 361 | else 362 | { 363 | break; 364 | } 365 | } 366 | 367 | verify: 368 | if(rw_buffer[aligned_value_size - 1] != index->end_byte) 369 | { 370 | TFDB_LOG("end_byte err\n"); 371 | goto read_next; 372 | } 373 | 374 | sum_verify_byte = 0xff; 375 | /* calculate sum verify */ 376 | for (i = 0; i < index->value_length; i++) 377 | { 378 | sum_verify_byte = ((sum_verify_byte + rw_buffer[i]) & 0xff); 379 | } 380 | if (sum_verify_byte != rw_buffer[index->value_length]) 381 | { 382 | /* not right data, maybe the flash is broken. */ 383 | TFDB_LOG("verify err:%02x,%02x\n", sum_verify_byte, rw_buffer[index->value_length]); 384 | read_next: 385 | #if (TFDB_WRITE_UNIT_BYTES==8) 386 | if (find_addr >= (index->flash_addr + 8 + aligned_value_size)) 387 | #else 388 | if (find_addr >= (index->flash_addr + 4 + aligned_value_size)) 389 | #endif 390 | { 391 | find_addr = find_addr - aligned_value_size; 392 | result = tfdb_port_read(find_addr, rw_buffer, aligned_value_size); 393 | if (result != TFDB_NO_ERR) 394 | { 395 | TFDB_DEBUG(" read err\n"); 396 | goto end; 397 | } 398 | goto verify; 399 | } 400 | else 401 | { 402 | TFDB_DEBUG(" no data in flash\n"); 403 | result = TFDB_NO_DATA; 404 | goto end; 405 | } 406 | } 407 | else 408 | { 409 | TFDB_DEBUG(" find success\n"); 410 | result = TFDB_NO_ERR; 411 | if(value_to != NULL) 412 | { 413 | tfdb_memcpy(value_to, rw_buffer, index->value_length); 414 | } 415 | if (addr_cache != NULL) 416 | { 417 | *addr_cache = find_addr; 418 | } 419 | } 420 | } 421 | else 422 | { 423 | TFDB_DEBUG(" header err\n"); 424 | result = TFDB_HDR_ERR; 425 | goto end; 426 | } 427 | } 428 | else 429 | { 430 | if (*addr_cache == 0) 431 | { 432 | /* addr_cache is not set */ 433 | goto start; 434 | } 435 | else 436 | { 437 | find_addr = *addr_cache; 438 | result = tfdb_port_read(find_addr, rw_buffer, aligned_value_size); 439 | if (result != TFDB_NO_ERR) 440 | { 441 | TFDB_DEBUG(" read err\n"); 442 | goto end; 443 | } 444 | goto verify; 445 | } 446 | } 447 | end: 448 | TFDB_LOG("tfdb_get:%d\n", result); 449 | return result; 450 | } 451 | 452 | /** 453 | * get the previous data in flash and save the addr of data to addr_cache. 454 | * 455 | * @param index the data manage index. 456 | * @param rw_buffer buffer to store prepared read data or write data. 457 | * @param addr_cache the pointer to addr which is user offered. 458 | * @param value_to the pointer to buffer which is user offered to save data. 459 | * 460 | * @return TFDB_Err_Code 461 | */ 462 | TFDB_Err_Code tfdb_get_pre(const tfdb_index_t *index, uint8_t *rw_buffer, tfdb_addr_t *addr_cache, tfdb_addr_t *pre_addr_cache, void *value_to) 463 | { 464 | TFDB_Err_Code result; 465 | uint8_t aligned_value_size; 466 | tfdb_addr_t find_addr; 467 | 468 | TFDB_LOG("tfdb_get_pre >\n"); 469 | 470 | if(addr_cache == NULL) 471 | { 472 | goto prepare; 473 | } 474 | else 475 | { 476 | if(*addr_cache != 0) 477 | { 478 | find_addr = *addr_cache; 479 | TFDB_LOG("find_addr:%x\n", find_addr); 480 | find: 481 | aligned_value_size = index->value_length + 2; /* data + verify + end_byte */ 482 | 483 | #if (TFDB_WRITE_UNIT_BYTES==2) 484 | /* aligned with TFDB_WRITE_UNIT_BYTES */ 485 | aligned_value_size = ((aligned_value_size + 1) & 0xfe); 486 | #elif (TFDB_WRITE_UNIT_BYTES==4) 487 | /* aligned with TFDB_WRITE_UNIT_BYTES */ 488 | aligned_value_size = ((aligned_value_size + 3) & 0xfc); 489 | #elif (TFDB_WRITE_UNIT_BYTES==8) 490 | /* aligned with TFDB_WRITE_UNIT_BYTES */ 491 | aligned_value_size = ((aligned_value_size + 7) & 0xf8); 492 | #endif 493 | 494 | TFDB_LOG("aigned size:%d\n", aligned_value_size); 495 | 496 | #if (TFDB_WRITE_UNIT_BYTES==8) 497 | if(find_addr >= (index->flash_addr + 8 + aligned_value_size)) 498 | #else 499 | if(find_addr >= (index->flash_addr + 4 + aligned_value_size)) 500 | #endif 501 | { 502 | find_addr = find_addr - aligned_value_size; 503 | result = tfdb_get(index, rw_buffer, &find_addr, value_to); 504 | if(result == TFDB_NO_ERR) 505 | { 506 | if(pre_addr_cache != NULL) 507 | { 508 | *pre_addr_cache = find_addr; 509 | } 510 | } 511 | } 512 | else 513 | { 514 | /* no old data. */ 515 | result = TFDB_NO_PRE_DATA; 516 | } 517 | } 518 | else 519 | { 520 | prepare: 521 | find_addr = 0; 522 | result = tfdb_get(index, rw_buffer, &find_addr, value_to); 523 | if(result != TFDB_NO_ERR) 524 | { 525 | goto end; 526 | } 527 | if(addr_cache != NULL) 528 | { 529 | *addr_cache = find_addr; 530 | } 531 | goto find; 532 | } 533 | } 534 | end: 535 | TFDB_LOG("tfdb_get_pre:%d\n", result); 536 | return result; 537 | } 538 | 539 | /** 540 | * judge which seq is new. 541 | * 542 | * @param seq the pointer to seq[2] buffer. 543 | * 544 | * @return uint8_t which seq is new, 0xff means all seq is illegal. 545 | */ 546 | static uint8_t tfdb_dual_judge(uint16_t *seq) 547 | { 548 | /* seq range: 0x00ff -> 0x0ff0 -> 0xff00 */ 549 | switch (seq[0]) 550 | { 551 | case 0xff00: 552 | if (seq[1] == 0x00ff) 553 | { 554 | return 1; 555 | } 556 | else 557 | { 558 | return 0; 559 | } 560 | case 0x0ff0: 561 | if (seq[1] == 0xff00) 562 | { 563 | return 1; 564 | } 565 | else 566 | { 567 | return 0; 568 | } 569 | case 0x00ff: 570 | if (seq[1] == 0x0ff0) 571 | { 572 | return 1; 573 | } 574 | else 575 | { 576 | return 0; 577 | } 578 | default: 579 | if ((seq[1] == 0x00ff) || (seq[1] == 0x0ff0) || (seq[1] == 0xff00)) 580 | { 581 | return 1; 582 | } 583 | break; 584 | } 585 | return 0xff; 586 | } 587 | 588 | /** 589 | * get next seq from given seq. 590 | * 591 | * @param seq the pointer to seq[2] buffer. 592 | * 593 | * @return uint8_t which seq is new, 0xff means all seq is illegal. 594 | */ 595 | static uint16_t tfdb_dual_get_next_seq(uint16_t seq) 596 | { 597 | /* seq range: 0x00ff -> 0x0ff0 -> 0xff00 */ 598 | if (seq == 0x00ff) 599 | { 600 | return 0x0ff0; 601 | } 602 | else if (seq == 0x0ff0) 603 | { 604 | return 0xff00; 605 | } 606 | else 607 | { 608 | return 0x00ff; 609 | } 610 | } 611 | 612 | /** 613 | * get the data in flash and save the addr and seq to cache. 614 | * 615 | * @param index the data manage index. 616 | * @param rw_buffer buffer to store prepared read data or write data. 617 | * @param rw_buffer_bak buffer to store prepared read data or write data. 618 | * @param cache the pointer to addr which is user offered, which will save read addr and seq. 619 | * @param value_from the pointer to buffer which is user offered that need to save. 620 | * 621 | * @return TFDB_Err_Code 622 | */ 623 | TFDB_Err_Code tfdb_dual_get(const tfdb_dual_index_t *index, uint8_t *rw_buffer, uint8_t *rw_buffer_bak, tfdb_dual_cache_t *cache, void *value_to) 624 | { 625 | TFDB_Err_Code rresult = TFDB_NO_ERR; 626 | TFDB_Err_Code result[2]; 627 | uint8_t judge_state; 628 | 629 | if (cache != NULL) 630 | { 631 | judge_state = tfdb_dual_judge(cache->seq); 632 | 633 | TFDB_DEBUG("tfdb_dual_judge:%d\n", judge_state); 634 | 635 | /* usually, we just read value once during the initializing. */ 636 | if (judge_state == 0xff) 637 | { 638 | result[0] = tfdb_get(&index->indexes[0], rw_buffer, &(cache->addr_cache[0]), rw_buffer_bak); 639 | if (result[0] == TFDB_NO_ERR) 640 | { 641 | cache->seq[0] = (rw_buffer_bak[0] << 8) | (rw_buffer_bak[1]); 642 | tfdb_memcpy(value_to, &(rw_buffer_bak[2]), index->indexes[0].value_length - 2); 643 | } 644 | else 645 | { 646 | cache->seq[0] = 0; 647 | } 648 | 649 | result[1] = tfdb_get(&index->indexes[1], rw_buffer, &(cache->addr_cache[1]), rw_buffer_bak); 650 | if (result[1] == TFDB_NO_ERR) 651 | { 652 | cache->seq[1] = (rw_buffer_bak[0] << 8) | (rw_buffer_bak[1]); 653 | } 654 | else 655 | { 656 | cache->seq[1] = 0; 657 | } 658 | 659 | judge_state = tfdb_dual_judge(cache->seq); 660 | if (judge_state == 1) 661 | { 662 | tfdb_memcpy(value_to, &(rw_buffer_bak[2]), index->indexes[1].value_length - 2); 663 | } 664 | else if (judge_state == 0xff) 665 | { 666 | rresult = TFDB_SEQ_ERR; 667 | } 668 | } 669 | else 670 | { 671 | rresult = tfdb_get(&index->indexes[judge_state], rw_buffer, &(cache->addr_cache[judge_state]), rw_buffer_bak); 672 | if (rresult == TFDB_NO_ERR) 673 | { 674 | tfdb_memcpy(value_to, &(rw_buffer_bak[2]), index->indexes[judge_state].value_length - 2); 675 | } 676 | else 677 | { 678 | /* block not right, don't read another block. */ 679 | } 680 | } 681 | } 682 | else 683 | { 684 | rresult = TFDB_CACHE_ERR; 685 | } 686 | 687 | return rresult; 688 | } 689 | 690 | /** 691 | * set data in flash and save the addr and seq to cache. 692 | * 693 | * @param index the data manage index. 694 | * @param rw_buffer buffer to store prepared read data or write data. 695 | * @param rw_buffer_bak buffer to store prepared read data or write data. 696 | * @param cache the pointer to addr which is user offered, which will save read addr and seq. 697 | * @param value_from the pointer to buffer which is user offered that need to save. 698 | * 699 | * @return TFDB_Err_Code 700 | */ 701 | TFDB_Err_Code tfdb_dual_set(const tfdb_dual_index_t *index, uint8_t *rw_buffer, uint8_t *rw_buffer_bak, tfdb_dual_cache_t *cache, void *value_from) 702 | { 703 | TFDB_Err_Code rresult = TFDB_NO_ERR; 704 | TFDB_Err_Code result[2]; 705 | uint8_t judge_state; 706 | uint16_t write_seq; 707 | 708 | if (cache != NULL) 709 | { 710 | judge_state = tfdb_dual_judge(cache->seq); 711 | 712 | TFDB_DEBUG("tfdb_dual_judge:%d\n", judge_state); 713 | 714 | /* usually, we just read value once during the initializing. */ 715 | if (judge_state != 0xff) 716 | { 717 | write: 718 | write_seq = tfdb_dual_get_next_seq(cache->seq[judge_state]); 719 | judge_state = 1 - judge_state; /* we need to write in another flash block. */ 720 | 721 | rw_buffer_bak[0] = (uint8_t)(write_seq >> 8); 722 | rw_buffer_bak[1] = (uint8_t)write_seq; 723 | 724 | tfdb_memcpy(&(rw_buffer_bak[2]), value_from, index->indexes[judge_state].value_length - 2); 725 | 726 | result[judge_state] = tfdb_set(&index->indexes[judge_state], rw_buffer, &(cache->addr_cache[judge_state]), rw_buffer_bak); 727 | if (result[judge_state] == TFDB_NO_ERR) 728 | { 729 | cache->seq[judge_state] = write_seq; 730 | } 731 | else 732 | { 733 | /* block is error, do not write to another block, for keeping the old data safe. 734 | * if you want to write in another block, please use tfdb_set directly. */ 735 | rresult = judge_state + TFDB_FLASH1_ERR; 736 | } 737 | } 738 | else 739 | { 740 | result[0] = tfdb_get(&index->indexes[0], rw_buffer, &(cache->addr_cache[0]), rw_buffer_bak); 741 | if (result[0] == TFDB_NO_ERR) 742 | { 743 | cache->seq[0] = (rw_buffer_bak[0] << 8) | (rw_buffer_bak[1]); 744 | } 745 | else 746 | { 747 | cache->seq[0] = 0; 748 | } 749 | 750 | result[1] = tfdb_get(&index->indexes[1], rw_buffer, &(cache->addr_cache[1]), rw_buffer_bak); 751 | if (result[1] == TFDB_NO_ERR) 752 | { 753 | cache->seq[1] = (rw_buffer_bak[0] << 8) | (rw_buffer_bak[1]); 754 | } 755 | else 756 | { 757 | cache->seq[1] = 0; 758 | } 759 | 760 | judge_state = tfdb_dual_judge(cache->seq); 761 | if (judge_state == 0xff) 762 | { 763 | judge_state = 0; /* first write 0 block. */ 764 | } 765 | 766 | goto write; 767 | } 768 | } 769 | else 770 | { 771 | return TFDB_CACHE_ERR; 772 | } 773 | 774 | return rresult; 775 | } 776 | -------------------------------------------------------------------------------- /tinyflashdb.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022-2023, smartmx - smartmx@qq.com 3 | * 4 | * SPDX-License-Identifier: MIT 5 | * 6 | * Change Logs: 7 | * Date Author Notes 8 | * 2022-02-03 smartmx the first version 9 | * 2022-02-08 smartmx fix bugs 10 | * 2022-02-12 smartmx fix bugs, add support for 2 byte write flash 11 | * 2022-03-15 smartmx fix bugs, add support for stm32l4 flash 12 | * 2022-08-02 smartmx add TFDB_VALUE_AFTER_ERASE_SIZE option 13 | * 2023-02-22 smartmx add dual flash index function 14 | * 2023-11-07 smartmx add TFDB_LOG macro. 15 | * 16 | */ 17 | #ifndef _TINY_FLASH_DB_H_ 18 | #define _TINY_FLASH_DB_H_ 19 | 20 | #include "tfdb_port.h" 21 | 22 | #define TFDB_VERSION "0.0.7" 23 | 24 | #define TFDB_MAX(A, B) (((A) > (B)) ? (A) : (B)) 25 | 26 | #if TFDB_WRITE_UNIT_BYTES <= 4 27 | 28 | #define TFDB_ALIGNED_RW_BUFFER_SIZE(VALUE_LENGTH, ALIGNED_SIZE) (TFDB_MAX(VALUE_LENGTH + 1 + ALIGNED_SIZE, 4) / (ALIGNED_SIZE)) 29 | #define TFDB_DUAL_ALIGNED_RW_BUFFER_SIZE(VALUE_LENGTH, ALIGNED_SIZE) (TFDB_MAX(VALUE_LENGTH + 3 + ALIGNED_SIZE, 4) / (ALIGNED_SIZE)) 30 | 31 | #else 32 | 33 | #define TFDB_ALIGNED_RW_BUFFER_SIZE(VALUE_LENGTH, ALIGNED_SIZE) (TFDB_MAX(VALUE_LENGTH + 1 + ALIGNED_SIZE, 8) / (ALIGNED_SIZE)) 34 | #define TFDB_DUAL_ALIGNED_RW_BUFFER_SIZE(VALUE_LENGTH, ALIGNED_SIZE) (TFDB_MAX(VALUE_LENGTH + 3 + ALIGNED_SIZE, 8) / (ALIGNED_SIZE)) 35 | 36 | #endif /* TFDB_WRITE_UNIT_BYTES < 8 */ 37 | 38 | #define TFDB_DUAL_VALUE_LENGTH(VALUE_LENGTH) (VALUE_LENGTH + 2) 39 | 40 | typedef struct _tfdb_index_struct 41 | { 42 | tfdb_addr_t flash_addr; /* the start address of the flash block */ 43 | uint16_t flash_size; /* the size of the flash block */ 44 | uint8_t value_length; /* the length of value that saved in this flash block */ 45 | uint8_t end_byte; /* must different to TFDB_VALUE_AFTER_ERASE */ 46 | /* 0x00 is recommended for end_byte, because almost all flash is 0xff after erase. */ 47 | } tfdb_index_t; 48 | 49 | extern TFDB_Err_Code tfdb_get(const tfdb_index_t *index, uint8_t *rw_buffer, tfdb_addr_t *addr_cache, void *value_to); 50 | 51 | extern TFDB_Err_Code tfdb_get_pre(const tfdb_index_t *index, uint8_t *rw_buffer, tfdb_addr_t *addr_cache, tfdb_addr_t *pre_addr_cache, void *value_to); 52 | 53 | extern TFDB_Err_Code tfdb_set(const tfdb_index_t *index, uint8_t *rw_buffer, tfdb_addr_t *addr_cache, void *value_from); 54 | 55 | typedef struct _tfdb_dual_index_struct 56 | { 57 | tfdb_index_t indexes[2]; 58 | } tfdb_dual_index_t; 59 | 60 | typedef struct _tfdb_dual_cache_struct 61 | { 62 | tfdb_addr_t addr_cache[2]; 63 | uint16_t seq[2]; 64 | } tfdb_dual_cache_t; 65 | 66 | extern TFDB_Err_Code tfdb_dual_get(const tfdb_dual_index_t *index, uint8_t *rw_buffer, uint8_t *rw_buffer_bak, tfdb_dual_cache_t *cache, void *value_to); 67 | 68 | extern TFDB_Err_Code tfdb_dual_set(const tfdb_dual_index_t *index, uint8_t *rw_buffer, uint8_t *rw_buffer_bak, tfdb_dual_cache_t *cache, void *value_from); 69 | 70 | #endif 71 | --------------------------------------------------------------------------------