I am trying to make write a code for i2c communication with a device (ADS1115). The communication uses different arrays of bits for commands and sending data back, in which most bits or group of bits have different meanings. So, I did the natural thing that came to my mind and wrote enum classes like this:
enum class latching_comparator : bool
{
non_latching= false, //default
latching = true
};
enum class comparator_polarity : bool
{
low = false, //default
high= true
};
I know I can derive my enum class from uint8_t and uint16_t for 8 and 16 bit entities but I don't need them. What I need instead are 2 and 3 bit entities. However, I can't do this:
typedef std::bitset<2> twobits;
enum class comperator : twobits
{
...
}
Is there a way I can group bits like this and make it easy to write further codes using this? Is there a way to combine and get back bits/groups of bits this way?
Suggestions for any other method of doing this is also welcome.
You can use bit fields in a struct to accomplish this. Depending on what you are doing, you may need to use #pragma pack
which will fix alignment issues.
enum class latching_comparator : bool
{
non_latching= false, //default
latching = true,
};
enum class comparator_polarity : bool
{
low = false, //default
high= true,
};
#pragma pack(push, 1)
typedef struct
{
comparator_polarity polarity : 1;
latching_comparator latching : 1;
uint8_t test : 3;
} comparator_t;
#pragma pack(pop)
This says that each enum in the struct uses only 1 bit. The test member uses 3 bits, which could be a 3 bit value or flag member, just as an example. This will only use one byte for both enums. Technically it only uses 5 bits, but 8 bit arch technically can't go lower, so you always use at least 8. If you use 9 bits in the struct, it will use 2 bytes since you move into another byte for bit 9.
std::cout << sizeof(comparitor_t) << std::endl;
Will print 1
because it only uses one byte to accomplish this.
I didn't read the documentation on the hardware you're interfacing with, but it would be best to design the entire struct around the registers you're configuring. The bit sizes will vary for each configuration in that register, but generally I write one struct for each register, manipulate the struct, then i just output the bytes of the struct straight into the communication interface, no need to hassle with bit shifting etc.
Edit: I'm feeling generous today, so here is some of the code to work with the config register of that chip (reading or writing). The register has more configurations in it, so you still need to finish it, but it gives you a direct example of what I'm talking about.
typedef enum
{
ADS115_CONFIG_COMP_QUE_ASSERT_ONE = 0b00,
ADS115_CONFIG_COMP_QUE_ASSERT_TWO = 0b01,
ADS115_CONFIG_COMP_QUE_ASSERT_FOUR = 0b10,
ADS115_CONFIG_COMP_QUE_ASSERT_DISABLE = 0b11
} ads115_config_comp_que_t;
typedef enum
{
ADS115_CONFIG_COMP_LAT_NONLATCHING = 0b0,
ADS115_CONFIG_COMP_LAT_LATCHING = 0b1
} ads115_config_comp_lat_t;
typedef enum
{
ADS115_CONFIG_COMP_POL_ACTIVE_LOW = 0b0,
ADS115_CONFIG_COMP_POL_ACTIVE_HIGH = 0b1,
} ads115_config_comp_pol_t;
typedef enum
{
ADS115_CONFIG_COMP_MODE_TRADITIONAL = 0b0,
ADS115_CONFIG_COMP_MODE_WINDOW = 0b1
} ads115_config_comp_mode_t;
typedef enum
{
ADS115_CONFIG_DR_8_SPS = 0b000,
ADS115_CONFIG_DR_16_SPS = 0b001,
ADS115_CONFIG_DR_32_SPS = 0b010,
ADS115_CONFIG_DR_64_SPS = 0b011,
ADS115_CONFIG_DR_128_SPS = 0b100,
ADS115_CONFIG_DR_250_SPS = 0b101,
ADS115_CONFIG_DR_475_SPS = 0b110,
ADS115_CONFIG_DR_860_SPS = 0b111,
} ads115_config_dr_t;
typedef enum
{
ADS115_CONFIG_MODE_CONTINUOUS = 0b0,
ADS115_CONFIG_MODE_ONESHOT = 0b1,
} ads115_config_mode_t;
#pragma pack(push, 1)
typedef struct
{
ads115_config_comp_que_t comp_que : 2;
ads115_config_comp_lat_t comp_lat : 1;
ads115_config_comp_pol_t comp_pol : 1;
ads115_config_comp_mode_t comp_mode : 1;
ads115_config_dr_t dr : 3;
ads115_config_mode_t mode : 1;
// @TODO: other configurations
} ads115_reg_config_t;
#pragma pack(pop)