Saturday, January 31, 2015

Noisy Simon

I merged the Simon game with what I learned about making sounds, and managed to get a perfectly functional Simon game with sound. I changed the names of the sound channels so that I could refer to them by number instead of color, but other than that it's a pretty straight-forward adaptation.
One modification I made to the code was to add a 0.1 second delay with the sound in order to avoid the situation that the sound has not quite finished playing before the light goes off, thus creating a situation where you press a button and no sound comes out. I stumbled across this error while playing around with it, and since adding the delay this has not been a problem.
This is the end of what I consider to be things I kind of already knew how to do. The next step for me is to go into the graphical world of pygame.
import RPi.GPIO as GPIO
import time
import random
import pygame
GPIO.setmode(GPIO.BCM)

##### Debug Scripts
def log(msg):
    if DebugDisplay == True:
        print msg

##### LED Scripts
def AllLEDsOn():
    for i in range(0,5):
        GPIO.output(LED[i], GPIO.HIGH)

def AllLEDsOff():
    for i in range(0,5):
        GPIO.output(LED[i], GPIO.LOW)

def Flashes(times, pause):
    AllLEDsOff()
    for j in range(0,times):
        AllLEDsOn()
        time.sleep(0.1)
        AllLEDsOff()
        time.sleep(0.1)
    time.sleep(pause)

def LEDFlash(LEDNum, OnTime, Sound):
    GPIO.output(LED[LEDNum], GPIO.HIGH)
    if Sound == 1:
        toneChannel[LEDNum-1].play(tone[LEDNum-1])
        time.sleep(0.1)
    time.sleep(OnTime)
    GPIO.output(LED[LEDNum], GPIO.LOW)

def PlayList(Listing):
    for i in Listing:
        LEDFlash(i, 0.5, 1)
        time.sleep(0.1)
    time.sleep(1)

##### Button Scripts

def LookupButton(channel):
    for i in range(0,5):
        if channel == button[i]:
            return i

def ButtonPressed(channel):
    global Pressed, Listening, Busy
    TheButton = LookupButton(channel)
    if (TheButton == 0):
        log("Button 0 Pressed")
        Pressed = 0
        Listening = 0
    elif (TheButton > 0 and Listening == 1):
        log("Button {} Pressed -- Listening".format(TheButton))
        Pressed = TheButton
        Listening = 0
    elif (TheButton > 0 and Listening == 0):
        log("Button {} Pressed -- NOT Listening".format(TheButton))
        Pressed = -1

##### MAIN GAME LOOP
# Game Constants
DebugDisplay = False
button = [4, 12, 16, 20, 21]
LED = [22, 5, 6, 13, 19]

pygame.mixer.init(48000, -16, 1, 1024)
tone = [pygame.mixer.Sound("RedTone.ogg"), \
        pygame.mixer.Sound("YellowTone.ogg"), \
        pygame.mixer.Sound("GreenTone.ogg"), \
        pygame.mixer.Sound("BlueTone.ogg")]

toneChannel = [pygame.mixer.Channel(1), \
               pygame.mixer.Channel(2), \
               pygame.mixer.Channel(3), \
               pygame.mixer.Channel(4) ]

# Variables
Pressed = -1
Listening = 1
GameState = 1

# Set GPIO pins
for i in range (0,5):
    GPIO.setup(button[i], GPIO.IN, pull_up_down=GPIO.PUD_UP)
    GPIO.setup(LED[i], GPIO.OUT)
    GPIO.add_event_detect(button[i], GPIO.FALLING, callback=ButtonPressed, bouncetime=200)

