[GUIDE] Setup I2C control for ES9018K2M DAC with Volumio 2

Hi,

this guide shows how to setup Volumio 2 with an ES9018K2M DAC with I2C control. It is based on this guide but instead of using UART commands I rewrote the pyton script to communicate to the DAC through it’s native I2C interface. It works on the cheap boards like shown in foto of my setup (RPi 3 with Waveshare 7" touch screen, see here).

volumio_electronics.jpg

The problem with this DAC is that information about it is scarce and with it’s default settings it can only play 32bit and 24bit I2S streams so all audio has to be resampled. To make it play 16bit audio natively the configuration register has to be adjusted (for example to play Spotify on Volumio).
Also in order to take advantage of the hardware volume control the registers for left and right channel attenuation have to be set.
The python script takes care of that by constantly polling the Volumio status and updating the DACs registers if needed.

Step by step setup:

Install Volumio, set I2S DAC to ‘on’ and choose ‘Volumio ESS 9028QM’ as the DAC. Reboot to make it work (selecting another I2C DACs may also work, I tested it only like this)

Setup the network connection through the Volumio webinterface, an internet connection is required. Connect to your RPi through SSH (Activate SSH on Volumio first)

Check that a DAC is recognized (listing as ‘hifiberry’)

aplay -l

must show card1 as something like:
card 1: sndrpihifiberry [snd_rpi_hifiberry_dac], device 0: HifiBerry DAC HiFi pcm5102a-hifi-0 [HifiBerry DAC HiFi pcm5102a-hifi-0] Subdevices: 1/1

Leave the other output settings unchanged. You should now be able to play 24bit files and radio streams but 16bit files will sound horrible. Check that you have the I2C lines properly connected: SCL is on pin 3 and SDA is on pin 4 of the DAC. Also make sure your board has pullup resistors to the 3.3V supply, add 4.7k resistors if it does not.

Now that the DAC is up and running, let’s add hardware volume control through I2C. First add a dummy volume control:

sudo nano /var/lib/alsa/asound.state

add these lines at the end (there may already be a similar block called state.sndrpihifiberry , can just replace that)

state.sndrpihifiberry { control.1 { iface MIXER name Digital value.0 99 value.1 99 comment { access 'read write user' type INTEGER count 2 range '0 - 99' tlv '00000001000000080000000000000032' dbmin 0 dbmax 4950 dbvalue.0 4950 dbvalue.1 4950 } } }

then run

sudo alsactl restore 1

to load the new digital mixer.
check it shows up by running:

amixer -c 1

it should sow a mixer called 'Simple mixer control ‘Digital’ ,0

Go to ‘Playback options’ on the webinterface, select ‘Hardware’ under mixer type, and select the
just created ‘Digital’ mixer. Save the settings. Now the mixer control works but does not do
anything yet as we have not linked it to the I2C bus yet. This is done with a python script.

On my system the I2C bus was already properly initialized.

it will show up as ‘i2c-1’ under /dev :

cd /dev ls

-> check the list for ‘i2c-1’

Google on how to install it on your raspberry pi if this is not installed or you can just install this plugin by ChrisPanda which will set it up as well plus this plugin will let you play with all of the DAC’s settings like jitter reduction and filters. Also if it does not work
there may be a wiring problem (it crashes if I2C lines are not properly connected, not the plugins fault). Please not that the plugin may not properly work after you setup the python script as it may overwrite some settings constantly.

cd /home/volumio wget https://github.com/ChrisPanda/volumio-es9018k2m-plugin/releases/download/0.1.0/volumio-es9018k2m-plugin-0.1.0-sys-2.4x.zip miniunzip volumio-es9018k2m-plugin-0.1.0-sys-2.4x.zip cd volumio-es9018k2m-plugin-0.1.0-sys-2.4x volumio plugin install

install the python I2C library called smbus

sudo apt-get install python-smbus i2c-tools

check that you have the correct I2C bus (should be loaded in /dev/i2c-1) if you are using port I2C0 then change that in the python file main function.

create a python script:

nano /volumio/app/plugins/system_controller/i2s_dacs/scripts/ES9018K2M_I2C_Volumio.py

paste this python code:

[code]#!/usr/bin/python2

coding: utf-8

ESS ES9018K2M DAC I2C bus direct hardware management script for Volumio

by Damian Schneider (January 2020)

Based on python script by vigo

https://forum.volumio.org/setup-audiophonics-es9018k2m-dac-with-volumio-t6041.html

which is based on script for Runeaudio by Audiophonics and Fujitus

https://github.com/audiophonics/ES9018K2M_serial_sync

Required: ES9018K2M DAC with I2C pins connected to Raspberry pi

import time , smbus
import subprocess , os
import json
from urllib2 import Request, urlopen, URLError

#global register definitions
DEVICE_ADDRESS = 0x48 #7 bit address (will be left shifted to add the read write bit), alternat address: 0x49 if pin 5 is pulled high

#useful register numbers, refer to datasheet for more info (is hard to find but can be found online, check chinese sites)
#register number refers to its address, reg 0 is addres 0x00
#Reg0 system&oscillator settings, write 0x01 to reset chip (default 0x00)
#Reg1 input configuration, default=I2S 32bit, 0x8C (highest two bits are bit rate, write 0x4C for 24bit and 0x0C for 16bit)
#Reg6 deemphasis filter settings (off by default) and softe-mute speed (0x40 = fastest, 0x47 = slowest (?) not tested but 3 LSB give the speed, default is 0x42)
#Reg7 filter setting and mute, set to 0x80 (default) for unmute and set to 0x83 to mute both channels (2 LSB are mute or unmute) will ramp up the volume, speed can be set in register 6
#Reg15 left channel volume (0 is 0dB = loudest, 255 is -127.5dB = almost mute)
#Reg16 right channel volume (0 is 0dB = loudest, 255 is -127.5dB = almost mute)

def VolumioGetStatus():
process = subprocess.Popen(’/volumio/app/plugins/system_controller/volumio_command_line_client/volumio.sh status’, stdout=subprocess.PIPE, shell=True)
os.waitpid(process.pid, 0)[1]
Status = process.stdout.read().strip()

ParamList = json.loads(Status)

#set the defaults in case the status call does not return a required value
volumioBitDepth = '32 bit'
volumioStatus = 'stop' #also sets volume = 0
volumioMute = False

# volumioService = ParamList['service'] #service currently not checked

if 'status' in ParamList:
    volumioStatus = ParamList['status']

if 'bitdepth' in ParamList:
    volumioBitDepth = ParamList['bitdepth'] #entry is '24 bit' o r '16 bit', we only need to check if it is 16bit (on webradio this string is empty but plays fine with 32bit)

if 'volume' in ParamList:
    volumioVolume = ParamList['volume'] #volume is an integer

if 'mute' in ParamList:
    volumioMute = ParamList['mute'] #is true or false

if(volumioStatus != 'play'):
    volumioVolume = 0 #set volume low if nothing is playing

return(volumioVolume, volumioBitDepth, volumioMute)

#set the volume by adjusting both left and right channel, input is 0-100
def ES9018K2M_set_volume(vol):
vol = max(min(100, vol), 0) #limit the input value 0-100
vol_set = (100-vol) #map 0-100 to 100-0 (register is attenuation not amplification so 0 = max, 256=min, 100 means -50dB which is qute silent already)
bus.write_byte_data(DEVICE_ADDRESS, 15, vol_set) #set volume of left channel
bus.write_byte_data(DEVICE_ADDRESS, 16, vol_set) #set volume of right channel

if name == ‘main’:
bus = smbus.SMBus(1) # 0 = /dev/i2c-0 (port I2C0), 1 = /dev/i2c-1 (port I2C1)
#set the defaults (also used to check if value changed)
volume = 100
bitrate = 32
mute = False

try:
    while True: #run the script forever
        volumioStatus = VolumioGetStatus() #read the status of the player: reading volume, bit debth and mute

        #check if a state changed
        if(volumioStatus[0] != volume):
            volume = volumioStatus[0]
            ES9018K2M_set_volume(volume)

        if(volumioStatus[1] == '16 bit'):
            if(bitrate != 16):
                bitrate = 16
                bus.write_byte_data(DEVICE_ADDRESS, 1, 0x0C)  # set 16bit mode
        else:
            if(bitrate != 32):
                bitrate = 32
                bus.write_byte_data(DEVICE_ADDRESS, 1, 0x8C)  # set 32bit mode (works fine for 24bit input but not for 16 bit)
                #bus.write_byte_data(DEVICE_ADDRESS, 1, 0x4C)  # set 24bit mode

        if(volumioStatus[2] != mute):  #mute changed
            mute = volumioStatus[2]
            if(mute):
                bus.write_byte_data(DEVICE_ADDRESS, 7, 0x83)  # mute both channels
            else:
                bus.write_byte_data(DEVICE_ADDRESS, 7, 0x80)  # unmute both channels

        time.sleep(0.05)
except KeyboardInterrupt:
    bus.close()

bus.close()[/code]

create a shell script that starts the python script:

nano /volumio/app/plugins/system_controller/i2s_dacs/scripts/ES9018K2M-control.sh

with this code

[code]#!/bin/sh

ESS ES9018K2M DAC hardware management starting script

Author: Damian Schneider

python /volumio/app/plugins/system_controller/i2s_dacs/scripts/ES9018K2M_I2C_Volumio.py[/code]

make the script executable:

chmod ugo+x /volumio/app/plugins/system_controller/i2s_dacs/scripts/ES9018K2M-control.sh

Add ES9018K2M DAC definition in Volumio:

nano /volumio/app/plugins/system_controller/i2s_dacs/dacs.json

add this line (in alphabetically correct order):

{"id":"es9018k2m","name":"ES9018K2M bare DAC","overlay":"hifiberry-dac","alsanum":"1","mixer":"Digital","modules":"","script":"ES9018K2M-control.sh","needsreboot":"yes"},

Reboot.

Go to ‘Playback options’ on the web interface, make sure I2S DAC is activated and select the just created ‘ES9018K2M bare DAC’ as the model and save. Set the Mixer type to ‘Hardware’ and the control to ‘Digital’ and save.

Reboot.
The python script is now be running in the background and the Volumio volume control is linked to the DAC hardware volume control. The script checks for the bit-rate and sets it correctly on the DAC. This also works for the Spotify plugin as well as Balbuze’s Volspotconnect plugin.

I was not able to fix the startup-sound so for now just disable it in the settings.

1 Like

I have tried to run your script with the latest volumio on a pi3b, it runs until i change the music when it crashes. When I restart in putty it reports an io error writing to a sound channel.
I have tried 4,7k and 10k resistors but the result is the same.
The plugin from Chrispanda works fine. Is there anything else I can try?
Thanks.

thanks for the feedback.
Hard to say what is going on. You can try to use python commands through ssh directly and see if you can write to the DAC. If the plugin from Chris Panda works fine then your wiring appears to be correct and working.
When the script is running:
-does the volume work on the web UI (i.e. it actually changes the volume)? If this works then the commands arrive at the DAC correctly.
-you can try to add print ‘info here’ commands in the script to see what exactly is going on, then run the script manually to see these messages
-the error writing to a sound channel is coming from the script or the system?
-what exact DAC chip version are you using?

Hello friend, how are you? first I would like to thank you for the tutorial, I’m having difficulty getting the code, it seems that the HTML formatting changed some lines, would I be able to provide a txt with the code?

Python is indent sensitive.
Make sure that if you copy the code, you make sure to keep the indents in place.
so:
try:
[TAB]while True: #run the script forever
[TAB][TAB]volumioStatus = VolumioGetStatus()
[TAB][TAB]if(volumioStatus[0] != volume):
[TAB][TAB][TAB]volume = volumioStatus[0]

I saw I did not put the link to the script, sorry about that… here it is

I just updated my system to the latest version of Volumio and it now does show a ‘generic I2S’ DAC. Select this one instead of the ‘Volumio ESS 9028QM’ which does not appear to work with this setup anymore.
As I am following my own guide to set up the volume control I noticed that it did work exactly the same as I have written in the initial guide (and unfortunately I cannot edit it) so here is some additional info:
when running
aplay -l
note which card the ‘sndrpihifiberry’ is. In the guide it was listed as ‘card 1’ and in my current setup process it is listed as ‘card 2’ which requires the next commands also to refer to this card. So for card 2 the commands are:
sudo alsactl restore 2
amixer -c 2

Hi mate! Mind me to ask if you are experiencing such stutter sound while playing music in any bit format from usb and sd card?
I am also using rpi4, ES9018K2M and latest os

If anyone interested I rewrote the ES9018K2M control code to work with Volumio 4 using Volumio WebSocket.
It updates DAC registers only when volume changes, instead of constant I²C writes every ~50 ms, which previously overloaded the I²C bus.
Bit depth switching (16/24/32) is intentionally not used — Volumio WebSocket reports it unreliably and causes clicks and playback issues, so i was forced to use resampler.

also there is updated plugin to control ES9018K2M registers which works with the current versions of volumio - GitHub - DanyHovard/volumio-es9018k2m-plugin: es9018k2m function controll by i2c

1 Like

@Grey_bird - Welcome to the community, and nice work on updating the ES9018K2M control for Volumio 4.

The WebSocket approach for volume sync is a good design choice - reacting to state changes rather than constant polling eliminates the I2C bus congestion you mentioned.

A few thoughts:

1. Plugin Store Consideration

If there is community interest, this could be a candidate for the official Volumio plugin store (volumio-plugins-sources-bookworm). The separate Python service repo could be consolidated into the plugin itself - install.sh can create the systemd service, and the plugin lifecycle (onStart/onStop) can manage starting and stopping it. This simplifies installation significantly for end users.

2. Architecture Note

Looking at the two repos, both write to the same DAC registers (volume in particular). The plugin has an H/W Volume slider while the Python service syncs Volumio’s volume. Users might find this confusing - worth clarifying in documentation which volume control to use, or consider removing the plugin slider if the Python service handles volume sync.

Kind Regards,

Thanks Nerd!

About the plugin, I’m not sure if anyone here is still using this old DAC, if there will be any community interest on this, maybe we can push it little further to Plugin Store:)

