Tidtagningsmodul till luftgevärsskytte

Berätta om dina pågående projekt.
gokartnisse
Inlägg: 120
Blev medlem: 3 mars 2011, 01:58:43

Tidtagningsmodul till luftgevärsskytte

Inlägg av gokartnisse »

Efter åratal av inaktivitet och paus i många projekt har jag nyligen genomfört ett lite mindre projekt som precis avslutats. Tänkte jag kunde dela det här och få lite idéer på vidareutveckling eller andra tips inför framtiden.

Modulen är till för att kunna mäta tiden för att skjuta ner fällmål typ skidskytte. 5 st mindre mål ska träffas och när alla har fällts stoppas tiden. I skytteföreningen brukar skyttekvällar avslutas med att tävla om bästa tid samtidigt som Survivors låt "Eye of the tiger" spelas för att öka stressnivån lite. ;)

Fällmålen återställs med att dra i ett snöre, då åker en båge upp och fäller tillbaka målen. Sensorerna (tungelement) monterades på bågen och magneter limmades på varje enskilt litet mål. Så när alla tungelementen leder så kortsluts signalen till jord.

Använde en Arduino nano, en extern oscillator som bättre tidsreferens, en vanlig 16x2 LCD jag hade liggandes och lite fina knappar från farnell. Knapparnas inbyggda LED signalerar att modulen förväntar sig att man ska trycka på den specifika knappen, de två knappar som är märkta "Skytt X" är parallellkopplade med fällmålens sensorer.

"Företagsloggan" Sten hård vara med fyrkantvågen med ringningar är en ordlek då mitt smeknamn är Sten. Märker mina projekt med den för skoj skull och företaget finns inte (ännu i alla fall).

Oscillatorn har tyvärr inte riktigt tillräckligt hög noggrannhet för det som behövs (endast 100 ppm) för en upplösning på hundradelar (maxtiden är 10 minuter, 9:59.99). Helt enkelt för att skärmen inte har plats för fler tecken och mer behövs oftast inte. Trodde i min enfald att det fanns 5V, 4 MHz oscillatorer med ca 10-20 ppm i samma kapsel ( dip-8 ), men inte hittat nån...

Har använt PIC tidigare men tänkte det skulle bli lätt att bara slänga ihop något med en Arduino, det kom tillbaka och bet mig rätt ordentligt. I Arduino nano är tydligen inte BOR-spänningen anpassad för 5V matning utan 3,3V. Det betydde att ibland när man slog till strömmen så hamnade spänningen (under kort tid) under gränsen för definierat beteende hos processorn men inte under BOR-spänningen så allt hängde sig bara. Efter mycket om och men lyckades jag programmera om bootloadern och då skriva om Fuse-bitsen till en BOR-spänning på 4,3V.

Hårdvaran (och mjukvaran till viss del) är förberedd för att kunna mata ut tiden till en stor 7-seg LED display. En där varje siffra får ett 8-bit seriell till parallell shiftregister typ 74HC595. Därav att SPI hårdvaran i AVR:en skickar ut en del data, displayen är dock inte byggd ännu.

Lådan gjordes i PLA med en 3d-printer, frontpanelen gjordes i svart plexi med en laserskärare. Panelens text och logga "graverades" med låg effekt på lasern samtidigt som den skars ut med hög effekt. Sen m.h.a. vit akrylfärg och en pappersservett geggades det "graverade" igen med färgen så att det blev vitt och tydligt. Resultat blev inte perfekt med det blev definitivt tillräckligt snyggt.

Anledningen till att jag använde D-Sub 9 kontakter var att det fanns tillgängligt gratis för mig. Alla kablar internt i lådan är kontakterade med stiftlister så att det är möjligt att serva enkelt när något går sönder.

Kod: Markera allt

// Tidtagare för "eye of the tiger" hos Chalmers skytteförening
// 2 fällmål
// Skrivet av: Sven "Sten" Åkersten


// PORTx set output value here if conf as output
// DDRx configure input/output, 0 = input, 1 = output
// PINx port input register, read here if conf as input

#include <avr/io.h>
#include <avr/interrupt.h>

#include <LiquidCrystal.h>

#include <avr/wdt.h>
 
#define STATE_INIT 0b00000000
#define STATE_CLEAR 0b00000111
#define STATE_WAIT_TO_START 0b00000001
#define STATE_RUNNING_BOTH 0b00000110
#define STATE_RUNNING_PL1 0b00000010
#define STATE_RUNNING_PL2 0b00000100

#define MAIN_BUTTON (0b00000001)
#define PLAYER_1_BUTTON (0b00000010)
#define PLAYER_2_BUTTON (0b00000100)
#define MAIN_LED (0b00001000)
#define PLAYER_1_LED (0b00010000)
#define PLAYER_2_LED (0b00100000)
#define BUTTON_PORT PINC
#define LED_PORT PORTC

#define MAXIMUM_TIME (300000)

#define DEBOUNCE_TIME 150 //testat till 150

#define EXT_OSC 0b00100000
#define SHIFT_REGISTER DDRB
#define LATCH_REGISTER DDRD
#define SHIFT_PORT PORTB
#define LATCH_PORT PORTD
#define DATA (1<<PB3)           //MOSI (SI)
#define LATCH (1<<PD4)          //
#define CLOCK (1<<PB5)          //SCK  (SCK)


LiquidCrystal lcd(2,3,7,8,9,10);

volatile unsigned long int timer_tick_vola = 0;  //timer value from interrupt
volatile unsigned long int last_tick_1_vola = 0; //time at last player 1 trigged from interrupt
volatile unsigned long int last_tick_2_vola = 0; //time at last player 2 trigged from interrupt
volatile unsigned short int holdoff = MAIN_BUTTON; // holdoff for buttons for interrupt

unsigned short int system_state = STATE_INIT;    // current system state
unsigned short int debounce_timer_holdoff = 0b00000000; // holdoff for statemachine, timer or internal state
unsigned long int actual_tick_1 = MAXIMUM_TIME;  //last player 1 time, non volatile 
unsigned long int actual_tick_2 = MAXIMUM_TIME;  //last player 2 time, non volatile 
unsigned long int time_to_display_1 = MAXIMUM_TIME;  //time to display player 1 time, non volatile 
unsigned long int time_to_display_2 = MAXIMUM_TIME;  //time to display player 2, non volatile 
unsigned long int start_tick_1 = 0;   //start time for player 1, non volatile 
unsigned long int start_tick_2 = 0;   //start time for player 2, non volatile
unsigned long int timer_tick = 0;     //timer value copy to use in state machine, non volatile
unsigned long int last_tick_1 = 0;    //last time player 1 copy to use in state machine, non volatile
unsigned long int last_tick_2 = 0;    //last time player 2 copy to use in state machine, non volatile

void setup()
{

    delay(1000);
    
    // initialize pinsz
    DDRC = ~(PLAYER_1_BUTTON | PLAYER_2_BUTTON | MAIN_BUTTON); // inputs
    DDRC |= (PLAYER_1_LED | PLAYER_2_LED | MAIN_LED); // output
    PORTC = (PLAYER_1_BUTTON | PLAYER_2_BUTTON | MAIN_BUTTON); // all inputs with internal pullup

    DDRB = 0b11111111; // all output, 

    //initialize SPI module and pins
    SHIFT_REGISTER |= (DATA | CLOCK);     //Set control pins as outputs
    LATCH_REGISTER = (LATCH | 0b11111111) & ~EXT_OSC ;
    
    SHIFT_PORT &= ~(DATA | CLOCK);                //Set control pins low
    SPCR = (1<<SPE) | (1<<MSTR);  //Start SPI as Master

    //Pull LATCH low (Important: this is necessary to start the SPI transfer!)
    LATCH_PORT &= ~LATCH;

    // initialize Timer1
    cli();          // disable global interrupts
    TCCR1A = 0;     // set entire TCCR1A register to 0
    DDRD = 0b11011111;  // T1 as input
    TCCR1B = 0b00001111;     // turn on CTC mode and set external clock
    //TCCR1B = 0b00001001;     // turn on CTC mode and set prescaler as 1
    // set compare match register to desired timer count:
    OCR1A = 7999; // calculated to 7999 for 2 ms
    //OCR1A = 31999; // calculated to 31999 for 2 ms

    // enable timer compare interrupt:
    TIMSK1 |= (1 << OCIE1A);
    sei();          // enable global interrupts

    wdt_enable(WDTO_4S);

    lcd.begin(16,2);
    lcd.print("Skytt 1  Skytt 2");
    
    // Start serial com.
    Serial.begin(9600);

}

