Map() function output

Hi everyone. I’m trying to write a program to control a linear actuator based on input from a motion control program. The problem is when the ‘Input’ variable is assigned a fixed value for troubleshooting the program works. When the ‘Input’ is less than ‘Setpoint’ the motor turns in one direction and when ‘Input’ is higher than ‘Setpoint’ the motor turns in the opposite direction. However when ‘Input’ gets its value from the sensor and the map() function is used it doesn’t work. The value of ‘Input’ is always seems higher than ‘Setpoint’ and the only turns in one direction. I’ve used another program to test the sensor output using the map() function and it seems to have a valid output in the serial monitor. Any suggestions would be helpful.

Thanks.

#include <PID_v1.h>
#include <Wire.h>
#include <Adafruit_AS5600.h>
Adafruit_AS5600 sensor;
//int RPWM_Output = 2; // Arduino PWM output pin 5; connect to IBT-2 pin 2 (RPWM)
//int LPWM_Output = 3; // Arduino PWM output pin 6; connect to IBT-2 pin 3 (LPWM)
int cr, count = 1, pin, c, time = 5, retlimit = 1, exlimit = 4095;
int sread[9];
//Define Variables we'll be connecting to
double Input, Output1, Output2, Output3, Output4, Output5, Output6, Setpoint, gap;
//Define the aggressive and conservative Tuning Parameters
double aggKp = 1.2, aggKi = 0.070, aggKd = 0.365;
double consKp = 0.125, consKi = 0.015, consKd = 0.08;

//Specify the links and initial tuning parameters
PID act1PID(&Input, &Output1, &Setpoint, consKp, consKi, consKd, DIRECT);
PID act2PID(&Input, &Output2, &Setpoint, consKp, consKi, consKd, DIRECT);
PID act3PID(&Input, &Output3, &Setpoint, consKp, consKi, consKd, DIRECT);
PID act4PID(&Input, &Output4, &Setpoint, consKp, consKi, consKd, DIRECT);
PID act5PID(&Input, &Output5, &Setpoint, consKp, consKi, consKd, DIRECT);
PID act6PID(&Input, &Output6, &Setpoint, consKp, consKi, consKd, DIRECT);

void setup() {
  Serial.begin(115200);
  for (c = 2; c < 14; c++) {
    pinMode(c, OUTPUT);
  }
  act1PID.SetMode(AUTOMATIC);
  act1PID.SetSampleTime(time);
  act2PID.SetMode(AUTOMATIC);
  act2PID.SetSampleTime(time);
  act3PID.SetMode(AUTOMATIC);
  act3PID.SetSampleTime(time);
  act4PID.SetMode(AUTOMATIC);
  act4PID.SetSampleTime(time);
  act5PID.SetMode(AUTOMATIC);
  act5PID.SetSampleTime(time);
  act6PID.SetMode(AUTOMATIC);
  act6PID.SetSampleTime(time);
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  if (Serial.available() > 0) {
    cr = Serial.read();
    if (cr == 65) {
      do {
        if (Serial.available() > 0) {
          sread[count] = (Serial.read());
          count++;
        }
      } while (count < 9);
    }
  }
  if (sread[1] == 66) {
    //--------------------------------------------------actuator 1
    Setpoint = sread[3];  //Input from BFF motion software. 8 bit. range 0-255
    //Setpoint=map(sread[3], 0, 255, retlimit, exlimit);
    //Setpoint=((Setpoint+1)*2.35)+100;
    Input = sensor.getAngle();            // Input from AS5600 sensor. 12 bit. 0-4095
    Input = map(Input, 0, 4095, 0, 255);  //  Mapped to range 0-255
    //Input = 120; Used for testing.
    if (Setpoint > Input) {
      act1PID.SetControllerDirection(DIRECT);
      analogWrite(3, LOW);
      pin = 2;
      digitalWrite(LED_BUILTIN, LOW);
    } else {
      act1PID.SetControllerDirection(REVERSE);
      analogWrite(2, LOW);
      pin = 3;
      //if (Input > 100 && Input < 150)
      //{
      digitalWrite(LED_BUILTIN, HIGH);
      //}
    }
    gap = abs(Setpoint - Input);  //distance away from setpoint
    if (gap < 6) {                //we're close to setpoint, use conservative tuning parameters
      act1PID.SetTunings(consKp, consKi, consKd);
    } else {
      //we're far from setpoint, use aggressive tuning parameters
      act1PID.SetTunings(aggKp, aggKi, aggKd);
    }

    act1PID.Compute();
    if (gap < 2) {
      Output1 = 0;
    }
    analogWrite(pin, 50);

    count = 1;
  }
}



Some Serial.Print's in here might be illuminating, don't you think?

I wish I could but in order run the the program I have to have the flight sim and the motion control software running at the same time with the motion software sending motion cues via usb. If I try to use Serial.print() with the Arduino IDE running to send data to the serial monitor I get com errors!

