From 985751886b82bfff6845a7d36ef5109776929486 Mon Sep 17 00:00:00 2001 From: Doug Szumski Date: Sat, 19 May 2018 14:23:26 +0100 Subject: [PATCH] parts: add rotary encoder demo board This connects an LED bar up to an ATMega, and uses the rotary encoder to scroll an LED up and down the bar. --- examples/board_rotenc/Makefile | 54 ++++ examples/board_rotenc/README | 6 + examples/board_rotenc/atmega32_rotenc_test.c | 145 +++++++++++ examples/board_rotenc/rotenc_test.c | 257 +++++++++++++++++++ 4 files changed, 462 insertions(+) create mode 100644 examples/board_rotenc/Makefile create mode 100644 examples/board_rotenc/README create mode 100644 examples/board_rotenc/atmega32_rotenc_test.c create mode 100644 examples/board_rotenc/rotenc_test.c diff --git a/examples/board_rotenc/Makefile b/examples/board_rotenc/Makefile new file mode 100644 index 0000000..48a89f8 --- /dev/null +++ b/examples/board_rotenc/Makefile @@ -0,0 +1,54 @@ +# +# Copyright 2008-2012 Michel Pollet +# Copyright 2018 Doug Szumski +# +# 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=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 index 0000000..cad8c47 --- /dev/null +++ b/examples/board_rotenc/README @@ -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 index 0000000..ec75714 --- /dev/null +++ b/examples/board_rotenc/atmega32_rotenc_test.c @@ -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 + + 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 "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 index 0000000..fdde754 --- /dev/null +++ b/examples/board_rotenc/rotenc_test.c @@ -0,0 +1,257 @@ +/* + rotenc_test.c + + Copyright 2018 Doug Szumski + + Based on i2ctest.c by: + + Copyright 2008-2011 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 . + */ + +#include +#include +#include +#if __APPLE__ +#include +#else +#include +#endif + +#include +#include + +#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"); +} -- 2.39.5