圧電スピーカーで音を鳴らしたり、RCサーボを動かそうと思うと、PWMライブラリが欲しいですよね。STM32のMicroPythonには、他のMCU用の実装とは異なり、PWMライブラリがなく、ちょっと困ってしまいます。

STM32は、各端子によって対応付けられるタイマーやPWMでデューティー比を制御するチャネルの組み合わせが複雑で、PWMの汎用的なライブラリを作成しようとすると少し気合がいりますが、ないととても不便なので作成しました。


PWMライブラリ

"""
STM32PWM: PWM driver for STM32
for MicroPython
Version 1.1

Copyright 2023 K.Kakizaki
"""

from machine import Pin
from pyb import Timer
import sys

class PWM:
    tim446 = {'PA0':2, 'PA1':2, 'PA2':9, 'PA3':9, 'PA4':None, 'PA5':2, 'PA6':3, 'PA7':3, 'PA8':1, 'PA9':1, 'PA10':1, 'PA11':1, 'PA12':None, 'PA13':None, 'PA14':None, 'PA15':2,
            'PB0':3, 'PB1':3, 'PB2':2, 'PB3':2, 'PB4':3, 'PB5':3, 'PB6':4, 'PB7':4, 'PB8':10, 'PB9':11, 'PB10':2, 'PB11':2, 'PB12':None, 'PB13':1, 'PB14':1, 'PB15':1,
            'PC0':None, 'PC1':None, 'PC2':None, 'PC3':None, 'PC4':None, 'PC5':None, 'PC6':8, 'PC7':8, 'PC8':8, 'PC9':8}
    ch446 = {'PA0':1, 'PA1':2, 'PA2':1, 'PA3':2, 'PA4':None, 'PA5': 1, 'PA6':1, 'PA7':2, 'PA8':1, 'PA9':2, 'PA10':3, 'PA11':4, 'PA12':None, 'PA13':None, 'PA14':None, 'PA15':1,
            'PB0':3, 'PB1':4, 'PB2':4, 'PB3':2, 'PB4':1, 'PB5':2, 'PB6':1, 'PB7':2, 'PB8':1, 'PB9':1, 'PB10':3, 'PB11':4, 'PB12':None, 'PB13':-1, 'PB14':-2, 'PB15':-3,
            'PC0':None, 'PC1':None, 'PC2':None, 'PC3':None, 'PC4':None, 'PC5':None, 'PC6':1, 'PC7':2, 'PC8':3, 'PC9':4}

#    tim412 = {'PA0':2, 'PA1':2, 'PA2':9, 'PA3':9, 'PA4':None, 'PA5':2, 'PA6':3, 'PA7':3, 'PA8':1, 'PA9':1, 'PA10':1, 'PA11':1, 'PA12':None, 'PA13':None, 'PA14':None, 'PA15':2,
#            'PB0':3, 'PB1':3, 'PB2':None, 'PB3':2, 'PB4':3, 'PB5':3, 'PB6':4, 'PB7':4, 'PB8':10, 'PB9':11, 'PB10':2, 'PB11':2, 'PB12':None, 'PB13':1, 'PB14':1, 'PB15':1,
#            'PC0':None, 'PC1':None, 'PC2':None, 'PC3':None, 'PC4':None, 'PC5':None, 'PC6':8, 'PC7':8, 'PC8':8, 'PC9':8}
#    ch412 = {'PA0':1, 'PA1':2, 'PA2':1, 'PA3':2, 'PA4':None, 'PA5': 1, 'PA6':1, 'PA7':2, 'PA8':1, 'PA9':2, 'PA10':3, 'PA11':4, 'PA12':None, 'PA13':None, 'PA14':None, 'PA15':1,
#            'PB0':3, 'PB1':4, 'PB2':None, 'PB3':2, 'PB4':1, 'PB5':2, 'PB6':1, 'PB7':2, 'PB8':1, 'PB9':1, 'PB10':3, 'PB11':4, 'PB12':None, 'PB13':-1, 'PB14':-2, 'PB15':-3,
#            'PC0':None, 'PC1':None, 'PC2':None, 'PC3':None, 'PC4':None, 'PC5':None, 'PC6':1, 'PC7':2, 'PC8':3, 'PC9':4}

    if sys.implementation[2].find('UNO') > 0:
        fpin = {'LED1':'PB13', 'LED_BLUE':'PB13', 'D13':'PB13', 'SND':'PC8', 'SRV1':'PA0', 'SRV2':'PA1', 'SRV3':'PA2', 'SRV4':'PA3'}
    else:
        fpin = {'LED1':'PA10', 'LED_BLUE':'PA10', 'SND':'PB9', 'SRV1':'PA0', 'SRV2':'PA1', 'SRV3':'PA2', 'SRV4':'PA3'}

    def __init__(self, pin, freq=1000, duty_percent=50):
        if pin in PWM.fpin:
            pin = PWM.fpin[pin]
        self._pin = pin
        self._freq = freq
        self._duty_percent = duty_percent
        self._tim = Timer(PWM.tim446[self._pin],freq=freq)
        self._mode = Timer.PWM
        if PWM.ch446[self._pin] < 0:
              self._mode = Timer.PWM_INVERTED
        self._ch = self._tim.channel(abs(PWM.ch446[self._pin]), self._mode, pin=Pin(self._pin))
        self._ch.pulse_width_percent(duty_percent)

    def freq(self, hz=-1):
        if hz == -1:
            return self._freq
        else:
            self._freq = hz
            self._tim = Timer(PWM.tim446[self._pin],freq=hz)

    def duty_percent(self, v=-1):
        if v == -1:
            return self._ch.pulse_width_percent()
        else:
            self._ch.pulse_width_percent(v)

    def duty_u16(self, v=-1):
        if v == -1:
            return int(self._ch.pulse_width_percent() / 100 * 65535)
        else:
            self._ch.pulse_width_percent(v / 65535 * 100)

    def pulse_width(self, v=-1):
        if v == -1:
            return self._ch.pulse_width()
        else:
            self._ch.pulse_width(v)

    def period(self):
        return self._tim.period()

    def stop(self): # Stop PWM of the channel
        self._ch.pulse_width_percent(0)

    def deinit(self): # Deinitialises the timer
        self._tim.deinit()