ADC: Fully functional core
authorMichel Pollet <buserror@gmail.com>
Wed, 14 Apr 2010 17:13:21 +0000 (18:13 +0100)
committerMichel Pollet <buserror@gmail.com>
Wed, 14 Apr 2010 17:13:21 +0000 (18:13 +0100)
simavr ADC allows external code to feed real voltages to the
simulator, and the simulator uses it's 'real' reference voltage
to do the right thing and return the 'proper' 10 bits ADC value
to the AVR firmware.

To send values to the ADC, register your code to wait for the
ADC_IRQ_OUT_TRIGGER irq, and at that point send any of the
ADC_IRQ_ADC* with Millivolts as value.

External trigger is not done yet.

Signed-off-by: Michel Pollet <buserror@gmail.com>
simavr/sim/avr_adc.c
simavr/sim/avr_adc.h

index 4b9a7bd..781bba3 100644 (file)
        along with simavr.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include "avr_adc.h"
 
+static avr_cycle_count_t avr_adc_int_raise(struct avr_t * avr, avr_cycle_count_t when, void * param)
+{
+       avr_adc_t * p = (avr_adc_t *)param;
+       if (avr_regbit_get(avr, p->aden)) {
+               // if the interrupts are not used, still raised the UDRE and TXC flag
+               avr_raise_interrupt(avr, &p->adc);
+               avr_regbit_clear(avr, p->adsc);
+               p->first = 0;
+               p->read_status = 0;
+       }
+       return 0;
+}
+
+static uint8_t avr_adc_read_l(struct avr_t * avr, avr_io_addr_t addr, void * param)
+{
+       avr_adc_t * p = (avr_adc_t *)param;
+
+       if (p->read_status)     // conversion already done
+               return avr_core_watch_read(avr, addr);
+
+       uint8_t refi = avr_regbit_get_array(avr, p->ref, ARRAY_SIZE(p->ref));
+       uint16_t ref = p->ref_values[refi];
+       uint8_t muxi = avr_regbit_get_array(avr, p->mux, ARRAY_SIZE(p->mux));
+       avr_adc_mux_t mux = p->muxmode[muxi];
+       // optional shift left/right
+       uint8_t shift = avr_regbit_get(avr, p->adlar) ? 0 : 6;
+
+       uint32_t reg = 0;
+       switch (mux.kind) {
+               case ADC_MUX_SINGLE:
+                       reg = p->adc_values[mux.src];
+                       break;
+               case ADC_MUX_DIFF:
+                       if (mux.gain == 0)
+                               mux.gain = 1;
+                       reg = ((uint32_t)p->adc_values[mux.src] * mux.gain) -
+                                       ((uint32_t)p->adc_values[mux.diff] * mux.gain);
+                       break;
+               case ADC_MUX_TEMP:
+                       reg = p->temp; // assumed to be already calibrated somehow
+                       break;
+               case ADC_MUX_REF:
+                       reg = mux.src; // reference voltage
+                       break;
+       }
+       uint32_t vref = 3300;
+       switch (ref) {
+               case ADC_VREF_AREF:
+                       if (!avr->aref)
+                               printf("ADC Warning : missing AREF analog voltage\n");
+                       else
+                               vref = avr->aref;
+                       break;
+               case ADC_VREF_AVCC:
+                       if (!avr->avcc)
+                               printf("ADC Warning : missing AVCC analog voltage\n");
+                       else
+                               vref = avr->avcc;
+                       break;
+               default:
+                       vref = ref;
+       }
+//     printf("ADCL %d:%3d:%3d read %4d vref %d:%d=%d\n",
+//                     mux.kind, mux.diff, mux.src,
+//                     reg, refi, ref, vref);
+       reg = (reg * 0x3ff) / vref;     // scale to 10 bits ADC
+//     printf("ADC to 10 bits 0x%x %d\n", reg, reg);
+       if (reg > 0x3ff) {
+               printf("ADC Warning channel %d clipped %u/%u VREF %d\n", mux.kind, reg, 0x3ff, vref);
+               reg = 0x3ff;
+       }
+       reg <<= shift;
+//     printf("ADC to 10 bits %x shifted %d\n", reg, shift);
+       avr->data[p->r_adcl] = reg;
+       avr->data[p->r_adch] = reg >> 8;
+       p->read_status = 1;
+       return avr_core_watch_read(avr, addr);
+}
+
 /*
- * PLACEHOLDER ADC MODULE
+ * From Datasheet:
+ * "When ADCL is read, the ADC Data Register is not updated until ADCH is read.
+ * Consequently, if the result is left adjusted and no more than 8-bit
+ * precision is required, it is sufficient to read ADCH.
+ * Otherwise, ADCL must be read first, then ADCH."
+ * So here if the H is read before the L, we still call the L to update the
+ * register value.
  */
