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