Improve multi (#7136)
[opentx.git] / radio / src / cli.cpp
blobcb72b8076d71e8201445d3625a4fb33d757acd53
1 /*
2 * Copyright (C) OpenTX
4 * Based on code named
5 * th9x - http://code.google.com/p/th9x
6 * er9x - http://code.google.com/p/er9x
7 * gruvin9x - http://code.google.com/p/gruvin9x
9 * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2 as
13 * published by the Free Software Foundation.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
21 #include "opentx.h"
22 #include "diskio.h"
23 #include <ctype.h>
24 #include <malloc.h>
25 #include <new>
27 #define CLI_COMMAND_MAX_ARGS 8
28 #define CLI_COMMAND_MAX_LEN 256
30 RTOS_TASK_HANDLE cliTaskId;
31 RTOS_DEFINE_STACK(cliStack, CLI_STACK_SIZE);
33 Fifo<uint8_t, 256> cliRxFifo;
34 uint8_t cliTracesEnabled = true;
35 char cliLastLine[CLI_COMMAND_MAX_LEN+1];
37 typedef int (* CliFunction) (const char ** args);
38 int cliExecLine(char * line);
39 int cliExecCommand(const char ** argv);
40 int cliHelp(const char ** argv);
42 struct CliCommand
44 const char * name;
45 CliFunction func;
46 const char * args;
49 struct MemArea
51 const char * name;
52 void * start;
53 int size;
56 void cliPrompt()
58 serialPutc('>');
61 int toLongLongInt(const char ** argv, int index, long long int * val)
63 if (*argv[index] == '\0') {
64 return 0;
66 else {
67 int base = 10;
68 const char * s = argv[index];
69 if (strlen(s) > 2 && s[0] == '0' && s[1] == 'x') {
70 base = 16;
71 s = &argv[index][2];
73 char * endptr = nullptr;
74 *val = strtoll(s, &endptr, base);
75 if (*endptr == '\0')
76 return 1;
77 else {
78 serialPrint("%s: Invalid argument \"%s\"", argv[0], argv[index]);
79 return -1;
84 int toInt(const char ** argv, int index, int * val)
86 long long int lval = 0;
87 int result = toLongLongInt(argv, index, &lval);
88 *val = (int)lval;
89 return result;
92 int cliBeep(const char ** argv)
94 int freq = BEEP_DEFAULT_FREQ;
95 int duration = 100;
96 if (toInt(argv, 1, &freq) >= 0 && toInt(argv, 2, &duration) >= 0) {
97 audioQueue.playTone(freq, duration, 20, PLAY_NOW);
99 return 0;
102 int cliPlay(const char ** argv)
104 audioQueue.playFile(argv[1], PLAY_NOW);
105 return 0;
108 int cliLs(const char ** argv)
110 FILINFO fno;
111 DIR dir;
113 FRESULT res = f_opendir(&dir, argv[1]); /* Open the directory */
114 if (res == FR_OK) {
115 for (;;) {
116 res = f_readdir(&dir, &fno); /* Read a directory item */
117 if (res != FR_OK || fno.fname[0] == 0) break; /* Break on error or end of dir */
118 serialPrint(fno.fname);
120 f_closedir(&dir);
122 else {
123 serialPrint("%s: Invalid directory \"%s\"", argv[0], argv[1]);
125 return 0;
128 int cliRead(const char ** argv)
130 FIL file;
131 uint32_t bytesRead = 0;
132 int bufferSize;
133 if (toInt(argv, 2, &bufferSize) == 0 || bufferSize < 0 ) {
134 serialPrint("%s: Invalid buffer size \"%s\"", argv[0], argv[2]);
135 return 0;
138 uint8_t * buffer = (uint8_t*) malloc(bufferSize);
139 if (!buffer) {
140 serialPrint("Not enough memory");
141 return 0;
144 FRESULT result = f_open(&file, argv[1], FA_OPEN_EXISTING | FA_READ);
145 if (result != FR_OK) {
146 free(buffer);
147 serialPrint("%s: File not found \"%s\"", argv[0], argv[1]);
148 return 0;
151 tmr10ms_t start = get_tmr10ms();
153 while (true) {
154 UINT read;
155 result = f_read(&file, buffer, sizeof(buffer), &read);
156 if (result == FR_OK) {
157 if (read == 0) {
158 // end of file
159 f_close(&file);
160 break;
162 bytesRead += read;
165 uint32_t elapsedTime = (get_tmr10ms() - start) * 10;
166 if (elapsedTime == 0) elapsedTime = 1;
167 uint32_t speed = bytesRead / elapsedTime;
168 serialPrint("Read %d bytes in %d ms, speed %d kB/s", bytesRead, elapsedTime, speed);
169 free(buffer);
170 return 0;
173 int cliReadSD(const char ** argv)
175 int startSector;
176 int numberOfSectors;
177 int bufferSectors;
178 if (toInt(argv, 1, &startSector) == 0 || startSector < 0 ) {
179 serialPrint("%s: Invalid start sector \"%s\"", argv[0], argv[1]);
180 return 0;
182 if (toInt(argv, 2, &numberOfSectors) == 0 || numberOfSectors < 0 ) {
183 serialPrint("%s: Invalid number of sectors \"%s\"", argv[0], argv[2]);
184 return 0;
187 if (toInt(argv, 3, &bufferSectors) == 0 || bufferSectors < 0 ) {
188 serialPrint("%s: Invalid number of buffer sectors \"%s\"", argv[0], argv[3]);
189 return 0;
192 uint8_t * buffer = (uint8_t*) malloc(512*bufferSectors);
193 if (!buffer) {
194 serialPrint("Not enough memory");
195 return 0;
198 uint32_t bytesRead = numberOfSectors * 512;
199 tmr10ms_t start = get_tmr10ms();
201 while (numberOfSectors > 0) {
202 DRESULT res = __disk_read(0, buffer, startSector, bufferSectors);
203 if (res != RES_OK) {
204 serialPrint("disk_read error: %d, sector: %d(%d)", res, startSector, numberOfSectors);
206 #if 0
207 for(uint32_t n=0; n<bufferSectors; ++n) {
208 dump(buffer + n*512, 32);
210 #endif
211 #if 0
212 // calc checksumm
213 uint32_t summ = 0;
214 for(int n=0; n<(bufferSectors*512); ++n) {
215 summ += buffer[n];
217 serialPrint("sector %d(%d) checksumm: %u", startSector, numberOfSectors, summ);
218 #endif
219 if (numberOfSectors >= bufferSectors) {
220 numberOfSectors -= bufferSectors;
221 startSector += bufferSectors;
223 else {
224 numberOfSectors = 0;
228 uint32_t elapsedTime = (get_tmr10ms() - start) * 10;
229 if (elapsedTime == 0) elapsedTime = 1;
230 uint32_t speed = bytesRead / elapsedTime;
231 serialPrint("Read %d bytes in %d ms, speed %d kB/s", bytesRead, elapsedTime, speed);
232 free(buffer);
233 return 0;
236 int cliTestSD(const char ** argv)
238 // Do the read test on the SD card and report back the result
240 // get sector count
241 uint32_t sectorCount;
242 if (disk_ioctl(0, GET_SECTOR_COUNT, &sectorCount) != RES_OK) {
243 serialPrint("Error: can't read sector count");
244 return 0;
246 serialPrint("SD card has %u sectors", sectorCount);
248 // read last 16 sectors one sector at the time
249 serialPrint("Starting single sector read test, reading 16 sectors one by one");
250 uint8_t * buffer = (uint8_t*) malloc(512);
251 if (!buffer) {
252 serialPrint("Not enough memory");
253 return 0;
255 for (uint32_t s = sectorCount - 16; s<sectorCount; ++s) {
256 DRESULT res = __disk_read(0, buffer, s, 1);
257 if (res != RES_OK) {
258 serialPrint("sector %d read FAILED, err: %d", s, res);
260 else {
261 serialPrint("sector %d read OK", s);
264 free(buffer);
265 serialCrlf();
267 // read last 16 sectors, two sectors at the time with a multi-block read
268 buffer = (uint8_t *) malloc(512*2);
269 if (!buffer) {
270 serialPrint("Not enough memory");
271 return 0;
274 serialPrint("Starting multiple sector read test, reading two sectors at the time");
275 for (uint32_t s = sectorCount - 16; s<sectorCount; s+=2) {
276 DRESULT res = __disk_read(0, buffer, s, 2);
277 if (res != RES_OK) {
278 serialPrint("sector %d-%d read FAILED, err: %d", s, s+1, res);
280 else {
281 serialPrint("sector %d-%d read OK", s, s+1);
284 free(buffer);
285 serialCrlf();
287 // read last 16 sectors, all sectors with single multi-block read
288 buffer = (uint8_t*) malloc(512*16);
289 if (!buffer) {
290 serialPrint("Not enough memory");
291 return 0;
294 serialPrint("Starting multiple sector read test, reading 16 sectors at the time");
295 DRESULT res = __disk_read(0, buffer, sectorCount-16, 16);
296 if (res != RES_OK) {
297 serialPrint("sector %d-%d read FAILED, err: %d", sectorCount-16, sectorCount-1, res);
299 else {
300 serialPrint("sector %d-%d read OK", sectorCount-16, sectorCount-1);
302 free(buffer);
303 serialCrlf();
305 return 0;
308 int cliTestNew()
310 char * tmp = 0;
311 serialPrint("Allocating 1kB with new()");
312 RTOS_WAIT_MS(200);
313 tmp = new char[1024];
314 if (tmp) {
315 serialPrint("\tsuccess");
316 delete[] tmp;
317 tmp = 0;
319 else {
320 serialPrint("\tFAILURE");
323 serialPrint("Allocating 10MB with (std::nothrow) new()");
324 RTOS_WAIT_MS(200);
325 tmp = new (std::nothrow) char[1024*1024*10];
326 if (tmp) {
327 serialPrint("\tFAILURE, tmp = %p", tmp);
328 delete[] tmp;
329 tmp = 0;
331 else {
332 serialPrint("\tsuccess, allocaton failed, tmp = 0");
335 serialPrint("Allocating 10MB with new()");
336 RTOS_WAIT_MS(200);
337 tmp = new char[1024*1024*10];
338 if (tmp) {
339 serialPrint("\tFAILURE, tmp = %p", tmp);
340 delete[] tmp;
341 tmp = 0;
343 else {
344 serialPrint("\tsuccess, allocaton failed, tmp = 0");
346 serialPrint("Test finished");
347 return 0;
350 #if defined(COLORLCD)
352 extern bool perMainEnabled;
353 typedef void (*timedTestFunc_t)(void);
355 void testDrawSolidFilledRectangle()
357 lcdDrawFilledRect(0, 0, LCD_W, LCD_H, SOLID, TEXT_BGCOLOR);
360 void testDrawFilledRectangle()
362 lcdDrawFilledRect(0, 0, LCD_W, LCD_H, DOTTED, TEXT_BGCOLOR);
365 void testDrawSolidFilledRoundedRectangle()
367 lcdDrawFilledRect(0, 0, LCD_W/2, LCD_H/2, SOLID, ROUND|TEXT_BGCOLOR);
370 void testDrawBlackOverlay()
372 lcdDrawBlackOverlay();
375 void testDrawSolidHorizontalLine1()
377 lcdDrawSolidHorizontalLine(0, 0, 1, 0);
380 void testDrawSolidHorizontalLine2()
382 lcdDrawSolidHorizontalLine(0, 0, LCD_W, 0);
385 void testDrawSolidVerticalLine1()
387 lcdDrawSolidVerticalLine(0, 0, 1, 0);
390 void testDrawSolidVerticalLine2()
392 lcdDrawSolidVerticalLine(0, 0, LCD_H, 0);
395 void testDrawDiagonalLine()
397 lcdDrawLine(0,0, LCD_W, LCD_H, SOLID, TEXT_COLOR);
400 void testEmpty()
404 void testDrawRect()
406 lcdDrawRect(0, 0, LCD_W, LCD_H, 2, SOLID, TEXT_COLOR);
409 void testDrawText()
411 lcdDrawText(0, LCD_H/2, "The quick brown fox jumps over the lazy dog", TEXT_COLOR);
414 void testDrawTextVertical()
416 lcdDrawText(30, LCD_H, "The quick brown fox ", TEXT_COLOR|VERTICAL|NO_FONTCACHE);
419 void testClear()
421 lcdClear();
424 #define RUN_GRAPHICS_TEST(name, runtime) runTimedFunctionTest(name, #name, runtime, 100)
426 float runTimedFunctionTest(timedTestFunc_t func, const char * name, uint32_t runtime, uint16_t step)
428 const uint32_t start = RTOS_GET_MS();
429 uint32_t noRuns = 0;
430 uint32_t actualRuntime = 0;
431 while ((actualRuntime = RTOS_GET_MS() - start) < runtime ) {
432 for (uint16_t n=0; n < step; n++) {
433 func();
435 lcdRefresh();
436 noRuns += step;
438 const float result = (noRuns * 500.0f) / (float)actualRuntime; // runs/second
439 serialPrint("Test %s speed: %lu.%02u, (%lu runs in %lu ms)", name, uint32_t(result), uint16_t((result - uint32_t(result)) * 100.0f), noRuns, actualRuntime);
440 RTOS_WAIT_MS(200);
441 return result;
444 int cliTestGraphics()
446 serialPrint("Starting graphics performance test...");
447 RTOS_WAIT_MS(200);
449 watchdogSuspend(6000/*60s*/);
450 if (pulsesStarted()) {
451 pausePulses();
453 pauseMixerCalculations();
454 perMainEnabled = false;
456 float result = 0;
457 RUN_GRAPHICS_TEST(testEmpty, 1000);
458 // result += RUN_GRAPHICS_TEST(testDrawSolidHorizontalLine1, 1000);
459 result += RUN_GRAPHICS_TEST(testDrawSolidHorizontalLine2, 1000);
460 // result += RUN_GRAPHICS_TEST(testDrawSolidVerticalLine1, 1000);
461 result += RUN_GRAPHICS_TEST(testDrawSolidVerticalLine2, 1000);
462 result += RUN_GRAPHICS_TEST(testDrawDiagonalLine, 1000);
463 result += RUN_GRAPHICS_TEST(testDrawSolidFilledRectangle, 1000);
464 result += RUN_GRAPHICS_TEST(testDrawSolidFilledRoundedRectangle, 1000);
465 result += RUN_GRAPHICS_TEST(testDrawRect, 1000);
466 result += RUN_GRAPHICS_TEST(testDrawFilledRectangle, 1000);
467 result += RUN_GRAPHICS_TEST(testDrawBlackOverlay, 1000);
468 result += RUN_GRAPHICS_TEST(testDrawText, 1000);
469 result += RUN_GRAPHICS_TEST(testDrawTextVertical, 1000);
470 result += RUN_GRAPHICS_TEST(testClear, 1000);
472 serialPrint("Total speed: %lu.%02u", uint32_t(result), uint16_t((result - uint32_t(result)) * 100.0f));
474 perMainEnabled = true;
475 if (pulsesStarted()) {
476 resumePulses();
478 resumeMixerCalculations();
479 watchdogSuspend(0);
481 return 0;
484 void memoryRead(const uint8_t * src, uint32_t size)
486 // uint8_t data;
487 while(size--) {
488 /*data =*/ *(const uint8_t volatile *)src;
489 ++src;
494 void memoryRead(const uint32_t * src, uint32_t size)
496 while(size--) {
497 *(const uint32_t volatile *)src;
498 ++src;
502 uint32_t * testbuff[100];
504 void memoryCopy(uint8_t * dest, const uint8_t * src, uint32_t size)
506 while(size--) {
507 *dest = *src;
508 ++src;
509 ++dest;
513 void memoryCopy(uint32_t * dest, const uint32_t * src, uint32_t size)
515 while(size--) {
516 *dest = *src;
517 ++src;
518 ++dest;
522 #define MEMORY_SPEED_BLOCK_SIZE (4*1024)
524 void testMemoryReadFrom_RAM_8bit()
526 memoryRead((const uint8_t *)cliLastLine, MEMORY_SPEED_BLOCK_SIZE);
529 void testMemoryReadFrom_RAM_32bit()
531 memoryRead((const uint32_t *)0x20000000, MEMORY_SPEED_BLOCK_SIZE/4);
534 void testMemoryReadFrom_SDRAM_8bit()
536 memoryRead((const uint8_t *)0xD0000000, MEMORY_SPEED_BLOCK_SIZE);
539 void testMemoryReadFrom_SDRAM_32bit()
541 memoryRead((const uint32_t *)0xD0000000, MEMORY_SPEED_BLOCK_SIZE/4);
544 extern uint8_t * LCD_FIRST_FRAME_BUFFER;
545 extern uint8_t * LCD_SECOND_FRAME_BUFFER;
548 void testMemoryCopyFrom_RAM_to_SDRAM_32bit()
550 memoryCopy((uint32_t *)LCD_FIRST_FRAME_BUFFER, (const uint32_t * )cliLastLine, MEMORY_SPEED_BLOCK_SIZE/4);
553 void testMemoryCopyFrom_RAM_to_SDRAM_8bit()
555 memoryCopy((uint8_t *)LCD_FIRST_FRAME_BUFFER, (const uint8_t * )cliLastLine, MEMORY_SPEED_BLOCK_SIZE);
558 void testMemoryCopyFrom_SDRAM_to_SDRAM_32bit()
560 memoryCopy((uint32_t *)LCD_FIRST_FRAME_BUFFER, (const uint32_t * )LCD_SECOND_FRAME_BUFFER, MEMORY_SPEED_BLOCK_SIZE/4);
563 void testMemoryCopyFrom_SDRAM_to_SDRAM_8bit()
565 memoryCopy((uint8_t *)LCD_FIRST_FRAME_BUFFER, (const uint8_t * )LCD_SECOND_FRAME_BUFFER, MEMORY_SPEED_BLOCK_SIZE);
568 #define RUN_MEMORY_TEST(name, runtime) runTimedFunctionTest(name, #name, runtime, 100)
570 int cliTestMemorySpeed()
572 serialPrint("Starting memory speed test...");
573 RTOS_WAIT_MS(200);
575 watchdogSuspend(6000/*60s*/);
576 if (pulsesStarted()) {
577 pausePulses();
579 pauseMixerCalculations();
580 perMainEnabled = false;
582 float result = 0;
583 result += RUN_MEMORY_TEST(testMemoryReadFrom_RAM_8bit, 200);
584 result += RUN_MEMORY_TEST(testMemoryReadFrom_RAM_32bit, 200);
585 result += RUN_MEMORY_TEST(testMemoryReadFrom_SDRAM_8bit, 200);
586 result += RUN_MEMORY_TEST(testMemoryReadFrom_SDRAM_32bit, 200);
587 result += RUN_MEMORY_TEST(testMemoryCopyFrom_RAM_to_SDRAM_8bit, 200);
588 result += RUN_MEMORY_TEST(testMemoryCopyFrom_RAM_to_SDRAM_32bit, 200);
589 result += RUN_MEMORY_TEST(testMemoryCopyFrom_SDRAM_to_SDRAM_8bit, 200);
590 result += RUN_MEMORY_TEST(testMemoryCopyFrom_SDRAM_to_SDRAM_32bit, 200);
592 LTDC_Cmd(DISABLE);
593 serialPrint("Disabling LCD...");
594 RTOS_WAIT_MS(200);
596 result += RUN_MEMORY_TEST(testMemoryReadFrom_RAM_8bit, 200);
597 result += RUN_MEMORY_TEST(testMemoryReadFrom_RAM_32bit, 200);
598 result += RUN_MEMORY_TEST(testMemoryReadFrom_SDRAM_8bit, 200);
599 result += RUN_MEMORY_TEST(testMemoryReadFrom_SDRAM_32bit, 200);
600 result += RUN_MEMORY_TEST(testMemoryCopyFrom_RAM_to_SDRAM_8bit, 200);
601 result += RUN_MEMORY_TEST(testMemoryCopyFrom_RAM_to_SDRAM_32bit, 200);
602 result += RUN_MEMORY_TEST(testMemoryCopyFrom_SDRAM_to_SDRAM_8bit, 200);
603 result += RUN_MEMORY_TEST(testMemoryCopyFrom_SDRAM_to_SDRAM_32bit, 200);
605 serialPrint("Total speed: %lu.%02u", uint32_t(result), uint16_t((result - uint32_t(result)) * 100.0f));
607 LTDC_Cmd(ENABLE);
609 perMainEnabled = true;
610 if (pulsesStarted()) {
611 resumePulses();
613 resumeMixerCalculations();
614 watchdogSuspend(0);
616 return 0;
619 #include "storage/modelslist.h"
620 using std::list;
622 int cliTestModelsList()
624 ModelsList modList;
625 modList.load();
627 int count=0;
629 serialPrint("Starting fetching RF data 100x...");
630 const uint32_t start = RTOS_GET_MS();
632 const list<ModelsCategory*>& cats = modList.getCategories();
633 while(1) {
634 for (list<ModelsCategory*>::const_iterator cat_it = cats.begin();
635 cat_it != cats.end(); ++cat_it) {
637 for (ModelsCategory::iterator mod_it = (*cat_it)->begin();
638 mod_it != (*cat_it)->end(); mod_it++) {
640 if (!(*mod_it)->fetchRfData()) {
641 serialPrint("Error while fetching RF data...");
642 return 0;
645 if (++count >= 100)
646 goto done;
651 done:
652 serialPrint("Done fetching %ix RF data: %lu ms", count, (RTOS_GET_MS() - start));
654 return 0;
657 #endif // #if defined(COLORLCD)
659 int cliTest(const char ** argv)
661 if (!strcmp(argv[1], "new")) {
662 return cliTestNew();
664 else if (!strcmp(argv[1], "std::exception")) {
665 serialPrint("Not implemented");
667 #if defined(COLORLCD)
668 else if (!strcmp(argv[1], "graphics")) {
669 return cliTestGraphics();
671 else if (!strcmp(argv[1], "memspd")) {
672 return cliTestMemorySpeed();
674 else if (!strcmp(argv[1], "modelslist")) {
675 return cliTestModelsList();
677 #endif
678 else {
679 serialPrint("%s: Invalid argument \"%s\"", argv[0], argv[1]);
681 return 0;
684 #if defined(DEBUG)
685 int cliTrace(const char ** argv)
687 if (!strcmp(argv[1], "on")) {
688 cliTracesEnabled = true;
690 else if (!strcmp(argv[1], "off")) {
691 cliTracesEnabled = false;
693 else {
694 serialPrint("%s: Invalid argument \"%s\"", argv[0], argv[1]);
696 return 0;
698 #endif
700 int cliStackInfo(const char ** argv)
702 serialPrint("[MAIN] %d available / %d", stackAvailable(), stackSize());
703 serialPrint("[MENUS] %d available / %d", menusStack.available(), menusStack.size());
704 serialPrint("[MIXER] %d available / %d", mixerStack.available(), mixerStack.size());
705 serialPrint("[AUDIO] %d available / %d", audioStack.available(), audioStack.size());
706 serialPrint("[CLI] %d available / %d", cliStack.available(), cliStack.size());
707 return 0;
710 extern int _end;
711 extern int _heap_end;
712 extern unsigned char *heap;
714 int cliMemoryInfo(const char ** argv)
716 // struct mallinfo {
717 // int arena; /* total space allocated from system */
718 // int ordblks; /* number of non-inuse chunks */
719 // int smblks; /* unused -- always zero */
720 // int hblks; /* number of mmapped regions */
721 // int hblkhd; /* total space in mmapped regions */
722 // int usmblks; /* unused -- always zero */
723 // int fsmblks; /* unused -- always zero */
724 // int uordblks; /* total allocated space */
725 // int fordblks; /* total non-inuse space */
726 // int keepcost; /* top-most, releasable (via malloc_trim) space */
727 // };
728 struct mallinfo info = mallinfo();
729 serialPrint("mallinfo:");
730 serialPrint("\tarena %d bytes", info.arena);
731 serialPrint("\tordblks %d bytes", info.ordblks);
732 serialPrint("\tuordblks %d bytes", info.uordblks);
733 serialPrint("\tfordblks %d bytes", info.fordblks);
734 serialPrint("\tkeepcost %d bytes", info.keepcost);
736 serialPrint("\nHeap:");
737 serialPrint("\tstart %p", (unsigned char *)&_end);
738 serialPrint("\tend %p", (unsigned char *)&_heap_end);
739 serialPrint("\tcurr %p", heap);
740 serialPrint("\tused %d bytes", (int)(heap - (unsigned char *)&_end));
741 serialPrint("\tfree %d bytes", (int)((unsigned char *)&_heap_end - heap));
743 #if defined(LUA)
744 serialPrint("\nLua:");
745 uint32_t s = luaGetMemUsed(lsScripts);
746 serialPrint("\tScripts %u", s);
747 #if defined(COLORLCD)
748 uint32_t w = luaGetMemUsed(lsWidgets);
749 uint32_t e = luaExtraMemoryUsage;
750 serialPrint("\tWidgets %u", w);
751 serialPrint("\tExtra %u", e);
752 serialPrint("------------");
753 serialPrint("\tTotal %u", s + w + e);
754 #endif
755 #endif
756 return 0;
759 int cliReboot(const char ** argv)
761 #if !defined(SIMU)
762 if (!strcmp(argv[1], "wdt")) {
763 // do a user requested watchdog test by pausing mixer thread
764 pausePulses();
766 else {
767 NVIC_SystemReset();
769 #endif
770 return 0;
773 const MemArea memAreas[] = {
774 { "RCC", RCC, sizeof(RCC_TypeDef) },
775 { "GPIOA", GPIOA, sizeof(GPIO_TypeDef) },
776 { "GPIOB", GPIOB, sizeof(GPIO_TypeDef) },
777 { "GPIOC", GPIOC, sizeof(GPIO_TypeDef) },
778 { "GPIOD", GPIOD, sizeof(GPIO_TypeDef) },
779 { "GPIOE", GPIOE, sizeof(GPIO_TypeDef) },
780 { "GPIOF", GPIOF, sizeof(GPIO_TypeDef) },
781 { "GPIOG", GPIOG, sizeof(GPIO_TypeDef) },
782 { "USART1", USART1, sizeof(USART_TypeDef) },
783 { "USART2", USART2, sizeof(USART_TypeDef) },
784 { "USART3", USART3, sizeof(USART_TypeDef) },
785 { nullptr, nullptr, 0 },
788 int cliSet(const char ** argv)
790 if (!strcmp(argv[1], "rtc")) {
791 struct gtm t;
792 int year, month, day, hour, minute, second;
793 if (toInt(argv, 2, &year) > 0 && toInt(argv, 3, &month) > 0 && toInt(argv, 4, &day) > 0 && toInt(argv, 5, &hour) > 0 && toInt(argv, 6, &minute) > 0 && toInt(argv, 7, &second) > 0) {
794 t.tm_year = year-TM_YEAR_BASE;
795 t.tm_mon = month-1;
796 t.tm_mday = day;
797 t.tm_hour = hour;
798 t.tm_min = minute;
799 t.tm_sec = second;
800 g_rtcTime = gmktime(&t); // update local timestamp and get wday calculated
801 rtcSetTime(&t);
803 else {
804 serialPrint("%s: Invalid arguments \"%s\" \"%s\"", argv[0], argv[1], argv[2]);
807 #if !defined(SOFTWARE_VOLUME)
808 else if (!strcmp(argv[1], "volume")) {
809 int level = 0;
810 if (toInt(argv, 2, &level) > 0) {
811 setVolume(level);
813 else {
814 serialPrint("%s: Invalid argument \"%s\" \"%s\"", argv[0], argv[1], argv[2]);
816 return 0;
818 #endif
819 return 0;
823 #if defined(DEBUG_INTERRUPTS)
824 void printInterrupts()
826 __disable_irq();
827 struct InterruptCounters ic = interruptCounters;
828 memset(&interruptCounters, 0, sizeof(interruptCounters));
829 interruptCounters.resetTime = get_tmr10ms();
830 __enable_irq();
831 serialPrint("Interrupts count in the last %u ms:", (get_tmr10ms() - ic.resetTime) * 10);
832 for(int n = 0; n < INT_LAST; n++) {
833 serialPrint("%s: %u", interruptNames[n], ic.cnt[n]);
836 #endif //#if defined(DEBUG_INTERRUPTS)
838 #if defined(DEBUG_TASKS)
840 void printTaskSwitchLog()
842 serialPrint("Tasks legend [<task_id>, <task name>]:");
843 for(int n = 0; n <= CFG_MAX_USER_TASKS+1; n++) {
844 if (0 == n) {
845 serialPrint("%d: Idle", n);
847 if (cliTaskId == n) {
848 serialPrint("%d: CLI", n);
850 else if (menusTaskId == n) {
851 serialPrint("%d: menus", n);
853 else if (mixerTaskId == n) {
854 serialPrint("%d: mixer", n);
856 else if (audioTaskId == n) {
857 serialPrint("%d: audio", n);
860 serialCrlf();
862 serialPrint("Tasks switch log at %u [<time>, <task_id>]:", get_tmr10ms());
863 uint32_t lastSwitchTime = 0;
864 uint32_t * tsl = new uint32_t[DEBUG_TASKS_LOG_SIZE];
865 if (!tsl) {
866 serialPrint("Not enough memory");
867 return;
869 memcpy(tsl, taskSwitchLog, sizeof(taskSwitchLog));
870 uint32_t * p = tsl + taskSwitchLogPos;
871 uint32_t * end = tsl + DEBUG_TASKS_LOG_SIZE;
872 for(int n = 0; n < DEBUG_TASKS_LOG_SIZE; n++) {
873 uint32_t taskId = *p >> 24;
874 uint32_t switchTime = *p & 0xFFFFFF;
875 if (lastSwitchTime != switchTime) {
876 serialPrintf("\r\n%06x: ", switchTime);
877 lastSwitchTime = switchTime;
879 serialPrintf("%u ", taskId);
880 if ( ++p >= end ) {
881 p = tsl;
884 delete[] tsl;
885 serialCrlf();
887 #endif // #if defined(DEBUG_TASKS)
889 #if defined(DEBUG_TIMERS)
891 void printDebugTime(uint32_t time)
893 if (time >= 30000) {
894 serialPrintf("%dms", time/1000);
896 else {
897 serialPrintf("%d.%03dms", time/1000, time%1000);
901 void printDebugTimer(const char * name, DebugTimer & timer)
903 serialPrintf("%s: ", name);
904 printDebugTime( timer.getMin());
905 serialPrintf(" - ");
906 printDebugTime(timer.getMax());
907 serialCrlf();
908 timer.reset();
910 void printDebugTimers()
912 for(int n = 0; n < DEBUG_TIMERS_COUNT; n++) {
913 printDebugTimer(debugTimerNames[n], debugTimers[n]);
916 #endif
918 #include "OsMutex.h"
919 extern RTOS_MUTEX_HANDLE audioMutex;
921 void printAudioVars()
923 for(int n = 0; n < AUDIO_BUFFER_COUNT; n++) {
924 serialPrint("Audio Buffer %d: size: %u, ", n, (uint32_t)audioBuffers[n].size);
925 dump((uint8_t *)audioBuffers[n].data, 32);
927 serialPrint("fragments:");
928 for(int n = 0; n < AUDIO_QUEUE_LENGTH; n++) {
929 serialPrint("%d: type %u: id: %u, repeat: %u, ", n, (uint32_t)audioQueue.fragmentsFifo.fragments[n].type,
930 (uint32_t)audioQueue.fragmentsFifo.fragments[n].id,
931 (uint32_t)audioQueue.fragmentsFifo.fragments[n].repeat);
932 if ( audioQueue.fragmentsFifo.fragments[n].type == FRAGMENT_FILE) {
933 serialPrint(" file: %s", audioQueue.fragmentsFifo.fragments[n].file);
937 serialPrint("FragmentFifo: ridx: %d, widx: %d", audioQueue.fragmentsFifo.ridx, audioQueue.fragmentsFifo.widx);
938 serialPrint("audioQueue: readIdx: %d, writeIdx: %d, full: %d", audioQueue.buffersFifo.readIdx, audioQueue.buffersFifo.writeIdx, audioQueue.buffersFifo.bufferFull);
940 serialPrint("normalContext: %u", (uint32_t)audioQueue.normalContext.fragment.type);
942 serialPrint("audioMutex[%u] = %u", (uint32_t)audioMutex, (uint32_t)MutexTbl[audioMutex].mutexFlag);
946 int cliDisplay(const char ** argv)
948 long long int address = 0;
950 for (const MemArea * area = memAreas; area->name != nullptr; area++) {
951 if (!strcmp(area->name, argv[1])) {
952 dump((uint8_t *)area->start, area->size);
953 return 0;
957 if (!strcmp(argv[1], "keys")) {
958 for (int i=0; i<TRM_BASE; i++) {
959 char name[8];
960 uint8_t len = STR_VKEYS[0];
961 strncpy(name, STR_VKEYS+1+len*i, len);
962 name[len] = '\0';
963 serialPrint("[%s] = %s", name, keys[i].state() ? "on" : "off");
965 #if defined(ROTARY_ENCODER_NAVIGATION)
966 serialPrint("[Enc.] = %d", rotencValue / ROTARY_ENCODER_GRANULARITY);
967 #endif
968 for (int i=TRM_BASE; i<=TRM_LAST; i++) {
969 serialPrint("[Trim%d] = %s", i-TRM_BASE, keys[i].state() ? "on" : "off");
971 for (int i=MIXSRC_FIRST_SWITCH; i<=MIXSRC_LAST_SWITCH; i++) {
972 mixsrc_t sw = i - MIXSRC_FIRST_SWITCH;
973 if (SWITCH_EXISTS(sw)) {
974 char swName[LEN_SWITCH_NAME + 1];
975 strAppend(swName, STR_VSWITCHES+1+sw*STR_VSWITCHES[0], STR_VSWITCHES[0]);
976 static const char * const SWITCH_POSITIONS[] = { "down", "mid", "up" };
977 serialPrint("[%s] = %s", swName, SWITCH_POSITIONS[1 + getValue(i) / 1024]);
981 else if (!strcmp(argv[1], "adc")) {
982 for (int i=0; i<NUM_ANALOGS; i++) {
983 serialPrint("adc[%d] = %04X", i, (int)adcValues[i]);
986 else if (!strcmp(argv[1], "outputs")) {
987 for (int i=0; i<MAX_OUTPUT_CHANNELS; i++) {
988 serialPrint("outputs[%d] = %04d", i, (int)channelOutputs[i]);
991 else if (!strcmp(argv[1], "rtc")) {
992 struct gtm utm;
993 gettime(&utm);
994 serialPrint("rtc = %4d-%02d-%02d %02d:%02d:%02d.%02d0", utm.tm_year+TM_YEAR_BASE, utm.tm_mon+1, utm.tm_mday, utm.tm_hour, utm.tm_min, utm.tm_sec, g_ms100);
996 #if !defined(SOFTWARE_VOLUME)
997 else if (!strcmp(argv[1], "volume")) {
998 serialPrint("volume = %d", getVolume());
1000 #endif
1001 #if defined(STM32)
1002 else if (!strcmp(argv[1], "uid")) {
1003 char str[LEN_CPU_UID+1];
1004 getCPUUniqueID(str);
1005 serialPrint("uid = %s", str);
1007 #endif
1008 else if (!strcmp(argv[1], "tim")) {
1009 int timerNumber;
1010 if (toInt(argv, 2, &timerNumber) > 0) {
1011 TIM_TypeDef * tim = TIM1;
1012 switch (timerNumber) {
1013 case 1:
1014 tim = TIM1;
1015 break;
1016 case 2:
1017 tim = TIM2;
1018 break;
1019 case 8:
1020 tim = TIM8;
1021 break;
1022 case 13:
1023 tim = TIM13;
1024 break;
1025 default:
1026 return 0;
1028 serialPrint("TIM%d", timerNumber);
1029 serialPrint(" CR1 0x%x", tim->CR1);
1030 serialPrint(" CR2 0x%x", tim->CR2);
1031 serialPrint(" DIER 0x%x", tim->DIER);
1032 serialPrint(" SR 0x%x", tim->SR);
1033 serialPrint(" EGR 0x%x", tim->EGR);
1034 serialPrint(" CCMR1 0x%x", tim->CCMR1);
1035 serialPrint(" CCMR2 0x%x", tim->CCMR2);
1037 serialPrint(" CNT 0x%x", tim->CNT);
1038 serialPrint(" ARR 0x%x", tim->ARR);
1039 serialPrint(" PSC 0x%x", tim->PSC);
1041 serialPrint(" CCER 0x%x", tim->CCER);
1042 serialPrint(" CCR1 0x%x", tim->CCR1);
1043 serialPrint(" CCR2 0x%x", tim->CCR2);
1044 serialPrint(" CCR3 0x%x", tim->CCR3);
1045 serialPrint(" CCR4 0x%x", tim->CCR4);
1048 else if (!strcmp(argv[1], "dma")) {
1049 serialPrint("DMA1_Stream7");
1050 serialPrint(" CR 0x%x", DMA1_Stream7->CR);
1052 #if defined(DEBUG_INTERRUPTS)
1053 else if (!strcmp(argv[1], "int")) {
1054 printInterrupts();
1056 #endif
1057 #if defined(DEBUG_TASKS)
1058 else if (!strcmp(argv[1], "tsl")) {
1059 printTaskSwitchLog();
1061 #endif
1062 #if defined(DEBUG_TIMERS)
1063 else if (!strcmp(argv[1], "dt")) {
1064 printDebugTimers();
1066 #endif
1067 else if (!strcmp(argv[1], "audio")) {
1068 printAudioVars();
1070 #if defined(DISK_CACHE)
1071 else if (!strcmp(argv[1], "dc")) {
1072 DiskCacheStats stats = diskCache.getStats();
1073 uint32_t hitRate = diskCache.getHitRate();
1074 serialPrint("Disk Cache stats: w:%u r: %u, h: %u(%0.1f%%), m: %u", stats.noWrites, (stats.noHits + stats.noMisses), stats.noHits, hitRate*0.1f, stats.noMisses);
1076 #endif
1077 else if (toLongLongInt(argv, 1, &address) > 0) {
1078 int size = 256;
1079 if (toInt(argv, 2, &size) >= 0) {
1080 dump((uint8_t *)address, size);
1083 return 0;
1086 int cliDebugVars(const char ** argv)
1088 #if defined(PCBHORUS)
1089 extern uint32_t ioMutexReq, ioMutexRel;
1090 extern uint32_t sdReadRetries;
1091 serialPrint("ioMutexReq=%d", ioMutexReq);
1092 serialPrint("ioMutexRel=%d", ioMutexRel);
1093 serialPrint("sdReadRetries=%d", sdReadRetries);
1094 #elif defined(PCBTARANIS)
1095 serialPrint("telemetryErrors=%d", telemetryErrors);
1096 #endif
1098 return 0;
1101 int cliRepeat(const char ** argv)
1103 int interval = 0;
1104 int counter = 0;
1105 if (toInt(argv, 1, &interval) > 0 && argv[2]) {
1106 interval *= 50;
1107 counter = interval;
1108 uint8_t c;
1109 while (!cliRxFifo.pop(c) || !(c == '\r' || c == '\n' || c == ' ')) {
1110 RTOS_WAIT_MS(20); // 20ms
1111 if (++counter >= interval) {
1112 cliExecCommand(&argv[2]);
1113 counter = 0;
1117 else {
1118 serialPrint("%s: Invalid arguments", argv[0]);
1120 return 0;
1123 #if defined(JITTER_MEASURE)
1124 int cliShowJitter(const char ** argv)
1126 serialPrint( "# anaIn rawJ avgJ");
1127 for (int i=0; i<NUM_ANALOGS; i++) {
1128 serialPrint("A%02d %04X %04X %3d %3d", i, getAnalogValue(i), anaIn(i), rawJitter[i].get(), avgJitter[i].get());
1129 if (IS_POT_MULTIPOS(i)) {
1130 StepsCalibData * calib = (StepsCalibData *) &g_eeGeneral.calib[i];
1131 for (int j=0; j<calib->count; j++) {
1132 serialPrint(" s%d %04X", j, calib->steps[j]);
1136 return 0;
1138 #endif
1140 #if defined(INTERNAL_GPS)
1141 int cliGps(const char ** argv)
1143 int baudrate = 0;
1145 if (argv[1][0] == '$') {
1146 // send command to GPS
1147 gpsSendFrame(argv[1]);
1149 #if defined(DEBUG)
1150 else if (!strcmp(argv[1], "trace")) {
1151 gpsTraceEnabled = !gpsTraceEnabled;
1153 #endif
1154 else if (toInt(argv, 1, &baudrate) > 0 && baudrate > 0) {
1155 gpsInit(baudrate);
1156 serialPrint("GPS baudrate set to %d", baudrate);
1158 else {
1159 serialPrint("%s: Invalid arguments", argv[0]);
1161 return 0;
1163 #endif
1165 #if defined(BLUETOOTH)
1166 int cliBlueTooth(const char ** argv)
1168 int baudrate = 0;
1169 if (!strncmp(argv[1], "AT", 2) || !strncmp(argv[1], "TTM", 3)) {
1170 bluetooth.writeString(argv[1]);
1171 char * line = bluetooth.readline();
1172 serialPrint("<BT %s", line);
1174 else if (toInt(argv, 1, &baudrate) > 0) {
1175 if (baudrate > 0) {
1176 bluetoothInit(baudrate, true);
1177 char * line = bluetooth.readline();
1178 serialPrint("<BT %s", line);
1180 else {
1181 bluetoothDisable();
1182 serialPrint("BT turned off");
1185 else {
1186 serialPrint("%s: Invalid arguments", argv[0]);
1188 return 0;
1190 #endif
1192 const CliCommand cliCommands[] = {
1193 { "beep", cliBeep, "[<frequency>] [<duration>]" },
1194 { "ls", cliLs, "<directory>" },
1195 { "read", cliRead, "<filename>" },
1196 { "readsd", cliReadSD, "<start sector> <sectors count> <read buffer size (sectors)>" },
1197 { "testsd", cliTestSD, "" },
1198 { "play", cliPlay, "<filename>" },
1199 { "print", cliDisplay, "<address> [<size>] | <what>" },
1200 { "p", cliDisplay, "<address> [<size>] | <what>" },
1201 { "reboot", cliReboot, "[wdt]" },
1202 { "set", cliSet, "<what> <value>" },
1203 { "stackinfo", cliStackInfo, "" },
1204 { "meminfo", cliMemoryInfo, "" },
1205 { "test", cliTest, "new | std::exception | graphics | memspd" },
1206 #if defined(DEBUG)
1207 { "trace", cliTrace, "on | off" },
1208 #endif
1209 { "help", cliHelp, "[<command>]" },
1210 { "debugvars", cliDebugVars, "" },
1211 { "repeat", cliRepeat, "<interval> <command>" },
1212 #if defined(JITTER_MEASURE)
1213 { "jitter", cliShowJitter, "" },
1214 #endif
1215 #if defined(INTERNAL_GPS)
1216 { "gps", cliGps, "<baudrate>|$<command>|trace" },
1217 #endif
1218 #if defined(BLUETOOTH)
1219 { "bt", cliBlueTooth, "<baudrate>|<command>" },
1220 #endif
1221 { nullptr, nullptr, nullptr } /* sentinel */
1224 int cliHelp(const char ** argv)
1226 for (const CliCommand * command = cliCommands; command->name != nullptr; command++) {
1227 if (argv[1][0] == '\0' || !strcmp(command->name, argv[0])) {
1228 serialPrint("%s %s", command->name, command->args);
1229 if (argv[1][0] != '\0') {
1230 return 0;
1234 if (argv[1][0] != '\0') {
1235 serialPrint("Invalid command \"%s\"", argv[0]);
1237 return -1;
1240 int cliExecCommand(const char ** argv)
1242 if (argv[0][0] == '\0')
1243 return 0;
1245 for (const CliCommand * command = cliCommands; command->name != nullptr; command++) {
1246 if (!strcmp(command->name, argv[0])) {
1247 return command->func(argv);
1250 serialPrint("Invalid command \"%s\"", argv[0]);
1251 return -1;
1254 int cliExecLine(char * line)
1256 int len = strlen(line);
1257 const char * argv[CLI_COMMAND_MAX_ARGS];
1258 memset(argv, 0, sizeof(argv));
1259 int argc = 1;
1260 argv[0] = line;
1261 for (int i=0; i<len; i++) {
1262 if (line[i] == ' ') {
1263 line[i] = '\0';
1264 if (argc < CLI_COMMAND_MAX_ARGS) {
1265 argv[argc++] = &line[i+1];
1269 return cliExecCommand(argv);
1272 void cliTask(void * pdata)
1274 char line[CLI_COMMAND_MAX_LEN+1];
1275 int pos = 0;
1277 cliPrompt();
1279 for (;;) {
1280 uint8_t c;
1282 while (!cliRxFifo.pop(c)) {
1283 RTOS_WAIT_MS(20); // 20ms
1286 if (c == 12) {
1287 // clear screen
1288 serialPrint("\033[2J\033[1;1H");
1289 cliPrompt();
1291 else if (c == 127) {
1292 // backspace
1293 if (pos) {
1294 line[--pos] = '\0';
1295 serialPutc(c);
1298 else if (c == '\r' || c == '\n') {
1299 // enter
1300 serialCrlf();
1301 line[pos] = '\0';
1302 if (pos == 0 && cliLastLine[0]) {
1303 // execute (repeat) last command
1304 strcpy(line, cliLastLine);
1306 else {
1307 // save new command
1308 strcpy(cliLastLine, line);
1310 cliExecLine(line);
1311 pos = 0;
1312 cliPrompt();
1314 else if (isascii(c) && pos < CLI_COMMAND_MAX_LEN) {
1315 line[pos++] = c;
1316 serialPutc(c);
1321 void cliStart()
1323 RTOS_CREATE_TASK(cliTaskId, cliTask, "CLI", cliStack, CLI_STACK_SIZE, CLI_TASK_PRIO);