You have got to be able to communicate with the outside world. Serial IO is usually very simple to implement, and is perfect for low bandwidth applications that often find themselves loaded onto a PIC.
We assume you have your PIC blinking and you've got C code under control.
Here is the code:
Serial_Calibration.c - Use this file for figuring out the serial timing.
Serial_Calibration.asm
Serial_Calibration.hex
Serial.c - Pass the character 'k' to the serial port.
Serial.asm
Serial.hex
The following discussion involves the 16F628 using the fairly fuzzy internal oscillator, but can be applied to all other PICs and uCs in theory.
Theory -
Serial Communication - RS-232. Look it up on google and find out everything you can. It is fairly easy to comprehend, but trying to get it to work can be evil.
Beyond Logic is a great source for the RS-232 definition.
All serial communication must be timed correctly. There is not a clock line, all communication is done over a single transmission line. Therefore, devices at both ends must be talking at the same speed. We operate all of our communications at 9600 bps.
1 / 9600 bits per second = 1.04e-4 seconds per bit
This equates to 1 bit every 104us (micro seconds).
Picture was stolen from Beyond Logic
This is a very important picture! You can see that high and low do not correspond to 1 and 0. Instead, -10V is a 1, and +10V is a 0 according the the RS-232C standard. And our PIC only has +Vdd to Gnd (nominally 5V-0V), nothing near -10V to +10V. But don't fret! We can still do it!
Making it work -
When you send the letter 'r' from the PIC to your computer's serial in pin, you are sending a series of ones and zeros. Simple enough.
The ASCII value for 'r' is 114 in decimal. This is 0b.0111.0010 in binary.
So to get the computer the see an 'r' in RS-232 you must transmit the compliment of 0b.0111.0010 or '0b.1000.1101' out of your PIC for the computer to see an 'r'. Got it?
Our code -
Using the following code, we managed to force serial communication even though we use signals that are TTL level!
Serial_Out = 1;
rs_wait();
for (j = 0 ; j < 8 ; j++)
{Serial_Out = !s_char.0;
s_char = rr(s_char);
rs_wait();}
Serial_Out = 0;
delay_ms(5);
Picture was stolen from Beyond Logic
Refer back to this picture because things are going to get screwy. Assume we are starting from a low voltage ('Mark'). Hold the serial line high voltage for at least a pulse width so that the computer sees the 'start' bit. Remember, we are only using 0-5V! Complete trickery:
Serial_Out = 1;
rs_wait();
Now transmit the 8 data bits. Transmission of the byte is done from least significant bit to most significant bit (LSB to MSB). Remember, low voltage is 1, high voltage is a 0. So take the LSB (right most bit), invert it, and place it on the Serial_Out port.
for (j = 0 ; j < 8 ; j++)
{Serial_Out = !s_char.0;
s_char = rr(s_char);
rs_wait();}
Now that the LSB is sitting at the port, hold it there (rs_wait) for the right timing for 9600bps - or 104us. Loop and output the next bit. Send 8 bits and we are good to go!
Serial_Out = 0;
delay_ms(5);
Finish the transmission by sending the Stop Bit (pull the voltage high to send a Stop Bit). There needs to be stop time between bits. We have found that 5ms of down time works well but this is really long, so tweak it as you see fit.
rs_wait -
How long do you need to wait? As we stated above, 104us total. If the PIC operates one instruction every 1us, we should sit still for 104 instructions. So why do we wait only 6 times through the loop in the rs_wait function?
void rs_wait(void)
{int i;
for(i = 0 ; i < 6 ; i++);}
As you can see, the wait loops 6 times. What is so magical about this value? The secret is behind the scenes in the assembly code. By using MPLAB's Stopwatch function (half way down the 'Window' drop down menu), you can simulate how long each function call will take. Simulating the rs_wait function under MPLAB we found the following times:
i |
Total Execution Time (Simulated) |
4 | 68 us |
5 | 81 us |
6 | 94 us |
7 | 107 us |
8 | 120 us |
9 | 133 us |
You can see, using the 16F628 internal oscillator, we get a wide range of values by tuning the i variable by one. Because the rs_wait for loop has more than one instruction per cycle, the i variable does not match microseconds as one would expect.
The value of 6 takes 94 us: this is just enough short of 104 that the additional instructions (in the outer loop of rs_out) can be squeezed in without causing timing issues.
Oye! -
This is why we wrote the Serial_Calibration.c. Using this file will give you a quick and easy way to figure out what timing value you need. The file varies rs_wait from 1 to 100. This will effectively give you the serial timing value you will need for your specific PIC.
Based off the position of the character 'r', you should be able to tell what the i variable should be changed to permanently.
For example, you should have a line of gibberish in front of you inside HyperTerminal. If the 'r' character appears around the 15th spot, change the 'i < calibrate_step' to 'i < 15'. Now that you have the correct value, you should be able to call 'rs_out('e') (or any other ASCII value) and have an 'e' print out to HyperTerminal.
With the addition of some standard io functions (the printf sort of thing) that call this basic rs_out function, you will be able to simply throw variables at any of the PIC ports and be able to debug what is going on with your variables.
So that's it! If you have problems, let us know, spark@sparkfun.com. We should be able to point you in the right direction. If you use this tutorial and something doesn't make sense, please let us know. We are always trying to make the PIC world a little easier to understand.
I'm go ahead and point out the obvious:
The pictures! They aren't there!
Ok I'm done.
Leaving comments is the coolest!