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