NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Project Help and Ideas » ultrasonic ping_pong

June 05, 2012
by ead3281
ead3281's Avatar

I just completed a project that uses an ultrasonic ping sensor. The distance from the sensor is used to position a paddle in a pong game. Pygame is used to make the pong interface. I used the code from a pygame tutorial example called chimp.py(see- www.pygame.org/docs/). It was great fun building the project and its a blast to play pong by running back and forth across the room. The code is shown below. Sorry if it is a little lengthy, I tried to insert comments to explain what was going on

Here's the C code for the microcontroller

// ping.c
// for NerdKits with ATmega168
// 
//05/29/2012
//This code was written to operate a parallax ping sensor
//It implements a timer and external interrupts via pin PCO
//The ping sensor has only one data line so PCO must be operated
//both in read and write mode
//It ouputs the objects distance via the uart

#define F_CPU 14318180//Note I'm using a different crystal than supplied
//with nerdkit
#include <avr/pgmspace.h>//Needed for PSTR function
#include <avr/io.h>
#include <inttypes.h>
#include <avr/interrupt.h>

#include "../libnerdkits/uart.h"
#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"

uint16_t counter=0;
uint16_t int_counter=0;//intervals between pings are 25ms (.01ms*2500)
double time;

ISR(TIMER1_COMPA_vect)//this is the interrupt handler, gets called by the timer
{

   if (counter==0){
     PCICR &= ~(1<<PCIE1);//Enables interrupts on PC0-PC7 see pg. 69
     PCMSK1 &= ~(1<<PCINT8);//pin PC0 triggers interrupt
     DDRC |= (1<<PC0);
     PORTC |=(1<<PC0);
     NOP;
     //5 us pulse tells ping to output a sound pulse
     delay_us(5);
     DDRC &=~(1<<PC0);
     PCICR |= (1<<PCIE1);//Enables interrupts on PC0-PC7 see pg. 69
     PCMSK1 |= (1<<PCINT8);//pin PC0 triggers interrupt
   }

   counter++; 
   if (counter==2500){

     counter=0;
   }

}

ISR(PCINT1_vect){
  if(counter>80){
    //Ping sensor outputs a pulse whose length corresponds to distance of object
    //The leading edge of the pulse occurs at .79 ms thats where the 
    //79.0(below) and 80 (above) come from 
    // The rest of the math is due to the speed of sound 
    //being 340 m/s.  The .00001 refers to .01 ms which are the increments
    //of the timer.  The factor of two is due to the sound has to go to
    //the sensed object and return to the sensor
    float distance=(counter-79.0)*340*.00001/2;
    printf_P(PSTR("%.2fM\r\n"),distance);
  }  
}
int main() {
  // start up the serial port
  uart_init();
  FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, 
                       _FDEV_SETUP_RW);
  stdin=stdout=&uart_stream;
  //I am going to add some code to try and generate a timestamp
  TCCR1B |=(1<<WGM12);//Configure timer 1 for CTC mode
  TIMSK1 |=(1<<OCIE1A);//Enable CTC interrupt
  sei();//Enable global interrupts
  OCR1A=143;//Set CTC compare value for .01 msec time steps
  TCCR1B |=(1<<CS10);//Start timer at F oscillator
  //Documentation for above can be found in ATMega guide
  //TCCR1B pg 132; OCR1A pg 133;TIMSK1 pg 13
  // loop keeps looking forever
  while(1) {
  }

  return 0;
}

And here's the code for the pong game:

#!/usr/bin/env python
"""
Below is the comments from the original chimp.py
This simple example is used for the line-by-line tutorial
that comes with pygame. It is based on a 'popular' web banner.
Note there are comments here, but for the full explanation,
follow along in the tutorial.
Below are my comments
I modified the code to make a pong game.  The paddle is controlled 
via an ultrasonic ping parallax sensor.  I never changed the pong ball
from the original Chimp class, so Chimp refers to the ball
"""

#Import Modules
import serial
import os, pygame
from pygame.locals import *
from pygame.compat import geterror

if not pygame.font: print ('Warning, fonts disabled')
if not pygame.mixer: print ('Warning, sound disabled')

main_dir = os.path.split(os.path.abspath(__file__))[0]
data_dir = os.path.join(main_dir, 'data')

#constants
PLAYER_SPEED   = 10
global going
#functions to create our resources
def load_image(name, colorkey=None):
    fullname = os.path.join(data_dir, name)
    try:
        image = pygame.image.load(fullname)
    except pygame.error:
        print ('Cannot load image:', fullname)
        raise SystemExit(str(geterror()))
    return image, image.get_rect()

class Paddle(pygame.sprite.Sprite):
    """moves a paddle across the screen.."""
    def __init__(self):
        pygame.sprite.Sprite.__init__(self) #call Sprite intializer
        self.image, self.rect = load_image('paddle_v.bmp', -1)
        global screen
        screen = pygame.display.get_surface()
        self.area = screen.get_rect()
        self.rect.topleft = 5, 250
    def move(self, direction):
        self.rect = self.rect.move(0,-direction*PLAYER_SPEED).\
            clamp(screen.get_rect())

