From c0985c4fbfa66af4f1f25062bb59d023035d20a9 Mon Sep 17 00:00:00 2001 From: ga Date: Sat, 23 Jan 2021 13:15:17 +0000 Subject: [PATCH] Add a test for basic port functions and fix problems found. avr_extint.c: prevent spurious interrupts. avr_ioport.c: allow calling program to control value read from PIN. --- simavr/sim/avr_extint.c | 20 ++++-- simavr/sim/avr_ioport.c | 6 +- tests/atmega168_ioport.c | 112 +++++++++++++++++++++++++++++ tests/test_atmega168_ioport.c | 129 ++++++++++++++++++++++++++++++++++ 4 files changed, 258 insertions(+), 9 deletions(-) create mode 100644 tests/atmega168_ioport.c create mode 100644 tests/test_atmega168_ioport.c diff --git a/simavr/sim/avr_extint.c b/simavr/sim/avr_extint.c index e8dd9bc..d5371eb 100644 --- a/simavr/sim/avr_extint.c +++ b/simavr/sim/avr_extint.c @@ -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); } diff --git a/simavr/sim/avr_ioport.c b/simavr/sim/avr_ioport.c index 64adbc4..9e936e3 100644 --- a/simavr/sim/avr_ioport.c +++ b/simavr/sim/avr_ioport.c @@ -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 index 0000000..9c3f19f --- /dev/null +++ b/tests/atmega168_ioport.c @@ -0,0 +1,112 @@ +#ifndef F_CPU +#define F_CPU 8000000 +#endif +#include +#include +#include +#include + +/* + * 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 index 0000000..d4cf7d1 --- /dev/null +++ b/tests/test_atmega168_ioport.c @@ -0,0 +1,129 @@ +#include +#include +#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