Jump to content

Recommended Posts

What is the recommended way of setting/clearing a single output line? Is there any difference between:

 

PORTC.1 = 1;

 

LATC.1 = 1;

 

set_bit(PORTC, 1);

 

set_bit(LATC, 1);

 

Should individual output bits be declared as volatile?

 

What is meant by a Read - Modify - Write operation in the PIC datasheets?

 

Thanks,

 

Danny, G3XVR

Link to post
Share on other sites

Writing a bit on a port sometime depends upon the hardware attached to the port. Sometime you can write a '1' to a port bit, but when you read the port that bit can read as a '0' because the output pin voltage is less than the '1' threshold due to what is connected to the port pin. i.e. base of a transistor.

 

To avoid Read-Modify-Write problems, I sometime use a shadow register in RAM. I do all my changes to the shadow register then do a write only to the port. If I need to know the state of an output pin I can refer to the shadow register which should reflect the port output. Updating the port can be done immediately after changing the shadow register or sometime I wait and write the shadow register to the port during a timer tick interrupt. This means there is only a single place for all the port writing. All depends on your application and hardware design really.

 

set_bit(PORTC, 1); is basically a macro for PORTC.1 = 1;

 

 

Cheers

 

Reynard

Link to post
Share on other sites

Thanks mchristisen and Reynard.

 

I am writing to some port bits and reading from others, not mixing read and write on the same pin. Writing to one pin was OK, but when I wrote to another pin it reset the first pin, so I'm not quite sure what is happening. I will investigare further.

 

Danny, G3XVR

Link to post
Share on other sites
Thanks mchristisen and Reynard.

 

I am writing to some port bits and reading from others, not mixing read and write on the same pin. Writing to one pin was OK, but when I wrote to another pin it reset the first pin, so I'm not quite sure what is happening. I will investigare further.

 

Danny, G3XVR

 

Hi Danny,

 

What you're seeing is the classic read-before-write problem. The only way reliably around it is to cache the writes before actually changing the port - and only the changing the port with a byte-wide write (no rhyming intended) B)

 

Here are the routines that come from the pic_utils.h in the PicPack library (see Embedded Adventures)

 

#define set_pin(port, pin) \
set_bit(port_shadow[port - PORTA], pin); \
port_array[port - PORTA] = port_shadow[port - PORTA]; 

#define clear_pin(port, pin) \
clear_bit(port_shadow[port - PORTA], pin); \
port_array[port - PORTA] = port_shadow[port - PORTA]; 

#define toggle_pin(port, pin) \
port_shadow[port - PORTA] ^= (1 << (pin)); \
port_array[port - PORTA] = port_shadow[port - PORTA]; 


#define test_pin(port, pin) \
 ((port_array[port - PORTA] & (1 << pin)) != 0)

#define change_pin(port, pin, value) \
if (value) { \
	set_pin(port, pin); \
} else { \
	clear_pin(port, pin); \
}

 

Fundamentally, the way these devices work is that they can only write to a port a complete byte at a time. So what happens if you only want to change one bit (a single pin)? Well, the device reads the value on all the pins in the port, then changes the pin (bit) you care about, then writes it all back to the port. Fine, except when as previous posters have mentioned, the current value on the port doesn't reflect the value that you last wrote to it! The only way around this is to "shadow" all your writes, so that your code remembers what was previously written to the port.

 

18f devices have a LAT (latch) register for each port that does this in hardware, however, the shadow register method is safe on 18f devices as well, although it uses slightly more code than just writing to the LAT registers directly.

 

The routines above allow you to do things like:

 

set_pint(PORTA, 1);

 

(note the PORTA is in captials due to the way the array is set up). This results in the fewest lines of assembler in order to use a shadow:

 

05A4  884A		  BSF gbl_port_shadow+D'1',4
05A6  504A		  MOVF gbl_port_shadow+D'1', W
05A8  6E81		  MOVWF gbl_port_array+D'1'

 

This is on a 18f device, but in either case, it's three instructions and saves you from the craziness of the chip seeming to do, well, crazy things. I've seen read-before-write issues on just led+resistor on the output, let along when the pins are tied to some other device. I use this method on everything and cringe when I see code that's simply

 

  porta.1 = 0;

 

since who knows what other pins you've just changed at that point?

 

  lata.1 = 0;

 

is however, perfectly safe on 18f devices.

 

cheers

Ian.

Edited by Ian Harris
Link to post
Share on other sites

Hi all,

 

I do exactly the same as Reynard. Since starting with embedded 16F PICs many moons ago (using assembler) I found the safest way

