Extreme Programming Bare Machine

Many small embedded projects start with a bare machine because there isn't enough room for an operating system or much of a runtime environment. (the one I'm working on has 32k of RAM and 256K of code space). I am trying to at least do UnitTests, but much of the code I'm writing seems to want to talk directly to real hardware (TooMuchGuiCode?). Any suggestions?


Several XP practices in addition to UnitTests are quite useful in the bare machine mode:

Not always. I managed to clobber a register in an interrupt handler once on a different project, and it very rarely broke anything visible. Hm, this seems to mean the interrupts need UnitTests to make sure the registers don't change.

Those are the main ones that come to my tiny mind ... what else, folks?


I don't do bare machine programming, so this probably will not work: Use indirection plus code generation to do tests.

Use a level of indirection - if there are 17 calls to hardware, use 17 wrapper functions that just pass stuff through to the hardware. see below

Use code generation - each unit test autogenerates (or at least loads or links) "test versions" of the 17 wrapper functions. The autogenerated wrapper functions ignore the hardware and do hardwired things or return hardwired results.


This suggests doing software simulation of the hardware. Because the instructions to address hardware are usually different than the function calls necessary for software simulation, you'd probably have to use macros (C "#define" statements) to switch between "use hardware" and "use simulation" versions. -- JeffGrigg


Working in small steps is important when you don't get lots of feedback from the system, as is often the case when working on small systems (ah, the memories). I try to do small pieces; it's sometimes hard to see much happening then though. Much of XP comes from watching my dad build process control software for 8-bit microprocessors. Get a little tiny thing working with automated tests. Make one tiny change and run all the tests. If they don't work, don't try to fix the problem, just go back and try again a slightly different way. --KentBeck


A typical piece of code that looks hard to test as it stands is something like

 serial1_int() interrupt S1 using 1 {
   if(S1CON & S1CON_RF) { /* receive buffer has a character */
     register char c=S1BUF;
     /* test c for XON/XOFF control characters, handle appropriately */
     qputn(&s1.q,c)   /* add to the queue, not blocking if queue is full */
     S1CON &= ~S1CON_RF;  /* clear the interrupt flag */
   }
   /* handle transmit case here */
 }

Right now I have some code that waits for characters to come in the serial port and sends them back out. This gives me some confidence in the serial code, and I can test everything there by hand.

The easy-to-test code that uses no peripherals gets linked into an object file along with a test driver and the scheduler, and a batch file invokes the emulator with a script that loads the object, sets up tracing of a few memory locations, and starts emulation. Each test case gets an approximately freshly reset machine and signals success or failure by calling a function that writes a log message to some of the memory locations being logged, then approximates a reset. After the emulator exits under control of the test program, a little C program on the PC turns the logfile into something more useful.

With that scheme, all the test cases can share the PC-side scripts, but no hardware is simulated.

Hmm, if I add some indirection to that interrupt, I get this:

 serial1_int() INTERRUPT(S1) {
   if(S1CON & S1CON_RF) { /* receive buffer has a character */
     register char c=S1BUF;
     /* test c for XON/XOFF control characters, handle appropriately */
     qputn(&s1.q,c)   /* add to the queue, not blocking if queue is full */
     WRITE_S1CON(S1CON & ~S1CON_RF);  /* clear the interrupt flag */
   }
   /* handle transmit case here */
 }

and then I could define all the indirection somewhere central. I would then have to make sure to get all the sources compiled the right way depending on whether I'm unit-testing or trying to build something functional. I'll try it and see what happens.


I know this sounds very strange, but LearnMorseCode?. You heard me right -- learn it!

Why, you might ask?

Because on most microcontrollers, you can easily code something which blinks an LED over time. Such code can be written without unit tests, they're that simple. And, since all Morse code digits consume only 15 bits (each numeral has five symbols, with a 'dah' being 3 times as long as a 'dit'. If a dit is encoded as 001 then a dah must be encoded as 111 in binary), memory usually isn't a problem at all, and complexity is minimized by using BCD-encoded values as indices into a look-up table. The sequence is pretty simple too:

 0 -----  | 5 .....
 1 .----  | 6 -....
 2 ..---  | 7 --...
 3 ...--  | 8 ---..
 4 ....-  | 9 ----.

If this is starting to look like those engine codes you read back from car automotive computers by toggling the ignition switch the right way, it should -- the concept is exactly the same.

Now that you have some means of communicating intelligence to the user, you can engineer a simple unit test framework with the trait that it transmits (in Morse code) the ID number of the unit test that has just failed, or the letter P (.--.) if everything passes. I would recommend keeping the Morse code rate between 8 and 12 "words per minute" -- this is slow enough for most people to read the numbers coming from the LED (or speaker, or piezo buzzer, or any other kind of on/off transducer), while not being too slow (transmitting "PARIS" at 5wpm is unbearably slow, even for most morse code novices. Imagine taking 60 seconds to send PARIS, back to back, five times. BTW, that's how "wpm" is derived with Morse code!).

Tips:

--SamuelFalvo?


CategoryMachineOrientation


EditText of this page (last edited December 9, 2014) or FindPage with title or text search