void loop() {
  // statemachine that runs over and over again
  wdt_reset();
  
  if (system_state == STATE_INIT) // inital state
  {
      //print_time_serial(1,actual_tick_1,actual_tick_2);  // display last time
      time_to_display_1 = actual_tick_1;
      time_to_display_2 = actual_tick_2;

      LED_PORT |= (PLAYER_1_LED | PLAYER_2_LED);  // turn on pla 1 and 2 led, to wait for them to be pressed
      LED_PORT &= ~MAIN_LED;  // turn off main button led
      
      if(holdoff & PLAYER_1_BUTTON)  // if player 1 is pressed
      {
          debounce_timer_holdoff |= PLAYER_1_BUTTON;
          // clear pla 1 led
          LED_PORT &= ~PLAYER_1_LED;
      }
      if(holdoff & PLAYER_2_BUTTON) // if player 2 is pressed
      {
          debounce_timer_holdoff |= PLAYER_2_BUTTON;
          // clear pla 1 led
          LED_PORT &= ~PLAYER_2_LED;
      }
      if( (debounce_timer_holdoff & PLAYER_1_BUTTON) && (debounce_timer_holdoff & PLAYER_2_BUTTON) ) // if both player have been pressed, go to next state
      {
         debounce_timer_holdoff &= ~(PLAYER_1_BUTTON | PLAYER_2_BUTTON);
         system_state = STATE_CLEAR;
      }    
  }
  
  if(system_state == STATE_CLEAR)
  {
      //print_time_serial(2,actual_tick_1,actual_tick_2);  // display last time
      time_to_display_1 = actual_tick_1;
      time_to_display_2 = actual_tick_2;

      holdoff &= ~(PLAYER_1_BUTTON | PLAYER_2_BUTTON | MAIN_BUTTON); // clear all buttons
      delay(50);  // wait to see if any button is depressed so it will set it's corresponding holdoff
      system_state = STATE_WAIT_TO_START;   //change state
      debounce_timer_holdoff |= MAIN_BUTTON;   //set main button state machine holdoff
  }
  
  if (system_state == STATE_WAIT_TO_START)
  {   
      if( !( holdoff & (PLAYER_1_BUTTON | PLAYER_2_BUTTON) )  )  // if any player button has been pressed since cleared in last state, return to that state. Makes sure player buttons is low (targets reset)
      {       
          if(holdoff & MAIN_BUTTON & ~debounce_timer_holdoff)  // if statemachine holdoff is cleared and main button been pressed, start time and change state. If main button is clear, clear main button holdoff to enable start else go back to clearing state
          {
              LED_PORT &= ~MAIN_LED; //clear main button led
              
              cli();
              start_tick_1 = last_tick_1_vola;
              start_tick_2 = last_tick_2_vola;
              sei();
              
              system_state = STATE_RUNNING_BOTH;
          }
          else if (~holdoff & MAIN_BUTTON)
          {
               time_to_display_1 = 0;
               time_to_display_2 = 0;
               //print_time_serial(3,0,0);   // print zeros, system ready to start
               LED_PORT |= MAIN_LED;  //turn on main button led        
               debounce_timer_holdoff &= ~MAIN_BUTTON;            
          }
          else
          {
               //print_time_serial(3,actual_tick_1,actual_tick_2);  // display last measured time again, system NOT ready to start
               time_to_display_1 = actual_tick_1;
               time_to_display_2 = actual_tick_2;
               LED_PORT &= ~MAIN_LED; //clear main button led
               system_state = STATE_CLEAR;
          }
      }
      else
      {
           LED_PORT &= ~MAIN_LED; //clear main button led
           system_state = STATE_CLEAR;
      }  
  } 
 
  cli();
  timer_tick = timer_tick_vola; // copy current timer tick from interrupt
  sei();

  if(system_state == STATE_RUNNING_PL1 || system_state == STATE_RUNNING_BOTH ) // if player time is running, recalculate time and display.
  {

      LED_PORT &= ~MAIN_LED;  //clear main button led, turn on pl 1
      LED_PORT |= PLAYER_1_LED;

      if( (actual_tick_1 = (timer_tick - start_tick_1)) < DEBOUNCE_TIME) // if time since start less then minimum time, set mimimum time
      {
          actual_tick_1 = 0;
      }    
      else // if time since start greater then minimum time
      {    
           time_to_display_1 = actual_tick_1 - DEBOUNCE_TIME;
          //print_time_serial(41,actual_tick_1,actual_tick_2); //display time pla 1
      }
      
      if(holdoff & PLAYER_1_BUTTON) // if player 1 holdoff
      {
          cli();
          last_tick_1 = last_tick_1_vola;
          sei();
          
          if( timer_tick - last_tick_1 > DEBOUNCE_TIME ) // Wait until debounce time have passed
          {
              if( ~BUTTON_PORT & PLAYER_1_BUTTON) // if button is still pressed, Calculate finish time and clear running state of that player, when both player been clear state return to init
              {
                  LED_PORT &= ~PLAYER_1_LED; //clear pla 1 led
                  debounce_timer_holdoff |=  MAIN_BUTTON;
                  time_to_display_1 = actual_tick_1 = last_tick_1 - start_tick_1;
                  system_state &= ~STATE_RUNNING_PL1;
              }
              else // else it was a button bounce, clear holdoff and do nothing
              {
                  holdoff &= ~PLAYER_1_BUTTON;
              }
          }   
      }
      else if(actual_tick_1 > MAXIMUM_TIME) // if time since start greater then maximum time and button not been pressed, terminate
      {   
          LED_PORT &= ~PLAYER_1_LED; //clear pla 1 led
          debounce_timer_holdoff |= MAIN_BUTTON;
          time_to_display_1 = actual_tick_1 = MAXIMUM_TIME;
          system_state &= ~STATE_RUNNING_PL1;
      }    
  }

  if(system_state == STATE_RUNNING_PL2 || system_state == STATE_RUNNING_BOTH ) // if player time is running, recalculate time and display.
  {

      LED_PORT &= ~MAIN_LED;  //clear main button led, turn on pl 2
      LED_PORT |= PLAYER_2_LED;

      if( (actual_tick_2 = (timer_tick - start_tick_2)) < DEBOUNCE_TIME) // if time since start less then minimum time, set mimimum time
      {
          time_to_display_2 = 0;
      }    
      else // if time since start greater then minimum time
      {    
          //print_time_serial(42,actual_tick_1,actual_tick_2); //display time pl 2
          time_to_display_2 = actual_tick_2 - DEBOUNCE_TIME;
      } 
 
      if(holdoff & PLAYER_2_BUTTON) // if player 2 holdoff
      {
          cli();
          last_tick_2 = last_tick_2_vola;
          sei();
          
          if( timer_tick - last_tick_2 > DEBOUNCE_TIME ) // Wait until debounce time have passed
          {
              if( ~BUTTON_PORT & PLAYER_2_BUTTON) // if button is still pressed, Calculate finish time and clear running state of that player, when both player been clear state return to init
              {
                  LED_PORT &= ~PLAYER_2_LED; //clear pla 2 led
                  debounce_timer_holdoff |=  MAIN_BUTTON;
                  time_to_display_2 = actual_tick_2 = last_tick_2 - start_tick_2;
                  system_state &= ~STATE_RUNNING_PL2;
              }
              else // else it was a button bounce, clear holdoff and do nothing
              {
                  holdoff &= ~PLAYER_2_BUTTON;
              }
          }   
      }
      else if(actual_tick_2 > MAXIMUM_TIME) // if time since start greater then maximum time and button not been pressed, terminate
      {   
          LED_PORT &= ~PLAYER_2_LED; //clear pla 2 led
          debounce_timer_holdoff |= MAIN_BUTTON;
          time_to_display_2 = actual_tick_2 = MAXIMUM_TIME;
          system_state &= ~STATE_RUNNING_PL2;
      }    
  }

  print_time_lcd(time_to_display_1,time_to_display_2);
  print_time_serial(1,time_to_display_1,time_to_display_2);

}


void output_byte_spi(unsigned short data)
{
    //Shift in some data
    SPDR = data;
    //Wait for SPI process to finish
    while(!(SPSR & (1<<SPIF))) ;
    
    //Latch into output latch
    LATCH_PORT |= LATCH;
    delayMicroseconds(1);
    LATCH_PORT &= ~LATCH;
    delayMicroseconds(1);
}

void send_message_spi(char* data,unsigned short message_length)
{
    for(int i = 0; i < message_length ; i++) 
    {   
        //Shift in some data
        SPDR = data[i];
        //Wait for SPI process to finish
        while(!(SPSR & (1<<SPIF))) ;
    }
    
    //Latch into output latch
    LATCH_PORT |= LATCH;
    delayMicroseconds(1);
    LATCH_PORT &= ~LATCH;
    delayMicroseconds(1);

}

void print_time_lcd(unsigned long time1, unsigned long time2)
{
    unsigned int t1_mm = time1/30000;
    unsigned int t2_mm = time2/30000;;
    unsigned int t1_ss = (time1/500)%60;
    unsigned int t2_ss = (time2/500)%60;
    unsigned short int t1_cc = (time1/5)%100;
    unsigned short int t2_cc = (time2/5)%100; 
    char lcd_char_time[16] ={'-',':','-','-','.','-','-',' ',' ','-',':','-','-','.','-','-'};

    if(time1 != MAXIMUM_TIME)
    {
       lcd_char_time[3] = t1_ss%10 + 48;
       lcd_char_time[2] = (t1_ss/10)%10 + 48;
       lcd_char_time[0] = t1_mm + 48;

       lcd_char_time[6] = t1_cc%10 + 48;
       lcd_char_time[5] = (t1_cc/10)%10 + 48;
    }
    if(time2 != MAXIMUM_TIME)
    {
       lcd_char_time[12] = t2_ss%10 + 48;
       lcd_char_time[11] = (t2_ss/10)%10 + 48;
       lcd_char_time[9] = t2_mm + 48;

       lcd_char_time[15] = t2_cc%10 + 48;
       lcd_char_time[14] = (t2_cc/10)%10 + 48;
    }
    lcd.setCursor(0, 1);

    for(int i = 0; i < 16 ; i++)
        lcd.print(lcd_char_time[i]); 

    send_message_spi(lcd_char_time,16);

}


void print_time_serial(unsigned short state, unsigned long time1, unsigned long time2) //debug printing of current state, volatile holdoff and the last measured time for each player
{
    unsigned int t1_ss = time1/500;
    unsigned int t2_ss = time2/500;
    unsigned short int t1_cc = (time1/5)%100;
    unsigned short int t2_cc = (time2/5)%100; 
    
    // calculate time as seconds and hundreds of seconds before sending on serial port
    
    Serial.print("State: ");
    Serial.print(state);
    Serial.print(" Holdoff: ");
    Serial.print(holdoff);
    Serial.print(" Time 1: ");
    Serial.print(t1_ss);
    Serial.print(".");
    if(t1_cc < 10)
    {
        Serial.print("0");
    }
    Serial.print(t1_cc);
    Serial.print(" Time 1: ");
    Serial.print(t2_ss);
    Serial.print(".");
    if(t2_cc < 10)
    {
        Serial.print("0");
    }
    Serial.println(t2_cc);    
}
    

ISR(TIMER1_COMPA_vect) // interrupt runs once for every timer tick. If button holdoff is clear and button is pressed, store current time in volatile variable of corresponding player
{   
    unsigned short int new_button_state;
    
    timer_tick_vola++;  //increment main timer, one tick (2 ms) have passed
    
    new_button_state = (~BUTTON_PORT & ~holdoff) & (MAIN_BUTTON | PLAYER_1_BUTTON | PLAYER_2_BUTTON);  // if a button is pressed and holdoff is clear set high button state, buttons is active low

    if(MAIN_BUTTON & new_button_state) // store both player time if start (main) button is pressed. The start time ie.
    {
        holdoff |= MAIN_BUTTON;
        last_tick_1_vola = timer_tick_vola;
        last_tick_2_vola = timer_tick_vola;        
    }

    if(PLAYER_1_BUTTON & new_button_state)  // store player time if player 1 button is pressed. The finish time for that player ie.
    {
        holdoff |= PLAYER_1_BUTTON;
        last_tick_1_vola = timer_tick_vola;         
    }

    if(PLAYER_2_BUTTON & new_button_state) // store player time if player 2 button is pressed. The finish time for that player ie.
    {
        holdoff |= PLAYER_2_BUTTON;
        last_tick_2_vola = timer_tick_vola;         
    }

}
Du har inte behörighet att öppna de filer som bifogats till detta inlägg.
Användarvisningsbild
Lennart Aspenryd
Tidigare Lasp
Inlägg: 12607
Blev medlem: 1 juli 2011, 19:09:09
Ort: Helsingborg

Re: Tidtagningsmodul till luftgevärsskytte

Inlägg av Lennart Aspenryd »

