fork download
  1. MOVLW 55H
  2. CLRF TRISB
  3. MOVWF PORTB
  4. B1 COMP PORTB, F
  5. GOTO B1
  6. dsPIC33E bootloader with support for CAN (CANopen) and UART
  7. Ken Caluwaerts <ken@caluwaerts.eu> 2014
  8.  
  9. Copyright (C) 2014, Ken Caluwaerts
  10. All rights reserved.
  11.  
  12. The CAN(CANopen) and UART bootloader for the dsPIC33E bootloader is licensed
  13. under the Apache License, Version 2.0 (the "License");
  14. you may not use this file except in compliance with the License.
  15. You may obtain a copy of the License at
  16. http://w...content-available-to-author-only...e.org/licenses/LICENSE-2.0.
  17.  
  18. Unless required by applicable law or agreed to in writing,
  19. software distributed under the License is distributed on an
  20. "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
  21. either express or implied. See the License for the specific language
  22. governing permissions and limitations under the License.
  23. '''
  24. import numpy as np
  25. import time
  26. import io
  27. import argparse
  28. import sys
  29. import os
  30. try:
  31. import serial
  32. except ImportError:
  33. print "Could not import python serial"
  34. try:
  35. import can
  36. except ImportError:
  37. print "Could not import python CAN"
  38.  
  39. canopenshell_loc = '/home/super/canfestival/CanFestival-3-a82d867e7850/examples/CANOpenShell/CANOpenShell'
  40. cansocket_loc = '/home/super/canfestival/CanFestival-3-a82d867e7850/drivers/can_socket/libcanfestival_can_socket.so'
  41.  
  42. bootloader_cmd = {"READ_ID":0x09,"WRITE_PM":0x03,"ACK":0x01,"NACK":0x00,"RESET":0x08}
  43.  
  44. def open_port(device = '/dev/ttyUSB0', baudrate=115200):
  45. '''
  46. Opens a serial port and returns its handle.
  47. '''
  48. return serial.Serial(device,baudrate)
  49.  
  50. def read_id(port):
  51. '''
  52. Reads the device id and revision of the uC.
  53. '''
  54. port.write(bytearray([bootloader_cmd["READ_ID"]]))
  55. tmp = port.read(8)
  56. dev_id = tmp[1::-1].encode('hex') #endianness
  57. dev_revision = tmp[5:3:-1].encode('hex')
  58. return dev_id, dev_revision
  59.  
  60. def parse_hex(hex_file,memory):
  61. '''
  62. Parses a hex file (provided as a list of strings) and copies its contents to a memory object.
  63. '''
  64. ext_address = 0
  65. for line in hex_file:
  66. #parse format
  67. byte_count = int(line[1:3],base=16)
  68. address = int(line[3:7],base=16)
  69. record_type = int(line[7:9],base=16)
  70. if(record_type==1):
  71. print "EOF record"
  72. elif(record_type==4):
  73. print "Extended address"
  74. ext_address = int(line[9:13],base=16)<<16
  75. elif(record_type==0):
  76. address = (ext_address+address)/2
  77. #for i in xrange(bytecount)
  78. print "data: %d bytes at address %d \t %.5x"%(byte_count,address,address)
  79. #instruction "==4bytes 00xxxxxx" per iteration
  80. for i in xrange(byte_count/4):
  81. cur_address = address+i*2#addresses increase in steps of two
  82. opcode_little_endian = line[9+i*8:9+(i+1)*8]
  83. opcode = opcode_little_endian[6:8]+opcode_little_endian[4:6]+opcode_little_endian[2:4]+opcode_little_endian[0:2]
  84. opcode_num = int(opcode,base=16)
  85. print "address: %.6x opcode: %.6x"%(cur_address,opcode_num)
  86. memory.write(cur_address,(0,(opcode_num>>16)&0xFF,(opcode_num>>8)&0xFF,opcode_num&0xFF))
  87.  
  88. def load_hex_file(file_name):
  89. '''
  90. Opens a hex file and loads its contents into a list.
  91. '''
  92. f = open(file_name,'rb')
  93. hex_file = [l for l in f]
  94. f.close()
  95. return hex_file
  96.  
  97. def program_uc(memory,dev_id,port):
  98. #read device id
  99. dev_id_r, dev_rev_r = read_id(port)
  100. if(int(dev_id_r,base=16)==dev_id):
  101. print "device IDs match %s"%dev_id_r
  102. else:
  103. raise "device IDs do not match %x - %s"%(dev_id,dev_id_r)
  104. #get pages to program
  105. pic_mem, pic_mem_addr = memory.data_to_transmit()
  106. for i, idx in enumerate(pic_mem_addr):
  107. print "programming page %d/%d: \t %.6x"%(i,pic_mem_addr.shape[0],idx)
  108. #send program page command
  109. port.write(bytearray([bootloader_cmd["WRITE_PM"]]))
  110. time.sleep(0.01)
  111. #send address
  112. port.write(bytearray([idx&0xFF,(idx>>8)&0xFF,(idx>>16)&0xFF])) #little endian
  113. #send page data
  114. for j in xrange(pic_mem.shape[1]):
  115. port.write(bytearray([pic_mem[i,j,2]])) #little endian
  116. port.write(bytearray([pic_mem[i,j,1]]))
  117. port.write(bytearray([pic_mem[i,j,0]]))
  118. #read acknowledgment
  119. reply = ord(port.read(1))
  120. if(reply==bootloader_cmd["ACK"]):
  121. print "success"
  122. else:
  123. print "failed: %x"%reply
  124. break
  125.  
  126. print "programming complete, resetting microcontroller"
  127. #send reset command
  128. time.sleep(0.01)
  129. port.write(bytearray([bootloader_cmd["RESET"]]))
  130.  
  131. def write_uC_code_memory(memory,dev_id,fname):
  132. '''
  133. Writes the microcontroller program memory (non-zero) to a file.
  134. The resulting file can be programmed using the CANOpen bootloader.
  135. '''
  136. #write header
  137. #byte 0 uint8 magic byte (0x00 = program memory)
  138. #byte 1-2 uint16 number of lines in file
  139. #byte 3-6 uint32 device id
  140. #byte 7-N page to program (lines)
  141. # byte 0 uint8 magic byte (0x00 = write page to uC memory)
  142. # byte 1-2 uint16 number of instructions on page
  143. # byte 3-5 uint24 page address
  144. # byte 6-8,9-11...uint24 instructions to program
  145. #read device id
  146. with io.FileIO(fname,'w') as stream:
  147. stream.write(bytearray([0x00])) #magic byte
  148.  
  149. pic_mem, pic_mem_addr = memory.data_to_transmit()
  150. stream.write(bytearray([pic_mem.shape[0]>>8,pic_mem.shape[0]&0xFF])) #number of lines
  151.  
  152. stream.write(bytearray([dev_id>>24,(dev_id>>16)&0xFF,(dev_id>>8)&0xFF,(dev_id)&0xFF])) #dev id
  153.  
  154. #program memory lines
  155. for i, idx in enumerate(pic_mem_addr):
  156. print "writing program page %d/%d: \t %.6x"%(i,pic_mem_addr.shape[0],idx)
  157. stream.write(bytearray([0x00]))
  158. stream.write(bytearray([0x04,0x00]))#1024 instructions per page
  159. stream.write(bytearray([idx&0xFF,(idx>>8)&0xFF,(idx>>16)&0xFF])) #page address little endian
  160. #send page data
  161. for j in xrange(pic_mem.shape[1]):
  162. stream.write(bytearray([pic_mem[i,j,2]])) #little endian
  163. stream.write(bytearray([pic_mem[i,j,1]]))
  164. stream.write(bytearray([pic_mem[i,j,0]]))
  165.  
  166. class pic_memory(object):
  167. def __init__(self,num_pages=171):
  168. self.data = np.zeros((num_pages*1024,4),dtype=np.uint8) #just one big continuous chunk of memory. Note that addresses increase in steps of two
  169. self.tags = np.zeros(num_pages,dtype=np.uint8) #0 = empty, 1 = dirty program memory
  170.  
  171. def write(self, address, data):#data is assumed to be in the format phantombyte(0) 23..16 15..8 7..0
  172. '''
  173. Stores an instruction in the memory object.
  174. Data is supposed to be a list/array of 4 uint8s (bytes). The first is a phantom byte (0).
  175. '''
  176. address=int(address) #just to make sure
  177. mem_address = address>>1 #addresses increase in steps of two
  178. page_address = mem_address>>10
  179. self.tags[page_address] = 1#mark as dirty
  180. self.data[mem_address] = data
  181.  
  182. def data_to_transmit(self):
  183. '''
  184. Creates a list of dirty pages to transmit to the microcontroller.
  185. Returns a numpy uint8 array (N by 1024 by 3, no phantom byte uint8) and a numpy array of page addresses (uint).
  186. '''
  187. N = np.sum(self.tags==1)
  188. pic_mem = np.zeros((N,1024,3),dtype=np.uint8)
  189. pic_mem_addr = np.where(self.tags==1)[0]<<11 #multiply addresses by 2048 (1024 instructions in steps of two)
  190. for i, idx in enumerate(pic_mem_addr):
  191. pic_mem[i] = self.data[idx>>1:(idx>>1)+1024,1:]
  192. return pic_mem, pic_mem_addr
  193.  
  194. def set_boot_address(self,address=0x800):
  195. '''
  196. Changes the goto instruction that is executed when the uC boots up.
  197. Address should be an unsigned int.
  198. '''
  199. self.write(0x0,(0x00,0x04,(address>>8)&0xFF,address&0xFE)) #0x0004 is a GOTO instruction (http://w...content-available-to-author-only...p.com/downloads/en/DeviceDoc/70157C.pdf page 196)
  200. self.write(0x2,(0x00,0x00,0x00,(address>>16)&0x7F))
  201.  
  202. def open_can_bus(busname='can0'):
  203. return can.interface.Bus(busname)
  204.  
  205. def upload_code_canopen_raw(memory, node_id, bus):
  206. '''
  207. Upload the program memory over CAN to a microcontroller by using raw CANopen messages.
  208. This code does NOT check for errors and should only be used for testing purposes.
  209. '''
  210. pic_mem, pic_mem_addr = memory.data_to_transmit()
  211. #reset uC
  212. print "Python hack resetting the microcontroller"
  213. msg = can.Message(arbitration_id=0x0,data=[129,node_id],extended_id=False)
  214. bus.send(msg)
  215. time.sleep(1) #wait a bit for the uC to reset
  216. #send data
  217. for i, idx in enumerate(pic_mem_addr):
  218. print "Writing program page using python hack %d/%d: \t %.6x"%(i,pic_mem_addr.shape[0],idx)
  219. #SDO download initiate
  220. msg = can.Message(arbitration_id=0x600+node_id,data=[0b00100001,0x50,0x1F,0x01,0,0,0,0],extended_id=False)
  221. bus.send(msg)
  222. time.sleep(0.05)
  223. data = []
  224.  
  225. data.extend([idx&0xFF,(idx>>8)&0xFF,(idx>>16)&0xFF]) #page address little endian
  226. for j in xrange(pic_mem.shape[1]):
  227. data.extend([pic_mem[i,j,2]]) #little endian
  228. data.extend([pic_mem[i,j,1]])
  229. data.extend([pic_mem[i,j,0]])
  230.  
  231. toggle = 0
  232. j = 0
  233. last_msg = 0
  234. num_bytes = len(data)
  235. while(not last_msg):
  236. #There are way more efficient ways of doing this, but it's just a hack
  237. data_msg = np.zeros(7,dtype=np.uint8)
  238. n = 0
  239. for k in xrange(7):
  240. if(j+k>=num_bytes):
  241. last_msg = 1
  242. n = 7-k
  243. break
  244. else:
  245. data_msg[k] = data[j+k]
  246. msg = can.Message(arbitration_id=0x600+node_id,data=[toggle<<4|n<<1|last_msg,data_msg[0],data_msg[1],data_msg[2],data_msg[3],data_msg[4],data_msg[5],data_msg[6]],extended_id=False)
  247. bus.send(msg)
  248. toggle = not toggle
  249. j+=7
  250.  
  251. time.sleep(0.1)
  252. #start uC
  253. msg = can.Message(arbitration_id=0x0,data=[1,node_id],extended_id=False)
  254. bus.send(msg)
  255.  
  256.  
  257. if __name__ == "__main__":
  258. parser = argparse.ArgumentParser(description='dsPIC33E bootloader with support for CAN (CANopen) and UART',epilog="Ken Caluwaerts <ken@caluwaerts.eu> 2014")
  259. parser.add_argument("--interface","-i",choices=["UART","CAN"],default="CAN",help="Hardware interface: CAN or UART. Default CAN.")
  260. parser.add_argument("--output","-o",type=str,help="Output: the filename of the generated binary file in case CAN is used (default == input filename.bin), the hardware interface in case of UART (default /dev/ttyUSB0)",default=None)
  261. parser.add_argument("--devid","-d",default=0x1f65,type=int,help="Device id (write as decimal number, e.g. 0x1f65 should be written as 8037). Default 0x1F65.")
  262. parser.add_argument("--bootaddress","-b",type=int,default=0x800,help="Bootloader address (write as decimal number, e.g. 0x800 should be written as 2048). Default 0x800.")
  263. parser.add_argument("--donotmodifybootaddress","-a", action="store_true", help="Do not modify boot address of the HEX file. (Default: modify).")
  264. parser.add_argument("--baudrate","-r",type=int,default=115200,help="UART baudrate (Default: 115200)")
  265. parser.add_argument("--uploadcan","-u",action="store_true",help="Uploads the firmware over CAN (see --canuploadmethod to define the implementation)")
  266. parser.add_argument("--canuploadmethod","-m",choices=["CANfestival","python"],default="CANfestival",help="How to upload firmware over CAN (if -u enabled). Using CANopen and CANfestival ('CANfestival') or raw python CAN messages ('python'). (Default: 'CANfestival')")
  267. parser.add_argument("--nodeid","-n",type=int, default=3,help="CANopen node ID (see --uploadcan). (Default: 3)")
  268. parser.add_argument("--canbus","-c",type=str,default="can0", help="Which can bus to use (only relevant when -u and -m python are active) (Default: 'can0')")
  269. parser.add_argument("hexfile",type=str,help="Input HEX file (typically generated by MPLAB X)")
  270. try:
  271. args = parser.parse_args()
  272. except:
  273. print "Unexpected error:", sys.exc_info()[0]
  274. parser.print_help()
  275. sys.exit(-1)
  276.  
  277. boot_address = args.bootaddress
  278. dev_id = args.devid
  279. fname = args.hexfile
  280. iface = args.interface
  281. output = args.output
  282. modify_boot_address = not args.donotmodifybootaddress
  283. baudrate = args.baudrate
  284. uploadcan = args.uploadcan
  285. node_id = args.nodeid
  286. canuploadmethod = args.canuploadmethod
  287. canbus = args.canbus
  288. if(iface=="UART"):
  289. #UART
  290. hex_file = load_hex_file(fname)
  291. memory = pic_memory()
  292. parse_hex(hex_file,memory)
  293. if(modify_boot_address):
  294. print "Modifying boot address"
  295. memory.set_boot_address(boot_address)
  296. if(output is None):
  297. output="/dev/ttyUSB0"
  298. print "Opening serial port"
  299. port = open_port(output,baudrate)
  300. print "Programming microcontroller"
  301. program_uc(memory,dev_id,port)
  302. else:
  303. #CAN
  304. hex_file = load_hex_file(fname)
  305. memory = pic_memory()
  306. parse_hex(hex_file,memory)
  307. if(modify_boot_address):
  308. print "Modifying boot address"
  309. memory.set_boot_address(boot_address)
  310. if(output is None):
  311. output=os.path.splitext(fname)[0]+".bin"
  312. print "Writing program memory to file (%s)"%output
  313. write_uC_code_memory(memory,dev_id,output)
  314. if(uploadcan):
  315. if(canuploadmethod=="CANfestival"):
  316. import subprocess
  317. subprocess.call([canopenshell_loc, 'load#%s,can0,1000,2,0'%cansocket_loc, 'srst#3', 'wait#1', 'bldr#%d,%s'%(node_id,output), 'ssta#3', 'wait#5', 'quit'])
  318. else:
  319. bus = open_can_bus(canbus)
  320. upload_code_canopen_raw(memory, node_id, bus)
  321.  
Success #stdin #stdout 0.04s 26144KB
stdin
Standard input is empty
stdout
  MOVLW 55H
   CLRF  TRISB
   MOVWF PORTB
B1 COMP PORTB, F
   GOTO B1
	dsPIC33E bootloader with support for CAN (CANopen) and UART
	Ken Caluwaerts <ken@caluwaerts.eu> 2014
	
	Copyright (C) 2014, Ken Caluwaerts
	All rights reserved.
	
	The CAN(CANopen) and UART bootloader for the dsPIC33E bootloader is licensed
	under the Apache License, Version 2.0 (the "License");
	you may not use this file except in compliance with the License.
	You may obtain a copy of the License at
	http://w...content-available-to-author-only...e.org/licenses/LICENSE-2.0.
	
	Unless required by applicable law or agreed to in writing,
	software distributed under the License is distributed on an
	"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
	either express or implied. See the License for the specific language
	governing permissions and limitations under the License.
'''
import numpy as np
import time
import io
import argparse
import sys
import os
try:
	import serial
