Skip to content

Commit

Permalink
deploy: 7f0040f
Browse files Browse the repository at this point in the history
  • Loading branch information
ErikUmble committed Nov 15, 2024
0 parents commit 08cb942
Show file tree
Hide file tree
Showing 102 changed files with 8,111 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .buildinfo
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Sphinx build info version 1
# This file records the configuration used when building these files. When it is not found, a full rebuild will be done.
config: 0d34b26585006095a78d690cb4db54a9
tags: 645f666f9bcd5a90fca523b33c5a78b7
Binary file added .doctrees/bluetooth.doctree
Binary file not shown.
Binary file added .doctrees/environment.pickle
Binary file not shown.
Binary file added .doctrees/faq.doctree
Binary file not shown.
Binary file added .doctrees/index.doctree
Binary file not shown.
Binary file added .doctrees/movement.doctree
Binary file not shown.
Binary file added .doctrees/sensors.doctree
Binary file not shown.
Binary file added .doctrees/usage.doctree
Binary file not shown.
Empty file added .nojekyll
Empty file.
9 changes: 9 additions & 0 deletions _downloads/051422046cba6049b278a5cebcee1f0c/blink_LED.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from machine import Pin
import time

LED = Pin(6, Pin.OUT)

while True:
LED.off()
time.sleep(2)
LED.on()
47 changes: 47 additions & 0 deletions _downloads/df14896bba634131bee7f870285c5b8c/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from nanonav import BLE, NanoBot
import time

### test Bluetooth ###

# Create a Bluetooth object
ble = BLE(name="NanoNav")

ble.send(43)
response = ble.read()
# wait until something changes, indicating a response
while response == 43:
response = ble.read()
time.sleep(0.5)

print("Received: ", response)

### test motors and encoders ###

# Create a NanoBot object
robot = NanoBot()

# Move forward for 2 seconds
print(f'encoder 1 start: {robot.get_enc1()}')
robot.m1_forward(30)
robot.m2_forward(30)
time.sleep(2)
print(f'encoder 1 end: {robot.get_enc1()}')

# Stop
robot.stop()
time.sleep(2)

# Move backward for 2 seconds
print(f'encoder 2 start: {robot.get_enc2()}')
robot.m1_backward(30)
robot.m2_backward(30)
time.sleep(2)
print(f'encoder 2 end: {robot.get_enc2()}')

# Stop
robot.stop()

### test ir sensors ###
while True:
print(f'left: {robot.ir_left()} right: {robot.ir_right()}')
time.sleep(0.5)
228 changes: 228 additions & 0 deletions _downloads/e6ffb4613247059f19b76a508c48412c/nanonav.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
from ble_advertising import advertising_payload
import bluetooth
from machine import Pin, PWM, ADC, freq
import machine
from micropython import const
import rp2

import time

# Define BLE constants (these are not packaged in bluetooth for space efficiency)
_IO_CAPABILITY_DISPLAY_ONLY = const(0)
_FLAG_READ = const(0x0002)
_FLAG_WRITE = const(0x0008)
_IRQ_CENTRAL_CONNECT = const(1)
_IRQ_CENTRAL_DISCONNECT = const(2)
_IRQ_GATTS_WRITE = const(3)

class BLE:
def __init__(self, ble=bluetooth.BLE(), name="NANO RP2040"):
# Setup bluetooth low energy communication service
_SERVICE_UUID = bluetooth.UUID(0x1523) # unique service id for the communication
_NanoNav_CHAR_UUID = (bluetooth.UUID(0x1525), _FLAG_WRITE | _FLAG_READ) # characteristic
_NanoNav_SERVICE = (_SERVICE_UUID, (_NanoNav_CHAR_UUID,),) # service to provide the characteristic