was to always use port shadow registers. Using them means direct writes to a port must not be done if unexpected operation is

to be avoided. All writes and port output status reads should be carried out via the shadow register.

 

Many of my projects require multiple port pin changes that need to be synchronised with an event of some sort, either

a timer, input edge or interrupt etc., so the shadow registers are typically changed in a number of places but are only

copied to the port when the required event occurs. This is in addition to any instantaneous, single pin, updates that

may also be required.

 

Ians PickPack library is a neat way of implementing port shadow registers as the user doesn't have to worry about the

shadow register itself. Unfortunately, it is not very code efficient if you need to change more than two pins

at a time. More importantly, for me at least, it does not allow delayed or synchronised writes to the actual port.

 

The way round this is to either do what I do and simply use shadow registers directly in the code or use the

PickPack library and create some additional macros to do the job. I think the later is preferable, certainly for

newbies, since it reduces the chance of errors. Just remember to use upper case for the port name!

 

Typically a port may need initialising to a value. Rather than using a series of the clear_port(PORT,x) or set_port(PORT,x)

instructions from the library simply do the whole lot in one go using the following macros:

// Set a port to a value via the shadow register. e.g. set_port(PORTB, 0b00111100)
#define set_port(port, value) \
port_shadow[port - PORTA] = value;
port_array[port - PORTA] = port_shadow[port - PORTA];

// Clear a port via the shadow register. e.g. clear_port(PORTB)
#define clear_port(port) \
port_shadow[port - PORTA] = 0;
port_array[port - PORTA] = port_shadow[port - PORTA];

 

Rather than change the whole port in one go it is often required to change the state of a number of pins so

we could use the following macro:

// Change multiple port pins via the shadow register using a literal and port pin mask to select the pins to be changed
// e.g. change_port_pins(PORTD, 0b00010011, 0b00011111)
// Note that this is very useful but not so user friendly since the individual pins are not named
#define change_port_pins(port, literal, mask) \
port_shadow[port - PORTA] ^= ((mask & 0xff) & (port_shadow[port - PORTA] ^ (literal & 0xff))); \
port_array[port - PORTA] = port_shadow[port - PORTA];

The rest of the macros allow a shadow register to be changed without writing the value directly to the port.

The shadow register is copied to the port when required using another macro (below).

 

First individual pins:

// Set port shadow register pin. e.g. set_shadow_pin(PORTB,6);
#define set_shadow_pin(port, pin) \
set_bit(port_shadow[port - PORTA], pin); 

// Clear port shadow register pin. e.g. clear_shadow_pin(PORTB,5);
#define clear_shadow_pin(port, pin) \
clear_bit(port_shadow[port - PORTA], pin);

// Toggle port shadow register pin. e.g. toggle_shadow_pin(PORTB,4);  
#define toggle_shadow_pin(port, pin) \
toggle_bit(port_shadow[port - PORTA], pin);

 

Now for multiple pins:

// Set port shadow register to a literal. e.g. set_shadow_port(PORTB, 0b00111100);
#define set_shadow_port(port, value) \
port_shadow[port - PORTA] = value;

// Clear port shadow register. e.g. clear_shadow_port(PORTB); 
#define clear_shadow_port(port) \
port_shadow[port - PORTA] = 0;

// Change multiple pins in a port shadow register using a literal and a port pin mask to select bits to change 
// e.g. change_shadow_pins(PORTC, 0b00001100, 0b00001111)
#define change_shadow_pins(port, literal, mask) \
port_shadow[port - PORTA] ^= ((mask & 0xff) & (port_shadow[port - PORTA] ^ (literal & 0xff)));

The following macro simply copies the shadow register to the port output register as and when required:

// Update port from shadow register. e.g. update_port(PORTC);
#define update_port(port) \
port_array[port - PORTA] = port_shadow[port - PORTA];

 

The last macro is used to read a port pin value from the shadow register:

// Test a port shadow register pin. e.g. test_shadow_pin(PORTB,3); 
#define test_shadow_pin(port, pin) \
((port_shadow[port - PORTA] & (1 << pin)) != 0)

Please note that I have not fully tested these macros and there may be errors, they are just offered as ideas at the moment.

The advantage is that they work in a consistent way with Ians PickPack library. The disadvantage is that they now expose

the user to the shadow register.

 

Can they be improved or are there better ways? Perhaps they can be given more imaginative names?

Perhaps Ian will consider adding them or similar macros to his library?

 

Thoughts anyone?

 

davidb

Link to post
Share on other sites

Join the conversation

You are posting as a guest. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
×
×
  • Create New...