我正在编程PIC18F94K20,以便与MCP7941X I2C RTCC和24AA128 I2C CMOS串行EEPROM器件配合使用。目前我的代码成功地对RTCC的秒/天/等值进行了初始化并启动了计时器,在每秒的周转时切换LED。
我正在尝试增加代码以回读这些值的正确数据,但是当我尝试考虑值中的各种“额外”位时,我遇到了麻烦。记忆图可能有助于解释我的问题:
例如,以小时栏或02h地址为例。位6设置为1以切换12小时时间,将小时位添加01000000。我可以在此地址读回字节的全部内容,但我想使用if语句来检测是否有12或24小时的时间,并进行相应的调整。我并不担心10小时的比特,因为我可以通过BCD转换循环轻松地计算出来(我认为)。
我之前使用C中的按位OR运算符将原始小时数据增加到24.我将此特定情况下的小时数初始化为0x11,并将12小时控制位设置为0x64。设置时间时:
WriteI2C(0x11|0x64);
你可以看到使用按位OR。
当回读小时时,如何将运算符合并到我的代码中以将多余的位与实际的时间位分开?我尝试过这样的事情:
current_seconds = ReadI2C();
current_seconds = ST & current_seconds;
但这完全破坏了一切。它编译,但设备被“卡住”在这个序列上。
如何将ST / AMPM / VBATEN位与我需要的实际数据分开,以及针对它们出现的各种情况实现循环的好方法是什么(例如,读取12小时时间如果位如果bit6 = 1,则6 = 0和24小时,依此类推。
我有点像C新手,这是我第一次涉足电子产品,所以我非常感谢任何帮助。感谢。
答案 0 :(得分:3)
要删除(零)一个位,您可以使用设置了所有其他位的掩码对该值进行AND运算,即,您希望为零的位的补码,例如:
value_without_bit_6 = value & ~(1<<6);
要隔离整数内的某个位,可以使用仅设置了这些位的掩码对该值进行AND运算。对于检查标志,这就是你需要做的,例如,
if (value & (1<<6)) {
// bit 6 is set
} else {
// bit 6 is not set
}
要读取较大的整数偏移量的值,首先将这些位隔离,然后将它们向右移动最低位的索引(以使最低有效位进入正确位置),例如:
value_in_bits_4_and_5 = (value & ((1<<4)|(1<<5))) >> 4;
对于更易读的代码,您应该使用常量或#define
d宏来表示您需要的各种位掩码,例如:
#define BIT_VBAT_EN (1<<3)
if (value & BIT_VBAT_EN) {
// VBAT is enabled
}
另一种方法是使用位域来定义位组织,例如:
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;
位域的一个潜在问题是编译器生成的代码可能无法预测大或效率低,这有时是微控制器的一个问题,但显然它的读写更好。 (经常提到的另一个问题是位域的组织,例如字节序,很大程度上没有被C标准规定,因此不能保证在编译器和平台上可移植。但是,我认为微控制器的低级开发往往是本质上是不可移植的,所以如果你找到合适的位布局我就不会考虑使用bitfields“错误”,特别是对于业余爱好者项目。)
然而,你可以用宏完成类似的可读语法;它只是宏本身不太可读:
#define GET_SECONDS(r) ( ((r) & 0x0F) + (((r) & 0x70) >> 4) * 10 )
uint8_t sr = READ_ADDRESS(0x00);
unsigned int seconds = GET_SECONDS(sr);
答案 1 :(得分:1)
关于位屏蔽本身,您将要在微控制器中创建该存储器映射的模型。最简单,最粗鲁的方法是#define
多个位掩码,如下所示:
#define REG1_ST 0x80u
#define REG1_10_SECONDS 0x70u
#define REG1_SECONDS 0x0Fu
#define REG2_10_MINUTES 0x70u
...
然后在读取每个字节时,屏蔽掉您感兴趣的数据。例如:
bool st = (data & REG1_ST) != 0;
uint8_t ten_seconds = (data & REG1_10_SECONDS) >> 4;
uint8_t seconds = (data & REG1_SECONDS);
重要的是要尽量减少源代码中的“幻数”。
写数据:
reg1 = 0;
reg1 |= st ? REG1_ST : 0;
reg1 |= (ten_seconds << 4) & REG1_10_SECONDS;
reg1 |= seconds & REG1_SECONDS;
请注意,我遗漏了这个I2C通信。