What is the inverse of analogWrite(pin,val)?

The analogWrite(pin.val) function, analogWrite() - Arduino Reference is defined as void analogWrite(uint8_t pin, int val) in ArduinoCore-avr/wiring_analog.c at master · arduino/ArduinoCore-avr · GitHub

Is there an inverse function like int analogReadWrite(uint8_t pin){} that would read and return the appropriate timer's OCnx?

My use-case is to simulate a device attached to a PWM, and I'd like to read back the PWM value without modifying the controlling code

/*
  FadeWOD & analogWriteRead, adapted from
  https://www.arduino.cc/en/Tutorial/BuiltInExamples/Fade
  https://github.com/arduino/arduino-examples/blob/main/examples/01.Basics/Fade/Fade.ino
  Simulation at https://wokwi.com/arduino/projects/323945277471851092
  Forum https://forum.arduino.cc/t/what-is-the-inverse-of-analogwrite-pin-val/960774
  Arduino CC0-1.0
  DaveX 2022-02-18 CC SA

  This example shows how to fade an LED on pin 9 using the analogWrite()
  function without blocking.

  The analogWrite() function uses PWM, so if you want to change the pin you're
  using, be sure to use another PWM capable pin. On most Arduino, the PWM pins
  are identified with a "~" sign, like ~3, ~5, ~6, ~9, ~10 and ~11.

  This example code is in the public domain.

  http://www.arduino.cc/en/Tutorial/Fade
*/

int led = 9;           // the PWM pin the LED is attached to
int brightness = 0;    // how bright the LED is
int fadeAmount = 5;    // how many points to fade the LED by

// the setup routine runs once when you press reset:
void setup() {
  // declare pin 9 to be an output:
  pinMode(led, OUTPUT);
}

// the loop routine runs over and over again forever:
void loop() {
  // non-blocking wrapper
  const unsigned long adjustInterval = 30;
  static unsigned long lastAdjust = 0;
  if ( millis() - lastAdjust > adjustInterval) {
    lastAdjust += adjustInterval;
    // set the brightness of pin 9:
    analogWrite(led, brightness);

    // change the brightness for next time through the loop:
    brightness = brightness + fadeAmount;

    // reverse the direction of the fading at the ends of the fade:
    if (brightness <= 0 || brightness >= 255) {
      fadeAmount = -fadeAmount;
    }
    // wait for 30 milliseconds to see the dimming effect
    //delay(30); // handled by adjustInterval
  
    // ***************
    // This `analogWriteRead(pin);` functionality is what I'm looking for: 
    // Serial.println(analogWriteRead(9));
    // *************** 

  } // end of millis() adjustInterval code

}

Is there handy function that reads the PWM value that has been written with analogWrite(pin,val); ?

It looks like for a particular pin, one could dig into ArduinoCore-avr/wiring_analog.c at master · arduino/ArduinoCore-avr · GitHub and the pinouts and write something like:

int analogWriteReadUnoPin9PWM(){
   // based on 9 = OC1A from 
  // https://upload.wikimedia.org/wikipedia/commons/c/c9/Pinout_of_ARDUINO_Board_and_ATMega328PU.svg
   return OCR1A;
}

...but that seems a bit awkward.

I’m sure I don’t understand.

Wouldn’t analogReadWrite (nice name!) just be returning what you “analogWrote}” a few kind back?

Or have a linear relation to another number you need?

And if it is in a separate place you need that number, I see that I would have to modify the code, but you could set a global variable at the time and place of the analogWrite.

I guess I’m hacking away at my code so much alla time I think nothing of strewing all kindsa changes around.

In this case, perhaps creating a function to do all the analogWriting, then calling that function everywhere you were doing ananlogWrite would let you have code that read the same, more or less, and a localized ability to change just what and all happens with every analaogWrite.

a7

Yes, it would be the same. But my intention is to not mess with the code that is doing the analogWrite() so I can simulate a device.

