#EDIT Arduino Nano ATmega328P /#
I'm saving ADC values in real time, and seeking to A) compress, and B) do it as quickly as possible.
I take the 10-bit ADC value (as uint16_t) then shift right varying amounts to compress down to 8-bit without too much precision loss.
I've created a small project (StrangeShiftRightCode) to demonstrate ...
void setup() {
}
volatile uint8_t res;
volatile uint16_t n=0;
void loop() {
++n;
if(n > 1023) { n = 123; }
res = (n) >> 1;
res = ((uint8_t)n) >> 1;
res = (static_cast<uint8_t>(n)) >> 1;
}
The (snipped) generated code:
(Noting that both the ((uint8_t)n) and static_cast<uint8_t>(n)) are being treated as 16 bit (and arguably signed) for the >> 1 operation.)
There seems to be extra machine instructions there.
Can someone explain why this is?
Maybe I should know, but my C++ & AVR Assembler is maybe rusty.
...
No answer to your question, but to go from 10 bit to 8 bit one should (at least) shift by 2?
res = (n) >> 1;
res = ((uint8_t)n) >> 1;
res = (static_cast<uint8_t>(n)) >> 1;
IIRC casting before shifting throws away the two most significant bits while you want to remove the least significant bits.
res = (n >> 2);
res = (uint8_t) (n >> 2);
res = (static_cast<uint8_t>(n >> 2));
I'm saving ADC values in real time,
Giving your goal, it might be worth to take a look at the library code how the 10 bit ADC is generated, it might be possible to rewrite the analogRead() or equivalent. Unless you need portable code.
It's a matter of byte order. If the highest byte resides leftmost in memory then casting a 2 byte value to 1 byte will return the high byte, not the low one.
If value is <256, shift right 1; range 0-127
Else if <512 shift right 2 + whatever; range 128-191
Else shift right 3 then + whatever; range 192-255.
This drops least sig bit then stores rest of number to 7 sig bits, in 8 bits
Yeah eh, if the analogRead() returns 1023, then adding 2 results in 1025, shifting that 2 bits = 256, which is not within an 8 bit variable. which i thought was the goal.
That is just nonsense. when you cast to a smaller variable size, you will always be left with the least significant part unless you shift. Endianness is not relevant, that is just the convention that is used for casting.
This is the correct answer and should be marked as solution.
First, the C-style cast has the same result as the static_cast; and since we're doing C++, use the latter, and you don't need the extra parentheses. Consider
Prints 1FE and FE. The cast does -- well, the opposite of truncation -- decapitation(?) but does not constrain the shift operation. It is handled as just plain int. In contrast, assigning to a variable will lop off the same bits and constrain the shift.
But yeah, the resulting machine code seems like a signed 16-bit shift and then 16-bit mask to clear the nine high bits. The high byte in r25 could be ignored entirely; instead just a clc to clear the carry bit before the ror r24.
The plain n >> 1 is simpler: just 16-bit zero-shift. Then storing the low byte.
So to implement your compression scheme, just do the shifts and adds, then assign. Or write inline assembly.
A shift-right-2 in all circumstances makes low values very granular; afterwards 4, 5, 6, & 7 all look the same.
I adopt a variable shift, to preserve about 7 bits of significance. This shifts to the right ...:
low(L) values (<256) by 1 (max error of 1)
medium(M) values (256 to 511) by 2 [then add 64] (max error of 2)
high(H) values (512 to 1023) by 3 [then add 128] (max error of 4)
That gives 256 encoded values; the extra 64 & 128 are subtracted to decode, and adding 0(L), 2(M) or 4(H) to 'round to middle-ish of range'.
My unit testing checks all 1024 inputs against all 256 encoded values, both encoding and decoding.
The 1st shift line is to show how the AVR assembler code for a 16 bit shift.
The 2nd & 3rd lines show (or SHOULD show) the AVR assembler code for an 8 bit shift - BUT IT LOOKS LIKE THE GENERATED CODE IS FAULTY.
BTW that casting to 8 bits only happens when 16 bit value is < 256; save time by chopping off 8 top zero bits!
Some more experimentation, and I THINK i have the answer:
The generated AVR machine code ALWAYS DOES A 16BIT SHIFT - even for 8 bit unsigned operand! And similar unnecessary code assigning 16bit to 8bit!
Note that n8 is 'volatile uint8_t', and n16 is renaming my original 'volatile uint16_t n':
That doesn't explain why a shift right of an 8bit unsigned number (whether high or low part of 16bit is irrelevant) is generating code to do a 16bit shift.
Subsequent simpler(!) testing shows that even a shift right of a standard uint8_t (no casting) generates code for a 16bit shift cf my comment at post#14.
void setup()
{
Serial.begin(115200);
Serial.println(__FILE__);
for (int i = 0; i < 1023; i+=4)
{
int value;
if (i < 256) value = i / 2;
else if (i < 512) value = 128 + (i-256)/4;
else value = 192 + (i-512)/8;
Serial.print(i);
Serial.print("\t");
Serial.println(value);
}
}
void loop()
{
}
Yes indeed. I reduce the arithmetic with [value = base + (i - offset)/N] by combining base & offset with a bit of algebra; maybe an optimiser will do that.
My actual code is
It's the generated code for that last line that had me scratching my head; I tried to do an 8bit shift (number is < 256) but AVR code is 16bit.
(My earlier comment was on phone w/o access to code)