Welcome to Tutorial 6!
Today's tutorial will be short one where we will develop a simple method to communicate data between one atmega328p and another using two ports connecting them. We will then take the dice roller from Tutorial 4 and the Register Analyzer from Tutorial 5, connect them together, and use our method to communicate the result of dice rolls from the roller to the analyzer. We will then print out the roll in binary using the LEDs that we constructed for the analyzer in Tutorial 5. Once we have this working we will be able to construct the next piece of our overall project in the next tutorial.
In this tutorial you will need:
- Your prototyping board
- Your dice roller from Tutorial 4
- Your Register Analyzer from Tutorial 5
- Two connecting wires
- A copy of the Complete Data Sheet (2014 revision):
- A copy of the Instruction Set Manual (2014 revision):
Here is a link to the complete collection of my AVR assembler tutorials: https://www.instructables.com/id/Command-Line-AVR-T...
Step 1: How Can We Get Two Microcontrollers to Talk to Each Other?
Since we are starting to expand our project so that our single end product is made up of a collection of smaller parts we are going to need more pins than a single Atmega328P can provide. Therefore we are going to do each piece of the overall project on a separate microcontroller and then have them share the data between them.
So the problem that we need to solve is how can we come up with a simple method for the controllers to talk to each other and transfer data between them?
Well, one thing about these controllers is that they each execute 16 million instructions per second. This is very precisely timed and so we can use this timing to transfer data. If we use millisecond delays to constitute the data then we don't really have to be all that precise since the CPU is executing 16,000 instructions in a single millisecond. In other words, a millisecond is an eternity for the CPU.
So let's try it with the dice rolls. I want to transmit the result of a dice roll from the dice roller chip to the analyzer chip. Suppose you were standing across the street and I wanted to signal to you the result of my roll of a pair of dice. One thing I could do, if we both had a watch, is I could turn on a flashlight, then when you are ready to receive my data you turn on your flashlight and we both start our clocks. Then I keep my flashlight on for the exact number of milliseconds as the dice roll and then shut it off. So if I rolled a 12 I would keep my light on for 12 milliseconds.
Now the problem with the above is that, for you and me, there is no way we would be able to time things accurately enough to distinguish between 5 milliseconds and 12 milliseconds. But what about this: Suppose we decided that I would keep my light on for one year for every number on the dice? Then if I roll a 12 I would shine the light at you for 12 years and I think you will agree that there is no possibility that you will make a mistake in figuring out the number right? You could take a break and go play baseball, you could even go play craps in Vegas for 6 months, as long as at some point during the year to glanced across the street to see if the light was on you would not miss a count. Well that is exactly what we are doing for the microcontrollers! A single millisecond for the CPU is like a year. So if I turn the signal on for 12 milliseconds there is almost no chance that the other microcontroller will confuse it for 10 or 11 no matter what interrupts and whatnot happen in the meantime. For the microcontrollers, a millisecond is an eternity.
So here is what we will do. First we will choose two ports on the controller to be our communication ports. I will use PD6 for Receiving Data (we could call it Rx if we like) and I will choose PD7 for transmitting data (we could call it Tx if we like). The analyzer chip will periodically check it's Rx pin and if it sees a signal it will drop to a "communication subroutine" and then transmit a return signal to the dice roller saying it is ready to receive. They will both start timing and the dice roller will transmit a signal (i.e. 5V) for a millisecond per number on the dice. So if the roll was double sixes, or a 12, then the dice roller would set it's PD7 to 5V for 12 milliseconds and then set it back to 0V. The analyzer will check its PD6 pin every millisecond, counting each time, and when it goes back to 0V then it outputs the resulting number to the analyzer display, showing a twelve in binary on the LED's.
So that is the plan. Let's see if we can implement it.
Step 2: Communications Subroutines
The first thing we need to do is connect the two controllers. So take a wire from PD6 on one and connect it to PD7 on the other, and vice-versa. Then initialize them by setting PD7 to OUTPUT on both and PD6 to INPUT on both. Finally set all of them to 0V. Specifically, add the following to the Init, or Reset section of the code on each microcontroller:
sbi DDRD,7 ; PD7 set to output cbi PortD,7 ; PD7 initially 0V cbi DDRD,6 ; PD6 set to input cbi PortD,6 ; PD6 initially 0V clr total ; total on dice initially 0
Now let's set up the communications subroutine on the dice-roller chip. First define a new variable at the top called "total" which will store the total number rolled on the pair of dice and initialize it to zero.
Then write a subroutine to communicate with the analyzer:
communicate: cbi PortD,7 sbi PortD,7 ; Send ready signal wait: sbic PinD,6 ; read PinD and skip if 0V rjmp wait delay 8 ; delay to synchronize (found this experimentally) send: dec total delay 2 ; delay for each die count cpi total,0 ; 0 here means "total" number delays have been sent breq PC+2 rjmp send cbi PortD,7 ; PD7 to 0V clr total ; reset dice total to 0 ret
In the analyzer we add a rcall from the main routine to the communicate subroutine:
clr analyzer ; prepare for new number sbic PinD,6 ; check PD6 for a 5V signal rcall communicate ; if 5V go to communicate mov analyzer, total ; output to analyzer display rcall analyzer
and then write the communicate subroutine as follows:
communicate: clr total ; reset total to 0 delay 10 ; delay to get rid of bounces sbi PortD,7 ; set PB7 to 5V to signal ready receive: delay 2 ; wait for next number inc total ; increment total sbic PinD,6 ; if PD6 goes back to 0V we're done rjmp receive; otherwise loop back up for more data cbi PortD,7 ; reset PD7 when done ret
There you go! Now each microcontroller is set up to communicate the result of the dice roll and then display it on the analyzer.
We will implement a much more efficient way of communicating later on when we need to transfer the contents of a register between controllers instead of just a dice roll. In that case, we will still use only two wires connecting them but we will use 1,1 to mean "begin transmission"; 0,1 to mean "1"; 1,0 to mean "0"; and finally 0,0 to mean "end transmission".
Exercise 1: See if you can implement the better method and use it to transfer the dice roll as an 8-bit binary number.
I will attach a video which shows mine in operation.
Step 3: Conclusion
I have attached the complete code for your reference. It is not as clean and tidy as I would like, but I will clean it up as we expand it in future tutorials.
From now on I will just attach the files containing code rather than type it all out here. We will just type out the sections that we are interested in discussing.
This was a short tutorial where we came up with a simple method of telling our analyzer microcontroller what the result of our dice roll from our dice-roller microcontroller while only using two ports.
Exercise 2: Instead of using a ready signal to show when the dice roller is ready to transmit and another when the analyzer is ready to receive, use an "external interrupt" called a "Pin Change Interrupt". The pins on the atmega328p can be used this way which is why they have PCINT0 throught PCINT23 beside them in the pinout diagram. You can implement this as an interrupt in a similar way as we did with the timer overflow interrupt. In this case the interrupt "handler" will be the subroutine that communicates with the dice roller. This way you don't need to actually call the communications subroutine from main: it will go there any time there is an interrupt coming from a change of state on that pin.
Exercise 3: A much better way of communicating and transferring data between one microcontroller to a collection of other ones is using the built in 2-wire serial interface on the microcontroller itself. Try to read section 22 of the datasheet and see if you can figure out how to implement it.
We will use these more sofisticated techniques in the future when we add further controllers.
The fact that all we did with our analyzer is take the total of the dice roll and then print it out in binary using LEDs is not what is important. The fact is that now our analyzer "knows" what the dice roll is and can use it accordingly.
In the next tutorial we will be changing the purpose of our "analyzer", introducing a few more circuit elements, and using the dice roll in a more interesting way.
Until next time...