In the FadingWOD example above, you could simulate an incandescent lamp that only lights above a threshold with:


pinMode(LED_BUILTIN,OUTPUT);
...
void simLamp(void){
   static bool haltAndCatchFire = FALSE;
   digitalWrite(LED_BUILTIN, (analogWriteRead(9) > 80));
   if(analogWriteRead(9) == 254) haltAndCatchFire = TRUE;
   if( haltAndCatchFire) 
      while(TRUE)
         ;
   ...
}
...

void loop(){
  simLamp();
 ...
}

Here's a simulated device that, for the wrong PWM setting can melt things down:

One might want to simulate a device that one doesn't own, or an ideal device, or to test someone else's code before hooking it up to your device.

I think you want this:

/*
  FadeWOD & analogWriteRead, adapted from
  https://www.arduino.cc/en/Tutorial/BuiltInExamples/Fade
  https://github.com/arduino/arduino-examples/blob/main/examples/01.Basics/Fade/Fade.ino
  Simulation at https://wokwi.com/arduino/projects/323945277471851092
  Forum https://forum.arduino.cc/t/what-is-the-inverse-of-analogwrite-pin-val/960774
  Arduino CC0-1.0
  DaveX 2022-02-18 CC SA

  This example shows how to fade an LED on pin 9 using the analogWrite()
  function without blocking.

  The analogWrite() function uses PWM, so if you want to change the pin you're
  using, be sure to use another PWM capable pin. On most Arduino, the PWM pins
  are identified with a "~" sign, like ~3, ~5, ~6, ~9, ~10 and ~11.

  This example code is in the public domain.

  http://www.arduino.cc/en/Tutorial/Fade
*/

int led = 9;           // the PWM pin the LED is attached to
int brightness = 0;    // how bright the LED is
int oldBrightness;
int fadeAmount = 5;    // how many points to fade the LED by

// the setup routine runs once when you press reset:
void setup()
{
  // declare pin 9 to be an output:
  pinMode(led, OUTPUT);
}

// the loop routine runs over and over again forever:
void loop()
{
  // non-blocking wrapper
  const unsigned long adjustInterval = 30;
  static unsigned long lastAdjust = 0;
  if ( millis() - lastAdjust > adjustInterval)
  {
    lastAdjust += adjustInterval;
    // remember and set the brightness of pin 9:
    oldBrightness = brightness;
    analogWrite(led, brightness);

    // change the brightness for next time through the loop:
    brightness = brightness + fadeAmount;

    // reverse the direction of the fading at the ends of the fade:
    if (brightness <= 0 || brightness >= 255)
    {
      fadeAmount = -fadeAmount;
    }
    // wait for 30 milliseconds to see the dimming effect
    //delay(30); // handled by adjustInterval

    // ***************
    // This `analogWriteRead(pin);` functionality is what I'm looking for:
    Serial.println(oldBrightness);
    // ***************

  } // end of millis() adjustInterval code

}

Or, even better, this:

/*
  FadeWOD & analogWriteRead, adapted from
  https://www.arduino.cc/en/Tutorial/BuiltInExamples/Fade
  https://github.com/arduino/arduino-examples/blob/main/examples/01.Basics/Fade/Fade.ino
  Simulation at https://wokwi.com/arduino/projects/323945277471851092
  Forum https://forum.arduino.cc/t/what-is-the-inverse-of-analogwrite-pin-val/960774
  Arduino CC0-1.0
  DaveX 2022-02-18 CC SA

  This example shows how to fade an LED on pin 9 using the analogWrite()
  function without blocking.

  The analogWrite() function uses PWM, so if you want to change the pin you're
  using, be sure to use another PWM capable pin. On most Arduino, the PWM pins
  are identified with a "~" sign, like ~3, ~5, ~6, ~9, ~10 and ~11.

  This example code is in the public domain.

  http://www.arduino.cc/en/Tutorial/Fade
*/

