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:
tx_haggis 2021-11-17 18:30:29 -06:00 committed by GitHub
parent aaa5b9090f
commit 5ed9ec3ca9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 1923 additions and 1316 deletions

386
Doxyfile
View File

@ -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

View File

@ -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 &currentTable)
void print_x_axis(const void *pTable, table_type_t key)
{
Serial.print(F(" "));
auto x_it = x_begin(&currentTable);
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 &currentTable)
void serial_print_3dtable(const void *pTable, table_type_t key)
{
auto y_it = y_begin(&currentTable);
auto row_it = rows_begin(&currentTable);
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:

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

47
speeduino/int16_ref.h Normal file
View File

@ -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;
};
/** @} */

View File

@ -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:

View File

@ -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);
}

View File

@ -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);

View File

@ -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"

View File

@ -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);
//*********************************************************************************************************************************************************************************
}

View File

@ -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

View File

@ -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;
}

39
speeduino/table2d.h Normal file
View File

@ -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

147
speeduino/table2d.ino Normal file
View File

@ -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;
}

33
speeduino/table3d.cpp Normal file
View File

@ -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);
}

123
speeduino/table3d.h Normal file
View File

@ -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);
/** @} */

146
speeduino/table3d_axes.h Normal file
View File

@ -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)
/** @} */

View File

@ -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;
}

View File

@ -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);

View File

@ -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)
/** @} */

199
speeduino/table3d_values.h Normal file
View File

@ -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)
/** @} */

View File

@ -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;
}

View File

@ -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;

View File

@ -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

View File

@ -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()

View File

@ -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);