Add a test for basic port functions and fix problems found.
authorga <ga@oldell.fish>
Sat, 23 Jan 2021 13:15:17 +0000 (13:15 +0000)
committerga <ga@oldell.fish>
Sat, 23 Jan 2021 13:21:15 +0000 (13:21 +0000)
avr_extint.c: prevent spurious interrupts.
avr_ioport.c: allow calling program to control value read from PIN.

simavr/sim/avr_extint.c
simavr/sim/avr_ioport.c
tests/atmega168_ioport.c [new file with mode: 0644]
tests/test_atmega168_ioport.c [new file with mode: 0644]

index e8dd9bc..d5371eb 100644 (file)
@@ -37,9 +37,14 @@ static avr_cycle_count_t avr_extint_poll_level_trig(
                void * param)
 {
        avr_extint_poll_context_t *poll = (avr_extint_poll_context_t *)param;
-       avr_extint_t * p = (avr_extint_t *)poll->extint;
+       avr_extint_t * p = poll->extint;
 
-       char port = p->eint[poll->eint_no].port_ioctl & 0xFF;
+        /* Check for change of interrupt mode. */
+
+        if (avr_regbit_get_array(avr, p->eint[poll->eint_no].isc, 2))
+               goto terminate_poll;
+
+        char port = p->eint[poll->eint_no].port_ioctl & 0xFF;
        avr_ioport_state_t iostate;
        if (avr_ioctl(avr, AVR_IOCTL_IOPORT_GETSTATE( port ), &iostate) < 0)
                goto terminate_poll;
@@ -176,9 +181,9 @@ static void avr_extint_reset(avr_io_t * port)
        avr_extint_t * p = (avr_extint_t *)port;
 
        for (int i = 0; i < EXTINT_COUNT; i++) {
-               avr_irq_register_notify(p->io.irq + i, avr_extint_irq_notify, p);
-
                if (p->eint[i].port_ioctl) {
+                        avr_irq_register_notify(p->io.irq + i, avr_extint_irq_notify, p);
+
                        if (p->eint[i].isc[1].reg) // level triggering available
                                p->eint[i].strict_lvl_trig = 1; // turn on repetitive level triggering by default
                        avr_irq_t * irq = avr_io_getirq(p->io.avr,
@@ -211,10 +216,13 @@ void avr_extint_init(avr_t * avr, avr_extint_t * p)
        p->io = _io;
 
        avr_register_io(avr, &p->io);
-       for (int i = 0; i < EXTINT_COUNT; i++)
+       for (int i = 0; i < EXTINT_COUNT; i++) {
+                if (!p->eint[i].port_ioctl)
+                     break;
                avr_register_vector(avr, &p->eint[i].vector);
-
+        }
        // allocate this module's IRQ
+
        avr_io_setirqs(&p->io, AVR_IOCTL_EXTINT_GETIRQ(), EXTINT_COUNT, NULL);
 }
 
index 64adbc4..9e936e3 100644 (file)
@@ -34,11 +34,11 @@ avr_ioport_read(
        uint8_t ddr = avr->data[p->r_ddr];
        uint8_t v = (avr->data[p->r_pin] & ~ddr) | (avr->data[p->r_port] & ddr);
        avr->data[addr] = v;
-       // made to trigger potential watchpoints
-       v = avr_core_watch_read(avr, addr);
        avr_raise_irq(p->io.irq + IOPORT_IRQ_REG_PIN, v);
        D(if (avr->data[addr] != v) printf("** PIN%c(%02x) = %02x\r\n", p->name, addr, v);)
 
+       // made to trigger potential watchpoints
+       v = avr_core_watch_read(avr, addr);
        return v;
 }
 
@@ -252,7 +252,7 @@ static const char * irq_names[IOPORT_IRQ_COUNT] = {
        [IOPORT_IRQ_PIN5] = "=pin5",
        [IOPORT_IRQ_PIN6] = "=pin6",
        [IOPORT_IRQ_PIN7] = "=pin7",
-       [IOPORT_IRQ_PIN_ALL] = "8=all",
+       [IOPORT_IRQ_PIN_ALL] = "8>all",
        [IOPORT_IRQ_DIRECTION_ALL] = "8>ddr",
        [IOPORT_IRQ_REG_PORT] = "8>port",
        [IOPORT_IRQ_REG_PIN] = "8>pin",
diff --git a/tests/atmega168_ioport.c b/tests/atmega168_ioport.c
new file mode 100644 (file)
index 0000000..9c3f19f
--- /dev/null
@@ -0,0 +1,112 @@
+#ifndef F_CPU
+#define F_CPU 8000000
+#endif
+#include <avr/io.h>
+#include <stdio.h>
+#include <avr/interrupt.h>
+#include <avr/sleep.h>
+
+/*
+ * 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, "atmega168");
+
+static int uart_putchar(char c, FILE *stream) {
+       if (c == '\n')
+               uart_putchar('\r', stream);
+       loop_until_bit_is_set(UCSR0A, UDRE0);
+       UDR0 = c;
+       return 0;
+}
+
+static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL,
+                                         _FDEV_SETUP_WRITE);
+
+ISR(INT0_vect)
+{
+    printf("I<%02X ", PIND);
+}
+
+ISR(PCINT2_vect)
+{
+    printf("J<%02X ", PORTD);
+    PORTD = 0;
+}
+
+int main()
+{
+       stdout = &mystdout;
+
+        /* Enable output on Port D pins 0-3 and write to them. */
+
+        DDRD = 0xf;
+        PORTD = 0xa;
+
+        printf("P<%02X ", PIND); // Should say P<2A as caller sets bit 5.
+
+        /* Toggle some outputs. */
+
+        PIND = 3;
+
+        /* Change directions. */
+
+        DDRD = 0x3c;
+
+        /* Change output. */
+
+        PORTD = 0xf0;
+
+        /* This should say P<70 - pullups and direct output give 0xF0
+         * but the caller sees that and turns off bit 7 input,
+         * overriding that pullup.
+         */
+
+        printf("P<%02X ", PIND);
+
+        /* Set-up rising edge interrupt on pin 2 (INT 0). */
+
+        EICRA = 3;
+        EIMSK = 1;
+
+        /* Turn off pin 4, signal the caller to raise pin 2. */
+
+        PORTD = 0xe0;
+
+        /* Verify the interrupt flag is set. */
+
+        printf("F<%02X ", EIFR);
+
+        sei();
+
+        /* This duplicates the value in the INT0 handler, but it
+         * takes sufficient time to be sure that there is only one
+         * interrupt.  There was a bug that caused continuous interrupts
+         * when this was first tried.
+         */
+
+        printf("P<%02X ", PIND);
+
+        /* TODO: Test the level-triggered interupt.  It can be started
+         * by a pin-value change or by writing to either of EICRA and EIMSK.
+         */
+
+        /* TODO: Set-up pin change interrupt on pin 7 (PCINT23) */
+
+        PCICR = (1 << PCIE2); /* Interrupt enable. */
+        PCMSK2 = 0x0a;        /* Pins 1 and 3. */
+        DDRD = 3;
+        PORTD = 1;            /* No interrupt. */
+        PORTD = 3;            /* Interrupt. */
+
+        /* Allow time for second interrupt. */
+
+        printf("P<%02X ", PIND);
+
+       // this quits the simulator, since interupts are off
+       // this is a "feature" that allows running tests cases and exit
+        cli();
+       sleep_cpu();
+}
diff --git a/tests/test_atmega168_ioport.c b/tests/test_atmega168_ioport.c
new file mode 100644 (file)
index 0000000..d4cf7d1
--- /dev/null
@@ -0,0 +1,129 @@
+#include <stdio.h>
+#include <string.h>
+#include "tests.h"
+#include "avr_ioport.h"
+
+/* Start of the IOPORT's IRQ list. */
+
+static avr_irq_t *base_irq;
+
+/* Accumulate log of events for comparison at the end. */
+
+static char  log[256];
+static char *fill = log;
+
+#define LOG(...) \
+    (fill += snprintf(fill, (log + sizeof log) - fill, __VA_ARGS__))
+
+/* IRQ call-back function for changes in pin levels. */
+
+static void monitor_5(struct avr_irq_t *irq, uint32_t value, void *param)
+{
+    LOG("5-%02X ", value);
+}
+
+/* This monitors the simulator's idea of the I/O pin states.
+ * Changes this program makes to inputs are not reported,
+ * presumably because the simulator "knows" we made them.
+ */
+
+static void monitor(struct avr_irq_t *irq, uint32_t value, void *param)
+{
+    LOG("P-%02X ", value);
+
+    if (value == 9) {
+        /* Assume this is because bit 0 was left high when its
+         * direction switched to input.  Make it low.
+         */
+
+        avr_raise_irq(base_irq + IOPORT_IRQ_PIN0, 0);
+    } if (value == 0xf0) {
+        /* Assume this is a combination of 0x30 (direct) and 0xc0 (pullups).
+         * So change inputs.
+         */
+
+        avr_raise_irq(base_irq + IOPORT_IRQ_PIN4, 0); // Ignored.
+        avr_raise_irq(base_irq + IOPORT_IRQ_PIN7, 0);
+    }
+
+}
+
+/* Writes to output ports and DDR are reported here. */
+
+static void reg_write(struct avr_irq_t *irq, uint32_t value, void *param)
+{
+    static int zero_count;
+    char       c;
+
+    if (irq->irq == IOPORT_IRQ_REG_PORT)
+        c = 'o';
+    else if (irq->irq == IOPORT_IRQ_DIRECTION_ALL)
+        c = 'd';
+    else
+        c = '?';
+    LOG("%c-%02X ", c, value);
+
+    if (irq->irq == IOPORT_IRQ_REG_PORT) {
+        if (value == 0xe0) {
+            /* Program request to raise bit 2: external interrupt. */
+
+            avr_raise_irq(base_irq + IOPORT_IRQ_PIN2, 1);
+        } else if (value == 0) {
+            if (zero_count++ == 0) {
+                /* Raise bit 3: pin change interrupt. */
+            
+                avr_raise_irq(base_irq + IOPORT_IRQ_PIN3, 1);
+            }
+        }
+    }
+}
+
+/* Called when the AVR reads the input port. */
+
+static void reg_read(struct avr_irq_t *irq, uint32_t value, void *param)
+{
+    LOG("I-%02X ", value);
+
+    /* Change the value read. */
+
+    avr_raise_irq(base_irq + IOPORT_IRQ_PIN5, 1);
+}
+
+/* This string should be sent by the firmware. */
+
+static const char *expected = "P<2A P<70 F<01 I<E0 P<E0 J<03 J<00 P<E8 ";
+
+/* This string is expected in variable log. */
+
+static const char *log_expected =
+    "d-0F P-00 o-0A P-0A I-0A 5-01 o-09 P-29 d-3C 5-00 P-09 o-F0 5-01 P-F0 "
+    "I-70 "                                     // Interrupts off testing.
+    "o-E0 P-E0 I-E0 "                           // External interrupt test.
+    "d-03 o-01 P-E1 o-03 P-E3 o-00 P-E8 I-E8 "; // Pin change interrupt test.
+
+
+int main(int argc, char **argv) {
+       avr_t             *avr;
+
+       tests_init(argc, argv);
+        avr = tests_init_avr("atmega168_ioport.axf");
+        base_irq = avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ('D'), 0);
+
+        avr_irq_register_notify(base_irq + IOPORT_IRQ_PIN5,
+                                monitor_5, NULL);
+        avr_irq_register_notify(base_irq + IOPORT_IRQ_PIN_ALL,
+                                monitor, NULL);
+        avr_irq_register_notify(base_irq + IOPORT_IRQ_DIRECTION_ALL,
+                                reg_write, NULL);
+        avr_irq_register_notify(base_irq + IOPORT_IRQ_REG_PORT,
+                                reg_write, NULL);
+        avr_irq_register_notify(base_irq + IOPORT_IRQ_REG_PIN,
+                                reg_read, NULL);
+
+        tests_assert_uart_receive_avr(avr, 100000, expected, '0');
+
+        if (strcmp(log, log_expected))
+            fail("Internal log: %s.\nExpected: %s.\n", log, log_expected);
+       tests_success();
+       return 0;
+}