except ImportError:
	print "Could not import python serial"
try:
	import can
except ImportError:
	print "Could not import python CAN"

canopenshell_loc = '/home/super/canfestival/CanFestival-3-a82d867e7850/examples/CANOpenShell/CANOpenShell'
cansocket_loc = '/home/super/canfestival/CanFestival-3-a82d867e7850/drivers/can_socket/libcanfestival_can_socket.so'

bootloader_cmd = {"READ_ID":0x09,"WRITE_PM":0x03,"ACK":0x01,"NACK":0x00,"RESET":0x08}

def open_port(device = '/dev/ttyUSB0', baudrate=115200):
	'''
		Opens a serial port and returns its handle.
	'''
	return serial.Serial(device,baudrate)

def read_id(port):
	'''
		Reads the device id and revision of the uC.
	'''
	port.write(bytearray([bootloader_cmd["READ_ID"]]))
	tmp = port.read(8)
	dev_id = tmp[1::-1].encode('hex') #endianness
	dev_revision = tmp[5:3:-1].encode('hex')
	return dev_id, dev_revision
	
def parse_hex(hex_file,memory):
	'''
		Parses a hex file (provided as a list of strings) and copies its contents to a memory object.
	'''
	ext_address = 0
	for line in hex_file:
		#parse format
		byte_count = int(line[1:3],base=16)
		address = int(line[3:7],base=16)
		record_type = int(line[7:9],base=16)
		if(record_type==1):
			print "EOF record"
		elif(record_type==4):
			print "Extended address"
			ext_address = int(line[9:13],base=16)<<16
		elif(record_type==0):
			address = (ext_address+address)/2
			#for i in xrange(bytecount)
			print "data: %d bytes at address %d \t %.5x"%(byte_count,address,address)
			#instruction "==4bytes 00xxxxxx" per iteration 
			for i in xrange(byte_count/4):
				cur_address = address+i*2#addresses increase in steps of two
				opcode_little_endian = line[9+i*8:9+(i+1)*8]
				opcode = opcode_little_endian[6:8]+opcode_little_endian[4:6]+opcode_little_endian[2:4]+opcode_little_endian[0:2]
				opcode_num = int(opcode,base=16)
				print "address: %.6x opcode: %.6x"%(cur_address,opcode_num)
				memory.write(cur_address,(0,(opcode_num>>16)&0xFF,(opcode_num>>8)&0xFF,opcode_num&0xFF))
	
