NVIDIA-AI-IOT / jetbot

An educational AI robot based on NVIDIA Jetson Nano.
MIT License
2.97k stars 1.02k forks source link

4wheel drive #320

Closed Cnys closed 1 year ago

Cnys commented 3 years ago

Hi is it possible to make a Jetbot 4whel drive?

I have tared down my old robot project and tried to make it a Jetbot. now it runs with only 2wheels.

If its possible, where do I find necessary information to rewrite the program?

20201117_065037 20201117_065047

tomMEM commented 3 years ago

Hello @Cnys , you might have to modify carefully the original scripts motor.py and robot.py localized outside of "build folder" in the jetbot repository and define the two additional motors. After this rebuild the "build" with python setup.py install. T

Cnys commented 3 years ago

If i modify robot.py like this will i destroy the program or will it work? i dont have a clue how to modify motor.py!

`` import time import traitlets from traitlets.config.configurable import SingletonConfigurable from Adafruit_MotorHAT import Adafruit_MotorHAT from .motor import Motor

class Robot(SingletonConfigurable):

left_motor = traitlets.Instance(Motor)
right_motor = traitlets.Instance(Motor)

left_motor2 = traitlets.Instance(Motor)
right_motor2 = traitlets.Instance(Motor)

# config
i2c_bus = traitlets.Integer(default_value=1).tag(config=True)
left_motor_channel = traitlets.Integer(default_value=1).tag(config=True)
left_motor_alpha = traitlets.Float(default_value=1.0).tag(config=True)
right_motor_channel = traitlets.Integer(default_value=2).tag(config=True)
right_motor_alpha = traitlets.Float(default_value=1.0).tag(config=True)

left_motor_channel2 = traitlets.Integer(default_value=3).tag(config=True)
left_motor_alpha2 = traitlets.Float(default_value=1.0).tag(config=True)
right_motor_channel2 = traitlets.Integer(default_value=4).tag(config=True)
right_motor_alpha2 = traitlets.Float(default_value=1.0).tag(config=True)

def __init__(self, *args, **kwargs):
    super(Robot, self).__init__(*args, **kwargs)
    self.motor_driver = Adafruit_MotorHAT(i2c_bus=self.i2c_bus)
    self.left_motor = Motor(self.motor_driver, channel=self.left_motor_channel, alpha=self.left_motor_alpha)
    self.right_motor = Motor(self.motor_driver, channel=self.right_motor_channel, alpha=self.right_motor_alpha)

    self.left_motor2 = Motor(self.motor_driver, channel=self.left_motor_channel2, alpha=self.left_motor_alpha2)
    self.right_motor2 = Motor(self.motor_driver, channel=self.right_motor_channel2, alpha=self.right_motor_alpha2)  

def set_motors(self, left_speed, right_speed):
    self.left_motor.value = left_speed
    self.right_motor.value = right_speed

    self.left_motor2.value = left_speed
    self.right_motor2.value = right_speed

def forward(self, speed=1.0, duration=None):
    self.left_motor.value = speed
    self.right_motor.value = speed

    self.left_motor2.value = speed
    self.right_motor2.value = speed

def backward(self, speed=1.0):
    self.left_motor.value = -speed
    self.right_motor.value = -speed

    self.left_motor2.value = -speed
    self.right_motor2.value = -speed

def left(self, speed=1.0):
    self.left_motor.value = -speed
    self.right_motor.value = speed

    self.left_motor2.value= -speed
    self.right_motor2.value = speed

def right(self, speed=1.0):
    self.left_motor.value = speed
    self.right_motor.value = -speed

    self.left_motor2.value = speed
    self.right_motor2.value = -speed

def stop(self):
    self.left_motor.value = 0
    self.right_motor.value = 0
    self.left_motor2.value = 0
    self.right_motor2.value = 0 ``
tomMEM commented 3 years ago

Hello, first you could change the motor ID in the original robot.py to 3 and 4 to see if your system is responding (e.g. left_motor_channel = traitlets.Integer(default_value=3).tag(config=True)). Motor.py might be not necessary to change (but I have no system to test it). Extended information might be available at Adafruit-Motor-HAT itself. T

Cnys commented 3 years ago

I can see the file robot.py outside the container but i cant edit them. Sudo wont work . Pardon my rookie´nes but how do I edit them? Is it possible from the terminal?

