I work with a guy (who shall remain nameless) who has done some incredible things with what we would consider to be very low spec machines. These are industrial type computers and they've been used to mobilize fire crews at various fire services here in the UK for many years. I figured if anyone could help me solve my problem it would be him as he has lots of experience of writing compact and efficient code that does some really cool things.
So I asked, and his response went something along the lines of "Write a program within the program".
"You can't fit a scripting engine on a PIC" was my reply.
A discussion ensued and actually the solution he proposed made perfect sense and it went something like this....
You want to display 4 colours - Off, Red, Green and Yellow. This requires two bits (00 = Off, 01 = Red, 10 = Green and 11 = Yellow)
You want to display lots of different states - 1 byte = 8 bits = 4 steps. Each step can be one of the four colours.
So, the 'program' 01 00 00 00 could be interpreted as show red for one cycle, then off for three. Likewise the program '10 01 10 01' would show green for a cycle, red for a cycle, green for a cycle and finally red before repeating.
With a single byte we have a four step 'program memory' with each step containing one of the four 'program instructions' (off, red, green, yellow). Using this scheme we can achieve 256 different programs, far more than I had achieved with my state-machine driven effort (it should be noted though, that due to timing etc. not all of these are unique. 01000100 is identical to 00010001 in terms of the visual effect and therefore using programs that look alike should be avoided, this reduces the total number of unique programs quite substantially but still allows a good range to be generated).
Code:
01: ledPrescaler++; 02: if (ledPrescaler>=ledPrescalerPeriod) { 03: ledCounter=ledCounter+1; 04: ledPrescaler=0; 05: } 06: 07: if ( ledCounter >= ledStepPeriod ) { 08: ledRot = (ledProgram & 0b00000011) << 6; 09: rotate_right(&ledProgram,1); 10: rotate_right(&ledProgram,1); 11: ledProgram = ((ledProgram & 0b00111111) + ledRot); 12: ledCounter=0; 13: } 14: 15: ledRed=0; 16: ledGreen=0; 17: if ((ledProgram & 0x03)==0x03) { 18: ledRed=((ledPrescaler & 0b01)==0b01); 19: ledGreen=!ledRed; 20: } else { 21: ledRed=((ledProgram & 0x01)==0x01); 22: ledGreen=((ledProgram & 0x02)==0x02); 23: } 24: 25: if (ledRed) { 26: portaval|=DRVVALUE_LEDRED; 27: } else { 28: portaval&=(DRVVALUE_LEDRED^0b11111111); 29: } 30: if (ledGreen) { 31: portaval|=DRVVALUE_LEDGRN; 32: } else { 33: portaval&=(DRVVALUE_LEDGRN^0b11111111); 34: } 35: output_a(portaval);
Obviously the first thing you'll note is that this is C not Pascal. This is the actual source for this element of the firmware. This code runs inside the main loop. This is a permanent loop that contains the firmware state-machine (more on state-machines coming in an article soon). The chip itself is clocked at 16MHz so each cycle through the loop is tiny in terms of time, so it's necessary to scale things down a little (lines 01 to 05 perform this scaling, incrementing 'ledCounter' only when we have been through the main loop 'ledPrescalerPeriod' times).
The next two sections (lines 07 to 13 and lines 15 to 23) are actually our program interpreter.
The first of these two sections performs more scaling (this time by ledStepPeriod). When it's time to move on, we take a copy of the right most bits of our program and shift them 6 bits to the left (line 8 ), we then rotate the program right by 2 bits (lines 9 and 10). This effectively moves us to the next step of our program. We then stuff the two bits we copied and shifted in line 8 back into the program buffer (line 11). In this way, our program forms a permanent loop. We then reset the scaling counter so that we don't advance too quickly.
You could think of this first section as the fetch element of a CPU in that we've fetched the next element we want to execute by shifting it along in the variable that contains the program. The next logical step is to decode the program. This is done in lines 15 to 23. The simplest instructions are off (00), red (01) and green (02). These are all dealt with by the 'else' section (lines 21 and 22). The 'Yellow' program instruction (11) is slightly more tricky in that we need to alternate between red and green. This is done by setting the status of the red LED based on the value of the 1 bit from our pre-scaling counter 'ledPrescaler' (line 18 ). Since this gets incremented each time we come through the loop, the first time through the red LED might be on, the next time off. We then simply set the green LED state to be the opposite of the red (line 19).
The final section of this code (lines 25-35) deals with actually sending the decoded program to the hardware. The variable portaval holds the current value of the IO port A (this is the physical digital IO port I used on the PIC to drive the LED, amongst other things). If the red LED should be on, we do a bit-wise OR to add the bit that turns the red LED on (line 26), if it should be off, we do a bit-wise AND on the value with the inverted bit pattern that it used to turn it on (line 28 ). If the bit pattern to turn it on is 0b00010000, the inverted pattern to turn it off is 0b11101111. We then do the same with the green (lines 30 to 34) and finally stuff the new data out to the IO port (line 35).
vBulletin Message