def load_hex_file(file_name):
	'''
		Opens a hex file and loads its contents into a list.
	'''
	f = open(file_name,'rb')
	hex_file = [l for l in f]
	f.close()
	return hex_file
	
def program_uc(memory,dev_id,port):
	#read device id
	dev_id_r, dev_rev_r = read_id(port)
	if(int(dev_id_r,base=16)==dev_id):
		print "device IDs match %s"%dev_id_r
	else:
		raise "device IDs do not match %x - %s"%(dev_id,dev_id_r)
	#get pages to program
	pic_mem, pic_mem_addr = memory.data_to_transmit()
	for i, idx in enumerate(pic_mem_addr):
		print "programming page %d/%d: \t %.6x"%(i,pic_mem_addr.shape[0],idx)
		#send program page command
		port.write(bytearray([bootloader_cmd["WRITE_PM"]]))
		time.sleep(0.01)
		#send address
		port.write(bytearray([idx&0xFF,(idx>>8)&0xFF,(idx>>16)&0xFF])) #little endian
		#send page data
		for j in xrange(pic_mem.shape[1]):
			port.write(bytearray([pic_mem[i,j,2]])) #little endian
			port.write(bytearray([pic_mem[i,j,1]]))
			port.write(bytearray([pic_mem[i,j,0]]))
		#read acknowledgment
		reply = ord(port.read(1))		
		if(reply==bootloader_cmd["ACK"]):
			print "success"
		else:
			print "failed: %x"%reply
			break
		
	print "programming complete, resetting microcontroller"
	#send reset command
	time.sleep(0.01)
	port.write(bytearray([bootloader_cmd["RESET"]]))
	
