Commit f4c1e69348d6da1caa4062a94f2deeadc6543ba7
authorKonstantin Begun <kostic2000@gmail.com>
Wed, 10 May 2017 22:55:25 +0000 (23:55 +0100)
committerMichel Pollet <github.com@pollet.net>
Thu, 11 May 2017 06:59:48 +0000 (07:59 +0100)
2 files changed:
simavr/sim/avr_acomp.c [new file with mode: 0755]
simavr/sim/avr_acomp.h [new file with mode: 0755]

diff --git a/simavr/sim/avr_acomp.c b/simavr/sim/avr_acomp.c
new file mode 100755 (executable)
index 0000000..f3e14e1
--- /dev/null
@@ -0,0 +1,241 @@
+/*\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
diff --git a/simavr/sim/avr_acomp.h b/simavr/sim/avr_acomp.h
new file mode 100755 (executable)
index 0000000..caa3c4e
--- /dev/null
@@ -0,0 +1,89 @@
+/*\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