Shifting an entire 16bit array (C question)

Hello all,

I have a question about shifting data.

Before everyone goes and asks for datasheets, schematics or example code, I can not give that out as part of a non-disclosure. The problem is fairly simple and straightforward anyways. (Also this is not a job, just helping a friend)

I am communicating with a sensor device using an HMI using ModbusTCP. The dimwits that designed the sensor didn't use the standard 16bit word for every register so every time they throw in an 8bit unsigned int it screws up the rest of the registers. Unfortunately fixing that is not something that's gonna happen any time soon.

The macro in the HMI uses standard C and has a few built in functions for reading registers.
The problem is that these functions only use unsigned shorts so 16bits each. (1 register)

This is the definition I have for some of the data: (Note the 1 byte in the middle)
Product Code Description – character string (32 bytes) 16 words
Constituent 1 Coefficient A– IEEE float (4 bytes) 2 words
Constituent 1 Coefficient B– IEEE float (4 bytes) 2 words
Constituent 1 Coefficient C– IEEE float (4 bytes) 2 words
Constituent 1 Use Log Fit - 8 bit integer (1 byte) 1= use log fit (1/2 Word........)
Constituent 2 Coefficient A– IEEE float (4 bytes)
Constituent 2 Coefficient B– IEEE float (4 bytes)
Constituent 2 Coefficient C– IEEE float (4 bytes)
Constituent 2 Use Log Fit - 8 bit integer (1 byte) 1= use log fit
Constituent 3 Coefficient A– IEEE float (4 bytes)
Constituent 3 Coefficient B– IEEE float (4 bytes)
Constituent 3 Coefficient C– IEEE float (4 bytes)
Constituent 3 Use Log Fit - 8 bit integer (1 byte) 1= use log fit

Everything is fine until I reach the "Use Log Fit" byte. After that, every single unsigned short is now offset by 8 bits.

I need a way to make this: x = data I don't want(The single byte). 0 = data i need.

{xxxxxxxx00000000}, {0000000000000000}, ....{00000000xxxxxxxx}

Into this:

{0000000000000000}, {0000000000000000}, ....{0000000000000000}

And no i'm not making that data out of thin air. It's just shifted to the left or right by 8 bits and then the data will be in order in the registers for the HMI to then display on screen.

I know I could split up every word into bytes and then do some bitwise operations to concatenate everything into words again but I was wondering if there was a simple way to do this without a lot of code.

Thanks.

How are you sending the data - as in, what is the line of code that says "transmit this data to x device"? I'm interested in the API and the result might influence my answer.

Shot in the dark: Use a struct instead of an array. Then each one of your parameters can be as large or small as you need.

Power_Broker:
How are you sending the data - as in, what is the line of code that says "transmit this data to x device"? I'm interested in the API and the result might influence my answer.

Thanks for the reply. I am okay with trying a struct if you think that's easier and it works with the code.

This is the code that requests data and then saves to local memory on the HMI.

Not only did they throw in the single byte thing, they also made individual register reads impossible. It has to be a full read of the entire block. Otherwise I could just modify the registers on the device. This is the reason for saving it temporarily on the HMI.

I left some comments that explain the function arguments.
There is a separate macro for sending the data back so it has to be packaged back up the original way it came in too.

( productConfig variables are initialized using a list, not in the code. You can initialize variables in the code too but you lose some control over endianness and what register type to read from.)
And yes the code works because I have used it to read other registers. It's just this single byte that throws things off.

#include "macrotypedef.h"
#include "math.h"

/*
 Read,Write Local address function:
   int ReadLocal( const char *type, int addr, int nRegs, void *buf, int flag  );
 int WriteLocal( const char *type, int addr, int nRegs, void *buf , int flag );

 Parameter:     type     is the string of "LW","LB" etc;
 address is the Operation address ;
 nRegs    is the length of read or write ;
 buf        is the buffer which store the reading or writing data;
 flag       is 0,then codetype is BIN,is 1  then codetype is BCD;
 return value : 1  ,Operation success
 0,  Operation fail.

 eg: read the value of local lw200 and write it to the lw202,with the codetype BIN,
 The code is :

     short buf[2] = {0};
 ReadLocal("LW", 200, 2, (void*)buf, 0);
 WriteLocal("LW", 202, 2, (void*)buf, 0);
*/
int MacroEntry()
{
 short productconfig[36] = {0};
 short productconfig2[37] = {0};
 // ReadData(VarName,PLCStation,Address+1,Number of Registers,(void*)&Array);
 ReadData(ProductConfig_PLC,1,2305,36,(void*)&productconfig); // read from 4X registers address 0x900
 WriteData(ProductConfig_HMI,0,2305,36,(void*)&productconfig); // Write to LW (Local Word) 0x900 length of 36 words/registers
 return 0;
}

