Advice on program structure

Go To Last Post
18 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi folks,
I have been programming in asm for some time.(15 years). The projects I have been involved in have always been fairly small so I have gotten away with
minimal structure in my code. I have always been able to produce functional results. What I am looking for is advice on how to write code that is properly stuctured and easily reusable. I know this post is very general but any tips would helpfull.

Thanks,

A

AVR Studio 4 Ver. 4.18 684
avr-gcc Ver. 4.3.0
ISIS 7
ELECTRA

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Your best bet is to re-visit some of your old programs.

Then examine each subroutine and document it thoroughly for its parameters, function, side-effects, use of resources.

Place each documented subroutine into a separate source file.

Re-assemble, link and check functionality.

After a few iterations of this you will decide that it is easier to write code that needs minimal documentation. e.g. minimal side-effects, minimal use of unnecessary registers etc.

But by far your best approach is to read some neat structured code from say K & R. There is also a lot of well-written code in the public domain.

The desired effect is to have relatively small subroutines / functions that are logical and neatly laid out. Your main function logic can probably be condensed into:

void main()
{
   initialise();
   while (1) {
       process_logic();
       do_a_few_things();
   }
}
   

Unfortunately much microcontroller code has a lot of pre-processor conditionals that muddy the water. But your "structuring" process will possibly show you how to consolidate this dross away from your main logic.

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

David,
I don't have too much difficulty with stucture in C. When I use C it seems to document itself for some reason. My isuue is with asm. I can produce workable code but I always end up moving data from registers to free up space at the beginning of a call. Is there some method that deals with the clearing of registers at the start of a routine when it is called? Do you just throw all the regs you need on to the stack? How do decide if makes more sense to use ram instead of registers for a given call?

A

AVR Studio 4 Ver. 4.18 684
avr-gcc Ver. 4.3.0
ISIS 7
ELECTRA

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If you know C why bother with Asm? ;)

[After all C is just effectively a macro language wrapper around Asm anyway.]

Cliff

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson,
Now you have gone and done it :)

I was really looking to learn something. The debate over C vs asm has been covered by those with more smarts than me but I bet it will be mentioned again before this day is through.
The reason I am using asm at this time has to do with the need to capture some data on a port using an interrupt as quickly as possible. (ie. capturing the byte from within the vector table) I can do this in asm but do not have the background to do it in C.

I am still interested in learning the practices around good asm programming if you care to add.

Thanks,

A

AVR Studio 4 Ver. 4.18 684
avr-gcc Ver. 4.3.0
ISIS 7
ELECTRA

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

I can do this in asm but do not have the background to do it in C.

So write the ISR in Asm and the rest of the program in C?

(a decent toolchain will allow you to do this (he said trying to trigger a different kind of war! ;)))

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson,

I have intrest in a lot of things but I have no interest in the GCC asm interface. I think I would rather write code in hex.

A

AVR Studio 4 Ver. 4.18 684
avr-gcc Ver. 4.3.0
ISIS 7
ELECTRA

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

And I wasn't suggest you try and do battle with the archaic ad impenetrable in-line asm() interface either.

Just write your asm for the ISRs in a completely separate .S file - very clean and easy and apart from a few differences in the .directives not really any different to writing in Atmel Asm2 except that the code is relocating and linkable so can share C data and code interfaces.

For an example see the user manual:

http://www.nongnu.org/avr-libc/u...

and in particular the GCC asm source example:

http://www.nongnu.org/avr-libc/e...

(does that really look very much different to anything you may have written in the Atmel assembler?)

If you include a "main:" in the asm code and make it .global then you don't need ANY .c files in the project and can just use avr-as to write complete Asm only projects.

Manual for the assembler is here by the way:

http://sourceware.org/binutils/d...

Oh and the bit that makes it AVR specific is:

http://sourceware.org/binutils/d...

Last Edited: Mon. Jan 5, 2009 - 04:31 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson,
I will view the link.

A

AVR Studio 4 Ver. 4.18 684
avr-gcc Ver. 4.3.0
ISIS 7
ELECTRA

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Andrew,

I added some more detail to my post above

Cliff

PS while foostering around there I stumbled across this page:

http://sourceware.org/binutils/d...

I'd not noticed that before - it's a very useful list of all the AVR opcode bit patterns - not immediately useful in this topic but I'm sure others before me have longed to find a list like this from time to time.

Last Edited: Mon. Jan 5, 2009 - 04:38 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Cliff,

Thanks again.

A

AVR Studio 4 Ver. 4.18 684
avr-gcc Ver. 4.3.0
ISIS 7
ELECTRA

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
I have intrest in a lot of things but I have no interest in the GCC asm interface. I think I would rather write code in hex.

