Commit e3a37a324adcd5db12fe8ee11384f9bd0f9081bb
authorMichel Pollet <buserror@gmail.com>
Mon, 21 Dec 2009 21:08:45 +0000 (21:08 +0000)
committerMichel Pollet <buserror@gmail.com>
Mon, 21 Dec 2009 21:08:45 +0000 (21:08 +0000)
TCNT read/write is now working. It is recalculated at read time.
You can also write to it to reset the timer to a fixed value, this resets
the simavr timer base accordingly.
Note that some timer modes should /not/ let the AVR write to TCNT, this
is not handled right now.

Also added an example of AVR code that uses timers, change TCNT1 and
generates a nice trace file with all the changes.

Signed-off-by: Michel Pollet <buserror@gmail.com>
3 files changed:
simavr/sim/avr_timer.c
simavr/sim/avr_timer.h
tests/atmega88_timer16.c [new file with mode: 0644]

index fc3d3c79f0a1f6753606fcf7aabc2bb8823060ed..4f0ac93b030797ba818f22731bbc9aca6c0cf425 100644 (file)
 #include <stdio.h>
 #include "avr_timer.h"
 
+
+/*
+ * The timers are /always/ 16 bits here, if the higher byte register
+ * is specified it's just added.
+ */
+static uint16_t _timer_get_ocra(avr_timer_t * p)
+{
+       return p->io.avr->data[p->r_ocra] |
+                               (p->r_ocrah ? (p->io.avr->data[p->r_ocrah] << 8) : 0);
+}
+static uint16_t _timer_get_ocrb(avr_timer_t * p)
+{
+       return p->io.avr->data[p->r_ocrb] |
+                               (p->r_ocrbh ? (p->io.avr->data[p->r_ocrbh] << 8) : 0);
+}
+static uint16_t _timer_get_tcnt(avr_timer_t * p)
+{
+       return p->io.avr->data[p->r_tcnt] |
+                               (p->r_tcnth ? (p->io.avr->data[p->r_tcnth] << 8) : 0);
+}
+
 static avr_cycle_count_t avr_timer_compa(struct avr_t * avr, avr_cycle_count_t when, void * param)
 {
        avr_timer_t * p = (avr_timer_t *)param;
@@ -44,35 +65,82 @@ static avr_cycle_count_t avr_timer_compb(struct avr_t * avr, avr_cycle_count_t w
 static avr_cycle_count_t avr_timer_tov(struct avr_t * avr, avr_cycle_count_t when, void * param)
 {
        avr_timer_t * p = (avr_timer_t *)param;
-       avr_raise_interrupt(avr, &p->overflow);
+       int start = p->tov_base == 0;
+       
+       if (!start)
+               avr_raise_interrupt(avr, &p->overflow);
+       p->tov_base = when;
+
+       if (p->compa_cycles) {
+               if (p->compa_cycles < p->tov_cycles)
+                       avr_cycle_timer_register(avr, 
+                               p->compa_cycles - (avr->cycle - p->tov_base), 
+                               avr_timer_compa, p);
+               else if (p->tov_cycles == p->compa_cycles && !start)
+                       avr_timer_compa(avr, when, param);
+       }
+       
+       if (p->compb_cycles) {
+               if (p->compb_cycles < p->tov_cycles)
+                       avr_cycle_timer_register(avr, 
+                               p->compb_cycles - (avr->cycle - p->tov_base), 
+                               avr_timer_compb, p);
+               else if (p->tov_cycles == p->compb_cycles && !start)
+                       avr_timer_compb(avr, when, param);
+       }
 
-       if (p->compa_cycles && p->tov_cycles > p->compa_cycles)
-               avr_cycle_timer_register(avr, p->compa_cycles, avr_timer_compa, p);
-       if (p->compb_cycles && p->tov_cycles > p->compb_cycles)
-               avr_cycle_timer_register(avr, p->compb_cycles, avr_timer_compb, p);
        return when + p->tov_cycles;
 }
 
+
 static uint8_t avr_timer_tcnt_read(struct avr_t * avr, avr_io_addr_t addr, void * param)
 {
-       //avr_timer_t * p = (avr_timer_t *)param;
+       avr_timer_t * p = (avr_timer_t *)param;
        // made to trigger potential watchpoints
+
+       uint64_t when = avr->cycle - p->tov_base;
+
+       uint16_t tcnt = (when * p->tov_top) / p->tov_cycles;
+//     printf("%s-%c when = %d tcnt = %d/%d\n", __FUNCTION__, p->name, (uint32_t)when, tcnt, p->tov_top);
+
+       avr->data[p->r_tcnt] = tcnt;
+       if (p->r_tcnth)
+               avr->data[p->r_tcnth] = tcnt >> 8;
+       
        return avr_core_watch_read(avr, addr);
 }
 
-/*
- * The timers are /always/ 16 bits here, if the higher byte register
- * is specified it's just added.
- */
-static uint16_t _timer_get_ocra(avr_timer_t * p)
+static void avr_timer_tcnt_write(struct avr_t * avr, avr_io_addr_t addr, uint8_t v, void * param)
 {
-       return p->io.avr->data[p->r_ocra] |
-                               (p->r_ocrah ? (p->io.avr->data[p->r_ocrah] << 8) : 0);
-}
-static uint16_t _timer_get_ocrb(avr_timer_t * p)
-{
-       return p->io.avr->data[p->r_ocrb] |
-                               (p->r_ocrbh ? (p->io.avr->data[p->r_ocrbh] << 8) : 0);
+       avr_timer_t * p = (avr_timer_t *)param;
+       avr_core_watch_write(avr, addr, v);
+       uint16_t tcnt = _timer_get_tcnt(p);
+
+       if (!p->tov_top)
+               return;
+               
+       if (tcnt >= p->tov_top)
+               tcnt = 0;
+       
+       // this involves some magicking
+       // cancel the current timers, recalculate the "base" we should be at, reset the
+       // timer base as it should, and re-shedule the timers using that base.
+       
+       avr_cycle_timer_cancel(avr, avr_timer_tov, p);
+       avr_cycle_timer_cancel(avr, avr_timer_compa, p);
+       avr_cycle_timer_cancel(avr, avr_timer_compb, p);
+
+       uint64_t cycles = (tcnt * p->tov_cycles) / p->tov_top;
+
+//     printf("%s-%c %d/%d -- cycles %d/%d\n", __FUNCTION__, p->name, tcnt, p->tov_top, (uint32_t)cycles, (uint32_t)p->tov_cycles);
+
+       // this reset the timers bases to the new base
+       p->tov_base = 0;
+       avr_cycle_timer_register(avr, p->tov_cycles - cycles, avr_timer_tov, p);
+       avr_timer_tov(avr, avr->cycle - cycles, p);
+
+//     tcnt = ((avr->cycle - p->tov_base) * p->tov_top) / p->tov_cycles;
+//     printf("%s-%c new tnt derive to %d\n", __FUNCTION__, p->name, tcnt);    
 }
 
 static void avr_timer_configure(avr_timer_t * p, uint32_t clock, uint32_t top)
@@ -84,12 +152,12 @@ static void avr_timer_configure(avr_timer_t * p, uint32_t clock, uint32_t top)
        float frequency = p->io.avr->frequency;
 
        p->compa_cycles = p->compb_cycles = p->tov_cycles = 0;
+       p->tov_top = top;
 
        printf("%s-%c clock %d top %d a %d b %d\n", __FUNCTION__, p->name, clock, top, ocra, ocrb);
-       if (top != ocra) {
-               p->tov_cycles = frequency / t; // avr_hz_to_cycles(frequency, t);
-               printf("%s-%c TOP %.2fHz = cycles = %d\n", __FUNCTION__, p->name, t, (int)p->tov_cycles);
-       }
+       p->tov_cycles = frequency / t; // avr_hz_to_cycles(frequency, t);
+       printf("%s-%c TOP %.2fHz = cycles = %d\n", __FUNCTION__, p->name, t, (int)p->tov_cycles);
+
        if (ocra && ocra <= top) {
                p->compa_cycles = frequency / fa; // avr_hz_to_cycles(p->io.avr, fa);
                printf("%s-%c A %.2fHz = cycles = %d\n", __FUNCTION__, p->name, fa, (int)p->compa_cycles);
@@ -99,24 +167,27 @@ static void avr_timer_configure(avr_timer_t * p, uint32_t clock, uint32_t top)
                printf("%s-%c B %.2fHz = cycles = %d\n", __FUNCTION__, p->name, fb, (int)p->compb_cycles);
        }
 
-       if (p->tov_cycles > 1)
+       if (p->tov_cycles > 1) {
                avr_cycle_timer_register(p->io.avr, p->tov_cycles, avr_timer_tov, p);
-       else {
-               if (p->compa_cycles > 1)
-                       avr_cycle_timer_register(p->io.avr, p->compa_cycles, avr_timer_compa, p);
-               if (p->compb_cycles > 1)
-                       avr_cycle_timer_register(p->io.avr, p->compb_cycles, avr_timer_compb, p);
+               // calling it once, with when == 0 tells it to arm the A/B timers if needed
+               p->tov_base = 0;
+               avr_timer_tov(p->io.avr, p->io.avr->cycle, p);
        }
 }
 
-static void avr_timer_write(struct avr_t * avr, avr_io_addr_t addr, uint8_t v, void * param)
+static void avr_timer_reconfigure(avr_timer_t * p)
 {
-       avr_timer_t * p = (avr_timer_t *)param;
+       avr_t * avr = p->io.avr;
 
+       // cancel everything
        p->compa_cycles = 0;
        p->compb_cycles = 0;
+       p->tov_cycles = 0;
+       
+       avr_cycle_timer_cancel(avr, avr_timer_tov, p);
+       avr_cycle_timer_cancel(avr, avr_timer_compa, p);
+       avr_cycle_timer_cancel(avr, avr_timer_compb, p);
 
-       avr_core_watch_write(avr, addr, v);
        long clock = avr->frequency;
 
        // only can exists on "asynchronous" 8 bits timers
@@ -126,9 +197,6 @@ static void avr_timer_write(struct avr_t * avr, avr_io_addr_t addr, uint8_t v, v
        uint8_t cs = avr_regbit_get_array(avr, p->cs, ARRAY_SIZE(p->cs));
        if (cs == 0) {
                printf("%s-%c clock turned off\n", __FUNCTION__, p->name);              
-               avr_cycle_timer_cancel(avr, avr_timer_tov, p);
-               avr_cycle_timer_cancel(avr, avr_timer_compa, p);
-               avr_cycle_timer_cancel(avr, avr_timer_compb, p);
                return;
        }
 
@@ -136,7 +204,7 @@ static void avr_timer_write(struct avr_t * avr, avr_io_addr_t addr, uint8_t v, v
        uint8_t cs_div = p->cs_div[cs];
        uint32_t f = clock >> cs_div;
 
-       printf("%s-%c clock %d, div %d(/%d) = %d ; mode %d\n", __FUNCTION__, p->name, clock, cs, 1 << cs_div, f, mode);
+       //printf("%s-%c clock %d, div %d(/%d) = %d ; mode %d\n", __FUNCTION__, p->name, clock, cs, 1 << cs_div, f, mode);
        switch (p->wgm_op[mode].kind) {
                case avr_timer_wgm_normal:
                        avr_timer_configure(p, f, (1 << p->wgm_op[mode].size) - 1);
@@ -150,7 +218,14 @@ static void avr_timer_write(struct avr_t * avr, avr_io_addr_t addr, uint8_t v, v
                }       break;
                default:
                        printf("%s-%c unsupported timer mode wgm=%d (%d)\n", __FUNCTION__, p->name, mode, p->wgm_op[mode].kind);
-       }
+       }       
+}
+
+static void avr_timer_write(struct avr_t * avr, avr_io_addr_t addr, uint8_t v, void * param)
+{
+       avr_timer_t * p = (avr_timer_t *)param;
+       avr_core_watch_write(avr, addr, v);
+       avr_timer_reconfigure(p);
 }
 
 static void avr_timer_reset(avr_io_t * port)
@@ -193,6 +268,7 @@ void avr_timer_init(avr_t * avr, avr_timer_t * p)
         */
        avr_register_io_write(avr, p->r_ocra, avr_timer_write, p);
        avr_register_io_write(avr, p->r_ocrb, avr_timer_write, p);
+       avr_register_io_write(avr, p->r_tcnt, avr_timer_tcnt_write, p);
 
        avr_register_io_read(avr, p->r_tcnt, avr_timer_tcnt_read, p);
 }
index cd5f8b4830348b9f8241bc53c7eb65610775212b..d863532a24569668745850314fb89b90c14ec488 100644 (file)
@@ -71,6 +71,8 @@ typedef struct avr_timer_t {
        uint64_t                compa_cycles;
        uint64_t                compb_cycles;
        uint64_t                tov_cycles;
+       uint64_t                tov_base;       // we we last were called
+       uint16_t                tov_top;        // current top value to calculate tnct
 } avr_timer_t;
 
 void avr_timer_init(avr_t * avr, avr_timer_t * port);
diff --git a/tests/atmega88_timer16.c b/tests/atmega88_timer16.c
new file mode 100644 (file)
index 0000000..e587ba4
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+       atmega88_timer16.c
+
+       Copyright 2008, 2009 Michel Pollet <buserror@gmail.com>
+
+       This file is part of simavr.
+
+       simavr 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.
+
+       simavr 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 simavr.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <avr/io.h>
+#include <stdio.h>
+#include <avr/interrupt.h>
+#include <avr/sleep.h>
+
+/*
+ * This demonstrate how to use the avr_mcu_section.h file
+ * The macro adds a section to the ELF file with useful
+ * information for the simulator
+ */
+#include "avr_mcu_section.h"
+AVR_MCU(F_CPU, "atmega88");
+
+/*
+ * This small section tells simavr to generate a VCD trace dump with changes to these
+ * registers.
+ * Opening it with gtkwave will show you the data being read & written to these
+ * It also demonstrate how you can use unused pins to generate your own traces, with
+ * your own events to be displayed.
+ * 
+ * Here the port B first 2 bits are used to display when a tick occurs, and when a 
+ * TCNT reset occurs.
+ */
+const struct avr_mmcu_vcd_trace_t _mytrace[]  _MMCU_ = {
+       { AVR_MCU_VCD_SYMBOL("TCNT1L"), .what = (void*)&TCNT1L, },      
+       { AVR_MCU_VCD_SYMBOL("TCNT1H"), .what = (void*)&TCNT1H, },      
+       { AVR_MCU_VCD_SYMBOL("tick"), .mask = (1 << 0), .what = (void*)&PORTB, },       
+       { AVR_MCU_VCD_SYMBOL("reset_timer"), .mask = (1 << 1), .what = (void*)&PORTB, },        
+};
+
+volatile uint16_t tcnt;
+
+ISR(TIMER2_COMPA_vect)         // handler for Output Compare 2 overflow interrupt
+{
+       // this really doesn't no anything but proves a way to wake the main()
+       // from sleep at regular intervals
+       PORTB ^= 1;
+}
+
+int main()
+{      
+       //
+       // start the 16 bits timer, with default "normal" waveform
+       // and no interupt enabled. This just increments TCNT1
+       // at a regular rate
+       //
+       // timer prescaler to 64
+       TCCR1B |= (0<<CS12 | 1<<CS11 | 1<<CS10);
+
+       DDRB = 3;
+       
+       //
+       // now enable a tick counter
+       // using an asynchronous mode
+       //
+       ASSR |= (1 << AS2);             // use "external" 32.7k crystal source
+       TCCR2A = (1 << WGM21); // use CLK/8 prescale value, clear timer/counter on compareA match
+    TCCR2B = (2 << CS20); // prescaler
+    OCR2A = 63;        // 64 hz
+    TIMSK2  |= (1 << OCIE2A);
+
+       sei();
+       
+       int count = 0;
+    while (count++ < 100) {
+       // we read TCNT1, which should contain some sort of incrementing value
+       tcnt = TCNT1;           // read it
+       if (tcnt > 10000) {
+               TCNT1 = 500;    // reset it arbitrarily
+               PORTB ^= 2;             // mark it in the waveform file
+       }
+               sleep_cpu();            // this will sleep until a new timer2 tick interrupt occurs
+       }
+       // sleeping with interrupt off is interpreted by simavr as "exit please"
+       cli();
+       sleep_cpu();
+}