[Elixir Nerves] Smoothly dimming LEDs using a servo driver
![[Elixir Nerves] Smoothly dimming LEDs using a servo driver](/_next/image?url=https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1612786153663%2Fc-Aet34L-.gif&w=3840&q=75)
Search for a command to run...
![[Elixir Nerves] Smoothly dimming LEDs using a servo driver](/_next/image?url=https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1612786153663%2Fc-Aet34L-.gif&w=3840&q=75)
I wrote a script called phx-docker-compose-new that can be used instead of mix phx.new to generate a new Phoenix application. Building the development environment for a Phoenix application using Docker Compose is convenient because you can set up not...
この記事について Raspberry Pi、TensorFlow、Pythonのいずれにも詳しくない筆者が、物体検出をやって楽しんだ成果の記録です。 TensorFlow公式の物体検出のサンプルプログラムを実行します。 動作環境 ボード Raspberry Pi 4 Model B OS Raspberry Pi OS (32-bit または 64-bit) デスクトップ環境 カメラ Raspberry Pi カメラモジュール v2 Python Python ...
One day I was trying to upgrade one of my Phoenix 1.5 apps to Phoenix 1.6. In that app, I customize Bootstrap styles using SCSS. I want to talk about what I found and stumbled on when setting up SCSS. TIL All explained in Phoenix 1.6 Asset Managemen...
This is written in Japanese. I might convert it to English later, maybe. はじめに Elixirのテストでモックを用意するときに利用するElixirパッケージとして、moxが人気です。Elixir作者のJosé Valimさんが作ったからということもありますが、ただモックを用意するだけではなくElixirアプリの構成をより良くするためのアイデアにまで言及されているので、教科書のようなものと思っています。 一言でいうと「その場...
TIL Access supports keyword lists (Keyword) and maps (Map) out of the box, but not structs. There is a reason why Elixir does not implement Access behaviour for structs. Perfer data.key for accessing predefined atom keys of map or struct. Access be...
It is so pleasant to do the IoT programming in Elixir thanks to the Nerves IoT platform. Elixir allows us to write beautiful, fluent and readable code for problems that would look scary in other languages. This is another random study note about my learning electronics through Elixir programming.
Anyway, today I feel like controlling the brightness of LEDs from my Raspberry Pi Zero W that is powered by the Nerves IoT platform. Before getting started, we need to understand what the Pulse Width Modulation (PWM) is and how we can use PWM from a Raspberry Pi if not already.
Long story short, it is about controlling the ratio of on-time to off-time of the waveform. In other words, we express analog data using digital signals. I personally like this image below from Wikipedia because it explains very concisely what PWM essentially is.
In the context of controlling an LED through GPIO, we can do two things using PWM, which was intriguing to me when I first learned it.
When the frequency (Hz) is low for example 1-2Hz, we can actually see the LED switching on and off on a regular basis. So by changing the duty cycle, we can change the timing of the blinking. It is fun.
As the frequency goes higher for example up to 100Hz, we no longer can observe the blinking beyond a certain point. With such a frequency, the duty cycle acts like the brightness. In other words, we can consider the area of the graph of the waves as the brightness. They say this technique of converting digital signal into analog is quite common and is applied to many different things such as controlling a servo motor.
Roughly speaking there are two ways to control PWM from Raspberry Pi.
According to Raspberry Pi's GPIO usage documentation, here are the pins that PWM is available on:
In Elixir, the pigpiox library provides functions that allow us to build and send waveforms with pigpio daemon.
gpio = 12
frequency = 100
# I don't know what unit it is but `1_000_000` was the maximum value.
Pigpiox.Pwm.hardware_pwm(gpio, frequency, 1_000_000)
Using an external servo driver board like Adafruit 16-Channel PWM/Servo Driver, we have more flexibility with more channels. The downside was there was no Elixir library for using that servo driver; however as I studied Adafruit's official Python code, it was not easy but not rocket science either. So I decided to write my own library in Elixir. It would be great if it helps other alchemists. The key thing is that board uses PCA9685 PWM controller, so the chip's data sheet is the single source of truth.
Using the mnishiguchi/servo_kit library, here is how to change the brightness of an LED.
# Initialize a driver.
driver = ServoKit.PCA9685.new(%{i2c_bus: "i2c-1", frequency: 100})
# Set the duty cycle to 66.6% for Channel 15.
ServoKit.PCA9685.set_pwm_duty_cycle(driver, 15, 66.6)
Here are the items I prepared:
I use channel 0, 4 and 8. The servo connectors have three pins for each channel:
But for LEDs, all we need is control signals and common ground.


Here is the code I wrote for this demo. It was inspired by Adafruit's RGB Backlit LCDs tutorial.
driver = ServoKit.PCA9685.new(%{i2c_bus: "i2c-1", frequency: 100})
ch_red = 0
ch_green = 4
ch_blue = 8
loop_fn = fn loop_fn ->
(0..99) |> Enum.each(fn x ->
driver
|> ServoKit.PCA9685.set_pwm_duty_cycle(ch_red, x)
|> ServoKit.PCA9685.set_pwm_duty_cycle(ch_green, 0)
|> ServoKit.PCA9685.set_pwm_duty_cycle(ch_blue, 99 - x)
Process.sleep(50)
end)
(0..99) |> Enum.each(fn x ->
driver
|> ServoKit.PCA9685.set_pwm_duty_cycle(ch_red, 99 - x)
|> ServoKit.PCA9685.set_pwm_duty_cycle(ch_green, x)
|> ServoKit.PCA9685.set_pwm_duty_cycle(ch_blue, 0)
Process.sleep(50)
end)
(0..99) |> Enum.each(fn x ->
driver
|> ServoKit.PCA9685.set_pwm_duty_cycle(ch_red, 0)
|> ServoKit.PCA9685.set_pwm_duty_cycle(ch_green, 99 - x)
|> ServoKit.PCA9685.set_pwm_duty_cycle(ch_blue, x)
Process.sleep(50)
end)
loop_fn.(loop_fn)
end
loop_fn.(loop_fn)
To me as an electronics noob, PWM looked a bit scary at first, but once getting used to it, its basic use is relatively easy. Specialists seem to precisely calculate and monitor the waveform, but I guess casual hobbyists do not need to worry about it.
Armed with PWM, we can go further, using a potentiometer, controlling a servo, changing RGB-backlit LCD display's background colors, etc.