self._ble = ble
self._ble.active(True)
self._ble.config(
bond=True,
mitm=True,
le_secure=True,
io=_IO_CAPABILITY_DISPLAY_ONLY
)
self._ble.irq(self._irq)
((self._handle,),) = self._ble.gatts_register_services((_NanoNav_SERVICE,))
self._connections = set()
self._payload = advertising_payload(name=name, services=[_SERVICE_UUID])
self._advertise()
self.value = b'a'

def _advertise(self, interval_us=500000):
self._ble.gap_advertise(interval_us, adv_data=self._payload)

def _irq(self, event, data):
# handle bluetooth event
if event == _IRQ_CENTRAL_CONNECT:
# handle succesfull connection
conn_handle, addr_type, addr = data
self._connections.add(conn_handle)

self.on_connected()

elif event == _IRQ_CENTRAL_DISCONNECT:
# handle disconnect
conn_handle, _, _ = data
self._connections.remove(conn_handle)
self._advertise()

self.on_disconnected()

elif event == _IRQ_GATTS_WRITE:
conn_handle, value_handle = data
if conn_handle in self._connections:
# Value has been written to the characteristic
self.value = self._ble.gatts_read(value_handle)

def on_connected(self):
pass

def on_disconnected(self):
pass

def send(self, value):
if not isinstance(value, bytes):
if isinstance(value, int):
value = value.to_bytes(1, "big")
elif isinstance(value, str):
value = value.encode('utf-8')
else:
raise ValueError("send value should be type int, bytes, or string")
self.value = value
self._ble.gatts_write(self._handle, value)

def read(self):
#use the last value written to characteristic
value = self.value
try:
return int.from_bytes(value, "big")
except Exception as e:
return None

class NanoBot:
def __init__(self, saturated_duty=22000, *args, **kwargs):
# turn ir sensor pin on (inactive because it's active low)
self.ir_right_sensor = Pin(28, Pin.OUT)
self.ir_right_sensor.on()

time.sleep(0.5)

# ir sensors
self.ir_left_sensor = ADC(Pin(29, Pin.IN))
self.ir_right_sensor = ADC(Pin(28, Pin.IN))

# initialize frequency
machine.freq(100000000)

# initialize motors
m1pin1 = Pin(21)
m1pin2 = Pin(4)
m2pin1 = Pin(18)
m2pin2 = Pin(17)

self.m1pwm1 = PWM(m1pin1)
self.m1pwm2 = PWM(m1pin2)
self.m2pwm1 = PWM(m2pin1)
self.m2pwm2 = PWM(m2pin2)

# initialize motor constants
self.max_duty = 65535 # constant
self.saturated_duty = saturated_duty # choice for max speed
assert(0 <= self.saturated_duty <= self.max_duty)
self.turn90ticks = 120
self.turn_error = 5
self.block_delay = 1550

# PID controller constants
self.battery_scaling = 1.05
self.kp = 0.8 * self.battery_scaling
self.ki = 0.08 * self.battery_scaling
self.kd = 0.04 * self.battery_scaling

# initialize encoder variables
self.encpins = (15, 25, 7, 27)
self.enc1p1 = Pin(self.encpins[0], Pin.IN)
self.enc1p2 = Pin(self.encpins[1], Pin.IN)
self.enc2p1 = Pin(self.encpins[2], Pin.IN)
self.enc2p2 = Pin(self.encpins[3], Pin.IN)

self.enc1 = 0
self.enc2 = 0
self.enc1dir = 1
self.enc2dir = 1

# add interrupt callbacks to track encoder ticks
self.enc1p1.irq(lambda pin: self.enc_pin_high(self.encpins[0]), Pin.IRQ_RISING)
self.enc1p2.irq(lambda pin: self.enc_pin_high(self.encpins[1]), Pin.IRQ_RISING)
self.enc2p1.irq(lambda pin: self.enc_pin_high(self.encpins[2]), Pin.IRQ_RISING)
self.enc2p2.irq(lambda pin: self.enc_pin_high(self.encpins[3]), Pin.IRQ_RISING)

self.setup()

