
Pi Pico W
Click to learn more
Our group takes pride in our robust hardware design incorporating custom PCBs, a 3D-printed case, and a variety of sensors and peripherals.
To support our hardware, we have developed a comphrensive software package that includes a user interface, real-time NTP time updating, texting, heart rate and SPO2 monitoring, activity step tracking, screen orientation checking, audio recording and playing, and a stopwatch.
The aim of this project was to create a custom smartwatch using a Raspberry Pi Pico W because of its great functionalities, while maintaining a simple and compact design embodying industry standard smartwatches and their common features.
The main motivation behind this project was combining and making the most out of all of the team members' individual strengths into an interesting and multifaceted project we would be proud of. By tackling this large and comprehensive project, we hoped to gain a deeper appreciation for devices we may take for granted, as well as create a project that is able to fuel our passions for health and well-being. We created a wearable, interactive device of similar dimensions to industry standard smartwatch devices which has many capabilities such as real-time NTP time updating, texting, heart rate and SPO2 monitoring, activity step tracking, screen orientation checking, audio recording and playing, and a stopwatch integrated. Specifically, we utilize the WiFi features of the Pico W for real time updating and texting.
The rationale behind this project is that we wanted to utilize a variety of sensors and peripherals to implement interesting functionalities, all of which interface with the central Raspberry Pi Pico W. Additionally, we wanted to complete a project which involved a complete embedded-systems design experience by developing both the hardware and software from scratch, while also incorporating our shared interests in wearable technology that promotes health. The design involves a combination of hardware design, software design, systems engineering, and user interface design, promoting cross-domain engineering thinking. Some of the sources that were very helpful for the project include Professor Adams's Github Repository, Raspberry Pi Pico Repository, Professor Land's Project Demos, as well as integrated component specific libraries, all of which can be found under our References section in the Appendix.
Much of our project was centered around embedded-system-level integration of various components to create a cohesive and multi-functional device. As such, there was not a significant amount of pure mathematical background and analysis required for this project. However, both the heart rate/SPO2 sensor as well as PDM microphone processing pipeline online code repositories (links in the Appendix section) utilized signal processing to interpret the raw data from the sensors. For instance, the heart rate/SPO2 sensor works by shining red and IR light onto the skin and measuring the amount of light reflected back using tuned photodetectors. These raw values from the photodetectors are then read out over the I2C bus to the host, where they are processed using a series of algorithms to determine the heart rate and SPO2 values.
For heart rate detection, an average DC estimator as well as a low-pass
FIR (finite-impulese-response) filter are used. The DC estimator updates the average value of the IR
photodetector over the past few samples with the a new sample by tracking the
DC component of the signal over time and smoothing out fluctuations. This
estimator is implemented with the following library code where "p" is the pointer
to the variable holding the average and "x" is the new sample. An estimation
of the DC component is also returned:
Click here for DC Component Estimation Code Snippet!
// Average DC Estimator
int16_t max30102_hr_avg_dc_estimator(
int32_t* p,
uint16_t x
) {
*p += ((((long) x << 15) - *p) >> 4);
return (*p >> 15);
}
The low-pass FIR filter works by multiplying accumulating the input signal
with multiplications by pre-computed coefficients in what is considered a
convolution. By doing this, the high-frequency components of the signal are filtered out,
to produce a more stable signal. The filter is implemented with the following library code
where "din" is the new sample minus the estimated average from the DC estimator,
"cbuf" is the circular buffer of the last 32 samples, "offset" is the current index in the
circular buffer, and "FIRCoeffs" is the array of pre-computed coefficients:
Click here for Low Pass FIR Filter Code Snippet!
// Low Pass FIR Filter
int16_t max30102_hr_low_pass_fir_filter(
int16_t din,
int16_t* cbuf,
uint8_t* offset,
const uint16_t* FIRCoeffs
) {
cbuf[*offset] = din;
int32_t z = (long)(FIRCoeffs[11]) * (long)(cbuf[(*offset - 11) & 0x1F]);
for (uint8_t i = 0; i < 11; i++)
z += (long)(FIRCoeffs[i]) * (long)(cbuf[(*offset - i) & 0x1F] + cbuf[(*offset - 22 + i) & 0x1F]);
*offset = *offset + 1;
*offset = *offset % 32;
return(z >> 15);
}
Once the signal is smoothed out using the DC estimator and low-pass FIR filter, a positive crossing is detected by checking if the previous sample was negative and the current sample is positive. If this is true, then a heartbeat has been detected and necessary calculations can be made to determine the heart rate as described later in our software implementation section.
For SPO2 detection, we first obtain the DC mean for the infrared (IR) signal by taking a simple
average. Next, we find the two lowest values in the IR values signal and find the
corresponding max to use between those. This process yields values for the following
equation for calculation of the
SPO2 percentage: $$an\_ratio = \frac{AC_{red} \cdot DC_{IR}}{AC_{IR} \cdot DC_{red}}$$
where the "AC_" values are the AC components of the red and IR signals
and the "DC_" values are the DC components of the red and IR signals.
The result of the calculation will then be put into a circular buffer, sorted in
ascending order, and then the median used to index into a lookup table for the
specific percentage to use. The library code to find the maximum value between the two
lowest points in the IR signal and compute the above formula is as follows (see
the code repository for more details on the specifics of the algorithm):
Click here for SPO2 Algorithm Code Snippet!
// find max between two valley locations
// and use an_ratio betwen AC compoent of Ir & Red and DC compoent of Ir & Red for SPO2
for (k = 0; k < n_exact_ir_valley_locs_count-1; k++) {
n_y_dc_max = -16777216;
n_x_dc_max = -16777216;
if (an_ir_valley_locs[k+1]-an_ir_valley_locs[k] >3) {
for (i = an_ir_valley_locs[k]; i < an_ir_valley_locs[k+1]; i++) {
if (an_ir[i] > n_x_dc_max) {n_x_dc_max = an_ir[i]; n_x_dc_max_idx = i;}
if (an_red[i] > n_y_dc_max) {n_y_dc_max = an_red[i]; n_y_dc_max_idx = i;}
}
n_y_ac = (an_red[an_ir_valley_locs[k+1]] -
an_red[an_ir_valley_locs[k] ] )*(n_y_dc_max_idx -an_ir_valley_locs[k]);
n_y_ac = an_red[an_ir_valley_locs[k]] +
n_y_ac/ (an_ir_valley_locs[k+1] - an_ir_valley_locs[k]) ;
n_y_ac = an_red[n_y_dc_max_idx] - n_y_ac; // subracting linear DC compoenents from raw
n_x_ac = (an_ir[an_ir_valley_locs[k+1]] -
an_ir[an_ir_valley_locs[k] ] )*(n_x_dc_max_idx -an_ir_valley_locs[k]);
n_x_ac = an_ir[an_ir_valley_locs[k]] + n_x_ac/ (an_ir_valley_locs[k+1] - an_ir_valley_locs[k]);
n_x_ac = an_ir[n_y_dc_max_idx] - n_x_ac; // subracting linear DC compoenents from raw
n_nume = (n_y_ac *n_x_dc_max)>>7; // prepare X100 to preserve floating value
n_denom = (n_x_ac *n_y_dc_max)>>7;
if (n_denom > 0 && n_i_ratio_count < 5 && n_nume != 0) {
// formula is ( n_y_ac *n_x_dc_max) / ( n_x_ac *n_y_dc_max)
an_ratio[n_i_ratio_count]= (n_nume*100)/n_denom;
n_i_ratio_count++;
}
}
}
A single sample produced by the PDM PIO block is 1024 bits wide. However,
this sample must be compressed to 16 bits so that multiple such samples can
be reasonably stored in memory, which is limited on the Pico W. This is done
using the OpenPDMFilter library which, at a high-level, extracts each bit of
the input sample, multiplies it with a corresponding filter coefficient, and
accumulates the result into a 16-bit output sample. The filter coefficients
are pre-computed and stored in a lookup table. Similar to the heart rate/SPO2
algorithm, low-pass and high-pass filtering is used to remove high-frequency
noise. A library code snippet which implements this is as follows:
Click here for PDM Compression Algorithm Code Snippet!
for (i = 0, data_out_index = 0; i < Param->Fs / 1000; i++, data_out_index += channels) {
Z0 = filter_tables_64[j](data, 0);
Z1 = filter_tables_64[j](data, 1);
Z2 = filter_tables_64[j](data, 2);
Z = Param->Coef[1] + Z2 - sub_const;
Param->Coef[1] = Param->Coef[0] + Z1;
Param->Coef[0] = Z0;
OldOut = (Param->HP_ALFA * (OldOut + Z - OldIn)) >> 8;
OldIn = Z;
OldZ = ((256 - Param->LP_ALFA) * OldZ + Param->LP_ALFA * OldOut) >> 8;
Z = OldZ * volume;
Z = RoundDiv(Z, div_const);
Z = SaturaLH(Z, -32700, 32700);
dataOut[data_out_index] = Z;
data += data_inc;
}
Since our system involves significant hardware as well as software design, we needed to prioritize the different design phases in terms of how long each would take. For instance, we knew designing the PCB and waiting for it to be delivered would take the longest, so we prioritized the design of it first such that we could send it out for manufacturing as soon as possible. While the PCB's were being made, we utilized breadboard testing to develop our basic software for menu navigation and interacting with our various peripherals that were accessible without the PCB, such as the OLED screen. In this way, we were able to structure our design process such that we could work on both our hardware and software in parallel to complete this large project within such a short time frame.
With such a small package, tradeoffs needed to be made for our hardware selection. For instance, all of our selected components needed to be small enough to fit on our compact PCB. Otherwise, the design would be far too large to be worn comfortably on the wrist. This meant we had to choose a very small speaker, which ended up being too small to be driven by the DAC without an external amplifier. Additionally, our OLED screen and battery needed to be quite small to fit within the design parameters. In terms of our software, we were primarily limited by the amount of on-board RAM on the Pi Pico W, which meant we could only use 5 second audio samples for our audio recording and playback application. The limited range of the built-in WiFi antenna also meant we needed to be quite close to the access point to get a reliable connection.
Apart from drawing inspiration from companies that hold numerous patents related to smartwatch design ideas such as Apple, Meta, and Velancell, there are not any specific patents that we were inspired by. The various high-level, user-facing features of such devices like heart rate monitoring, real-time updating, and microphone and speaker integration were the primary inspiration for our design choices.
Our hardware design is centered around our custom 2-layer PCB built to house all components in as compact of a package as possible. This compactness was our driving design principle as we did not want our watch to be unwieldy on the wrist, and we were aiming to have a similar form factor as an Apple Watch. To do this, we considered the placement of the largest components first: the Pico W and the OLED screen. Specifically, we placed the Pico W lengthwise on the back of the PCB, and the OLED screen on the front. Incidentally, the mounting holes for the OLED screen fit just outside the width of the Pico W, meaning we did not have any issues with their bounding boxes colliding. We also placed these components based on the intended direction the watch was to be worn on the wrist, specifically so that a typical right-handed person who would wear a watch on their left wrist would see the screen in the correct orientation (so that we would not have to do any 180 degree flips in software) and the Pico W's antenna would face outward from their wrist to have the best signal integrity. After placing these largest components, we placed the other smaller components in the voids remaining on the board. For the microphone and speaker, it was critical that they were on opposite sides of the board so as to not cause feedback issues, although this did not end up being an issue as we eventually replaced our tiny speaker with a standard 3.5mm headphone jack as the speaker was not suitable to be driven without an on-board amplifier. As such, the microphone was placed at the top left and the speaker/DAC output at the top right. Next, we placed the 1.8V LDO and LiPo connector on the bottom left of the board, and the 6-pin JST connector for the heart rate "daughter" board on the bottom right. For the heart rate sensor itself, we made a separate 2-layer daughter board for it as well as the decoupling capacitors since it needs to press tightly against the skin in order to work properly, and this could not be accomplished if put on the main board. We also placed the DAC and IMU on the right side of the board, as well as the three buttons on the left side, with various decoupling capacitors and resistors placed in any spare locations on either side of the board.
Click to learn more
Click to learn more
Click to learn more
Click to learn more
Click to learn more
Click to learn more
Click to learn more
Given our size constraints for our custom PCB, we needed to design a 3D-printable case that would mount the PCB, the LiPo battery, and the heart rate daughter board together in one compact package while also allowing for cable management and holes for the nylon wrist straps. To do this, we utilized the four mounting holes of the screen as our main connecting structure for the entire "sandwich," running M2.5x20 screws through them and mounted to the underlying case through 3D-printed cylinders. The LiPo battery sits directly below the Pico W and was chosen to have a width less than the distance between the mounting cylinders. We also needed to make sure the Pico W USB port was easily accessible so we could program it. The heart rate sensor daughter board is mounted directly behind the battery using some M2.5x8 screws to secure it to the surface, whose depth was chosen so that the surface of the heart rate sensor would sit flush against the wrist. There is also ample room under the PCB for routing the LiPo and heart rate sensor cables so that they do not get in the way of normal operation. Rectangular slits are also lengthwise on the bottom surface where 1" wide nylon straps were mounted so that the watch could be worn on the wrist. A simple nylon clip design was also made to secure the strap in place when worn.
Project specifications required our total unit cost to be under $125. We selected our parts using this as a guiding constraint. Even accounting for $15 worth of price slack for passive components, connectors, straps, hardware, etc., we are still well under the $125 budget constraint at just around $64 per smartwatch excluding shipping/customs for the custom PCBs.
Part | Qty/Board | Unit Price ($) | Total Price ($) |
---|---|---|---|
Main PCB | 1 | 0.86 | 0.86 |
Daughter PCB | 1 | 0.41 | 0.41 |
Pi Pico W | 1 | 10.99 | 10.99 |
OLED Screen | 1 | 3.60 | 3.60 |
1S LiPo | 1 | 3.00 | 3.00 |
PDM Microphone | 1 | 1.06 | 1.06 |
3.5mm Jack | 1 | 1.02 | 1.02 |
Heart Rate/SPO2 Sensor | 1 | 12.04 | 12.04 |
Angle Rate Sensor | 1 | 8.89 | 8.89 |
DAC | 1 | 3.97 | 3.97 |
LDO | 1 | 0.32 | 0.32 |
Buttons | 3 | 0.98 | 2.94 |
Misc Passives | 1 | ~5.00 | 5.00 |
Misc Connectors | 1 | ~5.00 | 5.00 |
Misc Hardware | 1 | ~5.00 | 5.00 |
Total | 64.10 |
We structure our directory to have a main .c file for our primary software loop, with a corresponding .h and .c file for each peripheral we use on our board. For instance, we have a .h/.c pair for our OLED screen, our heart rate sensor, and our angle rate sensor. Such structure allows for clear hierarchy and abstraction in our code to make it more readable and extensible for future iterations. To simplify includes, we also have a common.h file that every .h file includes that has common libraries such as stdio, math, string, and common definitions that are needed by nearly every .c dependency. For our custom screen images, we have a dat/ subdirectory that contains an array of (x,y) coordinates for each image we draw on the screen as generated by our image_convert.py Python script.
During initialization, we include a power-on-reset delay of 10 ms to allow all voltages to stabilize and all peripherals to properly start up. We then initialize our I2C and SPI busses, as well as initialize the GPIOs for the select and cycle buttons to be inputs with internal pull-downs enabled. We also initialize the ADC for the LiPo voltage reading, as well as call the initialization functions for the OLED screen, the angle rate sensor, the PDM microphone, and the heart rate sensor. Such initialization functions write configuration registers within each device to set it up to take the measurements we desire in our software. Next, we connect to WiFi using the wifi_udp_init() function to obtain a MAC and IP address for the Pico, as well as start the UDP threads for sending and receiving data via UDP packets. In this function, we connect to a beacon, in our case a WiFi hotspot, and receive our IP address back when connected. The status of this connection is shown on the OLED screen, and will time out after 10 seconds if a connection to the pre-programmed SSID in the wifi_udp.c file cannot be obtained. If successful, we are able to instantly poll using network time protocol to receive the real time in EST. A message indicating a connection's success or failure is written to the screen as a result. Next, the core 1 thread is launched which handles the main menu checking and other basic functionalities. Then, the main thread continues to its own loop on core 0 which grabs the latest text message from UDP and fills the character buffer. The UDP operations are performed on a separate core since they are quite resource-intensive and we found that using only a single core caused interference with the SPI interface to the DAC for reasons unknown.
Click to learn more
Click to learn more
Click to learn more
Click to learn more
Click to learn more
Click to learn more
Click to learn more
Click to learn more
Click to learn more
Each app is represented by a small image when cycling through the main menu. We generate this image using our image_convert.py program that takes in a png image as input, resizes it to a specified number of pixels in the x- and y-directions, and extracts the pixels that are not empty by checking if the alpha parameter is not equal to 0. The x and y dimensions of these extracted pixels are written out to a C-style array such that the array contains the (x,y) coordinates of pixels to draw to the screen. Since the screen is monochrome, we need only specify the (x,y) coordinates and not the color. When displaying these images via the generated C-arrays stored in the dat/ subdirectory, for loops can be used to iterate over each pair of (x,y) coordinates and draw them to the screen.
As previously mentioned, one challenge we ran into was not being able to drive the 8-ohm speaker to a sufficient enough volume using the MCP4822 DAC. To solve this, we simply replaced the speaker with a 3.5mm headphone jack so that any audio output device with a built-in amplifier could be used. This ended up working to our benefit, however, since including an amplifier on the PCB would have increased the size, and the speaker's volume would have reduced the discreteness of the smartwatch we were aiming to achieve.
We also intended to use the advertised temperature sensor in the IMU to report real-time temperature in the user interface. Upon testing, however, we discovered that the temperature sensor was not very accurate, and further research on online forums revealed its purpose was primarily to account for sensor drift, thus the relative differences in values read from it were intended by the manufacturer to be used, not the raw values.
Another issue we had was related to interference between the WiFi and SPI modules on the board. We found that using the texting feature and then switching to the audio recording was causing the loop to freeze on playback of the audio. Upon debugging, we found that the spi_write16_blocking() function would work properly for a few transactions, but freeze up about 100 transactions in. By temporarily disabling different peripherals, we identified the issue as being related to the WiFi infrastructure. To fix this, we put all WiFi-related functionality on core 0, and moved all other operations to core 1. By doing this, we no longer had interference between WiFi and SPI, and the audio recording worked correctly.
When using the PDM microphone for the first time, we found that the code would also freeze when calling the pdm_microphone_stop() function. By narrowing down the problem area to the dma_channel_abort() function call in the pdm_microphone_stop() function, we found that calling disable_interrupts() before aborting the DMA, and then calling enable_interrupts() to re-enable them after aborting the DMA fixed the issue.
The accuracy of our data is something our group took pride in. Our real time clock accuracy was checked by comparing against the time on any WiFi-connected device. After leaving the watch on for hours, we confirmed that the times still matched. A similar methodology was used for our stopwatch, for which we compared it against the stopwatch app on an iPhone. For our heart-rate sensor, we compared the reading against those provided by an Apple Watch, and found the measurements to be within 10 BPM, which is quite accurate given the complexity of the algorithm. While we did not have access to a dedicated pulse oximeter, all three of us knew our appropriate percentages were above 95%, and our SPO2 readings always showed at or above this value for all of us. Step tracking was verified by walking a predetermined number of steps that we counted manually and ensuring the reading on the watch matched it.
Additionally, ensuring safety with our watch was paramount for our design. Our first priority when designing the watch was to make it something practical the everyday consumer could use with limited drawbacks. When designing the 3D mount and straps for the watch, we were sure to make the design as sleek and comfortable as possible while fitting everything inside the case. This included making sure the screw heads were mounted against the wrist, instead of the other way around which would cause the ends of the screws to dig into the wrist. For our prototype, we made an open shell to encase everything, but for a final design, we would provide some waterproofing and enclose the shell to make sure all of the electronics were safe. The rechargeability of the battery is something else we made sure to include when making the design in that the Molex connector is easily accessible. As for the sensors utilized, none have been noted to cause any harm to the user, so we can be sure there are no long-term risks associated with our design.
As for usability, one aspect we aimed for was cost-efficiency, meaning we used parts that would get the results we wanted accurately without having to send much money. The placement of the components on the PCB as described in the hardware section were also carefully considered to allow for easy access of the buttons, as well as clear readability of all on-screen elements. Finally, we originally intended for the Pico W to connect to a stationary WiFi network, but eventually decided that this would limit its range to a singular network. This led us to make the decision to connect the watch using a phone's hotspot as most people who would have a smartwatch would likely have a smartphone to pair with it.
In reflection, the goals we set out to meet for this project were achieved. When pitching our smartwatch, our group aimed to create a custom PCB that would incorporate the Pico W, an OLED screen display, an angular rate sensor, mic and speaker, heart rate sensing, and a 3D printed case to hold all the components.
We decided to measure the success of our project in three parts. These aspects would take into account the effectiveness of the watch in reference to our original goal of “health tracking,” if we were able to effectively use the sensors and components we aimed to initially include, and the accuracy of the data we were able to collect from the sensors. Another skill that we took away from this project was the ability to distribute work among all three members to create a cohesive project utilizing each other's strengths while also learning new skills in the process.
The original motive of this project was to take inspiration from each of our passions for health and to partner it with the field of electronics, hence how we came to the idea of making a smartwatch. When defining the constraints for the project, we wanted to make sure we could monitor someone's everyday health with sensors along with using WiFi to enhance the features included. With that being said, we did have some important lessons learned during this project that contributed to some changes in our original design.
During the first prototype of our PCB and soldering our components, we found some of the components' packages to be quite challenging to mount such as the IMU. Luckily, we had access to a hot plate and solder paste which meant we did not have to rely solely on hand soldering. Additionally, we found that the lack of an on-board amplifier prevented us from using an 8-ohm speaker. As such, we ended up using a standard 3.5mm headphone jack to pipe the audio out to an external audio interface, which worked just fine for our application as a lightweight pair of earbuds could be easily plugged in and not add any bulk to the device.
In the mix of the software and hardware interaction, we found that data transmission was the biggest challenge as there was limited documentation for implementation. One example of an obstacle we faced coming into the smartwatch project was that we expected it would be relatively easy to communicate with two watches as walkie-talkies. There were two main limiting factors, that being Pico's limited pool of RAM and the integration of TCP and UDP together. With this, we managed to find some ways to integrate features that would compensate. This brought about the texting feature and the ability to record and playback audio samples up to 5 seconds. While these objectives were not originally planned, our incorporation of these shows our adaptability to solving problems within the scope of our 5 week project window.
In future versions of our project, we would like to include walkie-talkie functionality such that two smartwatches can talk to each other directly over WiFi, which would be accomplished by converting audio samples into UDP packets and sending them over the network. Additionally, we would add the ability for the watch to poll local weather data beased on the assigned IP address such as temperature, cloud cover, and humidity. We did not include this in our project since it requires TCP, which requires significantly more time, effort, and system resources to implement compared to UDP. Along with this, obtaining an approximate geolocation based on IP would also be a welcome addition. Finally, we would also like to include external flash memory on the next version of our smartwatch so that we can store longer audio samples and possibly even entire MP3 files. This way, we could store songs and play them back through the connected audio device, similar to an iPod.
With the conclusion of our prototype of a smartwatch, we are proud to see the work we have put in come to fruition under both tight time as well as budget constraints. We had established a system capable of a real-time clock, audio and microphone accessibility, heart rate and oxygen tracking, step count, stopwatch functionality, and text communications all with a rechargeable battery and a Raspberry Pi Pico W. This system made use of our experiences in PCB and circuit design, sensor data extraction, and microcontroller programming. We also enhanced our knowledge of communication protocols and physics related knowledge of how sensors can monitor and interact with metrics related to our health. Our team has met the goals we aimed to achieve coming into this project and produced a completely functional product within just a 5 week period.
In terms of IP considerations, all of our publicly-available code is shown in the Appendix section. The hardware design of the smartwatch was fully designed by our group, as well as the non-public software code.
The group approves this report for inclusion on the course website.
The group approves the video for inclusion on the course YouTube channel.
SPH0641LM4H-1 Microphone Datasheet
Parker was responsible for the high-level design of the PCB as well as the CAD for the case. He was also responsible for the overall software infrastructure including the menu system and basic sensor integration.
Katarina was responsible for PCB layout, integration of the heart rate sensor and angle rate sensor, and website design.
George was responsible for the integration of the Pico W libraries to make use of UDP and NTP for the real-time clock and communication between devices.
We are a group of friends with different interests!
Parker is a senior and early MEng student at Cornell University. He enjoys computer architecture and Digital VLSI.
Katarina is a senior and early MEng student at Cornell University. She enjoys Analog and Mixed-Signal Design, and Digital VLSI.