def write_uC_code_memory(memory,dev_id,fname):
	'''
		Writes the microcontroller program memory (non-zero) to a file.
		The resulting file can be programmed using the CANOpen bootloader.
	'''
	#write header
	#byte 0		uint8		magic byte (0x00 = program memory)
	#byte 1-2	uint16		number of lines in file
	#byte 3-6	uint32		device id
	#byte 7-N			page to program (lines)
	#				byte 0		uint8	magic byte (0x00 = write page to uC memory)
	#				byte 1-2	uint16	number of instructions on page 
	#				byte 3-5	uint24	page address	
	#				byte 6-8,9-11...uint24	instructions to program		
	#read device id
	with io.FileIO(fname,'w') as stream:
		stream.write(bytearray([0x00])) #magic byte
	
		pic_mem, pic_mem_addr = memory.data_to_transmit()
		stream.write(bytearray([pic_mem.shape[0]>>8,pic_mem.shape[0]&0xFF])) #number of lines
	
		stream.write(bytearray([dev_id>>24,(dev_id>>16)&0xFF,(dev_id>>8)&0xFF,(dev_id)&0xFF])) #dev id
	
		#program memory lines
		for i, idx in enumerate(pic_mem_addr):
			print "writing program page %d/%d: \t %.6x"%(i,pic_mem_addr.shape[0],idx)
			stream.write(bytearray([0x00]))
			stream.write(bytearray([0x04,0x00]))#1024 instructions per page
			stream.write(bytearray([idx&0xFF,(idx>>8)&0xFF,(idx>>16)&0xFF])) #page address little endian
			#send page data
			for j in xrange(pic_mem.shape[1]):
				stream.write(bytearray([pic_mem[i,j,2]])) #little endian
				stream.write(bytearray([pic_mem[i,j,1]]))
				stream.write(bytearray([pic_mem[i,j,0]]))	
	
