Arduino Uploader (or any AVR chip with the bootloader)

Share your advanced PureBasic knowledge/code with the community.
User avatar
Joakim Christiansen
Addict
Addict
Posts: 2452
Joined: Wed Dec 22, 2004 4:12 pm
Location: Norway
Contact:

Arduino Uploader (or any AVR chip with the bootloader)

Post by Joakim Christiansen »

I have had this code for some time now and thought that maybe I should share it...
Basically it contains a procedure to load code from a HEX file and a procedure for communicating with Arduino's bootloader to upload the code after it uses the DTR pin to cause a restart of the board. The uploader was made by looking at the source code of the bootloader and implementing the minimum communication needed for a upload.

I have not cleaned it up much though (it should probably have better error checking and whatever), but people are free to change it for their own needs! It is mostly shared as an example.

For now I made it as a console application where you use it like this:
uploader.exe COMtoUse "filePath"
For example: uploader.exe COM7 "F:\yourHexFile.hex"

There has sometimes been a problem causing the restart but for me it mostly works, but maybe it can be improved.
If you try it please share your experience!

Code: Select all

EnableExplicit

Macro ub: a: EndMacro: Macro uw: u: EndMacro ;unsigned byte and word

Procedure WriteSerialPortDataDebug(COM,*buffer,length)
  Protected i, hex$
  For i=0 To length-1
    hex$ + RSet(Hex(PeekB(*buffer+i),#PB_Byte),2,"0")+" "
  Next
  Debug hex$
  ProcedureReturn WriteSerialPortData(COM,*buffer,length)
EndProcedure
Procedure SerialFlushIncoming(COM,bytes)
  Protected byte.b, *buffer
  *buffer = AllocateMemory(bytes)
  While AvailableSerialPortInput(COM) < bytes: Delay(5): Wend
  ReadSerialPortData(COM,*buffer,bytes)
  FreeMemory(*buffer)
EndProcedure
Procedure.l waitSerial(COM,bytesToWaitFor,timeout=1000)
  Protected time, timedOut
  time = ElapsedMilliseconds()
  While AvailableSerialPortInput(COM) < bytesToWaitFor
    If ElapsedMilliseconds()-time >= timeout
      timedOut = #True
      Break
    EndIf
  Wend
  ProcedureReturn timedOut ! 1
EndProcedure

Procedure loadHexFile(file$,*dataBuffer,maxLength)
  ;http://en.wikipedia.org/wiki/Intel_HEX
  ;http://www.scienceprog.com/shelling-the-intel-8-bit-hex-file-format/
  ;Alt.version: http://www.purebasic.fr/english/viewtopic.php?f=12&t=43157
  Protected file, line$, stringPos, i, dataByte.ub, extOffset, mPos
  Protected dataLength.ub, address.uw, recordType.ub, checksum.b, calculatedChecksum.b
  
  file = ReadFile(#PB_Any,file$)
  If file
    While Not Eof(file)
      line$ = ReadString(file,#PB_Ascii)
      stringPos = 1
      If Mid(line$,stringPos,1) = ":": stringPos+1
        dataLength = Val("$"+Mid(line$,stringPos,2)): stringPos+2
        address = Val("$"+Mid(line$,stringPos,4)): stringPos+4
        recordType = Val("$"+Mid(line$,stringPos,2)): stringPos+2
        checksum = Val("$"+Right(line$,2))
        calculatedChecksum = dataLength+PeekA(@address+1)+PeekA(@address)+recordType
        Select recordType
          Case $00;: Debug "data record"
            mPos = address + extOffset
            If mPos+dataLength <= maxLength
              For i=0 To dataLength-1
                dataByte = Val("$"+Mid(line$,stringPos,2)): stringPos+2
                PokeB(*dataBuffer+mPos,dataByte)
                calculatedChecksum + dataByte
                mPos+1
              Next
              If checksum <> ~calculatedChecksum+1
                PrintN("Hex parser: Warning! Checksum error...")
              EndIf
            Else
              PrintN("Hex parser: Buffer is full!")
            EndIf
          Case $01;: Debug "end of file record"
          Case $02: extOffset = Val("$"+Mid(line$,10,4)) << 4
          Case $03: extOffset = Val("$"+Mid(line$,10,4)) << 16
          Default: PrintN("Hex parser: Record not implemented: "+Hex(recordType,#PB_Byte))
        EndSelect
      Else
        PrintN("Hex parser: Invalid line: "+line$)
      EndIf
    Wend
  Else
    PrintN("Hex parser: Error reading file!")
    ProcedureReturn 0
  EndIf
  
  ProcedureReturn mPos ;is now the size
EndProcedure


Procedure uploadData(COM$,*address,dataLength,baud=57600)
  Define COM, byte.ub, memPos.uw, bytesToWrite.uw, chipMemPos.uw
  Dim bytes.ub(32)
  ;Dim bytesIn.ub(32)
  COM = OpenSerialPort(#PB_Any,COM$,baud,#PB_SerialPort_NoParity,8,1,#PB_SerialPort_NoHandshake,0,0)
  If COM
    SetSerialPortStatus(COM,#PB_SerialPort_DTR,#True) ;cause restart
    Delay(100)
    SetSerialPortStatus(COM,#PB_SerialPort_DTR,#False)
    Delay(100)
    WriteSerialPortString(COM,"0 ")
    If waitSerial(COM,2)
      SerialFlushIncoming(COM,2)
      PrintN("Flashing...")
      While memPos < dataLength-1
        bytes(0) = 'U' ;set address command
        chipMemPos = memPos/2 ;?!?!
        bytes(1) = PeekB(@chipMemPos)
        bytes(2) = PeekB(@chipMemPos+1)
        bytes(3) = ' ' ;end of command char
        If WriteSerialPortDataDebug(COM,@bytes(),4) <> 4: Debug "error1": End: EndIf
        SerialFlushIncoming(COM,2)
        If memPos+128 <= dataLength-1
          bytesToWrite = 128 ;the bootloader has a buffer of 256 bytes :)
        Else
          bytesToWrite = dataLength-memPos
        EndIf
        bytes(0) = 'd' ;write memory command
        bytes(1) = $00 ;since we will not write more than 256 bytes
        bytes(2) = PeekB(@bytesToWrite)
        bytes(3) = 'F' ;F for flash, E for epprom
        If WriteSerialPortDataDebug(COM,@bytes(),4) <> 4: Debug "error2": End: EndIf
        If WriteSerialPortDataDebug(COM,*address+memPos,bytesToWrite) <> bytesToWrite: Debug "error3": End: EndIf
        memPos + bytesToWrite
        bytes(0) = ' ' ;end of command char
        If WriteSerialPortDataDebug(COM,@bytes(),1) <> 1: Debug "error4": End: EndIf
        SerialFlushIncoming(COM,2)
      Wend
      WriteSerialPortString(COM,"Q ") ;restart command
      CloseSerialPort(COM)
      PrintN("Done! "+Str(dataLength)+" bytes written!")
    Else
      PrintN("Contact with bootloader failed!")
    EndIf
  Else
    PrintN("Error connecting to "+COM$+"!")
  EndIf
EndProcedure

Define file$, dataBufferSize=1000000, *dataBuffer = AllocateMemory(dataBufferSize), dataLength
Define file, COM$

OpenConsole()
PrintN("JLC's Arduino Hex Uploader v1.00 Beta 1")

; file$ = "project.hex": COM$ = "COM7"
; Debug loadHexFile(file$,*dataBuffer,dataBufferSize)
; Input()
; End

COM$  = ProgramParameter(0)
file$ = ProgramParameter(1)
PrintN("Uploading: "+file$)

If FileSize(file$) > 0
  dataLength = loadHexFile(file$,*dataBuffer,dataBufferSize)
  If dataLength
    PrintN("Bytes to upload: "+Str(dataLength))
    uploadData(COM$,*dataBuffer,dataLength)
  Else
    PrintN("Error parsing hex file...")
  EndIf
Else
  PrintN("Error, file not found!")
EndIf
I like logic, hence I dislike humans but love computers.
User avatar
kenmo
Addict
Addict
Posts: 2033
Joined: Tue Dec 23, 2003 3:54 am

Re: Arduino Uploader (or any AVR chip with the bootloader)

Post by kenmo »

Joakim,

Interesting code, thanks for sharing. I have finally bought myself an Arduino (Uno) after wanting to try them out for a few years. Seems to be one of the most user-friendly microcontroller kits around, and USB communication was a must-have. (Of course I will start by communicating with PureBasic!)

But I am wondering: what is this code useful for? Doesn't the Arduino IDE provide the same function? Was it just a learning and sharing experience? (Not being negative, I'm just not clear.)
jasper
New User
New User
Posts: 7
Joined: Sat Jan 07, 2012 1:41 am

Re: Arduino Uploader (or any AVR chip with the bootloader)

Post by jasper »

I can see where there might be reason for a binary loader, even though it might not have been the OPs original intention. Providing a customer (who has also been provided with a "loader") with a binary can be a measure of security to protect your source or prevent it being tampered with.

PB is a good way to interface to these kind of microprocessors/microcontrollers
User avatar
Joakim Christiansen
Addict
Addict
Posts: 2452
Joined: Wed Dec 22, 2004 4:12 pm
Location: Norway
Contact:

Re: Arduino Uploader (or any AVR chip with the bootloader)

Post by Joakim Christiansen »

Personally I wrote this code to be able to upload code written in assembly (which I compile with AVR Studio). And also because of the learning experience yes, I always enjoy a challenge and like having complete control. Actually I want to make an alternative IDE and programming language for Arduinos (not that I got the time though)...

Not sure if this will work with the Uno bootloader though, I don't own the Uno yet.
I like logic, hence I dislike humans but love computers.
User avatar
kenmo
Addict
Addict
Posts: 2033
Joined: Tue Dec 23, 2003 3:54 am

Re: Arduino Uploader (or any AVR chip with the bootloader)

Post by kenmo »

Cool. I will look deeper into this when my kit arrives (and I knock all these other things off my to-do list :) )
Post Reply