int led = 9;           // the PWM pin the LED is attached to
int brightness = 0;    // how bright the LED is
int fadeAmount = 5;    // how many points to fade the LED by

// the setup routine runs once when you press reset:
void setup()
{
  // declare pin 9 to be an output:
  pinMode(led, OUTPUT);
}

// the loop routine runs over and over again forever:
void loop()
{
  // non-blocking wrapper
  const unsigned long adjustInterval = 30;
  static unsigned long lastAdjust = 0;
  if ( millis() - lastAdjust > adjustInterval)
  {
    lastAdjust += adjustInterval;

    // remember and set the brightness of pin 9:
    Serial.println(brightness);
    analogWrite(led, brightness);

    // change the brightness for next time through the loop:
    brightness = brightness + fadeAmount;

    // reverse the direction of the fading at the ends of the fade:
    if (brightness <= 0 || brightness >= 255)
    {
      fadeAmount = -fadeAmount;
    }

  } // end of millis() adjustInterval code

}

OK, so you want to have a function that you can stick into the original code, in your example at the end of the free running unblocked loop() that can read back the value the rest of the (otherwise unmolested not changed) program analogWrote.

I see the point, no need to go into it, no fear of otherwise wrecking it accidentally.

Reading back the register looks like the only solution, and that depends on the origin author using analogWrite in a plain vanilla fashion OR combing through to see how it might not be. Plain.

Awkward is in the mind of the beholder. I think it is clever and beautiful. :wink:

Just don’t know how ‘xactly to do that, registers and stuff you knw, ppl make a career of it.

In case anyone wonders, the wokwi runs on a simulation of the chip, so all the registers are there and operate with fidelity to the real world.

a7

1 Like

Aw shucks...

I think the more general function might look much like https://github.com/arduino/ArduinoCore-avr/blob/master/cores/arduino/wiring_analog.c#L96

There's the digitalPinToTimer(pin) function , and the trick is mapping the resultant timer code (https://github.com/arduino/ArduinoCore-avr/blob/master/cores/arduino/Arduino.h#L205) to it's control register. I doubt that exists in the Arduino core code, since they didn't make use of it in the forward direction in the analogWrite() code.

I looked at that code and if I was at the big rig would dare take a stab at it but I see @johnwasser over there who is the one to cut this butter like a torch.

That’s for sure the kind of thing you’d need to do to make a general solution for plain usage of anslogWrite for you anslogReadWrite.

a7

Hacking up the stock analogWrite wasn't too bad with find&replace:

