Can't read high on PWM pin.

Hello Everyone,

I'm having a problem with my code and was wondering if anyone could help. I'm not sure if I have to explain the whole program since I'm only having trouble with a single line but I will explain just in case the problem is global.

H/W: I'm trying to implement a 3-channel actuator board which opens and shuts a shutter per channel. I am basically controlling a dc motor via an H-Bridge controller (ZXBM5210) and detecting when a linear actuator hits the sides of a wall via current detection (shunt resistor on positive rail of Bridge controller). The motor works @ 12V so I use a resistor divider to lower it at around 5V so I can monitor the voltage drop with the arduino analog pins.

S/W: On a higher level, the open/close of each actuator is controlled via a P.C. On a lower level, I want to make sure that I record the running current value (current in motion before hitting the walls) on the high value of the PWM before it closes and from there on I can adjust the tolerance of the stop current. To do this I use the following loop before I read:

while (!digitalRead(ActiveWritePin));

But every time I insert this line, the program gets stuck for some reason. Once I comment it out, everything works fine. But of coarse this is not reliable since sometimes it may read on the OFF state of the PWM which is not the running current. Obviously, the above procedure is much quicker (uS) than the PWM (mS) so I don't think it has to do with it being quick enough. I'm including the code if anyone wants to have a look. Any help would be much appreciated. Thank you.

#include "IO.h"

  
 byte ActiveWritePin, ActiveReadPin;
 byte Threshold = 0;                 //0:100% - 255:355% of running current.
 int CuttOff, ZeroS, ZeroR, ZeroC, ActiveZero, RunningCurrent;

// the setup routine runs once when you press reset:
void setup() {                
  // I/O initialization.
  pinMode(ActS_R, OUTPUT);
  pinMode(ActS_F, OUTPUT);
  pinMode(ActS_A0, OUTPUT);
  pinMode(ActR_R, OUTPUT);
  pinMode(ActR_F, OUTPUT);
  pinMode(ActR_A0, OUTPUT);
  pinMode(ActR_A1, OUTPUT);
  pinMode(ActR_A2, OUTPUT);
  pinMode(ActC_R, OUTPUT);
  pinMode(ActC_F, OUTPUT);
  pinMode(ActC_A0, OUTPUT);
  pinMode(ActC_A1, OUTPUT);
  
  ZeroS = analogRead(ActS_SV);
  delay(1);
  ZeroR = analogRead(ActR_SV);
  delay(1);
  ZeroC = analogRead(ActC_SV);
  delay(1);
  
  Serial.begin(9600); 
}

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

 if (Serial.available()) {
    char buffer[3];
    Serial.readBytes(buffer,3);
    byte Channel = buffer[0];
    byte Actuator = buffer[1];
    byte Act_Open = buffer[2];
    //byte Threshold = buffer[3];
    switch (Channel) {
    case 0:
      if (Act_Open) {ActiveWritePin = ActS_F;}
      else {ActiveWritePin = ActS_R;}
      ActiveReadPin = ActS_SV;
      ActiveZero = ZeroS; 
      break; 
    case 1:
      if (Act_Open) {ActiveWritePin = ActR_F;}
      else {ActiveWritePin = ActR_R;}
      ActiveReadPin = ActR_SV;
      ActiveZero = ZeroR; 
      break; 
     case 2:
      if (Act_Open) {ActiveWritePin = ActC_F;}
      else {ActiveWritePin = ActC_R;}
      ActiveReadPin = ActC_SV;
      ActiveZero = ZeroC; 
      break; 
    }
    Serial.println("Starting motion...");
    //Serial.print("Acitve Zero: ");                // For debugging purposes
    //Serial.println(ActiveZero);                  // For debugging purposes
    analogWrite(ActiveWritePin, 250);            //Actuate at almost full speed.
    delay(1000);
    analogWrite(ActiveWritePin, 60);              //Approaching stop, Slow Down.
    delay (100);                                  // Wait for current settling.
    while (!digitalRead(ActiveWritePin));        // Make sure you sample on PWM High.
    RunningCurrent = analogRead(ActiveReadPin);   // Sample running current.
    //Serial.print("Running Current: ");         // For debugging purposes
    //Serial.println(RunningCurrent);            // For debugging purposes
    //delay(1500);
    CuttOff = ActiveZero - ((ActiveZero -  RunningCurrent) * (100 + Threshold) / 100);
    while (analogRead(ActiveReadPin) > CuttOff) { delay(1); }
    analogWrite(ActiveWritePin, 0);
    Serial.println("Motion complete.");
   // Serial.println(CuttOff);              // For debugging purposes
 }
  digitalWrite(ActS_A0, HIGH);    // turn the LED on (HIGH is the voltage level)
  delay(100);                     // wait for a 1/10th of a second
  digitalWrite(ActS_A0, LOW);     // turn the LED off by making the voltage LOW
  delay(100);                     // wait for 1/10th of a second
}

