Una de mis viejas aspiraciones cuando construyo robots es poder programarlos sin tener que recogerlos, enchufarles un cable usb o un programador ICSP y volverlos a dejar en su sitio.
En este artículo explicaré cómo con un Arduino UNO y un módulo ESP8266 (llamado WI07C) se puede programar un sketch de Arduino en la placa sin tener que estar cerca de esta, todo mediante wifi y sockets tcp/ip.
Descripción general
Como se puede ver en el vídeo tenemos un Arduino UNO conectado a un display HD44780 y a un módulo wifi ESP8266. Arduino envía cadenas hello world al servidor sockettest que está escuchando por el puerto 49000. Se modifica el código Arduino en el IDE poniendo hello folks, se compila y el fichero .hex generado (se puede ver donde está activandolo en Archivo/Preferencias/compilación) se copia a la carpeta del programa en python. Cuando Arduino recibe un comando reboot se conecta al servidor python por el puerto 50000 entrando en modo de datos, acto seguido se reinicia y empieza la programación remota de Arduino. El proceso se reliza dos veces en el vídeo.
Lo que se aprovecha es el método que tiene Arduino para programarse, ya que usando el puerto serie después de un reset se puede programar un Arduino si se sigue el protocolo STK500 implementado en el bootloader.
Esquema de conexiones
El Arduino y el display HD44780 se alimentan a 5 voltios, el módulo ESP8266 se alimenta a 3,3 voltios. Como el pin rx del módulo wifi sólo puede funcionar a 3,3V se usa un diodo zener de 3,3 voltios junto con una resistencia de 100 Ω.
En las placas Arduino, el pin de reset del microcontrolador está conectado a una resistencia y esta a su vez está conectada a VCC, con lo que para el microcontrolador el pin está a nivel alto. Cuando se pulsa el botón de reset lo que se hace es derivar la corriente hacia masa (ya que el bóton está conectado a esta) y el microcontrolador, al estar a nivel bajo el pin de reset, realiza un reseteo. Cuando el microcontrolador arranca, todos los pines están configurados como entradas (alta impedancia) y por eso no le afecta que el pin de reset esté conectado directamente al pin 12. Si se configura el pin 12 como salida y luego se conecta a masa (nivel bajo o LOW) se provoca el mismo efecto que si se pulsase el botón de reset, además, al existir la resistencia anteriormente mencionada, no hay que preocuparse de que se produzca un cortocircuito al unir VCC con masa (GND).
Sketch de Arduino
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 |
// Copyright (C) 2014 SISTEMAS O.R.P. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. #include <LiquidCrystal.h> #define SEARCH_AFTER_RESET "ready" #define INTRO "\r\n" #define AP_NORMAL "SISTEMASORP" #define PWD_NORMAL "mypassword" #define HOST_NORMAL "192.168.1.33" #define PORT_NORMAL "49000" #define AP_BOOTLOADER "SISTEMASORP" #define PWD_BOOTLOADER "mypassword" #define HOST_BOOTLOADER "192.168.1.33" #define PORT_BOOTLOADER "50000" boolean ok = false; int counter = 0; LiquidCrystal lcd(2, 3, 4, 5, 6, 7); //----------------------------------------------------------------------------------------------------------------------------------- /* Parameters: show: The string to be printed. Returns: Nothing. Description: It clears the LCD and print the string. */ void print_lcd(String show) { lcd.clear(); lcd.print(show); } //----------------------------------------------------------------------------------------------------------------------------------- /* Parameters: command: The string to send. timeout: The maximum time in milliseconds the function can be running before a time out. wait_for1: The search string when the command succeeded. wait_for1: The search string when the command failed. Returns: The string contained in wait_for1, wait_for2 or the string TIMEOUT. Description: It sends the command trough the serial port and waits for one of the expected strings or exit with timeout */ String send(String command, int timeout, String wait_for1, String wait_for2) { unsigned long time = millis(); String received = ""; Serial.print(command); Serial.print(INTRO); while(millis() < (time + timeout)) { if(Serial.available() > 0) { received.concat(char(Serial.read())); if(received.indexOf(wait_for1) > -1) { return wait_for1; } else if(received.indexOf(wait_for2) > -1) { return wait_for2; } } } return "TIMEOUT"; } //----------------------------------------------------------------------------------------------------------------------------------- /* Parameters: wait_for: The search string. timeout: The maximum time in milliseconds the function can be running before a time out. Returns: True if the string was found, otherwise False. Description: It waits for the expected string. */ boolean look_for(String wait_for, int timeout) { unsigned long time = millis(); String received = ""; while(millis() < (time + timeout)) { if(Serial.available() > 0) { received.concat(Serial.readString()); if(received.indexOf(wait_for) > -1) { return true; } } } return false; } //----------------------------------------------------------------------------------------------------------------------------------- /* Parameters: command: The string to send. timeout: The maximum time in milliseconds the function can be running before a timeout. wait_for1: The search string when the command succeeded. wait_for1: The search string when the command failed. Returns: True if the wait_for1 string was found, otherwise False. Description: It sends the command trough the serial port and waits for one of the expected strings or exit with timeout. */ boolean send_command(String command, int timeout, String wait_for1, String wait_for2) { String state; state = send(command, timeout, wait_for1, wait_for2); if(state == wait_for1) { return true; } else if(state == wait_for2) { // do something on error } else { // do something on timeout } return false; } //----------------------------------------------------------------------------------------------------------------------------------- /* Parameters: Nothing Returns: True if all commands were successfully, otherwise False. Description: It initializes the module, joins to the access point and connects to the server. */ boolean init_commands() { print_lcd("Changing mode"); if(send_command("AT+CWMODE=1", 5000, "OK", "ERROR")) { print_lcd("Resetting module"); if (send_command("AT+RST", 5000, SEARCH_AFTER_RESET, "ERROR")) { print_lcd("Joining AP"); String cwjap = "AT+CWJAP=\""; cwjap += AP_NORMAL; cwjap += "\",\""; cwjap += PWD_NORMAL; cwjap += "\""; if (send_command(cwjap, 20000, "OK", "FAIL")) if (send_command("AT+CIPMUX=0", 2000, "OK", "ERROR")) if (send_command("AT+CIPMODE=0", 2000, "OK", "ERROR")) { print_lcd("Connecting host"); String cipstart = "AT+CIPSTART=\"TCP\",\""; cipstart += HOST_NORMAL; cipstart += "\","; cipstart += PORT_NORMAL; if (send_command(cipstart, 5000, "OK", "ERROR")) return true; } } } return false; } //----------------------------------------------------------------------------------------------------------------------------------- /* Parameters: Nothing Returns: True if all commands were successfully, otherwise False. Description: It initializes the module, joins to the access point, connects to the server and starts the protocol. */ boolean boot_commands() { print_lcd("Joining AP"); String cwjap = "AT+CWJAP=\""; cwjap += AP_BOOTLOADER; cwjap += "\",\""; cwjap += PWD_BOOTLOADER; cwjap += "\""; if (send_command(cwjap, 20000, "OK", "FAIL")) if (send_command("AT+CIPMUX=0", 2000, "OK", "ERROR")) if (send_command("AT+CIPMODE=1", 2000, "OK", "ERROR")) { print_lcd("Connecting host"); String cipstart = "AT+CIPSTART=\"TCP\",\""; cipstart += HOST_BOOTLOADER; cipstart += "\","; cipstart += PORT_BOOTLOADER; if (send_command(cipstart, 5000, "OK", "ERROR")) if (send_command("AT+CIPSEND", 2000, ">", "ERROR")) { print_lcd("Init protocol"); if (send_command("hello", 2000, "welcome", "error")) if (send_command("Arduino_remote_example.cpp.hex", 2000, "ok", "error")) return true; } } return false; } //----------------------------------------------------------------------------------------------------------------------------------- /* Parameters: Nothing Returns: True if all commands were successfully, otherwise False. Description: It sends a string to the remote host and show it in the display. */ boolean test() { String command = "AT+CIPSEND="; String to_send = "hello guys! "; to_send += counter; command += to_send.length() + 2; if (send_command(command, 2000, ">", "ERROR")) if (send_command(to_send + "\r\n", 2000, "OK", "ERROR")) { lcd.clear(); lcd.print(to_send); counter++; return true; } return false; } //----------------------------------------------------------------------------------------------------------------------------------- void setup() { pinMode(13, OUTPUT); Serial.begin(115200); lcd.begin(16, 2); lcd.print("Init WI07C..."); // Remove any garbage from the RX buffer delay(3000); while(Serial.available() > 0) Serial.read(); ok = init_commands(); } //----------------------------------------------------------------------------------------------------------------------------------- void loop() { if(ok) { digitalWrite(13, HIGH); ok = test(); if(ok && look_for("reboot", 5000)) { if(boot_commands()) { pinMode(12, OUTPUT); digitalWrite(12, LOW); for(;;); } else { ok = false; } } } else { digitalWrite(13, LOW); lcd.clear(); lcd.print("Error sending"); lcd.setCursor(0, 1); lcd.print("AT commands"); for(;;); } } |
Lo que hace el skecth de Arduino es:
- Inicializa el puerto serie a 115200 bps.
- Elimina los caracteres que hubiera en el buffer de entrada del puerto serie.
- Inicializa el módulo wifi en modo estación, lo resetea, se conecta al punto de acceso normal, configura las conexiones como simples en modo normal y se conecta al servidor normal por el puerto 49000. Si hubiese algún fallo en algún punto pararía la ejecución y lo indicaría en el display.
- Envia una cadena de texto y un número consecutivo tanto al servidor como al display. Si hubiese algún fallo en algún punto pararía la ejecución y lo indicaría en el display.
- Si entre el envío de las cadenas de texto se recibe una cadena reboot entonces resetea el módulo, se conecta al punto de acceso bootloader, configura las conexiones como simples en modo normal y se conecta al servidor de programación por el puerto 50000. Acto seguido envía una cadena hello y espera a recibir una cadena welcome, si ha sido así, entonces envía el nombre del fichero .hex con el que quiere ser programado el Arduino y espera a recibir una cadena ok, momento en el cual configura el pin 12 como salida y lo pone a nivel bajo, conectándolo a masa y provocando un reset en el Arduino. Si hubiese algún fallo en algún punto pararía la ejecución y lo indicaría en el display.
Aquí cabe destacar cómo funciona el sistema de reseteo: Justo después del reseteo, el pin de TX de Arduino está a nivel bajo durante un tiempo, lo que provoca que el módulo wifi vea eso como un byte 0, que enviará a través de la conexión TCP/IP. El servidor de programación aprovechará esta circunstancia para saber cuando ha empezado el reseteo e iniciar el protocolo STK500. El bootloader de Arduino entra en acción después del reseteo y espera a recibir ordenes del protocolo STK500, si las recibe actúa en consecuencia, si no, ejecuta el programa principal.
Servidor de programación
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 |
#!/usr/bin/env python # Copyright (C) 2014 SISTEMAS O.R.P. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. import sys import binascii import struct import select import socket import errno MAX_TIMEOUT = 500 SUCCESS = "success" FAILED = "failed" PORT = 50000 #------------------------------------------------------------------------------------------------- ''' Class containing the data from non-contiguous memory allocations ''' class Data: def __init__(self, begin, data): self.begin = begin self.data = data self.count = len(data) #------------------------------------------------------------------------------------------------- ''' Parameters: line: The line to parse Returns: The size of data. The address of data. The type of data. The line checksum. True if the checksum is correct, otherwise False. Description: It parses a line from the .hex file. ''' def parse_line(line): ok = False size = int(line[1:3], 16) address = int(line[3:7], 16) type = int(line[7:9], 16) next_index = (9 + size * 2) data = binascii.a2b_hex(line[9:next_index]) checksum = int(line[next_index:], 16) #checking if checksum is correct sum = size + (address >> 8) + (address & 0xFF) + type for byte in data: sum += ord(byte) if (~(sum & 0xFF) + 1) & 0xFF == checksum: ok = True return (size, address, type, data, checksum, ok) #------------------------------------------------------------------------------------------------- ''' Parameters: chunks: An array with different chunks of data. path: The path to the .hex file to read Returns: True if the reading was successfully, otherwise False. Description: It reads a .hex file and stores the data in memory. ''' def read_hex_file(chunks, path): try: file = open(path, 'r') except IOError: print "Hex file not loaded" return False line = file.readline() if line[0] != ':': print "The file seems to be a not valid .hex file" file.close() return False size, address, type, data, checksum, ok = parse_line(line.strip()) if not ok: print "The checksum in line 1 is wrong" file.close() return False chunks.append(Data(address, data)) # Read the other lines index = 0 count = 2 for line in file: size, address, type, data, checksum, ok = parse_line(line.strip()) if not ok: print "The checksum in line", count, "is wrong" file.close() return False if chunks[index].begin + chunks[index].count == address: chunks[index].count += size for code in data: chunks[index].data += code else: chunks.append(Data(address, data)) index += 1 count += 1 return True #------------------------------------------------------------------------------------------------- ''' Parameters: None Returns: The server socket Description: It opens a server socket at the specified port and listens to connections. ''' def init_server(): server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(('', PORT)) server.listen(1) return server #------------------------------------------------------------------------------------------------- ''' Parameters: cli: The client socket response: The search string timeout: The maximum time in milliseconds the function can be running before a time out. Returns: True if the string was found, otherwise False. The received string. Description: It waits for the expected string. ''' def wait_for(cli, response, timeout): inputs = [cli] received = "" milliseconds = 0 while milliseconds < timeout: rlist, wlist, xlist = select.select(inputs,[],[], 0.001) if len(rlist) > 0: received += cli.recv(1) if response in received: return True, received milliseconds += 1 return False, received #------------------------------------------------------------------------------------------------- ''' Parameters: cli: The client socket timeout: The maximum time in milliseconds the function can be running before a time out. length: The number of bytes to receive. Returns: True if the string has the required length, otherwise False. The received string. Description: It waits for the required length of bytes. ''' def return_data(cli, timeout, length = 1): inputs = [cli] received = "" milliseconds = 0 while milliseconds < timeout: rlist, wlist, xlist = select.select(inputs,[],[], 0.001) if len(rlist) > 0: received = cli.recv(length) return True, received milliseconds += 1 return False, received #------------------------------------------------------------------------------------------------- ''' Parameters: cli: The client socket Returns: True if the string was found, otherwise False Description: It waits for the acknowledge string. ''' def acknowledge(cli): if wait_for(cli, "\x14\x10", MAX_TIMEOUT)[0]: #STK_INSYNC, STK_OK print SUCCESS return True else: print FAILED return False #------------------------------------------------------------------------------------------------- ''' Parameters: chunks: An array with different chunks of data. cli: The client socket Returns: Nothing Description: It starts the STK500 protocol to program the data at their respective memory address. ''' def program_process(chunks, cli): print "Connection to Arduino bootloader:", counter = 0 cli.send("\x30\x20") #STK_GET_SYNCH, SYNC_CRC_EOP if not acknowledge(cli): return print "Enter in programming mode:", cli.send("\x50\x20") #STK_ENTER_PROGMODE, SYNC_CRC_EOP if not acknowledge(cli): return print "Read device signature:", cli.send("\x75\x20") #STK_READ_SIGN, SYNC_CRC_EOP if wait_for(cli, "\x14", MAX_TIMEOUT)[0]: #STK_INSYNC ok,received = return_data(cli, MAX_TIMEOUT, 3) print binascii.b2a_hex(received) if not wait_for(cli, "\x10", MAX_TIMEOUT)[0]: #STK_INSYNC print FAILED return else: print FAILED return for chunk in chunks: total = chunk.count if total > 0: #avoid the last block (the last line of .hex file) current_page = chunk.begin pages = total / 0x80 index = 0 for page in range(pages): print "Load memory address",current_page,":", cli.send(struct.pack("<BHB", 0x55, current_page, 0x20)) #STK_LOAD_ADDRESS, address, SYNC_CRC_EOP if not acknowledge(cli): return print "Program memory address:", cli.send("\x64\x00\x80\x46" + chunk.data[index:index + 0x80] + "\x20") #STK_PROGRAM_PAGE, page size, flash memory, data, SYNC_CRC_EOP if not acknowledge(cli): return current_page += 0x40 total -= 0x80 index += 0x80 if total > 0: print "Load memory address",current_page,":", cli.send(struct.pack("<BHB", 0x55, current_page, 0x20)) #STK_LOAD_ADDRESS, address, SYNC_CRC_EOP if not acknowledge(cli): return print "Program memory address:", cli.send(struct.pack(">BHB", 0x64, total, 0x20) + chunk.data[index:index + total] + "\x20") #STK_PROGRAM_PAGE, page size, flash memory, data, SYNC_CRC_EOP if not acknowledge(cli): return print "Leave programming mode:", cli.send("\x51\x20") #STK_LEAVE_PROGMODE, SYNC_CRC_EOP acknowledge(cli) #------------------------------------------------------------------------------------------------- def main(): print "Arduino remote programmer 2014 (c) SISTEMAS O.R.P" print "Listen to connections" ser = init_server() inputs = [ser] while True: rlist, wlist, xlist = select.select(inputs,[],[]) for s in rlist: if s == ser: cli, addr = s.accept() print addr[0], "connected" # It assures the connection is for programming an Arduino and not other service. if wait_for(cli, "hello", 5000)[0]: cli.send("welcome") ok, received = wait_for(cli, "hex", 5000) if ok: chunks = [] print "Read hex file", received.strip() if read_hex_file(chunks, received.strip()): cli.send("ok") # Wait for the byte '0' sent by Arduino after resetting if wait_for(cli, "\x00", MAX_TIMEOUT)[0]: program_process(chunks, cli) else: cli.send("error") cli.close() print "Listen to connections" #------------------------------------------------------------------------------------------------- if __name__ == "__main__": main() |
Lo que hace el servidor de programación es:
- Crea un socket que escuche por el puerto 50000
- Cuando un cliente de conecta espera a la cadena hello, si la recibe reponde con una cadena welcome.
- Espera a que el cliente le envíe un nombre de fichero .hex. Trata de abrir el fichero en el mismo directorio y lo lee interpretando todas las líneas para guardar los datos del programa en memoria. Si todo va bien envía una cadena ok.
- Espera a recibir el byte 0, y cuando lo recibe empieza el protocolo STK500 para comunicarse con el bootloader de Arduino y programarlo. Básicamente lo que hace es entrar en modo de programación, indicarle a que páginas quiere acceder y enviar los datos de cada página, así hasta que ha enviado todos los datos del programa, después sale del modo de programación cerrando la conexión
Aquí cabe destacar que cuando se cierra la conexión TCP/IP con el cliente (ya sea por un error o porque el proceso de programación ya ha terminado), el módulo ESP8266 sale del modo de datos automáticamente y no es necesario que el nuevo sketch tenga que enviarle la cadena de escape +++ para poder entrar otra vez en modo de comandos.
Conclusiones
Espero que esto os sirva para que en vuestros proyectos podais programar remotamente vuestros Arduinos a través de una red local o Internet.
Habría que tener en cuenta que el proceso de actualización puede quedarse a medias si la conexión a la red wifi es lenta y provocaría que el programa no se ejecutara correctamente. Así que que una mejora sería acoplar un chip aparte que reprogramase el Arduino por ICSP con un programa preestablecido en caso de que detectase que la programación no fue finalizada correctamente.
Comentar que la versión de firmware del módulo ESP8266 que he usado es la 0019000902 y el Arduino es un UNO. Ambos funcionan a 115200 bps, pero si quisierais utilizar otras velocidades (en otros Arduinos el bootloader configura el puerto serie a 19200 o 57600 bps) habría que cargar un firmware que lo permitiese, como por ejemplo la versión 0.922 de electrodragon y su comando AT+CIOBAUD.
Enhorabuena por el articulo, todo un éxito salir en Hackaday.
El componente del ESP-01 para Fritzing, ¿lo has hecho tu? ¿o esta disponible en algún sitio?.
Gracias, parece que poco a poco van introduciendo artículos en castellano 🙂
El componente para fritzing lo descargué en https://github.com/ydonnelly/ESP8266_fritzing.
Muy buena la aportación. Estuve buscando por la red y aqui es donde esta mejor analizado en módulo(y en castellano. Estoy esperando los que he pedido para conectar con arduinos.
Great idea man!
I would like to point out that the zener diode is indispensable to make it work, not only because of level translation, but also because no zero byte is sent after reset without it..
I’ve rewritten your script in c++ under Qt, with a GUI that let choose the hex file to upload, directly searching in favourite paths..
My devices have a simple web interface with a server running on port 80, so I send the /upgrade command followed by the IP address and port of the progamming server. The device than reboots and connects to the server to be programmed.. Unfortunetely I’m experiensing some troubles during programming, which usually stops after 8-9 pages. So if I upload the blink sketch everything works because it’s about 5 pages, but for bigger sketch it fails, probably due to sync problems, because I receive no ack.
If you are interested we can work on togheter..
Trying to figure out the cause of my problem, I found that it depends on the responsivity of the router. In my network the delay is too high, whereas in my office it works pretty well with every kind of sketch… Again, nice job! I learned many things with your script, thank you..
That is my problem too. I have a VPS where the python script is running and due to net delays, sometimes the arduino bootloader runs the main program while it is not loaded at all. I’m working in an alternative circuit to store the downloaded sketch in a 24AA1025 eeprom memory and then program the arduino with ICSP from a PIC12F1822.
Your alternative solution seems similar to Moteino boards. I was minding to modify the esp8266 firmware instead to limit the need of new hardware. The sketch upload procedure may be driven by it: we can use it to parse the upload command and internally switch to programming mode. We can also use the GPIO2 pin to reset arduino and implement a little buffer to avoid delay problems.. Next week I wil try to read v0922 firmware to figure out if it’s feasible..
Well, it is a bit different because the boot loader is the original from arduino. The Arduino sketch should program the eeprom by I2c with the new sketch and then the pic reads the eeprom and program the AVR by isp.
Let me know your advances about the modified esp8266 firmware to program the Arduino. It seems interesting.
Hello can you help me? My code is blocking on AT+CIPSTART
can you send me you application to upload. Hex?
Thanks
Hi Carlo,
It’s a long time from this post. But… could you finally solve
your problem ?
Julio
tenes idea si es posible usar el firmware firmata y mandarle los comandos por wifi en lugar de por usb?
En principio es lo mismo, al final firmata usa el puerto serie al igual que el módulo esp8266
Pingback: Módulo WIFI ESP8266 - El blog de Marcos
Hello, very interesting article!
Could you write english version, cause did not understand details.
Thank you.
http://translate.google.com
hola , alguien sabe si es posible controlar el arduino uno remotamente con el módulo ESP8266 conectado por wifi a intternet para activar un contacto seco ?
Hola Eduardo si es posible controlar tu arduino por el modulo esp solo tienes que colocar el modulo al arduino como aparecen en internet rx/ tx tx/rx y listo envias tu cadena es cual programa el chip de arduino para que la reciba y utilizar los comandos AT+CIMUX = 1 & Chr$(13) luego AT+CIPSERVER=1,80 & Chr$(13) y AT+CIPSEND=0,30 & Chr$(13) donde el el 30 es un ejemplo de 30 caracteres que estas enviando.
Muchas gracias por compartir esta información, es muy útil. Lo he probado con un UNO y funciona de maravilla, mientras que con un Pro Mini no hay modo. ¿Qué puede ser?
Me imagino que por la velocidad del puerto serie a la que funciona el bootloader, en el arduino UNO es de 115200, en el mini pro es de 57600, por lo que para que funcione sólo deberías cambiar en el sketch de arduino la velocidad del puerto serie a 57600 y configurarla también a esa velocidad en el módulo esp8266.
Gracias por tan pronta respuesta. Lo he probado a 115200 y 57600 con el mismo resultado 🙁
El mensaje que veo desde el equipo donde corre el programa python es este:
Connection to Arduino bootloader: failed
Es decir, parece que va bien la secuencia hello-wellcome-programa-ok-reincio y entra en la función «program_process», pero ahí dentro es donde falla.
Can you helo me ?
please
Ole ole, solucionado 🙂 Funciona efectivamente a 57600. El problema, era además de ese que el pin RST del Arduino no estaba bien soldado y cuando lo ponía a GND desde la salida 12 en realidad no se estaba produciendo el reinicio. Tras muchas pruebas, me di cuenta que era ese el problema….quitar vieja soldadura, soldadura nueva, y ya funciona 🙂
Muchísimas gracias 🙂
Estupendo, gracias por compartir tu experiencia. Ahora ya se que con un arduino mini pro también funciona.
Can you help me ?
my circuit not function…
i have problem with bootloader…
i can upload new code…
can you explain me the circuit and the correct code please?
thanks
Muy interesante artículo!
Sabes si se podría implementar con un ESP que tiene el firmware NodeMCU ??
Si, pero desconozco como se programa con LUA
Pingback: Programming an Arduino via WiFi with the ESP8266 -Use Arduino for Projects
Hi your blog is really good..
help me out..
I am using arduino uno for this one
AT version:0.23.0.0(Apr 24 2015 21:11:01)
SDK version:1.0.1
my cmd terminal says
Listen to connections
192.168.1.xx connected
Traceback (most recent call last):
File «arduino_remote_program_wifi.py»,line 247. in
main()
File «arduino_remote_program_wifi.py»,line 231. in main
if wait_for(cli,»hello»,5000)[0]:
File «arduino_remote_program_wifi.py»,line 231. in wait_for
received +=cli.recv(1)
socket.error:[Errno 10054] An existing connection was forcibly closed by the remote host
help me out..
What moment does it happens? It seems Arduino close the connection during negotiation.
Hi, i’m trying to use this solution, all steps are working correct except for entering bootloader. TX is expected to be low for a while, but it dont, so python server do not recieve \x00 and do not start updating firmware. How can i solve this?
Please, help, your article is very helpful, just one more step.
It should do it automatically. Try to send a 0 byte before digitalWrite(12, LOW);
i have same problem, how exactly we send 0 bytes from wifi? Please give me a hint.
i have same problem, python server do not recieve \x00 .
i try sending 0 byte if (send_command(«\x00», 2000, «ok», «error»)) . but still not working
please give me a hit..
Hi
Try with this new code: https://www.sistemasorp.es/blog/programar_arduino_por_wifi.zip
Regards.
Hello i try to send a byte 0, but the program remain looped in
listen to connection
192.168.1.185 connected
listen to connection
192.168.1.185 connected
listen to connection
192.168.1.185 connected
…
where is the problem ?
Can you answer me please?
THANKS
Hello i am the same problem can you help me?
have you find a solution?
please answer me
thanks
Hi
Try with this new code: https://www.sistemasorp.es/blog/programar_arduino_por_wifi.zip
Regards.
Un post genial, me gustaria preguntarte una duda,
¿Este sistema de programación es compatible con el arduino Mega 2560?
He leido por la web el arduino mega dispone de un bootloder que usa el protocolo STK500v2 y no el STK500(v1)
Pues sinceramente no me he mirado la versión 2 del protocolo. La diferencia estriba en el acceso a bloques de memoria por encima de los 128K. Siempre puedes probar ya que si algo falla se puede reflashear el microcontrolador con su firmware original.
Really interesting tutorial, and i really appreciate your work!
Here I’m trying to up date my arduino uno firmware same manner as you described here, but instead of ESP 8266 I’m using RN171 WiFly module from roving network along with arduino.
every thing is working grate, but the problem is after sending «Reboot» command from my server, arduino get reset and connect the python server having different port, but at the last moments I’m getting message from python script as «connect to arduino boot-loader failed!»
then after I’d try with different baudrate like 9600, 19200 but nothing happen and getting same result!!!!
any suggestions
Regards
Mahendra sondagar
Hey, I don’t know if you’re still paying attention to this, but I was just wondering if you could give me some advice? For some reason when the arduino is sending out the «hello guys» string, my server is not picking it up. I have checked and I am sure they are on the same IP and port. Also, how are you inputting where the hex file is saved? Are you sending that to your python server? Those are my two questions, thanks so much for doing this!
The first question: If you follow the code as is, I guess you first received the «Hello world» successfully, however when you switch to the new firmware you didnt receive the «Hello guys», do you?. I think the firmware was not programmed correclty. Did the script python returns a «Program memory address: success» and «Leaving programming mode: success»?
The second question: The arduino sketch send the .hex file it wants to be programmed (Arduino_remote_example.cpp.hex); that file, after compiling in arduino ide, is stored in the same directory than python script is located.
OK, so I got the rest of that stuff to work. However, once the arduino connects with the Python script, nothing happens. Any idea what could be causing the server to not start sending the new info, or the arduino not entering the bootloader? This is a picture of the issue: https://drive.google.com/file/d/0B1f4r8iM9x2QdlA2eS1XRUl4VkU/view?usp=sharing
This is after I have told it to reboot and after it connected to the python server. Thanks for the help!
I know I already asked you for help, but I figured out wwhat the problem was. Unfortunately, I have a few other questions.
1- Do I send the zero byte before the reset, or after the reset?
2- What does pin 13 have to do with anything? I can’t see what it is being used for on the code or the schematic.
3- when I pull pin 12 low, that triggers the reset, correct?
Thanks so much!
Hi
Try with this new code: https://www.sistemasorp.es/blog/programar_arduino_por_wifi.zip
Regards.
Hey, so I am having a problem with entering the bootloader. Once I reset the board, it does not stay in the bootloader, and just keeps going in the loop without updating the board. I tried sending a zero byte, but that didn’t work either, I still kept timing out. Any suggestions for how to enter the bootloader?
I was doing this for the zero byte:
String zero = «\x00»;
Serial.println(«AT+CIPSEND=1»);
Serial.print(zero);
pinMode ( 12 , OUTPUT ) ;
digitalWrite ( 12 , LOW ) ;
for ( ; ; ) ;
Like I said, it does not enter the bootloader, and it does not echo anything back after the reset, it just keeps going with the original code. Thanks for the help!
We have the same problem. Does someone knows how to solve it?
Hi
Try with this new code: https://www.sistemasorp.es/blog/programar_arduino_por_wifi.zip
Regards.
OMG I HAVE IT WORKING ON MY UNO AND MEGA it took a few days but omg i just flashed the Blink sketch to it 🙂 oh happy days,
https://lh5.googleusercontent.com/k4IbWjQk8LpKXx6EPMdNOUgi7qYmade7e22k5a_CipBzBuGTWXXi8mO3VzjEbtBJ7hKkH0UVZDOik4v8IF44=w2560-h1260-rw
just remove the if wait for \x00 and call program_process right after clie.send(«ok»).
and note the python code is created to run on python 2.7, you also need the program, socket test and set that to 4900, and have the python app running , running it in idle is fine.
im forgetting something else i think but just pay around with the code.
Congratulations. I’m glad to see it works.
I have tried several times with mega and uno but the connection to Arduino bootloader is always failed. I am unable to resolve the problems.Can u help please.
Thanks for advance
https://www.dropbox.com/s/09d08cfshpestdd/immagine%20prova%20wifi.jpg?dl=0
look this photo is described the problem
thanks and sorry
Hi
Try with this new code: https://www.sistemasorp.es/blog/programar_arduino_por_wifi.zip
Regards.
Una preguntilla, porque en el codigo de arduino pones a nivel alto y bajo el pin 13? No veo que este conectado a ningun sitio ni que sirva para nada. Tambien veo que lo has declarado como OUTPUT.
Gracias.
Como el pin 13 está asociado al led que incorpora Arduino, lo utilizo para saber que está encendido y esperando ordenes y cuando lo apago es porque ha habido algún fallo.
Gracias, si, ya me di cuenta, ahora lo que me pasa es que no consigo que entre en modo programacion, el script the python me dice que no se puedo subir el bootloader.
Sin embago el arduino si que e resetea, pero vuelve a empezar desde el principio, no entra a ningun modo de programacion. Alguna idea?
Gracias!
En vez de con un Arduino nano he probado con el UNO y funciona todo correctamente hasta el momenot de poner el pin 12 a LOW, el arduino se reinicia y no entra el modo de programacion. En el script pone Read hex file… y a continuacion se pone a esccuchar para volver a conectarse al arduino puesto que este se ha desconectado.
No aparece nada de BOOTLOADER en el script. He probado a 57600bps y a 115200. Creo que el problema esta en como acceder al modo de programacion del arduino. Si declaro el pin que va al reset com OUTPUT y lo pongo a low se reinicia siempre.
Alguna idea? Muchas gracias!
Tengo que investigarlo, porque a más gente le está pasando. La clave es que se envía un 0 por el puerto serie cuando el arduino UNO se reinicia y este es el que toma el script en python para saber que se ha reiniciado y enviarle los datos del nuevo código.
He estado haciendo pruebas y he añadido modificaciones, a ver si te funcionan: https://www.sistemasorp.es/blog/programar_arduino_por_wifi.zip . Ahora puedes poner en los defines del sketch de Arduino la velocidad que funciona el puerto serie en condiciones normales (configurando el esp8266 en consecuencia) y la velocidad del puerto serie que utiliza el bootloader del arduino (configurando el esp8266 en consecuencia). En el script del servidor he cambiado el modo de saber cuando está el arduino en modo bootloader simplemente haciendo que envíe la secuencia de inicio hasta 5 veces antes de abortar. Si lo pruebas te agradecería que pusieras aquí tu experiencia para que pueda servir a otros. Gracias.
Hi,
not having a LCD screen handy, I made the following changes:
first I included SoftwareSerial:
#include
SoftwareSerial esp8266(A1,A0);
Then I replaced the Serial-calls by esp8266-calls and the lcd-calls by Serial calls, so that the output was displayed in the serial window of the Arduino IDE.
Speed of the esp8266 put to 9600. I have the python program without changes running as root.
The first AT commands are executed, and it connects to the SSID but the program stops at
AT+CIPSTART=»TCP»,»192.168.178.15″,49000
0,CONNECT Failed
and now I am stuck…
Paai
I see that the python program listens on port 50000, but I have no idea which program listens on port 49000.
It would really help if you would write down the steps to take and the programs to start. Spanish would not be a problem, thanks to Google translate.
Normally I would use a esp8266 based board, which has a reliable OTA system. But the esp8266 does only have seven or eight IO pins and I need more.
Another thing, if you want people to really understand what is happening, a video is perhaps the worst vehicle to transmit such concepts. I really get frustrated that all kinds of information that can be read in three minutes, are hidden in hour long videos.
Hi
For the example there are two servers running on the PC:
-The first is serversocket and it is listening on port 49000. It only receives the hello guys! string sent from arduino+esp8266. From this program you can send the text reboot, this command makes the arduino to:
–connect to port 50000 and send the hello string.
–to wait for the welcome response
–to send the name of the .hex file to program
–to wait for the ok string
–to perform a reset to execute the bootloader.
-The second is the python script and it is listening on port 50000:
–it waits for the hello string
–it sends welcome
–it waits for the name of the .hex file
–it sends the ok string
–it waits for the byte 0 to begin the programming (however most people told me that the 0 byte is not received, so you can avoid it)
I hope this is useful for you, I would like to read your success so don’t hesitate to ask more questions.
The video is only a demo for what is written in the article. In the next months I’m evaluating to translate it to english to widespread the content. By the moment google translate as you mentioned, or better, deepl cant help you with the translation.
Thank you for your kind words. Not everybody has the time or patience to keep answering questions. If I can help you with translations or tutorials I will gladly do so.
At the moment I tamed the esp8266 (always a temperamental component), using the SoftwareSerial library and got the UNO to attach to the socketserver, can type the ‘reboot’ and it will reboot. However, the UNO reboots in the main program, not the loader. Yes, in the Arduino IDE, I have the Atmel stk500 enabled and the new hex file is in the directory with the Python program. Immediatly before the reboot, I send a 0x00 byte to the esp8266, but that does not seem to make a difference.
As I said, my debug output goes to the Serial console and the esp8266 is connected to Softwareserial on pin 7 and 8, not to the hardware RX and TX. Would that be an issue?
My project is to make a Dirt Devil robot somewhat smarter: see http://www.paijmans.net/Dirtdevil/dirtdevil.html
The WEMOS offers easy OTA updates and lots of memory, but not enough IO pins. The UNO/ESP8266 combo on the other hand has lots of pins, but no OTA and that is where you come in.
Hi,
I followed your steps and codes for both Arduino and python but the bootloader didn’t work.
I tried several times but in vain.The execution of the program access in python is failed.
I tried also your new code but it’s the same.
Please can you help me cause i need it to work.I am doing an internship actually and i am using your method to program a robot wirelessly.
Thanks
best regards
I’m facing a problem with Bootloader.My python code failed to connect with the arduino bootloader and i get a failed message when I run the python script.
Please help me
Buenos días! buscando y buscando he llegado hasta aquí, me ha parecido muy interesante el artículo!!
Quería aclarar una duda, corrígeme si me equivoco, pero me parece que estoy en lo cierto por lo que he leído… Si se hace un programa que envíe/reciba los parámetros por socket a cualquier puerto (el que hallamos configurado en arduino) podría prescindir de Python, ya que lo único que hace es ser el intermediario para enviar al puerto, cierto? es decir, con esto se puede hacer un bootloader de arduino por wifi para windows evitando el python, no?
Muchas gracias y repito muy interesante!
Hola.
El propio programa de python es el encargado de enviar el firmware al bootloader de arduino siguiendo las directrices el protocolo stk500, no es un mero intermediario, interpreta el fichero .hex y lo traduce en comandos del protocolo stk500.
Buenísimo el tutorial, muchas gracias por compartirlo!
Un par de comentarios:
– Luego del comando AT+RST el arduino no detecta el ready, y tira error de timeout. Saqué ese comando del if, dejé que se ejecute pero sin hacer que finalice ahi el programa al tirar timeout.
– Intenté pasar el programa en python a python 3 pero luego de inociar el bootloader tuve problemas en la detección de \x14\x10. El error que me daba era que no podia identificar el primer y ultimo byte antes de esos 2. Luego de varias horas abandoné la idea y lo usé con oython 2.7, pero quería saber si lo has mudado a python 3.
– Utilicé una arduino nano, en un sketch bastante grande y no tuve ningún problema.
Hola muy buen tutorial
Estuve luchando una semana con esto pero al fin logre ponerlo en marcha en un Arduino uno.
Me gustaría saber si es posible hacer esto mismo en placas ARM equipadas con microcontrolador SAMD51