class pic_memory(object):
	def __init__(self,num_pages=171):
		self.data = np.zeros((num_pages*1024,4),dtype=np.uint8) #just one big continuous chunk of memory. Note that addresses increase in steps of two
		self.tags = np.zeros(num_pages,dtype=np.uint8) #0 = empty, 1 = dirty program memory
		
	def write(self, address, data):#data is assumed to be in the format phantombyte(0) 23..16 15..8 7..0
		'''
			Stores an instruction in the memory object.
			Data is supposed to be a list/array of 4 uint8s (bytes). The first is a phantom byte (0).
		'''
		address=int(address) #just to make sure
		mem_address = address>>1 #addresses increase in steps of two
		page_address = mem_address>>10
		self.tags[page_address] = 1#mark as dirty
		self.data[mem_address] = data
		
	def data_to_transmit(self):
		'''
			Creates a list of dirty pages to transmit to the microcontroller.
			Returns a numpy uint8 array (N by 1024 by 3, no phantom byte uint8) and a numpy array of page addresses (uint).
		'''
		N = np.sum(self.tags==1)
		pic_mem = np.zeros((N,1024,3),dtype=np.uint8)
		pic_mem_addr = np.where(self.tags==1)[0]<<11 #multiply addresses by 2048 (1024 instructions in steps of two)
		for i, idx in enumerate(pic_mem_addr):
			pic_mem[i] = self.data[idx>>1:(idx>>1)+1024,1:]
		return pic_mem, pic_mem_addr
	
	def set_boot_address(self,address=0x800):
		'''
			Changes the goto instruction that is executed when the uC boots up.
			Address should be an unsigned int.
		'''
		self.write(0x0,(0x00,0x04,(address>>8)&0xFF,address&0xFE)) #0x0004 is a GOTO instruction (http://w...content-available-to-author-only...p.com/downloads/en/DeviceDoc/70157C.pdf page 196)
		self.write(0x2,(0x00,0x00,0x00,(address>>16)&0x7F))
		
