Joined: 11 Mar 2010
|PIC TRIS & I/O modes
|Posted: Mon May 04, 2020 1:34 am
|The PIC I/O modes and registers.
Historically standard IC's, had pins that were either inputs or outputs. The
first PIC was one of the earliest chips that instead allowed the pin direction
to be changed with code.
You had a 'PORT' register, which you could read or write, and separately a
'TRIS' register that set the operating direction of each pin. On this register
a '1' specifies an input, and a '0' an output.
This worked pretty well in general, but has an issue if a pin has a load on
it that prevents it actually reaching the output voltage required.
So (for example), you have a pin driving a heavy load. It is set to be 'high',
but is actually only going up to perhaps 1.5v. You decide to set another pin
on the same port high. The chip when you do this performs what is known
as a 'READ modify WRITE' cycle (RMW). It reads the voltage level on all the
pins, then changes the requested bit in this value to the required level, and
writes the result back to the port. The problem is that because the other pin
is not actually going 'high', when the read is done, the wrong level is read.
Result, when you change the pin, the other pin that was already being
driven goes 'off'... :(
Problem. This is known as the PIC Read Modify Write problem (or RMW).
This also depends massively on the input threshold of the pin involved, So a
pin with a Schmitt trigger type input, may require the input to actually be at
80% of the supply voltage to be seen as 'high'. On such pins quite a small
load (or something like a capacitor that takes time to charge), can make this
problem really significant...
PIC18 and later, LAT.
To get round this the later PIC18 introduced a third register. The LAT
register. This holds the required 'output' levels for all the pins. So instead of
the set having to do a RMW on the actual port, you can just change the level
of the bit in this register. So on these PICs when wanting to set an output
level, this should be done by writing to the LAT register, rather than to the
port itself. The same system is used on all subsequent PICs like the PIC24,
30 & 33.
So on all of these if working directly to the registers, read from the port, and
write to the lat.
Now, working with CCS.
First thing here is that selection of LAT versus PORT, is automatic. If you
read PORTx with CCS, it reads the port register. If however you write
PORTx, on chips with the lat register, the compiler automatically writes to
The second thing is TRIS. Generally for 98% of code, you can forget this. If
you leave the compiler in its default 'standard_io' mode, it will handle TRIS
for you. When you setup a peripheral, it will automatically set the TRIS to
suit this peripheral. Then when doing normal I/O, when you output to a port,
it'll set the TRIS to '0' so the pin is an output. when you input, it'll set the
TRIS to '1' so the pin is an input. On current compilers this really does work
well, and is more likely to get the settings right than you are!...
So, unless you have a specific need to do something special, just let the
compiler handle this for you. It's one of the things that CCS really does help
to make easy.
Downsides are that there is a tiny cost in speed (the compiler sets the TRIS
for every I/O operation), and when you perform I/O the TRIS will always
There is though now a 'read' that does not change the TRIS (input_state).
Why then use TRIS?.
Now the exceptions. There are a very few situations where the automatic
control gives issues. An example would be using say pins B4 to B7 for
'interrupt on change', where you want to read all these bits at once for
speed, but you also want to output on perhaps B0 and 1. Problem is that if
you do the byte wide read on PortB, this sets the TRIS to all 1's, and B0 and
B1 are no longer driven. The second part of this is that possibly with the
interrupt on change, you are doing something that needs real speed
(handling a quadrature encoder for example), and the extra time that the
automatic TRIS handling introduces is also a problem. The way round this is
fast_io. If you use the line:
#use fast_io (B) in your setup, the compiler turns off its automatic handling.
It switches to leaving it for you to handle everything.
Sets up port b to have the top six bits as inputs, and the bottom two as
outputs. Using the binary notation for this makes it nice and easy to 'see'
what each pin is set as.
Then in the interrupt handler:
Reads the port without changing the TRIS. You have the top four bits
available for your interrupt handler as required.
Similarly you can output to the whole port, or just to the required bits. The
output will not change the TRIS.
You have the same speed as assembler code, the same flexibility, but the
If you need speed and port wide I/O this is a way to go.
There is a second way you can also work. This is direct port I/O.
//Then without selecting fast_io
val=PORTB; //reads the PORTB register value without changing TRIS.
LATB=nnn; //sends the value 'nnn' to the LATB output register.
Here we use the compilers ability to assign a usable name to any register,
and it's ability to find a register by name, and then write/read directly
to/from the register.
A comment here. On most of the PIC18's, the data sheet says that if you
write to PORTx, it'll automatically write the value to LATx. However on a lot
of PICs this does not work.
So you need to make sure that you do write to the lat register not the port
I haven't tracked down exactly what happens, but suspect it re-introduces
the RMW issue.
Now there is a third mode:
This is rather a 'hodge podge' really. Here you specify the TRIS setting
yourself with the setup line specifying which pins are to be set as outputs.
So in our example:
#use fixed_io(b_outputs=PIN_B0, PIN_B1)
Then you can use val=input_b();
and the B0/B1 remain as outputs.
Now as described in the manual, this would be a perfect way round the
requirement for our interrupt on change example. However though the
manual claims it saves a byte of space, it does not. It actually does the
same setting the TRIS for every I/O operation that the standard_io does. So
space and speed are basically the same as for standard_io.
Generally for 99% of standard applications, I'd suggest you start with the
standard_io mode. It is now very reliable, and is less likely to lead to issues
than the other modes.
If you have the specific situation, of wanting to do 'byte wide' (or for the
PIC24/30/33, 'wordwide') I/O, and want the best speed, then switch to
using FAST_IO on this port only, or use the direct register accesses