From: Philipp Hachtmann Date: Tue, 28 Feb 2017 16:59:18 +0000 (+0100) Subject: trennfix/sw: Added smokefix, further modularized the mm decoder X-Git-Url: http://gitweb.hachti.de/?p=eisenbahn.git;a=commitdiff_plain;h=f12652dfe40fa03634eb01b2cdda861a46e46a03 trennfix/sw: Added smokefix, further modularized the mm decoder The mm decoder part will probably be renamed to "railway" or some other more generic term when it also supports DCC. Signed-off-by: Philipp Hachtmann --- diff --git a/trennfix/sw/Makefile b/trennfix/sw/Makefile index f096339..0ad4b1b 100644 --- a/trennfix/sw/Makefile +++ b/trennfix/sw/Makefile @@ -1,129 +1,37 @@ -#---------------------------------------------------------------------------- -# -# 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__="" - -# 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__="" -#---------------- 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 @@ -137,7 +45,6 @@ PRINTF_LIB = #PRINTF_LIB = $(PRINTF_LIB_MIN) #PRINTF_LIB = $(PRINTF_LIB_FLOAT) - # Minimalistic scanf version SCANF_LIB_MIN = -Wl,-u,vfscanf -lscanf_min @@ -149,33 +56,8 @@ SCANF_LIB = #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) ---------------- @@ -185,16 +67,11 @@ LDFLAGS += $(PRINTF_LIB) $(SCANF_LIB) $(MATH_LIB) # 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 @@ -216,40 +93,7 @@ AVRDUDE_FLAGS = -p $(MCU) -P $(AVRDUDE_PORT) -c $(AVRDUDE_PROGRAMMER) 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 @@ -259,235 +103,130 @@ OBJDUMP = avr-objdump 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)))) diff --git a/trennfix/sw/config/trennfix_0.4.h b/trennfix/sw/config/trennfix_0.4.h index dbce67f..ffa98a9 100644 --- a/trennfix/sw/config/trennfix_0.4.h +++ b/trennfix/sw/config/trennfix_0.4.h @@ -12,7 +12,7 @@ #define STOP_TIMER1 ({TCCR1 = 0; TCNT1 = 1;}) -#define PWM_CYCLE 21 +#define PWM_CYCLE 14 static inline void setup_hw(void) { @@ -20,16 +20,15 @@ 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 */ @@ -39,12 +38,11 @@ static inline void setup_hw(void) /* 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; @@ -67,7 +65,6 @@ static inline void __attribute__((unused)) trigger(void) setpin(PIN_TRIGGER, 0); } - static inline void __attribute__((unused)) trigger_on(void) { setpin(PIN_TRIGGER, 1); @@ -91,7 +88,9 @@ static inline void __attribute__((unused)) trigger_off(void) } #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"); diff --git a/trennfix/sw/mk/hw/trennfix_0.4.mk b/trennfix/sw/mk/hw/trennfix_0.4.mk new file mode 100644 index 0000000..1e14a25 --- /dev/null +++ b/trennfix/sw/mk/hw/trennfix_0.4.mk @@ -0,0 +1,16 @@ +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 + + diff --git a/trennfix/sw/mk/prog/smokefix.mk b/trennfix/sw/mk/prog/smokefix.mk new file mode 100644 index 0000000..1e1d516 --- /dev/null +++ b/trennfix/sw/mk/prog/smokefix.mk @@ -0,0 +1,3 @@ + +PROGRAMS+=smokefix +smokefix_SRC=src/smokefix_main.c mm/src/mm_switch.c diff --git a/trennfix/sw/mk/prog/trennfix.mk b/trennfix/sw/mk/prog/trennfix.mk new file mode 100644 index 0000000..c5888b4 --- /dev/null +++ b/trennfix/sw/mk/prog/trennfix.mk @@ -0,0 +1,3 @@ + +PROGRAMS+=trennfix +trennfix_SRC=src/trennfix_main.c mm/src/mm_switch.c diff --git a/trennfix/sw/mk/trennfix_0.4.mk b/trennfix/sw/mk/trennfix_0.4.mk deleted file mode 100644 index b3558ce..0000000 --- a/trennfix/sw/mk/trennfix_0.4.mk +++ /dev/null @@ -1,33 +0,0 @@ - -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 - diff --git a/trennfix/sw/mm/include/mm/mm_decode.h b/trennfix/sw/mm/include/mm/mm_decode.h new file mode 100644 index 0000000..5b5b7b2 --- /dev/null +++ b/trennfix/sw/mm/include/mm/mm_decode.h @@ -0,0 +1,124 @@ +#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 + * + * <00> + * + * 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: + * + * <00> 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 + * 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 diff --git a/trennfix/sw/mm/include/mm/mm_switch.h b/trennfix/sw/mm/include/mm/mm_switch.h index 240bea9..fb6ccca 100644 --- a/trennfix/sw/mm/include/mm/mm_switch.h +++ b/trennfix/sw/mm/include/mm/mm_switch.h @@ -33,17 +33,17 @@ static void inline __attribute((unused)) mm_init(void) #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 @@ -52,17 +52,33 @@ static void inline __attribute((unused)) mm_init(void) * 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 diff --git a/trennfix/sw/mm/src/mm_switch.c b/trennfix/sw/mm/src/mm_switch.c index 35016fc..a960355 100644 --- a/trennfix/sw/mm/src/mm_switch.c +++ b/trennfix/sw/mm/src/mm_switch.c @@ -35,7 +35,7 @@ #include #include - +#include /* * @@ -46,18 +46,48 @@ #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 @@ -73,126 +103,6 @@ static uint8_t mm_polarity = 1; #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 - * - * <00> - * - * 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: - * - * <00> 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 - * 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 @@ -205,7 +115,7 @@ static uint8_t __attribute__((unused)) lookup_command(uint8_t mm_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) @@ -219,7 +129,7 @@ void shift(uint8_t value) } #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" @@ -242,7 +152,6 @@ static void mm_feed_bit(uint8_t bit) static volatile uint8_t shift_function_first; static volatile uint8_t shift_address_first; uint8_t address; - uint8_t command; shift(bit); mm_bitno++; @@ -251,27 +160,48 @@ static void mm_feed_bit(uint8_t bit) 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 @@ -331,14 +261,14 @@ void __attribute__((naked)) PCINT0_vect(void) ); #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); @@ -347,131 +277,81 @@ ISR(__vector_pinchange){ 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) { } diff --git a/trennfix/sw/src/main.c b/trennfix/sw/src/main.c deleted file mode 100644 index 3e7d265..0000000 --- a/trennfix/sw/src/main.c +++ /dev/null @@ -1,514 +0,0 @@ -/****************************************************************************** - * - * 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 . - * - *****************************************************************************/ - -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include - -#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 :-) - */ diff --git a/trennfix/sw/src/smokefix_main.c b/trennfix/sw/src/smokefix_main.c new file mode 100644 index 0000000..594f77f --- /dev/null +++ b/trennfix/sw/src/smokefix_main.c @@ -0,0 +1,731 @@ +/****************************************************************************** + * + * 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 . + * + *****************************************************************************/ + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +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 :-) + */ diff --git a/trennfix/sw/src/trennfix_main.c b/trennfix/sw/src/trennfix_main.c new file mode 100644 index 0000000..9baf6c2 --- /dev/null +++ b/trennfix/sw/src/trennfix_main.c @@ -0,0 +1,535 @@ +/****************************************************************************** + * + * 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 . + * + *****************************************************************************/ + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#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 :-) + */