I'm programming C on an embedded system. The processor architecture is 32 bits (sizeof(int)
is 32 bits, sizeof(short)
is 16 bits). There is a 32-bit variable that is a memory-mapped control register (CTRL_REG
) that is specified as only the bottom 16 bits being used, and they contain a signed 16 bit integer value (writing to the higher bits has no effect). Memory accesses must be 32-bit aligned, so I can't just bump the pointer over a couple bytes, and also I cannot assume an endianness. I'm concerned that automatic type promotion is going to mess with whatever I'm storing by extending the sign bit out to bit 31 instead of leaving it at bit 15 where I want it. What is the best way to store something in this location?
Here was my original code, which I am nearly certain is wrong:
#define CTRL_REG *((volatile unsigned int *)0x4000D008u)
short calibrationValue;
CTRL_REG = -2 * calibrationValue;
Then I tried this, but I think it still might be subject to integer promotion at the point of the assignment:
CTRL_REG = (short)(-2 * calibrationValue);
Then finally I thought of this:
CTRL_REG = (unsigned short)(short)(-2 * calibrationValue);
I can't evaluate these options very well because they all work in my tests because calibrationValue
happens to be negative (it's a calibration parameter specific to each device, and so could be positive on some devices), so after multiplying by -2, I end up storing a positive value and thus I don't actually run into the problem I'm expecting in my tests.
Your help is greatly appreciated, even if it's just to say "you're thinking too much".
A 16-bit integer can store 216 (or 65,536) distinct values. In an unsigned representation, these values are the integers between 0 and 65,535; using two's complement, possible values range from −32,768 to 32,767. Hence, a processor with 16-bit memory addresses can directly access 64 KB of byte-addressable memory.
16 bit unsigned numbers There are 65,536 different unsigned 16-bit numbers. The smallest unsigned 16-bit number is 0 and the largest is 65535. For example, 0010,0001,1000,01002 or 0x2184 is 8192+256+128+4 or 8580. Other examples are shown in the following table.
An unsigned integer is a 32-bit datum that encodes a nonnegative integer in the range [0 to 4294967295]. The signed integer is represented in twos complement notation.
For an unsigned short, all 16 bits are used to represent the value, so the largest representable number is 216 − 1 = 65,535.
Think about what a -16 ( for example ) looks like in 16 bit : '0xFFF0' , and in 33 bit: '0xFFFFFFF0'. Sign extending is exactly what you want to ensure you have a sign bit in the right place. Since the upper 16 are don't-care, it's fine to fill them with 1s. So create a signed 32 bit value, then cast it to an unsigned 32 to put in your register:
Int32 calreg= -2L * calibrationValue;
CTRl_REG = (Uint32)calreg;
If you would rather write 0s to the high bits, mask with 0xFFFF before the cast.
Instead of an unsigned int, define a union of an unsigned int and 2 short signed ints.
That's how I handle the 'funny hardware control registers' on my ARM systems where, quite often, odd bits here and there are either 'don't care' or 'must not have 1 written to them'.
Rgds, Martin
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With