Search code examples
microcontrollermicrochipmplabrs485dspic

RS485: Transmission issue from microcontroller


I have an issue related to "Transmission" from microcontroller. Microcontroller is able to receive, but unable to transmit.

this is an additional issue to my previously asked question [here]

Below is the max485 interface with microcontroller :

max485 interface with microcontroller

Here is my code snapshot :

// RS485
TRISBbits.TRISB6    = INPUT_PIN;        // RX - RB6/RP38 PIN<42>
TRISBbits.TRISB7    = OUTPUT_PIN;       // TX - RB7/RP39 PIN<43>
TRISBbits.TRISB8    = OUTPUT_PIN;       // !RE/DE Control Pin RB8/RP40 PIN<44>

// RS485 Config
#define RS485_TX    PORTBbits.RB6           // RS485 Transmitter
#define RS485_RX    LATBbits.LATB7          // RS485 Reciever
#define RS485_CTRL  LATBbits.LATB8          // RS485 Control Pin

// UART ISR
void __attribute__((interrupt, no_auto_psv)) _U4RXInterrupt(void) 
{
    rs485Char = U4RXREG;
    RS485_CTRL = 1;         // Enable driver
    U4TXREG = rs485Char;
    RS485_CTRL = 0;         // disable driver RE/DO
}  

void InitRs485(void){
        // configure U1MODE
    U4MODEbits.UARTEN = 0;      // Bit15 TX, RX DISABLED, ENABLE at end of func

    U4MODEbits.URXINV = 1;      // 1:URXINV Idle state is '0' ; 0=UxRX Idle state is '1';
    U4MODEbits.ABAUD = 0;       // Bit5 No Auto baud (would require sending '55')
    U4MODEbits.BRGH  = 0;       // Bit3 16 clocks per bit period
    U4MODEbits.PDSEL = 0;       // 0 : 8 bit,no parity; 1 : 8 bit,even parity; 2 : 8 bit,odd parity; 3 : 9 bit,no Parity
    U4MODEbits.STSEL = 1;       // 1 : 2 Stop bits; 0 : 1 Stop bits 
    U4MODEbits.LPBACK = 0;      // Lookback disable
    U4STAbits.URXISEL = 0;      // Interrupt flag bit is set when a character is received

    // Load a value into Baud Rate Generator.
    U4BRG = BRGVAL_RS485;             // 60Mhz osc, 9600 Baud

    // Load all values in for U1STA SFR
    U4STAbits.UTXISEL1 = 0;     // Bit15 Int when Char is transferred (1/2 config!)
    U4STAbits.UTXISEL0 = 0;     // Bit13 Other half of Bit15
    U4STAbits.UTXINV = 1;       // 1:UxTX Idle state is '0' ; 0=UxTX Idle state is '1';

    U4STAbits.UTXBRK = 0;       // Bit11 Disabled
    U4STAbits.UTXEN = 0;        // Bit10 TX pins controlled by peripheral
    U4STAbits.URXISEL = 0;      // Bits6,7 Int. on character received
    IPC22bits.U4RXIP = 7;
    IPC22bits.U4TXIP = 7;

    IFS5bits.U4TXIF = 0;        // Clear the Transmit Interrupt Flag
    IEC5bits.U4TXIE = 0;        // Enable Transmit Interrupts
    IFS5bits.U4RXIF = 0;        // Clear the Receive Interrupt Flag
    IEC5bits.U4RXIE = 0;        // Enable Receive Interrupts

    RPOR2bits.RP39R = 0x1D;     // dsPic33EP512GM604 => RP39 as U4TX PIN<43>
    _U4RXR = 38;                // dsPic33EP512GM604 => RP38 as U4RX PIN<42>

    U4MODEbits.UARTEN = 1;      // And turn the peripheral on
    U4STAbits.UTXEN = 1;

    // Hardware control bits
    IEC5bits.U4RXIE = 1;
    IFS5bits.U4RXIF = 0;
    RS485_CTRL = 0;             // disable driver; Receive Enable
}

