Затык с обработкой трёхбайтного значения от SPI
- Войдите на сайт для отправки комментариев
Вс, 29/10/2017 - 23:18
Здравствуйте, досточтимые носители семи пядей во лбу!
Уповаю на вашу помощь.
Юзаю 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);
// ***
}
Замените в строке сборки (long) на (unsigned long).
Дык всё верно у вас происходит: long имеет максимальное положительное значение в 65535, потом - заворот кишок в отрицательные значения, при переполнении. Хотите сдвигом манипулировать большими значениями в положительной области (а тип byte в качестве носителей отдельных байт сборного значения какбэ намекает, что имеет место быть беззнаковый тип) - юзайте unsigned long.
А ишшо проще - юзать массив и копировать:
byte byteArray[4] = {0}; byteArray[0] = 27; byteArray[1] = 0xF9; byteArray[2] = 0x91; unsigned long ulVal; memcpy(&ulVal,byteArray,sizeof(unsigned long));Порядок следования байт в массиве byteArray - подберите по вкусу ;)
long имеет максимальное положительное значение в 65535
ты не прав, Аркаша.
А ишшо проще - юзать массив и копировать:
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;Если через сдвиг хочется, то правильно будет сдвигать результурующую переменную, а не заниматься приведением типов.
Уже показали наиправильнейший вариант с юнионом, все остальные варианты просто меркнут на его фоне :)
Дык всё верно у вас происходит: long имеет максимальное положительное значение в 65535, потом - заворот кишок в отрицательные значения, при переполнении. Хотите сдвигом манипулировать большими значениями в положительной области (а тип byte в качестве носителей отдельных байт сборного значения какбэ намекает, что имеет место быть беззнаковый тип) - юзайте unsigned long.
Дык, сам об етом думал, да вот в чём загвоздка:
"Unsigned long используется для хранения положительных целых чисел в диапазоне от 0 до 4,294,967,295 (2^32 - 1) и занимает 32 бита (4 байта) в памяти". [http://arduino.ru/Reference/UnsignedLong]
Спасибо.
А можно просто использовать union.
union { long Value; byte ValueBytes[4]; } SPIData;Этто мы не проходили... Где можно погрызть union?
Спасибо.
Если через сдвиг хочется, то правильно будет сдвигать результурующую переменную, а не заниматься приведением типов.
Замечательно! Только мне нужно было для проверки преобразования выводить и побайтно.
Спасибо.
Замените в строке сборки (long) на (unsigned long).
Не помогло :((( Что за хрень? И не понятно, почему результат с побитным ИЛИ не совпадает со сложением, хотя, вроде, это одно и то же в данном случае.
Спасибо.
в union собираются переменные, которые занимают в памяти одно и то же место.
тоись, ты можешь присвоить SPIData.Value число типа long и потом обратиться к каждому его байту отдельно через SPIValue.ValueBytes[0..3]
а можешь наоборот, пхать в ValueBytes байты последовательно и считать потом весь long, чо там получилось.
Подробнее RTFM->MSDN().
в union собираются переменные, которые занимают в памяти одно и то же место.
скажите, а нет у вас такого юнион, что бы новые данные просто сдвигали старые - нужно хранить два значения переменной: новое и пердыдущее.
как раз у мня есть, сам писал. TStack называеца.
Замените в строке сборки (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 91PS. Но наиболее прямой метод, естественно, с union. Просто его писать чуть дольше.
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 */в union собираются переменные, которые занимают в памяти одно и то же место.
тоись, ты можешь присвоить SPIData.Value число типа long и потом обратиться к каждому его байту отдельно через SPIValue.ValueBytes[0..3]
а можешь наоборот, пхать в ValueBytes байты последовательно и считать потом весь long, чо там получилось.
Подробнее RTFM->MSDN().
DetSimen, спасибо за мастер-класс!
С 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); }long имеет максимальное положительное значение в 65535
ты не прав, Аркаша.
Йопт, бывает, Сеня, бывает - осень, склероз :) Но щас отбрехаюсь, как обычно: зависит от системы, и от размерности типов под ней ;)
xDriver, не совсем получается.
Во-первых, в операторе №07:
byteValueBytes[4];изменить наbyteValueBytes[3];Во-вторых, порядок байтов: у меня MSBFIRST, а в этом скетче выводится (SPIData.Value)LSBFIRST. Я пока не придумал, как исправить.Спасибо.andriano, большущее спасибо! Вот где собака порылась: раньше работал с двухбайтными конструкциями, и byte_2+(byte_1<<8) вполне прокатывало.
Правда, как это работает я так и не понял. Переполнения разрядов не вижу, отрицательных значений на входе не задаю,
Компилятор на этот счет имеет другое мнение и воспринимает 0xF9 и 0x91 как отрицательные числа.
а несовпадение логического и арифметического сложений вообще ведёт в ступор.
Напрасно. Именно этот факт - одна из двух подсказок, что компилятор трактует числа как отрицательные и при преобразовании типов расширяет знаковый разряд. (вторая подсказка - FF в старших разрядах результата)
andriano, спасибо! Теперь "всё в ёлочку".
Эх, надо чаще советоваться с компиллятором!
xDriver, не совсем получается.
Во-первых, в операторе №07:
byteValueBytes[4];изменить наbyteValueBytes[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); }Во-первых, не утверждайте то чего не знаете, не понимаете... компилятор за вас конечно же "додумает", но я размерность элементов union стараюсь описывать одинаковыми, так понятнее.
Приношу извинения за некорректность в адрес мэтра.
Меня смутил результат, вот, даже исправленный:
DATA:
D9162800
28
16
D9
Налицо лишний байт (00), четвёртый по счёту. Думал, опечатка в размерности массива.
Спасибо.
Никакой ноль не лишний, просто у вас порядок следования байт неправильный. SPI как MSBFIRST настроен, а байты в массив складываете с младшего. У вас SPIData.ValueBytes[0] всегда 0х00 должен быть. А в цикле, при размещении в массив, должно быть примерно так
при этом не забываем на всякий случай "занулять" SPIData.ValueBytes[0], можно в этом же цикле прописать, можно отдельной строчкой.
Т.е. у вас при настройке MSBFIRST первый пришедший байт (старший) должен попасть в ValueBytes[1], следующий в ValueBytes[2] и последнйи младший в ValueBytes[3], а ValueBytes[0] должен быть 0х00.