Commit 985751886b82bfff6845a7d36ef5109776929486
authorDoug Szumski <d.s.szumski@gmail.com>
Sat, 19 May 2018 13:23:26 +0000 (14:23 +0100)
committerDoug Szumski <d.s.szumski@gmail.com>
Sat, 19 May 2018 13:37:42 +0000 (14:37 +0100)
This connects an LED bar up to an ATMega, and uses the
rotary encoder to scroll an LED up and down the bar.

4 files changed:
examples/board_rotenc/Makefile [new file with mode: 0644]
examples/board_rotenc/README [new file with mode: 0644]
examples/board_rotenc/atmega32_rotenc_test.c [new file with mode: 0644]
examples/board_rotenc/rotenc_test.c [new file with mode: 0644]

diff --git a/examples/board_rotenc/Makefile b/examples/board_rotenc/Makefile
new file mode 100644 (file)
index 0000000..48a89f8
--- /dev/null
@@ -0,0 +1,54 @@
+#
+#      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
diff --git a/examples/board_rotenc/README b/examples/board_rotenc/README
new file mode 100644 (file)
index 0000000..cad8c47
--- /dev/null
@@ -0,0 +1,6 @@
+board_rotenc
+
+This sample code demonstrates the use of a virtual Panasonic EVEP rotary
+encoder. The encoder is hooked up to an LED array, and is used to scroll
+an LED up and down. It behaves just like in real life, except contact
+bounce is not modelled.
diff --git a/examples/board_rotenc/atmega32_rotenc_test.c b/examples/board_rotenc/atmega32_rotenc_test.c
new file mode 100644 (file)
index 0000000..ec75714
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ 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;
+}
diff --git a/examples/board_rotenc/rotenc_test.c b/examples/board_rotenc/rotenc_test.c
new file mode 100644 (file)
index 0000000..fdde754
--- /dev/null
@@ -0,0 +1,257 @@
+/*
+ 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");
+}