Also thanks for pointing out that the code in both repositories essentially duplicates the volume control logic. This plugin was originally written a long time ago… i mainly updated it to make it work with newer versions of Volumio and check my board. It is a fork of this project

Hi Grey_bird,

Thank you for your coding. I do have a few AOIDE DAC 2 hats and could not use them since driver development had discontinued

A real plugin would be great and i would love helping to test

Maybe you should mention that installation requires build essential to be installed:

sudo mount -o remount,rw /
sudo apt-get update
sudo apt-get install -y build-essential

Unfortunately the plugin does not start… :frowning:

grafik

Best Regards
Josef

Hi Josef,

You should probably look here first, this repo claims that its “Modernized for kernel 6.1.69-v8+” for AOIDE DAC II.

Oh yea sorry, you need to do

sudo apt update
sudo apt install -y build-essential
npm install

in the plugin directory before plugin installation.

So clean installation should look like this:

cd /home/volumio
wget https://github.com/DanyHovard/volumio-es9018k2m-plugin/archive/refs/tags/1.0.1.zip
miniunzip 1.0.1.zip
cd volumio-es9018k2m-plugin-1.0.1/
sudo apt update
sudo apt install -y build-essential
npm install
volumio plugin install

Let me know if it works!

Hi Grey_bird,

