My Experience Starting With Open Source

This is it.

This is the article that made me want to actually put in the effort to create this blog, and this is the fateful story

How It Happened

Recently(ish) I decided to contribute to some open source projects and I took the advice I had been given on youtube for getting started (cause how else am I supposed to learn anything) with open source and I made a contribution to... Drum Rooollll...

Yep! You guessed it!

the documentation

Kind of underwhelming, I know.

But it was definitely a good way to get started with open source work. It didn't cause me stress worrying if it was high enough quality and I leared how to actually open and work a pull request.

Let me show you

The image below is the first PR that I ever opened. Time First PR When I was using the Slint (then sixtyfps) framework I found a part of the documentation that was confusing to me and I opened a PR that made some clarifications.

The Result!

First PR result Woooohoooo!!!!

I was crazy excited for this PR to be merged. I probably bragged to my wife about my awesome changes to the docs for long enough to get some funny looks.

It seems kinda insignificant, but, to me, it was an awesome moment.

And Some corrections...

First PR Corrections

The Slint team members were super nice to work with

Why Docs??

For me, this has two major benefits.

The first is that I can get a feel for how a particular project is run. They might have a particular merge strategy and they prefer if all of the commits are squashed. Or maybe something crazy! There are process details like this that I feel are nice to get out of the way with low pressure.

But the second is probably the bigger reason.

I don't want to spend a chunk of my time working on a project when the maintainers are hostile.

A small change to the docs doesn't take too much time and I know I won't feel hurt (relatively) if it essentially becomes wasted time. This has never happened to me and it doesn't seem common, but I have seen it. I defnitely don't want to have to start working on a project with hostile maintainers.

Why at All?

I decided to start contributing to open source projects because I was going to be applying for summer internships and I was hoping that open source contributions would look good on a resume and in interviews.

And that was honestly the main motivation for my first PR.

Since then, I have found that I genuinely enjoy the challenge of working on new projects and unfamiliar codebases. I enjoy seeing other people use, and improve on my changes. It is definitely a great way to learn and get exposure to a variety of codebases.

What Was the Biggest Challenge in Getting Started?

Fear.

That sums it up pretty well.

I was afraid of doing something that was just an inconvenience to other developers.
I was afraid of implementing something incorrectly.
I was afraid that I would screw up the process.
I was afraid of being out of place.

Luckily, the Slint developers were super kind and helpful and made my first experiences with open souce work an awesome experience that made me want to do it again.

I also felt lucky that there was high quality content on youtube that showed the process of opening a PR and working with git to the point that I thought

"Yeah. I can do that."

The Results

The results came in the form of continued pull requests on several open source projects

Slint

Since that first PR I've been able to open a few other PR's on the Slint project and actually dig into the code. Bit by bit (haha), I was able to find my way around the codebase and find the relevant parts of the code.

One of the code changes I made that was actually merged was adding some built-in functions to the Slint language

    
0 Expression::BuiltinFunctionReference(BuiltinFunction::Log, _) => {
1 let x: f64 = eval_expression(&arguments[0], local_context).try_into().unwrap();
2 let y: f64 = eval_expression(&arguments[1], local_context).try_into().unwrap();
3 Value::Number(x.log(y))
4 }
5 Expression::BuiltinFunctionReference(BuiltinFunction::Pow, _) => {
6 let x: f64 = eval_expression(&arguments[0], local_context).try_into().unwrap();
7 let y: f64 = eval_expression(&arguments[1], local_context).try_into().unwrap();
8 Value::Number(x.powf(y))
9 }

This was the largest essential change in the PR where the functionality for pow and log functions were added to be language built-ins.

And again, the Slint maintainers were awesoeme to work with and answer my questions. #notanad

Esp-Hal

The next project I worked on was ESP-HAL, an open source hardware abstraction layer for espressif microcontrollers in rust.

My work on ESP-HAL started after I got my first internship!

Thank you, thank you. I know. You're too kind. Hold the applause please.

Haha, jokes aside, I was super happy to have my first internship. I was working as a firmware engineering intern in C for some internal company tools. It was an awesome opportunity because it was a brand new project that another intern and I built mostly from scratch.

While I was working there, I started to really fall in love with embedded development and, simultaneously, I wanted to add some more rust to my life.

So that's what I did.

I tried getting an LED to blink using a microcontroller that had no existing support in rust.

And wow. I did not know what I was in for

It was tons of time

