Page 1 of 1

Having trouble getting my program up and running

Posted: Fri Mar 31, 2017 2:32 pm
by TheQuestionMark?
Hello Raspberry Pi Community :)

I'm currently working on a program. I've been working on this project for weeks. I watched a lot of tutorials and searched solution for my problems on the internet but without success.
My project in a few sentences: It is going to be some kind of an exercise for a goalkeeper (reaction training). Maybe you've already seen such devices where some LEDs are mounted on a wall lighting up one after the other and you have to touch them as fast as you can. I want to rebuild that(because its super expensive) but not as professional as those Pro- Devices. (There's a link with an example if it wasn't clear enough: https://youtu.be/9P-_9OJ65Bc )

I'm using a Raspberry Pi 3 and a LCD 1602 with a backpack (so it will be controlled by the I2C). I have 8 LED + 8 Buttons and an 'OK'-Button(on pin 40).

In my code I described the task of some lines with a '##'
The lines which i commented out with a ' ## ????' are the lines i don't know what to type to make the described task working.

I would be very grateful if someone could help me out and post the code with the solutions in it.

This is called React-Pro8.py

Code: Select all

import I2C_LCD_driver
from time import sleep, time
from random import choice, randrange
import RPi.GPIO as GPIO
import os



def setup():
##  I always get a warning that the gpio pins are already in use. is that a problem?
    GPIO.setwarnings(False)
    GPIO.setmode(GPIO.BOARD)

    GPIO.setup(7,GPIO.OUT)
    GPIO.setup(11,GPIO.IN, pull_up_down=GPIO.PUD_UP)
    GPIO.setup(12,GPIO.OUT)
    GPIO.setup(13,GPIO.IN, pull_up_down=GPIO.PUD_UP)
##    Button for interrupt or select start -> OK button
    GPIO.setup(40,GPIO.IN, pull_up_down=GPIO.PUD_UP)
    ##There will be a total of 8 LED and 8 Buttons (When the program is finished i'll add those 6 LED and 6 Buttons)


def arcademode_logic():
    setup()

    global actledschalter
    global actledschalteralt
    global result
    global ledschalter
## should run until the timer reached 0 (but it doesn't work)
    while timer != 0:
    ## Prevents from lighting up the same LED twice in a row
        while actledschalter == actledschalteralt:
            actledschalter = choice(ledschalter)
        actledschalteralt = actledschalter
    ## Lights up the randomly chosen LED
        GPIO.output(actledschalter[0], True)
        start = time.time()
    ## Waits until the button is pushed
        GPIO.wait_for_edge(actledschalter[1],GPIO.FALLING)
            
        end = time.time()
    ## Turns the LED off
        GPIO.output(actledschalter[0], False)
        reaktime = round(end-start,3)
        result.append(reaktime)
        results()

    ##  ???? if the OK button is pressed (at any time during this while loop) it should
    ##  ???? break out of the loop and get back to the 'welcome' screen


##  waits until the ok button is pressed to get back to the welcome screen.
    GPIO.wait_for_edge(40,GPIO.FALLING)
    
##Deletes all the results of the previous run
    result = []
    welcome()







def results():
    global result

    total = 0.0

    bestreakt = min(result)
##amount of buttons you've already pushed within the countdown
    points = str(len(result))

##    calculates the average reaction time
    for x in result:
        total += x
    avreakt = (total/len(result))



## ???? i think i made a terrible job for the timer. i just want it to run
## ???? simultaneously with the arcademode_Display() and the arcademode_logic
## ???? and every second it sends them the new 'timer' value
def countdown(clocktime):
    timer = clocktime
   
    for x in range(0,(clocktime + 1)):
##  ????      each second a new clock value (timer) has to appear on the screen
##  ????      and be sent to the arcademode_logic while loop. 
        return timer

        sleep(1)
        timer -=1
        



def arcademode_Display():
   
    mylcd = I2C_LCD_driver.lcd()

    #Self-created Characters
    fontdata1 = [      
            [ 0b00100,
            0b01110,
            0b11111,
            0b00100,
            0b00100,
            0b00100,
            0b00100,
            0b00100 ],

            [ 0b00100,
            0b00100,
            0b01110,
            0b10101,
            0b10101,
            0b01110,
            0b00100,
            0b00100 ]

            
    ]

    mylcd.lcd_load_custom_chars(fontdata1)
    
    
