Skip to content

esp32/machine_pwm.c: Handle multiple timers: Bugfix PWM.deinit() critical error and fix pwm_print()#6654

Closed
IhorNehrutsa wants to merge 3 commits into
micropython:masterfrom
IhorNehrutsa:bugfix/esp32_pwm_print
Closed

esp32/machine_pwm.c: Handle multiple timers: Bugfix PWM.deinit() critical error and fix pwm_print()#6654
IhorNehrutsa wants to merge 3 commits into
micropython:masterfrom
IhorNehrutsa:bugfix/esp32_pwm_print

Conversation

@IhorNehrutsa
Copy link
Copy Markdown
Contributor

@IhorNehrutsa IhorNehrutsa commented Nov 27, 2020

This PR is inherited from esp32/machine_pwm: handle multiple timers. #6276 and replace it.
This PR uses PR ESP32: New feature: EspError exception #6638
This PR was tested together with PR ESP32: New features PCNT() and QUAD() #6639
It is tested with 4 different frequencies at 8 different channels with different duties.
Frequencies and duties are right on proper pins.

from machine import Pin, PWM
import pcnt

P1 = 15
P2 = 2
P3 = 0
P4 = 4
P5 = 18
P6 = 21
P7 = 22
P8 = 23

C1 = 14
C2 = 27
C3 = 26
C4 = 25
C5 = 33
C6 = 32
C7 = 35
C8 = 34

DIR1 = 5

f = 1 # 1..4
d = int(1024 / 9)

cnt1 = None
cnt2 = None
cnt3 = None
cnt4 = None
cnt5 = None
cnt6 = None
cnt7 = None
cnt8 = None

pwm1 = None
pwm2 = None
pwm3 = None
pwm4 = None
pwm5 = None
pwm6 = None
pwm7 = None
pwm8 = None

def prnt():
    global cnt1, cnt2, cnt3, cnt4, cnt5, cnt6, cnt7, cnt8
    global pwm1, pwm2, pwm3, pwm4, pwm5, pwm6, pwm7, pwm8
    cnt1.filter_disable()
    cnt2.filter_disable()
    cnt3.filter_disable()
    cnt4.filter_disable()
    cnt5.filter_disable()
    cnt6.filter_disable()
    cnt7.filter_disable()
    cnt8.filter_disable()

    print(pwm1)
    print(pwm2)
    print(pwm3)
    print(pwm4)
    print(pwm5)
    print(pwm6)
    print(pwm7)
    print(pwm8)

    print(cnt1)
    print(cnt2)
    print(cnt3)
    print(cnt4)
    print(cnt5)
    print(cnt6)
    print(cnt7)
    print(cnt8)

def init1():
    global cnt1, cnt2, cnt3, cnt4, cnt5, cnt6, cnt7, cnt8
    global pwm1, pwm2, pwm3, pwm4, pwm5, pwm6, pwm7, pwm8
    pwm1 = PWM(Pin(P1), freq=f*1, duty=d*1)  # 1024-1 == 100% # 512-1 == 50%
    pwm2 = PWM(Pin(P2), freq=f*1, duty=d*2)  
    pwm3 = PWM(Pin(P3), freq=f*100, duty=d*3)  
    pwm4 = PWM(Pin(P4), freq=f*100, duty=d*4-10)  
    pwm5 = PWM(Pin(P5), freq=f*100000, duty=d*5)  
    pwm6 = PWM(Pin(P6), freq=f*100000, duty=d*6)  
    pwm7 = PWM(Pin(P7), freq=f*10000000, duty=1000)#512)
    pwm8 = PWM(Pin(P8), freq=f*10000000, duty=256)#512)

    cnt1 = pcnt.PCNT(pcnt.Edge.FALL, C1, DIR1, pcnt.PinPull.UP)
    cnt2 = pcnt.PCNT(pcnt.Edge.FALL, C2, DIR1, pcnt.PinPull.UP)
    cnt3 = pcnt.PCNT(pcnt.Edge.FALL, C3, DIR1, pcnt.PinPull.UP)
    cnt4 = pcnt.PCNT(pcnt.Edge.FALL, C4, DIR1, pcnt.PinPull.UP)
    cnt5 = pcnt.PCNT(pcnt.Edge.FALL, C5, DIR1, pcnt.PinPull.UP)
    cnt6 = pcnt.PCNT(pcnt.Edge.FALL, C6, DIR1, pcnt.PinPull.UP)
    cnt7 = pcnt.PCNT(pcnt.Edge.FALL, C7, DIR1, pcnt.PinPull.UP)
    cnt8 = pcnt.PCNT(pcnt.Edge.FALL, C8, DIR1, pcnt.PinPull.UP)
    prnt()