In above code, I have a UART Receive Interrupt routine.

Whenever any character is received, UART ISR receives it, but unable to transmit anything back.

In my ISR, I am trying to send back the character received.

This issue might be related to the max485 control pins (!RE/DE), which is referred as RS485_CTRL in my code.

So, I tried to rectify the issue as below :

  1. If ISR is written as

    rs485Char = U4RXREG;
    RS485_CTRL = 1;         // Enable driver
    U4TXREG = rs485Char;
    

    then microcontroller transmit 2Bytes, first one is the character recieved, second byte is a false byte ie.0x00.Thereafter no character is received by the ISR.

  2. If ISR is written as :

    rs485Char = U4RXREG;
    RS485_CTRL = 0;         // Disable driver
    U4TXREG = rs485Char;
    RS485_CTRL = 1;         // Enable driver
    

than it transmit a first character recieved. But there after ISR gets into infinite loop ie. receives a NULL character and transmits a NULL character.

As per the RS485 implementation rules,

  1. RS485_CTRL (!RE/DE) should be 0 to receive data

  2. RS485_CTRL (!RE/DE) should be 1 to transmit data.

My microcontroller is acting as a SLAVE, so by default I have kept it into listening mode. But when data is received I am unable to transmit.

Pls help me get my mistake identified ???

As per the suggestion given by @linuxfan, Correct ISR should be as below :

// UART ISR
void __attribute__((interrupt, no_auto_psv)) _U4RXInterrupt(void) 
{
    rs485Char = U4RXREG;
    RS485_CTRL = 1;         // Enable driver
    U4TXREG = rs485Char;
    while(!U4STAbits.TRMT); // wait until character is transffered successfully
    RS485_CTRL = 0;         // disable driver RE/DO
} 

Now my code is working fine as expected.


Solution

  • The LTE-485 (RS-485 line driver) has a "driver enable pin" which must be asserted in order to transmit. You must assert it just before starting to send a character, and you must de-assert it after the last byte has finished transmission (you de-assert it in order to be able to receive data, or to let some other device to drive the bus. If you don't want to receive, and you don't have any other device willing to transmit, you can keep it asserted).

    In your code there is:

    RS485_CTRL = 1; // Enable driver
    U4TXREG = rs485Char;

    You enable the driver with RS485_CTRL=1, then you load the UART shift register, but perhaps you don't wait the time all the data is shifted out.

    In order to wait for the data to shift out, you can load a timer which will fire after a fixed time: the time a character, 10 bits probably, takes to be shifted out at the baud rate you programmed. Each time you transmit a character, start this timer. When the timer expires, disable the OE (output driver enable).

    Another way is to wait for the reception of the character you just sent out. You load the transmit register; when all the bits are shifted out, you will receive the character (RS-485 is a bus). At that time, you can disable the transmitter if you want. Of course, this is not very good if you want to transmit a stream of characters back-to-back. Moreover, in your hardware setup you can't do that because you disable the receiver (the receiver enable and driver enable are shorted together).

    But you can do what I think is the best. When you transmit a char, enable the driver of the interface OE; then use the TX interrupt of your UART to transmit the next character (if any), or de-assert the driver if there is nothing more to transmit. Beware to use the right transmitting interrupt: some UART (I don't know the one you are using) has buffering - you need the interrupt that fires when the datum has shifted out, not the one which says "you can load more data".

    NOTE ASIDE: you are using a DB9 connector, using the pins 2 and 3, and labelling them TX and RX. Well, there is NO TX and RX wires in the RS-485: the two wires are named A and B, and both carry the same, balanced signal. These wires are used to receive data, when left floating, and the same wires are used to transmit data, if driven by the LTC-485 driver. You can very well enable the transmitting driver and keep to "listen" on the wires. You will receive back what you just transmitted (and this can also diagnose that the wires are shorted or the driver is blown). You can choose to use a connector often used for RS-232, and you can also choose to use the same pins 2 and 3 as the RS-232, but in no way an RS-485 can be assimilated to an RS-232.