This video started with checking your "homework" (the LED display code), then got into writing the complete dice simulator using the Dice256 random number algorithm from Dice Part 1.
Not much in the way of colourful animations in this video, but a couple of really profound concepts. Neither of them is hard to understand, but understanding them is just the first step. Good programmers have absorbed them into their programming "reflexes".
set button,0x80 ; ; Initialise ; MOV #0x3f,DDRB ; full bright LEDs mainloop: MOV #0,PORTB ; all LEDs off ; ; Wait for a button press ; waitbutton: CALL shake ; rattle the dice BIT #button,PIND JEQ waitbutton ; ; Get value of dice ; CALL roll ; roll out dice ; ; Display on LEDs ; CALL showleds ; ; Wait for button release ; WAIT 5000 ; half second minimum display waitup: BIT #button,PIND JNE waitup JMP mainloop ; ; shake -- continue virtual shake of dice ; Inputs: ; R1 -- seed shake: ADD #1,R1 ; simple increment RET ; ; roll -- get result of roll ; Inputs: ; R1 -- dice seed value ; Outputs: ; R1 -- dice value number [0:5] roll: SUB #6,R1 ; R1 mod 6 JNC roll ADD #6,R1 RET ; ; showleds -- show 1 to 6 LEDs ; Inputs: ; R1 -- value [0:5], #LEDs to light - 1 ; Clobbers R1 org 40 showleds: MOV #1,PORTB ; one LED on slloop: ADD #0xff,R1 ; clever subtract 1 JNC shown ROL PORTB ; one more LED on JMP slloop shown: RETThis is the source code which needs to be fed through the ArdEx assembler.
If you don't want to use the assembler, here is the code ready to upload to ArdEx.
Since I glossed over it in the video, let's take a look at the "clever" improvement to the LED display code. Firstly, you need to be satisfied that adding 0xff gives the same result as subtracting 1. As I've said so often, think of it in just the same way as adding 999 to a three digit odometer. It ends up as if it had gone backwards by 1.
The other thing to be satisfied of is that the Carry flag behaves as we want. A moment's thought should convince you that adding anything other than 0 to 0xff will lead to a carry. So we can exit the loop when there's no carry, knowing the value was zero. And since the carry is definitely set, the ROL instruction will always end up with a 1 in bit 0 of PORTB.
As to what makes this "clever", well nothing really. But it's usually a good thing to get the same job done in fewer steps. It takes less time and uses fewer resources.
We throw out that approach anyway. Here are the changes to the above code using a lookup table to provide more easily read LED patterns:
MOV #0x04,0 MOV #0x12,1 MOV #0x15,2 MOV #0x1b,3 MOV #0x37,4 MOV #0x3f,5 40 MOV @R1,PORTB 41 RET
Those first MOV instructions load the LED patterns into the table in the lowest six bytes of RAM. The instruction in slot 40 copies the value from the table straight onto the LEDs. Pretty flexible!
A quick recap on the last part of the video on saving the code and data and having it run automatically when the Arduino is turned on.
The SAVE command saves instruction memory to EEPROM which can be restored using the LOAD command. Similarly RAMS saves the contents of RAM to EEPROM and this can be restored using the RAML command. Finally, the BOOT command tells ArdEx what to do at startup. Bit 0 says to load all instruction slots from EEPROM. Bit 1 says to load all RAM from EEPROM. Bit 2 says to automatically start running instructions.
BOOT 0 disables all special actions at boot time.
You can check out the commands in the ArdEx Command Reference Manual.
Incidentally, the reason powering up a computer is known as booting is another somewhat humorous reference. When you first power up a computer it has no program to run, but it can't do anything at all without a program. The process by which it finds that first program to run is likened to trying to get off the ground by pulling yourself up by your bootstraps. Fortunately the computer is better at this than I am.
This is the big one, although in this simple code, the benefits may not be very clear. I'll try to explain the bigger picture.
Modularity sounds good. It implies that you end up with general purpose modules that can be reused. That's often the case, but it's not the most important thing, and it doesn't explain how the programmer needs to think. It's better to look at it as a matter of privacy; that knowledge of the inner workings of a program should be kept on a "need to know" basis.
As a programmer you are the boss of the imaginary world inside the computer. When designing a program, it can be helpful to imagine that you are putting on workers to get the job done. You start by defining the workers' roles. For the dice simluator, you might have one worker, the watcher, just waiting for a button press. Another, the nudger, who turns the "wheel of fortune" to its next step. The reader reads the dice number from the wheel when the watcher says to, and immediately passes it on to the switcher, who flips the switches to light the right LEDs.
Of these jobs, the watcher and switcher are fully independent from all other roles. The watcher just waits for a button press. That's it. The switcher is given a number and lights that number of LEDs. Doesn't care where the number came from.
In contrast, the nudger and reader are tied to one another. If we upgrade the six segment wheel to the 256 segment wheel, the nudger and reader both need to know about the change in geometry. These two workers have to collaborate. Any change to the responsibilities of one is likely to affect the other.
Perhaps you're thinking "Oh nonsense! Just employ one person to roll the dice and flip the switches. Sheesh". Yes and no. To the outside world, that's exactly how it should look, but the process of programming is one of breaking down larger tasks into smaller ones, zooming in closer and closer until you've reached a manageable level of complexity. The thing is that there are many ways of breaking a problem into smaller pieces, and some are much worse than others. It helps a lot if you keep everything reasonably independent.
There are a few consequences of dividing a problem up like this.
Admittedly, there are times when very similar pieces of code are needed. In such cases I look into ways of generating the code automatically (usually with a Perl script), so there is one ultimate source file to work with.
This is a balancing act. I have seen programs where the programmers were too enthusiastic with hiding the details and ended up with an incredibly bureaucratic program. Even though this is bad, it isn't as big a problem as when no details have been hidden. When everything's tied to everything else it's known as spaghetti code.
Another consideration is how long you think the program is going to be around. You have to be pragmatic. What's the point of spending days writing the perfect program if your plan is to run it once and throw it away. Just be careful you don't fool yourself. When you have written a throwaway program, throw it away.
Modularity is part of software design, but a lookup table is merely a programming technique, and a very simple technique at that. Why do I stress it as being nearly as important?
Its simplicity is its curse. Computing courses teach all about algorithms and data structures, and they naturally go from simple to complex as the courses progress. The lookup table is so absurdly simple that it barely rates a mention. There might even be a kind of snobbery, that computers should compute things, not look them up in a table.
In fact, there's no getting away from lookup tables. Even a program which doesn't use them directly will usually compile into code that does use them. And beneath it all, the microprocessor's instruction decoder generally uses a lookup table to find the steps required for each instruction. If lookup tables are used by hardware designers and compiler writers, they're probably good enough for you and me too.
You can expect plenty more lookup tables in upcoming videos to see this simple and powerful technique in use.
But, as suggested, you might stave off boredom by trying different patterns in those first six bytes of RAM, or you could be more creative and write some code using a lookup table to run an animation on the LEDs.
Here's a little animation I wrote a while back which you might experiment with. You may need to look up the XOR instruction to understand what's going on. It's a very handy instruction when you want to turn some bits on and some bits off, while leaving other bits unchanged.
; ; Ping-pong 4 low order LEDs while leaving the upper nybble alone. ; ! MOV #0x03,0 ; XOR bit patterns ! MOV #0x06,1 ! MOV #0x0c,2 ! MOV #0x09,3 BIC #0x0f,PORTB BIS #1,PORTB ; start on the low order bit MOV #0,R8 lloop: WAIT 1000 XOR @R8+,PORTB ; going left CMP #0x03,R8 JNE lloop rloop: WAIT 1000 XOR @-R8,PORTB ; going right CMP #0,R8 JNE rloop JMP lloop
I'm giving you the unassembled source code to test your dedication. Either work out slot numbers for yourself (called hand assembling), or crank up perl and the ArdEx Assembler!
The other thing suggested was to replace the roll function with something using OUT and IN to prompt you to enter a number for the dice value. Remember, IN will receive your keystroke as ASCII, but roll needs to receive a binary value between 0 and 5.