def enc_pin_high(self, pin):
if pin == self.encpins[0] or pin == self.encpins[1]:
if self.enc1p1.value() == 1 and self.enc1p2.value() == 1:
self.enc1 += 1 * self.enc1dir
elif self.enc1p1.value() == 1:
self.enc1dir = 1
else:
self.enc1dir = -1
if pin == self.encpins[2] or pin == self.encpins[3]:
if self.enc2p1.value() == 1 and self.enc2p2.value() == 1:
self.enc2 += 1 * self.enc2dir
elif self.enc2p1.value() == 1:
self.enc2dir = -1
else:
self.enc2dir = 1

def calc_duty(self, duty_100):
return int(duty_100 * self.max_duty / 100)

def m1_forward(self, duty_cycle):
self.m1pwm1.duty_u16(min(self.calc_duty(duty_cycle), self.saturated_duty))
self.m1pwm2.duty_u16(0)

def m1_backward(self, duty_cycle):
self.m1pwm1.duty_u16(0)
self.m1pwm2.duty_u16(min(self.calc_duty(duty_cycle), self.saturated_duty))

def m1_signed(self, duty_cycle):
if duty_cycle >= 0:
self.m1_forward(duty_cycle)
else:
self.m2_backward(-duty_cycle)

def m2_forward(self, duty_cycle):
self.m2pwm1.duty_u16(min(self.calc_duty(duty_cycle), self.saturated_duty))
self.m2pwm2.duty_u16(0)

def m2_backward(self, duty_cycle):
self.m2pwm1.duty_u16(0)
self.m2pwm2.duty_u16(min(self.calc_duty(duty_cycle), self.saturated_duty))

def m2_signed(self, duty_cycle):
if duty_cycle >= 0:
self.m2_forward(duty_cycle)
else:
self.m2_backward(-duty_cycle)

def stop(self):
# set all duty cycles to 0
self.m1pwm1.duty_u16(0)
self.m1pwm2.duty_u16(0)
self.m2pwm1.duty_u16(0)
self.m2pwm2.duty_u16(0)

def setup(self):
# initialize frequencies
self.m1pwm1.freq(1000)
self.m1pwm2.freq(1000)
self.m2pwm1.freq(1000)
self.m2pwm2.freq(1000)

def ir_left(self):
return self.ir_left_sensor.read_u16() < 65535 // 2

def ir_right(self):
return self.ir_right_sensor.read_u16() < 65535 // 2

def get_enc1(self):
return self.enc1

def get_enc2(self):
return self.enc2

def set_enc1(self, value):
self.enc1 = value

def set_enc2(self, value):
self.enc2 = value
Binary file added _images/IR_assembled.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added _images/lightblue_characteristic_view.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added _images/lightblue_connected_view.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added _images/lightblue_devices_view.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added _images/openmv_green.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added _images/openmv_unconnected.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added _images/parts.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added _images/top_view.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added _images/wheels.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
72 changes: 72 additions & 0 deletions _sources/bluetooth.rst.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
.. _Bluetooth:

Bluetooth
=========
Using Bluetooth Low Energy (BLE) to communicate with the NanoNav.

Quick Example
-------------

.. code-block:: python
from nanonav import BLE
import time
# Create a Bluetooth object
ble = BLE(name="NanoNav")
ble.send(43)
response = ble.read()
# wait until something changes, indicating a response
while response == 43:
response = ble.read()
time.sleep(0.5)
print("Received: ", response)
Usage
-----

.. autoclass:: nanonav.BLE
:members:

.. note::
Just as a heads up, we've noticed that occasionally the value stored on the BLE characteristic gets corrupted. Wherever you call the `read` method, it's a good idea to verify that
the value is within the range you expect (and not ``None``), and if not, consider requesting the value again.

Connecting from Mobile
----------------------

Various mobile apps are available for communicating with Bluetooth Low Energy. We recommend LightBlue which is available for both iOS and Android.
After downloading and installing it, you will need to turn your phone's Bluetooth on and open the app (no pairing is needed for Bluetooth Low Energy).

