Files
edgetx-gx12/radio/src/cli.cpp
T
Raphael CoefficandMalte Langermann 2b100e0eb5 refact: ADC, switches and keys
Add "hardware_defs" target to dump hardware defines from hal.h per target

Fixed hal.h definitions for switches & ADC inputs

Tools to generate hardware definitions from current hal.h

generator: add support for EXTx and analog switches

WiP

Add generated header to hardware_defs target

Add support for ADC input direction and index per GPIO port

Incredible: it compiles

Removed StdPeriph ADC driver

Works on QX7

Works on TX16S (VBat missing)

Switch driver using generated switches for stm32 targets

Switch driver with generated switches for simu

Use switch names from switch driver

Fixed single conversion handling

Fixed horus VBat reading

Added switch name

Fix warnings about bitfield offsets and GCC 4.4

This is caused by bitfields that cross type boundaries. In this particular case, it happens as the bitfield is not defined the same in `rtc_backup.cpp`.

Switches has already been moved to generic driver

Jitter measurement for each sample, not just for oversampled values

Moved periph clock init to the ADC driver

ADC & GPIOs.

Add switchInit() to init pins

Fix NV14 CMake

Fix taranis target

Fix NV14 (again)

Model audio files: use new interface for switch index

Fix X9-lite

Fix simu headers

Fix getSwitchName()

Add support for ADC switches

Fix switch warning YAML

Test only switches which are defined

Conversions tests for QX7 only on QX7

Fix X12S

Fix function switch names

Enable ADC periph clock before initialising channels

NV14: offset inputs for the sticks & fix switches

WiP: get rid of VSRCRAW and some more

Simu shall use normal ADC driver

fix x7

fix nv14

Fix 9x nav

fix 212x64

fix xlite

fix x9e

fix simu lib

fix unit tests

fix x7 tests

PWM stick values should be written directly into adcValues

... just like FlySky sticks.

fix x12s

fix nv14

Add some support for SPI ADC (only unit tests)

Remove `jq` usage

fix generate_yaml.py for newer libclang

fix pot config

fix stick names

Some YAML generator improvements

Max 16 Pots on COLORLCD

Fix some source names

Use 10 bits for switches to prevent overflow

Removed horus constants for switches & ADC inputs

Fix unit tests

Fix curve edit

Fix libsimulator

Fix function switches (tpro)

Refine ADC & analog API

Moved hardware definitions scripts and templates into own dir

cleanup API refine

Fix unit tests

Mixer: apply calibration only on required inputs

Place ADC switches after all other ADC inputs

Fix X12S

Remove useless TR_POTS_VSRCRAW, TR_SW_VSRCRAW, and TR_VSRCRAW

Cleanup redundant generated YAML parsers

Fixes Boxer rebase

Add boxer to generate-hw-defs.sh

Obsolete LUA exports

Add boxer ADC_DIRECTION

Split generate_hw_defs.py into multiple files

Some cleanup (+ LUA switches)

Fix AXIS definitions

Fixed translation strings declarations

Some rebase fixes

Remove some useless StdPeriph drivers

fix: set pin mode to reset state on usart deinit

fix: use canonical names for calibration data

fix: ADC driver does not care about unconfigured inputs

fix X12S ADC

Add support for ADC conversion chaining

Better X12S ADC implem

Moved X12S ADC driver to HAL/LL

Improve ADC driver separation

Fixed taranis & nv14

Moved PWM gimbals to HAL/LL

Removed useless TR_EXTRA_VSRCRAW

Fixed include guards

Small fixes

Renamed control inputs

Moved internal and external module pulse driver def to boards

Move more things to the generic_stm32 board

Remove STR_VMIXTRIMS

Added analog labels

Generate main control names

Misuse labels / short labels for main controls

Backward compatibility: sliders and legacy names

New keys driver

Let's get rid of StdPeriph headers in simu

Add jinja2 to CI workflows

Use `grep` instead of `sed`

chore: add python jinja2 oto msys setup script

Removed more static definitions and use dynamic ones instead

Fixes X9E

Fix x7 nav too

212 switch display

Fix default sliders

Removed NUM_STICKS

212 switch display working nicely

Surface radio 'stick' replacements

fix: switch, trim & rotary pin init

Remove old key drivers

Removed VKEYS

Removed ETX_FOURCC

Fixed SURFACE_NAME

Removed TR_VSWITCHES & TR_TRIMS_SWITCHES

Fix xpot switches

Store calibrated analogs in physical order

Use getSourceString in drawSource

Implement missing YAML read/write

Fix isSourceAvailableInInputs()

Fix isSourceAvailable()

Some surface fixes

Fix CLI debug

Ported colour UI of analog diagnostics to new ADC API

Small fixes

Better input mapping

Minor tpro fixes

Fix trims available

Remove TX mode choice on surface radio

Rename surface controls

Channel order alignement

Fix throttle warning

Fix checkTrims()

Fix pot config casting issue

Fix IS_SWITCH_FS()

Fixed input mapping

Fix fswitch auto-switch

Fixed storing / reading function switches in YAML

Fix enableVBatBridge() / disableVBatBridge()

Fix interesting mode conversion

Fix sources issues

Fix special funnction list

Proper function switch handling

128 GUI improvements

Fix compilation

Boxer cosmetics

128 hardware screen cosmetic

Fix TX12 switches

More switches fixes

Simplify CPU type handling

Fix flash size

Fixed page-up / page-down in libopenui
2023-06-25 14:32:39 +02:00

1796 lines
46 KiB
C++