def open_can_bus(busname='can0'):
	return can.interface.Bus(busname)

def upload_code_canopen_raw(memory, node_id, bus):
	'''
		Upload the program memory over CAN to a microcontroller by using raw CANopen messages.
		This code does NOT check for errors and should only be used for testing purposes.
	'''
	pic_mem, pic_mem_addr = memory.data_to_transmit()
	#reset uC
	print "Python hack resetting the microcontroller"
	msg = can.Message(arbitration_id=0x0,data=[129,node_id],extended_id=False)
	bus.send(msg)
	time.sleep(1) #wait a bit for the uC to reset
	#send data
	for i, idx in enumerate(pic_mem_addr):
		print "Writing program page using python hack %d/%d: \t %.6x"%(i,pic_mem_addr.shape[0],idx)
		#SDO download initiate
		msg = can.Message(arbitration_id=0x600+node_id,data=[0b00100001,0x50,0x1F,0x01,0,0,0,0],extended_id=False)
		bus.send(msg)
		time.sleep(0.05)
		data = []
		
		data.extend([idx&0xFF,(idx>>8)&0xFF,(idx>>16)&0xFF]) #page address little endian
		for j in xrange(pic_mem.shape[1]):
			data.extend([pic_mem[i,j,2]]) #little endian
			data.extend([pic_mem[i,j,1]])
			data.extend([pic_mem[i,j,0]])
		
		toggle = 0
		j = 0
		last_msg = 0
		num_bytes = len(data)
		while(not last_msg):
			#There are way more efficient ways of doing this, but it's just a hack
			data_msg = np.zeros(7,dtype=np.uint8)
			n = 0
			for k in xrange(7):
				if(j+k>=num_bytes):
					last_msg = 1
					n = 7-k
					break
				else:
					data_msg[k] = data[j+k] 
			msg = can.Message(arbitration_id=0x600+node_id,data=[toggle<<4|n<<1|last_msg,data_msg[0],data_msg[1],data_msg[2],data_msg[3],data_msg[4],data_msg[5],data_msg[6]],extended_id=False)
			bus.send(msg)
			toggle = not toggle
			j+=7
		
		time.sleep(0.1)
	#start uC
	msg = can.Message(arbitration_id=0x0,data=[1,node_id],extended_id=False)
	bus.send(msg)


