Skip to content
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

Refactor motor drivers #945

Closed
Ezward opened this issue Oct 11, 2021 · 9 comments
Closed

Refactor motor drivers #945

Ezward opened this issue Oct 11, 2021 · 9 comments

Comments

@Ezward
Copy link
Contributor

Ezward commented Oct 11, 2021

Our motor drivers are pretty tightly bound to the libraries used to control PWM and GPIO pins. This makes it hard to re-use code when building a new driver.

I'm in the process of enabling donkeycar software on a Duckiebot robot homebrew clone here. It uses an TB6612FNG motor controller which is pin compatible with an L298N. However, due to the evolution of the Duckiebot platform, one of it's motors get's it's enable signals from GPIO and the other from a PCA9685. The other motor get's it's enable signals from GPIO and PWM from PCA9685.

This would all be actually pretty easy to handle if we had our motor drivers accept a more generic interface for setting GPIO and PWM signals. We could implement versions for the RPi.GPIO library, PiGPIO library and the PCA9685. We could even write a version that controls this via an arduino using I2c or serial.

  • Create abstract base classes for LogicPin and PwmPin.
  • Create concrete implementations for RPi.GPIO (and so Jetson.GPIO), PCA9685 and PiGPIO
  • Refactor motor drivers to take instances of LogicPin and PwmPin rather than raw pin numbers.
  • update templates to instantiate instances of LogicPin and PwmPin when constructing drivetrain parts.

I would also refactor the templates by extractng the drivetrain construction from complete.py and making this a function that can be called by any template. Then we could give other templates the flexibility to choose drivetrains, even if we only add a default in their config.

Add more documentation on how to wire the motor drivers. Each of our choices for motor implies a certain kind of hardware and wiring. It would be useful to add a little more documentation and references on this. Links to product descriptions on Amazon and Aliexpress tend to go stale very quickly, so we should focus on finding datasheets and tutorials that are likely to stay live longer.

This article does a very detailed job of describing the possible connections to an L298N https://www.etechnophiles.com/l298n-motor-driver-pin-diagram/ However, it does not explicitly describe how only the duty cycle inputs are used and the enable pins are not used. The links to the product pages are 404ing. I'm assuming this driver is for a 'mini' L298N. That board uses just 4 wired to hookup 2 motors as this driver assumes. Here is an article on how t use this with an arduino; https://www.instructables.com/Tutorial-for-Dual-Channel-DC-Motor-Driver-Board-PW/ Basically, the input pins are also used as PWM/duty cycle pins. I assume you can use a full L298N module in this mode by leaving the enable pins jumpered and sending PWM via the input pint.

@Ezward
Copy link
Contributor Author

Ezward commented Oct 26, 2021

@jithu83 I'm generalizing input/output/pwm handling so each motor controller is not tightly coupled to the particular library that is is using to get ttl or pwm outputs. There is code in actuators.py that you contributed; ArduinoFirmata, ArdPWMSteering, ArdPWMThrottle that uses the Arduino Firmata sketch and python pymata library to talk to it. This is used is in the templates/arduino_drive.py template, which creates a robot that can be driven by a joystick only. This is very specialized code for a very limited use case. Is see the real use-case is for LattePanda boards, since they have a build-in arduino compatible board for GPIO. That is not a board we intend to generally support. We are supporting RaspberryPi and Jetson Nano directly in the code.

