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 UnitTest''''''s, 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 UnitTest''''''s are quite useful in the bare machine mode: * DoTheSimplestThingThatCouldPossiblyWork has two aspects of applicability. Of course simple generally equals small, which is key on a tiny machine. More importantly, simple also equals easy to debug. These machines usually have weak debugging tools and simplicity is your only hope. * OnceAndOnlyOnce suggests the use of small and explicit code (whether objects or subroutines) to support all the required functionality. Since bare-machine code is likely to be wrong, centralizing each function lets you fix it OnceAndOnlyOnce. * ContinuousIntegration, the frequent release of very small increments of functionality, is very helpful in these hard-to-debug systems. If you release tiny changes, it's easy to identify what you did that just broke something. '''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 UnitTest''''''s to make sure the registers don't change.''' * YouArentGonnaNeedIt keeps you from putting in stuff that wastes space. Very important. * SpikeSolution, a simple test not linked with your main application, can be a great way to test an idea without wrecking everything. 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: * The test framework will ''halt'' on the first unit test failure. You won't get a compiled list of failures. You could code it to stream out test failures of course, but I don't recommend it. Fix one thing at a time. * If you're coding a system that has a "proper head," use the LED technique to headlessly unit-test the video display code, then switch to using the video display software for all remaining unit tests. * sUnit and its clones aren't the end-all, be-all of unit test frameworks. I have written CUT precisely because xUnit in C really "doesn't work," in that it doesn't fit the mechanical model of most C programming. Ditto for Forth, which is all-together different again. Here is some sample "headful" Forth code for unit testing in my Kestrel project (the former is the unit test "framework" itself, while the latter shows the result of using TDD to write the Kestrel's implementation of BLOCK, BUFFER, and friends, including my first use of mocks in a non-OO environment): ** http://www.falvotech.com/content/kestrel/2/firmware/repo/debugging/unit-test.fs ( the framework ) ** http://www.falvotech.com/content/kestrel/2/firmware/repo/runtime-environment/test-blocks.fs ( how it's used to write tests ) ** http://www.falvotech.com/content/kestrel/2/firmware/repo/kernel.fs ( the bootstrap and linkage into the boot sequence ) * Be sure to include 1 or 2 ''dit''-time (1 or 2 ''0'' bits) between morse code symbols, or else your 'zero' is going to sound/look like '''daaaaaaaaaaaaaaaaaaaaaaaaaah''' instead of '''dah dah dah dah dah.''' (Insert VW commercial here.) Between ''each digit'', however, include 3 or 6 ''dit'' times so that you can clearly identify the end of one digit and the next. Otherwise, a "01" will sound like '''dah dah dah dah dah dit dah dah dah dah''' instead of '''dah dah dah dah dah , dit dah dah dah dah'''. --SamuelFalvo ---- CategoryMachineOrientation