4

I have an SPI chip that I would like to read out with an Arduino. Some of the registers are signed int others are unsigned int. I am writing a function to read any register.

A simplified version:

readRegister(byte thisRegister, char type) {

  if(type == 's'){
    signed int result;
  }else{
    unsigned int result;
  }

  // take the chip select low to select the device:
  SPI.beginTransaction(SPISettings(2500000, MSBFIRST, SPI_MODE3));
  digitalWrite(SlaveSelectPin, LOW);

  //now get the value
  result = SPI.transfer16(thisRegister);

  // finish up
  digitalWrite(SlaveSelectPin, HIGH);
  SPI.endTransaction();

  // return the result:
  return (result);
}

readRegister(0x4B87,'s')  //read signed int register
readRegister(0xB357,'u')  //read unsigned int register

However this doesn't work since the variable result is created inside the if statement so the scope is wrong. I get error: 'result' was not declared in this scope on the SPI.transfer() line.

How to solve this?

  • 1
    You can't have a function that return two different types of variable. Easiest solution would be to use two function; readRegister and readRegisterSigned. Alternatively, since SPI.transfer only returns an 8 bit value, and int is 16 bit, you could just use signed int for both returned values. – Gerben Dec 21 '16 at 14:43
  • Sorry, I have editted the question so it reads SPI.transfer16, my mistake. It seems like a horrible waste of repeating code to write 2 allmost identical functions (the real function has more lines). Could I return the result as signed long no matter what the original type was? – Sebastian Dec 21 '16 at 14:53
  • 1
    A long would work. You could also implement the readRegisterSigned function as signed int readRegisterSigned(byte thisRegister){return (unsigned int)readRegister(thisRegister);}. Or just typecast at the calling end. I.e. (unsigned int)readRegister(0x4B87,'s'); – Gerben Dec 21 '16 at 15:05
  • Hmm, I understand what you are suggesting I think. But I don't follow the steps of your first example. Is this correct?: I read in the bits of an signed integer (from the SPI slave) into an unsigned int variable result,I return that from my function readRegister and I cast that return to unsigned again, but then I return it from a function that yields a signed int. And it gives me the correct value? In what steps is the value actually changing? – Sebastian Dec 21 '16 at 15:22
  • 1
    'cast' and 'conversion' are not the same thing, though casting native types may involve conversion. If you cast between signed and unsigned types of the same size, you are really only advising the compiler to treat this value as being of the new type. In the expression (uint8_t)signedVariable, you are really telling the complier: "trust me on this: signedVariable's value is really 8-bit unsigned". – JRobert Dec 21 '16 at 16:05
1

You could try something like this:

int16_t readRegister(byte thisRegister) {
  int16_t result;

  // take the chip select low to select the device:
  SPI.beginTransaction(SPISettings(2500000, MSBFIRST, SPI_MODE3));
  digitalWrite(SlaveSelectPin, LOW);

  //now get the value
  result = SPI.transfer16(thisRegister);

  // finish up
  digitalWrite(SlaveSelectPin, HIGH);
  SPI.endTransaction();

  // return the result:
  return (result);
}

uint16_t readUnsignedRegister(byte theRegister) {
  return (uint16_t) readRegister(theRegister);
}

Another alternative is to define a "universal" data type, i.e. a union and necessary cast operators.

union univ16_t {
  uint16_t as_uint16;
  int16_t as_int16;

  univ16_t(int value)
  {
    as_int16 = value;
  }

  operator uint16_t()
  {
    return (as_uint16);
  }

  operator int16_t()
  {
    return (as_int16);
  }
};

This allows the function to have the following form:

univ16_t readRegister(byte thisRegister) {
  int result;

  // take the chip select low to select the device:
  SPI.beginTransaction(SPISettings(2500000, MSBFIRST, SPI_MODE3));
  digitalWrite(SlaveSelectPin, LOW);

  //now get the value
  result = SPI.transfer16(thisRegister);

  // finish up
  digitalWrite(SlaveSelectPin, HIGH);
  SPI.endTransaction();

  // return the result:
  return (result);
}

With the two possible assignments.

uint16_t reg1 = readRegister(...);
int16_t reg2 = readRegister(...);

