├── Makefile ├── README.md ├── jlink ├── __init__.py ├── elf.py ├── jlink.py ├── lib32 │ └── .gitkeep ├── lib64 │ └── .gitkeep └── memory.py ├── nrfjprog └── setup.py /Makefile: -------------------------------------------------------------------------------- 1 | install: 2 | python setup.py install 3 | 4 | build: 5 | python setup.py build 6 | 7 | MANIFEST: 8 | python setup.py sdist --manifest-only 9 | 10 | clean: 11 | rm -f *.pyc *~ jlink/*.pyc jlink/*~ 12 | rm -f MANIFEST 13 | rm -rf build 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | nrfjprog 2 | -------- 3 | 4 | An implementation of the Nordic "nrfjprog.exe" program using python 5 | and jlink.py. 6 | 7 | nrfjprog.exe is used to program softdevices and application code in 8 | the nRF51422 and nRF51822 system-on-chip radio microcontrollers. 9 | 10 | It uses the Segger "J-Link" hardware, or the built-in Segger supplied 11 | with many of the Nordic development kits and dongles. 12 | 13 | Features 14 | -------- 15 | I mostly intend to support the subset of features that are needed to 16 | make the SDK Makefiles work on a Linux or OS X machine. 17 | 18 | I ran the following on a selection of installed SDKs to see what 19 | nrfjprog options are used by the Makefiles: 20 | 21 | grep nrfjprog -rI /opt/nrf51sdk-* | grep Makefile | sed s/.*nrfjprog/nrfjprog/ | sort | uniq 22 | 23 | This gives the following set of uses: 24 | 25 | nrfjprog --erase 26 | nrfjprog --reset 27 | nrfjprog --reset --program softdevice.hex 28 | nrfjprog --reset --program $(OUTPUT_BINARY_DIRECTORY)/$(OUTPUT_FILENAME).hex 29 | 30 | So nrfjprog implements the --erase, --reset, and --program commands. 31 | 32 | Setting up 33 | ---------- 34 | There are two paths, `lib32/` and `lib64/`. You must download the 35 | appropriate DLL from http://www.segger.com and add it to the 36 | appropriate directory depending on your system. 37 | 38 | The DLL ending is different among the three supported platforms: 39 | 40 | | Platform | Extension | 41 | | -------- | --------- | 42 | | Windows | .dll | 43 | | Linux | .so | 44 | | OS X | .dylib | 45 | 46 | The Windows dll is named `jlinkarm.dll`. 47 | 48 | The Linux dll is named something like `libjlinkarm.so.4.62.1`. After 49 | putting it in the right directory, make a symlink to 50 | `libjlinkarm.so.4` in the same directory: 51 | 52 | ln -s libjlinkarm.so.4.*.* libjlinkarm.so.4 53 | 54 | Linux users should also install the Segger-provided udev rule to allow 55 | the device to be used as a non-root user. See the Segger 56 | documentation. 57 | 58 | The OS X dll is named something like `libjlinkarm.4.62.1.dylib`. 59 | After putting it in the right directory, make a symlink to 60 | `libjlinkarm.4.dylib` in the same directory: 61 | 62 | ln -s libjlinkarm.4.*.*.dylib libjlinkarm.4.dylib 63 | 64 | To test that the DLL is functioning properly, connect a Segger and run 65 | `python jlink.py` by itself. It will error out if the library doesn't 66 | load or the Segger isn't connected. 67 | 68 | Installation 69 | ------------ 70 | Install by running "setup.py install". This will install the library 71 | and segger libs to a Python-findable location. 72 | 73 | Usage 74 | ----- 75 | Consult the nrfjprog.exe documentation. If this nrfjprog behavior 76 | diverges from the official one, please file a bug. 77 | 78 | Using Nordic SDK Makefiles 79 | -------------------------- 80 | 81 | Here's a hint for using the Nordic Makefiles. They have a weirdly 82 | specific GCC installation path. Furthermore, this path is hardcoded 83 | in the SDK Makefile.common and cannot be overridden by application 84 | Makefiles. 85 | 86 | Rather than horsing around with compiling GCC from scratch, or using 87 | ~~lockin~~ toolchain vendors, it is easier and better to use a 88 | distribution-supported GCC, or the ARM-unofficially-supported PPA (for 89 | Ubuntu achievers): 90 | 91 | sudo add-apt-repository ppa:terry.guo/gcc-arm-embedded 92 | sudo apt-get update 93 | sudo apt-get install gcc-arm-none-eabi 94 | 95 | A simple symlink will make things work fine with GCC in the normal 96 | location: 97 | 98 | sudo ln -rs /usr /usr/local/gcc-arm-none-eabi-4_8-2014q1 99 | 100 | Now the Makefiles in the SDK will work unchanged, including the "make 101 | flash" target. 102 | -------------------------------------------------------------------------------- /jlink/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markrages/jlinkpy/402195972e1592d0f4a3750e457c3e84de0e406f/jlink/__init__.py -------------------------------------------------------------------------------- /jlink/elf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # $Id: elf.py,v 1.1 2006/04/11 18:35:23 cliechti Exp $ 3 | 4 | import struct 5 | 6 | # ELF object file reader 7 | # (C) 2003 cliechti@gmx.net 8 | # Python license 9 | 10 | # size alignment 11 | # Elf32_Addr 4 4 Unsigned program address 12 | # Elf32_Half 2 2 Unsigned medium integer 13 | # Elf32_Off 4 4 Unsigned file offset 14 | # Elf32_Sword 4 4 Signed large integer 15 | # Elf32_Word 4 4 Unsigned large integer 16 | # unsignedchar 1 1 Unsigned small integer 17 | 18 | #define EI_NIDENT 16 19 | #~ typedef struct{ 20 | #~ unsigned char e_ident[EI_NIDENT]; 21 | #~ Elf32_Half e_type; 22 | #~ Elf32_Half e_machine; 23 | #~ Elf32_Word e_version; 24 | #~ Elf32_Addr e_entry; 25 | #~ Elf32_Off e_phoff; 26 | #~ Elf32_Off e_shoff; 27 | #~ Elf32_Word e_flags; 28 | #~ Elf32_Half e_ehsize; 29 | #~ Elf32_Half e_phentsize; 30 | #~ Elf32_Half e_phnum; 31 | #~ Elf32_Half e_shentsize; 32 | #~ Elf32_Half e_shnum; 33 | #~ Elf32_Half e_shstrndx; 34 | #~ } Elf32_Ehdr; 35 | 36 | 37 | #Section Header 38 | #~ typedef struct { 39 | #~ Elf32_Word sh_name; 40 | #~ Elf32_Word sh_type; 41 | #~ Elf32_Word sh_flags; 42 | #~ Elf32_Addr sh_addr; 43 | #~ Elf32_Off sh_offset; 44 | #~ Elf32_Word sh_size; 45 | #~ Elf32_Word sh_link; 46 | #~ Elf32_Word sh_info; 47 | #~ Elf32_Word sh_addralign; 48 | #~ Elf32_Word sh_entsize; 49 | #~ } Elf32_Shdr; 50 | 51 | #~ typedef struct { 52 | #~ Elf32_Word p_type; 53 | #~ Elf32_Off p_offset; 54 | #~ Elf32_Addr p_vaddr; 55 | #~ Elf32_Addr p_paddr; 56 | #~ Elf32_Word p_filesz; 57 | #~ Elf32_Word p_memsz; 58 | #~ Elf32_Word p_flags; 59 | #~ Elf32_Word p_align; 60 | #~ } Elf32_Phdr; 61 | 62 | 63 | class ELFException(Exception): pass 64 | 65 | class ELFSection: 66 | """read and store a section""" 67 | Elf32_Shdr = "= section.sh_addr + section.sh_size) \ 283 | and (not (section.sh_flags & ELFSection.SHF_ALLOC and section.sh_type != ELFSection.SHT_NOBITS) \ 284 | or (p.p_offset <= section.sh_offset \ 285 | and (p.p_offset + p.p_filesz >= section.sh_offset + section.sh_size)))): 286 | return section.sh_addr + p.p_paddr - p.p_vaddr 287 | return section.sh_addr 288 | 289 | def getSections(self): 290 | """get sections relevant for the application""" 291 | res = [] 292 | for section in self.sections: 293 | if section.sh_flags & ELFSection.SHF_ALLOC and section.sh_type != ELFSection.SHT_NOBITS: 294 | res.append(section) 295 | return res 296 | 297 | def __str__(self): 298 | """pretty print for debug...""" 299 | return "%s(self.e_type=%r, self.e_machine=%r, self.e_version=%r, sections=%r)" % ( 300 | self.__class__.__name__, 301 | self.e_type, self.e_machine, self.e_version, 302 | [section.name for section in self.sections]) 303 | 304 | 305 | if __name__ == '__main__': 306 | print "This is only a module test!" 307 | elf = ELFObject() 308 | elf.fromFile(open("test.elf")) 309 | if elf.e_type != ELFObject.ET_EXEC: 310 | raise Exception("No executable") 311 | print elf 312 | 313 | #~ print repr(elf.getSection('.text').data) 314 | #~ print [(s.name, hex(s.sh_addr)) for s in elf.getSections()] 315 | print "-"*20 316 | for p in elf.sections: print p 317 | print "-"*20 318 | for p in elf.getSections(): print p 319 | print "-"*20 320 | for p in elf.getProgrammableSections(): print p 321 | -------------------------------------------------------------------------------- /jlink/jlink.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import sys 4 | import os 5 | import ctypes 6 | import memory 7 | 8 | DEBUG=0 9 | 10 | BASE=0x4001e000 11 | READY=BASE+0x400 12 | CONFIG=BASE+0x504 13 | CONFIG_EEN=2 14 | CONFIG_WEN=1 15 | CONFIG_REN=0 16 | ERASEPAGE=BASE+0x508 17 | ERASEALL=BASE+0x50c 18 | 19 | def print_range(start, end, text=''): 20 | print " | 0x%08x -> 0x%08x (%d bytes)"%(start, end, end-start), 21 | print text 22 | 23 | def print_segment(segment): 24 | return print_range(segment.startaddress, segment.startaddress + len(segment.data)) 25 | 26 | def print_mem_map(progdata): 27 | for segment in progdata.segments: 28 | print_segment(segment) 29 | 30 | def locate_library(libname, paths=sys.path, loader=None): 31 | if loader is None: loader=ctypes.cdll #windll 32 | for path in paths: 33 | if path.lower().endswith('.zip'): 34 | path = os.path.dirname(path) 35 | library = os.path.join(path, libname) 36 | if DEBUG > 4: sys.stderr.write('trying %r...\n' % library) 37 | if os.path.exists(library): 38 | if DEBUG > 4: sys.stderr.write('using %r\n' % library) 39 | return loader.LoadLibrary(library), library 40 | else: 41 | raise IOError('%s not found' % libname) 42 | 43 | def get_jlink_dll(): 44 | 45 | # what kind of system am I? 46 | import platform 47 | if platform.architecture()[0]=='32bit': 48 | libpath='lib32' 49 | elif platform.architecture()[0]=='64bit': 50 | libpath='lib64' 51 | else: 52 | libpath='' 53 | raise Exception(repr(platform.architecture())) 54 | 55 | # start with the script path 56 | search_path = [os.path.join(os.path.dirname(os.path.realpath(__file__)),libpath)] 57 | search_path += sys.path[:] #copy sys.path list 58 | 59 | # if environment variable is set, insert this path first 60 | try: 61 | search_path.insert(0, os.environ['JLINK_PATH']) 62 | except KeyError: 63 | try: 64 | search_path.extend(os.environ['PATH'].split(os.pathsep)) 65 | except KeyError: 66 | pass 67 | 68 | if sys.platform == 'win32': 69 | jlink, backend_info = locate_library('jlinkarm.dll', search_path) 70 | elif sys.platform == 'linux2': 71 | jlink, backend_info = locate_library('libjlinkarm.so.4', 72 | search_path, ctypes.cdll) 73 | elif sys.platform == 'darwin': 74 | jlink, backend_info = locate_library('libjlinkarm.so.4.dylib', 75 | search_path, ctypes.cdll) 76 | return jlink, backend_info 77 | 78 | class JLinkException(Exception): pass 79 | 80 | import time 81 | class JLink(object): 82 | def __init__(self): 83 | timeout=10 84 | retried=False 85 | t0=time.time() 86 | elapsed=-1 87 | while time.time() < t0+timeout: 88 | self.jl,self.jlink_lib_name = get_jlink_dll() 89 | try: 90 | self._init() 91 | if retried: print "success" 92 | return 93 | except JLinkException,x: 94 | if x.args[0]==-258: 95 | new_elapsed=int(time.time()-t0) 96 | if new_elapsed != elapsed: 97 | elapsed=new_elapsed 98 | print timeout-elapsed, 99 | sys.stdout.flush() 100 | 101 | retried=True 102 | continue 103 | else: 104 | raise 105 | else: 106 | raise 107 | 108 | def _init(self): 109 | self.tif_select(1) 110 | self.set_speed(1000) 111 | self.reset() 112 | pass 113 | 114 | def clear_error(self): self.jl.JLINK_ClrError() 115 | def has_error(self): return self.jl.JLINK_HasError(); 116 | 117 | def check_err(fn): 118 | def checked_transaction(self,*args): 119 | self.clear_error() 120 | ret=fn(self, *args) 121 | errno=self.has_error() 122 | if errno: 123 | raise JLinkException(errno) 124 | return ret 125 | return checked_transaction 126 | 127 | @check_err 128 | def tif_select(self, tif): 129 | return self.jl.JLINKARM_TIF_Select(tif) 130 | @check_err 131 | def set_speed(self, khz): return self.jl.JLINKARM_SetSpeed(khz) 132 | @check_err 133 | def reset(self): return self.jl.JLINKARM_Reset() 134 | @check_err 135 | def halt(self): return self.jl.JLINKARM_Halt() 136 | @check_err 137 | def clear_tck(self): return self.jl.JLINKARM_ClrTCK() 138 | @check_err 139 | def clear_tms(self): return self.jl.JLINKARM_ClrTMS() 140 | @check_err 141 | def set_tms(self): return self.jl.JLINKARM_SetTMS() 142 | @check_err 143 | def read_reg(self,r): return self.jl.JLINKARM_ReadReg(r) 144 | @check_err 145 | def write_reg(self,r,val): return self.jl.JLINKARM_WriteReg(r,val) 146 | @check_err 147 | def write_U32(self,r,val): return self.jl.JLINKARM_WriteU32(r,val) 148 | @check_err 149 | def open(self): return self.jl.JLINKARM_Open() 150 | @check_err 151 | def close(self): return self.jl.JLINKARM_Close() 152 | @check_err 153 | def go(self): return self.jl.JLINKARM_Go() 154 | @check_err 155 | def write_mem(self,startaddress, data): 156 | buf=ctypes.create_string_buffer(data) 157 | return self.jl.JLINKARM_WriteMem(startaddress,len(data),buf) 158 | @check_err 159 | def read_mem(self, startaddress, length): 160 | buf=ctypes.create_string_buffer(length) 161 | ret=self.jl.JLINKARM_ReadMem(startaddress,length, buf) 162 | return buf,ret 163 | @check_err 164 | def read_mem_U32(self, startaddress, count): 165 | buftype=ctypes.c_uint32 * int(count) 166 | buf=buftype() 167 | ret=self.jl.JLINKARM_ReadMemU32(startaddress, count, buf, 0) 168 | return buf,ret 169 | 170 | # end of DLL functions 171 | 172 | def pinreset(self): 173 | """executes sequence from 174 | https://devzone.nordicsemi.com/question/18449 175 | """ 176 | self.write_U32(0x40000544, 1) 177 | self.tif_select(0) 178 | self.clear_tck() 179 | self.clear_tms() 180 | time.sleep(0.010) 181 | self.set_tms() 182 | 183 | def erase_minimal(self, memory): 184 | startaddress=min(s.startaddress for s in memory.segments) 185 | endaddress=max(s.startaddress+len(s.data) for s in memory.segments) 186 | binsize=endaddress-startaddress 187 | 188 | startaddr=startaddress&~511 189 | endaddr=startaddr+512*(1+((binsize-1)//512)) 190 | self.erase_partial(startaddr,endaddr) 191 | 192 | def erase_partial(self, startaddr, endaddr): 193 | print "erasing from 0x%x->0x%x"%(startaddr,endaddr) 194 | 195 | self.write_U32(CONFIG,CONFIG_EEN) 196 | for i in range(startaddr, endaddr, 512): 197 | print("0x%x (%d%%)\r"%(i,100*(i-startaddr)/(endaddr-startaddr))), 198 | sys.stdout.flush() 199 | self.write_U32(ERASEPAGE,i) 200 | self._wait_ready() 201 | 202 | self.write_U32(CONFIG,CONFIG_REN) 203 | self._wait_ready() 204 | 205 | def erase_all(self): 206 | self.write_U32(CONFIG, CONFIG_EEN) 207 | self.write_U32(ERASEALL,1) 208 | self._wait_ready() 209 | self.write_U32(CONFIG, CONFIG_REN) 210 | self._wait_ready() 211 | 212 | def _wait_ready(self): 213 | while 1: 214 | ready,_=self.read_mem_U32(READY,1) 215 | if ready[0]: break 216 | 217 | def make_dump(self,startaddress,endaddress,name): 218 | x,y=self.read_mem(startaddress, endaddress-startaddress) 219 | file('%s.bin'%name,'w').write(x[:]) 220 | 221 | def burn(self, memory): 222 | self.halt() 223 | #self.make_dump(0xa000,0x27c00,"before") 224 | self.erase_minimal(memory) 225 | #self.make_dump(0xa000,0x27c00,"erased") 226 | return self._burn_internal(memory.segments) 227 | 228 | def _burn_internal(self, segments): 229 | 230 | self.write_U32(CONFIG, CONFIG_WEN) 231 | self._wait_ready() 232 | startaddress=min(s.startaddress for s in segments) 233 | endaddress=max(s.startaddress+len(s.data) for s in segments) 234 | 235 | #startaddress=0 236 | 237 | if 0: 238 | mem_map=['\xff']*(endaddress-startaddress) 239 | 240 | for s in segments: 241 | s0=s.startaddress-startaddress 242 | s1=s0+len(s.data) 243 | mem_map[s0:s1]=s.data[:] 244 | 245 | print "writing from 0x%x->0x%x"%(startaddress, 246 | startaddress+len(mem_map)) 247 | self.write_mem(startaddress,''.join(mem_map)) 248 | 249 | else: 250 | for s in segments: 251 | print "writing from 0x%x->0x%x"%(s.startaddress, 252 | s.startaddress+len(s.data)) 253 | self.write_mem(s.startaddress,s.data) 254 | 255 | #self.make_dump(0xa000,0x27c00,"written") 256 | 257 | self.reset() 258 | 259 | self.go() 260 | #time.sleep(5) 261 | 262 | self.pinreset() 263 | 264 | return 265 | 266 | 267 | def burnfile(self,filename): 268 | print 269 | print "map of %s:"%filename 270 | progdata=load_elf(filename) 271 | print_mem_map(progdata) 272 | print 273 | return self.burn(progdata) 274 | 275 | def burnsoftdevice(self,softdevice_filename): 276 | mem=load_elf(softdevice_filename) 277 | print_mem_map(mem) 278 | print 279 | 280 | self.halt() 281 | self.erase_all() 282 | self.reset() 283 | self.halt() 284 | self._burn_internal(mem.segments) 285 | 286 | def auto_program(self, filename): 287 | mem=load_elf(filename) 288 | print_mem_map(mem) 289 | self.halt() 290 | self._burn_internal(mem.segments) 291 | 292 | 293 | def load_elf(filename): 294 | progdata=memory.Memory() 295 | progdata.loadFile(filename) 296 | return progdata 297 | 298 | if __name__=="__main__": 299 | jl=JLink() 300 | -------------------------------------------------------------------------------- /jlink/lib32/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markrages/jlinkpy/402195972e1592d0f4a3750e457c3e84de0e406f/jlink/lib32/.gitkeep -------------------------------------------------------------------------------- /jlink/lib64/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markrages/jlinkpy/402195972e1592d0f4a3750e457c3e84de0e406f/jlink/lib64/.gitkeep -------------------------------------------------------------------------------- /jlink/memory.py: -------------------------------------------------------------------------------- 1 | # $Id: memory.py,v 1.4 2008/05/22 16:20:02 cliechti Exp $ 2 | import sys 3 | import elf 4 | 5 | DEBUG = 0 6 | 7 | class FileFormatError(IOError): 8 | """file is not in the expected format""" 9 | 10 | 11 | class Segment: 12 | """store a string with memory contents along with its startaddress""" 13 | def __init__(self, startaddress = 0, data=None): 14 | if data is None: 15 | self.data = '' 16 | else: 17 | self.data = data 18 | self.startaddress = startaddress 19 | 20 | def __getitem__(self, index): 21 | return self.data[index] 22 | 23 | def __len__(self): 24 | return len(self.data) 25 | 26 | def __repr__(self): 27 | return "Segment(startaddress=0x%04x, data=%r)" % (self.startaddress, self.data) 28 | 29 | class Memory: 30 | """represent memory contents. with functions to load files""" 31 | def __init__(self, filename=None): 32 | self.segments = [] 33 | if filename: 34 | self.filename = filename 35 | self.loadFile(filename) 36 | 37 | def append(self, seg): 38 | self.segments.append(seg) 39 | 40 | def __getitem__(self, index): 41 | return self.segments[index] 42 | 43 | def __len__(self): 44 | return len(self.segments) 45 | 46 | def __repr__(self): 47 | return "Memory:\n%s" % ('\n'.join([repr(seg) for seg in self.segments]),) 48 | 49 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 50 | 51 | def loadIHex(self, file): 52 | """load data from a (opened) file in Intel-HEX format""" 53 | segmentdata = [] 54 | extSegAddr = 0 55 | currentAddr = 0 56 | startAddr = 0 57 | lines = file.readlines() 58 | for l in lines: 59 | if not l.strip(): continue #skip empty lines 60 | if l[0] != ':': raise FileFormatError("line not valid intel hex data: '%s...'" % l[0:10]) 61 | l = l.strip() #fix CR-LF issues... 62 | length = int(l[1:3],16) 63 | address = int(l[3:7],16) + extSegAddr 64 | type = int(l[7:9],16) 65 | check = int(l[-2:],16) 66 | if type == 0x00: 67 | if currentAddr != address: 68 | if segmentdata: 69 | self.segments.append( Segment(startAddr, ''.join(segmentdata)) ) 70 | startAddr = currentAddr = address 71 | segmentdata = [] 72 | for i in range(length): 73 | segmentdata.append( chr(int(l[9+2*i:11+2*i],16)) ) 74 | currentAddr = length + currentAddr 75 | elif type == 0x02: 76 | data=int(l[9:9+4],16) 77 | extSegAddr = 16 * data 78 | #print "2: Assign extSegAddr %x"%data 79 | elif type == 0x04: 80 | data=int(l[9:9+4],16) 81 | extSegAddr = 65536 * data 82 | #print "4: Assign extLinAddr %x"%data 83 | pass 84 | elif type == 0x03: 85 | data=int(l[9:9+length*2],16) 86 | self.applicationStartAddress=data 87 | pass 88 | elif type == 0x05: 89 | data=int(l[9:9+4*2],16) 90 | self.applicationStartAddress=data 91 | #print "5: Startaddr %x"%data 92 | pass 93 | elif type == 0x01: # EOF 94 | pass 95 | else: 96 | sys.stderr.write("Ignored unknown field (type 0x%02x) in ihex file.\n" % type) 97 | if segmentdata: 98 | self.segments.append( Segment(startAddr, ''.join(segmentdata)) ) 99 | 100 | def loadSRec(self, file): 101 | """load data from a (opened) file in S19 format""" 102 | startAddr = 0 103 | segmentdata = [] 104 | #Convert data for MSP430, TXT-File is parsed line by line 105 | for line in file: #Read one line 106 | if not line: break #EOF 107 | l = line.strip() 108 | if l[0] != 'S': raise FileFormatError('Not S-record file') 109 | if l[1] == '0': pass # manufacturer-specific 110 | elif l[1] in ['1','2','3']: 111 | addrwidth = {'1':2,'2':3,'3':4}[l[1]] 112 | newStartAddr = int(l[4:4+2*addrwidth],16) 113 | 114 | #print "new segment?",startAddr,"+",len(segmentdata),newStartAddr 115 | if newStartAddr != startAddr + len(segmentdata): 116 | #print "ok" 117 | #create a new segment 118 | self.segments.append( Segment(startAddr, ''.join(segmentdata)) ) 119 | segmentdata=[] 120 | 121 | startAddr=newStartAddr 122 | count = int(l[2:4],16) 123 | 124 | x=4+addrwidth*2 125 | for c in range(count-addrwidth-1): 126 | #print count,addrwidth,c 127 | segmentdata.append(chr(int(l[x+c*2:x+c*2+2],16))) 128 | 129 | elif l[1] in ['7','8','9']: 130 | pass # end of block 131 | else: 132 | raise Exception('S%s unimplemented'%l[1]) 133 | 134 | if segmentdata: 135 | self.segments.append( Segment(startAddr, ''.join(segmentdata)) ) 136 | 137 | def loadTIText(self, file): 138 | """load data from a (opened) file in TI-Text format""" 139 | startAddr = 0 140 | segmentdata = [] 141 | #Convert data for MSP430, TXT-File is parsed line by line 142 | for line in file: #Read one line 143 | if not line: break #EOF 144 | l = line.strip() 145 | if l[0] == 'q': break 146 | elif l[0] == '@': #if @ => new address => send frame and set new addr. 147 | #create a new segment 148 | if segmentdata: 149 | self.segments.append( Segment(startAddr, ''.join(segmentdata)) ) 150 | startAddr = int(l[1:],16) 151 | segmentdata = [] 152 | else: 153 | for i in l.split(): 154 | try: 155 | segmentdata.append(chr(int(i,16))) 156 | except ValueError, e: 157 | raise FileFormatError('File is no valid TI-Text (%s)' % e) 158 | if segmentdata: 159 | self.segments.append( Segment(startAddr, ''.join(segmentdata)) ) 160 | 161 | def loadELF(self, file): 162 | """load data from a (opened) file in ELF object format. 163 | File must be seekable""" 164 | obj = elf.ELFObject() 165 | obj.fromFile(file) 166 | if obj.e_type != elf.ELFObject.ET_EXEC: 167 | raise Exception("No executable") 168 | for section in obj.getSections(): 169 | if DEBUG: 170 | sys.stderr.write("ELF section %s at 0x%04x %d bytes\n" % (section.name, section.lma, len(section.data))) 171 | if len(section.data): 172 | self.segments.append( Segment(section.lma, section.data) ) 173 | 174 | def loadFile(self, filename, fileobj=None): 175 | """fill memory with the contents of a file. file type is determined from extension""" 176 | close = 0 177 | if fileobj is None: 178 | fileobj = open(filename, "rb") 179 | close = 1 180 | try: 181 | #first check extension 182 | try: 183 | if filename[-4:].lower() == '.txt': 184 | self.loadTIText(fileobj) 185 | return 186 | elif filename[-4:].lower() in ('.a43', '.hex'): 187 | self.loadIHex(fileobj) 188 | return 189 | elif filename[-5:].lower() in ('.srec'): 190 | self.loadSRec(fileobj) 191 | return 192 | except FileFormatError: 193 | pass #do contents based detection below 194 | #then do a contents based detection 195 | try: 196 | self.loadELF(fileobj) 197 | except elf.ELFException: 198 | fileobj.seek(0) 199 | try: 200 | self.loadIHex(fileobj) 201 | except FileFormatError: 202 | fileobj.seek(0) 203 | try: 204 | self.loadTIText(fileobj) 205 | except FileFormatError: 206 | raise FileFormatError('file could not be loaded (not ELF, Intel-Hex, or TI-Text)') 207 | finally: 208 | if close: 209 | fileobj.close() 210 | 211 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 212 | def saveIHex(self, filelike): 213 | """write a string containing intel hex to given file object""" 214 | noeof=0 215 | for seg in self.segments: 216 | address = seg.startaddress 217 | data = seg.data 218 | start = 0 219 | while start len(data): end = len(data) 222 | filelike.write(self._ihexline(address, data[start:end])) 223 | start += 16 224 | address += 16 225 | filelike.write(self._ihexline(0, [], end=1)) #append no data but an end line 226 | 227 | def _ihexline(self, address, buffer, end=0): 228 | """internal use: generate a line with intel hex encoded data""" 229 | out = [] 230 | if end: 231 | type = 1 232 | else: 233 | type = 0 234 | out.append( ':%02X%04X%02X' % (len(buffer),address&0xffff,type) ) 235 | sum = len(buffer) + ((address>>8)&255) + (address&255) + (type&255) 236 | for b in [ord(x) for x in buffer]: 237 | out.append('%02X' % (b&255) ) 238 | sum += b&255 239 | out.append('%02X\r\n' %( (-sum)&255)) 240 | return ''.join(out) 241 | 242 | def saveTIText(self, filelike): 243 | """output TI-Text to given file object""" 244 | for segment in self.segments: 245 | filelike.write("@%04x\n" % segment.startaddress) 246 | for i in range(0, len(segment.data), 16): 247 | filelike.write("%s\n" % " ".join(["%02x" % ord(x) for x in segment.data[i:i+16]])) 248 | filelike.write("q\n") 249 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 250 | 251 | def getMemrange(self, fromadr, toadr): 252 | """get a range of bytes from the memory. unavailable values are filled with 0xff.""" 253 | res = '' 254 | toadr = toadr + 1 #python indexes are excluding end, so include it 255 | while fromadr < toadr: 256 | for seg in self.segments: 257 | segend = seg.startaddress + len(seg.data) 258 | if seg.startaddress <= fromadr and fromadr < segend: 259 | if toadr > segend: #not all data in segment 260 | catchlength = segend - fromadr 261 | else: 262 | catchlength = toadr - fromadr 263 | res = res + seg.data[fromadr-seg.startaddress : fromadr-seg.startaddress+catchlength] 264 | fromadr = fromadr + catchlength #adjust start 265 | if len(res) >= toadr-fromadr: 266 | break #return res 267 | else: #undefined memory is filled with 0xff 268 | res = res + chr(255) 269 | fromadr = fromadr + 1 #adjust start 270 | return res 271 | 272 | def getMem(self, address, size): 273 | """get a range of bytes from the memory. a ValueError is raised if 274 | unavailable addresses are tried to read""" 275 | data = [] 276 | for seg in self.segments: 277 | #~ print "0x%04x " * 2 % (seg.startaddress, seg.startaddress + len(seg.data)) 278 | if seg.startaddress <= address and seg.startaddress + len(seg.data) >= address: 279 | #segment contains data in the address range 280 | offset = address - seg.startaddress 281 | length = min(len(seg.data)-offset, size) 282 | data.append(seg.data[offset:offset+length]) 283 | address += length 284 | value = ''.join(data) 285 | if len(value) != size: 286 | raise ValueError("could not collect the requested data") 287 | return value 288 | 289 | def setMem(self, address, contents): 290 | """write a range of bytes to the memory. a segment covering the address 291 | range to be written has to be existent. a ValueError is raised if not 292 | all data could be written (attention: a part of the data may have been 293 | written!)""" 294 | #~ print "%04x: %r" % (address, contents) 295 | for seg in self.segments: 296 | #~ print "0x%04x " * 3 % (address, seg.startaddress, seg.startaddress + len(seg.data)) 297 | if seg.startaddress <= address and seg.startaddress + len(seg.data) >= address: 298 | #segment contains data in the address range 299 | offset = address - seg.startaddress 300 | length = min(len(seg.data)-offset, len(contents)) 301 | seg.data = seg.data[:offset] + contents[:length] + seg.data[offset+length:] 302 | contents = contents[length:] #cut away what is used 303 | if not contents: return #stop if done 304 | address += length 305 | raise ValueError("could not write all data") 306 | -------------------------------------------------------------------------------- /nrfjprog: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | """ 4 | 5 | An implementation of the Nordic "nrfjprog.exe" program 6 | using python and jlink.py. 7 | 8 | See README.md for installation and more information. 9 | """ 10 | 11 | from jlink import jlink 12 | import sys 13 | 14 | if __name__=="__main__": 15 | _jl=None 16 | def jl(): 17 | global _jl 18 | _jl = _jl or jlink.JLink() 19 | return _jl 20 | 21 | args = sys.argv[1:] 22 | 23 | while (args): 24 | arg = args.pop(0) 25 | 26 | if arg=="--erase": 27 | jl().halt() 28 | #jl().erase_partial(0xA000,256*1024) 29 | jl().erase_all() 30 | 31 | elif arg=="--reset": 32 | jl().halt() 33 | jl().reset() 34 | 35 | elif arg=="--program": 36 | filename = args.pop(0) 37 | jl().auto_program(filename) 38 | 39 | elif arg=="--dumpfile": 40 | startaddr = int(args.pop(),base=0) 41 | endaddr = int(args.pop(),base=0) 42 | filename = args.pop(0) 43 | jl().halt() 44 | print 'Dumping %x -> %x to file "%s"'%(startaddr, 45 | endaddr, 46 | filename) 47 | jl().make_dump(startaddr,endaddr,filename) 48 | 49 | else: 50 | print "unknown argument: ",arg 51 | sys.exit(-1) 52 | 53 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from distutils.core import setup 4 | 5 | setup(name='jlink', 6 | version='0.1', 7 | description='Python wrapper for jlink closed libraries', 8 | author='Mark Rages', 9 | author_email='markrages@gmail.com', 10 | url='https://github.com/markrages/jlinkpy', 11 | packages=['jlink'], 12 | package_data={'jlink': ['lib32/*','lib64/*']}, 13 | scripts=['nrfjprog'], 14 | ) 15 | --------------------------------------------------------------------------------