Two's complement math with 32-bit Micros...

I bet this is going to come up as more and more folk are transitioning to 32-microprocessors like the Due... libraries will need to be updated to reflect the different lengths of bytes, etc. since an int on a Due can hold a greater range of numbers than on a Uno. Anyhow, I wonder if someone could look over this code for a MCP3421 ADC, which offers multiple resolutions, ranging from 12-18 bits that I am trying to use on a 32-bit micro with the Arduino IDE (Teensy 3.0)

The ADC uses a I2C connection, if 18-bits are used, three data bytes are sent, otherwise, only two. The databytes then have to be re-assembled into the signed numbers they represent. My code is an attempt to be flexible, hence I declared a int32_t as the ADC value being computed. As best as I can tell, the code below is OK for the positive portion of the range. However, negative numbers appear to be coming out incorrectly. Could someone more knowledgable see if I got the code right?

int32_t Read_Thermistor_Value (byte Bits)
{
  uint8_t Hi=0, Med=0, Lo=0;
  uint8_t reads=2; // default number of bytes that have to be read for every conversion
  int32_t ADC_Value=0; // return value

  if (Bits==18) reads+=1; // add another round of reads if 18 bit mode is used. 

  Wire.beginTransmission(MCP3421_address);
  Wire.requestFrom((int)MCP3421_address, (int) reads);
  if (reads ==3) Hi = Wire.read(); //only read top byte if 18 bit conversion is used
  Med = Wire.read(); //Otherwise, just read two bits
  Lo = Wire.read();
  Wire.endTransmission();

  //here use signed int32_t and right + left shifts to carry the sign and append the value as needed
  //for 18-bit operations, only the last bit of the Hi byte is relevant. The remaining bits are sign bits
  if (Bits == 18) ADC_Value = (((Hi << 30) | (Med << 22)) | (Lo << 14)) >> 14;
      
  //for all other resolutions, the returned value is left shifted based on the resolution to preserve the sign,
  //then right shifted back, i.e. if 16 bit resolution is needed, left shift Med 24 bits, while 12-byte values
  //would require a 28-bit left shift. 
  else ADC_Value = ((Med << (40-Bits)) | (Lo << (32-Bits))) >> (32-Bits);

return ADC_Value;
}

It looks like the sign bit (shown as M in their diagrams) is replicated up to the top of each sample input, whether it be 12, 14, 16 or 18 bits so I would load the bytes as they are read into a 32-bit word and then adjust as needed.
The nice thing about the ARM Cortex M4 used in the Teensy3 is that it has a barrel shifter so whether you shift 1 or 31 bits, it still takes one clock cycle.

  ADC_value = 0;
  if(reads ==3) ADC_value |= Wire.read();
  ADC_value <<= 8;
  ADC_value |= Wire.read();
  ADC_value <<= 8;
  ADC_value |= Wire.read();
  if(Bits == 18) {
  	ADC_value <<= 8;
  	ADC_value >>= 8;
  } else {
  	ADC_value <<= 16;
  	ADC_value >>= 16;
  }

Pete

if I got the code right?

  1. The code is very convoluted. I would keep a flag for data format (3 bytes vs. 2 byte read), and based on that flag, read the bytes out of the device.
  2. When you reassembly the signed long type, you will need to test for sign (most significant bit of the most significant byte) and reverse the sign if necessary. So the bytes are reassembled to an unsigned long type, and if the number is negative, output its two's complement (aka a negative number).

Thank you, your code confirmed my previous results but its a lot simpler!

el_supremo:
...The nice thing about the ARM Cortex M4 used in the Teensy3 is that it has a barrel shifter...

Is that like having stainless braided brake lines on motorcycles? :slight_smile:

Yeah, it's exactly the same, except smaller and different. :slight_smile:

Pete