RS485 half duplex using Teensy 3.0 hardware pins

Status
Not open for further replies.

Epyon

Well-known member
I'm trying to get Modbus to run over a half-duplex RS485 link, with my Teensy acting as a Modbus master. I successfully ported my Modbus stack to the Teensy 3.0, but I'm at a loss on how to toggle the DE/!RE pin of the RS485 transceiver. On the Arduino, I set a digital pin by checking the UCSR0A register, which contains a bit signaling the depletion of the transmit buffer. However, since the Teensy 3.0 has hardware handshaking pins, can't e.g. the RTS pin be used for toggling the transceiver? Or any other idea's on how to get a digital pin to function as the transceiver toggle? The Dynamixel library I found on this forum didn't help me much :( .
 
I fixed it by using Uart.flush() before disabling my DE pin. It's two instructions more, but it works.

TEK00000.PNG
 
Teensy 2.0's HardwareSerial object has an undocumented feature to automatically control the DE pin. You use with the Serial1.begin(baud, pin).

I've been considering adding this to Teensy 3.0.....
 
I would be deeply appreciative if you were able to post your code that you used to port the library? (yours or the standard modbus master?) and the DE control pin. Thank you!
Ofcourse, see the sketch attached. Works on the Teensy 3.0. You can determine the pin to use as DE toggle in the header.

It's actually based on this sketch which implements functions 3 and 16 (read holding registers and preset multiple registers) of the Modbus RTU Protocol. I planned on making it into a library, but haven't really had some free time lately :) .
 

Attachments

  • teensyModbusMasterRS485.ino
    12.6 KB · Views: 1,488
Ofcourse, see the sketch attached. Works on the Teensy 3.0. You can determine the pin to use as DE toggle in the header.

It's actually based on this sketch which implements functions 3 and 16 (read holding registers and preset multiple registers) of the Modbus RTU Protocol. I planned on making it into a library, but haven't really had some free time lately :) .

Thank you so much!!!

Is there any reason this couldn't work on a Due board? I have 3 Teensys in this project already and realized I filled their serial ports, but the Due has both. I've looked all over the Arduino forums and can't really find where someone has interfaced with the MAX chip and controlled the DE pin perfectly.
 
Is there any reason this couldn't work on a Due board?

Yes. You'll probably run into problems related to Arduino Due's hardware serial code.

The worst problem is Due's flush() function returns too soon. Here's their code:

Code:
void USARTClass::flush( void )
{
  // Wait for transmission to complete
  while ((_pUsart->US_CSR & US_CSR_TXRDY) != US_CSR_TXRDY)
;
}

The trouble is the TXRDY flag indicates when the UART is ready to accept the next character of data to transmit, which happens before the final stop bit of the currently transmitting character has been sent. If you rely upon this to clear the DE pin, you'll turn off the RS485 transmitter too soon.

Arduino Uno had this same bug for quite some time. It was finally fixed when Michele Mazzucchi ported my code from Teensy to fix Arduino's Serial.flush().


The other problem you're likely to encounter is Due's lack of transmit buffering. Due's write (and therefore print) function does not implement a buffer. When you use Serial.print(anything) or Serial.write(buf, len), only the first 2 bytes go into the UART quickly. If you write more than 2 characters, the function just waits until the UART's 2-byte buffer can accept the last 2 characters.

Arduino Uno has implemented transmit buffering since Arduino 1.0. Teensy has had it much longer, since Arduino 0016. Even using just one serial port, this really makes your program much faster, since you can start doing other work while a block of data from Serial.write() or a string from Serial.print() is transmitted from the buffer.

If you're using more than one port, the transmit buffering feature is pretty much essential to maintaining simultaneous data output. Arduino Due gives you 4 serial ports, and they work great if you're mostly receiving data and you only need to occasionally transmit, or you mostly transmit on just 1 port, or if you transmit only 1 or 2 byte messages with delays between them.

Arduino Due is an amazing piece of hardware, but if you're building an application that sends any substantial amount of data simultaneously on multiple serial ports, you'll quickly run into the limitations of Due's software.
 
Last edited:
Yes. You'll probably run into problems related to Arduino Due's hardware serial code.

The worst problem is Due's flush() function returns too soon. Here's their code:

Code:
void USARTClass::flush( void )
{
  // Wait for transmission to complete
  while ((_pUsart->US_CSR & US_CSR_TXRDY) != US_CSR_TXRDY)
;
}

The trouble is the TXRDY flag indicates when the UART is ready to accept the next character of data to transmit, which happens before the final stop bit of the currently transmitting character has been sent. If you rely upon this to clear the DE pin, you'll turn off the RS485 transmitter too soon.