Snyggt, enkelt och bra beskrivet! Tack.
gokartnisse
Inlägg: 120
Blev medlem: 3 mars 2011, 01:58:43

Re: Tidtagningsmodul till luftgevärsskytte

Inlägg av gokartnisse »

En liten men stor uppdatering! Efter en generös donation av Icecap så har jag nu 20 fungerade stora LED displayer som jag ska använda för att presentera tiden på under skjutning.
Displayerna är 8*8 pixlar (20 cm * 20 cm), 3 gula leds per pixel (tänk skylt på en buss). De styrs genom att man shiftar in data i shiftregister och sen latchar ut till drivstegen.
Varje display har 2 dataingångar, en klocka, latch/strobe för att klocka in till drivstegen och en output enable som man kan PWM styra för ljusstyrka.

Varje dataingång har varsin halva (delat horisontellt) och man klockar in 32 bitar (en för varje pixel) i ett lite klurigt mönster. Det är där jag är just nu för att lösa mjukvaran.
För att hårdvaran ska vara enkel tänkte jag bara använda en enda dataingång och seriekoppla hela undre och övre halvan på den sammansatta displayen, då kan jag använda hårdvaran jag har och SPI hårdvaran i Arduinon som klockar ut en byte i taget. Kruxet blir att tillräckligt snabbt få ut hela displayen och räkna fram vilka bytes som ska klockas ut, ska uppdatera displayens tid med hundradelsupplösning live... Så hundradelssiffran måste "flimmra".

Mönstret man klockar in en en halva 4*8 (4 rader 8 kolumner) pixlar är uppbyggt som att man börjar i nedre högra hörnet. Sen klockar man in 4 bitar (halva bredden) för de 4 första. Sen börjar en ny rad på 4 pixlar vilket upprepas 3 gånger till, så man har byggt den nedre högra kvadraten på 4*4 pixlar.

Man kan helt enkelt se det som att varje byte man skiftar in delas upp i två nibbles som läggs ovanpå varandra. Som i sin tur bygger upp kvadrater på 4*4 pixlar. Två såna kvadrater i bredd (eller 4 byte) per data in.

Men hur gör jag mjukvaran så effektiv som möjligt? Jag måste ju dela upp varje tecken (vill även kunna skriva text) i massa nibbles som jag sen måste hantera och skifta hit och dit. Någon som har några smidiga idéer? Vilken "font" ska man göra? 5*7 pixlar? 4*6 fast dubbelt så stor?

Tanken är att ha ca 10-12 displayer (varje med 8*8 pixlar) i en lång rad och skriva på.
Användarvisningsbild
Icecap
Inlägg: 26106
Blev medlem: 10 januari 2005, 14:52:15
Ort: Aabenraa, Danmark

Re: Tidtagningsmodul till luftgevärsskytte

Inlägg av Icecap »

JAG gör så att jag reserverar ett minnesarea som jag ritar i.
Den första rutin jag gör är en Set_Pixel(x, y) rutin som sedan sätter rätt bit i minnet så att utläsningen är rak och enkel.
Med de moduler blir det alltså 8*8*20 bits = 160 bytes.

Jag använder en 5×7(8) teckenmatris och i er system skulle jag rita ut nästa tiden och vänta med stroben till rätt tid.

Jag har även en rutin för att få data på hur lång en sträng är i pixlar (kom ihåg 1 tom pixel mellan tecken) då jag oftast använder olika teckenbredd (1, komma, punkt osv. är smala). I er fall vill jag dock rekommendera att hålla talen på fast bredd (5 pixlar) då det annars blir fladdrigt.

Med Set_pixel(x, y) kan man sedan scanna av teckentabellen och en '1' betyder att pixeln ska sättas, en '0' betyder att inget ska göras.

Allt detta görs i en "skriv ut text"-rutin och i mitt fall använder jag en flagga som anger huruvida texten ska skrives proportionellt eller inte (alltså med varierande teckenbredd eller fast). Då kan jag antingen centrera, högerställa eller vänsterställa texter.

När den nya tiden är latchad ut kan man rita den nya tiden och skicka datan och vänta med stroben osv.
Ska man skriva över de data som väntar är det ju bara att skriva dom utan att stroba innan.
gokartnisse
Inlägg: 120
Blev medlem: 3 mars 2011, 01:58:43

Re: Tidtagningsmodul till luftgevärsskytte

Inlägg av gokartnisse »

Efter några timmars kodande har jag nu kommit en liten bit påvägen. Kan nu skriva ut en siffra per 8*8 display.

För att ens ha en chans att ha koll på hur jag ska få det att fungera har jag reserverat minne som representerar hur det ser ut på skärmen. Sen låter jag SPI enhetens interrupt hämta de två nibbles som skrivas ut. Lite mindre effektivt men det var hopplöst att hålla i huvudet annars.

Det som ska göras härnäst är att skapa datan som man vill ska ut på displayen, funderar egentligen hur man ska göra för att kunna skifta in delar av tecken på ett bra sätt. Varje panels "minnesskugga" består ju av 8 st bytes, en för varje rad. De ligger i sin tur i en array så det blir en 2 dimensionell sådan. Lite klurigt verkar det bli att kapa tecknen på ett smidigt sätt... Funderar på bektrakta de som 16 bitars tal som man skiftar och sen plockar ut varje byte som egna delar. Bra ide?
Användarvisningsbild
Icecap
Inlägg: 26106
Blev medlem: 10 januari 2005, 14:52:15
Ort: Aabenraa, Danmark

Re: Tidtagningsmodul till luftgevärsskytte

Inlägg av Icecap »

Hur du än gör måste du ha rutiner som fyller i data i "bildminnet".
Mitt tips är att du gör det enkelt för dig:
* Reserver ett "bildminne" på 160 bytes (i detta fall).
* Skapa en rutin som kör ut dessa data vid att sända byte för byte.
* Skapa en rutin som setter en given pixel (x, y) på displayen. Jag har ofta gjort en 2-dimensionell uppslagstabell då de moduler ni har upprepar sig var 4*4 pixlar.

Med dessa rutiner kan du rakt av räkna med att om du kallar Set_Pixel (x, y) blir den visad rätt på displayen oavsett var den finns i bildminnet.

Tecken kan sedan läggas in i bildminnet vid en for (x) for (y) där teckentabellens bits testas.

Mellan varje tecken stegar man 1 tom pixelkolumn.

Jag anser att det sätt du än så länge har vald är mycket besvärlig och krånglig.
gokartnisse
Inlägg: 120
Blev medlem: 3 mars 2011, 01:58:43

Re: Tidtagningsmodul till luftgevärsskytte

Inlägg av gokartnisse »

Det jag har är ju redan i princip:
* Reserver ett "bildminne" på 160 bytes (i detta fall).
* Skapa en rutin som kör ut dessa data vid att sända byte för byte.
Skillnaden är att jag låter SPI-interruptet sköta utskriften och sen låter jag SPI-hårdvaran sköta utklockningen.

Det som jag vill göra nu är ju, fast på ett effektivare sätt:
* Skapa en rutin som setter en given pixel (x, y) på displayen. Jag har ofta gjort en 2-dimensionell uppslagstabell då de moduler ni har upprepar sig var 4*4 pixlar.
Hur du än gör måste du ha rutiner som fyller i data i "bildminnet".
Ja, precis. Frågan är hur jag kan göra det effektivt då det innebär en herrans massa bearbetning om jag inte är försiktig. Att hantera det pixel för pixel när datan går att få som bytes förstår jag inte poängen med mer än att det är lite enklare att koda. Det kommer bli otroligt ineffektivt med branch instruktioner och grejer för varje pixel.
Användarvisningsbild
Icecap
Inlägg: 26106
Blev medlem: 10 januari 2005, 14:52:15
Ort: Aabenraa, Danmark

Re: Tidtagningsmodul till luftgevärsskytte

Inlägg av Icecap »

Att skicka ut 160 bytes 2 bit åt gången med bit-bang tar inte lång tid, jag är mycket tveksam på att det går snabbare med en ISR som ska servas då själva overheaden för en interrupt ska räknas med.

Jag har testat lite olika sätt att sätta upp bildminnet och skicka det och vad jag beskrev tidigare gav bäst resultat.

Samtidig kan jag placera text var jag vill utan att ha det på fasta positioner.
gokartnisse
Inlägg: 120
Blev medlem: 3 mars 2011, 01:58:43

Re: Tidtagningsmodul till luftgevärsskytte

Inlägg av gokartnisse »

Efter mer kodande och lite trixande med felsökning har jag nu gjort grundläggande utskriftsrutiner. De kan skriva ut tecken var som helst på och ha en varierande bredd, tecken lagras i en tvådimensionell array som även lagrar teckenbredden i antal pixlar. För att kunna dela upp tecknen över flera 8*8 paneler så begränsas varje tecken till 8 pixlar brett. Gjorde min pekarmagi mycket enklare. För större "tecken" eller bilder måste man helt enkelt lagra flera man skriver ut bara.

Nästa steg blir att montera ihop en större display (experimenterat med 2 panel brett, 8*16 pixlar) och avlusa eventuella fel i koden jag inte hittat. Samt snygga upp den så jag kan lägga upp den här.

Eftersom hårvaran (det kortet jag etsat) inte har fler tillgängliga pinnar från Arduinon är det inte aktuellt att bitbanga 2 bitar i taget. Just nu har jag dragit ut för SPI-hårdvarans CLK, DATA och en pinne som latch/strobe. Då ser jag ingen anledning att inte låta hårdvaran klocka ut en byte istället för att bitbanga. Jag måste ju ändå p.g.a. mitt kretskort seriekoppla övre och undre halvan av displayen (endast en DATA tillgänglig).

Däremot är det möjligt att ISR overheaden käka upp allt positivt med att ha den och lite till (Interrupt 1 gång per byte). Ska när displayen är klar och stor jämföra för skoj skull och se hur mycket fortare det går att göra det i en rutin istället. Skillnaden i koden är ju nästintill ingen. Ska bara se till att koda den så att den förbereder nästa byte medans hårdvaran klockar ut en byte.
Användarvisningsbild
Icecap
Inlägg: 26106
Blev medlem: 10 januari 2005, 14:52:15
Ort: Aabenraa, Danmark

Re: Tidtagningsmodul till luftgevärsskytte

Inlägg av Icecap »

Ja, finns det bara 1 databit är det ju inte mycket att välja på.
I övrigt kan man shifta in data med upp till 20MHz.

Jag har gjort en teckentabell i 2 dimensioner med en fast bredd om 5 pixlar. Varje tecken innehåller sedan information om mönstret men även bredd och offset så att samma tabell kan användas till fast bredd och proportionell bredd.

