Reduce 3d table memory usage (>500 bytes) (#662)
* Use table_row_iterator_t to adjust ignitionTable * Separate 2d & 3d table code, abstract page to table iterator conversion. This is just moving code around in preparation for future changes. * Reduce table RAM (.bss) usage Generate a separate type for each possible 3d axis & size combination. This turns what was runtime information into compile time data. * Save 1 byte per table. Use a flag value (INT16_MAX) instead of a separate boolean flag * File renaming table_iterator -> table3d_iterator.h table3d.h -> table3d_interpolate.h table3d.cpp -> table3d_interpolate.cpp table3d_types.h -> table3d.h * Optimize page.cpp: reduce code clutter, maintain performance * Reduce flash usage * Encapsulate table metadata * Performance - hoist if statement * Replace function with macro * Use a packed enum as a type identifier Use a packed enum as a type identifier Slimmer data types * Use table iterators for random access to table values and axis. * Centralize write buffer check * Encapsulate 16-bit reference concept * Performance: make table iterators proper classes This allows us to chain calls on temporaries - not possible with regular function calls. * Performance: encapsulate EEPROM update & address increment * Save flash - don't duplicate function * Performance: directly invalidate table cache * Separate out iterator reversal * Separate out entity mapping & per-byte access Much faster, smaller code footprint & easier to understand * Code quality fixes * Separate out axis metadata * Doxygen comments * Separate int16_ref into separate file * Separate out table axies & values into separate types No need for metadata types & more localised code. E.g. creating iterators is now alongside the data over which they iterate. * Doxygen
This commit is contained in:
parent
aaa5b9090f
commit
5ed9ec3ca9
386
Doxyfile
386
Doxyfile
|
@ -1,4 +1,4 @@
|
|||
# Doxyfile 1.8.20
|
||||
# Doxyfile 1.9.2
|
||||
|
||||
# This file describes the settings to be used by the documentation system
|
||||
# doxygen (www.doxygen.org) for a project.
|
||||
|
@ -51,7 +51,7 @@ PROJECT_BRIEF =
|
|||
# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
|
||||
# the logo to the output directory.
|
||||
|
||||
PROJECT_LOGO = "reference/speeduino_logo.png"
|
||||
PROJECT_LOGO = reference/speeduino_logo.png
|
||||
|
||||
# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
|
||||
# into which the generated documentation will be written. If a relative path is
|
||||
|
@ -93,14 +93,6 @@ ALLOW_UNICODE_NAMES = NO
|
|||
|
||||
OUTPUT_LANGUAGE = English
|
||||
|
||||
# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all
|
||||
# documentation generated by doxygen is written. Doxygen will use this
|
||||
# information to generate all generated output in the proper direction.
|
||||
# Possible values are: None, LTR, RTL and Context.
|
||||
# The default value is: None.
|
||||
|
||||
OUTPUT_TEXT_DIRECTION = None
|
||||
|
||||
# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
|
||||
# descriptions after the members that are listed in the file and class
|
||||
# documentation (similar to Javadoc). Set to NO to disable this.
|
||||
|
@ -258,16 +250,16 @@ TAB_SIZE = 4
|
|||
# the documentation. An alias has the form:
|
||||
# name=value
|
||||
# For example adding
|
||||
# "sideeffect=@par Side Effects:\n"
|
||||
# "sideeffect=@par Side Effects:^^"
|
||||
# will allow you to put the command \sideeffect (or @sideeffect) in the
|
||||
# documentation, which will result in a user-defined paragraph with heading
|
||||
# "Side Effects:". You can put \n's in the value part of an alias to insert
|
||||
# newlines (in the resulting output). You can put ^^ in the value part of an
|
||||
# alias to insert a newline as if a physical newline was in the original file.
|
||||
# When you need a literal { or } or , in the value part of an alias you have to
|
||||
# escape them by means of a backslash (\), this can lead to conflicts with the
|
||||
# commands \{ and \} for these it is advised to use the version @{ and @} or use
|
||||
# a double escape (\\{ and \\})
|
||||
# "Side Effects:". Note that you cannot put \n's in the value part of an alias
|
||||
# to insert newlines (in the resulting output). You can put ^^ in the value part
|
||||
# of an alias to insert a newline as if a physical newline was in the original
|
||||
# file. When you need a literal { or } or , in the value part of an alias you
|
||||
# have to escape them by means of a backslash (\), this can lead to conflicts
|
||||
# with the commands \{ and \} for these it is advised to use the version @{ and
|
||||
# @} or use a double escape (\\{ and \\})
|
||||
|
||||
ALIASES =
|
||||
|
||||
|
@ -312,8 +304,8 @@ OPTIMIZE_OUTPUT_SLICE = NO
|
|||
# extension. Doxygen has a built-in mapping, but you can override or extend it
|
||||
# using this tag. The format is ext=language, where ext is a file extension, and
|
||||
# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,
|
||||
# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL,
|
||||
# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
|
||||
# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice,
|
||||
# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
|
||||
# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
|
||||
# tries to guess whether the code is fixed or free formatted code, this is the
|
||||
# default for Fortran type files). For instance to make doxygen treat .inc files
|
||||
|
@ -323,7 +315,10 @@ OPTIMIZE_OUTPUT_SLICE = NO
|
|||
# Note: For files without extension you can use no_extension as a placeholder.
|
||||
#
|
||||
# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
|
||||
# the files are not read by doxygen.
|
||||
# the files are not read by doxygen. When specifying no_extension you should add
|
||||
# * to the FILE_PATTERNS.
|
||||
#
|
||||
# Note see also the list of default file extension mappings.
|
||||
|
||||
EXTENSION_MAPPING = ino=C++
|
||||
|
||||
|
@ -463,7 +458,7 @@ LOOKUP_CACHE_SIZE = 0
|
|||
# than 0 to get more control over the balance between CPU load and processing
|
||||
# speed. At this moment only the input processing can be done using multiple
|
||||
# threads. Since this is still an experimental feature the default is set to 1,
|
||||
# which efficively disables parallel processing. Please report any issues you
|
||||
# which effectively disables parallel processing. Please report any issues you
|
||||
# encounter. Generating dot graphs in parallel is controlled by the
|
||||
# DOT_NUM_THREADS setting.
|
||||
# Minimum value: 0, maximum value: 32, default value: 1.
|
||||
|
@ -533,6 +528,13 @@ EXTRACT_LOCAL_METHODS = NO
|
|||
|
||||
EXTRACT_ANON_NSPACES = NO
|
||||
|
||||
# If this flag is set to YES, the name of an unnamed parameter in a declaration
|
||||
# will be determined by the corresponding definition. By default unnamed
|
||||
# parameters remain unnamed in the output.
|
||||
# The default value is: YES.
|
||||
|
||||
RESOLVE_UNNAMED_PARAMS = YES
|
||||
|
||||
# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
|
||||
# undocumented members inside documented classes or files. If set to NO these
|
||||
# members will be included in the various overviews, but no documentation
|
||||
|
@ -570,11 +572,18 @@ HIDE_IN_BODY_DOCS = NO
|
|||
|
||||
INTERNAL_DOCS = NO
|
||||
|
||||
# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
|
||||
# names in lower-case letters. If set to YES, upper-case letters are also
|
||||
# allowed. This is useful if you have classes or files whose names only differ
|
||||
# in case and if your file system supports case sensitive file names. Windows
|
||||
# (including Cygwin) and Mac users are advised to set this option to NO.
|
||||
# With the correct setting of option CASE_SENSE_NAMES doxygen will better be
|
||||
# able to match the capabilities of the underlying filesystem. In case the
|
||||
# filesystem is case sensitive (i.e. it supports files in the same directory
|
||||
# whose names only differ in casing), the option must be set to YES to properly
|
||||
# deal with such files in case they appear in the input. For filesystems that
|
||||
# are not case sensitive the option should be be set to NO to properly deal with
|
||||
# output files written for symbols that only differ in casing, such as for two
|
||||
# classes, one named CLASS and the other named Class, and to also support
|
||||
# references to files without having to specify the exact matching casing. On
|
||||
# Windows (including Cygwin) and MacOS, users should typically set this option
|
||||
# to NO, whereas on Linux or other Unix flavors it should typically be set to
|
||||
# YES.
|
||||
# The default value is: system dependent.
|
||||
|
||||
CASE_SENSE_NAMES = NO
|
||||
|
@ -593,6 +602,12 @@ HIDE_SCOPE_NAMES = NO
|
|||
|
||||
HIDE_COMPOUND_REFERENCE= NO
|
||||
|
||||
# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class
|
||||
# will show which file needs to be included to use the class.
|
||||
# The default value is: YES.
|
||||
|
||||
SHOW_HEADERFILE = YES
|
||||
|
||||
# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
|
||||
# the files that are included by a file in the documentation of that file.
|
||||
# The default value is: YES.
|
||||
|
@ -750,7 +765,8 @@ FILE_VERSION_FILTER =
|
|||
# output files in an output format independent way. To create the layout file
|
||||
# that represents doxygen's defaults, run doxygen with the -l option. You can
|
||||
# optionally specify a file name after the option, if omitted DoxygenLayout.xml
|
||||
# will be used as the name of the layout file.
|
||||
# will be used as the name of the layout file. See also section "Changing the
|
||||
# layout of pages" for information.
|
||||
#
|
||||
# Note that if you run doxygen from a directory containing a file called
|
||||
# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
|
||||
|
@ -796,24 +812,35 @@ WARNINGS = YES
|
|||
WARN_IF_UNDOCUMENTED = YES
|
||||
|
||||
# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
|
||||
# potential errors in the documentation, such as not documenting some parameters
|
||||
# in a documented function, or documenting parameters that don't exist or using
|
||||
# markup commands wrongly.
|
||||
# potential errors in the documentation, such as documenting some parameters in
|
||||
# a documented function twice, or documenting parameters that don't exist or
|
||||
# using markup commands wrongly.
|
||||
# The default value is: YES.
|
||||
|
||||
WARN_IF_DOC_ERROR = YES
|
||||
|
||||
# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete
|
||||
# function parameter documentation. If set to NO, doxygen will accept that some
|
||||
# parameters have no documentation without warning.
|
||||
# The default value is: YES.
|
||||
|
||||
WARN_IF_INCOMPLETE_DOC = YES
|
||||
|
||||
# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
|
||||
# are documented, but have no documentation for their parameters or return
|
||||
# value. If set to NO, doxygen will only warn about wrong or incomplete
|
||||
# parameter documentation, but not about the absence of documentation. If
|
||||
# EXTRACT_ALL is set to YES then this flag will automatically be disabled.
|
||||
# value. If set to NO, doxygen will only warn about wrong parameter
|
||||
# documentation, but not about the absence of documentation. If EXTRACT_ALL is
|
||||
# set to YES then this flag will automatically be disabled. See also
|
||||
# WARN_IF_INCOMPLETE_DOC
|
||||
# The default value is: NO.
|
||||
|
||||
WARN_NO_PARAMDOC = NO
|
||||
|
||||
# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
|
||||
# a warning is encountered.
|
||||
# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS
|
||||
# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but
|
||||
# at the end of the doxygen process doxygen will return with a non-zero status.
|
||||
# Possible values are: NO, YES and FAIL_ON_WARNINGS.
|
||||
# The default value is: NO.
|
||||
|
||||
WARN_AS_ERROR = NO
|
||||
|
@ -849,8 +876,8 @@ INPUT = speeduino
|
|||
# This tag can be used to specify the character encoding of the source files
|
||||
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
|
||||
# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
|
||||
# documentation (see: https://www.gnu.org/software/libiconv/) for the list of
|
||||
# possible encodings.
|
||||
# documentation (see:
|
||||
# https://www.gnu.org/software/libiconv/) for the list of possible encodings.
|
||||
# The default value is: UTF-8.
|
||||
|
||||
INPUT_ENCODING = UTF-8
|
||||
|
@ -863,12 +890,14 @@ INPUT_ENCODING = UTF-8
|
|||
# need to set EXTENSION_MAPPING for the extension otherwise the files are not
|
||||
# read by doxygen.
|
||||
#
|
||||
# Note the list of default checked file patterns might differ from the list of
|
||||
# default file extension mappings.
|
||||
#
|
||||
# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
|
||||
# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
|
||||
# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
|
||||
# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment),
|
||||
# *.doc (to be provided as doxygen C comment), *.txt (to be provided as doxygen
|
||||
# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd,
|
||||
# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml,
|
||||
# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C
|
||||
# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd,
|
||||
# *.vhdl, *.ucf, *.qsf and *.ice.
|
||||
|
||||
FILE_PATTERNS = *.c \
|
||||
|
@ -958,7 +987,12 @@ EXCLUDE_PATTERNS =
|
|||
# Note that the wildcards are matched against the file with absolute path, so to
|
||||
# exclude all test directories use the pattern */test/*
|
||||
|
||||
EXCLUDE_SYMBOLS =
|
||||
EXCLUDE_SYMBOLS = TO_TYPE_KEY \
|
||||
CTA_* \
|
||||
TABLE3D_GENERATOR \
|
||||
TABLE3D_GEN_* \
|
||||
TABLE3D_TYPENAME_* \
|
||||
CONCRETE_TABLE_ACTION_INNER
|
||||
|
||||
# The EXAMPLE_PATH tag can be used to specify one or more files or directories
|
||||
# that contain example code fragments that are included (see the \include
|
||||
|
@ -1128,6 +1162,46 @@ USE_HTAGS = NO
|
|||
|
||||
VERBATIM_HEADERS = YES
|
||||
|
||||
# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the
|
||||
# clang parser (see:
|
||||
# http://clang.llvm.org/) for more accurate parsing at the cost of reduced
|
||||
# performance. This can be particularly helpful with template rich C++ code for
|
||||
# which doxygen's built-in parser lacks the necessary type information.
|
||||
# Note: The availability of this option depends on whether or not doxygen was
|
||||
# generated with the -Duse_libclang=ON option for CMake.
|
||||
# The default value is: NO.
|
||||
|
||||
CLANG_ASSISTED_PARSING = NO
|
||||
|
||||
# If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS
|
||||
# tag is set to YES then doxygen will add the directory of each input to the
|
||||
# include path.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
|
||||
|
||||
CLANG_ADD_INC_PATHS = YES
|
||||
|
||||
# If clang assisted parsing is enabled you can provide the compiler with command
|
||||
# line options that you would normally use when invoking the compiler. Note that
|
||||
# the include paths will already be set by doxygen for the files and directories
|
||||
# specified with INPUT and INCLUDE_PATH.
|
||||
# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
|
||||
|
||||
CLANG_OPTIONS =
|
||||
|
||||
# If clang assisted parsing is enabled you can provide the clang parser with the
|
||||
# path to the directory containing a file called compile_commands.json. This
|
||||
# file is the compilation database (see:
|
||||
# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the
|
||||
# options used when the source files were built. This is equivalent to
|
||||
# specifying the -p option to a clang tool, such as clang-check. These options
|
||||
# will then be passed to the parser. Any options specified with CLANG_OPTIONS
|
||||
# will be added as well.
|
||||
# Note: The availability of this option depends on whether or not doxygen was
|
||||
# generated with the -Duse_libclang=ON option for CMake.
|
||||
|
||||
CLANG_DATABASE_PATH =
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to the alphabetical class index
|
||||
#---------------------------------------------------------------------------
|
||||
|
@ -1139,13 +1213,6 @@ VERBATIM_HEADERS = YES
|
|||
|
||||
ALPHABETICAL_INDEX = YES
|
||||
|
||||
# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in
|
||||
# which the alphabetical index list will be split.
|
||||
# Minimum value: 1, maximum value: 20, default value: 5.
|
||||
# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
|
||||
|
||||
COLS_IN_ALPHA_INDEX = 5
|
||||
|
||||
# In case all classes in a project start with a common prefix, all classes will
|
||||
# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
|
||||
# can be used to specify a prefix (or a list of prefixes) that should be ignored
|
||||
|
@ -1245,7 +1312,7 @@ HTML_EXTRA_FILES =
|
|||
|
||||
# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
|
||||
# will adjust the colors in the style sheet and background images according to
|
||||
# this color. Hue is specified as an angle on a colorwheel, see
|
||||
# this color. Hue is specified as an angle on a color-wheel, see
|
||||
# https://en.wikipedia.org/wiki/Hue for more information. For instance the value
|
||||
# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
|
||||
# purple, and 360 is red again.
|
||||
|
@ -1255,7 +1322,7 @@ HTML_EXTRA_FILES =
|
|||
HTML_COLORSTYLE_HUE = 220
|
||||
|
||||
# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
|
||||
# in the HTML output. For a value of 0 the output will use grayscales only. A
|
||||
# in the HTML output. For a value of 0 the output will use gray-scales only. A
|
||||
# value of 255 will produce the most vivid colors.
|
||||
# Minimum value: 0, maximum value: 255, default value: 100.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
@ -1316,10 +1383,11 @@ HTML_INDEX_NUM_ENTRIES = 100
|
|||
|
||||
# If the GENERATE_DOCSET tag is set to YES, additional index files will be
|
||||
# generated that can be used as input for Apple's Xcode 3 integrated development
|
||||
# environment (see: https://developer.apple.com/xcode/), introduced with OSX
|
||||
# 10.5 (Leopard). To create a documentation set, doxygen will generate a
|
||||
# Makefile in the HTML output directory. Running make will produce the docset in
|
||||
# that directory and running make install will install the docset in
|
||||
# environment (see:
|
||||
# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To
|
||||
# create a documentation set, doxygen will generate a Makefile in the HTML
|
||||
# output directory. Running make will produce the docset in that directory and
|
||||
# running make install will install the docset in
|
||||
# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
|
||||
# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy
|
||||
# genXcode/_index.html for more information.
|
||||
|
@ -1361,8 +1429,12 @@ DOCSET_PUBLISHER_NAME = Publisher
|
|||
# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
|
||||
# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
|
||||
# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
|
||||
# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on
|
||||
# Windows.
|
||||
# on Windows. In the beginning of 2021 Microsoft took the original page, with
|
||||
# a.o. the download links, offline the HTML help workshop was already many years
|
||||
# in maintenance mode). You can download the HTML help workshop from the web
|
||||
# archives at Installation executable (see:
|
||||
# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo
|
||||
# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe).
|
||||
#
|
||||
# The HTML Help Workshop contains a compiler that can convert all HTML output
|
||||
# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
|
||||
|
@ -1437,7 +1509,8 @@ QCH_FILE =
|
|||
|
||||
# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
|
||||
# Project output. For more information please see Qt Help Project / Namespace
|
||||
# (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).
|
||||
# (see:
|
||||
# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).
|
||||
# The default value is: org.doxygen.Project.
|
||||
# This tag requires that the tag GENERATE_QHP is set to YES.
|
||||
|
||||
|
@ -1445,8 +1518,8 @@ QHP_NAMESPACE = org.doxygen.Project
|
|||
|
||||
# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
|
||||
# Help Project output. For more information please see Qt Help Project / Virtual
|
||||
# Folders (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-
|
||||
# folders).
|
||||
# Folders (see:
|
||||
# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders).
|
||||
# The default value is: doc.
|
||||
# This tag requires that the tag GENERATE_QHP is set to YES.
|
||||
|
||||
|
@ -1454,16 +1527,16 @@ QHP_VIRTUAL_FOLDER = doc
|
|||
|
||||
# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
|
||||
# filter to add. For more information please see Qt Help Project / Custom
|
||||
# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-
|
||||
# filters).
|
||||
# Filters (see:
|
||||
# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).
|
||||
# This tag requires that the tag GENERATE_QHP is set to YES.
|
||||
|
||||
QHP_CUST_FILTER_NAME =
|
||||
|
||||
# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
|
||||
# custom filter to add. For more information please see Qt Help Project / Custom
|
||||
# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-
|
||||
# filters).
|
||||
# Filters (see:
|
||||
# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).
|
||||
# This tag requires that the tag GENERATE_QHP is set to YES.
|
||||
|
||||
QHP_CUST_FILTER_ATTRS =
|
||||
|
@ -1475,9 +1548,9 @@ QHP_CUST_FILTER_ATTRS =
|
|||
|
||||
QHP_SECT_FILTER_ATTRS =
|
||||
|
||||
# The QHG_LOCATION tag can be used to specify the location of Qt's
|
||||
# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
|
||||
# generated .qhp file.
|
||||
# The QHG_LOCATION tag can be used to specify the location (absolute path
|
||||
# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to
|
||||
# run qhelpgenerator on the generated .qhp file.
|
||||
# This tag requires that the tag GENERATE_QHP is set to YES.
|
||||
|
||||
QHG_LOCATION =
|
||||
|
@ -1520,16 +1593,28 @@ DISABLE_INDEX = NO
|
|||
# to work a browser that supports JavaScript, DHTML, CSS and frames is required
|
||||
# (i.e. any modern browser). Windows users are probably better off using the
|
||||
# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
|
||||
# further fine-tune the look of the index. As an example, the default style
|
||||
# sheet generated by doxygen has an example that shows how to put an image at
|
||||
# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
|
||||
# the same information as the tab index, you could consider setting
|
||||
# DISABLE_INDEX to YES when enabling this option.
|
||||
# further fine tune the look of the index (see "Fine-tuning the output"). As an
|
||||
# example, the default style sheet generated by doxygen has an example that
|
||||
# shows how to put an image at the root of the tree instead of the PROJECT_NAME.
|
||||
# Since the tree basically has the same information as the tab index, you could
|
||||
# consider setting DISABLE_INDEX to YES when enabling this option.
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
GENERATE_TREEVIEW = NO
|
||||
|
||||
# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the
|
||||
# FULL_SIDEBAR option determines if the side bar is limited to only the treeview
|
||||
# area (value NO) or if it should extend to the full height of the window (value
|
||||
# YES). Setting this to YES gives a layout similar to
|
||||
# https://docs.readthedocs.io with more room for contents, but less room for the
|
||||
# project logo, title, and description. If either GENERATOR_TREEVIEW or
|
||||
# DISABLE_INDEX is set to NO, this option has no effect.
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
FULL_SIDEBAR = NO
|
||||
|
||||
# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
|
||||
# doxygen will group on one line in the generated HTML documentation.
|
||||
#
|
||||
|
@ -1602,11 +1687,29 @@ FORMULA_MACROFILE =
|
|||
|
||||
USE_MATHJAX = NO
|
||||
|
||||
# With MATHJAX_VERSION it is possible to specify the MathJax version to be used.
|
||||
# Note that the different versions of MathJax have different requirements with
|
||||
# regards to the different settings, so it is possible that also other MathJax
|
||||
# settings have to be changed when switching between the different MathJax
|
||||
# versions.
|
||||
# Possible values are: MathJax_2 and MathJax_3.
|
||||
# The default value is: MathJax_2.
|
||||
# This tag requires that the tag USE_MATHJAX is set to YES.
|
||||
|
||||
MATHJAX_VERSION = MathJax_2
|
||||
|
||||
# When MathJax is enabled you can set the default output format to be used for
|
||||
# the MathJax output. See the MathJax site (see:
|
||||
# http://docs.mathjax.org/en/latest/output.html) for more details.
|
||||
# the MathJax output. For more details about the output format see MathJax
|
||||
# version 2 (see:
|
||||
# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3
|
||||
# (see:
|
||||
# http://docs.mathjax.org/en/latest/web/components/output.html).
|
||||
# Possible values are: HTML-CSS (which is slower, but has the best
|
||||
# compatibility), NativeMML (i.e. MathML) and SVG.
|
||||
# compatibility. This is the name for Mathjax version 2, for MathJax version 3
|
||||
# this will be translated into chtml), NativeMML (i.e. MathML. Only supported
|
||||
# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This
|
||||
# is the name for Mathjax version 3, for MathJax version 2 this will be
|
||||
# translated into HTML-CSS) and SVG.
|
||||
# The default value is: HTML-CSS.
|
||||
# This tag requires that the tag USE_MATHJAX is set to YES.
|
||||
|
||||
|
@ -1619,22 +1722,29 @@ MATHJAX_FORMAT = HTML-CSS
|
|||
# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
|
||||
# Content Delivery Network so you can quickly see the result without installing
|
||||
# MathJax. However, it is strongly recommended to install a local copy of
|
||||
# MathJax from https://www.mathjax.org before deployment.
|
||||
# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2.
|
||||
# MathJax from https://www.mathjax.org before deployment. The default value is:
|
||||
# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2
|
||||
# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3
|
||||
# This tag requires that the tag USE_MATHJAX is set to YES.
|
||||
|
||||
MATHJAX_RELPATH = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/
|
||||
|
||||
# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
|
||||
# extension names that should be enabled during MathJax rendering. For example
|
||||
# for MathJax version 2 (see
|
||||
# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions):
|
||||
# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
|
||||
# For example for MathJax version 3 (see
|
||||
# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html):
|
||||
# MATHJAX_EXTENSIONS = ams
|
||||
# This tag requires that the tag USE_MATHJAX is set to YES.
|
||||
|
||||
MATHJAX_EXTENSIONS =
|
||||
|
||||
# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
|
||||
# of code that will be used on startup of the MathJax code. See the MathJax site
|
||||
# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
|
||||
# (see:
|
||||
# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an
|
||||
# example see the documentation.
|
||||
# This tag requires that the tag USE_MATHJAX is set to YES.
|
||||
|
||||
|
@ -1681,7 +1791,8 @@ SERVER_BASED_SEARCH = NO
|
|||
#
|
||||
# Doxygen ships with an example indexer (doxyindexer) and search engine
|
||||
# (doxysearch.cgi) which are based on the open source search engine library
|
||||
# Xapian (see: https://xapian.org/).
|
||||
# Xapian (see:
|
||||
# https://xapian.org/).
|
||||
#
|
||||
# See the section "External Indexing and Searching" for details.
|
||||
# The default value is: NO.
|
||||
|
@ -1694,8 +1805,9 @@ EXTERNAL_SEARCH = NO
|
|||
#
|
||||
# Doxygen ships with an example indexer (doxyindexer) and search engine
|
||||
# (doxysearch.cgi) which are based on the open source search engine library
|
||||
# Xapian (see: https://xapian.org/). See the section "External Indexing and
|
||||
# Searching" for details.
|
||||
# Xapian (see:
|
||||
# https://xapian.org/). See the section "External Indexing and Searching" for
|
||||
# details.
|
||||
# This tag requires that the tag SEARCHENGINE is set to YES.
|
||||
|
||||
SEARCHENGINE_URL =
|
||||
|
@ -1804,29 +1916,31 @@ PAPER_TYPE = a4
|
|||
|
||||
EXTRA_PACKAGES =
|
||||
|
||||
# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
|
||||
# generated LaTeX document. The header should contain everything until the first
|
||||
# chapter. If it is left blank doxygen will generate a standard header. See
|
||||
# section "Doxygen usage" for information on how to let doxygen write the
|
||||
# default header to a separate file.
|
||||
# The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for
|
||||
# the generated LaTeX document. The header should contain everything until the
|
||||
# first chapter. If it is left blank doxygen will generate a standard header. It
|
||||
# is highly recommended to start with a default header using
|
||||
# doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty
|
||||
# and then modify the file new_header.tex. See also section "Doxygen usage" for
|
||||
# information on how to generate the default header that doxygen normally uses.
|
||||
#
|
||||
# Note: Only use a user-defined header if you know what you are doing! The
|
||||
# following commands have a special meaning inside the header: $title,
|
||||
# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
|
||||
# $projectbrief, $projectlogo. Doxygen will replace $title with the empty
|
||||
# string, for the replacement values of the other commands the user is referred
|
||||
# to HTML_HEADER.
|
||||
# Note: Only use a user-defined header if you know what you are doing!
|
||||
# Note: The header is subject to change so you typically have to regenerate the
|
||||
# default header when upgrading to a newer version of doxygen. The following
|
||||
# commands have a special meaning inside the header (and footer): For a
|
||||
# description of the possible markers and block names see the documentation.
|
||||
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
||||
|
||||
LATEX_HEADER =
|
||||
|
||||
# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
|
||||
# generated LaTeX document. The footer should contain everything after the last
|
||||
# chapter. If it is left blank doxygen will generate a standard footer. See
|
||||
# The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for
|
||||
# the generated LaTeX document. The footer should contain everything after the
|
||||
# last chapter. If it is left blank doxygen will generate a standard footer. See
|
||||
# LATEX_HEADER for more information on how to generate a default footer and what
|
||||
# special commands can be used inside the footer.
|
||||
#
|
||||
# Note: Only use a user-defined footer if you know what you are doing!
|
||||
# special commands can be used inside the footer. See also section "Doxygen
|
||||
# usage" for information on how to generate the default footer that doxygen
|
||||
# normally uses. Note: Only use a user-defined footer if you know what you are
|
||||
# doing!
|
||||
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
||||
|
||||
LATEX_FOOTER =
|
||||
|
@ -1871,8 +1985,7 @@ USE_PDFLATEX = YES
|
|||
|
||||
# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
|
||||
# command to the generated LaTeX files. This will instruct LaTeX to keep running
|
||||
# if errors occur, instead of asking the user for help. This option is also used
|
||||
# when generating formulas in HTML.
|
||||
# if errors occur, instead of asking the user for help.
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
||||
|
||||
|
@ -1885,16 +1998,6 @@ LATEX_BATCHMODE = NO
|
|||
|
||||
LATEX_HIDE_INDICES = NO
|
||||
|
||||
# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
|
||||
# code with syntax highlighting in the LaTeX output.
|
||||
#
|
||||
# Note that which sources are shown also depends on other settings such as
|
||||
# SOURCE_BROWSER.
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
||||
|
||||
LATEX_SOURCE_CODE = NO
|
||||
|
||||
# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
|
||||
# bibliography, e.g. plainnat, or ieeetr. See
|
||||
# https://en.wikipedia.org/wiki/BibTeX and \cite for more info.
|
||||
|
@ -1975,16 +2078,6 @@ RTF_STYLESHEET_FILE =
|
|||
|
||||
RTF_EXTENSIONS_FILE =
|
||||
|
||||
# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code
|
||||
# with syntax highlighting in the RTF output.
|
||||
#
|
||||
# Note that which sources are shown also depends on other settings such as
|
||||
# SOURCE_BROWSER.
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag GENERATE_RTF is set to YES.
|
||||
|
||||
RTF_SOURCE_CODE = NO
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to the man page output
|
||||
#---------------------------------------------------------------------------
|
||||
|
@ -2081,15 +2174,6 @@ GENERATE_DOCBOOK = NO
|
|||
|
||||
DOCBOOK_OUTPUT = docbook
|
||||
|
||||
# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the
|
||||
# program listings (including syntax highlighting and cross-referencing
|
||||
# information) to the DOCBOOK output. Note that enabling this will significantly
|
||||
# increase the size of the DOCBOOK output.
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
|
||||
|
||||
DOCBOOK_PROGRAMLISTING = NO
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options for the AutoGen Definitions output
|
||||
#---------------------------------------------------------------------------
|
||||
|
@ -2102,6 +2186,10 @@ DOCBOOK_PROGRAMLISTING = NO
|
|||
|
||||
GENERATE_AUTOGEN_DEF = NO
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to Sqlite3 output
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to the Perl module output
|
||||
#---------------------------------------------------------------------------
|
||||
|
@ -2157,7 +2245,7 @@ ENABLE_PREPROCESSING = YES
|
|||
# The default value is: NO.
|
||||
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
|
||||
|
||||
MACRO_EXPANSION = NO
|
||||
MACRO_EXPANSION = YES
|
||||
|
||||
# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
|
||||
# the macro expansion is limited to the macros specified with the PREDEFINED and
|
||||
|
@ -2165,7 +2253,7 @@ MACRO_EXPANSION = NO
|
|||
# The default value is: NO.
|
||||
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
|
||||
|
||||
EXPAND_ONLY_PREDEF = NO
|
||||
EXPAND_ONLY_PREDEF = YES
|
||||
|
||||
# If the SEARCH_INCLUDES tag is set to YES, the include files in the
|
||||
# INCLUDE_PATH will be searched if a #include is found.
|
||||
|
@ -2206,7 +2294,11 @@ PREDEFINED =
|
|||
# definition found in the source code.
|
||||
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
|
||||
|
||||
EXPAND_AS_DEFINED =
|
||||
EXPAND_AS_DEFINED = TABLE3D_GEN_XAXIS \
|
||||
TABLE3D_GEN_YAXIS \
|
||||
TABLE3D_GEN_VALUES \
|
||||
TABLE3D_GEN_TYPE \
|
||||
TABLE3D_GENERATOR
|
||||
|
||||
# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
|
||||
# remove all references to function-like macros that are alone on a line, have
|
||||
|
@ -2374,10 +2466,32 @@ UML_LOOK = NO
|
|||
# but if the number exceeds 15, the total amount of fields shown is limited to
|
||||
# 10.
|
||||
# Minimum value: 0, maximum value: 100, default value: 10.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
# This tag requires that the tag UML_LOOK is set to YES.
|
||||
|
||||
UML_LIMIT_NUM_FIELDS = 10
|
||||
|
||||
# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and
|
||||
# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS
|
||||
# tag is set to YES, doxygen will add type and arguments for attributes and
|
||||
# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen
|
||||
# will not generate fields with class member information in the UML graphs. The
|
||||
# class diagrams will look similar to the default class diagrams but using UML
|
||||
# notation for the relationships.
|
||||
# Possible values are: NO, YES and NONE.
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag UML_LOOK is set to YES.
|
||||
|
||||
DOT_UML_DETAILS = NO
|
||||
|
||||
# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters
|
||||
# to display on a single line. If the actual line length exceeds this threshold
|
||||
# significantly it will wrapped across multiple lines. Some heuristics are apply
|
||||
# to avoid ugly line breaks.
|
||||
# Minimum value: 0, maximum value: 1000, default value: 17.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
DOT_WRAP_THRESHOLD = 17
|
||||
|
||||
# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
|
||||
# collaboration graphs will show the relations between templates and their
|
||||
# instances.
|
||||
|
@ -2567,9 +2681,11 @@ DOT_MULTI_TARGETS = NO
|
|||
|
||||
GENERATE_LEGEND = YES
|
||||
|
||||
# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot
|
||||
# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate
|
||||
# files that are used to generate the various graphs.
|
||||
#
|
||||
# Note: This setting is not only used for dot files but also for msc temporary
|
||||
# files.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
DOT_CLEANUP = YES
|
||||
|
|
|
@ -17,7 +17,6 @@ A full copy of the license may be found in the projects root directory
|
|||
#include "errors.h"
|
||||
#include "pages.h"
|
||||
#include "page_crc.h"
|
||||
#include "table_iterator.h"
|
||||
#include "logger.h"
|
||||
#ifdef RTC_ENABLED
|
||||
#include "rtc_common.h"
|
||||
|
@ -949,30 +948,30 @@ namespace {
|
|||
Serial.write((byte *)entity.pData, entity.size);
|
||||
}
|
||||
|
||||
inline void send_table_values(table_row_iterator_t it)
|
||||
inline void send_table_values(table_value_iterator it)
|
||||
{
|
||||
while (!at_end(it))
|
||||
while (!it.at_end())
|
||||
{
|
||||
auto row = get_row(it);
|
||||
Serial.write(row.pValue, row.pEnd-row.pValue);
|
||||
advance_row(it);
|
||||
auto row = *it;
|
||||
Serial.write(&*row, row.size());
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
inline void send_table_axis(table_axis_iterator_t it)
|
||||
inline void send_table_axis(table_axis_iterator it)
|
||||
{
|
||||
while (!at_end(it))
|
||||
while (!it.at_end())
|
||||
{
|
||||
Serial.write(get_value(it));
|
||||
it = advance_axis(it);
|
||||
Serial.write((byte)*it);
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
void send_table_entity(table3D *pTable)
|
||||
void send_table_entity(const page_iterator_t &entity)
|
||||
{
|
||||
send_table_values(rows_begin(pTable));
|
||||
send_table_axis(x_begin(pTable));
|
||||
send_table_axis(y_begin(pTable));
|
||||
send_table_values(rows_begin(entity));
|
||||
send_table_axis(x_begin(entity));
|
||||
send_table_axis(y_begin(entity));
|
||||
}
|
||||
|
||||
void send_entity(const page_iterator_t &entity)
|
||||
|
@ -984,7 +983,7 @@ namespace {
|
|||
break;
|
||||
|
||||
case Table:
|
||||
return send_table_entity(entity.pTable);
|
||||
return send_table_entity(entity);
|
||||
break;
|
||||
|
||||
case NoEntity:
|
||||
|
@ -1067,42 +1066,43 @@ namespace {
|
|||
Serial.print(F(" "));
|
||||
}
|
||||
|
||||
void print_row(const table_axis_iterator_t &y_it, table_row_t row)
|
||||
void print_row(const table_axis_iterator &y_it, table_row_iterator row)
|
||||
{
|
||||
serial_print_prepadded_value(get_value(y_it));
|
||||
serial_print_prepadded_value((byte)*y_it);
|
||||
|
||||
while (!at_end(row))
|
||||
while (!row.at_end())
|
||||
{
|
||||
serial_print_prepadded_value(*row.pValue++);
|
||||
serial_print_prepadded_value(*row);
|
||||
++row;
|
||||
}
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
void print_x_axis(const table3D ¤tTable)
|
||||
void print_x_axis(const void *pTable, table_type_t key)
|
||||
{
|
||||
Serial.print(F(" "));
|
||||
|
||||
auto x_it = x_begin(¤tTable);
|
||||
while(!at_end(x_it))
|
||||
auto x_it = x_begin(pTable, key);
|
||||
while(!x_it.at_end())
|
||||
{
|
||||
serial_print_prepadded_value(get_value(x_it));
|
||||
advance_axis(x_it);
|
||||
serial_print_prepadded_value((byte)*x_it);
|
||||
++x_it;
|
||||
}
|
||||
}
|
||||
|
||||
void serial_print_3dtable(const table3D ¤tTable)
|
||||
void serial_print_3dtable(const void *pTable, table_type_t key)
|
||||
{
|
||||
auto y_it = y_begin(¤tTable);
|
||||
auto row_it = rows_begin(¤tTable);
|
||||
auto y_it = y_begin(pTable, key);
|
||||
auto row_it = rows_begin(pTable, key);
|
||||
|
||||
while (!at_end(row_it))
|
||||
while (!row_it.at_end())
|
||||
{
|
||||
print_row(y_it, get_row(row_it));
|
||||
advance_axis(y_it);
|
||||
advance_row(row_it);
|
||||
print_row(y_it, *row_it);
|
||||
++y_it;
|
||||
++row_it;
|
||||
}
|
||||
|
||||
print_x_axis(currentTable);
|
||||
print_x_axis(pTable, key);
|
||||
Serial.println();
|
||||
}
|
||||
}
|
||||
|
@ -1118,7 +1118,7 @@ void sendPageASCII()
|
|||
{
|
||||
case veMapPage:
|
||||
Serial.println(F("\nVE Map"));
|
||||
serial_print_3dtable(fuelTable);
|
||||
serial_print_3dtable(&fuelTable, fuelTable.type_key);
|
||||
break;
|
||||
|
||||
case veSetPage:
|
||||
|
@ -1139,7 +1139,7 @@ void sendPageASCII()
|
|||
|
||||
case ignMapPage:
|
||||
Serial.println(F("\nIgnition Map"));
|
||||
serial_print_3dtable(ignitionTable);
|
||||
serial_print_3dtable(&ignitionTable, ignitionTable.type_key);
|
||||
break;
|
||||
|
||||
case ignSetPage:
|
||||
|
@ -1157,7 +1157,7 @@ void sendPageASCII()
|
|||
|
||||
case afrMapPage:
|
||||
Serial.println(F("\nAFR Map"));
|
||||
serial_print_3dtable(afrTable);
|
||||
serial_print_3dtable(&afrTable, afrTable.type_key);
|
||||
break;
|
||||
|
||||
case afrSetPage:
|
||||
|
@ -1181,14 +1181,14 @@ void sendPageASCII()
|
|||
|
||||
case boostvvtPage:
|
||||
Serial.println(F("\nBoost Map"));
|
||||
serial_print_3dtable(boostTable);
|
||||
serial_print_3dtable(&boostTable, boostTable.type_key);
|
||||
Serial.println(F("\nVVT Map"));
|
||||
serial_print_3dtable(vvtTable);
|
||||
serial_print_3dtable(&vvtTable, vvtTable.type_key);
|
||||
break;
|
||||
|
||||
case seqFuelPage:
|
||||
Serial.println(F("\nTrim 1 Table"));
|
||||
serial_print_3dtable(trim1Table);
|
||||
serial_print_3dtable(&trim1Table, trim1Table.type_key);
|
||||
break;
|
||||
|
||||
case canbusPage:
|
||||
|
@ -1198,12 +1198,12 @@ void sendPageASCII()
|
|||
|
||||
case fuelMap2Page:
|
||||
Serial.println(F("\n2nd Fuel Map"));
|
||||
serial_print_3dtable(fuelTable2);
|
||||
serial_print_3dtable(&fuelTable2, fuelTable2.type_key);
|
||||
break;
|
||||
|
||||
case ignMap2Page:
|
||||
Serial.println(F("\n2nd Ignition Map"));
|
||||
serial_print_3dtable(ignitionTable2);
|
||||
serial_print_3dtable(&ignitionTable2, ignitionTable2.type_key);
|
||||
break;
|
||||
|
||||
case warmupPage:
|
||||
|
|
|
@ -25,7 +25,8 @@
|
|||
#ifndef GLOBALS_H
|
||||
#define GLOBALS_H
|
||||
#include <Arduino.h>
|
||||
#include "table.h"
|
||||
#include "table2d.h"
|
||||
#include "table3d.h"
|
||||
#include <assert.h>
|
||||
#include "logger.h"
|
||||
|
||||
|
@ -431,25 +432,25 @@ extern const char TSfirmwareVersion[] PROGMEM;
|
|||
|
||||
extern const byte data_structure_version; //This identifies the data structure when reading / writing. Now in use: CURRENT_DATA_VERSION (migration on-the fly) ?
|
||||
|
||||
extern struct table3D fuelTable; //16x16 fuel map
|
||||
extern struct table3D fuelTable2; //16x16 fuel map
|
||||
extern struct table3D ignitionTable; //16x16 ignition map
|
||||
extern struct table3D ignitionTable2; //16x16 ignition map
|
||||
extern struct table3D afrTable; //16x16 afr target map
|
||||
extern struct table3D stagingTable; //8x8 fuel staging table
|
||||
extern struct table3D boostTable; //8x8 boost map
|
||||
extern struct table3D vvtTable; //8x8 vvt map
|
||||
extern struct table3D vvt2Table; //8x8 vvt2 map
|
||||
extern struct table3D wmiTable; //8x8 wmi map
|
||||
extern struct table3D trim1Table; //6x6 Fuel trim 1 map
|
||||
extern struct table3D trim2Table; //6x6 Fuel trim 2 map
|
||||
extern struct table3D trim3Table; //6x6 Fuel trim 3 map
|
||||
extern struct table3D trim4Table; //6x6 Fuel trim 4 map
|
||||
extern struct table3D trim5Table; //6x6 Fuel trim 5 map
|
||||
extern struct table3D trim6Table; //6x6 Fuel trim 6 map
|
||||
extern struct table3D trim7Table; //6x6 Fuel trim 7 map
|
||||
extern struct table3D trim8Table; //6x6 Fuel trim 8 map
|
||||
extern struct table3D dwellTable; //4x4 Dwell map
|
||||
extern struct table3d16RpmLoad fuelTable; //16x16 fuel map
|
||||
extern struct table3d16RpmLoad fuelTable2; //16x16 fuel map
|
||||
extern struct table3d16RpmLoad ignitionTable; //16x16 ignition map
|
||||
extern struct table3d16RpmLoad ignitionTable2; //16x16 ignition map
|
||||
extern struct table3d16RpmLoad afrTable; //16x16 afr target map
|
||||
extern struct table3d8RpmLoad stagingTable; //8x8 fuel staging table
|
||||
extern struct table3d8RpmTps boostTable; //8x8 boost map
|
||||
extern struct table3d8RpmTps vvtTable; //8x8 vvt map
|
||||
extern struct table3d8RpmTps vvt2Table; //8x8 vvt map
|
||||
extern struct table3d8RpmLoad wmiTable; //8x8 wmi map
|
||||
extern struct table3d6RpmLoad trim1Table; //6x6 Fuel trim 1 map
|
||||
extern struct table3d6RpmLoad trim2Table; //6x6 Fuel trim 2 map
|
||||
extern struct table3d6RpmLoad trim3Table; //6x6 Fuel trim 3 map
|
||||
extern struct table3d6RpmLoad trim4Table; //6x6 Fuel trim 4 map
|
||||
extern struct table3d6RpmLoad trim5Table; //6x6 Fuel trim 5 map
|
||||
extern struct table3d6RpmLoad trim6Table; //6x6 Fuel trim 6 map
|
||||
extern struct table3d6RpmLoad trim7Table; //6x6 Fuel trim 7 map
|
||||
extern struct table3d6RpmLoad trim8Table; //6x6 Fuel trim 8 map
|
||||
extern struct table3d4RpmLoad dwellTable; //4x4 Dwell map
|
||||
extern struct table2D taeTable; //4 bin TPS Acceleration Enrichment map (2D)
|
||||
extern struct table2D maeTable;
|
||||
extern struct table2D WUETable; //10 bin Warm Up Enrichment map (2D)
|
||||
|
|
|
@ -7,25 +7,25 @@ const char TSfirmwareVersion[] PROGMEM = "Speeduino";
|
|||
|
||||
const byte data_structure_version = 2; //This identifies the data structure when reading / writing. (outdated ?)
|
||||
|
||||
struct table3D fuelTable; ///< 16x16 fuel map
|
||||
struct table3D fuelTable2; ///< 16x16 fuel map
|
||||
struct table3D ignitionTable; ///< 16x16 ignition map
|
||||
struct table3D ignitionTable2; ///< 16x16 ignition map
|
||||
struct table3D afrTable; ///< 16x16 afr target map
|
||||
struct table3D stagingTable; ///< 8x8 fuel staging table
|
||||
struct table3D boostTable; ///< 8x8 boost map
|
||||
struct table3D vvtTable; ///< 8x8 vvt map
|
||||
struct table3D vvt2Table; ///< 8x8 vvt2 map
|
||||
struct table3D wmiTable; ///< 8x8 wmi map
|
||||
struct table3D trim1Table; ///< 6x6 Fuel trim 1 map
|
||||
struct table3D trim2Table; ///< 6x6 Fuel trim 2 map
|
||||
struct table3D trim3Table; ///< 6x6 Fuel trim 3 map
|
||||
struct table3D trim4Table; ///< 6x6 Fuel trim 4 map
|
||||
struct table3D trim5Table; ///< 6x6 Fuel trim 5 map
|
||||
struct table3D trim6Table; ///< 6x6 Fuel trim 6 map
|
||||
struct table3D trim7Table; ///< 6x6 Fuel trim 7 map
|
||||
struct table3D trim8Table; ///< 6x6 Fuel trim 8 map
|
||||
struct table3D dwellTable; ///< 4x4 Dwell map
|
||||
struct table3d16RpmLoad fuelTable; ///< 16x16 fuel map
|
||||
struct table3d16RpmLoad fuelTable2; ///< 16x16 fuel map
|
||||
struct table3d16RpmLoad ignitionTable; ///< 16x16 ignition map
|
||||
struct table3d16RpmLoad ignitionTable2; ///< 16x16 ignition map
|
||||
struct table3d16RpmLoad afrTable; ///< 16x16 afr target map
|
||||
struct table3d8RpmLoad stagingTable; ///< 8x8 fuel staging table
|
||||
struct table3d8RpmTps boostTable; ///< 8x8 boost map
|
||||
struct table3d8RpmTps vvtTable; ///< 8x8 vvt map
|
||||
struct table3d8RpmTps vvt2Table; ///< 8x8 vvt2 map
|
||||
struct table3d8RpmLoad wmiTable; ///< 8x8 wmi map
|
||||
struct table3d6RpmLoad trim1Table; ///< 6x6 Fuel trim 1 map
|
||||
struct table3d6RpmLoad trim2Table; ///< 6x6 Fuel trim 2 map
|
||||
struct table3d6RpmLoad trim3Table; ///< 6x6 Fuel trim 3 map
|
||||
struct table3d6RpmLoad trim4Table; ///< 6x6 Fuel trim 4 map
|
||||
struct table3d6RpmLoad trim5Table; ///< 6x6 Fuel trim 5 map
|
||||
struct table3d6RpmLoad trim6Table; ///< 6x6 Fuel trim 6 map
|
||||
struct table3d6RpmLoad trim7Table; ///< 6x6 Fuel trim 7 map
|
||||
struct table3d6RpmLoad trim8Table; ///< 6x6 Fuel trim 8 map
|
||||
struct table3d4RpmLoad dwellTable; ///< 4x4 Dwell map
|
||||
struct table2D taeTable; ///< 4 bin TPS Acceleration Enrichment map (2D)
|
||||
struct table2D maeTable;
|
||||
struct table2D WUETable; ///< 10 bin Warm Up Enrichment map (2D)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
#define IDLE_H
|
||||
|
||||
#include "globals.h"
|
||||
#include "table.h"
|
||||
#include "table2d.h"
|
||||
#include BOARD_H //Note that this is not a real file, it is defined in globals.h.
|
||||
|
||||
#define IAC_ALGORITHM_NONE 0
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
#include "decoders.h"
|
||||
#include "corrections.h"
|
||||
#include "idle.h"
|
||||
#include "table.h"
|
||||
#include "table2d.h"
|
||||
#include "acc_mc33810.h"
|
||||
#include BOARD_H //Note that this is not a real file, it is defined in globals.h.
|
||||
#include EEPROM_LIB_H
|
||||
|
@ -52,25 +52,6 @@ void initialiseAll()
|
|||
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
digitalWrite(LED_BUILTIN, LOW);
|
||||
table3D_setSize(&fuelTable, 16);
|
||||
table3D_setSize(&fuelTable2, 16);
|
||||
table3D_setSize(&ignitionTable, 16);
|
||||
table3D_setSize(&ignitionTable2, 16);
|
||||
table3D_setSize(&afrTable, 16);
|
||||
table3D_setSize(&stagingTable, 8);
|
||||
table3D_setSize(&boostTable, 8);
|
||||
table3D_setSize(&vvtTable, 8);
|
||||
table3D_setSize(&vvt2Table, 8);
|
||||
table3D_setSize(&wmiTable, 8);
|
||||
table3D_setSize(&trim1Table, 6);
|
||||
table3D_setSize(&trim2Table, 6);
|
||||
table3D_setSize(&trim3Table, 6);
|
||||
table3D_setSize(&trim4Table, 6);
|
||||
table3D_setSize(&trim5Table, 6);
|
||||
table3D_setSize(&trim6Table, 6);
|
||||
table3D_setSize(&trim7Table, 6);
|
||||
table3D_setSize(&trim8Table, 6);
|
||||
table3D_setSize(&dwellTable, 4);
|
||||
|
||||
#if defined(CORE_STM32)
|
||||
configPage9.intcan_available = 1; // device has internal canbus
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* @addtogroup table_3d
|
||||
* @{
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/** @brief Byte type. This is not defined in any C or C++ standard header. */
|
||||
typedef uint8_t byte;
|
||||
|
||||
/** @brief Represents a 16-bit value as a byte. Useful for I/O.
|
||||
*
|
||||
* Often we need to deal internally with values that fit in 16-bits but do
|
||||
* not require much accuracy. E.g. table axes in RPM. For these values we can
|
||||
* save storage space (EEPROM) by scaling to/from 8-bits using a fixed divisor.
|
||||
*/
|
||||
class int16_ref
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
* @brief Construct
|
||||
* @param value The \c int16_t to encapsulate.
|
||||
* @param factor The factor to scale the \c int16_t value by when converting to/from a \c byte
|
||||
*/
|
||||
int16_ref(int16_t &value, uint8_t factor)
|
||||
: _value(value), _factor(factor)
|
||||
{
|
||||
}
|
||||
|
||||
/** @brief Convert to a \c byte */
|
||||
inline byte operator*() const { return (byte)(_value/_factor); }
|
||||
|
||||
/** @brief Convert to a \c byte */
|
||||
inline explicit operator byte() const { return **this; }
|
||||
|
||||
/** @brief Convert from a \c byte */
|
||||
inline int16_ref &operator=( byte in ) { _value = (int16_t)in * (int16_t)_factor; return *this; }
|
||||
|
||||
private:
|
||||
int16_t &_value;
|
||||
uint8_t _factor;
|
||||
};
|
||||
|
||||
/** @} */
|
|
@ -1,7 +1,6 @@
|
|||
#include "page_crc.h"
|
||||
#include "pages.h"
|
||||
#include "src/FastCRC/FastCRC.h"
|
||||
#include "table_iterator.h"
|
||||
|
||||
static FastCRC32 CRC32;
|
||||
|
||||
|
@ -12,43 +11,46 @@ static inline uint32_t compute_raw_crc(const page_iterator_t &entity, pCrcCalc c
|
|||
return (CRC32.*calcFunc)((uint8_t*)entity.pData, entity.size, false);
|
||||
}
|
||||
|
||||
static inline uint32_t compute_tablevalues_crc(table_row_iterator_t it, pCrcCalc calcFunc)
|
||||
static inline uint32_t compute_row_crc(const table_row_iterator &row, pCrcCalc calcFunc)
|
||||
{
|
||||
table_row_t row = get_row(it);
|
||||
uint32_t crc = (CRC32.*calcFunc)(row.pValue, row.pEnd-row.pValue, false);
|
||||
advance_row(it);
|
||||
return (CRC32.*calcFunc)(&*row, row.size(), false);
|
||||
}
|
||||
|
||||
while (!at_end(it))
|
||||
static inline uint32_t compute_tablevalues_crc(table_value_iterator it, pCrcCalc calcFunc)
|
||||
{
|
||||
uint32_t crc = compute_row_crc(*it, calcFunc);
|
||||
++it;
|
||||
|
||||
while (!it.at_end())
|
||||
{
|
||||
row = get_row(it);
|
||||
crc = CRC32.crc32_upd(row.pValue, row.pEnd-row.pValue, false);
|
||||
advance_row(it);
|
||||
crc = compute_row_crc(*it, &FastCRC32::crc32_upd);
|
||||
++it;
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
static inline uint32_t compute_tableaxis_crc(table_axis_iterator_t it, uint32_t crc)
|
||||
static inline uint32_t compute_tableaxis_crc(table_axis_iterator it, uint32_t crc)
|
||||
{
|
||||
byte values[32]; // Fingers crossed we don't have a table bigger than 32x32
|
||||
byte *pValue = values;
|
||||
while (!at_end(it))
|
||||
while (!it.at_end())
|
||||
{
|
||||
*pValue++ = get_value(it);
|
||||
it = advance_axis(it);
|
||||
*pValue++ = (byte)*it;
|
||||
++it;
|
||||
}
|
||||
return pValue-values==0 ? crc : CRC32.crc32_upd(values, pValue-values, false);
|
||||
}
|
||||
|
||||
static inline uint32_t compute_table_crc(table3D *pTable, pCrcCalc calcFunc)
|
||||
static inline uint32_t compute_table_crc(page_iterator_t &entity, pCrcCalc calcFunc)
|
||||
{
|
||||
return compute_tableaxis_crc(y_begin(pTable),
|
||||
compute_tableaxis_crc(x_begin(pTable),
|
||||
compute_tablevalues_crc(rows_begin(pTable), calcFunc)));
|
||||
return compute_tableaxis_crc(y_begin(entity),
|
||||
compute_tableaxis_crc(x_begin(entity),
|
||||
compute_tablevalues_crc(rows_begin(entity), calcFunc)));
|
||||
}
|
||||
|
||||
static inline uint32_t pad_crc(uint16_t padding, uint32_t crc)
|
||||
{
|
||||
uint8_t raw_value = 0u;
|
||||
const uint8_t raw_value = 0u;
|
||||
while (padding>0)
|
||||
{
|
||||
crc = CRC32.crc32_upd(&raw_value, 1, false);
|
||||
|
@ -66,7 +68,7 @@ static inline uint32_t compute_crc(page_iterator_t &entity, pCrcCalc calcFunc)
|
|||
break;
|
||||
|
||||
case Table:
|
||||
return compute_table_crc(entity.pTable, calcFunc);
|
||||
return compute_table_crc(entity, calcFunc);
|
||||
break;
|
||||
|
||||
case NoEntity:
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
#include "pages.h"
|
||||
#include "globals.h"
|
||||
#include "utilities.h"
|
||||
#include "table_iterator.h"
|
||||
|
||||
// This namespace maps from virtual page "addresses" to addresses/bytes of real in memory entities
|
||||
// Maps from virtual page "addresses" to addresses/bytes of real in memory entities
|
||||
//
|
||||
// For TunerStudio:
|
||||
// 1. Each page has a numeric identifier (0 to N-1)
|
||||
// 2. A single page is a continguous block of data.
|
||||
// So individual bytes are identified by a page number + offset
|
||||
// So individual bytes are identified by a (page number, offset)
|
||||
//
|
||||
// The TS layout is not what is in memory. E.g.
|
||||
//
|
||||
|
@ -26,289 +25,385 @@
|
|||
// Page sizes as defined in the .ini file
|
||||
constexpr const uint16_t PROGMEM ini_page_sizes[] = { 0, 128, 288, 288, 128, 288, 128, 240, 384, 192, 192, 288, 192, 128, 288 };
|
||||
|
||||
// What section of a 3D table the offset mapped to
|
||||
enum table3D_section_t {
|
||||
Value, // The values
|
||||
axisX, // X axis
|
||||
axisY, // Y axis
|
||||
TableSectionNone // Should never happen!
|
||||
};
|
||||
// ========================= Table size calculations =========================
|
||||
// Note that these should be computed at compile time, assuming the correct
|
||||
// calling context.
|
||||
|
||||
// Stores enough information to access a table element
|
||||
struct table_entity_t {
|
||||
table3D *pTable;
|
||||
uint8_t xIndex; // Value X index or X axis index
|
||||
uint8_t yIndex; // Value Y index or Y axis index
|
||||
table3D_section_t section;
|
||||
};
|
||||
template <class table_t>
|
||||
inline constexpr uint16_t get_table_value_end()
|
||||
{
|
||||
return table_t::xaxis_t::length*table_t::yaxis_t::length;
|
||||
}
|
||||
template <class table_t>
|
||||
inline constexpr uint16_t get_table_axisx_end()
|
||||
{
|
||||
return get_table_value_end<table_t>()+table_t::xaxis_t::length;
|
||||
}
|
||||
template <class table_t>
|
||||
inline constexpr uint16_t get_table_axisy_end(const table_t *)
|
||||
{
|
||||
return get_table_axisx_end<table_t>()+table_t::yaxis_t::length;
|
||||
}
|
||||
|
||||
struct entity_t {
|
||||
// The entity that the offset mapped to
|
||||
union {
|
||||
table_entity_t table;
|
||||
void *pData;
|
||||
// ========================= Intra-table offset to byte class =========================
|
||||
|
||||
template<class table_t>
|
||||
class offset_to_table
|
||||
{
|
||||
public:
|
||||
|
||||
// This class encapsulates mapping a linear offset to the various parts of a table
|
||||
// and exposing the linear offset as an mutable byte.
|
||||
//
|
||||
// Tables do not map linearly to the TS page address space, so special
|
||||
// handling is necessary (we do not use the normal array layout for
|
||||
// performance reasons elsewhere)
|
||||
//
|
||||
// We take the offset & map it to a single value, x-axis or y-axis element
|
||||
//
|
||||
// Using a template here is a performance boost - we can call functions that
|
||||
// are specialized per table type, which allows the compiler more optimization
|
||||
// opportunities. See get_table_value().
|
||||
|
||||
offset_to_table(table_t *pTable, uint16_t table_offset)
|
||||
: _pTable(pTable),
|
||||
_table_offset(table_offset)
|
||||
{
|
||||
}
|
||||
|
||||
// Getter
|
||||
inline byte operator*() const
|
||||
{
|
||||
switch (get_table_location())
|
||||
{
|
||||
case table_location_values:
|
||||
return get_value_value();
|
||||
case table_location_xaxis:
|
||||
return *get_xaxis_value();
|
||||
case table_location_yaxis:
|
||||
default:
|
||||
return *get_yaxis_value();
|
||||
}
|
||||
}
|
||||
|
||||
// Setter
|
||||
inline offset_to_table &operator=( byte new_value )
|
||||
{
|
||||
switch (get_table_location())
|
||||
{
|
||||
case table_location_values:
|
||||
get_value_value() = new_value;
|
||||
break;
|
||||
|
||||
case table_location_xaxis:
|
||||
get_xaxis_value() = new_value;
|
||||
break;
|
||||
|
||||
case table_location_yaxis:
|
||||
default:
|
||||
get_yaxis_value() = new_value;
|
||||
}
|
||||
invalidate_cache(&_pTable->get_value_cache);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
inline byte& get_value_value() const
|
||||
{
|
||||
return _pTable->values.value_at((uint8_t)_table_offset);
|
||||
}
|
||||
|
||||
inline int16_ref get_xaxis_value() const
|
||||
{
|
||||
return *_pTable->axisX.begin().advance(_table_offset - get_table_value_end<table_t>());
|
||||
}
|
||||
|
||||
inline int16_ref get_yaxis_value() const
|
||||
{
|
||||
return *_pTable->axisY.begin().advance(_table_offset - get_table_axisx_end<table_t>());
|
||||
}
|
||||
|
||||
enum table_location {
|
||||
table_location_values, table_location_xaxis, table_location_yaxis
|
||||
};
|
||||
uint8_t page; // The page the entity belongs to
|
||||
uint16_t start; // The start position of the entity, in bytes, from the start of the page
|
||||
uint16_t size; // Size of the entity in bytes
|
||||
entity_type type;
|
||||
|
||||
inline table_location get_table_location() const
|
||||
{
|
||||
if (_table_offset<get_table_value_end<table_t>())
|
||||
{
|
||||
return table_location_values;
|
||||
}
|
||||
if (_table_offset<get_table_axisx_end<table_t>())
|
||||
{
|
||||
return table_location_xaxis;
|
||||
}
|
||||
return table_location_yaxis;
|
||||
}
|
||||
|
||||
table_t *_pTable;
|
||||
uint16_t _table_offset;
|
||||
};
|
||||
|
||||
// ========================= Offset to entity byte mapping =========================
|
||||
|
||||
inline byte& get_raw_location(page_iterator_t &entity, uint16_t offset)
|
||||
{
|
||||
return *((byte*)entity.pData + (offset-entity.start));
|
||||
}
|
||||
|
||||
inline byte get_table_value(page_iterator_t &entity, uint16_t offset)
|
||||
{
|
||||
#define CTA_GET_TABLE_VALUE(size, xDomain, yDomain, pTable, offset) \
|
||||
return *offset_to_table<TABLE3D_TYPENAME_BASE(size, xDomain, yDomain)>((TABLE3D_TYPENAME_BASE(size, xDomain, yDomain)*)pTable, offset);
|
||||
CONCRETE_TABLE_ACTION(entity.table_key, CTA_GET_TABLE_VALUE, entity.pData, (offset-entity.start));
|
||||
}
|
||||
|
||||
inline byte get_value(page_iterator_t &entity, uint16_t offset)
|
||||
{
|
||||
if (Raw==entity.type)
|
||||
{
|
||||
return get_raw_location(entity, offset);
|
||||
}
|
||||
if (Table==entity.type)
|
||||
{
|
||||
return get_table_value(entity, offset);
|
||||
}
|
||||
return 0U;
|
||||
}
|
||||
|
||||
inline void set_table_value(page_iterator_t &entity, uint16_t offset, byte new_value)
|
||||
{
|
||||
#define CTA_SET_TABLE_VALUE(size, xDomain, yDomain, pTable, offset, new_value) \
|
||||
offset_to_table<TABLE3D_TYPENAME_BASE(size, xDomain, yDomain)>((TABLE3D_TYPENAME_BASE(size, xDomain, yDomain)*)pTable, offset) = new_value; break;
|
||||
CONCRETE_TABLE_ACTION(entity.table_key, CTA_SET_TABLE_VALUE, entity.pData, (offset-entity.start), new_value);
|
||||
}
|
||||
|
||||
inline void set_value(page_iterator_t &entity, byte value, uint16_t offset)
|
||||
{
|
||||
if (Raw==entity.type)
|
||||
{
|
||||
get_raw_location(entity, offset) = value;
|
||||
}
|
||||
else if (Table==entity.type)
|
||||
{
|
||||
set_table_value(entity, offset, value);
|
||||
}
|
||||
}
|
||||
|
||||
// ========================= Static page size computation & checking ===================
|
||||
|
||||
// This will fail AND print the page number and required size
|
||||
template <uint8_t pageNum, uint16_t min>
|
||||
static inline void check_size() {
|
||||
static_assert(ini_page_sizes[pageNum] >= min, "Size is off!");
|
||||
}
|
||||
|
||||
// Handy table macros
|
||||
#define TABLE_VALUE_END(size) ((uint16_t)size*(uint16_t)size)
|
||||
#define TABLE_AXISX_END(size) (TABLE_VALUE_END(size)+(uint16_t)size)
|
||||
#define TABLE_AXISY_END(size) (TABLE_AXISX_END(size)+(uint16_t)size)
|
||||
#define TABLE_SIZE(size) TABLE_AXISY_END(size)
|
||||
// Since pages are a logical contiguous block, we can automatically compute the
|
||||
// logical start address of every item: the first one starts at zero, following
|
||||
// items must start at the end of the previous.
|
||||
#define _ENTITY_START(entityNum) entity ## entityNum ## Start
|
||||
#define ENTITY_START_VAR(entityNum) _ENTITY_START(entityNum)
|
||||
// Compute the start address of the next entity. We need this to be a constexpr
|
||||
// so we can static assert on it later. So we cannot increment an exiting var.
|
||||
#define DECLARE_NEXT_ENTITY_START(entityIndex, entitySize) \
|
||||
constexpr uint16_t ENTITY_START_VAR( PP_INC(entityIndex) ) = ENTITY_START_VAR(entityIndex)+entitySize;
|
||||
|
||||
// Precompute for performance
|
||||
#define TABLE16_SIZE TABLE_SIZE(16)
|
||||
#define TABLE8_SIZE TABLE_SIZE(8)
|
||||
#define TABLE6_SIZE TABLE_SIZE(6)
|
||||
#define TABLE4_SIZE TABLE_SIZE(4)
|
||||
// ========================= Logical page end processing ===================
|
||||
|
||||
// Macros + compile time constants = fast division/modulus
|
||||
// The members of all page_iterator_t instances are compile time constants and
|
||||
// thus all page_iterator_t instances *could* be compile time constants.
|
||||
//
|
||||
// The various fast division libraries, E.g. libdivide, use
|
||||
// 32-bit operations for 16-bit division. Super slow.
|
||||
#define OFFSET_TOVALUE_YINDEX(offset, size) ((uint8_t)((size-1) - (offset / size)))
|
||||
#define OFFSET_TOVALUE_XINDEX(offset, size) ((uint8_t)(offset % size))
|
||||
#define OFFSET_TOAXIS_XINDEX(offset, size) ((uint8_t)(offset - TABLE_VALUE_END(size)))
|
||||
#define OFFSET_TOAXIS_YINDEX(offset, size) ((uint8_t)((size-1) - (offset - TABLE_AXISX_END(size))))
|
||||
|
||||
#define NULL_TABLE \
|
||||
{ nullptr, 0, 0, TableSectionNone }
|
||||
|
||||
#define CREATE_PAGE_END(pageNum, pageSize) \
|
||||
{ NULL_TABLE, .page = pageNum, .start = 0, .size = pageSize, .type = End }
|
||||
// If we declare them inline as part of return statements, gcc recognises they
|
||||
// are constants (even without constexpr). Constants need to be stored somewhere:
|
||||
// gcc places them in the .data section, which is placed in SRAM :-(.
|
||||
//
|
||||
// So we would end up using several hundred bytes of SRAM.
|
||||
//
|
||||
// Instead we use this (and other) intermediate factory function(s) - it provides a barrier that
|
||||
// forces GCC to construct the page_iterator_t instance at runtime.
|
||||
inline const page_iterator_t create_end_iterator(uint8_t pageNum, uint16_t start)
|
||||
{
|
||||
return page_iterator_t {
|
||||
.pData = nullptr,
|
||||
.table_key = table_type_None,
|
||||
.page = pageNum,
|
||||
.start = start,
|
||||
.size = start,
|
||||
.type = End,
|
||||
};
|
||||
}
|
||||
|
||||
// Signal the end of a page
|
||||
#define END_OF_PAGE(pageNum, pageSize) \
|
||||
check_size<pageNum, pageSize>(); \
|
||||
return CREATE_PAGE_END(pageNum, pageSize);
|
||||
#define END_OF_PAGE(pageNum, entityNum) \
|
||||
check_size<pageNum, ENTITY_START_VAR(entityNum)>(); \
|
||||
return create_end_iterator(pageNum, ENTITY_START_VAR(entityNum)); \
|
||||
|
||||
// If the offset is in range, create a None entity_t
|
||||
#define CHECK_NOENTITY(offset, startByte, blockSize, pageNum) \
|
||||
if (offset < (startByte)+blockSize) \
|
||||
{ \
|
||||
return { NULL_TABLE, .page = pageNum, .start = (startByte), .size = blockSize, .type = NoEntity }; \
|
||||
}
|
||||
// ========================= Table processing ===================
|
||||
|
||||
//
|
||||
#define TABLE_VALUE(offset, startByte, pTable, tableSize) \
|
||||
{ pTable, \
|
||||
OFFSET_TOVALUE_XINDEX((offset-(startByte)), tableSize), \
|
||||
OFFSET_TOVALUE_YINDEX((offset-(startByte)), tableSize), \
|
||||
Value }
|
||||
|
||||
#define TABLE_XAXIS(offset, startByte, pTable, tableSize) \
|
||||
{ pTable, \
|
||||
OFFSET_TOAXIS_XINDEX((offset-(startByte)), tableSize), \
|
||||
0U, \
|
||||
axisX }
|
||||
|
||||
#define TABLE_YAXIS(offset, startByte, pTable, tableSize) \
|
||||
{ pTable, \
|
||||
0U, \
|
||||
OFFSET_TOAXIS_YINDEX((offset-(startByte)), tableSize), \
|
||||
axisY }
|
||||
|
||||
#define TABLE_ENTITY(table_type, pageNum, startByte, tableSize) \
|
||||
{ table_type, .page = pageNum, .start = (startByte), .size = TABLE_SIZE(tableSize), .type = Table }
|
||||
inline const page_iterator_t create_table_iterator(void *pTable, table_type_t key, uint8_t pageNum, uint16_t start, uint16_t size)
|
||||
{
|
||||
return page_iterator_t {
|
||||
.pData = pTable,
|
||||
.table_key = key,
|
||||
.page = pageNum,
|
||||
.start = start,
|
||||
.size = size,
|
||||
.type = Table,
|
||||
};
|
||||
}
|
||||
|
||||
// If the offset is in range, create a Table entity_t
|
||||
#define CHECK_TABLE(offset, startByte, pTable, tableSize, pageNum) \
|
||||
if (offset < (startByte)+TABLE_VALUE_END(tableSize)) \
|
||||
#define CHECK_TABLE(pageNum, offset, pTable, entityNum) \
|
||||
if (offset < ENTITY_START_VAR(entityNum)+get_table_axisy_end(pTable)) \
|
||||
{ \
|
||||
return TABLE_ENTITY(TABLE_VALUE(offset, startByte, pTable, tableSize), pageNum, startByte, tableSize); \
|
||||
return create_table_iterator(pTable, (pTable)->type_key, \
|
||||
pageNum, \
|
||||
ENTITY_START_VAR(entityNum), get_table_axisy_end(pTable)); \
|
||||
} \
|
||||
if (offset < (startByte)+TABLE_AXISX_END(tableSize)) \
|
||||
{ \
|
||||
return TABLE_ENTITY(TABLE_XAXIS(offset, startByte, pTable, tableSize), pageNum, startByte, tableSize); \
|
||||
} \
|
||||
if (offset < (startByte)+TABLE_AXISY_END(tableSize)) \
|
||||
{ \
|
||||
return TABLE_ENTITY(TABLE_YAXIS(offset, startByte, pTable, tableSize), pageNum, startByte, tableSize); \
|
||||
}
|
||||
DECLARE_NEXT_ENTITY_START(entityNum, get_table_axisy_end(pTable))
|
||||
|
||||
// ========================= Raw memory block processing ===================
|
||||
|
||||
inline const page_iterator_t create_raw_iterator(void *pBuffer, uint8_t pageNum, uint16_t start, uint16_t size)
|
||||
{
|
||||
return page_iterator_t {
|
||||
.pData = pBuffer,
|
||||
.table_key = table_type_None,
|
||||
.page = pageNum,
|
||||
.start = start,
|
||||
.size = size,
|
||||
.type = Raw,
|
||||
};
|
||||
}
|
||||
|
||||
// If the offset is in range, create a Raw entity_t
|
||||
#define CHECK_RAW(offset, startByte, pDataBlock, blockSize, pageNum) \
|
||||
if (offset < (startByte)+blockSize) \
|
||||
#define CHECK_RAW(pageNum, offset, pDataBlock, blockSize, entityNum) \
|
||||
if (offset < ENTITY_START_VAR(entityNum)+blockSize) \
|
||||
{ \
|
||||
return { { (table3D*)pDataBlock, 0, 0, TableSectionNone }, .page = pageNum, .start = (startByte), .size = blockSize, .type = Raw }; \
|
||||
}
|
||||
return create_raw_iterator(pDataBlock, pageNum, ENTITY_START_VAR(entityNum), blockSize);\
|
||||
} \
|
||||
DECLARE_NEXT_ENTITY_START(entityNum, blockSize)
|
||||
|
||||
// ===============================================================================
|
||||
|
||||
// Does the heavy lifting of mapping page+offset to an entity
|
||||
//
|
||||
// Alternative implementation would be to encode the mapping into data structures
|
||||
// That uses flash memory, which is scarce. And it was too slow.
|
||||
static inline __attribute__((always_inline)) // <-- this is critical for performance
|
||||
entity_t map_page_offset_to_entity_inline(uint8_t pageNumber, uint16_t offset)
|
||||
page_iterator_t map_page_offset_to_entity(uint8_t pageNumber, uint16_t offset)
|
||||
{
|
||||
// The start address of the 1st entity in any page.
|
||||
static constexpr uint16_t ENTITY_START_VAR(0) = 0U;
|
||||
|
||||
switch (pageNumber)
|
||||
{
|
||||
case 0:
|
||||
return CREATE_PAGE_END(0, 0);
|
||||
END_OF_PAGE(0, 0)
|
||||
|
||||
case veMapPage:
|
||||
CHECK_TABLE(offset, 0U, &fuelTable, 16, pageNumber)
|
||||
END_OF_PAGE(veMapPage, TABLE16_SIZE);
|
||||
break;
|
||||
{
|
||||
CHECK_TABLE(veMapPage, offset, &fuelTable, 0)
|
||||
END_OF_PAGE(veMapPage, 1)
|
||||
}
|
||||
|
||||
case ignMapPage: //Ignition settings page (Page 2)
|
||||
CHECK_TABLE(offset, 0U, &ignitionTable, 16, pageNumber)
|
||||
END_OF_PAGE(ignMapPage, TABLE16_SIZE);
|
||||
break;
|
||||
{
|
||||
CHECK_TABLE(ignMapPage, offset, &ignitionTable, 0)
|
||||
END_OF_PAGE(ignMapPage, 1)
|
||||
}
|
||||
|
||||
case afrMapPage: //Air/Fuel ratio target settings page
|
||||
CHECK_TABLE(offset, 0U, &afrTable, 16, pageNumber)
|
||||
END_OF_PAGE(afrMapPage, TABLE16_SIZE);
|
||||
break;
|
||||
{
|
||||
CHECK_TABLE(afrMapPage, offset, &afrTable, 0)
|
||||
END_OF_PAGE(afrMapPage, 1)
|
||||
}
|
||||
|
||||
case boostvvtPage: //Boost, VVT and staging maps (all 8x8)
|
||||
CHECK_TABLE(offset, 0U, &boostTable, 8, pageNumber)
|
||||
CHECK_TABLE(offset, TABLE8_SIZE, &vvtTable, 8, pageNumber)
|
||||
CHECK_TABLE(offset, TABLE8_SIZE*2, &stagingTable, 8, pageNumber)
|
||||
END_OF_PAGE(boostvvtPage, TABLE8_SIZE*3);
|
||||
break;
|
||||
{
|
||||
CHECK_TABLE(boostvvtPage, offset, &boostTable, 0)
|
||||
CHECK_TABLE(boostvvtPage, offset, &vvtTable, 1)
|
||||
CHECK_TABLE(boostvvtPage, offset, &stagingTable, 2)
|
||||
END_OF_PAGE(boostvvtPage, 3)
|
||||
}
|
||||
|
||||
case seqFuelPage:
|
||||
CHECK_TABLE(offset, 0U, &trim1Table, 6, pageNumber)
|
||||
CHECK_TABLE(offset, TABLE6_SIZE*1, &trim2Table, 6, pageNumber)
|
||||
CHECK_TABLE(offset, TABLE6_SIZE*2, &trim3Table, 6, pageNumber)
|
||||
CHECK_TABLE(offset, TABLE6_SIZE*3, &trim4Table, 6, pageNumber)
|
||||
CHECK_TABLE(offset, TABLE6_SIZE*4, &trim5Table, 6, pageNumber)
|
||||
CHECK_TABLE(offset, TABLE6_SIZE*5, &trim6Table, 6, pageNumber)
|
||||
CHECK_TABLE(offset, TABLE6_SIZE*6, &trim7Table, 6, pageNumber)
|
||||
CHECK_TABLE(offset, TABLE6_SIZE*7, &trim8Table, 6, pageNumber)
|
||||
END_OF_PAGE(seqFuelPage, TABLE6_SIZE*8);
|
||||
break;
|
||||
{
|
||||
CHECK_TABLE(seqFuelPage, offset, &trim1Table, 0)
|
||||
CHECK_TABLE(seqFuelPage, offset, &trim2Table, 1)
|
||||
CHECK_TABLE(seqFuelPage, offset, &trim3Table, 2)
|
||||
CHECK_TABLE(seqFuelPage, offset, &trim4Table, 3)
|
||||
CHECK_TABLE(seqFuelPage, offset, &trim5Table, 4)
|
||||
CHECK_TABLE(seqFuelPage, offset, &trim6Table, 5)
|
||||
CHECK_TABLE(seqFuelPage, offset, &trim7Table, 6)
|
||||
CHECK_TABLE(seqFuelPage, offset, &trim8Table, 7)
|
||||
END_OF_PAGE(seqFuelPage, 8)
|
||||
}
|
||||
|
||||
case fuelMap2Page:
|
||||
CHECK_TABLE(offset, 0U, &fuelTable2, 16, pageNumber)
|
||||
END_OF_PAGE(fuelMap2Page, TABLE16_SIZE);
|
||||
break;
|
||||
{
|
||||
CHECK_TABLE(fuelMap2Page, offset, &fuelTable2, 0)
|
||||
END_OF_PAGE(fuelMap2Page, 1)
|
||||
}
|
||||
|
||||
case wmiMapPage:
|
||||
CHECK_TABLE(offset, 0U, &wmiTable, 8, pageNumber)
|
||||
CHECK_TABLE(offset, TABLE8_SIZE, &vvt2Table, 8, pageNumber)
|
||||
CHECK_TABLE(offset, TABLE8_SIZE*2, &dwellTable, 4, pageNumber)
|
||||
END_OF_PAGE(wmiMapPage, TABLE8_SIZE*2 + TABLE4_SIZE);
|
||||
break;
|
||||
{
|
||||
CHECK_TABLE(wmiMapPage, offset, &wmiTable, 0)
|
||||
CHECK_TABLE(wmiMapPage, offset, &vvt2Table, 1)
|
||||
CHECK_TABLE(wmiMapPage, offset, &dwellTable, 2)
|
||||
END_OF_PAGE(wmiMapPage, 3)
|
||||
}
|
||||
|
||||
case ignMap2Page:
|
||||
CHECK_TABLE(offset, 0U, &ignitionTable2, 16, pageNumber)
|
||||
END_OF_PAGE(ignMap2Page, TABLE16_SIZE);
|
||||
break;
|
||||
{
|
||||
CHECK_TABLE(ignMap2Page, offset, &ignitionTable2, 0)
|
||||
END_OF_PAGE(ignMap2Page, 1)
|
||||
}
|
||||
|
||||
case veSetPage:
|
||||
CHECK_RAW(offset, 0U, &configPage2, sizeof(configPage2), pageNumber)
|
||||
END_OF_PAGE(veSetPage, sizeof(configPage2));
|
||||
break;
|
||||
{
|
||||
CHECK_RAW(veSetPage, offset, &configPage2, sizeof(configPage2), 0)
|
||||
END_OF_PAGE(veSetPage, 1)
|
||||
}
|
||||
|
||||
case ignSetPage:
|
||||
CHECK_RAW(offset, 0U, &configPage4, sizeof(configPage4), pageNumber)
|
||||
END_OF_PAGE(ignSetPage, sizeof(configPage4));
|
||||
break;
|
||||
|
||||
{
|
||||
CHECK_RAW(ignSetPage, offset, &configPage4, sizeof(configPage4), 0)
|
||||
END_OF_PAGE(ignSetPage, 1)
|
||||
}
|
||||
|
||||
case afrSetPage:
|
||||
CHECK_RAW(offset, 0U, &configPage6, sizeof(configPage6), pageNumber)
|
||||
END_OF_PAGE(afrSetPage, sizeof(configPage6));
|
||||
break;
|
||||
{
|
||||
CHECK_RAW(afrSetPage, offset, &configPage6, sizeof(configPage6), 0)
|
||||
END_OF_PAGE(afrSetPage, 1)
|
||||
}
|
||||
|
||||
case canbusPage:
|
||||
CHECK_RAW(offset, 0U, &configPage9, sizeof(configPage9), pageNumber)
|
||||
END_OF_PAGE(canbusPage, sizeof(configPage9));
|
||||
break;
|
||||
{
|
||||
CHECK_RAW(canbusPage, offset, &configPage9, sizeof(configPage9), 0)
|
||||
END_OF_PAGE(canbusPage, 1)
|
||||
}
|
||||
|
||||
case warmupPage:
|
||||
CHECK_RAW(offset, 0U, &configPage10, sizeof(configPage10), pageNumber)
|
||||
END_OF_PAGE(warmupPage, sizeof(configPage10));
|
||||
break;
|
||||
{
|
||||
CHECK_RAW(warmupPage, offset, &configPage10, sizeof(configPage10), 0)
|
||||
END_OF_PAGE(warmupPage, 1)
|
||||
}
|
||||
|
||||
case progOutsPage:
|
||||
CHECK_RAW(offset, 0U, &configPage13, sizeof(configPage13), pageNumber)
|
||||
END_OF_PAGE(progOutsPage, sizeof(configPage13));
|
||||
break;
|
||||
{
|
||||
CHECK_RAW(progOutsPage, offset, &configPage13, sizeof(configPage13), 0)
|
||||
END_OF_PAGE(progOutsPage, 1)
|
||||
}
|
||||
|
||||
default:
|
||||
abort(); // Unkown page number. Not a lot we can do.
|
||||
abort(); // Unkown page number. Not a lot we can do.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Tables do not map linearly to the TS page address space, so special
|
||||
// handling is necessary (we do not use the normal array layout for
|
||||
// performance reasons elsewhere)
|
||||
//
|
||||
// We take the offset & map it to a single value, x-axis or y-axis element
|
||||
static inline byte get_table_value(const table_entity_t &table)
|
||||
{
|
||||
switch (table.section)
|
||||
{
|
||||
case Value:
|
||||
return table.pTable->values[table.yIndex][table.xIndex];
|
||||
|
||||
case axisX:
|
||||
return (byte)(table.pTable->axisX[table.xIndex] / getTableXAxisFactor(table.pTable));
|
||||
|
||||
case axisY:
|
||||
return (byte)(table.pTable->axisY[table.yIndex] / getTableYAxisFactor(table.pTable));
|
||||
|
||||
default: return 0; // no-op
|
||||
}
|
||||
return 0U;
|
||||
}
|
||||
|
||||
static inline void set_table_value(const table_entity_t &table, int8_t value)
|
||||
{
|
||||
switch (table.section)
|
||||
{
|
||||
case Value:
|
||||
table.pTable->values[table.yIndex][table.xIndex] = value;
|
||||
break;
|
||||
|
||||
case axisX:
|
||||
table.pTable->axisX[table.xIndex] = (int16_t)(value) * getTableXAxisFactor(table.pTable);
|
||||
break;
|
||||
|
||||
case axisY:
|
||||
table.pTable->axisY[table.yIndex]= (int16_t)(value) * getTableYAxisFactor(table.pTable);
|
||||
break;
|
||||
|
||||
default: ; // no-op
|
||||
}
|
||||
table.pTable->cacheIsValid = false; //Invalid the tables cache to ensure a lookup of new values
|
||||
}
|
||||
|
||||
static inline byte* get_raw_value(const entity_t &entity, uint16_t offset)
|
||||
{
|
||||
return (byte*)entity.pData + offset;
|
||||
}
|
||||
|
||||
// ============================ Page iteration support ======================
|
||||
|
||||
// Because the page iterators will not be called for every single byte
|
||||
// inlining the mapping function is not performance critical.
|
||||
//
|
||||
// So save some memory.
|
||||
static entity_t map_page_offset_to_entity(uint8_t pageNumber, uint16_t offset)
|
||||
{
|
||||
return map_page_offset_to_entity_inline(pageNumber, offset);
|
||||
}
|
||||
|
||||
static inline page_iterator_t to_page_entity(entity_t mapped)
|
||||
{
|
||||
return { { mapped.type==Table ? mapped.table.pTable : (table3D*)mapped.pData },
|
||||
.page=mapped.page, .start = mapped.start, .size = mapped.size, .type = mapped.type };
|
||||
}
|
||||
|
||||
// ====================================== External functions ====================================
|
||||
|
||||
uint8_t getPageCount()
|
||||
|
@ -323,50 +418,50 @@ uint16_t getPageSize(byte pageNum)
|
|||
|
||||
void setPageValue(byte pageNum, uint16_t offset, byte value)
|
||||
{
|
||||
entity_t entity = map_page_offset_to_entity_inline(pageNum, offset);
|
||||
page_iterator_t entity = map_page_offset_to_entity(pageNum, offset);
|
||||
|
||||
switch (entity.type)
|
||||
{
|
||||
case Table:
|
||||
set_table_value(entity.table, value);
|
||||
break;
|
||||
|
||||
case Raw:
|
||||
*get_raw_value(entity, offset-entity.start) = value;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
set_value(entity, value, offset);
|
||||
}
|
||||
|
||||
byte getPageValue(byte page, uint16_t offset)
|
||||
byte getPageValue(byte pageNum, uint16_t offset)
|
||||
{
|
||||
entity_t entity = map_page_offset_to_entity_inline(page, offset);
|
||||
page_iterator_t entity = map_page_offset_to_entity(pageNum, offset);
|
||||
|
||||
switch (entity.type)
|
||||
{
|
||||
case Table:
|
||||
return get_table_value(entity.table);
|
||||
break;
|
||||
|
||||
case Raw:
|
||||
return *get_raw_value(entity, offset);
|
||||
break;
|
||||
|
||||
default: return 0U;
|
||||
}
|
||||
return 0U;
|
||||
return get_value(entity, offset);
|
||||
}
|
||||
|
||||
// Support iteration over a pages entities.
|
||||
// Check for entity.type==End
|
||||
page_iterator_t page_begin(byte pageNum)
|
||||
{
|
||||
return to_page_entity(map_page_offset_to_entity(pageNum, 0U));
|
||||
return map_page_offset_to_entity(pageNum, 0U);
|
||||
}
|
||||
|
||||
page_iterator_t advance(const page_iterator_t &it)
|
||||
{
|
||||
return to_page_entity(map_page_offset_to_entity(it.page, it.start+it.size));
|
||||
return map_page_offset_to_entity(it.page, it.start+it.size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert page iterator to table value iterator.
|
||||
*/
|
||||
table_value_iterator rows_begin(const page_iterator_t &it)
|
||||
{
|
||||
return rows_begin(it.pData, it.table_key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert page iterator to table x axis iterator.
|
||||
*/
|
||||
table_axis_iterator x_begin(const page_iterator_t &it)
|
||||
{
|
||||
return x_begin(it.pData, it.table_key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert page iterator to table y axis iterator.
|
||||
*/
|
||||
table_axis_iterator y_begin(const page_iterator_t &it)
|
||||
{
|
||||
return y_begin(it.pData, it.table_key);
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
#pragma once
|
||||
#include <Arduino.h>
|
||||
#include "table.h"
|
||||
#include "table3d.h"
|
||||
|
||||
/**
|
||||
* Page count, as defined in the INI file
|
||||
|
@ -60,10 +60,8 @@ enum entity_type {
|
|||
|
||||
// A entity on a logical page.
|
||||
struct page_iterator_t {
|
||||
union {
|
||||
table3D *pTable;
|
||||
void *pData;
|
||||
};
|
||||
void *pData;
|
||||
table_type_t table_key;
|
||||
uint8_t page; // The page the entity belongs to
|
||||
uint16_t start; // The start position of the entity, in bytes, from the start of the page
|
||||
uint16_t size; // Size of the entity in bytes
|
||||
|
@ -80,3 +78,18 @@ page_iterator_t page_begin(byte pageNum /**< [in] The page number to iterate ove
|
|||
* Moves the iterator to the next sub-entity on the page
|
||||
*/
|
||||
page_iterator_t advance(const page_iterator_t &it /**< [in] The current iterator */);
|
||||
|
||||
/**
|
||||
* Convert page iterator to table value iterator.
|
||||
*/
|
||||
table_value_iterator rows_begin(const page_iterator_t &it);
|
||||
|
||||
/**
|
||||
* Convert page iterator to table x axis iterator.
|
||||
*/
|
||||
table_axis_iterator x_begin(const page_iterator_t &it);
|
||||
|
||||
/**
|
||||
* Convert page iterator to table y axis iterator.
|
||||
*/
|
||||
table_axis_iterator y_begin(const page_iterator_t &it);
|
||||
|
|
|
@ -23,7 +23,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|||
//************************************************
|
||||
#include "globals.h"
|
||||
#include "speeduino.h"
|
||||
#include "table.h"
|
||||
#include "scheduler.h"
|
||||
#include "comms.h"
|
||||
#include "cancomms.h"
|
||||
|
|
|
@ -8,10 +8,8 @@ A full copy of the license may be found in the projects root directory
|
|||
*/
|
||||
|
||||
#include "globals.h"
|
||||
#include "table.h"
|
||||
#include EEPROM_LIB_H //This is defined in the board .h files
|
||||
#include "storage.h"
|
||||
#include "table_iterator.h"
|
||||
#include "pages.h"
|
||||
|
||||
//The maximum number of write operations that will be performed in one go. If we try to write to the EEPROM too fast (Each write takes ~3ms) then the rest of the system can hang)
|
||||
|
@ -56,66 +54,78 @@ void writeAllConfig()
|
|||
|
||||
// ================================= Internal write support ===============================
|
||||
|
||||
typedef struct write_location {
|
||||
struct write_location {
|
||||
eeprom_address_t address;
|
||||
uint8_t counter;
|
||||
} write_location;
|
||||
|
||||
/** Update byte to EEPROM by first comparing content and the need to write it.
|
||||
We only ever write to the EEPROM where the new value is different from the currently stored byte
|
||||
This is due to the limited write life of the EEPROM (Approximately 100,000 writes)
|
||||
*/
|
||||
static inline write_location update(uint8_t value, write_location location)
|
||||
{
|
||||
if (EEPROM.read(location.address)!=value)
|
||||
/** Update byte to EEPROM by first comparing content and the need to write it.
|
||||
We only ever write to the EEPROM where the new value is different from the currently stored byte
|
||||
This is due to the limited write life of the EEPROM (Approximately 100,000 writes)
|
||||
*/
|
||||
void update(uint8_t value)
|
||||
{
|
||||
EEPROM.write(location.address, value);
|
||||
++location.counter;
|
||||
if (EEPROM.read(address)!=value)
|
||||
{
|
||||
EEPROM.write(address, value);
|
||||
++counter;
|
||||
}
|
||||
}
|
||||
return location;
|
||||
}
|
||||
|
||||
write_location& operator++()
|
||||
{
|
||||
++address;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool can_write() const
|
||||
{
|
||||
return counter<=EEPROM_MAX_WRITE_BLOCK;
|
||||
}
|
||||
};
|
||||
|
||||
static inline write_location write_range(const byte *pStart, const byte *pEnd, write_location location)
|
||||
{
|
||||
while (location.counter<=EEPROM_MAX_WRITE_BLOCK && pStart!=pEnd)
|
||||
while (location.can_write() && pStart!=pEnd)
|
||||
{
|
||||
location = update(*pStart, location);
|
||||
++pStart; ++location.address;
|
||||
location.update(*pStart);
|
||||
++pStart;
|
||||
++location;
|
||||
}
|
||||
return location;
|
||||
}
|
||||
|
||||
static inline write_location write(const table_row_t &row, write_location location)
|
||||
static inline write_location write(const table_row_iterator &row, write_location location)
|
||||
{
|
||||
return write_range(row.pValue, row.pEnd, location);
|
||||
return write_range(&*row, row.end(), location);
|
||||
}
|
||||
|
||||
static inline write_location write(table_row_iterator_t it, write_location location)
|
||||
static inline write_location write(table_value_iterator it, write_location location)
|
||||
{
|
||||
while ((location.counter<=EEPROM_MAX_WRITE_BLOCK) && !at_end(it))
|
||||
while (location.can_write() && !it.at_end())
|
||||
{
|
||||
location = write(get_row(it), location);
|
||||
it = advance_row(it);
|
||||
location = write(*it, location);
|
||||
++it;
|
||||
}
|
||||
return location;
|
||||
}
|
||||
|
||||
static inline write_location write(table_axis_iterator_t it, write_location location)
|
||||
static inline write_location write(table_axis_iterator it, write_location location)
|
||||
{
|
||||
while ((location.counter<=EEPROM_MAX_WRITE_BLOCK) && !at_end(it))
|
||||
while (location.can_write() && !it.at_end())
|
||||
{
|
||||
location = update(get_value(it), location);
|
||||
++location.address;
|
||||
it = advance_axis(it);
|
||||
location.update((byte)*it);
|
||||
++location;
|
||||
++it;
|
||||
}
|
||||
return location;
|
||||
}
|
||||
|
||||
static inline write_location writeTable(const table3D *pTable, write_location location)
|
||||
|
||||
static inline write_location writeTable(const void *pTable, table_type_t key, write_location location)
|
||||
{
|
||||
return write(y_rbegin(pTable),
|
||||
write(x_begin(pTable),
|
||||
write(rows_begin(pTable), location)));
|
||||
return write(y_begin(pTable, key).reverse(),
|
||||
write(x_begin(pTable, key),
|
||||
write(rows_begin(pTable, key), location)));
|
||||
}
|
||||
|
||||
// ================================= End write support ===============================
|
||||
|
@ -135,7 +145,7 @@ void writeConfig(uint8_t pageNum)
|
|||
| Fuel table (See storage.h for data layout) - Page 1
|
||||
| 16x16 table itself + the 16 values along each of the axis
|
||||
-----------------------------------------------------*/
|
||||
result = writeTable(&fuelTable, { EEPROM_CONFIG1_MAP, 0 } );
|
||||
result = writeTable(&fuelTable, fuelTable.type_key, { EEPROM_CONFIG1_MAP, 0 });
|
||||
break;
|
||||
|
||||
case veSetPage:
|
||||
|
@ -151,7 +161,7 @@ void writeConfig(uint8_t pageNum)
|
|||
| Ignition table (See storage.h for data layout) - Page 1
|
||||
| 16x16 table itself + the 16 values along each of the axis
|
||||
-----------------------------------------------------*/
|
||||
result = writeTable(&ignitionTable, { EEPROM_CONFIG3_MAP, 0 });
|
||||
result = writeTable(&ignitionTable, ignitionTable.type_key, { EEPROM_CONFIG3_MAP, 0 });
|
||||
break;
|
||||
|
||||
case ignSetPage:
|
||||
|
@ -167,7 +177,7 @@ void writeConfig(uint8_t pageNum)
|
|||
| AFR table (See storage.h for data layout) - Page 5
|
||||
| 16x16 table itself + the 16 values along each of the axis
|
||||
-----------------------------------------------------*/
|
||||
result = writeTable(&afrTable, {EEPROM_CONFIG5_MAP, 0} );
|
||||
result = writeTable(&afrTable, afrTable.type_key, { EEPROM_CONFIG5_MAP, 0 });
|
||||
break;
|
||||
|
||||
case afrSetPage:
|
||||
|
@ -183,9 +193,9 @@ void writeConfig(uint8_t pageNum)
|
|||
| Boost and vvt tables (See storage.h for data layout) - Page 8
|
||||
| 8x8 table itself + the 8 values along each of the axis
|
||||
-----------------------------------------------------*/
|
||||
result = writeTable(&boostTable, { EEPROM_CONFIG7_MAP1, 0 });
|
||||
result = writeTable(&vvtTable, { EEPROM_CONFIG7_MAP2, result.counter });
|
||||
result = writeTable(&stagingTable, { EEPROM_CONFIG7_MAP3, result.counter });
|
||||
result = writeTable(&boostTable, boostTable.type_key, { EEPROM_CONFIG7_MAP1, 0 });
|
||||
result = writeTable(&vvtTable, vvtTable.type_key, { EEPROM_CONFIG7_MAP2, result.counter });
|
||||
result = writeTable(&stagingTable, stagingTable.type_key, { EEPROM_CONFIG7_MAP3, result.counter });
|
||||
break;
|
||||
|
||||
case seqFuelPage:
|
||||
|
@ -193,14 +203,14 @@ void writeConfig(uint8_t pageNum)
|
|||
| Fuel trim tables (See storage.h for data layout) - Page 9
|
||||
| 6x6 tables itself + the 6 values along each of the axis
|
||||
-----------------------------------------------------*/
|
||||
result = writeTable(&trim1Table, { EEPROM_CONFIG8_MAP1, 0} );
|
||||
result = writeTable(&trim2Table, { EEPROM_CONFIG8_MAP2, result.counter});
|
||||
result = writeTable(&trim3Table, { EEPROM_CONFIG8_MAP3, result.counter});
|
||||
result = writeTable(&trim4Table, { EEPROM_CONFIG8_MAP4, result.counter});
|
||||
result = writeTable(&trim5Table, { EEPROM_CONFIG8_MAP5, result.counter});
|
||||
result = writeTable(&trim6Table, { EEPROM_CONFIG8_MAP6, result.counter});
|
||||
result = writeTable(&trim7Table, { EEPROM_CONFIG8_MAP7, result.counter});
|
||||
result = writeTable(&trim8Table, { EEPROM_CONFIG8_MAP8, result.counter});
|
||||
result = writeTable(&trim1Table, trim1Table.type_key, { EEPROM_CONFIG8_MAP1, 0 });
|
||||
result = writeTable(&trim2Table, trim2Table.type_key, { EEPROM_CONFIG8_MAP2, result.counter });
|
||||
result = writeTable(&trim3Table, trim3Table.type_key, { EEPROM_CONFIG8_MAP3, result.counter });
|
||||
result = writeTable(&trim4Table, trim4Table.type_key, { EEPROM_CONFIG8_MAP4, result.counter });
|
||||
result = writeTable(&trim5Table, trim5Table.type_key, { EEPROM_CONFIG8_MAP5, result.counter });
|
||||
result = writeTable(&trim6Table, trim6Table.type_key, { EEPROM_CONFIG8_MAP6, result.counter });
|
||||
result = writeTable(&trim7Table, trim7Table.type_key, { EEPROM_CONFIG8_MAP7, result.counter });
|
||||
result = writeTable(&trim8Table, trim8Table.type_key, { EEPROM_CONFIG8_MAP8, result.counter });
|
||||
break;
|
||||
|
||||
case canbusPage:
|
||||
|
@ -224,7 +234,7 @@ void writeConfig(uint8_t pageNum)
|
|||
| Fuel table 2 (See storage.h for data layout)
|
||||
| 16x16 table itself + the 16 values along each of the axis
|
||||
-----------------------------------------------------*/
|
||||
result = writeTable(&fuelTable2, { EEPROM_CONFIG11_MAP, 0 });
|
||||
result = writeTable(&fuelTable2, fuelTable2.type_key, { EEPROM_CONFIG11_MAP, 0 });
|
||||
break;
|
||||
|
||||
case wmiMapPage:
|
||||
|
@ -234,9 +244,9 @@ void writeConfig(uint8_t pageNum)
|
|||
| 8x8 VVT2 table + the 8 values along each of the axis
|
||||
| 4x4 Dwell table itself + the 4 values along each of the axis
|
||||
-----------------------------------------------------*/
|
||||
result = writeTable(&wmiTable, { EEPROM_CONFIG12_MAP, 0 });
|
||||
result = writeTable(&vvt2Table, { EEPROM_CONFIG12_MAP2, result.counter});
|
||||
result = writeTable(&dwellTable, { EEPROM_CONFIG12_MAP3, result.counter});
|
||||
result = writeTable(&wmiTable, wmiTable.type_key, { EEPROM_CONFIG12_MAP, 0 });
|
||||
result = writeTable(&vvt2Table, vvt2Table.type_key, { EEPROM_CONFIG12_MAP2, result.counter });
|
||||
result = writeTable(&dwellTable, dwellTable.type_key, { EEPROM_CONFIG12_MAP3, result.counter });
|
||||
break;
|
||||
|
||||
case progOutsPage:
|
||||
|
@ -251,14 +261,14 @@ void writeConfig(uint8_t pageNum)
|
|||
| Ignition table (See storage.h for data layout) - Page 1
|
||||
| 16x16 table itself + the 16 values along each of the axis
|
||||
-----------------------------------------------------*/
|
||||
result = writeTable(&ignitionTable2, { EEPROM_CONFIG14_MAP, 0});
|
||||
result = writeTable(&ignitionTable2, ignitionTable2.type_key, { EEPROM_CONFIG14_MAP, 0 });
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
eepromWritesPending = result.counter > EEPROM_MAX_WRITE_BLOCK;
|
||||
eepromWritesPending = !result.can_write();
|
||||
}
|
||||
|
||||
/** Reset all configPage* structs (2,4,6,9,10,13) and write them full of null-bytes.
|
||||
|
@ -295,39 +305,39 @@ static inline eeprom_address_t load_range(eeprom_address_t address, byte *pFirst
|
|||
return address;
|
||||
}
|
||||
|
||||
static inline eeprom_address_t load(table_row_t row, eeprom_address_t address)
|
||||
static inline eeprom_address_t load(table_row_iterator row, eeprom_address_t address)
|
||||
{
|
||||
return load_range(address, row.pValue, row.pEnd);
|
||||
return load_range(address, &*row, row.end());
|
||||
}
|
||||
|
||||
static inline eeprom_address_t load(table_row_iterator_t it, eeprom_address_t address)
|
||||
static inline eeprom_address_t load(table_value_iterator it, eeprom_address_t address)
|
||||
{
|
||||
while (!at_end(it))
|
||||
while (!it.at_end())
|
||||
{
|
||||
address = load(get_row(it), address);
|
||||
it = advance_row(it);
|
||||
address = load(*it, address);
|
||||
++it;
|
||||
}
|
||||
return address;
|
||||
}
|
||||
|
||||
static inline eeprom_address_t load(table_axis_iterator_t it, eeprom_address_t address)
|
||||
static inline eeprom_address_t load(table_axis_iterator it, eeprom_address_t address)
|
||||
{
|
||||
while (!at_end(it))
|
||||
while (!it.at_end())
|
||||
{
|
||||
set_value(it, EEPROM.read(address));
|
||||
*it = EEPROM.read(address);
|
||||
++address;
|
||||
it = advance_axis(it);
|
||||
++it;
|
||||
}
|
||||
return address;
|
||||
}
|
||||
|
||||
static inline eeprom_address_t loadTable(table3D *pTable, eeprom_address_t address)
|
||||
{
|
||||
return load(y_rbegin(pTable),
|
||||
load(x_begin(pTable),
|
||||
load(rows_begin(pTable), address)));
|
||||
}
|
||||
|
||||
static inline eeprom_address_t loadTable(void *pTable, table_type_t key, eeprom_address_t address)
|
||||
{
|
||||
return load(y_begin(pTable, key).reverse(),
|
||||
load(x_begin(pTable, key),
|
||||
load(rows_begin(pTable, key), address)));
|
||||
}
|
||||
|
||||
// ================================= End internal read support ===============================
|
||||
|
||||
|
@ -336,37 +346,37 @@ static inline eeprom_address_t loadTable(table3D *pTable, eeprom_address_t addre
|
|||
*/
|
||||
void loadConfig()
|
||||
{
|
||||
loadTable(&fuelTable, EEPROM_CONFIG1_MAP);
|
||||
loadTable(&fuelTable, fuelTable.type_key, EEPROM_CONFIG1_MAP);
|
||||
load_range(EEPROM_CONFIG2_START, (byte *)&configPage2, (byte *)&configPage2+sizeof(configPage2));
|
||||
|
||||
//*********************************************************************************************************************************************************************************
|
||||
//IGNITION CONFIG PAGE (2)
|
||||
|
||||
loadTable(&ignitionTable, EEPROM_CONFIG3_MAP);
|
||||
loadTable(&ignitionTable, ignitionTable.type_key, EEPROM_CONFIG3_MAP);
|
||||
load_range(EEPROM_CONFIG4_START, (byte *)&configPage4, (byte *)&configPage4+sizeof(configPage4));
|
||||
|
||||
//*********************************************************************************************************************************************************************************
|
||||
//AFR TARGET CONFIG PAGE (3)
|
||||
|
||||
loadTable(&afrTable, EEPROM_CONFIG5_MAP);
|
||||
loadTable(&afrTable, afrTable.type_key, EEPROM_CONFIG5_MAP);
|
||||
load_range(EEPROM_CONFIG6_START, (byte *)&configPage6, (byte *)&configPage6+sizeof(configPage6));
|
||||
|
||||
//*********************************************************************************************************************************************************************************
|
||||
// Boost and vvt tables load
|
||||
loadTable(&boostTable, EEPROM_CONFIG7_MAP1);
|
||||
loadTable(&vvtTable, EEPROM_CONFIG7_MAP2);
|
||||
loadTable(&stagingTable, EEPROM_CONFIG7_MAP3);
|
||||
loadTable(&boostTable, boostTable.type_key, EEPROM_CONFIG7_MAP1);
|
||||
loadTable(&vvtTable, vvtTable.type_key, EEPROM_CONFIG7_MAP2);
|
||||
loadTable(&stagingTable, stagingTable.type_key, EEPROM_CONFIG7_MAP3);
|
||||
|
||||
//*********************************************************************************************************************************************************************************
|
||||
// Fuel trim tables load
|
||||
loadTable(&trim1Table, EEPROM_CONFIG8_MAP1);
|
||||
loadTable(&trim2Table, EEPROM_CONFIG8_MAP2);
|
||||
loadTable(&trim3Table, EEPROM_CONFIG8_MAP3);
|
||||
loadTable(&trim4Table, EEPROM_CONFIG8_MAP4);
|
||||
loadTable(&trim5Table, EEPROM_CONFIG8_MAP5);
|
||||
loadTable(&trim6Table, EEPROM_CONFIG8_MAP6);
|
||||
loadTable(&trim7Table, EEPROM_CONFIG8_MAP7);
|
||||
loadTable(&trim8Table, EEPROM_CONFIG8_MAP8);
|
||||
loadTable(&trim1Table, trim1Table.type_key, EEPROM_CONFIG8_MAP1);
|
||||
loadTable(&trim2Table, trim2Table.type_key, EEPROM_CONFIG8_MAP2);
|
||||
loadTable(&trim3Table, trim3Table.type_key, EEPROM_CONFIG8_MAP3);
|
||||
loadTable(&trim4Table, trim4Table.type_key, EEPROM_CONFIG8_MAP4);
|
||||
loadTable(&trim5Table, trim5Table.type_key, EEPROM_CONFIG8_MAP5);
|
||||
loadTable(&trim6Table, trim6Table.type_key, EEPROM_CONFIG8_MAP6);
|
||||
loadTable(&trim7Table, trim7Table.type_key, EEPROM_CONFIG8_MAP7);
|
||||
loadTable(&trim8Table, trim8Table.type_key, EEPROM_CONFIG8_MAP8);
|
||||
|
||||
//*********************************************************************************************************************************************************************************
|
||||
//canbus control page load
|
||||
|
@ -379,13 +389,13 @@ void loadConfig()
|
|||
|
||||
//*********************************************************************************************************************************************************************************
|
||||
//Fuel table 2 (See storage.h for data layout)
|
||||
loadTable(&fuelTable2, EEPROM_CONFIG11_MAP);
|
||||
loadTable(&fuelTable2, fuelTable2.type_key, EEPROM_CONFIG11_MAP);
|
||||
|
||||
//*********************************************************************************************************************************************************************************
|
||||
// WMI, VVT2 and Dwell table load
|
||||
loadTable(&wmiTable, EEPROM_CONFIG12_MAP);
|
||||
loadTable(&vvt2Table, EEPROM_CONFIG12_MAP2);
|
||||
loadTable(&dwellTable, EEPROM_CONFIG12_MAP3);
|
||||
loadTable(&wmiTable, wmiTable.type_key, EEPROM_CONFIG12_MAP);
|
||||
loadTable(&vvt2Table, vvt2Table.type_key, EEPROM_CONFIG12_MAP2);
|
||||
loadTable(&dwellTable, dwellTable.type_key, EEPROM_CONFIG12_MAP3);
|
||||
|
||||
//*********************************************************************************************************************************************************************************
|
||||
//CONFIG PAGE (13)
|
||||
|
@ -394,7 +404,7 @@ void loadConfig()
|
|||
//*********************************************************************************************************************************************************************************
|
||||
//SECOND IGNITION CONFIG PAGE (14)
|
||||
|
||||
loadTable(&ignitionTable2, EEPROM_CONFIG14_MAP);
|
||||
loadTable(&ignitionTable2, ignitionTable2.type_key, EEPROM_CONFIG14_MAP);
|
||||
|
||||
//*********************************************************************************************************************************************************************************
|
||||
}
|
||||
|
|
|
@ -1,120 +0,0 @@
|
|||
/*
|
||||
This file is used for everything related to maps/tables including their definition, functions etc
|
||||
*/
|
||||
#ifndef TABLE_H
|
||||
#define TABLE_H
|
||||
|
||||
#define TABLE_RPM_MULTIPLIER 100
|
||||
#define TABLE_LOAD_MULTIPLIER 2
|
||||
|
||||
//The shift amount used for the 3D table calculations
|
||||
#define TABLE_SHIFT_FACTOR 8
|
||||
#define TABLE_SHIFT_POWER (1UL<<TABLE_SHIFT_FACTOR)
|
||||
|
||||
//Define the total table memory sizes. Used for adding up the static heap size
|
||||
#define TABLE3D_SIZE_16 (16 * 16 + 32 + 32 + (16 * sizeof(byte*))) //2 bytes for each value on the axis + allocation for array pointers
|
||||
#define TABLE3D_SIZE_12 (12 * 12 + 24 + 24 + (12 * sizeof(byte*))) //2 bytes for each value on the axis + allocation for array pointers
|
||||
#define TABLE3D_SIZE_8 (8 * 8 + 16 + 16 + (8 * sizeof(byte*))) //2 bytes for each value on the axis + allocation for array pointers
|
||||
#define TABLE3D_SIZE_6 (6 * 6 + 12 + 12 + (6 * sizeof(byte*))) //2 bytes for each value on the axis + allocation for array pointers
|
||||
#define TABLE3D_SIZE_4 (4 * 4 + 8 + 8 + (4 * sizeof(byte*))) //2 bytes for each value on the axis + allocation for array pointers
|
||||
|
||||
//Define the table sizes
|
||||
#define TABLE_FUEL1_SIZE 16;
|
||||
#define TABLE_FUEL2_SIZE 16;
|
||||
#define TABLE_IGN1_SIZE 16;
|
||||
#define TABLE_IGN2_SIZE 16;
|
||||
#define TABLE_AFR_SIZE 16;
|
||||
#define TABLE_STAGING_SIZE 8;
|
||||
#define TABLE_BOOST_SIZE 8;
|
||||
#define TABLE_VVT1_SIZE 8;
|
||||
#define TABLE_VVT2_SIZE 8;
|
||||
#define TABLE_WMI_SIZE 8;
|
||||
#define TABLE_TRIM1_SIZE 6;
|
||||
#define TABLE_TRIM2_SIZE 6;
|
||||
#define TABLE_TRIM3_SIZE 6;
|
||||
#define TABLE_TRIM4_SIZE 6;
|
||||
#define TABLE_TRIM5_SIZE 6;
|
||||
#define TABLE_TRIM6_SIZE 6;
|
||||
#define TABLE_TRIM7_SIZE 6;
|
||||
#define TABLE_TRIM8_SIZE 6;
|
||||
#define TABLE_DWELL_SIZE 4;
|
||||
|
||||
/*
|
||||
*********** WARNING! ***********
|
||||
YOU MUST UPDATE THE TABLE COUNTS IN THE LINE BELOW WHENEVER A NEW TABLE IS ADDED!
|
||||
*/
|
||||
#define TABLE_HEAP_SIZE ((5 * TABLE3D_SIZE_16) + (5 * TABLE3D_SIZE_8) + (8 * TABLE3D_SIZE_6) + (1 * TABLE3D_SIZE_4) + 1)
|
||||
|
||||
|
||||
/*
|
||||
The 2D table can contain either 8-bit (byte) or 16-bit (int) values
|
||||
The valueSize variable should be set to either 8 or 16 to indicate this BEFORE the table is used
|
||||
*/
|
||||
struct table2D {
|
||||
//Used 5414 RAM with original version
|
||||
byte valueSize;
|
||||
byte axisSize;
|
||||
byte xSize;
|
||||
|
||||
void *values;
|
||||
void *axisX;
|
||||
|
||||
//int16_t *values16;
|
||||
//int16_t *axisX16;
|
||||
|
||||
//Store the last X and Y coordinates in the table. This is used to make the next check faster
|
||||
int16_t lastXMax;
|
||||
int16_t lastXMin;
|
||||
|
||||
//Store the last input and output for caching
|
||||
int16_t lastInput;
|
||||
int16_t lastOutput;
|
||||
byte cacheTime; //Tracks when the last cache value was set so it can expire after x seconds. A timeout is required to pickup when a tuning value is changed, otherwise the old cached value will continue to be returned as the X value isn't changing.
|
||||
};
|
||||
|
||||
//void table2D_setSize(struct table2D targetTable, byte newSize);
|
||||
void table2D_setSize(struct table2D*, byte);
|
||||
int16_t table2D_getAxisValue(struct table2D*, byte);
|
||||
int16_t table2D_getRawValue(struct table2D*, byte);
|
||||
|
||||
struct table3D {
|
||||
|
||||
//All tables must be the same size for simplicity
|
||||
|
||||
byte xSize;
|
||||
byte ySize;
|
||||
|
||||
byte **values;
|
||||
int16_t *axisX;
|
||||
int16_t *axisY;
|
||||
|
||||
//Store the last X and Y coordinates in the table. This is used to make the next check faster
|
||||
byte lastXMax, lastXMin;
|
||||
byte lastYMax, lastYMin;
|
||||
|
||||
//Store the last input and output values, again for caching purposes
|
||||
int16_t lastXInput, lastYInput;
|
||||
byte lastOutput; //This will need changing if we ever have 16-bit table values
|
||||
bool cacheIsValid; ///< This tracks whether the tables cache should be used. Ordinarily this is true, but is set to false whenever TunerStudio sends a new value for the table
|
||||
};
|
||||
|
||||
//void table3D_setSize(struct table3D *targetTable, byte);
|
||||
void table3D_setSize(struct table3D *targetTable, byte);
|
||||
|
||||
/*
|
||||
3D Tables have an origin (0,0) in the top left hand corner. Vertical axis is expressed first.
|
||||
Eg: 2x2 table
|
||||
-----
|
||||
|2 7|
|
||||
|1 4|
|
||||
-----
|
||||
|
||||
(0,1) = 7
|
||||
(0,0) = 2
|
||||
(1,0) = 1
|
||||
|
||||
*/
|
||||
int get3DTableValue(struct table3D *fromTable, int, int);
|
||||
int table2D_getValue(struct table2D *fromTable, int);
|
||||
|
||||
#endif // TABLE_H
|
|
@ -1,427 +0,0 @@
|
|||
/*
|
||||
Speeduino - Simple engine management for the Arduino Mega 2560 platform
|
||||
Copyright (C) Josh Stewart
|
||||
A full copy of the license may be found in the projects root directory
|
||||
*/
|
||||
|
||||
/*
|
||||
Because the size of the table is dynamic, this functino is required to reallocate the array sizes
|
||||
Note that this may clear some of the existing values of the table
|
||||
*/
|
||||
#include "table.h"
|
||||
#include "globals.h"
|
||||
|
||||
/*
|
||||
void table2D_setSize(struct table2D* targetTable, byte newSize)
|
||||
{
|
||||
//Table resize is ONLY permitted during system initialisation.
|
||||
//if(initialisationComplete == false)
|
||||
{
|
||||
//2D tables can contain either bytes or ints, depending on the value of the valueSize field
|
||||
if(targetTable->valueSize == SIZE_BYTE)
|
||||
{
|
||||
//The following lines have MISRA suppressions as realloc is otherwise forbidden. These calls have been verified as unable to be executed from anywhere but controlled areas.
|
||||
//cppcheck-suppress misra-21.3
|
||||
targetTable->values = (byte *)realloc(targetTable->values, newSize * sizeof(byte)); //cppcheck-suppress misra_21.3
|
||||
targetTable->axisX = (byte *)realloc(targetTable->axisX, newSize * sizeof(byte));
|
||||
targetTable->xSize = newSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
targetTable->values16 = (int16_t *)realloc(targetTable->values16, newSize * sizeof(int16_t));
|
||||
targetTable->axisX16 = (int16_t *)realloc(targetTable->axisX16, newSize * sizeof(int16_t));
|
||||
targetTable->xSize = newSize;
|
||||
} //Byte or int
|
||||
} //initialisationComplete
|
||||
}
|
||||
*/
|
||||
|
||||
static uint8_t _3DTable_heap[TABLE_HEAP_SIZE];
|
||||
static uint16_t _heap_pointer = 0;
|
||||
|
||||
void* heap_alloc(uint16_t size)
|
||||
{
|
||||
uint8_t* value = nullptr;
|
||||
if (size < (TABLE_HEAP_SIZE - _heap_pointer))
|
||||
{
|
||||
value = &_3DTable_heap[_heap_pointer];
|
||||
_heap_pointer += size;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
void table3D_setSize(struct table3D *targetTable, byte newSize)
|
||||
{
|
||||
if(initialisationComplete == false)
|
||||
{
|
||||
/*
|
||||
targetTable->values = (byte **)malloc(newSize * sizeof(byte*));
|
||||
for(byte i = 0; i < newSize; i++) { targetTable->values[i] = (byte *)malloc(newSize * sizeof(byte)); }
|
||||
*/
|
||||
targetTable->values = (byte **)heap_alloc(newSize * sizeof(byte*));
|
||||
for(byte i = 0; i < newSize; i++) { targetTable->values[i] = (byte *)heap_alloc(newSize * sizeof(byte)); }
|
||||
|
||||
/*
|
||||
targetTable->axisX = (int16_t *)malloc(newSize * sizeof(int16_t));
|
||||
targetTable->axisY = (int16_t *)malloc(newSize * sizeof(int16_t));
|
||||
*/
|
||||
targetTable->axisX = (int16_t *)heap_alloc(newSize * sizeof(int16_t));
|
||||
targetTable->axisY = (int16_t *)heap_alloc(newSize * sizeof(int16_t));
|
||||
targetTable->xSize = newSize;
|
||||
targetTable->ySize = newSize;
|
||||
targetTable->cacheIsValid = false; //Invalid the tables cache to ensure a lookup of new values
|
||||
} //initialisationComplete
|
||||
}
|
||||
|
||||
/*
|
||||
This function pulls a 1D linear interpolated (ie averaged) value from a 2D table
|
||||
ie: Given a value on the X axis, it returns a Y value that coresponds to the point on the curve between the nearest two defined X values
|
||||
|
||||
This function must take into account whether a table contains 8-bit or 16-bit values.
|
||||
Unfortunately this means many of the lines are duplicated depending on this
|
||||
*/
|
||||
int table2D_getValue(struct table2D *fromTable, int X_in)
|
||||
{
|
||||
//Orig memory usage = 5414
|
||||
int returnValue = 0;
|
||||
bool valueFound = false;
|
||||
|
||||
int X = X_in;
|
||||
int xMinValue, xMaxValue;
|
||||
int xMin = 0;
|
||||
int xMax = fromTable->xSize-1;
|
||||
|
||||
//Check whether the X input is the same as last time this ran
|
||||
if( (X_in == fromTable->lastInput) && (fromTable->cacheTime == currentStatus.secl) )
|
||||
{
|
||||
returnValue = fromTable->lastOutput;
|
||||
valueFound = true;
|
||||
}
|
||||
//If the requested X value is greater/small than the maximum/minimum bin, simply return that value
|
||||
else if(X >= table2D_getAxisValue(fromTable, xMax))
|
||||
{
|
||||
returnValue = table2D_getRawValue(fromTable, xMax);
|
||||
valueFound = true;
|
||||
}
|
||||
else if(X <= table2D_getAxisValue(fromTable, xMin))
|
||||
{
|
||||
returnValue = table2D_getRawValue(fromTable, xMin);
|
||||
valueFound = true;
|
||||
}
|
||||
//Finally if none of that is found
|
||||
else
|
||||
{
|
||||
fromTable->cacheTime = currentStatus.secl; //As we're not using the cache value, set the current secl value to track when this new value was calc'd
|
||||
|
||||
//1st check is whether we're still in the same X bin as last time
|
||||
xMaxValue = table2D_getAxisValue(fromTable, fromTable->lastXMax);
|
||||
xMinValue = table2D_getAxisValue(fromTable, fromTable->lastXMin);
|
||||
if ( (X <= xMaxValue) && (X > xMinValue) )
|
||||
{
|
||||
xMax = fromTable->lastXMax;
|
||||
xMin = fromTable->lastXMin;
|
||||
}
|
||||
else
|
||||
{
|
||||
//If we're not in the same bin, loop through to find where we are
|
||||
xMaxValue = table2D_getAxisValue(fromTable, fromTable->xSize-1); // init xMaxValue in preparation for loop.
|
||||
for (int x = fromTable->xSize-1; x > 0; x--)
|
||||
{
|
||||
xMinValue = table2D_getAxisValue(fromTable, x-1); // fetch next Min
|
||||
|
||||
//Checks the case where the X value is exactly what was requested
|
||||
if (X == xMaxValue)
|
||||
{
|
||||
returnValue = table2D_getRawValue(fromTable, x); //Simply return the coresponding value
|
||||
valueFound = true;
|
||||
break;
|
||||
}
|
||||
else if (X > xMinValue)
|
||||
{
|
||||
// Value is in the current bin
|
||||
xMax = x;
|
||||
fromTable->lastXMax = xMax;
|
||||
xMin = x-1;
|
||||
fromTable->lastXMin = xMin;
|
||||
break;
|
||||
}
|
||||
// Otherwise, continue to next bin
|
||||
xMaxValue = xMinValue; // for the next bin, our Min is their Max
|
||||
}
|
||||
}
|
||||
} //X_in same as last time
|
||||
|
||||
if (valueFound == false)
|
||||
{
|
||||
int16_t m = X - xMinValue;
|
||||
int16_t n = xMaxValue - xMinValue;
|
||||
|
||||
int16_t yMax = table2D_getRawValue(fromTable, xMax);
|
||||
int16_t yMin = table2D_getRawValue(fromTable, xMin);
|
||||
|
||||
/* Float version (if m, yMax, yMin and n were float's)
|
||||
int yVal = (m * (yMax - yMin)) / n;
|
||||
*/
|
||||
|
||||
//Non-Float version
|
||||
int16_t yVal = ( ((int32_t) m) * (yMax-yMin) ) / n;
|
||||
returnValue = yMin + yVal;
|
||||
}
|
||||
|
||||
fromTable->lastInput = X_in;
|
||||
fromTable->lastOutput = returnValue;
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an axis (bin) value from the 2D table. This works regardless of whether that axis is bytes or int16_ts
|
||||
*
|
||||
* @param fromTable
|
||||
* @param X_index
|
||||
* @return int16_t
|
||||
*/
|
||||
int16_t table2D_getAxisValue(struct table2D *fromTable, byte X_index)
|
||||
{
|
||||
int returnValue = 0;
|
||||
|
||||
if(fromTable->axisSize == SIZE_INT) { returnValue = ((int16_t*)fromTable->axisX)[X_index]; }
|
||||
else if(fromTable->axisSize == SIZE_BYTE) { returnValue = ((uint8_t*)fromTable->axisX)[X_index]; }
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an value from the 2D table given an index value. No interpolation is performed
|
||||
*
|
||||
* @param fromTable
|
||||
* @param X_index
|
||||
* @return int16_t
|
||||
*/
|
||||
int16_t table2D_getRawValue(struct table2D *fromTable, byte X_index)
|
||||
{
|
||||
int returnValue = 0;
|
||||
|
||||
if(fromTable->valueSize == SIZE_INT) { returnValue = ((int16_t*)fromTable->values)[X_index]; }
|
||||
else if(fromTable->valueSize == SIZE_BYTE) { returnValue = ((uint8_t*)fromTable->values)[X_index]; }
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
|
||||
//This function pulls a value from a 3D table given a target for X and Y coordinates.
|
||||
//It performs a 2D linear interpolation as descibred in: www.megamanual.com/v22manual/ve_tuner.pdf
|
||||
int get3DTableValue(struct table3D *fromTable, int Y_in, int X_in)
|
||||
{
|
||||
int X = X_in;
|
||||
int Y = Y_in;
|
||||
|
||||
int tableResult = 0;
|
||||
//Loop through the X axis bins for the min/max pair
|
||||
//Note: For the X axis specifically, rather than looping from tableAxisX[0] up to tableAxisX[max], we start at tableAxisX[Max] and go down.
|
||||
// This is because the important tables (fuel and injection) will have the highest RPM at the top of the X axis, so starting there will mean the best case occurs when the RPM is highest (And hence the CPU is needed most)
|
||||
int xMinValue = fromTable->axisX[0];
|
||||
int xMaxValue = fromTable->axisX[fromTable->xSize-1];
|
||||
byte xMin = 0;
|
||||
byte xMax = 0;
|
||||
|
||||
//If the requested X value is greater/small than the maximum/minimum bin, reset X to be that value
|
||||
if(X > xMaxValue) { X = xMaxValue; }
|
||||
if(X < xMinValue) { X = xMinValue; }
|
||||
|
||||
//0th check is whether the same X and Y values are being sent as last time. If they are, this not only prevents a lookup of the axis, but prevents the interpolation calcs being performed
|
||||
if( (X_in == fromTable->lastXInput) && (Y_in == fromTable->lastYInput) && (fromTable->cacheIsValid == true))
|
||||
{
|
||||
return fromTable->lastOutput;
|
||||
}
|
||||
|
||||
//Commence the lookups on the X and Y axis
|
||||
|
||||
//1st check is whether we're still in the same X bin as last time
|
||||
if ( (X <= fromTable->axisX[fromTable->lastXMax]) && (X > fromTable->axisX[fromTable->lastXMin]) )
|
||||
{
|
||||
xMaxValue = fromTable->axisX[fromTable->lastXMax];
|
||||
xMinValue = fromTable->axisX[fromTable->lastXMin];
|
||||
xMax = fromTable->lastXMax;
|
||||
xMin = fromTable->lastXMin;
|
||||
}
|
||||
//2nd check is whether we're in the next RPM bin (To the right)
|
||||
else if ( ((fromTable->lastXMax + 1) < fromTable->xSize ) && (X <= fromTable->axisX[fromTable->lastXMax +1 ]) && (X > fromTable->axisX[fromTable->lastXMin + 1]) ) //First make sure we're not already at the last X bin
|
||||
{
|
||||
xMax = fromTable->lastXMax + 1;
|
||||
fromTable->lastXMax = xMax;
|
||||
xMin = fromTable->lastXMin + 1;
|
||||
fromTable->lastXMin = xMin;
|
||||
xMaxValue = fromTable->axisX[fromTable->lastXMax];
|
||||
xMinValue = fromTable->axisX[fromTable->lastXMin];
|
||||
}
|
||||
//3rd check is to look at the previous bin (to the left)
|
||||
else if ( (fromTable->lastXMin > 0 ) && (X <= fromTable->axisX[fromTable->lastXMax - 1]) && (X > fromTable->axisX[fromTable->lastXMin - 1]) ) //First make sure we're not already at the first X bin
|
||||
{
|
||||
xMax = fromTable->lastXMax - 1;
|
||||
fromTable->lastXMax = xMax;
|
||||
xMin = fromTable->lastXMin - 1;
|
||||
fromTable->lastXMin = xMin;
|
||||
xMaxValue = fromTable->axisX[fromTable->lastXMax];
|
||||
xMinValue = fromTable->axisX[fromTable->lastXMin];
|
||||
}
|
||||
else
|
||||
//If it's not caught by one of the above scenarios, give up and just run the loop
|
||||
{
|
||||
for (int8_t x = fromTable->xSize-1; x >= 0; x--)
|
||||
{
|
||||
//Checks the case where the X value is exactly what was requested
|
||||
if ( (X == fromTable->axisX[x]) || (x == 0) )
|
||||
{
|
||||
xMaxValue = fromTable->axisX[x];
|
||||
xMinValue = fromTable->axisX[x];
|
||||
xMax = x;
|
||||
fromTable->lastXMax = xMax;
|
||||
xMin = x;
|
||||
fromTable->lastXMin = xMin;
|
||||
break;
|
||||
}
|
||||
//Normal case
|
||||
if ( (X <= fromTable->axisX[x]) && (X > fromTable->axisX[x-1]) )
|
||||
{
|
||||
xMaxValue = fromTable->axisX[x];
|
||||
xMinValue = fromTable->axisX[x-1];
|
||||
xMax = x;
|
||||
fromTable->lastXMax = xMax;
|
||||
xMin = x-1;
|
||||
fromTable->lastXMin = xMin;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Loop through the Y axis bins for the min/max pair
|
||||
int yMaxValue = fromTable->axisY[0];
|
||||
int yMinValue = fromTable->axisY[fromTable->ySize-1];
|
||||
byte yMin = 0;
|
||||
byte yMax = 0;
|
||||
|
||||
//If the requested Y value is greater/small than the maximum/minimum bin, reset Y to be that value
|
||||
if(Y > yMaxValue) { Y = yMaxValue; }
|
||||
if(Y < yMinValue) { Y = yMinValue; }
|
||||
|
||||
//1st check is whether we're still in the same Y bin as last time
|
||||
if ( (Y >= fromTable->axisY[fromTable->lastYMax]) && (Y < fromTable->axisY[fromTable->lastYMin]) )
|
||||
{
|
||||
yMaxValue = fromTable->axisY[fromTable->lastYMax];
|
||||
yMinValue = fromTable->axisY[fromTable->lastYMin];
|
||||
yMax = fromTable->lastYMax;
|
||||
yMin = fromTable->lastYMin;
|
||||
}
|
||||
//2nd check is whether we're in the next MAP/TPS bin (Next one up)
|
||||
else if ( (fromTable->lastYMin > 0 ) && (Y <= fromTable->axisY[fromTable->lastYMin - 1 ]) && (Y > fromTable->axisY[fromTable->lastYMax - 1]) ) //First make sure we're not already at the top Y bin
|
||||
{
|
||||
yMax = fromTable->lastYMax - 1;
|
||||
fromTable->lastYMax = yMax;
|
||||
yMin = fromTable->lastYMin - 1;
|
||||
fromTable->lastYMin = yMin;
|
||||
yMaxValue = fromTable->axisY[fromTable->lastYMax];
|
||||
yMinValue = fromTable->axisY[fromTable->lastYMin];
|
||||
}
|
||||
//3rd check is to look at the previous bin (Next one down)
|
||||
else if ( ((fromTable->lastYMax + 1) < fromTable->ySize) && (Y <= fromTable->axisY[fromTable->lastYMin + 1]) && (Y > fromTable->axisY[fromTable->lastYMax + 1]) ) //First make sure we're not already at the bottom Y bin
|
||||
{
|
||||
yMax = fromTable->lastYMax + 1;
|
||||
fromTable->lastYMax = yMax;
|
||||
yMin = fromTable->lastYMin + 1;
|
||||
fromTable->lastYMin = yMin;
|
||||
yMaxValue = fromTable->axisY[fromTable->lastYMax];
|
||||
yMinValue = fromTable->axisY[fromTable->lastYMin];
|
||||
}
|
||||
else
|
||||
//If it's not caught by one of the above scenarios, give up and just run the loop
|
||||
{
|
||||
|
||||
for (int8_t y = fromTable->ySize-1; y >= 0; y--)
|
||||
{
|
||||
//Checks the case where the Y value is exactly what was requested
|
||||
if ( (Y == fromTable->axisY[y]) || (y==0) )
|
||||
{
|
||||
yMaxValue = fromTable->axisY[y];
|
||||
yMinValue = fromTable->axisY[y];
|
||||
yMax = y;
|
||||
fromTable->lastYMax = yMax;
|
||||
yMin = y;
|
||||
fromTable->lastYMin = yMin;
|
||||
break;
|
||||
}
|
||||
//Normal case
|
||||
if ( (Y >= fromTable->axisY[y]) && (Y < fromTable->axisY[y-1]) )
|
||||
{
|
||||
yMaxValue = fromTable->axisY[y];
|
||||
yMinValue = fromTable->axisY[y-1];
|
||||
yMax = y;
|
||||
fromTable->lastYMax = yMax;
|
||||
yMin = y-1;
|
||||
fromTable->lastYMin = yMin;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
At this point we have the 4 corners of the map where the interpolated value will fall in
|
||||
Eg: (yMin,xMin) (yMin,xMax)
|
||||
|
||||
(yMax,xMin) (yMax,xMax)
|
||||
|
||||
In the following calculation the table values are referred to by the following variables:
|
||||
A B
|
||||
|
||||
C D
|
||||
|
||||
*/
|
||||
int A = fromTable->values[yMin][xMin];
|
||||
int B = fromTable->values[yMin][xMax];
|
||||
int C = fromTable->values[yMax][xMin];
|
||||
int D = fromTable->values[yMax][xMax];
|
||||
|
||||
//Check that all values aren't just the same (This regularly happens with things like the fuel trim maps)
|
||||
if( (A == B) && (A == C) && (A == D) ) { tableResult = A; }
|
||||
else
|
||||
{
|
||||
//Create some normalised position values
|
||||
//These are essentially percentages (between 0 and 1) of where the desired value falls between the nearest bins on each axis
|
||||
|
||||
|
||||
//Initial check incase the values were hit straight on
|
||||
|
||||
unsigned long p = (long)X - xMinValue;
|
||||
if (xMaxValue == xMinValue) { p = (p << TABLE_SHIFT_FACTOR); } //This only occurs if the requested X value was equal to one of the X axis bins
|
||||
else { p = ( (p << TABLE_SHIFT_FACTOR) / (xMaxValue - xMinValue) ); } //This is the standard case
|
||||
|
||||
unsigned long q;
|
||||
if (yMaxValue == yMinValue)
|
||||
{
|
||||
q = (long)Y - yMinValue;
|
||||
q = (q << TABLE_SHIFT_FACTOR);
|
||||
}
|
||||
//Standard case
|
||||
else
|
||||
{
|
||||
q = long(Y) - yMaxValue;
|
||||
q = TABLE_SHIFT_POWER - ( (q << TABLE_SHIFT_FACTOR) / (yMinValue - yMaxValue) );
|
||||
}
|
||||
|
||||
uint32_t m = ((TABLE_SHIFT_POWER-p) * (TABLE_SHIFT_POWER-q)) >> TABLE_SHIFT_FACTOR;
|
||||
uint32_t n = (p * (TABLE_SHIFT_POWER-q)) >> TABLE_SHIFT_FACTOR;
|
||||
uint32_t o = ((TABLE_SHIFT_POWER-p) * q) >> TABLE_SHIFT_FACTOR;
|
||||
uint32_t r = (p * q) >> TABLE_SHIFT_FACTOR;
|
||||
tableResult = ( (A * m) + (B * n) + (C * o) + (D * r) ) >> TABLE_SHIFT_FACTOR;
|
||||
}
|
||||
|
||||
//Update the tables cache data
|
||||
fromTable->lastXInput = X_in;
|
||||
fromTable->lastYInput = Y_in;
|
||||
fromTable->lastOutput = tableResult;
|
||||
fromTable->cacheIsValid = true;
|
||||
|
||||
return tableResult;
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
This file is used for everything related to maps/tables including their definition, functions etc
|
||||
*/
|
||||
#ifndef TABLE_H
|
||||
#define TABLE_H
|
||||
|
||||
/*
|
||||
The 2D table can contain either 8-bit (byte) or 16-bit (int) values
|
||||
The valueSize variable should be set to either 8 or 16 to indicate this BEFORE the table is used
|
||||
*/
|
||||
struct table2D {
|
||||
//Used 5414 RAM with original version
|
||||
byte valueSize;
|
||||
byte axisSize;
|
||||
byte xSize;
|
||||
|
||||
void *values;
|
||||
void *axisX;
|
||||
|
||||
//int16_t *values16;
|
||||
//int16_t *axisX16;
|
||||
|
||||
//Store the last X and Y coordinates in the table. This is used to make the next check faster
|
||||
int16_t lastXMax;
|
||||
int16_t lastXMin;
|
||||
|
||||
//Store the last input and output for caching
|
||||
int16_t lastInput;
|
||||
int16_t lastOutput;
|
||||
byte cacheTime; //Tracks when the last cache value was set so it can expire after x seconds. A timeout is required to pickup when a tuning value is changed, otherwise the old cached value will continue to be returned as the X value isn't changing.
|
||||
};
|
||||
|
||||
void table2D_setSize(struct table2D*, byte);
|
||||
int16_t table2D_getAxisValue(struct table2D*, byte);
|
||||
int16_t table2D_getRawValue(struct table2D*, byte);
|
||||
|
||||
int table2D_getValue(struct table2D *fromTable, int);
|
||||
|
||||
#endif // TABLE_H
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
Speeduino - Simple engine management for the Arduino Mega 2560 platform
|
||||
Copyright (C) Josh Stewart
|
||||
A full copy of the license may be found in the projects root directory
|
||||
*/
|
||||
|
||||
/*
|
||||
Because the size of the table is dynamic, this functino is required to reallocate the array sizes
|
||||
Note that this may clear some of the existing values of the table
|
||||
*/
|
||||
#include "table2d.h"
|
||||
#include "globals.h"
|
||||
|
||||
/*
|
||||
This function pulls a 1D linear interpolated (ie averaged) value from a 2D table
|
||||
ie: Given a value on the X axis, it returns a Y value that coresponds to the point on the curve between the nearest two defined X values
|
||||
|
||||
This function must take into account whether a table contains 8-bit or 16-bit values.
|
||||
Unfortunately this means many of the lines are duplicated depending on this
|
||||
*/
|
||||
int table2D_getValue(struct table2D *fromTable, int X_in)
|
||||
{
|
||||
//Orig memory usage = 5414
|
||||
int returnValue = 0;
|
||||
bool valueFound = false;
|
||||
|
||||
int X = X_in;
|
||||
int xMinValue, xMaxValue;
|
||||
int xMin = 0;
|
||||
int xMax = fromTable->xSize-1;
|
||||
|
||||
//Check whether the X input is the same as last time this ran
|
||||
if( (X_in == fromTable->lastInput) && (fromTable->cacheTime == currentStatus.secl) )
|
||||
{
|
||||
returnValue = fromTable->lastOutput;
|
||||
valueFound = true;
|
||||
}
|
||||
//If the requested X value is greater/small than the maximum/minimum bin, simply return that value
|
||||
else if(X >= table2D_getAxisValue(fromTable, xMax))
|
||||
{
|
||||
returnValue = table2D_getRawValue(fromTable, xMax);
|
||||
valueFound = true;
|
||||
}
|
||||
else if(X <= table2D_getAxisValue(fromTable, xMin))
|
||||
{
|
||||
returnValue = table2D_getRawValue(fromTable, xMin);
|
||||
valueFound = true;
|
||||
}
|
||||
//Finally if none of that is found
|
||||
else
|
||||
{
|
||||
fromTable->cacheTime = currentStatus.secl; //As we're not using the cache value, set the current secl value to track when this new value was calc'd
|
||||
|
||||
//1st check is whether we're still in the same X bin as last time
|
||||
xMaxValue = table2D_getAxisValue(fromTable, fromTable->lastXMax);
|
||||
xMinValue = table2D_getAxisValue(fromTable, fromTable->lastXMin);
|
||||
if ( (X <= xMaxValue) && (X > xMinValue) )
|
||||
{
|
||||
xMax = fromTable->lastXMax;
|
||||
xMin = fromTable->lastXMin;
|
||||
}
|
||||
else
|
||||
{
|
||||
//If we're not in the same bin, loop through to find where we are
|
||||
xMaxValue = table2D_getAxisValue(fromTable, fromTable->xSize-1); // init xMaxValue in preparation for loop.
|
||||
for (int x = fromTable->xSize-1; x > 0; x--)
|
||||
{
|
||||
xMinValue = table2D_getAxisValue(fromTable, x-1); // fetch next Min
|
||||
|
||||
//Checks the case where the X value is exactly what was requested
|
||||
if (X == xMaxValue)
|
||||
{
|
||||
returnValue = table2D_getRawValue(fromTable, x); //Simply return the coresponding value
|
||||
valueFound = true;
|
||||
break;
|
||||
}
|
||||
else if (X > xMinValue)
|
||||
{
|
||||
// Value is in the current bin
|
||||
xMax = x;
|
||||
fromTable->lastXMax = xMax;
|
||||
xMin = x-1;
|
||||
fromTable->lastXMin = xMin;
|
||||
break;
|
||||
}
|
||||
// Otherwise, continue to next bin
|
||||
xMaxValue = xMinValue; // for the next bin, our Min is their Max
|
||||
}
|
||||
}
|
||||
} //X_in same as last time
|
||||
|
||||
if (valueFound == false)
|
||||
{
|
||||
int16_t m = X - xMinValue;
|
||||
int16_t n = xMaxValue - xMinValue;
|
||||
|
||||
int16_t yMax = table2D_getRawValue(fromTable, xMax);
|
||||
int16_t yMin = table2D_getRawValue(fromTable, xMin);
|
||||
|
||||
/* Float version (if m, yMax, yMin and n were float's)
|
||||
int yVal = (m * (yMax - yMin)) / n;
|
||||
*/
|
||||
|
||||
//Non-Float version
|
||||
int16_t yVal = ( ((int32_t) m) * (yMax-yMin) ) / n;
|
||||
returnValue = yMin + yVal;
|
||||
}
|
||||
|
||||
fromTable->lastInput = X_in;
|
||||
fromTable->lastOutput = returnValue;
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an axis (bin) value from the 2D table. This works regardless of whether that axis is bytes or int16_ts
|
||||
*
|
||||
* @param fromTable
|
||||
* @param X_in
|
||||
* @return int16_t
|
||||
*/
|
||||
int16_t table2D_getAxisValue(struct table2D *fromTable, byte X_in)
|
||||
{
|
||||
int returnValue = 0;
|
||||
|
||||
if(fromTable->axisSize == SIZE_INT) { returnValue = ((int16_t*)fromTable->axisX)[X_in]; }
|
||||
else if(fromTable->axisSize == SIZE_BYTE) { returnValue = ((uint8_t*)fromTable->axisX)[X_in]; }
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an value from the 2D table given an index value. No interpolation is performed
|
||||
*
|
||||
* @param fromTable
|
||||
* @param X_index
|
||||
* @return int16_t
|
||||
*/
|
||||
int16_t table2D_getRawValue(struct table2D *fromTable, byte X_index)
|
||||
{
|
||||
int returnValue = 0;
|
||||
|
||||
if(fromTable->valueSize == SIZE_INT) { returnValue = ((int16_t*)fromTable->values)[X_index]; }
|
||||
else if(fromTable->valueSize == SIZE_BYTE) { returnValue = ((uint8_t*)fromTable->values)[X_index]; }
|
||||
|
||||
return returnValue;
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
#include <stdlib.h>
|
||||
#include "table3d.h"
|
||||
|
||||
// =============================== Iterators =========================
|
||||
|
||||
table_value_iterator rows_begin(const void *pTable, table_type_t key)
|
||||
{
|
||||
#define CTA_GET_ROW_ITERATOR(size, xDomain, yDomain, pTable) \
|
||||
return ((TABLE3D_TYPENAME_BASE(size, xDomain, yDomain)*)pTable)->values.begin();
|
||||
CONCRETE_TABLE_ACTION(key, CTA_GET_ROW_ITERATOR, pTable);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert page iterator to table x axis iterator.
|
||||
*/
|
||||
table_axis_iterator x_begin(const void *pTable, table_type_t key)
|
||||
{
|
||||
#define CTA_GET_X_ITERATOR(size, xDomain, yDomain, pTable) \
|
||||
return ((TABLE3D_TYPENAME_BASE(size, xDomain, yDomain)*)pTable)->axisX.begin();
|
||||
CONCRETE_TABLE_ACTION(key, CTA_GET_X_ITERATOR, pTable);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert page iterator to table y axis iterator.
|
||||
*/
|
||||
table_axis_iterator y_begin(const void *pTable, table_type_t key)
|
||||
{
|
||||
#define CTA_GET_Y_ITERATOR(size, xDomain, yDomain, pTable) \
|
||||
return ((TABLE3D_TYPENAME_BASE(size, xDomain, yDomain)*)pTable)->axisY.begin();
|
||||
CONCRETE_TABLE_ACTION(key, CTA_GET_Y_ITERATOR, pTable);
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
/**
|
||||
* @defgroup table_3d 3D Tables
|
||||
* @brief Structures and functions related to 3D tables, such as VE, Spark Advance, AFR etc.
|
||||
*
|
||||
* Logical:
|
||||
* - each 3D table is a continuous height map spread over a cartesian (x, y) plane
|
||||
* - Continuous: we expect to interpolate between any 4 points
|
||||
* - The axes are
|
||||
* - Bounded. I.e. non-infnite
|
||||
* - Non-linear. I.e. x[n]-x[n-1] != x[n+1]-x[n]
|
||||
* - Increasing. I.e. x[n] >= x[n-1]
|
||||
* - Do not have to start at [0,0]
|
||||
*
|
||||
* E.g. for a 3x3 table, this is what the TS table editor would show:
|
||||
* <pre>
|
||||
* Y-Max V6 V7 V8
|
||||
* Y-Int V3 V4 V5
|
||||
* Y-Min V0 V1 V2
|
||||
* X-Min X-Int X-Max
|
||||
* </pre>
|
||||
*
|
||||
* In memory, we store rows in reverse:
|
||||
* - The X axis is conventional: <c>x[0]</c> stores \c X-Min
|
||||
* - The Y-axis is inverted: <c>y[0]</c> stores \c Y-Max
|
||||
* - The value locations match the axes.
|
||||
* - <c>value[0][0]</c> stores \c V6.
|
||||
* - <c>value[2][0]</c> stores \c V0.
|
||||
*
|
||||
* I.e.
|
||||
* <pre>
|
||||
* Y-Min V0 V1 V2
|
||||
* Y-Int V3 V4 V5
|
||||
* Y-Max V6 V7 V8
|
||||
* X-Min X-Int X-Max
|
||||
* </pre>
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** \file
|
||||
* @brief 3D table data types and functions
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "table3d_interpolate.h"
|
||||
#include "table3d_axes.h"
|
||||
#include "table3d_values.h"
|
||||
|
||||
#define TO_TYPE_KEY(size, xDom, yDom) table3d ## size ## xDom ## yDom ## _key
|
||||
|
||||
/**
|
||||
* @brief Table \b type identifiers. Limited compile time RTTI
|
||||
*
|
||||
* With no virtual functions (they have quite a bit of overhead in both space &
|
||||
* time), we have to pass void* around in certain cases. In order to cast that
|
||||
* back to a concrete table type, we need to somehow identify the type.
|
||||
*
|
||||
* Once approach is to register each type - but that requires a central registry
|
||||
* which will use RAM.
|
||||
*
|
||||
* Since we have a compile time fixed set of table types, we can map a unique
|
||||
* identifer to the type via a cast - this enum is that unique identifier.
|
||||
*
|
||||
* Typically used in conjunction with the '#CONCRETE_TABLE_ACTION' macro
|
||||
*/
|
||||
enum table_type_t {
|
||||
table_type_None,
|
||||
#define TABLE3D_GEN_TYPEKEY(size, xDom, yDom) TO_TYPE_KEY(size, xDom, yDom),
|
||||
TABLE3D_GENERATOR(TABLE3D_GEN_TYPEKEY)
|
||||
};
|
||||
|
||||
// Generate the 3D table types
|
||||
#define TABLE3D_GEN_TYPE(size, xDom, yDom) \
|
||||
/** @brief A 3D table with size x size dimensions, xDom x-axis and yDom y-axis */ \
|
||||
struct TABLE3D_TYPENAME_BASE(size, xDom, yDom) \
|
||||
{ \
|
||||
typedef TABLE3D_TYPENAME_XAXIS(size, xDom, yDom) xaxis_t; \
|
||||
typedef TABLE3D_TYPENAME_YAXIS(size, xDom, yDom) yaxis_t; \
|
||||
typedef TABLE3D_TYPENAME_VALUE(size, xDom, yDom) value_t; \
|
||||
/* This will take up zero space unless we take the address somewhere */ \
|
||||
static constexpr table_type_t type_key = TO_TYPE_KEY(size, xDom, yDom); \
|
||||
\
|
||||
table3DGetValueCache get_value_cache; \
|
||||
TABLE3D_TYPENAME_VALUE(size, xDom, yDom) values; \
|
||||
TABLE3D_TYPENAME_XAXIS(size, xDom, yDom) axisX; \
|
||||
TABLE3D_TYPENAME_YAXIS(size, xDom, yDom) axisY; \
|
||||
};
|
||||
TABLE3D_GENERATOR(TABLE3D_GEN_TYPE)
|
||||
|
||||
// Generate get3DTableValue() functions
|
||||
#define TABLE3D_GEN_GET_TABLE_VALUE(size, xDom, yDom) \
|
||||
inline int get3DTableValue(TABLE3D_TYPENAME_BASE(size, xDom, yDom) *pTable, table3d_axis_t y, table3d_axis_t x) \
|
||||
{ \
|
||||
return get3DTableValue( &pTable->get_value_cache, \
|
||||
TABLE3D_TYPENAME_BASE(size, xDom, yDom)::value_t::row_size, \
|
||||
pTable->values.values, \
|
||||
pTable->axisX.axis, \
|
||||
pTable->axisY.axis, \
|
||||
y, x); \
|
||||
}
|
||||
TABLE3D_GENERATOR(TABLE3D_GEN_GET_TABLE_VALUE)
|
||||
|
||||
// =============================== Table function calls =========================
|
||||
|
||||
// With no templates or inheritance we need some way to call functions
|
||||
// for the various distinct table types. CONCRETE_TABLE_ACTION dispatches
|
||||
// to a caller defined function overloaded by the type of the table.
|
||||
#define CONCRETE_TABLE_ACTION_INNER(size, xDomain, yDomain, action, ...) \
|
||||
case TO_TYPE_KEY(size, xDomain, yDomain): action(size, xDomain, yDomain, ##__VA_ARGS__);
|
||||
#define CONCRETE_TABLE_ACTION(testKey, action, ...) \
|
||||
switch ((table_type_t)testKey) { \
|
||||
TABLE3D_GENERATOR(CONCRETE_TABLE_ACTION_INNER, action, ##__VA_ARGS__ ) \
|
||||
default: abort(); }
|
||||
|
||||
// =============================== Table function calls =========================
|
||||
|
||||
table_value_iterator rows_begin(const void *pTable, table_type_t key);
|
||||
|
||||
table_axis_iterator x_begin(const void *pTable, table_type_t key);
|
||||
|
||||
table_axis_iterator y_begin(const void *pTable, table_type_t key);
|
||||
|
||||
/** @} */
|
|
@ -0,0 +1,146 @@
|
|||
/**
|
||||
* @addtogroup table_3d
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** \file
|
||||
* @brief 3D table axis types and iterators
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "table3d_typedefs.h"
|
||||
#include "int16_ref.h"
|
||||
|
||||
/**\enum axis_domain
|
||||
* @brief Encodes the real world measurement that a table axis captures
|
||||
* */
|
||||
enum axis_domain {
|
||||
/** RPM (engine speed) */
|
||||
axis_domain_Rpm,
|
||||
/** Load */
|
||||
axis_domain_Load,
|
||||
/** Throttle position */
|
||||
axis_domain_Tps
|
||||
};
|
||||
|
||||
/** @brief Iterate over table axis elements */
|
||||
class table_axis_iterator
|
||||
{
|
||||
public:
|
||||
|
||||
/** @brief Construct */
|
||||
table_axis_iterator(const table3d_axis_t *pStart, const table3d_axis_t *pEnd, uint8_t io_factor, int8_t stride)
|
||||
: _pAxis(pStart), _pAxisEnd(pEnd), _axisFactor(io_factor), _stride(stride)
|
||||
{
|
||||
}
|
||||
|
||||
/** @brief Advance the iterator
|
||||
* @param steps The number of elements to move the iterator
|
||||
*/
|
||||
inline table_axis_iterator& advance(table3d_dim_t steps)
|
||||
{
|
||||
_pAxis = _pAxis + (_stride * steps);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** @brief Increment the iterator by one element*/
|
||||
inline table_axis_iterator& operator++()
|
||||
{
|
||||
return advance(1);
|
||||
}
|
||||
|
||||
/** @brief Test for end of iteration */
|
||||
inline bool at_end() const
|
||||
{
|
||||
return _pAxis == _pAxisEnd;
|
||||
}
|
||||
|
||||
/** @brief Dereference the iterator */
|
||||
inline int16_ref operator*()
|
||||
{
|
||||
return int16_ref(*const_cast<table3d_axis_t*>(_pAxis), _axisFactor);
|
||||
}
|
||||
/** @copydoc table_axis_iterator::operator*() */
|
||||
inline const int16_ref operator*() const
|
||||
{
|
||||
return int16_ref(*const_cast<table3d_axis_t*>(_pAxis), _axisFactor);
|
||||
}
|
||||
|
||||
/** @brief Reverse the iterator direction
|
||||
*
|
||||
* Iterate from the end to the start. <b>This is only meant to be called on a freshly constructed iterator.</b>
|
||||
*/
|
||||
inline table_axis_iterator& reverse()
|
||||
{
|
||||
const table3d_axis_t *_pOldAxis = _pAxis;
|
||||
_pAxis = _pAxisEnd - _stride;
|
||||
_pAxisEnd = _pOldAxis - _stride;
|
||||
_stride = (int8_t)(_stride * -1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
const table3d_axis_t *_pAxis;
|
||||
const table3d_axis_t *_pAxisEnd;
|
||||
uint8_t _axisFactor;
|
||||
int8_t _stride;
|
||||
};
|
||||
|
||||
/** @brief Shared code for the axis types */
|
||||
class table3d_axis_base {
|
||||
protected:
|
||||
static constexpr uint8_t domain_to_iofactor(axis_domain domain) {
|
||||
// This really, really needs to be done at compile time, hence the contexpr
|
||||
return domain==axis_domain_Rpm ? 100 :
|
||||
domain==axis_domain_Load ? 2 : 1;
|
||||
}
|
||||
};
|
||||
|
||||
#define TABLE3D_TYPENAME_XAXIS(size, xDom, yDom) CONCAT(TABLE3D_TYPENAME_BASE(size, xDom, yDom), _xaxis)
|
||||
|
||||
#define TABLE3D_GEN_XAXIS(size, xDom, yDom) \
|
||||
/** @brief The x-axis for a 3D table with size x size dimensions, xDom x-axis and yDom y-axis */ \
|
||||
struct TABLE3D_TYPENAME_XAXIS(size, xDom, yDom) : public table3d_axis_base { \
|
||||
/** @brief The length of the axis in elements */ \
|
||||
static constexpr table3d_dim_t length = size; \
|
||||
/** @brief The domain the axis represents */ \
|
||||
static constexpr axis_domain domain = axis_domain_ ## xDom; \
|
||||
/**
|
||||
@brief The axis elements \
|
||||
@details The x-axis is conventional: axis[0] is the minimum \
|
||||
*/ \
|
||||
table3d_axis_t axis[size]; \
|
||||
\
|
||||
/** @brief Iterate over the axis elements */ \
|
||||
inline table_axis_iterator begin() \
|
||||
{ \
|
||||
return table_axis_iterator(axis, axis+size, domain_to_iofactor(domain), 1); \
|
||||
} \
|
||||
};
|
||||
TABLE3D_GENERATOR(TABLE3D_GEN_XAXIS)
|
||||
|
||||
#define TABLE3D_TYPENAME_YAXIS(size, xDom, yDom) CONCAT(TABLE3D_TYPENAME_BASE(size, xDom, yDom), _yaxis)
|
||||
|
||||
#define TABLE3D_GEN_YAXIS(size, xDom, yDom) \
|
||||
/** @brief The y-axis for a 3D table with size x size dimensions, xDom x-axis and yDom y-axis */ \
|
||||
struct CONCAT(TABLE3D_TYPENAME_BASE(size, xDom, yDom), _yaxis) : public table3d_axis_base { \
|
||||
/** @brief The length of the axis in elements */ \
|
||||
static constexpr table3d_dim_t length = size; \
|
||||
/** @brief The domain the axis represents */ \
|
||||
static constexpr axis_domain domain = axis_domain_ ## yDom; \
|
||||
/**
|
||||
@brief The axis elements \
|
||||
@details The y-axis is reversed: axis[n-1] is the minimum \
|
||||
*/ \
|
||||
table3d_axis_t axis[size]; \
|
||||
\
|
||||
/** @brief Iterate over the axis elements */ \
|
||||
inline table_axis_iterator begin() \
|
||||
{ \
|
||||
return table_axis_iterator(axis+(size-1), axis-1, domain_to_iofactor(domain), -1); \
|
||||
} \
|
||||
};
|
||||
TABLE3D_GENERATOR(TABLE3D_GEN_YAXIS)
|
||||
|
||||
/** @} */
|
|
@ -0,0 +1,225 @@
|
|||
#include "table3d_interpolate.h"
|
||||
|
||||
//The shift amount used for the 3D table calculations
|
||||
#define TABLE_SHIFT_FACTOR 8
|
||||
#define TABLE_SHIFT_POWER (1UL<<TABLE_SHIFT_FACTOR)
|
||||
|
||||
//This function pulls a value from a 3D table given a target for X and Y coordinates.
|
||||
//It performs a 2D linear interpolation as descibred in: www.megamanual.com/v22manual/ve_tuner.pdf
|
||||
table3d_value_t get3DTableValue(struct table3DGetValueCache *fromTable,
|
||||
table3d_dim_t axisSize,
|
||||
const table3d_value_t *pValues,
|
||||
const table3d_axis_t *pXAxis,
|
||||
const table3d_axis_t *pYAxis,
|
||||
table3d_axis_t Y_in, table3d_axis_t X_in)
|
||||
{
|
||||
table3d_axis_t X = X_in;
|
||||
table3d_axis_t Y = Y_in;
|
||||
|
||||
table3d_value_t tableResult = 0;
|
||||
//Loop through the X axis bins for the min/max pair
|
||||
//Note: For the X axis specifically, rather than looping from tableAxisX[0] up to tableAxisX[max], we start at tableAxisX[Max] and go down.
|
||||
// This is because the important tables (fuel and injection) will have the highest RPM at the top of the X axis, so starting there will mean the best case occurs when the RPM is highest (And hence the CPU is needed most)
|
||||
table3d_axis_t xMinValue = pXAxis[0];
|
||||
table3d_axis_t xMaxValue = pXAxis[axisSize-1];
|
||||
table3d_axis_t xMin = 0;
|
||||
table3d_axis_t xMax = 0;
|
||||
|
||||
//If the requested X value is greater/small than the maximum/minimum bin, reset X to be that value
|
||||
if(X > xMaxValue) { X = xMaxValue; }
|
||||
if(X < xMinValue) { X = xMinValue; }
|
||||
|
||||
//0th check is whether the same X and Y values are being sent as last time. If they are, this not only prevents a lookup of the axis, but prevents the interpolation calcs being performed
|
||||
if( (X_in == fromTable->lastXInput) && (Y_in == fromTable->lastYInput))
|
||||
{
|
||||
return fromTable->lastOutput;
|
||||
}
|
||||
|
||||
//Commence the lookups on the X and Y axis
|
||||
|
||||
//1st check is whether we're still in the same X bin as last time
|
||||
if ( (X <= pXAxis[fromTable->lastXMax]) && (X > pXAxis[fromTable->lastXMin]) )
|
||||
{
|
||||
xMaxValue = pXAxis[fromTable->lastXMax];
|
||||
xMinValue = pXAxis[fromTable->lastXMin];
|
||||
xMax = fromTable->lastXMax;
|
||||
xMin = fromTable->lastXMin;
|
||||
}
|
||||
//2nd check is whether we're in the next RPM bin (To the right)
|
||||
else if ( ((fromTable->lastXMax + 1) < axisSize ) && (X <= pXAxis[fromTable->lastXMax +1 ]) && (X > pXAxis[fromTable->lastXMin + 1]) ) //First make sure we're not already at the last X bin
|
||||
{
|
||||
xMax = fromTable->lastXMax + 1;
|
||||
fromTable->lastXMax = xMax;
|
||||
xMin = fromTable->lastXMin + 1;
|
||||
fromTable->lastXMin = xMin;
|
||||
xMaxValue = pXAxis[fromTable->lastXMax];
|
||||
xMinValue = pXAxis[fromTable->lastXMin];
|
||||
}
|
||||
//3rd check is to look at the previous bin (to the left)
|
||||
else if ( (fromTable->lastXMin > 0 ) && (X <= pXAxis[fromTable->lastXMax - 1]) && (X > pXAxis[fromTable->lastXMin - 1]) ) //First make sure we're not already at the first X bin
|
||||
{
|
||||
xMax = fromTable->lastXMax - 1;
|
||||
fromTable->lastXMax = xMax;
|
||||
xMin = fromTable->lastXMin - 1;
|
||||
fromTable->lastXMin = xMin;
|
||||
xMaxValue = pXAxis[fromTable->lastXMax];
|
||||
xMinValue = pXAxis[fromTable->lastXMin];
|
||||
}
|
||||
else
|
||||
//If it's not caught by one of the above scenarios, give up and just run the loop
|
||||
{
|
||||
for (int8_t x = axisSize-1; x >= 0; x--)
|
||||
{
|
||||
//Checks the case where the X value is exactly what was requested
|
||||
if ( (X == pXAxis[x]) || (x == 0) )
|
||||
{
|
||||
xMaxValue = pXAxis[x];
|
||||
xMinValue = pXAxis[x];
|
||||
xMax = x;
|
||||
fromTable->lastXMax = xMax;
|
||||
xMin = x;
|
||||
fromTable->lastXMin = xMin;
|
||||
break;
|
||||
}
|
||||
//Normal case
|
||||
if ( (X <= pXAxis[x]) && (X > pXAxis[x-1]) )
|
||||
{
|
||||
xMaxValue = pXAxis[x];
|
||||
xMinValue = pXAxis[x-1];
|
||||
xMax = x;
|
||||
fromTable->lastXMax = xMax;
|
||||
xMin = x-1;
|
||||
fromTable->lastXMin = xMin;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Loop through the Y axis bins for the min/max pair
|
||||
table3d_axis_t yMaxValue = pYAxis[0];
|
||||
table3d_axis_t yMinValue = pYAxis[axisSize-1];
|
||||
table3d_axis_t yMin = 0;
|
||||
table3d_axis_t yMax = 0;
|
||||
|
||||
//If the requested Y value is greater/small than the maximum/minimum bin, reset Y to be that value
|
||||
if(Y > yMaxValue) { Y = yMaxValue; }
|
||||
if(Y < yMinValue) { Y = yMinValue; }
|
||||
|
||||
//1st check is whether we're still in the same Y bin as last time
|
||||
if ( (Y >= pYAxis[fromTable->lastYMax]) && (Y < pYAxis[fromTable->lastYMin]) )
|
||||
{
|
||||
yMaxValue = pYAxis[fromTable->lastYMax];
|
||||
yMinValue = pYAxis[fromTable->lastYMin];
|
||||
yMax = fromTable->lastYMax;
|
||||
yMin = fromTable->lastYMin;
|
||||
}
|
||||
//2nd check is whether we're in the next MAP/TPS bin (Next one up)
|
||||
else if ( (fromTable->lastYMin > 0 ) && (Y <= pYAxis[fromTable->lastYMin - 1 ]) && (Y > pYAxis[fromTable->lastYMax - 1]) ) //First make sure we're not already at the top Y bin
|
||||
{
|
||||
yMax = fromTable->lastYMax - 1;
|
||||
fromTable->lastYMax = yMax;
|
||||
yMin = fromTable->lastYMin - 1;
|
||||
fromTable->lastYMin = yMin;
|
||||
yMaxValue = pYAxis[fromTable->lastYMax];
|
||||
yMinValue = pYAxis[fromTable->lastYMin];
|
||||
}
|
||||
//3rd check is to look at the previous bin (Next one down)
|
||||
else if ( ((fromTable->lastYMax + 1) < axisSize) && (Y <= pYAxis[fromTable->lastYMin + 1]) && (Y > pYAxis[fromTable->lastYMax + 1]) ) //First make sure we're not already at the bottom Y bin
|
||||
{
|
||||
yMax = fromTable->lastYMax + 1;
|
||||
fromTable->lastYMax = yMax;
|
||||
yMin = fromTable->lastYMin + 1;
|
||||
fromTable->lastYMin = yMin;
|
||||
yMaxValue = pYAxis[fromTable->lastYMax];
|
||||
yMinValue = pYAxis[fromTable->lastYMin];
|
||||
}
|
||||
else
|
||||
//If it's not caught by one of the above scenarios, give up and just run the loop
|
||||
{
|
||||
|
||||
for (int8_t y = axisSize-1; y >= 0; y--)
|
||||
{
|
||||
//Checks the case where the Y value is exactly what was requested
|
||||
if ( (Y == pYAxis[y]) || (y==0) )
|
||||
{
|
||||
yMaxValue = pYAxis[y];
|
||||
yMinValue = pYAxis[y];
|
||||
yMax = y;
|
||||
fromTable->lastYMax = yMax;
|
||||
yMin = y;
|
||||
fromTable->lastYMin = yMin;
|
||||
break;
|
||||
}
|
||||
//Normal case
|
||||
if ( (Y >= pYAxis[y]) && (Y < pYAxis[y-1]) )
|
||||
{
|
||||
yMaxValue = pYAxis[y];
|
||||
yMinValue = pYAxis[y-1];
|
||||
yMax = y;
|
||||
fromTable->lastYMax = yMax;
|
||||
yMin = y-1;
|
||||
fromTable->lastYMin = yMin;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
At this point we have the 4 corners of the map where the interpolated value will fall in
|
||||
Eg: (yMin,xMin) (yMin,xMax)
|
||||
|
||||
(yMax,xMin) (yMax,xMax)
|
||||
|
||||
In the following calculation the table values are referred to by the following variables:
|
||||
A B
|
||||
|
||||
C D
|
||||
|
||||
*/
|
||||
table3d_axis_t A = pValues[(yMin*axisSize)+xMin];
|
||||
table3d_axis_t B = pValues[(yMin*axisSize)+xMax];
|
||||
table3d_axis_t C = pValues[(yMax*axisSize)+xMin];
|
||||
table3d_axis_t D = pValues[(yMax*axisSize)+xMax];
|
||||
|
||||
//Check that all values aren't just the same (This regularly happens with things like the fuel trim maps)
|
||||
if( (A == B) && (A == C) && (A == D) ) { tableResult = A; }
|
||||
else
|
||||
{
|
||||
//Create some normalised position values
|
||||
//These are essentially percentages (between 0 and 1) of where the desired value falls between the nearest bins on each axis
|
||||
|
||||
|
||||
//Initial check incase the values were hit straight on
|
||||
|
||||
unsigned long p = (long)X - xMinValue;
|
||||
if (xMaxValue == xMinValue) { p = (p << TABLE_SHIFT_FACTOR); } //This only occurs if the requested X value was equal to one of the X axis bins
|
||||
else { p = ( (p << TABLE_SHIFT_FACTOR) / (xMaxValue - xMinValue) ); } //This is the standard case
|
||||
|
||||
unsigned long q;
|
||||
if (yMaxValue == yMinValue)
|
||||
{
|
||||
q = (long)Y - yMinValue;
|
||||
q = (q << TABLE_SHIFT_FACTOR);
|
||||
}
|
||||
//Standard case
|
||||
else
|
||||
{
|
||||
q = long(Y) - yMaxValue;
|
||||
q = TABLE_SHIFT_POWER - ( (q << TABLE_SHIFT_FACTOR) / (yMinValue - yMaxValue) );
|
||||
}
|
||||
|
||||
uint32_t m = ((TABLE_SHIFT_POWER-p) * (TABLE_SHIFT_POWER-q)) >> TABLE_SHIFT_FACTOR;
|
||||
uint32_t n = (p * (TABLE_SHIFT_POWER-q)) >> TABLE_SHIFT_FACTOR;
|
||||
uint32_t o = ((TABLE_SHIFT_POWER-p) * q) >> TABLE_SHIFT_FACTOR;
|
||||
uint32_t r = (p * q) >> TABLE_SHIFT_FACTOR;
|
||||
tableResult = ( (A * m) + (B * n) + (C * o) + (D * r) ) >> TABLE_SHIFT_FACTOR;
|
||||
}
|
||||
|
||||
//Update the tables cache data
|
||||
fromTable->lastXInput = X_in;
|
||||
fromTable->lastYInput = Y_in;
|
||||
fromTable->lastOutput = tableResult;
|
||||
|
||||
return tableResult;
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
#pragma once
|
||||
|
||||
#include "table3d_typedefs.h"
|
||||
|
||||
struct table3DGetValueCache {
|
||||
//Store the last X and Y coordinates in the table. This is used to make the next check faster
|
||||
table3d_dim_t lastXMax, lastXMin;
|
||||
table3d_dim_t lastYMax, lastYMin;
|
||||
|
||||
//Store the last input and output values, again for caching purposes
|
||||
table3d_axis_t lastXInput = INT16_MAX, lastYInput;
|
||||
table3d_value_t lastOutput; // This will need changing if we ever have 16-bit table values
|
||||
};
|
||||
|
||||
inline void invalidate_cache(table3DGetValueCache *pCache)
|
||||
{
|
||||
pCache->lastXInput = INT16_MAX;
|
||||
}
|
||||
|
||||
/*
|
||||
3D Tables have an origin (0,0) in the top left hand corner. Vertical axis is expressed first.
|
||||
Eg: 2x2 table
|
||||
-----
|
||||
|2 7|
|
||||
|1 4|
|
||||
-----
|
||||
|
||||
(0,1) = 7
|
||||
(0,0) = 2
|
||||
(1,0) = 1
|
||||
|
||||
*/
|
||||
table3d_value_t get3DTableValue(struct table3DGetValueCache *pValueCache,
|
||||
table3d_dim_t axisSize,
|
||||
const table3d_value_t *pValues,
|
||||
const table3d_axis_t *pXAxis,
|
||||
const table3d_axis_t *pYAxis,
|
||||
table3d_axis_t y, table3d_axis_t x);
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* @addtogroup table_3d
|
||||
* @{
|
||||
*/
|
||||
|
||||
|
||||
/** \file
|
||||
* @brief Typedefs for primitive 3D table elements
|
||||
*
|
||||
* These used are for consistency across functions that work on 3D table data.
|
||||
* For example:<br>
|
||||
* <c>table3d_value_t foo(table3d_axis_t input);</c><br>
|
||||
* instead of:<br>
|
||||
* <c>uint8_t foo(int16_t input);</c>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/** @brief Encodes the \b length of the axes */
|
||||
typedef uint8_t table3d_dim_t;
|
||||
|
||||
/** @brief The type of each table value */
|
||||
typedef uint8_t table3d_value_t;
|
||||
|
||||
/** @brief The type of each axis value */
|
||||
typedef int16_t table3d_axis_t;
|
||||
|
||||
/** @brief Core 3d table generation macro
|
||||
*
|
||||
* We have a fixed number of table types: they are defined by this macro.
|
||||
* GENERATOR is expected to be another macros that takes at least 3 arguments:
|
||||
* axis length, x-axis domain, y-axis domain
|
||||
*/
|
||||
#define TABLE3D_GENERATOR(GENERATOR, ...) \
|
||||
GENERATOR(6, Rpm, Load, ##__VA_ARGS__) \
|
||||
GENERATOR(4, Rpm, Load, ##__VA_ARGS__) \
|
||||
GENERATOR(8, Rpm, Load, ##__VA_ARGS__) \
|
||||
GENERATOR(8, Rpm, Tps, ##__VA_ARGS__) \
|
||||
GENERATOR(16, Rpm, Load, ##__VA_ARGS__)
|
||||
|
||||
// Each 3d table is given a distinct type based on size & axis domains
|
||||
// This encapsulates the generation of the type name
|
||||
#define TABLE3D_TYPENAME_BASE(size, xDom, yDom) table3d ## size ## xDom ## yDom
|
||||
|
||||
#define CAT_HELPER(a, b) a ## b
|
||||
#define CONCAT(A, B) CAT_HELPER(A, B)
|
||||
|
||||
/** @} */
|
|
@ -0,0 +1,199 @@
|
|||
/**
|
||||
* @addtogroup table_3d
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** \file
|
||||
* @brief 3D table value structs and iterators
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "table3d_typedefs.h"
|
||||
|
||||
// ========================= INTRA-ROW ITERATION =========================
|
||||
|
||||
/** @brief Iterate through a table row. I.e. constant Y, changing X
|
||||
*
|
||||
* Instances of this class are normally created via a table_value_iterator instance.
|
||||
*/
|
||||
class table_row_iterator {
|
||||
public:
|
||||
|
||||
/**
|
||||
* @brief Construct
|
||||
* @param pRowStart Pointer to the 1st element in the row
|
||||
* @param rowWidth The number of elements to in the row
|
||||
*/
|
||||
table_row_iterator(const table3d_value_t *pRowStart, table3d_dim_t rowWidth)
|
||||
: pValue(pRowStart), pEnd(pRowStart+rowWidth)
|
||||
{
|
||||
}
|
||||
|
||||
/** @brief Pointer to the end of the row */
|
||||
inline const table3d_value_t* end() const { return pEnd; }
|
||||
/** @copydoc table_row_iterator::end() const */
|
||||
inline table3d_value_t* end() { return const_cast<table3d_value_t *>(pEnd); }
|
||||
|
||||
/** @brief Advance the iterator
|
||||
* @param steps The number of elements to move the iterator
|
||||
*/
|
||||
inline table_row_iterator& advance(table3d_dim_t steps)
|
||||
{
|
||||
pValue = pValue + steps;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** @brief Increment the iterator by one element*/
|
||||
inline table_row_iterator& operator++()
|
||||
{
|
||||
return advance(1);
|
||||
}
|
||||
|
||||
/** @brief Test for end of iteration */
|
||||
inline bool at_end() const
|
||||
{
|
||||
return pValue == pEnd;
|
||||
}
|
||||
|
||||
/** @brief Dereference the iterator */
|
||||
inline const table3d_value_t& operator*() const
|
||||
{
|
||||
return *pValue;
|
||||
}
|
||||
/** @copydoc table_row_iterator::operator*() const */
|
||||
inline table3d_value_t& operator*()
|
||||
{
|
||||
return *const_cast<table3d_value_t *>(pValue);
|
||||
}
|
||||
|
||||
/** @brief Number of elements available */
|
||||
inline table3d_dim_t size() const { return pEnd-pValue; }
|
||||
|
||||
private:
|
||||
const table3d_value_t *pValue;
|
||||
const table3d_value_t *pEnd;
|
||||
};
|
||||
|
||||
// ========================= INTER-ROW ITERATION =========================
|
||||
|
||||
/** @brief Iterate through a tables values, row by row. */
|
||||
class table_value_iterator
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
* @brief Construct
|
||||
* @param pValues Pointer to the 1st value in a 1-d array
|
||||
* @param axisSize The number of columns & elements per row (square tables only)
|
||||
*/
|
||||
table_value_iterator(const table3d_value_t *pValues, table3d_dim_t axisSize)
|
||||
: pRowsStart(pValues + (axisSize*(axisSize-1))),
|
||||
pRowsEnd(pValues - axisSize),
|
||||
rowWidth(axisSize)
|
||||
{
|
||||
// Table values are not linear in memory - rows are in reverse order
|
||||
// E.g. a 4x4 table with logical element [0][0] at the bottom left
|
||||
// (normal cartesian coordinates) has this layout.
|
||||
// 0 1 2 3
|
||||
// 4 5 6 7
|
||||
// 8 9 10 11
|
||||
// 12 13 14 15
|
||||
// So we start at row 3 (index 12 of the array) and iterate towards
|
||||
// the start of the array
|
||||
//
|
||||
// This all supports fast 3d interpolation.
|
||||
}
|
||||
|
||||
/** @brief Advance the iterator
|
||||
* @param rows The number of \b rows to move
|
||||
*/
|
||||
inline table_value_iterator& advance(table3d_dim_t rows)
|
||||
{
|
||||
pRowsStart = pRowsStart - (rowWidth * rows);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** @brief Increment the iterator by one \b row */
|
||||
inline table_value_iterator& operator++()
|
||||
{
|
||||
return advance(1);
|
||||
}
|
||||
|
||||
/** @brief Dereference the iterator to access a row of data */
|
||||
inline const table_row_iterator operator*() const
|
||||
{
|
||||
return table_row_iterator(pRowsStart, rowWidth);
|
||||
}
|
||||
/** @copydoc table_value_iterator::operator*() const */
|
||||
inline table_row_iterator operator*()
|
||||
{
|
||||
return table_row_iterator(pRowsStart, rowWidth);
|
||||
}
|
||||
|
||||
/** @brief Test for end of iteration */
|
||||
inline bool at_end() const
|
||||
{
|
||||
return pRowsStart == pRowsEnd;
|
||||
}
|
||||
|
||||
private:
|
||||
const table3d_value_t *pRowsStart;
|
||||
const table3d_value_t *pRowsEnd;
|
||||
table3d_dim_t rowWidth;
|
||||
};
|
||||
|
||||
#define TABLE3D_TYPENAME_VALUE(size, xDom, yDom) CONCAT(TABLE3D_TYPENAME_BASE(size, xDom, yDom), _values)
|
||||
|
||||
#define TABLE3D_GEN_VALUES(size, xDom, yDom) \
|
||||
/** @brief The values for a 3D table with size x size dimensions, xDom x-axis and yDom y-axis */ \
|
||||
struct TABLE3D_TYPENAME_VALUE(size, xDom, yDom) { \
|
||||
/** @brief The number of items in a row. I.e. it's length */ \
|
||||
static constexpr table3d_dim_t row_size = size; \
|
||||
/** @brief The number of rows */ \
|
||||
static constexpr table3d_dim_t num_rows = size; \
|
||||
/** \
|
||||
@brief The row values \
|
||||
@details Table values are not linear in memory - rows are in reverse order<br> \
|
||||
E.g. a 3x3 table with logical element [0][0] at the bottom left \
|
||||
(normal cartesian coordinates) has this layout:<br> \
|
||||
6, 7, 8, 3, 4, 5, 0, 1, 2 \
|
||||
*/ \
|
||||
table3d_value_t values[row_size*num_rows]; \
|
||||
\
|
||||
/** @brief Iterate over the values */ \
|
||||
inline table_value_iterator begin() \
|
||||
{ \
|
||||
return table_value_iterator(values, row_size); \
|
||||
} \
|
||||
\
|
||||
/** \
|
||||
@brief Direct access to table value element from a linear index \
|
||||
@details Since table values aren't laid out linearly, converting a linear \
|
||||
offset to the equivalent memory address requires a modulus operation.<br> \
|
||||
<br> \
|
||||
This is slow, since AVR hardware has no divider. We can gain performance \
|
||||
in 2 ways:<br> \
|
||||
1. Forcing uint8_t calculations. These are much faster than 16-bit calculations<br> \
|
||||
2. Compiling this per table *type*. This encodes the axis length as a constant \
|
||||
thus allowing the optimizing compiler more opportunity. E.g. for axis lengths \
|
||||
that are a power of 2, the modulus can be optimised to add/multiply/shift - much \
|
||||
cheaper than calling a software division routine such as __udivmodqi4<br> \
|
||||
<br> \
|
||||
THIS IS WORTH 20% to 30% speed up<br> \
|
||||
<br> \
|
||||
This limits us to 16x16 tables. If we need bigger and move to 16-bit \
|
||||
operations, consider using libdivide. <br> \
|
||||
*/ \
|
||||
inline table3d_value_t& value_at(table3d_dim_t linear_index) \
|
||||
{ \
|
||||
static_assert(row_size<17, "Table is too big"); \
|
||||
static_assert(num_rows<17, "Table is too big"); \
|
||||
constexpr table3d_dim_t first_index = row_size*(num_rows-1); \
|
||||
const table3d_dim_t index = first_index + (2*(linear_index % row_size)) - linear_index; \
|
||||
return values[index]; \
|
||||
} \
|
||||
};
|
||||
TABLE3D_GENERATOR(TABLE3D_GEN_VALUES)
|
||||
|
||||
/** @} */
|
|
@ -1,106 +0,0 @@
|
|||
#pragma once
|
||||
#include <Arduino.h>
|
||||
#include "table.h"
|
||||
#include "globals.h"
|
||||
|
||||
inline int8_t getTableYAxisFactor(const table3D *pTable)
|
||||
{
|
||||
return pTable==&boostTable || pTable==&vvtTable ? 1 : TABLE_LOAD_MULTIPLIER;
|
||||
}
|
||||
|
||||
inline int8_t getTableXAxisFactor(const table3D *)
|
||||
{
|
||||
return TABLE_RPM_MULTIPLIER;
|
||||
}
|
||||
|
||||
// ========================= AXIS ITERATION =========================
|
||||
typedef struct table_axis_iterator_t
|
||||
{
|
||||
int16_t *_pAxis;
|
||||
int16_t *_pAxisEnd;
|
||||
int16_t _axisFactor;
|
||||
int8_t _stride;
|
||||
} table_axis_iterator_t;
|
||||
|
||||
inline table_axis_iterator_t& advance_axis(table_axis_iterator_t &it)
|
||||
{
|
||||
it._pAxis += it._stride;
|
||||
return it;
|
||||
}
|
||||
|
||||
inline bool at_end(const table_axis_iterator_t &it)
|
||||
{
|
||||
return it._pAxis == it._pAxisEnd;
|
||||
}
|
||||
|
||||
inline byte get_value(const table_axis_iterator_t &it)
|
||||
{
|
||||
return *it._pAxis / it._axisFactor;
|
||||
}
|
||||
|
||||
inline void set_value(table_axis_iterator_t &it, byte value)
|
||||
{
|
||||
*it._pAxis = value * it._axisFactor;
|
||||
}
|
||||
|
||||
inline table_axis_iterator_t y_begin(const table3D *pTable)
|
||||
{
|
||||
return { pTable->axisY+(pTable->ySize-1),
|
||||
(pTable->axisY)-1, getTableYAxisFactor(pTable),
|
||||
-1 };
|
||||
}
|
||||
|
||||
inline table_axis_iterator_t y_rbegin(const table3D *pTable)
|
||||
{
|
||||
return { pTable->axisY, pTable->axisY + pTable->ySize, getTableYAxisFactor(pTable), 1 };
|
||||
}
|
||||
|
||||
inline table_axis_iterator_t x_begin(const table3D *pTable)
|
||||
{
|
||||
return { pTable->axisX, pTable->axisX+pTable->xSize, getTableXAxisFactor(pTable), 1 };
|
||||
}
|
||||
|
||||
// ========================= INTRA-ROW ITERATION =========================
|
||||
|
||||
// A table row is directly iterable & addressable.
|
||||
typedef struct table_row_t {
|
||||
byte *pValue;
|
||||
byte *pEnd;
|
||||
} table_row_t;
|
||||
|
||||
inline bool at_end(const table_row_t &it)
|
||||
{
|
||||
return it.pValue == it.pEnd;
|
||||
}
|
||||
|
||||
// ========================= INTER-ROW ITERATION =========================
|
||||
typedef struct table_row_iterator_t
|
||||
{
|
||||
byte **pRowsStart;
|
||||
byte **pRowsEnd;
|
||||
uint8_t rowWidth;
|
||||
} table_row_iterator_t;
|
||||
|
||||
inline table_row_iterator_t rows_begin(const table3D *pTable)
|
||||
{
|
||||
return { pTable->values + (pTable->ySize-1),
|
||||
pTable->values - 1,
|
||||
pTable->xSize
|
||||
};
|
||||
};
|
||||
|
||||
inline bool at_end(const table_row_iterator_t &it)
|
||||
{
|
||||
return it.pRowsStart == it.pRowsEnd;
|
||||
}
|
||||
|
||||
inline table_row_t get_row(const table_row_iterator_t &it)
|
||||
{
|
||||
return { *it.pRowsStart, (*it.pRowsStart) + it.rowWidth };
|
||||
}
|
||||
|
||||
inline table_row_iterator_t& advance_row(table_row_iterator_t &it)
|
||||
{
|
||||
--it.pRowsStart;
|
||||
return it;
|
||||
}
|
|
@ -21,12 +21,16 @@ void doUpdates()
|
|||
//May 2017 firmware introduced a -40 offset on the ignition table. Update that table to +40
|
||||
if(readEEPROMVersion() == 2)
|
||||
{
|
||||
for(int x=0; x<16; x++)
|
||||
auto table_it = ignitionTable.values.begin();
|
||||
while (!table_it.at_end())
|
||||
{
|
||||
for(int y=0; y<16; y++)
|
||||
auto row = *table_it;
|
||||
while (!row.at_end())
|
||||
{
|
||||
ignitionTable.values[x][y] = ignitionTable.values[x][y] + 40;
|
||||
}
|
||||
*row = *row + 40;
|
||||
++row;
|
||||
}
|
||||
++table_it;
|
||||
}
|
||||
writeAllConfig();
|
||||
storeEEPROMVersion(3);
|
||||
|
@ -464,13 +468,18 @@ void doUpdates()
|
|||
if(readEEPROMVersion() == 17)
|
||||
{
|
||||
//VVT stuff has now 0.5 accurasy, so shift values in vvt table by one.
|
||||
for(int x=0; x<8; x++)
|
||||
auto table_it = vvtTable.values.begin();
|
||||
while (!table_it.at_end())
|
||||
{
|
||||
for(int y=0; y<8; y++)
|
||||
auto row = *table_it;
|
||||
while (!row.at_end())
|
||||
{
|
||||
vvtTable.values[x][y] = vvtTable.values[x][y] << 1;
|
||||
}
|
||||
*row = *row << 1;
|
||||
++row;
|
||||
}
|
||||
++table_it;
|
||||
}
|
||||
|
||||
configPage10.vvtCLholdDuty = configPage10.vvtCLholdDuty << 1;
|
||||
configPage10.vvtCLminDuty = configPage10.vvtCLminDuty << 1;
|
||||
configPage10.vvtCLmaxDuty = configPage10.vvtCLmaxDuty << 1;
|
||||
|
|
|
@ -45,4 +45,22 @@ int16_t ProgrammableIOGetData(uint16_t index);
|
|||
#define _end_range_address(array) (array + _countof(array))
|
||||
#define _end_range_byte_address(array) (((byte*)array) + sizeof(array))
|
||||
|
||||
// Pre-processor arithmetic increment (pulled from Boost.Preprocessor)
|
||||
#define PP_INC(x) PP_INC_I(x)
|
||||
#define PP_INC_I(x) PP_INC_ ## x
|
||||
|
||||
#define PP_INC_0 1
|
||||
#define PP_INC_1 2
|
||||
#define PP_INC_2 3
|
||||
#define PP_INC_3 4
|
||||
#define PP_INC_4 5
|
||||
#define PP_INC_5 6
|
||||
#define PP_INC_6 7
|
||||
#define PP_INC_7 8
|
||||
#define PP_INC_8 9
|
||||
#define PP_INC_9 10
|
||||
#define PP_INC_10 11
|
||||
#define PP_INC_11 12
|
||||
#define PP_INC_12 13
|
||||
|
||||
#endif // UTILS_H
|
||||
|
|
|
@ -3,6 +3,25 @@
|
|||
#include <unity.h>
|
||||
#include "tests_tables.h"
|
||||
|
||||
const PROGMEM byte values[] = {
|
||||
109, 111, 112, 113, 114, 114, 114, 115, 115, 115, 114, 114, 113, 112, 111, 111,
|
||||
104, 106, 107, 108, 109, 109, 110, 110, 110, 110, 110, 109, 108, 107, 107, 106,
|
||||
98, 101, 103, 103, 104, 105, 105, 105, 105, 105, 105, 104, 104, 103, 102, 102,
|
||||
93, 96, 98, 99, 99, 100, 100, 101, 101, 101, 100, 100, 99, 98, 98, 97,
|
||||
81, 86, 88, 89, 90, 91, 91, 91, 91, 91, 91, 90, 90, 89, 89, 88,
|
||||
74, 80, 83, 84, 85, 86, 86, 86, 87, 86, 86, 86, 85, 84, 84, 84,
|
||||
68, 75, 78, 79, 81, 81, 81, 82, 82, 82, 82, 81, 81, 80, 79, 79,
|
||||
61, 69, 72, 74, 76, 76, 77, 77, 77, 77, 77, 76, 76, 75, 75, 74,
|
||||
54, 62, 66, 69, 71, 71, 72, 72, 72, 72, 72, 72, 71, 71, 70, 70,
|
||||
48, 56, 60, 64, 66, 66, 68, 68, 68, 68, 67, 67, 67, 66, 66, 65,
|
||||
42, 49, 54, 58, 61, 62, 62, 63, 63, 63, 63, 62, 62, 61, 61, 61,
|
||||
38, 43, 48, 52, 55, 56, 57, 58, 58, 58, 58, 58, 57, 57, 57, 56,
|
||||
36, 39, 42, 46, 50, 51, 52, 53, 53, 53, 53, 53, 53, 52, 52, 52,
|
||||
35, 36, 38, 41, 44, 46, 47, 48, 48, 49, 48, 48, 48, 48, 47, 47,
|
||||
34, 35, 36, 37, 39, 41, 42, 43, 43, 44, 44, 44, 43, 43, 43, 43,
|
||||
34, 34, 34, 34, 34, 34, 34, 34, 34, 35, 34, 34, 34, 34, 34, 34
|
||||
};
|
||||
|
||||
void setup_FuelTable(void)
|
||||
{
|
||||
//Setup the fuel table with some sane values for testing
|
||||
|
@ -28,47 +47,15 @@ void setup_FuelTable(void)
|
|||
500 | 700 | 900 | 1200 | 1600 | 2000 | 2500 | 3100 | 3500 | 4100 | 4700 | 5300 | 5900 | 6500 | 6750 | 7000
|
||||
*/
|
||||
|
||||
int tempXAxis[16] = {500,700, 900, 1200, 1600, 2000, 2500, 3100, 3500, 4100, 4700, 5300, 5900, 6500, 6750, 7000};
|
||||
for (byte x = 0; x< fuelTable.xSize; x++) { fuelTable.axisX[x] = tempXAxis[x]; }
|
||||
int tempYAxis[16] = {100, 96, 90, 86, 76, 70, 66, 60, 56, 50, 46, 40, 36, 30, 26, 16};
|
||||
for (byte x = 0; x< fuelTable.ySize; x++) { fuelTable.axisY[x] = tempYAxis[x]; }
|
||||
table3d_axis_t tempXAxis[] = {500,700, 900, 1200, 1600, 2000, 2500, 3100, 3500, 4100, 4700, 5300, 5900, 6500, 6750, 7000};
|
||||
memcpy(fuelTable.axisX, tempXAxis, sizeof(fuelTable.axisX));
|
||||
table3d_axis_t tempYAxis[] = {100, 96, 90, 86, 76, 70, 66, 60, 56, 50, 46, 40, 36, 30, 26, 16};
|
||||
memcpy(fuelTable.axisY, tempYAxis, sizeof(fuelTable.axisY));
|
||||
|
||||
|
||||
for (byte x = 0; x< fuelTable.xSize; x++) { fuelTable.values[0][x] = pgm_read_byte_near(tempRow1 + x); }
|
||||
for (byte x = 0; x< fuelTable.xSize; x++) { fuelTable.values[1][x] = pgm_read_byte_near(tempRow2 + x); }
|
||||
for (byte x = 0; x< fuelTable.xSize; x++) { fuelTable.values[2][x] = pgm_read_byte_near(tempRow3 + x); }
|
||||
for (byte x = 0; x< fuelTable.xSize; x++) { fuelTable.values[3][x] = pgm_read_byte_near(tempRow4 + x);}
|
||||
for (byte x = 0; x< fuelTable.xSize; x++) { fuelTable.values[4][x] = pgm_read_byte_near(tempRow5 + x); }
|
||||
for (byte x = 0; x< fuelTable.xSize; x++) { fuelTable.values[5][x] = pgm_read_byte_near(tempRow6 + x); }
|
||||
for (byte x = 0; x< fuelTable.xSize; x++) { fuelTable.values[6][x] = pgm_read_byte_near(tempRow7 + x); }
|
||||
for (byte x = 0; x< fuelTable.xSize; x++) { fuelTable.values[7][x] = pgm_read_byte_near(tempRow8 + x); }
|
||||
for (byte x = 0; x< fuelTable.xSize; x++) { fuelTable.values[8][x] = pgm_read_byte_near(tempRow9 + x); }
|
||||
for (byte x = 0; x< fuelTable.xSize; x++) { fuelTable.values[9][x] = pgm_read_byte_near(tempRow10 + x); }
|
||||
for (byte x = 0; x< fuelTable.xSize; x++) { fuelTable.values[10][x] = pgm_read_byte_near(tempRow11 + x); }
|
||||
for (byte x = 0; x< fuelTable.xSize; x++) { fuelTable.values[11][x] = pgm_read_byte_near(tempRow12 + x); }
|
||||
for (byte x = 0; x< fuelTable.xSize; x++) { fuelTable.values[12][x] = pgm_read_byte_near(tempRow13 + x); }
|
||||
for (byte x = 0; x< fuelTable.xSize; x++) { fuelTable.values[13][x] = pgm_read_byte_near(tempRow14 + x); }
|
||||
for (byte x = 0; x< fuelTable.xSize; x++) { fuelTable.values[14][x] = pgm_read_byte_near(tempRow15 + x); }
|
||||
for (byte x = 0; x< fuelTable.xSize; x++) { fuelTable.values[15][x] = pgm_read_byte_near(tempRow16 + x); }
|
||||
/*
|
||||
for (byte x = 0; x< fuelTable.xSize; x++) { fuelTable.values[0][x] = tempRow1[x]; }
|
||||
for (byte x = 0; x< fuelTable.xSize; x++) { fuelTable.values[1][x] = tempRow2[x]; }
|
||||
for (byte x = 0; x< fuelTable.xSize; x++) { fuelTable.values[2][x] = tempRow3[x]; }
|
||||
for (byte x = 0; x< fuelTable.xSize; x++) { fuelTable.values[3][x] = tempRow4[x]; }
|
||||
for (byte x = 0; x< fuelTable.xSize; x++) { fuelTable.values[4][x] = tempRow5[x]; }
|
||||
for (byte x = 0; x< fuelTable.xSize; x++) { fuelTable.values[5][x] = tempRow6[x]; }
|
||||
for (byte x = 0; x< fuelTable.xSize; x++) { fuelTable.values[6][x] = tempRow7[x]; }
|
||||
for (byte x = 0; x< fuelTable.xSize; x++) { fuelTable.values[7][x] = tempRow8[x]; }
|
||||
for (byte x = 0; x< fuelTable.xSize; x++) { fuelTable.values[8][x] = tempRow9[x]; }
|
||||
for (byte x = 0; x< fuelTable.xSize; x++) { fuelTable.values[9][x] = tempRow10[x]; }
|
||||
for (byte x = 0; x< fuelTable.xSize; x++) { fuelTable.values[10][x] = tempRow11[x]; }
|
||||
for (byte x = 0; x< fuelTable.xSize; x++) { fuelTable.values[11][x] = tempRow12[x]; }
|
||||
for (byte x = 0; x< fuelTable.xSize; x++) { fuelTable.values[12][x] = tempRow13[x]; }
|
||||
for (byte x = 0; x< fuelTable.xSize; x++) { fuelTable.values[13][x] = tempRow14[x]; }
|
||||
for (byte x = 0; x< fuelTable.xSize; x++) { fuelTable.values[14][x] = tempRow15[x]; }
|
||||
for (byte x = 0; x< fuelTable.xSize; x++) { fuelTable.values[15][x] = tempRow16[x]; }
|
||||
*/
|
||||
|
||||
for (int loop=0; loop<sizeof(fuelTable.values); ++loop)
|
||||
{
|
||||
fuelTable.values[loop] = pgm_read_byte_near(values + loop);
|
||||
}
|
||||
}
|
||||
|
||||
void testTables()
|
||||
|
|
|
@ -7,22 +7,4 @@ void test_tableLookup_overMaxX(void);
|
|||
void test_tableLookup_overMaxY(void);
|
||||
void test_tableLookup_underMinX(void);
|
||||
void test_tableLookup_underMinY(void);
|
||||
void test_all_incrementing(void);
|
||||
|
||||
//Go through the 8 rows and add the column values
|
||||
const PROGMEM byte tempRow1[16] = {109, 111, 112, 113, 114, 114, 114, 115, 115, 115, 114, 114, 113, 112, 111, 111};
|
||||
const PROGMEM byte tempRow2[16] = {104, 106, 107, 108, 109, 109, 110, 110, 110, 110, 110, 109, 108, 107, 107, 106};
|
||||
const PROGMEM byte tempRow3[16] = {98, 101, 103, 103, 104, 105, 105, 105, 105, 105, 105, 104, 104, 103, 102, 102};
|
||||
const PROGMEM byte tempRow4[16] = {93, 96, 98, 99, 99, 100, 100, 101, 101, 101, 100, 100, 99, 98, 98, 97};
|
||||
const PROGMEM byte tempRow5[16] = {81, 86, 88, 89, 90, 91, 91, 91, 91, 91, 91, 90, 90, 89, 89, 88};
|
||||
const PROGMEM byte tempRow6[16] = {74, 80, 83, 84, 85, 86, 86, 86, 87, 86, 86, 86, 85, 84, 84, 84};
|
||||
const PROGMEM byte tempRow7[16] = {68, 75, 78, 79, 81, 81, 81, 82, 82, 82, 82, 81, 81, 80, 79, 79};
|
||||
const PROGMEM byte tempRow8[16] = {61, 69, 72, 74, 76, 76, 77, 77, 77, 77, 77, 76, 76, 75, 75, 74};
|
||||
const PROGMEM byte tempRow9[16] = {54, 62, 66, 69, 71, 71, 72, 72, 72, 72, 72, 72, 71, 71, 70, 70};
|
||||
const PROGMEM byte tempRow10[16] = {48, 56, 60, 64, 66, 66, 68, 68, 68, 68, 67, 67, 67, 66, 66, 65};
|
||||
const PROGMEM byte tempRow11[16] = {42, 49, 54, 58, 61, 62, 62, 63, 63, 63, 63, 62, 62, 61, 61, 61};
|
||||
const PROGMEM byte tempRow12[16] = {38, 43, 48, 52, 55, 56, 57, 58, 58, 58, 58, 58, 57, 57, 57, 56};
|
||||
const PROGMEM byte tempRow13[16] = {36, 39, 42, 46, 50, 51, 52, 53, 53, 53, 53, 53, 53, 52, 52, 52};
|
||||
const PROGMEM byte tempRow14[16] = {35, 36, 38, 41, 44, 46, 47, 48, 48, 49, 48, 48, 48, 48, 47, 47};
|
||||
const PROGMEM byte tempRow15[16] = {34, 35, 36, 37, 39, 41, 42, 43, 43, 44, 44, 44, 43, 43, 43, 43};
|
||||
const PROGMEM byte tempRow16[16] = {34, 34, 34, 34, 34, 34, 34, 34, 34, 35, 34, 34, 34, 34, 34, 34};
|
||||
void test_all_incrementing(void);
|
Loading…
Reference in New Issue