What you can't see, you can't debug. Off the top of my head I can think of at least two different ways to make that kind of information visible. Three now. I2C display, SoftwareSerial to a serial/USB converter, tapping off the Tx line and doing the same. And that was with all of 10 seconds thought.

I think the way you handle the Serial communication is not fool proof - Not only it's blocking but count /sread are not properly reset when a new packet starts, which might lead to misaligned or stale data and you end up with a wrong sread[3]

try with something like the below state machine where you build the payload as you go and act on it when it's ready. (I don't store 'A' and 'B' in the payload as it's not meaningful data)

const byte PAYLOAD_SIZE = 7; 
byte payload[PAYLOAD_SIZE];

enum ParserState { WAIT_A, WAIT_B, COLLECT_PAYLOAD };
ParserState state = WAIT_A;
byte count = 0;

void loop() {
  if (Serial.available() > 0) {
    byte b = Serial.read();

    switch (state) {
      case WAIT_A:
        if (b == 'A') state = WAIT_B;
        break;

      case WAIT_B:
        if (b == 'B') {
          state = COLLECT_PAYLOAD;
          count = 0;
        } else if (b == 'A') {
          // still in WAIT_B, allow repeated 'A'
          state = WAIT_B;
        } else {
          state = WAIT_A;
        }
        break;

      case COLLECT_PAYLOAD:
        payload[count++] = b;
        if (count == PAYLOAD_SIZE) {
          state = WAIT_A;
          count = 0;

          // >>>>>>---------------------------------- <<<<<
          // >>>>>> full payload received, process it <<<<<
          // >>>>>>---------------------------------- <<<<<

        }
        break;
    }
  }

  // other non-blocking tasks
}

PS/ if you want to study state machines. Here is a small introduction to the topic: Yet another Finite State Machine introduction

Thanks for tips. I will look in to them. Just an update. I replaced. Input = sensor.getAngle(); Input = map(Input, 0, 4095, 0, 255) with Input = ((sensor.getAngle()+1) / 16) to get the same output range and it still doesn’t work. So I don’t think it’s an issue with the map() function…

Another thing: the map with the given values does not evenly distribute the 0–4095 range into the 0–255 range (one output point every 16 input points); in fact, the value 255 appears only when the input is exactly 4095.
For a uniform distribution (0–15 → 0, 4080–4095 → 255), it should be written as:

map(Input, 0, 4096, 0, 256);

But this is also pointless, since simply dividing by 16 (or shifting four positions to the right >> 4) yields the same result.

J-M-L Jackson. ‘count’ is reset to 1 at the last line. I’ve never had an issue with pc to arduino communications, and this program was working before when I was using a potentiometer with the analogRead() function. This problem started when I switched to the as5600 using i2c.

I’m reading on my phone so can’t really check the indent but don’t you reset only if you got ‘B’ ?

Motion software motion cue protocol is:

BIN output format is - “AB” byte1 byte2 byte3 byte4 byte5 byte6 byte7 0x0D (CR)

"AB" - start of data identifier for the receiving micro controller

byte1 - reserved

byte2 - 8 bit binary number giving act1 demand in 0-255 scale

byte3 - 8 bit binary number giving act2 demand in 0-255 scale

byte4 - 8 bit binary number giving act3 demand in 0-255 scale

byte5 - 8 bit binary number giving act4 demand in 0-255 scale

byte6 - 8 bit binary number giving act5 demand in 0-255 scale

byte7 - 8 bit binary number giving act6 demand in 0-255 scale

0x0D - single byte Carriage Return data terminator

in case anyone is interested.

I’ve found an old i2c lcd display. I’ll use use that for troubleshooting. Thanks again.

Basically listen for ‘A’. Start storing data when received. Ignore ‘B’ and byte 1 and use byte2 to byte7 for motion info. (1 byte per actuator of a Stewart platform).

How often do you get the Serial input?

Correct me if I’m wrong, but an Arduino has only a 10 bit A/D. Map should be (0, 1023, 0, 255), maybe ?? Tom

The map() function used is for the i2c input from the as5600 sensor which has a 12 bit output.

However, to help with synchronization, I would also check the CR :wink:

definitely - the spec of the protocol was posted after I suggested some changes.

Ensuring the last byte received is indeed CR is needed

That’s what I thought. Even though the sensor outputs 12 bits, the arduino (depending on the model, maybe all of them??) can only read up to 10 bits worth. The arduino can still read the full output voltage, but I don’t think it is quite as precise using that voltage in a 10 bit format verses the 12 bit format. I think it truncates the 2 extra bits and only uses the 10 bits worth of info.

It looks like an i2c sensor to me. So it doesn't go through the AVR analog circuitry...

No the library doesn’t. You have a digital process through I2C to access the data and it does return 16 bits, 12 of which are the raw value scaled according to ZPOS/MPOS/MANG settings.

Side note - you still have not answered the question on how frequently you get serial input