Best regards P

Från: Thomasmailto:notifications@github.com Skickat: den 19 november 2020 05:47 Till: NVIDIA-AI-IOT/jetbotmailto:jetbot@noreply.github.com Kopia: Cnysmailto:peterjansson20@hotmail.com; Mentionmailto:mention@noreply.github.com Ämne: Re: [NVIDIA-AI-IOT/jetbot] 4wheel drive (#320)

Hello @Cnyshttps://github.com/Cnys , you might have to modify carefully the original scripts motor.py and robot.py localized outside of "build folder" in the jetbot repository and define the two additional motors. After this rebuild the "build" with python setup.py install. T

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHubhttps://github.com/NVIDIA-AI-IOT/jetbot/issues/320#issuecomment-730126824, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AAVPQLYAYHSKR3CKGM627FTSQSPOPANCNFSM4TZRBWNA.

tomMEM commented 3 years ago

Hello @Cnys, it looks like you are going for a very different project that requires many modifications of the jetbot scripts.

The Docker container is an encapsulated environment that does not easily allow communication with the I/O periphery, thus it might be not optimal for Edge devices.

Sudo commands are for system wide changes, so it is not possible to use within the Docker container.

In addition, any changes within the Docker container are not permanent, and would require rebuild of the Docker container. Please check treads #304 and #319. The jetbot docker container is just used for the jetbot hardware configuration and examples.

Might be you could start with an old SD image using "jetbot_JP4.3_JL1.24_PT1.30_0115.img" and clone an jetbot repository from the end of September on a different SD card to regain flexibility.

There you can modify roboty.py indicating the additional motor IDs 3 and 4 in a separated class and modify motor.py to get channels 3 and 4 activated at "if(channel == 1): ..... ". Now only 1 and 2 are available. Finally python3 setup.py install should work. You can also check for Mecanum 4 wheel robot car projects for other examples. Hope it works out. T

tokk-nv commented 3 years ago

Hi Cnys, You are getting great help from tomMEM! Thank you, TtomMEM, for sharing all the great advice.

first you could change the motor ID in the original robot.py to 3 and 4 to see if your system is responding I agree with tomMEM on this, so I think this is what you want to do first.

It would be like

  1. Access Jupyter Lab on your web browser
  2. Open the Terminal on Jupyter Lab
  3. Make the above change to jetbot under /opt/jetbot/ and re-install jetbot
    cd /opt/jetbot/
    vi ./jetbot/motor.py # apply the neccesary changes
    python3 setup.py install
  4. (Re)open teleoperation.ipynb on JupyterLab and see how other pair of motors work

The change you did to motor.py will not persist when you restart the Docker container. Once you confirm the above change realize what you want, you can follow John's comment as suggested by tomMem.

Regards, Chitoku

Cnys commented 3 years ago

hmm? now the left side motors is running? il get back when i have figuring out what append?

tomMEM commented 3 years ago

Hello, it reads like you get it going. As a note, in former versions, the "python3 setup.py install" will update files /jetbot/... into a subfolder "build". However this seems to be happen only when the size or script head of the new file e.g. ./jetbot/motor.py would be slightly different. In addition, if you rename to 1motor.py no update will take place. By the way Chitoku, thanks for this great practical tutorial. T

Cnys commented 3 years ago

it seems i was some random event when both motors on the left side was running. i cant replicate what happened.

tomMEM commented 3 years ago

Hello @Cnys , I had a chance to check with the new 4.4 Jetbot image. If you change the motor.py in workspace/jetbot/jetbot (e.g. a nonsense change if(channel == 5: #1) and rebuild using "python3 setup.py install", then the file in workspace/jetbot/build/lib/jetbot/motor.py will be changed. You can check directly there. However, the changes have no effect, even after the restart of the kernel. Only after shutdown and reboot of the nano (e.g. in my example I/O error), it will take effect. Surprisingly the changes in motor.py persist also after restart the nano, might be because the docker container is never shutting down - ? So it is indeed easy to get random effects. Finally, I hope you find the right settings to address all four motors, might be you can share then. Still it might be for your project better just to use older SD images without docker, is more hacker friendly. Best T

tomMEM commented 3 years ago

Hello @Cnys , it is written somewhere (jetbot.org) but to summarize briefly how to handle the new docker Jetbot 44 the following steps are required: 1) Install Putty or any SSH terminal software on your PC and set for example 192.168.55.1 port 22 in case of jetson-Nano Micro USB to PC USB connection a) The SSH terminal will be necessary for reboot or shutdown of the nano, it can remain open on the PC together with juptyer lab in the browser later. 2) In the terminal window on the PC, when first time start, use login jetbot, password jetbot and start with sudo nmcli device wifi list, then sudo nmcli device wifi connect e.g.HomeNet password e.g.HomeNet 3) sudo reboot 4) in browser: to juptyer lab using MICRO USB to PC USB (55.1:8888/lab?) or WIFI address (192.168.?.?:8888/lab?), 5) in cd jetbot/jetbot you might try to change scripts. 6) Rebuild into a new container using local changes according tread #319, or same container #296 7) The build of the new container will download all libraries again, so it takes space and time. 8) Disable old container (jupyter lab in browser will close) and enable new container with ./enable $HOME (in cd jetbot/docker)