def init2():
    global cnt1, cnt2, cnt3, cnt4, cnt5, cnt6, cnt7, cnt8
    global pwm1, pwm2, pwm3, pwm4, pwm5, pwm6, pwm7, pwm8
    pwm1 = PWM(Pin(P1), freq=f*10000000, duty=512)  # 1024 == 100% # 512 == 50%
    pwm2 = PWM(Pin(P2), freq=f*10000000, duty=512)  
    pwm3 = PWM(Pin(P3), freq=f*10000, duty=d*6)  
    pwm4 = PWM(Pin(P4), freq=f*10000, duty=d*5)  
    pwm5 = PWM(Pin(P5), freq=f*100, duty=d*4)  
    pwm6 = PWM(Pin(P6), freq=f*100, duty=d*3)  
    pwm7 = PWM(Pin(P7), freq=f*1, duty=d*2)
    pwm8 = PWM(Pin(P8), freq=f*1, duty=d*1)

    cnt1 = pcnt.PCNT(pcnt.Edge.BOTH, C1, DIR1, pcnt.PinPull.NONE)
    cnt2 = pcnt.PCNT(pcnt.Edge.BOTH, C2, DIR1, pcnt.PinPull.NONE)
    cnt3 = pcnt.PCNT(pcnt.Edge.BOTH, C3, DIR1, pcnt.PinPull.NONE)
    cnt4 = pcnt.PCNT(pcnt.Edge.BOTH, C4, DIR1, pcnt.PinPull.NONE)
    cnt5 = pcnt.PCNT(pcnt.Edge.BOTH, C5, DIR1, pcnt.PinPull.NONE)
    cnt6 = pcnt.PCNT(pcnt.Edge.BOTH, C6, DIR1, pcnt.PinPull.NONE)
    cnt7 = pcnt.PCNT(pcnt.Edge.BOTH, C7, DIR1, pcnt.PinPull.NONE)
    cnt8 = pcnt.PCNT(pcnt.Edge.BOTH, C8, DIR1, pcnt.PinPull.NONE)
    prnt()

def deinit_all():
    global cnt1, cnt2, cnt3, cnt4, cnt5, cnt6, cnt7, cnt8
    global pwm1, pwm2, pwm3, pwm4, pwm5, pwm6, pwm7, pwm8
    print('deinit_all():')
    try:
        pwm1.deinit()
    except:
        pass
    try:
        pwm2.deinit()
    except:
        pass
    try:
        pwm3.deinit()
    except:
        pass
    try:
        pwm4.deinit()
    except:
        pass
    try:
        pwm5.deinit()
    except:
        pass
    try:
        pwm6.deinit()
    except:
        pass
    try:
        pwm7.deinit()
    except:
        pass
    try:
        pwm8.deinit()
    except:
        pass
    try:
        cnt1.__del__()
    except:
        pass
    try:
        cnt2.__del__()
    except:
        pass
    try:
        cnt3.__del__()
    except:
        pass
    try:
        cnt4.__del__()
    except:
        pass
    try:
        cnt5.__del__()
    except:
        pass
    try:
        cnt6.__del__()
    except:
        pass
    try:
        cnt7.__del__()
    except:
        pass
    try:
        cnt8.__del__()
    except:
        pass
    

