-#----------------------------------------------------------------------------
-#
-# Makefile based on the public domain Makefile by Peter Fleury
-#
-#----------------------------------------------------------------------------
-# On command line:
-#
-# make all = Make software.
-#
-# make clean = Clean out built project files.
-#
-# make coff = Convert ELF to AVR COFF.
-#
-# make extcoff = Convert ELF to AVR Extended COFF.
-#
-# make program = Download the hex file to the device, using avrdude.
-# Please customize the avrdude settings below first!
-#
-# make debug = Start either simulavr or avarice as specified for debugging,
-# with avr-gdb or avr-insight as the front end for debugging.
-#
-# make filename.s = Just compile filename.c into the assembler code only.
-#
-# make filename.i = Create a preprocessed source file for use in submitting
-# bug reports to the GCC project.
-#
-# To rebuild project do "make clean" then "make all".
-#----------------------------------------------------------------------------
-
-CONFIG?=trennfix_0.4
-
-include mk/${CONFIG}.mk
-
-INCLUDES+=-I mm/include -I include
-SRC+= mm/src/mm_switch.c
-CFLAGS+=-D__HW_CONF_HEADER__="<config/${CONFIG}.h>"
-
-# MCU name
-#MCU ?= atmega328
-
-# Processor frequency.
-# This will define a symbol, F_CPU, in all source code files equal to the
-# processor frequency. You can then use this symbol in your source code to
-# calculate timings. Do NOT tack on a 'UL' at the end, this will be done
-# automatically to create a 32-bit value in your source code.
-#F_CPU ?= 20000000
-
-
-# Output format. (can be srec, ihex, binary)
-FORMAT = ihex
-
-
-# Target file name (without extension).
-TARGET = trennfix
-
-# List C source files here. (C dependencies are automatically generated.)
-#SRC ?= main.c
-
-
-ASRC =
-
-# Optimization level, can be [0, 1, 2, 3, s].
-# 0 = turn off optimization. s = optimize for size.
-# (Note: 3 is not always the best optimization level. See avr-libc FAQ.)
-OPT = s
-
-
-# Debugging format.
-# Native formats for AVR-GCC's -g are dwarf-2 [default] or stabs.
-# AVR Studio 4.10 requires dwarf-2.
-# AVR [Extended] COFF format requires stabs, plus an avr-objcopy run.
-DEBUG = dwarf-2
+#######################################
-# List any extra directories to look for include files here.
-# Each directory must be seperated by a space.
-# Use forward slashes for directory separators.
-# For a directory that has spaces, enclose it in quotes.
-EXTRAINCDIRS =
+HW?=trennfix_0.4
+PROG?=smokefix
+#######################################
-# Compiler flag to set the C Standard level.
-# c89 = "ANSI" C
-# gnu89 = c89 plus GCC extensions
-# c99 = ISO C99 standard (not yet fully implemented)
-# gnu99 = c99 plus GCC extensions
-CSTANDARD = -std=gnu99
-
-
-# Place -D or -U options here
-CDEFS = -DF_CPU=$(F_CPU)UL -DBOOT_START=${BOOT_START} -DBOOT_PAGES=${BOOT_PAGES}
+#######################################
+# VERBOSITY CONTROL
+ifeq ($(V),)
+Q=@
+else
+Q=
+endif
-# Place -I options here
-CINCS =
+include mk/hw/${HW}.mk
+include mk/prog/*.mk
-# Bootloader LDFLAGS
-#BOOT_LDFLAGS=-Wl,-Ttext=${BOOT_START}
+INCLUDES+=-I mm/include -I include -I.
+CFLAGS+=-D__HW_CONF_HEADER__="<config/${CONFIG}.h>"
-#---------------- Compiler Options ----------------
-# -g*: generate debugging information
-# -O*: optimization level
-# -f...: tuning, see GCC manual and avr-libc documentation
-# -Wall...: warning level
-# -Wa,...: tell GCC to pass this to the assembler.
-# -adhlns...: create assembler listing
-CFLAGS += -g$(DEBUG)
-CFLAGS += $(CDEFS) $(CINCS)
-CFLAGS += -O$(OPT)
+CFLAGS += -mmcu=$(MCU) -DF_CPU=$(F_CPU)UL
+CFLAGS += -std=gnu99
CFLAGS += -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums
CFLAGS += -Wall -Wstrict-prototypes
+CFLAGS += -Os -gdwarf-2
CFLAGS += -Wa,-adhlns=$(<:.c=.lst)
-CFLAGS += $(patsubst %,-I%,$(EXTRAINCDIRS))
-CFLAGS += $(CSTANDARD)
+CFLAGS += -MD $(@:.c:.d)
CFLAGS += $(INCLUDES)
-
-#---------------- Assembler Options ----------------
-# -Wa,...: tell GCC to pass this to the assembler.
-# -ahlms: create listing
-# -gstabs: have the assembler create line number information; note that
-# for use in COFF files, additional information about filenames
-# and function names needs to be present in the assembler source
-# files -- see avr-libc docs [FIXME: not yet described there]
-ASFLAGS = -Wa,-adhlns=$(<:.S=.lst),-gstabs
-
+ASFLAGS += -Wa,-adhlns=$(<:.S=.lst),-gstabs -mmcu=$(MCU)
+ASFLAGS += -x assembler-with-cpp
#---------------- Library Options ----------------
# Minimalistic printf version
#PRINTF_LIB = $(PRINTF_LIB_MIN)
#PRINTF_LIB = $(PRINTF_LIB_FLOAT)
-
# Minimalistic scanf version
SCANF_LIB_MIN = -Wl,-u,vfscanf -lscanf_min
#SCANF_LIB = $(SCANF_LIB_MIN)
#SCANF_LIB = $(SCANF_LIB_FLOAT)
-
-MATH_LIB = -lm
-
-
-#---------------- External Memory Options ----------------
-
-# 64 KB of external RAM, starting after internal RAM (ATmega128!),
-# used for variables (.data/.bss) and heap (malloc()).
-#EXTMEMOPTS = -Wl,-Tdata=0x801100,--defsym=__heap_end=0x80ffff
-
-# 64 KB of external RAM, starting after internal RAM (ATmega128!),
-# only used for heap (malloc()).
-#EXTMEMOPTS = -Wl,--defsym=__heap_start=0x801100,--defsym=__heap_end=0x80ffff
-
-EXTMEMOPTS =
-
-
-
-#---------------- Linker Options ----------------
-# -Wl,...: tell GCC to pass this to linker.
-# -Map: create map file
-# --cref: add cross reference to map file
-LDFLAGS = $(BOOT_LDFLAGS) -Wl,-Map=$(TARGET).map,--cref
-LDFLAGS += $(EXTMEMOPTS)
-LDFLAGS += $(PRINTF_LIB) $(SCANF_LIB) $(MATH_LIB)
-
-
+LDFLAGS = -Wl,-Map=$(@:.elf=.map),--cref
+LIBS += $(PRINTF_LIB) $(SCANF_LIB) $(MATH_LIB)
#---------------- Programming Options (avrdude) ----------------
# Type: avrdude -c ?
# to get a full listing.
#
-
AVRDUDE_PROGRAMMER ?= avrisp2
-
# com1 = serial port. Use lpt1 to connect to parallel port.
AVRDUDE_PORT ?= usb
-AVRDUDE_WRITE_FLASH = -U flash:w:$(TARGET).hex
-#AVRDUDE_WRITE_EEPROM = -U eeprom:w:$(TARGET).eep
-
AVRDUDE_WRITE_FUSES = -U lfuse:w:${LFUSE}:m -U hfuse:w:${HFUSE}:m \
-U efuse:w:${EFUSE}:m
AVRDUDE_FLAGS += $(AVRDUDE_NO_VERIFY)
AVRDUDE_FLAGS += $(AVRDUDE_VERBOSE)
AVRDUDE_FLAGS += $(AVRDUDE_ERASE_COUNTER)
-AVRDUDE_FLAGS += -B 6
-
-
-#---------------- Debugging Options ----------------
-
-# For simulavr only - target MCU frequency.
-DEBUG_MFREQ = $(F_CPU)
-
-# Set the DEBUG_UI to either gdb or insight.
-# DEBUG_UI = gdb
-DEBUG_UI = insight
-
-# Set the debugging back-end to either avarice, simulavr.
-DEBUG_BACKEND = avarice
-#DEBUG_BACKEND = simulavr
-
-# GDB Init Filename.
-GDBINIT_FILE = __avr_gdbinit
-
-# When using avarice settings for the JTAG
-JTAG_DEV = /dev/com1
-
-# Debugging port used to communicate between GDB / avarice / simulavr.
-DEBUG_PORT = 4242
-
-# Debugging host used to communicate between GDB / avarice / simulavr, normally
-# just set to localhost unless doing some sort of crazy debugging when
-# avarice is running on a different computer.
-DEBUG_HOST = localhost
-
-
-
-#============================================================================
-
+AVRDUDE_FLAGS += -B 5
# Define programs and commands.
SHELL = sh
SIZE = avr-size
NM = avr-nm
AVRDUDE = avrdude
-REMOVE = rm -rf
-COPY = cp
-WINSHELL = cmd
-
-
-# Define Messages
-# English
-MSG_ERRORS_NONE = Errors: none
-MSG_BEGIN = -------- begin --------
-MSG_END = -------- end --------
-MSG_SIZE_BEFORE = Size before:
-MSG_SIZE_AFTER = Size after:
-MSG_COFF = Converting to AVR COFF:
-MSG_EXTENDED_COFF = Converting to AVR Extended COFF:
-MSG_FLASH = Creating load file for Flash:
-MSG_EEPROM = Creating load file for EEPROM:
-MSG_EXTENDED_LISTING = Creating Extended Listing:
-MSG_SYMBOL_TABLE = Creating Symbol Table:
-MSG_LINKING = Linking:
-MSG_COMPILING = Compiling:
-MSG_ASSEMBLING = Assembling:
-MSG_CLEANING = Cleaning project:
-
-
-# Define all object files.
-OBJ = $(SRC:.c=.o) $(ASRC:.S=.o)
-
-# Define all listing files.
-LST = $(SRC:.c=.lst) $(ASRC:.S=.lst)
-
-
-# Compiler flags to generate dependency files.
-GENDEPFLAGS = -MD -MP -MF .dep/$(@F).d
-
-
-# Combine all necessary flags and optional flags.
-# Add target processor to flags.
-ALL_CFLAGS = -mmcu=$(MCU) -I. $(CFLAGS) $(GENDEPFLAGS)
-ALL_ASFLAGS = -mmcu=$(MCU) -I. -x assembler-with-cpp $(ASFLAGS)
-
-
-# Default target.
-all: begin gccversion sizebefore build sizeafter end
-
-build: elf hex eep lss sym
-
-elf: $(TARGET).elf
-hex: $(TARGET).hex
-eep: $(TARGET).eep
-lss: $(TARGET).lss
-sym: $(TARGET).sym
-
-
-# Eye candy.
-# AVR Studio 3.x does not check make's exit code but relies on
-# the following magic strings to be generated by the compile job.
-begin:
- @echo
- @echo $(MSG_BEGIN)
-
-end:
- @echo $(MSG_END)
- @echo
-
+RM = rm -rf
-# Display size of file.
-HEXSIZE = $(SIZE) --target=$(FORMAT) $(TARGET).hex
-ELFSIZE = $(SIZE) -A $(TARGET).elf
-AVRMEM = avr-mem.sh $(TARGET).elf $(MCU)
+define SELECT_DEFAULT_template =
+elf: $(1).elf
+hex: $(1).hex
+eep: $(1).eep
+lss: $(1).lss
+sym: $(1).sym
+clean: $(1)_clean
+build: $(1)_build
+program: $(1)_program
+fuses: $(1)_fuses
+endef
-sizebefore:
- @if test -f $(TARGET).elf; then echo; echo $(MSG_SIZE_BEFORE); $(ELFSIZE); \
- $(AVRMEM) 2>/dev/null; echo; fi
+all: build
-sizeafter:
- @if test -f $(TARGET).elf; then echo; echo $(MSG_SIZE_AFTER); $(ELFSIZE); \
- $(AVRMEM) 2>/dev/null; echo; fi
-
-
-
-# Display compiler version information.
-gccversion :
- @$(CC) --version
-
-
-
-# Program the device.
-program: $(TARGET).hex $(TARGET).eep
- $(AVRDUDE) $(AVRDUDE_FLAGS) $(AVRDUDE_WRITE_FLASH) $(AVRDUDE_WRITE_EEPROM)
-
-fuses:
- $(AVRDUDE) $(AVRDUDE_FLAGS) $(AVRDUDE_WRITE_FUSES)
-
-# Generate avr-gdb config/init file which does the following:
-# define the reset signal, load the target file, connect to target, and set
-# a breakpoint at main().
-gdb-config:
- @$(REMOVE) $(GDBINIT_FILE)
- @echo define reset >> $(GDBINIT_FILE)
- @echo SIGNAL SIGHUP >> $(GDBINIT_FILE)
- @echo end >> $(GDBINIT_FILE)
- @echo file $(TARGET).elf >> $(GDBINIT_FILE)
- @echo target remote $(DEBUG_HOST):$(DEBUG_PORT) >> $(GDBINIT_FILE)
-ifeq ($(DEBUG_BACKEND),simulavr)
- @echo load >> $(GDBINIT_FILE)
-endif
- @echo break main >> $(GDBINIT_FILE)
-
-debug: gdb-config $(TARGET).elf
-ifeq ($(DEBUG_BACKEND), avarice)
- @echo Starting AVaRICE - Press enter when "waiting to connect" message displays.
- @$(WINSHELL) /c start avarice --jtag $(JTAG_DEV) --erase --program --file \
- $(TARGET).elf $(DEBUG_HOST):$(DEBUG_PORT)
- @$(WINSHELL) /c pause
-
-else
- @$(WINSHELL) /c start simulavr --gdbserver --device $(MCU) --clock-freq \
- $(DEBUG_MFREQ) --port $(DEBUG_PORT)
-endif
- @$(WINSHELL) /c start avr-$(DEBUG_UI) --command=$(GDBINIT_FILE)
-
-
-# Convert ELF to COFF for use in debugging / simulating in AVR Studio or VMLAB.
-COFFCONVERT=$(OBJCOPY) --debugging \
---change-section-address .data-0x800000 \
---change-section-address .bss-0x800000 \
---change-section-address .noinit-0x800000 \
---change-section-address .eeprom-0x810000
-
-
-coff: $(TARGET).elf
- @echo
- @echo $(MSG_COFF) $(TARGET).cof
- $(COFFCONVERT) -O coff-avr $< $(TARGET).cof
-
-
-extcoff: $(TARGET).elf
- @echo
- @echo $(MSG_EXTENDED_COFF) $(TARGET).cof
- $(COFFCONVERT) -O coff-ext-avr $< $(TARGET).cof
+cleanall: $(foreach prog, $(PROGRAMS), $(prog)_clean)
+buildall: $(foreach prog, $(PROGRAMS), $(prog)_build)
+.PHONY: clean build program cleanall buildall all
+$(eval $(call SELECT_DEFAULT_template, $(PROG)))
# Create final output files (.hex, .eep) from ELF output file.
%.hex: %.elf
- @echo
- @echo $(MSG_FLASH) $@
- $(OBJCOPY) -O $(FORMAT) -R .eeprom $< $@
+ @echo HEX $@
+ $(Q)$(OBJCOPY) -O ihex -R .fuse -R .eeprom $< $@
%.eep: %.elf
- @echo
- @echo $(MSG_EEPROM) $@
- -$(OBJCOPY) -j .eeprom --set-section-flags=.eeprom="alloc,load" \
- --change-section-lma .eeprom=0 -O $(FORMAT) $< $@
+ @echo EEP $@
+ $(Q)$(OBJCOPY) -j .eeprom --set-section-flags=.eeprom="alloc,load" \
+ --change-section-lma .eeprom=0 -O ihex $< $@
# Create extended listing file from ELF output file.
%.lss: %.elf
- @echo
- @echo $(MSG_EXTENDED_LISTING) $@
- $(OBJDUMP) -h -S $< > $@
+ @echo LSS $@
+ $(Q)$(OBJDUMP) -h -S $< > $@
# Create a symbol table from ELF output file.
%.sym: %.elf
- @echo
- @echo $(MSG_SYMBOL_TABLE) $@
- $(NM) -n $< > $@
-
-
+ @echo SYM $@
+ $(Q)$(NM) -n $< > $@
# Link: create ELF output file from object files.
-.SECONDARY : $(TARGET).elf
-.PRECIOUS : $(OBJ)
%.elf: $(OBJ)
- @echo
- @echo $(MSG_LINKING) $@
- $(CC) $(ALL_CFLAGS) $^ --output $@ $(LDFLAGS)
-
+ @echo LD $@
+ $(Q)$(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)
# Compile: create object files from C source files.
%.o : %.c Makefile
- @echo
- @echo $(MSG_COMPILING) $<
- $(CC) -c $(ALL_CFLAGS) $< -o $@
-
+ @echo CC $@
+ $(Q)$(CC) -c $(CFLAGS) $< -o $@
# Compile: create assembler files from C source files.
%.s : %.c Makefile
- $(CC) -S $(ALL_CFLAGS) $< -o $@
-
+ @echo CC-ASM $<
+ $(Q)$(CC) -S $(CFLAGS) $< -o $@
# Assemble: create object files from assembler source files.
%.o : %.S
- @echo
- @echo $(MSG_ASSEMBLING) $<
- $(CC) -c $(ALL_ASFLAGS) $< -o $@
+ @echo ASM $@
+ $(Q)$(CC) -c $(ASFLAGS) $< -o $@
-# Create preprocessed source for use in sending a bug report.
-%.i : %.c
- $(CC) -E -mmcu=$(MCU) -I. $(CFLAGS) $< -o $@
+########################### The big template for each program ##################
+define PROGRAM_template=
+# Determine program's object files
+$(1)_OBJS=$$($(1)_SRC:.c=.o)
+$(1)_OBJS+=$$($(1)_ASRC:.S=.o)
-# Target: clean project.
-clean: begin clean_list end
+# Collect all listing files - for cleanup
+$(1)_LST += $$($(1)_SRC:.c=.lst) $$($(1)_ASRC:.S=.lst)
-clean_list :
- @echo
- @echo $(MSG_CLEANING)
- $(REMOVE) $(TARGET).hex
- $(REMOVE) $(TARGET).eep
- $(REMOVE) $(TARGET).cof
- $(REMOVE) $(TARGET).elf
- $(REMOVE) $(TARGET).map
- $(REMOVE) $(TARGET).sym
- $(REMOVE) $(TARGET).lss
- $(REMOVE) $(OBJ)
- $(REMOVE) $(LST)
- $(REMOVE) $(SRC:.c=.s)
- $(REMOVE) $(SRC:.c=.d)
- $(REMOVE) .dep
- $(REMOVE) *~
+$(1): $(1).elf
+$(1).elf: $$($(1)_OBJS)
+$(1)_program : $(1).hex $(1).eep
+ @echo PROG $(1)
+ $(Q)$(AVRDUDE) $(AVRDUDE_FLAGS) -U flash:w:$(1).hex -U eeprom:w:$(1).eep
+$(1)_unfug:
+ @echo UNFUG $$@
-# Include the dependency files.
--include $(shell mkdir .dep 2>/dev/null) $(wildcard .dep/*)
+$(1)_fuses.bin: $(1).elf
+ @echo OBJCOPY $$@
+ $(Q)$(OBJCOPY) -j .fuse $(1).elf -O binary $$@
+ $(Q)chmod -x $$@
+$(1)_fuses: $(1)_fuses.bin
+ @echo FUSES $$@
+ $(Q)$(AVRDUDE) $(AVRDUDE_FLAGS)\
+ -U lfuse:w:0x$$$$(hd $(1)_fuses.bin| awk '{print $$$$2}'):m \
+ -U hfuse:w:0x$$$$(hd $(1)_fuses.bin| awk '{print $$$$3}'):m \
+ -U efuse:w:0x$$$$(hd $(1)_fuses.bin| head -n1 |awk '{print $$$$4}'):m
-# Listing of phony targets.
-.PHONY : all begin finish end sizebefore sizeafter gccversion \
-build elf hex eep lss sym coff extcoff \
-clean clean_list program debug gdb-config
+$(1)_size: $(1).elf
+ @echo; echo
+ $(Q) $(SIZE) -A $(1).elf
+# Target: clean project.
+$(1)_clean:
+ @echo CLEAN $(1)
+ $(Q)$(RM) $(1).hex
+ $(Q)$(RM) $(1).eep
+ $(Q)$(RM) $(1).cof
+ $(Q)$(RM) $(1).elf
+ $(Q)$(RM) $(1).map
+ $(Q)$(RM) $(1).sym
+ $(Q)$(RM) $(1).lss
+ $(Q)$(RM) $$($(1)_OBJS)
+ $(Q)$(RM) $$($(1)_LST)
+ $(Q)$(RM) $$($(1)_SRC:.c=.s)
+ $(Q)$(RM) $$($(1)_SRC:.c=.d)
+ @$(Q)$(RM) $(1)_fuses.bin
+
+$(1)_build: $(1).hex $(1)_size
+
+-include $$($(1)_OBJS:.o=.d)
+
+.PRECIOUS : $$($(1)_OBJS)
+.PHONY: $(1) $(1)_clean $(1)_size $(1)_build
+
+#############
+endef
+################################################################################
+
+$(foreach prog,$(PROGRAMS),$(eval $(call PROGRAM_template,$(prog))))
#define STOP_TIMER1 ({TCCR1 = 0; TCNT1 = 1;})
-#define PWM_CYCLE 21
+#define PWM_CYCLE 14
static inline void setup_hw(void)
{
CLKPR = (1 << CLKPCE);
CLKPR = 0;
+ setpin(PIN_LED, 0);
+ setpin(PIN_TRIGGER, 0);
+ setpin(PIN_BTN, 1); /* Need pullup */
INPUT_PIN(PIN_SENSE);
INPUT_PIN(PIN_BTN);
OUTPUT_PIN(PIN_DRIVE);
OUTPUT_PIN(PIN_LED);
OUTPUT_PIN(PIN_TRIGGER);
- setpin(PIN_LED, 0);
- setpin(PIN_TRIGGER, 0);
-
- setpin(PIN_BTN, 1); /* Need pullup */
-
+
GIMSK |= _BV(PCIE); /* Enable pin change interrupt for sense port */
PCMSK |= _BV(PCINT4); /* PB4, Rail sense input */
/* Setup timer 0, used for mm_switch */
TCCR0A = 0; /* Normal mode */
TCCR0B = 2; /* Prescaler 8 */
- // TIMSK |= _BV(OCIE0A); /* Get a match interrupt */
+
TIMSK |= _BV(TOIE0);
- OCR0A = 100;
TCCR0B = 2;
-#ifdef WITH_PWM
+#ifdef WITH_PWM
/* Timer 1 as PWM */
OCR1C = PWM_CYCLE;
OCR1B = PWM_CYCLE;
setpin(PIN_TRIGGER, 0);
}
-
static inline void __attribute__((unused)) trigger_on(void)
{
setpin(PIN_TRIGGER, 1);
}
#define MM_RESETFLAG TIFR |= _BV(OVF0)
-
+//#define MM_USE_QUEUE
+#define MM_QUEUE_DEPTH 8
+#define MM_USE_CALLBACK
#ifdef USE_REGISTER_VARS
uint8_t register drive_on asm("r12");
--- /dev/null
+AVRDUDE_PROGRAMMER = avrisp2
+MCU?=attiny25
+F_CPU = 16000000
+
+CFLAGS+=-D__trennfix_0_4__
+
+CFLAGS+= -DDEBUG_DRIVE_LED
+CFLAGS+= -DUSE_REGISTER_VARS
+CFLAGS+= -DMM_USE_REGISTER_VARS
+
+CFLAGS+= -DWITH_EEPROM_UPDATE
+CFLAGS+= -DWITH_PWM
+CFLAGS+= -DWITH_SOUND
+CFLAGS+= -DWITH_INITIAL_PULSE
+
+
--- /dev/null
+
+PROGRAMS+=smokefix
+smokefix_SRC=src/smokefix_main.c mm/src/mm_switch.c
--- /dev/null
+
+PROGRAMS+=trennfix
+trennfix_SRC=src/trennfix_main.c mm/src/mm_switch.c
+++ /dev/null
-
-SRC = src/main.c
-
-################################################################################
-################################################################################
-
-AVRDUDE_PROGRAMMER = avrisp2
-MCU?=attiny25
-F_CPU = 16000000
-
-# Fuse-Bits gibts praktisch bei http://www.engbedded.com/fusecalc !
-# 8 MHz interner RC-Oszillator usw. Brown out 4V
-
-ifneq ($(KEEP_EEPROM),)
-
-HFUSE=0xd4
-
-else
-
-HFUSE=0xdc
-
-endif
-
-LFUSE=0x61
-EFUSE=0xff
-
-CFLAGS+= -DUSE_REGISTER_VARS
-CFLAGS+= -DMM_USE_REGISTER_VARS
-CFLAGS+= -DWITH_EEPROM_UPDATE
-#CFLAGS+= -DWITH_PWM
-CFLAGS+= -DWITH_SOUND
-CFLAGS+= -DWITH_INITIAL_PULSE
-
--- /dev/null
+#ifndef __MM_DECODE_H
+#define __MM_DECODE_H
+
+/*
+ * Lookup trinary nibble
+ *
+ * This was implemented using a switch statement before.
+ * Changing the lookup to a table did only add two bytes
+ * of memory and saved ca. 50 bytes program memory.
+ */
+static const uint8_t __mm_nibble_table[16]={
+ [0x0] = 0,
+ [0xc] = 1,
+ [0x8] = 2,
+ [0x3] = 3,
+ [0xf] = 4,
+ [0xb] = 5,
+ [0x2] = 6,
+ [0xe] = 7,
+ [0xa] = 8
+};
+#define __mm_lookup_nibble(nibble) __mm_nibble_table[nibble & 0xf]
+
+static uint8_t __attribute__((unused)) mm_lookup_decoder(uint8_t mm_byte)
+{
+ uint8_t low;
+ uint8_t high;
+ uint8_t retval;
+
+ if (mm_byte == 0)
+ return 80;
+ low = __mm_lookup_nibble(mm_byte >> 4);
+ high = __mm_lookup_nibble(mm_byte & 0xf);
+ if (!low)
+ return 0;
+
+ /* retval = 9 * high + low; */
+ retval = high << 3;
+ retval += high;
+ retval += low;
+ return retval;
+}
+
+static uint8_t __attribute__((unused)) mm_lookup_key(uint8_t mm_command)
+{
+ uint8_t res;
+ /*
+ * Check for aabbccdd condition
+ *
+ * a a b b c c d d mm_command
+ * XOR a b b c c d d 0 mm_command << 1
+ * Mask 1 0 1 0 1 0 1 0 0xaa
+ *
+ * Must be zero!
+ *
+ */
+
+ if ((mm_command ^ (mm_command << 1)) & 0xaa)
+ return 0;
+ /*
+ * Protocol differences:
+ * =====================
+ *
+ * I have an old "central control" 6022 and a "control unit" 6021
+ * for measurements and test. It is assumed that the 6022 outputs
+ * old MM1 format while the 6021 definitively outputs MM2 telegrams.
+ *
+ * In MM1, switch commands are different from MM2 with respect what
+ * happens if you release a button.
+ *
+ * When you press a button, both protocols send
+ *
+ * <aaaaaaaa><00><aabbcc11>
+ *
+ * where a = 1, b = 2, c = 4 and the keys are numerated from 0 to 7
+ * in the order 1 red, 1 green, 2 red, 2 green and so on.
+ *
+ * The last two bits correspond to "on" state of the button/coil.
+ *
+ * When a key is released under MM1 protocol, the sequence sent is
+ * analogue to the button down sequence:
+ *
+ * <aaaaaaaa><00><aabbcc00> where abc again represents the button's
+ * address and the last bits now signal "off".
+ *
+ * MM2 handles this differently:
+ * Whenever any key from the addressed decoder is released, the sequence
+ * <aaaaaaaa>00<00000000> is sent - not only for key 0, but for all
+ * keys!
+ *
+ * While MM1 presents the theoretical possibility to press several keys
+ * independently and simultaneously (which my keyboard does NOT
+ * support), MM2 supports only one key at a time (besides strange
+ * sequences like "one down, another down, all up"...
+ *
+ * A decoder that strictly adheres to the MM1 standard would not work
+ * properly with MM2 control units. As far as I know all K83/K84
+ * decoders always worked with MM2 control units. That means that
+ * they reduce the commands to the possibilities of MM2 from the
+ * beginning.
+ *
+ * Possible use cases for the old protocol button release commands:
+ * - Determine if the protocol is MM1 or MM2
+ * - Implement hidden evil features into the controller which can
+ * only be summoned by old MM1 gear or selfmade control telegram
+ * generators.
+ *
+ * What this code now actually does:
+ * =================================
+ *
+ * When key pressed (aabbcc11), it will send out the key number in the
+ * range 1-8 and 0 if it gets any key up command and therefore ignore
+ * the key number if it is transmitted with the key up command.
+ *
+ */
+ if (!(mm_command & 0x01))
+ res = 0;
+ else
+ res = (mm_command & 0x80) * 1 + (mm_command & 0x20) * 0x02
+ + (mm_command & 0x08) * 0x04 + 1;
+ return res;
+}
+
+#endif
#endif
-
+#ifdef MM_USE_CALLBACK
/*
- * mm_switch_command - Callback function!
+ * mm_key_cb - Callback function!
*
* This function must be defined whenever the mm_switch module is used.
* It will be called from interrupt context whenever a new valid command
* has arrived.
*
* decoder is in the range from 1 to 25. Other values will not occur.
- *
+ *
* key is in the range from 0 to 8:
*
* 1 - key1 green pressed
* 4 - key2 red pressed
* ...
* 0 - all keys up
- *
+ *
*/
-void mm_switch_command(uint8_t decoder, uint8_t key);
+void mm_key_cb(uint8_t decoder, uint8_t function, uint8_t key);
+void mm_drive_cb(uint8_t decoder, uint8_t function, uint8_t command);
-/*
- * empty comment :-)
- */
-void mm_switch_drive_cb(uint8_t address, uint8_t speed, uint8_t functions, uint8_t flags);
+#endif
+
+void mm_pinchange_callback(void);
+
+enum mm_recmode {
+ __MM_INIT,
+ __MM_ARMED,
+ MM_SLOW,
+ MM_FAST,
+};
+
+#ifdef MM_USE_QUEUE
+
+struct mm_command {
+ enum mm_recmode recmode; /* Only MM_FAST or MM_SLOW! */
+ uint8_t address; /* Undecoded loco/decoder address */
+ uint8_t function; /* The two function bits */
+ uint8_t command; /* Button/Speed/Functions */
+};
-void mm_switch_drive(uint8_t decoder, uint8_t function, uint8_t command);
+struct mm_command *mm_get(void);
-void mm_switch_pinchange_callback(void);
+#endif /* MM_USE_QUEUE */
#endif
#include <config/hardware.h>
#include <mm/mm_switch.h>
-
+#include <mm/mm_decode.h>
/*
*
#error Missing needed MM_... macro!
#endif
-/*
- * Private global variables
- */
+#ifndef MM_QUEUE_DEPTH
+#define MM_QUEUE_DEPTH 8
+#endif
-enum recmode {
- INIT,
- ARMED,
- MM_SLOW,
- MM_FAST,
- DCC,
-};
-enum recmode recmode = INIT;
+
+ /*
+ * The nominal length of a bit cycle is 208 us.
+ * This consists of 8 parts, each 26 us:
+ * 1 d d d d d d 0
+ *
+ * That means that the 1 pulse is 7 * 26 = 182us
+ * and the short pulse is 1 * 26 = 26us.
+ *
+ * Reality seems to look not as that exact. I measure
+ * 26us for the clock pulse, but 196 for the long pulse.
+ *
+ * Keep in mind: Time is counted in 500ns steps.
+ *
+ */
+#define IN_RANGE(duration, lower, upper) \
+ ((duration >= (2 * lower) \
+ && (duration <= (2 * upper))))
+
+#define MM_FAST_SHORT(duration) IN_RANGE(duration, 7, 18)
+#define MM_FAST_LONG(duration) IN_RANGE(duration, 70, 130)
+#define MM_FAST_GAP(duration) IN_RANGE(duration, 500, 1100)
+
+#define MM_SLOW_SHORT(duration) IN_RANGE(duration, 18, 35)
+#define MM_SLOW_LONG(duration) IN_RANGE(duration, 150, 210)
+#define MM_SLOW_GAP(duration) IN_RANGE(duration, 1000, 2000)
+
+#define MM_SUFFICIENT_IDLE(duration) (duration > 800)
+
+
+
+static enum mm_recmode recmode = __MM_INIT;
+
+#ifdef MM_USE_QUEUE
+static uint8_t queue_wpos = 0;
+static uint8_t queue_rpos = 0;
+static struct mm_command queue[MM_QUEUE_DEPTH];
+#endif
#ifndef MM_USE_REGISTER_VARS
#endif
-/*
- * Lookup trinary nibble
- *
- * This was implemented using a switch statement before.
- * Changing the lookup to a table did only add two bytes
- * of memory and saved ca. 50 bytes program memory.
- */
-static const uint8_t nibble_table[16]={
- [0x0] = 0,
- [0xc] = 1,
- [0x8] = 2,
- [0x3] = 3,
- [0xf] = 4,
- [0xb] = 5,
- [0x2] = 6,
- [0xe] = 7,
- [0xa] = 8
-};
-#define lookup_nibble(nibble) nibble_table[nibble & 0xf]
-
-static uint8_t __attribute__((unused)) lookup_decoder(uint8_t mm_byte)
-{
- uint8_t low;
- uint8_t high;
- uint8_t retval;
-
- if (mm_byte == 0)
- return 80;
- low = lookup_nibble(mm_byte >> 4);
- high = lookup_nibble(mm_byte & 0xf);
- if (!low)
- return 0;
-
- /* retval = 9 * high + low; */
- retval = high << 3;
- retval += high;
- retval += low;
- return retval;
-}
-
-static uint8_t __attribute__((unused)) lookup_command(uint8_t mm_command)
-{
- uint8_t res;
- /*
- * Check for aabbccdd condition
- *
- * a a b b c c d d mm_command
- * XOR a b b c c d d 0 mm_command << 1
- * Mask 1 0 1 0 1 0 1 0 0xaa
- *
- * Must be zero!
- *
- */
-
- if ((mm_command ^ (mm_command << 1)) & 0xaa)
- return 0;
- /*
- * Protocol differences:
- * =====================
- *
- * I have an old "central control" 6022 and a "control unit" 6021
- * for measurements and test. It is assumed that the 6022 outputs
- * old MM1 format while the 6021 definitively outputs MM2 telegrams.
- *
- * In MM1, switch commands are different from MM2 with respect what
- * happens if you release a button.
- *
- * When you press a button, both protocols send
- *
- * <aaaaaaaa><00><aabbcc11>
- *
- * where a = 1, b = 2, c = 4 and the keys are numerated from 0 to 7
- * in the order 1 red, 1 green, 2 red, 2 green and so on.
- *
- * The last two bits correspond to "on" state of the button/coil.
- *
- * When a key is released under MM1 protocol, the sequence sent is
- * analogue to the button down sequence:
- *
- * <aaaaaaaa><00><aabbcc00> where abc again represents the button's
- * address and the last bits now signal "off".
- *
- * MM2 handles this differently:
- * Whenever any key from the addressed decoder is released, the sequence
- * <aaaaaaaa>00<00000000> is sent - not only for key 0, but for all
- * keys!
- *
- * While MM1 presents the theoretical possibility to press several keys
- * independently and simultaneously (which my keyboard does NOT
- * support), MM2 supports only one key at a time (besides strange
- * sequences like "one down, another down, all up"...
- *
- * A decoder that strictly adheres to the MM1 standard would not work
- * properly with MM2 control units. As far as I know all K83/K84
- * decoders always worked with MM2 control units. That means that
- * they reduce the commands to the possibilities of MM2 from the
- * beginning.
- *
- * Possible use cases for the old protocol button release commands:
- * - Determine if the protocol is MM1 or MM2
- * - Implement hidden evil features into the controller which can
- * only be summoned by old MM1 gear or selfmade control telegram
- * generators.
- *
- * What this code now actually does:
- * =================================
- *
- * When key pressed (aabbcc11), it will send out the key number in the
- * range 1-8 and 0 if it gets any key up command and therefore ignore
- * the key number if it is transmitted with the key up command.
- *
- */
- if (!(mm_command & 0x01))
- res = 0;
- else
- res = (mm_command & 0x80) * 1 + (mm_command & 0x20) * 0x02
- + (mm_command & 0x08) * 0x04 + 1;
- return res;
-}
-
/* We will shift from right to left.
* XXXXXXXX XX XXXXXXXX
* shift_address shift_function shift_command
* The real shift function is written in assembly and saves many instructions.
*/
#if 0
-void shift(uint8_t value)
+static void shift(uint8_t value)
{
shift_address <<= 1;
if (shift_function & 2)
}
#else
-void shift(uint8_t value)
+static void shift(uint8_t value)
{
asm("ror %[val] ; Shift value right into carry\n\t"
"rol %[cmd] ; and shift to command reg\n\t"
static volatile uint8_t shift_function_first;
static volatile uint8_t shift_address_first;
uint8_t address;
- uint8_t command;
shift(bit);
mm_bitno++;
shift_address_first = shift_address;
shift_function_first = shift_function;
shift_command_first = shift_command;
- }
+ }
if (mm_bitno == 36) {
-
if ((shift_command == shift_command_first) &&
(shift_address == shift_address_first) &&
(shift_function == shift_function_first)) {
- address = lookup_decoder(shift_address);
- if (recmode == MM_SLOW) {
- trigger();
- mm_switch_drive(address, shift_function,
- shift_command);
+#ifdef MM_USE_CALLBACK
+ address = mm_lookup_decoder(shift_address);
+ if (recmode == MM_SLOW) {
+ mm_drive_cb(address, shift_function,
+ shift_command);
} else {
- command = lookup_command(shift_command);
- mm_switch_command(address, command);
- }
+ mm_key_cb(address, shift_function, shift_command);
+ }
+#endif
+
+#ifdef MM_USE_QUEUE
+ queue[queue_wpos].recmode = recmode;
+ queue[queue_wpos].address = shift_address;
+ queue[queue_wpos].function = shift_function;
+ queue[queue_wpos].command = shift_command;
+ queue_wpos = (queue_wpos + 1) % MM_QUEUE_DEPTH;
+#endif
}
-
}
}
+#ifdef MM_USE_QUEUE
+
+struct mm_command *mm_get(void)
+{
+ struct mm_command *result = NULL;
+ uint8_t sreg_bak = SREG;
+ cli();
+ if (queue_rpos != queue_wpos) {
+ result = &queue[queue_rpos];
+ queue_rpos = (queue_rpos + 1) % MM_QUEUE_DEPTH;
+ }
+ SREG = sreg_bak;
+ return result;
+}
+#endif
/*
* The timeout interrupt vector does nothing else
);
#endif
}
-
+
/* Pin change interrupt vector, here we have a bit more time */
ISR(__vector_pinchange){
uint16_t duration;
/* First kill off that timer */
MM_TSTART; /* Restart timer */
-
+
/* Account for not yet handled timer overflow */
TIFR |= _BV(TOV0);
mm_bit_val = MM_SENSE;
mm_time_h = 0;
- /*
- * The nominal length of a bit cycle is 208 us.
- * This consists of 8 parts, each 26 us:
- * 1 d d d d d d 0
- *
- * That means that the 1 pulse is 7 * 26 = 182us
- * and the short pulse is 1 * 26 = 26us.
- *
- * Reality seems to look not as that exact. I measure
- * 26us for the clock pulse, but 196 for the long pulse.
- *
- * Keep in mind: Time is counted in 500ns steps.
- *
- */
-#define D_MATCH(d, v) ((duration > (d * 2 - 2 * v)) && (duration < (d * 2 + 2 * v)))
-
- switch(recmode) {
- case INIT:
- default:
- break;
- case ARMED:
- /* Maerklin only interested when signal is 1 */
- if (mm_bit_val == mm_polarity) {
- /* Fast short MM pulse (logical 0) */
- if (D_MATCH(13, 4)){
- recmode = MM_FAST;
+ if (recmode != MM_SLOW) {
+ /* Fast short MM pulse */
+ if (MM_FAST_SHORT(duration)){
+ recmode = MM_FAST;
+ if (mm_bit_val == mm_polarity)
mm_feed_bit(0);
- goto done;
- }
-
- /* Slow short MM pulse (logical 0) */
- if (D_MATCH(26, 4)) {
- recmode = MM_SLOW;
- mm_feed_bit(0);
- goto done;
- }
-
- /* Fast long MM pulse (logical 1) */
- if (D_MATCH(91, 17)) {
- recmode = MM_FAST;
- mm_feed_bit(1);
- goto done;
- }
-
- /* Slow long MM pulse (logical 1) */
- if (D_MATCH(182, 25)) {
- recmode = MM_SLOW;
+ goto done;
+ }
+ /* Fast long MM pulse */
+ if (MM_FAST_LONG(duration)) {
+ recmode = MM_FAST;
+ if (mm_bit_val == mm_polarity)
mm_feed_bit(1);
+ goto done;
+ }
+ } else {
+ /* Accepted slow inter package gap */
+ if (MM_SLOW_GAP(duration))
+ if (mm_bit_val != mm_polarity)
goto done;
- }
- }
- break;
+ }
- case MM_FAST:
- if (mm_bit_val == mm_polarity) {
+ if (recmode != MM_FAST) {
- /* Fast short MM pulse (logical 0) */
- if (D_MATCH(13, 4)){
+ /* Slow short MM pulse */
+ if (MM_SLOW_SHORT(duration)) {
+ recmode = MM_SLOW;
+ if (mm_bit_val == mm_polarity)
mm_feed_bit(0);
- goto done;
- }
- /* Fast long MM pulse (logical 1) */
- if (D_MATCH(91, 17)) {
- mm_feed_bit(1);
- goto done;
- }
- } else {
- if (D_MATCH(13, 4))
- goto done;
- if (D_MATCH(91, 17))
- goto done;
-
- if (D_MATCH(700, 200))
- goto done;
+ goto done;
}
- break;
-
- case MM_SLOW:
- if (mm_bit_val == mm_polarity) {
- /* Slow short MM pulse (logical 0) */
- if (D_MATCH(26, 4)) {
- mm_feed_bit(0);
- goto done;
- }
- /* Slow long MM pulse (logical 1) */
- if (D_MATCH(182, 40)) {
+ /* Slow long MM pulse */
+ if (MM_SLOW_LONG(duration)) {
+ recmode = MM_SLOW;
+ if (mm_bit_val == mm_polarity)
mm_feed_bit(1);
- goto done;
- }
- } else {
- if (D_MATCH(26, 4))
- goto done;
- if (D_MATCH(182, 40))
- goto done;
-
- /* Accepted inter package gap */
- if (D_MATCH(1400, 400))
+ goto done;
+ }
+ } else {
+ /* Accepted fast interpackage gap */
+ if (MM_FAST_GAP(duration)) {
+ if (mm_bit_val != mm_polarity)
goto done;
}
- break;
}
- /*
+ /*
* If we have reached here, our pulse comes in somehow unexpected.
* We kill of everything by re-arming the state machine.
*/
/* Start over receiver */
mm_bitno = 0;
- mm_polarity = !mm_bit_val;
- recmode = ARMED;
+
+ if (MM_SUFFICIENT_IDLE(duration)) {
+ recmode = __MM_ARMED;
+ mm_polarity = !mm_bit_val;
+ } else {
+ recmode = __MM_INIT;
+ }
done:
- mm_switch_pinchange_callback();
+ mm_pinchange_callback();
}
-void __attribute__((weak))mm_switch_pinchange_callback(void)
+void __attribute__((weak))mm_pinchange_callback(void)
{
}
-void __attribute__((weak))mm_switch_drive(uint8_t decoder, uint8_t function,
+void __attribute__((weak))mm_drive_cb(uint8_t decoder, uint8_t function,
uint8_t command)
{
}
-void __attribute__((weak))mm_switch_command(uint8_t address, uint8_t command)
+void __attribute__((weak))mm_key_cb(uint8_t address, uint8_t function,
+ uint8_t command)
{
}
+++ /dev/null
-/******************************************************************************
- *
- * Trennfix firmware - main.c
- *
- * Copyright (C) 2017 Philipp Hachtmann
- *
- * This program 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.
- *
- * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
- *
- *****************************************************************************/
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <avr/io.h>
-#include <avr/eeprom.h>
-#include <avr/interrupt.h>
-#include <avr/pgmspace.h>
-
-#include <util/delay.h>
-#include <stdint.h>
-#include <mm/mm_switch.h>
-#include <config/hardware.h>
-
-#define EE_MAGIC 0xab
-
-enum op_mode {
- OM_MOMENTARY, /* on as long as "key on" pressed */
- OM_DOUBLE, /* On off with "on" and "off" keys */
- OM_TOGGLE, /* toggle on "key on" pressed */
- OM_ERASED = 0xff /* EEPROM erased, need setup */
-};
-
-enum learn_mode {
- LM_OFF = 0,
- LM_LEARN_ON_KEY, /* Learn primary key */
- LM_LEARN_OFF_KEY, /* Learn secondary key, relevant for OM_DOUBLE */
- LM_LEARN_INITIAL, /* Learn initial pulse length, 10ms steps */
- LM_LEARN_DUTY, /* Learn duty cycle 0-10 */
- LM_LEARN_OP_MODE, /* Learn operation mode */
- LM_EASY_WAIT_PRESS, /* Wait for key press to enter easy mode */
- LM_EASY_WAIT_UP, /* Wait for end of key press */
- LM_EASY_WAIT_TURN, /* Wait for loco 80 turn end */
- LM_EASY_MODE, /* Easy config mode for PWM and initial kick */
- LM_END, /* Only a label */
-};
-
-struct config {
- uint8_t magic; /* Magic value */
- enum op_mode op_mode;
- uint8_t decoder_on;
- uint8_t key_on;
- uint8_t decoder_off;
- uint8_t key_off;
- uint8_t initial_pulse; /* Lenghth of initial pulse in 10ms steps */
- uint8_t on_duty_cycle; /* Duty cycle for on. 0-10 */
- volatile enum learn_mode learn_mode;
-};
-
-static struct EEMEM config ee_config;
-static volatile struct config config;
-
-static volatile uint8_t easy_mode = 0;
-static volatile uint8_t easy_mode_possible = 0;
-
-#ifndef USE_REGISTER_VARS
-static volatile uint8_t drive_on = 0;
-#endif
-
-/******************************************************************************
- *
- * Some nice sounds to play on your coil
- *
- */
-
-#define G 180
-#define sekunde(g)(quinte((quinte(g)))*2)
-#define terz(g) ((g) * 4 / 5)
-#define kleine_terz(g) ((g) * 5 / 6)
-#define quarte(g) ((g) * 3 / 4)
-#define quinte(g) ((g) * 2 / 3)
-#define tt(g) ((g) * 32 / 45)
-#define septime(g) ((g) * 15 / 8)
-
-#if defined(WITH_SOUND) && defined(WITH_PWM)
-void play_tone(uint8_t divisor, uint8_t duration, uint8_t pause)
-{
- uint16_t c;
- TCCR1 = 0x8;
- OCR1C = divisor;
- c = (divisor * 2) / 3;
- OCR1B = c;
- for (c = 0; c < duration - pause; c++)
- _delay_ms(2);
- TCCR1 = 0x6;
- OCR1C = PWM_CYCLE;
- OCR1B = 12;
- for (c = 0; c < pause; c++)
- _delay_ms(2);
- TCCR1 = 0x6;
- OCR1C = PWM_CYCLE;
- OCR1B = PWM_CYCLE;
-}
-
-static void tone_enter(void)
-{
- play_tone((G), 70, 20);
- play_tone(terz(G), 70, 20);
- play_tone(quinte(G), 70, 20);
- play_tone(G/2, 100, 0);
-}
-
-static void tone_good(void)
-{
- play_tone(G, 150, 120);
- play_tone(terz(G), 100, 70);
- play_tone(tt(G), 100, 50);
- play_tone(quarte(terz((G))), 50, 0);
- play_tone(quinte(G), 150, 0);
- play_tone(terz(G), 100, 50);
-}
-
-static void snd_on(void)
-{
- TCCR1 = 0x8;
- OCR1C = 120;
- OCR1B = 60;
-}
-
-static void snd_off(void)
-{
- TCCR1 = 0x6;
- OCR1C = PWM_CYCLE;
- OCR1B = PWM_CYCLE;
-}
-
-#else
-#define play_tone(...)
-#define tone_enter(...)
-#define tone_good(...)
-#define snd_on(...)
-#define snd_off(...)
-#endif
-
-static void load_config(void)
-{
- eeprom_read_block((uint8_t *)&config, &ee_config, sizeof(config));
-}
-
-static void save_config(void)
-{
-#ifdef WITH_EEPROM_UPDATE
- eeprom_update_block((uint8_t *)&config, &ee_config, sizeof(config));
-#else
- eeprom_write_block((uint8_t *)&config, &ee_config, sizeof(config));
-#endif
-}
-
-void mm_switch_pinchange_callback(void)
-{
- static uint8_t btn_last = 0;
-
- if (BTN_PRESSED && !btn_last) {
- config.learn_mode++;
- config.learn_mode %= LM_END;
- }
- btn_last = BTN_PRESSED;
-}
-
-static uint8_t get_speed(uint8_t command)
-{
- uint8_t b1, b3, b5, b7;
-
- b1 = ((command & 0x80) != 0);
- b3 = ((command & 0x20) != 0);
- b5 = ((command & 0x8) != 0);
- b7 = ((command & 0x2) != 0);
-
- //if ((b1 != b2) || (b2 != b3) || (b3 != b4) || (b5 != b6) || (b7 != b8))
- // return 0xff;
- return (b1 + b3 * 2 + b5 * 4 +b7 * 8);
-}
-
-
-void mm_switch_drive(uint8_t loco, uint8_t function, uint8_t command)
-{
- uint8_t speed;
- speed = get_speed(command);
- static uint8_t alert_last = 0;
-
- switch(loco) {
- case 80:
- switch (config.learn_mode) {
- case LM_OFF:
- if (speed == 1)
- config.learn_mode = LM_EASY_WAIT_PRESS;
- break;
- case LM_EASY_MODE:
- if ((speed == 1) && (alert_last == 0)) {
- config.learn_mode = LM_OFF;
- save_config();
- tone_good();
- }
- break;
-
- case LM_EASY_WAIT_PRESS:
- if (speed != 1)
- config.learn_mode = LM_OFF;
- break;
-
- default:
- break;
- }
- alert_last = (speed == 1);
- break;
-
- case 50:
- if (speed)
- speed = speed - 1;
-
- if (config.learn_mode == LM_EASY_MODE) {
- config.initial_pulse = speed;
- }
- break;
- case 51:
- if (speed)
- speed = speed - 1;
-
- if (config.learn_mode == LM_EASY_MODE)
- config.on_duty_cycle = speed;
- break;
- }
-
-}
-
-void mm_switch_command(uint8_t decoder, uint8_t command)
-{
- static uint8_t toggle_lock = 0;
-
- switch(config.learn_mode) {
-
- case LM_OFF:
- default:
- if ((decoder == config.decoder_on) &&
- (command == config.key_on)) { /* Primary key pressed */
- switch(config.op_mode) {
- case OM_MOMENTARY:
- case OM_DOUBLE:
- drive_on = 1;
- break;
- case OM_TOGGLE:
- if (!toggle_lock) {
- drive_on = ~drive_on;
- toggle_lock = 1;
- }
- break;
- default:
- break;
- }
- }
- if ((decoder == config.decoder_on) &&
- (command == 0)) { /* Primary key released */
- switch(config.op_mode) {
- case OM_MOMENTARY:
- drive_on = 0;
- break;
- case OM_TOGGLE:
- toggle_lock = 0;
- break;
- default:
- break;
- }
- }
- break;
-
- case LM_EASY_WAIT_PRESS:
- if ((decoder == config.decoder_on) &&
- (command == config.key_on))
- config.learn_mode = LM_EASY_WAIT_UP;
- return;
-
- case LM_EASY_WAIT_UP:
- if ((decoder == config.decoder_on) &&
- (command == 0)) {
- config.learn_mode = LM_EASY_MODE;
- tone_enter();
- }
- return;
-
-#ifdef HANDLE_OFF_KEY
-
- if ((decoder == config.decoder_off) &&
- (command == config.key_off)) { /* Secondary "off" key pressed */
- switch(config.op_mode) {
- case OM_DOUBLE:
- drive_on = 0;
- break;
- case OM_TOGGLE:
- case OM_MOMENTARY:
- default:
- break;
- }
- }
-#endif
- break;
-
- case LM_LEARN_ON_KEY:
- if (command) {
- config.decoder_on = decoder;
- config.key_on = command;
- if (config.op_mode == OM_DOUBLE)
- config.learn_mode = LM_LEARN_OFF_KEY;
- else
- config.learn_mode = LM_OFF;
- save_config();
- }
- break;
-#ifdef LEARN_ADVANCED
- case LM_LEARN_OFF_KEY:
- if (command) {
- config.decoder_off = decoder;
- config.key_off = command;
- config.learn_mode = LM_OFF;
- save_config();
- }
- break;
-
- case LM_LEARN_INITIAL:
- if (drive_on) {
- if (command == 0)
- drive_on = 0;
-
- } else {
- switch(command) {
- case 1:
- if (config.initial_pulse >= 10)
- config.initial_pulse -= 10;
- else
- config.initial_pulse = 0;
- save_config();
- drive_on = 1;
- break;
- case 2:
- if (config.initial_pulse <= 245)
- config.initial_pulse += 10;
- else
- config.initial_pulse = 255;
- save_config();
- drive_on = 1;
- break;
- default:
- break;
- }
- }
- break;
-
- case LM_LEARN_DUTY:
- if (drive_on) {
- if (command == 0)
- drive_on = 0;
- } else {
- switch(command) {
- case 1:
- if (config.on_duty_cycle > 0)
- config.on_duty_cycle -= 1;
- save_config();
- drive_on = 1;
- break;
- case 2:
- if (config.on_duty_cycle < 10)
- config.on_duty_cycle += 1;
- save_config();
- drive_on = 1;
- break;
- default:
- break;
- }
- }
- break;
-
- case LM_LEARN_OP_MODE:
- switch(command) {
- case 1:
- config.op_mode = OM_MOMENTARY;
- save_config();
- config.learn_mode = LM_OFF;
- break;
- case 3:
- config.op_mode = OM_DOUBLE;
- save_config();
- config.learn_mode = LM_OFF;
- break;
- case 5:
- config.op_mode = OM_TOGGLE;
- save_config();
- config.learn_mode = LM_OFF;
- break;
- default:
- break;
- }
- break;
-#endif
- }
-}
-
-
-/******************************************************************************
- *
- * main() - The main routine
- *
- */
-void shift(uint8_t mu);
-
-#ifdef WITH_PWM
-#define DRIVE_OFF {OCR1B = PWM_CYCLE;}
-#define DRIVE_ON {OCR1B = PWM_CYCLE - config.on_duty_cycle;}
-#define DRIVE_FULL {OCR1B = 0;}
-#else
-#define DRIVE_OFF {setpin(PIN_DRIVE, 0); setpin(PIN_LED, 0);}
-#define DRIVE_ON {setpin(PIN_DRIVE, 1); setpin(PIN_LED, 1);}
-#define DRIVE_FULL {setpin(PIN_DRIVE, 1); setpin(PIN_LED, 1);}
-#endif
-
-int main(void) {
- uint16_t i;
-
-#ifdef WITH_INITIAL_PULSE
- uint8_t drive_last = 0;
- uint8_t drive_slope = 0;
-#endif
-
-#ifdef USE_REGISTER_VARS
- drive_on = 0;
-#endif
- mm_init();
- load_config();
- setup_hw();
-
- if (config.magic != EE_MAGIC) {
- config.magic = EE_MAGIC;
- config.op_mode = OM_MOMENTARY;
- config.decoder_on = 1;
- config.key_on = 1;
- config.decoder_off = 1;
- config.key_off = 2;
- config.initial_pulse = 10;
- config.on_duty_cycle = 5;
- config.learn_mode = LM_LEARN_ON_KEY;
- }
- sei();
- while (1) {
- drive_start:
-
-#ifdef WITH_INITIAL_PULSE
- cli();
- if (drive_on && !drive_last)
- drive_slope = 1;
- else
- drive_slope = 0;
- drive_last = drive_on;
- sei();
-#endif
- if (drive_on) {
-
-#ifdef WITH_INITIAL_PULSE
- if (drive_slope) {
- DRIVE_FULL;
- for (i = 0; i < config.initial_pulse; i++) {
- _delay_ms(5);
- }
- }
-#endif
- DRIVE_ON;
-
- } else {
- DRIVE_OFF;
-
- if (!config.learn_mode ||
- config.learn_mode > LM_LEARN_OP_MODE)
- continue;
-
- for (i = 0; i < config.learn_mode; i++) {
- setpin(PIN_LED, 1);
- snd_on();
- _delay_ms(10);
- setpin(PIN_LED, 0);
- snd_off();
- if (drive_on) goto drive_start;
- _delay_ms(135);
- if (drive_on) goto drive_start;
- }
- for (i = 0; i < 15 - config.learn_mode; i++) {
- if (drive_on) goto drive_start;
- _delay_ms(70);
- }
- }
- }
- return 0;
-}
-
-/******************************************************************************
- * The end :-)
- */
--- /dev/null
+/******************************************************************************
+ *
+ * Trennfix firmware - main.c
+ *
+ * Copyright (C) 2017 Philipp Hachtmann
+ *
+ * This program 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.
+ *
+ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *****************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <avr/io.h>
+#include <avr/eeprom.h>
+#include <avr/interrupt.h>
+#include <avr/pgmspace.h>
+
+#include <util/delay.h>
+#include <stdint.h>
+#include <mm/mm_switch.h>
+#include <mm/mm_decode.h>
+#include <config/hardware.h>
+
+uint8_t eeFooByte EEMEM = 123;
+
+#define EE_MAGIC 0xab
+
+FUSES = {
+ .low = FUSE_CKDIV8 & FUSE_SUT0 &\
+ FUSE_CKSEL3 & FUSE_CKSEL2 & FUSE_CKSEL1,
+ .high = FUSE_SPIEN & FUSE_BODLEVEL1 & FUSE_BODLEVEL0 & FUSE_EESAVE,
+ .extended = EFUSE_DEFAULT,
+};
+
+void mm_key_cb(uint8_t decoder, uint8_t function, uint8_t key);
+void mm_drive_cb(uint8_t decoder, uint8_t function, uint8_t command);
+
+enum op_mode {
+ OM_MOMENTARY, /* on as long as "key on" pressed */
+ OM_DOUBLE, /* On off with "on" and "off" keys */
+ OM_TOGGLE, /* toggle on "key on" pressed */
+ OM_ERASED = 0xff /* EEPROM erased, need setup */
+};
+
+enum learn_mode {
+ LM_OFF = 0,
+ LM_LEARN_ON_KEY, /* Learn primary key */
+ LM_LEARN_OFF_KEY, /* Learn secondary key, relevant for OM_DOUBLE */
+ LM_LEARN_INITIAL, /* Learn initial pulse length, 10ms steps */
+ LM_LEARN_DUTY, /* Learn duty cycle 0-10 */
+ LM_LEARN_OP_MODE, /* Learn operation mode */
+ LM_EASY_WAIT_PRESS, /* Wait for key press to enter easy mode */
+ LM_EASY_WAIT_UP, /* Wait for end of key press */
+ LM_EASY_WAIT_TURN, /* Wait for loco 80 turn end */
+ LM_EASY_MODE, /* Easy config mode for PWM and initial kick */
+ LM_END, /* Only a label */
+};
+
+struct config {
+ uint8_t magic; /* Magic value */
+ enum op_mode op_mode;
+ uint8_t decoder_on;
+ uint8_t key_on;
+ uint8_t decoder_off;
+ uint8_t key_off;
+ uint8_t initial_pulse; /* Lenghth of initial pulse in 10ms steps */
+ uint8_t on_duty_cycle[15]; /* Duty cycle for on. 0-10 */
+ volatile enum learn_mode learn_mode;
+};
+
+static uint8_t main_speed = 0;
+static struct EEMEM config ee_config;
+static volatile struct config config;
+
+static volatile uint8_t easy_mode = 0;
+static volatile uint8_t easy_mode_possible = 0;
+
+#ifndef USE_REGISTER_VARS
+static volatile uint8_t drive_on = 0;
+#endif
+
+/******************************************************************************
+ *
+ * Some nice sounds to play on your coil
+ *
+ */
+
+#define G 180
+#define sekunde(g)(quinte((quinte(g)))*2)
+#define terz(g) ((g) * 4 / 5)
+#define kleine_terz(g) ((g) * 5 / 6)
+#define quarte(g) ((g) * 3 / 4)
+#define quinte(g) ((g) * 2 / 3)
+#define tt(g) ((g) * 32 / 45)
+#define septime(g) ((g) * 15 / 8)
+
+#if defined(WITH_SOUND) && defined(WITH_PWM)
+void play_tone(uint8_t divisor, uint8_t duration, uint8_t pause)
+{
+ uint16_t c;
+ TCCR1 = 0x8;
+ OCR1C = divisor;
+ c = (divisor * 2) / 3;
+ OCR1B = c;
+ for (c = 0; c < duration - pause; c++)
+ _delay_ms(2);
+ TCCR1 = 0x6;
+ OCR1C = PWM_CYCLE;
+ OCR1B = 12;
+ for (c = 0; c < pause; c++)
+ _delay_ms(2);
+ TCCR1 = 0x6;
+ OCR1C = PWM_CYCLE;
+ OCR1B = PWM_CYCLE;
+}
+
+static void tone_enter(void)
+{
+ play_tone((G), 70, 20);
+ play_tone(terz(G), 70, 20);
+ play_tone(quinte(G), 70, 20);
+ play_tone(G/2, 100, 0);
+}
+
+static void tone_good(void)
+{
+ play_tone(G, 150, 120);
+ play_tone(terz(G), 100, 70);
+ play_tone(tt(G), 100, 50);
+ play_tone(quarte(terz((G))), 50, 0);
+ play_tone(quinte(G), 150, 0);
+ play_tone(terz(G), 100, 50);
+}
+
+static void snd_on(void)
+{
+ TCCR1 = 0x8;
+ OCR1C = 120;
+ OCR1B = 60;
+}
+
+static void snd_off(void)
+{
+ TCCR1 = 0x6;
+ OCR1C = PWM_CYCLE;
+ OCR1B = PWM_CYCLE;
+}
+
+#else
+#define play_tone(...)
+#define tone_enter(...) {setpin(PIN_LED, 1); _delay_ms(500); setpin(PIN_LED, 0);}
+#define tone_good(...)
+#define snd_on(...)
+#define snd_off(...)
+#endif
+
+static void load_config(void)
+{
+ eeprom_read_block((uint8_t *)&config, &ee_config, sizeof(config));
+}
+
+static void save_config(void)
+{
+#ifdef WITH_EEPROM_UPDATE
+ eeprom_update_block((uint8_t *)&config, &ee_config, sizeof(config));
+#else
+ eeprom_write_block((uint8_t *)&config, &ee_config, sizeof(config));
+#endif
+}
+
+void mm_pinchange_callback(void)
+{
+ static uint8_t btn_last = 0;
+
+ if (BTN_PRESSED && !btn_last) {
+ config.learn_mode++;
+ config.learn_mode %= LM_END;
+ }
+ btn_last = BTN_PRESSED;
+}
+
+static uint8_t get_speed(uint8_t command)
+{
+ uint8_t b0, b2, b4, b6;
+
+ b0 = ((command & 0x80) != 0);
+ b2 = ((command & 0x20) != 0);
+ b4 = ((command & 0x8) != 0);
+ b6 = ((command & 0x2) != 0);
+
+ //if ((b9!= b2) || (b2 != b3) || (b3 != b4) || (b5 != b6) || (b7 != b8))
+ // return 0xff;
+ return (b0+ b2 * 2 + b4 * 4 +b6 * 8);
+}
+
+enum mm2_command {
+ CF_MM1 ,
+ CF_REVERSE,
+ CF_FORWARD,
+ CF_F1_ON,
+ CF_F1_OFF,
+ CF_F2_ON,
+ CF_F2_OFF,
+ CF_F3_ON,
+ CF_F3_OFF,
+ CF_F4_ON,
+ CF_F4_OFF,
+};
+
+#ifdef MM_USE_QUEUE
+static void dequeue_commands(void)
+{
+
+ return;
+
+ struct mm_command *cmd = mm_get();
+ if (!cmd)
+ return;
+ if (cmd->recmode != MM_FAST)
+ return;
+ if ((cmd->function & 0x3) != 0x3)
+ return;
+ // uint8_t loco = mm_lookup_decoder(cmd->address);
+
+}
+
+#endif
+
+void mm_drive_cb(uint8_t loco, uint8_t func_enc, uint8_t cmd_enc)
+{
+ uint8_t b1, b3, b5, b7;
+ uint8_t fcode;
+
+ /* Those three are decoded from fun and command */
+ uint8_t speed;
+ uint8_t function;
+ enum mm2_command mm2_command = CF_MM1;
+ static uint8_t alert_last = 0;
+ speed = get_speed(cmd_enc);
+
+#ifdef MUELL
+ goto mm2_done; // FIXME
+
+ b1 = ((cmd_enc & 0x4) != 0);
+ b3 = ((cmd_enc & 0x1) != 0);
+ b5 = ((cmd_enc & 0x4) != 0);
+ b7 = ((cmd_enc & 0x1) != 0);
+
+ fcode = b1 * 8 + b3 * 4 + b5 *2 + b7;
+
+ /* The ugly speed section */
+ if (speed < 8) {
+ if (fcode == 0xb) {
+ mm2_command = CF_REVERSE;
+ goto mm2_done;
+ }
+ if (fcode == 0x5) {
+ mm2_command = CF_FORWARD;
+ goto mm2_done;
+ }
+ } else {
+ if (fcode == 0xa) {
+ mm2_command = CF_REVERSE;
+ goto mm2_done;
+ }
+ if (fcode == 0x4) {
+ mm2_command = CF_FORWARD;
+ goto mm2_done;
+ }
+ }
+
+ /* Special cases for f1-f4 commands */
+ if (fcode == 0xa) {
+ switch(speed) {
+ case 3:
+ mm2_command = CF_F1_ON;
+ goto mm2_done;
+ case 4:
+ mm2_command = CF_F2_ON;
+ goto mm2_done;
+ case 6:
+ mm2_command = CF_F3_ON;
+ goto mm2_done;
+ case 7:
+ mm2_command = CF_F4_ON;
+ goto mm2_done;
+ default:
+ goto mm2_done;
+ }
+ } else if (fcode == 0x5) {
+ switch(speed) {
+ case 11:
+ mm2_command = CF_F1_OFF;
+ goto mm2_done;
+ case 12:
+ mm2_command = CF_F2_OFF;
+ goto mm2_done;
+ case 14:
+ mm2_command = CF_F3_OFF;
+ goto mm2_done;
+ case 15:
+ mm2_command = CF_F4_OFF;
+ goto mm2_done;
+ }
+ }
+
+ switch(fcode) {
+ case 0xc:
+ mm2_command = CF_F1_OFF;
+ goto mm2_done;
+ case 0xd:
+ mm2_command = CF_F1_ON;
+ goto mm2_done;
+
+ case 0x2:
+ mm2_command = CF_F2_OFF;
+ goto mm2_done;
+ case 0x3:
+ mm2_command = CF_F2_ON;
+ goto mm2_done;
+
+ case 0x6:
+ mm2_command = CF_F3_OFF;
+ goto mm2_done;
+
+ case 0x7:
+ mm2_command = CF_F3_ON;
+ goto mm2_done;
+
+ case 0xe:
+ mm2_command = CF_F4_OFF;
+ goto mm2_done;
+
+ case 0xf:
+ mm2_command = CF_F4_ON;
+ goto mm2_done;
+ default:
+ break;
+ }
+ mm2_done:
+ if (loco == 34) {
+ switch(mm2_command) {
+ case CF_F3_ON:
+ drive_on = 1;
+ break;
+ case CF_F3_OFF:
+ drive_on = 0;
+ break;
+ default:
+ break;
+ }
+ }
+#endif
+ if (loco == config.decoder_on) {
+ if (speed)
+ speed = speed - 1;
+ main_speed = speed;
+ return;
+ }
+
+ switch(loco) {
+ case 80:
+ switch (config.learn_mode) {
+ case LM_OFF:
+ if (speed == 1)
+ config.learn_mode = LM_EASY_WAIT_PRESS;
+ break;
+ case LM_EASY_MODE:
+ if ((speed == 1) && (alert_last == 0)) {
+ config.learn_mode = LM_OFF;
+ save_config();
+ tone_good();
+ }
+ break;
+
+ case LM_EASY_WAIT_PRESS:
+ if (speed != 1)
+ config.learn_mode = LM_OFF;
+ break;
+
+ default:
+ break;
+ }
+ alert_last = (speed == 1);
+ break;
+
+ case 50:
+ if (speed)
+ speed = speed - 1;
+
+ if (config.learn_mode == LM_EASY_MODE) {
+ config.initial_pulse = speed;
+ }
+ break;
+ case 51:
+ if (speed)
+ speed = speed - 1;
+
+ if (config.learn_mode == LM_EASY_MODE)
+ config.on_duty_cycle[main_speed] = speed;
+ break;
+ }
+}
+
+void mm_key_cb(uint8_t decoder, uint8_t function, uint8_t raw_command)
+{
+ static uint8_t toggle_lock = 0;
+ uint8_t command;
+
+ static uint8_t last_raw = 0;
+ static uint8_t last_fkey = 0;
+
+ if ((function & 3) == 3) { /* F key hack! */
+ uint8_t diff;
+ diff = last_raw ^ raw_command;
+ if (diff) {
+ if (diff & 0x80)
+ command = 4;
+ else if (diff & 0x20)
+ command = 3;
+ else if (diff & 0x8)
+ command = 2;
+ else
+ command = 1;
+ command |= 0x80;
+ if ((command == config.key_on)
+ && (last_raw & ~raw_command))
+ command = 0;
+ last_raw = raw_command;
+ } else {
+ return;
+ }
+ } else {
+ command = mm_lookup_key(raw_command);
+ }
+
+ switch(config.learn_mode) {
+ case LM_OFF:
+ default:
+ if ((decoder == config.decoder_on) &&
+ (command == config.key_on)) { /* Primary key pressed */
+ switch(config.op_mode) {
+ case OM_MOMENTARY:
+ case OM_DOUBLE:
+ drive_on = 1;
+ break;
+ case OM_TOGGLE:
+ if (!toggle_lock) {
+ drive_on = ~drive_on;
+ toggle_lock = 1;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ if ((decoder == config.decoder_on) &&
+ (command == 0)) { /* Primary key released */
+ switch(config.op_mode) {
+ case OM_MOMENTARY:
+ drive_on = 0;
+ break;
+ case OM_TOGGLE:
+ toggle_lock = 0;
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+
+ case LM_EASY_WAIT_PRESS:
+ if ((decoder == config.decoder_on) &&
+ (command == config.key_on))
+ config.learn_mode = LM_EASY_WAIT_UP;
+ return;
+
+ case LM_EASY_WAIT_UP:
+ if ((decoder == config.decoder_on) &&
+ (command == 0)) {
+ config.learn_mode = LM_EASY_MODE;
+ tone_enter();
+ }
+ return;
+
+#ifdef HANDLE_OFF_KEY
+
+ if ((decoder == config.decoder_off) &&
+ (command == config.key_off)) { /* Secondary "off" key pressed */
+ switch(config.op_mode) {
+ case OM_DOUBLE:
+ drive_on = 0;
+ break;
+ case OM_TOGGLE:
+ case OM_MOMENTARY:
+ default:
+ break;
+ }
+ }
+#endif
+ break;
+
+ case LM_LEARN_ON_KEY:
+ if (command) {
+ config.decoder_on = decoder;
+ config.key_on = command;
+ if (config.op_mode == OM_DOUBLE)
+ config.learn_mode = LM_LEARN_OFF_KEY;
+ else
+ config.learn_mode = LM_OFF;
+ save_config();
+ }
+ break;
+#ifdef LEARN_ADVANCED
+ case LM_LEARN_OFF_KEY:
+ if (command) {
+ config.decoder_off = decoder;
+ config.key_off = command;
+ config.learn_mode = LM_OFF;
+ save_config();
+ }
+ break;
+
+ case LM_LEARN_INITIAL:
+ if (drive_on) {
+ if (command == 0)
+ drive_on = 0;
+
+ } else {
+ switch(command) {
+ case 1:
+ if (config.initial_pulse >= 10)
+ config.initial_pulse -= 10;
+ else
+ config.initial_pulse = 0;
+ save_config();
+ drive_on = 1;
+ break;
+ case 2:
+ if (config.initial_pulse <= 245)
+ config.initial_pulse += 10;
+ else
+ config.initial_pulse = 255;
+ save_config();
+ drive_on = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+
+ case LM_LEARN_DUTY:
+ if (drive_on) {
+ if (command == 0)
+ drive_on = 0;
+ } else {
+ switch(command) {
+ case 1:
+ if (config.on_duty_cycle > 0)
+ config.on_duty_cycle -= 1;
+ save_config();
+ drive_on = 1;
+ break;
+ case 2:
+ if (config.on_duty_cycle < 10)
+ config.on_duty_cycle += 1;
+ save_config();
+ drive_on = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+
+ case LM_LEARN_OP_MODE:
+ switch(command) {
+ case 1:
+ config.op_mode = OM_MOMENTARY;
+ save_config();
+ config.learn_mode = LM_OFF;
+ break;
+ case 3:
+ config.op_mode = OM_DOUBLE;
+ save_config();
+ config.learn_mode = LM_OFF;
+ break;
+ case 5:
+ config.op_mode = OM_TOGGLE;
+ save_config();
+ config.learn_mode = LM_OFF;
+ break;
+ default:
+ break;
+ }
+ break;
+#endif
+ }
+}
+
+
+/******************************************************************************
+ *
+ * main() - The main routine
+ *
+ */
+void shift(uint8_t mu);
+
+#ifdef DEBUG_DRIVE_LED
+#define MON_LED(val) setpin(PIN_LED, val)
+#else
+#define MON_LED(val)
+#endif
+
+#ifdef WITH_PWM
+#define DRIVE_OFF {OCR1B = PWM_CYCLE; MON_LED(0);}
+#define DRIVE_ON {OCR1B = PWM_CYCLE - config.on_duty_cycle[main_speed]; \
+ MON_LED(1);}
+#define DRIVE_FULL {OCR1B = 0; MON_LED(1);}
+#else
+#define DRIVE_OFF {setpin(PIN_DRIVE, 0); MON_LED(0);}
+#define DRIVE_ON {setpin(PIN_DRIVE, 1); MON_LED(1);}
+#define DRIVE_FULL {setpin(PIN_DRIVE, 1); MON_LED(1);}
+#endif
+
+int main(void) {
+ uint16_t i;
+
+#ifdef WITH_INITIAL_PULSE
+ uint8_t drive_last = 0;
+ uint8_t drive_slope = 0;
+#endif
+
+#ifdef USE_REGISTER_VARS
+ drive_on = 0;
+#endif
+ mm_init();
+ load_config();
+ setup_hw();
+
+ while(0) {
+ setpin(PIN_LED, 1);
+ _delay_ms(2);
+ _delay_ms(2);
+ trigger();
+ sei();
+ }
+ if (config.magic != EE_MAGIC) {
+ config.magic = EE_MAGIC;
+ config.op_mode = OM_MOMENTARY;
+ config.decoder_on = 1;
+ config.key_on = 1;
+ config.decoder_off = 1;
+ config.key_off = 2;
+ config.initial_pulse = 10;
+ config.on_duty_cycle[0] = 5;
+ config.learn_mode = LM_LEARN_ON_KEY;
+ }
+ sei();
+ while (1) {
+#ifdef MM_USE_QUEUE
+ dequeue_commands();
+#endif
+ drive_start:
+
+#ifdef WITH_INITIAL_PULSE
+ cli();
+ if (drive_on && !drive_last)
+ drive_slope = 1;
+ else
+ drive_slope = 0;
+ drive_last = drive_on;
+ sei();
+#endif
+ if (drive_on) {
+
+#ifdef WITH_INITIAL_PULSE
+ if (drive_slope) {
+ DRIVE_FULL;
+ for (i = 0; i < config.initial_pulse; i++) {
+ _delay_ms(5);
+ }
+ }
+#endif
+ DRIVE_ON;
+
+ } else {
+ DRIVE_OFF;
+
+ if (!config.learn_mode ||
+ config.learn_mode > LM_LEARN_OP_MODE)
+ continue;
+
+ for (i = 0; i < config.learn_mode; i++) {
+ setpin(PIN_LED, 1);
+ snd_on();
+ _delay_ms(10);
+ setpin(PIN_LED, 0);
+ snd_off();
+ if (drive_on) goto drive_start;
+ _delay_ms(135);
+ if (drive_on) goto drive_start;
+ }
+ for (i = 0; i < 15 - config.learn_mode; i++) {
+ if (drive_on) goto drive_start;
+ _delay_ms(70);
+ }
+ }
+ // MCUCR |= _BV(SE);
+ // sleep();
+ }
+ return 0;
+}
+
+/******************************************************************************
+ * The end :-)
+ */
--- /dev/null
+/******************************************************************************
+ *
+ * Trennfix firmware - main.c
+ *
+ * Copyright (C) 2017 Philipp Hachtmann
+ *
+ * This program 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.
+ *
+ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *****************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <avr/io.h>
+#include <avr/eeprom.h>
+#include <avr/interrupt.h>
+#include <avr/pgmspace.h>
+
+#include <util/delay.h>
+#include <stdint.h>
+#include <mm/mm_switch.h>
+#include <config/hardware.h>
+
+#define EE_MAGIC 0xab
+
+FUSES = {
+ .low = FUSE_CKDIV8 & FUSE_SUT0 &\
+ FUSE_CKSEL3 & FUSE_CKSEL2 & FUSE_CKSEL1,
+ .high = FUSE_SPIEN & FUSE_BODLEVEL1 & FUSE_BODLEVEL0,
+ .extended = EFUSE_DEFAULT,
+};
+enum op_mode {
+ OM_MOMENTARY, /* on as long as "key on" pressed */
+ OM_DOUBLE, /* On off with "on" and "off" keys */
+ OM_TOGGLE, /* toggle on "key on" pressed */
+ OM_ERASED = 0xff /* EEPROM erased, need setup */
+};
+
+enum learn_mode {
+ LM_OFF = 0,
+ LM_LEARN_ON_KEY, /* Learn primary key */
+ LM_LEARN_OFF_KEY, /* Learn secondary key, relevant for OM_DOUBLE */
+ LM_LEARN_INITIAL, /* Learn initial pulse length, 10ms steps */
+ LM_LEARN_DUTY, /* Learn duty cycle 0-10 */
+ LM_LEARN_OP_MODE, /* Learn operation mode */
+ LM_EASY_WAIT_PRESS, /* Wait for key press to enter easy mode */
+ LM_EASY_WAIT_UP, /* Wait for end of key press */
+ LM_EASY_WAIT_TURN, /* Wait for loco 80 turn end */
+ LM_EASY_MODE, /* Easy config mode for PWM and initial kick */
+ LM_END, /* Only a label */
+};
+
+struct config {
+ uint8_t magic; /* Magic value */
+ enum op_mode op_mode;
+ uint8_t decoder_on;
+ uint8_t key_on;
+ uint8_t decoder_off;
+ uint8_t key_off;
+ uint8_t initial_pulse; /* Lenghth of initial pulse in 10ms steps */
+ uint8_t on_duty_cycle; /* Duty cycle for on. 0-10 */
+ volatile enum learn_mode learn_mode;
+};
+
+static struct EEMEM config ee_config;
+static volatile struct config config;
+
+static volatile uint8_t easy_mode = 0;
+static volatile uint8_t easy_mode_possible = 0;
+
+#ifndef USE_REGISTER_VARS
+static volatile uint8_t drive_on = 0;
+#endif
+
+/******************************************************************************
+ *
+ * Some nice sounds to play on your coil
+ *
+ */
+
+#define G 180
+#define sekunde(g)(quinte((quinte(g)))*2)
+#define terz(g) ((g) * 4 / 5)
+#define kleine_terz(g) ((g) * 5 / 6)
+#define quarte(g) ((g) * 3 / 4)
+#define quinte(g) ((g) * 2 / 3)
+#define tt(g) ((g) * 32 / 45)
+#define septime(g) ((g) * 15 / 8)
+
+#if defined(WITH_SOUND) && defined(WITH_PWM)
+void play_tone(uint8_t divisor, uint8_t duration, uint8_t pause)
+{
+ uint16_t c;
+ TCCR1 = 0x8;
+ OCR1C = divisor;
+ c = (divisor * 2) / 3;
+ OCR1B = c;
+ for (c = 0; c < duration - pause; c++)
+ _delay_ms(2);
+ TCCR1 = 0x6;
+ OCR1C = PWM_CYCLE;
+ OCR1B = 12;
+ for (c = 0; c < pause; c++)
+ _delay_ms(2);
+ TCCR1 = 0x6;
+ OCR1C = PWM_CYCLE;
+ OCR1B = PWM_CYCLE;
+}
+
+static void tone_enter(void)
+{
+ play_tone((G), 70, 20);
+ play_tone(terz(G), 70, 20);
+ play_tone(quinte(G), 70, 20);
+ play_tone(G/2, 100, 0);
+}
+
+static void tone_good(void)
+{
+ play_tone(G, 150, 120);
+ play_tone(terz(G), 100, 70);
+ play_tone(tt(G), 100, 50);
+ play_tone(quarte(terz((G))), 50, 0);
+ play_tone(quinte(G), 150, 0);
+ play_tone(terz(G), 100, 50);
+}
+
+static void snd_on(void)
+{
+ TCCR1 = 0x8;
+ OCR1C = 120;
+ OCR1B = 60;
+}
+
+static void snd_off(void)
+{
+ TCCR1 = 0x6;
+ OCR1C = PWM_CYCLE;
+ OCR1B = PWM_CYCLE;
+}
+
+#else
+#define play_tone(...)
+#define tone_enter(...)
+#define tone_good(...)
+#define snd_on(...)
+#define snd_off(...)
+#endif
+
+static void load_config(void)
+{
+ eeprom_read_block((uint8_t *)&config, &ee_config, sizeof(config));
+}
+
+static void save_config(void)
+{
+#ifdef WITH_EEPROM_UPDATE
+ eeprom_update_block((uint8_t *)&config, &ee_config, sizeof(config));
+#else
+ eeprom_write_block((uint8_t *)&config, &ee_config, sizeof(config));
+#endif
+}
+
+void mm_pinchange_callback(void)
+{
+ static uint8_t btn_last = 0;
+
+ if (BTN_PRESSED && !btn_last) {
+ config.learn_mode++;
+ config.learn_mode %= LM_END;
+ }
+ btn_last = BTN_PRESSED;
+}
+
+static uint8_t get_speed(uint8_t command)
+{
+ uint8_t b1, b3, b5, b7;
+
+ b1 = ((command & 0x80) != 0);
+ b3 = ((command & 0x20) != 0);
+ b5 = ((command & 0x8) != 0);
+ b7 = ((command & 0x2) != 0);
+
+ //if ((b1 != b2) || (b2 != b3) || (b3 != b4) || (b5 != b6) || (b7 != b8))
+ // return 0xff;
+ return (b1 + b3 * 2 + b5 * 4 +b7 * 8);
+}
+
+
+void mm_drive_cb(uint8_t loco, uint8_t function, uint8_t command)
+{
+ uint8_t speed;
+ speed = get_speed(command);
+ static uint8_t alert_last = 0;
+
+ switch(loco) {
+ case 10:
+ switch (config.learn_mode) {
+ case LM_OFF:
+ if (speed == 1)
+ config.learn_mode = LM_EASY_WAIT_PRESS;
+ break;
+ case LM_EASY_MODE:
+ if ((speed == 1) && (alert_last == 0)) {
+ config.learn_mode = LM_OFF;
+ save_config();
+ tone_good();
+ }
+ break;
+
+ case LM_EASY_WAIT_PRESS:
+ if (speed != 1)
+ config.learn_mode = LM_OFF;
+ break;
+
+ default:
+ break;
+ }
+ alert_last = (speed == 1);
+ break;
+
+ case 50:
+ if (speed)
+ speed = speed - 1;
+
+ if (config.learn_mode == LM_EASY_MODE) {
+ config.initial_pulse = speed;
+ }
+ break;
+ case 51:
+ if (speed)
+ speed = speed - 1;
+
+ if (config.learn_mode == LM_EASY_MODE)
+ config.on_duty_cycle = speed;
+ break;
+ }
+}
+
+void mm_key_cb(uint8_t decoder, uint8_t function, uint8_t command)
+{
+ static uint8_t toggle_lock = 0;
+
+ /* We don't listen to F1-F4 secret function codes */
+ if (function & 0x3)
+ return;
+
+ switch(config.learn_mode) {
+ case LM_OFF:
+ default:
+ if ((decoder == config.decoder_on) &&
+ (command == config.key_on)) { /* Primary key pressed */
+ switch(config.op_mode) {
+ case OM_MOMENTARY:
+ case OM_DOUBLE:
+ drive_on = 1;
+ break;
+ case OM_TOGGLE:
+ if (!toggle_lock) {
+ drive_on = ~drive_on;
+ toggle_lock = 1;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ if ((decoder == config.decoder_on) &&
+ (command == 0)) { /* Primary key released */
+ switch(config.op_mode) {
+ case OM_MOMENTARY:
+ drive_on = 0;
+ break;
+ case OM_TOGGLE:
+ toggle_lock = 0;
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+
+ case LM_EASY_WAIT_PRESS:
+ if ((decoder == config.decoder_on) &&
+ (command == config.key_on))
+ config.learn_mode = LM_EASY_WAIT_UP;
+ return;
+
+ case LM_EASY_WAIT_UP:
+ if ((decoder == config.decoder_on) &&
+ (command == 0)) {
+ config.learn_mode = LM_EASY_MODE;
+ tone_enter();
+ }
+ return;
+
+#ifdef HANDLE_OFF_KEY
+
+ if ((decoder == config.decoder_off) &&
+ (command == config.key_off)) { /* Secondary "off" key pressed */
+ switch(config.op_mode) {
+ case OM_DOUBLE:
+ drive_on = 0;
+ break;
+ case OM_TOGGLE:
+ case OM_MOMENTARY:
+ default:
+ break;
+ }
+ }
+#endif
+ break;
+
+ case LM_LEARN_ON_KEY:
+ if (command) {
+ config.decoder_on = decoder;
+ config.key_on = command;
+ if (config.op_mode == OM_DOUBLE)
+ config.learn_mode = LM_LEARN_OFF_KEY;
+ else
+ config.learn_mode = LM_OFF;
+ save_config();
+ }
+ break;
+#ifdef LEARN_ADVANCED
+ case LM_LEARN_OFF_KEY:
+ if (command) {
+ config.decoder_off = decoder;
+ config.key_off = command;
+ config.learn_mode = LM_OFF;
+ save_config();
+ }
+ break;
+
+ case LM_LEARN_INITIAL:
+ if (drive_on) {
+ if (command == 0)
+ drive_on = 0;
+
+ } else {
+ switch(command) {
+ case 1:
+ if (config.initial_pulse >= 10)
+ config.initial_pulse -= 10;
+ else
+ config.initial_pulse = 0;
+ save_config();
+ drive_on = 1;
+ break;
+ case 2:
+ if (config.initial_pulse <= 245)
+ config.initial_pulse += 10;
+ else
+ config.initial_pulse = 255;
+ save_config();
+ drive_on = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+
+ case LM_LEARN_DUTY:
+ if (drive_on) {
+ if (command == 0)
+ drive_on = 0;
+ } else {
+ switch(command) {
+ case 1:
+ if (config.on_duty_cycle > 0)
+ config.on_duty_cycle -= 1;
+ save_config();
+ drive_on = 1;
+ break;
+ case 2:
+ if (config.on_duty_cycle < 10)
+ config.on_duty_cycle += 1;
+ save_config();
+ drive_on = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+
+ case LM_LEARN_OP_MODE:
+ switch(command) {
+ case 1:
+ config.op_mode = OM_MOMENTARY;
+ save_config();
+ config.learn_mode = LM_OFF;
+ break;
+ case 3:
+ config.op_mode = OM_DOUBLE;
+ save_config();
+ config.learn_mode = LM_OFF;
+ break;
+ case 5:
+ config.op_mode = OM_TOGGLE;
+ save_config();
+ config.learn_mode = LM_OFF;
+ break;
+ default:
+ break;
+ }
+ break;
+#endif
+ }
+}
+
+
+/******************************************************************************
+ *
+ * main() - The main routine
+ *
+ */
+void shift(uint8_t mu);
+
+#ifdef DEBUG_DRIVE_LED
+#define MON_LED(val) setpin(PIN_LED, val)
+#else
+#define MON_LED(val)
+#endif
+
+#ifdef WITH_PWM
+#define DRIVE_OFF {OCR1B = PWM_CYCLE; MON_LED(0);}
+#define DRIVE_ON {OCR1B = PWM_CYCLE - config.on_duty_cycle; MON_LED(1);}
+#define DRIVE_FULL {OCR1B = 0; MON_LED(1);}
+#else
+#define DRIVE_OFF {setpin(PIN_DRIVE, 0); MON_LED(0);}
+#define DRIVE_ON {setpin(PIN_DRIVE, 1); MON_LED(1);}
+#define DRIVE_FULL {setpin(PIN_DRIVE, 1); MON_LED(1);}
+#endif
+
+int main(void) {
+ uint16_t i;
+
+#ifdef WITH_INITIAL_PULSE
+ uint8_t drive_last = 0;
+ uint8_t drive_slope = 0;
+#endif
+
+#ifdef USE_REGISTER_VARS
+ drive_on = 0;
+#endif
+ mm_init();
+ load_config();
+ setup_hw();
+
+ while(0) {
+ setpin(PIN_LED, 1);
+ _delay_ms(2);
+ _delay_ms(2);
+ trigger();
+ sei();
+ }
+ if (config.magic != EE_MAGIC) {
+ config.magic = EE_MAGIC;
+ config.op_mode = OM_MOMENTARY;
+ config.decoder_on = 1;
+ config.key_on = 1;
+ config.decoder_off = 1;
+ config.key_off = 2;
+ config.initial_pulse = 10;
+ config.on_duty_cycle = 5;
+ config.learn_mode = LM_LEARN_ON_KEY;
+ }
+ sei();
+ while (1) {
+ drive_start:
+
+#ifdef WITH_INITIAL_PULSE
+ cli();
+ if (drive_on && !drive_last)
+ drive_slope = 1;
+ else
+ drive_slope = 0;
+ drive_last = drive_on;
+ sei();
+#endif
+ if (drive_on) {
+
+#ifdef WITH_INITIAL_PULSE
+ if (drive_slope) {
+ DRIVE_FULL;
+ for (i = 0; i < config.initial_pulse; i++) {
+ _delay_ms(5);
+ }
+ }
+#endif
+ DRIVE_ON;
+
+ } else {
+ DRIVE_OFF;
+
+ if (!config.learn_mode ||
+ config.learn_mode > LM_LEARN_OP_MODE)
+ continue;
+
+ for (i = 0; i < config.learn_mode; i++) {
+ setpin(PIN_LED, 1);
+ snd_on();
+ _delay_ms(10);
+ setpin(PIN_LED, 0);
+ snd_off();
+ if (drive_on) goto drive_start;
+ _delay_ms(135);
+ if (drive_on) goto drive_start;
+ }
+ for (i = 0; i < 15 - config.learn_mode; i++) {
+ if (drive_on) goto drive_start;
+ _delay_ms(70);
+ }
+ }
+ }
+ return 0;
+}
+
+/******************************************************************************
+ * The end :-)
+ */