UNO Q: Interaction between pinMode() and analogWrite()

Continuing the discussion from Enabling PWM prevents some GPIO pins from working:

I believe that anoalogWrte(pwm-pin) function embeds the pinMode(pwm-pin, OUTPUT) line to set the PWM-pin as output. Now, if a user executes the following code, what function will be effective? Practically, as I have understood from this thread, the concerned pin is confused if working as pwm-pin or digital-pin.

pinMode(pwm-pin, OUTPUT);
analogWrite(pwm-pin);

@pert

Thank you for opening a new thread on my behalf. Now, we can discuss the issue elaborately.

First off this conversation probably depends on what version of the UNOQ board release you are talking about as some of this is and has changed.

Note: sometimes zephyr and Arduino are like round pegs in square holes. That is with native Zephyr, for your app you define a specific device tree for your app to say what devices you are using, what pins, etc. So for example if you define that you are using some SPI object than when your sketch/app starts the pins are preconfigured for SPI, this include mode, drive, pullup/down, etc... With old/current released version of UnoQ, some of the pins were configured for PWM and if you did pinMode(...) that overwrote that settings and nothing else set it back into PWM or SPI, or Wire, or... mode.

With current released version, there are some hacks that try to reset the pins in places like SPI.begin back to SPI mode...

With the current sources, which are probably soon to be released, this has additional support to allow pins to be reconfigured at run time.

One of the great things about Arduino, is that almost all of the source code is available to look at. If you look in the ArduinoCore-zephyr project source code under the directories cores/arduino, and look
at wiring_analog.cpp, it contains:

