/************************************************************************** * * Copyright (c) 2019-2020 Diality Inc. - All Rights Reserved. * * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. * * @file SystemComm.c * * @author (last) Quang Nguyen * @date (last) 26-Aug-2020 * * @author (original) Dara Navaei * @date (original) 05-Nov-2019 * ***************************************************************************/ #include // for memcpy() #include "can.h" #include "sci.h" #include "sys_dma.h" #include "SystemComm.h" #include "Comm.h" #include "Interrupts.h" #include "Timers.h" #include "Utilities.h" #include "SystemCommMessages.h" /** * @addtogroup SystemComm * @{ */ // ********** private definitions ********** #define NUM_OF_CAN_OUT_BUFFERS 5 ///< Number of CAN buffers for transmit #define NUM_OF_CAN_IN_BUFFERS 6 ///< Number of CAN buffers for receiving #ifndef DEBUG_ENABLED #define NUM_OF_MSG_IN_BUFFERS 6 ///< Number of Msg buffers for receiving #else #define NUM_OF_MSG_IN_BUFFERS 7 #define SCI1_RECEIVE_DMA_REQUEST 30 #define SCI1_TRANSMIT_DMA_REQUEST 31 #endif #define CAN_XMIT_PACKET_TIMEOUT_MS 200 ///< if transmitted CAN frame does not cause a transmit complete interrupt within this time, re-send or move on #define MAX_XMIT_RETRIES 5 ///< maximum number of retries on no transmit complete interrupt timeout #define HD_COMM_TIMEOUT_IN_MS 2000 ///< HD has not sent any broadcast messages for this much time #define MAX_COMM_CRC_FAILURES 5 ///< maximum number of CRC errors within window period before alarm #define MAX_COMM_CRC_FAILURE_WINDOW_MS (10 * SEC_PER_MIN * MS_PER_SECOND) ///< CRC error window #define MSG_NOT_ACKED_TIMEOUT_MS ( MS_PER_SECOND * 1 ) ///< maximum time for a Denali message that requires ACK to be ACK'd #define MSG_NOT_ACKED_MAX_RETRIES 3 ///< maximum number of times a message that requires ACK that was not ACK'd can be re-sent before alarm #define PENDING_ACK_LIST_SIZE 25 ///< maximum number of Delanli messages that can be pending ACK at any given time #pragma pack(push, 1) /// Record for transmitted message that is pending acknowledgement from receiver. typedef struct { BOOL used; ///< Flag indicates whether the pending ACK slot is used or not U16 seqNo; ///< Sequence number U16 retries; ///< Retry count U32 timeStamp; ///< Message time stamp COMM_BUFFER_T channel; ///< Comm buffer channel U32 msgSize; ///< Message size U08 msg[ MAX_ACK_MSG_SIZE ]; ///< Bytes representation of the message } PENDING_ACK_RECORD_T; #pragma pack(pop) // ********** private data ********** /// Array of out-going CAN buffers. const COMM_BUFFER_T CAN_OUT_BUFFERS[ NUM_OF_CAN_OUT_BUFFERS ] = { COMM_BUFFER_OUT_CAN_DG_ALARM, COMM_BUFFER_OUT_CAN_DG_2_HD, COMM_BUFFER_OUT_CAN_DG_BROADCAST, COMM_BUFFER_OUT_CAN_PC, COMM_BUFFER_OUT_CAN_DG_2_UI }; /// Array of in-coming CAN buffers. const COMM_BUFFER_T MSG_IN_BUFFERS[ NUM_OF_MSG_IN_BUFFERS ] = { COMM_BUFFER_IN_CAN_HD_ALARM, COMM_BUFFER_IN_CAN_UI_ALARM, COMM_BUFFER_IN_CAN_HD_2_DG, COMM_BUFFER_IN_CAN_HD_BROADCAST, COMM_BUFFER_IN_CAN_UI_BROADCAST, COMM_BUFFER_IN_CAN_PC, #ifdef DEBUG_ENABLED COMM_BUFFER_IN_UART_PC #endif }; static U08 lastCANPacketSent[ CAN_MESSAGE_PAYLOAD_SIZE ]; ///< Keep last packet sent on CAN bus in case we need to re-send. static CAN_MESSAGE_BOX_T lastCANPacketSentChannel = (CAN_MESSAGE_BOX_T)0; ///< Keep channel last packet was sent on CAN bus in case we need to re-send. static U32 lastCANPacketSentTimeStamp = 0; ///< Keep time last packet sent on CAN bus so we can timeout on transmission attempt. static PENDING_ACK_RECORD_T pendingAckList[ PENDING_ACK_LIST_SIZE ]; ///< list of outgoing messages that are awaiting an ACK static volatile BOOL dgIsOnlyCANNode = TRUE; ///< flag indicating whether DG is alone on CAN bus. static U32 canXmitRetryCtr = 0; ///< counter for CAN transmit retries. static volatile BOOL hdIsCommunicating = FALSE; ///< has HD sent a message since last check static volatile U32 timeOfLastHDCheckIn = 0; ///< last time we received an HD broadcast #ifdef DEBUG_ENABLED // debug buffers static U08 pcXmitPacket[ 1024 ]; static U08 pcRecvPacket[ PC_MESSAGE_PACKET_SIZE ] = { 0, 0, 0, 0, 0, 0, 0, 0 }; // DMA control records static g_dmaCTRL pcDMAXmitControlRecord; static g_dmaCTRL pcDMARecvControlRecord; #endif // ********** private function prototypes ********** static void clearCANXmitBuffers( void ); static COMM_BUFFER_T findNextHighestPriorityCANPacketToTransmit( void ); static U32 transmitNextCANPacket( void ); static void processIncomingData( void ); static S32 parseMessageFromBuffer( U08 *data, U32 len ); static void consumeBufferPaddingBeforeSync( COMM_BUFFER_T buffer ); static void processReceivedMessages( void ); static void processReceivedMessage( MESSAGE_T *message ); static void checkForCommTimeouts( void ); static void checkTooManyBadMsgCRCs( void ); static BOOL matchACKtoPendingACKList( S16 seqNo ); static void checkPendingACKList( void ); #ifdef DEBUG_ENABLED static void initUARTAndDMA( void ); static U32 transmitNextUARTPacket( void ); #endif /*********************************************************************//** * @brief * The initSystemComm function initializes the SystemComm module. * @details * Inputs : none * Outputs : SystemComm module initialized. * @return none *************************************************************************/ void initSystemComm( void ) { U32 i; #ifdef DEBUG_ENABLED // initialize UART and DMA for PC communication initUARTAndDMA(); #endif // initialize bad message CRC time windowed count initTimeWindowedCount( TIME_WINDOWED_COUNT_BAD_MSG_CRC, MAX_COMM_CRC_FAILURES, MAX_COMM_CRC_FAILURE_WINDOW_MS ); // initialize pending ACK list for ( i = 0; i < PENDING_ACK_LIST_SIZE; i++ ) { pendingAckList[ i ].used = FALSE; } } /*********************************************************************//** * @brief * The isHDCommunicating function determines whether the HD is communicating * with the DG. * @details * Inputs : hdIsCommunicating * Outputs : none * @return TRUE if HD has broadcast since last call, FALSE if not *************************************************************************/ BOOL isHDCommunicating( void ) { return hdIsCommunicating; } /*********************************************************************//** * @brief * The isDGOnlyCANNode function determines whether the DG is the only node * currently on the CAN bus. * @details * Inputs : dgIsOnlyCANNode * Outputs : none * @return TRUE if DG is only node on CAN bus, FALSE if not *************************************************************************/ BOOL isDGOnlyCANNode( void ) { return dgIsOnlyCANNode; } /*********************************************************************//** * @brief * The execSystemCommRx function manages received data from other sub-systems. * @details * Inputs : none * Outputs : Incoming frames processed. * @return none *************************************************************************/ void execSystemCommRx( void ) { // parse messages from comm buffers and queue them processIncomingData(); // process received messages in the queue processReceivedMessages(); // check for sub-system comm timeouts checkForCommTimeouts(); // check ACK list for messages that need to be re-sent because they haven't been ACK'd checkPendingACKList(); } /*********************************************************************//** * @brief * The execSystemCommTx function manages data to be transmitted to other sub-systems. * @details * Inputs : none * Outputs : Next outgoing frame transmitted. * @return none *************************************************************************/ void execSystemCommTx( void ) { // don't bother with transmitting if no other nodes on CAN bus if ( FALSE == dgIsOnlyCANNode ) { // if CAN transmitter is idle, start transmitting any pending packets if ( FALSE == isCAN1TransmitInProgress() ) { transmitNextCANPacket(); } else { // generally, transmitter should not be busy at time of this function call - check timeout just in case so we don't get stuck waiting forever if ( TRUE == didTimeout( lastCANPacketSentTimeStamp, CAN_XMIT_PACKET_TIMEOUT_MS ) ) { // assume last packet was not successfully transmitted. Re-send last packet. if ( ++canXmitRetryCtr <= MAX_XMIT_RETRIES ) { // ensure we have a previous CAN packet/channel to resend - canTransmit() channel param MUST be valid if ( ( lastCANPacketSentChannel > COMM_BUFFER_NOT_USED ) && ( lastCANPacketSentChannel <= COMM_BUFFER_LAST_CAN_BUFFER ) ) { canTransmit( canREG1, lastCANPacketSentChannel, lastCANPacketSent ); } #ifdef DEBUG_ENABLED { char debugStr[100]; strcpy( debugStr, "SystemComm-DG resend Last Frame.\n" ); sendDebugData( (U08*)debugStr, strlen(debugStr) ); sendDebugDataToUI( (U08*)debugStr ); } #endif } // we must be only node on CAN bus - nobody is ACKing our transmitted frames else { dgIsOnlyCANNode = TRUE; // set only CAN node flag canXmitRetryCtr = MAX_XMIT_RETRIES; signalCANXmitsCompleted(); // clear pending xmit flag clearCANXmitBuffers(); // clear xmit buffers - nothing is going out right now #ifdef DEBUG_ENABLED char debugStr[100]; strcpy( debugStr, "SystemComm-DG is only node.\n" ); sendDebugData( (U08*)debugStr, strlen(debugStr) ); #endif } // end - are we retrying xmit or are we alone on CAN bus } // end - pending xmit timeout? } // end - transmit in progress or not } // end - DG not alone on CAN bus #ifdef DEBUG_ENABLED // if UART transmitter is idle, start transmitting any pending packets if ( FALSE == isSCI1DMATransmitInProgress() ) { transmitNextUARTPacket(); } #endif } /*********************************************************************//** * @brief * The handleCANMsgInterrupt function handles a CAN message interrupt. This * may have occurred because a CAN packet transmission has completed or * because a CAN packet has been received. The appropriate handler is called. * @details * Inputs : none * Outputs : message interrupt handled * @param srcCANBox which CAN message box triggered this interrupt * @return none *************************************************************************/ void handleCANMsgInterrupt( CAN_MESSAGE_BOX_T srcCANBox ) { // message interrupt is for a transmit message box? if ( TRUE == isCANBoxForXmit( srcCANBox ) ) { U32 bytesXmitted = transmitNextCANPacket(); if ( 0 == bytesXmitted ) { signalCANXmitsCompleted(); } } else if ( TRUE == isCANBoxForRecv( srcCANBox ) ) { U08 data[ CAN_MESSAGE_PAYLOAD_SIZE ]; // get CAN packet received on given CAN message box if ( FALSE != canIsRxMessageArrived( canREG1, srcCANBox ) ) { U32 result = canGetData( canREG1, srcCANBox, data ); // if packet retrieved, add to buffer if ( result != 0 ) { // add CAN packet to appropriate comm buffer based on the message box it came in on (s/b same #) addToCommBuffer( srcCANBox, data, CAN_MESSAGE_PAYLOAD_SIZE ); } } } else { // shouldn't get here - not an active message box // s/w fault? } } /*********************************************************************//** * @brief * The handleUARTMsgRecvPacketInterrupt function handles a DMA UART receive * packet completed interrupt. * @details * Inputs : none * Outputs : none * @return none *************************************************************************/ #ifdef DEBUG_ENABLED void handleUARTMsgRecvPacketInterrupt( void ) { // buffer received packet addToCommBuffer( COMM_BUFFER_IN_UART_PC, pcRecvPacket, PC_MESSAGE_PACKET_SIZE ); // prepare to receive next packet dmaSetCtrlPacket( DMA_CH1, pcDMARecvControlRecord ); dmaSetChEnable( DMA_CH1, DMA_HW ); setSCI1DMAReceiveInterrupt(); } #endif /*********************************************************************//** * @brief * The handleUARTMsgXmitPacketInterrupt function handles a DMA UART transmit * packet completed interrupt. * @details * Inputs : none * Outputs : none * @return none *************************************************************************/ #ifdef DEBUG_ENABLED void handleUARTMsgXmitPacketInterrupt( void ) { U32 bytesXmitted = transmitNextUARTPacket(); if ( 0 == bytesXmitted ) { signalSCI1XmitsCompleted(); } } #endif /*********************************************************************//** * @brief * The initUARTAndDMA function initializes the SCI1 peripheral and the DMA * to go with it for PC communication. * @details * Inputs : none * Outputs : SCI1 and DMA initialized * @return none *************************************************************************/ #ifdef DEBUG_ENABLED static void initUARTAndDMA( void ) { // Enable DMA block transfer complete interrupts dmaEnableInterrupt( DMA_CH1, BTC ); dmaEnableInterrupt( DMA_CH3, BTC ); // assign DMA channels to h/w DMA requests dmaReqAssign( DMA_CH1, SCI1_RECEIVE_DMA_REQUEST ); dmaReqAssign( DMA_CH3, SCI1_TRANSMIT_DMA_REQUEST ); // set DMA channel priorities dmaSetPriority( DMA_CH1, HIGHPRIORITY ); dmaSetPriority( DMA_CH3, LOWPRIORITY ); // initialize PC DMA Transmit Control Record pcDMAXmitControlRecord.PORTASGN = 4; // port B (only choice per datasheet) pcDMAXmitControlRecord.DADD = (U32)(&(sciREG->TD)); // dest. is SCI2 xmit register pcDMAXmitControlRecord.SADD = (U32)pcXmitPacket; // source pcDMAXmitControlRecord.CHCTRL = 0; // no chaining pcDMAXmitControlRecord.ELCNT = 1; // frame is 1 element pcDMAXmitControlRecord.FRCNT = PC_MESSAGE_PACKET_SIZE; // block is 8 frames pcDMAXmitControlRecord.RDSIZE = ACCESS_8_BIT; // element size is 1 byte pcDMAXmitControlRecord.WRSIZE = ACCESS_8_BIT; // pcDMAXmitControlRecord.TTYPE = FRAME_TRANSFER; // transfer type is block transfer pcDMAXmitControlRecord.ADDMODEWR = ADDR_FIXED; // dest. addressing mode is fixed pcDMAXmitControlRecord.ADDMODERD = ADDR_INC1; // source addressing mode is post-increment pcDMAXmitControlRecord.AUTOINIT = AUTOINIT_OFF; // auto-init off pcDMAXmitControlRecord.ELSOFFSET = 0; // not used pcDMAXmitControlRecord.ELDOFFSET = 0; // not used pcDMAXmitControlRecord.FRSOFFSET = 0; // not used pcDMAXmitControlRecord.FRDOFFSET = 0; // not used // initialize PC DMA Receipt Control Record pcDMARecvControlRecord.PORTASGN = 4; // port B (only choice per datasheet) pcDMARecvControlRecord.SADD = (U32)(&(sciREG->RD)); // source is SCI2 recv register pcDMARecvControlRecord.DADD = (U32)pcRecvPacket; // transfer destination address pcDMARecvControlRecord.CHCTRL = 0; // no chaining pcDMARecvControlRecord.ELCNT = 1; // frame is 1 element pcDMARecvControlRecord.FRCNT = PC_MESSAGE_PACKET_SIZE; // block is 8 frames pcDMARecvControlRecord.RDSIZE = ACCESS_8_BIT; // element size is 1 byte pcDMARecvControlRecord.WRSIZE = ACCESS_8_BIT; // pcDMARecvControlRecord.TTYPE = FRAME_TRANSFER; // transfer type is block transfer pcDMARecvControlRecord.ADDMODERD = ADDR_FIXED; // source addressing mode is fixed pcDMARecvControlRecord.ADDMODEWR = ADDR_INC1; // dest. addressing mode is post-increment pcDMARecvControlRecord.AUTOINIT = AUTOINIT_OFF; // auto-init off pcDMARecvControlRecord.ELDOFFSET = 0; // not used pcDMARecvControlRecord.ELSOFFSET = 0; // not used pcDMARecvControlRecord.FRDOFFSET = 0; // not used pcDMARecvControlRecord.FRSOFFSET = 0; // not used // initiate PC packet receiving readiness via DMA dmaSetCtrlPacket( DMA_CH1, pcDMARecvControlRecord ); dmaSetChEnable( DMA_CH1, DMA_HW ); setSCI1DMAReceiveInterrupt(); } #endif /*********************************************************************//** * @brief * The isCANBoxForXmit function determines whether a given CAN message box * is configured for transmit. * @details * Inputs : CAN_OUT_BUFFERS[] * Outputs : none * @param srcCANBox which CAN message box to check * @return TRUE if the given CAN message box is configured for transmit, FALSE if not. *************************************************************************/ BOOL isCANBoxForXmit( CAN_MESSAGE_BOX_T srcCANBox ) { BOOL result = FALSE; U32 i; for ( i = 0; i < NUM_OF_CAN_OUT_BUFFERS; i++ ) { if ( CAN_OUT_BUFFERS[ i ] == srcCANBox ) { result = TRUE; break; } } return result; } /*********************************************************************//** * @brief * The isCANBoxForRecv function determines whether a given CAN message box * is configured for receiving. * @details * Inputs : MSG_IN_BUFFERS[] * Outputs : none * @param srcCANBox which CAN message box to check * @return TRUE if the given CAN message box is configured for receiving, FALSE if not. *************************************************************************/ BOOL isCANBoxForRecv( CAN_MESSAGE_BOX_T srcCANBox ) { BOOL result = FALSE; U32 i; for ( i = 0; i < NUM_OF_CAN_IN_BUFFERS; i++ ) { if ( MSG_IN_BUFFERS[ i ] == srcCANBox ) { result = TRUE; break; } } return result; } /*********************************************************************//** * @brief * The clearCANXmitBuffers function clears all CAN transmit buffers. * @details * Inputs : CAN_OUT_BUFFERS[] * Outputs : CAN transmit buffers cleared. * @return none *************************************************************************/ static void clearCANXmitBuffers( void ) { U32 i; for ( i = 0; i < NUM_OF_CAN_OUT_BUFFERS; i++ ) { clearBuffer( CAN_OUT_BUFFERS[ i ] ); } } /************************************************************************* ********************** TRANSMIT SUPPORT FUNCTIONS ************************ *************************************************************************/ /*********************************************************************//** * @brief * The findNextHighestPriorityCANPacketToTransmit function gets the next * 8 byte packet and initiates a CAN transmit on the appropriate CAN channel. * @details * Inputs : Output CAN Comm Buffer(s) * Outputs : none * @return buffer with highest priority CAN packet to transmit, * COMM_BUFFER_NOT_USED if not CAN packets pending transmit found *************************************************************************/ static COMM_BUFFER_T findNextHighestPriorityCANPacketToTransmit( void ) { COMM_BUFFER_T result = COMM_BUFFER_NOT_USED; U32 i; // search for next priority CAN packet to transmit for ( i = 0; i < NUM_OF_CAN_OUT_BUFFERS; i++ ) { if ( numberOfBytesInCommBuffer( CAN_OUT_BUFFERS[ i ] ) >= CAN_MESSAGE_PAYLOAD_SIZE ) { result = CAN_OUT_BUFFERS[ i ]; break; // found highest priority packet to transmit - we're done } } return result; } /*********************************************************************//** * @brief * The transmitNextCANPacket function gets the next 8 byte packet and initiates * a CAN transmit on the appropriate CAN channel. * @details * Inputs : Output CAN Comm Buffers * Outputs : CAN packet transmit initiated. * @return number of bytes transmitted *************************************************************************/ static U32 transmitNextCANPacket( void ) { U32 result = 0; COMM_BUFFER_T buffer = findNextHighestPriorityCANPacketToTransmit(); // if a buffer is found with a packet to transmit, get packet from buffer and transmit it if ( buffer != COMM_BUFFER_NOT_USED ) { U08 data[ CAN_MESSAGE_PAYLOAD_SIZE ]; U32 dataSize = getFromCommBuffer( buffer, data, CAN_MESSAGE_PAYLOAD_SIZE ); CAN_MESSAGE_BOX_T mBox = buffer; // CAN message boxes and comm buffers are aligned // if there's another CAN packet to send, send it if ( dataSize == CAN_MESSAGE_PAYLOAD_SIZE ) { // we're transmitting another packet - signal transmitter is busy signalCANXmitsInitiated(); // remember packet data being transmitted here in case transmission fails and we need to re-send memcpy( lastCANPacketSent, data, CAN_MESSAGE_PAYLOAD_SIZE ); lastCANPacketSentChannel = mBox; lastCANPacketSentTimeStamp = getMSTimerCount(); if ( 0 != canTransmit( canREG1, mBox, data ) ) { result = CAN_MESSAGE_PAYLOAD_SIZE; } else { signalCANXmitsCompleted(); // TODO - shouldn't get here, but let's see if we do SET_ALARM_WITH_1_U32_DATA( ALARM_ID_DG_SOFTWARE_FAULT, (U32)mBox ) } } else { // TODO - shouldn't get here - just testing - set first data to new s/w fault enum later SET_ALARM_WITH_2_U32_DATA( ALARM_ID_DG_SOFTWARE_FAULT, (U32)buffer, (U32)dataSize ) } } return result; } /*********************************************************************//** * @brief * The transmitNextUARTPacket function sets up and initiates a DMA transmit * of the next packet pending transmit (if any) via UART. * @details * Inputs : Output UART Comm Buffer(s) * Outputs : UART DMA transmit initiated. * @return number of bytes transmitted *************************************************************************/ #ifdef DEBUG_ENABLED static U32 transmitNextUARTPacket( void ) { U32 result = 0; U32 dataPend = numberOfBytesInCommBuffer( COMM_BUFFER_OUT_UART_PC ); if ( dataPend > 0 ) { result = getFromCommBuffer( COMM_BUFFER_OUT_UART_PC, pcXmitPacket, dataPend ); // if there's data to transmit, transmit it if ( result > 0 ) { signalSCI1XmitsInitiated(); pcDMAXmitControlRecord.FRCNT = result; // set DMA transfer size dmaSetCtrlPacket( DMA_CH3, pcDMAXmitControlRecord ); dmaSetChEnable( DMA_CH3, DMA_HW ); setSCI1DMATransmitInterrupt(); } } return result; } #endif /************************************************************************* ********************** RECEIVE SUPPORT FUNCTIONS ************************* *************************************************************************/ /*********************************************************************//** * @brief * The processIncomingData function parses out messages from the input * comm buffers and adds them to the received message queue. * @details * Inputs : Input Comm Buffers * Outputs : Parsed message(s) added to received message queue * @return none *************************************************************************/ static void processIncomingData( void ) { U08 data[ 512 ]; // message work space U32 i; BOOL badCRCDetected = FALSE; // queue any received messages for ( i = 0; i < NUM_OF_MSG_IN_BUFFERS; i++ ) { BOOL messagesInBuffer = TRUE; // assume true at first to get into while loop while ( TRUE == messagesInBuffer ) { U32 numOfBytesInBuffer; // assume false so we don't get stuck in loop - only set to true if we find another complete message in buffer messagesInBuffer = FALSE; // since messages can have 8-byte alignment padding left unconsumed by last get, get padding out of buffer consumeBufferPaddingBeforeSync( MSG_IN_BUFFERS[ i ] ); // do we have enough bytes in buffer for smallest message? numOfBytesInBuffer = numberOfBytesInCommBuffer( MSG_IN_BUFFERS[ i ] ); if ( numOfBytesInBuffer >= MESSAGE_OVERHEAD_SIZE ) { // peek at minimum of all bytes available or max message size (+1 for sync byte) U32 bytesPeeked = peekFromCommBuffer( MSG_IN_BUFFERS[ i ], data, MIN( numOfBytesInBuffer, sizeof( MESSAGE_WRAPPER_T ) + 1 ) ); S32 msgSize = parseMessageFromBuffer( data, bytesPeeked ); dgIsOnlyCANNode = FALSE; // if we're getting a message, we can't be alone canXmitRetryCtr = 0; if ( msgSize > 0 ) // valid, complete message found? { MESSAGE_WRAPPER_T rcvMsg; U08 *dataPtr = data+1; // skip over sync byte // consume message (+sync byte) msgSize = getFromCommBuffer( MSG_IN_BUFFERS[ i ], data, msgSize + 1 ); // convert received message data to a message and add to message queue messagesInBuffer = TRUE; // keep processing this buffer // blank the new message record blankMessageInWrapper( &rcvMsg ); // copy message header portion of message data to the new message memcpy( &(rcvMsg.msg.hdr), dataPtr, sizeof(MESSAGE_HEADER_T) ); dataPtr += sizeof(MESSAGE_HEADER_T); // copy message payload portion of message data to the new message memcpy( &(rcvMsg.msg.payload), dataPtr, rcvMsg.msg.hdr.payloadLen ); dataPtr += rcvMsg.msg.hdr.payloadLen; // copy CRC portion of message data to the new message rcvMsg.crc = *dataPtr; // add new message to queue for later processing addToMsgQueue( MSG_Q_IN, &rcvMsg ); // if message from HD broadcast channel, update HD comm status if ( COMM_BUFFER_IN_CAN_HD_BROADCAST == MSG_IN_BUFFERS[ i ] ) { hdIsCommunicating = TRUE; timeOfLastHDCheckIn = getMSTimerCount(); } } else if ( -1 == msgSize ) // candidate message with bad CRC found? { badCRCDetected = TRUE; getFromCommBuffer( MSG_IN_BUFFERS[ i ], data, 1 ); // consume sync byte so we can re-sync messagesInBuffer = TRUE; // keep processing this buffer } // looks like there is a complete message in the comm buffer } // enough data left in comm buffer to possibly be a complete message } // while loop to get all complete messages for each comm buffer } // for loop to check all comm buffers for messages // if any bad CRCs detected, see if too many if ( TRUE == badCRCDetected ) { checkTooManyBadMsgCRCs(); } } /*********************************************************************//** * @brief * The consumeBufferPaddingBeforeSync function removes any bytes in a given * buffer that lie before a sync byte. * @details * Inputs : none * Outputs : Any padding at front of comm buffer is consumed. * @param buffer the comm buffer to process * @return none *************************************************************************/ static void consumeBufferPaddingBeforeSync( COMM_BUFFER_T buffer ) { U08 data; U32 numOfBytesInBuffer = numberOfBytesInCommBuffer( buffer ); // consume bytes out of buffer 1 at a time until we find the sync byte or it's empty while ( numOfBytesInBuffer > 0 ) { peekFromCommBuffer( buffer, &data, 1 ); if ( MESSAGE_SYNC_BYTE == data ) { break; // we found a sync - we're done } else // not a sync byte, so consume it { getFromCommBuffer( buffer, &data, 1 ); numOfBytesInBuffer = numberOfBytesInCommBuffer( buffer ); } } } /*********************************************************************//** * @brief * The parseMessageFromBuffer function looks for a complete message in a * given buffer. If a message is found, its size is returned. * @details * Inputs : none * Outputs : If a complete message can be found in buffer contents, it is parsed out. * @param data pointer to byte array to search for a message * @param len number of bytes in the data to search * @return size of message if found, zero if no complete message found, * -1 if message found but CRC fails. *************************************************************************/ static S32 parseMessageFromBuffer( U08 *data, U32 len ) { U32 i; U32 payloadSize; U32 msgSize; S32 result = 0; for ( i = 0; i < len; i++ ) { // find sync byte if ( MESSAGE_SYNC_BYTE == data[ i ] ) { U32 pos = i + 1; // skip past sync byte implemented U32 remSize = len - pos; // if a minimum sized msg would fit in remaining, continue if ( remSize >= MESSAGE_OVERHEAD_SIZE ) { payloadSize = data[ pos + sizeof(MESSAGE_HEADER_T) - sizeof(U08) ]; msgSize = MESSAGE_OVERHEAD_SIZE + payloadSize; // we now know the size of the message - we can now know if full message is contained in buffer if ( msgSize <= remSize ) { // check CRC to make sure it's a valid message if ( data[i+msgSize] == crc8( &data[pos], msgSize - 1 ) ) { result = msgSize; // we found a complete, valid message of this size } else // CRC failed { result = -1; // we found a complete, invalid message } } } break; } } return result; } /*********************************************************************//** * @brief * The processReceivedMessages function processes any messages in the * received message queues. * @details * Inputs : Received Message Queues * Outputs : Message(s) processed. * @return none *************************************************************************/ static void processReceivedMessages( void ) { BOOL isThereMsgRcvd = TRUE; // assume TRUE at first to get into while loop MESSAGE_WRAPPER_T message; while ( TRUE == isThereMsgRcvd ) { // see if any messages received isThereMsgRcvd = getFromMsgQueue( MSG_Q_IN, &message ); if ( TRUE == isThereMsgRcvd ) { // CRC should be good because we checked it during parsing before adding to queue - but check it again for good measure if ( message.crc == crc8( (U08*)(&message), sizeof(MESSAGE_HEADER_T) + message.msg.hdr.payloadLen ) ) { // if ACK, mark pending message ACK'd if ( MSG_ID_ACK == message.msg.hdr.msgID ) { matchACKtoPendingACKList( message.msg.hdr.seqNo ); } else { // if received message requires ACK, queue one up if ( message.msg.hdr.seqNo < 0 ) { sendACKMsg( &message.msg ); } // otherwise, process the received message processReceivedMessage( &message.msg ); } } else // CRC failed { checkTooManyBadMsgCRCs(); } } } } /*********************************************************************//** * @brief * The checkForCommTimeouts function checks for sub-system communication timeout errors. * @details * Inputs : timeOfLastDGCheckIn, timeOfLastUICheckIn * Outputs : possibly a comm t/o alarm * @return none *************************************************************************/ static void checkForCommTimeouts( void ) { if ( TRUE == didTimeout( timeOfLastHDCheckIn, HD_COMM_TIMEOUT_IN_MS ) ) { hdIsCommunicating = FALSE; #ifndef DEBUG_ENABLED activateAlarmNoData( ALARM_ID_HD_COMM_TIMEOUT ); // TODO - add this alarm if we're in middle of a treatment? or if in a mode that comm loss would impact badly? #endif } } /*********************************************************************//** * @brief * The checkTooManyBadMsgCRCs function checks for too many bad message CRCs * within a set period of time. Assumed function is being called when a new * bad CRC is detected so a new bad CRC will be added to the list. * @details * Inputs : badCRCTimeStamps[], badCRCListIdx, badCRCListCount * Outputs : possibly a "too many bad CRCs" alarm * @return none *************************************************************************/ static void checkTooManyBadMsgCRCs( void ) { if ( TRUE == incTimeWindowedCount( TIME_WINDOWED_COUNT_BAD_MSG_CRC ) ) { SET_ALARM_WITH_1_U32_DATA( ALARM_ID_COMM_TOO_MANY_BAD_CRCS, 2 ); // 2 for DG } #ifdef DEBUG_ENABLED { char debugStr[100]; strcpy( debugStr, "SystemComm-DG-Bad Msg CRC.\n" ); sendDebugDataToUI( (U08*)debugStr ); } #endif } /*********************************************************************//** * @brief * The addMsgToPendingACKList function adds a given message to the pending * ACK list. Messages in this list will require receipt of an ACK message * for this particular message within a limited time. * @details * Inputs : pendingAckList[] * Outputs : pendingAckList[] * @param msg pointer to msg within the message data * @param msgData pointer to message data to add to pending ACK list * @param len number of bytes of message data * @return TRUE if message added successfully, FALSE if not *************************************************************************/ BOOL addMsgToPendingACKList( MESSAGE_T *msg, COMM_BUFFER_T channel, U08 *msgData, U32 len ) { BOOL result = FALSE; U32 i; // find first open slot in pending ACK list and add given msg data to it for ( i = 0; i < PENDING_ACK_LIST_SIZE; i++ ) { _disable_IRQ(); // slot selection needs interrupt protection if ( FALSE == pendingAckList[ i ].used ) { S16 seqNo = msg->hdr.seqNo * -1; // remove ACK bit from seq # pendingAckList[ i ].used = TRUE; _enable_IRQ(); pendingAckList[ i ].seqNo = seqNo; pendingAckList[ i ].channel = channel; pendingAckList[ i ].timeStamp = getMSTimerCount(); pendingAckList[ i ].retries = MSG_NOT_ACKED_MAX_RETRIES; pendingAckList[ i ].msgSize = len; memcpy( pendingAckList[ i ].msg, msgData, len ); result = TRUE; break; } else { _enable_IRQ(); } } return result; } /*********************************************************************//** * @brief * The matchACKtoPendingACKList function searches the pending ACK list to * see if the sequence number from a received ACK msg matches any. If found, * the list entry is removed. * @details * Inputs : pendingAckList[] * Outputs : pendingAckList[] * @param seqNo sequence number to match to an entry in the list * @return TRUE if a match was found, FALSE if not *************************************************************************/ static BOOL matchACKtoPendingACKList( S16 seqNo ) { BOOL result = FALSE; U32 i; // find match for ( i = 0; i < PENDING_ACK_LIST_SIZE; i++ ) { if ( ( TRUE == pendingAckList[ i ].used ) && ( pendingAckList[ i ].seqNo == seqNo ) ) { // remove message pending ACK from list pendingAckList[ i ].used = FALSE; result = TRUE; break; } } return result; } /*********************************************************************//** * @brief * The checkPendingACKList function searches the pending ACK list to see if * any have expired. Any such messages will be queued for retransmission * and if max retries reached a fault is triggered. * @details * Inputs : pendingAckList[] * Outputs : pendingAckList[] * @return none *************************************************************************/ static void checkPendingACKList( void ) { U32 i; // find expired messages pending ACK for ( i = 0; i < PENDING_ACK_LIST_SIZE; i++ ) { // pending ACK expired? if ( ( TRUE == pendingAckList[ i ].used ) && ( TRUE == didTimeout( pendingAckList[ i ].timeStamp, MSG_NOT_ACKED_TIMEOUT_MS ) ) ) { // if retries left, reset and resend pending message if ( pendingAckList[ i ].retries > 0 ) { // re-queue message for transmit pendingAckList[ i ].retries--; pendingAckList[ i ].timeStamp = getMSTimerCount(); addToCommBuffer( pendingAckList[ i ].channel, pendingAckList[ i ].msg, pendingAckList[ i ].msgSize ); } // if no retries left, alarm else { U16 msgID; memcpy( &msgID, &pendingAckList[ i ].msg[ sizeof( U08 ) + sizeof( U16) ], sizeof( U16 ) ); SET_ALARM_WITH_1_U32_DATA( ALARM_ID_CAN_MESSAGE_NOT_ACKED, (U32)msgID ); pendingAckList[ i ].used = FALSE; // take pending message off of list } } } } /*********************************************************************//** * @brief * The processReceivedMessage function processes a given message. * @details * Inputs : none * Outputs : message processed * @param message pointer to message to process * @return none *************************************************************************/ static void processReceivedMessage( MESSAGE_T *message ) { U16 msgID = message->hdr.msgID; // handle any messages from other sub-systems switch ( msgID ) { case MSG_ID_POWER_OFF_WARNING: handlePowerOffWarning( message ); break; case MSG_ID_SET_DG_DIALYSATE_TEMP_TARGETS: handleSetDialysateTemperatureCmd( message ); break; case MSG_ID_REQUEST_FW_VERSIONS: handleFWVersionCmd( message ); break; case MSG_ID_DG_SWITCH_RESERVOIR_CMD: handleSwitchReservoirCmd( message ); break; case MSG_ID_DG_FILL_CMD: handleFillCmd( message ); break; case MSG_ID_DG_DRAIN_CMD: handleDrainCmd( message ); break; case MSG_ID_STARTING_STOPPING_TREATMENT_CMD: handleStartStopTreatmentMsg( message ); break; case MSG_ID_DG_SAMPLE_WATER_CMD: handleSampleWaterCmd( message ); break; case MSG_ID_DG_START_STOP_TRIMMER_HEATER_CMD: handleStartStopTrimmerHeaterCmd( message ); break; case MSG_ID_DG_TESTER_LOGIN_REQUEST: handleTesterLogInRequest( message ); break; case MSG_ID_DG_START_STOP_HEAT_DISINFECT: handleStartStopDGHeatDisinfect( message ); break; default: // unrecognized message ID received - ok, ignore - may be a test message handled below break; } // handle any test messages if tester has logged in successfully if ( ( msgID > MSG_ID_FIRST_DG_TESTER_MESSAGE ) && ( msgID <= END_OF_MSG_IDS ) && ( TRUE == isTestingActivated() ) ) { switch ( msgID ) { case MSG_ID_DG_ALARM_STATE_OVERRIDE: handleTestAlarmStateOverrideRequest( message ); break; case MSG_ID_DG_WATCHDOG_TASK_CHECKIN_OVERRIDE: handleTestWatchdogCheckInStateOverrideRequest( message ); break; case MSG_ID_DG_SET_RTC_DATE_TIME: handleSetRTCTimestamp( message ); break; case MSG_ID_START_STOP_PRIMARY_HEATER: handleStartStopPrimaryHeater ( message ); break; case MSG_ID_LOAD_CELL_OVERRIDE: handleTestLoadCellOverrideRequest( message ); break; case MSG_ID_LOAD_CELLL_SEND_INTERVAL_OVERRIDE: handleTestLoadCellDataBroadcastIntervalOverrideRequest( message ); break; case MSG_ID_PRESSURE_OVERRIDE: handleTestPressureSensorOverrideRequest( message ); break; case MSG_ID_PRESSURE_SEND_INTERVAL_OVERRIDE: handleTestPressureDataBroadcastIntervalOverrideRequest( message ); break; case MSG_ID_RO_MEASURED_FLOW_OVERRIDE: handleTestROMeasuredFlowOverrideRequest( message ); break; case MSG_ID_RO_PUMP_SEND_INTERVAL_OVERRIDE: handleTestROPumpDataBroadcastIntervalOverrideRequest( message ); break; case MSG_ID_DRAIN_PUMP_SET_RPM_OVERRIDE: handleTestDrainPumpRPMOverrideRequest( message ); break; case MSG_ID_DRAIN_PUMP_SEND_INTERVAL_OVERRIDE: handleTestDrainPumpDataBroadcastIntervalOverrideRequest( message ); break; case MSG_ID_VALVE_STATE_OVERRIDE: handleTestValveStateOverrideRequest( message ); break; case MSG_ID_VALVES_STATES_PUBLISH_INTERVAL_OVERRIDE: handleTestValvesStatesPublishIntervalOverrideRequest( message ); break; case MSG_ID_DG_SAFETY_SHUTDOWN_OVERRIDE: handleTestDGSafetyShutdownOverrideRequest( message ); break; case MSG_ID_TEMPERATURE_SENSORS_VALUE_OVERRIDE: handleTestTemperatureSensorsOverrideRequest ( message ); break; case MSG_ID_TEMPERATURE_SENSORS_PUBLISH_INTERVAL_OVERRIDE: handleTestTemperatureSensorsDataPublishOverrideRequest ( message ); break; case MSG_ID_HEATERS_PUBLISH_INTERVAL_ORVERRIDE: handleTestHeatersDataPublishOverrideRequest ( message ); break; case MSG_ID_CONDUCTIVITY_OVERRIDE: handleTestSetConductivityOverrideRequest ( message ); break; case MSG_ID_CONDUCTIVITY_PUBLISH_INTERVAL_OVERRIDE: handleTestSetConductivityDataPublishIntervalOverrideRequest ( message ); break; case MSG_ID_DG_ACCEL_OVERRIDE: handleTestDGAccelOverrideRequest( message ); break; case MSG_ID_DG_ACCEL_MAX_OVERRIDE: handleTestDGAccelMaxOverrideRequest( message ); break; case MSG_ID_DG_ACCEL_SEND_INTERVAL_OVERRIDE: handleTestDGAccelBroadcastIntervalOverrideRequest( message ); break; case MSG_ID_DG_ACCEL_SET_CALIBRATION: handleSetAccelCalibration( message ); break; case MSG_ID_DRAIN_PUMP_SET_DELTA_PRESSURE_OVERRIDE: handleSetDrainPumpDeltaPressureOverrideRequest( message ); break; case MSG_ID_DG_START_STOP_INLET_UV_REACTOR: handleStartStopInletUVReactor( message ); break; case MSG_ID_UV_REACTORS_DATA_PUBLISH_INTERVAL_OVERRIDE: handleUVReactorsDataPublishIntervalOverride( message ); break; case MSG_ID_DG_START_STOP_OUTLET_UV_REACTOR: handleStartStopOutletUVReactor( message ); break; case MSG_ID_DG_UV_REACTORS_HEALTH_OVERRIDE: handleUVReactorsHealthOverride( message ); break; case MSG_ID_DG_THERMISTORS_DATA_PUBLISH_INTERVAL_OVERRIDE: handleThermistorsDataPublishIntervalOverride( message ); break; case MSG_ID_DG_THERMISTORS_VALUE_OVERRIDE: handleThermisotrsValueOverride( message ); break; case MSG_ID_DG_RO_PUMP_DUTY_CYCLE_OVERRIDE: handleROPumpDutyCycleOverride( message ); break; case MSG_ID_DG_RO_FLOW_RATE_OVERRIDE: handleROFlowRateOverride( message ); break; case MSG_ID_DG_RO_PUMP_TARGET_FLOW_OVERRIDE: handleROPumpTargetFlowOverride( message ); break; case MSG_ID_DG_RO_PUMP_TARGET_PRESSURE_OVERRIDE: break; default: // TODO - unrecognized message ID received - ignore break; } } } /**@}*/