## ???? each time a new value comes in (bestreakt, avreakt, countdown (timer), points)
## ???? the lcd has to refresh
    while True:
        mylcd.lcd_display_string("%s: %0.3fs" % (chr(0), bestreakt), 1,0)
        mylcd.lcd_display_string("%s: %0.3fs" % (chr(1), avreakt), 2,0)
        mylcd.lcd_display_string("%d" % (timer),1,14)
        mylcd.lcd_display_string("%s" % (points),2,14)
    



def welcome():
    mylcd = I2C_LCD_driver.lcd()
## ???? wait until OK button is pushed to break out of the loop
    while True:
        mylcd.lcd_display_string("%s" % ('React-Pro'), 1, 3)
        mylcd.lcd_display_string("%s" % ('Start'), 2,5)
        sleep(0.75)
        mylcd.lcd_display_string("%s" % ('     '), 2,5)
        sleep(0.75)

    mylcd.lcd_clear()
    
    mylcd.lcd_display_string("%s" % ('React-Pro'), 1, 3)
    mylcd.lcd_display_string("%s" % ('...3...'), 2,4)
    sleep(1)
    mylcd.lcd_display_string("%s" % ('...2...'), 2,4)
    sleep(1)
    mylcd.lcd_display_string("%s" % ('...1...'), 2,4)
    sleep(1)
    mylcd.lcd_clear()

## ???? all three of them have to be executed simultaneously
    arcademode_logic()
    arcademode_Display()
    countdown(20)





##every LED is asigned to a button
ledschalter = ((7,11),(12,13)) 
result = []
actledschalter = 0
actledschalteralt = 0

welcome()

