Modifying Optiboot to Upload Sketches over RS485

A board I'm currently working on has a primary interface over RS485 (it is expected to be part of a multi-drop network, with several like and different devices at the same time - with a single master controlling them). All of the communication works great, and the network design hasn't shown any issues yet, etc. The RS485 driver is an SN75176AD and its output goes to the RX/TX lines on the Atmega328P, which is running at 16Mhz w/ a crystal and 5V.

The board is designed to fit in as small of a space as possible (it's 1.5"x1.5") - it could be smaller, yes, but it has an A4983, Dual OK, and of course the SN75176 plus all the accoutrements, with pin-pads and such that's about as small as I've gotten it at this stage. Given this, I decided that adding a USB->serial converter was a luxury, but left the standard RX/TX lines accessible via pins.

The challenge I'm facing: RS-485 is the primary interface for the vast majority of users in its final destination. As such, for PC communication, a special USB->RS-485 adapter is required. I don't want the users to have one interface for interaction and yet another for programming the board, both because buying two USB adapter cables is an undesired expense on the user, and adding a USB port on the board will prevent it from being used in some of the cases which it is designed for (where the 1.5"x1.5" footprint is the absolute maximum that will fit).

After modifying optiboot 4.4 to check for uploads on power-up, and not just after external reset, I also changed it to eliminate interference from the RS485 driver on the RX line during upload using the standard RX/TX lines, by adding the following to the main function:

  DDRD |= _BV(PORTD5);
  PORTD |= _BV(PORTD5);

Digital 5 (PORTD5) is the control line which is shared between the DE/RE pins on the driver. Putting the driver in transmit mode (DE = RE = HIGH) causes it to release the RX line, which allows upload using the standard RX/TX lines (and another arduino salvaged for its FTDI interface =). So, at least this option works, ok.

Now, I'm working on the ability to upload via the RS485 interface, which requires the board to put the transceiver (ok, I've been calling it a driver, because it's a shorter word =) into the transmit state before transmitting data, and then putting it back into receive state to get more data. This is where I've run into a stumbling block, and I'm wondering if anyone else has tried something like this and can see what I've been missing as obvious - I hope!

So, to control the state of the driver, I made sure to change the above mod to the put the driver into receive mode first, and then modified putch() in the following way:

void putch(char ch) {
#ifndef SOFT_UART

  while (!(UCSR0A & _BV(UDRE0)));

    // enable transmit mode
  PORTD |= _BV(PORTD5);
    // clear out TXC0
  UCSR0A |= _BV(TXC0);
  
  UDR0 = ch;
  
    // wait for transmit complete
  while(!(UCSR0A & _BV(TXC0)) );
    // enable receive mode
  PORTD &= ~_BV(PORTD5);

I can validate that it somewhat works, as I see the first response byte go back to the RS485 cable (clear housing = I can see RX/TX leds on the adapter, RX = green TX = red) every time, but then it tends to get stuck here and we get the inevitable timeouts from avrdude.

An odd thing I encountered during testing was that if I disabled (via defines in the makefile) the LED flash functions, and then added a LED_PIN |= _BV(LED) to the beginning of the putch() function with no corresponding change to turn off the LED, it would light up for a fraction of second, no matter what the first watchdog timer is set to (went to _8S). Which, seems to indicate that it's bailing out quickly on a timeout? If I removed the first call to config the watchdog entirely, surprisingly about 5% of the time, I can get it to start uploading the sketch, only to lose sync at some random point. This, of course, only happens at 57,600 - any lower or higher baud rate, and it never starts uploading. Natch: to go to the lower bauds, like 9600, I had to add a special define to avoid the SOFT_UART, because the code included there triggers compiler errors.

Does anyone have any experience with this, that they could shed some light on why the oddity? Why would the first byte response be ignored? Or, is it possible that the driver is not ready when the first response from avrdude comes after the first byte sent by the bootloader?

Thanks!

You are deciding to switch over from transmit to receive when the transmit buffer register is empty. However, this becomes empty when the last byte of data is transferred from it into the output shift register. Therefore it is still being clocked out of the shift register when you switch over. As a result you chop off the last byte and it doesn't get completely sent. This is a problem with RS485 communications.

On a wider issue how are you going to implement the auto reset that normally occurs on opening the serial port on the PC.

Grumpy_Mike:
You are deciding to switch over from transmit to receive when the transmit buffer register is empty. However, this becomes empty when the last byte of data is transferred from it into the output shift register. Therefore it is still being clocked out of the shift register when you switch over. As a result you chop off the last byte and it doesn't get completely sent. This is a problem with RS485 communications.

Spot-on Mike, Spot-on. I had added delays before, but they always seemed to go into the next received byte, blocking me from receiving it. You reminded me to remember that there's about 70nS signal delay on the transceiver (35nS to go from low to high, and another 35nS to go from high to low), and I just had an eye-rolling moment where I realized that by the time TXC0 went to one, I didn't have to wait for -all- bits to go out, only the last, and doing some math to find the best bitrate (i.e. that which adding 70nS got me closest back to a full uS), led me back to 115200 (woot.) with the following code:

  while (!(UCSR0A & _BV(UDRE0)));

  PORTD |= _BV(PORTD5);
  
  UCSR0A |= _BV(TXC0);
  
  UDR0 = ch;
  

  while(!(UCSR0A & _BV(TXC0)) );

    // delay for period to send one bit (8.68uS) plus rise and fall delay time on transceiver (70nS)
  _delay_us(8.75);
  
  PORTD &= ~_BV(PORTD5);

.. and now it works, every single time - like a charm. =)

On a wider issue how are you going to implement the auto reset that normally occurs on opening the serial port on the PC.

I changed the quickboot sequence to also look for a new sketch after power on, and increased watchdog timer to 8s to give a little extra time. There is no auto-reset, b/c I'm only using two wires for the RS-485, and even though the cable I have has RTS line, I choose not to use it. (Because there is no where to apply it without affecting normal cabling options between devices, and still use the same cable for programming or normal operations.)

e.g.:

  // Adaboot no-wait mod
  ch = MCUSR;
  MCUSR = 0;
  
  	// add check for power-on reset
  if (!(ch & _BV(EXTRF)) && !(ch & _BV(PORF)) ) appStart();

Thanks for the help! It's always nice to go have great minds to lean on, went from what seemed like an intractable problem (I had never touched the bootloader before, always just used standard one or just burned hex w/o one using ISP), to a working model in just north of 24 hours - would've been a lot less had I paid better attention to my arithmetic early on in the process grin Not to mention, I just got the secret juice to bump my normal RS-485 traffic up to 115.2k from 9.6k: would've never known about _delay_us if I hadn't touched the bootloader - fractional uS delays are nice! Admittedly, there's probably some inaccuracy here, but it works well enough.

Thanks again!

Hi Drone,

I'm also looking for a solution to upload a new sketch via RS485. I connected a MAX483 to the TX/RX of the 328p and use digital PIN2 to control the send/receive of data. The sketch works fine, so far so good. But I don't really understand the boot-loader...
Can you please help me out and tell me what/how and where I must modify the boot-loader to receive a new sketch (for example within the first seconds after power-up)? I think I'm missing some parts in the code samples above...

Already thanks.

Hi Korstian,

The code I included in my latest response should be added to the putch() function. With RS485 you must tell the transceiver when to go into transmit mode, and you do that whenever you are sending data back. Since putch() is used to push each byte out down the serial channel, you add the control over the DE/RE line to that function, and make sure to account for the proper amount of time it takes the driver to push out the byte before disabling transmit mode.

There's a complete working implementation of the bootloader in the source package you can download from here: Dynamic Perception you could simply modify the putch() function to use your correct DE/RE pin and then "make nanoMoCo"

Hi Drone,

First of all thanks for the reply.
In the mean time I found the way to do it. (Also by looking at the nanoMoCo early this week!)
I've made my own bootloader, started from the optiboot. (just 512 bytes, wasn't easy...)
I now also have the following functions:

  • When I power on, my RGD LED turns red for 4 seconds. This to indicate that it waits to get a new sketch. During this time I can start a download for a new sketch via RS485. If I don't send a new sketch, the watchdog fires and the application will start.
  • During a download of a new sketch, via this RS485 link, the LED blinks yellow.
  • After the download or after the 4 seconds, the sketch starts.
    I can also go back to the bootloader by sending a command via the RS485. (in my case I send 'Rxx', R for RESET and xx is the RS485 address)
    So now I can do everything via the RS485 without the use of pushing a reset button or toggle the power or via another direct connection.

