My own AVR ISP programmer using PIC16f877a and python!

Introduction: 

(don't skip to read the note below)
I recently purchased few AVR microcontrollers. I don't know much about AVR since I am using it for first time. Any way, I have some experience on working with PIC and MSP430. To program AVR using USB, I came to know that USBASP is the best and cheap choice. But any way, I thought of making my own ISP programmer (both PC software and burning hardware) for AVR using my fav PIC (PIC16f877A) microcontroller just for getting familiarised with the process of loading the binary into the AVR flash.
      From PC side, coding is done on python. In short words, the python script reads the hex file (intel hex format) and make some ascii conversion on it and then send it to the PIC16F877A line by line. Now by using some commands for isp programming, the PIC communicate with AVR and transfer the data to it via SPI. I have tested my programmer on atmega8, atmega16 and atmega32.

Note:
 I did this just for learning some thing about the burning process, please don't consider this seriously because this may have many bugs , I don't know and I didn't tested much.. Also I am not interested to continue this because it will be time wasting as there are already many cheap and efficient avr programmers available now. So please don't complain about bugs and don't use this for any important purpose ;-)


 Intel hex format:

Intel hex format is well explained in wikipedia. I learned about it from there. Here is the link towards the wikipedia page. An intel hex file contains the binary information about the flash address and data. It is a text file format. Each line of intel hex file consist of 6 parts. See below picture.







explanation is there on wikipedia. But I think the above two picture itself explains it...:-)

Python code for PC:

#!/usr/bin/python
import serial,time,sys,os

SERIALPORT = "/dev/ttyUSB0"
HEX = "a.hex"
BAUD = 115200
IF_FUSE_LOW = 0
IF_FUSE_HIGH = 0

if not sys.argv[1:]:
    print "usage: ./avrisp.py [hex file] [fuse bits low] [fuse bits high]"
    print "example: ./avrisp.py led.hex 225 133"
    print "note: use fuse bits only if it is necessary"
if sys.argv[1:]:
    HEX = sys.argv[1]
if sys.argv[2:]:
    FUSE_LOW = sys.argv[2]
    IF_FUSE_LOW = 1
if sys.argv[3:]:
    FUSE_HIGH = sys.argv[3]
    IF_FUSE_HIGH = 1

ser = serial.Serial(SERIALPORT,BAUD)
try:
    ser.open()
    ser.setDTR(1)
except Exception,e:
    print "error open serial port: " + str(e)
    exit()
if ser.isOpen():
    try:
        ser.flushInput()
        ser.flushOutput()
        f = open(HEX)
        hex = f.read()
        list = hex.split("\r\n")
        list = list[:-1]
        ser.write(chr(4));time.sleep(.01)
        ser.write('0');time.sleep(.01)
        ser.write('0');time.sleep(.01)
        ser.write('0');time.sleep(.01)
        ser.write('B');time.sleep(.01)
        ser.write('U');time.sleep(.01)
        ser.write('R');time.sleep(.01)
        ser.write('N');time.sleep(.01)
        time.sleep(.1)
        for line in list:
            line = line[1:-2]
            l = len(line)
            count = 0;
            while count < l:
                value = int(line[count] + line[count + 1],16)
                ser.write(chr(value))
                time.sleep(.0008)
                count = count + 2
            print line
            time.sleep(.0005)
        time.sleep(.1)
        if IF_FUSE_LOW == 1:
            ser.write(chr(1));time.sleep(.01)
            ser.write('Y');time.sleep(.01)
            ser.write('E');time.sleep(.01)
            ser.write('S');time.sleep(.01)
            ser.write(chr(int(FUSE_LOW)));time.sleep(.01)
        else:
            ser.write(chr(1));time.sleep(.01)
            ser.write('N');time.sleep(.01)
            ser.write('O');time.sleep(.01)
            ser.write('O');time.sleep(.01)
            ser.write('O');time.sleep(.01)
            time.sleep(.2)
        if IF_FUSE_HIGH == 1:
            ser.write(chr(1));time.sleep(.01)
            ser.write('Y');time.sleep(.01)
            ser.write('E');time.sleep(.01)
            ser.write('S');time.sleep(.01)
            ser.write(chr(int(FUSE_HIGH)));time.sleep(.01)
        else:
            ser.write(chr(1));time.sleep(.01)
            ser.write('N');time.sleep(.01)
            ser.write('O');time.sleep(.01)
            ser.write('O');time.sleep(.01)
            ser.write('O');time.sleep(.01)
        ser.close()
        print "BURNING IS COMPLETED"
    except Exception, e1:
        print "error communicating...: " + str(e1)