If the NanoNav is waiting for a BLE connection, you will see it as one of the connection options in LightBlue. You may need to scroll down to find it.

.. note::
On some versions of iOS, the BLE devices are automatically renamed, and NanoNav's connection may show up as "Arduino" or something else. If you find yourself in this
situation, it can be helpful to search for the Arduino's MAC address instead, which means identifying the device once in LightBlue and keeping track of its MAC address for future connections.

.. image:: images/lightblue_devices_view.png
:width: 400
:alt: LightBlue Application with Bluetooth connections available

After clicking the connect button, you will see a screen like this, which gives information about the connection.

.. image:: images/lightblue_connected_view.png
:width: 400
:alt: LightBlue Application after connecting to device

It is possible to configure the BLE for more complex behavior, but with this kit we only need to send small numbers back and forth with the Arduino. Click on the
option at the bottom (highlighted in red in the above screenshot) to open the portal where you can perform this simplified communication with NanoNav.

.. image:: images/lightblue_characteristic_view.png
:width: 400
:alt: LightBlue display of BLE characteristic, with options to read or write values to it

You can think of a BLE connection as a secret whiteboard that you and your friend share. There is always some number written on it, and each of you
can look at (read) whatever is on it whenever you like, and can also change (write to) it whenever you like. Inside the LightBlue app, as shown in the
above picture, you can click the :blue:`Read Again` button as often as you would like, but the value will only
change when you (or NanoNav) writes to it. And you can send a number as often as you want in LightBlue, but NanoNav will not know unless you program it to
read the value periodically. (Actually, you can setup BLE interrupts for NanoNav to run code when something changes in the BLE connection, similar to the :py:meth:`~nanonav.BLE.on_connected` and :py:meth:`~nanonav.BLE.on_disconnected`, but we think
you'll have an easier time getting your code to work as expected by avoiding that kind of programming for now).
If interested in learning about other bluetooth capabilities beyond the scope of NanoNav, see `here <https://docs.micropython.org/en/latest/library/bluetooth.html>`_.
29 changes: 29 additions & 0 deletions _sources/faq.rst.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
FAQ
===

No questions yet.

Troubleshooting
===============

Arduino not connecting to OpenMV
--------------------------------

If your Arduino is not connecting to OpenMV, either because OpenMV is loading forever, or because the cursor is flickering between loading and a pointer, you should reset your board. Do this by double tapping the button on top of the Arduino. This will reset the board. Wait for OpenMV to update. When OpenMV updates, it should offer to load the latest firmware. Agree and load the firmware, and hopefully the Arduino will connect.

If this doesn't work, it's probably because your code has a frequently repeating while loop which keeps the Arduino occupied. Keep trying to reset the board by double pressing the top button, or open Finder or FileExplorer, open the board's external drive, and replace its main.py (your code) with a basic main.py such as :download:`blink_LED.py <../../tests/installation_check/blink_LED.py>`. This should enable the Arduino to connect.

If you still cannot get the Arduino to connect, it could be a problem on your computer. Try changing the port you're using to connect to the Arduino.

Strange problems with code which was just working
-------------------------------------------------

When you run in laptop mode from OpenMV, sometimes the code gives you errors the first 2 times you try to run. We're not sure why this happens, but these errors always go away after the third attempt.

If when running in solo mode, the Arduino behaves strangely when running code which you know works, it could be because you are out of storage. This will not happen until your code files are at least 30 KB in total!!! If your code files are not 30 KB or larger in total, what you're experiencing is an ordinary bug. If you suspect that you make be out of storage, try to shrink your code by removing comments and making functions more efficient. Every character counts, because it's the storage of your .py files which is running out. Try removing excess newlines or whitespace.

Issues
------

Found a bug? Create an issue on GitHub. Submit `here <https://github.com/Bram-Hub/NanoNav/issues>`_ with as much information as you can provide
about the context of the bug.
Loading

0 comments on commit 08cb942

Please sign in to comment.