T

Cnys commented 3 years ago

on my other sd card i have code_oss and jetson_inference. If i want to try there first how do i do it? i' m not so sure it' s possible to run 4 dc motors on this motor driver any more it seems nobody else have done it.

tomMEM commented 3 years ago

Hello @Cnys, if you have a Jetpack version (best 4.3) you can still follow SD card image from Scratch as outlined in https://github.com/NVIDIA-AI-IOT/jetbot/wiki/Create-SD-Card-Image-From-Scratch. You might start from point 3 and if you have already Jupyter lab, then it could be also skipped. In step 9 the jetbot repository will be cloned. After build you should have a functional jetbot repository. Now you can experiment with the motor.py and robot.py settings localized in jetbot/jetbot/ and after change just run python3 setup.py install every-time you changed something.

(If you have Jebot 4.4 SD image with docker, then you would need to ./disable docker, -also prevent it from system start, and follow steps outline above).

T

tomMEM commented 3 years ago

Hello @Cnys , in case you are still looking: it is actually quite straight forward- I uploaded three files in my repository under Jetbot44. You can use the SD image (41) Jetbot4 Docker. The workspace is linked to the outside of the docker, so changes there will remain, in case the docker container is restarted from the docker image. Please try: a) Change files (motor and robot.py) under cd /workspace/jetbot/jetbot in jupyterlab, or copy my two files into this folder b) from a terminal window in Jupyter lab copy the files to container in opt/jetbot/jetbot with: cp /workspace/jetbot/jetbot/motor.py /opt/jetbot/jetbot/motor.py c ) same: cp /workspace/jetbot/jetbot/robot.py /opt/jetbot/jetbot/robot.py d) cd /opt/jetbot/ e) python3 setup.py install Restart kernel and start e.g. 4Wheels_basic_motion.ipyn from /workspace/jetbot/notebooks/ in jupyter lab. Here you can also change the notebooks as you like, they do not need to be copied to /opt/jupyter/.

Hope it works out for you. T

Main changes are in motor.py:

self._motor = self._driver.getMotor(channel) if (channel == 1): self._ina = 1 self._inb = 0 elif (channel == 2): self._ina = 2 self._inb = 3 elif (channel == 3): self._ina = 3 self._inb = 0 elif (channel == 4): self._ina = 4 self._inb = 3 atexit.register(self._release)

and robot.py left_motor2 = traitlets.Instance(Motor) right_motor2 = traitlets.Instance(Motor)

# config
 left_motor_channel2 = traitlets.Integer(default_value=3).tag(config=True)
left_motor_alpha2 = traitlets.Float(default_value=1.0).tag(config=True)

etc.

def left2(self, speed2=1.0): self.left_motor2.value = -speed2 self.right_motor2.value = speed2 etc.

Cnys commented 3 years ago

now all 4 motors are running but some how randomized.

robot.py

import time import traitlets from traitlets.config.configurable import SingletonConfigurable from Adafruit_MotorHAT import Adafruit_MotorHAT from .motor import Motor

class Robot(SingletonConfigurable):

left_motor = traitlets.Instance(Motor)
right_motor = traitlets.Instance(Motor)
left_motor2 = traitlets.Instance(Motor)
right_motor2 = traitlets.Instance(Motor)

