First commit
This commit is contained in:
@@ -0,0 +1,73 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright 2026 hangpersonal
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
# Distance-Driven Robot with Tilt Detection
|
||||||
|
|
||||||
|
This project controls a small two-wheel robot that travels toward a target based on an ultrasonic distance measurement. The robot waits for the user to press a button, calculates the required number of motor steps, drives both stepper motors, and continuously checks its accelerometer for unsafe tilt.
|
||||||
|
|
||||||
|
The OLED screen, LED, and buzzer provide clear status feedback throughout the process.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Button-controlled start
|
||||||
|
- Ultrasonic distance measurement
|
||||||
|
- Automatic distance-to-step conversion
|
||||||
|
- Synchronized left and right stepper-motor movement
|
||||||
|
- Continuous Y-axis tilt monitoring
|
||||||
|
- OLED status and result messages
|
||||||
|
- Audible success and warning signals
|
||||||
|
- LED warning when the robot tilts
|
||||||
|
- Repeating operation for multiple runs
|
||||||
|
|
||||||
|
## Hardware
|
||||||
|
|
||||||
|
The program expects the hardware objects configured in `main.py`:
|
||||||
|
|
||||||
|
- LED
|
||||||
|
- Push button
|
||||||
|
- Buzzer
|
||||||
|
- Two stepper motors (left and right)
|
||||||
|
- Ultrasonic distance sensor
|
||||||
|
- OLED display
|
||||||
|
- Accelerometer
|
||||||
|
|
||||||
|
Refer to the object initialization section at the top of `main.py` for the GPIO pins, I²C addresses, and other board-specific configuration used by your build.
|
||||||
|
|
||||||
|
> **Safety:** Place the robot on a clear, level surface before pressing the button. Be ready to lift or power off the robot if it approaches an obstacle or edge.
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
|
||||||
|
The main control loop runs continuously:
|
||||||
|
|
||||||
|
1. The warning LED is turned off.
|
||||||
|
2. The OLED is cleared and displays **Press button to start**.
|
||||||
|
3. `wait_button_press()` blocks execution until the user presses the button.
|
||||||
|
4. The buzzer beeps once to confirm the start.
|
||||||
|
5. The ultrasonic sensor measures the distance to the target.
|
||||||
|
6. `get_steps_from_distance(distance)` converts that measurement into a motor-step count.
|
||||||
|
7. The OLED displays the measured distance and calculated steps.
|
||||||
|
8. A `reached` flag is initialized to `True`.
|
||||||
|
9. The program repeats once for every calculated step:
|
||||||
|
- Move the right motor one step.
|
||||||
|
- Move the left motor one step.
|
||||||
|
- Read the accelerometer's Y-axis value (`AcY`).
|
||||||
|
- If the tilt threshold is exceeded, set `reached` to `False` and stop driving.
|
||||||
|
10. The robot reports the result:
|
||||||
|
- **REACHED:** Display the message and beep once.
|
||||||
|
- **TILTED:** Turn on the LED, display the warning, and beep three times with short pauses.
|
||||||
|
11. The final message remains visible for 2–3 seconds before the loop resets.
|
||||||
|
|
||||||
|
## Program flow
|
||||||
|
|
||||||
|
```text
|
||||||
|
Idle / LED off
|
||||||
|
|
|
||||||
|
Display "Press button to start"
|
||||||
|
|
|
||||||
|
Wait for button press
|
||||||
|
|
|
||||||
|
Beep and measure distance
|
||||||
|
|
|
||||||
|
Convert distance to motor steps
|
||||||
|
|
|
||||||
|
Move both motors one step at a time
|
||||||
|
|
|
||||||
|
Check AcY after every step
|
||||||
|
|
|
||||||
|
+---+------------------+
|
||||||
|
| |
|
||||||
|
Tilt detected All steps completed
|
||||||
|
| |
|
||||||
|
Stop movement Display "REACHED"
|
||||||
|
LED on Beep once
|
||||||
|
Display "TILTED"
|
||||||
|
Beep three times
|
||||||
|
| |
|
||||||
|
+----------+-----------+
|
||||||
|
|
|
||||||
|
Pause, then reset
|
||||||
|
```
|
||||||
|
|
||||||
|
## Core functions
|
||||||
|
|
||||||
|
### `wait_button_press()`
|
||||||
|
|
||||||
|
Waits until the physical button is pressed. The function deliberately blocks so the robot cannot begin moving before user confirmation.
|
||||||
|
|
||||||
|
### `get_steps_from_distance(distance)`
|
||||||
|
|
||||||
|
Converts the ultrasonic sensor's distance measurement into the number of steps required by the motors. Keeping this calculation in a separate function makes it easy to recalibrate the robot for different wheels, motors, or measurement units.
|
||||||
|
|
||||||
|
## Tilt detection
|
||||||
|
|
||||||
|
After every pair of motor movements, the program reads `AcY`. A reading outside the permitted range indicates that the robot has tilted and may no longer be driving safely.
|
||||||
|
|
||||||
|
The intended check is equivalent to:
|
||||||
|
|
||||||
|
```python
|
||||||
|
if AcY > 12000 or AcY < -12000:
|
||||||
|
reached = False
|
||||||
|
break
|
||||||
|
```
|
||||||
|
|
||||||
|
This detects a magnitude greater than `12000` in either direction. A condition such as `AcY > 12000 or AcY > -12000` is not equivalent and would be true for most normal readings.
|
||||||
|
|
||||||
|
The threshold is a test value and may require calibration for the specific accelerometer orientation and chassis.
|
||||||
|
|
||||||
|
## Status indicators
|
||||||
|
|
||||||
|
| State | OLED | LED | Buzzer |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Waiting | `Press button to start` | Off | Silent |
|
||||||
|
| Starting | Distance and step count | Off | One beep |
|
||||||
|
| Target reached | `REACHED` | Off | One beep |
|
||||||
|
| Excessive tilt | `TILTED` | On | Three beeps |
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- The program intentionally uses an infinite loop so the robot returns to its waiting state after each run.
|
||||||
|
- The `reached` flag separates successful completion from an interrupted movement.
|
||||||
|
- Modular helper functions make the control logic easier to test, reuse, and adapt in future projects.
|
||||||
|
- Keep comments concise and consistent, especially around hardware initialization, calibration values, and safety checks.
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
from utime import sleep
|
||||||
|
from machine import Pin
|
||||||
|
|
||||||
|
class Button:
|
||||||
|
def __init__(self, pin):
|
||||||
|
self.pin = Pin(pin, mode=Pin.IN, pull=Pin.PULL_UP)
|
||||||
|
|
||||||
|
def is_pressed(self):
|
||||||
|
return self.pin.value() == 0
|
||||||
|
|
||||||
|
def wait_button_press(self):
|
||||||
|
while self.is_pressed() == False:
|
||||||
|
sleep(0.01)
|
||||||
|
return
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
from machine import PWM, Pin
|
||||||
|
from utime import sleep
|
||||||
|
|
||||||
|
# From https://github.com/udacity/cd13765-deci-emb-sys-l4-term2-project.git
|
||||||
|
|
||||||
|
class Buzzer:
|
||||||
|
def __init__(self, pin):
|
||||||
|
self.pin = pin
|
||||||
|
self.buzzer = PWM(Pin(pin, Pin.OUT))
|
||||||
|
self.buzzer.duty(0)
|
||||||
|
|
||||||
|
def beep_once(self):
|
||||||
|
self.buzzer.freq(1047)
|
||||||
|
self.buzzer.duty(50)
|
||||||
|
sleep(0.2)
|
||||||
|
self.buzzer.duty(0)
|
||||||
+132
@@ -0,0 +1,132 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"author": "Gary Vladimir Núñez López",
|
||||||
|
"editor": "wokwi",
|
||||||
|
"parts": [
|
||||||
|
{
|
||||||
|
"type": "board-esp32-devkit-c-v4",
|
||||||
|
"id": "esp",
|
||||||
|
"top": 0,
|
||||||
|
"left": 0,
|
||||||
|
"attrs": { "env": "micropython-20231227-v1.22.0" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "wokwi-led",
|
||||||
|
"id": "led1",
|
||||||
|
"top": -13.2,
|
||||||
|
"left": 205.4,
|
||||||
|
"attrs": { "color": "red" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "wokwi-resistor",
|
||||||
|
"id": "r1",
|
||||||
|
"top": 100.8,
|
||||||
|
"left": 201.05,
|
||||||
|
"rotate": 90,
|
||||||
|
"attrs": { "value": "220" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "wokwi-pushbutton",
|
||||||
|
"id": "btn1",
|
||||||
|
"top": 47.4,
|
||||||
|
"left": -150.6,
|
||||||
|
"rotate": 90,
|
||||||
|
"attrs": { "color": "green", "key": "1" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "wokwi-buzzer",
|
||||||
|
"id": "bz1",
|
||||||
|
"top": -122.4,
|
||||||
|
"left": 184.2,
|
||||||
|
"attrs": { "volume": "0.1" }
|
||||||
|
},
|
||||||
|
{ "type": "wokwi-a4988", "id": "drv1", "top": 235.2, "left": -177.6, "attrs": {} },
|
||||||
|
{
|
||||||
|
"type": "wokwi-stepper-motor",
|
||||||
|
"id": "stepper1",
|
||||||
|
"top": 19.19,
|
||||||
|
"left": -274.98,
|
||||||
|
"attrs": { "size": "8", "arrow": "orange" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "wokwi-stepper-motor",
|
||||||
|
"id": "stepper2",
|
||||||
|
"top": 19.19,
|
||||||
|
"left": 274.98,
|
||||||
|
"attrs": { "size": "8", "arrow": "orange" }
|
||||||
|
},
|
||||||
|
{ "type": "wokwi-a4988", "id": "drv2", "top": 235.2, "left": 177.6, "attrs": {} },
|
||||||
|
{
|
||||||
|
"type": "wokwi-hc-sr04",
|
||||||
|
"id": "ultrasonic1",
|
||||||
|
"top": -132.9,
|
||||||
|
"left": -42.5,
|
||||||
|
"attrs": { "distance": "194" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "board-ssd1306",
|
||||||
|
"id": "oled1",
|
||||||
|
"top": 233.54,
|
||||||
|
"left": 0.23,
|
||||||
|
"attrs": { "i2cAddress": "0x3c" }
|
||||||
|
},
|
||||||
|
{ "type": "wokwi-mpu6050", "id": "imu1", "top": -92.18, "left": -218.48, "attrs": {} }
|
||||||
|
],
|
||||||
|
"connections": [
|
||||||
|
[ "esp:TX", "$serialMonitor:RX", "", [] ],
|
||||||
|
[ "esp:RX", "$serialMonitor:TX", "", [] ],
|
||||||
|
[ "esp:GND.2", "led1:C", "black", [ "v0" ] ],
|
||||||
|
[ "led1:A", "r1:1", "cyan", [ "v0" ] ],
|
||||||
|
[ "esp:12", "r1:2", "cyan", [ "h-14.21", "v76.8", "h240" ] ],
|
||||||
|
[ "esp:15", "bz1:2", "orange", [ "h81.64", "v-192", "h38.8" ] ],
|
||||||
|
[ "bz1:1", "esp:GND.2", "black", [ "v9.6", "h-48", "v57.6" ] ],
|
||||||
|
[ "drv1:SLEEP", "drv1:RESET", "white", [ "h-9.6", "v-9.6" ] ],
|
||||||
|
[ "drv1:GND.1", "esp:GND.1", "black", [ "v0.08", "h86.55", "v-153.6" ] ],
|
||||||
|
[ "esp:5V", "drv1:VDD", "red", [ "h-33.41", "v96" ] ],
|
||||||
|
[ "drv2:GND.1", "esp:GND.1", "black", [ "v0.08", "h19.35", "v19.2", "h-288", "v-172.8" ] ],
|
||||||
|
[ "drv2:VDD", "esp:5V", "red", [ "h28.95", "v38.48", "h-288", "v-134.4" ] ],
|
||||||
|
[ "drv2:RESET", "drv2:SLEEP", "white", [ "h-9.6", "v9.6" ] ],
|
||||||
|
[ "stepper2:A-", "drv2:2B", "magenta", [ "v0" ] ],
|
||||||
|
[ "stepper2:A+", "drv2:2A", "orange", [ "v0" ] ],
|
||||||
|
[ "stepper2:B+", "drv2:1A", "gold", [ "v0" ] ],
|
||||||
|
[ "stepper2:B-", "drv2:1B", "blue", [ "v0" ] ],
|
||||||
|
[ "stepper1:A-", "drv1:2B", "magenta", [ "v105.6", "h144", "v28.8" ] ],
|
||||||
|
[ "stepper1:A+", "drv1:2A", "orange", [ "v96", "h144", "v57.6" ] ],
|
||||||
|
[ "stepper1:B+", "drv1:1A", "gold", [ "v76.8", "h144", "v86.4" ] ],
|
||||||
|
[ "stepper1:B-", "drv1:1B", "blue", [ "v67.2", "h144", "v96" ] ],
|
||||||
|
[ "esp:19", "drv2:STEP", "yellow", [ "h62.44", "v201.6" ] ],
|
||||||
|
[ "esp:13", "drv1:STEP", "yellow", [ "h-196.61", "v134.4" ] ],
|
||||||
|
[ "ultrasonic1:VCC", "esp:5V", "red", [ "v19.2", "h-57.6", "v192" ] ],
|
||||||
|
[ "ultrasonic1:GND", "esp:GND.2", "black", [ "v19.2", "h75.6", "v38.4" ] ],
|
||||||
|
[ "ultrasonic1:TRIG", "esp:5", "cyan", [ "v9.6", "h105.2", "v115.2", "v28.8" ] ],
|
||||||
|
[ "esp:3V3", "oled1:VCC", "red", [ "h-23.81", "v201.6", "h67.05" ] ],
|
||||||
|
[ "esp:GND.3", "oled1:GND", "black", [ "h14.44", "v144", "h-76.8" ] ],
|
||||||
|
[ "oled1:SDA", "esp:21", "white", [ "v-28.8", "h134.47", "v-134.4" ] ],
|
||||||
|
[ "oled1:SCL", "esp:22", "violet", [ "v9.6", "h77.1", "v-201.6" ] ],
|
||||||
|
[ "imu1:VCC", "esp:3V3", "red", [ "v-9.6", "h67.28", "v57.6", "h57.6", "v67.2" ] ],
|
||||||
|
[ "imu1:GND", "esp:GND.1", "black", [ "v-19.2", "h48.08", "v105.6", "h67.2", "v153.6" ] ],
|
||||||
|
[ "imu1:SCL", "esp:22", "violet", [ "v-28.8", "h96.08", "v67.2", "h220.8", "v96" ] ],
|
||||||
|
[
|
||||||
|
"imu1:SDA",
|
||||||
|
"esp:21",
|
||||||
|
"white",
|
||||||
|
[
|
||||||
|
"v-28.8",
|
||||||
|
"h-57.52",
|
||||||
|
"v105.6",
|
||||||
|
"h48",
|
||||||
|
"v153.6",
|
||||||
|
"h124.8",
|
||||||
|
"v201.6",
|
||||||
|
"h326.4",
|
||||||
|
"v-172.8",
|
||||||
|
"h-67.2",
|
||||||
|
"v-96"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[ "esp:27", "btn1:2.r", "cyan", [ "h-71.81", "h-60.4" ] ],
|
||||||
|
[ "btn1:1.l", "esp:GND.1", "black", [ "v-38.4", "h67.2", "v153.6" ] ],
|
||||||
|
[ "ultrasonic1:ECHO", "esp:18", "green", [ "v28.8", "h76", "v115.2" ] ]
|
||||||
|
],
|
||||||
|
"dependencies": {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import machine, time
|
||||||
|
from machine import Pin
|
||||||
|
|
||||||
|
# From https://github.com/udacity/cd13765-deci-emb-sys-l4-term2-project.git
|
||||||
|
|
||||||
|
class HCSR04:
|
||||||
|
# echo_timeout_us is based in chip range limit (400cm)
|
||||||
|
def __init__(self, trigger_pin, echo_pin, echo_timeout_us=500*2*30):
|
||||||
|
"""
|
||||||
|
trigger_pin : Output pin to send pulses
|
||||||
|
echo_pin : Readonly pin to measure the distance. The pin should be protected with 1k resistor
|
||||||
|
echo_timeout_us : Timeout in microseconds to listen to echo pin.
|
||||||
|
By default is based in sensor limit range (4m)
|
||||||
|
"""
|
||||||
|
self.echo_timeout_us = echo_timeout_us
|
||||||
|
# Init trigger pin (out)
|
||||||
|
self.trigger = Pin(trigger_pin, mode=Pin.OUT, pull=None)
|
||||||
|
self.trigger.value(0)
|
||||||
|
# Init echo pin (in)
|
||||||
|
self.echo = Pin(echo_pin, mode=Pin.IN, pull=None)
|
||||||
|
|
||||||
|
def _send_pulse_and_wait(self):
|
||||||
|
"""
|
||||||
|
Send the pulse to trigger and listen on echo pin.
|
||||||
|
We use the method `machine.time_pulse_us()` to get the microseconds until the echo is received.
|
||||||
|
"""
|
||||||
|
self.trigger.value(0) # Stabilize the sensor
|
||||||
|
time.sleep_us(5)
|
||||||
|
self.trigger.value(1)
|
||||||
|
# Send a 10us pulse.
|
||||||
|
time.sleep_us(10)
|
||||||
|
self.trigger.value(0)
|
||||||
|
try:
|
||||||
|
pulse_time = machine.time_pulse_us(self.echo, 1, self.echo_timeout_us)
|
||||||
|
return pulse_time
|
||||||
|
except OSError as ex:
|
||||||
|
if ex.args[0] == 110: # 110 = ETIMEDOUT
|
||||||
|
raise OSError("Out of range")
|
||||||
|
raise ex
|
||||||
|
|
||||||
|
def distance_cm(self):
|
||||||
|
pulse_time = self._send_pulse_and_wait()
|
||||||
|
# To calculate the distance we get the pulse_time and divide it by 2
|
||||||
|
# (the pulse walk the distance twice) and by 29.1 becasue
|
||||||
|
# the sound speed on air (343.2 m/s), that It's equivalent to
|
||||||
|
# 0.034320 cm/us that is 1cm each 29.1us
|
||||||
|
cms = int((pulse_time / 2) // 29.1)
|
||||||
|
return cms
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 147 KiB |
@@ -0,0 +1,18 @@
|
|||||||
|
from utime import sleep
|
||||||
|
from machine import Pin
|
||||||
|
|
||||||
|
class LED:
|
||||||
|
def __init__(self, pin):
|
||||||
|
self.pin = Pin(pin, mode=Pin.OUT, pull=None)
|
||||||
|
self.pin.value(0)
|
||||||
|
|
||||||
|
def on(self):
|
||||||
|
self.pin.value(1)
|
||||||
|
|
||||||
|
def off(self):
|
||||||
|
self.pin.value(0)
|
||||||
|
|
||||||
|
def blink(self, time):
|
||||||
|
self.on()
|
||||||
|
sleep(time)
|
||||||
|
self.off()
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
from machine import SoftI2C, Pin, time_pulse_us
|
||||||
|
from utime import sleep
|
||||||
|
|
||||||
|
from led import LED
|
||||||
|
from button import Button
|
||||||
|
from buzzer import Buzzer
|
||||||
|
from stepper import Stepper
|
||||||
|
from hcsr04 import HCSR04
|
||||||
|
import oled
|
||||||
|
import mpu6050
|
||||||
|
|
||||||
|
led = LED(12) # D12
|
||||||
|
button = Button(27) # D27
|
||||||
|
buzzer = Buzzer(15) # D15
|
||||||
|
leftWheel = Stepper(13) # Step: D13
|
||||||
|
rightWheel = Stepper(19) # Step: D19
|
||||||
|
ultrasonic = HCSR04(5, 18) # Trigger: D5, Echo: 18
|
||||||
|
|
||||||
|
i2c = SoftI2C(scl=Pin(22), sda=Pin(21))
|
||||||
|
oled = oled.I2C(128, 64, i2c)
|
||||||
|
mpu = mpu6050.accel(i2c)
|
||||||
|
|
||||||
|
# 1 cm -> 11 steps
|
||||||
|
# 10 cm -> 106 steps
|
||||||
|
# 49 cm -> 520 steps
|
||||||
|
def get_steps_from_distance(distance):
|
||||||
|
wheel_diameter = 6 # centimeters
|
||||||
|
wheel_circumference = wheel_diameter * 3.14159265
|
||||||
|
steps_per_revolution = 200
|
||||||
|
return int(
|
||||||
|
distance * steps_per_revolution / wheel_circumference
|
||||||
|
)
|
||||||
|
|
||||||
|
# ------------------------- Part 1: Test -------------------------
|
||||||
|
|
||||||
|
# while True:
|
||||||
|
# value = button.pin.value()
|
||||||
|
# print(value)
|
||||||
|
# if button.is_pressed():
|
||||||
|
# led.on()
|
||||||
|
# buzzer.beep_once()
|
||||||
|
# else:
|
||||||
|
# led.off()
|
||||||
|
# sleep(0.5)
|
||||||
|
|
||||||
|
# while True:
|
||||||
|
# led.off()
|
||||||
|
# # Program pauses here until the button is pressed
|
||||||
|
# button.wait_button_press()
|
||||||
|
# led.on()
|
||||||
|
# buzzer.beep_once()
|
||||||
|
# # Wait until the button is released before restarting
|
||||||
|
# while button.is_pressed():
|
||||||
|
# sleep(0.01)
|
||||||
|
|
||||||
|
# ------------------------- Part 2: Test -------------------------
|
||||||
|
|
||||||
|
# while True:
|
||||||
|
# if button.is_pressed():
|
||||||
|
# # Opposite directions make the two robot wheels move forward
|
||||||
|
# leftWheel.move_one_step()
|
||||||
|
# rightWheel.move_one_step()
|
||||||
|
# else:
|
||||||
|
# sleep(0.01)
|
||||||
|
|
||||||
|
# ------------------------- Part 3: Test -------------------------
|
||||||
|
|
||||||
|
# while True:
|
||||||
|
# try:
|
||||||
|
# distance = ultrasonic.distance_cm()
|
||||||
|
# print("Distance:", distance, "cm")
|
||||||
|
# except OSError as error:
|
||||||
|
# print("Ultrasonic error:", error)
|
||||||
|
# sleep(0.5)
|
||||||
|
|
||||||
|
# test_distances = (10, 20, 30, 50, 100)
|
||||||
|
# for distance in test_distances:
|
||||||
|
# steps = get_steps_from_distance(distance)
|
||||||
|
# print(distance, "cm =", steps, "steps")
|
||||||
|
|
||||||
|
# while True:
|
||||||
|
# led.off()
|
||||||
|
# print("Press the button to measure and move")
|
||||||
|
# button.wait_button_press()
|
||||||
|
# try:
|
||||||
|
# distance = ultrasonic.distance_cm()
|
||||||
|
# target_steps = get_steps_from_distance(distance)
|
||||||
|
# print("Distance:", distance, "cm")
|
||||||
|
# print("Target steps:", target_steps)
|
||||||
|
# led.on()
|
||||||
|
# for step in range(target_steps):
|
||||||
|
# leftWheel.move_one_step()
|
||||||
|
# rightWheel.move_one_step()
|
||||||
|
# buzzer.beep_once()
|
||||||
|
# print("Movement complete")
|
||||||
|
# except OSError as error:
|
||||||
|
# print("Ultrasonic error:", error)
|
||||||
|
# # Prevent another measurement while the button remains held
|
||||||
|
# while button.is_pressed():
|
||||||
|
# sleep(0.01)
|
||||||
|
|
||||||
|
# ------------------------- Part 4: Test -------------------------
|
||||||
|
|
||||||
|
# print("I2C devices:", [hex(address) for address in i2c.scan()])
|
||||||
|
# I2C devices: ['0x3c', '0x68']
|
||||||
|
|
||||||
|
# OLED test
|
||||||
|
# while True:
|
||||||
|
# try:
|
||||||
|
# distance = ultrasonic.distance_cm()
|
||||||
|
# print("Distance:", distance, "cm")
|
||||||
|
# oled.clear()
|
||||||
|
# oled.text("Distance:", 0, 1)
|
||||||
|
# oled.text(str(distance) + " cm", 0, 2)
|
||||||
|
# oled.show()
|
||||||
|
# except OSError as error:
|
||||||
|
# print("Ultrasonic error:", error)
|
||||||
|
# oled.clear()
|
||||||
|
# oled.text("Sensor error", 0, 2)
|
||||||
|
# oled.show()
|
||||||
|
# sleep(0.5)
|
||||||
|
|
||||||
|
# MPU6050 test
|
||||||
|
# while True:
|
||||||
|
# values = mpu.get_values()
|
||||||
|
# ac_y = values["AcY"]
|
||||||
|
# print("AcY:", ac_y)
|
||||||
|
# oled.clear()
|
||||||
|
# oled.text("Accelerometer", 0, 1)
|
||||||
|
# oled.text("AcY:", 0, 2)
|
||||||
|
# oled.text(str(ac_y), 0, 3)
|
||||||
|
# oled.show()
|
||||||
|
# sleep(0.25)
|
||||||
|
|
||||||
|
# Combined test
|
||||||
|
# while True:
|
||||||
|
# try:
|
||||||
|
# distance = ultrasonic.distance_cm()
|
||||||
|
# values = mpu.get_values()
|
||||||
|
# ac_y = values["AcY"]
|
||||||
|
# print("Distance:", distance, "cm")
|
||||||
|
# print("AcY:", ac_y)
|
||||||
|
# oled.clear()
|
||||||
|
# oled.text("Distance: " + str(distance), 0, 1)
|
||||||
|
# oled.text("cm", 0, 2)
|
||||||
|
# oled.text("AcY: " + str(ac_y), 0, 4)
|
||||||
|
# oled.show()
|
||||||
|
# except OSError as error:
|
||||||
|
# print("Ultrasonic error:", error)
|
||||||
|
# sleep(0.5)
|
||||||
|
|
||||||
|
# ------------------------- Final Project -------------------------
|
||||||
|
|
||||||
|
while True:
|
||||||
|
|
||||||
|
# Set LED to off, # IDLE state
|
||||||
|
led.off()
|
||||||
|
|
||||||
|
oled.clear()
|
||||||
|
oled.text("Press button", 0, 2)
|
||||||
|
oled.text("To Start", 0, 3)
|
||||||
|
oled.show()
|
||||||
|
|
||||||
|
# Wait here until the button is pressed
|
||||||
|
button.wait_button_press()
|
||||||
|
buzzer.beep_once()
|
||||||
|
|
||||||
|
# Read the ultrasonic distance
|
||||||
|
try:
|
||||||
|
distance = ultrasonic.distance_cm()
|
||||||
|
except OSError:
|
||||||
|
display.clear()
|
||||||
|
display.text("Sensor error", 0, 3)
|
||||||
|
display.show()
|
||||||
|
sleep(3)
|
||||||
|
# Require the button to be released
|
||||||
|
while button.is_pressed():
|
||||||
|
sleep(0.01)
|
||||||
|
continue
|
||||||
|
|
||||||
|
target_steps = get_steps_from_distance(distance)
|
||||||
|
|
||||||
|
# Display the measured distance and steps
|
||||||
|
oled.clear()
|
||||||
|
oled.text("Distance: " + str(distance), 0, 2)
|
||||||
|
oled.text("Steps: " + str(target_steps), 0, 3)
|
||||||
|
oled.show()
|
||||||
|
|
||||||
|
reached = True
|
||||||
|
|
||||||
|
# Drive the required number of steps
|
||||||
|
for _ in range(target_steps):
|
||||||
|
leftWheel.move_one_step()
|
||||||
|
rightWheel.move_one_step()
|
||||||
|
|
||||||
|
# Check the accelerometer after each step
|
||||||
|
values = mpu.get_values()
|
||||||
|
ac_y = values["AcY"]
|
||||||
|
|
||||||
|
if ac_y > 12000 or ac_y < -12000:
|
||||||
|
reached = False
|
||||||
|
break
|
||||||
|
|
||||||
|
oled.clear()
|
||||||
|
|
||||||
|
if reached:
|
||||||
|
oled.text("REACHED", 0, 3)
|
||||||
|
oled.show()
|
||||||
|
buzzer.beep_once()
|
||||||
|
else:
|
||||||
|
led.on()
|
||||||
|
oled.text("TILTED", 0, 3)
|
||||||
|
oled.show()
|
||||||
|
# Beep three times
|
||||||
|
for beep in range(3):
|
||||||
|
buzzer.beep_once()
|
||||||
|
if beep < 2:
|
||||||
|
sleep(0.2)
|
||||||
|
|
||||||
|
# Keep the result visible
|
||||||
|
sleep(3)
|
||||||
|
|
||||||
|
# Require a new button press for the next run
|
||||||
|
while button.is_pressed():
|
||||||
|
sleep(0.01)
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
import machine
|
||||||
|
|
||||||
|
# From https://github.com/udacity/cd13765-deci-emb-sys-l4-term2-project.git
|
||||||
|
|
||||||
|
class accel:
|
||||||
|
def __init__(self, i2c, addr=0x68):
|
||||||
|
self.iic = i2c
|
||||||
|
self.addr = addr
|
||||||
|
self.iic.start()
|
||||||
|
self.iic.writeto(self.addr, bytearray([107, 0]))
|
||||||
|
self.iic.stop()
|
||||||
|
|
||||||
|
def get_raw_values(self):
|
||||||
|
self.iic.start()
|
||||||
|
a = self.iic.readfrom_mem(self.addr, 0x3B, 14)
|
||||||
|
self.iic.stop()
|
||||||
|
return a
|
||||||
|
|
||||||
|
def get_ints(self):
|
||||||
|
b = self.get_raw_values()
|
||||||
|
c = []
|
||||||
|
for i in b:
|
||||||
|
c.append(i)
|
||||||
|
return c
|
||||||
|
|
||||||
|
def bytes_toint(self, firstbyte, secondbyte):
|
||||||
|
if not firstbyte & 0x80:
|
||||||
|
return firstbyte << 8 | secondbyte
|
||||||
|
return -(((firstbyte ^ 255) << 8) | (secondbyte ^ 255) + 1)
|
||||||
|
|
||||||
|
def get_values(self):
|
||||||
|
raw_ints = self.get_raw_values()
|
||||||
|
vals = {}
|
||||||
|
vals["AcX"] = self.bytes_toint(raw_ints[0], raw_ints[1])
|
||||||
|
vals["AcY"] = self.bytes_toint(raw_ints[2], raw_ints[3])
|
||||||
|
vals["AcZ"] = self.bytes_toint(raw_ints[4], raw_ints[5])
|
||||||
|
vals["Tmp"] = self.bytes_toint(raw_ints[6], raw_ints[7]) / 340.00 + 36.53
|
||||||
|
vals["GyX"] = self.bytes_toint(raw_ints[8], raw_ints[9])
|
||||||
|
vals["GyY"] = self.bytes_toint(raw_ints[10], raw_ints[11])
|
||||||
|
vals["GyZ"] = self.bytes_toint(raw_ints[12], raw_ints[13])
|
||||||
|
return vals
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
#MicroPython SSD1306 OLED driver, I2C and SPI interfaces created by Adafruit
|
||||||
|
|
||||||
|
import time
|
||||||
|
import framebuf
|
||||||
|
|
||||||
|
# From https://github.com/udacity/cd13765-deci-emb-sys-l4-term2-project.git
|
||||||
|
|
||||||
|
# register definitions
|
||||||
|
SET_CONTRAST = const(0x81)
|
||||||
|
SET_ENTIRE_ON = const(0xa4)
|
||||||
|
SET_NORM_INV = const(0xa6)
|
||||||
|
SET_DISP = const(0xae)
|
||||||
|
SET_MEM_ADDR = const(0x20)
|
||||||
|
SET_COL_ADDR = const(0x21)
|
||||||
|
SET_PAGE_ADDR = const(0x22)
|
||||||
|
SET_DISP_START_LINE = const(0x40)
|
||||||
|
SET_SEG_REMAP = const(0xa0)
|
||||||
|
SET_MUX_RATIO = const(0xa8)
|
||||||
|
SET_COM_OUT_DIR = const(0xc0)
|
||||||
|
SET_DISP_OFFSET = const(0xd3)
|
||||||
|
SET_COM_PIN_CFG = const(0xda)
|
||||||
|
SET_DISP_CLK_DIV = const(0xd5)
|
||||||
|
SET_PRECHARGE = const(0xd9)
|
||||||
|
SET_VCOM_DESEL = const(0xdb)
|
||||||
|
SET_CHARGE_PUMP = const(0x8d)
|
||||||
|
|
||||||
|
|
||||||
|
class OLED:
|
||||||
|
def __init__(self, width, height, external_vcc):
|
||||||
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
self.external_vcc = external_vcc
|
||||||
|
self.pages = self.height // 8
|
||||||
|
# Note the subclass must initialize self.framebuf to a framebuffer.
|
||||||
|
# This is necessary because the underlying data buffer is different
|
||||||
|
# between I2C and SPI implementations (I2C needs an extra byte).
|
||||||
|
self.poweron()
|
||||||
|
self.init_display()
|
||||||
|
|
||||||
|
def init_display(self):
|
||||||
|
for cmd in (
|
||||||
|
SET_DISP | 0x00, # off
|
||||||
|
# address setting
|
||||||
|
SET_MEM_ADDR, 0x00, # horizontal
|
||||||
|
# resolution and layout
|
||||||
|
SET_DISP_START_LINE | 0x00,
|
||||||
|
SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0
|
||||||
|
SET_MUX_RATIO, self.height - 1,
|
||||||
|
SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0
|
||||||
|
SET_DISP_OFFSET, 0x00,
|
||||||
|
SET_COM_PIN_CFG, 0x02 if self.height == 32 else 0x12,
|
||||||
|
# timing and driving scheme
|
||||||
|
SET_DISP_CLK_DIV, 0x80,
|
||||||
|
SET_PRECHARGE, 0x22 if self.external_vcc else 0xf1,
|
||||||
|
SET_VCOM_DESEL, 0x30, # 0.83*Vcc
|
||||||
|
# display
|
||||||
|
SET_CONTRAST, 0xff, # maximum
|
||||||
|
SET_ENTIRE_ON, # output follows RAM contents
|
||||||
|
SET_NORM_INV, # not inverted
|
||||||
|
# charge pump
|
||||||
|
SET_CHARGE_PUMP, 0x10 if self.external_vcc else 0x14,
|
||||||
|
SET_DISP | 0x01): # on
|
||||||
|
self.write_cmd(cmd)
|
||||||
|
self.fill(0)
|
||||||
|
self.show()
|
||||||
|
|
||||||
|
def poweroff(self):
|
||||||
|
self.write_cmd(SET_DISP | 0x00)
|
||||||
|
|
||||||
|
def contrast(self, contrast):
|
||||||
|
self.write_cmd(SET_CONTRAST)
|
||||||
|
self.write_cmd(contrast)
|
||||||
|
|
||||||
|
def invert(self, invert):
|
||||||
|
self.write_cmd(SET_NORM_INV | (invert & 1))
|
||||||
|
|
||||||
|
def show(self):
|
||||||
|
x0 = 0
|
||||||
|
x1 = self.width - 1
|
||||||
|
if self.width == 64:
|
||||||
|
# displays with width of 64 pixels are shifted by 32
|
||||||
|
x0 += 32
|
||||||
|
x1 += 32
|
||||||
|
self.write_cmd(SET_COL_ADDR)
|
||||||
|
self.write_cmd(x0)
|
||||||
|
self.write_cmd(x1)
|
||||||
|
self.write_cmd(SET_PAGE_ADDR)
|
||||||
|
self.write_cmd(0)
|
||||||
|
self.write_cmd(self.pages - 1)
|
||||||
|
self.write_framebuf()
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self.fill(0)
|
||||||
|
|
||||||
|
def fill(self, col):
|
||||||
|
self.framebuf.fill(col)
|
||||||
|
|
||||||
|
def pixel(self, x, y, col):
|
||||||
|
self.framebuf.pixel(x, y, col)
|
||||||
|
|
||||||
|
def scroll(self, dx, dy):
|
||||||
|
self.framebuf.scroll(dx, dy)
|
||||||
|
|
||||||
|
def text(self, string, x, y, col=1):
|
||||||
|
self.framebuf.text(string, x, y * 10, col)
|
||||||
|
|
||||||
|
def rect(self, x, y, w, h, fill=False, col=1):
|
||||||
|
if (fill):
|
||||||
|
self.framebuf.fill_rect(x, y, w, h, col)
|
||||||
|
else:
|
||||||
|
self.framebuf.rect(x, y, w, h, col)
|
||||||
|
|
||||||
|
|
||||||
|
class I2C(OLED):
|
||||||
|
def __init__(self, width, height, i2c, addr=0x3c, external_vcc=False):
|
||||||
|
self.i2c = i2c
|
||||||
|
self.addr = addr
|
||||||
|
self.temp = bytearray(2)
|
||||||
|
# Add an extra byte to the data buffer to hold an I2C data/command byte
|
||||||
|
# to use hardware-compatible I2C transactions. A memoryview of the
|
||||||
|
# buffer is used to mask this byte from the framebuffer operations
|
||||||
|
# (without a major memory hit as memoryview doesn't copy to a separate
|
||||||
|
# buffer).
|
||||||
|
self.buffer = bytearray(((height // 8) * width) + 1)
|
||||||
|
self.buffer[0] = 0x40 # Set first byte of data buffer to Co=0, D/C=1
|
||||||
|
self.framebuf = framebuf.FrameBuffer1(memoryview(self.buffer)[1:], width, height)
|
||||||
|
super().__init__(width, height, external_vcc)
|
||||||
|
|
||||||
|
def write_cmd(self, cmd):
|
||||||
|
self.temp[0] = 0x80 # Co=1, D/C#=0
|
||||||
|
self.temp[1] = cmd
|
||||||
|
self.i2c.writeto(self.addr, self.temp)
|
||||||
|
|
||||||
|
def write_framebuf(self):
|
||||||
|
# Blast out the frame buffer using a single I2C transaction to support
|
||||||
|
# hardware I2C interfaces.
|
||||||
|
self.i2c.writeto(self.addr, self.buffer)
|
||||||
|
|
||||||
|
def poweron(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SPI(OLED):
|
||||||
|
def __init__(self, width, height, spi, dc, res, cs, external_vcc=False):
|
||||||
|
self.rate = 10 * 1024 * 1024
|
||||||
|
dc.init(dc.OUT, value=0)
|
||||||
|
res.init(res.OUT, value=0)
|
||||||
|
cs.init(cs.OUT, value=1)
|
||||||
|
self.spi = spi
|
||||||
|
self.dc = dc
|
||||||
|
self.res = res
|
||||||
|
self.cs = cs
|
||||||
|
self.buffer = bytearray((height // 8) * width)
|
||||||
|
self.framebuf = framebuf.FrameBuffer1(self.buffer, width, height)
|
||||||
|
super().__init__(width, height, external_vcc)
|
||||||
|
|
||||||
|
def write_cmd(self, cmd):
|
||||||
|
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
|
||||||
|
self.cs.high()
|
||||||
|
self.dc.low()
|
||||||
|
self.cs.low()
|
||||||
|
self.spi.write(bytearray([cmd]))
|
||||||
|
self.cs.high()
|
||||||
|
|
||||||
|
def write_framebuf(self):
|
||||||
|
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
|
||||||
|
self.cs.high()
|
||||||
|
self.dc.high()
|
||||||
|
self.cs.low()
|
||||||
|
self.spi.write(self.buffer)
|
||||||
|
self.cs.high()
|
||||||
|
|
||||||
|
def poweron(self):
|
||||||
|
self.res.high()
|
||||||
|
time.sleep_ms(1)
|
||||||
|
self.res.low()
|
||||||
|
time.sleep_ms(10)
|
||||||
|
self.res.high()
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
import machine, utime
|
||||||
|
from machine import Pin
|
||||||
|
|
||||||
|
# From https://github.com/udacity/cd13765-deci-emb-sys-l4-term2-project.git
|
||||||
|
|
||||||
|
class Stepper:
|
||||||
|
def __init__(self, step_pin):
|
||||||
|
self.step_pin = Pin(step_pin, mode=Pin.OUT, pull=None)
|
||||||
|
|
||||||
|
def move_one_step(self):
|
||||||
|
self.step_pin.value(True)
|
||||||
|
utime.sleep(0.001)
|
||||||
|
self.step_pin.value(False)
|
||||||
|
utime.sleep(0.001)
|
||||||
|
|
||||||
Reference in New Issue
Block a user