Loopback PWM

Status
Not open for further replies.

brtaylor

Well-known member
I'm experiencing issues trying to loopback PWM. I have pin 5 connected to pin 23 using a jumper, that is the only connection.

Here is what I am trying to do:
1. Create a 1 Hz sine wave
2. Scale the sine wave from +/-1 to 1000 to 2000
3. Use analogWrite to send this as a 50 Hz, 16 bit resolution PWM to pin 5
4. Have my ISR measure the pulse width on pin 23
5. Print the results

In theory the PWM that was sent on pin 5 should be roughly the same as measured on pin 23. Individually, these functions work, I can send a PWM to a servo and have it move according to the 1 Hz sine wave. I can connect an RC receiver to pin 23 and see the PWM commands. But in this loopback function, I can see my sent PWM, but my received PWM remains 0. I am using elapsedMicros for measuring the pulse width and analogWrite for sending the PWM.

My code is below, any thoughts?

Code:
/* PWM read pin */
const unsigned int ReadPin = 23;
/* PWM write pin */
const unsigned int WritePin = 5;
/* used for reading the PWM */
elapsedMicros elapsedTime_us;
/* storing the read value */
volatile unsigned int ReadVal_us;
/* storing the write value */
unsigned int WriteVal_us;
/* PWM update frequency, 50 Hz */
const unsigned int Freq = 50;
/* PWM update period */
const unsigned int Period_us = 1000000 / Freq;
/* PWM resolution */
const unsigned int PWM_Resolution = 16;
/* Time, ms */
elapsedMillis time_ms;

void meas_pwm() 
{
  /*
  * Read the value, if it's high, we just started the pulse and need
  * to zero the timer. If it's low, we just finished the pulse and
  * should store the pulse width result.
  */
  if (digitalReadFast(ReadPin)) {
    elapsedTime_us = 0;
  } else {
    ReadVal_us = elapsedTime_us;
  }
}

void setup()
{
  /*
  * Start USB serial to display results
  */
  Serial.begin(115200);
  while(!Serial) {}
 
  /* setting the analog frequency to 50 Hz and resolution to 16 bit */
  analogWriteFrequency(WritePin, Freq);
  analogWriteResolution(PWM_Resolution);
 /*
  * Assign PWM pin as input
  */
  pinMode(ReadPin,INPUT);
  /*
  * Attach our ISRs to changes in the respective pins
  */
  attachInterrupt(ReadPin,meas_pwm,CHANGE);
}

void loop()
{
  /*
  * Print the results
  */
  Serial.print(ReadVal_us);
  Serial.print("\t");
  Serial.println(WriteVal_us);
  /* 1 Hz sine wave */
  float Cmd = sinf(2.0f * M_PI * time_ms / 1000.0f);
  /* Scale from +/- 1 to a range of 1000 us to 2000 us */
  Cmd = Cmd * 500.0f + 1500.0f;
  /* Command channel */
  WriteVal_us = Cmd;
  analogWrite(WritePin,WriteVal_us / Period_us * powf(2,PWM_Resolution));
  delay(20);
}
 
16 bit resolution on PWM says 0 to 65535.

Until it hits near HALF scale it won't show as a 1 on a digital input. Anything less is read as Zero

Not sure this was right but putting " Cmd = Cmd * 500.0f + 1500.0f;" to :: Cmd = Cmd * 20000.0f + 4500.0f;

