/************************************************************************** * * Copyright (c) 2019-2019 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 * * @date 08-Oct-2019 * @author S. Nash * * @brief SystemComm service module. Provides system message communication \n * functionality. Messages can be queued for transmission. Incoming messages \n * are processed. * **************************************************************************/ #include // for memcpy() #include "can.h" #include "sci.h" #include "sys_dma.h" #include "SystemComm.h" #include "Comm.h" #include "Interrupts.h" #include "MsgQueues.h" #include "SystemCommMessages.h" #include "Utilities.h" #ifdef RM46_EVAL_BOARD_TARGET #include "CPLD.h" #endif // ********** private definitions ********** #define NUM_OF_CAN_OUT_BUFFERS 5 // # of CAN buffers for transmit #define NUM_OF_CAN_IN_BUFFERS 3 // # of CAN buffers for receiving #define NUM_OF_MSG_IN_BUFFERS 4 // # of Msg buffers for receiving #define SCI1_RECEIVE_DMA_REQUEST 30 #define SCI1_TRANSMIT_DMA_REQUEST 31 // ********** private data ********** 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_DG_2_PC, COMM_BUFFER_OUT_UART_PC }; const COMM_BUFFER_T MSG_IN_BUFFERS[ NUM_OF_MSG_IN_BUFFERS ] = { COMM_BUFFER_IN_CAN_HD_2_DG, COMM_BUFFER_IN_CAN_PC_2_DG, COMM_BUFFER_IN_UART_PC }; static U08 pcXmitPacket[ PC_MESSAGE_PACKET_SIZE ] = { 0, 0, 0, 0, 0, 0, 0, 0 };// = { 1,2,3,4,5,6,7,8}; 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; // ********** private function prototypes ********** static void initUARTAndDMA( void ); static BOOL isCANBoxForXmit( CAN_MESSAGE_BOX_T srcCANBox ); static BOOL isCANBoxForRecv( CAN_MESSAGE_BOX_T srcCANBox ); static COMM_BUFFER_T findNextHighestPriorityCANPacketToTransmit( void ); static void transmitNextCANPacket( void ); static void transmitNextUARTPacket( void ); static void processIncomingData( void ); static U32 parseMessageFromBuffer( U08 *data, U32 len ); static void consumeBufferPaddingBeforeSync( COMM_BUFFER_T buffer ); static void processReceivedMessages( void ); static void processReceivedMessage( MESSAGE_T *message ); /************************************************************************* * @brief initSystemComm * The initSystemComm function initializes the SystemComm module. * @details * Inputs : none * Outputs : SystemComm module initialized. * @param none * @return none *************************************************************************/ void initSystemComm( void ) { // initialize UART and DMA for PC communication initUARTAndDMA(); // TODO - remove this test code that sends a packet // dmaSetCtrlPacket( DMA_CH3, pcDMAXmitControlRecord ); // dmaSetChEnable( DMA_CH3, DMA_HW ); // setSCI1DMATransmitInterrupt(); } /************************************************************************* * @brief execSystemCommRx * The execSystemCommRx function manages received data from other sub-systems. * @details * Inputs : none * Outputs : none * @param none * @return none *************************************************************************/ void execSystemCommRx( void ) { // parse messages from comm buffers and queue them processIncomingData(); // process received messages in the queue processReceivedMessages(); } /************************************************************************* * @brief execSystemCommTx * The execSystemCommTx function manages data to be transmitted to other \n * sub-systems. * @details * Inputs : none * Outputs : none * @param none * @return none *************************************************************************/ void execSystemCommTx( void ) { // if CAN transmitter is idle, start transmitting any pending packets if ( FALSE == isCAN1TransmitInProgress() ) { transmitNextCANPacket(); } // if UART transmitter is idle, start transmitting any pending packets if ( FALSE == isSCI1DMATransmitInProgress() ) { transmitNextUARTPacket(); } } /************************************************************************* * @brief handleCANMsgInterrupt * The handleCANMsgInterrupt function handles a CAN message interrupt. \n * This may have occurred because a CAN packet transmission has completed \n * or because a CAN packet has been received. The appropriate handler is \n * 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 ) ) { transmitNextCANPacket(); } 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 ) ) { canGetData( canREG1, srcCANBox, data ); // 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 handleUARTMsgRecvPacketInterrupt * The handleUARTMsgRecvPacketInterrupt function handles a DMA UART receive \n * packet completed interrupt. * @details * Inputs : none * Outputs : none * @param none * @return none *************************************************************************/ 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(); } /************************************************************************* * @brief handleUARTMsgXmitPacketInterrupt * The handleUARTMsgXmitPacketInterrupt function handles a DMA UART transmit \n * packet completed interrupt. * @details * Inputs : none * Outputs : none * @param none * @return none *************************************************************************/ void handleUARTMsgXmitPacketInterrupt( void ) { transmitNextUARTPacket(); } /************************************************************************* * @brief initUARTAndDMA * The initUARTAndDMA function initializes the SCI1 peripheral and the DMA \n * to go with it for PC communication. * @details * Inputs : none * Outputs : SCI1 and DMA initialized * @param none * @return none *************************************************************************/ 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(); } /************************************************************************* * @brief isCANBoxForXmit * The isCANBoxForXmit function determines whether a given CAN message box \n * 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. *************************************************************************/ static 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 isCANBoxForRecv * The isCANBoxForRecv function determines whether a given CAN message box \n * 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. *************************************************************************/ static 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; } /************************************************************************* ********************** TRANSMIT SUPPORT FUNCTIONS ************************ *************************************************************************/ /************************************************************************* * @brief findNextHighestPriorityCANPacketToTransmit * The findNextHighestPriorityCANPacketToTransmit function gets the next \n * 8 byte packet and initiates a CAN transmit on the appropriate CAN channel. \n * @details * Inputs : Output CAN Comm Buffer(s) * Outputs : none * @param msg : none * @return buffer with highest priority CAN packet to transmit, \n * 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 transmitNextCANPacket * The transmitNextCANPacket function gets the next 8 byte packet and initiates \n * a CAN transmit on the appropriate CAN channel. * @details * Inputs : Output CAN Comm Buffers * Outputs : CAN packet transmit initiated. * @param msg : none * @return none *************************************************************************/ static void transmitNextCANPacket( void ) { 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 ) { canTransmit( canREG1, mBox, data ); } } } /************************************************************************* * @brief transmitNextUARTPacket * The transmitNextUARTPacket function sets up and initiates a DMA transmit \n * of the next packet pending transmit (if any) via UART. * @details * Inputs : Output UART Comm Buffer(s) * Outputs : UART DMA transmit initiated. * @param msg : none * @return none *************************************************************************/ static void transmitNextUARTPacket( void ) { U32 dataSize = getFromCommBuffer( COMM_BUFFER_OUT_UART_PC, pcXmitPacket, PC_MESSAGE_PACKET_SIZE ); // if there's another UART packet to send, send it if ( dataSize == PC_MESSAGE_PACKET_SIZE ) { dmaSetCtrlPacket( DMA_CH3, pcDMAXmitControlRecord ); dmaSetChEnable( DMA_CH3, DMA_HW ); setSCI1DMATransmitInterrupt(); } } /************************************************************************* ********************** RECEIVE SUPPORT FUNCTIONS ************************* *************************************************************************/ /************************************************************************* * @brief processIncomingData * The processIncomingData function parses out messages from the Input \n * Comm Buffers and adds them to the Received Message Queue. * @details * Inputs : Input Comm Buffers * Outputs : Parsed message(s) added to Received Message Queue * @param msg : none * @return none *************************************************************************/ static void processIncomingData( void ) { U08 data[sizeof(MESSAGE_WRAPPER_T)+1]; U32 i; // 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) ); U32 msgSize = parseMessageFromBuffer( data, bytesPeeked ); if ( msgSize > 0 ) { // consume message (+sync byte) msgSize = getFromCommBuffer( MSG_IN_BUFFERS[i], data, msgSize+1 ); // if message data is at least minimum size, convert received message data to a message and add to message queue if ( msgSize > MESSAGE_OVERHEAD_SIZE ) { MESSAGE_WRAPPER_T rcvMsg; U08 *dataPtr = data+1; // skip over sync byte messagesInBuffer = TRUE; // 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 cargo 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 ); } // message is at least as large as minimum size } // 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 } /************************************************************************* * @brief consumeBufferPaddingBeforeSync * The consumeBufferPaddingBeforeSync function removes any bytes in a given \n * buffer that lie before a sync byte. * @details * Inputs : none * Outputs : none * @param msg : 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 parseMessageFromBuffer * The parseMessageFromBuffer function looks for a complete message in a \n * given buffer. If a message is found, its size is returned. * @details * Inputs : none * Outputs : none * @param data : pointer to byte array to search for a message * @param len : # of bytes in the data to search * @return size of message if found, zero if no complete message found. *************************************************************************/ static U32 parseMessageFromBuffer( U08 *data, U32 len ) { U32 i; U32 cargoSize; U32 msgSize; U32 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 ) { cargoSize = data[pos+sizeof(U16)]; msgSize = MESSAGE_OVERHEAD_SIZE + cargoSize; // we now know the size of the message - we can now know if full message is contained in buffer if ( msgSize <= remSize ) { result = msgSize; // we found a complete message of this size } } break; } } return result; } /************************************************************************* * @brief processReceivedMessages * The processReceivedMessages function processes any messages in the \n * received message queues. * @details * Inputs : Received Message Queues * Outputs : Message(s) processed. * @param msg : none * @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 ) { // TODO - check CRC before processing a message and if it fails we will... if ( message.crc == crc8( (U08*)(&message), sizeof(MESSAGE_HEADER_T) + message.msg.hdr.payloadLen ) ) { processReceivedMessage( &message.msg ); } else // CRC failed { // TODO - probably wouldn't want to fault on this. ignore? } } } // TODO - process UART (script) messages too } /************************************************************************* * @brief processReceivedMessage * The processReceivedMessage function processes a given message. * @details * Inputs : none * Outputs : message processed * @param msg : * @return none *************************************************************************/ static void processReceivedMessage( MESSAGE_T *message ) { U16 msgID = message->hdr.msgID; switch ( msgID ) { case MSD_ID_DG_FILL_START_STOP: handleDGFillStartStopMessages( message ); break; case MSG_ID_TESTER_LOGIN_REQUEST: handleTesterLogInRequest( message ); break; default: // TODO - unrecognized message ID received - ignore break; } // handle any test messages if tester has logged in successfully if ( ( msgID > MSG_ID_FIRST_TESTER_MESSAGE ) && ( TRUE == isTestingActivated() ) ) { switch ( msgID ) { case MSG_ID_HD_MESSAGE: handleTestHDMessageRequest( message ); break; case MSG_ID_ALARM_LAMP_PATTERN_OVERRIDE: handleTestAlarmLampPatternOverrideRequest( message ); break; case MSG_ID_WATCHDOG_TASK_CHECKIN_OVERRIDE: handleTestWatchdogCheckInStateOverrideRequest( message ); break; default: // TODO - unrecognized message ID received - ignore break; } } }