Det blir spännande att se slutresultatet av ditt arbete.
gokartnisse
Inlägg: 120
Blev medlem: 3 mars 2011, 01:58:43

Re: Tidtagningsmodul till luftgevärsskytte

Inlägg av gokartnisse »

Har fått hela displayen att skriva ut tid nu. Dock blir det bara 10 paneler och inte 12, var enklare och det behövs inte mer eftersom jag har variabel teckenbredd ( ":" och "," blir "smala").
Testade för skoj skull att mäta hur lång tid det tog att uppdatera (och beräkna hela displayens bitar) med att använda interrupt och inte för att styra SPI hårdvaran. Tog ca 740 us med interrupt och 530 us utan. Tyckte skillnaden var lite för stor för att vara rimlig (bara interrupt-overheaden som är skillnaden i praktiken). Sen insåg jag hur galet jag mätt. Så får testa om...

Dessutom är varianten utan SPI-interrupt sämre kodad (långsammare) än den behöver vara just nu. Var så irriterad på ett löjligt fel att jag glömda att lägga själva beräkningen som hämtar nästa byte som ska klockas ut medans hårdvaran klockar ut nuvarande. Just nu står processorn bara och väntar medans hårdvaran klockar ut data.

Ful och ännu inte uppsnyggad kod:

Kod: Markera allt

// Tidtagare för "eye of the tiger" hos Chalmers skytteförening
// 2 fällmål
// Skrivet av: Sven "Sten" Åkersten


// PORTx set output value here if conf as output
// DDRx configure input/output, 0 = input, 1 = output
// PINx port input register, read here if conf as input

#include <avr/io.h>
#include <avr/interrupt.h>

#include <LiquidCrystal.h>

#include <avr/wdt.h>
 
#define STATE_INIT 0b00000000
#define STATE_CLEAR 0b00000111
#define STATE_WAIT_TO_START 0b00000001
#define STATE_RUNNING_BOTH 0b00000110
#define STATE_RUNNING_PL1 0b00000010
#define STATE_RUNNING_PL2 0b00000100

#define MAIN_BUTTON (0b00000001)
#define PLAYER_1_BUTTON (0b00000010)
#define PLAYER_2_BUTTON (0b00000100)
#define MAIN_LED (0b00001000)
#define PLAYER_1_LED (0b00010000)
#define PLAYER_2_LED (0b00100000)
#define BUTTON_PORT PINC
#define LED_PORT PORTC

#define MAXIMUM_TIME (300000)

#define DEBOUNCE_TIME 150 //testat till 150

#define EXT_OSC 0b00100000
#define SHIFT_REGISTER DDRB
#define LATCH_REGISTER DDRD
#define SHIFT_PORT PORTB
#define LATCH_PORT PORTD
#define DATA (1<<PB3)           //MOSI (SI)
#define LATCH (1<<PD4)          //
#define CLOCK (1<<PB5)          //SCK  (SCK)

#define DISPLAY_SIZE 80
#define CHARACTER_WIDTH 6
#define LINE_CHAR_WIDTH 6
#define COLON_CHAR_WIDTH 3
#define DOT_CHAR_WIDTH 3
#define LINE_CHAR 10
#define COLON_CHAR 11
#define DOT_CHAR 12


LiquidCrystal lcd(2,3,7,8,9,10);

volatile unsigned long int timer_tick_vola = 0;  //timer value from interrupt
volatile unsigned long int last_tick_1_vola = 0; //time at last player 1 trigged from interrupt
volatile unsigned long int last_tick_2_vola = 0; //time at last player 2 trigged from interrupt
volatile unsigned short int holdoff = MAIN_BUTTON; // holdoff for buttons for interrupt

unsigned short int system_state = STATE_INIT;    // current system state
unsigned short int debounce_timer_holdoff = 0b00000000; // holdoff for statemachine, timer or internal state
unsigned long int actual_tick_1 = MAXIMUM_TIME;  //last player 1 time, non volatile 
unsigned long int actual_tick_2 = MAXIMUM_TIME;  //last player 2 time, non volatile 
unsigned long int time_to_display_1 = MAXIMUM_TIME;  //time to display player 1 time, non volatile 
unsigned long int time_to_display_2 = MAXIMUM_TIME;  //time to display player 2, non volatile 
unsigned long int start_tick_1 = 0;   //start time for player 1, non volatile 
unsigned long int start_tick_2 = 0;   //start time for player 2, non volatile
unsigned long int timer_tick = 0;     //timer value copy to use in state machine, non volatile
unsigned long int last_tick_1 = 0;    //last time player 1 copy to use in state machine, non volatile
unsigned long int last_tick_2 = 0;    //last time player 2 copy to use in state machine, non volatile

struct time_characters
{
  unsigned short int t1_mm;
  unsigned short int t1_ss_tenth;
  unsigned short int t1_ss_unit;
  unsigned short int t1_cc_tenth;
  unsigned short int t1_cc_unit;
      
  unsigned short int t2_mm;
  unsigned short int t2_ss_tenth;
  unsigned short int t2_ss_unit;
  unsigned short int t2_cc_tenth;
  unsigned short int t2_cc_unit;
} ;

const char charTable[13][9] ={{0x70,0x88,0x88,0x88,0x88,0x88,0x70,0x00,CHARACTER_WIDTH},  // Pixelmap for characters on LED display 
                              {0x20,0x60,0x20,0x20,0x20,0x20,0x70,0x00,CHARACTER_WIDTH},
                              {0x70,0x88,0x08,0x10,0x20,0x40,0xf8,0x00,CHARACTER_WIDTH},
                              {0x70,0x88,0x08,0x10,0x08,0x88,0x70,0x00,CHARACTER_WIDTH},
                              {0x10,0x30,0x50,0x90,0xf8,0x10,0x38,0x00,CHARACTER_WIDTH},
                              {0xf8,0x80,0xf0,0x08,0x08,0x88,0x70,0x00,CHARACTER_WIDTH},
                              {0x30,0x40,0x80,0xf0,0x88,0x88,0x70,0x00,CHARACTER_WIDTH},
                              {0xf8,0x08,0x10,0x20,0x40,0x40,0x40,0x00,CHARACTER_WIDTH},
                              {0x70,0x88,0x88,0x70,0x88,0x88,0x70,0x00,CHARACTER_WIDTH},
                              {0x70,0x88,0x88,0x78,0x08,0x10,0x60,0x00,CHARACTER_WIDTH},
                              {0x00,0x00,0x00,0xf8,0x00,0x00,0x00,0x00,LINE_CHAR_WIDTH},
                              {0x00,0xc0,0xc0,0x00,0xc0,0xc0,0x00,0x00,COLON_CHAR_WIDTH},
                              {0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0x00,DOT_CHAR_WIDTH}};

char display_shadow[DISPLAY_SIZE/8+1]∞; //LED display shadow

void setup()
{

    delay(100);
    
    // initialize pinsz
    DDRC = ~(PLAYER_1_BUTTON | PLAYER_2_BUTTON | MAIN_BUTTON); // inputs
    DDRC |= (PLAYER_1_LED | PLAYER_2_LED | MAIN_LED); // output
    PORTC = (PLAYER_1_BUTTON | PLAYER_2_BUTTON | MAIN_BUTTON); // all inputs with internal pullup

    DDRB = 0b11111111; // all output, 

    //initialize SPI module and pins
    SHIFT_REGISTER |= (DATA | CLOCK);     //Set control pins as outputs
    LATCH_REGISTER = (LATCH | 0b11111111) & ~EXT_OSC ;
    
    SHIFT_PORT &= ~(DATA | CLOCK);                //Set control pins low
    SPCR = (1<<SPE) | (1<<MSTR);  //Start SPI as Master
    //SPSR =  (1<<SPI2X);

    //Pull LATCH low (Important: this is necessary to start the SPI transfer!)
    LATCH_PORT &= ~LATCH;

    // initialize Timer1
    cli();          // disable global interrupts
    TCCR1A = 0;     // set entire TCCR1A register to 0
    DDRD = 0b11011111;  // T1 as input
    TCCR1B = 0b00001111;     // turn on CTC mode and set external clock
    //TCCR1B = 0b00001001;     // turn on CTC mode and set prescaler as 1
    // set compare match register to desired timer count:
    OCR1A = 7999; // calculated to 7999 for 2 ms
    //OCR1A = 31999; // calculated to 31999 for 2 ms

    // enable timer compare interrupt:
    TIMSK1 |= (1 << OCIE1A);
    sei();          // enable global interrupts

    wdt_enable(WDTO_4S);

    lcd.begin(16,2);
    lcd.print("Skytt 1  Skytt 2");
    
    // Start serial com.
    Serial.begin(9600);

}