I spent a very long time trying to figure out how to support it in rust and then one day it worked!!!

The LED BLINKED!!!!

It was probably in the top 3 most magical moments of my life.

I had blinked LED's before using an arduino but I had made this thing work by following a datasheet and building periphal access crates and writing to registers and AN L-E-D BLINKED!!!

I probably just ranted about how cool it was for at least 2 hours. After saying "IT WORKS!!" and "THE LED BLINKS" for probably the 300th time I knew my wife must truly love me because she was still replying with "Wow. Yeah that's really cool."

Unfortunately, I can't find the original code that I used to blink the led. Figuring it out again would involve digging into the datasheet to see exactly what bits need to be set, but it would have started about like this (after finally generating a peripheral access crate from an svd file)

    
0#![no_std]
1#![no_main]
2
3use cortex_m::asm;
4use cortex_m_rt::entry;
5use panic_halt as _;
6
7#[entry]
8fn main() -> ! {
9 let peripherals = psoc4_pac::Peripherals::take().unwrap();
10 peripherals.GPIO.prt1.pc.write(|w| unsafe { w.dm3().bits(6) });
11 peripherals.GPIO.prt1.dr.write(|w| w.data3().set_bit());
12
13 loop {
14 // delay and blinking goes here
15 }
16}
17

This code gets a global singleton of the peripherals and then sets both a configuration register to control the pin drive mode and sets another register with the data to set the pin high.

All this work led me to find projects that had already built out a lot of support for chips in rust in the form of "Hardware Abstraction Layers" or "HALS". So far my favorite of these is the ESP-HAL.

The work I did involved implementing one of the greatest things about rust and embedded programming. Currently, rust is a super exciting language for writing embedded code because of a project called embedded-hal that defines a set of common traits that allow device drivers to be hardware independent!

And that is crazy cool because previously any time you wanted to use an external peripheral you had to write a custom driver for your target microcontroller to interface with the device. But now it really is as easy as writing the device driver once and using it on every microcontroller.

Now, there is an awesome series of fairly cheap but powerful microcontrollers from espressif... and rust is awesome...

So back to the purpose of this section...

I was building a device driver for a digital to analog converter DACx0501 and I worked a bit on the SPI support in ESP-HAL.

    
0#[cfg(feature = "eh1")]
1 impl<T> embedded_hal_1::spi::blocking::SpiBusWrite for Spi<T>
2 where
3 T: Instance,
4 {
5 fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> {
6 self.spi.send_bytes(words)
7 }
8 }
9
10 #[cfg(feature = "eh1")]
11 impl<T> embedded_hal_1::spi::blocking::SpiBusFlush for Spi<T>
12 where
13 T: Instance,
14 {
15 fn flush(&mut self) -> Result<(), Self::Error> {
16 self.spi.flush()
17 }
18 }

This code is the main driver code that implements the embedded_hal traits and calls out to other functions.

Essentially it allows users of the HAL to call two functions.

The write function takes in any stream of bytes and transfers them over the SPI protocol using the device's built in hardware SPI controller.

The flush function ensures that the bus is free (allowing the user to know that the transfer has finished).

And the implementation

    
0 fn send_bytes(&mut self, words: &[u8]) -> Result<(), Infallible> {
1 let reg_block = self.register_block();
2 let num_chuncks = words.len() / 64;
3
4 for (i, chunk) in words.chunks(64).enumerate() {
5 self.configure_datalen(chunk.len() as u32 * 8);
6
7 let mut fifo_ptr = reg_block.w0.as_ptr();
8 for chunk in chunk.chunks(4) {
9 let mut u32_as_bytes = [0u8; 4];
10 unsafe {
11 let ptr = u32_as_bytes.as_mut_ptr();
12 ptr.copy_from(chunk.as_ptr(), chunk.len());
13 }
14 let reg_val: u32 = u32::from_le_bytes(u32_as_bytes);
15
16 unsafe {
17 *fifo_ptr = reg_val;
18 fifo_ptr = fifo_ptr.offset(1);
19 };
20 }
21
22 self.update();
23
24 reg_block.cmd.modify(|_, w| w.usr().set_bit());
25
26 // Wait for all chunks to complete except the last one.
27 // The function is allowed to return before the bus is idle.
28 // see [embedded-hal flushing](https://docs.rs/embedded-hal/1.0.0-alpha.8/embedded_hal/spi/blocking/index.html#flushing)
29 if i < num_chuncks {
30 while reg_block.cmd.read().usr().bit_is_set() {
31 // wait
32 }
33 }
34 }
35 Ok(())
36 }
37
38 // Check if the bus is busy and if it is wait for it to be idle
39 fn flush(&mut self) -> Result<(), Infallible> {
40 let reg_block = self.register_block();
41
42 while reg_block.cmd.read().usr().bit_is_set() {
43 // wait for bus to be clear
44 }
45 Ok(())
46 }