In that case, I think trying a struct would work as long as you keep your endian-ness straight

I have the data being correctly modified and saved on the HMI. I assume i’m going to need to do the reverse here to pack it back up.

For future reference. (I have many more macros to write) how would I use a struct for this purpose? Can I use a struct for the ReadData/WriteData function? If so how? do I need a pointer? and will it somehow know the correct variable type to save?

++Karma for the help

#include "macrotypedef.h"
#include "math.h"

//ReadData(VarName,PLCStation,Address+1,Number of Registers,(void*)&Array);
/*
Data Size (Bytes): 72
The Product Code Calibration Database starts at modbus address 0x900
repeats for each Product Code (currently 0 – 49). Each product code
calibration table is 72 bytes long. To calculate the start address for each
product code: address = 0x900 + (72 * Product Code number)
Data Definition:
Product Code Description – character string (32 bytes)
Constituent 1 Coefficient A– IEEE float (4 bytes)
Constituent 1 Coefficient B– IEEE float (4 bytes)
Constituent 1 Coefficient C– IEEE float (4 bytes)
Constituent 1 Use Log Fit - 8 bit integer (1 byte) 1= use log fit
Constituent 2 Coefficient A– IEEE float (4 bytes)
Constituent 2 Coefficient B– IEEE float (4 bytes)
Constituent 2 Coefficient C– IEEE float (4 bytes)
Constituent 2 Use Log Fit - 8 bit integer (1 byte) 1= use log fit
Constituent 3 Coefficient A– IEEE float (4 bytes)
Constituent 3 Coefficient B– IEEE float (4 bytes)
Constituent 3 Coefficient C– IEEE float (4 bytes)
Constituent 3 Use Log Fit - 8 bit integer (1 byte) 1= use log fit
*/
int MacroEntry()
{
	int i;
	short productconfig[36] = {0};
	short productconfig2[37]={0};
	char productBytes[73] = {0};
	ReadData(ProductConfig_PLC,1,2305,36,(void*)&productconfig);
	for(i = 0; i < 36; i++){
		productBytes[i*2] = productconfig[i] >> 8;
		productBytes[(i*2)+1] = productconfig[i];
	}

	for(i = 0; i < 22; i++){
		productconfig2[i] = (productBytes[i*2] << 8 | (productBytes[(i*2)+1] & 0xff));
	}
	productconfig2[22] = productBytes[44]; // Log Value 2
	
	for(i = 23; i < 29; i++){
		productconfig2[i] = (productBytes[(i*2)-1] << 8 | (productBytes[(i*2)] & 0xff));
	}
	productconfig2[29] = productBytes[57]; // Log Value 2
	
	for(i = 30; i < 36; i++){
		productconfig2[i] = (productBytes[(i*2)-2] << 8 | (productBytes[(i*2)-1] & 0xff));
	}
	productconfig2[36] = productBytes[70]; // Log Value 3
	
	WriteData(ProductConfig_HMI,0,2305,37,(void*)&productconfig2);
	//WriteData(ProductConfig_HMI,0,2305,36,(void*)&productconfig);
	return 0;
}

Yeah, you can pass the struct pointer and, as long as ReadData() and WriteData() know how many bytes the struct is, you'll be fine.

Power_Broker:
Yeah, you can pass the struct pointer and, as long as ReadData() and WriteData() know how many bytes the struct is, you’ll be fine.

Will this work if the HMI has a 16bit processor? The problem is trying to receive data that has been broken up into different 16bit registers because of random 8bit values.

E.g.

foo = 8 bit char
bar = 16bit int

The sensor sends a 16bit value that is essientially:
[ ((foo << 8 ) & 0xff00) | bar >> 8 ] or {foo bar}{bar, bar2}{bar2 bar3}… {x} is the register

