Output Devices
Group Assignment:
Measure the power consumption of an output device.
Individual Assignment:
Add an output device to a microcontroller board you've designed, and program it to do something
Have you answered these questions?
Linked to the group assignment page ✅
Documented how you determined power consumption of an output device with your group. ✅
Documented what you learned from interfacing output device(s) to microcontroller and controlling the device(s).✅
Linked to the board you made in a previous assignment or documented your design and fabrication process if you made a new board.✅
Explained the programming process/es you used.✅
Explained any problems you encountered and how you fixed them.✅
Included original source code and any new design files.✅
Included a ‘hero shot’ of your board.✅
Group assignment
Measure the power consumption of an output device.
During the group project meeting, I had the opportunity to collaborate with my teammate Jhonathan, who provided a detailed explanation of various measurement concepts. His help was crucial in gaining a better understanding of how a multimeter works and how to interpret its readings. Thanks to his support, I was able to realize that when the voltage is extremely low, the multimeter may not accurately reflect the smallest details, making it difficult to observe small variations. Here is the link to learn more about the group project.
Reflections
By completing this grupal assignment, I have learned many valuable aspects, both technically and practically. One of the most important takeaways was understanding the use and limitations of the tools we are using. Specifically, when measuring the buzzer with a multimeter, I realized how essential it is to interpret data correctly and how small variations, which might seem insignificant at first glance, actually provide crucial information about a device’s functioning.
I was also surprised by the difference between the measurements obtained with the multimeter and what could be achieved with a power meter. This allowed me to reflect on the most appropriate tools for each type of measurement. Understanding that a multimeter can be useful for measuring certain parameters but may not always be precise enough for others made me appreciate the use of specialized devices depending on the context.
The challenge of working remotely with a teammate in another city also taught me the importance of effective communication. Even though we were separated by distance, we were able to coordinate our tasks and discuss our progress thanks to online communication tools. This taught me that remote collaboration is not only possible but can be as productive as in-person work, as long as there is clarity in the goals and task distribution.
Individual assignment
Add an output device to a microcontroller board you've designed, and program it to do something
1. Buzzer
This document describes the programming process used to create a melody project in Python, adapted to run in HTML with JavaScript. (ChatGPT)
1.1. Defining Musical Notes (in Hertz)
First, we define the frequencies of musical notes in Hertz. These notes form the basis for generating the tones:
// Defining notes (in Hertz)
const NOTES = {
NOTE_B0: 31, NOTE_C1: 33, NOTE_CS1: 35, NOTE_D1: 37,
NOTE_E1: 41, NOTE_F1: 44, NOTE_G1: 49, NOTE_A1: 55,
// ... (other notes up to NOTE_CS8)
};
1.2. Setting Up the Buzzer
In a system like Arduino, the buzzer is controlled via a pin. In HTML/JavaScript, we emulate this by generating sounds through the AudioContext API:
// We use the AudioContext API to create a sound generator
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
const oscillator = audioContext.createOscillator();
oscillator.type = 'sine'; // Sine wave type
oscillator.connect(audioContext.destination);
1.3. Melody and Note Durations
We define the sequence of notes and their durations in an array of pairs (note, duration). Here, we indicate which note to play and how long to play it:
// Melody: Each note and its duration
const melody = [
[NOTES.NOTE_D4, 4], [NOTES.NOTE_G4, 4],
[NOTES.NOTE_A4, 8], [NOTES.NOTE_B4, 4],
// ... (more notes)
];
1.4. Calculating the Tempo and Note Duration
The tempo controls the speed of the melody, i.e., how many notes are played per minute. Then, we calculate the duration of a whole note (a quarter note) and adjust the duration of each note accordingly:
// Calculate the duration of a whole note based on the tempo
const tempo = 144;
const wholeNoteDuration = (60000 * 4) / tempo;
1.5. Playing the Notes
To play a note, we use an oscillator with the frequency of the desired note and the corresponding duration:
// Function to play a tone
function playTone(frequency, duration) {
if (frequency > 0) {
const oscillator = audioContext.createOscillator();
oscillator.frequency.setValueAtTime(frequency, audioContext.currentTime);
oscillator.start();
setTimeout(() => oscillator.stop(), duration);
}
}
1.6. Playing the Full Melody
Once we have the function to play individual notes, we loop through the melody and play the notes one by one:
// Function to play the full melody
function playMelody() {
melody.forEach(([note, duration]) => {
const noteDuration = wholeNoteDuration / duration;
playTone(note, noteDuration); // Play the note
});
}
1.7. Integrating and Running the Melody
Finally, we run the melody when the user clicks a button:
// Button to play the melody
Conclusion
This process allows us to create a melody and play it on the web using HTML, JavaScript, and the AudioContext API to generate the tones. It's a similar approach to how it would work on a microcontroller, but using web capabilities.
Choosing and Connecting the Component
For the output device, one of the components I will use is a buzzer. But what is a buzzer? A buzzer is a device that produces a loud sound when activated and is used in different contexts. On this occasion, I will use it to simulate the song Hedwig's Theme from Harry Potter. To do this, I need to identify the positive and negative terminals to know which pins they should be connected to on an RP2040 microcontroller.
First, I identify the positive terminal on the buzzer, which will be connected to the GND. The negative terminal will be connected to any pin, in this case, I am connecting it to D1, which is pin 27.
After creating the schematic diagram, I will proceed to connect it to the actual PCB.
Programming in MicroPython
import time
import machine
# Note definitions (in Hertz)
NOTE_B0 = 31
NOTE_C1 = 33
NOTE_CS1 = 35
NOTE_D1 = 37
NOTE_DS1 = 39
NOTE_E1 = 41
NOTE_F1 = 44
NOTE_FS1 = 46
NOTE_G1 = 49
NOTE_GS1 = 52
NOTE_A1 = 55
NOTE_AS1 = 58
NOTE_B1 = 62
NOTE_C2 = 65
NOTE_CS2 = 69
NOTE_D2 = 73
NOTE_DS2 = 78
NOTE_E2 = 82
NOTE_F2 = 87
NOTE_FS2 = 93
NOTE_G2 = 98
NOTE_GS2 = 104
NOTE_A2 = 110
NOTE_AS2 = 117
NOTE_B2 = 123
NOTE_C3 = 131
NOTE_CS3 = 139
NOTE_D3 = 147
NOTE_DS3 = 156
NOTE_E3 = 165
NOTE_F3 = 175
NOTE_FS3 = 185
NOTE_G3 = 196
NOTE_GS3 = 208
NOTE_A3 = 220
NOTE_AS3 = 233
NOTE_B3 = 247
NOTE_C4 = 262
NOTE_CS4 = 277
NOTE_D4 = 294
NOTE_DS4 = 311
NOTE_E4 = 330
NOTE_F4 = 349
NOTE_FS4 = 370
NOTE_G4 = 392
NOTE_GS4 = 415
NOTE_A4 = 440
NOTE_AS4 = 466
NOTE_B4 = 494
NOTE_C5 = 523
NOTE_CS5 = 554
NOTE_D5 = 587
NOTE_DS5 = 622
NOTE_E5 = 659
NOTE_F5 = 698
NOTE_FS5 = 740
NOTE_G5 = 784
NOTE_GS5 = 831
NOTE_A5 = 880
NOTE_AS5 = 932
NOTE_B5 = 988
NOTE_C6 = 1047
NOTE_CS6 = 1109
NOTE_D6 = 1175
NOTE_DS6 = 1245
NOTE_E6 = 1319
NOTE_F6 = 1397
NOTE_FS6 = 1480
NOTE_G6 = 1568
NOTE_GS6 = 1661
NOTE_A6 = 1760
NOTE_AS6 = 1865
NOTE_B6 = 1976
NOTE_C7 = 2093
NOTE_CS7 = 2217
NOTE_D7 = 2349
NOTE_DS7 = 2489
NOTE_E7 = 2637
NOTE_F7 = 2794
NOTE_FS7 = 2960
NOTE_G7 = 3136
NOTE_GS7 = 3322
NOTE_A7 = 3520
NOTE_AS7 = 3729
NOTE_B7 = 3951
NOTE_C8 = 4186
NOTE_CS8 = 4435
NOTE_D8 = 4699
NOTE_DS8 = 4978
REST = 0
# Buzzer pin (change this depending on your setup)
buzzer_pin = machine.Pin(27, machine.Pin.OUT) # Pin 15 on the Xiao RP2040
# Create a PWM object to control the buzzer
buzzer = machine.PWM(buzzer_pin)
# Melody with notes and durations
melody = [
(REST, 2), (NOTE_D4, 4),
(NOTE_G4, -4), (NOTE_AS4, 8), (NOTE_A4, 4),
(NOTE_G4, 2), (NOTE_D5, 4),
(NOTE_C5, -2),
(NOTE_A4, -2),
(NOTE_G4, -4), (NOTE_AS4, 8), (NOTE_A4, 4),
(NOTE_F4, 2), (NOTE_GS4, 4),
(NOTE_D4, -1),
(NOTE_D4, 4),
(NOTE_G4, -4), (NOTE_AS4, 8), (NOTE_A4, 4),
(NOTE_G4, 2), (NOTE_D5, 4),
(NOTE_F5, 2), (NOTE_E5, 4),
(NOTE_DS5, 2), (NOTE_B4, 4),
(NOTE_DS5, -4), (NOTE_D5, 8), (NOTE_CS5, 4),
(NOTE_CS4, 2), (NOTE_B4, 4),
(NOTE_G4, -1),
(NOTE_AS4, 4),
(NOTE_D5, 2), (NOTE_AS4, 4),
(NOTE_D5, 2), (NOTE_AS4, 4),
(NOTE_DS5, 2), (NOTE_D5, 4),
(NOTE_CS5, 2), (NOTE_A4, 4),
(NOTE_AS4, -4), (NOTE_D5, 8), (NOTE_CS5, 4),
(NOTE_CS4, 2), (NOTE_D4, 4),
(NOTE_D5, -1),
(REST, 4), (NOTE_AS4, 4),
(NOTE_D5, 2), (NOTE_AS4, 4),
(NOTE_D5, 2), (NOTE_AS4, 4),
(NOTE_F5, 2), (NOTE_E5, 4),
(NOTE_DS5, 2), (NOTE_B4, 4),
(NOTE_DS5, -4), (NOTE_D5, 8), (NOTE_CS5, 4),
(NOTE_CS4, 2), (NOTE_AS4, 4),
(NOTE_G4, -1),
]
# Song speed (adjust if necessary)
tempo = 144
# Duration of a whole note (in milliseconds)
wholenote = (60000 * 4) / tempo
def play_tone(frequency, duration):
if frequency > 0:
buzzer.freq(frequency) # Set the frequency for the buzzer
buzzer.duty_u16(32768) # Control the intensity (50% intensity with duty_u16)
time.sleep(duration / 1000) # Play the note for the appropriate time
buzzer.duty_u16(0) # Turn off the sound after the duration
def play_melody():
for note, duration in melody:
if note == REST:
time.sleep(wholenote / duration / 1000) # Pause based on the note duration
else:
# Calculate the note duration
note_duration = wholenote / duration
play_tone(note, note_duration)
# Play the melody in the main loop
while True:
play_melody()
time.sleep(1) # A break before repeating the song (if needed)
For this project, while researching, I came across my instructor Cristian's work and I really liked the code he used for the Imperial March from Star Wars, written by Robson Couto (2019). I decided to take on the challenge, as he used Arduino IDE, while I am working with MicroPython in the Thonny program.
I visited the following link: Git Hub - Robson Couto because I wanted to make Hedwig's Theme from Harry Potter. I copied the code and used the ChatGPT tool for adjustments.
Did I face any difficulties?
Yes, I encountered some difficulties. The original code was written for Arduino IDE, and I needed to adapt it for MicroPython in the Thonny environment. When making the adaptation, I forgot to include the technical specifications of the microcontroller I was using. Once identified, I used the prompt: "convert Arduino code to MicroPython for a Xiao RP2040 with pin 27."
However, the code still didn’t work properly. I received the following error:
>>> %Run -c $EDITOR_CONTENT
MPY: soft reboot
Traceback (most recent call last):
File "", line 162, in
File "", line 158, in play_melody
File "", line 147, in play_tone
It appeared that the issue was because the PWM class in MicroPython for the Xiao RP2040 doesn't have a method called duty. Instead, you should use duty_u16() to set the PWM intensity value, which accepts a range from 0 to 65535, unlike the duty() method commonly used in other microcontrollers like the ESP32.
After making this adjustment, the code started working correctly.
Result
Finally, Hedwig's Theme from Harry Potter plays correctly on the buzzer I connected. After making the necessary adjustments to the code and properly connecting the components, I managed to get the buzzer to play the melody. Now the system works as expected, and the sound is just right for the song.
2. SG90 servo motor
Process of Programming Servo Motor with PWM
2.1. Import Necessary Libraries
To begin programming, we need to import the necessary libraries:
from machine import Pin, PWM
from time import sleep
- Pin: Used to control GPIO pins on the microcontroller.
- PWM: Allows us to create a PWM signal to control the servo motor.
- sleep: Pauses the program for a specified amount of time, used to hold the servo in a specific position.
2.2. Set Up PWM for Servo Control
We need to set up a PWM object on a GPIO pin to control the servo motor:
servo = PWM(machine.Pin(29)) # Set up PWM Pin for servo control
Here, Pin 29 is connected to the servo, and the PWM signal will be used to control its movement.
2.3. Define Duty Cycle for Different Angles
The duty cycle controls the angle of the servo motor. The following duty cycles correspond to specific angles:
max_duty = 7864 # Duty cycle for 180 degrees
min_duty = 1802 # Duty cycle for 0 degrees
half_duty = int(max_duty / 2) # Duty cycle for 90 degrees
Explanation:
- max_duty: Corresponds to a servo angle of 180°.
- min_duty: Corresponds to a servo angle of 0°.
- half_duty: Corresponds to a servo angle of 90°.
2.4. Set PWM Frequency
The servo motor operates at a frequency of 50Hz, which is set as follows:
servo.freq(50) # Set PWM frequency to 50Hz (common for servos)
2.5. Main Control Loop
The program continuously moves the servo between 0°, 90°, and 180° by adjusting the duty cycle:
try:
while True:
servo.duty_u16(min_duty) # Servo at 0 degrees
sleep(2)
servo.duty_u16(half_duty) # Servo at 90 degrees
sleep(2)
servo.duty_u16(max_duty) # Servo at 180 degrees
sleep(2)
Explanation:
- servo.duty_u16(min_duty): Moves the servo to 0°.
- servo.duty_u16(half_duty): Moves the servo to 90°.
- servo.duty_u16(max_duty): Moves the servo to 180°.
2.6. Handle Keyboard Interrupt (Graceful Exit)
If the program is interrupted (e.g., pressing Ctrl+C), the following code ensures that the PWM signal is turned off and the servo is stopped gracefully:
except KeyboardInterrupt:
print("Keyboard interrupt")
servo.deinit() # Turn off PWM
2.7. Full Code
Here is the full code, combining all the steps explained above:
from machine import Pin, PWM # Import necessary libraries for pin and PWM control
from time import sleep # Import the sleep function to pause the program
# Set up PWM Pin for servo control on GPIO Pin 29
servo = PWM(machine.Pin(29))
# Define the duty cycle values for the servo at different angles (0°, 90°, and 180°)
max_duty = 7864 # Duty cycle for 180 degrees
min_duty = 1802 # Duty cycle for 0 degrees
half_duty = int(max_duty / 2) # Duty cycle for 90 degrees
# Set the PWM frequency to 50Hz (common for servos)
servo.freq(50)
try:
while True:
# Move the servo to 0 degrees
servo.duty_u16(min_duty)
sleep(2) # Hold position for 2 seconds
# Move the servo to 90 degrees
servo.duty_u16(half_duty)
sleep(2) # Hold position for 2 seconds
# Move the servo to 180 degrees
servo.duty_u16(max_duty)
sleep(2) # Hold position for 2 seconds
# Handle keyboard interruption gracefully
except KeyboardInterrupt:
print("Keyboard interrupt")
servo.deinit() # Stop PWM and turn off the servo
Key Concepts
- Duty Cycle: Controls the servo position. A higher duty cycle corresponds to a larger angle (e.g., 180°), while a lower duty cycle corresponds to a smaller angle (e.g., 0°).
- PWM Frequency: Servos typically operate at a frequency of 50Hz for movement.
- Servo Motor Movement: The servo moves between 0°, 90°, and 180° with pauses between each position.
- Graceful Shutdown: When the program is interrupted, the servo is properly turned off.
Connecting the Servo
For the second exercise, I will be using an SG90 servomotor, a precision electric motor specifically designed to control angular movement in applications that require exact position control. Unlike conventional motors, the SG90 servomotor is characterized by its ability to move an axis within a limited rotation range, typically from 0 to 180 degrees, which makes it an ideal choice for tasks that demand controlled and repetitive movements.
Now, I will proceed to connect the SG90 servomotor to my PCB, for which it is important to recognize the correct connections. The GND (ground) should be connected to the GND of the board, ensuring a common voltage reference between the servomotor and the board. The VCC is the voltage input to power the servomotor, and in this case, it should be connected to 5V to provide the appropriate power. Finally, the PWM pin is responsible for receiving the control signal to set the servomotor's position. In this case, I will connect the PWM to pin 29 of the board, which will send the pulse-width modulation (PWM) signals necessary to control the servomotor's rotation angle.
Now, I will connect the components I have. I will make sure all connections are correct for everything to work properly.
Programming in MicroPython
from machine import Pin, PWM
from time import sleep
servo = PWM(machine.Pin(29)) # Set up PWM Pin for servo control
max_duty = 7864 # Set Duty Cycle for Different Angles
min_duty = 1802
half_duty = int(max_duty/2)
servo.freq (50) #Set PWM frequency
try:
while True:
servo.duty_u16(min_duty) #Servo at 0 degrees
sleep(2)
servo.duty_u16(half_duty) #Servo at 90 degrees
sleep(2)
servo.duty_u16(max_duty) #Servo at 180 degrees
sleep(2)
except KeyboardInterrupt:
print("Keyboard interrupt")
servo.deinit() # Turn off PWM
The code I will use is the one taught by instructor Ulises during a masterclass he led.
Result
This is the result of the SG90 servo motor connected to the XIAO RP2040 microcontroller.
Reflections
Working with the buzzer, I learned several key lessons about how these components function and how to measure them to understand their behavior. A buzzer is a device that emits sound when an electrical signal is applied.
One of the first observations I made was that the buzzer has a very low power consumption, making it difficult to measure accurately with a standard multimeter, especially when measuring in amperes of direct current (DCA). This led me to reflect on the difference between a multimeter and a specialized power meter. A power meter could provide more accurate readings of energy consumption in low-power devices like the buzzer.
Additionally, I learned that a buzzer can be powered by either direct or alternating current, and that its operating frequency, which determines the pitch of the sound emitted, is influenced by the electrical signal it receives. Measuring the buzzer allowed me to see how small changes in current or voltage directly affect its operation, and how the multimeter, although useful, has certain limitations when measuring components with low current demand.