# LED loop in waiting mode
while True:
    Listening = 1
    if GameState == 0:
        log("GameState = 0, Exiting Game")
        break
    elif GameState == 1:
        log("GameState = 1, Pressed = {}".format(Pressed))
        if Pressed == 0:
            GameState = 0
            break
        else:
            for i in range(1,5):
                LEDFlash(i,0.2,0)
                if Pressed == 0:
                    GameState = 0
                    break
                elif Pressed > 0:
                    GameState = 2
                    break
            Pressed = -1
    elif GameState == 2:
        log("GameState = 2, Pressed = {}".format(Pressed))
        Pressed = -1
        Listening = 0
        Simon = [random.randint(1,4)]
        Flashes(5, 2)
        while GameState == 2:
            log("Starting a round, length = {}".format(len(Simon)))
            Listening = 0
            PlayList(Simon)
            Flashes(1,0)
            for Levels in range(0, len(Simon)):
                log("Waiting for user input, correct answer = {}".format(Simon[Levels]))
                GPIO.output(LED[0], GPIO.HIGH)
                Listening = 1
                Pressed = -1
                for timerloop in range(0,500):
                    if Pressed > -1:
                        break
                    time.sleep(0.01)
                if Pressed == Simon[Levels]:
                    LEDFlash(Pressed, 0.5, 1)
                else:
                    toneChannel[0].play(tone[0])
                    toneChannel[1].play(tone[1])
                    toneChannel[2].play(tone[2])
                    toneChannel[3].play(tone[3])
                    Flashes(5,2)
                    GameState = 1
                    break
            GPIO.output(LED[0], GPIO.LOW)
            if (GameState == 1 and Pressed != 0):
                PlayList(Simon[0:Levels])
                for i in range(0,10):
                    LEDFlash(Simon[Levels], 0.1, 1)
                    time.sleep(0.1)
                time.sleep(1)
            elif GameState == 2:
                Simon.append(random.randint(1,4))
                time.sleep(0.2)
                Flashes(2,0)
                time.sleep(1)
        GameState = 1
        Pressed = -1

AllLEDsOff()
GPIO.cleanup()

Saturday, January 24, 2015

Making noise

Having made the sound files, I needed to test everything to make sure that it's working properly. I think this is my first time invoking the pygame module. There are a few new commands that I need:
  • import pygame.mixer: This calls up the pygame mixer module.
  • pygame.mixer.init(48000, -16, 1, 1024): This initializes the mixer. I don't understand all of the parameters really mean. The documentation theoretically explains this to me, but I don't know anything about audio sampling, so it's still nonsense to me.
  • refFileName = pygame.mixer.Sound(fileName): The actual command is the pygame.mixer.Sound() part of this statement. As far as I can tell, this is just making it easier to reference the file in the code.
  • channelName = pygame.mixer.Channel(n): This creates the audio channel for playback. The default number of channels is 8.
  • channelName.play(refFileName): This actually plays the sound.
  • Armed with these commands, I was able to create a fairly basic file that plays through my four tones for the Simon game.
    import pygame.mixer
    import time
    
    pygame.mixer.init(48000, -16, 1, 1024)
    redtone = pygame.mixer.Sound("RedTone.ogg")
    yellowtone = pygame.mixer.Sound("YellowTone.ogg")
    greentone = pygame.mixer.Sound("GreenTone.ogg")
    bluetone = pygame.mixer.Sound("BlueTone.ogg")
    
    redChannel = pygame.mixer.Channel(1)
    yellowChannel = pygame.mixer.Channel(2)
    greenChannel = pygame.mixer.Channel(3)
    blueChannel = pygame.mixer.Channel(4)
    
    redChannel.play(redtone)
    time.sleep(1)
    yellowChannel.play(yellowtone)
    time.sleep(1)
    greenChannel.play(greentone)
    time.sleep(1)
    blueChannel.play(bluetone)
    time.sleep(1)
    
I learned a couple things while playing around with this. First, it's possible to play multiple sounds at the same time on different channels. In fact, playing the four tones at once makes for a very good error sound. So I think I'll incorporate that fortunate discovery into the game. Another thing that I learned is that if the program reaches the end while a sound is play, the sound does not finish playing. It just cuts off. This is why the final time.sleep() command is there. It just ensures that half-second tone completely finishes.

My next task will be to get the sounds to play with the flashing lights. And then I'll start into a graphical presentation of the game using more of the pygame module.

