A couple of posts the other day made me think about something.
First someone posted to say that when debugging with gdb they were a bit disappointed that all the symbols in io.h don't make it through to the debugging. This is, of course, because they are preprocessor defines, not C symbols.
Then in another thread Morten mentioned that the XML files in AS6/7 had recently been changed from .xml to .atdf extension. Now I always knew those files existed and I always knew that (amongst other things) they define the layout of the AVR but I'd never really looked at them in any detail.
But recently my day job has involved me learning Python and, in particular using it to write C++ code generators taking XML as input.
So I threw this together...
######################################################################################################### # AVRRead # # A utility to read the XML files (probably .atdf in fact) in a Studio 6/7 installation and use # # the data within to define one big structure to overlay the SFR area of any AVR. Just use: # # # # python avrread.py -i ATmgea16.atdf # # # # or any other .atdf to generat an ATmega16.h (or whatever) header file for use in programming the # # AVR. In this case don't use "#include <avr/io.h>" but instead just do something like this: # # # # #include "ATmega16.h" # # # # USE_SFRS(pSFR); # # # # int main(void) # # { # # pSFR->DDRB.byte = 0xFF; # # pSFR->UCSRB.bits.TXEN = 1; # # while (1) # # { # # pSFR->PORTB.byte ^= (1 << 3); # # } # # } # # # # Use the USE_SFR() macro to name a struct pointer variable (like "pSFR") that you then want to # # use to access the registers in the code. # # # # by Cliff Lawson # # # # Licence: I need beer - see what you can do! # ######################################################################################################### # following ("from...") line is useful if you are trying to make Python 3 code run in Python 2 # (however things like "argparse" here means this program is 3.2+ anyway. # from __future__ import print_function import sys import os import argparse import xml.etree.ElementTree as et # found this on StackOverflow - it simply returns the lowest bit that is set in a byte def lowestSet(int_type): low = (int_type & -int_type) lowBit = -1 while (low): low >>= 1 lowBit += 1 return(lowBit) # often find this useful to check the raw command line args # for n in range(0, len(sys.argv)): # print(n, sys.argv[n]) # I like argparse - it makes for very clean command line interfaces parser = argparse.ArgumentParser(description='Read Atmel XML (version 1.0)') parser.add_argument("-i", "--input", dest='in_fname', help="name of .XML file to read as input", required=True) parser.add_argument("-o", "--output", dest='out_name', help="Name of output file (overides default)") parser.add_argument("-q", "--quiet", dest='quiet', action="store_true", help="Don't print to console") parser.add_argument("-v", "--verbose", dest='verbose', action="store_true", help="Show developer info") # my one argument with argparse is that if you run the app without args it doesn't show help info, so # this will achieve that... if len(sys.argv) == 1: parser.print_help() sys.exit(1) # this actually runs the argument parser on sys.argv args = parser.parse_args() # the user has the opportunity to use -o to set an output filename but if they haven't used that this # takes the input .atdf/.xml filename and replaces the extension with ".h" to create the output name if args.out_name is None: args.out_name = os.path.splitext(args.in_fname)[0]+".h" # The following creates an empty list. As the XML is parsed this will be appened()'d too to build a complete # picture of the AVR layout as a list of dictionaries mainlist = [] # Assuming the user has given a name then do our thing! if args.in_fname is not None and os.path.isfile(args.in_fname): print("Creating:", args.out_name) # following two lines are the classic way to invoke ElementTree to read an XML then get access to the # root from which access to all other data then occurs tree = et.parse(args.in_fname) root = tree.getroot() # So the first thing I search for is the "memory-segment" entry with attribute name='MAPPED_IO' # this has the start/length of the SFRs (start is bound to be 0x20, it's the length I need to # later build the struct that covers the entire SFR region) io_node = root.find(".//memory-segment[@name='MAPPED_IO']") sfr_start = io_node.attrib['start'] sfr_size = io_node.attrib['size'] # The "interesting stuff" (as far as this program is concerned) is then the "modules" which are things # like "UART", "TIMER_COUNTER_1" and so on. modules = root.find("modules") # this then iterates over each module found... for mod in modules.findall("module"): # rather curiously there are two "modules" called "FUSE" and "LOCKBIT" - I want to ignore them... if mod.attrib['name'] in ['FUSE', 'LOCKBIT']: continue # To keep the user entertained with some output - print the name of each module as I find it... print("===============", mod.attrib['name'], "===============") # Now there's a load of data for each "module" that I'm not interested in. All I want are the registers # and bits and these appear under one or more "register-groups" rg = mod.find("register-group") # then in each register group iterate through the individual registers.. for reg in rg.findall("register"): # for each register pick out the useful bits of information addr = int(reg.attrib['offset'], 0) name = reg.attrib['name'] capt = reg.attrib['caption'] sz = int(reg.attrib['size']) # use the following to add extra detail if more than one byte involved xtra = "" if sz != 1: xtra = str(sz) + " bytes" print(name, "addr=", hex(addr), xtra, "// ", capt) # Have a look to see if there is a "bitfield" defined for this register bits = reg.findall("bitfield") # going to create a list of tuples (eventually sorted and duplicates removed) for any groups of bits bitlist = [] if len(bits) != 0: # if there is/are bitfields then work through each entry in turn for bit in bits: # int(x, 0) converts "0xNN" into an integer value - unfortunately the XML only holds these # as a "mask" so if it's bits 3,4,5,6 the mask will be 0x78 and so on - need to process this # later to get the lowest bit (3 in this example) and the starting bit position. For the # lowest bit set I found something useful on StackOverflow which is at the start of this file # for the number of bits set in a mask I found a very clever technique in another answer # on StackOverflow - you use bit() to convert the mask to a "10101.." string then use count('1') # on this to find out how many bits there are (as it happens they're always adjacent in fact) mask = int(bit.attrib['mask'], 0) bitlist.append((lowestSet(mask), bin(mask).count('1'), bit.attrib['name'], bit.attrib['caption'])) for n in sorted(bitlist): print(n) # now we assume we are going to this register/bits as a whole new entry in mainlist[]. However it turns # out the XML may have several different register/bit definitions in different places for the same # register - but you can spot this because you have already seen the address used. (BTW this all occurs # with a register like TIMSK that may have some TMR_0 bits defined under the section for "TIMER 0" but # then has some more bits defined later under "TIMER 1" and so on) do_add = 1 # so now we check to see if the address of the register we're currently looking at was already seen # and recorded in mainlist[]. If it has been then what we need to do is extract the existing "bitlist" # (which is a list of tuples), then append each new tuple we've just found to this. However that may # lead to duplicates. for n in mainlist: if n['addr'] == addr: # so pull the "bits" from the existing entry that was found to have the same address updated_bits = n['bits'] # then append each new bit entry tuple to this.. for entry in bitlist: do_add = 1 for eb in updated_bits: if entry[0] == eb[0]: do_add = 0 if do_add: updated_bits.append(entry) # I'll leave this (one of my development print()s as I found it MOST useful!) # print("YOIKS!", "now=", sorted(nodups)) # now search the entrie mainlist[] again to find the index (i) of the one where we found the # existing entry for the same address for i in range(0, len(mainlist)): # and when we stumble upon it.. if mainlist[i]['addr'] == addr: # replace what was there with new details including the sorted, duplicate removed list of bits mainlist[i] = {'addr': addr, 'name': name, 'size': sz, 'caption': capt, 'bits': sorted(updated_bits)} # as we've updated an existing entry we don't want to add the data as a new one so.. do_add = 0 # if the address has not occurred before then just add the details including the sorted list of bits - # it sort by default on the first item in the tuple which is the bit position if do_add: mainlist.append({'addr': addr, 'name': name, 'size': sz, 'caption': capt, 'bits': sorted(bitlist)}) # The order of the "modules" in the XML is arbitrary and does not follow address order so now we sort the mainlist # of dictionaries using the 'addr' field in each one. Again this clever technique came from Stack Overflow mainlist = sorted(mainlist, key=lambda k: k['addr']) # finally entertain the user with something interesting looking (good for debugging too!) print("\n++++++++++ All of that data from XML now stored as.... +++++++++++\n") for ent in mainlist: print(hex(ent['addr']), ent) # So we arrive here with mainlist[] fully populated with the complete info of all registers/bits stripped # from the XMLso and the list now ordered by address. Now it's time to generate some output regidx = 0 # remember the "MAPPED_IO" which was the first thing taken from the XML - this is where we use the sfr_start/size # we pulled from it at that time addr = int(sfr_start, 0) # this is just standard Python file IO - open xxx.h as a writable text file.. hdr = open(args.out_name, "wt") # the preamble is a fixed text so write that now... hdr.write("#include <stdint.h>\n\ntypedef struct {\n") # now we build a struct that will span sfr_start to sfr_start+sfr_size (remember int(x,0) converts "0xNN" to int) # Oh and if you are wondering why this is a while() loop and not a for() loop it's because in Python (I found # out the hard way!) you cannot use "for x in range(start,end)" and the modify x within the loop to skip some values # the range() builds a list at the very start and x will be set to every member in that list for each iteration # of the loop - updates to the iteration variable are over-written! while addr < (int(sfr_start, 0) + int(sfr_size,0)): # now for each address in the SFR range we see if the next mainlist[] entry has something for it if mainlist[regidx]['addr'] == addr: # if here then this address has an entry in mainlist[] so now the question is "is it a whole register or # is it a group of bits?". Whole registers are things like PORTB, ADC, OCR0 and so on, while registers with # (named) bits are things like UCSRA, TWCR and so on. For a whole register with anonymous bits then # just generate something like: # # union { # uint8_t byte; // (@ 0x32) Port D Data Register # struct { # int b0:1; # int b1:1; # int b2:1; # int b3:1; # int b4:1; # int b5:1; # int b6:1; # int b7:1; # } bits; # } PORTD; # # while for a register with named bits generate something like: # # union { # uint8_t byte; // (@ 0x2e) SPI Status Register # struct { # int SPI2X:1; // b0 Double SPI Speed Bit # int unused:5; // b1 # int WCOL:1; // b6 Write Collision Flag # int SPIF:1; // b7 SPI Interrupt Flag # } bits; # } SPSR; # # # If it is a whole register then it might be more than one byte so need to decide to uint8_t, uint16_t # and so on (I'm hoping they haven't got one defined as 'size':3 because that would lead to uint24_t # as I just multiply by 8!!) hdr.write("\tunion {\n\t\tuint" + str(8 * mainlist[regidx]['size']) + "_t byte; // (@ " + str(hex(addr)) + ") " + mainlist[regidx]['caption'] + "\n\t\tstruct {\n") # now for a whole register just write bN fields for the number of bits there are if len(mainlist[regidx]['bits']) == 0: for b in range(0, 8 * mainlist[regidx]['size']): hdr.write("\t\t\tint b" + str(b) + ":1;\n") else: # So this is the complicated bit when there are named bits defined bitpos = 0; for b in mainlist[regidx]['bits']: # We have tuples like (2, 5, 'FOO') which means FOO is at bit position 2 and spans 5 bits but # some of the structs have "gaps" that are unused and we need to fill these with a padding # entry to define "unusedN", the gap is padded using the following... if b[0] > bitpos: hdr.write("\t\t\tint unused"+ str(bitpos) + ":" + str(b[0] - bitpos) + "; // b" + str(bitpos) + "\n") # and step bitpos on to the bit position of the enrty we're about to write bitpos = b[0] # then the actual named "FOO:5" entry is created by this... hdr.write("\t\t\tint " + b[2] + ":" + str(b[1]) + "; // b" + str(b[0]) + " " + b[3] + "\n") bitpos += b[1] hdr.write("\t\t} bits;\n\t} " + mainlist[regidx]['name'] + ";\n") # following adds 0 for size:1 entries but is mainly here for multi-byte entries so that addr can be # stepped on for uint16_t registers and so on addr += int(mainlist[regidx]['size']) - 1 # now step the mainlist[] index on to the next entry regidx += 1 # this may look "odd" but it prevents regidx trying to index beyond the end of mainlist and setting it # to 1 is benign as "addr" has already moved on so there's no chance of it matching as it's now "behind" # (hope that makes sense!) if regidx >= len(mainlist): regidx = 1 else: # this just writes an unused0xNN entry for each byte that has nothing in mainlist[] hdr.write("\tuint8_t unused" + str(hex(addr)) + ";\n") addr += 1 # then just finish with the closing part of the struct{} definition and we're all done! :-) hdr.write("} SFRS_t;\n\n#define USE_SFRS(x) volatile SFRS_t * x = (SFRS_t *)" + sfr_start + ";") # BTW I wanted to call the whole thing "AVR" not "SFRS" but the compiler alredy defines "AVR" hdr.close() else: print("No valid input file")
If you dabble in Python then you should be able to write this to a .py file (mine is called avrread.py) and then simply run it with:
python avrread.py
If you do that it will say something like:
usage: avrread.py [-h] -i IN_FNAME [-o OUT_NAME] [-q] [-v] Read Atmel XML (version 1.0) optional arguments: -h, --help show this help message and exit -i IN_FNAME, --input IN_FNAME name of .XML file to read as input -o OUT_NAME, --output OUT_NAME Name of output file (overides default) -q, --quiet Don't print to console -v, --verbose Show developer info
The non-optional parameter there is -i so you actually run it with something like:
python avrread.py -i ATmega16.atdf
Where ATmega16.atdf is one of the XML files taken from the "packs" installation in your AS7 - pick anything you like the look of. When run it will produce output something like:
Creating: ATmega16.h =============== TIMER_COUNTER_0 =============== TCCR0 addr= 0x53 // Timer/Counter Control Register (0, 3, 'CS0', 'Clock Selects') (3, 1, 'WGM01', 'Waveform Generation Mode 1') (4, 2, 'COM0', 'Compare Match Output Modes') (6, 1, 'WGM00', 'Waveform Generation Mode 0') (7, 1, 'FOC0', 'Force Output Compare') TCNT0 addr= 0x52 // Timer/Counter Register OCR0 addr= 0x5c // Output Compare Register TIMSK addr= 0x59 // Timer/Counter Interrupt Mask Register (0, 1, 'TOIE0', 'Timer/Counter0 Overflow Interrupt Enable') (1, 1, 'OCIE0', 'Timer/Counter0 Output Compare Match Interrupt register') TIFR addr= 0x58 // Timer/Counter Interrupt Flag register (0, 1, 'TOV0', 'Timer/Counter0 Overflow Flag') (1, 1, 'OCF0', 'Output Compare Flag 0') SFIOR addr= 0x50 // Special Function IO Register (0, 1, 'PSR10', 'Prescaler Reset Timer/Counter1 and Timer/Counter0') =============== TIMER_COUNTER_1 =============== TIMSK addr= 0x59 // Timer/Counter Interrupt Mask Register (2, 1, 'TOIE1', 'Timer/Counter1 Overflow Interrupt Enable') (3, 1, 'OCIE1B', 'Timer/Counter1 Output CompareB Match Interrupt Enable') (4, 1, 'OCIE1A', 'Timer/Counter1 Output CompareA Match Interrupt Enable') (5, 1, 'TICIE1', 'Timer/Counter1 Input Capture Interrupt Enable') TIFR addr= 0x58 // Timer/Counter Interrupt Flag register (2, 1, 'TOV1', 'Timer/Counter1 Overflow Flag') (3, 1, 'OCF1B', 'Output Compare Flag 1B') (4, 1, 'OCF1A', 'Output Compare Flag 1A') (5, 1, 'ICF1', 'Input Capture Flag 1') TCCR1A addr= 0x4f // Timer/Counter1 Control Register A (0, 2, 'WGM1', 'Waveform Generation Mode') (2, 1, 'FOC1B', 'Force Output Compare 1B') (3, 1, 'FOC1A', 'Force Output Compare 1A') (4, 2, 'COM1B', 'Compare Output Mode 1B, bits') (6, 2, 'COM1A', 'Compare Output Mode 1A, bits') TCCR1B addr= 0x4e // Timer/Counter1 Control Register B (0, 3, 'CS1', 'Prescaler source of Timer/Counter 1') (3, 2, 'WGM1', 'Waveform Generation Mode') (6, 1, 'ICES1', 'Input Capture 1 Edge Select') (7, 1, 'ICNC1', 'Input Capture 1 Noise Canceler') TCNT1 addr= 0x4c 2 bytes // Timer/Counter1 Bytes OCR1A addr= 0x4a 2 bytes // Timer/Counter1 Output Compare Register Bytes OCR1B addr= 0x48 2 bytes // Timer/Counter1 Output Compare Register Bytes ICR1 addr= 0x46 2 bytes // Timer/Counter1 Input Capture Register Bytes =============== EXTERNAL_INTERRUPT =============== GICR addr= 0x5b // General Interrupt Control Register (0, 1, 'IVCE', 'Interrupt Vector Change Enable') (1, 1, 'IVSEL', 'Interrupt Vector Select') (5, 1, 'INT2', 'External Interrupt Request 2 Enable') (6, 2, 'INT', 'External Interrupt Request 1 Enable') etc.
that just shows it is reading the XML OK and picking the "interesting bits" from it (have a look at the Python if you want to know how). It then outputs a more "internal" representation of the data it has culled from the XML:
++++++++++ All of that data from XML now stored as.... +++++++++++ 0x20 {'name': 'TWBR', 'caption': 'TWI Bit Rate register', 'bits': [], 'size': 1, 'addr': 32} 0x21 {'name': 'TWSR', 'caption': 'TWI Status Register', 'bits': [(0, 2, 'TWPS', 'TWI Prescaler'), (3, 5, 'TWS', 'TWI Status')], 'size': 1, 'addr': 33} 0x22 {'name': 'TWAR', 'caption': 'TWI (Slave) Address register', 'bits': [(0, 1, 'TWGCE', 'TWI General Call Recognition Enable Bit'), (1, 7, 'TWA', 'TWI (Slave) Address register Bits')], 'size': 1, 'addr': 34} 0x23 {'name': 'TWDR', 'caption': 'TWI Data register', 'bits': [], 'size': 1, 'addr': 35} 0x24 {'name': 'ADC', 'caption': 'ADC Data Register Bytes', 'bits': [], 'size': 2, 'addr': 36} 0x26 {'name': 'ADCSRA', 'caption': 'The ADC Control and Status register', 'bits': [(0, 3, 'ADPS', 'ADC Prescaler Select Bits'), (3, 1, 'ADIE', 'ADC Interrupt Enable'), (4, 1, 'ADIF', 'ADC Interrupt Flag'), (5, 1, 'ADATE', 'When this bit is written to one,the Timer/Counter2 prescaler will be reset.The bit will be cleared by hardware after the operation is performed.Writing a zero to this bit will have no effect.This bit will always be read as zero if Timer/Counter2 is clocked by the internal CPU clock.If this bit is written when Timer/Counter2 is operating in asynchronous mode,the bit will remain one until the prescaler has been reset.'), (6, 1, 'ADSC', 'ADC Start Conversion'), (7, 1, 'ADEN', 'ADC Enable')], 'size': 1, 'addr': 38} etc.
and finally it does what it's supposed to and creates a .h file with the same name as the .atdf you used as input:
#include <stdint.h> typedef struct { union { uint8_t byte; // (@ 0x20) TWI Bit Rate register struct { int b0:1; int b1:1; int b2:1; int b3:1; int b4:1; int b5:1; int b6:1; int b7:1; } bits; } TWBR; union { uint8_t byte; // (@ 0x21) TWI Status Register struct { int TWPS:2; // b0 TWI Prescaler int unused2:1; // b2 int TWS:5; // b3 TWI Status } bits; } TWSR; union { uint8_t byte; // (@ 0x22) TWI (Slave) Address register struct { int TWGCE:1; // b0 TWI General Call Recognition Enable Bit int TWA:7; // b1 TWI (Slave) Address register Bits } bits; } TWAR; union { uint8_t byte; // (@ 0x23) TWI Data register struct { int b0:1; int b1:1; int b2:1; int b3:1; int b4:1; int b5:1; int b6:1; int b7:1; } bits; } TWDR;
and that's kind of the point. It has scoured the XML and produce a structure definition of the entire SFR are of the chosen AVR.
As the comment in the Python notes you can then use it like this...
#include "ATmega16.h" USE_SFRS(pSFR); int main(void) { pSFR->DDRB.byte = 0xFF; pSFR->UCSRB.bits.TXEN = 1; while (1) { pSFR->PORTB.byte ^= (1 << 3); } }
The key thing here being that you just #include the generated header and then invoke USE_SFR() and give the name for a structure pointer you would like to use.
Now when you type in Studio you will find things like this happening...
I am being pestered by cats as I type this but after I've been out for a walk I'll post some generated .h files for some popular AVRs.
Have fun and let me know what works/doesn't work or how you think it could be improved.