Thank you - that worked. I am now able to activate the installed plugin and see the settings page.
Unfortunately the slider in the volume control (GUI) does not change the volume.

Do i have to repeat the steps to create the “digital” mixer ?

@Grey_bird @Josh2000 - Thanks for tinkering with this and good to see community interest.

However, I need to flag a critical issue with the installation approach:

sudo apt install -y build-essential
npm install

This breaks Volumio plugin and code delivery requirements. Strong caution here:

  1. Native modules and device tree overlays must be shipped pre-compiled for the target kernel
  2. When kernel changes (OTA updates), compiled modules break
  3. Installing build-essential and compiling on-device invalidates Volumio build integrity
  4. This approach will fail after any OTA update

For personal development and tinkering - this is fine. For distribution or plugin store submission - this is not acceptable.

Proper plugin packaging requires:

  • Pre-compiled native modules shipped with the plugin (per architecture)
  • install.sh installs from bundled artifacts, not from source
  • No build toolchain required on target device

The i2c-bus npm package contains native code that must be pre-built. See how other plugins handle this - they ship binaries in architecture-specific directories and install.sh selects the correct ones.

This is the difference between “works on my device” and “works reliably across the Volumio ecosystem.”

Kind Regards,

@nerd thanks!
Will see what I can do with this since I used this plugin just to check some parameters in DAC.

@Josh2000 Perfect. Do you mean slider in the plugin settings page or slider in the volumio GUI?

  • In the plugin settings page slider should work as it is, it does not reflect slider from the volumio. First of all check in the settings page that “Device status” button gives you smth like “found ES9018K2M revision V” to make sure we have connection with DAC.
  • If you want volume change through DAC with volumio GUI - yes you need to do all steps from here (instructions in the wiki)

i did - but got an error with

pip install websocket-client smbus2 RPi.GPIO

will retry in the evening

thank you

Make sure you follow all steps before. In fact, in this code you dont need RPi.GPIO, so you can skip it and just pip install websocket-client smbus2
In the end with pip list you should have websocket-client and smbus2 installed

please don’t use pip on bookworm, you need to create a virtual environment or break system packages.
Just use:
sudo apt-get install python3-websocket python3-smbus2

i followed the instructions, where a virtual env is created but used override parameter for the RPi.GPIO part and broke system packages…

will start from scratch and report…

@Grey_bird - do you think @Wheaten 's command will also do the thing ?
@Wheaten - did you try ?