Preparing to make music

Well, not music. It's more like making tones. These tones are going to be used for the various buttons for the Simon game.

In order to generate the tones, I went to http://onlinetonegenerator.com/, which allows you to generate several types of tones. I picked 440 Hz (A), 490 Hz (B), 554 Hz (C#), and 659 Hz (E). I got these values from http://www.phy.mtu.edu/~suits/notefreqs.html.

The files created by the tone generator are .wav files. But according to the Dummies book, it's better to use .ogg files. So I downloaded a program called Audacity. This was accomplished by running sudo apt-get update and then sudo apt-get install audacity.

I actually downloaded the files onto my laptop and then tried to transfer them over to the Raspberry Pi. This turned out to be more complicated than I thought it should have been. Originally, I had uploaded the files to Dropbox and used Midori on the Pi to try to download them. But the files never transferred despite multiple attempts. So after some hopping around on the internet, I found a program called WinSCP. After multiple failed attempts to install the program, I discovered that the default settings try to write to a directory that you need administrative access to get to, and that (for some reason) the program isn't set up to ask you for that permission. But I was able to install it in a different folder and that solved my problem.

And it was at this point that I finally opened up Audacity, and it turns out that it has its own tone generator. So all of the effort I just expended was kind of worthless. Interestingly, I couldn't figure out how to make a 0.5 second tone. I had to make a one second tone and then cut it in half. But in the end, I was finally able to create the .ogg sound files that I needed.

Automatic graphical boot-up

I've decided that it's time to get the Raspberry Pi to boot up with the VNC server running so that I could choose between logging in with the SSH protocol or go straight to the graphical interface. This wasn't too complicated. I followed the instructions on http://www.raspberrypi.org/documentation/remote-access/vnc/.
  • sudo su: Log in as root.
  • cd /etc/init.d/: This is apparently the directory where startup scripts live.
  • nano vncboot: Opens up a text file where the following commands were pasted. I don't really understand too much about what this is doing. The options in the VNC server are different from the ones I normally use, but I can't really tell what the difference is, so I'm going to assume for now that it's irrelevant.
    ### BEGIN INIT INFO
    # Provides: vncboot
    # Required-Start: $remote_fs $syslog
    # Required-Stop: $remote_fs $syslog
    # Default-Start: 2 3 4 5
    # Default-Stop: 0 1 6
    # Short-Description: Start VNC Server at boot time
    # Description: Start VNC Server at boot time.
    ### END INIT INFO
    
    #! /bin/sh
    # /etc/init.d/vncboot
    
    USER=root
    HOME=/root
    
    export USER HOME
    
    case "$1" in
     start)
      echo "Starting VNC Server"
      #Insert your favoured settings for a VNC session
      /usr/bin/vncserver :0 -geometry 1280x800 -depth 16 -pixelformat rgb565
      ;;
    
     stop)
      echo "Stopping VNC Server"
      /usr/bin/vncserver -kill :0
      ;;
    
     *)
      echo "Usage: /etc/init.d/vncboot {start|stop}"
      exit 1
      ;;
    esac
    
    exit 0
    
  • chmod 755 vncboot: This makes the script executable.
  • update-rc.d vncboot defaults: I think this is what enables the script to run at bootup. There was another command that the website instructed me to use that didn't work (update-rc.d /etc/init.d/vncboot defaults). My guess is that the reason it doesn't work is because I was in the particular directory so that the extra location information was extraneous and caused some sort of problem.
And that was pretty much it.

EDIT: I later noticed that this setup logged you in as root, and that's not such a good thing. I tried changing the user=root to user=pi and HOME=/root to HOME=/home/pi, but that didn't seem to work for some reason. So I went around and found another website that had instructions for logging in as pi: http://www.maketecheasier.com/setting-vnc-raspberry-pi/. The script is very similar, but it is different.
#!/bin/sh
### BEGIN INIT INFO
# Provides:          VNC
# Required-Start:    $local_fs
# Required-Stop:
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Start or stop the VNC server
### END INIT INFO
 