The way I explained this to a friend was like so:
If you have a bunch of houses that are made for 16 people then if you get an 8 person family the sebsequent families will be split between the homes until you get another family of 8. I dont know why they wrote the firmware for the sensor this way since modbus uses only 16bit values.

foo = 8 bit char
bar = 16bit int

We can store these values in a struct, such as:

struct STRUCT {
  char foo;
  uint16_t bar;
} testStruct;

Where we would have the data saved in memory as:

[foo, bar (lsb), bar (msb)] --> Each index is a byte wide

Think of a struct as an array that has variable-sized indicies and each index has a name (i.e. foo and bar). The only catch is that when you store a value that is larger than a byte, the endian-ness may not be what you expect. Normal Arduino boards are little endian.

You could, however, get around the problem by having all elements of the struct as byte sized and do something like this:

struct STRUCT {
  char foo;
  uint8_t barMSB;
  uint8_t barLSB;
} testStruct;

And if you know you will receive a dummy byte because of the one non-byte value, you can do this as well:

struct STRUCT {
  char foo;
  char dummyByte;
  uint8_t barMSB;
  uint8_t barLSB;
} testStruct;

Does that help?

Power_Broker:
Does that help?

Yes! very helpful, thank you!

One thing you forgot to mention though. I had asked how this would work with a 16bit processor. I now know that the processor is a 32-bit, 800 MHz RISC CPU from this datasheet.

Does it still operate using 8bit memory? I thought the purpose of having a higher bit CPU was to increase the number of bits that could be calculated at once + allow more instructions. seems counter-intuitive that the CPU would still need to access memory multiple times to fill up the CPU registers.

I'm not a professional with advanced computer systems so enlighten me.

Thanks again for your help!

EDIT: I also wanna ask, do I create a variable for each value as listed in my first post?
Is it then treated as an array (of sorts) with different variable types?

If each one of your struct values specify the bit length explicitly (i.e. uint8_t, uint16_t, etc), you don't need to worry about the processor's nominal word length.

bears0:
EDIT: I also wanna ask, do I create a variable for each value as listed in my first post?

It depends - if the CPU's endian-ness is a problem, you'll have to have a struct value for each of the 17 bytes. Otherwise, you'll probably want to create struct values for each variable you care about, regardless of variable size. All of this is rephrasing the examples in my last post.

bears0:
Is it then treated as an array (of sorts) with different variable types?

Yup!

Okay great.

I think i forgot to mention that the sensor only accepts multi-register reads and writes. So I will most likely need to create a struct that includes every variable so that everything lines up when reading and writing. I wont be able to read a specific register and save it but rather the entire thing at once.

Thanks for all of your help

So I attempted using a struct and this is as far as I got. I tried WriteData with the struct but it ended up doing the same thing when I first started this.

How can I easily take this data and put it into an array of unsigned shorts (16bits)?
If I can do this then I know that the data will be saved in the HMI properly.
I know how to make a for loop that copies the data into an array, but I need a way to do this easily. Otherwise I’m back to where I started with the complicated code. This is just a simple example but there are other memory “blocks” that I need to copy over that have 180 different variables.

Requirements:
must take char variables (8bits) and save them as an unsigned short (16bits)
must take floats and save them in 2 unsigned shorts (16bits x2)
Doesn’t need to be fiddled with much every time I use the code for a different block.

I’m thinking I could create an array: unsigned short data[37];
and somehow copy the data to it.

Here is the code I have right now:
for(i=0;i<1000;i++){
++Karma;
}

#include "macrotypedef.h"
#include "math.h"
#include <string.h>

//ReadData(VarName,PLCStation,Address+1,Number of Registers,(void*)&Array);
/*
Data Size (Bytes): 72
The Product Code Calibration Database starts at modbus address 0x900
repeats for each Product Code (currently 0 – 49). Each product code
calibration table is 72 bytes long. To calculate the start address for each
product code: address = 0x900 + (72 * Product Code number)
Data Definition:
Product Code Description – character string (32 bytes)
Constituent 1 Coefficient A– IEEE float (4 bytes)
Constituent 1 Coefficient B– IEEE float (4 bytes)
Constituent 1 Coefficient C– IEEE float (4 bytes)
Constituent 1 Use Log Fit - 8 bit integer (1 byte) 1= use log fit
Constituent 2 Coefficient A– IEEE float (4 bytes)
Constituent 2 Coefficient B– IEEE float (4 bytes)
Constituent 2 Coefficient C– IEEE float (4 bytes)
Constituent 2 Use Log Fit - 8 bit integer (1 byte) 1= use log fit
Constituent 3 Coefficient A– IEEE float (4 bytes)
Constituent 3 Coefficient B– IEEE float (4 bytes)
Constituent 3 Coefficient C– IEEE float (4 bytes)
Constituent 3 Use Log Fit - 8 bit integer (1 byte) 1= use log fit
*/

