1

I'm working on sketches uploading over BLE. I had to write my own uploader (looking at avrdude sources with one eye) so i can change uploading behaviour as needed and it's working over USB just perfect but it does not get response from bootloader if trying over Bluetooth LE.

My hardware:

  • Arduino Uno (and i'm having Arduino Mega2560)
  • HM-10 BLE module

As HM-10 does not support DTR/RTS i had to add my own software reboot:

In brief: it reads incoming bytes on interrupt event and compares to reset command passed in ble_setup. If it's equal then it uses watchdog timer to reboot the board. If it's not equal it stores byte in byffer, then sketch loop() just returns incoming byes back ("echo").

#include <avr/wdt.h>

const char *ble_reset_cmd;
int ble_reset_index = 0;
int ble_available = 0;       // chars available
int ble_byte;                // income buffer as 1 char
int *ble_bytes = &ble_byte;  // income buffer
boolean delete_ble_bytes = false;

//#define BLE_DEBUG

// reboot Arduino board using Watchdog timer
void reboot() {
//    #ifdef BLE_DEBUG
    Serial.println("rebooting ...");
//    #endif

//    digitalWrite(13, HIGH);
//    delay(2000);

    wdt_enable(WDTO_15MS);
    while(1) { };
}

void ble_setup(int baud, const char *reset_cmd) {
    Serial.begin(baud);
    ble_reset_cmd = reset_cmd;
}

int Serial_available() {
    return ble_available;
}

int Serial_read() {
    if (ble_available < 0)
        return -1;

    int inByte = ble_bytes[--ble_available];
    if (ble_available == 0 && delete_ble_bytes) {
        // restore buffer as 1 char buffer
        delete[] ble_bytes;
        ble_bytes = &ble_byte;
        delete_ble_bytes = false;
    }
    return inByte;
}

void serialEvent() {
    if (Serial.available() <= 0)
        return;

    int inByte = Serial.read();
    if (inByte == ble_reset_cmd[ble_reset_index]) {
        // entered char is cmd character

        if (ble_reset_cmd[++ble_reset_index] == 0)
            reboot();

        #ifdef BLE_DEBUG  
        Serial.write("ble_index = ");
        Serial.print((byte)ble_reset_index);
        Serial.write('\n');
        #endif 

    } else {
        // entered char is NOT cmd character

        if (ble_reset_index > 0) {
            // got character not from cmd, so we should to return characters
            // starting ble_reset_cmd until current index and then this character

            ble_available = ble_reset_index + 1;
            ble_reset_index = 0;

            #ifdef BLE_DEBUG
            Serial.print("Copy ");
            Serial.print((byte)(ble_available - 1));
            Serial.println(" bytes");
            #endif

            // recreate input buffer using cmd characters
            ble_bytes = new int[ble_available + 1];
            delete_ble_bytes = true;
            ble_bytes[0] = inByte;
            for (int i=0; i<(ble_available-1); i++) {
              ble_bytes[i + 1] = ble_reset_cmd[ble_available - i - 2];
            }
            ble_bytes[ble_available] = 0; // null-terminated array

            #ifdef BLE_DEBUG
            Serial.print("input buffer now contains ");
            Serial.print(ble_available);
            Serial.println(" bytes");
            #endif

        } else {

            #ifdef BLE_DEBUG
            Serial.println("ble_index = 0");
            #endif

            // pass inByte next ...
            ble_available++;
            ble_byte = inByte;
        }

    }
} 

// ----------------------------------- 

int led = 13;
int ledHIGH = 0;

void setup() {
  ble_setup(9600, "AT+RESET");
  Serial.print("ready\n");
  pinMode(led, OUTPUT);
}

void loop() {
  while (Serial_available() > 0) {
    int inByte = Serial_read();

//    Serial.write('>');
//    Serial.write(inByte);
//    Serial.write('\n');
    ledHIGH = 1- ledHIGH;
    digitalWrite(led, ledHIGH);
    Serial.write(inByte); // echo
  }
}

As i said it's working good for USB uploading: right after rebooting bootloader waits for upload commands and start to reply:

015-04-07 21:22:10.860 Apploader[11226:303] Apploader v0.1
2015-04-07 21:22:12.772 Apploader[11226:303] DTR/RTS supported by Serial, resetting
2015-04-07 21:22:13.075 Apploader[11226:303] draining ...
2015-04-07 21:22:13.327 Apploader[11226:303] drain done
2015-04-07 21:22:13.328 Apploader[11226:303] Send: 0 [0x30]   [0x20] 
2015-04-07 21:22:13.328 Apploader[11226:303] draining ...
2015-04-07 21:22:13.723 Apploader[11226:303] drain done
2015-04-07 21:22:13.724 Apploader[11226:303] Send: 0 [0x30]   [0x20] 
2015-04-07 21:22:13.724 Apploader[11226:303] draining ...
2015-04-07 21:22:13.977 Apploader[11226:303] drain done
2015-04-07 21:22:13.978 Apploader[11226:303] Send: 0 [0x30]   [0x20] 
2015-04-07 21:22:13.979 Apploader[11226:303] Recv: . [0x14] 
2015-04-07 21:22:13.980 Apploader[11226:303] Recv: . [0x10] 
2015-04-07 21:22:13.980 Apploader[11226:303] Send: A [0x41]  . [0x81]   [0x20] 
2015-04-07 21:22:13.984 Apploader[11226:303] Recv: . [0x14] 
2015-04-07 21:22:13.984 Apploader[11226:303] Recv: . [0x04] 
2015-04-07 21:22:13.985 Apploader[11226:303] Recv: . [0x10] 
2015-04-07 21:22:13.985 Apploader[11226:303] Send: A [0x41]  . [0x82]   [0x20] 
2015-04-07 21:22:13.988 Apploader[11226:303] Recv: . [0x14] 
2015-04-07 21:22:13.988 Apploader[11226:303] Recv: . [0x04] 
2015-04-07 21:22:13.989 Apploader[11226:303] Recv: . [0x10] 
2015-04-07 21:22:13.989 Apploader[11226:303] Send: B [0x42]  . [0x86]  . [0x00]  . [0x01]  . [0x00]  . [0x01]  . [0x01]  . [0x01]  . [0x03]  . [0xff]  . [0xff]  . [0xff]  . [0xff]  . [0x00]  . [0x80]  . [0x04]  . [0x00]  . [0x00]  . [0x00]  . [0x80]  . [0x00]   [0x20] 
2015-04-07 21:22:13.996 Apploader[11226:303] Recv: . [0x14] 
2015-04-07 21:22:13.997 Apploader[11226:303] Recv: . [0x10] 
2015-04-07 21:22:13.997 Apploader[11226:303] Send: E [0x45]  . [0x05]  . [0x04]  . [0xd7]  . [0xc2]  . [0x00]   [0x20] 
2015-04-07 21:22:14.000 Apploader[11226:303] Recv: . [0x14] 
2015-04-07 21:22:14.001 Apploader[11226:303] Recv: . [0x10] 
2015-04-07 21:22:14.003 Apploader[11226:303] Send: P [0x50]   [0x20] 
2015-04-07 21:22:14.004 Apploader[11226:303] Recv: . [0x14] 
2015-04-07 21:22:14.008 Apploader[11226:303] Recv: . [0x10] 

But when trying over BLE bootloader stops listening in 200ms and then launches sketch, i can see sketch setup() started as it prints "ready". Also i can see it actually reboots the board as it sends "rebooting".

015-04-07 21:23:30.287 Apploader[11253:303] Apploader v0.1
2015-04-07 21:23:30.296 Apploader[11253:1503] CBCentralManager state is 5
2015-04-07 21:23:31.297 Apploader[11253:303] BleSerialDevice: open()
2015-04-07 21:23:31.298 Apploader[11253:303] Waiting for peripheral to be discovered ...
2015-04-07 21:23:31.591 Apploader[11253:1503] Found peripheral: <CBPeripheral: 0x100401940 identifier = D92ECAAB-51A5-44D5-8C6C-536CD797867E, Name = "Ardu", state = disconnected>
2015-04-07 21:23:31.593 Apploader[11253:1503] Stop scanning
2015-04-07 21:23:31.595 Apploader[11253:1503] Connecting to <CBPeripheral: 0x100401940 identifier = D92ECAAB-51A5-44D5-8C6C-536CD797867E, Name = "Ardu", state = disconnected>
2015-04-07 21:23:32.025 Apploader[11253:1503] Connected to peripheral <CBPeripheral: 0x100401940 identifier = D92ECAAB-51A5-44D5-8C6C-536CD797867E, Name = "Ardu", state = connected>
2015-04-07 21:23:32.041 Apploader[11253:1503] Discovered services for peripheral <CBPeripheral: 0x100401940 identifier = D92ECAAB-51A5-44D5-8C6C-536CD797867E, Name = "Ardu", state = connected>
2015-04-07 21:23:32.042 Apploader[11253:1503] Discovered service <CBService: 0x103701450>
2015-04-07 21:23:32.043 Apploader[11253:1503] Discovered service <CBService: 0x1037015f0>
2015-04-07 21:23:32.043 Apploader[11253:1503] Discovered service <CBService: 0x103701630>
2015-04-07 21:23:32.044 Apploader[11253:1503] Discovered characteristics for service <CBService: 0x103701630>
2015-04-07 21:23:32.044 Apploader[11253:1503] Discovered characteristic <CBCharacteristic: 0x1037005c0>
2015-04-07 21:23:32.045 Apploader[11253:1503] Subscribed to Rx value
2015-04-07 21:23:32.046 Apploader[11253:303] Connected successfully
2015-04-07 21:23:33.342 Apploader[11253:303] BleSerialDevice: open()
2015-04-07 21:23:33.343 Apploader[11253:303] already connected, exiting
2015-04-07 21:23:33.343 Apploader[11253:303] DTR/RTS supported by Serial, resetting
2015-04-07 21:23:33.594 Apploader[11253:303] Within interval
2015-04-07 21:23:33.595 Apploader[11253:303] Perform reset
2015-04-07 21:23:33.596 Apploader[11253:303] Send: A [0x41] T [0x54] + [0x2b] R [0x52] E [0x45] S [0x53] E [0x45] T [0x54] 
2015-04-07 21:23:33.647 Apploader[11253:303] Skip draining after reset
2015-04-07 21:23:33.648 Apploader[11253:303] Send: 0 [0x30]   [0x20] 
2015-04-07 21:23:33.648 Apploader[11253:303] Draining for 200 ms ...
2015-04-07 21:23:33.649 Apploader[11253:303] Start reading
2015-04-07 21:23:33.712 Apploader[11253:1503] Rx value received 15 bytes: r [0x72] e [0x65] b [0x62] o [0x6f] o [0x6f] t [0x74] i [0x69] n [0x6e] g [0x67]   [0x20] . [0x2e] . [0x2e] . [0x2e] . [0x0d] . [0x0a] 
2015-04-07 21:23:33.713 Apploader[11253:1503] _characterRead = true
2015-04-07 21:23:33.780 Apploader[11253:1503] Rx value received 6 bytes: r [0x72] e [0x65] a [0x61] d [0x64] y [0x79] . [0x0a] 
2015-04-07 21:23:33.780 Apploader[11253:1503] _characterRead = true
2015-04-07 21:23:33.850 Apploader[11253:303] Finish reading
2015-04-07 21:23:33.851 Apploader[11253:303] Drained 21 bytes
2015-04-07 21:23:33.852 Apploader[11253:303] Send: 0 [0x30]   [0x20] 
2015-04-07 21:23:33.853 Apploader[11253:303] Draining for 200 ms ...
2015-04-07 21:23:33.853 Apploader[11253:303] Start reading
2015-04-07 21:23:33.982 Apploader[11253:1503] Rx value received 2 bytes: 0 [0x30]   [0x20] 
2015-04-07 21:23:33.983 Apploader[11253:1503] _characterRead = true
2015-04-07 21:23:34.054 Apploader[11253:303] Finish reading
2015-04-07 21:23:34.055 Apploader[11253:303] Drained 2 bytes
2015-04-07 21:23:34.056 Apploader[11253:303] Send: 0 [0x30]   [0x20] 
2015-04-07 21:23:34.056 Apploader[11253:303] Reading 1 bytes ...
2015-04-07 21:23:34.057 Apploader[11253:303] Start reading
2015-04-07 21:23:34.057 Apploader[11253:303] _characterRead = false
2015-04-07 21:23:34.184 Apploader[11253:1503] Rx value received 1 bytes: . [0x00] 
2015-04-07 21:23:34.185 Apploader[11253:1503] _characterRead = true
2015-04-07 21:23:34.186 Apploader[11253:303] Finish reading
2015-04-07 21:23:34.186 Apploader[11253:1503] Rx value received 2 bytes: 0 [0x30]   [0x20] 
2015-04-07 21:23:34.186 Apploader[11253:1503] _characterRead = true
2015-04-07 21:23:34.186 Apploader[11253:303] Recv: . [0x00] 
2015-04-07 21:23:34.187 Apploader[11253:303] stk500_getsync() attempt 1 of 5: not in sync: resp=0x00

Once again - bootloader stops listening in 200 ms instead of 1-1.5 secons as over USB:

2015-04-07 21:23:33.596 Apploader[11253:303] Send: A [0x41] T [0x54] + [0x2b] R [0x52] E [0x45] S [0x53] E [0x45] T [0x54] 
...
2015-04-07 21:23:33.780 Apploader[11253:1503] Rx value received 6 bytes: r [0x72] e [0x65] a [0x61] d [0x64] y [0x79] . [0x0a]

I expect bootloader to reply with 0x14 0x10 as over USB:

2015-04-07 21:22:13.978 Apploader[11226:303] Send: 0 [0x30]   [0x20] 
2015-04-07 21:22:13.979 Apploader[11226:303] Recv: . [0x14] 
2015-04-07 21:22:13.980 Apploader[11226:303] Recv: . [0x10] 

As you can see later communication over Bluetooth is working good, as i receive exactly what was sent (0x30 0x20)

So when sketch is launched it's ok.

My ideas what's wrong:

  1. baud rate

Since serial.begin(N) is invoked after bootloader stops and launch sketch bootloader by default uses another baud rate? Is it correct? HM-10 default daud rate is 9600 and in sketch i also use 9600.

  1. wrong wire connection?

Bluetooth-arduino: rx -> tx tx -> rx Since its working for sketch i believe it's ok. Do i need any capacitors or resistors?

Any thoughts are highly appreciated!

UPDATE:

It seems i've found Uno bootloader source code and bootloader baud rate is 19200 by default

/* set the UART baud rate */
/* 20060803: hacked by DojoCorp */
//#define BAUD_RATE   115200
#ifndef BAUD_RATE
#define BAUD_RATE   19200
#endif

UPDATE2:

I'm in stuck as in this tutorial 115200 is used: http://makezine.com/projects/diy-arduino-bluetooth-programming-shield/

in step 8. "build the programmer circuit" R1 2.2K is set on arduino rx pin. Why? Another thing i can't understand is that SoftwareSerial baud rate is set to 38400 but BT module baud rate is set 115200 using "AT" command. why?

2 Answers 2

2

The bootloader used on the Uno is optiboot. But it contains the same "feature" of skipping straight to the application program if the reset source wasn't external.

You could remove this feature and reprogram the bootloader to the chip (using an external programmer).

Alternatively you can use a dirty trick, and jump into the bootloader right after this check of the reset source happens. It happens to be right in the beginning, so not much has happened. The problem is that optiboot makes the assumption that the system is in a freshly reset state. So either you can write a flag to EEPROM to indicate that you just made an intentional WDT reset, or you can jump and hope for the best(not really recomendable).

You could also write a routine to reset the system to the same state as after a reset. But it might not be trivial since there are a lot of reserved memory addresses that Atmel (the manufacturer of AVR's the microcontroller used on the uno, mega, and others) recommends you never write to. So basically you would have to more or less manually set a lot of registers to their reset default (usually zero), in the right order (eg. first tri-state / set pins to input).

You can jump to the bootloader using this inline assembler code:

asm volatile ("jmp 0x7e00");

Optiboot fits in 512 bytes = 256 AVR words. The bootloader address can be found in the datasheet Table 27-13. Boot Size Configuration, ATmega328/328P. It is printed in the datasheet as the word address, but the avr-as assembler takes the byte address. Word address 0x3f00 * 2 = 0x7e00 byte address.

You can also see the address in optiboot_atmega328.lst where code starts at byte address 0x7e00.

In order to jump past the boot source check we find the first address in optiboot_atmega328.lst that comes after the check. It is 0x7e0a. We then modify our bootloader jump:

asm volatile ("jmp 0x7e0a");

You will want to make this jump first thing after a reset. We want to skip the Arduino initialization code. As per suggestion by user Comintern in another question you can write a main routine with a longer prototype to make the linker prefer it over the one shipped with Arduino:

int main(int argc, char** argv) {
  if (/* is your bootloader reset flag set in EEPROM */) {
    // first clear the flag
    // ...

    // Jump to bootloader, after reset source check (optiboot on atmega328)
    asm volatile ("jmp 0x7e0a");
  }

  // Else proceed same as Arduino main
  init();

  #if defined(USBCON)
      USBDevice.attach();
  #endif

  setup();

  //The one shot loop...
  loop();
  if (serialEventRun) serialEventRun();
  //Put whatever else you need to do between before the unending loop here.

  for (;;) {
      loop();
      if (serialEventRun) serialEventRun();
  }

  return 0;
}

Note:

Using these jumps depends on your bootloader being exactly the one referenced. AFAIK that is the one the Uno ships with, but that could change in the future.

The code is untested.

The main overwrite trick depends on the linker exhibiting this behaviour which I think is also prone to change (it could be guaranteed by c/c++ standard, I don't know that).

15
  • Thanks a lot for such a detailed suggestion. In general i believe it would be better to perform reset instead of jumping into bootloader code directly and having deal with restoring state/registers/etc. Is there any possibility to perform "clean" reset to jump into bootloader just external reset button clicked or just powered on?
    – 4ntoine
    Apr 9, 2015 at 10:08
  • 1
    The reset is taken care of by your watchdog reset. The manual reset was just an alternative suggestion to avoid using EEPROM. The flow will be: set EEPROM flag - do watchdog reset - bootloader starts - bootloader exits because not external reset - your main runs - read EEPROM flag - clear EEPROM flag - jump to bootloader past check Hardware state will be very close to external reset.
    – user2973
    Apr 9, 2015 at 10:27
  • 1
    Some people just connect a wire from an output to reset pin and pulls it low to reset. That isn't recommended though, because the pin won't be held low long enough to guarantee a proper reset. But supposedly it works for some people.
    – user2973
    Apr 9, 2015 at 10:31
  • 1
    yes, that's correct. On the mega the bootloader is different. It might not perform the check of the reset source.
    – user2973
    Apr 10, 2015 at 19:36
  • 1
    It would have had to be provided by those who compiled it originally. You can disassemble the hex file and manually find the place, but it won't be very convenient. But I don't think the bootloader on the mega performs that check so try just jumping to the bootloader (address from hex file or datasheet).
    – user2973
    Apr 12, 2015 at 17:12
1

It seems that's the reason why bootloader does not wait:

// Check if the WDT was used to reset, in which case we dont bootload and skip straight to the code. woot.
    if (! (ch &  _BV(EXTRF))) // if its a not an external reset...
        app_start();  // skip bootloader

Now i have another question ;) How to reboot arduino board from the sketch to jump into bootloader waiting for upload command?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.