I also added the _delay_us(8.75) to make sure but for me it also works without this delay.
Because I'm afraid that it would not work every time, I kept this delay. (even it worked fine without...)

Korstiaan

korstiaan:
I can also go back to the bootloader by sending a command via the RS485. (in my case I send 'Rxx', R for RESET and xx is the RS485 address)
So now I can do everything via the RS485 without the use of pushing a reset button or toggle the power or via another direct connection.

Thank you very much for sharing your time and work. Now I can upload a sketch on 485 after power-on, but I would like to do it after a software reset (sending a command like 'RXX'). The software reset can be done with the watchdog timer, but it seems that the bootloader doesn' wait a sketch after a watchdog reset. If I change the bootloader to wait for a sketch, I think it will get into a watchdog reset loop, 'appStart()' never will be called.

How have you done it?

Thank you.

Hi,

How have you done it?

Yes, that was not easy to find out. You cannot use the 'normal' way via a watchdog reset because of the way that optiboot works.
So I searched for another solution.
This is how it has to be done (it is more explaining then coding...)

In my sketch file I wrote this:

// next lines are for jumping to the correct place in the bootloader
// 0x3F07 comes from 7e0e out of the .lst file after compilation customized optiboot
// via 'omake atmega328' in directory C:\arduino-1.0.2\hardware\arduino\bootloaders\optiboot
// 0x3F07 = 7e0e/2 (words, bytes)
// 7e0e is the place AFTER the appstart via a watchdog reset
// (omake van optiboot maybe max. 512 dec or else you can't burn it
// you see the size as output with omake)