int analogWriteRead(uint8_t pin)
{
  // This reads what has been written

  int val ;
  switch (digitalPinToTimer(pin))
  {
      // XXX fix needed for atmega8
#if defined(TCCR0) && defined(COM00) && !defined(__AVR_ATmega8__)
    case TIMER0A:
      val = OCR0; // get pwm duty
      break;
#endif

#if defined(TCCR0A) && defined(COM0A1)
    case TIMER0A:
      val = OCR0A; // get pwm duty
      break;
#endif

#if defined(TCCR0A) && defined(COM0B1)
    case TIMER0B:
      val = OCR0B; // get pwm duty
      break;
#endif

#if defined(TCCR1A) && defined(COM1A1)
    case TIMER1A:
      val = OCR1A; // get pwm duty
      break;
#endif

#if defined(TCCR1A) && defined(COM1B1)
    case TIMER1B:
      val = OCR1B; // get pwm duty
      break;
#endif

#if defined(TCCR1A) && defined(COM1C1)
    case TIMER1C:
      val = OCR1C; // get pwm duty
      break;
#endif

#if defined(TCCR2) && defined(COM21)
    case TIMER2:
      val = OCR2; // get pwm duty
      break;
#endif

#if defined(TCCR2A) && defined(COM2A1)
    case TIMER2A:
      val = OCR2A; // get pwm duty
      break;
#endif

#if defined(TCCR2A) && defined(COM2B1)
    case TIMER2B:
      val = OCR2B; // get pwm duty
      break;
#endif

#if defined(TCCR3A) && defined(COM3A1)
    case TIMER3A:
      val = OCR3A; // get pwm duty
      break;
#endif

#if defined(TCCR3A) && defined(COM3B1)
    case TIMER3B:
      val = OCR3B; // get pwm duty
      break;
#endif

#if defined(TCCR3A) && defined(COM3C1)
    case TIMER3C:
      val = OCR3C; // get pwm duty
      break;
#endif

#if defined(TCCR4A)
    case TIMER4A:
      val = OCR4A;  // get pwm duty
      break;
#endif

#if defined(TCCR4A) && defined(COM4B1)
    case TIMER4B:
      val = OCR4B; // get pwm duty
      break;
#endif

#if defined(TCCR4A) && defined(COM4C1)
    case TIMER4C:
      val = OCR4C; // get pwm duty
      break;
#endif

#if defined(TCCR4C) && defined(COM4D1)
    case TIMER4D:
      val = OCR4D;  // get pwm duty
      break;
#endif


#if defined(TCCR5A) && defined(COM5A1)
    case TIMER5A:
      val = OCR5A; // get pwm duty
      break;
#endif

#if defined(TCCR5A) && defined(COM5B1)
    case TIMER5B:
      val = OCR5B; // get pwm duty
      break;
#endif

#if defined(TCCR5A) && defined(COM5C1)
    case TIMER5C:
      val = OCR5C; // get pwm duty
      break;
#endif

    case NOT_ON_TIMER:
    default:
      val = digitalRead(pin) ? 255 : 0;
  }
  return val;
}

And best of all, it works:

0
5
10
15
20
25
30
35
40
45
50
55
60
65
70
75
80
85
90
95
100
105
110
115
120
125
130
135
140
145
150
155
160
165
170
175
180
185
190
195
200
205
210
215
220
225
230
235
240
245
250
250
250
245
240
235
230
225
220
215
210
205
200
195
190
185
180
175
170
165
160
155
150
145
140
135
130
125
120
115
110
105
100
95
90
85
80
75
70
65
60
55
50
45
40
35
30
25
20
15
10
5
5
5
10
15
20
25
30
35
40
45
50
55
60
65
70
75
80
...

edited to fix some comments.

Thanks. It does have the same effect for this code, but I was looking to leave the the code mostly untouched an add some instrumentation for monitoring the code's IO in a corner of the unblocked loop()

I think I did modify the stock analogWrite() into an analogWriteRead() in What is the inverse of analogWrite(pin,val)? - #8 by DaveX

I'm not sure about the name though. I'll misremember it backwards too easily.

Nice work.

When you think of a better name (analogGetPWM(…) ?) fix these comments, too. :expressionless:

a7

1 Like

edit-edit... Done!

int analogGetPWM(uint8_t pin) is good.

There is nothing really analog about this, it’s a succession of HIGH and LOW…

readPWM() or getPWM() would be good enough :innocent:

1 Like

This is way out of my wheelhouse, but the 328p data sheet (page 116 and 117 of my old version) recommends disabling interrupts when reading OCRxx. I'm not seeing that in these code samples. Any reasons why?
image

OP's hack (fragment)

#if defined(TCCR1A) && defined(COM1C1)
    case TIMER1C:
      val = OCR1C; // get pwm duty
      break;
#endif

Original code informing that hack (fragment)

 #if defined(TCCR1A) && defined(COM1C1)
    case TIMER1C:
       // connect pwm to pin on timer 1, channel C
     sbi(TCCR1A, COM1C1);
     OCR1C = val; // set pwm duty
     break;
#endif

for all cases.

analogWrite code, huge switch based on underlying architecture.

