Divide big endian value by two

I'm using an Automation direct P1AM-100.
I need to replicate 0-10V signals from analog inputs to analog outputs. The input modules are 0-10V = 0-8191 counts but the output modules are 0-10V = 0-4095 counts, so I need to read > rescale > write.
The following code works, but I'm wondering if if there a more direct way to do this without the intermediate step of the halfAnalogInCounts array that I'm using.

The AI/AO module arrays use 4 bytes per channel, big endian. It seems that this could be done with one step by bit shifting, but I'm not seeing it.

Thanks.

#include <P1AM.h>

void setup() {  // the setup routine runs once:

  Serial.begin(115200);  //initialize serial communication at 115200 bits per second

  while (!Serial) {
    ;  //Wait for open serial monitor
  }

  while (!P1.init()) {
    ;  //Wait for Modules to Sign on
  }
}

char analogInRaw[32];   //Array to store module formatted analog input bytes (8 channels, 4 bytes per channel big endian)
char analogOutRaw[32];  //Array to store module formatted output bytes (8 channels, 4 bytes per channel big endian)
int analogInCounts[8];      //Array to store individual channel counts (2 bytes per channel little endian)
// The input modules are 0-10V = 0-8191 counts but the output modules are 0-10V = 0-4095 counts.  Since we need to ouput the same voltage as input, We need an array to store the divided counts 
int halfAnalogInCounts[8];  //Array to store individual divided channel counts (2 bytes per channel little endian)
float analogVoltageIn[8];  // = sensorValue * (10.0 / 8191.0);


void loop() {

  P1.readBlockData(analogInRaw, 32, 0, ANALOG_IN_BLOCK);  //Read first 32 bytes (8 channels) of analog input data from the controller

  for (int i = 0; i < 8; i++) {
    analogInCounts[i] = analogInRaw[4 * i + 0] << 24;   //OR and shift MSB
    analogInCounts[i] |= analogInRaw[4 * i + 1] << 16;  //OR and shift 3rd byte
    analogInCounts[i] |= analogInRaw[4 * i + 2] << 8;   //OR and shift 2nd byte
    analogInCounts[i] |= analogInRaw[4 * i + 3];        //OR LSB
    analogVoltageIn[i] = analogInCounts[i] * (10.0 / 8191.0);  //Calculate the input voltage
    halfAnalogInCounts[i] = (analogInCounts[i] - 1) / 2; //Convert 0-8191 input counts to 0-4095 output counts. 
  }

  for (int i = 0; i < 8; i++) {

    analogOutRaw[4 * i + 0] = 0;
    analogOutRaw[4 * i + 1] = 0;
    analogOutRaw[4 * i + 2] = highByte(halfAnalogInCounts[i]);
    analogOutRaw[4 * i + 3] = lowByte(halfAnalogInCounts[i]);
  }

  P1.writeBlockData(analogOutRaw, 32, 0, ANALOG_OUT_BLOCK);  //Write first 32 bytes (8 channels) of analog input data from the controller

  for (int i = 0; i < 8; i++) {
    Serial.print("AI #");
    Serial.print(i + 1);
    Serial.print(" Volts = ");
    Serial.println(analogVoltageIn[i], DEC);
  }

  Serial.println();

  delay(2000);  //
}

You get the binary of the analog input value, divide by 2, and write it to the analog output. I don't see where you do an analogRead(), so I assume that this is buried in the library. If that library does not reveal the value in binary form then get rid of it and do the I/O yourself, using Arduino analogRead/Write functions.

