From f0fca34b7e22c82fe37ba599f7508b5a7f8de7a4 Mon Sep 17 00:00:00 2001 From: bsongis Date: Tue, 13 Sep 2011 18:11:01 +0000 Subject: [PATCH] Backup before extended trims function merge --- src/Makefile | 850 +++++++++ src/diskio-test.cpp | 62 + src/diskio.cpp | 631 +++++++ src/diskio.h | 103 ++ src/drivers.cpp | 445 +++++ src/eeprom_v3.h | 135 ++ src/eeprom_v4.h | 158 ++ src/ff.cpp | 3736 ++++++++++++++++++++++++++++++++++++++ src/ff.h | 541 ++++++ src/ffconf.h | 188 ++ src/file.cpp | 682 +++++++ src/file.h | 174 ++ src/font.lbm | 9 + src/font_6x1.png | Bin 0 -> 6732 bytes src/font_6x1.xbm | 44 + src/font_dblsize.lbm | 15 + src/font_dblsize.png | Bin 0 -> 7789 bytes src/font_dblsize.xbm | 164 ++ src/frsky.cpp | 460 +++++ src/frsky.h | 62 + src/fuses_2561.txt | 36 + src/general_menus.cpp | 552 ++++++ src/gruvin9x.cpp | 2243 +++++++++++++++++++++++ src/gruvin9x.h | 678 +++++++ src/gtests.cpp | 218 +++ src/integer.h | 37 + src/jeti.cpp | 200 ++ src/jeti.h | 49 + src/lcd.cpp | 670 +++++++ src/lcd.h | 139 ++ src/main_views.cpp | 416 +++++ src/menus.cpp | 514 ++++++ src/menus.h | 129 ++ src/model_menus.cpp | 1773 ++++++++++++++++++ src/myeeprom.h | 256 +++ src/pers.cpp | 411 +++++ src/rtc.cpp | 259 +++ src/rtc.h | 23 + src/s9xsplash.lbm | 11 + src/s9xsplash.xbm | 89 + src/s9xsplash.xcf | Bin 0 -> 13358 bytes src/simpgmspace.cpp | 104 ++ src/simpgmspace.h | 85 + src/simu.cpp | 442 +++++ src/stamp-gruvin9x.h | 6 + src/stamp.cpp | 13 + src/statistics_views.cpp | 105 ++ src/sticks.lbm | 7 + src/sticks_4x1.xbm | 11 + src/templates.cpp | 207 +++ src/templates.h | 88 + src/time.cpp | 463 +++++ src/time.h | 34 + 53 files changed, 18727 insertions(+) create mode 100644 src/Makefile create mode 100644 src/diskio-test.cpp create mode 100644 src/diskio.cpp create mode 100644 src/diskio.h create mode 100644 src/drivers.cpp create mode 100644 src/eeprom_v3.h create mode 100644 src/eeprom_v4.h create mode 100644 src/ff.cpp create mode 100644 src/ff.h create mode 100644 src/ffconf.h create mode 100644 src/file.cpp create mode 100644 src/file.h create mode 100644 src/font.lbm create mode 100644 src/font_6x1.png create mode 100644 src/font_6x1.xbm create mode 100644 src/font_dblsize.lbm create mode 100644 src/font_dblsize.png create mode 100644 src/font_dblsize.xbm create mode 100644 src/frsky.cpp create mode 100644 src/frsky.h create mode 100644 src/fuses_2561.txt create mode 100644 src/general_menus.cpp create mode 100644 src/gruvin9x.cpp create mode 100644 src/gruvin9x.h create mode 100644 src/gtests.cpp create mode 100644 src/integer.h create mode 100644 src/jeti.cpp create mode 100644 src/jeti.h create mode 100644 src/lcd.cpp create mode 100644 src/lcd.h create mode 100644 src/main_views.cpp create mode 100644 src/menus.cpp create mode 100644 src/menus.h create mode 100644 src/model_menus.cpp create mode 100644 src/myeeprom.h create mode 100644 src/pers.cpp create mode 100644 src/rtc.cpp create mode 100644 src/rtc.h create mode 100644 src/s9xsplash.lbm create mode 100644 src/s9xsplash.xbm create mode 100644 src/s9xsplash.xcf create mode 100644 src/simpgmspace.cpp create mode 100644 src/simpgmspace.h create mode 100644 src/simu.cpp create mode 100644 src/stamp-gruvin9x.h create mode 100644 src/stamp.cpp create mode 100644 src/statistics_views.cpp create mode 100644 src/sticks.lbm create mode 100644 src/sticks_4x1.xbm create mode 100644 src/templates.cpp create mode 100644 src/templates.h create mode 100644 src/time.cpp create mode 100644 src/time.h diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 000000000..70286e2da --- /dev/null +++ b/src/Makefile @@ -0,0 +1,850 @@ +# !!!! BETA Makefile !!!! +# !!!! Use at own risk !!!! +#---------------------------------------------------------------------------- +# 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". +#---------------------------------------------------------------------------- + +#----------- BUILD OPTIONS --------------------------- + +#gruvin: PCB version -- OVERRIDES the following settings if not STD +# Values: STD, V3, V4 +PCB = V3 + +# Following options for PCB=STD only (ignored otherwise) ... + +# Enable JETI-Telemetry or FrSky Telemetry reception on UART0 +# For this option you need to modify your hardware! +# More information at [insertURLhere] +# Values = STD, JETI, FRSKY +EXT = FRSKY + +# Enable heli menu +# Values = YES, NO +HELI = NO + +# Enable templates menu +# Values = YES, NO +TEMPLATES = YES + +# gruvin: BEEPER. Values = BUZZER, BUZZER_MOD or SPEAKER +# (without a mod, BUZZER can make PPM jack output switch from output to input at random) +BEEPER = SPEAKER + +# gruvin: Legacy support freeing of USART1 TX/RX pins [DEPRECATED] +# OPTIONS STD or FREED +USART1 = FREED + +# gruvin: PCM-in circuit mod for JR/Spektrum (and others) compatability +# Values = STD, MOD1 +PPMIN = STD + +BATT voltage algorithm. Values = BANDGAP, UNSTABLE_BANDGAP +BATT = BANDGAP + +# Decimals display in the main view (PPM calibration, +# Values = YES, NO +DECIMALS = NO + +# TRANSLATIONS from previous EEPROM formats +# Values = YES, NO +TRANSLATIONS = YES + +#------- END BUILD OPTIONS --------------------------- + +# MCU name +ifeq ($(PCB), STD) +MCU = atmega64 +endif +ifeq ($(PCB), V3) +MCU = atmega2561 +endif +ifeq ($(PCB), V4) +MCU = atmega2560 +endif + +# Processor frequency. +F_CPU = 16000000 + +# Output format. (can be srec, ihex, binary) +FORMAT = ihex + +# Target file name (without extension). +TARGET = gruvin9x + +# Object files directory +OBJDIR = obj + +# List C++ source files here. (C dependencies are automatically generated.) +CPPSRC = gruvin9x.cpp stamp.cpp menus.cpp model_menus.cpp general_menus.cpp main_views.cpp statistics_views.cpp pers.cpp file.cpp lcd.cpp drivers.cpp templates.cpp + +ifeq ($(EXT), JETI) + CPPSRC += jeti.cpp +endif + +ifeq ($(EXT), FRSKY) + CPPSRC += frsky.cpp +endif + +# Disk IO support (PCB V2+ only) +ifneq ($(PCB), STD) + CPPSRC += time.cpp + CPPSRC += rtc.cpp + CPPSRC += ff.cpp + CPPSRC += diskio.cpp +endif + +# List Assembler source files here. +# Make them always end in a capital .S. Files ending in a lowercase .s +# will not be considered source files but generated files (assembler +# output from the compiler), and will be deleted upon "make clean"! +# Even though the DOS/Win* filesystem matches both .s and .S the same, +# it will preserve the spelling of the filenames, and gcc itself does +# care about how the name is spelled on its command-line. +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 = + + +# 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 for C sources +CDEFS = -DF_CPU=$(F_CPU)UL + + +# Place -D or -U options here for C++ sources +CPPDEFS = -DF_CPU=$(F_CPU)UL +#CPPDEFS += -D__STDC_LIMIT_MACROS +#CPPDEFS += -D__STDC_CONSTANT_MACROS + +# NOTE: PCB version now overrides all the earlier individual settings +# These individual settings work only for PCB=STD + +ifeq ($(PCB), STD) +# STD PCB, so ... + + CPPDEFS += -DPCBSTD + +# If JETI-Support is enabled + ifeq ($(EXT), JETI) + CPPDEFS += -DJETI + endif + +# If FRSKY-Support is enabled + ifeq ($(EXT), FRSKY) + CPPDEFS += -DFRSKY + endif + +# gruvin: If buzzer speaker replacment supported + ifeq ($(BEEPER), SPEAKER) + CPPDEFS += -DBEEPSPKR + endif + +# If buzzer modified (no interference with PPM jack) + ifeq ($(BEEPER), BUZZER_MOD) + CPPDEFS += -DBUZZER_MOD + endif + +# If BandGap is not rock solid + ifeq ($(BATT), UNSTABLE_BANDGAP) + CPPDEFS += -DBATT_UNSTABLE_BANDGAP + endif + +# gruvin: Legacy support for hardware mod freeing USART1 [DEPRECATED] + ifeq ($(USART1), FREED) + CPPDEFS += -DUSART1FREED + endif + +# gruvin: PCM-in circuit mod for JR/Spektrum (and others) compatability + ifeq ($(PPMIN), MOD1) + CPPDEFS += -DPPMIN_MOD1 + endif + +else +# not PCB=STD, so ... + ifeq ($(PCB), V3) + CPPDEFS += -DPCBV3 -DFRSKY -DBEEPSPKR + endif + ifeq ($(PCB), V4) + CPPDEFS += -DPCBV4 -DFRSKY -DBEEPSPKR + endif +endif + + +### Global Build-Option Directives ### + +ifeq ($(DECIMALS), YES) + CPPDEFS += -DDECIMALS_DISPLAYED +endif + +ifeq ($(TRANSLATIONS), YES) + CPPDEFS += -DTRANSLATIONS +endif + +ifeq ($(HELI), YES) + CPPDEFS += -DHELI +endif + +ifeq ($(TEMPLATES), YES) + CPPDEFS += -DTEMPLATES +endif + + +#---------------- Compiler Options C ---------------- +# -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) +CFLAGS += -O$(OPT) +#CFLAGS += -mint8 +#CFLAGS += -mshort-calls +CFLAGS += -funsigned-char +CFLAGS += -funsigned-bitfields +CFLAGS += -fpack-struct +CFLAGS += -fshort-enums +#CFLAGS += -fno-unit-at-a-time +CFLAGS += -Wall +CFLAGS += -Wstrict-prototypes +CFLAGS += -Wundef +#CFLAGS += -Wunreachable-code +#CFLAGS += -Wsign-compare +CFLAGS += -Wa,-adhlns=$(<:%.c=$(OBJDIR)/%.lst) +CFLAGS += $(patsubst %,-I%,$(EXTRAINCDIRS)) +CFLAGS += $(CSTANDARD) + +CFLAGS+= --combine -fwhole-program + + +#---------------- Compiler Options C++ ---------------- +# -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 +CPPFLAGS = -g$(DEBUG) +CPPFLAGS += $(CPPDEFS) +CPPFLAGS += -O$(OPT) +#CPPFLAGS += -mint8 +#CPPFLAGS += -mshort-calls +#CPPFLAGS += -funsigned-char +#CPPFLAGS += -funsigned-bitfields +#CPPFLAGS += -fpack-struct +#CPPFLAGS += -fshort-enums +#CPPFLAGS += -fno-exceptions +#CPPFLAGS += -fno-unit-at-a-time +#CPPFLAGS += -fno-inline-small-functions +CPPFLAGS += -Wall +CPPFLAGS += -Wno-strict-aliasing +#CPPFLAGS += -Wstrict-prototypes +#CFLAGS += -Wundef +#CPPFLAGS += -Wunreachable-code +#CPPFLAGS += -Wsign-compare +#CPPFLAGS += -Wa,-adhlns=$(<:%.cpp=$(OBJDIR)/%.lst) +CPPFLAGS += $(patsubst %,-I%,$(EXTRAINCDIRS)) +#CPPFLAGS += $(CSTANDARD) + + + +#---------------- 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=$(OBJDIR)/%.lst),-gstabs + + +#---------------- Library Options ---------------- +# Minimalistic printf version +PRINTF_LIB_MIN = -Wl,-u,vfprintf -lprintf_min + +# Floating point printf version (requires MATH_LIB = -lm below) +PRINTF_LIB_FLOAT = -Wl,-u,vfprintf -lprintf_flt + +# If this is left blank, then it will use the Standard printf version. +PRINTF_LIB = +#PRINTF_LIB = $(PRINTF_LIB_MIN) +#PRINTF_LIB = $(PRINTF_LIB_FLOAT) + + +# Minimalistic scanf version +SCANF_LIB_MIN = -Wl,-u,vfscanf -lscanf_min + +# Floating point + %[ scanf version (requires MATH_LIB = -lm below) +SCANF_LIB_FLOAT = -Wl,-u,vfscanf -lscanf_flt + +# If this is left blank, then it will use the Standard scanf version. +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 = -Wl,-Map=$(TARGET).map,--cref +LDFLAGS += $(EXTMEMOPTS) +LDFLAGS += $(PRINTF_LIB) $(SCANF_LIB) $(MATH_LIB) +#LDFLAGS += -T linker_script.x + + + +#---------------- Programming Options (avrdude) ---------------- + +# Programming hardware: alf avr910 avrisp bascom bsd +# dt006 pavr picoweb pony-stk200 sp12 stk200 stk500 +# +# Type: avrdude -c ? +# to get a full listing. +# +AVRDUDE_PROGRAMMER = usbtiny + + +# com1 = serial port. Use lpt1 to connect to parallel port. +AVRDUDE_PORT = /dev/ttyUSB01 + +AVRDUDE_WRITE_FLASH = -U flash:w:$(TARGET).hex:a +AVRDUDE_WRITE_EEPROM = -U eeprom:w:$(TARGET).bin:a +AVRDUDE_READ_FLASH = -U flash:r:$(TARGET).hex:r +AVRDUDE_READ_EEPROM = -U eeprom:r:$(TARGET).bin:r + + + +# Uncomment the following if you want avrdude's erase cycle counter. +# Note that this counter needs to be initialized first using -Yn, +# see avrdude manual. +#AVRDUDE_ERASE_COUNTER = -y + +# Uncomment the following if you do /not/ wish a verification to be +# performed after programming the device. +AVRDUDE_NO_VERIFY = -V + +# Increase verbosity level. Please use this when submitting bug +# reports about avrdude. See +# to submit bug reports. +#AVRDUDE_VERBOSE = -v -v + +#AVRDUDE_FLAGS = -p $(MCU) -P $(AVRDUDE_PORT) -c $(AVRDUDE_PROGRAMMER) +AVRDUDE_FLAGS = -B 1 -p $(MCU) -c $(AVRDUDE_PROGRAMMER) +AVRDUDE_FLAGS += $(AVRDUDE_NO_VERIFY) +AVRDUDE_FLAGS += $(AVRDUDE_VERBOSE) +AVRDUDE_FLAGS += $(AVRDUDE_ERASE_COUNTER) + + + +#---------------- 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 + + + +#============================================================================ + + +# Define programs and commands. +SHELL = sh +CC = avr-gcc +OBJCOPY = avr-objcopy +OBJDUMP = avr-objdump +SIZE = avr-size +NM = avr-nm +AVRDUDE = avrdude +REMOVE = rm -f +REMOVEDIR = rm -rf +COPY = cp +WINSHELL = cmd + +# th9x/gruvin9x-specific +XBM2LBM = ruby ../util/xbm2lbm.rb +AREV = $(shell sh -c "cat .svn/entries | sed -n '4p'") +REV = $(shell echo $$(( $(AREV) + 1 ))) + + +# 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 C: +MSG_COMPILING_CPP = Compiling C++: +MSG_ASSEMBLING = Assembling: +MSG_CLEANING = Cleaning project: +MSG_CREATING_LIBRARY = Creating library: + + + + +# Define all object files. +OBJ = $(SRC:%.c=$(OBJDIR)/%.o) $(CPPSRC:%.cpp=$(OBJDIR)/%.o) $(ASRC:%.S=$(OBJDIR)/%.o) + +# Define all listing files. +LST = $(SRC:%.c=$(OBJDIR)/%.lst) $(CPPSRC:%.cpp=$(OBJDIR)/%.lst) $(ASRC:%.S=$(OBJDIR)/%.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_CPPFLAGS = -mmcu=$(MCU) -I. -x c++ $(CPPFLAGS) $(GENDEPFLAGS) +ALL_ASFLAGS = -mmcu=$(MCU) -I. -x assembler-with-cpp $(ASFLAGS) + +SUB_VER = ${shell sh -c "grep \"SUB_VERS\" gruvin9x.h | cut -d\ -f3 | egrep -o \"[[:digit:]]\""} +ABUILD_NUM = ${shell sh -c "grep \"BUILD_NUM\" stamp-gruvin9x.h | egrep -o \"[[:digit:]]+\""} +BUILD_NUM = $(shell echo $$(( $(ABUILD_NUM) + 1 ))) +BUILD_DIR = $(shell pwd | awk -F'/' '{print $$((NF-1))}') +ifeq "$(USER)" "bryan" + THEUSER=gruvin +else + THEUSER=$(USER) +endif + +# Default target. +all: begin gccversion sizebefore build sizeafter end + +# Change the build target to build a HEX file or a library. +build: stamp font.lbm font_dblsize.lbm sticks.lbm s9xsplash.lbm elf hex eep lss sym +#build: lib + + +elf: $(TARGET).elf +hex: $(TARGET).hex +eep: $(TARGET).eep +lss: $(TARGET).lss +sym: $(TARGET).sym +LIBNAME=lib$(TARGET).a +lib: $(LIBNAME) + + +# Build stamp-file +stamp: + @echo + @echo "Generate Version-stamp:" + @echo "//Automatically generated file (Makefile) - do not edit" > stamp-gruvin9x.h + @echo "#define DATE_STR \"`date +%Y-%m-%d`\"" >> stamp-gruvin9x.h + @echo "#define TIME_STR \"`date +%H:%I:%S`\"" >> stamp-gruvin9x.h + @echo "#define TAG_VERS $(SUB_VER)-$(THEUSER)" >> stamp-gruvin9x.h + @echo "#define SVN_VERS \"$(BUILD_DIR)-r$(REV)\"" >> stamp-gruvin9x.h + @echo "#define BUILD_NUM $(BUILD_NUM)" >> stamp-gruvin9x.h + @cat stamp-gruvin9x.h + +font.lbm: font_6x1.xbm + @echo + @echo "Convert font from xbm to lbm:" + $(XBM2LBM) $< + +font_dblsize.lbm: font_dblsize.xbm + @echo + @echo "Convert font from xbm to lbm:" + $(XBM2LBM) $< + +sticks.lbm: sticks_4x1.xbm + @echo + @echo "Convert font from xbm to lbm:" + $(XBM2LBM) $< + +s9xsplash.lbm: s9xsplash.xbm + @echo + @echo "Convert font from xbm to lbm:" + $(XBM2LBM) $< + +# 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 + + + +# Display size of file. +HEXSIZE = $(SIZE) --target=$(FORMAT) $(TARGET).hex +ELFSIZE = $(SIZE) --mcu=$(MCU) --format=avr $(TARGET).elf +AVRMEM = avr-mem.sh $(TARGET).elf $(MCU) + +sizebefore: + @if test -f $(TARGET).elf; then echo; echo $(MSG_SIZE_BEFORE); $(ELFSIZE); \ + $(AVRMEM) 2>/dev/null; echo; fi + +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. +wflash: $(TARGET).hex + $(AVRDUDE) $(AVRDUDE_FLAGS) $(AVRDUDE_WRITE_FLASH) + +weeprom: $(TARGET).bin + $(AVRDUDE) $(AVRDUDE_FLAGS) $(AVRDUDE_WRITE_EEPROM) + +# Write flash and eeprom +wfe: $(TARGET).hex + $(AVRDUDE) $(AVRDUDE_FLAGS) $(AVRDUDE_WRITE_FLASH) + $(AVRDUDE) $(AVRDUDE_FLAGS) $(AVRDUDE_WRITE_EEPROM) + +rflash: $(TARGET).hex + $(AVRDUDE) $(AVRDUDE_FLAGS) $(AVRDUDE_READ_FLASH) + +reeprom: $(TARGET).bin + $(AVRDUDE) $(AVRDUDE_FLAGS) $(AVRDUDE_READ_EEPROM) + + +# 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) + +# gruvin: added extra include and lib paths to get simu working on my Mac +FOXINC=-I/usr/local/include/fox-1.6 -I/usr/include/fox-1.6 \ + -I$(FOXPATH)/include \ + -I/opt/local/include/fox-1.6 +FOXLIB=-L/usr/local/lib \ + -L$(FOXPATH)/src/.libs \ + -L/opt/local/lib \ + -lFOX-1.6 \ + -Wl,-rpath,$(FOXPATH)/src/.libs + +LBITS := $(shell getconf LONG_BIT) +ifeq ($(LBITS),64) + ARCH=-arch x86_64 +else + ARCH= +endif + +simu: $(CPPSRC) Makefile simu.cpp $(CPPSRC) simpgmspace.cpp *.h *.lbm eeprom.bin + g++ simu.cpp $(CPPFLAGS) $(CPPSRC) simpgmspace.cpp $(ARCH) -o simu $(FOXINC) $(FOXLIB) -MD -DSIMU + +eeprom.bin: + dd if=/dev/zero of=$@ bs=1 count=2048 + +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 + COFFCONVERT += --change-section-address .data-0x800000 + COFFCONVERT += --change-section-address .bss-0x800000 + COFFCONVERT += --change-section-address .noinit-0x800000 + COFFCONVERT += --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 + + + +# Create final output files (.hex, .eep) from ELF output file. +%.hex: %.elf + @echo + @echo $(MSG_FLASH) $@ + $(OBJCOPY) -O $(FORMAT) -R .eeprom $< $@ + +%.eep: %.elf + @echo + @echo $(MSG_EEPROM) $@ + -$(OBJCOPY) -j .eeprom --set-section-flags=.eeprom="alloc,load" \ +--change-section-lma .eeprom=0 -O $(FORMAT) $< $@ + +# Create extended listing file from ELF output file. +%.lss: %.elf + @echo + @echo $(MSG_EXTENDED_LISTING) $@ + $(OBJDUMP) -h -S $< > $@ + +# Create a symbol table from ELF output file. +%.sym: %.elf + @echo + @echo $(MSG_SYMBOL_TABLE) $@ + $(NM) -n $< > $@ + + + +# Create library from object files. +.SECONDARY : $(TARGET).a +.PRECIOUS : $(OBJ) +%.a: $(OBJ) + @echo + @echo $(MSG_CREATING_LIBRARY) $@ + $(AR) $@ $(OBJ) + + +# Link: create ELF output file from object files. +.SECONDARY : $(TARGET).elf +.PRECIOUS : $(OBJ) +%.elf: $(OBJ) + @echo + @echo $(MSG_LINKING) $@ + $(CC) $(ALL_CFLAGS) $^ --output $@ $(LDFLAGS) + + +# Compile: create object files from C source files. +$(OBJDIR)/%.o : %.c + @echo + @echo $(MSG_COMPILING) $< + $(CC) -c $(ALL_CFLAGS) $< -o $@ + + +# Compile: create object files from C++ source files. +$(OBJDIR)/%.o : %.cpp + @echo + @echo $(MSG_COMPILING_CPP) $< + $(CC) -c $(ALL_CPPFLAGS) $< -o $@ + + +# Compile: create assembler files from C source files. +%.s : %.c + $(CC) -S $(ALL_CFLAGS) $< -o $@ + + +# Compile: create assembler files from C++ source files. +%.s : %.cpp + $(CC) -S $(ALL_CPPFLAGS) $< -o $@ + + +# Assemble: create object files from assembler source files. +$(OBJDIR)/%.o : %.S + @echo + @echo $(MSG_ASSEMBLING) $< + $(CC) -c $(ALL_ASFLAGS) $< -o $@ + + +# Create preprocessed source for use in sending a bug report. +%.i : %.c + $(CC) -E -mmcu=$(MCU) -I. $(CFLAGS) $< -o $@ + + +# Target: clean project. +clean: begin clean_list end + +clean_list : + @echo + @echo $(MSG_CLEANING) + $(REMOVE) simu + $(REMOVE) gtests + $(REMOVE) gtest.a + $(REMOVE) gtest_main.a + $(REMOVE) $(TARGET).hex + $(REMOVE) $(TARGET).eep + $(REMOVE) $(TARGET).cof + $(REMOVE) $(TARGET).elf + $(REMOVE) $(TARGET).map + $(REMOVE) $(TARGET).sym + $(REMOVE) $(TARGET).lss + $(REMOVEDIR) $(OBJDIR) + $(REMOVE) $(SRC:.c=.s) + $(REMOVE) *.o + $(REMOVE) *.d + $(REMOVEDIR) .dep + + +# Create object files directory +$(shell mkdir $(OBJDIR) 2>/dev/null) + + +# Include the dependency files. +-include $(shell mkdir .dep 2>/dev/null) $(wildcard .dep/*) + + +# 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 stamp + + +#### GOOGLE TESTS + +GTEST_DIR = ../gtest-1.6.0/ + +# Where to find user code. +USER_DIR = ./ + +# Flags passed to the preprocessor. +CPPFLAGS += -I$(GTEST_DIR)/include + +# Flags passed to the C++ compiler. +CXXFLAGS += -g -Wall -Wextra + +# All Google Test headers. Usually you shouldn't change this +# definition. +GTEST_HEADERS = $(GTEST_DIR)/include/gtest/*.h \ + $(GTEST_DIR)/include/gtest/internal/*.h + +# House-keeping build targets. + +# Builds gtest.a and gtest_main.a. + +# Usually you shouldn't tweak such internal variables, indicated by a +# trailing _. +GTEST_SRCS_ = $(GTEST_DIR)/src/*.cc $(GTEST_DIR)/src/*.h $(GTEST_HEADERS) + +# For simplicity and to avoid depending on Google Test's +# implementation details, the dependencies specified below are +# conservative and not optimized. This is fine as Google Test +# compiles fast and for ordinary users its source rarely changes. +gtest-all.o : $(GTEST_SRCS_) + $(CXX) $(CPPFLAGS) -I$(GTEST_DIR) $(CXXFLAGS) -c \ + $(GTEST_DIR)/src/gtest-all.cc + +gtest_main.o : $(GTEST_SRCS_) + $(CXX) $(CPPFLAGS) -I$(GTEST_DIR) $(CXXFLAGS) -c \ + $(GTEST_DIR)/src/gtest_main.cc + +gtest.a : gtest-all.o + $(AR) $(ARFLAGS) $@ $^ + +gtest_main.a : gtest-all.o gtest_main.o + $(AR) $(ARFLAGS) $@ $^ + +# Builds a sample test. A test should link with either gtest.a or +# gtest_main.a, depending on whether it defines its own main() +# function. + +gtests: $(CPPSRC) gtests.cpp simpgmspace.cpp gtest_main.a + g++ $(CPPSRC) gtests.cpp simpgmspace.cpp $(CPPFLAGS) -I$(GTEST_DIR) $(ARCH) -o gtests -lpthread -MD -DSIMU gtest_main.a + \ No newline at end of file diff --git a/src/diskio-test.cpp b/src/diskio-test.cpp new file mode 100644 index 000000000..7297a4da5 --- /dev/null +++ b/src/diskio-test.cpp @@ -0,0 +1,62 @@ +/* + I put this in place of the code in the FRSKY first menu page, just as a quick test +*/ + // DISK_IO DEBUG -- XXX DELETE ME + + static uint8_t onceonly = 0; + static FRESULT f_err_code; + static FATFS FATFS_Obj; + static uint8_t result = 0; + static TCHAR sBuffer[100] = {0}; + static TCHAR *myStr = sBuffer; + + if (!onceonly) + { + // First, let's try to set the RTC date/time + RTC rtc; + + rtc.year = 2011; + rtc.month = 6; + rtc.mday = 23; + rtc.wday = 5; + rtc.hour = 21; + rtc.min = 53; + rtc.sec = 0; + +// rtc_settime(&rtc); +// IT WORKED! And battery back-up for the RTC chip is working also. + ////////////////////// + + f_err_code = f_mount(0, &FATFS_Obj); + FIL fil_obj; + + // atempt creating and writing to a new file + result = f_open(&fil_obj, "/foo.txt", FA_CREATE_ALWAYS | FA_WRITE); + f_printf(&fil_obj, "gruvin9x created this file! Yay!\n"); + f_close(&fil_obj); + + // That worked! Now test reading a line from another file. + result = f_open(&fil_obj, "/foo.txt", FA_READ); + myStr = f_gets(sBuffer, 100, &fil_obj); + + f_close(&fil_obj); + + onceonly = 1; + } + + lcd_outdezAtt(5*FW, 2*FH, f_err_code, 0); + lcd_outdezAtt(5*FW, 3*FH, result, 0); + lcd_outdezAtt(5*FW, 4*FH, strlen(myStr), 0); + lcd_outdezAtt(5*FW, 5*FH, myStr[0], 0); + + // can't use lcd_puts... becasue it specifies prog_char mem space, not SRAM + uint8_t x=0; + uint8_t j=6; + for (uint8_t i=0; i + * - Bryan J. Rentoul (Gruvin) + * + * gruvin9x is based on code named er9x by + * Author - Erez Raviv , which is in turn + * was based on the original (and ongoing) project by Thomas Husterer, + * th9x -- http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + */ + +/*-----------------------------------------------------------------------*/ +/* MMCv3/SDv1/SDv2 (in SPI mode) control module (C)ChaN, 2010 */ +/*-----------------------------------------------------------------------*/ +/* Only rcvr_spi(), xmit_spi(), disk_timerproc() and some macros */ +/* are platform dependent. */ +/*-----------------------------------------------------------------------*/ + + +#include +#include "diskio.h" + + +/* Definitions for MMC/SDC command */ +#define CMD0 (0) /* GO_IDLE_STATE */ +#define CMD1 (1) /* SEND_OP_COND (MMC) */ +#define ACMD41 (0x80+41) /* SEND_OP_COND (SDC) */ +#define CMD8 (8) /* SEND_IF_COND */ +#define CMD9 (9) /* SEND_CSD */ +#define CMD10 (10) /* SEND_CID */ +#define CMD12 (12) /* STOP_TRANSMISSION */ +#define ACMD13 (0x80+13) /* SD_STATUS (SDC) */ +#define CMD16 (16) /* SET_BLOCKLEN */ +#define CMD17 (17) /* READ_SINGLE_BLOCK */ +#define CMD18 (18) /* READ_MULTIPLE_BLOCK */ +#define CMD23 (23) /* SET_BLOCK_COUNT (MMC) */ +#define ACMD23 (0x80+23) /* SET_WR_BLK_ERASE_COUNT (SDC) */ +#define CMD24 (24) /* WRITE_BLOCK */ +#define CMD25 (25) /* WRITE_MULTIPLE_BLOCK */ +#define CMD55 (55) /* APP_CMD */ +#define CMD58 (58) /* READ_OCR */ + +/* Card type flags (CardType) */ +#define CT_MMC 0x01 /* MMC ver 3 */ +#define CT_SD1 0x02 /* SD ver 1 */ +#define CT_SD2 0x04 /* SD ver 2 */ +#define CT_SDC (CT_SD1|CT_SD2) /* SD */ +#define CT_BLOCK 0x08 /* Block addressing */ + +/* Port Controls (Platform dependent) */ +// GCC optimisation should result in a single CBI/SBI instructions here +#define CS_LOW() PORTB &= ~0x10 /* MMC CS = L */ +#define CS_HIGH() PORTB |= 0x10 /* MMC CS = H */ + +#define SOCKPORT PINB /* Socket contact port */ +#define SOCKWP 0x00 // not implemented /* Write protect switch */ +#define SOCKINS 0x00 // not implemented /* Card detect switch */ + +#define FCLK_SLOW() SPCR = 0x52 /* Set slow clock (100k-400k) */ +#define FCLK_FAST() SPCR = 0x50 /* Set fast clock (depends on the CSD) */ + + +/*-------------------------------------------------------------------------- + + Module Private Functions + + ---------------------------------------------------------------------------*/ + +static volatile +DSTATUS Stat = STA_NOINIT; /* Disk status */ + +volatile BYTE Timer1, Timer2; /* 100Hz decrement timer */ + +static +BYTE CardType; /* Card type flags */ + + +/*-----------------------------------------------------------------------*/ +/* Transmit a byte to MMC via SPI (Platform dependent) */ +/*-----------------------------------------------------------------------*/ + +#define xmit_spi(dat) SPDR=(dat); loop_until_bit_is_set(SPSR,SPIF) + + + +/*-----------------------------------------------------------------------*/ +/* Receive a byte from MMC via SPI (Platform dependent) */ +/*-----------------------------------------------------------------------*/ + +static +BYTE rcvr_spi (void) +{ + SPDR = 0xFF; + loop_until_bit_is_set(SPSR, SPIF); + return SPDR; +} + +/* Alternative macro to receive data fast */ +#define rcvr_spi_m(dst) SPDR=0xFF; loop_until_bit_is_set(SPSR,SPIF); *(dst)=SPDR + + + +/*-----------------------------------------------------------------------*/ +/* Wait for card ready */ +/*-----------------------------------------------------------------------*/ + +static +int wait_ready (void) /* 1:OK, 0:Timeout */ +{ + Timer2 = 50; /* Wait for ready in timeout of 500ms (G: now 50x16ms) */ + rcvr_spi(); + do + if (rcvr_spi() == 0xFF) return 1; + while (Timer2); + + return 0; +} + + + +/*-----------------------------------------------------------------------*/ +/* Deselect the card and release SPI bus */ +/*-----------------------------------------------------------------------*/ + +static +void deselect (void) +{ + CS_HIGH(); + rcvr_spi(); +} + + + +/*-----------------------------------------------------------------------*/ +/* Select the card and wait for ready */ +/*-----------------------------------------------------------------------*/ + +static +int select (void) /* 1:Successful, 0:Timeout */ +{ + CS_LOW(); + if (!wait_ready()) { + deselect(); + return 0; + } + return 1; +} + + + +/*-----------------------------------------------------------------------*/ +/* Power Control (Platform dependent) */ +/*-----------------------------------------------------------------------*/ +/* When the target system does not support socket power control, there */ +/* is nothing to do in these functions and chk_power always returns 1. */ + +static +int power_status(void) /* Socket power state: 0=off, 1=on */ +{ + return (PORTE & 0x80) ? 0 : 1; +} + + +static +void power_on (void) +{ + // PORTE &= ~0x80; // Socket power on + for (Timer1 = 2; Timer1; ); // Wait for 20ms + //PORTB = 0b10110101; // Enable drivers + //DDRB = 0b11000111; + + SPCR = 0x52; // Enable SPI function in mode 0 + SPSR = 0x00; // G: was 0x01; // SPI 2x mode +} + + +static +void power_off (void) +{ + SPCR = 0; /* Disable SPI function */ + // DDRB = 0b11000000; /* Disable drivers */ + // PORTB = 0b10110000; + + // PORTE |= 0x80; /* Socket power off */ + Stat |= STA_NOINIT; +} + + + +/*-----------------------------------------------------------------------*/ +/* Receive a data packet from MMC */ +/*-----------------------------------------------------------------------*/ + +static +int rcvr_datablock ( + BYTE *buff, /* Data buffer to store received data */ + UINT btr /* Byte count (must be multiple of 4) */ + ) +{ + BYTE token; + + + Timer1 = 20; + do { /* Wait for data packet in timeout of 200ms */ + token = rcvr_spi(); + } while ((token == 0xFF) && Timer1); + if(token != 0xFE) return 0; /* If not valid data token, retutn with error */ + + do { /* Receive the data block into buffer */ + rcvr_spi_m(buff++); + rcvr_spi_m(buff++); + rcvr_spi_m(buff++); + rcvr_spi_m(buff++); + } while (btr -= 4); + rcvr_spi(); /* Discard CRC */ + rcvr_spi(); + + return 1; /* Return with success */ +} + + + +/*-----------------------------------------------------------------------*/ +/* Send a data packet to MMC */ +/*-----------------------------------------------------------------------*/ + +static +int xmit_datablock ( + const BYTE *buff, /* 512 byte data block to be transmitted */ + BYTE token /* Data/Stop token */ + ) +{ + BYTE resp, wc; + + + if (!wait_ready()) return 0; + + xmit_spi(token); /* Xmit data token */ + if (token != 0xFD) { /* Is data token */ + wc = 0; + do { /* Xmit the 512 byte data block to MMC */ + xmit_spi(*buff++); + xmit_spi(*buff++); + } while (--wc); + xmit_spi(0xFF); /* CRC (Dummy) */ + xmit_spi(0xFF); + resp = rcvr_spi(); /* Reveive data response */ + if ((resp & 0x1F) != 0x05) /* If not accepted, return with error */ + return 0; + } + + return 1; +} + + + +/*-----------------------------------------------------------------------*/ +/* Send a command packet to MMC */ +/*-----------------------------------------------------------------------*/ + +static +BYTE send_cmd ( /* Returns R1 resp (bit7==1:Send failed) */ + BYTE cmd, /* Command index */ + DWORD arg /* Argument */ + ) +{ + BYTE n, res; + + + if (cmd & 0x80) { /* ACMD is the command sequense of CMD55-CMD */ + cmd &= 0x7F; + res = send_cmd(CMD55, 0); + if (res > 1) return res; + } + + /* Select the card and wait for ready */ + deselect(); + if (!select()) return 0xFF; + + /* Send command packet */ + xmit_spi(0x40 | cmd); /* Start + Command index */ + xmit_spi((BYTE)(arg >> 24)); /* Argument[31..24] */ + xmit_spi((BYTE)(arg >> 16)); /* Argument[23..16] */ + xmit_spi((BYTE)(arg >> 8)); /* Argument[15..8] */ + xmit_spi((BYTE)arg); /* Argument[7..0] */ + n = 0x01; /* Dummy CRC + Stop */ + if (cmd == CMD0) n = 0x95; /* Valid CRC for CMD0(0) */ + if (cmd == CMD8) n = 0x87; /* Valid CRC for CMD8(0x1AA) */ + xmit_spi(n); + + /* Receive command response */ + if (cmd == CMD12) rcvr_spi(); /* Skip a stuff byte when stop reading */ + n = 10; /* Wait for a valid response in timeout of 10 attempts */ + do + res = rcvr_spi(); + while ((res & 0x80) && --n); + + return res; /* Return with the response value */ +} + + + +/*-------------------------------------------------------------------------- + + Public Functions + + ---------------------------------------------------------------------------*/ + + +/*-----------------------------------------------------------------------*/ +/* Initialize Disk Drive */ +/*-----------------------------------------------------------------------*/ + +DSTATUS disk_initialize ( + BYTE drv /* Physical drive nmuber (0) */ + ) +{ + BYTE n, cmd, ty, ocr[4]; + + + if (drv) return STA_NOINIT; /* Supports only single drive */ + if (Stat & STA_NODISK) return Stat; /* No card in the socket */ + + power_on(); /* Force socket power on */ + FCLK_SLOW(); + for (n = 10; n; n--) rcvr_spi(); /* 80 dummy clocks */ + + ty = 0; + if (send_cmd(CMD0, 0) == 1) { /* Enter Idle state */ + Timer1 = 100; /* Initialization timeout of 1000 msec */ + if (send_cmd(CMD8, 0x1AA) == 1) { /* SDv2? */ + for (n = 0; n < 4; n++) ocr[n] = rcvr_spi(); /* Get trailing return value of R7 resp */ + if (ocr[2] == 0x01 && ocr[3] == 0xAA) { /* The card can work at vdd range of 2.7-3.6V */ + while (Timer1 && send_cmd(ACMD41, 1UL << 30)); /* Wait for leaving idle state (ACMD41 with HCS bit) */ + if (Timer1 && send_cmd(CMD58, 0) == 0) { /* Check CCS bit in the OCR */ + for (n = 0; n < 4; n++) ocr[n] = rcvr_spi(); + ty = (ocr[0] & 0x40) ? CT_SD2 | CT_BLOCK : CT_SD2; /* SDv2 */ + } + } + } else { /* SDv1 or MMCv3 */ + if (send_cmd(ACMD41, 0) <= 1) { + ty = CT_SD1; cmd = ACMD41; /* SDv1 */ + } else { + ty = CT_MMC; cmd = CMD1; /* MMCv3 */ + } + while (Timer1 && send_cmd(cmd, 0)); /* Wait for leaving idle state */ + if (!Timer1 || send_cmd(CMD16, 512) != 0) /* Set R/W block length to 512 */ + ty = 0; + } + } + CardType = ty; + deselect(); + + if (ty) { /* Initialization succeded */ + Stat &= ~STA_NOINIT; /* Clear STA_NOINIT */ + FCLK_FAST(); + } else { /* Initialization failed */ + power_off(); + } + + return Stat; +} + + + +/*-----------------------------------------------------------------------*/ +/* Get Disk Status */ +/*-----------------------------------------------------------------------*/ + +DSTATUS disk_status ( + BYTE drv /* Physical drive nmuber (0) */ + ) +{ + if (drv) return STA_NOINIT; /* Supports only single drive */ + return Stat; +} + + + +/*-----------------------------------------------------------------------*/ +/* Read Sector(s) */ +/*-----------------------------------------------------------------------*/ + +DRESULT disk_read ( + BYTE drv, /* Physical drive nmuber (0) */ + BYTE *buff, /* Pointer to the data buffer to store read data */ + DWORD sector, /* Start sector number (LBA) */ + BYTE count /* Sector count (1..255) */ + ) +{ + if (drv || !count) return RES_PARERR; + if (Stat & STA_NOINIT) return RES_NOTRDY; + + if (!(CardType & CT_BLOCK)) sector *= 512; /* Convert to byte address if needed */ + + if (count == 1) { /* Single block read */ + if ((send_cmd(CMD17, sector) == 0) /* READ_SINGLE_BLOCK */ + && rcvr_datablock(buff, 512)) + count = 0; + } + else { /* Multiple block read */ + if (send_cmd(CMD18, sector) == 0) { /* READ_MULTIPLE_BLOCK */ + do { + if (!rcvr_datablock(buff, 512)) break; + buff += 512; + } while (--count); + send_cmd(CMD12, 0); /* STOP_TRANSMISSION */ + } + } + deselect(); + + return count ? RES_ERROR : RES_OK; +} + + + +/*-----------------------------------------------------------------------*/ +/* Write Sector(s) */ +/*-----------------------------------------------------------------------*/ + +DRESULT disk_write ( + BYTE drv, /* Physical drive nmuber (0) */ + const BYTE *buff, /* Pointer to the data to be written */ + DWORD sector, /* Start sector number (LBA) */ + BYTE count /* Sector count (1..255) */ + ) +{ + if (drv || !count) return RES_PARERR; + if (Stat & STA_NOINIT) return RES_NOTRDY; + if (Stat & STA_PROTECT) return RES_WRPRT; + + if (!(CardType & CT_BLOCK)) sector *= 512; /* Convert to byte address if needed */ + + if (count == 1) { /* Single block write */ + if ((send_cmd(CMD24, sector) == 0) /* WRITE_BLOCK */ + && xmit_datablock(buff, 0xFE)) + count = 0; + } + else { /* Multiple block write */ + if (CardType & CT_SDC) send_cmd(ACMD23, count); + if (send_cmd(CMD25, sector) == 0) { /* WRITE_MULTIPLE_BLOCK */ + do { + if (!xmit_datablock(buff, 0xFC)) break; + buff += 512; + } while (--count); + if (!xmit_datablock(0, 0xFD)) /* STOP_TRAN token */ + count = 1; + } + } + deselect(); + + return count ? RES_ERROR : RES_OK; +} + + + +/*-----------------------------------------------------------------------*/ +/* Miscellaneous Functions */ +/*-----------------------------------------------------------------------*/ + +DRESULT disk_ioctl ( + BYTE drv, /* Physical drive nmuber (0) */ + BYTE ctrl, /* Control code */ + BYTE *buff /* Buffer to send/receive control data */ + ) +{ + DRESULT res; + BYTE n, csd[16], *ptr = buff; + WORD csize; + + + if (drv) return RES_PARERR; + + res = RES_ERROR; + + if (ctrl == CTRL_POWER) { + switch (ptr[0]) { + case 0: /* Sub control code (POWER_OFF) */ + power_off(); /* Power off */ + res = RES_OK; + break; + case 1: /* Sub control code (POWER_GET) */ + ptr[1] = (BYTE)power_status(); + res = RES_OK; + break; + default : + res = RES_PARERR; + } + } + else { + if (Stat & STA_NOINIT) return RES_NOTRDY; + + switch (ctrl) { + case CTRL_SYNC : /* Make sure that no pending write process. Do not remove this or written sector might not left updated. */ + if (select()) { + deselect(); + res = RES_OK; + } + break; + + case GET_SECTOR_COUNT : /* Get number of sectors on the disk (DWORD) */ + if ((send_cmd(CMD9, 0) == 0) && rcvr_datablock(csd, 16)) { + if ((csd[0] >> 6) == 1) { /* SDC ver 2.00 */ + csize = csd[9] + ((WORD)csd[8] << 8) + 1; + *(DWORD*)buff = (DWORD)csize << 10; + } else { /* SDC ver 1.XX or MMC*/ + n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2; + csize = (csd[8] >> 6) + ((WORD)csd[7] << 2) + ((WORD)(csd[6] & 3) << 10) + 1; + *(DWORD*)buff = (DWORD)csize << (n - 9); + } + res = RES_OK; + } + break; + + case GET_SECTOR_SIZE : /* Get R/W sector size (WORD) */ + *(WORD*)buff = 512; + res = RES_OK; + break; + + case GET_BLOCK_SIZE : /* Get erase block size in unit of sector (DWORD) */ + if (CardType & CT_SD2) { /* SDv2? */ + if (send_cmd(ACMD13, 0) == 0) { /* Read SD status */ + rcvr_spi(); + if (rcvr_datablock(csd, 16)) { /* Read partial block */ + for (n = 64 - 16; n; n--) rcvr_spi(); /* Purge trailing data */ + *(DWORD*)buff = 16UL << (csd[10] >> 4); + res = RES_OK; + } + } + } else { /* SDv1 or MMCv3 */ + if ((send_cmd(CMD9, 0) == 0) && rcvr_datablock(csd, 16)) { /* Read CSD */ + if (CardType & CT_SD1) { /* SDv1 */ + *(DWORD*)buff = (((csd[10] & 63) << 1) + ((WORD)(csd[11] & 128) >> 7) + 1) << ((csd[13] >> 6) - 1); + } else { /* MMCv3 */ + *(DWORD*)buff = ((WORD)((csd[10] & 124) >> 2) + 1) * (((csd[11] & 3) << 3) + ((csd[11] & 224) >> 5) + 1); + } + res = RES_OK; + } + } + break; + + case MMC_GET_TYPE : /* Get card type flags (1 byte) */ + *ptr = CardType; + res = RES_OK; + break; + + case MMC_GET_CSD : /* Receive CSD as a data block (16 bytes) */ + if (send_cmd(CMD9, 0) == 0 /* READ_CSD */ + && rcvr_datablock(ptr, 16)) + res = RES_OK; + break; + + case MMC_GET_CID : /* Receive CID as a data block (16 bytes) */ + if (send_cmd(CMD10, 0) == 0 /* READ_CID */ + && rcvr_datablock(ptr, 16)) + res = RES_OK; + break; + + case MMC_GET_OCR : /* Receive OCR as an R3 resp (4 bytes) */ + if (send_cmd(CMD58, 0) == 0) { /* READ_OCR */ + for (n = 4; n; n--) *ptr++ = rcvr_spi(); + res = RES_OK; + } + break; + + case MMC_GET_SDSTAT : /* Receive SD statsu as a data block (64 bytes) */ + if (send_cmd(ACMD13, 0) == 0) { /* SD_STATUS */ + rcvr_spi(); + if (rcvr_datablock(ptr, 64)) + res = RES_OK; + } + break; + + default: + res = RES_PARERR; + } + + deselect(); + } + + return res; +} + + + +/*-----------------------------------------------------------------------*/ +/* Device Timer Interrupt Procedure */ +/*-----------------------------------------------------------------------*/ +/* This function must be called in period of 10ms */ + +void disk_timerproc (void) +{ + BYTE s; + + /* + n = Timer1; // 100Hz decrement timer + if (n) Timer1 = --n; + n = Timer2; + if (n) Timer2 = --n; + */ + if (Timer1) Timer1--; + if (Timer2) Timer2--; + + s = Stat; + + /* G: Not implemented + if (SOCKWP) // Write protected + s |= STA_PROTECT; + else // Write enabled + s &= ~STA_PROTECT; + if (SOCKINS) // Card inserted + s &= ~STA_NODISK; + else // Socket empty + s |= (STA_NODISK | STA_NOINIT); + */ + s &= ~STA_NODISK; + s &= ~STA_PROTECT; + + Stat = s; +} + diff --git a/src/diskio.h b/src/diskio.h new file mode 100644 index 000000000..d89c1e75b --- /dev/null +++ b/src/diskio.h @@ -0,0 +1,103 @@ +/* + * Authors (alphabetical order) + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * + * gruvin9x is based on code named er9x by + * Author - Erez Raviv , which is in turn + * was based on the original (and ongoing) project by Thomas Husterer, + * th9x -- http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + */ + +/*----------------------------------------------------------------------- +/ Low level disk interface modlue include file (C)ChaN, 2010 +/-----------------------------------------------------------------------*/ + +#ifndef DEF_DISKIO +#define DEF_DISKIO + +#include "integer.h" + +#define DN_MCI 0 /* Physical drive number for MCI */ +#define DN_NAND 1 /* Physical drive number for NAND flash */ + + +/* These functions are defined in asmfunc.S */ +void Copy_al2un (BYTE *dst, const DWORD *src, int count); /* Copy aligned to unaligned. */ +void Copy_un2al (DWORD *dst, const BYTE *src, int count); /* Copy unaligned to aligned. */ + + +/* Status of Disk Functions */ +typedef BYTE DSTATUS; + +/* Results of Disk Functions */ +typedef enum { + RES_OK = 0, /* 0: Successful */ + RES_ERROR, /* 1: R/W Error */ + RES_WRPRT, /* 2: Write Protected */ + RES_NOTRDY, /* 3: Not Ready */ + RES_PARERR /* 4: Invalid Parameter */ +} DRESULT; + + +/*---------------------------------------*/ +/* Prototypes for disk control functions */ + +DSTATUS disk_initialize (BYTE); +DSTATUS disk_status (BYTE); +DRESULT disk_read (BYTE, BYTE*, DWORD, BYTE); +DRESULT disk_write (BYTE, const BYTE*, DWORD, BYTE); +DRESULT disk_ioctl (BYTE, BYTE, BYTE*); +void disk_timerproc (void); + + +/* Disk Status Bits (DSTATUS) */ + +#define STA_NOINIT 0x01 /* Drive not initialized */ +#define STA_NODISK 0x02 /* No medium in the drive */ +#define STA_PROTECT 0x04 /* Write protected */ + + +/* Command code for disk_ioctrl fucntion */ + +/* Generic ioctl command (defined for FatFs) */ +#define CTRL_SYNC 0 /* Flush disk cache (for write functions) */ +#define GET_SECTOR_COUNT 1 /* Get media size (for only f_mkfs()) */ +#define GET_SECTOR_SIZE 2 /* Get sector size (for multiple sector size (_MAX_SS >= 1024)) */ +#define GET_BLOCK_SIZE 3 /* Get erase block size (for only f_mkfs()) */ +#define CTRL_ERASE_SECTOR 4 /* Force erased a block of sectors (for only _USE_ERASE) */ + +/* Generic ioctl command */ +#define CTRL_POWER 5 /* Get/Set power status */ +#define CTRL_LOCK 6 /* Lock/Unlock media removal */ +#define CTRL_EJECT 7 /* Eject media */ + +/* MMC/SDC specific ioctl command */ +#define MMC_GET_TYPE 10 /* Get card type */ +#define MMC_GET_CSD 11 /* Get CSD */ +#define MMC_GET_CID 12 /* Get CID */ +#define MMC_GET_OCR 13 /* Get OCR */ +#define MMC_GET_SDSTAT 14 /* Get SD status */ + +/* ATA/CF specific ioctl command */ +#define ATA_GET_REV 20 /* Get F/W revision */ +#define ATA_GET_MODEL 21 /* Get model name */ +#define ATA_GET_SN 22 /* Get serial number */ + +/* NAND specific ioctl command */ +#define NAND_FORMAT 30 /* Create physical format */ + + +volatile extern BYTE Timer1, Timer2; /* 100Hz decrement timer */ + +#endif diff --git a/src/drivers.cpp b/src/drivers.cpp new file mode 100644 index 000000000..cda701c5f --- /dev/null +++ b/src/drivers.cpp @@ -0,0 +1,445 @@ +/* + * Authors (alphabetical order) + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * + * gruvin9x is based on code named er9x by + * Author - Erez Raviv , which is in turn + * was based on the original (and ongoing) project by Thomas Husterer, + * th9x -- http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + */ + +#include "gruvin9x.h" + +#ifndef SIMU + +#ifdef ASYNC_WRITE + +uint16_t eeprom_pointer; +const char* eeprom_buffer_data; +volatile size_t eeprom_buffer_size = 0; + +inline void eeprom_write_byte() +{ + EEAR = eeprom_pointer; + EEDR = *eeprom_buffer_data; +#if defined (PCBV3) + EECR |= 1< 0) { + eeprom_write_byte(); + } + else { +#if defined (PCBV3) + EECR &= ~(1< 0); +} + +#else + +///opt/cross/avr/include/avr/eeprom.h +static inline void __attribute__ ((always_inline)) +eeprom_write_byte_cmp (uint8_t dat, uint16_t pointer_eeprom) +{ + //see /home/thus/work/avr/avrsdk4/avr-libc-1.4.4/libc/misc/eeprom.S:98 143 +#if defined (PCBV3) + while(EECR & (1<16) m_dblcnt=0; //pause zu lang fuer double + m_cnt = 0; + } + break; + //fallthrough + case KSTATE_START: + putEvent(EVT_KEY_FIRST(enuk)); + m_dblcnt++; + m_state = KSTATE_RPTDELAY; + m_cnt = 0; + break; + + case KSTATE_RPTDELAY: // gruvin: delay state before first key repeat + if(m_cnt == 24) putEvent(EVT_KEY_LONG(enuk)); + if (m_cnt == 40) { + m_state = 16; + m_cnt = 0; + } + break; + + case 16: + case 8: + case 4: + case 2: + if(m_cnt >= 48) { //3 6 12 24 48 pulses in every 480ms + m_state >>= 1; + m_cnt = 0; + } + //fallthrough + case 1: + if( (m_cnt & (m_state-1)) == 0) putEvent(EVT_KEY_REPT(enuk)); + break; + + case KSTATE_PAUSE: //pause + if(m_cnt >= 64) { + m_state = 8; + m_cnt = 0; + } + break; + + case KSTATE_KILLED: //killed + break; + } +} + +bool keyState(EnumKeys enuk) +{ + if(enuk < (int)DIM(keys)) return keys[enuk].state() ? 1 : 0; + switch(enuk){ + case SW_ElevDR : return PINE & (1<> 4); + + PORTD = 0xFF; + +#else + + in = ~PIND; + +// Legacy support for USART1 free hardware mod [DEPRECATED] +#if defined(USART1FREED) + // mask out original INP_D_TRM_LV_UP and INP_D_TRM_LV_DWN bits + in &= ~((1< 10ms intervals + if ( FrskyAlarmSendState ) + FRSKY10mspoll() ; + + if (frskyStreaming > 0) + frskyStreaming--; + else if (g_eeGeneral.enableTelemetryAlarm && (g_model.frsky.channels[0].ratio || g_model.frsky.channels[1].ratio)) { +#if defined (BEEPSPKR) + if (!(g_tmr10ms % 30)) beepWarn2Spkr((g_tmr10ms % 60) ? 25 : 20); +#else + if (!(g_tmr10ms % 30)) + { + warble = !(g_tmr10ms % 60); + beepWarn2(); + } +#endif + } +#endif + + // These moved here from perOut() [gruvin9x.cpp] to improve beep trigger reliability. +#if defined (BEEPSPKR) + if(mixWarning & 1) if(((g_tmr10ms&0xFF)== 0)) beepWarn1Spkr(BEEP_DEFAULT_FREQ+7); + if(mixWarning & 2) if(((g_tmr10ms&0xFF)== 64) + || ((g_tmr10ms&0xFF)== 72)) beepWarn1Spkr(BEEP_DEFAULT_FREQ+9); + if(mixWarning & 4) if(((g_tmr10ms&0xFF)==128) || ((g_tmr10ms&0xFF)==136) + || ((g_tmr10ms&0xFF)==144)) beepWarn1Spkr(BEEP_DEFAULT_FREQ+11); +#else + if(mixWarning & 1) if(((g_tmr10ms&0xFF)== 0)) beepWarn1(); + if(mixWarning & 2) if(((g_tmr10ms&0xFF)== 64) || ((g_tmr10ms&0xFF)== 72)) beepWarn1(); + if(mixWarning & 4) if(((g_tmr10ms&0xFF)==128) || ((g_tmr10ms&0xFF)==136) || ((g_tmr10ms&0xFF)==144)) beepWarn1(); +#endif +} diff --git a/src/eeprom_v3.h b/src/eeprom_v3.h new file mode 100644 index 000000000..1c842200e --- /dev/null +++ b/src/eeprom_v3.h @@ -0,0 +1,135 @@ +#ifndef eepromv3_h +#define eepromv3_h + +#include + +namespace EEPROM_V3 { + + typedef struct t_TrainerMix { + uint8_t srcChn:3; //0-7 = ch1-8 + int8_t swtch:5; + int8_t studWeight:6; + uint8_t mode:2; //off,add-mode,subst-mode + } __attribute__((packed)) TrainerMix; // + + typedef struct t_TrainerData { + int16_t calib[4]; + TrainerMix mix[4]; + } __attribute__((packed)) TrainerData; + + typedef struct t_EEGeneral { + uint8_t myVers; + int16_t calibMid[7]; + int16_t calibSpanNeg[7]; + int16_t calibSpanPos[7]; + uint16_t chkSum; + uint8_t currModel; //0..15 + uint8_t contrast; + uint8_t vBatWarn; + int8_t vBatCalib; + int8_t lightSw; + TrainerData trainer; + uint8_t view; //index of subview in main scrren + // uint8_t warnOpts; //bitset for several warnings + uint8_t disableThrottleWarning:1; + uint8_t disableSwitchWarning:1; + uint8_t disableMemoryWarning:1; + uint8_t beeperVal:3; + uint8_t reserveWarning:1; + uint8_t disableAlarmWarning:1; + uint8_t stickMode; + uint8_t inactivityTimer; + uint8_t throttleReversed:1; + uint8_t minuteBeep:1; + uint8_t preBeep:1; + uint8_t flashBeep:1; + uint8_t disableSplashScreen:1; + uint8_t res1:3; + uint8_t filterInput; + uint8_t lightAutoOff; + uint8_t templateSetup; //RETA order according to chout_ar array + int8_t PPM_Multiplier; + uint8_t res[1]; + } __attribute__((packed)) EEGeneral; + + + + //eeprom modelspec + //expo[3][2][2] //[Norm/Dr][expo/weight][R/L] + + typedef struct t_ExpoData { + int8_t expo[3][2][2]; + int8_t drSw1; + int8_t drSw2; + } __attribute__((packed)) ExpoData; + + + typedef struct t_LimitData { + int8_t min; + int8_t max; + bool revert; + int16_t offset; + } __attribute__((packed)) LimitData; + + + typedef struct t_MixData { + uint8_t destCh; // 1..NUM_CHNOUT + uint8_t srcRaw; // + int8_t weight; + int8_t swtch; + uint8_t curve; //0=symmetrisch 1=no neg 2=no pos + uint8_t delayUp:4; + uint8_t delayDown:4; + uint8_t speedUp:4; // Servogeschwindigkeit aus Tabelle (10ms Cycle) + uint8_t speedDown:4; // 0 nichts + uint8_t carryTrim:1; + uint8_t mltpx:3; // multiplex method 0=+ 1=* 2=replace + uint8_t mixWarn:4; // mixer warning + int8_t sOffset; + int8_t res; + } __attribute__((packed)) MixData; + + + typedef struct t_CSwData { // Custom Switches data + int8_t v1; //input + int8_t v2; //offset + uint8_t func; + } __attribute__((packed)) CSwData; + + + typedef struct t_SwashRingData { // Swash Ring data + uint8_t lim; // 0 mean off 100 full deflection + uint8_t chX; // 2 channels to limit + uint8_t chY; // 2 channels to limit + } __attribute__((packed)) SwashRingData; + + typedef struct t_ModelData { + char name[10]; // 10 must be first for eeLoadModelName + uint8_t mdVers; + int8_t tmrMode; // timer trigger source -> off, abs, stk, stk%, sw/!sw, !m_sw/!m_sw + uint8_t tmrDir; // 0=>Count Down, 1=>Count Up + uint16_t tmrVal; + uint8_t protocol; + int8_t ppmNCH; + int8_t thrTrim:4; // Enable Throttle Trim + int8_t thrExpo:4; // Enable Throttle Expo + int8_t trimInc; // Trim Increments + int8_t ppmDelay; + int8_t trimSw; + uint8_t beepANACenter; // 1<<0->A1.. 1<<6->A7 + uint8_t pulsePol; + char res[3]; + MixData mixData[32]; + LimitData limitData[16]; + ExpoData expoData[4]; + int8_t trim[4]; + int8_t curves5[8][5]; + int8_t curves9[8][9]; + CSwData customSw[6]; + SwashRingData swashR; + } __attribute__((packed)) ModelData; + +} + +#endif + diff --git a/src/eeprom_v4.h b/src/eeprom_v4.h new file mode 100644 index 000000000..8ba2af781 --- /dev/null +++ b/src/eeprom_v4.h @@ -0,0 +1,158 @@ +#ifndef eepromv4_h +#define eepromv4_h + +#include + +namespace EEPROM_V4 { + +typedef struct t_TrainerMix { + uint8_t srcChn:3; //0-7 = ch1-8 + int8_t swtch:5; + int8_t studWeight:6; + uint8_t mode:2; //off,add-mode,subst-mode +} __attribute__((packed)) TrainerMix; // + +typedef struct t_TrainerData { + int16_t calib[4]; + TrainerMix mix[4]; +} __attribute__((packed)) TrainerData; + +typedef struct t_EEGeneral { + uint8_t myVers; + int16_t calibMid[7]; + int16_t calibSpanNeg[7]; + int16_t calibSpanPos[7]; + uint16_t chkSum; + uint8_t currModel; //0..15 + uint8_t contrast; + uint8_t vBatWarn; + int8_t vBatCalib; + int8_t lightSw; + TrainerData trainer; + uint8_t view; //index of subview in main scrren + uint8_t disableThrottleWarning:1; + uint8_t disableSwitchWarning:1; + uint8_t disableMemoryWarning:1; + uint8_t beeperVal:3; + uint8_t reserveWarning:1; + uint8_t disableAlarmWarning:1; + uint8_t stickMode; + uint8_t inactivityTimer; + uint8_t throttleReversed:1; + uint8_t minuteBeep:1; + uint8_t preBeep:1; + uint8_t flashBeep:1; + uint8_t disableSplashScreen:1; + uint8_t res1:3; + uint8_t filterInput; + uint8_t lightAutoOff; + uint8_t templateSetup; //RETA order according to chout_ar array + int8_t PPM_Multiplier; + uint8_t res[1]; +} __attribute__((packed)) EEGeneral; + +//eeprom modelspec + +typedef struct t_ExpoData { + int8_t expo[3][2][2]; //[Norm/Dr][expo/weight][R/L] + int8_t drSw1; + int8_t drSw2; +} __attribute__((packed)) ExpoData; + +typedef struct t_LimitData { + int8_t min; + int8_t max; + bool revert; + int16_t offset; +} __attribute__((packed)) LimitData; + +typedef struct t_MixData { + uint8_t destCh; // 1..NUM_CHNOUT + uint8_t srcRaw; // + int8_t weight; + int8_t swtch; + uint8_t curve; //0=symmetrisch 1=no neg 2=no pos + uint8_t delayUp:4; + uint8_t delayDown:4; + uint8_t speedUp:4; // Servogeschwindigkeit aus Tabelle (10ms Cycle) + uint8_t speedDown:4; // 0 nichts + uint8_t carryTrim:1; + uint8_t mltpx:3; // multiplex method 0=+ 1=* 2=replace + uint8_t mixWarn:4; // mixer warning + int8_t sOffset; + int8_t res; +} __attribute__((packed)) MixData; + +typedef struct t_CSwData { // Custom Switches data + int8_t v1; //input + int8_t v2; //offset + uint8_t func; +} __attribute__((packed)) CSwData; + + +typedef struct t_SwashRingData { // Swash Ring data + uint8_t lim; // 0 mean off 100 full deflection + uint8_t chX; // 2 channels to limit + uint8_t chY; // 2 channels to limit +} __attribute__((packed)) SwashRingData; + +typedef struct t_SafetySwData { // Custom Switches data + int8_t swtch; + int8_t val; +} __attribute__((packed)) SafetySwData; + +typedef struct t_FrSkyChannelData { + uint8_t ratio; // 0.0 means not used, 0.1V steps EG. 6.6 Volts = 66. 25.1V = 251, etc. + uint8_t alarms_value[2]; // 0.1V steps EG. 6.6 Volts = 66. 25.1V = 251, etc. + uint8_t alarms_level:4; + uint8_t alarms_greater:2; // 0=LT(<), 1=GT(>) + uint8_t type:2; // future use: 0=volts, ... +} __attribute__((packed)) FrSkyChannelData; + +typedef struct t_FrSkyData { + FrSkyChannelData channels[2]; +} __attribute__((packed)) FrSkyData; + +typedef struct t_ModelData { + char name[10]; // 10 must be first for eeLoadModelName + uint8_t mdVers; + int8_t tmrMode; // timer trigger source -> off, abs, stk, stk%, sw/!sw, !m_sw/!m_sw + uint8_t tmrDir:1; //0=>Count Down, 1=>Count Up + uint8_t traineron:1; // 0 disable trainer, 1 allow trainer + uint8_t spare:6; + uint16_t tmrVal; + uint8_t protocol; + int8_t ppmNCH; + int8_t thrTrim:4; // Enable Throttle Trim + int8_t thrExpo:4; // Enable Throttle Expo + int8_t trimInc; // Trim Increments + int8_t ppmDelay; + int8_t trimSw; + uint8_t beepANACenter; // 1<<0->A1.. 1<<6->A7 + uint8_t pulsePol:1; + uint8_t extendedLimits:1; + uint8_t swashInvertELE:1; + uint8_t swashInvertAIL:1; + uint8_t swashInvertCOL:1; + uint8_t swashType:3; + uint8_t swashCollectiveSource; + uint8_t swashRingValue; + char res1; + MixData mixData[32]; + LimitData limitData[16]; + ExpoData expoData[4]; + int8_t trim[4]; + int8_t curves5[8][5]; + int8_t curves9[8][9]; + CSwData customSw[12/*er9x=12, gruvin9x=6*/]; + /* er9x only */ + uint8_t a1voltRatio; //FrSky + uint8_t a2voltRatio; //FrSky + uint8_t res3; + SafetySwData safetySw[16]; + FrSkyData frsky; +} __attribute__((packed)) ModelData; + +} + +#endif diff --git a/src/ff.cpp b/src/ff.cpp new file mode 100644 index 000000000..3b5e53fa0 --- /dev/null +++ b/src/ff.cpp @@ -0,0 +1,3736 @@ +/*----------------------------------------------------------------------------/ +/ FatFs - FAT file system module R0.08a (C)ChaN, 2010 +/-----------------------------------------------------------------------------/ +/ FatFs module is a generic FAT file system module for small embedded systems. +/ This is a free software that opened for education, research and commercial +/ developments under license policy of following terms. +/ +/ Copyright (C) 2010, ChaN, all right reserved. +/ +/ * The FatFs module is a free software and there is NO WARRANTY. +/ * No restriction on use. You can use, modify and redistribute it for +/ personal, non-profit or commercial products UNDER YOUR RESPONSIBILITY. +/ * Redistributions of source code must retain the above copyright notice. +/ +/-----------------------------------------------------------------------------/ +/ Feb 26,'06 R0.00 Prototype. +/ +/ Apr 29,'06 R0.01 First stable version. +/ +/ Jun 01,'06 R0.02 Added FAT12 support. +/ Removed unbuffered mode. +/ Fixed a problem on small (<32M) partition. +/ Jun 10,'06 R0.02a Added a configuration option (_FS_MINIMUM). +/ +/ Sep 22,'06 R0.03 Added f_rename(). +/ Changed option _FS_MINIMUM to _FS_MINIMIZE. +/ Dec 11,'06 R0.03a Improved cluster scan algorithm to write files fast. +/ Fixed f_mkdir() creates incorrect directory on FAT32. +/ +/ Feb 04,'07 R0.04 Supported multiple drive system. +/ Changed some interfaces for multiple drive system. +/ Changed f_mountdrv() to f_mount(). +/ Added f_mkfs(). +/ Apr 01,'07 R0.04a Supported multiple partitions on a physical drive. +/ Added a capability of extending file size to f_lseek(). +/ Added minimization level 3. +/ Fixed an endian sensitive code in f_mkfs(). +/ May 05,'07 R0.04b Added a configuration option _USE_NTFLAG. +/ Added FSInfo support. +/ Fixed DBCS name can result FR_INVALID_NAME. +/ Fixed short seek (<= csize) collapses the file object. +/ +/ Aug 25,'07 R0.05 Changed arguments of f_read(), f_write() and f_mkfs(). +/ Fixed f_mkfs() on FAT32 creates incorrect FSInfo. +/ Fixed f_mkdir() on FAT32 creates incorrect directory. +/ Feb 03,'08 R0.05a Added f_truncate() and f_utime(). +/ Fixed off by one error at FAT sub-type determination. +/ Fixed btr in f_read() can be mistruncated. +/ Fixed cached sector is not flushed when create and close without write. +/ +/ Apr 01,'08 R0.06 Added fputc(), fputs(), fprintf() and fgets(). +/ Improved performance of f_lseek() on moving to the same or following cluster. +/ +/ Apr 01,'09 R0.07 Merged Tiny-FatFs as a buffer configuration option. (_FS_TINY) +/ Added long file name support. +/ Added multiple code page support. +/ Added re-entrancy for multitask operation. +/ Added auto cluster size selection to f_mkfs(). +/ Added rewind option to f_readdir(). +/ Changed result code of critical errors. +/ Renamed string functions to avoid name collision. +/ Apr 14,'09 R0.07a Separated out OS dependent code on reentrant cfg. +/ Added multiple sector size support. +/ Jun 21,'09 R0.07c Fixed f_unlink() can return FR_OK on error. +/ Fixed wrong cache control in f_lseek(). +/ Added relative path feature. +/ Added f_chdir() and f_chdrive(). +/ Added proper case conversion to extended char. +/ Nov 03,'09 R0.07e Separated out configuration options from ff.h to ffconf.h. +/ Fixed f_unlink() fails to remove a sub-dir on _FS_RPATH. +/ Fixed name matching error on the 13 char boundary. +/ Added a configuration option, _LFN_UNICODE. +/ Changed f_readdir() to return the SFN with always upper case on non-LFN cfg. +/ +/ May 15,'10 R0.08 Added a memory configuration option. (_USE_LFN = 3) +/ Added file lock feature. (_FS_SHARE) +/ Added fast seek feature. (_USE_FASTSEEK) +/ Changed some types on the API, XCHAR->TCHAR. +/ Changed fname member in the FILINFO structure on Unicode cfg. +/ String functions support UTF-8 encoding files on Unicode cfg. +/ Aug 16,'10 R0.08a Added f_getcwd(). (_FS_RPATH = 2) +/ Added sector erase feature. (_USE_ERASE) +/ Moved file lock semaphore table from fs object to the bss. +/ Fixed a wrong directory entry is created on non-LFN cfg when the given name contains ';'. +/ Fixed f_mkfs() creates wrong FAT32 volume. +/---------------------------------------------------------------------------*/ + +#include "ff.h" /* FatFs configurations and declarations */ +#include "diskio.h" /* Declarations of low level disk I/O functions */ + + +/*-------------------------------------------------------------------------- + + Module Private Definitions + +---------------------------------------------------------------------------*/ + +#if _FATFS != 8255 +#error Wrong include file (ff.h). +#endif + + +/* Definitions on sector size */ +#if _MAX_SS != 512 && _MAX_SS != 1024 && _MAX_SS != 2048 && _MAX_SS != 4096 +#error Wrong sector size. +#endif +#if _MAX_SS != 512 +#define SS(fs) ((fs)->ssize) /* Multiple sector size */ +#else +#define SS(fs) 512U /* Fixed sector size */ +#endif + + +/* Reentrancy related */ +#if _FS_REENTRANT +#if _USE_LFN == 1 +#error Static LFN work area must not be used in re-entrant configuration. +#endif +#define ENTER_FF(fs) { if (!lock_fs(fs)) return FR_TIMEOUT; } +#define LEAVE_FF(fs, res) { unlock_fs(fs, res); return res; } +#else +#define ENTER_FF(fs) +#define LEAVE_FF(fs, res) return res +#endif + +#define ABORT(fs, res) { fp->flag |= FA__ERROR; LEAVE_FF(fs, res); } + + +/* File shareing feature */ +#if _FS_SHARE +#if _FS_READONLY +#error _FS_SHARE must be 0 on read-only cfg. +#endif +typedef struct { + FATFS *fs; /* File ID 1, volume (NULL:blank entry) */ + DWORD clu; /* File ID 2, directory */ + WORD idx; /* File ID 3, directory index */ + WORD ctr; /* File open counter, 0:none, 0x01..0xFF:read open count, 0x100:write mode */ +} FILESEM; +#endif + + +/* Misc definitions */ +#define LD_CLUST(dir) (((DWORD)LD_WORD(dir+DIR_FstClusHI)<<16) | LD_WORD(dir+DIR_FstClusLO)) +#define ST_CLUST(dir,cl) {ST_WORD(dir+DIR_FstClusLO, cl); ST_WORD(dir+DIR_FstClusHI, (DWORD)cl>>16);} + + +/* Character code support macros */ +#define IsUpper(c) (((c)>='A')&&((c)<='Z')) +#define IsLower(c) (((c)>='a')&&((c)<='z')) +#define IsDigit(c) (((c)>='0')&&((c)<='9')) + +#if _DF1S /* Code page is DBCS */ + +#ifdef _DF2S /* Two 1st byte areas */ +#define IsDBCS1(c) (((BYTE)(c) >= _DF1S && (BYTE)(c) <= _DF1E) || ((BYTE)(c) >= _DF2S && (BYTE)(c) <= _DF2E)) +#else /* One 1st byte area */ +#define IsDBCS1(c) ((BYTE)(c) >= _DF1S && (BYTE)(c) <= _DF1E) +#endif + +#ifdef _DS3S /* Three 2nd byte areas */ +#define IsDBCS2(c) (((BYTE)(c) >= _DS1S && (BYTE)(c) <= _DS1E) || ((BYTE)(c) >= _DS2S && (BYTE)(c) <= _DS2E) || ((BYTE)(c) >= _DS3S && (BYTE)(c) <= _DS3E)) +#else /* Two 2nd byte areas */ +#define IsDBCS2(c) (((BYTE)(c) >= _DS1S && (BYTE)(c) <= _DS1E) || ((BYTE)(c) >= _DS2S && (BYTE)(c) <= _DS2E)) +#endif + +#else /* Code page is SBCS */ + +#define IsDBCS1(c) 0 +#define IsDBCS2(c) 0 + +#endif /* _DF1S */ + + +/* Name status flags */ +#define NS 11 /* Offset of name status byte */ +#define NS_LOSS 0x01 /* Out of 8.3 format */ +#define NS_LFN 0x02 /* Force to create LFN entry */ +#define NS_LAST 0x04 /* Last segment */ +#define NS_BODY 0x08 /* Lower case flag (body) */ +#define NS_EXT 0x10 /* Lower case flag (ext) */ +#define NS_DOT 0x20 /* Dot entry */ + + +/* FAT sub-type boundaries */ +/* Note that the FAT spec by Microsoft says 4085 but Windows works with 4087! */ +#define MIN_FAT16 4086 /* Minimum number of clusters for FAT16 */ +#define MIN_FAT32 65526 /* Minimum number of clusters for FAT32 */ + + +/* FatFs refers the members in the FAT structures as byte array instead of +/ structure member because there are incompatibility of the packing option +/ between compilers. */ + +#define BS_jmpBoot 0 +#define BS_OEMName 3 +#define BPB_BytsPerSec 11 +#define BPB_SecPerClus 13 +#define BPB_RsvdSecCnt 14 +#define BPB_NumFATs 16 +#define BPB_RootEntCnt 17 +#define BPB_TotSec16 19 +#define BPB_Media 21 +#define BPB_FATSz16 22 +#define BPB_SecPerTrk 24 +#define BPB_NumHeads 26 +#define BPB_HiddSec 28 +#define BPB_TotSec32 32 +#define BS_DrvNum 36 +#define BS_BootSig 38 +#define BS_VolID 39 +#define BS_VolLab 43 +#define BS_FilSysType 54 +#define BPB_FATSz32 36 +#define BPB_ExtFlags 40 +#define BPB_FSVer 42 +#define BPB_RootClus 44 +#define BPB_FSInfo 48 +#define BPB_BkBootSec 50 +#define BS_DrvNum32 64 +#define BS_BootSig32 66 +#define BS_VolID32 67 +#define BS_VolLab32 71 +#define BS_FilSysType32 82 +#define FSI_LeadSig 0 +#define FSI_StrucSig 484 +#define FSI_Free_Count 488 +#define FSI_Nxt_Free 492 +#define MBR_Table 446 +#define BS_55AA 510 + +#define DIR_Name 0 +#define DIR_Attr 11 +#define DIR_NTres 12 +#define DIR_CrtTime 14 +#define DIR_CrtDate 16 +#define DIR_FstClusHI 20 +#define DIR_WrtTime 22 +#define DIR_WrtDate 24 +#define DIR_FstClusLO 26 +#define DIR_FileSize 28 +#define LDIR_Ord 0 +#define LDIR_Attr 11 +#define LDIR_Type 12 +#define LDIR_Chksum 13 +#define LDIR_FstClusLO 26 + + + +/*------------------------------------------------------------*/ +/* Work area */ + +#if _VOLUMES +static +FATFS *FatFs[_VOLUMES]; /* Pointer to the file system objects (logical drives) */ +#else +#error Number of drives must not be 0. +#endif + +static +WORD Fsid; /* File system mount ID */ + +#if _FS_RPATH +static +BYTE CurrVol; /* Current drive */ +#endif + +#if _FS_SHARE +static +FILESEM Files[_FS_SHARE]; /* File lock semaphores */ +#endif + +#if _USE_LFN == 0 /* No LFN */ +#define DEF_NAMEBUF BYTE sfn[12] +#define INIT_BUF(dobj) (dobj).fn = sfn +#define FREE_BUF() + +#elif _USE_LFN == 1 /* LFN with static LFN working buffer */ +static WCHAR LfnBuf[_MAX_LFN+1]; +#define DEF_NAMEBUF BYTE sfn[12] +#define INIT_BUF(dobj) { (dobj).fn = sfn; (dobj).lfn = LfnBuf; } +#define FREE_BUF() + +#elif _USE_LFN == 2 /* LFN with dynamic LFN working buffer on the stack */ +#define DEF_NAMEBUF BYTE sfn[12]; WCHAR lbuf[_MAX_LFN+1] +#define INIT_BUF(dobj) { (dobj).fn = sfn; (dobj).lfn = lbuf; } +#define FREE_BUF() + +#elif _USE_LFN == 3 /* LFN with dynamic LFN working buffer on the heap */ +#define DEF_NAMEBUF BYTE sfn[12]; WCHAR *lfn +#define INIT_BUF(dobj) { lfn = ff_memalloc((_MAX_LFN + 1) * 2); \ + if (!lfn) LEAVE_FF((dobj).fs, FR_NOT_ENOUGH_CORE); \ + (dobj).lfn = lfn; (dobj).fn = sfn; } +#define FREE_BUF() ff_memfree(lfn) + +#else +#error Wrong LFN configuration. +#endif + + + + +/*-------------------------------------------------------------------------- + + Module Private Functions + +---------------------------------------------------------------------------*/ + + +/*-----------------------------------------------------------------------*/ +/* String functions */ +/*-----------------------------------------------------------------------*/ + +/* Copy memory to memory */ +static +void mem_cpy (void* dst, const void* src, UINT cnt) { + BYTE *d = (BYTE*)dst; + const BYTE *s = (const BYTE*)src; + +#if _WORD_ACCESS == 1 + while (cnt >= sizeof(int)) { + *(int*)d = *(int*)s; + d += sizeof(int); s += sizeof(int); + cnt -= sizeof(int); + } +#endif + while (cnt--) + *d++ = *s++; +} + +/* Fill memory */ +static +void mem_set (void* dst, int val, UINT cnt) { + BYTE *d = (BYTE*)dst; + + while (cnt--) + *d++ = (BYTE)val; +} + +/* Compare memory to memory */ +static +int mem_cmp (const void* dst, const void* src, UINT cnt) { + const BYTE *d = (const BYTE *)dst, *s = (const BYTE *)src; + int r = 0; + + while (cnt-- && (r = *d++ - *s++) == 0) ; + return r; +} + +/* Check if chr is contained in the string */ +static +int chk_chr (const char* str, int chr) { + while (*str && *str != chr) str++; + return *str; +} + + + +/*-----------------------------------------------------------------------*/ +/* Request/Release grant to access the volume */ +/*-----------------------------------------------------------------------*/ +#if _FS_REENTRANT + +static +int lock_fs ( + FATFS *fs /* File system object */ +) +{ + return ff_req_grant(fs->sobj); +} + + +static +void unlock_fs ( + FATFS *fs, /* File system object */ + FRESULT res /* Result code to be returned */ +) +{ + if (res != FR_NOT_ENABLED && + res != FR_INVALID_DRIVE && + res != FR_INVALID_OBJECT && + res != FR_TIMEOUT) { + ff_rel_grant(fs->sobj); + } +} +#endif + + + +/*-----------------------------------------------------------------------*/ +/* File shareing control functions */ +/*-----------------------------------------------------------------------*/ +#if _FS_SHARE + +static +FRESULT chk_lock ( /* Check if the file can be accessed */ + DIR* dj, /* Directory object pointing the file to be checked */ + int acc /* Desired access (0:Read, 1:Write, 2:Delete/Rename) */ +) +{ + UINT i, be; + + /* Search file semaphore table */ + for (i = be = 0; i < _FS_SHARE; i++) { + if (Files[i].fs) { /* Existing entry */ + if (Files[i].fs == dj->fs && /* Check if the file matched with an open file */ + Files[i].clu == dj->sclust && + Files[i].idx == dj->index) break; + } else { /* Blank entry */ + be++; + } + } + if (i == _FS_SHARE) /* The file is not opened */ + return (be || acc == 2) ? FR_OK : FR_TOO_MANY_OPEN_FILES; /* Is there a blank entry for new file? */ + + /* The file has been opened. Reject any open against writing file and all write mode open */ + return (acc || Files[i].ctr == 0x100) ? FR_LOCKED : FR_OK; +} + + +static +int enq_lock ( /* Check if an entry is available for a new file */ + FATFS* fs /* File system object */ +) +{ + UINT i; + + for (i = 0; i < _FS_SHARE && Files[i].fs; i++) ; + return (i == _FS_SHARE) ? 0 : 1; +} + + +static +UINT inc_lock ( /* Increment file open counter and returns its index (0:int error) */ + DIR* dj, /* Directory object pointing the file to register or increment */ + int acc /* Desired access mode (0:Read, !0:Write) */ +) +{ + UINT i; + + + for (i = 0; i < _FS_SHARE; i++) { /* Find the file */ + if (Files[i].fs == dj->fs && + Files[i].clu == dj->sclust && + Files[i].idx == dj->index) break; + } + + if (i == _FS_SHARE) { /* Not opened. Register it as new. */ + for (i = 0; i < _FS_SHARE && Files[i].fs; i++) ; + if (i == _FS_SHARE) return 0; /* No space to register (int err) */ + Files[i].fs = dj->fs; + Files[i].clu = dj->sclust; + Files[i].idx = dj->index; + Files[i].ctr = 0; + } + + if (acc && Files[i].ctr) return 0; /* Access violation (int err) */ + + Files[i].ctr = acc ? 0x100 : Files[i].ctr + 1; /* Set semaphore value */ + + return i + 1; +} + + +static +FRESULT dec_lock ( /* Decrement file open counter */ + UINT i /* Semaphore index */ +) +{ + WORD n; + FRESULT res; + + + if (--i < _FS_SHARE) { + n = Files[i].ctr; + if (n == 0x100) n = 0; + if (n) n--; + Files[i].ctr = n; + if (!n) Files[i].fs = 0; + res = FR_OK; + } else { + res = FR_INT_ERR; + } + return res; +} + + +static +void clear_lock ( /* Clear lock entries of the volume */ + FATFS *fs +) +{ + UINT i; + + for (i = 0; i < _FS_SHARE; i++) { + if (Files[i].fs == fs) Files[i].fs = 0; + } +} +#endif + + + +/*-----------------------------------------------------------------------*/ +/* Change window offset */ +/*-----------------------------------------------------------------------*/ + +static +FRESULT move_window ( + FATFS *fs, /* File system object */ + DWORD sector /* Sector number to make appearance in the fs->win[] */ +) /* Move to zero only writes back dirty window */ +{ + DWORD wsect; + + + wsect = fs->winsect; + if (wsect != sector) { /* Changed current window */ +#if !_FS_READONLY + if (fs->wflag) { /* Write back dirty window if needed */ + if (disk_write(fs->drv, fs->win, wsect, 1) != RES_OK) + return FR_DISK_ERR; + fs->wflag = 0; + if (wsect < (fs->fatbase + fs->fsize)) { /* In FAT area */ + BYTE nf; + for (nf = fs->n_fats; nf > 1; nf--) { /* Reflect the change to all FAT copies */ + wsect += fs->fsize; + disk_write(fs->drv, fs->win, wsect, 1); + } + } + } +#endif + if (sector) { + if (disk_read(fs->drv, fs->win, sector, 1) != RES_OK) + return FR_DISK_ERR; + fs->winsect = sector; + } + } + + return FR_OK; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Clean-up cached data */ +/*-----------------------------------------------------------------------*/ +#if !_FS_READONLY +static +FRESULT sync ( /* FR_OK: successful, FR_DISK_ERR: failed */ + FATFS *fs /* File system object */ +) +{ + FRESULT res; + + + res = move_window(fs, 0); + if (res == FR_OK) { + /* Update FSInfo sector if needed */ + if (fs->fs_type == FS_FAT32 && fs->fsi_flag) { + fs->winsect = 0; + mem_set(fs->win, 0, 512); + ST_WORD(fs->win+BS_55AA, 0xAA55); + ST_DWORD(fs->win+FSI_LeadSig, 0x41615252); + ST_DWORD(fs->win+FSI_StrucSig, 0x61417272); + ST_DWORD(fs->win+FSI_Free_Count, fs->free_clust); + ST_DWORD(fs->win+FSI_Nxt_Free, fs->last_clust); + disk_write(fs->drv, fs->win, fs->fsi_sector, 1); + fs->fsi_flag = 0; + } + /* Make sure that no pending write process in the physical drive */ + if (disk_ioctl(fs->drv, CTRL_SYNC, (BYTE*)0) != RES_OK) + res = FR_DISK_ERR; + } + + return res; +} +#endif + + + + +/*-----------------------------------------------------------------------*/ +/* Get sector# from cluster# */ +/*-----------------------------------------------------------------------*/ + + +DWORD clust2sect ( /* !=0: Sector number, 0: Failed - invalid cluster# */ + FATFS *fs, /* File system object */ + DWORD clst /* Cluster# to be converted */ +) +{ + clst -= 2; + if (clst >= (fs->n_fatent - 2)) return 0; /* Invalid cluster# */ + return clst * fs->csize + fs->database; +} + + + + +/*-----------------------------------------------------------------------*/ +/* FAT access - Read value of a FAT entry */ +/*-----------------------------------------------------------------------*/ + + +DWORD get_fat ( /* 0xFFFFFFFF:Disk error, 1:Internal error, Else:Cluster status */ + FATFS *fs, /* File system object */ + DWORD clst /* Cluster# to get the link information */ +) +{ + UINT wc, bc; + BYTE *p; + + + if (clst < 2 || clst >= fs->n_fatent) /* Chack range */ + return 1; + + switch (fs->fs_type) { + case FS_FAT12 : + bc = (UINT)clst; bc += bc / 2; + if (move_window(fs, fs->fatbase + (bc / SS(fs)))) break; + wc = fs->win[bc % SS(fs)]; bc++; + if (move_window(fs, fs->fatbase + (bc / SS(fs)))) break; + wc |= fs->win[bc % SS(fs)] << 8; + return (clst & 1) ? (wc >> 4) : (wc & 0xFFF); + + case FS_FAT16 : + if (move_window(fs, fs->fatbase + (clst / (SS(fs) / 2)))) break; + p = &fs->win[clst * 2 % SS(fs)]; + return LD_WORD(p); + + case FS_FAT32 : + if (move_window(fs, fs->fatbase + (clst / (SS(fs) / 4)))) break; + p = &fs->win[clst * 4 % SS(fs)]; + return LD_DWORD(p) & 0x0FFFFFFF; + } + + return 0xFFFFFFFF; /* An error occurred at the disk I/O layer */ +} + + + + +/*-----------------------------------------------------------------------*/ +/* FAT access - Change value of a FAT entry */ +/*-----------------------------------------------------------------------*/ +#if !_FS_READONLY + +FRESULT put_fat ( + FATFS *fs, /* File system object */ + DWORD clst, /* Cluster# to be changed in range of 2 to fs->n_fatent - 1 */ + DWORD val /* New value to mark the cluster */ +) +{ + UINT bc; + BYTE *p; + FRESULT res; + + + if (clst < 2 || clst >= fs->n_fatent) { /* Check range */ + res = FR_INT_ERR; + + } else { + switch (fs->fs_type) { + case FS_FAT12 : + bc = clst; bc += bc / 2; + res = move_window(fs, fs->fatbase + (bc / SS(fs))); + if (res != FR_OK) break; + p = &fs->win[bc % SS(fs)]; + *p = (clst & 1) ? ((*p & 0x0F) | ((BYTE)val << 4)) : (BYTE)val; + bc++; + fs->wflag = 1; + res = move_window(fs, fs->fatbase + (bc / SS(fs))); + if (res != FR_OK) break; + p = &fs->win[bc % SS(fs)]; + *p = (clst & 1) ? (BYTE)(val >> 4) : ((*p & 0xF0) | ((BYTE)(val >> 8) & 0x0F)); + break; + + case FS_FAT16 : + res = move_window(fs, fs->fatbase + (clst / (SS(fs) / 2))); + if (res != FR_OK) break; + p = &fs->win[clst * 2 % SS(fs)]; + ST_WORD(p, (WORD)val); + break; + + case FS_FAT32 : + res = move_window(fs, fs->fatbase + (clst / (SS(fs) / 4))); + if (res != FR_OK) break; + p = &fs->win[clst * 4 % SS(fs)]; + val |= LD_DWORD(p) & 0xF0000000; + ST_DWORD(p, val); + break; + + default : + res = FR_INT_ERR; + } + fs->wflag = 1; + } + + return res; +} +#endif /* !_FS_READONLY */ + + + + +/*-----------------------------------------------------------------------*/ +/* FAT handling - Remove a cluster chain */ +/*-----------------------------------------------------------------------*/ +#if !_FS_READONLY +static +FRESULT remove_chain ( + FATFS *fs, /* File system object */ + DWORD clst /* Cluster# to remove a chain from */ +) +{ + FRESULT res; + DWORD nxt; +#if _USE_ERASE + DWORD scl = clst, ecl = clst, resion[2]; +#endif + + if (clst < 2 || clst >= fs->n_fatent) { /* Check range */ + res = FR_INT_ERR; + + } else { + res = FR_OK; + while (clst < fs->n_fatent) { /* Not a last link? */ + nxt = get_fat(fs, clst); /* Get cluster status */ + if (nxt == 0) break; /* Empty cluster? */ + if (nxt == 1) { res = FR_INT_ERR; break; } /* Internal error? */ + if (nxt == 0xFFFFFFFF) { res = FR_DISK_ERR; break; } /* Disk error? */ + res = put_fat(fs, clst, 0); /* Mark the cluster "empty" */ + if (res != FR_OK) break; + if (fs->free_clust != 0xFFFFFFFF) { /* Update FSInfo */ + fs->free_clust++; + fs->fsi_flag = 1; + } +#if _USE_ERASE + if (ecl + 1 == nxt) { /* Next cluster is contiguous */ + ecl = nxt; + } else { /* End of contiguous clusters */ + resion[0] = clust2sect(fs, scl); /* Start sector */ + resion[1] = clust2sect(fs, ecl) + fs->csize - 1; /* End sector */ + disk_ioctl(fs->drv, CTRL_ERASE_SECTOR, resion); /* Erase the block */ + scl = ecl = nxt; + } +#endif + clst = nxt; /* Next cluster */ + } + } + + return res; +} +#endif + + + + +/*-----------------------------------------------------------------------*/ +/* FAT handling - Stretch or Create a cluster chain */ +/*-----------------------------------------------------------------------*/ +#if !_FS_READONLY +static +DWORD create_chain ( /* 0:No free cluster, 1:Internal error, 0xFFFFFFFF:Disk error, >=2:New cluster# */ + FATFS *fs, /* File system object */ + DWORD clst /* Cluster# to stretch. 0 means create a new chain. */ +) +{ + DWORD cs, ncl, scl; + FRESULT res; + + + if (clst == 0) { /* Create a new chain */ + scl = fs->last_clust; /* Get suggested start point */ + if (!scl || scl >= fs->n_fatent) scl = 1; + } + else { /* Stretch the current chain */ + cs = get_fat(fs, clst); /* Check the cluster status */ + if (cs < 2) return 1; /* It is an invalid cluster */ + if (cs < fs->n_fatent) return cs; /* It is already followed by next cluster */ + scl = clst; + } + + ncl = scl; /* Start cluster */ + for (;;) { + ncl++; /* Next cluster */ + if (ncl >= fs->n_fatent) { /* Wrap around */ + ncl = 2; + if (ncl > scl) return 0; /* No free cluster */ + } + cs = get_fat(fs, ncl); /* Get the cluster status */ + if (cs == 0) break; /* Found a free cluster */ + if (cs == 0xFFFFFFFF || cs == 1)/* An error occurred */ + return cs; + if (ncl == scl) return 0; /* No free cluster */ + } + + res = put_fat(fs, ncl, 0x0FFFFFFF); /* Mark the new cluster "last link" */ + if (res == FR_OK && clst != 0) { + res = put_fat(fs, clst, ncl); /* Link it to the previous one if needed */ + } + if (res == FR_OK) { + fs->last_clust = ncl; /* Update FSINFO */ + if (fs->free_clust != 0xFFFFFFFF) { + fs->free_clust--; + fs->fsi_flag = 1; + } + } else { + ncl = (res == FR_DISK_ERR) ? 0xFFFFFFFF : 1; + } + + return ncl; /* Return new cluster number or error code */ +} +#endif /* !_FS_READONLY */ + + + + +/*-----------------------------------------------------------------------*/ +/* Directory handling - Set directory index */ +/*-----------------------------------------------------------------------*/ + +static +FRESULT dir_sdi ( + DIR *dj, /* Pointer to directory object */ + WORD idx /* Directory index number */ +) +{ + DWORD clst; + WORD ic; + + + dj->index = idx; + clst = dj->sclust; + if (clst == 1 || clst >= dj->fs->n_fatent) /* Check start cluster range */ + return FR_INT_ERR; + if (!clst && dj->fs->fs_type == FS_FAT32) /* Replace cluster# 0 with root cluster# if in FAT32 */ + clst = dj->fs->dirbase; + + if (clst == 0) { /* Static table (root-dir in FAT12/16) */ + dj->clust = clst; + if (idx >= dj->fs->n_rootdir) /* Index is out of range */ + return FR_INT_ERR; + dj->sect = dj->fs->dirbase + idx / (SS(dj->fs) / 32); /* Sector# */ + } + else { /* Dynamic table (sub-dirs or root-dir in FAT32) */ + ic = SS(dj->fs) / 32 * dj->fs->csize; /* Entries per cluster */ + while (idx >= ic) { /* Follow cluster chain */ + clst = get_fat(dj->fs, clst); /* Get next cluster */ + if (clst == 0xFFFFFFFF) return FR_DISK_ERR; /* Disk error */ + if (clst < 2 || clst >= dj->fs->n_fatent) /* Reached to end of table or int error */ + return FR_INT_ERR; + idx -= ic; + } + dj->clust = clst; + dj->sect = clust2sect(dj->fs, clst) + idx / (SS(dj->fs) / 32); /* Sector# */ + } + + dj->dir = dj->fs->win + (idx % (SS(dj->fs) / 32)) * 32; /* Ptr to the entry in the sector */ + + return FR_OK; /* Seek succeeded */ +} + + + + +/*-----------------------------------------------------------------------*/ +/* Directory handling - Move directory index next */ +/*-----------------------------------------------------------------------*/ + +static +FRESULT dir_next ( /* FR_OK:Succeeded, FR_NO_FILE:End of table, FR_DENIED:EOT and could not stretch */ + DIR *dj, /* Pointer to directory object */ + int stretch /* 0: Do not stretch table, 1: Stretch table if needed */ +) +{ + DWORD clst; + WORD i; + + + i = dj->index + 1; + if (!i || !dj->sect) /* Report EOT when index has reached 65535 */ + return FR_NO_FILE; + + if (!(i % (SS(dj->fs) / 32))) { /* Sector changed? */ + dj->sect++; /* Next sector */ + + if (dj->clust == 0) { /* Static table */ + if (i >= dj->fs->n_rootdir) /* Report EOT when end of table */ + return FR_NO_FILE; + } + else { /* Dynamic table */ + if (((i / (SS(dj->fs) / 32)) & (dj->fs->csize - 1)) == 0) { /* Cluster changed? */ + clst = get_fat(dj->fs, dj->clust); /* Get next cluster */ + if (clst <= 1) return FR_INT_ERR; + if (clst == 0xFFFFFFFF) return FR_DISK_ERR; + if (clst >= dj->fs->n_fatent) { /* When it reached end of dynamic table */ +#if !_FS_READONLY + BYTE c; + if (!stretch) return FR_NO_FILE; /* When do not stretch, report EOT */ + clst = create_chain(dj->fs, dj->clust); /* Stretch cluster chain */ + if (clst == 0) return FR_DENIED; /* No free cluster */ + if (clst == 1) return FR_INT_ERR; + if (clst == 0xFFFFFFFF) return FR_DISK_ERR; + /* Clean-up stretched table */ + if (move_window(dj->fs, 0)) return FR_DISK_ERR; /* Flush active window */ + mem_set(dj->fs->win, 0, SS(dj->fs)); /* Clear window buffer */ + dj->fs->winsect = clust2sect(dj->fs, clst); /* Cluster start sector */ + for (c = 0; c < dj->fs->csize; c++) { /* Fill the new cluster with 0 */ + dj->fs->wflag = 1; + if (move_window(dj->fs, 0)) return FR_DISK_ERR; + dj->fs->winsect++; + } + dj->fs->winsect -= c; /* Rewind window address */ +#else + return FR_NO_FILE; /* Report EOT */ +#endif + } + dj->clust = clst; /* Initialize data for new cluster */ + dj->sect = clust2sect(dj->fs, clst); + } + } + } + + dj->index = i; + dj->dir = dj->fs->win + (i % (SS(dj->fs) / 32)) * 32; + + return FR_OK; +} + + + + +/*-----------------------------------------------------------------------*/ +/* LFN handling - Test/Pick/Fit an LFN segment from/to directory entry */ +/*-----------------------------------------------------------------------*/ +#if _USE_LFN +static +const BYTE LfnOfs[] = {1,3,5,7,9,14,16,18,20,22,24,28,30}; /* Offset of LFN chars in the directory entry */ + + +static +int cmp_lfn ( /* 1:Matched, 0:Not matched */ + WCHAR *lfnbuf, /* Pointer to the LFN to be compared */ + BYTE *dir /* Pointer to the directory entry containing a part of LFN */ +) +{ + UINT i, s; + WCHAR wc, uc; + + + i = ((dir[LDIR_Ord] & 0xBF) - 1) * 13; /* Get offset in the LFN buffer */ + s = 0; wc = 1; + do { + uc = LD_WORD(dir+LfnOfs[s]); /* Pick an LFN character from the entry */ + if (wc) { /* Last char has not been processed */ + wc = ff_wtoupper(uc); /* Convert it to upper case */ + if (i >= _MAX_LFN || wc != ff_wtoupper(lfnbuf[i++])) /* Compare it */ + return 0; /* Not matched */ + } else { + if (uc != 0xFFFF) return 0; /* Check filler */ + } + } while (++s < 13); /* Repeat until all chars in the entry are checked */ + + if ((dir[LDIR_Ord] & 0x40) && wc && lfnbuf[i]) /* Last segment matched but different length */ + return 0; + + return 1; /* The part of LFN matched */ +} + + + +static +int pick_lfn ( /* 1:Succeeded, 0:Buffer overflow */ + WCHAR *lfnbuf, /* Pointer to the Unicode-LFN buffer */ + BYTE *dir /* Pointer to the directory entry */ +) +{ + UINT i, s; + WCHAR wc, uc; + + + i = ((dir[LDIR_Ord] & 0x3F) - 1) * 13; /* Offset in the LFN buffer */ + + s = 0; wc = 1; + do { + uc = LD_WORD(dir+LfnOfs[s]); /* Pick an LFN character from the entry */ + if (wc) { /* Last char has not been processed */ + if (i >= _MAX_LFN) return 0; /* Buffer overflow? */ + lfnbuf[i++] = wc = uc; /* Store it */ + } else { + if (uc != 0xFFFF) return 0; /* Check filler */ + } + } while (++s < 13); /* Read all character in the entry */ + + if (dir[LDIR_Ord] & 0x40) { /* Put terminator if it is the last LFN part */ + if (i >= _MAX_LFN) return 0; /* Buffer overflow? */ + lfnbuf[i] = 0; + } + + return 1; +} + + +#if !_FS_READONLY +static +void fit_lfn ( + const WCHAR *lfnbuf, /* Pointer to the LFN buffer */ + BYTE *dir, /* Pointer to the directory entry */ + BYTE ord, /* LFN order (1-20) */ + BYTE sum /* SFN sum */ +) +{ + UINT i, s; + WCHAR wc; + + + dir[LDIR_Chksum] = sum; /* Set check sum */ + dir[LDIR_Attr] = AM_LFN; /* Set attribute. LFN entry */ + dir[LDIR_Type] = 0; + ST_WORD(dir+LDIR_FstClusLO, 0); + + i = (ord - 1) * 13; /* Get offset in the LFN buffer */ + s = wc = 0; + do { + if (wc != 0xFFFF) wc = lfnbuf[i++]; /* Get an effective char */ + ST_WORD(dir+LfnOfs[s], wc); /* Put it */ + if (!wc) wc = 0xFFFF; /* Padding chars following last char */ + } while (++s < 13); + if (wc == 0xFFFF || !lfnbuf[i]) ord |= 0x40; /* Bottom LFN part is the start of LFN sequence */ + dir[LDIR_Ord] = ord; /* Set the LFN order */ +} + +#endif +#endif + + + +/*-----------------------------------------------------------------------*/ +/* Create numbered name */ +/*-----------------------------------------------------------------------*/ +#if _USE_LFN +void gen_numname ( + BYTE *dst, /* Pointer to generated SFN */ + const BYTE *src, /* Pointer to source SFN to be modified */ + const WCHAR *lfn, /* Pointer to LFN */ + WORD seq /* Sequence number */ +) +{ + BYTE ns[8], c; + UINT i, j; + + + mem_cpy(dst, src, 11); + + if (seq > 5) { /* On many collisions, generate a hash number instead of sequential number */ + do seq = (seq >> 1) + (seq << 15) + (WORD)*lfn++; while (*lfn); + } + + /* itoa */ + i = 7; + do { + c = (seq % 16) + '0'; + if (c > '9') c += 7; + ns[i--] = c; + seq /= 16; + } while (seq); + ns[i] = '~'; + + /* Append the number */ + for (j = 0; j < i && dst[j] != ' '; j++) { + if (IsDBCS1(dst[j])) { + if (j == i - 1) break; + j++; + } + } + do { + dst[j++] = (i < 8) ? ns[i++] : ' '; + } while (j < 8); +} +#endif + + + + +/*-----------------------------------------------------------------------*/ +/* Calculate sum of an SFN */ +/*-----------------------------------------------------------------------*/ +#if _USE_LFN +static +BYTE sum_sfn ( + const BYTE *dir /* Ptr to directory entry */ +) +{ + BYTE sum = 0; + UINT n = 11; + + do sum = (sum >> 1) + (sum << 7) + *dir++; while (--n); + return sum; +} +#endif + + + + +/*-----------------------------------------------------------------------*/ +/* Directory handling - Find an object in the directory */ +/*-----------------------------------------------------------------------*/ + +static +FRESULT dir_find ( + DIR *dj /* Pointer to the directory object linked to the file name */ +) +{ + FRESULT res; + BYTE c, *dir; +#if _USE_LFN + BYTE a, ord, sum; +#endif + + res = dir_sdi(dj, 0); /* Rewind directory object */ + if (res != FR_OK) return res; + +#if _USE_LFN + ord = sum = 0xFF; +#endif + do { + res = move_window(dj->fs, dj->sect); + if (res != FR_OK) break; + dir = dj->dir; /* Ptr to the directory entry of current index */ + c = dir[DIR_Name]; + if (c == 0) { res = FR_NO_FILE; break; } /* Reached to end of table */ +#if _USE_LFN /* LFN configuration */ + a = dir[DIR_Attr] & AM_MASK; + if (c == 0xE5 || ((a & AM_VOL) && a != AM_LFN)) { /* An entry without valid data */ + ord = 0xFF; + } else { + if (a == AM_LFN) { /* An LFN entry is found */ + if (dj->lfn) { + if (c & 0x40) { /* Is it start of LFN sequence? */ + sum = dir[LDIR_Chksum]; + c &= 0xBF; ord = c; /* LFN start order */ + dj->lfn_idx = dj->index; + } + /* Check validity of the LFN entry and compare it with given name */ + ord = (c == ord && sum == dir[LDIR_Chksum] && cmp_lfn(dj->lfn, dir)) ? ord - 1 : 0xFF; + } + } else { /* An SFN entry is found */ + if (!ord && sum == sum_sfn(dir)) break; /* LFN matched? */ + ord = 0xFF; dj->lfn_idx = 0xFFFF; /* Reset LFN sequence */ + if (!(dj->fn[NS] & NS_LOSS) && !mem_cmp(dir, dj->fn, 11)) break; /* SFN matched? */ + } + } +#else /* Non LFN configuration */ + if (!(dir[DIR_Attr] & AM_VOL) && !mem_cmp(dir, dj->fn, 11)) /* Is it a valid entry? */ + break; +#endif + res = dir_next(dj, 0); /* Next entry */ + } while (res == FR_OK); + + return res; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Read an object from the directory */ +/*-----------------------------------------------------------------------*/ +#if _FS_MINIMIZE <= 1 +static +FRESULT dir_read ( + DIR *dj /* Pointer to the directory object that pointing the entry to be read */ +) +{ + FRESULT res; + BYTE c, *dir; +#if _USE_LFN + BYTE a, ord = 0xFF, sum = 0xFF; +#endif + + res = FR_NO_FILE; + while (dj->sect) { + res = move_window(dj->fs, dj->sect); + if (res != FR_OK) break; + dir = dj->dir; /* Ptr to the directory entry of current index */ + c = dir[DIR_Name]; + if (c == 0) { res = FR_NO_FILE; break; } /* Reached to end of table */ +#if _USE_LFN /* LFN configuration */ + a = dir[DIR_Attr] & AM_MASK; + if (c == 0xE5 || (!_FS_RPATH && c == '.') || ((a & AM_VOL) && a != AM_LFN)) { /* An entry without valid data */ + ord = 0xFF; + } else { + if (a == AM_LFN) { /* An LFN entry is found */ + if (c & 0x40) { /* Is it start of LFN sequence? */ + sum = dir[LDIR_Chksum]; + c &= 0xBF; ord = c; + dj->lfn_idx = dj->index; + } + /* Check LFN validity and capture it */ + ord = (c == ord && sum == dir[LDIR_Chksum] && pick_lfn(dj->lfn, dir)) ? ord - 1 : 0xFF; + } else { /* An SFN entry is found */ + if (ord || sum != sum_sfn(dir)) /* Is there a valid LFN? */ + dj->lfn_idx = 0xFFFF; /* It has no LFN. */ + break; + } + } +#else /* Non LFN configuration */ + if (c != 0xE5 && (_FS_RPATH || c != '.') && !(dir[DIR_Attr] & AM_VOL)) /* Is it a valid entry? */ + break; +#endif + res = dir_next(dj, 0); /* Next entry */ + if (res != FR_OK) break; + } + + if (res != FR_OK) dj->sect = 0; + + return res; +} +#endif + + + +/*-----------------------------------------------------------------------*/ +/* Register an object to the directory */ +/*-----------------------------------------------------------------------*/ +#if !_FS_READONLY +static +FRESULT dir_register ( /* FR_OK:Successful, FR_DENIED:No free entry or too many SFN collision, FR_DISK_ERR:Disk error */ + DIR *dj /* Target directory with object name to be created */ +) +{ + FRESULT res; + BYTE c, *dir; +#if _USE_LFN /* LFN configuration */ + WORD n, ne, is; + BYTE sn[12], *fn, sum; + WCHAR *lfn; + + + fn = dj->fn; lfn = dj->lfn; + mem_cpy(sn, fn, 12); + + if (_FS_RPATH && (sn[NS] & NS_DOT)) /* Cannot create dot entry */ + return FR_INVALID_NAME; + + if (sn[NS] & NS_LOSS) { /* When LFN is out of 8.3 format, generate a numbered name */ + fn[NS] = 0; dj->lfn = 0; /* Find only SFN */ + for (n = 1; n < 100; n++) { + gen_numname(fn, sn, lfn, n); /* Generate a numbered name */ + res = dir_find(dj); /* Check if the name collides with existing SFN */ + if (res != FR_OK) break; + } + if (n == 100) return FR_DENIED; /* Abort if too many collisions */ + if (res != FR_NO_FILE) return res; /* Abort if the result is other than 'not collided' */ + fn[NS] = sn[NS]; dj->lfn = lfn; + } + + if (sn[NS] & NS_LFN) { /* When LFN is to be created, reserve an SFN + LFN entries. */ + for (ne = 0; lfn[ne]; ne++) ; + ne = (ne + 25) / 13; + } else { /* Otherwise reserve only an SFN entry. */ + ne = 1; + } + + /* Reserve contiguous entries */ + res = dir_sdi(dj, 0); + if (res != FR_OK) return res; + n = is = 0; + do { + res = move_window(dj->fs, dj->sect); + if (res != FR_OK) break; + c = *dj->dir; /* Check the entry status */ + if (c == 0xE5 || c == 0) { /* Is it a blank entry? */ + if (n == 0) is = dj->index; /* First index of the contiguous entry */ + if (++n == ne) break; /* A contiguous entry that required count is found */ + } else { + n = 0; /* Not a blank entry. Restart to search */ + } + res = dir_next(dj, 1); /* Next entry with table stretch */ + } while (res == FR_OK); + + if (res == FR_OK && ne > 1) { /* Initialize LFN entry if needed */ + res = dir_sdi(dj, is); + if (res == FR_OK) { + sum = sum_sfn(dj->fn); /* Sum of the SFN tied to the LFN */ + ne--; + do { /* Store LFN entries in bottom first */ + res = move_window(dj->fs, dj->sect); + if (res != FR_OK) break; + fit_lfn(dj->lfn, dj->dir, (BYTE)ne, sum); + dj->fs->wflag = 1; + res = dir_next(dj, 0); /* Next entry */ + } while (res == FR_OK && --ne); + } + } + +#else /* Non LFN configuration */ + res = dir_sdi(dj, 0); + if (res == FR_OK) { + do { /* Find a blank entry for the SFN */ + res = move_window(dj->fs, dj->sect); + if (res != FR_OK) break; + c = *dj->dir; + if (c == 0xE5 || c == 0) break; /* Is it a blank entry? */ + res = dir_next(dj, 1); /* Next entry with table stretch */ + } while (res == FR_OK); + } +#endif + + if (res == FR_OK) { /* Initialize the SFN entry */ + res = move_window(dj->fs, dj->sect); + if (res == FR_OK) { + dir = dj->dir; + mem_set(dir, 0, 32); /* Clean the entry */ + mem_cpy(dir, dj->fn, 11); /* Put SFN */ +#if _USE_LFN + dir[DIR_NTres] = *(dj->fn+NS) & (NS_BODY | NS_EXT); /* Put NT flag */ +#endif + dj->fs->wflag = 1; + } + } + + return res; +} +#endif /* !_FS_READONLY */ + + + + +/*-----------------------------------------------------------------------*/ +/* Remove an object from the directory */ +/*-----------------------------------------------------------------------*/ +#if !_FS_READONLY && !_FS_MINIMIZE +static +FRESULT dir_remove ( /* FR_OK: Successful, FR_DISK_ERR: A disk error */ + DIR *dj /* Directory object pointing the entry to be removed */ +) +{ + FRESULT res; +#if _USE_LFN /* LFN configuration */ + WORD i; + + i = dj->index; /* SFN index */ + res = dir_sdi(dj, (WORD)((dj->lfn_idx == 0xFFFF) ? i : dj->lfn_idx)); /* Goto the SFN or top of the LFN entries */ + if (res == FR_OK) { + do { + res = move_window(dj->fs, dj->sect); + if (res != FR_OK) break; + *dj->dir = 0xE5; /* Mark the entry "deleted" */ + dj->fs->wflag = 1; + if (dj->index >= i) break; /* When reached SFN, all entries of the object has been deleted. */ + res = dir_next(dj, 0); /* Next entry */ + } while (res == FR_OK); + if (res == FR_NO_FILE) res = FR_INT_ERR; + } + +#else /* Non LFN configuration */ + res = dir_sdi(dj, dj->index); + if (res == FR_OK) { + res = move_window(dj->fs, dj->sect); + if (res == FR_OK) { + *dj->dir = 0xE5; /* Mark the entry "deleted" */ + dj->fs->wflag = 1; + } + } +#endif + + return res; +} +#endif /* !_FS_READONLY */ + + + + +/*-----------------------------------------------------------------------*/ +/* Pick a segment and create the object name in directory form */ +/*-----------------------------------------------------------------------*/ + +static +FRESULT create_name ( + DIR *dj, /* Pointer to the directory object */ + const TCHAR **path /* Pointer to pointer to the segment in the path string */ +) +{ +#ifdef _EXCVT + static const BYTE excvt[] = _EXCVT; /* Upper conversion table for extended chars */ +#endif + +#if _USE_LFN /* LFN configuration */ + BYTE b, cf; + WCHAR w, *lfn; + UINT i, ni, si, di; + const TCHAR *p; + + /* Create LFN in Unicode */ + si = di = 0; + p = *path; + lfn = dj->lfn; + for (;;) { + w = p[si++]; /* Get a character */ + if (w < ' ' || w == '/' || w == '\\') break; /* Break on end of segment */ + if (di >= _MAX_LFN) /* Reject too long name */ + return FR_INVALID_NAME; +#if !_LFN_UNICODE + w &= 0xFF; + if (IsDBCS1(w)) { /* Check if it is a DBC 1st byte (always false on SBCS cfg) */ + b = (BYTE)p[si++]; /* Get 2nd byte */ + if (!IsDBCS2(b)) + return FR_INVALID_NAME; /* Reject invalid sequence */ + w = (w << 8) + b; /* Create a DBC */ + } + w = ff_convert(w, 1); /* Convert ANSI/OEM to Unicode */ + if (!w) return FR_INVALID_NAME; /* Reject invalid code */ +#endif + if (w < 0x80 && chk_chr("\"*:<>\?|\x7F", w)) /* Reject illegal chars for LFN */ + return FR_INVALID_NAME; + lfn[di++] = w; /* Store the Unicode char */ + } + *path = &p[si]; /* Return pointer to the next segment */ + cf = (w < ' ') ? NS_LAST : 0; /* Set last segment flag if end of path */ +#if _FS_RPATH + if ((di == 1 && lfn[di-1] == '.') || /* Is this a dot entry? */ + (di == 2 && lfn[di-1] == '.' && lfn[di-2] == '.')) { + lfn[di] = 0; + for (i = 0; i < 11; i++) + dj->fn[i] = (i < di) ? '.' : ' '; + dj->fn[i] = cf | NS_DOT; /* This is a dot entry */ + return FR_OK; + } +#endif + while (di) { /* Strip trailing spaces and dots */ + w = lfn[di-1]; + if (w != ' ' && w != '.') break; + di--; + } + if (!di) return FR_INVALID_NAME; /* Reject nul string */ + + lfn[di] = 0; /* LFN is created */ + + /* Create SFN in directory form */ + mem_set(dj->fn, ' ', 11); + for (si = 0; lfn[si] == ' ' || lfn[si] == '.'; si++) ; /* Strip leading spaces and dots */ + if (si) cf |= NS_LOSS | NS_LFN; + while (di && lfn[di - 1] != '.') di--; /* Find extension (di<=si: no extension) */ + + b = i = 0; ni = 8; + for (;;) { + w = lfn[si++]; /* Get an LFN char */ + if (!w) break; /* Break on end of the LFN */ + if (w == ' ' || (w == '.' && si != di)) { /* Remove spaces and dots */ + cf |= NS_LOSS | NS_LFN; continue; + } + + if (i >= ni || si == di) { /* Extension or end of SFN */ + if (ni == 11) { /* Long extension */ + cf |= NS_LOSS | NS_LFN; break; + } + if (si != di) cf |= NS_LOSS | NS_LFN; /* Out of 8.3 format */ + if (si > di) break; /* No extension */ + si = di; i = 8; ni = 11; /* Enter extension section */ + b <<= 2; continue; + } + + if (w >= 0x80) { /* Non ASCII char */ +#ifdef _EXCVT + w = ff_convert(w, 0); /* Unicode -> OEM code */ + if (w) w = excvt[w - 0x80]; /* Convert extended char to upper (SBCS) */ +#else + w = ff_convert(ff_wtoupper(w), 0); /* Upper converted Unicode -> OEM code */ +#endif + cf |= NS_LFN; /* Force create LFN entry */ + } + + if (_DF1S && w >= 0x100) { /* Double byte char (always false on SBCS cfg) */ + if (i >= ni - 1) { + cf |= NS_LOSS | NS_LFN; i = ni; continue; + } + dj->fn[i++] = (BYTE)(w >> 8); + } else { /* Single byte char */ + if (!w || chk_chr("+,;=[]", w)) { /* Replace illegal chars for SFN */ + w = '_'; cf |= NS_LOSS | NS_LFN;/* Lossy conversion */ + } else { + if (IsUpper(w)) { /* ASCII large capital */ + b |= 2; + } else { + if (IsLower(w)) { /* ASCII small capital */ + b |= 1; w -= 0x20; + } + } + } + } + dj->fn[i++] = (BYTE)w; + } + + if (dj->fn[0] == 0xE5) dj->fn[0] = 0x05; /* If the first char collides with deleted mark, replace it with 0x05 */ + + if (ni == 8) b <<= 2; + if ((b & 0x0C) == 0x0C || (b & 0x03) == 0x03) /* Create LFN entry when there are composite capitals */ + cf |= NS_LFN; + if (!(cf & NS_LFN)) { /* When LFN is in 8.3 format without extended char, NT flags are created */ + if ((b & 0x03) == 0x01) cf |= NS_EXT; /* NT flag (Extension has only small capital) */ + if ((b & 0x0C) == 0x04) cf |= NS_BODY; /* NT flag (Filename has only small capital) */ + } + + dj->fn[NS] = cf; /* SFN is created */ + + return FR_OK; + + +#else /* Non-LFN configuration */ + BYTE b, c, d, *sfn; + UINT ni, si, i; + const char *p; + + /* Create file name in directory form */ + sfn = dj->fn; + mem_set(sfn, ' ', 11); + si = i = b = 0; ni = 8; + p = *path; +#if _FS_RPATH + if (p[si] == '.') { /* Is this a dot entry? */ + for (;;) { + c = (BYTE)p[si++]; + if (c != '.' || si >= 3) break; + sfn[i++] = c; + } + if (c != '/' && c != '\\' && c > ' ') return FR_INVALID_NAME; + *path = &p[si]; /* Return pointer to the next segment */ + sfn[NS] = (c <= ' ') ? NS_LAST | NS_DOT : NS_DOT; /* Set last segment flag if end of path */ + return FR_OK; + } +#endif + for (;;) { + c = (BYTE)p[si++]; + if (c <= ' ' || c == '/' || c == '\\') break; /* Break on end of segment */ + if (c == '.' || i >= ni) { + if (ni != 8 || c != '.') return FR_INVALID_NAME; + i = 8; ni = 11; + b <<= 2; continue; + } + if (c >= 0x80) { /* Extended char? */ + b |= 3; /* Eliminate NT flag */ +#ifdef _EXCVT + c = excvt[c-0x80]; /* Upper conversion (SBCS) */ +#else +#if !_DF1S /* ASCII only cfg */ + return FR_INVALID_NAME; +#endif +#endif + } + if (IsDBCS1(c)) { /* Check if it is a DBC 1st byte (always false on SBCS cfg) */ + d = (BYTE)p[si++]; /* Get 2nd byte */ + if (!IsDBCS2(d) || i >= ni - 1) /* Reject invalid DBC */ + return FR_INVALID_NAME; + sfn[i++] = c; + sfn[i++] = d; + } else { /* Single byte code */ + if (chk_chr("\"*+,:;<=>\?[]|\x7F", c)) /* Reject illegal chrs for SFN */ + return FR_INVALID_NAME; + if (IsUpper(c)) { /* ASCII large capital? */ + b |= 2; + } else { + if (IsLower(c)) { /* ASCII small capital? */ + b |= 1; c -= 0x20; + } + } + sfn[i++] = c; + } + } + *path = &p[si]; /* Return pointer to the next segment */ + c = (c <= ' ') ? NS_LAST : 0; /* Set last segment flag if end of path */ + + if (!i) return FR_INVALID_NAME; /* Reject nul string */ + if (sfn[0] == 0xE5) sfn[0] = 0x05; /* When first char collides with 0xE5, replace it with 0x05 */ + + if (ni == 8) b <<= 2; + if ((b & 0x03) == 0x01) c |= NS_EXT; /* NT flag (Name extension has only small capital) */ + if ((b & 0x0C) == 0x04) c |= NS_BODY; /* NT flag (Name body has only small capital) */ + + sfn[NS] = c; /* Store NT flag, File name is created */ + + return FR_OK; +#endif +} + + + + +/*-----------------------------------------------------------------------*/ +/* Get file information from directory entry */ +/*-----------------------------------------------------------------------*/ +#if _FS_MINIMIZE <= 1 +static +void get_fileinfo ( /* No return code */ + DIR *dj, /* Pointer to the directory object */ + FILINFO *fno /* Pointer to the file information to be filled */ +) +{ + UINT i; + BYTE nt, *dir; + TCHAR *p, c; + + + p = fno->fname; + if (dj->sect) { + dir = dj->dir; + nt = dir[DIR_NTres]; /* NT flag */ + for (i = 0; i < 8; i++) { /* Copy name body */ + c = dir[i]; + if (c == ' ') break; + if (c == 0x05) c = (TCHAR)0xE5; + if (_USE_LFN && (nt & NS_BODY) && IsUpper(c)) c += 0x20; +#if _LFN_UNICODE + if (IsDBCS1(c) && i < 7 && IsDBCS2(dir[i+1])) + c = (c << 8) | dir[++i]; + c = ff_convert(c, 1); + if (!c) c = '?'; +#endif + *p++ = c; + } + if (dir[8] != ' ') { /* Copy name extension */ + *p++ = '.'; + for (i = 8; i < 11; i++) { + c = dir[i]; + if (c == ' ') break; + if (_USE_LFN && (nt & NS_EXT) && IsUpper(c)) c += 0x20; +#if _LFN_UNICODE + if (IsDBCS1(c) && i < 10 && IsDBCS2(dir[i+1])) + c = (c << 8) | dir[++i]; + c = ff_convert(c, 1); + if (!c) c = '?'; +#endif + *p++ = c; + } + } + fno->fattrib = dir[DIR_Attr]; /* Attribute */ + fno->fsize = LD_DWORD(dir+DIR_FileSize); /* Size */ + fno->fdate = LD_WORD(dir+DIR_WrtDate); /* Date */ + fno->ftime = LD_WORD(dir+DIR_WrtTime); /* Time */ + } + *p = 0; /* Terminate SFN str by a \0 */ + +#if _USE_LFN + if (fno->lfname && fno->lfsize) { + TCHAR *tp = fno->lfname; + WCHAR w, *lfn; + + i = 0; + if (dj->sect && dj->lfn_idx != 0xFFFF) {/* Get LFN if available */ + lfn = dj->lfn; + while ((w = *lfn++) != 0) { /* Get an LFN char */ +#if !_LFN_UNICODE + w = ff_convert(w, 0); /* Unicode -> OEM conversion */ + if (!w) { i = 0; break; } /* Could not convert, no LFN */ + if (_DF1S && w >= 0x100) /* Put 1st byte if it is a DBC (always false on SBCS cfg) */ + tp[i++] = (TCHAR)(w >> 8); +#endif + if (i >= fno->lfsize - 1) { i = 0; break; } /* Buffer overflow, no LFN */ + tp[i++] = (TCHAR)w; + } + } + tp[i] = 0; /* Terminate the LFN str by a \0 */ + } +#endif +} +#endif /* _FS_MINIMIZE <= 1 */ + + + + +/*-----------------------------------------------------------------------*/ +/* Follow a file path */ +/*-----------------------------------------------------------------------*/ + +static +FRESULT follow_path ( /* FR_OK(0): successful, !=0: error code */ + DIR *dj, /* Directory object to return last directory and found object */ + const TCHAR *path /* Full-path string to find a file or directory */ +) +{ + FRESULT res; + BYTE *dir, ns; + + +#if _FS_RPATH + if (*path == '/' || *path == '\\') { /* There is a heading separator */ + path++; dj->sclust = 0; /* Strip it and start from the root dir */ + } else { /* No heading separator */ + dj->sclust = dj->fs->cdir; /* Start from the current dir */ + } +#else + if (*path == '/' || *path == '\\') /* Strip heading separator if exist */ + path++; + dj->sclust = 0; /* Start from the root dir */ +#endif + + if ((UINT)*path < ' ') { /* Nul path means the start directory itself */ + res = dir_sdi(dj, 0); + dj->dir = 0; + + } else { /* Follow path */ + for (;;) { + res = create_name(dj, &path); /* Get a segment */ + if (res != FR_OK) break; + res = dir_find(dj); /* Find it */ + ns = *(dj->fn+NS); + if (res != FR_OK) { /* Failed to find the object */ + if (res != FR_NO_FILE) break; /* Abort if any hard error occured */ + /* Object not found */ + if (_FS_RPATH && (ns & NS_DOT)) { /* If dot entry is not exit */ + dj->sclust = 0; dj->dir = 0; /* It is the root dir */ + res = FR_OK; + if (!(ns & NS_LAST)) continue; + } else { /* Could not find the object */ + if (!(ns & NS_LAST)) res = FR_NO_PATH; + } + break; + } + if (ns & NS_LAST) break; /* Last segment match. Function completed. */ + dir = dj->dir; /* There is next segment. Follow the sub directory */ + if (!(dir[DIR_Attr] & AM_DIR)) { /* Cannot follow because it is a file */ + res = FR_NO_PATH; break; + } + dj->sclust = LD_CLUST(dir); + } + } + + return res; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Load boot record and check if it is an FAT boot record */ +/*-----------------------------------------------------------------------*/ + +static +BYTE check_fs ( /* 0:The FAT BR, 1:Valid BR but not an FAT, 2:Not a BR, 3:Disk error */ + FATFS *fs, /* File system object */ + DWORD sect /* Sector# (lba) to check if it is an FAT boot record or not */ +) +{ + if (disk_read(fs->drv, fs->win, sect, 1) != RES_OK) /* Load boot record */ + return 3; + if (LD_WORD(&fs->win[BS_55AA]) != 0xAA55) /* Check record signature (always placed at offset 510 even if the sector size is >512) */ + return 2; + + if ((LD_DWORD(&fs->win[BS_FilSysType]) & 0xFFFFFF) == 0x544146) /* Check "FAT" string */ + return 0; + if ((LD_DWORD(&fs->win[BS_FilSysType32]) & 0xFFFFFF) == 0x544146) + return 0; + + return 1; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Check if the file system object is valid or not */ +/*-----------------------------------------------------------------------*/ + +static +FRESULT chk_mounted ( /* FR_OK(0): successful, !=0: any error occurred */ + const TCHAR **path, /* Pointer to pointer to the path name (drive number) */ + FATFS **rfs, /* Pointer to pointer to the found file system object */ + BYTE chk_wp /* !=0: Check media write protection for write access */ +) +{ + BYTE fmt, b, *tbl; + UINT vol; + DSTATUS stat; + DWORD bsect, fasize, tsect, sysect, nclst, szbfat; + WORD nrsv; + const TCHAR *p = *path; + FATFS *fs; + + /* Get logical drive number from the path name */ + vol = p[0] - '0'; /* Is there a drive number? */ + if (vol <= 9 && p[1] == ':') { /* Found a drive number, get and strip it */ + p += 2; *path = p; /* Return pointer to the path name */ + } else { /* No drive number is given */ +#if _FS_RPATH + vol = CurrVol; /* Use current drive */ +#else + vol = 0; /* Use drive 0 */ +#endif + } + + /* Check if the logical drive is valid or not */ + if (vol >= _VOLUMES) /* Is the drive number valid? */ + return FR_INVALID_DRIVE; + *rfs = fs = FatFs[vol]; /* Return pointer to the corresponding file system object */ + if (!fs) return FR_NOT_ENABLED; /* Is the file system object available? */ + + ENTER_FF(fs); /* Lock file system */ + + if (fs->fs_type) { /* If the logical drive has been mounted */ + stat = disk_status(fs->drv); + if (!(stat & STA_NOINIT)) { /* and the physical drive is kept initialized (has not been changed), */ +#if !_FS_READONLY + if (chk_wp && (stat & STA_PROTECT)) /* Check write protection if needed */ + return FR_WRITE_PROTECTED; +#endif + return FR_OK; /* The file system object is valid */ + } + } + + /* The logical drive must be mounted. */ + /* Following code attempts to mount a volume. (analyze BPB and initialize the fs object) */ + + fs->fs_type = 0; /* Clear the file system object */ + fs->drv = (BYTE)LD2PD(vol); /* Bind the logical drive and a physical drive */ + stat = disk_initialize(fs->drv); /* Initialize low level disk I/O layer */ + if (stat & STA_NOINIT) /* Check if the initialization succeeded */ + return FR_NOT_READY; /* Failed to initialize due to no media or hard error */ +#if _MAX_SS != 512 /* Get disk sector size (variable sector size cfg only) */ + if (disk_ioctl(fs->drv, GET_SECTOR_SIZE, &fs->ssize) != RES_OK) + return FR_DISK_ERR; +#endif +#if !_FS_READONLY + if (chk_wp && (stat & STA_PROTECT)) /* Check disk write protection if needed */ + return FR_WRITE_PROTECTED; +#endif + /* Search FAT partition on the drive. Supports only generic partitionings, FDISK and SFD. */ + fmt = check_fs(fs, bsect = 0); /* Check sector 0 if it is a VBR */ + if (fmt == 1) { /* Not an FAT-VBR, the disk may be partitioned */ + /* Check the partition listed in top of the partition table */ + tbl = &fs->win[MBR_Table + LD2PT(vol) * 16]; /* Partition table */ + if (tbl[4]) { /* Is the partition existing? */ + bsect = LD_DWORD(&tbl[8]); /* Partition offset in LBA */ + fmt = check_fs(fs, bsect); /* Check the partition */ + } + } + if (fmt == 3) return FR_DISK_ERR; + if (fmt) return FR_NO_FILESYSTEM; /* No FAT volume is found */ + + /* Following code initializes the file system object */ + + if (LD_WORD(fs->win+BPB_BytsPerSec) != SS(fs)) /* (BPB_BytsPerSec must be equal to the physical sector size) */ + return FR_NO_FILESYSTEM; + + fasize = LD_WORD(fs->win+BPB_FATSz16); /* Number of sectors per FAT */ + if (!fasize) fasize = LD_DWORD(fs->win+BPB_FATSz32); + fs->fsize = fasize; + + fs->n_fats = b = fs->win[BPB_NumFATs]; /* Number of FAT copies */ + if (b != 1 && b != 2) return FR_NO_FILESYSTEM; /* (Must be 1 or 2) */ + fasize *= b; /* Number of sectors for FAT area */ + + fs->csize = b = fs->win[BPB_SecPerClus]; /* Number of sectors per cluster */ + if (!b || (b & (b - 1))) return FR_NO_FILESYSTEM; /* (Must be power of 2) */ + + fs->n_rootdir = LD_WORD(fs->win+BPB_RootEntCnt); /* Number of root directory entries */ + if (fs->n_rootdir % (SS(fs) / 32)) return FR_NO_FILESYSTEM; /* (BPB_RootEntCnt must be sector aligned) */ + + tsect = LD_WORD(fs->win+BPB_TotSec16); /* Number of sectors on the volume */ + if (!tsect) tsect = LD_DWORD(fs->win+BPB_TotSec32); + + nrsv = LD_WORD(fs->win+BPB_RsvdSecCnt); /* Number of reserved sectors */ + if (!nrsv) return FR_NO_FILESYSTEM; /* (BPB_RsvdSecCnt must not be 0) */ + + /* Determine the FAT sub type */ + sysect = nrsv + fasize + fs->n_rootdir / (SS(fs) / 32); /* RSV+FAT+DIR */ + if (tsect < sysect) return FR_NO_FILESYSTEM; /* (Invalid volume size) */ + nclst = (tsect - sysect) / fs->csize; /* Number of clusters */ + if (!nclst) return FR_NO_FILESYSTEM; /* (Invalid volume size) */ + fmt = FS_FAT12; + if (nclst >= MIN_FAT16) fmt = FS_FAT16; + if (nclst >= MIN_FAT32) fmt = FS_FAT32; + + /* Boundaries and Limits */ + fs->n_fatent = nclst + 2; /* Number of FAT entries */ + fs->database = bsect + sysect; /* Data start sector */ + fs->fatbase = bsect + nrsv; /* FAT start sector */ + if (fmt == FS_FAT32) { + if (fs->n_rootdir) return FR_NO_FILESYSTEM; /* (BPB_RootEntCnt must be 0) */ + fs->dirbase = LD_DWORD(fs->win+BPB_RootClus); /* Root directory start cluster */ + szbfat = fs->n_fatent * 4; /* (Required FAT size) */ + } else { + if (!fs->n_rootdir) return FR_NO_FILESYSTEM; /* (BPB_RootEntCnt must not be 0) */ + fs->dirbase = fs->fatbase + fasize; /* Root directory start sector */ + szbfat = (fmt == FS_FAT16) ? /* (Required FAT size) */ + fs->n_fatent * 2 : fs->n_fatent * 3 / 2 + (fs->n_fatent & 1); + } + if (fs->fsize < (szbfat + (SS(fs) - 1)) / SS(fs)) /* (FAT size must not be less than FAT sectors */ + return FR_NO_FILESYSTEM; + +#if !_FS_READONLY + /* Initialize cluster allocation information */ + fs->free_clust = 0xFFFFFFFF; + fs->last_clust = 0; + + /* Get fsinfo if available */ + if (fmt == FS_FAT32) { + fs->fsi_flag = 0; + fs->fsi_sector = bsect + LD_WORD(fs->win+BPB_FSInfo); + if (disk_read(fs->drv, fs->win, fs->fsi_sector, 1) == RES_OK && + LD_WORD(fs->win+BS_55AA) == 0xAA55 && + LD_DWORD(fs->win+FSI_LeadSig) == 0x41615252 && + LD_DWORD(fs->win+FSI_StrucSig) == 0x61417272) { + fs->last_clust = LD_DWORD(fs->win+FSI_Nxt_Free); + fs->free_clust = LD_DWORD(fs->win+FSI_Free_Count); + } + } +#endif + fs->fs_type = fmt; /* FAT sub-type */ + fs->id = ++Fsid; /* File system mount ID */ + fs->winsect = 0; /* Invalidate sector cache */ + fs->wflag = 0; +#if _FS_RPATH + fs->cdir = 0; /* Current directory (root dir) */ +#endif +#if _FS_SHARE /* Clear file lock semaphores */ + clear_lock(fs); +#endif + + return FR_OK; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Check if the file/dir object is valid or not */ +/*-----------------------------------------------------------------------*/ + +static +FRESULT validate ( /* FR_OK(0): The object is valid, !=0: Invalid */ + FATFS *fs, /* Pointer to the file system object */ + WORD id /* Member id of the target object to be checked */ +) +{ + if (!fs || !fs->fs_type || fs->id != id) + return FR_INVALID_OBJECT; + + ENTER_FF(fs); /* Lock file system */ + + if (disk_status(fs->drv) & STA_NOINIT) + return FR_NOT_READY; + + return FR_OK; +} + + + + +/*-------------------------------------------------------------------------- + + Public Functions + +--------------------------------------------------------------------------*/ + + + +/*-----------------------------------------------------------------------*/ +/* Mount/Unmount a Logical Drive */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_mount ( + BYTE vol, /* Logical drive number to be mounted/unmounted */ + FATFS *fs /* Pointer to new file system object (NULL for unmount)*/ +) +{ + FATFS *rfs; + + + if (vol >= _VOLUMES) /* Check if the drive number is valid */ + return FR_INVALID_DRIVE; + rfs = FatFs[vol]; /* Get current fs object */ + + if (rfs) { +#if _FS_SHARE + clear_lock(rfs); +#endif +#if _FS_REENTRANT /* Discard sync object of the current volume */ + if (!ff_del_syncobj(rfs->sobj)) return FR_INT_ERR; +#endif + rfs->fs_type = 0; /* Clear old fs object */ + } + + if (fs) { + fs->fs_type = 0; /* Clear new fs object */ +#if _FS_REENTRANT /* Create sync object for the new volume */ + if (!ff_cre_syncobj(vol, &fs->sobj)) return FR_INT_ERR; +#endif + } + FatFs[vol] = fs; /* Register new fs object */ + + return FR_OK; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Open or Create a File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_open ( + FIL *fp, /* Pointer to the blank file object */ + const TCHAR *path, /* Pointer to the file name */ + BYTE mode /* Access mode and file open mode flags */ +) +{ + FRESULT res; + DIR dj; + BYTE *dir; + DEF_NAMEBUF; + + + fp->fs = 0; /* Clear file object */ + +#if !_FS_READONLY + mode &= FA_READ | FA_WRITE | FA_CREATE_ALWAYS | FA_OPEN_ALWAYS | FA_CREATE_NEW; + res = chk_mounted(&path, &dj.fs, (BYTE)(mode & ~FA_READ)); +#else + mode &= FA_READ; + res = chk_mounted(&path, &dj.fs, 0); +#endif + INIT_BUF(dj); + if (res == FR_OK) + res = follow_path(&dj, path); /* Follow the file path */ + dir = dj.dir; + +#if !_FS_READONLY /* R/W configuration */ + if (res == FR_OK) { + if (!dir) /* Current dir itself */ + res = FR_INVALID_NAME; +#if _FS_SHARE + else + res = chk_lock(&dj, (mode & ~FA_READ) ? 1 : 0); +#endif + } + /* Create or Open a file */ + if (mode & (FA_CREATE_ALWAYS | FA_OPEN_ALWAYS | FA_CREATE_NEW)) { + DWORD dw, cl; + + if (res != FR_OK) { /* No file, create new */ + if (res == FR_NO_FILE) /* There is no file to open, create a new entry */ +#if _FS_SHARE + res = enq_lock(dj.fs) ? dir_register(&dj) : FR_TOO_MANY_OPEN_FILES; +#else + res = dir_register(&dj); +#endif + mode |= FA_CREATE_ALWAYS; /* File is created */ + dir = dj.dir; /* New entry */ + } + else { /* Any object is already existing */ + if (mode & FA_CREATE_NEW) { /* Cannot create new */ + res = FR_EXIST; + } else { + if (dir[DIR_Attr] & (AM_RDO | AM_DIR)) /* Cannot overwrite it (R/O or DIR) */ + res = FR_DENIED; + } + } + if (res == FR_OK && (mode & FA_CREATE_ALWAYS)) { /* Truncate it if overwrite mode */ + dw = get_fattime(); /* Created time */ + ST_DWORD(dir+DIR_CrtTime, dw); + dir[DIR_Attr] = 0; /* Reset attribute */ + ST_DWORD(dir+DIR_FileSize, 0); /* size = 0 */ + cl = LD_CLUST(dir); /* Get start cluster */ + ST_CLUST(dir, 0); /* cluster = 0 */ + dj.fs->wflag = 1; + if (cl) { /* Remove the cluster chain if exist */ + dw = dj.fs->winsect; + res = remove_chain(dj.fs, cl); + if (res == FR_OK) { + dj.fs->last_clust = cl - 1; /* Reuse the cluster hole */ + res = move_window(dj.fs, dw); + } + } + } + } + else { /* Open an existing file */ + if (res == FR_OK) { /* Follow succeeded */ + if (dir[DIR_Attr] & AM_DIR) { /* It is a directory */ + res = FR_NO_FILE; + } else { + if ((mode & FA_WRITE) && (dir[DIR_Attr] & AM_RDO)) /* R/O violation */ + res = FR_DENIED; + } + } + } + if (res == FR_OK) { + if (mode & FA_CREATE_ALWAYS) /* Set file change flag if created or overwritten */ + mode |= FA__WRITTEN; + fp->dir_sect = dj.fs->winsect; /* Pointer to the directory entry */ + fp->dir_ptr = dir; +#if _FS_SHARE + fp->lockid = inc_lock(&dj, (mode & ~FA_READ) ? 1 : 0); + if (!fp->lockid) res = FR_INT_ERR; +#endif + } + +#else /* R/O configuration */ + if (res == FR_OK) { /* Follow succeeded */ + if (!dir) { /* Current dir itself */ + res = FR_INVALID_NAME; + } else { + if (dir[DIR_Attr] & AM_DIR) /* It is a directory */ + res = FR_NO_FILE; + } + } +#endif + FREE_BUF(); + + if (res == FR_OK) { + fp->flag = mode; /* File access mode */ + fp->org_clust = LD_CLUST(dir); /* File start cluster */ + fp->fsize = LD_DWORD(dir+DIR_FileSize); /* File size */ + fp->fptr = 0; /* File pointer */ + fp->dsect = 0; +#if _USE_FASTSEEK + fp->cltbl = 0; /* No cluster link map table */ +#endif + fp->fs = dj.fs; fp->id = dj.fs->id; /* Validate file object */ + } + + LEAVE_FF(dj.fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Read File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_read ( + FIL *fp, /* Pointer to the file object */ + BYTE *buff, /* Pointer to data buffer */ + UINT btr, /* Number of bytes to read */ + UINT *br /* Pointer to number of bytes read */ +) +{ + FRESULT res; + DWORD clst, sect, remain; + UINT rcnt, cc; + BYTE csect, *rbuff = buff; + + + *br = 0; /* Initialize byte counter */ + + res = validate(fp->fs, fp->id); /* Check validity of the object */ + if (res != FR_OK) LEAVE_FF(fp->fs, res); + if (fp->flag & FA__ERROR) /* Check abort flag */ + LEAVE_FF(fp->fs, FR_INT_ERR); + if (!(fp->flag & FA_READ)) /* Check access mode */ + LEAVE_FF(fp->fs, FR_DENIED); + remain = fp->fsize - fp->fptr; + if (btr > remain) btr = (UINT)remain; /* Truncate btr by remaining bytes */ + + for ( ; btr; /* Repeat until all data transferred */ + rbuff += rcnt, fp->fptr += rcnt, *br += rcnt, btr -= rcnt) { + if ((fp->fptr % SS(fp->fs)) == 0) { /* On the sector boundary? */ + csect = (BYTE)(fp->fptr / SS(fp->fs) & (fp->fs->csize - 1)); /* Sector offset in the cluster */ + if (!csect) { /* On the cluster boundary? */ + clst = (fp->fptr == 0) ? /* On the top of the file? */ + fp->org_clust : get_fat(fp->fs, fp->curr_clust); + if (clst <= 1) ABORT(fp->fs, FR_INT_ERR); + if (clst == 0xFFFFFFFF) ABORT(fp->fs, FR_DISK_ERR); + fp->curr_clust = clst; /* Update current cluster */ + } + sect = clust2sect(fp->fs, fp->curr_clust); /* Get current sector */ + if (!sect) ABORT(fp->fs, FR_INT_ERR); + sect += csect; + cc = btr / SS(fp->fs); /* When remaining bytes >= sector size, */ + if (cc) { /* Read maximum contiguous sectors directly */ + if (csect + cc > fp->fs->csize) /* Clip at cluster boundary */ + cc = fp->fs->csize - csect; + if (disk_read(fp->fs->drv, rbuff, sect, (BYTE)cc) != RES_OK) + ABORT(fp->fs, FR_DISK_ERR); +#if !_FS_READONLY && _FS_MINIMIZE <= 2 /* Replace one of the read sectors with cached data if it contains a dirty sector */ +#if _FS_TINY + if (fp->fs->wflag && fp->fs->winsect - sect < cc) + mem_cpy(rbuff + ((fp->fs->winsect - sect) * SS(fp->fs)), fp->fs->win, SS(fp->fs)); +#else + if ((fp->flag & FA__DIRTY) && fp->dsect - sect < cc) + mem_cpy(rbuff + ((fp->dsect - sect) * SS(fp->fs)), fp->buf, SS(fp->fs)); +#endif +#endif + rcnt = SS(fp->fs) * cc; /* Number of bytes transferred */ + continue; + } +#if !_FS_TINY +#if !_FS_READONLY + if (fp->flag & FA__DIRTY) { /* Write sector I/O buffer if needed */ + if (disk_write(fp->fs->drv, fp->buf, fp->dsect, 1) != RES_OK) + ABORT(fp->fs, FR_DISK_ERR); + fp->flag &= ~FA__DIRTY; + } +#endif + if (fp->dsect != sect) { /* Fill sector buffer with file data */ + if (disk_read(fp->fs->drv, fp->buf, sect, 1) != RES_OK) + ABORT(fp->fs, FR_DISK_ERR); + } +#endif + fp->dsect = sect; + } + rcnt = SS(fp->fs) - (fp->fptr % SS(fp->fs)); /* Get partial sector data from sector buffer */ + if (rcnt > btr) rcnt = btr; +#if _FS_TINY + if (move_window(fp->fs, fp->dsect)) /* Move sector window */ + ABORT(fp->fs, FR_DISK_ERR); + mem_cpy(rbuff, &fp->fs->win[fp->fptr % SS(fp->fs)], rcnt); /* Pick partial sector */ +#else + mem_cpy(rbuff, &fp->buf[fp->fptr % SS(fp->fs)], rcnt); /* Pick partial sector */ +#endif + } + + LEAVE_FF(fp->fs, FR_OK); +} + + + + +#if !_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Write File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_write ( + FIL *fp, /* Pointer to the file object */ + const BYTE *buff, /* Pointer to the data to be written */ + UINT btw, /* Number of bytes to write */ + UINT *bw /* Pointer to number of bytes written */ +) +{ + FRESULT res; + DWORD clst, sect; + UINT wcnt, cc; + const BYTE *wbuff = buff; + BYTE csect; + + + *bw = 0; /* Initialize byte counter */ + + res = validate(fp->fs, fp->id); /* Check validity of the object */ + if (res != FR_OK) LEAVE_FF(fp->fs, res); + if (fp->flag & FA__ERROR) /* Check abort flag */ + LEAVE_FF(fp->fs, FR_INT_ERR); + if (!(fp->flag & FA_WRITE)) /* Check access mode */ + LEAVE_FF(fp->fs, FR_DENIED); + if (fp->fsize + btw < fp->fsize) btw = 0; /* File size cannot reach 4GB */ + + for ( ; btw; /* Repeat until all data transferred */ + wbuff += wcnt, fp->fptr += wcnt, *bw += wcnt, btw -= wcnt) { + if ((fp->fptr % SS(fp->fs)) == 0) { /* On the sector boundary? */ + csect = (BYTE)(fp->fptr / SS(fp->fs) & (fp->fs->csize - 1)); /* Sector offset in the cluster */ + if (!csect) { /* On the cluster boundary? */ + if (fp->fptr == 0) { /* On the top of the file? */ + clst = fp->org_clust; /* Follow from the origin */ + if (clst == 0) /* When there is no cluster chain, */ + fp->org_clust = clst = create_chain(fp->fs, 0); /* Create a new cluster chain */ + } else { /* Middle or end of the file */ + clst = create_chain(fp->fs, fp->curr_clust); /* Follow or stretch cluster chain */ + } + if (clst == 0) break; /* Could not allocate a new cluster (disk full) */ + if (clst == 1) ABORT(fp->fs, FR_INT_ERR); + if (clst == 0xFFFFFFFF) ABORT(fp->fs, FR_DISK_ERR); + fp->curr_clust = clst; /* Update current cluster */ + } +#if _FS_TINY + if (fp->fs->winsect == fp->dsect && move_window(fp->fs, 0)) /* Write back data buffer prior to following direct transfer */ + ABORT(fp->fs, FR_DISK_ERR); +#else + if (fp->flag & FA__DIRTY) { /* Write back data buffer prior to following direct transfer */ + if (disk_write(fp->fs->drv, fp->buf, fp->dsect, 1) != RES_OK) + ABORT(fp->fs, FR_DISK_ERR); + fp->flag &= ~FA__DIRTY; + } +#endif + sect = clust2sect(fp->fs, fp->curr_clust); /* Get current sector */ + if (!sect) ABORT(fp->fs, FR_INT_ERR); + sect += csect; + cc = btw / SS(fp->fs); /* When remaining bytes >= sector size, */ + if (cc) { /* Write maximum contiguous sectors directly */ + if (csect + cc > fp->fs->csize) /* Clip at cluster boundary */ + cc = fp->fs->csize - csect; + if (disk_write(fp->fs->drv, wbuff, sect, (BYTE)cc) != RES_OK) + ABORT(fp->fs, FR_DISK_ERR); +#if _FS_TINY + if (fp->fs->winsect - sect < cc) { /* Refill sector cache if it gets dirty by the direct write */ + mem_cpy(fp->fs->win, wbuff + ((fp->fs->winsect - sect) * SS(fp->fs)), SS(fp->fs)); + fp->fs->wflag = 0; + } +#else + if (fp->dsect - sect < cc) { /* Refill sector cache if it gets dirty by the direct write */ + mem_cpy(fp->buf, wbuff + ((fp->dsect - sect) * SS(fp->fs)), SS(fp->fs)); + fp->flag &= ~FA__DIRTY; + } +#endif + wcnt = SS(fp->fs) * cc; /* Number of bytes transferred */ + continue; + } +#if _FS_TINY + if (fp->fptr >= fp->fsize) { /* Avoid silly buffer filling at growing edge */ + if (move_window(fp->fs, 0)) ABORT(fp->fs, FR_DISK_ERR); + fp->fs->winsect = sect; + } +#else + if (fp->dsect != sect) { /* Fill sector buffer with file data */ + if (fp->fptr < fp->fsize && + disk_read(fp->fs->drv, fp->buf, sect, 1) != RES_OK) + ABORT(fp->fs, FR_DISK_ERR); + } +#endif + fp->dsect = sect; + } + wcnt = SS(fp->fs) - (fp->fptr % SS(fp->fs));/* Put partial sector into file I/O buffer */ + if (wcnt > btw) wcnt = btw; +#if _FS_TINY + if (move_window(fp->fs, fp->dsect)) /* Move sector window */ + ABORT(fp->fs, FR_DISK_ERR); + mem_cpy(&fp->fs->win[fp->fptr % SS(fp->fs)], wbuff, wcnt); /* Fit partial sector */ + fp->fs->wflag = 1; +#else + mem_cpy(&fp->buf[fp->fptr % SS(fp->fs)], wbuff, wcnt); /* Fit partial sector */ + fp->flag |= FA__DIRTY; +#endif + } + + if (fp->fptr > fp->fsize) fp->fsize = fp->fptr; /* Update file size if needed */ + fp->flag |= FA__WRITTEN; /* Set file change flag */ + + LEAVE_FF(fp->fs, FR_OK); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Synchronize the File Object */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_sync ( + FIL *fp /* Pointer to the file object */ +) +{ + FRESULT res; + DWORD tim; + BYTE *dir; + + + res = validate(fp->fs, fp->id); /* Check validity of the object */ + if (res == FR_OK) { + if (fp->flag & FA__WRITTEN) { /* Has the file been written? */ +#if !_FS_TINY /* Write-back dirty buffer */ + if (fp->flag & FA__DIRTY) { + if (disk_write(fp->fs->drv, fp->buf, fp->dsect, 1) != RES_OK) + LEAVE_FF(fp->fs, FR_DISK_ERR); + fp->flag &= ~FA__DIRTY; + } +#endif + /* Update the directory entry */ + res = move_window(fp->fs, fp->dir_sect); + if (res == FR_OK) { + dir = fp->dir_ptr; + dir[DIR_Attr] |= AM_ARC; /* Set archive bit */ + ST_DWORD(dir+DIR_FileSize, fp->fsize); /* Update file size */ + ST_CLUST(dir, fp->org_clust); /* Update start cluster */ + tim = get_fattime(); /* Update updated time */ + ST_DWORD(dir+DIR_WrtTime, tim); + fp->flag &= ~FA__WRITTEN; + fp->fs->wflag = 1; + res = sync(fp->fs); + } + } + } + + LEAVE_FF(fp->fs, res); +} + +#endif /* !_FS_READONLY */ + + + + +/*-----------------------------------------------------------------------*/ +/* Close File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_close ( + FIL *fp /* Pointer to the file object to be closed */ +) +{ + FRESULT res; + +#if _FS_READONLY + FATFS *fs = fp->fs; + res = validate(fs, fp->id); + if (res == FR_OK) fp->fs = 0; /* Discard file object */ + LEAVE_FF(fs, res); + +#else + res = f_sync(fp); /* Flush cached data */ +#if _FS_SHARE + if (res == FR_OK) { /* Decrement open counter */ +#if _FS_REENTRANT + res = validate(fp->fs, fp->id); + if (res == FR_OK) { + res = dec_lock(fp->lockid); + unlock_fs(fp->fs, FR_OK); + } +#else + res = dec_lock(fp->lockid); +#endif + } +#endif + if (res == FR_OK) fp->fs = 0; /* Discard file object */ + return res; +#endif +} + + + + +/*-----------------------------------------------------------------------*/ +/* Current Drive/Directory Handlings */ +/*-----------------------------------------------------------------------*/ + +#if _FS_RPATH >= 1 + +FRESULT f_chdrive ( + BYTE drv /* Drive number */ +) +{ + if (drv >= _VOLUMES) return FR_INVALID_DRIVE; + + CurrVol = drv; + + return FR_OK; +} + + + +FRESULT f_chdir ( + const TCHAR *path /* Pointer to the directory path */ +) +{ + FRESULT res; + DIR dj; + DEF_NAMEBUF; + + + res = chk_mounted(&path, &dj.fs, 0); + if (res == FR_OK) { + INIT_BUF(dj); + res = follow_path(&dj, path); /* Follow the path */ + FREE_BUF(); + if (res == FR_OK) { /* Follow completed */ + if (!dj.dir) { + dj.fs->cdir = dj.sclust; /* Start directory itself */ + } else { + if (dj.dir[DIR_Attr] & AM_DIR) /* Reached to the directory */ + dj.fs->cdir = LD_CLUST(dj.dir); + else + res = FR_NO_PATH; /* Reached but a file */ + } + } + if (res == FR_NO_FILE) res = FR_NO_PATH; + } + + LEAVE_FF(dj.fs, res); +} + + +#if _FS_RPATH >= 2 +FRESULT f_getcwd ( + TCHAR *path, /* Pointer to the directory path */ + UINT sz_path /* Size of path */ +) +{ + FRESULT res; + DIR dj; + UINT i, n; + DWORD ccl; + TCHAR *tp; + FILINFO fno; + DEF_NAMEBUF; + + + *path = 0; + res = chk_mounted((const TCHAR**)&path, &dj.fs, 0); /* Get current volume */ + if (res == FR_OK) { + INIT_BUF(dj); + i = sz_path; /* Bottom of buffer (dir stack base) */ + dj.sclust = dj.fs->cdir; /* Start to follow upper dir from current dir */ + while ((ccl = dj.sclust) != 0) { /* Repeat while current dir is a sub-dir */ + res = dir_sdi(&dj, 1); /* Get parent dir */ + if (res != FR_OK) break; + res = dir_read(&dj); + if (res != FR_OK) break; + dj.sclust = LD_CLUST(dj.dir); /* Goto parent dir */ + res = dir_sdi(&dj, 0); + if (res != FR_OK) break; + do { /* Find the entry links to the child dir */ + res = dir_read(&dj); + if (res != FR_OK) break; + if (ccl == LD_CLUST(dj.dir)) break; /* Found the entry */ + res = dir_next(&dj, 0); + } while (res == FR_OK); + if (res == FR_NO_FILE) res = FR_INT_ERR;/* It cannot be 'not found'. */ + if (res != FR_OK) break; +#if _USE_LFN + fno.lfname = path; + fno.lfsize = i; +#endif + get_fileinfo(&dj, &fno); /* Get the dir name and push it to the buffer */ + tp = fno.fname; + if (_USE_LFN && *path) tp = path; + for (n = 0; tp[n]; n++) ; + if (i < n + 3) { + res = FR_NOT_ENOUGH_CORE; break; + } + while (n) path[--i] = tp[--n]; + path[--i] = '/'; + } + tp = path; + if (res == FR_OK) { + *tp++ = '0' + CurrVol; /* Put drive number */ + *tp++ = ':'; + if (i == sz_path) { /* Root-dir */ + *tp++ = '/'; + } else { /* Sub-dir */ + do /* Add stacked path str */ + *tp++ = path[i++]; + while (i < sz_path); + } + } + *tp = 0; + FREE_BUF(); + } + + LEAVE_FF(dj.fs, res); +} +#endif /* _FS_RPATH >= 2 */ +#endif /* _FS_RPATH >= 1 */ + + + +#if _FS_MINIMIZE <= 2 +/*-----------------------------------------------------------------------*/ +/* Seek File R/W Pointer */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_lseek ( + FIL *fp, /* Pointer to the file object */ + DWORD ofs /* File pointer from top of file */ +) +{ + FRESULT res; + + + res = validate(fp->fs, fp->id); /* Check validity of the object */ + if (res != FR_OK) LEAVE_FF(fp->fs, res); + if (fp->flag & FA__ERROR) /* Check abort flag */ + LEAVE_FF(fp->fs, FR_INT_ERR); + +#if _USE_FASTSEEK + if (fp->cltbl) { /* Fast seek */ + DWORD cl, pcl, ncl, tcl, dsc, tlen, *tbl = fp->cltbl; + BYTE csc; + + tlen = *tbl++; + if (ofs == CREATE_LINKMAP) { /* Create link map table */ + cl = fp->org_clust; + if (cl) { + do { + if (tlen < 4) { /* Not enough table items */ + res = FR_NOT_ENOUGH_CORE; break; + } + tcl = cl; ncl = 0; + do { /* Get a fragment and store the top and length */ + pcl = cl; ncl++; + cl = get_fat(fp->fs, cl); + if (cl <= 1) ABORT(fp->fs, FR_INT_ERR); + if (cl == 0xFFFFFFFF) ABORT(fp->fs, FR_DISK_ERR); + } while (cl == pcl + 1); + *tbl++ = ncl; *tbl++ = tcl; + tlen -= 2; + } while (cl < fp->fs->n_fatent); + } + *tbl = 0; /* Terminate table */ + + } else { /* Fast seek */ + if (ofs > fp->fsize) /* Clip offset at the file size */ + ofs = fp->fsize; + fp->fptr = ofs; /* Set file pointer */ + if (ofs) { + dsc = (ofs - 1) / SS(fp->fs); + cl = dsc / fp->fs->csize; + for (;;) { + ncl = *tbl++; + if (!ncl) ABORT(fp->fs, FR_INT_ERR); + if (cl < ncl) break; + cl -= ncl; tbl++; + } + fp->curr_clust = cl + *tbl; + csc = (BYTE)(dsc & (fp->fs->csize - 1)); + dsc = clust2sect(fp->fs, fp->curr_clust); + if (!dsc) ABORT(fp->fs, FR_INT_ERR); + dsc += csc; + if (fp->fptr % SS(fp->fs) && dsc != fp->dsect) { +#if !_FS_TINY +#if !_FS_READONLY + if (fp->flag & FA__DIRTY) { /* Flush dirty buffer if needed */ + if (disk_write(fp->fs->drv, fp->buf, fp->dsect, 1) != RES_OK) + ABORT(fp->fs, FR_DISK_ERR); + fp->flag &= ~FA__DIRTY; + } +#endif + if (disk_read(fp->fs->drv, fp->buf, dsc, 1) != RES_OK) + ABORT(fp->fs, FR_DISK_ERR); +#endif + fp->dsect = dsc; + } + } + } + } else +#endif + + /* Normal Seek */ + { + DWORD clst, bcs, nsect, ifptr; + + if (ofs > fp->fsize /* In read-only mode, clip offset with the file size */ +#if !_FS_READONLY + && !(fp->flag & FA_WRITE) +#endif + ) ofs = fp->fsize; + + ifptr = fp->fptr; + fp->fptr = nsect = 0; + if (ofs) { + bcs = (DWORD)fp->fs->csize * SS(fp->fs); /* Cluster size (byte) */ + if (ifptr > 0 && + (ofs - 1) / bcs >= (ifptr - 1) / bcs) { /* When seek to same or following cluster, */ + fp->fptr = (ifptr - 1) & ~(bcs - 1); /* start from the current cluster */ + ofs -= fp->fptr; + clst = fp->curr_clust; + } else { /* When seek to back cluster, */ + clst = fp->org_clust; /* start from the first cluster */ +#if !_FS_READONLY + if (clst == 0) { /* If no cluster chain, create a new chain */ + clst = create_chain(fp->fs, 0); + if (clst == 1) ABORT(fp->fs, FR_INT_ERR); + if (clst == 0xFFFFFFFF) ABORT(fp->fs, FR_DISK_ERR); + fp->org_clust = clst; + } +#endif + fp->curr_clust = clst; + } + if (clst != 0) { + while (ofs > bcs) { /* Cluster following loop */ +#if !_FS_READONLY + if (fp->flag & FA_WRITE) { /* Check if in write mode or not */ + clst = create_chain(fp->fs, clst); /* Force stretch if in write mode */ + if (clst == 0) { /* When disk gets full, clip file size */ + ofs = bcs; break; + } + } else +#endif + clst = get_fat(fp->fs, clst); /* Follow cluster chain if not in write mode */ + if (clst == 0xFFFFFFFF) ABORT(fp->fs, FR_DISK_ERR); + if (clst <= 1 || clst >= fp->fs->n_fatent) ABORT(fp->fs, FR_INT_ERR); + fp->curr_clust = clst; + fp->fptr += bcs; + ofs -= bcs; + } + fp->fptr += ofs; + if (ofs % SS(fp->fs)) { + nsect = clust2sect(fp->fs, clst); /* Current sector */ + if (!nsect) ABORT(fp->fs, FR_INT_ERR); + nsect += ofs / SS(fp->fs); + } + } + } + if (fp->fptr % SS(fp->fs) && nsect != fp->dsect) { +#if !_FS_TINY +#if !_FS_READONLY + if (fp->flag & FA__DIRTY) { /* Flush dirty buffer if needed */ + if (disk_write(fp->fs->drv, fp->buf, fp->dsect, 1) != RES_OK) + ABORT(fp->fs, FR_DISK_ERR); + fp->flag &= ~FA__DIRTY; + } +#endif + if (disk_read(fp->fs->drv, fp->buf, nsect, 1) != RES_OK) + ABORT(fp->fs, FR_DISK_ERR); +#endif + fp->dsect = nsect; + } +#if !_FS_READONLY + if (fp->fptr > fp->fsize) { /* Set change flag if the file size is extended */ + fp->fsize = fp->fptr; + fp->flag |= FA__WRITTEN; + } +#endif + } + + LEAVE_FF(fp->fs, res); +} + + + +#if _FS_MINIMIZE <= 1 +/*-----------------------------------------------------------------------*/ +/* Create a Directroy Object */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_opendir ( + DIR *dj, /* Pointer to directory object to create */ + const TCHAR *path /* Pointer to the directory path */ +) +{ + FRESULT res; + DEF_NAMEBUF; + + + res = chk_mounted(&path, &dj->fs, 0); + if (res == FR_OK) { + INIT_BUF(*dj); + res = follow_path(dj, path); /* Follow the path to the directory */ + FREE_BUF(); + if (res == FR_OK) { /* Follow completed */ + if (dj->dir) { /* It is not the root dir */ + if (dj->dir[DIR_Attr] & AM_DIR) { /* The object is a directory */ + dj->sclust = LD_CLUST(dj->dir); + } else { /* The object is not a directory */ + res = FR_NO_PATH; + } + } + if (res == FR_OK) { + dj->id = dj->fs->id; + res = dir_sdi(dj, 0); /* Rewind dir */ + } + } + if (res == FR_NO_FILE) res = FR_NO_PATH; + } + + LEAVE_FF(dj->fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Read Directory Entry in Sequense */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_readdir ( + DIR *dj, /* Pointer to the open directory object */ + FILINFO *fno /* Pointer to file information to return */ +) +{ + FRESULT res; + DEF_NAMEBUF; + + + res = validate(dj->fs, dj->id); /* Check validity of the object */ + if (res == FR_OK) { + if (!fno) { + res = dir_sdi(dj, 0); /* Rewind the directory object */ + } else { + INIT_BUF(*dj); + res = dir_read(dj); /* Read an directory item */ + if (res == FR_NO_FILE) { /* Reached end of dir */ + dj->sect = 0; + res = FR_OK; + } + if (res == FR_OK) { /* A valid entry is found */ + get_fileinfo(dj, fno); /* Get the object information */ + res = dir_next(dj, 0); /* Increment index for next */ + if (res == FR_NO_FILE) { + dj->sect = 0; + res = FR_OK; + } + } + FREE_BUF(); + } + } + + LEAVE_FF(dj->fs, res); +} + + + +#if _FS_MINIMIZE == 0 +/*-----------------------------------------------------------------------*/ +/* Get File Status */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_stat ( + const TCHAR *path, /* Pointer to the file path */ + FILINFO *fno /* Pointer to file information to return */ +) +{ + FRESULT res; + DIR dj; + DEF_NAMEBUF; + + + res = chk_mounted(&path, &dj.fs, 0); + if (res == FR_OK) { + INIT_BUF(dj); + res = follow_path(&dj, path); /* Follow the file path */ + if (res == FR_OK) { /* Follow completed */ + if (dj.dir) /* Found an object */ + get_fileinfo(&dj, fno); + else /* It is root dir */ + res = FR_INVALID_NAME; + } + FREE_BUF(); + } + + LEAVE_FF(dj.fs, res); +} + + + +#if !_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Get Number of Free Clusters */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_getfree ( + const TCHAR *path, /* Pointer to the logical drive number (root dir) */ + DWORD *nclst, /* Pointer to the variable to return number of free clusters */ + FATFS **fatfs /* Pointer to pointer to corresponding file system object to return */ +) +{ + FRESULT res; + DWORD n, clst, sect, stat; + UINT i; + BYTE fat, *p; + + + /* Get drive number */ + res = chk_mounted(&path, fatfs, 0); + if (res == FR_OK) { + /* If free_clust is valid, return it without full cluster scan */ + if ((*fatfs)->free_clust <= (*fatfs)->n_fatent - 2) { + *nclst = (*fatfs)->free_clust; + } else { + /* Get number of free clusters */ + fat = (*fatfs)->fs_type; + n = 0; + if (fat == FS_FAT12) { + clst = 2; + do { + stat = get_fat(*fatfs, clst); + if (stat == 0xFFFFFFFF) { res = FR_DISK_ERR; break; } + if (stat == 1) { res = FR_INT_ERR; break; } + if (stat == 0) n++; + } while (++clst < (*fatfs)->n_fatent); + } else { + clst = (*fatfs)->n_fatent; + sect = (*fatfs)->fatbase; + i = 0; p = 0; + do { + if (!i) { + res = move_window(*fatfs, sect++); + if (res != FR_OK) break; + p = (*fatfs)->win; + i = SS(*fatfs); + } + if (fat == FS_FAT16) { + if (LD_WORD(p) == 0) n++; + p += 2; i -= 2; + } else { + if ((LD_DWORD(p) & 0x0FFFFFFF) == 0) n++; + p += 4; i -= 4; + } + } while (--clst); + } + (*fatfs)->free_clust = n; + if (fat == FS_FAT32) (*fatfs)->fsi_flag = 1; + *nclst = n; + } + } + LEAVE_FF(*fatfs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Truncate File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_truncate ( + FIL *fp /* Pointer to the file object */ +) +{ + FRESULT res; + DWORD ncl; + + + res = validate(fp->fs, fp->id); /* Check validity of the object */ + if (res == FR_OK) { + if (fp->flag & FA__ERROR) { /* Check abort flag */ + res = FR_INT_ERR; + } else { + if (!(fp->flag & FA_WRITE)) /* Check access mode */ + res = FR_DENIED; + } + } + if (res == FR_OK) { + if (fp->fsize > fp->fptr) { + fp->fsize = fp->fptr; /* Set file size to current R/W point */ + fp->flag |= FA__WRITTEN; + if (fp->fptr == 0) { /* When set file size to zero, remove entire cluster chain */ + res = remove_chain(fp->fs, fp->org_clust); + fp->org_clust = 0; + } else { /* When truncate a part of the file, remove remaining clusters */ + ncl = get_fat(fp->fs, fp->curr_clust); + res = FR_OK; + if (ncl == 0xFFFFFFFF) res = FR_DISK_ERR; + if (ncl == 1) res = FR_INT_ERR; + if (res == FR_OK && ncl < fp->fs->n_fatent) { + res = put_fat(fp->fs, fp->curr_clust, 0x0FFFFFFF); + if (res == FR_OK) res = remove_chain(fp->fs, ncl); + } + } + } + if (res != FR_OK) fp->flag |= FA__ERROR; + } + + LEAVE_FF(fp->fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Delete a File or Directory */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_unlink ( + const TCHAR *path /* Pointer to the file or directory path */ +) +{ + FRESULT res; + DIR dj, sdj; + BYTE *dir; + DWORD dclst; + DEF_NAMEBUF; + + + res = chk_mounted(&path, &dj.fs, 1); + if (res == FR_OK) { + INIT_BUF(dj); + res = follow_path(&dj, path); /* Follow the file path */ + if (_FS_RPATH && res == FR_OK && (dj.fn[NS] & NS_DOT)) + res = FR_INVALID_NAME; /* Cannot remove dot entry */ +#if _FS_SHARE + if (res == FR_OK) res = chk_lock(&dj, 2); /* Cannot remove open file */ +#endif + if (res == FR_OK) { /* The object is accessible */ + dir = dj.dir; + if (!dir) { + res = FR_INVALID_NAME; /* Cannot remove the start directory */ + } else { + if (dir[DIR_Attr] & AM_RDO) + res = FR_DENIED; /* Cannot remove R/O object */ + } + dclst = LD_CLUST(dir); + if (res == FR_OK && (dir[DIR_Attr] & AM_DIR)) { /* Is it a sub-dir? */ + if (dclst < 2) { + res = FR_INT_ERR; + } else { + mem_cpy(&sdj, &dj, sizeof(DIR)); /* Check if the sub-dir is empty or not */ + sdj.sclust = dclst; + res = dir_sdi(&sdj, 2); /* Exclude dot entries */ + if (res == FR_OK) { + res = dir_read(&sdj); + if (res == FR_OK /* Not empty dir */ +#if _FS_RPATH + || dclst == sdj.fs->cdir /* Current dir */ +#endif + ) res = FR_DENIED; + if (res == FR_NO_FILE) res = FR_OK; /* Empty */ + } + } + } + if (res == FR_OK) { + res = dir_remove(&dj); /* Remove the directory entry */ + if (res == FR_OK) { + if (dclst) /* Remove the cluster chain if exist */ + res = remove_chain(dj.fs, dclst); + if (res == FR_OK) res = sync(dj.fs); + } + } + } + FREE_BUF(); + } + LEAVE_FF(dj.fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Create a Directory */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_mkdir ( + const TCHAR *path /* Pointer to the directory path */ +) +{ + FRESULT res; + DIR dj; + BYTE *dir, n; + DWORD dsc, dcl, pcl, tim = get_fattime(); + DEF_NAMEBUF; + + + res = chk_mounted(&path, &dj.fs, 1); + if (res == FR_OK) { + INIT_BUF(dj); + res = follow_path(&dj, path); /* Follow the file path */ + if (res == FR_OK) res = FR_EXIST; /* Any object with same name is already existing */ + if (_FS_RPATH && res == FR_NO_FILE && (dj.fn[NS] & NS_DOT)) + res = FR_INVALID_NAME; + if (res == FR_NO_FILE) { /* Can create a new directory */ + dcl = create_chain(dj.fs, 0); /* Allocate a cluster for the new directory table */ + res = FR_OK; + if (dcl == 0) res = FR_DENIED; /* No space to allocate a new cluster */ + if (dcl == 1) res = FR_INT_ERR; + if (dcl == 0xFFFFFFFF) res = FR_DISK_ERR; + if (res == FR_OK) /* Flush FAT */ + res = move_window(dj.fs, 0); + if (res == FR_OK) { /* Initialize the new directory table */ + dsc = clust2sect(dj.fs, dcl); + dir = dj.fs->win; + mem_set(dir, 0, SS(dj.fs)); + mem_set(dir+DIR_Name, ' ', 8+3); /* Create "." entry */ + dir[DIR_Name] = '.'; + dir[DIR_Attr] = AM_DIR; + ST_DWORD(dir+DIR_WrtTime, tim); + ST_CLUST(dir, dcl); + mem_cpy(dir+32, dir, 32); /* Create ".." entry */ + dir[33] = '.'; pcl = dj.sclust; + if (dj.fs->fs_type == FS_FAT32 && pcl == dj.fs->dirbase) + pcl = 0; + ST_CLUST(dir+32, pcl); + for (n = dj.fs->csize; n; n--) { /* Write dot entries and clear following sectors */ + dj.fs->winsect = dsc++; + dj.fs->wflag = 1; + res = move_window(dj.fs, 0); + if (res != FR_OK) break; + mem_set(dir, 0, SS(dj.fs)); + } + } + if (res == FR_OK) res = dir_register(&dj); /* Register the object to the directoy */ + if (res != FR_OK) { + remove_chain(dj.fs, dcl); /* Could not register, remove cluster chain */ + } else { + dir = dj.dir; + dir[DIR_Attr] = AM_DIR; /* Attribute */ + ST_DWORD(dir+DIR_WrtTime, tim); /* Created time */ + ST_CLUST(dir, dcl); /* Table start cluster */ + dj.fs->wflag = 1; + res = sync(dj.fs); + } + } + FREE_BUF(); + } + + LEAVE_FF(dj.fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Change Attribute */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_chmod ( + const TCHAR *path, /* Pointer to the file path */ + BYTE value, /* Attribute bits */ + BYTE mask /* Attribute mask to change */ +) +{ + FRESULT res; + DIR dj; + BYTE *dir; + DEF_NAMEBUF; + + + res = chk_mounted(&path, &dj.fs, 1); + if (res == FR_OK) { + INIT_BUF(dj); + res = follow_path(&dj, path); /* Follow the file path */ + FREE_BUF(); + if (_FS_RPATH && res == FR_OK && (dj.fn[NS] & NS_DOT)) + res = FR_INVALID_NAME; + if (res == FR_OK) { + dir = dj.dir; + if (!dir) { /* Is it a root directory? */ + res = FR_INVALID_NAME; + } else { /* File or sub directory */ + mask &= AM_RDO|AM_HID|AM_SYS|AM_ARC; /* Valid attribute mask */ + dir[DIR_Attr] = (value & mask) | (dir[DIR_Attr] & (BYTE)~mask); /* Apply attribute change */ + dj.fs->wflag = 1; + res = sync(dj.fs); + } + } + } + + LEAVE_FF(dj.fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Change Timestamp */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_utime ( + const TCHAR *path, /* Pointer to the file/directory name */ + const FILINFO *fno /* Pointer to the time stamp to be set */ +) +{ + FRESULT res; + DIR dj; + BYTE *dir; + DEF_NAMEBUF; + + + res = chk_mounted(&path, &dj.fs, 1); + if (res == FR_OK) { + INIT_BUF(dj); + res = follow_path(&dj, path); /* Follow the file path */ + FREE_BUF(); + if (_FS_RPATH && res == FR_OK && (dj.fn[NS] & NS_DOT)) + res = FR_INVALID_NAME; + if (res == FR_OK) { + dir = dj.dir; + if (!dir) { /* Root directory */ + res = FR_INVALID_NAME; + } else { /* File or sub-directory */ + ST_WORD(dir+DIR_WrtTime, fno->ftime); + ST_WORD(dir+DIR_WrtDate, fno->fdate); + dj.fs->wflag = 1; + res = sync(dj.fs); + } + } + } + + LEAVE_FF(dj.fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Rename File/Directory */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_rename ( + const TCHAR *path_old, /* Pointer to the old name */ + const TCHAR *path_new /* Pointer to the new name */ +) +{ + FRESULT res; + DIR djo, djn; + BYTE buf[21], *dir; + DWORD dw; + DEF_NAMEBUF; + + + res = chk_mounted(&path_old, &djo.fs, 1); + if (res == FR_OK) { + djn.fs = djo.fs; + INIT_BUF(djo); + res = follow_path(&djo, path_old); /* Check old object */ + if (_FS_RPATH && res == FR_OK && (djo.fn[NS] & NS_DOT)) + res = FR_INVALID_NAME; +#if _FS_SHARE + if (res == FR_OK) res = chk_lock(&djo, 2); +#endif + if (res == FR_OK) { /* Old object is found */ + if (!djo.dir) { /* Is root dir? */ + res = FR_NO_FILE; + } else { + mem_cpy(buf, djo.dir+DIR_Attr, 21); /* Save the object information except for name */ + mem_cpy(&djn, &djo, sizeof(DIR)); /* Check new object */ + res = follow_path(&djn, path_new); + if (res == FR_OK) res = FR_EXIST; /* The new object name is already existing */ + if (res == FR_NO_FILE) { /* Is it a valid path and no name collision? */ +/* Start critical section that any interruption or error can cause cross-link */ + res = dir_register(&djn); /* Register the new entry */ + if (res == FR_OK) { + dir = djn.dir; /* Copy object information except for name */ + mem_cpy(dir+13, buf+2, 19); + dir[DIR_Attr] = buf[0] | AM_ARC; + djo.fs->wflag = 1; + if (djo.sclust != djn.sclust && (dir[DIR_Attr] & AM_DIR)) { /* Update .. entry in the directory if needed */ + dw = clust2sect(djn.fs, LD_CLUST(dir)); + if (!dw) { + res = FR_INT_ERR; + } else { + res = move_window(djn.fs, dw); + dir = djn.fs->win+32; /* .. entry */ + if (res == FR_OK && dir[1] == '.') { + dw = (djn.fs->fs_type == FS_FAT32 && djn.sclust == djn.fs->dirbase) ? 0 : djn.sclust; + ST_CLUST(dir, dw); + djn.fs->wflag = 1; + } + } + } + if (res == FR_OK) { + res = dir_remove(&djo); /* Remove old entry */ + if (res == FR_OK) + res = sync(djo.fs); + } + } +/* End critical section */ + } + } + } + FREE_BUF(); + } + LEAVE_FF(djo.fs, res); +} + +#endif /* !_FS_READONLY */ +#endif /* _FS_MINIMIZE == 0 */ +#endif /* _FS_MINIMIZE <= 1 */ +#endif /* _FS_MINIMIZE <= 2 */ + + + +/*-----------------------------------------------------------------------*/ +/* Forward data to the stream directly (available on only tiny cfg) */ +/*-----------------------------------------------------------------------*/ +#if _USE_FORWARD && _FS_TINY + +FRESULT f_forward ( + FIL *fp, /* Pointer to the file object */ + UINT (*func)(const BYTE*,UINT), /* Pointer to the streaming function */ + UINT btr, /* Number of bytes to forward */ + UINT *bf /* Pointer to number of bytes forwarded */ +) +{ + FRESULT res; + DWORD remain, clst, sect; + UINT rcnt; + BYTE csect; + + + *bf = 0; /* Initialize byte counter */ + + res = validate(fp->fs, fp->id); /* Check validity of the object */ + if (res != FR_OK) LEAVE_FF(fp->fs, res); + if (fp->flag & FA__ERROR) /* Check error flag */ + LEAVE_FF(fp->fs, FR_INT_ERR); + if (!(fp->flag & FA_READ)) /* Check access mode */ + LEAVE_FF(fp->fs, FR_DENIED); + + remain = fp->fsize - fp->fptr; + if (btr > remain) btr = (UINT)remain; /* Truncate btr by remaining bytes */ + + for ( ; btr && (*func)(0, 0); /* Repeat until all data transferred or stream becomes busy */ + fp->fptr += rcnt, *bf += rcnt, btr -= rcnt) { + csect = (BYTE)(fp->fptr / SS(fp->fs) & (fp->fs->csize - 1)); /* Sector offset in the cluster */ + if ((fp->fptr % SS(fp->fs)) == 0) { /* On the sector boundary? */ + if (!csect) { /* On the cluster boundary? */ + clst = (fp->fptr == 0) ? /* On the top of the file? */ + fp->org_clust : get_fat(fp->fs, fp->curr_clust); + if (clst <= 1) ABORT(fp->fs, FR_INT_ERR); + if (clst == 0xFFFFFFFF) ABORT(fp->fs, FR_DISK_ERR); + fp->curr_clust = clst; /* Update current cluster */ + } + } + sect = clust2sect(fp->fs, fp->curr_clust); /* Get current data sector */ + if (!sect) ABORT(fp->fs, FR_INT_ERR); + sect += csect; + if (move_window(fp->fs, sect)) /* Move sector window */ + ABORT(fp->fs, FR_DISK_ERR); + fp->dsect = sect; + rcnt = SS(fp->fs) - (WORD)(fp->fptr % SS(fp->fs)); /* Forward data from sector window */ + if (rcnt > btr) rcnt = btr; + rcnt = (*func)(&fp->fs->win[(WORD)fp->fptr % SS(fp->fs)], rcnt); + if (!rcnt) ABORT(fp->fs, FR_INT_ERR); + } + + LEAVE_FF(fp->fs, FR_OK); +} +#endif /* _USE_FORWARD */ + + + +#if _USE_MKFS && !_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Create File System on the Drive */ +/*-----------------------------------------------------------------------*/ +#define N_ROOTDIR 512 /* Multiple of 32 */ +#define N_FATS 1 /* 1 or 2 */ + + +FRESULT f_mkfs ( + BYTE drv, /* Logical drive number */ + BYTE sfd, /* Partitioning rule 0:FDISK, 1:SFD */ + UINT au /* Allocation unit size [bytes] */ +) +{ + static const WORD vst[] = { 1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 0}; + static const WORD cst[] = {32768, 16384, 8192, 4096, 2048, 16384, 8192, 4096, 2048, 1024, 512}; + BYTE fmt, md, *tbl; + DWORD n_clst, vs, n, wsect; + UINT i; + DWORD b_vol, b_fat, b_dir, b_data; /* Offset (LBA) */ + DWORD n_vol, n_rsv, n_fat, n_dir; /* Size */ + FATFS *fs; + DSTATUS stat; + + + /* Check mounted drive and clear work area */ + if (drv >= _VOLUMES) return FR_INVALID_DRIVE; + fs = FatFs[drv]; + if (!fs) return FR_NOT_ENABLED; + fs->fs_type = 0; + drv = LD2PD(drv); + + /* Get disk statics */ + stat = disk_initialize(drv); + if (stat & STA_NOINIT) return FR_NOT_READY; + if (stat & STA_PROTECT) return FR_WRITE_PROTECTED; +#if _MAX_SS != 512 /* Get disk sector size */ + if (disk_ioctl(drv, GET_SECTOR_SIZE, &SS(fs)) != RES_OK) + return FR_DISK_ERR; +#endif + if (disk_ioctl(drv, GET_SECTOR_COUNT, (BYTE*)&n_vol) != RES_OK || n_vol < 128) + return FR_DISK_ERR; + b_vol = (sfd) ? 0 : 63; /* Volume start sector */ + n_vol -= b_vol; + if (au & (au - 1)) au = 0; /* Check validity of the allocation unit size */ + if (!au) { /* AU auto selection */ + vs = n_vol / (2000 / (SS(fs) / 512)); + for (i = 0; vs < vst[i]; i++) ; + au = cst[i]; + } + au /= SS(fs); /* Number of sectors per cluster */ + if (au == 0) au = 1; + if (au > 128) au = 128; + + /* Pre-compute number of clusters and FAT syb-type */ + n_clst = n_vol / au; + fmt = FS_FAT12; + if (n_clst >= MIN_FAT16) fmt = FS_FAT16; + if (n_clst >= MIN_FAT32) fmt = FS_FAT32; + + /* Determine offset and size of FAT structure */ + if (fmt == FS_FAT32) { + n_fat = ((n_clst * 4) + 8 + SS(fs) - 1) / SS(fs); + n_rsv = 32; + n_dir = 0; + } else { + n_fat = (fmt == FS_FAT12) ? (n_clst * 3 + 1) / 2 + 3 : (n_clst * 2) + 4; + n_fat = (n_fat + SS(fs) - 1) / SS(fs); + n_rsv = 1; + n_dir = N_ROOTDIR * 32UL / SS(fs); + } + b_fat = b_vol + n_rsv; /* FAT area start sector */ + b_dir = b_fat + n_fat * N_FATS; /* Directory area start sector */ + b_data = b_dir + n_dir; /* Data area start sector */ + if (n_vol < b_data + au) return FR_MKFS_ABORTED; /* Too small volume */ + + /* Align data start sector to erase block boundary (for flash memory media) */ + if (disk_ioctl(drv, GET_BLOCK_SIZE, (BYTE*)&n) != RES_OK || !n || n > 32768) n = 1; + n = (b_data + n - 1) & ~(n - 1); /* Next nearest erase block from current data start */ + n = (n - b_data) / N_FATS; + if (fmt == FS_FAT32) { /* FAT32: Move FAT offset */ + n_rsv += n; + b_fat += n; + } else { /* FAT12/16: Expand FAT size */ + n_fat += n; + } + + /* Determine number of cluster and final check of validity of the FAT sub-type */ + n_clst = (n_vol - n_rsv - n_fat * N_FATS - n_dir) / au; + if ( (fmt == FS_FAT16 && n_clst < MIN_FAT16) + || (fmt == FS_FAT32 && n_clst < MIN_FAT32)) + return FR_MKFS_ABORTED; + + /* Create partition table if required */ + if (sfd) { + md = 0xF0; + } else { + DWORD n_disk = b_vol + n_vol; + + mem_set(fs->win, 0, SS(fs)); + tbl = fs->win+MBR_Table; + ST_DWORD(tbl, 0x00010180); /* Partition start in CHS */ + if (n_disk < 63UL * 255 * 1024) { /* Partition end in CHS */ + n_disk = n_disk / 63 / 255; + tbl[7] = (BYTE)n_disk; + tbl[6] = (BYTE)((n_disk >> 2) | 63); + } else { + ST_WORD(&tbl[6], 0xFFFF); + } + tbl[5] = 254; + if (fmt != FS_FAT32) /* System ID */ + tbl[4] = (n_vol < 0x10000) ? 0x04 : 0x06; + else + tbl[4] = 0x0c; + ST_DWORD(tbl+8, 63); /* Partition start in LBA */ + ST_DWORD(tbl+12, n_vol); /* Partition size in LBA */ + ST_WORD(tbl+64, 0xAA55); /* Signature */ + if (disk_write(drv, fs->win, 0, 1) != RES_OK) + return FR_DISK_ERR; + md = 0xF8; + } + + /* Create volume boot record */ + tbl = fs->win; /* Clear sector */ + mem_set(tbl, 0, SS(fs)); + mem_cpy(tbl, "\xEB\xFE\x90" "MSDOS5.0", 11);/* Boot code, OEM name */ + i = SS(fs); /* Sector size */ + ST_WORD(tbl+BPB_BytsPerSec, i); + tbl[BPB_SecPerClus] = (BYTE)au; /* Sectors per cluster */ + ST_WORD(tbl+BPB_RsvdSecCnt, n_rsv); /* Reserved sectors */ + tbl[BPB_NumFATs] = N_FATS; /* Number of FATs */ + i = (fmt == FS_FAT32) ? 0 : N_ROOTDIR; /* Number of rootdir entries */ + ST_WORD(tbl+BPB_RootEntCnt, i); + if (n_vol < 0x10000) { /* Number of total sectors */ + ST_WORD(tbl+BPB_TotSec16, n_vol); + } else { + ST_DWORD(tbl+BPB_TotSec32, n_vol); + } + tbl[BPB_Media] = md; /* Media descriptor */ + ST_WORD(tbl+BPB_SecPerTrk, 63); /* Number of sectors per track */ + ST_WORD(tbl+BPB_NumHeads, 255); /* Number of heads */ + ST_DWORD(tbl+BPB_HiddSec, b_vol); /* Hidden sectors */ + n = get_fattime(); /* Use current time as VSN */ + if (fmt == FS_FAT32) { + ST_DWORD(tbl+BS_VolID32, n); /* VSN */ + ST_DWORD(tbl+BPB_FATSz32, n_fat); /* Number of sectors per FAT */ + ST_DWORD(tbl+BPB_RootClus, 2); /* Root directory start cluster (2) */ + ST_WORD(tbl+BPB_FSInfo, 1); /* FSInfo record offset (VBR+1) */ + ST_WORD(tbl+BPB_BkBootSec, 6); /* Backup boot record offset (VBR+6) */ + tbl[BS_DrvNum32] = 0x80; /* Drive number */ + tbl[BS_BootSig32] = 0x29; /* Extended boot signature */ + mem_cpy(tbl+BS_VolLab32, "NO NAME " "FAT32 ", 19); /* Volume label, FAT signature */ + } else { + ST_DWORD(tbl+BS_VolID, n); /* VSN */ + ST_WORD(tbl+BPB_FATSz16, n_fat); /* Number of sectors per FAT */ + tbl[BS_DrvNum] = 0x80; /* Drive number */ + tbl[BS_BootSig] = 0x29; /* Extended boot signature */ + mem_cpy(tbl+BS_VolLab, "NO NAME " "FAT ", 19); /* Volume label, FAT signature */ + } + ST_WORD(tbl+BS_55AA, 0xAA55); /* Signature (Offset is fixed here regardless of sector size) */ + if (disk_write(drv, tbl, b_vol, 1) != RES_OK)/* Write original (VBR) */ + return FR_DISK_ERR; + if (fmt == FS_FAT32) /* Write backup (VBR+6) */ + disk_write(drv, tbl, b_vol + 6, 1); + + /* Initialize FAT area */ + wsect = b_fat; + for (i = 0; i < N_FATS; i++) { + mem_set(tbl, 0, SS(fs)); /* 1st sector of the FAT */ + n = md; /* Media descriptor byte */ + if (fmt != FS_FAT32) { + n |= (fmt == FS_FAT12) ? 0x00FFFF00 : 0xFFFFFF00; + ST_DWORD(tbl+0, n); /* Reserve cluster #0-1 (FAT12/16) */ + } else { + n |= 0xFFFFFF00; + ST_DWORD(tbl+0, n); /* Reserve cluster #0-1 (FAT32) */ + ST_DWORD(tbl+4, 0xFFFFFFFF); + ST_DWORD(tbl+8, 0x0FFFFFFF); /* Reserve cluster #2 for root dir */ + } + if (disk_write(drv, tbl, wsect++, 1) != RES_OK) + return FR_DISK_ERR; + mem_set(tbl, 0, SS(fs)); /* Fill following FAT entries with zero */ + for (n = 1; n < n_fat; n++) { /* This loop may take a time on FAT32 volume due to many single sector write */ + if (disk_write(drv, tbl, wsect++, 1) != RES_OK) + return FR_DISK_ERR; + } + } + + /* Initialize root directory */ + i = (fmt == FS_FAT32) ? au : n_dir; + do { + if (disk_write(drv, tbl, wsect++, 1) != RES_OK) + return FR_DISK_ERR; + } while (--i); + +#if _USE_ERASE /* Erase data area if needed */ + { + DWORD eb[2]; + + eb[0] = wsect; eb[1] = wsect + n_clst * au - 1; + disk_ioctl(drv, CTRL_ERASE_SECTOR, eb); + } +#endif + + /* Create FSInfo if needed */ + if (fmt == FS_FAT32) { + ST_WORD(tbl+BS_55AA, 0xAA55); + ST_DWORD(tbl+FSI_LeadSig, 0x41615252); + ST_DWORD(tbl+FSI_StrucSig, 0x61417272); + ST_DWORD(tbl+FSI_Free_Count, n_clst - 1); + ST_DWORD(tbl+FSI_Nxt_Free, 0xFFFFFFFF); + disk_write(drv, tbl, b_vol + 1, 1); /* Write original (VBR+1) */ + disk_write(drv, tbl, b_vol + 7, 1); /* Write backup (VBR+7) */ + } + + return (disk_ioctl(drv, CTRL_SYNC, (BYTE*)0) == RES_OK) ? FR_OK : FR_DISK_ERR; +} + +#endif /* _USE_MKFS && !_FS_READONLY */ + + + + +#if _USE_STRFUNC +/*-----------------------------------------------------------------------*/ +/* Get a string from the file */ +/*-----------------------------------------------------------------------*/ +TCHAR* f_gets ( + TCHAR* buff, /* Pointer to the string buffer to read */ + int len, /* Size of string buffer (characters) */ + FIL* fil /* Pointer to the file object */ +) +{ + int n = 0; + TCHAR c, *p = buff; + BYTE s[2]; + UINT rc; + + + while (n < len - 1) { /* Read bytes until buffer gets filled */ + f_read(fil, s, 1, &rc); + if (rc != 1) break; /* Break on EOF or error */ + c = s[0]; +#if _LFN_UNICODE /* Read a character in UTF-8 encoding */ + if (c >= 0x80) { + if (c < 0xC0) continue; /* Skip stray trailer */ + if (c < 0xE0) { /* Two-byte sequense */ + f_read(fil, s, 1, &rc); + if (rc != 1) break; + c = ((c & 0x1F) << 6) | (s[0] & 0x3F); + if (c < 0x80) c = '?'; + } else { + if (c < 0xF0) { /* Three-byte sequense */ + f_read(fil, s, 2, &rc); + if (rc != 2) break; + c = (c << 12) | ((s[0] & 0x3F) << 6) | (s[1] & 0x3F); + if (c < 0x800) c = '?'; + } else { /* Reject four-byte sequense */ + c = '?'; + } + } + } +#endif +#if _USE_STRFUNC >= 2 + if (c == '\r') continue; /* Strip '\r' */ +#endif + *p++ = c; + n++; + if (c == '\n') break; /* Break on EOL */ + } + *p = 0; + return n ? buff : 0; /* When no data read (eof or error), return with error. */ +} + + + +#if !_FS_READONLY +#include +/*-----------------------------------------------------------------------*/ +/* Put a character to the file */ +/*-----------------------------------------------------------------------*/ +int f_putc ( + TCHAR c, /* A character to be output */ + FIL* fil /* Pointer to the file object */ +) +{ + UINT bw, btw; + BYTE s[3]; + + +#if _USE_STRFUNC >= 2 + if (c == '\n') f_putc ('\r', fil); /* LF -> CRLF conversion */ +#endif + +#if _LFN_UNICODE /* Write the character in UTF-8 encoding */ + if (c < 0x80) { /* 7-bit */ + s[0] = (BYTE)c; + btw = 1; + } else { + if (c < 0x800) { /* 11-bit */ + s[0] = (BYTE)(0xC0 | (c >> 6)); + s[1] = (BYTE)(0x80 | (c & 0x3F)); + btw = 2; + } else { /* 16-bit */ + s[0] = (BYTE)(0xE0 | (c >> 12)); + s[1] = (BYTE)(0x80 | ((c >> 6) & 0x3F)); + s[2] = (BYTE)(0x80 | (c & 0x3F)); + btw = 3; + } + } +#else /* Write the character without conversion */ + s[0] = (BYTE)c; + btw = 1; +#endif + f_write(fil, s, btw, &bw); /* Write the char to the file */ + return (bw == btw) ? 1 : EOF; /* Return the result */ +} + + + + +/*-----------------------------------------------------------------------*/ +/* Put a string to the file */ +/*-----------------------------------------------------------------------*/ +int f_puts ( + const TCHAR* str, /* Pointer to the string to be output */ + FIL* fil /* Pointer to the file object */ +) +{ + int n; + + + for (n = 0; *str; str++, n++) { + if (f_putc(*str, fil) == EOF) return EOF; + } + return n; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Put a formatted string to the file */ +/*-----------------------------------------------------------------------*/ +int f_printf ( + FIL* fil, /* Pointer to the file object */ + const TCHAR* str, /* Pointer to the format string */ + ... /* Optional arguments... */ +) +{ + va_list arp; + BYTE f, r; + UINT i, w; + ULONG val; + TCHAR c, d, s[16]; + int res, cc; + + + va_start(arp, str); + + for (cc = res = 0; cc != EOF; res += cc) { + c = *str++; + if (c == 0) break; /* End of string */ + if (c != '%') { /* Non escape character */ + cc = f_putc(c, fil); + if (cc != EOF) cc = 1; + continue; + } + w = f = 0; + c = *str++; + if (c == '0') { /* Flag: '0' padding */ + f = 1; c = *str++; + } + while (IsDigit(c)) { /* Precision */ + w = w * 10 + c - '0'; + c = *str++; + } + if (c == 'l' || c == 'L') { /* Prefix: Size is long int */ + f |= 2; c = *str++; + } + if (!c) break; + d = c; + if (IsLower(d)) d -= 0x20; + switch (d) { /* Type is... */ + case 'S' : /* String */ + cc = f_puts(va_arg(arp, TCHAR*), fil); continue; + case 'C' : /* Character */ + cc = f_putc((TCHAR)va_arg(arp, int), fil); continue; + case 'B' : /* Binary */ + r = 2; break; + case 'O' : /* Octal */ + r = 8; break; + case 'D' : /* Signed decimal */ + case 'U' : /* Unsigned decimal */ + r = 10; break; + case 'X' : /* Hexdecimal */ + r = 16; break; + default: /* Unknown */ + cc = f_putc(c, fil); continue; + } + + /* Get an argument */ + val = (f & 2) ? va_arg(arp, long) : ((d == 'D') ? (long)va_arg(arp, int) : va_arg(arp, unsigned int)); + if (d == 'D' && (val & 0x80000000)) { + val = 0 - val; + f |= 4; + } + /* Put it in numeral string */ + i = 0; + do { + d = (TCHAR)(val % r); val /= r; + if (d > 9) { + d += 7; + if (c == 'x') d += 0x20; + } + s[i++] = d + '0'; + } while (val && i < sizeof(s) / sizeof(s[0])); + if (f & 4) s[i++] = '-'; + cc = 0; + while (i < w-- && cc != EOF) { + cc = f_putc((TCHAR)((f & 1) ? '0' : ' '), fil); + res++; + } + do { + cc = f_putc(s[--i], fil); + res++; + } while (i && cc != EOF); + if (cc != EOF) cc = 0; + } + + va_end(arp); + return (cc == EOF) ? cc : res; +} + +#endif /* !_FS_READONLY */ +#endif /* _USE_STRFUNC */ diff --git a/src/ff.h b/src/ff.h new file mode 100644 index 000000000..e348efe2a --- /dev/null +++ b/src/ff.h @@ -0,0 +1,541 @@ +/*---------------------------------------------------------------------------/ +/ FatFs - FAT file system module include file R0.08a (C)ChaN, 2010 +/----------------------------------------------------------------------------/ +/ FatFs module is a generic FAT file system module for small embedded systems. +/ This is a free software that opened for education, research and commercial +/ developments under license policy of following trems. +/ +/ Copyright (C) 2010, ChaN, all right reserved. +/ +/ * The FatFs module is a free software and there is NO WARRANTY. +/ * No restriction on use. You can use, modify and redistribute it for +/ personal, non-profit or commercial product UNDER YOUR RESPONSIBILITY. +/ * Redistributions of source code must retain the above copyright notice. +/ +/----------------------------------------------------------------------------*/ + +#ifndef _FATFS +#define _FATFS 8255 /* Revision ID */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "integer.h" /* Basic integer types */ +#include "ffconf.h" /* FatFs configuration options */ + +#if _FATFS != _FFCONF +#error Wrong configuration file (ffconf.h). +#endif + + +/* DBCS code ranges and SBCS extend char conversion table */ + +#if _CODE_PAGE == 932 /* Japanese Shift-JIS */ +#define _DF1S 0x81 /* DBC 1st byte range 1 start */ +#define _DF1E 0x9F /* DBC 1st byte range 1 end */ +#define _DF2S 0xE0 /* DBC 1st byte range 2 start */ +#define _DF2E 0xFC /* DBC 1st byte range 2 end */ +#define _DS1S 0x40 /* DBC 2nd byte range 1 start */ +#define _DS1E 0x7E /* DBC 2nd byte range 1 end */ +#define _DS2S 0x80 /* DBC 2nd byte range 2 start */ +#define _DS2E 0xFC /* DBC 2nd byte range 2 end */ + +#elif _CODE_PAGE == 936 /* Simplified Chinese GBK */ +#define _DF1S 0x81 +#define _DF1E 0xFE +#define _DS1S 0x40 +#define _DS1E 0x7E +#define _DS2S 0x80 +#define _DS2E 0xFE + +#elif _CODE_PAGE == 949 /* Korean */ +#define _DF1S 0x81 +#define _DF1E 0xFE +#define _DS1S 0x41 +#define _DS1E 0x5A +#define _DS2S 0x61 +#define _DS2E 0x7A +#define _DS3S 0x81 +#define _DS3E 0xFE + +#elif _CODE_PAGE == 950 /* Traditional Chinese Big5 */ +#define _DF1S 0x81 +#define _DF1E 0xFE +#define _DS1S 0x40 +#define _DS1E 0x7E +#define _DS2S 0xA1 +#define _DS2E 0xFE + +#elif _CODE_PAGE == 437 /* U.S. (OEM) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x9A,0x90,0x41,0x8E,0x41,0x8F,0x80,0x45,0x45,0x45,0x49,0x49,0x49,0x8E,0x8F,0x90,0x92,0x92,0x4F,0x99,0x4F,0x55,0x55,0x59,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0x41,0x49,0x4F,0x55,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0x21,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 720 /* Arabic (OEM) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x81,0x45,0x41,0x84,0x41,0x86,0x43,0x45,0x45,0x45,0x49,0x49,0x8D,0x8E,0x8F,0x90,0x92,0x92,0x93,0x94,0x95,0x49,0x49,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 737 /* Greek (OEM) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x90,0x92,0x92,0x93,0x94,0x95,0x96,0x97,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87, \ + 0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x90,0x91,0xAA,0x92,0x93,0x94,0x95,0x96,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0x97,0xEA,0xEB,0xEC,0xE4,0xED,0xEE,0xE7,0xE8,0xF1,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 775 /* Baltic (OEM) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x9A,0x91,0xA0,0x8E,0x95,0x8F,0x80,0xAD,0xED,0x8A,0x8A,0xA1,0x8D,0x8E,0x8F,0x90,0x92,0x92,0xE2,0x99,0x95,0x96,0x97,0x97,0x99,0x9A,0x9D,0x9C,0x9D,0x9E,0x9F, \ + 0xA0,0xA1,0xE0,0xA3,0xA3,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xB5,0xB6,0xB7,0xB8,0xBD,0xBE,0xC6,0xC7,0xA5,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE5,0xE5,0xE6,0xE3,0xE8,0xE8,0xEA,0xEA,0xEE,0xED,0xEE,0xEF,0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 850 /* Multilingual Latin 1 (OEM) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x9A,0x90,0xB6,0x8E,0xB7,0x8F,0x80,0xD2,0xD3,0xD4,0xD8,0xD7,0xDE,0x8E,0x8F,0x90,0x92,0x92,0xE2,0x99,0xE3,0xEA,0xEB,0x59,0x99,0x9A,0x9D,0x9C,0x9D,0x9E,0x9F, \ + 0xB5,0xD6,0xE0,0xE9,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0x21,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC7,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE5,0xE5,0xE6,0xE7,0xE7,0xE9,0xEA,0xEB,0xED,0xED,0xEE,0xEF,0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 852 /* Latin 2 (OEM) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x9A,0x90,0xB6,0x8E,0xDE,0x8F,0x80,0x9D,0xD3,0x8A,0x8A,0xD7,0x8D,0x8E,0x8F,0x90,0x91,0x91,0xE2,0x99,0x95,0x95,0x97,0x97,0x99,0x9A,0x9B,0x9B,0x9D,0x9E,0x9F, \ + 0xB5,0xD6,0xE0,0xE9,0xA4,0xA4,0xA6,0xA6,0xA8,0xA8,0xAA,0x8D,0xAC,0xB8,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBD,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC6,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD1,0xD1,0xD2,0xD3,0xD2,0xD5,0xD6,0xD7,0xB7,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE3,0xD5,0xE6,0xE6,0xE8,0xE9,0xE8,0xEB,0xED,0xED,0xDD,0xEF,0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xEB,0xFC,0xFC,0xFE,0xFF} + +#elif _CODE_PAGE == 855 /* Cyrillic (OEM) */ +#define _DF1S 0 +#define _EXCVT {0x81,0x81,0x83,0x83,0x85,0x85,0x87,0x87,0x89,0x89,0x8B,0x8B,0x8D,0x8D,0x8F,0x8F,0x91,0x91,0x93,0x93,0x95,0x95,0x97,0x97,0x99,0x99,0x9B,0x9B,0x9D,0x9D,0x9F,0x9F, \ + 0xA1,0xA1,0xA3,0xA3,0xA5,0xA5,0xA7,0xA7,0xA9,0xA9,0xAB,0xAB,0xAD,0xAD,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB6,0xB6,0xB8,0xB8,0xB9,0xBA,0xBB,0xBC,0xBE,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC7,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD1,0xD1,0xD3,0xD3,0xD5,0xD5,0xD7,0xD7,0xDD,0xD9,0xDA,0xDB,0xDC,0xDD,0xE0,0xDF, \ + 0xE0,0xE2,0xE2,0xE4,0xE4,0xE6,0xE6,0xE8,0xE8,0xEA,0xEA,0xEC,0xEC,0xEE,0xEE,0xEF,0xF0,0xF2,0xF2,0xF4,0xF4,0xF6,0xF6,0xF8,0xF8,0xFA,0xFA,0xFC,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 857 /* Turkish (OEM) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x9A,0x90,0xB6,0x8E,0xB7,0x8F,0x80,0xD2,0xD3,0xD4,0xD8,0xD7,0x98,0x8E,0x8F,0x90,0x92,0x92,0xE2,0x99,0xE3,0xEA,0xEB,0x98,0x99,0x9A,0x9D,0x9C,0x9D,0x9E,0x9E, \ + 0xB5,0xD6,0xE0,0xE9,0xA5,0xA5,0xA6,0xA6,0xA8,0xA9,0xAA,0xAB,0xAC,0x21,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC7,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE5,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xDE,0x59,0xEE,0xEF,0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 858 /* Multilingual Latin 1 + Euro (OEM) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x9A,0x90,0xB6,0x8E,0xB7,0x8F,0x80,0xD2,0xD3,0xD4,0xD8,0xD7,0xDE,0x8E,0x8F,0x90,0x92,0x92,0xE2,0x99,0xE3,0xEA,0xEB,0x59,0x99,0x9A,0x9D,0x9C,0x9D,0x9E,0x9F, \ + 0xB5,0xD6,0xE0,0xE9,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0x21,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC7,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD1,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE5,0xE5,0xE6,0xE7,0xE7,0xE9,0xEA,0xEB,0xED,0xED,0xEE,0xEF,0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 862 /* Hebrew (OEM) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0x41,0x49,0x4F,0x55,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0x21,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 866 /* Russian (OEM) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0x90,0x91,0x92,0x93,0x9d,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F,0xF0,0xF0,0xF2,0xF2,0xF4,0xF4,0xF6,0xF6,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 874 /* Thai (OEM, Windows) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 1250 /* Central Europe (Windows) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x8A,0x9B,0x8C,0x8D,0x8E,0x8F, \ + 0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,0xB0,0xB1,0xB2,0xA3,0xB4,0xB5,0xB6,0xB7,0xB8,0xA5,0xAA,0xBB,0xBC,0xBD,0xBC,0xAF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xF7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xFF} + +#elif _CODE_PAGE == 1251 /* Cyrillic (Windows) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x81,0x82,0x82,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x80,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x8A,0x9B,0x8C,0x8D,0x8E,0x8F, \ + 0xA0,0xA2,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,0xB0,0xB1,0xB2,0xB2,0xA5,0xB5,0xB6,0xB7,0xA8,0xB9,0xAA,0xBB,0xA3,0xBD,0xBD,0xAF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF} + +#elif _CODE_PAGE == 1252 /* Latin 1 (Windows) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0xAd,0x9B,0x8C,0x9D,0xAE,0x9F, \ + 0xA0,0x21,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xF7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0x9F} + +#elif _CODE_PAGE == 1253 /* Greek (Windows) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xA2,0xB8,0xB9,0xBA, \ + 0xE0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xF2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xFB,0xBC,0xFD,0xBF,0xFF} + +#elif _CODE_PAGE == 1254 /* Turkish (Windows) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x8A,0x9B,0x8C,0x9D,0x9E,0x9F, \ + 0xA0,0x21,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xF7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0x9F} + +#elif _CODE_PAGE == 1255 /* Hebrew (Windows) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0xA0,0x21,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 1256 /* Arabic (Windows) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x8C,0x9D,0x9E,0x9F, \ + 0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0x41,0xE1,0x41,0xE3,0xE4,0xE5,0xE6,0x43,0x45,0x45,0x45,0x45,0xEC,0xED,0x49,0x49,0xF0,0xF1,0xF2,0xF3,0x4F,0xF5,0xF6,0xF7,0xF8,0x55,0xFA,0x55,0x55,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 1257 /* Baltic (Windows) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xA8,0xB9,0xAA,0xBB,0xBC,0xBD,0xBE,0xAF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xF7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xFF} + +#elif _CODE_PAGE == 1258 /* Vietnam (OEM, Windows) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0xAC,0x9D,0x9E,0x9F, \ + 0xA0,0x21,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xEC,0xCD,0xCE,0xCF,0xD0,0xD1,0xF2,0xD3,0xD4,0xD5,0xD6,0xF7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xFE,0x9F} + +#elif _CODE_PAGE == 1 /* ASCII (for only non-LFN cfg) */ +#define _DF1S 0 + +#else +#error Unknown code page + +#endif + + + +/* Definitions of volume management */ + +#if _MULTI_PARTITION /* Multiple partition configuration */ +#define LD2PD(vol) (VolToPart[vol].pd) /* Get physical drive# */ +#define LD2PT(vol) (VolToPart[vol].pt) /* Get partition# */ +typedef struct { + BYTE pd; /* Physical drive# */ + BYTE pt; /* Partition # (0-3) */ +} PARTITION; +extern const PARTITION VolToPart[]; /* Volume - Physical location resolution table */ + +#else /* Single partition configuration */ +#define LD2PD(vol) (vol) /* Logical drive# is bound to the same physical drive# */ +#define LD2PT(vol) 0 /* Always mounts the 1st partition */ + +#endif + + + +/* Type of path name strings on FatFs API */ + +#if _LFN_UNICODE /* Unicode string */ +#if !_USE_LFN +#error _LFN_UNICODE must be 0 in non-LFN cfg. +#endif +#ifndef _INC_TCHAR +typedef WCHAR TCHAR; +#define _T(x) L ## x +#define _TEXT(x) L ## x +#endif + +#else /* ANSI/OEM string */ +#ifndef _INC_TCHAR +typedef char TCHAR; +#define _T(x) x +#define _TEXT(x) x +#endif + +#endif + + + +/* File system object structure (FATFS) */ + +typedef struct { + BYTE fs_type; /* FAT sub-type (0:Not mounted) */ + BYTE drv; /* Physical drive number */ + BYTE csize; /* Sectors per cluster (1,2,4...128) */ + BYTE n_fats; /* Number of FAT copies (1,2) */ + BYTE wflag; /* win[] dirty flag (1:must be written back) */ + BYTE fsi_flag; /* fsinfo dirty flag (1:must be written back) */ + WORD id; /* File system mount ID */ + WORD n_rootdir; /* Number of root directory entries (FAT12/16) */ +#if _MAX_SS != 512 + WORD ssize; /* Bytes per sector (512,1024,2048,4096) */ +#endif +#if _FS_REENTRANT + _SYNC_t sobj; /* Identifier of sync object */ +#endif +#if !_FS_READONLY + DWORD last_clust; /* Last allocated cluster */ + DWORD free_clust; /* Number of free clusters */ + DWORD fsi_sector; /* fsinfo sector (FAT32) */ +#endif +#if _FS_RPATH + DWORD cdir; /* Current directory start cluster (0:root) */ +#endif + DWORD n_fatent; /* Number of FAT entries (= number of clusters + 2) */ + DWORD fsize; /* Sectors per FAT */ + DWORD fatbase; /* FAT start sector */ + DWORD dirbase; /* Root directory start sector (FAT32:Cluster#) */ + DWORD database; /* Data start sector */ + DWORD winsect; /* Current sector appearing in the win[] */ + BYTE win[_MAX_SS]; /* Disk access window for Directory, FAT (and Data on tiny cfg) */ +} FATFS; + + + +/* File object structure (FIL) */ + +typedef struct { + FATFS* fs; /* Pointer to the owner file system object */ + WORD id; /* Owner file system mount ID */ + BYTE flag; /* File status flags */ + BYTE pad1; + DWORD fptr; /* File read/write pointer (0 on file open) */ + DWORD fsize; /* File size */ + DWORD org_clust; /* File start cluster (0 when fsize==0) */ + DWORD curr_clust; /* Current cluster */ + DWORD dsect; /* Current data sector */ +#if !_FS_READONLY + DWORD dir_sect; /* Sector containing the directory entry */ + BYTE* dir_ptr; /* Ponter to the directory entry in the window */ +#endif +#if _USE_FASTSEEK + DWORD* cltbl; /* Pointer to the cluster link map table (null on file open) */ +#endif +#if _FS_SHARE + UINT lockid; /* File lock ID (index of file semaphore table) */ +#endif +#if !_FS_TINY + BYTE buf[_MAX_SS]; /* File data read/write buffer */ +#endif +} FIL; + + + +/* Directory object structure (DIR) */ + +typedef struct { + FATFS* fs; /* Pointer to the owner file system object */ + WORD id; /* Owner file system mount ID */ + WORD index; /* Current read/write index number */ + DWORD sclust; /* Table start cluster (0:Root dir) */ + DWORD clust; /* Current cluster */ + DWORD sect; /* Current sector */ + BYTE* dir; /* Pointer to the current SFN entry in the win[] */ + BYTE* fn; /* Pointer to the SFN (in/out) {file[8],ext[3],status[1]} */ +#if _USE_LFN + WCHAR* lfn; /* Pointer to the LFN working buffer */ + WORD lfn_idx; /* Last matched LFN index number (0xFFFF:No LFN) */ +#endif +} DIR; + + + +/* File status structure (FILINFO) */ + +typedef struct { + DWORD fsize; /* File size */ + WORD fdate; /* Last modified date */ + WORD ftime; /* Last modified time */ + BYTE fattrib; /* Attribute */ + TCHAR fname[13]; /* Short file name (8.3 format) */ +#if _USE_LFN + TCHAR* lfname; /* Pointer to the LFN buffer */ + UINT lfsize; /* Size of LFN buffer in TCHAR */ +#endif +} FILINFO; + + + +/* File function return code (FRESULT) */ + +typedef enum { + FR_OK = 0, // (0) Succeeded + FR_DISK_ERR, // (1) A hard error occured in the low level disk I/O layer + FR_INT_ERR, // (2) Assertion failed + FR_NOT_READY, // (3) The physical drive cannot work + FR_NO_FILE, // (4) Could not find the file + FR_NO_PATH, // (5) Could not find the path + FR_INVALID_NAME, // (6) The path name format is invalid + FR_DENIED, // (7) Acces denied due to prohibited access or directory full + FR_EXIST, // (8) Acces denied due to prohibited access + FR_INVALID_OBJECT, // (9) The file/directory object is invalid + FR_WRITE_PROTECTED, // (10) The physical drive is write protected + FR_INVALID_DRIVE, // (11) The logical drive number is invalid + FR_NOT_ENABLED, // (12) The volume has no work area + FR_NO_FILESYSTEM, // (13) There is no valid FAT volume on the physical drive + FR_MKFS_ABORTED, // (14) The f_mkfs() aborted due to any parameter error + FR_TIMEOUT, // (15) Could not get a grant to access the volume within defined period + FR_LOCKED, // (16) The operation is rejected according to the file shareing policy + FR_NOT_ENOUGH_CORE, // (17) LFN working buffer could not be allocated + FR_TOO_MANY_OPEN_FILES // (18) Number of open files > _FS_SHARE +} FRESULT; + + + +/*--------------------------------------------------------------*/ +/* FatFs module application interface */ + +FRESULT f_mount (BYTE, FATFS*); /* Mount/Unmount a logical drive */ +FRESULT f_open (FIL*, const TCHAR*, BYTE); /* Open or create a file */ +FRESULT f_read (FIL*, void*, UINT, UINT*); /* Read data from a file */ +FRESULT f_lseek (FIL*, DWORD); /* Move file pointer of a file object */ +FRESULT f_close (FIL*); /* Close an open file object */ +FRESULT f_opendir (DIR*, const TCHAR*); /* Open an existing directory */ +FRESULT f_readdir (DIR*, FILINFO*); /* Read a directory item */ +FRESULT f_stat (const TCHAR*, FILINFO*); /* Get file status */ + +#if !_FS_READONLY +FRESULT f_write (FIL*, const void*, UINT, UINT*); /* Write data to a file */ +FRESULT f_getfree (const TCHAR*, DWORD*, FATFS**); /* Get number of free clusters on the drive */ +FRESULT f_truncate (FIL*); /* Truncate file */ +FRESULT f_sync (FIL*); /* Flush cached data of a writing file */ +FRESULT f_unlink (const TCHAR*); /* Delete an existing file or directory */ +FRESULT f_mkdir (const TCHAR*); /* Create a new directory */ +FRESULT f_chmod (const TCHAR*, BYTE, BYTE); /* Change attriburte of the file/dir */ +FRESULT f_utime (const TCHAR*, const FILINFO*); /* Change timestamp of the file/dir */ +FRESULT f_rename (const TCHAR*, const TCHAR*); /* Rename/Move a file or directory */ +#endif + +#if _USE_FORWARD +FRESULT f_forward (FIL*, UINT(*)(const BYTE*,UINT), UINT, UINT*); /* Forward data to the stream */ +#endif + +#if _USE_MKFS +FRESULT f_mkfs (BYTE, BYTE, UINT); /* Create a file system on the drive */ +#endif + +#if _FS_RPATH +FRESULT f_chdrive (BYTE); /* Change current drive */ +FRESULT f_chdir (const TCHAR*); /* Change current directory */ +FRESULT f_getcwd (TCHAR*, UINT); /* Get current directory */ +#endif + +#if _USE_STRFUNC +int f_putc (TCHAR, FIL*); /* Put a character to the file */ +int f_puts (const TCHAR*, FIL*); /* Put a string to the file */ +int f_printf (FIL*, const TCHAR*, ...); /* Put a formatted string to the file */ +TCHAR* f_gets (TCHAR*, int, FIL*); /* Get a string from the file */ +#ifndef EOF +#define EOF (-1) +#endif +#endif + +#define f_eof(fp) (((fp)->fptr == (fp)->fsize) ? 1 : 0) +#define f_error(fp) (((fp)->flag & FA__ERROR) ? 1 : 0) +#define f_tell(fp) ((fp)->fptr) +#define f_size(fp) ((fp)->fsize) + + + +/*--------------------------------------------------------------*/ +/* Additional user defined functions */ + +/* RTC function */ +#if !_FS_READONLY +extern DWORD get_fattime (void); /* Returns 32-bit packed date and time */ +#endif + +/* Unicode support functions */ +#if _USE_LFN /* Unicode - OEM code conversion */ +WCHAR ff_convert (WCHAR, UINT); /* OEM-Unicode bidirectional conversion */ +WCHAR ff_wtoupper (WCHAR); /* Unicode upper-case conversion */ +#if _USE_LFN == 3 /* Memory functions */ +void* ff_memalloc (UINT); /* Allocate memory block */ +void ff_memfree (void*); /* Free memory block */ +#endif +#endif + +/* Sync functions */ +#if _FS_REENTRANT +int ff_cre_syncobj (BYTE, _SYNC_t*);/* Create a sync object */ +int ff_del_syncobj (_SYNC_t); /* Delete a sync object */ +int ff_req_grant (_SYNC_t); /* Lock sync object */ +void ff_rel_grant (_SYNC_t); /* Unlock sync object */ +#endif + + + + +/*--------------------------------------------------------------*/ +/* Flags and offset address */ + + +/* File access control and file status flags (FIL.flag) */ + +#define FA_READ 0x01 +#define FA_OPEN_EXISTING 0x00 +#define FA__ERROR 0x80 + +#if !_FS_READONLY +#define FA_WRITE 0x02 +#define FA_CREATE_NEW 0x04 +#define FA_CREATE_ALWAYS 0x08 +#define FA_OPEN_ALWAYS 0x10 +#define FA__WRITTEN 0x20 +#define FA__DIRTY 0x40 +#endif + + +/* FAT sub type (FATFS.fs_type) */ + +#define FS_FAT12 1 +#define FS_FAT16 2 +#define FS_FAT32 3 + + +/* File attribute bits for directory entry */ + +#define AM_RDO 0x01 /* Read only */ +#define AM_HID 0x02 /* Hidden */ +#define AM_SYS 0x04 /* System */ +#define AM_VOL 0x08 /* Volume label */ +#define AM_LFN 0x0F /* LFN entry */ +#define AM_DIR 0x10 /* Directory */ +#define AM_ARC 0x20 /* Archive */ +#define AM_MASK 0x3F /* Mask of defined bits */ + + +/* Fast seek function */ +#define CREATE_LINKMAP 0xFFFFFFFF + + +/*--------------------------------*/ +/* Multi-byte word access macros */ + +#if _WORD_ACCESS == 1 /* Enable word access to the FAT structure */ +#define LD_WORD(ptr) (WORD)(*(WORD*)(BYTE*)(ptr)) +#define LD_DWORD(ptr) (DWORD)(*(DWORD*)(BYTE*)(ptr)) +#define ST_WORD(ptr,val) *(WORD*)(BYTE*)(ptr)=(WORD)(val) +#define ST_DWORD(ptr,val) *(DWORD*)(BYTE*)(ptr)=(DWORD)(val) +#else /* Use byte-by-byte access to the FAT structure */ +#define LD_WORD(ptr) (WORD)(((WORD)*(BYTE*)((ptr)+1)<<8)|(WORD)*(BYTE*)(ptr)) +#define LD_DWORD(ptr) (DWORD)(((DWORD)*(BYTE*)((ptr)+3)<<24)|((DWORD)*(BYTE*)((ptr)+2)<<16)|((WORD)*(BYTE*)((ptr)+1)<<8)|*(BYTE*)(ptr)) +#define ST_WORD(ptr,val) *(BYTE*)(ptr)=(BYTE)(val); *(BYTE*)((ptr)+1)=(BYTE)((WORD)(val)>>8) +#define ST_DWORD(ptr,val) *(BYTE*)(ptr)=(BYTE)(val); *(BYTE*)((ptr)+1)=(BYTE)((WORD)(val)>>8); *(BYTE*)((ptr)+2)=(BYTE)((DWORD)(val)>>16); *(BYTE*)((ptr)+3)=(BYTE)((DWORD)(val)>>24) +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* _FATFS */ diff --git a/src/ffconf.h b/src/ffconf.h new file mode 100644 index 000000000..0b20b311e --- /dev/null +++ b/src/ffconf.h @@ -0,0 +1,188 @@ +/*---------------------------------------------------------------------------/ +/ FatFs - FAT file system module configuration file R0.08a (C)ChaN, 2010 +/----------------------------------------------------------------------------/ +/ +/ CAUTION! Do not forget to make clean the project after any changes to +/ the configuration options. +/ +/----------------------------------------------------------------------------*/ +#ifndef _FFCONF +#define _FFCONF 8255 /* Revision ID */ + + +/*---------------------------------------------------------------------------/ +/ Function and Buffer Configurations +/----------------------------------------------------------------------------*/ + +#define _FS_TINY 1 /* 0:Normal or 1:Tiny */ +/* When _FS_TINY is set to 1, FatFs uses the sector buffer in the file system +/ object instead of the sector buffer in the individual file object for file +/ data transfer. This reduces memory consumption 512 bytes each file object. */ + + +#define _FS_READONLY 0 /* 0:Read/Write or 1:Read only */ +/* Setting _FS_READONLY to 1 defines read only configuration. This removes +/ writing functions, f_write, f_sync, f_unlink, f_mkdir, f_chmod, f_rename, +/ f_truncate and useless f_getfree. */ + + +#define _FS_MINIMIZE 0 /* 0 to 3 */ +/* The _FS_MINIMIZE option defines minimization level to remove some functions. +/ +/ 0: Full function. +/ 1: f_stat, f_getfree, f_unlink, f_mkdir, f_chmod, f_truncate and f_rename +/ are removed. +/ 2: f_opendir and f_readdir are removed in addition to 1. +/ 3: f_lseek is removed in addition to 2. */ + + +#define _USE_STRFUNC 1 /* 0:Disable or 1/2:Enable */ +/* To enable string functions, set _USE_STRFUNC to 1 or 2. */ + + +#define _USE_MKFS 1 /* 0:Disable or 1:Enable */ +/* To enable f_mkfs function, set _USE_MKFS to 1 and set _FS_READONLY to 0 */ + + +#define _USE_FORWARD 0 /* 0:Disable or 1:Enable */ +/* To enable f_forward function, set _USE_FORWARD to 1 and set _FS_TINY to 1. */ + + +#define _USE_FASTSEEK 0 /* 0:Disable or 1:Enable */ +/* To enable fast seek feature, set _USE_FASTSEEK to 1. */ + + + +/*---------------------------------------------------------------------------/ +/ Locale and Namespace Configurations +/----------------------------------------------------------------------------*/ + +#define _CODE_PAGE 932 +/* The _CODE_PAGE specifies the OEM code page to be used on the target system. +/ Incorrect setting of the code page can cause a file open failure. +/ +/ 932 - Japanese Shift-JIS (DBCS, OEM, Windows) +/ 936 - Simplified Chinese GBK (DBCS, OEM, Windows) +/ 949 - Korean (DBCS, OEM, Windows) +/ 950 - Traditional Chinese Big5 (DBCS, OEM, Windows) +/ 1250 - Central Europe (Windows) +/ 1251 - Cyrillic (Windows) +/ 1252 - Latin 1 (Windows) +/ 1253 - Greek (Windows) +/ 1254 - Turkish (Windows) +/ 1255 - Hebrew (Windows) +/ 1256 - Arabic (Windows) +/ 1257 - Baltic (Windows) +/ 1258 - Vietnam (OEM, Windows) +/ 437 - U.S. (OEM) +/ 720 - Arabic (OEM) +/ 737 - Greek (OEM) +/ 775 - Baltic (OEM) +/ 850 - Multilingual Latin 1 (OEM) +/ 858 - Multilingual Latin 1 + Euro (OEM) +/ 852 - Latin 2 (OEM) +/ 855 - Cyrillic (OEM) +/ 866 - Russian (OEM) +/ 857 - Turkish (OEM) +/ 862 - Hebrew (OEM) +/ 874 - Thai (OEM, Windows) +/ 1 - ASCII only (Valid for non LFN cfg.) +*/ + + +#define _USE_LFN 0 /* 0 to 3 */ +#define _MAX_LFN 255 /* Maximum LFN length to handle (12 to 255) */ +/* The _USE_LFN option switches the LFN support. +/ +/ 0: Disable LFN feature. _MAX_LFN and _LFN_UNICODE have no effect. +/ 1: Enable LFN with static working buffer on the BSS. Always NOT reentrant. +/ 2: Enable LFN with dynamic working buffer on the STACK. +/ 3: Enable LFN with dynamic working buffer on the HEAP. +/ +/ The LFN working buffer occupies (_MAX_LFN + 1) * 2 bytes. To enable LFN, +/ Unicode handling functions ff_convert() and ff_wtoupper() must be added +/ to the project. When enable to use heap, memory control functions +/ ff_memalloc() and ff_memfree() must be added to the project. */ + + +#define _LFN_UNICODE 0 /* 0:ANSI/OEM or 1:Unicode */ +/* To switch the character code set on FatFs API to Unicode, +/ enable LFN feature and set _LFN_UNICODE to 1. */ + + +#define _FS_RPATH 2 /* 0 to 2 */ +/* The _FS_RPATH option configures relative path feature. +/ +/ 0: Disable relative path feature and remove related functions. +/ 1: Enable relative path. f_chdrive() and f_chdir() are available. +/ 2: f_getcwd() is available in addition to 1. +/ +/ Note that output of the f_readdir fnction is affected by this option. */ + + + +/*---------------------------------------------------------------------------/ +/ Physical Drive Configurations +/----------------------------------------------------------------------------*/ + +#define _VOLUMES 1 +/* Number of volumes (logical drives) to be used. */ + + +#define _MAX_SS 512 /* 512, 1024, 2048 or 4096 */ +/* Maximum sector size to be handled. +/ Always set 512 for memory card and hard disk but a larger value may be +/ required for floppy disk (512/1024) and optical disk (512/2048). +/ When _MAX_SS is larger than 512, GET_SECTOR_SIZE command must be implememted +/ to the disk_ioctl function. */ + + +#define _MULTI_PARTITION 0 /* 0:Single partition or 1:Multiple partition */ +/* When set to 0, each volume is bound to the same physical drive number and +/ it can mount only first primaly partition. When it is set to 1, each volume +/ is tied to the partitions listed in VolToPart[]. */ + + +#define _USE_ERASE 0 /* 0:Disable or 1:Enable */ +/* To enable sector erase feature, set _USE_ERASE to 1. */ + + + +/*---------------------------------------------------------------------------/ +/ System Configurations +/----------------------------------------------------------------------------*/ + +#define _WORD_ACCESS 0 /* 0 or 1 */ +/* Set 0 first and it is always compatible with all platforms. The _WORD_ACCESS +/ option defines which access method is used to the word data on the FAT volume. +/ +/ 0: Byte-by-byte access. +/ 1: Word access. Do not choose this unless following condition is met. +/ +/ When the byte order on the memory is big-endian or address miss-aligned word +/ access results incorrect behavior, the _WORD_ACCESS must be set to 0. +/ If it is not the case, the value can also be set to 1 to improve the +/ performance and code size. */ + + +/* Include a header file here to define sync object types on the O/S */ +/* #include , , or ohters. */ + +#define _FS_REENTRANT 0 /* 0:Disable or 1:Enable */ +#define _FS_TIMEOUT 1000 /* Timeout period in unit of time ticks */ +#define _SYNC_t HANDLE /* O/S dependent type of sync object. e.g. HANDLE, OS_EVENT*, ID and etc.. */ + +/* The _FS_REENTRANT option switches the reentrancy of the FatFs module. +/ +/ 0: Disable reentrancy. _SYNC_t and _FS_TIMEOUT have no effect. +/ 1: Enable reentrancy. Also user provided synchronization handlers, +/ ff_req_grant, ff_rel_grant, ff_del_syncobj and ff_cre_syncobj +/ function must be added to the project. */ + + +#define _FS_SHARE 0 /* 0:Disable or >=1:Enable */ +/* To enable file shareing feature, set _FS_SHARE to 1 or greater. The value + defines how many files can be opened simultaneously. */ + + +#endif /* _FFCONFIG */ diff --git a/src/file.cpp b/src/file.cpp new file mode 100644 index 000000000..90a4f071b --- /dev/null +++ b/src/file.cpp @@ -0,0 +1,682 @@ +/* + * + * gruvin9x Author Bryan J.Rentoul (Gruvin) + * + * gruvin9x is based on code named er9x by + * Author - Erez Raviv , which is in turn + * based on th9x -> http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + */ + + +#include "gruvin9x.h" +#include "stdio.h" +#include "inttypes.h" +#include "string.h" + +uint8_t s_write_err = 0; // error reasons +#ifdef ASYNC_WRITE +uint8_t s_sync_write = false; +#endif + +#define RESV 64 //reserv for eeprom header with directory (eeFs) +#define FIRSTBLK (RESV/BS) +#define BLOCKS (EESIZE/BS) + +#define EEFS_VERS 4 +struct DirEnt{ + uint8_t startBlk; + uint16_t size:12; + uint16_t typ:4; +}__attribute__((packed)); +#define MAXFILES (1+MAX_MODELS+3) +struct EeFs{ + uint8_t version; + uint8_t mySize; + uint8_t freeList; + uint8_t bs; + DirEnt files[MAXFILES]; +}__attribute__((packed)) eeFs; + + +static uint8_t EeFsRead(uint8_t blk,uint8_t ofs){ + uint8_t ret; + eeprom_read_block(&ret,(const void*)(blk*BS+ofs),1); + return ret; +} +static void EeFsWrite(uint8_t blk,uint8_t ofs,uint8_t val){ + eeWriteBlockCmp(&val, (void*)(blk*BS+ofs), 1); +} + +static uint8_t EeFsGetLink(uint8_t blk){ + return EeFsRead( blk,0); +} +static void EeFsSetLink(uint8_t blk,uint8_t val){ + EeFsWrite( blk,0,val); +} +static uint8_t EeFsGetDat(uint8_t blk,uint8_t ofs){ + return EeFsRead( blk,ofs+1); +} +static void EeFsSetDat(uint8_t blk,uint8_t ofs,uint8_t*buf,uint8_t len){ + //EeFsWrite( blk,ofs+1,val); + eeWriteBlockCmp(buf, (void*)(blk*BS+ofs+1), len); +} +static void EeFsFlushFreelist() +{ + eeWriteBlockCmp(&eeFs.freeList,&((EeFs*)0)->freeList ,sizeof(eeFs.freeList)); +} +static void EeFsFlush() +{ + eeWriteBlockCmp(&eeFs, 0,sizeof(eeFs)); +} + +uint16_t EeFsGetFree() +{ + uint16_t ret = 0; + uint8_t i = eeFs.freeList; + while( i ){ + ret += BS-1; + i = EeFsGetLink(i); + } + return ret; +} + +// #ifndef ASYNC_WRITE TODO because duplicate code +static void EeFsFree(uint8_t blk){///free one or more blocks + uint8_t i = blk; + while( EeFsGetLink(i)) i = EeFsGetLink(i); + EeFsSetLink(i,eeFs.freeList); + eeFs.freeList = blk; //chain in front + EeFsFlushFreelist(); +} +// #endif + +#ifndef ASYNC_WRITE +static uint8_t EeFsAlloc(){ ///alloc one block from freelist + uint8_t ret=eeFs.freeList; + if(ret){ + eeFs.freeList = EeFsGetLink(ret); + EeFsFlushFreelist(); + EeFsSetLink(ret,0); + } + return ret; +} +#endif + +int8_t EeFsck() +{ +#ifdef ASYNC_WRITE + s_sync_write = true; +#endif + + uint8_t *bufp; + static uint8_t buffer[BLOCKS]; + bufp = buffer; + memset(bufp,0,BLOCKS); + uint8_t blk ; + int8_t ret=0; + + for(uint8_t i = 0; i <= MAXFILES; i++){ + uint8_t *startP = i==MAXFILES ? &eeFs.freeList : &eeFs.files[i].startBlk; + uint8_t lastBlk = 0; + blk = *startP; + while(blk){ + if( ( blk < FIRSTBLK ) //goto err_1; //bad blk index + || (blk >= BLOCKS ) //goto err_2; //bad blk index + || (bufp[blk] ))//goto err_3; //blk double usage + { + if(lastBlk){ + EeFsSetLink(lastBlk,0); + }else{ + *startP = 0; //interrupt chain at startpos + EeFsFlush(); + } + blk=0; //abort + }else{ + bufp[blk] = i+1; + lastBlk = blk; + blk = EeFsGetLink(blk); + } + } + } + for(blk = FIRSTBLK; blk < BLOCKS; blk++){ + if(bufp[blk]==0) { //goto err_4; //unused block + EeFsSetLink(blk,eeFs.freeList); + eeFs.freeList = blk; //chain in front + EeFsFlushFreelist(); + } + } + +#ifdef ASYNC_WRITE + s_sync_write = false; +#endif + return ret; +} + +void EeFsFormat() +{ +#ifdef ASYNC_WRITE + s_sync_write = true; +#endif + if(sizeof(eeFs) != RESV){ + extern void eeprom_RESV_mismatch(); + eeprom_RESV_mismatch(); + } + memset(&eeFs,0, sizeof(eeFs)); + eeFs.version = EEFS_VERS; + eeFs.mySize = sizeof(eeFs); + eeFs.freeList = 0; + eeFs.bs = BS; + for(uint8_t i = FIRSTBLK; i < BLOCKS; i++) EeFsSetLink(i,i+1); + EeFsSetLink(BLOCKS-1, 0); + eeFs.freeList = FIRSTBLK; + EeFsFlush(); +#ifdef ASYNC_WRITE + s_sync_write = false; +#endif +} + +bool EeFsOpen() +{ + eeprom_read_block(&eeFs,0,sizeof(eeFs)); +#ifdef SIMU + if(eeFs.version != EEFS_VERS) printf("bad eeFs.version\n"); + if(eeFs.mySize != sizeof(eeFs)) printf("bad eeFs.mySize\n"); +#endif + + return eeFs.version == EEFS_VERS && eeFs.mySize == sizeof(eeFs); +} + +bool EFile::exists(uint8_t i_fileId) +{ + return eeFs.files[i_fileId].startBlk; +} + +void EFile::swap(uint8_t i_fileId1, uint8_t i_fileId2) +{ + DirEnt tmp = eeFs.files[i_fileId1]; + eeFs.files[i_fileId1] = eeFs.files[i_fileId2]; + eeFs.files[i_fileId2] = tmp; + EeFsFlush(); +} + +void EFile::rm(uint8_t i_fileId) +{ +#ifdef ASYNC_WRITE + s_sync_write = true; +#endif + uint8_t i = eeFs.files[i_fileId].startBlk; + memset(&eeFs.files[i_fileId], 0, sizeof(eeFs.files[i_fileId])); + EeFsFlush(); //chained out + + if(i) EeFsFree( i ); //chain in +#ifdef ASYNC_WRITE + s_sync_write = false; +#endif +} + +uint16_t EFile::size(){ + return eeFs.files[m_fileId].size; +} + + +// G: Open file ID for reading. Return the file's type +void EFile::openRd(uint8_t i_fileId){ + m_fileId = i_fileId; + m_pos = 0; + m_currBlk = eeFs.files[m_fileId].startBlk; + m_ofs = 0; + s_write_err = ERR_NONE; // error reasons */ +} + +void RlcFile::openRlc(uint8_t i_fileId) +{ + EFile::openRd(i_fileId); + m_zeroes = 0; + m_bRlc = 0; +} + +uint8_t EFile::read(uint8_t*buf,uint16_t i_len){ + uint16_t len = eeFs.files[m_fileId].size - m_pos; + if(len < i_len) i_len = len; + len = i_len; + while(len) + { + if(!m_currBlk) break; + *buf++ = EeFsGetDat(m_currBlk, m_ofs++); + if(m_ofs>=(BS-1)){ + m_ofs=0; + m_currBlk=EeFsGetLink(m_currBlk); + } + len--; + } + m_pos += i_len - len; + return i_len - len; +} + +// G: Read runlength (RLE) compressed bytes into buf. +#ifdef TRANSLATIONS +uint16_t RlcFile::readRlc12(uint8_t*buf,uint16_t i_len, bool rlc2) +#else +uint16_t RlcFile::readRlc(uint8_t*buf,uint16_t i_len) +#endif +{ + uint16_t i=0; + for( ; 1; ){ + uint8_t l=min(m_zeroes,i_len-i); + memset(&buf[i],0,l); + i += l; + m_zeroes -= l; + if(m_zeroes) break; + + l=min(m_bRlc,i_len-i); + uint8_t lr = read(&buf[i],l); + i += lr ; + m_bRlc -= lr; + if(m_bRlc) break; + + if(read(&m_bRlc,1)!=1) break; //read how many bytes to read + + assert(m_bRlc & 0x7f); +#ifdef TRANSLATIONS + if (rlc2) { +#endif + if(m_bRlc&0x80){ // if contains high byte + m_zeroes =(m_bRlc>>4) & 0x7; + m_bRlc = m_bRlc & 0x0f; + } + else if(m_bRlc&0x40){ + m_zeroes = m_bRlc & 0x3f; + m_bRlc = 0; + } + //else m_bRlc +#ifdef TRANSLATIONS + } + else { + if(m_bRlc&0x80){ // if contains high byte + m_zeroes = m_bRlc & 0x7f; + m_bRlc = 0; + } + } +#endif + } + return i; +} + +#ifdef ASYNC_WRITE + +void RlcFile::write1(uint8_t b) +{ + m_write1_byte = b; + write(&m_write1_byte, 1); +} + +void RlcFile::write(uint8_t *buf, uint8_t i_len) +{ + m_write_len = i_len; + m_write_buf = buf; + + do { + nextWriteStep(); + } while (s_sync_write && m_write_len && !s_write_err); +} + +void RlcFile::nextWriteStep() +{ + if(!m_currBlk && m_pos==0) + { + eeFs.files[FILE_TMP].startBlk = m_currBlk = eeFs.freeList; + if (m_currBlk) { + eeFs.freeList = EeFsGetLink(m_currBlk); + m_write_step = WRITE_START_STEP + WRITE_FIRST_LINK; + EeFsFlushFreelist(); + return; + } + } + if (m_write_step == WRITE_START_STEP + WRITE_FIRST_LINK) { + m_write_step = WRITE_START_STEP; + EeFsSetLink(m_currBlk, 0); + return; + } + + while (m_write_len) { + if (!m_currBlk) { + s_write_err = ERR_FULL; + break; + } + if (m_ofs >= (BS-1)) { + m_ofs = 0; + if (!EeFsGetLink(m_currBlk)) { + if (!eeFs.freeList) { + s_write_err = ERR_FULL; + break; + } + m_write_step += WRITE_NEXT_LINK_1; // TODO review all these names + EeFsSetLink(m_currBlk, eeFs.freeList); + return; + } + m_currBlk = EeFsGetLink(m_currBlk); + } + switch (m_write_step & 0x0f) { + case WRITE_NEXT_LINK_1: + m_currBlk = eeFs.freeList; + eeFs.freeList = EeFsGetLink(eeFs.freeList); + m_write_step += 1; + EeFsFlushFreelist(); + return; + case WRITE_NEXT_LINK_2: + m_write_step -= WRITE_NEXT_LINK_2; + EeFsSetLink(m_currBlk, 0); // TODO needed? + return; + } + if (!m_currBlk) { // TODO needed? + s_write_err = ERR_FULL; + break; + } + uint8_t tmp = BS-1-m_ofs; if(tmp>m_write_len) tmp=m_write_len; + m_write_buf += tmp; + m_write_len -= tmp; + m_ofs += tmp; + m_pos += tmp; + EeFsSetDat(m_currBlk, m_ofs-tmp, m_write_buf-tmp, tmp); + return; + } + + if (s_write_err == ERR_FULL) { + alert(PSTR("EEPROM overflow")); + m_write_step = 0; + m_write_len = 0; + } + + if (!s_sync_write) + nextRlcWriteStep(); +} + +#else + +uint8_t RlcFile::write1(uint8_t b) +{ + return write(&b,1); +} + +uint8_t RlcFile::write(uint8_t*buf, uint8_t i_len) +{ + uint8_t len=i_len; + if(!m_currBlk && m_pos==0) + { + eeFs.files[m_fileId].startBlk = m_currBlk = EeFsAlloc(); + } + while(len) + { +#ifndef SIMU + if( (int16_t)(m_stopTime10ms - get_tmr10ms()) < 0) + { + s_write_err = ERR_TMO; + break; + } +#endif + if(!m_currBlk) { + s_write_err = ERR_FULL; + break; + } + if(m_ofs>=(BS-1)){ + m_ofs=0; + if( ! EeFsGetLink(m_currBlk) ){ + EeFsSetLink(m_currBlk, EeFsAlloc()); + } + m_currBlk = EeFsGetLink(m_currBlk); + } + if(!m_currBlk) { + s_write_err = ERR_FULL; + break; + } + uint8_t l = BS-1-m_ofs; if(l>len) l=len; + EeFsSetDat(m_currBlk, m_ofs, buf, l); + buf +=l; + m_ofs +=l; + len -=l; + } + m_pos += i_len - len; + return i_len - len; +} + +#endif + + +#ifdef ASYNC_WRITE + +void RlcFile::create(uint8_t i_fileId, uint8_t typ, uint8_t sync_write) +{ + // all write operations will be executed on FILE_TMP + openRlc(FILE_TMP); // internal use + eeFs.files[FILE_TMP].typ = typ; + eeFs.files[FILE_TMP].size = 0; + m_fileId = i_fileId; + s_sync_write = sync_write; +} + +void RlcFile::writeRlc(uint8_t i_fileId, uint8_t typ, uint8_t*buf, uint16_t i_len, uint8_t sync_write) +{ + create(i_fileId, typ, sync_write); + + m_write_step = WRITE_START_STEP; + m_rlc_buf = buf; + m_rlc_len = i_len; + m_cur_rlc_len = 0; + + do { + nextRlcWriteStep(); + } while (s_sync_write && m_write_step && !s_write_err); +} + +void RlcFile::nextRlcWriteStep() +{ + uint8_t cnt = 1; + uint8_t cnt0 = 0; + uint16_t i = 0; + + if (m_cur_rlc_len) { + uint8_t tmp1 = m_cur_rlc_len; + uint8_t *tmp2 = m_rlc_buf; + m_rlc_buf += m_cur_rlc_len; + m_cur_rlc_len = 0; + write(tmp2, tmp1); + return; + } + + bool run0 = m_rlc_buf[0] == 0; + + if(m_rlc_len==0) goto close; + + for (i=1; 1; i++) // !! laeuft ein byte zu weit !! + { + bool cur0 = m_rlc_buf[i] == 0; + if (cur0 != run0 || cnt==0x3f || (cnt0 && cnt==0xf)|| i==m_rlc_len){ + if (run0) { + assert(cnt0==0); + if (cnt<8 && i!=m_rlc_len) + cnt0 = cnt; //aufbew fuer spaeter + else { + m_rlc_buf+=cnt; + m_rlc_len-=cnt; + write1(cnt|0x40); + return; + } + } + else{ + m_rlc_buf+=cnt0; + m_rlc_len-=cnt0+cnt; + m_cur_rlc_len=cnt; + if(cnt0){ + write1(0x80 | (cnt0<<4) | cnt); + } + else{ + write1(cnt); + } + return; + } + cnt=0; + if (i==m_rlc_len) break; + run0 = cur0; + } + cnt++; + } + + close: + + switch(m_write_step) { + case WRITE_START_STEP: + { + uint8_t fri=0; + eeFs.files[FILE_TMP].size = m_pos; + if (m_currBlk && ( fri = EeFsGetLink(m_currBlk))) { + uint8_t prev_freeList = eeFs.freeList; + eeFs.freeList = fri; + while( EeFsGetLink(fri)) fri = EeFsGetLink(fri); + m_write_step = WRITE_FREE_UNUSED_BLOCKS_STEP1; + EeFsSetLink(fri, prev_freeList); + return; + } + } + + case WRITE_FLUSH_STEP: // TODO could be avoided + m_write_step = WRITE_SWAP_STEP; + EeFsFlush(); //chained out + return; + + case WRITE_FREE_UNUSED_BLOCKS_STEP1: + m_write_step = WRITE_FREE_UNUSED_BLOCKS_STEP2; + EeFsSetLink(m_currBlk, 0); + return; + + case WRITE_FREE_UNUSED_BLOCKS_STEP2: + m_write_step = WRITE_FLUSH_STEP; + EeFsFlushFreelist(); + return; + + case WRITE_SWAP_STEP: + m_write_step = 0; + EFile::swap(m_fileId, FILE_TMP); + } +} + +void RlcFile::flush() +{ + while (eeprom_buffer_size > 0); + s_sync_write = true; + while (theFile.m_write_len && !s_write_err) + theFile.nextWriteStep(); + while (theFile.isWriting() && !s_write_err) + theFile.nextRlcWriteStep(); + s_sync_write = false; +} + +#else + +void RlcFile::create(uint8_t i_fileId, uint8_t typ, uint16_t maxTme10ms) +{ + openRlc(i_fileId); //internal use + eeFs.files[i_fileId].typ = typ; + eeFs.files[i_fileId].size = 0; + m_stopTime10ms = get_tmr10ms() + maxTme10ms; +} + +// G: Write runlength (RLE) compressed bytes +uint16_t RlcFile::writeRlc(uint8_t i_fileId, uint8_t typ,uint8_t*buf,uint16_t i_len, uint8_t maxTme10ms){ + + create(i_fileId,typ,maxTme10ms); + bool run0 = buf[0] == 0; + uint8_t cnt = 1; + uint8_t cnt0 = 0; + uint16_t i = 0; + if(i_len==0) goto close; + + //RLE compression: + //rb = read byte + //if (rb | 0x80) write rb & 0x7F zeros + //else write rb bytes + for( i=1; 1 ; i++) // !! laeuft ein byte zu weit !! + { + bool cur0 = buf[i] == 0; + if(cur0 != run0 || cnt==0x3f || (cnt0 && cnt==0xf)|| i==i_len){ + if(run0){ + assert(cnt0==0); + if(cnt<8 && i!=i_len) + cnt0 = cnt; //aufbew fuer spaeter + else { + if( write1(cnt|0x40)!=1) goto error;//-cnt&0x3f + } + }else{ + if(cnt0){ + if( write1(0x80 | (cnt0<<4) | cnt)!=1) goto error;//-cnt0xx-cnt + cnt0 = 0; + }else{ + if( write1(cnt) !=1) goto error;//-cnt + } + uint8_t ret=write(&buf[i-cnt],cnt); + if( ret !=cnt) { cnt-=ret; goto error;}//-cnt + } + cnt=0; + if(i==i_len) break; + run0 = cur0; + } + cnt++; + } + if(0){ + error: + i-=cnt+cnt0; +#ifdef SIMU + switch(s_write_err){ + default: + case ERR_NONE: + assert(!"missing errno"); + break; + case ERR_FULL: + printf("ERROR filesystem overflow! written: %d missing: %d\n",i,i_len-i); + break; + case ERR_TMO: + printf("ERROR filesystem write timeout %d 0ms\n",(int16_t)(m_stopTime10ms - g_tmr10ms)); + break; + } +#endif + } + close: + close(); + return i; +} + +#endif + +// G: Close file and truncate at this blk. Add any remaining blocks to freeList chain +void RlcFile::close() +{ + uint8_t fri=0; + + if(m_currBlk && ( fri = EeFsGetLink(m_currBlk))) EeFsSetLink(m_currBlk, 0); + +#ifdef ASYNC_WRITE + eeFs.files[FILE_TMP].size = m_pos; + EFile::swap(m_fileId, FILE_TMP); +#else + eeFs.files[m_fileId].size = m_pos; + EeFsFlush(); //chained out +#endif + + if(fri) EeFsFree( fri ); //chain in + +#ifdef ASYNC_WRITE + m_write_step = 0; + s_sync_write = false; +#endif +} + + diff --git a/src/file.h b/src/file.h new file mode 100644 index 000000000..abcd63db0 --- /dev/null +++ b/src/file.h @@ -0,0 +1,174 @@ +#ifndef file_h +/* + * Authors (alphabetical order) + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * + * gruvin9x is based on code named er9x by + * Author - Erez Raviv , which is in turn + * was based on the original (and ongoing) project by Thomas Husterer, + * th9x -- http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + */ + +#define file_h + +#include + +// +// bs=16 128 blocks verlust link:128 16files:16*8 128 sum 256 +// bs=32 64 blocks verlust link: 64 16files:16*16 256 sum 320 +// +#if defined(PCBV3) +#define EESIZE 4096 +#define BS 32 +#else +#define EESIZE 2048 +#define BS 16 +#endif + +/// fileId of general file +#define FILE_GENERAL 0 +/// convert model number 0..MAX_MODELS-1 int fileId +#define FILE_MODEL(n) (1+n) +#define FILE_TMP (1+16) + +bool EeFsOpen(); +int8_t EeFsck(); +void EeFsFormat(); +uint16_t EeFsGetFree(); + +#ifdef ASYNC_WRITE +extern volatile size_t eeprom_buffer_size; +#endif + +class EFile +{ + public: + + ///remove contents of given file + static void rm(uint8_t i_fileId); + + ///swap contents of file1 with them of file2 + static void swap(uint8_t i_fileId1,uint8_t i_fileId2); + + ///return true if the file with given fileid exists + static bool exists(uint8_t i_fileId); + + ///return size of compressed file without block overhead + uint16_t size(); // TODO static ? + + ///open file for reading, no close necessary + void openRd(uint8_t i_fileId); + + uint8_t read(uint8_t*buf, uint16_t i_len); + + protected: + + uint8_t m_fileId; //index of file in directory = filename + uint16_t m_pos; //over all filepos + uint8_t m_currBlk; //current block.id + uint8_t m_ofs; //offset inside of the current block +}; + +#define ERR_NONE 0 +#define ERR_FULL 1 +#ifndef ASYNC_WRITE +#define ERR_TMO 2 +#endif +extern uint8_t s_write_err; // error reasons +extern uint8_t s_sync_write; + +///deliver current errno, this is reset in open +inline uint8_t errno() { return s_write_err; } + +class RlcFile: public EFile +{ + uint8_t m_bRlc; // control byte for run length decoder + uint8_t m_zeroes; + +#ifdef ASYNC_WRITE + uint8_t m_flags; +#define WRITE_FIRST_LINK 0x01 +#define WRITE_NEXT_LINK_1 0x02 +#define WRITE_NEXT_LINK_2 0x03 +#define WRITE_START_STEP 0x10 +#define WRITE_FREE_UNUSED_BLOCKS_STEP1 0x20 +#define WRITE_FREE_UNUSED_BLOCKS_STEP2 0x30 +#define WRITE_FLUSH_STEP 0x40 +#define WRITE_SWAP_STEP 0x50 + uint8_t m_write_step; + + uint16_t m_rlc_len; + uint8_t * m_rlc_buf; + uint8_t m_cur_rlc_len; + uint8_t m_write1_byte; + uint8_t m_write_len; + uint8_t * m_write_buf; +#else + uint16_t m_stopTime10ms; // maximum point of time for writing +#endif + +public: + + void openRlc(uint8_t i_fileId); + + +#ifdef ASYNC_WRITE + void create(uint8_t i_fileId, uint8_t typ, uint8_t sync_write); + inline bool isWriting() { return m_write_step != 0; } + void write(uint8_t*buf, uint8_t i_len); + void write1(uint8_t b); + void nextWriteStep(); + void nextRlcWriteStep(); + void writeRlc(uint8_t i_fileId, uint8_t typ, uint8_t *buf, uint16_t i_len, uint8_t sync_write); + void flush(); +#else + /// create a new file with given fileId, + /// !!! if this file already exists, then all blocks are reused + /// and all contents will be overwritten. + /// after writing close has to be called + + void create(uint8_t i_fileId, uint8_t typ, uint16_t maxTme10ms); + + uint8_t write(uint8_t*buf, uint8_t i_len); + uint8_t write1(uint8_t b); + ///open file, write to file and close it. + ///If file existed before, then contents is overwritten. + ///If file was larger before, then unused blocks are freed + uint16_t writeRlc(uint8_t i_fileId, uint8_t typ,uint8_t*buf,uint16_t i_len, uint8_t maxTme10ms); +#endif + + /* + * close file and truncate the blockchain if to long. + */ + void close(); + + ///read from opened file and decode rlc-coded data +#ifdef TRANSLATIONS + uint16_t readRlc12(uint8_t*buf,uint16_t i_len,bool rlc2); + inline uint16_t readRlc1(uint8_t*buf,uint16_t i_len) + { + return readRlc12(buf,i_len,false); + } + inline uint16_t readRlc(uint8_t*buf, uint16_t i_len) + { + return readRlc12(buf,i_len,true); + } +#else + uint16_t readRlc(uint8_t*buf, uint16_t i_len); // TODO should be like writeRlc? +#endif + +}; + +#endif +/*eof*/ diff --git a/src/font.lbm b/src/font.lbm new file mode 100644 index 000000000..74193796b --- /dev/null +++ b/src/font.lbm @@ -0,0 +1,9 @@ +prog_uchar APM font[] = { +80,8,80, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6f,0x00,0x00,0x07,0x00,0x07,0x00,0x14,0x7f,0x14,0x7f,0x14,0x24,0x2a,0x7f,0x2a,0x12,0x23,0x13,0x08,0x64,0x62,0x36,0x49,0x55,0x22,0x50,0x00,0x05,0x03,0x00,0x00,0x00,0x1c,0x22,0x41,0x00,0x00,0x41,0x22,0x1c,0x00,0x14,0x08,0x3e,0x08,0x14,0x08,0x08,0x3e,0x08,0x08,0x00,0x50,0x30,0x00,0x00,0x08,0x08,0x08,0x08,0x08,0x00,0x60,0x60,0x00,0x00,0x20,0x10,0x08,0x04,0x02, +0x00,0x3e,0x41,0x41,0x3e,0x00,0x00,0x42,0x7f,0x40,0x00,0x62,0x51,0x49,0x46,0x00,0x41,0x49,0x49,0x36,0x00,0x18,0x14,0x12,0x7f,0x00,0x27,0x45,0x45,0x39,0x00,0x3e,0x49,0x49,0x32,0x00,0x01,0x79,0x05,0x03,0x00,0x36,0x49,0x49,0x36,0x00,0x06,0x49,0x29,0x1e,0x00,0x00,0x36,0x36,0x00,0x00,0x00,0x56,0x36,0x00,0x00,0x08,0x14,0x22,0x41,0x14,0x14,0x14,0x14,0x14,0x41,0x22,0x14,0x08,0x00,0x02,0x01,0x51,0x09,0x06, +0x06,0x09,0x09,0x06,0x00,0x7e,0x09,0x09,0x09,0x7e,0x7f,0x49,0x49,0x49,0x36,0x3e,0x41,0x41,0x41,0x22,0x7f,0x41,0x41,0x22,0x1c,0x7f,0x49,0x49,0x49,0x41,0x7f,0x09,0x09,0x09,0x01,0x3e,0x41,0x49,0x49,0x7a,0x7f,0x08,0x08,0x08,0x7f,0x00,0x41,0x7f,0x41,0x00,0x20,0x40,0x41,0x3f,0x01,0x7f,0x08,0x14,0x22,0x41,0x7f,0x40,0x40,0x40,0x40,0x7f,0x02,0x0c,0x02,0x7f,0x7f,0x04,0x08,0x10,0x7f,0x3e,0x41,0x41,0x41,0x3e, +0x7f,0x09,0x09,0x09,0x06,0x3e,0x41,0x51,0x21,0x5e,0x7f,0x09,0x19,0x29,0x46,0x26,0x49,0x49,0x49,0x32,0x01,0x01,0x7f,0x01,0x01,0x3f,0x40,0x40,0x40,0x3f,0x1f,0x20,0x40,0x20,0x1f,0x3f,0x40,0x30,0x40,0x3f,0x63,0x14,0x08,0x14,0x63,0x07,0x08,0x70,0x08,0x07,0x61,0x51,0x49,0x45,0x43,0x00,0x7f,0x41,0x41,0x00,0x02,0x04,0x08,0x10,0x20,0x00,0x41,0x41,0x7f,0x00,0x04,0x02,0x01,0x02,0x04,0x40,0x40,0x40,0x40,0x40, +0x00,0x01,0x02,0x04,0x00,0x20,0x54,0x54,0x54,0x78,0x7f,0x48,0x44,0x44,0x38,0x38,0x44,0x44,0x44,0x20,0x38,0x44,0x44,0x48,0x7f,0x38,0x54,0x54,0x54,0x18,0x08,0x7e,0x09,0x01,0x02,0x0c,0x52,0x52,0x52,0x3e,0x7f,0x08,0x04,0x04,0x78,0x00,0x44,0x7d,0x40,0x00,0x20,0x40,0x44,0x3d,0x00,0x7f,0x10,0x28,0x44,0x00,0x00,0x41,0x7f,0x40,0x00,0x7c,0x04,0x18,0x04,0x78,0x7c,0x08,0x04,0x04,0x78,0x38,0x44,0x44,0x44,0x38, +0x7c,0x14,0x14,0x14,0x08,0x08,0x14,0x14,0x18,0x7c,0x7c,0x08,0x04,0x04,0x08,0x48,0x54,0x54,0x54,0x20,0x04,0x3f,0x44,0x40,0x20,0x3c,0x40,0x40,0x20,0x7c,0x1c,0x20,0x40,0x20,0x1c,0x3c,0x40,0x20,0x40,0x3c,0x44,0x28,0x10,0x28,0x44,0x0c,0x50,0x50,0x50,0x3c,0x44,0x64,0x54,0x4c,0x44,0x00,0x08,0x36,0x41,0x00,0x00,0x00,0x7f,0x00,0x00,0x00,0x41,0x36,0x08,0x00,0x08,0x08,0x22,0x1c,0x08,0x08,0x1c,0x22,0x08,0x08, +}; diff --git a/src/font_6x1.png b/src/font_6x1.png new file mode 100644 index 0000000000000000000000000000000000000000..d0d7fdf28d927598325770e3e82e50b51b43dbd8 GIT binary patch literal 6732 zcmV-S8nfkzP)KLZ*U+ghZh-Bq^1YD74Rm@A>xo zpR?9^aMt=i`M)2%)_dLe`?{`2_q{Fv-ts^iEe2%+h>fEsxLBKsdU^YZ5+;BIWMBb3 z&<_kwq?tN7Il%u@KgR$80C=txNTbET|DV`HLKA}l5GMc#3P}u(1wd{9z;ZAxfewJ4 z2Y{huI*ks1p#uOl!OPnR0CN}ssNw%`B>+%^{^2VCKuvISF$cg501(N6feGONq#*zh z-4h&62Oup2;5p(#BI5u!umE_Y;E2Ew0A33KP>hX@4*}q%0f21Kf5{2|KQe>Og^luFg03gT3>~H`8A^>~{3AC{PmuWFhfBn!nch`S8 zE@+42zZ{le;9PO^6JSpo^N)XfdIRqPB6t z%8H`us%on6KS^HRKBE87cNaha0O5Z2A1^@xKwk*}<@k>m-~!NI2f(KEA5YR7fT9f0 z`z1IjVb4Dv761ST%)km%-~(Zh09jB1bJ)j>^Bd(;ixg@&WM(YO~rNC+g*3Auz*gl58R!U$oWuudcqMTqJ| zE20lEj+jj>BQ_Fyh$F-W;sz5NlN6I4lOt0IQz}yt(|M*&re{p^OdHJX%reY|%x=t4 z%vsFk%$J$(GEXsoCy_|vBwdm-DUy^)DkohbJs{1HevvuI@?>-JPI40YD7l{8OMXTE zPN7hwDJB#j3Y~J4a*=X}@`kd`!pWk{V#^Z3lEG5J(#A5v@`aU&Rf^S=)sJ;AYZ>cx z)@Q6sY)ov@Z02l%Z2Q?N*lx0ov#qjova7K>vd6L)vNy6nWdFcH;E?99M|Dx*ETK>t`x2cu5PYrZVb0H zw=H)JcM!JePS!c-D9YcujaidGmQ&cwg}T<`d%wCx$nJHO{tg-BF*&5kVIlP>XT(sO7xgmK}UQ<3ozC!+)0;-^;5UFrRVOSBT zsH+&KSfe6Y+N0E@G^5O|?4X>j+^+mZMN-8_<(SHSl|QPQsxhkPRVURr)a=x< z)jHKy)aBKK)hpCTH7FWZ8W|cL8sD}lYzy6XcH2u$PR;F_hcx>&x3sji60|OBEow_@ z2WVGnkLyr%oOB9x9_Zq9&2%$#yL5l)Y3b4RuIYW%SJIExZ`A)_AY%|}aNc0fP~0%k zu-b6iNYp66=$z5Cv6yk7agFh;iG)drNu9}pshnw)X|w4UGgY$$vsSY;b3OBY=Dije z3u}vfi$O~cOLxoDmT#=Yt-`IEtd^}ct<$V~ZE!aBHpgtnZG~)uZ7GIwx4y7cSvyP+>UOy+kSle)DEc~aXZ=_!O_<7xZ`Ul8K>P&x190LPREp&!3rj9<4utAC*X^#ELeXFy}XW}stWP2g&f zZBS*2{RCLyIEi=l?0C7}yphG8XPAHog8kB2Wt7)O*vEJd0}o{sz$WfN5u zwHECdT_4Se@r-GSCB_EC-i+gji;f$J7miPfAEqhLa%t1M^>&}wy_{g5P?v}#?n>;S zQ|UDNlO)-sgGn=cjP_LQSx@#zzMjIK5|{EMRX#N@bz!g7-V15iwBWSOXN`-ArPXGmn^WXxw;XEtOpXGLc{%~s7W$^LP`^FU{gP)=sf+(GMuO}Q+&3Ay8k z^bb`X#vYD5Jea4EcPj5sen|eK0@Z@Dg3ZF;d&%CCx#Qc9x0Q;P=9m6B;eX;`*|xH(lguZRPEMCQly{txJXL&Z^K``N7Zqj| zEtP_md6jEtLe7kwH932^N~o%!>i4vcpZ7R_|ANkii*@{U z1$A5XarM&;E)4@0wJ%<56l^SNLYtDB7MpiA4_&gj)Nxtqa&-%LOTiVml62+c)qtyG z*X*zLU)Q;Q`G(Yu%2v+S{5GU5wQadQqJ6rHF{C@xIczZ8Gh#f__uS(7y;0lI$1irg7#?#U zdpW*yeEMa`%MY(&UVWR`GqEw5Ifa|bf6ex~?2X`?nzwRqTc)+9yJswBp3J(=PR<3- zeR`MpZgW28J>`Abg2=+f59%Mf7OfVCKKg!~|3v$=v6TCn{d46P*)KP~ntXk-?6o}q zE#VturSLo7_xc~(e)O+8t-kpg`*UM0f1Pi=;g`;@`@cPZFKi@l5;n`X$zyU;$G&hX^BQP+jyB<`Omw z7mW8HcoBn`vY9JMcgbrkLadH#``BAJ#yB^*sN9M?y1b5je*AQSLct3{w}r<<=0rb> zeG^}nSd^TV8kc@7(<9p=S1n(nkflgd@>6zHF;Z1klT{be;M&HbiPik2^-24U&aiI3 z-ZlLigA&6GqgdmeCU&M;W)kMC=D#dvEFW53u`aVow+*zjw3l@tIV^2|w&S{Exl^Wd ztjkVU2RB1^B@aPQX3sURIqzpaw|wh%9@~}X7wYd4U>>Lwq!z3gA`>bRCLGQa!5+yR zg+#4Ke~p=sor)WcAEFKIev$Axae=;)w3SRqVNVsX;asjR5s+Vgpyy(~%)RfeG=u-LRrk3t2!&eusZQY=@Dzurl zdv?U!%g4{tx(e4_I-VX*1h>@equ+4H#3 zlP_+My?;r5r9R;~nLbth`rezRX|5Ub*`&FqceC&L792kuT)g@5%aZtKw=Y>=FD&2s z*1OXBz52(Y)u^8iYs%|v>np!Tf46OvZ|>dl{bR@wW-tJN3_6g9pa>nsq4gMltSk<2 zv-mfJapEk~PZCNNp_s4)vSzc@u=jCHa4u3;xiLIMUS2*){%ry_f?h(Q!f_%r(MYjC zac2o*NhK*>X^{RTGb(#au15Z_LcF4zl8&;F3aYZGI-=IC-k?#sElV>-%U#<@M^=|j zcU5mvzt5n~u)rwZ*u_NCl*ja!*(>u7i<6dntlX{DZCGqR+TOK0XP@TazFlPpamSov zuhTi_9G6H}XE$wk5f8k_XU`F@cJB(G4Bz0L*1P2WD1Iyc69M-ETZ8I@D?^Gy4~C_N z$3+B2x%_>#3dB+3(fD7qAG=o)ek88b|Lj4N2`Qvhmc49goatQqxc75sP&3)G zh*?|N%Lit1h7b1Uwj4ThxG*m@KfJ)L(DaD%QNbc|F%+*KTP;~VzFxXjhMdHeGo7NG zW~<<+q@Lk9%U>mYPP$sP#<qITe-*Pc0_Mc-|hZi1NwI}?%jJpdg%P<mh7lVg~SBKAoKUtt$uvO@h@Px>W=(N~`_*02K z$!k(I(j_ukvNX9}@-_+@iXut`rEkiUDtA>|)GF2UG?KT4X}V|`Y0K$w>TK%H>pj)K zVNhY1WfW>`Yoch%V*17GsdJ9NQOn^f^{IB|Ce# zXuI;cZo0j7@AIhfJm3}LZQ&!~i~3IQ?AcZ6m*&4Sz$8#Qh%IO<_+!Y+(7Rz*!_P+K zMbe}Eqitg}Vuj*}aX;ecXk)vdB-~5vr}re?+S8VNE#*>b!`_;-iuBTbNA~Arq-I8E zd1qT3P|D%T**rLx`|wc9;nKX+e7^#-Lg^#SN4_3?QPfl1bnI-&iQ`431t)ULvQDOz zC!LBv9Z?Zb>2=2WtX-AmIg4tm8i!i<^8pv4>-N-VH56PdYdqJ~*nHzs&*l3q&#z2h z{d$dYgRNDpO}*W`!|i5h=bl@+U1i-3Jzck7_HOkn-w7KyfA{@;*#}V%n;(6CqVzO& zu=&}?VW|=S=O;%8$8h6%FKMspCSFf*ytaFj^Y+dRW!8VLb6(*6{)M?k>yOP#?4MJ< zyj`~a*7{xiNAW6SEp~nCx7|kjmeijT1_R)qJqQ2*UJKy-8i2FEAW9NsDdHbfj5LhG8~3e&;+;PIV=JL;X%|82P6u~ zLz#}x*PQLCwy#)OzgHXd@qi|!I zNv3I;*;Vr)i|nowi)GT%+C2xDR?TywttJeX4vXck%oA z`kxE<5TqWwKjdK;XSh#9b>u>{a!hh;e>^MAfA_US0^KjEBbh5DG4*+xMtbSK)eNu9 z?rf<8MLAo!v4>{zJoASN?T_3qGA(W^kvv{d`l-zNWOe!P)9w{l&#<12uNtn_tf@SY zzd)~h-{5y~s>#23;WGUS=4#n>g&TL;cD4V$dHR+?*V~@#Udg`xJG%x}?-f3fc-ZyW z>&e<+`H;cz?DPB=nq%)@)=bc*Jl_ON7t9XN%PizBE-pEK>G&r3z4RwxJ>&PrRvLo= z00dwML68MaFb8J{gx!!0C!hhkU>H6C1K~q75GN!aDMqd#VvR-j}n)e+?YCB*Cq!yRX<{bgoZ^cT{gP*;_DZ|UsK_$O&dT-3S1RmN3{bLGR#OpHWl=-aeyV@g zc(-jrb5N^K`-V=v?isxk`o#uE4U3FQjLS``O)r^sn~zw0wqmi?v)7p#y4bsxxxMqy@XYdh?8ENswzF*4OMiiYpunb}wGgY&^03blmXT+pe#N-PHpinh zkKK(447z91m1J^COzL2odiv@8xQv9%>1@XXJqML?D-V{46*iUE&vIAM&kfZm)E+uNUuRKYcafE)IyqE}B`XS;E zrby?-F0Jmnw>^75_Gb)m-)+8c`e5Wyz~c{3qXs_?MGP-I4;x(=iyZ&*Drw@+RQ?-* zx7TMp=J4+Z-WPv}{OGWx^+jTtx>eep?0|1D?38J73hOiy{ARe;e z1T?~Jcmbag93q0~As$E)Qi`-8FOf}D6tzNQ(G%!x^b>|3V~@$ibYQ+?)v*cKHtYt@ z9CrjaftSaph;K)9TY2Gu!4g-)YTiPwTwaoz$Ds zpEg)9TsGP=A)AVrX_{}hh_XCl)oT6TR>;oVzQSR8hn!=Q(_I%nSDM>n4>`{Qug^Y? zzRkO^eqR2K0b4u<-Clk*ZOrWAL%`xOX&{-93pi^dm_t$zCbFd*#zk?j!6^ z&G?)Zn*BD%_u#}K@53+iJqyN;cpQCMyz|)X@#xZ@WjW>4Q}q=FmBVL4tG-ny)_gke zd*NQaazp9G&8D#C;mgJ?EmuXamEIt==CngcW+&!Wem7T7O|M$tojaZbOZN^u5PaDF z*!9Wh!Q3JK;S0~@M(f8!$ID)kC$c8jU(?i__FHh{`50DyZD;VJ+3j`V*4qoY><9o6Na0000WV@Og>003>600482 z008+B004xf00486008k7001x#000p4A1pT#000ADNkl3CT{vTUcFc9O$RHV@l?0D;q9uh&r>t+ngcb%OqeCy(Rs1BNU4fjrUi!^gS#{v|_e z?K<(-F99y?To9~}K%TkW|fo)_s_Yd?Y*=)5#(m)Xm_6g&F zTO^?}MLBYiIXeZp8-|4zMz4k6ZsC40NSiIDTceNMvExDHH4-33G<$ONmRj&j@p5|O z1pAchD4o_%K~&Gfn_obfeJf~r#pgwIjby<}nM;>OfAx?a@&5~%;7=WfGGy?^ei!AK zY{{IQ`CXK#MQ`U<`lME-{*V>?T@;Nu@bbGuZlkE91UBMDEDsN32GM9HA~(o#FbN|{=P+%_AuND9cW*n*!B{3EaoIfjq(K5ipOm*j`c*#Fu>R4BEc ziN_{Thj}}25^VvZktt<(#JoldBFC7NmJ+ovGH8IiiC}ft`*Mf*Cy!@6UL^jZ7?Ia(S(jvgfNU(Fo(92A-j~P?SFt7wa*~ds4IODJJ(PhR zfpBvVX^jQiaED>GWL4Io003YJ0ssI26-DmO00009a7bBm000XU z000XU0RWnu7ytkkXK6!2P*7-ZbZ>KLZ*U+ghZh-Bq^1YD74Rm@A>xo zpR?9^aMt=i`M)2%)_dLe`?{`2_q{Fv-ts^iEe2%+h>fEsxLBKsdU^YZ5+;BIWMBb3 z&<_kwq?tN7Il%u@KgR$80C=txNTbET|DV`HLKA}l5GMc#3P}u(1wd{9z;ZAxfewJ4 z2Y{huI*ks1p#uOl!OPnR0CN}ssNw%`B>+%^{^2VCKuvISF$cg501(N6feGONq#*zh z-4h&62Oup2;5p(#BI5u!umE_Y;E2Ew0A33KP>hX@4*}q%0f21Kf5{2|KQe>Og^luFg03gT3>~H`8A^>~{3AC{PmuWFhfBn!nch`S8 zE@+42zZ{le;9PO^6JSpo^N)XfdIRqPB6t z%8H`us%on6KS^HRKBE87cNaha0O5Z2A1^@xKwk*}<@k>m-~!NI2f(KEA5YR7fT9f0 z`z1IjVb4Dv761ST%)km%-~(Zh09jB1bJ)j>^Bd(;ixg@&WM(YO~rNC+g*3Auz*gl58R!U$oWuudcqMTqJ| zE20lEj+jj>BQ_Fyh$F-W;sz5NlN6I4lOt0IQz}yt(|M*&re{p^OdHJX%reY|%x=t4 z%vsFk%$J$(GEXsoCy_|vBwdm-DUy^)DkohbJs{1HevvuI@?>-JPI40YD7l{8OMXTE zPN7hwDJB#j3Y~J4a*=X}@`kd`!pWk{V#^Z3lEG5J(#A5v@`aU&Rf^S=)sJ;AYZ>cx z)@Q6sY)ov@Z02l%Z2Q?N*lx0ov#qjova7K>vd6L)vNy6nWdFcH;E?99M|Dx*ETK>t`x2cu5PYrZVb0H zw=H)JcM!JePS!c-D9YcujaidGmQ&cwg}T<`d%wCx$nJHO{tg-BF*&5kVIlP>XT(sO7xgmK}UQ<3ozC!+)0;-^;5UFrRVOSBT zsH+&KSfe6Y+N0E@G^5O|?4X>j+^+mZMN-8_<(SHSl|QPQsxhkPRVURr)a=x< z)jHKy)aBKK)hpCTH7FWZ8W|cL8sD}lYzy6XcH2u$PR;F_hcx>&x3sji60|OBEow_@ z2WVGnkLyr%oOB9x9_Zq9&2%$#yL5l)Y3b4RuIYW%SJIExZ`A)_AY%|}aNc0fP~0%k zu-b6iNYp66=$z5Cv6yk7agFh;iG)drNu9}pshnw)X|w4UGgY$$vsSY;b3OBY=Dije z3u}vfi$O~cOLxoDmT#=Yt-`IEtd^}ct<$V~ZE!aBHpgtnZG~)uZ7GIwx4y7cSvyP+>UOy+kSle)DEc~aXZ=_!O_<7xZ`Ul8K>P&x190LPREp&!3rj9<4utAC*X^#ELeXFy}XW}stWP2g&f zZBS*2{RCLyIEi=l?0C7}yphG8XPAHog8kB2Wt7)O*vEJd0}o{sz$WfN5u zwHECdT_4Se@r-GSCB_EC-i+gji;f$J7miPfAEqhLa%t1M^>&}wy_{g5P?v}#?n>;S zQ|UDNlO)-sgGn=cjP_LQSx@#zzMjIK5|{EMRX#N@bz!g7-V15iwBWSOXN`-ArPXGmn^WXxw;XEtOpXGLc{%~s7W$^LP`^FU{gP)=sf+(GMuO}Q+&3Ay8k z^bb`X#vYD5Jea4EcPj5sen|eK0@Z@Dg3ZF;d&%CCx#Qc9x0Q;P=9m6B;eX;`*|xH(lguZRPEMCQly{txJXL&Z^K``N7Zqj| zEtP_md6jEtLe7kwH932^N~o%!>i4vcpZ7R_|ANkii*@{U z1$A5XarM&;E)4@0wJ%<56l^SNLYtDB7MpiA4_&gj)Nxtqa&-%LOTiVml62+c)qtyG z*X*zLU)Q;Q`G(Yu%2v+S{5GU5wQadQqJ6rHF{C@xIczZ8Gh#f__uS(7y;0lI$1irg7#?#U zdpW*yeEMa`%MY(&UVWR`GqEw5Ifa|bf6ex~?2X`?nzwRqTc)+9yJswBp3J(=PR<3- zeR`MpZgW28J>`Abg2=+f59%Mf7OfVCKKg!~|3v$=v6TCn{d46P*)KP~ntXk-?6o}q zE#VturSLo7_xc~(e)O+8t-kpg`*UM0f1Pi=;g`;@`@cPZFKi@l5;n`X$zyU;$G&hX^BQP+jyB<`Omw z7mW8HcoBn`vY9JMcgbrkLadH#``BAJ#yB^*sN9M?y1b5je*AQSLct3{w}r<<=0rb> zeG^}nSd^TV8kc@7(<9p=S1n(nkflgd@>6zHF;Z1klT{be;M&HbiPik2^-24U&aiI3 z-ZlLigA&6GqgdmeCU&M;W)kMC=D#dvEFW53u`aVow+*zjw3l@tIV^2|w&S{Exl^Wd ztjkVU2RB1^B@aPQX3sURIqzpaw|wh%9@~}X7wYd4U>>Lwq!z3gA`>bRCLGQa!5+yR zg+#4Ke~p=sor)WcAEFKIev$Axae=;)w3SRqVNVsX;asjR5s+Vgpyy(~%)RfeG=u-LRrk3t2!&eusZQY=@Dzurl zdv?U!%g4{tx(e4_I-VX*1h>@equ+4H#3 zlP_+My?;r5r9R;~nLbth`rezRX|5Ub*`&FqceC&L792kuT)g@5%aZtKw=Y>=FD&2s z*1OXBz52(Y)u^8iYs%|v>np!Tf46OvZ|>dl{bR@wW-tJN3_6g9pa>nsq4gMltSk<2 zv-mfJapEk~PZCNNp_s4)vSzc@u=jCHa4u3;xiLIMUS2*){%ry_f?h(Q!f_%r(MYjC zac2o*NhK*>X^{RTGb(#au15Z_LcF4zl8&;F3aYZGI-=IC-k?#sElV>-%U#<@M^=|j zcU5mvzt5n~u)rwZ*u_NCl*ja!*(>u7i<6dntlX{DZCGqR+TOK0XP@TazFlPpamSov zuhTi_9G6H}XE$wk5f8k_XU`F@cJB(G4Bz0L*1P2WD1Iyc69M-ETZ8I@D?^Gy4~C_N z$3+B2x%_>#3dB+3(fD7qAG=o)ek88b|Lj4N2`Qvhmc49goatQqxc75sP&3)G zh*?|N%Lit1h7b1Uwj4ThxG*m@KfJ)L(DaD%QNbc|F%+*KTP;~VzFxXjhMdHeGo7NG zW~<<+q@Lk9%U>mYPP$sP#<qITe-*Pc0_Mc-|hZi1NwI}?%jJpdg%P<mh7lVg~SBKAoKUtt$uvO@h@Px>W=(N~`_*02K z$!k(I(j_ukvNX9}@-_+@iXut`rEkiUDtA>|)GF2UG?KT4X}V|`Y0K$w>TK%H>pj)K zVNhY1WfW>`Yoch%V*17GsdJ9NQOn^f^{IB|Ce# zXuI;cZo0j7@AIhfJm3}LZQ&!~i~3IQ?AcZ6m*&4Sz$8#Qh%IO<_+!Y+(7Rz*!_P+K zMbe}Eqitg}Vuj*}aX;ecXk)vdB-~5vr}re?+S8VNE#*>b!`_;-iuBTbNA~Arq-I8E zd1qT3P|D%T**rLx`|wc9;nKX+e7^#-Lg^#SN4_3?QPfl1bnI-&iQ`431t)ULvQDOz zC!LBv9Z?Zb>2=2WtX-AmIg4tm8i!i<^8pv4>-N-VH56PdYdqJ~*nHzs&*l3q&#z2h z{d$dYgRNDpO}*W`!|i5h=bl@+U1i-3Jzck7_HOkn-w7KyfA{@;*#}V%n;(6CqVzO& zu=&}?VW|=S=O;%8$8h6%FKMspCSFf*ytaFj^Y+dRW!8VLb6(*6{)M?k>yOP#?4MJ< zyj`~a*7{xiNAW6SEp~nCx7|kjmeijT1_R)qJqQ2*UJKy-8i2FEAW9NsDdHbfj5LhG8~3e&;+;PIV=JL;X%|82P6u~ zLz#}x*PQLCwy#)OzgHXd@qi|!I zNv3I;*;Vr)i|nowi)GT%+C2xDR?TywttJeX4vXck%oA z`kxE<5TqWwKjdK;XSh#9b>u>{a!hh;e>^MAfA_US0^KjEBbh5DG4*+xMtbSK)eNu9 z?rf<8MLAo!v4>{zJoASN?T_3qGA(W^kvv{d`l-zNWOe!P)9w{l&#<12uNtn_tf@SY zzd)~h-{5y~s>#23;WGUS=4#n>g&TL;cD4V$dHR+?*V~@#Udg`xJG%x}?-f3fc-ZyW z>&e<+`H;cz?DPB=nq%)@)=bc*Jl_ON7t9XN%PizBE-pEK>G&r3z4RwxJ>&PrRvLo= z00dwML68MaFb8J{gx!!0C!hhkU>H6C1K~q75GN!aDMqd#VvR-j}n)e+?YCB*Cq!yRX<{bgoZ^cT{gP*;_DZ|UsK_$O&dT-3S1RmN3{bLGR#OpHWl=-aeyV@g zc(-jrb5N^K`-V=v?isxk`o#uE4U3FQjLS``O)r^sn~zw0wqmi?v)7p#y4bsxxxMqy@XYdh?8ENswzF*4OMiiYpunb}wGgY&^03blmXT+pe#N-PHpinh zkKK(447z91m1J^COzL2odiv@8xQv9%>1@XXJqML?D-V{46*iUE&vIAM&kfZm)E+uNUuRKYcafE)IyqE}B`XS;E zrby?-F0Jmnw>^75_Gb)m-)+8c`e5Wyz~c{3qXs_?MGP-I4;x(=iyZ&*Drw@+RQ?-* zx7TMp=J4+Z-WPv}{OGWx^+jTtx>eep?0|1D?38J73hOiy{ARe;e z1T?~Jcmbag93q0~As$E)Qi`-8FOf}D6tzNQ(G%!x^b>|3V~@$ibYQ+?)v*cKHtYt@ z9CrjaftSaph;K)9TY2Gu!4g-)YTiPwTwaoz$Ds zpEg)9TsGP=A)AVrX_{}hh_XCl)oT6TR>;oVzQSR8hn!=Q(_I%nSDM>n4>`{Qug^Y? zzRkO^eqR2K0b4u<-Clk*ZOrWAL%`xOX&{-93pi^dm_t$zCbFd*#zk?j!6^ z&G?)Zn*BD%_u#}K@53+iJqyN;cpQCMyz|)X@#xZ@WjW>4Q}q=FmBVL4tG-ny)_gke zd*NQaazp9G&8D#C;mgJ?EmuXamEIt==CngcW+&!Wem7T7O|M$tojaZbOZN^u5PaDF z*!9Wh!Q3JK;S0~@M(f8!$ID)kC$c8jU(?i__FHh{`50DyZD;VJ+3j`V*4qoY><9o6Na0000WV@Og>003>600482 z008+B004xf00486008k7001x#000p4A1pT#000MoNklav zU;PhtKfM8t0^Eh?{f6poAi%HNs$ahW#y^Ya*BcCH%(}R~04SxD@@ulbPbmuB=bz7? z4)4ST%oSw7zb-6X!OrNvhPbt?tM6$oVqXUMAo-797T{Oof99{wv+%4Ahl5u2ZMo=H z*D<9-TtO~A9*=|-d>8^`fg%7qc)ad+aLQU?X9;u=FwQQ%1oGopr2tF~kUs+)eVEe1 zvij29DZu?6(d4yf{jpjJFoJPc0&jpjD`A`%!5iJ01U|ZF@fd+^j))GF$NF#!8k7OP|-dh&?PEBIz*C))19dtV#i zZvq(cuy%D$kQSiqPJ{_TlroAA8)vg>`Ugvu9H~kl36Q&Q>9-)pRlBpRJmcR5pOww) zK{^+1(Nzr2Dre*jbl0Mg-8ih$M19xi$f!TF%v?&NW-YqcKMU^1#YMN!|EjN-eGhJZ zvXLk3XmpVi?4>SoTfM*FWLp-~>yQ`+mI{%g+Vo!waH~CC%U)RY`2e#by36+l*(-#~ zm?@&W-cL|51h?&EfD!MWKP6c)>+3<_mJg12T+d43$S4;5&lPw$@P2%;#)0`!{8W~+0Rnhm_q#U0-vO|u8L~cCTy!I8FTPRXe>WbA ziyL?mtsCD&Jby6TA22=89|eSGmI3a5cN)Mgj#gkloHl#?Z9#&sLcdEUrGSoR^W#oW z+%ESgR%`tTldSF!?%Ixy$ruqVJ_$D>tUTAN%~Gd0z#wM{_NW7JWHku84kge=z@G)M z>|yMNf3pT;M??#&AqY~O2wV=ZkTFcKm~Tw zZ+|PBvJbHF!KWMGB>-Eyltl+%mL&T`5j{=G3W{jWI-LjbWtFU%T69(zS0tTa8Ni=w zCTT^YB9?tbQce=lcOp6vhji|g#g|I!(+PF5oGsy1XR4)wA&QBt-^m){xenK}VbwQ- zmA2v(tkBAYxJ)y2_wW! zQp%tRu&F-X__G^eMC(MuSQAYh>e*ln_L5(ue1PLtvTq!xrw6dP zHY)+fXX~JeC5JHJc@KjUe3U%W2n{BP=qmt>sD4z%+w>H@M6q8GibPO4sI-Rp02@Y$ zTXt59k=YCh8OaDjxl9%^C<_6$SXN6UO@1K2S&ryPL4^?j{Np8eZXRgTcq1Mb*y(8js2pRbYz6shDFIYa7BXdDop zTb%+hMMHvzp$pNEC3pmvZ0QSSXTj6XC>N(t1`(%I40a^on~Rar`Jx>hDJ_7{Vy(6f zSd`qdnP=qORODOn9E6%nZ+A(8H3Q%pC9JsJG z<}f|v3<-R@VI#doGbvuHhP8a+2V;#ZMvCVLbcJ zADV>Mxok~^=NlsH`21$S9A;GblzE0%%ITu(PdjJZ?rv3A zp(^JCtes2{2`*>zgJs!}Vk6wj*|4wC*WUpEhEk=o6w&#B00000NkvXXu0mjf4$LV` literal 0 HcmV?d00001 diff --git a/src/font_dblsize.xbm b/src/font_dblsize.xbm new file mode 100644 index 000000000..5e39757fe --- /dev/null +++ b/src/font_dblsize.xbm @@ -0,0 +1,164 @@ +#define font_dblsize_width 160 +#define font_dblsize_height 96 +static unsigned char font_dblsize_bits[]={ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0x0c, + 0x33,0x30,0x18,0xc0,0x03,0x0f,0xc0,0x30,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0x0c,0x33,0x30,0x3c,0xe0, + 0x07,0x0f,0xe0,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xc0,0xc0,0x0c,0x33,0xfc,0x3f,0x7c,0x0c,0x0c,0x70,0xe0, + 0x00,0x03,0x0c,0x00,0x00,0x00,0x00,0xc0,0x00,0xc0,0xc0,0x0c, + 0x33,0xfe,0x1b,0x3e,0x0e,0x0e,0x38,0xc0,0x01,0x03,0x0c,0x00, + 0x00,0x00,0x00,0xe0,0x00,0xc0,0xc0,0xcc,0xff,0x33,0x00,0x37, + 0x07,0x07,0x1c,0x80,0x33,0x33,0x0c,0x00,0x00,0x00,0x00,0x70, + 0x00,0xc0,0x80,0xc4,0xff,0x33,0x80,0xb3,0x03,0x03,0x0c,0x00, + 0x73,0x3b,0x0c,0x00,0x00,0x00,0x00,0x38,0x00,0xc0,0x00,0x00, + 0x33,0xfe,0xc0,0xe1,0x01,0x00,0x0c,0x00,0xc3,0xcf,0xff,0x00, + 0xfc,0x0f,0x00,0x1c,0x00,0xc0,0x00,0x00,0x33,0xfc,0xe1,0xe0, + 0x01,0x00,0x0c,0x00,0xc3,0xcf,0xff,0x00,0xfc,0x0f,0x00,0x0e, + 0x00,0x00,0x00,0xc0,0xff,0x30,0x73,0xb0,0x33,0x00,0x0c,0x00, + 0x73,0x3b,0x0c,0x3c,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0xc0, + 0xff,0x30,0x3b,0x30,0x3f,0x00,0x1c,0x80,0x33,0x33,0x0c,0x3c, + 0x00,0x00,0x80,0x03,0x00,0x00,0x00,0x00,0x33,0xff,0x1d,0x36, + 0x0c,0x00,0x38,0xc0,0x01,0x03,0x0c,0x30,0x00,0x80,0xc1,0x01, + 0x00,0xc0,0x00,0x00,0x33,0xff,0x0c,0x7f,0x0c,0x00,0x70,0xe0, + 0x00,0x03,0x0c,0x38,0x00,0xc0,0xc3,0x00,0x00,0xc0,0x00,0x00, + 0x33,0x30,0x00,0xef,0x3f,0x00,0xe0,0x70,0x00,0x00,0x00,0x1c, + 0x00,0xc0,0x03,0x00,0x00,0xc0,0x00,0x00,0x33,0x30,0x00,0xc6, + 0x33,0x00,0xc0,0x30,0x00,0x00,0x00,0x0c,0x00,0x80,0x01,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xf0,0xc0,0x00,0x0f,0x3f,0x00,0xf3,0x0f, + 0x0f,0xff,0xf0,0xc0,0x03,0x00,0x00,0x00,0x03,0x30,0x00,0x3f, + 0xf8,0xe1,0x80,0x1f,0x7f,0x80,0xf3,0x8f,0x1f,0xff,0xf8,0xe1, + 0x07,0x00,0x00,0x80,0x03,0x70,0x80,0x7f,0x9c,0xf3,0xc0,0x39, + 0xe0,0xc0,0x33,0xc0,0x39,0xc0,0x9c,0x73,0x8e,0x01,0x06,0xc0, + 0x01,0xe0,0xc0,0xe1,0x0c,0xf3,0xc0,0x30,0xc0,0xe0,0x33,0xc0, + 0x30,0xe0,0x0c,0x33,0xcc,0x03,0x0f,0xe0,0x00,0xc0,0xc1,0xc0, + 0x0c,0xc3,0x00,0x30,0xc0,0x70,0xf3,0xc3,0x00,0x70,0x0c,0x33, + 0xcc,0x03,0x0f,0x70,0xfc,0x8f,0x03,0xc0,0x0c,0xc3,0x00,0x38, + 0xe0,0x38,0xf3,0xc7,0x00,0x38,0x9c,0x73,0x8e,0x01,0x06,0x38, + 0xfc,0x0f,0x07,0xe0,0x0c,0xc3,0x00,0x1c,0x7c,0x1c,0x03,0xce, + 0x0f,0x1c,0xf8,0xe1,0x0f,0x00,0x00,0x1c,0x00,0x00,0x0e,0x70, + 0x0c,0xc3,0x00,0x0e,0x7c,0x0c,0x03,0xcc,0x1f,0x0c,0xf8,0xc1, + 0x0f,0x00,0x00,0x1c,0x00,0x00,0x0e,0x38,0x0c,0xc3,0x00,0x07, + 0xe0,0xfc,0x03,0xcc,0x38,0x0c,0x9c,0x03,0x8c,0x01,0x0f,0x38, + 0xfc,0x0f,0x07,0x1c,0x0c,0xc3,0x80,0x03,0xc0,0xfc,0x03,0xcc, + 0x30,0x0c,0x0c,0x03,0xce,0x03,0x0f,0x70,0xfc,0x8f,0x03,0x0c, + 0x0c,0xc3,0xc0,0x01,0xc0,0x00,0x33,0xcc,0x30,0x0c,0x0c,0x03, + 0xc7,0x03,0x0c,0xe0,0x00,0xc0,0x01,0x00,0x9c,0xc3,0xc0,0x00, + 0xe0,0x00,0x73,0xce,0x39,0x0c,0x9c,0x83,0x83,0x01,0x0e,0xc0, + 0x01,0xe0,0x00,0x00,0xf8,0xf1,0xc3,0x3f,0x7f,0x00,0xe3,0x87, + 0x1f,0x0c,0xf8,0xc1,0x01,0x00,0x07,0x80,0x03,0x70,0x00,0x0c, + 0xf0,0xf0,0xc3,0x3f,0x3f,0x00,0xc3,0x03,0x0f,0x0c,0xf0,0xc0, + 0x00,0x00,0x03,0x00,0x03,0x30,0x00,0x0c,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x3c,0xc0,0xf0,0x0f,0x3f,0x3f,0xfc,0xff,0x3f,0x3f,0x03,0xf3, + 0x03,0xff,0xc0,0x03,0x0c,0x3c,0x30,0x3f,0x7e,0xe0,0xf1,0x9f, + 0x7f,0x7f,0xfc,0xff,0xbf,0x7f,0x03,0xf3,0x03,0xff,0xe0,0x03, + 0x1c,0x3e,0xb0,0x7f,0xe7,0xf0,0x33,0xf8,0xe1,0xe3,0x0c,0x30, + 0xc0,0xe1,0x03,0xc3,0x00,0xcc,0x70,0x03,0x3c,0x3f,0xf0,0xe1, + 0xc3,0x38,0x37,0xf0,0xc0,0xc3,0x0d,0x30,0xc0,0xc0,0x03,0xc3, + 0x00,0xcc,0x38,0x03,0xfc,0x7f,0xf0,0xc0,0xc3,0x1c,0x3e,0xf0, + 0x00,0x83,0x0f,0x30,0xc0,0x00,0x03,0xc3,0x00,0xcc,0x1c,0x03, + 0xec,0xfd,0xf0,0xc0,0xe7,0x0c,0x3c,0xf8,0x00,0x03,0x0f,0x30, + 0xc0,0x00,0x03,0xc3,0x00,0xcc,0x0e,0x03,0xcc,0xfc,0xf1,0xc0, + 0x7e,0xfc,0xff,0xdf,0x00,0x03,0xff,0xf3,0xcf,0xfc,0xff,0xc3, + 0x00,0xcc,0x07,0x03,0xcc,0xbc,0xf3,0xc0,0x3c,0xfc,0xff,0xdf, + 0x00,0x03,0xff,0xf3,0xcf,0xfc,0xff,0xc3,0x00,0xcc,0x07,0x03, + 0xcc,0x3c,0xf7,0xc0,0x00,0x0c,0x3c,0xf8,0x00,0x03,0x0f,0x30, + 0xc0,0xc0,0x03,0xc3,0x00,0xcc,0x0e,0x03,0x0c,0x3c,0xfe,0xc0, + 0x00,0x0c,0x3c,0xf0,0x00,0x83,0x0f,0x30,0xc0,0xc0,0x03,0xc3, + 0x00,0xcc,0x1c,0x03,0x0c,0x3c,0xfc,0xc0,0x00,0x0c,0x3c,0xf0, + 0xc0,0xc3,0x0d,0x30,0xc0,0xc0,0x03,0xc3,0x30,0xcc,0x38,0x03, + 0x0c,0x3c,0xf8,0xc0,0x00,0x0c,0x3c,0xf8,0xe1,0xe3,0x0c,0x30, + 0xc0,0xe1,0x03,0xc3,0x70,0xce,0x70,0x03,0x0c,0x3c,0xf0,0xe1, + 0x00,0x0c,0xfc,0x9f,0x7f,0x7f,0xfc,0x3f,0x80,0xff,0x03,0xf3, + 0xe3,0xc7,0xe0,0xff,0x0f,0x3c,0xb0,0x7f,0x00,0x0c,0xfc,0x0f, + 0x3f,0x3f,0xfc,0x3f,0x00,0x7f,0x03,0xf3,0xc3,0xc3,0xc0,0xff, + 0x0f,0x3c,0x30,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xf0,0xf3,0x0f, + 0x3f,0xff,0x0f,0x3c,0xf0,0xc0,0x03,0x0f,0xfc,0x3f,0x3f,0x00, + 0xf0,0x03,0x03,0x00,0xff,0xf9,0xf7,0x9f,0x7f,0xff,0x0f,0x3c, + 0xf0,0xc0,0x03,0x0f,0xfc,0x3f,0x3f,0x00,0xf0,0x83,0x07,0x00, + 0x83,0x1f,0x3e,0xf8,0xe1,0x30,0x0c,0x3c,0xf0,0xc0,0x03,0x0f, + 0x0c,0x30,0x03,0x03,0x00,0xc3,0x0f,0x00,0x03,0x0f,0x3c,0xf0, + 0xc0,0x30,0x0c,0x3c,0xf0,0xc0,0x87,0x0f,0x0c,0x38,0x03,0x07, + 0x00,0xe3,0x1c,0x00,0x03,0x0f,0x3c,0xf0,0x00,0x30,0x0c,0x3c, + 0xf0,0xc0,0xce,0x0d,0x0c,0x1c,0x03,0x0e,0x00,0x73,0x38,0x00, + 0x83,0x0f,0x3c,0xf8,0x01,0x30,0x0c,0x3c,0xf0,0xc0,0xfc,0x1c, + 0x0e,0x0e,0x03,0x1c,0x00,0x33,0x30,0x00,0xff,0x0d,0xfc,0x9f, + 0x3f,0x30,0x0c,0x3c,0xf0,0xc0,0x78,0x38,0x07,0x07,0x03,0x38, + 0x00,0x03,0x00,0x00,0xff,0x0c,0xfc,0x0f,0x7f,0x30,0x0c,0x3c, + 0xf0,0xc0,0x78,0xf0,0x83,0x03,0x03,0x70,0x00,0x03,0x00,0x00, + 0x03,0xcc,0x3c,0x03,0xe0,0x30,0x0c,0x3c,0xf0,0xcc,0xfc,0xe0, + 0xc1,0x01,0x03,0xe0,0x00,0x03,0x00,0x00,0x03,0xcc,0x3d,0x07, + 0xc0,0x30,0x0c,0x7c,0xf8,0xcc,0xce,0xc1,0xe0,0x00,0x03,0xc0, + 0x01,0x03,0x00,0x00,0x03,0x8c,0x37,0xce,0xc0,0x30,0x0c,0xec, + 0xdc,0xcc,0x87,0xc3,0x70,0x00,0x03,0x80,0x03,0x03,0x00,0x00, + 0x03,0x1c,0x37,0xdc,0xe1,0x30,0x1c,0xce,0xcf,0xff,0x03,0xc3, + 0x30,0x00,0x03,0x00,0x03,0x03,0x00,0x00,0x03,0xf8,0x3f,0xb8, + 0x7f,0x30,0xf8,0x87,0x87,0x7f,0x03,0xc3,0xf0,0x3f,0x3f,0x00, + 0xf0,0x03,0xc0,0xff,0x03,0xf0,0x3c,0x30,0x3f,0x30,0xf0,0x03, + 0x03,0x33,0x03,0xc3,0xf0,0x3f,0x3f,0x00,0xf0,0x03,0xc0,0xff, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x0c,0x00,0x30,0x00,0x00,0x00,0x03,0x00, + 0x0f,0x00,0x03,0xc0,0x00,0xcc,0x00,0x3c,0x00,0x00,0x00,0x00, + 0x1c,0x00,0x30,0x00,0x00,0x00,0x03,0x80,0x1f,0x00,0x03,0xc0, + 0x00,0xcc,0x00,0x3c,0x00,0x00,0x00,0x00,0x38,0x00,0x30,0x00, + 0x00,0x00,0x03,0xc0,0x39,0xff,0x03,0x00,0x00,0xc0,0x00,0x30, + 0x00,0x00,0x00,0x00,0x70,0x00,0x30,0x00,0x00,0x00,0x03,0xc0, + 0xb0,0xff,0x03,0x00,0x00,0xc0,0x00,0x30,0x00,0x00,0x00,0x00, + 0xe0,0xf0,0x33,0x0f,0x3f,0x3c,0xf3,0xc3,0xc0,0xc1,0xf3,0xf0, + 0x00,0xcf,0x30,0x30,0x3c,0x33,0x0f,0x3f,0xc0,0xf0,0xb7,0x9f, + 0x3f,0x7e,0xfb,0xc7,0xc0,0xc0,0xfb,0xf1,0x00,0xcf,0x38,0x30, + 0xfc,0xb7,0x9f,0x7f,0x00,0x00,0xfe,0xf9,0x01,0xe7,0x1f,0xfe, + 0xc3,0xc0,0x9f,0xc3,0x00,0xcc,0x1c,0x30,0xec,0xff,0xf9,0xe1, + 0x00,0x00,0xfc,0xf0,0x00,0xc3,0x0f,0xfc,0xc3,0xc1,0x0f,0xc3, + 0x00,0xcc,0x0e,0x30,0xcc,0xfc,0xf0,0xc0,0x00,0xf0,0x3f,0xf0, + 0x00,0x03,0xff,0xcf,0x80,0xff,0x03,0xc3,0x00,0xcc,0x07,0x30, + 0xcc,0x3c,0xf0,0xc0,0x00,0xf8,0x3f,0xf0,0x00,0x03,0xff,0xcf, + 0x00,0xff,0x03,0xc3,0x00,0xcc,0x07,0x30,0xcc,0x3c,0xf0,0xc0, + 0x00,0x1c,0x3c,0xf0,0xc0,0x03,0x0f,0xc0,0x00,0xc0,0x03,0xc3, + 0x30,0xcc,0x0e,0x30,0x0c,0x3c,0xf0,0xc0,0x00,0x1c,0x3c,0xf8, + 0xe1,0x07,0x1f,0xc0,0x00,0xe0,0x03,0xc3,0x70,0xce,0x1c,0x30, + 0x0c,0x3c,0xf0,0xe1,0x00,0xf8,0xff,0x9f,0x7f,0xfe,0xfb,0xc3, + 0x00,0x7f,0x03,0xf3,0xe3,0xc7,0x38,0xfc,0x0c,0x3c,0xb0,0x7f, + 0x00,0xf0,0xff,0x0f,0x3f,0xfc,0xf3,0xc3,0x00,0x3f,0x03,0xf3, + 0xc3,0xc3,0x30,0xfc,0x0c,0x3c,0x30,0x3f,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x0c,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x30,0x30,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x38,0x30, + 0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0c,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x1c,0x30,0xe0,0x00,0x03,0x0c, + 0x00,0x00,0x00,0x00,0x00,0x0c,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x0c,0x30,0xc0,0x00,0x07,0x0e,0xff,0xf0,0x3c,0x0f, + 0x3f,0x3f,0x0c,0x3c,0xf0,0xc0,0x03,0x0f,0xfc,0x3f,0x0c,0x30, + 0xc0,0x00,0x0e,0x07,0xff,0xf9,0xbd,0x9f,0x3f,0x3f,0x0c,0x3c, + 0xf0,0xc0,0x87,0x0f,0xfc,0x3f,0x0e,0x30,0xc0,0x01,0x9c,0x03, + 0x83,0x1f,0xff,0xf9,0x01,0x0c,0x0c,0x3c,0xf0,0xc0,0xce,0x0d, + 0x0c,0x1c,0x03,0x30,0x00,0xf3,0xfc,0xf3,0x83,0x1f,0xff,0xf0, + 0x01,0x0c,0x0c,0x3c,0xf0,0xc0,0xfc,0x1c,0x0c,0x0e,0x03,0x30, + 0x00,0xf3,0xfc,0xf3,0xff,0xf9,0x3f,0x80,0x3f,0x0c,0x0c,0x3c, + 0xf0,0xc0,0x78,0xf8,0x0f,0x07,0x0e,0x30,0xc0,0x01,0x9c,0x03, + 0xff,0xf0,0x3f,0x00,0x7f,0x0c,0x0c,0x7c,0xf8,0xc0,0x78,0xf0, + 0x8f,0x03,0x0c,0x30,0xc0,0x00,0x0e,0x07,0x03,0x00,0x3c,0x00, + 0xe0,0x0c,0x0f,0xef,0xdc,0xcc,0xfc,0x00,0xcc,0x01,0x0c,0x30, + 0xc0,0x00,0x07,0x0e,0x03,0x00,0x3c,0x00,0xe0,0x9c,0x9f,0xcf, + 0xcf,0xff,0xce,0x01,0xee,0x00,0x1c,0x30,0xe0,0x00,0x03,0x0c, + 0x03,0x00,0x3c,0xc0,0x7f,0xf8,0xf9,0x8d,0x87,0x7f,0x87,0xf3, + 0xf7,0x3f,0x38,0x30,0x70,0x00,0x00,0x00,0x03,0x00,0x3c,0xc0, + 0x3f,0xf0,0xf0,0x0c,0x03,0x33,0x03,0xf3,0xf3,0x3f,0x30,0x30, + 0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00}; diff --git a/src/frsky.cpp b/src/frsky.cpp new file mode 100644 index 000000000..fec5f7e72 --- /dev/null +++ b/src/frsky.cpp @@ -0,0 +1,460 @@ +/* + * Authors (alpahbetical order) + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * + * Original contributors + * - Philip Moss Adapted first frsky functions from jeti.cpp code by + * - Karl Szmutny + + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * 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. + * + */ + +#include "gruvin9x.h" +#include "frsky.h" + +// Enumerate FrSky packet codes +#define LINKPKT 0xfe +#define USRPKT 0xfd +#define A11PKT 0xfc +#define A12PKT 0xfb +#define A21PKT 0xfa +#define A22PKT 0xf9 +#define ALRM_REQUEST 0xf8 +#define RSSI1PKT 0xf7 +#define RSSI2PKT 0xf6 + +#define START_STOP 0x7e +#define BYTESTUFF 0x7d +#define STUFF_MASK 0x20 + +uint8_t frskyRxBuffer[19]; // Receive buffer. 9 bytes (full packet), worst case 18 bytes with byte-stuffing (+1) +uint8_t frskyTxBuffer[19]; // Ditto for transmit buffer +uint8_t frskyTxBufferCount = 0; +uint8_t FrskyRxBufferReady = 0; +uint8_t frskyStreaming = 0; +uint8_t frskyTxISRIndex = 0; + +FrskyData frskyTelemetry[2]; +FrskyData frskyRSSI[2]; + +struct FrskyAlarm { + uint8_t level; // The alarm's 'urgency' level. 0=disabled, 1=yellow, 2=orange, 3=red + uint8_t greater; // 1 = 'if greater than'. 0 = 'if less than' + uint8_t value; // The threshold above or below which the alarm will sound +}; +struct FrskyAlarm frskyAlarms[4]; + +void frskyPushValue(uint8_t & i, uint8_t value); + +/* + Called from somewhere in the main loop or a low prioirty interrupt + routine perhaps. This funtcion processes Fr-Sky telemetry data packets + assembled byt he USART0_RX_vect) ISR function (below) and stores + extracted data in global variables for use by other parts of the program. + + Packets can be any of the following: + + - A1/A2/RSSI telemtry data + - Alarm level/mode/threshold settings for Ch1A, Ch1B, Ch2A, Ch2B + - User Data packets + + User Data packets are not yet implementedi (they are simply ignored), + but will likely one day contain the likes of GPS long/lat/alt/speed, + AoA, airspeed, etc. +*/ + +void processFrskyPacket(uint8_t *packet) +{ + // What type of packet? + switch (packet[0]) + { + case A22PKT: + case A21PKT: + case A12PKT: + case A11PKT: + { + struct FrskyAlarm *alarmptr ; + alarmptr = &frskyAlarms[(packet[0]-A22PKT)] ; + alarmptr->value = packet[1]; + alarmptr->greater = packet[2] & 0x01; + alarmptr->level = packet[3] & 0x03; + } + break; + case LINKPKT: // A1/A2/RSSI values + frskyTelemetry[0].set(packet[1]); + frskyTelemetry[1].set(packet[2]); + frskyRSSI[0].set(packet[3]); + frskyRSSI[1].set(packet[4] / 2); + break; + + case USRPKT: // User Data packet -- not yet implemented + break; + } + + FrskyRxBufferReady = 0; + frskyStreaming = FRSKY_TIMEOUT10ms; // reset counter only if valid frsky packets are being detected +} + +// Receive buffer state machine state defs +#define frskyDataIdle 0 +#define frskyDataStart 1 +#define frskyDataInFrame 2 +#define frskyDataXOR 3 +/* + Receive serial (RS-232) characters, detecting and storing each Fr-Sky + 0x7e-framed packet as it arrives. When a complete packet has been + received, process its data into storage variables. NOTE: This is an + interrupt routine and should not get too lengthy. I originally had + the buffer being checked in the perMain function (because per10ms + isn't quite often enough for data streaming at 9600baud) but alas + that scheme lost packets also. So each packet is parsed as it arrives, + directly at the ISR function (through a call to frskyProcessPacket). + + If this proves a problem in the future, then I'll just have to implement + a second buffer to receive data while one buffer is being processed (slowly). +*/ + +#ifndef SIMU +ISR(USART0_RX_vect) +{ + uint8_t stat; + uint8_t data; + + static uint8_t numPktBytes = 0; + static uint8_t dataState = frskyDataIdle; + + UCSR0B &= ~(1 << RXCIE0); // disable Interrupt + sei() ; + stat = UCSR0A; // USART control and Status Register 0 A + + /* + bit 7 6 5 4 3 2 1 0 + RxC0 TxC0 UDRE0 FE0 DOR0 UPE0 U2X0 MPCM0 + + RxC0: Receive complete + TXC0: Transmit Complete + UDRE0: USART Data Register Empty + FE0: Frame Error + DOR0: Data OverRun + UPE0: USART Parity Error + U2X0: Double Tx Speed + PCM0: MultiProcessor Comms Mode + */ + // rh = UCSR0B; //USART control and Status Register 0 B + + /* + bit 7 6 5 4 3 2 1 0 + RXCIE0 TxCIE0 UDRIE0 RXEN0 TXEN0 UCSZ02 RXB80 TXB80 + + RxCIE0: Receive Complete int enable + TXCIE0: Transmit Complete int enable + UDRIE0: USART Data Register Empty int enable + RXEN0: Rx Enable + TXEN0: Tx Enable + UCSZ02: Character Size bit 2 + RXB80: Rx data bit 8 + TXB80: Tx data bit 8 + */ + + data = UDR0; // USART data register 0 + + if (stat & ((1 << FE0) | (1 << DOR0) | (1 << UPE0))) + { // discard buffer and start fresh on any comms error + FrskyRxBufferReady = 0; + numPktBytes = 0; + } + else + { + if (FrskyRxBufferReady == 0) // can't get more data if the buffer hasn't been cleared + { + switch (dataState) + { + case frskyDataStart: + if (data == START_STOP) break; // Remain in userDataStart if possible 0x7e,0x7e doublet found. + + if (numPktBytes < 19) + frskyRxBuffer[numPktBytes++] = data; + dataState = frskyDataInFrame; + break; + + case frskyDataInFrame: + if (data == BYTESTUFF) + { + dataState = frskyDataXOR; // XOR next byte + break; + } + if (data == START_STOP) // end of frame detected + { + processFrskyPacket(frskyRxBuffer); // FrskyRxBufferReady = 1; + dataState = frskyDataIdle; + break; + } + frskyRxBuffer[numPktBytes++] = data; + break; + + case frskyDataXOR: + if (numPktBytes < 19) + frskyRxBuffer[numPktBytes++] = data ^ STUFF_MASK; + dataState = frskyDataInFrame; + break; + + case frskyDataIdle: + if (data == START_STOP) + { + numPktBytes = 0; + dataState = frskyDataStart; + } + break; + + } // switch + } // if (FrskyRxBufferReady == 0) + } + cli() ; + UCSR0B |= (1 << RXCIE0); // enable Interrupt +} + +/* + USART0 (transmit) Data Register Emtpy ISR + Usef to transmit FrSky data packets, which are buffered in frskyTXBuffer. +*/ + +ISR(USART0_UDRE_vect) +{ + if (frskyTxBufferCount > 0) { + UDR0 = frskyTxBuffer[frskyTxISRIndex++]; + frskyTxBufferCount--; + } + else { + UCSR0B &= ~(1 << UDRIE0); // disable UDRE0 interrupt + } +} +#endif + +/******************************************/ + +void frskyTransmitBuffer() +{ + frskyTxISRIndex = 0; + UCSR0B |= (1 << UDRIE0); // enable UDRE0 interrupt +} + + +uint8_t FrskyAlarmSendState = 0 ; +uint8_t FrskyDelay = 0 ; + + +void FRSKY10mspoll(void) +{ + if (FrskyDelay) + { + FrskyDelay -= 1 ; + return ; + } + + if (frskyTxBufferCount) + { + return; // we only have one buffer. If it's in use, then we can't send yet. + } + + // Now send a packet + { + FrskyAlarmSendState -= 1 ; + uint8_t channel = 1 - (FrskyAlarmSendState / 2); + uint8_t alarm = 1 - (FrskyAlarmSendState % 2); + + uint8_t i = 0; + frskyTxBuffer[i++] = START_STOP; // Start of packet + frskyTxBuffer[i++] = (A22PKT + FrskyAlarmSendState); // fc - fb - fa - f9 + frskyPushValue(i, g_model.frsky.channels[channel].alarms_value[alarm]); + { + uint8_t *ptr ; + ptr = &frskyTxBuffer[i] ; + *ptr++ = ALARM_GREATER(channel, alarm); + *ptr++ = ALARM_LEVEL(channel, alarm); + *ptr++ = 0x00 ; + *ptr++ = 0x00 ; + *ptr++ = 0x00 ; + *ptr++ = 0x00 ; + *ptr++ = 0x00 ; + *ptr++ = START_STOP; // End of packet + i += 8 ; + } + FrskyDelay = 5 ; // 50mS + frskyTxBufferCount = i; + frskyTransmitBuffer(); + } +} + +// Send packet requesting all alarm settings be sent back to us +void FRSKY_setRSSIAlarms(void) +{ + if (frskyTxBufferCount) return; // we only have one buffer. If it's in use, then we can't send. Sorry. + + uint8_t i = 0; + + for (int alarm=0; alarm<2; alarm++) { + frskyTxBuffer[i++] = START_STOP; // Start of packet + frskyTxBuffer[i++] = (RSSI1PKT-alarm); // f7 - f6 + frskyPushValue(i, g_eeGeneral.frskyRssiAlarms[alarm].value+50-(10*i)); + { + uint8_t *ptr ; + ptr = &frskyTxBuffer[i] ; + *ptr++ = 0x00 ; + *ptr++ = g_eeGeneral.frskyRssiAlarms[alarm].level; + *ptr++ = 0x00 ; + *ptr++ = 0x00 ; + *ptr++ = 0x00 ; + *ptr++ = 0x00 ; + *ptr++ = 0x00 ; + *ptr++ = START_STOP; // End of packet + i += 8 ; + } + } + + frskyTxBufferCount = i; + frskyTransmitBuffer(); +} + +bool FRSKY_alarmRaised(uint8_t idx) +{ + for (int i=0; i<2; i++) { + if (ALARM_LEVEL(idx, i) != alarm_off) { + if (ALARM_GREATER(idx, i)) { + if (frskyTelemetry[idx].value > g_model.frsky.channels[idx].alarms_value[i]) + return true; + } + else { + if (frskyTelemetry[idx].value < g_model.frsky.channels[idx].alarms_value[i]) + return true; + } + } + } + return false; +} + +inline void FRSKY_EnableTXD(void) +{ + frskyTxBufferCount = 0; + UCSR0B |= (1 << TXEN0) | (1 << UDRIE0); // enable TX and TX interrupt +} + +inline void FRSKY_EnableRXD(void) +{ + + UCSR0B |= (1 << RXEN0); // enable RX + UCSR0B |= (1 << RXCIE0); // enable Interrupt +} + +#if 0 +void FRSKY_DisableTXD(void) +{ + UCSR0B &= ~((1 << TXEN0) | (1 << UDRIE0)); // disable TX pin and interrupt +} + +void FRSKY_DisableRXD(void) +{ + UCSR0B &= ~(1 << RXEN0); // disable RX + UCSR0B &= ~(1 << RXCIE0); // disable Interrupt +} +#endif + +void FRSKY_Init(void) +{ + // clear frsky variables + memset(frskyAlarms, 0, sizeof(frskyAlarms)); + resetTelemetry(); + + DDRE &= ~(1 << DDE0); // set RXD0 pin as input + PORTE &= ~(1 << PORTE0); // disable pullup on RXD0 pin + +#undef BAUD +#define BAUD 9600 +#ifndef SIMU +#include + + UBRR0H = UBRRH_VALUE; + UBRR0L = UBRRL_VALUE; + UCSR0A &= ~(1 << U2X0); // disable double speed operation. + + // set 8 N1 + UCSR0B = 0 | (0 << RXCIE0) | (0 << TXCIE0) | (0 << UDRIE0) | (0 << RXEN0) | (0 << TXEN0) | (0 << UCSZ02); + UCSR0C = 0 | (1 << UCSZ01) | (1 << UCSZ00); + + + while (UCSR0A & (1 << RXC0)) UDR0; // flush receive buffer + +#endif + + // These should be running right from power up on a FrSky enabled '9X. + FRSKY_EnableTXD(); // enable FrSky-Telemetry reception + FRSKY_EnableRXD(); // enable FrSky-Telemetry reception +} + +#if 0 +// Send packet requesting all alarm settings be sent back to us +void frskyAlarmsRefresh() +{ + + if (frskyTxBufferCount) return; // we only have one buffer. If it's in use, then we can't send. Sorry. + + { + uint8_t *ptr ; + ptr = &frskyTxBuffer[0] ; + *ptr++ = START_STOP; // Start of packet + *ptr++ = ALRM_REQUEST; + *ptr++ = 0x00 ; + *ptr++ = 0x00 ; + *ptr++ = 0x00 ; + *ptr++ = 0x00 ; + *ptr++ = 0x00 ; + *ptr++ = 0x00 ; + *ptr++ = 0x00 ; + *ptr++ = 0x00 ; + *ptr++ = START_STOP; // End of packet + } + + frskyTxBufferCount = 11; + frskyTransmitBuffer(); +} +#endif + +void frskyPushValue(uint8_t & i, uint8_t value) +{ + // byte stuff the only byte than might need it + if (value == START_STOP) { + frskyTxBuffer[i++] = BYTESTUFF; + frskyTxBuffer[i++] = 0x5e; + } + else if (value == BYTESTUFF) { + frskyTxBuffer[i++] = BYTESTUFF; + frskyTxBuffer[i++] = 0x5d; + } + else { + frskyTxBuffer[i++] = value; + } +} + +void FrskyData::set(uint8_t value) +{ + this->value = value; + if (!max || max < value) + max = value; + if (!min || min > value) + min = value; + } + +void resetTelemetry() +{ + memset(frskyTelemetry, 0, sizeof(frskyTelemetry)); + memset(frskyRSSI, 0, sizeof(frskyRSSI)); +} + diff --git a/src/frsky.h b/src/frsky.h new file mode 100644 index 000000000..9a400a068 --- /dev/null +++ b/src/frsky.h @@ -0,0 +1,62 @@ +/* + * Author - Bertrand Songis + * + * frsky.cpp original authors - Bryan J.Rentoul (Gruvin) and Philip Moss Adapted from jeti.cpp code by Karl + * Szmutny * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + */ + +#ifndef FRSKY_H +#define FRSKY_H + +#include + +// .20 seconds +#define FRSKY_TIMEOUT10ms 20 + +enum AlarmLevel { + alarm_off = 0, + alarm_yellow = 1, + alarm_orange = 2, + alarm_red = 3 +}; + +#define ALARM_GREATER(channel, alarm) ((g_model.frsky.channels[channel].alarms_greater >> alarm) & 1) +#define ALARM_LEVEL(channel, alarm) ((g_model.frsky.channels[channel].alarms_level >> (2*alarm)) & 3) + +struct FrskyData { + uint8_t value; + uint8_t min; + uint8_t max; + void set(uint8_t value); +}; + +// Global Fr-Sky telemetry data variables +extern uint8_t frskyStreaming; // >0 (true) == data is streaming in. 0 = nodata detected for some time +extern uint8_t FrskyAlarmSendState; +extern FrskyData frskyTelemetry[2]; +extern FrskyData frskyRSSI[2]; + +void FRSKY_Init(void); +void FRSKY10mspoll(void); + +inline void FRSKY_setModelAlarms(void) +{ + FrskyAlarmSendState = 4 ; +} + +bool FRSKY_alarmRaised(uint8_t idx); + +void resetTelemetry(); + +#endif + diff --git a/src/fuses_2561.txt b/src/fuses_2561.txt new file mode 100644 index 000000000..0c1417a63 --- /dev/null +++ b/src/fuses_2561.txt @@ -0,0 +1,36 @@ +The ATmega2561 uses quite different bit assignment for its option fuses + + +Fuse defaults for ATmega64A + +Default values for ATmega64 are: + hfuse = 0b10011001 (0x99) + lfuse = 0b11100001 (0xE1) + efuse = 0b11111101 (0xFD) + +Preferred values for ATmega64A are: + hfuse = 0x11 + lfuse = 0x1F + efuse = 0xff + +... which includes EESAVE feature, so that the EEPROM model data does NOT get +erased during chip erase prior to re-programming ... respectively. + +- - - - - - - - - - - - - - - - - - - - - - + +Fuse defaults for ATmega2560/1 are: + + hfuse = 0b10011001 (0x99) + lfuse = 0b01100010 (0x62) + efuse = 0b11111111 (0xFF) + +The bit assignments ae quite different from the AT'64A in all cases. + +Preferred vale es for ATmega2560/1 are: + hfuse = 0b10011001 (0x11) [OCD enabled, JTAG enabled, WDT disabled :(, + EESAVE enabled, BOOT at 0x0000] + lfuse = 0b01101110 (0xd7) [CKDIV8 disabled, CKOUT disabled, + Full Swing Xtal Osc, 16K CK + 0ms reset, + BOD enabled] + efuse = 0b11111111 (0xfc) [ Brown out detector (BOD) = 4.3V ] + diff --git a/src/general_menus.cpp b/src/general_menus.cpp new file mode 100644 index 000000000..bd6844f7d --- /dev/null +++ b/src/general_menus.cpp @@ -0,0 +1,552 @@ +/* + * Authors (alphabetical order) + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * + * gruvin9x is based on code named er9x by + * Author - Erez Raviv , which is in turn + * was based on the original (and ongoing) project by Thomas Husterer, + * th9x -- http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + */ + +#include "menus.h" +#include "sticks.lbm" + +enum EnumTabDiag { + e_Setup, +#if defined(PCBV3) + e_FrskyTime, +#endif + e_Trainer, + e_Vers, + e_Keys, + e_Ana, + e_Calib +}; + +void menuProcSetup(uint8_t event); +#if defined(PCBV3) +void menuProcTime(uint8_t event); +#endif +void menuProcTrainer(uint8_t event); +void menuProcDiagVers(uint8_t event); +void menuProcDiagKeys(uint8_t event); +void menuProcDiagAna(uint8_t event); +void menuProcDiagCalib(uint8_t event); + +MenuFuncP_PROGMEM APM menuTabDiag[] = { + menuProcSetup, +#if defined(PCBV3) + menuProcTime, +#endif + menuProcTrainer, + menuProcDiagVers, + menuProcDiagKeys, + menuProcDiagAna, + menuProcDiagCalib +}; + +void menuProcSetup(uint8_t event) +{ +#define COUNT_ITEMS 19 +#define PARAM_OFS 17*FW + + SIMPLE_MENU("RADIO SETUP", menuTabDiag, e_Setup, COUNT_ITEMS+1); + + int8_t sub = m_posVert; + + // last 2 lines (radio mode) are non break-able + if(s_pgOfs==COUNT_ITEMS-7) s_pgOfs= sub<(COUNT_ITEMS-4) ? COUNT_ITEMS-8 : COUNT_ITEMS-6; + + uint8_t y = 1*FH; + + uint8_t subN = 1; + if(s_pgOfs7*FH) return; + }subN++; + + if(s_pgOfs7*FH) return; + }subN++; + + if(s_pgOfs7*FH) return; + }subN++; + + if(s_pgOfs7*FH) return; + }subN++; + + if(s_pgOfs7*FH) return; + }subN++; + + if(s_pgOfs7*FH) return; + }subN++; + + if(s_pgOfs7*FH) return; + }subN++; + + if(s_pgOfs7*FH) return; + }subN++; + + if(s_pgOfs7*FH) return; + }subN++; + + if(s_pgOfs7*FH) return; + }subN++; + + if(s_pgOfs7*FH) return; + }subN++; + + if(s_pgOfs7*FH) return; + }subN++; + + if(s_pgOfs7*FH) return; + }subN++; + + if(s_pgOfs7*FH) return; + }subN++; + + if(s_pgOfs7*FH) return; + }subN++; + + if(s_pgOfs7*FH) return; + }subN++; + + if(s_pgOfs7*FH) return; + }subN++; + + if(s_pgOfsAETR + for (uint8_t i=1; i<=4; i++) + putsChnLetter((16+i)*FW, y, i, attr); + if(attr) CHECK_INCDEC_GENVAR(event, g_eeGeneral.templateSetup, 0, 23); + if((y+=FH)>7*FH) return; + }subN++; + + if(s_pgOfs7*FH) return; + + lcd_putcAtt( 3*FW, y, '1'+g_eeGeneral.stickMode,sub==subN?INVERS:0); + for(uint8_t i=0; i<4; i++) putsChnRaw( (6+4*i)*FW, y,i+1,0);//sub==3?INVERS:0); + + if(sub==subN) CHECK_INCDEC_GENVAR(event,g_eeGeneral.stickMode,0,3); + if((y+=FH)>7*FH) return; + } +} + + +#if defined(PCBV3) +// SD card interface contains Real-Time-Clock chip +void menuProcTime(uint8_t event) +{ + MENU("DATE AND TIME", menuTabDiag, e_FrskyTime, 3, {0, 2/*, 2*/}); + + int8_t sub = m_posVert - 1; // vertical position (1 = page counter, top/right) + uint8_t subSub = m_posHorz; // horizontal position + static struct tm t; + struct tm *at = &t; + + switch(event) + { + case EVT_KEY_LONG(KEY_MENU): + // get data time from RTC chip (may not implement) + killEvents(event); + break; + case EVT_KEY_FIRST(KEY_MENU): + if (sub >= 0 && !s_editMode) // set the date and time into RTC chip + { + g_ms100 = 0; // start of next second begins now + g_unixTime = mktime(&t); // update local timestamp and get wday calculated + + RTC rtc; + rtc.year = t.tm_year + 1900; + rtc.month = t.tm_mon + 1; + rtc.mday = t.tm_mday; + rtc.hour = t.tm_hour; + rtc.min = t.tm_min; + rtc.sec = t.tm_sec; + rtc.wday = t.tm_wday + 1; + + rtc_settime(&rtc); + + } + break; + } + + if (!s_editMode) filltm(&g_unixTime, &t); + + lcd_putc(FW*10+2, FH*2, '-'); lcd_putc(FW*13, FH*2, '-'); + lcd_putc(FW*10+1, FH*4, ':'); lcd_putc(FW*13-1, FH*4, ':'); + + for(uint8_t i=0; i<2; i++) // 2 rows, date then time + { + uint8_t y=(i*2+2)*FH; + + lcd_putsnAtt(0, y, PSTR("DATE:""TIME:")+i*5, 5, 0); + + for(uint8_t j=0; j<3;j++) // 3 settings each for date and time (YMD and HMS) + { + uint8_t attr = (sub==i && subSub==j) ? (s_editMode ? BLINK : INVERS) : 0; + switch(i) + { + case 0: // DATE + switch(j) + { + case 0: + lcd_outdezAtt(FW*10+2, y, at->tm_year+1900, attr); + if(attr && (s_editMode || p1valdiff)) at->tm_year = checkIncDec( event, at->tm_year, 110, 200, 0); + break; + case 1: + lcd_outdezNAtt(FW*13, y, at->tm_mon+1, attr|LEADING0, 2); + if(attr && (s_editMode || p1valdiff)) at->tm_mon = checkIncDec( event, at->tm_mon, 0, 11, 0); + break; + case 2: + lcd_outdezNAtt(FW*16-2, y, at->tm_mday, attr|LEADING0, 2); + if(attr && (s_editMode || p1valdiff)) at->tm_mday = checkIncDec( event, at->tm_mday, 1, 31, 0); + break; + } + break; + + case 1: + switch (j) + { + case 0: + lcd_outdezNAtt(FW*10+1, y, at->tm_hour, attr|LEADING0, 2); + if(attr && (s_editMode || p1valdiff)) at->tm_hour = checkIncDec( event, at->tm_hour, 0, 23, 0); + break; + case 1: + lcd_outdezNAtt(FW*13-1, y, at->tm_min, attr|LEADING0, 2); + if(attr && (s_editMode || p1valdiff)) at->tm_min = checkIncDec( event, at->tm_min, 0, 59, 0); + break; + case 2: + lcd_outdezNAtt(FW*16-2, y, at->tm_sec, attr|LEADING0, 2); + if(attr && (s_editMode || p1valdiff)) at->tm_sec = checkIncDec( event, at->tm_sec, 0, 59, 0); + break; + } + break; + + } + } + } +} +#endif + +void menuProcTrainer(uint8_t event) +{ + MENU("TRAINER", menuTabDiag, e_Trainer, 7, {0, 2, 2, 2, 2/*, 0, 0*/}); + + int8_t sub = m_posVert; + uint8_t subSub = m_posHorz; + uint8_t y; + bool edit; + uint8_t blink ; + + if (SLAVE_MODE) { // i am the slave + lcd_puts_P(7*FW, 3*FH, PSTR("Slave")); + return; + } + + lcd_puts_P(3*FW, 1*FH, PSTR("mode % src")); + + sub--; + y = 2*FH; + blink = s_editMode ? BLINK : INVERS ; + + for (uint8_t i=0; i<4; i++) { + uint8_t chan = chout_ar[g_eeGeneral.templateSetup][i]; // G: Issue 30. + + volatile TrainerMix *td = &g_eeGeneral.trainer.mix[chan-1]; + + putsChnRaw(0, y, chan, 0); + + edit = (sub==i && subSub==0); + lcd_putsnAtt(4*FW, y, PSTR("off += :=")+3*td->mode, 3, edit ? blink : 0); + if (edit && s_editMode) + CHECK_INCDEC_GENVAR(event, td->mode, 0, 2); + + edit = (sub==i && subSub==1); + lcd_outdezAtt(11*FW, y, td->studWeight, edit ? blink : 0); + if (edit && s_editMode) + CHECK_INCDEC_GENVAR(event, td->studWeight, -100, 100); + + edit = (sub==i && subSub==2); + lcd_putsnAtt(12*FW, y, PSTR("ch1ch2ch3ch4")+3*td->srcChn, 3, edit ? blink : 0); + if (edit && s_editMode) + CHECK_INCDEC_GENVAR(event, td->srcChn, 0, 3); + + edit = (sub==i && subSub==3); + + y += FH; + } + + lcd_puts_P(0*FW, y, PSTR("Multiplier")); + lcd_outdezAtt(13*FW, y, g_eeGeneral.PPM_Multiplier+10, (sub==4 ? INVERS : 0)|PREC1); + if(sub==4) CHECK_INCDEC_GENVAR(event, g_eeGeneral.PPM_Multiplier, -10, 40); + y += FH; + + edit = (sub==5); + lcd_putsAtt(0*FW, y, PSTR("Cal"), edit ? INVERS : 0); + for (uint8_t i=0; i<4; i++) { + uint8_t x = (i*8+16)*FW/2; +#if defined (DECIMALS_DISPLAYED) + lcd_outdezAtt(x , y, (g_ppmIns[i]-g_eeGeneral.trainer.calib[i])*2, PREC1); +#else + lcd_outdezAtt(x , y, (g_ppmIns[i]-g_eeGeneral.trainer.calib[i])/5, 0); +#endif + } + + if (edit) { + if (event==EVT_KEY_FIRST(KEY_MENU)){ + memcpy(g_eeGeneral.trainer.calib, g_ppmIns, sizeof(g_eeGeneral.trainer.calib)); + eeDirty(EE_GENERAL); + beepKey(); + } + } +} + +void menuProcDiagVers(uint8_t event) +{ + SIMPLE_MENU("VERSION", menuTabDiag, e_Vers, 1); + + lcd_puts_P(0, 2*FH,stamp4 ); + lcd_puts_P(0, 3*FH,stamp1 ); + lcd_puts_P(0, 4*FH,stamp5 ); + lcd_puts_P(0, 5*FH,stamp2 ); + lcd_puts_P(0, 6*FH,stamp3 ); + lcd_puts_P(0, 7*FH,PSTR("EEPROM v")); + lcd_outdezAtt(8*FW, 7*FH, g_eeGeneral.myVers, LEFT); +} + +void menuProcDiagKeys(uint8_t event) +{ + SIMPLE_MENU("DIAG", menuTabDiag, e_Keys, 1); + + for(uint8_t i=0; i<9; i++) + { + uint8_t y=i*FH; //+FH; + if(i>(SW_ID0-SW_BASE_DIAG)) y-=FH; //overwrite ID0 + bool t=keyState((EnumKeys)(SW_BASE_DIAG+i)); + putsSwitches(8*FW, y, i+1, 0); //ohne off,on + lcd_putcAtt(11*FW+2, y, t+'0', t ? INVERS : 0); + } + + for(uint8_t i=0; i<6; i++) + { + uint8_t y=(5-i)*FH+2*FH; + bool t=keyState((EnumKeys)(KEY_MENU+i)); + lcd_putsn_P(0, y, PSTR(" Menu Exit Down UpRight Left")+5*i, 5); + lcd_putcAtt(5*FW+2, y, t+'0', t); + } + + lcd_putsn_P(14*FW, 3*FH, PSTR("Trim- +"), 7); + for(uint8_t i=0; i<4; i++) + { + uint8_t y=i*FH+FH*4; + lcd_img(14*FW, y, sticks,i,0); + bool tm=keyState((EnumKeys)(TRM_BASE+2*i)); + bool tp=keyState((EnumKeys)(TRM_BASE+2*i+1)); + lcd_putcAtt(18*FW, y, tm+'0',tm ? INVERS : 0); + lcd_putcAtt(20*FW, y, tp+'0',tp ? INVERS : 0); + } +} + +void menuProcDiagAna(uint8_t event) +{ + SIMPLE_MENU("ANA", menuTabDiag, e_Ana, 2); + + int8_t sub = m_posVert ; + + for(uint8_t i=0; i<8; i++) + { + uint8_t y=i*FH; + lcd_putsn_P( 4*FW, y,PSTR("A1A2A3A4A5A6A7A8")+2*i,2); + lcd_outhex4( 8*FW, y,anaIn(i)); + if(i<7) lcd_outdez8(17*FW, y, (int32_t)calibratedStick[i]*100/1024); + if(i==7) putsVolts(17*FW, y, g_vbat100mV, (sub==1 ? INVERS : 0)); + } + // lcd_outdezAtt( 21*FW, 3*FH, g_eeGeneral.vBatCalib, 0) ; + // lcd_outdezAtt( 21*FW, 4*FH, abRunningAvg, 0) ; + // Display raw BandGap result (debug) + lcd_putsn_P( 19*FW, 5*FH,PSTR("BG"),2) ; + lcd_outdezAtt(21*FW, 6*FH, BandGap, 0); + lcd_outdezAtt(21*FW, 7*FH, anaIn(7)*35/512, PREC1); + + if(sub==1) CHECK_INCDEC_GENVAR(event, g_eeGeneral.vBatCalib, -127, 127); + +} + +void menuProcDiagCalib(uint8_t event) +{ + SIMPLE_MENU("CALIBRATION", menuTabDiag, e_Calib, 4); + + int8_t sub = m_posVert ; + static int16_t midVals[7]; + static int16_t loVals[7]; + static int16_t hiVals[7]; + + for(uint8_t i=0; i<7; i++) { //get low and high vals for sticks and trims + int16_t vt = anaIn(i); + loVals[i] = min(vt,loVals[i]); + hiVals[i] = max(vt,hiVals[i]); + //if(i>=4) midVals[i] = (loVals[i] + hiVals[i])/2; + } + + switch(event) + { + case EVT_ENTRY: + for(uint8_t i=0; i<7; i++) loVals[i] = 15000; + break; + case EVT_KEY_BREAK(KEY_DOWN): // !! achtung sub schon umgesetzt + switch(sub) + { + case 2: //get mid + for(uint8_t i=0; i<7; i++)midVals[i] = anaIn(i); + beepKey(); + break; + case 3: + for(uint8_t i=0; i<7; i++) + if(abs(loVals[i]-hiVals[i])>50) { + g_eeGeneral.calibMid[i] = midVals[i]; + int16_t v = midVals[i] - loVals[i]; + g_eeGeneral.calibSpanNeg[i] = v - v/64; + v = hiVals[i] - midVals[i]; + g_eeGeneral.calibSpanPos[i] = v - v/64; + } + int16_t sum=0; + for(uint8_t i=0; i<12;i++) sum+=g_eeGeneral.calibMid[i]; + g_eeGeneral.chkSum = sum; + eeDirty(EE_GENERAL); //eeWriteGeneral(); + beepKey(); + break; + } + break; + } + for(uint8_t i=1; i<4; i++) + { + uint8_t y=i*FH+FH; + lcd_putsnAtt( 0, y,PSTR("SetMid SetSpanDone ")+7*(i-1),7, + sub==i ? INVERS : 0); + } + for(uint8_t i=0; i<7; i++) + { + uint8_t y=i*FH; + lcd_puts_P( 11*FW, y+1*FH, PSTR("< >")); + lcd_outhex4( 8*FW-3, y+1*FH, sub==2 ? loVals[i] : g_eeGeneral.calibSpanNeg[i]); + lcd_outhex4(12*FW, y+1*FH, sub==1 ? anaIn(i) : (sub==2 ? midVals[i] : g_eeGeneral.calibMid[i])); + lcd_outhex4(17*FW, y+1*FH, sub==2 ? hiVals[i] : g_eeGeneral.calibSpanPos[i]); + } + +} diff --git a/src/gruvin9x.cpp b/src/gruvin9x.cpp new file mode 100644 index 000000000..0a1543b9b --- /dev/null +++ b/src/gruvin9x.cpp @@ -0,0 +1,2243 @@ +/* + * Authors (alphabetical order) + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * + * gruvin9x is based on code named er9x by + * Author - Erez Raviv , which is in turn + * was based on the original (and ongoing) project by Thomas Husterer, + * th9x -- http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + */ + +#include "gruvin9x.h" +#include "s9xsplash.lbm" +#include "menus.h" + +// MM/SD card Disk IO Support +#if defined (PCBV3) +#include "integer.h" +#include "time.h" +#include "rtc.h" +#include "ff.h" +#include "diskio.h" +time_t g_unixTime; // Global date/time register, incremented each second in per10ms() +#endif + +/* +mode1 rud ele thr ail +mode2 rud thr ele ail +mode3 ail ele thr rud +mode4 ail thr ele rud +*/ + +EEGeneral g_eeGeneral; +ModelData g_model; + +uint16_t g_tmr1Latency_max; +uint16_t g_tmr1Latency_min = 0x7ff; +uint16_t g_timeMain; +uint16_t g_time_per10; + +#if defined (PCBSTD) && defined (BEEPSPKR) +uint8_t toneFreq = BEEP_DEFAULT_FREQ; +uint8_t toneOn = false; +#endif + +bool warble = false; + +const prog_char APM modi12x3[]= + "RUD ELE THR AIL " + "RUD THR ELE AIL " + "AIL ELE THR RUD " + "AIL THR ELE RUD "; + +const prog_char APM s_charTab[] = "_-.,"; + +#ifdef TRANSLATIONS +int8_t char2idx(char c) +{ + if (c==' ') return 0; + if (c>='A' && c<='Z') return 1+c-'A'; + if (c>='a' && c<='z') return -1-c+'a'; + if (c>='0' && c<='9') return 27+c-'0'; + for (int8_t i=0;;i++) { + char cc = pgm_read_byte(s_charTab+i); + if(cc==c) return 37+i; + if(cc==0) return 0; + } +} +#endif + +char idx2char(int8_t idx) +{ + if (idx == 0) return ' '; + if (idx < 0) { + if (idx > -27) return 'a' - idx - 1; + idx = -idx; + } + if (idx < 27) return 'A' + idx - 1; + if (idx < 37) return '0' + idx - 27; + if (idx <= ZCHAR_MAX) return pgm_read_byte(s_charTab+idx-37); + return ' '; +} + +PhaseData *phaseaddress(uint8_t idx) +{ + return &g_model.phaseData[idx]; +} + +ExpoData *expoaddress(uint8_t idx ) +{ + return &g_model.expoData[idx]; +} + +MixData *mixaddress(uint8_t idx) +{ + return &g_model.mixData[idx]; +} + +LimitData *limitaddress(uint8_t idx) +{ + return &g_model.limitData[idx]; +} + +int16_t intpol(int16_t x, uint8_t idx) // -100, -75, -50, -25, 0 ,25 ,50, 75, 100 +{ +#define D9 (RESX * 2 / 8) +#define D5 (RESX * 2 / 4) + bool cv9 = idx >= MAX_CURVE5; + int8_t *crv = cv9 ? g_model.curves9[idx-MAX_CURVE5] : g_model.curves5[idx]; + int16_t erg; + + x+=RESXu; + if(x < 0) { + erg = (int16_t)crv[0] * (RESX/4); + } else if(x >= (RESX*2)) { + erg = (int16_t)crv[(cv9 ? 8 : 4)] * (RESX/4); + } else { + int16_t a,dx; + if(cv9){ + a = (uint16_t)x / D9; + dx =((uint16_t)x % D9) * 2; + } else { + a = (uint16_t)x / D5; + dx = (uint16_t)x % D5; + } + erg = (int16_t)crv[a]*((D5-dx)/2) + (int16_t)crv[a+1]*(dx/2); + } + return erg / 25; // 100*D5/RESX; +} + +int16_t applyCurve(int16_t x, uint8_t idx, uint8_t srcRaw) +{ + switch(idx) { + case 0: + return x; + case 1: + if (srcRaw == MIX_FULL) { //FULL + if (x<0 ) x=-RESX; //x|x>0 + else x=-RESX+2*x; + } + else { + if (x<0) x=0; //x|x>0 + } + return x; + case 2: + if (srcRaw == MIX_FULL) { //FULL + if (x>0) x=RESX; //x|x<0 + else x=RESX+2*x; + } + else { + if (x>0) x=0; //x|x<0 + } + return x; + case 3: // x|abs(x) + return abs(x); + case 4: //f|f>0 + return x>0 ? RESX : 0; + case 5: //f|f<0 + return x<0 ? -RESX : 0; + case 6: //f|abs(f) + return x>0 ? RESX : -RESX; + } + return intpol(x, idx-7); +} + +// expo-funktion: +// --------------- +// kmplot +// f(x,k)=exp(ln(x)*k/10) ;P[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20] +// f(x,k)=x*x*x*k/10 + x*(1-k/10) ;P[0,1,2,3,4,5,6,7,8,9,10] +// f(x,k)=x*x*k/10 + x*(1-k/10) ;P[0,1,2,3,4,5,6,7,8,9,10] +// f(x,k)=1+(x-1)*(x-1)*(x-1)*k/10 + (x-1)*(1-k/10) ;P[0,1,2,3,4,5,6,7,8,9,10] + +uint16_t expou(uint16_t x, uint16_t k) +{ + // k*x*x*x + (1-k)*x + return ((unsigned long)x*x*x/0x10000*k/(RESXul*RESXul/0x10000) + (RESKul-k)*x+RESKul/2)/RESKul; +} + +int16_t expo(int16_t x, int16_t k) +{ + if(k == 0) return x; + int16_t y; + bool neg = x < 0; + if(neg) x = -x; + if(k<0){ + y = RESXu-expou(RESXu-x,-k); + }else{ + y = expou(x,k); + } + return neg? -y:y; +} + +#ifdef EXTENDED_EXPO +/// expo with y-offset +class Expo +{ + uint16_t c; + int16_t d,drx; +public: + void init(uint8_t k, int8_t yo); + static int16_t expou(uint16_t x,uint16_t c, int16_t d); + int16_t expo(int16_t x); +}; +void Expo::init(uint8_t k, int8_t yo) +{ + c = (uint16_t) k * 256 / 100; + d = (int16_t) yo * 256 / 100; + drx = d * ((uint16_t)RESXu/256); +} +int16_t Expo::expou(uint16_t x,uint16_t c, int16_t d) +{ + uint16_t a = 256 - c - d; + if( (int16_t)a < 0 ) a = 0; + // a x^3 + c x + d + // 9 18 27 11 20 18 + uint32_t res = ((uint32_t)x * x * x / 0x10000 * a / (RESXul*RESXul/0x10000) + + (uint32_t)x * c + ) / 256; + return (int16_t)res; +} +int16_t Expo::expo(int16_t x) +{ + if(c==256 && d==0) return x; + if(x>=0) return expou(x,c,d) + drx; + return -expou(-x,c,-d) + drx; +} +#endif + + +void applyExpos(int16_t *anas) +{ + static int16_t anas2[4]; // values before expo, to ensure same expo base when multiple expo lines are used + memcpy(anas2, anas, sizeof(anas2)); + + uint8_t phase = getFlightPhase(); + + for (uint8_t i=0; i=0 && ed.mode&2)) { + int16_t k = ed.expo; + if (IS_THROTTLE(ed.chn) && g_model.thrExpo) + v = 2*expo((v+RESX)/2, k); + else + v = expo(v, k); + if (ed.curve) v = applyCurve(v, ed.curve, 0); + v = ((int32_t)v * ed.weight) / 100; + if (IS_THROTTLE(ed.chn) && g_model.thrExpo) v -= RESX; + anas[ed.chn] = v; + } + } + } +} + +const prog_char *get_switches_string() +{ + return PSTR(SWITCHES_STR); +} + +static bool s_noStickInputs = false; +inline int16_t getValue(uint8_t i) +{ + if(iswtch && getSwitch(phase->swtch, 0)) { + return i; + } + } + return 0; +} + +uint8_t getTrimFlightPhase(uint8_t idx, int8_t phase) +{ + if (phase == -1) phase = getFlightPhase(); + + for (uint8_t i=0; i TRIM_MAX) return 0; + if (trim >= TRIM_MIN) return phase; + uint8_t result = 129 + trim; + if (result >= phase) result++; + phase = result; + } + return 0; +} + +bool getSwitch(int8_t swtch, bool nc, uint8_t level) +{ + if(level>5) return false; //prevent recursive loop going too deep + + switch(swtch){ + case 0: return nc; + case MAX_SWITCH: return true; + case -MAX_SWITCH: return false; + } + + uint8_t dir = swtch>0; + if(abs(swtch)<(MAX_SWITCH-NUM_CSW)) { + if(!dir) return ! keyState((EnumKeys)(SW_BASE-swtch-1)); + return keyState((EnumKeys)(SW_BASE+swtch-1)); + } + + //custom switch, Issue 78 + //use putsChnRaw + //input -> 1..4 -> sticks, 5..8 pots + //MAX,FULL - disregard + //ppm + CustomSwData &cs = g_model.customSw[abs(swtch)-(MAX_SWITCH-NUM_CSW)]; + if(!cs.func) return false; + + + int8_t a = cs.v1; + int8_t b = cs.v2; + int16_t x = 0; + int16_t y = 0; + + // init values only if needed + uint8_t s = CS_STATE(cs.func); + if(s == CS_VOFS) + { + x = getValue(cs.v1-1); +#ifdef FRSKY + if (cs.v1 > CHOUT_BASE+NUM_CHNOUT) + y = 125+cs.v2; + else +#endif + y = calc100toRESX(cs.v2); + } + else if(s == CS_VCOMP) + { + x = getValue(cs.v1-1); + y = getValue(cs.v2-1); + } + + switch (cs.func) { + case (CS_VPOS): + return swtch>0 ? (x>y) : !(x>y); + break; + case (CS_VNEG): + return swtch>0 ? (xy) ; + return swtch>0 ? res : !res ; + } + break; + case (CS_ANEG): + { + bool res = (abs(x)0 ? res : !res ; + } + break; + + case (CS_AND): + case (CS_OR): + case (CS_XOR): + { + bool res1 = getSwitch(a,0,level+1) ; + bool res2 = getSwitch(b,0,level+1) ; + if ( cs.func == CS_AND ) + { + return res1 && res2 ; + } + else if ( cs.func == CS_OR ) + { + return res1 || res2 ; + } + else // CS_XOR + { + return res1 ^ res2 ; + } + } + break; + case (CS_EQUAL): + return (x==y); + break; + case (CS_NEQUAL): + return (x!=y); + break; + case (CS_GREATER): + return (x>y); + break; + case (CS_LESS): + return (x=y); + break; + case (CS_ELESS): + return (x<=y); + break; + default: + return false; + break; + } + +} + + +//#define CS_EQUAL 8 +//#define CS_NEQUAL 9 +//#define CS_GREATER 10 +//#define CS_LESS 11 +//#define CS_EGREATER 12 +//#define CS_ELESS 13 + +#if defined (PCBV3) +// The ugly scanned keys thing should be gone for PCBV4+. In the meantime ... +uint8_t keyDown() +{ + uint8_t in; + PORTD = ~1; // select KEY_Y0 row (Bits 3:2 EXIT:MENU) + _delay_us(1); + in = (~PIND & 0b11000000) >> 5; + PORTD = ~2; // select Y1 row. (Bits 3:0 Left/Right/Up/Down) + _delay_us(1); + in |= (~PIND & 0xf0) >> 1; + PORTD = 0xff; + return (in); +} +#else +inline uint8_t keyDown() +{ + return (~PINB) & 0x7E; +} +#endif + +void clearKeyEvents() +{ + while(keyDown()); // loop until all keys are up + putEvent(0); +} + +void doSplash() +{ + if(!g_eeGeneral.disableSplashScreen) + { + if(getSwitch(g_eeGeneral.lightSw,0) || g_eeGeneral.lightAutoOff) + BACKLIGHT_ON; + else + BACKLIGHT_OFF; + + lcd_clear(); + lcd_img(0, 0, s9xsplash,0,0); + refreshDiplay(); + lcdSetRefVolt(g_eeGeneral.contrast); + clearKeyEvents(); + +#ifndef SIMU + for(uint8_t i=0; i<32; i++) + getADC_filt(); // init ADC array +#endif + +#define INAC_DEVISOR 256 // Bypass splash screen with stick movement + uint16_t inacSum = 0; + for(uint8_t i=0; i<4; i++) + inacSum += anaIn(i)/INAC_DEVISOR; + + uint16_t tgtime = get_tmr10ms() + SPLASH_TIMEOUT; //2sec splash screen + while(tgtime != get_tmr10ms()) + { +#ifndef SIMU + getADC_filt(); +#endif + uint16_t tsum = 0; + for(uint8_t i=0; i<4; i++) + tsum += anaIn(i)/INAC_DEVISOR; + + if(keyDown() || (tsum!=inacSum)) return; //wait for key release + + if(getSwitch(g_eeGeneral.lightSw,0) || g_eeGeneral.lightAutoOff) + BACKLIGHT_ON; + else + BACKLIGHT_OFF; + } + } +} + +void checkMem() +{ + if(g_eeGeneral.disableMemoryWarning) return; + if(EeFsGetFree() < 200) + { + alert(PSTR("EEPROM low mem")); + } +} + +void alertMessages( const prog_char * s, const prog_char * t ) +{ + lcd_clear(); + lcd_putsAtt(64-5*FW,0*FH,PSTR("ALERT"),DBLSIZE); + lcd_puts_P(0,4*FH,s); + lcd_puts_P(0,5*FH,t); + lcd_puts_P(0,6*FH, PSTR("Press any key to skip") ) ; + refreshDiplay(); + lcdSetRefVolt(g_eeGeneral.contrast); + + clearKeyEvents(); +} + +void checkTHR() +{ + if(g_eeGeneral.disableThrottleWarning) return; + + int thrchn=(2-(g_eeGeneral.stickMode&1));//stickMode=0123 -> thr=2121 + + int16_t lowLim = THRCHK_DEADBAND + g_eeGeneral.calibMid[thrchn] - g_eeGeneral.calibSpanNeg[thrchn]; + +#ifndef SIMU + getADC_single(); // if thr is down - do not display warning at all +#endif + int16_t v = anaIn(thrchn); + if(v<=lowLim) return; + + // first - display warning + alertMessages( PSTR("Throttle not idle"), PSTR("Reset throttle") ) ; + + //loop until all switches are reset + while (1) + { +#ifndef SIMU + getADC_single(); +#endif + int16_t v = anaIn(thrchn); + + if(v<=lowLim || keyDown()) { + clearKeyEvents(); + return; + } + + if(getSwitch(g_eeGeneral.lightSw,0) || g_eeGeneral.lightAutoOff) + BACKLIGHT_ON; + else + BACKLIGHT_OFF; + } +} + +void checkAlarm() // added by Gohst +{ + if(g_eeGeneral.disableAlarmWarning) return; + if(!g_eeGeneral.beeperVal) alert(PSTR("Alarms Disabled")); +} + +void checkSwitches() +{ + if(!g_eeGeneral.switchWarning) return; // if warning is on + + // first - display warning + alertMessages( PSTR("Switches not off"), PSTR("Please reset them") ) ; + + bool state = (g_eeGeneral.switchWarning > 0); + + //loop until all switches are reset + while (1) + { + uint8_t i; + for(i=SW_BASE; itrim[idx]; + if (trim >= TRIM_MIN && trim <= TRIM_MAX) { + int16_t newtrim = trim - inc; + if (newtrim < TRIM_MIN) { + newtrim = TRIM_MIN; + } + if (newtrim > TRIM_MAX) { + newtrim = TRIM_MAX; + } + phase->trim[idx] = newtrim; + } + } + int16_t subtrim = g_model.subtrim[idx] + inc; + if (subtrim > SUBTRIM_MAX) + subtrim = SUBTRIM_MAX; + if (subtrim < SUBTRIM_MIN) + subtrim = SUBTRIM_MIN; + g_model.subtrim[idx] = subtrim; +} + +uint8_t checkTrim(uint8_t event) +{ + int8_t k = (event & EVT_KEY_MASK) - TRM_BASE; + int8_t s = g_model.trimInc; + + if (k>=0 && k<8) { // && (event & _MSK_KEY_REPT)) + //LH_DWN LH_UP LV_DWN LV_UP RV_DWN RV_UP RH_DWN RH_UP + uint8_t idx = k/2; + uint8_t phase = getTrimFlightPhase(idx); + int8_t t = g_model.phaseData[phase].trim[idx]; + int16_t before = g_model.subtrim[idx] + t; + int8_t v = (s==0) ? min(32, abs(before)/4+1) : 1 << (s-1); // 1=>1 2=>2 3=>4 4=>8 + bool thro = (((2-(g_eeGeneral.stickMode&1)) == idx) && g_model.thrTrim); + if (thro) v = 4; // if throttle trim and trim trottle then step=4 + int16_t trim = (k&1) ? t + v : t - v; // positive = k&1 + int16_t after = g_model.subtrim[idx] + trim; + + bool beepTrim = false; + for (int8_t mark=TRIM_MIN; mark!=-6; mark+=TRIM_MAX) { // because (int8_t)125+125==-6 + if ((mark!=0 || !thro) && ((mark!=TRIM_MIN && after>=mark && beforemark))) { + trim = mark - g_model.subtrim[idx]; + beepTrim = true; + } + } + + if ((beforeTRIM_MAX) || (before>after && after TRIM_MAX) { + incSubtrim(idx, trim-TRIM_MAX); + trim = TRIM_MAX; + } + + g_model.phaseData[phase].trim[idx] = (int8_t)trim; + STORE_MODELVARS; + +#if defined (BEEPSPKR) + // toneFreq higher/lower according to trim position + // limit the frequency, range -125 to 125 = toneFreq: 19 to 101 + if (after > TRIM_MAX) + after = TRIM_MAX; + if (after < TRIM_MIN) + after = TRIM_MIN; + after /= 4; + after += 60; +#endif + + if (beepTrim) { + killEvents(event); + warble = false; +#if defined (BEEPSPKR) + beepWarn2Spkr(after); +#else + beepWarn2(); +#endif + } + else { + if (event & _MSK_KEY_REPT) warble = true; +#if defined (BEEPSPKR) + beepTrimSpkr(after); +#else + beepWarn1(); +#endif + } + return 0; + } + return event; +} + +#ifndef SIMU +class AutoLock +{ + uint8_t m_saveFlags; +public: + AutoLock(){ + m_saveFlags = SREG; + cli(); + }; + ~AutoLock(){ + if(m_saveFlags & (1<> 1; + t_ana[1][adc_input] = (t_ana[1][adc_input] + t_ana[0][adc_input]) >> 1; + t_ana[0][adc_input] = (t_ana[0][adc_input] + ADCW ) >> 1; + } +} +/* + s_anaFilt[chan] = (s_anaFilt[chan] + sss_ana[chan]) >> 1; + sss_ana[chan] = (sss_ana[chan] + ss_ana[chan]) >> 1; + ss_ana[chan] = (ss_ana[chan] + s_ana[chan]) >> 1; + s_ana[chan] = (ADC + s_ana[chan]) >> 1; + */ + +void getADC_osmp() +{ + uint16_t temp_ana[8] = {0}; + for (uint8_t adc_input=0;adc_input<8;adc_input++){ + for (uint8_t i=0; i<4;i++) { // Going from 10bits to 11 bits. Addition = n. Loop 4^n times + ADMUX=adc_input|ADC_VREF_TYPE; + // Start the AD conversion + ADCSRA|=0x40; + // Wait for the AD conversion to complete + while ((ADCSRA & 0x10)==0); + ADCSRA|=0x10; + temp_ana[adc_input] += ADCW; + } + s_anaFilt[adc_input] = temp_ana[adc_input] / 2; // divide by 2^n to normalize result. + } +} + +void getADC_single() +{ + for (uint8_t adc_input=0;adc_input<8;adc_input++){ + ADMUX=adc_input|ADC_VREF_TYPE; + // Start the AD conversion + ADCSRA|=0x40; + // Wait for the AD conversion to complete + while ((ADCSRA & 0x10)==0); + ADCSRA|=0x10; + s_anaFilt[adc_input]= ADCW * 2; // use 11 bit numbers + } +} + +void getADC_bandgap() +{ +#if defined(PCBSTD) + ADMUX=0x1E|ADC_VREF_TYPE; // Switch MUX to internal 1.22V reference + _delay_us(5); // short delay to stablise reference voltage + ADCSRA|=0x40; while ((ADCSRA & 0x10)==0); ADCSRA|=0x10; // grab a sample + ADCSRA|=0x40; while ((ADCSRA & 0x10)==0); ADCSRA|=0x10; // again becasue first one is usually inaccurate + BandGap=ADCW; +#else + //BandGap=225; // gruvin: 1.1V internal Vref doesn't seem to work on the ATmega2561. :/ Weird. + // See http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&p=847208#847208 + // In the end, simply using a longer delay (presumably to account for the higher + // impedance Vbg internal source) solved the problem. NOTE: Does NOT adversely affect PPM_out latency. + ADMUX=0x1E|ADC_VREF_TYPE; // Switch MUX to internal 1.1V reference + _delay_us(300); // this somewhat costly delay is the only remedy for stable results on the Atmega2560/1 chips + ADCSRA|=0x40; while ((ADCSRA & 0x10)==0); ADCSRA|=0x10; // again becasue first one is usually inaccurate + BandGap=ADCW; + +#endif +} + +getADCp getADC[3] = { + getADC_single, + getADC_osmp, + getADC_filt +}; +#else +uint16_t BandGap = 225; +#endif + +#ifndef BATT_UNSTABLE_BANDGAP +uint16_t abRunningAvg = 0; +uint8_t g_vbat100mV; +#else +uint16_t g_vbat100mV = 0; +#endif + +volatile uint8_t tick10ms = 0; +uint16_t g_LightOffCounter; + +uint8_t beepAgain = 0; +uint8_t beepAgainOrig = 0; +uint8_t beepOn = false; +int16_t p1val; + +inline bool checkSlaveMode() +{ + // no power -> only phone jack = slave mode + +#if defined(BUZZER_MOD) || defined(BEEPSPKR) + return SLAVE_MODE; +#else + static bool lastSlaveMode = false; + static uint8_t checkDelay = 0; + if (g_beepCnt || beepAgain || beepOn) { + checkDelay = 20; + } + else if (checkDelay) { + --checkDelay; + } + else { + lastSlaveMode = SLAVE_MODE; + } + return lastSlaveMode; +#endif +} + +uint16_t s_timeCumTot; +uint16_t s_timeCumAbs; //laufzeit in 1/16 sec +uint16_t s_timeCumSw; //laufzeit in 1/16 sec +uint16_t s_timeCumThr; //gewichtete laufzeit in 1/16 sec +uint16_t s_timeCum16ThrP; //gewichtete laufzeit in 1/16 sec + +uint8_t s_timerState; +int16_t s_timerVal[2]; +uint8_t Timer1_pre = 0 ; + +uint8_t Timer2_running = 0 ; +uint8_t Timer2_pre = 0 ; +uint16_t timer2 = 0 ; + +uint8_t trimsCheckTimer = 0; + +void resetTimer1() +{ + s_timerState = TMR_OFF; // is changed to RUNNING dep from mode + s_timeCumAbs=0; + s_timeCumThr=0; + s_timeCumSw=0; + s_timeCum16ThrP=0; + Timer1_pre = 0 ; +} + +void resetTimer2() +{ + Timer2_pre = 0 ; + timer2 = 0 ; +} + +inline void incTimers() +{ + TimerData *timer = &g_model.timer1; + + for (uint8_t i=0; i<1; i++, timer=&g_model.timer2) { + int8_t tm = timer->mode; + uint8_t atm = abs(tm); + + // value for time described in timer->mode + // OFFABSRUsRU%ELsEL%THsTH%ALsAL%P1P1%P2P2%P3P3% + uint16_t val = 0; + if (atm>1 && atm=(TMR_VAROFS+MAX_SWITCH-1)){ // toggeled switch + static uint8_t lastSwPos[2] = {0, 0}; + if(!(sw_toggled[i] | s_sum | s_cnt | s_time | lastSwPos[i])) lastSwPos[i] = tm < 0; // if initializing then init the lastSwPos + uint8_t swPos = getSwitch(tm>0 ? tm-(TMR_VAROFS+MAX_SWITCH-1-1) : tm+(TMR_VAROFS+MAX_SWITCH-1-1), 0); + if (swPos && !lastSwPos[i]) sw_toggled[i] = !sw_toggled[i]; // if switch is flipped first time -> change counter state + lastSwPos[i] = swPos; + } + + s_cnt++; + s_sum+=val; + + if ( (Timer1_pre += 1 ) >= 100 ) { + Timer1_pre -= 100 ; + s_time += 100; + val = s_sum/s_cnt; + s_sum -= val*s_cnt; //rest + s_cnt = 0; + + if (atm0 ? tm-(TMR_VAROFS-1) : tm+(TMR_VAROFS-1)), 0); // normal switch + + s_timeCumTot += 1; + s_timeCumAbs += 1; + if(val) s_timeCumThr += 1; + if(sw_toggled) s_timeCumSw += 1; + s_timeCum16ThrP += val/2; + + s_timerVal[i] = timer->val; + if (atm==TMRMODE_NONE) s_timerState = TMR_OFF; + else if (atm==TMRMODE_ABS) s_timerVal[i] -= s_timeCumAbs; + else if(atmval) s_timerState=TMR_BEEPING; + break; + case TMR_BEEPING: + if (s_timerVal[i] <= -MAX_ALERT_TIME) s_timerState=TMR_STOPPED; + if (timer->val == 0) s_timerState=TMR_RUNNING; + break; + case TMR_STOPPED: + break; + } + + static int16_t last_tmr; + + if (last_tmr != s_timerVal[i]) //beep only if seconds advance + { + if(s_timerState==TMR_RUNNING) + { + if(g_eeGeneral.preBeep && timer->val) // beep when 30, 15, 10, 5,4,3,2,1 seconds remaining + { + if(s_timerVal[i]==30) {beepAgain=2; beepWarn2();} //beep three times + if(s_timerVal[i]==20) {beepAgain=1; beepWarn2();} //beep two times + if(s_timerVal[i]==10) beepWarn2(); + if(s_timerVal[i]<= 3) beepWarn2(); + + if(g_eeGeneral.flashBeep && (s_timerVal[i]==30 || s_timerVal[i]==20 || s_timerVal[i]==10 || s_timerVal[i]<=3)) + g_LightOffCounter = FLASH_DURATION; + } + + if(g_eeGeneral.minuteBeep && (((timer->dir ? timer->val-s_timerVal[i] : s_timerVal[i])%60)==0)) //short beep every minute + { + beepWarn2(); + if(g_eeGeneral.flashBeep) g_LightOffCounter = FLASH_DURATION; + } + } + else if(s_timerState==TMR_BEEPING) + { + beepWarn(); + if(g_eeGeneral.flashBeep) g_LightOffCounter = FLASH_DURATION; + } + } + last_tmr = s_timerVal[i]; + if(timer->dir) s_timerVal[i] = timer->val-s_timerVal[i]; //if counting backwards - display backwards + } + } + + if (Timer2_running) { + if ( (Timer2_pre += 1 ) >= 100 ) { + Timer2_pre -= 100 ; + timer2 += 1 ; + } + } +} + +uint8_t s_traceBuf[MAXTRACE]; +uint16_t s_traceWr; +uint16_t s_traceCnt; +inline void trace() // called in perOut - once envery 0.01sec +{ + incTimers(); + + uint16_t val = calibratedStick[CONVERT_MODE(3)-1]; // get throttle channel value + val = (g_eeGeneral.throttleReversed ? RESX-val : val+RESX); + val /= (RESX/16); // calibrate it + + static uint16_t s_time; + static uint16_t s_cnt; + static uint16_t s_sum; + s_cnt++; + s_sum+=val; + if((get_tmr10ms()-s_time)<1000) //10 sec + return; + s_time= get_tmr10ms(); + val = s_sum/s_cnt; + s_sum = 0; + s_cnt = 0; + + s_traceCnt++; + s_traceBuf[s_traceWr++] = val; + if(s_traceWr>=MAXTRACE) s_traceWr=0; +} + +#ifdef HELI +uint16_t isqrt32(uint32_t n) +{ + uint16_t c = 0x8000; + uint16_t g = 0x8000; + + for(;;) { + if((uint32_t)g*g > n) + g ^= c; + c >>= 1; + if(c == 0) + return g; + g |= c; + } +} +#endif + +// static variables used in perOut - moved here so they don't interfere with the stack +// It's also easier to initialize them here. +uint16_t pulses2MHz[120] = {0}; +int16_t anas [NUM_XCHNRAW] = {0}; +int16_t trims[NUM_STICKS] = {0}; +int32_t chans[NUM_CHNOUT] = {0}; +uint32_t inacCounter = 0; +uint16_t inacSum = 0; +uint8_t bpanaCenter = 0; +int16_t sDelay[MAX_MIXERS] = {0}; +int32_t act [MAX_MIXERS] = {0}; +uint8_t swOn [MAX_MIXERS] = {0}; +uint8_t mixWarning; + +inline void evalTrims() +{ + for (uint8_t i=0; i throttle trim if applicable + int16_t v = anas[i]; + int32_t vv = 2*RESX; + int16_t trim = g_model.subtrim[i] + g_model.phaseData[getTrimFlightPhase(i)].trim[i]; + if (IS_THROTTLE(i) && g_model.thrTrim) { + vv = (g_eeGeneral.throttleReversed) ? + ((int32_t)trim+TRIM_MIN)*(RESX+v)/(2*RESX) : + ((int32_t)trim-TRIM_MIN)*(RESX-v)/(2*RESX); + } + else if (trimsCheckTimer > 0) { + trim = 0; + } + + trims[i] = (vv==2*RESX) ? trim*2 : (int16_t)vv*2; // if throttle trim -> trim low end + } +} + +uint8_t evalSticks() +{ +#ifdef HELI + uint16_t d = 0; + if(g_model.swashR.value) { + uint32_t v = (int32_t(calibratedStick[ELE_STICK])*calibratedStick[ELE_STICK] + + int32_t(calibratedStick[AIL_STICK])*calibratedStick[AIL_STICK]); + uint32_t q = int32_t(RESX)*g_model.swashR.value/100; + q *= q; + if(v>q) + d = isqrt32(v); + } +#endif + + uint8_t anaCenter = 0; + + for(uint8_t i=0; i [-1024..1024] + + int16_t v = anaIn(i); + v -= g_eeGeneral.calibMid[i]; + v = v * (int32_t)RESX / (max((int16_t)100,(v>0 ? + g_eeGeneral.calibSpanPos[i] : + g_eeGeneral.calibSpanNeg[i]))); + if(v <= -RESX) v = -RESX; + if(v >= RESX) v = RESX; + calibratedStick[i] = v; //for show in expo + if(!(v/16)) anaCenter |= 1<<(CONVERT_MODE((i+1))-1); + + + if (i < NUM_STICKS) { //only do this for sticks + if (!s_noStickInputs && isFunctionActive(FUNC_TRAINER)) { + // trainer mode + TrainerMix* td = &g_eeGeneral.trainer.mix[i]; + if (td->mode) { + uint8_t chStud = td->srcChn; + int32_t vStud = (g_ppmIns[chStud]- g_eeGeneral.trainer.calib[chStud]); + vStud *= td->studWeight; + vStud /= 50; + switch (td->mode) { + case 1: v += vStud; break; // add-mode + case 2: v = vStud; break; // subst-mode + } + } + } + +#ifdef HELI + if(d && (i==ELE_STICK || i==AIL_STICK)) + v = int32_t(v)*g_model.swashR.value*RESX/(int32_t(d)*100); +#endif + + } + anas[i] = v; //set values for mixer + } + + /* EXPOs */ + applyExpos(anas); + + /* TRIMs */ + evalTrims(); + + return anaCenter; +} + +uint16_t active_functions = 0; // current max = 16 functions + +void evalFunctions() +{ + assert(sizeof(active_functions)*8 >= FUNC_LAST); + + for (uint8_t i=0; iswtch && sd->func) { + uint16_t mask = (1 << (sd->func-1)); + if (getSwitch(sd->swtch, 0)) + active_functions |= mask; + else + active_functions &= (~mask); + } + } +} + +void perOut(int16_t *chanOut) +{ + uint8_t phase = getFlightPhase(); + uint8_t anaCenter = evalSticks(); + + //===========BEEP CENTER================ + anaCenter &= g_model.beepANACenter; + if(((bpanaCenter ^ anaCenter) & anaCenter)) beepWarn1(); + bpanaCenter = anaCenter; + + anas[MIX_MAX-1] = RESX; // MAX + anas[MIX_FULL-1] = RESX; // FULL + +#ifdef HELI + if(g_model.swashR.value) + { + uint32_t v = ((int32_t)anas[ELE_STICK]*anas[ELE_STICK] + (int32_t)anas[AIL_STICK]*anas[AIL_STICK]); + uint32_t q = (int32_t)RESX*g_model.swashR.value/100; + q *= q; + if(v>q) + { + uint16_t d = isqrt32(v); + anas[ELE_STICK] = (int32_t)anas[ELE_STICK]*g_model.swashR.value*RESX/((int32_t)d*100); + anas[AIL_STICK] = (int32_t)anas[AIL_STICK]*g_model.swashR.value*RESX/((int32_t)d*100); + } + } + +#define REZ_SWASH_X(x) ((x) - (x)/8 - (x)/128 - (x)/512) // 1024*sin(60) ~= 886 +#define REZ_SWASH_Y(x) ((x)) // 1024 => 1024 + + if(g_model.swashR.type) + { + int16_t vp = anas[ELE_STICK]+trims[ELE_STICK]; + int16_t vr = anas[AIL_STICK]+trims[AIL_STICK]; + int16_t vc = 0; + if(g_model.swashR.collectiveSource) + vc = anas[g_model.swashR.collectiveSource-1]; + + if(g_model.swashR.invertELE) vp = -vp; + if(g_model.swashR.invertAIL) vr = -vr; + if(g_model.swashR.invertCOL) vc = -vc; + + switch (g_model.swashR.type) + { + case (SWASH_TYPE_120): + vp = REZ_SWASH_Y(vp); + vr = REZ_SWASH_X(vr); + anas[MIX_CYC1-1] = vc - vp; + anas[MIX_CYC2-1] = vc + vp/2 + vr; + anas[MIX_CYC3-1] = vc + vp/2 - vr; + break; + case (SWASH_TYPE_120X): + vp = REZ_SWASH_X(vp); + vr = REZ_SWASH_Y(vr); + anas[MIX_CYC1-1] = vc - vr; + anas[MIX_CYC2-1] = vc + vr/2 + vp; + anas[MIX_CYC3-1] = vc + vr/2 - vp; + break; + case (SWASH_TYPE_140): + vp = REZ_SWASH_Y(vp); + vr = REZ_SWASH_Y(vr); + anas[MIX_CYC1-1] = vc - vp; + anas[MIX_CYC2-1] = vc + vp + vr; + anas[MIX_CYC3-1] = vc + vp - vr; + break; + case (SWASH_TYPE_90): + vp = REZ_SWASH_Y(vp); + vr = REZ_SWASH_Y(vr); + anas[MIX_CYC1-1] = vc - vp; + anas[MIX_CYC2-1] = vc + vr; + anas[MIX_CYC3-1] = vc - vr; + break; + default: + break; + } + } +#endif + + if (s_noStickInputs) { + for (uint8_t i=0; idestCh==0) || (md->destCh>NUM_CHNOUT)) break; + + if (md->phase != 0) { + if (md->phase > 0) { + if (phase+1 != md->phase) + continue; + } + else { + if (phase+1 == -md->phase) + continue; + } + } + + //Notice 0 = NC switch means not used -> always on line + int16_t v = 0; + uint8_t swTog; + + //swOn[i]=false; + if(!getSwitch(md->swtch,1)){ // switch on? if no switch selected => on + swTog = swOn[i]; + swOn[i] = false; + if(md->srcRaw!=MIX_MAX && md->srcRaw!=MIX_FULL) continue;// if not MAX or FULL - next loop + if(md->mltpx==MLTPX_REP) continue; // if switch is off and REPLACE then off + v = (md->srcRaw == MIX_FULL ? -RESX : 0); // switch is off and it is either MAX=0 or FULL=-512 + } + else { + swTog = !swOn[i]; + swOn[i] = true; + uint8_t k = md->srcRaw-1; + v = anas[k]; //Switch is on. MAX=FULL=512 or value. + if(k>=CHOUT_BASE && (kmixWarn) mixWarning |= 1<<(md->mixWarn-1); // Mix warning + } + + //========== INPUT OFFSET =============== + if(md->sOffset) v += calc100toRESX(md->sOffset); + + //========== DELAY and PAUSE =============== + if (md->speedUp || md->speedDown || md->delayUp || md->delayDown) // there are delay values + { +#define DEL_MULT 256 + + //if(init) { + //act[i]=(int32_t)v*DEL_MULT; + //swTog = false; + //} + int16_t diff = v-act[i]/DEL_MULT; + + if(swTog) { + //need to know which "v" will give "anas". + //curves(v)*weight/100 -> anas + // v * weight / 100 = anas => anas*100/weight = v + if(md->mltpx==MLTPX_REP) + { + act[i] = (int32_t)anas[md->destCh-1+CHOUT_BASE]*DEL_MULT; + act[i] *=100; + if(md->weight) act[i] /= md->weight; + } + diff = v-act[i]/DEL_MULT; + if(diff) sDelay[i] = (diff<0 ? md->delayUp : md->delayDown) * 100; + } + + if(sDelay[i]){ // perform delay + if(tick10ms) sDelay[i]--; + v = act[i]/DEL_MULT; + diff = 0; + } + + if(diff && (md->speedUp || md->speedDown)){ + //rate = steps/sec => 32*1024/100*md->speedUp/Down + //act[i] += diff>0 ? (32768)/((int16_t)100*md->speedUp) : -(32768)/((int16_t)100*md->speedDown); + //-100..100 => 32768 -> 100*83886/256 = 32768, For MAX we divide by 2 sincde it's asymmetrical + if(tick10ms) { + int32_t rate = (int32_t)DEL_MULT*2048*100; + if(md->weight) rate /= abs(md->weight); + act[i] = (diff>0) ? ((md->speedUp>0) ? act[i]+(rate)/((int16_t)100*md->speedUp) : (int32_t)v*DEL_MULT) : + ((md->speedDown>0) ? act[i]-(rate)/((int16_t)100*md->speedDown) : (int32_t)v*DEL_MULT) ; + } + + if(((diff>0) && (v<(act[i]/DEL_MULT))) || ((diff<0) && (v>(act[i]/DEL_MULT)))) act[i]=(int32_t)v*DEL_MULT; //deal with overflow + v = act[i]/DEL_MULT; + } + } + + //========== CURVES =============== + if (md->curve) + v = applyCurve(v, md->curve, md->srcRaw); + + //========== TRIM =============== + if((md->carryTrim==0) && (md->srcRaw>0) && (md->srcRaw<=4)) v += trims[md->srcRaw-1]; // 0 = Trim ON = Default + + //========== MULTIPLEX =============== + int32_t dv = (int32_t)v*md->weight; + switch(md->mltpx){ + case MLTPX_REP: + chans[md->destCh-1] = dv; + break; + case MLTPX_MUL: + chans[md->destCh-1] *= dv/100l; + chans[md->destCh-1] /= RESXl; + break; + default: // MLTPX_ADD + chans[md->destCh-1] += dv; //Mixer output add up to the line (dv + (dv>0 ? 100/2 : -100/2))/(100); + break; + } + } + + //========== MIXER WARNING =============== + // 1,2 or 3 "bips" (short beeps) + //1= 00,08 + //2= 24,32,40 + //3= 56,64,72,80 + // Gruvin: Sometimes, one or more of these beeps do not fire. That will be because the tmr10ms counter + // may not necessarily be exactly (==) the below figures when queried from inside perOut(). + // But we only ever want a beep to fire once, so we have to use anexact counter match (not a range). + // My solution was to make mixWarning global and have the counter checks done inside per10ms(); + /* { + uint16_t tmr10ms ; + tmr10ms = get_tmr10ms() ; + if(mixWarning & 1) if(((tmr10ms&0xFF)== 0)) beepWarn1(); + if(mixWarning & 2) if(((tmr10ms&0xFF)== 64) || ((tmr10ms&0xFF)== 72)) beepWarn1(); + if(mixWarning & 4) if(((tmr10ms&0xFF)==128) || ((tmr10ms&0xFF)==136) || ((tmr10ms&0xFF)==144)) beepWarn1(); + } */ + + //========== LIMITS =============== + for(uint8_t i=0;i 1024*100 + // later we multiply by the limit (up to 100) and then we need to normalize + // at the end chans[i] = chans[i]/100 => -1024..1024 + // interpolate value with min/max so we get smooth motion from center to stop + // this limits based on v original values and min=-1024, max=1024 RESX=1024 + + int32_t q = chans[i];// + (int32_t)g_model.limitData[i].offset*100; // offset before limit + + chans[i] /= 100; // chans back to -1024..1024 + ex_chans[i] = chans[i]; //for getswitch + + int16_t ofs = g_model.limitData[i].offset; + int16_t lim_p = 10*(g_model.limitData[i].max+100); + int16_t lim_n = 10*(g_model.limitData[i].min-100); //multiply by 10 to get same range as ofs (-1000..1000) + if(ofs>lim_p) ofs = lim_p; + if(ofs0) ? + q*((int32_t)lim_p-ofs)/100000 : + -q*((int32_t)lim_n-ofs)/100000 ; //div by 100000 -> output = -1024..1024 + + q += calc1000toRESX(ofs); + lim_p = calc1000toRESX(lim_p); + lim_n = calc1000toRESX(lim_n); + if(q>lim_p) q = lim_p; + if(q49) { + inacCounter++; + uint16_t tsum = 0; + for(uint8_t i=0;i<4;i++) tsum += anaIn(i)/64; // reduce sensitivity + if(tsum!=inacSum){ + inacSum = tsum; + inacCounter=0; + } + if(inacCounter>((uint32_t)g_eeGeneral.inactivityTimer*100*60)) + if((inacCounter&0x3F)==10) beepWarn(); + } + + if (trimsCheckTimer > 0) + trimsCheckTimer -= 1; + +#ifndef ASYNC_WRITE + eeCheck(); +#endif + + lcd_clear(); + uint8_t evt=getEvent(); + evt = checkTrim(evt); + + if(g_LightOffCounter) g_LightOffCounter--; + if(evt) g_LightOffCounter = g_eeGeneral.lightAutoOff*500; // on keypress turn the light on 5*100 + + if( getSwitch(g_eeGeneral.lightSw,0) || g_LightOffCounter) + BACKLIGHT_ON; + else + BACKLIGHT_OFF; + + static int16_t p1valprev; + p1valdiff = (p1val-calibratedStick[6])/32; + if(p1valdiff) { + p1valdiff = (p1valprev-calibratedStick[6])/2; + p1val = calibratedStick[6]; + } + p1valprev = calibratedStick[6]; + + g_menuStack[g_menuStackPtr](evt); + refreshDiplay(); + + // PPM signal on phono-jack. In or out? ... + if(checkSlaveMode()) { + PORTG &= ~(1<49) { + beepErr(); + if (g_eeGeneral.flashBeep) g_LightOffCounter = FLASH_DURATION; + } + + } + break; + + + + case 3: + { + // The various "beep" tone lengths + static prog_uint8_t APM beepTab[]= { + // 0 1 2 3 4 + 0, 0, 0, 0, 0, //quiet + 0, 1, 8, 30, 100, //silent + 1, 1, 8, 30, 100, //normal + 1, 1, 15, 50, 150, //for motor + 10, 10, 30, 50, 150, //for motor + }; + memcpy_P(g_beepVal,beepTab+5*g_eeGeneral.beeperVal,5); + //g_beepVal = BEEP_VAL; + } + break; + } + +} +int16_t g_ppmIns[8]; +uint8_t ppmInState = 0; //0=unsync 1..8= wait for value i-1 + +#ifndef SIMU + +#define HEART_TIMER2Mhz 1; +#define HEART_TIMER10ms 2; + +uint8_t heartbeat; + + +ISR(TIMER1_COMPA_vect) //2MHz pulse generation +{ + static uint8_t pulsePol; + static uint16_t *pulsePtr = pulses2MHz; + + // TODO understand what Bryan did here + uint8_t i = 0; + while((TCNT1L < 10) && (++i < 50)) // Timer does not read too fast, so i + ; + uint16_t dt=TCNT1;//-OCR1A; + + if(pulsePol) + { + PORTB |= (1</=0 + //beepon/off + //beepagain y/n + if(g_beepCnt) { + if(!beepAgainOrig) { + beepAgainOrig = g_beepCnt; + beepOn = true; + } + g_beepCnt--; + } + else + { + if(beepAgain && beepAgainOrig) { + beepOn = !beepOn; + g_beepCnt = beepOn ? beepAgainOrig : 8; + if(beepOn) beepAgain--; + } + else { + beepAgainOrig = 0; + beepOn = false; + warble = false; + } + } + +#if defined (PCBV3) + // G: use timer0 WGM mode tone generator for beeps + if(beepOn) + { + static bool warbleC; + warbleC = warble && !warbleC; + if(warbleC) + TCCR0A &= ~(0b01<4000 && val < 16000) // G: Priorotise reset pulse. (Needed when less than 8 incoming pulses) + ppmInState = 1; // triggered + else + { + if (ppmInState && ppmInState<=8) + { + if (val>800 && val<2200) // if valid pulse-width range + { + g_ppmIns[ppmInState++ - 1] = + (int16_t)(val - 1500)*(g_eeGeneral.PPM_Multiplier+10)/10; //+-500 != 512, but close enough. + } + else + ppmInState = 0; // not triggered + } + } + + lastCapt = capture; + + cli(); +#if defined (PCBV3) + TIMSK3 |= (1<> 1); +} +#endif + +extern uint16_t g_timeMain; + +/* +// gruvin: Fuse declarations work if we use the .elf file for AVR Studio (v4) +// instead of the Intel .hex files. They should also work with AVRDUDE v5.10 +// (reading from the .hex file), since a bug relating to Intel HEX file record +// interpretation was fixed. However, I leave these commented out, just in case +// it causes trouble for others. +#if defined (PCBV3) +// See fuses_2561.txt + FUSES = + { + // BOD=4.3V, WDT OFF (enabled in code), Boot Flash 4096 bytes at 0x1F000, + // JTAG and OCD enabled, EESAVE enabled, BOOTRST/CKDIV8/CKOUT disabled, + // Full swing Xtal oscillator. Start-up 16K clks + 0ms. BOD enabled. + 0xD7, // .low + 0x11, // .high + 0xFC // .extended + }; +#else + FUSES = + { + // G: Changed 2011-07-04 to include EESAVE. Tested OK on stock 9X + 0x1F, // LFUSE + 0x11, // HFUSE + 0xFF // EFUSE + }; +#endif +*/ + +#ifdef DEBUG +uint16_t DEBUG1 = 0; +uint16_t DEBUG2 = 0; +#endif + +extern unsigned char __bss_end ; + +unsigned int stack_free() +{ + unsigned char *p ; + + p = &__bss_end + 1 ; + while ( *p == 0x55 ) + { + p+= 1 ; + } + return p - &__bss_end ; +} + +#endif + + +void instantTrim() +{ + for (uint8_t i=0; i trim=%d\n", i, anas[i], i, trims[i], g_model.phaseData[phase].trim[i], g_model.subtrim[i], trim); + if (trim < TRIM_MIN) { + incSubtrim(i, trim-TRIM_MIN); + trim = TRIM_MIN; + } + if (trim > TRIM_MAX) { + incSubtrim(i, trim-TRIM_MAX); + trim = TRIM_MAX; + } + g_model.phaseData[phase].trim[i] = (int8_t)trim; + } + } + + STORE_MODELVARS; + beepWarn1(); +} + +void moveTrimsToOffsets() // copy state of 3 primary to subtrim +{ + int16_t zero_chans512[NUM_CHNOUT]; + + s_noStickInputs = true; + perOut(zero_chans512); // do output loop - zero input sticks + s_noStickInputs = false; + + for (uint8_t i=0; i= TRIM_MIN && trim <= TRIM_MAX) + g_model.phaseData[phase].trim[i] = 0; + } + } + } + + STORE_MODELVARS; + beepWarn1(); +} + +#ifndef SIMU +int main(void) +{ + // Set up I/O port data diretions and initial states + + DDRA = 0xff; PORTA = 0x00; +#if defined (PCBV3) + DDRB = 0x97; PORTB = 0x1e; // 7:AUDIO, SD_CARD[6:SDA 5:SCL 4:CS 3:MISO 2:MOSI 1:SCK], 0:PPM_OUT + DDRC = 0x3f; PORTC = 0xc0; // PC0 used for LCD back light control + DDRD = 0x0F; PORTD = 0xff; // 7:4=inputs (keys/trims, pull-ups on), 3:0=outputs (keyscan row select) +#else + DDRB = 0x81; PORTB = 0x7e; //pullups keys+nc + DDRC = 0x3e; PORTC = 0xc1; //pullups nc + DDRD = 0x00; PORTD = 0xff; //pullups keys +#endif + DDRE = (1< + * - Bryan J. Rentoul (Gruvin) + * + * gruvin9x is based on code named er9x by + * Author - Erez Raviv , which is in turn + * was based on the original (and ongoing) project by Thomas Husterer, + * th9x -- http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + */ + +#ifndef gruvin9x_h +#define gruvin9x_h + +#define VERS 1 +#define SUB_VERS 2 + +// #define ASYNC_WRITE + +#include +#include + +#if defined(PCBV3) +#include "time.h" +#endif + +#ifdef SIMU + #include "simpgmspace.h" + #define APM + #include "stdio.h" +#else +///opt/cross/avr/include/avr/pgmspace.h +#include +#include +#define assert(x) +//disable whole pgmspace functionality for all avr-gcc because +//avr-gcc > 4.2.1 does not work anyway +//http://www.mail-archive.com/gcc-bugs@gcc.gnu.org/msg239240.html +//http://gcc.gnu.org/bugzilla/show_bug.cgi?id=34734 +// +//Workarounds: +// +//PSTR is fixed below +//all prog_xx definitions must use APM explicitely + +//#define __ATTR_PROGMEM__ +#include +#ifdef __cplusplus +#define APM __attribute__(( section(".progmem.data") )) +#undef PSTR +#define PSTR(s) (__extension__({static prog_char APM __c[] = (s);&__c[0];})) +#endif + +#include +#include +#include +#define F_CPU 16000000UL // 16 MHz +#include +#define pgm_read_adr(address_short) pgm_read_word(address_short) +#include +#endif + +#include "file.h" + +extern RlcFile theFile; //used for any file operation + +// +// elev thr +// LV RV +// 2 ^ 4 ^ +// 1 2 +// | rudd | aile +// <----X--3-> LH <----X--0-> RH +// 6 | 7 1 | 0 +// | | +// 3 v 5 v +// + + +//PORTA 7 6 5 4 3 2 1 0 +// O O O O O O O O +// ------------------------ LCD_DAT ----------------------- +// +//PORTB 7 6 5 4 3 2 1 0 +// O i i i i i i O +// light KEY_LFT KEY_RGT KEY_UP KEY_DWN KEY_EXT KEY_MEN PPM +// +//PORTC 7 6 5 4 3 2 1 0 +// - - O O O O O - +// NC NC LCD_E LCD_RNW LCD_A0 LCD_RES LCD_CS1 NC +// +//PORTD 7 6 5 4 3 2 1 0 +// i i i i i i i i +// TRM_D_DWN _UP TRM_C_DWN _UP TRM_B_DWN _UP TRM_A_DWN _UP +// +//PORTE 7 6 5 4 3 2 1 0 +// i i i i O i i i +// PPM_IN ID2 Trainer Gear Buzzer ElevDR AileDR THRCT +// +//PORTF 7 6 5 4 3 2 1 0 +// ai ai ai ai ai ai ai ai +// ANA_ BAT PITT_TRM HOV_THR HOV_PIT STCK_LH STCK_RV STCK_LV STCK_RH +// rud thro elev aile +//PORTG 7 6 5 4 3 2 1 0 +// - - - O i i i +// SIM_CTL ID1 NC RF_POW RuddDR + +#define PORTA_LCD_DAT PORTA + +#if defined (PCBV3) +#define OUT_C_LIGHT 0 +#else +#define OUT_B_LIGHT 7 +#endif + +#define INP_B_KEY_LFT 6 +#define INP_B_KEY_RGT 5 +#define INP_B_KEY_UP 4 +#define INP_B_KEY_DWN 3 +#define INP_B_KEY_EXT 2 +#define INP_B_KEY_MEN 1 +#define OUT_B_PPM 0 +#define PORTC_LCD_CTRL PORTC +#define OUT_C_LCD_E 5 +#define OUT_C_LCD_RnW 4 +#define OUT_C_LCD_A0 3 +#define OUT_C_LCD_RES 2 +#define OUT_C_LCD_CS1 1 + +#define INP_D_TRM_LH_UP 7 +#define INP_D_TRM_LH_DWN 6 +#define INP_D_TRM_RV_DWN 5 +#define INP_D_TRM_RV_UP 4 +#define INP_D_TRM_LV_DWN 3 +#define INP_D_TRM_LV_UP 2 +#define INP_D_TRM_RH_DWN 1 +#define INP_D_TRM_RH_UP 0 + +// Legacy support for USART1 hardware mod [DEPRECATED] +#if defined(USART1FREED) +// do not undef the original INP_D_TRM_LV_DWN/UP as we need them later + #define INP_C_TRM_LV_UP 0 + #define INP_G_TRM_LV_DWN 2 +#endif + +#define INP_E_PPM_IN 7 +#define INP_E_ID2 6 +#define INP_E_Trainer 5 +#define INP_E_Gear 4 +#define OUT_E_BUZZER 3 +#define INP_E_ElevDR 2 + + +#define INP_E_AileDR 1 +#define INP_E_ThrCt 0 + +#if defined(JETI) || defined(FRSKY) + #undef INP_E_ThrCt + #undef INP_E_AileDR + #define INP_C_ThrCt 6 + #define INP_C_AileDR 7 +#endif + +#if defined (BEEPSPKR) +#define BEEP_KEY_TIME 5 +#define BEEP_DEFAULT_FREQ 50 +#define BEEP_KEY_UP_FREQ 55 +#define BEEP_KEY_DOWN_FREQ 45 +#endif + +#define OUT_G_SIM_CTL 4 //1 : phone-jack=ppm_in +#define INP_G_ID1 3 +#define INP_G_RF_POW 1 +#define INP_G_RuddDR 0 + +#define SLAVE_MODE (PING & (1< template setup, Second is relevant channel out +{1,2,3,4},{1,2,4,3},{1,3,2,4},{1,3,4,2},{1,4,2,3},{1,4,3,2}, +{2,1,3,4},{2,1,4,3},{2,3,1,4},{2,3,4,1},{2,4,1,3},{2,4,3,1}, +{3,1,2,4},{3,1,4,2},{3,2,1,4},{3,2,4,1},{3,4,1,2},{3,4,2,1}, +{4,1,2,3},{4,1,3,2},{4,2,1,3},{4,2,3,1},{4,3,1,2},{4,3,2,1} }; + +//convert from mode 1 to mode g_eeGeneral.stickMode +//NOTICE! => 1..4 -> 1..4 +#define CONVERT_MODE(x) (((x)<=4) ? modn12x3[g_eeGeneral.stickMode][((x)-1)] : (x)) +#define CHANNEL_ORDER(x) (chout_ar[g_eeGeneral.templateSetup][(x)-1]) +#define THR_STICK (2-(g_eeGeneral.stickMode&1)) +#define ELE_STICK (1+(g_eeGeneral.stickMode&1)) +#define AIL_STICK ((g_eeGeneral.stickMode&2) ? 0 : 3) +#define RUD_STICK ((g_eeGeneral.stickMode&2) ? 3 : 0) + +enum EnumKeys { + KEY_MENU , + KEY_EXIT , + KEY_DOWN , + KEY_UP , + KEY_RIGHT , + KEY_LEFT , + TRM_LH_DWN , + TRM_LH_UP , + TRM_LV_DWN , + TRM_LV_UP , + TRM_RV_DWN , + TRM_RV_UP , + TRM_RH_DWN , + TRM_RH_UP , + //SW_NC , + //SW_ON , + SW_ThrCt , + SW_RuddDR , + SW_ElevDR , + SW_ID0 , + SW_ID1 , + SW_ID2 , + SW_AileDR , + SW_Gear , + SW_Trainer +}; + +#define SWITCHES_STR "THR""RUD""ELE""ID0""ID1""ID2""AIL""GEA""TRN""SW1""SW2""SW3""SW4""SW5""SW6""SW7""SW8""SW9""SWA""SWB""SWC" + +#define CURV_STR "---x>0x<0|x|f>0f<0|f|c1 c2 c3 c4 c5 c6 c7 c8 c9 c10c11c12c13c14c15c16" +#define CURVE_BASE 7 + +#define CSWITCH_STR "---- v>ofs vofs|v|v2 ""v1=v2 ""v1<=v2 " +#define CSW_LEN_FUNC 7 + +#define FSWITCH_STR "---- ""Trainer ""Instant Trim ""Trims2Offsets ""Telemetry View" +#define FSW_LEN_FUNC 14 + +#define SWASH_TYPE_STR "--- ""120 ""120X ""140 ""90 " + +#define SWASH_TYPE_120 1 +#define SWASH_TYPE_120X 2 +#define SWASH_TYPE_140 3 +#define SWASH_TYPE_90 4 +#define SWASH_TYPE_NUM 4 + +#define CS_OFF 0 +#define CS_VPOS 1 //v>offset +#define CS_VNEG 2 //voffset +#define CS_ANEG 4 //|v| inline t abs(t a){ return a>0?a:-a; } +/// liefert das Minimum der Argumente +template inline t min(t a, t b){ return a inline t max(t a, t b){ return a>b?a:b; } +template inline int8_t sgn(t a){ return a>0 ? 1 : (a < 0 ? -1 : 0); } + +/// Markiert einen EEPROM-Bereich als dirty. der Bereich wird dann in +/// eeCheck ins EEPROM zurueckgeschrieben. +void eeWriteBlockCmp(const void *i_pointer_ram, void *i_pointer_eeprom, size_t size); +void eeDirty(uint8_t msk); +void eeCheck(bool immediately=false); +//void eeWriteGeneral(); +void eeReadAll(); +bool eeModelExists(uint8_t id); +uint16_t eeLoadModelName(uint8_t id, char *name); +void eeLoadModel(uint8_t id); +//void eeSaveModel(uint8_t id); +bool eeDuplicateModel(uint8_t id); + +///number of real input channels (1-9) plus virtual input channels X1-X4 +#define NUM_XCHNRAW (NUM_STICKS+NUM_POTS+2/*MAX/FULL*/+3/*CYC1-CYC3*/+NUM_PPM+NUM_CHNOUT+NUM_TELEMETRY) +///number of real output channels (CH1-CH8) plus virtual output channels X1-X4 +#define NUM_XCHNOUT (NUM_CHNOUT) //(NUM_CHNOUT)//+NUM_VIRT) + +extern inline int16_t calc100toRESX(int8_t x) +{ + // return (int16_t)x*10 + x/4 - x/64; + return ((x*41)>>2) - x/64; +} + +extern inline int16_t calc1000toRESX(int16_t x) +{ + // return x + x/32 - x/128 + x/512; + int16_t y = x>>5; + x+=y; + y=y>>2; + x-=y; + return x+(y>>2); +} + +extern volatile uint16_t g_tmr10ms; + +extern inline uint16_t get_tmr10ms() +{ + uint16_t time ; + cli(); + time = g_tmr10ms ; + sei(); + return time ; +} + +#define TMR_VAROFS 16 + +#define SUB_MODE_V 1 +#define SUB_MODE_H 2 +#define SUB_MODE_H_DBL 3 + +void setupPulses(); +void setupPulsesPPM(); +void setupPulsesSilver(); +void setupPulsesTracerCtp1009(); + +void initTemplates(); + +#include "lcd.h" +extern const char stamp1[]; +extern const char stamp2[]; +extern const char stamp3[]; +extern const char stamp4[]; +extern const char stamp5[]; +#include "myeeprom.h" + +#ifdef JETI +// Jeti-DUPLEX Telemetry +extern uint16_t jeti_keys; +#include "jeti.h" +#endif + +#if defined (FRSKY) +// FrSky Telemetry +#include "frsky.h" +#endif + +#ifndef BATT_UNSTABLE_BANDGAP +extern uint16_t abRunningAvg; +extern uint8_t g_vbat100mV; +#else +extern uint16_t g_vbat100mV; +#endif +extern volatile uint16_t g_tmr10ms; +extern volatile uint8_t g_blinkTmr10ms; +extern uint8_t g_beepCnt; +extern uint8_t g_beepVal[5]; +extern const PROGMEM char modi12x3[]; +extern uint16_t pulses2MHz[120]; +extern int16_t g_ppmIns[8]; +extern int16_t g_chans512[NUM_CHNOUT]; +extern volatile uint8_t tick10ms; +extern uint16_t BandGap; + +extern uint16_t expou(uint16_t x, uint16_t k); +extern int16_t expo(int16_t x, int16_t k); +extern int16_t intpol(int16_t, uint8_t); +extern int16_t applyCurve(int16_t, uint8_t, uint8_t srcRaw); +extern void applyExpos(int16_t *anas); + +extern uint16_t anaIn(uint8_t chan); +extern int16_t calibratedStick[7]; +extern int16_t ex_chans[NUM_CHNOUT]; + +#define FLASH_DURATION 50 + +extern uint8_t beepAgain; +extern uint16_t g_LightOffCounter; +extern uint8_t mixWarning; + +/// Erzeugt einen beep der laenge b +inline void _beep(uint8_t b) { + g_beepCnt=b; +} + +extern uint8_t toneFreq; +#if defined (PCBV3) +inline void _beepSpkr(uint8_t d, uint8_t f) +{ + g_beepCnt=d; + OCR0A = (5000 / f); // sticking with old values approx 20(abs. min) to 90, 60 being the default tone(?). +} +#elif defined (BEEPSPKR) +inline void _beepSpkr(uint8_t d, uint8_t f) +{ + g_beepCnt=d; + toneFreq=f; +} +#endif + +#if defined (BEEPSPKR) + +#define beepKeySpkr(freq) _beepSpkr(g_beepVal[0],freq) +#define beepTrimSpkr(freq) _beepSpkr(g_beepVal[0],freq) +#define beepWarn1Spkr(freq) _beepSpkr(g_beepVal[1],freq) +#define beepWarn2Spkr(freq) _beepSpkr(g_beepVal[2],freq) +#define beepKey() _beepSpkr(g_beepVal[0],BEEP_DEFAULT_FREQ) +#define beepWarn() _beepSpkr(g_beepVal[3],BEEP_DEFAULT_FREQ) +#define beepWarn1() _beepSpkr(g_beepVal[1],BEEP_DEFAULT_FREQ) +#define beepWarn2() _beepSpkr(g_beepVal[2],BEEP_DEFAULT_FREQ) +#define beepErr() _beepSpkr(g_beepVal[4],BEEP_DEFAULT_FREQ) + +#else + +/// Erzeugt einen kurzen beep +#define beepKey() _beep(g_beepVal[0]) +#define beepWarn() _beep(g_beepVal[3]) +#define beepWarn1() _beep(g_beepVal[1]) +#define beepWarn2() _beep(g_beepVal[2]) +#define beepErr() _beep(g_beepVal[4]) + +#endif + +// MM/SD card Disk IO Support +#if defined (PCBV3) +#include "rtc.h" +extern void disk_timerproc(void); +extern time_t g_unixTime; // global unix timestamp -- hold current time in seconds since 1970-01-01 00:00:00 +extern uint8_t g_ms100; // defined in drivers.cpp +#endif + +extern PhaseData *phaseaddress(uint8_t idx); +extern ExpoData *expoaddress(uint8_t idx); +extern MixData *mixaddress(uint8_t idx); +extern LimitData *limitaddress(uint8_t idx); + +extern void incSubtrim(uint8_t idx, int16_t inc); +extern void instantTrim(); +extern void moveTrimsToOffsets(); // move state of 3 primary trims to offsets + +extern uint16_t active_functions; +inline bool isFunctionActive(uint8_t func) +{ + return active_functions & (1 << (func-1)); +} + + +#endif // gruvin9x_h +/*eof*/ diff --git a/src/gtests.cpp b/src/gtests.cpp new file mode 100644 index 000000000..646ac3949 --- /dev/null +++ b/src/gtests.cpp @@ -0,0 +1,218 @@ +/* + * Authors (alphabetical order) + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * + * gruvin9x is based on code named er9x by + * Author - Erez Raviv , which is in turn + * was based on the original (and ongoing) project by Thomas Husterer, + * th9x -- http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + */ + +#include +#include "gruvin9x.h" + +uint16_t anaIn(uint8_t chan) +{ + return 0; +} + +TEST(incSubtrim, test1) { + memset(&g_model, 0, sizeof(g_model)); + incSubtrim(0, 10); + for (int p=0; ptrim[0], -10) << "Should be equal to -10"; + } + EXPECT_EQ(g_model.subtrim[0], 10) << "Should be equal to 10"; +} + +TEST(incSubtrim, test2) { + memset(&g_model, 0, sizeof(g_model)); + phaseaddress(0)->trim[0] = -120; + phaseaddress(1)->trim[0] = +125; + incSubtrim(0, +10); // FP1 is trimmed by +10 + EXPECT_EQ(phaseaddress(0)->trim[0], TRIM_MIN); + EXPECT_EQ(phaseaddress(1)->trim[0], +115); // and set to 125 just after + EXPECT_EQ(g_model.subtrim[0], 10); +} + +TEST(trims, greaterTrimLink) +{ + memset(&g_model, 0, sizeof(g_model)); + phaseaddress(1)->trim[0] = -127; // link to FP3 trim + phaseaddress(3)->trim[0] = 32; + EXPECT_EQ(phaseaddress(getTrimFlightPhase(0, 1))->trim[0], 32); +} + +TEST(trims, chainedTrims) +{ + memset(&g_model, 0, sizeof(g_model)); + phaseaddress(0)->trim[0] = 32; + phaseaddress(1)->trim[0] = +127; // link to FP0 trim + phaseaddress(2)->trim[0] = -128; // link to FP1 trim + EXPECT_EQ(phaseaddress(getTrimFlightPhase(0, 2))->trim[0], 32); +} + +TEST(trims, infiniteChainedTrims) +{ + memset(&g_model, 0, sizeof(g_model)); + phaseaddress(0)->trim[0] = 32; + phaseaddress(1)->trim[0] = -127; // link to FP3 trim + phaseaddress(2)->trim[0] = -128; // link to FP1 trim + phaseaddress(3)->trim[0] = -127; // link to FP2 trim + EXPECT_EQ(phaseaddress(getTrimFlightPhase(0, 2))->trim[0], 32); +} + +TEST(outdezNAtt, test_unsigned) { + uint16_t altitude = 65530; + + uint8_t refBuf[sizeof(displayBuf)]; + memset(displayBuf, 0, sizeof(displayBuf)); + lcd_putc(0*FWNUM, 0, '6'); + lcd_putc(1*FWNUM, 0, '5'); + lcd_putc(2*FWNUM, 0, '5'); + lcd_putc(3*FWNUM, 0, '3'); + lcd_putc(4*FWNUM, 0, '0'); + memcpy(refBuf, displayBuf, sizeof(displayBuf)); + + memset(displayBuf, 0, sizeof(displayBuf)); + lcd_outdezNAtt(1, 0, altitude, LEFT|UNSIGN); + + EXPECT_EQ(memcmp(refBuf, displayBuf, sizeof(displayBuf)), 0) << "Unsigned numbers will be bad displayed"; +} + +TEST(EEPROM, test1) { + eepromFile = NULL; // in memory + RlcFile f; + uint8_t buf[1000]; + uint8_t buf2[1000]; + + EeFsFormat(); + + for(int i=0; i<10000; i++) { + int size = rand()%800; + for(int j=0; j +#include + +#else /* Embedded platform */ + +/* These types must be 16-bit, 32-bit or larger integer */ +typedef int INT; +typedef unsigned int UINT; + +/* These types must be 8-bit integer */ +typedef char CHAR; +typedef unsigned char UCHAR; +typedef unsigned char BYTE; + +/* These types must be 16-bit integer */ +typedef short SHORT; +typedef unsigned short USHORT; +typedef unsigned short WORD; +typedef unsigned short WCHAR; + +/* These types must be 32-bit integer */ +typedef long LONG; +typedef unsigned long ULONG; +typedef unsigned long DWORD; + +#endif + +#endif diff --git a/src/jeti.cpp b/src/jeti.cpp new file mode 100644 index 000000000..ffd8ff158 --- /dev/null +++ b/src/jeti.cpp @@ -0,0 +1,200 @@ +/* + * gruvin9x Author Bryan J.Rentoul (Gruvin) + * + * jeti.cpp original author - Karl Szmutny + * + * Based on the Code from Peter "woggle" Mack, mac@denich.net + * -> http://svn.mikrokopter.de/filedetails.php?repname=Projects&path=/Transportables_Koptertool/trunk/jeti.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + */ + +#include "jeti.h" +#include "gruvin9x.h" + +uint16_t jeti_keys = JETI_KEY_NOCHANGE; +uint8_t JetiBuffer[32]; // 32 characters +uint8_t JetiBufferReady; + + +ISR (USART0_RX_vect) +{ + uint8_t stat; + uint8_t rh; + uint8_t rl; + static uint8_t jbp; + + stat = UCSR0A; + rh = UCSR0B; + rl = UDR0; + + + if (stat & ((1 << FE0) | (1 << DOR0) | (1 << UPE0))) + { // discard buffer and start new on any error + JetiBufferReady = 0; + jbp = 0; + } + else if ((rh & (1 << RXB80)) == 0) + { // control + if (rl == 0xfe) + { // start condition + JetiBufferReady = 0; + jbp = 0; + } + else if (rl == 0xff) + { // stop condition + JetiBufferReady = 1; + } + } + else + { // data + if (jbp < 32) + { + JetiBuffer[jbp++] = rl; + } + } + +} + + + +void JETI_Init (void) +{ + + jeti_keys = JETI_KEY_NOCHANGE; + + DDRE &= ~(1 << DDE0); // set RXD0 pin as input + PORTE &= ~(1 << PORTE0); // disable pullup on RXD0 pin + +#undef BAUD +#define BAUD 9600 +#include + UBRR0H = UBRRH_VALUE; + UBRR0L = UBRRL_VALUE; + + UCSR0A &= ~(1 << U2X0); // disable double speed operation + + // set 9O1 + UCSR0C = (1 << UPM01) | (1 << UPM00) | (1 << UCSZ01) | (1 << UCSZ00); + UCSR0B = (1 << UCSZ02); + + // flush receive buffer + while ( UCSR0A & (1 << RXC0) ) UDR0; + +} + + +void JETI_DisableTXD (void) +{ + UCSR0B &= ~(1 << TXEN0); // disable TX +} + + +void JETI_EnableTXD (void) +{ + UCSR0B |= (1 << TXEN0); // enable TX +} + + +void JETI_DisableRXD (void) +{ + UCSR0B &= ~(1 << RXEN0); // disable RX + UCSR0B &= ~(1 << RXCIE0); // disable Interrupt +} + + +void JETI_EnableRXD (void) +{ + UCSR0B |= (1 << RXEN0); // enable RX + UCSR0B |= (1 << RXCIE0); // enable Interrupt +} + + +void JETI_putw (uint16_t c) +{ + loop_until_bit_is_set(UCSR0A, UDRE0); + UCSR0B &= ~(1 << TXB80); + if (c & 0x0100) + { + UCSR0B |= (1 << TXB80); + } + UDR0 = c; +} + +void JETI_putc (uint8_t c) +{ + loop_until_bit_is_set(UCSR0A, UDRE0); + // UCSRB &= ~(1 << TXB8); + UCSR0B |= (1 << TXB80); + UDR0 = c; +} + +void JETI_puts (char *s) +{ + while (*s) + { + JETI_putc (*s); + s++; + } +} + +void JETI_put_start (void) +{ + loop_until_bit_is_set(UCSR0A, UDRE0); + UCSR0B &= ~(1 << TXB80); + UDR0 = 0xFE; +} + +void JETI_put_stop (void) +{ + loop_until_bit_is_set(UCSR0A, UDRE0); + UCSR0B &= ~(1 << TXB80); + UDR0 = 0xFF; +} + +#include "menus.h" +void menuProcJeti(uint8_t event) +{ + TITLE("JETI"); + + switch(event) + { + //case EVT_KEY_FIRST(KEY_MENU): + // break; + case EVT_KEY_FIRST(KEY_EXIT): + JETI_DisableRXD(); + chainMenu(menuProc0); + break; + } + + for (uint8_t i = 0; i < 16; i++) + { + lcd_putcAtt((i+2)*FW, 3*FH, JetiBuffer[i], BSS); + lcd_putcAtt((i+2)*FW, 4*FH, JetiBuffer[i+16], BSS); + } + + if (JetiBufferReady) + { + JETI_EnableTXD(); + if (keyState((EnumKeys)(KEY_UP))) jeti_keys &= JETI_KEY_UP; + if (keyState((EnumKeys)(KEY_DOWN))) jeti_keys &= JETI_KEY_DOWN; + if (keyState((EnumKeys)(KEY_LEFT))) jeti_keys &= JETI_KEY_LEFT; + if (keyState((EnumKeys)(KEY_RIGHT))) jeti_keys &= JETI_KEY_RIGHT; + + JetiBufferReady = 0; // invalidate buffer + + JETI_putw((uint16_t) jeti_keys); + _delay_ms (1); + JETI_DisableTXD(); + + jeti_keys = JETI_KEY_NOCHANGE; + } +} diff --git a/src/jeti.h b/src/jeti.h new file mode 100644 index 000000000..b2d74d004 --- /dev/null +++ b/src/jeti.h @@ -0,0 +1,49 @@ +/* + * Author - Karl Szmutny + * + * Based on the Code from Peter "woggle" Mack, mac@denich.net + * -> http://svn.mikrokopter.de/filedetails.php?repname=Projects&path=/Transportables_Koptertool/trunk/jeti.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + */ + +#ifndef jeti_h +#define jeti_h + + +#include "gruvin9x.h" + +#define JETI_KEY_LEFT 0x70 +#define JETI_KEY_RIGHT 0xe0 +#define JETI_KEY_UP 0xd0 +#define JETI_KEY_DOWN 0xb0 +#define JETI_KEY_NOCHANGE 0xf0 + +extern uint16_t jeti_keys; +extern uint8_t JetiBuffer[32]; // 32 characters +extern uint8_t JetiBufferReady; + +void JETI_Init(void); +void JETI_DisableTXD (void); +void JETI_EnableTXD (void); +void JETI_DisableRXD (void); +void JETI_EnableRXD (void); + +void JETI_putw (uint16_t c); +void JETI_putc (uint8_t c); +void JETI_puts (char *s); +void JETI_put_start (void); +void JETI_put_stop (void); + +void menuProcJeti(uint8_t event); + +#endif + diff --git a/src/lcd.cpp b/src/lcd.cpp new file mode 100644 index 000000000..24b3c43b4 --- /dev/null +++ b/src/lcd.cpp @@ -0,0 +1,670 @@ +/* + * Authors (alphabetical order) + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * + * gruvin9x is based on code named er9x by + * Author - Erez Raviv , which is in turn + * was based on the original (and ongoing) project by Thomas Husterer, + * th9x -- http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + */ + +#include "lcd.h" + +uint8_t displayBuf[DISPLAY_W*DISPLAY_H/8]; +#define DISPLAY_END (displayBuf+sizeof(displayBuf)) +#include "font.lbm" +#define font_5x8_x20_x7f (font+3) + +#include "font_dblsize.lbm" +#define font_10x16_x20_x7f (font_dblsize+3) + +void lcd_clear() +{ + memset(displayBuf, 0, sizeof(displayBuf)); +} + +void lcd_img(uint8_t i_x,uint8_t i_y,const prog_uchar * imgdat,uint8_t idx,uint8_t mode) +{ + const prog_uchar *q = imgdat; + uint8_t w = pgm_read_byte(q++); + uint8_t hb = (pgm_read_byte(q++)+7)/8; + uint8_t sze1 = pgm_read_byte(q++); + q += idx*sze1; + bool inv = (mode & INVERS) ? true : (mode & BLINK ? BLINK_ON_PHASE : false); + for(uint8_t yb = 0; yb < hb; yb++){ + uint8_t *p = &displayBuf[ (i_y / 8 + yb) * DISPLAY_W + i_x ]; + for(uint8_t x=0; x < w; x++){ + uint8_t b = pgm_read_byte(q++); + *p++ = inv ? ~b : b; + } + } +} + +uint8_t lcd_lastPos; + +void lcd_putcAtt(uint8_t x, uint8_t y, const char c, uint8_t mode) +{ + uint8_t *p = &displayBuf[ y / 8 * DISPLAY_W + x ]; + + prog_uchar *q = &font_5x8_x20_x7f[ + (c-0x20)*5]; + bool inv = (mode & INVERS) ? true : (mode & BLINK ? BLINK_ON_PHASE : false); + if(mode & DBLSIZE) + { + /* each letter consists of ten top bytes followed by + * by ten bottom bytes (20 bytes per * char) */ + q = &font_10x16_x20_x7f[(c-0x20)*10 + ((c-0x20)/16)*160]; + for(char i=5; i>=0; i--) { + if (mode & CONDENSED && i==0) break; + /*top byte*/ + uint8_t b1 = i>0 ? pgm_read_byte(q) : 0; + /*bottom byte*/ + uint8_t b3 = i>0 ? pgm_read_byte(160+q) : 0; + /*top byte*/ + uint8_t b2 = i>0 ? pgm_read_byte(++q) : 0; + /*bottom byte*/ + uint8_t b4 = i>0 ? pgm_read_byte(160+q) : 0; + + if(inv) { + b1=~b1; + b2=~b2; + b3=~b3; + b4=~b4; + } + + if(&p[DISPLAY_W+1] < DISPLAY_END){ + p[0]=b1; + p[1]=b2; + p[DISPLAY_W] = b3; + p[DISPLAY_W+1] = b4; + p+=2; + } + q++; + } +#if OUTDEZ_SPEED != 0 + lcd_lastPos = x + 2*FW; +#endif + } + else { + uint8_t condense=0; + + if (mode & CONDENSED) { + *p++ = inv ? ~0 : 0; + condense=1; + } + + for (char i=5; i!=0; i--) { + uint8_t b = pgm_read_byte(q++); + if (condense && i==4) { + /*condense the letter by skipping column 4 */ + continue; + } + if(p9 ? c+'A'-10 : c+'0'; + lcd_putcAtt(x,y,c,c>='A'?CONDENSED:0); + val>>=4; + } +} +void lcd_outdez8(uint8_t x, uint8_t y, int8_t val) +{ + lcd_outdezAtt(x, y, val); +} + +void lcd_outdezAtt(uint8_t x, uint8_t y, int16_t val, uint8_t mode) +{ + lcd_outdezNAtt(x, y, val, mode); +} + +// TODO use doxygen style comments here + +/* +USAGE: + lcd_outdezNAtt(x-coord, y-coord, (un)signed-value{0..65535|0..+/-32768}, + mode_flags, length) + + Available mode_flas: PREC{1..3}, UNSIGN (for programmer selected signed numbers + to allow for unsigned values up to the ful 65535 16-bit limt)) + + LEADING0 means pad 0 to the left of sig. digits up to 'len' total characters +*/ + +#if OUTDEZ_SPEED != 0 +void lcd_outdezNAtt(uint8_t x, uint8_t y, int16_t val, uint8_t flags, uint8_t len) +{ + assert(len <= 5); + + char digits[5]; // sig. digits buffered in reverse order + int8_t lastDigit = 4; + int8_t mode = MODE(flags); + + bool neg = false; + if (flags & UNSIGN) { flags -= UNSIGN; } + else if (val < 0) { neg=true; val=-val; } + + bool dblsize = (flags & DBLSIZE); + + // Buffer characters and determine the significant digit count + for (int8_t i=4; i>=0; i--) + { + if (val) lastDigit = i; + digits[i] = ((uint16_t)val % 10) + '0'; + val = (uint16_t)val / 10; + } + + switch(mode) + { + case MODE(LEADING0): + lastDigit = 5-len; + break; + default: + if (4-lastDigit < mode) + lastDigit = 4 - mode; + break; + } + + lcd_lastPos = x; + if (~flags & LEFT) // determine correct x-coord starting point for decimal aligned + { + // Starting point for regular unsigned, non-decimal number + lcd_lastPos -= (5-lastDigit) * (dblsize ? 2*FWNUM : FWNUM) + (dblsize ? 1 : 0); + if (mode>0 && !dblsize) lcd_lastPos -= dblsize ? 3 : 2; + if (neg) lcd_lastPos -= dblsize ? 2*FW : FW; + } + + if (neg) lcd_putcAtt(lcd_lastPos, y, '-', flags); // apply sign when required + + uint8_t xn = 0; + uint8_t ln = 2; + + for (int8_t i=lastDigit; i<5; i++) + { + lcd_putcAtt(lcd_lastPos-1, y, digits[i], flags); + + if (dblsize) { + lcd_lastPos--; + } + + // Draw decimal point + + // Use direct screen writes to save flash, function calls, stack, cpu load + +#if OUTDEZ_SPEED == 2 + bool inv = (flags & INVERS) ? true : (flags & BLINK ? BLINK_ON_PHASE : false); +#endif + + if (mode>0 && 4-i==mode) // .. then draw a d'point + { + if (dblsize) + { + xn = lcd_lastPos-1; + if (digits[i+1]=='2' || digits[i+1]=='3' || digits[i+1]=='1') ln++; + if (digits[i]=='2' || digits[i]=='4') { + if (digits[i+1]=='4') xn++; + else { xn--; ln++; } + } + } + else + { +#if OUTDEZ_SPEED == 2 + displayBuf[ y * (DISPLAY_W/8) + lcd_lastPos ] = (inv ? 0x40 ^ 0xff : 0x40); +#else + lcd_plot(lcd_lastPos, y+6); + if (flags & INVERS || (flags & BLINK && BLINK_ON_PHASE)) + lcd_vline(lcd_lastPos, y, 8); +#endif + lcd_lastPos += 2; + } + } + } + + if (xn) { + lcd_hline(xn, y+2*FH-3, ln); + lcd_hline(xn, y+2*FH-2, ln); + } +} + +#else + +void lcd_outdezNAtt(uint8_t x, uint8_t y, int16_t val, uint8_t flags, uint8_t len) +{ + uint8_t fw = FWNUM; + int8_t mode = MODE(flags); + + bool neg = false; + if (flags & UNSIGN) { flags -= UNSIGN; } + else if (val < 0) { neg=true; val=-val; } + + uint8_t xn = 0; + uint8_t ln = 2; + char c; + + if (mode != MODE(LEADING0)) { + len = 1; + uint16_t tmp = ((uint16_t)val) / 10; + while (tmp) { + len++; + tmp /= 10; + } + if (len <= mode) + len = mode + 1; + } + + if (flags & DBLSIZE) { + fw += FWNUM; + } + else { + if (flags & LEFT) { + if (mode > 0) + x += 2; + } + } + + if (flags & LEFT) { + x += len * fw; + if (neg) + x += FWNUM; + } + + lcd_lastPos = x; + x -= fw + 1; + + for (uint8_t i=1; i<=len; i++) { + c = ((uint16_t)val % 10) + '0'; + if (c=='1' && flags&DBLSIZE && i==len) { x+=2; flags|=CONDENSED; } + lcd_putcAtt(x, y, c, flags); + if (mode==i) { + flags &= ~PREC2; // TODO not needed but removes 64bytes, could be improved for sure, check asm + if (flags & DBLSIZE) { + xn = x; + if(c=='2' || c=='3' || c=='1') ln++; + uint8_t tn = ((uint16_t)val/10) % 10; + if (tn==2 || tn==4) { + if (c=='4') { xn++; } + else { xn--; ln++; } + } + } + else { + x -= 2; + lcd_plot(x+1, y+6); + if (flags & INVERS || (flags & BLINK && BLINK_ON_PHASE)) + lcd_vline(x+1, y, 8); + } + } + val = ((uint16_t)val) / 10; + x-=fw; + } + if (xn) { + lcd_hline(xn, y+2*FH-3, ln); + lcd_hline(xn, y+2*FH-2, ln); + } + + // TODO we could change the '-' to have one pixel removed at its left + if (neg) { lcd_putcAtt(x, y, '-', flags); lcd_plot(x, y+3); } +} +#endif + + +void lcd_mask(uint8_t *p, uint8_t mask, uint8_t att) +{ + assert(p < DISPLAY_END); + + if (att & BLACK) + *p |= mask; + else if (att & WHITE) + *p &= ~mask; + else + *p ^= mask; +} + +void lcd_plot(uint8_t x,uint8_t y, uint8_t att) +{ + uint8_t *p = &displayBuf[ y / 8 * DISPLAY_W + x ]; + if (p= DISPLAY_H) return; + if (x<0) { w+=x; x=0; } + if (x+w > DISPLAY_W) { w = DISPLAY_W - x; } + + uint8_t *p = &displayBuf[ y / 8 * DISPLAY_W + x ]; + uint8_t msk = BITMASK(y%8); + while(w) { + if(pat&1) { + lcd_mask(p, msk, att); + pat = (pat >> 1) | 0x80; + } + else { + pat = pat >> 1; + } + w--; + p++; + } +} + +void lcd_hline(uint8_t x,uint8_t y, uint8_t w, uint8_t att) +{ + lcd_hlineStip(x, y, w, 0xff, att); +} + +void lcd_vlineStip(uint8_t x, int8_t y, int8_t h, uint8_t pat) +{ + if (y<0) { h+=y; y=0; } + if (y+h > DISPLAY_H) { h = DISPLAY_H - y; } + + uint8_t *p = &displayBuf[ y / 8 * DISPLAY_W + x ]; + y = y % 8; + if (y) { + assert(p < DISPLAY_END); + *p ^= ~(BITMASK(y)-1) & pat; + p += DISPLAY_W; + h -= 8-y; + } + while (h>0) { + assert(p < DISPLAY_END); + *p ^= pat; + p += DISPLAY_W; + h -= 8; + } + h = (h+8) % 8; + if (h) { + p -= DISPLAY_W; + assert(p < DISPLAY_END); + *p ^= ~(BITMASK(h)-1) & pat; + } +} + +void lcd_vline(uint8_t x, int8_t y, int8_t h) +{ + lcd_vlineStip(x, y, h, 0xff); +} + +void lcd_rect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t pat, uint8_t att) +{ + if (!((att & BLINK) && BLINK_ON_PHASE)) { + lcd_vlineStip(x, y, h, pat); + lcd_hlineStip(x, y+h-1, w, pat); + lcd_vlineStip(x+w-1, y, h, pat); + lcd_hlineStip(x, y, w, pat); + } +} + +void lcd_filled_rect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t att) +{ + for (uint8_t i=y; i 0 && idx <= NUM_CHNOUT) + putsChnRaw(x, y, idx+20, att); +} + +void putsChnLetter(uint8_t x, uint8_t y, uint8_t idx, uint8_t attr) +{ + lcd_putsnAtt(x, y, PSTR("RETA")+CHANNEL_ORDER(idx)-1, 1, attr); +} + +void putsModelName(uint8_t x, uint8_t y, char *name, uint8_t id, uint8_t att) +{ + uint8_t len = sizeof(g_model.name); + while (len>0 && !name[len-1]) --len; + if (len==0) { + lcd_putsAtt(x, y, PSTR("MODEL"/*MODEL*/), att); + lcd_outdezNAtt(lcd_lastPos, y, id+1, att|LEADING0|LEFT, 2); + } + else { + lcd_putsnAtt(x, y, name, sizeof(g_model.name), ZCHAR|att); + } +} + +void putsSwitches(uint8_t x,uint8_t y,int8_t idx,uint8_t att) +{ + switch(idx){ + case 0: lcd_putsAtt(x,y,PSTR("---"),att);return; + case MAX_SWITCH: lcd_putsAtt(x,y,PSTR("ON "),att);return; + case -MAX_SWITCH: lcd_putsAtt(x,y,PSTR("OFF"),att);return; + } + if (idx<0) lcd_putcAtt(x-FW, y, '!', att); + lcd_putsnAtt(x,y,get_switches_string()+3*(abs(idx)-1),3,att); +} + +void putsFlightPhase(uint8_t x, uint8_t y, int8_t idx, uint8_t att) +{ + if (idx==0) { lcd_putsAtt(x,y,PSTR("---"),att); return; } + if (idx < 0) { lcd_putcAtt(x-FW, y, '!', att); idx = -idx; } + lcd_putsAtt(x, y, PSTR("FP"), att); + lcd_putcAtt(x+2*FW, y, '0'+idx-1, att); +} + +void putsTmrMode(uint8_t x, uint8_t y, int8_t mode, uint8_t att) +{ + if (mode < 0) { + mode = -mode; + lcd_putcAtt(x-1*FW, y, '!', att); + } + + if (mode < TMR_VAROFS) { + lcd_putsnAtt(x, y, PSTR("OFFABSRUsRU%ELsEL%THsTH%ALsAL%P1 P1%P2 P2%P3 P3%")+3*mode, 3, att); + return; + } + + if (mode < TMR_VAROFS+MAX_SWITCH-1) { // normal on-off + putsSwitches(x, y, mode-(TMR_VAROFS-1), att); + return; + } + + putsSwitches(x, y, mode-(TMR_VAROFS+MAX_SWITCH-1-1), att); // momentary on-off + if (~att & SHRT_TM_MODE) lcd_putcAtt(x+3*FW, y, 'm', att); +} + +#ifdef FRSKY +// TODO move this into frsky.cpp +void putsTelemetry(uint8_t x, uint8_t y, uint8_t val, uint8_t unit, uint8_t att) +{ + if (unit == 0/*v*/) { + putsVolts(x, y, val, att); + } + else /* raw or reserved unit */ { + lcd_outdezAtt(x, y, val, att); + } +} +#endif + +void lcdSendCtl(uint8_t val) +{ + PORTC_LCD_CTRL &= ~(1<SEG1) + lcdSendCtl(0xA6); //REV = 0: non-reverse display + lcdSendCtl(0xA4); //EON = 0: normal display. non-entire + lcdSendCtl(0xA2); // Select LCD bias=0 + lcdSendCtl(0xC0); //SHL = 0: normal direction (COM1->COM64) + lcdSendCtl(0x2F); //Control power circuit operation VC=VR=VF=1 + lcdSendCtl(0x25); //Select int resistance ratio R2 R1 R0 =5 + lcdSendCtl(0x81); //Set reference voltage Mode + lcdSendCtl(0x22); // 24 SV5 SV4 SV3 SV2 SV1 SV0 = 0x18 + lcdSendCtl(0xAF); //DON = 1: display ON + g_eeGeneral.contrast = 0x22; +} + +void lcdSetRefVolt(uint8_t val) +{ + lcdSendCtl(0x81); + lcdSendCtl(val); +} + +void refreshDiplay() +{ + uint8_t *p=displayBuf; + for(uint8_t y=0; y < 8; y++) { + lcdSendCtl(0x04); + lcdSendCtl(0x10); //column addr 0 + lcdSendCtl( y | 0xB0); //page addr y + PORTC_LCD_CTRL &= ~(1<0; --x) { + PORTA_LCD_DAT = *p++; + PORTC_LCD_CTRL |= (1< + * - Bryan J. Rentoul (Gruvin) + * + * gruvin9x is based on code named er9x by + * Author - Erez Raviv , which is in turn + * was based on the original (and ongoing) project by Thomas Husterer, + * th9x -- http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + */ + +#ifndef lcd_h +#define lcd_h + +#include "gruvin9x.h" + +#define DISPLAY_W 128 +#define DISPLAY_H 64 +#define FW 6 +#define FWNUM 5 +#define FH 8 + +#define OUTDEZ_SPEED 0 + +/* lcd common flags */ +#define BLINK 0x01 + +/* lcd text flags */ +#define INVERS 0x02 +#define DBLSIZE 0x04 + +/* lcd putc flags */ +#define CONDENSED 0x08 + +/* lcd puts flags */ +#define BSS 0x10 +#define ZCHAR 0x20 + +/* lcd outdez flags */ +#define UNSIGN 0x08 +#define LEADING0 0x10 +#define SPARE1 0x20 +#define SPARE2 0x30 +#define SPARE3 0x40 +#define PREC1 0x50 +#define PREC2 0x60 +#define PREC3 0x70 +#define MODE(flags) (-4 + ((int8_t)(flags & 0x70) >> 4)) +#define LEFT 0x80 /* align left */ + +/* line, rect, square flags */ +#define BLACK 0x02 +#define WHITE 0x04 + +/* other flags */ +#define NO_UNIT UNSIGN +#define SHRT_TM_MODE 0x10 + + +extern uint8_t displayBuf[DISPLAY_W*DISPLAY_H/8]; +extern uint8_t lcd_lastPos; + +extern void lcd_putc(unsigned char x,unsigned char y,const char c); +extern void lcd_putcAtt(unsigned char x,unsigned char y,const char c,uint8_t mode); + +extern void lcd_putsAtt(unsigned char x,unsigned char y,const prog_char * s,uint8_t mode); +extern void lcd_putsnAtt(unsigned char x,unsigned char y,const prog_char * s,unsigned char len,uint8_t mode); +extern void lcd_puts_P(unsigned char x,unsigned char y,const prog_char * s); +extern void lcd_putsn_P(unsigned char x,unsigned char y,const prog_char * s,unsigned char len); + +extern void lcd_outhex4(unsigned char x,unsigned char y,uint16_t val); + +extern void lcd_outdezAtt(uint8_t x, uint8_t y, int16_t val, uint8_t mode=0); +extern void lcd_outdezNAtt(uint8_t x, uint8_t y, int16_t val, uint8_t mode=0, uint8_t len=0); +extern void lcd_outdez8(uint8_t x, uint8_t y, int8_t val); + +extern void putsModelName(uint8_t x, uint8_t y, char *name, uint8_t id, uint8_t att); +extern void putsSwitches(uint8_t x, uint8_t y, int8_t swtch, uint8_t att); +extern void putsFlightPhase(uint8_t x, uint8_t y, int8_t idx, uint8_t att); +extern void putsTmrMode(uint8_t x, uint8_t y, int8_t mode, uint8_t att); + +extern void putsChnRaw(uint8_t x,uint8_t y,uint8_t idx1,uint8_t att); +extern void putsChn(uint8_t x,uint8_t y,uint8_t idx1,uint8_t att); +extern void putsChnLetter(uint8_t x, uint8_t y, uint8_t idx, uint8_t attr); + +extern void putsVolts(uint8_t x, uint8_t y, uint16_t volts, uint8_t att); +extern void putsVBat(uint8_t x, uint8_t y, uint8_t att); +extern void putsTime(uint8_t x,uint8_t y, int16_t tme, uint8_t att, uint8_t att2); + +#ifdef FRSKY +// TODO move this into frsky.h +extern void putsTelemetry(uint8_t x, uint8_t y, uint8_t val, uint8_t unit, uint8_t att); +#endif + +extern void lcd_plot(unsigned char x, unsigned char y, uint8_t att=0); +extern void lcd_hline(unsigned char x,unsigned char y, uint8_t w, uint8_t att=0); +extern void lcd_hlineStip(int8_t x, uint8_t y, uint8_t w, uint8_t pat, uint8_t att=0); +extern void lcd_vline(uint8_t x, int8_t y, int8_t h); +extern void lcd_vlineStip(uint8_t x, int8_t y, int8_t h, uint8_t pat); + +extern void lcd_rect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t pat=0xff, uint8_t att=0); +extern void lcd_filled_rect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t att=0); +inline void lcd_square(uint8_t x, uint8_t y, uint8_t w, uint8_t att=0) { lcd_rect(x, y, w, w, 0xff, att); } + +#define DO_CROSS(xx,yy,ww) \ + lcd_vline(xx,yy-ww/2,ww); \ + lcd_hline(xx-ww/2,yy,ww); + +#define V_BAR(xx,yy,ll) \ + lcd_vline(xx-1,yy-ll,ll); \ + lcd_vline(xx ,yy-ll,ll); \ + lcd_vline(xx+1,yy-ll,ll); + +extern void lcd_img_f(unsigned char x,unsigned char y); +extern void lcd_img(uint8_t i_x,uint8_t i_y,const prog_uchar * imgdat,uint8_t idx,uint8_t mode); + +extern void lcdSetRefVolt(unsigned char val); + +extern void lcd_init(); +extern void lcd_clear(); + +extern void refreshDiplay(); + +#define BLINK_ON_PHASE (g_blinkTmr10ms & (1<<6)) +#define BLINK_SYNC g_blinkTmr10ms = (3<<5) + + +#endif +/*eof*/ diff --git a/src/main_views.cpp b/src/main_views.cpp new file mode 100644 index 000000000..b5881e863 --- /dev/null +++ b/src/main_views.cpp @@ -0,0 +1,416 @@ +/* + * Authors (alphabetical order) + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * + * gruvin9x is based on code named er9x by + * Author - Erez Raviv , which is in turn + * was based on the original (and ongoing) project by Thomas Husterer, + * th9x -- http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + */ + +#include "menus.h" + +#define ALTERNATE 0x10 + +enum MainViews { + e_outputValues, + e_outputBars, + e_inputs, + e_timer2, +#ifdef FRSKY + e_telemetry, +#endif + MAX_VIEWS +}; + +uint8_t tabViews[] = { + 1, /*e_outputValues*/ + 1, /*e_outputBars*/ + 3, /*e_inputs*/ + 1, /*e_timer2*/ +#ifdef FRSKY + 2, /*e_telemetry*/ +#endif +}; + +void menuMainView(uint8_t event) +{ + static uint8_t switchView = 255; + static bool instantTrimSwLock; + static bool trim2OfsSwLock; + + uint8_t view = (switchView == 255 ? g_eeGeneral.view : switchView); + + bool telemViewSw = isFunctionActive(FUNC_VIEW_TELEMETRY); + if (switchView == 255 && telemViewSw) { view = switchView = e_telemetry + ALTERNATE; } + if (switchView != 255 && !telemViewSw) { view = g_eeGeneral.view; switchView = 255; } + + uint8_t view_base = view & 0x0f; + + switch(event) + { + case EVT_KEY_BREAK(KEY_MENU): + if (view_base == e_timer2) { + Timer2_running = !Timer2_running; + beepKey(); + } + break; + case EVT_KEY_LONG(KEY_MENU):// go to last menu + pushMenu(lastPopMenu()); + killEvents(event); + break; + case EVT_KEY_BREAK(KEY_RIGHT): + case EVT_KEY_BREAK(KEY_LEFT): + if (switchView != 255) break; + g_eeGeneral.view = (view + (event == EVT_KEY_BREAK(KEY_RIGHT) ? ALTERNATE : tabViews[view_base]*ALTERNATE-ALTERNATE)) % (tabViews[view_base]*ALTERNATE); + eeDirty(EE_GENERAL); + beepKey(); + break; + case EVT_KEY_LONG(KEY_RIGHT): + pushMenu(menuProcModelSelect); + killEvents(event); + break; + case EVT_KEY_LONG(KEY_LEFT): + pushMenu(menuProcSetup); + killEvents(event); + break; + case EVT_KEY_BREAK(KEY_UP): + if (switchView != 255) break; + g_eeGeneral.view = view+1; + if(g_eeGeneral.view>=MAX_VIEWS) g_eeGeneral.view=0; + eeDirty(EE_GENERAL); + beepKey(); + break; + case EVT_KEY_BREAK(KEY_DOWN): + if (switchView != 255) break; + if(view>0) + g_eeGeneral.view = view - 1; + else + g_eeGeneral.view = MAX_VIEWS-1; + eeDirty(EE_GENERAL); + beepKey(); + break; + case EVT_KEY_LONG(KEY_UP): + chainMenu(menuProcStatistic); + killEvents(event); + break; + case EVT_KEY_LONG(KEY_DOWN): +#if defined(JETI) + JETI_EnableRXD(); // enable JETI-Telemetry reception + chainMenu(menuProcJeti); +#else + chainMenu(menuProcStatistic2); +#endif + killEvents(event); + break; + case EVT_KEY_FIRST(KEY_EXIT): + if(s_timerState==TMR_BEEPING) { + s_timerState = TMR_STOPPED; + beepKey(); + } + else if (view == e_timer2) { + resetTimer2(); + beepKey(); + } +#ifdef FRSKY + else if (view == e_telemetry) { + resetTelemetry(); + beepKey(); + } +#endif + else { + resetTimer1(); + } + break; + case EVT_KEY_LONG(KEY_EXIT): + resetTimer1(); + resetTimer2(); +#ifdef FRSKY + resetTelemetry(); +#endif + beepKey(); + break; + case EVT_ENTRY: + killEvents(KEY_EXIT); + killEvents(KEY_UP); + killEvents(KEY_DOWN); + instantTrimSwLock = true; + trim2OfsSwLock = true; + break; + } + + bool trimSw = isFunctionActive(FUNC_INSTANT_TRIM); + if (!instantTrimSwLock && trimSw) instantTrim(); + instantTrimSwLock = trimSw; + + trimSw = isFunctionActive(FUNC_TRIMS_2_OFS); + if (!trim2OfsSwLock && trimSw) moveTrimsToOffsets(); + trim2OfsSwLock = trimSw; + + if (view == e_telemetry+ALTERNATE) { + putsModelName(0, 0, g_model.name, g_eeGeneral.currModel, 0); + uint8_t att = (g_vbat100mV < g_eeGeneral.vBatWarn ? BLINK : 0); + putsVBat(14*FW,0,att); + if(s_timerState != TMR_OFF){ + att = (s_timerState==TMR_BEEPING ? BLINK : 0); + putsTime(17*FW, 0, s_timerVal[0], att, att); + } + lcd_filled_rect(0, 0, DISPLAY_W, 8); + } + else { + uint8_t phase = getFlightPhase(); + lcd_putsnAtt(6*FW+2, 2*FH, g_model.phaseData[phase].name, sizeof(g_model.phaseData[phase].name), ZCHAR); + + uint8_t att = (g_vbat100mV < g_eeGeneral.vBatWarn ? BLINK : 0) | DBLSIZE; + putsModelName(2*FW-2, 0*FH, g_model.name, g_eeGeneral.currModel, DBLSIZE); + putsVBat(6*FW+1, 2*FH, att|NO_UNIT); + lcd_putc(6*FW+2, 3*FH, 'V'); + + if (s_timerState != TMR_OFF) { + uint8_t att = DBLSIZE | (s_timerState==TMR_BEEPING ? BLINK : 0); + putsTime(12*FW+3, FH*2, s_timerVal[0], att,att); + putsTmrMode(s_timerVal[0] >= 0 ? 9*FW-FW/2+5 : 9*FW-FW/2-2, FH*3, g_model.timer1.mode, SHRT_TM_MODE); + } + + // trim sliders + for(uint8_t i=0; i<4; i++) + { +#define TL 27 + // LH LV RV RH + static uint8_t x[4] = {128*1/4+2, 4, 128-4, 128*3/4-2}; + static uint8_t vert[4] = {0,1,1,0}; + uint8_t xm, ym; + xm = x[i]; + + uint8_t att = 0; + int16_t val = g_model.subtrim[i] + phaseaddress(getTrimFlightPhase(i, phase))->trim[i]; + + if (val < -125 || val > 125) + att = BLINK; + + if (val < -(TL+1)*4) + val = -(TL+1); + else if (val > (TL+1)*4) + val = TL+1; + else + val /= 4; + + if (vert[i]) { + ym = 31; + lcd_vline(xm, ym-TL, TL*2); + if(((g_eeGeneral.stickMode&1) != (i&1)) || !(g_model.thrTrim)){ + lcd_vline(xm-1, ym-1, 3); + lcd_vline(xm+1, ym-1, 3); + } + ym -= val; + } + else { + ym = 60; + lcd_hline(xm-TL, ym, TL*2); + lcd_hline(xm-1, ym-1, 3); + lcd_hline(xm-1, ym+1, 3); + xm += val; + } + lcd_square(xm-3, ym-3, 7, att); + } + } + + if(view_baseWBAR2) l = WBAR2; // prevent bars from going over the end - comment for debugging + + lcd_hlineStip(x0-WBAR2,y0,WBAR2*2+1,0x55); + lcd_vline(x0,y0-2,5); + if(val>0){ + x0+=1; + }else{ + x0-=l; + } + lcd_hline(x0,y0+1,l); + lcd_hline(x0,y0-1,l); + break; + } + } + } +#ifdef FRSKY + else if(view_base == e_telemetry) { + static uint8_t displayCount = 0; + static uint8_t staticTelemetry[2]; + static uint8_t staticRSSI[2]; + static bool alarmRaised[2]; + + if (frskyStreaming) { + uint8_t y0, x0, val, blink; + if (!displayCount) { + for (int i=0; i<2; i++) { + staticTelemetry[i] = frskyTelemetry[i].value; + staticRSSI[i] = frskyRSSI[i].value; + alarmRaised[i] = FRSKY_alarmRaised(i); + } + } + displayCount = (displayCount+1) % 50; + if (view & ALTERNATE) { + if (g_model.frsky.channels[0].ratio || g_model.frsky.channels[1].ratio) { + x0 = 0; + for (int i=0; i<2; i++) { + if (g_model.frsky.channels[i].ratio) { + blink = (alarmRaised[i] ? INVERS : 0); + lcd_puts_P(x0, 3*FH, PSTR("A =")); + lcd_putc(x0+FW, 3*FH, '1'+i); + x0 += 3*FW; + val = ((uint16_t)staticTelemetry[i]+g_model.frsky.channels[i].offset)*g_model.frsky.channels[i].ratio / 255; + putsTelemetry(x0, 2*FH, val, g_model.frsky.channels[i].type, blink|DBLSIZE|LEFT); + val = ((int16_t)frskyTelemetry[i].min+g_model.frsky.channels[i].offset)*g_model.frsky.channels[i].ratio / 255; + putsTelemetry(x0+FW, 4*FH, val, g_model.frsky.channels[i].type, 0); + val = ((int16_t)frskyTelemetry[i].max+g_model.frsky.channels[i].offset)*g_model.frsky.channels[i].ratio / 255; + putsTelemetry(x0+3*FW, 4*FH, val, g_model.frsky.channels[i].type, LEFT); + x0 = 11*FW-2; + } + } + } + +#if 0 + // Display RX Batt Volts only if a valid channel (A1/A2) has been selected + if (g_eeFrsky.rxVoltsChannel >0) + { + y+=FH; lcd_puts_P(2*FW, y, PSTR("Rx Batt:")); + // Rx batt voltage bar frame + + // Minimum voltage + lcd_vline(3, 58, 6); // marker + + y = 6*FH; + putsVolts(1, y, g_eeFrsky.rxVoltsBarMin, LEFT); + uint8_t middleVolts = g_eeFrsky.rxVoltsBarMin+(g_eeFrsky.rxVoltsBarMax - g_eeFrsky.rxVoltsBarMin)/2; + putsVolts(64-FW, y, middleVolts, LEFT); + lcd_vline(64, 58, 6); // marker + putsVolts(128-FW, y, g_eeFrsky.rxVoltsBarMax, 0); + lcd_vline(125, 58, 6); // marker + + // Rx Batt: volts (255 == g_eefrsky.rxVoltsMax) + uint16_t centaVolts = (voltsVal > 0) ? (10 * (uint16_t)g_eeFrsky.rxVoltsMax * (uint32_t)(voltsVal) / 255) + g_eeFrsky.rxVoltsOfs : 0; + lcd_outdezAtt(13*FW, 4*FH, centaVolts, 0|PREC2); + lcd_putc(13*FW, 4*FH, 'v'); + + // draw the actual voltage bar + uint16_t centaVoltsMin = 10 * g_eeFrsky.rxVoltsBarMin; + if (centaVolts >= centaVoltsMin) + { + uint8_t vbarLen = (centaVolts - (10 * (uint16_t)g_eeFrsky.rxVoltsBarMin)) * 12 + / (g_eeFrsky.rxVoltsBarMax - g_eeFrsky.rxVoltsBarMin); + for (uint8_t i = 59; i < 63; i++) // Bar 4 pixels thick (high) + lcd_hline(4, i, (vbarLen > 120) ? 120 : vbarLen); + } + } +#endif + + lcd_puts_P(0, 6*FH, PSTR("Rx=")); + lcd_outdezAtt(3 * FW, 5*FH+2, staticRSSI[0], DBLSIZE|LEFT); + lcd_outdezAtt(4 * FW, 7*FH, frskyRSSI[0].min, 0); + lcd_outdezAtt(6 * FW, 7*FH, frskyRSSI[0].max, LEFT); + lcd_puts_P(11 * FW - 2, 6*FH, PSTR("Tx=")); + lcd_outdezAtt(14 * FW - 2, 5*FH+2, staticRSSI[1], DBLSIZE|LEFT); + lcd_outdezAtt(15 * FW - 2, 7*FH, frskyRSSI[1].min, 0); + lcd_outdezAtt(17 * FW - 2, 7*FH, frskyRSSI[1].max, LEFT); + } + else { + y0 = 5*FH; + //lcd_puts_P(2*FW-3, y0, PSTR("Tele:")); + x0 = 4*FW-3; + for (int i=0; i<2; i++) { + if (g_model.frsky.channels[i].ratio) { + blink = (alarmRaised[i] ? INVERS+BLINK : 0)|LEFT; + lcd_puts_P(x0, y0, PSTR("A =")); + lcd_putc(x0+FW, y0, '1'+i); + val = ((int16_t)staticTelemetry[i]+g_model.frsky.channels[i].offset)*g_model.frsky.channels[i].ratio / 255; + putsTelemetry(x0+3*FW, y0, val, g_model.frsky.channels[i].type, blink); + x0 = 13*FW-3; + } + } + y0+=FH; + //lcd_puts_P(2*FW-3, y0, PSTR("RSSI:")); + lcd_puts_P(4*FW-3, y0, PSTR("Rx=")); + lcd_outdezAtt(7*FW-3, y0, staticRSSI[0], LEFT); + lcd_puts_P(13*FW-3, y0, PSTR("Tx=")); + lcd_outdezAtt(16*FW-3, y0, staticRSSI[1], LEFT); + } + } + else { + lcd_putsAtt(22, 40, PSTR("NO DATA"), DBLSIZE); + } + } +#endif + else if (view_base + * - Bryan J. Rentoul (Gruvin) + * + * gruvin9x is based on code named er9x by + * Author - Erez Raviv , which is in turn + * was based on the original (and ongoing) project by Thomas Husterer, + * th9x -- http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + */ + + +#include "gruvin9x.h" +#include "templates.h" +#include "menus.h" + +int16_t calibratedStick[NUM_STICKS+NUM_POTS]; +int16_t ex_chans[NUM_CHNOUT]; // Outputs + intermidiates +uint8_t s_pgOfs; +uint8_t s_editMode; +uint8_t s_noHi; + +int16_t g_chans512[NUM_CHNOUT]; + +void menu_lcd_onoff( uint8_t x,uint8_t y, uint8_t value, uint8_t mode ) +{ + lcd_putsnAtt( x, y, PSTR("OFFON ")+3*value,3,mode ? INVERS:0) ; +} + +void menu_lcd_HYPHINV( uint8_t x,uint8_t y, uint8_t value, uint8_t mode ) +{ + lcd_putsnAtt( x, y, PSTR("---INV")+3*value,3,mode ? INVERS:0) ; +} + +void DisplayScreenIndex(uint8_t index, uint8_t count, uint8_t attr) +{ + lcd_outdezAtt(128,0,count,attr); + lcd_putcAtt(1+128-FW*(count>9 ? 3 : 2),0,'/',attr); + lcd_outdezAtt(1+128-FW*(count>9 ? 3 : 2),0,index+1,attr); +} + +int16_t p1valdiff; +int8_t checkIncDec_Ret; +int16_t checkIncDec(uint8_t event, int16_t val, int16_t i_min, int16_t i_max, uint8_t i_flags) +{ + int16_t newval = val; + uint8_t kpl=KEY_RIGHT, kmi=KEY_LEFT, kother = -1; + + if(event & _MSK_KEY_DBL){ + uint8_t hlp=kpl; + kpl=kmi; + kmi=hlp; + event=EVT_KEY_FIRST(EVT_KEY_MASK & event); + } + if(event==EVT_KEY_FIRST(kpl) || event== EVT_KEY_REPT(kpl) || (s_editMode && (event==EVT_KEY_FIRST(KEY_UP) || event== EVT_KEY_REPT(KEY_UP))) ) { + newval++; +#if defined (BEEPSPKR) + beepKeySpkr(BEEP_KEY_UP_FREQ); +#else + beepKey(); +#endif + kother=kmi; + }else if(event==EVT_KEY_FIRST(kmi) || event== EVT_KEY_REPT(kmi) || (s_editMode && (event==EVT_KEY_FIRST(KEY_DOWN) || event== EVT_KEY_REPT(KEY_DOWN))) ) { + newval--; +#if defined (BEEPSPKR) + beepKeySpkr(BEEP_KEY_DOWN_FREQ); +#else + beepKey(); +#endif + kother=kpl; + } + if((kother != (uint8_t)-1) && keyState((EnumKeys)kother)){ + newval=-val; + killEvents(kmi); + killEvents(kpl); + } + if(i_min==0 && i_max==1 && event==EVT_KEY_FIRST(KEY_MENU)) { + s_editMode = false; + newval=!val; + killEvents(event); + } + + //change values based on P1 + newval -= p1valdiff; + + if(newval > i_max) + { + newval = i_max; + killEvents(event); +#if defined (BEEPSPKR) + beepWarn2Spkr(BEEP_KEY_UP_FREQ); +#else + beepWarn2(); +#endif + } + if(newval < i_min) + { + newval = i_min; + killEvents(event); +#if defined (BEEPSPKR) + beepWarn2Spkr(BEEP_KEY_DOWN_FREQ); +#else + beepWarn2(); +#endif + } + if(newval != val){ + if(newval==0) { + pauseEvents(event); // delay before auto-repeat continues +#if defined (BEEPSPKR) + if (newval>val) + beepWarn2Spkr(BEEP_KEY_UP_FREQ); + else + beepWarn2Spkr(BEEP_KEY_DOWN_FREQ); +#else + beepKey(); +#endif + } + eeDirty(i_flags & (EE_GENERAL|EE_MODEL)); + checkIncDec_Ret = (newval > val ? 1 : -1); + } + else { + checkIncDec_Ret = 0; + } + return newval; +} + +int8_t checkIncDecModel(uint8_t event, int8_t i_val, int8_t i_min, int8_t i_max) +{ + return checkIncDec(event,i_val,i_min,i_max,EE_MODEL); +} + +int8_t checkIncDecGen(uint8_t event, int8_t i_val, int8_t i_min, int8_t i_max) +{ + return checkIncDec(event,i_val,i_min,i_max,EE_GENERAL); +} + +bool check_simple(uint8_t event, uint8_t curr, MenuFuncP *menuTab, uint8_t menuTabSize, uint8_t maxrow) +{ + return check(event, curr, menuTab, menuTabSize, 0, 0, maxrow); +} + +bool check_submenu_simple(uint8_t event, uint8_t maxrow) +{ + return check_simple(event, 0, 0, 0, maxrow); +} + +#define MAXCOL(row) (horTab ? pgm_read_byte(horTab+min(row, horTabMax)) : (const uint8_t)0) +#define INC(val,max) if(val0 ) {val--;} else {val=max;} +bool check(uint8_t event, uint8_t curr, MenuFuncP *menuTab, uint8_t menuTabSize, prog_uint8_t *horTab, uint8_t horTabMax, uint8_t maxrow) +{ + if (menuTab) { + uint8_t attr = m_posVert==0 ? INVERS : 0; + + if (m_posVert==0) { + switch(event) + { + case EVT_KEY_FIRST(KEY_LEFT): + if(curr>0) + chainMenu((MenuFuncP)pgm_read_adr(&menuTab[curr-1])); + else + chainMenu((MenuFuncP)pgm_read_adr(&menuTab[menuTabSize-1])); + return false; + case EVT_KEY_FIRST(KEY_RIGHT): + if(curr < (menuTabSize-1)) + chainMenu((MenuFuncP)pgm_read_adr(&menuTab[curr+1])); + else + chainMenu((MenuFuncP)pgm_read_adr(&menuTab[0])); + return false; + } + } + DisplayScreenIndex(curr, menuTabSize, attr); + } + + uint8_t maxcol = MAXCOL(m_posVert); + switch(event) + { + case EVT_ENTRY: + minit(); + s_editMode = false; + break; + case EVT_KEY_FIRST(KEY_MENU): + if (maxcol > 0) + s_editMode = !s_editMode; + break; + case EVT_KEY_LONG(KEY_EXIT): + s_editMode = false; + popMenu(false); + break; + case EVT_KEY_BREAK(KEY_EXIT): + if(s_editMode) { + s_editMode = false; + break; + } + if(m_posVert==0 || !menuTab) { + popMenu(); //beeps itself + } + else { + beepKey(); + minit();BLINK_SYNC; + } + break; + + case EVT_KEY_REPT(KEY_RIGHT): //inc + if(m_posHorz==maxcol) break; + case EVT_KEY_FIRST(KEY_RIGHT)://inc + if(!horTab || s_editMode)break; + INC(m_posHorz,maxcol); + BLINK_SYNC; + break; + + case EVT_KEY_REPT(KEY_LEFT): //dec + if(m_posHorz==0) break; + case EVT_KEY_FIRST(KEY_LEFT)://dec + if(!horTab || s_editMode)break; + DEC(m_posHorz,maxcol); + BLINK_SYNC; + break; + + case EVT_KEY_REPT(KEY_DOWN): //inc + if(m_posVert==maxrow) break; + case EVT_KEY_FIRST(KEY_DOWN): //inc + if(s_editMode)break; + do { + INC(m_posVert,maxrow); + } while(MAXCOL(m_posVert) == (uint8_t)-1); + m_posHorz = min(m_posHorz, MAXCOL(m_posVert)); + BLINK_SYNC; + break; + + case EVT_KEY_REPT(KEY_UP): //dec + if(m_posVert==0) break; + case EVT_KEY_FIRST(KEY_UP): //dec + if(s_editMode)break; + do { + DEC(m_posVert,maxrow); + } while(MAXCOL(m_posVert) == (uint8_t)-1); + m_posHorz = min(m_posHorz, MAXCOL(m_posVert)); + BLINK_SYNC; + break; + } + + uint8_t max = menuTab ? 7 : 6; + if(m_posVert<1) s_pgOfs=0; + else if(m_posVert-s_pgOfs>max) s_pgOfs = m_posVert-max; + else if(m_posVert-s_pgOfs<1) s_pgOfs = m_posVert-1; + return true; +} + +MenuFuncP g_menuStack[5]; +uint8_t g_menuPos[4]; +uint8_t g_menuStackPtr = 0; + +uint8_t m_posVert; +uint8_t m_posHorz; + +void popMenu(bool uppermost) +{ + if (g_menuStackPtr>0) { + g_menuStackPtr = uppermost ? 0 : g_menuStackPtr-1; + beepKey(); + m_posHorz = g_menuPos[g_menuStackPtr] & 0x0F; + m_posVert = g_menuPos[g_menuStackPtr] >> 4; + (*g_menuStack[g_menuStackPtr])(EVT_ENTRY_UP); + } + else { + alert(PSTR("menuStack underflow")); + } +} + +void chainMenu(MenuFuncP newMenu) +{ + g_menuStack[g_menuStackPtr] = newMenu; + (*newMenu)(EVT_ENTRY); + beepKey(); +} + +void pushMenu(MenuFuncP newMenu) +{ + g_menuPos[g_menuStackPtr] = (m_posVert << 4) + m_posHorz; + + g_menuStackPtr++; + if(g_menuStackPtr >= DIM(g_menuStack)) + { + g_menuStackPtr--; + alert(PSTR("menuStack overflow")); + return; + } + beepKey(); + g_menuStack[g_menuStackPtr] = newMenu; + (*newMenu)(EVT_ENTRY); +} + +/****************************************************************************** + the functions below are from int-level + the functions below are from int-level + the functions below are from int-level +******************************************************************************/ + +void setupPulses() +{ + switch(g_model.protocol) + { + case PROTO_PPM: + setupPulsesPPM(); + break; + case PROTO_SILV_A: + case PROTO_SILV_B: + case PROTO_SILV_C: + setupPulsesSilver(); + break; + case PROTO_TRACER_CTP1009: + setupPulsesTracerCtp1009(); + break; + } +} + +//inline int16_t reduceRange(int16_t x) // for in case we want to have room for subtrims +//{ +// return x-(x/4); //512+128 =? 640, 640 - 640/4 == 640 * 3/4 => 480 (just below 500msec - it can still reach 500 with offset) +//} + +void setupPulsesPPM() // changed 10/05/2010 by dino Issue 128 +{ +#define PPM_CENTER 1200*2 + int16_t PPM_range = g_model.extendedLimits ? 640*2 : 512*2; //range of 0.7..1.7msec + + //Total frame length = 22.5msec + //each pulse is 0.7..1.7ms long with a 0.3ms stop tail + //The pulse ISR is 2mhz so everything is multiplied by 2 + + // G: Found the following reference at th9x. The below code does not seem + // to produce quite exactly this, to my eye. *shrug* + // http://www.aerodesign.de/peter/2000/PCM/frame_ppm.gif + + uint8_t j=0; + uint8_t p=8+g_model.ppmNCH*2; //Channels *2 + uint16_t q=(g_model.ppmDelay*50+300)*2; //Stoplen *2 + uint16_t rest=22500u*2-q; //Minimum Framelen=22.5 ms + if(p>9) rest=p*(1720u*2 + q) + 4000u*2; //for more than 9 channels, frame must be longer + for(uint8_t i=0;i>2 -m1 -m2>>2 -m2 +//<= 500us Probleme +//>= 650us Probleme +//periode orig: 450ms +void setupPulsesSilver() +{ + int8_t chan=1; //chan 1=C 2=B 0=A? + + switch(g_model.protocol) + { + case PROTO_SILV_A: chan=0; break; + case PROTO_SILV_B: chan=2; break; + case PROTO_SILV_C: chan=1; break; + } + + int8_t m1 = (uint16_t)(g_chans512[0]+1024)*2 / 256; + int8_t m2 = (uint16_t)(g_chans512[1]+1024)*2 / 256; + if (m1 < 0) m1=0; + if (m2 < 0) m2=0; + if (m1 > 15) m1=15; + if (m2 > 15) m2=15; + if (m2 > m1+9) m1=m2-9; + if (m1 > m2+9) m2=m1-9; + //uint8_t i=0; + pulses2MHzPtr=pulses2MHz; + send_hilo_silv(5,1); //idx 0 erzeugt pegel=0 am Ausgang, wird als high gesendet + send2BitsSilv(0); + send_hilo_silv(2,1); + send_hilo_silv(2,1); + + send2BitsSilv(chan); //chan 1=C 2=B 0=A? + uint8_t sum = 0 - chan; + + send2BitsSilv(m1>>2); //m1 + sum-=m1>>2; + send2BitsSilv(m1); + sum-=m1; + + send2BitsSilv(m2>>2); //m2 + sum-=m2>>2; + send2BitsSilv(m2); + sum-=m2; + + send2BitsSilv(sum); //chk + + sendBitSilv(0); + pulses2MHzPtr--; + send_hilo_silv(50,0); //low-impuls (pegel=1) ueberschreiben + + +} + + + +/* + TRACE CTP-1009 + - = send 45MHz + _ = send nix + start1 0 1 start2 + -------__ --_ -__ -----__ + 7ms 2 .8 .4 .4 .8 5 2 + + frame: + start1 24Bits_1 start2 24_Bits2 + + 24Bits_1: + 7 x Bits Throttle lsb first + 1 x 0 + + 6 x Bits rotate lsb first + 1 x Bit 1=rechts + 1 x 0 + + 4 x Bits chk5 = nib2 ^ nib4 + 4 x Bits chk6 = nib1 ^ nib3 + + 24Bits_2: + 7 x Bits Vorwaets lsb first 0x3f = mid + 1 x 1 + + 7 x Bits 0x0e lsb first + 1 x 1 + + 4 x Bits chk5 = nib2 ^ nib4 + 4 x Bits chk6 = nib1 ^ nib3 + + */ + +#define BIT_TRA (400u*2) +void sendBitTra(uint8_t val) +{ + if(val) _send_hilo( BIT_TRA*1 , BIT_TRA*2 ); + else _send_hilo( BIT_TRA*2 , BIT_TRA*1 ); +} +void sendByteTra(uint8_t val) +{ + for(uint8_t i=0; i<8; i++, val>>=1) sendBitTra(val&1); +} +void setupPulsesTracerCtp1009() +{ + pulses2MHzPtr=pulses2MHz; + static bool phase; + if( (phase=!phase) ){ + uint8_t thr = min(127u,(uint16_t)(g_chans512[0]+1024+8) / 16u); + uint8_t rot; + if (g_chans512[1] >= 0) + { + rot = min(63u,(uint16_t)( g_chans512[1]+16) / 32u) | 0x40; + }else{ + rot = min(63u,(uint16_t)(-g_chans512[1]+16) / 32u); + } + sendByteTra(thr); + sendByteTra(rot); + uint8_t chk=thr^rot; + sendByteTra( (chk>>4) | (chk<<4) ); + _send_hilo( 5000*2, 2000*2 ); + }else{ + uint8_t fwd = min(127u,(uint16_t)(g_chans512[2]+1024) / 16u) | 0x80; + sendByteTra(fwd); + sendByteTra(0x8e); + uint8_t chk=fwd^0x8e; + sendByteTra( (chk>>4) | (chk<<4) ); + _send_hilo( 7000*2, 2000*2 ); + } + *pulses2MHzPtr++=0; + if((pulses2MHzPtr-pulses2MHz) >= (signed)DIM(pulses2MHz)) alert(PSTR("pulse tab overflow")); +} diff --git a/src/menus.h b/src/menus.h new file mode 100644 index 000000000..8f3ee5248 --- /dev/null +++ b/src/menus.h @@ -0,0 +1,129 @@ +/* + * Authors (alphabetical order) + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * + * gruvin9x is based on code named er9x by + * Author - Erez Raviv , which is in turn + * was based on the original (and ongoing) project by Thomas Husterer, + * th9x -- http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + */ + +/* + Insert obligatories here + + */ + +#ifndef menus_h +#define menus_h + +#include +#include "gruvin9x.h" + +#define IS_THROTTLE(x) (((2-(g_eeGeneral.stickMode&1)) == x) && (x<4)) + +#define NO_HI_LEN 25 + +#define RESX 1024 +#define RESXu 1024u +#define RESXul 1024ul +#define RESXl 1024l +#define RESKul 100ul +#define RESX_PLUS_TRIM (RESX+128) + +typedef void (*MenuFuncP)(uint8_t event); + +void DisplayScreenIndex(uint8_t index, uint8_t count, uint8_t attr); + +extern uint8_t s_pgOfs; +extern uint8_t s_noHi; + +// extern int16_t expo(int16_t x, int16_t k); + +void menu_lcd_onoff(uint8_t x, uint8_t y, uint8_t value, uint8_t mode); +void menu_lcd_HYPHINV(uint8_t x, uint8_t y, uint8_t value, uint8_t mode); + +extern MenuFuncP g_menuStack[5]; +extern uint8_t g_menuStackPtr; + +/// goto given Menu, but substitute current menu in menuStack +void chainMenu(MenuFuncP newMenu); +/// goto given Menu, store current menu in menuStack +void pushMenu(MenuFuncP newMenu); +///deliver address of last menu which was popped from +inline MenuFuncP lastPopMenu() +{ + return g_menuStack[g_menuStackPtr+1]; +} +/// return to last menu in menustack +/// if uppermost is set true, thenmenu return to uppermost menu in menustack +void popMenu(bool uppermost=false); + +void menuMainView(uint8_t event); +void menuProcSetup(uint8_t event); +void menuProcModelSelect(uint8_t event); +void menuProcStatistic(uint8_t event); +void menuProcStatistic2(uint8_t event); + +extern int16_t p1valdiff; +extern int8_t checkIncDec_Ret; // global helper vars +extern uint8_t s_editMode; // global editmode + +int16_t checkIncDec(uint8_t event, int16_t i_pval, int16_t i_min, int16_t i_max, uint8_t i_flags); +int8_t checkIncDecModel(uint8_t event, int8_t i_val, int8_t i_min, int8_t i_max); +int8_t checkIncDecGen(uint8_t event, int8_t i_val, int8_t i_min, int8_t i_max); + +#define CHECK_INCDEC_MODELVAR( event, var, min, max) \ + var = checkIncDecModel(event,var,min,max) + +#define CHECK_INCDEC_GENVAR( event, var, min, max) \ + var = checkIncDecGen(event,var,min,max) + +// Menus related stuff ... +extern uint8_t m_posVert; +extern uint8_t m_posHorz; +inline void minit(){m_posVert=m_posHorz=0;} +bool check(uint8_t event, uint8_t curr, MenuFuncP *menuTab, uint8_t menuTabSize, prog_uint8_t *subTab, uint8_t subTabMax, uint8_t maxrow); +bool check_simple(uint8_t event, uint8_t curr, MenuFuncP *menuTab, uint8_t menuTabSize, uint8_t maxrow); +bool check_submenu_simple(uint8_t event, uint8_t maxrow); + +typedef PROGMEM void (*MenuFuncP_PROGMEM)(uint8_t event); + +#define TITLEP(pstr) lcd_putsAtt(0,0,pstr,INVERS) +#define TITLE(str) TITLEP(PSTR(str)) + +#define MENU(title, tab, menu, lines_count, lines...) \ +TITLE(title); \ +static prog_uint8_t APM mstate_tab[] = lines; \ +if (!check(event,menu,tab,DIM(tab),mstate_tab,DIM(mstate_tab)-1,lines_count-1)) return; + +#define SIMPLE_MENU_NOTITLE(tab, menu, lines_count) \ +if (!check_simple(event,menu,tab,DIM(tab),lines_count-1)) return; + +#define SIMPLE_MENU(title, tab, menu, lines_count) \ +TITLE(title); \ +SIMPLE_MENU_NOTITLE(tab, menu, lines_count) + +#define SUBMENU(title, lines_count, lines...) \ +TITLE(title); \ +static prog_uint8_t APM mstate_tab[] = lines; \ +if (!check(event,0,NULL,0,mstate_tab,DIM(mstate_tab)-1,lines_count-1)) return; + +#define SIMPLE_SUBMENU_NOTITLE(lines_count) \ +if (!check_submenu_simple(event,lines_count-1)) return; + +#define SIMPLE_SUBMENU(title, lines_count) \ +TITLE(title); \ +SIMPLE_SUBMENU_NOTITLE(lines_count) + +#endif diff --git a/src/model_menus.cpp b/src/model_menus.cpp new file mode 100644 index 000000000..ac7ab408f --- /dev/null +++ b/src/model_menus.cpp @@ -0,0 +1,1773 @@ +/* + * Authors (alphabetical order) + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * + * gruvin9x is based on code named er9x by + * Author - Erez Raviv , which is in turn + * was based on the original (and ongoing) project by Thomas Husterer, + * th9x -- http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + */ + +#include "menus.h" +#include "templates.h" + +#define WCHART 32 +#define X0 (128-WCHART-2) +#define Y0 32 + +enum EnumTabModel { + e_ModelSelect, + e_Model, +#ifdef HELI + e_Heli, +#endif + e_PhasesAll, + e_ExposAll, + e_MixAll, + e_Limits, + e_CurvesAll, + e_CustomSwitches, + e_FunctionSwitches, + e_SafetySwitches, +#ifdef FRSKY + e_Telemetry, +#endif +#ifdef TEMPLATES + e_Templates +#endif +}; + +void menuProcModelSelect(uint8_t event); +void menuProcModel(uint8_t event); +#ifdef HELI +void menuProcHeli(uint8_t event); +#endif +void menuProcPhasesAll(uint8_t event); +void menuProcExposAll(uint8_t event); +void menuProcMixAll(uint8_t event); +void menuProcLimits(uint8_t event); +void menuProcCurvesAll(uint8_t event); +void menuProcCustomSwitches(uint8_t event); +void menuProcFunctionSwitches(uint8_t event); +void menuProcSafetySwitches(uint8_t event); +#ifdef FRSKY +void menuProcTelemetry(uint8_t event); +#endif +#ifdef TEMPLATES +void menuProcTemplates(uint8_t event); +#endif + +MenuFuncP_PROGMEM APM menuTabModel[] = { + menuProcModelSelect, + menuProcModel, +#ifdef HELI + menuProcHeli, +#endif + menuProcPhasesAll, + menuProcExposAll, + menuProcMixAll, + menuProcLimits, + menuProcCurvesAll, + menuProcCustomSwitches, + menuProcFunctionSwitches, + menuProcSafetySwitches, +#ifdef FRSKY + menuProcTelemetry, +#endif +#ifdef TEMPLATES + menuProcTemplates +#endif +}; + +void menuProcModelSelect(uint8_t event) // TODO lignes sur tout du long +{ + TITLE("MODELSEL"); + int8_t subOld = m_posVert; + if (!check_submenu_simple(event, MAX_MODELS-1)) return; + + lcd_puts_P( 9*FW, 0, PSTR("free")); + lcd_outdezAtt( 17*FW, 0, EeFsGetFree(),0); + + DisplayScreenIndex(e_ModelSelect, DIM(menuTabModel), INVERS); + + int8_t sub = m_posVert; + static uint8_t sel_editMode; + + eeCheck(true); // force writing of current model data before this is changed + + switch(event) + { + //case EVT_KEY_FIRST(KEY_MENU): + case EVT_KEY_FIRST(KEY_EXIT): + if(sel_editMode){ + sel_editMode = false; + beepKey(); + killEvents(event); + eeLoadModel(g_eeGeneral.currModel = m_posVert); + STORE_GENERALVARS; + break; + } + //fallthrough + case EVT_KEY_FIRST(KEY_LEFT): + case EVT_KEY_FIRST(KEY_RIGHT): + if(g_eeGeneral.currModel != m_posVert) + { + killEvents(event); + g_eeGeneral.currModel = m_posVert; + eeLoadModel(g_eeGeneral.currModel); + STORE_GENERALVARS; + beepWarn1(); + } + //case EXIT handled in checkExit + if(event==EVT_KEY_FIRST(KEY_LEFT)) chainMenu(menuTabModel[DIM(menuTabModel)-1]); + if(event==EVT_KEY_FIRST(KEY_RIGHT)) chainMenu(menuProcModel); + return; + case EVT_KEY_FIRST(KEY_MENU): + sel_editMode = true; + beepKey(); + break; + case EVT_KEY_LONG(KEY_MENU): + if(sel_editMode){ + // message(PSTR("Duplicating model")); + if (eeDuplicateModel(sub)) { + beepKey(); + sel_editMode = false; + } + else { + beepWarn(); + } + } + break; + + case EVT_ENTRY: + sel_editMode = false; + m_posVert = g_eeGeneral.currModel; + break; + } + if(sel_editMode && subOld!=sub){ +#ifdef ASYNC_WRITE + s_sync_write = true; +#endif + EFile::swap(FILE_MODEL(subOld),FILE_MODEL(sub)); +#ifdef ASYNC_WRITE + s_sync_write = false; +#endif + } + + if(sub-s_pgOfs < 1) s_pgOfs = max(0,sub-1); + else if(sub-s_pgOfs >4 ) s_pgOfs = min(MAX_MODELS-6,sub-4); + for(uint8_t i=0; i<6; i++){ + uint8_t y=(i+2)*FH; + uint8_t k=i+s_pgOfs; + lcd_outdezNAtt( 3*FW, y, k+1, LEADING0+((sub==k) ? INVERS : 0), 2); + if(k==g_eeGeneral.currModel) lcd_putc(1, y,'*'); + char name[sizeof(g_model.name)]; + uint16_t size = eeLoadModelName(k, name); + if (size) { + putsModelName(4*FW, y, name, k, ((sub==k) ? (sel_editMode ? INVERS : 0 ) : 0)); + lcd_outdezAtt(20*FW, y, size, ((sub==k) ? (sel_editMode ? INVERS : 0 ) : 0)); + } + } +} + +const prog_char * s_warning = 0; +const prog_char * s_warning_info; +uint8_t s_warning_info_len; +// uint8_t s_warning_info_att not needed now +uint8_t s_confirmation = 0; + +void displayWarning(uint8_t event) +{ + if (s_warning) { + lcd_filled_rect(10, 16, 108, 40, WHITE); + lcd_rect(10, 16, 108, 40); + lcd_puts_P(16, 3*FH, s_warning); + // could be a place for a s_warning_info + lcd_puts_P(16, 5*FH, PSTR("[EXIT]")); + + switch(event) { + case EVT_KEY_FIRST(KEY_EXIT): + killEvents(event); + s_warning = 0; + break; + } + } +} + +void displayConfirmation(uint8_t event) +{ + if (s_warning) { + s_confirmation = false; + lcd_filled_rect(10, 16, 108, 40, WHITE); + lcd_rect(10, 16, 108, 40); + lcd_puts_P(16, 3*FH, s_warning); + if (s_warning_info) + lcd_putsnAtt(16, 4*FH, s_warning_info, s_warning_info_len, ZCHAR); + lcd_puts_P(16, 5*FH, PSTR("[MENU] [EXIT]")); + + switch(event) { + case EVT_KEY_FIRST(KEY_MENU): + s_confirmation = true; + // no break + case EVT_KEY_FIRST(KEY_EXIT): + killEvents(event); + s_warning = 0; + break; + } + } +} + +void EditName(uint8_t x, uint8_t y, char *name, uint8_t size, uint8_t event, bool active, uint8_t & cur) +{ + if (active && s_editMode) { + switch(event) { + case EVT_KEY_BREAK(KEY_LEFT): + if (cur>0) cur--; + break; + case EVT_KEY_BREAK(KEY_RIGHT): + if (cur=-26 && v<=26 && (event==EVT_KEY_LONG(KEY_RIGHT) || event==EVT_KEY_LONG(KEY_LEFT))) { + v = -v; // toggle case + STORE_MODELVARS; + if (event==EVT_KEY_LONG(KEY_LEFT)) + killEvents(KEY_LEFT); + } + name[cur] = v; + lcd_putcAtt(x+cur*FW, y, idx2char(v), INVERS); + } +} + +#define PARAM_OFS (9*FW) +void menuProcModel(uint8_t _event) +{ + uint8_t event = (s_warning ? 0 : _event); + + if (s_confirmation) { + EFile::rm(FILE_MODEL(g_eeGeneral.currModel)); // delete file + + uint8_t i = g_eeGeneral.currModel; // loop to find next available model + while (!EFile::exists(FILE_MODEL(i))) { + i--; + if (i>MAX_MODELS) i=MAX_MODELS-1; + if (i==g_eeGeneral.currModel) { + i=0; + break; + } + } + g_eeGeneral.currModel = i; + STORE_GENERALVARS; + eeLoadModel(i); // load default values + s_confirmation = 0; + chainMenu(menuProcModelSelect); + } + + MENU("SETUP", menuTabModel, e_Model, 11, {0,sizeof(g_model.name)-1,3,3,0,0,0,1,6,3/*, 0*/}); + + uint8_t sub = m_posVert; + uint8_t y = 1*FH; + + lcd_outdezNAtt(7*FW,0,g_eeGeneral.currModel+1,INVERS+LEADING0,2); + + uint8_t subN = 1; + if(s_pgOfs7*FH) return; + }subN++; + + TimerData *timer = &g_model.timer1; + for (uint8_t i=0; i<2; i++, timer=&g_model.timer2) { + if (s_pgOfsmode, sub==subN && m_posHorz==0 ? (s_editMode ? BLINK : INVERS) : 0); + putsTime(14*FW-3, y, timer->val, + (sub==subN && m_posHorz==1 ? (s_editMode ? BLINK : INVERS):0), + (sub==subN && m_posHorz==2 ? (s_editMode ? BLINK : INVERS):0) ); + lcd_putsnAtt(19*FW, y, PSTR("\x7e\x7f")+1-timer->dir,1,sub==subN && m_posHorz==3 ? (s_editMode ? BLINK : INVERS) : 0); + if (sub==subN && (s_editMode || p1valdiff)) { + switch (m_posHorz) { + case 0: + CHECK_INCDEC_MODELVAR(event, timer->mode, -(13+2*MAX_SWITCH),(13+2*MAX_SWITCH)); + break; + case 1: + { + int8_t min = timer->val/60; + CHECK_INCDEC_MODELVAR(event, min, 0, 59); + timer->val = timer->val%60 + min*60; + break; + } + case 2: + { + int8_t sec = timer->val%60; + sec -= checkIncDecModel(event, sec+2, 1, 62)-2; + timer->val -= sec ; + if ((int16_t)timer->val < 0) timer->val=0; + break; + } + case 3: + CHECK_INCDEC_MODELVAR(event, timer->dir, 0, 1); + break; + } + } + if ((y+=FH)>7*FH) return; + } subN++; + } + + if(s_pgOfs7*FH) return; + }subN++; + + if(s_pgOfs7*FH) return; + }subN++; + + if(s_pgOfs7*FH) return; + }subN++; + + if(s_pgOfs7*FH) return; + }subN++; + + if(s_pgOfs7*FH) return; + }subN++; + + if(s_pgOfs7*FH) return; + }subN++; + + if(s_pgOfsname, sizeof(p->name), event, attr, m_posHorz); + break; + case 1: + lcd_puts_P(0, y, PSTR("Switch")); + putsSwitches(10*FW, y, p->swtch, attr); + if(attr) CHECK_INCDEC_MODELVAR(event, p->swtch, -MAX_DRSWITCH, MAX_DRSWITCH); + break; + case 2: + lcd_puts_P(0, y, PSTR("Trims")); + for (uint8_t t=0; ttrim[t]; + if (v > 125) v = -129; + if (v < -125) { + uint8_t c = v < 0 ? 129 + v : 0; + if (c >= s_currIdx) c++; + lcd_putcAtt((10+t)*FW, y, '0'+c, (attr && m_posHorz==t) ? (s_editMode ? BLINK : INVERS) : 0); + } + else { + v = -130; + putsChnLetter((10+t)*FW, y, t+1, (attr && m_posHorz==t) ? (s_editMode ? BLINK : INVERS) : 0); + } + if (attr && m_posHorz==t && (s_editMode || p1valdiff)) { + v = checkIncDec(event, v, -130, -126, EE_MODEL); + if (checkIncDec_Ret) { + if (v == -130) v = 0; + p->trim[t] = (int8_t)v; + } + } + } + break; + case 3: + lcd_puts_P(0, y, PSTR("Fade In")); + lcd_outdezAtt(10*FW, y, p->fadeIn, attr|LEFT); + if(attr) CHECK_INCDEC_MODELVAR(event, p->fadeIn, 0, 15); + break; + case 4: + lcd_puts_P(0, y, PSTR("Fade Out")); + lcd_outdezAtt(10*FW, y, p->fadeOut, attr|LEFT); + if(attr) CHECK_INCDEC_MODELVAR(event, p->fadeOut, 0, 15); + break; + } + } +} + +void menuProcPhasesAll(uint8_t event) +{ + SIMPLE_MENU("FLIGHT PHASES", menuTabModel, e_PhasesAll, 1+MAX_PHASES+1); + + int8_t sub = m_posVert - 1; + + switch (event) { + case EVT_KEY_FIRST(KEY_MENU): + if (sub == MAX_PHASES) { + trimsCheckTimer = 200; // 2 seconds + } + // no break + case EVT_KEY_FIRST(KEY_RIGHT): + if (sub >= 0 && sub < MAX_PHASES) { + s_currIdx = sub; + pushMenu(menuProcPhaseOne); + } + break; + } + + uint8_t att; + for (uint8_t i=0; iname, 6, ZCHAR); + if (i == 0) { + lcd_puts_P(11*FW+FW/2, y, PSTR("(default)")); + } + else { + putsSwitches(11*FW+FW/2, y, p->swtch, 0); + for (uint8_t t=0; ttrim[t]; + if (v > 125) v = -129; + if (v < -125) { + uint8_t c = v < 0 ? 129 + v : 0; + if (c >= i) c++; + lcd_putc((16+t)*FW-FW/2, y, '0'+c); + } + else { + putsChnLetter((16+t)*FW-FW/2, y, t+1, 0); + } + } + } + if (p->fadeIn) lcd_putc(20*FW+2, y, 'I'); + if (p->fadeOut) lcd_putc(20*FW+2, y, 'O'); + if (p->fadeIn && p->fadeOut) lcd_putc(20*FW+2, y, '*'); + } + + att = (sub==MAX_PHASES && !trimsCheckTimer) ? INVERS : 0; + lcd_putsAtt(0, 7*FH, PSTR("Check Trims"), att); + putsFlightPhase(6*FW, 7*FH, getFlightPhase()+1, att); +} + +#ifdef HELI +void menuProcHeli(uint8_t event) +{ + SIMPLE_MENU("HELI SETUP", menuTabModel, e_Heli, 7); + + int8_t sub = m_posVert; + uint8_t y = 1*FH; + + uint8_t subN = 1; + if(s_pgOfs7*FH) return; + }subN++; + + if(s_pgOfs7*FH) return; + }subN++; + + if(s_pgOfs7*FH) return; + }subN++; + + if(s_pgOfs7*FH) return; + }subN++; + + if(s_pgOfs7*FH) return; + }subN++; + + if(s_pgOfs7*FH) return; + }subN++; +} +#endif + +static uint8_t s_curveChan; + +void menuProcCurveOne(uint8_t event) +{ +#define XD X0-2 + bool cv9 = s_curveChan >= MAX_CURVE5; + + SUBMENU("CURVE", 2+(cv9 ? 9 : 5), { 9/*,0 repeated...*/}); + lcd_outdezAtt(6*FW, 0, s_curveChan+1, INVERS); + + int8_t *crv = cv9 ? g_model.curves9[s_curveChan-MAX_CURVE5] : g_model.curves5[s_curveChan]; + + int8_t sub = m_posVert-1; + int8_t subSub = m_posHorz; + + switch(event){ + case EVT_KEY_FIRST(KEY_EXIT): + if(subSub!=0) { + subSub = m_posHorz = 0; + killEvents(event); + } + break; + case EVT_KEY_REPT(KEY_LEFT): + case EVT_KEY_FIRST(KEY_LEFT): + if (s_editMode && subSub>0) m_posHorz--; + break; + case EVT_KEY_REPT(KEY_RIGHT): + case EVT_KEY_FIRST(KEY_RIGHT): + if(s_editMode && subSub<(cv9 ? 9 : 5)) m_posHorz++; + break; + } + + s_editMode = m_posHorz; + + for (uint8_t i = 0; i < 5; i++) { + uint8_t y = i * FH + 16; + uint8_t attr = sub == i ? INVERS : 0; + lcd_outdezAtt(4 * FW, y, crv[i], attr); + } + if(cv9) + for (uint8_t i = 0; i < 4; i++) { + uint8_t y = i * FH + 16; + uint8_t attr = sub == i + 5 ? INVERS : 0; + lcd_outdezAtt(8 * FW, y, crv[i + 5], attr); + } + lcd_putsAtt( 2*FW, 1*FH,PSTR("EDIT->"),((sub == -1) && (subSub == 0)) ? INVERS : 0); + lcd_putsAtt( 2*FW, 7*FH,PSTR("PRESET"),sub == (cv9 ? 9 : 5) ? INVERS : 0); + + static int8_t dfltCrv; + if((sub<(cv9 ? 9 : 5)) && (sub>-1)) CHECK_INCDEC_MODELVAR( event, crv[sub], -100,100); + else if(sub>0){ //make sure we're not on "EDIT" + dfltCrv = checkIncDec(event, dfltCrv, -4, 4, EE_MODEL); + if (checkIncDec_Ret) { + if(cv9) for (uint8_t i = 0; i < 9; i++) crv[i] = (i-4)*dfltCrv* 100 / 16; + else for (uint8_t i = 0; i < 5; i++) crv[i] = (i-2)*dfltCrv* 100 / 8; + } + } + + if(s_editMode) + { + for(uint8_t i=0; i<(cv9 ? 9 : 5); i++) + { + uint8_t xx = XD-1-WCHART+i*WCHART/(cv9 ? 4 : 2); + uint8_t yy = Y0-crv[i]*WCHART/100; + + + if(subSub==(i+1)) + { + if((yy-2)=0; i--) { + ch = (expo ? expoaddress(i)->mode : mixaddress(i)->destCh); + if (ch != 0) { + count++; + } + } + return count; +} + +bool reachExpoMixCountLimit(uint8_t expo) +{ + // check mixers count limit + if (getExpoMixCount(expo) >= (expo ? MAX_EXPOS : MAX_MIXERS)) { + s_warning = (expo ? PSTR("No free expo!") : PSTR("No free mixer!")); + return true; + } + return false; +} + +void deleteExpoMix(uint8_t expo, uint8_t idx) +{ + if (expo) { + memmove(expoaddress(idx), expoaddress(idx+1), (MAX_EXPOS-(idx+1))*sizeof(ExpoData)); + memset(expoaddress(MAX_EXPOS-1), 0, sizeof(ExpoData)); + } + else { + memmove(mixaddress(idx), mixaddress(idx+1), (MAX_MIXERS-(idx+1))*sizeof(MixData)); + memset(mixaddress(MAX_MIXERS-1), 0, sizeof(MixData)); + } + STORE_MODELVARS; +} + +static int8_t s_currCh; +void insertExpoMix(uint8_t expo, uint8_t idx) +{ + if (expo) { + ExpoData *expo = expoaddress(idx); + memmove(expo+1, expo, (MAX_EXPOS-(idx+1))*sizeof(ExpoData)); + memset(expo,0,sizeof(ExpoData)); + expo->mode = 3; // pos&neg + expo->chn = s_currCh - 1; + expo->weight = 100; + } + else { + MixData *mix = mixaddress(idx); + memmove(mix+1, mix, (MAX_MIXERS-(idx+1))*sizeof(MixData)); + memset(mix,0,sizeof(MixData)); + mix->destCh = s_currCh; + mix->srcRaw = s_currCh; + mix->weight = 100; + } + STORE_MODELVARS; +} + +void copyExpoMix(uint8_t expo, uint8_t idx) +{ + if (expo) { + ExpoData *expo = expoaddress(idx); + memmove(expo+1, expo, (MAX_EXPOS-(idx+1))*sizeof(ExpoData)); + } + else { + MixData *mix = mixaddress(idx); + memmove(mix+1, mix, (MAX_MIXERS-(idx+1))*sizeof(MixData)); + } + STORE_MODELVARS; +} + +void memswap(void *a, void *b, uint8_t size) +{ + uint8_t *x = (uint8_t*)a; + uint8_t *y = (uint8_t*)b; + uint8_t temp ; + + while (size--) { + temp = *x; + *x++ = *y; + *y++ = temp; + } +} + +bool swapExpo(uint8_t &idx, uint8_t up) +{ + ExpoData *x = expoaddress(idx); + int8_t tgt_idx = (up ? idx-1 : idx+1); + + if (tgt_idx < 0) { + if (x->chn == 0) + return false; + x->chn--; + return true; + } + + if (tgt_idx == MAX_EXPOS) { + if (x->chn == NUM_STICKS-1) + return false; + x->chn++; + return true; + } + + ExpoData *y = expoaddress(tgt_idx); + if(x->chn != y->chn || !y->mode) { + if (up) { + if (x->chn>0) x->chn--; + else return false; + } + else { + if (x->chnchn++; + else return false; + } + return true; + } + + memswap(x, y, sizeof(ExpoData)); + idx = tgt_idx; + return true; +} + +bool swapMix(uint8_t &idx, uint8_t up) +{ + MixData *x = mixaddress(idx); + int8_t tgt_idx = (up ? idx-1 : idx+1); + + if (tgt_idx < 0) { + if (x->destCh == 1) + return false; + x->destCh--; + return true; + } + + if (tgt_idx == MAX_MIXERS) { + if (x->destCh == NUM_CHNOUT) + return false; + x->destCh++; + return true; + } + + MixData *y = mixaddress(tgt_idx); + if(x->destCh != y->destCh) { + if (up) { + if (x->destCh>1) x->destCh--; + else return false; + } + else { + if (x->destChdestCh++; + else return false; + } + return true; + } + + memswap(x, y, sizeof(MixData)); + idx = tgt_idx; + return true; +} + +bool swapExpoMix(uint8_t expo, uint8_t &idx, uint8_t up) +{ + bool result = (expo ? swapExpo(idx, up) : swapMix(idx, up)); + if (result) + STORE_MODELVARS; + return result; +} + +void editExpoVals(uint8_t event, uint8_t which, bool edit, uint8_t y, uint8_t idt) +{ + uint8_t invBlk = edit ? INVERS : 0; + // if(edit && stopBlink) invBlk = INVERS; + + ExpoData *ed = expoaddress(idt); + + switch(which) + { + case 0: + lcd_outdezAtt(9*FW, y, ed->expo, invBlk); + if(edit) CHECK_INCDEC_MODELVAR(event, ed->expo,-100, 100); + break; + case 1: + lcd_outdezAtt(9*FW, y, ed->weight, invBlk); + if(edit) CHECK_INCDEC_MODELVAR(event, ed->weight, 0, 100); + break; + case 2: + { + int8_t phase = ed->negPhase ? -ed->phase : +ed->phase; + putsFlightPhase(6*FW, y, phase, invBlk); + if(edit) { phase = checkIncDecModel(event, phase, -MAX_PHASES, MAX_PHASES); ed->negPhase = (phase < 0); ed->phase = abs(phase); } + } + break; + case 3: + putsSwitches(6*FW, y, ed->swtch, invBlk); + if(edit) CHECK_INCDEC_MODELVAR(event, ed->swtch, -MAX_DRSWITCH, MAX_DRSWITCH); + break; + case 4: + lcd_putsnAtt(6*FW, y, PSTR(CURV_STR)+3*(ed->curve+(ed->curve >= CURVE_BASE+4 ? 4 : 0)), 3, invBlk); + if(invBlk) CHECK_INCDEC_MODELVAR(event, ed->curve, 0, 15); + if(invBlk && ed->curve>=CURVE_BASE && event==EVT_KEY_FIRST(KEY_MENU)) { + s_curveChan = ed->curve - (ed->curve >= CURVE_BASE+4 ? CURVE_BASE-4 : CURVE_BASE); + pushMenu(menuProcCurveOne); + } + break; + case 5: + lcd_putsnAtt(6*FW, y, PSTR("---PosNeg")+9-3*ed->mode, 3, invBlk); + if(edit) ed->mode = 4 - checkIncDecModel(event, 4-ed->mode, 1, 3); + break; + case 6: + lcd_putsAtt(0*FW, y, PSTR("Delete"), invBlk); + if(edit && event==EVT_KEY_FIRST(KEY_MENU)) { + deleteExpoMix(1, idt); + killEvents(event); + popMenu(); + } + break; + } +} + +void menuProcExpoOne(uint8_t event) +{ + SIMPLE_SUBMENU("EXPO/DR", 7); + + ExpoData *ed = expoaddress(s_currIdx); + + putsChnRaw(lcd_lastPos+FW/2,0,ed->chn+1,0); + + int8_t sub = m_posVert; + + uint8_t y = FH; + + for (uint8_t i=0; i<7; i++) { + lcd_putsnAtt(0, y, PSTR("Expo WeightPhase Swtch Curve Mode ")+6*i, 6, 0); + editExpoVals(event, i, sub==i, y, s_currIdx); + y+=FH; + } + + lcd_vlineStip(X0, 0, DISPLAY_H, 0xee); + lcd_hlineStip(X0-WCHART, Y0, WCHART*2, 0xee); + + int16_t anas[NUM_STICKS] = {0}; + for(int8_t xv=-WCHART; xvchn] = xv*(RESX/WCHART); + applyExpos(anas); + uint16_t yv = (RESX + anas[ed->chn]) / 2; + yv = (DISPLAY_H-1) - yv * (DISPLAY_H-1) / RESX; + lcd_plot(X0+xv, yv, BLACK); + } + + int16_t x512 = calibratedStick[ed->chn]; + anas[ed->chn] = x512; + applyExpos(anas); + int16_t y512 = anas[ed->chn]; + + lcd_outdezAtt(20*FW, 6*FH, x512*25/256, 0); + lcd_outdezAtt(14*FW, 1*FH, y512*25/256, 0); + + x512 = X0+x512/(RESXu/WCHART); + y512 = (DISPLAY_H-1) - (uint16_t)((y512+RESX)/2) * (DISPLAY_H-1) / RESX; + + lcd_vline(x512, y512-3,3*2+1); + lcd_hline(x512-3, y512,3*2+1); +} + +void menuProcMixOne(uint8_t event) +{ + SIMPLE_SUBMENU_NOTITLE(14); + TITLEP(s_currCh ? PSTR("INSERT MIX ") : PSTR("EDIT MIX ")); + MixData *md2 = mixaddress(s_currIdx) ; + putsChn(lcd_lastPos+1*FW,0,md2->destCh,0); + int8_t sub = m_posVert; + + for(uint8_t k=0; k<7; k++) + { + uint8_t y = (k+1) * FH; + uint8_t i = k + s_pgOfs; + uint8_t attr = sub==i ? INVERS : 0; + switch(i) { + case 0: + lcd_puts_P( 2*FW,y,PSTR("Source")); + putsChnRaw( FW*10,y,md2->srcRaw,attr); + if(attr) CHECK_INCDEC_MODELVAR( event, md2->srcRaw, 1,NUM_XCHNRAW); + break; + case 1: + lcd_puts_P( 2*FW,y,PSTR("Weight")); + lcd_outdezAtt(FW*10,y,md2->weight,attr|LEFT); + if(attr) CHECK_INCDEC_MODELVAR( event, md2->weight, -125,125); + break; + case 2: + lcd_puts_P( 2*FW,y,PSTR("Offset")); + lcd_outdezAtt(FW*10,y,md2->sOffset,attr|LEFT); + if(attr) CHECK_INCDEC_MODELVAR( event, md2->sOffset, -125,125); + break; + case 3: + // TODO hidden when src is not a STICK as it has no sense + lcd_puts_P( 2*FW,y,PSTR("Trim")); + lcd_putsnAtt(FW*10,y, PSTR("ON OFF")+3*md2->carryTrim,3,attr); //default is 0=ON + if(attr) CHECK_INCDEC_MODELVAR( event, md2->carryTrim, 0,1); + break; + case 4: + lcd_putsAtt( 2*FW,y,PSTR("Curves"),0); + lcd_putsnAtt( FW*10,y,PSTR(CURV_STR)+md2->curve*3,3,attr); + if(attr) CHECK_INCDEC_MODELVAR( event, md2->curve, 0,MAX_CURVE5+MAX_CURVE9+7-1); + if(attr && md2->curve>=CURVE_BASE && event==EVT_KEY_FIRST(KEY_MENU)){ + s_curveChan = md2->curve-CURVE_BASE; + pushMenu(menuProcCurveOne); + } + break; + case 5: + lcd_puts_P( 2*FW,y,PSTR("Switch")); + putsSwitches(10*FW, y,md2->swtch,attr); + if(attr) CHECK_INCDEC_MODELVAR( event, md2->swtch, -MAX_SWITCH, MAX_SWITCH); + break; + case 6: + lcd_puts_P( 2*FW,y,PSTR("F.Phase")); + putsFlightPhase(10*FW, y, md2->phase, attr); + if(attr) CHECK_INCDEC_MODELVAR( event, md2->phase, -MAX_PHASES, MAX_PHASES); + break; + case 7: + lcd_puts_P( 2*FW,y,PSTR("Warning")); + if(md2->mixWarn) + lcd_outdezAtt(FW*10,y,md2->mixWarn,attr|LEFT); + else + lcd_putsAtt( FW*10,y,PSTR("OFF"),attr); + if(attr) CHECK_INCDEC_MODELVAR( event, md2->mixWarn, 0,3); + break; + case 8: + lcd_puts_P( 2*FW,y,PSTR("Multpx")); + lcd_putsnAtt(10*FW, y,PSTR("Add MultiplyReplace ")+8*md2->mltpx,8,attr); + if(attr) CHECK_INCDEC_MODELVAR( event, md2->mltpx, 0, 2); + break; + case 9: + lcd_puts_P( 2*FW,y,PSTR("Delay Down")); + lcd_outdezAtt(FW*16,y,md2->delayDown,attr); + if(attr) CHECK_INCDEC_MODELVAR( event, md2->delayDown, 0,15); + break; + case 10: + lcd_puts_P( 2*FW,y,PSTR("Delay Up")); + lcd_outdezAtt(FW*16,y,md2->delayUp,attr); + if(attr) CHECK_INCDEC_MODELVAR( event, md2->delayUp, 0,15); + break; + case 11: + lcd_puts_P( 2*FW,y,PSTR("Slow Down")); + lcd_outdezAtt(FW*16,y,md2->speedDown,attr); + if(attr) CHECK_INCDEC_MODELVAR( event, md2->speedDown, 0,15); + break; + case 12: + lcd_puts_P( 2*FW,y,PSTR("Slow Up")); + lcd_outdezAtt(FW*16,y,md2->speedUp,attr); + if(attr) CHECK_INCDEC_MODELVAR( event, md2->speedUp, 0,15); + break; + case 13: lcd_putsAtt( 2*FW,y,PSTR("DELETE MIX [MENU]"),attr); + if(attr && event==EVT_KEY_LONG(KEY_MENU)){ + killEvents(event); + deleteExpoMix(0, s_currIdx); + beepWarn1(); + popMenu(); + } + break; + } + } +} + +static uint8_t s_maxLines = 8; +static int8_t s_copyTgtOfs; +static uint8_t s_copySrcIdx; +static uint8_t s_copySrcCh; +static uint8_t s_copySrcRow; +#define COPY_MODE 1 +#define MOVE_MODE 2 +static uint8_t s_copyMode = 0; + +#define FIRST 0x10 +void displayMixerLine(uint8_t row, uint8_t mix, uint8_t ch, uint8_t idx, uint8_t cur, uint8_t event) +{ + uint8_t y = (row-s_pgOfs)*FH; + MixData *md = mixaddress(mix); + if (idx > 0) + lcd_putsnAtt(FW, y, PSTR("+=*=:=")+md->mltpx*2, 2, 0); + + putsChnRaw(4*FW, y, md->srcRaw, 0); + + uint8_t attr = ((s_copyMode || cur != row) ? 0 : INVERS); + lcd_outdezAtt(11*FW, y, md->weight, attr); + if (attr != 0) + CHECK_INCDEC_MODELVAR(event, md->weight, -125, 125); + + if (md->curve) lcd_putsnAtt(12*FW, y, PSTR(CURV_STR)+md->curve*3, 3, 0); + if (md->swtch) putsSwitches(16*FW+FW/2, y, md->swtch, 0); + + char cs = ' '; + if (md->speedDown || md->speedUp) + cs = 'S'; + if ((md->delayUp || md->delayDown)) + cs = (cs =='S' ? '*' : 'D'); + lcd_putcAtt(20*FW+1, y, cs, 0); + + if (s_copyMode) { + if ((s_copyMode==COPY_MODE || s_copyTgtOfs == 0) && s_copySrcCh == ch && mix == (s_copySrcIdx + (s_copyTgtOfs<0))) { + /* draw a border around the raw on selection mode (copy/move) */ + lcd_rect(22, y-1, DISPLAY_W-1-21, 9, s_copyMode == COPY_MODE ? 0xff : 0x55); + } + + if (row == cur) { + /* invert the raw when it's the current one */ + lcd_filled_rect(23, y, DISPLAY_W-1-23, 7); + } + } +} + +void displayExpoLine(uint8_t row, uint8_t expo, uint8_t ch, uint8_t idx, uint8_t cur, uint8_t event) +{ + uint8_t y = (row-s_pgOfs)*FH; + ExpoData *ed = expoaddress(expo); + + lcd_outdezAtt(7*FW, y, ed->expo, 0); + + uint8_t attr = ((s_copyMode || cur != row) ? 0 : INVERS); + lcd_outdezAtt(10*FW, y, ed->weight, attr); + if (attr != 0) + CHECK_INCDEC_MODELVAR(event, ed->weight, 0, 100); + + putsSwitches(11*FW+FW/2, y, ed->swtch, 0); + if (ed->curve) lcd_putsnAtt(16*FW, y, PSTR(CURV_STR)+ed->curve*3, 3, 0); + if (ed->mode!=3) lcd_putc(20*FW, y, ed->mode == 2 ? 127 : 126);//'|' : (stkVal[i] ? '<' : '>'),0);*/ + + if (s_copyMode) { + if ((s_copyMode==COPY_MODE || s_copyTgtOfs == 0) && s_copySrcCh == ch && expo == (s_copySrcIdx + (s_copyTgtOfs<0))) { + /* draw a border around the raw on selection mode (copy/move) */ + lcd_rect(22, y-1, DISPLAY_W-1-21, 9, s_copyMode == COPY_MODE ? 0xff : 0x55); + } + + if (row == cur) { + /* invert the raw when it's the current one */ + lcd_filled_rect(23, y, DISPLAY_W-1-23, 7); + } + } +} + +void menuProcExpoMix(uint8_t expo, uint8_t __event) +{ + uint8_t _event = (s_warning ? 0 : __event); + uint8_t event = _event; + uint8_t key = (event & 0x1f); + + if (s_copyMode) { + if (key == KEY_EXIT) + event -= KEY_EXIT; + } + + SIMPLE_MENU_NOTITLE(menuTabModel, expo ? e_ExposAll : e_MixAll, s_maxLines); + TITLEP(expo ? PSTR("EXPO/DR") : PSTR("MIXER")); + + uint8_t sub = m_posVert; + + switch(_event) + { + case EVT_ENTRY: + case EVT_ENTRY_UP: + s_copyMode = 0; + s_copyTgtOfs = 0; + break; + case EVT_KEY_LONG(KEY_EXIT): + if (s_copyMode && s_copyTgtOfs == 0) { + deleteExpoMix(expo, s_currIdx); + } + killEvents(_event); + // no break + case EVT_KEY_BREAK(KEY_EXIT): + if (s_copyTgtOfs) { + // cancel the current copy / move operation + if (s_copyMode == COPY_MODE) { + deleteExpoMix(expo, s_currIdx); + } + else { + do { + swapExpoMix(expo, s_currIdx, s_copyTgtOfs > 0); + if (s_copyTgtOfs < 0) + s_copyTgtOfs++; + else + s_copyTgtOfs--; + } while (s_copyTgtOfs != 0); + } + sub = m_posVert = s_copySrcRow; + } + s_copyMode = 0; + s_copyTgtOfs = 0; + break; + case EVT_KEY_BREAK(KEY_MENU): + if (!s_currCh || (s_copyMode && !s_copyTgtOfs)) { + s_copyMode = (s_copyMode == COPY_MODE ? MOVE_MODE : COPY_MODE); + s_copySrcIdx = s_currIdx; + s_copySrcCh = expo ? expoaddress(s_currIdx)->chn+1 : mixaddress(s_currIdx)->destCh; + s_copySrcRow = sub; + break; + } + // no break + case EVT_KEY_LONG(KEY_MENU): + if (s_copyTgtOfs) { + s_copyMode = 0; + s_copyTgtOfs = 0; + } + else { + if (s_copyMode) s_currCh = 0; + if (s_currCh) { + if (reachExpoMixCountLimit(expo)) break; + insertExpoMix(expo, s_currIdx); + } + pushMenu(expo ? menuProcExpoOne : menuProcMixOne); + s_copyMode = 0; + return; + } + killEvents(_event); + break; + case EVT_KEY_LONG(KEY_LEFT): + case EVT_KEY_LONG(KEY_RIGHT): + if (s_copyMode && !s_copyTgtOfs) { + if (reachExpoMixCountLimit(expo)) break; + s_currCh = (expo ? expoaddress(s_currIdx)->chn+1 : mixaddress(s_currIdx)->destCh); + if (_event == EVT_KEY_LONG(KEY_RIGHT)) s_currIdx++; + insertExpoMix(expo, s_currIdx); + pushMenu(expo ? menuProcExpoOne : menuProcMixOne); + s_copyMode = 0; + return; + } + break; + case EVT_KEY_REPT(KEY_UP): + case EVT_KEY_FIRST(KEY_UP): + case EVT_KEY_REPT(KEY_DOWN): + case EVT_KEY_FIRST(KEY_DOWN): + if (s_copyMode) { + uint8_t next_ofs = (key == KEY_UP ? s_copyTgtOfs - 1 : s_copyTgtOfs + 1); + + if (s_copyTgtOfs==0 && s_copyMode==COPY_MODE) { + // insert a mix on the same channel (just above / just below) + if (reachExpoMixCountLimit(expo)) break; + copyExpoMix(expo, s_currIdx); + if (key==KEY_DOWN) s_currIdx++; + else if (sub-s_pgOfs >= 6) s_pgOfs++; + } + else if (next_ofs==0 && s_copyMode==COPY_MODE) { + // delete the mix + deleteExpoMix(expo, s_currIdx); + if (key==KEY_UP) s_currIdx--; + } + else { + // only swap the mix with its neighbor + if (!swapExpoMix(expo, s_currIdx, key==KEY_UP)) break; + } + + s_copyTgtOfs = next_ofs; + } + break; + } + + lcd_outdezAtt(lcd_lastPos+2*FW+FW/2, 0, getExpoMixCount(expo)); + lcd_puts_P(lcd_lastPos, 0, expo ? PSTR("/14") : PSTR("/32")); + + s_currCh = 0; + uint8_t cur = 1; + uint8_t i = 0; + + for (uint8_t ch=1; ch<=(expo ? NUM_STICKS : NUM_CHNOUT); ch++) { + MixData *md=NULL; ExpoData *ed=NULL; + if (expo ? (ichn+1 == ch && ed->mode) : (idestCh == ch)) { + if (s_pgOfs < cur && cur-s_pgOfs < 8) { + if (expo) + putsChnRaw(0, (cur-s_pgOfs)*FH, ch, 0); + else + putsChn(0, (cur-s_pgOfs)*FH, ch, 0); // show CHx + } + uint8_t mixCnt = 0; + do { + if (s_copyMode) { + if (s_copyMode == MOVE_MODE && s_pgOfs < cur && cur-s_pgOfs < 8 && s_copySrcCh == ch && s_copyTgtOfs != 0 && i == (s_copySrcIdx + (s_copyTgtOfs<0))) { + uint8_t y = (cur-s_pgOfs)*FH; + lcd_rect(22, y-1, DISPLAY_W-1-21, 9, 0x55); + cur++; + } + if (s_currIdx == i) { + sub = m_posVert = cur; + s_currCh = ch; + } + } + else if (sub == cur) { + s_currIdx = i; + } + if (s_pgOfs < cur && cur-s_pgOfs < 8) { + if (expo) + displayExpoLine(cur, i, ch, mixCnt, sub, _event); + else + displayMixerLine(cur, i, ch, mixCnt, sub, _event); + } + cur++; mixCnt++; i++; md++; ed++; + } while (expo ? (ichn+1 == ch && ed->mode) : (idestCh == ch)); + if (s_copyMode == MOVE_MODE && s_pgOfs < cur && cur-s_pgOfs < 8 && s_copySrcCh == ch && i == (s_copySrcIdx + (s_copyTgtOfs<0))) { + uint8_t y = (cur-s_pgOfs)*FH; + lcd_rect(22, y-1, DISPLAY_W-1-21, 9, 0x55); + cur++; + } + } + else { + uint8_t attr = 0; + if (sub == cur) { + s_currIdx = i; + s_currCh = ch; + if (!s_copyMode) { + attr = INVERS; + } + } + if (s_pgOfs < cur && cur-s_pgOfs < 8) { + if (expo) + putsChnRaw(0, (cur-s_pgOfs)*FH, ch, attr); + else + putsChn(0, (cur-s_pgOfs)*FH, ch, attr); // show CHx + if (s_copyMode == MOVE_MODE && s_copySrcCh == ch) { + uint8_t y = (cur-s_pgOfs)*FH; + lcd_rect(22, y-1, DISPLAY_W-1-21, 9, 0x55); + } + } + cur++; + } + } + s_maxLines = cur; + if (sub >= s_maxLines-1) m_posVert = s_maxLines-1; + displayWarning(__event); +} + +void menuProcExposAll(uint8_t event) +{ + return menuProcExpoMix(1, event); +} + +void menuProcMixAll(uint8_t event) +{ + return menuProcExpoMix(0, event); +} + +void menuProcLimits(uint8_t event) +{ + MENU("LIMITS", menuTabModel, e_Limits, NUM_CHNOUT+2, {0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3/*, 0*/}); + + static bool swVal[NUM_CHNOUT]; + + uint8_t y = 0; + uint8_t k = 0; + int8_t sub = m_posVert - 1; + + switch(event) + { + case EVT_KEY_LONG(KEY_MENU): + if(sub>=0 && suboffset = (ld->revert) ? -v : v; + STORE_MODELVARS; + break; + } + } + break; + } + + for(uint8_t i=0; i<7; i++){ + y=(i+1)*FH; + k=i+s_pgOfs; + if(k==NUM_CHNOUT) break; + LimitData *ld = limitaddress( k ) ; + int16_t v = (ld->revert) ? -ld->offset : ld->offset; + if((g_chans512[k] - v) > 50) swVal[k] = (true==ld->revert);// Switch to raw inputs? - remove trim! + if((g_chans512[k] - v) < -50) swVal[k] = (false==ld->revert); + putsChn(0,y,k+1,0); + lcd_putcAtt(12*FW+FW/2, y, (swVal[k] ? 127 : 126),0); //'<' : '>' + for(uint8_t j=0; j<4;j++){ + uint8_t attr = ((sub==k && m_posHorz==j) ? (s_editMode ? BLINK : INVERS) : 0); + switch(j) + { + case 0: + lcd_outdezAtt( 8*FW, y, ld->offset, attr|PREC1); + if(attr && (s_editMode || p1valdiff)) { + ld->offset = checkIncDec(event, ld->offset, -1000, 1000, EE_MODEL); + } + break; + case 1: + lcd_outdezAtt( 12*FW, y, (int8_t)(ld->min-100), attr); + if(attr && (s_editMode || p1valdiff)) { + ld->min -= 100; + if(g_model.extendedLimits) + CHECK_INCDEC_MODELVAR( event, ld->min, -125,125); + else + CHECK_INCDEC_MODELVAR( event, ld->min, -100,100); + ld->min += 100; + } + break; + case 2: + lcd_outdezAtt( 17*FW, y, (int8_t)(ld->max+100), attr); + if(attr && (s_editMode || p1valdiff)) { + ld->max += 100; + if(g_model.extendedLimits) + CHECK_INCDEC_MODELVAR( event, ld->max, -125,125); + else + CHECK_INCDEC_MODELVAR( event, ld->max, -100,100); + ld->max -= 100; + } + break; + case 3: + lcd_putsnAtt( 18*FW, y, PSTR("---INV")+ld->revert*3,3,attr); + if(attr && (s_editMode || p1valdiff)) { + CHECK_INCDEC_MODELVAR(event, ld->revert, 0, 1); + } + break; + } + } + } + if(k==NUM_CHNOUT){ + //last line available - add the "copy trim menu" line + uint8_t attr = (sub==NUM_CHNOUT) ? INVERS : 0; + lcd_putsAtt(3*FW,y,PSTR("COPY TRIM [MENU]"),s_noHi ? 0 : attr); + if(attr && event==EVT_KEY_LONG(KEY_MENU)) { + s_noHi = NO_HI_LEN; + killEvents(event); + moveTrimsToOffsets(); // if highlighted and menu pressed - copy trims + } + } +} + +void menuProcCurvesAll(uint8_t event) +{ + SIMPLE_MENU("CURVES", menuTabModel, e_CurvesAll, 1+MAX_CURVE5+MAX_CURVE9); + + int8_t sub = m_posVert - 1; + + switch (event) { + case EVT_KEY_FIRST(KEY_RIGHT): + case EVT_KEY_FIRST(KEY_MENU): + if (sub >= 0) { + s_curveChan = sub; + pushMenu(menuProcCurveOne); + } + break; + } + + uint8_t y = 1*FH; + uint8_t yd = 1; + uint8_t m = 0; + for (uint8_t i = 0; i < 7; i++) { + uint8_t k = i + s_pgOfs; + uint8_t attr = sub == k ? INVERS : 0; + bool cv9 = k >= MAX_CURVE5; + + if(cv9 && (yd>6)) break; + if(yd>7) break; + if(!m) m = attr; + lcd_putsAtt( FW*0, y,PSTR("CV"),attr); + lcd_outdezAtt( (k<9) ? FW*3 : FW*4-1, y,k+1 ,attr); + + int8_t *crv = cv9 ? g_model.curves9[k-MAX_CURVE5] : g_model.curves5[k]; + for (uint8_t j = 0; j < (5); j++) { + lcd_outdezAtt( j*(3*FW+3) + 7*FW, y, crv[j], 0); + } + y += FH;yd++; + if(cv9){ + for (uint8_t j = 0; j < 4; j++) { + lcd_outdezAtt( j*(3*FW+3) + 7*FW, y, crv[j+5], 0); + } + y += FH;yd++; + } + } + + if(!m) s_pgOfs++; +} + +void menuProcCustomSwitches(uint8_t event) +{ + MENU("CUSTOM SWITCHES", menuTabModel, e_CustomSwitches, NUM_CSW+1, {0, 2/*repeated...*/}); + + uint8_t y = 0; + uint8_t k = 0; + int8_t sub = m_posVert - 1; + + for(uint8_t i=0; i<7; i++){ + y=(i+1)*FH; + k=i+s_pgOfs; + if(k==NUM_CSW) break; + uint8_t attr = (sub==k ? (s_editMode ? BLINK : INVERS) : 0); + CustomSwData &cs = g_model.customSw[k]; + + //write SW names here + lcd_putsnAtt( 0*FW , y, PSTR("SW"),2,0); + lcd_putc( 2*FW , y, k + (k>8 ? 'A'-9: '1')); + lcd_putsnAtt( 4*FW , y, PSTR(CSWITCH_STR)+CSW_LEN_FUNC*cs.func,CSW_LEN_FUNC,m_posHorz==0 ? attr : 0); + + uint8_t cstate = CS_STATE(cs.func); + + if(cstate == CS_VOFS) + { + putsChnRaw( 12*FW, y, cs.v1 ,m_posHorz==1 ? attr : 0); +#if defined(FRSKY) + if (cs.v1 > NUM_XCHNRAW-NUM_TELEMETRY) + lcd_outdezAtt( 20*FW, y, 125+cs.v2 ,m_posHorz==2 ? attr : 0); + else +#endif + lcd_outdezAtt( 20*FW, y, cs.v2 ,m_posHorz==2 ? attr : 0); + } + else if(cstate == CS_VBOOL) + { + putsSwitches(13*FW, y, cs.v1 ,m_posHorz==1 ? attr : 0); + putsSwitches(17*FW, y, cs.v2 ,m_posHorz==2 ? attr : 0); + } + else // cstate == CS_COMP + { + putsChnRaw( 12*FW, y, cs.v1 ,m_posHorz==1 ? attr : 0); + putsChnRaw( 17*FW, y, cs.v2 ,m_posHorz==2 ? attr : 0); + } + + if((s_editMode || p1valdiff) && attr) + switch (m_posHorz) { + case 0: + CHECK_INCDEC_MODELVAR( event, cs.func, 0,CS_MAXF); + if(cstate != CS_STATE(cs.func)) + { + cs.v1 = 0; + cs.v2 = 0; + } + break; + case 1: + switch (cstate) { + case (CS_VOFS): + CHECK_INCDEC_MODELVAR( event, cs.v1, 0,NUM_XCHNRAW); + break; + case (CS_VBOOL): + CHECK_INCDEC_MODELVAR( event, cs.v1, -MAX_SWITCH,MAX_SWITCH); + break; + case (CS_VCOMP): + CHECK_INCDEC_MODELVAR( event, cs.v1, 0,NUM_XCHNRAW); + break; + default: + break; + } + break; + case 2: + switch (cstate) { + case (CS_VOFS): + CHECK_INCDEC_MODELVAR( event, cs.v2, -125,125); + break; + case (CS_VBOOL): + CHECK_INCDEC_MODELVAR( event, cs.v2, -MAX_SWITCH,MAX_SWITCH); + break; + case (CS_VCOMP): + CHECK_INCDEC_MODELVAR( event, cs.v2, 0,NUM_XCHNRAW); + break; + default: + break; + } + } + } +} + +void menuProcFunctionSwitches(uint8_t event) +{ + MENU("FUNCTION SWITCHES", menuTabModel, e_FunctionSwitches, NUM_FSW+1, {0, 1/*repeated*/}); + + uint8_t y = 0; + uint8_t k = 0; + int8_t sub = m_posVert - 1; + + for(uint8_t i=0; i<7; i++) { + y=(i+1)*FH; + k=i+s_pgOfs; + if(k==NUM_CHNOUT) break; + FuncSwData *sd = &g_model.funcSw[k]; + for (uint8_t j=0; j<2; j++) { + uint8_t attr = ((sub==k && m_posHorz==j) ? (s_editMode ? BLINK : INVERS) : 0); + switch (j) { + case 0: + putsSwitches(1*FW, y, sd->swtch, attr); + if (attr && (s_editMode || p1valdiff)) { + CHECK_INCDEC_MODELVAR( event, sd->swtch, -MAX_SWITCH, MAX_SWITCH); + } + break; + case 1: + if (sd->swtch) { + lcd_putsnAtt(5*FW, y, PSTR(FSWITCH_STR)+FSW_LEN_FUNC*sd->func, FSW_LEN_FUNC, attr); + if (attr && (s_editMode || p1valdiff)) { + CHECK_INCDEC_MODELVAR( event, sd->func, 0, FUNC_LAST); + } + } + else if (attr) { + m_posHorz = 0; + } + break; + } + } + } +} + +void menuProcSafetySwitches(uint8_t event) +{ + MENU("SAFETY SWITCHES", menuTabModel, e_SafetySwitches, NUM_CHNOUT+1, {0, 1/*repeated*/}); + + uint8_t y = 0; + uint8_t k = 0; + + for(uint8_t i=0; i<7; i++){ + y=(i+1)*FH; + k=i+s_pgOfs; + if(k==NUM_CHNOUT) break; + SafetySwData *sd = &g_model.safetySw[k]; + putsChn(0,y,k+1,0); + for(uint8_t j=0; j<=2;j++){ + uint8_t attr = ((m_posVert-1==k && m_posHorz==j) ? (s_editMode ? BLINK : INVERS) : 0); + switch(j) + { + case 0: + putsSwitches(6*FW, y, sd->swtch , attr); + if(attr && (s_editMode || p1valdiff)) { + CHECK_INCDEC_MODELVAR( event, sd->swtch, -MAX_SWITCH, MAX_SWITCH); + } + break; + case 1: + lcd_outdezAtt(16*FW, y, sd->val, attr); + if(attr && (s_editMode || p1valdiff)) { + CHECK_INCDEC_MODELVAR( event, sd->val, -125, 125); + } + break; + } + } + } +} + +#ifdef FRSKY +void menuProcTelemetry(uint8_t event) +{ + MENU("TELEMETRY", menuTabModel, e_Telemetry, 13, {0, -1, 1, 0, 1, 2, 2, -1, 1, 0, 1, 2/*, 2*/}); + + int8_t sub = m_posVert; + uint8_t blink; + uint8_t y; + + switch(event){ + case EVT_KEY_BREAK(KEY_DOWN): + case EVT_KEY_BREAK(KEY_UP): + case EVT_KEY_BREAK(KEY_LEFT): + case EVT_KEY_BREAK(KEY_RIGHT): + if(s_editMode) + FRSKY_setModelAlarms(); // update Fr-Sky module when edit mode exited + } + + blink = s_editMode ? BLINK : INVERS ; + uint8_t subN = 1; + uint8_t t; + int16_t val; + + for (int i=0; i<2; i++) { + if(s_pgOfs")+ALARM_GREATER(i, j),1,(sub==subN && m_posHorz==1 ? blink:0)); + uint8_t alarmValue = ((uint16_t)g_model.frsky.channels[i].alarms_value[j] * g_model.frsky.channels[i].ratio) / 255; + putsTelemetry(14*FW, y, alarmValue, g_model.frsky.channels[i].type, (sub==subN && m_posHorz==2 ? blink:0) | LEFT); + + if(sub==subN && (s_editMode || p1valdiff)) { + switch (m_posHorz) { + case 0: + t = ALARM_LEVEL(i, j); + g_model.frsky.channels[i].alarms_level = (g_model.frsky.channels[i].alarms_level & ~(3<<(2*j))) + (checkIncDec(event, t, 0, 3, EE_MODEL) << (2*j)); + break; + case 1: + t = ALARM_GREATER(i, j); + g_model.frsky.channels[i].alarms_greater = (g_model.frsky.channels[i].alarms_greater & ~(1<=0) && (sub<(int8_t)NUM_TEMPLATES)) + applyTemplate(sub); + beepWarn1(); + break; + } + + y=1*FH; + for(uint8_t i=0; i<7; i++){ + k=i+s_pgOfs; + if(k==NUM_TEMPLATES) break; + + //write mix names here + lcd_outdezNAtt(3*FW, y, k+1, (sub==k ? INVERS : 0)|LEADING0, 2); + lcd_putsAtt( 4*FW, y, n_Templates[k],BSS | (s_noHi ? 0 : (sub==k ? INVERS : 0))); + y+=FH; + } + if(y>7*FH) return; + + uint8_t attr = s_noHi ? 0 : ((sub==NUM_TEMPLATES) ? INVERS : 0); + lcd_putsAtt( 1*FW,y,PSTR("CLEAR MIXES [MENU]"),attr); + y+=FH; +} +#endif + diff --git a/src/myeeprom.h b/src/myeeprom.h new file mode 100644 index 000000000..54321f5ed --- /dev/null +++ b/src/myeeprom.h @@ -0,0 +1,256 @@ +/* + * Author - Erez Raviv + * + * Based on th9x -> http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + */ +#ifndef eeprom_h +#define eeprom_h + +#include + +#define WARN_THR_BIT 0x01 +#define WARN_BEP_BIT 0x80 +#define WARN_SW_BIT 0x02 +#define WARN_MEM_BIT 0x04 +#define WARN_BVAL_BIT 0x38 + +#define WARN_THR (!(g_eeGeneral.warnOpts & WARN_THR_BIT)) +#define WARN_BEP (!(g_eeGeneral.warnOpts & WARN_BEP_BIT)) +#define WARN_SW (!(g_eeGeneral.warnOpts & WARN_SW_BIT)) +#define WARN_MEM (!(g_eeGeneral.warnOpts & WARN_MEM_BIT)) +#define BEEP_VAL ( (g_eeGeneral.warnOpts & WARN_BVAL_BIT) >>3 ) + +#define EEPROM_ER9X_VER 4 +#define EEPROM_ER9X_r751 9 + +#define EEPROM_VER_r584 3 +#define EEPROM_VER_r751 5 +#define EEPROM_VER 105 + +typedef struct t_TrainerMix { + uint8_t srcChn:6; // 0-7 = ch1-8 + uint8_t mode:2; // off,add-mode,subst-mode + int8_t studWeight; +} __attribute__((packed)) TrainerMix; // + +typedef struct t_TrainerData { + int16_t calib[4]; + TrainerMix mix[4]; +} __attribute__((packed)) TrainerData; + +typedef struct t_FrSkyRSSIAlarm { + uint8_t level:2; + int8_t value:6; +} __attribute__((packed)) FrSkyRSSIAlarm; + +typedef struct t_EEGeneral { + uint8_t myVers; + int16_t calibMid[7]; + int16_t calibSpanNeg[7]; + int16_t calibSpanPos[7]; + uint16_t chkSum; + uint8_t currModel; //0..15 + uint8_t contrast; + uint8_t vBatWarn; + int8_t vBatCalib; + int8_t lightSw; + TrainerData trainer; + uint8_t view; //index of subview in main scrren + uint8_t disableThrottleWarning:1; + int8_t switchWarning:2; // -1=down, 0=off, 1=up + uint8_t beeperVal:3; + uint8_t disableMemoryWarning:1; + uint8_t disableAlarmWarning:1; + uint8_t stickMode; + uint8_t inactivityTimer; + uint8_t throttleReversed:1; + uint8_t minuteBeep:1; + uint8_t preBeep:1; + uint8_t flashBeep:1; + uint8_t disableSplashScreen:1; + uint8_t enableTelemetryAlarm:1; // 0=no, 1=yes (Sound alarm when there's no telem. data coming in) + uint8_t spare:2; + uint8_t filterInput; + uint8_t lightAutoOff; + uint8_t templateSetup; //RETA order according to chout_ar array + int8_t PPM_Multiplier; + FrSkyRSSIAlarm frskyRssiAlarms[2]; +} __attribute__((packed)) EEGeneral; + +// eeprom modelspec + +typedef struct t_ExpoData { + uint8_t mode:2; // 0=end, 1=pos, 2=neg, 3=both + uint8_t chn:2; + uint8_t curve:4; // 0=no curve, 1-6=std curves, 7-10=CV1-CV4, 11-15=CV9-CV13 + int8_t swtch:5; + uint8_t phase:3; // if negPhase is 0: 0=normal, 5=FP4 if negPhase is 1: 5=!FP4 + uint8_t negPhase:1; + uint8_t weight:7; + int8_t expo; +} __attribute__((packed)) ExpoData; + +typedef struct t_LimitData { + int8_t min; + int8_t max; + bool revert; + int16_t offset; +} __attribute__((packed)) LimitData; + +typedef struct t_MixData { + uint8_t destCh:5; // 0, 1..NUM_CHNOUT + uint8_t mixWarn:3; // mixer warning +#define MIX_P1 5 +#define MIX_P2 6 +#define MIX_P3 7 +#define MIX_MAX 8 +#define MIX_FULL 9 +#define MIX_CYC1 10 +#define MIX_CYC2 11 +#define MIX_CYC3 12 + uint8_t srcRaw; // + int8_t weight; + int8_t swtch; + uint8_t curve; // 0=symmetrisch, 1=no neg, 2=no pos + uint8_t delayUp:4; + uint8_t delayDown:4; + uint8_t speedUp:4; // Servogeschwindigkeit aus Tabelle (10ms Cycle) + uint8_t speedDown:4; // 0 nichts + uint8_t carryTrim:1; +#define MLTPX_ADD 0 +#define MLTPX_MUL 1 +#define MLTPX_REP 2 + uint8_t mltpx:3; // multiplex method 0=+ 1=* 2=replace + int8_t phase:4; // -5=!FP4, 0=normal, 5=FP4 + int8_t sOffset; +} __attribute__((packed)) MixData; + +typedef struct t_CustomSwData { // Custom Switches data + int8_t v1; //input + int8_t v2; //offset + uint8_t func; +} __attribute__((packed)) CustomSwData; + +typedef struct t_SafetySwData { // Safety Switches data + int8_t swtch; + int8_t val; +} __attribute__((packed)) SafetySwData; + +#define FUNC_TRAINER 1 +#define FUNC_INSTANT_TRIM 2 +#define FUNC_TRIMS_2_OFS 3 +#define FUNC_VIEW_TELEMETRY 4 +#define FUNC_LAST 4 + +typedef struct t_FuncSwData { // Function Switches data + int8_t swtch; //input + uint8_t func; +} __attribute__((packed)) FuncSwData; + +typedef struct t_FrSkyChannelData { + uint8_t ratio; // 0.0 means not used, 0.1V steps EG. 6.6 Volts = 66. 25.1V = 251, etc. + uint8_t type:4; // channel unit (0=volts, ...) + int8_t offset:4; // calibration offset. Signed 0.1V steps. EG. -4 to substract 0.4V + uint8_t alarms_value[2]; // 0.1V steps EG. 6.6 Volts = 66. 25.1V = 251, etc. + uint8_t alarms_level:4; + uint8_t alarms_greater:2; // 0=LT(<), 1=GT(>) + uint8_t spare:2; + int8_t barMin; // minimum for bar display + uint8_t barMax; // ditto for max display (would usually = ratio) +} __attribute__((packed)) FrSkyChannelData; + +typedef struct t_FrSkyData { + FrSkyChannelData channels[2]; +} __attribute__((packed)) FrSkyData; + +typedef struct t_SwashRingData { // Swash Ring data + uint8_t invertELE:1; + uint8_t invertAIL:1; + uint8_t invertCOL:1; + uint8_t type:5; + uint8_t collectiveSource; + uint8_t value; + +/* TODO BSS everything is in comments in menus.cpp, how should it be used? + uint8_t lim; // 0 mean off 100 full deflection + uint8_t chX; // 2 channels to limit + uint8_t chY; // 2 channels to limit */ +} __attribute__((packed)) SwashRingData; + +typedef struct t_PhaseData { +#define TRIM_MAX 125 +#define TRIM_MIN (-TRIM_MAX) + int8_t trim[4]; // -125..125 => trim value, 127 => use trim of phase 0, -128, -127, -126 => use trim of phases 1|2|3|4 instead + int8_t swtch; // swtch of phase[0] is not used + char name[6]; + uint8_t fadeIn:4; + uint8_t fadeOut:4; +} __attribute__((packed)) PhaseData; + +#define MAX_MODELS 16 +#define MAX_PHASES 5 +#define MAX_MIXERS 32 +#define MAX_EXPOS 14 +#define MAX_CURVE5 8 +#define MAX_CURVE9 8 + +#define NUM_CHNOUT 16 // number of real output channels CH1-CH16 +#define NUM_CSW 12 // number of custom switches +#define NUM_FSW 12 // number of functions assigned to switches + +typedef struct t_TimerData { + int8_t mode; // timer trigger source -> off, abs, stk, stk%, sw/!sw, !m_sw/!m_sw + uint16_t val:14; + uint8_t persistent:1; + uint8_t dir:1; // 0=>Count Down, 1=>Count Up +} __attribute__((packed)) TimerData; + +typedef struct t_ModelData { + char name[10]; // 10 must be first for eeLoadModelName + TimerData timer1; // TODO timers array + uint8_t protocol:3; + int8_t ppmNCH:3; + uint8_t thrTrim:1; // Enable Throttle Trim + uint8_t thrExpo:1; // Enable Throttle Expo + uint8_t trimInc:3; // Trim Increments + uint8_t spare1:1; + uint8_t pulsePol:1; + uint8_t extendedLimits:1; + uint8_t extendedTrims:1; + uint8_t spare2:1; + int8_t ppmDelay; + uint8_t beepANACenter; // 1<<0->A1.. 1<<6->A7 + TimerData timer2; + MixData mixData[MAX_MIXERS]; + LimitData limitData[NUM_CHNOUT]; + ExpoData expoData[MAX_EXPOS]; + int8_t curves5[MAX_CURVE5][5]; + int8_t curves9[MAX_CURVE9][9]; + CustomSwData customSw[NUM_CSW]; + SafetySwData safetySw[NUM_CHNOUT]; + FuncSwData funcSw[NUM_FSW]; + SwashRingData swashR; + PhaseData phaseData[MAX_PHASES]; +#define SUBTRIM_MAX (1024-TRIM_MAX) +#define SUBTRIM_MIN (-SUBTRIM_MAX) + int16_t subtrim[NUM_STICKS]; + FrSkyData frsky; +} __attribute__((packed)) ModelData; + +extern EEGeneral g_eeGeneral; +extern ModelData g_model; + +#define TOTAL_EEPROM_USAGE (sizeof(ModelData)*MAX_MODELS + sizeof(EEGeneral)) + +#endif +/*eof*/ diff --git a/src/pers.cpp b/src/pers.cpp new file mode 100644 index 000000000..b4fa8694f --- /dev/null +++ b/src/pers.cpp @@ -0,0 +1,411 @@ +/* + * Authors (alphabetical order) + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * + * gruvin9x is based on code named er9x by + * Author - Erez Raviv , which is in turn + * was based on the original (and ongoing) project by Thomas Husterer, + * th9x -- http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + */ + +#ifdef TRANSLATIONS +#include "eeprom_v4.h" +#include "eeprom_v3.h" +#endif + +#include "gruvin9x.h" +#include "templates.h" + +RlcFile theFile; //used for any file operation + +#define FILE_TYP_GENERAL 1 +#define FILE_TYP_MODEL 2 + +void generalDefault() +{ + memset(&g_eeGeneral,0,sizeof(g_eeGeneral)); + g_eeGeneral.myVers = EEPROM_VER; + g_eeGeneral.currModel= 0; + g_eeGeneral.contrast = 25; + g_eeGeneral.vBatWarn = 90; +#ifdef DEFAULTMODE1 + g_eeGeneral.stickMode= 0; // default to mode 1 +#else + g_eeGeneral.stickMode= 2; // default to mode 2 +#endif + for (int i = 0; i < 7; ++i) { + g_eeGeneral.calibMid[i] = 0x200; + g_eeGeneral.calibSpanNeg[i] = 0x180; + g_eeGeneral.calibSpanPos[i] = 0x180; + } + int16_t sum=0; + for(int i=0; i<12;i++) sum+=g_eeGeneral.calibMid[i]; + g_eeGeneral.chkSum = sum; +} + +#ifdef TRANSLATIONS +uint8_t Translate() +{ + if (g_eeGeneral.myVers == EEPROM_VER_r584 || g_eeGeneral.myVers == EEPROM_ER9X_VER) { + alert(g_eeGeneral.myVers == EEPROM_VER_r584 ? PSTR("EEprom Data v3") : PSTR("EEprom Data Er9x v4"), true); + message(PSTR("EEPROM Converting")); + theFile.readRlc1((uint8_t*)&g_eeGeneral, sizeof(g_eeGeneral)); + memset(&g_eeGeneral.frskyRssiAlarms, 0 , sizeof(g_eeGeneral.frskyRssiAlarms)); + if (g_eeGeneral.myVers == EEPROM_VER_r584) { + // previous version had only 6 custom switches, OFF and ON values have to be shifted 6 + if (g_eeGeneral.lightSw == MAX_SWITCH-6) + g_eeGeneral.lightSw += 6; + if (g_eeGeneral.lightSw == -MAX_SWITCH+6) + g_eeGeneral.lightSw -= 6; + } + g_eeGeneral.view = 0; // will not translate the view index + EEPROM_V3::EEGeneral *old = (EEPROM_V3::EEGeneral *)&g_eeGeneral; + g_eeGeneral.disableMemoryWarning = old->disableMemoryWarning; + g_eeGeneral.switchWarning = old->disableSwitchWarning ? 0 : -1; + for (uint8_t i=0; i<4; i++) { + g_eeGeneral.trainer.mix[i].srcChn = old->trainer.mix[i].srcChn; + g_eeGeneral.trainer.mix[i].mode = old->trainer.mix[i].mode; + g_eeGeneral.trainer.mix[i].studWeight = old->trainer.mix[i].studWeight * 13 / 4; + } + for (uint8_t id=0; id 0) { + EEPROM_V4::ModelData *v4 = (EEPROM_V4::ModelData *)&g_model; + EEPROM_V3::ModelData *v3 = (EEPROM_V3::ModelData *)&g_model; + SwashRingData swashR; + swashR.invertELE = v4->swashInvertELE; + swashR.invertAIL = v4->swashInvertAIL; + swashR.invertCOL = v4->swashInvertCOL; + swashR.type = v4->swashType; + swashR.collectiveSource = v4->swashCollectiveSource; + swashR.value = v4->swashRingValue; + int8_t trims[4]; + memcpy(&trims[0], &v3->trim[0], 4); + int8_t trimSw = v3->trimSw; + for (uint8_t i=0; i<10; i++) + g_model.name[i] = char2idx(g_model.name[i]); + g_model.timer1.mode = v3->tmrMode; + g_model.timer1.val = v3->tmrVal; + g_model.timer1.persistent = 0; + g_model.timer1.dir = v3->tmrDir; + g_model.protocol = v3->protocol; + g_model.ppmNCH = v3->ppmNCH; + g_model.thrTrim = v3->thrTrim; + g_model.thrExpo = v3->thrExpo; + g_model.trimInc = v3->trimInc; + g_model.spare1 = 0; + g_model.pulsePol = v3->pulsePol; + if (g_eeGeneral.myVers == EEPROM_ER9X_VER) { + g_model.extendedLimits = v4->extendedLimits; + } + else { + g_model.extendedLimits = 0; + } + g_model.extendedTrims = 0; + g_model.spare2 = 0; + g_model.ppmDelay = v3->ppmDelay; + g_model.beepANACenter = v3->beepANACenter; + g_model.timer2.mode = 0; + g_model.timer2.val = 0; + g_model.timer2.persistent = 0; + g_model.timer2.dir = 0; + for (uint8_t i=0; imixData[i], sizeof(MixData)); // MixData size changed! + g_model.mixData[i].mixWarn = g_model.mixData[i].phase; + g_model.mixData[i].phase = 0; + } + assert((char *)&g_model.limitData[0] < (char *)&v3->limitData[0]); + memmove(&g_model.limitData[0], &v3->limitData[0], sizeof(LimitData)*NUM_CHNOUT); + assert((char *)&g_model.expoData[0] < (char *)v3->expoData); + EEPROM_V4::ExpoData expo4[4]; + memcpy(&expo4[0], &v4->expoData[0], sizeof(expo4)); + memset(&g_model.expoData[0], 0, sizeof(expo4)); + uint8_t e = 0; + for (uint8_t ch=0; ch<4 && e=0 && ecurves5[0][0]); + memmove(&g_model.curves5[0][0], &v3->curves5[0][0], 5*MAX_CURVE5); + assert((char *)&g_model.curves9[0][0] < (char *)&v3->curves9[0][0]); + memmove(&g_model.curves9[0][0], &v3->curves9[0][0], 9*MAX_CURVE9); + if (g_eeGeneral.myVers == EEPROM_VER_r584) { + memmove(&g_model.customSw[0], &v3->customSw[0], sizeof(CustomSwData)*6); + memset(&g_model.customSw[6], 0, sizeof(CustomSwData)*6); + memset(&g_model.safetySw[0], 0, sizeof(SafetySwData)*NUM_CHNOUT + sizeof(SwashRingData) + sizeof(FrSkyData)); + } + else { + assert((char *)&g_model.customSw[0] < (char *)&v4->customSw[0]); + memmove(&g_model.customSw[0], &v4->customSw[0], sizeof(CustomSwData)*12); + assert((char *)&g_model.safetySw[0] < (char *)&v4->safetySw[0]); + memmove(&g_model.safetySw[0], &v4->safetySw[0], sizeof(SafetySwData)*NUM_CHNOUT); + memcpy(&g_model.swashR, &swashR, sizeof(SwashRingData)); + for (uint8_t i=0; i<2; i++) { + // TODO this conversion is bad + // assert(&g_model.frsky.channels[i].ratio < &v4->frsky.channels[i].ratio); + g_model.frsky.channels[i].ratio = v4->frsky.channels[i].ratio; + g_model.frsky.channels[i].type = v4->frsky.channels[i].type; + g_model.frsky.channels[i].offset = 0; + g_model.frsky.channels[i].alarms_value[0] = v4->frsky.channels[i].alarms_value[0]; + g_model.frsky.channels[i].alarms_value[1] = v4->frsky.channels[i].alarms_value[1]; + g_model.frsky.channels[i].alarms_level = v4->frsky.channels[i].alarms_level; + g_model.frsky.channels[i].alarms_greater = v4->frsky.channels[i].alarms_greater; + g_model.frsky.channels[i].barMin = 0; + g_model.frsky.channels[i].barMax = 0; + } + } + memset(&g_model.phaseData[0], 0, sizeof(g_model.phaseData) + sizeof(g_model.subtrim)); + memset(&g_model.funcSw[0], 0, sizeof(g_model.funcSw)); + if (trimSw) { + g_model.funcSw[0].swtch = trimSw; + g_model.funcSw[0].func = FUNC_INSTANT_TRIM; + g_model.funcSw[1].swtch = trimSw; + g_model.funcSw[1].func = FUNC_TRIMS_2_OFS; + } + memcpy(&g_model.phaseData[0].trim[0], &trims[0], 4); + theFile.writeRlc(FILE_MODEL(id), FILE_TYP_MODEL, (uint8_t*)&g_model, sizeof(g_model), true); + } + } + g_eeGeneral.myVers = EEPROM_VER; + theFile.writeRlc(FILE_GENERAL, FILE_TYP_GENERAL, (uint8_t*)&g_eeGeneral, sizeof(EEGeneral), true); + return sizeof(EEGeneral); + } + + return 0; +} +#endif + + +bool eeLoadGeneral() +{ + theFile.openRlc(FILE_GENERAL); + uint8_t sz = 0; + + if (theFile.readRlc((uint8_t*)&g_eeGeneral, 1) == 1) { + theFile.openRlc(FILE_GENERAL); + if (g_eeGeneral.myVers == EEPROM_VER) { + sz = theFile.readRlc((uint8_t*)&g_eeGeneral, sizeof(g_eeGeneral)); + } +#ifdef TRANSLATIONS + else { + sz = Translate(); + } +#endif + } + + if (sz == sizeof(EEGeneral)) { + uint16_t sum=0; + for(int i=0; i<12;i++) sum+=g_eeGeneral.calibMid[i]; + return g_eeGeneral.chkSum == sum; + } + return false; +} + +void modelDefault(uint8_t id) +{ + memset(&g_model, 0, sizeof(g_model)); + applyTemplate(0); //default 4 channel template +} + +uint16_t eeLoadModelName(uint8_t id, char *name) +{ + memset(name, 0, sizeof(g_model.name)); + if (id +#include +#include "rtc.h" + + + +#define SCL_LOW() DDRB |= 0x20 /* SCL = LOW */ +#define SCL_HIGH() DDRB &= ~0x20 /* SCL = High-Z */ +#define SCL_VAL ((PINB & 0x20) ? 1 : 0) /* SCL input level */ +#define SDA_LOW() DDRB |= 0x40 /* SDA = LOW */ +#define SDA_HIGH() DDRB &= ~0x40 /* SDA = High-Z */ +#define SDA_VAL ((PINB & 0x40) ? 1 : 0) /* SDA input level */ + + + +/*-------------------------------------------------*/ +/* I2C bus protocol */ + + +static +void iic_delay (void) +{ + int n; + + for (n = 4; n; n--) PINB; +} + + +/* Generate start condition on the IIC bus */ +static +void iic_start (void) +{ + SDA_HIGH(); + iic_delay(); + SCL_HIGH(); + iic_delay(); + SDA_LOW(); + iic_delay(); + SCL_LOW(); + iic_delay(); +} + + +/* Generate stop condition on the IIC bus */ +static +void iic_stop (void) +{ + SDA_LOW(); + iic_delay(); + SCL_HIGH(); + iic_delay(); + SDA_HIGH(); + iic_delay(); +} + + +/* Send a byte to the IIC bus */ +static +int iic_send (BYTE dat) +{ + BYTE b = 0x80; + int ack; + + + do { + if (dat & b) { /* SDA = Z/L */ + SDA_HIGH(); + } else { + SDA_LOW(); + } + iic_delay(); + SCL_HIGH(); + iic_delay(); + SCL_LOW(); + iic_delay(); + } while (b >>= 1); + SDA_HIGH(); + iic_delay(); + SCL_HIGH(); + ack = SDA_VAL ? 0 : 1; /* Sample ACK */ + iic_delay(); + SCL_LOW(); + iic_delay(); + return ack; +} + + +/* Receive a byte from the IIC bus */ +static +BYTE iic_rcvr (int ack) +{ + UINT d = 1; + + + do { + d <<= 1; + SCL_HIGH(); + if (SDA_VAL) d++; + iic_delay(); + SCL_LOW(); + iic_delay(); + } while (d < 0x100); + if (ack) { /* SDA = ACK */ + SDA_LOW(); + } else { + SDA_HIGH(); + } + iic_delay(); + SCL_HIGH(); + iic_delay(); + SCL_LOW(); + SDA_HIGH(); + iic_delay(); + + return (BYTE)d; +} + + + +/*-------------------------------------------------*/ +/* I2C block read/write controls */ + + +int iic_read ( + BYTE dev, /* Device address */ + UINT adr, /* Read start address */ + UINT cnt, /* Read byte count */ + BYTE *buff /* Read data buffer */ +) +{ + BYTE *rbuff = buff; + int n; + + + if (!cnt) return 0; + + n = 10; + do { /* Select device */ + iic_start(); + } while (!iic_send(dev) && --n); + if (n) { + if (iic_send((BYTE)adr)) { /* Set start address */ + iic_start(); /* Reselect device in read mode */ + if (iic_send(dev | 1)) { + do { /* Receive data */ + cnt--; + *rbuff++ = iic_rcvr(cnt ? 1 : 0); + } while (cnt); + } + } + } + + iic_stop(); /* Deselect device */ + + return cnt ? 0 : 1; +} + + + +int iic_write ( + BYTE dev, /* Device address */ + UINT adr, /* Write start address */ + UINT cnt, /* Write byte count */ + const BYTE *buff /* Data to be written */ +) +{ + const BYTE *wbuff = buff; + int n; + + + if (!cnt) return 0; + + n = 10; + do { /* Select device */ + iic_start(); + } while (!iic_send(dev) && --n); + if (n) { + if (iic_send((BYTE)adr)) { /* Set start address */ + do { /* Send data */ + if (!iic_send(*wbuff++)) break; + } while (--cnt); + } + } + + iic_stop(); /* Deselect device */ + + return cnt ? 0 : 1; +} + + + +/*-------------------------------------------------*/ +/* RTC functions */ + + +int rtc_gettime (RTC *rtc) +{ + BYTE buf[8]; + + + if (!iic_read(0xD0, 0, 7, buf)) return 0; + + rtc->sec = (buf[0] & 0x0F) + ((buf[0] >> 4) & 7) * 10; + rtc->min = (buf[1] & 0x0F) + (buf[1] >> 4) * 10; + rtc->hour = (buf[2] & 0x0F) + ((buf[2] >> 4) & 3) * 10; + rtc->wday = (buf[2] & 0x07); + rtc->mday = (buf[4] & 0x0F) + ((buf[4] >> 4) & 3) * 10; + rtc->month = (buf[5] & 0x0F) + ((buf[5] >> 4) & 1) * 10; + rtc->year = 2000 + (buf[6] & 0x0F) + (buf[6] >> 4) * 10; + + return 1; +} + + + + +int rtc_settime (const RTC *rtc) +{ + + BYTE buf[8]; + + + buf[0] = rtc->sec / 10 * 16 + rtc->sec % 10; + buf[1] = rtc->min / 10 * 16 + rtc->min % 10; + buf[2] = rtc->hour / 10 * 16 + rtc->hour % 10; + buf[3] = rtc->wday & 7; + buf[4] = rtc->mday / 10 * 16 + rtc->mday % 10; + buf[5] = rtc->month / 10 * 16 + rtc->month % 10; + buf[6] = (rtc->year - 2000) / 10 * 16 + (rtc->year - 2000) % 10; + return iic_write(0xD0, 0, 7, buf); +} + + + + +int rtc_init (void) +{ + BYTE buf[8]; /* RTC R/W buffer */ + UINT adr; + + + /* Read RTC registers */ + if (!iic_read(0xD0, 0, 8, buf)) return 0; /* IIC error */ + + if (buf[7] & 0x20) { /* When data has been volatiled, set default time */ + /* Clear nv-ram. Reg[8..63] */ + memset(buf, 0, 8); + for (adr = 8; adr < 64; adr += 8) + iic_write(0x0D, adr, 8, buf); + /* Reset time to Jan 1, '08. Reg[0..7] */ + buf[4] = 1; buf[5] = 1; buf[6] = 8; + iic_write(0x0D, 0, 8, buf); + } + return 1; +} + diff --git a/src/rtc.h b/src/rtc.h new file mode 100644 index 000000000..66a911ee7 --- /dev/null +++ b/src/rtc.h @@ -0,0 +1,23 @@ +#ifndef RTC_H +#define RTC_H + +#include "integer.h" + +typedef struct { + WORD year; /* 2000..2099 */ + BYTE month; /* 1..12 */ + BYTE mday; /* 1.. 31 */ + BYTE wday; /* 1..7 */ + BYTE hour; /* 0..23 */ + BYTE min; /* 0..59 */ + BYTE sec; /* 0..59 */ +} RTC; + +int iic_write (BYTE, UINT, UINT, const void*); /* Write to IIC device */ +int iic_read (BYTE, UINT, UINT, void*); /* Read from IIC device */ + +int rtc_init (void); /* Initialize RTC */ +int rtc_gettime (RTC*); /* Get time */ +int rtc_settime (const RTC*); /* Set time */ + +#endif diff --git a/src/s9xsplash.lbm b/src/s9xsplash.lbm new file mode 100644 index 000000000..934c6a0ff --- /dev/null +++ b/src/s9xsplash.lbm @@ -0,0 +1,11 @@ +prog_uchar APM s9xsplash[] = { +128,64,1024, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0x08,0x04,0x04,0x84,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0xc4,0x04,0x04,0x88,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0xbf,0x80,0x80,0x80,0xbf,0x80,0x80,0x80,0x80,0x00,0x00,0x30,0x48,0x48,0x48,0x48,0x48,0x48,0x48,0x48,0x88,0x09,0x09,0x08,0xf0,0x00,0x00,0x00,0x01,0x01,0x01,0x7f,0x01,0x01,0x01,0x01,0x00,0x00,0x7f,0x49,0x49,0x49,0x49,0x41,0x41,0x40,0x00,0x7f,0x40,0x40,0x40,0x40,0x40,0x40,0x00,0x7f,0x49,0x49,0x49,0x49,0x41,0x41,0x40,0x00,0x7f,0x01,0x06,0x0c,0x10,0x10,0x0c,0x06,0x01,0x7f,0x00,0x00,0x7f,0x49,0x49,0x49,0x49,0x41,0x41,0x40,0x01,0x01,0x01,0x7f,0x01,0x01,0x01,0x01,0x00,0x00,0x7f,0x09,0x09,0x09,0x09,0x19,0x29,0x4e,0x00,0x01,0x03,0x06,0x0c,0x78,0x0c,0x06,0x03,0x01,0x00,0x00, +0x00,0x00,0xf8,0xfe,0xff,0xff,0xff,0x0f,0x07,0x07,0x07,0x07,0x07,0x07,0x37,0x47,0x87,0x87,0x87,0x97,0x97,0x07,0x5f,0xdf,0xdf,0xdf,0xde,0xd8,0x80,0x10,0x10,0x10,0x10,0x10,0x0f,0x00,0x80,0x40,0x3f,0x80,0xc0,0xe0,0xe0,0xe0,0x60,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0xd0,0x90,0x90,0x90,0x90,0x90,0x00,0x00,0x00,0xf0,0x90,0x90,0x90,0x90,0x10,0x10,0x00,0x00,0xf0,0x90,0x90,0x90,0x90,0x90,0x90,0xe0,0x00,0x00,0xf0,0x00,0x00,0x00,0xf0,0x90,0x90,0x90,0x90,0x10,0x10,0x00,0x60,0xd0,0x90,0x90,0x90,0x90,0x90,0x00,0x00,0x00,0x00, +0x00,0x00,0x01,0x07,0x07,0x0f,0x0f,0x0f,0x0e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x0e,0x0e,0x0e,0x0e,0x0e,0x8e,0xce,0xee,0xfc,0xf9,0x73,0x27,0x0f,0x1f,0x3f,0x7e,0xfc,0xf9,0xf1,0xf1,0xf9,0xfc,0x3e,0x1f,0x0f,0x07,0x03,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x06,0x04,0x04,0x04,0x04,0x04,0x03,0x00,0x00,0x07,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x00,0x07,0x00,0x00,0x00,0x00,0x01,0x02,0x04,0x00,0x00,0x07,0x00,0x00,0x00,0x07,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x02,0x06,0x04,0x04,0x04,0x04,0x04,0x03,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x0c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1e,0x1e,0x1e,0x0e,0x0e,0x0f,0x07,0x07,0x03,0x03,0x01,0x80,0xc0,0xe0,0xf0,0xf8,0x7c,0x3e,0x1f,0x0f,0x07,0x07,0x0f,0x1f,0x3f,0x7e,0xfc,0xf8,0xf0,0xe0,0xc0,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7e,0x4a,0x4a,0x4a,0x4a,0x4a,0x7c,0x00,0x02,0x06,0x0c,0x70,0x08,0x04,0x02,0x02,0x00,0x00,0x00,0x00,0x1c,0x22,0x41,0x41,0x49,0x49,0x49,0x3a,0x18,0x00,0x00,0x7e,0x12,0x12,0x12,0x12,0x32,0x4c,0x00,0x00,0x3e,0x40,0x40,0x40,0x40,0x40,0x3e,0x00,0x02,0x0c,0x18,0x60,0x60,0x30,0x0c,0x02,0x00,0x7e,0x00,0x00,0x7e,0x02,0x0c,0x18,0x30,0x40,0x7e,0x00,0x00,0x00,0x00, +0x00,0x00,0xe0,0x10,0x10,0x10,0xe0,0x00,0x00,0xc0,0x80,0x40,0x80,0x00,0x00,0x80,0x40,0x40,0x80,0x00,0x00,0xcc,0x8e,0x4f,0x8f,0x0f,0x07,0x03,0x01,0x00,0x00,0x00,0x60,0x90,0x90,0x10,0x00,0x00,0x00,0x80,0x41,0x43,0x87,0x0f,0x0f,0xcf,0x0e,0x0c,0xc0,0x00,0x00,0xc0,0x80,0x40,0xc0,0x00,0x00,0x80,0x40,0x40,0x40,0x00,0x00,0x80,0x40,0x40,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x07,0x08,0x08,0x08,0x07,0x00,0x00,0x3f,0x04,0x08,0x07,0x00,0x00,0x07,0x09,0xc9,0x49,0x40,0x40,0x8f,0x00,0x00,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x08,0x08,0x09,0x06,0x00,0x00,0x07,0x08,0x08,0x07,0x00,0x00,0x07,0x08,0x04,0x0f,0x00,0x00,0x0f,0x00,0x00,0x00,0x00,0x40,0x07,0x08,0x08,0x08,0x00,0x00,0x07,0x09,0x09,0x09,0x00,0x00,0x00,0xc8,0x00,0x00,0x08,0xc0,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0x00,0x00,0x00,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x20,0x00,0x00,0x20,0x00,0x00,0x00,0x3f,0x04,0x04,0x04,0x03,0x00,0x00,0x1e,0x21,0x21,0x1e,0x00,0x07,0x38,0x0e,0x0e,0x38,0x07,0x00,0x1e,0x25,0x25,0x26,0x00,0x00,0x3f,0x02,0x01,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x3f,0x00,0x00,0x3f,0x02,0x01,0x3e,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x3c,0x03,0x00,0x00,0x00,0x1e,0x21,0x21,0x1e,0x00,0x00,0x1f,0x20,0x10,0x3f,0x00,0x3f,0x02,0x01,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3f,0x02,0x02,0x02,0x3f,0x00,0x00,0x19,0x25,0x15,0x1e,0x20,0x00,0x3f,0x02,0x01,0x3e,0x00,0x1e,0x21,0x12,0x3f,0x00,0x00,0x22,0x25,0x29,0x11,0x00 +}; diff --git a/src/s9xsplash.xbm b/src/s9xsplash.xbm new file mode 100644 index 000000000..cd999f022 --- /dev/null +++ b/src/s9xsplash.xbm @@ -0,0 +1,89 @@ +#define s9xsplash_width 128 +#define s9xsplash_height 64 +static unsigned char s9xsplash_bits[] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, + 0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x80,0x00,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00, + 0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x40,0xf8,0xff,0x47,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x40,0x04,0x00,0x24,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x04,0x00, + 0x18,0xfc,0xf3,0x27,0xe0,0xcf,0xc0,0xfc,0xfd,0xf3,0x67,0x30, + 0x00,0x40,0x04,0x00,0x00,0x20,0x10,0x20,0x20,0x40,0xa1,0x04, + 0x20,0x10,0xc8,0x18,0x00,0x40,0x04,0x00,0x00,0x20,0x10,0x20, + 0x20,0x40,0xb3,0x04,0x20,0x10,0x88,0x0d,0x00,0x40,0x04,0xfc, + 0x3f,0x20,0xf0,0x21,0xe0,0x43,0x92,0x7c,0x20,0xf0,0x0f,0x07, + 0x00,0x40,0x04,0x02,0x40,0x20,0x10,0x20,0x20,0x40,0x8c,0x04, + 0x20,0x10,0x02,0x02,0x00,0x40,0x04,0x02,0x40,0x20,0x10,0x20, + 0x20,0x40,0x80,0x04,0x20,0x10,0x04,0x02,0x00,0x00,0x00,0xfc, + 0x43,0x20,0xf0,0xef,0xef,0x5f,0x80,0xfc,0x23,0x10,0x08,0x02, + 0x80,0xff,0x7f,0x00,0x44,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xf0,0xff,0xff,0x03,0x44,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xff,0xff,0x07, + 0x44,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf8,0xff,0xff,0x07,0x44,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xfc,0x00,0xc0,0x0f,0x44,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7c,0x40,0xd8,0xef, + 0x43,0x00,0x00,0x00,0x00,0xe0,0xc7,0x9f,0x3f,0xe2,0xcf,0x0f, + 0x7c,0x40,0x00,0x00,0x40,0x3e,0x00,0x00,0x00,0x10,0x40,0x80, + 0x40,0x22,0x20,0x00,0x7c,0x80,0xc0,0x0f,0x20,0x1f,0x00,0x00, + 0x00,0x30,0x40,0x80,0x40,0x22,0x60,0x00,0x7c,0x00,0x9f,0x1f, + 0x90,0x0f,0x00,0x00,0x00,0xe0,0xc7,0x87,0x7f,0xe2,0xc3,0x0f, + 0xfc,0x00,0x00,0x3f,0xcf,0x07,0x00,0x00,0x00,0x00,0x48,0x80, + 0x10,0x22,0x00,0x10,0xf8,0xff,0x7f,0x7e,0xe0,0x03,0x00,0x00, + 0x00,0x30,0x48,0x80,0x20,0x22,0x60,0x10,0xf8,0xff,0xff,0xfc, + 0xf0,0x01,0x00,0x00,0x00,0xe0,0xc7,0xbf,0x40,0xe2,0xdf,0x0f, + 0xe0,0xff,0xff,0xf9,0xf9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x7e,0x80,0xf3,0x7f,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xe7, + 0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xe0,0xc3,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0x81,0x1f,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0x80, + 0x3f,0x00,0x00,0x00,0x00,0x80,0x0f,0x00,0x00,0x00,0x00,0x00, + 0x00,0xe0,0x7f,0xc0,0x7f,0x00,0x00,0x7e,0x86,0x41,0x10,0x3f, + 0x82,0x02,0x65,0x08,0xc0,0xff,0x1f,0xe0,0xff,0x00,0x00,0x82, + 0x4c,0x20,0x00,0x41,0x82,0x84,0xa4,0x08,0x80,0xff,0x07,0xf0, + 0xf9,0x01,0x00,0xfe,0x28,0x20,0x3e,0x41,0x82,0x8c,0xa4,0x09, + 0x00,0xff,0x00,0xf8,0xf0,0x03,0x00,0x82,0x10,0x20,0x30,0x3f, + 0x82,0x48,0x24,0x0b,0x00,0x00,0x00,0x7c,0xe0,0x07,0x00,0x82, + 0x10,0x40,0x10,0x21,0x82,0x70,0x24,0x0a,0x00,0x00,0x00,0x3e, + 0xc0,0x0f,0x00,0xfe,0x10,0x80,0x0f,0x41,0x7c,0x30,0x24,0x0c, + 0x00,0x00,0x00,0x1f,0x80,0x1f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x1f,0x00,0x3f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0x0f, + 0x00,0x7e,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xe0,0x07,0x00,0xfc,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0x03,0x00,0xf8,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x38,0x00,0x00,0x00, + 0x0e,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x44,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x44,0x0a,0xa3,0x00,0x01,0x23,0x69,0x1c, + 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x44,0x96,0x64,0x01, + 0x86,0x24,0x59,0x82,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x44,0x92,0x27,0x01,0x88,0x24,0x09,0x82,0x07,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x44,0x92,0x20,0x01,0x90,0x24,0x09,0x82, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x44,0x96,0x20,0x01, + 0x90,0xa4,0x09,0x82,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x38,0x0a,0x27,0x01,0x0f,0x43,0x09,0x1c,0x47,0x12,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x1e,0x00,0x00,0x00,0x00,0x01,0x40,0x04,0x00,0x00, + 0x88,0x00,0x00,0x01,0x00,0x00,0x22,0x00,0x00,0x00,0x00,0x00, + 0x40,0x04,0x00,0x00,0x88,0x00,0x00,0x01,0x00,0x00,0x22,0x26, + 0x64,0x34,0xc0,0x29,0x80,0xc2,0x48,0x0d,0x88,0x1c,0x45,0x71, + 0x00,0x00,0x22,0xa9,0x95,0x2c,0x00,0x59,0x80,0x22,0x49,0x0b, + 0xf8,0x20,0xab,0x09,0x00,0x00,0x1e,0xa9,0xf5,0x04,0x00,0x49, + 0x00,0x21,0x49,0x01,0x88,0x38,0x29,0x11,0x00,0x00,0x02,0xc9, + 0x13,0x04,0x00,0x49,0x00,0x21,0x49,0x01,0x88,0x24,0x29,0x21, + 0x00,0x00,0x02,0x49,0x12,0x04,0x00,0x49,0x00,0x21,0x69,0x01, + 0x88,0x34,0xa9,0x41,0x80,0x24,0x02,0x46,0xe2,0x04,0x00,0x49, + 0x00,0xc1,0x50,0x01,0x88,0x48,0x49,0x39,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00}; diff --git a/src/s9xsplash.xcf b/src/s9xsplash.xcf new file mode 100644 index 0000000000000000000000000000000000000000..580636ef9cd7ac163a1cdfba00f7f7b68eb71c11 GIT binary patch literal 13358 zcmeI2S&v*t700XY(zln{9J2`t)V73hga*YVkOX)zh6q`TlPJyuuVcGs++jRpX2!zi zWegJHm5+gt5D$ETzz2XlAmk7rfJGMBnZ+~H%iW6Kzq)&-lQ_tEh!mvmaoteSiK zsZ;lQseScY^Twsc=3;xfW%%8qouTP>jAkvDs#%k9gL)=w=iU0PjUU2C55xB0cDiwn)i zzWSK|J)m=XIQ1w?FFs)DXukR39WA}Fer@4Wdu6G4dPIxCn-3Zsj?G_RX>T;!E0?ac z*0p|hadEx1amb)ne{g6p^4_J=!_3RqT1)e+-++bnk!I@QOJQLB&m<=1FEMp9Wu`HL zo?>)eGYvm7cuM@!EVf9POPHB5b3A>Kbpyy_*yao71mjvaH+(t)&MBjHn$zDgMzn75 zbOKyeGh;q~I9~<+nECAC{0vXW51&o|c{C>+BfKMgAN4Ms_+Ri_T5UVKl9?#H@JI8ltVgNm8QM`YQsJ^)G`=XzcJRI8tYGw^=IjOT+X?Ielj*U3INf; z>FtZ$2Ts4yy0J08yl}I%#*laVZ=BLt^Q3q^iLWfKuJClez1Cj1+C0CyvfBLav*(*< zSC=n0pTD@={?YZ;2x@-4{gYPnYfq2dez4Y$4*cp``$lV7E_tQBB6n)u^bWef!@_cV zVZFJs%H;CG%F=a0v3at!GJkzt*e@=ux8^Um*IJi0+N&$g<&8B4-ye&I4*tQ8!szAe z>l^LGo6Y6c;)YPJ-k86-dbvd=xzSQ^Yv9N2%NtjkPd;@dfkv;i+Dlh9nvXyE-eDhQ zBax)N;V=62uro3}T95vK(P{oS9FP9O*e^7)Svzyo>~Bouo-&D>GcztQbyqc2-?wJU zRZPfO=w{6{_h}cKz(urUv{64|USoQmqg805=g~bdu}c^ajCGbK*zxt6x2AL^WWv&_ z9zcl2Ks0O~An}u$h)u(VO!}1pY*%4FF*PPCOz64hY5)pj1row87HZ7R(oa|{7B#PN z&4<8G(<&avKpc&WV8f4xt~AcFKnS#8Fj)gx>{+z}kTOM4yjPisJq0GFA(wzod?%h& zksq5FNL=+R0=F3&vln)ww=H7o9=r#hT}UCL|F5sMPJ#>A%ioy`4Z+9T@ld!F_c znxg-6&-~zp?>zUtbI*Oh`NH!TE}T1i?%8uMoqOg2Qzp=bpSG}%D|2`r!2h7kA-c*O z=ed7a<~Tjd91j*_lqQ-dFD$J1uvTgrB#qOfEs#Jw9R2@F8e?g1ENMLP_`@ZQ>KiCl zzmY>=+Hqx;}gSmbOoPH~Z{BM_#P zMSLgHlkW{LeL?Z301Qm#pN3Q82>g&o?%3|3p%ot05rU3J2mb=IGwP1+3eF*$A8{Sv zm4SRiA%^@x4{H80MsGBvur7EQ=XQ_}{`b0YAl>^E4~mb&P_Fv8Mzq?{ z0z>#iPmrVN1mw3DQnmlx;(EsSC?d};T>9bC+UoU{%NqGOcakB+2ks=JoeY@iKMw(K zaX&+2Yn{Gl%o8W+>vxNGhBFAancG~;zn41K%^khM^(l7gr5Se(<^8O1-*jo| zIxctLc4g|m<+7p!AontJncIw=`=sHixw4>yDF{vCGY+v14(0YKm8$`0mv zN_Z|B#??>+0{5uA7%Att?7D8(+0Yghq96$ImmJ^WugCtLOyg5kx7BrhGzI`E#Z+yw z3$cR|rwD|aA+uX$#$~y?&w*pgu&9UjT$xkdm?CJv2eG-In?ShppyHXbyI=%wBKB3d z*4>>0hu2Q)Y!YGL)a4}wMwA4xe6V7+6KlzgJhA0v;DXehox&AmVKtPxF16K`&NM8q za4>9``_x({U@}Tb=Pq$OrE~Z31ZyfF$YRjkcd68Ak;Wv9o9)O{5RsIpoQ$Gb_pj3R z@SH3{RmPR2ZUKmGC=*zi-XT4slYnJ`En?M?+e{%Pxx7xzEV;?<%vkq!Ub=$vSjZ%> zvzAQc7af$SpYxpz9GJgTiy9a#q3OCkx9uoAO^O0I_W7ioK$++>iyjnIqSWuWeaGt1?WEu`{W3+R4e@@=ZJB7WF;sv~VQGgIZ}wduLn-`J zRY1RH;)L_Ns35B{+CS@3UgnCzWf6hwdST5V8A!m)KWkD;Cvmgxt;`)r^@gn8m926r zYL+WnKo?+nL`Mj{g-G~=%uYt)4Kpz!g{45xr`?WC|&>`@G%={>R=S*`0bS&ovxb`d|Sh>n=plClBl_mUL9 z>3d%&Is)CnSaQ37K!aQc{G8iC2>3=8W)+JlE|cO2C&FDZ##|;RpXDZVTXzT3xIV&( z7eEx_a#>@cUf81GS;*hTy~IuC@*FIA%xep9hI>R&LY2b$Ji{$#-7Z*Mw}6R^e5BY) z15*oyco$6^h(V@x`Vb?1a*jbXDz+}0tSLe*DV60y(xSL@-LL_y*x21SmE84H1jBy> z`L6t~D^qv#u6H)EHcLpM4gOf3o3Pif1a416a7WTDEZ~ibqX8Gx9^1%ZNkJ~G?`Xq8 zd!$#lhr|NzxL!65e`Vkqxt~E1#k)M{GG|=}+afcr$HT!c$Q3`{SLfV+`eZ}=tBn0N z=GEOJEW{wt16U~?+_27_oT2n3Rm(MV!1@JofZRU81MKs$D#;ufhz8QnoFQLYBUTP% zlAgja>nLOliFXSAJ2o#WWNpG^2L9`Kg*9Em3C2B{x9~qanm}ZRvw?|}UX=tN#)&Fh|bp7h!i1DGfx`0*m`NG|nflb#31ltCR|2+f`2+>j!kMXMh&DUPq_^vS~>M?nzHdWMca@tP>Mxh|7hrVwsWY z`;-_cQ|_jGHVd0?X~#od=m1aV9G3DdUEqwVsr0J6Wg%*F&&F-gCGtvihM5u&nz%@% zg$%!BBPq1`NxquF-qyUq3bafCgLfb*?x(2pR#rUUcXO^ca6^fB2Q6Y3Fp-~hVqkgB z){qk!s(X5|-vT7&JqZRFZ=PVp)j9*uv-ZAL$K?ThD~`1N@UCs2J+4RP)2(?2{Rixm z^GO@V#W~3fp&MoCk76*Mz$v+*htw1?-c|46Uc9-YOv6XW2dQ=voiKcr*JTPZ!Fb%2 z$_|AuTkfG#%7jSz2NTAI1yVa{PgN9a3c^zQdfo4CN*XqoCEyPpy0LdiTo~TqktoLV zaT3ahIS~p;@@Suljy7_mlmJ)zHAbSM4yV)fs3?I+GU&YOce_De3yEIMVE%x_rRa%O zstFxP5_Zu7GOzlKgfpfDj7l}_S?EMik*Q}WL^Rg?I5^!`oKw2-uBFNPW(tUxHyLm#E6ptA0EpKYg+7hX-e z07StF{RU}+eT_^{Pjmc?aa@v@jHYTTy$+r!TqmyqNlW!Z7ZyS?u m8 + * - Bryan J. Rentoul (Gruvin) + * + * gruvin9x is based on code named er9x by + * Author - Erez Raviv , which is in turn + * was based on the original (and ongoing) project by Thomas Husterer, + * th9x -- http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + */ + +#include +#include +#include +#include "simpgmspace.h" +#include "lcd.h" +#include "gruvin9x.h" +#include "menus.h" + +volatile unsigned char pinb=0, pinc=0xff, pind, pine=0xff, ping=0xff; +unsigned char portb, dummyport; +const char *eepromFile = "eeprom.bin"; +volatile size_t eeprom_buffer_size = 0; + +uint8_t eeprom[EESIZE]; + +void eeWriteBlockCmp(const void *i_pointer_ram, void *pointer_eeprom, size_t size) +{ +#if 0 + printf(" eeWriteBlockCmp(%d %d)", size, (int)pointer_eeprom); + for(uint8_t i=0; i + * - Bryan J. Rentoul (Gruvin) + * + * gruvin9x is based on code named er9x by + * Author - Erez Raviv , which is in turn + * was based on the original (and ongoing) project by Thomas Husterer, + * th9x -- http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + */ + +#include +#include +#include + +typedef unsigned char prog_uchar; +typedef const char prog_char; +typedef const uint16_t prog_uint16_t; +typedef const uint8_t prog_uint8_t; +typedef const int16_t prog_int16_t; +typedef const int8_t prog_int8_t; + + + +#define PROGMEM +#define pgm_read_byte(address_short) (*(uint8_t*)(address_short)) +#define pgm_read_word(address_short) (*(uint16_t*)(address_short)) +#define pgm_read_adr(address_short) *address_short +#define pgm_read_stringP(adr) ((adr)) +#define PSTR(adr) adr +#define _delay_us(a) +#define cli() +#define sei() +#define strcpy_P strcpy +#define memcpy_P memcpy + +#define PORTA dummyport +#define PORTB portb +#define PORTC dummyport +#define PORTD dummyport +#define PORTE dummyport +#define PORTF dummyport +#define PORTG dummyport +#define DDRA dummyport +#define DDRB dummyport +#define DDRC dummyport +#define DDRD dummyport +#define DDRE dummyport +#define DDRF dummyport +#define DDRG dummyport +#define PINB ~pinb +#define PINC ~pinc +#define PIND ~pind +#define PINE ~pine +#define PING ~ping +#define EEMEM + +#define UCSR0B dummyport +#define UDRIE0 dummyport +#define TXEN0 dummyport +#define RXEN0 dummyport +#define DDE0 dummyport +#define PORTE0 dummyport +#define RXCIE0 dummyport + +extern volatile unsigned char pinb,pinc,pind,pine,ping; +extern unsigned char portb,dummyport; + +extern const char *eepromFile; +void eeprom_read_block (void *pointer_ram, + const void *pointer_eeprom, + size_t size); + +#define offsetof(st, m) ((size_t) ( (char *)&((st *)(0))->m - (char *)0 )) +#define wdt_reset() diff --git a/src/simu.cpp b/src/simu.cpp new file mode 100644 index 000000000..572ad1231 --- /dev/null +++ b/src/simu.cpp @@ -0,0 +1,442 @@ +/* + * Authors (alphabetical order) + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * + * gruvin9x is based on code named er9x by + * Author - Erez Raviv , which is in turn + * was based on the original (and ongoing) project by Thomas Husterer, + * th9x -- http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + */ + +#include "fx.h" +#include "FXExpression.h" +#include "FXPNGImage.h" +#include +#include "simpgmspace.h" +#include "lcd.h" +#include "fxkeys.h" +#include "gruvin9x.h" +#include "menus.h" +#include +#include + +#define W DISPLAY_W +#define H DISPLAY_H +#define W2 W*2 +#define H2 H*2 + +int g_snapshot_idx = 0; + +class Gruvin9xSim: public FXMainWindow +{ + FXDECLARE(Gruvin9xSim) +public: + Gruvin9xSim(){}; + Gruvin9xSim(FXApp* a); + long onKeypress(FXObject*,FXSelector,void*); + long onArrowPress(FXObject*,FXSelector,void*); + long onChore(FXObject*,FXSelector,void*); + long onTimeout(FXObject*,FXSelector,void*); + void makeSnapshot(const FXDrawable* drawable); + void doEvents(); + void refreshDiplay(); +private: + + + FX::FXuchar buf2[W2*H2/8]; + FXBitmap *bmp; + FXBitmapFrame *bmf; + bool firstTime; + +public: + FXSlider *sliders[8]; + FXKnob *knobs[8]; + FXKnob *knobsppm[8]; + FXArrowButton *arrow[3]; + FXArrowButton *arrow2[3]; + FXToggleButton *togButPpm; +}; +// Message Map +FXDEFMAP(Gruvin9xSim) Gruvin9xSimMap[]={ + + //________Message_Type_________ID_____________________Message_Handler_______ + FXMAPFUNC(SEL_CHORE, 1, Gruvin9xSim::onChore), + FXMAPFUNC(SEL_TIMEOUT, 2, Gruvin9xSim::onTimeout), + FXMAPFUNC(SEL_COMMAND, 1000, Gruvin9xSim::onArrowPress), + FXMAPFUNC(SEL_KEYPRESS, 0, Gruvin9xSim::onKeypress), + }; + +FXIMPLEMENT(Gruvin9xSim,FXMainWindow,Gruvin9xSimMap,ARRAYNUMBER(Gruvin9xSimMap)) + + +Gruvin9xSim::Gruvin9xSim(FXApp* a) +:FXMainWindow(a,"Gruvin9xSim",NULL,NULL,DECOR_ALL,20,90,0,0) +{ + + firstTime=true; + for(int i=0; i<(W*H/8); i++) displayBuf[i]=0;//rand(); + for(int i=0; i<(W2*H2/8); i++) buf2[i]=0;//rand(); + bmp = new FXBitmap(a,&buf2,BITMAP_KEEP,W2,H2); + + FXHorizontalFrame *hf00=new FXHorizontalFrame(this,LAYOUT_CENTER_X); + FXHorizontalFrame *hf01=new FXHorizontalFrame(this,LAYOUT_CENTER_X); + FXHorizontalFrame *hf02=new FXHorizontalFrame(this,LAYOUT_CENTER_X); + //FXHorizontalFrame *hf2=new FXHorizontalFrame(this,LAYOUT_FILL_X); + FXHorizontalFrame *hf10=new FXHorizontalFrame(this,LAYOUT_CENTER_X); + FXHorizontalFrame *hf11=new FXHorizontalFrame(this,LAYOUT_CENTER_X); + FXHorizontalFrame *hf1=new FXHorizontalFrame(this,LAYOUT_FILL_X); + + //rh lv rv lh + for(int i=0; i<4; i++){ + switch(i) + { +#define L LAYOUT_FIX_WIDTH|LAYOUT_FIX_HEIGHT|LAYOUT_FIX_X|LAYOUT_FIX_Y +#undef X0 +#define X0 10 +#define Y0 20 + case 0: + sliders[i]=new FXSlider(hf1,NULL,0,L|SLIDER_HORIZONTAL,X0+0,Y0+120,100,20); + break; + case 1: + sliders[i]=new FXSlider(hf1,NULL,0,L|SLIDER_VERTICAL,X0+100,Y0+20,20,100); + break; + case 2: + sliders[i]=new FXSlider(hf1,NULL,0,L|SLIDER_VERTICAL,X0+120,Y0+20,20,100); + break; + case 3: + sliders[i]=new FXSlider(hf1,NULL,0,L|SLIDER_HORIZONTAL,X0+140,Y0+120,100,20); + break; + default:; + } + sliders[i]->setRange(0+i*50,1023); + sliders[i]->setTickDelta(7); + sliders[i]->setValue(i==1 ? 200 : 512+i*25); + } + arrow[0]= new FXArrowButton(hf10,this,1000,ARROW_LEFT); + arrow[1]= new FXArrowButton(hf10,this,1000,ARROW_UP); + arrow[2]= new FXArrowButton(hf10,this,1000,ARROW_RIGHT); + for(int i=4; i<8; i++){ + knobs[i]= new FXKnob(hf11,NULL,0,KNOB_TICKS|LAYOUT_LEFT); + knobs[i]->setRange(0,1023); + knobs[i]->setValue(512); + } + + arrow2[0]= new FXArrowButton(hf00,this,1000,ARROW_LEFT); + arrow2[1]= new FXArrowButton(hf00,this,1000,ARROW_UP); + arrow2[2]= new FXArrowButton(hf00,this,1000,ARROW_RIGHT); + togButPpm = new FXToggleButton(hf00,"on", "off", NULL, NULL, NULL, 0, TOGGLEBUTTON_NORMAL); + for(int i=0; i<8; i++){ + knobsppm[i]= new FXKnob(i<4?hf01:hf02,NULL,0,KNOB_TICKS|LAYOUT_LEFT); + knobsppm[i]->setRange(1000,2000); + knobsppm[i]->setValue(1500+i*20); + } + + + bmf = new FXBitmapFrame(this,bmp,0,0,0,0,0,0,0,0,0); + bmf->setOnColor(FXRGB(0,0,0)); + + //getApp()->addChore(this,1); + getApp()->addTimeout(this,2,100); +} + +void Gruvin9xSim::makeSnapshot(const FXDrawable* drawable) +{ + // Construct and create an FXImage object + FXPNGImage snapshot(getApp(), NULL, 0, drawable->getWidth(), drawable->getHeight()); + snapshot.create(); + + // Create a window device context and lock it onto the image + FXDCWindow dc(&snapshot); + + // Draw from the widget to this + dc.drawArea(drawable, 0, 0, drawable->getWidth(), drawable->getHeight(), 0, 0); + + // Release lock + dc.end(); + + // Grab pixels from server side back to client side + snapshot.restore(); + + // Save recovered pixels to a file + FXFileStream stream; + char buf[100]; + sprintf(buf,"snapshot-%02d.png", ++g_snapshot_idx); + for(unsigned i=4; iaddChore(this,1); + getApp()->runOneEvent(false); +} + +long Gruvin9xSim::onArrowPress(FXObject*sender,FXSelector sel,void*v) +{ + int which,val; + if(sender==arrow[0]) { which=1; val=0;} + if(sender==arrow[1]) { which=1; val=512;} + if(sender==arrow[2]) { which=1; val=1023;} + if(sender==arrow2[0]){ which=2; val=1000;} + if(sender==arrow2[1]){ which=2; val=1500;} + if(sender==arrow2[2]){ which=2; val=2000;} + if(which == 1){ + for(int i=0; i<4; i++) sliders[i]->setValue(val); + for(int i=4; i<7; i++) knobs[i]->setValue(val); + } + if(which == 2){ + for(int i=0; i<8; i++) knobsppm[i]->setValue(val); + } + return 0; +} +long Gruvin9xSim::onKeypress(FXObject*,FXSelector,void*v) +{ + FXEvent *evt=(FXEvent*)v; + // printf("keypress %x\n", evt->code); + if (evt->code=='s'){ + makeSnapshot(bmf); + } + return 0; +} + +extern uint16_t s_trainerLast10ms; +long Gruvin9xSim::onTimeout(FXObject*,FXSelector,void*) +{ + if(togButPpm->getState()){ + for(int i=0; i<8; i++){ + g_ppmIns[i]=knobsppm[i]->getValue()-1500; + if(g_ppmIns[i]<-400){ + // TODO BSS g_trainerSlaveActiveChns = i; + // TODO BSS s_trainerLast10ms = g_tmr10ms; + break; + } + } + } + + per10ms(); + getApp()->addChore(this,1); + getApp()->addTimeout(this,2,10); + return 0; +} + +void Gruvin9xSim::refreshDiplay() +{ + if(portb & 1<setOffColor(FXRGB(150,200,152)); + else bmf->setOffColor(FXRGB(200,200,200)); + + for(int x=0;xsetData (buf2,0); + bmp->render(); + bmf->setBitmap( bmp ); + + if(hasFocus()) { + static FXuint keys1[]={ + KEY_Return, INP_B_KEY_MEN, + KEY_Page_Up, INP_B_KEY_MEN, + KEY_KP_1, INP_B_KEY_MEN, + KEY_Page_Down, INP_B_KEY_EXT, + KEY_BackSpace, INP_B_KEY_EXT, + KEY_KP_0, INP_B_KEY_EXT, + KEY_Down, INP_B_KEY_DWN, + KEY_Up, INP_B_KEY_UP, + KEY_Right, INP_B_KEY_RGT, + KEY_Left, INP_B_KEY_LFT + }; + + pinb &= ~ 0x7e; + for(unsigned i=0; igetKeyState(keys1[i])) pinb |= (1<getKeyState(keys2[i])) pind |= (1<getKeyState(keys3[i].key); + if (ks != keys3[i].value) { + if (ks) keys3[i].pin ^= (1<getKeyState(KEY_4); + if(ks != k4st){ + if(ks && id>0) id--; + k4st = ks; + } + ks=getApp()->getKeyState(KEY_5); + if(ks != k5st){ + if(ks && id<2) id++; + k5st = ks; + } + switch(id){ + case 0: ping |= (1< +long Gruvin9xSim::onChore(FXObject*,FXSelector,void*) +{ + pthread_t pid; + + refreshDiplay(); + + switch (state) { + case 0: + state = 1; + pthread_create(&pid, NULL, &init_function, NULL); + break; + case 2: + perMain(); + break; + } + + return 0; +} + +Gruvin9xSim *th9xSim; +void doFxEvents() +{ + //puts("doFxEvents"); + th9xSim->getApp()->runOneEvent(false); + th9xSim->refreshDiplay(); +} + +int main(int argc,char **argv) +{ + if(argc>=2){ + eepromFile = argv[1]; + } + printf("eeprom = %s\n",eepromFile); + + // Each FOX GUI program needs one, and only one, application object. + // The application objects coordinates some common stuff shared between + // all the widgets; for example, it dispatches events, keeps track of + // all the windows, and so on. + // We pass the "name" of the application, and its "vendor", the name + // and vendor are used to search the registry database (which stores + // persistent information e.g. fonts and colors). + FXApp application("Gruvin9xSim", "thus"); + + // Here we initialize the application. We pass the command line arguments + // because FOX may sometimes need to filter out some of the arguments. + // This opens up the display as well, and reads the registry database + // so that persistent settings are now available. + application.init(argc,argv); + + // This creates the main window. We pass in the title to be displayed + // above the window, and possibly some icons for when its iconified. + // The decorations determine stuff like the borders, close buttons, + // drag handles, and so on the Window Manager is supposed to give this + // window. + //FXMainWindow *main=new FXMainWindow(&application,"Hello",NULL,NULL,DECOR_ALL); + th9xSim = new Gruvin9xSim(&application); + application.create(); + + // Pretty self-explanatory:- this shows the window, and places it in the + // middle of the screen. +#ifndef __APPLE__ + th9xSim->show(PLACEMENT_SCREEN); +#else + th9xSim->show(); // Otherwise the main window gets centred across my two monitors, split down the middle. +#endif + + return application.run(); +} + +uint16_t anaIn(uint8_t chan) +{ + if(chan<4) return th9xSim->sliders[chan]->getValue(); + return th9xSim->knobs[chan]->getValue(); + //return 512 - 512*10*chan/100; + //return (rand() & 0x1f) + 0x2f8; +} diff --git a/src/stamp-gruvin9x.h b/src/stamp-gruvin9x.h new file mode 100644 index 000000000..0f3308de8 --- /dev/null +++ b/src/stamp-gruvin9x.h @@ -0,0 +1,6 @@ +//Automatically generated file (Makefile) - do not edit +#define DATE_STR "2011-09-13" +#define TIME_STR "19:07:15" +#define TAG_VERS 2-bsongis +#define SVN_VERS "gruvin9x-dev-r819" +#define BUILD_NUM 827 diff --git a/src/stamp.cpp b/src/stamp.cpp new file mode 100644 index 000000000..6ab1a2df1 --- /dev/null +++ b/src/stamp.cpp @@ -0,0 +1,13 @@ +#include "gruvin9x.h" +#include "stamp-gruvin9x.h" + +#define STR2(s) #s +#define DEFNUMSTR(s) STR2(s) + +const char APM stamp1[] = "VERS: V" DEFNUMSTR(VERS) "." DEFNUMSTR(TAG_VERS); +const char APM stamp2[] = "DATE: " DATE_STR; +const char APM stamp3[] = "TIME: " TIME_STR; +const char APM stamp4[] = " SVN: " SVN_VERS; +const char APM stamp5[] = " BLD: " DEFNUMSTR(BUILD_NUM); + + diff --git a/src/statistics_views.cpp b/src/statistics_views.cpp new file mode 100644 index 000000000..92725f2ee --- /dev/null +++ b/src/statistics_views.cpp @@ -0,0 +1,105 @@ +/* + * Authors (alphabetical order) + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * + * gruvin9x is based on code named er9x by + * Author - Erez Raviv , which is in turn + * was based on the original (and ongoing) project by Thomas Husterer, + * th9x -- http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + */ + +#include "menus.h" + +void menuProcStatistic(uint8_t event) +{ + TITLE("STAT"); + switch(event) + { + case EVT_KEY_FIRST(KEY_UP): + chainMenu(menuProcStatistic2); + break; + case EVT_KEY_FIRST(KEY_DOWN): + case EVT_KEY_FIRST(KEY_EXIT): + chainMenu(menuMainView); + break; + } + + lcd_puts_P( 1*FW, FH*1, PSTR("TME")); + putsTime( 5*FW, FH*1, s_timeCumAbs, 0, 0); + lcd_puts_P( 17*FW, FH*1, PSTR("TSW")); + putsTime( 11*FW, FH*1, s_timeCumSw, 0, 0); + + lcd_puts_P( 1*FW, FH*2, PSTR("STK")); + putsTime( 5*FW, FH*2, s_timeCumThr, 0, 0); + lcd_puts_P( 17*FW, FH*2, PSTR("ST%")); + putsTime( 11*FW, FH*2, s_timeCum16ThrP/16, 0, 0); + + lcd_puts_P( 17*FW, FH*0, PSTR("TOT")); + putsTime( 11*FW, FH*0, s_timeCumTot, 0, 0); + + uint16_t traceRd = s_traceCnt>MAXTRACE ? s_traceWr : 0; + uint8_t x=5; + uint8_t y=60; + lcd_hline(x-3,y,120+3+3); + lcd_vline(x,y-32,32+3); + + for(uint8_t i=0; i<120; i+=6) + { + lcd_vline(x+i+6,y-1,3); + } + for(uint8_t i=1; i<=120; i++) + { + lcd_vline(x+i,y-s_traceBuf[traceRd],s_traceBuf[traceRd]); + traceRd++; + if(traceRd>=MAXTRACE) traceRd=0; + if(traceRd==s_traceWr) break; + } +} + +void menuProcStatistic2(uint8_t event) +{ + TITLE("STAT2"); + switch(event) + { + case EVT_KEY_FIRST(KEY_MENU): + g_tmr1Latency_min = 0x7ff; + g_tmr1Latency_max = 0; + g_timeMain = 0; +// g_time_per10 = 0; + beepKey(); + break; + case EVT_KEY_FIRST(KEY_DOWN): + chainMenu(menuProcStatistic); + break; + case EVT_KEY_FIRST(KEY_UP): + case EVT_KEY_FIRST(KEY_EXIT): + chainMenu(menuMainView); + break; + } + lcd_puts_P( 0*FW, 1*FH, PSTR("tmr1Lat max us")); + lcd_outdez8(15*FW , 1*FH, g_tmr1Latency_max/2 ); + lcd_puts_P( 0*FW, 2*FH, PSTR("tmr1Lat min us")); + lcd_outdez8(15*FW , 2*FH, g_tmr1Latency_min/2 ); + lcd_puts_P( 0*FW, 3*FH, PSTR("tmr1 Jitter us")); + lcd_outdez8(15*FW , 3*FH, (g_tmr1Latency_max - g_tmr1Latency_min) /2 ); + lcd_puts_P( 0*FW, 4*FH, PSTR("tmain max ms")); + lcd_outdezAtt(15*FW, 4*FH, (g_timeMain*100)/16, PREC2); + lcd_puts_P( 0*FW, 5*FH, PSTR("t10ms us")); + lcd_outdez8(15*FW , 5*FH, g_time_per10/2 ); +#ifndef SIMU + lcd_puts_P( 0*FW, 6*FH, PSTR("Free Stack min b")); + lcd_outdezAtt(18*FW-1, 6*FH, stack_free() ) ; +#endif + lcd_puts_P( 3*FW, 7*FH, PSTR("[MENU] to reset")); +} diff --git a/src/sticks.lbm b/src/sticks.lbm new file mode 100644 index 000000000..496b1c2f1 --- /dev/null +++ b/src/sticks.lbm @@ -0,0 +1,7 @@ +prog_uchar APM sticks[] = { +18,8,18, +0x00,0x08,0x1c,0x08,0x08,0x08,0x1c,0x08,0x00,0x00,0x08,0x22,0x00,0x49,0x00,0x22,0x08,0x00, +0x00,0x00,0x00,0x00,0x22,0x7f,0x22,0x00,0x00,0x00,0x08,0x22,0x00,0x49,0x00,0x22,0x08,0x00, +0x00,0x08,0x22,0x00,0x49,0x00,0x22,0x08,0x00,0x00,0x00,0x22,0x7f,0x22,0x00,0x00,0x00,0x00, +0x00,0x08,0x22,0x00,0x49,0x00,0x22,0x08,0x00,0x00,0x08,0x1c,0x08,0x08,0x08,0x1c,0x08,0x00, +}; diff --git a/src/sticks_4x1.xbm b/src/sticks_4x1.xbm new file mode 100644 index 000000000..52436e597 --- /dev/null +++ b/src/sticks_4x1.xbm @@ -0,0 +1,11 @@ +#define sticks_width 18 +#define sticks_height 32 +static unsigned char sticks_bits[] = { + 0x00, 0x20, 0x00, 0x00, 0x88, 0x00, 0x44, 0x00, 0x00, 0xfe, 0x24, 0x01, + 0x44, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x20, 0x20, 0x00, 0x70, 0x88, 0x00, 0x20, 0x00, 0x00, 0x20, 0x24, 0x01, + 0x20, 0x00, 0x00, 0x70, 0x88, 0x00, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x10, 0x10, 0x00, 0x44, 0x38, 0x00, 0x00, 0x10, 0x00, 0x92, 0x10, 0x00, + 0x00, 0x10, 0x00, 0x44, 0x38, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x88, 0x00, 0x92, 0xfc, 0x01, + 0x00, 0x88, 0x00, 0x44, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00 }; diff --git a/src/templates.cpp b/src/templates.cpp new file mode 100644 index 000000000..e6c0d56e1 --- /dev/null +++ b/src/templates.cpp @@ -0,0 +1,207 @@ +/* + * gruvin9x Author Bryan J.Rentoul (Gruvin) + * + * templates.cpp original author - Erez Raviv + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + * ============================================================ + * Templates file + * + * eccpm + * crow + * throttle cut + * flaperon + * elevon + * v-tail + * throttle hold + * Aileron Differential + * Spoilers + * Snap Roll + * ELE->Flap + * Flap->ELE + * + * + * + * ============================================================= + * Assumptions: + * All primary channels are per modi12x3 + * Each template added to the end of each channel + * + * + * + */ + +#include "gruvin9x.h" +#include "templates.h" + +MixData* setDest(uint8_t dch) +{ + uint8_t i = 0; + while ((g_model.mixData[i].destCh<=dch) && (g_model.mixData[i].destCh) && (i vSTK + //ICC(vSTK) -> STK +#define ICC(x) icc[(x)-1] + uint8_t icc[4] = {0}; + for(uint8_t i=1; i<=4; i++) //generate inverse array + for(uint8_t j=1; j<=4; j++) if(CC(i)==j) icc[j-1]=i; + + + switch (idx){ + //Simple 4-Ch + case (0): + md=setDest(ICC(STK_RUD)); md->srcRaw=CM(STK_RUD); md->weight=100; + md=setDest(ICC(STK_ELE)); md->srcRaw=CM(STK_ELE); md->weight=100; + md=setDest(ICC(STK_THR)); md->srcRaw=CM(STK_THR); md->weight=100; + md=setDest(ICC(STK_AIL)); md->srcRaw=CM(STK_AIL); md->weight=100; + break; + + //T-Cut + case (1): + md=setDest(ICC(STK_THR)); md->srcRaw=MIX_MAX; md->weight=-100; md->swtch=DSW_THR; md->mltpx=MLTPX_REP; + break; + + //V-Tail + case (2): + md=setDest(ICC(STK_RUD)); md->srcRaw=CM(STK_RUD); md->weight= 100; + md=setDest(ICC(STK_RUD)); md->srcRaw=CM(STK_ELE); md->weight=-100; + md=setDest(ICC(STK_ELE)); md->srcRaw=CM(STK_RUD); md->weight= 100; + md=setDest(ICC(STK_ELE)); md->srcRaw=CM(STK_ELE); md->weight= 100; + break; + + //Elevon\\Delta + case (3): + md=setDest(ICC(STK_ELE)); md->srcRaw=CM(STK_ELE); md->weight= 100; + md=setDest(ICC(STK_ELE)); md->srcRaw=CM(STK_AIL); md->weight= 100; + md=setDest(ICC(STK_AIL)); md->srcRaw=CM(STK_ELE); md->weight= 100; + md=setDest(ICC(STK_AIL)); md->srcRaw=CM(STK_AIL); md->weight=-100; + break; + + //eCCPM + case (4): + md=setDest(ICC(STK_ELE)); md->srcRaw=CM(STK_ELE); md->weight= 72; + md=setDest(ICC(STK_ELE)); md->srcRaw=CM(STK_THR); md->weight= 55; + md=setDest(ICC(STK_AIL)); md->srcRaw=CM(STK_ELE); md->weight=-36; + md=setDest(ICC(STK_AIL)); md->srcRaw=CM(STK_AIL); md->weight= 62; + md=setDest(ICC(STK_AIL)); md->srcRaw=CM(STK_THR); md->weight= 55; + md=setDest(6); md->srcRaw=CM(STK_ELE); md->weight=-36; + md=setDest(6); md->srcRaw=CM(STK_AIL); md->weight=-62; + md=setDest(6); md->srcRaw=CM(STK_THR); md->weight= 55; + break; + + //Heli Setup + case (5): + clearMixes(); //This time we want a clean slate + clearCurves(); + + //Set up Mixes + md=setDest(ICC(STK_AIL)); md->srcRaw=CH(9); md->weight= 50; + md=setDest(ICC(STK_AIL)); md->srcRaw=CH(10); md->weight=-100; + md=setDest(ICC(STK_AIL)); md->srcRaw=CH(11); md->weight= 100; md->carryTrim=TRIM_OFF; + + md=setDest(ICC(STK_ELE)); md->srcRaw=CH(9); md->weight=-100; + md=setDest(ICC(STK_ELE)); md->srcRaw=CH(11); md->weight= 100; md->carryTrim=TRIM_OFF; + + md=setDest(ICC(STK_THR)); md->srcRaw=CM(STK_THR); md->weight= 100; md->swtch=DSW_ID0; md->curve=CV(1); md->carryTrim=TRIM_OFF; + md=setDest(ICC(STK_THR)); md->srcRaw=CM(STK_THR); md->weight= 100; md->swtch=DSW_ID1; md->curve=CV(2); md->carryTrim=TRIM_OFF; + md=setDest(ICC(STK_THR)); md->srcRaw=CM(STK_THR); md->weight= 110; md->swtch=DSW_ID2; md->curve=CV(2); md->carryTrim=TRIM_OFF; + md=setDest(ICC(STK_THR)); md->srcRaw=MIX_MAX; md->weight=-125; md->swtch=DSW_THR; md->mltpx=MLTPX_REP; md->carryTrim=TRIM_OFF; + + md=setDest(ICC(STK_RUD)); md->srcRaw=CM(STK_RUD); md->weight=100; + + md=setDest(5); md->srcRaw=MIX_MAX; md->weight= 50; md->swtch=-DSW_GEA; md->carryTrim=TRIM_OFF; + md=setDest(5); md->srcRaw=MIX_MAX; md->weight=-50; md->swtch= DSW_GEA; md->carryTrim=TRIM_OFF; + md=setDest(5); md->srcRaw=STK_P3; md->weight= 40; md->carryTrim=TRIM_OFF; + + md=setDest(6); md->srcRaw=CH(9); md->weight= -50; + md=setDest(6); md->srcRaw=CH(10); md->weight=-100; + md=setDest(6); md->srcRaw=CH(11); md->weight=-100; md->carryTrim=TRIM_OFF; + + md=setDest(9); md->srcRaw=CM(STK_ELE); md->weight= 60; + md=setDest(10); md->srcRaw=CM(STK_AIL); md->weight=-52; + md=setDest(11); md->srcRaw=CM(STK_THR); md->weight= 70; md->swtch=DSW_ID0; md->curve=CV(3); md->carryTrim=TRIM_OFF; + md=setDest(11); md->srcRaw=CM(STK_THR); md->weight= 70; md->swtch=DSW_ID1; md->curve=CV(4); md->carryTrim=TRIM_OFF; + md=setDest(11); md->srcRaw=CM(STK_THR); md->weight= 70; md->swtch=DSW_ID2; md->curve=CV(4); md->carryTrim=TRIM_OFF; + md=setDest(11); md->srcRaw=CM(STK_THR); md->weight=100; md->swtch=DSW_THR; md->curve=CV(5); md->carryTrim=TRIM_OFF; md->mltpx=MLTPX_REP; + + //Set up Curves + setCurve(CURVE5(1),heli_ar1); + setCurve(CURVE5(2),heli_ar2); + setCurve(CURVE5(3),heli_ar3); + setCurve(CURVE5(4),heli_ar4); + setCurve(CURVE5(5),heli_ar5); + break; + + //Servo Test + case (6): + md=setDest(15); md->srcRaw=CH(16); md->weight= 100; md->speedUp = 8; md->speedDown = 8; + md=setDest(16); md->srcRaw=MIX_FULL; md->weight= 110; md->swtch=DSW_SW1; + md=setDest(16); md->srcRaw=MIX_MAX; md->weight=-110; md->swtch=DSW_SW2; md->mltpx=MLTPX_REP; + md=setDest(16); md->srcRaw=MIX_MAX; md->weight= 110; md->swtch=DSW_SW3; md->mltpx=MLTPX_REP; + + setSwitch(1,CS_LESS,CH(15),CH(16)); + setSwitch(2,CS_VPOS,CH(15), 105); + setSwitch(3,CS_VNEG,CH(15), -105); + break; + + + default: + break; + + } + STORE_MODELVARS; + +} diff --git a/src/templates.h b/src/templates.h new file mode 100644 index 000000000..2e1a6d6d6 --- /dev/null +++ b/src/templates.h @@ -0,0 +1,88 @@ +/* + * Author - Erez Raviv + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + * ============================================================ + * Templates file + * + * eccpm + * crow + * throttle cut + * flaperon + * elevon + * v-tail + * throttle hold + * Aileron Differential + * Spoilers + * Snap Roll + * ELE->Flap + * Flap->ELE + * + * + * + * ============================================================= + * Assumptions: + * All primary channels are per modi12x3 + * Each template added to the end of each channel + * + * + * + */ + +#ifndef TEMPLATES_H +#define TEMPLATES_H + +#include + + +#define STK_RUD 1 +#define STK_ELE 2 +#define STK_THR 3 +#define STK_AIL 4 +#define STK_P1 5 +#define STK_P2 6 +#define STK_P3 7 +#define NUM_TEMPLATES DIM(n_Templates) +#define NUM_TEMPLATE_MIX 8 +#define TEMPLATE_NLEN 15 + +#define TRIM_ON 0 +#define TRIM_OFF 1 + +#define CM(x) (CONVERT_MODE(x)) //good for SRC +#define CH(x) (CHOUT_BASE+(x)) +#define CV(x) (CURVE_BASE+(x)-1) +#define CC(x) (CHANNEL_ORDER(x)) //need to invert this to work with dest + +#define CURVE5(x) ((x)-1) +#define CURVE9(x) (MAX_CURVE5+(x)-1) + +const char n_Templates[][TEMPLATE_NLEN] = { + "Simple 4-CH", + "T-Cut", + "V-Tail", + "Elevon\\Delta", + "eCCPM", + "Heli Setup", + "Servo Test" +}; + + + + +void clearMixes(); +void clearCurves(); +void applyTemplate(uint8_t idx); + + +#endif //TEMPLATES_H + + diff --git a/src/time.cpp b/src/time.cpp new file mode 100644 index 000000000..1a85b41ca --- /dev/null +++ b/src/time.cpp @@ -0,0 +1,463 @@ +#include "time.h" + +#define LEAP_SECONDS_POSSIBLE 0 + +/* Shift A right by B bits portably, by dividing A by 2**B and + truncating towards minus infinity. A and B should be free of side + effects, and B should be in the range 0 <= B <= INT_BITS - 2, where + INT_BITS is the number of useful bits in an int. GNU code can + assume that INT_BITS is at least 32. + + ISO C99 says that A >> B is implementation-defined if A < 0. Some + implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift + right in the usual way when A < 0, so SHR falls back on division if + ordinary A >> B doesn't seem to be the usual signed shift. */ +#define SHR(a, b) \ + (-1 >> 1 == -1 \ + ? (a) >> (b) \ + : (a) / (1 << (b)) - ((a) % (1 << (b)) < 0)) + +/* The extra casts in the following macros work around compiler bugs, + e.g., in Cray C 5.0.3.0. */ + +/* True if the arithmetic type T is an integer type. bool counts as + an integer. */ +#define TYPE_IS_INTEGER(t) ((t) 1.5 == 1) + +/* True if negative values of the signed integer type T use two's + complement, ones' complement, or signed magnitude representation, + respectively. Much GNU code assumes two's complement, but some + people like to be portable to all possible C hosts. */ +#define TYPE_TWOS_COMPLEMENT(t) ((t) ~ (t) 0 == (t) -1) +#define TYPE_ONES_COMPLEMENT(t) ((t) ~ (t) 0 == 0) +#define TYPE_SIGNED_MAGNITUDE(t) ((t) ~ (t) 0 < (t) -1) + +/* True if the arithmetic type T is signed. */ +#define TYPE_SIGNED(t) (! ((t) 0 < (t) -1)) + +/* The maximum and minimum values for the integer type T. These + macros have undefined behavior if T is signed and has padding bits. + If this is a problem for you, please let us know how to fix it for + your host. */ +#define TYPE_MINIMUM(t) \ + ((t) (! TYPE_SIGNED (t) \ + ? (t) 0 \ + : TYPE_SIGNED_MAGNITUDE (t) \ + ? ~ (t) 0 \ + : ~ (t) 0 << (sizeof (t) * CHAR_BIT - 1))) +#define TYPE_MAXIMUM(t) \ + ((t) (! TYPE_SIGNED (t) \ + ? (t) -1 \ + : ~ (~ (t) 0 << (sizeof (t) * CHAR_BIT - 1)))) + +#ifndef TIME_T_MIN +# define TIME_T_MIN TYPE_MINIMUM (time_t) +#endif +#ifndef TIME_T_MAX +# define TIME_T_MAX TYPE_MAXIMUM (time_t) +#endif +#define TIME_T_MIDPOINT (SHR (TIME_T_MIN + TIME_T_MAX, 1) + 1) + +/* Verify a requirement at compile-time (unlike assert, which is runtime). */ +#define verify(name, assertion) struct name { char a[(assertion) ? 1 : -1]; } + +verify (time_t_is_integer, TYPE_IS_INTEGER (time_t)); +verify (twos_complement_arithmetic, TYPE_TWOS_COMPLEMENT (int)); +/* The code also assumes that signed integer overflow silently wraps + around, but this assumption can't be stated without causing a + diagnostic on some hosts. */ + +#define EPOCH_YEAR 1970 +#define TM_YEAR_BASE 1900 +verify (base_year_is_a_multiple_of_100, TM_YEAR_BASE % 100 == 0); + +#define SECS_PER_HOUR 3600ul +#define SECS_PER_DAY 86400ul +#define EOVERFLOW 0 + +/* Return 1 if YEAR + TM_YEAR_BASE is a leap year. */ +static inline int +leapyear (long int year) +{ + /* Don't add YEAR to TM_YEAR_BASE, as that might overflow. + Also, work even if YEAR is negative. */ + return + ((year & 3) == 0 + && (year % 100 != 0 + || ((year / 100) & 3) == (- (TM_YEAR_BASE / 100) & 3))); +} + +const unsigned short int __mon_yday[2][13] = + { + /* Normal years. */ + { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }, + /* Leap years. */ + { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 } + }; + +/* Compute the `struct tm' representation of *T, + offset OFFSET seconds east of UTC, + and store year, yday, mon, mday, wday, hour, min, sec into *TP. + Return nonzero if successful. */ +int +__offtime ( + time_t *t, + long int offset, + struct tm *tp) +{ + long int days, rem, y; + const unsigned short int *ip; + + days = *t / SECS_PER_DAY; + rem = *t % SECS_PER_DAY; + rem += offset; + while (rem < 0) + { + rem += SECS_PER_DAY; + --days; + } + while (rem >= SECS_PER_DAY) + { + rem -= SECS_PER_DAY; + ++days; + } + tp->tm_hour = rem / SECS_PER_HOUR; + rem %= SECS_PER_HOUR; + tp->tm_min = rem / 60; + tp->tm_sec = rem % 60; + /* January 1, 1970 was a Thursday. */ + tp->tm_wday = (4 + days) % 7; + if (tp->tm_wday < 0) + tp->tm_wday += 7; + y = 1970; + +#define DIV(a, b) ((a) / (b) - ((a) % (b) < 0)) +#define LEAPS_THRU_END_OF(y) (DIV (y, 4) - DIV (y, 100) + DIV (y, 400)) + + while (days < 0 || days >= (leapyear (y) ? 366 : 365)) + { + /* Guess a corrected year, assuming 365 days per year. */ + long int yg = y + days / 365 - (days % 365 < 0); + + /* Adjust DAYS and Y to match the guessed year. */ + days -= ((yg - y) * 365 + + LEAPS_THRU_END_OF (yg - 1) + - LEAPS_THRU_END_OF (y - 1)); + y = yg; + } + tp->tm_year = y - 1900; + if (tp->tm_year != y - 1900) + { + /* The year cannot be represented due to overflow. */ + // __set_errno (EOVERFLOW); + return 0; + } + tp->tm_yday = days; + ip = __mon_yday[leapyear(y)]; + for (y = 11; days < (long int) ip[y]; --y) + continue; + days -= ip[y]; + tp->tm_mon = y; + tp->tm_mday = days + 1; + return 1; +} + +/* time_r function implementations */ +// G: No time zones in our implementation so just do the converion from time_t to struct tm +struct tm * +__localtime_r (time_t * t, struct tm * tp) +{ + __offtime(t, 0, tp); + return tp; +} + + +/* Return an integer value measuring (YEAR1-YDAY1 HOUR1:MIN1:SEC1) - + (YEAR0-YDAY0 HOUR0:MIN0:SEC0) in seconds, assuming that the clocks + were not adjusted between the time stamps. + + The YEAR values uses the same numbering as TP->tm_year. Values + need not be in the usual range. However, YEAR1 must not be less + than 2 * INT_MIN or greater than 2 * INT_MAX. + + The result may overflow. It is the caller's responsibility to + detect overflow. */ + +static inline time_t +ydhms_diff (long int year1, long int yday1, int hour1, int min1, int sec1, + int year0, int yday0, int hour0, int min0, int sec0) +{ + verify (C99_integer_division, -1 / 2 == 0); + verify (long_int_year_and_yday_are_wide_enough, + INT_MAX <= LONG_MAX / 2 || TIME_T_MAX <= UINT_MAX); + + /* Compute intervening leap days correctly even if year is negative. + Take care to avoid integer overflow here. */ + int a4 = SHR (year1, 2) + SHR (TM_YEAR_BASE, 2) - ! (year1 & 3); + int b4 = SHR (year0, 2) + SHR (TM_YEAR_BASE, 2) - ! (year0 & 3); + int a100 = a4 / 25 - (a4 % 25 < 0); + int b100 = b4 / 25 - (b4 % 25 < 0); + int a400 = SHR (a100, 2); + int b400 = SHR (b100, 2); + int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400); + + /* Compute the desired time in time_t precision. Overflow might + occur here. */ + time_t tyear1 = year1; + time_t years = tyear1 - year0; + time_t days = 365 * years + yday1 - yday0 + intervening_leap_days; + time_t hours = 24 * days + hour1 - hour0; + time_t minutes = 60 * hours + min1 - min0; + time_t seconds = 60 * minutes + sec1 - sec0; + return seconds; +} + +/* Return a time_t value corresponding to (YEAR-YDAY HOUR:MIN:SEC), + assuming that *T corresponds to *TP and that no clock adjustments + occurred between *TP and the desired time. + If TP is null, return a value not equal to *T; this avoids false matches. + If overflow occurs, yield the minimal or maximal value, except do not + yield a value equal to *T. */ +static time_t +guess_time_tm (long int year, long int yday, int hour, int min, int sec, + time_t *t, struct tm *tp) +{ + if (tp) + { + time_t d = ydhms_diff (year, yday, hour, min, sec, + tp->tm_year, tp->tm_yday, + tp->tm_hour, tp->tm_min, tp->tm_sec); + time_t t1 = *t + d; + if ((t1 < *t) == (TYPE_SIGNED (time_t) ? d < 0 : TIME_T_MAX / 2 < d)) + return t1; + } + + /* Overflow occurred one way or another. Return the nearest result + that is actually in range, except don't report a zero difference + if the actual difference is nonzero, as that would cause a false + match; and don't oscillate between two values, as that would + confuse the spring-forward gap detector. */ + return (*t < TIME_T_MIDPOINT + ? (*t <= TIME_T_MIN + 1 ? *t + 1 : TIME_T_MIN) + : (TIME_T_MAX - 1 <= *t ? *t - 1 : TIME_T_MAX)); +} + +/* Use CONVERT to convert *T to a broken down time in *TP. + If *T is out of range for conversion, adjust it so that + it is the nearest in-range value and then convert that. */ +static struct tm * +ranged_convert (struct tm *(*convert) (time_t *, struct tm *), + time_t *t, struct tm *tp) +{ + struct tm *r = convert (t, tp); + + if (!r && *t) + { + time_t bad = *t; + time_t ok = 0; + + /* BAD is a known unconvertible time_t, and OK is a known good one. + Use binary search to narrow the range between BAD and OK until + they differ by 1. */ + while (bad != ok + (bad < 0 ? -1 : 1)) + { + time_t mid = *t = (bad < 0 + ? bad + ((ok - bad) >> 1) + : ok + ((bad - ok) >> 1)); + r = convert (t, tp); + if (r) + ok = mid; + else + bad = mid; + } + + if (!r && ok) + { + /* The last conversion attempt failed; + revert to the most recent successful attempt. */ + *t = ok; + r = convert (t, tp); + } + } + + return r; +} + +/* Convert *TP to a time_t value, inverting + the monotonic and mostly-unit-linear conversion function CONVERT. + Use *OFFSET to keep track of a guess at the offset of the result, + compared to what the result would be for UTC without leap seconds. + If *OFFSET's guess is correct, only one CONVERT call is needed. + This function is external because it is used also by timegm.c. */ +time_t +__mktime_internal (struct tm *tp, + struct tm *(*convert) (time_t *, struct tm *), + time_t *offset) +{ + time_t t, gt, t0, t1, t2; + struct tm tm; + + /* The maximum number of probes (calls to CONVERT) should be enough + to handle any combinations of time zone rule changes, solar time, + leap seconds, and oscillations around a spring-forward gap. + POSIX.1 prohibits leap seconds, but some hosts have them anyway. */ + int remaining_probes = 6; + + /* Time requested. Copy it in case CONVERT modifies *TP; this can + occur if TP is localtime's returned value and CONVERT is localtime. */ + int sec = tp->tm_sec; + int min = tp->tm_min; + int hour = tp->tm_hour; + int mday = tp->tm_mday; + int mon = tp->tm_mon; + int year_requested = tp->tm_year; + + /* Ensure that mon is in range, and set year accordingly. */ + int mon_remainder = mon % 12; + int negative_mon_remainder = mon_remainder < 0; + int mon_years = mon / 12 - negative_mon_remainder; + long int lyear_requested = year_requested; + long int year = lyear_requested + mon_years; + + /* The other values need not be in range: + the remaining code handles minor overflows correctly, + assuming int and time_t arithmetic wraps around. + Major overflows are caught at the end. */ + + /* Calculate day of year from year, month, and day of month. + The result need not be in range. */ + int mon_yday = ((__mon_yday[leapyear (year)] + [mon_remainder + 12 * negative_mon_remainder]) + - 1); + long int lmday = mday; + long int yday = mon_yday + lmday; + + time_t guessed_offset = *offset; + + int sec_requested = sec; + +/* + if (LEAP_SECONDS_POSSIBLE) + { + // Handle out-of-range seconds specially, + // since ydhms_tm_diff assumes every minute has 60 seconds. + if (sec < 0) + sec = 0; + if (59 < sec) + sec = 59; + } +*/ + + /* Invert CONVERT by probing. First assume the same offset as last + time. */ + + t0 = ydhms_diff (year, yday, hour, min, sec, + EPOCH_YEAR - TM_YEAR_BASE, 0, 0, 0, - guessed_offset); + + if (TIME_T_MAX / INT_MAX / 366 / 24 / 60 / 60 < 3) + { + /* time_t isn't large enough to rule out overflows, so check + for major overflows. A gross check suffices, since if t0 + has overflowed, it is off by a multiple of TIME_T_MAX - + TIME_T_MIN + 1. So ignore any component of the difference + that is bounded by a small value. */ + + /* Approximate log base 2 of the number of time units per + biennium. A biennium is 2 years; use this unit instead of + years to avoid integer overflow. For example, 2 average + Gregorian years are 2 * 365.2425 * 24 * 60 * 60 seconds, + which is 63113904 seconds, and rint (log2 (63113904)) is + 26. */ + int ALOG2_SECONDS_PER_BIENNIUM = 26; + int ALOG2_MINUTES_PER_BIENNIUM = 20; + int ALOG2_HOURS_PER_BIENNIUM = 14; + int ALOG2_DAYS_PER_BIENNIUM = 10; + int LOG2_YEARS_PER_BIENNIUM = 1; + + int approx_requested_biennia = + (SHR (year_requested, LOG2_YEARS_PER_BIENNIUM) + - SHR (EPOCH_YEAR - TM_YEAR_BASE, LOG2_YEARS_PER_BIENNIUM) + + SHR (mday, ALOG2_DAYS_PER_BIENNIUM) + + SHR (hour, ALOG2_HOURS_PER_BIENNIUM) + + SHR (min, ALOG2_MINUTES_PER_BIENNIUM) + + (LEAP_SECONDS_POSSIBLE + ? 0 + : SHR (sec, ALOG2_SECONDS_PER_BIENNIUM))); + + int approx_biennia = SHR (t0, ALOG2_SECONDS_PER_BIENNIUM); + int diff = approx_biennia - approx_requested_biennia; + int abs_diff = diff < 0 ? - diff : diff; + + /* IRIX 4.0.5 cc miscalculates TIME_T_MIN / 3: it erroneously + gives a positive value of 715827882. Setting a variable + first then doing math on it seems to work. + (ghazi@caip.rutgers.edu) */ + time_t time_t_max = TIME_T_MAX; + time_t time_t_min = TIME_T_MIN; + time_t overflow_threshold = + (time_t_max / 3 - time_t_min / 3) >> ALOG2_SECONDS_PER_BIENNIUM; + + if (overflow_threshold < abs_diff) + { + /* Overflow occurred. Try repairing it; this might work if + the time zone offset is enough to undo the overflow. */ + time_t repaired_t0 = -1 - t0; + approx_biennia = SHR (repaired_t0, ALOG2_SECONDS_PER_BIENNIUM); + diff = approx_biennia - approx_requested_biennia; + abs_diff = diff < 0 ? - diff : diff; + if (overflow_threshold < abs_diff) + return -1; + guessed_offset += repaired_t0 - t0; + t0 = repaired_t0; + } + } + + /* Repeatedly use the error to improve the guess. */ + + for (t = t1 = t2 = t0; + (gt = guess_time_tm (year, yday, hour, min, sec, &t, + ranged_convert (convert, &t, &tm)), + t != gt); + t1 = t2, t2 = t, t = gt) + if (t == t1 && t != t2) + goto offset_found; + else if (--remaining_probes == 0) + return -1; + + offset_found: + *offset = guessed_offset + t - t0; + + if (LEAP_SECONDS_POSSIBLE && sec_requested != tm.tm_sec) + { + /* Adjust time to reflect the tm_sec requested, not the normalized value. + Also, repair any damage from a false match due to a leap second. */ + int sec_adjustment = (sec == 0 && tm.tm_sec == 60) - sec; + t1 = t + sec_requested; + t2 = t1 + sec_adjustment; + if (((t1 < t) != (sec_requested < 0)) + | ((t2 < t1) != (sec_adjustment < 0)) + | ! convert (&t2, &tm)) + return -1; + t = t2; + } + + *tp = tm; + return t; +} + +/* Convert *TP to a time_t value. */ +time_t +mktime (struct tm *tp) +{ + // no time zone stuff. Just do the math ;) + static time_t localtime_offset; + return __mktime_internal (tp, __localtime_r, &localtime_offset); +} + +/* Fill a (struct tm) TP* from a given time_t time stamp */ +time_t +filltm(time_t *t, struct tm *tp) +{ + return __offtime(t, 0, tp); +} + diff --git a/src/time.h b/src/time.h new file mode 100644 index 000000000..e7a4d0e8b --- /dev/null +++ b/src/time.h @@ -0,0 +1,34 @@ +#ifndef g9x_time_h +#define g9x_time_h + +#include + +#define CHAR_BIT 8 +#define INT_MAX 32767 +#define INT_MIN -32767 +#define LONG_MAX 0x7FFFFFFFL +#define LONG_MIN ((long) 0x80000000L) +#define UINT_MAX 0xFFFFU/0xFFFFFFFFUL +#define ULONG_MAX 0xFFFFFFFFUL + +typedef long int time_t; + +struct tm +{ + int8_t tm_sec; /* Seconds. [0-60] (1 leap second) */ + int8_t tm_min; /* Minutes. [0-59] */ + int8_t tm_hour; /* Hours. [0-23] */ + int8_t tm_mday; /* Day. [1-31] */ + int8_t tm_mon; /* Month. [0-11] */ + int8_t tm_year; /* Year - 1900. Limited to the year 2115. Oh no! :P */ + int8_t tm_wday; /* Day of week. [0-6] */ + int16_t tm_yday; /* Day of year. [0-365] Needed internally for calculations */ +}; + +extern const unsigned short int __mon_yday[2][13]; + +extern time_t mktime(struct tm *tp); +extern time_t filltm(time_t *t, struct tm *tp); + +#endif +