This mornings work, dumb h-bridge dc motor driver skin and code module;
This afternoons work; inline assembler tutorial and lots of googe'ling ... but no PWM speed control, yet ...
dead bug dc motor driver
- JonHylands
- Posts: 69
- Joined: Sun Dec 29, 2013 1:33 am
Re: dead bug dc motor driver
I'm attaching the pwm module I wrote back in February. This was before the move to stmhal, so it almost certainly won't work now, but it does show how to set the timer modules to allow for 10 kHz dual-motor-control PWM on a couple pins (PA5 and PB14 in this case).
- Jon
- Jon
- Attachments
-
- pwm.zip
- pwm.c and pwm.h (doesn't work with current micropython)
- (1.52 KiB) Downloaded 320 times
Re: dead bug dc motor driver
Jon, many thanks - I'm trying desperately to avoid C, for various (personal) reasons, but I've had a look through your sample code and it verifies what I've learnt (from Google) needs to be done to setup and control PWM (and substantiated by peering into and manipulating LED 4's associated GPIO and TIMER memory, using uPy's inline assembler).
I've come to the conclusion that it *should* be relatively trivial to create a mainly uPy class, with a minimal subset of methods in assembler (note to self: haven't yet tested that inline assembler functions as methods of classes) to create a functional PWM interface ... however in an attempt to keep the inline assembler bits simple and generic, rather than convoluted and complex (because I only started learning assembler 3 days ago, find branch-logic *fugly* and I don't like the idea of hard-coding *lots* of memory addresses into routines when sanity says I should pass them in) I am running into (and getting distracted by) what I *think* are bugs in the inline assembler, which I'm about to write-up elsewhere in this forum.
I've come to the conclusion that it *should* be relatively trivial to create a mainly uPy class, with a minimal subset of methods in assembler (note to self: haven't yet tested that inline assembler functions as methods of classes) to create a functional PWM interface ... however in an attempt to keep the inline assembler bits simple and generic, rather than convoluted and complex (because I only started learning assembler 3 days ago, find branch-logic *fugly* and I don't like the idea of hard-coding *lots* of memory addresses into routines when sanity says I should pass them in) I am running into (and getting distracted by) what I *think* are bugs in the inline assembler, which I'm about to write-up elsewhere in this forum.
Re: dead bug dc motor driver
Finally managed to botch together hardware pwm in uPy ... and can now drive my motor.
Until pwm is implemented properly this might be useful to others.
Known issues (it'll let you know);
- can't pwm pins that use reserved timer 3, because I can't work out how to directly 'start' a timer so am using pyb.Timer() to do so
- some pins can't be used simultaneously with default alternate function, may need to select alternate alternate function ;o)
pwm.py
Until pwm is implemented properly this might be useful to others.
Code: Select all
import pwm
pY3 = pwm.PWM(pyb.Pin.board.Y3) #pass in desired pin, only Y3,Y4,Y7,Y8,Y9,Y10,X1,X2,X3,X4,X9,X10 known to work
pY3.duty(125) #range is 0 (off - 0v) to 255 (on - 3.3v)
pY3.duty()
125 #current set duty is returned
- can't pwm pins that use reserved timer 3, because I can't work out how to directly 'start' a timer so am using pyb.Timer() to do so
- some pins can't be used simultaneously with default alternate function, may need to select alternate alternate function ;o)
pwm.py
Code: Select all
import pyb, stm
#as pwm is configured 0-3.3v is 0-10000
# used to truncate to 0-255;
# change if you need finer resolution
RESOLUTION = 255
#for each pin name
# list of possible pwm available alt fn's
# and corresponding timer/channel
__PIN_TIMERS = { \
'A0': { 0b0010: ( 5,1) }, \
'A1': { 0b0001: ( 2,2), 0b0010: ( 5,2) }, \
'A2': { 0b0001: ( 2,3), 0b0010: ( 5,3), 0b0011: ( 9,1) }, \
'A3': { 0b0001: ( 2,4), 0b0010: ( 5,4), 0b0011: ( 9,2) }, \
'A6': { 0b0010: ( 3,1), 0b1001: (13,1) }, \
'A7': { 0b0010: ( 3,2), 0b1001: (14,1) }, \
'A8': { 0b0001: ( 1,1) }, \
'A9': { 0b0001: ( 1,2) }, \
'A10': { 0b0001: ( 1,3) }, \
'A11': { 0b0001: ( 1,4) }, \
'B0': { 0b0010: ( 3,3) }, \
'B1': { 0b0010: ( 3,4) }, \
'B3': { 0b0001: ( 2,2) }, \
'B4': { 0b0010: ( 3,1) }, \
'B5': { 0b0010: ( 3,2) }, \
'B6': { 0b0010: ( 4,1) }, \
'B7': { 0b0010: ( 4,2) }, \
'B8': { 0b0010: ( 4,3), 0b0011: (10,1) }, \
'B9': { 0b0010: ( 4,4), 0b0011: (11,1) }, \
'B10': { 0b0001: ( 2,3) }, \
'B11': { 0b0001: ( 2,4) }, \
'B14': { 0b1001: (12,1) }, \
'B15': { 0b1001: (12,2) }, \
'C6': { 0b0010: ( 3,1), 0b0011: ( 8,1) }, \
'C7': { 0b0010: ( 3,2), 0b0011: ( 8,2) }, \
'C8': { 0b0010: ( 3,3), 0b0011: ( 8,3) }, \
'C9': { 0b0010: ( 3,4), 0b0011: ( 8,4) }, \
'D12': { 0b0010: ( 4,1) }, \
'D13': { 0b0010: ( 4,2) }, \
'D14': { 0b0010: ( 4,3) }, \
'D15': { 0b0010: ( 4,4) }, \
'E5': { 0b0011: ( 9,1) }, \
'E6': { 0b0011: ( 9,2) }, \
'E9': { 0b0001: ( 1,1) }, \
'E11': { 0b0001: ( 1,2) }, \
'E13': { 0b0001: ( 1,3) }, \
'E14': { 0b0001: ( 1,4) }, \
'F6': { 0b0011: (10,1) }, \
'F7': { 0b0011: (11,1) }, \
'F8': { 0b1001: (13,1) }, \
'F9': { 0b1001: (14,1) }, \
'H9': { 0b1001: (12,2) }, \
'H10': { 0b0010: ( 5,1) }, \
'H11': { 0b0010: ( 5,2) }, \
'H12': { 0b0010: ( 5,3) }, \
'H6': { 0b1001: (12,1) }, \
'I0': { 0b0010: ( 5,4) }, \
'I2': { 0b0011: ( 8,4) } \
}
class PWM:
"""dirty Pulse Width Modulation class"""
def __init__(self, pin, afmode=None):
if not pin.name() in __PIN_TIMERS:
raise ValueError('Pin does not support pwm')
if afmode == None:
#select the first timer in the dictionary
pwm_afmode = list(__PIN_TIMERS[pin.name()].keys())[0]
else:
pwm_afmode = afmode
if not pwm_afmode in __PIN_TIMERS[pin.name()]:
raise ValueError('Pin does not support afmode {0}'.format(bin(afmode)))
self.pin = pin
self.gpio = eval('stm.GPIO{0}'.format(pin.name()[0]))
pwm_pin = int(self.pin.name()[1:])
pwm_timer_no = __PIN_TIMERS[self.pin.name()][pwm_afmode][0]
pwm_timer_ch = __PIN_TIMERS[self.pin.name()][pwm_afmode][1]
#setup gpio
#set alternate fn mode, before setting pin to alternate function
if pwm_pin < 7:
stm.mem32[self.gpio + stm.GPIO_AFR0] |= pwm_afmode << (pwm_pin * 4)
elif pwm_pin == 7:
#avoid potential overflow on 32nd bit being set
stm.mem8[self.gpio + stm.GPIO_AFR0 + 3] |= pwm_afmode << 4
elif pwm_pin < 15:
stm.mem32[self.gpio + stm.GPIO_AFR1] |= pwm_afmode << ((pwm_pin - 8) * 4)
elif pwm_pin == 15:
#avoid potential overflow on 32nd bit being set
stm.mem8[self.gpio + stm.GPIO_AFR1 + 3] |= pwm_afmode << 4
if pwm_pin < 15:
stm.mem32[self.gpio + stm.GPIO_MODER] |= 0b10 << (pwm_pin * 2)
stm.mem32[self.gpio + stm.GPIO_OSPEEDR] |= 0b10 << (pwm_pin * 2)
elif pwm_pin == 15:
stm.mem8[self.gpio + stm.GPIO_MODER + 3] |= 0b10 << 6
stm.mem8[self.gpio + stm.GPIO_OSPEEDR + 3] |= 0b10 << 6
#setup timer
#TODO: work out how to init timer directly, so can use reserved ones TIM3
pyb.Timer(pwm_timer_no).init(freq=5) #use pyb module to kickstart the timer
pwm_timer = eval('stm.TIM{0}'.format(pwm_timer_no))
self.__pwm_duty = pwm_timer + eval('stm.TIM_CCR{0}'.format(pwm_timer_ch))
stm.mem32[self.__pwm_duty] = 0 #make sure duty is 0
stm.mem32[pwm_timer + stm.TIM_ARR] = 9999 #replicate damiens's prescalar, period
stm.mem32[pwm_timer + stm.TIM_PSC] = 83 #gives 10,000 steps between 0 and 3.3v
stm.mem32[pwm_timer + stm.TIM_DIER] |= 0b1
stm.mem32[pwm_timer + stm.TIM_SR] |= 0b11110 #sets all channel interupt flags
if pwm_timer_ch <= 2:
stm.mem32[pwm_timer + stm.TIM_CCMR1] |= 0b01101000 << ((pwm_timer_ch - 1) * 8)
else:
stm.mem32[pwm_timer + stm.TIM_CCMR2] |= 0b01101000 << ((pwm_timer_ch - 3) * 8)
stm.mem32[pwm_timer + stm.TIM_CCER] |= 0b1 << ((pwm_timer_ch -1) * 4)
stm.mem32[pwm_timer + stm.TIM_DMAR] |= 0b1
def duty(self, value=None):
if value == None:
return int( stm.mem32[self.__pwm_duty] / 10000 * RESOLUTION )
else:
if value > RESOLUTION: value = RESOLUTION
if value < 0: value = 0
stm.mem32[self.__pwm_duty] = int(value / RESOLUTION * 10000)