From: Michel Pollet Date: Mon, 21 Dec 2009 21:08:45 +0000 (+0000) Subject: timers: Added TCNT reading/writing support X-Git-Tag: v1.0a1~33 X-Git-Url: https://git.htl-mechatronik.at/public/?a=commitdiff_plain;h=e3a37a324adcd5db12fe8ee11384f9bd0f9081bb;p=sx%2Fsimavr.git timers: Added TCNT reading/writing support 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 --- diff --git a/simavr/sim/avr_timer.c b/simavr/sim/avr_timer.c index fc3d3c7..4f0ac93 100644 --- a/simavr/sim/avr_timer.c +++ b/simavr/sim/avr_timer.c @@ -27,6 +27,27 @@ #include #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); } diff --git a/simavr/sim/avr_timer.h b/simavr/sim/avr_timer.h index cd5f8b4..d863532 100644 --- a/simavr/sim/avr_timer.h +++ b/simavr/sim/avr_timer.h @@ -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 index 0000000..e587ba4 --- /dev/null +++ b/tests/atmega88_timer16.c @@ -0,0 +1,98 @@ +/* + atmega88_timer16.c + + Copyright 2008, 2009 Michel Pollet + + 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 . + */ + +#include +#include +#include +#include + +/* + * 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< 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(); +}