/************************************************************************** * * Copyright (C) 2005 Steve Karg * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * *********************************************************************/ /* The module handles sending data out the RS-485 port */ /* and handles receiving data from the RS-485 port. */ /* Customize this file for your specific hardware */ #include #include #include #include "hardware.h" #include "mstp.h" /* public port info */ extern volatile struct mstp_port_struct_t MSTP_Port; static uint32_t RS485_Baud_Rate = 9600; /* UART transmission buffer and index */ static volatile uint8_t RS485_Tx_Buffer[MAX_MPDU]; static volatile uint8_t RS485_Tx_Index = 0; static volatile uint8_t RS485_Tx_Length = 0; static volatile char RS485_Tx_Postdrive_Delay = 0; static struct { unsigned TransmitStart:1; /* TRUE if we are requested to transmit */ unsigned TransmitComplete:1; /* TRUE if we are finished transmitting frame */ } RS485_Flags; /* Duplicate of the RCSTA reg used due to the double buffering of the */ /* fifo. Reading the RCREG reg will cause the second RCSTA reg to be */ /* loaded if there is one. */ */ struct _rcstabits { unsigned char RX9D:1; unsigned char OERR:1; unsigned char FERR:1; unsigned char ADDEN:1; unsigned char CREN:1; unsigned char SREN:1; unsigned char RX9:1; unsigned char SPEN:1; }; volatile static enum { RS485_STATE_IDLE = 0, RS485_STATE_RX_DATA = 1, RS485_STATE_RX_CHECKSUM = 2, RS485_STATE_RX_PROCESS = 3, RS485_STATE_TX_DATA = 4, RS485_STATE_WAIT_FOR_ACK = 5, RS485_STATE_WAIT_COMPLETE = 6, RS485_STATE_TX_GLOBAL_ACK = 7, RS485_STATE_TX_POSTDRIVE_DELAY = 8, RS485_STATE_ERROR = 9, RS485_STATE_RX_TEST = 10, RS485_STATE_RX_TEST_EEPROM = 11, RS485_STATE_RX_TEST_DELAY = 12, RS485_STATE_TX_TEST_WAIT = 13, RS485_STATE_TX_TEST = 14 } RS485_State; /**************************************************************************** * DESCRIPTION: Transmits a frame using the UART * RETURN: none * ALGORITHM: none * NOTES: none *****************************************************************************/ void RS485_Send_Frame(volatile struct mstp_port_struct_t *mstp_port, /* port specific data */ uint8_t * buffer, /* frame to send (up to 501 bytes of data) */ uint16_t nbytes) { /* number of bytes of data (up to 501) */ /* do we check for tx buffer in-use? */ /* Or do we just stop the in-progress transmission? */ /* Drop any transmission in progress, but don't worry about */ /* cleaning up the hardware - the start routine will handle that */ /* Disable the interrupt since it depends on the global transmit buffer. */ USART_TX_INT_DISABLE(); switch (RS485_State) { case RS485_STATE_TX_DATA: case RS485_STATE_WAIT_FOR_ACK: case RS485_STATE_WAIT_COMPLETE: case RS485_STATE_TX_GLOBAL_ACK: RS485_State = RS485_STATE_IDLE; break; } /* load the frame */ RS485_Tx_Length = 0; while (buffer && nbytes) { RS485_Tx_Buffer[RS485_Tx_Length] = *buffer; buffer++; nbytes--; RS485_Tx_Length++; /* check bounds - should this error be indicated somehow? */ /* perhaps not send the message? */ if (RS485_Tx_Length >= MAX_MPDU) break; } /* signal the task to start sending when it is ready */ RS485_Flags.TransmitStart = TRUE; return; } /**************************************************************************** * DESCRIPTION: Processes the next RS485 byte for transmit * RETURN: none * ALGORITHM: none * NOTES: Called by interrupt service routine (ISR) *****************************************************************************/ void RS485_Transmit_Interrupt(void) { uint8_t data; /* data byte to send */ switch (RS485_State) { case RS485_STATE_TX_DATA: RS485_Tx_Index++; if (RS485_Tx_Index < RS485_Tx_Length) { data = RS485_Tx_Buffer[RS485_Tx_Index]; USART_TRANSMIT(data); MSTP_Port.SilenceTimer = 0; } else { /* wait until the last bit is sent */ while (!USART_TX_EMPTY()); RS485_TRANSMIT_DISABLE(); /* wait 2 characters after sending (min=15 bit times) */ RS485_Tx_Postdrive_Delay = 2; RS485_State = RS485_STATE_TX_POSTDRIVE_DELAY; USART_TRANSMIT(0); } break; case RS485_STATE_TX_POSTDRIVE_DELAY: /* after the message is sent, we wait a certain */ /* number of character times to get a delay */ if (RS485_Tx_Postdrive_Delay) { RS485_Tx_Postdrive_Delay--; if (RS485_Tx_Postdrive_Delay == 0) RS485_State = RS485_STATE_WAIT_COMPLETE; USART_TRANSMIT(0); } else RS485_State = RS485_STATE_WAIT_COMPLETE; break; case RS485_STATE_WAIT_COMPLETE: /* wait until the last delay bit is shifted */ while (!USART_TX_EMPTY()); USART_TX_INT_DISABLE(); RS485_Flags.TransmitComplete = TRUE; RS485_State = RS485_STATE_IDLE; USART_RX_SETUP(); break; default: break; } return; } /**************************************************************************** * DESCRIPTION: Processes the RS485 message to be sent * RETURN: none * ALGORITHM: none * NOTES: none *****************************************************************************/ void RS485_Process_Tx_Message(void) { if (RS485_Flags.TransmitComplete) RS485_Flags.TransmitComplete = FALSE; /* start a new transmisstion if we are ready */ if (RS485_Flags.TransmitStart && (RS485_State == RS485_STATE_IDLE)) { /* Disable the receiver */ USART_RX_INT_DISABLE(); USART_CONTINUOUS_RX_DISABLE(); /* Enable the transmit line driver and interrupts */ RS485_TRANSMIT_ENABLE(); RS485_State = RS485_STATE_TX_DATA; /* Configure the ISR handler for an outgoing message */ RS485_Tx_Index = 0; /* update the flags for beginning a send */ RS485_Flags.TransmitComplete = FALSE; RS485_Flags.TransmitStart = FALSE; /* send the first byte */ USART_TRANSMIT(RS485_Tx_Buffer[0]); USART_TX_SETUP(); } return; } /**************************************************************************** * DESCRIPTION: Checks for data on the receive UART, and handles errors * RETURN: none * ALGORITHM: none * NOTES: none *****************************************************************************/ void RS485_Check_UART_Data(volatile struct mstp_port_struct_t *mstp_port) { struct _rcstabits rcstabits; /* reading it more than once gets wrong data */ /* check for data */ if (USART_RX_COMPLETE()) { /* Read the data and the Rx status reg */ rcstabits = USART_RX_STATUS(); mstp_port->DataRegister = USART_RECEIVE(); /* Check for buffer overrun error */ if (rcstabits.OERR) { /* clear the error */ USART_CONTINUOUS_RX_DISABLE(); USART_CONTINUOUS_RX_ENABLE(); /* let the state machine know */ mstp_port->ReceiveError = TRUE; } /* Check for framing errors */ else if (USART_RX_FRAME_ERROR()) { /* let the state machine know */ mstp_port->FramingError = TRUE; mstp_port->ReceiveError = TRUE; } /* We read a good byte */ else { /* state machine will clear this */ mstp_port->DataAvailable = TRUE; } } return; } /**************************************************************************** * DESCRIPTION: Receives a data byte from the USART * RETURN: none * ALGORITHM: none * NOTES: none *****************************************************************************/ void RS485_Receive_Interrupt(void) { /* get as many bytes as we can get */ for (;;) { RS485_Check_UART_Data(&MSTP_Port); if (MSTP_Port.ReceiveError || MSTP_Port.DataAvailable) MSTP_Receive_Frame_FSM(&MSTP_Port); else break; } return; } /**************************************************************************** * DESCRIPTION: Returns the baud rate that we are currently running at * RETURN: none * ALGORITHM: none * NOTES: none *****************************************************************************/ uint32_t RS485_Get_Baud_Rate(void) { return RS485_Baud_Rate; } /**************************************************************************** * DESCRIPTION: Sets the baud rate for the chip USART * RETURN: none * ALGORITHM: none * NOTES: none *****************************************************************************/ void RS485_Set_Baud_Rate(uint32_t baud) { if (baud < 19200) RS485_Baud_Rate = 9600; else if (baud < 38400) RS485_Baud_Rate = 19200; else if (baud < 57600) RS485_Baud_Rate = 38400; else if (baud < 57600) RS485_Baud_Rate = 57600; else if (baud < 115200) RS485_Baud_Rate = 76800; else RS485_Baud_Rate = 115200; } void RS485_Initialize_Baud(void) { /* setup USART Baud Rate Generator */ /* see BAUD RATES FOR ASYNCHRONOUS MODE in Data Book */ /* Fosc=20MHz BRGH=1 BRGH=0 Rate SPBRG Rate SPBRG ------- ----- ------- ----- 9615 129 9469 32 19230 64 19530 15 37878 32 78130 3 56818 21 104200 2 113630 10 312500 0 250000 4 625000 1 1250000 0 */ switch (RS485_Baud_Rate) { case 19200: SPBRG = 64; TXSTAbits.BRGH = 1; break; case 38400: SPBRG = 32; TXSTAbits.BRGH = 1; break; case 57600: SPBRG = 21; TXSTAbits.BRGH = 1; break; case 76800: SPBRG = 3; TXSTAbits.BRGH = 0; break; case 115200: SPBRG = 10; TXSTAbits.BRGH = 1; break; case 9600: default: SPBRG = 129; TXSTAbits.BRGH = 1; break; } /* select async mode */ TXSTAbits.SYNC = 0; /* serial port enable */ RCSTAbits.SPEN = 1; } /**************************************************************************** * DESCRIPTION: Initializes the RS485 hardware and variables, and starts in * receive mode. * RETURN: none * ALGORITHM: none * NOTES: none *****************************************************************************/ void RS485_Initialize(void) { RS485_Initialize_Baud(); /* configure interrupts */ USART_TX_INT_DISABLE(); USART_RX_INT_ENABLE(); /* configure USART for receiving */ /* since the TX will handle setting up for transmit */ USART_CONTINUOUS_RX_ENABLE(); /* since we are using RS485, we need to explicitly say transmit enable or not */ RS485_TRANSMIT_DISABLE(); }