Затык с обработкой трёхбайтного значения от SPI

Peotr
Peotr аватар
Offline
Зарегистрирован: 17.02.2014

Здравствуйте, досточтимые носители семи пядей во лбу!
Уповаю на вашу помощь.
Юзаю 24-разрядную АЦП-уху с SPI-интерфейсом, и обломался на выводе результата.
Монитор несёт какую-то хрень, а на вид, вроде, всё правильно задано.
Своими мОзгами иссяк, теперь либо в пивную, либо к знатокам :)
Ниже привожу нарезки из скетча.
Спасибо.

#include <SPI.h>

byte byte_1, byte_2, byte_0;
volatile long sensorValue = 0;

// ***
void ADC_read()
{
  digitalWrite(ADC_CS_Pin, LOW);
//Чтение регистра Data:
  SPI.transfer(0x42);
  
  byte_0=SPI.transfer(0);
  byte_1=SPI.transfer(0);
  byte_2=SPI.transfer(0);

  digitalWrite(ADC_CS_Pin, HIGH);

// в случае следующей запси:
  sensorValue = byte_2|(byte_1<<8)|((long)byte_0<<16);
//имеем на мониторе вместо sensorValue просто УЖОС:

DATA: 
FFFFF991
27
F9
91


//(Здесь, правда, сразу хочется спросить, а почему работает:
  byte_2|(byte_1<<8);
//хотя логика подсказывает следующее:
  byte_2|((int)byte_1<<8);
//???)

//а при такой:
  sensorValue = byte_2+(byte_1<<8)+((long)byte_0<<16);
//получаем на мониторе искажённый sensorValue:

DATA: 
26FCC0
27
FC
C0


// И шо теперь?
}
// ***
void loop() 
{
	ADC_read();
	Serial.println("DATA: ");
	Serial.println(sensorValue,HEX);

	Serial.println(byte_0,HEX);
	Serial.println(byte_1,HEX);
	Serial.println(byte_2,HEX);

// ***
}
andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Замените в строке сборки (long) на (unsigned long).

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

Дык всё верно у вас происходит: long имеет максимальное положительное значение в 65535, потом - заворот кишок в отрицательные значения, при переполнении. Хотите сдвигом манипулировать большими значениями в положительной области (а тип byte в качестве носителей отдельных байт сборного значения какбэ намекает, что имеет место быть беззнаковый тип) - юзайте unsigned long.

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

А ишшо проще - юзать массив и копировать:

byte byteArray[4] = {0};
byteArray[0] = 27;
byteArray[1] = 0xF9;
byteArray[2] = 0x91;

unsigned long ulVal;
memcpy(&ulVal,byteArray,sizeof(unsigned long));

Порядок следования байт в массиве byteArray - подберите по вкусу ;)

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

DIYMan пишет:

 long имеет максимальное положительное значение в 65535

ты не прав, Аркаша. 

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

DIYMan пишет:

А ишшо проще - юзать массив и копировать:

byte byteArray[4] = {0};
byteArray[0] = 27;
byteArray[1] = 0xF9;
byteArray[2] = 0x91;

unsigned long ulVal;
memcpy(&ulVal,byteArray,sizeof(unsigned long));

Порядок следования байт в массиве byteArray - подберите по вкусу ;)

А можно просто использовать union.

union
{
   long Value;
   byte ValueBytes[4];
} SPIData;

 

Penni
Penni аватар
Offline
Зарегистрирован: 18.01.2015

Если через сдвиг хочется, то правильно будет сдвигать результурующую переменную, а не заниматься приведением типов.

sensorValue = SPI.transfer(0);
sensorValue = (sensorValue << 8) | SPI.transfer(0);
sensorValue = (sensorValue << 8) | SPI.transfer(0);

 

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

Уже показали наиправильнейший вариант с юнионом, все остальные варианты просто меркнут на его фоне :)

Peotr
Peotr аватар
Offline
Зарегистрирован: 17.02.2014