typedef void (*AppPtr_t) (void);
AppPtr_t GoToBootloader = (AppPtr_t)0x3F07; 

void setup() 
{
...
}

void loop()
{
...
  case 'R': // RESET TO BOOTLOADER
        noInterrupts();
        GoToBootloader();
        break;
...
}

I hope you understand it...

Good luck.

Korstiaan

korstiaan:
I hope you understand it...

Good luck.

Korstiaan

Great!! It works for me too! I probably never would have thought to do it this way. Or maybe after a few weeks researching. I really thank you for your help. Now I will work in some scripts for automate the process of sending the "reset" command before uploading the sketch, to one or to a set of devices.

Thank you!

Hi again,

to ensure the correct transmission of a sketch, I would like to set the baud rate to 9600 bps.

I change the Makefile

nanoMoCo: CFLAGS + = '-DFORCE_HARD_UART' '-DBAUD_RATE = 115200'

by

nanoMoCo: CFLAGS + = '-DFORCE_HARD_UART' '-DBAUD_RATE = 9600'

In boards.txt I change the speed to 9600 too. But when I try to upload a sketch I get a sync error. However, if instead of a 9600 I change it to 38400, the transmission is done without any problem.

What could be the problem?

Thank you for your time reading.

serch:
In boards.txt I change the speed to 9600 too. But when I try to upload a sketch I get a sync error. However, if instead of a 9600 I change it to 38400, the transmission is done without any problem.

What could be the problem?

Perhaps you missed a clock rate or fuse setting somewhere?

http://forum.arduino.cc/index.php?topic=124879.0

hi
I also have the same problem as you were.
In fact, I would try to download the sketch via RS485 Arduino UNO.
Among the Arduino UNO and the RS485 bus is just a MAX485 (rx and tx lines Arduino pin that is 0 and pin 1) on the other hand, there is a PC with a USB-RS485 converter.
At home I have 9 Arduino Uno on different boxes and if I can do this thing I avoid open junction boxes to update the firmware Arduino UNO.
Do you wonder if you managed to get this thing working, and if so if you can send me a copy.
Thanks.

korstiaan:

  • When I power on, my RGD LED turns red for 4 seconds. This to indicate that it waits to get a new sketch. During this time I can start a download for a new sketch via RS485. If I don't send a new sketch, the watchdog fires and the application will start.
  • During a download of a new sketch, via this RS485 link, the LED blinks yellow.
  • After the download or after the 4 seconds, the sketch starts.
    I can also go back to the bootloader by sending a command via the RS485. (in my case I send 'Rxx', R for RESET and xx is the RS485 address)
    So now I can do everything via the RS485 without the use of pushing a reset button or toggle the power or via another direct connection.

Hello, Could you share your working sources? I am trying to do exactly the same but the part that you reset from sketch and use Rxx is not working for me.

thanks in advance,