Hi folks!
I am on a quest to use Julia to drive an E-Ink Display that I am running from a Raspberry Pi 4 (Raspbian OS 64-bit). Here is an image of what I would like to be seeing happen:
This is using a Python library ā but letās use Julia instead! This little post is me summarizing what I am trying to do, what I have accomplished so far, and obstacles I have encountered.
Please help! I would love to put this up on my wall:
Goal: A Julia-Driven E-Ink Display
My mission is simple: I want to drive an E-Ink display using Julia running on a Raspberry Pi 4! Specifically, here is the hardware and software I am using:
- Raspberry Pi 4B (64GB SD Card)
- Waveshare 800Ć480, 7.5inch E-Ink display HAT for Raspberry Pi, SPI interface
- Raspbian OS (64-bit)
- Julia 1.10.7 (via juliaup)
- PiGPIO.jl 0.2.1
I have wanted to do this for quite some time and now am on holidays ā so, I have some time now to tackle this fun engineering problem! Specifically, what I would like to accomplish in terms of features is the following:
- Communicate and update GPIO pin states
- Use SPI to interface with the E-Ink display
- Transmit images to E-ink display
- Periodic refresh of E-ink display
- Create a dashboard to display info I want
- Daily agenda
- TODOs
In terms of priority, the first 4 tasks are the most critical. I have the first task done thanks to PiGPIO.jlās support for GPIO which is pretty rad!
The Story So Far!
So, Waveshare actually has a great foundation for interacting with the E-ink display via Python. The files that are most pertinent are:
In fact, I have begun translating some of the code from epdconfig.py and epd_7in5_V2_test.py into a new Julia package that I am calling Jinkies.jl. Jinkies.jl is built upon PiGPIO.jl and is building properly on the Pi.
Jinkies! Julia: E-Ink made Easy!
Obstacles and Questions
Some of the most outstanding obstacles and questions I have run into are as follows:
Obstacle 1: I have no idea how to get SPI communication working from within Julia. PiGPIO.jlās support for SPI is completely broken; here is an example:
julia> using PiGPIO
julia> p = Pi()
[ Info: Successfully connected!
Pi("localhost", 8888, true, PiGPIO.SockLock(Sockets.TCPSocket(RawFD(17) open, 0 bytes waiting), ReentrantLock(nothing, 0x00000000, 0x00, Base.GenericCondition{Base.Threads.SpinLock}(Base.IntrusiveLinkedList{Task}(nothing, nothing), Base.Threads.SpinLock(0)), (0, 0, 0))), PiGPIO.CallbackThread(PiGPIO.SockLock(Sockets.TCPSocket(RawFD(17) open, 0 bytes waiting), ReentrantLock(nothing, 0x00000000, 0x00, Base.GenericCondition{Base.Threads.SpinLock}(Base.IntrusiveLinkedList{Task}(nothing, nothing), Base.Threads.SpinLock(0)), (0, 0, 0))), PiGPIO.SockLock(Sockets.TCPSocket(RawFD(18) paused, 0 bytes waiting), ReentrantLock(nothing, 0x00000000, 0x00, Base.GenericCondition{Base.Threads.SpinLock}(Base.IntrusiveLinkedList{Task}(nothing, nothing), Base.Threads.SpinLock(0)), (0, 0, 0))), true, true, 0, 0x00000000, Any[]))
julia> h = PiGPIO.spi_open(p, 1, 100000, Int32(0))
ERROR: MethodError: no method matching write(::IOBuffer, ::Int32)
You may have intended to import Base.write
Closest candidates are:
write(::Pi, ::Any, ::Any)
@ PiGPIO ~/.julia/packages/PiGPIO/U0QFH/src/pi.jl:458
Stacktrace:
[1] spi_open(self::Pi, spi_channel::Int64, baud::Int64, spi_flags::Int32)
@ PiGPIO ~/.julia/packages/PiGPIO/U0QFH/src/spiSerial.jl:92
[2] top-level scope
@ REPL[5]:1
I went through a very onerous debugging process for several hours yesterday testing out various versions of Julia (including 1.0, 1.3, 1.5, 1.6, and 1.10) and concluded the code itself is actually flawed. Once I kinda patched spi_open
for example, I would run into further problems within PiGPIO.jl. Hereās the code that was involved in the failures:
PiGPIO Problems with SPI
Erroneous code from within spiSerial.jl:
function spi_open(self::Pi, spi_channel, baud, spi_flags=0)
# I p1 spi_channel
# I p2 baud
# I p3 4
## extension ##
# I spi_flags
extents=IOBuffer()
write(extents, spi_flags::Cint)
return _u2i(_pigpio_command_ext(
self.sl, _PI_CMD_SPIO, spi_channel, baud, 4, extents))
end
function _u2i(x::UInt32)
v = convert(Int32, x)
if v < 0
if exceptions
error(error_text(v))
end
end
return v
end
Erroneous code from within pi.jl:
"""
Runs an extended pigpio socket command.
* `sl`: command socket and lock.
* `cmd`: the command to be executed.
* `p1`: command parameter 1 (if applicable).
* `p2`: command parameter 2 (if applicable).
* `p3`: total size in bytes of following extents
* `extents`: additional data blocks
"""
function _pigpio_command_ext(sl, cmd, p1, p2, p3, extents, rl=true)
ext = IOBuffer()
Base.write(ext, Array(reinterpret(UInt8, [cmd, p1, p2, p3])))
for x in extents
write(ext, string(x))
end
lock(sl.l)
write(sl.s, ext)
msg = reinterpret(Cuint, sl.s)[4]
if rl
unlock(sl.l)
end
return res
end
So, this led me to looking at @Ronis_BR 's project BaremetalPi.jl which seems to handle SPI interfaces but I have no idea how to really use it and it doesnāt seem to work on my Pi:
julia> using BaremetalPi
julia> init_spi("/dev/spidev0.0", max_speed_hz = 1000)
julia> tx_buf = [0x01, 0x80, 0x00]
3-element Vector{UInt8}:
0x01
0x80
0x00
julia> rx_buf = zeros(UInt8, 3)
3-element Vector{UInt8}:
0x00
0x00
0x00
julia> ret = spi_transfer!(1, tx_buf, rx_buf)
3
julia> rx_buf
3-element Vector{UInt8}:
0x00
0x00
0x00
I also took a look at @notinaboat 's PiGPIOC.jl which provides a Clang.jl-based wrapper for pigpio. Again however, I was a bit lost as to how to make this work.
Obstacle 2: I am struggling with translate some of the Python code to Julia. Overall, I would like to be able to translate the following Python code:
# Translated to Julia
def digital_write(self, pin, value):
if pin == self.RST_PIN:
if value:
self.GPIO_RST_PIN.on()
else:
self.GPIO_RST_PIN.off()
elif pin == self.DC_PIN:
if value:
self.GPIO_DC_PIN.on()
else:
self.GPIO_DC_PIN.off()
elif pin == self.PWR_PIN:
if value:
self.GPIO_PWR_PIN.on()
else:
self.GPIO_PWR_PIN.off()
# Translated to Julia
def reset(self):
epdconfig.digital_write(self.reset_pin, 1)
epdconfig.delay_ms(20)
epdconfig.digital_write(self.reset_pin, 0)
epdconfig.delay_ms(2)
epdconfig.digital_write(self.reset_pin, 1)
epdconfig.delay_ms(20)
# Unsure how to translate this
def spi_writebyte(self, data):
self.SPI.writebytes(data)
# Unsure how to translate this
def send_command(self, command):
epdconfig.digital_write(self.dc_pin, 0)
epdconfig.digital_write(self.cs_pin, 0)
epdconfig.spi_writebyte([command])
epdconfig.digital_write(self.cs_pin, 1)
def send_data(self, data):
epdconfig.digital_write(self.dc_pin, 1)
epdconfig.digital_write(self.cs_pin, 0)
epdconfig.spi_writebyte([data])
epdconfig.digital_write(self.cs_pin, 1)
Into Julia. Here are methods I have tried so far:
function digital_write(p, pin, value)
if pin == RST_PIN
if value
PiGPIO.write(p, RST_PIN, PiGPIO.ON)
else
PiGPIO.write(p, RST_PIN, PiGPIO.OFF)
end
elseif pin == DC_PIN
if value
PiGPIO.write(p, DC_PIN, PiGPIO.ON)
else
PiGPIO.write(p, DC_PIN, PiGPIO.OFF)
end
elseif pin == PWR_PIN
if value
PiGPIO.write(p, PWR_PIN, PiGPIO.ON)
else
PiGPIO.write(p, PWR_PIN, PiGPIO.OFF)
end
end
end
function reset(p::Pi)
digital_write(p, reset_pin, true)
delay_ms(20)
digital_write(p, reset_pin, false)
delay_ms(2)
digital_write(p, reset_pin, true)
delay_ms(20)
end
# Unsure how to translate this
function send_command(p, command):
digital_write(p, DC_PIN, false)
digital_write(p, CS_PIN, false)
# Unsure how to translate this
epdconfig.spi_writebyte([command])
digital_write(p, CS_PIN, true)
end
The SPI interfacing is the most confusing to me at the moment. However, if I can get spi_writebyte
, send_command
, and send_data
translated properly, then I can keep proceeding as I could do something like this:
self.reset()
self.send_command(0x06)
self.send_data(0x17)
to properly initialize my display.
Final Thoughts
So far, we are making progress, but I am stuck now! Would anyone be able to give some thoughts and guidance on how to best proceed? I was thinking if need be, maybe I should using PiGPIO.jl GPIO communication and BaremetalPi.jl or PiGPIOC.jl for SPI communication?
What do people think? I am going to CC a few folks whose opinions I would love to hear: @notinaboat @Ronis_BR @Sukera @Alexander-Barth @avik @yakir12 @asinghvi17
Cheers and happy holidays
~ tcp