void loop() {
  // statemachine that runs over and over again
  wdt_reset();
  
  if (system_state == STATE_INIT) // inital state
  {
      //print_time_serial(1,actual_tick_1,actual_tick_2);  // display last time
      time_to_display_1 = actual_tick_1;
      time_to_display_2 = actual_tick_2;

      LED_PORT |= (PLAYER_1_LED | PLAYER_2_LED);  // turn on pla 1 and 2 led, to wait for them to be pressed
      LED_PORT &= ~MAIN_LED;  // turn off main button led
      
      if(holdoff & PLAYER_1_BUTTON)  // if player 1 is pressed
      {
          debounce_timer_holdoff |= PLAYER_1_BUTTON;
          // clear pla 1 led
          LED_PORT &= ~PLAYER_1_LED;
      }
      if(holdoff & PLAYER_2_BUTTON) // if player 2 is pressed
      {
          debounce_timer_holdoff |= PLAYER_2_BUTTON;
          // clear pla 1 led
          LED_PORT &= ~PLAYER_2_LED;
      }
      if( (debounce_timer_holdoff & PLAYER_1_BUTTON) && (debounce_timer_holdoff & PLAYER_2_BUTTON) ) // if both player have been pressed, go to next state
      {
         debounce_timer_holdoff &= ~(PLAYER_1_BUTTON | PLAYER_2_BUTTON);
         system_state = STATE_CLEAR;
      }    
  }
  
  if(system_state == STATE_CLEAR)
  {
      //print_time_serial(2,actual_tick_1,actual_tick_2);  // display last time
      time_to_display_1 = actual_tick_1;
      time_to_display_2 = actual_tick_2;

      holdoff &= ~(PLAYER_1_BUTTON | PLAYER_2_BUTTON | MAIN_BUTTON); // clear all buttons
      delay(50);  // wait to see if any button is depressed so it will set it's corresponding holdoff
      system_state = STATE_WAIT_TO_START;   //change state
      debounce_timer_holdoff |= MAIN_BUTTON;   //set main button state machine holdoff
  }
  
  if (system_state == STATE_WAIT_TO_START)
  {   
      if( !( holdoff & (PLAYER_1_BUTTON | PLAYER_2_BUTTON) )  )  // if any player button has been pressed since cleared in last state, return to that state. Makes sure player buttons is low (targets reset)
      {       
          if(holdoff & MAIN_BUTTON & ~debounce_timer_holdoff)  // if statemachine holdoff is cleared and main button been pressed, start time and change state. If main button is clear, clear main button holdoff to enable start else go back to clearing state
          {
              LED_PORT &= ~MAIN_LED; //clear main button led
              
              cli();
              start_tick_1 = last_tick_1_vola;
              start_tick_2 = last_tick_2_vola;
              sei();
              
              system_state = STATE_RUNNING_BOTH;
          }
          else if (~holdoff & MAIN_BUTTON)
          {
               time_to_display_1 = 0;
               time_to_display_2 = 0;
               //print_time_serial(3,0,0);   // print zeros, system ready to start
               LED_PORT |= MAIN_LED;  //turn on main button led        
               debounce_timer_holdoff &= ~MAIN_BUTTON;            
          }
          else
          {
               //print_time_serial(3,actual_tick_1,actual_tick_2);  // display last measured time again, system NOT ready to start
               time_to_display_1 = actual_tick_1;
               time_to_display_2 = actual_tick_2;
               LED_PORT &= ~MAIN_LED; //clear main button led
               system_state = STATE_CLEAR;
          }
      }
      else
      {
           LED_PORT &= ~MAIN_LED; //clear main button led
           system_state = STATE_CLEAR;
      }  
  } 
 
  cli();
  timer_tick = timer_tick_vola; // copy current timer tick from interrupt
  sei();

  if(system_state == STATE_RUNNING_PL1 || system_state == STATE_RUNNING_BOTH ) // if player time is running, recalculate time and display.
  {

      LED_PORT &= ~MAIN_LED;  //clear main button led, turn on pl 1
      LED_PORT |= PLAYER_1_LED;

      if( (actual_tick_1 = (timer_tick - start_tick_1)) < DEBOUNCE_TIME) // if time since start less then minimum time, set mimimum time
      {
          actual_tick_1 = 0;
      }    
      else // if time since start greater then minimum time
      {    
           time_to_display_1 = actual_tick_1 - DEBOUNCE_TIME;
          //print_time_serial(41,actual_tick_1,actual_tick_2); //display time pla 1
      }
      
      if(holdoff & PLAYER_1_BUTTON) // if player 1 holdoff
      {
          cli();
          last_tick_1 = last_tick_1_vola;
          sei();
          
          if( timer_tick - last_tick_1 > DEBOUNCE_TIME ) // Wait until debounce time have passed
          {
              if( ~BUTTON_PORT & PLAYER_1_BUTTON) // if button is still pressed, Calculate finish time and clear running state of that player, when both player been clear state return to init
              {
                  LED_PORT &= ~PLAYER_1_LED; //clear pla 1 led
                  debounce_timer_holdoff |=  MAIN_BUTTON;
                  time_to_display_1 = actual_tick_1 = last_tick_1 - start_tick_1;
                  system_state &= ~STATE_RUNNING_PL1;
              }
              else // else it was a button bounce, clear holdoff and do nothing
              {
                  holdoff &= ~PLAYER_1_BUTTON;
              }
          }   
      }
      else if(actual_tick_1 > MAXIMUM_TIME) // if time since start greater then maximum time and button not been pressed, terminate
      {   
          LED_PORT &= ~PLAYER_1_LED; //clear pla 1 led
          debounce_timer_holdoff |= MAIN_BUTTON;
          time_to_display_1 = actual_tick_1 = MAXIMUM_TIME;
          system_state &= ~STATE_RUNNING_PL1;
      }    
  }

  if(system_state == STATE_RUNNING_PL2 || system_state == STATE_RUNNING_BOTH ) // if player time is running, recalculate time and display.
  {

      LED_PORT &= ~MAIN_LED;  //clear main button led, turn on pl 2
      LED_PORT |= PLAYER_2_LED;

      if( (actual_tick_2 = (timer_tick - start_tick_2)) < DEBOUNCE_TIME) // if time since start less then minimum time, set mimimum time
      {
          time_to_display_2 = 0;
      }    
      else // if time since start greater then minimum time
      {    
          //print_time_serial(42,actual_tick_1,actual_tick_2); //display time pl 2
          time_to_display_2 = actual_tick_2 - DEBOUNCE_TIME;
      } 
 
      if(holdoff & PLAYER_2_BUTTON) // if player 2 holdoff
      {
          cli();
          last_tick_2 = last_tick_2_vola;
          sei();
          
          if( timer_tick - last_tick_2 > DEBOUNCE_TIME ) // Wait until debounce time have passed
          {
              if( ~BUTTON_PORT & PLAYER_2_BUTTON) // if button is still pressed, Calculate finish time and clear running state of that player, when both player been clear state return to init
              {
                  LED_PORT &= ~PLAYER_2_LED; //clear pla 2 led
                  debounce_timer_holdoff |=  MAIN_BUTTON;
                  time_to_display_2 = actual_tick_2 = last_tick_2 - start_tick_2;
                  system_state &= ~STATE_RUNNING_PL2;
              }
              else // else it was a button bounce, clear holdoff and do nothing
              {
                  holdoff &= ~PLAYER_2_BUTTON;
              }
          }   
      }
      else if(actual_tick_2 > MAXIMUM_TIME) // if time since start greater then maximum time and button not been pressed, terminate
      {   
          LED_PORT &= ~PLAYER_2_LED; //clear pla 2 led
          debounce_timer_holdoff |= MAIN_BUTTON;
          time_to_display_2 = actual_tick_2 = MAXIMUM_TIME;
          system_state &= ~STATE_RUNNING_PL2;
      }    
  }

  print_time(time_to_display_1,time_to_display_2);
  //print_time_serial(1,time_to_display_1,time_to_display_2);

}


void output_byte_spi(unsigned short data)
{
    //Shift in some data
    SPDR = data;
    //Wait for SPI process to finish
    while(!(SPSR & (1<<SPIF))) ;
    
    //Latch into output latch
    LATCH_PORT |= LATCH;
    delayMicroseconds(1);
    LATCH_PORT &= ~LATCH;
    delayMicroseconds(1);
}

void send_message_spi(char* data,unsigned short message_length)
{
    for(int i = 0; i < message_length ; i++) 
    {   
        //Shift in some data
        SPDR = data[i];
        //Wait for SPI process to finish
        while(!(SPSR & (1<<SPIF))) ;
    }
    
    //Latch into output latch
    LATCH_PORT |= LATCH;
    delayMicroseconds(1);
    LATCH_PORT &= ~LATCH;
    delayMicroseconds(1);

}

void print_time(unsigned long time1, unsigned long time2)
{
    unsigned int t1_mm = time1/30000;
    unsigned int t2_mm = time2/30000;;
    unsigned int t1_ss = (time1/500)%60;
    unsigned int t2_ss = (time2/500)%60;
    unsigned short int t1_cc = (time1/5)%100;
    unsigned short int t2_cc = (time2/5)%100; 
    char lcd_char_time[16] ={'-',':','-','-','.','-','-',' ',' ','-',':','-','-','.','-','-'};
    
    time_characters led_char_time = {LINE_CHAR,LINE_CHAR,LINE_CHAR,LINE_CHAR,LINE_CHAR,LINE_CHAR,LINE_CHAR,LINE_CHAR,LINE_CHAR,LINE_CHAR};

    if(time1 != MAXIMUM_TIME)
    {
      
      lcd_char_time[3] = (led_char_time.t1_ss_unit = t1_ss%10) + 48;
      lcd_char_time[2] = (led_char_time.t1_ss_tenth = (t1_ss/10)%10) + 48;
      lcd_char_time[0] = (led_char_time.t1_mm = t1_mm) + 48;
      lcd_char_time[6] = (led_char_time.t1_cc_unit = t1_cc%10) + 48;
      lcd_char_time[5] = (led_char_time.t1_cc_tenth = (t1_cc/10)%10) + 48;
    }
    if(time2 != MAXIMUM_TIME)
    {
      lcd_char_time[12] = (led_char_time.t2_ss_unit = t2_ss%10) + 48;
      lcd_char_time[11] = (led_char_time.t2_ss_tenth = (t2_ss/10)%10) + 48;
      lcd_char_time[9] = (led_char_time.t2_mm = t2_mm) + 48;
      lcd_char_time[15] = (led_char_time.t2_cc_unit = t2_cc%10) + 48;
      lcd_char_time[14] = (led_char_time.t2_cc_tenth = (t2_cc/10)%10) + 48;
    }
    lcd.setCursor(0, 1);

    for(unsigned short int i = 0; i < 16 ; i++)
        lcd.print(lcd_char_time[i]); 

   unsigned short int col = 0;
      
   clear_all_display_shadow();   //Clear displayshodow memory

   //Fungerar denna på full storlek?+?
   col +=2; // two blank pixel coloumns     
   col = write_character_to_display_shadow(charTable[led_char_time.t1_mm],col);
   col = write_character_to_display_shadow(charTable[COLON_CHAR],col);
   col = write_character_to_display_shadow(charTable[led_char_time.t1_ss_tenth],col);
   col = write_character_to_display_shadow(charTable[led_char_time.t1_ss_unit],col);
   col = write_character_to_display_shadow(charTable[DOT_CHAR],col);
   col = write_character_to_display_shadow(charTable[led_char_time.t1_cc_tenth],col);
   col = write_character_to_display_shadow(charTable[led_char_time.t1_cc_unit],col);

   col += 5; // pixel columns in between the two times
   col = write_character_to_display_shadow(charTable[led_char_time.t2_mm],col);
   col = write_character_to_display_shadow(charTable[COLON_CHAR],col);
   col = write_character_to_display_shadow(charTable[led_char_time.t2_ss_tenth],col);
   col = write_character_to_display_shadow(charTable[led_char_time.t2_ss_unit],col);
   col = write_character_to_display_shadow(charTable[DOT_CHAR],col);
   col = write_character_to_display_shadow(charTable[led_char_time.t2_cc_tenth],col);
   col = write_character_to_display_shadow(charTable[led_char_time.t2_cc_unit],col);

   update_display();  // send display shadow to LED screen
     
   //LATCH_PORT &= ~LATCH;

   delay(10);
   //   SPCR |= (1<<SPIE); // SPI interrupt enable
     
}


