µLogTimer: Lap Timer Project Source Code

source code

Posted here for convenience. Source code consists of the following 6 files:

uart.h

//asynchronous 8n1 serial transmission per AVR AppNote #305
void SerialPutChar(char);
char SerialGetChar(void);

uart.s

//
// UART Functions per AVR Application Note #305
//
#include <avr/io.h>
#include "uLogTimer.h"

//#define _SFR_ASM_COMPAT 1  /* Not sure when/if this is needed */
//#define __SFR_OFFSET 0

//
//half bit delay
//
#define b 31				//38400 bps@8MHz with 0.3% error	
//#define b 66				//19200 bps@8MHz with 0.6% error
UARTDelay:	
		ldi  r17, b
UARTDelay1:
		dec  r17
		brne UARTDelay1
		ret

//
//asynchronous 8n1 serial transmit byte
//
#define TX_PIN 6			//TX pin is PA6

.global SerialPutChar

SerialPutChar:
		push r16
		push r17
		ldi  r16, 10		;1 start + 8 data + 1 stop bits (bit count) 
		com  r24			;Invert everything (r24 = byte to xmit)
		sec					;Start bit
putchar0:
		brcc putchar1		;If carry set
		cbi  _SFR_IO_ADDR(PORTA), TX_PIN ;send a '0'
		rjmp putchar2		;else	
putchar1:
		sbi  _SFR_IO_ADDR(PORTA), TX_PIN ;send a '1'
		nop
putchar2:
		rcall UARTDelay		;1/2 bit delay +
		rcall UARTDelay		;1/2 bit delay = 1bit delay
		lsr   r24			;Get next bit
		dec   r16			;If not all bits sent
		brne  putchar0		;send next
							;else
		pop   r17
		pop   r16
		ret					;return

//
//asynchronous 8n1 serial receive byte
//
#define RX_PIN 5			//RX pin is PA5

.global SerialGetChar

SerialGetChar:
		push  r16
		push  r17
		ldi   r16, 9		;8 data bits + 1 stop bit
getchar1:
		sbic  _SFR_IO_ADDR(PINA), RX_PIN ;Wait for start bit
		rjmp  getchar1
		rcall UARTDelay	;we need 1.5 bit delay after start bit edge