I am not doubting the data sheet, but I am waiting for the heavies to 'splain it, as it appears that analogWrite ignores this advisory.

@DaveX is curtainly innocent. :expressionless:

a7

yes protecting against an interrupt at the wrong time is useful.

I think the trick is that when you access a 16 bit register with its 16 bit name, the compiler is smart enough to protect the process.

But if you access it by bytes, for example as OCR1AH and OCR1AL, you have to do the protection yourself.

Either way, I was aiming to do be no more dangerous than analogWrite.

Would be interesting to see the assembly for that just to be sure. I can’t test right now

when you access a 16 bit register with its 16 bit name, the compiler is smart enough to protect the process

I don't believe that that's true.

I also don't believe that it matters, unless you're also reading/writing the registers in ISRs.

And it probably doesn't matter because the timers are all operated in 8bit mode, anyway.

I'm wrong: Here's a bit of avr-objdump -dS on some simple setup code:

void setup() {
  // put your setup code here, to run once:
  OCR1A = OCR1B+1;
 1aa:   80 91 8a 00     lds     r24, 0x008A     ; 0x80008a <__DATA_REGION_ORIGIN__+0x2a>
 1ae:   90 91 8b 00     lds     r25, 0x008B     ; 0x80008b <__DATA_REGION_ORIGIN__+0x2b>
 1b2:   01 96           adiw    r24, 0x01       ; 1
 1b4:   90 93 89 00     sts     0x0089, r25     ; 0x800089 <__DATA_REGION_ORIGIN__+0x29>
 1b8:   80 93 88 00     sts     0x0088, r24     ; 0x800088 <__DATA_REGION_ORIGIN__+0x28>
  noInterrupts();
 1bc:   f8 94           cli
  OCR1A = OCR1B+0xa5;
 1be:   80 91 8a 00     lds     r24, 0x008A     ; 0x80008a <__DATA_REGION_ORIGIN__+0x2a>
 1c2:   90 91 8b 00     lds     r25, 0x008B     ; 0x80008b <__DATA_REGION_ORIGIN__+0x2b>
 1c6:   8b 55           subi    r24, 0x5B       ; 91
 1c8:   9f 4f           sbci    r25, 0xFF       ; 255
 1ca:   90 93 89 00     sts     0x0089, r25     ; 0x800089 <__DATA_REGION_ORIGIN__+0x29>
 1ce:   80 93 88 00     sts     0x0088, r24     ; 0x800088 <__DATA_REGION_ORIGIN__+0x28>
  interrupts();  
 1d2:   78 94           sei
        
        setup();

I discovered an issue with my code--

Since the analogWrite code at ArduinoCore-avr/wiring_analog.c at master · arduino/ArduinoCore-avr · GitHub switches to digitalWrite for the analogWrite(pin,0) and analogWrite(pin,255) cases, just reading the OCRnx register isn't enough.

For example, analogWrite(pin,127);analogWrite(pin,0); would leave the OCRnx register with 127, but would turn off the TCCRnx & bit(COMnx1) bit so the pin is controlled by the PORTx:bit

one would have to detect whether TCCR1A & bit(COM1A1); is set and if so, then the OCR1A is controlling the pin, but if not, then the pin is controlled by digitalWrite and you have to dig up something like
*portOutputRegister(digitalPinToPort(pin))&digitalPintoBitMask(pin)?255:0;

Maybe:

...
#if defined(TCCR1A) && defined(COM1A1)
    case TIMER1A:
      if (TCCRA1 & bit(COM1A1) { // pin is OC1A
         val = OCR1A; // get pwm duty
      } else { // pin not controlled PWM OC1A, but by output port instead
         //val = *portOutputRegister(digitalPinToPort(pin))&digitalPinToBitMask(pin)?255:0;
         digitalRead(pin)*255;
      }
      break;
#endif
...

It was almost working in dc_motor_sim.ino - Wokwi Arduino and ESP32 Simulator