finished code and testing of functions on AVR 328P.

This commit is contained in:
Jake Goodwin 2023-03-02 21:16:03 -08:00
parent f70de31502
commit 599f5a32d9
4 changed files with 461 additions and 1 deletions

22
AVR_ENCODER.hex Normal file
View File

@ -0,0 +1,22 @@
:100000000C9434000C9446000C9446000C9446006A
:100010000C9446000C9446000C9446000C94460048
:100020000C9446000C9446000C9446000C94460038
:100030000C9446000C9446000C9446000C94460028
:100040000C9446000C9446000C9446000C94460018
:100050000C9446000C9446000C9446000C94460008
:100060000C9446000C94460011241FBECFEFD8E03C
:10007000DEBFCDBF21E0A0E0B1E001C01D92A23003
:10008000B207E1F70E9491000C949F000C940000CD
:10009000389A90E031E09817D1F028B1232728B999
:1000A0002FEF4FE756E1215040405040E1F700C0AC
:1000B000000028B1232728B92FEF4FE756E1215040
:1000C00040405040E1F700C000009F5FE4CF08953A
:1000D00087B1897F87B988B1897F88B9089586B14A
:1000E000867090E09595879520910101281749F039
:1000F0008093010190910001990F990F892B8093B2
:100100000001809100018B3431F0873841F4109266
:10011000000182E008951092000181E0089580E0DE
:1001200008950E94680082E00E9448000E946F00CB
:10013000813019F08230B9F3F9CF81E0F5CFF8942E
:02014000FFCFEF
:00000001FF

217
Makefile Normal file
View File

