В языке Си есть интересная структура union (объединение).
С помощью него можно, как ни странно, объединять несколько переменных в одной структуре так, чтобы они занимали общую область данных.
Это позволяет использовать отдельные "части" большей по размеру переменной (например, типа int или float) или наоборот, набирать из отдельных байт большую переменную.
Пример объединения для переменной для числа с плавающей запятой float, занимающей в памяти 32 бита (4 байта).
union my_float {
float f;
struct {
uint8_t byte0, byte1, byte2, byte3;
};
};
А теперь покажем, как это использовать на практике.
Имеется 12-битный АЦП MAX1241 с интерфейсом SPI.
Формат передачи следующий: сперва передается старший байт, потом - оставшиеся 4 байта, сдвинутые на 3 влево.
Можно было бы создать просто два отдельных байта, принять по очереди и объединить:
unsigned int ADC_read(void){
unsigned int byte0, byte1;
NSHDN = 1; // включить АЦП
delay_us(5); // пауза 5 мкс для инициализации
NCS = 0; // выбрать АЦП
while (!DOUT); // дождаться, пока преобразование не будет окончено
byte1 = spi(0); // считать старший байт
byte0 = spi(0); // считать младший байт
NCS = 1; // отменить выбор АЦП
NSHDN = 0; // выключить АЦП
byte0 = (byte1<<8) | byte0;
// привести данные с АЦП к напряжению: сдвинуть на 3 бита, ограничить 12 битами, умножить на разрешение (опорное напряжение/шкала = 5000 мВ/4095)
return ((byte0 >> 3)&0xfff)*VREF/SCALE;
}
Это работает, но с объединением, как по мне, код выглядит лучше.
Создадим объединение Word (слово), для объединения двух байт в 16-битную переменную (uint16_t):
/* Объединение для сохранения 2 байтового слова */
union Word {
unsigned char byte[2];
unsigned int word;
};
/* Функция считывания данных с АЦП */
unsigned int ADC_read(void){
union Word adc_data;
NSHDN = 1; // включить АЦП
delay_us(5); // пауза 5 мкс для инициализации
NCS = 0; // выбрать АЦП
while (!DOUT); // дождаться, пока преобразование не будет окончено
adc_data.byte[1] = spi(0); // считать старший байт
adc_data.byte[0] = spi(0); // считать младший байт
NCS = 1; // отменить выбор АЦП
NSHDN = 0; // выключить АЦП
// привести данные с АЦП к напряжению
return ((adc_data.word >> 3)&0xfff)*VREF/SCALE;
}
Программа стала чуть короче, мелочь, а приятно :)