1

I am programming a PIC18F94K20 to work in conjunction with a MCP7941X I2C RTCC ship and a 24AA128 I2C CMOS Serial EEPROM device. Currently I have code which successfully intialises the seconds/days/etc values of the RTCC and starts the timer, toggling a LED upon the turnover of every second.

I am attempting to augment the code to read back the correct data for these values, however I am running into trouble when I try to account for the various 'extra' bits in the values. The memory map may help elucidate my problem somewhat:

RTCC Memory Map

Taking, for example, the hours column, or the 02h address. Bit 6 is set as 1 to toggle 12 hour time, adding 01000000 to the hours bit. I can read back the entire contents of the byte at this address, but I want to employ an if statement to detect whether 12 or 24 hour time is in place, and adjust accordingly. I'm not worried about the 10-hour bits, as I can calculate that easily enough with a BCD conversion loop (I think).

I earlier used the bitwise OR operator in C to augment the original hours data to 24. I initialised the hours in this particular case to 0x11, and set the 12 hour control bit which is 0x64. When setting the time:

WriteI2C(0x11|0x64);

which as you can see uses the bitwise OR.

When reading back the hours, how can I incorporate operators into my code to separate the superfluous bits from the actual time bits? I tried doing something like this:

current_seconds = ReadI2C();
current_seconds = ST & current_seconds;

but that completely ruins everything. It compiles, but the device gets 'stuck' on this sequence.

How do I separate the ST / AMPM / VBATEN bits from the actual data I need, and what would a good method be of implementing for loops for the various circumstances they present (e.g. reading back 12 hour time if bit 6 = 0 and 24 hour time if bit6 = 1, and so on).

I'm a bit of a C novice and this is my first foray into electronics so I really appreciate any help. Thanks.

samanthapants
  • 77
  • 1
  • 1
  • 8
  • I don't think this question fits here. just read about C bitwise operators(|,&,<<) and bit manipulation. – Karoly Horvath Feb 20 '14 at 15:21
  • Do you recommend anywhere which might be more appropriate? Sorry if I've misplaced it. – samanthapants Feb 20 '14 at 15:24
  • Masking of the bits you want to look at is done with the & operator and the bits set to 1 that you want to separate. So if are you are looking from my read seconds would be perhaps simply converted with ( ( ( regdata & 0x7 ) * 10 ) + ( regdata & 0xf ); I think – kenny Feb 20 '14 at 15:38
  • Questions like these will probably get a better reception at http://electronics.stackexchange.com. SO is unfortunately over-populated by PC programmers. Still, you need to narrow down the actual question and don't ask several questions in one. – Lundin Feb 20 '14 at 15:44
  • @MartinJames No. Never use bit fields for bit mapping. In fact, don't use them for _anything_. [See this](http://stackoverflow.com/questions/6043483/why-bit-endianness-is-an-issue-in-bitfields/6044223#6044223). – Lundin Feb 20 '14 at 15:46
  • @Lundin - obviously, a packed directive would be required. I have used such an approach for many years with several compilers. No problem. – Martin James Feb 20 '14 at 16:23
  • @samanthapants Unrelated to the question itself, but your values in the `WriteI2C(0x11|0x64)` are not at all what you say you think they are - the 12-hour bit 6 is decimal `64`, whereas your hexadecimal `0x64` has bits 2, 5, and 6 set. In hexadecimal the correct value would be `0x40`, or you could just use bitwise shifts to arrive at the same value: `(1<<6)` – Arkku Feb 20 '14 at 19:01
  • http://stackoverflow.com/questions/47981/how-do-you-set-clear-and-toggle-a-single-bit-in-c-c – Karoly Horvath Feb 21 '14 at 01:24
  • @Arkku oops, I'm an idiot. Thanks for pointing that out Thanks for all the responses, everybody. It looks like my initial approach was far too simplistic, so I'll be implementing some of this now. Much appreciated. – samanthapants Feb 21 '14 at 13:07

2 Answers2

3

To remove (zero) a bit, you can AND the value with a mask having all other bits set, i.e., the complement of the bits that you wish to zero, e.g.:

value_without_bit_6 = value & ~(1<<6);

To isolate a bit within an integer, you can AND the value with a mask having only those bits set. For checking flags this is all you need to do, e.g.,

if (value & (1<<6)) {
    // bit 6 is set
} else {
    // bit 6 is not set
}

To read the value of a small integer offset within a larger one, first isolate the bits, and then shift them right by the index of the lowest bit (to get the least significant bit into correct position), e.g.:

value_in_bits_4_and_5 = (value & ((1<<4)|(1<<5))) >> 4;

For more readable code, you should use constants or #defined macros to represent the various bit masks you need, e.g.:

#define BIT_VBAT_EN (1<<3)

if (value & BIT_VBAT_EN) {
   // VBAT is enabled
}

Another way to do this is to use bitfields to define the organisation of bits, e.g.:

typedef union {
    struct {
        unsigned ones:4;
        unsigned tens:3;
        unsigned st:1;
    } seconds;
    uint8_t byte;
} seconds_register_t;

seconds_register_t sr;
sr.byte = READ_ADDRESS(0x00);
unsigned int seconds = sr.seconds.ones + sr.seconds.tens * 10;

A potential problem with bitfields is that the code generated by the compiler may be unpredictably large or inefficient, which is sometimes a concern with microcontrollers, but obviously it's nicer to read and write. (Another problem often cited is that the organisation of bit fields, e.g., endianness, is largely unspecified by the C standard and thus not guaranteed portable across compilers and platforms. However, it is my opinion that low-level development for microcontrollers tends to be inherently non-portable, so if you find the right bit layout I wouldn't consider using bitfields “wrong”, especially for hobbyist projects.)

Yet you can accomplish similarly readable syntax with macros; it's just the macro itself that is less readable:

#define GET_SECONDS(r) ( ((r) & 0x0F) + (((r) & 0x70) >> 4) * 10 )
uint8_t sr = READ_ADDRESS(0x00);
unsigned int seconds = GET_SECONDS(sr);
Arkku
  • 41,011
  • 10
  • 62
  • 84
1

Regarding the bit masking itself, you are going to want to make a model of that memory map in your microcontroller. The simplest, cudest way to do that is to #define a number of bit masks, like this:

#define REG1_ST          0x80u
#define REG1_10_SECONDS  0x70u
#define REG1_SECONDS     0x0Fu

#define REG2_10_MINUTES  0x70u
...

And then when reading each byte, mask out the data you are interested in. For example:

bool    st          = (data & REG1_ST) != 0;
uint8_t ten_seconds = (data & REG1_10_SECONDS) >> 4;
uint8_t seconds     = (data & REG1_SECONDS);

The important part is to minimize the amount of "magic numbers" in the source code.

Writing data:

reg1 = 0;
reg1 |= st ? REG1_ST : 0;
reg1 |= (ten_seconds << 4) & REG1_10_SECONDS;
reg1 |= seconds & REG1_SECONDS;

Please note that I left out the I2C communication of this.

Lundin
  • 195,001
  • 40
  • 254
  • 396