/** \file * \brief This file implements a simple serial debug console and command interpreter. */ /** \defgroup CONSOLE Console * * \brief Serial debug console command interpreter. * * \todo Describe the command interpreter. * * @{ * @} */ /* Include Files */ #include "KTag.h" /* Local Definitions and Constants */ //! Text representations of numeric digits, used by COMM_Console_Print_UInt32(). static const char8 DIGITS[] = "0123456789ABCDEF"; #if (CONFIG__FEATURE_COMM_CONSOLE == CONFIG__FEATURE_ENABLED) //! Maximum number of characters (save one) able to be printed by COMM_Console_Print_String(). #define MAX_CONSOLE_STRING_LENGTH 81 //! States in the COMM_Console_Task() state machine. typedef enum { COMM_STATE_INITIALIZING = 0, COMM_STATE_DISPLAY_POWERUP_INFO, COMM_STATE_IDLE, COMM_STATE_COMMAND_TOO_LONG, COMM_STATE_IDENTIFY_COMMAND, COMM_STATE_EXECUTE_COMMAND, COMM_STATE_UNKNOWN_COMMAND } COMM_Console_State_T; /* Public Variables */ char8 Command_Buffer[COMM_CONSOLE_COMMAND_MAX_LENGTH]; uint_fast16_t Command_Buffer_Index = 0; TaskHandle_t COMM_Console_Task_Handle; /* Private Variables */ //! Current state of the COMM_Console_Task() state machine. static COMM_Console_State_T Current_State = COMM_STATE_INITIALIZING; //! Next state of the COMM_Console_Task() state machine. static COMM_Console_State_T Next_State = COMM_STATE_INITIALIZING; //! Index into the #COMM_Console_Command_Table for the command currently being handled. /*! * If #Current_Command is set to UINT_FAST16_MAX, the command being handled is unknown, or no command is being handled. */ static uint_fast16_t Current_Command = 0; /* Private Function Prototypes */ static void ConsoleISR(void); static bool ConsoleCommandMatches(const char8 * const command_name); static void ReverseString(char8 * value, uint32_t length); /* Inline Functions */ //! Swaps the characters in x and y. static inline void Swap_Char8(char8 * x, char8 * y) { uint8_t temp = *x; *x = *y; *y = temp; } static inline void Reset_Command_Buffer() { taskENTER_CRITICAL(); for (uint_fast16_t i = 0; i < COMM_CONSOLE_COMMAND_MAX_LENGTH; i++) { Command_Buffer[i] = COMM_CONSOLE_STRING_TERMINATOR; } Command_Buffer_Index = 0; taskEXIT_CRITICAL(); } /* Public Functions */ //! Initializes the console. /*! * \ingroup CONSOLE */ void COMM_Console_Init(void) { // Enable the pullup on the Rx pin to keep the noise down. Cy_GPIO_SetDrivemode(UART_Console_rx_PORT, UART_Console_rx_NUM, CY_GPIO_DM_PULLUP); UART_Console_Start(); /// Unmask only the RX FIFO not empty interrupt bit. UART_Console_HW->INTR_RX_MASK = SCB_INTR_RX_MASK_NOT_EMPTY_Msk; Cy_SysInt_Init(&Int_UART_Console_cfg, ConsoleISR); NVIC_ClearPendingIRQ(Int_UART_Console_cfg.intrSrc); NVIC_EnableIRQ(Int_UART_Console_cfg.intrSrc); } //! Parses and handle console commands in the background. /*! * \ingroup CONSOLE * * The [UML State Machine Diagram](http://www.uml-diagrams.org/state-machine-diagrams.html) below * shows how the console messages processed by this code. Note that all of the *Character Rx'd* * transitions occur in the #UART_Console_SPI_UART_ISR_EntryCallback() itself on the PSoC4, in * #ConsoleRxISR() on the PSoC5, and in #ConsoleISR() on the PSoC6, to improve overall performance. * * \startuml{COMM_Console_Task.png} "Console Task" * * skinparam headerFontSize 18 * skinparam state { * BackgroundColor #eeeeee * BackgroundColor<> #ffaaaa * FontName Impact * FontSize 18 * } * skinparam note { * FontName "Comic Sans MS" * FontStyle italic * } * * state "Initializing" as STATE_INITIALIZING * [*] --> STATE_INITIALIZING * note left of STATE_INITIALIZING : Wait for the rest of the system to come online. * state "Display Powerup Info" as STATE_DISPLAY_POWERUP_INFO * STATE_DISPLAY_POWERUP_INFO : do/ print OS version * STATE_DISPLAY_POWERUP_INFO : do/ print configuration (debug or release) * STATE_INITIALIZING --> STATE_DISPLAY_POWERUP_INFO : after(100ms) * state "Idle" as STATE_IDLE * STATE_IDLE : do/ RTOS_Sleep() * STATE_DISPLAY_POWERUP_INFO --> STATE_IDLE * state STATE_IS_EOM <> * note top of STATE_IS_EOM : This happens in\nUART_Console_SPI_UART_ISR_ExitCallback() on PSoC4,\nConsoleRxISR() on PSoC5,\nand ConsoleISR() on PSoC6. * STATE_IDLE --> STATE_IS_EOM : character rx'd * state STATE_IS_COMMAND_BUFFER_FULL <> * note top of STATE_IS_COMMAND_BUFFER_FULL : This happens in\nUART_Console_SPI_UART_ISR_ExitCallback() on PSoC4,\nConsoleRxISR() on PSoC5,\nand ConsoleISR() on PSoC6. * STATE_IS_EOM --> STATE_IS_COMMAND_BUFFER_FULL : [else] * state "Identify Command" as STATE_IDENTIFY_COMMAND * STATE_IDENTIFY_COMMAND : do/ look for command in the COMM_Console_Command_Table[] * STATE_IS_EOM --> STATE_IDENTIFY_COMMAND : [rx'd character is EOM] * STATE_IDLE --> STATE_IDENTIFY_COMMAND : COMM_Console_Execute_Internal_Command() * state "Command Too Long" as STATE_COMMAND_TOO_LONG * STATE_COMMAND_TOO_LONG : do/ print error message * STATE_COMMAND_TOO_LONG : do/ reset command buffer * STATE_IS_COMMAND_BUFFER_FULL --> STATE_COMMAND_TOO_LONG : [command buffer is full] * STATE_IS_COMMAND_BUFFER_FULL --> STATE_IDLE : [else]/\nAppend received character to command buffer * STATE_COMMAND_TOO_LONG --> STATE_IDLE * state "Execute Command" as STATE_EXECUTE_COMMAND * STATE_EXECUTE_COMMAND : do/ execute console command * STATE_EXECUTE_COMMAND : exit/ reset command buffer * STATE_EXECUTE_COMMAND --> STATE_IDLE * STATE_IDENTIFY_COMMAND --> STATE_EXECUTE_COMMAND : [command matched] * state "Unknown Command" as STATE_UNKNOWN_COMMAND * STATE_UNKNOWN_COMMAND : do/ print error message * STATE_UNKNOWN_COMMAND : do/ reset command buffer * STATE_IDENTIFY_COMMAND --> STATE_UNKNOWN_COMMAND : [else] * STATE_UNKNOWN_COMMAND --> STATE_IDLE * * left footer Key: UML 2.5\nLast modified 2020-12-14 * \enduml * * \return None (infinite loop) */ void COMM_Console_Task(void * pvParameters) { static TickType_t xTicksToWait = pdMS_TO_TICKS(10); static uint32_t * NotificationValue; while(true) { (void) xTaskNotifyWait(0, 0, (uint32_t *)&NotificationValue, xTicksToWait); // Change to the next state atomically. taskENTER_CRITICAL(); Current_State = Next_State; taskEXIT_CRITICAL(); switch (Current_State) { default: case COMM_STATE_INITIALIZING: Next_State = COMM_STATE_DISPLAY_POWERUP_INFO; vTaskDelay(pdMS_TO_TICKS(10)); xTicksToWait = 1; break; case COMM_STATE_DISPLAY_POWERUP_INFO: COMM_Console_Print_String("[COMM] "); COMM_RTOS_HandleConsoleVersion(NULL, 0); vTaskDelay(pdMS_TO_TICKS(10)); COMM_Console_Print_String("[COMM] Console ready (awaiting commands).\n"); Next_State = COMM_STATE_IDLE; xTicksToWait = 1; break; case COMM_STATE_IDLE: xTicksToWait = pdMS_TO_TICKS(100); break; case COMM_STATE_COMMAND_TOO_LONG: COMM_Console_Print_String("[COMM] ERROR: Command \""); COMM_Console_Print_String(Command_Buffer); COMM_Console_Print_String("\" too long!\n"); Reset_Command_Buffer(); Next_State = COMM_STATE_IDLE; xTicksToWait = 1; break; case COMM_STATE_IDENTIFY_COMMAND: Current_Command = UINT_FAST16_MAX; for (uint_fast16_t i = 0; i < COMM_N_CONSOLE_COMMANDS; i++) { if (ConsoleCommandMatches(COMM_Console_Command_Table[i].Command_Name) == true) { Current_Command = i; Next_State = COMM_STATE_EXECUTE_COMMAND; xTicksToWait = 1; break; } } if (Current_Command == UINT_FAST16_MAX) { // No matching command was found. Next_State = COMM_STATE_UNKNOWN_COMMAND; xTicksToWait = 1; } break; case COMM_STATE_EXECUTE_COMMAND: if (COMM_Console_Command_Table[Current_Command].Execute_Command != NULL) { COMM_Console_Command_Result_T result = COMM_Console_Command_Table[Current_Command].Execute_Command(Command_Buffer, Command_Buffer_Index); if (result == COMM_CONSOLE_CMD_RESULT_PARAMETER_ERROR) { COMM_Console_Print_String("ERROR: Parameter error!\n"); } } Reset_Command_Buffer(); Next_State = COMM_STATE_IDLE; xTicksToWait = 1; break; case COMM_STATE_UNKNOWN_COMMAND: COMM_Console_Print_String("ERROR: Command \""); COMM_Console_Print_String(Command_Buffer); COMM_Console_Print_String("\" not recognized! Try '?' for help.\n"); Reset_Command_Buffer(); Next_State = COMM_STATE_IDLE; xTicksToWait = 1; break; } } } SystemKResult_T HW_Execute_Console_Command(const uint8_t * const command) { COMM_Console_Execute_Internal_Command(command); return SYSTEMK_RESULT_SUCCESS; } //! Executes a (potentially cross-task) console command. /*! * This function is used to initiate a console command from a software source internal to this * CPU. This provides a way to use preexisting console commands on TX-only consoles. * * \note If two calls to this function are made back-to-back (before the COMM_Console_Task() has an * opportunity to run), only the second command will be executed, as it will have overwritten the * first. Allow time for the console commands to execute between calls to this function. * * \param command String containing the command to be executed. */ void COMM_Console_Execute_Internal_Command(const uint8_t * const command) { bool finished = false; uint_fast16_t i = 0; taskENTER_CRITICAL(); while ( (finished == false) && (i < COMM_CONSOLE_COMMAND_MAX_LENGTH) && (command[i] != COMM_CONSOLE_END_OF_MESSAGE ) && (command[i] != COMM_CONSOLE_STRING_TERMINATOR ) ) { Command_Buffer[i] = command[i]; i++; } Command_Buffer_Index = i; // If there is still room, terminate the command. if (i < COMM_CONSOLE_COMMAND_MAX_LENGTH) { Command_Buffer[i] = COMM_CONSOLE_END_OF_MESSAGE; } taskEXIT_CRITICAL(); Next_State = COMM_STATE_IDENTIFY_COMMAND; xTaskNotifyGive(COMM_Console_Task_Handle); } //! Prints a NULL-terminated string to the serial console. void COMM_Console_Print_String(const char8 * const text) { for (size_t i = 0; i < MAX_CONSOLE_STRING_LENGTH; i++) { // Check for the end of the string. If there is no NULL terminator, up to // MAX_CONSOLE_STRING_LENGTH characters of randomness will be printed. if (text[i] == COMM_CONSOLE_STRING_TERMINATOR) { break; } // Send out the string, one character at a time. COMM_Console_PutChar(text[i]); } } //! Prints a 32-bit unsigned integer to the serial console. void COMM_Console_Print_UInt32(uint32_t value) { // The largest string for a unit32_t is 10 characters (4294967296). char8 buffer[10+1]; uint_fast8_t buffer_index = 0; while (value > 9) { uint8_t digit_index = value % 10; buffer[buffer_index] = DIGITS[digit_index]; value = value / 10; buffer_index++; } buffer[buffer_index] = DIGITS[value]; buffer_index++; ReverseString(buffer, buffer_index); // NULL-terminate the string. buffer[buffer_index] = 0; COMM_Console_Print_String(buffer); } //! Prints a 32-bit signed integer to the serial console. void COMM_Console_Print_SInt32(int32_t value) { if (value < 0) { value *= -1; COMM_Console_PutChar('-'); } COMM_Console_Print_UInt32(value); } //! Prints a 32-bit unsigned integer to the serial console using a hexadecimal representation. void COMM_Console_Print_UInt32AsHex(uint32_t value) { // The largest hexadecimal string for a unit32_t is 8 characters (FFFFFFFF). char8 buffer[8+1]; uint_fast8_t buffer_index = 0; while (value > 15) { uint8_t digit_index = value % 16; buffer[buffer_index] = DIGITS[digit_index]; value = value / 16; buffer_index++; } buffer[buffer_index] = DIGITS[value]; buffer_index++; ReverseString(buffer, buffer_index); // NULL-terminate the string. buffer[buffer_index] = 0; COMM_Console_PutChar('0'); COMM_Console_PutChar('x'); COMM_Console_Print_String(buffer); } //! Prints a 64-bit unsigned integer to the serial console. void COMM_Console_Print_UInt64(uint64_t value) { // The largest string for a unit64_t is 20 characters (18446744073709551615). char8 buffer[20+1]; uint_fast8_t buffer_index = 0; while (value > 9) { uint8_t digit_index = value % 10; buffer[buffer_index] = DIGITS[digit_index]; value = value / 10; buffer_index++; } buffer[buffer_index] = DIGITS[value]; buffer_index++; ReverseString(buffer, buffer_index); // NULL-terminate the string. buffer[buffer_index] = 0; COMM_Console_Print_String(buffer); } //! Prints a 64-bit unsigned integer to the serial console using a hexadecimal representation. void COMM_Console_Print_UInt64AsHex(uint64_t value) { // The largest hexadecimal string for a unit64_t is 16 characters (FFFFFFFFFFFFFFFF). char8 buffer[16+1]; uint_fast8_t buffer_index = 0; while (value > 15) { uint8_t digit_index = value % 16; buffer[buffer_index] = DIGITS[digit_index]; value = value / 16; buffer_index++; } buffer[buffer_index] = DIGITS[value]; buffer_index++; ReverseString(buffer, buffer_index); // NULL-terminate the string. buffer[buffer_index] = 0; COMM_Console_PutChar('0'); COMM_Console_PutChar('x'); COMM_Console_Print_String(buffer); } //! Prints a floating-point number to the serial console. /*! * With thanks to Rick Regan and his [Quick and Dirty Floating-Point to Decimal Conversion](https://www.exploringbinary.com/quick-and-dirty-floating-point-to-decimal-conversion/). */ void COMM_Console_Print_Float(float value) { #define MAX_INTEGRAL_DIGITS 12 #define MAX_FRACTIONAL_DIGITS 6 #define BUFFER_SIZE (MAX_INTEGRAL_DIGITS + MAX_FRACTIONAL_DIGITS + 2) char8 buffer[BUFFER_SIZE]; char8 integral_buffer_reversed[MAX_INTEGRAL_DIGITS]; uint16_t buffer_index = 0; double integral_value; double fractional_value; bool overflow = false; if (value < 0.0) { COMM_Console_Print_String("-"); value *= -1.0; } // Break the given value into fractional and integral parts. fractional_value = modf(value, &integral_value); if (integral_value > 0) { // Convert the integral part. while ((integral_value > 0) && (buffer_index < MAX_INTEGRAL_DIGITS)) { integral_buffer_reversed[buffer_index++] = '0' + (int)fmod(integral_value, 10); integral_value = floor(integral_value / 10); } // If there is still an integral part remaining, and overflow has occurred. if (integral_value > 0) { overflow = true; } // Reverse and append the integral part. for (uint16_t i = 0; i < buffer_index; i++) { buffer[i] = integral_buffer_reversed[buffer_index-i-1]; } } else { // Append a leading zero. buffer[buffer_index++] = '0'; } // Append the decimal point. buffer[buffer_index++] = '.'; // Convert the fractional part, even if it is zero, and leave room for the NULL terminator. while (buffer_index < (BUFFER_SIZE - 1)) { fractional_value *= 10; buffer[buffer_index++] = '0' + (int)fractional_value; fractional_value = modf(fractional_value, &integral_value); } // Append the NULL terminator. buffer[buffer_index] = 0; if (overflow == true) { COMM_Console_Print_String("OVERFLOW"); } else { COMM_Console_Print_String(buffer); } } #endif // (CONFIG__FEATURE_COMM_CONSOLE == CONFIG__FEATURE_ENABLED) //! Converts a byte to a two-character hexadecimal representation. /*! * \param buffer Buffer into which to place the resulting sting. It needs to be at least three * characters wide. * \param byte The byte to be converted. */ void COMM_Console_ByteToHex(char8 * buffer, uint8_t byte) { if (byte < 16) { buffer[0] = '0'; buffer[1] = DIGITS[byte]; buffer[2] = 0; } else { buffer[0] = DIGITS[byte / 16]; buffer[1] = DIGITS[byte % 16]; buffer[2] = 0; } } #if (CONFIG__FEATURE_COMM_CONSOLE == CONFIG__FEATURE_ENABLED) /* Private Functions */ static void ConsoleISR(void) { portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE; // Check for the "Rx FIFO not empty" interrput. if ((UART_Console_HW->INTR_RX_MASKED & SCB_INTR_RX_MASKED_NOT_EMPTY_Msk ) != 0) { // Clear the "Rx FIFO not empty" interrput. UART_Console_HW->INTR_RX = UART_Console_HW->INTR_RX & SCB_INTR_RX_NOT_EMPTY_Msk; // Get the character. uint32_t value = UART_Console_Get(); // Check if there is actually data. Sometimes the flag is set when there is no data (why?). if (value != CY_SCB_UART_RX_NO_DATA) { char8 rx_data = (char8) value; // Determine what to do with it. if (Command_Buffer_Index < COMM_CONSOLE_COMMAND_MAX_LENGTH) { if (rx_data == COMM_CONSOLE_END_OF_MESSAGE) { Command_Buffer[Command_Buffer_Index] = COMM_CONSOLE_STRING_TERMINATOR; Next_State = COMM_STATE_IDENTIFY_COMMAND; vTaskNotifyGiveFromISR(COMM_Console_Task_Handle, &xHigherPriorityTaskWoken); } else { Command_Buffer[Command_Buffer_Index] = rx_data; Command_Buffer_Index++; } } else { Next_State = COMM_STATE_COMMAND_TOO_LONG; vTaskNotifyGiveFromISR(COMM_Console_Task_Handle, &xHigherPriorityTaskWoken); } } } NVIC_ClearPendingIRQ(Int_UART_Console_cfg.intrSrc); // If the state needs to change, a context switch might be required. portEND_SWITCHING_ISR(xHigherPriorityTaskWoken); } static bool ConsoleCommandMatches(const char8 * const command_name) { uint32_t i = 0; bool is_match = false; if (Command_Buffer[i] == command_name[i]) { is_match = true; i++; } while ( (is_match == true) && (i < COMM_CONSOLE_COMMAND_MAX_LENGTH) && (Command_Buffer[i] != COMM_CONSOLE_PARAMETER_DELIMITER) && (Command_Buffer[i] != COMM_CONSOLE_END_OF_MESSAGE ) && (Command_Buffer[i] != COMM_CONSOLE_STRING_TERMINATOR ) ) { if ( Command_Buffer[i] != command_name[i] ) { is_match = false; } i++; } return is_match; } //! Reverses a string in place. /*! * \param value Pointer to the string to be reversed. * \param length Length of the string, including the NULL terminator. */ static void ReverseString(char8 * value, uint32_t length) { if (length > 1) { uint_fast32_t start = 0; uint_fast32_t end = length - 1; while (start < end) { Swap_Char8(value + start, value + end); start++; end--; } } } #endif // (CONFIG__FEATURE_COMM_CONSOLE == CONFIG__FEATURE_ENABLED)