....shunt resistor on positive rail of Bridge controller). The motor works @ 12V so I use a resistor divider to lower

If you have a shunt resistor in the positive path to the load, then the voltage between the two ends of the shunt resistor is going to be proportional to the current flowing through it.

Using a resistor divider to lower the voltage to the 5V level is not straightforward, and the "obvious" way won't work.

Sorry, my mistake....I'm not using a shunt resistor but a normal current sensing one (1 Ohm). I then take the voltage after that and divide it down to 5V. I'm not interested in current accuracy so much as differential comparing between one state and another.

while (!digitalRead(ActiveWritePin));

what exactly do you expect this line to do ?

Actually nothing... I'm just polling on the PWM's output and waiting for the positive half of the PWM cycle. Once it's positive, I then go to the next step which is measure the current drop on the resistor. I just want to make sure that I sample on an ON (loaded) state and not an OFF (High-Z) state. Have in mind that "ActiveWritePin" is my control pin going to the Motor driver which I assume is in sync with the actual output to the motor.

I suspect if you want to detect the voltage on a PWM pin as it moves from LOW to HIGH you should connect that pin to another I/O pin set as INPUT and use digitalRead() or direct port manipulation to read that pin. That way you don’t have to interfere with the PWM process.

…R

Hello Robin2,

Thank you for your answer. But I have some questions. What's the difference between reading the PWM's pin register or a say a nearby hardwired register? How does the reading process interfere with the PWM? Isn't the PWM purely a low level hardware process (after it's started by the software)? What difference would a direct port read have since again it involves reading the register?

vlago: How does the reading process interfere with the PWM? Isn't the PWM purely a low level hardware process (after it's started by the software)? What difference would a direct port read have since again it involves reading the register?

I don't know. That's why I suggested separating them.

I suspect a direct port read would be less intrusive than digitalRead(). As far as I know there is a lot of code in the digitalRead() function.

...R

I don't get it.... My PWM pin is on PORTB, so I tried reading PORTB (temp = PORTB;), instead of using digitalRead and then I outputed it (Serial.println(temp);). It is always zero. Probably that is the reason why my previous while loop gets stuck. But the question is why does it always read zero??

Externally connecting the PWM on another pin and polling on that pin does indeed work. But it's not too practical in my case since I would either have to use up 6 additional pins (REV and FWD for each channel) which I don't have or have an external 6 input OR gate and connect it to 1 pin, which I would like to avoid. I still don't get what's going on..... :~

Perhaps it is time to RTFM - the Atmega datasheet and the Arduino PWM code.

...R

vlago: I don't get it.... My PWM pin is on PORTB, so I tried reading PORTB (temp = PORTB;), instead of using digitalRead and then I outputed it (Serial.println(temp);). It is always zero. Probably that is the reason why my previous while loop gets stuck. But the question is why does it always read zero??

PORTB is an output latch register. You read PINB, write PORTB.

int digitalRead(uint8_t pin)
{
    uint8_t timer = digitalPinToTimer(pin);
    uint8_t bit = digitalPinToBitMask(pin);
    uint8_t port = digitalPinToPort(pin);

    if (port == NOT_A_PIN) return LOW;

    // If the pin that support PWM output, we need to turn it off
    // before getting a digital reading.
    if (timer != NOT_ON_TIMER) turnOffPWM(timer);

    if (*portInputRegister(port) & bit) return HIGH;
    return LOW;
}

Yeah....digitalReading your own PWM signal...not so good.

That's really a bug in digitalRead, there's no reason to change the pin mode or PWM drive when just sensing it - digitalWrite needs to do this, digitalRead has no business changing things as its an input function.

BTW one way I've sensed a change on a PWM pin is using pin-change interrupt, specifically to re-broadcast the PWM signal on several pins at once conditional on some variables...

The code for digital read sees if the PWM pin is active and if it is then turns it off. Therefore your trick will not work with a digitalRead call, you need to access the port the bit is sitting on.