class Chimp(pygame.sprite.Sprite):
    """This was the original code to move the monkey across the screen.
    I am using it to move the pong ball acrosss the screen"""
    def __init__(self):
        self.PLAYER_SPEED  = 10
        self.B_SPEED=5
        self.HITS=0
        pygame.sprite.Sprite.__init__(self) #call Sprite intializer
        self.image, self.rect = load_image('blip.bmp', -1)
        screen = pygame.display.get_surface()
        self.area = screen.get_rect()
        self.rect.topleft = 787, 488
        self.moveX=-self.B_SPEED
        self.moveY=-self.B_SPEED

    def collide(self):
        self.moveX=self.B_SPEED
        newpos = self.rect.move((self.moveX, self.moveY))
        self.HITS=self.HITS+1
        if (self.HITS==5):
            self.B_SPEED=self.B_SPEED+1
            self.HITS=0
    def update(self):
        test=self._walk()
    def _walk(self):
        state=0
        "move the monkey across the screen, and turn at the ends"
        newpos = self.rect.move((self.moveX,self.moveY))
        if self.rect.left < self.area.left-25:
            self.moveX=-self.B_SPEED
            self.moveY=-self.B_SPEED
            self.rect.topleft=787,200
            self.B_SPEED=5
            self.HITS=0
            newpos = self.rect.move((self.moveX, self.moveY))
        if self.rect.right > self.area.right:
            self.moveX=-self.B_SPEED
            newpos = self.rect.move((self.moveX, self.moveY))
        if self.rect.top < self.area.top:
            self.moveY = self.B_SPEED
            newpos = self.rect.move((self.moveX, self.moveY))
        if self.rect.bottom>self.area.bottom:
            self.moveY = -self.B_SPEED
            newpos = self.rect.move((self.moveX, self.moveY))
        self.rect = newpos
        return state
def main():
    """this function is called when the program starts.
       it initializes everything it needs, then runs in
       a loop until the function returns."""
#Initialize Everything
    pygame.init()
    screen = pygame.display.set_mode((800, 600))
    pygame.display.set_caption('Back to the Seventies')
    pygame.mouse.set_visible(0)

#Create The Backgound
    background = pygame.Surface(screen.get_size())
    background = background.convert()
    background.fill((250, 250, 250))

#Put Text On The Background, Centered
    if pygame.font:
        font = pygame.font.Font(None, 20)
        text = font.render("And we're off", 1, (10, 10, 10))
        textpos = text.get_rect(centerx=background.get_width()/2)
        background.blit(text, textpos)

#Display The Background
    screen.blit(background, (0, 0))
    pygame.display.flip()

#Prepare Game Objects
    clock = pygame.time.Clock()
    chimp = Chimp()
    paddle=Paddle()
    allsprites = pygame.sprite.RenderPlain( chimp,paddle)

#Main Loop
    going = True
    while going:
        clock.tick(60)

        #Handle Input Events
        pygame.event.pump()
        keystate = pygame.key.get_pressed()
        if keystate[K_ESCAPE] or pygame.event.peek(QUIT):
            going=False
        direction = keystate[K_UP] - keystate[K_DOWN]
        paddle.move(direction)

        allsprites.update()
        paddlerect=[paddle.rect]

        x=chimp.rect.collidelist(paddlerect)
        if x==0:
            #print "we have contact"
            chimp.collide()
        #Draw Everything
        screen.blit(background, (0, 0))
        allsprites.draw(screen)
        pygame.display.flip()

pygame.quit()

#Game Over

#this calls the 'main' function when this script is executed
if __name__ == '__main__':
    main()
June 05, 2012
by ead3281
ead3281's Avatar

Ooops, I forgot I was modifying the pygame code so that the ball would speed up every five paddle hits and I had taken out the code that reads in the serial input. Here's what the main loop in the bottom of the pygame code should be to read the serial input and move the paddle

#Main Loop
    going = True
    s = serial.Serial("/dev/ttyUSB0", 115200)
    while going:

        x=s.readline()
        str_m=x.split('M')
        paddle_x= int(eval(str_m[0])*100)
        clock.tick(60)

        #Handle Input Events
        pygame.event.pump()
        keystate = pygame.key.get_pressed()
        if keystate[K_ESCAPE] or pygame.event.peek(QUIT):
            going=False
        direction = keystate[K_UP] - keystate[K_DOWN]
        paddle.move(direction)

        allsprites.update()
        paddlerect=[paddle.rect]

        x=chimp.rect.collidelist(paddlerect)
        if x==0:
            print "we have contact"
            chimp.collide()
        #Draw Everything
        paddle.rect.topleft = (10,600-(paddle_x-50)*3)

        screen.blit(background, (0, 0))
        allsprites.draw(screen)
        pygame.display.flip()

pygame.quit()

#Game Over

#this calls the 'main' function when this script is executed
if __name__ == '__main__':
    main()

Sorry for the earlier mix-up

Post a Reply

Please log in to post a reply.

Did you know that hobby servos are controlled through a particular kind of PWM (Pulse Width Modulation) signal? Learn more...