From bc1c129846783a62f85e728b1e9d68db0948a568 Mon Sep 17 00:00:00 2001 From: Simon Frei Date: Thu, 17 Nov 2022 14:06:20 +0100 Subject: [PATCH] splitflap: add rs485 module, mux rx,tx,rts pin for rs485 --- .../ADIS_tinyK22_SplitFlap.mex | 36 +- ADIS_tinyK22_SplitFlap/board/pin_mux.c | 52 +- ADIS_tinyK22_SplitFlap/board/pin_mux.h | 29 + ADIS_tinyK22_SplitFlap/source/platform.h | 1 + ADIS_tinyK22_SplitFlap/source/rs485.c | 630 ++++++++++++++++++ ADIS_tinyK22_SplitFlap/source/rs485.h | 61 ++ 6 files changed, 807 insertions(+), 2 deletions(-) create mode 100644 ADIS_tinyK22_SplitFlap/source/rs485.c create mode 100644 ADIS_tinyK22_SplitFlap/source/rs485.h diff --git a/ADIS_tinyK22_SplitFlap/ADIS_tinyK22_SplitFlap.mex b/ADIS_tinyK22_SplitFlap/ADIS_tinyK22_SplitFlap.mex index 8c181ae..81d5453 100644 --- a/ADIS_tinyK22_SplitFlap/ADIS_tinyK22_SplitFlap.mex +++ b/ADIS_tinyK22_SplitFlap/ADIS_tinyK22_SplitFlap.mex @@ -28,6 +28,9 @@ + + + @@ -39,13 +42,44 @@ true + + + true + + true + + + true + + - + + + + + + + + + + + + + + + + + + + + + + diff --git a/ADIS_tinyK22_SplitFlap/board/pin_mux.c b/ADIS_tinyK22_SplitFlap/board/pin_mux.c index 1dbb1b5..05ec3b0 100644 --- a/ADIS_tinyK22_SplitFlap/board/pin_mux.c +++ b/ADIS_tinyK22_SplitFlap/board/pin_mux.c @@ -16,11 +16,15 @@ pin_labels: - {pin_num: '45', pin_signal: ADC0_SE4b/CMP1_IN0/PTC2/SPI0_PCS2/UART1_CTS_b/FTM0_CH1/FB_AD12/I2S0_TX_FS/LPUART0_CTS_b, label: LED_BLUE, identifier: LED_BLUE} - {pin_num: '46', pin_signal: CMP1_IN1/PTC3/LLWU_P7/SPI0_PCS1/UART1_RX/FTM0_CH2/CLKOUT/I2S0_TX_BCLK/LPUART0_RX, label: UART1_RX, identifier: UART1_RX} - {pin_num: '49', pin_signal: PTC4/LLWU_P8/SPI0_PCS0/UART1_TX/FTM0_CH3/FB_AD11/CMP1_OUT/LPUART0_TX, label: UART1_TX, identifier: UART1_TX} +- {pin_num: '40', pin_signal: PTB17/SPI1_SIN/UART0_TX/FTM_CLKIN1/FB_AD16/EWM_OUT_b, label: RS485_TX, identifier: RS;RS485_TX} +- {pin_num: '39', pin_signal: PTB16/SPI1_SOUT/UART0_RX/FTM_CLKIN0/FB_AD17/EWM_IN, label: RS485_RX, identifier: RS485_RX} +- {pin_num: '37', pin_signal: ADC0_SE12/PTB2/I2C0_SCL/UART0_RTS_b/FTM0_FLT3, label: RS485_RTS, identifier: RS485_RTS} * BE CAREFUL MODIFYING THIS COMMENT - IT IS YAML SETTINGS FOR TOOLS *********** */ /* clang-format on */ #include "fsl_common.h" +#include "fsl_port.h" #include "pin_mux.h" /* FUNCTION ************************************************************************************************************ @@ -39,7 +43,11 @@ void BOARD_InitBootPins(void) * TEXT BELOW IS USED AS SETTING FOR TOOLS ************************************* BOARD_InitPins: - options: {callFromInitBoot: 'true', coreID: core0, enableClock: 'true'} -- pin_list: [] +- pin_list: + - {pin_num: '40', peripheral: UART0, signal: TX, pin_signal: PTB17/SPI1_SIN/UART0_TX/FTM_CLKIN1/FB_AD16/EWM_OUT_b, identifier: RS485_TX, direction: OUTPUT, pull_select: up, + pull_enable: enable} + - {pin_num: '39', peripheral: UART0, signal: RX, pin_signal: PTB16/SPI1_SOUT/UART0_RX/FTM_CLKIN0/FB_AD17/EWM_IN, pull_select: up, pull_enable: enable} + - {pin_num: '37', peripheral: UART0, signal: RTS, pin_signal: ADC0_SE12/PTB2/I2C0_SCL/UART0_RTS_b/FTM0_FLT3, pull_select: up, pull_enable: enable} * BE CAREFUL MODIFYING THIS COMMENT - IT IS YAML SETTINGS FOR TOOLS *********** */ /* clang-format on */ @@ -52,6 +60,48 @@ BOARD_InitPins: * END ****************************************************************************************************************/ void BOARD_InitPins(void) { + /* Port B Clock Gate Control: Clock enabled */ + CLOCK_EnableClock(kCLOCK_PortB); + + /* PORTB16 (pin 39) is configured as UART0_RX */ + PORT_SetPinMux(BOARD_INITPINS_RS485_RX_PORT, BOARD_INITPINS_RS485_RX_PIN, kPORT_MuxAlt3); + + PORTB->PCR[16] = ((PORTB->PCR[16] & + /* Mask bits to zero which are setting */ + (~(PORT_PCR_PS_MASK | PORT_PCR_PE_MASK | PORT_PCR_ISF_MASK))) + + /* Pull Select: Internal pullup resistor is enabled on the corresponding pin, if the + * corresponding PE field is set. */ + | (uint32_t)(kPORT_PullUp)); + + /* PORTB17 (pin 40) is configured as UART0_TX */ + PORT_SetPinMux(BOARD_INITPINS_RS485_TX_PORT, BOARD_INITPINS_RS485_TX_PIN, kPORT_MuxAlt3); + + PORTB->PCR[17] = ((PORTB->PCR[17] & + /* Mask bits to zero which are setting */ + (~(PORT_PCR_PS_MASK | PORT_PCR_PE_MASK | PORT_PCR_ISF_MASK))) + + /* Pull Select: Internal pullup resistor is enabled on the corresponding pin, if the + * corresponding PE field is set. */ + | (uint32_t)(kPORT_PullUp)); + + /* PORTB2 (pin 37) is configured as UART0_RTS_b */ + PORT_SetPinMux(BOARD_INITPINS_RS485_RTS_PORT, BOARD_INITPINS_RS485_RTS_PIN, kPORT_MuxAlt3); + + PORTB->PCR[2] = ((PORTB->PCR[2] & + /* Mask bits to zero which are setting */ + (~(PORT_PCR_PS_MASK | PORT_PCR_PE_MASK | PORT_PCR_ISF_MASK))) + + /* Pull Select: Internal pullup resistor is enabled on the corresponding pin, if the + * corresponding PE field is set. */ + | (uint32_t)(kPORT_PullUp)); + + SIM->SOPT5 = ((SIM->SOPT5 & + /* Mask bits to zero which are setting */ + (~(SIM_SOPT5_UART0TXSRC_MASK))) + + /* UART 0 transmit data source select: UART0_TX pin. */ + | SIM_SOPT5_UART0TXSRC(SOPT5_UART0TXSRC_UART_TX)); } /*********************************************************************************************************************** * EOF diff --git a/ADIS_tinyK22_SplitFlap/board/pin_mux.h b/ADIS_tinyK22_SplitFlap/board/pin_mux.h index 598ae8e..6c4afc8 100644 --- a/ADIS_tinyK22_SplitFlap/board/pin_mux.h +++ b/ADIS_tinyK22_SplitFlap/board/pin_mux.h @@ -25,6 +25,35 @@ extern "C" { */ void BOARD_InitBootPins(void); +#define SOPT5_UART0TXSRC_UART_TX 0x00u /*!<@brief UART 0 transmit data source select: UART0_TX pin */ + +/*! @name PORTB17 (number 40), RS485_TX + @{ */ + +/* Symbols to be used with PORT driver */ +#define BOARD_INITPINS_RS485_TX_PORT PORTB /*!<@brief PORT peripheral base pointer */ +#define BOARD_INITPINS_RS485_TX_PIN 17U /*!<@brief PORT pin number */ +#define BOARD_INITPINS_RS485_TX_PIN_MASK (1U << 17U) /*!<@brief PORT pin mask */ + /* @} */ + +/*! @name PORTB16 (number 39), RS485_RX + @{ */ + +/* Symbols to be used with PORT driver */ +#define BOARD_INITPINS_RS485_RX_PORT PORTB /*!<@brief PORT peripheral base pointer */ +#define BOARD_INITPINS_RS485_RX_PIN 16U /*!<@brief PORT pin number */ +#define BOARD_INITPINS_RS485_RX_PIN_MASK (1U << 16U) /*!<@brief PORT pin mask */ + /* @} */ + +/*! @name PORTB2 (number 37), RS485_RTS + @{ */ + +/* Symbols to be used with PORT driver */ +#define BOARD_INITPINS_RS485_RTS_PORT PORTB /*!<@brief PORT peripheral base pointer */ +#define BOARD_INITPINS_RS485_RTS_PIN 2U /*!<@brief PORT pin number */ +#define BOARD_INITPINS_RS485_RTS_PIN_MASK (1U << 2U) /*!<@brief PORT pin mask */ + /* @} */ + /*! * @brief Configures pin routing and optionally pin electrical features. * diff --git a/ADIS_tinyK22_SplitFlap/source/platform.h b/ADIS_tinyK22_SplitFlap/source/platform.h index 1079c2d..3c1bf74 100644 --- a/ADIS_tinyK22_SplitFlap/source/platform.h +++ b/ADIS_tinyK22_SplitFlap/source/platform.h @@ -11,6 +11,7 @@ /* SETTINGS */ #define PL_CONFIG_USE_NVMC (1) #define PL_CONFIG_USE_MININI (1 && PL_CONFIG_USE_NVMC) +#define PL_CONFIG_USE_RS485 (1) /* Platform initialization */ void PL_Init(void); diff --git a/ADIS_tinyK22_SplitFlap/source/rs485.c b/ADIS_tinyK22_SplitFlap/source/rs485.c new file mode 100644 index 0000000..36e9304 --- /dev/null +++ b/ADIS_tinyK22_SplitFlap/source/rs485.c @@ -0,0 +1,630 @@ +/* + * Copyright (c) 2019-2022, Erich Styger + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "platform.h" +#if PL_CONFIG_USE_RS485 +#include "rs485.h" +#include "McuGPIO.h" +#include "McuUart485.h" +#include "McuShell.h" +#include "McuRTOS.h" +#include "McuUtility.h" +#include "McuLog.h" +#include "Shell.h" +#if McuLib_CONFIG_CPU_IS_KINETIS || McuLib_CONFIG_CPU_IS_LPC + #include "nvmc.h" +#endif +#if PL_CONFIG_USE_WDT + #include "watchdog.h" +#endif +#include "stepper.h" + +typedef enum RS485_Response_e { + RS485_RESPONSE_CONTINUE, /* continue scanning and parsing */ + RS485_RESPONSE_OK, /* ok response */ + RS485_RESPONSE_NOK, /* not ok response */ + RS485_RESPONSE_TIMEOUT, /* timeout */ +} RS485_Response_e; + +static bool RS485_DoLogging = false; /* if traffic on the bus shall be reported on the shell */ +static SemaphoreHandle_t RS485_stdioMutex; /* mutex to protect access to standard I/O */ + +uint8_t RS485_GetAddress(void) { +#if McuLib_CONFIG_CPU_IS_KINETIS || McuLib_CONFIG_CPU_IS_LPC + uint8_t addr = 0; + + if (NVMC_GetRS485Addr(&addr)==ERR_OK) { + return addr; + } + return 0; /* failed */ +#elif McuLib_CONFIG_CPU_IS_ESP32 + return 1; /* hard coded */ +#endif +} + +static void RS485_SendChar(unsigned char ch) { + McuUart485_stdio.stdOut(ch); +} + +static void RS485_NullSend(unsigned char ch) { + /* do nothing */ +} + +static void RS485_ReadChar(uint8_t *c) { + *c = '\0'; /* only sending on this channel */ +} + +static bool RS485_CharPresent(void) { + return false; /* only sending on this channel */ +} + +McuShell_ConstStdIOType RS485_stdio = { + .stdIn = (McuShell_StdIO_In_FctType)RS485_ReadChar, + .stdOut = (McuShell_StdIO_OutErr_FctType)RS485_SendChar, + .stdErr = (McuShell_StdIO_OutErr_FctType)RS485_SendChar, + .keyPressed = RS485_CharPresent, /* if input is not empty */ + #if McuShell_CONFIG_ECHO_ENABLED + .echoEnabled = false, + #endif +}; + +McuShell_ConstStdIOType RS485_stdioBroadcast = { + .stdIn = (McuShell_StdIO_In_FctType)RS485_ReadChar, + .stdOut = (McuShell_StdIO_OutErr_FctType)RS485_NullSend, + .stdErr = (McuShell_StdIO_OutErr_FctType)RS485_NullSend, + .keyPressed = RS485_CharPresent, /* if input is not empty */ + #if McuShell_CONFIG_ECHO_ENABLED + .echoEnabled = false, + #endif +}; + +/*-----------------------------------------------------------------------*/ +/* parser I/O handler, used to parse the returned data after sending a command to the RS-485 bus */ +static void RS485Parse_SendChar(unsigned char ch) { + if (xSemaphoreTakeRecursive(RS485_stdioMutex, portMAX_DELAY)==pdPASS) { /* take mutex */ + McuUart485_stdio.stdOut(ch); + (void)xSemaphoreGiveRecursive(RS485_stdioMutex); /* give back mutex */ + } +} + +static void RS485Parse_ReadChar(uint8_t *c) { + if (xSemaphoreTakeRecursive(RS485_stdioMutex, portMAX_DELAY)==pdPASS) { /* take mutex */ + McuUart485_stdio.stdIn(c); + (void)xSemaphoreGiveRecursive(RS485_stdioMutex); /* give back mutex */ + } +} + +static bool RS485Parse_CharPresent(void) { + bool inputPresent = false; + + if (xSemaphoreTakeRecursive(RS485_stdioMutex, portMAX_DELAY)==pdPASS) { /* take mutex */ + inputPresent = McuUart485_stdio.keyPressed(); + (void)xSemaphoreGiveRecursive(RS485_stdioMutex); /* give back mutex */ + } + return inputPresent; +} + +static McuShell_ConstStdIOType RS485Parse_stdio = { + .stdIn = (McuShell_StdIO_In_FctType)RS485Parse_ReadChar, + .stdOut = (McuShell_StdIO_OutErr_FctType)RS485Parse_SendChar, + .stdErr = (McuShell_StdIO_OutErr_FctType)RS485Parse_SendChar, + .keyPressed = RS485Parse_CharPresent, /* if input is not empty */ + #if McuShell_CONFIG_ECHO_ENABLED + .echoEnabled = false, + #endif + }; +/*-----------------------------------------------------------------------*/ +static void RS485_SendStr(unsigned char *str) { + while(*str!='\0') { + RS485_stdio.stdOut(*str++); + } +} + +static uint8_t CalcCRC(const uint8_t *data, uint8_t dataSize, uint8_t start) { + uint8_t crc, i, x, y; + + crc = start; + for(x=0;x>= 1; + crc ^= 0x8c; + } else { + crc >>= 1; + } + y >>= 1; + } + } + return crc; +} + +static uint8_t CalcMsgCrc(const unsigned char *msg) { + /* header is "@dd ss cc ...", both dd, ss and cc are 8bit HEX values. The CRC (cc) is not included in the CRC */ + uint8_t crc; + + crc = CalcCRC(msg, sizeof("@dd ss ")-1, 0); + crc = CalcCRC(msg+sizeof("@dd ss cc")-1, strlen((char*)msg+sizeof("@dd ss cc")-1), crc); + return crc; +} + +typedef enum CMD_ParserState_e { + CMD_PARSER_INIT, + CMD_PARSER_START_DETECTED, /* start '@' detected */ + CMD_PARSER_SCAN_DST_ADDR, /* scanning destination address */ + CMD_PARSER_SCAN_SRC_ADDR, /* scan source address */ + CMD_PARSER_SCAN_CRC, /* scan CRC */ + CMD_PARSER_SCAN_OK_NOK, /* reading NOK or OK */ +} CMD_ParserState_e; + +static RS485_Response_e Scan(CMD_ParserState_e *state, unsigned char ch, unsigned char *buf, size_t bufSize, uint8_t fromAddr) { + /* scan for "@ OK" + * or "@ NOK" + */ + const unsigned char *p; + uint8_t addr; + uint8_t exp_crc; + static uint8_t crc = 0; + + if (ch=='@') { /* a marker? start scanning again */ + *state = CMD_PARSER_INIT; + } + for(;;) { /* breaks or returns */ + switch(*state) { + case CMD_PARSER_INIT: + buf[0] = '\0'; /* reset buffer */ + if (ch=='@') { /* a marker? start scanning again */ + McuUtility_chcat(buf, bufSize, ch); + *state = CMD_PARSER_START_DETECTED; + break; /* continue state machine */ + } + return RS485_RESPONSE_CONTINUE; + + case CMD_PARSER_START_DETECTED: + *state = CMD_PARSER_SCAN_DST_ADDR; + return RS485_RESPONSE_CONTINUE; + + case CMD_PARSER_SCAN_DST_ADDR: + McuUtility_chcat(buf, bufSize, ch); + if (ch==' ') { + p = &buf[sizeof("@")-1]; + if (McuUtility_ScanHex8uNumberNoPrefix(&p, &addr)==ERR_OK && addr==RS485_GetAddress()) { + *state = CMD_PARSER_SCAN_SRC_ADDR; + return RS485_RESPONSE_CONTINUE; + } else { + *state = CMD_PARSER_INIT; + } + } else if (ch=='\n') { /* failed */ + *state = CMD_PARSER_INIT; + } + return RS485_RESPONSE_CONTINUE; + + case CMD_PARSER_SCAN_SRC_ADDR: + McuUtility_chcat(buf, bufSize, ch); + if (ch==' ') { + p = &buf[sizeof("@ss ")-1]; + if (McuUtility_ScanHex8uNumberNoPrefix(&p, &addr)==ERR_OK && addr==fromAddr) { + *state = CMD_PARSER_SCAN_CRC; + return RS485_RESPONSE_CONTINUE; + } else { + *state = CMD_PARSER_INIT; + } + } else if (ch=='\n') { /* failed */ + *state = CMD_PARSER_INIT; + } + return RS485_RESPONSE_CONTINUE; + + case CMD_PARSER_SCAN_CRC: + McuUtility_chcat(buf, bufSize, ch); + if (ch==' ') { + p = &buf[sizeof("@ss dd ")-1]; + if (McuUtility_ScanHex8uNumberNoPrefix(&p, &crc)==ERR_OK) { + *state = CMD_PARSER_SCAN_OK_NOK; + return RS485_RESPONSE_CONTINUE; + } else { + *state = CMD_PARSER_INIT; + } + } else if (ch=='\n') { /* failed */ + *state = CMD_PARSER_INIT; + } + return RS485_RESPONSE_CONTINUE; + + case CMD_PARSER_SCAN_OK_NOK: + McuUtility_chcat(buf, bufSize, ch); + if (ch=='\n') { + p = &buf[sizeof("@ss dd cc ")-1]; + if (McuUtility_strcmp((char*)p, (char*)"OK\n")==0) { /* a match! */ + buf[sizeof("@ss dd cc OK")-1] = '\0'; /* overwrite '\n' as not included in CRC */ + exp_crc = CalcMsgCrc(buf); + *state = CMD_PARSER_INIT; + if (crc==exp_crc) { + return RS485_RESPONSE_OK; + } else { + return RS485_RESPONSE_NOK; + } + } else if (McuUtility_strcmp((char*)buf, (char*)"NOK\n")==0) { /* a match! */ + buf[sizeof("@ss dd cc NOK")-1] = '\0'; /* overwrite '\n' as not included in CRC */ + exp_crc = CalcMsgCrc(buf); + *state = CMD_PARSER_INIT; + if (crc==exp_crc) { + return RS485_RESPONSE_NOK; + } else { + return RS485_RESPONSE_NOK; /* CRC is not ok, and message is NOK too */ + } + } else if (ch=='\n') { /* failed */ + *state = CMD_PARSER_INIT; + break; /* continue in state machine */ + } + } + return RS485_RESPONSE_CONTINUE; + + default: + break; + } /* switch */ + /* get here with a break */ + } /* for */ + return RS485_RESPONSE_CONTINUE; +} + +static RS485_Response_e WaitForResponse(int32_t timeoutMs, uint8_t fromAddr, McuShell_ConstStdIOType *shellIO, McuShell_ConstStdIOType *rsIO) { + unsigned char buf[24] = ""; /* enough for "@ OK" or "@ NOK" */ + unsigned char ch; + RS485_Response_e resp; + CMD_ParserState_e state = CMD_PARSER_INIT; + + for(;;) { /* returns */ + /* read response text and write into buffer or to console */ + static unsigned char lineBuffer[512]; /* enough for a line of text coming back from the bus */ + + lineBuffer[0] = '\0'; /* initialize buffer */ + do { + rsIO->stdIn(&ch); + if (ch!='\0') { + McuUtility_chcat(lineBuffer, sizeof(lineBuffer), ch); + if (ch=='\n') { + if (lineBuffer[0]!='@') { /* do not send things like OK or NOK messages from bus */ + SHELL_SendStringToIO(lineBuffer, shellIO); + } + lineBuffer[0] = '\0'; /* reset buffer */ + } + } + } while(ch!='\0'); + + ch = McuUart485_GetResponseQueueChar(); + if (ch!='\0') { + resp = Scan(&state, ch, buf, sizeof(buf), fromAddr); + if (resp==RS485_RESPONSE_OK || resp==RS485_RESPONSE_NOK) { + return resp; + } + } else { /* empty response buffer: check normal incoming characters */ + vTaskDelay(pdMS_TO_TICKS(50)); + #if PL_CONFIG_USE_WDT + WDT_Report(WDT_REPORT_ID_CURR_TASK, 50); + #endif + timeoutMs -= 50; + if (timeoutMs<=0) { + return RS485_RESPONSE_TIMEOUT; + } + } + } /* for */ + return RS485_RESPONSE_CONTINUE; +} + +uint8_t RS485_SendCommand(uint8_t dstAddr, const unsigned char *cmd, int32_t timeoutMs, uint32_t nofRetry, McuShell_ConstStdIOType *shellIO, McuShell_ConstStdIOType *rsIO) { + /* example: send "@16 1 cmd stepper status" */ + unsigned char buf[McuShell_DEFAULT_SHELL_BUFFER_SIZE]; + uint8_t res = ERR_OK; + RS485_Response_e resp; + uint8_t crc, hex; + + if (rsIO==NULL) { /* assign default */ + rsIO = &RS485Parse_stdio; + } + McuUtility_strcpy(buf, sizeof(buf), (unsigned char*)("@")); + McuUtility_strcatNum8Hex(buf, sizeof(buf), dstAddr); /* add destination address */ + McuUtility_chcat(buf, sizeof(buf), ' '); + McuUtility_strcatNum8Hex(buf, sizeof(buf), RS485_GetAddress()); /* add src address */ + McuUtility_chcat(buf, sizeof(buf), ' '); + McuUtility_strcatNum8Hex(buf, sizeof(buf), 0); /* dummy crc, will be replaced with real one */ + McuUtility_strcat(buf, sizeof(buf), (unsigned char*)(" cmd ")); + McuUtility_strcat(buf, sizeof(buf), cmd); + /* update crc */ + crc = CalcMsgCrc(buf); + hex = (char)((crc>>4) & 0x0F); + buf[sizeof("@dd ss ")-1] = (char)(hex + ((hex <= 9) ? '0' : ('A'-10))); + hex = (char)(crc & 0x0F); + buf[sizeof("@dd ss c")-1] = (char)(hex + ((hex <= 9) ? '0' : ('A'-10))); + + if (xSemaphoreTakeRecursive(RS485_stdioMutex, portMAX_DELAY)==pdPASS) { /* take mutex */ + for(;;) { /* breaks */ + McuUart485_ClearResponseQueue(); /* clear up if there is something pending */ + if (RS485_DoLogging) { + McuLog_trace("Tx: %s", buf); + } + RS485_SendStr(buf); + RS485_SendStr((unsigned char*)"\n"); + if (dstAddr==RS485_BROADCAST_ADDRESS) { + /* do not wait for a OK/NOK response for broadcast messages. The caller has to check with 'lastError' */ + res = ERR_OK; + break; /* leave loop */ + } else { + vTaskDelay(pdMS_TO_TICKS(100)); /* give back some time for receiving a response */ + resp = WaitForResponse(timeoutMs, dstAddr, shellIO, rsIO); + if (resp==RS485_RESPONSE_OK) { + res = ERR_OK; + break; /* fine, leave loop */ + } else if (resp==RS485_RESPONSE_TIMEOUT) { /* board did not respond? */ + res = ERR_BUSOFF; /* retry */ + } else if (resp==RS485_RESPONSE_NOK) { /* not ok, crc error? */ + res = ERR_CRC; /* retry */ + } + } + /* NOK or timeout */ + if (nofRetry==0) { /* tried enough */ + res = ERR_FAILED; + break; /* leave loop */ + } + nofRetry--; /* try again */ + } /* for */ + (void)xSemaphoreGiveRecursive(RS485_stdioMutex); /* give back mutex */ + } + return res; +} + +static uint8_t PrintStatus(const McuShell_StdIOType *io) { + uint8_t buf[16]; + + McuShell_SendStatusStr((unsigned char*)"rs", (unsigned char*)"RS-485 settings\r\n", io->stdOut); + McuUtility_strcpy(buf, sizeof(buf), (unsigned char*)"0x"); + McuUtility_strcatNum8Hex(buf, sizeof(buf), RS485_GetAddress()); + McuUtility_strcat(buf, sizeof(buf), (unsigned char*)"\r\n"); + McuShell_SendStatusStr((unsigned char*)" addr", buf, io->stdOut); + McuShell_SendStatusStr((unsigned char*)" log", RS485_DoLogging?(unsigned char*)"on\r\n":(unsigned char*)"off\r\n", io->stdOut); + return ERR_OK; +} + +static uint8_t PrintHelp(const McuShell_StdIOType *io) { + McuShell_SendHelpStr((unsigned char*)"rs", (unsigned char*)"Group of RS-485 commands\r\n", io->stdOut); + McuShell_SendHelpStr((unsigned char*)" help|status", (unsigned char*)"Print help or status information\r\n", io->stdOut); +#if PL_CONFIG_USE_NVMC + McuShell_SendHelpStr((unsigned char*)" addr ", (unsigned char*)"Set RS-485 address\r\n", io->stdOut); +#endif + McuShell_SendHelpStr((unsigned char*)" send ", (unsigned char*)"Send a text to the RS-485 bus\r\n", io->stdOut); + McuShell_SendHelpStr((unsigned char*)" sendcmd ", (unsigned char*)"Send a shell command to the RS-485 address and check response\r\n", io->stdOut); + McuShell_SendHelpStr((unsigned char*)" log on|off", (unsigned char*)"Log RS-485 bus activity to McuLog\r\n", io->stdOut); + return ERR_OK; +} + +uint8_t RS485_ParseCommand(const unsigned char *cmd, bool *handled, const McuShell_StdIOType *io) { + const unsigned char *p; + int32_t val; + + if (McuUtility_strcmp((char*)cmd, McuShell_CMD_HELP)==0 || McuUtility_strcmp((char*)cmd, "rs help")==0) { + *handled = TRUE; + return PrintHelp(io); + } else if ((McuUtility_strcmp((char*)cmd, McuShell_CMD_STATUS)==0) || (McuUtility_strcmp((char*)cmd, "rs status")==0)) { + *handled = TRUE; + return PrintStatus(io); + } else if (McuUtility_strncmp((char*)cmd, "rs log ", sizeof("rs log ")-1)==0) { + *handled = TRUE; + p = cmd + sizeof("rs log ")-1; + if (McuUtility_strcmp((char*)p, "on")==0) { + RS485_DoLogging = true; + return ERR_OK; + } else if (McuUtility_strcmp((char*)p, "off")==0) { + RS485_DoLogging = false; + return ERR_OK; + } + return ERR_FAILED; +#if PL_CONFIG_USE_NVMC + } else if (McuUtility_strncmp((char*)cmd, "rs addr ", sizeof("rs addr ")-1)==0) { + *handled = true; + p = cmd + sizeof("rs addr ")-1; + if (McuUtility_xatoi(&p, &val)==ERR_OK && val>=0 && val<=0xff) { + return NVMC_SetRS485Addr(val); + } + return ERR_FAILED; +#endif + } else if (McuUtility_strncmp((char*)cmd, "rs send ", sizeof("rs send ")-1)==0) { + *handled = true; + RS485_SendStr((unsigned char*)cmd+sizeof("rs send ")-1); + RS485_SendStr((unsigned char*)("\n")); + } else if (McuUtility_strncmp((char*)cmd, "rs sendcmd ", sizeof("rs sendcmd ")-1)==0) { + *handled = true; + p = cmd + sizeof("rs sendcmd ")-1; + if (McuUtility_xatoi(&p, &val)==ERR_OK) { /* parse destination address */ + unsigned char buffer[McuShell_CONFIG_DEFAULT_SHELL_BUFFER_SIZE]; + + while (*p==' ') { /* skip leading spaces */ + p++; + } + if (*p=='"') { /* double-quoted command: it can contain multiple commands */ + if (McuUtility_ScanDoubleQuotedString(&p, buffer, sizeof(buffer))!=ERR_OK) { + return ERR_FAILED; + } + p = buffer; + } + return RS485_SendCommand(val, (unsigned char*)p, 10000, 0, io, &RS485Parse_stdio); /* 10 seconds should be enough */ + } + return ERR_FAILED; + } + return ERR_OK; +} + +static uint8_t CheckHeader(unsigned char *msg, const unsigned char **startCmd, uint8_t *sourceAddr, uint8_t *destinationAddr) { + /* format is in the form "@ cmd help" */ + const unsigned char *p; + uint8_t dstAddr, srcAddr; + uint8_t expected_crc, crc, res; + unsigned char buf[42]; + + /* init with defaults in case of error */ + *sourceAddr = RS485_BROADCAST_ADDRESS; + *destinationAddr = RS485_BROADCAST_ADDRESS; + *startCmd = msg; + if (*msg=='@') { + p = msg+1; /* skip '@' */ + /* check for "@ " */ + if ( McuUtility_ScanHex8uNumberNoPrefix(&p, &dstAddr)==ERR_OK + && (dstAddr==RS485_GetAddress() || dstAddr==RS485_BROADCAST_ADDRESS) /* broadcast or matching destination address */ + && McuUtility_ScanHex8uNumberNoPrefix(&p, &srcAddr)==ERR_OK /* get source address */ + ) + { + *sourceAddr = srcAddr; + *destinationAddr = dstAddr; + /* check CRC */ + res = McuUtility_ScanHex8uNumberNoPrefix(&p, &crc); + if (res!=ERR_OK) { + return ERR_CRC; + } + expected_crc = CalcMsgCrc(msg); + if (crc!=expected_crc) { + if (dstAddr!=RS485_BROADCAST_ADDRESS) { /* only send back error if it was not a broadcast */ + McuUtility_strcpy(buf, sizeof(buf), (uint8_t*)"CRC_ERR 0x"); + McuUtility_strcatNum8Hex(buf, sizeof(buf), RS485_GetAddress()); + McuUtility_strcat(buf, sizeof(buf), (uint8_t*)": 0x"); + McuUtility_strcatNum8Hex(buf, sizeof(buf), crc); + McuUtility_strcat(buf, sizeof(buf), (uint8_t*)" expected 0x"); + McuUtility_strcatNum8Hex(buf, sizeof(buf), expected_crc); + McuUtility_chcat(buf, sizeof(buf), '\n'); + McuShell_SendStr(buf, RS485_stdio.stdErr); + } + return ERR_CRC; + } + *startCmd = p; + return ERR_OK; + } + } + return ERR_FAILED; +} + +static void RS485Task(void *pv) { + static uint8_t cmdBuf[McuShell_DEFAULT_SHELL_BUFFER_SIZE]; /* command line and text from the RS-485 bus */ + const unsigned char *startCmd; + uint8_t srcAddr, dstAddr; + uint8_t res, crc; + uint8_t buf[32]; + unsigned char hex; + bool reply; + static uint8_t lastError = ERR_OK; + + (void)pv; /* not used */ + McuLog_trace("Starting RS485 Task"); + #if PL_CONFIG_USE_WDT + WDT_SetTaskHandle(WDT_REPORT_ID_TASK_RS485, xTaskGetCurrentTaskHandle()); + #endif + cmdBuf[0] = '\0'; + for(;;) { + while (!McuUart485_stdio.keyPressed()) { /* if nothing in input queue, give back some CPU time */ + vTaskDelay(pdMS_TO_TICKS(10)); + #if PL_CONFIG_USE_WDT + WDT_Report(WDT_REPORT_ID_TASK_RS485, 10); + #endif + } + if (McuShell_ReadCommandLine(cmdBuf, sizeof(cmdBuf), &RS485Parse_stdio)==ERR_OK) { + reply = false; + srcAddr = RS485_ILLEGAL_ADDRESS; + dstAddr = RS485_ILLEGAL_ADDRESS; + if (cmdBuf[0]=='@' && strlen((char*)cmdBuf)>sizeof("@dd ss cc ")-1) { /* have a valid message? */ + if (RS485_DoLogging) { + McuLog_trace("Rx: %s", cmdBuf); + } + reply = false; /* default */ + res = CheckHeader(cmdBuf, &startCmd, &srcAddr, &dstAddr); + if (res == ERR_CRC) { /* wrong crc */ + lastError = ERR_CRC; + reply = true; + } else if (res==ERR_OK) { /* header was ok */ + res = ERR_FAILED; /* set default return value */ + if (McuUtility_strcmp((char*)startCmd, (char*)" cmd lastError")==0) { + reply = true; + res = lastError; /* report back last error */ + lastError = ERR_OK; /* clear error */ + } else if (McuUtility_strcmp((char*)startCmd, (char*)" cmd idle")==0) { + reply = true; +#if 0 /* \TODO */ + if (STEPPER_IsIdle()) { + res = ERR_OK; /* ERR_OK if board is idle */ + } else { + res = ERR_FAILED; /* not idle */ + } +#else + res = ERR_FAILED; /* not idle */ +#endif + } else if (McuUtility_strncmp((char*)startCmd, " cmd ", sizeof(" cmd ")-1)==0) { /* shell command? */ + McuUart485_ClearResponseQueue(); /* clear any pending response: we are going to parse a new command */ + startCmd += sizeof(" cmd ")-1; + if (dstAddr==RS485_BROADCAST_ADDRESS) { + res = SHELL_ParseCommandIO(startCmd, &RS485_stdioBroadcast, true); /* do not write anything back if broadcast */ + } else { + res = SHELL_ParseCommandIO(startCmd, &RS485_stdio, true); + } + lastError = res; /* remember error status if we get asked later on */ + reply = true; + } else if (McuUtility_strcmp((char*)startCmd, (char*)" OK")==0) { + reply = false; + } else if (McuUtility_strcmp((char*)startCmd, (char*)" NOK")==0) { + reply = false; + } + } + } else { + /* not starting with '@', print it ... */ + McuUtility_strcat(cmdBuf, sizeof(cmdBuf), (unsigned char*)"\r\n"); /* for the shell parser, the new-line has been removed. Add it again for output */ + SHELL_SendString((unsigned char *)cmdBuf); /* \TODO do not send directly to UART: instead, use a stdio which buffers the output */ + } + cmdBuf[0] = '\0'; /* reset buffer for next iteration */ + /* send response back to sender */ + if (reply && dstAddr!=RS485_BROADCAST_ADDRESS) { /* normal message, send response. For broadcasts it is up to the caller to check the last error */ + McuUtility_strcpy(buf, sizeof(buf), (unsigned char*)"@"); + McuUtility_strcatNum8Hex(buf, sizeof(buf), srcAddr); + McuUtility_chcat(buf, sizeof(buf), ' '); + McuUtility_strcatNum8Hex(buf, sizeof(buf), RS485_GetAddress()); + McuUtility_chcat(buf, sizeof(buf), ' '); + McuUtility_strcatNum8Hex(buf, sizeof(buf), 0); /* dummy crc, will be replaced later */ + if (res==ERR_OK) { + McuUtility_strcat(buf, sizeof(buf), (unsigned char*)" OK"); + } else { + McuUtility_strcat(buf, sizeof(buf), (unsigned char*)" NOK"); + } + crc = CalcMsgCrc(buf); + hex = (char)((crc>>4) & 0x0F); + buf[sizeof("@dd ss ")-1] = (char)(hex + ((hex <= 9) ? '0' : ('A'-10))); + hex = (char)(crc & 0x0F); + buf[sizeof("@dd ss c")-1] = (char)(hex + ((hex <= 9) ? '0' : ('A'-10))); + McuUtility_chcat(buf, sizeof(buf), '\n'); + RS485_SendStr(buf); + } + } + } /* for */ +} + +void RS485_Deinit(void) { + McuUart485_Deinit(); +} + +void RS485_Init(void) { + McuUart485_Init(); + if (xTaskCreate( + RS485Task, /* pointer to the task */ + "RS-485", /* task name for kernel awareness debugging */ + 1300/sizeof(StackType_t), /* task stack size */ + (void*)NULL, /* optional task startup argument */ + tskIDLE_PRIORITY+4, /* initial priority */ + (TaskHandle_t*)NULL /* optional task handle to create */ + ) != pdPASS) + { + McuLog_fatal("Failed creating RS-485 task"); + for(;;){} /* error! probably out of memory */ + } + RS485_stdioMutex = xSemaphoreCreateRecursiveMutex(); + if (RS485_stdioMutex==NULL) { /* creation failed? */ + McuLog_fatal("Failed creating RS-485 Standard I/O mutex"); + for(;;); + } + vQueueAddToRegistry(RS485_stdioMutex, "RS485StdIoMutex"); +} + +#endif /* PL_CONFIG_USE_RS485 */ diff --git a/ADIS_tinyK22_SplitFlap/source/rs485.h b/ADIS_tinyK22_SplitFlap/source/rs485.h new file mode 100644 index 0000000..765d3d8 --- /dev/null +++ b/ADIS_tinyK22_SplitFlap/source/rs485.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2019-2022, Erich Styger + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef RS485_H_ +#define RS485_H_ + +#include +#include +#include "McuShell.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/*! special pre-defined node addresses */ +#define RS485_BROADCAST_ADDRESS (0x00) + /*!< special broadcast address */ +#define RS485_ILLEGAL_ADDRESS (0xff) + /*!< illegal/initialization value */ + +/*! + * \brief Send a shell command to the RS-485 bus + * \param dstAddr Destination node address + * \param cmd The shell command string + * \param timeoutMs timeout in milliseconds to wait for a response + * \param nofRetry Number of retries + * \param shellIO I/O handler of the calling shell, used for writing output of the command + * \param rsIO I/O handler for the RS-485 bus, used to read incoming characters + * \return Error code, or ERR_OK + */ +uint8_t RS485_SendCommand(uint8_t dstAddr, const unsigned char *cmd, int32_t timeoutMs, uint32_t nofRetry, McuShell_ConstStdIOType *shellIO, McuShell_ConstStdIOType *rsIO); + +/*! + * \brief Getter for RS-485 node address + * \return address of node + */ +uint8_t RS485_GetAddress(void); + +/*! + * \brief Shell command line parser + * \param cmd command to be parsed + * \param handled if command was recognized + * \param io I/O handler + * \return Error code, or ERR_OK + */ +uint8_t RS485_ParseCommand(const unsigned char *cmd, bool *handled, const McuShell_StdIOType *io); + +/*! \brief Module de-initialization */ +void RS485_Deinit(void); + +/*! \brief Module initialization */ +void RS485_Init(void); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* RS485_H_ */