PATH=/sbin:/usr/sbin:/bin:/usr/bin
 
eval cd ~pi
 
case "$1" in
  start)
        su pi -c "/usr/bin/vncserver :1 -geometry 1024x728 -depth 24"
        echo "Started VNC server."
        ;;
  stop)
        su pi -c "/usr/bin/vncserver -kill :1"
        echo "Stopped VNC server."
        ;;
  *)
        echo "Usage: vncserver [start|stop]" >&2
        exit 3
        ;;
esac
 
:
However, this gave me some problems, too. The connection was now getting refused when I tried to the run the VNC client from my desktop. After staring blankly at the code for a while, I changed the :1 to :0. That did the trick. I don't really know why it behaves like this, but it does. I may or may not change the resolution later.

Monday, January 12, 2015

Simon, Version 1

I have completed my first version of Simon. The game seems to work completely. Here are some features:
  • While the game is in "waiting" mode, it cycles through the four main game LEDs.
  • When the game starts (by pressing one of the four main buttons), the lights flash a few times to let you know that the game is about to start. Then there's a little pause, and it the game begins.
  • After the game is done giving you the sequence, there's a pause, and then the indicator light turns on to let you know it's your turn.
  • When you mess up, it shows you the sequence up to the point where you made your error, and then shows you the correct answer.
The code below gives a user display which tells you what the right button to press is. It's a bit of cheating and was used for debugging. To get rid of that, just change the DebugDisplay variable to False.
This concludes the program structure phase of this project. However, there are several more things I want to do.
  • Sounds: This is a memory tool, plus it's a programming challenge.
  • Visuals: I need to learn Pygame. It doesn't look too complex, but it will still take some time to learn the syntax and the structure of how to make the visuals work the way I want them to.
  • High scores: This is file input/output exercise. I need to read scores from a file and then export scores to a file.
And the code:
import RPi.GPIO as GPIO
import time
import random
GPIO.setmode(GPIO.BCM)

##### Debug Scripts
def log(msg):
    if DebugDisplay == True:
        print msg

##### LED Scripts
def AllLEDsOn():
    for i in range(0,5):
        GPIO.output(LED[i], GPIO.HIGH)

def AllLEDsOff():
    for i in range(0,5):
        GPIO.output(LED[i], GPIO.LOW)

def Flashes(times, pause):
    AllLEDsOff()
    for j in range(0,times):
        AllLEDsOn()
        time.sleep(0.1)
        AllLEDsOff()
        time.sleep(0.1)
    time.sleep(pause)

def LEDFlash(LEDNum, OnTime):
    GPIO.output(LED[LEDNum], GPIO.HIGH)
    time.sleep(OnTime)
    GPIO.output(LED[LEDNum], GPIO.LOW)

def PlayList(Listing):
    for i in Listing:
        LEDFlash(i, 0.5)
        time.sleep(0.1)
    time.sleep(1)

##### Button Scripts

def LookupButton(channel):
    for i in range(0,5):
        if channel == button[i]:
            return i

def ButtonPressed(channel):
    global Pressed, Listening, Busy
    TheButton = LookupButton(channel)
    if (TheButton == 0):
        log("Button 0 Pressed")
        Pressed = 0
        Listening = 0
    elif (TheButton > 0 and Listening == 1):
        log("Button {} Pressed -- Listening".format(TheButton))
        Pressed = TheButton
        Listening = 0
    elif (TheButton > 0 and Listening == 0):
        log("Button {} Pressed -- NOT Listening".format(TheButton))
        Pressed = -1

##### MAIN GAME LOOP
# Game Constants
DebugDisplay = True
button = [4, 12, 16, 20, 21]
LED = [22, 5, 6, 13, 19]

# Variables
Pressed = -1
Listening = 1
GameState = 1