unsigned short int write_character_to_display_shadow(char* data, unsigned short col)
{
  unsigned int tmp;
  unsigned short int i;
  //uint16_t tmp[8];
  for(i = 0; i < 8 ; i++) // for all rows, do pointer magic so it the character will be split between two bytes.  
  {
    *((char *)&tmp+1) = data[i];
    *((char *)&tmp) = 0;
    tmp = tmp>>col%8;

    display_shadow[col/8][i] |= *(((char *)&tmp)+1);
    display_shadow[(col/8)+1][i] = *((char *)&tmp);  
  }
  return col+data[i]; // add the character width
}

void clear_rest_display_shadow(unsigned short col)
{
  for(unsigned short panel = col/8 + 1 ; panel < DISPLAY_SIZE/8 ; panel++)
  {
    for(unsigned short i = 0; i < 8 ; i++)   
    {
      display_shadow[panel][i] = 0;  
    }
  }
}

void clear_all_display_shadow(void)
{
  for(unsigned short panel = 0 ; panel < DISPLAY_SIZE/8 ; panel++)
  {
    for(unsigned short i = 0; i < 8 ; i++)   
    {
      display_shadow[panel][i] = 0;  
    }
  }
}

void update_display(void) // fixa så att själva beräkningen för nästa utklockning sker medans hårdavaran sköter nuvarande. D.v.s. lägg till en en bytes temp buffer...
{ 
    SPDR = (display_shadow[0][0] & 0xF0) | (0x0F & display_shadow[0][1]>>4);
    
    for(unsigned short int i = 1; i < DISPLAY_SIZE ; i++) 
    {   
        
        while(!(SPSR & (1<<SPIF))) ; //Wait for SPI process to finish

        unsigned short int row = (i&1)<<1;
        unsigned short int panel = i>>2;
        if(i >= (DISPLAY_SIZE/2))
        {
          row = row+4;
          panel -= (DISPLAY_SIZE/8);
        }
    
        if(i & 0b00000010)
          SPDR = (display_shadow[panel][row+1] & 0x0F) | (0xF0 & display_shadow[panel][row]<<4);
        else
          SPDR = (display_shadow[panel][row] & 0xF0) | (0x0F & display_shadow[panel][row+1]>>4);        
    }

  while(!(SPSR & (1<<SPIF))) ; //Wait for SPI process to finish
  LATCH_PORT |= LATCH; //Latch into output latch
  delayMicroseconds(1);
  LATCH_PORT &= ~LATCH;
}


void clear_first_panel_display_shadow(void)
{
  for(unsigned short i = 0; i < 8 ; i++)   
  {
    display_shadow[0][i] = 0;  
  }
}


void print_time_serial(unsigned short state, unsigned long time1, unsigned long time2) //debug printing of current state, volatile holdoff and the last measured time for each player
{
    unsigned int t1_ss = time1/500;
    unsigned int t2_ss = time2/500;
    unsigned short int t1_cc = (time1/5)%100;
    unsigned short int t2_cc = (time2/5)%100; 
    // calculate time as seconds and hundreds of seconds before sending on serial port
    Serial.print("State: ");
    Serial.print(state);
    Serial.print(" Holdoff: ");
    Serial.print(holdoff);
    Serial.print(" Time 1: ");
    Serial.print(t1_ss);
    Serial.print(".");
    if(t1_cc < 10)
    {
        Serial.print("0");
    }
    Serial.print(t1_cc);
    Serial.print(" Time 1: ");
    Serial.print(t2_ss);
    Serial.print(".");
    if(t2_cc < 10)
    {
        Serial.print("0");
    }
    Serial.println(t2_cc); 
}
    

ISR(TIMER1_COMPA_vect) // interrupt runs once for every timer tick. If button holdoff is clear and button is pressed, store current time in volatile variable of corresponding player
{   
    unsigned short int new_button_state;
    
    timer_tick_vola++;  //increment main timer, one tick (2 ms) have passed
    
    new_button_state = (~BUTTON_PORT & ~holdoff) & (MAIN_BUTTON | PLAYER_1_BUTTON | PLAYER_2_BUTTON);  // if a button is pressed and holdoff is clear set high button state, buttons is active low

    if(MAIN_BUTTON & new_button_state) // store both player time if start (main) button is pressed. The start time ie.
    {
        holdoff |= MAIN_BUTTON;
        last_tick_1_vola = timer_tick_vola;
        last_tick_2_vola = timer_tick_vola;        
    }

    if(PLAYER_1_BUTTON & new_button_state)  // store player time if player 1 button is pressed. The finish time for that player ie.
    {
        holdoff |= PLAYER_1_BUTTON;
        last_tick_1_vola = timer_tick_vola;         
    }

    if(PLAYER_2_BUTTON & new_button_state) // store player time if player 2 button is pressed. The finish time for that player ie.
    {
        holdoff |= PLAYER_2_BUTTON;
        last_tick_2_vola = timer_tick_vola;         
    }
}
Du har inte behörighet att öppna de filer som bifogats till detta inlägg.
Användarvisningsbild
adent
Inlägg: 4094
Blev medlem: 27 november 2008, 22:56:23
Ort: Utanför Jönköping
Kontakt:

Re: Tidtagningsmodul till luftgevärsskytte

Inlägg av adent »

Snyggt!!
Användarvisningsbild
Icecap
Inlägg: 26106
Blev medlem: 10 januari 2005, 14:52:15
Ort: Aabenraa, Danmark

Re: Tidtagningsmodul till luftgevärsskytte

Inlägg av Icecap »

Ser bra ut.

Kul att se att det går framåt.
gokartnisse
Inlägg: 120
Blev medlem: 3 mars 2011, 01:58:43

Re: Tidtagningsmodul till luftgevärsskytte

Inlägg av gokartnisse »

I helgen har projektet gått framåt en bit. Displayen är nu monterad till en enhet med hjälp av två långa plywood-remsor. Ännu finns inga väggfästen dock.

Strömkablar är fixade och anpassade men jag har inte löst strömförsörjningsfrågan ännu. Moddade ett datornätagg och drog ut bara 12V lina som kunde leverera 12A. När jag drog mellan 3-4 A och körde displayen på det så hörde jag ett högt vinande ljud, lät som om switchdelen ballade ur. Sen dog datornätagget, antagligen termiskt skydd som löste ut... Aja, gillade kanske inte att man drog så mycket ström från 12V men inget från nån av de andra linorna. Skulle behöva nåt är billigt och kan leverera ordentligt med ström. I praktiken drar den ca 4A när man visar tid, men tänder man alla pixlarna så drar displayen nästan 15A.

Mjukvaran är uppdaterad med en lite snyggare utskrift och sen har jag snabbat upp utklockningen av data en del. Jag sänkte SPI-enhetens klockhastighet till hälften utan att påverka den totala tiden för utklockning nämnvärt. Helt enkelt att eftersom hårdvaran sköter utklockningen så gör jag en del av beräkningen av nästa byte som ska ut.
Nu tar det ca 400 us, att klocka ut all data (i jämförelse med över 700 us för samma uppgift med SPI-interrupt). Det tar ca 5 ms att köra ett helt "varv", d.v.s. kolla om någon spelare stannat, omräkning av aktuell tid samt beräkning av displayer och utskrift på båda displayerna.

Koden i dess nuvarande form:

Kod: Markera allt

 
// Tidtagare för "eye of the tiger" hos Chalmers skytteförening
// 2 fällmål
// Skrivet av: Sven "Sten" Åkersten


// PORTx set output value here if conf as output
// DDRx configure input/output, 0 = input, 1 = output
// PINx port input register, read here if conf as input

#include <avr/io.h>
#include <avr/interrupt.h>

#include <LiquidCrystal.h>

#include <avr/wdt.h>
 
#define STATE_INIT 0b00000000
#define STATE_CLEAR 0b00000111
#define STATE_WAIT_TO_START 0b00000001
#define STATE_RUNNING_BOTH 0b00000110
#define STATE_RUNNING_PL1 0b00000010
#define STATE_RUNNING_PL2 0b00000100

#define MAIN_BUTTON (0b00000001)
#define PLAYER_1_BUTTON (0b00000010)
#define PLAYER_2_BUTTON (0b00000100)
#define MAIN_LED (0b00001000)
#define PLAYER_1_LED (0b00010000)
#define PLAYER_2_LED (0b00100000)
#define BUTTON_PORT PINC
#define LED_PORT PORTC

#define MAXIMUM_TIME (300000)

#define DEBOUNCE_TIME 150 //testat till 150

#define EXT_OSC 0b00100000
#define SHIFT_REGISTER DDRB
#define LATCH_REGISTER DDRD
#define SHIFT_PORT PORTB
#define LATCH_PORT PORTD
#define DATA (1<<PB3)           //MOSI (SI)
#define LATCH (1<<PD4)          //
#define CLOCK (1<<PB5)          //SCK  (SCK)

#define DISPLAY_SIZE 80
#define CHARACTER_WIDTH 6
#define LINE_CHAR_WIDTH 6
#define COLON_CHAR_WIDTH 2
#define DOT_CHAR_WIDTH 2
#define LINE_CHAR 10
#define COLON_CHAR 11
#define DOT_CHAR 12


LiquidCrystal lcd(2,3,7,8,9,10);

volatile unsigned long int timer_tick_vola = 0;  //timer value from interrupt
volatile unsigned long int last_tick_1_vola = 0; //time at last player 1 trigged from interrupt
volatile unsigned long int last_tick_2_vola = 0; //time at last player 2 trigged from interrupt
volatile unsigned short int holdoff = MAIN_BUTTON; // holdoff for buttons for interrupt

unsigned short int system_state = STATE_INIT;    // current system state
unsigned short int debounce_timer_holdoff = 0b00000000; // holdoff for statemachine, timer or internal state
unsigned long int actual_tick_1 = MAXIMUM_TIME;  //last player 1 time, non volatile 
unsigned long int actual_tick_2 = MAXIMUM_TIME;  //last player 2 time, non volatile 
unsigned long int time_to_display_1 = MAXIMUM_TIME;  //time to display player 1 time, non volatile 
unsigned long int time_to_display_2 = MAXIMUM_TIME;  //time to display player 2, non volatile 
unsigned long int start_tick_1 = 0;   //start time for player 1, non volatile 
unsigned long int start_tick_2 = 0;   //start time for player 2, non volatile
unsigned long int timer_tick = 0;     //timer value copy to use in state machine, non volatile
unsigned long int last_tick_1 = 0;    //last time player 1 copy to use in state machine, non volatile
unsigned long int last_tick_2 = 0;    //last time player 2 copy to use in state machine, non volatile

struct time_characters
{
  unsigned short int t1_mm;
  unsigned short int t1_ss_tenth;
  unsigned short int t1_ss_unit;
  unsigned short int t1_cc_tenth;
  unsigned short int t1_cc_unit;
      
  unsigned short int t2_mm;
  unsigned short int t2_ss_tenth;
  unsigned short int t2_ss_unit;
  unsigned short int t2_cc_tenth;
  unsigned short int t2_cc_unit;
} ;

