[shell] add a shell module with ChibiOS (#2634)

* [shell] add a shell module with ChibiOS

- add a set of core commands
- allow to add custom commands from external modules at runtime
- fix small errors in rtos_mon module

* [shell] only for ap target at the moment
This commit is contained in:
Gautier Hattenberger
2021-01-05 16:01:40 +01:00
committed by GitHub
parent 614f6a1c6a
commit e0b299acd4
12 changed files with 1826 additions and 5 deletions
+6 -2
View File
@@ -7,7 +7,7 @@
<airframe name="Apogee">
<firmware name="fixedwing">
<configure name="RTOS_DEBUG" value="1"/>
<configure name="RTOS_DEBUG" value="0"/>
<target name="ap" board="apogee_1.0_chibios">
<module name="radio_control" type="sbus"/>
@@ -39,7 +39,11 @@
<!-- Sensors -->
<module name="gps" type="ublox"/>
<!--module name="sys_mon"/-->
<module name="shell">
<configure name="SHELL_PORT" value="uart6"/>
</module>
<module name="sys_mon"/>
<!--module name="spi_master"/-->
<!-- <module name="mcp355x"> -->
+32
View File
@@ -0,0 +1,32 @@
<!DOCTYPE module SYSTEM "module.dtd">
<module name="shell" dir="core">
<doc>
<description>Simple debug shell</description>
<configure name="SHELL_PORT" value="uartX" description="serial link to use for the shell"/>
<configure name="SHELL_BAUD" value="B115200" description="baudrate for the shell"/>
<configure name="SHELL_DYNAMIC_ENTRIES_NUMBER" value="5" description="maximum number of dynamic commands to register"/>
</doc>
<header>
<file name="shell.h"/>
</header>
<init fun="shell_init()"/>
<makefile target="ap">
<configure name="SHELL_PORT" case="upper|lower"/>
<configure name="SHELL_BAUD" default="B115200"/>
<configure name="SHELL_DYNAMIC_ENTRIES_NUMBER" default="5"/>
<file name="shell.c"/>
<file_arch name="shell_arch.c"/>
<file_arch name="microrl/microrl.c"/>
<file_arch name="microrl/microrlShell.c"/>
<include name="$(SRC_ARCH)/modules/core"/>
<include name="modules/loggers/sdlog_chibios"/>
<file name="printf.c" dir="modules/loggers/sdlog_chibios"/>
<define name="USE_SHELL"/>
<define name="USE_$(SHELL_PORT_UPPER)"/>
<define name="USE_$(SHELL_PORT_UPPER)_TX" value="FALSE"/>
<define name="USE_$(SHELL_PORT_UPPER)_RX" value="FALSE"/>
<define name="$(SHELL_PORT_UPPER)_BAUD" value="$(SHELL_BAUD)"/>
<define name="SHELL_DEV" value="$(SHELL_PORT_LOWER)"/>
<define name="SHELL_DYNAMIC_ENTRIES_NUMBER" value="$(SHELL_DYNAMIC_ENTRIES_NUMBER)"/>
</makefile>
</module>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,110 @@
#ifndef _MICRORL_H_
#define _MICRORL_H_
#include "microrlConfig.h"
#define true 1
#define false 0
/* define the Key codes */
#define KEY_NUL 0 /**< ^@ Null character */
#define KEY_SOH 1 /**< ^A Start of heading, = console interrupt */
#define KEY_STX 2 /**< ^B Start of text, maintenance mode on HP console */
#define KEY_ETX 3 /**< ^C End of text */
#define KEY_EOT 4 /**< ^D End of transmission, not the same as ETB */
#define KEY_ENQ 5 /**< ^E Enquiry, goes with ACK; old HP flow control */
#define KEY_ACK 6 /**< ^F Acknowledge, clears ENQ logon hand */
#define KEY_BEL 7 /**< ^G Bell, rings the bell... */
#define KEY_BS 8 /**< ^H Backspace, works on HP terminals/computers */
#define KEY_HT 9 /**< ^I Horizontal tab, move to next tab stop */
#define KEY_LF 10 /**< ^J Line Feed */
#define KEY_VT 11 /**< ^K Vertical tab */
#define KEY_FF 12 /**< ^L Form Feed, page eject */
#define KEY_CR 13 /**< ^M Carriage Return*/
#define KEY_SO 14 /**< ^N Shift Out, alternate character set */
#define KEY_SI 15 /**< ^O Shift In, resume defaultn character set */
#define KEY_DLE 16 /**< ^P Data link escape */
#define KEY_DC1 17 /**< ^Q XON, with XOFF to pause listings; "okay to send". */
#define KEY_DC2 18 /**< ^R Device control 2, block-mode flow control */
#define KEY_DC3 19 /**< ^S XOFF, with XON is TERM=18 flow control */
#define KEY_DC4 20 /**< ^T Device control 4 */
#define KEY_NAK 21 /**< ^U Negative acknowledge */
#define KEY_SYN 22 /**< ^V Synchronous idle */
#define KEY_ETB 23 /**< ^W End transmission block, not the same as EOT */
#define KEY_CAN 24 /**< ^X Cancel line, MPE echoes !!! */
#define KEY_EM 25 /**< ^Y End of medium, Control-Y interrupt */
#define KEY_SUB 26 /**< ^Z Substitute */
#define KEY_ESC 27 /**< ^[ Escape, next character is not echoed */
#define KEY_FS 28 /**< ^\ File separator */
#define KEY_GS 29 /**< ^] Group separator */
#define KEY_RS 30 /**< ^^ Record separator, block-mode terminator */
#define KEY_US 31 /**< ^_ Unit separator */
#define KEY_DEL 127 /**< Delete (not a real control character...) */
// direction of history navigation
#define _HIST_UP 0
#define _HIST_DOWN 1
// esc seq internal codes
#define _ESC_BRACKET 1
#define _ESC_HOME 2
#define _ESC_END 3
#ifdef _USE_HISTORY
// history struct, contain internal variable
// history store in static ring buffer for memory saving
typedef struct {
char ring_buf [_RING_HISTORY_LEN];
int begin;
int end;
int cur;
} ring_history_t;
#endif
// microrl struct, contain internal library data
typedef struct {
#ifdef _USE_HISTORY
ring_history_t ring_hist; // history object
#endif
char * prompt_str; // pointer to prompt string
char cmdline [_COMMAND_LINE_LEN]; // cmdline buffer
int cmdlen; // last position in command line
int cursor; // input cursor
char const * tkn_arr [_COMMAND_TOKEN_NMB]; // array of token for call 'execute' callback
void (*execute) (int argc, const char * const * argv ); // ptr to 'execute' callback
const char ** (*get_completion) (int argc, const char * const * argv ); // ptr to 'completion' callback
void (*print) (const char *); // ptr to 'print' callback
#ifdef _USE_CTLR_C
void (*sigint) (void);
#endif
} microrl_t;
// init internal data, calls once at start up
void microrl_init (microrl_t * pThis, void (*print)(const char*));
// set echo mode (true/false), using for disabling echo for password input
// echo mode will enabled after user press Enter.
void microrl_set_echo (int);
// set pointer to callback complition func, that called when user press 'Tab'
// callback func description:
// param: argc - argument count, argv - pointer array to token string
// must return NULL-terminated string, contain complite variant splitted by 'Whitespace'
// If complite token found, it's must contain only one token to be complitted
// Empty string if complite not found, and multiple string if there are some token
void microrl_set_complete_callback (microrl_t * pThis,
const char ** (*get_completion)(int, const char* const*));
// pointer to callback func, that called when user press 'Enter'
// execute func param: argc - argument count, argv - pointer array to token string
void microrl_set_execute_callback (microrl_t * pThis, void (*execute)(int, const char* const*));
// set callback for Ctrl+C terminal signal
#ifdef _USE_CTLR_C
void microrl_set_sigint_callback (microrl_t * pThis, void (*sigintf)(void));
#endif
// insert char to cmdline (for example call in usart RX interrupt)
void microrl_insert_char (microrl_t * pThis, int ch);
#endif
@@ -0,0 +1,102 @@
/*
Microrl library config files
Autor: Eugene Samoylov aka Helius (ghelius@gmail.com)
*/
#ifndef _MICRORL_CONFIG_H_
#define _MICRORL_CONFIG_H_
#define MICRORL_LIB_VER "1.5"
/*********** CONFIG SECTION **************/
/*
Command line length, define cmdline buffer size. Set max number of chars + 1,
because last byte of buffer need to contain '\0' - NULL terminator, and
not use for storing inputed char.
If user input chars more then it parametrs-1, chars not added to command line.*/
#define _COMMAND_LINE_LEN (1+100) // for 32 chars
/*
Command token number, define max token it command line, if number of token
typed in command line exceed this value, then prints message about it and
command line not to be parced and 'execute' callback will not calls.
Token is word separate by white space, for example 3 token line:
"IRin> set mode test" */
#define _COMMAND_TOKEN_NMB 30
/*
Define you prompt string here. You can use colors escape code, for highlight you prompt,
for example this prompt will green color (if you terminal supports color)*/
#define _PROMPT_DEFAUTL "\033[32mpprz >\033[0m " // green color
/*
Define prompt text (without ESC sequence, only text) prompt length, it needs because if you use
ESC sequence, it's not possible detect only text length*/
#define _PROMPT_LEN 7
/*Define it, if you wanna use completion functional, also set completion callback in you code,
now if user press TAB calls 'copmlitetion' callback. If you no need it, you can just set
NULL to callback ptr and do not use it, but for memory saving tune,
if you are not going to use it - disable this define.*/
#define _USE_COMPLETE
/*Define it, if you wanna use history. It s work's like bash history, and
set stored value to cmdline, if UP and DOWN key pressed. Using history add
memory consuming, depends from _RING_HISTORY_LEN parametr */
#define _USE_HISTORY
/*
History ring buffer length, define static buffer size.
For saving memory, each entered cmdline store to history in ring buffer,
so we can not say, how many line we can store, it depends from cmdline len,
but memory using more effective. We not prefer dinamic memory allocation for
small and embedded devices. Overhead is 2 char on each saved line*/
#define _RING_HISTORY_LEN 256
/*
Enable Handling terminal ESC sequence. If disabling, then cursor arrow, HOME, END will not work,
use Ctrl+A(B,F,P,N,A,E,H,K,U,C) see README, but decrease code memory.*/
#define _USE_ESC_SEQ
/*
Use snprintf from you standard complier library, but it gives some overhead.
If not defined, use my own u16int_to_str variant, it's save about 800 byte of code size
on AVR (avr-gcc build).
Try to build with and without, and compare total code size for tune library.
*/
//#define _USE_LIBC_STDIO
/*
Enable 'interrupt signal' callback, if user press Ctrl+C */
#define _USE_CTLR_C
/*
Print prompt at 'microrl_init', if enable, prompt will print at startup,
otherwise first prompt will print after first press Enter in terminal
NOTE!: Enable it, if you call 'microrl_init' after your communication subsystem
already initialize and ready to print message */
#undef _ENABLE_INIT_PROMPT
/*
New line symbol */
#define _ENDL_CR
#if defined(_ENDL_CR)
#define ENDL "\r\n"
#elif defined(_ENDL_CRLF)
#define ENDL "\r\n"
#elif defined(_ENDL_LF)
#define ENDL "\n"
#elif defined(_ENDL_LFCR)
#define ENDL "\n\r"
#else
#error "You must define new line symbol."
#endif
/********** END CONFIG SECTION ************/
#if _RING_HISTORY_LEN > 256
#error "This history implementation (ring buffer with 1 byte iterator) allow 256 byte buffer size maximum"
#endif
#endif
@@ -0,0 +1,472 @@
/**
* @file shell.c
* @brief Enhanced CLI shell code.
*
* @addtogroup SHELL
* @{
*/
#include <string.h>
#include "microrl/microrlShell.h"
#include "microrl/microrl.h"
#include "printf.h"
//#include "stdutil.h"
#define printScreen(...) {chprintf (chpg, __VA_ARGS__); chprintf (chpg, "\r\n");}
typedef struct {
void (*altFunc) (uint8_t c, uint32_t mode);
uint32_t param;
} AltCbParam;
static void cmd_info(BaseSequentialStream *lchp, int argc,
const char * const argv[]);
/**
* @brief Shell termination event source.
*/
event_source_t shell_terminated;
static MUTEX_DECL(mut);
static microrl_t rl;
static BaseSequentialStream *chpg;
static const ShellCommand *staticCommands = NULL;
static const char * complWorlds[64];
/**
* @brief Array of the default and dynamic commands.
*/
#if SHELL_DYNAMIC_ENTRIES_NUMBER // compatibility with legacy static only behavior
static ShellCommand localCommands[SHELL_DYNAMIC_ENTRIES_NUMBER + 2U] = {
#else
static const ShellCommand localCommands[] = {
#endif
{"info", cmd_info},
{NULL, NULL}
};
static AltCbParam altCbParam = {.altFunc = NULL, .param = 0};
void microrlPrint (const char * str)
{
int i = 0;
while (str[i] != 0) {
streamPut(chpg, str[i++]);
}
}
void microrlExecute (int argc, const char * const *argv)
{
const ShellCommand *scp = staticCommands;
const char *name = argv[0];
chMtxLock(&mut);
while (scp->sc_name != NULL) {
if (strcasecmp(scp->sc_name, name) == 0) {
scp->sc_function(chpg, argc-1, &argv[1]);
goto exit;
}
scp++;
}
scp = localCommands;
while (scp->sc_name != NULL) {
if (strcasecmp(scp->sc_name, name) == 0) {
scp->sc_function(chpg, argc-1, &argv[1]);
goto exit;
}
scp++;
}
exit:
chMtxUnlock(&mut);
}
const char ** microrlComplet (int argc, const char * const * argv)
{
uint32_t j = 0;
complWorlds [0] = NULL;
chMtxLock(&mut);
// if there is token in cmdline
if (argc == 1) {
// get last entered token
const char *bit = argv[argc-1];
// iterate through our available token and match it
for (const ShellCommand *scp = localCommands;
scp->sc_name != NULL; scp++) {
// if token is matched (text is part of our token starting from 0 char)
if (strstr(scp->sc_name, bit) == scp->sc_name) {
// add it to completion set
complWorlds[j++] = scp->sc_name;
}
}
for (const ShellCommand *scp = staticCommands;
scp->sc_name != NULL; scp++) {
// if token is matched (text is part of our token starting from 0 char)
if (strstr(scp->sc_name, bit) == scp->sc_name) {
// add it to completion set
complWorlds[j++] = scp->sc_name;
}
}
} else { // if there is no token in cmdline, just print all available token
for (const ShellCommand *scp = localCommands; scp->sc_name != NULL; scp++)
complWorlds[j++] = scp->sc_name;
for (const ShellCommand *scp = staticCommands; scp->sc_name != NULL; scp++)
complWorlds[j++] = scp->sc_name;
}
// note! last ptr in array always must be NULL!!!
complWorlds[j] = NULL;
chMtxUnlock(&mut);
// return set of variants
return complWorlds;
}
void microrlSigint (void)
{
chprintf (chpg, "^C catched!\n\r");
}
static void usage(BaseSequentialStream *lchp, char *p) {
chprintf(lchp, "Usage: %s\r\n", p);
}
static void cmd_info(BaseSequentialStream *lchp, int argc, const char * const argv[]) {
(void)argv;
if (argc > 0) {
usage(lchp, "info");
return;
}
/*
Bits 31:16 REV_ID[15:0] Revision identifier
This field indicates the revision of the device.
STM32F405xx/07xx and STM32F415xx/17xx devices:
0x1000 = Revision A
0x1001 = Revision Z
0x1003 = Revision 1
0x1007 = Revision 2
0x100F= Revision Y
STM32F42xxx and STM32F43xxx devices:
0x1000 = Revision A
0x1003 = Revision Y
0x1007 = Revision 1
0x2001= Revision 3
Bits 15:12 Reserved, must be kept at reset value.
Bits 11:0 DEV_ID[11:0]: Device identifier (STM32F405xx/07xx and STM32F415xx/17xx)
The device ID is 0x413.
Bits 11:0 DEV_ID[11:0]: Device identifier (STM32F42xxx and STM32F43xxx)
The device ID is 0x419
F7
Bits 31:16 REV_ID[15:0] Revision identifier
This field indicates the revision of the device:
0x1000 = Revision A
0x1001 = Revision Z
Bits 15:12 Reserved, must be kept at reset value.
Bits 11:0 DEV_ID[11:0]: Device identifier
The device ID is 0x449.
L47x 49x
Bits 31:16 REV_ID[15:0] Revision identifier
This field indicates the revision of the device.
For STM32L475xx/476xx/486xx devices
0x1000: Rev 1
0x1001: Rev 2
0x1003: Rev 3
0x1007: Rev 4
For STM32L496xx/4A6xx devices
0x1000: Rev A
0x2000: Rev B
Bits 11:0 DEV_ID[11:0]: Device identifier
The device ID is:
0x461 for STM32L496xx/4A6xx devices
0x415 for STM32L475xx/476xx/486xx devices.
*/
const uint16_t mcu_revid = (DBGMCU->IDCODE & DBGMCU_IDCODE_REV_ID) >> 16;
const uint16_t mcu_devid = DBGMCU->IDCODE & DBGMCU_IDCODE_DEV_ID;
char *mcu_devid_str ="not known, please fix microrlShell.c";
char mcu_revid_chr = '?';
switch (mcu_devid) {
case 0x415 : mcu_devid_str = "STM32L475xx/476xx/486xx devices";
switch (mcu_revid) {
case 0x1000 : mcu_revid_chr = '1'; break;
case 0x1001 : mcu_revid_chr = '2'; break;
case 0x1003 : mcu_revid_chr = '3'; break;
case 0x1007 : mcu_revid_chr = '4'; break;
}
break;
case 0x461 : mcu_devid_str = "STM32L496xx/4A6xx devices";
switch (mcu_revid) {
case 0x1000 : mcu_revid_chr = 'A'; break;
case 0x2000 : mcu_revid_chr = 'B'; break;
}
break;
case 0x411 : mcu_devid_str = "STM32F2xx and *EARLY* STM32F40x and 41x";
switch (mcu_revid) {
case 0x1000 : mcu_revid_chr = 'A'; break;
case 0x1001 : mcu_revid_chr = 'Z'; break;
case 0x2000 : mcu_revid_chr = 'B'; break;
case 0x2001 : mcu_revid_chr = 'Y'; break;
case 0x2003 : mcu_revid_chr = 'X'; break;
}
break;
case 0x413 : mcu_devid_str = "STM32F40x and 41x";
switch (mcu_revid) {
case 0x1000 : mcu_revid_chr = 'A'; break;
case 0x1001 : mcu_revid_chr = 'Z'; break;
case 0x1003 : mcu_revid_chr = '1'; break;
case 0x1007 : mcu_revid_chr = '2'; break;
case 0x100F : mcu_revid_chr = 'Y'; break;
}
break;
case 0x419 : mcu_devid_str = "STM32F42x and F43x";
switch (mcu_revid) {
case 0x1000 : mcu_revid_chr = 'A'; break;
case 0x1003 : mcu_revid_chr = 'Y'; break;
case 0x1007 : mcu_revid_chr = '1'; break;
case 0x2001 : mcu_revid_chr = '3'; break;
}
break;
case 0x449 : mcu_devid_str = "STM32F74x and STM32F75x";
switch (mcu_revid) {
case 0x1000 : mcu_revid_chr = 'A'; break;
case 0x1001 : mcu_revid_chr = 'Z'; break;
}
break;
case 0x451 : mcu_devid_str = "STM32F76x and STM32F77x";
switch (mcu_revid) {
case 0x1000 : mcu_revid_chr = 'A'; break;
case 0x1001 : mcu_revid_chr = 'Z'; break;
}
break;
case 0x435 : mcu_devid_str = "STM32L43x";
switch (mcu_revid) {
case 0x1000 : mcu_revid_chr = 'A'; break;
case 0x1001 : mcu_revid_chr = 'Z'; break;
}
break;
case 0x446 : mcu_devid_str = "STM32F303xD/E and STM32F398xE";
switch (mcu_revid) {
case 0x1001 : mcu_revid_chr = 'Z'; break;
case 0x1003 : mcu_revid_chr = 'Y'; break;
}
break;
}
chprintf(lchp, "Kernel: %s\r\n", CH_KERNEL_VERSION);
#ifdef HAL_VERSION
chprintf(lchp, "Hal: %s\r\n", HAL_VERSION);
#endif
#ifdef CH_COMPILER_NAME
chprintf(lchp, "Compiler: %s\r\n", CH_COMPILER_NAME);
#endif
#ifdef PORT_COMPILER_NAME
chprintf(lchp, "Compiler: %s\r\n", PORT_COMPILER_NAME);
#endif
#ifdef CH_ARCHITECTURE_NAME
chprintf(lchp, "Architecture: %s\r\n", CH_ARCHITECTURE_NAME);
#endif
#ifdef PORT_ARCHITECTURE_NAME
chprintf(lchp, "Architecture: %s\r\n", PORT_ARCHITECTURE_NAME);
#endif
#ifdef CH_CORE_VARIANT_NAME
chprintf(lchp, "Core Variant: %s\r\n", CH_CORE_VARIANT_NAME);
#endif
#ifdef PORT_CORE_VARIANT_NAME
chprintf(lchp, "Core Variant: %s\r\n", PORT_CORE_VARIANT_NAME);
#endif
#ifdef STM32_SYSCLK
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdouble-promotion"
chprintf(lchp, "Main STM32_SYSCLK frequency %.2f Mhz\r\n", STM32_SYSCLK/1e6f);
#pragma GCC diagnostic pop
#endif
#ifdef CH_PORT_INFO
chprintf(lchp, "Port Info: %s\r\n", CH_PORT_INFO);
#endif
#ifdef PORT_INFO
chprintf(lchp, "Port Info: %s\r\n", PORT_INFO);
#endif
#ifdef PLATFORM_NAME
chprintf(lchp, "Platform: %s\r\n", PLATFORM_NAME);
#endif
#ifdef BOARD_NAME
chprintf(lchp, "Board: %s\r\n", BOARD_NAME);
#endif
chprintf(lchp, "Chip Revision: %s REV '%c' (0x%x:0x%x)\r\n", mcu_devid_str, mcu_revid_chr, mcu_devid, mcu_revid);
#if (!defined STM32_USE_REVISION_A_FIX) || (STM32_USE_REVISION_A_FIX == 0)
if ((mcu_devid == 0x413) && (mcu_revid_chr == 'A')) {
chprintf(lchp, "Chip Revision: %s REV '%c' PLEASE define STM32_USE_REVISION_A_FIX in mcuconf.h !!\r\n",
mcu_devid_str, mcu_revid_chr);
}
#endif
#ifdef __DATE__
#ifdef __TIME__
chprintf(lchp, "Build time: %s%s%s\r\n", __DATE__, " - ", __TIME__);
#endif
#endif
chprintf(lchp, "systime= %lu\r\n", (unsigned long)chVTGetSystemTimeX());
}
/**
* @brief Shell thread function.
*
* @param[in] p pointer to a @p BaseSequentialStream object
* @return Termination reason.
* @retval MSG_OK terminated by command.
* @retval RDY_RESET terminated by reset condition on the I/O channel.
*/
static THD_FUNCTION(shell_thread, p) {
msg_t msg = MSG_OK;
chpg = ((ShellConfig *)p)->sc_channel;
staticCommands = ((ShellConfig *)p)->sc_commands;
bool readOk=TRUE;
chRegSetThreadName("Enhanced_shell");
printScreen ("ChibiOS/RT Enhanced Shell");
while (!chThdShouldTerminateX() && readOk) {
uint8_t c;
if (streamRead(chpg, &c, 1) == 0) {
readOk=FALSE;
} else {
if (altCbParam.altFunc == NULL) {
microrl_insert_char (&rl, c);
} else {
(*altCbParam.altFunc) (c, altCbParam.param);
}
}
}
/* Atomically broadcasting the event source and terminating the thread,
there is not a chSysUnlock() because the thread terminates upon return.*/
printScreen ("exit");
chSysLock();
chEvtBroadcastI(&shell_terminated);
chThdExitS(msg);
}
/**
* @brief Shell manager initialization.
*/
void shellInit(void) {
chEvtObjectInit(&shell_terminated);
microrl_init (&rl, microrlPrint);
microrl_set_execute_callback (&rl, &microrlExecute);
microrl_set_complete_callback (&rl, &microrlComplet);
microrl_set_sigint_callback (&rl, &microrlSigint);
}
/**
* @brief Spawns a new shell.
* @pre @p CH_USE_MALLOC_HEAP and @p CH_USE_DYNAMIC must be enabled.
*
* @param[in] scp pointer to a @p ShellConfig object
* @param[in] size size of the shell working area to be allocated
* @param[in] prio priority level for the new shell
* @return A pointer to the shell thread.
* @retval NULL thread creation failed because memory allocation.
*/
#if CH_CFG_USE_HEAP && CH_CFG_USE_DYNAMIC
thread_t *shellCreateFromHeap(const ShellConfig *scp, size_t size, tprio_t prio) {
return chThdCreateFromHeap(NULL, size, "shell", prio, shell_thread, (void *)scp);
}
#endif
/**
* @brief Create statically allocated shell thread.
*
* @param[in] scp pointer to a @p ShellConfig object
* @param[in] wsp pointer to a working area dedicated to the shell thread stack
* @param[in] size size of the shell working area
* @param[in] prio priority level for the new shell
* @return A pointer to the shell thread.
*/
thread_t *shellCreateStatic(const ShellConfig *scp, void *wsp,
size_t size, tprio_t prio) {
return chThdCreateStatic(wsp, size, prio, shell_thread, (void *)scp);
}
#if SHELL_DYNAMIC_ENTRIES_NUMBER
/**
* @brief add/change/remove dynamically new shell entries
* @pre @p SHELL_DYNAMIC_ENTRIES_NUMBER must be set
* @note if sc.sc_name already exists, entry will be overwrite
if sc.sc_name already exists and sc.sc_function is null,
entry will be removed
* @param[in] sc ShellCommand object
* @return true : OK, false : error table is full.
*/
bool shellAddEntry(const ShellCommand sc)
{
ShellCommand* lcp = localCommands;
static const int len = (sizeof(localCommands) / sizeof(localCommands[0]))
-1U;
chMtxLock(&mut);
while ((lcp->sc_function != NULL) &&
(strcmp(lcp->sc_name, sc.sc_name) != 0) &&
((lcp - localCommands) < len))
lcp++;
if ((lcp - localCommands) == len) {
chMtxUnlock(&mut);
return false;
}
lcp->sc_function = sc.sc_function;
lcp->sc_name = sc.sc_function == NULL ? NULL : sc.sc_name;
*(++lcp) = (ShellCommand){NULL, NULL};
chMtxUnlock(&mut);
return true;
}
#endif
void modeAlternate(void (*funcp) (uint8_t c, uint32_t mode), uint32_t mode)
{
chMtxLock(&mut);
altCbParam.altFunc = funcp;
altCbParam.param = mode;
chMtxUnlock(&mut);
}
void modeShell(void)
{
chMtxLock(&mut);
altCbParam.altFunc = NULL;
chMtxUnlock(&mut);
printScreen ("retour au shell");
}
@@ -0,0 +1,69 @@
#pragma once
/**
* @file microrlShell.h
* @brief Simple CLI shell header.
*
* @addtogroup SHELL
* @{
*/
#include "ch.h"
#include "hal.h"
#ifndef SHELL_DYNAMIC_ENTRIES_NUMBER
#define SHELL_DYNAMIC_ENTRIES_NUMBER 0U
#endif
// legacy compatibility
#define shellCreate(C, S, P) shellCreateFromHeap(C, S, P)
/**
* @brief Command handler function type.
*/
typedef void (shellcmd_f)(BaseSequentialStream *chp, int argc, const char * const argv[]);
/**
* @brief Custom command entry type.
*/
typedef struct {
const char *sc_name; /**< @brief Command name. */
shellcmd_f *sc_function; /**< @brief Command function. */
} ShellCommand;
/**
* @brief Shell descriptor type.
*/
typedef struct {
BaseSequentialStream *sc_channel; /**< @brief I/O channel associated
to the shell. */
const ShellCommand *sc_commands; /**< @brief Shell extra commands
table. */
} ShellConfig;
#if !defined(__DOXYGEN__)
extern event_source_t shell_terminated;
#endif
#ifdef __cplusplus
extern "C" {
#endif
void shellInit(void);
#if CH_CFG_USE_HEAP && CH_CFG_USE_DYNAMIC
thread_t *shellCreateFromHeap(const ShellConfig *scp, size_t size, tprio_t prio);
#endif
thread_t *shellCreateStatic(const ShellConfig *scp, void *wsp,
size_t size, tprio_t prio);
#if SHELL_DYNAMIC_ENTRIES_NUMBER
bool shellAddEntry(const ShellCommand sc);
#endif
bool shellGetLine(BaseSequentialStream *chp, char *line, unsigned size);
void modeAlternate (void (*) (uint8_t c, uint32_t mode), uint32_t mode);
void modeShell (void);
#ifdef __cplusplus
}
#endif
/** @} */
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2016 Gautier Hattenberger <gautier.hattenberger@enac.fr>
* 2020 Gautier Hattenberger, Alexandre Bustico
*
* This file is part of paparazzi
*
@@ -36,10 +37,119 @@ static uint32_t idle_counter, last_idle_counter;
static uint16_t get_stack_free(const thread_t *tp);
#if USE_SHELL
#include "modules/core/shell.h"
#include "printf.h"
#include "string.h"
#define MAX_CPU_INFO_ENTRIES 20
typedef struct _ThreadCpuInfo {
float ticks[MAX_CPU_INFO_ENTRIES];
float cpu[MAX_CPU_INFO_ENTRIES];
float totalTicks;
} ThreadCpuInfo ;
static void stampThreadCpuInfo (ThreadCpuInfo *ti)
{
const thread_t *tp = chRegFirstThread();
uint32_t idx=0;
float totalTicks =0;
do {
totalTicks+= (float) tp->time;
ti->cpu[idx] = (float) tp->time - ti->ticks[idx];;
ti->ticks[idx] = (float) tp->time;
tp = chRegNextThread ((thread_t *)tp);
idx++;
} while ((tp != NULL) && (idx < MAX_CPU_INFO_ENTRIES));
const float diffTotal = totalTicks- ti->totalTicks;
ti->totalTicks = totalTicks;
tp = chRegFirstThread();
idx=0;
do {
ti->cpu[idx] = (ti->cpu[idx]*100.f)/diffTotal;
tp = chRegNextThread ((thread_t *)tp);
idx++;
} while ((tp != NULL) && (idx < MAX_CPU_INFO_ENTRIES));
}
static float stampThreadGetCpuPercent (const ThreadCpuInfo *ti, const uint32_t idx)
{
if (idx >= MAX_CPU_INFO_ENTRIES)
return -1.f;
return ti->cpu[idx];
}
static void cmd_threads(shell_stream_t *lchp, int argc,const char* const argv[]) {
static const char *states[] = {CH_STATE_NAMES};
thread_t *tp = chRegFirstThread();
(void)argv;
(void)argc;
float totalTicks=0;
float idleTicks=0;
static ThreadCpuInfo threadCpuInfo = {
.ticks = {[0 ... MAX_CPU_INFO_ENTRIES-1] = 0.f},
.cpu = {[0 ... MAX_CPU_INFO_ENTRIES-1] =-1.f},
.totalTicks = 0.f
};
stampThreadCpuInfo (&threadCpuInfo);
chprintf (lchp, " addr stack frestk prio refs state time \t percent name\r\n");
uint32_t idx=0;
do {
chprintf (lchp, "%.8lx %.8lx %6lu %4lu %4lu %9s %9lu %.1f \t%s\r\n",
(uint32_t)tp, (uint32_t)tp->ctx.sp,
get_stack_free (tp),
(uint32_t)tp->prio, (uint32_t)(tp->refs - 1),
states[tp->state], (uint32_t)tp->time,
stampThreadGetCpuPercent (&threadCpuInfo, idx),
chRegGetThreadNameX(tp));
totalTicks+= (float) tp->time;
if (strcmp (chRegGetThreadNameX(tp), "idle") == 0)
idleTicks = (float) tp->time;
tp = chRegNextThread ((thread_t *)tp);
idx++;
} while (tp != NULL);
const float idlePercent = (idleTicks*100.f)/totalTicks;
const float cpuPercent = 100.f - idlePercent;
chprintf (lchp, "\r\ncpu load = %.2f%%\r\n", cpuPercent);
}
static void cmd_rtos_mon(shell_stream_t *sh, int argc, const char * const argv[])
{
(void) argv;
if (argc > 0) {
chprintf(sh, "Usage: rtos_mon\r\n");
return;
}
chprintf(sh, "Data reported in the RTOS_MON message:\r\n");
chprintf(sh, " core free mem: %u\r\n", rtos_mon.core_free_memory);
chprintf(sh, " heap free mem: %u\r\n", rtos_mon.heap_free_memory);
chprintf(sh, " heap fragments: %u\r\n", rtos_mon.heap_fragments);
chprintf(sh, " heap largest: %u\r\n", rtos_mon.heap_largest);
chprintf(sh, " CPU load: %d \%\r\n", rtos_mon.cpu_load);
chprintf(sh, " number of threads: %d\r\n", rtos_mon.thread_counter);
chprintf(sh, " thread names: %s\r\n", rtos_mon.thread_names);
for (int i = 0; i < rtos_mon.thread_counter; i++) {
chprintf(sh, " thread %d load: %0.1f, free stack: %d\r\n", i,
(float)rtos_mon.thread_load[i] / 10.f, rtos_mon.thread_free_stack[i]);
}
chprintf(sh, " CPU time: %.2f\r\n", rtos_mon.cpu_time);
}
#endif
void rtos_mon_init_arch(void)
{
idle_counter = 0;
last_idle_counter = 0;
#if USE_SHELL
shell_add_entry("rtos_mon", cmd_rtos_mon);
shell_add_entry("threads", cmd_threads);
#endif
}
// Fill data structure
@@ -85,15 +195,15 @@ void rtos_mon_periodic_arch(void)
rtos_mon.thread_counter++;
} while (tp != NULL && rtos_mon.thread_counter < RTOS_MON_MAX_THREADS);
// store individual thread load (as centi-percent integer)
// store individual thread load (as centi-percent integer, i.e. (th_time/sum)*10*100)
for (i = 0; i < rtos_mon.thread_counter; i ++) {
rtos_mon.thread_load[i] = (uint16_t)(10000.f * (float)thread_p_time[i] / sum);
rtos_mon.thread_load[i] = (uint16_t)(1000.f * (float)thread_p_time[i] / sum);
}
// assume we call the counter once a second
// so the difference in seconds is always one
// NOTE: not perfectly precise, +-5% on average so take it into consideration
rtos_mon.cpu_load = (1 - (float)(idle_counter - last_idle_counter) / CH_CFG_ST_FREQUENCY) * 100;
rtos_mon.cpu_load = (uint8_t)((1.f - ((float)idle_counter / sum)) * 100.f);
last_idle_counter = idle_counter;
}
@@ -0,0 +1,104 @@
/*
* Copyright (C) Alexandre Bustico <alexandre.bustico@enac.fr>
*
* This file is part of paparazzi
*
* paparazzi is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* paparazzi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with paparazzi; see the file COPYING. If not, see
* <http://www.gnu.org/licenses/>.
*/
/** @file "modules/core/shell_arch.c"
* @author Alexandre Bustico <alexandre.bustico@enac.fr>
* Simple debug shell
*/
#include "modules/core/shell.h"
#include "modules/core/microrl/microrlShell.h"
#include "mcu_periph/uart.h"
#include "printf.h"
#include "subsystems/abi.h"
/*************************
* Basic static commands *
*************************/
static void cmd_mem(BaseSequentialStream *lchp, int argc, const char * const argv[])
{
(void)argv;
if (argc > 0) {
chprintf(lchp, "Usage: mem\r\n");
return;
}
chprintf (lchp, "core free memory : %u bytes\r\n", chCoreGetStatusX());
//chprintf (lchp, "heap free memory : %u bytes\r\n", getHeapFree());
}
static void cmd_abi(BaseSequentialStream *lchp, int argc, const char * const argv[])
{
(void)argv;
if (argc > 0) {
chprintf(lchp, "Usage: abi\r\n");
return;
}
chprintf(lchp, "ABI message bindings\r\n");
for (int i = 0; i < ABI_MESSAGE_NB; i++) {
chprintf(lchp, " msg %d: ", i);
if (abi_queues[i] == NULL) {
chprintf(lchp, "no bindings\r\n");
} else {
abi_event *e;
ABI_FOREACH(abi_queues[i], e) {
chprintf(lchp, "(cb 0x%lx, id %d), ", e->cb, e->id);
}
chprintf(lchp, "\r\n");
}
}
}
static const ShellCommand commands[] = {
{"mem", cmd_mem},
{"abi", cmd_abi},
{NULL, NULL}
};
static ShellConfig shell_cfg = {
NULL,
commands
};
/**
* Add dynamic entry
*/
void shell_add_entry(char *cmd_name, shell_cmd_t *cmd)
{
shellAddEntry((ShellCommand) {cmd_name, cmd});
}
/** Arch init
*/
void shell_init_arch(void)
{
// This should be called after mcu periph init
shell_cfg.sc_channel = (BaseSequentialStream *) (SHELL_DEV.reg_addr);
shellInit();
thread_t * shelltp = shellCreateFromHeap(&shell_cfg, 2048U, NORMALPRIO);
if (shelltp == NULL) {
chSysHalt("fail starting shell");
}
}
@@ -0,0 +1,35 @@
/*
* Copyright (C) Alexandre Bustico <alexandre.bustico@enac.fr>
*
* This file is part of paparazzi
*
* paparazzi is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* paparazzi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with paparazzi; see the file COPYING. If not, see
* <http://www.gnu.org/licenses/>.
*/
/** @file "modules/core/shell_arch.h"
* @author Alexandre Bustico <alexandre.bustico@enac.fr>
* Simple debug shell
*/
#ifndef SHELL_ARCH_H
#define SHELL_ARCH_H
#include <hal.h>
typedef BaseSequentialStream shell_stream_t;
extern void shell_init_arch(void);
#endif // SHELL_ARCH_H
+35
View File
@@ -0,0 +1,35 @@
/*
* Copyright (C) Alexandre Bustico <alexandre.bustico@enac.fr>
*
* This file is part of paparazzi
*
* paparazzi is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* paparazzi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with paparazzi; see the file COPYING. If not, see
* <http://www.gnu.org/licenses/>.
*/
/** @file "modules/core/shell.c"
* @author Alexandre Bustico <alexandre.bustico@enac.fr>
* Simple debug shell
*/
#include "modules/core/shell.h"
#include "modules/core/shell_arch.h"
void shell_init(void)
{
shell_init_arch();
}
// add entry function implemented in arch part directly
+45
View File
@@ -0,0 +1,45 @@
/*
* Copyright (C) Alexandre Bustico <alexandre.bustico@enac.fr>
*
* This file is part of paparazzi
*
* paparazzi is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* paparazzi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with paparazzi; see the file COPYING. If not, see
* <http://www.gnu.org/licenses/>.
*/
/** @file "modules/core/shell.h"
* @author Alexandre Bustico <alexandre.bustico@enac.fr>
* Simple debug shell
*/
#ifndef SHELL_H
#define SHELL_H
#include "modules/core/shell_arch.h"
/** Init shell
*/
extern void shell_init(void);
/** Command handler
*/
typedef void (shell_cmd_t)(shell_stream_t *stream, int argc, const char * const argv[]);
/** Add new shell entry
*
* maximum number of entry might be limited, see specific implementations
*/
extern void shell_add_entry(char *cmd_name, shell_cmd_t *cmd);
#endif // SHELL_H