diff --git a/.github/workflows/pages.yaml b/.github/workflows/pages.yaml new file mode 100644 index 0000000..47245b7 --- /dev/null +++ b/.github/workflows/pages.yaml @@ -0,0 +1,76 @@ +# Sample workflow for building and deploying a Hugo site to GitHub Pages +name: Deploy Hugo site to Pages + +on: + # Runs on pushes targeting the default branch + push: + branches: ["main"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +# Default to bash +defaults: + run: + shell: bash + +jobs: + # Build job + build: + runs-on: ubuntu-latest + env: + HUGO_VERSION: 0.126.3 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 # fetch all history for .GitInfo and .Lastmod + submodules: recursive + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: '1.22' + - name: Setup Pages + id: pages + uses: actions/configure-pages@v4 + - name: Setup Hugo + run: | + wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \ + && sudo dpkg -i ${{ runner.temp }}/hugo.deb + - name: Build with Hugo + env: + # For maximum backward compatibility with Hugo modules + HUGO_ENVIRONMENT: production + HUGO_ENV: production + run: | + hugo \ + --gc --minify \ + --baseURL "${{ steps.pages.outputs.base_url }}/" + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: ./public + + # Deployment job + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ed5e96e --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# Hugo output +public/ +resources/ +.hugo_build.lock + +# Editor +.vscode/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..946f304 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Xin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..84b11b2 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# ECE 196 Website + +The ECE 196 website, visit [here](ece-196.github.io/site). diff --git a/content/_index.md b/content/_index.md new file mode 100644 index 0000000..8ab718d --- /dev/null +++ b/content/_index.md @@ -0,0 +1,11 @@ +--- +title: ECE 196 Technical Documentation +toc: false +--- + +{{< cards >}} + {{< card link="about" title="About" icon="information-circle" >}} + {{< card link="projects" title="Projects" icon="code" >}} + {{< card link="assignments" title="Assignments" icon="book-open" >}} + {{< card link="additional-resources" title="Resources" icon="external-link" >}} +{{< /cards >}} diff --git a/content/about.md b/content/about.md new file mode 100644 index 0000000..e9fa3c8 --- /dev/null +++ b/content/about.md @@ -0,0 +1,8 @@ +--- +title: About +type: about +--- + +Karcher... about. + +## Tutors diff --git a/content/additional-resources/_index.md b/content/additional-resources/_index.md new file mode 100644 index 0000000..57d64ce --- /dev/null +++ b/content/additional-resources/_index.md @@ -0,0 +1,11 @@ +--- +title: Resources +toc: false +--- + +Some useful external resources. + +{{< cards >}} + {{< card link="arduino" title="Arduino" >}} + {{< card link="swiftui" title="SwiftUI" subtitle="Developing idiomatic SwiftUI apps and using BLE.">}} +{{< /cards >}} diff --git a/content/additional-resources/arduino.md b/content/additional-resources/arduino.md new file mode 100644 index 0000000..697d597 --- /dev/null +++ b/content/additional-resources/arduino.md @@ -0,0 +1,7 @@ +--- +title: Arduino +type: docs +--- + + +TODO... diff --git a/content/additional-resources/swiftui.md b/content/additional-resources/swiftui.md new file mode 100644 index 0000000..09090e1 --- /dev/null +++ b/content/additional-resources/swiftui.md @@ -0,0 +1,35 @@ +--- +title: SwiftUI +type: docs +--- + +![](https://developer.apple.com/assets/elements/icons/swiftui/swiftui-96x96_2x.png) +## Getting Started with SwiftUI - ECE 196 + +### What is SwiftUI? + +**SwiftUI** is a framework for developing applications for any Apple device. SwiftUI is written in the language **Swift**, hence the name. + +### Download XCode + +XCode is the [IDE](https://www.codecademy.com/article/what-is-an-ide) that you will use to develop Swift apps. +XCode allows you to write code, preview your app, debug, and deploy to your Apple devices. + +You can download XCode [here](https://developer.apple.com/xcode/). + +### Resources +#### Official +- [Apple SwiftUI Tutorials](https://developer.apple.com/tutorials/swiftui) +- [SwiftUI Documentation](https://developer.apple.com/documentation/swiftui/) +- [Develop In Swift](https://developer.apple.com/tutorials/develop-in-swift/) (New!) + +#### 3rd Party +- [Hacking with Swift - 100 Days of SwiftUI](https://www.hackingwithswift.com/100/swiftui): Will introduce you to the Swift language, then ease you into using SwiftUI. +- [Hacking with Swift - SwiftUI by Example](https://www.hackingwithswift.com/quick-start/swiftui): A large collection of SwiftUI idioms, examples of common things you may want to do. +- [SwiftOnTap](https://swiftontap.com): SwiftUI database, gifs showing off how SwiftUI components work and look. + +## BLE + +It is common for teams to want to develop an iOS app that communicates with their device via BLE. + +An old EnVision tutorial for creating a simple RGB lamp can be found [here](https://github.com/AdinAck/EnVision-Tutorial-Lamp). This tutorial goes over setting up a Swift app to use BLE and writing Arduino BLE code for the device. diff --git a/content/assignments/ControlWithPython/_index.md b/content/assignments/ControlWithPython/_index.md new file mode 100644 index 0000000..63bffd5 --- /dev/null +++ b/content/assignments/ControlWithPython/_index.md @@ -0,0 +1,15 @@ +--- +title: ControlWIthPython +type: docs +prev: assignments/VUMeter/ +next: assignments/ControlWithPython/tutorial +weight: 4 +--- + +In this assignment you will learn to create a **G**raphical **U**ser **I**nterface (GUI) to interact with a microcontroller. + +We will be migrating from CircuitPython to the Arduino C ecosystem. + +Download the Arduino software [here](https://www.arduino.cc/en/software). + +If you are unfamiliar with Arduino, the official [Arduino website](https://www.arduino.cc/reference/en/) contains great resources for learning about embedded development. You may also want to take a look at the [reference page](https://www.arduino.cc/reference/en/) for help with Arduino's syntax and language. diff --git a/content/assignments/ControlWithPython/quiz.md b/content/assignments/ControlWithPython/quiz.md new file mode 100644 index 0000000..ae187d5 --- /dev/null +++ b/content/assignments/ControlWithPython/quiz.md @@ -0,0 +1,16 @@ +--- +title: Quiz +type: docs +prev: assignments/ControlWithPython/tutorial +weight: 2 +--- + +This assignment is the first to have a quiz! + +You can find the quiz in the root directory of the assignment repo, named `QUIZ.md`. The questions are there, you just need to fill in the answers. + +If you need hints, feel free to talk to the TAs. + +{{< callout type="warning" >}} + You may discuss with your peers, but **do not** copy each other. +{{< /callout >}} diff --git a/content/assignments/ControlWithPython/submission.md b/content/assignments/ControlWithPython/submission.md new file mode 100644 index 0000000..00f41e4 --- /dev/null +++ b/content/assignments/ControlWithPython/submission.md @@ -0,0 +1,9 @@ +--- +title: Submission +type: docs +weight: 3 +--- + +1. Completed code (in `app.py`). +1. Answers in `QUIZ.md`. +1. Video of LED being controlled by the Python GUI named `submission.*` placed in root of this repository. diff --git a/content/assignments/ControlWithPython/tutorial.md b/content/assignments/ControlWithPython/tutorial.md new file mode 100644 index 0000000..625732e --- /dev/null +++ b/content/assignments/ControlWithPython/tutorial.md @@ -0,0 +1,637 @@ +--- +title: Tutorial +type: docs +prev: assignments/ControlWithPython/ +weight: 1 +--- + +Let's take a look at the hierarchy of the system we are about to design: + +```mermaid +graph LR + subgraph "DevBoard" + subgraph "Front End" + A2(LED) + end + + subgraph "Back End" + B2(Serial) + C2(Callbacks) + D2(Logic) + end + + B2 --> C2 --> D2 --> A2 + D2 --> B2 + end + + subgraph "Computer (Python)" + subgraph "Front End" + A1(UI) + B1(Alerts) + end + + subgraph "Back End" + C1(Callbacks) + D1(Logic) + E1(Serial) + end + + A1 --> C1 --> D1 <--> E1 + D1 --> B1 + end + + B2 <--> E1 +``` + +Now, I know this looks complicated, but we will tackle each block one by one, nice and slow. + +Once you are done with this, you will have a solid understanding of creating systems involving bidirectional communication between 2 devices, which is _super_ useful for _a ton_ of applications (including your final project). + +So let's get started. + +## Driving an LED + +We'll start simple, and just make a user controllable LED blink once per second. Before you begin, make sure that your Board is configured to the ESP32S3 Dev Module. + +On the DevBoard, the pin of the LED we will use is `17` (LED1). + +Here is the code: + +```c +const int LED{17}; + +void setup() { + pinMode(LED, OUTPUT); +} + +void loop() { + digitalWrite(LED, HIGH); + delay(1000); + digitalWrite(LED, LOW); + delay(1000); +} +``` + +The `setup` function runs once on boot. + +The `loop` function runs over and over again forever. + +## Hello, World! + +Now let's print some messages over the USB connection. + +```c +void setup() { + USBSerial.begin(9600); +} + +void loop() { + USBSerial.println("Hello, World!"); + delay(1000); +} +``` + +This prints `Hello, World!` every second. But how can we see it? + +If you are using the Arduino IDE, you can open the serial monitor, set the [baudrate](https://www.setra.com/blog/what-is-baud-rate-and-what-cable-length-is-required-1) to `9600`, and watch the hello's flow in! + +Another way we can read these messages is with Python! + +Python has a library called `pyserial`. + +To install it in your active Python environment, simply run: + +``` +python -m pip install pyserial +``` + +...in your terminal. + +{{< callout type="info" >}} + You can use Arduino IDE to determine the name of the port your DevBoard is on. +{{< /callout >}} + +You can now open a Python file or REPL and write/run the following code: + +```python +from serial import Serial, SerialException + +with Serial('/your/port', 9600) as ser: + while True: + print(ser.readline().decode()) +``` + +`.readline()` accumulates bytes until the newline (`\n` ) byte is received. + +We use `.decode()` because `.readline()` returns `bytes` which can be _decoded_ into a string. + +## Serial LED + +Ok, so we can blink an LED, and we can send bytes from the DevBoard to our computer, but what about the other way 'round? + +In order to control the LED from our computer, we need to send messages the other way. + +To do this, we first set up _receiving_ bytes over serial on the DevBoard: + +### Interrupts + +An _interrupt_ is an event driven signal that runs code. + +In our case, an event we care to _handle_ is if we receive a byte over serial. + +Luckily, this event is available to us, it's called `ARDUINO_HW_CDC_RX_EVENT`. + +Wow, what it's trying to say is that if the Serial port's _receive buffer_ is not empty, this event will be triggered. + +So let's define a function we want to be called when that event is triggered (we receive a byte): + +```c +void on_receive(void* event_handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data) { ... } +``` + +> The arguments of this function are defined by the type `esp_event_handler_t`. You can refer to Espressif's [documentation](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/esp_event.html) to see more. + +Then, we register the interrupt with the `USBSerial` peripheral in our `setup()` like so: + +```c +void setup() { + pinMode(LED, OUTPUT); + + // register "on_receive" as callback for RX event + USBSerial.onEvent(ARDUINO_HW_CDC_RX_EVENT, on_receive); + USBSerial.begin(9600); +} +``` + +Ok, we also still configure the LED to be an output, great. + +Oh, and we correspond the byte received event to our function, nice! + +### Serial + +So, what should be in our `on_receive` function? + +Well, the first thing we need to do is get the data from the Serial port's buffer: + +```c +// read one byte +int state = USBSerial.read(); +``` + +We consider each byte received to be the target LED state (_sent by the computer_). + +Then we need to do some validation, we know the LED can only be set to `LOW`, or `HIGH`, so we need to check the received byte is equal to either of those: + +```c +// guard byte is valid LED state +if (!(state == LOW || state == HIGH)) { + // invalid byte received + // what else should we do? + return; +} +``` + +If we find the byte to be valid, we proceed to updating the LED: + +```c +// update LED with valid state +digitalWrite(LED, state); +``` + +Ok, this is pretty good, let's hop back over to Python. + +Let's try sending the byte `0x1` and see what happens: + +```python +with Serial('/your/port', 9600) as ser: + ser.write(bytes([0x1])) + input() # keep port open to see the LED turn on +``` + +The LED should turn on! + +Ok! This is cool! But... + +### Validation + +What if we send an invalid byte? How could the app know? It _should_ know, right? + +Validation is an important consideration when developing communication systems. So let's add it. + +Let's create two more constants at the top of our file: + +```c +const int LED{17}; + +// add these +const int S_OK{0xaa}; +const int S_ERR{0xff}; +``` + +We can send back one of these depending on the validity of the received data. + +Let's go back and update `on_receive`: + +```c +void on_receive(void* event_handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data) { + // read one byte + int state = USBSerial.read(); + + // guard byte is valid LED state + if (!(state == LOW || state == HIGH)) { + // invalid byte received + // report error + USBSerial.write(S_ERR); + return; + } + + // update LED with valid state + digitalWrite(LED, state); + USBSerial.write(S_OK); +} +``` + +Now whenever the app sends an LED state, it should expect a confirmation response. + +You can try this in Python: + +```python +with Serial('/your/port', 9600) as ser: + ser.write(bytes([0x1])) + print(ser.read() == bytes([0xaa])) + + ser.write(bytes([0x0])) + print(ser.read() == bytes([0xaa])) + + ser.write(bytes([0x2])) + print(ser.read() == bytes([0xff])) +``` + +You should see 3 `True`'s. + +### Loop? + +What happened to `loop()`? What do we need to put in there? + +Well... + +```c +void loop() { } +``` + +Nothing! + +Our code is completely interrupt driven, so the `loop` function need not be populated :\) + +At this point, we have completed half of the system! + +```mermaid +graph LR + subgraph "DevBoard" + subgraph "Front End" + A2(LED) + end + + subgraph "Back End" + B2(Serial) + C2(Callbacks) + D2(Logic) + end + + B2 --> C2 --> D2 --> A2 + D2 --> B2 + end +``` + +## App + +Time for the other half... + +This is going to be a lot, not gonna lie this is going to go pretty deep. + +We need to write code that does the following: + +- Renders the UI +- Handles the serial port +- Connects the UI and serial together + +This may seem trivial at first, but you must realize that Python - by default - is single threaded. This means if we make a _Button_ that sends a message over serial, _by default_ the UI will be **frozen** until that serial code is done executing. + +This is bad, we don't like this, so we will need to take care to design our app with _concurrency_ in mind. + +### UI + +Let's start easy and just set up a simple UI. + +We will be using [Tkinter](https://docs.python.org/3/library/tkinter.html) for this app. + +We start by creating the object that represents our Tkinter session: + +```python +import tkinter as tk +import tkinter.ttk as ttk + +class App(tk.Tk): + def __init__(self): + super().__init__() + + self.title("LED Blinker") + +if __name__ == '__main__': + app = App() + app.mainloop() +``` + +Ok this is pretty neat, when you run this program you should see an empty window appear. + +Let's add our UI elements (called "widgets" in Tkinter): + +```python +def __init__(self): + super().__init__() + + self.title("LED Blinker") + + ttk.Checkbutton(self, text='Toggle LED').pack() + ttk.Button(self, text='Send Invalid').pack() + ttk.Button(self, text='Disconnect', default='active').pack() +``` + +What a nice simple app. + +We have: + +- a checkbox to toggle the LED +- a button to test sending an invalid byte +- a disconnect button + +### Backend + +We now need to implement the missing backend components. + +_How_ do we run code when the checkbox is checked or unchecked? + +Well, `ttk.CheckButton` has two more kwargs: `variable` and `command`. + +It will set the passed `variable` to the state of the checkbox on change, and call the passed `command` on change. + +We need to create both of those though, let's start with the `variable`: + +```python +self.led = tk.BooleanVar() + +ttk.Checkbutton(self, text='Toggle LED', variable=self.led, command=self.update_led).pack() +ttk.Button(self, text='Send Invalid').pack() +ttk.Button(self, text='Disconnect', default='active').pack() +``` + +We create a member variable `led` that represents the state of the checkbox. It is a special Tkinter variable type that can be mutated and observed by Tkinter widgets. + +{{< callout type="info" >}} + We need `led` to be a member variable, so we can access it throughout the lifetime of our `App` instance, even after the constructor finishes. +{{< /callout >}} + +We also need to create the function `self.update_led()`: + +```python +def update_led(self): + value = self.led.get() + + # send `value` somehow?? +``` + +Hmm... so how can we send our message? + +We need to have access to the serial port in our app. Well, just like `led`, we can make a member variable of type `Serial`. + +We can change our `App` class: + +```python +class App(tk.Tk): + ser: Serial + + def __init__(self): + ... +``` + +Oh... but, how do we connect to the serial device? We can't just hardcode `/your/port` because it changes all the time. + +We need to create a menu the user can select their port from and then connect. + +Let's make another class called `SerialPortal` that provides this functionality: + +```python +from serial import Serial +from serial.tools.list_ports import comports + +class SerialPortal(tk.Toplevel): + def __init__(self, parent: App): + super().__init__(parent) + + self.parent = parent + self.parent.withdraw() # hide App until connected + + ttk.OptionMenu(self, parent.port, '', *[d.device for d in comports()]).pack() + ttk.Button(self, text='Connect', command=self.connect, default='active').pack() + + def connect(self): + self.parent.connect() + self.destroy() + self.parent.deiconify() # reveal App +``` + +...and add the new popup window, a `connect` function and `port` variable to `App`: + +```python +class App(tk.Tk): + ser: Serial + + def __init__(self): + super().__init__() + + self.title("LED Blinker") + + self.port = tk.StringVar() # add this + self.led = tk.BooleanVar() + + ttk.Checkbutton(self, text='Toggle LED', variable=self.led, command=self.update_led).pack() + ttk.Button(self, text='Send Invalid').pack() + ttk.Button(self, text='Disconnect', default='active').pack() + + SerialPortal(self) # and this + + # and finally this + def connect(self): + self.ser = Serial(self.port.get()) + + def update_led(self): + value = self.led.get() + + # send `value` somehow?? +``` + +Ok cool, now we can fill in `update_led`: + +```python +def update_led(self): + self.ser.write(bytes([self.led.get()])) +``` + +At this point, checking the LED checkbox should toggle the LED! + +But there is still more to do... + +--- + +Let's finish the backend for the _Send Invalid_ and _Disconnect_ buttons: + +```python +def disconnect(self): + self.ser.close() + + SerialPortal(self) # display portal to reconnect + +def send_invalid(self): + self.ser.write(bytes([0x10])) +``` + +This is pretty great, but we haven't used the response from the DevBoard yet, you'll notice clicking the _Send Invalid_ button does nothing. + +We should standardize writing to the serial port by making one `write` function that all callbacks use. + +First add this import for displaying an alert: + +```python +from tkinter.messagebox import showerror +``` + +Then define the same constants as in our firmware on the DevBoard: + +```python +S_OK: int = 0xaa +S_ERR: int = 0xff +``` + +And finally add the `write` function to `App`: + +```python +def write(self, b: bytes): + try: + self.ser.write(b) + if int.from_bytes(self.ser.read(), 'big') == S_ERR: + showerror('Device Error', 'The device reported an invalid command.') + except SerialException: + showerror('Serial Error', 'Write failed.') +``` + +Now we can change our callbacks that wrote to the serial port to use this: + +```python +def update_led(self): + self.write(bytes([self.led.get()])) + +def send_invalid(self): + self.write(bytes([0x10])) +``` + +And now these will properly handle an `S_ERR` response. + +### Safe Resource Acquisition + +Another thing we need to do is make sure our serial port is properly closed when our app exits. + +We can achieve this by allowing `App` to be constructed in a managed context. + +This is what the [with](https://docs.python.org/3/reference/compound_stmts.html#with) keyword is for in Python. + +To add this support to `App`, we add these functions: + +```python +def __enter__(self): + return self + +def __exit__(self, *_): + self.disconnect() +``` + +And change our main code to guarantee resource release: + +```python +if __name__ == '__main__': + with App() as app: + app.mainloop() +``` + +### Threading + +Right now, if any serial code gets stuck or just takes a long time, our UI will freeze for that time. + +To avoid this, we need to make sure all registered callbacks spawn as a [detached thread](https://www.youtube.com/watch?v=-i8Kzuwr4T4). + +We can make a helper [decorator](https://peps.python.org/pep-0318/) that we can use to mark any functions we want to be spawned in a detached thread: + +```python +from threading import Thread, Lock # we'll use Lock later ;) + +def detached_callback(f): + return lambda *args, **kwargs: Thread(target=f, args=args, kwargs=kwargs).start() +``` + +> Don't worry too hard about understanding this decorator, just know it coerces the function it's applied to into being spawned _into a thread_ upon invocation. + +This decorator can be applied to our callbacks like so: + +```python +@detached_callback +def update_led(self): + self.write(bytes([self.led.get()])) +``` + +We have one final problem to solve... + +It is now possible that multiple threads could try to use the serial port at once, this would cause [undefined behavior](https://en.wikipedia.org/wiki/Undefined_behavior) in the form of a [race condition](https://en.wikipedia.org/wiki/Race_condition). + +To solve this, we need to wrap the `Serial` object in a [lock](https://docs.python.org/3/library/threading.html#threading.Lock). + +We can do this by creating a new type, `LockedSerial`: + +```python +class LockedSerial(Serial): + _lock: Lock = Lock() + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def read(self, size=1) -> bytes: + with self._lock: + return super().read(size) + + def write(self, b: bytes, /) -> int | None: + with self._lock: + super().write(b) + + def close(self): + with self._lock: + super().close() +``` + +Our custom type inherits from `Serial` and overrides the member functions we use with a lock acquisition of the super's implementation. + +Effectively, our type behaves _exactly like the `Serial` type_ but with a lock around every function call. + +Now replace every use of the `Serial` type with `LockedSerial`. + +## What Just Happened + +Ok! That was a lot! But i'm glad you made it :\) + +You now know how to: + +- write interrupt driven firmware +- use the serial peripheral +- digital communication between two devices + - validate data +- create user interfaces with Python + - interaction between front and back end +- threading + - locks diff --git a/content/assignments/DevBoard/_index.md b/content/assignments/DevBoard/_index.md new file mode 100644 index 0000000..e3487bb --- /dev/null +++ b/content/assignments/DevBoard/_index.md @@ -0,0 +1,15 @@ +--- +title: DevBoard +type: docs +prev: assignments/LightShield/ +next: assignments/DevBoard/assembly +weight: 2 +--- + +For this assignment you will be assembling an **EnVision DevBoard**. + +This assignment is the next piece to making your own VUMeter. The DevBoard contains the microcontroller that will control the light shield. + +The DevBoard is _not_ as simple as the previous, and will require additional equipment. This time, you will use a **stencil** to apply **solder paste** _exactly_ on each pad. + +Hope you don't have shaky hands! diff --git a/content/assignments/DevBoard/assembly.md b/content/assignments/DevBoard/assembly.md new file mode 100644 index 0000000..cdfd776 --- /dev/null +++ b/content/assignments/DevBoard/assembly.md @@ -0,0 +1,124 @@ +--- +title: Assembly +type: docs +prev: assignments/DevBoard/ +weight: 1 +--- + +## Video Walkthrough + +There is a video walkthrough for this process [here](https://youtu.be/2PzJOzyYS3M?si=xmUvoAxYBVRmF37I). + +## Step-by-step + +{{% steps %}} + +### Required Materials + +Grab: + +- Jig +- Stencil +- Paste spreader +- Tweezers +- Masking tape +- Solder paste +- DevBoard PCB + +![](images/equipment.png) + +{{< callout type="info" >}} +Write your initials in sharpie on the back of the PCB so you don't get it mixed up with other students in some later steps. +{{< /callout >}} + +You may first need to clean the stencil if it has solder paste on either side. To do this you will need +isopropyl alcohol (found in Envision), and a paper towel. After wetting the paper towel with the alcohol, +gently clean the dirty side(s) of the stencil, being careful not to deform it. + +Once you have all of your tools and a clean stencil, take your PCB and place it _flush_ in the jig. Then, take the +stencil and line it up with the PCB. Once you have it lined up well, you can tape one side of the stencil down. Now take time and +care to line up every hole in the stencil with every pad on the PCB. You should only see metal pads through the stencil holes, with none +of the black substrate visible. Tape the stencil down on the other end, ensuring that the stencil is still lined up. + +Now, take the solder paste and put a ~3mm wide (As wide as the stick of a lollipop) along one of the taped sides. Take your +spreader and, while pressing down on the stencil, spread the solder paste along the stencil. When doing this, make sure you fill +every hole in the stencil without leaving excess paste on the stencil surface. + +| setup | spread | +| ----------------------------- | ------------------------------- | +| ![](images/stencil_setup.png) | ![](images/solder_spreader.png) | + +Once you have solder paste flush in all of the stencil holes, very carefully remove the tape from one side of the stencil and lift the stencil off of the PCB. +You should see a thin layer of solder neatly covering each pad. Make sure that none of the solder paste is bridging pads together. Pay close attention to the USB-C pads, as they have the least clearance. + +If you have any bridging, you will have to clean the PCB and stencil with isopropyl alcohol and try again. Trust me, it is much easier +to restart at this point, rather than trying to deal with the bridging later. If no bridging is present, and every pad has solder paste, then you +can move on to the next step after you clean the stencil. + +### Component Placement + +Now we will start placing our components. + +As you work, you can request the components you need from the TAs at the component table. + +To find out what you need, refer to the **B**ill **O**f **M**aterials [here](https://github.com/ECE-196/DevBoard/blob/main/BOM.xlsx). + +Once you have a component from the B.O.M. you can see which PCB labels the component corresponds to in the _designator_ column. With tweezers, +carefully place each component on to the labeled position on the PCB. Make sure that the components you place are centered on the pads, and be careful not +to smear the paste. + +for the USB-C connector, you can use Collin's trick to smear solder paste on the legs of the connector, so it will be soldered in place after the oven. +If you do not use this trick, you can turn the board around after it's been in the oven, and solder the four mounting points of the USB-C connector manually. + +The orientation of the resistors and capacitors does not matter. There are components which have specific orientations: + +- For the diodes, there is a line on the component that you must put on the same side as the white line printed on the PCB next to the diode positions. + +![](images/diodes.png) + +- The LEDs have a small green arrow-like shape on the bottom, the pointier end indicates ground, which must be lined up with the white lines on the PCB. +- The longer end of the ESP32 lines up with the outlined space on the PCB. +- The buttons are oblong rectangles, and need to be aligned with their rectangular pads. +- The switch should have the shiny side facing upwards, and the lever facing away from the center of the PCB. + +![](images/switch.png) + +- The two 5 pin components will have only one orientation where all pins are in contact with the pads. + +When you have all of the components placed, try to check with a TA to confirm your components are placed correctly. + +### Reflow + +It is now time for the oven. Go to a TA to get your board in line for the reflow oven. The oven takes around 20 +minutes, so boards will be done in batches. + +When you get your board back from the oven, inspect the solder points to make sure no bridging occurred. In our +experience, this will mainly be the USB-C pins that can experience bridging. You can use the +microscope in envision to get a good look at your USB-C connector, you are looking for any solder that is touching between pins. + +![](images/USB_no_bridging.png) + +> A USB connector with no bridged pins + +If you do not have any solder bridging, then you are done. + +However, if there is bridging present, you wall have to do your best to remove the solder that is causing the bridging. + +You can use the copper solder wick in Envision, along with solder flux, to remove the excess solder: + +- first put a small amount of flux on the bridged pins +- then press the solder wick into the pins with a sodlering iron +- check the pins to see if you fixed the bridging +- repeat these steps until you fix the bridging + +Alternatively, you can use a heat gun to remelt the solder holding the USB-C connector down, and lift the connector off of the board. +Place the connector back down, remove the heat gun, and look again at your pins to see if that fixed the bridging. +... + +{{% /steps %}} + +## Things to Consider + +- If your solder paste didn't turn out clean, it is much easier to just repaste. Don't try to make it work if you know you can do better. +- Double check the BOM as you place components, while you can take components off of the paste if, it can easily cause bridging. +- Take your time on every step, this is a very delicate process. diff --git a/content/assignments/DevBoard/images/USB_no_bridging.png b/content/assignments/DevBoard/images/USB_no_bridging.png new file mode 100644 index 0000000..4a6129c Binary files /dev/null and b/content/assignments/DevBoard/images/USB_no_bridging.png differ diff --git a/content/assignments/DevBoard/images/diodes.png b/content/assignments/DevBoard/images/diodes.png new file mode 100644 index 0000000..b1c3dad Binary files /dev/null and b/content/assignments/DevBoard/images/diodes.png differ diff --git a/content/assignments/DevBoard/images/equipment.png b/content/assignments/DevBoard/images/equipment.png new file mode 100644 index 0000000..534ddf0 Binary files /dev/null and b/content/assignments/DevBoard/images/equipment.png differ diff --git a/content/assignments/DevBoard/images/solder_spreader.png b/content/assignments/DevBoard/images/solder_spreader.png new file mode 100644 index 0000000..05cd31e Binary files /dev/null and b/content/assignments/DevBoard/images/solder_spreader.png differ diff --git a/content/assignments/DevBoard/images/stencil_setup.png b/content/assignments/DevBoard/images/stencil_setup.png new file mode 100644 index 0000000..9a7a67c Binary files /dev/null and b/content/assignments/DevBoard/images/stencil_setup.png differ diff --git a/content/assignments/DevBoard/images/switch.png b/content/assignments/DevBoard/images/switch.png new file mode 100644 index 0000000..9531196 Binary files /dev/null and b/content/assignments/DevBoard/images/switch.png differ diff --git a/content/assignments/DevBoard/images/video_thumbnail.png b/content/assignments/DevBoard/images/video_thumbnail.png new file mode 100644 index 0000000..9e19975 Binary files /dev/null and b/content/assignments/DevBoard/images/video_thumbnail.png differ diff --git a/content/assignments/DevBoard/submission.md b/content/assignments/DevBoard/submission.md new file mode 100644 index 0000000..3d360c5 --- /dev/null +++ b/content/assignments/DevBoard/submission.md @@ -0,0 +1,8 @@ +--- +title: Submission +type: docs +next: assignments/VUMeter/ +weight: 2 +--- + +1. Image of assembled board _powered on_ named `submission.*` placed in root of assignment repository. diff --git a/content/assignments/LightShield/_index.md b/content/assignments/LightShield/_index.md new file mode 100644 index 0000000..60b92bf --- /dev/null +++ b/content/assignments/LightShield/_index.md @@ -0,0 +1,17 @@ +--- +title: LightShield +type: docs +prev: assignments/ +next: assignments/LightShield/assembly +weight: 1 +--- + +For this assignment you will be assembling a **Light Shield**. + +This assignment is the first piece to making your own VUMeter. Over the course of the next few weeks, you will build one of these: + +![](images/in-action.png) + +To complete the light shield, you will learn about assembling PCBs with **S**urface **M**ounte**D** components by hand. + +Good luck, and don't be afraid to ask for help. diff --git a/content/assignments/LightShield/assembly.md b/content/assignments/LightShield/assembly.md new file mode 100644 index 0000000..b579b1c --- /dev/null +++ b/content/assignments/LightShield/assembly.md @@ -0,0 +1,34 @@ +--- +title: Assembly +type: docs +prev: assignments/LightShield/ +weight: 1 +--- + +## Step-by-step + +{{% steps %}} + +### Components + +First, grab the components you need from the component table. + +You need: + +- 11 LEDs (with colors of your choice) +- 11 560-1k resistors (to select brightness of your choice) +- Pin headers +- 1 Microphone and accompanying module + +### Soldering + +Then, grab a solder paste syringe, you may need to share with your peers. + +Then, go to EnVision to solder the components with the available equipment of your choice (probably a soldering iron). + +{{% /steps %}} + +## Things to Consider + +- Which direction should the LEDs point? +- Which side should the pin headers and microphone be placed? diff --git a/content/assignments/LightShield/images/in-action.png b/content/assignments/LightShield/images/in-action.png new file mode 100644 index 0000000..fc21d31 Binary files /dev/null and b/content/assignments/LightShield/images/in-action.png differ diff --git a/content/assignments/LightShield/submission.md b/content/assignments/LightShield/submission.md new file mode 100644 index 0000000..b31b152 --- /dev/null +++ b/content/assignments/LightShield/submission.md @@ -0,0 +1,8 @@ +--- +title: Submission +type: docs +next: assignments/DevBoard/ +weight: 2 +--- + +1. Image of assembled board named `submission.*` placed in root of assignment repository. diff --git a/content/assignments/VUMeter/_index.md b/content/assignments/VUMeter/_index.md new file mode 100644 index 0000000..cfad3b4 --- /dev/null +++ b/content/assignments/VUMeter/_index.md @@ -0,0 +1,13 @@ +--- +title: VUMeter +type: docs +prev: assignments/DevBoard/ +next: assignments/VUMeter/firmware +weight: 3 +--- + +It's time for the culmination of the work you have done so far. + +You are going to combine the two boards you have built to create the VUMeter. + +But that's not enough to make it operate, you will need to write **firmware** that enables its operation. diff --git a/content/assignments/VUMeter/enclosure.md b/content/assignments/VUMeter/enclosure.md new file mode 100644 index 0000000..cd3f595 --- /dev/null +++ b/content/assignments/VUMeter/enclosure.md @@ -0,0 +1,71 @@ +--- +title: Enclosure +type: docs +weight: 2 +--- + +Usually PCB's are nicely packaged in some thoughtful form of an enclosure. + +For the VU meter, you will need to design and manufacture your own thoughtful enclosure. + +## Design + +{{% steps %}} + +### Sketch by Hand + +Make a sketch of your PCB and enclosure + +- Use either a paper and pencil or some drawing tool on a laptop or Ipad-like device +- Measure your PCB using a ruler and/or caliper, and mark the measurements on your sketch +- Sketch, even roughly, your enclosure idea and mark some of the primary measurements (these can change later) + +### CAD - Computer Aided Design + +Translate your sketch to a CAD file + +- You may use any CAD software, but we highly recommend the use of [Onshape](https://onshape.com), [SolidWorks](https://solidWorks.com), or [Fusion360](https://www.autodesk.com/products/fusion-360/). +- When creating your CAD file, consider how to improve 3D printability and consider tolerance + +### Save Files and Submit + +- Save your CAD file(s) as either .sldprt, .iges, or .step files. +- Take Screen Shots (or export improved renderings) of your CAD +- Submit onto Canvas + +We will discuss these in class, and after we complete PCB Assembly we will 3D print these CAD Files! + +{{% /steps %}} + +## Fabrication + +Before moving on to 3D printing, be sure to review your enclosure design with your instructors and peers. + +{{% steps %}} + +### Review your CAD File / Design + +Consider both the Assembled VU meter dimensions and 3D printability + +- Consider your dimensions, and leave some room (i.e., tolerance) so that your VU meter could fit nicely. +- Consider what you may need to access, for example, USB connector, small switch, or LEDs. +- Consider the time your 3D print will take to finish. If too long, consider breaking into multiple smaller prints. + +### Complete EnVision Training + +EnVision 3D printing trainings are found within the student portal at [Envision's Website] (envision.ucsd.edu). + +- Remember that there are instructor assistants and envision staff (orange vests) there to help you if you are unsure about printing +- Remember also that you get $5 (i.e., 5 hours of printing) for joining envision, but ECE 196 provides another $5 for printing during the class (request more if needed) + +### Build a 3D print File + +- Save your CAD file as an .STL and transfer to the computers near the 3D printers with a USB drive +- Double check your selections for resolution and support. We recommend the lowest resolution (for fastest times) whenever prototyping although the tolerance may be slightly worse. This will help tremendously when iterating multiple times. +- Submit onto Canvas + +#### Check out your print and iterate if need + +- Please bring in your assembled VU meter including the enclosure in a future class. + +{{% /steps %}} diff --git a/content/assignments/VUMeter/firmware.md b/content/assignments/VUMeter/firmware.md new file mode 100644 index 0000000..83ceb17 --- /dev/null +++ b/content/assignments/VUMeter/firmware.md @@ -0,0 +1,169 @@ +--- +title: Firmware +type: docs +prev: assignments/VUMeter/ +weight: 1 +--- + +It's time to start writing code for your board! + +For this assignment, we have opted to use [CircuitPython](https://circuitpython.org) to demonstrate just how easy embedded development can be. + +## Setup CircuitPython + +### Preparation + +#### Download Binary + +The first step is to download [this](https://github.com/ECE-196/VUMeter/blob/main/envision_mini_devboard_cp_firmware.uf2) file. (No need to open it just remember where you put it) + +> Why? This is the CircuitPython binary we made for the EnVision DevBoard that tells CircuitPython what pins are available, what processor is in use, etc. + +#### Connect the Mini DevBoard + +1. Switch the DevBoard's power switch to `OFF` +2. Plug it in to your computer +3. Press and hold the `BOOT` button +4. Switch the power switch to `ON` +5. Release the `BOOT` button + +(Refer to table to determine on/off switch positions) + +| ON | OFF | +| --------------------- | ---------------------- | +| ![](images/vu-on.jpg) | ![](images/vu-off.jpg) | + +{{< callout type="info" >}} + You may see a pop-up asking if you would like to allow the USB device to connect, of course, click yes. +{{< /callout >}} + +### Uploading + +#### Bootloader + +**With a [Chromium](https://www.google.com/search?q=chromium+browsers) based browser...** + +Navigate to [this](https://circuitpython.org/board/espressif_esp32s3_devkitc_1_n8) page. + +Click "_Open Installer_"... + +![](images/open-installer.png) + +and then click "_Install Bootloader Only_". + +![](images/installer-select.png) + +Click "_Next_" until you reach this screen: + +![](images/connect-button.png) + +Click "_Connect_". This dialog should appear: + +{{< callout type="info" >}} + You may need to grant your browser permission to access your USB ports. +{{< /callout >}} + +![](images/connect-dialog.png) + +Click "_Connect_". + +Click "_Continue_" and wait for the bootloader to flash... + +![](images/flashing.png) + +When it finishes you may close this page. + +#### CircuitPython + +Reset the DevBoard by pressing the `RST` button or by toggling the power switch to `OFF` and then `ON` again. + +{{< callout type="info" >}} + Another USB access dialog may appear, allow it. +{{< /callout >}} + +You should see a mass storage device has mounted... + +![](images/s3dkc1boot.png) + +with these contents: + +![](images/boot-contents.png) + +Drag and drop the downloaded binary from [earlier](https://github.com/ECE-196/VUMeter/wiki/Firmware#download-binary) onto the mass storage device. + +When the transfer is complete, it will disconnect, and a new drive will appear: + +![](images/circuitpy.png) + +{{< callout type="info" >}} + If a keyboard dialog appears, click "_Quit_" +{{< /callout >}} + +### Setup Workspace + +Now that CircuitPython has been installed on your DevBoard, you can open VSCode in the `CIRCUITPY` directory. + +![](images/vscode-open.png) + +Click "_Open Folder_" and select the `CIRCUITPY` directory. + +The first thing you should do is rename `code.py` to `main.py`. + +![](images/vscode-main.png) + +_So how do I run this?_ you may ask. + +Well we first need to tell VSCode how to interact with our CircuitPython runtime. + +Luckily for us, there is a VSCode extension for this! + +Navigate to the extensions pane and search "_python_", install the first result. + +Then search "_circuitpython_" -- there should only be one result -- and click install. + +![](images/vsscode-ext-install.png) + +Then go back to the explorer pane and select `main.py`. + +In the bottom right, click "_\_", then search for the `ESP32-S3-DevKitC-1-N8`. + +![](images/devkit.png) + +Then press the little USB icon next to that button, or open the command pallette and search "_open serial_". + +Select the appropriate USB port. + +You should see this pane appear: + +![](images/serial-monitor.png) + +Congrats! You can now interact with the CircuitPython REPL just like Python on your computer. + +Also, any code you write in `main.py` will run on the DevBoard when it powers on, you reset it with `Ctrl + D`, or change your code. + +There are lots of resources for learning how to use CircuitPython on their [website](https://circuitpython.org). + +#### This doesn't work + +If for whatever reason you can't get VSCode or the CircuitPython extension to work on your computer, you can try: + +- [Mu Editor](https://learn.adafruit.com/welcome-to-circuitpython/installing-mu-editor) +- [Online Editor](https://code.circuitpython.org) + +## Challenge + +We have some starting code for you [here](https://github.com/ECE-196/VUMeter/blob/main/main.py) to get started with the VU behavior. + +{{< callout type="warning" >}} + Because the VSCode extension for CircuitPython does not know about the EnVision Mini DevBoard, you will see errors whenever you use pins `IO26`, `IO33`, or `IO34`. These errors are not real and can be suppressed by adding `# type: ignore` on any line dealing with those pins. +{{< /callout >}} + +Think about the following: + +1. How do you get the volume of the environment from the microphone? +2. How do you only turn on certain LEDs based on this number? +3. How do you do this in realtime? + +## Extra Credit + +Filter the displayed volume to slowly go down but still quickly go up, to give a better visual of the volume in the room. diff --git a/content/assignments/VUMeter/images/boot-contents.png b/content/assignments/VUMeter/images/boot-contents.png new file mode 100644 index 0000000..4b30bfd Binary files /dev/null and b/content/assignments/VUMeter/images/boot-contents.png differ diff --git a/content/assignments/VUMeter/images/circuitpy.png b/content/assignments/VUMeter/images/circuitpy.png new file mode 100644 index 0000000..5143b6b Binary files /dev/null and b/content/assignments/VUMeter/images/circuitpy.png differ diff --git a/content/assignments/VUMeter/images/connect-button.png b/content/assignments/VUMeter/images/connect-button.png new file mode 100644 index 0000000..1f4a6b9 Binary files /dev/null and b/content/assignments/VUMeter/images/connect-button.png differ diff --git a/content/assignments/VUMeter/images/connect-dialog.png b/content/assignments/VUMeter/images/connect-dialog.png new file mode 100644 index 0000000..06d2d12 Binary files /dev/null and b/content/assignments/VUMeter/images/connect-dialog.png differ diff --git a/content/assignments/VUMeter/images/devkit.png b/content/assignments/VUMeter/images/devkit.png new file mode 100644 index 0000000..41261fa Binary files /dev/null and b/content/assignments/VUMeter/images/devkit.png differ diff --git a/content/assignments/VUMeter/images/flashing.png b/content/assignments/VUMeter/images/flashing.png new file mode 100644 index 0000000..e14734f Binary files /dev/null and b/content/assignments/VUMeter/images/flashing.png differ diff --git a/content/assignments/VUMeter/images/installer-select.png b/content/assignments/VUMeter/images/installer-select.png new file mode 100644 index 0000000..690f5a3 Binary files /dev/null and b/content/assignments/VUMeter/images/installer-select.png differ diff --git a/content/assignments/VUMeter/images/open-installer.png b/content/assignments/VUMeter/images/open-installer.png new file mode 100644 index 0000000..7f549e3 Binary files /dev/null and b/content/assignments/VUMeter/images/open-installer.png differ diff --git a/content/assignments/VUMeter/images/s3dkc1boot.png b/content/assignments/VUMeter/images/s3dkc1boot.png new file mode 100644 index 0000000..37fc63f Binary files /dev/null and b/content/assignments/VUMeter/images/s3dkc1boot.png differ diff --git a/content/assignments/VUMeter/images/serial-monitor.png b/content/assignments/VUMeter/images/serial-monitor.png new file mode 100644 index 0000000..57779cc Binary files /dev/null and b/content/assignments/VUMeter/images/serial-monitor.png differ diff --git a/content/assignments/VUMeter/images/vscode-main.png b/content/assignments/VUMeter/images/vscode-main.png new file mode 100644 index 0000000..f82385f Binary files /dev/null and b/content/assignments/VUMeter/images/vscode-main.png differ diff --git a/content/assignments/VUMeter/images/vscode-open.png b/content/assignments/VUMeter/images/vscode-open.png new file mode 100644 index 0000000..239df5d Binary files /dev/null and b/content/assignments/VUMeter/images/vscode-open.png differ diff --git a/content/assignments/VUMeter/images/vsscode-ext-install.png b/content/assignments/VUMeter/images/vsscode-ext-install.png new file mode 100644 index 0000000..92ce3ac Binary files /dev/null and b/content/assignments/VUMeter/images/vsscode-ext-install.png differ diff --git a/content/assignments/VUMeter/images/vu-off.jpg b/content/assignments/VUMeter/images/vu-off.jpg new file mode 100644 index 0000000..e895a94 Binary files /dev/null and b/content/assignments/VUMeter/images/vu-off.jpg differ diff --git a/content/assignments/VUMeter/images/vu-on.jpg b/content/assignments/VUMeter/images/vu-on.jpg new file mode 100644 index 0000000..6ec47e1 Binary files /dev/null and b/content/assignments/VUMeter/images/vu-on.jpg differ diff --git a/content/assignments/VUMeter/submission.md b/content/assignments/VUMeter/submission.md new file mode 100644 index 0000000..c1cbd3a --- /dev/null +++ b/content/assignments/VUMeter/submission.md @@ -0,0 +1,11 @@ +--- +title: Submission +type: docs +next: assignments/ControlWithPython/ +weight: 3 +--- + +1. Completed code (in `main.py`). +1. Short video of VUMeter and enclosure in operation named `submission.*` placed in root of assignment repository. (Keep under 10MB) + +> If you elect to try the extra credit, the contents of `main.py` should be the extra credit solution, the original solution is extraneous. diff --git a/content/assignments/_index.md b/content/assignments/_index.md new file mode 100644 index 0000000..67ed224 --- /dev/null +++ b/content/assignments/_index.md @@ -0,0 +1,14 @@ +--- +title: Assignments +next: assignments/LightShield/ +toc: false +--- + +All of the ECE 196 assignment instructions can be found here. + +{{< cards >}} + {{< card link="lightshield" title="LightShield" icon="light-bulb" >}} + {{< card link="devboard" title="DevBoard" icon="chip" >}} + {{< card link="vumeter" title="VUMeter" icon="chart-square-bar" >}} + {{< card link="controlwithpython" title="ControlWithPython" icon="code" >}} +{{< /cards >}} diff --git a/content/projects.md b/content/projects.md new file mode 100644 index 0000000..1c3b215 --- /dev/null +++ b/content/projects.md @@ -0,0 +1,5 @@ +--- +title: Projects +--- + +List some previous team projects... diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..7e18fbc --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module github.com/imfing/hextra-starter-template + +go 1.21 + +require github.com/imfing/hextra v0.8.0 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..947d58a --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/imfing/hextra v0.8.0 h1:bG2GavmsMAgRRCrm+N4c+m6mBm9KYrgGH0mgaJpIXjM= +github.com/imfing/hextra v0.8.0/go.mod h1:cEfel3lU/bSx7lTE/+uuR4GJaphyOyiwNR3PTqFTXpI= diff --git a/hugo.yaml b/hugo.yaml new file mode 100644 index 0000000..f92e3c1 --- /dev/null +++ b/hugo.yaml @@ -0,0 +1,68 @@ +# Hugo configuration file +title: ECE 196 + +# import hextra as module +module: + imports: + - path: github.com/imfing/hextra + +markup: + # allow raw html + goldmark: + renderer: + unsafe: true + + # enable hextra syntax highlight + highlight: + noClasses: false + +menu: + main: + - name: About + pageRef: /about + weight: 1 + - name: Projects + pageRef: /projects + weight: 2 + - name: Assignments + pageRef: /assignments + weight: 3 + - name: Resources + pageRef: /additional-resources + weight: 4 + - name: Search + weight: 5 + params: + type: search + - name: GitHub + weight: 6 + url: "https://github.com/ece-196" + params: + icon: github + + sidebar: + - name: Get Help + params: + type: separator + weight: 1 + - name: "Resources" + pageRef: "/additional-resources" + weight: 2 + - name: "Tutors" + url: "/about/#tutors" + weight: 3 + +params: + navbar: + displayTitle: true + displayLogo: false + + footer: + displayCopyright: false + displayPoweredBy: false + + editURL: + enable: false + + page: + width: full