try:
    _c = None
    
    init1()
    while True:
        c = cnt1.count(), cnt2.count(), cnt3.count(), cnt4.count(), cnt5.count(), cnt6.count(), cnt7.count(), cnt8.count()
        if _c != c[1]:
            _c = c[1]
            #print(c, end='        \r')
            print(c)
        if _c == 10:
            break
            pass
    deinit_all()

    print('')
    init2()
    while True:
        c = cnt1.count(), cnt2.count(), cnt3.count(), cnt4.count(), cnt5.count(), cnt6.count(), cnt7.count(), cnt8.count()
        if _c != c[7]:
            _c = c[7]
            #print(c, end='        \r')
            print(c)
        if _c == 10:
            break
    deinit_all()
finally:
    print('finally:')
    deinit_all()

Output is

>>> %Run -c $EDITOR_CONTENT
PWM(15, freq=1, duty=4(4/1023))
PWM(2, freq=1, duty=4(4/1023))
PWM(0, freq=100, duty=512(512/1023))
PWM(4, freq=100, duty=565(565/1023))
PWM(18, freq=100000, duty=564(282/511))
PWM(21, freq=100000, duty=678(339/511))
PWM(22, freq=10000000, duty=896(7/7))
PWM(23, freq=10000000, duty=256(2/7))
PCNT(unit=0, Pin(14), Pin(5), pin_pull_type=2)
PCNT(unit=1, Pin(27), Pin(5), pin_pull_type=2)
PCNT(unit=2, Pin(26), Pin(5), pin_pull_type=2)
PCNT(unit=3, Pin(25), Pin(5), pin_pull_type=2)
PCNT(unit=4, Pin(33), Pin(5), pin_pull_type=2)
PCNT(unit=5, Pin(32), Pin(5), pin_pull_type=2)
PCNT(unit=6, Pin(35), Pin(5), pin_pull_type=2)
PCNT(unit=7, Pin(34), Pin(5), pin_pull_type=2)
(1, 1, 5, 4, 4846, 4846, 484659, 484723)
(2, 2, 23, 22, 22613, 22613, 2261315, 2261358)
(3, 3, 123, 122, 122612, 122612, 12261240, 12261283)
(4, 4, 223, 222, 222616, 222616, 22261628, 22261670)
(5, 5, 323, 322, 322604, 322604, 32260413, 32260456)
(6, 6, 423, 422, 422610, 422610, 42261029, 42261072)
(7, 7, 523, 522, 522616, 522616, 52261652, 52261694)
(8, 8, 623, 622, 622614, 622614, 62261425, 62261467)
(9, 9, 723, 722, 722609, 722609, 72260934, 72260976)
(10, 10, 823, 822, 822606, 822606, 82260679, 82260722)
deinit_all():

PWM(15, freq=10000000, duty=512(4/7))
PWM(2, freq=10000000, duty=512(4/7))
PWM(0, freq=10000, duty=678(678/1023))
PWM(4, freq=10000, duty=565(565/1023))
PWM(18, freq=100, duty=452(452/1023))
PWM(21, freq=100, duty=339(339/1023))
PWM(22, freq=1, duty=226(226/1023))
PWM(23, freq=1, duty=113(113/1023))
PCNT(unit=0, Pin(14), Pin(5), pin_pull_type=0)
PCNT(unit=1, Pin(27), Pin(5), pin_pull_type=0)
PCNT(unit=2, Pin(26), Pin(5), pin_pull_type=0)
PCNT(unit=3, Pin(25), Pin(5), pin_pull_type=0)
PCNT(unit=4, Pin(33), Pin(5), pin_pull_type=0)
PCNT(unit=5, Pin(32), Pin(5), pin_pull_type=0)
PCNT(unit=6, Pin(35), Pin(5), pin_pull_type=0)
PCNT(unit=7, Pin(34), Pin(5), pin_pull_type=0)
(1029082, 1030001, 1041, 1039, 11, 10, 2, 2)
(2339568, 2339659, 2351, 2349, 24, 24, 2, 3)
(20134572, 20134666, 20145, 20144, 202, 201, 4, 4)
(22341441, 22341537, 22352, 22351, 224, 224, 4, 5)
(40133566, 40133661, 40145, 40143, 402, 401, 6, 6)
(42338735, 42338831, 42349, 42348, 424, 424, 6, 7)
(60147638, 60147733, 60159, 60157, 602, 602, 8, 8)
(62339969, 62340064, 62351, 62349, 624, 624, 8, 9)
(80133437, 80133530, 80144, 80143, 802, 801, 10, 10)
deinit_all():
finally:
deinit_all():
>>> 