If we are going to keep Arduino Firmata support, then like to either generalize this code so we can use the Arduino Firmata like other sources of pwm/duty_cycle. I have a branch which is abstracting input/output/pwm pins into what I am calling the 'pin provider' api (see https://github.com/autorope/donkeycar/blob/945-refactor-motor-drivers/donkeycar/parts/pins.py). Is it worth supporting Arduino Firmata as a source of PWM? One of the issues is that it seems Arduino Firmata can produce a duty cycle, but we don't have control over the frequency; it seems like it would be difficult or impossible to use to control a servo for instance, although the arduino_drive.py template seems to imply that it can. Basically, I'm trying to figure out if it is worth keeping in the main code and building into a more generalized system. Should we put time into this?

@Ezward
Copy link
Contributor Author

Ezward commented Oct 26, 2021

Status
I've got a branch, https://github.com/autorope/donkeycar/tree/945-refactor-motor-drivers where I am doing this work. The strategy is:

  1. create a layer of abstraction over input/output/pwm pins. I call this the pin provider api and it can be found in donkeycar/parts/pins.py. There are 3 pin providers currently implemented; PCA9685, Rpi.GPIO (and so Jetsion.GPIO) and PIGPIO. I'm trying to figure out of we need to support ServoBlaster and Arduino Firmata (or possibly our own Arduino sketch).
  2. Refactor pwm controllers (those support set_pulse() methods) to use this new system, so we can eliminate the other library specific implementations that are tied to specific configurations. This would allow us to be more general in our support of these underlying hardware architectures without users having to write their own parts. For instance, the PIGPIO_PWM configuration supports an hardware layer that takes pwm pulses for both steering and throttle. However, it requires the use of the PIGPIO libary, which does not work on Jetson Nano. By converting this to use the pin providers for the pwm sources, we can support this architecture on Jetson Nano or by using a PCA9685 as the source for PWM. Further, we can support the use of the Rpi.GPIO library, which is installed by default. There are other examples all of which have a similar benefit.
  3. Refactor non-pwm-controller configurations, like the DC_TWO_WHEEL and DC_TWO_WHEEL_L298N, to use pin providers. This has those same benefits; now a differential drive robot can use the GPIO header or a PCA9685 for PWM. In fact, it can mix the two; I have a Duckiebot that does just that; now it can run donkeycar. Duckies and Donkeys living together in peace!
  4. Delete unused code. We have a bunch of code that is not hooked up to anything OR is supporting boards that we really don't support. That makes actuators much harder to maintain.
  5. Add a lot more commenting and documentation while we are at it.

1 and 3 are well along. I'm starting on 2 & 4 now.

@Ezward
Copy link
Contributor Author

Ezward commented Oct 31, 2021

I've checked in code to the branch to address #2 & #4 from my prior comment.

#2. There is now a drivetrain named PWM_STEERING_THROTTLE and a generalized pulse controller called PulseController that use the pins.py pin provider api to send servo style PWM pulses to Servo/ESC style RC car drivetrains.

#4. I've ported the @deprecated decorator from another branch and marked a bunch of stuff in actuators.py as deprecated. I've also left large comments on those classes that explain why they are deprecated and an explanation to how we might proceed if we wanted to keep them in the code. I think one in particular, the Arduino Firmata support, is worth putting into the pins.py pin provider api, but I don't want to block getting this code tested and merged. We can make that a separate issue if we want to move forward with that.

@Ezward
Copy link
Contributor Author

Ezward commented Nov 12, 2021

  • TODO: Add two more drivetrains to support L298 2pin + server and L298 3pin + servo. L298 2pin + servo would replace the current 'SERVO_HBRIDGE_PWM' drivetrain. L298 3pin + servo would be new, but we do have a user out there that needs this.

@Ezward
Copy link
Contributor Author

Ezward commented Dec 21, 2021

Testing

Testers please see the Testers second at the bottom.

Overall Tests to be performed

For ESC/Steering Servo hardware configurations:

  • run donkey calibrate and set PWM values in myconfig.py. Please keep notes. I would like to know how the calibration differs if at all between the older configurations and the new configurations, especially PCA9685 vs GPIO pwm.
    For each hardware configuration:
  • run python manage.py drive and open the web ui
  • verify the drive wheels turn in the correct direction when moving forward and that the speed of the wheel is proportional to the amount of throttle.
  • verify the drive wheels turn in the correct direction when reversing.
  • verify the front wheels turn in the correct direction and proportional to the user's input

Related Documentation

Configurations:

Raspberry Pi, Steering Servo + ESC (standard RC car)

  • 1. Verify that older configuration still works: Raspberry Pi, Steering Servo + ESC (standard RC car)

    • Hardware: Raspberry Pi 3 or 4, PCA9685, ESC, Steering Servo
    • DRIVE_TRAIN_TYPE = "I2C_SERVO"
    • Set the PCA9685 pin assignments for your PCA9685 in myconfig.py in the #STEERING and #THROTTLE sections
    • verify that donkey calibrate calibration process works
    • verify the python manage.py drive works
  • 2. Verify the same hardware but using PCA9685 pin specifiers

    • Hardware: Raspberry Pi 3 or 4, PCA9685, ESC, Steering Servo
    • DRIVE_TRAIN_TYPE = "PWM_STEERING_THROTTLE"
    • Set the pin specifiers for PCA9685 in the # PWM_STEERING_THROTTLE section. For example:
    PWM_STEERING_PIN = "PCA9685.1:40.0"  # PCA9685, I2C bus 1, address 0x40, channel 0
    PWM_THROTTLE_PIN = "PCA9685.1:40.1"  # PCA9685, I2C bus 1, address 0x40, channel 1
    
    • verify that donkey calibrate calibration process works
    • verify the python manage.py drive works
    • Verify the documentation branch describes the how to format pin specifiers for this configuration.
  • 3. Verify same hardware but using Rpi GPIO pins for PWM

    • Hardware: Raspberry Pi 3 or 4, ESC, Steering Servo
    • DRIVE_TRAIN_TYPE = "PWM_STEERING_THROTTLE"
    • Set the pin specifiers for GPIO in the # PWM_STEERING_THROTTLE section. For example:
    PWM_STEERING_PIN = "RPI_GPIO.BOARD.33"  # GPIO board mode pin 33
    PWM_THROTTLE_PIN = "RPI_GPIO.BOARD.35"  # GPIO board mode pin 35
    
    • verify that donkey calibrate calibration process works
    • verify the python manage.py drive works
    • Verify the documentation branch describes the how to format pin specifiers for this configuration.
    • Verify that the documentation branch describes how to activate PWM output on the Raspberry Pi GPIO bus and which two pins can generate hardware PWM (on the RPi 4).
    • I'm going to SKIP testing of this configuration prior to merging. I don't have a car with this configuration. I think this is minimal risk because we have tested PWM/Duty Cycle output via GPIO using configuration Write function to generate image variants #7 below.

Jetson Nano, Steering Servo + ESC (standard RC car)

  • 4. Verify older configuration still works using Jetson Nano

    • Hardware: Jetson Nano, PCA9685, ESC, Servo
    • DRIVE_TRAIN_TYPE = "I2C_SERVO" using PCA9685.
    • Set the PCA9685 pin assignments for your PCA9685 in myconfig.py in the #STEERING and #THROTTLE sections
    • verify that donkey calibrate calibration process works
    • verify the python manage.py drive works
  • 5. Verify the same hardware but using PCA9685 pin specifiers

    • Hardware: Jetson Nano, PCA9685, ESC, Servo
    • DRIVE_TRAIN_TYPE = "PWM_STEERING_THROTTLE"
    • Set the pin specifiers for PCA9685 in the # PWM_STEERING_THROTTLE section. For example:
    PWM_STEERING_PIN = "PCA9685.1:40.0"  # PCA9685, I2C bus 1, address 0x40, channel 0
    PWM_THROTTLE_PIN = "PCA9685.1:40.1"  # PCA9685, I2C bus 1, address 0x40, channel 1
    
    • verify that donkey calibrate calibration process works
    • verify the python manage.py drive works
    • Verify the documentation branch describes the how to format pin specifiers for this configuration.
    • Verify that the documentation branch describes how to activate PWM output on the Jetson Nano GPIO bus and which two pins can generate PWM.
  • 6. Verify same hardware using Jetson Nano GPIO pins for PWM

    • Hardware: Jetson Nano, ESC, Steering Servo
    • DRIVE_TRAIN_TYPE = "PWM_STEERING_THROTTLE"
    • Set the pin specifiers for GPIO in the # PWM_STEERING_THROTTLE section. for example:
    PWM_STEERING_PIN = "RPI_GPIO.BOARD.33"  # GPIO board mode pin 33
    PWM_THROTTLE_PIN = "RPI_GPIO.BOARD.35"  # GPIO board mode pin 35
    
    • verify that donkey calibrate calibration process works
    • verify the python manage.py drive works
    • Verify the documentation branch describes the how to format pin specifiers for this configuration.
    • Verify that the documentation branch describes how to activate PWM output on the Jetson Nano GPIO bus and which two pins can generate hardware PWM.
    • I'm going to SKIP testing of this configuration prior to merging. I don't have a car with this configuration. I think this is minimal risk because we have tested PWM/Duty Cycle output via GPIO using configuration Write function to generate image variants #7 below.

Differential Drive

  • 7. Verify Differential Drive configuration with 2 Pin HBridge on RPi

    • Hardware: Raspberry Pi 3 or 4 or Zero 2 W, L298N Mini HBridge in 2 pin mode, 2 Gear Motors
    • DRIVE_TRAIN_TYPE = "DC_TWO_WHEEL"
    • Set the pins specifiers for GPIO, for example:
    HBRIDGE_L298N_PIN_LEFT_FWD = "RPI_GPIO.BCM.16"  # BCM.16 == BOARD.36
    HBRIDGE_L298N_PIN_LEFT_BWD = "RPI_GPIO.BCM.20"  # BCM.20 == BOARD.38
    HBRIDGE_L298N_PIN_RIGHT_FWD = "RPI_GPIO.BCM.5"  # BCM.5 == BOARD.29
    HBRIDGE_L298N_PIN_RIGHT_BWD = "RPI_GPIO.BCM.6"  # BCM.6 == BOARD.31
    
    • verify the python manage.py drive works
    • I'm going to SKIP testing of this configuration prior to merging. I don't have a car with this configuration. I think this is minimal risk because we have tested PWM/Duty Cycle output via GPIO using configuration Write function to generate image variants #7 below.
  • 7. Verify Differential Drive configuration 3 pin HBridge on Jetson Nano

    • Hardware: Jetson Nano, L298N HBridge in 3pin mode, 2 Gear Motors
    • DRIVE_TRAIN_TYPE = "DC_TWO_WHEEL_L298N"
    • Set the pins specifiers for GPIO, for example:
    HBRIDGE_L298N_PIN_LEFT_FWD = "RPI_GPIO.BCM.16"  # BCM.16 == BOARD.36
    HBRIDGE_L298N_PIN_LEFT_BWD = "RPI_GPIO.BCM.20"  # BCM.20 == BOARD.38
    HBRIDGE_L298N_PIN_LEFT_EN = "RPI_GPIO.BCM.12"   # BCM.12 == BOARD.32 - NOTE: ENABLE pwm using /opt/nvidia/jetson-io/jetson-io.py
    HBRIDGE_L298N_PIN_RIGHT_FWD = "RPI_GPIO.BCM.5"  # BCM.5 == BOARD.29
    HBRIDGE_L298N_PIN_RIGHT_BWD = "RPI_GPIO.BCM.6"  # BCM.6 == BOARD.31
    HBRIDGE_L298N_PIN_RIGHT_EN = "RPI_GPIO.BCM.13"  # BCM.13 == BOARD.33 - NOTE: ENABLE pwm using /opt/nvidia/jetson-io/jetson-io.py
    
    • verify the python manage.py drive works
  • verified: Refactor motor drivers #945 (comment)

  • 8. Verify Differential Drive configuration with PCA9685 on Jetson Nano

    • Hardware: Jetson Nano, L298N HBridge in 3pin mode, 2 Gear Motors
    • DRIVE_TRAIN_TYPE = "DC_TWO_WHEEL_L298N"
    • Set the pins specifiers for GPIO, for example:
    HBRIDGE_L298N_PIN_LEFT_FWD = "PCA9685.1:60.10"
    HBRIDGE_L298N_PIN_LEFT_BWD = "PCA9685.1:60.9"
    HBRIDGE_L298N_PIN_LEFT_EN = "PCA9685.1:60.8"
    HBRIDGE_L298N_PIN_RIGHT_FWD = "PCA9685.1:60.3"
    HBRIDGE_L298N_PIN_RIGHT_BWD = "PCA9685.1:60.2"
    HBRIDGE_L298N_PIN_RIGHT_EN = "PCA9685.1:60.1"
    
    • verify the python manage.py drive works
  • verified: Refactor motor drivers #945 (comment)

RC hat on RaspberryPi using ESC/Servo drive train

Verify that the new RC hat works correctly in the old config and the new config.

  • 9. Verify RC Hat works using the PIGPIO_PWM configuration

    • DRIVE_TRAIN_TYPE = "PIGPIO_PWM"
    • Set the pwm output pins
    #STEERING FOR PIGPIO_PWM OUTPUT
    STEERING_PWM_PIN = 13           #Pin numbering according to Broadcom numbers
    STEERING_PWM_FREQ = 50          #Frequency for PWM
    STEERING_PWM_INVERTED = False   #If PWM needs to be inverted
    
    #THROTTLE FOR PIGPIO_PWM OUTPUT
    THROTTLE_PWM_PIN = 18           #Pin numbering according to Broadcom numbers
    THROTTLE_PWM_FREQ = 50          #Frequency for PWM
    THROTTLE_PWM_INVERTED = False   #If PWM needs to be inverted 
    
    • Also set the RC controller input values as specified in docs
    #PIGPIO RC control
    STEERING_RC_GPIO = 26
    THROTTLE_RC_GPIO = 20
    DATA_WIPER_RC_GPIO = 19
    PIGPIO_STEERING_MID = 1500         # Adjust this value if your car cannot run in a straight line
    PIGPIO_MAX_FORWARD = 2000          # Max throttle to go fowrward. The bigger the faster
    PIGPIO_STOPPED_PWM = 1500
    PIGPIO_MAX_REVERSE = 1000          # Max throttle to go reverse. The smaller the faster
    PIGPIO_SHOW_STEERING_VALUE = False
    PIGPIO_INVERT = False
    PIGPIO_JITTER = 0.025   # threshold below which no signal is reported
    
  • 10. Verify the same machine works on the PWM_STEERING_THROTTLE configuration using "PIGPIO.BCM.##" pin specifiers.

    • DRIVE_TRAIN_TYPE = "PWM_STEERING_THROTTLE"
    • configure PWM output pins
    PWM_STEERING_PIN = "PIGPIO.BCM.13"           # PWM output pin for steering servo
    PWM_THROTTLE_PIN = "PIGPIO.BCM.18"           # PWM output pin for ESC
    
    STEERING_LEFT_PWM = int(4096 * 1 / 20)       # pwm value for full left steering (1ms pulse)
    STEERING_RIGHT_PWM = int(4096 * 2 / 20)      # pwm value for full right steering (2ms pulse)
    
    THROTTLE_FORWARD_PWM = int(4096 * 2 / 20)    # pwm value for max forward (2ms pulse)
    THROTTLE_STOPPED_PWM = int(4096 * 1.5 / 20)  # pwm value for no movement (1.5ms pulse)
    THROTTLE_REVERSE_PWM = int(4096 * 1 / 20)    # pwm value for max reverse throttle (1ms pulse)
    

Testers

Thank you for helping. If you have a configuration that matches one of the above, please checkout the code branch and the documentation branches. The older 'legacy' DRIVE_TRAIN_TYPE configurations, like I2C_SERVO are deprecated and will be removed in a future version. However, they should continue to work now, so we need those tested to make sure we did not break them. We especially need help on the newer configurations that use new DRIVE_TRAIN_TYPE configurations, so after you test your legacy configuration please test the new configuration that corresponds to your donkeycar.

Instructions

  • if you have not yet cloned the documentation branch then do that (change to you project folder or home folder or wherever you keep your projects)
git clone https://github.com/autorope/donkeydocs.git
cd donkeydocs
git checkout 945-refactor-motor-drivers
  • from your donkeycar project folder checkout the branch we are testing
git fetch
git checkout 945-refactor-motor-drivers
  • create a new mycar directory with this branch
donkey createcar --path=~/mycar945
  • Edit your myconfig in conformance with the configuration you are testing.
  • Run through the tests described above with your donkeycar. Please add a comment to this issue with your results. If you run into problems please add a comment to this issue and include;
    • The configuration you are testing (you can just refer to one of the numbers above) and the relevant entries from your myconfig.py
    • The results you got like, "it worked like a charm!", 'It gave me an error message :(", "The wheels did not turn.", or "The documentation is wrong.", etc.
    • If you find a problem, add this additional information
      • A copy of the console output from the python manage.py or donkey calibrate command, whichever you had the problem with.
      • Steps to reproduce the issue. Please try more than once and let us know if you could repeat the issue a) every time b) sometimes or intermittently c) can't repeat it, only got it the one time.