Cheers!

  • It would be better to have the main function returning unsigned int because otherwise you are loosing precision and the compiler will winge. – Code Gorilla Dec 22 '16 at 13:00
0

Based on the comments by Gerben and JRoberts, this is the answer I distilled (correct me if I misunderstood):

Since you need to define a return type of the function anyway, you cannot have a function returning one of two datatypes based on the input.

So there are basically four approaches:

  1. Write two seperate functions
  2. Have only one function and use datatype casting on the result
  3. Combination of the above, where the second function is only a wrapper for the first function but with datatype casting.
  4. Return both unsigned int and signed int register values as a signed long

Some extra info:

2. When you cast between signed and unsigned of the same size, you are not changing the bits, you are just changing the way they are being interpreted. So that would work perfectly.

my_signed_register = (signed int)readUnsignedRegister(0x4B87)  
my_unsigned_register = readUnsignedRegister(0xB357) 

3. The same thing can be done in a function:

signed int readSignedRegister(byte theRegister) {
  return (signed int) readUnsignedRegister(theRegister);
}

(Also see the answer of Mikael Patel)

4. If you want to use one function only, you could set the return type of the function to signed long. Because to represent both signed and unsigned ints you need signed long. You then read in the bits from the SPI.transfer in signed (or unsigned, whatever you like) and then change to signed long based on the type given in the function call 's' or 'u'.

0

One final option that isn't mentioned in any of the above solutions - Create a function that returns both as separate parameters.
I'm not sure why you would ever do this over Mikael Patel's solution, yes it's then a single function but it's not as nice to use. But it is a possible option.

The only possible advantage in using this pass by reference method of returning the value is that it allows the option for the read function to return a flag indicating whether the read failed or not (timeout, invalid address etc...). And even then it would still be more logical to have two functions, one for signed and one for unsigned.

void readRegister(byte thisRegister, int16_t *signedValue, uint16_t *unsignedValue) {

  unsigned int result_u;

  // take the chip select low to select the device:
  SPI.beginTransaction(SPISettings(2500000, MSBFIRST, SPI_MODE3));
  digitalWrite(SlaveSelectPin, LOW);

  //now get the value
  result_u = SPI.transfer16(thisRegister);

  // finish up
  digitalWrite(SlaveSelectPin, HIGH);
  SPI.endTransaction();

  if (signedValue)
    *signedValue = (int16_t)result_u;
  if (unsignedValue)
    *signedValue = result_u;
}

void someOtherFunction() {
  uint16_t unsignedRead;
  int16_t signedRead;

  readRegister(0x4B87,&signedRead,NULL);  //read signed int register
  readRegister(0xB357,NULL,&unsignedRead); //read unsigned int register 

  //read the register as both signed and unsigned (fairly pointless)
  readRegister(0xA55A,&signedRead,&unsignedRead);

  //read the register and throw the result away
  readRegister(0xA55A,NULL,NULL);
}   
0

Signed ints are capable of being stored in unsigned int, but unsigned its are not [technically] capable of being stored in signed ints if the compiler is being pedantic. This is because an unsigned int can store a number between 0 and 0xFFFF where as a signed int can store numbers between -(0x7FFF) and 0x7FFF So if you have a compiler that is doing type checking then you will get problems if you cast an unsigned int to an int, because you might loose data.

That said an unsigned in, int, char, long, float or double is just a series of sequential bits held in memory, so if you know what you are doing you can convert a char into an unsigned long and back again. Or you can go the whole hog and store all your variables as void*.

So to answer the question I think you function should be this:

uint16_t readRegister(const byte theRegister);

If you want to read a value then you should just call:

uint16_t reg1 = readRegister(0x4B87);
int16_t  reg2 = static_cast<int16>(readRegister(0xB357));

If you really want a separate function then you might be better just #defining it.

#define readSignedRegister(r) {static_cast<int16>(readRegister(r))}
// And you would call it like this.
int16_t  reg2 = readSignedRegister(0xB357);

EDIT to correct the type typo and remove word aligned.

  • 1
    You said: “word aligned”. Not on the AVR-based Arduinos. And it's int16_t, not int_16. – Edgar Bonet Dec 22 '16 at 15:11
  • Thaks @EdgarBonet - corrected. – Code Gorilla Dec 23 '16 at 8:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.