One of my good friends in years past over at allaboutcircuits.com, RJ Oldenkamp was kind enough to help me out a lot with the code for a project I was working on. I am very much a novice now but far much more then. I had written my program which was 565 lines of code, thinking it was pretty good. Everything worked well but I wanted a second set of eyes on it to maybe point out some things I'd missed and give me some help on proper code writing. Well RJ got a hold of the project and after teaching me endless things I'd never even thought of, and the "proper" way to write embedded C, the same project was now 815 lines, but far more stable than I even dreamed of at the time.
All of this transpired during the first year of me writing this blog, and in fact is a part of why I had been absent from it for so long. That and making a career change twice, moving half-way across the United States, and getting settled in a whole new routine. The point of this story is that one of the things that RJ brought to my attention was the concept of Shadow Registers.
It is very difficult sometimes when explaining microprocessor things to beginners not to get too technical and start throwing terms that might confuse them; and the point of this blog after all, is to be truly beginner friendly, so I'm really trying to keep this as simple as possible. After looking around, the best explanation I could find comes from Michael Rigby-Jones from Nortel Networks.
When you perform nearly any operation on a register, the PIC first reads the register, then it performs the operation on the register it just read, and finally the PIC writes the result back to the register. This is known as read/modify/write. This process is fine when dealing with normal registers and most special function registers, but when you perform a read/modify/write on a port register such as TRISIO, PORTA, PORTB, etc or some timer/counter registers, it could cause problems.
When the microprocessor reads a port register, it reads the ACTUAL state of the pins on the port rather than the output latch, which can cause two problems. If the pin is an input, then the input pin state will be read, the operation performed on it, and the result sent to the output latch. This may not immediately cause problems, but if that pin is made into an output later on, the state of the output latch may have changed from the time it was deliberately set by the code.
If the pin is defined as an output, the output latch and the actual pin should be in the same state, though in practice sometimes they aren't. If you are controlling a capacitive load, for example, the pin may take some time to respond as it charges and discharges the capacitor. A common problem occurs when using the bit set (GP1=1) or bit clear (GP1=0) function directly on a port.
Example, consider two lines of code:
GP4=0
GP4=1
If pin 4 is loaded in any way, such as being connected to a capacitive load, then it may not have time to respond to the first instruction before the second one is executed. During the second instruction, the microcontroller reads the data port pins, sees that GP4 is set low (0), and during the write portion of the read/modify/write it writes GP4 low back into the output latch. The result would be that GP4 never gets set.
Confused? Think of it this way. A capacitor holds a charge. If you have a closed circuit with a battery, a capacitor, and an LED connected, the LED is on. Think of the LED as our pin and your eyes as the microcontroller. Turn on the pin (connect the battery), what is the state of the pin? The LED is on right? Now turn off the pin (disconnect the battery) and immediately determine the state of the pin. Since the capacitor is discharging through the LED, we still see the LED on even though the battery is disconnected. We have told the pin to turn off, but our eyes (the PIC) is seeing it as on. It's the same kind of concept, and that's the best I can do to explain it.
So how do we fix it? I have mentioned already that it is bad practice to use read/modify/write instructions directly on a port, so you use a shadow register. We are taking one byte of RAM and using it to mimic the data port. We perform all operations on the RAM location and then simply copy the RAM location to the port register. This essentially works because rather than telling the MCU to write to just one I/O pin, we are writing to the entire register (TRISIO, PORTA, etc.). What we are doing is creating a RAM variable (the shadow register) that enables us to control an individual GPIO pin without the risk of corrupting the settings of the other GPIO pins on the register.
Let's see this in practice. We want to turn on our LED, which is connected to GP0. We have been doing that by writing to the individual GP0 I/O port.
GP0=1;
Now we want to use a shadow register. When we are setting aliases we will set a variable (more on this in the next lesson) to a RAM location to mimic the entire GP register. That is, rather than setting just GP0 to something, we will also set all the other pins at the same time.
unsigned char GPIOimg; //I've just defined a RAM location as a GPIO Image.
GPIOimg = 00000001; //I've just set the image of GPIO to set GP0 to on
GPIO = GPIOimg; //I've just copied the image of GPIO to the actual GPIO port.
So rather than setting the single bit of GP0, I have written the entire port. We are getting to a point in lessons where overlap is occurring, and as such, I'm having to use examples we haven't covered yet. All will come together in the next lesson or two and it will make a lot more sense, and we will write another full program to put the lessons to constructive use.
No comments:
Post a Comment