Arduino Uno had this same bug for quite some time. It was finally fixed when Michele Mazzucchi ported my code from Teensy to fix Arduino's Serial.flush().


The other problem you're likely to encounter is Due's lack of transmit buffering. Due's write (and therefore print) function does not implement a buffer. When you use Serial.print(anything) or Serial.write(buf, len), only the first 2 bytes go into the UART quickly. If you write more than 2 characters, the function just waits until the UART's 2-byte buffer can accept the last 2 characters.

Arduino Uno has implemented transmit buffering since Arduino 1.0. Teensy has had it much longer, since Arduino 0016. Even using just one serial port, this really makes your program much faster, since you can start doing other work while a block of data from Serial.write() or a string from Serial.print() is transmitted from the buffer.

If you're using more than one port, the transmit buffering feature is pretty much essential to maintaining simultaneous data output. Arduino Due gives you 4 serial ports, and they work great if you're mostly receiving data and you only need to occasionally transmit, or you mostly transmit on just 1 port, or if you transmit only 1 or 2 byte messages with delays between them.

Arduino Due is an amazing piece of hardware, but if you're building an application that sends any substantial amount of data simultaneously on multiple serial ports, you'll quickly run into the limitations of Due's software.

Thank you for such a detailed response! I will have to find a way to fit yet another teensy into my project. At least they are, in fact, teensy.
 
Thank you so much!!!

Is there any reason this couldn't work on a Due board? I have 3 Teensys in this project already and realized I filled their serial ports, but the Due has both. I've looked all over the Arduino forums and can't really find where someone has interfaced with the MAX chip and controlled the DE pin perfectly.
I was looking into porting it to the Due, because the Teensy 3.0 has a yet to be resolved bug which prevents it from using more than two SPI peripherals at the same time (more specifically a Wiznet Ethernet chip and a SD card), and this project needed both those features. Unfortunately, other projects required more of my attention and I haven't been able to either port the DE-toggle to the Due or search for ways to circumvent the SPI bug.
For the former, I was going to look if there were any registers in the SAM3X chip of the Due which can be used to signal an empty transmit buffer, if necessary coupled with some minor delay function. For the latter, I suspect the CS lines of the Teensy don't behave as they should, activating the two SPI peripherals at the same time. But I'll need to hit the lab to test that :) .
 
Last edited:
This is working for rs485 on the arduino due for serial3 :

while (( USART3->US_CSR & US_CSR_TXEMPTY) != US_CSR_TXEMPTY) {};

rs485.jpg
 
Or you can change it for all serials in the due ide 1.5.2, and use the Serial.flush() command

go to --> arduino-1.5.2\hardware\arduino\sam\cores\arduino

edit in the USARTClass.CPP ( NOT UARTClass.CPP)!!!!

Use the TXEMPTY flag, and not the TXRDY flag

FROM :

void USARTClass::flush( void )
{
// Wait for transmission to complete
while ((_pUsart->US_CSR & US_CSR_TXRDY) != US_CSR_TXRDY)
;
}


TO :
void USARTClass::flush( void )
{
// Wait for transmission to complete
while ((_pUsart->US_CSR & US_CSR_TXEMPTY) != US_CSR_TXEMPTY)
;
}


Now you can use the flush command before you disable the DE pin.

For Serial3 --> Serial3.flush();
then disable the DE pin

I am using the ISL3178EIBZ RS485, at 18.432 Mhz clock for the slave (ATmega44), at 115200Baud (c.q. bps) speed, without any error.
 
Or you can change it for all serials in the due ide 1.5.2, and use the Serial.flush() command

go to --> arduino-1.5.2\hardware\arduino\sam\cores\arduino

edit in the USARTClass.CPP ( NOT UARTClass.CPP)!!!!

Use the TXEMPTY flag, and not the TXRDY flag

FROM :

void USARTClass::flush( void )
{
// Wait for transmission to complete
while ((_pUsart->US_CSR & US_CSR_TXRDY) != US_CSR_TXRDY)
;
}


TO :
void USARTClass::flush( void )
{
// Wait for transmission to complete
while ((_pUsart->US_CSR & US_CSR_TXEMPTY) != US_CSR_TXEMPTY)
;
}


Now you can use the flush command before you disable the DE pin.

For Serial3 --> Serial3.flush();
then disable the DE pin

I am using the ISL3178EIBZ RS485, at 18.432 Mhz clock for the slave (ATmega44), at 115200Baud (c.q. bps) speed, without any error.