@ -0,0 +1,217 @@
##########------------------------------------------------------##########
########## Project-specific Details ##########
########## Check these every time you start a new project ##########
##########------------------------------------------------------##########
#MCU = atmega168p
#MCU = atmega2560
#MCU = attiny85
MCU = atmega328p
F_CPU = 14745600UL
#F_CPU = 1000000UL
BAUD = 9600UL
## Also try BAUD = 19200 or 38400 if you're feeling lucky.
## A directory for common include files and the simple USART library.
## If you move either the current folder or the Library folder, you'll
## need to change this path to match.
#LIBDIR = ../../AVR-Programming-Library
#LIBDIR = /usr/lib/gcc/avr/5.4.0/
LIBDIR = /lib/avr/
SUB_MODULES = ./lib/*/src/
##########------------------------------------------------------##########
########## Programmer Defaults ##########
########## Set up once, then forget about it ##########
########## (Can override. See bottom of file.) ##########
##########------------------------------------------------------##########
PROGRAMMER_TYPE = usbasp-clone
# extra arguments to avrdude: baud rate, chip type, -F flag, etc.
PROGRAMMER_ARGS = -b 9600 -B 4 -v
##########------------------------------------------------------##########
########## Program Locations ##########
########## Won't need to change if they're in your PATH ##########
##########------------------------------------------------------##########
CC = avr-gcc
OBJCOPY = avr-objcopy
OBJDUMP = avr-objdump
AVRSIZE = avr-size
AVRDUDE = avrdude
##########------------------------------------------------------##########
########## Makefile Magic! ##########
########## Summary: ##########
########## We want a .hex file ##########
########## Compile source files into .elf ##########
########## Convert .elf file into .hex ##########
########## You shouldn't need to edit below. ##########
##########------------------------------------------------------##########
## The name of your project (without the .c)
# TARGET = blinkLED
## Or name it automatically after the enclosing directory
TARGET = $(lastword $(subst /, ,$(CURDIR)))
# Object files: will find all .c/.h files in current directory
# and in LIBDIR. If you have any other (sub-)directories with code,
# you can add them in to SOURCES below in the wildcard statement.
SOURCES=$(wildcard ./src/*.c $(LIBDIR)/*.c $(SUB_MODULES)/*.c)
OBJECTS=$(SOURCES:.c=.o)
HEADERS=$(SOURCES:.c=.h)
## Compilation options, type man avr-gcc if you're curious.
CPPFLAGS = -DF_CPU=$(F_CPU) -DBAUD=$(BAUD) -I. -I$(LIBDIR)
CFLAGS = -Os -g -std=gnu99 -Wall
## Use short (8-bit) data types
CFLAGS += -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums
## Splits up object files per function
CFLAGS += -ffunction-sections -fdata-sections
LDFLAGS = -Wl,-Map,$(TARGET).map
## Optional, but often ends up with smaller code
LDFLAGS += -Wl,--gc-sections
## Relax shrinks code even more, but makes disassembly messy
## LDFLAGS += -Wl,--relax
## LDFLAGS += -Wl,-u,vfprintf -lprintf_flt -lm ## for floating-point printf
## LDFLAGS += -Wl,-u,vfprintf -lprintf_min ## for smaller printf
TARGET_ARCH = -mmcu=$(MCU)
## Explicit pattern rules:
## To make .o files from .c files
%.o: %.c $(HEADERS) Makefile
$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c -o $@ $<;
$(TARGET).elf: $(OBJECTS)
$(CC) $(LDFLAGS) $(TARGET_ARCH) $^ $(LDLIBS) -o $@
%.hex: %.elf
$(OBJCOPY) -j .text -j .data -O ihex $< $@
%.eeprom: %.elf
$(OBJCOPY) -j .eeprom --change-section-lma .eeprom=0 -O ihex $< $@
%.lst: %.elf
$(OBJDUMP) -S $< > $@
## These targets don't have files named after them
.PHONY: all disassemble disasm eeprom size clean squeaky_clean flash fuses
all: $(TARGET).hex
debug:
@echo
@echo "Source files:" $(SOURCES)
@echo "MCU, F_CPU, BAUD:" $(MCU), $(F_CPU), $(BAUD)
@echo
# Optionally create listing file from .elf
# This creates approximate assembly-language equivalent of your code.
# Useful for debugging time-sensitive bits,
# or making sure the compiler does what you want.
disassemble: $(TARGET).lst
disasm: disassemble
# Optionally show how big the resulting program is
size: $(TARGET).elf
$(AVRSIZE) -C --mcu=$(MCU) $(TARGET).elf
clean:
rm -f $(TARGET).elf $(TARGET).hex $(TARGET).obj \
$(TARGET).o $(TARGET).d $(TARGET).eep $(TARGET).lst \
$(TARGET).lss $(TARGET).sym $(TARGET).map $(TARGET)~ \
$(TARGET).eeprom
squeaky_clean:
rm -f *.elf *.hex *.obj *.o *.d *.eep *.lst *.lss *.sym *.map *~ *.eeprom
##########------------------------------------------------------##########
########## Programmer-specific details ##########
########## Flashing code to AVR using avrdude ##########
##########------------------------------------------------------##########
flash: $(TARGET).hex
$(AVRDUDE) -c $(PROGRAMMER_TYPE) -p $(MCU) $(PROGRAMMER_ARGS) -U flash:w:$<
## An alias
program: flash
flash_eeprom: $(TARGET).eeprom
$(AVRDUDE) -c $(PROGRAMMER_TYPE) -p $(MCU) $(PROGRAMMER_ARGS) -U eeprom:w:$<
avrdude_terminal:
$(AVRDUDE) -c $(PROGRAMMER_TYPE) -p $(MCU) $(PROGRAMMER_ARGS) -nt
## If you've got multiple programmers that you use,
## you can define them here so that it's easy to switch.
## To invoke, use something like `make flash_arduinoISP`
flash_usbtiny: PROGRAMMER_TYPE = usbtiny
flash_usbtiny: PROGRAMMER_ARGS = # USBTiny works with no further arguments
flash_usbtiny: flash
flash_usbasp: PROGRAMMER_TYPE = usbasp
flash_usbasp: PROGRAMMER_ARGS = # USBasp works with no further arguments
flash_usbasp: flash
flash_arduinoISP: PROGRAMMER_TYPE = avrisp
flash_arduinoISP: PROGRAMMER_ARGS = -b 19200 -P /dev/ttyACM0
## (for windows) flash_arduinoISP: PROGRAMMER_ARGS = -b 19200 -P com5
flash_arduinoISP: flash
flash_109: PROGRAMMER_TYPE = avr109
flash_109: PROGRAMMER_ARGS = -b 9600 -P /dev/ttyUSB0
flash_109: flash
##########------------------------------------------------------##########
########## Fuse settings and suitable defaults ##########
##########------------------------------------------------------##########
## Mega 48, 88, 168, 328 default values
#LFUSE=0x62 ##Low Fuse
#HFUSE=0xdf ##High fuses
#EFUSE=0x00 ##
## Mega 328p fuses
LFUSE = 0x62
HFUSE = 0xd9
EFUSE = 0xff
## Generic
FUSE_STRING = -U lfuse:w:$(LFUSE):m -U hfuse:w:$(HFUSE):m -U efuse:w:$(EFUSE):m
fuses:
$(AVRDUDE) -c $(PROGRAMMER_TYPE) -p $(MCU) \
$(PROGRAMMER_ARGS) $(FUSE_STRING)
show_fuses:
$(AVRDUDE) -c $(PROGRAMMER_TYPE) -p $(MCU) $(PROGRAMMER_ARGS) -nv
## Called with no extra definitions, sets to defaults
set_default_fuses: FUSE_STRING = -U lfuse:w:$(LFUSE):m -U hfuse:w:$(HFUSE):m -U efuse:w:$(EFUSE):m
set_default_fuses: fuses
## For external clock low-power crystal.
# The low order fuse is set for a low-power crystal. 14.7456Mhz
# cksel[3:1] 8.0-16.0 = 111 //Sets it for the range of 8Mhz - 16Mhz
# cksel[0] 1 SUT[1:0] 11 //gives 16K ck startup time aprox 65ms
set_crystal_fuses: LFUSE = 0xFF
set_crystal_fuses: FUSE_STRING = -U lfuse:w:$(LFUSE):m
set_crystal_fuses: fuses
## Set the fuse byte for full-speed mode
## Note: can also be set in firmware for modern chips
set_fast_fuse: LFUSE = 0xE2
set_fast_fuse: FUSE_STRING = -U lfuse:w:$(LFUSE):m
set_fast_fuse: fuses
## Set the EESAVE fuse byte to preserve EEPROM across flashes
set_eeprom_save_fuse: HFUSE = 0xD7
set_eeprom_save_fuse: FUSE_STRING = -U hfuse:w:$(HFUSE):m
set_eeprom_save_fuse: fuses
## Clear the EESAVE fuse byte
clear_eeprom_save_fuse: FUSE_STRING = -U hfuse:w:$(HFUSE):m
clear_eeprom_save_fuse: fuses

View File

@ -1,3 +1,47 @@
# AVR_ENCODER
This is small library for rotary encoders often found being used for controls or volume knobs in embedded platforms.
This is small library for rotary encoders often found being used for controls
or volume knobs in embedded platforms.
The recomended way to use the code is by calling the poll_encoder() function
in the C file.
This allows the use of polling for monitoring the state of the encoder. This
should be fine for most applications that don't require interrupts for encoder
use.
The only issue would be if running the AVR at very very low speed and
preforming too many other taskts to the point that the polling loop doesn't
capture the full wave form from the encoder. But this is a well known weakness
in polling.
## Demo Funciton:
```C
//This is just an example function of how you could use it.
int demo() {
init_encoder();
uint8_t volume = 0;
led_blink(2);
while(1) {
switch(poll_encoder()) {
case CW:
if(volume < 100){volume++;}
led_blink(1);
break;
case CCW:
if(volume > 0){volume--;}
led_blink(2);
break;
default:
;//nothing.
}
//Do other polling operations here
}
return 0;
}
```

177
src/rotary_encoder.c Normal file
View File

@ -0,0 +1,177 @@
/*
* Author: Jake Goodwin
* Date: 2023
* Description: Small library for using standard rotary encoders.
*/
/*
* Rotary pin state bits
* | NOUSE |A0 |B0 |A1 |B1 |
* | 0000b | 0 | 0 | 0 | 0 |
*/
/* Rotary encoder mechanical contacts.
* Clockwise sequence:
* | A B | DEC
* t1 | 0 1 | 0x1
* t2 | 0 0 | 0x0
* t3 | 1 0 | 0x2
* t4 | 1 1 | 0x3
*
* Counter-Clockwise sequence:
* | A B | Hex
* t1 | 1 0 | 0x2
* t2 | 0 0 | 0x0
* t3 | 0 1 | 0x1
* t4 | 1 1 | 0x3
*
* If tossed into a 8bit register or var:
* CW: 0b01001011 = 75
* CCW: 0b10000111 = 135
*/
#include <avr/io.h>
#include <stdint.h>
#include <util/delay.h>
// PIN DEFINITIONS
//PUT YOUR PINS IN FOR ENCODER A/B
//Along with pin offset if needed. So i'm using pins 1,2 so because I
//skipped 0 I set the offset too 1.
#define ENCODER_A PINC1
#define ENCODER_B PINC2
#define PIN_OFFSET 1
#define ENCODER_DDR DDRC
#define ENCODER_PORT PORTC
#define INPUT_REG PINC
#define LED_PIN PINC0
//#############################
//Globals
//#############################
//if you want you can replace this with just encoder_byte
//and bitshift/mask to compare it with the pins state, but it's less readable.
uint8_t old_state = 0x0;
uint8_t encoder_byte = 0x0;
//A more friendly way to check state.
enum encoder_dir {
CW = 1,
CCW = 2,
none = 0,
};
//#############################
//FUNCTIONS
//#############################
/*
* Input: None
* Output: None
* Description: Toggles the pin for the LED indicator.
*/
void led_blink(uint8_t x) {
//Set the DDR for output.
DDRC |= (1<<LED_PIN);
for(uint8_t i = 0; i < x; i++) {
PORTC ^= (1<<LED_PIN);
_delay_ms(500);
PORTC ^= (1<<LED_PIN);
_delay_ms(500);
}
}
void init_encoder(void) {
//Then encoder has two outputs, A, B also labeled as clk, dt.
//Set the two pins too monitor digital inputs
ENCODER_DDR &= ~( (1<<ENCODER_A)|(1<<ENCODER_B) );
ENCODER_PORT &= ~( (1<<ENCODER_A)|(1<<ENCODER_B) );
}
/*
* Input: None,
* Output: rotary_state: 0=No change, 1= Clockwise tick, 2= counter clockwise tick.
* Description: checks the rotary encoder for changes in it's state. It updates
* the global encoder byte by taking the state of A and B pins from the encoder
* and shifting their state bits into it 2bits per shift. After four shifts
* the value in the byte should equal 75 or 135 which would mean the encoder
* completed a single step or tick.
*/
uint8_t poll_encoder() {
//in this scope we have defined the state this way
enum state {
CW = 75,
CCW = 135,
};
//clear out old state bits.
uint8_t encoder_state = 0x0;
//mask out the other pins, and deal with the pin offset.
encoder_state |= ( (INPUT_REG & ((1<<ENCODER_A)|(1<<ENCODER_B)) )>> PIN_OFFSET);
//we want to check for the correct sequence of bits.
//we also only want to accept new states as input. Not old states.
if(encoder_state != old_state){
old_state = encoder_state;
encoder_byte = (encoder_byte<<2);
encoder_byte |= encoder_state;
}
switch(encoder_byte) {
case CCW:
encoder_byte = 0x0;
encoder_state= 2;
break;
case CW:
encoder_byte = 0x0;
encoder_state= 1;
break;
default:
encoder_state= 0x0;
break;
}
return encoder_state;
}
//This is just an example function of how you could use it.
int demo() {
init_encoder();
uint8_t volume = 0;
led_blink(2);
while(1) {
switch(poll_encoder()) {
case CW:
if(volume < 100){volume++;}
led_blink(1);
break;
case CCW:
if(volume > 0){volume--;}
led_blink(2);
break;
default:
;//nothing.
}
//Do other polling operations here
}
return 0;
}