diff --git a/examples/board_keypress/Makefile b/examples/board_keypress/Makefile new file mode 100644 index 000000000..741577c79 --- /dev/null +++ b/examples/board_keypress/Makefile @@ -0,0 +1,49 @@ +# +# Copyright 2008, 2009 Michel Pollet +# +# This file is part of simavr. +# +# simavr is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# simavr is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with simavr. If not, see . + +target= keypress +firm_src = ${wildcard at*${board}.c} +firmware = ${firm_src:.c=.axf} +simavr = ../../ + +IPATH = . +IPATH += ../parts +IPATH += ${simavr}/include +IPATH += ${simavr}/simavr/sim + +VPATH = . +VPATH += ../parts + +LDFLAGS += -lpthread + +include ../Makefile.opengl + +all: obj ${firmware} ${target} + +include ${simavr}/Makefile.common + +board = ${OBJ}/${target}.elf + +${board} : ${OBJ}/button.o +${board} : ${OBJ}/${target}.o + +${target}: ${board} + @echo $@ done + +clean: clean-${OBJ} + rm -rf *.a *.axf ${target} *.vcd *.hex diff --git a/examples/board_keypress/README b/examples/board_keypress/README new file mode 100644 index 000000000..314464f30 --- /dev/null +++ b/examples/board_keypress/README @@ -0,0 +1,24 @@ + +This contains a sample program to demonstrate the use of simavr +using 'custom' code, and own "peripherals". It shows how it is +possible to "hook" code to the AVR pins, and also how to make +"peripherals" and also hook them up to AVR pins. + +The demo will display two LEDs, one connected to the PORTB5 pin +and second to the PORTD7 pin of the mega48. +Two other pins, PORTD0 and PORTD1, are configured as internally +pulled-up inputs and push buttons are hooked-up to them. + +Firmware transfers the state of every input pin to corresponding +output pin in a loop. The state of PORTD0 pin is copied to the PORTB5 +and PORTD1 is copied to PORTD7. + +On startup, both LEDs should lit, this demonstrates a wark of internall pull-ups. +If you hit '1' key on the keyboard, PORTD0 and PORTB5 should go into LOW state +and the LED will be turned off. +On '2' key hit the same will happen with the second LED. + + +The display uses opengl and "glut" so it should be very portable. +On most linux you will need "freeglut-dev" package. + diff --git a/examples/board_keypress/atmega48_keypress.c b/examples/board_keypress/atmega48_keypress.c new file mode 100644 index 000000000..9266e600b --- /dev/null +++ b/examples/board_keypress/atmega48_keypress.c @@ -0,0 +1,54 @@ +/* + atmega48_keypress.c + + Copyright 2017 Al Popov + + This file is part of simavr. + + simavr is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + simavr is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with simavr. If not, see . + */ + +#include +#include +#include + +// for linker, emulator, and programmer's sake +#include "avr_mcu_section.h" +AVR_MCU(F_CPU, "atmega48"); + +int main() +{ + // Setup output pins for LEDs + DDRB = _BV(PINB5); + DDRD = _BV(PIND7); + // Turn on internal pull-ups on input pins + PORTD = _BV(PIND0) | _BV(PIND1); + + sei(); + + for (;;) { + if (PIND & _BV(PIND0)) { + PORTB |= _BV(PINB5); + } else { + PORTB &= ~_BV(PINB5); + } + if (PIND & _BV(PIND1)) { + PORTD |= _BV(PIND7); + } else { + PORTD &= ~_BV(PIND7); + } + //sleep_mode(); + } +} + diff --git a/examples/board_keypress/keypress.c b/examples/board_keypress/keypress.c new file mode 100644 index 000000000..405313bc9 --- /dev/null +++ b/examples/board_keypress/keypress.c @@ -0,0 +1,251 @@ +/* + keypress.c + + Copyright 2017 Al Popov + + This file is part of simavr. + + simavr is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + simavr is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with simavr. If not, see . + */ + +#include +#include +#include +#if __APPLE__ +#include +#else +#include +#endif +#include + +#include "sim_avr.h" +#include "avr_ioport.h" +#include "sim_elf.h" +#include "sim_gdb.h" +//#include "sim_vcd_file.h" + +#include "button.h" + +button_t button1; +button_t button2; +int do_button_press1 = 0; +int do_button_press2 = 0; +avr_t * avr = NULL; +//avr_vcd_t vcd_file; +uint8_t pin_state = 0; + +float pixsize = 64; +int window; + +/* + * called when some button is pressed (i.e. pin state on the port D is changed) + * so lets update our buffer + */ +void output_pin_changed_hook(struct avr_irq_t * irq, uint32_t value, void * param) +{ + int pin_no = irq->irq; + if (pin_no == 5) + pin_no = 0; + else if (pin_no == 7) + pin_no = 1; + pin_state = (pin_state & ~(1 << pin_no)) | (value << pin_no); +} + +void displayCB(void) /* function called whenever redisplay needed */ +{ + // OpenGL rendering goes here... + glClear(GL_COLOR_BUFFER_BIT); + + // Set up modelview matrix + glMatrixMode(GL_MODELVIEW); // Select modelview matrix + glLoadIdentity(); // Start with an identity matrix + + float grid = pixsize; + float size = grid * 0.8; + glBegin(GL_QUADS); + glColor3f(1,0,0); + + for (int di = 0; di < 8; di++) { + char on = (pin_state & (1 << di)) != 0; + if (on) { + float x = (di) * grid; + float y = 0; //(si * grid * 8) + (di * grid); + glVertex2f(x + size, y + size); + glVertex2f(x, y + size); + glVertex2f(x, y); + glVertex2f(x + size, y); + } + } + + glEnd(); + glutSwapBuffers(); + //glFlush(); /* Complete any pending operations */ +} + +void keyCB(unsigned char key, int x, int y) /* called on key press */ +{ + if (key == 'q') + exit(0); + //static uint8_t buf[64]; + switch (key) { + case 'q': + case 0x1f: // escape + exit(0); + break; + case '1': + do_button_press1++; // pass the message to the AVR thread + break; + case '2': + do_button_press2++; // pass the message to the AVR thread + break; + } +} + +// gl timer. if the pin have changed states, refresh display +void timerCB(int i) +{ + static uint8_t oldstate = 0xff; + // restart timer + glutTimerFunc(1000/64, timerCB, 0); + + if (oldstate != pin_state) { + oldstate = pin_state; + glutPostRedisplay(); + } +} + +//#undef AVR_IOPORT_INTRN_PULLUP_IMP +static void * avr_run_thread(void * oaram) +{ + int b_press1 = do_button_press1; + int b_press2 = do_button_press2; + int b_buttons_hooked_up = 0; + + while (1) { + int b_user_activity = 0; + avr_run(avr); + if (do_button_press1 != b_press1) { + b_press1 = do_button_press1; + printf("Button 1 pressed\n"); + button_press(&button1, 1000000); + b_user_activity = 1; + } + if (do_button_press2 != b_press2) { + b_press2 = do_button_press2; + printf("Button 2 pressed\n"); + button_press(&button2, 1000000); + b_user_activity = 2; + } +#ifdef AVR_IOPORT_INTRN_PULLUP_IMP + // If we wish to take a control over internally pulled-up input pin, + // that is, we have a "low output impedance source" connected to the pin, + // we must explicitly inform simavr about it. + if (!b_buttons_hooked_up && b_user_activity) { + avr_raise_irq_float(avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ('D'), + IOPORT_IRQ_PIN0_SRC_IMP), 0, 1); + avr_raise_irq_float(avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ('D'), + IOPORT_IRQ_PIN0_SRC_IMP + 1), 0, 1); + b_buttons_hooked_up = 1; + } + // Otherwise simavr internall pull-ups handling is active and will "override" + // the pin state in some situations. +#endif //AVR_IOPORT_INTRN_PULLUP_IMP + } + return NULL; +} + + +int main(int argc, char *argv[]) +{ + elf_firmware_t f; + const char * fname = "atmega48_keypress.axf"; + //char path[256]; + +// sprintf(path, "%s/%s", dirname(argv[0]), fname); +// printf("Firmware pathname is %s\n", path); + elf_read_firmware(fname, &f); + + printf("firmware %s f=%d mmcu=%s\n", fname, (int)f.frequency, f.mmcu); + + avr = avr_make_mcu_by_name(f.mmcu); + if (!avr) { + fprintf(stderr, "%s: AVR '%s' not known\n", argv[0], f.mmcu); + exit(1); + } + avr_init(avr); + avr_load_firmware(avr, &f); + + // initialize our 'peripherals' + button_init(avr, &button1, "button0"); + button_init(avr, &button2, "button1"); + // "connect" the output irq of our buttons to the AVR input pins of port D + avr_connect_irq( + button1.irq + IRQ_BUTTON_OUT, + avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ('D'), 0)); + avr_connect_irq( + button2.irq + IRQ_BUTTON_OUT, + avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ('D'), 1)); + + // connect output pins of the AVR to our callback + avr_irq_register_notify( + avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ('B'), 5), + output_pin_changed_hook, + NULL); + avr_irq_register_notify( + avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ('D'), 7), + output_pin_changed_hook, + NULL); + + // even if not setup at startup, activate gdb if crashing + avr->gdb_port = 1234; + if (0) { + //avr->state = cpu_Stopped; + avr_gdb_init(avr); + } + + // 'raise' it, it's a "pullup" + //avr_raise_irq(button0.irq + IRQ_BUTTON_OUT, 1); + + printf( "Demo launching:\n" + " Press 'q' to quit\n\n" + " Press '1' to extinguish the first LED\n" + " Press '2' to extinguish the second LED\n" + ); + + /* + * OpenGL init, can be ignored + */ + glutInit(&argc, argv); /* initialize GLUT system */ + + glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE); + glutInitWindowSize(8 * pixsize, 1 * pixsize); /* width=400pixels height=500pixels */ + window = glutCreateWindow("Simavr key press test"); /* create window */ + + // Set up projection matrix + glMatrixMode(GL_PROJECTION); // Select projection matrix + glLoadIdentity(); // Start with an identity matrix + glOrtho(0, 8 * pixsize, 0, 1 * pixsize, 0, 10); + glScalef(1,-1,1); + glTranslatef(0, -1 * pixsize, 0); + + glutDisplayFunc(displayCB); /* set window's display callback */ + glutKeyboardFunc(keyCB); /* set window's key callback */ + glutTimerFunc(1000 / 24, timerCB, 0); + + // the AVR run on it's own thread. it even allows for debugging! + pthread_t run; + pthread_create(&run, NULL, avr_run_thread, NULL); + + glutMainLoop(); +} diff --git a/tests/atmega88_pullups_test.c b/tests/atmega88_pullups_test.c new file mode 100644 index 000000000..ce96b6b5d --- /dev/null +++ b/tests/atmega88_pullups_test.c @@ -0,0 +1,208 @@ +/* + atmega88_pullups_test.c + + Copyright 2017 Al Popov + + This file is part of simavr. + + simavr is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + simavr is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with simavr. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +/* + * This test verifies correctness of handling AVR internal pull-ups + * The macro adds a section to the ELF file with useful + * information for the simulator + */ +#include "avr_mcu_section.h" +AVR_MCU(F_CPU, "atmega88"); +// tell simavr to listen to commands written in this (unused) register +//AVR_MCU_SIMAVR_COMMAND(&GPIOR0); + +void +init_uart() +{ + UCSR0C |= (3 << UCSZ00); // 8 bits + // see http://www.nongnu.org/avr-libc/user-manual/group__util__setbaud.html +#define BAUD 38400 +#include + UBRR0H = UBRRH_VALUE; + UBRR0L = UBRRL_VALUE; +#if USE_2X + UCSR0A |= (1 << U2X0); +#else + UCSR0A &= ~(1 << U2X0); +#endif + + // enable receiver & transmitter + UCSR0B |= (1 << RXCIE0) | (1 << RXEN0) | (1 << TXEN0); +} + +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 int +uart_getchar(FILE *stream) +{ + // Wait for data to be received + while ( !(UCSR0A & (1<. + */ +#include "tests.h" +#include "sim_avr.h" +#include "sim_elf.h" +#include "sim_core.h" +#include "avr_uart.h" +#include "avr_ioport.h" +#include +#include +#include +#include +#include +#include + +enum { + IRQ_UART_TERM_BYTE_IN = 0, + IRQ_UART_TERM_BYTE_OUT, + IRQ_UART_TERM_COUNT +}; + +typedef struct uart_TERM_t { + avr_irq_t * irq; // irq list +} uart_TERM_t; + +static const char * irq_names[IRQ_UART_TERM_COUNT] = { + [IRQ_UART_TERM_BYTE_IN] = "8currlen > buf->alloclen-1) + fail("Internal error"); + if (buf->alloclen == 0) + fail("Internal error"); + if (buf->currlen == buf->alloclen-1) { + buf->alloclen *= 2; + buf->str = realloc(buf->str, buf->alloclen); + } + buf->str[buf->currlen++] = value; + buf->str[buf->currlen] = 0; + if (value == '\n') { + buf->head = buf->currlen; + avr_irq_t * dst = avr_io_getirq(buf->avr, AVR_IOCTL_UART_GETIRQ('0'), UART_IRQ_INPUT); + avr_raise_irq(dst, '1'); + avr_raise_irq(dst, '\n'); + } else if (value == '\r') { + if ((buf->str[buf->head] == '~') + && (buf->head < (buf->currlen-2))) { + // simulate the button press or release on the specified pin + int m_pinN = buf->str[buf->head + 2] - '0'; + avr_irq_t *irq = avr_io_getirq( buf->avr, AVR_IOCTL_IOPORT_GETIRQ('D'), m_pinN ); + if (buf->str[buf->head + 1] == 'p') { +#ifdef AVR_IOPORT_INTRN_PULLUP_IMP + // If we wish to take a control over internally pulled-up input pin, + // that is, we have a "low output impedance source" connected to the pin, + // we must explicitly inform simavr about it. + avr_irq_t *src_imp_irq = avr_io_getirq(buf->avr, AVR_IOCTL_IOPORT_GETIRQ('D'), m_pinN + IOPORT_IRQ_PIN0_SRC_IMP); + avr_raise_irq_float(src_imp_irq, 0, 1); + // Otherwise simavr internall pull-ups handling is active and will "override" the pin state in some situations. +#endif //AVR_IOPORT_INTRN_PULLUP_IMP + avr_raise_irq(irq, 0); + } else if (buf->str[buf->head + 1] == 'r') { + avr_raise_irq(irq, 1); + } + } + } +} + +static void init_output_buffer(struct output_buffer *buf, avr_t *avr) { + buf->str = malloc(128); + buf->str[0] = 0; + buf->currlen = 0; + buf->alloclen = 128; + buf->maxlen = 4096; + buf->head = 0; + buf->avr = avr; +} + +static void +test_assert_uart_receive_avr(avr_t *avr, + unsigned long run_usec, + const char *expected + ) { + struct output_buffer buf; + init_output_buffer(&buf, avr); + + memset(&uart_term, 0, sizeof(uart_term)); + + uart_term.irq = avr_alloc_irq(&avr->irq_pool, 0, IRQ_UART_TERM_COUNT, irq_names); + avr_irq_register_notify(uart_term.irq + IRQ_UART_TERM_BYTE_IN, buf_output_cb, &buf); + //avr_irq_register_notify(avr_io_getirq(avr, AVR_IOCTL_UART_GETIRQ('0'), UART_IRQ_OUTPUT), buf_output_cb, &buf); + avr_irq_t * src = avr_io_getirq(avr, AVR_IOCTL_UART_GETIRQ('0'), UART_IRQ_OUTPUT); + avr_connect_irq(src, uart_term.irq + IRQ_UART_TERM_BYTE_IN); + + enum tests_finish_reason reason = tests_run_test(avr, run_usec); + if (reason == LJR_CYCLE_TIMER) { + if (strcmp(buf.str, expected) == 0) { + _fail(NULL, 0, "Simulation did not finish within %lu simulated usec. " + "UART output is correct and complete.", run_usec); + } + _fail(NULL, 0, "Simulation did not finish within %lu simulated usec. " + "UART output so far: \"%s\"", run_usec, buf.str); + } + if (strcmp(buf.str, expected) != 0) + _fail(NULL, 0, "UART outputs differ: expected \"%s\", got \"%s\"", expected, buf.str); +} + +int main(int argc, char **argv) { + tests_init(argc, argv); + + static const char *expected = + "Read internally pulled-up input pins on PIND2:4 and mirror its state to PORTD5:7 outputs\r\n" + "PIND2 expected=HIGH, actual=HIGH\r\n" + "PIND3 expected=HIGH, actual=HIGH\r\n" + "PIND4 expected=HIGH, actual=HIGH\r\n" + "~p2\r\n" + "PIND2 expected=LOW, actual=LOW\r\n" + "PIND3 expected=HIGH, actual=HIGH\r\n" + "PIND4 expected=HIGH, actual=HIGH\r\n" + "~r2\r\n" + "PIND2 expected=HIGH, actual=HIGH\r\n" + "PIND3 expected=HIGH, actual=HIGH\r\n" + "PIND4 expected=HIGH, actual=HIGH\r\n" + "~p3\r\n" + "PIND2 expected=HIGH, actual=HIGH\r\n" + "PIND3 expected=LOW, actual=LOW\r\n" + "PIND4 expected=HIGH, actual=HIGH\r\n" + "~r3\r\n" + "PIND2 expected=HIGH, actual=HIGH\r\n" + "PIND3 expected=HIGH, actual=HIGH\r\n" + "PIND4 expected=HIGH, actual=HIGH\r\n" + "~p4\r\n" + "PIND2 expected=HIGH, actual=HIGH\r\n" + "PIND3 expected=HIGH, actual=HIGH\r\n" + "PIND4 expected=LOW, actual=LOW\r\n" + "~r4\r\n" + "PIND2 expected=HIGH, actual=HIGH\r\n" + "PIND3 expected=HIGH, actual=HIGH\r\n" + "PIND4 expected=HIGH, actual=HIGH\r\n" + ; + avr_t *avr = tests_init_avr("atmega88_pullups_test.axf"); + + test_assert_uart_receive_avr(avr, 5000000L, + expected); + + tests_success(); + return 0; +}