Thank you both for your further followup! Your contributions will be most helpful to me!!!
 
Ofcourse, see the sketch attached. Works on the Teensy 3.0. You can determine the pin to use as DE toggle in the header.

It's actually based on this sketch which implements functions 3 and 16 (read holding registers and preset multiple registers) of the Modbus RTU Protocol. I planned on making it into a library, but haven't really had some free time lately :) .

So I am having some crazy difficulty with this... I am ending up talking to a modbus device that uses RS232 (still modbus RTU not ASCII) instead of 485, so I ditched my max485 chip and plugged straight in. I get the same reply from the device no matter what I do:


Code:
read returned: 65280 2757 59760 0 0 0 0 0 0 0 
write returned: 0

When I use a modbus test application on my PC it comes back with the correct numbers (a 345 in the 1st register and a 1 in the 7th register)

I'm really confused what is going on and would love any suggestions.

Thanks!

Code:
const int ledPin = 13;
int read_holding_registers(int slave, int start_addr, int count,int *dest, int dest_size);
unsigned int Txenpin = 14; //this pin will be the DE toggle
void setup()
{
        const int baudrate = 9600;
        if (baudrate <= 19200)
                interframe_delay = (unsigned long)(3.5 * 11 / baudrate);  /* Modbus t3.5 */
        Serial1.begin(baudrate); 	/* format 8N1, DOES NOT comply with Modbus spec. */
        pinMode(Txenpin, OUTPUT); 
        Serial.begin(9600);
        delay(1000);
        Serial.println("Ready");
        pinMode(ledPin, OUTPUT);
        
  
}

/* example data */
int retval;
int setval;
int data[11];

int data2[1] = {2};
void loop()
{
                Serial.print("read returned: ");
                digitalWrite(ledPin,HIGH);
                retval = read_holding_registers(1,0,10,data,10);
                for(int i = 0; i < 10; i++){
                  Serial.print(data[i]);
                  Serial.print(' ');}
                  
                Serial.println();



 delay(125);
//                Serial.print("write returned: ");
//int preset_multiple_registers(int slave, int start_addr,int reg_count, int *data);                
//                setval = preset_multiple_registers(1,40008,1,data2);
//                setval = preset_multiple_registers(1,1,10,data2);
//                setval = preset_multiple_registers(2,1,10,data2);
//                setval = preset_multiple_registers(3,1,10,data2);
//                setval = preset_multiple_registers(4,1,10,data2);
                digitalWrite(ledPin,LOW);
//                Serial.println(setval);

                 delay(125);
  
}


My logic analyzer is seeing this:

Please ignore the attached thumbnail, I don't see a way to remove it. Just use the inline image here
IrTSgyM.jpg
 

Attachments

  • UxdOCcO.jpg
    UxdOCcO.jpg
    95.6 KB · Views: 541
Last edited:
Lord almighty...

this line:

Code:
 * start_addr: address of the slave's first register (+1)

means "you need to add one yourself" I guess... so I just changed to

retval = read_holding_registers(1,1,10,data,10);

and amazingly... it works perfectly now!!
 
Teensy 2.0's HardwareSerial object has an undocumented feature to automatically control the DE pin. You use with the Serial1.begin(baud, pin).

I've been considering adding this to Teensy 3.0.....

Hi Paul!
Was this ever implemented for Teensy 3? My latest experiment suggests no… hope you are well!
 
I've added Serial.transmitterEnable(pin) for both Teensy 2.0 and 3.0/3.1. Currently it's only on Serial1 in Teensy 3, but I'll add it on Serial2 and Serial3.

The code is on github.

https://github.com/PaulStoffregen/cores

For Teensy 3, you need to update your copy of serial1.c and HardwareSerial.h. For Teensy 2.0, update HardwareSerial.cpp and HardwareSerial.h.

I tested this by viewing waveforms on my scope. It seems to work (and the code in Teensy 2.0 has been there for years, but it's new in 3.0).

Please give this a try, especially if you have any projects with real RS-485 hardware connected, and let me know how it works for you? Unless any major problems are discovered, I'd like to release this in 1.18 and then officially document it on the website.
 
Ofcourse, see the sketch attached. Works on the Teensy 3.0. You can determine the pin to use as DE toggle in the header.

It's actually based on this sketch which implements functions 3 and 16 (read holding registers and preset multiple registers) of the Modbus RTU Protocol. I planned on making it into a library, but haven't really had some free time lately :) .

Great, do you have a newer version of this code or even a library?

Thanks,
Regards,
Mike
 
Status
Not open for further replies.
Back
Top