If you think this code looks impressive I'll feel better about the rather long amount of time that it took me to write it...

It takes the stream of bytes and chucks them in 64 byte chuncks and then fills the devices registers with that data and then sets a flag bit on a control register that allows the device to start sending data that is in the registers.

Oh, and then it totally worked for me!

I used it to test/finish implementing my driver for the digital to analog converter.

My Own Projects

I guess I can also count my personal projects as open source... since they are... open source.

Tree-Sitter-Slint

More Slint stuff!!

This was just a project that I started as a quality of life improvement when I was frustrated by the complete lack of color in the Slint language.

Tree-sitter is a tool that allows you to write a language gramer in javascript and is primarily used for syntax highliting on code (it's what I'm using to highlight the code on this website).

So I wrote a grammar that handles Slint code!

(The code below isn't fully functional. Just meant to show off the syntax highlighting)

    
0
1MemoryTile := Rectangle {
2 border-radius: 8px;
3 callback clicked;
4 callback some-callback;
5 property <bool> open-curtain;
6 property <bool> solved;
7 property <image> icon;
8
9 background: solved ? #70ff00 : #858585;
10 animate background { duration: 800ms; }
11
12 clicked => {
13 if (condition) {
14 foo = 42;
15 } else if (other-condition) {
16 bar = 28;
17 } else {
18 foo = 4;
19 }
20 }
21 // Left curtain
22 Rectangle {
23 background: #0025ff;
24 border-radius: 4px;
25 width: open-curtain ? 0px : parent.width / 2 + 4px;
26 height: parent.height;
27 animate width { duration: 250ms; easing: ease-in; }
28 clip: true;
29
30 Image {
31 width: root.width - 32px;
32 height: root.height - 32px;
33 x: 16px;
34 y: 16px;
35 source: @image-url("icons/tile_logo.png");
36 }
37 }
38
39 states [
40 disabled when !is-enabled : {
41 color: gray; // same as root.color: gray;
42 root.color: white;
43 }
44 down when pressed : {
45 background: blue;
46 }
47 ]
48
49 transitions [
50 out disabled : {
51 animate * { duration: 800ms; }
52 }
53 in down : {
54 animate color { duration: 300ms; }
55 }
56 ]
57}

Vs what it basically looked like before a proper grammar

    
0MemoryTile := Rectangle {
1 border-radius: 8px;
2 callback clicked;
3 callback some-callback;
4 property <bool> open-curtain;
5 property <bool> solved;
6 property <image> icon;
7
8 background: solved ? #70ff00 : #858585;
9 animate background { duration: 800ms; }
10
11 clicked => {
12 if (condition) {
13 foo = 42;
14 } else if (other-condition) {
15 bar = 28;
16 } else {
17 foo = 4;
18 }
19 }
20 // Left curtain
21 Rectangle {
22 background: #0025ff;
23 border-radius: 4px;
24 width: open-curtain ? 0px : parent.width / 2 + 4px;
25 height: parent.height;
26 animate width { duration: 250ms; easing: ease-in; }
27 clip: true;
28
29 Image {
30 width: root.width - 32px;
31 height: root.height - 32px;
32 x: 16px;
33 y: 16px;
34 source: @image-url("icons/tile_logo.png");
35 }
36 }
37
38 states [
39 disabled when !is-enabled : {
40 color: gray; // same as root.color: gray;
41 root.color: white;
42 }
43 down when pressed : {
44 background: blue;
45 }
46 ]
47
48 transitions [
49 out disabled : {
50 animate * { duration: 800ms; }
51 }
52 in down : {
53 animate color { duration: 300ms; }
54 }
55 ]
56}

(This is actually just trying to highlight this as css but it's pretty close to what it looked like before)

Closing Thoughts

My open source work (and programming in general) has already been a long journey, but I'm just begginning. I've learned so much and there's no way I'm stopping.

But even with everything I'm learning, I'm highly suspicious that, in the future, any new project I contribute to will probably first receive a new contributor to the documentation.

Sunday, January 1, 2023