const char charTable[13][9] ={{0x70,0x88,0x88,0x88,0x88,0x88,0x70,0x00,CHARACTER_WIDTH},  // Pixelmap for characters on LED display 
                              {0x20,0x60,0x20,0x20,0x20,0x20,0x70,0x00,CHARACTER_WIDTH},
                              {0x70,0x88,0x08,0x10,0x20,0x40,0xf8,0x00,CHARACTER_WIDTH},
                              {0x70,0x88,0x08,0x10,0x08,0x88,0x70,0x00,CHARACTER_WIDTH},
                              {0x10,0x30,0x50,0x90,0xf8,0x10,0x38,0x00,CHARACTER_WIDTH},
                              {0xf8,0x80,0xf0,0x08,0x08,0x88,0x70,0x00,CHARACTER_WIDTH},
                              {0x30,0x40,0x80,0xf0,0x88,0x88,0x70,0x00,CHARACTER_WIDTH},
                              {0xf8,0x08,0x10,0x20,0x40,0x40,0x40,0x00,CHARACTER_WIDTH},
                              {0x70,0x88,0x88,0x70,0x88,0x88,0x70,0x00,CHARACTER_WIDTH},
                              {0x70,0x88,0x88,0x78,0x08,0x10,0x60,0x00,CHARACTER_WIDTH},
                              {0x00,0x00,0x00,0xf8,0x00,0x00,0x00,0x00,LINE_CHAR_WIDTH},
                              {0x00,0x80,0x80,0x00,0x80,0x80,0x00,0x00,COLON_CHAR_WIDTH},
                              {0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,DOT_CHAR_WIDTH}};

char display_shadow[DISPLAY_SIZE/8+1]∞; //LED display shadow

void setup()
{

    delay(100);
    
    // initialize pinsz
    DDRC = ~(PLAYER_1_BUTTON | PLAYER_2_BUTTON | MAIN_BUTTON); // inputs
    DDRC |= (PLAYER_1_LED | PLAYER_2_LED | MAIN_LED); // output
    PORTC = (PLAYER_1_BUTTON | PLAYER_2_BUTTON | MAIN_BUTTON); // all inputs with internal pullup

    DDRB = 0b11111111; // all output, 

    //initialize SPI module and pins
    SHIFT_REGISTER |= (DATA | CLOCK);     //Set control pins as outputs
    LATCH_REGISTER = (LATCH | 0b11111111) & ~EXT_OSC ;
    
    SHIFT_PORT &= ~(DATA | CLOCK);                //Set control pins low
    SPCR = (1<<SPE) | (1<<MSTR)| (1<<SPR0);  //Start SPI as Master
    SPSR =  (1<<SPI2X);

    //Pull LATCH low (Important: this is necessary to start the SPI transfer!)
    LATCH_PORT &= ~LATCH;

    // initialize Timer1
    cli();          // disable global interrupts
    TCCR1A = 0;     // set entire TCCR1A register to 0
    DDRD = 0b11011111;  // T1 as input
    TCCR1B = 0b00001111;     // turn on CTC mode and set external clock
    //TCCR1B = 0b00001001;     // turn on CTC mode and set prescaler as 1
    // set compare match register to desired timer count:
    OCR1A = 7999; // calculated to 7999 for 2 ms
    //OCR1A = 31999; // calculated to 31999 for 2 ms

    // enable timer compare interrupt:
    TIMSK1 |= (1 << OCIE1A);
    sei();          // enable global interrupts

    wdt_enable(WDTO_4S);

    lcd.begin(16,2);
    lcd.print("Skytt 1  Skytt 2");
    
    // Start serial com.
    Serial.begin(9600);

}

void loop() {
  // statemachine that runs over and over again
  wdt_reset();
  
  if (system_state == STATE_INIT) // inital state
  {
      //print_time_serial(1,actual_tick_1,actual_tick_2);  // display last time
      time_to_display_1 = actual_tick_1;
      time_to_display_2 = actual_tick_2;

      LED_PORT |= (PLAYER_1_LED | PLAYER_2_LED);  // turn on pla 1 and 2 led, to wait for them to be pressed
      LED_PORT &= ~MAIN_LED;  // turn off main button led
      
      if(holdoff & PLAYER_1_BUTTON)  // if player 1 is pressed
      {
          debounce_timer_holdoff |= PLAYER_1_BUTTON;
          // clear pla 1 led
          LED_PORT &= ~PLAYER_1_LED;
      }
      if(holdoff & PLAYER_2_BUTTON) // if player 2 is pressed
      {
          debounce_timer_holdoff |= PLAYER_2_BUTTON;
          // clear pla 1 led
          LED_PORT &= ~PLAYER_2_LED;
      }
      if( (debounce_timer_holdoff & PLAYER_1_BUTTON) && (debounce_timer_holdoff & PLAYER_2_BUTTON) ) // if both player have been pressed, go to next state
      {
         debounce_timer_holdoff &= ~(PLAYER_1_BUTTON | PLAYER_2_BUTTON);
         system_state = STATE_CLEAR;
      }    
  }
  
  if(system_state == STATE_CLEAR)
  {
      //print_time_serial(2,actual_tick_1,actual_tick_2);  // display last time
      time_to_display_1 = actual_tick_1;
      time_to_display_2 = actual_tick_2;

      holdoff &= ~(PLAYER_1_BUTTON | PLAYER_2_BUTTON | MAIN_BUTTON); // clear all buttons
      delay(50);  // wait to see if any button is depressed so it will set it's corresponding holdoff
      system_state = STATE_WAIT_TO_START;   //change state
      debounce_timer_holdoff |= MAIN_BUTTON;   //set main button state machine holdoff
  }
  
  if (system_state == STATE_WAIT_TO_START)
  {   
      if( !( holdoff & (PLAYER_1_BUTTON | PLAYER_2_BUTTON) )  )  // if any player button has been pressed since cleared in last state, return to that state. Makes sure player buttons is low (targets reset)
      {       
          if(holdoff & MAIN_BUTTON & ~debounce_timer_holdoff)  // if statemachine holdoff is cleared and main button been pressed, start time and change state. If main button is clear, clear main button holdoff to enable start else go back to clearing state
          {
              LED_PORT &= ~MAIN_LED; //clear main button led
              
              cli();
              start_tick_1 = last_tick_1_vola;
              start_tick_2 = last_tick_2_vola;
              sei();
              
              system_state = STATE_RUNNING_BOTH;
          }
          else if (~holdoff & MAIN_BUTTON)
          {
               time_to_display_1 = 0;
               time_to_display_2 = 0;
               //print_time_serial(3,0,0);   // print zeros, system ready to start
               LED_PORT |= MAIN_LED;  //turn on main button led        
               debounce_timer_holdoff &= ~MAIN_BUTTON;            
          }
          else
          {
               //print_time_serial(3,actual_tick_1,actual_tick_2);  // display last measured time again, system NOT ready to start
               time_to_display_1 = actual_tick_1;
               time_to_display_2 = actual_tick_2;
               LED_PORT &= ~MAIN_LED; //clear main button led
               system_state = STATE_CLEAR;
          }
      }
      else
      {
           LED_PORT &= ~MAIN_LED; //clear main button led
           system_state = STATE_CLEAR;
      }  
  } 
 
  cli();
  timer_tick = timer_tick_vola; // copy current timer tick from interrupt
  sei();

  if(system_state == STATE_RUNNING_PL1 || system_state == STATE_RUNNING_BOTH ) // if player time is running, recalculate time and display.
  {

      LED_PORT &= ~MAIN_LED;  //clear main button led, turn on pl 1
      LED_PORT |= PLAYER_1_LED;

      if( (actual_tick_1 = (timer_tick - start_tick_1)) < DEBOUNCE_TIME) // if time since start less then minimum time, set mimimum time
      {
          actual_tick_1 = 0;
      }    
      else // if time since start greater then minimum time
      {    
           time_to_display_1 = actual_tick_1 - DEBOUNCE_TIME;
          //print_time_serial(41,actual_tick_1,actual_tick_2); //display time pla 1
      }
      
      if(holdoff & PLAYER_1_BUTTON) // if player 1 holdoff
      {
          cli();
          last_tick_1 = last_tick_1_vola;
          sei();
          
          if( timer_tick - last_tick_1 > DEBOUNCE_TIME ) // Wait until debounce time have passed
          {
              if( ~BUTTON_PORT & PLAYER_1_BUTTON) // if button is still pressed, Calculate finish time and clear running state of that player, when both player been clear state return to init
              {
                  LED_PORT &= ~PLAYER_1_LED; //clear pla 1 led
                  debounce_timer_holdoff |=  MAIN_BUTTON;
                  time_to_display_1 = actual_tick_1 = last_tick_1 - start_tick_1;
                  system_state &= ~STATE_RUNNING_PL1;
              }
              else // else it was a button bounce, clear holdoff and do nothing
              {
                  holdoff &= ~PLAYER_1_BUTTON;
              }
          }   
      }
      else if(actual_tick_1 > MAXIMUM_TIME) // if time since start greater then maximum time and button not been pressed, terminate
      {   
          LED_PORT &= ~PLAYER_1_LED; //clear pla 1 led
          debounce_timer_holdoff |= MAIN_BUTTON;
          time_to_display_1 = actual_tick_1 = MAXIMUM_TIME;
          system_state &= ~STATE_RUNNING_PL1;
      }    
  }

  if(system_state == STATE_RUNNING_PL2 || system_state == STATE_RUNNING_BOTH ) // if player time is running, recalculate time and display.
  {

      LED_PORT &= ~MAIN_LED;  //clear main button led, turn on pl 2
      LED_PORT |= PLAYER_2_LED;

      if( (actual_tick_2 = (timer_tick - start_tick_2)) < DEBOUNCE_TIME) // if time since start less then minimum time, set mimimum time
      {
          time_to_display_2 = 0;
      }    
      else // if time since start greater then minimum time
      {    
          //print_time_serial(42,actual_tick_1,actual_tick_2); //display time pl 2
          time_to_display_2 = actual_tick_2 - DEBOUNCE_TIME;
      } 
 
      if(holdoff & PLAYER_2_BUTTON) // if player 2 holdoff
      {
          cli();
          last_tick_2 = last_tick_2_vola;
          sei();
          
          if( timer_tick - last_tick_2 > DEBOUNCE_TIME ) // Wait until debounce time have passed
          {
              if( ~BUTTON_PORT & PLAYER_2_BUTTON) // if button is still pressed, Calculate finish time and clear running state of that player, when both player been clear state return to init
              {
                  LED_PORT &= ~PLAYER_2_LED; //clear pla 2 led
                  debounce_timer_holdoff |=  MAIN_BUTTON;
                  time_to_display_2 = actual_tick_2 = last_tick_2 - start_tick_2;
                  system_state &= ~STATE_RUNNING_PL2;
              }
              else // else it was a button bounce, clear holdoff and do nothing
              {
                  holdoff &= ~PLAYER_2_BUTTON;
              }
          }   
      }
      else if(actual_tick_2 > MAXIMUM_TIME) // if time since start greater then maximum time and button not been pressed, terminate
      {   
          LED_PORT &= ~PLAYER_2_LED; //clear pla 2 led
          debounce_timer_holdoff |= MAIN_BUTTON;
          time_to_display_2 = actual_tick_2 = MAXIMUM_TIME;
          system_state &= ~STATE_RUNNING_PL2;
      }    
  }

  print_time(time_to_display_1,time_to_display_2);
  //print_time_serial(1,time_to_display_1,time_to_display_2);

}


