dead bug dc motor driver

Showroom for MicroPython related hardware projects.
Target audience: Users wanting to show off their project!
Post Reply
PinkInk
Posts: 65
Joined: Tue Mar 11, 2014 3:42 pm

dead bug dc motor driver

Post by PinkInk » Fri Jul 11, 2014 6:30 pm

This mornings work, dumb h-bridge dc motor driver skin and code module;

Image

This afternoons work; inline assembler tutorial and lots of googe'ling ... but no PWM speed control, yet ...

User avatar
JonHylands
Posts: 69
Joined: Sun Dec 29, 2013 1:33 am

Re: dead bug dc motor driver

Post by JonHylands » Mon Jul 14, 2014 12:27 pm

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
Attachments
pwm.zip
pwm.c and pwm.h (doesn't work with current micropython)
(1.52 KiB) Downloaded 315 times

PinkInk
Posts: 65
Joined: Tue Mar 11, 2014 3:42 pm

Re: dead bug dc motor driver

Post by PinkInk » Mon Jul 14, 2014 2:49 pm

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.

PinkInk
Posts: 65
Joined: Tue Mar 11, 2014 3:42 pm

Re: dead bug dc motor driver

Post by PinkInk » Sun Jul 27, 2014 4:12 pm

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.

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

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)

Post Reply