2023-12-09 17:14:38 +00:00
|
|
|
/*
|
|
|
|
* FileName: *.c
|
|
|
|
* Date: 2023
|
|
|
|
* Descripton: Program for Atiny13A controllers, controls DPDT relays and
|
|
|
|
* has options to save settings in EEPROM.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifndef MCU
|
|
|
|
#define MCU atiny13a
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef F_CPU
|
|
|
|
#define F_CPU 4800000UL
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
#include <avr/io.h>
|
|
|
|
#include <avr/interrupt.h>
|
|
|
|
#include <util/delay.h>
|
|
|
|
#include <avr/eeprom.h>
|
|
|
|
|
|
|
|
//These are just defined in case for compatability with Arduino
|
|
|
|
#ifndef HIGH
|
|
|
|
#define HIGH 1
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef LOW
|
|
|
|
#define LOW 0
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef UINT8_MAX
|
|
|
|
#define UINT8_MAX 256
|
|
|
|
#endif
|
|
|
|
|
|
|
|
//This is the bitpattern that should be at address 0x00
|
|
|
|
#define START_PATTERN 0xAA
|
|
|
|
#define END_PATTERN 0x55
|
|
|
|
|
|
|
|
//Addresses in the eeprom for the two bypasses
|
|
|
|
#define ROM_SP_ADR 0x0
|
|
|
|
#define ROM_BP1_ADR 0x1
|
|
|
|
#define ROM_BP2_ADR 0x2
|
|
|
|
#define ROM_EP_ADR 0x3
|
|
|
|
|
|
|
|
//Debounce check number.
|
|
|
|
#define MAX_CHECKS 10
|
|
|
|
|
|
|
|
// Define DISABLE_TEMPORARY_SWITCH to turn off the ability to hold
|
|
|
|
// the switch to temporarily engage/bypass.
|
|
|
|
//#define DISABLE_TEMPORARY_SWITCH
|
|
|
|
|
|
|
|
#define PIN_SW2 PINB3
|
|
|
|
#define PIN_BYPASS2 PB4
|
|
|
|
#define PIN_SW1 PINB2
|
|
|
|
#define PIN_BYPASS1 PB1
|
|
|
|
#define PIN_MUTE PB0
|
|
|
|
|
|
|
|
//The BLINK_QTY can be edited from 0-255 blinks
|
|
|
|
#define BLINK_QTY 2
|
|
|
|
#define BLINK_CNT 2 * BLINK_QTY
|
|
|
|
#define BLINK_DELAY 200
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*The timing of "ticks" is dependent on the AVR timer's counter register
|
|
|
|
* so for an 8bit register the maximum value is 256. Given we stick with
|
|
|
|
* a 1Mhz cpu frequency can use this formula to calculate the number of
|
|
|
|
* interrupts(timer overflows) per second:
|
|
|
|
*
|
|
|
|
* OVF_S = (F_CPU / DIV)/ (2^8)
|
|
|
|
* 61 = (1000000 / 64) / 256
|
|
|
|
*
|
|
|
|
* This is important because the ATiny13/A only have a single timer built into
|
|
|
|
* them.
|
|
|
|
*
|
|
|
|
* Ticks are used as our way of keep track of long button presses.
|
|
|
|
* */
|
2024-01-04 01:24:37 +00:00
|
|
|
//original sent had 60
|
|
|
|
#define LONG_PRESS_TICKS 66
|
2023-12-09 17:14:38 +00:00
|
|
|
|
|
|
|
|
|
|
|
/*A structure to hold the button info*/
|
|
|
|
typedef struct {
|
|
|
|
uint8_t is_pressed: 1;
|
|
|
|
uint8_t is_bypassed: 1;
|
|
|
|
uint8_t is_long_pressed: 1;
|
|
|
|
uint8_t timer_enabled: 1;
|
|
|
|
uint8_t pressed_ticks;
|
|
|
|
uint8_t input_pin;
|
|
|
|
uint8_t output_pin;
|
|
|
|
}btn_state;
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ###############################
|
|
|
|
* Global Variables
|
|
|
|
* ###############################
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
/*Create two diffent instances of the button state structure*/
|
|
|
|
btn_state btn1;
|
|
|
|
btn_state btn2;
|
|
|
|
|
|
|
|
/*Some variables for managing the debouncing*/
|
|
|
|
volatile uint8_t debounced_state;
|
|
|
|
volatile uint8_t db_state[MAX_CHECKS];
|
|
|
|
volatile uint8_t idx;
|
|
|
|
|
|
|
|
volatile uint16_t tick_count;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ###############################
|
|
|
|
* FUNCTION PROTOTYPES
|
|
|
|
* ###############################
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void toggle_output(btn_state *b);
|
|
|
|
static inline void init_btn(btn_state *b, uint8_t input_pin, uint8_t output_pin);
|
|
|
|
static void clear_button_timer(btn_state *b);
|
|
|
|
static inline void start_button_timer(btn_state *b);
|
|
|
|
static inline void check_button_longpress(btn_state *b);
|
|
|
|
static void update_button_output(btn_state *b);
|
|
|
|
static void update_button_input(btn_state *b);
|
|
|
|
|
|
|
|
static inline void blink_bypass1(void);
|
|
|
|
static inline void blink_bypass2(void);
|
|
|
|
|
|
|
|
static inline void init_timer0();
|
|
|
|
static inline void debounce_switch();
|
|
|
|
/*
|
|
|
|
* ###############################
|
|
|
|
* SETUP AND LOOP
|
|
|
|
* ###############################
|
|
|
|
*/
|
|
|
|
void init_prog()
|
|
|
|
{
|
|
|
|
/*Set the debounced state to all high*/
|
|
|
|
debounced_state = 0xFF;
|
|
|
|
|
|
|
|
/*Configures the PINS for the input and output.*/
|
|
|
|
init_btn(&btn1, PIN_SW1, PIN_BYPASS1);
|
|
|
|
init_btn(&btn2, PIN_SW2, PIN_BYPASS2);
|
|
|
|
|
|
|
|
/*Wait 5ms for pull-up resistors voltage to become stable.*/
|
|
|
|
_delay_ms(5);
|
|
|
|
|
|
|
|
/*check if the eeprom has info. */
|
|
|
|
while(! eeprom_is_ready()) {} //Blocks until eeprom is ready.
|
|
|
|
|
|
|
|
//Checks against a bit pattern we defined to represent the start of data.
|
|
|
|
if(eeprom_read_byte((uint8_t *)ROM_SP_ADR) == START_PATTERN) {
|
|
|
|
//Reads the two bytes representing the two states.
|
|
|
|
btn1.is_bypassed = eeprom_read_byte((uint8_t *)ROM_BP1_ADR);
|
|
|
|
btn2.is_bypassed = eeprom_read_byte((uint8_t *)ROM_BP2_ADR);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
//otherwise we write the init values for the start pattern and bypass states.
|
|
|
|
eeprom_write_byte((uint8_t *)ROM_SP_ADR, START_PATTERN);
|
|
|
|
eeprom_write_byte((uint8_t *)ROM_BP1_ADR, 0x0);
|
|
|
|
eeprom_write_byte((uint8_t *)ROM_BP2_ADR, 0x0);
|
|
|
|
}
|
|
|
|
|
|
|
|
btn1.is_pressed = ((PINB & (1<<btn1.input_pin)) == 0);
|
|
|
|
btn2.is_pressed = ((PINB & (1<<btn2.input_pin)) == 0);
|
|
|
|
|
|
|
|
|
|
|
|
/*This is to read if the user wants to change the saved states.*/
|
|
|
|
/*Manually read the current switch state*/
|
|
|
|
if(btn1.is_pressed){
|
|
|
|
btn1.is_bypassed = ! btn1.is_bypassed;
|
|
|
|
eeprom_write_byte((uint8_t *)ROM_BP1_ADR, btn1.is_bypassed);
|
|
|
|
blink_bypass1();
|
|
|
|
}
|
|
|
|
if(btn2.is_pressed){
|
|
|
|
btn2.is_bypassed = ! btn2.is_bypassed;
|
|
|
|
eeprom_write_byte((uint8_t *)ROM_BP2_ADR, btn2.is_bypassed);
|
|
|
|
blink_bypass2();
|
|
|
|
}
|
|
|
|
|
|
|
|
if(btn1.is_bypassed){PORTB |= (1<<btn1.output_pin);}
|
|
|
|
if(btn2.is_bypassed){PORTB |= (1<<btn2.output_pin);}
|
|
|
|
|
|
|
|
init_timer0();
|
|
|
|
}
|
|
|
|
|
|
|
|
int main()
|
|
|
|
{
|
|
|
|
|
|
|
|
init_prog();
|
|
|
|
|
|
|
|
while(1){
|
|
|
|
update_button_output(&btn1);
|
|
|
|
update_button_output(&btn2);
|
|
|
|
|
|
|
|
update_button_input(&btn1);
|
|
|
|
update_button_input(&btn2);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ###############################
|
|
|
|
* FUNCTION DEFs
|
|
|
|
* ###############################
|
|
|
|
*/
|
|
|
|
|
|
|
|
void inf_blink_test(){
|
|
|
|
DDRB |= (1<<PB3);
|
|
|
|
while(1){
|
|
|
|
PORTB ^= (1<<PB3);
|
|
|
|
_delay_ms(250);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//Made heavy use of static inline functions to improve readability.
|
|
|
|
|
|
|
|
static inline void blink_bypass1(void)
|
|
|
|
{
|
|
|
|
for(uint8_t i = 0; i < BLINK_CNT; i++) {
|
|
|
|
PORTB ^= (1<<PIN_BYPASS1);
|
|
|
|
_delay_ms(BLINK_DELAY);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void blink_bypass2(void)
|
|
|
|
{
|
|
|
|
for(uint8_t i = 0; i < BLINK_CNT; i++) {
|
|
|
|
PORTB ^= (1<<PIN_BYPASS2);
|
|
|
|
_delay_ms(BLINK_DELAY);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ############################
|
|
|
|
* BUTTON methods
|
|
|
|
* ############################
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*This is kinda like our button constructor*/
|
|
|
|
static void init_btn(btn_state *b, uint8_t input_pin, uint8_t output_pin)
|
|
|
|
{
|
|
|
|
b->is_long_pressed = 0;
|
|
|
|
b->is_pressed = 0;
|
|
|
|
b->is_bypassed = 0;
|
|
|
|
b->pressed_ticks = 0;
|
|
|
|
b->timer_enabled = 0;
|
|
|
|
b->input_pin = input_pin;
|
|
|
|
b->output_pin = output_pin;
|
|
|
|
|
|
|
|
/*Configure the buttons inputs and outputs*/
|
|
|
|
DDRB &= ~(1<<b->input_pin);
|
|
|
|
PORTB |= (1<<b->input_pin);
|
|
|
|
|
|
|
|
DDRB |= (1<<b->output_pin);
|
|
|
|
PORTB &= ~(1<<b->output_pin);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*This is the fancy function to toggle output pins*/
|
|
|
|
static void toggle_output(btn_state *b)
|
|
|
|
{
|
|
|
|
if(!b->is_bypassed){
|
|
|
|
b->is_bypassed = 1;
|
|
|
|
PORTB |= (1<<b->output_pin);
|
|
|
|
}
|
|
|
|
else{
|
|
|
|
b->is_bypassed = 0;
|
|
|
|
PORTB &= ~(1<<b->output_pin);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void clear_button_timer(btn_state *b)
|
|
|
|
{
|
|
|
|
b->timer_enabled = 0;
|
|
|
|
b->is_long_pressed = 0;
|
|
|
|
b->pressed_ticks = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void start_button_timer(btn_state *b)
|
|
|
|
{
|
|
|
|
clear_button_timer(b);
|
|
|
|
b->timer_enabled = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void check_button_longpress(btn_state *b)
|
|
|
|
{
|
|
|
|
if(b->pressed_ticks >= LONG_PRESS_TICKS) {
|
|
|
|
b->is_long_pressed = 1;
|
|
|
|
b->timer_enabled = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void update_button_input(btn_state *b)
|
|
|
|
{
|
|
|
|
/*Check from the global debounced port input*/
|
|
|
|
|
|
|
|
/*check for pin HIGH*/
|
|
|
|
if(debounced_state & (1<<b->input_pin)) {
|
|
|
|
b->is_pressed = 0;
|
|
|
|
}
|
|
|
|
/*otherwise assume pin LOW*/
|
|
|
|
else{
|
|
|
|
b->is_pressed = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void update_button_output(btn_state *b)
|
|
|
|
{
|
|
|
|
/*If the button is actually pressed.*/
|
|
|
|
if(b->is_pressed){
|
|
|
|
|
|
|
|
/*If this is a new event.*/
|
|
|
|
if(!b->is_long_pressed && !b->timer_enabled){
|
|
|
|
/*Then start the timer and update the output*/
|
|
|
|
toggle_output(b);
|
|
|
|
start_button_timer(b);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*If the timer is already going.*/
|
|
|
|
else if(b->timer_enabled){
|
|
|
|
/*Then just check if it's hit the threshold.*/
|
|
|
|
check_button_longpress(b);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
else{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*Else the button was realeased*/
|
|
|
|
else if(!b->is_pressed){
|
|
|
|
/*If the button was released on a long press.*/
|
|
|
|
if(b->is_long_pressed){
|
|
|
|
toggle_output(b);
|
|
|
|
}
|
|
|
|
clear_button_timer(b);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ############################
|
|
|
|
* DEBOUNCING CODE
|
|
|
|
* ############################
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* INPUT: The port to check.
|
|
|
|
* OUTPUT: None
|
|
|
|
* DESCRIPTION: Updates the global debounced state. This function
|
|
|
|
* should be called in a ISR a set rate using the hardware timer.
|
|
|
|
*/
|
|
|
|
static inline void debounce_switch() {
|
|
|
|
uint8_t i, j;
|
|
|
|
db_state[idx] = PINB & 0xFF;
|
|
|
|
idx+=1;
|
|
|
|
j = 0xff;
|
|
|
|
/*Loop through a number of checks*/
|
|
|
|
for(i = 0; i < MAX_CHECKS; i++) {
|
|
|
|
j = j & db_state[i];
|
|
|
|
}
|
|
|
|
debounced_state = j;
|
|
|
|
|
|
|
|
if(idx >= MAX_CHECKS) {
|
|
|
|
idx = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*Setup the timer0 on the AVR*/
|
|
|
|
static inline void init_timer0() {
|
|
|
|
/*Zero out the tick_count var*/
|
|
|
|
tick_count = 0;
|
|
|
|
|
|
|
|
/*config to normal mode.*/
|
|
|
|
TCCR0A = 0x00; //stop timer
|
|
|
|
TCCR0B = 0x00; //zero timer
|
|
|
|
|
|
|
|
/*set prescaler*/
|
2024-01-04 01:24:37 +00:00
|
|
|
//Set to div64
|
2023-12-09 17:14:38 +00:00
|
|
|
TCCR0B |= (1<<CS01)|(1<<CS00);
|
|
|
|
|
|
|
|
|
|
|
|
/*Enable global interrupts*/
|
|
|
|
sei();
|
|
|
|
|
|
|
|
/*Enabling timer0 interrupt*/
|
|
|
|
TCNT0 = 0;
|
|
|
|
TIMSK0 |= (1<<TOIE0);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//ISR(TIMER0_OVF_vect)
|
|
|
|
|
|
|
|
/*The interrupt service routine for the timer0*/
|
|
|
|
ISR(TIM0_OVF_vect)
|
|
|
|
{
|
|
|
|
/*Disable global interrupts*/
|
|
|
|
cli();
|
|
|
|
|
2024-01-04 01:24:37 +00:00
|
|
|
/*Check the state of the switches*/
|
|
|
|
debounce_switch();
|
|
|
|
|
2023-12-09 17:14:38 +00:00
|
|
|
/*Update the tick_count*/
|
|
|
|
if(btn1.timer_enabled && btn1.pressed_ticks <= UINT8_MAX){
|
|
|
|
btn1.pressed_ticks += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(btn2.timer_enabled && btn2.pressed_ticks <= UINT8_MAX){
|
|
|
|
btn2.pressed_ticks += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*Re-Enable global interrupts*/
|
|
|
|
sei();
|
|
|
|
}
|