if __name__ == "__main__":
	parser = argparse.ArgumentParser(description='dsPIC33E bootloader with support for CAN (CANopen) and UART',epilog="Ken Caluwaerts <ken@caluwaerts.eu> 2014")
	parser.add_argument("--interface","-i",choices=["UART","CAN"],default="CAN",help="Hardware interface: CAN or UART. Default CAN.")
	parser.add_argument("--output","-o",type=str,help="Output: the filename of the generated binary file in case CAN is used (default == input filename.bin), the hardware interface in case of UART (default /dev/ttyUSB0)",default=None)
	parser.add_argument("--devid","-d",default=0x1f65,type=int,help="Device id (write as decimal number, e.g. 0x1f65 should be written as 8037). Default 0x1F65.")
	parser.add_argument("--bootaddress","-b",type=int,default=0x800,help="Bootloader address (write as decimal number, e.g. 0x800 should be written as 2048). Default 0x800.")
	parser.add_argument("--donotmodifybootaddress","-a", action="store_true", help="Do not modify boot address of the HEX file. (Default: modify).")
	parser.add_argument("--baudrate","-r",type=int,default=115200,help="UART baudrate (Default: 115200)")
	parser.add_argument("--uploadcan","-u",action="store_true",help="Uploads the firmware over CAN (see --canuploadmethod to define the implementation)")
	parser.add_argument("--canuploadmethod","-m",choices=["CANfestival","python"],default="CANfestival",help="How to upload firmware over CAN (if -u enabled). Using CANopen and CANfestival ('CANfestival') or raw python CAN messages ('python'). (Default: 'CANfestival')")
	parser.add_argument("--nodeid","-n",type=int, default=3,help="CANopen node ID (see --uploadcan). (Default: 3)")
	parser.add_argument("--canbus","-c",type=str,default="can0", help="Which can bus to use (only relevant when -u and -m python are active) (Default: 'can0')")
	parser.add_argument("hexfile",type=str,help="Input HEX file (typically generated by MPLAB X)")
	try:
		args = parser.parse_args()
	except:
		print "Unexpected error:", sys.exc_info()[0]
		parser.print_help() 
		sys.exit(-1)
		
	boot_address = args.bootaddress
	dev_id = args.devid
	fname = args.hexfile
	iface = args.interface
	output = args.output
	modify_boot_address = not args.donotmodifybootaddress
	baudrate = args.baudrate
	uploadcan = args.uploadcan
	node_id = args.nodeid
	canuploadmethod = args.canuploadmethod
	canbus = args.canbus
	if(iface=="UART"):
		#UART
		hex_file = load_hex_file(fname)
		memory = pic_memory()
		parse_hex(hex_file,memory)
		if(modify_boot_address):
			print "Modifying boot address"
			memory.set_boot_address(boot_address)	
		if(output is None):
			output="/dev/ttyUSB0"
		print "Opening serial port"
		port = open_port(output,baudrate)
		print "Programming microcontroller"
		program_uc(memory,dev_id,port)
	else:
		#CAN
		hex_file = load_hex_file(fname)
		memory = pic_memory()
		parse_hex(hex_file,memory)
		if(modify_boot_address):
			print "Modifying boot address"
			memory.set_boot_address(boot_address)	
		if(output is None):
			output=os.path.splitext(fname)[0]+".bin"
		print "Writing program memory to file (%s)"%output
		write_uC_code_memory(memory,dev_id,output)
		if(uploadcan):
			if(canuploadmethod=="CANfestival"):
				import subprocess
				subprocess.call([canopenshell_loc, 'load#%s,can0,1000,2,0'%cansocket_loc, 'srst#3', 'wait#1', 'bldr#%d,%s'%(node_id,output), 'ssta#3', 'wait#5', 'quit'])
			else:
				bus = open_can_bus(canbus)
				upload_code_canopen_raw(memory, node_id, bus)