struct configStruct {
	char description[32];
	float c1CE_A;
	float c1CE_B;
	float c1CE_C;
	char c1useLogFit;
	float c2CE_A;
	float c2CE_B;
	float c2CE_C;
	char c2useLogFit;
	float c3CE_A;
	float c3CE_B;
	float c3CE_C;
	char c3useLogFit;
	char dummyByte;
};


int MacroEntry()
{

	struct configStruct *dataPtr, data1;
	short buf[1] = {1};
	WriteLocal("LW", 9812, 1, (void*)buf, 0); // Sets the decoding order of floats on HMI
	short productconfig2[37]={0};
	 
	ReadData(ProductConfig_PLC,1,2305,36,(void*)&data1);
	

	WriteData(ProductConfig_HMI,0,2305,36,(void*)&data1);
	return 0;
}

What specifically is wrong in your code? How do you know the contents are not copied to the struct properly?

As long as the variables are in the correct order, have the right bit width, and endianness is not a problem you should be good to go. If the problem is due to having an odd number of bytes to "deal with", you need to add a dummy char in the correct spot within the struct as I mentioned in my previous posts.

Power_Broker:
What specifically is wrong in your code? How do you know the contents are not copied to the struct properly?

I'm assuming the variables in the struct are received correctly.

I think I realized what's confusing here. I have a macro that reads the data, then this data is saved to registers on the HMI. The ReadData is getting data from the sensor. The WriteData is writing the data to the HMI's registers, not back to the sensor. (That will be done in the next macro I write.)

The problem arises when I have a single byte value. The HMI's drag-and-drop function parts can only read an entire register, not half of it.

The problem is making the data align with the HMI registers.

Hopefully this image will explain what I mean.

Assuming the code I have copies all of the data correctly, I just need to separate the byte values into their own register. This has to be done between the ReadData and WriteData.

The program that creates the HMI interface has "number displays" that I drag and drop onto pages that I can create. You just enter the address and data type and it automatically shows that number. If I read a float from address x then it implicitly reads from x to x+1. That's two registers for the 32bit float.

I cant access a single byte using the HMI screen unless it's in its own register by itself Even if I modify the struct with a dummy byte it doesn't separate the single bytes into their own registers. The variables end up overlapping registers and the HMI doesn't have options to correct for this. It needs to be done in the macro first.

P.S. I've been working on this a while and I understand pretty deeply how the HMI works. That makes it hard for me to know what parts I need to elaborate on.

Yeah, you've explained all that before. But if you're reading/writing to all the registers en masse, then why do you care what byte is in what register? All you need to know are what bytes pertain to what "values", right? I'm not sure I understand the point of worrying about the details of the registers apart from simply getting the data and writing it back out - which you seem to have working.

Power_Broker:
Yeah, you’ve explained all that before. But if you’re reading/writing to all the registers en masse, then why do you care what byte is in what register? All you need to know are what bytes pertain to what “values”, right?

No. The reason I care what registers they go in is because I can only access a single register with the HMI’s drag-and-drop objects.

If a byte is saved in a variable I can access that variable in the macro; however, with the HMI objects, I have no control over if it reads the MSB or LSB of that register. It only reads the whole thing.

Each byte needs it’s own register.

Here is an example of what normally happens in a CPU using a struct.
Struct Padding

This also explains kinda what I mean.
Packing and byte alignment

The problem is that the WriteData function loses this padding so the word boundaries overlap the registers and bytes get mixed in with other data. This normally isn’t a problem because PLC’s have 16bit registers. Since the sensor is odd in that it contains bytes, the standard procedures for moving data don’t work.

Also, I found the macro definition for WriteData and ReadData but it doesn’t seem to affect anything. I even put random stuff in to “break” it and it still compiles fine. I’ve searched file contents and everything and can’t find where the actual header file is.

If I could find that, then I might be able to put in a logic statement that adds a padding byte.