Home and Links
 Your PC and Security
 Server NAS
 DVD making
 Raspberry Pi
 PIC projects
 Other projects
 Next >>

Controlling the Pi photoframe from a GPIO button

Pi GPIO button

Controlling (pausing) the photoframe display with a button (or switch)

The first thing you are likely to want is the ability to 'pause' your 'photo slide show' so you can talk about a photo. This requires a button or switch be fitted to one of the GPIO pins .. and then 'sensed' in software .. and then (somehow) used to 'pause' the display ... let the fun begin !

Fitting the button turns out to be the 'easy' part. Problems start when you discover that the 'pi' user has 'insufficient permissions' to access the gpio /sys/class path (so can't set up the pin) and, after solving that, you discover that the fbi utility is unable to run in 'background mode' (adding '&' to the end of the command just results in the dreaded 'ioctl error') so the script 'stops' on the fbi command and never reaches the 'check the button' code

Fitting the button

Assuming the button will be 'removable' (i.e. optional), we would ideally like to 'plug' it into the GPIO pin strip using a multi-pin wiring socket.

If you want Power (3v3) and Gnd as well as some i/o pin, and restrict yourself to 'uncommitted' io pins (and want to stick to the 'top half' of the header, which is identical across all Pi's), the minimum 'in one row' you can get away with are the 5 pins at GPIO header pin9 to pin17 inclusive (pin9 is Gnd, pin11,13,15 i/o 17,27 and 22 with pin17 3v3 power)

If the button is at the end of a 'long' cable wired direct to an i/o pin, any RFI 'pick up' on the cable could be injected into the i/o pin and damage to the Pi. To avoid problems it's a 'good idea' to use an opto-isolator (as well as UTP or co-ax cable).

Needless to say, the button should 'ground' the opto-isolator input, thus avoiding the possibility of RFI pick-up being injected into the Pi 3.3v line

Reading and writing GPIO pins from the Command Line Interface (CLI) is easy but very 'non-intuitive'. For one, you must have 'root' permissions to actually 'enable' (or disable) the GPIO pins

Alternatively, just install the Wiring Pi library from https://projects.drogon.net/raspberry-pi/wiringpi/download-and-install/- once installed, the new command 'gpio' can be used at non-root level to control the GPIO pins and will save you a lot's of time (but introduces more confusion by re-numbering the pins yet again):-)

Testing the button from the command line (PuTTY)

Needless to say, the 'pi' user account has no 'permission' to access the gpio 'system class', so you can't 'test' things are working manually. The ONLY thing I found to work was to actually log-in as 'root' (putting 'sudo' in front of the commands gets you past the 'Permission denied' but then you get locked into a 'No such file or directory' error)

If you installed Wiring Pi, you need to add the 'pi' user to the 'gpio' user group (sudo usermod -aG gpio pi) - others have suggested giving another group that pi is a member of (eg the dial-out user group) permission to access gpio, however I never got that to work

So, to access /sys/class/gpio from PuTTY (eg test the button is working), you must log-in as 'root'. If you have not already created a root password, login as 'pi' then type 'sudo -i'. When you get the 'root@raspberrypi:-#' prompt, type 'passwrd root'. Then, in response to 'Enter new UNIX password:' respond with whatever you want the password to be (eg 'root') and confirm.

Then CLOSE PuTTY, launch it again and this time log-in as root / {your password}. The commands below (echo "17".., cat /sys/class.. etc) will now just work 'OK'

The following will 'work' from PuTTY ONLY if you are logged in as 'root' (if you are logged in as 'pi', even if you prefix 'echo' with 'sudo', you will get a 'Permission denied' error)

# Enable GPIO 17 ('export' it), set it to input mode (actually, it defaults to that) :- echo "17" > /sys/class/gpio/export echo "in" > /sys/class/gpio/gpio17/direction# if your button circuit has no pull-up, to enable the GPIO internal 'pull up' resistor :- echo "high" > /sys/class/gpio/gpio17/direction# Read the button state from GPIO17 tot he command line (0 = pushed or 1 = not pushed)) cat /sys/class/gpio/gpio17/value# When finished, disable the input again echo "17" > /sys/class/gpio/unexport

Modify the Photoframe display sequence

Of course, having a shell script set up a gpio input runs straight into the 'permission denied' error problem.

Changing the owner of the script to root (chown root:root myscript.sh) and giving it root permissions (chmod 4755 myscript.sh) had no effect on the dreaded 'Permission denied' error.

In the end, the ONLY thing that worked is to prefix each gpio setup command with "sudo sh -c" as follows :-

# clear any current setting (gpio remains set after the script crashes) sudo sh -c 'echo "17" > /sys/class/gpio/unexport' sudo sh -c 'echo "17" > /sys/class/gpio/export' sudo sh -c 'echo "in" > /sys/class/gpio/gpio17/direction' # show the button status and give the user a chance to see it (allows test at power-on) echo "Button detect - 'blank' = gpio didn't program, 1=default (not pressed, not plugged in), 0 = pressed" cat /sys/class/gpio/gpio17/value sleep 3

Once the gpio pin has been setup as an input, it can be monitored by the script (using 'cat /sys/class/gpio/gpio17/value') and used to control the Photoframe display. All the script has to do is check that the button is 'released' after copying the new photo and before updating the display 'alias' to point at the new buffer.

The go-button.sh script

The go-button.sh script replaces the 'go-photo.sh' script. For more information, see below :-

(-) go button script - (photoframe pause control)

The 'go-photo' display script

The basic 'go-photo' script just has to find the 'next' photo, copy it to the 'free' buffer and re-link display.jpg to the just 'filled' buffer (thus freeing the other buffer).

Of course it's not quite that simple :-)

To avoid any 'missing file' errors (and speed up the appearance of new photos), the 'source' folder contents will need to be checked after each photo is shown.

Further, because the fbi script has to be launched last, this script has to set-up the files needed

Note, it is assumed that the ram-disk folder (/photos/ram-disk) has already been created and 'mounted' to tmpfs, however to make sure type :-

df -HT
/photos holds the .jpg's to be displayed, and the /photos/ram-disk (tmpfs) folder holds both 'buffers' (buffer1.jpg, buffer2.jpg) and the display.jpg 'virtual' file name (which will be 'linked' to one of the buffers)

The 3 'alias' files needed by fbi will be created (and linked actual to virtual, display.jpg to alias) by the fbi script.

One thing to watch out for is to avoid 'overwriting' the 'just free' buffer too quickly = it takes fbi 1 second to stop 'reading' the old buffer and follow the alias - display - new buffer link.

The way it works is as follows :-
The outer loop runs 'while true' (i.e. for ever).
A directory list is generated in file date order (so the 'newest' photo is top of the list) and then we inner loop
- first name = top, compare with current, if it differs exit with next=top
New top is compared with previous top
- if no match (new top is newer), new top is used as the next photo
- else the dir list is scanned, and the photo after the current is chosen
- if current is not found, the top photo is used
The existing 'freeze' button will be replaced with a 'remote control', so there is no point in adding it at this stage

sudo nano go-photo.sh
#!/bin/bash # go-button.sh (based on go-photo.sh) # Start by setting up the gpio pin # clear any current setting (gpio remains set after the script crashes) sudo sh -c 'echo "17" > /sys/class/gpio/unexport'
sudo sh -c 'echo "17" > /sys/class/gpio/export'
sudo sh -c 'echo "in" > /sys/class/gpio/gpio17/direction'
# show the button status and give the user a chance to see it (allows test at power-on)
echo "Button detect - 'blank' = gpio didn't program, 1=default (not pressed, not plugged in), 0 = pressed"
cat /sys/class/gpio/gpio17/value
sleep 3
# this script copies the next photo into the free buffer and then updates the display alias
# source photos are held in the /photos/ folder (fetching and resizing is performed by the fetch-resize script)
# both buffers1&2.jpg and display.jpg are held in the /ram-disk/ folder (actual display is performed by the fbi script)
#  (all ram-disk files have to be recreated after each power on)
mkdir -p /photos/ram-disk # -p means prevents errors if dir already exists
mount -t tmpfs -o size=10M,mode=0755 tmpfs /photos/ram-disk # mount to tmpfs (10M is max allowed use, not reserved ram)
showtime=4 #photo show time -1 (so 4 = 5 seconds total)
cfile="0" # current file name
ctop="0" # current top by date
nbuf="buffer1.jpg" # next (free) buffer
# loop forever
while [ true ]; do
	top="0" # no top found
	next="0" # no next found
	# Scan the photos dir for the current file
	for filename in $(ls -A1c /photos/*.jpg); do
	# note, when you ls a different folder, the path is added to each file name (so file is /photos/name.jpg )
	if [ $top = "0" ]; then
		if [ $filename != $ctop ]; then next=$filename; fi
	# find the next file AFTER the current
	if [ $next = "0" ]; then
		if [ $cfile = "0" ]; then next=$filename; fi
		if [ $filename = $cfile ]; then cfile="0"; fi
	# if no next found, use top
	if [ $next = "0" ]; then next=$top; fi
	# OK, we have the next file, pause in case fbi is still reading the free buffer
	sleep 1
	cp -f $next /photos/ram-disk/$nbuf
	if [ $nbuf = "buffer1" ] then;
	# wait if button (GPIO 17) is held down (Lo). Check every 0.1s for fast response
	while [ "$(cat /sys/class/gpio/gpio17/value)" == '0' ]; do
	sleep 0.1
	# now switch the link (on the first pass this creates display.jpg)
	ln -s -f /photos/ram-disk/$nbuf /photos/ram-disk/display.jpg
	# wait for the rest of the show time
	sleep $showtime
	# now go find the next

After creating any new script, don't forget to make it 'executable' :-)

sudo chmod +x go-photo.sh
To test this from the command line (assuming you are in the directory where the script resides) just type :-


If you have samba installed and are 'sharing' the /photos/ folder with your PC, on your PC you can 'map' the share and go look in the (share)/ram-disk folder and watch the buffer1/2 file sizes change as new photos are copied in. You should also see display.jpg 'switching' (i.e. 'tracking' buffer1.jpg file size then buffer2.jpg file size and back again)

This note last modified: 17th Nov 2017 17:29.


Common problems

The script outputs 'permission denied'

Most likely incorrect "sudo sh -c 'echo...'" construct OR the gpio pin is ALREADY in use (which is why the script starts with 'unexport')

The 'cat' command returns 'unknown file or directory'

'cat' will fail if the 'echo' command that sets up the gpio pin failed

'cat' command always returns '1', even when the button is pressed

Check via PuTTY - if still always 'hi' (1), and assuming you already tested the operation with a multimeter (so it 'did work' before running the script) and you have no 'Permission denied' or 'unknown file or directory' error, try removing the 'internal pull up' command ('high' > direction) - some opto-isolators just can't cope with a combination of external 100k + internal 55k when working from a 3.3 supply.

display 'stops / freezes' on the first photo

go-button.sh is not running in background = the '&' has been left off the end of the command (so the script halts waiting for the fbi command to complete, which it never will)

display won't stop (always shows photos at 5 second intervals)

If the button 'works' and, when pressed and released, results in the current display being 'cut short', it's a scripting problem

If the button is having no effect, then the 'cat' / wait commands (in the script) is failing for some reason. Test the gpio button ('cat') from PuTTY , if you get 'no such file ..' = the script 'echo' command failed to set up the gpio, if '1' all the time = electrical problem, check wiring, if it works OK = check the script 'cat' / wait loop coding

display won't stop, shows 2 (or more) flickering images, Pi crashes

If you don't kill one fbi before starting another, you can end up with multiple fbi's 'fighting' over the display ! The 'killall' must be prefixed with 'sudo' (and you must 'sudo killall fbi' BEFORE trying to start fbi a second (or third etc) time :-)).

Writing status (to a GPIO pin)

If you need a status LED (eg on GPIO22), you set this up in a similar way :-
# Set up GPIO 22 and set to output echo "22" > /sys/class/gpio/export echo "out" > /sys/class/gpio/gpio22/direction# Write to the output echo "1" > /sys/class/gpio/gpio22/value# Clean up echo "22" > /sys/class/gpio/unexport

For details on using a USB stick with the Photoframe, click the 'Next >>' button, left

Next page :- Pi USB - (for Photoframe)