--- /dev/null
+/*\r
+ avr_acomp.c\r
+\r
+ Copyright 2017 Konstantin Begun\r
+\r
+ This file is part of simavr.\r
+\r
+ simavr is free software: you can redistribute it and/or modify\r
+ it under the terms of the GNU General Public License as published by\r
+ the Free Software Foundation, either version 3 of the License, or\r
+ (at your option) any later version.\r
+\r
+ simavr is distributed in the hope that it will be useful,\r
+ but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+ GNU General Public License for more details.\r
+\r
+ You should have received a copy of the GNU General Public License\r
+ along with simavr. If not, see <http://www.gnu.org/licenses/>.\r
+ */\r
+\r
+#include <stdlib.h>\r
+#include "avr_acomp.h"\r
+#include "avr_timer.h"\r
+\r
+static uint8_t\r
+avr_acomp_get_state(\r
+ struct avr_t * avr,\r
+ avr_acomp_t *ac)\r
+{\r
+ if (avr_regbit_get(avr, ac->disabled))\r
+ return 0;\r
+\r
+ // get positive voltage\r
+ uint16_t positive_v;\r
+\r
+ if (avr_regbit_get(avr, ac->acbg)) { // if bandgap\r
+ positive_v = ACOMP_BANDGAP;\r
+ } else {\r
+ positive_v = ac->ain_values[0]; // AIN0\r
+ }\r
+\r
+ // get negative voltage\r
+ uint16_t negative_v = 0;\r
+\r
+ // multiplexer is enabled if acme is set and adc is off\r
+ if (avr_regbit_get(avr, ac->acme) && !avr_regbit_get(avr, ac->aden)) {\r
+ if (!avr_regbit_get(avr, ac->pradc)) {\r
+ uint8_t adc_i = avr_regbit_get_array(avr, ac->mux, ARRAY_SIZE(ac->mux));\r
+ if (adc_i < ac->mux_inputs && adc_i < ARRAY_SIZE(ac->adc_values)) {\r
+ negative_v = ac->adc_values[adc_i];\r
+ }\r
+ }\r
+\r
+ } else {\r
+ negative_v = ac->ain_values[1]; // AIN1\r
+ }\r
+\r
+ return positive_v > negative_v;\r
+}\r
+\r
+static avr_cycle_count_t\r
+avr_acomp_sync_state(\r
+ struct avr_t * avr,\r
+ avr_cycle_count_t when,\r
+ void * param)\r
+{\r
+ avr_acomp_t * p = (avr_acomp_t *)param;\r
+ if (!avr_regbit_get(avr, p->disabled)) {\r
+\r
+ uint8_t cur_state = avr_regbit_get(avr, p->aco);\r
+ uint8_t new_state = avr_acomp_get_state(avr, p);\r
+\r
+ if (new_state != cur_state) {\r
+ avr_regbit_setto(avr, p->aco, new_state); // set ACO\r
+\r
+ uint8_t acis0 = avr_regbit_get(avr, p->acis[0]);\r
+ uint8_t acis1 = avr_regbit_get(avr, p->acis[1]);\r
+\r
+ if ((acis0 == 0 && acis1 == 0) || (acis1 == 1 && acis0 == new_state)) {\r
+ avr_raise_interrupt(avr, &p->ac);\r
+ }\r
+\r
+ avr_raise_irq(p->io.irq + ACOMP_IRQ_OUT, new_state);\r
+ }\r
+\r
+ }\r
+\r
+ return 0;\r
+}\r
+\r
+static inline void\r
+avr_schedule_sync_state(\r
+ struct avr_t * avr,\r
+ void *param)\r
+{\r
+ avr_cycle_timer_register(avr, 1, avr_acomp_sync_state, param);\r
+}\r
+\r
+static void\r
+avr_acomp_write_acsr(\r
+ struct avr_t * avr,\r
+ avr_io_addr_t addr,\r
+ uint8_t v,\r
+ void * param)\r
+{\r
+ avr_acomp_t * p = (avr_acomp_t *)param;\r
+\r
+ avr_core_watch_write(avr, addr, v);\r
+\r
+ if (avr_regbit_get(avr, p->acic) != (p->timer_irq ? 1:0)) {\r
+ if (p->timer_irq) {\r
+ avr_unconnect_irq(p->io.irq + ACOMP_IRQ_OUT, p->timer_irq);\r
+ p->timer_irq = NULL;\r
+ }\r
+ else {\r
+ avr_irq_t *irq = avr_io_getirq(avr, AVR_IOCTL_TIMER_GETIRQ(p->timer_name), TIMER_IRQ_IN_ICP);\r
+ if (irq) {\r
+ avr_connect_irq(p->io.irq + ACOMP_IRQ_OUT, irq);\r
+ p->timer_irq = irq;\r
+ }\r
+ }\r
+ }\r
+\r
+ avr_schedule_sync_state(avr, param);\r
+}\r
+\r
+static void\r
+avr_acomp_dependencies_changed(\r
+ struct avr_irq_t * irq,\r
+ uint32_t value,\r
+ void * param)\r
+{\r
+ avr_acomp_t * p = (avr_acomp_t *)param;\r
+ avr_schedule_sync_state(p->io.avr, param);\r
+}\r
+\r
+static void\r
+avr_acomp_irq_notify(\r
+ struct avr_irq_t * irq,\r
+ uint32_t value,\r
+ void * param)\r
+{\r
+ avr_acomp_t * p = (avr_acomp_t *)param;\r
+\r
+ switch (irq->irq) {\r
+ case ACOMP_IRQ_AIN0 ... ACOMP_IRQ_AIN1: {\r
+ p->ain_values[irq->irq - ACOMP_IRQ_AIN0] = value;\r
+ avr_schedule_sync_state(p->io.avr, param);\r
+ } break;\r
+ case ACOMP_IRQ_ADC0 ... ACOMP_IRQ_ADC15: {\r
+ p->adc_values[irq->irq - ACOMP_IRQ_ADC0] = value;\r
+ avr_schedule_sync_state(p->io.avr, param);\r
+ } break;\r
+ }\r
+}\r
+\r
+static void\r
+avr_acomp_register_dependencies(\r
+ avr_acomp_t *p,\r
+ avr_regbit_t rb)\r
+{\r
+ if (rb.reg) {\r
+ avr_irq_register_notify(\r
+ avr_iomem_getirq(p->io.avr, rb.reg, NULL, rb.bit),\r
+ avr_acomp_dependencies_changed,\r
+ p);\r
+ }\r
+}\r
+\r
+static void\r
+avr_acomp_reset(avr_io_t * port)\r
+{\r
+ avr_acomp_t * p = (avr_acomp_t *)port;\r
+\r
+ for (int i = 0; i < ACOMP_IRQ_COUNT; i++)\r
+ avr_irq_register_notify(p->io.irq + i, avr_acomp_irq_notify, p);\r
+\r
+ // register notification for changes of registers comparator does not own\r
+ // avr_register_io_write is tempting instead, but it requires that the handler\r
+ // updates the actual memory too. Given this is for the registers this module\r
+ // does not own, it is tricky to know whether it should write to the actual memory.\r
+ // E.g., if there is already a native handler for it then it will do the writing\r
+ // (possibly even omitting some bits etc). IInterefering would probably be wrong.\r
+ // On the other hand if there isn't a handler already, then this hadnler would have to,\r
+ // as otherwise nobody will.\r
+ // This write notification mechanism should probably need reviewing and fixing\r
+ // For now using IRQ mechanism, as it is not intrusive\r
+\r
+ avr_acomp_register_dependencies(p, p->pradc);\r
+ avr_acomp_register_dependencies(p, p->aden);\r
+ avr_acomp_register_dependencies(p, p->acme);\r
+\r
+ // mux\r
+ for (int i = 0; i < ARRAY_SIZE(p->mux); ++i) {\r
+ avr_acomp_register_dependencies(p, p->mux[i]);\r
+ }\r
+}\r
+\r
+static const char * irq_names[ACOMP_IRQ_COUNT] = {\r
+ [ACOMP_IRQ_AIN0] = "16<ain0",\r
+ [ACOMP_IRQ_AIN1] = "16<ain1",\r
+ [ACOMP_IRQ_ADC0] = "16<adc0",\r
+ [ACOMP_IRQ_ADC1] = "16<adc1",\r
+ [ACOMP_IRQ_ADC2] = "16<adc2",\r
+ [ACOMP_IRQ_ADC3] = "16<adc3",\r
+ [ACOMP_IRQ_ADC4] = "16<adc4",\r
+ [ACOMP_IRQ_ADC5] = "16<adc5",\r
+ [ACOMP_IRQ_ADC6] = "16<adc6",\r
+ [ACOMP_IRQ_ADC7] = "16<adc7",\r
+ [ACOMP_IRQ_ADC8] = "16<adc0",\r
+ [ACOMP_IRQ_ADC9] = "16<adc9",\r
+ [ACOMP_IRQ_ADC10] = "16<adc10",\r
+ [ACOMP_IRQ_ADC11] = "16<adc11",\r
+ [ACOMP_IRQ_ADC12] = "16<adc12",\r
+ [ACOMP_IRQ_ADC13] = "16<adc13",\r
+ [ACOMP_IRQ_ADC14] = "16<adc14",\r
+ [ACOMP_IRQ_ADC15] = "16<adc15",\r
+ [ACOMP_IRQ_OUT] = ">out"\r
+};\r
+\r
+static avr_io_t _io = {\r
+ .kind = "ac",\r
+ .reset = avr_acomp_reset,\r
+ .irq_names = irq_names,\r
+};\r
+\r
+void\r
+avr_acomp_init(\r
+ avr_t * avr,\r
+ avr_acomp_t * p)\r
+{\r
+ p->io = _io;\r
+\r
+ avr_register_io(avr, &p->io);\r
+ avr_register_vector(avr, &p->ac);\r
+ // allocate this module's IRQ\r
+ avr_io_setirqs(&p->io, AVR_IOCTL_ACOMP_GETIRQ, ACOMP_IRQ_COUNT, NULL);\r
+\r
+ avr_register_io_write(avr, p->r_acsr, avr_acomp_write_acsr, p);\r
+}\r
--- /dev/null
+/*\r
+ avr_acomp.h\r
+\r
+ Copyright 2017 Konstantin Begun\r
+\r
+ This file is part of simavr.\r
+\r
+ simavr is free software: you can redistribute it and/or modify\r
+ it under the terms of the GNU General Public License as published by\r
+ the Free Software Foundation, either version 3 of the License, or\r
+ (at your option) any later version.\r
+\r
+ simavr is distributed in the hope that it will be useful,\r
+ but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+ GNU General Public License for more details.\r
+\r
+ You should have received a copy of the GNU General Public License\r
+ along with simavr. If not, see <http://www.gnu.org/licenses/>.\r
+ */\r
+\r
+#ifndef __AVR_COMP_H___\r
+#define __AVR_COMP_H___\r
+\r
+#ifdef __cplusplus\r
+extern "C" {\r
+#endif\r
+\r
+#include "sim_avr.h"\r
+\r
+/*\r
+ * simavr Analog Comparator allows external code to feed real voltages to the\r
+ * simulator, and the simulator uses it's 'real' reference voltage\r
+ * to set comparator output accordingly and trigger in interrupt, if set up this way\r
+ *\r
+ */\r
+\r
+enum {\r
+ // input IRQ values. Values are /always/ volts * 1000 (millivolts)\r
+ ACOMP_IRQ_AIN0 = 0, ACOMP_IRQ_AIN1,\r
+ ACOMP_IRQ_ADC0, ACOMP_IRQ_ADC1, ACOMP_IRQ_ADC2, ACOMP_IRQ_ADC3,\r
+ ACOMP_IRQ_ADC4, ACOMP_IRQ_ADC5, ACOMP_IRQ_ADC6, ACOMP_IRQ_ADC7,\r
+ ACOMP_IRQ_ADC8, ACOMP_IRQ_ADC9, ACOMP_IRQ_ADC10, ACOMP_IRQ_ADC11,\r
+ ACOMP_IRQ_ADC12, ACOMP_IRQ_ADC13, ACOMP_IRQ_ADC14, ACOMP_IRQ_ADC15,\r
+ ACOMP_IRQ_OUT, // output has changed\r
+ ACOMP_IRQ_COUNT\r
+};\r
+\r
+// Get the internal IRQ corresponding to the INT\r
+#define AVR_IOCTL_ACOMP_GETIRQ AVR_IOCTL_DEF('a','c','m','p')\r
+\r
+enum {\r
+ ACOMP_BANDGAP = 1100\r
+};\r
+\r
+typedef struct avr_acomp_t {\r
+ avr_io_t io;\r
+\r
+ uint8_t mux_inputs; // number of inputs (not mux bits!) in multiplexer. Other bits in mux below would be expected to be zero\r
+ avr_regbit_t mux[4];\r
+ avr_regbit_t pradc; // ADC power reduction, this impacts on ability to use adc multiplexer\r
+ avr_regbit_t aden; // ADC Enabled, this impacts on ability to use adc multiplexer\r
+ avr_regbit_t acme; // AC multiplexed input enabled\r
+\r
+ avr_io_addr_t r_acsr; // control & status register\r
+ avr_regbit_t acis[2]; //\r
+ avr_regbit_t acic; // input capture enable\r
+ avr_regbit_t aco; // output\r
+ avr_regbit_t acbg; // bandgap select\r
+ avr_regbit_t disabled;\r
+\r
+ char timer_name; // connected timer for incput capture triggering\r
+\r
+ // use ACI and ACIE bits\r
+ avr_int_vector_t ac;\r
+\r
+ // runtime data\r
+ uint16_t adc_values[16]; // current values on the ADCs inputs\r
+ uint16_t ain_values[2]; // current values on AIN inputs\r
+ avr_irq_t* timer_irq;\r
+} avr_acomp_t;\r
+\r
+void avr_acomp_init(avr_t * avr, avr_acomp_t * port);\r
+\r
+#ifdef __cplusplus\r
+};\r
+#endif\r
+\r
+#endif // __AVR_COMP_H___\r