I am with you on that one !

Seriously, the way to tidy your ASM structure is to apply the same rules as with C :

1. reduce global variables
2. make your subroutines as "independent" as possible. e.g. preserve any non-scratch registers.
3. you will dislike documentation. so reduce the need for it. e.g. the subroutine just needs a name and input parameters / output returned.

4. your ASM may lose a little efficiency. You will have to obey some "self-imposed" rules.

5. At the end of the day, you or someone else might be able to read your program structure.

6. I have never seen any book on ASM style. I can only assume that any author who dared would be torn to shreds by the likely audience.

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

David,

This audience anyway.

Is there a typical way to pass data to a routine?
Is there a best practices way to this?
Do most people push the regs they intend to use?

A

AVR Studio 4 Ver. 4.18 684
avr-gcc Ver. 4.3.0
ISIS 7
ELECTRA

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The common methods are:

1. Pass values in selected registers.
2. Pass values via common memory locations.
3. Pass values on the stack.

Return values:

1. in selected registers
2. via a common memory area
3. via the stack.

Personally I would choose method 1 for both. But you can always look to see how a C compiler does things.

Traditionally they did everything via a stack frame. Most modern compilers will use a limited number of registers, only using the stack when they have used all the registers.

The AVR was designed with C in mind. So you have Y register for your stack frame, X for SRAM and Z for flash. Then certain register pairs can do simple 16 bit operations. So you make good use of these while you use the less handy registers for global storage.

Yes, you can stack registers or possibly use a global scratch area for temporary saves. Everything has its associated costs and benefits. And of course everyone has her own preferred style.

All you can do is copy methods that appeal to you. Nowadays there is not that much ASM source to read.

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Another aspect of program organization that I think could stand some attention is how you go about packaging code that you expect to reuse. I'm a big, big proponent of a "One-stop Shopping" approach to packaging. The routines and features of a TWI send/receive package coded this way should be useable in a turnkey fashion after doing just one thing: ".include"-ing the appropriate header or sourcecode file. If you're using Atmel's linkerless, "all source code concatenated into one giant source stream, then assembled" assembler, then these "library code module" files will contain the actual code of the library concerned. If you're using third-party tools that can link separately-assembled object modules, then the "include" files look more like C-language headers - just function and global variable declarations. But whichever you use, something I like to strive for is to package any initializing or setup sequences in such a way that it gets automatically added to the list of startup operations, without needing to find and manually edit some sequence of

    rcall initializeThis
    rcall initializeThat
    rcall initializeSomethingElse

. If, from within some module or other, I decide I need to interoperate with a TWI-connected peripheral, the only thing that module should need to do is ".include TWImaster.inc", and then make calls on "twiTransact". I shouldn't have to go scurrying off to some potentially other "startup" module and add a new "rcall twiInit".

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

Personally I would choose method 1 for both. But you can always look to see how a C compiler does things.

Just for interests sake, this is how GCC does it:

http://www.nongnu.org/avr-libc/u...

It also makes use of just one stack (the hardware one) for both return addresses and stack frame variables though it will use Y to point to the base of such a stack frame

Other C compilers (certainly ICC, maybe others) use two separate stacks - one use the hardware SP for return addresses and a soft stack for holding data that is separate.

Of course you don't HAVE to have the concept of "local " stack frame based variables at all in an Asm program you write - a common technique is to hold a subset of the registers as reusable "locals" and maybe also set aside one "buffer"/array that you re-use. However problems occur when you call a routine that starts to use these "locals" and then it, itself, calls another routine that also needs to use them. This is why C does it on a stack - so each nested routine can create/use it's own set of locals.

Cliff

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
Of course you don't HAVE to have the concept of "local " stack frame based variables at all in an Asm program you write - a common technique is to hold a subset of the registers as reusable "locals" and maybe also set aside one "buffer"/array that you re-use.

You just select suitably naff names for these parameter passing registers. Their sole purpose is to just live long enough for passing a value. TEMP1 or PARAM1 strike me as likely candidates.

Of course any self-respecting ASM programmer will then attempt to extend their short-life. If they are clever, success and a smug feeling. In the event that Cliff has suggested, that name choice serves as a useful caution.

Other techniques for re-use of memory for local variables require some serious call-depth-analysis. Nice work for a compiler but hard work for humans.

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Oh, and make sure your temp registers/variables are treated as local-to-the-function temporary values. Do NOT use them to pass parameters to another function, even if "it's only called from this one place". (Voice of experience here -- others did it, and I have to maintain it. :< )

--Rich