void analogWrite(pin_size_t pinNumber, int value) {
	const int maxInput = BIT(_analog_write_resolution) - 1U;
	const int digitalThreshold = maxInput >> 1;
	size_t idx = pwm_pin_index(pinNumber);

	if (idx >= ARRAY_SIZE(arduino_pwm)) {
		pinMode(pinNumber, OUTPUT);
		digitalWrite(pinNumber, value > digitalThreshold ? HIGH : LOW);
		return;
	}

	(void)init_dev_apply_channel_pinctrl(arduino_pwm[idx].dev,
										 state_pin_index_from_spec_index(arduino_pwm, idx));

...

That init_dev_apply_... function is new and it resets the pin to PWM mode...

likewise: wiring_digital.cpp has pinMode: with calls like:
gpio_pin_configure_dt(&arduino_pins[pinNumber], GPIO_INPUT | GPIO_ACTIVE_HIGH);

Among other things, these functions, configure the STM32's GPIO registers to the right mode and to the right Alternate function.

It is also good to look at the device tree information, that is both in the ArduinoCore-zephyr as well as the zephyr projects. From zephyr side the device tree has in it

		/* Currently only the pins marked with ~ on the pin headers are enabled */
		pwms = 
		       /*<&pwm4 2 PWM_HZ(500) PWM_POLARITY_NORMAL>,*/   /* D0/PB7 → TIM4_CH2 */
		       /*<&pwm4 1 PWM_HZ(500) PWM_POLARITY_NORMAL>,*/   /* D1/PB6 → TIM4_CH1 */
		       <&pwm2 2 PWM_HZ(500) PWM_POLARITY_NORMAL>,   /* D2/PB3 → TIM2_CH2 */
		       <&pwm3 3 PWM_HZ(500) PWM_POLARITY_NORMAL>,   /* D3/PB0 → TIM3_CH3 */
		       <&pwm1 4 PWM_HZ(500) PWM_POLARITY_NORMAL>,   /* D5/PA11 → TIM1_CH4 */
		       <&pwm3 4 PWM_HZ(500) PWM_POLARITY_NORMAL>,   /* D6/PB1 → TIM3_CH4 */
		       <&pwm8 4 PWM_HZ(500) (PWM_POLARITY_NORMAL | STM32_PWM_COMPLEMENTARY)>, /* D7/PB2 → TIM8_CH4N */
		       <&pwm3 1 PWM_HZ(500) PWM_POLARITY_NORMAL>,   /* D8/PB4 → TIM3_CH1 */
		       <&pwm4 3 PWM_HZ(500) PWM_POLARITY_NORMAL>,   /* D9/PB8 → TIM4_CH3 */
		       <&pwm4 4 PWM_HZ(500) PWM_POLARITY_NORMAL>,   /* D10/PB9 → TIM4_CH4 */
		       <&pwm1 3 PWM_HZ(500) (PWM_POLARITY_NORMAL | STM32_PWM_COMPLEMENTARY)>, /* D11/PB15 → TIM1_CH3N */
		       <&pwm1 2 PWM_HZ(500) (PWM_POLARITY_NORMAL | STM32_PWM_COMPLEMENTARY)>, /* D12/PB14 → TIM1_CH2N */
		       <&pwm1 1 PWM_HZ(500) (PWM_POLARITY_NORMAL | STM32_PWM_COMPLEMENTARY)>, /* D13/PB13 → TIM1_CH1N */
		       <&pwm2 4 PWM_HZ(500) PWM_POLARITY_NORMAL>,   /* D20/PB11 → TIM2_CH4 */
		       <&pwm2 3 PWM_HZ(500) PWM_POLARITY_NORMAL>,   /* D21/PB10 → TIM2_CH3 */
		       <&pwm5 1 PWM_HZ(500) PWM_POLARITY_INVERTED>,   /* LED3_R/PH10 → TIM5_CH1 */
		       <&pwm5 2 PWM_HZ(500) PWM_POLARITY_INVERTED>,   /* LED3_G/PH11 → TIM5_CH2 */
		       <&pwm5 3 PWM_HZ(500) PWM_POLARITY_INVERTED>;   /* LED3_B/PH12 → TIM5_CH3 */

Again look at chapter 13 of the reference manual.

Also when in doubt, experiment, I do that a lot, when it is unclear if certain things are working or not...

For example in some of my UNOQ sketches I have code, that prints out the state of the different GPIO ports, such that I can verify that things are as I expect them to be:

Top of this program is some of my dump functions as well as helper functions that allow me to update different possible pins...

void set_pin_moder(GPIO_TypeDef *port, uint8_t pin, uint8_t pin_mode) {
  // Set the MODER = could be done in fewer steps
  uint32_t moder = port->MODER;
  uint32_t mask = ~(0x3 << (pin * 2));
  moder = (moder & mask) | (pin_mode << (pin * 2));
  port->MODER = moder;
}

void set_gpio_pin_mode(GPIO_TypeDef *port, uint8_t pin, uint8_t af) {
  // Set the MODER = could be done in fewer steps
  uint32_t moder = port->MODER;
  uint32_t mask = ~(0x3 << (pin * 2));
  moder = (moder & mask) | (0x2 << (pin * 2));
  port->MODER = moder;

  if (pin < 8) {
    port->AFR[0] = port->AFR[0] & ~(0xf << (pin * 4)) | (af << (pin * 4));
  } else {
    pin -= 8;
    port->AFR[1] = port->AFR[1] & ~(0xf << (pin * 4)) | (af << (pin * 4));
  }
}

void print_gpio_regs(const char *name, GPIO_TypeDef *port) {
  //printk("GPIO%s(%p) %08X %08X %08x\n", name, port, port->MODER, port->AFR[0], port->AFR[1]);
  Serial.print("GPIO");
  Serial.print(name);
  Serial.print(" ");
  uint32_t moder = port->MODER;
  Serial.print(moder, HEX);
  Serial.print(" : ");
  for (uint8_t i = 0; i < 16; i++) {
    switch (moder & 0xC0000000) {
      case 0x00000000ul: Serial.print("I"); break;
      case 0x40000000ul: Serial.print("O"); break;
      case 0x80000000ul: Serial.print("F"); break;
      default: Serial.print("A"); break;
    }
    moder <<= 2;
  }
  Serial.print(" ");
  Serial.print(port->AFR[0], HEX);
  Serial.print(" ");
  Serial.print(port->AFR[1], HEX);
  Serial.print(" ");
  Serial.print(port->IDR, HEX);
  Serial.print(" ");
  Serial.print(port->ODR, HEX);
  Serial.print(" ");
  uint32_t pupdr = port->PUPDR;
  Serial.print(pupdr, HEX);
  Serial.print(" : ");
  for (uint8_t i = 0; i < 16; i++) {
    switch (pupdr & 0xC0000000) {
      case 0x00000000ul: Serial.print("-"); break;
      case 0x40000000ul: Serial.print("U"); break;
      case 0x80000000ul: Serial.print("D"); break;
      default: Serial.print("?"); break;
    }
    pupdr <<= 2;
  }
  Serial.println();
}

void show_all_gpio_regs() {
  print_gpio_regs("A", (GPIO_TypeDef *)GPIOA_BASE);
  print_gpio_regs("B", (GPIO_TypeDef *)GPIOB_BASE);
  print_gpio_regs("C", (GPIO_TypeDef *)GPIOC_BASE);
  print_gpio_regs("D", (GPIO_TypeDef *)GPIOD_BASE);
  print_gpio_regs("E", (GPIO_TypeDef *)GPIOE_BASE);
  print_gpio_regs("F", (GPIO_TypeDef *)GPIOF_BASE);
  print_gpio_regs("G", (GPIO_TypeDef *)GPIOG_BASE);
  print_gpio_regs("H", (GPIO_TypeDef *)GPIOH_BASE);
  print_gpio_regs("I", (GPIO_TypeDef *)GPIOI_BASE);
}

// <&pwm2 2 PWM_HZ(500) PWM_POLARITY_NORMAL>,   /* D2/PB3 → TIM2_CH2 */


void setup() {
  Serial.begin(115200);
  delay(500);

  show_all_gpio_regs();

  pinMode(2, OUTPUT);
  digitalWrite(2, LOW);
  Serial.println("After write low");
  print_gpio_regs("B", (GPIO_TypeDef *)GPIOB_BASE);

  analogWrite(2, 128);
  Serial.println("After write low");
  print_gpio_regs("B", (GPIO_TypeDef *)GPIOB_BASE);

  pinMode(2, OUTPUT);
  digitalWrite(2, HIGH);
  Serial.println("After write high");
  print_gpio_regs("B", (GPIO_TypeDef *)GPIOB_BASE);
}

void loop() {
  // put your main code here, to run repeatedly:
}

Output:

GPIOA ABFEFFDF : FFFAAAAFAAAAAOAA 0 0 C000 0 64000000 : UDU-------------
GPIOB FFFFAEBF : AAAAAAAAFFAFFAAA 77000000 0 D0 0 5100 : --------UU-U----
GPIOC FFFFFFFF : AAAAAAAAAAAAAAAA 0 0 0 0 0 : ----------------
GPIOD FFFFFFFF : AAAAAAAAAAAAAAAA 0 0 0 0 0 : ----------------
GPIOE FFFFFFFF : AAAAAAAAAAAAAAAA 0 0 0 0 0 : ----------------
GPIOF FFFFFFFF : AAAAAAAAAAAAAAAA 0 0 0 0 0 : ----------------
GPIOG F3FEABFF : AAIAAAAFFFFAAAAA 88800000 8 2180 0 8014400 : --D----UU-U-----
GPIOH FFFFFFFF : AAAAAAAAAAAAAAAA 0 0 0 0 0 : ----------------
GPIOI FFFF : IIIIIIIIAAAAAAAA 0 0 0 0 0 : ----------------
After write low
GPIOB FFFFAE7F : AAAAAAAAFFAFOAAA 77000000 0 D0 0 5100 : --------UU-U----
After write low
GPIOB FFFFAEBF : AAAAAAAAFFAFFAAA 77001000 0 D8 0 5100 : --------UU-U----
After write high
GPIOB FFFFAE7F : AAAAAAAAFFAFOAAA 77001000 0 D8 8 5100 : --------UU-U----

Now it might take you a while to decode this output, pin B3
AAAAAAAAFFAFOAAA 77000000

The first part 4th character from right: O -> Output next number is for Alternate function Low again 4th value is 0... not used as in Output mode.

AAAAAAAAFFAFFAAA 77001000
4th character is now F -> Alternate function (1)

From my Excel document:

Arduino Pin usage Port Tolerant AF0 AF1 AF2 AF3 AF4 AF5 AF6 AF7 AF8 AF9 AF10 AF11 AF12 AF13 AF14 AF15
0 D0 PB7 5v - LPTIM1_IN2 TIM4_CH2 TIM8_BKIN I2C1_SDA I2C4_SDA MDF1_CKI5 USART1_RX UART4_CTS TSC_G2_IO4 DCMI_VSYNC/
PSSI_RDY
- FMC_NL - TIM17_CH1N EVENTOUT
1 D1 PB6 5v - LPTIM1_ETR TIM4_CH1 TIM8_BKIN2 I2C1_SCL I2C4_SCL MDF1_SDI5 USART1_TX - TSC_G2_IO3 DCMI_D5/PSSI_D5 - - SAI1_FS_B TIM16_CH1N EVENTOUT
2 D2 PB3 5v JTDO/
TRACESWO
TIM2_CH2 LPTIM1_CH1 ADF1_CCK0 I2C1_SDA SPI1_SCK SPI3_SCK USART1_
RTS/USART1_
DE
- - CRS_SYNC LPGPIO1_P11 SDMMC2_D2 SAI1_SCK_B - EVENTOUT

AF1 -> TIM2_CH2... which makes sense for PWM...

Edit: And again from the PWM table I mentioned earlier you see:
<&pwm2 2 PWM_HZ(500) PWM_POLARITY_NORMAL>, /* D2/PB3 → TIM2_CH2 */

And also in the same file we have:

/* Currently only the pins marked with ~ on the pin headers are enabled */
/* PB10 and PB11 conflict with I2C configuation */
&timers2 {
	status = "okay";
	st,prescaler = <4>;

	pwm2: pwm {
		status = "okay";
		zephyr,deferred-init;
		pinctrl-0 = <>;
		pinctrl-1 = <>;
		pinctrl-2 = <&tim2_ch2_pb3 &tim2_ch3_pb10 &tim2_ch4_pb11>;
		pinctrl-names = "default", "sleep", "arduino";
	};
};

Which again shows: &tim2_ch2_pb3 where pb3 is on Timer 2 channel 2...

Hope this helps

As an honor to your post #3, I am making this write up as a reply.

1. Starting from the fact that the header pins of UNO R3 are compatible with that of UNO Q.

2. In UNO R3, the physical pin -15 (PPin-15) of the underlying MCU assumes digital signal name PB1 at reset and it is connected with header pin - 9 (DPin-9).

3. The above PPin-15 of Step-2 has an alternate function named OC1A which is a PWM signal coming from the internal TC1 driven PWM Module.

4. The PPin-15/OC1A-pin starts delivering PWM signal of 490 Hz (for example) when the following code are executed in setup() function. The PWM appears as free running signal on DPin-9 without any intervention from MCU.

#define OC1A 9

void setup()
{
    pinMode(OC1A, OUTPUT);  //DPin-9
    //-- register level codes to genertae 1 Hz PWM signal using TC1 Mode-14
    TCCR1A = 0x00;
    TCCR1B = 0x00;
    TCCR1A |= bit(WGM11);
    TCCR1B |= bit(WGM13) | bit(WGM12);  //Mode-14 FPWM
    ICR1 = 32652;    //TOP value
    OCR1A = 16326; //innital duty cycle
    bitSet(TCCR1A, COM1A1);
    bitClear(TCCR1A, COM1A0);  //Non-invert: HIGH-LOW
    TCCR1B |= bit(CS10);  //Start TC1 PWM with division facto1
}

void loop(){}

5. The PWM signal of Step-4 can also be genertaed by executing the following sketch; where, the library function analogWrite(9, 128); is called only once in the setup() function.

void setup()
{
    analogWrite(9, 128);
}

void loop(){}

6. Generation of PWM signal of Step-5 is not affected if pinMode(9, OUTPT) is inserted after calling or before calling the analogWrite() function.

7. I understand that similar theory should exist in the case of UNO Q and its MCU.

8. Practically in the case of UNO Q, the insertion of pinMode() before analogWrite(9, 128) function deos affect the generation of PWM signal. The reasons and the required corrections are being currently investigated by Arduino Dev Team.

9. I have gone through your code of post #3, and I must confess that I am unable to understand due to lack of my pre-background knowledge. I was trying to see which code are related with MCUs timers and DPin- 5, 6, 9, 10, 3, 11 of header.

10. In the meantime, for academic point of view, I am making an exploration for generating the 490 Hz PWM signal at DPin-9 using register level codes of the MCU of the UNO Q.