# config
i2c_bus = traitlets.Integer(default_value=1).tag(config=True)
left_motor_channel = traitlets.Integer(default_value=1).tag(config=True)
left_motor_alpha = traitlets.Float(default_value=1.0).tag(config=True)
right_motor_channel = traitlets.Integer(default_value=2).tag(config=True)
right_motor_alpha = traitlets.Float(default_value=1.0).tag(config=True)
left_motor_channel2 = traitlets.Integer(default_value=3).tag(config=True)
left_motor_alpha2 = traitlets.Float(default_value=1.0).tag(config=True)
right_motor_channel2 = traitlets.Integer(default_value=4).tag(config=True)
right_motor_alpha2 = traitlets.Float(default_value=1.0).tag(config=True)

def __init__(self, *args, **kwargs):
    super(Robot, self).__init__(*args, **kwargs)
    self.motor_driver = Adafruit_MotorHAT(i2c_bus=self.i2c_bus)
    self.left_motor = Motor(self.motor_driver, channel=self.left_motor_channel, alpha=self.left_motor_alpha)
    self.left_motor2 = Motor(self.motor_driver, channel=self.left_motor_channel2, alpha=self.left_motor_alpha2)
    self.right_motor = Motor(self.motor_driver, channel=self.right_motor_channel, alpha=self.right_motor_alpha)
    self.right_motor2 = Motor(self.motor_driver, channel=self.right_motor_channel2, alpha=self.right_motor_alpha2)

def set_motors(self, left_speed, right_speed):
    self.left_motor.value = left_speed
    self.left_motor2.value = left_speed
    self.right_motor.value = right_speed
    self.right_motor2.value = right_speed

def forward(self, speed=1.0, duration=None):
    self.left_motor.value = speed
    self.right_motor.value = speed
    self.left_motor2.value = speed
    self.right_motor2.value = speed

def backward(self, speed=1.0):
    self.left_motor.value = -speed
    self.right_motor.value = -speed
    self.left_motor2.value = -speed
    self.right_motor2.value = -speed

def left(self, speed=1.0):
    self.left_motor.value = -speed
    self.right_motor.value = speed
    self.left_motor2.value = -speed
    self.right_motor2.value = speed

def right(self, speed=1.0):
    self.left_motor.value = speed
    self.right_motor.value = -speed
    self.left_motor2.value = speed
    self.right_motor2.value = -speed

def stop(self):
    self.left_motor.value = 0
    self.left_motor2.value = 0
    self.right_motor.value = 0
    self.right_motor2.value = 0

and motor.py

import atexit from Adafruit_MotorHAT import Adafruit_MotorHAT import traitlets from traitlets.config.configurable import Configurable

class Motor(Configurable):

value = traitlets.Float()

# config
alpha = traitlets.Float(default_value=1.0).tag(config=True)
beta = traitlets.Float(default_value=0.0).tag(config=True)

def __init__(self, driver, channel, *args, **kwargs):
    super(Motor, self).__init__(*args, **kwargs)  # initializes traitlets

    self._driver = driver
    self._motor = self._driver.getMotor(channel)
    if(channel == 1):
        self._ina = 1
        self._inb = 0
    if(channel == 2):
        self._ina = 2
        self._inb = 3
    if(channel == 3):
        self._ina = 3
        self._inb = 0
    if(channel == 4):
        self._ina = 4
        self._inb = 3

    atexit.register(self._release)

@traitlets.observe('value')
def _observe_value(self, change):
    self._write_value(change['new'])

def _write_value(self, value):
    """Sets motor value between [-1, 1]"""
    mapped_value = int(255.0 * (self.alpha * value + self.beta))
    speed = min(max(abs(mapped_value), 0), 255)
    self._motor.setSpeed(speed)
    if mapped_value < 0:
        self._motor.run(Adafruit_MotorHAT.FORWARD)
        self._driver._pwm.setPWM(self._ina,0,0)
        self._driver._pwm.setPWM(self._inb,0,speed*16)
    else:
        self._motor.run(Adafruit_MotorHAT.BACKWARD)
        self._driver._pwm.setPWM(self._ina,0,speed*16)
        self._driver._pwm.setPWM(self._inb,0,0)

def _release(self):
    """Stops motor by releasing control"""
    self._motor.run(Adafruit_MotorHAT.RELEASE)
    self._driver._pwm.setPWM(self._ina,0,0)
    self._driver._pwm.setPWM(self._inb,0,0)
