[Elixir Nerves] Potentiometer with SPI-based Analog to Digital Converter
![[Elixir Nerves] Potentiometer with SPI-based Analog to Digital Converter](/_next/image?url=https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1612787131292%2F_8Y7cKDUZ.jpeg&w=3840&q=75)
Search for a command to run...
![[Elixir Nerves] Potentiometer with SPI-based Analog to Digital Converter](/_next/image?url=https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1612787131292%2F_8Y7cKDUZ.jpeg&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...
I will talk about how to read values from a potentiometer using an SPI-based Analog to Digital Converter (ADC). This is my study note. I am hoping it will help somebody.


I found the following resources helpful:
There are different types of ADC out there. The higher the resolution, more accurate (10-bit vs 12-bit). The more channels, the more different inputs you can read (2-channel vs 8-channel).
I chose MCP3002 (10-bit resolution 2-channel) because I need neither high resolution or many channels.
Some options
| MCP3002 | Raspberry Pi | Potentiometer |
| VDD/VREF | 3.3V | Vcc (either side) |
| CLK | SPI0 SCLK (Serial Clock) | - |
| Dout | SPI0 CIPO (Controllor In Peripheral Out) | - |
| Din | SPI0 COPI (Controllor Out Peripheral In) | - |
| CS/SHDN | SPI0 CS (Chip Select) | - |
| Vss | GND | GND (either side) |
| CH0 | - | wiper pin (middle) |
It is explained in Figure 6-1 of the MCP3002 datasheet.
# Raspberry Pis have two SPI buses.
iex> Circuits.SPI.bus_names
["spidev0.0", "spidev0.1"]
# Open the connection to one of them.
iex> {:ok, ref} = Circuits.SPI.open("spidev0.0")
{:ok, #Reference<0.3977234826.537788429.135085>}
We specify which channel we want to read data from, configuring the config bits in Table 5-1 of the MCP3002 datasheet.
For using Ch0, the data we need to transmit is two bytes like the following, where x is ignored.
| Start | SGL/DIFF | ODD/SIGN | MSBF | ||||||||||||
| x | 1 | 1 | 0 | 1 | x | x | x | x | x | x | x | x | x | x | x |
| bit | value | description |
| Start | 1 | always 1 |
| SGL/DIFF | 1 | single-ended-mode Ch0 |
| ODD/SIGN | 0 | single-ended-mode Ch0 |
| MSBF | 1 | most-significant bit first |
In Elixir, the above two bytes can be expressed as follows:
# First byte as an integer
iex> 0b01101000
104
iex> 0x68
104
# Second byte as an integer
iex> 0b00000000
0
iex> 0x00
0
# Together as a binary
iex> ch0 = <<0x68, 0x00>>
<<104, 0>>
Then we send it to the peripheral (MCP3002).
iex> {:ok, <<_::size(6), value::size(10)>>} = Circuits.SPI.transfer(ref, ch0)
{:ok, <<1, 197>>}
Since the resolution of MCP3002 is 10-bit (0..1023), we only read low 10 bits and ignore the rest.
# Min value of the potentiometer when the potetiometer is at one limit.
iex> with {:ok, <<_::6, value::10>>} <- Circuits.SPI.transfer(ref, ch0), do: value
1
# Max value of the potentiometer when the potetiometer is at the other limit.
iex> with {:ok, <<_::6, value::10>>} <- Circuits.SPI.transfer(ref, ch0), do: value
1023
Once we are able to read values from the potentiometer, we will most likely need to map the value to a different range, such as percentage.
defmodule MyModule do
@doc """
## Examples
iex> MyModule.map_range(65, {0, 1023}, {0, 100})
6.35386119257087
"""
def map_range(x, {in_min, in_max}, {out_min, out_max}) do
(x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
end
end
Once started, this demo will keep on logging the potentiometer value every second.
Only dependency is the elixir-circuits/circuits_spi library. As long as it is installed, you can just SSH into your target device and copy and paste the following snippet to play with the demo.
Or you could use my example Elixir/Nerves app as a playground.
defmodule SpiPotentiometer do
@moduledoc """
## Examples
RingLogger.attach
(
"spidev0.0"
|> SpiPotentiometer.open_potentiometer()
|> SpiPotentiometer.read_potentiometner_forever(1000)
)
"""
require Logger
def open_potentiometer(spi_device) do
{:ok, ref} = Circuits.SPI.open(spi_device)
ref
end
def read_potentiometner_forever(spi_ref, interval \\ 1000) do
{:ok, <<_::size(6), ten_bit_value::size(10)>>} = Circuits.SPI.transfer(spi_ref, <<0x68, 0x00>>)
Logger.info("#{ten_bit_value} (#{map_range(ten_bit_value, {0, 1023}, {0, 100})}%)")
Process.sleep(interval)
read_potentiometner_forever(spi_ref, interval)
end
defp map_range(x, {in_min, in_max}, {out_min, out_max}) do
(x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
end
end

We could apply this technique to many other things like LEDs and servo motors. I used

Here is my source code.
This might be nothing special for electrical engineers but to me an Elixir/Nerves hobbist it was quite challenging. This post is actually for myself so that I can do the same thing quickly next time. That would be great if this post helps someone.