From f4c1e69348d6da1caa4062a94f2deeadc6543ba7 Mon Sep 17 00:00:00 2001 From: Konstantin Begun Date: Wed, 10 May 2017 23:55:25 +0100 Subject: [PATCH] acomp: Add support --- simavr/sim/avr_acomp.c | 241 +++++++++++++++++++++++++++++++++++++++++ simavr/sim/avr_acomp.h | 89 +++++++++++++++ 2 files changed, 330 insertions(+) create mode 100755 simavr/sim/avr_acomp.c create mode 100755 simavr/sim/avr_acomp.h diff --git a/simavr/sim/avr_acomp.c b/simavr/sim/avr_acomp.c new file mode 100755 index 0000000..f3e14e1 --- /dev/null +++ b/simavr/sim/avr_acomp.c @@ -0,0 +1,241 @@ +/* + avr_acomp.c + + Copyright 2017 Konstantin Begun + + 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 "avr_acomp.h" +#include "avr_timer.h" + +static uint8_t +avr_acomp_get_state( + struct avr_t * avr, + avr_acomp_t *ac) +{ + if (avr_regbit_get(avr, ac->disabled)) + return 0; + + // get positive voltage + uint16_t positive_v; + + if (avr_regbit_get(avr, ac->acbg)) { // if bandgap + positive_v = ACOMP_BANDGAP; + } else { + positive_v = ac->ain_values[0]; // AIN0 + } + + // get negative voltage + uint16_t negative_v = 0; + + // multiplexer is enabled if acme is set and adc is off + if (avr_regbit_get(avr, ac->acme) && !avr_regbit_get(avr, ac->aden)) { + if (!avr_regbit_get(avr, ac->pradc)) { + uint8_t adc_i = avr_regbit_get_array(avr, ac->mux, ARRAY_SIZE(ac->mux)); + if (adc_i < ac->mux_inputs && adc_i < ARRAY_SIZE(ac->adc_values)) { + negative_v = ac->adc_values[adc_i]; + } + } + + } else { + negative_v = ac->ain_values[1]; // AIN1 + } + + return positive_v > negative_v; +} + +static avr_cycle_count_t +avr_acomp_sync_state( + struct avr_t * avr, + avr_cycle_count_t when, + void * param) +{ + avr_acomp_t * p = (avr_acomp_t *)param; + if (!avr_regbit_get(avr, p->disabled)) { + + uint8_t cur_state = avr_regbit_get(avr, p->aco); + uint8_t new_state = avr_acomp_get_state(avr, p); + + if (new_state != cur_state) { + avr_regbit_setto(avr, p->aco, new_state); // set ACO + + uint8_t acis0 = avr_regbit_get(avr, p->acis[0]); + uint8_t acis1 = avr_regbit_get(avr, p->acis[1]); + + if ((acis0 == 0 && acis1 == 0) || (acis1 == 1 && acis0 == new_state)) { + avr_raise_interrupt(avr, &p->ac); + } + + avr_raise_irq(p->io.irq + ACOMP_IRQ_OUT, new_state); + } + + } + + return 0; +} + +static inline void +avr_schedule_sync_state( + struct avr_t * avr, + void *param) +{ + avr_cycle_timer_register(avr, 1, avr_acomp_sync_state, param); +} + +static void +avr_acomp_write_acsr( + struct avr_t * avr, + avr_io_addr_t addr, + uint8_t v, + void * param) +{ + avr_acomp_t * p = (avr_acomp_t *)param; + + avr_core_watch_write(avr, addr, v); + + if (avr_regbit_get(avr, p->acic) != (p->timer_irq ? 1:0)) { + if (p->timer_irq) { + avr_unconnect_irq(p->io.irq + ACOMP_IRQ_OUT, p->timer_irq); + p->timer_irq = NULL; + } + else { + avr_irq_t *irq = avr_io_getirq(avr, AVR_IOCTL_TIMER_GETIRQ(p->timer_name), TIMER_IRQ_IN_ICP); + if (irq) { + avr_connect_irq(p->io.irq + ACOMP_IRQ_OUT, irq); + p->timer_irq = irq; + } + } + } + + avr_schedule_sync_state(avr, param); +} + +static void +avr_acomp_dependencies_changed( + struct avr_irq_t * irq, + uint32_t value, + void * param) +{ + avr_acomp_t * p = (avr_acomp_t *)param; + avr_schedule_sync_state(p->io.avr, param); +} + +static void +avr_acomp_irq_notify( + struct avr_irq_t * irq, + uint32_t value, + void * param) +{ + avr_acomp_t * p = (avr_acomp_t *)param; + + switch (irq->irq) { + case ACOMP_IRQ_AIN0 ... ACOMP_IRQ_AIN1: { + p->ain_values[irq->irq - ACOMP_IRQ_AIN0] = value; + avr_schedule_sync_state(p->io.avr, param); + } break; + case ACOMP_IRQ_ADC0 ... ACOMP_IRQ_ADC15: { + p->adc_values[irq->irq - ACOMP_IRQ_ADC0] = value; + avr_schedule_sync_state(p->io.avr, param); + } break; + } +} + +static void +avr_acomp_register_dependencies( + avr_acomp_t *p, + avr_regbit_t rb) +{ + if (rb.reg) { + avr_irq_register_notify( + avr_iomem_getirq(p->io.avr, rb.reg, NULL, rb.bit), + avr_acomp_dependencies_changed, + p); + } +} + +static void +avr_acomp_reset(avr_io_t * port) +{ + avr_acomp_t * p = (avr_acomp_t *)port; + + for (int i = 0; i < ACOMP_IRQ_COUNT; i++) + avr_irq_register_notify(p->io.irq + i, avr_acomp_irq_notify, p); + + // register notification for changes of registers comparator does not own + // avr_register_io_write is tempting instead, but it requires that the handler + // updates the actual memory too. Given this is for the registers this module + // does not own, it is tricky to know whether it should write to the actual memory. + // E.g., if there is already a native handler for it then it will do the writing + // (possibly even omitting some bits etc). IInterefering would probably be wrong. + // On the other hand if there isn't a handler already, then this hadnler would have to, + // as otherwise nobody will. + // This write notification mechanism should probably need reviewing and fixing + // For now using IRQ mechanism, as it is not intrusive + + avr_acomp_register_dependencies(p, p->pradc); + avr_acomp_register_dependencies(p, p->aden); + avr_acomp_register_dependencies(p, p->acme); + + // mux + for (int i = 0; i < ARRAY_SIZE(p->mux); ++i) { + avr_acomp_register_dependencies(p, p->mux[i]); + } +} + +static const char * irq_names[ACOMP_IRQ_COUNT] = { + [ACOMP_IRQ_AIN0] = "16io = _io; + + avr_register_io(avr, &p->io); + avr_register_vector(avr, &p->ac); + // allocate this module's IRQ + avr_io_setirqs(&p->io, AVR_IOCTL_ACOMP_GETIRQ, ACOMP_IRQ_COUNT, NULL); + + avr_register_io_write(avr, p->r_acsr, avr_acomp_write_acsr, p); +} diff --git a/simavr/sim/avr_acomp.h b/simavr/sim/avr_acomp.h new file mode 100755 index 0000000..caa3c4e --- /dev/null +++ b/simavr/sim/avr_acomp.h @@ -0,0 +1,89 @@ +/* + avr_acomp.h + + Copyright 2017 Konstantin Begun + + 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 . + */ + +#ifndef __AVR_COMP_H___ +#define __AVR_COMP_H___ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "sim_avr.h" + +/* + * simavr Analog Comparator allows external code to feed real voltages to the + * simulator, and the simulator uses it's 'real' reference voltage + * to set comparator output accordingly and trigger in interrupt, if set up this way + * + */ + +enum { + // input IRQ values. Values are /always/ volts * 1000 (millivolts) + ACOMP_IRQ_AIN0 = 0, ACOMP_IRQ_AIN1, + ACOMP_IRQ_ADC0, ACOMP_IRQ_ADC1, ACOMP_IRQ_ADC2, ACOMP_IRQ_ADC3, + ACOMP_IRQ_ADC4, ACOMP_IRQ_ADC5, ACOMP_IRQ_ADC6, ACOMP_IRQ_ADC7, + ACOMP_IRQ_ADC8, ACOMP_IRQ_ADC9, ACOMP_IRQ_ADC10, ACOMP_IRQ_ADC11, + ACOMP_IRQ_ADC12, ACOMP_IRQ_ADC13, ACOMP_IRQ_ADC14, ACOMP_IRQ_ADC15, + ACOMP_IRQ_OUT, // output has changed + ACOMP_IRQ_COUNT +}; + +// Get the internal IRQ corresponding to the INT +#define AVR_IOCTL_ACOMP_GETIRQ AVR_IOCTL_DEF('a','c','m','p') + +enum { + ACOMP_BANDGAP = 1100 +}; + +typedef struct avr_acomp_t { + avr_io_t io; + + uint8_t mux_inputs; // number of inputs (not mux bits!) in multiplexer. Other bits in mux below would be expected to be zero + avr_regbit_t mux[4]; + avr_regbit_t pradc; // ADC power reduction, this impacts on ability to use adc multiplexer + avr_regbit_t aden; // ADC Enabled, this impacts on ability to use adc multiplexer + avr_regbit_t acme; // AC multiplexed input enabled + + avr_io_addr_t r_acsr; // control & status register + avr_regbit_t acis[2]; // + avr_regbit_t acic; // input capture enable + avr_regbit_t aco; // output + avr_regbit_t acbg; // bandgap select + avr_regbit_t disabled; + + char timer_name; // connected timer for incput capture triggering + + // use ACI and ACIE bits + avr_int_vector_t ac; + + // runtime data + uint16_t adc_values[16]; // current values on the ADCs inputs + uint16_t ain_values[2]; // current values on AIN inputs + avr_irq_t* timer_irq; +} avr_acomp_t; + +void avr_acomp_init(avr_t * avr, avr_acomp_t * port); + +#ifdef __cplusplus +}; +#endif + +#endif // __AVR_COMP_H___ -- 2.39.5