Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

Setting specific bits in variables *PIC*

Status
Not open for further replies.

Brian Hoskins

New Member
Hi everyone,

I am part way through writing some software for an 8-bit PIC Microcontroller that will receive an RC5 datastream. I am writing the software in C, which is a new adventure for me (previously I've always written in Assembly).

I'm at the point in my program where I am reading in the RC5 datastream. I have setup two 8-bit variables for this (Address and Command). The idea is that the input pin is sampled at the correct time, and then the value (0 or 1) is loaded into the LSB of "Address". Then I will shift Address left, and at the appropriate time the input pin will be sampled again and the new value stored in bit 0 of "Address". Sampling will continue in this manner until all 6 bits of the RC5 Address (inc Toggle bit) are collected. The same will then be done for the Command.

I've been thinking about the best way to go about this. I already have my timing sorted out, but I'm unsure of the best way to set or clear a bit in a variable. Of course I could use a mask, but surely that's not the most efficient way to go about this for an embedded system. In assembly it can be done using BCF and BSF instructions, does anyone know if there is a globally accepted way of doing the same thing in C?

If not, I guess I'll just revert to assembly for the set and clear bit operations.

Thanks all,

Brian
 
Doing Var|=1 to set the bottom bit is probably the best way. Most compilers will compile this to a bsf instruction anyway.

Mike.
Edit, I am assuming that the bit will be clear after a Var<<1 instruction.
 
Pommie said:
Doing Var|=1 to set the bottom bit is probably the best way. Most compilers will compile this to a bsf instruction anyway.

Mike.
Edit, I am assuming that the bit will be clear after a Var<<1 instruction.

Hmmm ok - so you do use a mask then, but the way you've implemented it is a lot simpler than the way I was thinking. I'll try that and see how I get on.

Regarding the shift instruction, I too am assuming that the variable is filled with a 0 (or 0's) after a shift instruction. I shall try it and see!

Brian
 
I believe it's as simple as this:
PORTAbits.RA0 = 1;
Check the header file for the PIC you are using. It's full of clues. ;)
 
kchriste said:
I believe it's as simple as this:
PORTAbits.RA0 = 1;
Check the header file for the PIC you are using. It's full of clues. ;)

That is specific to the C18 complier. The |= will always work on any compiler.

Mike.
 
kchriste said:
I believe it's as simple as this:
PORTAbits.RA0 = 1;
Check the header file for the PIC you are using. It's full of clues. ;)

Yes you are quite correct. Infact on my compiler it is even simpler, you just use RA0 = 1; RC4 = 0; etc etc.

But I was reffering to a variable I have created in my program, not an I/O port of the PIC. Before posting I did check the header file to see how they accomplish the setting of the ports. I could see what they were doing but wasn't able to relate it to my application. The address of the I/O ports never changes so I couldn't copy the code they had used for the I/O ports :-/

Brian
 
Another option:
Code:
unsigned char var;   // your 8-bit variable
//...

var = (var<<1)&0xFE + RC5;
Brian Hoskins said:
If not, I guess I'll just revert to assembly for the set and clear bit operations.
If you want to 'force' the compiler to use specific instructions, you could use the #asm directive and mix C & assembly.
 
Last edited:
eng1 said:
Another option:
Code:
unsigned char var;   // your 8-bit variable
//...

var = (var<<1)&0xFE + RC5;

Your suggestion is to AND with a mask which is perfectly reasonable and I was aware of its use, but I hoped there would be a more efficient way of doing it for embedded systems that I wasn't aware of. Your code makes sense to me though - I guess "RC5" is the I/O pin where the stream is sampled (in my case RA0)

eng1 said:
If you want to 'force' the compiler to use specific instructions, you could use the #asm directive and mix C & assembly.

Yes that was what I meant by my original suggestion that I would revert to Assembly for the set and clear bit operations. I'm trying out Pommie's suggestion though and all is well so far. I had a different problem with a for loop though, I can't seem to work it out. The code I wrote was;

for (char i = 6; i > 0; i--)
{Code to be repeated 6 times here
}

The compiler complained at me for that. In the end I had to declare i outside of the for loop first - then it accepted it without issue.

Weird...

Brian
 
Brian Hoskins said:
Your suggestion is to AND with a mask which is perfectly reasonable and I was aware of its use, but I hoped there would be a more efficient way of doing it for embedded systems that I wasn't aware of. Your code makes sense to me though - I guess "RC5" is the I/O pin where the stream is sampled (in my case RA0)
Yes, RXx is the pin you're going to sample (ohh, you said RC5 type datastream! and RA0 is the I/O pin, I've got it now). I would suggest ANDing the byte just to ensure that the PIC will do what you expect. The code would be reliable without looking at the assembly code generated by the C compiler.
var<<1 will translate into an 'rlf' instruction, so if the carry bit is cleared before its execution, you don't need to use the mask. I usually include it, just in case... ;) Are you using those instructions in time-critical routines?

Have a nice day.
 
Last edited:
It is a time critical routine yes, although the PIC is running so quickly that it's spending quite a lot of its time waiting for something to happen on RA0 (The RC5 datastream happens quickly for me but slowly for the PIC). So I don't need to be extremely time concious, but if I can make something more efficient then I will if you know what I mean.

I've got my program working now. There are a few areas I need to improve on but according to the simulator (MPLAB IDE) the Address and Command data are collected and stored in their associated variables. The program synchronises to the data stream as well so timing differences between remote controls should not prevent the program collecting the data.

Considering this is my first attempt at a C program, I'm quite happy.

I love it when a plan comes together!

I won't tell you how much time I've spent though :-/

Brian
 
Any pic will be fast compared to the IR stream. You do not have to choose the fastest code. Put your effort into making it readable and portable.

Code:
#define IR_BIT PORTAbits.RA0

unsigned char  i, irByte;

for (char i = 6; i > 0; i--)
{
        irByte = irByte << 1;         // will shift in a zero
        irByte = irByte | IR_BIT;
}

You could write the lines inside the loop as a single line and that would be OK.

In general terse C code is harder for the compiler to optimize and harder for you to debug.
 
3v0 said:
Any pic will be fast compared to the IR stream. You do not have to choose the fastest code. Put your effort into making it readable and portable.

Code:
#define IR_BIT PORTAbits.RA0

unsigned char  i, irByte;

for (char i = 6; i > 0; i--)
{
        irByte = irByte << 1;         // will shift in a zero
        irByte = irByte | IR_BIT;
}

You could write the lines inside the loop as a single line and that would be OK.

In general terse C code is harder for the compiler to optimize and harder for you to debug.


I agree that a significant effort should be put into making code portable and readable. However, I feel it is useful to avoid a mindset where you waste resources and time just because your application allows you to do so. I won't spend hours optimising code that I don't have to, but I will always try to consider the most economical way of doing something if I can. I think it's useful to get used to thinking that way. Someone should tell Microsoft about this concept.

Regarding the for loop, it is my understanding that you can declare the variable inside the single line as well. That's pretty much the point of the for loop (everything is declared in one line). But my compiler complains about it unless I first declare my variable outside of the loop. Weird.

Actually I'm using a PIC 16F877 for this task :-/ Why? Well because I have a couple kicking around in my spares bin, and my programmer will deal with them. I also have some old PIC16F84s but my Pic Kit2 won't program those so I find them a hassle. But obviously it would make sense to target this application to a smaller device in the real world.

Brian
 
Ok I thought I'd paste my first version of the program incase any of you are interested. It works in the MPLAB simulator using a perfectly timed stimulus for the IR stream. I haven't tried it out on the hardware yet.

I would consider myself a novice in C programming so feel free to critisise if you want. If you read my comments though you will see that there are a few things I've already identified for improvement.

Apologies for the formatting - it looks fine in my compiler but a lot of the tabs have gone astray here.



Code:
// 	Title:			RC5 IR Receive Program
// 	Author:			Brian Hoskins
//	Version:		0.1
//	Date			01 March 2008

#include	<pic.h>
unsigned char Timer = 0x00;
unsigned char Address = 0x00;
unsigned char Command = 0x00;
bit Sync = 0;

void display_data (void)

{
	PORTB = Address;		// Display Address Data on PortB
	PORTC = Command;	// Display Command Data on PortC

	TMR0 = 0x00;
	while (TMR0 != Timer)	// Do nothing for a millisecond or so.  This ensures that the comms line has returned 
		{		// high before going back to main (), otherwise main will think there is a new
		}		// incoming datastream (data_validate should throw that out anyway though)
	
}


void process_data (void)

// Collect Address Data

{
	char i;
	for (i = 6; i>0; i--)
	{
		{
			while (RA0 != Sync)		// Wait for RA0 to equal sync, then we are at sync point 
				{
				}
			TMR0 = 0x00;			// Reset TMR0 then wait for it to equal Timer
			while (TMR0 != Timer)		// Do nothing until TMR0 == Timer 
				{
				}
			Address |= RA0;			// Set bottom bit of Address to RA0. (using logical OR) 
			Address = Address << 1;		// Shift Left Address
			Sync = !RA0;			// Set Sync to complement of RA0 
		}
	}
	Address = Address >> 1;				// Address Shift correction

// Collect Command Data

	for (i = 6; i>0; i--)
	{
		{
			while (RA0 != Sync)		// Wait for RA0 to equal sync, then we are at sync point 
				{
				}
			TMR0 = 0x00;			// Reset TMR0 then wait for it to equal Timer
			while (TMR0 != Timer)		// Do nothing until TMR0 == Timer 
				{
				}
			Command |= RA0;			// Set bottom bit of Command to RA0. (using logical OR) 
			Command = Command << 1;		// Shift Left Command
			Sync = !RA0;			// Set Sync to complement of RA0 
		}
	}
	Command = Command >> 1;				// Command Shift correction
	display_data () ;					// Display that data.

}

void data_validate (void)
	// The purpose of this function is as follows;
	// When this function is called it means PortA bit 0 has gone low, which indicates that there may be some incoming
	// data.  TMR0 is reset so that timing of the incoming data pulse is started.  The function then waits for PortA bit0
	// to return high again.  The TMR0 value is then stored in Timer, and this value is tested to ensure that it falls within
	// 20% of the 889uS duration of an RC5 communication pulse.  If it is within spec, the processing function is called
	// so that the data can be further processed.  If it is ouside of the limits, the function returns to main().  The value
	// of Timer is used as the timing for sampling of the data stream in process_data ().

	// AUTHOR NOTE: What if RA0 never returns high? Program will get stuck.  Function should timeout once TMR0 reaches 
	// a certain value (although if it never returns high there is a fault condition anyway)
{
	TMR0 = 0x00;			// Reset Timer to 0 
	while (RA0 == 0)			// Wait for PortA bit 0 to return high
		{
		}
	Timer = TMR0;			// Store Timer value 
	if (Timer > 0x6F && Timer <0x9D)	// Check Pulse duration was within 20% of that stated in RC5 protocol 
		{
		Timer = Timer + (Timer/2);	// After sync the process_data function must sample RA0 after 1.5 pulse durations.  This will
					// cause a sample to be taken in the middle of the required data pulse (which allows for a
					// timing error either side
		process_data () ;
		}
}



	void setup_hardware (void)
{								
	TRISC = 0x00;			// Set all of Port C for output
	TRISB = 0x00 ;			// Set all of Port B for output 
	TRISA = 0x01 ;			// Set bit 0 of Port A for input, the rest for output 						
	OPTION = 0b10000100;		// Set Option Reg for /32 Timer clocked on internal instructions 
	GIE = 0;				// Disable all interrupts 
	ADCON1 = 0b00000111;		// Disable A/D (otherwise you can't use RA0 for digital I/O) 
}



	void main (void)
{
	setup_hardware () ;		// Setup I/O ports and Registers etc

	while (1)
		{								 
		while (RA0 == 1)		// Do nothing while PortA bit 0 is high (wait for IR signal)
			{		// AUTHOR NOTE: This would be better as an interrupt
			}							
		data_validate () ;
		}
}
 
Last edited:
You can declare a variable at the start of any code block. When the compiler will not let you declare the variable within the for use.

Code:
#define IR_BIT PORTAbits.RA0

unsigned char  irByte;
...
{
    unsigned char i;

    for (char i = 6; i > 0; i--)
    {
        irByte = irByte << 1;         // will shift in a zero
        irByte = irByte | IR_BIT;
    }
....
}

I agree that a significant effort should be put into making code portable and readable. However, I feel it is useful to avoid a mindset where you waste resources and time just because your application allows you to do so. I won't spend hours optimising code that I don't have to, but I will always try to consider the most economical way of doing something if I can. I think it's useful to get used to thinking that way. Someone should tell Microsoft about this concept.
I was pointing out that terse C code can use more memory when compiled then the plain Jane variety because the compiler has to untangle it prior to optimization.

I am in favor of using programming techniques that favor economical use of resources.

The people at Microsoft work in an almost entirely different world in terms of code size and complexity. I too think they should do better and given time I think they will. But their management needs to learn a few lessons before that can happen.
 
I would suggest you're probably going about RC5 the wrong way?

889uS duration of an RC5 communication pulse

The whole point of Manchester coding is that you don't need accurate timing (because you don't get it with IR or wireless), it's the transitions from high to low, and low to high, that are the data - not the time between them.
 
Yes I think you're right, accurate timing is not required. Infact there is quite a large margin for error with my program. I decided to measure the pulse so that I could account for errors between remote controls. I didn't check a sample of remotes to see what the error was likely to be though, I just decided to write the software such that almost any error could be accounted for.

It works though - I was quite chuffed to be honest!

Brian
 
Oh, the other reason I timed the pulse was so that I could calculate where to sample. The program synchronizes on the high-low low-high transistions and then takes a measurement which should be not far off smack-bang in the middle of the pulse.

Hang on... I think I just understood what you're saying. Hmmm I'm gonna go back to my timing diagram and see if that would work. If it does, I've done quite a bit of work for nothing, but I learnt a lot so it doesn't matter anyway :)

Brian
 
You cannot ignore time. If you ignore time wont you always receive 1010101010. Surely, receiving Manchester encoded data is exactly the same as RS232 (timing wise) except you get twice as many bits.

Mike.
 
Here's a copy of the timing diagram I drew when thinking about how to write my program. The data is just a random address and command that I picked off the top of my head.

Brian
 

Attachments

  • RC5 Timing003.jpg
    RC5 Timing003.jpg
    186.8 KB · Views: 167
Status
Not open for further replies.

New Articles From Microcontroller Tips

Back
Top