Hi Dr,
This is a modular PLC style controller running an arduino CPU. The underlying hardware communicates with the various I/O modules. It supports (15) 8 channel modules (120 possible I/O points total) The inputs can be read one channel at a time, Arduino style, or they can be read all at once (which I'm doing), which is much faster.
If you read one channel at a time, then you get a little endian integer for the counts of each channel, which is easy to manipulate, but there is more hardware overhead to read every channel one by one.
The block read (readBlockData) returns a big endian array, 4 bytes per channel.
I have upcoming projects with many I/O modules, so the block read is attractive.

Arduino compatibility is lost with big endian data. You better use the development system compatible with your system.


Looks like it’s designed to work with Arduino.

"is not compatible with the Productivity Suite".

So the Productivity Suite is the native development system, and with the Arduino IDE you may run into many more unexpected problems. Good luck, I don't hinder you learning it the hard way.

Do the modules come with any libraries to convert back and forth between endian-ness? That would make the code easier to read but you can just as easily write procedures to do it yourself. There’s no getting around having to do the conversions if you want to do any arithmetic in the Arduino IDE.
Presumably you have to convert back to big endian before you write an array back to the module. Correct?

Is this a slight improvement?

#include <P1AM.h>

void setup() {  // the setup routine runs once:

  Serial.begin(115200);  //initialize serial communication at 115200 bits per second

  while (!Serial) {
    ;  //Wait for open serial monitor
  }

  while (!P1.init()) {
    ;  //Wait for Modules to Sign on
  }
}

char analogInRaw[32];   //Array to store module formatted analog input bytes (8 channels, 4 bytes per channel big endian)
char analogOutRaw[32];  //Array to store module formatted output bytes (8 channels, 4 bytes per channel big endian)

void loop() {

  P1.readBlockData(analogInRaw, 32, 0, ANALOG_IN_BLOCK);  //Read first 32 bytes (8 channels) of analog input data from the controller

  for (int i = 0; i < 8; i++) {
    byte j = 4 * i;
    int analogInCounts = word(analogInRaw[j + 2], analogInRaw[j + 3]);
    float analogVoltageIn = analogInCounts * (10.0 / 8191.0);  //Calculate the input voltage
    int halfAnalogInCounts = (analogInCounts - 1) / 2; //Convert 0-8191 input counts to 0-4095 output counts. 
    analogOutRaw[j + 0] = 0;
    analogOutRaw[j + 1] = 0;
    analogOutRaw[j + 2] = highByte(halfAnalogInCounts);
    analogOutRaw[j + 3] = lowByte(halfAnalogInCounts);
    Serial.print("AI #");
    Serial.print(i + 1);
    Serial.print(" Volts = ");
    Serial.println(analogVoltageIn, DEC);
  }
  Serial.println();

  P1.writeBlockData(analogOutRaw, 32, 0, ANALOG_OUT_BLOCK);  //Write first 32 bytes (8 channels) of analog input data from the controller

  delay(2000);  //
}

If the values in the 4-byte fields are 8191 maximum, then the two most-significant bytes should always be zero. If that's true, there's no point even reading those two bytes, and certainly not shifting them by 16 or 24 bits, and then or-ing them into a 2 byte integer, which cannot hold the result if they are not zero.

Some of your arrays seem unnecessary, so I removed them. They are not used again in the code.

I also removed 2 of your 3 for-loops.

“ This product uses Arduino IDE…”
Context is everything.:grinning:

That looks much better, thank you. I always struggle with bit shifting.
This is just a portion of the full code, just for the analog read / write. Some of those arrays will be needed for other uses later.

The reason for the 4-byte length is that the analog temperature modules (that I'm not currently using in this project, but may need to) return a float and require the upper bytes. I plan to turn this into a universal function that I can use with any of their analog modules.

The product IS specifically designed for the Arduino IDE. "Productivity Suite" is the development platform for the non-Arduino PLC style CPUs.
They do have the "Productivity Blocks" software, which is a graphical drag-and-drop programming software (similar to ArduBlock), that runs ON TOP of the Arduino IDE.

Basically, this is an industrial hardened Arduino CPU that can be used in industrial settings where DIN rail mount, 24VDC, 4-20mA, etc. are the standard. There is even a proto board module with the same DIN rail form factor that stacks with everything else.

Normally, I would use a PLC, but this is a very budget sensitive project, and the ProductivityOpen controllers modules are half the price of your typical PLC modules.

No library modules provide, but they do have a little example code.
The code I posted is doing the math / endian conversion, but is just a little clunky.

Which product? I assume it's the development software that can be used in the Arduino IDE, not the hardware modules.

The 8 bit Arduinos have no data byte order, it's only a matter of the compiler. Every CPU can claim to be a Arduino CPU when it is supported in a platform. Then 32 bit CPUs have a specific byte order which most probably is not big endian. I wonder how a mix of big and little endian compilers can be compatible. Here every claim of compatibility is marketing speak only.

I feel like you are trying to poke holes in this hardware.
What development software? Arduino IDE IS the development software. Take one second to look here and it will all become obvious.

The CPU is a Atmel SAMD21G18 microcontroller. It is natively Arduino compatible (it's basically a MKRZero clone). The CPU is NOT big endian.
The CPU module is compatible with DIN rail PLC I/O modules on the right side, and accepts stackable Arduino MKR shields on the left. The MKR shield pin headers are the pinouts you would expect.

The right side modules are not directly accessible via the CPU pins like Arduino shields are. They are interfaced via "base controller" hardware. The CPU communicates to the base controller via SPI. The P1AM.h library provides the means to send/receive discrete and analog I/O.
If you read / write analog I/O one channel at a time, then you get/give a little Endian int

int inputCounts = 0;    //Reading in Counts
inputCounts = P1.readAnalog(1, 2); //Reads analog data from slot 1 channel 2 of the analog input module
inputVolts = 10 * ((float)inputCounts / 8191);  //Convert 13-bit value to Volts

If you want to read/write ALL of the I/O in one shot (faster), that can be done as well. The discreet I/O modules return a little endian array, one BIT per channel.
The analog modules return a big endian formatted array. Why they chose to do that, I don't know. This is the only big-endianness I'm aware of in the system.
Here is their provided example code for the analog block transfer, they can explain better than I can.

/*
  These functions are not recommended for new programmers who are unfamiliar with using arrays or the 
  P1000 I/O modules. These functions do not prevent you from writing incorrectly formatted data. You 
  should have a good understanding of the bitmapped writeDiscrete function before using these functions.
---------------------------------------------------------------------------------------------------------
  
  Example: BlockDataTransferAnalog  
  This example shows how to use the readBlockData and writeBlockData functions specifically with analog 
  P1000 series modules. It is largely the same as the BlockDataTransfer example but accounts for the
  endianess of analog modules. We recommend getting familiar with that example before using this one.
    
	
  Each analog channel uses 4 bytes of data, regardless of the resolution of the module. For Voltage/Current analog
  modules, this data uses the big endian byte order. This means that the 4th byte of each channel contains the
  least significant byte and the 1st byte contains the most significant byte. For temperature modules, the data is
  simply encoded as a 32-bit float regardless of whether a temperature or other range is used. Enabling/Disabling 
  channels or otherwise changing configurations does not affect the amount of data reserved by a module. 
  
  A base is shown below using a mix of analog inputs and outputs. The offset of the data arrays for each module 
  is in each box. Each analog channel uses 4 bytes. 
   _____   _______   _______   _______   _______  _______    
  |  P1 | |   0   | |   0   | |   0   | |   0   ||   0   | 
  |  AM | |   4   | |   8   | |   4   | |   4   ||   4   | 
  |  -  | |   A   | |   D   | |   A   | |   T   ||   D   | 
  |  1  | |   D   | |   A   | |   D   | |   H   ||   A   | 
  |  0  | |   L   | |   L   | |       | |   M   ||   L   | 
  |  0  | |AI - 0 | |AO - 0 | |AI - 16| |AI -32 ||AO - 32| 
   ¯¯¯¯¯   ¯¯¯¯¯¯¯   ¯¯¯¯¯¯¯   ¯¯¯¯¯¯¯   ¯¯¯¯¯¯¯  ¯¯¯¯¯¯¯  
  
  
  
  This example will set the first 8 analog output channels in the base to 0x7FF and read the first 12 analog 
  input channels. It assumes the first 8 analog inputs channels are not from temperature modules
  and the 9th-12th channels are temperature measurements.

  
  This example works with all P1000 Series Analog Modules, but can be adapted to any type of module.
  
  Written by FACTS Engineering
  Copyright (c) 2019 FACTS Engineering, LLC
  Licensed under the MIT license.
*/
#include <P1AM.h>

void setup(){ // the setup routine runs once:

  Serial.begin(115200);  //initialize serial communication at 115200 bits per second 
  
  while(!Serial){
	  ;	//Wait for open serial monitor
  }

  while (!P1.init()){ 
    ; //Wait for Modules to Sign on   
  }

}

char analogOutData[32];    //Raw Data for analog output array
char analogOutCheck[32];   //Array to read back and check values sent match	

char rawAnalogIn[32];   		//Array to store unformatted analog input bytes
int  analogChannelReadings[8];	//Array for individual channel data

char  littleEndianTemp[16];			//Array to store unformatted little endian temperature Bytes
char  bigEndianTemp[16];		    //Array to store unformatted big endian temperature Bytes
float temperatureChannelReadings[4];//Array for individual channel data properly formatted

void loop(){  // the loop routine runs over and over again forever:

  /*Analog Output Data*/
  for(int i = 0; i < 8; i++){     //Copy 0x000007FF into each channel with a big endian byte order
  	analogOutData[4*i + 0] = 0x00;  
	  analogOutData[4*i + 1] = 0x00;  
	  analogOutData[4*i + 2] = 0x07;  	
    analogOutData[4*i + 3] = 0xFF;  
  }

  Serial.println("Analog output data");
  P1.writeBlockData(analogOutData,32,0,ANALOG_OUT_BLOCK);  //Write all 32 bytes of dataArrayOut to the BC Discrete Output Data array
  P1.readBlockData(analogOutCheck,32,0,ANALOG_OUT_BLOCK);  //Read back the 32 bytes just sent to the BC to dataArrayIn

  //Print out each of the bytes just read in. These should match up the dataArrayOut values we just sent
  for(int i = 0; i < 32; i++){
    Serial.print("Analog Output Byte ");
    Serial.print(i);
    Serial.print(" = ");
    Serial.println(analogOutCheck[i],HEX);
  }
  Serial.println("");
  delay(5000);	//Wait until we do the next section
   
   
   /*Analog Input Data*/
   P1.readBlockData(rawAnalogIn,32,0,ANALOG_IN_BLOCK);  //Read first 32 bytes (8 channels) of analog input data
   
  for(int i = 0; i < 8; i++){
	  analogChannelReadings[i] = rawAnalogIn[4*i + 0]<<24;   //OR and shift MSB
	  analogChannelReadings[i] |= rawAnalogIn[4*i + 1]<<16;   //OR and shift 3rd byte
	  analogChannelReadings[i] |= rawAnalogIn[4*i + 2]<<8; 	  //OR and shift 2nd byte
    analogChannelReadings[i] |= rawAnalogIn[4*i + 3];       //OR LSB
  }

  //Print out formatted data for each channel
  for(int i = 0; i < 8; i++){
    Serial.print("Analog Input Channel ");
    Serial.print(i+1);
    Serial.print(" Hex Counts = ");
    Serial.println(analogChannelReadings[i],HEX);
  }
  
  Serial.println("");
  delay(5000);	//Wait 5 seconds before we do the next section

  
  /*Temperature Input Data*/
  P1.readBlockData(littleEndianTemp,16,32,ANALOG_IN_BLOCK); // Read 16 bytes (4 channels) of analog data starting at offset 32 (9th analog in channel in base)

  //Reverse byte order to correct for endianess
  for(int i = 0; i < 4; i++){
    bigEndianTemp[4*i + 3] = littleEndianTemp[4*i + 0];   
    bigEndianTemp[4*i + 2] = littleEndianTemp[4*i + 1];  
    bigEndianTemp[4*i + 1] = littleEndianTemp[4*i + 2];  
    bigEndianTemp[4*i + 0] = littleEndianTemp[4*i + 3];  
  }

  memcpy(temperatureChannelReadings,bigEndianTemp,16); //Copy 16 bytes into our float array. Floats are 4 bytes each.

  for(int i = 0; i < 4; i++){
    Serial.print("Temperature Input Channel ");
    Serial.print(i+1);
    Serial.print(" = ");
    Serial.print(temperatureChannelReadings[i],2);
	  Serial.println(" degrees");
  }

  Serial.println("");
  delay(60000);	//Wait 1 minute before re-running
}

So let's hope that it will stay the only exception in the system.

When I saw this comment, I assumed, with good reason, that the CPU was an 8-bit such as AVR used in Uno/Mega etc.

So, in fact, int will be 4 bytes, not 2 as per your comment, and my comments about int not being able to hold the most significant bytes was wrong also. I was right about those bytes being zero if the maximum value is 8191.

Sorry, I did not RTFM to realize the underlying CPU was 32 bit.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.