Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed

Conversation

IhorNehrutsa
Copy link
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
Contributor Author

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

Copy link
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.

return 0;
}
return 1;
}

STATIC int found_timer(int freq, bool same_freq_only) {
Copy link
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

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
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;

// 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
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;

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
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

freq = chan_timer[self->channel] != -1 ? timers[chan_timer[self->channel]].freq_hz : PWFREQ;
}

timer = found_timer(freq, false);
Copy link
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 =

}
}

if (new_timer != -1 && new_timer != current_timer) {
Copy link
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.


current_timer = new_timer;
}

Copy link
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.

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
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?

}

// 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
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?


// Flag it unused
timers[current_timer].freq_hz = -1;
}
Copy link
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
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