Nelson
Nelson

Reputation: 339

Converting bit-shift operations to specific binary number format (for a PIC microcontroller)

I am working on several home automation projects with PIC12F675. To maximize portability, I am using bit-shift operations to map GPIOs, like this:

// Hardware Mapping
#define pressure_pin  (1<<0)  // Pressue sensor output (analog input) 
#define button_pin    (1<<1)  // Multi-function button (digital input)
#define buzzer_pin    (1<<2)  // NPN transitor to activate buzzer (digital output)
#define pause_pin     (1<<4)  // NPN transistor to pause button (digital output)
#define led_pin       (1<<6)  // Led (digital output)
#define PORT          GPIO    // Port 

To set input pins ('pressure_pin" and 'button_pin'), it is necessary change TRISIO, ANSEL and ADCON0 registers. TRISIO and ANSEL changes are easily done by:

TRISIO |= (button_pin | pressure_pin);  // Output pins 
ANSEL = (0x50 | pressure_pin);          // Fosc/16 and 'pressure_pin' as analog input`

ADCON0 must have bits 7 and 0 setted and bits 3 and 2 must receive the GPIO number used as analog input, as shown in the figure below (extracted from PIC12F675 datasheet)

enter image description here

So, I need ADCON0 to receive one of the following values ​​(according to 'pressure_pin' definition):

pressure_pin ADCON0
(1<<0) 0x81
(1<<1) 0x85
(1<<2) 0x89
(1<<3) 0x8D

My solution was:

ADCON0 = 0x81;  // 0b10000001. ADC value right justified ; ADC turned on
ADCON0 |= (((0x82>>(pressure_pin-1))&0x1)<<2) | (((0x88>>(pressure_pin-1))&0x1)<<3);

This code snippet reproduces my solution in C language:

int main(int, char**)
{
    unsigned char pressure_pin = (1<<3);
    unsigned char ADCON0 = 0x81;
    ADCON0 |= (((0x82>>(pressure_pin-1))&0x1)<<2) | (((0x88>>(pressure_pin-1))&0x1)<<3);   
    return 0;
}

Considering PIC12F675 restrictions,is there a more elegant and/or more efficient solution for this conversion?

Thanks in advance.

Upvotes: 0

Views: 83

Answers (2)

Kozmotronik
Kozmotronik

Reputation: 2520

As far as I understand, your goal is to be able to set only some specific bits in a register. Do I get it correct?

If so, you can use basic masking and shifting operations in a function to set the channel of the ADC. I recommend you to do it in a function because might need to change the ADC channel more than once in your application. Doing this way will reduce the ROM usage. So the following snippet provides you a sight on how it would be implemented: Let's define some constants for masking and shifting:

#define ADC_CHANNEL_MASK    0xC // or 0b00001100
#define ADC_CHANNEL_OFFSET  2   // bit offset to be used in shift operations

Now let's implement the ADC_setChannel function:

void ADC_setChannel(char channel) {
    // clear the channel bits first
    ADCON0 &= ~ADC_CHANNEL_MASK;
    // mask the channel parameter for 2 bits first, then left shift the value 2 times
    // then or it with the current register value. This way only tha channel bits
    // will be modified
    ADCON0 |= (channel & 3) << ADC_CHANNEL_OFFSET;
    // Delay for 20 micros to comply the acquisition time after changing the channel
    // See the ADC section of datasheet for the calculation of acquisition time
    __delay_us(20);
}

However, if your application uses only one ADC channel for a lifetime, then you don't need to implement it as a function. Instead, you would set it up once in the main function before entering the main loop. The same operations but not in a function. See the following snippet:

// The definitions made above + your pin definitions
...

void main(void) {
    // Other init codes go here...
    ...
    ADCON0 &= ~ADC_CHANNEL_MASK;
    ADCON0 |= 2 << ADC_CHANNEL_OFFSET; // Let's say you wanna select 2nd AN channel
    __delay_us(20);
    ...

    while(1) {
        // Application code
    }
}

But if you also want to map the port pins to channel numbers then you would use a simple array that holds channel numbers that corresponds to the port number. In this case there are 4 AN channels whose inputs are GP0, GP1, GP2 and GP4, respectively. And those numbers are highly variable depending on the MCU model. That's why I don't know any formula to solve this pin to channel mapping for all devices using bit shifts since the bit placement is not sequential. Using arrays are more practical to create this type of mapping since the numbers are not big. The array would look like this:

// We add new definitions for ADC
#define GPIO_PIN_COUNT 7

// The indexes represent the GPIO pin numbers. So if the corresponding pin does not have
// analog feature, this array will give us -1.
char getChannelForPin[GPIO_PIN_COUNT] = { 0, 1, 2, -1, 3, -1, -1 };

// Then you would use that array like this in the application
char channel = getChannelForPin[pressure_pin];
if (channel >= 0) {
    ADC_setChannel(channel);
}
else {
    // Handle; that pin does not have analog feature
}

Upvotes: 1

0___________
0___________

Reputation: 67820

Maybe lookup table. No calculations needed.

const unsigned char data[] = {[1 << 0] = 0x81, [1 << 1] = 0x85, [1 << 2] = 0x89, [1 << 3] = 0x8D};
unsigned char foo(unsigned pressurepin)
{
    return data[pressurepin];
}

I do not have pic compiler but similar 8 bit code generated for AVR shows that it will be much faster (and most likely smaller)

https://godbolt.org/z/j9aWP3Tqx

Upvotes: 1

Related Questions