# Set GPIO pins
for i in range (0,5):
    GPIO.setup(button[i], GPIO.IN, pull_up_down=GPIO.PUD_UP)
    GPIO.setup(LED[i], GPIO.OUT)
    GPIO.add_event_detect(button[i], GPIO.FALLING, callback=ButtonPressed, bouncetime=200)

# LED loop in waiting mode
while True:
    Listening = 1
    if GameState == 0:
        log("GameState = 0, Exiting Game")
        break
    elif GameState == 1:
        log("GameState = 1, Pressed = {}".format(Pressed))
        if Pressed == 0:
            GameState = 0
            break
        else:
            for i in range(1,5):
                LEDFlash(i,0.2)
                if Pressed == 0:
                    GameState = 0
                    break
                elif Pressed > 0:
                    GameState = 2
                    break
            Pressed = -1
    elif GameState == 2:
        log("GameState = 2, Pressed = {}".format(Pressed))
        Pressed = -1
        Listening = 0
        Simon = [random.randint(1,4)]
        Flashes(5, 2)
        while GameState == 2:
            log("Starting a round, length = {}".format(len(Simon)))
            Listening = 0
            PlayList(Simon)
            Flashes(1,0)
            for Levels in range(0, len(Simon)):
                log("Waiting for user input, correct answer = {}".format(Simon[Levels]))
                GPIO.output(LED[0], GPIO.HIGH)
                Listening = 1
                Pressed = -1
                for timerloop in range(0,500):
                    if Pressed > -1:
                        break
                    time.sleep(0.01)
                if Pressed == Simon[Levels]:
                    LEDFlash(Pressed, 0.5)
                else:
                    Flashes(5,2)
                    GameState = 1
                    break
            GPIO.output(LED[0], GPIO.LOW)
            if (GameState == 1 and Pressed != 0):
                PlayList(Simon[0:Levels])
                for i in range(0,10):
                    LEDFlash(Simon[Levels], 0.1)
                    time.sleep(0.1)
                time.sleep(1)
            elif GameState == 2:
                Simon.append(random.randint(1,4))
                time.sleep(0.2)
                Flashes(2,0)
                time.sleep(1)
        GameState = 1
        Pressed = -1

AllLEDsOff()
GPIO.cleanup()

I think I'm doomed to fail, unless...

After quite a bit of reflection and contemplation, I think I've set myself up for failure because of a failure of understanding of one crucial element. I didn't really understand this, but the interrupt doesn't do quite what I thought it would do.
My impression was that the interrupt caused the program to jump out of its current "line of thinking" to run a different code. But the main thread actually continues to run while the second thread gets started. This means that I kind of need to think of the interrupts as being slightly separate programs. They're not quite different because they can call on the same variables that are used in the main thread (and so can interact with that code), but it means that I can't just use the interrupts to record button presses.
For example, I had intended to have a 5 second timer count down while it waits for the player to press a button. But when the button is pressed, it doesn't really do anything to or with the timer. It just causes some other program to run while the timer continues on and does its own thing. Several button pushes in a short period of time leads to several threads running, and that non-linearity of programs running is not really the best thing.
That being said, there may be a way to deal with it. It comes down to creating a couple variables. One variable acts like the busy variable, telling the interrupt whether to pay attention to the button press or ignore it. And the second one is a variable that simply carries the value of the pressed button. This will hopefully restore the linearity of the program and allow me to keep some of the things that have been brewing in my mind. Otherwise, I'm going to have to go back to a polling method. But now that I'm thinking about it, this two-variable solution is not that far away from polling, anyway. But I'm just going to go with it and see what happens.

Sunday, January 11, 2015

Using the append method

The switch from pre-initializing a blank list to using the append method was much, much faster than I thought it would be. It only required one new function, which was the len() function. This just returns the length of a list. That's really all I have to say about this changes. Here's the code:
import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BCM)

def LEDflash(channel):
    GPIO.output(LED[i], GPIO.HIGH)
    time.sleep(0.5)
    GPIO.output(LED[i], GPIO.LOW)