This is called I2C_LCD_driver.py which i got from the internet. (There's nothing wrong with that code)

Code: Select all

# -*- coding: utf-8 -*-
# Original code found at:
# https://gist.github.com/DenisFromHR/cc863375a6e19dce359d

"""
Compiled, mashed and generally mutilated 2014-2015 by Denis Pleic
Made available under GNU GENERAL PUBLIC LICENSE

# Modified Python I2C library for Raspberry Pi
# as found on http://www.recantha.co.uk/blog/?p=4849
# Joined existing 'i2c_lib.py' and 'lcddriver.py' into a single library
# added bits and pieces from various sources
# By DenisFromHR (Denis Pleic)
# 2015-02-10, ver 0.1

"""

# i2c bus (0 -- original Pi, 1 -- Rev 2 Pi)
I2CBUS = 1

# LCD Address
ADDRESS = 0x27

import smbus
from time import sleep

class i2c_device:
   def __init__(self, addr, port=I2CBUS):
      self.addr = addr
      self.bus = smbus.SMBus(port)

# Write a single command
   def write_cmd(self, cmd):
      self.bus.write_byte(self.addr, cmd)
      sleep(0.0001)

# Write a command and argument
   def write_cmd_arg(self, cmd, data):
      self.bus.write_byte_data(self.addr, cmd, data)
      sleep(0.0001)

# Write a block of data
   def write_block_data(self, cmd, data):
      self.bus.write_block_data(self.addr, cmd, data)
      sleep(0.0001)

# Read a single byte
   def read(self):
      return self.bus.read_byte(self.addr)

# Read
   def read_data(self, cmd):
      return self.bus.read_byte_data(self.addr, cmd)

# Read a block of data
   def read_block_data(self, cmd):
      return self.bus.read_block_data(self.addr, cmd)


# commands
LCD_CLEARDISPLAY = 0x01
LCD_RETURNHOME = 0x02
LCD_ENTRYMODESET = 0x04
LCD_DISPLAYCONTROL = 0x08
LCD_CURSORSHIFT = 0x10
LCD_FUNCTIONSET = 0x20
LCD_SETCGRAMADDR = 0x40
LCD_SETDDRAMADDR = 0x80

# flags for display entry mode
LCD_ENTRYRIGHT = 0x00
LCD_ENTRYLEFT = 0x02
LCD_ENTRYSHIFTINCREMENT = 0x01
LCD_ENTRYSHIFTDECREMENT = 0x00

# flags for display on/off control
LCD_DISPLAYON = 0x04
LCD_DISPLAYOFF = 0x00
LCD_CURSORON = 0x02
LCD_CURSOROFF = 0x00
LCD_BLINKON = 0x01
LCD_BLINKOFF = 0x00

# flags for display/cursor shift
LCD_DISPLAYMOVE = 0x08
LCD_CURSORMOVE = 0x00
LCD_MOVERIGHT = 0x04
LCD_MOVELEFT = 0x00

# flags for function set
LCD_8BITMODE = 0x10
LCD_4BITMODE = 0x00
LCD_2LINE = 0x08
LCD_1LINE = 0x00
LCD_5x10DOTS = 0x04
LCD_5x8DOTS = 0x00

# flags for backlight control
LCD_BACKLIGHT = 0x08
LCD_NOBACKLIGHT = 0x00

En = 0b00000100 # Enable bit
Rw = 0b00000010 # Read/Write bit
Rs = 0b00000001 # Register select bit

class lcd:
   #initializes objects and lcd
   def __init__(self):
      self.lcd_device = i2c_device(ADDRESS)

      self.lcd_write(0x03)
      self.lcd_write(0x03)
      self.lcd_write(0x03)
      self.lcd_write(0x02)

      self.lcd_write(LCD_FUNCTIONSET | LCD_2LINE | LCD_5x8DOTS | LCD_4BITMODE)
      self.lcd_write(LCD_DISPLAYCONTROL | LCD_DISPLAYON)
      self.lcd_write(LCD_CLEARDISPLAY)
      self.lcd_write(LCD_ENTRYMODESET | LCD_ENTRYLEFT)
      sleep(0.2)


   # clocks EN to latch command
   def lcd_strobe(self, data):
      self.lcd_device.write_cmd(data | En | LCD_BACKLIGHT)
      sleep(.0005)
      self.lcd_device.write_cmd(((data & ~En) | LCD_BACKLIGHT))
      sleep(.0001)

   def lcd_write_four_bits(self, data):
      self.lcd_device.write_cmd(data | LCD_BACKLIGHT)
      self.lcd_strobe(data)

   # write a command to lcd
   def lcd_write(self, cmd, mode=0):
      self.lcd_write_four_bits(mode | (cmd & 0xF0))
      self.lcd_write_four_bits(mode | ((cmd << 4) & 0xF0))

   # write a character to lcd (or character rom) 0x09: backlight | RS=DR<
   # works!
   def lcd_write_char(self, charvalue, mode=1):
      self.lcd_write_four_bits(mode | (charvalue & 0xF0))
      self.lcd_write_four_bits(mode | ((charvalue << 4) & 0xF0))
  
   # put string function with optional char positioning
   def lcd_display_string(self, string, line=1, pos=0):
    if line == 1:
      pos_new = pos
    elif line == 2:
      pos_new = 0x40 + pos
    elif line == 3:
      pos_new = 0x14 + pos
    elif line == 4:
      pos_new = 0x54 + pos

    self.lcd_write(0x80 + pos_new)

    for char in string:
      self.lcd_write(ord(char), Rs)

   # clear lcd and set to home
   def lcd_clear(self):
      self.lcd_write(LCD_CLEARDISPLAY)
      self.lcd_write(LCD_RETURNHOME)

   # define backlight on/off (lcd.backlight(1); off= lcd.backlight(0)
   def backlight(self, state): # for state, 1 = on, 0 = off
      if state == 1:
         self.lcd_device.write_cmd(LCD_BACKLIGHT)
      elif state == 0:
         self.lcd_device.write_cmd(LCD_NOBACKLIGHT)

   # add custom characters (0 - 7)
   def lcd_load_custom_chars(self, fontdata):
      self.lcd_write(0x40);
      for char in fontdata:
         for line in char:
            self.lcd_write_char(line)         
         

Kind Regards
TheQuestionMark?

Hello everybody, its me again.
I just wanted to thank all of you because it has been an amazing journey. 1.5 Years after i got the idea i finally finished the Project with two friends. You helped me a lot. See you soon on another Project :P

Re: Having trouble getting my program up and running

Posted: Fri Mar 31, 2017 4:13 pm
by ghp
Hello,
this is an already complicated program and not easy to tear apart.

(1) there are some global variables in the code which should be declared in an overall section
bestreakt, avreakt, timer, points and possibly others. Just place these to the top, example:

Code: Select all

globvar = 0

def set_globvar_to_one():
    global globvar    # Needed to modify global copy of globvar
    globvar = 1

(2) you want to have some code run in parallel. Threads are handy for this. https://docs.python.org/2/library/threading.html
From this

Code: Select all

## ???? all three of them have to be executed simultaneously
    arcademode_logic()
    arcademode_Display()
    countdown(20)
construct this:

Code: Select all

    import threading
    threads = []
    t = threading.Thread(target=arcademode_logic)
    threads.append(t)
    t = threading.Thread(target=arcademode_Display)
    threads.append(t)
    t = threading.Thread(target=countdown, args=(20,))
    threads.append(t)
    for t in threads:
        t.start()
Now these functions run in parallel, which rises more problems to solve, see 'the threads should terminate'.

(3) the arcademode_Display method will consume lot of cpu, there should be some delay in it. Just to give you an impression on how this should look like, here an example:

Code: Select all

    previous_bestreact = None
    while True:
        sleep(0.1)
        if previous_bestreact != bestreact: 
            mylcd.lcd_display_string("%s: %0.3fs" % (chr(0), bestreakt), 1,0)
            previous_bestreact = bestreact
        # similiar for the other variables
        # ..
(4) the threads should terminate. If you send 'the control flow' into a while True-loop, it will never come out of this. There are complicated ways to do this, simple one is to create a global variable 'runGame = False' and use this to terminate endless loops.

Code: Select all

import threading  # put this to import section
runGame = False    #put this below import section

    # this is the part discussed before where the threads are started.
    # set runGame to true 
    global runGame
    runGame = True
    threads = []
    t = threading.Thread(target=arcademode_logic)
The difficult part is to to listen in all these three modules arcademode_logic, arcademode_Display, countdown to this runGame-flag and terminate loop. An example for the arcademode_Display

Code: Select all

    while runGame:  # the loop terminates when runGame is set to false.
        sleep(0.1)
        if previous_bestreact != bestreact: 
            mylcd.lcd_display_string("%s: %0.3fs" % (chr(0), bestreakt), 1,0)
            previous_bestreact = bestreact
(5) someones needs to terminate.
It is most possibly the timer?

Code: Select all

def countdown(clocktime):
    global timer
    timer = clocktime
   
    for x in range(0,(clocktime + 1)):
        # this is usually not needed, but if someone decides to end the game when timer is running, then stop it
        if runGame == False:
            break
##  ????      each second a new clock value (timer) has to appear on the screen
##  ????      and be sent to the arcademode_logic while loop.
        sleep(1)
        timer -=1
    global runGame
    runGame = False
(6) the game must go on
After you started the threads, wait for the end of all other threads
So you end with a code like

Code: Select all

    threads = []
    t = threading.Thread(target=arcademode_logic)
    threads.append(t)
    t = threading.Thread(target=arcademode_Display)
    threads.append(t)
    t = threading.Thread(target=countdown, args=(20,))
    threads.append(t)
    global runGame
    runGame = True
    for t in threads:
        t.start()

    while runGame == True:
        sleep(0.1)
    # wait for all threads have stopped
    for t in threads:
        t.join()
The code fragments are not tested, you might run into syntax errors.

Hope this helps,
Gerhard

Re: Having trouble getting my program up and running

Posted: Fri Mar 31, 2017 9:31 pm
by TheQuestionMark?
Dear Gerhard

You are literally my hero.
I finally got my program up and running *.*
After a couple of hours studying your code, inserting and cutting out some code it finally worked.

I have a question though:
The LED's don't turn off after the program, which is pretty annoying. I tried it with GPIO.cleanup() but that didn't work.
Do you have any suggestions how i could solve that problem?

Re: Having trouble getting my program up and running

Posted: Sat Apr 01, 2017 5:29 am
by ghp
Hello,
the GPIO.cleanup() should shut off outputs and LED. Perhaps organization of code is still not optimal.
Example from posted code: the last statement in code is to call welcome(). This welcome() uses arcade..logic() which then calls welcome() to run next game. This results in a long chain of nested calls, not good. And in addition to this, the setup() of hardware is done each time in in arcade..logic().
Actions:
Move setup() to begin of code.
Make 'welcome' a function which displays version and 'press GO to start'.
Move the countdown to a separate funktion which shows ("3,2,1,go")

something like

Code: Select all

...imports
...functions

setup()
try:
    while True:
        welcome()
        .... waitfor ok button
        showCountdown()
        ....start the actionThreads as it was previously in welcome()
except KeyboardException:
    runGame=False
finally:
    cleanup()
 


In this way, the main code 'makes the major decisions'.

Hope this helps,
Gerhard