/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <FreeRTOS/include/FreeRTOS.h>
#include <FreeRTOS/include/stream_buffer.h>
#include "opentx.h"
#include "diskio.h"
#include "timers_driver.h"
#include "watchdog_driver.h"
#if defined(BLUETOOTH)
#include "bluetooth_driver.h"
#endif
#include "hal/adc_driver.h"
#include "hal/module_port.h"
#include "tasks.h"
#include "tasks/mixer_task.h"
#include "cli.h"
#include <ctype.h>
#include <malloc.h>
#include <new>
#include <stdarg.h>
#define CLI_COMMAND_MAX_ARGS 8
#define CLI_COMMAND_MAX_LEN 256
// CLI receive buffer size
#define CLI_RX_BUFFER_SIZE 256
#define CLI_PRINT_BUFFER_SIZE 128
RTOS_TASK_HANDLE cliTaskId;
RTOS_DEFINE_STACK(cliTaskId, cliStack, CLI_STACK_SIZE);
static uint8_t cliRxBufferStorage[CLI_RX_BUFFER_SIZE];
static StaticStreamBuffer_t cliRxBufferStatic;
// CLI receive stream buffer
static StreamBufferHandle_t cliRxBuffer;
// CLI receive call back
void (*cliReceiveCallBack)(uint8_t* buf, uint32_t len) = nullptr;
// CLI send call back
static void (*cliSendCb)(void*, uint8_t) = nullptr;
static void* cliSendCtx = nullptr;
static const etx_serial_driver_t* cliSerialDriver = nullptr;
static void* cliSerialDriverCtx = nullptr;
static uint8_t cliTracesEnabled = false;
static void (*cliTracesOldCb)(void*, uint8_t);
static void* cliTracesOldCbCtx;
void cliSetSendCb(void* ctx, void (*cb)(void*, uint8_t))
{
cliSendCb = nullptr;
cliSendCtx = ctx;
cliSendCb = cb;
}
// Called from receive ISR (either USB or UART)
static void cliReceiveData(uint8_t* buf, uint32_t len)
{
if (cliReceiveCallBack)
cliReceiveCallBack(buf, len);
}
// Assumes it is called from ISR...
static void cliDefaultRx(uint8_t *buf, uint32_t len)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xStreamBufferSendFromISR(cliRxBuffer, buf, len, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
static void cliEnableDbg()
{
if (dbgSerialGetSendCb() != cliSendCb) {
cliTracesOldCb = dbgSerialGetSendCb();
cliTracesOldCbCtx = dbgSerialGetSendCbCtx();
dbgSerialSetSendCb(nullptr, nullptr);
dbgSerialSetSendCb(cliSendCtx, cliSendCb);
}
}
static void cliDisableDbg()
{
if (dbgSerialGetSendCb() == cliSendCb) {
dbgSerialSetSendCb(nullptr, nullptr);
dbgSerialSetSendCb(cliTracesOldCbCtx, cliTracesOldCb);
}
}
void cliSetSerialDriver(void* ctx, const etx_serial_driver_t* drv)
{
cliSerialDriver = nullptr;
cliSerialDriverCtx = ctx;
cliSerialDriver = drv;
if (drv) {
if (drv->setReceiveCb) drv->setReceiveCb(ctx, cliReceiveData);
cliSetSendCb(ctx, drv->sendByte);
if (cliTracesEnabled) {
cliEnableDbg();
}
} else {
cliSetSendCb(nullptr, nullptr);
cliDisableDbg();
}
}
static void cliSerialPrintf(const char * format, ...)
{
va_list arglist;
char tmp[CLI_PRINT_BUFFER_SIZE];
// no need to do anything if we don't have an output
if (!cliSendCb) return;
va_start(arglist, format);
vsnprintf(tmp, CLI_PRINT_BUFFER_SIZE-1, format, arglist);
tmp[CLI_PRINT_BUFFER_SIZE-1] = '\0';
va_end(arglist);
const char *t = tmp;
while (*t && cliSendCb) {
cliSendCb(cliSendCtx, *t++);
}
}
#define cliSerialPrint(...) \
do { cliSerialPrintf(__VA_ARGS__); cliSerialCrlf(); } while(0)
static void cliSerialPutc(uint8_t c)
{
if (cliSendCb) cliSendCb(cliSendCtx, c);
}
static void cliSerialCrlf()
{
cliSerialPutc('\r');
cliSerialPutc('\n');
}
static uint32_t cliGetBaudRate()
{
auto drv = cliSerialDriver;
auto ctx = cliSerialDriverCtx;
if (drv && drv->getBaudrate) {
return drv->getBaudrate(ctx);
}
return 0;
}
char cliLastLine[CLI_COMMAND_MAX_LEN+1];
typedef int (* CliFunction) (const char ** args);
int cliExecLine(char * line);
int cliExecCommand(const char ** argv);
int cliHelp(const char ** argv);
struct CliCommand
{
const char * name;
CliFunction func;
const char * args;
};
struct MemArea
{
const char * name;
void * start;
int size;
};
void cliPrompt()
{
cliSerialPutc('>');
cliSerialPutc(' ');
}
int toLongLongInt(const char ** argv, int index, long long int * val)
{
if (*argv[index] == '\0') {
return 0;
}
else {
int base = 10;
const char * s = argv[index];
if (strlen(s) > 2 && s[0] == '0' && s[1] == 'x') {
base = 16;
s = &argv[index][2];
}
char * endptr = nullptr;
*val = strtoll(s, &endptr, base);
if (*endptr == '\0')
return 1;
else {
cliSerialPrint("%s: Invalid argument \"%s\"", argv[0], argv[index]);
return -1;
}
}
}
int toInt(const char ** argv, int index, int * val)
{
long long int lval = 0;
int result = toLongLongInt(argv, index, &lval);
*val = (int)lval;
return result;
}
int cliBeep(const char ** argv)
{
int freq = BEEP_DEFAULT_FREQ;
int duration = 100;
if (toInt(argv, 1, &freq) >= 0 && toInt(argv, 2, &duration) >= 0) {
audioQueue.playTone(freq, duration, 20, PLAY_NOW);
}
return 0;
}
int cliPlay(const char ** argv)
{
audioQueue.playFile(argv[1], PLAY_NOW);
return 0;
}
int cliLs(const char ** argv)
{
FILINFO fno;
DIR dir;
FRESULT res = f_opendir(&dir, argv[1]); /* Open the directory */
if (res == FR_OK) {
for (;;) {
res = f_readdir(&dir, &fno); /* Read a directory item */
if (res != FR_OK || fno.fname[0] == 0) break; /* Break on error or end of dir */
cliSerialPrint(fno.fname);
}
f_closedir(&dir);
}
else {
cliSerialPrint("%s: Invalid directory \"%s\"", argv[0], argv[1]);
}
return 0;
}
int cliRead(const char ** argv)
{
FIL file;
uint32_t bytesRead = 0;
int bufferSize;
if (toInt(argv, 2, &bufferSize) == 0 || bufferSize < 0 ) {
cliSerialPrint("%s: Invalid buffer size \"%s\"", argv[0], argv[2]);
return 0;
}
uint8_t * buffer = (uint8_t*) malloc(bufferSize);
if (!buffer) {
cliSerialPrint("Not enough memory");
return 0;
}
FRESULT result = f_open(&file, argv[1], FA_OPEN_EXISTING | FA_READ);
if (result != FR_OK) {
free(buffer);
cliSerialPrint("%s: File not found \"%s\"", argv[0], argv[1]);
return 0;
}
tmr10ms_t start = get_tmr10ms();
while (true) {
UINT read;
result = f_read(&file, buffer, sizeof(buffer), &read);
if (result == FR_OK) {
if (read == 0) {
// end of file
f_close(&file);
break;
}
bytesRead += read;
}
}
uint32_t elapsedTime = (get_tmr10ms() - start) * 10;
if (elapsedTime == 0) elapsedTime = 1;
uint32_t speed = bytesRead / elapsedTime;
cliSerialPrint("Read %d bytes in %d ms, speed %d kB/s", bytesRead, elapsedTime, speed);
free(buffer);
return 0;
}
int cliReadSD(const char ** argv)
{
int startSector;
int numberOfSectors;
int bufferSectors;
if (toInt(argv, 1, &startSector) == 0 || startSector < 0 ) {
cliSerialPrint("%s: Invalid start sector \"%s\"", argv[0], argv[1]);
return 0;
}
if (toInt(argv, 2, &numberOfSectors) == 0 || numberOfSectors < 0 ) {
cliSerialPrint("%s: Invalid number of sectors \"%s\"", argv[0], argv[2]);
return 0;
}
if (toInt(argv, 3, &bufferSectors) == 0 || bufferSectors < 0 ) {
cliSerialPrint("%s: Invalid number of buffer sectors \"%s\"", argv[0], argv[3]);
return 0;
}
uint8_t * buffer = (uint8_t*) malloc(512*bufferSectors);
if (!buffer) {
cliSerialPrint("Not enough memory");
return 0;
}
uint32_t bytesRead = numberOfSectors * 512;
tmr10ms_t start = get_tmr10ms();
while (numberOfSectors > 0) {
DRESULT res = __disk_read(0, buffer, startSector, bufferSectors);
if (res != RES_OK) {
cliSerialPrint("disk_read error: %d, sector: %d(%d)", res, startSector, numberOfSectors);
}
#if 0
for(uint32_t n=0; n<bufferSectors; ++n) {
dump(buffer + n*512, 32);
}
#endif
#if 0
// calc checksumm
uint32_t summ = 0;
for(int n=0; n<(bufferSectors*512); ++n) {
summ += buffer[n];
}
cliSerialPrint("sector %d(%d) checksumm: %u", startSector, numberOfSectors, summ);
#endif
if (numberOfSectors >= bufferSectors) {
numberOfSectors -= bufferSectors;
startSector += bufferSectors;
}
else {
numberOfSectors = 0;
}
}
uint32_t elapsedTime = (get_tmr10ms() - start) * 10;
if (elapsedTime == 0) elapsedTime = 1;
uint32_t speed = bytesRead / elapsedTime;
cliSerialPrint("Read %d bytes in %d ms, speed %d kB/s", bytesRead, elapsedTime, speed);
free(buffer);
return 0;
}
int cliTestSD(const char ** argv)
{
// Do the read test on the SD card and report back the result
// get sector count
uint32_t sectorCount;
if (disk_ioctl(0, GET_SECTOR_COUNT, &sectorCount) != RES_OK) {
cliSerialPrint("Error: can't read sector count");
return 0;
}
cliSerialPrint("SD card has %u sectors", sectorCount);
// read last 16 sectors one sector at the time
cliSerialPrint("Starting single sector read test, reading 16 sectors one by one");
uint8_t * buffer = (uint8_t*) malloc(512);
if (!buffer) {
cliSerialPrint("Not enough memory");
return 0;
}
for (uint32_t s = sectorCount - 16; s<sectorCount; ++s) {
DRESULT res = __disk_read(0, buffer, s, 1);
if (res != RES_OK) {
cliSerialPrint("sector %d read FAILED, err: %d", s, res);
}
else {
cliSerialPrint("sector %d read OK", s);
}
}
free(buffer);
cliSerialCrlf();
// read last 16 sectors, two sectors at the time with a multi-block read
buffer = (uint8_t *) malloc(512*2);
if (!buffer) {
cliSerialPrint("Not enough memory");
return 0;
}
cliSerialPrint("Starting multiple sector read test, reading two sectors at the time");
for (uint32_t s = sectorCount - 16; s<sectorCount; s+=2) {
DRESULT res = __disk_read(0, buffer, s, 2);
if (res != RES_OK) {
cliSerialPrint("sector %d-%d read FAILED, err: %d", s, s+1, res);
}
else {
cliSerialPrint("sector %d-%d read OK", s, s+1);
}
}
free(buffer);
cliSerialCrlf();
// read last 16 sectors, all sectors with single multi-block read
buffer = (uint8_t*) malloc(512*16);
if (!buffer) {
cliSerialPrint("Not enough memory");
return 0;
}
cliSerialPrint("Starting multiple sector read test, reading 16 sectors at the time");
DRESULT res = __disk_read(0, buffer, sectorCount-16, 16);
if (res != RES_OK) {
cliSerialPrint("sector %d-%d read FAILED, err: %d", sectorCount-16, sectorCount-1, res);
}
else {
cliSerialPrint("sector %d-%d read OK", sectorCount-16, sectorCount-1);
}
free(buffer);
cliSerialCrlf();
return 0;
}
int cliTestNew()
{
char * tmp = 0;
cliSerialPrint("Allocating 1kB with new()");
RTOS_WAIT_MS(200);
tmp = new char[1024];
if (tmp) {
cliSerialPrint("\tsuccess");
delete[] tmp;
tmp = 0;
}
else {
cliSerialPrint("\tFAILURE");
}
cliSerialPrint("Allocating 10MB with (std::nothrow) new()");
RTOS_WAIT_MS(200);
tmp = new (std::nothrow) char[1024*1024*10];
if (tmp) {
cliSerialPrint("\tFAILURE, tmp = %p", tmp);
delete[] tmp;
tmp = 0;
}
else {
cliSerialPrint("\tsuccess, allocaton failed, tmp = 0");
}
cliSerialPrint("Allocating 10MB with new()");
RTOS_WAIT_MS(200);
tmp = new char[1024*1024*10];
if (tmp) {
cliSerialPrint("\tFAILURE, tmp = %p", tmp);
delete[] tmp;
tmp = 0;
}
else {
cliSerialPrint("\tsuccess, allocaton failed, tmp = 0");
}
cliSerialPrint("Test finished");
return 0;
}
#if defined(COLORLCD)
extern bool perMainEnabled;
typedef void (*timedTestFunc_t)(void);
#if defined(DEBUG_LCD)
void testDrawSolidFilledRectangle()
{
lcdDrawFilledRect(0, 0, LCD_W, LCD_H, SOLID, COLOR_THEME_SECONDARY3);
}
void testDrawFilledRectangle()
{
lcdDrawFilledRect(0, 0, LCD_W, LCD_H, DOTTED, COLOR_THEME_SECONDARY3);
}
void testDrawSolidFilledRoundedRectangle()
{
lcdDrawFilledRect(0, 0, LCD_W / 2, LCD_H / 2, SOLID,
ROUND | COLOR_THEME_SECONDARY3);
}
void testDrawSolidHorizontalLine1() { lcdDrawSolidHorizontalLine(0, 0, 1, 0); }
void testDrawSolidHorizontalLine2()
{
lcdDrawSolidHorizontalLine(0, 0, LCD_W, 0);
}
void testDrawSolidVerticalLine1() { lcdDrawSolidVerticalLine(0, 0, 1, 0); }
void testDrawSolidVerticalLine2() { lcdDrawSolidVerticalLine(0, 0, LCD_H, 0); }
void testDrawDiagonalLine()
{
lcdDrawLine(0, 0, LCD_W, LCD_H, SOLID, COLOR_THEME_SECONDARY1);
}
void testEmpty() {}
void testDrawRect()
{
lcdDrawRect(0, 0, LCD_W, LCD_H, 2, SOLID, COLOR_THEME_SECONDARY1);
}
void testDrawText()
{
lcdDrawText(0, LCD_H / 2, "The quick brown fox jumps over the lazy dog",
COLOR_THEME_SECONDARY1);
}
void testDrawTextVertical()
{
lcdDrawText(30, LCD_H, "The quick brown fox ",
COLOR_THEME_SECONDARY1 | VERTICAL);
}
void testClear() { lcdClear(); }
#define RUN_GRAPHICS_TEST(name, runtime) \
runTimedFunctionTest(name, #name, runtime, 100)
float runTimedFunctionTest(timedTestFunc_t func, const char *name,
uint32_t runtime, uint16_t step)
{
const uint32_t start = RTOS_GET_MS();
uint32_t noRuns = 0;
uint32_t actualRuntime = 0;
while ((actualRuntime = RTOS_GET_MS() - start) < runtime) {
for (uint16_t n = 0; n < step; n++) {
func();
}
lcdRefresh();
noRuns += step;
}
const float result = (noRuns * 500.0f) / (float)actualRuntime; // runs/second
cliSerialPrint("Test %s speed: %lu.%02u, (%lu runs in %lu ms)", name,
uint32_t(result), uint16_t((result - uint32_t(result)) * 100.0f),
noRuns, actualRuntime);
RTOS_WAIT_MS(200);
return result;
}
int cliTestGraphics()
{
cliSerialPrint("Starting graphics performance test...");
RTOS_WAIT_MS(200);
watchdogSuspend(6000 /*60s*/);
if (pulsesStarted()) {
pausePulses();
}
mixerTaskStop();
perMainEnabled = false;
float result = 0;
RUN_GRAPHICS_TEST(testEmpty, 1000);
// result += RUN_GRAPHICS_TEST(testDrawSolidHorizontalLine1, 1000);
result += RUN_GRAPHICS_TEST(testDrawSolidHorizontalLine2, 1000);
// result += RUN_GRAPHICS_TEST(testDrawSolidVerticalLine1, 1000);
result += RUN_GRAPHICS_TEST(testDrawSolidVerticalLine2, 1000);
result += RUN_GRAPHICS_TEST(testDrawDiagonalLine, 1000);
result += RUN_GRAPHICS_TEST(testDrawSolidFilledRectangle, 1000);
result += RUN_GRAPHICS_TEST(testDrawSolidFilledRoundedRectangle, 1000);
result += RUN_GRAPHICS_TEST(testDrawRect, 1000);
result += RUN_GRAPHICS_TEST(testDrawFilledRectangle, 1000);
result += RUN_GRAPHICS_TEST(testDrawBlackOverlay, 1000);
result += RUN_GRAPHICS_TEST(testDrawText, 1000);
result += RUN_GRAPHICS_TEST(testDrawTextVertical, 1000);
result += RUN_GRAPHICS_TEST(testClear, 1000);
cliSerialPrint("Total speed: %lu.%02u", uint32_t(result),
uint16_t((result - uint32_t(result)) * 100.0f));
perMainEnabled = true;
if (pulsesStarted()) {
resumePulses();
}
mixerTaskStart();
watchdogSuspend(0);
return 0;
}
#endif
#if defined(DEBUG_RAM)
void memoryRead(const uint8_t *src, uint32_t size)
{
// uint8_t data;
while (size--) {
/*data =*/*(const uint8_t volatile *)src;
++src;
}
}
void memoryRead(const uint32_t *src, uint32_t size)
{
while (size--) {
*(const uint32_t volatile *)src;
++src;
}
}
uint32_t *testbuff[100];
void memoryCopy(uint8_t *dest, const uint8_t *src, uint32_t size)
{
while (size--) {
*dest = *src;
++src;
++dest;
}
}
void memoryCopy(uint32_t *dest, const uint32_t *src, uint32_t size)
{
while (size--) {
*dest = *src;
++src;
++dest;
}
}
#define MEMORY_SPEED_BLOCK_SIZE (4 * 1024)
void testMemoryReadFrom_RAM_8bit()
{
memoryRead((const uint8_t *)cliLastLine, MEMORY_SPEED_BLOCK_SIZE);
}
void testMemoryReadFrom_RAM_32bit()
{
memoryRead((const uint32_t *)0x20000000, MEMORY_SPEED_BLOCK_SIZE / 4);
}
void testMemoryReadFrom_SDRAM_8bit()
{
memoryRead((const uint8_t *)0xD0000000, MEMORY_SPEED_BLOCK_SIZE);
}
void testMemoryReadFrom_SDRAM_32bit()
{
memoryRead((const uint32_t *)0xD0000000, MEMORY_SPEED_BLOCK_SIZE / 4);
}
extern uint8_t *LCD_FIRST_FRAME_BUFFER;
extern uint8_t *LCD_SECOND_FRAME_BUFFER;
void testMemoryCopyFrom_RAM_to_SDRAM_32bit()
{
memoryCopy((uint32_t *)LCD_FIRST_FRAME_BUFFER, (const uint32_t *)cliLastLine,
MEMORY_SPEED_BLOCK_SIZE / 4);
}
void testMemoryCopyFrom_RAM_to_SDRAM_8bit()
{
memoryCopy((uint8_t *)LCD_FIRST_FRAME_BUFFER, (const uint8_t *)cliLastLine,
MEMORY_SPEED_BLOCK_SIZE);
}
void testMemoryCopyFrom_SDRAM_to_SDRAM_32bit()
{
memoryCopy((uint32_t *)LCD_FIRST_FRAME_BUFFER,
(const uint32_t *)LCD_SECOND_FRAME_BUFFER,
MEMORY_SPEED_BLOCK_SIZE / 4);
}
void testMemoryCopyFrom_SDRAM_to_SDRAM_8bit()
{
memoryCopy((uint8_t *)LCD_FIRST_FRAME_BUFFER,
(const uint8_t *)LCD_SECOND_FRAME_BUFFER, MEMORY_SPEED_BLOCK_SIZE);
}
#define RUN_MEMORY_TEST(name, runtime) \
runTimedFunctionTest(name, #name, runtime, 100)
int cliTestMemorySpeed()
{
cliSerialPrint("Starting memory speed test...");
RTOS_WAIT_MS(200);
watchdogSuspend(6000 /*60s*/);
if (pulsesStarted()) {
pausePulses();
}
mixerTaskStop();
perMainEnabled = false;
float result = 0;
result += RUN_MEMORY_TEST(testMemoryReadFrom_RAM_8bit, 200);
result += RUN_MEMORY_TEST(testMemoryReadFrom_RAM_32bit, 200);
result += RUN_MEMORY_TEST(testMemoryReadFrom_SDRAM_8bit, 200);
result += RUN_MEMORY_TEST(testMemoryReadFrom_SDRAM_32bit, 200);
result += RUN_MEMORY_TEST(testMemoryCopyFrom_RAM_to_SDRAM_8bit, 200);
result += RUN_MEMORY_TEST(testMemoryCopyFrom_RAM_to_SDRAM_32bit, 200);
result += RUN_MEMORY_TEST(testMemoryCopyFrom_SDRAM_to_SDRAM_8bit, 200);
result += RUN_MEMORY_TEST(testMemoryCopyFrom_SDRAM_to_SDRAM_32bit, 200);
LTDC_Cmd(DISABLE);
cliSerialPrint("Disabling LCD...");
RTOS_WAIT_MS(200);
result += RUN_MEMORY_TEST(testMemoryReadFrom_RAM_8bit, 200);
result += RUN_MEMORY_TEST(testMemoryReadFrom_RAM_32bit, 200);
result += RUN_MEMORY_TEST(testMemoryReadFrom_SDRAM_8bit, 200);
result += RUN_MEMORY_TEST(testMemoryReadFrom_SDRAM_32bit, 200);
result += RUN_MEMORY_TEST(testMemoryCopyFrom_RAM_to_SDRAM_8bit, 200);
result += RUN_MEMORY_TEST(testMemoryCopyFrom_RAM_to_SDRAM_32bit, 200);
result += RUN_MEMORY_TEST(testMemoryCopyFrom_SDRAM_to_SDRAM_8bit, 200);
result += RUN_MEMORY_TEST(testMemoryCopyFrom_SDRAM_to_SDRAM_32bit, 200);
cliSerialPrint("Total speed: %lu.%02u", uint32_t(result),
uint16_t((result - uint32_t(result)) * 100.0f));
LTDC_Cmd(ENABLE);
perMainEnabled = true;
if (pulsesStarted()) {
resumePulses();
}
mixerTaskStart();
watchdogSuspend(0);
return 0;
}
#endif
#if defined(DEBUG_MODELSLIST)
#include "modelslist.h"
using std::list;
int cliTestModelsList()
{
ModelsList modList;
modList.load();
int count = 0;
cliSerialPrint("Starting fetching RF data 100x...");
const uint32_t start = RTOS_GET_MS();
const list<ModelsCategory *> &cats = modList.getCategories();
while (1) {
for (list<ModelsCategory *>::const_iterator cat_it = cats.begin();
cat_it != cats.end(); ++cat_it) {
for (ModelsCategory::iterator mod_it = (*cat_it)->begin();
mod_it != (*cat_it)->end(); mod_it++) {
if (!(*mod_it)->fetchRfData()) {
cliSerialPrint("Error while fetching RF data...");
return 0;
}
if (++count >= 100) goto done;
}
}
}
done:
cliSerialPrint("Done fetching %ix RF data: %lu ms", count,
(RTOS_GET_MS() - start));
return 0;
}
#endif
#endif // #if defined(COLORLCD)
#if defined(DEBUG)
int cliTest(const char ** argv)
{
if (!strcmp(argv[1], "new")) {
return cliTestNew();
}
#if defined(COLORLCD)
#if defined(DEBUG_LCD)
else if (!strcmp(argv[1], "graphics")) {
return cliTestGraphics();
}
#endif
#if defined(DEBUG_RAM)
else if (!strcmp(argv[1], "memspd")) {
return cliTestMemorySpeed();
}
#endif
#if defined(DEBUG_MODELSLIST)
else if (!strcmp(argv[1], "modelslist")) {
return cliTestModelsList();
}
#endif
#endif
else {
cliSerialPrint("%s: Invalid argument \"%s\"", argv[0], argv[1]);
}
return 0;
}
int cliTrace(const char ** argv)
{
if (!strcmp(argv[1], "on")) {
cliEnableDbg();
cliTracesEnabled = true;
}
else if (!strcmp(argv[1], "off")) {
cliTracesEnabled = false;
cliDisableDbg();
}
else {
cliSerialPrint("%s: Invalid argument \"%s\"", argv[0], argv[1]);
}
return 0;
}
int cliStackInfo(const char ** argv)
{
cliSerialPrint("[MAIN] %d available / %d bytes", mainStackAvailable()*4, stackSize()*4);
cliSerialPrint("[MENUS] %d available / %d bytes", menusStack.available()*4, menusStack.size());
cliSerialPrint("[MIXER] %d available / %d bytes", mixerStack.available()*4, mixerStack.size());
cliSerialPrint("[AUDIO] %d available / %d bytes", audioStack.available()*4, audioStack.size());
cliSerialPrint("[CLI] %d available / %d bytes", cliStack.available()*4, cliStack.size());
return 0;
}
extern int _end;
extern int _heap_end;
extern unsigned char *heap;
int cliMemoryInfo(const char ** argv)
{
// struct mallinfo {
// int arena; /* total space allocated from system */
// int ordblks; /* number of non-inuse chunks */
// int smblks; /* unused -- always zero */
// int hblks; /* number of mmapped regions */
// int hblkhd; /* total space in mmapped regions */
// int usmblks; /* unused -- always zero */
// int fsmblks; /* unused -- always zero */
// int uordblks; /* total allocated space */
// int fordblks; /* total non-inuse space */
// int keepcost; /* top-most, releasable (via malloc_trim) space */
// };
struct mallinfo info = mallinfo();
cliSerialPrint("mallinfo:");
cliSerialPrint("\tarena %d bytes", info.arena);
cliSerialPrint("\tordblks %d bytes", info.ordblks);
cliSerialPrint("\tuordblks %d bytes", info.uordblks);
cliSerialPrint("\tfordblks %d bytes", info.fordblks);
cliSerialPrint("\tkeepcost %d bytes", info.keepcost);
cliSerialPrint("\nHeap:");
cliSerialPrint("\tstart %p", (unsigned char *)&_end);
cliSerialPrint("\tend %p", (unsigned char *)&_heap_end);
cliSerialPrint("\tcurr %p", heap);
cliSerialPrint("\tused %d bytes", (int)(heap - (unsigned char *)&_end));
cliSerialPrint("\tfree %d bytes", (int)((unsigned char *)&_heap_end - heap));
#if defined(LUA)
cliSerialPrint("\nLua:");
uint32_t s = luaGetMemUsed(lsScripts);
cliSerialPrint("\tScripts %u", s);
#if defined(COLORLCD)
uint32_t w = luaGetMemUsed(lsWidgets);
uint32_t e = luaExtraMemoryUsage;
cliSerialPrint("\tWidgets %u", w);
cliSerialPrint("\tExtra %u", e);
cliSerialPrint("------------");
cliSerialPrint("\tTotal %u", s + w + e);
#endif
#endif
return 0;
}
#endif
int cliReboot(const char ** argv)
{
#if !defined(SIMU)
if (!strcmp(argv[1], "wdt")) {
// do a user requested watchdog test by pausing mixer thread
pulsesStop();
}
else {
NVIC_SystemReset();
}
#endif
return 0;
}
const MemArea memAreas[] = {
{ "RCC", RCC, sizeof(RCC_TypeDef) },
{ "GPIOA", GPIOA, sizeof(GPIO_TypeDef) },
{ "GPIOB", GPIOB, sizeof(GPIO_TypeDef) },
{ "GPIOC", GPIOC, sizeof(GPIO_TypeDef) },
{ "GPIOD", GPIOD, sizeof(GPIO_TypeDef) },
{ "GPIOE", GPIOE, sizeof(GPIO_TypeDef) },
{ "GPIOF", GPIOF, sizeof(GPIO_TypeDef) },
{ "GPIOG", GPIOG, sizeof(GPIO_TypeDef) },
{ "USART1", USART1, sizeof(USART_TypeDef) },
{ "USART2", USART2, sizeof(USART_TypeDef) },
{ "USART3", USART3, sizeof(USART_TypeDef) },
{ nullptr, nullptr, 0 },
};
int cliSet(const char **argv)
{
if (!strcmp(argv[1], "rtc")) {
struct gtm t;
int year, month, day, hour, minute, second;
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) {
t.tm_year = year - TM_YEAR_BASE;
t.tm_mon = month - 1;
t.tm_mday = day;
t.tm_hour = hour;
t.tm_min = minute;
t.tm_sec = second;
// update local timestamp and get wday calculated
g_rtcTime = gmktime(&t);
rtcSetTime(&t);
} else {
cliSerialPrint("%s: Invalid arguments \"%s\" \"%s\"", argv[0], argv[1],
argv[2]);
return -1;
}
}
#if !defined(SOFTWARE_VOLUME)
else if (!strcmp(argv[1], "volume")) {
int level = 0;
if (toInt(argv, 2, &level) > 0) {
setVolume(level);
} else {
cliSerialPrint("%s: Invalid argument \"%s\" \"%s\"", argv[0], argv[1],
argv[2]);
return -1;
}
}
#endif
else if (!strcmp(argv[1], "rfmod")) {
int module = 0;
if (toInt(argv, 2, &module) < 0) {
cliSerialPrint("%s: invalid module argument '%s'", argv[0], argv[2]);
return -1;
}
if (!strcmp(argv[3], "power")) {
if (!strcmp("on", argv[4])) {
modulePortSetPower(module, true);
} else if (!strcmp("off", argv[4])) {
modulePortSetPower(module, false);
} else {
cliSerialPrint("%s: invalid power argument '%s'", argv[0], argv[4]);
return -1;
}
cliSerialPrint("%s: rfmod %d power %s", argv[0], module, argv[4]);
}
#if defined(INTMODULE_BOOTCMD_GPIO)
else if (!strcmp(argv[3], "bootpin")) {
int level = 0;
if (toInt(argv, 4, &level) < 0) {
cliSerialPrint("%s: invalid bootpin argument '%s'", argv[0], argv[4]);
return -1;
}
if (module == 0) {
if (level) {
GPIO_SetBits(INTMODULE_BOOTCMD_GPIO, INTMODULE_BOOTCMD_GPIO_PIN);
cliSerialPrint("%s: bootpin set", argv[0]);
} else {
GPIO_ResetBits(INTMODULE_BOOTCMD_GPIO, INTMODULE_BOOTCMD_GPIO_PIN);
cliSerialPrint("%s: bootpin reset", argv[0]);
}
}
}
#endif
else {
if (strlen(argv[2]) == 0) {
cliSerialPrint("%s: missing rfmod arguments", argv[0]);
} else {
cliSerialPrint("%s: invalid rfmod argument '%s'", argv[0], argv[3]);
return -1;
}
}
}
else if (!strcmp(argv[1], "pulses")) {
int level = 0;
if (toInt(argv, 2, &level) < 0) {
cliSerialPrint("%s: invalid level argument '%s'", argv[0], argv[2]);
return -1;
}
if (level) {
cliSerialPrint("%s: pulses start", argv[0]);
pulsesStart();
} else {
cliSerialPrint("%s: pulses stop", argv[0]);
pulsesStop();
}
}
return 0;
}
#if defined(ENABLE_SERIAL_PASSTHROUGH)
static etx_module_state_t *spInternalModuleState = nullptr;
static void spInternalModuleTx(uint8_t* buf, uint32_t len)
{
auto drv = modulePortGetSerialDrv(spInternalModuleState->tx);
auto ctx = modulePortGetCtx(spInternalModuleState->tx);
while (len > 0) {
drv->sendByte(ctx, *(buf++));
len--;
}
}
static const etx_serial_init spIntmoduleSerialInitParams = {
.baudrate = 0,
.encoding = ETX_Encoding_8N1,
.direction = ETX_Dir_TX_RX,
.polarity = ETX_Pol_Normal,
};
// TODO: use proper method instead
extern bool cdcConnected;
extern uint32_t usbSerialBaudRate(void*);
int cliSerialPassthrough(const char **argv)
{
const char* port_type = argv[1];
const char* port_num = argv[2];
// 3rd argument (baudrate is optional)
if (!port_type || !port_num) {
cliSerialPrint("%s: missing argument", argv[0]);
return -1;
}
int port_n = 0;
int err = toInt(argv, 2, &port_n);
if (err == -1) return err;
if (err == 0) {
cliSerialPrint("%s: missing port #", argv[0]);
return -1;
}
int baudrate = 0;
err = toInt(argv, 3, &baudrate);
if (err <= 0) {
// use current baudrate
baudrate = cliGetBaudRate();
if (!baudrate) {
// default to 115200
baudrate = 115200;
cliSerialPrint("%s: baudrate is 0, default to 115200", argv[0]);
}
}
if (!strcmp("rfmod", port_type)) {
if (port_n >= NUM_MODULES
// only internal module supported for now
&& port_n != INTERNAL_MODULE) {
cliSerialPrint("%s: invalid port # '%s'", port_num);
return -1;
}
// stop pulses
watchdogSuspend(200/*2s*/);
pulsesStop();
// suspend RTOS scheduler
vTaskSuspendAll();
#if defined(HARDWARE_INTERNAL_MODULE)
if (port_n == INTERNAL_MODULE) {
// setup serial com
// TODO: '8n1' param
etx_serial_init params(spIntmoduleSerialInitParams);
params.baudrate = baudrate;
spInternalModuleState = modulePortInitSerial(port_n, ETX_MOD_PORT_UART,
&params);
auto drv = modulePortGetSerialDrv(spInternalModuleState->rx);
auto ctx = modulePortGetCtx(spInternalModuleState->rx);
// backup and swap CLI input
auto backupCB = cliReceiveCallBack;
cliReceiveCallBack = spInternalModuleTx;
// loop until cable disconnected
while (cdcConnected) {
uint32_t cli_br = cliGetBaudRate();
if (cli_br && (cli_br != (uint32_t)baudrate)) {
baudrate = cli_br;
drv->setBaudrate(ctx, baudrate);
}
uint8_t data;
if (drv->getByte(ctx, &data) > 0) {
uint8_t timeout = 10; // 10 ms
while(!usbSerialFreeSpace() && (timeout > 0)) {
delay_ms(1);
timeout--;
}
cliSerialPutc(data);
}
// keep us up & running
WDG_RESET();
}
// restore callsbacks
cliReceiveCallBack = backupCB;
// and stop module
modulePortDeInit(spInternalModuleState);
spInternalModuleState = nullptr;
// power off the module and wait for a bit
modulePortSetPower(port_n, false);
delay_ms(200);
}
#endif
// TODO:
// - external module (S.PORT?)
watchdogSuspend(200/*2s*/);
pulsesStart();
// suspend RTOS scheduler
xTaskResumeAll();
} else {
cliSerialPrint("%s: invalid port type '%s'", port_type);
return -1;
}
return 0;
}
#endif
#if defined(DEBUG_INTERRUPTS)
void printInterrupts()
{
__disable_irq();
struct InterruptCounters ic = interruptCounters;
memset(&interruptCounters, 0, sizeof(interruptCounters));
interruptCounters.resetTime = get_tmr10ms();
__enable_irq();
cliSerialPrint("Interrupts count in the last %u ms:", (get_tmr10ms() - ic.resetTime) * 10);
for(int n = 0; n < INT_LAST; n++) {
cliSerialPrint("%s: %u", interruptNames[n], ic.cnt[n]);
}
}
#endif //#if defined(DEBUG_INTERRUPTS)
#if defined(DEBUG_TIMERS)
void printDebugTime(uint32_t time)
{
if (time >= 30000) {
cliSerialPrintf("%dms", time/1000);
}
else {
cliSerialPrintf("%d.%03dms", time/1000, time%1000);
}
}
void printDebugTimer(const char * name, DebugTimer & timer)
{
cliSerialPrintf("%s: ", name);
printDebugTime( timer.getMin());
cliSerialPrintf(" - ");
printDebugTime(timer.getMax());
cliSerialCrlf();
timer.reset();
}
void printDebugTimers()
{
for(int n = 0; n < DEBUG_TIMERS_COUNT; n++) {
printDebugTimer(debugTimerNames[n], debugTimers[n]);
}
}
#endif
#if defined(DEBUG_AUDIO)
void printAudioVars()
{
for (int n = 0; n < AUDIO_BUFFER_COUNT; n++) {
cliSerialPrint("Audio Buffer %d: size: %u, ", n,
(uint32_t)audioBuffers[n].size);
dump((uint8_t *)audioBuffers[n].data, 32);
}
cliSerialPrint("fragments:");
for (int n = 0; n < AUDIO_QUEUE_LENGTH; n++) {
cliSerialPrint("%d: type %u: id: %u, repeat: %u, ", n,
(uint32_t)audioQueue.fragmentsFifo.fragments[n].type,
(uint32_t)audioQueue.fragmentsFifo.fragments[n].id,
(uint32_t)audioQueue.fragmentsFifo.fragments[n].repeat);
if (audioQueue.fragmentsFifo.fragments[n].type == FRAGMENT_FILE) {
cliSerialPrint(" file: %s", audioQueue.fragmentsFifo.fragments[n].file);
}
}
cliSerialPrint("FragmentFifo: ridx: %d, widx: %d",
audioQueue.fragmentsFifo.ridx, audioQueue.fragmentsFifo.widx);
cliSerialPrint("audioQueue: readIdx: %d, writeIdx: %d, full: %d",
audioQueue.buffersFifo.readIdx, audioQueue.buffersFifo.writeIdx,
audioQueue.buffersFifo.bufferFull);
cliSerialPrint("normalContext: %u",
(uint32_t)audioQueue.normalContext.fragment.type);
}
#endif
#if defined(DEBUG)
#include "hal/switch_driver.h"
int cliDisplay(const char ** argv)
{
long long int address = 0;
for (const MemArea * area = memAreas; area->name != nullptr; area++) {
if (!strcmp(area->name, argv[1])) {
dump((uint8_t *)area->start, area->size);
return 0;
}
}
if (!strcmp(argv[1], "keys")) {
for (int i = 0; i <= MAX_KEYS; i++) {
if (keysGetSupported() & (1 << i)) {
cliSerialPrint("[Key %s] = %s",
keysGetLabel((EnumKeys)i),
keysGetState(i) ? "on" : "off");
}
}
for (int i = 0; i < keysGetMaxTrims(); i++) {
cliSerialPrint("[Trim %s] = %s", getTrimLabel(i),
keysGetTrimState(i) ? "on" : "off");
}
for (int i = 0; i < switchGetMaxSwitches(); i++) {
if (SWITCH_EXISTS(i)) {
static const char * const SWITCH_POSITIONS[] = { "up", "mid", "down" };
auto pos = switchGetPosition(i);
cliSerialPrint("[%s] = %s", switchGetName(i), SWITCH_POSITIONS[pos]);
}
}
}
else if (!strcmp(argv[1], "adc")) {
for (int i = 0; i < adcGetMaxInputs(ADC_INPUT_ALL); i++) {
cliSerialPrint("adc[%d] = %04X", i, getAnalogValue(i));
}
}
else if (!strcmp(argv[1], "outputs")) {
for (int i = 0; i < MAX_OUTPUT_CHANNELS; i++) {
cliSerialPrint("outputs[%d] = %04d", i, (int)channelOutputs[i]);
}
}
else if (!strcmp(argv[1], "rtc")) {
struct gtm utm;
gettime(&utm);
cliSerialPrint("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);
}
#if !defined(SOFTWARE_VOLUME)
else if (!strcmp(argv[1], "volume")) {
cliSerialPrint("volume = %d", getVolume());
}
#endif
else if (!strcmp(argv[1], "uid")) {
char str[LEN_CPU_UID+1];
getCPUUniqueID(str);
cliSerialPrint("uid = %s", str);
}
else if (!strcmp(argv[1], "tim")) {
int timerNumber;
if (toInt(argv, 2, &timerNumber) > 0) {
TIM_TypeDef * tim = TIM1;
switch (timerNumber) {
case 1:
tim = TIM1;
break;
case 2:
tim = TIM2;
break;
case 8:
tim = TIM8;
break;
case 13:
tim = TIM13;
break;
default:
return 0;
}
cliSerialPrint("TIM%d", timerNumber);
cliSerialPrint(" CR1 0x%x", tim->CR1);
cliSerialPrint(" CR2 0x%x", tim->CR2);
cliSerialPrint(" DIER 0x%x", tim->DIER);
cliSerialPrint(" SR 0x%x", tim->SR);
cliSerialPrint(" EGR 0x%x", tim->EGR);
cliSerialPrint(" CCMR1 0x%x", tim->CCMR1);
cliSerialPrint(" CCMR2 0x%x", tim->CCMR2);
cliSerialPrint(" CNT 0x%x", tim->CNT);
cliSerialPrint(" ARR 0x%x", tim->ARR);
cliSerialPrint(" PSC 0x%x", tim->PSC);
cliSerialPrint(" CCER 0x%x", tim->CCER);
cliSerialPrint(" CCR1 0x%x", tim->CCR1);
cliSerialPrint(" CCR2 0x%x", tim->CCR2);
cliSerialPrint(" CCR3 0x%x", tim->CCR3);
cliSerialPrint(" CCR4 0x%x", tim->CCR4);
}
}
else if (!strcmp(argv[1], "dma")) {
cliSerialPrint("DMA1_Stream7");
cliSerialPrint(" CR 0x%x", DMA1_Stream7->CR);
}
#if defined(DEBUG_INTERRUPTS)
else if (!strcmp(argv[1], "int")) {
printInterrupts();
}
#endif
#if defined(DEBUG_TIMERS)
else if (!strcmp(argv[1], "dt")) {
printDebugTimers();
}
#endif
#if defined(DEBUG_AUDIO)
else if (!strcmp(argv[1], "audio")) {
printAudioVars();
}
#endif
#if defined(DISK_CACHE)
else if (!strcmp(argv[1], "dc")) {
DiskCacheStats stats = diskCache.getStats();
uint32_t hitRate = diskCache.getHitRate();
cliSerialPrint("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);
}
#endif
else if (toLongLongInt(argv, 1, &address) > 0) {
int size = 256;
if (toInt(argv, 2, &size) >= 0) {
dump((uint8_t *)address, size);
}
}
return 0;
}
int cliDebugVars(const char ** argv)
{
#if defined(PCBHORUS)
extern uint32_t ioMutexReq, ioMutexRel;
extern uint32_t sdReadRetries;
cliSerialPrint("ioMutexReq=%d", ioMutexReq);
cliSerialPrint("ioMutexRel=%d", ioMutexRel);
cliSerialPrint("sdReadRetries=%d", sdReadRetries);
#if defined(INTERNAL_MODULE_PXX2) && defined(ACCESS_DENIED) && !defined(SIMU)
extern volatile int32_t authenticateFrames;
cliSerialPrint("authenticateFrames=%d", authenticateFrames);
#endif
#elif defined(PCBTARANIS)
//cliSerialPrint("telemetryErrors=%d", telemetryErrors);
#endif
return 0;
}
#endif
#if defined(DEBUG)
int cliRepeat(const char ** argv)
{
int interval = 0;
int counter = 0;
if (toInt(argv, 1, &interval) > 0 && argv[2]) {
interval *= 50;
counter = interval;
uint8_t c;
const TickType_t xTimeout = 20 / RTOS_MS_PER_TICK;
while (!xStreamBufferReceive(cliRxBuffer, &c, 1, xTimeout)
|| !(c == '\r' || c == '\n' || c == ' ')) {
if (++counter >= interval) {
cliExecCommand(&argv[2]);
counter = 0;
}
}
}
else {
cliSerialPrint("%s: Invalid arguments", argv[0]);
}
return 0;
}
#endif
#if defined(JITTER_MEASURE)
int cliShowJitter(const char ** argv)
{
cliSerialPrint( "# anaIn rawJ avgJ");
for (int i = 0; i < MAX_ANALOG_INPUTS; i++) {
cliSerialPrint("A%02d %04X %04X %3d %3d", i, getAnalogValue(i), anaIn(i),
rawJitter[i].get(), avgJitter[i].get());
if (i >= MAX_STICKS && IS_POT_MULTIPOS(i - MAX_STICKS)) {
StepsCalibData *calib = (StepsCalibData *)&g_eeGeneral.calib[i];
for (int j = 0; j < calib->count; j++) {
cliSerialPrint(" s%d %04X", j, calib->steps[j]);
}
}
}
return 0;
}
#endif
#if defined(INTERNAL_GPS)
int cliGps(const char ** argv)
{
if (argv[1][0] == '$') {
// send command to GPS
gpsSendFrame(argv[1]);
}
#if defined(DEBUG)
else if (!strcmp(argv[1], "trace")) {
gpsTraceEnabled = !gpsTraceEnabled;
}
#endif
else {
cliSerialPrint("%s: Invalid arguments", argv[0]);
}
return 0;
}
#endif
#if defined(SPACEMOUSE)
int cliSpaceMouse(const char ** argv)
{
#if defined(DEBUG)
if (!strcmp(argv[1], "trace")) {
spacemouseTraceEnabled = !spacemouseTraceEnabled;
} else
#endif
if (!strcmp(argv[1], "poll")) {
spacemousePoll();
} else if (!strcmp(argv[1], "tare")) {
spacemouseTare();
} else if (!strcmp(argv[1], "startstreaming")) {
spacemouseStartStreaming();
} else if (!strcmp(argv[1], "stopstreaming")) {
spacemouseStopStreaming();
}
return 0;
}
#endif
#if defined(BLUETOOTH)
int cliBlueTooth(const char ** argv)
{
int baudrate = 0;
if (!strncmp(argv[1], "AT", 2) || !strncmp(argv[1], "TTM", 3)) {
bluetooth.writeString(argv[1]);
char * line = bluetooth.readline();
cliSerialPrint("<BT %s", line);
}
else if (toInt(argv, 1, &baudrate) > 0) {
if (baudrate > 0) {
bluetoothInit(baudrate, true);
char * line = bluetooth.readline();
cliSerialPrint("<BT %s", line);
}
else {
bluetoothDisable();
cliSerialPrint("BT turned off");
}
}
else {
cliSerialPrint("%s: Invalid arguments", argv[0]);
}
return 0;
}
#endif
#if defined(ACCESS_DENIED) && defined(DEBUG_CRYPT)
extern "C" {
#include "AccessDenied/access_denied.h"
}
static uint8_t cryptInput[16];
static uint8_t cryptOutput[16];
static void testAccessDenied(uint32_t runs)
{
while(runs--)
access_denied(0, cryptInput, cryptOutput);
}
int cliCrypt(const char ** argv)
{
if (argv[1][0] == '\0')
return -1;
strncpy((char*)cryptInput, argv[1], sizeof(cryptInput));
cliSerialPrint("Input:");
dumpBody(cryptInput, sizeof(cryptInput));
dumpEnd();
watchdogSuspend(2000/* 20s */);
if (pulsesStarted()) {
pausePulses();
}
mixerTaskStop();
perMainEnabled = false;
uint32_t startMs = RTOS_GET_MS();
testAccessDenied(100000);
cliSerialPrintf("access_denied: %f us/run\r\n", (RTOS_GET_MS() - startMs)*1000.0f / 100000.0f);
cliSerialPrint("Decrypted (SW):");
dumpBody(cryptOutput, sizeof(cryptOutput));
dumpEnd();
perMainEnabled = true;
if (pulsesStarted()) {
resumePulses();
}
mixerTaskStart();
watchdogSuspend(0);
return 0;
}
#endif
#if defined(HARDWARE_TOUCH) && !defined(PCBNV14)
// from tp_gt911.cpp
extern uint8_t tp_gt911_cfgVer;
int cliResetGT911(const char** argv)
{
(void)argv;
if (!touchGT911Flag) {
cliSerialPrint("GT911 not detected: exit\n");
return 0;
}
// stop pulses & suspend RTOS scheduler
watchdogSuspend(200/*2s*/);
pulsesStop();
vTaskSuspendAll();
// reset touch controller
touchPanelDeInit();
cliSerialPrintf("GT911: old config version is %u\n", tp_gt911_cfgVer);
tp_gt911_cfgVer = 0;
touchPanelInit();
cliSerialPrintf("GT911: new config version is %u\n", tp_gt911_cfgVer);
// restart pulses & RTOS scheduler
pulsesStart();
xTaskResumeAll();
return 0;
}
#endif
const CliCommand cliCommands[] = {
{ "beep", cliBeep, "[<frequency>] [<duration>]" },
{ "ls", cliLs, "<directory>" },
{ "read", cliRead, "<filename>" },
{ "readsd", cliReadSD, "<start sector> <sectors count> <read buffer size (sectors)>" },
{ "testsd", cliTestSD, "" },
{ "play", cliPlay, "<filename>" },
{ "reboot", cliReboot, "[wdt]" },
{ "set", cliSet, "<what> <value>" },
#if defined(ENABLE_SERIAL_PASSTHROUGH)
{ "serialpassthrough", cliSerialPassthrough, "<port type> <port number>"},
#endif
#if defined(DEBUG)
{ "print", cliDisplay, "<address> [<size>] | <what>" },
{ "p", cliDisplay, "<address> [<size>] | <what>" },
{ "stackinfo", cliStackInfo, "" },
{ "meminfo", cliMemoryInfo, "" },
{ "test", cliTest, "new | graphics | memspd" },
{ "trace", cliTrace, "on | off" },
{ "debugvars", cliDebugVars, "" },
{ "repeat", cliRepeat, "<interval> <command>" },
#endif
{ "help", cliHelp, "[<command>]" },
#if defined(JITTER_MEASURE)
{ "jitter", cliShowJitter, "" },
#endif
#if defined(INTERNAL_GPS)
{ "gps", cliGps, "<baudrate>|$<command>|trace" },
#endif
#if defined(SPACEMOUSE)
{ "spacemouse", cliSpaceMouse, "poll | tare | startstreaming | stopstreaming | trace" },
#endif
#if defined(BLUETOOTH)
{ "bt", cliBlueTooth, "<baudrate>|<command>" },
#endif
#if defined(ACCESS_DENIED) && defined(DEBUG_CRYPT)
{ "crypt", cliCrypt, "<string to be encrypted>" },
#endif
#if defined(HARDWARE_TOUCH) && !defined(PCBNV14)
{ "reset_gt911", cliResetGT911, ""},
#endif
{ nullptr, nullptr, nullptr } /* sentinel */
};
int cliHelp(const char ** argv)
{
for (const CliCommand * command = cliCommands; command->name != nullptr; command++) {
if (argv[1][0] == '\0' || !strcmp(command->name, argv[0])) {
cliSerialPrint("%s %s", command->name, command->args);
if (argv[1][0] != '\0') {
return 0;
}
}
}
if (argv[1][0] != '\0') {
cliSerialPrint("Invalid command \"%s\"", argv[0]);
}
return -1;
}
int cliExecCommand(const char ** argv)
{
if (argv[0][0] == '\0')
return 0;
for (const CliCommand * command = cliCommands; command->name != nullptr; command++) {
if (!strcmp(command->name, argv[0])) {
return command->func(argv);
}
}
cliSerialPrint("Invalid command \"%s\"", argv[0]);
return -1;
}
int cliExecLine(char * line)
{
int len = strlen(line);
const char * argv[CLI_COMMAND_MAX_ARGS];
memset(argv, 0, sizeof(argv));
int argc = 1;
argv[0] = line;
for (int i=0; i<len; i++) {
if (line[i] == ' ') {
line[i] = '\0';
if (argc < CLI_COMMAND_MAX_ARGS) {
argv[argc++] = &line[i+1];
}
}
}
return cliExecCommand(argv);
}
#define CHAR_LF 0x0A
#define CHAR_NEWPAGE 0x0C
#define CHAR_CR 0x0D
#define CHAR_DEL 0x7F
void cliTask(void * pdata)
{
char line[CLI_COMMAND_MAX_LEN+1];
int pos = 0;
cliPrompt();
for (;;) {
uint8_t c;
// TODO: implement block read instead
// of going byte-by-byte.
/* Block for max 100ms. */
const TickType_t xTimeout = 100 / RTOS_MS_PER_TICK;
size_t xReceivedBytes = xStreamBufferReceive(cliRxBuffer, &c, 1, xTimeout);
if (!mixerTaskRunning()) {
WDG_RESET();
}
if (!xReceivedBytes) {
continue;
}
switch(c) {
case CHAR_NEWPAGE:
// clear screen
cliSerialPrint("\033[2J\033[1;1H");
cliPrompt();
break;
case CHAR_DEL:
if (pos) {
line[--pos] = '\0';
cliSerialPutc('\010');
cliSerialPutc(' ');
cliSerialPutc('\010');
}
break;
case CHAR_CR:
// ignore
break;
case CHAR_LF:
// enter
cliSerialCrlf();
line[pos] = '\0';
if (pos == 0 && cliLastLine[0]) {
// execute (repeat) last command
strcpy(line, cliLastLine);
}
else {
// save new command
strcpy(cliLastLine, line);
}
// TODO: check return value
cliExecLine(line);
pos = 0;
cliPrompt();
break;
default:
if (isascii(c) && pos < CLI_COMMAND_MAX_LEN) {
line[pos++] = c;
cliSerialPutc(c);
}
break;
}
}
}
void cliStart()
{
// Init stream buffer
const size_t xTriggerLevel = 1;
cliRxBuffer =
xStreamBufferCreateStatic(sizeof(cliRxBufferStorage), xTriggerLevel,
cliRxBufferStorage, &cliRxBufferStatic);
// Setup consumer callback
cliReceiveCallBack = cliDefaultRx;
RTOS_CREATE_TASK(cliTaskId, cliTask, "CLI", cliStack, CLI_STACK_SIZE,
CLI_TASK_PRIO);
}