--- /dev/null
+#
+# Copyright 2008-2012 Michel Pollet <buserror@gmail.com>
+# Copyright 2018 Doug Szumski <d.s.szumski@gmail.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+target=rotenc_test
+firm_src = ${wildcard at*${board}.c}
+firmware = ${firm_src:.c=.axf}
+simavr = ../..
+
+IPATH = .
+IPATH += ${simavr}/examples/shared
+IPATH += ${simavr}/examples/parts
+IPATH += ${simavr}/include
+IPATH += ${simavr}/simavr/sim
+
+VPATH = .
+VPATH += ${simavr}/examples/shared
+VPATH += ${simavr}/examples/parts
+
+LDFLAGS += -lpthread
+
+include ../Makefile.opengl
+
+all: obj ${firmware} ${target}
+
+include ${simavr}/Makefile.common
+
+atmega1280_${target}.axf: atmega32_${target}.c
+
+board = ${OBJ}/${target}.elf
+
+${board} : ${OBJ}/${target}.o
+${board} : ${OBJ}/rotenc.o
+
+${target}: ${board}
+ @echo $@ done
+
+clean: clean-${OBJ}
+ rm -rf *.hex *.a *.axf ${target} *.vcd .*.swo .*.swp .*.swm .*.swn
--- /dev/null
+/*
+ atmega32_rotenc_test.c
+
+ A simple example demonstrating a Pansonic EVEP rotary encoder
+ scrolling an LED up and down an 8 segment LED bar.
+
+ Copyright 2018 Doug Szumski <d.s.szumski@gmail.com>
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <avr/io.h>
+#include <avr/interrupt.h>
+#include <avr/sleep.h>
+#include <stdio.h>
+
+#include "avr_mcu_section.h"
+AVR_MCU(F_CPU, "atmega32");
+
+typedef enum {
+ CLOCKWISE,
+ COUNTERCLOCKWISE
+} led_scroll_dir_t;
+
+/* Initialise the inputs for signal A and signal B from the rotary encoder.
+ * They are hooked up to INTO on PD2 and GPIO pin PA0. Pull-ups are on the
+ * PCB. */
+void
+rotary_spin_int_init(void)
+{
+ // Set INT0 and GPIO pin as inputs
+ DDRD &= ~(1 << PD2) & ~(1 << PA0);
+ // Any logical change on INT0 generates an interrupt (see DS p.67)
+ MCUCR |= (1 << ISC00);
+ MCUCR &= ~(1 << ISC01);
+ // Enable interrupt pin PD2
+ GICR |= (1 << INT0);
+}
+
+/* Initialise inputs for signal C from the rotary encoder (button). This
+ * is hooked up to INT2 on PB2. A pull-up is on the PCB. */
+void
+rotary_button_int_init(void)
+{
+ DDRB &= ~(1 << PB2);
+ // Falling edge trigger (pin is pulled up) (see DS p.67)
+ MCUCSR &= ~(1 << ISC2);
+ // Enable interrupt pin PB2
+ GICR |= (1 << INT2);
+}
+
+/* Configure 8 segment virtual 'LED bar' on port C */
+void
+led_bar_init(void)
+{
+ DDRC = 0xFF; // All outputs
+ PORTC = 0b00010000; // Start with a light on in the middle
+}
+
+void
+led_bar_scroll(led_scroll_dir_t dir)
+{
+ switch (dir) {
+ case CLOCKWISE:
+ if (PORTC < 0b10000000) PORTC <<= 1;
+ break;
+ case COUNTERCLOCKWISE:
+ if (PORTC > 0b00000001) PORTC >>= 1;
+ break;
+ default:
+ break;
+ }
+}
+
+int
+main()
+{
+ // Disable interrupts whilst configuring them to avoid false triggers
+ cli();
+ rotary_spin_int_init();
+ rotary_button_int_init();
+ sei();
+
+ led_bar_init();
+
+ while (1) {
+ // Wait for some input
+ }
+
+ cli();
+ sleep_mode();
+}
+
+ISR(INT0_vect)
+{
+ // The Panasonic EVEP rotary encoder this is written for moves two
+ // phases for every 'click' it emits. The interrupt is configured
+ // to fire on every edge change of B, which is once per 'click'.
+ // Moving forwards in phase, after B has changed state we poll A.
+ // We get the sequence (A=0,B=1), (A=1,B=0). Moving backwards in
+ // phase, after B has changed state we get (A=1,B=1), (A=0,B=0).
+ //
+ // +-------+---+---+
+ // | Phase | A | B |
+ // +-------+---+---+
+ // | 0 | 0 | 0 |
+ // | 1 | 0 | 1 |
+ // | 2 | 1 | 1 |
+ // | 3 | 1 | 0 |
+ // +-------+---+---+
+ //
+ // The twist direction is then obtained by taking the logical
+ // XOR of outputs A and B after the interrupt has fired. Of
+ // course with a 'real life' part you might be better off
+ // using a state machine, or some extra hardware to filter out
+ // contact bounces which aren't modelled in the virtual part.
+ uint8_t ccw_twist = ((PIND & (1 << PD2)) >> 2) ^ (PINA & (1 << PA0));
+
+ // Scroll the LED bar
+ if (ccw_twist) {
+ led_bar_scroll(COUNTERCLOCKWISE);
+ } else {
+ led_bar_scroll(CLOCKWISE);
+ }
+}
+
+ISR(INT2_vect)
+{
+ // Fires when rotary encoder button was pressed and resets
+ // the LED bar to the starting position
+ PORTC = 0b00010000;
+}
--- /dev/null
+/*
+ rotenc_test.c
+
+ Copyright 2018 Doug Szumski <d.s.szumski@gmail.com>
+
+ Based on i2ctest.c by:
+
+ Copyright 2008-2011 Michel Pollet <buserror@gmail.com>
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <libgen.h>
+#if __APPLE__
+#include <GLUT/glut.h>
+#else
+#include <GL/glut.h>
+#endif
+
+#include <pthread.h>
+#include <curses.h>
+
+#include "sim_avr.h"
+#include "sim_gdb.h"
+#include "sim_elf.h"
+#include "rotenc.h"
+#include "avr_ioport.h"
+#include "sim_vcd_file.h"
+
+avr_t * avr = NULL;
+avr_vcd_t vcd_file;
+rotenc_t rotenc;
+
+volatile int state = cpu_Running;
+volatile unsigned char key_g;
+uint8_t pin_state_g = 0b00010000;
+
+float pixsize = 64;
+
+/*
+ * Called when the AVR changes any of the pins on port C. We use this to
+ * hook up these pins to the virtual 'LED bar'.
+ */
+void
+pin_changed_hook(struct avr_irq_t * irq, uint32_t value, void * param)
+{
+ pin_state_g = (pin_state_g & ~(1 << irq->irq)) | (value << irq->irq);
+}
+
+void
+displayCB(void)
+{
+ glClear(GL_COLOR_BUFFER_BIT);
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+
+ 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_g & (1 << di)) != 0;
+ if (on) {
+ float x = (di) * grid;
+ float y = 0;
+ glVertex2f(x + size, y + size);
+ glVertex2f(x, y + size);
+ glVertex2f(x, y);
+ glVertex2f(x + size, y);
+ }
+ }
+
+ glEnd();
+ glutSwapBuffers();
+ glFlush();
+}
+
+void
+keyCB(unsigned char key, int x, int y)
+{
+ if (key == 'q')
+ exit(0);
+
+ switch (key) {
+ case 'q':
+ case 0x1f: // escape
+ exit(0);
+ break;
+ default:
+ // Pass key to avr thread
+ key_g = key;
+ break;
+ }
+}
+
+void
+timerCB(int i)
+{
+ static uint8_t oldstate = 0b00010000;
+
+ // Restart timer
+ glutTimerFunc(1000 / 64, timerCB, 0);
+
+ // Only update if the pin changed state
+ if (oldstate != pin_state_g) {
+ oldstate = pin_state_g;
+ glutPostRedisplay();
+ }
+}
+
+static void *
+avr_run_thread(void * ignore)
+{
+ state = cpu_Running;
+
+ while ((state != cpu_Done) && (state != cpu_Crashed)) {
+ if (key_g != 0) {
+ switch (key_g) {
+ case 'j':
+ rotenc_twist(&rotenc, ROTENC_CCW_CLICK);
+ break;
+ case 'k':
+ rotenc_button_press(&rotenc);
+ break;
+ case 'l':
+ rotenc_twist(&rotenc, ROTENC_CW_CLICK);
+ break;
+ default:
+ // ignore
+ break;
+ }
+ key_g = 0;
+ }
+ state = avr_run(avr);
+ }
+ return NULL ;
+}
+
+int
+main(int argc, char *argv[])
+{
+ elf_firmware_t f;
+ const char * fname = "atmega32_rotenc_test.axf";
+
+ printf(
+ "ROTARY ENCODER LED bar demo (press q to quit):\n\n"
+ "Press 'j' for CCW turn \n"
+ "Press 'l' for CW turn \n"
+ "Press 'k' for button click \n\n");
+
+ printf("Firmware pathname is %s\n", fname);
+ 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);
+
+ // Uncomment for debugging
+ //avr->gdb_port = 1234;
+ //avr->state = cpu_Stopped;
+ //avr_gdb_init(avr);
+
+ // Initialise rotary encoder
+ rotenc_init(avr, &rotenc);
+ rotenc.verbose = 1;
+
+ // RE GPIO
+ avr_connect_irq(
+ rotenc.irq + IRQ_ROTENC_OUT_A_PIN,
+ avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ('A'), 0));
+
+ // RE INT
+ avr_connect_irq(
+ rotenc.irq + IRQ_ROTENC_OUT_B_PIN,
+ avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ('D'), 2));
+
+ // INT (button)
+ // Pull up
+ avr_raise_irq(rotenc.irq + IRQ_ROTENC_OUT_BUTTON_PIN, 1);
+ avr_connect_irq(
+ rotenc.irq + IRQ_ROTENC_OUT_BUTTON_PIN,
+ avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ('B'), 2));
+
+ // VCD output
+ avr_vcd_init(avr, "gtkwave_output.vcd", &vcd_file, 1000 /* usec */);
+ avr_vcd_add_signal(
+ &vcd_file,
+ avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ('D'), 2), 1,
+ "INT0");
+ avr_vcd_add_signal(&vcd_file, rotenc.irq + IRQ_ROTENC_OUT_A_PIN, 1, "A");
+ avr_vcd_add_signal(&vcd_file, rotenc.irq + IRQ_ROTENC_OUT_B_PIN, 1, "B");
+ avr_vcd_start(&vcd_file);
+
+ // Connect all the pins on port C to our callback. This is the 'LED bar'.
+ for (int i = 0; i < 8; i++) {
+ avr_irq_register_notify(
+ avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ('C'), i),
+ pin_changed_hook,
+ NULL);
+ }
+
+ // Set the button in the 'up' state
+ avr_raise_irq(rotenc.irq + IRQ_ROTENC_OUT_BUTTON_PIN, 1);
+
+ // Start the encoder at phase 1
+ avr_raise_irq(rotenc.irq + IRQ_ROTENC_OUT_A_PIN, 0);
+ avr_raise_irq(rotenc.irq + IRQ_ROTENC_OUT_B_PIN, 0);
+
+ /*
+ * OpenGL init, can be ignored
+ */
+ glutInit(&argc, argv);
+
+ glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
+ glutInitWindowSize(8 * pixsize, 1 * pixsize);
+ glutCreateWindow("Glut");
+
+ // Set up projection matrix
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glOrtho(0, 8 * pixsize, 0, 1 * pixsize, 0, 10);
+ glScalef(1, -1, 1);
+ glTranslatef(0, -1 * pixsize, 0);
+
+ glutDisplayFunc(displayCB);
+ glutKeyboardFunc(keyCB);
+ glutTimerFunc(1000 / 24, timerCB, 0);
+
+ pthread_t run;
+ pthread_create(&run, NULL, avr_run_thread, NULL);
+
+ glutMainLoop();
+
+ printf("quit\n");
+}