+static uint8_t avr_adc_read_h(struct avr_t * avr, avr_io_addr_t addr, void * param)
+{
+       avr_adc_t * p = (avr_adc_t *)param;
+       // no "break" here on purpose
+       switch (p->read_status) {
+               case 0:
+                       avr_adc_read_l(avr, p->r_adcl, param);
+               case 1:
+                       p->read_status = 2;
+               default:
+                       return avr_core_watch_read(avr, addr);
+       }
+}
+
+static void avr_adc_write(struct avr_t * avr, avr_io_addr_t addr, uint8_t v, void * param)
+{
+       avr_adc_t * p = (avr_adc_t *)param;
+       uint8_t adsc = avr_regbit_get(avr, p->adsc);
+       uint8_t aden = avr_regbit_get(avr, p->aden);
+
+       avr->data[p->adsc.reg] = v;
+
+       // can't write zero to adsc
+       if (adsc && !avr_regbit_get(avr, p->adsc)) {
+               avr_regbit_set(avr, p->adsc);
+               v = avr->data[p->adsc.reg];
+       }
+       if (!aden && avr_regbit_get(avr, p->aden)) {
+               // first conversion
+               p->first = 1;
+               printf("ADC Start AREF %d AVCC %d\n", avr->aref, avr->avcc);
+       }
+       if (aden && !avr_regbit_get(avr, p->aden)) {
+               // stop ADC
+               avr_cycle_timer_cancel(avr, avr_adc_int_raise, p);
+               avr_regbit_clear(avr, p->adsc);
+       }
+       if (!adsc && avr_regbit_get(avr, p->adsc)) {
+               // start one!
+               uint8_t muxi = avr_regbit_get_array(avr, p->mux, ARRAY_SIZE(p->mux));
+               union {
+                       avr_adc_mux_t mux;
+                       uint32_t v;
+               } e = { .mux = p->muxmode[muxi] };
+               avr_raise_irq(p->io.irq + ADC_IRQ_OUT_TRIGGER, e.v);
+
+               // clock prescaler are just a bit shift.. and 0 means 1
+               uint32_t div = avr_regbit_get_array(avr, p->adps, ARRAY_SIZE(p->adps));
+               if (!div) div++;
+
+               div = avr->frequency >> div;
+               if (p->first)
+                       printf("ADC starting at %uKHz\n", div / 13 / 100);
+               div /= p->first ? 25 : 13;      // first cycle is longer
+
+               avr_cycle_timer_register(avr,
+                               avr_hz_to_cycles(avr, div),
+                               avr_adc_int_raise, p);
+       }
+       avr_core_watch_write(avr, addr, v);
+}
+
+static void avr_adc_irq_notify(struct avr_irq_t * irq, uint32_t value, void * param)
+{
+       avr_adc_t * p = (avr_adc_t *)param;
+       avr_t * avr = p->io.avr;
+
+       switch (irq->irq) {
+               case ADC_IRQ_ADC0 ... ADC_IRQ_ADC7: {
+                       p->adc_values[irq->irq] = value;
+               }       break;
+               case ADC_IRQ_TEMP: {
+                       p->temp = value;
+               }       break;
+               case ADC_IRQ_IN_TRIGGER: {
+                       if (avr_regbit_get(avr, p->adate)) {
+                               // start a conversion
+                       }
+               }       break;
+       }
+}
+
+static void avr_adc_reset(avr_io_t * port)
+{
+       avr_adc_t * p = (avr_adc_t *)port;
+
+       // stop ADC
+       avr_cycle_timer_cancel(p->io.avr, avr_adc_int_raise, p);
+       avr_regbit_clear(p->io.avr, p->adsc);
+
+       for (int i = 0; i < ADC_IRQ_COUNT; i++)
+               avr_irq_register_notify(p->io.irq + i, avr_adc_irq_notify, p);
+}
 
 static avr_io_t        _io = {
        .kind = "adc",
+       .reset = avr_adc_reset,
 };
 
 void avr_adc_init(avr_t * avr, avr_adc_t * p)
 {
        p->io = _io;
 
+       // allocate this module's IRQ
+       p->io.irq_count = ADC_IRQ_COUNT;
+       p->io.irq = avr_alloc_irq(0, p->io.irq_count);
+       p->io.irq_ioctl_get = AVR_IOCTL_ADC_GETIRQ;
+
        avr_register_io(avr, &p->io);
        avr_register_vector(avr, &p->adc);
 
-//     avr_register_io_write(avr, p->r_eecr, avr_eeprom_write, p);
+       avr_register_io_write(avr, p->r_adcsra, avr_adc_write, p);
+       avr_register_io_read(avr, p->r_adcl, avr_adc_read_l, p);
+       avr_register_io_read(avr, p->r_adch, avr_adc_read_h, p);
 }