@Ezward
Copy link
Contributor Author

Ezward commented Dec 23, 2021

I have verified configuration 8. Verify Differential Drive configuration with PCA9685 on Jetson Nano. Using this robot https://github.com/Ezward/diyduckie which was originally designed to run ROS, but with this new feature in donkey I can run donkey on it now.

@Ezward
Copy link
Contributor Author

Ezward commented Dec 23, 2021

I have verified this configuration 7. Verify Differential Drive configuration 3 pin HBridge on Jetson Nano usng this robot https://github.com/Ezward/diyduckie , but with all control signals moved from the PCA9685 to the Jetson Nano bus. This is the configuration.

DRIVE_TRAIN_TYPE = "DC_TWO_WHEEL_L298N" # I2C_SERVO|DC_STEER_THROTTLE|DC_TWO_WHEEL|DC_TWO_WHEEL_L298N|SERVO_HBRIDGE_PWM|PIGPIO_PWM|MM1|MOCK

HBRIDGE_L298N_PIN_LEFT_FWD = "RPI_GPIO.BCM.16"  # BCM.16 == BOARD.36
HBRIDGE_L298N_PIN_LEFT_BWD = "RPI_GPIO.BCM.20"  # BCM.20 == BOARD.38
HBRIDGE_L298N_PIN_LEFT_EN = "RPI_GPIO.BCM.12"   # BCM.12 == BOARD.32 - NOTE: ENABLE pwm using /opt/nvidia/jetson-io/jetson-io.py

