Performance: optimized 32-bit shifts (#1187)

* Add optimized 32-bit shifting

* Tooth based time to angle coversion is only used by a few decoders.
So move the functions into decoders.cpp

* Better separation of deocders and crank maths.

* Apply optimised shifts

* Doxygen
This commit is contained in:
tx_haggis 2024-05-29 23:12:14 -05:00 committed by GitHub
parent 478e16cc52
commit 0f13753ed3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 1259 additions and 310 deletions

380
Doxyfile
View File

@ -1,4 +1,4 @@
# Doxyfile 1.9.4
# Doxyfile 1.10.0
# This file describes the settings to be used by the documentation system
# doxygen (www.doxygen.org) for a project.
@ -19,7 +19,8 @@
# configuration file:
# doxygen -x [configFile]
# Use doxygen to compare the used configuration file with the template
# configuration file without replacing the environment variables:
# configuration file without replacing the environment variables or CMake type
# replacement variables:
# doxygen -x_noenv [configFile]
#---------------------------------------------------------------------------
@ -62,6 +63,12 @@ PROJECT_BRIEF =
PROJECT_LOGO = reference/speeduino_logo.png
# With the PROJECT_ICON tag one can specify an icon that is included in the tabs
# when the HTML document is shown. Doxygen will copy the logo to the output
# directory.
PROJECT_ICON =
# 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
# entered, it will be relative to the location where doxygen was started. If
@ -85,7 +92,7 @@ CREATE_SUBDIRS = NO
# level increment doubles the number of directories, resulting in 4096
# directories at level 8 which is the default and also the maximum value. The
# sub-directories are organized in 2 levels, the first level always has a fixed
# numer of 16 directories.
# number of 16 directories.
# Minimum value: 0, maximum value: 8, default value: 8.
# This tag requires that the tag CREATE_SUBDIRS is set to YES.
@ -362,6 +369,17 @@ MARKDOWN_SUPPORT = YES
TOC_INCLUDE_HEADINGS = 0
# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to
# generate identifiers for the Markdown headings. Note: Every identifier is
# unique.
# Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a
# sequence number starting at 0 and GITHUB use the lower case version of title
# with any whitespace replaced by '-' and punctuation characters removed.
# The default value is: DOXYGEN.
# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
MARKDOWN_ID_STYLE = DOXYGEN
# When enabled doxygen tries to link words that correspond to documented
# classes, or namespaces to their corresponding documentation. Such a link can
# be prevented in individual cases by putting a % sign in front of the word or
@ -486,6 +504,14 @@ LOOKUP_CACHE_SIZE = 0
NUM_PROC_THREADS = 1
# If the TIMESTAMP tag is set different from NO then each generated page will
# contain the date or date and time when the page was generated. Setting this to
# NO can help when comparing the output of multiple runs.
# Possible values are: YES, NO, DATETIME and DATE.
# The default value is: NO.
TIMESTAMP = NO
#---------------------------------------------------------------------------
# Build related configuration options
#---------------------------------------------------------------------------
@ -567,7 +593,8 @@ HIDE_UNDOC_MEMBERS = NO
# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
# undocumented classes that are normally visible in the class hierarchy. If set
# to NO, these classes will be included in the various overviews. This option
# has no effect if EXTRACT_ALL is enabled.
# will also hide undocumented C++ concepts if enabled. This option has no effect
# if EXTRACT_ALL is enabled.
# The default value is: NO.
HIDE_UNDOC_CLASSES = NO
@ -605,7 +632,8 @@ INTERNAL_DOCS = NO
# 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.
# Possible values are: SYSTEM, NO and YES.
# The default value is: SYSTEM.
CASE_SENSE_NAMES = NO
@ -857,11 +885,26 @@ WARN_IF_INCOMPLETE_DOC = YES
WARN_NO_PARAMDOC = NO
# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about
# undocumented enumeration values. If set to NO, doxygen will accept
# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag
# will automatically be disabled.
# The default value is: NO.
WARN_IF_UNDOC_ENUM_VAL = NO
# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
# 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.
# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then doxygen behaves
# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined doxygen will not
# write the warning messages in between other messages but write them at the end
# of a run, in case a WARN_LOGFILE is defined the warning messages will be
# besides being in the defined file also be shown at the end of a run, unless
# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case
# the behavior will remain as with the setting FAIL_ON_WARNINGS.
# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT.
# The default value is: NO.
WARN_AS_ERROR = NO
@ -914,10 +957,21 @@ INPUT = speeduino/ \
# 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.
# See also: INPUT_FILE_ENCODING
# The default value is: UTF-8.
INPUT_ENCODING = UTF-8
# This tag can be used to specify the character encoding of the source files
# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify
# character encoding on a per file pattern basis. Doxygen will compare the file
# name with each pattern and apply the encoding instead of the default
# INPUT_ENCODING) if there is a match. The character encodings are a list of the
# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding
# "INPUT_ENCODING" for further information on supported encodings.
INPUT_FILE_ENCODING =
# If the value of the INPUT tag contains directories, you can use the
# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
# *.h) to filter out the source-files in the directories.
@ -929,12 +983,12 @@ INPUT_ENCODING = UTF-8
# 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++, *.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.
# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm,
# *.cpp, *.cppm, *.ccm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl,
# *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.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 \
*.cc \
@ -1020,9 +1074,6 @@ EXCLUDE_PATTERNS =
# output. The symbol name can be a fully qualified name, a word, or if the
# wildcard * is used, a substring. Examples: ANamespace, AClass,
# ANamespace::AClass, ANamespace::*Test
#
# Note that the wildcards are matched against the file with absolute path, so to
# exclude all test directories use the pattern */test/*
EXCLUDE_SYMBOLS = TO_TYPE_KEY \
CTA_* \
@ -1072,6 +1123,11 @@ IMAGE_PATH =
# code is scanned, but not when the output code is generated. If lines are added
# or removed, the anchors will not be placed correctly.
#
# Note that doxygen will use the data processed and written to standard output
# for further processing, therefore nothing else, like debug statements or used
# commands (so in case of a Windows batch file always use @echo OFF), should be
# written to standard output.
#
# Note that for custom extensions or not directly supported extensions you also
# need to set EXTENSION_MAPPING for the extension otherwise the files are not
# properly processed by doxygen.
@ -1113,6 +1169,15 @@ FILTER_SOURCE_PATTERNS =
USE_MDFILE_AS_MAINPAGE =
# The Fortran standard specifies that for fixed formatted Fortran code all
# characters from position 72 are to be considered as comment. A common
# extension is to allow longer lines before the automatic comment starts. The
# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can
# be processed before the automatic comment starts.
# Minimum value: 7, maximum value: 10000, default value: 72.
FORTRAN_COMMENT_AFTER = 72
#---------------------------------------------------------------------------
# Configuration options related to source browsing
#---------------------------------------------------------------------------
@ -1127,7 +1192,8 @@ USE_MDFILE_AS_MAINPAGE =
SOURCE_BROWSER = NO
# Setting the INLINE_SOURCES tag to YES will include the body of functions,
# classes and enums directly into the documentation.
# multi-line macros, enums or list initialized variables directly into the
# documentation.
# The default value is: NO.
INLINE_SOURCES = NO
@ -1250,10 +1316,11 @@ CLANG_DATABASE_PATH =
ALPHABETICAL_INDEX = YES
# 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
# while generating the index headers.
# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes)
# that should be ignored while generating the index headers. The IGNORE_PREFIX
# tag works for classes, function and member names. The entity will be placed in
# the alphabetical list under the first letter of the entity name that remains
# after removing the prefix.
# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
IGNORE_PREFIX =
@ -1332,7 +1399,12 @@ HTML_STYLESHEET =
# Doxygen will copy the style sheet files to the output directory.
# Note: The order of the extra style sheet files is of importance (e.g. the last
# style sheet in the list overrules the setting of the previous ones in the
# list). For an example see the documentation.
# list).
# Note: Since the styling of scrollbars can currently not be overruled in
# Webkit/Chromium, the styling will be left out of the default doxygen.css if
# one or more extra stylesheets have been specified. So if scrollbar
# customization is desired it has to be added explicitly. For an example see the
# documentation.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_EXTRA_STYLESHEET =
@ -1347,6 +1419,19 @@ HTML_EXTRA_STYLESHEET =
HTML_EXTRA_FILES =
# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output
# should be rendered with a dark or light theme.
# Possible values are: LIGHT always generate light mode output, DARK always
# generate dark mode output, AUTO_LIGHT automatically set the mode according to
# the user preference, use light mode if no preference is set (the default),
# AUTO_DARK automatically set the mode according to the user preference, use
# dark mode if no preference is set and TOGGLE allow to user to switch between
# light and dark mode via a button.
# The default value is: AUTO_LIGHT.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_COLORSTYLE = AUTO_LIGHT
# 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 color-wheel, see
@ -1377,15 +1462,6 @@ HTML_COLORSTYLE_SAT = 100
HTML_COLORSTYLE_GAMMA = 80
# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
# page will contain the date and time when the page was generated. Setting this
# to YES can help to show when doxygen was last run and thus if the
# documentation is up to date.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_TIMESTAMP = YES
# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
# documentation will contain a main index with vertical navigation menus that
# are dynamically created via JavaScript. If disabled, the navigation index will
@ -1405,6 +1481,33 @@ HTML_DYNAMIC_MENUS = YES
HTML_DYNAMIC_SECTIONS = NO
# If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be
# dynamically folded and expanded in the generated HTML source code.
# The default value is: YES.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_CODE_FOLDING = YES
# If the HTML_COPY_CLIPBOARD tag is set to YES then doxygen will show an icon in
# the top right corner of code and text fragments that allows the user to copy
# its content to the clipboard. Note this only works if supported by the browser
# and the web page is served via a secure context (see:
# https://www.w3.org/TR/secure-contexts/), i.e. using the https: or file:
# protocol.
# The default value is: YES.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_COPY_CLIPBOARD = YES
# Doxygen stores a couple of settings persistently in the browser (via e.g.
# cookies). By default these settings apply to all HTML pages generated by
# doxygen across all projects. The HTML_PROJECT_COOKIE tag can be used to store
# the settings under a project specific key, such that the user preferences will
# be stored separately.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_PROJECT_COOKIE =
# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
# shown in the various tree structured indices initially; the user can expand
# and collapse entries dynamically later on. Doxygen will expand the tree to
@ -1535,6 +1638,16 @@ BINARY_TOC = NO
TOC_EXPAND = NO
# The SITEMAP_URL tag is used to specify the full URL of the place where the
# generated documentation will be placed on the server by the user during the
# deployment of the documentation. The generated sitemap is called sitemap.xml
# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL
# is specified no sitemap is generated. For information about the sitemap
# protocol see https://www.sitemaps.org
# This tag requires that the tag GENERATE_HTML is set to YES.
SITEMAP_URL =
# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
@ -1710,17 +1823,6 @@ HTML_FORMULA_FORMAT = png
FORMULA_FONTSIZE = 10
# Use the FORMULA_TRANSPARENT tag to determine whether or not the images
# generated for formulas are transparent PNGs. Transparent PNGs are not
# supported properly for IE 6.0, but are supported on all modern browsers.
#
# Note that when changing this option you need to delete any form_*.png files in
# the HTML output directory before the changes have effect.
# The default value is: YES.
# This tag requires that the tag GENERATE_HTML is set to YES.
FORMULA_TRANSPARENT = YES
# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands
# to create new LaTeX commands to be used in formulas as building blocks. See
# the section "Including formulas" for details.
@ -2034,9 +2136,16 @@ PDF_HYPERLINKS = YES
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.
# The LATEX_BATCHMODE tag signals the behavior of LaTeX in case of an error.
# Possible values are: NO same as ERROR_STOP, YES same as BATCH, BATCH In batch
# mode nothing is printed on the terminal, errors are scrolled as if <return> is
# hit at every error; missing files that TeX tries to input or request from
# keyboard input (\read on a not open input stream) cause the job to abort,
# NON_STOP In nonstop mode the diagnostic message will appear on the terminal,
# but there is no possibility of user interaction just like in batch mode,
# SCROLL In scroll mode, TeX will stop only for missing files to input or if
# keyboard input is necessary and ERROR_STOP In errorstop mode, TeX will stop at
# each error, asking for user intervention.
# The default value is: NO.
# This tag requires that the tag GENERATE_LATEX is set to YES.
@ -2057,14 +2166,6 @@ LATEX_HIDE_INDICES = NO
LATEX_BIB_STYLE = plain
# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
# page will contain the date and time when the page was generated. Setting this
# to NO can help when comparing the output of multiple runs.
# The default value is: NO.
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_TIMESTAMP = NO
# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)
# path from which the emoji images will be read. If a relative path is entered,
# it will be relative to the LATEX_OUTPUT directory. If left blank the
@ -2230,7 +2331,7 @@ DOCBOOK_OUTPUT = docbook
#---------------------------------------------------------------------------
# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures
# AutoGen Definitions (see https://autogen.sourceforge.net/) file that captures
# the structure of the code including all documentation. Note that this feature
# is still experimental and incomplete at the moment.
# The default value is: NO.
@ -2241,6 +2342,28 @@ GENERATE_AUTOGEN_DEF = NO
# Configuration options related to Sqlite3 output
#---------------------------------------------------------------------------
# If the GENERATE_SQLITE3 tag is set to YES doxygen will generate a Sqlite3
# database with symbols found by doxygen stored in tables.
# The default value is: NO.
GENERATE_SQLITE3 = NO
# The SQLITE3_OUTPUT tag is used to specify where the Sqlite3 database will be
# put. If a relative path is entered the value of OUTPUT_DIRECTORY will be put
# in front of it.
# The default directory is: sqlite3.
# This tag requires that the tag GENERATE_SQLITE3 is set to YES.
SQLITE3_OUTPUT = sqlite3
# The SQLITE3_RECREATE_DB tag is set to YES, the existing doxygen_sqlite3.db
# database file will be recreated with each doxygen run. If set to NO, doxygen
# will warn if a database file is already found and not modify it.
# The default value is: YES.
# This tag requires that the tag GENERATE_SQLITE3 is set to YES.
SQLITE3_RECREATE_DB = YES
#---------------------------------------------------------------------------
# Configuration options related to the Perl module output
#---------------------------------------------------------------------------
@ -2338,7 +2461,8 @@ INCLUDE_FILE_PATTERNS =
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
PREDEFINED = __attribute__((x))= \
PROGMEM
PROGMEM \
USE_OPTIMIZED_SHIFTS=1
# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
# tag can be used to specify a list of macro names that should be expanded. The
@ -2388,15 +2512,15 @@ TAGFILES =
GENERATE_TAGFILE =
# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
# the class index. If set to NO, only the inherited external classes will be
# listed.
# If the ALLEXTERNALS tag is set to YES, all external classes and namespaces
# will be listed in the class and namespace index. If set to NO, only the
# inherited external classes will be listed.
# The default value is: NO.
ALLEXTERNALS = NO
# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
# in the modules index. If set to NO, only the current project's groups will be
# in the topic index. If set to NO, only the current project's groups will be
# listed.
# The default value is: YES.
@ -2410,16 +2534,9 @@ EXTERNAL_GROUPS = YES
EXTERNAL_PAGES = YES
#---------------------------------------------------------------------------
# Configuration options related to the dot tool
# Configuration options related to diagram generator tools
#---------------------------------------------------------------------------
# You can include diagrams made with dia in doxygen documentation. Doxygen will
# then run dia to produce the diagram and insert it in the documentation. The
# DIA_PATH tag allows you to specify the directory where the dia binary resides.
# If left empty dia is assumed to be found in the default search path.
DIA_PATH =
# If set to YES the inheritance and collaboration graphs will hide inheritance
# and usage relations if the target is undocumented or is not a class.
# The default value is: YES.
@ -2428,7 +2545,7 @@ HIDE_UNDOC_RELATIONS = YES
# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
# available from the path. This tool is part of Graphviz (see:
# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
# https://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
# Bell Labs. The other options in this section have no effect if this option is
# set to NO
# The default value is: NO.
@ -2445,37 +2562,55 @@ HAVE_DOT = YES
DOT_NUM_THREADS = 0
# When you want a differently looking font in the dot files that doxygen
# generates you can specify the font name using DOT_FONTNAME. You need to make
# sure dot is able to find the font, which can be done by putting it in a
# standard location or by setting the DOTFONTPATH environment variable or by
# setting DOT_FONTPATH to the directory containing the font.
# The default value is: Helvetica.
# DOT_COMMON_ATTR is common attributes for nodes, edges and labels of
# subgraphs. When you want a differently looking font in the dot files that
# doxygen generates you can specify fontname, fontcolor and fontsize attributes.
# For details please see <a href=https://graphviz.org/doc/info/attrs.html>Node,
# Edge and Graph Attributes specification</a> You need to make sure dot is able
# to find the font, which can be done by putting it in a standard location or by
# setting the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the
# directory containing the font. Default graphviz fontsize is 14.
# The default value is: fontname=Helvetica,fontsize=10.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_FONTNAME = Helvetica
DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10"
# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
# dot graphs.
# Minimum value: 4, maximum value: 24, default value: 10.
# DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can
# add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. <a
# href=https://graphviz.org/doc/info/arrows.html>Complete documentation about
# arrows shapes.</a>
# The default value is: labelfontname=Helvetica,labelfontsize=10.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_FONTSIZE = 10
DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10"
# By default doxygen will tell dot to use the default font as specified with
# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
# the path where dot can find it using this tag.
# DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes
# around nodes set 'shape=plain' or 'shape=plaintext' <a
# href=https://www.graphviz.org/doc/info/shapes.html>Shapes specification</a>
# The default value is: shape=box,height=0.2,width=0.4.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4"
# You can set the path where dot can find font specified with fontname in
# DOT_COMMON_ATTR and others dot attributes.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_FONTPATH =
# If the CLASS_GRAPH tag is set to YES (or GRAPH) then doxygen will generate a
# graph for each documented class showing the direct and indirect inheritance
# relations. In case HAVE_DOT is set as well dot will be used to draw the graph,
# otherwise the built-in generator will be used. If the CLASS_GRAPH tag is set
# to TEXT the direct and indirect inheritance relations will be shown as texts /
# links.
# Possible values are: NO, YES, TEXT and GRAPH.
# If the CLASS_GRAPH tag is set to YES or GRAPH or BUILTIN then doxygen will
# generate a graph for each documented class showing the direct and indirect
# inheritance relations. In case the CLASS_GRAPH tag is set to YES or GRAPH and
# HAVE_DOT is enabled as well, then dot will be used to draw the graph. In case
# the CLASS_GRAPH tag is set to YES and HAVE_DOT is disabled or if the
# CLASS_GRAPH tag is set to BUILTIN, then the built-in generator will be used.
# If the CLASS_GRAPH tag is set to TEXT the direct and indirect inheritance
# relations will be shown as texts / links. Explicit enabling an inheritance
# graph or choosing a different representation for an inheritance graph of a
# specific class, can be accomplished by means of the command \inheritancegraph.
# Disabling an inheritance graph can be accomplished by means of the command
# \hideinheritancegraph.
# Possible values are: NO, YES, TEXT, GRAPH and BUILTIN.
# The default value is: YES.
CLASS_GRAPH = YES
@ -2483,15 +2618,21 @@ CLASS_GRAPH = YES
# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
# graph for each documented class showing the direct and indirect implementation
# dependencies (inheritance, containment, and class references variables) of the
# class with other documented classes.
# class with other documented classes. Explicit enabling a collaboration graph,
# when COLLABORATION_GRAPH is set to NO, can be accomplished by means of the
# command \collaborationgraph. Disabling a collaboration graph can be
# accomplished by means of the command \hidecollaborationgraph.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.
COLLABORATION_GRAPH = YES
# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
# groups, showing the direct groups dependencies. See also the chapter Grouping
# in the manual.
# groups, showing the direct groups dependencies. Explicit enabling a group
# dependency graph, when GROUP_GRAPHS is set to NO, can be accomplished by means
# of the command \groupgraph. Disabling a directory graph can be accomplished by
# means of the command \hidegroupgraph. See also the chapter Grouping in the
# manual.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.
@ -2533,8 +2674,8 @@ 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.
# significantly it will be wrapped across multiple lines. Some heuristics are
# applied 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.
@ -2551,7 +2692,9 @@ TEMPLATE_RELATIONS = NO
# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
# YES then doxygen will generate a graph for each documented file showing the
# direct and indirect include dependencies of the file with other documented
# files.
# files. Explicit enabling an include graph, when INCLUDE_GRAPH is is set to NO,
# can be accomplished by means of the command \includegraph. Disabling an
# include graph can be accomplished by means of the command \hideincludegraph.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.
@ -2560,7 +2703,10 @@ INCLUDE_GRAPH = NO
# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
# set to YES then doxygen will generate a graph for each documented file showing
# the direct and indirect include dependencies of the file with other documented
# files.
# files. Explicit enabling an included by graph, when INCLUDED_BY_GRAPH is set
# to NO, can be accomplished by means of the command \includedbygraph. Disabling
# an included by graph can be accomplished by means of the command
# \hideincludedbygraph.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.
@ -2600,7 +2746,10 @@ GRAPHICAL_HIERARCHY = NO
# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
# dependencies a directory has on other directories in a graphical way. The
# dependency relations are determined by the #include relations between the
# files in the directories.
# files in the directories. Explicit enabling a directory graph, when
# DIRECTORY_GRAPH is set to NO, can be accomplished by means of the command
# \directorygraph. Disabling a directory graph can be accomplished by means of
# the command \hidedirectorygraph.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.
@ -2616,7 +2765,7 @@ DIR_GRAPH_MAX_DEPTH = 1
# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
# generated by dot. For an explanation of the image formats see the section
# output formats in the documentation of the dot tool (Graphviz (see:
# http://www.graphviz.org/)).
# https://www.graphviz.org/)).
# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
# to make the SVG files visible in IE 9+ (other browsers do not have this
# requirement).
@ -2653,11 +2802,12 @@ DOT_PATH =
DOTFILE_DIRS =
# The MSCFILE_DIRS tag can be used to specify one or more directories that
# contain msc files that are included in the documentation (see the \mscfile
# command).
# You can include diagrams made with dia in doxygen documentation. Doxygen will
# then run dia to produce the diagram and insert it in the documentation. The
# DIA_PATH tag allows you to specify the directory where the dia binary resides.
# If left empty dia is assumed to be found in the default search path.
MSCFILE_DIRS =
DIA_PATH =
# The DIAFILE_DIRS tag can be used to specify one or more directories that
# contain dia files that are included in the documentation (see the \diafile
@ -2707,18 +2857,6 @@ DOT_GRAPH_MAX_NODES = 500
MAX_DOT_GRAPH_DEPTH = 0
# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
# background. This is disabled by default, because dot on Windows does not seem
# to support this out of the box.
#
# Warning: Depending on the platform used, enabling this option may lead to
# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
# read).
# The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_TRANSPARENT = YES
# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
# files in one run (i.e. multiple -o and -T options on the command line). This
# makes dot run faster, but since only newer versions of dot (>1.8.10) support
@ -2746,3 +2884,19 @@ GENERATE_LEGEND = YES
# The default value is: YES.
DOT_CLEANUP = YES
# You can define message sequence charts within doxygen comments using the \msc
# command. If the MSCGEN_TOOL tag is left empty (the default), then doxygen will
# use a built-in version of mscgen tool to produce the charts. Alternatively,
# the MSCGEN_TOOL tag can also specify the name an external tool. For instance,
# specifying prog as the value, doxygen will call the tool as prog -T
# <outfile_format> -o <outputfile> <inputfile>. The external tool should support
# output file formats "png", "eps", "svg", and "ismap".
MSCGEN_TOOL =
# The MSCFILE_DIRS tag can be used to specify one or more directories that
# contain msc files that are included in the documentation (see the \mscfile
# command).
MSCFILE_DIRS =

687
speeduino/bit_shifts.h Normal file
View File

@ -0,0 +1,687 @@
#pragma once
/** @file
* @brief Optimized multi-byte bit shifting *for AVR-GCC only*. See @ref group-opt-shift
*/
#include <stdint.h>
#include "globals.h" // Required for CPU/architecture preprocessor symbols
/// @defgroup group-opt-shift Optimised bitwise shifts
///
/// @brief As of AVR-GCC 13.2.0, the code produced for 32-bit shifts with a compile time shift distance is very poor.
/// The templates here implement optimised shifting. Average 35% increase in shift performance on AtMega2560: see unit tests.
///
/// Usage:
/// @code
/// rpmDelta = lshift<10>(toothDeltaV) / (6 * toothDeltaT);
/// @endcode
///
/// If there is no overload for a certain shift, that's because GCC produced decent ASM
/// in that case.
///
/// @note Code is usable on all architectures, but the optimization only applies to AVR-GCC.
/// Other compilers will see a standard bitwise shift.
/// @{
// Flag if we should turn on optimized shifts
#if !defined(USE_OPTIMIZED_SHIFTS)
#if (defined(CORE_AVR) || defined(ARDUINO_ARCH_AVR)) && defined(__GNUC__)
#define USE_OPTIMIZED_SHIFTS 1
#else
#define USE_OPTIMIZED_SHIFTS 0
#endif
#endif
/**
* @brief Bitwise left shift - generic, unoptimized, case
*
* @tparam b number of bits to shift by
* @param a value to shift
* @return uint32_t a<<b
*/
template <uint8_t b>
static inline uint32_t lshift(uint32_t a) {
#if USE_OPTIMIZED_SHIFTS==1
// The shifts below have been validated to produce performant code in GCC.
// Other shift amounts are either in a specialized template below (good) or are unvalidated (bad).
static_assert(b==1 || b==2 || b==3 || b==8 || b==16 || b==24,
"Unvalidated shift - confirm gcc produces performant code");
#endif
return a << b;
}
#if USE_OPTIMIZED_SHIFTS==1
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
/// @{
/// @brief uint32_t bitwise left shift optimised for the specified shift distance
///
/// @note The assembler was generated using clang 17.0.1 cross compiling: -O3 --target=avr -mmcu=atmega2560. See https://godbolt.org/z/71cPnMYqs
///
/// @param a value to shift
/// @return uint32_t a<<b
template <>
uint32_t lshift<4U>(uint32_t a)
{
asm(
"swap %D0\n"
"andi %D0, 240\n"
"swap %C0\n"
"eor %D0, %C0\n"
"andi %C0, 240\n"
"eor %D0, %C0\n"
"swap %B0\n"
"eor %C0, %B0\n"
"andi %B0, 240\n"
"eor %C0, %B0\n"
"swap %A0\n"
"eor %B0, %A0\n"
"andi %A0, 240\n"
"eor %B0, %A0\n"
: "=d" (a)
: "0" (a)
:
);
return a;
}
template <>
uint32_t lshift<5U>(uint32_t a)
{
asm(
"swap %D0\n"
"andi %D0, 240\n"
"swap %C0\n"
"eor %D0, %C0\n"
"andi %C0, 240\n"
"eor %D0, %C0\n"
"swap %B0\n"
"eor %C0, %B0\n"
"andi %B0, 240\n"
"eor %C0, %B0\n"
"swap %A0\n"
"eor %B0, %A0\n"
"andi %A0, 240\n"
"eor %B0, %A0\n"
"lsl %A0\n"
"rol %B0\n"
"rol %C0\n"
"rol %D0\n"
: "=d" (a)
: "0" (a)
:
);
return a;
}
template <>
uint32_t lshift<6U>(uint32_t a)
{
asm(
"lsr %D0\n"
"ror %C0\n"
"ror %B0\n"
"ror %A0\n"
"mov r18, __zero_reg__\n"
"ror r18\n"
"lsr %D0\n"
"ror %C0\n"
"ror %B0\n"
"ror %A0\n"
"ror r18\n"
"mov %D0, %C0\n"
"mov %C0, %B0\n"
"mov r19, %A0\n"
"movw %A0, r18\n"
: "=d" (a)
: "0" (a)
: "r18", "r19"
);
return a;
}
template <>
uint32_t lshift<7U>(uint32_t a)
{
asm(
"lsr %D0\n"
"ror %C0\n"
"ror %B0\n"
"ror %A0\n"
"mov r18, __zero_reg__\n"
"ror r18\n"
"mov %D0, %C0\n"
"mov %C0, %B0\n"
"mov r19, %A0\n"
"movw %A0, r18\n"
: "=d" (a)
: "0" (a)
: "r18", "r19"
);
return a;
}
template <>
uint32_t lshift<9U>(uint32_t a)
{
asm(
"lsl %A0\n"
"rol %B0\n"
"rol %C0\n"
"mov %D0, %C0\n"
"mov %C0, %B0\n"
"mov %B0, %A0\n"
"mov %A0, __zero_reg__\n"
: "=d" (a)
: "0" (a)
:
);
return a;
}
template <>
uint32_t lshift<10U>(uint32_t a)
{
asm(
"lsl %A0\n"
"rol %B0\n"
"rol %C0\n"
"lsl %A0\n"
"rol %B0\n"
"rol %C0\n"
"mov %D0, %C0\n"
"mov %C0, %B0\n"
"mov %B0, %A0\n"
"mov %A0, __zero_reg__\n"
: "=d" (a)
: "0" (a)
:
);
return a;
}
template <>
uint32_t lshift<11U>(uint32_t a)
{
asm(
"lsl %A0\n"
"rol %B0\n"
"rol %C0\n"
"lsl %A0\n"
"rol %B0\n"
"rol %C0\n"
"lsl %A0\n"
"rol %B0\n"
"rol %C0\n"
"mov %D0, %C0\n"
"mov %C0, %B0\n"
"mov %B0, %A0\n"
"mov %A0, __zero_reg__\n"
: "=d" (a)
: "0" (a)
:
);
return a;
}
template <>
uint32_t lshift<12U>(uint32_t a)
{
asm(
"swap %C0\n"
"andi %C0, 240\n"
"swap %B0\n"
"eor %C0, %B0\n"
"andi %B0, 240\n"
"eor %C0, %B0\n"
"swap %A0\n"
"eor %B0, %A0\n"
"andi %A0, 240\n"
"eor %B0, %A0\n"
"mov %D0, %C0\n"
"mov %C0, %B0\n"
"mov %B0, %A0\n"
"mov %A0, __zero_reg__\n"
: "=d" (a)
: "0" (a)
:
);
return a;
}
template <>
uint32_t lshift<13U>(uint32_t a)
{
asm(
"swap %C0\n"
"andi %C0, 240\n"
"swap %B0\n"
"eor %C0, %B0\n"
"andi %B0, 240\n"
"eor %C0, %B0\n"
"swap %A0\n"
"eor %B0, %A0\n"
"andi %A0, 240\n"
"eor %B0, %A0\n"
"lsl %A0\n"
"rol %B0\n"
"rol %C0\n"
"mov %D0, %C0\n"
"mov %C0, %B0\n"
"mov %B0, %A0\n"
"mov %A0, __zero_reg__\n"
: "=d" (a)
: "0" (a)
:
);
return a;
}
template <>
uint32_t lshift<14U>(uint32_t a)
{
asm(
"movw r18, %A0\n"
"lsr %C0\n"
"ror r19\n"
"ror r18\n"
"mov %B0, __zero_reg__\n"
"ror %B0\n"
"lsr %C0\n"
"ror r19\n"
"ror r18\n"
"ror %B0\n"
"mov %A0, __zero_reg__\n"
"movw %C0, r18\n"
: "=d" (a)
: "0" (a)
: "r18", "r19"
);
return a;
}
template <>
uint32_t lshift<15U>(uint32_t a)
{
asm(
"movw r18, %A0\n"
"lsr %C0\n"
"ror r19\n"
"ror r18\n"
"mov %B0, __zero_reg__\n"
"ror %B0\n"
"mov %A0, __zero_reg__\n"
"movw %C0, r18\n"
: "=d" (a)
: "0" (a)
: "r18", "r19"
);
return a;
}
///@}
#pragma GCC diagnostic pop
#endif
/**
* @brief Bitwise right shift - generic, unoptimized, case
*
* @tparam b number of bits to shift by
* @param a value to shift
* @return uint32_t
*/
template <uint8_t b>
static inline uint32_t rshift(uint32_t a) {
#if USE_OPTIMIZED_SHIFTS==1 // The shifts below have been validated to produce performant code in GCC.
// Other shift amounts are either in a specialized template below (good) or are unvalidated (bad).
static_assert(b==1 || b==2 || b==8 || b==16 || b==24,
"Unvalidated shift - confirm gcc produces performant code");
#endif
return a >> b;
}
#if USE_OPTIMIZED_SHIFTS==1
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
/// @{
/// @brief uint32_t bitwise right shift optimised for the specified shift distance
///
/// @note The assembler was generated using clang 17.0.1 cross compiling: -O3 --target=avr -mmcu=atmega2560. See https://godbolt.org/z/71cPnMYqs
///
/// @param a value to shift
/// @return uint32_t a>>b
template <>
uint32_t rshift<3U>(uint32_t a)
{
asm(
"lsr %D0\n"
"ror %C0\n"
"ror %B0\n"
"ror %A0\n"
"lsr %D0\n"
"ror %C0\n"
"ror %B0\n"
"ror %A0\n"
"lsr %D0\n"
"ror %C0\n"
"ror %B0\n"
"ror %A0\n"
: "=d" (a)
: "0" (a)
:
);
return a;
}
template <>
uint32_t rshift<4U>(uint32_t a)
{
asm(
"swap %A0\n"
"andi %A0, 15\n"
"swap %B0\n"
"eor %A0, %B0\n"
"andi %B0, 15\n"
"eor %A0, %B0\n"
"swap %C0\n"
"eor %B0, %C0\n"
"andi %C0, 15\n"
"eor %B0, %C0\n"
"swap %D0\n"
"eor %C0, %D0\n"
"andi %D0, 15\n"
"eor %C0, %D0\n"
: "=d" (a)
: "0" (a)
:
);
return a;
}
template <>
uint32_t rshift<5U>(uint32_t a)
{
asm(
"swap %A0\n"
"andi %A0, 15\n"
"swap %B0\n"
"eor %A0, %B0\n"
"andi %B0, 15\n"
"eor %A0, %B0\n"
"swap %C0\n"
"eor %B0, %C0\n"
"andi %C0, 15\n"
"eor %B0, %C0\n"
"swap %D0\n"
"eor %C0, %D0\n"
"andi %D0, 15\n"
"eor %C0, %D0\n"
"lsr %D0\n"
"ror %C0\n"
"ror %B0\n"
"ror %A0\n"
: "=d" (a)
: "0" (a)
:
);
return a;
}
template <>
uint32_t rshift<6U>(uint32_t a)
{
asm(
"lsl %A0\n"
"rol %B0\n"
"rol %C0\n"
"rol %D0\n"
"mov r19, __zero_reg__\n"
"rol r19\n"
"lsl %A0\n"
"rol %B0\n"
"rol %C0\n"
"rol %D0\n"
"rol r19\n"
"mov %A0, %B0\n"
"mov %B0, %C0\n"
"mov r18, %D0\n"
"movw %C0, r18\n"
: "=d" (a)
: "0" (a)
: "r18", "r19"
);
return a;
}
template <>
uint32_t rshift<7U>(uint32_t a)
{
asm(
"lsl %A0\n"
"rol %B0\n"
"rol %C0\n"
"rol %D0\n"
"mov r19, __zero_reg__\n"
"rol r19\n"
"mov %A0, %B0\n"
"mov %B0, %C0\n"
"mov r18, %D0\n"
"movw %C0, r18\n"
: "=d" (a)
: "0" (a)
: "r18", "r19"
);
return a;
}
template <>
uint32_t rshift<9U>(uint32_t a)
{
asm(
"lsr %D0\n"
"ror %C0\n"
"ror %B0\n"
"mov %A0, %B0\n"
"mov %B0, %C0\n"
"mov %C0, %D0\n"
"mov %D0, __zero_reg__\n"
: "=d" (a)
: "0" (a)
:
);
return a;
}
template <>
uint32_t rshift<10U>(uint32_t a)
{
asm(
"lsr %D0\n"
"ror %C0\n"
"ror %B0\n"
"lsr %D0\n"
"ror %C0\n"
"ror %B0\n"
"mov %A0, %B0\n"
"mov %B0, %C0\n"
"mov %C0, %D0\n"
"mov %D0, __zero_reg__\n"
: "=d" (a)
: "0" (a)
:
);
return a;
}
template <>
uint32_t rshift<11U>(uint32_t a)
{
asm(
"lsr %D0\n"
"ror %C0\n"
"ror %B0\n"
"lsr %D0\n"
"ror %C0\n"
"ror %B0\n"
"lsr %D0\n"
"ror %C0\n"
"ror %B0\n"
"mov %A0, %B0\n"
"mov %B0, %C0\n"
"mov %C0, %D0\n"
"mov %D0, __zero_reg__\n"
: "=d" (a)
: "0" (a)
:
);
return a;
}
template <>
uint32_t rshift<12U>(uint32_t a)
{
asm(
"swap %B0\n"
"andi %B0, 15\n"
"swap %C0\n"
"eor %B0, %C0\n"
"andi %C0, 15\n"
"eor %B0, %C0\n"
"swap %D0\n"
"eor %C0, %D0\n"
"andi %D0, 15\n"
"eor %C0, %D0\n"
"mov %A0, %B0\n"
"mov %B0, %C0\n"
"mov %C0, %D0\n"
"mov %D0, __zero_reg__\n"
: "=d" (a)
: "0" (a)
:
);
return a;
}
template <>
uint32_t rshift<13U>(uint32_t a)
{
asm(
"swap %B0\n"
"andi %B0, 15\n"
"swap %C0\n"
"eor %B0, %C0\n"
"andi %C0, 15\n"
"eor %B0, %C0\n"
"swap %D0\n"
"eor %C0, %D0\n"
"andi %D0, 15\n"
"eor %C0, %D0\n"
"lsr %D0\n"
"ror %C0\n"
"ror %B0\n"
"mov %A0, %B0\n"
"mov %B0, %C0\n"
"mov %C0, %D0\n"
"mov %D0, __zero_reg__\n"
: "=d" (a)
: "0" (a)
:
);
return a;
}
template <>
uint32_t rshift<14U>(uint32_t a)
{
asm(
"movw r18, %C0\n"
"lsl %B0\n"
"rol r18\n"
"rol r19\n"
"mov %C0, __zero_reg__\n"
"rol %C0\n"
"lsl %B0\n"
"rol r18\n"
"rol r19\n"
"rol %C0\n"
"mov %D0, __zero_reg__\n"
"movw %A0, r18\n"
: "=d" (a)
: "0" (a)
: "r18", "r19"
);
return a;
}
template <>
uint32_t rshift<15U>(uint32_t a)
{
asm(
"movw r18, %C0\n"
"lsl %B0\n"
"rol r18\n"
"rol r19\n"
"mov %C0, __zero_reg__\n"
"rol %C0\n"
"mov %D0, __zero_reg__\n"
"movw %A0, r18\n"
: "=d" (a)
: "0" (a)
: "r18", "r19"
);
return a;
}
///@}
#pragma GCC diagnostic pop
#endif
/**
* @brief Rounded arithmetic right shift
*
* Right shifting throws away bits. When use for fixed point division, this
* effecitvely rounds down (towards zero). To round-to-the-nearest-integer
* when right-shifting by S, just add in 2 power b1 (which is the
* fixed-point equivalent of 0.5) first
*
* @tparam b number of bits to shift by
* @param a value to shift
* @return uint32_t
*/
template <uint8_t b>
static inline uint32_t rshift_round(uint32_t a) {
return rshift<b>(a+(1UL<<(b-1UL)));
}
///@}

View File

@ -1,6 +1,6 @@
#include "globals.h"
#include "crankMaths.h"
#include "decoders.h"
#include "bit_shifts.h"
#define SECOND_DERIV_ENABLED 0
@ -10,51 +10,36 @@ byte deltaToothCount = 0; //The last tooth that was used with the deltaV calc
int rpmDelta;
#endif
uint32_t angleToTimeMicroSecPerDegree(uint16_t angle) {
UQ24X8_t micros = (uint32_t)angle * (uint32_t)microsPerDegree;
return RSHIFT_ROUND(micros, microsPerDegree_Shift);
typedef uint32_t UQ24X8_t;
static constexpr uint8_t UQ24X8_Shift = 8U;
/** @brief uS per degree at current RPM in UQ24.8 fixed point */
static UQ24X8_t microsPerDegree;
static constexpr uint8_t microsPerDegree_Shift = UQ24X8_Shift;
typedef uint16_t UQ1X15_t;
static constexpr uint8_t UQ1X15_Shift = 15U;
/** @brief Degrees per uS in UQ1.15 fixed point.
*
* Ranges from 8 (0.000246) at MIN_RPM to 3542 (0.108) at MAX_RPM
*/
static UQ1X15_t degreesPerMicro;
static constexpr uint8_t degreesPerMicro_Shift = UQ1X15_Shift;
void setAngleConverterRevolutionTime(uint32_t revolutionTime) {
microsPerDegree = div360(lshift<microsPerDegree_Shift>(revolutionTime));
degreesPerMicro = (uint16_t)UDIV_ROUND_CLOSEST(lshift<degreesPerMicro_Shift>(UINT32_C(360)), revolutionTime, uint32_t);
}
uint32_t angleToTimeIntervalTooth(uint16_t angle) {
noInterrupts();
if(BIT_CHECK(decoderState, BIT_DECODER_TOOTH_ANG_CORRECT))
{
unsigned long toothTime = (toothLastToothTime - toothLastMinusOneToothTime);
uint16_t tempTriggerToothAngle = triggerToothAngle; // triggerToothAngle is set by interrupts
interrupts();
return (toothTime * (uint32_t)angle) / tempTriggerToothAngle;
}
//Safety check. This can occur if the last tooth seen was outside the normal pattern etc
else {
interrupts();
return angleToTimeMicroSecPerDegree(angle);
}
uint32_t angleToTimeMicroSecPerDegree(uint16_t angle) {
UQ24X8_t micros = (uint32_t)angle * (uint32_t)microsPerDegree;
return rshift_round<microsPerDegree_Shift>(micros);
}
uint16_t timeToAngleDegPerMicroSec(uint32_t time) {
uint32_t degFixed = time * (uint32_t)degreesPerMicro;
return RSHIFT_ROUND(degFixed, degreesPerMicro_Shift);
}
uint16_t timeToAngleIntervalTooth(uint32_t time)
{
noInterrupts();
//Still uses a last interval method (ie retrospective), but bases the interval on the gap between the 2 most recent teeth rather than the last full revolution
if(BIT_CHECK(decoderState, BIT_DECODER_TOOTH_ANG_CORRECT))
{
unsigned long toothTime = (toothLastToothTime - toothLastMinusOneToothTime);
uint16_t tempTriggerToothAngle = triggerToothAngle; // triggerToothAngle is set by interrupts
interrupts();
return (unsigned long)(time * (uint32_t)tempTriggerToothAngle) / toothTime;
}
else {
interrupts();
//Safety check. This can occur if the last tooth seen was outside the normal pattern etc
return timeToAngleDegPerMicroSec(time);
}
return rshift_round<degreesPerMicro_Shift>(degFixed);
}
#if SECOND_DERIV_ENABLED!=0
@ -92,7 +77,7 @@ void doCrankSpeedCalcs(void)
uint32_t toothDeltaT = toothHistory[toothHistoryIndex];
//long timeToLastTooth = micros() - toothLastToothTime;
rpmDelta = (toothDeltaV << 10) / (6 * toothDeltaT);
rpmDelta = lshift<10>(toothDeltaV) / (6 * toothDeltaT);
}
timePerDegreex16 = ldiv( 2666656L, currentStatus.RPM + rpmDelta).quot; //This gives accuracy down to 0.1 of a degree and can provide noticeably better timing results on low resolution triggers

View File

@ -46,6 +46,13 @@ static inline int16_t injectorLimits(int16_t angle)
*/
#define MIN_RPM ((MICROS_PER_DEG_1_RPM/(UINT16_MAX/16UL))+1UL)
/**
* @brief Set the revolution time, from which some of the degree<-->angle conversions are derived
*
* @param revolutionTime The crank revolution time.
*/
void setAngleConverterRevolutionTime(uint32_t revolutionTime);
/**
* @name Converts angular degrees to the time interval that amount of rotation
* will take at current RPM.
@ -56,39 +63,17 @@ static inline int16_t injectorLimits(int16_t angle)
* @param angle Angle in degrees
* @return Time interval in uS
*/
///@{
/** @brief Converts based on the time one degree of rotation takes
*
* Inverse of timeToAngleDegPerMicroSec
*/
uint32_t angleToTimeMicroSecPerDegree(uint16_t angle);
/** @brief Converts based on the time interval between the 2 most recently detected decoder teeth
*
* Inverse of timeToAngleIntervalTooth
*/
uint32_t angleToTimeIntervalTooth(uint16_t angle);
///@}
/**
* @name Converts a time interval in microsecods to the equivalent degrees of angular (crank)
* rotation at current RPM.
*
* Inverse of angleToTimeMicroSecPerDegree
*
* @param time Time interval in uS
* @return Angle in degrees
*/
///@{
/** @brief Converts based on the the interval on time one degree of rotation takes
*
* Inverse of angleToTimeMicroSecPerDegree
*/
uint16_t timeToAngleDegPerMicroSec(uint32_t time);
/** @brief Converts based on the time interval between the 2 most recently detected decoder teeth
*
* Inverse of angleToTimeIntervalTooth
*/
uint16_t timeToAngleIntervalTooth(uint32_t time);
///@}
#endif

View File

@ -100,9 +100,6 @@ volatile unsigned long triggerThirdFilterTime; // The shortest time (in uS) that
volatile uint8_t decoderState = 0;
UQ24X8_t microsPerDegree;
UQ1X15_t degreesPerMicro;
unsigned int triggerSecFilterTime_duration; // The shortest valid time (in uS) pulse DURATION
volatile uint16_t triggerToothAngle; //The number of crank degrees that elapse per tooth
byte checkSyncToothCount; //How many teeth must've been seen on this revolution before we try to confirm sync (Useful for missing tooth type decoders)
@ -293,6 +290,47 @@ void loggerTertiaryISR(void)
}
}
#if false
#if !defined(UNIT_TEST)
static
#endif
uint32_t angleToTimeIntervalTooth(uint16_t angle) {
noInterrupts();
if(BIT_CHECK(decoderState, BIT_DECODER_TOOTH_ANG_CORRECT))
{
unsigned long toothTime = (toothLastToothTime - toothLastMinusOneToothTime);
uint16_t tempTriggerToothAngle = triggerToothAngle; // triggerToothAngle is set by interrupts
interrupts();
return (toothTime * (uint32_t)angle) / tempTriggerToothAngle;
}
//Safety check. This can occur if the last tooth seen was outside the normal pattern etc
else {
interrupts();
return angleToTimeMicroSecPerDegree(angle);
}
}
#endif
static uint16_t timeToAngleIntervalTooth(uint32_t time)
{
noInterrupts();
//Still uses a last interval method (ie retrospective), but bases the interval on the gap between the 2 most recent teeth rather than the last full revolution
if(BIT_CHECK(decoderState, BIT_DECODER_TOOTH_ANG_CORRECT))
{
unsigned long toothTime = (toothLastToothTime - toothLastMinusOneToothTime);
uint16_t tempTriggerToothAngle = triggerToothAngle; // triggerToothAngle is set by interrupts
interrupts();
return (unsigned long)(time * (uint32_t)tempTriggerToothAngle) / toothTime;
}
else {
interrupts();
//Safety check. This can occur if the last tooth seen was outside the normal pattern etc
return timeToAngleDegPerMicroSec(time);
}
}
static inline bool IsCranking(const statuses &status) {
return (status.RPM < status.crankRPM) && (status.startRevolutions == 0U);
}
@ -305,8 +343,7 @@ static __attribute__((noinline)) bool SetRevolutionTime(uint32_t revTime)
{
if (revTime!=revolutionTime) {
revolutionTime = revTime;
microsPerDegree = div360(revolutionTime << microsPerDegree_Shift);
degreesPerMicro = (uint16_t)UDIV_ROUND_CLOSEST((UINT32_C(360) << degreesPerMicro_Shift), revolutionTime, uint32_t);
setAngleConverterRevolutionTime(revolutionTime);
return true;
}
return false;
@ -1470,7 +1507,7 @@ void triggerPri_4G63(void)
if(configPage2.nCylinders == 4)
{
triggerToothAngle = 110;
triggerFilterTime = (curGap * 3) >> 3; //Trigger filter is set to (110*3)/8=41.25=41 degrees (Next trigger is 70 degrees away).
triggerFilterTime = rshift<3>(curGap * 3UL); //Trigger filter is set to (110*3)/8=41.25=41 degrees (Next trigger is 70 degrees away).
}
else if(configPage2.nCylinders == 6)
{
@ -1516,7 +1553,7 @@ void triggerPri_4G63(void)
triggerToothAngle = 70;
if(configPage2.nCylinders == 4)
{
triggerFilterTime = (curGap * 11) >> 3;//96.26 degrees with a target of 110
triggerFilterTime = rshift<3>(curGap * 11UL);//96.26 degrees with a target of 110
}
else
{
@ -1528,7 +1565,7 @@ void triggerPri_4G63(void)
if(configPage2.nCylinders == 4)
{
triggerToothAngle = 110;
triggerFilterTime = (curGap * 9) >> 5; //61.87 degrees with a target of 70
triggerFilterTime = rshift<5>(curGap * 9UL); //61.87 degrees with a target of 70
}
else
{
@ -2451,7 +2488,7 @@ void triggerPri_Miata9905(void)
{
//Lite filter
if( (toothCurrentCount == 1) || (toothCurrentCount == 3) || (toothCurrentCount == 5) || (toothCurrentCount == 7) ) { triggerToothAngle = 70; triggerFilterTime = curGap; } //Trigger filter is set to whatever time it took to do 70 degrees (Next trigger is 110 degrees away)
else { triggerToothAngle = 110; triggerFilterTime = (curGap * 3) >> 3; } //Trigger filter is set to (110*3)/8=41.25=41 degrees (Next trigger is 70 degrees away).
else { triggerToothAngle = 110; triggerFilterTime = rshift<3>(curGap * 3UL); } //Trigger filter is set to (110*3)/8=41.25=41 degrees (Next trigger is 70 degrees away).
}
else if(configPage4.triggerFilter == 2)
{
@ -2462,8 +2499,8 @@ void triggerPri_Miata9905(void)
else if (configPage4.triggerFilter == 3)
{
//Aggressive filter level
if( (toothCurrentCount == 1) || (toothCurrentCount == 3) || (toothCurrentCount == 5) || (toothCurrentCount == 7) ) { triggerToothAngle = 70; triggerFilterTime = (curGap * 11) >> 3 ; } //96.26 degrees with a target of 110
else { triggerToothAngle = 110; triggerFilterTime = (curGap * 9) >> 5; } //61.87 degrees with a target of 70
if( (toothCurrentCount == 1) || (toothCurrentCount == 3) || (toothCurrentCount == 5) || (toothCurrentCount == 7) ) { triggerToothAngle = 70; triggerFilterTime = rshift<3>(curGap * 11UL) ; } //96.26 degrees with a target of 110
else { triggerToothAngle = 110; triggerFilterTime = rshift<5>(curGap * 9UL); } //61.87 degrees with a target of 70
}
else if (configPage4.triggerFilter == 0)
{
@ -2695,7 +2732,7 @@ void triggerPri_MazdaAU(void)
//Whilst this is an uneven tooth pattern, if the specific angle between the last 2 teeth is specified, 1st deriv prediction can be used
if( (toothCurrentCount == 1) || (toothCurrentCount == 3) ) { triggerToothAngle = 72; triggerFilterTime = curGap; } //Trigger filter is set to whatever time it took to do 72 degrees (Next trigger is 108 degrees away)
else { triggerToothAngle = 108; triggerFilterTime = (curGap * 3) >> 3; } //Trigger filter is set to (108*3)/8=40 degrees (Next trigger is 70 degrees away).
else { triggerToothAngle = 108; triggerFilterTime = rshift<3>(curGap * 3UL); } //Trigger filter is set to (108*3)/8=40 degrees (Next trigger is 70 degrees away).
toothLastMinusOneToothTime = toothLastToothTime;
toothLastToothTime = curTime;
@ -5709,13 +5746,13 @@ void triggerPri_SuzukiK6A(void)
switch (configPage4.triggerFilter)
{
case 1: // 25 % 17 degrees
triggerFilterTime = curGap>>3;
triggerFilterTime = rshift<3>(curGap);
break;
case 2: // 50 % 35 degrees
triggerFilterTime = (curGap>>3) + (curGap>>4);
triggerFilterTime = rshift<3>(curGap) + rshift<4>(curGap);
break;
case 3: // 75 % 52 degrees
triggerFilterTime = (curGap>>2) + (curGap>>4);
triggerFilterTime = rshift<2>(curGap) + rshift<4>(curGap);
break;
default:
triggerFilterTime = 0;
@ -5728,13 +5765,13 @@ void triggerPri_SuzukiK6A(void)
switch (configPage4.triggerFilter)
{
case 1: // 25 % 8 degrees
triggerFilterTime = curGap>>3;
triggerFilterTime = rshift<3>(curGap);
break;
case 2: // 50 % 17 degrees
triggerFilterTime = curGap>>2;
triggerFilterTime = rshift<2>(curGap);
break;
case 3: // 75 % 25 degrees
triggerFilterTime = (curGap>>2) + (curGap>>3);
triggerFilterTime = rshift<2>(curGap) + rshift<3>(curGap);
break;
default:
triggerFilterTime = 0;
@ -5766,13 +5803,13 @@ void triggerPri_SuzukiK6A(void)
switch (configPage4.triggerFilter)
{
case 1: // 25 % 17 degrees
triggerFilterTime = curGap>>3;
triggerFilterTime = rshift<3>(curGap);
break;
case 2: // 50 % 35 degrees
triggerFilterTime = curGap>>2;
triggerFilterTime = rshift<2>(curGap);
break;
case 3: // 75 % 52 degrees
triggerFilterTime = (curGap>>2) + (curGap>>3);
triggerFilterTime = rshift<2>(curGap) + rshift<3>(curGap);
break;
default:
triggerFilterTime = 0;
@ -5786,13 +5823,13 @@ void triggerPri_SuzukiK6A(void)
switch (configPage4.triggerFilter)
{
case 1: // 25 % 42 degrees
triggerFilterTime = (curGap>>1) + (curGap>>3);
triggerFilterTime = rshift<1>(curGap) + rshift<3>(curGap);
break;
case 2: // 50 % 85 degrees
triggerFilterTime = curGap + (curGap>>2);
triggerFilterTime = curGap + rshift<2>(curGap);
break;
case 3: // 75 % 127 degrees
triggerFilterTime = curGap + (curGap>>1) + (curGap>>2);
triggerFilterTime = curGap + rshift<1>(curGap) + rshift<2>(curGap);
break;
default:
triggerFilterTime = 0;

View File

@ -303,23 +303,6 @@ extern unsigned long elapsedTime;
extern unsigned long lastCrankAngleCalc;
extern unsigned long lastVVTtime; //The time between the vvt reference pulse and the last crank pulse
typedef uint32_t UQ24X8_t;
constexpr uint8_t UQ24X8_Shift = 8U;
/** @brief uS per degree at current RPM in UQ24.8 fixed point */
extern UQ24X8_t microsPerDegree;
constexpr uint8_t microsPerDegree_Shift = UQ24X8_Shift;
typedef uint16_t UQ1X15_t;
constexpr uint8_t UQ1X15_Shift = 15U;
/** @brief Degrees per uS in UQ1.15 fixed point.
*
* Ranges from 8 (0.000246) at MIN_RPM to 3542 (0.108) at MAX_RPM
*/
extern UQ1X15_t degreesPerMicro;
constexpr uint8_t degreesPerMicro_Shift = UQ1X15_Shift;
extern uint16_t ignition1EndTooth;
extern uint16_t ignition2EndTooth;
extern uint16_t ignition3EndTooth;

View File

@ -3,6 +3,7 @@
#include <stdint.h>
#include "globals.h"
#include "bit_shifts.h"
#ifdef USE_LIBDIVIDE
// We use pre-computed constant parameters with libdivide where possible.
@ -91,26 +92,19 @@ extern uint8_t random1to100(void);
*/
#define UDIV_ROUND_CLOSEST(n, d, t) ((t)((n) + DIV_ROUND_CORRECT(d, t))/(t)(d))
/**
* @brief Rounded arithmetic right shift
*
* Right shifting throws away bits. When use for fixed point division, this
* effectively rounds down (towards zero). To round-to-the-nearest-integer
* when right-shifting by S, just add in 2 power S1 (which is the
* fixed-point equivalent of 0.5) first
*
* @param n The value to shift
* @param s The shift distance in bits
*/
#define RSHIFT_ROUND(n, s) (((n)+(1UL<<(s-1UL)))>>(s))
///@}
/** @brief Test whether the parameter is an integer or not. */
#define IS_INTEGER(d) ((d) == (int32_t)(d))
/**
* @defgroup group-div100 Optimised integer division by 100
/**
* @{
* @brief Performance optimised integer division by 100. I.e. same as n/100
*
* Uses the rounding behaviour controlled by @ref DIV_ROUND_BEHAVIOR
*
* @param n Dividend to divide by 100
* @return n/100, with rounding behavior applied
*/
static inline uint16_t div100(uint16_t n) {
// As of avr-gcc 5.4.0, the compiler will optimize this to a multiply/shift
@ -223,9 +217,9 @@ static inline int16_t nudge(int16_t min, int16_t max, int16_t value, int16_t nud
//This is a dedicated function that specifically handles the case of mapping 0-1023 values into a 0 to X range
//This is a common case because it means converting from a standard 10-bit analog input to a byte or 10-bit analog into 0-511 (Eg the temperature readings)
#define fastMap1023toX(x, out_max) ( ((uint32_t)(x) * (out_max)) >> 10)
#define fastMap1023toX(x, out_max) ( rshift<10>((uint32_t)(x) * (out_max)) )
//This is a new version that allows for out_min
#define fastMap10Bit(x, out_min, out_max) ( ( ((uint32_t)(x) * ((out_max)-(out_min))) >> 10 ) + (out_min))
#define fastMap10Bit(x, out_min, out_max) ( rshift<10>( (uint32_t)(x) * ((out_max)-(out_min)) ) + (out_min))
#if defined(CORE_AVR) || defined(ARDUINO_ARCH_AVR)

View File

@ -1199,47 +1199,47 @@ uint16_t PW(int REQ_FUEL, byte VE, long MAP, uint16_t corrections, int injOpen)
//Standard float version of the calculation
//return (REQ_FUEL * (float)(VE/100.0) * (float)(MAP/100.0) * (float)(TPS/100.0) * (float)(corrections/100.0) + injOpen);
//Note: The MAP and TPS portions are currently disabled, we use VE and corrections only
uint16_t iVE, iCorrections;
uint16_t iVE;
uint16_t iMAP = 100;
uint16_t iAFR = 147;
//100% float free version, does sacrifice a little bit of accuracy, but not much.
//If corrections are huge, use less bitshift to avoid overflow. Sacrifices a bit more accuracy (basically only during very cold temp cranking)
byte bitShift = 7;
if (corrections > 511 ) { bitShift = 6; }
if (corrections > 1023) { bitShift = 5; }
//iVE = ((unsigned int)VE << 7) / 100;
iVE = div100(((uint16_t)VE << 7));
iVE = div100(((uint16_t)VE << 7U));
//Check whether either of the multiply MAP modes is turned on
//if ( configPage2.multiplyMAP == MULTIPLY_MAP_MODE_100) { iMAP = ((unsigned int)MAP << 7) / 100; }
if ( configPage2.multiplyMAP == MULTIPLY_MAP_MODE_100) { iMAP = div100( ((uint16_t)MAP << 7U) ); }
else if( configPage2.multiplyMAP == MULTIPLY_MAP_MODE_BARO) { iMAP = ((unsigned int)MAP << 7) / currentStatus.baro; }
else if( configPage2.multiplyMAP == MULTIPLY_MAP_MODE_BARO) { iMAP = ((unsigned int)MAP << 7U) / currentStatus.baro; }
if ( (configPage2.includeAFR == true) && (configPage6.egoType == EGO_TYPE_WIDE) && (currentStatus.runSecs > configPage6.ego_sdelay) ) {
iAFR = ((unsigned int)currentStatus.O2 << 7) / currentStatus.afrTarget; //Include AFR (vs target) if enabled
iAFR = ((unsigned int)currentStatus.O2 << 7U) / currentStatus.afrTarget; //Include AFR (vs target) if enabled
}
if ( (configPage2.incorporateAFR == true) && (configPage2.includeAFR == false) ) {
iAFR = ((unsigned int)configPage2.stoich << 7) / currentStatus.afrTarget; //Incorporate stoich vs target AFR, if enabled.
iAFR = ((unsigned int)configPage2.stoich << 7U) / currentStatus.afrTarget; //Incorporate stoich vs target AFR, if enabled.
}
//iCorrections = (corrections << bitShift) / 100;
iCorrections = div100((corrections << bitShift));
uint32_t intermediate = ((uint32_t)REQ_FUEL * (uint32_t)iVE) >> 7; //Need to use an intermediate value to avoid overflowing the long
if ( configPage2.multiplyMAP > 0 ) { intermediate = (intermediate * (uint32_t)iMAP) >> 7; }
uint32_t intermediate = rshift<7U>((uint32_t)REQ_FUEL * (uint32_t)iVE); //Need to use an intermediate value to avoid overflowing the long
if ( configPage2.multiplyMAP > 0 ) { intermediate = rshift<7U>(intermediate * (uint32_t)iMAP); }
if ( (configPage2.includeAFR == true) && (configPage6.egoType == EGO_TYPE_WIDE) && (currentStatus.runSecs > configPage6.ego_sdelay) ) {
//EGO type must be set to wideband and the AFR warmup time must've elapsed for this to be used
intermediate = (intermediate * (uint32_t)iAFR) >> 7;
intermediate = rshift<7U>(intermediate * (uint32_t)iAFR);
}
if ( (configPage2.incorporateAFR == true) && (configPage2.includeAFR == false) ) {
intermediate = (intermediate * (uint32_t)iAFR) >> 7;
intermediate = rshift<7U>(intermediate * (uint32_t)iAFR);
}
intermediate = (intermediate * (uint32_t)iCorrections) >> bitShift;
//If corrections are huge, use less bitshift to avoid overflow. Sacrifices a bit more accuracy (basically only during very cold temp cranking)
if (corrections < 512 ) {
intermediate = rshift<7U>(intermediate * div100(lshift<7U>(corrections)));
} else if (corrections < 1024 ) {
intermediate = rshift<6U>(intermediate * div100(lshift<6U>(corrections)));
} else {
intermediate = rshift<5U>(intermediate * div100(lshift<5U>(corrections)));
}
if (intermediate != 0)
{
//If intermediate is not 0, we need to add the opening time (0 typically indicates that one of the full fuel cuts is active)

View File

@ -320,11 +320,10 @@ void oneMSInterval(void) //Most ARM chips can simply call a function
currentStatus.ethanolPct = ADC_FILTER(tempEthPct, configPage4.FILTER_FLEX, currentStatus.ethanolPct);
//Continental flex sensor fuel temperature can be read with following formula: (Temperature = (41.25 * pulse width(ms)) - 81.25). 1000μs = -40C and 5000μs = 125C
if(flexPulseWidth > 5000) { flexPulseWidth = 5000; }
else if(flexPulseWidth < 1000) { flexPulseWidth = 1000; }
currentStatus.fuelTemp = div100( (int16_t)(((4224 * (long)flexPulseWidth) >> 10) - 8125) );
flexPulseWidth = constrain(flexPulseWidth, 1000UL, 5000UL);
int32_t tempX100 = (int32_t)rshift<10>(4224UL * flexPulseWidth) - 8125L; //Split up for MISRA compliance
currentStatus.fuelTemp = div100((int16_t)tempX100);
}
}
//Turn off any of the pulsed testing outputs if they are active and have been running for long enough

View File

@ -4,6 +4,7 @@
extern void testPercent(void);
extern void testDivision(void);
extern void testBitShift(void);
#define UNITY_EXCLUDE_DETAILS
@ -20,6 +21,7 @@ void setup()
testCrankMaths();
testPercent();
testDivision();
testBitShift();
UNITY_END(); // stop unit testing
}

View File

@ -0,0 +1,118 @@
#include <unity.h>
#include <Arduino.h>
#include "bit_shifts.h"
#include "maths.h"
#include "../timer.hpp"
#include "../test_utils.h"
template <uint8_t shiftDistance>
static void test_lshift(void) {
uint32_t value = 33333;
char szMsg[16];
sprintf(szMsg, "%" PRIu8, shiftDistance);
TEST_ASSERT_EQUAL_MESSAGE(value << shiftDistance, lshift<shiftDistance>(value), szMsg);
}
static void test_LShift()
{
test_lshift<1U>();
test_lshift<2U>();
test_lshift<3U>();
test_lshift<4U>();
test_lshift<5U>();
test_lshift<6U>();
test_lshift<7U>();
test_lshift<8U>();
test_lshift<9U>();
test_lshift<10U>();
test_lshift<11U>();
test_lshift<12U>();
test_lshift<13U>();
test_lshift<14U>();
test_lshift<15U>();
}
template <uint8_t shiftDistance>
static void test_rshift(void) {
uint32_t value = 33333;
char szMsg[16];
sprintf(szMsg, "%" PRIu8, shiftDistance);
TEST_ASSERT_EQUAL_MESSAGE(value >> shiftDistance, rshift<shiftDistance>(value), szMsg);
}
void test_RShift()
{
test_rshift<1U>();
test_rshift<2U>();
test_rshift<3U>();
test_rshift<4U>();
test_rshift<5U>();
test_rshift<6U>();
test_rshift<7U>();
test_rshift<8U>();
test_rshift<9U>();
test_rshift<10U>();
test_rshift<11U>();
test_rshift<12U>();
test_rshift<13U>();
test_rshift<14U>();
test_rshift<15U>();
}
static uint32_t seedValue;
// Force no inline, or compiler will optimize shifts away
// (which it won't do in normal operaton when the left shift operand is unknown at compile time.)
static void __attribute__((noinline)) nativeTest(uint8_t index, uint32_t &checkSum) {
if (index==1U) { checkSum = seedValue; }
if (index==4U) { checkSum += checkSum >> 4U; }
if (index==5U) { checkSum += checkSum >> 5U; }
if (index==6U) { checkSum += checkSum >> 6U; }
if (index==7U) { checkSum += checkSum >> 7U; }
if (index==9U) { checkSum += checkSum >> 9U; }
if (index==10U) { checkSum += checkSum >> 10U; }
if (index==11U) { checkSum += checkSum >> 11U; }
if (index==12U) { checkSum += checkSum >> 12U; }
if (index==13U) { checkSum += checkSum >> 13U; }
if (index==14U) { checkSum += checkSum >> 14U; }
if (index==15U) { checkSum += checkSum >> 15U; }
};
static void __attribute__((noinline)) optimizedTest(uint8_t index, uint32_t &checkSum) {
if (index==1U) { checkSum = seedValue; }
if (index==4U) { checkSum += rshift<4U>(checkSum); }
if (index==5U) { checkSum += rshift<5U>(checkSum); }
if (index==6U) { checkSum += rshift<6U>(checkSum); }
if (index==7U) { checkSum += rshift<7U>(checkSum); }
if (index==9U) { checkSum += rshift<9U>(checkSum); }
if (index==10U) { checkSum += rshift<10U>(checkSum); }
if (index==11U) { checkSum += rshift<11U>(checkSum); }
if (index==12U) { checkSum += rshift<12U>(checkSum); }
if (index==13U) { checkSum += rshift<13U>(checkSum); }
if (index==14U) { checkSum += rshift<14U>(checkSum); }
if (index==15U) { checkSum += rshift<15U>(checkSum); }
};
static void test_rshift_perf(void) {
constexpr uint16_t iters = 128;
constexpr uint8_t start_index = 1;
constexpr uint8_t end_index = 16;
constexpr uint8_t step = 1;
seedValue = rand();
TEST_MESSAGE("rshift ");
auto comparison = compare_executiontime<uint8_t, uint32_t>(iters, start_index, end_index, step, nativeTest, optimizedTest);
// This must be here to force the compiler to run the loops above
TEST_ASSERT_EQUAL(comparison.timeA.result, comparison.timeB.result);
TEST_ASSERT_LESS_THAN(comparison.timeA.durationMicros, comparison.timeB.durationMicros);
}
void testBitShift() {
SET_UNITY_FILENAME() {
RUN_TEST(test_LShift);
RUN_TEST(test_RShift);
RUN_TEST(test_rshift_perf);
}
}

View File

@ -1,7 +1,7 @@
#include <unity.h>
// #include "globals.h"
#include "crankMaths.h"
#include "decoders.h"
#include "../test_utils.h"
extern void SetRevolutionTime(uint32_t revTime);
@ -18,6 +18,7 @@ void test_crankmaths_angletotime_revolution_execute() {
TEST_ASSERT_INT32_WITHIN(1, testdata->expected, angleToTimeMicroSecPerDegree(testdata->angle));
}
#if false
struct crankmaths_tooth_testdata {
uint16_t rpm;
uint16_t triggerToothAngle;
@ -26,64 +27,70 @@ struct crankmaths_tooth_testdata {
unsigned long expected;
} *crankmaths_tooth_testdata_current;
extern uint32_t angleToTimeIntervalTooth(uint16_t angle);
void test_crankmaths_angletotime_tooth_execute() {
crankmaths_tooth_testdata *testdata = crankmaths_tooth_testdata_current;
triggerToothAngle = testdata->triggerToothAngle;
toothLastToothTime = toothLastMinusOneToothTime + testdata->toothTime;
TEST_ASSERT_EQUAL(testdata->expected, angleToTimeIntervalTooth(testdata->angle));
}
#endif
void testCrankMaths()
{
const byte testNameLength = 200;
char testName[testNameLength];
SET_UNITY_FILENAME() {
constexpr byte testNameLength = 200;
char testName[testNameLength];
const crankmaths_rev_testdata crankmaths_rev_testdatas[] = {
{ .rpm = 50, .revolutionTime = 1200000, .angle = 0, .expected = 0 },
{ .rpm = 50, .revolutionTime = 1200000, .angle = 25, .expected = 83333 }, // 83333,3333
{ .rpm = 50, .revolutionTime = 1200000, .angle = 720, .expected = 2400000 },
{ .rpm = 2500, .revolutionTime = 24000, .angle = 0, .expected = 0 },
{ .rpm = 2500, .revolutionTime = 24000, .angle = 25, .expected = 1667 }, // 1666,6666
{ .rpm = 2500, .revolutionTime = 24000, .angle = 720, .expected = 48000 },
{ .rpm = 20000, .revolutionTime = 3000, .angle = 0, .expected = 0 },
{ .rpm = 20000, .revolutionTime = 3000, .angle = 25, .expected = 208 }, // 208,3333
{ .rpm = 20000, .revolutionTime = 3000, .angle = 720, .expected = 6000 }
};
const crankmaths_rev_testdata crankmaths_rev_testdatas[] = {
{ .rpm = 50, .revolutionTime = 1200000, .angle = 0, .expected = 0 },
{ .rpm = 50, .revolutionTime = 1200000, .angle = 25, .expected = 83333 }, // 83333,3333
{ .rpm = 50, .revolutionTime = 1200000, .angle = 720, .expected = 2400000 },
{ .rpm = 2500, .revolutionTime = 24000, .angle = 0, .expected = 0 },
{ .rpm = 2500, .revolutionTime = 24000, .angle = 25, .expected = 1667 }, // 1666,6666
{ .rpm = 2500, .revolutionTime = 24000, .angle = 720, .expected = 48000 },
{ .rpm = 20000, .revolutionTime = 3000, .angle = 0, .expected = 0 },
{ .rpm = 20000, .revolutionTime = 3000, .angle = 25, .expected = 208 }, // 208,3333
{ .rpm = 20000, .revolutionTime = 3000, .angle = 720, .expected = 6000 }
};
for (auto testdata : crankmaths_rev_testdatas) {
crankmaths_rev_testdata_current = &testdata;
snprintf(testName, testNameLength, "crankmaths/angletotime/revolution/%urpm/%uangle", testdata.rpm, testdata.angle);
UnityDefaultTestRun(test_crankmaths_angletotime_revolution_execute, testName, __LINE__);
for (auto testdata : crankmaths_rev_testdatas) {
crankmaths_rev_testdata_current = &testdata;
snprintf(testName, testNameLength, "crankmaths/angletotime/revolution/%urpm/%uangle", testdata.rpm, testdata.angle);
UnityDefaultTestRun(test_crankmaths_angletotime_revolution_execute, testName, __LINE__);
}
#if false
const crankmaths_tooth_testdata crankmaths_tooth_testdatas[] = {
{ .rpm = 50, .triggerToothAngle = 3, .toothTime = 10000, .angle = 0, .expected = 0 },
{ .rpm = 50, .triggerToothAngle = 3, .toothTime = 10000, .angle = 25, .expected = 83333 }, // 83333,3333
{ .rpm = 50, .triggerToothAngle = 3, .toothTime = 10000, .angle = 720, .expected = 2400000 },
{ .rpm = 2500, .triggerToothAngle = 3, .toothTime = 200, .angle = 0, .expected = 0 },
{ .rpm = 2500, .triggerToothAngle = 3, .toothTime = 200, .angle = 25, .expected = 1666 }, // 1666,6666
{ .rpm = 2500, .triggerToothAngle = 3, .toothTime = 200, .angle = 720, .expected = 48000 },
{ .rpm = 20000, .triggerToothAngle = 3, .toothTime = 25, .angle = 0, .expected = 0 },
{ .rpm = 20000, .triggerToothAngle = 3, .toothTime = 25, .angle = 25, .expected = 208 }, // 208,3333
{ .rpm = 20000, .triggerToothAngle = 3, .toothTime = 25, .angle = 720, .expected = 6000 },
{ .rpm = 50, .triggerToothAngle = 180, .toothTime = 600000, .angle = 0, .expected = 0 },
{ .rpm = 50, .triggerToothAngle = 180, .toothTime = 600000, .angle = 25, .expected = 83333 }, // 83333,3333
{ .rpm = 50, .triggerToothAngle = 180, .toothTime = 600000, .angle = 720, .expected = 2400000 },
{ .rpm = 2500, .triggerToothAngle = 180, .toothTime = 12000, .angle = 0, .expected = 0 },
{ .rpm = 2500, .triggerToothAngle = 180, .toothTime = 12000, .angle = 25, .expected = 1666 }, // 1666,6666
{ .rpm = 2500, .triggerToothAngle = 180, .toothTime = 12000, .angle = 720, .expected = 48000 },
{ .rpm = 20000, .triggerToothAngle = 180, .toothTime = 1500, .angle = 0, .expected = 0 },
{ .rpm = 20000, .triggerToothAngle = 180, .toothTime = 1500, .angle = 25, .expected = 208 }, // 208,3333
{ .rpm = 20000, .triggerToothAngle = 180, .toothTime = 1500, .angle = 720, .expected = 6000 },
};
// The same for all tests
BIT_SET(decoderState, BIT_DECODER_TOOTH_ANG_CORRECT);
toothLastMinusOneToothTime = 200000;
for (auto testdata : crankmaths_tooth_testdatas) {
crankmaths_tooth_testdata_current = &testdata;
snprintf(testName, testNameLength, "crankmaths/angletotime/tooth/%urpm/%uangle/%utoothangle", testdata.rpm, testdata.angle, testdata.triggerToothAngle);
UnityDefaultTestRun(test_crankmaths_angletotime_tooth_execute, testName, __LINE__);
}
#endif
}
const crankmaths_tooth_testdata crankmaths_tooth_testdatas[] = {
{ .rpm = 50, .triggerToothAngle = 3, .toothTime = 10000, .angle = 0, .expected = 0 },
{ .rpm = 50, .triggerToothAngle = 3, .toothTime = 10000, .angle = 25, .expected = 83333 }, // 83333,3333
{ .rpm = 50, .triggerToothAngle = 3, .toothTime = 10000, .angle = 720, .expected = 2400000 },
{ .rpm = 2500, .triggerToothAngle = 3, .toothTime = 200, .angle = 0, .expected = 0 },
{ .rpm = 2500, .triggerToothAngle = 3, .toothTime = 200, .angle = 25, .expected = 1666 }, // 1666,6666
{ .rpm = 2500, .triggerToothAngle = 3, .toothTime = 200, .angle = 720, .expected = 48000 },
{ .rpm = 20000, .triggerToothAngle = 3, .toothTime = 25, .angle = 0, .expected = 0 },
{ .rpm = 20000, .triggerToothAngle = 3, .toothTime = 25, .angle = 25, .expected = 208 }, // 208,3333
{ .rpm = 20000, .triggerToothAngle = 3, .toothTime = 25, .angle = 720, .expected = 6000 },
{ .rpm = 50, .triggerToothAngle = 180, .toothTime = 600000, .angle = 0, .expected = 0 },
{ .rpm = 50, .triggerToothAngle = 180, .toothTime = 600000, .angle = 25, .expected = 83333 }, // 83333,3333
{ .rpm = 50, .triggerToothAngle = 180, .toothTime = 600000, .angle = 720, .expected = 2400000 },
{ .rpm = 2500, .triggerToothAngle = 180, .toothTime = 12000, .angle = 0, .expected = 0 },
{ .rpm = 2500, .triggerToothAngle = 180, .toothTime = 12000, .angle = 25, .expected = 1666 }, // 1666,6666
{ .rpm = 2500, .triggerToothAngle = 180, .toothTime = 12000, .angle = 720, .expected = 48000 },
{ .rpm = 20000, .triggerToothAngle = 180, .toothTime = 1500, .angle = 0, .expected = 0 },
{ .rpm = 20000, .triggerToothAngle = 180, .toothTime = 1500, .angle = 25, .expected = 208 }, // 208,3333
{ .rpm = 20000, .triggerToothAngle = 180, .toothTime = 1500, .angle = 720, .expected = 6000 },
};
// The same for all tests
BIT_SET(decoderState, BIT_DECODER_TOOTH_ANG_CORRECT);
toothLastMinusOneToothTime = 200000;
for (auto testdata : crankmaths_tooth_testdatas) {
crankmaths_tooth_testdata_current = &testdata;
snprintf(testName, testNameLength, "crankmaths/angletotime/tooth/%urpm/%uangle/%utoothangle", testdata.rpm, testdata.angle, testdata.triggerToothAngle);
UnityDefaultTestRun(test_crankmaths_angletotime_tooth_execute, testName, __LINE__);
}
}

View File

@ -71,8 +71,6 @@ void test_calc_ign_timeout_360()
setEngineSpeed(4000, 360);
TEST_ASSERT_EQUAL(15000, revolutionTime);
TEST_ASSERT_EQUAL(786, degreesPerMicro);
TEST_ASSERT_EQUAL(10667, microsPerDegree);
TEST_ASSERT_EQUAL(96, dwellAngle);
// Expected test values were generated using floating point calculations (in Excel)