tomMEM commented 3 years ago

Hello, I think you need to control the first and second pair of motors individually. So instead def left(self, speed=1.0): self.left_motor.value = -speed self.right_motor.value = speed self.left_motor2.value = -speed self.right_motor2.value = speed

you might try to control your motors separately (left, and left2), otherwise one speed (speed) will be called for all motors: def left2(self, speed2=1.0): self.left_motor2.value = -speed2 self.right_motor2.value = speed2

or better this that allows to control every motor individually: def set_motors2(self, left_speed2, right_speed2): self.left_motor2.value = left_speed2 self.right_motor2.value = right_speed2 def stop2(self): self.left_motor2.value = 0 self.right_motor2.value = 0

Then in basic_motion.ipynb try in a new cell: robot.set_motors2(1, 0) or robot.set_motors2(0,1) robot.set_motors(0,0)

or other cell robot.left2(speed2=0.7)

other cell: robot.stop2()

to test second and first pair, as well as every motor one-by-one (see 4Wheel_basic_motion_Tb.ipynb). T

whmzsu commented 3 years ago

I have the same question, I think the best way is the offical repo updating to support the 4 wheel drive mode. Or a simple way that just connect the sencond pair wheels wire connect to the first pair at the same control channel, not the new wheel channel.

tomMEM commented 3 years ago

Hello @Cnys, by the way, it is better to write the "if .." as nested (or elif) block otherwise the outcome might be random, in case the function is called nearly simultaneously by left and right, left2 and right2.

In addition, the steering of a four wheel robot car is not simple and requires many adjustments to the individual wheels. I found this web helpful "https://www.rakeshmondal.info/4-Wheel-Drive-Robot-Design", however it might be more easy if you replace the wheels with 4 Mecanum wheels. T

whmzsu commented 3 years ago

Yestoday I tested the simple way, just connecting the sencond pair wheels's wire to the first pair at the same control channel, it worked as espected. Although the turning is not so elegent. Be care of the wheel install direction, it took me a lot of time to adjust the left ,right ,forward and backward direction. BTW: I use the 1:220 TT motor, so my jetbot run in a low speed .

Cnys commented 3 years ago

I have tried to connect the left motors in serial and the left motors in serial and it woks so far all wheels are touching the flor, otherwise all torque are lost to the wheel who is not touching the floor .

my robot will go in ruff terrain and not a plain floor so I will fix the program when the time is on my side.

/P Från: Mingomailto:notifications@github.com Skickat: den 3 december 2020 02:46 Till: NVIDIA-AI-IOT/jetbotmailto:jetbot@noreply.github.com Kopia: Cnysmailto:peterjansson20@hotmail.com; Mentionmailto:mention@noreply.github.com Ämne: Re: [NVIDIA-AI-IOT/jetbot] 4wheel drive (#320)

Yestoday I tested the simple way, just connecting the sencond pair wheels's wire to the first pair at the same control channel, it worked as espected. Although the turning is not so elegent. Be care of the wheel install direction, it took me a lot of time to adjust the left ,right ,forward and backward direction. BTW: I use the 1:220 TT motor, so my jetbot run in a low speed .

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHubhttps://github.com/NVIDIA-AI-IOT/jetbot/issues/320#issuecomment-737605366, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AAVPQLYCVDVOFE4JXY72Y6LSS3UY7ANCNFSM4TZRBWNA.

ppolnik commented 2 years ago

@tomMEM

I am using your code to control 4 motors through Adafruit controller and the robot works great. Thanks CI for the very well done documentation on your github profile.

But in my case, I need to control 6 motors using 2 Adafruit drivers in Jetson Nano board in Jetbot project. And a problem arises because I need to use two i2c buses. I am trying to change this robot.py value in i2c_bus variable: i2c_bus = traitlets.Integer(default_value=1).tag(config=True) "on:" i2c_bus = traitlets.Integer(0,1).tag(config=True)

and errors are generated with the value i2c_bus = traitlets.Integer(1).tag(config=True) The code works fine, however when I even use i2c_bus = traitlets.Integer(0).tag(config=True) errors will appear.

I don't know where else to define the use of the second bus in the jetbot files ? after executing the command in i2cdetect -r -y 1 drivers are found on both buses.