HBRIDGE_L298N_PIN_RIGHT_FWD = "RPI_GPIO.BCM.5"  # BCM.5 == BOARD.29
HBRIDGE_L298N_PIN_RIGHT_BWD = "RPI_GPIO.BCM.6"  # BCM.6 == BOARD.31
HBRIDGE_L298N_PIN_RIGHT_EN = "RPI_GPIO.BCM.13"  # BCM.13 == BOARD.33 - NOTE: ENABLE pwm using /opt/nvidia/jetson-io/jetson-io.py

I enabled pwm on board pin 13 and board pin 12 using this documentation https://github.com/autorope/donkeydocs/blob/945-refactor-motor-drivers/docs/parts/pins.md#generating-pwm-from-the-jetson-nano

@Ezward
Copy link
Contributor Author

Ezward commented Jan 3, 2022

Verified configurations #9 and #10.
I updated the test comment to include testing the RC Hat on a Raspberry Pi with a servo and esc and an RC controllers. Those are configurations #9 and #10. I tested both of them and they worked, including the OLED display, RC steering and RC Throttle control. We should add a note about pairing the RC controller to the Receiver. After pairing it took a bunch of fiddling with the various trip and scale controls on the RC controller, so we want to also describe that generally in the docs.

Ezward added a commit to autorope/donkeydocs that referenced this issue Jan 3, 2022
Ezward added a commit to autorope/donkeydocs that referenced this issue Jan 4, 2022
* Add PWM explanatino to servo/esc section of actuators.

* Move PWM svg into assets

* Fix PWM svg link

* Added docs for pin providers and specifiers

* Added instructions for enabling PWM on Jetson Nano

* Documentation for new drive-train configurations that use pin providers

* Add link to RC setup in controllers.md

* Add known issue with OLED driver and diff. drive configurations.

* Add wiring diagram for PCA9685

* gitignore hidden files

* Fix broken asset links in build_hardware.md
- Ok, ok, I broke them.  You got me.  Happy!?

* Fix broken asset links, typos

* Remove redundant reference to rc.md
- yes, I did it again.

* Fix broken links.

* Fix broken links.

* Change link for 3-pin L298N example.

* Update RC hat docs to use PWM_STEERING_THROTTLE drive train.
This has been tested autorope/donkeycar#945 (comment)

* Update known issue with OLED and RPi.GPIO
@Ezward
Copy link
Contributor Author

Ezward commented Jan 5, 2022

merged code PR #951 and docs PR autorope/donkeydocs#25

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant