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


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).


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:


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


which is based on script for Runeaudio by Audiophonics and Fujitus


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

    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]

        if(volumioStatus[1] == '16 bit'):
            if(bitrate != 16):
                bitrate = 16
                bus.write_byte_data(DEVICE_ADDRESS, 1, 0x0C)  # set 16bit mode
            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]
                bus.write_byte_data(DEVICE_ADDRESS, 7, 0x83)  # mute both channels
                bus.write_byte_data(DEVICE_ADDRESS, 7, 0x80)  # unmute both channels

except KeyboardInterrupt:


create a shell script that starts the python script:

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

with this code


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"},


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.

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 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.
[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