sd-card-test.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658
  1. #include "app-template.h"
  2. #include "stm32_adafruit_sd.h"
  3. #include "fnv1a-hash.h"
  4. #include "filesystem-api.h"
  5. // event enumeration type
  6. typedef uint8_t event_t;
  7. class SdTestState {
  8. public:
  9. // state data
  10. static const uint8_t lines_count = 6;
  11. const char* line[lines_count];
  12. // state initializer
  13. SdTestState() {
  14. for(uint8_t i = 0; i < lines_count; i++) {
  15. line[i] = "";
  16. }
  17. }
  18. };
  19. // events class
  20. class SdTestEvent {
  21. public:
  22. // events enum
  23. static const event_t EventTypeTick = 0;
  24. static const event_t EventTypeKey = 1;
  25. // payload
  26. union {
  27. InputEvent input;
  28. } value;
  29. // event type
  30. event_t type;
  31. };
  32. // our app derived from base AppTemplate class
  33. // with template variables <state, events>
  34. class SdTest : public AppTemplate<SdTestState, SdTestEvent> {
  35. public:
  36. // vars
  37. GpioPin* red_led_record;
  38. GpioPin* green_led_record;
  39. const uint32_t benchmark_data_size = 4096;
  40. uint8_t* benchmark_data;
  41. FS_Api* fs_api;
  42. // funcs
  43. void run();
  44. void render(Canvas* canvas);
  45. template <class T> void set_text(std::initializer_list<T> list);
  46. template <class T> void set_error(std::initializer_list<T> list);
  47. void wait_for_button(Input input_button);
  48. bool ask(Input input_button_cancel, Input input_button_ok);
  49. void blink_red();
  50. void set_red();
  51. void blink_green();
  52. // "tests"
  53. void detect_sd_card();
  54. void show_warning();
  55. void get_sd_card_info();
  56. void prepare_benchmark_data();
  57. void free_benchmark_data();
  58. void write_benchmark();
  59. uint32_t write_benchmark_internal(const uint32_t size, const uint32_t tcount);
  60. void read_benchmark();
  61. uint32_t read_benchmark_internal(const uint32_t size, const uint32_t count, File* file);
  62. void hash_benchmark();
  63. };
  64. // start app
  65. void SdTest::run() {
  66. // create pin
  67. GpioPin red_led = led_gpio[0];
  68. GpioPin green_led = led_gpio[1];
  69. // TODO open record
  70. red_led_record = &red_led;
  71. green_led_record = &green_led;
  72. // configure pin
  73. gpio_init(red_led_record, GpioModeOutputOpenDrain);
  74. gpio_init(green_led_record, GpioModeOutputOpenDrain);
  75. app_ready();
  76. fs_api = static_cast<FS_Api*>(furi_open("sdcard"));
  77. if(fs_api == NULL) {
  78. set_error({"cannot get sdcard api"});
  79. exit();
  80. }
  81. detect_sd_card();
  82. get_sd_card_info();
  83. show_warning();
  84. prepare_benchmark_data();
  85. write_benchmark();
  86. read_benchmark();
  87. hash_benchmark();
  88. free_benchmark_data();
  89. set_text({
  90. "test complete",
  91. "",
  92. "",
  93. "",
  94. "",
  95. "press BACK to exit",
  96. });
  97. wait_for_button(InputBack);
  98. exit();
  99. }
  100. // detect sd card insertion
  101. void SdTest::detect_sd_card() {
  102. const uint8_t str_buffer_size = 40;
  103. const uint8_t dots_animation_size = 4;
  104. char str_buffer[str_buffer_size];
  105. const char dots[dots_animation_size][4] = {"", ".", "..", "..."};
  106. uint8_t i = 0;
  107. // detect sd card pin
  108. while(fs_api->common.get_fs_info(NULL, NULL) == FSE_NOT_READY) {
  109. delay(100);
  110. snprintf(str_buffer, str_buffer_size, "Waiting%s", dots[i]);
  111. set_text({static_cast<const char*>(str_buffer), "Please insert sd card"});
  112. if(i < (dots_animation_size - 1)) {
  113. i++;
  114. } else {
  115. i = 0;
  116. }
  117. }
  118. blink_green();
  119. }
  120. // show warning about test
  121. void SdTest::show_warning() {
  122. set_text(
  123. {"!!Warning!!",
  124. "during the tests",
  125. "files can be overwritten",
  126. "or data on card may be lost",
  127. "",
  128. "press UP DOWN OK to continue"});
  129. wait_for_button(InputUp);
  130. wait_for_button(InputDown);
  131. wait_for_button(InputOk);
  132. }
  133. // get info about sd card, label, sn
  134. // sector, cluster, total and free size
  135. void SdTest::get_sd_card_info() {
  136. const uint8_t str_buffer_size = 26;
  137. char str_buffer[2][str_buffer_size];
  138. FS_Error result;
  139. uint64_t bytes_total, bytes_free;
  140. int __attribute__((unused)) snprintf_count = 0;
  141. result = fs_api->common.get_fs_info(&bytes_total, &bytes_free);
  142. if(result != FSE_OK) set_error({"get_fs_info error", fs_api->error.get_desc(result)});
  143. snprintf(
  144. str_buffer[0], str_buffer_size, "%lu KB total", static_cast<uint32_t>(bytes_total / 1024));
  145. snprintf(
  146. str_buffer[1], str_buffer_size, "%lu KB free", static_cast<uint32_t>(bytes_free / 1024));
  147. set_text(
  148. {static_cast<const char*>(str_buffer[0]),
  149. static_cast<const char*>(str_buffer[1]),
  150. "",
  151. "",
  152. "",
  153. "press OK to continue"});
  154. blink_green();
  155. wait_for_button(InputOk);
  156. }
  157. // prepare benchmark data (allocate data in ram)
  158. void SdTest::prepare_benchmark_data() {
  159. set_text({"preparing benchmark data"});
  160. benchmark_data = static_cast<uint8_t*>(malloc(benchmark_data_size));
  161. if(benchmark_data == NULL) {
  162. set_error({"cannot allocate buffer", "for benchmark data"});
  163. }
  164. for(size_t i = 0; i < benchmark_data_size; i++) {
  165. benchmark_data[i] = static_cast<uint8_t>(i);
  166. }
  167. set_text({"benchmark data prepared"});
  168. }
  169. void SdTest::free_benchmark_data() {
  170. free(benchmark_data);
  171. }
  172. // write speed test
  173. void SdTest::write_benchmark() {
  174. const uint32_t b1_size = 1;
  175. const uint32_t b8_size = 8;
  176. const uint32_t b32_size = 32;
  177. const uint32_t b256_size = 256;
  178. const uint32_t b4096_size = 4096;
  179. const uint32_t benchmark_data_size = 16384 * 4;
  180. uint32_t benchmark_bps = 0;
  181. const uint8_t str_buffer_size = 32;
  182. char str_buffer[6][str_buffer_size] = {"", "", "", "", "", ""};
  183. auto string_list = {
  184. static_cast<const char*>(str_buffer[0]),
  185. static_cast<const char*>(str_buffer[1]),
  186. static_cast<const char*>(str_buffer[2]),
  187. static_cast<const char*>(str_buffer[3]),
  188. static_cast<const char*>(str_buffer[4]),
  189. static_cast<const char*>(str_buffer[5])};
  190. set_text({"write speed test", "procedure can be lengthy", "please wait"});
  191. delay(100);
  192. // 1b test
  193. benchmark_bps = write_benchmark_internal(b1_size, benchmark_data_size / b1_size);
  194. snprintf(str_buffer[0], str_buffer_size, "1-byte: %lu bps", benchmark_bps);
  195. set_text(string_list);
  196. delay(100);
  197. // 8b test
  198. benchmark_bps = write_benchmark_internal(b8_size, benchmark_data_size / b8_size);
  199. snprintf(str_buffer[1], str_buffer_size, "8-byte: %lu bps", benchmark_bps);
  200. set_text(string_list);
  201. delay(100);
  202. // 32b test
  203. benchmark_bps = write_benchmark_internal(b32_size, benchmark_data_size / b32_size);
  204. snprintf(str_buffer[2], str_buffer_size, "32-byte: %lu bps", benchmark_bps);
  205. set_text(string_list);
  206. delay(100);
  207. // 256b test
  208. benchmark_bps = write_benchmark_internal(b256_size, benchmark_data_size / b256_size);
  209. snprintf(str_buffer[3], str_buffer_size, "256-byte: %lu bps", benchmark_bps);
  210. set_text(string_list);
  211. delay(100);
  212. // 4096b test
  213. benchmark_bps = write_benchmark_internal(b4096_size, benchmark_data_size / b4096_size);
  214. snprintf(str_buffer[4], str_buffer_size, "4096-byte: %lu bps", benchmark_bps);
  215. snprintf(str_buffer[5], str_buffer_size, "press OK to continue");
  216. set_text(string_list);
  217. blink_green();
  218. wait_for_button(InputOk);
  219. }
  220. uint32_t SdTest::write_benchmark_internal(const uint32_t size, const uint32_t count) {
  221. uint32_t start_tick, stop_tick, benchmark_bps, benchmark_time, bytes_written;
  222. File file;
  223. const uint8_t str_buffer_size = 32;
  224. char str_buffer[str_buffer_size];
  225. if(!fs_api->file.open(&file, "write.test", FSAM_WRITE, FSOM_OPEN_ALWAYS)) {
  226. snprintf(str_buffer, str_buffer_size, "in %lu-byte write test", size);
  227. set_error({"cannot open file ", static_cast<const char*>(str_buffer)});
  228. }
  229. start_tick = osKernelGetTickCount();
  230. for(size_t i = 0; i < count; i++) {
  231. bytes_written = fs_api->file.write(&file, benchmark_data, size);
  232. if(bytes_written != size || file.error_id != FSE_OK) {
  233. snprintf(str_buffer, str_buffer_size, "in %lu-byte write test", size);
  234. set_error({"cannot write to file ", static_cast<const char*>(str_buffer)});
  235. }
  236. }
  237. stop_tick = osKernelGetTickCount();
  238. if(!fs_api->file.close(&file)) {
  239. snprintf(str_buffer, str_buffer_size, "in %lu-byte write test", size);
  240. set_error({"cannot close file ", static_cast<const char*>(str_buffer)});
  241. }
  242. benchmark_time = stop_tick - start_tick;
  243. benchmark_bps = (count * size) * osKernelGetTickFreq() / benchmark_time;
  244. return benchmark_bps;
  245. }
  246. // read speed test
  247. void SdTest::read_benchmark() {
  248. const uint32_t benchmark_data_size = 16384 * 8;
  249. uint32_t bytes_written;
  250. uint32_t benchmark_bps = 0;
  251. const uint8_t str_buffer_size = 32;
  252. char str_buffer[6][str_buffer_size] = {"", "", "", "", "", ""};
  253. auto string_list = {
  254. static_cast<const char*>(str_buffer[0]),
  255. static_cast<const char*>(str_buffer[1]),
  256. static_cast<const char*>(str_buffer[2]),
  257. static_cast<const char*>(str_buffer[3]),
  258. static_cast<const char*>(str_buffer[4]),
  259. static_cast<const char*>(str_buffer[5])};
  260. File file;
  261. const uint32_t b1_size = 1;
  262. const uint32_t b8_size = 8;
  263. const uint32_t b32_size = 32;
  264. const uint32_t b256_size = 256;
  265. const uint32_t b4096_size = 4096;
  266. // prepare data for read test
  267. set_text({"prepare data", "for read speed test", "procedure can be lengthy", "please wait"});
  268. delay(100);
  269. if(!fs_api->file.open(&file, "read.test", FSAM_WRITE, FSOM_OPEN_ALWAYS)) {
  270. set_error({"cannot open file ", "in prepare read"});
  271. }
  272. for(size_t i = 0; i < benchmark_data_size / b4096_size; i++) {
  273. bytes_written = fs_api->file.write(&file, benchmark_data, b4096_size);
  274. if(bytes_written != b4096_size || file.error_id != FSE_OK) {
  275. set_error({"cannot write to file ", "in prepare read"});
  276. }
  277. }
  278. if(!fs_api->file.close(&file)) {
  279. set_error({"cannot close file ", "in prepare read"});
  280. }
  281. // test start
  282. set_text({"read speed test", "procedure can be lengthy", "please wait"});
  283. delay(100);
  284. // open file
  285. if(!fs_api->file.open(&file, "read.test", FSAM_READ, FSOM_OPEN_EXISTING)) {
  286. set_error({"cannot open file ", "in read benchmark"});
  287. }
  288. // 1b test
  289. benchmark_bps = read_benchmark_internal(b1_size, benchmark_data_size / b1_size, &file);
  290. snprintf(str_buffer[0], str_buffer_size, "1-byte: %lu bps", benchmark_bps);
  291. set_text(string_list);
  292. delay(100);
  293. // 8b test
  294. benchmark_bps = read_benchmark_internal(b8_size, benchmark_data_size / b8_size, &file);
  295. snprintf(str_buffer[1], str_buffer_size, "8-byte: %lu bps", benchmark_bps);
  296. set_text(string_list);
  297. delay(100);
  298. // 32b test
  299. benchmark_bps = read_benchmark_internal(b32_size, benchmark_data_size / b32_size, &file);
  300. snprintf(str_buffer[2], str_buffer_size, "32-byte: %lu bps", benchmark_bps);
  301. set_text(string_list);
  302. delay(100);
  303. // 256b test
  304. benchmark_bps = read_benchmark_internal(b256_size, benchmark_data_size / b256_size, &file);
  305. snprintf(str_buffer[3], str_buffer_size, "256-byte: %lu bps", benchmark_bps);
  306. set_text(string_list);
  307. delay(100);
  308. // 4096b test
  309. benchmark_bps = read_benchmark_internal(b4096_size, benchmark_data_size / b4096_size, &file);
  310. snprintf(str_buffer[4], str_buffer_size, "4096-byte: %lu bps", benchmark_bps);
  311. snprintf(str_buffer[5], str_buffer_size, "press OK to continue");
  312. set_text(string_list);
  313. // close file
  314. if(!fs_api->file.close(&file)) {
  315. set_error({"cannot close file ", "in read test"});
  316. }
  317. blink_green();
  318. wait_for_button(InputOk);
  319. }
  320. uint32_t SdTest::read_benchmark_internal(const uint32_t size, const uint32_t count, File* file) {
  321. uint32_t start_tick, stop_tick, benchmark_bps, benchmark_time, bytes_readed;
  322. //FRESULT result;
  323. const uint8_t str_buffer_size = 32;
  324. char str_buffer[str_buffer_size];
  325. uint8_t* read_buffer;
  326. read_buffer = static_cast<uint8_t*>(malloc(size));
  327. if(read_buffer == NULL) {
  328. snprintf(str_buffer, str_buffer_size, "in %lu-byte read test", size);
  329. set_error({"cannot allocate memory", static_cast<const char*>(str_buffer)});
  330. }
  331. fs_api->file.seek(file, 0, true);
  332. start_tick = osKernelGetTickCount();
  333. for(size_t i = 0; i < count; i++) {
  334. bytes_readed = fs_api->file.read(file, read_buffer, size);
  335. if(bytes_readed != size || file->error_id != FSE_OK) {
  336. snprintf(str_buffer, str_buffer_size, "in %lu-byte read test", size);
  337. set_error({"cannot read from file ", static_cast<const char*>(str_buffer)});
  338. }
  339. }
  340. stop_tick = osKernelGetTickCount();
  341. free(read_buffer);
  342. benchmark_time = stop_tick - start_tick;
  343. benchmark_bps = (count * size) * osKernelGetTickFreq() / benchmark_time;
  344. return benchmark_bps;
  345. }
  346. // hash benchmark, store data to sd with known hash
  347. // then read, calculate hash and compare both hashes
  348. void SdTest::hash_benchmark() {
  349. uint32_t mcu_data_hash = FNV_1A_INIT;
  350. uint32_t sdcard_data_hash = FNV_1A_INIT;
  351. uint8_t* read_buffer;
  352. uint32_t bytes_readed;
  353. uint32_t bytes_written;
  354. const uint8_t str_buffer_size = 32;
  355. char str_buffer[3][str_buffer_size] = {"", "", ""};
  356. File file;
  357. const uint32_t b4096_size = 4096;
  358. const uint32_t benchmark_count = 20;
  359. // prepare data for hash test
  360. set_text({"prepare data", "for hash test"});
  361. delay(100);
  362. // write data to test file and calculate hash
  363. if(!fs_api->file.open(&file, "hash.test", FSAM_WRITE, FSOM_OPEN_ALWAYS)) {
  364. set_error({"cannot open file ", "in prepare hash"});
  365. }
  366. for(uint32_t i = 0; i < benchmark_count; i++) {
  367. mcu_data_hash = fnv1a_buffer_hash(benchmark_data, b4096_size, mcu_data_hash);
  368. bytes_written = fs_api->file.write(&file, benchmark_data, b4096_size);
  369. if(bytes_written != b4096_size || file.error_id != FSE_OK) {
  370. set_error({"cannot write to file ", "in prepare hash"});
  371. }
  372. snprintf(str_buffer[0], str_buffer_size, "writing %lu of %lu x 4k", i, benchmark_count);
  373. set_text({"prepare data", "for hash test", static_cast<const char*>(str_buffer[0])});
  374. delay(100);
  375. }
  376. if(!fs_api->file.close(&file)) {
  377. set_error({"cannot close file ", "in prepare hash"});
  378. }
  379. // show hash of data located in mcu memory
  380. snprintf(str_buffer[0], str_buffer_size, "hash in mcu 0x%lx", mcu_data_hash);
  381. set_text({str_buffer[0]});
  382. delay(100);
  383. // read data from sd card and calculate hash
  384. read_buffer = static_cast<uint8_t*>(malloc(b4096_size));
  385. if(read_buffer == NULL) {
  386. set_error({"cannot allocate memory", "in hash test"});
  387. }
  388. if(!fs_api->file.open(&file, "hash.test", FSAM_READ, FSOM_OPEN_EXISTING)) {
  389. set_error({"cannot open file ", "in hash test"});
  390. }
  391. for(uint32_t i = 0; i < benchmark_count; i++) {
  392. bytes_readed = fs_api->file.read(&file, read_buffer, b4096_size);
  393. sdcard_data_hash = fnv1a_buffer_hash(read_buffer, b4096_size, sdcard_data_hash);
  394. if(bytes_readed != b4096_size || file.error_id != FSE_OK) {
  395. set_error({"cannot read from file ", "in hash test"});
  396. }
  397. snprintf(str_buffer[1], str_buffer_size, "reading %lu of %lu x 4k", i, benchmark_count);
  398. set_text({str_buffer[0], str_buffer[1]});
  399. delay(100);
  400. }
  401. if(!fs_api->file.close(&file)) {
  402. set_error({"cannot close file ", "in hash test"});
  403. }
  404. free(read_buffer);
  405. snprintf(str_buffer[1], str_buffer_size, "hash in sdcard 0x%lx", sdcard_data_hash);
  406. if(mcu_data_hash == sdcard_data_hash) {
  407. snprintf(str_buffer[2], str_buffer_size, "hashes are equal, press OK");
  408. set_text(
  409. {static_cast<const char*>(str_buffer[0]),
  410. static_cast<const char*>(str_buffer[1]),
  411. "",
  412. "",
  413. "",
  414. static_cast<const char*>(str_buffer[2])});
  415. } else {
  416. snprintf(str_buffer[2], str_buffer_size, "hash error, press BACK to exit");
  417. set_error(
  418. {static_cast<const char*>(str_buffer[0]),
  419. static_cast<const char*>(str_buffer[1]),
  420. "",
  421. "",
  422. "",
  423. static_cast<const char*>(str_buffer[2])});
  424. }
  425. blink_green();
  426. wait_for_button(InputOk);
  427. }
  428. // wait for button press
  429. void SdTest::wait_for_button(Input input_button) {
  430. SdTestEvent event;
  431. osMessageQueueReset(event_queue);
  432. while(1) {
  433. osStatus_t result = osMessageQueueGet(event_queue, &event, NULL, osWaitForever);
  434. if(result == osOK && event.type == SdTestEvent::EventTypeKey) {
  435. if(event.value.input.state == true) {
  436. if(event.value.input.input == InputBack) {
  437. exit();
  438. } else {
  439. if(event.value.input.input == input_button) {
  440. blink_green();
  441. break;
  442. } else {
  443. blink_red();
  444. }
  445. }
  446. }
  447. }
  448. }
  449. osMessageQueueReset(event_queue);
  450. }
  451. // ask user to proceed or cancel
  452. bool SdTest::ask(Input input_button_cancel, Input input_button_ok) {
  453. bool return_result;
  454. SdTestEvent event;
  455. osMessageQueueReset(event_queue);
  456. while(1) {
  457. osStatus_t result = osMessageQueueGet(event_queue, &event, NULL, osWaitForever);
  458. if(result == osOK && event.type == SdTestEvent::EventTypeKey) {
  459. if(event.value.input.state == true) {
  460. if(event.value.input.input == InputBack) {
  461. exit();
  462. } else {
  463. if(event.value.input.input == input_button_ok) {
  464. blink_green();
  465. return_result = true;
  466. break;
  467. } else if(event.value.input.input == input_button_cancel) {
  468. blink_green();
  469. return_result = false;
  470. break;
  471. } else {
  472. blink_red();
  473. }
  474. }
  475. }
  476. }
  477. }
  478. osMessageQueueReset(event_queue);
  479. return return_result;
  480. }
  481. // blink red led
  482. void SdTest::blink_red() {
  483. gpio_write(red_led_record, 0);
  484. delay(50);
  485. gpio_write(red_led_record, 1);
  486. }
  487. // light up red led
  488. void SdTest::set_red() {
  489. gpio_write(red_led_record, 0);
  490. }
  491. // blink green led
  492. void SdTest::blink_green() {
  493. gpio_write(green_led_record, 0);
  494. delay(50);
  495. gpio_write(green_led_record, 1);
  496. }
  497. // set text, but with infinite loop
  498. template <class T> void SdTest::set_error(std::initializer_list<T> list) {
  499. set_text(list);
  500. set_red();
  501. wait_for_button(InputBack);
  502. exit();
  503. }
  504. // set text, sort of variadic function
  505. template <class T> void SdTest::set_text(std::initializer_list<T> list) {
  506. uint8_t line_position = 0;
  507. acquire_state();
  508. printf("------------------------\n");
  509. // set line strings from args
  510. for(auto element : list) {
  511. state.line[line_position] = element;
  512. printf("%s\n", element);
  513. line_position++;
  514. if(line_position == state.lines_count) break;
  515. }
  516. // set empty lines
  517. for(; line_position < state.lines_count; line_position++) {
  518. state.line[line_position] = "";
  519. printf("\n");
  520. }
  521. printf("------------------------\n");
  522. release_state();
  523. }
  524. // render app
  525. void SdTest::render(Canvas* canvas) {
  526. canvas_set_color(canvas, ColorBlack);
  527. canvas_set_font(canvas, FontSecondary);
  528. for(uint8_t i = 0; i < state.lines_count; i++) {
  529. canvas_draw_str(canvas, 0, (i + 1) * 10, state.line[i]);
  530. }
  531. }
  532. // app enter function
  533. extern "C" void sd_card_test(void* p) {
  534. SdTest* app = new SdTest();
  535. app->run();
  536. }