Gives the following where added iCnt shows the isr() trigger count and 0[# or 1[# lines show how many times that value ReadFast during the wait:
Code:
220067	 Read <  > iCnt 0	0
220067	 Read <  > iCnt 0	1247
220067	 Read <  > iCnt 0	3746
220067	 Read <  > iCnt 0	6257
220067	 Read <  > iCnt 0	8740
220067	 Read <  > iCnt 0	11156
220067	 Read <  > iCnt 0	13467
220067	 Read <  > iCnt 0	15637
220067	 Read <  > iCnt 0	17631
220067	 Read <  > iCnt 0	19418
0[3695091
220067	 Read <  > iCnt 1	20970
220067	 Read <  > iCnt 0	22262
220067	 Read <  > iCnt 0	23274
220067	 Read <  > iCnt 0	23990
220067	 Read <  > iCnt 0	24399
220067	 Read <  > iCnt 0	24493
220067	 Read <  > iCnt 0	24273
220067	 Read <  > iCnt 0	23740
220067	 Read <  > iCnt 0	22904
220067	 Read <  > iCnt 0	21778
220067	 Read <  > iCnt 0	20379
1[1042184
220068	 Read <  > iCnt 1	18730
220068	 Read <  > iCnt 0	16857
220068	 Read <  > iCnt 0	14788
220068	 Read <  > iCnt 0	12558
220068	 Read <  > iCnt 0	10200
220068	 Read <  > iCnt 0	7752
220068	 Read <  > iCnt 0	5253
220068	 Read <  > iCnt 0	2742
220068	 Read <  > iCnt 0	259
220068	 Read <  > iCnt 0	0

Where I used this code edit to count how many times the interrupt triggers, and instead of delay(20) I watched the ReadPin during that time:
Code:
// https://forum.pjrc.com/threads/54270-Loopback-PWM
/* PWM read pin */
const unsigned int ReadPin = 23;
/* PWM write pin */
const unsigned int WritePin = 5;
/* used for reading the PWM */
elapsedMicros elapsedTime_us;
/* storing the read value */
volatile unsigned int ReadVal_us;
/* storing the write value */
unsigned int WriteVal_us;
/* PWM update frequency, 50 Hz */
const unsigned int Freq = 50;
/* PWM update period */
const unsigned int Period_us = 1000000 / Freq;
/* PWM resolution */
const unsigned int PWM_Resolution = 16;
/* Time, ms */
elapsedMillis time_ms;

volatile uint32_t iCnt = 0;
void meas_pwm()
{
  /*
    Read the value, if it's high, we just started the pulse and need
    to zero the timer. If it's low, we just finished the pulse and
    should store the pulse width result.
  */
  if (digitalReadFast(ReadPin)) {
    elapsedTime_us = 0;
  } else {
    ReadVal_us = elapsedTime_us;
  }
  iCnt++;
}

void setup()
{
  /*
    Start USB serial to display results
  */
  Serial.begin(115200);
  while (!Serial) {}

  /* setting the analog frequency to 50 Hz and resolution to 16 bit */
  analogWriteFrequency(WritePin, Freq);
  analogWriteResolution(PWM_Resolution);
  /*
     Assign PWM pin as input
  */
  pinMode(ReadPin, INPUT);
  /*
    Attach our ISRs to changes in the respective pins
  */
  attachInterrupt(ReadPin, meas_pwm, CHANGE);
}

elapsedMicros usWait;
uint32_t rpLast = 0, ii = 0;
void loop()
{
  /*
    Print the results
  */
  Serial.print(ReadVal_us);
  Serial.print("\t Read <  > iCnt ");
  Serial.print(iCnt);
  iCnt = 0;
  Serial.print("\t");
  Serial.println(WriteVal_us);
  /* 1 Hz sine wave */
  float Cmd = sinf(2.0f * M_PI * time_ms / 1000.0f);
  /* Scale from +/- 1 to a range of 1000 us to 2000 us */
  Cmd = Cmd * 20000.0f + 4500.0f;
  /* Command channel */
  WriteVal_us = Cmd;
  analogWrite(WritePin, WriteVal_us / Period_us * powf(2, PWM_Resolution));
  usWait = 0;
  //delay(20);
  uint32_t rpNow;
  while ( usWait < 20000 ) {
    rpNow = digitalReadFast(ReadPin);
    if ( rpNow == rpLast ) {
      ii++;
    }
    else {
      Serial.print(rpLast);
      Serial.print("[");
      Serial.println(ii);
      rpLast = rpNow;
      ii = 1;

    }
  }
}
 
Too Late ...

Maybe my 'Not sure this was right ' part just moved the decimal enough? Would like to see your updated code.
 
Here's the fixed code:

Code:
/* PWM read pin */
const unsigned int ReadPin = 23;
/* PWM write pin */
const unsigned int WritePin = 5;
/* used for reading the PWM */
elapsedMicros elapsedTime_us;
/* storing the read value */
volatile unsigned int ReadVal_us;
/* storing the write value */
unsigned int WriteVal_us;
/* PWM update frequency, 50 Hz */
const unsigned int Freq = 50;
/* PWM update period */
const unsigned int Period_us = 1000000 / Freq;
/* PWM resolution */
const unsigned int PWM_Resolution = 16;
/* Time, ms */
elapsedMillis time_ms;

void meas_pwm() 
{
  /*
  * Read the value, if it's high, we just started the pulse and need
  * to zero the timer. If it's low, we just finished the pulse and
  * should store the pulse width result.
  */
  if (digitalReadFast(ReadPin)) {
    elapsedTime_us = 0;
  } else {
    ReadVal_us = elapsedTime_us;
  }
}

void setup()
{
  /*
  * Start USB serial to display results
  */
  Serial.begin(115200);
  while(!Serial) {}
 
  /* setting the analog frequency to 50 Hz and resolution to 16 bit */
  analogWriteFrequency(WritePin, Freq);
  analogWriteResolution(PWM_Resolution);
 /*
  * Assign PWM pin as input
  */
  pinMode(ReadPin,INPUT);
  /*
  * Attach our ISRs to changes in the respective pins
  */
  attachInterrupt(ReadPin,meas_pwm,CHANGE);
}

void loop()
{
  /*
  * Print the results
  */
  Serial.print(ReadVal_us);
  Serial.print("\t");
  Serial.println(WriteVal_us);
  /* 1 Hz sine wave */
  float Cmd = sinf(2.0f * M_PI * time_ms / 1000.0f);
  /* Scale from +/- 1 to a range of 1000 us to 2000 us */
  Cmd = Cmd * 500.0f + 1500.0f;
  /* Command channel */
  WriteVal_us = Cmd;
  analogWrite(WritePin,(float) WriteVal_us / Period_us * powf(2,PWM_Resolution));
  delay(20);
}

The only difference is casting WriteVal_us to a float in the analogWrite so the division goes as expected. I was just using Cmd in a previous version, but I wanted to print the value written and value received, which is why this was changed in the first place.
 
Thanks. With that the iCnt code added in isr() and the checking during the 'delay(20)' period works with that to catch the changes that happen with the (float) cast.

Just got a $20 Logic Analyzer from SparkFun and it catches transitions - though the exaggerated math looks more interesting.
 
Status
Not open for further replies.
Back
Top