void output_byte_spi(unsigned short data)
{
    //Shift in some data
    SPDR = data;
    //Wait for SPI process to finish
    while(!(SPSR & (1<<SPIF))) ;
    
    //Latch into output latch
    LATCH_PORT |= LATCH;
    delayMicroseconds(1);
    LATCH_PORT &= ~LATCH;
    delayMicroseconds(1);
}

void send_message_spi(char* data,unsigned short message_length)
{
    for(int i = 0; i < message_length ; i++) 
    {   
        //Shift in some data
        SPDR = data[i];
        //Wait for SPI process to finish
        while(!(SPSR & (1<<SPIF))) ;
    }
    
    //Latch into output latch
    LATCH_PORT |= LATCH;
    delayMicroseconds(1);
    LATCH_PORT &= ~LATCH;
    delayMicroseconds(1);

}

void print_time(unsigned long time1, unsigned long time2)
{
    unsigned int t1_mm = time1/30000;
    unsigned int t2_mm = time2/30000;;
    unsigned int t1_ss = (time1/500)%60;
    unsigned int t2_ss = (time2/500)%60;
    unsigned short int t1_cc = (time1/5)%100;
    unsigned short int t2_cc = (time2/5)%100; 
    char lcd_char_time[16] ={'-',':','-','-','.','-','-',' ',' ','-',':','-','-','.','-','-'};
    
    time_characters led_char_time = {LINE_CHAR,LINE_CHAR,LINE_CHAR,LINE_CHAR,LINE_CHAR,LINE_CHAR,LINE_CHAR,LINE_CHAR,LINE_CHAR,LINE_CHAR};

    if(time1 != MAXIMUM_TIME)
    {
      
      lcd_char_time[3] = (led_char_time.t1_ss_unit = t1_ss%10) + 48;
      lcd_char_time[2] = (led_char_time.t1_ss_tenth = (t1_ss/10)%10) + 48;
      lcd_char_time[0] = (led_char_time.t1_mm = t1_mm) + 48;
      lcd_char_time[6] = (led_char_time.t1_cc_unit = t1_cc%10) + 48;
      lcd_char_time[5] = (led_char_time.t1_cc_tenth = (t1_cc/10)%10) + 48;
    }
    if(time2 != MAXIMUM_TIME)
    {
      lcd_char_time[12] = (led_char_time.t2_ss_unit = t2_ss%10) + 48;
      lcd_char_time[11] = (led_char_time.t2_ss_tenth = (t2_ss/10)%10) + 48;
      lcd_char_time[9] = (led_char_time.t2_mm = t2_mm) + 48;
      lcd_char_time[15] = (led_char_time.t2_cc_unit = t2_cc%10) + 48;
      lcd_char_time[14] = (led_char_time.t2_cc_tenth = (t2_cc/10)%10) + 48;
    }
    lcd.setCursor(0, 1);

    for(unsigned short int i = 0; i < 16 ; i++)
        lcd.print(lcd_char_time[i]); 

   unsigned short int col = 0;
      
   clear_all_display_shadow();   //Clear displayshodow memory

   //Fungerar denna på full storlek?+?
   col +=2; // two blank pixel coloumns     
   col = write_character_to_display_shadow(charTable[led_char_time.t1_mm],col);
   col = write_character_to_display_shadow(charTable[COLON_CHAR],col);
   col = write_character_to_display_shadow(charTable[led_char_time.t1_ss_tenth],col);
   col = write_character_to_display_shadow(charTable[led_char_time.t1_ss_unit],col);
   col = write_character_to_display_shadow(charTable[DOT_CHAR],col);
   col = write_character_to_display_shadow(charTable[led_char_time.t1_cc_tenth],col);
   col = write_character_to_display_shadow(charTable[led_char_time.t1_cc_unit],col);

   col += 9; // pixel columns in between the two times
   col = write_character_to_display_shadow(charTable[led_char_time.t2_mm],col);
   col = write_character_to_display_shadow(charTable[COLON_CHAR],col);
   col = write_character_to_display_shadow(charTable[led_char_time.t2_ss_tenth],col);
   col = write_character_to_display_shadow(charTable[led_char_time.t2_ss_unit],col);
   col = write_character_to_display_shadow(charTable[DOT_CHAR],col);
   col = write_character_to_display_shadow(charTable[led_char_time.t2_cc_tenth],col);
   col = write_character_to_display_shadow(charTable[led_char_time.t2_cc_unit],col);

   update_display();  // send display shadow to LED screen
     
   //LATCH_PORT &= ~LATCH;

   //delay(10);
   //   SPCR |= (1<<SPIE); // SPI interrupt enable
     
}


unsigned short int write_character_to_display_shadow(char* data, unsigned short col)
{
  unsigned int tmp;
  unsigned short int i;
  //uint16_t tmp[8];
  for(i = 0; i < 8 ; i++) // for all rows, do pointer magic so it the character will be split between two bytes.  
  {
    *((char *)&tmp+1) = data[i];
    *((char *)&tmp) = 0;
    tmp = tmp>>col%8;

    display_shadow[col/8][i] |= *(((char *)&tmp)+1);
    display_shadow[(col/8)+1][i] = *((char *)&tmp);  
  }
  return col+data[i]; // add the character width
}

void clear_rest_display_shadow(unsigned short col)
{
  for(unsigned short panel = col/8 + 1 ; panel < DISPLAY_SIZE/8 ; panel++)
  {
    for(unsigned short i = 0; i < 8 ; i++)   
    {
      display_shadow[panel][i] = 0;  
    }
  }
}

void clear_all_display_shadow(void)
{
  for(unsigned short panel = 0 ; panel < DISPLAY_SIZE/8 ; panel++)
  {
    for(unsigned short i = 0; i < 8 ; i++)   
    {
      display_shadow[panel][i] = 0;  
    }
  }
}

void update_display(void) // fixa så att själva beräkningen för nästa utklockning sker medans hårdavaran sköter nuvarande. D.v.s. lägg till en en bytes temp buffer...
{ 
    SPDR = (display_shadow[0][0] & 0xF0) | (0x0F & display_shadow[0][1]>>4);
    
    for(unsigned short int i = 1; i < DISPLAY_SIZE ; i++) 
    {   
        unsigned short int row = (i&1)<<1;
        unsigned short int panel = i>>2;
        char tmp;
        
        if(i >= (DISPLAY_SIZE/2))
        {
          row = row+4;
          panel -= (DISPLAY_SIZE/8);
        }
    
        if(i & 0b00000010)
          tmp = (display_shadow[panel][row+1] & 0x0F) | (0xF0 & display_shadow[panel][row]<<4);
        else
          tmp = (display_shadow[panel][row] & 0xF0) | (0x0F & display_shadow[panel][row+1]>>4);  

        while(!(SPSR & (1<<SPIF))) ; //Wait for SPI process to finish
        
        SPDR = tmp;
    }

  while(!(SPSR & (1<<SPIF))) ; //Wait for SPI process to finish
  LATCH_PORT |= LATCH; //Latch into output latch
  delayMicroseconds(1);
  LATCH_PORT &= ~LATCH;
}


void clear_first_panel_display_shadow(void)
{
  for(unsigned short i = 0; i < 8 ; i++)   
  {
    display_shadow[0][i] = 0;  
  }
}


void print_time_serial(unsigned short state, unsigned long time1, unsigned long time2) //debug printing of current state, volatile holdoff and the last measured time for each player
{
    unsigned int t1_ss = time1/500;
    unsigned int t2_ss = time2/500;
    unsigned short int t1_cc = (time1/5)%100;
    unsigned short int t2_cc = (time2/5)%100; 
    // calculate time as seconds and hundreds of seconds before sending on serial port
    Serial.print("State: ");
    Serial.print(state);
    Serial.print(" Holdoff: ");
    Serial.print(holdoff);
    Serial.print(" Time 1: ");
    Serial.print(t1_ss);
    Serial.print(".");
    if(t1_cc < 10)
    {
        Serial.print("0");
    }
    Serial.print(t1_cc);
    Serial.print(" Time 1: ");
    Serial.print(t2_ss);
    Serial.print(".");
    if(t2_cc < 10)
    {
        Serial.print("0");
    }
    Serial.println(t2_cc); 
}
    

ISR(TIMER1_COMPA_vect) // interrupt runs once for every timer tick. If button holdoff is clear and button is pressed, store current time in volatile variable of corresponding player
{   
    unsigned short int new_button_state;
    
    timer_tick_vola++;  //increment main timer, one tick (2 ms) have passed
    
    new_button_state = (~BUTTON_PORT & ~holdoff) & (MAIN_BUTTON | PLAYER_1_BUTTON | PLAYER_2_BUTTON);  // if a button is pressed and holdoff is clear set high button state, buttons is active low

    if(MAIN_BUTTON & new_button_state) // store both player time if start (main) button is pressed. The start time ie.
    {
        holdoff |= MAIN_BUTTON;
        last_tick_1_vola = timer_tick_vola;
        last_tick_2_vola = timer_tick_vola;        
    }

    if(PLAYER_1_BUTTON & new_button_state)  // store player time if player 1 button is pressed. The finish time for that player ie.
    {
        holdoff |= PLAYER_1_BUTTON;
        last_tick_1_vola = timer_tick_vola;         
    }

    if(PLAYER_2_BUTTON & new_button_state) // store player time if player 2 button is pressed. The finish time for that player ie.
    {
        holdoff |= PLAYER_2_BUTTON;
        last_tick_2_vola = timer_tick_vola;         
    }
}
Du har inte behörighet att öppna de filer som bifogats till detta inlägg.
Användarvisningsbild
GeekJoan
Admin
Inlägg: 10642
Blev medlem: 26 maj 2003, 15:59:27
Ort: Solna

Re: Tidtagningsmodul till luftgevärsskytte

Inlägg av GeekJoan »

:tumupp:
Skriv svar