Welcome, Guest
Username: Password: Secret Key Remember me

TOPIC: resgister like variables for I2C devices.

resgister like variables for I2C devices. 5 years 2 weeks ago #16772

  • Baldor
  • Baldor's Avatar
  • Offline
  • Expert Boarder
  • Posts: 135
  • Thanks received: 91
I find usefull to treat the registrers of I2C devices as if it were PIC registes. (Well, almost. :) I find specialy anoying to use ANDs & ORs to buid a register every time.
Here is were struct and union shine to build an almost register like variable.
Lets take a look at the configuration register of the INA219:

Is a 16bit wide register:
  • RST: Reset Bit
    Bit 15 Setting this bit to '1' generates a system reset that is the same as power-on reset. Resets all registers to default
    values; this bit self-clears.
  • BRNG: Bus Voltage Range
    Bit 13
    0 = 16V FSR
    1 = 32V FSR (default value)
  • PG: PGA (Shunt Voltage Only)
    Bits 11, 12
    Sets PGA gain and range. Note that the PGA defaults to รท8 (320mV range). Table 5 shows the gain and range for
    the various product gain settings.
  • BADC: BADC Bus ADC Resolution/Averaging
    Bits 7โ€“10 These bits adjust the Bus ADC resolution (9-, 10-, 11-, or 12-bit) or set the number of samples used when
    averaging results for the Bus Voltage Register (02h).
  • SADC: SADC Shunt ADC Resolution/Averaging
    Bits 3โ€“6
    These bits adjust the Shunt ADC resolution (9-, 10-, 11-, or 12-bit) or set the number of samples used when
    averaging results for the Shunt Voltage Register (01h).
  • MODE: Operating Mode
    Bits 0โ€“2
    Selects continuous, triggered, or power-down mode of operation. These bits default to continuous shunt and bus
    measurement mode
  • .

So, I found a practical way to work:

Create a typedef union struct. Notice that you must declare in the struct the lower bits first:
typedef union {
    struct {
        char mode   :3;
        char sadc   :4;
        char badc   :4;
        char pg     :2;
        char brng   :1;
        char        :1; //not used, alwais blank.
        char rst    :1;
    }INA219CONFIGBITS;
    struct {
        char LSB    :8;
        char MSB    :8;
    }INA219CONFIGBYTES;
    int INA219CONFIG;
}INA219configT;

Notice that I also declared both bytes for easy of writing to the I2C device. The int INA219CONFIG is there only for completeness, since I don't think I will use it in the code.


Now that we have a new variable type, whe declare the variable:
extern INA219configT INA219Configuration[2];

Since I'm using two INAs, it is a 2 elements matrix for easy reference.

And now, some macros to refer to the variables as whe where using PIC registers.
#define INA219CONFIGbits(t) INA219Configuration[(t)].INA219CONFIGBITS
#define INA219CONFIG_LSB(t)  INA219Configuration[(t)].INA219CONFIGBYTES.LSB
#define INA219CONFIG_MSB(t)  INA219Configuration[(t)].INA219CONFIGBYTES.MSB
#define INA219CONFIG(t)  INA219Configuration[(t)].INA219CONFIG

From here, it is easy. after initializing the "register" to default walues, the only we need to modify the register in the INA219 is:
INA219configbits(0).pg = 0; //12 bits voltage range.

//pseudocode
WriteI2C(adress write);
WriteI2C(configregister);
WriteI2C(INA219confMSB);
WriteI2C(INA219confLSB);

Of course, we can modify all the parameters we want before writing the register.

An added benefit, is we can read at any time the values in the "register" and use something like this to know, for example, the adquisition time for the current config:
const struct  {
    char res;
    char average;
    long int time;
   }INA219Resolution[16] = {
    {9,1,84},
    {10,1,148},
    {11,1,276},
    {12,1,532},
    {9,1,84}, //duplicate value
    {10,1,148}, //duplicate value
    {11,1,276}, //duplicate value
    {12,1,532}, //duplicate value
    {12,1,532}, //default value
    {12,2,1060},
    {12,4,2013},
    {12,8,4026},
    {12,16,8150},
    {12,32,17020},
    {12,64,34050},
    {12,128,68100}
  };
time = INA219Resolution(INA219configbits(t).sadc).time;

I left out of the post all the macros for the register values, like
//SHUNT RANGE
#define INA219_40mV     0b00
#define INA219_80mV     0b01
#define INA219_160mV    0b10
#define INA219_320mV    0b11 //default

since I think is something already known, and will only fill the post with unnecesary information. I can post the complete header and source files for the INA219 in another post.

I'm sure some of the resident C gurus will correct me. Corrections and sudgeriments are very well welcomed.

Thanks for the patience if you read this to the end. Maybe an article once all the kirks are ironed out?
Aprendiz de mucho, maestro de casi nada.

resgister like variables for I2C devices. 5 years 2 weeks ago #16773

  • Jon Chandler
  • Jon Chandler's Avatar
  • Offline
  • Moderator
  • Posts: 365
  • Thanks received: 352
Swordfish has structures like C but I must admit I haven't managed to wrap my head around understanding how / where to use them. This looks like it could be useful.

resgister like variables for I2C devices. 5 years 2 weeks ago #16774

  • jmessina
  • jmessina's Avatar
  • Offline
  • Senior Boarder
  • Posts: 44
  • Thanks received: 189
I like using them too, but you have to be careful. C doesn't guarantee how the structure is represented in memory (high byte/lowbyte, bit location, etc), so it can change if you use a different compiler. Just make sure it does what you want. Also, it's a good idea to use unsigned type for the bitfileds...for a byte something like

union {
    unsigned char byte;
    struct {
        unsigned RB0:1;
        unsigned RB1:1;
        unsigned RB2:1;
        unsigned RB3:1;
        unsigned RB4:1;
        unsigned RB5:1;
        unsigned RB6:1;
        unsigned RB7:1;
   } bits;
} portb;

portb.bits.RB0 = 1;
portb.bits.RB1 = 1;
PORTB = portb.byte;

Unfortunately, Swordfish structures aren't quite as flexible... it doesn't allow variable length bitfields, and you can't initialize a const array of structures (which means you can't have them prebuilt in ROM).

resgister like variables for I2C devices. 5 years 1 week ago #16796

  • Baldor
  • Baldor's Avatar
  • Offline
  • Expert Boarder
  • Posts: 135
  • Thanks received: 91
Mi gozo en un pozo. XC8 shift arround the bits of the struc it they don't fit a Byte, even if you map it to an unsigned int with the union. I hadn't had time to test the code until today, and the config register is being writen wrong. The padding bit is put at the end of the lower byte, breaking all the config.

This method only works with byte wide registers, or when the bitfields align nicely in 8 bits groups. (Why couldnt TI put the unused bit in D8 instead of D14?)
Aprendiz de mucho, maestro de casi nada.

resgister like variables for I2C devices. 5 years 1 week ago #16801

  • jmessina
  • jmessina's Avatar
  • Offline
  • Senior Boarder
  • Posts: 44
  • Thanks received: 189
That's a common limitation.

From the XC8 manual:
Bit-fields are always allocated within 8-bit words, even though it is usual to use the type
unsigned int in the definition.

The first bit defined will be the LSb of the word in which it will be stored. When a bit-field
is declared, it is allocated within the current 8-bit unit if it will fit; otherwise, a new byte
is allocated
within the structure. Bit-fields can never cross the boundary between 8-bit
allocation units.
Time to create page: 0.290 seconds