else:
    print "cannot open serial port "
Actually the hex file is in txt format. So, for example, a byte 0xff is represented as string "FF" in text file. So it takes two bytes in the text file. But I need to send the ascii ie 0xff to PIC. So I need to convert the string "FF" to byte 0xff. For that, I have used
value = int(line[count] + int[count + 1],16) 
Now in value, there will be the ascii of string 'FF' ie now value = 255 or 0xff. I just ignored the ":" and checksum and send  (via UART) the remaining part each line after converting two adjacent bytes in text to corresponding ascii byte. PIC collects the received byte until it receives a complete line of hex. Now the PIC process the received data which will be explained later. Similarly all of the bytes in hex are send to the PIC. After sending the hex file, Next is the fuse bits. We can add the fuse bits as command line argument (optional), then it checks for the existence of sys.argv[2] and sys.arg[3]. Pyserial (a python module) is used for accessing the serial port of PC. In my case, I am using USB to UART converter. So my port is /dev/ttyUSB0. Also baud rate I am using is 115200. If file name is not specified in command line argument, it uses "a.hex" as default value. Also, if no fuse bits are provided in command line argument, it doesn't modify the default fuse bits...........

  Note: 
    At first time, when I tested my programmer with random fuse bits, I got in trouble :( . The AVR clock source is changed to external clock. Then I couldn't use the ISP programmer since the AVR lacks a clock source. Later , I am forced to provide an external clock source. I used an MSP430 launchpad for providing clock to AVR. (or we can use any multi-vibrator or external frequency generator). So, it is not recommended to alter the fuse bits unless it is necessary.


PIC16F877A to burn AVR (isp):
code (for Hi Tech C compiler, MPLAB) 
#include<pic.h>
#define _XTAL_FREQ 20e6
#define RESET RC2
#define MOSI RC5
#define MISO RC4
#define SCK RC3
unsigned char x,y,z, buff[4], buffer[50];
int count;
char flag;
void usrt_init()
{
    GIE = 1;
    PEIE = 1;
    RCIE = 1;
    TRISC6=0;
    TXSTA=0b00100110;
    RCSTA=0b11010000;
    SPBRG=10;
}
void printf(const char *p)
{
    while(*p){
        TXREG=*p;
        while(TRMT==0);
        p++;
    }
}
void txd(unsigned char vv)
{
    TXREG=vv;
    while(TRMT==0);
}
void spi_init()
{
    TRISC4=1;
    RC3=0;RC5=0;
    TRISC2=TRISC3=TRISC5=0;
}
static unsigned char spi_write(unsigned char ibuf)
{
    unsigned char obuf;
    int i;
    for (obuf = 0, i = 7; i >= 0; --i)
    {
        MOSI = (ibuf >> i) & 1;
        SCK = 1;
        obuf |= (MISO << i);
        SCK = 0;
    }
    return obuf;
}
void isp(char a, char b, char c, char d)
{
    buff[0] = spi_write(a);
    buff[1] = spi_write(b);
    buff[2] = spi_write(c);
    buff[3] = spi_write(d);
}
int isp_start(void)
{
    unsigned int n;
    for (n = 0; n < 5; ++n)
    {
        RESET = 0;
        __delay_ms(25);
        isp(0xac, 0x53, 0, 0);
        if (buff[2] == 0x53)
        return 1;
        RESET = 1;
    }
    return 0;
}
void isp_close()
{
    RESET = 1;
}
unsigned char ispReadFlash(unsigned int address) {
    spi_write(0x20 | ((address & 1) << 3));
    spi_write(address >> 9);
    spi_write(address >> 1);
    return spi_write(0);
}
unsigned char ispWriteFlash(unsigned int address, unsigned char data) {
    spi_write(0x40 | ((address & 1) << 3));
    spi_write(address >> 9);
    spi_write(address >> 1);
    spi_write(data);
    return 0;
}
unsigned char ispFlushPage(unsigned int address) {
    spi_write(0x4C);
    spi_write(address >> 9);
    spi_write(address >> 1);
    spi_write(0);
    __delay_ms(4.8);
    return 0;
}
isp_erase()
{
    isp(0xac,0x80,0,0);
    __delay_ms(10);
    int i=4;
    while(i) {
        i--;
        RESET = 1;
        __delay_ms(5);
        RESET = 0;
        __delay_ms(5);
    }
    isp_start();
}
isp_burn_AVR()
{
    unsigned int mult = 1, checkpoint = 64*1 - 1;
    unsigned int address, length;
    char exit = 0;
    while(1)
    {
        if(flag == 1) {
            flag = 0;
            unsigned int temp_address;
            if(length = buffer[0]) {
                address = buffer[1];
                address <<= 8;
                address |= buffer[2];
                temp_address = address;
                char k = 4;
                while(length) {
                    ispWriteFlash(address, buffer[k]);
                    address++;
                    k++;
                    length--;
                }
                address = temp_address + 15;
            }
            else {
                exit = 1;
                ispFlushPage(64*(mult - 1));
            }
            if(address == checkpoint) {
                ispFlushPage(64*(mult-1));
                mult++;
                checkpoint = 64*mult -1;
            }
            address++;
        }
        if(exit){
            break;
        }
    }
}
void isp_write_fuse_low(char fuse_low)
{
    isp(0xac, 0xa0, 0, fuse_low);
    __delay_ms(10);
}
void isp_write_fuse_high(char fuse_high)
{
    isp(0xac, 0xa8, 0, fuse_high);
    __delay_ms(10);
}
void interrupt UART()
{
    x = RCREG;
    static char len = 16;
    if(count == 0){
        len = x;
        len+=3;
    }
    buffer[count] = x;
    if(count == len){
        count = 0;
        flag = 1;
    }
    else {
        count++;
    }
}
void main(void)
{
    TRISD=0;
    usrt_init();
    spi_init();
    while(1) {
        while(!flag);
        if(buffer[4] == 'B' && buffer[5] == 'U' && buffer[6] == 'R' && buffer[7] == 'N') {
            flag = 0;
            if(!isp_start())
            while(1);
            isp_erase();
            isp_burn_AVR();
            while(!flag);
            if(buffer[1] == 'Y' && buffer[2] == 'E' && buffer[3] == 'S' && (buffer[4]&0xf) != 0)
            isp_write_fuse_low(buffer[4]);
            while(!flag);
            if(buffer[1] == 'Y' && buffer[2] == 'E' && buffer[3] == 'S')
            isp_write_fuse_high(buffer[4]);
            RESET = 1;
        }
    }
}
At  first the PIC waits for a string "BURN" to be received via UART which tells the PIC that the python script in PC is trying to burn a code into the AVR, and thus PIC enter to the programming mode. In programming mode, the RESET PIN on AVR is made LOW by the RC2 pin of PIC. It then erases the AVR using command set {0xac, 0x80, 0, 0}. After erasing command set is send, then few +ve pulses are applied on reset pin. Now the PIC waits for the line buffer to get filled with the first line of hex code.Then it checks for the number of data bytes and if it is non zero, it means there are some data to be loaded to AVR flash. The flash address is extracted from the buffer. Then using command 40/48 (even/odd address), it sends the data byte to AVR via SPI. For example, for address 0x0000, if data byte is 0x34, then we need to send it to AVR buffer by using command 40.
ie, isp(0x40, 0x00, 0x00, 0x34). Now for odd address it is command 48. So the function ispWriteFlash(address,data) will do the task. Similarly the remaining data bytes are also loaded to AVR buffer. Then the PIC waits for next hex line to be filled in the same buffer.  Then it do the same steps as above. When the address reaches a value equal to the multiple of 64, we need to give another command (0x4c) which really burns the buffered 64 bytes to flash. Now after programming the flash, it program the fuse bits (if provided in python command line argument). There are specific commands for all operation... When the programming is completed, the reset pin is made high (RC2 pin of PIC will do it). Thus AVR starts executing the new code burned into it and the PIC waits for next burn request from python script (ie the same "BURN" string).  Thats all about PIC burner...;-)
Screenshots and photos:

1 comment :

  1. Hi Vinod, Would like your advice or suggestions on how to interface a keyboard or digitizer with a TV? My mother is hearing impaired and sometimes its very convenient to type a message and show her on cellphone or laptop. I would really like to do this on a big screen. Its very easy to connect a laptop to a TV with vga/hdmi/svideo these days but I would like something permanent (cant keep the laptop connected to TV all the time). I have software background but havent worked much with microcontrollers so dont know which to use and what the best way to do this is? Any help would be appreaciated. Thanks

    ReplyDelete