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_ */