getchar2:
		rcall UARTDelay		;1 bit delay from here
		rcall UARTDelay		
		clc					;clear carry
		sbic  _SFR_IO_ADDR(PINA), RX_PIN ;if RX pin high
		sec					;
		dec   r16			;If bit is stop bit
		breq  getchar3		;   return
							;else
		ror   r24			;   shift bit into r24 (rec'd byte)
		rjmp  getchar2		;   go get next
getchar3:
		ldi   r25, 0x00		;zero extend r24:r25 for return
		pop   r17
		pop   r16
		ret

dataflash.h

//
//at45db161d dataflash memory functions
//

//
//pinouts for uLog/uLogTimer
//

#define SCK 	3		//PA3
#define SI 		1		//PB1
#define SO 		7		//PA7
#define CS 		2		//PB2

//
//declerations
//

//void DFFlashClock(void);
void DFSendCommand(uint8_t command);
uint8_t DFStatus(void);
void DFSend10Bits(uint16_t buffer);
void DFSend14Bits(uint16_t page);
uint32_t DFReadPage(uint16_t index);
void DFWritePageThruBuffer(uint16_t index, uint32_t value);
void DFChipErase(void);
void DFBlockErase(void);
void DFTransferPage(uint16_t index);

/*
void DFFlashClock(void) {
  PORTA |= (1<<SCK);	//SCK hi, SCK = 1 
  PORTA &= ~(1<<SCK);	//SCK lo, SCK = 0
}
*/

#define DFFlashClock()	\
  PORTA |= (1<<SCK);	\
  PORTA &= ~(1<<SCK);

dataflash.c

//
//selected at45db161d dataflash memory functions
//for uLogTimer application
//1/11/2012
//
#include <avr/io.h>
#include "dataflash.h"

//submit command
void DFSendCommand(uint8_t command) {
  uint8_t i;
	
  PORTB &= ~(1<<CS);			//CS low
  //send command
  for (i=0; i<8; i++) {
    if (command & 0x80) 
      PORTB |= (1<<SI);			//SI high
    else 
      PORTB &= ~(1<<SI);		//SI low
    DFFlashClock();
    command <<= 1;   
  }
}

//get dataflash status
uint8_t DFStatus(void) {
  uint8_t i, j, temp;
  
  temp = 0;
  DFSendCommand(0x57);			//legacy staus register read

  DFFlashClock();
  for (i=0; i<8; i++) {
    temp <<= 1;
    PORTA |= (1<<SCK);			//SCK hi//SCK = 1
    j = PINA;
    if (j & (1<<SO)) 
      temp += 1;
    PORTA &= ~(1<<SCK);			//SCK low//SCK = 0;
  }

  PORTB |= (1<<CS);				//CS hi
  return temp;
}

//send 2 don't care & 12 page address bits
void DFSend14Bits(uint16_t page) {
  uint8_t i;

  //send 2 "dont care" bits
  PORTB &= ~(1<<SI);			//SI low, SI = 0
  DFFlashClock();
  DFFlashClock();

  //send 12 page address bits
  for (i=0; i<12; i++) {   
    if (page & 0x800)			//0x800 = 1000 0000 0000 (bits)
      PORTB |= (1<<SI);			//SI high
    else 
      PORTB &= ~(1<<SI);		//SI low
    DFFlashClock();
    page <<= 1;
  }
}

//
void DFSend10Bits(uint16_t buffer) {
  uint8_t i;

  //send 10 address bits
  for (i=0; i<10; i++) {   
    if (buffer & 0x200)			//0x200 = 0010 0000 0000 (bits)
      PORTB |= (1<<SI);			//SI high
    else 
      PORTB &= ~(1<<SI);		//SI low
    DFFlashClock();
    buffer <<= 1;
  }
}

//read 32bit value from dataflash memory
uint32_t DFReadPage(uint16_t index) {
  uint32_t temp;
  uint16_t page, buffer;
  uint8_t i, bit;

  //4096 pages @ 528 bytes per page
  page = index/132;
  buffer = (index*4)%528;
  DFSendCommand(0xD2);			//command
  DFSend14Bits(page);			//page address
  DFSend10Bits(buffer);			//buffer address
  
  //send 4 don't care bytes
  for (i=0; i<32; i++) {   
    PORTB &= ~(1<<SI);			//SI low
    DFFlashClock();
  }

  //read 32 bits of data
  temp = 0;
  for (i=0; i<32; i++) {
    temp <<= 1;
    PORTA |= (1<<SCK);			//SCK hi
    bit = PINA;
    if (bit & (1<<SO)) 
      temp += 1;
    PORTA &= ~(1<<SCK);			//SCK low
  }

  PORTB |= (1<<CS);				//CS hi, flash_CS = 1;  
  /*
  if (temp == 0xFFFFFFFF)		//all 1s indicate memory is empty 
    return 0;
  */
  return temp;
}

//write 32bit to dataflash memory
void DFWritePageThruBuffer(uint16_t index, uint32_t value) {
  uint16_t page, buffer;
  uint8_t i;

  //4096 pages @ 528 bytes per page
  page = index/132;
  buffer = (index*4)%528;
  DFSendCommand(0x82);			//utilize buffer 1
  DFSend14Bits(page);			//page address+
  DFSend10Bits(buffer);			//buffer address

  //send 32 bits of data
  for (i=0; i<32; i++) {   
    if (value & 0x80000000) 
      PORTB |= (1<<SI);			//SI high
    else 
      PORTB &= ~(1<<SI);		//SI low
    DFFlashClock();
	value <<= 1;
  }

  //page complete
  PORTB |= (1<<CS);				//CS hi 
}

//erase entire memory at one time
void DFChipErase(void) {
  //
  PORTB &= 0xFE;				//status LED on
  //
  DFSendCommand(0xC7);			//4-byte command sequence
  DFSendCommand(0x94);
  DFSendCommand(0x80);
  DFSendCommand(0x9A);
  PORTB |= (1<<CS);				//chip erase @ rising edge of flash_CS
  while (!(DFStatus() & 0x80)) 	//check the ready bit
    ;
  //
  PORTB |= 0x01; 				//status LED off
  //
}

//transfer dataflash page memory to buffer #1
void DFTransferPage(uint16_t index) {
  uint16_t page;
  //uint8_t i;

  page = index/132;				//only page address required
  DFSendCommand(0x53);			//utilize buffer 1
  DFSend14Bits(page);			//page address
  DFSend10Bits(0);				//don't care bits
  //buffer complete
  PORTB |= (1<<CS);				//CS hi 
  //wait for ready bit on the flash  
  while (!(DFStatus() & 0xA0))
    ;
}

uLogTimer.h

//
//uLog pin outs
//
//PA0 = Not used this implementation*
//PA1 = Not used this implementation*
//PA2 = IR detector*
//PA3 = SCK clock (DataFlash)
//PA4 = SCL*
//PA5 = RX/MISO*
//PA6 = TX/MOSI*
//PA7 = SO (DataFlash)
//PB0 = status LED
//PB1 = SI (DataFlash)
//PB2 = CS (DataFlash)
//PB3 = RESET*
//(*) pins on edge connector

//basic bit manipulation macros/defines
#define bit_set(p, m) ((p) |= (m))
#define bit_clear(p, m) ((p) &= ~(m))
#define bit_flip(p, m)  ((p) ^= (m))
#define bit_get(p, m) ((p) & (m))
#define BIT(x) (0x01 << (x))
#define LONGBIT(x) ((unsigned long)0x00000001 << (x))
#define loword(l) ((uint16_t)(l))
#define hiword(l) ((uint16_t)(((uint32_t)(l) >> 16) & 0xFFFF))
#define lobyte(w) ((byte)(w))
#define hibyte(w) ((byte)(((uint16_t)(w) >> 8) & 0xFF))

#define TRUE                  	1
#define FALSE                 	0

//internal oscillator freq
#define FCPU					8000000UL
#define STATUS_LED				0		//PB0
//frequency in MHz
#define CLOCK_CYCYLES_PER_MS	(FCPU/1000000UL)

//maimum # of laps
#define MAX_LAPS               	1000UL	//maximum in 1-page of dataflash: 528/4=132
										//4096 pages 132*4096=540672 laps!
//
//ir beacon codes
//
#define AIM_CODE				0
#define PRIVATE_CODE			1

//beacon pulse duration +/- tolerance
#define TOLERANCE				36       //can't remember why I selected 36 

//AIM beacon pattern [inverted by PNA4602M]:
//6ms low/624us high/1.2ms low/624us high/1.2ms low/624us high [repeat]
#define AIM_MAX_PULSE_US		(624 + TOLERANCE)
#define AIM_MIN_PULSE_US		(624 - TOLERANCE)

//Our private beacon pattern [inverted by PNA4602M]:
//6ms low/332us high/1.2ms low/332us high/1.2ms low/332us high [repeat]
//beacon pulse duration +/- tolerance
#define PRIVATE_MAX_PULSE_US	(332 + TOLERANCE)
#define PRIVATE_MIN_PULSE_US	(332 - TOLERANCE)

//we require reciept of 3 valid tokens
#define TOKEN_TARGET			3

//amount of time (ms) to ignore ir beacon after crossing start/finish line
#define IGNORE_BEACON_MS		2000UL	//7.5 secomds

//
//eeprom address
//

//lap times saved in eeprom
#define LAP_ADDRESS				0        //byte (number of laps stored)
#define BEACON_CODE_ADDRESS 	4

uLogTimer.c

/*
  uLog Lap Timer 
  Copyright 2012, all rights reserved.
  James M. Eli <j_eli@hotmail.com>
  1/18/2012
 
  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.
 
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
 
  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
  ---
  
  Progect Used:
    uLog - http://www.sparkfun.com/products/9228
    GP1UX311QS IR Detector (or similar) - http://www.adafruit.com/products/157
	AVR Programmer - http://www.pololu.com/catalog/product/1300
    FTDI Breakout - http://www.sparkfun.com/products/9716
	110 mAh LiPo Battery - http://www.sparkfun.com/products/731
    LiPo Battery Charger - http://www.sparkfun.com/products/10217
    IR Beacon - http://www.aimsports.com/

  ---
  
  Note:
    ATtiny24 program memory 98.0% full
*/
#include <string.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/eeprom.h>
#include "uLogTimer.h"
#include "dataflash.h"
#include "uart.h"

//
//declare globals 
//

//lap counter
volatile uint16_t lap;
//number of laps stored in datflash
uint16_t laps_completed;
//first lap of the current session
uint16_t first_lap;
//lap time (in milliseconds)
volatile uint32_t lap_time;
//start & finish time [us] of ir beacon pulses
uint32_t start_edge;
uint32_t pulse_us;
//valid ir beacon pulse counter
uint8_t tokens;
//millisecond counter
volatile uint32_t timer0_millis;

//
//General short delay, not terribly accurate
//

//milliseconds
void delay_ms(uint8_t ms) {
  for (; ms>0 ; ms--) {
    TIFR1 |= 0x01;		//Clear any interrupt flags on Timer1
    TCNT1 = 64538; 		//65536 - 998(us) = ~1ms, tweaked with AVR Studio Stopwatch
    while (!(TIFR1 & 0x01))
      ;
  }
}

//
//timer0 functions
//

//timer0 interrupt handler
ISR(TIM0_COMPA_vect) { 
  //incremented every 1ms
  timer0_millis++;
}

//returns current microsecond [us] count
uint32_t MicroSeconds(void) {
  uint32_t m;
  uint8_t t;
 
  m = timer0_millis;
  t = TCNT0;
  if ((TIFR0 & BIT(TOV0)) && (t < 124))
    m++;
  return (((m*125) + t)<<3);		//hard coded for 8MHz (<<3) = (64/CLOCK_CYCYLES_PER_MS)
}

//
//ir detector interrupt
//

//this interrupt is called when the ir detector senses activity (which could be sunlight, etc.)
ISR(PCINT0_vect) { 
  //lots of activity here for an interrupt...
  if (PINA & (1<<PINA2)) { //PA2 = ir detector
    //high edge change, so calculate ir pulse length
    pulse_us = MicroSeconds() - start_edge;
    start_edge = 0;

    //look for a pulse time that matches an aim beacon
    if (pulse_us > AIM_MIN_PULSE_US && pulse_us < AIM_MAX_PULSE_US)
      tokens++;
    else
	  return;	//unrecognized ir pulse
	 
    //wait until specified number of pulses (or "tokens")
    if (tokens >= TOKEN_TARGET) {
      //a valid beacon was tripped...
      tokens = 0;
      //capture now
      lap_time = timer0_millis;
      //kickoff?
      if (lap == (first_lap - 1)) {
        //start lap #1
        lap++;
        //start timer now...
        timer0_millis = 0;
        TCNT0 = 0;
        return;
      }

      //ignore unwanted (sector?) beacon
      if (lap_time < IGNORE_BEACON_MS)
        return; 
 
      //increment lap count
	  lap++;
      //reset lap timer because we are starting a new lap
      timer0_millis = 0;
      TCNT0 = 0;
      return;
    }

  } else 
    //low edge change, so save time the ir pulse starts
    start_edge = MicroSeconds();
}

//string buffer used by ultoa
static char ultoa_buffer[12];

char * ultoa(unsigned long val) {
  char *p;
     
  p = ultoa_buffer + sizeof(ultoa_buffer);
  *--p = '\0';
  do {
    *--p = '0' + val%10;
    val /= 10;
  } while (val);
  return p;
}

void PrintNumber(uint32_t number) {
  char *s;
  uint8_t i, j;

  //convert UL to ascii
  s = ultoa(number);
  //output byte by byte
  j = strlen(s);
  for (i=0; i<j; i++) {
    //insert decimal point
    if (i == (j - 3))
      SerialPutChar(46);
    SerialPutChar(s[i]);
  }
}

//out line feed & carriage return
void LineFeedCarriageReturn(void) {
  SerialPutChar(10);	//NL
  SerialPutChar(13);	//CR
}

//init various stuff
void InitStuff(void) {
  //lap variables
  laps_completed = 0;
  lap = 0;
  first_lap = 1;
  //init ir detector pulse counter 
  start_edge = 0;
  pulse_us = 0;
  tokens = 0;
}

//extract a bunch of stuff from eeprom
void InitEepromData(void) {
  uint16_t i;

  //fetch previous number of laps stored in dataflash
  i = eeprom_read_word((uint16_t *)LAP_ADDRESS);
  if (i > 0 && i <= MAX_LAPS) {                 //validate value
    laps_completed = i;                         //these values init'd previously 
	lap = i;
	first_lap = i + 1;
    //load dataflash page-sized buffer prior to 1st write, otherwise buffer contains
    //garbage in previous lap locations and df writes garbage into memory
    DFTransferPage(laps_completed - 1);
  } 
}

//dump portions of dataflash memory
void DumpMemory(uint16_t end) {
  uint16_t i;
 
  for (i=0; i<=end; i++) {
    uint32_t temp;

    temp = DFReadPage(i);
    if (temp != 0xFFFFFFFF) {          //0xffffffff==empty memory
      //number of laps 
      SerialPutChar(48 + (uint8_t)(i/100U));
      SerialPutChar(48 + (uint8_t)((i - ((i/100U)*100U))/10U)); 
      SerialPutChar(48 + (uint8_t)(i%10));
      SerialPutChar(32);               //space
      PrintNumber(temp);               //lap time
      if (i == (laps_completed - 1)) { //mark last completed lap
        SerialPutChar(32);             //space
        SerialPutChar(60);             //'<'
      }
      LineFeedCarriageReturn();
      delay_ms(10);                    //delay to slow dataflash fetch
    }
  }
}

//very basic user menu
void Menu(void) {
  //uint16_t i;
  char c;
	
  while (1) {
    SerialPutChar('?');			//anybody out there?
    LineFeedCarriageReturn();
    c = SerialGetChar();		//get user input
    SerialPutChar(c);			//echo
    LineFeedCarriageReturn();	//start on fresh line

    //erase memory
	if (c == 'e') {
	  DFChipErase();			//switched to chip erase to save program space
	  eeprom_write_word((uint16_t *)LAP_ADDRESS, 0);
      //re-init various stuff
      InitStuff();
    }

	//download just completed laps
    if (c == 'd')
      if (laps_completed > 0)
	    DumpMemory(laps_completed - 1);

	//download all memory
    if (c == 'a')
	  DumpMemory(MAX_LAPS);

  }  //while
}

//
//main loop
//

int main (void) {
  char a;

  cli();					//hold all interrupts

  //init output ports
  //PA6=TX/MOSI, PA3=SCK
  DDRA = (1<<PA3) | (1<<PA6);
  //PB0=status LED, PB1=SI(local output), PB2=CS
  DDRB =  (1<<PB0) | (1<<PB1) | (1<<PB2);
  PORTB = (1<<PB0) | (1<<PB1) | (1<<PB2); //set high

  //setup timer1 for use by delay functions
  TCCR1B |= (1<<CS11);	//clk/8

  //wait for ready bit on the flash  
  while (!(DFStatus() & 0xA0))
    ;

  //init various stuff
  InitStuff();
  InitEepromData();                      

  //blink LED 
  PORTB &= 0xFE;		//status LED on
  delay_ms(20);			//pause...
  PORTB |= 0x01;		//status LED off

  //check for UART connection
  PORTA &= 0xDF;			//RX pin low to discharge a floating pin
  PORTA |= 0x40;			//TX pin hi to start communication
  DDRA |= 0x20;
  DDRA &= 0xDF;
  //pause
  delay_ms(1);
  a = PINA;
  //if there's a high-ish voltage on the MISO line, we're likely connected to a serial link
  if (a & 0x20) 
    Menu();
	
  //setup timer0 as 1ms counter
  //8Mhz/64 prescale/125 ticks = 8000000/64/125 = 1000us (1ms) per interrupt
  TCCR0A = (1<<WGM01);                  //mode #2 CTC, top=OCR1A, TOV1 set @ max, update immediate
  TCCR0B = (1<<CS01) | (1<<CS00);       //Fcpu/64
  TIMSK0 = (1<<OCIE0A);                 //enable TIM0_COMPA_vect interrupt
  OCR0A = 124;                          //124 results in a 125 count rollover
  //timer0_millis = 0;                  //these are zeroed inside interrupt function
  //TCNT0 = 0;

  //setup pa2 pin-change interrupt for ir detector
  GIMSK |= (1<<PCIE0);                  //enable interrupt
  PCMSK0 |= (1<<PCINT2);                //PA2

  sei();                                //start interrupts

  while (1) {                           //endless loop

    //limit the amount of laps we save
    if (lap > MAX_LAPS)
      cli();                            //stop timer

    if (lap > first_lap) {              //lap=2 @ end of first timed lap, and so on...
      if (laps_completed != (lap - 1)) {//has lap been stored?
        PORTB &= 0xFE;                  //status LED on
        laps_completed = lap - 1;
        //sequential dataflash lap time storage
        DFWritePageThruBuffer((laps_completed - 1), lap_time);
        //total number of laps stored in eeprom
        eeprom_write_word((uint16_t *)LAP_ADDRESS, laps_completed);
        PORTB |= 0x01;                  //status LED off
      }
    }

  }  //while
}

Advertisements

About Jim Eli

µC experimenter
This entry was posted in Uncategorized and tagged , , , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s