/*USB-CDC BOOTLOADER PIC_18F4550 by Remi04*/
// Datos de la memoria del PIC 18F4550
/* Flash = 32768 bytes
* Write_size holder = 32 bytes
* erase_size holder = 64 bytes
*/
// Requerimientos de espacio para el bootloader:
// USANDO USB:
// Requerimiento de espacio para el bootloader: 5.872 bytes (Sin uso de led indicador)
// 5.992 bytes (con uso de led indicador)
// En ambos casos el espacio reservado es 6016 bytes (Multiplo de 64 bytes mas cercano del valor del espacio requerido) para que la funcion "write_program_memory()" trabaje adecuadamente.
// USANDO USART:
// Requerimiento de espacio para el bootloader: 1.216 bytes (Sin uso de led indicador)
// 1.336 bytes (con uso de led indicador)
// En ambos casos el espacio reservado es 1344 bytes (Multiplo de 64 bytes mas cercano del valor del espacio requerido) para que la funcion "write_program_memory()" trabaje adecuadamente.
// Las opciones se configuran mediante macros mas abajo.
/* FUNCIONAMIENTO
*
* Este programa utiliza un led bicolor con cátodo a común cuyos ánodos son conectado a los puertos D6 (Led rojo, y D7 led verde) con su correspondiente resistencia de limitación.
* Si no se quiere utilizar el led, comentar la macro al efecto mas abajo para ahorrar espacio de flash ocupada por el bootloader.
* El pin de VCC del conector USB es conectado al pin D5 y en este caso no provee alimentación al pic. Solo se usa para detectar si hay o no conexión fisica usb con el equipo host.
* El led es usado para indicar el funcionamiento y progreso del bootloader.
*
*/
/* COMO CARGAR UN FIRMWARE
*
* Para cargar un fichero *hex utilizar un terminal exterior rs232, como por ejemplo, Coolterm, Realterm, etc.
* -Conectar la placa pic al ordenador por usb o por usart segun se elija la compilación. (El usb no debería alimentar al pic).
* -Dar alimentación a la placa pic. El led encenderá en rojo.
* -Si la compilación es para USB El programa quedará en espera de enumeración por parte del ordenador. Una vez que el pic haya sido enumerado el led parpadeará 3 veces y quedará encendido en verde permanente.
* -A partir de ahora el envío del firmware es posible.
* - Si la compilación es para USART se usa un pulsador para llamar al bootloader. Pulsarlo antes de dar alimentacion y mantenerlo pulsado 2 segundos.
*
* - Iniciar el software terminal elegido y configurar el puerto en la velocidad de baudios elegida en la compilación, 8N1 con la opción de control de flujo XON habilitada.
* - Establecer la comunicaión con el botón "conectar" correspondiente.
* - Elegir el fichero a enviar.
* - El fichero se enviará al pic. El led irá emitiendo parpadeos por cada linea hex que haya sido correctamente recibida, decodificada y grabada.
*
* - Concluida la carga del firmware el bootloader saltará automáticamente a iniciarla.
*/
/* INDICACIONES DE ERROR.
* Led parpadeando en color ambar (rojo + verde simultaneos) = Error de Checksum. Desconectar todo y repetir el proceso.
* Led parpadeando alternativamente entre rojo y verde) = Error por dirección de memoria fuera del rango permitido.
*/
#include <18F4550.h> //18F4550
#fuses HSPLL,NOWDT,NOPROTECT,NOLVP,NODEBUG,USBDIV,PLL5,CPUDIV1,VREGEN,CCP2B3
#use delay(clock=48000000)
// Macros de configuración de compilación.
//#define USE_LED // Comentar si no se quiere utilizar el led.
//#define USE_USB // Si no se quiere utilizar el modulo USB, el bootloader funcionará en modo RS232 con el modulo USART utilizando los pines TX y RX del pic.
// Se habilita un pulsador para llamar al bootloader en esta modalidad.
#define BAUDRATE 115200 // Seleccion de velocidad del puerto usb o usart.
// Fin macros de opciones y configuración.
#include <string.h>
#include <stdlib.h>
#ifdef USE_USB
#include <usb_cdc.h>
#define USB_CON_SENSE_PIN PIN_D5 // El Pin D5 recibe los 5v procedentes del puerto usb del ordenador. Este voltaje se utiliza para determinar si hay o no conexión fisica con el ordenador.
#define usb_stat input(PIN_D5) // Vdd 5v procedentes del cable usb exterior. Ese vdd se utiliza como señal para detectar que se ha conectado el cable
#bit usben=0xf6d.3 // Bit USBEN, habilita o desactiva el modulo USB del pic.
#endif
#ifndef USE_USB
#bit push=0xf83.5 // PIN_D5 pulsador para llamar al bootloader.
#endif
#ifdef USE_LED
#bit usbled_rojo= 0xf83.6 // PIN_D6 Led rojo de USB (bicolor).
#bit usbled_verde=0xf83.7 // PIN_D7 Led verde de USB (bicolor))
#endif
#ifndef USE_USB
#use rs232(baud=BAUDRATE,parity=N,xmit=PIN_C6,rcv=PIN_C7,bits=8)
#endif
#ifdef USE_USB
#define START_PROGRAM 0x1780 // Primera posicion autorizada para la escritura del programa.
#else
#define START_PROGRAM 0x540
#endif
#org START_PROGRAM + 4,0x7ffe {} // a partir de la posicion START_PROGRAM hasta el final de la flash es reservado para el programa a cargar
// pero permitimos que el bootloader ocupe hasta 4 words más para colocar el vector de arranque el cual será luego sustituido por
// el firmware a cargar.
char linea[43] = {0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff}; // Array para contener los caracteres ascii que representan una linea hex a su tamaño máximo.
int16 h_addr = 0; // Address 16 bytes MSB para la flash.
int16 l_addr = 0; // Address 16 bits LSB para flash.
int32 address = 0; // Address 32 bits final para la flash.
void get_line(); // Detecta y recibe una linea hex completa.
void decode(); // decodifica cada linea y si es procedente, graba su contenido en el buffer.
unsigned int atoi_b16(char *s); // Recibe dos caracteres Ascii que representan el literal de un numero hexadecimal de 8 bits y devuelve su valor entero equivalente.
#org START_PROGRAM + 1,START_PROGRAM + 2 // vector de arranque de la aplicación a cargar. Esto será sobreescrito por el "goto" inicial de la app cargada
void main_program (void) {
while(1);
} // fin vector de arranque
void main() {
set_tris_a(0b11111111); // Configuración de E/S de todos los puertos. Configurar según necesidades.
set_tris_b(0b11010011);
set_tris_c(0b10001111);
set_tris_d(0b00110111);
set_tris_e(0b101);
delay_ms(200); // Estabilizar fuente de energia y sistemas.
#ifdef USE_LED
usbled_rojo = 1; // Estado inicial de los led´s. Bootloader inicia con el led rojo encendido.
usbled_verde = 0;
#endif
#ifdef USE_USB
usb_init(); // Inicializacion del modulo USB
usb_cdc_init(); //configuración de parámetros de comunicación serial generada por el USB(baudios, bit paridad etc)
usb_cdc_line_coding.dwDTERrate = BAUDRATE; // configuro la velocidad rs232 emulado por el USB de la cpu a 115.200 bps.
if (!usb_stat) main_program(); // Si no hay cable usb conectado en el inicio saltamos a la aplicación.
delay_ms(500); // Estabiliza inicializado del USB.
while(!usb_enumerated()); // Esperando enumeración por parte de Windows.
#else
if (push) main_program(); // Si no hemos pulsado, saltamos a la aplicación.
#endif
#ifdef USE_LED
for (int i=0;i<3;i++) {
usbled_verde = 0; // indicamos enumerado correcto. (Tres cambios de rojo a verde del led, y lo dejamos con el verde encendido).
usbled_rojo = 1;
delay_ms(300);
usbled_rojo = 0;
usbled_verde = 1;
delay_ms(300);
}
#endif
while(1) get_line(); // Vamos a la función para cargar el programa.
}
void get_line() {
int index = 0; // indice para el array linea.
#ifdef USE_USB
while (usb_cdc_getc() != ':') // Mientras no recibamos un inicio de linea (:) esperaremos en este bucle.
while(!usb_cdc_kbhit()) if (!usb_stat) main_program(); // Esperamos primer caracter desde el software externo. Si desconectamos el cable usb salimos del bootloader y saltamos a la aplicación.
// Una vez recibido el caracter ":" (inicio de linea) salimos del bucle y comenzamos a parsearla.
// Parseando linea hex del fichero hexadecimal.
while(linea[index] != 0x0D) // Recibiremos los caracter en el array hasta que se recibe un ENDLINE (0x0d).
linea[index++] = usb_cdc_getc(); // Cargamos el caracter recibido en orden en el array "linea".
// Una vez recibido el endline:
usb_cdc_putc(19); // XOFF // Enviamos XOFF al software exterior para que detenga la transferencia de datos mientras procesamos la linea.
#else
while(!kbhit());
while(linea[index] != 0x0D) // Recibiremos los caracter en el array hasta que se recibe un ENDLINE (0x0d).
#endif
decode(); // Enviamos a decodificar la linea y grabarla en la flash si procede.
}
void decode() { // Decodificamos la linea y la grabamos.
int8 counter = 0; // contador numero de bytes de datos en la linea.
int8 linetype = 0; // Tipo de linea.
int8 data[32]; // Buffer de preparación de los datos para enviar a la flash.
int8 i = 0; // control indices iteracciones varias.
int8 dataidx = 0; // indice paso de datos entre linea --> buffer data.
int16 timeout = 0; // para detectar conclusión del envío de todos los caracteres restantes en coolterm u otras al terminar la descarga de lineas.
counter = atoi_b16(&linea[0]); // atoi_b16 tomará dos caracteres ascii que representan un numero hexadecimal y los convertirá en un entero de 8 bits.
l_addr = make16(atoi_b16(&linea[2]),atoi_b16(&linea[4])); // cargamos en address la dirección (16 bits bajos).
linetype = atoi_b16(&linea[6]); // cargamos el tipo de linea.
address += l _addr; // pasamos a address, su valor (por si tiene cargado algo en 32 bits) + el byte bajo)
int8 left = (counter * 2) + 9; // Counter contiene el numero de enteros de 8 bits que hay que grabar, son dos caracteres por entero, por eso multiplicamos su valor por dos.
//, le sumamos 9 por que los caracteres del principio de la linea ":",numero de bytes "00", address, "0000", tipo de linea "00", son 9 caracteres
// Aqui lo usamos para saber el numero de bytes que hay que meter en checksum, mas abajo también es utilizado por el buffer Data
// ahora Left contiene la posición del primer caracter de checksum.
// Comprobación de errores en la linea recibida
int checksum = 0; // valor inicial a checksum.
for (i=0; i<left; i+=2) // el checksum se verifica con los caracteres desde posicion 0 de la linea, hasta el final excepto los dos ultimos valores (que son el propio checksum). tampoco se tiene en cuenta el endline, es decir, el LF ni el CR que terminan cada línea.
checksum += atoi_b16 (&linea[i]); // concatenamos y sumamos todos los valores arriba indicados.
checksum = 0xff - checksum + 1; // restamos a 255 el valor que nos da la concatenación y le sumamos 1. El resultado ha de coincidir con el valor del entero
// que contienen los dos ultimos caracteres de la linea.
if (checksum != atoi_b16 (&linea[left])){ // comprobamos checksum, si no es correcto, se detiene la carga del firmware y permanecemos en bucle cerrado
while(1) { // indicando el error mediante el led.
#ifdef USE_LED
usbled_rojo = 1;
usbled_verde = 1;
delay_ms(800);
usbled_rojo = 0;
usbled_verde = 0;
delay_ms(200);
#endif
}
}
// Si checksum es correcto, procedemos a decodificar la linea:
if (linetype == 1) { // Linetype = 1 significa "Envio del programa concluido".
#ifdef USE_USB
usb_cdc_putc(6); // ACK // Acuse de recibo al software externo.
usb_cdc_putc(17); // XON // Ordena al software externo que reanude la transferencia de datos.
#else
putc(6); // ACK // Acuse de recibo al software externo. putc(17); // XON // Ordena al software externo que reanude la transferencia de datos. #endif
// Una vez concluida la carga del programa seguiremos aqui recibiendo caracteres procedentes del software que envía el fichero *.hex al bootloader
// (coolterm u otros) ya que el pie del fichero *.hex suele contener metadatos como fecha de compilación y algunos mas que
// no sirven aqui, pero que si no los recibimos, el software no puede concluir el envío y se nos puede quedar "pillado" con la barra de progreso
// en el 99% o incluso en el 100% esperando que atendamos esos caracteres.
#ifdef USE_USB
while(1) { // Seguimos entonces recibiendo todo lo que nos envíe el software externo hasta que ya no envíe nada. Eso lo detectaremos mediante una espera
while(!usb_cdc_kbhit()) {
timeout++;
delay_ms(1);
if (timeout > 1000) { // Si tras 1000 ms no se ha recibido nada, entendemos que el sotware externo a concluido.
delay_ms(1000);
usben = 0; // Detiene el modulo USB.
main_program(); // Enviamos a ejecutar el programa principal.
}
}
char c = usb_cdc_getc();
if (c == 0x0d) usb_cdc_putc(6); // si recibimos endline enviamos ack para que la app siga enviando lineas.
timeout = 0;
}
#else
while(1) { // Seguimos entonces recibiendo todo lo que nos envíe el software externo hasta que ya no envíe nada. Eso lo detectaremos mediante una espera
while(!kbhit()) {
timeout++;
delay_ms(1);
if (timeout > 1000) { // Si tras 1000 ms no se ha recibido nada, entendemos que el sotware externo a concluido.
delay_ms(1000);
main_program(); // Enviamos a ejecutar el programa principal.
}
}
if (c
== 0x0d) putc(6); // si recibimos endline enviamos ack para que la app siga enviando lineas. timeout = 0;
}
#endif
}
else if (linetype == 4) { // si el tipo de linea es "4" es por que tenemos que usar una dirección de más de 16 bits de datos, obtenemos de esta linea los otros 16 bits, y saltamos al final del bucle para pedir la siguiente linea.
h_addr = make16(atoi_b16(&linea[9]), atoi_b16(&linea[11])); // ya tenemos en h_addr los otros 16 bits.
address = make32(h_addr,l_addr);
#ifdef USE_USB
usb_cdc_putc(6); // ACK // Acuse de recibo al software externo.
usb_cdc_putc(17); // XON // Ordena al software externo que reanude la transferencia de datos.
#else
putc(6); // ACK // Acuse de recibo al software externo. putc(17); // XON // Ordena al software externo que reanude la transferencia de datos. #endif
}
else if (linetype == 0) { // si el tipo de linea es "0", se trata de una linea relevante de datos para la flash.
if (((address >= START_PROGRAM) && (address < 0x7fff)) || (address == 0x300000) || (address == 0xf00000)) { // Solo si la posicion de memoria está comprendida entre la zona reservada grabamos la flash
// Si está fuera de rango pero es 0x300000 o 0xf00000 si que grabamos, pues son los fuses y los datos de la eeprom interna
#ifdef USE_LED
usbled_verde = 1; // Daremos un parpadeo del led cada vez que una linea haya sido grabada en la flash.
#endif
for (i = 9,dataidx = 0; i < left; i += 2) data[dataidx++]=atoi_b16(&linea[i]); // a partir del caracter 9 de una linea, hasta la posicion "left" calculada segun "count" (numero de bytes)
// cargamos en el buffer DATA los enteros ya convertidos de los caracteres. Aunque no está lleno, data es un array de 32 bytes
// debido a que la función write_program_memory en el caso de este pic, trabaja por bloques de 32 bytes
write_program_memory(address, data, counter); // Grabamos en la flash el bloque completo de 32 bytes.
#ifdef USE_LED
usbled_verde = 0; // Función Parpadeo led.
#endif
address = 0; // dejamos limpio Address para evitar acumulacion no deseada al principio de esta función.
#ifdef USE_USB
usb_cdc_putc(6); // ACK // Acuse de recibo al software externo.
usb_cdc_putc(17); // XON // Ordena al software externo que reanude la transferencia de datos.
#else
putc(6); // ACK // Acuse de recibo al software externo. putc(17); // XON // Ordena al software externo que reanude la transferencia de datos. #endif
} // notese que hemos elegido que el programa ocupa a partir de la dirección START_PROGRAM. Esa dirección es el multiplo de 64 "flash_erase_size" mas cercano.
// que tengo por encima de lo minimo que ocupa este bootloader + una reserva de espacio por si el dia de mañana tuviese que actualizar el propio bootloader.
// desde el firmware.
// de modo que esta función trabajará de forma mas optima por que borrará todo el bloque de 64 bytes en cada operación multiplo,
// y eso garantiza que no queden datos residuales de anteriores versiones del programa cargado.
// Si esto no se cumple, sería prudente efectuar un borrado completo de la zona reservada en flash antes de cargar un programa.
else { // Si ADDRESS está fuera de rango, generamos indicación de error y detenemos la carga de firmware.
while(1) { // Led rojos parpadeando alternativamente entre ellos indicando error por address fuera de rango autorizado.
#ifdef USE_LED
usbled_verde = 0; // Led parpadea alternativamente entre verde y rojo.
usbled_rojo = 1;
delay_ms(500);
usbled_verde = 1;
usbled_rojo = 0;
delay_ms(500);
#endif
}
}
}
}
unsigned int atoi_b16(char *s) { // obtiene un numero entero en hexadecimal de 8 bits a partir de dos caracter headecimales.-
unsigned int result = 0; // A esta funcion se le pasa como argumento la direcion de memoria donde está el primer caracter mediante &linea[8] por ejemplo
int i;
for (i=0; i<2; i++,s++) {
if (*s >= 'A') // si el valor que hay en la posicion de memoria apuntada por "s" es igual o superior a 65 (A = 65)
result = 16*result + (*s) - 'A' + 10; // calculamos.
else
result = 16*result + (*s) - '0'; // calculamos.
} // siguiente iteracción; incrementa s++, es decir, leerá el valor de la siguiente posicion de memoria y repetirá el proceso.
return(result);
}