def pushed(channel):
    global busy, MaxLength
    if busy == False:
        busy = True
        for i in range(1,5):
            if channel == button[i]:
                pressed = i
                LEDflash(i)
        buttonpush.append(pressed)
        ShowData()
        if len(buttonpush) == MaxLength:
            print "MaxLength reached"
            ResetData()
        busy = False

def ResetData():
    global MaxLength, buttonpush
    buttonpush = []
    print "Data cleared"
    ShowData()

def ShowData():
    if Display == True:
        print buttonpush

# Game Constants
MaxLength = 5
Display = True

# Label buttons and pins
button = [4, 12, 16, 20, 21]
LED = [22, 5, 6, 13, 19]

# Set GPIO pins
for i in range (0,5):
    GPIO.setup(button[i], GPIO.IN, pull_up_down=GPIO.PUD_UP)
    GPIO.setup(LED[i], GPIO.OUT)

for i in range (1,5):
    GPIO.add_event_detect(button[i], GPIO.FALLING, callback=pushed, bouncetime=200)

# Initialize variables
busy = False
buttonpush = []
game = True

GPIO.output(LED[0], GPIO.HIGH)
while game == True:
    GPIO.wait_for_edge(button[0], GPIO.FALLING)
    if len(buttonpush) == 0:
        game = False
    else:
        ResetData()
GPIO.output(LED[0], GPIO.LOW)
GPIO.cleanup()
I need to think carefully about the actual game now. I tried to just wing it, and I don't really like how the code is turning out. It's a confused mess, which I suppose is exactly what one would expect to happen if the code writer was just winging it. I'm going to spend some time thinking of the right way to organize the code so that everything works the way I want it to.

Friday, January 9, 2015

Recording button presses

I had to take a couple minutes to read up on initializing lists. I thought about using the append() method to do this, and I don't think it would really be that hard, but I wanted to go with what I knew, which was working with complete arrays from the start. Maybe because this was so simple, I'll go back and use append() just to make sure I understand it.
The basic idea here is that I wanted to get the button presses recorded into a list so that when the game is functioning I could check to see if the buttons were pressed in the correct order. I've got a few new variables in the program now:
  • MaxLength: This variable just indicates how long the maximum length of the list is going to be. I set it to 5 because I wanted to be able to work through the full list quickly. I'll probably set this to 50 when the game is actually running.
  • Display: I created this just so that I could create a function to display information for debugging, and then turn it off when I don't want it.
  • buttonpush: This is the list that contains the values of the buttons that were pressed. It's initialized as being all zeros and then the values are updated as the program runs.
  • loc: This is the location in the list.
This program is designed so that the kill button has two functions. If there is currently data in the system, then the data is cleared. If there is no data in the system, then it quits.
Here's the code:
import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BCM)

def LEDflash(channel):
    GPIO.output(LED[i], GPIO.HIGH)
    time.sleep(0.5)
    GPIO.output(LED[i], GPIO.LOW)

def pushed(channel):
    global busy, loc, MaxLength
    if busy == False:
        busy = True
        for i in range(1,5):
            if channel == button[i]:
                pressed = i
                LEDflash(i)
        buttonpush[loc] = pressed
        loc = loc + 1
        ShowData()
        if loc == MaxLength:
            print "MaxLength reached"
            ResetData()
        busy = False

def ResetData():
    global loc, MaxLength, buttonpush
    loc = 0
    buttonpush = [0] * MaxLength
    print "Data cleared"
    ShowData()

def ShowData():
    if Display == True:
        print loc
        print buttonpush

# Game Constants
MaxLength = 5
Display = True

# Label buttons and pins
button = [4, 12, 16, 20, 21]
LED = [22, 5, 6, 13, 19]

# Set GPIO pins
for i in range (0,5):
    GPIO.setup(button[i], GPIO.IN, pull_up_down=GPIO.PUD_UP)
    GPIO.setup(LED[i], GPIO.OUT)

