I am working on a Motorola HCS08 µCU in CodeWarrior V10.6, I am trying to create an extern bitfield which has bits from existing registers. The way the bitfields are created in the µCU header is like
typedef unsigned char byte;
typedef union {
byte Byte;
struct {
byte PTAD0 :1;
byte PTAD1 :1;
byte PTAD2 :1;
byte PTAD3 :1;
byte PTAD4 :1;
byte PTAD5 :1;
byte PTAD6 :1;
byte PTAD7 :1;
} Bits;
} PTADSTR;
extern volatile PTADSTR _PTAD #0x00000000;
#define PTAD _PTAD.Byte
#define PTAD_PTAD0 _PTAD.Bits.PTAD0
#define PTAD_PTAD1 _PTAD.Bits.PTAD1
#define PTAD_PTAD2 _PTAD.Bits.PTAD2
#define PTAD_PTAD3 _PTAD.Bits.PTAD3
#define PTAD_PTAD4 _PTAD.Bits.PTAD4
#define PTAD_PTAD5 _PTAD.Bits.PTAD5
#define PTAD_PTAD6 _PTAD.Bits.PTAD6
#define PTAD_PTAD7 _PTAD.Bits.PTAD7
Which will let the register value be changed either by PTAD = 0x01, or PTAD_PTAD0 = 1, for example. This definition is basically the same for PTAD, PTBD, PTCD, ... PTGD, the only thing changing is the address.
My attemp to create a custom bitfield out of the previous existing variables is
typedef union {
byte Byte;
struct {
byte *DB0;
byte *DB1;
byte *DB2;
byte *DB3;
byte *DB4;
byte *DB5;
byte *DB6;
byte *DB7;
} Bits;
} LCDDSTR;
I would create and initialize the bitfield as LCDDSTR lcd = {{&PTGD_PTGD6, &PTBD_PTBD5, ...}}, because by some reason, the initialization like LCDSTR lcd = {*.Bits.DB0 = &PTGD_PTGD6, *.Bits.DB1 = &PTBD_PTBD5, ...} (treating it as a struct, please correct me again) advice in How to initialize a struct in accordance with C programming language standards does not work with this compiler (it does work on an online compiler).
However, as you may see I am sort of grouping the bits, and (if it would work) I would be able to change the values of the actual register by doing *lcd.Bits.DB0 = 1, or something like that, but if I do lcd.Byte = 0x00, I would be changing the last (I think) byte of the memory address contained in lcd.Bits.DB0, you know, because the struct doesn't actually contains the data, but the pointers instead.
How would I go on achieving a struct that is able to contain and modify bits from several registers? (I guess the problem here is that in memory the bits are not one next to the other, which I guess would make it easier). Is it even possible? I hope it is.
How would I go on achieving a struct that is able to contain and modify bits from several registers? (I guess the problem here is that in memory the bits are not one next to the other..
I don't think you can do it with a struct. That is because bitfields by definition have to occupy the same or contiguous addresses.
However macros may be useful here
#define DB0 PTGD_PTGD6
#define DB1 PTBD_PTBD5
....
And to clear the bits to all 0's or set to all 1's you can use a multiline macro
#define SET_DB(x) do { \
PTGD_PTGD6 = x; \
PTBD_PTBD5 = x; \
...... \
} while(0)
How would I go on achieving a struct that is able to contain and modify bits from several registers?
You can't.
A structure must represent a single, continuous block of memory -- otherwise, operations like taking the sizeof the structure, or performing operations on a pointer to one would make no sense.
If you want to permute the bits of a value, you will need to find some way of doing so explicitly. If the order of your bits is relatively simple, this may be possible with a few bitwise operations; if it's weirder, you may need to use a lookup table.
Beyond that: bitfields in C are pretty limited. The language does not make a lot of guarantees about how a structure containing bitfields will end up laid out in memory; they are generally best avoided for portable code. (Which doesn't apply here, as you're writing code for a specific compiler/microcontroller combination, but it's worth keeping in mind in general.)
Your union does unfortunately not make any sense, because it forms a union of one byte and 8 byte*. Since a pointer is 16 bit on HCS08, this ends up as 8*2 = 16 bytes of data, which can't be used in any meaningful way.
Please note that the C structure called bit-fields is very poorly specified by the standard and therefore should be avoided in any program. See this.
Please note that the Codewarrior register maps aren't remotely close to following the C standard (nor MISRA-C).
Please note that structs in general are problematic for hardware register mapping, since structs can contain padding. You don't have that problem on HCS08 specifically, since it doesn't require alignment of data. But most MCUs do require that.
It is therefore better to roll out your own register map in standard C if you have that option. The port A data register could simply be defined like this:
#define PTAD (*(volatile uint8_t*)0x0000U)
#define PTAD7 (1U << 7)
#define PTAD6 (1U << 6)
#define PTAD5 (1U << 5)
#define PTAD4 (1U << 4)
#define PTAD3 (1U << 3)
#define PTAD2 (1U << 2)
#define PTAD1 (1U << 1)
#define PTAD0 (1U << 0)
As we can tell, defining the bit masks is mostly superfluous anyway, as PTAD |= 1 << 7; is equally readable to PTAD |= PTAD7;. This is because this was a pure I/O port. Defining textual bit masks for status and control registers on the other hand, increases the readability of the code significantly.
If you want to modify bits from several registers, you'd do something like the following:
Assume we have a RGB (red-green-blue) LED, common cathode, with 3 colors connected to 3 different pins on 3 different ports. Instead of beating up the PCB designer, you could do this:
#define RGB_RED_PTD PTAD
#define RGB_RED_PTDD PTADD
...
#define RGB_BLUE_PTD PTBD
#define RGB_BLUE_PTDD PTBDD
...
#define RGB_GREEN_PTD PTDD
#define RGB_GREEN PTDD PTDDD
#define RGB_RED_PIN 1
#define RGB_BLUE_PIN 5
#define RGB_GREEN_PIN 3
You can now set these independently of where they happen to be located on the hardware:
void rgb_init (void)
{
RGB_RED_PTDD |= (1 << RGB_RED_PIN);
RGB_BLUE_PTDD |= (1 << RGB_BLUE_PIN);
RGB_GREEN_PTDD |= (1 << RGB_GREEN_PIN);
}
void rgb_yellow (void)
{
RGB_RED_PTD |= (1 << RGB_RED_PIN);
RGB_BLUE_PTD &= ~(1 << RGB_BLUE_PIN);
RGB_GREEN_PTD |= (1 << RGB_GREEN_PIN);
}
And so on. Examples were for HCS08 but the same can of course be used universally on any MCU with direct port I/O.
It sounds like an approach such as the following is along the lines of where you would like to go with a solution.
I have not tested this as I do not have the hardware however this should provide an alternative to look at.
This assumes that you want to turn on particular pins or turn off particular pins but there will not be a case where you will want to turn on some pins and turn off other pins for a particular device in a single operation. If that should be the case I would consider making the type of RegPinNo be an unsigned short to include an op code for each register/pin number combination.
This also assumes that timing of operations is not a critical constraint and that the hardware has sufficient horsepower such that small loops are not much of a burden on throughput and hogging CPU time needed for other things. So this code may need changes to improve optimization if that is a consideration.
I assume that you want some kind of a easily readable way of expressing a command that will turn on and off a series of bits scattered across several areas of memory.
The first thing is to come up with a representation of what such a command would look like and it seems to me that borrowing from a char array to represent a string would suffice.
typedef byte RegPinNo; // upper nibble indicates register number 0 - 7, lower nibble indicates pin number 0 - 7
const byte REGPINNOEOS = 0xff; // the end of string for a RegPinNo array.
And these would be used to define an array of register/pin numbers as in the following.
RegPinNo myLed[] = { 0x01, 0x12, REGPINNOEOS }; // LED is addressed through Register 0, Pin 0 and Register 1, Pin 1 (zero based)
So at this point we have a way to describe that a particular device, an LED in this case, is addressed through a series of register/pin number items.
Next lets create a small library of functions that will use this representation to actually modify the specific pins in specific registers by traversing this array of register/pin numbers and performing an operation on it such as setting the bit in the register or clearing the bit in the register.
typedef unsigned char byte;
typedef union {
byte Byte;
struct {
byte PTAD0 : 1;
byte PTAD1 : 1;
byte PTAD2 : 1;
byte PTAD3 : 1;
byte PTAD4 : 1;
byte PTAD5 : 1;
byte PTAD6 : 1;
byte PTAD7 : 1;
} Bits;
} PTADSTR;
// Define a pointer to the beginning of the register area. This area is composed of
// 8 different registers each of which is one byte in size.
// We will address these registers as Register 0, Register 1, ... Register 7 which just happens
// to be how C does its zero based indexing.
// The bits representing pins on the PCB we will address as Pin 0, Pin 1, ... Pin 7.
extern volatile PTADSTR (* const _PTAD) = 0x00000000;
void SetRegPins(RegPinNo *x)
{
byte pins[] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 };
int i;
for (i = 0; x[i] != REGPINNOEOS; i++) {
byte bRegNo = (x[i] >> 4) & 0x07; // get the register number, 0 - 7
byte bPinNo = x[i] & 0x07; // get the pin number, 0 - 7
_PTAD[bRegNo].Byte |= pins[bPinNo];
}
}
void ClearRegPins(RegPinNo *x)
{
byte pins[] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 };
int i;
for (i = 0; x[i] != REGPINNOEOS; i++) {
byte bRegNo = (x[i] >> 4) & 0x07; // get the register number, 0 - 7
byte bPinNo = x[i] & 0x07; // get the pin number, 0 - 7
_PTAD[bRegNo].Byte &= ~pins[bPinNo];
}
}
void ToggleRegPins(RegPinNo *x)
{
byte pins[] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 };
int i;
for (i = 0; x[i] != REGPINNOEOS; i++) {
byte bRegNo = (x[i] >> 4) & 0x07; // get the register number, 0 - 7
byte bPinNo = x[i] & 0x07; // get the pin number, 0 - 7
_PTAD[bRegNo].Byte ^= pins[bPinNo];
}
}
You would use the above something like the following. Not sure what a time delay function would look like in your environment so I am using a function Sleep() which takes an argument as to the number of milliseconds to delay or sleep.
void LightLed (int nMilliSeconds)
{
RegPinNo myLed[] = { 0x01, 0x12, REGPINNOEOS }; // LED is addressed through Register 0, Pin 0 and Register 1, Pin 1 (zero based)
SetRegPins(myLed); // turn on the LED
Sleep(nMilliSeconds); // delay for a time with the LED lit
ClearRegPins(myLed); // turn the LED back off
}
Edit - A Refinement
A more efficient implementation that would allow multiple pins to be set in a particular register at the same time would be to define the use of RegPinNo as being an unsigned short` with the upper byte being the register number and the lower byte being the pins to manipulate as a bit mask for the byte.
With this approach you would have a SetRegPins() function that would look like the following. A similar change would be needed for the other functions.
void SetRegPins(RegPinNo *x)
{
int i;
for (i = 0; x[i] != REGPINNOEOS; i++) {
byte bRegNo = (x[i] >> 8) & 0x07; // get the register number, 0 - 7
byte bPinNo = x[i] & 0xFF; // get the pin mask
_PTAD[bRegNo].Byte |= bPinNo;
}
}
And the typedefs would look like:
typedef unsigned short RegPinNo; // upper byte indicates register number 0 - 7, lower byte provides pin mask
const byte REGPINNOEOS = 0xffff; // the end of string for a RegPinNo array.
And these elements would be used like:
void LightLed (int nMilliSeconds)
{
RegPinNo myLed[] = { 0x0002, 0x0103, REGPINNOEOS }; // LED is addressed through Register 0, Pin 1 and Register 1, Pin 0 and Pin 1 (zero based)
SetRegPins(myLed); // turn on the LED
Sleep(nMilliSeconds); // delay for a time with the LED lit
ClearRegPins(myLed); // turn the LED back off
}
Related
Came across this question during a past interview, but got no feedback.
Since its a register, would I need to disable interrupts before accessing the register inorder to prevent data corruption? Thought of using two buffers, 32 bit and 64 bit, and sending the 32 bit buffer into a read32() and shifting it over accordingly to the 64 bit buffer. Lets just assume this is little-endian architecture.
I wrote a quick sample code on repl.it (Output does not match register value)
#include <stdio.h>
#include <string.h>
#include <stdint.h>
void read32(uint64_t *reg, uint32_t *buffer){
memcpy(buffer, reg, 4);
}
int main(void) {
//register
uint64_t reg = 0xAAAAAAAAFFFFFFFF;
//buffers
uint32_t buf_32 = 0;
uint64_t buf_64 = 0;
//read LSW
read32(®, &buf_32);
buf_64 |= buf_32; //add LSW
//read MSW
read32(®+4, &buf_32);
buf_64 |= ((uint64_t)buf_32 << 32);
printf("64 bit register value: 0x%lx\n", buf_64);
return 0;
}
Output:
64 bit register value: 0x1ffffffff
Disabling interrupts will not prevent an I/O register that may change independently of the code sequence from changing.
Often where data consistency is required between two hardware registers that are larger than the architecture width, the hardware data sheet or reference manual will advise on how to read the data - usually by specifying the order in which the registers must be read to work with hardware mechanisms that make that "safe".
In other cases the method might be dictated by the nature of the registers and their function/behaviour. For example if you have two 32bit timer/counters, with the overflow of one triggering an increment of the other, to form a 64 bit counter, then clearly the high-order counter will only change when the low-order counter overflows. In that case you can simply read the low, then the high and repeat if the low has since wrapped :
uint32_t low_word = 0, high_word = 0;
do
{
low_word = *low_reg_addr ;
high_word = *high_reg_addr ;
} while( *low_reg_addr > low_word ) ; // If low has wrapped, re-read
uint64_t full_word = (uint64_t)high_word << 32 | low_word;
So if the low-order register not wrapped after the high-order register has been read, then the data must be consistent, and the loop exit. If it has wrapped, the data may not be consistent, and must be re-read.
One mistake I see with your code is ®+4 would increment the pointer to x + 32 bytes. This isn't what you want since it should be just x + 4 bytes. You might want to consider typecasting to uint32_t. ((uint32_t *)® + 1) should increment by 4 byte. However, you will have to change your read function accordingly too.
This should work...
void read32(uint32_t *reg, uint32_t *buffer){
memcpy(buffer, reg, 4);
}
int main(void) {
//register
uint64_t reg = 0xAAAAAAAAFFFFFFFF;
//buffers
uint32_t buf_32 = 0;
uint32_t buf1_32 = 0;
uint64_t buf_64 = 0;
uint64_t *ptr = ®
read32((uint32_t *)ptr, &buf_32);
printf("32 bit register value: 0x%lx\n", buf_32);
read32((uint32_t *)ptr+1, &buf1_32);
printf("32 bit register value: 0x%lx\n", buf1_32);
buf_64 = ((uint64_t)buf1_32 << 32 )| buf_32;
printf("64 bit register value: 0x%lx\n", buf_64);
return 0;
}
I'm not sure if this is happening when assigning a variable specifically but when debugging the assembly code, the compiler executes RJMP $+0000 where it hangs the program.
EDIT: I added included libraries if that's relevant
#define __DELAY_BACKWARD_COMPATIBLE__
#define F_CPU 8000000UL
#include <avr/io.h>
#include <avr/delay.h>
#include <stdint.h>
void ReadTemp(uint8_t address){
ADCSRA = ADCSRA | 0b10000111; //enable ADC, CLK/128 conversion speed
ADMUX = ADMUX | 0b01000000; //Use internal 2.56V Vref and PA0 as input, right-hand justified
ADCSRA |= (1 << ADSC); //start conversion
while(!(ADCSRA & (1 << ADIF))) {} // wait until process is finished;
uint8_t low_value = ADC & 0x00FF;
// or low_value = ADCL;
uint8_t high_value = ADC & 0xFF00; //problem here
...
}
I don't know what any of this is doing, but I do see an error in the bitwise math.
uint8_t low_value = ADC & 0x00FF;
uint8_t high_value = ADC & 0xFF00; //problem here
low_value and high_value are both 8 bits (uint8_t). I am going to go out on a limb here and say ADC is 16 bit. For high_value, you are anding ADC with 0xFF00 then truncating the value to 8 bit. high_value will always be zero.
What should be done is:
uint8_t high_value = (ADC & 0xFF00) >> 8;
This will grab the left byte of ADC and shift it right by 8 bits then assign it to the high_value byte storage giving you the correct value.
How you are doing low_value is correct. As a matter of fact, you could simply do:
uint8_t low_value = ADC;
There is something somewhat suboptimal here:
uint8_t low_value = ADC & 0x00FF;
You are reading ADC, which is a 16-bit register, implemented as a pair
of 8-bit registers. As shown in the disassembly, this requires two in
instructions, one per byte. And then you are just throwing away one of
those bytes. You may think that the compiler is smart enough to avoid
reading the byte it is going to discard right away. Unfortunately, it
cannot do that, as I/O registers are declared as volatile. The
compiler is forced to access the register as many times as the source
code does.
If you just want the low byte, you should read only that byte:
uint8_t low_value = ADCL;
You then wrote:
uint8_t high_value = ADC & 0xFF00;
As explained in the previous answer, high_value will be zero. Yet the
compiler will have to read the two bytes again, because the I/O register
is volatile. If you want to read the high byte, read ADCH.
But why would you want to read those two bytes one by one? Is it to put
them back together into a 16-bit variable? If this is the case, then
there is no need to read them separately. Instead, just read the 16-bit
register in the most straight-forward fashion:
uint16_t value = ADC;
A long time ago, gcc didn't know how to handle 16-bit registers, and
people had to resort to reading the bytes one by one, and then gluing
them together. You may still find very old example code on the Internet
that does that. Today, there is absolutely no reason to continue
programming this way.
Then you wrote:
//problem here
Nope, the problem is not here. That is not what generated the rjmp
instruction. The problem is probably right after that line, in the code
you have chosen not to post. You have some bug that manifests itself
only when optimizations are turned on. This is typical of code that
produces undefined behavior: works as expected with optimizations off,
then does weird “unexplainable” things when you enable optimizations.
In the Code below
void I2C_Write(uint8_t v_i2cData_u8)
{
uint8_t i;
for(i=0;i<8;i++) // loop 8 times to send 1-byte of data
{
SDA_PIN = v_i2cData_u8 & 0x80; // Send Bit by Bit on SDA line
i2c_Clock(); // Generate Clock at SCL
v_i2cData_u8 = v_i2cData_u8<<1;// Bring the next bit to be transmitted to MSB position
}
i2c_Clock();
}
in the statement: SDA_PIN = v_i2cData_u8 & 0x80; it is told that data will be sent bit by bit,if data is sent bit by bit then what will be stored in SDA_PIN,will SDA_PIN have the value = 0x80 or 1?
SDA_PIN will be assigned either 0x80 (128) or 0 depending on the high order bit of v_i2cData_u8 in that loop. If you want to insure a 0x01 byte is written, you would need to do this:
SDA_PIN = (v_i2cData_u8 & 0x80) ? 1 : 0;
I want to read and write from/to an unsigned char according to the table below:
for example I have following variables:
unsigned char hsi_div = 0x01; /* HSI/2 */
unsigned char cpu_div = 0x05; /* Fmaster/32 */
I want to write hsi_div to bits 4,3 and cpu_div to bits 2,1,0 (imagine the whole char is named CLK_DIVR):
CLK_DIVR |= hsi_div << 4; //not correct!
CLK_DIVR |= cpu_div << 2; //not correct!
And lets say I want to read the register back to make sure I did it correct:
if( ((CLK_DIVR << 4) - 1) & hsi_div) ) { /* SET OK */ }
if( ((CLK_DIVR << 2) - 1) & cpu_div) ) { /* SET OK */ }
Is there something wrong with my bitwise operations!? I do not get correct behaviour.
I assume CLK_DIVR is a hardware peripheral register which should be qualified volatile. Such registers should be set up with as few writes as possible. You change all write-able bits, so just
CLK_DIVR = (uint8_t)((hsi_div << 3) | (cpu_div << 0));
Note using fixed width type. That makes mentioniong it is an 8 bit register unnecessary. According to the excerpt, the upper bits are read-only, so they are not changed when writing. The cast keeps the compiler from issuing a truncation warning which is one of the recommended warnings to always enable (included in -Wconversion for gcc).
The shift count is actually the bit the field starts (the LSbit). A shift count of 0 means "no shifting", so the shift-operator is not required. I still use it to clarify I meant the field starts at bit 0. Just let the compiler optimize, concentrate on writing maintainable code.
Note: Your code bit-or's whatever already is in the register. Bit-or can only set bits, but not clear them. Addiionally the shift counts were wrong.
Not sure, but if the excerpt is for an ARM Cortex-M CPU (STM32Fxxxx?), reducing external bus-cycles becomes more relevant, as the ARM can take quite some cycles for an access.
For the HSIDIV bit fields you want:
hw_register = (hw_register & 0x18) | (hsi_value & 0x03) << 0x03;
This will mask the value to 2 bits wide then shift to bit position 3 and 4.
The CPUDIV fields are:
hw_register = (hw_register & 0x7) | (cpu_value & 7);
Reading the register:
hsi_value = (hw_register & 0x18) >> 3;
cpu_value = hw_register & 0x07;
Just
CLK_DIVR |= hsi_div << 3;
CLK_DIVR |= cpu_div << 0;
Since hsi_div is a 2-digit binary, you have to move it three positions to skip the CPUDIV field. And the cpu_div is already at the end of the field.
Currently I meet one technique issue, which makes me want to improve the previous implementation, the situation is:
I have 5 GPIO pins, I need use these pins as the hardware identifier, for example:
pin1: LOW
pin2: LOW
pin3: LOW
pin4: LOW
pin5: LOW
this means one of my HW variants, so we can have many combinations. In previous design, the developer use if-else to implement this, just like:
if(PIN1 == LOW && ... && ......&& PIN5 ==LOW)
{
HWID = variant1;
}
else if( ... )
{
}
...
else
{
}
but I think this is not good because it will have more than 200 variants, and the code will become to long, and I want changed it to a mask. The idea is I treat this five pins as a five bits register, and because I can predict which variant I need to assign according to GPIOs status(this already defined by hardware team, they provide a variant list, with all these GPIO pins configuration), therefore, the code may look like this:
enum{
variant 0x0 //GPIO config 1
...
variant 0xF3 //GPIO config 243
}
then I can first read these five GPIO pins status, and compare to some mask to see if they are equal or not.
Question
However, for GPIO, it has three status, namely: LOW, HIGH, OPEN. If there is any good calculation method to have a 3-D mask?
You have 5 pins of 3 states each. You can approach representing this in a few ways.
First, imagine using this sort of framework:
#define LOW (0)
#define HIGH (1)
#define OPEN (2)
uint16_t config = PIN_CONFIG(pin1, pin2, pin3, pin4, pin5);
if(config == PIN_CONFIG(LOW, HIGH, OPEN, LOW, LOW))
{
// do something
}
switch(config) {
case PIN_CONFIG(LOW, HIGH, OPEN, LOW, HIGH):
// do something;
break;
}
uint16_t config_max = PIN_CONFIG(OPEN, OPEN, OPEN, OPEN, OPEN);
uint32_t hardware_ids[config_max + 1] = {0};
// init your hardware ids
hardware_ids[PIN_CONFIG(LOW, HIGH, HIGH, LOW, LOW)] = 0xF315;
hardware_ids[PIN_CONFIG(LOW, LOW, HIGH, LOW, LOW)] = 0xF225;
// look up a HWID
uint32_t hwid = hardware_ids[config];
This code is just the sort of stuff you'd like to do with pin configurations. The only bit left to implement is PIN_CONFIG
Approach 1
The first approach is to keep using it as a bitfield, but instead of 1 bit per pin you use 2 bits to represent each pin state. I think this is the cleanest, even though you're "wasting" half a bit for each pin.
#define PIN_CLAMP(x) ((x) & 0x03)
#define PIN_CONFIG(p1, p2, p3, p4, p5) \\
(PIN_CLAMP(p1) & \\
(PIN_CLAMP(p2) << 2) & \\
(PIN_CLAMP(p3) << 4) & \\
(PIN_CLAMP(p4) << 6) & \\
(PIN_CLAMP(p5) << 8))
This is kind of nice because it leaves room for a "Don't care" or "Invalid" value if you are going to do searches later.
Approach 2
Alternatively, you can use arithmetic to do it, making sure you use the minimum amount of bits necessary. That is, ~1.5 bits to encode 3 values. As expected, this goes from 0 up to 242 for a total of 3^5=243 states.
Without knowing anything else about your situation I believe this is the smallest complete encoding of your pin states.
(Practically, you have to use 8 bits to encode 243 values, so it's higher 1.5 bits per pin)
#define PIN_CLAMP(x) ((x) % 3) /* note this should really assert */
#define PIN_CONFIG(p1, p2, p3, p4, p5) \\
(PIN_CLAMP(p1) & \\
(PIN_CLAMP(p2) * 3) & \\
(PIN_CLAMP(p3) * 9) & \\
(PIN_CLAMP(p4) * 27) & \\
(PIN_CLAMP(p5) * 81))
Approach 1.1
If you don't like preprocessor stuff, you could use functions a bit like this:
enum PinLevel (low = 0, high, open);
void set_pin(uint32_t * config, uint8_t pin_number, enum PinLevel value) {
int shift = pin_number * 2; // 2 bits
int mask = 0x03 << shift; // 2 bits set to on, moved to the right spot
*config &= ~pinmask;
*config |= (((int)value) << shift) & pinmask;
}
enum PinLevel get_pin(uint32_t config, uint8_t pin_number) {
int shift = pin_number * 2; // 2 bits
return (enum PinLevel)((config >> shift) & 0x03);
}
This follows the first (2 bit per value) approach.
Approach 1.2
YET ANOTHER WAY using C's cool bitfield syntax:
struct pins {
uint16_t pin1 : 2;
uint16_t pin2 : 2;
uint16_t pin3 : 2;
uint16_t pin4 : 2;
uint16_t pin5 : 2;
};
typedef union pinconfig_ {
struct pins pins;
uint16_t value;
} pinconfig;
pinconfig input;
input.value = 0; // don't forget to init the members unless static
input.pins.pin1 = HIGH;
input.pins.pin2 = LOW;
printf("%d", input.value);
input.value = 0x0003;
printd("%d", input.pins.pin1);
The union lets you view the bitfield as a number and vice versa.
(note: all code completely untested)
This is my suggestion to solve the problem
#include<stdio.h>
#define LOW 0
#define HIGH 1
#define OPEN 2
#define MAXGPIO 5
int main()
{
int gpio[MAXGPIO] = { LOW, LOW, OPEN, HIGH, OPEN };
int mask = 0;
for (int i = 0; i < MAXGPIO; i++)
mask = mask << 2 | gpio[i];
printf("Masked: %d\n", mask);
printf("Unmasked:\n");
for (int i = 0; i < MAXGPIO; i++)
printf("GPIO %d = %d\n", i + 1, (mask >> (2*(MAXGPIO-1-i))) & 0x03);
return 0;
}
A little explanation about the code.
Masking
I am using 2 bits to save each GPIO value. The combinations are:
00: LOW
01: HIGH
02: OPEN
03 is Invalid
I am iterating the array gpio (where I have the acquired values) and creating a mask in the mask variable shifting left 2 bits and applying an or operation.
Unmasking
To get the initial values I am just making the opposite operation shifting right 2 bits multiplied by the amount of GPIO - 1 and masking with 0x03
I am applying a mask with 0x03 because those are the bit I am interested.
This is the result of the program
$ cc -Wall test.c -o test;./test
Masked: 38
Unmasked:
GPIO 1 = 0
GPIO 2 = 0
GPIO 3 = 2
GPIO 4 = 1
GPIO 5 = 2
Hope this helps