-
index e756871..b86b905 100644 (file)
 
 #include "sim_avr.h"
 
+/*
+ * simavr ADC allows external code to feed real voltages to the
+ * simulator, and the simulator uses it's 'real' reference voltage
+ * to do the right thing and return the 'proper' 10 bits ADC value
+ * to the AVR firmware.
+ *
+ * To send values to the ADC, register your code to wait for the
+ * ADC_IRQ_OUT_TRIGGER irq, and at that point send any of the
+ * ADC_IRQ_ADC* with Millivolts as value.
+ *
+ * External trigger is not done yet.
+ */
+
+enum {
+       // input IRQ values. Values are /always/ volts * 1000 (millivolts)
+       ADC_IRQ_ADC0 = 0, ADC_IRQ_ADC1, ADC_IRQ_ADC2, ADC_IRQ_ADC3,
+       ADC_IRQ_ADC4, ADC_IRQ_ADC5, ADC_IRQ_ADC6, ADC_IRQ_ADC7,
+       ADC_IRQ_TEMP,                   // see the datasheet
+       ADC_IRQ_IN_TRIGGER,
+       ADC_IRQ_OUT_TRIGGER,    // sends a avr_adc_mux_t
+       ADC_IRQ_COUNT
+};
+
+// Get the internal IRQ corresponding to the INT
+#define AVR_IOCTL_ADC_GETIRQ AVR_IOCTL_DEF('a','d','c','i')
+
+/*
+ * Definition of a ADC mux mode.
+ */
+enum {
+       ADC_MUX_NONE = 0,               // Nothing. return 0
+       ADC_MUX_NOISE,                  // Nothing. return something random
+       ADC_MUX_SINGLE,                 // Normal ADC pin reading
+       ADC_MUX_DIFF,                   // differencial channels (src-diff)
+       ADC_MUX_TEMP,                   // internal temp sensor
+       ADC_MUX_REF,                    // reference voltage (in src * 100)
+};
+typedef struct avr_adc_mux_t {
+       unsigned long kind : 3, gain : 8, diff : 8, src : 13;
+} avr_adc_mux_t;
+
+enum {
+       ADC_VREF_AREF   = 0,    // default mode
+       ADC_VREF_AVCC,
+       ADC_VREF_V110   = 1100,
+       ADC_VREF_V256   = 2560,
+};
+
 typedef struct avr_adc_t {
        avr_io_t                io;
 
        uint8_t                 r_admux;
        // if the last bit exists in the mux, we are an extended ADC
        avr_regbit_t    mux[5];
-       avr_regbit_t    ref[3];         // reference voltage
+       avr_regbit_t    ref[3];         // reference voltages bits
+       uint16_t                ref_values[7]; // ADC_VREF_*
+
        avr_regbit_t    adlar;          // left/right adjustment bit
 
        uint8_t                 r_adcsra;       // ADC Control and Status Register A
@@ -47,11 +97,41 @@ typedef struct avr_adc_t {
        avr_regbit_t    bin;            // Bipolar Input Mode (tinyx5 have it)
        avr_regbit_t    ipr;            // Input Polarity Reversal (tinyx5 have it)
 
-
        // use ADIF and ADIE bits
        avr_int_vector_t adc;
+
+       /*
+        * runtime bits
+        */
+       avr_adc_mux_t   muxmode[32];// maximum 5 bits of mux modes
+       uint16_t                adc_values[8];  // current values on the ADCs
+       uint16_t                temp;           // temp sensor reading
+       uint8_t                 first;
+       uint8_t                 read_status;    // marked one when adcl is read
 } avr_adc_t;
 
 void avr_adc_init(avr_t * avr, avr_adc_t * port);
 
+
+/*
+ * Helper macros for the Cores definition of muxes
+ */
+#define AVR_ADC_SINGLE(_chan) { \
+               .kind = ADC_MUX_SINGLE, \
+               .src = (_chan), \
+       }
+#define AVR_ADC_DIFF(_a,_b,_g) { \
+               .kind = ADC_MUX_DIFF, \
+               .src = (_a), \
+               .diff = (_b), \
+               .gain = (_g), \
+       }
+#define AVR_ADC_REF(_t) { \
+               .kind = ADC_MUX_REF, \
+               .src = (_t), \
+       }
+#define AVR_ADC_TEMP() { \
+               .kind = ADC_MUX_TEMP, \
+       }
+
 #endif /* __AVR_ADC_H___ */