for i in range (1,5):
    GPIO.add_event_detect(button[i], GPIO.FALLING, callback=pushed, bouncetime=$

# Initialize variables
busy = False
buttonpush = [0] * MaxLength
loc = 0
game = True

GPIO.output(LED[0], GPIO.HIGH)
while game == True:
    GPIO.wait_for_edge(button[0], GPIO.FALLING)
    if loc == 0:
        game = False
    else:
        ResetData()
GPIO.output(LED[0], GPIO.LOW)
GPIO.cleanup()

I think I am going to use the append() method and rewrite this program again. I think it's going to make the loc variable obsolete because I can use the length of the buttonpush array instead.

Ready to move forward again!

After being stalled for about a week on the interrupt problem, I was given some insights by the posters at the Raspberry Pi forum and was able to get my primary issue resolve. Unfortunately, I did not ever reach a point of truly learning why there was a problem.
However, I think I came close. One poster suggested that there might be an issue with disabling the interrupt in the middle of an interrupt. He used two terms that I don't fully understand: reentrant and thread-safe. Reentrancy has to do with the ability of a program to jump out of a thread in the middle of execution and reenter without problem. Thread safety has to do with the manipulation of shared data when it's being run on multiple threads. I can vaguely see that disabling the interrupt inside of the interrupt may cause some problems here.
But the functional resolution that came about was to introduce a "busy" variable that tells the interrupt that it's already in the middle of an interrupt. This avoids the problem of needing to disable the interrupts in the first place. The variable starts off being false and the first line of the interrupt checks the busy variable. If it's not busy, then it sets the variable to true and begins to run the flashing program. Here's the full code:
import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BCM)

busy = False

def LEDflash(channel):
    global busy
    if busy == False:
        busy = True
        for i in range(1,5):
            if channel == button[i]:
                GPIO.output(LED[i], GPIO.HIGH)
                time.sleep(0.5)
                GPIO.output(LED[i], GPIO.LOW)
        busy = False

# Label buttons and pins
button = [4, 12, 16, 20, 21]
LED = [22, 5, 6, 13, 19]

# Set GPIO pins
for i in range (0,5):
    GPIO.setup(button[i], GPIO.IN, pull_up_down=GPIO.PUD_UP)
    GPIO.setup(LED[i], GPIO.OUT)

for i in range (1,5):
    GPIO.add_event_detect(button[i], GPIO.FALLING, callback=LEDflash, bouncetime=200)

GPIO.output(LED[0], GPIO.HIGH)
GPIO.wait_for_edge(button[0], GPIO.FALLING)
GPIO.output(LED[0], GPIO.LOW)
GPIO.cleanup()

I don't think I'll have the time to play with this some more tonight, but the next thing I need to do is to start recording the button presses in a list and create a checking protocol. It shouldn't take long, but I've got some other stuff to do tonight before I jump into that.

Saturday, January 3, 2015

The GPIO interrupt problem is way over my head

After some more debugging (which means randomly changing things around and seeing what would happen), I think I've been able to show that the problem lies much deeper in the heart of the program than I am able to access and understand.
In the original code, I had it set up to call the interrupts in order from left to right as I'm looking at the breadboard. That code looked like this:
# Label buttons and pins
button = [4, 12, 16, 20, 21]
LED = [22, 5, 6, 13, 19]

I then rearranged it so that it would go through the buttons in the opposite order (leaving button 0 intact):
# Label buttons and pins
button = [4, 21, 20, 16, 12]
LED = [22, 19, 13, 6, 5]

And now the problem is happening with the left-most buttons instead of the right-most buttons. I don't know exactly what this means, but I think it has something to do with memory allocation or something like that. I think that some piece of code that's supposed to be able to point back to main code (before the interrupt is called) isn't pointing where it's supposed to.
Having speculated that this is the problem, I'm not going to pursue fixing it because I really have no idea how that part of the code functions, and I have no interest in learning that right now. This means that my project now needs to go forward without using interrupts. So I'm going to need to spend some time and rethink how I'm going to try to do this by polling the buttons instead.