DIYMan пишет:

Дык всё верно у вас происходит: long имеет максимальное положительное значение в 65535, потом - заворот кишок в отрицательные значения, при переполнении. Хотите сдвигом манипулировать большими значениями в положительной области (а тип byte в качестве носителей отдельных байт сборного значения какбэ намекает, что имеет место быть беззнаковый тип) - юзайте unsigned long.

Дык, сам об етом думал, да вот в чём загвоздка:

"Unsigned long используется для хранения положительных целых чисел в диапазоне от 0 до 4,294,967,295 (2^32 - 1) и занимает 32 бита (4 байта) в памяти". [http://arduino.ru/Reference/UnsignedLong]

Спасибо.

Peotr
Peotr аватар
Offline
Зарегистрирован: 17.02.2014

DetSimen пишет:

А можно просто использовать union.

union
{
   long Value;
   byte ValueBytes[4];
} SPIData;

 

Этто мы не проходили... Где можно погрызть union?

Спасибо.

Peotr
Peotr аватар
Offline
Зарегистрирован: 17.02.2014

Penni пишет:

Если через сдвиг хочется, то правильно будет сдвигать результурующую переменную, а не заниматься приведением типов.

sensorValue = SPI.transfer(0);
sensorValue = (sensorValue << 8) | SPI.transfer(0);
sensorValue = (sensorValue << 8) | SPI.transfer(0);

 

Замечательно! Только мне нужно было для проверки преобразования выводить и побайтно.

Спасибо.

Peotr
Peotr аватар
Offline
Зарегистрирован: 17.02.2014

andriano пишет:

Замените в строке сборки (long) на (unsigned long).

Не помогло :((( Что за хрень? И не понятно, почему результат с побитным ИЛИ не совпадает со сложением, хотя, вроде, это одно и то же в данном случае.

Спасибо.

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

в union собираются переменные, которые занимают в памяти одно и то же место.

тоись, ты можешь присвоить SPIData.Value число типа long и потом обратиться к каждому его байту отдельно через SPIValue.ValueBytes[0..3]

а можешь наоборот, пхать в ValueBytes байты последовательно и считать потом весь long, чо там получилось. 

Подробнее  RTFM->MSDN().

Клапауций 112
Клапауций 112 аватар
Offline
Зарегистрирован: 01.03.2017

DetSimen пишет:

в union собираются переменные, которые занимают в памяти одно и то же место.

скажите, а нет у вас такого юнион, что бы новые данные просто сдвигали старые - нужно хранить два значения переменной: новое и пердыдущее.

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

как раз у мня есть, сам писал.  TStack называеца. 

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Peotr пишет:

andriano пишет:

Замените в строке сборки (long) на (unsigned long).

Не помогло :((( Что за хрень? И не понятно, почему результат с побитным ИЛИ не совпадает со сложением, хотя, вроде, это одно и то же в данном случае.

Спасибо.

void setup() {
  byte byte_1 = 0xF9, byte_2 = 0x91, byte_0 = 0x27;
  long sensorValue_0 = byte_2|((unsigned)byte_1<<8)|((unsigned long)byte_0<<16);;
  long sensorValue_1 = byte_2+((unsigned)byte_1<<8)+((unsigned long)byte_0<<16);
  Serial.begin(115200);
  Serial.println("DATA: ");
  Serial.println(sensorValue_0,HEX);
  Serial.println(sensorValue_1,HEX);

  Serial.println(byte_0,HEX);
  Serial.println(byte_1,HEX);
  Serial.println(byte_2,HEX);
}

void loop() {
  // put your main code here, to run repeatedly:

}


DATA: 
27F991
27F991
27
F9
91

 

PS. Но наиболее прямой метод, естественно, с union. Просто его писать чуть дольше.

Peotr
Peotr аватар
Offline
Зарегистрирован: 17.02.2014

andriano, большущее спасибо! Вот где собака порылась: раньше работал с двухбайтными конструкциями, и byte_2+(byte_1<<8) вполне прокатывало.

Правда, как это работает я так и не понял. Переполнения разрядов не вижу, отрицательных значений на входе не задаю, а несовпадение логического и арифметического сложений вообще ведёт в ступор.

Итак, вот что я увидел:

#include <SPI.h>
const int ADC_CS_Pin;

byte byte_1, byte_2, byte_0;
volatile long  sensorValue_1 = 0;
volatile long  sensorValue_2 = 0;

volatile long  sensorValue_3 = 0;
volatile long  sensorValue_4 = 0;

void SPI_init()
{	SPI.setClockDivider(SPI_CLOCK_DIV128);
	SPI.setBitOrder(MSBFIRST);
	SPI.setDataMode(SPI_MODE3);
	SPI.begin();
	pinMode(ADC_CS_Pin, OUTPUT);
	digitalWrite(ADC_CS_Pin, HIGH);
}
//===============================================
void ADC_init()
{
  digitalWrite(ADC_CS_Pin, LOW);
  SPI.transfer(0x19);
  SPI.transfer(0);
  SPI.transfer(0x70);
  digitalWrite(ADC_CS_Pin, HIGH);
  
  digitalWrite(ADC_CS_Pin, LOW);
  SPI.transfer(0x09);
  SPI.transfer(0x80);
  SPI.transfer(0x11);
  digitalWrite(ADC_CS_Pin, HIGH);
  
  digitalWrite(ADC_CS_Pin, LOW);
  SPI.transfer(0x01);
  SPI.transfer(0x01);
  SPI.transfer(0xC0);
  digitalWrite(ADC_CS_Pin, HIGH);
}

void ADC_read()
{
  digitalWrite(ADC_CS_Pin, LOW);
  SPI.transfer(0x42);
  byte_0=SPI.transfer(0);
  byte_1=SPI.transfer(0);
  byte_2=SPI.transfer(0);
  digitalWrite(ADC_CS_Pin, HIGH);
  
  sensorValue_1  = byte_2|((unsigned)byte_1<<8)|((unsigned long)byte_0<<16);
  sensorValue_2 = byte_2+((unsigned)byte_1<<8)+((unsigned long)byte_0<<16);
  
  sensorValue_3  = byte_2|(byte_1<<8)|((unsigned long)byte_0<<16);	//!!!
  sensorValue_4 = byte_2+(byte_1<<8)+((unsigned long)byte_0<<16);	//!!!
  
}
//============================================
void setup() 
{
	Serial.begin(9600);
	SPI_init();			//Инициализация SPI
	ADC_init();			//Инициализация АЦП
}
//===========================================
void loop() 
{
	ADC_read();
	Serial.println("DATA: ");
	Serial.println(sensorValue_1,HEX);
	Serial.println(sensorValue_2,HEX);
	
	Serial.println(sensorValue_3,HEX);
	Serial.println(sensorValue_4,HEX);

	Serial.println(byte_0,HEX);
	Serial.println(byte_1,HEX);
	Serial.println(byte_2,HEX);
	
	delay(10);
}

/*
DATA: 
9C94FE
9C94FE
FFFF94FE  !!!
9B94FE    !!!
9C
94
FE

*/

 

Peotr
Peotr аватар
Offline
Зарегистрирован: 17.02.2014

DetSimen пишет:

в union собираются переменные, которые занимают в памяти одно и то же место.

тоись, ты можешь присвоить SPIData.Value число типа long и потом обратиться к каждому его байту отдельно через SPIValue.ValueBytes[0..3]

а можешь наоборот, пхать в ValueBytes байты последовательно и считать потом весь long, чо там получилось. 

Подробнее  RTFM->MSDN().

DetSimen, спасибо за мастер-класс!

xDriver
xDriver аватар
Offline
Зарегистрирован: 14.08.2015

С Union выглядит примерно так (тока я не проверял может с порядком байт напутал, убегаю, вы проверьте)

#include <SPI.h>
const int ADC_CS_Pin;

union
{
  long Value;
  byte ValueBytes[4];
} SPIData;

//===============================================
void SPI_init()
{ SPI.setClockDivider(SPI_CLOCK_DIV128);
  SPI.setBitOrder(MSBFIRST);
  SPI.setDataMode(SPI_MODE3);
  SPI.begin();
  pinMode(ADC_CS_Pin, OUTPUT);
  digitalWrite(ADC_CS_Pin, HIGH);
  SPIData.Value = 0;
}
//===============================================
void ADC_init()
{
  digitalWrite(ADC_CS_Pin, LOW);
  SPI.transfer(0x19);
  SPI.transfer(0);
  SPI.transfer(0x70);
  digitalWrite(ADC_CS_Pin, HIGH);

  digitalWrite(ADC_CS_Pin, LOW);
  SPI.transfer(0x09);
  SPI.transfer(0x80);
  SPI.transfer(0x11);
  digitalWrite(ADC_CS_Pin, HIGH);

  digitalWrite(ADC_CS_Pin, LOW);
  SPI.transfer(0x01);
  SPI.transfer(0x01);
  SPI.transfer(0xC0);
  digitalWrite(ADC_CS_Pin, HIGH);
}
//===============================================
void ADC_read()
{
  digitalWrite(ADC_CS_Pin, LOW);
  SPI.transfer(0x42);
  for (uint8_t n = 1; n < 4; n++) SPIData.ValueBytes[n] = SPI.transfer(0);
  digitalWrite(ADC_CS_Pin, HIGH);
}
//============================================
void setup()
{
  Serial.begin(9600);
  SPI_init();     //Инициализация SPI
  ADC_init();     //Инициализация АЦП
}
//===========================================
void loop()
{
  ADC_read();
  Serial.println("DATA: ");
  Serial.println(SPIData.Value, HEX);
  for (uint8_t n = 1; n < 4; n++) Serial.println(SPIData.ValueBytes[n], HEX);
  delay(10);
}
 
DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

DetSimen пишет:

DIYMan пишет:

 long имеет максимальное положительное значение в 65535

ты не прав, Аркаша. 

Йопт, бывает, Сеня, бывает - осень, склероз :) Но щас отбрехаюсь, как обычно: зависит от системы, и от размерности типов под ней ;)

Peotr
Peotr аватар
Offline
Зарегистрирован: 17.02.2014

xDriver, не совсем получается.

Во-первых, в операторе №07:

byte ValueBytes[4];изменить на byte ValueBytes[3];

Во-вторых, порядок байтов: у меня MSBFIRST, а в этом скетче выводится (SPIData.Value) LSBFIRST. Я пока не придумал, как исправить.

Спасибо.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Peotr пишет:

andriano, большущее спасибо! Вот где собака порылась: раньше работал с двухбайтными конструкциями, и byte_2+(byte_1<<8) вполне прокатывало.

Правда, как это работает я так и не понял. Переполнения разрядов не вижу, отрицательных значений на входе не задаю,

Компилятор на этот счет имеет другое мнение и воспринимает 0xF9 и 0x91 как отрицательные числа.

Цитата:

а несовпадение логического и арифметического сложений вообще ведёт в ступор.

Напрасно. Именно этот факт - одна из двух подсказок, что компилятор трактует числа как отрицательные и при преобразовании типов расширяет знаковый разряд. (вторая подсказка - FF в старших разрядах результата)

Peotr
Peotr аватар
Offline
Зарегистрирован: 17.02.2014

andriano, спасибо! Теперь "всё в ёлочку".

Эх, надо чаще советоваться с компиллятором!

xDriver
xDriver аватар
Offline
Зарегистрирован: 14.08.2015

Peotr пишет:

xDriver, не совсем получается.

Во-первых, в операторе №07:

byte ValueBytes[4];изменить на byte ValueBytes[3];

Во-вторых, порядок байтов: у меня MSBFIRST, а в этом скетче выводится (SPIData.Value) LSBFIRST. Я пока не придумал, как исправить.

Спасибо.

Во-первых, не утверждайте то чего не знаете, не понимаете... компилятор за вас конечно же "додумает", но я размерность элементов union стараюсь описывать одинаковыми, так понятнее.

Во-вторых, да я как и подозревапл порядок байт попутал, исправляюсь...

#include <SPI.h>
const int ADC_CS_Pin;

union
{
  long Value;
  byte ValueBytes[4];
} SPIData;

//===============================================
void SPI_init()
{ SPI.setClockDivider(SPI_CLOCK_DIV128);
  SPI.setBitOrder(MSBFIRST);
  SPI.setDataMode(SPI_MODE3);
  SPI.begin();
  pinMode(ADC_CS_Pin, OUTPUT);
  digitalWrite(ADC_CS_Pin, HIGH);
  SPIData.Value = 0;
}
//===============================================
void ADC_init()
{
  digitalWrite(ADC_CS_Pin, LOW);
  SPI.transfer(0x19);
  SPI.transfer(0);
  SPI.transfer(0x70);
  digitalWrite(ADC_CS_Pin, HIGH);

  digitalWrite(ADC_CS_Pin, LOW);
  SPI.transfer(0x09);
  SPI.transfer(0x80);
  SPI.transfer(0x11);
  digitalWrite(ADC_CS_Pin, HIGH);

  digitalWrite(ADC_CS_Pin, LOW);
  SPI.transfer(0x01);
  SPI.transfer(0x01);
  SPI.transfer(0xC0);
  digitalWrite(ADC_CS_Pin, HIGH);
}
//===============================================
void ADC_read()
{
  digitalWrite(ADC_CS_Pin, LOW);
  SPI.transfer(0x42);
  for (uint8_t n = 0; n < 3; n++) SPIData.ValueBytes[2-n] = SPI.transfer(0);
  digitalWrite(ADC_CS_Pin, HIGH);
}
//============================================
void setup()
{
  Serial.begin(9600);
  SPI_init();     //Инициализация SPI
  ADC_init();     //Инициализация АЦП
}
//===========================================
void loop()
{
  ADC_read();
  Serial.println("DATA: ");
  Serial.println(SPIData.Value, HEX);
  for (uint8_t n = 0; n < 3; n++) Serial.println(SPIData.ValueBytes[2-n], HEX);
  delay(10);
}

 

Peotr
Peotr аватар
Offline
Зарегистрирован: 17.02.2014

xDriver пишет:

Во-первых, не утверждайте то чего не знаете, не понимаете... компилятор за вас конечно же "додумает", но я размерность элементов union стараюсь описывать одинаковыми, так понятнее.

Приношу извинения за некорректность в адрес мэтра.

Меня смутил результат, вот, даже исправленный:

DATA:
D9162800
28
16
D9
 

Налицо лишний байт (00), четвёртый по счёту. Думал, опечатка в размерности массива.

Спасибо.

Penni
Penni аватар
Offline
Зарегистрирован: 18.01.2015

Никакой ноль не лишний, просто у вас порядок следования байт неправильный. SPI как MSBFIRST настроен, а байты в массив складываете с младшего. У вас SPIData.ValueBytes[0] всегда 0х00 должен быть. А в цикле, при размещении в массив, должно быть примерно так

for (uint8_t n = 0; n < 3; n++) SPIData.ValueBytes[n+1] = SPI.transfer(0);

при этом не забываем на всякий случай "занулять" SPIData.ValueBytes[0], можно в этом же цикле прописать, можно отдельной строчкой.

Т.е. у вас при настройке MSBFIRST первый пришедший байт (старший) должен попасть в ValueBytes[1], следующий в ValueBytes[2] и последнйи младший в ValueBytes[3], а ValueBytes[0] должен быть 0х00.