@IhorNehrutsa
Copy link
Copy Markdown
Contributor Author

Several questions on the forum
https://forum.micropython.org/viewtopic.php?f=3&t=9135&p=52581#p52581

Copy link
Copy Markdown
Contributor

@tve tve left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been using this PR for a few days. In general "it works" :-)
There is also #3608, which is a slightly different, more explicit approach.

A big issue I'm having is that this PR doesn't work with soft-reset. After a soft-reset the PWM unit continues running and when the python code creates a fresh PWM object of the same pin as an existing PWM unit then the outcome is somewhat undefined. At least, during interactive testing I was running into situations where a pin was stuck on an old PWM and despite creating a new one for it in python nothing would change. At the very least, there needs to be an init method that is called from main.c where the soft-reset comes in that stops all PWM activity.

Comment thread ports/esp32/machine_pwm.c
return 1;
}

STATIC int found_timer(int freq, bool same_freq_only) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be called find_timer

Comment thread ports/esp32/machine_pwm.c
int free_timer_found = -1;
int timer;
// Find a free PWM Timer using the same freq
for (timer = 0; timer < LEDC_TIMER_MAX; ++timer) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change to for (int timer = 0;

Comment thread ports/esp32/machine_pwm.c
// return true if the timer is in use in addition to curr_channel
STATIC bool is_timer_in_use(int curr_channel, int timer) {
int i;
for (i = 0; i < LEDC_CHANNEL_MAX; ++i) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change to for (int i = 0;

Comment thread ports/esp32/machine_pwm.c
STATIC void set_duty(esp32_pwm_obj_t *self, uint32_t duty) {
duty &= ((1 << PWRES) - 1);
duty >>= PWRES - timers[chan_timer[self->channel]].duty_resolution;
ESP_EXCEPTIONS(ledc_set_duty(PWMODE, self->channel, duty));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should use check_esp_err, found in mphalport.c

Comment thread ports/esp32/machine_pwm.c
freq = chan_timer[self->channel] != -1 ? timers[chan_timer[self->channel]].freq_hz : PWFREQ;
}

timer = found_timer(freq, false);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change to int timer =

Comment thread ports/esp32/machine_pwm.c
}
}

if (new_timer != -1 && new_timer != current_timer) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add comment: // If another timer already has the right freq then just switch over to it
How could new_timer == current_timer here? I believe this whole if statement would be clearer if placed right after the call to found_timer.

Comment thread ports/esp32/machine_pwm.c

current_timer = new_timer;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cases here are very confusing and difficult to follow. It would be nice to find a better way to structure this.

Comment thread ports/esp32/machine_pwm.c
chan_timer[self->channel] = new_timer;

if (ledc_bind_channel_timer(PWMODE, self->channel, new_timer) != ESP_OK) {
mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("failed to bind timer to channel"));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you use check_esp_err?

Comment thread ports/esp32/machine_pwm.c

// Set the freq
if (!set_freq(tval, &timers[current_timer])) {
mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("bad frequency %d"), tval);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use check_esp_err?

Comment thread ports/esp32/machine_pwm.c

// Flag it unused
timers[current_timer].freq_hz = -1;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this can return here.

@IhorNehrutsa
Copy link
Copy Markdown
Contributor Author

FIXED in esp32/machine_pwm: handle multiple timers. #6276

@IhorNehrutsa IhorNehrutsa deleted the bugfix/esp32_pwm_print branch September 21, 2021 18:31
tannewt added a commit to tannewt/circuitpython that referenced this pull request Aug 10, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants