Volume change with analog potentiometer - fedormil - 04-23-2019
Hello!
Wrote F.A.Q. on request Drone7
To use an analog potentiometer as a volume control with a raspberry pi you will need:
- ADC, I used ADS1115 link
- analog POT, I used the simplest link
We increase or decrease the system volume using the command-line program
Install the ADS1115 ADC in the following order (source - link):
1. Enable I2C on the Raspberry Pi using raspi-config
Code: sudo apt-get install -y python-smbus
sudo apt-get install -y i2c-tools
Run
and follow the prompts to install i2c support for the ARM core and linux kernel
Go to Interfacing Options, then I2C -> Enable! –> Yes
Once this is all done, reboot!
2. Testing i2c
This shows that I2C address ADC
3. Connect the ADC to the Pi as follows:
- ADS1x15 VDD to Raspberry Pi 3.3V
- ADS1x15 GND to Raspberry Pi GND
- ADS1x15 SCL to Raspberry Pi SCL
- ADS1x15 SDA to Raspberry Pi SDA
4. Source Install
To install from the source on Github connect to a terminal on the Raspberry Pi and run the following commands:
Code: sudo apt-get update
sudo apt-get install build-essential python-dev python-smbus git
cd ~
git clone https://github.com/adafruit/Adafruit_Python_ADS1x15.git
cd Adafruit_Python_ADS1x15
sudo python setup.py install
5. Connecting an analog potentiometer
It goes according to the following scheme:
Left leg - GND
Central leg - A1 ADS1115
Right leg - VCC 3.3 v
6. Check potentiometer connection
Go to the folder examples:
Code: cd ~/Adafruit_Python_ADS1x15/examples
run the simpletest.py
Code: sudo python simpletest.py
When rotating the potentiometer knob, the values of channel 1 should change. Check that there are no negative values in the leftmost position (readings 0-2). Otherwise, check the quality of connections, it is better to collect on the soldering.
Write down the value in the extreme right position (required to correct the code). I have - 26274
Now set the code for the operation of the potentiometer, the manual is taken as the basis - link
First, make sure amixer is present and install it if it isn’t.
Code: which amixer || sudo apt-get install alsa-utils
You might not have Python 3; if which python3 turns up nothing, try this:
Code: sudo apt-get install python3
in home / pi, create a python file with any name (in my case it is volknob.py) and copy this code into it
Code: #!/usr/bin/python
# coding=utf-8
#############################################################################################################
### Copyright by Joy-IT
### Published under Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License
### Commercial use only after permission is requested and granted
### Programme traduit par Go tronic
###
### Single Analog Sensor - Raspberry Pi Python Code Example
###
#############################################################################################################
# Ce code utilise les librairies Python ADS1115 et I2C pour la Raspberry Pi
# Ces librairies sont publiées sous licence BSD sur le lien ci-dessous
# [https://github.com/adafruit/Adafruit-Raspberry-Pi-Python-Code]
import Adafruit_ADS1x15
from time import sleep
# Les modules nécessaires sont importés et mis en place
import time, signal, os, math
import logging
import signal
import subprocess
import sys
# Les variables utilisées sont initialisées
delayTime = 0.2
voltageMax = 3300
# valeur de tension maximale possible à l'entrée du convertisseur ADC
# attribution d'adresse ADS1x15 ADC
ADS1015 = 0x00 # 12-bit ADC
ADS1115 = 0x01 # 16-bit ADC
# Choix du gain
gain = 1 # +/- 4.096V
# gain = 2048 # +/- 2.048V
# gain = 1024 # +/- 1.024V
# gain = 512 # +/- 0.512V
# gain = 256 # +/- 0.256V
# Choix de la fréquence d'échantillonnage ADC (SampleRate)
# sps = 8 # 8 échantillons par seconde
# sps = 16 # 16 échantillons par seconde
# sps = 32 # 32 échantillons par seconde
# sps = 64 # 64 échantillons par seconde
# sps = 128 # 128 échantillons par seconde
sps = 250 # 250 échantillons par seconde
# sps = 860 # 860 échantillons par seconde
# choix du canal ADC (1-4)
# adc_channel = 0 # Channel 0
adc_channel = 1 # Channel 1
# adc_channel = 2 # Channel 2
# adc_channel = 3 # Channel 3
# initialisation du convertisseur
adc = Adafruit_ADS1x15.ADS1115()
#############################################################################################################
# ########
# boucle de programme principale
# ########
# Le programme mesure la tension à l'aide du convertisseur ADS1115.
# Il calcule la résistance de la LDR et les transmet à la console.
logger = logging.getLogger(__name__)
# logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)-15s - %(message)s'
)
# SETTINGS
# ========
DEBUG = True
# The minimum and maximum volumes, as percentages.
#
# The default max is less than 100 to prevent distortion. The default min is
# greater than zero because if your system is like mine, sound gets
# completely inaudible _long_ before 0%. If you've got a hardware amp or
# serious speakers or something, your results will vary.
VOLUME_MIN = 0
VOLUME_MAX = 100
# The amount you want one click of the knob to increase or decrease the
# volume. I don't think that non-integer values work here, but you're welcome
# to try.
VOLUME_INCREMENT = 1
# Audio device name, e.g. 'PCM' or 'Master'. Find with: amixer scontrols
DEVICE_NAME = 'PCM'
# (END SETTINGS)
#
def debug(str):
if not DEBUG:
return
logger.debug(str)
class RotaryEncoder(object):
"""
A class to decode mechanical rotary encoder pulses.
"""
def __init__(self, channel, tolerance=5):
self.adc = Adafruit_ADS1x15.ADS1115()
self.volume = Volume()
self.channel = adc_channel
# to keep from being jittery we'll only change
# volume when the pot has moved more than 5 'counts'
self.tolerance = tolerance
# this keeps track of the last potentiometer value
self.last_read = 0
def destroy(self):
debug('Destroying...')
def read(self):
# we'll assume that the pot didn't move
trim_pot_changed = False
# read the analog pin
trim_pot = self.adc.read_adc(self.channel, gain, sps)
# how much has it changed since the last read?
pot_adjust = abs(trim_pot - self.last_read)
if pot_adjust > self.tolerance:
trim_pot_changed = True
if trim_pot_changed:
# convert trim pot read into 0-100 volume level
set_volume = int(round(trim_pot / 262.74))
self.volume.set_volume(set_volume)
# save the potentiometer reading for the next loop
self.last_read = trim_pot
class VolumeError(Exception):
pass
class Volume(object):
"""
A wrapper API for interacting with the volume settings on the RPi.
"""
MIN = VOLUME_MIN
MAX = VOLUME_MAX
INCREMENT = VOLUME_INCREMENT
def __init__(self):
# Set an initial value for last_volume in case we're muted when we start.
self.last_volume = self.MIN
self._sync()
def up(self):
"""
Increases the volume by one increment.
"""
return self.change(self.INCREMENT)
def down(self):
"""
Decreases the volume by one increment.
"""
return self.change(-self.INCREMENT)
def change(self, delta):
v = self.volume + delta
v = self._constrain(v)
return self.set_volume(v)
def set_volume(self, v):
"""
Sets volume to a specific value.
"""
self.volume = self._constrain(v)
debug("set volume: {}".format(self.volume))
output = self.amixer("set -M '{}' unmute {}%".format(DEVICE_NAME, v))
self._sync(output)
return self.volume
def toggle(self):
"""
Toggles muting between on and off.
"""
if self.is_muted:
output = self.amixer("set -M '{}' unmute".format(DEVICE_NAME))
else:
# We're about to mute ourselves, so we should remember the last volume
# value we had because we'll want to restore it later.
self.last_volume = self.volume
output = self.amixer("set -M '{}' mute".format(DEVICE_NAME))
self._sync(output)
if not self.is_muted:
# If we just unmuted ourselves, we should restore whatever volume we
# had previously.
self.set_volume(self.last_volume)
return self.is_muted
def status(self):
if self.is_muted:
return "{}% (muted)".format(self.volume)
return "{}%".format(self.volume)
# Read the output of `amixer` to get the system volume and mute state.
#
# This is designed not to do much work because it'll get called with every
# click of the knob in either direction, which is why we're doing simple
# string scanning and not regular expressions.
def _sync(self, output=None):
if output is None:
output = self.amixer("get -M '{}'".format(DEVICE_NAME))
lines = output.readlines()
if DEBUG:
strings = [line.decode('utf8') for line in lines]
debug("OUTPUT:")
debug("".join(strings))
last = lines[-1].decode('utf-8')
# The last line of output will have two values in square brackets. The
# first will be the volume (e.g., "[95%]") and the second will be the
# mute state ("[off]" or "[on]").
i1 = last.rindex('[') + 1
i2 = last.rindex(']')
self.is_muted = last[i1:i2] == 'off'
i1 = last.index('[') + 1
i2 = last.index('%')
# In between these two will be the percentage value.
pct = last[i1:i2]
self.volume = int(pct)
# Ensures the volume value is between our minimum and maximum.
def _constrain(self, v):
if v < self.MIN:
return self.MIN
if v > self.MAX:
return self.MAX
return v
def amixer(self, cmd):
p = subprocess.Popen("amixer {}".format(cmd), shell=True, stdout=subprocess.PIPE)
code = p.wait()
if code != 0:
raise VolumeError("Unknown error: {}".format(code))
sys.exit(0)
return p.stdout
if __name__ == "__main__":
def on_exit(a, b):
debug("Exiting...")
encoder.destroy()
sys.exit(0)
encoder = RotaryEncoder( adc_channel)
signal.signal(signal.SIGINT, on_exit)
debug("Initial volume: {}".format(encoder.volume.volume))
while True:
encoder.read()
# hang out and do nothing for a half second
time.sleep(0.5)
In the code variables are the following values:
VOLUME_MIN = 0
VOLUME_MAX = 100
VOLUME_INCREMENT = 1
# Audio device name, e.g. 'PCM’, 'Master' or your sound card. Find with: amixer scontrols
DEVICE_NAME = 'PCM'
# convert trim pot read into 0-100 volume level
set_volume = int(round(trim_pot / 262.74)) – specify the value from clause 6/100
Next we create an autorun service (described on the author’s code page) and enjoy.
P.s. translation via google, code copied from Putty, could be wrong
RE: Volume change with analog potentiometer - pkdick - 10-24-2024
Hello Fedormil,
I tried to use your python script on my MOODE 9.1.3 installation: it seems that it does not work anymore on the new bookworm system...
Could you try to update it ? I would like to replace my encoder with a potentiometer (I bought an ADC ADS1115), but my trials to modify your script are currently unsuccessful.
Thank you in advance for the support you may bring.
|