added sonar filtering page
parent
5f0ff276e9
commit
e24b957fa0
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
272
index.html
272
index.html
|
|
@ -28,6 +28,7 @@
|
||||||
<button class="tab-btn text-gray-600 hover:text-blue-600" data-target="lesson6">OLED Display</button>
|
<button class="tab-btn text-gray-600 hover:text-blue-600" data-target="lesson6">OLED Display</button>
|
||||||
<button class="tab-btn text-gray-600 hover:text-blue-600" data-target="lesson7">RGB LED(Neopixel)</button>
|
<button class="tab-btn text-gray-600 hover:text-blue-600" data-target="lesson7">RGB LED(Neopixel)</button>
|
||||||
<button class="tab-btn text-gray-600 hover:text-blue-600" data-target="lesson8">Sonar</button>
|
<button class="tab-btn text-gray-600 hover:text-blue-600" data-target="lesson8">Sonar</button>
|
||||||
|
<button class="tab-btn text-gray-600 hover:text-blue-600" data-target="lesson8b">Sonar Filtering</button>
|
||||||
<button class="tab-btn text-gray-600 hover:text-blue-600 hidden" data-target="lesson9">Motor
|
<button class="tab-btn text-gray-600 hover:text-blue-600 hidden" data-target="lesson9">Motor
|
||||||
Encoders</button>
|
Encoders</button>
|
||||||
<button hidden class="tab-btn text-gray-600 hover:text-blue-600" data-target="lesson10">Reciever</button>
|
<button hidden class="tab-btn text-gray-600 hover:text-blue-600" data-target="lesson10">Reciever</button>
|
||||||
|
|
@ -48,13 +49,16 @@
|
||||||
<!-- Step 1 -->
|
<!-- Step 1 -->
|
||||||
<div class="prose">
|
<div class="prose">
|
||||||
<h2>Step 1: Setting up the IDE</h2>
|
<h2>Step 1: Setting up the IDE</h2>
|
||||||
<p>There are many IDEs that we can use to program MicroPython robot, we'll be using one called "Thonny". So let's begin by downloading it.</p>
|
<p>There are many IDEs that we can use to program MicroPython robot, we'll be using one called
|
||||||
|
"Thonny". So let's begin by downloading it.</p>
|
||||||
<h3>Download</h3>
|
<h3>Download</h3>
|
||||||
<ul class="ml-6 list-disc">
|
<ul class="ml-6 list-disc">
|
||||||
<li><a href="https://github.com/thonny/thonny/releases/download/v4.1.7/thonny-4.1.7.exe" class="text-blue-600 hover:underline">Windows</a>
|
<li><a href="https://github.com/thonny/thonny/releases/download/v4.1.7/thonny-4.1.7.exe"
|
||||||
|
class="text-blue-600 hover:underline">Windows</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li><a href="https://github.com/thonny/thonny/releases/download/v4.1.7/thonny-4.1.7.pkg" class="text-blue-600 hover:underline">Mac</a></li>
|
<li><a href="https://github.com/thonny/thonny/releases/download/v4.1.7/thonny-4.1.7.pkg"
|
||||||
|
class="text-blue-600 hover:underline">Mac</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>You can also get it from the Thonny website, <a
|
<p>You can also get it from the Thonny website, <a
|
||||||
href="https://thonny.org/">https://thonny.org/</a></p>
|
href="https://thonny.org/">https://thonny.org/</a></p>
|
||||||
|
|
@ -69,13 +73,16 @@
|
||||||
<ol class="list-decimal ml-6">
|
<ol class="list-decimal ml-6">
|
||||||
<li>In Thonny, click on the Run->Configure Interpreter buttons at the top of the window.</li>
|
<li>In Thonny, click on the Run->Configure Interpreter buttons at the top of the window.</li>
|
||||||
<li>Choose "MicroPython (Raspberry Pi Pico) from the first dropdown menu.</li>
|
<li>Choose "MicroPython (Raspberry Pi Pico) from the first dropdown menu.</li>
|
||||||
<li>Click the "<u>Install or update MicroPython</u>" button down the bottom-right of the window.</li>
|
<li>Click the "<u>Install or update MicroPython</u>" button down the bottom-right of the window.
|
||||||
<li>You need to put your device in FLASH mode, hold down the BOOT button, press and release the RESET button.</li>
|
</li>
|
||||||
|
<li>You need to put your device in FLASH mode, hold down the BOOT button, press and release the
|
||||||
|
RESET button.</li>
|
||||||
<li>You should now find a drive called "RPI-RP2". in the Target volume dropdown, shoose it.</li>
|
<li>You should now find a drive called "RPI-RP2". in the Target volume dropdown, shoose it.</li>
|
||||||
<li>Choose the variant "Raspberry Pi Pico - Pico / Pico H"</li>
|
<li>Choose the variant "Raspberry Pi Pico - Pico / Pico H"</li>
|
||||||
<li>Press Install button to install the firmware.</li>
|
<li>Press Install button to install the firmware.</li>
|
||||||
<li>Reset the device again with the RESET button.</li>
|
<li>Reset the device again with the RESET button.</li>
|
||||||
<li>Press the cancel button to close the firmware update panel, then press OK to officially begin coding.</li>
|
<li>Press the cancel button to close the firmware update panel, then press OK to officially
|
||||||
|
begin coding.</li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -100,8 +107,10 @@
|
||||||
<!-- Step 1 -->
|
<!-- Step 1 -->
|
||||||
<div class="prose">
|
<div class="prose">
|
||||||
<h2>Step 1: Open/Create the main.py file</h2>
|
<h2>Step 1: Open/Create the main.py file</h2>
|
||||||
<p>When your device is plugged into your computer, you should be able to press the File->Load button, and choose "Raspberry Pi Pico" to load from.</p>
|
<p>When your device is plugged into your computer, you should be able to press the File->Load
|
||||||
<p>If no such file exists we need to make it, press File->New, and then save it to the Raspberry Pi Pico as "main.py"</p>
|
button, and choose "Raspberry Pi Pico" to load from.</p>
|
||||||
|
<p>If no such file exists we need to make it, press File->New, and then save it to the Raspberry Pi
|
||||||
|
Pico as "main.py"</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|
||||||
|
|
@ -110,7 +119,8 @@
|
||||||
<!-- Step 2: Image -->
|
<!-- Step 2: Image -->
|
||||||
<div class="prose">
|
<div class="prose">
|
||||||
<h2>Step 2: Open the Serial Monitor</h2>
|
<h2>Step 2: Open the Serial Monitor</h2>
|
||||||
<p>To get messages back from your device, and to read errors, press the view->shellbutton and make sure
|
<p>To get messages back from your device, and to read errors, press the view->shellbutton and make
|
||||||
|
sure
|
||||||
you have the shell window open at the bottom.</p>
|
you have the shell window open at the bottom.</p>
|
||||||
|
|
||||||
</br>
|
</br>
|
||||||
|
|
@ -119,7 +129,8 @@
|
||||||
<code>print("Hello World")</code> and then save, yuou should see the message appear in the
|
<code>print("Hello World")</code> and then save, yuou should see the message appear in the
|
||||||
Serial monitor.
|
Serial monitor.
|
||||||
</p>
|
</p>
|
||||||
<p>Note that the code won't persist on the robot after a reset unless you also SAVE it to the device, RUN just runs it once</p>
|
<p>Note that the code won't persist on the robot after a reset unless you also SAVE it to the
|
||||||
|
device, RUN just runs it once</p>
|
||||||
|
|
||||||
</br>
|
</br>
|
||||||
<h2>Step 4: Code all the things!</h2>
|
<h2>Step 4: Code all the things!</h2>
|
||||||
|
|
@ -908,9 +919,12 @@ while True:
|
||||||
<!-- Step 2 -->
|
<!-- Step 2 -->
|
||||||
<div class="prose">
|
<div class="prose">
|
||||||
<h2>Step 2: Coding</h2>
|
<h2>Step 2: Coding</h2>
|
||||||
<p>We'll use a library to handle sending and listening for the pulses. Put this module on your device.</p>
|
<p>We'll use a library to handle sending and listening for the pulses. Put this module on your
|
||||||
|
device.</p>
|
||||||
</br>
|
</br>
|
||||||
<p>When the "distance()" function is called, it sends a short pulse on the trigger pin, then waits for a response on the echo pin. The time it takes for the echo to return is used to calculate the distance.</p>
|
<p>When the "distance()" function is called, it sends a short pulse on the trigger pin, then waits
|
||||||
|
for a response on the echo pin. The time it takes for the echo to return is used to calculate
|
||||||
|
the distance.</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -955,7 +969,8 @@ class Sonar:
|
||||||
<!-- Step 3 -->
|
<!-- Step 3 -->
|
||||||
<div class="prose">
|
<div class="prose">
|
||||||
<h2>Step 3: Call it in the main.py</h2>
|
<h2>Step 3: Call it in the main.py</h2>
|
||||||
<p>Now all we need to do is create the object and call the distance function in the main.py file.</p>
|
<p>Now all we need to do is create the object and call the distance function in the main.py file.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<pre class="bg-gray-100 p-4 rounded shadow text-sm"><code class="language-python">
|
<pre class="bg-gray-100 p-4 rounded shadow text-sm"><code class="language-python">
|
||||||
|
|
@ -1076,6 +1091,237 @@ while True:
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Lesson 6 (hidden initially) -->
|
||||||
|
<section id="lesson8b" class="lesson-content hidden">
|
||||||
|
<h1 class="text-3xl font-bold mb-6">Sonar Filtering</h1>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
|
||||||
|
<!-- Step 1 -->
|
||||||
|
<div class="prose">
|
||||||
|
<h2>Step 1: Understand the problem</h2>
|
||||||
|
<p>Like most inputs, the sonar data is noisy, and there are different types of noise.</p>
|
||||||
|
<p>On the right you can see a fairly fixed output, but with sudden spikes of irregular data.</p>
|
||||||
|
</br>
|
||||||
|
<p>So we need code that will filter our spikes like that, the easiest is to just throw them away. To
|
||||||
|
do this we'll use a simple moving average filter.</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<img src="images/sonar_noise.png" alt="Robot turning right"
|
||||||
|
class="rounded shadow w-full max-w-xs md:max-w-full" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Step 2 -->
|
||||||
|
<div class="prose">
|
||||||
|
<h2>Step 2: Coding a moving average filter</h2>
|
||||||
|
<p>Instead of just taking single data points and making decisions, we'll keep an array of the last 8
|
||||||
|
readings.</p>
|
||||||
|
</br>
|
||||||
|
<p>First we need to start keeping a rolling buffer of the last 8 readings.</p>
|
||||||
|
<p>We'll use a list to store the readings, and we'll use the <code>append()</code> method to add new
|
||||||
|
readings to the end of the list.</p>
|
||||||
|
<p>We'll use the <code>pop(0)</code> method to remove the oldest reading from the start of the list.
|
||||||
|
</p>
|
||||||
|
<p>We'll use the <code>len()</code> method to check the size of the list.</p>
|
||||||
|
<p>We'll use the <code>print()</code> method to print the list.</p>
|
||||||
|
</br>
|
||||||
|
<p><b>NOTE:</b> We can get smoother results by using a larger buffer size, but this can also start
|
||||||
|
to introduce lag.</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<pre class="bg-gray-100 p-4 rounded shadow text-sm"><code class="language-python">
|
||||||
|
|
||||||
|
|
||||||
|
BUFFER_SIZE = 8 # The size of the buffer
|
||||||
|
buffer = [] # Create an empty array to hold
|
||||||
|
# the readings (this is the rolling buffer)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
dist = sonar.distance() # Get a new reading
|
||||||
|
buffer.append(dist) # Add it to the buffer
|
||||||
|
|
||||||
|
# If the buffer is too big, remove the oldest reading
|
||||||
|
if len(buffer) > BUFFER_SIZE:
|
||||||
|
buffer.pop(0)
|
||||||
|
|
||||||
|
print(buffer) # Print the buffer
|
||||||
|
|
||||||
|
|
||||||
|
</code></pre>
|
||||||
|
</div>
|
||||||
|
<!-- Step 3 -->
|
||||||
|
<div class="prose">
|
||||||
|
<h2>Step 3: Take the average</h2>
|
||||||
|
<p>Now all we need to do is create the object and call the distance function in the main.py file.
|
||||||
|
</p>
|
||||||
|
<pre class="bg-gray-100 p-4 rounded shadow text-sm"><code class="language-python">
|
||||||
|
def average_filter(values):
|
||||||
|
total = 0
|
||||||
|
for i in range(len(values)):
|
||||||
|
total = total + values[i]
|
||||||
|
|
||||||
|
return total / len(values)
|
||||||
|
|
||||||
|
|
||||||
|
# Call in the main loop
|
||||||
|
filtered = average_filter(values)
|
||||||
|
print(filtered)
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<img src="images/sonar_noise_averaged.png" alt="Robot turning right"
|
||||||
|
class="rounded shadow w-full max-w-xs md:max-w-full" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="prose">
|
||||||
|
<h2>Step 4: Take the median</h2>
|
||||||
|
<p>That's better, but those large peaks are still throwing our average off quite a bit. We can improve this by taking the MEDIAN of the buffer rather than the average.
|
||||||
|
</p>
|
||||||
|
<pre class="bg-gray-100 p-4 rounded shadow text-sm"><code class="language-python">
|
||||||
|
def median_filter(values):
|
||||||
|
sorted_vals = sorted(values) # sort the values from low->high
|
||||||
|
mid = int(len(sorted_vals) / 2) # get the middle index
|
||||||
|
return sorted_vals[mid] # return the value of the median
|
||||||
|
|
||||||
|
|
||||||
|
# Call in the main loop
|
||||||
|
filtered = median_filter(values)
|
||||||
|
print(filtered)
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<img src="images/sonar_noise_median.png" alt="Robot turning right"
|
||||||
|
class="rounded shadow w-full max-w-xs md:max-w-full" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="prose">
|
||||||
|
<h2>Step 5: Fine tuning</h2>
|
||||||
|
<p>Before moving on, play with the buffer size to see how it effects the results AND the responsiveness to changes.</p>
|
||||||
|
<p>Try and find a good balance with the time.sleep() value as well</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Lesson 6 (hidden initially) -->
|
||||||
|
<section id="lesson10" class="lesson-content hidden">
|
||||||
|
<h1 class="text-3xl font-bold mb-6">Receiver</h1>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Step 2 -->
|
||||||
|
<div class="prose">
|
||||||
|
<h2>Step 1: Coding</h2>
|
||||||
|
<p>Create a new file called <code>remote.py</code> and add the code to the right.</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<pre class="bg-gray-100 p-4 rounded shadow text-sm"><code class="language-python">
|
||||||
|
# remote.py
|
||||||
|
|
||||||
|
import board
|
||||||
|
import busio
|
||||||
|
|
||||||
|
START_BYTE = 0xAA
|
||||||
|
END_BYTE = 0xBB
|
||||||
|
PACKET_LENGTH = 8
|
||||||
|
|
||||||
|
# Calibration values
|
||||||
|
LEFT_MIN = 0
|
||||||
|
LEFT_MID = 131
|
||||||
|
LEFT_MAX = 203
|
||||||
|
RIGHT_MIN = 51
|
||||||
|
RIGHT_MID = 120
|
||||||
|
RIGHT_MAX = 255
|
||||||
|
|
||||||
|
class ThumbInput:
|
||||||
|
def __init__(self, tx=board.GP0, rx=board.GP1, baudrate=115200):
|
||||||
|
self.uart = busio.UART(tx=tx, rx=rx, baudrate=baudrate, timeout=0.1)
|
||||||
|
self.buffer = bytearray()
|
||||||
|
|
||||||
|
def read(self):
|
||||||
|
"""Returns (left_percent, right_percent) in range [-100, 100], or None if packet incomplete."""
|
||||||
|
data = self.uart.read(1)
|
||||||
|
|
||||||
|
if data:
|
||||||
|
byte = data[0]
|
||||||
|
self.buffer.append(byte)
|
||||||
|
|
||||||
|
if len(self.buffer) > PACKET_LENGTH:
|
||||||
|
self.buffer = self.buffer[-PACKET_LENGTH:]
|
||||||
|
|
||||||
|
if len(self.buffer) == PACKET_LENGTH and self.buffer[0] == START_BYTE and self.buffer[-1] == END_BYTE:
|
||||||
|
payload = self.buffer[1:7]
|
||||||
|
self.buffer = bytearray()
|
||||||
|
left_raw = payload[1]
|
||||||
|
right_raw = payload[3]
|
||||||
|
|
||||||
|
left = self._map_thumbstick(left_raw, LEFT_MIN, LEFT_MID, LEFT_MAX)
|
||||||
|
right = self._map_thumbstick(right_raw, RIGHT_MIN, RIGHT_MID, RIGHT_MAX)
|
||||||
|
|
||||||
|
return (int(left), int(right))
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _map_thumbstick(self, x, min_val, mid_val, max_val):
|
||||||
|
if x < mid_val:
|
||||||
|
return (x - mid_val) / (mid_val - min_val) * 100
|
||||||
|
else:
|
||||||
|
return (x - mid_val) / (max_val - mid_val) * 100
|
||||||
|
|
||||||
|
</code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Step 3 -->
|
||||||
|
<div class="prose">
|
||||||
|
<h2>Step 2: Using the recieved signal</h2>
|
||||||
|
<p>Import just the <code>ThumbInput</code> part of the library.</p>
|
||||||
|
</br>
|
||||||
|
<p>Initialize the receiver with <code>receiver = ThumbInput()</code></p>
|
||||||
|
</br>
|
||||||
|
<p>Create a new loop as this won't work nicely with any i2c, this loop should be placed after the
|
||||||
|
motors are created, and before the i2c is initialized.</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<pre class="bg-gray-100 p-4 rounded shadow text-sm"><code class="language-python">
|
||||||
|
|
||||||
|
from remote import ThumbInput
|
||||||
|
|
||||||
|
receiver = ThumbInput()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
result = receiver.read()
|
||||||
|
if result:
|
||||||
|
left_motor.move(result[0])
|
||||||
|
right_motor.move(result[1])
|
||||||
|
#print("Left:", result[0], "Right:", result[1])
|
||||||
|
time.sleep(0.001)
|
||||||
|
</code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
|
||||||
<!-- Lesson 6 (hidden initially) -->
|
<!-- Lesson 6 (hidden initially) -->
|
||||||
<section id="lesson11" class="lesson-content hidden">
|
<section id="lesson11" class="lesson-content hidden">
|
||||||
<h1 class="text-3xl font-bold mb-6">PID</h1>
|
<h1 class="text-3xl font-bold mb-6">PID</h1>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue