diff --git a/firmware/.cproject b/firmware/.cproject
new file mode 100644
index 0000000000..3a7aac2cb3
--- /dev/null
+++ b/firmware/.cproject
@@ -0,0 +1,372 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/firmware/.project b/firmware/.project
new file mode 100644
index 0000000000..9cbf9e05bb
--- /dev/null
+++ b/firmware/.project
@@ -0,0 +1,47 @@
+
+
+ chibios_template
+
+
+
+
+
+ org.eclipse.cdt.managedbuilder.core.genmakebuilder
+ clean,full,incremental,
+
+
+
+
+ org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder
+ full,incremental,
+
+
+
+
+
+ org.eclipse.cdt.core.cnature
+ org.eclipse.cdt.core.ccnature
+ org.eclipse.cdt.managedbuilder.core.managedBuildNature
+ org.eclipse.cdt.managedbuilder.core.ScannerConfigNature
+
+
+
+ 1381697360903
+
+ 10
+
+ org.eclipse.ui.ide.multiFilter
+ 1.0-name-matches-false-false-iar
+
+
+
+ 1381675425786
+ chibios/os/ports
+ 10
+
+ org.eclipse.ui.ide.multiFilter
+ 1.0-name-matches-false-false-IAR
+
+
+
+
diff --git a/firmware/Doxyfile b/firmware/Doxyfile
new file mode 100644
index 0000000000..26965acd49
--- /dev/null
+++ b/firmware/Doxyfile
@@ -0,0 +1,2318 @@
+# Doxyfile 1.8.5
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all text
+# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv
+# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv
+# for the list of possible encodings.
+# The default value is: UTF-8.
+
+DOXYFILE_ENCODING = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
+# double-quotes, unless you are using Doxywizard) that should identify the
+# project for which the documentation is generated. This name is used in the
+# title of most generated pages and in a few other places.
+# The default value is: My Project.
+
+PROJECT_NAME = "rusEFI"
+
+LAYOUT_FILE = DoxygenLayout.xml
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
+# could be handy for archiving the generated documentation or if some version
+# control system is used.
+
+PROJECT_NUMBER =
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer a
+# quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF = "An attempt to build an Engine Control Unit"
+
+# With the PROJECT_LOGO tag one can specify an logo or icon that is included in
+# the documentation. The maximum height of the logo should not exceed 55 pixels
+# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo
+# to the output directory.
+
+PROJECT_LOGO =
+
+# 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
+# left blank the current directory will be used.
+
+OUTPUT_DIRECTORY = ../doxygen
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub-
+# directories (in 2 levels) under the output directory of each output format and
+# will distribute the generated files over these directories. Enabling this
+# option can be useful when feeding doxygen a huge amount of source files, where
+# putting all generated files in the same directory would otherwise causes
+# performance problems for the file system.
+# The default value is: NO.
+
+CREATE_SUBDIRS = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# Possible values are: Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-
+# Traditional, Croatian, Czech, Danish, Dutch, English, Esperanto, Farsi,
+# Finnish, French, German, Greek, Hungarian, Italian, Japanese, Japanese-en,
+# Korean, Korean-en, Latvian, Norwegian, Macedonian, Persian, Polish,
+# Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, Swedish,
+# Turkish, Ukrainian and Vietnamese.
+# The default value is: English.
+
+OUTPUT_LANGUAGE = English
+
+# 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.
+# The default value is: YES.
+
+BRIEF_MEMBER_DESC = YES
+
+# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief
+# description of a member or function before the detailed description
+#
+# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+# The default value is: YES.
+
+REPEAT_BRIEF = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator that is
+# used to form the text in various listings. Each string in this list, if found
+# as the leading text of the brief description, will be stripped from the text
+# and the result, after processing the whole list, is used as the annotated
+# text. Otherwise, the brief description is used as-is. If left blank, the
+# following values are used ($name is automatically replaced with the name of
+# the entity):The $name class, The $name widget, The $name file, is, provides,
+# specifies, contains, represents, a, an and the.
+
+ABBREVIATE_BRIEF =
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# doxygen will generate a detailed section even if there is only a brief
+# description.
+# The default value is: NO.
+
+ALWAYS_DETAILED_SEC = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+# The default value is: NO.
+
+INLINE_INHERITED_MEMB = NO
+
+# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path
+# before files name in the file list and in the header files. If set to NO the
+# shortest path that makes the file name unique will be used
+# The default value is: YES.
+
+FULL_PATH_NAMES = YES
+
+# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
+# Stripping is only done if one of the specified strings matches the left-hand
+# part of the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the path to
+# strip.
+#
+# Note that you can specify absolute paths here, but also relative paths, which
+# will be relative from the directory where doxygen is started.
+# This tag requires that the tag FULL_PATH_NAMES is set to YES.
+
+STRIP_FROM_PATH =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
+# path mentioned in the documentation of a class, which tells the reader which
+# header file to include in order to use a class. If left blank only the name of
+# the header file containing the class definition is used. Otherwise one should
+# specify the list of include paths that are normally passed to the compiler
+# using the -I flag.
+
+STRIP_FROM_INC_PATH =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
+# less readable) file names. This can be useful is your file systems doesn't
+# support long names like on DOS, Mac, or CD-ROM.
+# The default value is: NO.
+
+SHORT_NAMES = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
+# first line (until the first dot) of a Javadoc-style comment as the brief
+# description. If set to NO, the Javadoc-style will behave just like regular Qt-
+# style comments (thus requiring an explicit @brief command for a brief
+# description.)
+# The default value is: NO.
+
+JAVADOC_AUTOBRIEF = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
+# line (until the first dot) of a Qt-style comment as the brief description. If
+# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
+# requiring an explicit \brief command for a brief description.)
+# The default value is: NO.
+
+QT_AUTOBRIEF = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
+# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
+# a brief description. This used to be the default behavior. The new default is
+# to treat a multi-line C++ comment block as a detailed description. Set this
+# tag to YES if you prefer the old behavior instead.
+#
+# Note that setting this tag to YES also means that rational rose comments are
+# not recognized any more.
+# The default value is: NO.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
+# documentation from any documented member that it re-implements.
+# The default value is: YES.
+
+INHERIT_DOCS = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a
+# new page for each member. If set to NO, the documentation of a member will be
+# part of the file/class/namespace that contains it.
+# The default value is: NO.
+
+SEPARATE_MEMBER_PAGES = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
+# uses this value to replace tabs by spaces in code fragments.
+# Minimum value: 1, maximum value: 16, default value: 4.
+
+TAB_SIZE = 4
+
+# This tag can be used to specify a number of aliases that act as commands in
+# the documentation. An alias has the form:
+# name=value
+# For example adding
+# "sideeffect=@par Side Effects:\n"
+# 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.
+
+ALIASES =
+
+# This tag can be used to specify a number of word-keyword mappings (TCL only).
+# A mapping has the form "name=value". For example adding "class=itcl::class"
+# will allow you to use the command class in the itcl::class meaning.
+
+TCL_SUBST =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C. For
+# instance, some of the names that are used will be different. The list of all
+# members will be omitted, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_FOR_C = YES
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
+# Python sources only. Doxygen will then generate output that is more tailored
+# for that language. For instance, namespaces will be presented as packages,
+# qualified scopes will look different, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_JAVA = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources. Doxygen will then generate output that is tailored for Fortran.
+# The default value is: NO.
+
+OPTIMIZE_FOR_FORTRAN = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for VHDL.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_VHDL = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given
+# 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,
+# C#, C, C++, D, PHP, Objective-C, Python, Fortran, VHDL. For instance to make
+# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C
+# (default is Fortran), use: inc=Fortran f=C.
+#
+# 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.
+
+EXTENSION_MAPPING =
+
+# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
+# according to the Markdown format, which allows for more readable
+# documentation. See http://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you can
+# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
+# case of backward compatibilities issues.
+# The default value is: YES.
+
+MARKDOWN_SUPPORT = YES
+
+# 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 by putting a % sign in front of the word
+# or globally by setting AUTOLINK_SUPPORT to NO.
+# The default value is: YES.
+
+AUTOLINK_SUPPORT = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should set this
+# tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string);
+# versus func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+# The default value is: NO.
+
+BUILTIN_STL_SUPPORT = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+# The default value is: NO.
+
+CPP_CLI_SUPPORT = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
+# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen
+# will parse them like normal C++ but will assume all classes use public instead
+# of private inheritance when no explicit protection keyword is present.
+# The default value is: NO.
+
+SIP_SUPPORT = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate
+# getter and setter methods for a property. Setting this option to YES will make
+# doxygen to replace the get and set methods by a property in the documentation.
+# This will only work if the methods are indeed getting or setting a simple
+# type. If this is not the case, or you want to show the methods anyway, you
+# should set this option to NO.
+# The default value is: YES.
+
+IDL_PROPERTY_SUPPORT = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+# The default value is: NO.
+
+DISTRIBUTE_GROUP_DOC = NO
+
+# Set the SUBGROUPING tag to YES to allow class member groups of the same type
+# (for instance a group of public functions) to be put as a subgroup of that
+# type (e.g. under the Public Functions section). Set it to NO to prevent
+# subgrouping. Alternatively, this can be done per class using the
+# \nosubgrouping command.
+# The default value is: YES.
+
+SUBGROUPING = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
+# are shown inside the group in which they are included (e.g. using \ingroup)
+# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
+# and RTF).
+#
+# Note that this feature does not work in combination with
+# SEPARATE_MEMBER_PAGES.
+# The default value is: NO.
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
+# with only public data fields or simple typedef fields will be shown inline in
+# the documentation of the scope in which they are defined (i.e. file,
+# namespace, or group documentation), provided this scope is documented. If set
+# to NO, structs, classes, and unions are shown on a separate page (for HTML and
+# Man pages) or section (for LaTeX and RTF).
+# The default value is: NO.
+
+INLINE_SIMPLE_STRUCTS = NO
+
+# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
+# enum is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically be
+# useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+# The default value is: NO.
+
+TYPEDEF_HIDES_STRUCT = NO
+
+# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
+# cache is used to resolve symbols given their name and scope. Since this can be
+# an expensive process and often the same symbol appears multiple times in the
+# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
+# doxygen will become slower. If the cache is too large, memory is wasted. The
+# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
+# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
+# symbols. At the end of a run doxygen will report the cache usage and suggest
+# the optimal cache size from a speed point of view.
+# Minimum value: 0, maximum value: 9, default value: 0.
+
+LOOKUP_CACHE_SIZE = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available. Private
+# class members and static file members will be hidden unless the
+# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
+# Note: This will also disable the warnings about undocumented members that are
+# normally produced when WARNINGS is set to YES.
+# The default value is: NO.
+
+EXTRACT_ALL = YES
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will
+# be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIVATE = NO
+
+# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal
+# scope will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PACKAGE = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file will be
+# included in the documentation.
+# The default value is: NO.
+
+EXTRACT_STATIC = YES
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined
+# locally in source files will be included in the documentation. If set to NO
+# only classes defined in header files are included. Does not have any effect
+# for Java sources.
+# The default value is: YES.
+
+EXTRACT_LOCAL_CLASSES = NO
+
+# This flag is only useful for Objective-C code. When set to YES local methods,
+# which are defined in the implementation section but not in the interface are
+# included in the documentation. If set to NO only methods in the interface are
+# included.
+# The default value is: NO.
+
+EXTRACT_LOCAL_METHODS = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base name of
+# the file that contains the anonymous namespace. By default anonymous namespace
+# are hidden.
+# The default value is: NO.
+
+EXTRACT_ANON_NSPACES = NO
+
+# 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
+# section is generated. This option has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+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.
+# The default value is: NO.
+
+HIDE_UNDOC_CLASSES = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
+# (class|struct|union) declarations. If set to NO these declarations will be
+# included in the documentation.
+# The default value is: NO.
+
+HIDE_FRIEND_COMPOUNDS = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
+# documentation blocks found inside the body of a function. If set to NO these
+# blocks will be appended to the function's detailed documentation block.
+# The default value is: NO.
+
+HIDE_IN_BODY_DOCS = NO
+
+# The INTERNAL_DOCS tag determines if documentation that is typed after a
+# \internal command is included. If the tag is set to NO then the documentation
+# will be excluded. Set it to YES to include the internal documentation.
+# The default value is: 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
+# and Mac users are advised to set this option to NO.
+# The default value is: system dependent.
+
+CASE_SENSE_NAMES = NO
+
+# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
+# their full class and namespace scopes in the documentation. If set to YES the
+# scope will be hidden.
+# The default value is: NO.
+
+HIDE_SCOPE_NAMES = NO
+
+# 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.
+
+SHOW_INCLUDE_FILES = YES
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
+# files with double quotes in the documentation rather than with sharp brackets.
+# The default value is: NO.
+
+FORCE_LOCAL_INCLUDES = NO
+
+# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
+# documentation for inline members.
+# The default value is: YES.
+
+INLINE_INFO = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
+# (detailed) documentation of file and class members alphabetically by member
+# name. If set to NO the members will appear in declaration order.
+# The default value is: YES.
+
+SORT_MEMBER_DOCS = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
+# descriptions of file, namespace and class members alphabetically by member
+# name. If set to NO the members will appear in declaration order.
+# The default value is: NO.
+
+SORT_BRIEF_DOCS = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
+# (brief and detailed) documentation of class members so that constructors and
+# destructors are listed first. If set to NO the constructors will appear in the
+# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
+# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
+# member documentation.
+# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
+# detailed member documentation.
+# The default value is: NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
+# of group names into alphabetical order. If set to NO the group names will
+# appear in their defined order.
+# The default value is: NO.
+
+SORT_GROUP_NAMES = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
+# fully-qualified names, including namespaces. If set to NO, the class list will
+# be sorted only by class name, not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the alphabetical
+# list.
+# The default value is: NO.
+
+SORT_BY_SCOPE_NAME = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
+# type resolution of all parameters of a function it will reject a match between
+# the prototype and the implementation of a member function even if there is
+# only one candidate or it is obvious which candidate to choose by doing a
+# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
+# accept a match between prototype and implementation in such cases.
+# The default value is: NO.
+
+STRICT_PROTO_MATCHING = NO
+
+# The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the
+# todo list. This list is created by putting \todo commands in the
+# documentation.
+# The default value is: YES.
+
+GENERATE_TODOLIST = YES
+
+# The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the
+# test list. This list is created by putting \test commands in the
+# documentation.
+# The default value is: YES.
+
+GENERATE_TESTLIST = YES
+
+# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug
+# list. This list is created by putting \bug commands in the documentation.
+# The default value is: YES.
+
+GENERATE_BUGLIST = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO)
+# the deprecated list. This list is created by putting \deprecated commands in
+# the documentation.
+# The default value is: YES.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional documentation
+# sections, marked by \if ... \endif and \cond
+# ... \endcond blocks.
+
+ENABLED_SECTIONS =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
+# initial value of a variable or macro / define can have for it to appear in the
+# documentation. If the initializer consists of more lines than specified here
+# it will be hidden. Use a value of 0 to hide initializers completely. The
+# appearance of the value of individual variables and macros / defines can be
+# controlled using \showinitializer or \hideinitializer command in the
+# documentation regardless of this setting.
+# Minimum value: 0, maximum value: 10000, default value: 30.
+
+MAX_INITIALIZER_LINES = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
+# the bottom of the documentation of classes and structs. If set to YES the list
+# will mention the files that were used to generate the documentation.
+# The default value is: YES.
+
+SHOW_USED_FILES = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
+# will remove the Files entry from the Quick Index and from the Folder Tree View
+# (if specified).
+# The default value is: YES.
+
+SHOW_FILES = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
+# page. This will remove the Namespaces entry from the Quick Index and from the
+# Folder Tree View (if specified).
+# The default value is: YES.
+
+SHOW_NAMESPACES = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command command input-file, where command is the value of the
+# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
+# by doxygen. Whatever the program writes to standard output is used as the file
+# version. For an example see the documentation.
+
+FILE_VERSION_FILTER =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# 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.
+#
+# 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
+# tag is left empty.
+
+LAYOUT_FILE =
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
+# the reference definitions. This must be a list of .bib files. The .bib
+# extension is automatically appended if omitted. This requires the bibtex tool
+# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info.
+# For LaTeX the style of the bibliography can be controlled using
+# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
+# search path. Do not use file names with spaces, bibtex cannot handle them. See
+# also \cite for info how to create references.
+
+CITE_BIB_FILES =
+
+#---------------------------------------------------------------------------
+# Configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated to
+# standard output by doxygen. If QUIET is set to YES this implies that the
+# messages are off.
+# The default value is: NO.
+
+QUIET = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES
+# this implies that the warnings are on.
+#
+# Tip: Turn warnings on while writing the documentation.
+# The default value is: YES.
+
+WARNINGS = YES
+
+# If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate
+# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: 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.
+# The default value is: YES.
+
+WARN_IF_DOC_ERROR = 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.
+# The default value is: NO.
+
+WARN_NO_PARAMDOC = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that doxygen
+# can produce. The string should contain the $file, $line, and $text tags, which
+# will be replaced by the file and line number from which the warning originated
+# and the warning text. Optionally the format may contain $version, which will
+# be replaced by the version of the file (if it could be obtained via
+# FILE_VERSION_FILTER)
+# The default value is: $file:$line: $text.
+
+WARN_FORMAT = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning and error
+# messages should be written. If left blank the output is written to standard
+# error (stderr).
+
+WARN_LOGFILE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag is used to specify the files and/or directories that contain
+# documented source files. You may enter file names like myfile.cpp or
+# directories like /usr/src/myproject. Separate the files or directories with
+# spaces.
+# Note: If this tag is empty the current directory is searched.
+
+INPUT = . \
+ config \
+ config/engines \
+ config/boards \
+ console_util \
+ util \
+ console \
+ controllers \
+ controllers/algo \
+ controllers/core \
+ controllers/math \
+ controllers/sensors \
+ controllers/system \
+ controllers/trigger \
+ emulation \
+ hw_layer/lcd \
+ hw_layer
+
+
+# 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: http://www.gnu.org/software/libiconv) for the list of
+# possible encodings.
+# The default value is: UTF-8.
+
+INPUT_ENCODING = UTF-8
+
+# 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. 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, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf,
+# *.qsf, *.as and *.js.
+
+FILE_PATTERNS =
+
+# The RECURSIVE tag can be used to specify whether or not subdirectories should
+# be searched for input files as well.
+# The default value is: NO.
+
+RECURSIVE = NO
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+#
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
+EXCLUDE =
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+# The default value is: NO.
+
+EXCLUDE_SYMLINKS = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories for example use the pattern */test/*
+
+EXCLUDE_PATTERNS =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, 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 = cisnan, validateBuffer, append
+
+# 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
+# command).
+
+EXAMPLE_PATH =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank all
+# files are included.
+
+EXAMPLE_PATTERNS =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude commands
+# irrespective of the value of the RECURSIVE tag.
+# The default value is: NO.
+
+EXAMPLE_RECURSIVE = YES
+
+# The IMAGE_PATH tag can be used to specify one or more files or directories
+# that contain images that are to be included in the documentation (see the
+# \image command).
+
+IMAGE_PATH =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command:
+#
+#
+#
+# where is the value of the INPUT_FILTER tag, and is the
+# name of an input file. Doxygen will then use the output that the filter
+# program writes to standard output. If FILTER_PATTERNS is specified, this tag
+# will be ignored.
+#
+# Note that the filter must not add or remove lines; it is applied before the
+# code is scanned, but not when the output code is generated. If lines are added
+# or removed, the anchors will not be placed correctly.
+
+INPUT_FILTER =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form: pattern=filter
+# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
+# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
+# patterns match the file name, INPUT_FILTER is applied.
+
+FILTER_PATTERNS =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER ) will also be used to filter the input files that are used for
+# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
+# The default value is: NO.
+
+FILTER_SOURCE_FILES = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
+# it is also possible to disable source filtering for a specific pattern using
+# *.ext= (so without naming a filter).
+# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
+
+FILTER_SOURCE_PATTERNS =
+
+# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
+# is part of the input, its contents will be placed on the main page
+# (index.html). This can be useful if you have a project on for instance GitHub
+# and want to reuse the introduction page also for the doxygen output.
+
+USE_MDFILE_AS_MAINPAGE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
+# generated. Documented entities will be cross-referenced with these sources.
+#
+# Note: To get rid of all source code in the generated output, make sure that
+# also VERBATIM_HEADERS is set to NO.
+# The default value is: NO.
+
+SOURCE_BROWSER = YES
+
+# Setting the INLINE_SOURCES tag to YES will include the body of functions,
+# classes and enums directly into the documentation.
+# The default value is: NO.
+
+INLINE_SOURCES = YES
+
+# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
+# special comment blocks from generated source code fragments. Normal C, C++ and
+# Fortran comments will always remain visible.
+# The default value is: YES.
+
+STRIP_CODE_COMMENTS = NO
+
+# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
+# function all documented functions referencing it will be listed.
+# The default value is: NO.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES then for each documented function
+# all documented entities called/used by that function will be listed.
+# The default value is: NO.
+
+REFERENCES_RELATION = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
+# to YES, then the hyperlinks from functions in REFERENCES_RELATION and
+# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
+# link to the documentation.
+# The default value is: YES.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
+# source code will show a tooltip with additional information such as prototype,
+# brief description and links to the definition and documentation. Since this
+# will make the HTML file larger and loading of large files a bit slower, you
+# can opt to disable this feature.
+# The default value is: YES.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+SOURCE_TOOLTIPS = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code will
+# point to the HTML generated by the htags(1) tool instead of doxygen built-in
+# source browser. The htags tool is part of GNU's global source tagging system
+# (see http://www.gnu.org/software/global/global.html). You will need version
+# 4.8.6 or higher.
+#
+# To use it do the following:
+# - Install the latest version of global
+# - Enable SOURCE_BROWSER and USE_HTAGS in the config file
+# - Make sure the INPUT points to the root of the source tree
+# - Run doxygen as normal
+#
+# Doxygen will invoke htags (and that will in turn invoke gtags), so these
+# tools must be available from the command line (i.e. in the search path).
+#
+# The result: instead of the source browser generated by doxygen, the links to
+# source code will now point to the output of htags.
+# The default value is: NO.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+USE_HTAGS = NO
+
+# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
+# verbatim copy of the header file for each class for which an include is
+# specified. Set to NO to disable this.
+# See also: Section \class.
+# The default value is: YES.
+
+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 acurate 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
+# compiled with the --with-libclang option.
+# The default value is: NO.
+
+CLANG_ASSISTED_PARSING = NO
+
+# 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 =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
+# compounds will be generated. Enable this if the project contains a lot of
+# classes, structs, unions or interfaces.
+# The default value is: 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
+# while generating the index headers.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+IGNORE_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output
+# The default value is: YES.
+
+GENERATE_HTML = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs 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: html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_OUTPUT = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
+# generated HTML page (for example: .htm, .php, .asp).
+# The default value is: .html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FILE_EXTENSION = .html
+
+# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
+# each generated HTML page. If the tag is left blank doxygen will generate a
+# standard header.
+#
+# To get valid HTML the header file that includes any scripts and style sheets
+# that doxygen needs, which is dependent on the configuration options used (e.g.
+# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
+# default header using
+# doxygen -w html new_header.html new_footer.html new_stylesheet.css
+# YourConfigFile
+# and then modify the file new_header.html. See also section "Doxygen usage"
+# for information on how to generate the default header that doxygen normally
+# uses.
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. For a description
+# of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_HEADER =
+
+# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
+# generated HTML page. If the tag is left blank doxygen will generate a standard
+# footer. See HTML_HEADER for more information on how to generate a default
+# footer and what 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.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FOOTER =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
+# sheet that is used by each HTML page. It can be used to fine-tune the look of
+# the HTML output. If left blank doxygen will generate a default style sheet.
+# See also section "Doxygen usage" for information on how to generate the style
+# sheet that doxygen normally uses.
+# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
+# it is more robust and this tag (HTML_STYLESHEET) will in the future become
+# obsolete.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_STYLESHEET =
+
+# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional user-
+# defined cascading style sheet that is included after the standard style sheets
+# created by doxygen. Using this option one can overrule certain style aspects.
+# This is preferred over using HTML_STYLESHEET since it does not replace the
+# standard style sheet and is therefor more robust against future updates.
+# Doxygen will copy the style sheet file to the output directory. For an example
+# see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_STYLESHEET =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
+# files will be copied as-is; there are no commands or markers available.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_FILES =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
+# will adjust the colors in the stylesheet and background images according to
+# this color. Hue is specified as an angle on a colorwheel, see
+# http://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.
+# Minimum value: 0, maximum value: 359, default value: 220.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+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
+# 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.
+
+HTML_COLORSTYLE_SAT = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
+# luminance component of the colors in the HTML output. Values below 100
+# gradually make the output lighter, whereas values above 100 make the output
+# darker. The value divided by 100 is the actual gamma applied, so 80 represents
+# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
+# change the gamma.
+# Minimum value: 40, maximum value: 240, default value: 80.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+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 NO can help when comparing the output of multiple runs.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_TIMESTAMP = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_SECTIONS = NO
+
+# 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
+# such a level that at most the specified number of entries are visible (unless
+# a fully collapsed tree already exceeds this amount). So setting the number of
+# entries 1 will produce a full collapsed tree by default. 0 is a special value
+# representing an infinite number of entries and will result in a full expanded
+# tree by default.
+# Minimum value: 0, maximum value: 9999, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+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: http://developer.apple.com/tools/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 http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_DOCSET = NO
+
+# This tag determines the name of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# The default value is: Doxygen generated docs.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_FEEDNAME = "Doxygen generated docs"
+
+# This tag specifies a string that should uniquely identify the documentation
+# set bundle. This should be a reverse domain-name style string, e.g.
+# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_BUNDLE_ID = org.doxygen.Project
+
+# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+# The default value is: org.doxygen.Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_ID = org.doxygen.Publisher
+
+# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
+# The default value is: Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+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: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on
+# Windows.
+#
+# 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
+# files are now used as the Windows 98 help format, and will replace the old
+# Windows help format (.hlp) on all Windows platforms in the future. Compressed
+# HTML files also contain an index, a table of contents, and you can search for
+# words in the documentation. The HTML workshop also contains a viewer for
+# compressed HTML files.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_HTMLHELP = NO
+
+# The CHM_FILE tag can be used to specify the file name of the resulting .chm
+# file. You can add a path in front of the file if the result should not be
+# written to the html output directory.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_FILE =
+
+# The HHC_LOCATION tag can be used to specify the location (absolute path
+# including file name) of the HTML help compiler ( hhc.exe). If non-empty
+# doxygen will try to run the HTML help compiler on the generated index.hhp.
+# The file has to be specified with full path.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+HHC_LOCATION =
+
+# The GENERATE_CHI flag controls if a separate .chi index file is generated (
+# YES) or that it should be included in the master .chm file ( NO).
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+GENERATE_CHI = NO
+
+# The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc)
+# and project file content.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_INDEX_ENCODING =
+
+# The BINARY_TOC flag controls whether a binary table of contents is generated (
+# YES) or a normal table of contents ( NO) in the .chm file.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+BINARY_TOC = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members to
+# the table of contents of the HTML help documentation and to the tree view.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+TOC_EXPAND = NO
+
+# 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
+# (.qch) of the generated HTML documentation.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_QHP = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
+# the file name of the resulting .qch file. The path specified is relative to
+# the HTML output folder.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+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: http://qt-project.org/doc/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.
+
+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: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual-
+# folders).
+# The default value is: doc.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+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: http://qt-project.org/doc/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: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_ATTRS =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's filter section matches. Qt Help Project / Filter Attributes (see:
+# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+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.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHG_LOCATION =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
+# generated, together with the HTML files, they form an Eclipse help plugin. To
+# install this plugin and make it available under the help contents menu in
+# Eclipse, the contents of the directory containing the HTML and XML files needs
+# to be copied into the plugins directory of eclipse. The name of the directory
+# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
+# After copying Eclipse needs to be restarted before the help appears.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_ECLIPSEHELP = NO
+
+# A unique identifier for the Eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have this
+# name. Each documentation set should have its own identifier.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
+
+ECLIPSE_DOC_ID = org.doxygen.Project
+
+# If you want full control over the layout of the generated HTML pages it might
+# be necessary to disable the index and replace it with your own. The
+# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
+# of each HTML page. A value of NO enables the index and the value YES disables
+# it. Since the tabs in the index contain the same information as the navigation
+# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+DISABLE_INDEX = NO
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information. If the tag
+# value is set to YES, a side panel will be generated containing a tree-like
+# index structure (just like the one that is generated for HTML Help). For this
+# 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 stylesheets (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.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_TREEVIEW = 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.
+#
+# Note that a value of 0 will completely suppress the enum values from appearing
+# in the overview section.
+# Minimum value: 0, maximum value: 20, default value: 4.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+ENUM_VALUES_PER_LINE = 4
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
+# to set the initial width (in pixels) of the frame in which the tree is shown.
+# Minimum value: 0, maximum value: 1500, default value: 250.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+TREEVIEW_WIDTH = 250
+
+# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to
+# external symbols imported via tag files in a separate window.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+EXT_LINKS_IN_WINDOW = NO
+
+# Use this tag to change the font size of LaTeX formulas included as images in
+# the HTML documentation. When you change the font size after a successful
+# doxygen run you need to manually remove any form_*.png images from the HTML
+# output directory to force them to be regenerated.
+# Minimum value: 8, maximum value: 50, default value: 10.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_FONTSIZE = 10
+
+# Use the FORMULA_TRANPARENT 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
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
+# http://www.mathjax.org) which uses client side Javascript for the rendering
+# instead of using prerendered bitmaps. Use this if you do not have LaTeX
+# installed or if you want to formulas look prettier in the HTML output. When
+# enabled you may also need to install MathJax separately and configure the path
+# to it using the MATHJAX_RELPATH option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+USE_MATHJAX = NO
+
+# 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.
+# Possible values are: HTML-CSS (which is slower, but has the best
+# compatibility), NativeMML (i.e. MathML) and SVG.
+# The default value is: HTML-CSS.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_FORMAT = HTML-CSS
+
+# When MathJax is enabled you need to specify the location relative to the HTML
+# output directory using the MATHJAX_RELPATH option. The destination directory
+# should contain the MathJax.js script. For instance, if the mathjax directory
+# is located at the same level as the HTML output directory, then
+# 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 http://www.mathjax.org before deployment.
+# The default value is: http://cdn.mathjax.org/mathjax/latest.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
+# extension names that should be enabled during MathJax rendering. For example
+# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
+# 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
+# example see the documentation.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_CODEFILE =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
+# the HTML output. The underlying search engine uses javascript and DHTML and
+# should work on any modern browser. Note that when using HTML help
+# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
+# there is already a search function so this one should typically be disabled.
+# For large projects the javascript based search engine can be slow, then
+# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
+# search using the keyboard; to jump to the search box use + S
+# (what the is depends on the OS and browser, but it is typically
+# , /, or both). Inside the search box use the to jump into the search results window, the results can be navigated
+# using the . Press to select an item or to cancel
+# the search. The filter options can be selected when the cursor is inside the
+# search box by pressing +. Also here use the
+# to select a filter and or to activate or cancel the filter
+# option.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+SEARCHENGINE = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a web server instead of a web client using Javascript. There
+# are two flavours of web server based searching depending on the
+# EXTERNAL_SEARCH setting. When disabled, doxygen will generate a PHP script for
+# searching and an index file used by the script. When EXTERNAL_SEARCH is
+# enabled the indexing and searching needs to be provided by external tools. See
+# the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SERVER_BASED_SEARCH = NO
+
+# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
+# script for searching. Instead the search results are written to an XML file
+# which needs to be processed by an external indexer. Doxygen will invoke an
+# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
+# search results.
+#
+# Doxygen ships with an example indexer ( doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/).
+#
+# See the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH = NO
+
+# The SEARCHENGINE_URL should point to a search engine hosted by a web server
+# which will return the search results when EXTERNAL_SEARCH is enabled.
+#
+# Doxygen ships with an example indexer ( doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/). See the section "External Indexing and
+# Searching" for details.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHENGINE_URL =
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
+# search data is written to a file for indexing by an external tool. With the
+# SEARCHDATA_FILE tag the name of this file can be specified.
+# The default file is: searchdata.xml.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHDATA_FILE = searchdata.xml
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
+# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
+# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
+# projects and redirect the results back to the right project.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH_ID =
+
+# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
+# projects other than the one defined by this configuration file, but that are
+# all added to the same external search index. Each project needs to have a
+# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
+# to a relative location where the documentation can be found. The format is:
+# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTRA_SEARCH_MAPPINGS =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES doxygen will generate LaTeX output.
+# The default value is: YES.
+
+GENERATE_LATEX = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs 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: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_OUTPUT = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked.
+#
+# Note that when enabling USE_PDFLATEX this option is only used for generating
+# bitmaps for formulas in the HTML output, but not in the Makefile that is
+# written to the output directory.
+# The default file is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_CMD_NAME = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
+# index for LaTeX.
+# The default file is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+MAKEINDEX_CMD_NAME = makeindex
+
+# If the COMPACT_LATEX tag is set to YES doxygen generates more compact LaTeX
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+COMPACT_LATEX = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used by the
+# printer.
+# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
+# 14 inches) and executive (7.25 x 10.5 inches).
+# The default value is: a4.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PAPER_TYPE = a4
+
+# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
+# that should be included in the LaTeX output. To get the times font for
+# instance you can specify
+# EXTRA_PACKAGES=times
+# If left blank no extra packages will be included.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+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.
+#
+# 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. Doxygen will
+# replace them by respectively the title of the page, the current date and time,
+# only the current date, the version number of doxygen, the project name (see
+# PROJECT_NAME), or the project number (see PROJECT_NUMBER).
+# 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.
+#
+# 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 =
+
+# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the LATEX_OUTPUT output
+# directory. Note that the files will be copied as-is; there are no commands or
+# markers available.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_FILES =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
+# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
+# contain links (just like the HTML output) instead of page references. This
+# makes the output suitable for online browsing using a PDF viewer.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PDF_HYPERLINKS = YES
+
+# If the LATEX_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
+# the PDF file directly from the LaTeX files. Set this option to YES to get a
+# higher quality PDF documentation.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to 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. This option is also used
+# when generating formulas in HTML.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BATCHMODE = NO
+
+# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
+# index chapters (such as File Index, Compound Index, etc.) in the output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+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
+# http://en.wikipedia.org/wiki/BibTeX and \cite for more info.
+# The default value is: plain.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BIB_STYLE = plain
+
+#---------------------------------------------------------------------------
+# Configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES doxygen will generate RTF output. The
+# RTF output is optimized for Word 97 and may not look too pretty with other RTF
+# readers/editors.
+# The default value is: NO.
+
+GENERATE_RTF = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs 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: rtf.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_OUTPUT = rtf
+
+# If the COMPACT_RTF tag is set to YES doxygen generates more compact RTF
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+COMPACT_RTF = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
+# contain hyperlink fields. The RTF file will contain links (just like the HTML
+# output) instead of page references. This makes the output suitable for online
+# browsing using Word or some other Word compatible readers that support those
+# fields.
+#
+# Note: WordPad (write) and others do not support links.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_HYPERLINKS = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's config
+# file, i.e. a series of assignments. You only have to provide replacements,
+# missing definitions are set to their default value.
+#
+# See also section "Doxygen usage" for information on how to generate the
+# default style sheet that doxygen normally uses.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_STYLESHEET_FILE =
+
+# Set optional variables used in the generation of an RTF document. Syntax is
+# similar to doxygen's config file. A template extensions file can be generated
+# using doxygen -e rtf extensionFile.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_EXTENSIONS_FILE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES doxygen will generate man pages for
+# classes and files.
+# The default value is: NO.
+
+GENERATE_MAN = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it. A directory man3 will be created inside the directory specified by
+# MAN_OUTPUT.
+# The default directory is: man.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_OUTPUT = man
+
+# The MAN_EXTENSION tag determines the extension that is added to the generated
+# man pages. In case the manual section does not start with a number, the number
+# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
+# optional.
+# The default value is: .3.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_EXTENSION = .3
+
+# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
+# will generate one additional man file for each entity documented in the real
+# man page(s). These additional files only source the real man page, but without
+# them the man command would be unable to find the correct page.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_LINKS = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES doxygen will generate an XML file that
+# captures the structure of the code including all documentation.
+# The default value is: NO.
+
+GENERATE_XML = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages 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: xml.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_OUTPUT = xml
+
+# The XML_SCHEMA tag can be used to specify a XML schema, which can be used by a
+# validating XML parser to check the syntax of the XML files.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_SCHEMA =
+
+# The XML_DTD tag can be used to specify a XML DTD, which can be used by a
+# validating XML parser to check the syntax of the XML files.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_DTD =
+
+# If the XML_PROGRAMLISTING tag is set to YES doxygen will dump the program
+# listings (including syntax highlighting and cross-referencing information) to
+# the XML output. Note that enabling this will significantly increase the size
+# of the XML output.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_PROGRAMLISTING = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_DOCBOOK tag is set to YES doxygen will generate Docbook files
+# that can be used to generate PDF.
+# The default value is: NO.
+
+GENERATE_DOCBOOK = NO
+
+# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages 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: docbook.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_OUTPUT = docbook
+
+#---------------------------------------------------------------------------
+# Configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES doxygen will generate an AutoGen
+# Definitions (see http://autogen.sf.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.
+
+GENERATE_AUTOGEN_DEF = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES doxygen will generate a Perl module
+# 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.
+
+GENERATE_PERLMOD = NO
+
+# If the PERLMOD_LATEX tag is set to YES doxygen will generate the necessary
+# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
+# output from the Perl module output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_LATEX = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be nicely
+# formatted so it can be parsed by a human reader. This is useful if you want to
+# understand what is going on. On the other hand, if this tag is set to NO the
+# size of the Perl module output will be much smaller and Perl will parse it
+# just the same.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_PRETTY = YES
+
+# The names of the make variables in the generated doxyrules.make file are
+# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
+# so different doxyrules.make files included by the same Makefile don't
+# overwrite each other's variables.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES doxygen will evaluate all
+# C-preprocessor directives found in the sources and include files.
+# The default value is: YES.
+
+ENABLE_PREPROCESSING = YES
+
+# If the MACRO_EXPANSION tag is set to YES doxygen will expand all macro names
+# in the source code. If set to NO only conditional compilation will be
+# performed. Macro expansion can be done in a controlled way by setting
+# EXPAND_ONLY_PREDEF to YES.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+MACRO_EXPANSION = NO
+
+# 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
+# EXPAND_AS_DEFINED tags.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_ONLY_PREDEF = NO
+
+# If the SEARCH_INCLUDES tag is set to YES the includes files in the
+# INCLUDE_PATH will be searched if a #include is found.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SEARCH_INCLUDES = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by the
+# preprocessor.
+# This tag requires that the tag SEARCH_INCLUDES is set to YES.
+
+INCLUDE_PATH = ../
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will be
+# used.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+INCLUDE_FILE_PATTERNS = *.h
+
+# The PREDEFINED tag can be used to specify one or more macro names that are
+# defined before the preprocessor is started (similar to the -D option of e.g.
+# gcc). The argument of the tag is a list of macros of the form: name or
+# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
+# is assumed. To prevent a macro definition from being undefined via #undef or
+# recursively expanded use the := operator instead of the = operator.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+PREDEFINED = __DOXYGEN__
+
+# 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
+# macro definition that is found in the sources will be used. Use the PREDEFINED
+# tag if you want to use a different macro definition that overrules the
+# definition found in the source code.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_AS_DEFINED =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
+# remove all refrences to function-like macros that are alone on a line, have an
+# all uppercase name, and do not end with a semicolon. Such function macros are
+# typically used for boiler-plate code, and will confuse the parser if not
+# removed.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SKIP_FUNCTION_MACROS = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES tag can be used to specify one or more tag files. For each tag
+# file the location of the external documentation should be added. The format of
+# a tag file without this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where loc1 and loc2 can be relative or absolute paths or URLs. See the
+# section "Linking to external documentation" for more information about the use
+# of tag files.
+# Note: Each tag file must have an unique name (where the name does NOT include
+# the path). If a tag file is not located in the directory in which doxygen is
+# run, you must also specify the path to the tagfile here.
+
+TAGFILES =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
+# tag file that is based on the input files it reads. See section "Linking to
+# external documentation" for more information about the usage of tag files.
+
+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.
+# 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
+# listed.
+# The default value is: YES.
+
+EXTERNAL_GROUPS = YES
+
+# If the EXTERNAL_PAGES tag is set to YES all external pages will be listed in
+# the related pages index. If set to NO, only the current project's pages will
+# be listed.
+# The default value is: YES.
+
+EXTERNAL_PAGES = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of 'which perl').
+# The default file (with absolute path) is: /usr/bin/perl.
+
+PERL_PATH = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES doxygen will generate a class diagram
+# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
+# NO turns the diagrams off. Note that this option also works with HAVE_DOT
+# disabled, but it is recommended to install and use dot, since it yields more
+# powerful graphs.
+# The default value is: YES.
+
+CLASS_DIAGRAMS = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see:
+# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_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.
+
+HIDE_UNDOC_RELATIONS = NO
+
+# 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
+# Bell Labs. The other options in this section have no effect if this option is
+# set to NO
+# The default value is: NO.
+
+HAVE_DOT = YES
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
+# to run in parallel. When set to 0 doxygen will base this on the number of
+# processors available in the system. You can set it explicitly to a value
+# larger than 0 to get control over the balance between CPU load and processing
+# speed.
+# Minimum value: 0, maximum value: 32, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_NUM_THREADS = 0
+
+# When you want a differently looking font n 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.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTNAME = Helvetica
+
+# 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.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTSIZE = 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.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTPATH =
+
+# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
+# each documented class showing the direct and indirect inheritance relations.
+# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+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.
+# 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.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GROUP_GRAPHS = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LOOK = NO
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
+# class node. If there are many fields or methods and many nodes the graph may
+# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
+# number of items for each type to make the size more manageable. Set this to 0
+# for no limit. Note that the threshold may be exceeded by 50% before the limit
+# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
+# 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.
+
+UML_LIMIT_NUM_FIELDS = 10
+
+# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
+# collaboration graphs will show the relations between templates and their
+# instances.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+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.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDE_GRAPH = YES
+
+# 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.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDED_BY_GRAPH = YES
+
+# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALL_GRAPH = YES
+
+# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable caller graphs for selected
+# functions only using the \callergraph command.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALLER_GRAPH = YES
+
+# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
+# hierarchy of all classes instead of a textual one.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GRAPHICAL_HIERARCHY = YES
+
+# 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.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DIRECTORY_GRAPH = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot.
+# 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).
+# Possible values are: png, jpg, gif and svg.
+# The default value is: png.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_IMAGE_FORMAT = png
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+#
+# Note that this requires a modern browser other than Internet Explorer. Tested
+# and working are Firefox, Chrome, Safari, and Opera.
+# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
+# the SVG files visible. Older versions of IE do not have SVG support.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INTERACTIVE_SVG = NO
+
+# The DOT_PATH tag can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_PATH =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the \dotfile
+# command).
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+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).
+
+MSCFILE_DIRS =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
+# that will be shown in the graph. If the number of nodes in a graph becomes
+# larger than this value, doxygen will truncate the graph, which is visualized
+# by representing a node as a red box. Note that doxygen if the number of direct
+# children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
+# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+# Minimum value: 0, maximum value: 10000, default value: 50.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_GRAPH_MAX_NODES = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
+# generated by dot. A depth value of 3 means that only nodes reachable from the
+# root by following a path via at most 3 edges will be shown. Nodes that lay
+# further from the root node will be omitted. Note that setting this option to 1
+# or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+# Minimum value: 0, maximum value: 1000, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+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 = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES 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
+# this, this feature is disabled by default.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_MULTI_TARGETS = NO
+
+# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
+# explaining the meaning of the various boxes and arrows in the dot generated
+# graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GENERATE_LEGEND = YES
+
+# If the DOT_CLEANUP tag is set to YES doxygen will remove the intermediate dot
+# files that are used to generate the various graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_CLEANUP = YES
diff --git a/firmware/DoxygenLayout.xml b/firmware/DoxygenLayout.xml
new file mode 100644
index 0000000000..1287713df5
--- /dev/null
+++ b/firmware/DoxygenLayout.xml
@@ -0,0 +1,194 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/firmware/Makefile b/firmware/Makefile
new file mode 100644
index 0000000000..dd4f5e4c1b
--- /dev/null
+++ b/firmware/Makefile
@@ -0,0 +1,311 @@
+##############################################################################
+# Build global options
+# NOTE: Can be overridden externally.
+#
+
+PROJECT_DIR = .
+CHIBIOS = chibios
+
+# Compiler options here.
+ifeq ($(USE_OPT),)
+ USE_OPT = $(RFLAGS) -O1 -fgnu89-inline -ggdb -fomit-frame-pointer -falign-functions=16 -std=gnu99 -Werror-implicit-function-declaration -Werror -Wno-error=pointer-sign -Wno-error=unused-function -Wno-error=unused-variable -Wno-error=sign-compare -Wno-error=unused-parameter -Wno-error=missing-field-initializers
+endif
+
+# C specific options here (added to USE_OPT).
+ifeq ($(USE_COPT),)
+ USE_COPT =
+endif
+
+# C++ specific options here (added to USE_OPT).
+ifeq ($(USE_CPPOPT),)
+ USE_CPPOPT = -std=c++11 -fno-rtti -fno-exceptions -fno-use-cxa-atexit -Werror=write-strings
+endif
+
+# Enable this if you want the linker to remove unused code and data
+ifeq ($(USE_LINK_GC),)
+ USE_LINK_GC = yes
+endif
+
+# If enabled, this option allows to compile the application in THUMB mode.
+ifeq ($(USE_THUMB),)
+ USE_THUMB = yes
+endif
+
+# Enable this if you want to see the full log while compiling.
+ifeq ($(USE_VERBOSE_COMPILE),)
+ USE_VERBOSE_COMPILE = no
+endif
+
+#
+# Build global options
+##############################################################################
+
+##############################################################################
+# Architecture or project specific options
+#
+
+USE_FPU = yes
+
+# Enables the use of FPU on Cortex-M4.
+# Enable this if you really want to use the STM FWLib.
+ifeq ($(USE_FPU),)
+ USE_FPU = no
+endif
+
+# Enable this if you really want to use the STM FWLib.
+ifeq ($(USE_FWLIB),)
+ USE_FWLIB = no
+endif
+
+# List all default C defines here, like -D_DEBUG=1
+DDEFS =
+
+#
+# Architecture or project specific options
+##############################################################################
+
+##############################################################################
+# Project, sources and paths
+#
+
+# Define project name here
+PROJECT = rusefi
+
+#PROJECT_BOARD = OLIMEX_STM32_E407
+ifneq ($(PROJECT_BOARD),OLIMEX_STM32_E407)
+ PROJECT_BOARD = ST_STM32F4_DISCOVERY
+endif
+DDEFS += -D$(PROJECT_BOARD)
+
+# Imported source files and paths
+include $(CHIBIOS)/boards/$(PROJECT_BOARD)/board.mk
+include $(CHIBIOS)/os/hal/platforms/STM32F4xx/platform.mk
+include $(CHIBIOS)/os/hal/hal.mk
+include $(CHIBIOS)/os/ports/GCC/ARMCMx/STM32F4xx/port.mk
+include $(CHIBIOS)/os/kernel/kernel.mk
+include $(CHIBIOS)/os/various/cpp_wrappers/kernel.mk
+
+include $(CHIBIOS)/os/various/fatfs_bindings/fatfs.mk
+include console/tunerstudio/tunerstudio.mk
+include ext/ext.mk
+include hw_layer/hw_layer.mk
+include emulation/emulation.mk
+include controllers/controllers.mk
+
+include $(PROJECT_DIR)/util/util.mk
+include $(PROJECT_DIR)/config/engines/engines.mk
+include $(PROJECT_DIR)/controllers/algo/algo.mk
+include $(PROJECT_DIR)/controllers/core/core.mk
+include $(PROJECT_DIR)/controllers/math/math.mk
+include $(PROJECT_DIR)/controllers/sensors/sensors.mk
+include $(PROJECT_DIR)/controllers/system/system.mk
+include $(PROJECT_DIR)/controllers/trigger/trigger.mk
+include $(PROJECT_DIR)/console/console.mk
+include $(PROJECT_DIR)/console_util/console_util.mk
+
+
+# Define linker script file here
+LDSCRIPT= config/system/STM32F407xG_CCM.ld
+#LDSCRIPT= $(PORTLD)/STM32F407xG_CCM.ld
+
+# C sources that can be compiled in ARM or THUMB mode depending on the global
+# setting.
+CSRC = $(PORTSRC) \
+ $(KERNSRC) \
+ $(VARIOUSSRC) \
+ chibios/os/various/chprintf.c \
+ chibios/os/various/memstreams.c \
+ chibios/os/various/chrtclib.c \
+ $(UTILSRC) \
+ $(ENGINES_SRC) \
+ $(CONSOLESRC) \
+ $(CONSOLEUTILSRC) \
+ $(HALSRC) \
+ $(EMULATIONSRC) \
+ $(HW_LAYERSRC) \
+ $(CONTROLLERSSRC) \
+ $(CONTROLLERS_ALGO_SRC) \
+ $(CONTROLLERS_CORE_SRC) \
+ $(CONTROLLERS_MATH_SRC) \
+ $(CONTROLLERS_SENSORS_SRC) \
+ $(PLATFORMSRC) \
+ $(BOARDSRC) \
+ $(FATFSSRC) \
+ $(TRIGGER_SRC) \
+ $(SYSTEMSRC)
+
+# C++ sources that can be compiled in ARM or THUMB mode depending on the global
+# setting.
+CPPSRC = $(CHCPPSRC) \
+ $(TRIGGER_SRC_CPP) \
+ $(TRIGGER_DECODERS_SRC_CPP) \
+ $(EMULATIONSRC_CPP) \
+ $(CONTROLLERS_ALGO_SRC_CPP) \
+ $(SYSTEMSRC_CPP) \
+ $(ENGINES_SRC_CPP) \
+ $(HW_LAYER_SRC_CPP) \
+ $(TUNERSTUDIO_SRC_CPP) \
+ $(CONSOLE_SRC_CPP) \
+ $(CONTROLLERS_SENSORS_SRC_CPP) \
+ $(CONTROLLERS_SRC_CPP) \
+ $(UTILSRC_CPP) \
+ $(CONTROLLERS_CORE_SRC_CPP) \
+ $(CONTROLLERS_MATH_SRC_CPP) \
+ rusefi.cpp \
+ main.cpp
+
+# C sources to be compiled in ARM mode regardless of the global setting.
+# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
+# option that results in lower performance and larger code size.
+ACSRC =
+
+# C++ sources to be compiled in ARM mode regardless of the global setting.
+# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
+# option that results in lower performance and larger code size.
+ACPPSRC =
+
+# C sources to be compiled in THUMB mode regardless of the global setting.
+# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
+# option that results in lower performance and larger code size.
+TCSRC =
+
+# C sources to be compiled in THUMB mode regardless of the global setting.
+# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
+# option that results in lower performance and larger code size.
+TCPPSRC =
+
+# List ASM source files here
+ASMSRC = $(PORTASM)
+
+INCDIR = $(PORTINC) $(KERNINC) $(TESTINC) \
+ $(HALINC) $(PLATFORMINC) $(BOARDINC) \
+ $(CHCPPINC) \
+ $(CHIBIOS)/os/various \
+ config/system \
+ config/engines \
+ config/boards \
+ config \
+ chibios/os/various \
+ ext \
+ ext_algo \
+ util \
+ console_util \
+ console \
+ console/tunerstudio \
+ hw_layer \
+ hw_layer/serial_over_usb \
+ hw_layer/algo \
+ hw_layer/lcd \
+ hw_layer/stm32f4 \
+ emulation \
+ emulation/hw_layer \
+ emulation/test \
+ controllers \
+ controllers/sensors \
+ controllers/system \
+ controllers/algo \
+ controllers/core \
+ controllers/math \
+ controllers/trigger
+
+
+#
+# Project, sources and paths
+##############################################################################
+
+##############################################################################
+# Compiler settings
+#
+
+MCU = cortex-m4
+
+#TRGT = arm-elf-
+TRGT = arm-none-eabi-
+CC = $(TRGT)gcc
+CPPC = $(TRGT)g++
+# Enable loading with g++ only if you need C++ runtime support.
+# NOTE: You can use C++ even without C++ support if you are careful. C++
+# runtime support makes code size explode.
+#LD = $(TRGT)gcc
+LD = $(TRGT)g++
+CP = $(TRGT)objcopy
+AS = $(TRGT)gcc -x assembler-with-cpp
+OD = $(TRGT)objdump
+HEX = $(CP) -O ihex
+BIN = $(CP) -O binary
+
+# ARM-specific options here
+AOPT =
+
+# THUMB-specific options here
+TOPT = -mthumb -DTHUMB
+
+# Define C warning options here
+CWARN = -Wall -Wextra -Wstrict-prototypes
+
+# Define C++ warning options here
+CPPWARN = -Wall -Wextra
+
+#
+# Compiler settings
+##############################################################################
+
+##############################################################################
+# Start of default section
+#
+
+# List all default ASM defines here, like -D_DEBUG=1
+DADEFS =
+
+# List all default directories to look for include files here
+DINCDIR =
+
+# List the default directory to look for the libraries here
+DLIBDIR =
+
+# List all default libraries here
+DLIBS =
+
+#
+# End of default section
+##############################################################################
+
+##############################################################################
+# Start of user section
+#
+
+# List all user C define here, like -D_DEBUG=1
+UDEFS =
+
+# Define ASM defines here
+UADEFS =
+
+# List all user directories here
+UINCDIR =
+
+# List the user directory to look for the libraries here
+ULIBDIR =
+
+# List all user libraries here
+ULIBS = -lm
+
+#
+# End of user defines
+##############################################################################
+
+ifeq ($(USE_FPU),yes)
+ USE_OPT += -mfloat-abi=hard -mfpu=fpv4-sp-d16 -fsingle-precision-constant
+ USE_CPPOPT += -mfloat-abi=hard -mfpu=fpv4-sp-d16 -fsingle-precision-constant
+ DDEFS += -DCORTEX_USE_FPU=TRUE
+else
+ DDEFS += -DCORTEX_USE_FPU=FALSE
+endif
+
+ifeq ($(USE_FWLIB),yes)
+ include $(CHIBIOS)/ext/stm32lib/stm32lib.mk
+ CSRC += $(STM32SRC)
+ INCDIR += $(STM32INC)
+ USE_OPT += -DUSE_STDPERIPH_DRIVER
+endif
+
+include $(CHIBIOS)/os/ports/GCC/ARMCMx/rules.mk
diff --git a/firmware/chibios/file.lst b/firmware/chibios/file.lst
new file mode 100644
index 0000000000..f3d84bd725
--- /dev/null
+++ b/firmware/chibios/file.lst
@@ -0,0 +1,172 @@
+os/hal/hal.mk
+os/hal/include/adc.h
+os/hal/include/can.h
+os/hal/include/ext.h
+os/hal/include/gpt.h
+os/hal/include/hal.h
+os/hal/include/i2c.h
+os/hal/include/icu.h
+os/hal/include/io_block.h
+os/hal/include/io_channel.h
+os/hal/include/mac.h
+os/hal/include/mii.h
+os/hal/include/mmcsd.h
+os/hal/include/mmc_spi.h
+os/hal/include/pal.h
+os/hal/include/pwm.h
+os/hal/include/rtc.h
+os/hal/include/sdc.h
+os/hal/include/serial.h
+os/hal/include/serial_usb.h
+os/hal/include/spi.h
+os/hal/include/tm.h
+os/hal/include/uart.h
+os/hal/include/usb.h
+os/hal/platforms/STM32/can_lld.c
+os/hal/platforms/STM32/can_lld.h
+os/hal/platforms/STM32/ext_lld.c
+os/hal/platforms/STM32/ext_lld.h
+os/hal/platforms/STM32/GPIOv2/pal_lld.c
+os/hal/platforms/STM32/GPIOv2/pal_lld.h
+os/hal/platforms/STM32/i2s_lld.c
+os/hal/platforms/STM32/i2s_lld.h
+os/hal/platforms/STM32/mac_lld.c
+os/hal/platforms/STM32/mac_lld.h
+os/hal/platforms/STM32/OTGv1/stm32_otg.h
+os/hal/platforms/STM32/OTGv1/usb_lld.c
+os/hal/platforms/STM32/OTGv1/usb_lld.h
+os/hal/platforms/STM32/RTCv2/rtc_lld.c
+os/hal/platforms/STM32/RTCv2/rtc_lld.h
+os/hal/platforms/STM32/sdc_lld.c
+os/hal/platforms/STM32/sdc_lld.h
+os/hal/platforms/STM32/SPIv1/spi_lld.c
+os/hal/platforms/STM32/SPIv1/spi_lld.h
+os/hal/platforms/STM32/stm32.h
+os/hal/platforms/STM32/TIMv1/gpt_lld.c
+os/hal/platforms/STM32/TIMv1/gpt_lld.h
+os/hal/platforms/STM32/TIMv1/icu_lld.c
+os/hal/platforms/STM32/TIMv1/icu_lld.h
+os/hal/platforms/STM32/TIMv1/pwm_lld.c
+os/hal/platforms/STM32/TIMv1/pwm_lld.h
+os/hal/platforms/STM32/TIMv1/stm32_tim.h
+os/hal/platforms/STM32/USARTv1/serial_lld.c
+os/hal/platforms/STM32/USARTv1/serial_lld.h
+os/hal/platforms/STM32/USARTv1/uart_lld.c
+os/hal/platforms/STM32/USARTv1/uart_lld.h
+os/hal/platforms/STM32F4xx/adc_lld.c
+os/hal/platforms/STM32F4xx/adc_lld.h
+os/hal/platforms/STM32F4xx/ext_lld_isr.c
+os/hal/platforms/STM32F4xx/ext_lld_isr.h
+os/hal/platforms/STM32F4xx/hal_lld.c
+os/hal/platforms/STM32F4xx/hal_lld.h
+os/hal/platforms/STM32F4xx/platform.dox
+os/hal/platforms/STM32F4xx/platform.mk
+os/hal/platforms/STM32F4xx/stm32f2xx.h
+os/hal/platforms/STM32F4xx/stm32f4xx.h
+os/hal/platforms/STM32F4xx/stm32_dma.c
+os/hal/platforms/STM32F4xx/stm32_dma.h
+os/hal/platforms/STM32F4xx/stm32_isr.h
+os/hal/platforms/STM32F4xx/stm32_rcc.h
+os/hal/src/adc.c
+os/hal/src/can.c
+os/hal/src/ext.c
+os/hal/src/gpt.c
+os/hal/src/hal.c
+os/hal/src/i2c.c
+os/hal/src/icu.c
+os/hal/src/mac.c
+os/hal/src/mmcsd.c
+os/hal/src/mmc_spi.c
+os/hal/src/pal.c
+os/hal/src/pwm.c
+os/hal/src/rtc.c
+os/hal/src/sdc.c
+os/hal/src/serial.c
+os/hal/src/serial_usb.c
+os/hal/src/spi.c
+os/hal/src/tm.c
+os/hal/src/uart.c
+os/hal/src/usb.c
+os/kernel/include/ch.h
+os/kernel/include/chbsem.h
+os/kernel/include/chcond.h
+os/kernel/include/chdebug.h
+os/kernel/include/chdynamic.h
+os/kernel/include/chevents.h
+os/kernel/include/chfiles.h
+os/kernel/include/chheap.h
+os/kernel/include/chinline.h
+os/kernel/include/chlists.h
+os/kernel/include/chmboxes.h
+os/kernel/include/chmemcore.h
+os/kernel/include/chmempools.h
+os/kernel/include/chmsg.h
+os/kernel/include/chmtx.h
+os/kernel/include/chqueues.h
+os/kernel/include/chregistry.h
+os/kernel/include/chschd.h
+os/kernel/include/chsem.h
+os/kernel/include/chstreams.h
+os/kernel/include/chsys.h
+os/kernel/include/chthreads.h
+os/kernel/include/chvt.h
+os/kernel/kernel.mk
+os/kernel/src/chcond.c
+os/kernel/src/chdebug.c
+os/kernel/src/chdynamic.c
+os/kernel/src/chevents.c
+os/kernel/src/chheap.c
+os/kernel/src/chlists.c
+os/kernel/src/chmboxes.c
+os/kernel/src/chmemcore.c
+os/kernel/src/chmempools.c
+os/kernel/src/chmsg.c
+os/kernel/src/chmtx.c
+os/kernel/src/chqueues.c
+os/kernel/src/chregistry.c
+os/kernel/src/chschd.c
+os/kernel/src/chsem.c
+os/kernel/src/chsys.c
+os/kernel/src/chthreads.c
+os/kernel/src/chvt.c
+os/ports/common/ARMCMx/CMSIS/include/arm_common_tables.h
+os/ports/common/ARMCMx/CMSIS/include/arm_math.h
+os/ports/common/ARMCMx/CMSIS/include/core_cm0.h
+os/ports/common/ARMCMx/CMSIS/include/core_cm0plus.h
+os/ports/common/ARMCMx/CMSIS/include/core_cm3.h
+os/ports/common/ARMCMx/CMSIS/include/core_cm4.h
+os/ports/common/ARMCMx/CMSIS/include/core_cm4_simd.h
+os/ports/common/ARMCMx/CMSIS/include/core_cmFunc.h
+os/ports/common/ARMCMx/CMSIS/include/core_cmInstr.h
+os/ports/common/ARMCMx/CMSIS/readme.txt
+os/ports/common/ARMCMx/nvic.c
+os/ports/common/ARMCMx/nvic.h
+os/ports/GCC/ARMCMx/chcore.c
+os/ports/GCC/ARMCMx/chcore.h
+os/ports/GCC/ARMCMx/chcore_v7m.c
+os/ports/GCC/ARMCMx/chcore_v7m.h
+os/ports/GCC/ARMCMx/chtypes.h
+os/ports/GCC/ARMCMx/crt0.c
+os/ports/GCC/ARMCMx/rules.mk
+os/ports/GCC/ARMCMx/STM32F4xx/cmparams.h
+os/ports/GCC/ARMCMx/STM32F4xx/port.mk
+os/ports/GCC/ARMCMx/STM32F4xx/vectors.c
+os/ports/IAR/ARMCMx/chcore.c
+os/ports/IAR/ARMCMx/chcore.h
+os/ports/IAR/ARMCMx/chcoreasm_v6m.s
+os/ports/IAR/ARMCMx/chcoreasm_v7m.s
+os/ports/IAR/ARMCMx/chcore_v6m.c
+os/ports/IAR/ARMCMx/chcore_v6m.h
+os/ports/IAR/ARMCMx/chcore_v7m.c
+os/ports/IAR/ARMCMx/chcore_v7m.h
+os/ports/IAR/ARMCMx/chtypes.h
+os/ports/IAR/ARMCMx/cstartup.s
+os/ports/IAR/ARMCMx/STM32F4xx/cmparams.h
+os/ports/IAR/ARMCMx/STM32F4xx/vectors.s
+os/various/chprintf.c
+os/various/chprintf.h
+os/various/fatfs_diskio.c
+os/various/fatfs_syscall.c
+os/various/memstreams.c
+os/various/memstreams.h
+os/various/various.mk
diff --git a/firmware/chibios/rusefi_chibios.patch b/firmware/chibios/rusefi_chibios.patch
new file mode 100644
index 0000000000..2237a8ca7a
--- /dev/null
+++ b/firmware/chibios/rusefi_chibios.patch
@@ -0,0 +1,111 @@
+Index: boards/ST_STM32F4_DISCOVERY/board.h
+===================================================================
+--- boards/ST_STM32F4_DISCOVERY/board.h (revision 2723)
++++ boards/ST_STM32F4_DISCOVERY/board.h (working copy)
+@@ -27,6 +27,7 @@
+ #define BOARD_ST_STM32F4_DISCOVERY
+ #define BOARD_NAME "STMicroelectronics STM32F4-Discovery"
+
++#define STM32_LSECLK 32768
+
+ /*
+ * Board oscillators-related settings.
+Index: os/kernel/src/chdebug.c
+===================================================================
+--- os/kernel/src/chdebug.c (revision 2723)
++++ os/kernel/src/chdebug.c (working copy)
+@@ -114,7 +114,7 @@
+ void dbg_check_lock(void) {
+
+ if ((dbg_isr_cnt != 0) || (dbg_lock_cnt != 0))
+- chDbgPanic("SV#4");
++ chDbgPanic("SV#4 misplaced chSysLock()");
+ dbg_enter_lock();
+ }
+
+@@ -138,7 +138,7 @@
+ void dbg_check_lock_from_isr(void) {
+
+ if ((dbg_isr_cnt <= 0) || (dbg_lock_cnt != 0))
+- chDbgPanic("SV#6");
++ chDbgPanic("SV#6 misplaced chSysLockFromIsr");
+ dbg_enter_lock();
+ }
+
+@@ -193,7 +193,7 @@
+ void chDbgCheckClassI(void) {
+
+ if ((dbg_isr_cnt < 0) || (dbg_lock_cnt <= 0))
+- chDbgPanic("SV#10");
++ chDbgPanic("SV#10 misplaced I-class function");
+ }
+
+ /**
+@@ -268,10 +268,11 @@
+ *
+ * @param[in] msg the pointer to the panic message string
+ */
++
++void chDbgPanic3(const char *msg, char * file, int line);
++
+ void chDbgPanic(const char *msg) {
+-
+- dbg_panic_msg = msg;
+- chSysHalt();
++ chDbgPanic3(msg, __FILE__, __LINE__);
+ }
+ #endif /* CH_DBG_ENABLED */
+
+Index: os/ports/GCC/ARMCMx/chcore_v7m.h
+===================================================================
+--- os/ports/GCC/ARMCMx/chcore_v7m.h (revision 2723)
++++ os/ports/GCC/ARMCMx/chcore_v7m.h (working copy)
+@@ -36,6 +36,8 @@
+ #ifndef _CHCORE_V7M_H_
+ #define _CHCORE_V7M_H_
+
++#include "chdebug.h"
++
+ /*===========================================================================*/
+ /* Port constants. */
+ /*===========================================================================*/
+@@ -486,6 +488,8 @@
+ #define port_wait_for_interrupt()
+ #endif
+
++void chDbgStackOverflowPanic(Thread *otp);
++
+ /**
+ * @brief Performs a context switch between two threads.
+ * @details This is the most critical code in any port, this function
+@@ -502,7 +506,7 @@
+ #define port_switch(ntp, otp) { \
+ register struct intctx *r13 asm ("r13"); \
+ if ((stkalign_t *)(r13 - 1) < otp->p_stklimit) \
+- chDbgPanic("stack overflow"); \
++ chDbgStackOverflowPanic(otp); \
+ _port_switch(ntp, otp); \
+ }
+ #endif
+Index: os/ports/GCC/ARMCMx/rules.mk
+===================================================================
+--- os/ports/GCC/ARMCMx/rules.mk (revision 2723)
++++ os/ports/GCC/ARMCMx/rules.mk (working copy)
+@@ -60,7 +60,7 @@
+ ASFLAGS = $(MCFLAGS) -Wa,-amhls=$(LSTDIR)/$(notdir $(<:.s=.lst)) $(ADEFS)
+ ASXFLAGS = $(MCFLAGS) -Wa,-amhls=$(LSTDIR)/$(notdir $(<:.S=.lst)) $(ADEFS)
+ CFLAGS = $(MCFLAGS) $(OPT) $(COPT) $(CWARN) -Wa,-alms=$(LSTDIR)/$(notdir $(<:.c=.lst)) $(DEFS)
+-CPPFLAGS = $(MCFLAGS) $(OPT) $(CPPOPT) $(CPPWARN) -Wa,-alms=$(LSTDIR)/$(notdir $(<:.cpp=.lst)) $(DEFS)
++CPPFLAGS = $(MCFLAGS) $(CPPOPT) $(CPPWARN) -Wa,-alms=$(LSTDIR)/$(notdir $(<:.cpp=.lst)) $(DEFS)
+ ifeq ($(USE_LINK_GC),yes)
+ LDFLAGS = $(MCFLAGS) -nostartfiles -T$(LDSCRIPT) -Wl,-Map=$(BUILDDIR)/$(PROJECT).map,--cref,--no-warn-mismatch,--gc-sections $(LLIBDIR)
+ else
+@@ -113,7 +113,7 @@
+ $(BUILDDIR) $(OBJDIR) $(LSTDIR):
+ ifneq ($(USE_VERBOSE_COMPILE),yes)
+ @echo Compiler Options
+- @echo $(CC) -c $(CFLAGS) -I. $(IINCDIR) main.c -o main.o
++ @echo $(CPPC) -c $(CPPFLAGS) -I. $(IINCDIR) main.cpp -o main.o -llibstd++
+ @echo
+ endif
+ mkdir -p $(OBJDIR)
diff --git a/firmware/clean.bat b/firmware/clean.bat
new file mode 100644
index 0000000000..241e3ea846
--- /dev/null
+++ b/firmware/clean.bat
@@ -0,0 +1,2 @@
+rd /s /q .dep
+rd /s /q build
diff --git a/firmware/clean_build.bat b/firmware/clean_build.bat
new file mode 100644
index 0000000000..6da1c07e8f
--- /dev/null
+++ b/firmware/clean_build.bat
@@ -0,0 +1,7 @@
+rem make, gcc, Windows and Cygwin combined have some issue with spaces or colons in paths, that's a workaround
+rem that's more or less 'make clean'
+rd /s /q .dep
+rd /s /q build
+
+# that's 'make' with some extra utilities
+compile.bat
\ No newline at end of file
diff --git a/firmware/compile.bat b/firmware/compile.bat
new file mode 100644
index 0000000000..e9a5b87f7c
--- /dev/null
+++ b/firmware/compile.bat
@@ -0,0 +1,44 @@
+@echo off
+rm -rf .dep/
+
+rm -rf build\rusefi.hex
+
+call update_version.bat
+
+echo Starting compilation
+rem the important piece
+make
+
+rem cd build
+rem if not exist rusefi.hex echo "compilation failed"
+rem if not exist rusefi.hex exit -1
+rem cd ..
+if errorlevel 1 goto error
+
+echo Build complete success.
+
+
+rem svn info > ../firmware_binary/version.txt
+rem cp config/features.h ../firmware_binary
+rem cp build/rusefi.hex ../firmware_binary
+rem cp build/rusefi.elf ../firmware_binary
+
+rem cp tunerstudio/rusefi.ini ../firmware_binary
+
+rem cd ../firmware_binary
+rem del firmaware_binary.zip
+rem 7z a firmaware_binary.zip rusefi.hex rusefi.ini features.h flash.bat
+rem cd ../firmware
+
+
+cd build
+rem Generate human-readable version of the .map memory usage report
+java -jar ../../java_tools/gcc_map_reader.jar > ../../firmware_binary/rusefi_ram_report.txt
+cd ..
+
+rem file, let's program the board right away
+flash.bat
+exit
+
+:error
+echo Compilation failed
diff --git a/firmware/config/boards/arro_board.h b/firmware/config/boards/arro_board.h
new file mode 100644
index 0000000000..b2eeaa39b5
--- /dev/null
+++ b/firmware/config/boards/arro_board.h
@@ -0,0 +1,234 @@
+/**
+ * @file arro_board.h
+ *
+ * This file contents a configuration of default ecu board. Pinout and other.
+ * TODO: most of the pins should get configurable
+ *
+ *
+ * @date Nov 14, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ * @author frig
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ */
+#ifndef ARRO_BOARD_H_
+#define ARRO_BOARD_H_
+
+
+#define STM32_ICU_USE_TIM1 TRUE // wave input
+#define STM32_ICU_USE_TIM2 TRUE // primary position sensor
+#define STM32_ICU_USE_TIM3 TRUE // secondary position sensor
+#define STM32_ICU_USE_TIM4 FALSE
+#define STM32_ICU_USE_TIM5 FALSE
+#define STM32_ICU_USE_TIM8 FALSE
+#define STM32_ICU_USE_TIM9 TRUE // wave input
+
+// todo: switch to continues ADC conversion for slow ADC?
+#define EFI_INTERNAL_SLOW_ADC_PWM &PWMD8
+// todo: switch to continues ADC conversion for fast ADC?
+#define EFI_INTERNAL_FAST_ADC_PWM &PWMD4
+
+
+#define STM32_PWM_USE_TIM1 FALSE
+#define STM32_PWM_USE_TIM2 FALSE
+#define STM32_PWM_USE_TIM3 FALSE
+//
+#define STM32_PWM_USE_TIM4 TRUE // fast adc
+#define STM32_PWM_USE_TIM5 FALSE
+#define STM32_PWM_USE_TIM8 TRUE // slow adc
+#define STM32_PWM_USE_TIM9 FALSE
+
+#define STM32_SPI_USE_SPI1 TRUE
+#define STM32_SPI_USE_SPI2 FALSE // external ADC
+#define STM32_SPI_USE_SPI3 TRUE // potentiometer
+
+#define STM32_CAN_USE_CAN1 TRUE
+#define STM32_CAN_USE_CAN2 TRUE
+
+#define STM32_I2C_I2C1_RX_DMA_STREAM STM32_DMA_STREAM_ID(1, 5)
+#define STM32_I2C_I2C1_TX_DMA_STREAM STM32_DMA_STREAM_ID(1, 6)
+
+#define EFI_CAN_DEVICE CAND2
+#define EFI_CAN_RX_AF 9
+#define EFI_CAN_TX_AF 9
+
+//#define EFI_CAN_DEVICE CAND1
+//#define EFI_CAN_RX_AF 9
+//#define EFI_CAN_TX_AF 9
+
+/**
+ * This section is for bottom-left corner SPI
+ */
+//#define SPI_CS1_PORT GPIOE
+//#define SPI_CS1_PIN 13
+//#define SPI_CS2_PORT GPIOE
+//#define SPI_CS2_PIN 14
+//#define SPI_CS3_PORT GPIOE
+//#define SPI_CS3_PIN 15
+//#define SPI_CS4_PORT GPIOD
+//#define SPI_CS4_PIN 10
+//#define SPI_SD_MODULE_PORT GPIOD
+//#define SPI_SD_MODULE_PIN 11
+
+#define EFI_SPI1_SCK_PORT GPIOB
+#define EFI_SPI1_SCK_PIN 3
+#define EFI_SPI1_MISO_PORT GPIOB
+#define EFI_SPI1_MISO_PIN 4
+#define EFI_SPI1_MOSI_PORT GPIOB
+#define EFI_SPI1_MOSI_PIN 5
+#define EFI_SPI1_AF 5
+
+
+#define EFI_SPI2_SCK_PORT GPIOB
+#define EFI_SPI2_SCK_PIN 13
+#define EFI_SPI2_MISO_PORT GPIOB
+#define EFI_SPI2_MISO_PIN 14
+#define EFI_SPI2_MOSI_PORT GPIOB
+#define EFI_SPI2_MOSI_PIN 15
+#define EFI_SPI2_AF 5
+
+
+/**
+ * This section is for right-side center SPI
+ */
+// this is pointing into the sky for now - conflict with I2C
+#define SPI_CS2_PORT GPIOH
+// this is pointing into the sky for now - conflict with I2C
+#define SPI_CS2_PIN 0
+#define SPI_CS4_PORT GPIOD
+#define SPI_CS4_PIN 3
+#define SPI_SD_MODULE_PORT GPIOD
+#define SPI_SD_MODULE_PIN 4
+#define EFI_SPI3_SCK_PORT GPIOB
+#define EFI_SPI3_SCK_PIN 3
+#define EFI_SPI3_MISO_PORT GPIOB
+#define EFI_SPI3_MISO_PIN 4
+#define EFI_SPI3_MOSI_PORT GPIOB
+#define EFI_SPI3_MOSI_PIN 5
+#define EFI_SPI3_AF 6
+#define MMC_CARD_SPI SPID3
+
+#define EFI_I2C_SCL_PORT GPIOB
+#define EFI_I2C_SCL_PIN 6
+#define EFI_I2C_SDA_PORT GPIOB
+#define EFI_I2C_SDA_PIN 7
+#define EFI_I2C_AF 4
+
+/**
+ * Patched version of ChibiOS/RT support extra details in the system error messages
+ */
+#define EFI_CUSTOM_PANIC_METHOD TRUE
+
+/*
+ * 10 channel board is (from left to right):
+ * ADC 15 PC5 TPS
+ * ADC 14 PC4 MAP
+ * ADC 7 PA7 IAT
+ * ADC 6 PA6 CLT
+ * ADC 5 PA5 TIM2_CH1
+ * ADC 4 PA4
+ * ADC 3 PA3
+ * ADC 2 PA2
+ * ADC 1 PA1 vBatt
+ * ADC 0 PA0 MAF
+ */
+
+#define ADC_LOGIC_TPS_2 ADC_CHANNEL_IN0
+
+#define ADC_CHANNEL_VREF ADC_CHANNEL_IN14
+
+
+/**
+ * currently ChibiOS uses only first and second channels of each timer for input capture
+ *
+ * So, our options are:
+ *
+ * TIM2_CH1
+ * PA5
+ *
+ * TIM4_CH1
+ * PB6
+ * PD12
+ *
+ * TIM9_CH1
+ * PE5
+ */
+
+//#define ETB_CONTROL_LINE_1_PORT GPIOE
+//#define ETB_CONTROL_LINE_1_PIN 0
+//
+//#define ETB_CONTROL_LINE_2_PORT GPIOB
+//#define ETB_CONTROL_LINE_2_PIN 8
+
+//#define CONSOLE_PORT GPIOB
+//#define CONSOLE_TX_PIN 10
+//#define CONSOLE_RX_PIN 11
+
+/**
+ * Here we define the pinout for the human-readable protocol via UART, TunerStudio pinout is defined separately
+ */
+//#define EFI_CONSOLE_TX_PORT GPIOD
+//#define EFI_CONSOLE_TX_PIN 8
+//#define EFI_CONSOLE_RX_PORT GPIOD
+//#define EFI_CONSOLE_RX_PIN 9
+//#define EFI_CONSOLE_AF 7
+
+#define EFI_CONSOLE_UART_DEVICE (&SD3)
+
+#define EFI_CONSOLE_TX_PORT GPIOC
+#define EFI_CONSOLE_TX_PIN 10
+#define EFI_CONSOLE_RX_PORT GPIOC
+#define EFI_CONSOLE_RX_PIN 11
+#define EFI_CONSOLE_AF 7
+
+//#define TS_SERIAL_TX_PORT GPIOD
+//#define TS_SERIAL_TX_PIN 8
+//#define TS_SERIAL_RX_PORT GPIOD
+//#define TS_SERIAL_RX_PIN 9
+//#define TS_SERIAL_AF 7
+
+#define TS_SERIAL_TX_PORT GPIOC
+#define TS_SERIAL_TX_PIN 10
+#define TS_SERIAL_RX_PORT GPIOC
+#define TS_SERIAL_RX_PIN 11
+#define TS_SERIAL_AF 7
+
+#define LED_WARNING_PORT GPIOD
+#define LED_WARNING_PIN GPIOD_LED3
+
+#define LED_RUNNING_STATUS_PORT GPIOD
+#define LED_RUNNING_STATUS_PIN GPIOD_LED4
+
+#define LED_ERROR_PORT GPIOD
+#define LED_ERROR_PIN GPIOD_LED5
+
+#define LED_COMMUNICATION_PORT GPIOD
+#define LED_COMMUNICATION_PIN GPIOD_LED6
+
+#define EFI_SIGNAL_EXECUTOR_SLEEP FALSE
+#define EFI_SIGNAL_EXECUTOR_ONE_TIMER TRUE
+#define EFI_SIGNAL_EXECUTOR_HW_TIMER FALSE
+
+// USART1 -> check defined STM32_SERIAL_USE_USART1
+// For GPS we have USART1. We can start with PB7 USART1_RX and PB6 USART1_TX
+#define GPS_SERIAL_DEVICE &SD1
+#define GPS_SERIAL_SPEED 38400
+
+#define CONSOLE_MODE_SWITCH_PORT GPIOB
+#define CONSOLE_MODE_SWITCH_PIN 1
+
+#define CONFIG_RESET_SWITCH_PORT GPIOD
+#define CONFIG_RESET_SWITCH_PIN 6
+
+#endif /*ARRO_BOARD_H_*/
diff --git a/firmware/config/boards/boards.h b/firmware/config/boards/boards.h
new file mode 100644
index 0000000000..f11dd22757
--- /dev/null
+++ b/firmware/config/boards/boards.h
@@ -0,0 +1,31 @@
+/**
+ * @file board.h
+ * Board configuration. Only general confuration, and including custom configs
+ *
+ * @date Nov 14, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ * @author frig
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ */
+#ifndef BOARD_H_
+#define BOARD_H_
+
+#define EFI_BOARD_ARRO TRUE
+
+#if EFI_BOARD_ARRO
+#include "arro_board.h"
+#endif
+
+#endif /*BOARD_H_*/
diff --git a/firmware/config/efifeatures.h b/firmware/config/efifeatures.h
new file mode 100644
index 0000000000..45bf8b40ed
--- /dev/null
+++ b/firmware/config/efifeatures.h
@@ -0,0 +1,136 @@
+/**
+ * @file efifeatures.h
+ *
+ * @brief In this header we can configure which firmware modules are used.
+ *
+ * @date Aug 29, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef EFIFEATURES_H_
+#define EFIFEATURES_H_
+
+#define EFI_USE_CCM TRUE
+
+//#define EFI_UART_ECHO_TEST_MODE TRUE
+
+#define EFI_USE_UART_FOR_CONSOLE FALSE
+
+/**
+ * Build-in logic analyzer support. Logic analyzer viewer is one of the java console panes.
+ */
+#define EFI_WAVE_ANALYZER TRUE
+
+//#define SERIAL_SPEED (8 * 115200)
+//#define SERIAL_SPEED (2 * 115200)
+#define SERIAL_SPEED 115200
+
+/**
+ * TunerStudio support.
+ */
+#define EFI_TUNER_STUDIO TRUE
+
+/**
+ * TunerStudio debug output
+ */
+#define EFI_TUNER_STUDIO_VERBOSE TRUE
+
+#define EFI_DEFAILED_LOGGING FALSE
+
+/**
+ * Dev console support.
+ */
+#define EFI_CLI_SUPPORT TRUE
+
+#define EFI_INTERNAL_FLASH TRUE
+
+/**
+ * Usually you need shaft position input, but maybe you do not need it?
+ */
+#define EFI_SHAFT_POSITION_INPUT TRUE
+
+#define EFI_ANALOG_INPUTS TRUE
+
+/**
+ * Maybe we are just sniffing what's going on?
+ */
+#define EFI_ENGINE_CONTROL TRUE
+
+#define EFI_SPEED_DENSITY TRUE
+
+/**
+ * MCP42010 digital potentiometer support. This could be useful if you are stimulating some
+ * stock ECU
+ */
+//#define EFI_POTENTIOMETER FALSE
+#define EFI_POTENTIOMETER TRUE
+
+#define EFI_INTERNAL_ADC TRUE
+
+#define EFI_DENSO_ADC FALSE
+
+#define EFI_CAN_SUPPORT FALSE
+
+#define EFI_HD44780_LCD TRUE
+
+#define EFI_IDLE_CONTROL TRUE
+
+#define EFI_FUEL_PUMP TRUE
+
+#define EFI_ENGINE_EMULATOR TRUE
+#define EFI_EMULATE_POSITION_SENSORS TRUE
+
+/**
+ * This macros is used to hide pieces of the code from unit tests, so it only makes sense in folders exposed to the tests project.
+ * This macros is NOT about taking out logging in general.
+ */
+#define EFI_PROD_CODE TRUE
+
+/**
+ * Do we need Malfunction Indicator blinking logic?
+ */
+#define EFI_MALFUNCTIONAL_INDICATOR TRUE
+/**
+ * Do we need file logging (like SD card) logic?
+ */
+#define EFI_FILE_LOGGING TRUE
+
+/**
+ * While we embed multiple PnP configurations into the same firmware binary, these marcoses give us control
+ * over which configurations go into the binary
+ */
+#define EFI_SUPPORT_DODGE_NEON TRUE
+#define EFI_SUPPORT_FORD_ASPIRE TRUE
+#define EFI_SUPPORT_FORD_FIESTA TRUE
+#define EFI_SUPPORT_NISSAN_PRIMERA TRUE
+#define EFI_SUPPORT_1995_FORD_INLINE_6 TRUE
+
+#define EFI_WAVE_CHART TRUE
+
+#define EFI_HISTOGRAMS TRUE
+#define EFI_ANALOG_CHART TRUE
+
+#if defined __GNUC__
+#define EFI_PERF_METRICS TRUE
+#define DL_OUTPUT_BUFFER 9000
+#else
+#define EFI_PERF_METRICS FALSE
+#define DL_OUTPUT_BUFFER 9000
+#endif
+
+/**
+ * Do we need GPS logic?
+ */
+#define EFI_UART_GPS TRUE
+//#define EFI_UART_GPS FALSE
+
+//#define EFI_ELECTRONIC_THROTTLE_BODY TRUE
+#define EFI_ELECTRONIC_THROTTLE_BODY FALSE
+
+#define EFI_MALFUNCTION_INDICATOR TRUE
+//#define EFI_MALFUNCTION_INDICATOR FALSE
+
+#define EFI_MAP_AVERAGING TRUE
+//#define EFI_MAP_AVERAGING FALSE
+
+#endif /* EFIFEATURES_H_ */
diff --git a/firmware/config/engines/GY6_139QMB.cpp b/firmware/config/engines/GY6_139QMB.cpp
new file mode 100644
index 0000000000..a7cafe0f6e
--- /dev/null
+++ b/firmware/config/engines/GY6_139QMB.cpp
@@ -0,0 +1,33 @@
+/**
+ * @file GY6_139QMB.cpp
+ * @brief 139qmb default engine configuration
+ *
+ * @date Feb 13, 2014
+ * @author rus084
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+#include "GY6_139QMB.h"
+
+void setGy6139qmbDefaultEngineConfiguration(engine_configuration_s *engineConfiguration) {
+ engineConfiguration->rpmHardLimit = 9000;
+ engineConfiguration->crankingSettings.crankingRpm = 800;
+ engineConfiguration->analogInputDividerCoefficient = 1.52;
+ engineConfiguration->algorithm = LM_MAP;
+ engineConfiguration->globalTriggerAngleOffset = 15;
+ engineConfiguration->analogChartMode = AC_MAP;
+ engineConfiguration->cylindersCount = 1;
+ setOperationMode(engineConfiguration, FOUR_STROKE_CRANK_SENSOR);
+
+ engineConfiguration->firingOrder = FO_ONE_CYLINDER;
+
+ /**
+ * We treat the trigger as 1/0 toothed wheel
+ */
+ engineConfiguration->triggerConfig.triggerType = TT_TOOTHED_WHEEL;
+ engineConfiguration->triggerConfig.customTotalToothCount = 1;
+ engineConfiguration->triggerConfig.customSkippedToothCount = 0;
+ engineConfiguration->triggerConfig.customIsSynchronizationNeeded = false;
+//todo engineConfiguration2->triggerShape.needSecondTriggerInput = false;
+}
diff --git a/firmware/config/engines/GY6_139QMB.h b/firmware/config/engines/GY6_139QMB.h
new file mode 100644
index 0000000000..fdca02c139
--- /dev/null
+++ b/firmware/config/engines/GY6_139QMB.h
@@ -0,0 +1,16 @@
+/*
+ * @file GY6_139QMB.h
+ *
+ * @date 13 feb 2014 y.
+ * @author rus084
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef GY6_139QMB_H_
+#define GY6_139QMB_H_
+
+#include "engine_configuration.h"
+
+void setGy6139qmbDefaultEngineConfiguration(engine_configuration_s *engineConfiguration);
+
+#endif /* GY6_139QMB_H_ */
diff --git a/firmware/config/engines/MiniCooperR50.cpp b/firmware/config/engines/MiniCooperR50.cpp
new file mode 100644
index 0000000000..23cfc1ca3d
--- /dev/null
+++ b/firmware/config/engines/MiniCooperR50.cpp
@@ -0,0 +1,15 @@
+/**
+ * @file MiniCooperR50.cpp
+ *
+ * MINI_COOPER_R50 = 13
+ *
+ * @date Apr 9, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "MiniCooperR50.h"
+
+void setMiniCooperR50(engine_configuration_s *engineConfiguration, board_configuration_s *boardConfiguration) {
+ engineConfiguration->triggerConfig.triggerType = TT_MINI_COOPER_R50;
+
+}
diff --git a/firmware/config/engines/MiniCooperR50.h b/firmware/config/engines/MiniCooperR50.h
new file mode 100644
index 0000000000..f5d96dde61
--- /dev/null
+++ b/firmware/config/engines/MiniCooperR50.h
@@ -0,0 +1,16 @@
+/*
+ * @file MiniCooperR50.h
+ *
+ * @date Apr 9, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef MINICOOPERR50_H_
+#define MINICOOPERR50_H_
+
+#include "main.h"
+#include "engine_configuration.h"
+
+void setMiniCooperR50(engine_configuration_s *engineConfiguration, board_configuration_s *boardConfiguration);
+
+#endif /* MINICOOPERR50_H_ */
diff --git a/firmware/config/engines/audi_aan.cpp b/firmware/config/engines/audi_aan.cpp
new file mode 100644
index 0000000000..720bd3818b
--- /dev/null
+++ b/firmware/config/engines/audi_aan.cpp
@@ -0,0 +1,47 @@
+/**
+ * @file audi_aan.cpp
+ * @brief Audo AAN default engine configuration
+ *
+ * @date Nov 24, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ *
+ */
+
+#include "main.h"
+
+#if EFI_ENGINE_AUDI_AAN || defined(__DOXYGEN__)
+
+#include "engine_controller.h"
+
+extern engine_configuration2_s engineConfiguration2;
+
+static void configureShaftPositionEmulatorShapeWhat(PwmConfig *state) {
+ /**
+ * One signal per cam shaft revolution
+ */
+ int pinStates0[] = { 1, 0 };
+ float switchTimes[] = { 0.8, 1 };
+
+ int *pinStates[2] = { pinStates0 };
+
+ weComplexInit("distributor", state, 0, 2, switchTimes, 1, pinStates);
+}
+
+void setDefaultEngineConfiguration(EngineConfiguration *engineConfiguration) {
+ engineConfiguration2.shaftPositionEventCount = 2;
+}
+
+#endif /* EFI_ENGINE_AUDI_AAN */
diff --git a/firmware/config/engines/audi_aan.h b/firmware/config/engines/audi_aan.h
new file mode 100644
index 0000000000..8a0cd36c1b
--- /dev/null
+++ b/firmware/config/engines/audi_aan.h
@@ -0,0 +1,16 @@
+/**
+ * @file audi_aan.h
+ * @brief Audo AAN default engine configuration
+ *
+ * @date Nov 24, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef AUDI_AAN_H_
+#define AUDI_AAN_H_
+
+#if EFI_ENGINE_AUDI_AAN
+
+#endif /* EFI_ENGINE_AUDI_AAN */
+
+#endif /* AUDI_AAN_H_ */
diff --git a/firmware/config/engines/citroenBerlingoTU3JP.cpp b/firmware/config/engines/citroenBerlingoTU3JP.cpp
new file mode 100644
index 0000000000..76548dd6a8
--- /dev/null
+++ b/firmware/config/engines/citroenBerlingoTU3JP.cpp
@@ -0,0 +1,31 @@
+/**
+ * @file citroenBerlingoTU3JP.cpp
+ *
+ * CITROEN_TU3JP: engine_type 15
+ *
+ * This config overrides some values of the default configuration which is set by setDefaultConfiguration() method
+ *
+ *
+ * @date Apr 15, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "citroenBerlingoTU3JP.h"
+
+void setCitroenBerlingoTU3JPConfiguration(engine_configuration_s *engineConfiguration, board_configuration_s *boardConfiguration) {
+ engineConfiguration->engineType = CITROEN_TU3JP;
+
+ engineConfiguration->triggerConfig.triggerType = TT_TOOTHED_WHEEL_60_2;
+
+ // set_cranking_injection_mode 0
+ engineConfiguration->crankingInjectionMode = IM_SIMULTANEOUS;
+ // set_injection_mode 2
+ engineConfiguration->injectionMode = IM_BATCH;
+ // set_ignition_mode 2
+ engineConfiguration->ignitionMode = IM_WASTED_SPARK;
+ // set_firing_order 2
+ engineConfiguration->firingOrder = FO_1_THEN_3_THEN_4_THEN2;
+
+
+}
+
diff --git a/firmware/config/engines/citroenBerlingoTU3JP.h b/firmware/config/engines/citroenBerlingoTU3JP.h
new file mode 100644
index 0000000000..6fab4811a3
--- /dev/null
+++ b/firmware/config/engines/citroenBerlingoTU3JP.h
@@ -0,0 +1,15 @@
+/**
+ * @file citroenBerlingoTU3JP.h
+ *
+ * @date Apr 15, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef CITROENBERLINGOTU3JP_H_
+#define CITROENBERLINGOTU3JP_H_
+
+#include "engine_configuration.h"
+
+void setCitroenBerlingoTU3JPConfiguration(engine_configuration_s *engineConfiguration, board_configuration_s *boardConfiguration);
+
+#endif /* CITROENBERLINGOTU3JP_H_ */
diff --git a/firmware/config/engines/dodge_neon.cpp b/firmware/config/engines/dodge_neon.cpp
new file mode 100644
index 0000000000..f3893caa76
--- /dev/null
+++ b/firmware/config/engines/dodge_neon.cpp
@@ -0,0 +1,108 @@
+/**
+ * @file dodge_neon.cpp
+ *
+ * DODGE_NEON_1995 = 2
+ *
+ * This config overrides some values of the default configuration which is set by setDefaultConfiguration() method
+ *
+ * @date Dec 16, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+
+#if EFI_SUPPORT_DODGE_NEON || defined(__DOXYGEN__)
+
+#include "dodge_neon.h"
+#include "engine_configuration.h"
+#include "thermistors.h"
+#include "engine_math.h"
+
+void setDodgeNeonEngineConfiguration(engine_configuration_s *engineConfiguration,
+ board_configuration_s *boardConfiguration) {
+
+ engineConfiguration->triggerConfig.triggerType = TT_DODGE_NEON;
+
+ engineConfiguration->algorithm = LM_TPS;
+
+ // set_rpm_hard_limit 4000
+ engineConfiguration->rpmHardLimit = 4000; // yes, 4k. let's play it safe for now
+ // set_cranking_rpm 550
+ engineConfiguration->crankingSettings.crankingRpm = 550;
+
+ // since CLT is not wired up yet let's just use same value for min and max
+ // set_cranking_fuel_max 6 40
+ engineConfiguration->crankingSettings.coolantTempMaxC = 37.7; // 6ms at 37.7C
+ engineConfiguration->crankingSettings.fuelAtMaxTempMs = 6;
+
+ // set_cranking_fuel_min 6 -40
+ engineConfiguration->crankingSettings.coolantTempMinC = -40; // 6ms at -40C
+ engineConfiguration->crankingSettings.fuelAtMinTempMs = 6;
+
+ // set_whole_fuel_map 3
+ setWholeFuelMap(engineConfiguration, 3);
+
+ // set_cranking_injection_mode 0
+ engineConfiguration->crankingInjectionMode = IM_SIMULTANEOUS;
+ // set_injection_mode 1
+ engineConfiguration->injectionMode = IM_SEQUENTIAL;
+
+ // set_ignition_mode 2
+ engineConfiguration->ignitionMode = IM_WASTED_SPARK;
+ // set_firing_order 2
+ engineConfiguration->firingOrder = FO_1_THEN_3_THEN_4_THEN2;
+
+ // set_global_trigger_offset_angle 497
+ engineConfiguration->globalTriggerAngleOffset = 497;
+ // set_ignition_offset 350
+ engineConfiguration->ignitionOffset = 350;
+ // set_injection_offset 510
+ engineConfiguration->injectionOffset = 510;
+
+ // set_cranking_charge_angle 70
+ engineConfiguration->crankingChargeAngle = 7;
+ // set_cranking_timing_angle 0
+ engineConfiguration->crankingTimingAngle = 0;
+
+ // Frankenstein: low side - inj #1: PC14
+ // Frankenstein: low side - inj #2: PC15
+ // Frankenstein: low side - inj #3: PE6
+ // Frankenstein: low side - inj #4: PC13
+ // Frankenstein: low side - inj #5: PE4
+ // Frankenstein: low side - inj #6: PE5
+ // Frankenstein: low side - inj #7: PE2
+ // Frankenstein: low side - inj #8: PE3
+ // Frankenstein: low side - inj #9: PE0
+ // Frankenstein: low side - inj #10: PE1
+ // Frankenstein: low side - inj #11: PB8
+ // Frankenstein: low side - inj #12: PB9
+
+ boardConfiguration->injectionPins[0] = GPIOB_9; // Frankenstein: low side - inj #12
+ boardConfiguration->injectionPins[1] = GPIOB_8; // Frankenstein: low side - inj #11
+ boardConfiguration->injectionPins[2] = GPIOE_3; // Frankenstein: low side - inj #8
+ boardConfiguration->injectionPins[3] = GPIOE_5; // Frankenstein: low side - inj #6
+
+ boardConfiguration->fuelPumpPin = GPIOC_13; // Frankenstein: low side - inj #4
+ boardConfiguration->fuelPumpPinMode = OM_DEFAULT;
+
+ // set_injection_pin_mode 0
+ boardConfiguration->injectionPinMode = OM_DEFAULT;
+
+ // Frankenstein: high side #1: PE8
+ // Frankenstein: high side #2: PE10
+
+ boardConfiguration->ignitionPins[0] = GPIOE_8; // Frankenstein: high side #1
+ boardConfiguration->ignitionPins[1] = GPIO_NONE;
+ boardConfiguration->ignitionPins[2] = GPIOE_10; // // Frankenstein: high side #2
+
+ // set_ignition_pin_mode 0
+ boardConfiguration->ignitionPinMode = OM_DEFAULT;
+
+ setThermistorConfiguration(&engineConfiguration->cltThermistorConf, 0, 32500, 30, 7550, 100, 700);
+ engineConfiguration->cltThermistorConf.bias_resistor = 2700;
+
+ engineConfiguration->analogChartFrequency = 7;
+}
+
+#endif /* EFI_SUPPORT_DODGE_NEON */
+
diff --git a/firmware/config/engines/dodge_neon.h b/firmware/config/engines/dodge_neon.h
new file mode 100644
index 0000000000..baae197d11
--- /dev/null
+++ b/firmware/config/engines/dodge_neon.h
@@ -0,0 +1,21 @@
+/**
+ * @file dodge_neon.h
+ * @brief 1995 Dodge Neon default engine configuration
+ *
+ * @date Dec 16, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef DODGE_NEON_H_
+#define DODGE_NEON_H_
+
+#if EFI_SUPPORT_DODGE_NEON
+
+#include "engine_configuration.h"
+
+void setDodgeNeonEngineConfiguration(engine_configuration_s *engineConfiguration,
+ board_configuration_s *boardConfiguration);
+
+#endif /* EFI_SUPPORT_DODGE_NEON */
+
+#endif /* DODGE_NEON_H_ */
diff --git a/firmware/config/engines/engines.mk b/firmware/config/engines/engines.mk
new file mode 100644
index 0000000000..a46d18c73a
--- /dev/null
+++ b/firmware/config/engines/engines.mk
@@ -0,0 +1,20 @@
+
+ENGINES_SRC =
+
+ENGINES_SRC_CPP = $(PROJECT_DIR)/config/engines/ford_aspire.cpp \
+ $(PROJECT_DIR)/config/engines/MiniCooperR50.cpp \
+ $(PROJECT_DIR)/config/engines/audi_aan.cpp \
+ $(PROJECT_DIR)/config/engines/ford_escort_gt.cpp \
+ $(PROJECT_DIR)/config/engines/citroenBerlingoTU3JP.cpp \
+ $(PROJECT_DIR)/config/engines/dodge_neon.cpp \
+ $(PROJECT_DIR)/config/engines/ford_fiesta.cpp \
+ $(PROJECT_DIR)/config/engines/ford_1995_inline_6.cpp \
+ $(PROJECT_DIR)/config/engines/nissan_primera.cpp \
+ $(PROJECT_DIR)/config/engines/mazda_miata_nb.cpp \
+ $(PROJECT_DIR)/config/engines/honda_accord.cpp \
+ $(PROJECT_DIR)/config/engines/snow_blower.cpp \
+ $(PROJECT_DIR)/config/engines/GY6_139QMB.cpp \
+ $(PROJECT_DIR)/config/engines/rover_v8.cpp \
+ $(PROJECT_DIR)/config/engines/mazda_323.cpp \
+ $(PROJECT_DIR)/config/engines/saturn_ion.cpp \
+ $(PROJECT_DIR)/config/engines/mitsubishi.cpp
\ No newline at end of file
diff --git a/firmware/config/engines/ford_1995_inline_6.cpp b/firmware/config/engines/ford_1995_inline_6.cpp
new file mode 100644
index 0000000000..c74b2db9d8
--- /dev/null
+++ b/firmware/config/engines/ford_1995_inline_6.cpp
@@ -0,0 +1,116 @@
+/**
+ * @file ford_1995_inline_6.cpp
+ * @brief Default engine configuration for a 1995 Ford inline 6 engine
+ *
+ * http://rusefi.com/forum/viewtopic.php?f=3&t=469
+ *
+ * This config overrides some values of the default configuration which is set by setDefaultConfiguration() method
+ *
+ * FORD_INLINE_6_1995 = 7
+ *
+ * @date Feb 12, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+#include "ford_1995_inline_6.h"
+#include "engine_math.h"
+#include "allsensors.h"
+
+#if EFI_SUPPORT_1995_FORD_INLINE_6 || defined(__DOXYGEN__)
+
+/**
+ * @brief Default values for persistent properties
+ */
+void setFordInline6(engine_configuration_s *engineConfiguration, board_configuration_s *boardConfiguration) {
+ engineConfiguration->cylindersCount = 6;
+
+ setOperationMode(engineConfiguration, FOUR_STROKE_CAM_SENSOR);
+
+ engineConfiguration->ignitionMode = IM_ONE_COIL;
+ engineConfiguration->firingOrder = FO_1_THEN_5_THEN_3_THEN_6_THEN_2_THEN_4;
+ engineConfiguration->crankingInjectionMode = IM_SIMULTANEOUS;
+ engineConfiguration->injectionMode = IM_BATCH;
+
+ /**
+ * 0.5ms dweel time just to be sure it would fit within camshaft revolution, dwell is not controlled by us anyway
+ */
+ setConstantDwell(engineConfiguration, 0.5);
+
+ /**
+ * We treat the trigger as 6/0 toothed wheel
+ */
+ engineConfiguration->triggerConfig.triggerType = TT_TOOTHED_WHEEL;
+ engineConfiguration->triggerConfig.customTotalToothCount = 6;
+ engineConfiguration->triggerConfig.customSkippedToothCount = 0;
+ engineConfiguration->triggerConfig.customIsSynchronizationNeeded = false;
+
+ engineConfiguration->globalTriggerAngleOffset = 0;
+ engineConfiguration->ignitionOffset = 13;
+
+ setThermistorConfiguration(&engineConfiguration->cltThermistorConf, -10.0, 160310.0, 60.0, 7700.0, 120.0, 1180.0);
+ engineConfiguration->cltThermistorConf.bias_resistor = 2700;
+
+ setThermistorConfiguration(&engineConfiguration->iatThermistorConf, -10.0, 160310.0, 60.0, 7700.0, 120.0, 1180.0);
+ engineConfiguration->iatThermistorConf.bias_resistor = 2700;
+
+ // 12ch analog board pinout:
+ // input channel 3 is PA7, that's ADC7
+ // input channel 5 is PA4, that's ADC4
+ // input channel 6 is PA3, that's ADC3
+ // input channel 7 is PA2, that's ADC2
+ // input channel 8 is PA1, that's ADC1
+ // input channel 9 is PA0, that's ADC0
+ // input channel 10 is PC3, that's ADC13
+ // input channel 12 is PC1, that's ADC11
+
+ memset(boardConfiguration->adcHwChannelEnabled, 0, sizeof(boardConfiguration->adcHwChannelEnabled));
+ boardConfiguration->adcHwChannelEnabled[1] = ADC_SLOW;
+ boardConfiguration->adcHwChannelEnabled[2] = ADC_SLOW;
+ boardConfiguration->adcHwChannelEnabled[3] = ADC_SLOW;
+ boardConfiguration->adcHwChannelEnabled[4] = ADC_SLOW;
+
+ boardConfiguration->adcHwChannelEnabled[7] = ADC_SLOW;
+ boardConfiguration->adcHwChannelEnabled[11] = ADC_SLOW;
+ boardConfiguration->adcHwChannelEnabled[15] = ADC_FAST;
+
+
+ engineConfiguration->tpsAdcChannel = EFI_ADC_4;
+ engineConfiguration->iatAdcChannel = EFI_ADC_2;
+ engineConfiguration->cltAdcChannel = EFI_ADC_1;
+ engineConfiguration->afrSensor.afrAdcChannel = EFI_ADC_11;
+
+ engineConfiguration->map.sensor.sensorType = MT_MPX4250;
+ engineConfiguration->map.sensor.hwChannel = EFI_ADC_15;
+ engineConfiguration->baroSensor.sensorType = MT_MPX4250;
+ engineConfiguration->baroSensor.hwChannel = EFI_ADC_7;
+
+ // 6 channel output board
+ // output 1 is PB9
+ // output 3 is PE3
+ // output 5 is PC13
+ // output 6 is PC15
+
+ boardConfiguration->fuelPumpPin = GPIOC_13;
+ boardConfiguration->injectionPins[0] = GPIOB_9;
+ boardConfiguration->injectionPins[1] = GPIOE_3;
+ boardConfiguration->ignitionPins[0] = GPIOC_15;
+
+ boardConfiguration->injectionPins[2] = GPIO_NONE;
+ boardConfiguration->fanPin = GPIO_NONE;
+
+ engineConfiguration->tpsMin = convertVoltageTo10bitADC(1.250);
+ engineConfiguration->tpsMax = convertVoltageTo10bitADC(4.538);
+
+ // engineConfiguration->vBattAdcChannel = 0; //
+// engineConfiguration->mafAdcChannel = 1;
+
+ boardConfiguration->triggerInputPins[0] = GPIOA_8;
+ boardConfiguration->triggerInputPins[1] = GPIOA_5;
+ boardConfiguration->logicAnalyzerPins[0] = GPIOC_6;
+ boardConfiguration->logicAnalyzerPins[1] = GPIOE_7;
+
+
+}
+
+#endif /* EFI_SUPPORT_1995_FORD_INLINE_6 */
diff --git a/firmware/config/engines/ford_1995_inline_6.h b/firmware/config/engines/ford_1995_inline_6.h
new file mode 100644
index 0000000000..a9522a5b8f
--- /dev/null
+++ b/firmware/config/engines/ford_1995_inline_6.h
@@ -0,0 +1,15 @@
+/*
+ * @file ford_1995_inline_6.h
+ *
+ * @date Feb 12, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef FORD_1995_INLINE_6_H_
+#define FORD_1995_INLINE_6_H_
+
+#include "engine_configuration.h"
+
+void setFordInline6(engine_configuration_s *engineConfiguration, board_configuration_s *boardConfiguration);
+
+#endif /* FORD_1995_INLINE_6_H_ */
diff --git a/firmware/config/engines/ford_aspire.cpp b/firmware/config/engines/ford_aspire.cpp
new file mode 100644
index 0000000000..2094933938
--- /dev/null
+++ b/firmware/config/engines/ford_aspire.cpp
@@ -0,0 +1,161 @@
+/**
+ * @file ford_aspire.cpp
+ * @brief 1996 Ford Aspire default engine configuration
+ *
+ * FORD_ASPIRE_1996 = 3
+ *
+ * @date Sep 9, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+
+#include "ford_aspire.h"
+
+#include "allsensors.h"
+#include "engine_math.h"
+#include "advance_map.h"
+#include "engine_configuration.h"
+
+#if EFI_SUPPORT_FORD_ASPIRE || defined(__DOXYGEN__)
+
+/**
+ * This is just the default map which is stored into flash memory in case flash is empty
+ * The convenient way to override these default would be to tune this map using TunerStudio software
+ * with which rusEfi is integrated
+ */
+static float default_fuel_table[FUEL_LOAD_COUNT][FUEL_RPM_COUNT] = {
+/* RPM 800.000000 1213.333374 1626.666748 2040.000000 2453.333496 2866.666748 3280.000000 3693.333496 4106.666992 4520.000000 4933.333496 5346.666992 5760.000000 6173.333496 6586.666992 7000.000000*/
+/* Load 1.200000 */{ 1.542000, 1.547600, 1.551867, 1.570000, 1.550000, 0.908666, 0.794800, 0.775200, 0.791733, 0.800000, 0.798667, 0.805733, 0.810000, 0.810000, 0.810000, 0.810000},
+/* Load 1.413333 */{ 1.532133, 1.565325, 1.551244, 1.552773, 1.546018, 0.802089, 0.810000, 0.788507, 0.808898, 0.744987, 0.701378, 0.711404, 0.744667, 0.810000, 0.810000, 0.810000},
+/* Load 1.626667 */{ 1.543600, 1.545573, 1.555956, 1.545973, 1.415333, 0.115288, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000},
+/* Load 1.840000 */{ 1.538800, 1.741893, 1.558426, 1.591440, 1.228773, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000},
+/* Load 2.053333 */{ 2.322933, 3.439928, 2.818523, 2.242266, 2.125839, 1.194041, 0.488959, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000},
+/* Load 2.266667 */{ 7.696667, 5.957510, 4.700978, 3.879200, 3.099600, 2.403111, 2.458666, 1.790979, 2.190533, 2.074667, 1.368887, 1.287556, 0.000000, 0.000000, 0.000000, 0.000000},
+/* Load 2.480000 */{ 14.330001, 8.710773, 6.558134, 5.491520, 5.117494, 4.308798, 3.185521, 3.339520, 2.883200, 2.840399, 2.643334, 2.505092, 2.296640, 0.000000, 0.000000, 0.000000},
+/* Load 2.693333 */{ 21.354004, 15.415981, 10.953371, 8.779520, 5.866481, 5.038577, 5.113254, 4.395565, 4.537200, 3.559386, 3.828622, 3.147404, 3.377706, 2.780000, 2.780000, 2.780000},
+/* Load 2.906667 */{ 20.020401, 16.311201, 17.209972, 15.600482, 10.950183, 7.773465, 6.243252, 7.400613, 6.672044, 6.133946, 5.529999, 4.769466, 4.528134, 4.010000, 4.010000, 4.010000},
+/* Load 3.120000 */{ 18.264000, 15.464134, 15.595227, 15.395760, 12.987042, 13.339199, 8.897678, 8.927333, 8.032880, 6.769040, 5.823335, 6.413146, 6.089281, 5.730000, 5.730000, 5.730000},
+/* Load 3.333333 */{ 17.414667, 15.366401, 15.187378, 15.338401, 15.306623, 15.386889, 14.470800, 11.941733, 10.985557, 9.956400, 9.019111, 8.441555, 7.515199, 6.900000, 6.900000, 6.900000},
+/* Load 3.546667 */{ 17.005333, 15.372302, 15.188160, 15.278268, 15.325876, 15.561645, 15.638906, 15.415441, 13.630393, 10.918774, 11.508314, 10.660010, 9.551816, 9.230000, 9.230000, 9.230000},
+/* Load 3.760000 */{ 16.978800, 15.360346, 15.170587, 15.301680, 15.331520, 15.684401, 15.810480, 15.702454, 15.828107, 15.114964, 14.666001, 11.908847, 12.046723, 9.230000, 9.230000, 9.230000},
+/* Load 3.973333 */{ 16.975599, 15.343084, 15.305715, 15.287172, 15.288801, 15.484089, 15.928854, 15.780366, 15.669041, 15.836693, 16.123335, 15.054674, 14.972587, 14.610000, 14.610000, 14.610000},
+/* Load 4.186667 */{ 16.961868, 15.368391, 15.210348, 15.290641, 15.263707, 15.485778, 15.827333, 15.802435, 15.677885, 15.827120, 15.997155, 16.027468, 15.478480, 15.190000, 15.190000, 15.190000},
+/* Load 4.400000 */{ 17.010000, 15.250000, 15.680000, 15.440000, 15.270000, 15.470000, 15.800000, 15.730000, 15.600000, 15.790000, 16.120001, 16.110001, 15.630000, 15.150000, 15.150000, 15.150000}
+};
+
+static float default_timing_table[AD_LOAD_COUNT][AD_RPM_COUNT] = {
+/* RPM 800.000000 1213.333374 1626.666748 2040.000000 2453.333496 2866.666748 3280.000000 3693.333496 4106.666992 4520.000000 4933.333496 5346.666992 5760.000000 6173.333496 6586.666992 7000.000000*/
+/* Load 1.200000 */{ 0.662000, -7.730000, -16.722000, -23.139999, -29.398001, -31.268000, -32.108002, -30.436001, -30.896000, -26.656000, -24.704000, -25.108000, -25.132000, -25.459999, -25.459999, -25.459999},
+/* Load 1.413333 */{ 0.546000, -7.662000, -16.882000, -23.482000, -29.520000, -31.323999, -32.108002, -30.656000, -30.468000, -26.879999, -24.746000, -24.742001, -29.032000, -25.562000, -25.562000, -25.562000},
+/* Load 1.626667 */{ 0.584000, -7.870000, -16.714001, -23.025999, -29.542000, -31.166000, -32.175999, -30.540001, -30.268000, -26.416000, -24.134001, -25.007999, -24.698000, -26.167999, -26.167999, -26.167999},
+/* Load 1.840000 */{ 0.584000, -7.658000, -16.714001, -23.254000, -29.351999, -30.978001, -32.141998, -30.874001, -30.896000, -26.507999, -24.558001, -24.389999, -25.761999, -35.492001, -35.492001, -35.492001},
+/* Load 2.053333 */{ 0.584000, -7.862000, -16.538000, -23.254000, -29.232000, -31.296000, -32.520000, -30.142000, -30.388000, -25.903999, -24.370001, -24.082001, -24.792000, -24.351999, -24.351999, -24.351999},
+/* Load 2.266667 */{ -1.364000, -7.726000, -16.806000, -23.254000, -29.639999, -31.006001, -32.298000, -30.912001, -29.882000, -26.392000, -24.664000, -27.233999, -25.374001, -25.417999, -25.417999, -25.417999},
+/* Load 2.480000 */{ 1.364000, -10.490000, -16.705999, -22.441999, -28.101999, -30.238001, -32.363998, -30.719999, -30.896000, -26.608000, -24.664000, -24.431999, -24.500000, -25.510000, -25.510000, -25.510000},
+/* Load 2.693333 */{ 9.864000, -10.416000, -11.680000, -19.150000, -25.754000, -27.936001, -32.554001, -30.656000, -30.153999, -27.184000, -25.252001, -22.812000, -24.452000, -25.219999, -25.219999, -25.219999},
+/* Load 2.906667 */{ 9.866000, 5.452000, 2.854000, -17.212000, -17.552000, -20.688000, -25.660000, -27.809999, -27.691999, -27.224001, -25.882000, -25.360001, -26.100000, -27.992001, -27.992001, -27.992001},
+/* Load 3.120000 */{ 9.864000, 5.452000, 2.854000, -0.342000, -12.526000, -16.218000, -21.364000, -27.590000, -25.780001, -24.170000, -24.664000, -25.584000, -26.490000, -31.968000, -31.968000, -31.968000},
+/* Load 3.333333 */{ 9.864000, 5.516000, 2.854000, -0.226000, -2.738000, -3.816000, -11.924000, -18.808001, -21.038000, -21.538000, -21.209999, -22.228001, -25.046000, -25.156000, -25.156000, -25.156000},
+/* Load 3.546667 */{ 9.866000, 5.518000, 2.854000, 0.000000, -3.022000, -3.816000, -6.428000, -7.788000, -19.426001, -20.860001, -19.966000, -21.030001, -21.396000, -21.570000, -21.570000, -21.570000},
+/* Load 3.760000 */{ 9.864000, 5.516000, 2.772000, -0.226000, -2.732000, -3.500000, -6.798000, -8.102000, -8.660000, -9.500000, -11.788000, -20.132000, -20.072001, -20.510000, -20.510000, -20.510000},
+/* Load 3.973333 */{ 9.864000, 5.518000, 2.854000, 0.000000, -2.880000, -3.816000, -6.420000, -8.320000, -8.426000, -8.532000, -11.470000, -11.442000, -13.610000, -12.022000, -12.022000, -12.022000},
+/* Load 4.186667 */{ 9.750000, 5.518000, 2.604000, 0.000000, -2.880000, -3.654000, -6.050000, -6.888000, -8.372000, -9.364000, -11.764000, -11.732000, -11.864000, -12.376000, -12.376000, -12.376000},
+/* Load 4.400000 */{ 0.350000, 5.590000, 0.502000, 0.910000, 0.864000, 0.954000, 1.324000, -7.436000, 1.170000, 1.054000, 2.058000, 2.098000, 2.636000, -12.352000, -12.352000, -12.352000}
+};
+
+static void setDefaultMaps(engine_configuration_s *engineConfiguration) {
+
+ setFuelLoadBin(engineConfiguration, 1.2, 4.4);
+ setFuelRpmBin(engineConfiguration, 800, 7000);
+ setTimingLoadBin(engineConfiguration, 1.2, 4.4);
+ setTimingRpmBin(engineConfiguration, 800, 7000);
+
+ for (int k = 0; k < FUEL_LOAD_COUNT; k++) {
+ for (int r = 0; r < FUEL_RPM_COUNT; r++) {
+ engineConfiguration->fuelTable[k][r] = default_fuel_table[k][r];
+ }
+ }
+
+ for (int k = 0; k < AD_LOAD_COUNT; k++) {
+ for (int r = 0; r < AD_RPM_COUNT; r++) {
+ engineConfiguration->ignitionTable[k][r] = default_timing_table[k][r];
+ }
+ }
+}
+
+void setFordAspireEngineConfiguration(engine_configuration_s *engineConfiguration, board_configuration_s *boardConfiguration) {
+ engineConfiguration->tpsMin = 100;
+ engineConfiguration->tpsMax = 750;
+
+ engineConfiguration->rpmHardLimit = 7000;
+
+ /**
+ * 18K Ohm @ -20C
+ * 2.1K Ohm @ 24C
+ * 1K Ohm @ 49C
+ */
+ setThermistorConfiguration(&engineConfiguration->cltThermistorConf, -20, 18000, 23.8889, 2100, 48.8889, 1000);
+ engineConfiguration->cltThermistorConf.bias_resistor = 3300; // that's my custom resistor value!
+
+ engineConfiguration->crankingSettings.coolantTempMaxC = 65; // 8ms at 65C
+ engineConfiguration->crankingSettings.fuelAtMaxTempMs = 8;
+
+ engineConfiguration->crankingSettings.coolantTempMinC = 0; // 20ms at 0C
+ engineConfiguration->crankingSettings.fuelAtMinTempMs = 15;
+
+// engineConfiguration->ignitionPinMode = OM_INVERTED;
+
+ engineConfiguration->cylindersCount = 4;
+ engineConfiguration->displacement = 1.3;
+ // Denso 195500-2110
+ engineConfiguration->injectorFlow = 119.8;
+
+ engineConfiguration->firingOrder = FO_1_THEN_3_THEN_4_THEN2;
+ engineConfiguration->globalTriggerAngleOffset = 175;
+ engineConfiguration->ignitionOffset = 98 - 11;
+ engineConfiguration->injectionOffset = 59;
+
+ setDefaultMaps(engineConfiguration);
+ // set_cranking_rpm 550
+ engineConfiguration->crankingSettings.crankingRpm = 550;
+ // set_cranking_charge_angle 70
+ engineConfiguration->crankingChargeAngle = 70;
+ // set_cranking_timing_angle 37
+ engineConfiguration->crankingTimingAngle = 37;
+
+ setSingleCoilDwell(engineConfiguration);
+ engineConfiguration->ignitionMode = IM_ONE_COIL;
+ engineConfiguration->triggerConfig.triggerType = TT_FORD_ASPIRE;
+
+ boardConfiguration->injectionPins[4] = GPIO_NONE;
+ boardConfiguration->injectionPins[5] = GPIO_NONE;
+
+ engineConfiguration->HD44780width = 20;
+ engineConfiguration->HD44780height = 4;
+
+ // Frankenstein analog input #1: adc1
+ // Frankenstein analog input #2: adc3
+ // Frankenstein analog input #3: adc13
+ // Frankenstein analog input #4: adc11
+ // Frankenstein analog input #5: adc
+ // Frankenstein analog input #6: adc
+ // Frankenstein analog input #7: adc
+ // Frankenstein analog input #8: adc
+ // Frankenstein analog input #9: adc
+ // Frankenstein analog input #10: adc
+ // Frankenstein analog input #11: adc
+ // Frankenstein analog input #12: adc
+
+
+ engineConfiguration->tpsAdcChannel = EFI_ADC_3;
+ engineConfiguration->vBattAdcChannel = EFI_ADC_0;
+ engineConfiguration->map.sensor.hwChannel = EFI_ADC_4;
+ engineConfiguration->mafAdcChannel = EFI_ADC_1;
+ engineConfiguration->cltAdcChannel = EFI_ADC_11;
+// engineConfiguration->iatAdcChannel =
+
+ engineConfiguration->map.sensor.sensorType = MT_DENSO183;
+}
+
+#endif /* EFI_SUPPORT_FORD_ASPIRE */
diff --git a/firmware/config/engines/ford_aspire.h b/firmware/config/engines/ford_aspire.h
new file mode 100644
index 0000000000..447e20105e
--- /dev/null
+++ b/firmware/config/engines/ford_aspire.h
@@ -0,0 +1,19 @@
+/**
+ * @file ford_aspire.h
+ * @brief 1996 Ford Aspire default engine configuration
+ *
+ * @date Aug 30, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ * http://rusefi.com/forum/viewtopic.php?t=375
+ *
+ */
+
+#ifndef FORD_ASPIRE_H_
+#define FORD_ASPIRE_H_
+
+#include "engine_configuration.h"
+
+void setFordAspireEngineConfiguration(engine_configuration_s *engineConfiguration, board_configuration_s *boardConfiguration);
+
+#endif /* FORD_ASPIRE_H_ */
diff --git a/firmware/config/engines/ford_escort_gt.cpp b/firmware/config/engines/ford_escort_gt.cpp
new file mode 100644
index 0000000000..966410fd09
--- /dev/null
+++ b/firmware/config/engines/ford_escort_gt.cpp
@@ -0,0 +1,162 @@
+/**
+ * @file ford_escort_gt.cpp
+ *
+ * FORD_ESCORT_GT = 14
+ *
+ * @date Apr 11, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "ford_escort_gt.h"
+#include "engine_math.h"
+
+static void setDefaultCrankingFuel(engine_configuration_s *engineConfiguration) {
+ // todo: set cranking parameters method based on injectors and displacement?
+
+ // since CLT is not wired up yet let's just use same value for min and max
+ // set_cranking_fuel_max 6 40
+ engineConfiguration->crankingSettings.coolantTempMaxC = 37.7; // 6ms at 37.7C
+ engineConfiguration->crankingSettings.fuelAtMaxTempMs = 6;
+
+ // set_cranking_fuel_min 6 -40
+ engineConfiguration->crankingSettings.coolantTempMinC = -40; // 6ms at -40C
+ engineConfiguration->crankingSettings.fuelAtMinTempMs = 6;
+}
+
+static void common079721_2351(engine_configuration_s *engineConfiguration, board_configuration_s *boardConfiguration) {
+
+ engineConfiguration->cylindersCount = 4;
+ engineConfiguration->firingOrder = FO_1_THEN_3_THEN_4_THEN2;
+
+ boardConfiguration->fuelPumpPin = GPIO_NONE; // fuel pump is not controlled by ECU on this engine
+
+ // set_cranking_injection_mode 0
+ engineConfiguration->crankingInjectionMode = IM_SIMULTANEOUS;
+ // set_injection_mode 2
+ engineConfiguration->injectionMode = IM_BATCH;
+
+ // Frankenstein analog input #1: adc1
+ // Frankenstein analog input #2: adc3
+ // Frankenstein analog input #3: adc13
+ // Frankenstein analog input #4: adc11
+ // Frankenstein analog input #5: adc
+ // Frankenstein analog input #6: adc
+ // Frankenstein analog input #7: adc
+ // Frankenstein analog input #8: adc
+ // Frankenstein analog input #9: adc
+ // Frankenstein analog input #10: adc
+ // Frankenstein analog input #11: adc
+ // Frankenstein analog input #12: adc
+ engineConfiguration->mafAdcChannel = EFI_ADC_1;
+ engineConfiguration->tpsAdcChannel = EFI_ADC_3;
+ engineConfiguration->cltAdcChannel = EFI_ADC_11;
+}
+
+void setMiata1990(engine_configuration_s *engineConfiguration, board_configuration_s *boardConfiguration) {
+ engineConfiguration->triggerConfig.triggerType = TT_FORD_ESCORT_GT;
+
+ common079721_2351(engineConfiguration, boardConfiguration);
+
+
+ // Frankenstein: high side #1 is PE8
+ // Frankenstein: high side #2 is PE10
+ // Frankenstein: high side #3 is PE12
+ // Frankenstein: high side #4 is PE14
+ // Frankenstein: high side #5 is PC9
+ // Frankenstein: high side #6 is PC7
+
+ boardConfiguration->ignitionPins[0] = GPIOE_12; // Frankenstein: high side #3
+ boardConfiguration->ignitionPins[1] = GPIOE_14; // Frankenstein: high side #4
+ boardConfiguration->ignitionPins[2] = GPIO_NONE;
+ boardConfiguration->ignitionPins[3] = GPIO_NONE;
+ boardConfiguration->ignitionPinMode = OM_DEFAULT;
+
+ // Frankenstein: low side - inj #1: PC14
+ // Frankenstein: low side - inj #2: PC15
+ // Frankenstein: low side - inj #3: PE6
+ // Frankenstein: low side - inj #4: PC13
+ // Frankenstein: low side - inj #5: PE4
+ // Frankenstein: low side - inj #6: PE5
+ // Frankenstein: low side - inj #7: PE2
+ // Frankenstein: low side - inj #8: PE3
+ // Frankenstein: low side - inj #9: PE0
+ // Frankenstein: low side - inj #10: PE1
+ // Frankenstein: low side - inj #11: PB8
+ // Frankenstein: low side - inj #12: PB9
+
+ boardConfiguration->injectionPins[0] = GPIOB_9; // Frankenstein: low side - inj #12
+ boardConfiguration->injectionPins[1] = GPIOB_8; // Frankenstein: low side - inj #11
+ boardConfiguration->injectionPins[2] = GPIO_NONE;
+ boardConfiguration->injectionPins[3] = GPIO_NONE;
+ boardConfiguration->injectionPins[4] = GPIO_NONE;
+ boardConfiguration->injectionPins[5] = GPIO_NONE;
+ boardConfiguration->injectionPinMode = OM_DEFAULT;
+
+
+// todo: idleValvePin
+}
+
+void setFordEscortGt(engine_configuration_s *engineConfiguration, board_configuration_s *boardConfiguration) {
+ engineConfiguration->triggerConfig.triggerType = TT_FORD_ESCORT_GT;
+
+ common079721_2351(engineConfiguration, boardConfiguration);
+
+ // set_global_trigger_offset_angle 256
+ engineConfiguration->globalTriggerAngleOffset = 256;
+ // set_ignition_offset 170
+ engineConfiguration->ignitionOffset = 170;
+ // set_injection_offset 510
+ engineConfiguration->injectionOffset = 59;
+
+ setSingleCoilDwell(engineConfiguration);
+ engineConfiguration->ignitionMode = IM_ONE_COIL;
+
+ boardConfiguration->triggerSimulatorPinModes[0] = OM_OPENDRAIN;
+ boardConfiguration->triggerSimulatorPinModes[1] = OM_OPENDRAIN;
+
+ // Frankenstein: high side #1 is PE8
+ // Frankenstein: high side #2 is PE10
+ // Frankenstein: high side #3 is PE12
+ // Frankenstein: high side #4 is PE14
+ // Frankenstein: high side #5 is PC9
+ // Frankenstein: high side #6 is PC7
+
+ boardConfiguration->ignitionPins[0] = GPIOE_12; // Frankenstein: high side #3
+ boardConfiguration->ignitionPins[1] = GPIO_NONE;
+ boardConfiguration->ignitionPins[2] = GPIO_NONE;
+ boardConfiguration->ignitionPins[3] = GPIO_NONE;
+ boardConfiguration->ignitionPinMode = OM_DEFAULT;
+
+ // set_whole_fuel_map 3
+ setWholeFuelMap(engineConfiguration, 3);
+
+ setDefaultCrankingFuel(engineConfiguration);
+}
+
+/**
+ * set_engine_type 20
+ */
+void setMiata1994(engine_configuration_s *engineConfiguration, board_configuration_s *boardConfiguration) {
+ engineConfiguration->triggerConfig.triggerType = TT_MAZDA_MIATA_NA;
+ engineConfiguration->displacement = 1.839;
+
+ boardConfiguration->triggerSimulatorPins[0] = GPIOD_2; // 2G - YEL/BLU
+ boardConfiguration->triggerSimulatorPins[1] = GPIOB_3; // 2E - WHT - four times
+ boardConfiguration->triggerSimulatorPinModes[0] = OM_OPENDRAIN;
+ boardConfiguration->triggerSimulatorPinModes[1] = OM_OPENDRAIN;
+
+ boardConfiguration->triggerInputPins[0] = GPIO_NONE;
+ boardConfiguration->triggerInputPins[1] = GPIO_NONE;
+
+ boardConfiguration->is_enabled_spi_1 = false;
+ boardConfiguration->is_enabled_spi_2 = false;
+ boardConfiguration->is_enabled_spi_3 = false;
+
+ setDefaultCrankingFuel(engineConfiguration);
+}
+
+void setMiata1996(engine_configuration_s *engineConfiguration, board_configuration_s *boardConfiguration) {
+
+ setDefaultCrankingFuel(engineConfiguration);
+}
+
diff --git a/firmware/config/engines/ford_escort_gt.h b/firmware/config/engines/ford_escort_gt.h
new file mode 100644
index 0000000000..6058696aa2
--- /dev/null
+++ b/firmware/config/engines/ford_escort_gt.h
@@ -0,0 +1,23 @@
+/**
+ * @file ford_escort_gt.h
+ * @brief 1993 Ford Escort GT engine configuration. The US Escort.
+ *
+ * @date Oct 31, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ * http://rusefi.com/forum/viewtopic.php?t=537
+ * Injectors: Denso 195500-2180, 230-265cc (?), tan, 13.9 ohms
+ */
+
+#ifndef FORD_ESCORT_GT_H_
+#define FORD_ESCORT_GT_H_
+
+#include "engine_configuration.h"
+
+void setFordEscortGt(engine_configuration_s *engineConfiguration, board_configuration_s *boardConfiguration);
+// todo: maybe it's time to rename this file to Miata NA? Miata? Mazda?
+void setMiata1990(engine_configuration_s *engineConfiguration, board_configuration_s *boardConfiguration);
+void setMiata1994(engine_configuration_s *engineConfiguration, board_configuration_s *boardConfiguration);
+void setMiata1996(engine_configuration_s *engineConfiguration, board_configuration_s *boardConfiguration);
+
+#endif /* FORD_ESCORT_GT_H_ */
diff --git a/firmware/config/engines/ford_fiesta.cpp b/firmware/config/engines/ford_fiesta.cpp
new file mode 100644
index 0000000000..1fb2b3b109
--- /dev/null
+++ b/firmware/config/engines/ford_fiesta.cpp
@@ -0,0 +1,29 @@
+/**
+ * @file ford_fiesta.cpp
+ * @brief European 1990 Ford Fiesta
+ *
+ * FORD_FIESTA = 4
+ *
+ * @date Nov 22, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+
+#if EFI_SUPPORT_FORD_FIESTA || defined(__DOXYGEN__)
+
+#include "ford_fiesta.h"
+#include "engine_configuration.h"
+#include "engine_math.h"
+
+void setFordFiestaDefaultEngineConfiguration(engine_configuration_s *engineConfiguration) {
+ engineConfiguration->rpmHardLimit = 7000;
+ setOperationMode(engineConfiguration, FOUR_STROKE_CRANK_SENSOR);
+
+ engineConfiguration->triggerConfig.triggerType = TT_TOOTHED_WHEEL_36_1;
+
+ engineConfiguration->ignitionMode = IM_WASTED_SPARK;
+ engineConfiguration->firingOrder = FO_1_THEN_3_THEN_4_THEN2;
+}
+
+#endif /* EFI_SUPPORT_FORD_FIESTA */
diff --git a/firmware/config/engines/ford_fiesta.h b/firmware/config/engines/ford_fiesta.h
new file mode 100644
index 0000000000..d519d56c7f
--- /dev/null
+++ b/firmware/config/engines/ford_fiesta.h
@@ -0,0 +1,17 @@
+/**
+ * @file ford_fiesta.h
+ *
+ * @date Nov 22, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef FORD_FIESTA_H_
+#define FORD_FIESTA_H_
+#if EFI_SUPPORT_FORD_FIESTA || defined(__DOXYGEN__)
+
+#include "engine_configuration.h"
+
+void setFordFiestaDefaultEngineConfiguration(engine_configuration_s *engineConfiguration);
+
+#endif /* EFI_SUPPORT_FORD_FIESTA */
+#endif /* FORD_FIESTA_H_ */
diff --git a/firmware/config/engines/honda_accord.cpp b/firmware/config/engines/honda_accord.cpp
new file mode 100644
index 0000000000..9c3127f828
--- /dev/null
+++ b/firmware/config/engines/honda_accord.cpp
@@ -0,0 +1,203 @@
+/**
+ * @file honda_accord.cpp
+ *
+ * 1995 Honda Accord EX
+ * http://rusefi.com/wiki/index.php?title=Vehicle:Honda_Accord_1995
+ * http://rusefi.com/forum/viewtopic.php?f=3&t=621
+ *
+ * engine_type 6
+ * engine_type 17
+ *
+ * @date Jan 12, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+#include "trigger_decoder.h"
+#include "thermistors.h"
+#include "honda_accord.h"
+#include "engine_math.h"
+
+static void setHondaAccordConfigurationCommon(engine_configuration_s *engineConfiguration, board_configuration_s *boardConfiguration) {
+ engineConfiguration->map.sensor.sensorType = MT_DENSO183;
+
+ engineConfiguration->ignitionMode = IM_ONE_COIL;
+ engineConfiguration->injectionMode = IM_BATCH;
+
+ engineConfiguration->idleMode = IM_MANUAL;
+
+ engineConfiguration->HD44780height = 4;
+
+ engineConfiguration->cylindersCount = 4;
+ engineConfiguration->displacement = 2.156;
+
+ // Keihin 06164-P0A-A00
+ engineConfiguration->injectorFlow = 248;
+
+// engineConfiguration->algorithm = LM_SPEED_DENSITY;
+ // I want to start with a simple Alpha-N
+ engineConfiguration->algorithm = LM_TPS;
+ setFuelLoadBin(engineConfiguration, 0, 100);
+ setTimingLoadBin(engineConfiguration, 0, 100);
+
+ engineConfiguration->crankingSettings.coolantTempMaxC = 65; // 8ms at 65C
+ engineConfiguration->crankingSettings.fuelAtMaxTempMs = 8;
+
+ engineConfiguration->crankingSettings.coolantTempMinC = 0; // 20ms at 0C
+ engineConfiguration->crankingSettings.fuelAtMinTempMs = 15;
+
+ /**
+ * 18K Ohm @ -20C
+ * 2.1K Ohm @ 24C
+ * 100 Ohm @ 120C
+ */
+ setThermistorConfiguration(&engineConfiguration->cltThermistorConf, -20.0, 18000.0, 23.8889, 2100.0, 120.0, 100.0);
+ engineConfiguration->cltThermistorConf.bias_resistor = 1500; // same as OEM ECU
+
+ setThermistorConfiguration(&engineConfiguration->iatThermistorConf, -20.0, 18000.0, 23.8889, 2100.0, 120.0, 100.0);
+ engineConfiguration->iatThermistorConf.bias_resistor = 1500; // same as OEM ECU
+
+ // set_cranking_charge_angle 35
+ engineConfiguration->crankingChargeAngle = 70;
+ // set_cranking_timing_angle 0
+ engineConfiguration->crankingTimingAngle = 45;
+
+ // set_global_trigger_offset_angle 34
+ engineConfiguration->globalTriggerAngleOffset = 34;
+
+ // set_rpm_hard_limit 4000
+ engineConfiguration->rpmHardLimit = 4000; // yes, 4k. let's play it safe for now
+ // set_cranking_rpm 2000
+ engineConfiguration->crankingSettings.crankingRpm = 500;
+
+
+ // set_ignition_offset 350
+// engineConfiguration->ignitionOffset = 350;
+ // set_injection_offset 510
+// engineConfiguration->injectionOffset = 510;
+
+
+ /**
+ * ADC inputs:
+ *
+ * Inp1/ADC12 PC2: CLT
+ * Inp2/ADC11 PC1: AIT/IAT
+ * Inp3/ADC0 PA0: MAP
+ * Inp4/ADC13 PC3: AFR
+ * Inp6/ADC1 PA1: TPS
+ * Inp12/ADC14 PC4: VBatt
+ */
+ memset(boardConfiguration->adcHwChannelEnabled, 0, sizeof(boardConfiguration->adcHwChannelEnabled));
+ boardConfiguration->adcHwChannelEnabled[0] = ADC_FAST; // ADC0 - PA0 - MAP
+ boardConfiguration->adcHwChannelEnabled[1] = ADC_SLOW; // TPS
+ boardConfiguration->adcHwChannelEnabled[2] = ADC_SLOW;
+ boardConfiguration->adcHwChannelEnabled[3] = ADC_SLOW;
+ boardConfiguration->adcHwChannelEnabled[4] = ADC_SLOW;
+
+ boardConfiguration->adcHwChannelEnabled[6] = ADC_SLOW;
+ boardConfiguration->adcHwChannelEnabled[11] = ADC_SLOW; // IAT
+ boardConfiguration->adcHwChannelEnabled[12] = ADC_SLOW; // CLT
+ boardConfiguration->adcHwChannelEnabled[13] = ADC_SLOW; // AFR
+ boardConfiguration->adcHwChannelEnabled[14] = ADC_SLOW; // VBatt
+
+ /**
+ * D14/W10 O2 Sensor
+ */
+ engineConfiguration->afrSensor.afrAdcChannel = EFI_ADC_13;
+
+ /**
+ * VBatt
+ */
+ engineConfiguration->vBattAdcChannel = EFI_ADC_14;
+ engineConfiguration->vbattDividerCoeff = ((float) (8.2 + 33)) / 8.2 * 2;
+
+ // todo engineConfiguration->afrSensor.afrAdcChannel = 14;
+
+
+ /**
+ * MAP D17/W5
+ */
+ engineConfiguration->map.sensor.hwChannel = EFI_ADC_0;
+
+
+ /**
+ * TPS D11/W11
+ */
+ engineConfiguration->tpsAdcChannel = EFI_ADC_1;
+
+ /**
+ * IAT D15/W7
+ */
+ engineConfiguration->iatAdcChannel = EFI_ADC_11;
+
+ /**
+ * CLT D13/W9
+ */
+ engineConfiguration->cltAdcChannel = EFI_ADC_12;
+
+
+ /**
+ * Outputs
+ */
+ // Frankenso low out #:
+ // Frankenso low out #:
+ // Frankenso low out #:
+ // Frankenso low out #:
+ // Frankenso low out #5: PE3
+ // Frankenso low out #6: PE4
+ // Frankenso low out #7: PE1 (do not use with discovery!)
+ // Frankenso low out #:
+ // Frankenso low out #9: PB9
+ // Frankenso low out #10: PE0 (do not use with discovery!)
+ // Frankenso low out #11: PB8
+ // Frankenso low out #12: PB7
+
+ boardConfiguration->idleValvePin = GPIOE_5;
+ boardConfiguration->o2heaterPin = GPIOC_13;
+
+ boardConfiguration->injectionPins[0] = GPIOB_8;
+ boardConfiguration->injectionPins[1] = GPIOB_9;
+ boardConfiguration->injectionPins[2] = GPIOE_1;
+ boardConfiguration->injectionPins[3] = GPIOB_7;
+
+ boardConfiguration->ignitionPins[0] = GPIOE_4;
+ boardConfiguration->ignitionPins[1] = GPIO_NONE;
+ boardConfiguration->ignitionPins[2] = GPIO_NONE;
+ boardConfiguration->ignitionPins[3] = GPIO_NONE;
+
+ boardConfiguration->fuelPumpPin = GPIOE_3;
+ boardConfiguration->fuelPumpPinMode = OM_DEFAULT;
+
+ boardConfiguration->gps_rx_pin = GPIO_NONE;
+ boardConfiguration->gps_tx_pin = GPIO_NONE;
+
+ boardConfiguration->HD44780_rs = GPIOE_7;
+ boardConfiguration->HD44780_e = GPIOE_9;
+ boardConfiguration->HD44780_db4 = GPIOE_11;
+ boardConfiguration->HD44780_db5 = GPIOE_13;
+ boardConfiguration->HD44780_db6 = GPIOE_15;
+ boardConfiguration->HD44780_db7 = GPIOB_10;
+
+ boardConfiguration->logicAnalyzerPins[1] = GPIO_NONE;
+
+
+ boardConfiguration->idleSolenoidFrequency = 500;
+}
+
+void setHondaAccordConfigurationTwoWires(engine_configuration_s *engineConfiguration, board_configuration_s *boardConfiguration) {
+ engineConfiguration->engineType = HONDA_ACCORD_CD_TWO_WIRES;
+ engineConfiguration->triggerConfig.triggerType = TT_HONDA_ACCORD_CD_TWO_WIRES;
+ setHondaAccordConfigurationCommon(engineConfiguration, boardConfiguration);
+}
+
+void setHondaAccordConfigurationThreeWires(engine_configuration_s *engineConfiguration, board_configuration_s *boardConfiguration) {
+ engineConfiguration->engineType = HONDA_ACCORD_CD;
+ engineConfiguration->triggerConfig.triggerType = TT_HONDA_ACCORD_CD;
+ setHondaAccordConfigurationCommon(engineConfiguration, boardConfiguration);
+}
+
+void setHondaAccordConfigurationDip(engine_configuration_s *engineConfiguration, board_configuration_s *boardConfiguration) {
+ engineConfiguration->engineType = HONDA_ACCORD_CD_DIP;
+ engineConfiguration->triggerConfig.triggerType = TT_HONDA_ACCORD_CD_DIP;
+ setHondaAccordConfigurationCommon(engineConfiguration, boardConfiguration);
+}
diff --git a/firmware/config/engines/honda_accord.h b/firmware/config/engines/honda_accord.h
new file mode 100644
index 0000000000..f4e8881385
--- /dev/null
+++ b/firmware/config/engines/honda_accord.h
@@ -0,0 +1,17 @@
+/**
+ * @file honda_accord.h
+ *
+ * @date Jan 12, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef HONDA_ACCORD_H_
+#define HONDA_ACCORD_H_
+
+#include "engine_configuration.h"
+
+void setHondaAccordConfigurationTwoWires(engine_configuration_s *engineConfiguration, board_configuration_s *boardConfiguration);
+void setHondaAccordConfigurationThreeWires(engine_configuration_s *engineConfiguration, board_configuration_s *boardConfiguration);
+void setHondaAccordConfigurationDip(engine_configuration_s *engineConfiguration, board_configuration_s *boardConfiguration);
+
+#endif /* HONDA_ACCORD_H_ */
diff --git a/firmware/config/engines/mazda_323.cpp b/firmware/config/engines/mazda_323.cpp
new file mode 100644
index 0000000000..571f9bc446
--- /dev/null
+++ b/firmware/config/engines/mazda_323.cpp
@@ -0,0 +1,20 @@
+/**
+ * @file mazda_323.cpp
+ *
+ * @date Mar 8, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "mazda_323.h"
+
+void setMazda323EngineConfiguration(engine_configuration_s *engineConfiguration) {
+ engineConfiguration->cylindersCount = 4;
+ engineConfiguration->displacement = 1.6;
+
+ engineConfiguration->ignitionMode = IM_ONE_COIL;
+
+ /**
+ * We treat the trigger as 4/0 toothed wheel
+ */
+// setToothedWheelConfiguration(engineConfiguration, 4, 0);
+}
diff --git a/firmware/config/engines/mazda_323.h b/firmware/config/engines/mazda_323.h
new file mode 100644
index 0000000000..fd2cd99653
--- /dev/null
+++ b/firmware/config/engines/mazda_323.h
@@ -0,0 +1,21 @@
+/**
+ * @file mazda_323.h
+ *
+ * 90-94 Mazda 323 (1.6l SOHC)
+ * 90-93 Ford Festiva (1.3l SOHC)
+ *
+ * http://rusefi.com/forum/viewtopic.php?f=3&t=498
+ *
+ * @date Mar 8, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef MAZDA_323_H_
+#define MAZDA_323_H_
+
+#include "main.h"
+#include "engine_configuration.h"
+
+void setMazda323EngineConfiguration(engine_configuration_s *engineConfiguration);
+
+#endif /* MAZDA_323_H_ */
diff --git a/firmware/config/engines/mazda_miata_nb.cpp b/firmware/config/engines/mazda_miata_nb.cpp
new file mode 100644
index 0000000000..1ee33e2273
--- /dev/null
+++ b/firmware/config/engines/mazda_miata_nb.cpp
@@ -0,0 +1,79 @@
+/**
+ * @file mazda_miata_nb.cpp
+ *
+ * MAZDA_MIATA_NB = 9
+ *
+ * @date Feb 18, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "mazda_miata_nb.h"
+#include "thermistors.h"
+
+void setMazdaMiataNbEngineConfiguration(engine_configuration_s *engineConfiguration,
+ board_configuration_s *boardConfiguration) {
+ // set_rpm_hard_limit 3000
+ engineConfiguration->rpmHardLimit = 3000; // yes, 3k. let's play it safe for now
+
+ engineConfiguration->triggerConfig.triggerType = TT_MAZDA_MIATA_NB;
+
+ engineConfiguration->globalTriggerAngleOffset = 276;
+
+ // set_cranking_injection_mode 0
+ engineConfiguration->crankingInjectionMode = IM_SIMULTANEOUS;
+ // set_injection_mode 1
+ engineConfiguration->injectionMode = IM_SEQUENTIAL;
+ // set_ignition_mode 2
+ engineConfiguration->ignitionMode = IM_WASTED_SPARK;
+ // set_firing_order 2
+ engineConfiguration->firingOrder = FO_1_THEN_3_THEN_4_THEN2;
+
+ setThermistorConfiguration(&engineConfiguration->cltThermistorConf, 0, 32500, 30, 7550, 100, 700);
+ engineConfiguration->cltThermistorConf.bias_resistor = 2700;
+
+ setThermistorConfiguration(&engineConfiguration->iatThermistorConf, -10, 160310, 60, 7700, 120.00, 1180);
+ engineConfiguration->iatThermistorConf.bias_resistor = 2700;
+
+ engineConfiguration->tpsAdcChannel = EFI_ADC_3; // 15 is the old value
+ engineConfiguration->vBattAdcChannel = EFI_ADC_0; // 1 is the old value
+// engineConfiguration->map.channel = 1;
+ engineConfiguration->mafAdcChannel = EFI_ADC_1;
+ engineConfiguration->cltAdcChannel = EFI_ADC_11;
+ engineConfiguration->iatAdcChannel = EFI_ADC_13;
+ engineConfiguration->afrSensor.afrAdcChannel = EFI_ADC_2;
+
+ boardConfiguration->idleValvePin = GPIOE_0;
+ boardConfiguration->idleValvePinMode = OM_DEFAULT;
+
+ boardConfiguration->fuelPumpPin = GPIOC_14; // Frankenstein: low side - inj #4
+ boardConfiguration->fuelPumpPinMode = OM_DEFAULT;
+
+ boardConfiguration->injectionPins[0] = GPIOB_9; // Frankenstein: low side - inj #12
+ boardConfiguration->injectionPins[1] = GPIOB_8; // Frankenstein: low side - inj #11
+ boardConfiguration->injectionPins[2] = GPIOE_3; // Frankenstein: low side - inj #8
+ boardConfiguration->injectionPins[3] = GPIOE_5; // Frankenstein: low side - inj #6
+ boardConfiguration->injectionPins[4] = GPIO_NONE;
+ boardConfiguration->injectionPins[5] = GPIO_NONE;
+ boardConfiguration->injectionPinMode = OM_DEFAULT;
+
+ boardConfiguration->ignitionPins[0] = GPIOE_10; // Frankenstein: high side #1
+ boardConfiguration->ignitionPins[1] = GPIO_NONE;
+ boardConfiguration->ignitionPins[2] = GPIOC_9; // // Frankenstein: high side #2
+ boardConfiguration->ignitionPins[3] = GPIO_NONE;
+ boardConfiguration->ignitionPinMode = OM_INVERTED;
+
+ boardConfiguration->malfunctionIndicatorPin = GPIOE_1;
+ boardConfiguration->malfunctionIndicatorPinMode = OM_DEFAULT;
+
+ boardConfiguration->fanPin = GPIOE_6;
+ boardConfiguration->fanPinMode = OM_DEFAULT;
+
+ boardConfiguration->electronicThrottlePin1 = GPIO_NONE;
+ boardConfiguration->idleSwitchPin = GPIO_NONE;
+
+ // set_whole_fuel_map 3
+ setWholeFuelMap(engineConfiguration, 3);
+
+
+// 10 deg before TDC is default timing
+}
diff --git a/firmware/config/engines/mazda_miata_nb.h b/firmware/config/engines/mazda_miata_nb.h
new file mode 100644
index 0000000000..fb9c088dec
--- /dev/null
+++ b/firmware/config/engines/mazda_miata_nb.h
@@ -0,0 +1,16 @@
+/**
+ * @file mazda_miata_nb.h
+ *
+ * @date Feb 18, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef MAZDA_MIATA_NB_H_
+#define MAZDA_MIATA_NB_H_
+
+#include "main.h"
+#include "engine_configuration.h"
+
+void setMazdaMiataNbEngineConfiguration(engine_configuration_s *engineConfiguration, board_configuration_s *boardConfiguration);
+
+#endif /* MAZDA_MIATA_NB_H_ */
diff --git a/firmware/config/engines/mitsubishi.cpp b/firmware/config/engines/mitsubishi.cpp
new file mode 100644
index 0000000000..ca3b4bfcad
--- /dev/null
+++ b/firmware/config/engines/mitsubishi.cpp
@@ -0,0 +1,97 @@
+/**
+ * @file mitsubishi.cpp
+ *
+ * MITSU_4G93 16
+ *
+ * @date Aug 5, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "mitsubishi.h"
+#include "allsensors.h"
+
+void setMitsubishiConfiguration(engine_configuration_s *engineConfiguration, board_configuration_s *boardConfiguration) {
+ engineConfiguration->engineType = MITSU_4G93;
+
+ engineConfiguration->triggerConfig.triggerType = TT_MITSU;
+
+ engineConfiguration->cylindersCount = 4;
+ engineConfiguration->displacement = 1.800;
+
+ // set_ignition_mode 2
+ engineConfiguration->ignitionMode = IM_WASTED_SPARK;
+ engineConfiguration->firingOrder = FO_1_THEN_3_THEN_4_THEN2;
+
+ // set_global_trigger_offset_angle 671
+ engineConfiguration->globalTriggerAngleOffset = 671;
+
+ // set_cranking_rpm 550
+ engineConfiguration->crankingSettings.crankingRpm = 550;
+ // set_cranking_charge_angle 70
+ engineConfiguration->crankingChargeAngle = 70;
+ // set_cranking_timing_angle 715
+ engineConfiguration->crankingTimingAngle = 715;
+
+ // set_whole_fuel_map 3
+ setWholeFuelMap(engineConfiguration, 3);
+
+ // since CLT is not wired up yet let's just use same value for min and max
+ // set_cranking_fuel_max 6 40
+ engineConfiguration->crankingSettings.coolantTempMaxC = 37.7; // 6ms at 37.7C
+ engineConfiguration->crankingSettings.fuelAtMaxTempMs = 6;
+
+ // set_cranking_fuel_min 6 -40
+ engineConfiguration->crankingSettings.coolantTempMinC = -40; // 6ms at -40C
+ engineConfiguration->crankingSettings.fuelAtMinTempMs = 6;
+
+// /**
+// * 29150 Ohm @ 40C
+// * 10160 Ohm @ 70C
+// * 1270 Ohm @ 150C
+// */
+// setThermistorConfiguration(&engineConfiguration->cltThermistorConf, 40, 29150, 70, 10160, 150, 1270);
+
+ /**
+ * 18K Ohm @ -20C
+ * 2.1K Ohm @ 24C
+ * 294 Ohm @ 80C
+ * http://www.rexbo.eu/hella/coolant-temperature-sensor-6pt009107121?c=100334&at=3130
+ */
+ setThermistorConfiguration(&engineConfiguration->cltThermistorConf, -20, 18000, 23.8889, 2100, 80, 294);
+
+ engineConfiguration->cltThermistorConf.bias_resistor = 2700;
+
+ // Frankenstein: low side - inj #1: PC14
+ // Frankenstein: low side - inj #2: PC15
+ // Frankenstein: low side - inj #3: PE6
+ // Frankenstein: low side - inj #4: PC13
+ // Frankenstein: low side - inj #5: PE4
+ // Frankenstein: low side - inj #6: PE5
+ // Frankenstein: low side - inj #7: PE2
+ // Frankenstein: low side - inj #8: PE3
+ // Frankenstein: low side - inj #9: PE0
+ // Frankenstein: low side - inj #10: PE1
+ // Frankenstein: low side - inj #11: PB8
+ // Frankenstein: low side - inj #12: PB9
+
+ boardConfiguration->injectionPins[0] = GPIOB_9; // Frankenstein: low side - inj #12
+ boardConfiguration->injectionPins[1] = GPIOB_8; // Frankenstein: low side - inj #11
+ boardConfiguration->injectionPins[2] = GPIOE_3; // Frankenstein: low side - inj #8
+ boardConfiguration->injectionPins[3] = GPIOE_5; // Frankenstein: low side - inj #6
+
+
+ // Frankenstein: high side #1: PE8
+ // Frankenstein: high side #2: PE10
+
+ boardConfiguration->ignitionPins[0] = GPIOE_8; // Frankenstein: high side #1
+ boardConfiguration->ignitionPins[1] = GPIO_NONE;
+ boardConfiguration->ignitionPins[2] = GPIOE_10; // // Frankenstein: high side #2
+ boardConfiguration->ignitionPins[3] = GPIO_NONE;
+
+ engineConfiguration->HD44780width = 20;
+ engineConfiguration->HD44780height = 4;
+
+ initEgoSensor(&engineConfiguration->afrSensor, ES_Innovate_MTX_L);
+}
+
+
diff --git a/firmware/config/engines/mitsubishi.h b/firmware/config/engines/mitsubishi.h
new file mode 100644
index 0000000000..653de9fdf2
--- /dev/null
+++ b/firmware/config/engines/mitsubishi.h
@@ -0,0 +1,16 @@
+/**
+ * @file mitsubishi.h
+ *
+ * @date Aug 5, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+#ifndef MITSUBISHI_H_
+#define MITSUBISHI_H_
+
+#include "main.h"
+
+#include "engine_configuration.h"
+
+void setMitsubishiConfiguration(engine_configuration_s *engineConfiguration, board_configuration_s *boardConfiguration);
+
+#endif /* MITSUBISHI_H_ */
diff --git a/firmware/config/engines/nissan_primera.cpp b/firmware/config/engines/nissan_primera.cpp
new file mode 100644
index 0000000000..b16e29ea26
--- /dev/null
+++ b/firmware/config/engines/nissan_primera.cpp
@@ -0,0 +1,19 @@
+/**
+ * @file nissan_primera.cpp
+ *
+ * engine_type 5
+ *
+ * @date Oct 14, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+
+#if EFI_SUPPORT_NISSAN_PRIMERA || defined(__DOXYGEN__)
+#include "nissan_primera.h"
+
+void setNissanPrimeraEngineConfiguration(engine_configuration_s *engineConfiguration) {
+ engineConfiguration->triggerConfig.triggerType = TT_TOOTHED_WHEEL_60_2;
+}
+
+#endif /* EFI_SUPPORT_NISSAN_PRIMERA */
diff --git a/firmware/config/engines/nissan_primera.h b/firmware/config/engines/nissan_primera.h
new file mode 100644
index 0000000000..005ead2e0c
--- /dev/null
+++ b/firmware/config/engines/nissan_primera.h
@@ -0,0 +1,21 @@
+/**
+ * @file nissan_primera.h
+ *
+ * @date Oct 14, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "efifeatures.h"
+
+#ifndef NISSAN_PRIMERA_H_
+#define NISSAN_PRIMERA_H_
+
+#if EFI_SUPPORT_NISSAN_PRIMERA
+
+#include "engine_configuration.h"
+
+void setNissanPrimeraEngineConfiguration(engine_configuration_s *engineConfiguration);
+
+#endif /* EFI_SUPPORT_NISSAN_PRIMERA */
+
+#endif /* NISSAN_PRIMERA_H_ */
diff --git a/firmware/config/engines/rover_v8.cpp b/firmware/config/engines/rover_v8.cpp
new file mode 100644
index 0000000000..e1cab7645f
--- /dev/null
+++ b/firmware/config/engines/rover_v8.cpp
@@ -0,0 +1,70 @@
+/**
+ * @file rover_v8.cpp
+ *
+ * V8, firing order 18436572
+ *
+ * This config overrides some values of the default configuration which is set by setDefaultConfiguration() method
+ *
+ * ROVER_V8 = 10
+ *
+ * @date Jun 27, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+#include "rover_v8.h"
+
+void setRoverv8(engine_configuration_s *engineConfiguration,
+ board_configuration_s *boardConfiguration) {
+
+ setOperationMode(engineConfiguration, FOUR_STROKE_CRANK_SENSOR);
+ engineConfiguration->triggerConfig.triggerType = TT_TOOTHED_WHEEL_36_1;
+
+ engineConfiguration->displacement = 3.528;
+ engineConfiguration->cylindersCount = 8;
+ engineConfiguration->firingOrder = FO_1_8_4_3_6_5_7_2;
+
+ // set_rpm_hard_limit 4000
+ engineConfiguration->rpmHardLimit = 4000; // yes, 4k. let's play it safe for now
+ // set_cranking_rpm 550
+ engineConfiguration->crankingSettings.crankingRpm = 550;
+
+ // set_whole_fuel_map 3
+ setWholeFuelMap(engineConfiguration, 3);
+
+
+ // set_cranking_injection_mode 0
+ engineConfiguration->crankingInjectionMode = IM_SIMULTANEOUS;
+ // set_injection_mode 1
+ engineConfiguration->injectionMode = IM_SEQUENTIAL;
+
+ // set_ignition_mode 2
+ engineConfiguration->ignitionMode = IM_WASTED_SPARK;
+
+
+ // Frankenstein: low side - inj #1: PC14
+ // Frankenstein: low side - inj #2: PC15
+ // Frankenstein: low side - inj #3: PE6
+ // Frankenstein: low side - inj #4: PC13
+ // Frankenstein: low side - inj #5: PE4
+ // Frankenstein: low side - inj #6: PE5
+ // Frankenstein: low side - inj #7: PE2
+ // Frankenstein: low side - inj #8: PE3
+ // Frankenstein: low side - inj #9: PE0
+ // Frankenstein: low side - inj #10: PE1
+ // Frankenstein: low side - inj #11: PB8
+ // Frankenstein: low side - inj #12: PB9
+
+ boardConfiguration->injectionPins[0] = GPIOB_9; // Frankenstein: low side - inj #12
+ boardConfiguration->injectionPins[1] = GPIOB_8; // Frankenstein: low side - inj #11
+ boardConfiguration->injectionPins[2] = GPIOE_3; // Frankenstein: low side - inj #8
+ boardConfiguration->injectionPins[3] = GPIOE_5; // Frankenstein: low side - inj #6
+
+ boardConfiguration->fuelPumpPin = GPIOC_13; // Frankenstein: low side - inj #4
+ boardConfiguration->fuelPumpPinMode = OM_DEFAULT;
+
+ // set_injection_pin_mode 0
+ boardConfiguration->injectionPinMode = OM_DEFAULT;
+
+
+}
diff --git a/firmware/config/engines/rover_v8.h b/firmware/config/engines/rover_v8.h
new file mode 100644
index 0000000000..30a3f6f319
--- /dev/null
+++ b/firmware/config/engines/rover_v8.h
@@ -0,0 +1,15 @@
+/**
+ * @file rover_v8.h
+ *
+ * @date Jun 27, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+#ifndef ROVER_V8_H_
+#define ROVER_V8_H_
+
+#include "engine_configuration.h"
+
+void setRoverv8(engine_configuration_s *engineConfiguration,
+ board_configuration_s *boardConfiguration);
+
+#endif /* ROVER_V8_H_ */
diff --git a/firmware/config/engines/saturn_ion.cpp b/firmware/config/engines/saturn_ion.cpp
new file mode 100644
index 0000000000..482cd338a5
--- /dev/null
+++ b/firmware/config/engines/saturn_ion.cpp
@@ -0,0 +1,17 @@
+/**
+ * @file saturn_ion.cpp
+ *
+ * SATURN_ION_2004 = 12
+ *
+ * 7x GM DIS trigger
+ *
+ * @date Mar 28, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "saturn_ion.h"
+
+void setSaturnIonEngineConfiguration(engine_configuration_s *engineConfiguration) {
+ engineConfiguration->triggerConfig.triggerType = TT_GM_7X;
+
+}
diff --git a/firmware/config/engines/saturn_ion.h b/firmware/config/engines/saturn_ion.h
new file mode 100644
index 0000000000..eac1a3ef2d
--- /dev/null
+++ b/firmware/config/engines/saturn_ion.h
@@ -0,0 +1,25 @@
+/**
+ * @file saturn_ion.h
+ *
+ * @date Mar 28, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef SATURN_ION_H_
+#define SATURN_ION_H_
+
+#include "engine_configuration.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void setSaturnIonEngineConfiguration(engine_configuration_s *engineConfiguration);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+
+#endif /* SATURN_ION_H_ */
diff --git a/firmware/config/engines/snow_blower.cpp b/firmware/config/engines/snow_blower.cpp
new file mode 100644
index 0000000000..71ca161a6a
--- /dev/null
+++ b/firmware/config/engines/snow_blower.cpp
@@ -0,0 +1,9 @@
+/**
+ * @file snow_blower.cpp
+ * @brief Default configuration of a single-cylinder engine
+ *
+ * @date Sep 9, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
diff --git a/firmware/config/engines/snow_blower.h b/firmware/config/engines/snow_blower.h
new file mode 100644
index 0000000000..f37596356d
--- /dev/null
+++ b/firmware/config/engines/snow_blower.h
@@ -0,0 +1,29 @@
+/**
+ * @file snow_blower.h
+ * @brief Default configuration of a single-cylinder engine
+
+ * @date Sep 8, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef SNOW_BLOWER_H_
+#define SNOW_BLOWER_H_
+
+#if EFI_ENGINE_SNOW_BLOWER
+
+#define NUMBER_OF_CYLINDERS 1
+
+#define EFI_ENGINE_ID "Snow Blower"
+
+#define RPM_MULT (1)
+
+#define getCoolantTemperature() 0
+#define getIntakeAirTemperature() 0
+
+#define getMaf() 0
+#define getTPS() 0
+#define getAfr() 0
+
+#endif /* EFI_ENGINE_SNOW_BLOWER */
+
+#endif /* SNOW_BLOWER_H_ */
diff --git a/firmware/config/system/STM32F407xG_CCM.ld b/firmware/config/system/STM32F407xG_CCM.ld
new file mode 100644
index 0000000000..f235150f52
--- /dev/null
+++ b/firmware/config/system/STM32F407xG_CCM.ld
@@ -0,0 +1,186 @@
+/*
+ ChibiOS/RT - Copyright (C) 2006,2007,2008,2009,2010,
+ 2011,2012,2013 Giovanni Di Sirio.
+
+ This file is part of ChibiOS/RT.
+
+ ChibiOS/RT is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ ChibiOS/RT is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+ ---
+
+ A special exception to the GPL can be applied should you wish to distribute
+ a combined work that includes ChibiOS/RT, without being obliged to provide
+ the source code for any proprietary components. See the file exception.txt
+ for full details of how and when the exception can be applied.
+*/
+
+/*
+ * ST32F407xG memory setup.
+ */
+__main_stack_size__ = 0x0600;
+__process_stack_size__ = 0x0600;
+
+MEMORY
+{
+ flash : org = 0x08000000, len = 1M
+ ram : org = 0x20000000, len = 112k
+ ethram : org = 0x2001C000, len = 16k
+ ccmram : org = 0x10000000, len = 64k
+}
+
+__ram_start__ = ORIGIN(ram);
+__ram_size__ = LENGTH(ram);
+__ram_end__ = __ram_start__ + __ram_size__;
+
+ENTRY(ResetHandler)
+
+SECTIONS
+{
+ . = 0;
+ _text = .;
+
+ startup : ALIGN(16) SUBALIGN(16)
+ {
+ KEEP(*(vectors))
+ } > flash
+
+ constructors : ALIGN(4) SUBALIGN(4)
+ {
+ PROVIDE(__init_array_start = .);
+ KEEP(*(SORT(.init_array.*)))
+ KEEP(*(.init_array))
+ PROVIDE(__init_array_end = .);
+ } > flash
+
+ destructors : ALIGN(4) SUBALIGN(4)
+ {
+ PROVIDE(__fini_array_start = .);
+ KEEP(*(.fini_array))
+ KEEP(*(SORT(.fini_array.*)))
+ PROVIDE(__fini_array_end = .);
+ } > flash
+
+ .text : ALIGN(16) SUBALIGN(16)
+ {
+ *(.text.startup.*)
+ *(.text)
+ *(.text.*)
+ *(.rodata)
+ *(.rodata.*)
+ *(.glue_7t)
+ *(.glue_7)
+ *(.gcc*)
+ } > flash
+
+ .ARM.extab :
+ {
+ *(.ARM.extab* .gnu.linkonce.armextab.*)
+ } > flash
+
+ .ARM.exidx : {
+ PROVIDE(__exidx_start = .);
+ *(.ARM.exidx* .gnu.linkonce.armexidx.*)
+ PROVIDE(__exidx_end = .);
+ } > flash
+
+ .eh_frame_hdr :
+ {
+ *(.eh_frame_hdr)
+ } > flash
+
+ .eh_frame : ONLY_IF_RO
+ {
+ *(.eh_frame)
+ } > flash
+
+ .textalign : ONLY_IF_RO
+ {
+ . = ALIGN(8);
+ } > flash
+
+ _etext = .;
+ _textdata = _etext;
+
+ .stacks :
+ {
+ . = ALIGN(8);
+ __main_stack_base__ = .;
+ . += __main_stack_size__;
+ . = ALIGN(8);
+ __main_stack_end__ = .;
+ __process_stack_base__ = .;
+ __main_thread_stack_base__ = .;
+ . += __process_stack_size__;
+ . = ALIGN(8);
+ __process_stack_end__ = .;
+ __main_thread_stack_end__ = .;
+ } > ram
+
+ .ccm (NOLOAD) :
+ {
+ PROVIDE(_cmm_start = .);
+ . = ALIGN(4);
+ *(.bss.mainthread.*)
+ . = ALIGN(4);
+ *(.bss._idle_thread_wa)
+ . = ALIGN(4);
+ *(.bss.rlist)
+ . = ALIGN(4);
+ *(.bss.vtlist)
+ . = ALIGN(4);
+ *(.bss.endmem)
+ . = ALIGN(4);
+ *(.bss.nextmem)
+ . = ALIGN(4);
+ *(.bss.default_heap)
+ . = ALIGN(4);
+ *(.ccm)
+ . = ALIGN(4);
+ *(.ccm.*)
+ . = ALIGN(4);
+ PROVIDE(_cmm_end = .);
+ } > ccmram
+
+ .data :
+ {
+ . = ALIGN(4);
+ PROVIDE(_data = .);
+ *(.data)
+ . = ALIGN(4);
+ *(.data.*)
+ . = ALIGN(4);
+ *(.ramtext)
+ . = ALIGN(4);
+ PROVIDE(_edata = .);
+ } > ram AT > flash
+
+ .bss :
+ {
+ . = ALIGN(4);
+ PROVIDE(_bss_start = .);
+ *(.bss)
+ . = ALIGN(4);
+ *(.bss.*)
+ . = ALIGN(4);
+ *(COMMON)
+ . = ALIGN(4);
+ PROVIDE(_bss_end = .);
+ } > ram
+}
+
+PROVIDE(end = .);
+_end = .;
+
+__heap_base__ = _end;
+__heap_end__ = __ram_end__;
diff --git a/firmware/config/system/chconf.h b/firmware/config/system/chconf.h
new file mode 100644
index 0000000000..052139d75b
--- /dev/null
+++ b/firmware/config/system/chconf.h
@@ -0,0 +1,575 @@
+/*
+ ChibiOS/RT - Copyright (C) 2006,2007,2008,2009,2010,
+ 2011,2012 Giovanni Di Sirio.
+
+ This file is part of ChibiOS/RT.
+
+ ChibiOS/RT is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ ChibiOS/RT is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+ ---
+
+ A special exception to the GPL can be applied should you wish to distribute
+ a combined work that includes ChibiOS/RT, without being obliged to provide
+ the source code for any proprietary components. See the file exception.txt
+ for full details of how and when the exception can be applied.
+*/
+
+/**
+ * @file templates/chconf.h
+ * @brief Configuration file template.
+ * @details A copy of this file must be placed in each project directory, it
+ * contains the application specific kernel settings.
+ *
+ * @addtogroup config
+ * @details Kernel related settings and hooks.
+ * @{
+ */
+
+#ifndef _CHCONF_H_
+#define _CHCONF_H_
+
+#define chDbgCheck(c, func) { \
+ if (!(c)) \
+ chDbgPanic3(__QUOTE_THIS(func)"()", __FILE__, __LINE__); \
+}
+
+
+#define PORT_IDLE_THREAD_STACK_SIZE 1024
+
+#define PORT_INT_REQUIRED_STACK 768
+
+#define CHPRINTF_USE_FLOAT TRUE
+
+/**
+ * number of ticks per second
+ *
+ * 8000 rpm equals 133Hz of crankshaft
+ * that's 266Hz camshaft
+ * for timing measures we need 95760 Hz precision
+ */
+#define CH_FREQUENCY 1000
+
+/*===========================================================================*/
+/**
+ * @name Kernel parameters and options
+ * @{
+ */
+/*===========================================================================*/
+
+/**
+ * @brief System tick frequency.
+ * @details Frequency of the system timer that drives the system ticks. This
+ * setting also defines the system tick time unit.
+ */
+#if !defined(CH_FREQUENCY) || defined(__DOXYGEN__)
+#define CH_FREQUENCY 1000
+#endif
+
+/**
+ * @brief Round robin interval.
+ * @details This constant is the number of system ticks allowed for the
+ * threads before preemption occurs. Setting this value to zero
+ * disables the preemption for threads with equal priority and the
+ * round robin becomes cooperative. Note that higher priority
+ * threads can still preempt, the kernel is always preemptive.
+ *
+ * @note Disabling the round robin preemption makes the kernel more compact
+ * and generally faster.
+ */
+#if !defined(CH_TIME_QUANTUM) || defined(__DOXYGEN__)
+#define CH_TIME_QUANTUM 20
+#endif
+
+/**
+ * @brief Managed RAM size.
+ * @details Size of the RAM area to be managed by the OS. If set to zero
+ * then the whole available RAM is used. The core memory is made
+ * available to the heap allocator and/or can be used directly through
+ * the simplified core memory allocator.
+ *
+ * @note In order to let the OS manage the whole RAM the linker script must
+ * provide the @p __heap_base__ and @p __heap_end__ symbols.
+ * @note Requires @p CH_USE_MEMCORE.
+ */
+#if !defined(CH_MEMCORE_SIZE) || defined(__DOXYGEN__)
+#define CH_MEMCORE_SIZE 0
+#endif
+
+/**
+ * @brief Idle thread automatic spawn suppression.
+ * @details When this option is activated the function @p chSysInit()
+ * does not spawn the idle thread automatically. The application has
+ * then the responsibility to do one of the following:
+ * - Spawn a custom idle thread at priority @p IDLEPRIO.
+ * - Change the main() thread priority to @p IDLEPRIO then enter
+ * an endless loop. In this scenario the @p main() thread acts as
+ * the idle thread.
+ * .
+ * @note Unless an idle thread is spawned the @p main() thread must not
+ * enter a sleep state.
+ */
+#if !defined(CH_NO_IDLE_THREAD) || defined(__DOXYGEN__)
+#define CH_NO_IDLE_THREAD FALSE
+#endif
+
+/** @} */
+
+/*===========================================================================*/
+/**
+ * @name Performance options
+ * @{
+ */
+/*===========================================================================*/
+
+/**
+ * @brief OS optimization.
+ * @details If enabled then time efficient rather than space efficient code
+ * is used when two possible implementations exist.
+ *
+ * @note This is not related to the compiler optimization options.
+ * @note The default is @p TRUE.
+ */
+#if !defined(CH_OPTIMIZE_SPEED) || defined(__DOXYGEN__)
+#define CH_OPTIMIZE_SPEED TRUE
+#endif
+
+/** @} */
+
+/*===========================================================================*/
+/**
+ * @name Subsystem options
+ * @{
+ */
+/*===========================================================================*/
+
+/**
+ * @brief Threads registry APIs.
+ * @details If enabled then the registry APIs are included in the kernel.
+ *
+ * @note The default is @p TRUE.
+ */
+#if !defined(CH_USE_REGISTRY) || defined(__DOXYGEN__)
+#define CH_USE_REGISTRY TRUE
+#endif
+
+/**
+ * @brief Threads synchronization APIs.
+ * @details If enabled then the @p chThdWait() function is included in
+ * the kernel.
+ *
+ * @note The default is @p TRUE.
+ */
+#if !defined(CH_USE_WAITEXIT) || defined(__DOXYGEN__)
+#define CH_USE_WAITEXIT TRUE
+#endif
+
+/**
+ * @brief Semaphores APIs.
+ * @details If enabled then the Semaphores APIs are included in the kernel.
+ *
+ * @note The default is @p TRUE.
+ */
+#if !defined(CH_USE_SEMAPHORES) || defined(__DOXYGEN__)
+#define CH_USE_SEMAPHORES TRUE
+#endif
+
+/**
+ * @brief Semaphores queuing mode.
+ * @details If enabled then the threads are enqueued on semaphores by
+ * priority rather than in FIFO order.
+ *
+ * @note The default is @p FALSE. Enable this if you have special requirements.
+ * @note Requires @p CH_USE_SEMAPHORES.
+ */
+#if !defined(CH_USE_SEMAPHORES_PRIORITY) || defined(__DOXYGEN__)
+#define CH_USE_SEMAPHORES_PRIORITY FALSE
+#endif
+
+/**
+ * @brief Atomic semaphore API.
+ * @details If enabled then the semaphores the @p chSemSignalWait() API
+ * is included in the kernel.
+ *
+ * @note The default is @p TRUE.
+ * @note Requires @p CH_USE_SEMAPHORES.
+ */
+#if !defined(CH_USE_SEMSW) || defined(__DOXYGEN__)
+#define CH_USE_SEMSW TRUE
+#endif
+
+/**
+ * @brief Mutexes APIs.
+ * @details If enabled then the mutexes APIs are included in the kernel.
+ *
+ * @note The default is @p TRUE.
+ */
+#if !defined(CH_USE_MUTEXES) || defined(__DOXYGEN__)
+#define CH_USE_MUTEXES TRUE
+#endif
+
+/**
+ * @brief Conditional Variables APIs.
+ * @details If enabled then the conditional variables APIs are included
+ * in the kernel.
+ *
+ * @note The default is @p TRUE.
+ * @note Requires @p CH_USE_MUTEXES.
+ */
+#if !defined(CH_USE_CONDVARS) || defined(__DOXYGEN__)
+#define CH_USE_CONDVARS TRUE
+#endif
+
+/**
+ * @brief Conditional Variables APIs with timeout.
+ * @details If enabled then the conditional variables APIs with timeout
+ * specification are included in the kernel.
+ *
+ * @note The default is @p TRUE.
+ * @note Requires @p CH_USE_CONDVARS.
+ */
+#if !defined(CH_USE_CONDVARS_TIMEOUT) || defined(__DOXYGEN__)
+#define CH_USE_CONDVARS_TIMEOUT TRUE
+#endif
+
+/**
+ * @brief Events Flags APIs.
+ * @details If enabled then the event flags APIs are included in the kernel.
+ *
+ * @note The default is @p TRUE.
+ */
+#if !defined(CH_USE_EVENTS) || defined(__DOXYGEN__)
+#define CH_USE_EVENTS TRUE
+#endif
+
+/**
+ * @brief Events Flags APIs with timeout.
+ * @details If enabled then the events APIs with timeout specification
+ * are included in the kernel.
+ *
+ * @note The default is @p TRUE.
+ * @note Requires @p CH_USE_EVENTS.
+ */
+#if !defined(CH_USE_EVENTS_TIMEOUT) || defined(__DOXYGEN__)
+#define CH_USE_EVENTS_TIMEOUT TRUE
+#endif
+
+/**
+ * @brief Synchronous Messages APIs.
+ * @details If enabled then the synchronous messages APIs are included
+ * in the kernel.
+ *
+ * @note The default is @p TRUE.
+ */
+#if !defined(CH_USE_MESSAGES) || defined(__DOXYGEN__)
+#define CH_USE_MESSAGES TRUE
+#endif
+
+/**
+ * @brief Synchronous Messages queuing mode.
+ * @details If enabled then messages are served by priority rather than in
+ * FIFO order.
+ *
+ * @note The default is @p FALSE. Enable this if you have special requirements.
+ * @note Requires @p CH_USE_MESSAGES.
+ */
+#if !defined(CH_USE_MESSAGES_PRIORITY) || defined(__DOXYGEN__)
+#define CH_USE_MESSAGES_PRIORITY FALSE
+#endif
+
+/**
+ * @brief Mailboxes APIs.
+ * @details If enabled then the asynchronous messages (mailboxes) APIs are
+ * included in the kernel.
+ *
+ * @note The default is @p TRUE.
+ * @note Requires @p CH_USE_SEMAPHORES.
+ */
+#if !defined(CH_USE_MAILBOXES) || defined(__DOXYGEN__)
+#define CH_USE_MAILBOXES TRUE
+#endif
+
+/**
+ * @brief I/O Queues APIs.
+ * @details If enabled then the I/O queues APIs are included in the kernel.
+ *
+ * @note The default is @p TRUE.
+ */
+#if !defined(CH_USE_QUEUES) || defined(__DOXYGEN__)
+#define CH_USE_QUEUES TRUE
+#endif
+
+/**
+ * @brief Core Memory Manager APIs.
+ * @details If enabled then the core memory manager APIs are included
+ * in the kernel.
+ *
+ * @note The default is @p TRUE.
+ */
+#if !defined(CH_USE_MEMCORE) || defined(__DOXYGEN__)
+#define CH_USE_MEMCORE TRUE
+#endif
+
+/**
+ * @brief Heap Allocator APIs.
+ * @details If enabled then the memory heap allocator APIs are included
+ * in the kernel.
+ *
+ * @note The default is @p TRUE.
+ * @note Requires @p CH_USE_MEMCORE and either @p CH_USE_MUTEXES or
+ * @p CH_USE_SEMAPHORES.
+ * @note Mutexes are recommended.
+ */
+#if !defined(CH_USE_HEAP) || defined(__DOXYGEN__)
+#define CH_USE_HEAP TRUE
+#endif
+
+/**
+ * @brief C-runtime allocator.
+ * @details If enabled the the heap allocator APIs just wrap the C-runtime
+ * @p malloc() and @p free() functions.
+ *
+ * @note The default is @p FALSE.
+ * @note Requires @p CH_USE_HEAP.
+ * @note The C-runtime may or may not require @p CH_USE_MEMCORE, see the
+ * appropriate documentation.
+ */
+#if !defined(CH_USE_MALLOC_HEAP) || defined(__DOXYGEN__)
+#define CH_USE_MALLOC_HEAP FALSE
+#endif
+
+/**
+ * @brief Memory Pools Allocator APIs.
+ * @details If enabled then the memory pools allocator APIs are included
+ * in the kernel.
+ *
+ * @note The default is @p TRUE.
+ */
+#if !defined(CH_USE_MEMPOOLS) || defined(__DOXYGEN__)
+#define CH_USE_MEMPOOLS TRUE
+#endif
+
+/**
+ * @brief Dynamic Threads APIs.
+ * @details If enabled then the dynamic threads creation APIs are included
+ * in the kernel.
+ *
+ * @note The default is @p TRUE.
+ * @note Requires @p CH_USE_WAITEXIT.
+ * @note Requires @p CH_USE_HEAP and/or @p CH_USE_MEMPOOLS.
+ */
+#if !defined(CH_USE_DYNAMIC) || defined(__DOXYGEN__)
+#define CH_USE_DYNAMIC TRUE
+#endif
+
+/** @} */
+
+/*===========================================================================*/
+/**
+ * @name Debug options
+ * @{
+ */
+/*===========================================================================*/
+
+/**
+ * @brief Debug option, system state check.
+ * @details If enabled the correct call protocol for system APIs is checked
+ * at runtime.
+ *
+ * @note The default is @p FALSE.
+ */
+#if !defined(CH_DBG_SYSTEM_STATE_CHECK) || defined(__DOXYGEN__)
+#define CH_DBG_SYSTEM_STATE_CHECK TRUE
+#endif
+
+/**
+ * @brief Debug option, parameters checks.
+ * @details If enabled then the checks on the API functions input
+ * parameters are activated.
+ *
+ * @note The default is @p FALSE.
+ */
+#if !defined(CH_DBG_ENABLE_CHECKS) || defined(__DOXYGEN__)
+#define CH_DBG_ENABLE_CHECKS TRUE
+#endif
+
+/**
+ * @brief Debug option, consistency checks.
+ * @details If enabled then all the assertions in the kernel code are
+ * activated. This includes consistency checks inside the kernel,
+ * runtime anomalies and port-defined checks.
+ *
+ * @note The default is @p FALSE.
+ */
+#if !defined(CH_DBG_ENABLE_ASSERTS) || defined(__DOXYGEN__)
+#define CH_DBG_ENABLE_ASSERTS TRUE
+#endif
+
+/**
+ * @brief Debug option, trace buffer.
+ * @details If enabled then the context switch circular trace buffer is
+ * activated.
+ *
+ * @note The default is @p FALSE.
+ */
+#if !defined(CH_DBG_ENABLE_TRACE) || defined(__DOXYGEN__)
+#define CH_DBG_ENABLE_TRACE FALSE
+#endif
+
+/**
+ * @brief Debug option, stack checks.
+ * @details If enabled then a runtime stack check is performed.
+ *
+ * @note The default is @p FALSE.
+ * @note The stack check is performed in a architecture/port dependent way.
+ * It may not be implemented or some ports.
+ * @note The default failure mode is to halt the system with the global
+ * @p panic_msg variable set to @p NULL.
+ */
+#if !defined(CH_DBG_ENABLE_STACK_CHECK) || defined(__DOXYGEN__)
+#define CH_DBG_ENABLE_STACK_CHECK TRUE
+#endif
+
+/**
+ * @brief Debug option, stacks initialization.
+ * @details If enabled then the threads working area is filled with a byte
+ * value when a thread is created. This can be useful for the
+ * runtime measurement of the used stack.
+ *
+ * @note The default is @p FALSE.
+ */
+#if !defined(CH_DBG_FILL_THREADS) || defined(__DOXYGEN__)
+#define CH_DBG_FILL_THREADS FALSE
+#endif
+
+/**
+ * @brief Debug option, threads profiling.
+ * @details If enabled then a field is added to the @p Thread structure that
+ * counts the system ticks occurred while executing the thread.
+ *
+ * @note The default is @p TRUE.
+ * @note This debug option is defaulted to TRUE because it is required by
+ * some test cases into the test suite.
+ */
+#if !defined(CH_DBG_THREADS_PROFILING) || defined(__DOXYGEN__)
+#define CH_DBG_THREADS_PROFILING TRUE
+#endif
+
+/** @} */
+
+/*===========================================================================*/
+/**
+ * @name Kernel hooks
+ * @{
+ */
+/*===========================================================================*/
+
+/**
+ * @brief Threads descriptor structure extension.
+ * @details User fields added to the end of the @p Thread structure.
+ */
+#if !defined(THREAD_EXT_FIELDS) || defined(__DOXYGEN__)
+#define THREAD_EXT_FIELDS \
+ void *activeStack; \
+ int remainingStack; \
+ /* Add threads custom fields here.*/
+#endif
+
+/**
+ * @brief Threads initialization hook.
+ * @details User initialization code added to the @p chThdInit() API.
+ *
+ * @note It is invoked from within @p chThdInit() and implicitly from all
+ * the threads creation APIs.
+ */
+#if !defined(THREAD_EXT_INIT_HOOK) || defined(__DOXYGEN__)
+#define THREAD_EXT_INIT_HOOK(tp) { \
+ /* Add threads initialization code here.*/ \
+}
+#endif
+
+/**
+ * @brief Threads finalization hook.
+ * @details User finalization code added to the @p chThdExit() API.
+ *
+ * @note It is inserted into lock zone.
+ * @note It is also invoked when the threads simply return in order to
+ * terminate.
+ */
+#if !defined(THREAD_EXT_EXIT_HOOK) || defined(__DOXYGEN__)
+#define THREAD_EXT_EXIT_HOOK(tp) { \
+ /* Add threads finalization code here.*/ \
+}
+#endif
+
+/**
+ * @brief Context switch hook.
+ * @details This hook is invoked just before switching between threads.
+ */
+#if !defined(THREAD_CONTEXT_SWITCH_HOOK) || defined(__DOXYGEN__)
+#define THREAD_CONTEXT_SWITCH_HOOK(ntp, otp) { \
+ /* System halt code here.*/ \
+}
+#endif
+
+/**
+ * @brief Idle Loop hook.
+ * @details This hook is continuously invoked by the idle thread loop.
+ */
+#if !defined(IDLE_LOOP_HOOK) || defined(__DOXYGEN__)
+#define IDLE_LOOP_HOOK() { \
+ /* Idle loop code here.*/ \
+}
+#endif
+
+/**
+ * @brief System tick event hook.
+ * @details This hook is invoked in the system tick handler immediately
+ * after processing the virtual timers queue.
+ */
+#if !defined(SYSTEM_TICK_EVENT_HOOK) || defined(__DOXYGEN__)
+#define SYSTEM_TICK_EVENT_HOOK() { \
+ /* System tick event code here.*/ \
+}
+#endif
+
+/**
+ * @brief System halt hook.
+ * @details This hook is invoked in case to a system halting error before
+ * the system is halted.
+ */
+#if !defined(SYSTEM_HALT_HOOK) || defined(__DOXYGEN__)
+#if CH_DBG_ENABLED
+
+#define SYSTEM_HALT_HOOK() { \
+ print("FATAL %s\r\n", dbg_panic_msg); \
+ chThdSleepMilliseconds(100); \
+/* System halt code here.*/ \
+
+}
+#endif
+
+#endif
+
+
+/** @} */
+
+/*===========================================================================*/
+/* Port-specific settings (override port settings defaulted in chcore.h). */
+/*===========================================================================*/
+
+#define CORTEX_USE_FPU TRUE
+
+#endif /* _CHCONF_H_ */
+
+/** @} */
diff --git a/firmware/config/system/halconf.h b/firmware/config/system/halconf.h
new file mode 100644
index 0000000000..cb9bfc666f
--- /dev/null
+++ b/firmware/config/system/halconf.h
@@ -0,0 +1,349 @@
+/*
+ ChibiOS/RT - Copyright (C) 2006,2007,2008,2009,2010,
+ 2011,2012 Giovanni Di Sirio.
+
+ This file is part of ChibiOS/RT.
+
+ ChibiOS/RT is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ ChibiOS/RT is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+ ---
+
+ A special exception to the GPL can be applied should you wish to distribute
+ a combined work that includes ChibiOS/RT, without being obliged to provide
+ the source code for any proprietary components. See the file exception.txt
+ for full details of how and when the exception can be applied.
+*/
+
+/**
+ * @file templates/halconf.h
+ * @brief HAL configuration header.
+ * @details HAL configuration file, this file allows to enable or disable the
+ * various device drivers from your application. You may also use
+ * this file in order to override the device drivers default settings.
+ *
+ * @addtogroup HAL_CONF
+ * @{
+ */
+
+#ifndef _HALCONF_H_
+#define _HALCONF_H_
+
+#include "mcuconf.h"
+
+/**
+ * @brief Enables the TM subsystem.
+ */
+#if !defined(HAL_USE_TM) || defined(__DOXYGEN__)
+#define HAL_USE_TM FALSE
+#endif
+
+/**
+ * @brief Enables the PAL subsystem.
+ */
+#if !defined(HAL_USE_PAL) || defined(__DOXYGEN__)
+#define HAL_USE_PAL TRUE
+#endif
+
+/**
+ * @brief Enables the ADC subsystem.
+ */
+#if !defined(HAL_USE_ADC) || defined(__DOXYGEN__)
+#define HAL_USE_ADC TRUE
+#endif
+
+/**
+ * @brief Enables the CAN subsystem.
+ */
+#if !defined(HAL_USE_CAN) || defined(__DOXYGEN__)
+#define HAL_USE_CAN TRUE
+#endif
+
+/**
+ * @brief Enables the EXT subsystem.
+ */
+#if !defined(HAL_USE_EXT) || defined(__DOXYGEN__)
+#define HAL_USE_EXT FALSE
+#endif
+
+/**
+ * @brief Enables the GPT subsystem.
+ */
+#if !defined(HAL_USE_GPT) || defined(__DOXYGEN__)
+#define HAL_USE_GPT TRUE
+#endif
+
+/**
+ * @brief Enables the I2C subsystem.
+ */
+#if !defined(HAL_USE_I2C) || defined(__DOXYGEN__)
+#define HAL_USE_I2C TRUE
+#endif
+
+/**
+ * @brief Enables the ICU subsystem.
+ */
+#if !defined(HAL_USE_ICU) || defined(__DOXYGEN__)
+#define HAL_USE_ICU TRUE
+#endif
+
+/**
+ * @brief Enables the MAC subsystem.
+ */
+#if !defined(HAL_USE_MAC) || defined(__DOXYGEN__)
+#define HAL_USE_MAC FALSE
+#endif
+
+/**
+ * @brief Enables the MMC_SPI subsystem.
+ */
+#if !defined(HAL_USE_MMC_SPI) || defined(__DOXYGEN__)
+#define HAL_USE_MMC_SPI TRUE
+#endif
+
+/**
+ * @brief Enables the PWM subsystem.
+ */
+#if !defined(HAL_USE_PWM) || defined(__DOXYGEN__)
+#define HAL_USE_PWM TRUE
+#endif
+
+/**
+ * @brief Enables the RTC subsystem.
+ */
+#if !defined(HAL_USE_RTC) || defined(__DOXYGEN__)
+#define HAL_USE_RTC TRUE
+#endif
+
+/**
+ * @brief Enables the SDC subsystem.
+ */
+#if !defined(HAL_USE_SDC) || defined(__DOXYGEN__)
+#define HAL_USE_SDC FALSE
+#endif
+
+/**
+ * @brief Enables the SERIAL subsystem.
+ */
+#if !defined(HAL_USE_SERIAL) || defined(__DOXYGEN__)
+#define HAL_USE_SERIAL TRUE
+#endif
+
+/**
+ * @brief Enables the SERIAL over USB subsystem.
+ */
+#if !defined(HAL_USE_SERIAL_USB) || defined(__DOXYGEN__)
+#define HAL_USE_SERIAL_USB TRUE
+#endif
+
+/**
+ * @brief Enables the SPI subsystem.
+ */
+#if !defined(HAL_USE_SPI) || defined(__DOXYGEN__)
+#define HAL_USE_SPI TRUE
+#endif
+
+/**
+ * @brief Enables the UART subsystem.
+ */
+#if !defined(HAL_USE_UART) || defined(__DOXYGEN__)
+#define HAL_USE_UART FALSE
+#endif
+
+/**
+ * @brief Enables the USB subsystem.
+ */
+#if !defined(HAL_USE_USB) || defined(__DOXYGEN__)
+#define HAL_USE_USB TRUE
+#endif
+
+/*===========================================================================*/
+/* ADC driver related settings. */
+/*===========================================================================*/
+
+/**
+ * @brief Enables synchronous APIs.
+ * @note Disabling this option saves both code and data space.
+ */
+#if !defined(ADC_USE_WAIT) || defined(__DOXYGEN__)
+#define ADC_USE_WAIT TRUE
+#endif
+
+/**
+ * @brief Enables the @p adcAcquireBus() and @p adcReleaseBus() APIs.
+ * @note Disabling this option saves both code and data space.
+ */
+#if !defined(ADC_USE_MUTUAL_EXCLUSION) || defined(__DOXYGEN__)
+#define ADC_USE_MUTUAL_EXCLUSION TRUE
+#endif
+
+/*===========================================================================*/
+/* CAN driver related settings. */
+/*===========================================================================*/
+
+/**
+ * @brief Sleep mode related APIs inclusion switch.
+ */
+#if !defined(CAN_USE_SLEEP_MODE) || defined(__DOXYGEN__)
+#define CAN_USE_SLEEP_MODE TRUE
+#endif
+
+/*===========================================================================*/
+/* I2C driver related settings. */
+/*===========================================================================*/
+
+/**
+ * @brief Enables the mutual exclusion APIs on the I2C bus.
+ */
+#if !defined(I2C_USE_MUTUAL_EXCLUSION) || defined(__DOXYGEN__)
+#define I2C_USE_MUTUAL_EXCLUSION TRUE
+#endif
+
+/*===========================================================================*/
+/* MAC driver related settings. */
+/*===========================================================================*/
+
+/**
+ * @brief Enables an event sources for incoming packets.
+ */
+#if !defined(MAC_USE_EVENTS) || defined(__DOXYGEN__)
+#define MAC_USE_EVENTS TRUE
+#endif
+
+/*===========================================================================*/
+/* MMC_SPI driver related settings. */
+/*===========================================================================*/
+
+/**
+ * @brief Block size for MMC transfers.
+ */
+#if !defined(MMC_SECTOR_SIZE) || defined(__DOXYGEN__)
+#define MMC_SECTOR_SIZE 512
+#endif
+
+/**
+ * @brief Delays insertions.
+ * @details If enabled this options inserts delays into the MMC waiting
+ * routines releasing some extra CPU time for the threads with
+ * lower priority, this may slow down the driver a bit however.
+ * This option is recommended also if the SPI driver does not
+ * use a DMA channel and heavily loads the CPU.
+ */
+#if !defined(MMC_NICE_WAITING) || defined(__DOXYGEN__)
+#define MMC_NICE_WAITING TRUE
+#endif
+
+/**
+ * @brief Number of positive insertion queries before generating the
+ * insertion event.
+ */
+#if !defined(MMC_POLLING_INTERVAL) || defined(__DOXYGEN__)
+#define MMC_POLLING_INTERVAL 10
+#endif
+
+/**
+ * @brief Interval, in milliseconds, between insertion queries.
+ */
+#if !defined(MMC_POLLING_DELAY) || defined(__DOXYGEN__)
+#define MMC_POLLING_DELAY 10
+#endif
+
+/**
+ * @brief Uses the SPI polled API for small data transfers.
+ * @details Polled transfers usually improve performance because it
+ * saves two context switches and interrupt servicing. Note
+ * that this option has no effect on large transfers which
+ * are always performed using DMAs/IRQs.
+ */
+#if !defined(MMC_USE_SPI_POLLING) || defined(__DOXYGEN__)
+#define MMC_USE_SPI_POLLING TRUE
+#endif
+
+/*===========================================================================*/
+/* SDC driver related settings. */
+/*===========================================================================*/
+
+/**
+ * @brief Number of initialization attempts before rejecting the card.
+ * @note Attempts are performed at 10mS intervals.
+ */
+#if !defined(SDC_INIT_RETRY) || defined(__DOXYGEN__)
+#define SDC_INIT_RETRY 100
+#endif
+
+/**
+ * @brief Include support for MMC cards.
+ * @note MMC support is not yet implemented so this option must be kept
+ * at @p FALSE.
+ */
+#if !defined(SDC_MMC_SUPPORT) || defined(__DOXYGEN__)
+#define SDC_MMC_SUPPORT FALSE
+#endif
+
+/**
+ * @brief Delays insertions.
+ * @details If enabled this options inserts delays into the MMC waiting
+ * routines releasing some extra CPU time for the threads with
+ * lower priority, this may slow down the driver a bit however.
+ */
+#if !defined(SDC_NICE_WAITING) || defined(__DOXYGEN__)
+#define SDC_NICE_WAITING TRUE
+#endif
+
+/*===========================================================================*/
+/* SERIAL driver related settings. */
+/*===========================================================================*/
+
+/**
+ * @brief Default bit rate.
+ * @details Configuration parameter, this is the baud rate selected for the
+ * default configuration.
+ */
+#if !defined(SERIAL_DEFAULT_BITRATE) || defined(__DOXYGEN__)
+#define SERIAL_DEFAULT_BITRATE 38400
+#endif
+
+/**
+ * @brief Serial buffers size.
+ * @details Configuration parameter, you can change the depth of the queue
+ * buffers depending on the requirements of your application.
+ * @note The default is 64 bytes for both the transmission and receive
+ * buffers.
+ */
+#if !defined(SERIAL_BUFFERS_SIZE) || defined(__DOXYGEN__)
+#define SERIAL_BUFFERS_SIZE 16
+#endif
+
+/*===========================================================================*/
+/* SPI driver related settings. */
+/*===========================================================================*/
+
+/**
+ * @brief Enables synchronous APIs.
+ * @note Disabling this option saves both code and data space.
+ */
+#if !defined(SPI_USE_WAIT) || defined(__DOXYGEN__)
+#define SPI_USE_WAIT TRUE
+#endif
+
+/**
+ * @brief Enables the @p spiAcquireBus() and @p spiReleaseBus() APIs.
+ * @note Disabling this option saves both code and data space.
+ */
+#if !defined(SPI_USE_MUTUAL_EXCLUSION) || defined(__DOXYGEN__)
+#define SPI_USE_MUTUAL_EXCLUSION TRUE
+#endif
+
+#endif /* _HALCONF_H_ */
+
+/** @} */
diff --git a/firmware/config/system/mcuconf.h b/firmware/config/system/mcuconf.h
new file mode 100644
index 0000000000..711b98173c
--- /dev/null
+++ b/firmware/config/system/mcuconf.h
@@ -0,0 +1,272 @@
+/*
+ ChibiOS/RT - Copyright (C) 2006-2013 Giovanni Di Sirio
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+/*
+ * STM32F4xx drivers configuration.
+ * The following settings override the default settings present in
+ * the various device driver implementation headers.
+ * Note that the settings for each driver only have effect if the whole
+ * driver is enabled in halconf.h.
+ *
+ * IRQ priorities:
+ * 15...0 Lowest...Highest.
+ *
+ * DMA priorities:
+ * 0...3 Lowest...Highest.
+ */
+
+#define STM32F4xx_MCUCONF
+
+#include "boards.h"
+#include "efifeatures.h"
+#include "rusefi_enums.h"
+
+/*
+ * HAL driver system settings.
+ */
+#define STM32_NO_INIT FALSE
+#define STM32_HSI_ENABLED TRUE
+#define STM32_LSI_ENABLED TRUE
+#define STM32_HSE_ENABLED TRUE
+
+// change this 'FALSE to TRUE if you have the LSE 32768 quarts
+#define STM32_LSE_ENABLED FALSE
+
+#define STM32_CLOCK48_REQUIRED TRUE
+#define STM32_SW STM32_SW_PLL
+#define STM32_PLLSRC STM32_PLLSRC_HSE
+#ifdef OLIMEX_STM32_E407
+#define STM32_PLLM_VALUE 12
+#else
+#define STM32_PLLM_VALUE 8
+#endif
+#define STM32_PLLN_VALUE 336
+#define STM32_PLLP_VALUE 2
+#define STM32_PLLQ_VALUE 7
+#define STM32_HPRE STM32_HPRE_DIV1
+#define STM32_PPRE1 STM32_PPRE1_DIV4
+#define STM32_PPRE2 STM32_PPRE2_DIV2
+#if STM32_LSE_ENABLED
+ #define STM32_RTCSEL STM32_RTCSEL_LSE
+#else
+ #define STM32_RTCSEL STM32_RTCSEL_LSI
+#endif
+#define STM32_RTCPRE_VALUE 8
+#define STM32_MCO1SEL STM32_MCO1SEL_HSI
+#define STM32_MCO1PRE STM32_MCO1PRE_DIV1
+#define STM32_MCO2SEL STM32_MCO2SEL_SYSCLK
+#define STM32_MCO2PRE STM32_MCO2PRE_DIV5
+#define STM32_I2SSRC STM32_I2SSRC_CKIN
+#define STM32_PLLI2SN_VALUE 192
+#define STM32_PLLI2SR_VALUE 5
+//#define STM32_VOS STM32_VOS_HIGH
+#define STM32_PVD_ENABLE FALSE
+#define STM32_PLS STM32_PLS_LEV0
+
+/*
+ * ADC driver system settings.
+ */
+#define STM32_ADC_ADCPRE ADC_CCR_ADCPRE_DIV4
+#define STM32_ADC_USE_ADC1 TRUE // slow ADC
+#define STM32_ADC_USE_ADC2 TRUE // fast ADC
+#define STM32_ADC_USE_ADC3 FALSE
+#define STM32_ADC_ADC1_DMA_STREAM STM32_DMA_STREAM_ID(2, 4)
+#define STM32_ADC_ADC2_DMA_STREAM STM32_DMA_STREAM_ID(2, 2)
+#define STM32_ADC_ADC3_DMA_STREAM STM32_DMA_STREAM_ID(2, 1)
+#define STM32_ADC_ADC1_DMA_PRIORITY 2
+#define STM32_ADC_ADC2_DMA_PRIORITY 2
+#define STM32_ADC_ADC3_DMA_PRIORITY 2
+#define STM32_ADC_IRQ_PRIORITY 6
+#define STM32_ADC_ADC1_DMA_IRQ_PRIORITY 6
+#define STM32_ADC_ADC2_DMA_IRQ_PRIORITY 6
+#define STM32_ADC_ADC3_DMA_IRQ_PRIORITY 6
+
+/*
+ * CAN driver system settings.
+ */
+#define STM32_CAN_CAN1_IRQ_PRIORITY 11
+#define STM32_CAN_CAN2_IRQ_PRIORITY 11
+
+/*
+ * EXT driver system settings.
+ */
+#define STM32_EXT_EXTI0_IRQ_PRIORITY 6
+#define STM32_EXT_EXTI1_IRQ_PRIORITY 6
+#define STM32_EXT_EXTI2_IRQ_PRIORITY 6
+#define STM32_EXT_EXTI3_IRQ_PRIORITY 6
+#define STM32_EXT_EXTI4_IRQ_PRIORITY 6
+#define STM32_EXT_EXTI5_9_IRQ_PRIORITY 6
+#define STM32_EXT_EXTI10_15_IRQ_PRIORITY 6
+#define STM32_EXT_EXTI16_IRQ_PRIORITY 6
+#define STM32_EXT_EXTI17_IRQ_PRIORITY 15
+#define STM32_EXT_EXTI18_IRQ_PRIORITY 6
+#define STM32_EXT_EXTI19_IRQ_PRIORITY 6
+#define STM32_EXT_EXTI20_IRQ_PRIORITY 6
+#define STM32_EXT_EXTI21_IRQ_PRIORITY 15
+#define STM32_EXT_EXTI22_IRQ_PRIORITY 15
+
+/*
+ * GPT driver system settings.
+ */
+#define STM32_GPT_USE_TIM1 FALSE
+#define STM32_GPT_USE_TIM2 FALSE
+#define STM32_GPT_USE_TIM3 FALSE
+#define STM32_GPT_USE_TIM4 FALSE
+#define STM32_GPT_USE_TIM5 TRUE
+#define STM32_GPT_USE_TIM6 FALSE
+#define STM32_GPT_USE_TIM7 FALSE
+#define STM32_GPT_USE_TIM8 FALSE
+#define STM32_GPT_USE_TIM9 FALSE
+#define STM32_GPT_USE_TIM11 FALSE
+#define STM32_GPT_USE_TIM12 FALSE
+#define STM32_GPT_USE_TIM14 FALSE
+#define STM32_GPT_TIM1_IRQ_PRIORITY 7
+#define STM32_GPT_TIM2_IRQ_PRIORITY 7
+#define STM32_GPT_TIM3_IRQ_PRIORITY 7
+#define STM32_GPT_TIM4_IRQ_PRIORITY 7
+#define STM32_GPT_TIM5_IRQ_PRIORITY 3
+#define STM32_GPT_TIM6_IRQ_PRIORITY 7
+#define STM32_GPT_TIM7_IRQ_PRIORITY 7
+#define STM32_GPT_TIM8_IRQ_PRIORITY 7
+#define STM32_GPT_TIM9_IRQ_PRIORITY 7
+#define STM32_GPT_TIM11_IRQ_PRIORITY 7
+#define STM32_GPT_TIM12_IRQ_PRIORITY 7
+#define STM32_GPT_TIM14_IRQ_PRIORITY 7
+
+/*
+ * I2C driver system settings.
+ */
+#define STM32_I2C_USE_I2C1 TRUE
+#define STM32_I2C_USE_I2C2 FALSE
+#define STM32_I2C_USE_I2C3 FALSE
+#define STM32_I2C_I2C2_RX_DMA_STREAM STM32_DMA_STREAM_ID(1, 2)
+#define STM32_I2C_I2C2_TX_DMA_STREAM STM32_DMA_STREAM_ID(1, 7)
+#define STM32_I2C_I2C3_RX_DMA_STREAM STM32_DMA_STREAM_ID(1, 2)
+#define STM32_I2C_I2C3_TX_DMA_STREAM STM32_DMA_STREAM_ID(1, 4)
+#define STM32_I2C_I2C1_IRQ_PRIORITY 5
+#define STM32_I2C_I2C2_IRQ_PRIORITY 5
+#define STM32_I2C_I2C3_IRQ_PRIORITY 5
+#define STM32_I2C_I2C1_DMA_PRIORITY 3
+#define STM32_I2C_I2C2_DMA_PRIORITY 3
+#define STM32_I2C_I2C3_DMA_PRIORITY 3
+#define STM32_I2C_I2C1_DMA_ERROR_HOOK() chSysHalt()
+#define STM32_I2C_I2C2_DMA_ERROR_HOOK() chSysHalt()
+#define STM32_I2C_I2C3_DMA_ERROR_HOOK() chSysHalt()
+
+/*
+ * ICU driver system settings.
+ */
+#define STM32_ICU_TIM1_IRQ_PRIORITY 7
+#define STM32_ICU_TIM2_IRQ_PRIORITY 7
+#define STM32_ICU_TIM3_IRQ_PRIORITY 7
+#define STM32_ICU_TIM4_IRQ_PRIORITY 7
+#define STM32_ICU_TIM5_IRQ_PRIORITY 7
+#define STM32_ICU_TIM8_IRQ_PRIORITY 7
+#define STM32_ICU_TIM9_IRQ_PRIORITY 7
+
+/*
+ * MAC driver system settings.
+ */
+#define STM32_MAC_TRANSMIT_BUFFERS 2
+#define STM32_MAC_RECEIVE_BUFFERS 4
+#define STM32_MAC_BUFFERS_SIZE 1522
+#define STM32_MAC_PHY_TIMEOUT 100
+#define STM32_MAC_ETH1_CHANGE_PHY_STATE TRUE
+#define STM32_MAC_ETH1_IRQ_PRIORITY 13
+#define STM32_MAC_IP_CHECKSUM_OFFLOAD 0
+
+/*
+ * PWM driver system settings.
+ */
+#define STM32_PWM_USE_ADVANCED FALSE
+#define STM32_PWM_TIM1_IRQ_PRIORITY 7
+#define STM32_PWM_TIM2_IRQ_PRIORITY 7
+#define STM32_PWM_TIM3_IRQ_PRIORITY 7
+#define STM32_PWM_TIM4_IRQ_PRIORITY 7
+#define STM32_PWM_TIM5_IRQ_PRIORITY 7
+#define STM32_PWM_TIM8_IRQ_PRIORITY 7
+#define STM32_PWM_TIM9_IRQ_PRIORITY 7
+
+/*
+ * SERIAL driver system settings.
+ */
+#define STM32_SERIAL_USE_USART1 TRUE
+#define STM32_SERIAL_USE_USART2 TRUE
+#define STM32_SERIAL_USE_USART3 TRUE
+#define STM32_SERIAL_USE_UART4 FALSE
+#define STM32_SERIAL_USE_UART5 FALSE
+#define STM32_SERIAL_USE_USART6 FALSE
+#define STM32_SERIAL_USART1_PRIORITY 12
+#define STM32_SERIAL_USART2_PRIORITY 12
+#define STM32_SERIAL_USART3_PRIORITY 12
+#define STM32_SERIAL_UART4_PRIORITY 12
+#define STM32_SERIAL_UART5_PRIORITY 12
+#define STM32_SERIAL_USART6_PRIORITY 12
+
+/*
+ * SPI driver system settings.
+ */
+#define STM32_SPI_SPI1_RX_DMA_STREAM STM32_DMA_STREAM_ID(2, 0)
+#define STM32_SPI_SPI1_TX_DMA_STREAM STM32_DMA_STREAM_ID(2, 3)
+#define STM32_SPI_SPI2_RX_DMA_STREAM STM32_DMA_STREAM_ID(1, 3)
+#define STM32_SPI_SPI2_TX_DMA_STREAM STM32_DMA_STREAM_ID(1, 4)
+#define STM32_SPI_SPI3_RX_DMA_STREAM STM32_DMA_STREAM_ID(1, 0)
+#define STM32_SPI_SPI3_TX_DMA_STREAM STM32_DMA_STREAM_ID(1, 7)
+#define STM32_SPI_SPI1_DMA_PRIORITY 1
+#define STM32_SPI_SPI2_DMA_PRIORITY 1
+#define STM32_SPI_SPI3_DMA_PRIORITY 1
+#define STM32_SPI_SPI1_IRQ_PRIORITY 10
+#define STM32_SPI_SPI2_IRQ_PRIORITY 10
+#define STM32_SPI_SPI3_IRQ_PRIORITY 10
+#define STM32_SPI_DMA_ERROR_HOOK(spip) chDbgCheck(TRUE, "STM32_SPI_DMA_ERROR_HOOK")
+
+/*
+ * UART driver system settings.
+ */
+#define STM32_UART_USE_USART1 FALSE
+#define STM32_UART_USE_USART2 FALSE
+#define STM32_UART_USE_USART3 TRUE
+#define STM32_UART_USE_USART6 FALSE
+#define STM32_UART_USART1_RX_DMA_STREAM STM32_DMA_STREAM_ID(2, 5)
+#define STM32_UART_USART1_TX_DMA_STREAM STM32_DMA_STREAM_ID(2, 7)
+#define STM32_UART_USART2_RX_DMA_STREAM STM32_DMA_STREAM_ID(1, 5)
+#define STM32_UART_USART2_TX_DMA_STREAM STM32_DMA_STREAM_ID(1, 6)
+#define STM32_UART_USART3_RX_DMA_STREAM STM32_DMA_STREAM_ID(1, 1)
+#define STM32_UART_USART3_TX_DMA_STREAM STM32_DMA_STREAM_ID(1, 3)
+#define STM32_UART_USART6_RX_DMA_STREAM STM32_DMA_STREAM_ID(2, 2)
+#define STM32_UART_USART6_TX_DMA_STREAM STM32_DMA_STREAM_ID(2, 7)
+#define STM32_UART_USART1_IRQ_PRIORITY 12
+#define STM32_UART_USART2_IRQ_PRIORITY 12
+#define STM32_UART_USART3_IRQ_PRIORITY 12
+#define STM32_UART_USART6_IRQ_PRIORITY 12
+#define STM32_UART_USART1_DMA_PRIORITY 0
+#define STM32_UART_USART2_DMA_PRIORITY 0
+#define STM32_UART_USART3_DMA_PRIORITY 0
+#define STM32_UART_USART6_DMA_PRIORITY 0
+#define STM32_UART_DMA_ERROR_HOOK(uartp) chSysHalt()
+
+/*
+ * USB driver system settings.
+ */
+#define STM32_USB_USE_OTG1 TRUE
+#define STM32_USB_USE_OTG2 FALSE
+#define STM32_USB_OTG1_IRQ_PRIORITY 14
+#define STM32_USB_OTG2_IRQ_PRIORITY 14
+#define STM32_USB_OTG1_RX_FIFO_SIZE 512
+#define STM32_USB_OTG2_RX_FIFO_SIZE 1024
+#define STM32_USB_OTG_THREAD_PRIO LOWPRIO
+#define STM32_USB_OTG_THREAD_STACK_SIZE 1024
+#define STM32_USB_OTGFIFO_FILL_BASEPRI 0
diff --git a/firmware/console/console.mk b/firmware/console/console.mk
new file mode 100644
index 0000000000..e75257e6eb
--- /dev/null
+++ b/firmware/console/console.mk
@@ -0,0 +1,6 @@
+
+CONSOLESRC = $(PROJECT_DIR)/console/eficonsole.c \
+ $(PROJECT_DIR)/console/console_io.c
+
+CONSOLE_SRC_CPP = $(PROJECT_DIR)/console/status_loop.cpp
+
\ No newline at end of file
diff --git a/firmware/console/console_io.c b/firmware/console/console_io.c
new file mode 100644
index 0000000000..a8042b8435
--- /dev/null
+++ b/firmware/console/console_io.c
@@ -0,0 +1,245 @@
+/**
+ * @file console_io.c
+ *
+ * @date Dec 29, 2012
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ */
+
+#include "main.h"
+#include "console_io.h"
+
+#if EFI_PROD_CODE
+extern SerialUSBDriver SDU1;
+#include "usbcfg.h"
+#include "usbconsole.h"
+#endif
+#include "rfiutil.h"
+
+int lastWriteSize;
+int lastWriteActual;
+
+static bool isSerialConsoleStarted = false;
+
+static EventListener consoleEventListener;
+
+/**
+ * @brief Reads a whole line from the input channel.
+ *
+ * @param[in] chp pointer to a @p BaseChannel object
+ * @param[in] line pointer to the line buffer
+ * @param[in] size buffer maximum length
+ * @return The operation status.
+ * @retval TRUE the channel was reset or CTRL-D pressed.
+ * @retval FALSE operation successful.
+ */
+static bool getConsoleLine(BaseSequentialStream *chp, char *line, unsigned size) {
+ char *p = line;
+
+ while (TRUE) {
+ if (!isConsoleReady()) {
+ // we better do not read from USB serial before it is ready
+ chThdSleepMilliseconds(10);
+ continue;
+ }
+
+ short c = (short) chSequentialStreamGet(chp);
+
+ if (isSerialOverUart()) {
+ uint32_t flags;
+ chSysLock()
+ ;
+
+ flags = chEvtGetAndClearFlagsI(&consoleEventListener);
+ chSysUnlock()
+ ;
+
+ if (flags & SD_OVERRUN_ERROR) {
+// firmwareError("serial overrun");
+ }
+
+ }
+
+#if EFI_UART_ECHO_TEST_MODE
+ /**
+ * That's test code - let's test connectivity
+ */
+ consolePutChar((uint8_t) c);
+ continue;
+#endif
+
+ if (c < 0 || c == 4) {
+ return TRUE;
+ }
+ if (c == 8) {
+ if (p != line) {
+ // backspace
+ consolePutChar((uint8_t) c);
+ consolePutChar(0x20);
+ consolePutChar((uint8_t) c);
+ p--;
+ }
+ continue;
+ }
+ if (c == '\r') {
+ consolePutChar('\r');
+ consolePutChar('\n');
+ *p = 0;
+ return false;
+ }
+ if (c < 0x20) {
+ continue;
+ }
+ if (p < line + size - 1) {
+ consolePutChar((uint8_t) c);
+ *p++ = (char) c;
+ }
+ }
+}
+
+// todo: this is ugly as hell!
+static char consoleInput[] = " ";
+
+void (*console_line_callback)(char *);
+
+static bool is_serial_over_uart;
+
+bool isSerialOverUart(void) {
+ return is_serial_over_uart;
+}
+
+static THD_WORKING_AREA(consoleThreadStack, 2 * UTILITY_THREAD_STACK_SIZE);
+static msg_t consoleThreadThreadEntryPoint(void *arg) {
+ (void) arg;
+ chRegSetThreadName("console thread");
+
+ while (TRUE) {
+ bool end = getConsoleLine((BaseSequentialStream*) getConsoleChannel(), consoleInput, sizeof(consoleInput));
+ if (end) {
+ // firmware simulator is the only case when this happens
+ continue;
+ }
+
+ (console_line_callback)(consoleInput);
+ }
+#if defined __GNUC__
+ return false;
+#endif
+}
+
+#if EFI_PROD_CODE
+
+static SerialConfig serialConfig = { SERIAL_SPEED, 0, USART_CR2_STOP1_BITS | USART_CR2_LINEN, 0 };
+
+SerialDriver * getConsoleChannel(void) {
+ if (isSerialOverUart()) {
+ return (SerialDriver *) EFI_CONSOLE_UART_DEVICE;
+ } else {
+ return (SerialDriver *) &SDU1;
+ }
+}
+
+bool isConsoleReady(void) {
+ if (isSerialOverUart()) {
+ return isSerialConsoleStarted;
+ } else {
+ return is_usb_serial_ready();
+ }
+}
+
+#endif /* EFI_PROD_CODE */
+
+void consolePutChar(int x) {
+ chSequentialStreamPut(getConsoleChannel(), (uint8_t )(x));
+}
+
+// 10 seconds
+#define CONSOLE_WRITE_TIMEOUT 10000
+
+void consoleOutputBuffer(const uint8_t *buf, int size) {
+ lastWriteSize = size;
+#if !EFI_UART_ECHO_TEST_MODE
+ lastWriteActual = chnWriteTimeout(getConsoleChannel(), buf, size, CONSOLE_WRITE_TIMEOUT);
+// if (r != size)
+// firmwareError("Partial console write");
+#endif /* EFI_UART_ECHO_TEST_MODE */
+}
+
+void startConsole(void (*console_line_callback_p)(char *)) {
+ console_line_callback = console_line_callback_p;
+
+#if EFI_PROD_CODE
+
+ palSetPadMode(CONSOLE_MODE_SWITCH_PORT, CONSOLE_MODE_SWITCH_PIN, PAL_MODE_INPUT_PULLUP);
+
+ is_serial_over_uart = GET_CONSOLE_MODE_VALUE() == EFI_USE_UART_FOR_CONSOLE;
+
+ if (isSerialOverUart()) {
+ /*
+ * Activates the serial using the driver default configuration (that's 38400)
+ * it is important to set 'NONE' as flow control! in terminal application on the PC
+ */
+ sdStart(EFI_CONSOLE_UART_DEVICE, &serialConfig);
+
+// cannot use pin repository here because pin repository prints to console
+ palSetPadMode(EFI_CONSOLE_RX_PORT, EFI_CONSOLE_RX_PIN, PAL_MODE_ALTERNATE(EFI_CONSOLE_AF));
+ palSetPadMode(EFI_CONSOLE_TX_PORT, EFI_CONSOLE_TX_PIN, PAL_MODE_ALTERNATE(EFI_CONSOLE_AF));
+
+ isSerialConsoleStarted = TRUE;
+
+ chEvtRegisterMask((EventSource *) chnGetEventSource(EFI_CONSOLE_UART_DEVICE), &consoleEventListener, 1);
+ } else {
+ usb_serial_start();
+ }
+#endif /* EFI_PROD_CODE */
+ chThdCreateStatic(consoleThreadStack, sizeof(consoleThreadStack), NORMALPRIO, consoleThreadThreadEntryPoint, NULL);
+}
+
+extern cnt_t dbg_isr_cnt;
+
+/**
+ * @return TRUE if already in locked context
+ */
+bool lockAnyContext(void) {
+ int alreadyLocked = isLocked();
+ if (alreadyLocked)
+ return TRUE;
+ if (isIsrContext()) {
+ chSysLockFromIsr()
+ ;
+ } else {
+ chSysLock()
+ ;
+ }
+ return false;
+}
+
+bool lockOutputBuffer(void) {
+ return lockAnyContext();
+}
+
+void unlockAnyContext(void) {
+ if (isIsrContext()) {
+ chSysUnlockFromIsr()
+ ;
+ } else {
+ chSysUnlock()
+ ;
+ }
+}
+
+void unlockOutputBuffer(void) {
+ unlockAnyContext();
+}
diff --git a/firmware/console/console_io.h b/firmware/console/console_io.h
new file mode 100644
index 0000000000..121a750352
--- /dev/null
+++ b/firmware/console/console_io.h
@@ -0,0 +1,44 @@
+/**
+ * @file console_io.h
+ *
+ * @date Dec 29, 2012
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+#pragma once
+#ifndef CONSOLE_IO_H_
+#define CONSOLE_IO_H_
+
+#include
+#include
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+#include "efifeatures.h"
+#include "boards.h"
+
+#define GET_CONSOLE_MODE_VALUE() palReadPad(CONSOLE_MODE_SWITCH_PORT, CONSOLE_MODE_SWITCH_PIN)
+#define SHOULD_INGORE_FLASH() (palReadPad(CONFIG_RESET_SWITCH_PORT, CONFIG_RESET_SWITCH_PIN) == 0)
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+SerialDriver * getConsoleChannel(void);
+
+void consolePutChar(int x);
+void consoleOutputBuffer(const uint8_t *buf, int size);
+void startConsole(void (*console_line_callback_p)(char *));
+bool isConsoleReady(void);
+bool isSerialOverUart(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* CONSOLE_IO_H_ */
diff --git a/firmware/console/eficonsole.c b/firmware/console/eficonsole.c
new file mode 100644
index 0000000000..a3b86b1b0e
--- /dev/null
+++ b/firmware/console/eficonsole.c
@@ -0,0 +1,174 @@
+/**
+ * @file eficonsole.c
+ * @brief Console package entry point code
+ *
+ *
+ * @date Nov 15, 2012
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ */
+
+#include
+#include
+#include "main.h"
+#include
+#include "eficonsole.h"
+#include "console_io.h"
+#include "svnversion.h"
+
+static Logging logger;
+
+static char fatalErrorMessage[200];
+
+void fatal3(char *msg, char *file, int line) {
+ strcpy(fatalErrorMessage, msg);
+#if EFI_CUSTOM_PANIC_METHOD
+ chDbgPanic3(fatalErrorMessage, file, line);
+#else
+ chDbgPanic(fatalErrorMessage);
+#endif
+}
+
+static void myfatal(void) {
+ chDbgCheck(0, "my fatal");
+}
+
+static void myerror(void) {
+ firmwareError("firmwareError: %d", getRusEfiVersion());
+}
+
+static void sayNothing(void) {
+ /**
+ * @see EngineState#TS_PROTOCOL_TAG
+ * this empty response is part of protocol check
+ * todo: make this logic smarter?
+ */
+}
+
+static void sayHello(void) {
+ printMsg(&logger, "*** rusEFI (c) Andrey Belomutskiy, 2012-2014. All rights reserved.");
+ printMsg(&logger, "rusEFI v%d@%s", getRusEfiVersion(), VCS_VERSION);
+ printMsg(&logger, "*** Chibios Kernel: %s", CH_KERNEL_VERSION);
+ printMsg(&logger, "*** Compiled: " __DATE__ " - " __TIME__ "");
+ printMsg(&logger, "COMPILER=%s", __VERSION__);
+ printMsg(&logger, "CH_FREQUENCY=%d", CH_FREQUENCY);
+#ifdef SERIAL_SPEED
+ printMsg(&logger, "SERIAL_SPEED=%d", SERIAL_SPEED);
+#endif
+
+#ifdef CORTEX_MAX_KERNEL_PRIORITY
+ printMsg(&logger, "CORTEX_MAX_KERNEL_PRIORITY=%d", CORTEX_MAX_KERNEL_PRIORITY);
+#endif
+
+#ifdef STM32_ADCCLK
+ printMsg(&logger, "STM32_ADCCLK=%d", STM32_ADCCLK);
+ printMsg(&logger, "STM32_TIMCLK1=%d", STM32_TIMCLK1);
+ printMsg(&logger, "STM32_TIMCLK2=%d", STM32_TIMCLK2);
+ printMsg(&logger, "STM32_PCLK1=%d", STM32_PCLK1);
+ printMsg(&logger, "STM32_PCLK2=%d", STM32_PCLK2);
+#endif
+
+
+ printMsg(&logger, "PORT_IDLE_THREAD_STACK_SIZE=%d", PORT_IDLE_THREAD_STACK_SIZE);
+
+ printMsg(&logger, "CH_DBG_ENABLE_ASSERTS=%d", CH_DBG_ENABLE_ASSERTS);
+ printMsg(&logger, "CH_DBG_ENABLED=%d", CH_DBG_ENABLED);
+ printMsg(&logger, "CH_DBG_SYSTEM_STATE_CHECK=%d", CH_DBG_SYSTEM_STATE_CHECK);
+ printMsg(&logger, "CH_DBG_ENABLE_STACK_CHECK=%d", CH_DBG_ENABLE_STACK_CHECK);
+
+#ifdef EFI_WAVE_ANALYZER
+ printMsg(&logger, "EFI_WAVE_ANALYZER=%d", EFI_WAVE_ANALYZER);
+#endif
+#ifdef EFI_TUNER_STUDIO
+ printMsg(&logger, "EFI_TUNER_STUDIO=%d", EFI_TUNER_STUDIO);
+#else
+ printMsg(&logger, "EFI_TUNER_STUDIO=%d", 0);
+#endif
+
+#ifdef EFI_SIGNAL_EXECUTOR_SLEEP
+ printMsg(&logger, "EFI_SIGNAL_EXECUTOR_SLEEP=%d", EFI_SIGNAL_EXECUTOR_SLEEP);
+#endif
+
+#ifdef EFI_SIGNAL_EXECUTOR_HW_TIMER
+ printMsg(&logger, "EFI_SIGNAL_EXECUTOR_HW_TIMER=%d", EFI_SIGNAL_EXECUTOR_HW_TIMER);
+#endif
+
+ printMsg(&logger, "EFI_SHAFT_POSITION_INPUT=%d", EFI_SHAFT_POSITION_INPUT);
+ printMsg(&logger, "EFI_INTERNAL_ADC=%d", EFI_INTERNAL_ADC);
+
+// printSimpleMsg(&logger, "", );
+// printSimpleMsg(&logger, "", );
+
+
+ /**
+ * Time to finish output. This is needed to avoid mix-up of this methods output and console command confirmation
+ */
+ chThdSleepMilliseconds(5);
+}
+
+/**
+ * This methods prints all threads and their total times
+ */
+static void cmd_threads(void) {
+#if CH_DBG_THREADS_PROFILING || defined(__DOXYGEN__)
+ static const char *states[] = { THD_STATE_NAMES };
+ Thread *tp;
+
+ print(" addr stack prio refs state time\r\n");
+ tp = chRegFirstThread();
+ do {
+ print("%.8lx [%.8lx] %4lu %4lu %9s %lu %s\r\n", (uint32_t) tp, 0,
+ (uint32_t) tp->p_prio, (uint32_t) (tp->p_refs - 1),
+ states[tp->p_state], (uint32_t) tp->p_time, tp->p_name);
+ tp = chRegNextThread(tp);
+ } while (tp != NULL );
+#endif
+}
+
+/**
+ * This methods prints the message to whatever is configured as our primary console
+ */
+void print(const char *format, ...) {
+#if !EFI_UART_ECHO_TEST_MODE
+ if (!isConsoleReady()) {
+ return;
+ }
+ va_list ap;
+ va_start(ap, format);
+ chvprintf((BaseSequentialStream*)getConsoleChannel(), format, ap);
+ va_end(ap);
+#endif /* EFI_UART_ECHO_TEST_MODE */
+}
+
+void initializeConsole(void) {
+ initIntermediateLoggingBuffer();
+ initConsoleLogic();
+
+ startConsole(&handleConsoleLine);
+
+ initLogging(&logger, "console");
+
+ sayHello();
+ addConsoleAction("test", sayNothing);
+ addConsoleAction("hello", sayHello);
+#if EFI_HAS_RESET
+ addConsoleAction("reset", scheduleReset);
+#endif
+
+ addConsoleAction("fatal", myfatal);
+ addConsoleAction("error", myerror);
+ addConsoleAction("threadsinfo", cmd_threads);
+}
diff --git a/firmware/console/eficonsole.h b/firmware/console/eficonsole.h
new file mode 100644
index 0000000000..4d4f825530
--- /dev/null
+++ b/firmware/console/eficonsole.h
@@ -0,0 +1,25 @@
+/**
+ * @file eficonsole.h
+ * @brief Console package entry point header
+ *
+ * @date Nov 15, 2012
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#pragma once
+#ifndef RFICONSOLE_H_
+#define RFICONSOLE_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void initializeConsole(void);
+void print(const char *fmt, ...);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* RFICONSOLE_H_ */
diff --git a/firmware/console/status_loop.cpp b/firmware/console/status_loop.cpp
new file mode 100644
index 0000000000..5a0fc8b88e
--- /dev/null
+++ b/firmware/console/status_loop.cpp
@@ -0,0 +1,442 @@
+/**
+ * @file status_loop.cpp
+ * @brief Human-readable protocol status messages
+ *
+ * http://rusefi.com/forum/viewtopic.php?t=263 Dev console overview
+ * http://rusefi.com/forum/viewtopic.php?t=210 Commands overview
+ *
+ *
+ * @date Mar 15, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ *
+ */
+
+#include "main.h"
+#include "status_loop.h"
+#include "ec2.h"
+
+#include "adc_inputs.h"
+#if EFI_WAVE_ANALYZER
+#include "wave_analyzer.h"
+#endif
+
+#include "trigger_central.h"
+#include "engine_state.h"
+#include "io_pins.h"
+#include "mmc_card.h"
+#include "console_io.h"
+#include "malfunction_central.h"
+#include "speed_density.h"
+
+#include "advance_map.h"
+#if EFI_TUNER_STUDIO
+#include "tunerstudio.h"
+#endif /* EFI_TUNER_STUDIO */
+
+#include "fuel_math.h"
+#include "main_trigger_callback.h"
+#include "engine_math.h"
+#include "idle_thread.h"
+#include "engine_configuration.h"
+#include "rfiutil.h"
+#include "svnversion.h"
+#include "engine.h"
+#include "lcd_controller.h"
+#include "fuel_math.h"
+
+#if EFI_PROD_CODE
+// todo: move this logic to algo folder!
+#include "rtc_helper.h"
+#include "lcd_HD44780.h"
+#include "rusefi.h"
+#include "pin_repository.h"
+#include "flash_main.h"
+#endif
+
+extern Engine engine;
+
+// this 'true' value is needed for simulator
+static volatile bool fullLog = true;
+int warningEnabled = TRUE;
+//int warningEnabled = FALSE;
+
+extern engine_configuration_s * engineConfiguration;
+extern engine_configuration2_s * engineConfiguration2;
+extern board_configuration_s *boardConfiguration;
+#define FULL_LOGGING_KEY "fl"
+
+#if EFI_PROD_CODE || EFI_SIMULATOR
+static Logging logger;
+
+static void setWarningEnabled(int value) {
+ warningEnabled = value;
+}
+
+#endif /* EFI_PROD_CODE || EFI_SIMULATOR */
+
+#if EFI_FILE_LOGGING
+static Logging fileLogger;
+#endif /* EFI_FILE_LOGGING */
+
+static void reportSensorF(const char *caption, float value, int precision) {
+#if EFI_PROD_CODE || EFI_SIMULATOR
+ debugFloat(&logger, caption, value, precision);
+#endif /* EFI_PROD_CODE || EFI_SIMULATOR */
+
+#if EFI_FILE_LOGGING
+ debugFloat(&fileLogger, caption, value, precision);
+#endif /* EFI_FILE_LOGGING */
+}
+
+static void reportSensorI(const char *caption, int value) {
+#if EFI_PROD_CODE || EFI_SIMULATOR
+ debugInt(&logger, caption, value);
+#endif /* EFI_PROD_CODE || EFI_SIMULATOR */
+#if EFI_FILE_LOGGING
+ debugInt(&fileLogger, caption, value);
+#endif /* EFI_FILE_LOGGING */
+}
+
+static const char* boolean2string(int value) {
+ return value ? "YES" : "NO";
+}
+
+void printSensors(void) {
+#if EFI_FILE_LOGGING
+ resetLogging(&fileLogger);
+#endif /* EFI_FILE_LOGGING */
+
+ // current time, in milliseconds
+ int nowMs = currentTimeMillis();
+ float sec = ((float) nowMs) / 1000;
+ reportSensorF("time", sec, 3);
+
+ reportSensorI("rpm", getRpm());
+ reportSensorF("maf", getMaf(), 2);
+
+ if (engineConfiguration->hasMapSensor) {
+ reportSensorF(getCaption(LP_MAP), getMap(), 2);
+ reportSensorF("map_r", getRawMap(), 2);
+ }
+ reportSensorF("baro", getBaroPressure(), 2);
+
+ reportSensorF("afr", getAfr(), 2);
+ reportSensorF("vref", getVRef(), 2);
+ reportSensorF("vbatt", getVBatt(), 2);
+
+ reportSensorF("TRG_0_DUTY", getTriggerDutyCycle(0), 2);
+ reportSensorF("TRG_1_DUTY", getTriggerDutyCycle(1), 2);
+
+ reportSensorF(getCaption(LP_THROTTLE), getTPS(), 2);
+
+ if (engineConfiguration->hasCltSensor) {
+ reportSensorF(getCaption(LP_ECT), getCoolantTemperature(), 2);
+ }
+
+ reportSensorF(getCaption(LP_IAT), getIntakeAirTemperature(), 2);
+
+// debugFloat(&logger, "tch", getTCharge1(tps), 2);
+
+#if EFI_FILE_LOGGING
+ appendPrintf(&fileLogger, "\r\n");
+ appendToLog(fileLogger.buffer);
+#endif /* EFI_FILE_LOGGING */
+}
+
+void printState(int currentCkpEventCounter) {
+#if EFI_SHAFT_POSITION_INPUT
+ printSensors();
+
+ int rpm = getRpm();
+ debugInt(&logger, "ckp_c", currentCkpEventCounter);
+
+// debugInt(&logger, "idl", getIdleSwitch());
+
+// debugFloat(&logger, "table_spark", getAdvance(rpm, getMaf()), 2);
+
+ float engineLoad = getEngineLoad();
+ float baseFuel = getBaseFuel(&engine, rpm);
+ debugFloat(&logger, "fuel_base", baseFuel, 2);
+// debugFloat(&logger, "fuel_iat", getIatCorrection(getIntakeAirTemperature()), 2);
+// debugFloat(&logger, "fuel_clt", getCltCorrection(getCoolantTemperature()), 2);
+ debugFloat(&logger, "fuel_lag", getInjectorLag(getVBatt()), 2);
+ debugFloat(&logger, "fuel", getRunningFuel(baseFuel, &engine, rpm), 2);
+
+ debugFloat(&logger, "timing", getAdvance(rpm, engineLoad), 2);
+
+// float map = getMap();
+// float fuel = getDefaultFuel(rpm, map);
+// debugFloat(&logger, "d_fuel", fuel, 2);
+
+#endif /* EFI_SHAFT_POSITION_INPUT */
+}
+
+#define INITIAL_FULL_LOG TRUE
+//#define INITIAL_FULL_LOG FALSE
+
+static char LOGGING_BUFFER[700];
+
+volatile int needToReportStatus = FALSE;
+static int prevCkpEventCounter = -1;
+
+static Logging logger2;
+
+static void printStatus(void) {
+ needToReportStatus = TRUE;
+}
+
+/**
+ * Time when the firmware version was reported last time, in seconds
+ * TODO: implement a request/response instead of just constantly sending this out
+ */
+static systime_t timeOfPreviousPrintVersion = (systime_t) -1;
+
+#if EFI_PROD_CODE
+static void printOutPin(const char *pinName, brain_pin_e hwPin) {
+ appendPrintf(&logger, "outpin%s%s@%s%s", DELIMETER, pinName,
+ hwPortname(hwPin), DELIMETER);
+}
+#endif /* EFI_PROD_CODE */
+
+static void printInfo(systime_t nowSeconds) {
+ /**
+ * we report the version every 4 seconds - this way the console does not need to
+ * request it and we will display it pretty soon
+ */
+ if (overflowDiff(nowSeconds, timeOfPreviousPrintVersion) < 4) {
+ return;
+ }
+ timeOfPreviousPrintVersion = nowSeconds;
+ appendPrintf(&logger, "rusEfiVersion%s%d@%s %s%s", DELIMETER, getRusEfiVersion(), VCS_VERSION,
+ getConfigurationName(engineConfiguration),
+ DELIMETER);
+#if EFI_PROD_CODE
+ printOutPin(WC_CRANK1, boardConfiguration->triggerInputPins[0]);
+ printOutPin(WC_CRANK2, boardConfiguration->triggerInputPins[1]);
+ printOutPin(WA_CHANNEL_1, boardConfiguration->logicAnalyzerPins[0]);
+ printOutPin(WA_CHANNEL_2, boardConfiguration->logicAnalyzerPins[1]);
+
+ for (int i = 0; i < engineConfiguration->cylindersCount; i++) {
+ // todo: extract method?
+ io_pin_e pin = (io_pin_e) ((int) SPARKOUT_1_OUTPUT + i);
+
+ printOutPin(getPinName(pin), boardConfiguration->ignitionPins[i]);
+
+ pin = (io_pin_e) ((int) INJECTOR_1_OUTPUT + i);
+ printOutPin(getPinName(pin), boardConfiguration->injectionPins[i]);
+ }
+#endif
+
+}
+
+static systime_t timeOfPreviousReport = (systime_t) -1;
+
+extern char errorMessageBuffer[200];
+
+/**
+ * @brief Sends all pending data to dev console
+ */
+void updateDevConsoleState(void) {
+ if (!isConsoleReady()) {
+ return;
+ }
+// looks like this is not needed anymore
+// checkIfShouldHalt();
+ printPending();
+
+#if EFI_PROD_CODE
+ // todo: unify with simulator!
+ if (hasFirmwareError()) {
+ printMsg(&logger, "firmware error: %s", errorMessageBuffer);
+ warningEnabled = FALSE;
+ chThdSleepMilliseconds(200);
+ return;
+ }
+#endif
+
+#if EFI_PROD_CODE
+ pokeAdcInputs();
+#endif
+
+ if (!fullLog) {
+ return;
+ }
+
+ systime_t nowSeconds = getTimeNowSeconds();
+ printInfo(nowSeconds);
+
+ int currentCkpEventCounter = getCrankEventCounter();
+ if (prevCkpEventCounter == currentCkpEventCounter && timeOfPreviousReport == nowSeconds) {
+ return;
+ }
+
+ timeOfPreviousReport = nowSeconds;
+
+ prevCkpEventCounter = currentCkpEventCounter;
+
+ printState(currentCkpEventCounter);
+
+#if EFI_WAVE_ANALYZER
+ printWave(&logger);
+#endif
+
+ printLine(&logger);
+}
+
+#if EFI_PROD_CODE
+
+/*
+ * command example:
+ * sfm 3500 400
+ * that would be 'show fuel for rpm 3500 maf 4.0'
+ */
+
+static void showFuelMap2(float rpm, float engineLoad) {
+ float baseFuel = getBaseTableFuel((int) rpm, engineLoad);
+
+ float iatCorrection = getIatCorrection(getIntakeAirTemperature());
+ float cltCorrection = getCltCorrection(getCoolantTemperature());
+ float injectorLag = getInjectorLag(getVBatt());
+ scheduleMsg(&logger2, "rpm=%f engineLoad=%f", rpm, engineLoad);
+ scheduleMsg(&logger2, "baseFuel=%f", baseFuel);
+
+ scheduleMsg(&logger2, "iatCorrection=%f cltCorrection=%f injectorLag=%f", iatCorrection, cltCorrection,
+ injectorLag);
+
+ float value = getRunningFuel(baseFuel, &engine, (int) rpm);
+ scheduleMsg(&logger2, "injection pulse width: %f", value);
+}
+
+static void showFuelMap(void) {
+ showFuelMap2((float) getRpm(), getEngineLoad());
+}
+
+#endif /* EFI_PROD_CODE */
+
+static THD_WORKING_AREA(lcdThreadStack, UTILITY_THREAD_STACK_SIZE);
+
+static void lcdThread(void) {
+ chRegSetThreadName("lcd");
+ while (true) {
+#if EFI_HD44780_LCD
+ updateHD44780lcd();
+#endif
+ chThdSleepMilliseconds(boardConfiguration->lcdThreadPeriod);
+ }
+}
+
+static THD_WORKING_AREA(tsThreadStack, UTILITY_THREAD_STACK_SIZE);
+
+#if EFI_TUNER_STUDIO
+
+void updateTunerStudioState(TunerStudioOutputChannels *tsOutputChannels) {
+#if EFI_SHAFT_POSITION_INPUT
+ int rpm = getRpm();
+#else
+ int rpm = 0;
+#endif
+
+ float tps = getTPS();
+ float coolant = getCoolantTemperature();
+ float intake = getIntakeAirTemperature();
+
+ float engineLoad = getEngineLoad();
+ float baseFuel = getBaseTableFuel((int) rpm, engineLoad);
+
+ tsOutputChannels->rpm = rpm;
+ tsOutputChannels->coolant_temperature = coolant;
+ tsOutputChannels->intake_air_temperature = intake;
+ tsOutputChannels->throttle_positon = tps;
+ tsOutputChannels->mass_air_flow = getMaf();
+ tsOutputChannels->air_fuel_ratio = getAfr();
+ tsOutputChannels->v_batt = getVBatt();
+ tsOutputChannels->tpsADC = getTPS10bitAdc();
+ tsOutputChannels->atmospherePressure = getBaroPressure();
+ tsOutputChannels->manifold_air_pressure = getMap();
+ tsOutputChannels->checkEngine = hasErrorCodes();
+#if EFI_PROD_CODE
+ tsOutputChannels->needBurn = getNeedToWriteConfiguration();
+ tsOutputChannels->hasSdCard = isSdCardAlive();
+ tsOutputChannels->isFuelPumpOn = getOutputPinValue(FUEL_PUMP_RELAY);
+ tsOutputChannels->isFanOn = getOutputPinValue(FAN_RELAY);
+ tsOutputChannels->isO2HeaterOn = getOutputPinValue(O2_HEATER);
+ tsOutputChannels->ignition_enabled = engineConfiguration->isIgnitionEnabled;
+ tsOutputChannels->injection_enabled = engineConfiguration->isInjectionEnabled;
+ tsOutputChannels->cylinder_cleanup_enabled = engineConfiguration->isCylinderCleanupEnabled;
+ tsOutputChannels->secondTriggerChannelEnabled = engineConfiguration->secondTriggerChannelEnabled;
+
+ tsOutputChannels->isCltError = !isValidCoolantTemperature(getCoolantTemperature());
+ tsOutputChannels->isIatError = !isValidIntakeAirTemperature(getIntakeAirTemperature());
+#endif
+ tsOutputChannels->tCharge = getTCharge(rpm, tps, coolant, intake);
+ tsOutputChannels->sparkDwell = getSparkDwellMs(rpm);
+ tsOutputChannels->pulseWidth = getRunningFuel(baseFuel, &engine, rpm);
+ tsOutputChannels->crankingFuel = getCrankingFuel();
+}
+
+extern TunerStudioOutputChannels tsOutputChannels;
+#endif /* EFI_TUNER_STUDIO */
+
+static void tsStatusThread(void *arg) {
+ chRegSetThreadName("tuner s");
+ while (true) {
+#if EFI_TUNER_STUDIO
+ // sensor state for EFI Analytics Tuner Studio
+ updateTunerStudioState(&tsOutputChannels);
+#endif /* EFI_TUNER_STUDIO */
+ chThdSleepMilliseconds(boardConfiguration->tunerStudioThreadPeriod);
+ }
+}
+
+void initStatusLoop(void) {
+#if EFI_PROD_CODE || EFI_SIMULATOR
+ initLoggingExt(&logger, "status loop", LOGGING_BUFFER, sizeof(LOGGING_BUFFER));
+#endif /* EFI_PROD_CODE || EFI_SIMULATOR */
+
+ setFullLog(INITIAL_FULL_LOG);
+ addConsoleActionI(FULL_LOGGING_KEY, setFullLog);
+ addConsoleActionI("warn", setWarningEnabled);
+
+#if EFI_PROD_CODE
+ initLogging(&logger2, "main event handler");
+
+ addConsoleActionFF("fuelinfo2", showFuelMap2);
+ addConsoleAction("fuelinfo", showFuelMap);
+
+ addConsoleAction("status", printStatus);
+#endif /* EFI_PROD_CODE */
+
+#if EFI_FILE_LOGGING
+ initLogging(&fileLogger, "file logger");
+#endif /* EFI_FILE_LOGGING */
+
+}
+
+void startStatusThreads(void) {
+ // todo: refactoring needed, this file should probably be split into pieces
+ chThdCreateStatic(lcdThreadStack, sizeof(lcdThreadStack), NORMALPRIO, (tfunc_t) lcdThread, (void*) NULL);
+ chThdCreateStatic(tsThreadStack, sizeof(tsThreadStack), NORMALPRIO, (tfunc_t) tsStatusThread, (void*) NULL);
+}
+
+void setFullLog(int value) {
+ print("Setting full logging: %s\r\n", boolean2string(value));
+ printMsg(&logger, "%s%d", FULL_LOGGING_KEY, value);
+ fullLog = value;
+}
+
+bool getFullLog(void) {
+ return fullLog;
+}
diff --git a/firmware/console/status_loop.h b/firmware/console/status_loop.h
new file mode 100644
index 0000000000..dd884aefd0
--- /dev/null
+++ b/firmware/console/status_loop.h
@@ -0,0 +1,30 @@
+/**
+ * @file status_loop.h
+ *
+ * @date Mar 15, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef CONSOLE_LOOP_H_
+#define CONSOLE_LOOP_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void printState(int currentCkpEventCounter);
+
+void initStatusLoop(void);
+void updateDevConsoleState(void);
+bool getFullLog(void);
+void printSensors(void);
+void setFullLog(int value);
+void startStatusThreads(void);
+void sayOsHello(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* CONSOLE_LOOP_H_ */
diff --git a/firmware/console/tunerstudio/tunerstudio.cpp b/firmware/console/tunerstudio/tunerstudio.cpp
new file mode 100644
index 0000000000..b0920a7d62
--- /dev/null
+++ b/firmware/console/tunerstudio/tunerstudio.cpp
@@ -0,0 +1,526 @@
+/**
+ * @file tunerstudio.cpp
+ * @brief Integration with EFI Analytics Tuner Studio software
+ *
+ * todo: merge this file with tunerstudio_algo.c?
+ *
+ * @date Aug 26, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ *
+ */
+
+#include "main.h"
+
+#include "engine_state.h"
+#include "tunerstudio.h"
+
+#include "main_trigger_callback.h"
+#include "flash_main.h"
+
+#include "tunerstudio_algo.h"
+#include "tunerstudio_configuration.h"
+#include "malfunction_central.h"
+#include "console_io.h"
+#include "crc.h"
+
+#if EFI_TUNER_STUDIO
+
+#if EFI_PROD_CODE
+#include "pin_repository.h"
+#include "usbconsole.h"
+#include "map_averaging.h"
+extern SerialUSBDriver SDU1;
+#define CONSOLE_DEVICE &SDU1
+
+#define TS_SERIAL_UART_DEVICE &SD3
+//#define TS_SERIAL_SPEED 115200
+#define TS_SERIAL_SPEED 38400
+
+static SerialConfig tsSerialConfig = { TS_SERIAL_SPEED, 0, USART_CR2_STOP1_BITS | USART_CR2_LINEN, 0 };
+#endif /* EFI_PROD_CODE */
+
+#define MAX_PAGE_ID 0
+#define PAGE_0_SIZE 5928
+#define TS_OUTPUT_SIZE 116
+
+// in MS, that's 10 seconds
+#define TS_READ_TIMEOUT 10000
+
+#define PROTOCOL "001"
+
+BaseChannel * getTsSerialDevice(void) {
+#if EFI_PROD_CODE
+ if (isSerialOverUart()) {
+ // if console uses UART then TS uses USB
+ return (BaseChannel *) &SDU1;
+ } else {
+ return (BaseChannel *) TS_SERIAL_UART_DEVICE;
+ }
+#else
+ return (BaseChannel *) TS_SIMULATOR_PORT;
+#endif
+}
+
+static Logging logger;
+
+extern persistent_config_s configWorkingCopy;
+extern persistent_config_container_s persistentState;
+
+static efitimems_t previousWriteReportMs = 0;
+
+static int ts_serail_ready(void) {
+#if EFI_PROD_CODE
+ if (isSerialOverUart()) {
+ // TS uses USB when console uses serial
+ return is_usb_serial_ready();
+ } else {
+ // TS uses serial when console uses USB
+ return TRUE;
+ }
+#else
+ return TRUE;
+#endif
+}
+
+static THD_WORKING_AREA(TS_WORKING_AREA, UTILITY_THREAD_STACK_SIZE);
+
+static int tsCounter = 0;
+
+//static TunerStudioWriteValueRequest writeValueRequest;
+//static TunerStudioWriteChunkRequest writeChunkRequest;
+
+extern TunerStudioOutputChannels tsOutputChannels;
+
+extern TunerStudioState tsState;
+
+extern engine_configuration_s *engineConfiguration;
+extern board_configuration_s *boardConfiguration;
+
+
+static void printStats(void) {
+#if EFI_PROD_CODE
+ if (!isSerialOverUart()) {
+ scheduleMsg(&logger, "TS RX on %s%d/TX on %s%d", portname(TS_SERIAL_RX_PORT), TS_SERIAL_RX_PIN,
+ portname(TS_SERIAL_TX_PORT), TS_SERIAL_TX_PIN);
+ }
+#endif /* EFI_PROD_CODE */
+ scheduleMsg(&logger, "TunerStudio total/error counter=%d/%d H=%d / O counter=%d size=%d / P=%d / B=%d", tsCounter, tsState.errorCounter, tsState.queryCommandCounter, tsState.outputChannelsCommandCounter,
+ sizeof(tsOutputChannels), tsState.readPageCommandsCounter, tsState.burnCommandCounter);
+ scheduleMsg(&logger, "TunerStudio W counter=%d / C = %d / P = %d / current page %d", tsState.writeValueCommandCounter, tsState.writeChunkCommandCounter,
+ tsState.pageCommandCounter, tsState.currentPageId);
+ scheduleMsg(&logger, "page size=%d", sizeof(engine_configuration_s));
+
+// scheduleMsg(&logger, "analogChartFrequency %d",
+// (int) (&engineConfiguration->analogChartFrequency) - (int) engineConfiguration);
+//
+// int fuelMapOffset = (int) (&engineConfiguration->fuelTable) - (int) engineConfiguration;
+// scheduleMsg(&logger, "fuelTable %d", fuelMapOffset);
+//
+// int offset = (int) (&boardConfiguration->o2heaterPin) - (int) engineConfiguration;
+// scheduleMsg(&logger, "o2heaterPin %d", offset);
+//
+// offset = (int) (&boardConfiguration->idleSolenoidFrequency) - (int) engineConfiguration;
+// scheduleMsg(&logger, "idleSolenoidFrequency %d", offset);
+}
+
+void tunerStudioWriteData(const uint8_t * buffer, int size) {
+ chSequentialStreamWrite(getTsSerialDevice(), buffer, size);
+}
+
+void tunerStudioDebug(const char *msg) {
+#if EFI_TUNER_STUDIO_VERBOSE
+ scheduleMsg(&logger, "%s", msg);
+ printStats();
+#endif
+}
+
+char *getWorkingPageAddr(int pageIndex) {
+ switch (pageIndex) {
+ case 0:
+ return (char*) &configWorkingCopy.engineConfiguration;
+// case 1:
+// return (char*) &configWorkingCopy.boardConfiguration;
+// case 2: // fuelTable
+// case 3: // ignitionTable
+// case 4: // veTable
+// case 5: // afrTable
+// return (char*) &configWorkingCopy.engineConfiguration + PAGE_0_SIZE + (pageIndex - 2) * 1024;
+ }
+ return NULL;
+}
+
+int getTunerStudioPageSize(int pageIndex) {
+ switch (pageIndex) {
+ case 0:
+ return PAGE_0_SIZE;
+// case 1:
+// return sizeof(configWorkingCopy.boardConfiguration);
+// case 2:
+// case 3:
+// case 4:
+// return 1024;
+ }
+ return 0;
+}
+
+void handlePageSelectCommand(ts_response_format_e mode, uint16_t pageId) {
+ tsState.pageCommandCounter++;
+
+ tsState.currentPageId = pageId;
+ scheduleMsg(&logger, "page %d selected", tsState.currentPageId);
+ tsSendResponse(mode, NULL, 0);
+}
+
+/**
+ * This command is needed to make the whole transfer a bit faster
+ * @note See also handleWriteValueCommand
+ */
+void handleWriteChunkCommand(ts_response_format_e mode, short offset, short count, void *content) {
+ tsState.writeChunkCommandCounter++;
+
+ scheduleMsg(&logger, "receiving page %d chunk offset %d size %d", tsState.currentPageId, offset, count);
+
+ if (offset > getTunerStudioPageSize(tsState.currentPageId)) {
+ scheduleMsg(&logger, "ERROR offset %d", offset);
+ tunerStudioError("ERROR: out of range");
+ offset = 0;
+ }
+
+ if (count > getTunerStudioPageSize(tsState.currentPageId)) {
+ tunerStudioError("ERROR: unexpected count");
+ scheduleMsg(&logger, "ERROR count %d", count);
+ count = 0;
+ }
+
+ uint8_t * addr = (uint8_t *) (getWorkingPageAddr(tsState.currentPageId) + offset);
+ memcpy(addr, content, count);
+
+ tsSendResponse(mode, NULL, 0);
+}
+
+/**
+ * 'Write' command receives a single value at a given offset
+ * @note Writing values one by one is pretty slow
+ */
+void handleWriteValueCommand(ts_response_format_e mode, uint16_t page, uint16_t offset, uint8_t value) {
+ tsState.writeValueCommandCounter++;
+
+ tsState.currentPageId = page;
+
+//tunerStudioDebug("got W (Write)"); // we can get a lot of these
+
+#if EFI_TUNER_STUDIO_VERBOSE
+// scheduleMsg(&logger, "Page number %d\r\n", pageId); // we can get a lot of these
+#endif
+
+// int size = sizeof(TunerStudioWriteValueRequest);
+// scheduleMsg(&logger, "Reading %d\r\n", size);
+
+ if (offset > getTunerStudioPageSize(tsState.currentPageId)) {
+ tunerStudioError("ERROR: out of range2");
+ scheduleMsg(&logger, "ERROR offset %d", offset);
+ offset = 0;
+ return;
+ }
+
+ efitimems_t nowMs = currentTimeMillis();
+ if (nowMs - previousWriteReportMs > 5) {
+ previousWriteReportMs = nowMs;
+ scheduleMsg(&logger, "page %d offset %d: value=%d", tsState.currentPageId, offset, value);
+ }
+
+ getWorkingPageAddr(tsState.currentPageId)[offset] = value;
+
+// scheduleMsg(&logger, "va=%d", configWorkingCopy.boardConfiguration.idleValvePin);
+}
+
+static void sendErrorCode(void) {
+ tunerStudioWriteCrcPacket(TS_RESPONSE_CRC_FAILURE, NULL, 0);
+}
+
+void handlePageReadCommand(ts_response_format_e mode, uint16_t pageId, uint16_t offset, uint16_t count) {
+ tsState.readPageCommandsCounter++;
+ tunerStudioDebug("got R (Read page)");
+ tsState.currentPageId = pageId;
+
+#if EFI_TUNER_STUDIO_VERBOSE
+ scheduleMsg(&logger, "Page requested: page %d offset=%d count=%d", (int)tsState.currentPageId, offset, count);
+#endif
+
+ if (tsState.currentPageId > MAX_PAGE_ID) {
+ scheduleMsg(&logger, "invalid Page number %x", tsState.currentPageId);
+
+ // something is not right here
+ tsState.currentPageId = 0;
+ tunerStudioError("ERROR: invalid page");
+ return;
+ }
+
+ int size = getTunerStudioPageSize(tsState.currentPageId);
+
+ if (size < offset + count) {
+ scheduleMsg(&logger, "invalid offset/count %d/%d", offset, count);
+ sendErrorCode();
+ return;
+ }
+
+ const uint8_t *addr = (const uint8_t *) (getWorkingPageAddr(tsState.currentPageId) + offset);
+ tsSendResponse(mode, addr, count);
+#if EFI_TUNER_STUDIO_VERBOSE
+ scheduleMsg(&logger, "Sending %d done", count);
+#endif
+}
+
+/**
+ * 'Burn' command is a command to commit the changes
+ */
+void handleBurnCommand(ts_response_format_e mode, uint16_t page) {
+ efitimems_t nowMs = currentTimeMillis();
+ tsState.burnCommandCounter++;
+
+ tunerStudioDebug("got B (Burn)");
+
+ tsState.currentPageId = page;
+
+#if EFI_TUNER_STUDIO_VERBOSE
+ // pointless since we only have one page now
+// scheduleMsg(&logger, "Page number %d", tsState.currentPageId);
+#endif
+
+// todo: how about some multi-threading?
+ memcpy(&persistentState.persistentConfiguration, &configWorkingCopy, sizeof(persistent_config_s));
+
+#if EFI_INTERNAL_FLASH
+ setNeedToWriteConfiguration();
+#endif
+ incrementGlobalConfigurationVersion();
+ tunerStudioWriteCrcPacket(TS_RESPONSE_BURN_OK, NULL, 0);
+ scheduleMsg(&logger, "burned in (ms): %d", currentTimeMillis() - nowMs);
+}
+
+static TunerStudioReadRequest readRequest;
+static short int pageIn;
+
+static bool handlePlainCommand(uint8_t command) {
+ if (command == TS_HELLO_COMMAND) {
+ scheduleMsg(&logger, "Got naked Query command");
+ handleQueryCommand(TS_PLAIN);
+ return true;
+ } else if (command == 't' || command == 'T') {
+ handleTestCommand();
+ return true;
+ } else if (command == TS_PAGE_COMMAND) {
+ int recieved = chSequentialStreamRead(getTsSerialDevice(), (uint8_t *)&pageIn, sizeof(pageIn));
+ // todo: validate 'recieved' value
+ handlePageSelectCommand(TS_PLAIN, pageIn);
+ return true;
+ } else if (command == TS_READ_COMMAND) {
+ //scheduleMsg(&logger, "Got naked READ PAGE???");
+ int recieved = chSequentialStreamRead(getTsSerialDevice(), (uint8_t *)&readRequest, sizeof(readRequest));
+ if (recieved != sizeof(readRequest)) {
+ // todo: handler error
+ return true;
+ }
+ handlePageReadCommand(TS_PLAIN, readRequest.page, readRequest.offset, readRequest.count);
+ return true;
+ } else if (command == TS_OUTPUT_COMMAND) {
+ //scheduleMsg(&logger, "Got naked Channels???");
+ handleOutputChannelsCommand(TS_PLAIN);
+ return true;
+ } else if (command == 'F') {
+ tunerStudioDebug("not ignoring F");
+ tunerStudioWriteData((const uint8_t *) PROTOCOL, strlen(PROTOCOL));
+ return true;
+ } else {
+ return false;
+ }
+}
+
+static bool isKnownCommand(char command) {
+ return command == TS_HELLO_COMMAND || command == TS_READ_COMMAND || command == TS_OUTPUT_COMMAND
+ || command == TS_PAGE_COMMAND || command == TS_BURN_COMMAND || command == TS_SINGLE_WRITE_COMMAND
+ || command == TS_CHUNK_WRITE_COMMAND;
+}
+
+static uint8_t firstByte;
+static uint8_t secondByte;
+
+#define CRC_VALUE_SIZE 4
+// todo: double-check this
+#define CRC_WRAPPING_SIZE 7
+
+/**
+ * we use 'blockingFactor = 256' in rusefi.ini
+ * todo: should we just do (256 + CRC_WRAPPING_SIZE) ?
+ */
+
+static uint8_t crcIoBuffer[300];
+
+
+
+
+static msg_t tsThreadEntryPoint(void *arg) {
+ (void) arg;
+ chRegSetThreadName("tunerstudio thread");
+
+ int wasReady = false;
+ while (true) {
+ int isReady = ts_serail_ready();
+ if (!isReady) {
+ chThdSleepMilliseconds(10);
+ wasReady = false;
+ continue;
+ }
+
+ if (!wasReady) {
+ wasReady = TRUE;
+// scheduleSimpleMsg(&logger, "ts channel is now ready ", hTimeNow());
+ }
+
+ tsCounter++;
+
+ int recieved = chSequentialStreamRead(getTsSerialDevice(), &firstByte, 1);
+ if (recieved != 1) {
+ tunerStudioError("ERROR: no command");
+ continue;
+ }
+// scheduleMsg(&logger, "Got first=%x=[%c]", firstByte, firstByte);
+ if (handlePlainCommand(firstByte))
+ continue;
+
+ recieved = chSequentialStreamRead(getTsSerialDevice(), &secondByte, 1);
+ if (recieved != 1) {
+ tunerStudioError("ERROR: no second");
+ continue;
+ }
+// scheduleMsg(&logger, "Got secondByte=%x=[%c]", secondByte, secondByte);
+
+ uint32_t incomingPacketSize = firstByte * 256 + secondByte;
+
+ if (incomingPacketSize == 0 || incomingPacketSize > (sizeof(crcIoBuffer) - CRC_WRAPPING_SIZE)) {
+ scheduleMsg(&logger, "TunerStudio: invalid size: %d", incomingPacketSize);
+ tunerStudioError("ERROR: size");
+ sendErrorCode();
+ continue;
+ }
+
+ recieved = chnReadTimeout(getTsSerialDevice(), crcIoBuffer, 1, MS2ST(TS_READ_TIMEOUT));
+ if (recieved != 1) {
+ tunerStudioError("ERROR: did not receive command");
+ continue;
+ }
+
+ char command = crcIoBuffer[0];
+ if (!isKnownCommand(command)) {
+ scheduleMsg(&logger, "unexpected command %x", command);
+ sendErrorCode();
+ continue;
+ }
+
+// scheduleMsg(&logger, "TunerStudio: reading %d+4 bytes(s)", incomingPacketSize);
+
+ recieved = chnReadTimeout(getTsSerialDevice(), (uint8_t * ) (crcIoBuffer + 1), incomingPacketSize + CRC_VALUE_SIZE - 1,
+ MS2ST(TS_READ_TIMEOUT));
+ int expectedSize = incomingPacketSize + CRC_VALUE_SIZE - 1;
+ if (recieved != expectedSize) {
+ scheduleMsg(&logger, "got ONLY %d for packet size %d/%d for command %c", recieved, incomingPacketSize,
+ expectedSize, command);
+ tunerStudioError("ERROR: not enough");
+ continue;
+ }
+
+ uint32_t expectedCrc = *(uint32_t*) (crcIoBuffer + incomingPacketSize);
+
+ expectedCrc = SWAP_UINT32(expectedCrc);
+
+ uint32_t actualCrc = crc32(crcIoBuffer, incomingPacketSize);
+ if (actualCrc != expectedCrc) {
+ scheduleMsg(&logger, "TunerStudio: CRC %x %x %x %x", crcIoBuffer[incomingPacketSize + 0],
+ crcIoBuffer[incomingPacketSize + 1], crcIoBuffer[incomingPacketSize + 2],
+ crcIoBuffer[incomingPacketSize + 3]);
+
+ scheduleMsg(&logger, "TunerStudio: command %c actual CRC %x/expected %x", crcIoBuffer[0], actualCrc,
+ expectedCrc);
+ tunerStudioError("ERROR: CRC issue");
+ continue;
+ }
+
+// scheduleMsg(&logger, "TunerStudio: P00-07 %x %x %x %x %x %x %x %x", crcIoBuffer[0], crcIoBuffer[1],
+// crcIoBuffer[2], crcIoBuffer[3], crcIoBuffer[4], crcIoBuffer[5], crcIoBuffer[6], crcIoBuffer[7]);
+
+ int success = tunerStudioHandleCrcCommand(crcIoBuffer, incomingPacketSize);
+ if (!success)
+ print("got unexpected TunerStudio command %x:%c\r\n", command, command);
+
+ }
+#if defined __GNUC__
+ return 0;
+#endif
+}
+
+void syncTunerStudioCopy(void) {
+ memcpy(&configWorkingCopy, &persistentState.persistentConfiguration, sizeof(persistent_config_s));
+}
+
+void startTunerStudioConnectivity(void) {
+ initLogging(&logger, "tuner studio");
+
+ if (sizeof(engine_configuration_s) != getTunerStudioPageSize(0))
+ firmwareError("TS page size mismatch: %d/%d", sizeof(engine_configuration_s), getTunerStudioPageSize(0));
+
+ if (sizeof(TunerStudioOutputChannels) != TS_OUTPUT_SIZE)
+ firmwareError("TS outputs size mismatch: %d/%d", sizeof(TunerStudioOutputChannels), TS_OUTPUT_SIZE);
+
+ memset(&tsState, 0, sizeof(tsState));
+#if EFI_PROD_CODE
+ if (isSerialOverUart()) {
+ print("TunerStudio over USB serial");
+ usb_serial_start();
+ } else {
+
+ print("TunerStudio over USART");
+ mySetPadMode("tunerstudio rx", TS_SERIAL_RX_PORT, TS_SERIAL_RX_PIN, PAL_MODE_ALTERNATE(TS_SERIAL_AF));
+ mySetPadMode("tunerstudio tx", TS_SERIAL_TX_PORT, TS_SERIAL_TX_PIN, PAL_MODE_ALTERNATE(TS_SERIAL_AF));
+
+ sdStart(TS_SERIAL_UART_DEVICE, &tsSerialConfig);
+ }
+#endif /* EFI_PROD_CODE */
+ syncTunerStudioCopy();
+
+ addConsoleAction("tsinfo", printStats);
+
+ chThdCreateStatic(TS_WORKING_AREA, sizeof(TS_WORKING_AREA), NORMALPRIO, tsThreadEntryPoint, NULL);
+}
+
+/**
+ * Adds size to the beginning of a packet and a crc32 at the end. Then send the packet.
+ */
+void tunerStudioWriteCrcPacket(const uint8_t command, const void *buf, const uint16_t size) {
+ // todo: max size validation
+ *(uint16_t *) crcIoBuffer = SWAP_UINT16(size + 1); // packet size including command
+ *(uint8_t *) (crcIoBuffer + 2) = command;
+ if (size != 0)
+ memcpy(crcIoBuffer + 3, buf, size);
+ // CRC on whole packet
+ uint32_t crc = crc32((void *) (crcIoBuffer + 2), (uint32_t) (size + 1));
+ *(uint32_t *) (crcIoBuffer + 2 + 1 + size) = SWAP_UINT32(crc);
+
+// scheduleMsg(&logger, "TunerStudio: CRC command %x size %d", command, size);
+
+ tunerStudioWriteData(crcIoBuffer, size + 2 + 1 + 4); // with size, command and CRC
+}
+
+#endif /* EFI_TUNER_STUDIO */
diff --git a/firmware/console/tunerstudio/tunerstudio.h b/firmware/console/tunerstudio/tunerstudio.h
new file mode 100644
index 0000000000..28970f2510
--- /dev/null
+++ b/firmware/console/tunerstudio/tunerstudio.h
@@ -0,0 +1,55 @@
+/**
+ * @file tunerstudio.h
+ *
+ * @date Aug 26, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef TUNERSTUDIO_H_
+#define TUNERSTUDIO_H_
+
+#include "tunerstudio_configuration.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+void startTunerStudioConnectivity(void);
+void syncTunerStudioCopy(void);
+void updateTunerStudioState(TunerStudioOutputChannels *tsOutputChannels);
+void tunerStudioWriteCrcPacket(const uint8_t command, const void *buf, const uint16_t size);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#if defined __GNUC__
+// GCC
+#define pre_packed
+#define post_packed __attribute__((packed))
+#else
+// IAR
+#define pre_packed __packed
+#define post_packed
+#endif
+
+typedef pre_packed struct
+ post_packed {
+ short int offset;
+ short int count;
+ } TunerStudioWriteChunkRequest;
+
+ typedef pre_packed struct
+ post_packed {
+ short int page;
+ short int offset;
+ short int count;
+ } TunerStudioReadRequest;
+
+ typedef pre_packed struct
+ post_packed {
+ short int offset;
+ unsigned char value;
+ } TunerStudioWriteValueRequest;
+
+#endif /* TUNERSTUDIO_H_ */
diff --git a/firmware/console/tunerstudio/tunerstudio.mk b/firmware/console/tunerstudio/tunerstudio.mk
new file mode 100644
index 0000000000..b57efb9b1c
--- /dev/null
+++ b/firmware/console/tunerstudio/tunerstudio.mk
@@ -0,0 +1,3 @@
+
+TUNERSTUDIO_SRC_CPP = $(PROJECT_DIR)/console/tunerstudio/tunerstudio_algo.cpp \
+ $(PROJECT_DIR)/console/tunerstudio/tunerstudio.cpp
diff --git a/firmware/console/tunerstudio/tunerstudio_algo.cpp b/firmware/console/tunerstudio/tunerstudio_algo.cpp
new file mode 100644
index 0000000000..a9769b84d4
--- /dev/null
+++ b/firmware/console/tunerstudio/tunerstudio_algo.cpp
@@ -0,0 +1,160 @@
+/**
+ * @file tunerstudio_algo.cpp
+ * @brief Tuner Studio plain protocol implementation
+ *
+ * This implementation would not happen without the documentation
+ * provided by Jon Zeeff (jon@zeeff.com)
+ *
+ * Tuner Studio has a really simple protocol, a minimal implementation
+ * capable of displaying current engine state on the gauges would
+ * require only two commands: queryCommand and ochGetCommand
+ *
+ * queryCommand:
+ * Communication initialization command. TunerStudio sends a single byte H
+ * ECU response:
+ * One of the known ECU id strings. We are using "MShift v0.01" id string.
+ *
+ * ochGetCommand:
+ * Request for output channels state.TunerStudio sends a single byte O
+ * ECU response:
+ * A snapshot of output channels as described in [OutputChannels] section of the .ini file
+ * The length of this block is 'ochBlockSize' property of the .ini file
+ *
+ * These two commands are enough to get working gauges. In order to start configuring the ECU using
+ * tuner studio, three more commands should be implemented:
+ *
+ * todo: merge this file with tunerstudio.c?
+ *
+ *
+ * @date Oct 22, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ */
+
+#include
+#include "tunerstudio_algo.h"
+#include "tunerstudio_configuration.h"
+#include "engine_configuration.h"
+#include "tunerstudio.h"
+#include "svnversion.h"
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+#ifndef TRUE
+#define TRUE (!FALSE)
+#endif
+
+TunerStudioState tsState;
+TunerStudioOutputChannels tsOutputChannels;
+/**
+ * this is a local copy of the configuration. Any changes to this copy
+ * have no effect until this copy is explicitly propagated to the main working copy
+ */
+persistent_config_s configWorkingCopy;
+
+void tunerStudioError(const char *msg) {
+ tunerStudioDebug(msg);
+ tsState.errorCounter++;
+}
+
+int tunerStudioHandleCrcCommand(uint8_t *data, int incomingPacketSize) {
+ char command = data[0];
+ data++;
+ if (command == TS_HELLO_COMMAND) {
+ tunerStudioDebug("got CRC Query");
+ handleQueryCommand(TS_CRC);
+ } else if (command == TS_OUTPUT_COMMAND) {
+ handleOutputChannelsCommand(TS_CRC);
+ } else if (command == TS_PAGE_COMMAND) {
+ uint16_t page = *(uint16_t *) data;
+ handlePageSelectCommand(TS_CRC, page);
+ } else if (command == TS_CHUNK_WRITE_COMMAND) {
+ uint16_t offset = *(uint16_t *) data;
+ uint16_t count = *(uint16_t *) (data + 2);
+ handleWriteChunkCommand(TS_CRC, offset, count, data + 4);
+ } else if (command == TS_SINGLE_WRITE_COMMAND) {
+ uint16_t page = *(uint16_t *) data;
+ uint16_t offset = *(uint16_t *) (data + 2);
+ uint8_t value = data[4];
+ handleWriteValueCommand(TS_CRC, page, offset, value);
+ } else if (command == TS_BURN_COMMAND) {
+ uint16_t page = *(uint16_t *) data;
+ handleBurnCommand(TS_CRC, page);
+ } else if (command == TS_READ_COMMAND) {
+ uint16_t page = *(uint16_t *) data;
+ uint16_t offset = *(uint16_t *) (data + 2);
+ uint16_t count = *(uint16_t *) (data + 4);
+ handlePageReadCommand(TS_CRC, page, offset, count);
+ } else if (command == 't' || command == 'T') {
+ handleTestCommand();
+ } else if (command == 'F') {
+ tunerStudioDebug("ignoring F");
+ /**
+ * http://www.msextra.com/forums/viewtopic.php?f=122&t=48327
+ * Response from TS support: This is an optional command *
+ * "The F command is used to find what ini. file needs to be loaded in TunerStudio to match the controller.
+ * If you are able to just make your firmware ignore the command that would work.
+ * Currently on some firmware versions the F command is not used and is just ignored by the firmware as a unknown command."
+ */
+ } else {
+ tunerStudioError("ERROR: ignoring unexpected command");
+ return FALSE;
+ }
+ return TRUE;
+}
+
+void tsSendResponse(ts_response_format_e mode, const uint8_t * buffer, int size) {
+ if (mode == TS_CRC) {
+ tunerStudioWriteCrcPacket(TS_RESPONSE_OK, buffer, size);
+ } else {
+ if (size > 0)
+ tunerStudioWriteData(buffer, size);
+ }
+}
+
+/**
+ * Query with CRC takes place while re-establishing connection
+ * Query without CRC takes place on TunerStudio startup
+ */
+void handleQueryCommand(ts_response_format_e mode) {
+ tsState.queryCommandCounter++;
+ tunerStudioDebug("got H (queryCommand)");
+ tsSendResponse(mode, (const uint8_t *) TS_SIGNATURE, strlen(TS_SIGNATURE) + 1);
+}
+
+/**
+ * @brief 'Output' command sends out a snapshot of current values
+ */
+void handleOutputChannelsCommand(ts_response_format_e mode) {
+ tsState.outputChannelsCommandCounter++;
+ // this method is invoked too often to print any debug information
+ tsSendResponse(mode, (const uint8_t *) &tsOutputChannels, sizeof(TunerStudioOutputChannels));
+}
+
+void handleTestCommand(void) {
+ /**
+ * this is NOT a standard TunerStudio command, this is my own
+ * extension of the protocol to simplify troubleshooting
+ */
+ tunerStudioDebug("got T (Test)");
+ tunerStudioWriteData((const uint8_t *)VCS_VERSION, sizeof(VCS_VERSION));
+ /**
+ * Please note that this response is a magic constant used by dev console for protocol detection
+ * @see EngineState#TS_PROTOCOL_TAG
+ */
+ tunerStudioWriteData((const uint8_t *) " ts_p_alive\r\n", 8);
+}
diff --git a/firmware/console/tunerstudio/tunerstudio_algo.h b/firmware/console/tunerstudio/tunerstudio_algo.h
new file mode 100644
index 0000000000..eebc526979
--- /dev/null
+++ b/firmware/console/tunerstudio/tunerstudio_algo.h
@@ -0,0 +1,74 @@
+/**
+ * @file tunerstudio_algo.h
+ *
+ * @date Oct 22, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef TUNERSTUDIO_ALGO_H_
+#define TUNERSTUDIO_ALGO_H_
+
+#define TS_SIGNATURE "MShift v0.01"
+
+#include
+
+// http://en.wikipedia.org/wiki/Endianness
+
+#define SWAP_UINT16(x) ((x) << 8) | ((x) >> 8)
+
+#define SWAP_UINT32(x) (((x) >> 24) & 0xff) | (((x) << 8) & 0xff0000) | (((x) >> 8) & 0xff00) | (((x) << 24) & 0xff000000)
+
+// response codes
+
+#define TS_RESPONSE_OK 0x00
+#define TS_RESPONSE_BURN_OK 0x04
+#define TS_RESPONSE_CRC_FAILURE 0x82
+
+typedef enum {
+ TS_PLAIN = 0,
+ TS_CRC = 1
+} ts_response_format_e;
+
+typedef struct {
+ int queryCommandCounter;
+ int outputChannelsCommandCounter;
+ int readPageCommandsCounter;
+ int burnCommandCounter;
+ int pageCommandCounter;
+ int writeValueCommandCounter;
+ int writeChunkCommandCounter;
+ int errorCounter;
+ // this field is in the end to simply aligning situation
+ short currentPageId;
+} TunerStudioState;
+
+int tunerStudioHandleCrcCommand(uint8_t *data, int incomingPacketSize);
+
+void handleTestCommand(void);
+void handleQueryCommand(ts_response_format_e mode);
+void tsSendResponse(ts_response_format_e mode, const uint8_t * buffer, int size);
+void handleOutputChannelsCommand(ts_response_format_e mode);
+
+char *getWorkingPageAddr(int pageIndex);
+int getTunerStudioPageSize(int pageIndex);
+void handleWriteValueCommand(ts_response_format_e mode, uint16_t page, uint16_t offset, uint8_t value);
+void handleWriteChunkCommand(ts_response_format_e mode, short offset, short count, void *content);
+void handlePageSelectCommand(ts_response_format_e mode, uint16_t pageId);
+void handlePageReadCommand(ts_response_format_e mode, uint16_t pageId, uint16_t offset, uint16_t count);
+void handleBurnCommand(ts_response_format_e mode, uint16_t page);
+
+void tunerStudioWriteData(const uint8_t * buffer, int size);
+void tunerStudioDebug(const char *msg);
+
+void tunerStudioError(const char *msg);
+
+#define TS_HELLO_COMMAND 'H'
+#define TS_OUTPUT_COMMAND 'O'
+#define TS_READ_COMMAND 'R'
+#define TS_PAGE_COMMAND 'P'
+
+#define TS_SINGLE_WRITE_COMMAND 'W'
+#define TS_CHUNK_WRITE_COMMAND 'C'
+#define TS_BURN_COMMAND 'B'
+
+#endif /* TUNERSTUDIO_ALGO_H_ */
diff --git a/firmware/console/tunerstudio/tunerstudio_configuration.h b/firmware/console/tunerstudio/tunerstudio_configuration.h
new file mode 100644
index 0000000000..aae27e65be
--- /dev/null
+++ b/firmware/console/tunerstudio/tunerstudio_configuration.h
@@ -0,0 +1,62 @@
+/*
+ * @file tunerstudio_configuration.h
+ * @brief Tuner Studio connectivity configuration
+ *
+ * In this file the configuration of TunerStudio is defined
+ *
+ * @date Oct 22, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef TUNERSTUDIO_CONFIGURATION_H_
+#define TUNERSTUDIO_CONFIGURATION_H_
+
+/**
+ * please be aware that current "stable" version of TunerStudio does not
+ * support 'float' (F32) type. You would need a beta version to handle floats
+ */
+typedef struct {
+ // primary instrument cluster gauges
+ int rpm; // size 4, offset 0
+ float coolant_temperature; // size 4, offset 4
+ float intake_air_temperature; // size 4, offset 8
+ float throttle_positon; // size 4, offset 12
+ float mass_air_flow; // size 4, offset 16
+ float air_fuel_ratio; // size 4, offset 20
+ float fuel_load; // size 4, offset 24
+ float v_batt; // size 4, offset 28
+ short int tpsADC; // size 2, offset 32
+ short int alignment; // size 2, offset 34
+ float atmospherePressure; // size 4, offset 36
+ float manifold_air_pressure; // size 4, offset 40
+ float crankingFuel;
+ int tpsVolrage;
+ float tCharge; // 52
+ float inj_adv; // 56
+ float sparkDwell; // 60
+ float pulseWidth; // 64
+ float warmUpEnrich; // 68
+ /**
+ * Yes, I do not really enjoy packing bits into integers but we simply have too many boolean flags and I cannot
+ * water 4 bytes per trafic - I want gauges to work as fast as possible
+ */
+ unsigned int hasSdCard : 1; // bit 0
+ unsigned int ignition_enabled : 1; // bit 1
+ unsigned int injection_enabled : 1; // bit 2
+ unsigned int cylinder_cleanup_enabled : 1; // bit 3
+ unsigned int cylinder_cleanup : 1; // bit 4
+ unsigned int isFuelPumpOn : 1; // bit 5
+ unsigned int isFanOn : 1; // bit 6
+ unsigned int isO2HeaterOn : 1; // bit 7
+ unsigned int checkEngine : 1; // bit 8
+ unsigned int needBurn : 1; // bit 9
+ unsigned int secondTriggerChannelEnabled : 1; // bit 10
+ int unused2;
+ unsigned int isTpsError : 1; // bit 0
+ unsigned int isCltError : 1; // bit 1
+ unsigned int isMapError : 1; // bit 2
+ unsigned int isIatError : 1; // bit 3
+ int unused[8];
+} TunerStudioOutputChannels;
+
+#endif /* TUNERSTUDIO_CONFIGURATION_H_ */
diff --git a/firmware/console_util/console_util.mk b/firmware/console_util/console_util.mk
new file mode 100644
index 0000000000..7a75ff1361
--- /dev/null
+++ b/firmware/console_util/console_util.mk
@@ -0,0 +1,3 @@
+
+CONSOLEUTILSRC = $(PROJECT_DIR)/console_util/datalogging.c \
+ $(PROJECT_DIR)/console_util/rfiutil.c
\ No newline at end of file
diff --git a/firmware/console_util/datalogging.c b/firmware/console_util/datalogging.c
new file mode 100644
index 0000000000..63987f6e47
--- /dev/null
+++ b/firmware/console_util/datalogging.c
@@ -0,0 +1,414 @@
+/**
+ * @file datalogging.c
+ * @brief Buffered console output stream code
+ *
+ * Here we have a memory buffer and method related to
+ * printing messages into this buffer. The purpose of the
+ * buffer is to allow fast, non-blocking, thread-safe logging.
+ *
+ * The idea is that each interrupt handler would have it's own logging buffer. You can add
+ * stuff into this buffer without any locking since it's you own buffer, and once you get
+ * the whole message you invoke the scheduleLogging() method which appends your local content
+ * into the global logging buffer, from which it is later dispatched to the console by our
+ * main console thread.
+ *
+ * @date Feb 25, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ *
+ */
+
+#include
+#include "main.h"
+#include "rfiutil.h"
+#include "chprintf.h"
+#include "chmtx.h"
+#include "memstreams.h"
+#include "console_io.h"
+
+/**
+ * This is the size of the MemoryStream used by chvprintf
+ */
+#define INTERMEDIATE_LOGGING_BUFFER_SIZE 2000
+
+// we use this magic constant to make sure it's not just a random non-zero int in memory
+#define MAGIC_LOGGING_FLAG 45234441
+
+/**
+ * This is the buffer into which all the data providers write
+ */
+static char pendingBuffer[DL_OUTPUT_BUFFER] CCM_OPTIONAL
+;
+
+/**
+ * We copy all the pending data into this buffer once we are ready to push it out
+ */
+static char outputBuffer[DL_OUTPUT_BUFFER];
+
+static MemoryStream intermediateLoggingBuffer;
+static uint8_t intermediateLoggingBufferData[INTERMEDIATE_LOGGING_BUFFER_SIZE] CCM_OPTIONAL
+;
+//todo define max-printf-buffer
+static bool intermediateLoggingBufferInited = FALSE;
+
+static int validateBuffer(Logging *logging, uint32_t extraLen, const char *text) {
+ if (logging->buffer == NULL) {
+ firmwareError("Logging not initialized: %s", logging->name);
+ return TRUE;
+ }
+
+ if (remainingSize(logging) < extraLen + 1) {
+ strcpy(logging->SMALL_BUFFER, "Logging buffer overflow: ");
+ strcat(logging->SMALL_BUFFER, logging->name);
+ strcat(logging->SMALL_BUFFER, "/");
+ strcat(logging->SMALL_BUFFER, text);
+ firmwareError(logging->SMALL_BUFFER);
+// unlockOutputBuffer();
+// resetLogging(logging);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+void append(Logging *logging, const char *text) {
+ efiAssertVoid(text != NULL, "append NULL");
+ uint32_t extraLen = strlen(text);
+ int errcode = validateBuffer(logging, extraLen, text);
+ if (errcode) {
+ return;
+ }
+ strcpy(logging->linePointer, text);
+ logging->linePointer += extraLen;
+}
+
+/**
+ * @note This method if fast because it does not validate much, be sure what you are doing
+ */
+void appendFast(Logging *logging, const char *text) {
+// todo: fix this implementation? this would be a one-pass implementation instead of a two-pass
+// char c;
+// char *s = (char *) text;
+// do {
+// c = *s++;
+// *logging->linePointer++ = c;
+// } while (c != '\0');
+ int extraLen = strlen(text);
+ strcpy(logging->linePointer, text);
+ logging->linePointer += extraLen;
+}
+
+static void vappendPrintfI(Logging *logging, const char *fmt, va_list arg) {
+ intermediateLoggingBuffer.eos = 0; // reset
+ chvprintf((BaseSequentialStream *) &intermediateLoggingBuffer, fmt, arg);
+ intermediateLoggingBuffer.buffer[intermediateLoggingBuffer.eos] = 0; // need to terminate explicitly
+ append(logging, (char *) intermediateLoggingBufferData);
+}
+
+void vappendPrintf(Logging *logging, const char *fmt, va_list arg) {
+ efiAssertVoid(getRemainingStack(chThdSelf()) > 16, "stack#5b");
+ if (!intermediateLoggingBufferInited) {
+ firmwareError("intermediateLoggingBufferInited not inited!");
+ return;
+ }
+ int is_locked = isLocked();
+ int icsr_vectactive = isIsrContext();
+ if (is_locked) {
+ vappendPrintfI(logging, fmt, arg);
+ } else {
+ if (icsr_vectactive == 0) {
+ chSysLock()
+ ;
+ vappendPrintfI(logging, fmt, arg);
+ chSysUnlock()
+ ;
+ } else {
+ chSysLockFromIsr()
+ ;
+ vappendPrintfI(logging, fmt, arg);
+ chSysUnlockFromIsr()
+ ;
+ }
+ }
+}
+
+void appendPrintf(Logging *logging, const char *fmt, ...) {
+ efiAssertVoid(getRemainingStack(chThdSelf()) > 16, "stack#4");
+ va_list ap;
+ va_start(ap, fmt);
+ vappendPrintf(logging, fmt, ap);
+ va_end(ap);
+}
+
+// todo: this method does not really belong to this file
+char* getCaption(LoggingPoints loggingPoint) {
+ switch (loggingPoint) {
+ case LP_RPM:
+ return "RPM";
+ case LP_THROTTLE:
+ return "TP";
+ case LP_IAT:
+ return "MAT";
+ case LP_ECT:
+ return "CLT";
+// case LP_SECONDS:
+// return "SecL";
+ case LP_MAF:
+ return "MAF";
+ case LP_MAP:
+ return "MAP";
+ case LP_MAP_RAW:
+ return "MAP_R";
+ default:
+ firmwareError("No such loggingPoint");
+ return NULL;
+ }
+}
+
+/*
+// todo: this method does not really belong to this file
+static char* get2ndCaption(int loggingPoint) {
+ switch (loggingPoint) {
+ case LP_RPM:
+ return "RPM";
+ case LP_THROTTLE:
+ return "%";
+ case LP_IAT:
+ return "°F";
+ case LP_ECT:
+ return "°F";
+ case LP_SECONDS:
+ return "s";
+ case LP_MAP:
+ return "MAP";
+ case LP_MAF:
+ return "MAF";
+ }
+ firmwareError("No such loggingPoint");
+ return NULL;
+}
+*/
+
+void initLoggingExt(Logging *logging, const char *name, char *buffer, int bufferSize) {
+ print("Init logging %s\r\n", name);
+ logging->name = name;
+ logging->buffer = buffer;
+ logging->bufferSize = bufferSize;
+ resetLogging(logging);
+ logging->isInitialized = MAGIC_LOGGING_FLAG;
+}
+
+int isInitialized(Logging *logging) {
+ return logging->isInitialized == MAGIC_LOGGING_FLAG;
+}
+
+void initLogging(Logging *logging, const char *name) {
+ initLoggingExt(logging, name, logging->DEFAULT_BUFFER, sizeof(logging->DEFAULT_BUFFER));
+}
+
+void debugInt(Logging *logging, const char *caption, int value) {
+ append(logging, caption);
+ append(logging, DELIMETER);
+ appendPrintf(logging, "%d%s", value, DELIMETER);
+}
+
+void appendFloat(Logging *logging, float value, int precision) {
+ /**
+ * todo: #1 this implementation is less than perfect
+ * todo: #2 The only way to avoid double promotion would probably be using *float instead of float
+ * See also http://stackoverflow.com/questions/5522051/printing-a-float-in-c-while-avoiding-variadic-parameter-promotion-to-double
+ */
+ switch (precision) {
+ case 1:
+ appendPrintf(logging, "%..10f", value);
+ break;
+ case 2:
+ appendPrintf(logging, "%..100f", value);
+ break;
+ case 3:
+ appendPrintf(logging, "%..1000f", value);
+ break;
+ case 4:
+ appendPrintf(logging, "%..10000f", value);
+ break;
+ case 5:
+ appendPrintf(logging, "%..100000f", value);
+ break;
+ case 6:
+ appendPrintf(logging, "%..1000000f", value);
+ break;
+
+ default:
+ appendPrintf(logging, "%f", value);
+ }
+}
+
+void debugFloat(Logging *logging, const char *caption, float value, int precision) {
+ append(logging, caption);
+ append(logging, DELIMETER);
+
+ appendFloat(logging, value, precision);
+ append(logging, DELIMETER);
+}
+
+static char header[16];
+
+/**
+ * this method should invoked on the main thread only
+ */
+static void printWithLength(char *line) {
+ /**
+ * this is my way to detect serial port transmission errors
+ * following code is functionally identical to
+ * print("line:%d:%s\r\n", len, line);
+ * but it is faster because it outputs the whole buffer, not single characters
+ * We need this optimization because when we output larger chunks of data like the wave_chart:
+ * When we work with actual hardware, it is faster to invoke 'chSequentialStreamWrite' for the
+ * whole buffer then to invoke 'chSequentialStreamPut' once per character.
+ */
+ int len = strlen(line);
+ strcpy(header, "line:");
+ char *p = header + strlen(header);
+ p = itoa10(p, len);
+ *p++ = ':';
+ *p++ = '\0';
+
+ p = line;
+ p += len;
+ *p++ = '\r';
+ *p++ = '\n';
+
+ if (!isConsoleReady())
+ return;
+ consoleOutputBuffer((const uint8_t *) header, strlen(header));
+ consoleOutputBuffer((const uint8_t *) line, p - line);
+}
+
+void printLine(Logging *logging) {
+ printWithLength(logging->buffer);
+ resetLogging(logging);
+}
+
+void appendMsgPrefix(Logging *logging) {
+ appendPrintf(logging, "msg%s", DELIMETER);
+}
+
+void appendMsgPostfix(Logging *logging) {
+ append(logging, DELIMETER);
+}
+
+void resetLogging(Logging *logging) {
+ char *buffer = logging->buffer;
+ if (buffer == NULL) {
+ firmwareError("Null buffer: %s", logging->name);
+ return;
+ }
+ logging->linePointer = buffer;
+}
+
+/**
+ * This method would output a simple console message immediately.
+ * This method should only be invoked on main thread because only the main thread can write to the console
+ */
+void printMsg(Logging *logger, const char *fmt, ...) {
+// resetLogging(logging); // I guess 'reset' is not needed here?
+ appendMsgPrefix(logger);
+
+ va_list ap;
+ va_start(ap, fmt);
+ vappendPrintf(logger, fmt, ap);
+ va_end(ap);
+
+ append(logger, DELIMETER);
+ printLine(logger);
+}
+
+void scheduleMsg(Logging *logging, const char *fmt, ...) {
+ resetLogging(logging); // todo: is 'reset' really needed here?
+ appendMsgPrefix(logging);
+
+ va_list ap;
+ va_start(ap, fmt);
+ vappendPrintf(logging, fmt, ap);
+ va_end(ap);
+
+ appendMsgPostfix(logging);
+ scheduleLogging(logging);
+}
+
+// todo: remove this method, replace with 'scheduleMsg'
+void scheduleIntValue(Logging *logging, const char *msg, int value) {
+ resetLogging(logging);
+
+ append(logging, msg);
+ append(logging, DELIMETER);
+ appendPrintf(logging, "%d", value);
+ append(logging, DELIMETER);
+
+ scheduleLogging(logging);
+}
+
+void scheduleLogging(Logging *logging) {
+ // this could be done without locking
+ int newLength = strlen(logging->buffer);
+
+ bool alreadyLocked = lockOutputBuffer();
+ // I hope this is fast enough to operate under sys lock
+ int curLength = strlen(pendingBuffer);
+ if (curLength + newLength >= DL_OUTPUT_BUFFER) {
+ /**
+ * if no one is consuming the data we have to drop it
+ * this happens in case of serial-over-USB, todo: find a better solution
+ *
+ */
+// strcpy(fatalMessage, "datalogging.c: output buffer overflow: ");
+// strcat(fatalMessage, logging->name);
+// fatal(fatalMessage);
+ if (!alreadyLocked) {
+ unlockOutputBuffer();
+ }
+ resetLogging(logging);
+ return;
+ }
+
+ strcat(pendingBuffer, logging->buffer);
+ if (!alreadyLocked) {
+ unlockOutputBuffer();
+ }
+ resetLogging(logging);
+}
+
+uint32_t remainingSize(Logging *logging) {
+ return logging->bufferSize - loggingSize(logging);
+}
+
+/**
+ * This method actually sends all the pending data to the communication layer
+ */
+void printPending(void) {
+ lockOutputBuffer();
+ // we cannot output under syslock, so another buffer
+ strcpy(outputBuffer, pendingBuffer);
+ pendingBuffer[0] = 0; // reset pending buffer
+ unlockOutputBuffer();
+
+ if (strlen(outputBuffer) > 0) {
+ printWithLength(outputBuffer);
+ }
+}
+
+void initIntermediateLoggingBuffer(void) {
+ msObjectInit(&intermediateLoggingBuffer, intermediateLoggingBufferData, INTERMEDIATE_LOGGING_BUFFER_SIZE, 0);
+ intermediateLoggingBufferInited = TRUE;
+}
diff --git a/firmware/console_util/datalogging.h b/firmware/console_util/datalogging.h
new file mode 100644
index 0000000000..a7a2177269
--- /dev/null
+++ b/firmware/console_util/datalogging.h
@@ -0,0 +1,108 @@
+/**
+ * @file datalogging.h
+ * @brief Buffered console output stream header
+ *
+ * @date Feb 25, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef DATALOGGING_H_
+#define DATALOGGING_H_
+
+#include "global.h"
+#include
+
+#define DELIMETER ","
+
+typedef enum {
+ LP_RPM = 0,
+ LP_ECT = 1,
+ LP_IAT = 2,
+
+ LP_THROTTLE = 3,
+ LP_THROTTLE_ADC = 4,
+
+ LP_MAP = 5,
+ LP_MAP_RAW = 6,
+
+ LP_MAF = 7,
+ LP_TRG_CH0_DUTY = 8,
+ LP_TRG_CH1_DUTY = 9,
+
+
+ // LP_SECONDS,
+ LP_COUNT = 9
+
+} LoggingPoints;
+
+// todo: migrate to external buffer so that different instances have different
+// size of buffers?
+typedef struct {
+ const char *name;
+ char SMALL_BUFFER[40];
+ // todo: explicitly default buffer externally so that we do not have default_buffer where we do not need it?
+ char DEFAULT_BUFFER[200];
+ char *buffer;
+ char *linePointer;
+ int bufferSize;
+ volatile int isInitialized;
+} Logging;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+bool lockOutputBuffer(void);
+void unlockOutputBuffer(void);
+
+void initIntermediateLoggingBuffer(void);
+uint32_t remainingSize(Logging *logging);
+
+#define loggingSize(logging) ((int) (logging)->linePointer - (int) ((logging)->buffer))
+
+int isInitialized(Logging *logging);
+
+void initLogging(Logging *logging, const char *name);
+void initLoggingExt(Logging *logging, const char *name, char *buffer, int bufferSize);
+
+void debugInt(Logging *logging, const char *caption, int value);
+void logInt(Logging *logging, LoggingPoints loggingPoint, int value);
+
+void debugFloat(Logging *logging, const char *text, float value, int precision);
+void logFloat(Logging *logging, LoggingPoints loggingPoint, float value);
+void appendFloat(Logging *logging, float value, int precision);
+
+void resetLogging(Logging *logging);
+void printLine(Logging *logging);
+
+void appendMsgPrefix(Logging *logging);
+void appendMsgPostfix(Logging *logging);
+
+void scheduleMsg(Logging *logging, const char *fmt, ...);
+
+void printMsg(Logging *logging, const char *fmt, ...);
+char* getCaption(LoggingPoints loggingPoint);
+void appendPrintf(Logging *logging, const char *fmt, ...);
+void vappendPrintf(Logging *logging, const char *fmt, va_list arg);
+void append(Logging *logging, const char *text);
+void appendFast(Logging *logging, const char *text);
+/**
+ * this method copies the line into the intermediate buffer for later output by
+ * the main thread
+ */
+
+void scheduleLogging(Logging *logging);
+
+void scheduleIntValue(Logging *logging, const char *msg, int value);
+
+/**
+ * this should only be invoked by the 'main' thread in order to keep the console safe
+ */
+void printPending(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* DATALOGGING_H_ */
diff --git a/firmware/console_util/rfiutil.c b/firmware/console_util/rfiutil.c
new file mode 100644
index 0000000000..1cca36c6d9
--- /dev/null
+++ b/firmware/console_util/rfiutil.c
@@ -0,0 +1,113 @@
+/**
+ * @file rfiutil.c
+ * @brief Number to string conversion code
+ *
+ * @date Nov 15, 2012
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ */
+
+#include
+#include "main.h"
+#include "rfiutil.h"
+
+/*
+not used, not sure if we still need it. I guess we will remove it in 2015
+int mylog10(int param) {
+ if (param < 10)
+ return 0;
+ if (param < 100)
+ return 1;
+ if (param < 1000)
+ return 2;
+ if (param < 10000)
+ return 3;
+ if (param < 100000)
+ return 4;
+ if (param < 1000000)
+ return 5;
+ if (param < 10000000)
+ return 6;
+ if (param < 100000000)
+ return 7;
+ #warning This would be better without recursion
+ return mylog10(param / 10) + 1;
+}
+*/
+
+char hexChar(int v) {
+ v = v & 0xF;
+ if (v < 10)
+ return (char)('0' + v);
+ return 'A' - 10 + v;
+}
+
+// todo: why does it not compile if I make this function 'inline'?
+int isIsrContext(void) {
+ /**
+ * Unfortunately ChibiOS has two versions of methods for different
+ * contexts.
+ */
+ return dbg_isr_cnt > 0;
+}
+
+int isLocked(void) {
+ return dbg_lock_cnt > 0;
+}
+
+void chVTSetAny(virtual_timer_t *vtp, systime_t time, vtfunc_t vtfunc, void *par) {
+ if (isIsrContext()) {
+ bool wasLocked = lockAnyContext();
+
+ /**
+ * todo: this could be simplified once we migrate to ChibiOS 3.0
+ * See http://www.chibios.org/dokuwiki/doku.php?id=chibios:howtos:porting_from_2_to_3
+ */
+ if (chVTIsArmedI(vtp))
+ chVTResetI(vtp);
+
+ chVTSetI(vtp, time, vtfunc, par);
+ if (!wasLocked)
+ chSysUnlockFromIsr()
+ ;
+ } else {
+ chSysLock()
+ ;
+ if (chVTIsArmedI(vtp))
+ chVTResetI(vtp);
+ chVTSetI(vtp, time, vtfunc, par);
+ chSysUnlock()
+ ;
+ }
+}
+
+/**
+ * @brief This function knows how to print a histogram_s summary
+ */
+void printHistogram(Logging *logging, histogram_s *histogram) {
+#if EFI_HISTOGRAMS
+ int report[5];
+ int len = hsReport(histogram, report);
+
+ resetLogging(logging);
+ appendMsgPrefix(logging);
+ appendPrintf(logging, "histogram %s *", histogram->name);
+ for (int i = 0; i < len; i++)
+ appendPrintf(logging, "%d ", report[i]);
+ appendPrintf(logging, "*");
+ appendMsgPostfix(logging);
+ scheduleLogging(logging);
+#endif /* EFI_HISTOGRAMS */
+}
diff --git a/firmware/console_util/rfiutil.h b/firmware/console_util/rfiutil.h
new file mode 100644
index 0000000000..f4c9a29f0d
--- /dev/null
+++ b/firmware/console_util/rfiutil.h
@@ -0,0 +1,31 @@
+/*
+ * @file rfiutil.h
+ * @brief Number to string conversion header
+ *
+ * @date Nov 15, 2012
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef RFIUTIL_H_
+#define RFIUTIL_H_
+
+#include "global.h"
+#include "histogram.h"
+#include "datalogging.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+char hexC(int v);
+int isIsrContext(void);
+int isLocked(void);
+void chVTSetAny(virtual_timer_t *vtp, systime_t time, vtfunc_t vtfunc, void *par);
+void printHistogram(Logging *logging, histogram_s *histogram);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* RFIUTIL_H_ */
diff --git a/firmware/controllers/PwmTester.cpp b/firmware/controllers/PwmTester.cpp
new file mode 100644
index 0000000000..3e09cb13f9
--- /dev/null
+++ b/firmware/controllers/PwmTester.cpp
@@ -0,0 +1,41 @@
+/**
+ * @file PwmTester.cpp
+ * This is a tool to measure rusEfi PWM generation quality
+ *
+ * @date Apr 29, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+#include "PwmTester.h"
+#include "EfiWave.h"
+#include "pwm_generator_logic.h"
+#include "engine_configuration.h"
+#include "pwm_generator.h"
+
+static Logging logger;
+
+static SimplePwm pwmTest[5];
+
+extern board_configuration_s *boardConfiguration;
+
+static void startPwmTest(int freq) {
+ scheduleMsg(&logger, "running pwm test @%d", freq);
+
+ // PD13, GPIO_NONE because pin is initialized elsewhere already
+ startSimplePwm(&pwmTest[0], "tester", LED_WARNING, 10, 0.5f);
+ // currently this is PB9 by default - see boardConfiguration->injectionPins
+ startSimplePwm(&pwmTest[1], "tester", INJECTOR_1_OUTPUT, freq / 1.3333333333, 0.5f);
+ // currently this is PB8 by default
+ startSimplePwm(&pwmTest[2], "tester", INJECTOR_2_OUTPUT, freq / 1000, 0.5f);
+ // currently this is PE3 by default
+ startSimplePwm(&pwmTest[3], "tester", INJECTOR_3_OUTPUT, freq, 0.5);
+ // currently this is PE5 by default
+ startSimplePwm(&pwmTest[4], "tester", INJECTOR_4_OUTPUT, freq / 33.33333333333, 0.5);
+}
+
+void initPwmTester(void) {
+ initLogging(&logger, "pwm test");
+ addConsoleActionI("pwmtest", startPwmTest);
+// un-comment this to start pwm test on start up startPwmTest(1000);
+}
diff --git a/firmware/controllers/PwmTester.h b/firmware/controllers/PwmTester.h
new file mode 100644
index 0000000000..e376961b80
--- /dev/null
+++ b/firmware/controllers/PwmTester.h
@@ -0,0 +1,12 @@
+/**
+ * @file PwmTester.h
+ *
+ * @date Apr 29, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+#ifndef PWMTESTER_H_
+#define PWMTESTER_H_
+
+void initPwmTester(void);
+
+#endif /* PWMTESTER_H_ */
diff --git a/firmware/controllers/algo/OutputSignalArray.cpp b/firmware/controllers/algo/OutputSignalArray.cpp
new file mode 100644
index 0000000000..3eca9004a7
--- /dev/null
+++ b/firmware/controllers/algo/OutputSignalArray.cpp
@@ -0,0 +1,48 @@
+/**
+ * @file OutputSignalArray.cpp
+ *
+ * @date Mar 20, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ */
+
+#include "OutputSignalList.h"
+
+OutputSignalList::OutputSignalList() {
+ clear();
+}
+
+void OutputSignalList::clear() {
+ size = 0;
+}
+
+int OutputSignalList::getSize() {
+ return size;
+}
+
+
+OutputSignal * OutputSignalList::add(io_pin_e ioPin) {
+ if (size == OUTPUT_SIGNAL_MAX_SIZE) {
+ firmwareError("Too many signals, adding %d", ioPin);
+ return NULL;
+ }
+
+ OutputSignal *signal = &signals[size++];
+
+ initOutputSignal(signal, ioPin);
+
+ return signal;
+}
+
diff --git a/firmware/controllers/algo/OutputSignalList.h b/firmware/controllers/algo/OutputSignalList.h
new file mode 100644
index 0000000000..0f9c81e02c
--- /dev/null
+++ b/firmware/controllers/algo/OutputSignalList.h
@@ -0,0 +1,44 @@
+/*
+ * @file OutputSignalList.h
+ *
+ * @date Mar 20, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ */
+
+#ifndef OUTPUTSIGNALLIST_H_
+#define OUTPUTSIGNALLIST_H_
+
+#include "signal_executor.h"
+#include "io_pins.h"
+
+// todo: this value is too low for 6 cyl engine, get it back to 60
+#define OUTPUT_SIGNAL_MAX_SIZE 90
+
+class OutputSignalList {
+public:
+ OutputSignalList();
+ void clear();
+ OutputSignal * add(io_pin_e ioPin);
+ int getSize();
+
+private:
+ int size;
+ OutputSignal signals[OUTPUT_SIGNAL_MAX_SIZE];
+
+};
+
+
+#endif /* OUTPUTSIGNALLIST_H_ */
diff --git a/firmware/controllers/algo/accel_enrichment.cpp b/firmware/controllers/algo/accel_enrichment.cpp
new file mode 100644
index 0000000000..585942ed99
--- /dev/null
+++ b/firmware/controllers/algo/accel_enrichment.cpp
@@ -0,0 +1,64 @@
+/**
+ * @file accel_enrichment.cpp
+ * @brief Acceleration enrichment calculator
+ *
+ * @date Apr 21, 2014
+ * @author Dmitry Sidin
+ * @author Andrey Belomutskiy (c) 2012-2014
+ */
+
+#include "main.h"
+#include "trigger_central.h"
+#include "accel_enrichment.h"
+#include "engine_state.h"
+#include "engine_math.h"
+#include "signal_executor.h"
+
+extern Engine engine;
+static AccelEnrichmemnt instance;
+
+void AccelEnrichmemnt::updateDiffEnrichment(engine_configuration_s *engineConfiguration, float engineLoad) {
+ for (int i = 3; i == 1; i--)
+ engineLoadD[i] = engineLoadD[i - 1];
+
+ engineLoadD[0] = engineLoad;
+
+ diffEnrichment = ((3 * (engineLoadD[0] - engineLoadD[1]) + (engineLoadD[2] - engineLoadD[3])) / 4)
+ * (engineConfiguration->diffLoadEnrichmentCoef);
+}
+
+float AccelEnrichmemnt::getDiffEnrichment() {
+ return diffEnrichment;
+}
+AccelEnrichmemnt::AccelEnrichmemnt() {
+ for (int i = 0; i < 4; i++)
+ engineLoadD[i] = 0;
+ diffEnrichment = 0;
+}
+
+float getAccelEnrichment(void) {
+ return instance.getDiffEnrichment();
+}
+
+#if EFI_PROD_CODE
+static THD_WORKING_AREA(aeThreadStack, UTILITY_THREAD_STACK_SIZE);
+
+extern engine_configuration_s *engineConfiguration;
+
+static msg_t DiffEnrichmentThread(int param) {
+ chRegSetThreadName("Diff Enrichment");
+ while (TRUE) {
+ instance.updateDiffEnrichment(engineConfiguration, getEngineLoad());
+ chThdSleepMilliseconds(100);
+ }
+#if defined __GNUC__
+ return -1;
+#endif
+}
+
+void initDiffEnrichment(void) {
+ chThdCreateStatic(aeThreadStack, sizeof(aeThreadStack), LOWPRIO, (tfunc_t) DiffEnrichmentThread, NULL);
+}
+
+#endif
+
diff --git a/firmware/controllers/algo/accel_enrichment.h b/firmware/controllers/algo/accel_enrichment.h
new file mode 100644
index 0000000000..554eb336b1
--- /dev/null
+++ b/firmware/controllers/algo/accel_enrichment.h
@@ -0,0 +1,31 @@
+/**
+ * @file accel_enrichment.h
+ * @brief Acceleration enrichment calculator
+ *
+ * @date Apr 21, 2014
+ * @author Dmitry Sidin
+ * @author Andrey Belomutskiy (c) 2012-2014
+ */
+
+#ifndef ACC_ENRICHMENT_H_
+#define ACC_ENRICHMENT_H_
+
+#include "engine_configuration.h"
+
+class AccelEnrichmemnt {
+public:
+ AccelEnrichmemnt();
+ void updateDiffEnrichment(engine_configuration_s *engineConfiguration,
+ float engineLoad);
+ float getDiffEnrichment(void);
+private:
+ float engineLoadD[4];
+ float diffEnrichment;
+};
+
+void initDiffEnrichment(void);
+float getAccelEnrichment(void);
+
+
+#endif /* ACC_ENRICHMENT_H_ */
+
diff --git a/firmware/controllers/algo/advance_map.cpp b/firmware/controllers/algo/advance_map.cpp
new file mode 100644
index 0000000000..20920226fa
--- /dev/null
+++ b/firmware/controllers/algo/advance_map.cpp
@@ -0,0 +1,54 @@
+/**
+ * @file advance_map.c
+ *
+ * @date Mar 27, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ */
+
+#include "main.h"
+#include "advance_map.h"
+#include "interpolation.h"
+// that's for 'max' function
+#include "idle_controller.h"
+
+#include "engine_configuration.h"
+#include "engine_math.h"
+
+extern engine_configuration_s *engineConfiguration;
+//extern engine_configuration2_s *engineConfiguration2;
+
+static Map3D1616 advanceMap;
+
+float getBaseAdvance(int rpm, float engineLoad) {
+ efiAssert(!cisnan(engineLoad), "invalid el", NAN);
+ efiAssert(!cisnan(engineLoad), "invalid rpm", NAN);
+ return advanceMap.getValue(engineLoad, engineConfiguration->ignitionLoadBins, (float)rpm,
+ engineConfiguration->ignitionRpmBins);
+}
+
+float getAdvance(int rpm, float engineLoad) {
+ float angle;
+ if (isCrankingR(rpm)) {
+ angle = engineConfiguration->crankingTimingAngle;
+ } else {
+ angle = getBaseAdvance(rpm, engineLoad);
+ }
+ return fixAngle(angle + engineConfiguration->ignitionOffset);
+}
+
+void prepareTimingMap(void) {
+ advanceMap.init(engineConfiguration->ignitionTable);
+}
diff --git a/firmware/controllers/algo/advance_map.h b/firmware/controllers/algo/advance_map.h
new file mode 100644
index 0000000000..3867bb6891
--- /dev/null
+++ b/firmware/controllers/algo/advance_map.h
@@ -0,0 +1,26 @@
+/*
+ * @file advance_map.h
+ *
+ * @date Mar 27, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef ADVANCE_H_
+#define ADVANCE_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+#define AD_LOAD_COUNT 16
+#define AD_RPM_COUNT 16
+
+float getAdvance(int rpm, float engineLoad);
+void prepareTimingMap(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* ADVANCE_H_ */
diff --git a/firmware/controllers/algo/algo.cpp b/firmware/controllers/algo/algo.cpp
new file mode 100644
index 0000000000..34fe9c50c3
--- /dev/null
+++ b/firmware/controllers/algo/algo.cpp
@@ -0,0 +1,48 @@
+/*
+ * @file algo.cpp
+ *
+ * @date Mar 2, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ */
+
+#include "global.h"
+#include "algo.h"
+#include "advance_map.h"
+#include "fuel_math.h"
+#include "wave_chart.h"
+#include "settings.h"
+#include "signal_executor.h"
+#include "speed_density.h"
+
+WaveChart waveChart;
+
+void initDataStructures(engine_configuration_s *engineConfiguration) {
+ prepareFuelMap();
+ prepareTimingMap();
+ initSpeedDensity(engineConfiguration);
+}
+
+void initAlgo(engine_configuration_s *engineConfiguration) {
+#if EFI_PROD_CODE || EFI_SIMULATOR
+ initSettings();
+ initSignalExecutor();
+#endif
+
+#if EFI_WAVE_CHART
+ initWaveChart(&waveChart);
+#endif
+}
diff --git a/firmware/controllers/algo/algo.h b/firmware/controllers/algo/algo.h
new file mode 100644
index 0000000000..62a1ff5795
--- /dev/null
+++ b/firmware/controllers/algo/algo.h
@@ -0,0 +1,25 @@
+/*
+ * @file algo.h
+ *
+ * @date Mar 2, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef ALGO_H_
+#define ALGO_H_
+
+#include "engine_configuration.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void initDataStructures(engine_configuration_s *engineConfiguration);
+void initAlgo(engine_configuration_s *engineConfiguration);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* ALGO_H_ */
diff --git a/firmware/controllers/algo/algo.mk b/firmware/controllers/algo/algo.mk
new file mode 100644
index 0000000000..d5930ee80e
--- /dev/null
+++ b/firmware/controllers/algo/algo.mk
@@ -0,0 +1,17 @@
+
+CONTROLLERS_ALGO_SRC = $(PROJECT_DIR)/controllers/algo/map_adjuster.c \
+ $(PROJECT_DIR)/controllers/algo/signal_executor.c \
+ $(PROJECT_DIR)/controllers/algo/malfunction_central.c \
+ $(PROJECT_DIR)/controllers/algo/idle_controller.c \
+ $(PROJECT_DIR)/controllers/algo/wave_chart.c \
+ $(PROJECT_DIR)/controllers/algo/nmea.c
+
+CONTROLLERS_ALGO_SRC_CPP = $(PROJECT_DIR)/controllers/algo/OutputSignalArray.cpp \
+ $(PROJECT_DIR)/controllers/algo/advance_map.cpp \
+ $(PROJECT_DIR)/controllers/algo/fuel_math.cpp \
+ $(PROJECT_DIR)/controllers/algo/accel_enrichment.cpp \
+ $(PROJECT_DIR)/controllers/algo/engine_configuration.cpp \
+ $(PROJECT_DIR)/controllers/algo/engine.cpp \
+ $(PROJECT_DIR)/controllers/algo/event_registry.cpp \
+ $(PROJECT_DIR)/controllers/algo/algo.cpp
+
diff --git a/firmware/controllers/algo/can_header.h b/firmware/controllers/algo/can_header.h
new file mode 100644
index 0000000000..f18cc9f792
--- /dev/null
+++ b/firmware/controllers/algo/can_header.h
@@ -0,0 +1,24 @@
+/**
+ * @file can_header.h
+ *
+ * @date Dec 21, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef CAN_HEADER_H_
+#define CAN_HEADER_H_
+
+#include "rusefi_enums.h"
+
+/**
+ * Net Body Computer types
+ */
+typedef enum {
+ CAN_BUS_NBC_BMW = 0,
+ CAN_BUS_NBC_FIAT = 1,
+ CAN_BUS_NBC_VAG = 2,
+
+ Internal_ForceMyEnumIntSize_can_nbc = ENUM_SIZE_HACK,
+} can_nbc_e;
+
+#endif /* CAN_HEADER_H_ */
diff --git a/firmware/controllers/algo/ec2.h b/firmware/controllers/algo/ec2.h
new file mode 100644
index 0000000000..e071f0eb06
--- /dev/null
+++ b/firmware/controllers/algo/ec2.h
@@ -0,0 +1,89 @@
+/**
+ * @file ec2.h
+ *
+ * this is a mess because some code is still in C and some is
+ * already in C++. trigger structure is C++
+ * TODO: rename? merge? Do something with this file
+ *
+ * @date Apr 26, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef EC2_H_
+#define EC2_H_
+
+#include "engine_configuration.h"
+#include "event_registry.h"
+#include "trigger_structure.h"
+
+/**
+ * @brief Here we store information about which injector or spark should be fired when.
+ */
+typedef struct {
+ ActuatorEventList crankingInjectionEvents;
+ ActuatorEventList injectionEvents;
+ /**
+ * We are alternating two event lists in order to avoid a potential issue around revolution boundary
+ * when an event is scheduled within the next revolution.
+ */
+ IgnitionEventList ignitionEvents[2];
+} EventHandlerConfiguration;
+
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+/**
+ * this part of the structure is separate just because so far
+ * these fields are not integrated with Tuner Studio. Step by step :)
+ */
+class engine_configuration2_s {
+ // todo: move these fields into Engine class, eliminate this class
+public:
+ engine_configuration2_s();
+
+ Thermistor iat;
+ Thermistor clt;
+
+ trigger_shape_s triggerShape;
+
+ EventHandlerConfiguration engineEventConfiguration;
+
+ /**
+ * This coefficient translates ADC value directly into voltage adjusted according to
+ * voltage divider configuration. This is a future (?) performance optimization.
+ */
+ float adcToVoltageInputDividerCoefficient;
+};
+
+typedef struct {
+ engine_configuration_s *engineConfiguration;
+ engine_configuration2_s *engineConfiguration2;
+} configuration_s;
+
+void prepareOutputSignals(engine_configuration_s *engineConfiguration,
+ engine_configuration2_s *engineConfiguration2);
+
+void initializeIgnitionActions(float advance, float dwellAngle, engine_configuration_s *engineConfiguration, engine_configuration2_s *engineConfiguration2, IgnitionEventList *list);
+float getFuelMultiplier(engine_configuration_s const *e, injection_mode_e mode);
+void addFuelEvents(engine_configuration_s const *e, engine_configuration2_s *engineConfiguration2, ActuatorEventList *list, injection_mode_e mode);
+
+void registerActuatorEventExt(engine_configuration_s const *engineConfiguration, trigger_shape_s * s, ActuatorEvent *e, OutputSignal *actuator, float angleOffset);
+
+void resetConfigurationExt(Logging * logger, engine_type_e engineType,
+ engine_configuration_s *engineConfiguration,
+ engine_configuration2_s *engineConfiguration2,
+ board_configuration_s *boardConfiguration);
+void applyNonPersistentConfiguration(Logging * logger, engine_configuration_s *engineConfiguration,
+ engine_configuration2_s *engineConfiguration2);
+
+void setDefaultNonPersistentConfiguration(engine_configuration2_s *engineConfiguration2);
+void printConfiguration(engine_configuration_s *engineConfiguration, engine_configuration2_s *engineConfiguration2);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* EC2_H_ */
diff --git a/firmware/controllers/algo/engine.cpp b/firmware/controllers/algo/engine.cpp
new file mode 100644
index 0000000000..3122f29cb3
--- /dev/null
+++ b/firmware/controllers/algo/engine.cpp
@@ -0,0 +1,23 @@
+/**
+ * @file engine.cpp
+ *
+ *
+ * This might be a http://en.wikipedia.org/wiki/God_object but that's best way I can
+ * express myself in C/C++. I am open for suggestions :)
+ *
+ * @date May 21, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+#include "engine.h"
+#include "engine_state.h"
+
+/**
+ * We are executing these heavy (logarithm) methods from outside the trigger callbacks for performance reasons.
+ */
+void Engine::updateSlowSensors() {
+ engineState.iat = getIntakeAirTemperature();
+ engineState.clt = getCoolantTemperature();
+}
+
diff --git a/firmware/controllers/algo/engine.h b/firmware/controllers/algo/engine.h
new file mode 100644
index 0000000000..18b9959d11
--- /dev/null
+++ b/firmware/controllers/algo/engine.h
@@ -0,0 +1,35 @@
+/**
+ * @file engine.h
+ *
+ * @date May 21, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+#ifndef ENGINE_H_
+#define ENGINE_H_
+
+#include "main.h"
+#include "engine_configuration.h"
+
+class EngineState {
+public:
+ /**
+ * Access to these two fields is not synchronized in any way - that should work since float read/write are atomic.
+ */
+ float iat;
+ float clt;
+};
+
+class RpmCalculator;
+
+class Engine {
+public:
+ RpmCalculator *rpmCalculator;
+ engine_configuration_s *engineConfiguration;
+
+
+ EngineState engineState;
+
+ void updateSlowSensors();
+};
+
+#endif /* ENGINE_H_ */
diff --git a/firmware/controllers/algo/engine_configuration.cpp b/firmware/controllers/algo/engine_configuration.cpp
new file mode 100644
index 0000000000..ebf4de1a91
--- /dev/null
+++ b/firmware/controllers/algo/engine_configuration.cpp
@@ -0,0 +1,492 @@
+/**
+ * @file engine_configuration.cpp
+ * @brief Utility method related to the engine configuration data structure.
+ *
+ * @date Nov 22, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ *
+ */
+
+#include "main.h"
+#include "engine_configuration.h"
+#include "allsensors.h"
+#include "interpolation.h"
+#include "trigger_decoder.h"
+#include "engine_math.h"
+#include "speed_density.h"
+
+#if EFI_TUNER_STUDIO
+#include "tunerstudio.h"
+#endif
+
+#include "audi_aan.h"
+#include "dodge_neon.h"
+#include "ford_aspire.h"
+#include "ford_fiesta.h"
+#include "ford_1995_inline_6.h"
+#include "snow_blower.h"
+#include "nissan_primera.h"
+#include "honda_accord.h"
+#include "GY6_139QMB.h"
+#include "mazda_miata_nb.h"
+#include "mazda_323.h"
+#include "saturn_ion.h"
+#include "MiniCooperR50.h"
+#include "ford_escort_gt.h"
+#include "citroenBerlingoTU3JP.h"
+#include "rover_v8.h"
+#include "mitsubishi.h"
+
+static volatile int globalConfigurationVersion = 0;
+
+int getGlobalConfigurationVersion(void) {
+ return globalConfigurationVersion;
+}
+
+void incrementGlobalConfigurationVersion(void) {
+ globalConfigurationVersion++;
+}
+
+/**
+ * @brief Sets the same dwell time across the whole getRpm() range
+ */
+void setConstantDwell(engine_configuration_s *engineConfiguration, float dwellMs) {
+ for (int i = 0; i < DWELL_CURVE_SIZE; i++) {
+ engineConfiguration->sparkDwellBins[i] = 1000 * i;
+ engineConfiguration->sparkDwell[i] = dwellMs;
+ }
+}
+
+void setWholeVEMap(engine_configuration_s *engineConfiguration, float value) {
+ // todo: table helper?
+// for (int l = 0; l < VE_LOAD_COUNT; l++) {
+// for (int r = 0; r < VE_RPM_COUNT; r++) {
+// engineConfiguration->veTable[l][r] = value;
+// }
+// }
+}
+
+void setWholeFuelMap(engine_configuration_s *engineConfiguration, float value) {
+ // todo: table helper?
+ for (int l = 0; l < FUEL_LOAD_COUNT; l++) {
+ for (int r = 0; r < FUEL_RPM_COUNT; r++) {
+ engineConfiguration->fuelTable[l][r] = value;
+ }
+ }
+}
+
+/**
+ * @brief Global default engine configuration
+ * This method sets the default global engine configuration. These values are later overridden by engine-specific defaults
+ * and the settings saves in flash memory.
+ */
+void setDefaultConfiguration(engine_configuration_s *engineConfiguration, board_configuration_s *boardConfiguration) {
+ memset(engineConfiguration, 0, sizeof(engine_configuration_s));
+ memset(boardConfiguration, 0, sizeof(board_configuration_s));
+
+ setDetaultVETable(engineConfiguration);
+
+ engineConfiguration->injectorLag = 0.0;
+
+ for (int i = 0; i < IAT_CURVE_SIZE; i++) {
+ engineConfiguration->iatFuelCorrBins[i] = -40 + i * 10;
+ engineConfiguration->iatFuelCorr[i] = 1; // this correction is a multiplier
+ }
+
+ for (int i = 0; i < CLT_CURVE_SIZE; i++) {
+ engineConfiguration->cltFuelCorrBins[i] = -40 + i * 10;
+ engineConfiguration->cltFuelCorr[i] = 1; // this correction is a multiplier
+ }
+
+// setTableValue(engineConfiguration->cltFuelCorrBins, engineConfiguration->cltFuelCorr, CLT_CURVE_SIZE, -40, 1.5);
+// setTableValue(engineConfiguration->cltFuelCorrBins, engineConfiguration->cltFuelCorr, CLT_CURVE_SIZE, -30, 1.5);
+// setTableValue(engineConfiguration->cltFuelCorrBins, engineConfiguration->cltFuelCorr, CLT_CURVE_SIZE, -20, 1.42);
+// setTableValue(engineConfiguration->cltFuelCorrBins, engineConfiguration->cltFuelCorr, CLT_CURVE_SIZE, -10, 1.36);
+// setTableValue(engineConfiguration->cltFuelCorrBins, engineConfiguration->cltFuelCorr, CLT_CURVE_SIZE, 0, 1.28);
+// setTableValue(engineConfiguration->cltFuelCorrBins, engineConfiguration->cltFuelCorr, CLT_CURVE_SIZE, 10, 1.19);
+// setTableValue(engineConfiguration->cltFuelCorrBins, engineConfiguration->cltFuelCorr, CLT_CURVE_SIZE, 20, 1.12);
+// setTableValue(engineConfiguration->cltFuelCorrBins, engineConfiguration->cltFuelCorr, CLT_CURVE_SIZE, 30, 1.10);
+// setTableValue(engineConfiguration->cltFuelCorrBins, engineConfiguration->cltFuelCorr, CLT_CURVE_SIZE, 40, 1.06);
+// setTableValue(engineConfiguration->cltFuelCorrBins, engineConfiguration->cltFuelCorr, CLT_CURVE_SIZE, 50, 1.06);
+// setTableValue(engineConfiguration->cltFuelCorrBins, engineConfiguration->cltFuelCorr, CLT_CURVE_SIZE, 60, 1.03);
+// setTableValue(engineConfiguration->cltFuelCorrBins, engineConfiguration->cltFuelCorr, CLT_CURVE_SIZE, 70, 1.01);
+
+ for (int i = 0; i < VBAT_INJECTOR_CURVE_SIZE; i++) {
+ engineConfiguration->battInjectorLagCorrBins[i] = 12 - VBAT_INJECTOR_CURVE_SIZE / 2 + i;
+ engineConfiguration->battInjectorLagCorr[i] = 0; // zero extra time by default
+ }
+
+ setConstantDwell(engineConfiguration, 4); // 4ms is global default dwell
+
+ setFuelLoadBin(engineConfiguration, 1.2, 4.4);
+ setFuelRpmBin(engineConfiguration, 800, 7000);
+ setTimingLoadBin(engineConfiguration, 1.2, 4.4);
+ setTimingRpmBin(engineConfiguration, 800, 7000);
+
+ setTableBin2(engineConfiguration->map.samplingAngleBins, MAP_ANGLE_SIZE, 800, 7000, 1);
+ setTableBin2(engineConfiguration->map.samplingAngle, MAP_ANGLE_SIZE, 100, 130, 1);
+ setTableBin2(engineConfiguration->map.samplingWindowBins, MAP_ANGLE_SIZE, 800, 7000, 1);
+ setTableBin2(engineConfiguration->map.samplingWindow, MAP_ANGLE_SIZE, 50, 50, 1);
+
+ // set_whole_timing_map 3
+ setWholeFuelMap(engineConfiguration, 3);
+
+ setThermistorConfiguration(&engineConfiguration->cltThermistorConf, 0, 9500, 23.8889, 2100, 48.8889, 1000);
+ engineConfiguration->cltThermistorConf.bias_resistor = 1500;
+
+ setThermistorConfiguration(&engineConfiguration->iatThermistorConf, 32, 9500, 75, 2100, 120, 1000);
+// todo: this value is way off! I am pretty sure temp coeffs are off also
+ engineConfiguration->iatThermistorConf.bias_resistor = 2700;
+
+ engineConfiguration->rpmHardLimit = 7000;
+ engineConfiguration->crankingSettings.crankingRpm = 550;
+
+ // set_cranking_fuel_max 6 40
+ engineConfiguration->crankingSettings.coolantTempMaxC = 40; // 6ms at 40C
+ engineConfiguration->crankingSettings.fuelAtMaxTempMs = 6;
+
+ // set_cranking_fuel_min 6 -40
+ engineConfiguration->crankingSettings.coolantTempMinC = -40; // 6ms at -40C
+ engineConfiguration->crankingSettings.fuelAtMinTempMs = 6;
+
+ engineConfiguration->analogInputDividerCoefficient = 2;
+
+ engineConfiguration->crankingChargeAngle = 70;
+ engineConfiguration->timingMode = TM_DYNAMIC;
+ engineConfiguration->fixedModeTiming = 50;
+
+ engineConfiguration->analogChartMode = AC_TRIGGER;
+
+ engineConfiguration->map.sensor.hwChannel = EFI_ADC_4;
+ engineConfiguration->baroSensor.hwChannel = EFI_ADC_4;
+
+ engineConfiguration->firingOrder = FO_1_THEN_3_THEN_4_THEN2;
+ engineConfiguration->crankingInjectionMode = IM_SIMULTANEOUS;
+ engineConfiguration->injectionMode = IM_SEQUENTIAL;
+
+ engineConfiguration->ignitionMode = IM_ONE_COIL;
+ engineConfiguration->globalTriggerAngleOffset = 0;
+ engineConfiguration->injectionOffset = 0;
+ engineConfiguration->ignitionOffset = 0;
+ engineConfiguration->overrideCrankingIgnition = TRUE;
+ engineConfiguration->analogChartFrequency = 20;
+
+ engineConfiguration->algorithm = LM_MAF;
+
+ engineConfiguration->vbattDividerCoeff = ((float) (15 + 65)) / 15;
+
+ engineConfiguration->fanOnTemperature = 75;
+ engineConfiguration->fanOffTemperature = 70;
+
+ engineConfiguration->tpsMin = convertVoltageTo10bitADC(1.250);
+ engineConfiguration->tpsMax = convertVoltageTo10bitADC(4.538);
+
+ engineConfiguration->can_nbc_type = CAN_BUS_NBC_BMW;
+ engineConfiguration->can_sleep_period = 50;
+ engineConfiguration->canReadEnabled = TRUE;
+ engineConfiguration->canWriteEnabled = false;
+
+ setOperationMode(engineConfiguration, FOUR_STROKE_CAM_SENSOR);
+ engineConfiguration->cylindersCount = 4;
+ engineConfiguration->displacement = 2;
+ /**
+ * By the way http://users.erols.com/srweiss/tableifc.htm has a LOT of data
+ */
+ engineConfiguration->injectorFlow = 200;
+
+ engineConfiguration->displayMode = DM_HD44780;
+
+ engineConfiguration->logFormat = LF_NATIVE;
+ engineConfiguration->directSelfStimulation = false;
+
+ engineConfiguration->needSecondTriggerInput = true;
+ engineConfiguration->triggerConfig.triggerType = TT_TOOTHED_WHEEL_60_2;
+
+ engineConfiguration->HD44780width = 20;
+ engineConfiguration->HD44780height = 4;
+
+ engineConfiguration->tpsAdcChannel = EFI_ADC_3;
+ engineConfiguration->vBattAdcChannel = EFI_ADC_5;
+ engineConfiguration->cltAdcChannel = EFI_ADC_6;
+ engineConfiguration->iatAdcChannel = EFI_ADC_7;
+ engineConfiguration->mafAdcChannel = EFI_ADC_0;
+ engineConfiguration->afrSensor.afrAdcChannel = EFI_ADC_14;
+
+ initEgoSensor(&engineConfiguration->afrSensor, ES_BPSX_D1);
+
+ engineConfiguration->globalFuelCorrection = 1;
+
+ engineConfiguration->map.sensor.sensorType = MT_MPX4250;
+
+ engineConfiguration->baroSensor.sensorType = MT_CUSTOM;
+ engineConfiguration->baroSensor.Min = 0;
+ engineConfiguration->baroSensor.Max = 500;
+
+ engineConfiguration->diffLoadEnrichmentCoef = 1;
+
+ engineConfiguration->hasMapSensor = TRUE;
+ engineConfiguration->hasCltSensor = TRUE;
+
+ boardConfiguration->idleSolenoidFrequency = 200;
+// engineConfiguration->idleMode = IM_AUTO;
+ engineConfiguration->idleMode = IM_MANUAL;
+
+ engineConfiguration->isInjectionEnabled = true;
+ engineConfiguration->isIgnitionEnabled = true;
+ engineConfiguration->isCylinderCleanupEnabled = true;
+ engineConfiguration->secondTriggerChannelEnabled = true;
+
+ boardConfiguration->idleValvePin = GPIOE_2;
+ boardConfiguration->idleValvePinMode = OM_DEFAULT;
+ boardConfiguration->fuelPumpPin = GPIOC_13;
+ boardConfiguration->fuelPumpPinMode = OM_DEFAULT;
+ boardConfiguration->electronicThrottlePin1 = GPIOC_9;
+ boardConfiguration->o2heaterPin = GPIO_NONE;
+
+ boardConfiguration->injectionPins[0] = GPIOB_9;
+ boardConfiguration->injectionPins[1] = GPIOB_8;
+ boardConfiguration->injectionPins[2] = GPIOE_3;
+ boardConfiguration->injectionPins[3] = GPIOE_5;
+ boardConfiguration->injectionPins[4] = GPIOE_6;
+ boardConfiguration->injectionPins[5] = GPIOC_12;
+ boardConfiguration->injectionPins[6] = GPIO_NONE;
+ boardConfiguration->injectionPins[7] = GPIO_NONE;
+ boardConfiguration->injectionPins[8] = GPIO_NONE;
+ boardConfiguration->injectionPins[9] = GPIO_NONE;
+ boardConfiguration->injectionPins[10] = GPIO_NONE;
+ boardConfiguration->injectionPins[11] = GPIO_NONE;
+ boardConfiguration->injectionPinMode = OM_DEFAULT;
+
+ boardConfiguration->ignitionPins[0] = GPIOC_7;
+ boardConfiguration->ignitionPins[1] = GPIOE_4; // todo: update this value
+ boardConfiguration->ignitionPins[2] = GPIOE_0; // todo: update this value
+ boardConfiguration->ignitionPins[3] = GPIOE_1; // todo: update this value
+ boardConfiguration->ignitionPins[4] = GPIO_NONE;
+ boardConfiguration->ignitionPins[5] = GPIO_NONE;
+ boardConfiguration->ignitionPins[6] = GPIO_NONE;
+ boardConfiguration->ignitionPins[7] = GPIO_NONE;
+ boardConfiguration->ignitionPins[8] = GPIO_NONE;
+ boardConfiguration->ignitionPins[9] = GPIO_NONE;
+ boardConfiguration->ignitionPins[10] = GPIO_NONE;
+ boardConfiguration->ignitionPins[11] = GPIO_NONE;
+ boardConfiguration->ignitionPinMode = OM_DEFAULT;
+
+ boardConfiguration->malfunctionIndicatorPin = GPIOC_9;
+ boardConfiguration->malfunctionIndicatorPinMode = OM_DEFAULT;
+
+ boardConfiguration->fanPin = GPIOC_15;
+ boardConfiguration->fanPinMode = OM_DEFAULT;
+
+ boardConfiguration->idleSwitchPin = GPIOC_8;
+
+ boardConfiguration->triggerSimulatorPins[0] = GPIOD_1;
+ boardConfiguration->triggerSimulatorPins[1] = GPIOD_2;
+ boardConfiguration->triggerSimulatorPins[2] = GPIOD_3;
+
+ boardConfiguration->triggerSimulatorPinModes[0] = OM_DEFAULT;
+ boardConfiguration->triggerSimulatorPinModes[1] = OM_DEFAULT;
+ boardConfiguration->triggerSimulatorPinModes[2] = OM_DEFAULT;
+
+ boardConfiguration->HD44780_rs = GPIOE_9;
+ boardConfiguration->HD44780_e = GPIOE_11;
+ boardConfiguration->HD44780_db4 = GPIOE_13;
+ boardConfiguration->HD44780_db5 = GPIOE_15;
+ boardConfiguration->HD44780_db6 = GPIOB_11;
+ boardConfiguration->HD44780_db7 = GPIOB_13;
+
+ boardConfiguration->gps_rx_pin = GPIOB_7;
+ boardConfiguration->gps_tx_pin = GPIOB_6;
+
+ memset(boardConfiguration->adcHwChannelEnabled, 0, sizeof(boardConfiguration->adcHwChannelEnabled));
+ boardConfiguration->adcHwChannelEnabled[0] = ADC_SLOW;
+ boardConfiguration->adcHwChannelEnabled[1] = ADC_SLOW;
+ boardConfiguration->adcHwChannelEnabled[2] = ADC_SLOW;
+ boardConfiguration->adcHwChannelEnabled[3] = ADC_SLOW;
+ boardConfiguration->adcHwChannelEnabled[4] = ADC_FAST;
+
+ boardConfiguration->adcHwChannelEnabled[6] = ADC_SLOW;
+ boardConfiguration->adcHwChannelEnabled[7] = ADC_SLOW;
+ boardConfiguration->adcHwChannelEnabled[11] = ADC_SLOW;
+ boardConfiguration->adcHwChannelEnabled[12] = ADC_SLOW;
+ boardConfiguration->adcHwChannelEnabled[13] = ADC_SLOW;
+
+ boardConfiguration->triggerInputPins[0] = GPIOC_6;
+ boardConfiguration->triggerInputPins[1] = GPIOA_5;
+ boardConfiguration->logicAnalyzerPins[0] = GPIOA_8;
+ boardConfiguration->logicAnalyzerPins[1] = GPIOE_7;
+
+ boardConfiguration->idleThreadPeriod = 100;
+ boardConfiguration->consoleLoopPeriod = 200;
+ boardConfiguration->lcdThreadPeriod = 300;
+ boardConfiguration->tunerStudioThreadPeriod = 300;
+ boardConfiguration->generalPeriodicThreadPeriod = 50;
+
+ boardConfiguration->tunerStudioSerialSpeed = 38400;
+
+ boardConfiguration->boardTestModeJumperPin = GPIOB_0;
+
+ boardConfiguration->canDeviceMode = CD_USE_CAN2;
+ boardConfiguration->canTxPin = GPIOB_0;
+ boardConfiguration->canRxPin = GPIOB_12;
+
+ boardConfiguration->digitalPotentiometerSpiDevice = SPI_NONE;
+ boardConfiguration->digitalPotentiometerChipSelect[0] = GPIOD_7;
+ boardConfiguration->digitalPotentiometerChipSelect[1] = GPIO_NONE;
+ boardConfiguration->digitalPotentiometerChipSelect[2] = GPIOD_5;
+ boardConfiguration->digitalPotentiometerChipSelect[3] = GPIO_NONE;
+
+ boardConfiguration->is_enabled_spi_1 = false;
+ boardConfiguration->is_enabled_spi_2 = true;
+ boardConfiguration->is_enabled_spi_3 = true;
+}
+
+//void setDefaultNonPersistentConfiguration(engine_configuration2_s *engineConfiguration2) {
+//}
+
+void resetConfigurationExt(Logging * logger, engine_type_e engineType, engine_configuration_s *engineConfiguration,
+ engine_configuration2_s *engineConfiguration2, board_configuration_s *boardConfiguration) {
+ /**
+ * Let's apply global defaults first
+ */
+ setDefaultConfiguration(engineConfiguration, boardConfiguration);
+ engineConfiguration->engineType = engineType;
+ /**
+ * And override them with engine-specific defaults
+ */
+ switch (engineType) {
+#if EFI_SUPPORT_DODGE_NEON || defined(__DOXYGEN__)
+ case DODGE_NEON_1995:
+ setDodgeNeonEngineConfiguration(engineConfiguration, boardConfiguration);
+ break;
+#endif /* EFI_SUPPORT_DODGE_NEON */
+#if EFI_SUPPORT_FORD_ASPIRE || defined(__DOXYGEN__)
+ case FORD_ASPIRE_1996:
+ setFordAspireEngineConfiguration(engineConfiguration, boardConfiguration);
+ break;
+#endif /* EFI_SUPPORT_FORD_ASPIRE */
+#if EFI_SUPPORT_FORD_FIESTA || defined(__DOXYGEN__)
+ case FORD_FIESTA:
+ setFordFiestaDefaultEngineConfiguration(engineConfiguration);
+ break;
+#endif /* EFI_SUPPORT_FORD_FIESTA */
+#if EFI_SUPPORT_NISSAN_PRIMERA || defined(__DOXYGEN__)
+ case NISSAN_PRIMERA:
+ setNissanPrimeraEngineConfiguration(engineConfiguration);
+ break;
+#endif
+ case HONDA_ACCORD_CD:
+ setHondaAccordConfigurationThreeWires(engineConfiguration, boardConfiguration);
+ break;
+ case HONDA_ACCORD_CD_TWO_WIRES:
+ setHondaAccordConfigurationTwoWires(engineConfiguration, boardConfiguration);
+ break;
+ case HONDA_ACCORD_CD_DIP:
+ setHondaAccordConfigurationDip(engineConfiguration, boardConfiguration);
+ break;
+ case MITSU_4G93:
+ setMitsubishiConfiguration(engineConfiguration, boardConfiguration);
+ break;
+#if EFI_SUPPORT_1995_FORD_INLINE_6 || defined(__DOXYGEN__)
+ case FORD_INLINE_6_1995:
+ setFordInline6(engineConfiguration, boardConfiguration);
+ break;
+#endif /* EFI_SUPPORT_1995_FORD_INLINE_6 */
+ case GY6_139QMB:
+ setGy6139qmbDefaultEngineConfiguration(engineConfiguration);
+ break;
+ case MAZDA_MIATA_NB:
+ setMazdaMiataNbEngineConfiguration(engineConfiguration, boardConfiguration);
+ break;
+ case MAZDA_323:
+ setMazda323EngineConfiguration(engineConfiguration);
+ break;
+ case SATURN_ION_2004:
+ setSaturnIonEngineConfiguration(engineConfiguration);
+ break;
+ case MINI_COOPER_R50:
+ setMiniCooperR50(engineConfiguration, boardConfiguration);
+ break;
+ case FORD_ESCORT_GT:
+ setFordEscortGt(engineConfiguration, boardConfiguration);
+ break;
+ case MIATA_1990:
+ setMiata1990(engineConfiguration, boardConfiguration);
+ break;
+ case MIATA_1994:
+ setMiata1994(engineConfiguration, boardConfiguration);
+ break;
+ case MIATA_1996:
+ setMiata1996(engineConfiguration, boardConfiguration);
+ break;
+ case CITROEN_TU3JP:
+ setCitroenBerlingoTU3JPConfiguration(engineConfiguration, boardConfiguration);
+ break;
+ case ROVER_V8:
+ setRoverv8(engineConfiguration, boardConfiguration);
+ break;
+
+ default:
+ firmwareError("Unexpected engine type: %d", engineType);
+
+ }
+ applyNonPersistentConfiguration(logger, engineConfiguration, engineConfiguration2);
+
+#if EFI_TUNER_STUDIO
+ syncTunerStudioCopy();
+#endif
+}
+
+engine_configuration2_s::engine_configuration2_s() {
+}
+
+void applyNonPersistentConfiguration(Logging * logger, engine_configuration_s *engineConfiguration,
+ engine_configuration2_s *engineConfiguration2) {
+// todo: this would require 'initThermistors() to re-establish a reference, todo: fix
+// memset(engineConfiguration2, 0, sizeof(engine_configuration2_s));
+#if EFI_PROD_CODE
+ scheduleMsg(logger, "applyNonPersistentConfiguration()");
+#endif
+ initializeTriggerShape(logger, engineConfiguration, engineConfiguration2);
+ if (engineConfiguration2->triggerShape.getSize() == 0) {
+ firmwareError("triggerShape size is zero");
+ return;
+ }
+ if (engineConfiguration2->triggerShape.shaftPositionEventCount == 0) {
+ firmwareError("shaftPositionEventCount is zero");
+ return;
+ }
+
+ prepareOutputSignals(engineConfiguration, engineConfiguration2);
+ // todo: looks like this is here only for unit tests. todo: remove
+ initializeIgnitionActions(0, 0, engineConfiguration, engineConfiguration2,
+ &engineConfiguration2->engineEventConfiguration.ignitionEvents[0]);
+
+}
+
+void setOperationMode(engine_configuration_s *engineConfiguration, operation_mode_e mode) {
+ if (mode == FOUR_STROKE_CAM_SENSOR) {
+ engineConfiguration->rpmMultiplier = 0.5;
+ } else if (mode == FOUR_STROKE_CRANK_SENSOR) {
+ engineConfiguration->rpmMultiplier = 1;
+ }
+}
+
+operation_mode_e getOperationMode(engine_configuration_s const *engineConfiguration) {
+ if (engineConfiguration->rpmMultiplier == 1)
+ return FOUR_STROKE_CRANK_SENSOR;
+ return FOUR_STROKE_CAM_SENSOR;
+
+}
diff --git a/firmware/controllers/algo/engine_configuration.h b/firmware/controllers/algo/engine_configuration.h
new file mode 100644
index 0000000000..162ad53fed
--- /dev/null
+++ b/firmware/controllers/algo/engine_configuration.h
@@ -0,0 +1,437 @@
+/**
+ * @file engine_configuration.h
+ * @brief Main engine configuration data structure.
+ *
+ * @date Oct 30, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef ENGINE_CONFIGURATION_H_
+#define ENGINE_CONFIGURATION_H_
+
+#include "efifeatures.h"
+#include "crc.h"
+#include "sensor_types.h"
+#include "can_header.h"
+#include "rusefi_enums.h"
+
+typedef struct {
+ float coolantTempMinC;
+ float coolantTempMaxC;
+ float fuelAtMinTempMs;
+ float fuelAtMaxTempMs;
+
+ /**
+ * This value controls what RPM values we consider 'cranking' (any RPM below 'crankingRpm')
+ * Anything above 'crankingRpm' would be 'running'
+ */
+ short int crankingRpm;
+} cranking_parameters_s;
+
+#define FUEL_RPM_COUNT 16
+#define FUEL_LOAD_COUNT 16
+#define VE_RPM_COUNT 16
+#define VE_LOAD_COUNT 16
+#define AFR_RPM_COUNT 16
+#define AFR_LOAD_COUNT 16
+
+#define CLT_CURVE_SIZE 16
+#define IAT_CURVE_SIZE 16
+#define VBAT_INJECTOR_CURVE_SIZE 8
+
+#define IGN_LOAD_COUNT 16
+#define IGN_RPM_COUNT 16
+
+#define DWELL_CURVE_SIZE 8
+
+typedef enum {
+ AC_OFF = 0,
+ /**
+ * You would use this value if you want to see a detailed graph of your trigger events
+ */
+ AC_TRIGGER = 1, AC_MAP = 2,
+
+ Internal_ForceMyEnumIntSize_analog_chart_mode = ENUM_SIZE_HACK,
+} analog_chart_e;
+
+typedef enum {
+ /**
+ * This is the default mode in which ECU controls timing dynamically
+ */
+ TM_DYNAMIC = 0,
+ /**
+ * Fixed timing is useful while you are playing with a timing gun - you need to have fixed
+ * timing if you want to install your distributor at some specific angle
+ */
+ TM_FIXED = 1,
+
+ Internal_ForceMyEnumIntSize_timing_mode = ENUM_SIZE_HACK,
+} timing_mode_e;
+
+typedef enum {
+ CD_OFF = 0,
+ CD_USE_CAN1 = 1,
+ CD_USE_CAN2 = 2,
+
+ Internal_ForceMyEnumIntSize_can_device_mode = ENUM_SIZE_HACK,
+} can_device_mode_e;
+
+typedef struct {
+ adc_channel_e afrAdcChannel;
+ float v1;
+ float value1;
+ float v2;
+ float value2;
+} afr_sensor_s;
+
+#define DWELL_COUNT 8
+
+/**
+ * @brief Trigger wheel(s) configuration
+ */
+typedef struct {
+ trigger_type_e triggerType;
+
+ int customIsSynchronizationNeeded;
+
+ int customTotalToothCount;
+ int customSkippedToothCount;
+
+ float customSyncRatioFrom;
+ float customSyncRatioTo;
+
+ int customUseRiseEdge;
+
+} trigger_config_s;
+
+
+#define HW_MAX_ADC_INDEX 16
+
+typedef struct {
+ // WARNING: by default, our small enums are ONE BYTE. this one is made 4-byte with the 'ENUM_SIZE_HACK' hack
+ brain_pin_e idleValvePin;
+ pin_output_mode_e idleValvePinMode;
+
+ brain_pin_e fuelPumpPin;
+ pin_output_mode_e fuelPumpPinMode;
+
+ brain_pin_e injectionPins[12];
+ pin_output_mode_e injectionPinMode;
+
+ brain_pin_e ignitionPins[12];
+ pin_output_mode_e ignitionPinMode;
+
+ brain_pin_e malfunctionIndicatorPin;
+ pin_output_mode_e malfunctionIndicatorPinMode;
+
+ brain_pin_e fanPin;
+ pin_output_mode_e fanPinMode;
+
+ brain_pin_e electronicThrottlePin1;
+ pin_output_mode_e electronicThrottlePin1Mode;
+
+ brain_pin_e idleSwitchPin;
+ pin_input_mode_e idleSwitchPinMode;
+
+ brain_pin_e alternatorControlPin;
+ pin_output_mode_e alternatorControlPinMode;
+
+ brain_pin_e HD44780_rs;
+ brain_pin_e HD44780_e;
+ brain_pin_e HD44780_db4;
+ brain_pin_e HD44780_db5;
+ brain_pin_e HD44780_db6;
+ brain_pin_e HD44780_db7;
+
+ brain_pin_e gps_rx_pin;
+ brain_pin_e gps_tx_pin;
+
+ int idleSolenoidFrequency;
+
+ int unused;
+
+
+ /**
+ * Digital Potentiometer is used by stock ECU stimulation code
+ */
+ spi_device_e digitalPotentiometerSpiDevice;
+ brain_pin_e digitalPotentiometerChipSelect[DIGIPOT_COUNT];
+
+ adc_channel_mode_e adcHwChannelEnabled[HW_MAX_ADC_INDEX];
+
+ brain_pin_e triggerInputPins[2];
+ brain_pin_e logicAnalyzerPins[2];
+
+ int idleThreadPeriod;
+ int consoleLoopPeriod;
+ int lcdThreadPeriod;
+ int tunerStudioThreadPeriod;
+ int generalPeriodicThreadPeriod;
+
+ int tunerStudioSerialSpeed;
+
+ brain_pin_e boardTestModeJumperPin;
+
+ can_device_mode_e canDeviceMode;
+ brain_pin_e canTxPin;
+ brain_pin_e canRxPin;
+
+ brain_pin_e triggerSimulatorPins[3];
+ pin_output_mode_e triggerSimulatorPinModes[3];
+
+ brain_pin_e o2heaterPin;
+ pin_output_mode_e o2heaterPinModeTodO;
+
+ unsigned int is_enabled_spi_1 : 1;
+ unsigned int is_enabled_spi_2 : 1;
+ unsigned int is_enabled_spi_3 : 1;
+
+ int unused2[7];
+
+
+} board_configuration_s;
+
+
+
+/**
+ * @brief Engine configuration.
+ * Values in this data structure are adjustable and persisted in on-board flash RAM.
+ *
+ * The offsets are tracked using
+ * https://docs.google.com/spreadsheet/ccc?key=0AiAmAn6tn3L_dGJXZDZOcVVhaG9SaHZKU1dyMjhEV0E
+ *
+ * todo: currently the fields here are simply in the order in which they were implemented
+ * todo: re-arrange this structure one we have a stable code version
+ */
+typedef struct {
+ float injectorLag; // size 4, offset 0
+ /**
+ * cc/min, cubic centimeter per minute
+ *
+ * By the way, g/s = 0.125997881 * (lb/hr)
+ * g/s = 0.125997881 * (cc/min)/10.5
+ * g/s = 0.0119997981 * cc/min
+ *
+ */
+ float injectorFlow; // size 4, offset 4
+ float battInjectorLagCorrBins[VBAT_INJECTOR_CURVE_SIZE]; // size 32, offset 8
+ float battInjectorLagCorr[VBAT_INJECTOR_CURVE_SIZE]; // size 32, offset 40
+
+ float cltFuelCorrBins[CLT_CURVE_SIZE]; // size 64, offset 72
+ float cltFuelCorr[CLT_CURVE_SIZE]; // size 64, offset 136
+
+ float iatFuelCorrBins[IAT_CURVE_SIZE]; // size 64, offset 200
+ float iatFuelCorr[IAT_CURVE_SIZE]; // size 64, offset 264
+
+ /**
+ * Should the trigger emulator push data right into trigger input, eliminating the need for physical jumper wires?
+ * PS: Funny name, right? :)
+ */
+ short int directSelfStimulation; // size 2, offset 328
+
+ // todo: extract these two fields into a structure
+ // todo: we need two sets of TPS parameters - modern ETBs have to sensors
+ short int tpsMin; // size 2, offset 330
+ // tpsMax value as 10 bit ADC value. Not Voltage!
+ short int tpsMax; // size 2, offset 332
+ short int analogChartMode;
+
+ cranking_parameters_s crankingSettings;
+
+ MAP_sensor_config_s map;
+
+ // todo: merge with channel settings, use full-scale Thermistor here!
+ ThermistorConf cltThermistorConf; // size 40 (10*4), offset 336
+ ThermistorConf iatThermistorConf; // size 40, offset 376
+
+ float sparkDwellBins[DWELL_COUNT]; // offset 580
+ float sparkDwell[DWELL_COUNT];
+
+ float ignitionLoadBins[IGN_LOAD_COUNT];
+ float ignitionRpmBins[IGN_RPM_COUNT];
+
+ /**
+ * this value could be used to offset the whole ignition timing table by a constant
+ */
+ float ignitionOffset;
+
+ /**
+ * While cranking (which causes battery voltage to drop) we can calculate dwell time in shaft
+ * degrees, not in absolute time as in running mode.
+ */
+ float crankingChargeAngle;
+
+ timing_mode_e timingMode;
+ /**
+ * This value is used in 'fixed timing' mode, i.e. constant timing
+ * This mode is useful for instance while adjusting distributor location
+ */
+ float fixedModeTiming;
+
+ // WARNING: by default, our small enums are ONE BYTE. but if the are surrounded by non-enums - alignments do the trick
+ engine_type_e engineType;
+
+ float fuelLoadBins[FUEL_LOAD_COUNT]; //
+ // RPM is float and not integer in order to use unified methods for interpolation
+ float fuelRpmBins[FUEL_RPM_COUNT]; //
+
+ /**
+ * Engine displacement, in liters
+ * see also cylindersCount
+ */
+ float displacement;
+ int rpmHardLimit;
+
+ injection_mode_e crankingInjectionMode;
+ injection_mode_e injectionMode;
+
+ /**
+ * Inside rusEfi all the angles are handled in relation to the trigger synchronization event
+ * which depends on the trigger shape and has nothing to do wit Top Dead Center (TDC)
+ *
+ * For engine configuration humans need angles from TDC.
+ *
+ * This field is the angle between Top Dead Center (TDC) and the first trigger event.
+ * Knowing this angle allows us to control timing and other angles in reference to TDC.
+ */
+ float globalTriggerAngleOffset;
+ /**
+ * We have 3.3V ADC and most of the analog input signals are 5V, this forces us to use
+ * voltage dividers on the input circuits. This parameter holds the coefficient of these dividers.
+ * see also vbattDividerCoeff
+ */
+ float analogInputDividerCoefficient;
+
+ /**
+ * This setting controls which algorithm is used for ENGINE LOAD
+ */
+ engine_load_mode_e algorithm;
+
+ /**
+ * see
+ */
+ float vbattDividerCoeff;
+ /**
+ * Cooling fan turn-on temperature threshold, in Celsuis
+ */
+ float fanOnTemperature;
+ /**
+ * Cooling fan turn-off temperature threshold, in Celsuis
+ */
+ float fanOffTemperature;
+
+ int canReadEnabled;
+ int canWriteEnabled;
+ can_nbc_e can_nbc_type;
+ int can_sleep_period;
+
+ int cylindersCount;
+
+ ignition_mode_e ignitionMode;
+ firing_order_e firingOrder;
+
+ /**
+ * This magic constant is about four-stroke engines with camshaft position sensors.
+ * On any four stroke engine, each revolution of the camshaft is two revolutions
+ * of the crankshaft. If camshaft position is our primary sensor, we use this multiplier
+ * to convert from camshaft angles to crankshaft angles. All angels across the system
+ * should be crankshaft angles.
+ */
+
+ float rpmMultiplier;
+
+ display_mode_e displayMode;
+
+ log_format_e logFormat;
+
+ int firmwareVersion;
+ int HD44780width;
+ int HD44780height;
+
+ adc_channel_e tpsAdcChannel;
+ int overrideCrankingIgnition;
+ int analogChartFrequency;
+
+ trigger_config_s triggerConfig;
+
+ int space;
+ adc_channel_e vBattAdcChannel;
+
+ float globalFuelCorrection;
+
+ // todo: merge with channel settings, use full-scale Thermistor!
+ adc_channel_e cltAdcChannel;
+ adc_channel_e iatAdcChannel;
+ adc_channel_e mafAdcChannel;
+
+ afr_sensor_s afrSensor;
+
+ float injectionOffset;
+
+ float crankingTimingAngle;
+
+ float diffLoadEnrichmentCoef;
+
+ air_pressure_sensor_config_s baroSensor;
+
+ float veLoadBins[VE_LOAD_COUNT];
+ float veRpmBins[VE_RPM_COUNT];
+ float afrLoadBins[AFR_LOAD_COUNT];
+ float afrRpmBins[AFR_RPM_COUNT];
+
+ // the large tables are always in the end - that's related to TunerStudio paging implementation
+ float fuelTable[FUEL_LOAD_COUNT][FUEL_RPM_COUNT]; // size 1024
+ float ignitionTable[IGN_LOAD_COUNT][IGN_RPM_COUNT]; // size 1024
+
+ float veTable[VE_LOAD_COUNT][VE_RPM_COUNT]; // size 1024
+ float afrTable[AFR_LOAD_COUNT][AFR_RPM_COUNT]; // size 1024
+
+ board_configuration_s bc;
+
+ int hasMapSensor;
+ int hasCltSensor;
+
+ idle_mode_e idleMode;
+
+ bool isInjectionEnabled : 1; // bit 0
+ bool isIgnitionEnabled : 1; // bit 1
+ bool isCylinderCleanupEnabled : 1; // bit 2
+ bool secondTriggerChannelEnabled : 1; // bit 3
+ bool needSecondTriggerInput : 1; // bit 4
+
+ int unused3[8];
+
+} engine_configuration_s;
+
+void setOperationMode(engine_configuration_s *engineConfiguration, operation_mode_e mode);
+operation_mode_e getOperationMode(engine_configuration_s const *engineConfiguration);
+
+typedef struct {
+ engine_configuration_s engineConfiguration;
+} persistent_config_s;
+
+typedef struct {
+ int version;
+ int size;
+ persistent_config_s persistentConfiguration;
+ crc_t value;
+} persistent_config_container_s;
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+const char* getConfigurationName(engine_configuration_s *engineConfiguration);
+void setDefaultConfiguration(engine_configuration_s *engineConfiguration, board_configuration_s *boardConfiguration);
+void setWholeFuelMap(engine_configuration_s *engineConfiguration, float value);
+void setConstantDwell(engine_configuration_s *engineConfiguration, float dwellMs);
+void printFloatArray(const char *prefix, float array[], int size);
+
+void incrementGlobalConfigurationVersion(void);
+int getGlobalConfigurationVersion(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* ENGINE_CONFIGURATION_H_ */
diff --git a/firmware/controllers/algo/engine_state.h b/firmware/controllers/algo/engine_state.h
new file mode 100644
index 0000000000..9f371146c5
--- /dev/null
+++ b/firmware/controllers/algo/engine_state.h
@@ -0,0 +1,15 @@
+/**
+ * @file engine_state.h
+ * @brief One header which acts as gateway to current engine state
+ *
+ * @date Dec 20, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef ENGINE_STATE_H_
+#define ENGINE_STATE_H_
+
+#include "allsensors.h"
+#include "rpm_calculator.h"
+
+#endif /* ENGINE_STATE_H_ */
diff --git a/firmware/controllers/algo/error_handling.h b/firmware/controllers/algo/error_handling.h
new file mode 100644
index 0000000000..b9df792b39
--- /dev/null
+++ b/firmware/controllers/algo/error_handling.h
@@ -0,0 +1,64 @@
+/**
+ * @file error_handling.h
+ *
+ * @date Mar 6, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef ERROR_HANDLING_H_
+#define ERROR_HANDLING_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+#include "obd_error_codes.h"
+#include "stdbool.h"
+
+/**
+ * Something is wrong, but we can live with it: some minor sensor is disconnected
+ * or something like that
+ *
+ */
+int warning(obd_code_e code, const char *fmt, ...);
+/**
+ * Something really bad had happened - firmware cannot function
+ *
+ * todo: better method name?
+ */
+void firmwareError(const char *fmt, ...);
+bool hasFirmwareError(void);
+char *getFirmwareError(void);
+
+/**
+ * declared as a macro so that this code does not use stack
+ * so that it would not crash the error handler in case of stack issues
+ */
+#if CH_DBG_SYSTEM_STATE_CHECK
+#define hasFatalError() (dbg_panic_msg != NULL)
+#else
+#define hasFatalError() (FALSE)
+#endif
+
+void chDbgPanic3(const char *msg, const char * file, int line);
+
+void initErrorHandling(void);
+char *getWarninig(void);
+
+// todo: better place for this shared declaration?
+int getRusEfiVersion(void);
+
+/**
+ * @deprecated Global panic is inconvenient because it's hard to deliver the error message while whole instance
+ * is stopped. Please use firmwareWarning() instead
+ */
+#define efiAssert(condition, message, result) { if (!(condition)) { firmwareError(message); return result; } }
+
+#define efiAssertVoid(condition, message) { if (!(condition)) { firmwareError(message); return; } }
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* ERROR_HANDLING_H_ */
diff --git a/firmware/controllers/algo/event_registry.cpp b/firmware/controllers/algo/event_registry.cpp
new file mode 100644
index 0000000000..2091ea189c
--- /dev/null
+++ b/firmware/controllers/algo/event_registry.cpp
@@ -0,0 +1,34 @@
+/**
+ * @file event_registry.cpp
+ * @brief This data structure knows when to do what
+ *
+ * @date Nov 27, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ */
+
+#include "event_registry.h"
+#include "main.h"
+#include "engine_math.h"
+
+//void registerActuatorEventWhat(ActuatorEventList *list, int eventIndex, OutputSignal *actuator, float angleOffset) {
+// ActuatorEvent *e = list->getNextActuatorEvent();
+// if (e == NULL)
+// return; // error already reported
+// e->position.eventIndex = eventIndex;
+// e->actuator = actuator;
+// e->position.angleOffset = angleOffset;
+//}
diff --git a/firmware/controllers/algo/event_registry.h b/firmware/controllers/algo/event_registry.h
new file mode 100644
index 0000000000..9b2ddd9828
--- /dev/null
+++ b/firmware/controllers/algo/event_registry.h
@@ -0,0 +1,83 @@
+/**
+ * @file event_registry.h
+ *
+ * @date Nov 27, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef EVENT_REGISTRY_H_
+#define EVENT_REGISTRY_H_
+
+#include "global.h"
+#include "signal_executor.h"
+
+#define MAX_EVENT_COUNT 80
+
+/**
+ * This structure defines an angle position within the trigger
+ */
+typedef struct {
+ /**
+ * That's trigger event index
+ */
+ int eventIndex;
+ float eventAngle;
+ /**
+ * Angle offset from the trigger event
+ */
+ float angleOffset;
+} event_trigger_position_s;
+
+typedef struct {
+ event_trigger_position_s position;
+ OutputSignal *actuator;
+} ActuatorEvent;
+
+typedef struct IgnitionEvent_struct IgnitionEvent;
+
+struct IgnitionEvent_struct {
+ io_pin_e io_pin;
+ scheduling_s signalTimerUp;
+ scheduling_s signalTimerDown;
+ event_trigger_position_s dwellPosition;
+ float advance;
+ event_trigger_position_s sparkPosition;
+ IgnitionEvent *next;
+ char *name;
+};
+
+template
+class ArrayList {
+public:
+ int size;
+ Type events[Dimention];
+ void resetEventList(void);
+ Type *getNextActuatorEvent(void);
+};
+
+template
+void ArrayList< Type, Dimention>::resetEventList(void) {
+ size = 0;
+}
+
+template
+Type * ArrayList< Type, Dimention>::getNextActuatorEvent(void) {
+ efiAssert(size < Dimention, "registerActuatorEvent() too many events", (Type *)NULL);
+ return &events[size++];
+}
+
+typedef ArrayList ActuatorEventList;
+
+typedef ArrayList IgnitionEventList;
+
+///**
+// * this is an intermediate implementation of flexible event handling.
+// *
+// * In the future implementation we will drop the 'eventIndex' parameter and everything will be
+// * angle-driven. But that's just a plan for next iteration.
+// *
+// * @param actuator injector or coil OutputSignal
+// */
+//void registerActuatorEvent(ActuatorEventList *list, int eventIndex, OutputSignal *actuator, float angleOffset);
+
+#endif /* EVENT_REGISTRY_H_ */
diff --git a/firmware/controllers/algo/fuel_math.cpp b/firmware/controllers/algo/fuel_math.cpp
new file mode 100644
index 0000000000..084abddfaf
--- /dev/null
+++ b/firmware/controllers/algo/fuel_math.cpp
@@ -0,0 +1,166 @@
+/**
+ * @file fuel_math.cpp
+ * @brief Fuel amount calculation logic
+ *
+ * While engine running, fuel amount is an interpolated value from the fuel map by getRpm() and getEngineLoad()
+ * On top of the value from the fuel map we also apply
+ * 1) getInjectorLag() correction to account for fuel injector lag
+ * 2) getCltCorrection() for warm-up
+ * 3) getIatCorrection() to account for cold weather
+ *
+ * getCrankingFuel() depents only on getCoolantTemperature()
+ *
+ *
+ * @date May 27, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ *
+ */
+
+#include "main.h"
+#include "fuel_math.h"
+#include "interpolation.h"
+#include "engine_configuration.h"
+#include "allsensors.h"
+#include "engine_math.h"
+#include "rpm_calculator.h"
+#include "speed_density.h"
+#if EFI_ACCEL_ENRICHMENT
+#include "accel_enrichment.h"
+#endif /* EFI_ACCEL_ENRICHMENT */
+
+float getBaseFuel(Engine *engine, int rpm) {
+ if (engine->engineConfiguration->algorithm == LM_SPEED_DENSITY) {
+ return getSpeedDensityFuel(engine, rpm);
+ } else {
+ float engineLoad = getEngineLoadT(engine);
+ return getBaseTableFuel(rpm, engineLoad);
+ }
+}
+
+/**
+ * @returns Length of fuel injection, in milliseconds
+ */
+float getFuelMs(int rpm, Engine *engine) {
+ if (isCranking()) {
+ return getCrankingFuel();
+ } else {
+ float baseFuel = getBaseFuel(engine, rpm);
+ float fuel = getRunningFuel(baseFuel, engine, rpm);
+ return fuel;
+ }
+}
+
+float getRunningFuel(float baseFuel, Engine *engine, int rpm) {
+ float iatCorrection = getIatCorrection(getIntakeAirTemperature());
+ float cltCorrection = getCltCorrection(getCoolantTemperature());
+ float injectorLag = getInjectorLag(getVBatt());
+
+#if EFI_ACCEL_ENRICHMENT
+ float accelEnrichment = getAccelEnrichment();
+ // todo: accelEnrichment
+#endif /* EFI_ACCEL_ENRICHMENT */
+
+ return baseFuel * cltCorrection * iatCorrection + injectorLag;
+}
+
+extern Engine engine;
+extern engine_configuration_s *engineConfiguration;
+
+static Map3D1616 fuelMap;
+
+/**
+ * @brief Injector lag correction
+ * @param vBatt Battery voltage.
+ * @return Time in ms for injection opening time based on current battery voltage
+ */
+float getInjectorLag(float vBatt) {
+ if (cisnan(vBatt)) {
+ warning(OBD_System_Voltage_Malfunction, "vBatt=%f", vBatt);
+ return 0;
+ }
+ float vBattCorrection = interpolate2d(vBatt, engineConfiguration->battInjectorLagCorrBins,
+ engineConfiguration->battInjectorLagCorr, VBAT_INJECTOR_CURVE_SIZE);
+ return engineConfiguration->injectorLag + vBattCorrection;
+}
+
+/**
+ * @brief Initialize fuel map data structure
+ * @note this method has nothing to do with fuel map VALUES - it's job
+ * is to prepare the fuel map data structure for 3d interpolation
+ */
+void prepareFuelMap(void) {
+ fuelMap.init(engineConfiguration->fuelTable);
+}
+
+/**
+ * @brief Engine warm-up fuel correction.
+ */
+float getCltCorrection(float clt) {
+ if (cisnan(clt))
+ return 1; // this error should be already reported somewhere else, let's just handle it
+ return interpolate2d(clt, engineConfiguration->cltFuelCorrBins, engineConfiguration->cltFuelCorr, CLT_CURVE_SIZE);
+}
+
+float getIatCorrection(float iat) {
+ if (cisnan(iat))
+ return 1; // this error should be already reported somewhere else, let's just handle it
+ return interpolate2d(iat, engineConfiguration->iatFuelCorrBins, engineConfiguration->iatFuelCorr, IAT_CURVE_SIZE);
+}
+
+float getBaseTableFuel(int rpm, float engineLoad) {
+ efiAssert(!cisnan(engineLoad), "invalid el", NAN);
+ return fuelMap.getValue(engineLoad, engineConfiguration->fuelLoadBins, rpm,
+ engineConfiguration->fuelRpmBins);
+}
+
+float getCrankingFuel(void) {
+ return getStartingFuel(getCoolantTemperature());
+}
+
+float getStartingFuel(float coolantTemperature) {
+ // these magic constants are in Celsius
+ if (cisnan(coolantTemperature) || coolantTemperature < engineConfiguration->crankingSettings.coolantTempMinC)
+ return engineConfiguration->crankingSettings.fuelAtMinTempMs;
+ if (coolantTemperature > engineConfiguration->crankingSettings.coolantTempMaxC)
+ return engineConfiguration->crankingSettings.fuelAtMaxTempMs;
+ return interpolate(engineConfiguration->crankingSettings.coolantTempMinC,
+ engineConfiguration->crankingSettings.fuelAtMinTempMs,
+ engineConfiguration->crankingSettings.coolantTempMaxC,
+ engineConfiguration->crankingSettings.fuelAtMaxTempMs, coolantTemperature);
+}
+
+/**
+ * @return 0 for OM_DEFAULT and OM_OPENDRAIN
+ */
+
+inline static int getElectricalValue0(pin_output_mode_e mode) {
+ return mode == OM_INVERTED || mode == OM_OPENDRAIN_INVERTED;
+}
+
+/**
+ * @return 1 for OM_DEFAULT and OM_OPENDRAIN
+ */
+inline static int getElectricalValue1(pin_output_mode_e mode) {
+ return mode == OM_DEFAULT || mode == OM_OPENDRAIN;
+}
+
+// todo: this method is here for unit test visibility. todo: move to a bette place!
+int getElectricalValue(int logicalValue, pin_output_mode_e mode) {
+ efiAssert(mode <= OM_OPENDRAIN_INVERTED, "invalid pin_output_mode_e", -1);
+
+ return logicalValue ? getElectricalValue1(mode) : getElectricalValue0(mode);
+}
+
diff --git a/firmware/controllers/algo/fuel_math.h b/firmware/controllers/algo/fuel_math.h
new file mode 100644
index 0000000000..c8c20e517b
--- /dev/null
+++ b/firmware/controllers/algo/fuel_math.h
@@ -0,0 +1,34 @@
+/**
+ * @file fuel_math.h
+ *
+ * @date May 27, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef FUEL_MAP_H_
+#define FUEL_MAP_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+#include "engine.h"
+
+void prepareFuelMap(void);
+
+float getBaseFuel(Engine *engine, int rpm);
+float getBaseTableFuel(int rpm, float engineLoad);
+float getIatCorrection(float iat);
+float getInjectorLag(float vBatt);
+float getCltCorrection(float clt);
+float getRunningFuel(float baseFuel, Engine *engine, int rpm);
+float getCrankingFuel(void);
+float getStartingFuel(float coolantTemperature);
+float getFuelMs(int rpm, Engine *engine);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* FUEL_MAP_H_ */
diff --git a/firmware/controllers/algo/idle_controller.c b/firmware/controllers/algo/idle_controller.c
new file mode 100644
index 0000000000..65b1e92b51
--- /dev/null
+++ b/firmware/controllers/algo/idle_controller.c
@@ -0,0 +1,95 @@
+/**
+ * @file idle_controller.c
+ * @brief Simple Idle Air Valve control algorithm
+ *
+ * This algorithm is trying to get current RPM to the desired 'target' value
+ * by changing Idle Air Valve solenoid duty cycle. Depending on how far current RPM
+ * is from the target RPM, the incremental change would be smaller or bigger.
+ *
+ *
+ * todo: DEFAULT_IDLE_DUTY should be a field on the IdleValveState, not a constant
+ *
+ * @date May 22, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "idle_controller.h"
+#include "efilib.h"
+
+// todo: move this to "idle_controller.h"
+int isCranking(void);
+
+static int lastGoodValue = DEFAULT_IDLE_DUTY;
+
+void idleInit(IdleValveState *idle) {
+ idle->value = DEFAULT_IDLE_DUTY;
+ setIdleRpm(idle, DEFAULT_TARGET_RPM);
+ idle->timeOfLastIdleChange = 0;
+}
+
+void setIdleRpm(IdleValveState *idle, int targetRpm) {
+ idle->targetRpmRangeLeft = (int)(targetRpm * 0.93);
+ idle->targetRpmRangeRight = (int)(targetRpm * 1.07);
+}
+
+/**
+ * @brief sets new idle valve duty cycle: checks the bounds and reports new value
+ */
+static int setNewValue(IdleValveState *idle, int currentRpm, int now, char * msg, int newValue) {
+ newValue = maxI(newValue, MIN_IDLE);
+ newValue = minI(newValue, MAX_IDLE);
+
+ if (idle->value != newValue) {
+ idleDebug(msg, currentRpm);
+ idle->timeOfLastIdleChange = now;
+ }
+
+ idle->value = newValue;
+ return newValue;
+}
+
+static int changeValue(IdleValveState *idle, int currentRpm, int now, char * msg, int delta) {
+ int newValue = idle->value + delta;
+ return setNewValue(idle, currentRpm, now, msg, newValue);
+}
+
+/**
+ * now - current time in seconds
+ */
+int getIdle(IdleValveState *idle, int currentRpm, int now) {
+ if (currentRpm == 0 || isCranking()) {
+ return setNewValue(idle, currentRpm, now, "cranking value: ", DEFAULT_IDLE_DUTY);
+ }
+
+ if (currentRpm < 0.7 * idle->targetRpmRangeLeft) {
+ return setNewValue(idle, currentRpm, now, "RPMs are seriously low: ", lastGoodValue);
+ }
+
+ if (now - idle->timeOfLastIdleChange < IDLE_PERIOD) {
+ // too soon to adjust anything - exiting
+ return idle->value;
+ }
+
+ if (currentRpm > idle->targetRpmRangeLeft && currentRpm < idle->targetRpmRangeRight) {
+ // current RPM is good enough
+ // todo: need idle signal input
+ //lastGoodValue = idle->value;
+ return idle->value;
+ }
+
+ if (currentRpm >= idle->targetRpmRangeRight + 100)
+ return changeValue(idle, currentRpm, now, "idle control: rpm is too high: ", -IDLE_DECREASE_STEP);
+
+ if (currentRpm >= idle->targetRpmRangeRight)
+ return changeValue(idle, currentRpm, now, "idle control: rpm is a bit too high: ", -1);
+
+ // we are here if RPM is low, let's see how low
+// if (currentRpm < 0.7 * idle->targetRpmRangeLeft) {
+// // todo: act faster in case of really low RPM?
+// return setNewValue(idle, currentRpm, now, "RPMs are seriously low: ", 15 * IDLE_INCREASE_STEP);
+// } else
+ if (currentRpm < idle->targetRpmRangeLeft - 100) {
+ return changeValue(idle, currentRpm, now, "idle control: RPMs are low: ", IDLE_INCREASE_STEP);
+ }
+ return changeValue(idle, currentRpm, now, "idle control: RPMs are a bit low: ", 1);
+}
diff --git a/firmware/controllers/algo/idle_controller.h b/firmware/controllers/algo/idle_controller.h
new file mode 100644
index 0000000000..b26ae30318
--- /dev/null
+++ b/firmware/controllers/algo/idle_controller.h
@@ -0,0 +1,50 @@
+/**
+ * @file idle_controller.h
+ *
+ * @date May 22, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef IDLE_CONTROLLER_H_
+#define IDLE_CONTROLLER_H_
+
+// 700‰ duty cycle by default
+#define DEFAULT_IDLE_DUTY 600
+
+#define DEFAULT_TARGET_RPM 1000
+//#define DEFAULT_TARGET_RPM 2000
+
+#define IDLE_PERIOD 1
+
+// Per mil (1/1000) values
+#define MIN_IDLE 100
+#define MAX_IDLE 900
+#define IDLE_INCREASE_STEP 5
+#define IDLE_DECREASE_STEP 5
+
+
+typedef struct {
+ int time;
+
+ int targetRpmRangeLeft, targetRpmRangeRight;
+
+ int value;
+ int timeOfLastIdleChange;
+} IdleValveState;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void idleInit(IdleValveState *idle);
+int getIdle(IdleValveState *idle, int currentRpm, int time);
+void setIdleRpm(IdleValveState *idle, int targetRpm);
+
+void idleDebug(char *msg, int value);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* IDLE_CONTROLLER_H_ */
diff --git a/firmware/controllers/algo/io_pins.h b/firmware/controllers/algo/io_pins.h
new file mode 100644
index 0000000000..b68283dd5a
--- /dev/null
+++ b/firmware/controllers/algo/io_pins.h
@@ -0,0 +1,142 @@
+/**
+ * @file io_pins.h
+ *
+ * @date Jan 24, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef STATUS_LEDS_H_
+#define STATUS_LEDS_H_
+
+#include "main.h"
+
+#define GPIO_NULL NULL
+
+/**
+ * Logical pins. See brain_pin_e for physical pins.
+ */
+typedef enum {
+ LED_WARNING, // Orange on-board LED
+ LED_RUNNING, // Green on-board LED
+ LED_ERROR, // Red on-board LED
+ LED_COMMUNICATION_1, // Blue on-board LED
+ LED_EXT_1, // external board LED
+ LED_EXT_2, // external board LED
+ LED_EXT_3, // external board LED
+ LED_DEBUG,
+ LED_EMULATOR,
+
+ /**
+ * see board_configuration_s->idleValvePin
+ */
+ IDLE_VALVE,
+ TRIGGER_EMULATOR_PRIMARY,
+ TRIGGER_EMULATOR_SECONDARY,
+ TRIGGER_EMULATOR_3RD,
+
+ SPARKOUT_1_OUTPUT,
+ SPARKOUT_2_OUTPUT,
+ SPARKOUT_3_OUTPUT,
+ SPARKOUT_4_OUTPUT,
+ SPARKOUT_5_OUTPUT,
+ SPARKOUT_6_OUTPUT,
+ SPARKOUT_7_OUTPUT,
+ SPARKOUT_8_OUTPUT,
+ SPARKOUT_9_OUTPUT,
+ SPARKOUT_10_OUTPUT,
+ SPARKOUT_11_OUTPUT,
+ SPARKOUT_12_OUTPUT,
+
+ INJECTOR_1_OUTPUT,
+ INJECTOR_2_OUTPUT,
+ INJECTOR_3_OUTPUT,
+ INJECTOR_4_OUTPUT,
+ INJECTOR_5_OUTPUT,
+ INJECTOR_6_OUTPUT,
+ INJECTOR_7_OUTPUT,
+ INJECTOR_8_OUTPUT,
+ INJECTOR_9_OUTPUT,
+ INJECTOR_10_OUTPUT,
+ INJECTOR_11_OUTPUT,
+ INJECTOR_12_OUTPUT,
+
+ ELECTRONIC_THROTTLE_CONTROL_1,
+ ELECTRONIC_THROTTLE_CONTROL_2,
+ ELECTRONIC_THROTTLE_CONTROL_3,
+
+ /**
+ * these seven segment display pins are related to unused external tachometer code
+ * I still have the hardware so maybe one day I will fix it, but for now it's just dead code
+ * See https://www.youtube.com/watch?v=YYiHoN6MBqE
+ * todo: this should be re-implemented in a smarter way with some sort of multiplexing anyway
+ */
+ /* digit 1 */
+ LED_HUGE_0, // B2
+ LED_HUGE_1,
+ LED_HUGE_2,
+ LED_HUGE_3,
+ LED_HUGE_4,
+ LED_HUGE_5,
+ LED_HUGE_6,
+ /* digit 2 */
+ LED_HUGE_7,
+ LED_HUGE_8,
+ LED_HUGE_9, // E15
+ LED_HUGE_10,
+ LED_HUGE_11,
+ LED_HUGE_12,
+ LED_HUGE_13,
+ /* digit 3 */
+ LED_HUGE_14,
+ LED_HUGE_15,
+ LED_HUGE_16,
+ LED_HUGE_17,
+ LED_HUGE_18,
+ LED_HUGE_19,
+ LED_HUGE_20,
+
+ // malfunction LED indicator - CheckEngine
+ LED_CHECK_ENGINE,
+
+ FUEL_PUMP_RELAY,
+ FAN_RELAY,
+ O2_HEATER,
+
+ SPI_CS_1,
+ SPI_CS_2,
+ SPI_CS_3,
+ SPI_CS_4,
+ SPI_CS_SD_MODULE,
+
+
+ /**
+ * This output pin is used to turn alternator on or off
+ */
+ ALTERNATOR_SWITCH,
+
+} io_pin_e;
+
+#define IO_PIN_COUNT 100
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void initPrimaryPins(void);
+void initOutputPins(void);
+const char *getPinName(io_pin_e io_pin);
+void turnOutputPinOn(io_pin_e pin);
+void turnOutputPinOff(io_pin_e pin);
+void setOutputPinValue(io_pin_e pin, int logicValue);
+int getElectricalValue(int logicalValue, pin_output_mode_e mode);
+int getOutputPinValue(io_pin_e pin);
+void setDefaultPinState(io_pin_e pin, pin_output_mode_e *defaultState);
+
+void outputPinRegisterExt2(const char *msg, io_pin_e ioPin, brain_pin_e brainPin, pin_output_mode_e *outputMode);
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+
+#endif /* STATUS_LEDS_H_ */
diff --git a/firmware/controllers/algo/main_trigger_callback.h b/firmware/controllers/algo/main_trigger_callback.h
new file mode 100644
index 0000000000..1f21d89996
--- /dev/null
+++ b/firmware/controllers/algo/main_trigger_callback.h
@@ -0,0 +1,54 @@
+/**
+ * @file main_trigger_callback.h
+ * @brief Main logic header
+ *
+ *
+ * @date Feb 9, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef MAIN_LOOP_H_
+#define MAIN_LOOP_H_
+
+#include "main.h"
+
+#define MAX_INJECTOR_COUNT 12
+#define MAX_IGNITER_COUNT 4
+
+#ifdef __cplusplus
+#include "engine_configuration.h"
+#include "ec2.h"
+#include "event_registry.h"
+#include "engine.h"
+
+class MainTriggerCallback {
+public:
+// MainTriggerCallback();
+ void init(Engine *engine, engine_configuration2_s *engineConfiguration2);
+
+ Engine *engine;
+ engine_configuration_s *engineConfiguration;
+ engine_configuration2_s *engineConfiguration2;
+
+};
+void initMainEventListener(Engine *engine, engine_configuration2_s *engineConfiguration2);
+void onTriggerEvent(trigger_event_e ckpSignalType, int eventIndex, MainTriggerCallback *mainTriggerCallback);
+#endif
+
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+
+
+void showMainHistogram(void);
+void onEveryMillisecondTimerSignal(void);
+int isIgnitionTimingError(void);
+
+float getFuel(int rpm, float key);
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif /* MAIN_LOOP_H_ */
diff --git a/firmware/controllers/algo/malfunction_central.c b/firmware/controllers/algo/malfunction_central.c
new file mode 100644
index 0000000000..2a4be99162
--- /dev/null
+++ b/firmware/controllers/algo/malfunction_central.c
@@ -0,0 +1,60 @@
+/**
+ * @file malfunction_central.c
+ * @brief This data structure holds current malfunction codes
+ *
+ * @date Dec 20, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "malfunction_central.h"
+
+static error_codes_set_s error_codes_set;
+
+/**
+ * @return -1 if code not found
+ */
+static int find_position(obd_code_e e_code) // Search if code is present
+{
+ // cycle for searching element equal seaching code
+ for (int t = 0; t < error_codes_set.count; t++)
+ if (error_codes_set.error_codes[t] == e_code)
+ return t; // we found position where this code is present
+ return -1; // -1 if code not found
+}
+
+void initMalfunctionCentral(void) {
+ error_codes_set.count = 0;
+}
+
+void addError(obd_code_e errorCode) {
+ if (error_codes_set.count < MAX_ERROR_CODES_COUNT && find_position(errorCode) == -1) {
+ error_codes_set.error_codes[error_codes_set.count] = errorCode;
+ error_codes_set.count++;
+ }
+}
+
+void removeError(obd_code_e errorCode) {
+ int pos = find_position(errorCode);
+ if (pos >= 0) {
+ for (int t = pos; t < error_codes_set.count; t++) // shift all right elements to one pos left
+ error_codes_set.error_codes[t] = error_codes_set.error_codes[t + 1];
+ error_codes_set.error_codes[--error_codes_set.count] = (obd_code_e)0; // place 0
+ }
+}
+
+void setError(int flag, obd_code_e errorCode) {
+ if (flag)
+ addError(errorCode);
+ else
+ removeError(errorCode);
+}
+
+void getErrorCodes(error_codes_set_s * copy) {
+ copy->count = error_codes_set.count;
+ for (int i = 0; i < copy->count; i++)
+ copy->error_codes[i] = error_codes_set.error_codes[i];
+}
+
+bool hasErrorCodes(void) {
+ return error_codes_set.count > 0;
+}
diff --git a/firmware/controllers/algo/malfunction_central.h b/firmware/controllers/algo/malfunction_central.h
new file mode 100644
index 0000000000..2190bf9b12
--- /dev/null
+++ b/firmware/controllers/algo/malfunction_central.h
@@ -0,0 +1,57 @@
+/**
+ * @file malfunction_central.h
+ * @brief This data structure holds current malfunction codes
+ *
+ * @date Dec 20, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef MALFUNCTION_CENTRAL_H_
+#define MALFUNCTION_CENTRAL_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+
+
+#include "main.h"
+#include "obd_error_codes.h"
+
+#define MAX_ERROR_CODES_COUNT 10
+
+typedef struct {
+ int count;
+ obd_code_e error_codes[MAX_ERROR_CODES_COUNT];
+} error_codes_set_s;
+
+void initMalfunctionCentral(void);
+
+/**
+ * @brief Adds an error code into the set of current errors.
+ * The error code is placed into the fixed-size data structure if it fits into it.
+ * The error code stays in the data structure till it is removed by 'clearError'
+ *
+ */
+void addError(obd_code_e errorCode);
+/**
+ * @brief Removed the error code from the set of current errors.
+ *
+ */
+void removeError(obd_code_e errorCode);
+
+void setError(int flag, obd_code_e errorCode);
+
+/**
+ * @brief Copies the current set of errors into the specified buffer
+ */
+void getErrorCodes(error_codes_set_s * buffer);
+
+bool hasErrorCodes(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* MALFUNCTION_CENTRAL_H_ */
diff --git a/firmware/controllers/algo/map_adjuster.c b/firmware/controllers/algo/map_adjuster.c
new file mode 100644
index 0000000000..293fab0a49
--- /dev/null
+++ b/firmware/controllers/algo/map_adjuster.c
@@ -0,0 +1,88 @@
+/*
+ * map_adjuster.c
+ *
+ * @date Jul 23, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ */
+
+#include
+#include "map_adjuster.h"
+#include "avg_values.h"
+
+#define TARGET_MIN_AFR 11
+#define TARGET_MAX_AFR 16
+
+#define HOW_MANY_MEASURMENTS_ARE_NEEDED 5
+#define MULT_STEP_DOWN 0.92
+
+#if 0
+
+static AvgTable adjustments;
+
+static AvgTable afrs;
+
+void initMapAdjuster(void) {
+ avgReset(&afrs);
+
+ avgFill(&adjustments, 1, 1);
+}
+
+float maxF(float i1, float i2) {
+ return i1 > i2 ? i1 : i2;
+}
+
+static int adjustCell(int i, int j, void (*callback)(int, float, float)) {
+ int count = afrs.counts[i][j];
+ if (count < HOW_MANY_MEASURMENTS_ARE_NEEDED)
+ return 0;
+
+ float value = avgGetValueByIndexes(&afrs, i, j);
+ afrs.counts[i][j] = 0;
+ afrs.values[i][j] = 0;
+
+ if (value < TARGET_MIN_AFR) {
+ float currentMult = adjustments.values[i][j];
+// printf("adj %d %d. cur=%f\r\n", i, j, currentMult);
+ float newValue = maxF(0.1, MULT_STEP_DOWN * currentMult);
+ adjustments.values[i][j] = newValue;
+// printf("adj %d %d. new=%f\r\n", i, j, adjustments.values[i][j]);
+ if (callback != NULL)
+ callback(MAX_RPM * i / AVG_TAB_SIZE, 1.0 * MAX_KEY * j / AVG_TAB_SIZE, newValue);
+ return 1;
+ }
+ return 0;
+}
+
+int runMapAdjustments(void (*callback)(int, float, float)) {
+ int total = 0;
+ for (int i = 0; i < AVG_TAB_SIZE; i++) {
+ for (int j = 0; j < AVG_TAB_SIZE; j++) {
+ total += adjustCell(i, j, callback);
+ }
+ }
+ return total;
+}
+
+void addAfr(int rpm, float key, float afr) {
+ avgAddValue(&afrs, rpm, key, afr);
+}
+
+float getMultiplier(int rpm, float key) {
+ return avgGetValue(&adjustments, rpm, key);
+}
+
+#endif /* 0 */
diff --git a/firmware/controllers/algo/map_adjuster.h b/firmware/controllers/algo/map_adjuster.h
new file mode 100644
index 0000000000..058e5bb91c
--- /dev/null
+++ b/firmware/controllers/algo/map_adjuster.h
@@ -0,0 +1,19 @@
+/**
+ * map_adjuster.h
+ *
+ * Auto-tuning core algorithm
+ *
+ * @date Jul 23, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef MAP_ADJUSTER_H_
+#define MAP_ADJUSTER_H_
+
+void initMapAdjuster(void);
+int runMapAdjustments(void (*callback)(int, float, float));
+
+void addAfr(int rpm, float key, float afr);
+float getMultiplier(int rpm, float key);
+
+#endif /* MAP_ADJUSTER_H_ */
diff --git a/firmware/controllers/algo/nmea.c b/firmware/controllers/algo/nmea.c
new file mode 100644
index 0000000000..b20f4b83b7
--- /dev/null
+++ b/firmware/controllers/algo/nmea.c
@@ -0,0 +1,318 @@
+/**
+ * @date Dec 20, 2013
+ *
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ * @author Kot_dnz
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ */
+#include
+#include
+#include "main.h"
+#include "nmea.h"
+
+long hex2int(char *a, int len) {
+ int i;
+ long val = 0;
+
+ for (i = 0; i < len; i++)
+ if (a[i] <= 57)
+ val += (a[i] - 48) * (1 << (4 * (len - 1 - i))); // it's number
+ else
+ val += (a[i] - 87) * (1 << (4 * (len - 1 - i))); // it's a-f -> work only with low case hex
+ return val;
+}
+
+int str2int(char *a, int len) {
+ int i = 0, k = 0;
+ while (i GPS_MAX_STRING)
+ sLen = GPS_MAX_STRING;
+
+ while (a[i] != 44 && i < sLen) { // while not comma or end
+ dStr[i] = a[i];
+ i++;
+ }
+ dStr[i] = '\0';
+ return i;
+}
+
+/*
+GxGGA - name code
+Parameter Value Unit Description
+UTC hhmmss.sss Universal time coordinated
+Lat ddmm.mmmm Latitude
+Northing Indicator N=North, S=South
+Lon dddmm.mmmm Longitude
+Easting Indicator E=East, W=West
+Status 0 0=Invalid, 1=2D/3D, 2=DGPS, 6=Dead Reckoning
+SVs Used 00 Number of SVs used for Navigation
+HDOP 99.99 Horizontal Dilution of Precision
+Alt (MSL) m Altitude (above means sea level)
+Unit M=Meters
+Geoid Sep. m Geoid Separation = Alt(HAE) - Alt(MSL)
+Unit M=Meters
+Age of DGPS Corr s Age of Differential Corrections
+DGPS Ref Station ID of DGPS Reference Station
+*/
+void nmea_parse_gpgga(char *nmea, loc_t *loc) {
+ char *p = nmea;
+ char dStr[GPS_MAX_STRING];
+
+ p = strchr(p, ',') + 1; //skip time - we read date&time if Valid in GxRMC
+
+ p = strchr(p, ',') + 1; // in p string started with searching address
+ str_till_comma(p, dStr); // str to float till comma saved modified string
+ if(strlen(p) == 0) return; // if no data in field - empty data - we return
+
+ loc->latitude = atoff(dStr); // fulfil data
+
+ p = strchr(p, ',') + 1; // see above
+ switch (p[0]) {
+ case 'N':
+ loc->lat = 'N';
+ break;
+ case 'S':
+ loc->lat = 'S';
+ break;
+ case ',':
+ loc->lat = '\0';
+ break;
+ }
+
+ p = strchr(p, ',') + 1;
+ str_till_comma(p, dStr); // str to float till comma saved modified string
+ loc->longitude = atoff(dStr);
+
+ p = strchr(p, ',') + 1;
+ switch (p[0]) {
+ case 'W':
+ loc->lon = 'W';
+ break;
+ case 'E':
+ loc->lon = 'E';
+ break;
+ case ',':
+ loc->lon = '\0';
+ break;
+ }
+
+ p = strchr(p, ',') + 1;
+ str_till_comma(p, dStr); // str to float till comma saved modified string
+ loc->quality = atoi(dStr);
+
+ p = strchr(p, ',') + 1;
+ str_till_comma(p, dStr); // str to float till comma saved modified string
+ loc->satellites = atoi(dStr);
+
+ p = strchr(p, ',') + 1;
+
+ p = strchr(p, ',') + 1;
+ str_till_comma(p, dStr); // str to float till comma saved modified string
+ loc->altitude = atoff(dStr);
+}
+
+/*
+GxRMC - nmea code
+Parameter Value Unit Description
+UTC hhmmss.sss Universal time coordinated
+Status V A=Valid, V=Invalid
+Lat ddmm.mmmm Latitude
+Northing Indicator N=North, S=South
+Lon dddmm.mmmm Longitude
+Easting Indicator E=East, W=West
+SOG nots Speed Over Ground
+COG (true) ° Course Over Ground (true)
+Date ddmmyy Universal time coordinated
+Magnetic Variation ° Magnetic Variation
+Magnetic Variation E=East,W=West
+Mode Indicator N A=Autonomous, D=Differential, E=Dead Reckoning, N=None
+Navigational Status S=Safe C=Caution U=Unsafe V=Not valid
+
+*/
+void nmea_parse_gprmc(char *nmea, loc_t *loc) {
+ char *p = nmea;
+ char dStr[GPS_MAX_STRING];
+ struct tm timp;
+
+ p = strchr(p, ',') + 1; //read time
+ str_till_comma(p, dStr);
+ if(strlen(dStr) > 5){
+ timp.tm_hour = str2int(dStr,2);
+ timp.tm_min = str2int(dStr+2,2);
+ timp.tm_sec = str2int(dStr+4,2);
+ }
+
+ p = strchr(p, ',') + 1; //read field Valid status
+ str_till_comma(p, dStr);
+
+ if(dStr[0] == 'V') { // if field is invalid
+ loc->quality = 0;
+ return;
+ }
+
+ loc->quality = 4; // this is declaration that last receive field VALID
+
+ p = strchr(p, ',') + 1; // latitude
+ str_till_comma(p, dStr); // str to float till comma saved modified string
+ loc->latitude = atoff(dStr);
+
+ p = strchr(p, ',') + 1;
+ switch (p[0]) {
+ case 'N':
+ loc->lat = 'N';
+ break;
+ case 'S':
+ loc->lat = 'S';
+ break;
+ case ',':
+ loc->lat = '\0';
+ break;
+ }
+
+ p = strchr(p, ',') + 1; // longitude
+ str_till_comma(p, dStr); // str to float till comma saved modified string
+ loc->longitude = atoff(dStr);
+
+ p = strchr(p, ',') + 1;
+ switch (p[0]) {
+ case 'W':
+ loc->lon = 'W';
+ break;
+ case 'E':
+ loc->lon = 'E';
+ break;
+ case ',':
+ loc->lon = '\0';
+ break;
+ }
+
+ p = strchr(p, ',') + 1;
+ str_till_comma(p, dStr); // str to float till comma saved modified string
+ loc->speed = atoff(dStr);
+
+ p = strchr(p, ',') + 1;
+ str_till_comma(p, dStr); // str to float till comma saved modified string
+ loc->course = atoff(dStr);
+
+ p = strchr(p, ',') + 1; //read date
+ str_till_comma(p, dStr);
+ if(strlen(dStr) > 5){
+ timp.tm_mday = str2int(dStr,2);
+ timp.tm_mon = str2int(dStr+2,2);
+ timp.tm_year = str2int(dStr+4,2)+100; // we receive -200, but standard wait -1900 = add correction
+ }
+
+ if( timp.tm_year > 0 ) { // check if date field is valid
+ memcpy(&loc->GPStm, &timp, sizeof(timp));
+ }
+}
+
+/**
+ * Get the message type (GPGGA, GPRMC, etc..)
+ *
+ * This function filters out also wrong packages (invalid checksum)
+ *
+ * @param message The NMEA message
+ * @return The type of message if it is valid
+ */
+nmea_message_type nmea_get_message_type(const char *message) {
+ int checksum = nmea_valid_checksum(message);
+ if (checksum != _EMPTY) {
+ return checksum;
+ }
+
+ if (strstr(message, NMEA_GPGGA_STR) != NULL) {
+ return NMEA_GPGGA;
+ }
+
+ if (strstr(message, NMEA_GPRMC_STR) != NULL) {
+ return NMEA_GPRMC;
+ }
+
+ return NMEA_UNKNOWN;
+}
+
+int nmea_valid_checksum(const char *message) {
+ char p;
+ int sum = 0;
+ char *starPtr = strrchr(message, '*');
+ if (starPtr == NULL)
+ return NMEA_CHECKSUM_ERR;
+ char *int_message = starPtr + 1;
+ long checksum = hex2int(int_message, 2);
+
+ ++message;
+ while ((p = *message++) != '*') {
+ sum ^= p;
+ }
+
+ if (sum != checksum) {
+ return NMEA_CHECKSUM_ERR;
+ }
+ return _EMPTY;
+}
+
+// Compute the GPS location using decimal scale
+void gps_location(loc_t *coord, char *buffer) {
+
+ coord->type = nmea_get_message_type(buffer);
+
+ switch (coord->type) {
+ case NMEA_GPGGA:
+ nmea_parse_gpgga(buffer, coord);
+ gps_convert_deg_to_dec(&(coord->latitude), coord->lat, &(coord->longitude), coord->lon);
+ break;
+ case NMEA_GPRMC:
+ nmea_parse_gprmc(buffer, coord);
+ break;
+ case NMEA_UNKNOWN:
+ // unknown message type
+ break;
+ }
+
+}
diff --git a/firmware/controllers/algo/nmea.h b/firmware/controllers/algo/nmea.h
new file mode 100644
index 0000000000..ef4931be4d
--- /dev/null
+++ b/firmware/controllers/algo/nmea.h
@@ -0,0 +1,57 @@
+/**
+ *
+ * https://github.com/wdalmut/libgps/tree/develop/src
+ *
+ */
+#ifndef _NMEA_H_
+#define _NMEA_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+
+#define GPS_MAX_STRING 256
+
+typedef enum {
+ NMEA_UNKNOWN = 0x00,
+ NMEA_GPRMC = 0x01,
+ NMEA_GPGGA = 0x02
+} nmea_message_type;
+
+#define _EMPTY 0x00
+#define NMEA_GPRMC_STR "$GPRMC"
+#define NMEA_GPGGA_STR "$GPGGA"
+#define _COMPLETED 0x03
+
+#define NMEA_CHECKSUM_ERR 0x80
+#define NMEA_MESSAGE_ERR 0xC0
+
+struct GPSlocation {
+ float latitude;
+ float longitude;
+ float speed;
+ float altitude;
+ float course;
+ struct tm GPStm;
+ nmea_message_type type;
+ int quality;
+ int satellites;
+ char lat; // Northing Indicator N=North, S=South
+ char lon; // Easting Indicator E=East, W=West
+};
+typedef struct GPSlocation loc_t;
+
+nmea_message_type nmea_get_message_type(const char *);
+int nmea_valid_checksum(const char *);
+void nmea_parse_gpgga(char *, loc_t *);
+void nmea_parse_gprmc(char *, loc_t *);
+void gps_location(loc_t *, char *);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif
diff --git a/firmware/controllers/algo/obd_error_codes.h b/firmware/controllers/algo/obd_error_codes.h
new file mode 100644
index 0000000000..554a2ab520
--- /dev/null
+++ b/firmware/controllers/algo/obd_error_codes.h
@@ -0,0 +1,1676 @@
+/**
+ * @file obd_error_codes.h
+ * @brief Standart OBD-II error codes
+ *
+ * More info at http://www.obd-codes.com/faq/obd2-codes-explained.php
+ *
+ * @date Dec 20, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef OBD_ERROR_CODES_H_
+#define OBD_ERROR_CODES_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+#include "rusefi_enums.h"
+
+typedef enum {
+//P0001 Fuel Volume Regulator Control Circuit/Open
+//P0002 Fuel Volume Regulator Control Circuit Range/Performance
+//P0003 Fuel Volume Regulator Control Circuit Low
+//P0004 Fuel Volume Regulator Control Circuit High
+//P0005 Fuel Shutoff Valve "A" Control Circuit/Open
+//P0006 Fuel Shutoff Valve "A" Control Circuit Low
+//P0007 Fuel Shutoff Valve "A" Control Circuit High
+//P0008 Engine Positions System Performance Bank 1
+//P0009 Engine Position System Performance Bank 2
+//P0010 "A" Camshaft Position Actuator Circuit (Bank 1)
+ //P0001 Fuel Volume Regulator Control Circuit/Open
+ //P0002 Fuel Volume Regulator Control Circuit Range/Performance
+ //P0003 Fuel Volume Regulator Control Circuit Low
+ //P0004 Fuel Volume Regulator Control Circuit High
+ //P0005 Fuel Shutoff Valve "A" Control Circuit/Open
+ //P0006 Fuel Shutoff Valve "A" Control Circuit Low
+ //P0007 Fuel Shutoff Valve "A" Control Circuit High
+ //P0008 Engine Positions System Performance Bank 1
+ //P0009 Engine Position System Performance Bank 2
+ //P0010 "A" Camshaft Position Actuator Circuit (Bank 1)
+ //P0011 "A" Camshaft Position - Timing Over-Advanced or System Performance (Bank 1)
+ //P0012 "A" Camshaft Position - Timing Over-Retarded (Bank 1)
+ //P0013 "B" Camshaft Position - Actuator Circuit (Bank 1)
+ //P0014 "B" Camshaft Position - Timing Over-Advanced or System Performance (Bank 1)
+ //P0015 "B" Camshaft Position -Timing Over-Retarded (Bank 1)
+ //P0016 Crankshaft Position - Camshaft Position Correlation (Bank 1 Sensor A)
+ //P0017 Crankshaft Position - Camshaft Position Correlation (Bank 1 Sensor B)
+ //P0018 Crankshaft Position - Camshaft Position Correlation (Bank 2 Sensor A)
+ //P0019 Crankshaft Position - Camshaft Position Correlation (Bank 2 Sensor B)
+ //P0020 "A" Camshaft Position Actuator Circuit (Bank 2)
+ //P0021 "A" Camshaft Position - Timing Over-Advanced or System Performance (Bank 2)
+ //P0022 "A" Camshaft Position - Timing Over-Retarded (Bank 2)
+ //P0023 "B" Camshaft Position - Actuator Circuit (Bank 2)
+ //P0024 "B" Camshaft Position - Timing Over-Advanced or System Performance (Bank 2)
+ //P0025 "B" Camshaft Position - Timing Over-Retarded (Bank 2)
+ //P0026 Intake Valve Control Solenoid Circuit Range/Performance Bank 1
+ //P0027 Exhaust Valve Control solenoid Circuit Range/Performance Bank 1
+ //P0028 Intake valve Control Solenoid Circuit Range/Performance Bank 2
+ //P0029 Exhaust Valve Control Solenoid Circuit Range/Performance Bank 2
+ //P0030 HO2S Heater Control Circuit (Bank 1 Sensor 1)
+ //P0031 HO2S Heater Control Circuit Low (Bank 1 Sensor 1)
+ //P0032 HO2S Heater Control Circuit High (Bank 1 Sensor 1)
+ //P0033 Turbo Charger Bypass Valve Control Circuit
+ //P0034 Turbo Charger Bypass Valve Control Circuit Low
+ //P0035 Turbo Charger Bypass Valve Control Circuit High
+ //P0036 HO2S Heater Control Circuit (Bank 1 Sensor 2)
+ //P0037 HO2S Heater Control Circuit Low (Bank 1 Sensor 2)
+ //P0038 HO2S Heater Control Circuit High (Bank 1 Sensor 2)
+ //P0039 Turbo/Super Charger Bypass Valve Control Circuit Range/Performance
+ //P0040 Upstream Oxygen Sensors Swapped From Bank To Bank
+ //P0041 Downstream Oxygen Sensors Swapped From Bank To Bank
+ //P0042 HO2S Heater Control Circuit (Bank 1 Sensor 3)
+ //P0043 HO2S Heater Control Circuit Low (Bank 1 Sensor 3)
+ //P0044 HO2S Heater Control Circuit High (Bank 1 Sensor 3)
+ //P0050 HO2S Heater Control Circuit (Bank 2 Sensor 1)
+ //P0051 HO2S Heater Control Circuit Low (Bank 2 Sensor 1)
+ //P0052 HO2S Heater Control Circuit High (Bank 2 Sensor 1)
+ //P0053 HO2S Heater Resistance (Bank 1, Sensor 1)
+ //P0054 HO2S Heater Resistance (Bank 1, Sensor 2)
+ //P0055 HO2S Heater Resistance (Bank 1, Sensor 3)
+ //P0056 HO2S Heater Control Circuit (Bank 2 Sensor 2)
+ //P0057 HO2S Heater Control Circuit Low (Bank 2 Sensor 2)
+ //P0058 HO2S Heater Control Circuit High (Bank 2 Sensor 2)
+ //P0059 HO2S Heater Resistance (Bank 2, Sensor 1)
+ //P0060 HO2S Heater Resistance (Bank 2, Sensor 2)
+ //P0061 HO2S Heater Resistance (Bank 2, Sensor 3)
+ //P0062 HO2S Heater Control Circuit (Bank 2 Sensor 3)
+ //P0063 HO2S Heater Control Circuit Low (Bank 2 Sensor 3)
+ //P0064 HO2S Heater Control Circuit High (Bank 2 Sensor 3)
+ //P0065 Air Assisted Injector Control Range/Performance
+ //P0066 Air Assisted Injector Control Circuit or Circuit Low
+ //P0067 Air Assisted Injector Control Circuit High
+ //P0068 MAP/MAF - Throttle Position Correlation
+ //P0069 Manifold Absolute Pressure - Barometric Pressure Correlation
+ //P0070 Ambient Air Temperature Sensor Circuit
+ //P0071 Ambient Air Temperature Sensor Range/Performance
+ //P0072 Ambient Air Temperature Sensor Circuit Low Input
+ //P0073 Ambient Air Temperature Sensor Circuit High Input
+ //P0074 Ambient Air Temperature Sensor Circuit Intermittent
+ //P0075 Intake Valve Control Solenoid Circuit (Bank 1)
+ //P0076 Intake Valve Control Solenoid Circuit Low (Bank 1)
+ //P0077 Intake Valve Control Solenoid Circuit High (Bank 1)
+ //P0078 Exhaust Valve Control Solenoid Circuit (Bank 1)
+ //P0079 Exhaust Valve Control Solenoid Circuit Low (Bank 1)
+ //P0080 Exhaust Valve Control Solenoid Circuit High (Bank 1)
+ //P0081 Intake valve Control Solenoid Circuit (Bank 2)
+ //P0082 Intake Valve Control Solenoid Circuit Low (Bank 2)
+ //P0083 Intake Valve Control Solenoid Circuit High (Bank 2)
+ //P0084 Exhaust Valve Control Solenoid Circuit (Bank 2)
+ //P0085 Exhaust Valve Control Solenoid Circuit Low (Bank 2)
+ //P0086 Exhaust Valve Control Solenoid Circuit High (Bank 2)
+ //P0087 Fuel Rail/System Pressure - Too Low
+ //P0088 Fuel Rail/System Pressure - Too High
+ //P0089 Fuel Pressure Regulator 1 Performance
+ //P0090 Fuel Pressure Regulator 1 Control Circuit
+ //P0091 Fuel Pressure Regulator 1 Control Circuit Low
+ //P0092 Fuel Pressure Regulator 1 Control Circuit High
+ //P0093 Fuel System Leak Detected - Large Leak
+ //P0094 Fuel System Leak Detected - Small Leak
+ //P0095 Intake Air Temperature Sensor 2 Circuit
+ //P0096 Intake Air Temperature Sensor 2 Circuit Range/Performance
+ //P0097 Intake Air Temperature Sensor 2 Circuit Low
+ //P0098 Intake Air Temperature Sensor 2 Circuit High
+ //P0099 Intake Air Temperature Sensor 2 Circuit Intermittent/Erratic
+ OBD_Mass_or_Volume_Air_Flow_Circuit_Malfunction = 100,
+ //P0101 Mass or Volume Air Flow Circuit Range/Performance Problem
+ //P0102 Mass or Volume Air Flow Circuit Low Input
+ //P0103 Mass or Volume Air Flow Circuit High Input
+ //P0104 Mass or Volume Air Flow Circuit Intermittent
+ //P0105 Manifold Absolute Pressure/Barometric Pressure Circuit Malfunction
+ //P0106 Manifold Absolute Pressure/Barometric Pressure Circuit Range/Performance Problem
+ //P0107 Manifold Absolute Pressure/Barometric Pressure Circuit Low Input
+ //P0108 Manifold Absolute Pressure/Barometric Pressure Circuit High Input
+ //P0109 Manifold Absolute Pressure/Barometric Pressure Circuit Intermittent
+ OBD_Intake_Air_Temperature_Circuit_Malfunction = 110,
+ //P0111 Intake Air Temperature Circuit Range/Performance Problem
+ //P0112 Intake Air Temperature Circuit Low Input
+ //P0113 Intake Air Temperature Circuit High Input
+ //P0114 Intake Air Temperature Circuit Intermittent
+ OBD_Engine_Coolant_Temperature_Circuit_Malfunction = 115,
+ //P0116 Engine Coolant Temperature Circuit Range/Performance Problem
+ //P0117 Engine Coolant Temperature Circuit Low Input
+ //P0118 Engine Coolant Temperature Circuit High Input
+ //P0119 Engine Coolant Temperature Circuit Intermittent
+ //P0120 Throttle Position Sensor/Switch A Circuit Malfunction
+ //P0121 Throttle Position Sensor/Switch A Circuit Range/Performance Problem
+ //P0122 Throttle Position Sensor/Switch A Circuit Low Input
+ //P0123 Throttle Position Sensor/Switch A Circuit High Input
+ //P0124 Throttle Position Sensor/Switch A Circuit Intermittent
+ //P0125 Insufficient Coolant Temperature for Closed Loop Fuel Control
+ //P0126 Insufficient Coolant Temperature for Stable Operation
+ //P0128 Coolant Thermostat (Coolant Temperature Below Thermostat Regulating Temperature)
+ //P0130 02 Sensor Circuit Malfunction (Bank I Sensor 1)
+ //P0131 02 Sensor Circuit Low Voltage (Bank I Sensor I)
+ //P0132 02 Sensor Circuit High Voltage (Bank I Sensor 1)
+ //P0133 02 Sensor Circuit Slow Response (Bank 1 Sensor 1)
+ //P0134 02 Sensor Circuit No Activity Detected (Bank I Sensor 1)
+ //P0135 02 Sensor Heater Circuit Malfunction (Bank 1 Sensor 1)
+ //P0136 02 Sensor Circuit Malfunction (Bank I Sensor 2)
+ //P0137 02 Sensor Circuit Low Voltage (Bank I Sensor 2)
+ //P0138 02 Sensor Circuit High Voltage (Bank I Sensor 2)
+ //P0139 02 Sensor Circuit Slow Response (Bank 1 Sensor 2)
+ //P0140 02 Sensor Circuit No Activity Detected (Bank 1 Sensor 2)
+ //P0141 02 Sensor Heater Circuit Malfunction (Bank 1 Sensor 2)
+ //P0142 02 Sensor Circuit Malfunction (Bank I Sensor 3)
+ //P0143 02 Sensor Circuit Low Voltage (Bank I Sensor 3)
+ //P0144 02 Sensor Circuit High Voltage (Bank I Sensor 3)
+ //P0145 02 Sensor Circuit Slow Response (Bank 1 Sensor 3)
+ //P0146 02 Sensor Circuit No Activity Detected (Bank I Sensor 3)
+ //P0147 02 Sensor Heater Circuit Malfunction (Bank I Sensor 3)
+ //P0150 02 Sensor Circuit Malfunction (Bank 2 Sensor I)
+ //P0151 02 Sensor Circuit Low Voltage (Bank 2 Sensor I)
+ //P0152 02 Sensor Circuit High Voltage (Bank 2 Sensor 1)
+ //P0153 02 Sensor Circuit Slow Response (Bank 2 Sensor 1)
+ //P0154 02 Sensor Circuit No Activity Detected (Bank 2 Sensor 1)
+ //P0155 02 Sensor Heater Circuit Malfunction (Bank 2 Sensor 1)
+ //P0156 02 Sensor Circuit Malfunction (Bank 2 Sensor 2)
+ //P0157 02 Sensor Circuit Low Voltage (Bank 2 Sensor 2)
+ //P0158 02 Sensor Circuit High Voltage (Bank 2 Sensor 2)
+ //P0159 02 Sensor Circuit Slow Response (Bank 2 Sensor 2)
+ //P0160 02 Sensor Circuit No Activity Detected (Bank 2 Sensor 2)
+ //P0161 02 Sensor Heater Circuit Malfunction (Bank 2 Sensor 2)
+ //P0162 02 Sensor Circuit Malfunction (Bank 2 Sensor 3)
+ //P0163 02 Sensor Circuit Low Voltage (Bank 2 Sensor 3)
+ //P0164 02 Sensor Circuit High Voltage (Bank 2 Sensor 3)
+ //P0165 02 Sensor Circuit Slow Response (Bank 2 Sensor 3)
+ //P0166 02 Sensor Circuit No Activity Detected (Bank 2 Sensor 3)
+ //P0167 02 Sensor Heater Circuit Malfunction (Bank 2 Sensor 3)
+ //P0170 Fuel Trim Malfunction (Bank 1)
+ //P0171 System too Lean (Bank 1)
+ //P0172 System too Rich (Bank 1)
+ //P0173 Fuel Trim Malfunction (Bank 2)
+ //P0174 System too Lean (Bank 2)
+ //P0175 System too Rich (Bank 2)
+ //P0176 Fuel Composition Sensor Circuit Malfunction
+ //P0177 Fuel Composition Sensor Circuit Range/Performance
+ //P0178 Fuel Composition Sensor Circuit Low Input
+ //P0179 Fuel Composition Sensor Circuit High Input
+ //P0180 Fuel Temperature Sensor A Circuit Malfunction
+ //P0181 Fuel Temperature Sensor A Circuit Range/Performance
+ //P0182 Fuel Temperature Sensor A Circuit Low Input
+ //P0183 Fuel Temperature Sensor A Circuit High Input
+ //P0184 Fuel Temperature Sensor A Circuit Intermittent
+ //P0185 Fuel Temperature Sensor B Circuit Malfunction
+ //P0186 Fuel Temperature Sensor B Circuit Range/Performance
+ //P0187 Fuel Temperature Sensor B Circuit Low Input
+ //P0188 Fuel Temperature Sensor B Circuit High Input
+ //P0189 Fuel Temperature Sensor B Circuit Intermittent
+ //P0190 Fuel Rail Pressure Sensor Circuit Malfunction
+ //P0191 Fuel Rail Pressure Sensor Circuit Range/Performance
+ //P0192 Fuel Rail Pressure Sensor Circuit Low Input
+ //P0193 Fuel Rail Pressure Sensor Circuit High Input
+ //P0194 Fuel Rail Pressure Sensor Circuit Intermittent
+ //P0195 Engine Oil Temperature Sensor Malfunction
+ //P0196 Engine Oil Temperature Sensor Range/Performance
+ //P0197 Engine Oil Temperature Sensor Low
+ //P0198 Engine Oil Temperature Sensor High
+ //P0199 Engine Oil Temperature Sensor Intermittent
+ //DTC Codes - P0200-P0299 - Fuel and Air Metering (Injector Circuit)
+ //P0200 Injector Circuit Malfunction
+ //P0201 Injector Circuit Malfunction - Cylinder 1
+ //P0202 Injector Circuit Malfunction - Cylinder 2
+ //P0203 Injector Circuit Malfunction - Cylinder 3
+ //P0204 Injector Circuit Malfunction - Cylinder 4
+ //P0205 Injector Circuit Malfunction - Cylinder 5
+ //P0206 Injector Circuit Malfunction - Cylinder 6
+ //P0207 Injector Circuit Malfunction - Cylinder 7
+ //P0208 Injector Circuit Malfunction - Cylinder 8
+ //P0209 Injector Circuit Malfunction - Cylinder 9
+ //P0210 Injector Circuit Malfunction - Cylinder 10
+ //P0211 Injector Circuit Malfunction - Cylinder 11
+ //P0212 Injector Circuit Malfunction - Cylinder 12
+ //P0213 Cold Start Injector 1 Malfunction
+ //P0214 Cold Start Injector 2 Malfunction
+ //P0215 Engine Shutoff Solenoid Malfunction
+ //P0216 Injection Timing Control Circuit Malfunction
+ //P0217 Engine Overtemp Condition
+ //P0218 Transmission Over Temperature Condition
+ //P0219 Engine Overspeed Condition
+ //P0220 Throttle/Petal Position Sensor/Switch B Circuit Malfunction
+ //P0221 Throttle/Petal Position Sensor/Switch B Circuit Range/Performance Problem
+ //P0222 Throttle/Petal Position Sensor/Switch B Circuit Low Input
+ //P0223 Throttle/Petal Position Sensor/Switch B Circuit High Input
+ //P0224 Throttle/Petal Position Sensor/Switch B Circuit Intermittent
+ //P0225 Throttle/Petal Position Sensor/Switch C Circuit Malfunction
+ //P0226 Throttle/Petal Position Sensor/Switch C Circuit Range/Performance Problem
+ //P0227 Throttle/Petal Position Sensor/Switch C Circuit Low Input
+ //P0228 Throttle/Petal Position Sensor/Switch C Circuit High Input
+ //P0229 Throttle/Petal Position Sensor/Switch C Circuit Intermittent
+ //P0230 Fuel Pump Primary Circuit Malfunction
+ //P0231 Fuel Pump Secondary Circuit Low
+ //P0232 Fuel Pump Secondary Circuit High
+ //P0233 Fuel Pump Secondary Circuit Intermittent
+ //P0234 Engine Turbocharger/Supercharger Overboost Condition
+ //P0235 Turbocharger Boost Sensor A Circuit Malfunction
+ //P0236 Turbocharger Boost Sensor A Circuit Range/Performance
+ //P0237 Turbocharger Boost Sensor A Circuit Low
+ //P0238 Turbocharger Boost Sensor A Circuit High
+ //P0239 Turbocharger Boost Sensor B Malfunction
+ //P0240 Turbocharger Boost Sensor B Circuit Range/Performance
+ //P0241 Turbocharger Boost Sensor B Circuit Low
+ //P0242 Turbocharger Boost Sensor B Circuit High
+ //P0243 Turbocharger Wastegate Solenoid A Malfunction
+ //P0244 Turbocharger Wastegate Solenoid A Range/Performance
+ //P0245 Turbocharger Wastegate Solenoid A Low
+ //P0246 Turbocharger Wastegate Solenoid A High
+ //P0247 Turbocharger Wastegate Solenoid B Malfunction
+ //P0248 Turbocharger Wastegate Solenoid B Range/Performance
+ //P0249 Turbocharger Wastegate Solenoid B Low
+ //P0250 Turbocharger Wastegate Solenoid B High
+ //P0251 Injection Pump Fuel Metering Control "A" Malfunction (Cam/Rotor/Injector)
+ //P0252 Injection Pump Fuel Metering Control "A" Range/Performance (Cam/Rotor/Injector)
+ //P0253 Injection Pump Fuel Metering Control "A" Low (Cam/Rotor/Injector)
+ //P0254 Injection Pump Fuel Metering Control "A" High (Cam/Rotor/Injector)
+ //P0255 Injection Pump Fuel Metering Control "A" Intermittent (Cam/Rotor/Injector)
+ //P0256 Injection Pump Fuel Metering Control "B" Malfunction (Cam/Rotor/Injector)
+ //P0257 Injection Pump Fuel Metering Control "B" Range/Performance Injector)
+ //P0258 Injection Pump Fuel Metering Control "B" Low (Cam/R
+ //P0259 Injection Pump Fuel Metering Control "B" High (Cam/R
+ //P0260 Injection Pump Fuel Metering Control "B" Intermittent Injector)
+ //P0261 Cylinder I Injector Circuit Low
+ //P0262 Cylinder I Injector Circuit High
+ //P0263 Cylinder I Contribution/Balance Fault
+ //P0264 Cylinder 2 Injector Circuit Low
+ //P0265 Cylinder 2 Injector Circuit High
+ //P0266 Cylinder 2 Contribution/Balance Fault
+ //P0267 Cylinder 3 Injector Circuit Low
+ //P0268 Cylinder 3 Injector Circuit High
+ //P0269 Cylinder 3 Contribution/Balance Fault
+ //P0270 Cylinder 4 Injector Circuit Low
+ //P0271 Cylinder 4 Injector Circuit High
+ //P0272 Cylinder 4 Contribution/Balance Fault
+ //P0273 Cylinder 5 Injector Circuit Low
+ //P0274 Cylinder 5 Injector Circuit High
+ //P0275 Cylinder S Contribution/Balance Fault
+ //P0276 Cylinder 6 Injector Circuit Low
+ //P0277 Cylinder 6 Injector Circuit High
+ //P0278 Cylinder 6 Contribution/Balance Fault
+ //P0279 Cylinder 7 Injector Circuit Low
+ //P0280 Cylinder 7 Injector Circuit High
+ //P0281 Cylinder 7 Contribution/Balance Fault
+ //P0282 Cylinder 8 Injector Circuit Low
+ //P0283 Cylinder 8 Injector Circuit High
+ //P0284 Cylinder 8 Contribution/Balance Fault
+ //P0285 Cylinder 9 Injector Circuit Low
+ //P0286 Cylinder 9 Injector Circuit High
+ //P0287 Cylinder 9 Contribution/Balance Fault
+ //P0288 Cylinder 10 Injector Circuit Low
+ //P0289 Cylinder 10 Injector Circuit High
+ //P0290 Cylinder 10 Contribution/Balance Fault
+ //P0291 Cylinder 11 Injector Circuit Low
+ //P0292 Cylinder 11 Injector Circuit High
+ //P0293 Cylinder 11 Contribution/Balance Fault
+ //P0294 Cylinder 12 Injector Circuit Low
+ //P0295 Cylinder 12 Injector Circuit High
+ //P0296 Cylinder 12 Contribution/Range Fault
+ //P0297 Vehicle Overspeed Condition
+ //P0298 Engine Oil Over Temperature Condition
+ //P0299 Turbocharger/Supercharger "A" Underboost Condition
+ //P0300 Random/Multiple Cylinder Misfire Detected
+ //P0301 Cylinder 1 Misfire Detected
+ //P0302 Cylinder 2 Misfire Detected
+ //P0303 Cylinder 3 Misfire Detected
+ //P0304 Cylinder 4 Misfire Detected
+ //P0305 Cylinder 5 Misfire Detected
+ //P0306 Cylinder 6 Misfire Detected
+ //P0307 Cylinder 7 Misfire Detected
+ //P0308 Cylinder 8 Misfire Detected
+ //P0309 Cylinder 9 Misfire Detected
+ //P0310 Cylinder 10 Misfire Detected
+ //P0311 Cylinder 11 Misfire Detected
+ //P0312 Cylinder 12 Misfire Detected
+ //P0313 Misfire Detected with Low Fuel
+ //P0314 Single Cylinder Misfire (Cylinder not Specified)
+ //P0315 Crankshaft Position System Variation Not Learned
+ //P0316 Misfire Detected On Startup (First 1000 Revolutions)
+ //P0317 Rough Road Hardware Not Present
+ //P0318 Rough Road Sensor A Signal Circuit
+ //P0319 Rough Road Sensor B Signal Circuit
+ //P0320 Ignition/Distributor Engine Speed Input Circuit Malfunction
+ //P0321 Ignition/Distributor Engine Speed Input Circuit Range/Performance
+ //P0322 Ignition/Distributor Engine Speed Input Circuit No Signal
+ //P0323 Ignition/Distributor Engine Speed Input Circuit Intermittent
+ //P0324 Knock Control System Error
+ //P0325 Knock Sensor 1 Circuit Malfunction (Bank I or Single Sensor)
+ //P0326 Knock Sensor 1 Circuit Range/Performance (Bank 1 or Single Sensor)
+ //P0327 Knock Sensor 1 Circuit Low Input (Bank I or Single Sensor)
+ //P0328 Knock Sensor 1 Circuit High Input (Bank I or Single Sensor)
+ //P0329 Knock Sensor 1 Circuit Intermittent (Bank 1 or Single Sensor)
+ //P0330 Knock Sensor 2 Circuit Malfunction (Bank 2)
+ //P0331 Knock Sensor 2 Circuit Range/Performance (Bank 2)
+ //P0332 Knock Sensor 2 Circuit Low Input (Bank 2)
+ //P0333 Knock Sensor 2 Circuit High Input (Bank 2)
+ //P0334 Knock Sensor 2 Circuit Intermittent (Bank 2)
+ //P0335 Crankshaft Position Sensor A Circuit Malfunction
+ //P0336 Crankshaft Position Sensor A Circuit Range/Performance
+ //P0337 Crankshaft Position Sensor A Circuit Low Input
+ //P0338 Crankshaft Position Sensor A Circuit High Input
+ //P0339 Crankshaft Position Sensor A Circuit Intermittent
+ //P0340 Camshaft Position Sensor Circuit Malfunction
+ OBD_Camshaft_Position_Sensor_Circuit_Range_Performance = 341,
+ //P0342 Camshaft Position Sensor Circuit Low Input
+ //P0343 Camshaft Position Sensor Circuit High Input
+ //P0344 Camshaft Position Sensor Circuit Intermittent
+ //P0345 Camshaft Position Sensor A Circuit Malfunction (Bank 2)
+ //P0346 Camshaft Position Sensor A Circuit Range/Performance (Bank 2)
+ //P0347 Camshaft Position Sensor A Circuit Low Input (Bank 2)
+ //P0348 Camshaft Position Sensor A Circuit High Input (Bank 2)
+ //P0349 Camshaft Position Sensor A Circuit Intermittent (Bank 2)
+ //P0350 Ignition Coil Primary/Secondary Circuit Malfunction
+ //P0351 Ignition Coil A Primary/Secondary Circuit Malfunction
+ //P0352 Ignition Coil B Primary/Secondary Circuit Malfunction
+ //P0353 Ignition Coil C Primary/Secondary Circuit Malfunction
+ //P0354 Ignition Coil D Primary/Secondary Circuit Malfunction
+ //P0355 Ignition Coil E Primary/Secondary Circuit Malfunction
+ //P0356 Ignition Coil F Primary/Secondary Circuit Malfunction
+ //P0357 Ignition Coil G Primary/Secondary Circuit Malfunction
+ //P0358 Ignition Coil H Primary/Secondary Circuit Malfunction
+ //P0359 Ignition Coil I Primary/Secondary Circuit Malfunction
+ //P0360 Ignition Coil J Primary/Secondary Circuit Malfunction
+ //P0361 Ignition Coil K Primary/Secondary Circuit Malfunction
+ //P0362 Ignition Coil L Primary/Secondary Circuit Malfunction
+ //P0363 Misfire Detected - Fueling Disabled
+ //P0364 Reserved
+ //P0365 Camshaft Position Sensor "B" Circuit (Bank 1)
+ //P0366 Camshaft Position Sensor "B" Circuit Range/Performance (Bank 1)
+ //P0367 Camshaft Position Sensor "B" Circuit Low (Bank 1)
+ //P0368 Camshaft Position Sensor "B" Circuit High (Bank 1)
+ //P0369 Camshaft Position Sensor "B" Circuit Intermittent (Bank 1)
+ //P0370 Timing Reference High Resolution Signal A Malfunction
+ //P0371 Timing Reference High Resolution Signal A Too Many Pulses
+ //P0372 Timing Reference High Resolution Signal A Too Few Pulses
+ //P0373 Timing Reference High Resolution Signal A Intermittent/Erratic Pulses
+ //P0374 Timing Reference High Resolution Signal A No Pulses
+ //P0375 Timing Reference High Resolution Signal B Malfunction
+ //P0376 Timing Reference High Resolution Signal B Too Many Pulses
+ //P0377 Timing Reference High Resolution Signal B Too Few Pulses
+ //P0378 Timing Reference High Resolution Signal B Intermittent/Erratic Pulses
+ //P0379 Timing Reference High Resolution Signal B No Pulses
+ //P0380 Glow Plug/Heater Circuit "A" Malfunction
+ //P0381 Glow Plug/Heater Indicator Circuit Malfunction
+ //P0382 Exhaust Gas Recirculation Flow Malfunction
+ //P0383 Glow Plug Control Module Control Circuit Low
+ //P0384 Glow Plug Control Module Control Circuit High
+ //P0385 Crankshaft Position Sensor B Circuit Malfunction
+ //P0386 Crankshaft Position Sensor B Circuit Range/Performance
+ //P0387 Crankshaft Position Sensor B Circuit Low Input
+ //P0388 Crankshaft Position Sensor B Circuit High Input
+ //P0389 Crankshaft Position Sensor B Circuit Intermittent
+ //P0390 Camshaft Position Sensor "B" Circuit (Bank 2)
+ //P0391 Camshaft Position Sensor "B" Circuit Range/Performance (Bank 2)
+ //P0392 Camshaft Position Sensor "B" Circuit Low (Bank 2)
+ //P0393 Camshaft Position Sensor "B" Circuit High (Bank 2)
+ //P0394 Camshaft Position Sensor "B" Circuit Intermittent (Bank 2)
+ //DTC Codes - P0400-P0499 - Auxiliary Emissions Controls
+ //P0400 Exhaust Gas Recirculation Flow Malfunction
+ //P0401 Exhaust Gas Recirculation Flow Insufficient Detected
+ //P0402 Exhaust Gas Recirculation Flow Excessive Detected
+ //P0403 Exhaust Gas Recirculation Circuit Malfunction
+ //P0404 Exhaust Gas Recirculation Circuit Range/Performance
+ //P0405 Exhaust Gas Recirculation Sensor A Circuit Low
+ //P0406 Exhaust Gas Recirculation Sensor A Circuit High
+ //P0407 Exhaust Gas Recirculation Sensor B Circuit Low
+ //P0408 Exhaust Gas Recirculation Sensor B Circuit High
+ //P0409 Exhaust Gas Recirculation Sensor "A" Circuit
+ //P0410 Secondary Air Injection System Malfunction
+ //P0411 Secondary Air Injection System Incorrect Flow Detected
+ //P0412 Secondary Air Injection System Switching Valve A Circuit Malfunction
+ //P0413 Secondary Air Injection System Switching Valve A Circuit Open
+ //P0414 Secondary Air Injection System Switching Valve A Circuit Shorted
+ //P0415 Secondary Air Injection System Switching Valve B Circuit Malfunction
+ //P0416 Secondary Air Injection System Switching Valve B Circuit Open
+ //P0417 Secondary Air Injection System Switching Valve B Circuit Shorted
+ //P0418 Secondary Air Injection System Relay "A" Circuit Malfunction
+ //P0419 Secondary Air Injection System Relay "B" Circuit Malfunction
+ //P0420 Catalyst System Efficiency Below Threshold (Bank 1)
+ //P0421 Warm Up Catalyst Efficiency Below Threshold (Bank 1)
+ //P0422 Main Catalyst Efficiency Below Threshold (Bank 1)
+ //P0423 Heated Catalyst Efficiency Below Threshold (Bank 1)
+ //P0424 Heated Catalyst Temperature Below Threshold (Bank 1)
+ //P0424 Heated Catalyst Temperature Below Threshold (Bank 1)
+ //P0425 Catalyst Temperature Sensor (Bank 1)
+ //P0426 Catalyst Temperature Sensor Range/Performance (Bank 1)
+ //P0427 Catalyst Temperature Sensor Low (Bank 1)
+ //P0428 Catalyst Temperature Sensor High (Bank 1)
+ //P0429 Catalyst Heater Control Circuit
+ //P0430 Catalyst System Efficiency Below Threshold (Bank 2)
+ //P0431 Warm Up Catalyst Efficiency Below Threshold (Bank 2)
+ //P0432 Main Catalyst Efficiency Below Threshold (Bank 2)
+ //P0433 Heated Catalyst Efficiency Below Threshold (Bank 2)
+ //P0434 Heated Catalyst Temperature Below Threshold (Bank 2)
+ //P0435 Catalyst Temperature Sensor (Bank 2)
+ //P0436 Catalyst Temperature Sensor Range/Performance (Bank 2)
+ //P0437 Catalyst Temperature Sensor Low (Bank 2)
+ //P0438 Catalyst Temperature Sensor High (Bank 2)
+ //P0439 Catalyst Heater Control Circuit
+ //P0440 Evaporative Emission Control System Malfunction
+ //P0441 Evaporative Emission Control System Incorrect Purge Flow
+ //P0442 Evaporative Emission Control System Leak Detected (small leak)
+ //P0443 Evaporative Emission Control System Purge Control Valve Circuit
+ //P0444 Evaporative Emission Control System Purge Control Valve Circuit Open
+ //P0445 Evaporative Emission Control System Purge Control Valve Circuit Shorted
+ //P0446 Evaporative Emission Control System Vent Control Circuit Malfunction
+ //P0447 Evaporative Emission Control System Vent Control Circuit Open
+ //P0448 Evaporative Emission Control System Vent Control Circuit Shorted
+ //P0449 Evaporative Emission Control System Vent Valve/Solenoid Circuit Malfunction
+ //P0450 Evaporative Emission Control System Pressure Sensor Malfunction
+ //P0451 Evaporative Emission Control System Pressure Sensor Range/Performance
+ //P0452 Evaporative Emission Control System Pressure Sensor Low Input
+ //P0453 Evaporative Emission Control System Pressure Sensor High Input
+ //P0454 Evaporative Emission Control System Pressure Sensor Intermittent
+ //P0455 Evaporative Emission Control System Leak Detected (gross leak)
+ //P0456 Evaporative Emissions System Small Leak Detected
+ //P0457 Evaporative Emission Control System Leak Detected
+ //P0458 Evaporative Emission System Purge Control Valve Circuit Low
+ //P0459 Evaporative Emission System Purge Control Valve Circuit High
+ //P0460 Fuel Level Sensor Circuit Malfunction
+ //P0461 Fuel Level Sensor Circuit Range/Performance
+ //P0462 Fuel Level Sensor Circuit Low Input
+ //P0463 Fuel Level Sensor Circuit High Input
+ //P0464 Fuel Level Sensor Circuit Intermittent
+ //P0465 Purge Flow Sensor Circuit Malfunction
+ //P0466 Purge Flow Sensor Circuit Range/Performance
+ //P0467 Purge Flow Sensor Circuit Low Input
+ //P0468 Purge Flow Sensor Circuit High Input
+ //P0469 Purge Flow Sensor Circuit Intermittent
+ //P0470 Exhaust Pressure Sensor Malfunction
+ //P0471 Exhaust Pressure Sensor Range/Performance
+ //P0472 Exhaust Pressure Sensor Low
+ //P0473 Exhaust Pressure Sensor High
+ //P0474 Exhaust Pressure Sensor Intermittent
+ //P0475 Exhaust Pressure Control Valve Malfunction
+ //P0476 Exhaust Pressure Control Valve Range/Performance
+ //P0477 Exhaust Pressure Control Valve Low
+ //P0478 Exhaust Pressure Control Valve High
+ //P0479 Exhaust Pressure Control Valve Intermittent
+ //P0480 Cooling Fan I Control Circuit Malfunction
+ //P0481 Cooling Fan 2 Control Circuit Malfunction
+ //P0482 Cooling Fan 3 Control Circuit Malfunction
+ //P0483 Cooling Fan Rationality Check Malfunction
+ //P0484 Cooling Fan Circuit Over Current
+ //P0485 Cooling Fan Power/Ground Circuit Malfunction
+ //P0486 Exhaust Gas Recirculation Sensor "B" Circuit
+ //P0487 Exhaust Gas Recirculation Throttle Control Circuit "A" Open
+ //P0488 Exhaust Gas Recirculation Throttle Control Circuit "A" Range/Performance
+ //P0489 Exhaust Gas Recirculation "A" Control Circuit Low
+ //P0490 Exhaust Gas Recirculation "A" Control Circuit High
+ //P0491 Secondary Air Injection System Insufficient Flow Bank 1
+ //P0492 Secondary Air Injection System Insufficient Flow Bank 2
+ //P0493 Fan Overspeed
+ //P0494 Fan Speed Low
+ //P0495 Fan Speed High
+ //P0496 EVAP Flow During A Non-Purge Condition
+ //P0497 Evaporative Emission System Low Purge Flow
+ //P0498 Evaporative Emission System Vent Valve Control Circuit Low
+ //P0499 Evaporative Emission System Vent Valve Control Circuit High
+ //DTC Codes - P0500-P0599 - Vehicle Speed Controls and Idle Control System
+ //P0500 Vehicle Speed Sensor Malfunction
+ //P0501 Vehicle Speed Sensor Range/Performance
+ //P0502 Vehicle Speed Sensor Low Input
+ //P0503 Vehicle Speed Sensor Intermittent/Erratic/High
+ //P0504 Brake Switch "A"/"B" Correlation
+ //P0505 Idle Control System Malfunction
+ //P0506 Idle Control System RPM Lower Than Expected
+ //P0507 Idle Control System RPM Higher Than Expected
+ //P0508 Idle Air Control System Circuit Low
+ //P0509 Idle Air Control System Circuit High
+ //P0510 Closed Throttle Position Switch Malfunction
+ //P0511 Idle Air Control Circuit
+ //P0512 Starter Request Circuit
+ //P0513 Incorrect Immobilizer Key
+ //P0514 Battery Temperature Sensor Circuit Range/Performance
+ //P0515 Battery Temperature Sensor Circuit
+ //P0516 Battery Temperature Sensor Circuit Low
+ //P0517 Battery Temperature Sensor Circuit High
+ //P0518 Idle Air Control Circuit Intermittent
+ //P0519 Idle Air Control System Performance
+ //P0520 Engine Oil Pressure Sensor/Switch Circuit Malfunction
+ //P0521 Engine Oil Pressure Sensor/Switch Circuit Range/Performance
+ //P0522 Engine Oil Pressure Sensor/Switch Circuit Low Voltage
+ //P0523 Engine Oil Pressure Sensor/Switch Circuit High Voltage
+ //P0524 Engine Oil Pressure Too Low
+ //P0525 Cruise Control Servo Control Circuit Range/Performance
+ //P0526 Fan Speed Sensor Circuit
+ //P0527 Fan Speed Sensor Circuit Range/Performance
+ //P0528 Fan Speed Sensor Circuit No Signal
+ //P0529 Fan Speed Sensor Circuit Intermittent
+ //P0530 A/C Refrigerant Pressure Sensor Circuit Malfunction
+ //P0531 A/C Refrigerant Pressure Sensor Circuit Range/Performance
+ //P0532 A/C Refrigerant Pressure Sensor Circuit Low Input
+ //P0533 A/C Refrigerant Pressure Sensor Circuit High Input
+ //P0534 Air Conditioner Refrigerant Charge Loss
+ //P0535 A/C Evaporator Temperature Sensor Circuit
+ //P0536 A/C Evaporator Temperature Sensor Circuit Range/Performance
+ //P0537 A/C Evaporator Temperature Sensor Circuit Low
+ //P0538 A/C Evaporator Temperature Sensor Circuit High
+ //P0539 A/C Evaporator Temperature Sensor Circuit Intermittent
+ //P0540 Intake Air Heater "A" Circuit
+ //P0541 Intake Air Heater "A" Circuit Low
+ //P0542 Intake Air Heater "A" Circuit High
+ //P0543 Intake Air Heater "A" Circuit Open
+ //P0544 Exhaust Gas Temperature Sensor Circuit
+ //P0545 Exhaust Gas Temperature Sensor Circuit Low
+ //P0546 Exhaust Gas Temperature Sensor Circuit High
+ //P0547 Exhaust Gas Temperature Sensor Circuit
+ //P0548 Exhaust Gas Temperature Sensor Circuit Low
+ //P0549 Exhaust Gas Temperature Sensor Circuit High
+ //P0550 Power Steering Pressure Sensor Circuit Malfunction
+ //P0551 Power Steering Pressure Sensor Circuit Range/Performance
+ //P0552 Power Steering Pressure Sensor Circuit Low Input
+ //P0553 Power Steering Pressure Sensor Circuit High Input
+ //P0554 Power Steering Pressure Sensor Circuit Intermittent
+ //P0555 Brake Booster Pressure Sensor Circuit
+ //P0556 Brake Booster Pressure Sensor Circuit Range/Performance
+ //P0557 Brake Booster Pressure Sensor Circuit Low
+ //P0558 Brake Booster Pressure Sensor Circuit High
+ //P0559 Brake Booster Pressure Sensor Circuit Intermittent
+ OBD_System_Voltage_Malfunction = 560,
+ //P0561 System Voltage Unstable
+ //P0562 System Voltage Low
+ //P0563 System Voltage High
+ //P0564 Cruise Control Multi-Function Input "A" Circuit
+ //P0565 Cruise Control On Signal Malfunction
+ //P0566 Cruise Control Off Signal Malfunction
+ //P0567 Cruise Control Resume Signal Malfunction
+ //P0568 Cruise Control Set Signal Malfunction
+ //P0569 Cruise Control Coast Signal Malfunction
+ //P0570 Cruise Control Accel Signal Malfunction
+ //P0571 Cruise Control/Brake Switch A Circuit Malfunction
+ //P0572 Cruise Control/Brake Switch A Circuit Low
+ //P0573 Cruise Control/Brake Switch A Circuit High
+ //P0574 Cruise Control System - Vehicle Speed Too High
+ //P0575 Cruise Control Input Circuit
+ //P0576 Cruise Control Input Circuit Low
+ //P0577 Cruise Control Input Circuit High
+ //P0578 Cruise Control Multi-Function Input "A" Circuit Stuck
+ //P0579 Cruise Control Multi-Function Input "A" Circuit Range/Performance
+ //P0580 Cruise Control Multi-Function Input "A" Circuit Low
+ //P0581 Cruise Control Multi-Function Input "A" Circuit High
+ //P0582 Cruise Control Vacuum Control Circuit/Open
+ //P0583 Cruise Control Vacuum Control Circuit Low
+ //P0584 Cruise Control Vacuum Control Circuit High
+ //P0585 Cruise Control Multi-Function Input "A"/"B" Correlation
+ //P0586 Cruise Control Vent Control Circuit/Open
+ //P0587 Cruise Control Vent Control Circuit Low
+ //P0588 Cruise Control Vent Control Circuit High
+ //P0589 Cruise Control Multi-Function Input "B" Circuit
+ //P0590 Cruise Control Multi-Function Input "B" Circuit Stuck
+ //P0591 Cruise Control Multi-Function Input "B" Circuit Range/Performance
+ //P0592 Cruise Control Multi-Function Input "B" Circuit Low
+ //P0593 Cruise Control Multi-Function Input "B" Circuit High
+ //P0594 Cruise Control Servo Control Circuit/Open
+ //P0595 Cruise Control Servo Control Circuit Low
+ //P0596 Cruise Control Servo Control Circuit High
+ //P0597 Thermostat Heater Control Circuit/Open
+ //P0598 Thermostat Heater Control Circuit Low
+ //P0599 Thermostat Heater Control Circuit High
+ //P0600 Serial Communication Link Malfunction
+ //P0601 Internal Control Module Memory Check Sum Error
+ //P0602 Control Module Programming Error
+ //P0603 Internal Control Module Keep Alive Memory (KAM) Error
+ //P0604 Internal Control Module Random Access Memory (RAM) Error
+ //P0605 Internal Control Module Read Only Memory (ROM) Error
+ OBD_PCM_Processor_Fault = 606,
+ //P0607 Control Module Performance
+ //P0608 Control Module VSS Output "A" Malfunction
+ //P0609 Control Module VSS Output "B" Malfunction
+ //P0610 Control Module Vehicle Options Error
+ //P0611 Fuel Injector Control Module Performance
+ //P0612 Fuel Injector Control Module Relay Control
+ //P0613 TCM Processor
+ //P0614 ECM / TCM Incompatible
+ //P0615 Starter Relay Circuit
+ //P0616 Starter Relay Circuit Low
+ //P0617 Starter Relay Circuit High
+ //P0618 Alternative Fuel Control Module KAM Error
+ //P0619 Alternative Fuel Control Module RAM/ROM Error
+ //P0620 Generator Control Circuit Malfunction
+ //P0621 Generator Lamp "L" Control Circuit Malfunction
+ //P0622 Generator Field "F" Control Circuit Malfunction
+ //P0623 Generator Lamp Control Circuit
+ //P0624 Fuel Cap Lamp Control Circuit
+ //P0625 Generator Field/F Terminal Circuit Low
+ //P0626 Generator Field/F Terminal Circuit High
+ //P0627 Fuel Pump “A” Control Circuit /Open
+ //P0628 Fuel Pump “A” Control Circuit Low
+ //P0629 Fuel Pump “A” Control Circuit High
+ //P0630 VIN Not Programmed or Incompatible - ECM/PCM
+ //P0631 VIN Not Programmed or Incompatible - TCM
+ //P0632 Odometer Not Programmed - ECM/PCM
+ //P0633 Immobilizer Key Not Programmed - ECM/PCM
+ //P0634 PCM/ECM/TCM Internal Temperature Too High
+ //P0635 Power Steering Control Circuit
+ //P0636 Power Steering Control Circuit Low
+ //P0637 Power Steering Control Circuit High
+ //P0638 Throttle Actuator Control Range/Performance (Bank 1)
+ //P0639 Throttle Actuator Control Range/Performance (Bank 2)
+ //P0640 Intake Air Heater Control Circuit
+ //P0641 Sensor Reference Voltage “A” Circuit/Open
+ //P0642 Sensor Reference Voltage “A” Circuit Low
+ //P0643 Sensor Reference Voltage “A” Circuit High
+ //P0644 Driver Display Serial Communication Circuit
+ //P0645 A/C Clutch Relay Control Circuit
+ //P0646 A/C Clutch Relay Control Circuit Low
+ //P0647 A/C Clutch Relay Control Circuit High
+ //P0648 Immobilizer Lamp Control Circuit
+ //P0649 Speed Control Lamp Control Circuit
+ //P0650 Malfunction Indicator Lamp (MIL) Control Circuit Malfunction
+ //P0651 Sensor Reference Voltage “B” Circuit/Open
+ //P0652 Sensor Reference Voltage “B” Circuit Low
+ //P0653 Sensor Reference Voltage “B” Circuit High
+ //P0654 Engine RPM Output Circuit Malfunction
+ //P0655 Engine Hot Lamp Output Control Circuit Malfunction
+ //P0656 Fuel Level Output Circuit Malfunction
+ //P0657 Actuator Supply Voltage "A" Circuit/Open
+ //P0658 Actuator Supply Voltage "A" Circuit Low
+ //P0659 Actuator Supply Voltage "A" Circuit High
+ //P0660 Intake Manifold Tuning Valve Control Circuit/Open Bank 1
+ //P0661 Intake Manifold Tuning Valve Control Circuit Low Bank 1
+ //P0662 Intake Manifold Tuning Valve Control Circuit High Bank 1
+ //P0663 Intake Manifold Tuning Valve Control Circuit/Open Bank 2
+ //P0664 Intake Manifold Tuning Valve Control Circuit Low Bank 2
+ //P0665 Intake Manifold Tuning Valve Control Circuit High Bank 2
+ //P0666 PCM/ECM/TCM Internal Temperature Sensor Circuit
+ //P0667 PCM/ECM/TCM Internal Temperature Sensor Range/Performance
+ //P0668 PCM/ECM/TCM Internal Temperature Sensor Circuit Low
+ //P0669 PCM/ECM/TCM Internal Temperature Sensor Circuit High
+ //P0670 Glow Plug Module Control Circuit
+ //P0671 Cylinder 1 Glow Plug Circuit
+ //P0672 Cylinder 2 Glow Plug Circuit
+ //P0673 Cylinder 3 Glow Plug Circuit
+ //P0674 Cylinder 4 Glow Plug Circuit
+ //P0675 Cylinder 5 Glow Plug Circuit
+ //P0676 Cylinder 6 Glow Plug Circuit
+ //P0677 Cylinder 7 Glow Plug Circuit
+ //P0678 Cylinder 8 Glow Plug Circuit
+ //P0679 Cylinder 9 Glow Plug Circuit
+ //P0680 Cylinder 10 Glow Plug Circuit
+ //P0681 Cylinder 11 Glow Plug Circuit
+ //P0682 Cylinder 12 Glow Plug Circuit
+ //P0683 Glow Plug Control Module to PCM Communication Circuit
+ //P0684 Glow Plug Control Module to PCM Communication Circuit Range/Performance
+ //P0685 ECM/PCM Power Relay Control Circuit /Open
+ //P0686 ECM/PCM Power Relay Control Circuit Low
+ //P0687 ECM/PCM Power Relay Control Circuit High
+ //P0688 ECM/PCM Power Relay Sense Circuit /Open
+ //P0689 ECM/PCM Power Relay Sense Circuit Low
+ //P0690 ECM/PCM Power Relay Sense Circuit High
+ //P0691 Fan 1 Control Circuit Low P0692 Fan 1 Control Circuit High
+ //P0693 Fan 2 Control Circuit Low
+ //P0694 Fan 2 Control Circuit High
+ //P0695 Fan 3 Control Circuit Low
+ //P0696 Fan 3 Control Circuit High
+ //P0697 Sensor Reference Voltage “C” Circuit/Open
+ //P0698 Sensor Reference Voltage “C” Circuit Low
+ //P0699 Sensor Reference Voltage “C” Circuit High
+ //P0700 Transmission Control System Malfunction
+ //P0701 Transmission Control System Range/Performance
+ //P0702 Transmission Control System Electrical
+ //P0703 Torque Converter/Brake Switch B Circuit Malfunction
+ //P0704 Clutch Switch Input Circuit Malfunction
+ //P0705 Transmission Range Sensor Circuit malfunction (PRNDL Input)
+ //P0706 Transmission Range Sensor Circuit Range/Performance
+ //P0707 Transmission Range Sensor Circuit Low Input
+ //P0708 Transmission Range Sensor Circuit High Input
+ //P0709 Transmission Range Sensor Circuit Intermittent
+ //P0710 Transmission Fluid Temperature Sensor Circuit Malfunction
+ //P0711 Transmission Fluid Temperature Sensor Circuit Range/Performance
+ //P0712 Transmission Fluid Temperature Sensor Circuit Low Input
+ //P0713 Transmission Fluid Temperature Sensor Circuit High Input
+ //P0714 Transmission Fluid Temperature Sensor Circuit Intermittent
+ //P0715 Input/Turbine Speed Sensor Circuit Malfunction
+ //P0716 Input/Turbine Speed Sensor Circuit Range/Performance
+ //P0717 Input/Turbine Speed Sensor Circuit No Signal
+ //P0718 Input/Turbine Speed Sensor Circuit Intermittent
+ //P0719 Torque Converter/Brake Switch B Circuit Low
+ //P0720 Output Speed Sensor Circuit Malfunction
+ //P0721 Output Speed Sensor Range/Performance
+ //P0722 Output Speed Sensor No Signal
+ //P0723 Output Speed Sensor Intermittent
+ //P0724 Torque Converter/Brake Switch B Circuit High
+ //P0725 Engine Speed input Circuit Malfunction
+ //P0726 Engine Speed Input Circuit Range/Performance
+ //P0727 Engine Speed Input Circuit No Signal
+ //P0728 Engine Speed Input Circuit Intermittent
+ //P0729 Gear 6 Incorrect Ratio
+ //P0730 Incorrect Gear Ratio
+ //P0731 Gear I Incorrect ratio
+ //P0732 Gear 2 Incorrect ratio
+ //P0733 Gear 3 Incorrect ratio
+ //P0734 Gear 4 Incorrect ratio
+ //P0735 Gear 5 Incorrect ratio
+ //P0736 Reverse incorrect gear ratio
+ //P0737 TCM Engine Speed Output Circuit
+ //P0738 TCM Engine Speed Output Circuit Low
+ //P0739 TCM Engine Speed Output Circuit High
+ //P0740 Torque Converter Clutch Circuit Malfunction
+ //P0741 Torque Converter Clutch Circuit Performance or Stuck Off
+ //P0742 Torque Converter Clutch Circuit Stock On
+ //P0743 Torque Converter Clutch Circuit Electrical
+ //P0744 Torque Converter Clutch Circuit Intermittent
+ //P0745 Pressure Control Solenoid Malfunction
+ //P0746 Pressure Control Solenoid Performance or Stuck Off
+ //P0747 Pressure Control Solenoid Stuck On
+ //P0748 Pressure Control Solenoid Electrical
+ //P0749 Pressure Control Solenoid Intermittent
+ //P0750 Shift Solenoid A Malfunction
+ //P0751 Shift Solenoid A Performance or Stuck Off
+ //P0752 Shift Solenoid A Stuck On
+ //P0753 Shift Solenoid A Electrical
+ //P0754 Shift Solenoid A Intermittent
+ //P0755 Shift Solenoid B Malfunction
+ //P0756 Shift Solenoid B Performance or Stock Off
+ //P0757 Shift Solenoid B Stuck On
+ //P0758 Shift Solenoid B Electrical
+ //P0759 Shift Solenoid B Intermittent
+ //P0760 Shift Solenoid C Malfunction
+ //P0761 Shift Solenoid C Performance or Stuck Off
+ //P0762 Shift Solenoid C Stuck On
+ //P0763 Shift Solenoid C Electrical
+ //P0764 Shift Solenoid C Intermittent
+ //P0765 Shift Solenoid D Malfunction
+ //P0766 Shift Solenoid D Performance or Stuck Off
+ //P0767 Shift Solenoid D Stuck On
+ //P0768 Shift Solenoid D Electrical
+ //P0769 Shift Solenoid D Intermittent
+ //P0770 Shift Solenoid E Malfunction
+ //P0771 Shift Solenoid E Performance or Stuck Off
+ //P0772 Shift Solenoid E Stuck On
+ //P0773 Shift Solenoid E Electrical
+ //P0774 Shift Solenoid E Intermittent
+ //P0775 Pressure Control Solenoid "B"
+ //P0776 Pressure Control Solenoid "B" Performance or Stuck off
+ //P0777 Pressure Control Solenoid "B" Stuck On
+ //P0778 Pressure Control Solenoid "B" Electrical
+ //P0779 Pressure Control Solenoid "B" Intermittent
+ //P0780 Shift Malfunction
+ //P0781 1-2 Shift Malfunction
+ //P0782 2-3 Shift Malfunction
+ //P0783 3-4 Shift Malfunction
+ //P0784 4-5 Shift Malfunction
+ //P0785 Shift/Timing Solenoid Malfunction
+ //P0786 Shift/Timing Solenoid Range/Performance
+ //P0787 Shift/Timing Solenoid Low
+ //P0788 Shift/Timing Solenoid High
+ //P0789 Shift/Timing Solenoid Intermittent
+ //P0790 Normal/Performance Switch Circuit Malfunction
+ //P0791 Intermediate Shaft Speed Sensor “A” Circuit
+ //P0792 Intermediate Shaft Speed Sensor “A” Circuit Range/Performance
+ //P0793 Intermediate Shaft Speed Sensor “A” Circuit No Signal
+ //P0794 Intermediate Shaft Speed Sensor “A” Circuit Intermittent
+ //P0795 Pressure Control Solenoid "C"
+ //P0796 Pressure Control Solenoid "C" Performance or Stuck off
+ //P0797 Pressure Control Solenoid "C" Stuck On
+ //P0798 Pressure Control Solenoid "C" Electrical
+ //P0799 Pressure Control Solenoid "C" Intermittent
+ //P0800 Transfer Case Control System (MIL Request)
+ //P0801 Reverse Inhibit Control Circuit Malfunction
+ //P0802 Transmission Control System MIL Request Circuit/Open
+ //P0803 1-4 Upshift (Skip Shift) Solenoid Control Circuit Malfunction
+ //P0804 1-4 Upshift (Skip Shift) Lamp Control Circuit Malfunction
+ //P0805 Clutch Position Sensor Circuit
+ //P0806 Clutch Position Sensor Circuit Range/Performance
+ //P0807 Clutch Position Sensor Circuit Low
+ //P0808 Clutch Position Sensor Circuit High
+ //P0809 Clutch Position Sensor Circuit Intermittent
+ //P0810 Clutch Position Control Error
+ //P0811 Excessive Clutch Slippage
+ //P0812 Reverse Input Circuit
+ //P0813 Reverse Output Circuit
+ //P0814 Transmission Range Display Circuit
+ //P0815 Upshift Switch Circuit
+ //P0816 Downshift Switch Circuit
+ //P0817 Starter Disable Circuit
+ //P0818 Driveline Disconnect Switch Input Circuit
+ //P0819 Up and Down Shift Switch to Transmission Range Correlation
+ //P0820 Gear Lever X-Y Position Sensor Circuit
+ //P0821 Gear Lever X Position Circuit
+ //P0822 Gear Lever Y Position Circuit
+ //P0823 Gear Lever X Position Circuit Intermittent
+ //P0824 Gear Lever Y Position Circuit Intermittent
+ //P0825 Gear Lever Push-Pull Switch (Shift Anticipate
+ //P0826 Up and Down Shift Switch Circuit
+ //P0827 Up and Down Shift Switch Circuit Low
+ //P0828 Up and Down Shift Switch Circuit High
+ //P0829 5-6 Shift
+ //P0830 Clutch Pedal Switch "A" Circuit
+ //P0831 Clutch Pedal Switch "A" Circuit Low
+ //P0832 Clutch Pedal Switch "A" Circuit High
+ //P0833 Clutch Pedal Switch "B" Circuit
+ //P0834 Clutch Pedal Switch "B" Circuit Low
+ //P0835 Clutch Pedal Switch "B" Circuit High
+ //P0836 Four Wheel Drive (4WD) Switch Circuit
+ //P0837 Four Wheel Drive (4WD) Switch Circuit Range/Performance
+ //P0838 Four Wheel Drive (4WD) Switch Circuit Low
+ //P0839 Four Wheel Drive (4WD) Switch Circuit High P0840 Transmission Fluid Pressure Sensor/Switch "A" Circuit
+ //P0840 Transmission Fluid Pressure Sensor/Switch "A" Circuit
+ //P0841 Transmission Fluid Pressure Sensor/Switch "A" Circuit Range/Performance
+ //P0842 Transmission Fluid Pressure Sensor/Switch "A" Circuit Low
+ //P0843 Transmission Fluid Pressure Sensor/Switch "A" Circuit High
+ //P0844 Transmission Fluid Pressure Sensor/Switch "A" Circuit Intermittent
+ //P0845 Transmission Fluid Pressure Sensor/Switch "B" Circuit
+ //P0846 Transmission Fluid Pressure Sensor/Switch "B" Circuit Range/Performance
+ //P0847 Transmission Fluid Pressure Sensor/Switch "B" Circuit Low
+ //P0848 Transmission Fluid Pressure Sensor/Switch "B" Circuit High
+ //P0849 Transmission Fluid Pressure Sensor/Switch "B" Circuit Intermittent
+ //P0850 Park/Neutral Switch Input Circuit
+ //P0851 Park/Neutral Switch Input Circuit Low
+ //P0852 Park/Neutral Switch Input Circuit High
+ //P0853 Drive Switch Input Circuit
+ //P0854 Drive Switch Input Circuit Low
+ //P0855 Drive Switch Input Circuit High
+ //P0856 Traction Control Input Signal
+ //P0857 Traction Control Input Signal Range/Performance
+ //P0858 Traction Control Input Signal Low
+ //P0859 Traction Control Input Signal High
+ //P0860 Gear Shift Module Communication Circuit
+ //P0861 Gear Shift Module Communication Circuit Low
+ //P0862 Gear Shift Module Communication Circuit High
+ //P0863 TCM Communication Circuit
+ //P0864 TCM Communication Circuit Range/Performance
+ //P0865 TCM Communication Circuit Low
+ //P0866 TCM Communication Circuit High
+ //P0867 Transmission Fluid Pressure
+ //P0868 Transmission Fluid Pressure Low P0869 Transmission Fluid Pressure High
+ //P0869 Transmission Fluid Pressure High
+ //P0870 Transmission Fluid Pressure Sensor/Switch “C” Circuit
+ //P0871 Transmission Fluid Pressure Sensor/Switch “C” Circuit Range/Performance
+ //P0872 Transmission Fluid Pressure Sensor/Switch “C” Circuit Low
+ //P0873 Transmission Fluid Pressure Sensor/Switch “C” Circuit High
+ //P0874 Transmission Fluid Pressure Sensor/Switch “C” Circuit Intermittent
+ //P0875 Transmission Fluid Pressure Sensor/Switch “D” Circuit
+ //P0876 Transmission Fluid Pressure Sensor/Switch “D” Circuit Range/Performance
+ //P0877 Transmission Fluid Pressure Sensor/Switch “D” Circuit Low
+ //P0878 Transmission Fluid Pressure Sensor/Switch “D” Circuit High
+ //P0879 Transmission Fluid Pressure Sensor/Switch “D” Circuit Intermittent
+ //P0880 TCM Power Input Signal
+ //P0881 TCM Power Input Signal Range/Performance
+ //P0882 TCM Power Input Signal Low
+ //P0883 TCM Power Input Signal High
+ //P0884 TCM Power Input Signal Intermittent
+ //P0885 TCM Power Relay Control Circuit/Open
+ //P0886 TCM Power Relay Control Circuit Low
+ //P0887 TCM Power Relay Control Circuit High
+ //P0888 TCM Power Relay Sense Circuit
+ //P0889 TCM Power Relay Sense Circuit Range/Performance
+ //P0890 TCM Power Relay Sense Circuit Low
+ //P0891 TCM Power Relay Sense Circuit High
+ //P0892 TCM Power Relay Sense Circuit Intermittent
+ //P0893 Multiple Gears Engaged
+ //P0894 Transmission Component Slipping
+ //P0895 Shift Time Too Short
+ //P0896 Shift Time Too Long
+ //P0897 Transmission Fluid Deteriorated
+ //P0898 Transmission Control System MIL Request Circuit Low
+ //P0899 Transmission Control System MIL Request Circuit High
+ //P2000 NOx Trap Efficiency Below Threshold Bank1
+ //P2001 NOx Trap Efficiency Below Threshold Bank2
+ //P2002 Particulate Trap Efficiency Below Threshold Bank1
+ //P2003 Particulate Trap Efficiency Below Threshold Bank2
+ //P2004 Intake Manifold Runner Ctrl Stuck Open Bank1
+ //P2005 Intake Manifold Runner Ctrl Stuck Open Bank2
+ //P2006 Intake Manifold Runner Ctrl Stuck Closed Bank1
+ //P2007 Intake Manifold Runner Ctrl Stuck Closed Bank2
+ //P2008 Intake Manifold Runner Ctrl Circ/Open Bank1
+ //P2009 Intake Manifold Runner Ctrl Circ Low Bank1
+ //P2010 Intake Manifold Runner Ctrl Circ High Bank1
+ //P2011 Intake Manifold Runner Ctrl Circ/Open Bank2
+ //P2012 Intake Manifold Runner Ctrl Circ Low Bank2
+ //P2013 Intake Manifold Runner Ctrl Circ High Bank2
+ //P2014 Intake Manifold Runner Pos Sensor/Switch Circ Bank1
+ //P2015 Intake Manifold Runner Pos Sensor/Switch Circ Range/Perf Bank1
+ //P2016 Intake Manifold Runner Pos Sensor/Switch Circ Low Bank1
+ //P2017 Intake Manifold Runner Pos Sensor/Switch Circ High Bank1
+ //P2018 Intake Manifold Runner Pos Sensor/Switch Circ Interm Bank1
+ //P2019 Intake Manifold Runner Pos Sensor/Switch Circ Bank2
+ //P2020 Intake Manifold Runner Pos Sensor/Switch Circ Range/Perf Bank2
+ //P2021 Intake Manifold Runner Pos Sensor/Switch Circ Low Bank2
+ //P2022 Intake Manifold Runner Pos Sensor/Switch Circ High Bank2
+ //P2023 Intake Manifold Runner Pos Sensor/Switch Circ Interm Bank2
+ //P2024 EVAP Fuel Vapor Temp Sensor Circ
+ //P2025 EVAP Fuel Vapor Temp Sensor Perf
+ //P2026 EVAP Fuel Vapor Temp Sensor Circ Low Voltage
+ //P2027 EVAP Fuel Vapor Temp Sensor Circ High Voltage
+ //P2028 EVAP Fuel Vapor Temp Sensor Circ Interm
+ //P2029 Fuel Fired Heater Disabled
+ //P2030 Fuel Fired Heater Perf
+ //P2031 Exhaust Gas Temp Sensor Circ Bank1 Sensor 2
+ //P2032 Exhaust Gas Temp Sensor Circ Low Bank1 Sensor 2
+ //P2033 Exhaust Gas Temp Sensor Circ High Bank1 Sensor 2
+ //P2034 Exhaust Gas Temp Sensor Circ Bank2 Sensor 2
+ //P2035 Exhaust Gas Temp Sensor Circ Low Bank2 Sensor 2
+ //P2036 Exhaust Gas Temp Sensor Circ High Bank2 Sensor 2
+ //P2037 Reductant Inj Air Press Sensor Circ
+ //P2038 Reductant Inj Air Press Sensor Circ Range/Perf
+ //P2039 Reductant Inj Air Press Sensor Circ Low Input
+ //P2040 Reductant Inj Air Press Sensor Circ High Input
+ //P2041 Reductant Inj Air Press Sensor Circ Interm
+ //P2042 Reductant Temp Sensor Circ
+ //P2043 Reductant Temp Sensor Circ Range/Perf
+ //P2044 Reductant Temp Sensor Circ Low Input
+ //P2045 Reductant Temp Sensor Circ High Input
+ //P2046 Reductant Temp Sensor Circ Interm
+ //P2047 Reductant Injector Circ/Open Bank1 Unit 1
+ //P2048 Reductant Injector Circ Low Bank1 Unit 1
+ //P2049 Reductant Injector Circ High Bank1 Unit 1
+ //P2050 Reductant Injector Circ/Open Bank2 Unit 1
+ //P2051 Reductant Injector Circ Low Bank2 Unit 1
+ //P2052 Reductant Injector Circ High Bank2 Unit 1
+ //P2053 Reductant Injector Circ/Open Bank1 Unit 2
+ //P2054 Reductant Injector Circ Low Bank1 Unit 2
+ //P2055 Reductant Injector Circ High Bank1 Unit 2
+ //P2056 Reductant Injector Circ/Open Bank2 Unit 2
+ //P2057 Reductant Injector Circ Low Bank2 Unit 2
+ //P2058 Reductant Injector Circ High Bank2 Unit 2
+ //P2059 Reductant Inj Air Pump Ctrl Circ/Open
+ //P2060 Reductant Inj Air Pump Ctrl Circ Low
+ //P2061 Reductant Inj Air Pump Ctrl Circ High
+ //P2062 Reductant Supply Ctrl Circ/Open
+ //P2063 Reductant Supply Ctrl Circ Low
+ //P2064 Reductant Supply Ctrl Circ High
+ //P2065 Fuel Level SensorB Circ
+ //P2066 Fuel Level SensorB Perf
+ //P2067 Fuel Level SensorB Circ Low
+ //P2068 Fuel Level SensorB Circ High
+ //P2069 Fuel Level SensorB Circ Interm
+ //P2070 Intake Manifold Tuning (IMT) Valve Stuck Open
+ //P2071 IMT Valve Stuck Closed
+ //P2072 Throttle Actuator Control System - Ice Blockage
+ //P2073 Manifold Absolute Pressure/Mass Air Flow - Throttle Position Correlation at Idle
+ //P2074 Manifold Absolute Pressure/Mass Air Flow - Throttle Position Correlation at Higher Load
+ //P2075 IMT Valve Pos Sensor/Switch Circ
+ //P2076 IMT Valve Pos Sensor/Switch Circ Range/Perf
+ //P2077 IMT Valve Pos Sensor/Switch Circ Low
+ //P2078 IMT Valve Pos Sensor/Switch Circ High
+ //P2079 IMT Valve Pos Sensor/Switch Circ Interm
+ //P2080 Exhaust Gas Temp Sensor Circ Range/Perf Bank1 Sensor 1
+ //P2081 Exhaust Gas Temp Sensor Circ Interm Bank1 Sensor 1
+ //P2082 Exhaust Gas Temp Sensor Circ Range/Perf Bank2 Sensor 1
+ //P2083 Exhaust Gas Temp Sensor Circ Interm Bank2 Sensor 1
+ //P2084 Exhaust Gas Temp Sensor Circ Range/Perf Bank1 Sensor 2
+ //P2085 Exhaust Gas Temp Sensor Circ Interm Bank1 Sensor 2
+ //P2086 Exhaust Gas Temp Sensor Circ Range/Perf Bank2 Sensor 2
+ //P2087 Exhaust Gas Temp Sensor Circ Interm Bank2 Sensor 2
+ //P2088 A Camshaft Pos Actuator Ctrl Circ Low Bank1
+ //P2089 A Camshaft Pos Actuator Ctrl Circ High Bank1
+ //P2090 B Camshaft Pos Actuator Ctrl Circ Low Bank1
+ //P2091 B Camshaft Pos Actuator Ctrl Circ High Bank1
+ //P2092 A Camshaft Pos Actuator Ctrl Circ Low Bank2
+ //P2093 A Camshaft Pos Actuator Ctrl Circ High Bank2
+ //P2094 B Camshaft Pos Actuator Ctrl Circ Low Bank2
+ //P2095 B Camshaft Pos Actuator Ctrl Circ High Bank2
+ //P2096 Post Catalyst Fuel Trim Sys Too Lean Bank1
+ //P2097 Post Catalyst Fuel Trim Sys Too Rich Bank1
+ //P2098 Post Catalyst Fuel Trim Sys Too Lean Bank2
+ //P2099 Post Catalyst Fuel Trim Sys Too Rich Bank2
+ //P2100 Throttle Actuator Ctrl Motor Circ/Open
+ //P2101 Throttle Actuator Ctrl Motor Circ Range/Perf
+ //P2102 Throttle Actuator Ctrl Motor Circ Low
+ //P2103 Throttle Actuator Ctrl Motor Circ High
+ //P2104 Throttle Actuator Ctrl Sys-Forced Idle
+ //P2105 Throttle Actuator Ctrl Sys-Forced Engine Shutdown
+ //P2106 Throttle Actuator Ctrl Sys-Forced Limited Power
+ //P2107 Throttle Actuator Ctrl Mod Processor
+ //P2108 Throttle Actuator Ctrl Mod Perf
+ //P2109 Throttle/Pedal Pos SensorA Minimum Stop Perf
+ //P2110 Throttle Actuator Ctrl Sys-Forced Limited RPM
+ //P2111 Throttle Actuator Ctrl Sys-Stuck Open
+ //P2112 Throttle Actuator Ctrl Sys-Stuck Closed
+ //P2113 Throttle/Pedal Pos SensorB Minimum Stop Perf
+ //P2114 Throttle/Pedal Pos Sensor C Minimum Stop Perf
+ //P2115 Throttle/Pedal Pos Sensor D Minimum Stop Perf
+ //P2116 Throttle/Pedal Pos Sensor E Minimum Stop Perf
+ //P2117 Throttle/Pedal Pos Sensor F Minimum Stop Perf
+ //P2118 Throttle Actuator Ctrl Motor Current Range/Perf
+ //P2119 Throttle Actuator Ctrl Throttle Body Range/Perf
+ //P2120 Throttle/Pedal Pos Sensor/Switch D Circ
+ //P2121 Throttle/Pedal Pos Sensor/Switch D Circ Range/Perf
+ //P2122 Throttle/Pedal Pos Sensor/Switch D Circ Low Input
+ //P2123 Throttle/Pedal Pos Sensor/Switch D Circ High Input
+ //P2124 Throttle/Pedal Pos Sensor/Switch D Circ Interm
+ //P2125 Throttle/Pedal Pos Sensor/Switch E Circ
+ //P2126 Throttle/Pedal Pos Sensor/Switch E Circ Range/Perf
+ //P2127 Throttle/Pedal Pos Sensor/Switch E Circ Low Input
+ //P2128 Throttle/Pedal Pos Sensor/Switch E Circ High Input
+ //P2129 Throttle/Pedal Pos Sensor/Switch E Circ Interm
+ //P2130 Throttle/Pedal Pos Sensor/Switch F Circ
+ //P2131 Throttle/Pedal Pos Sensor/Switch F Circ Range Perf
+ //P2132 Throttle/Pedal Pos Sensor/Switch F Circ Low Input
+ //P2133 Throttle/Pedal Pos Sensor/Switch F Circ High Input
+ //P2134 Throttle/Pedal Pos Sensor/Switch F Circ Interm
+ //P2135 Throttle/Pedal Pos Sensor/Switch A / B Voltage Correlation
+ //P2136 Throttle/Pedal Pos Sensor/Switch A / C Voltage Correlation
+ //P2137 Throttle/Pedal Pos Sensor/Switch B / C Voltage Correlation
+ //P2138 Throttle/Pedal Pos Sensor/Switch D / E Voltage Correlation
+ //P2139 Throttle/Pedal Pos Sensor/Switch D / F Voltage Correlation
+ //P2140 Throttle/Pedal Pos Sensor/Switch E / F Voltage Correlation
+ //P2141 Exhaust Gas Recirculation Throttle Ctrl Circ Low
+ //P2142 Exhaust Gas Recirculation Throttle Ctrl Circ High
+ //P2143 Exhaust Gas Recirculation Vent Ctrl Circ/Open
+ //P2144 Exhaust Gas Recirculation Vent Ctrl Circ Low
+ //P2145 Exhaust Gas Recirculation Vent Ctrl Circ High
+ //P2146 Fuel Injector Group A Supply Voltage Circ/Open
+ //P2147 Fuel Injector Group A Supply Voltage Circ Low
+ //P2148 Fuel Injector Group A Supply Voltage Circ High
+ //P2149 Fuel Injector Group B Supply Voltage Circ/Open
+ //P2150 Fuel Injector Group B Supply Voltage Circ Low
+ //P2151 Fuel Injector Group B Supply Voltage Circ High
+ //P2152 Fuel Injector Group C Supply Voltage Circ/Open
+ //P2153 Fuel Injector Group C Supply Voltage Circ Low
+ //P2154 Fuel Injector Group C Supply Voltage Circ High
+ //P2155 Fuel Injector Group D Supply Voltage Circ/Open
+ //P2156 Fuel Injector Group D Supply Voltage Circ Low
+ //P2157 Fuel Injector Group D Supply Voltage Circ High
+ //P2158 Vehicle Speed SensorB
+ //P2159 Vehicle Speed SensorB Range/Perf
+ //P2160 Vehicle Speed SensorB Circ Low
+ //P2161 Vehicle Speed SensorB Interm/Erratic
+ //P2162 Vehicle Speed SensorA / B Correlation
+ //P2163 Throttle/Pedal Pos SensorA Maximum Stop Perf
+ //P2164 Throttle/Pedal Pos SensorB Maximum Stop Perf
+ //P2165 Throttle/Pedal Pos Sensor C Maximum Stop Perf
+ //P2166 Throttle/Pedal Pos Sensor D Maximum Stop Perf
+ //P2167 Throttle/Pedal Pos Sensor E Maximum Stop Perf
+ //P2168 Throttle/Pedal Pos Sensor F Maximum Stop Perf
+ //P2169 Exhaust Press Reg Vent Solenoid Ctrl Circ/Open
+ //P2170 Exhaust Press Reg Vent Solenoid Ctrl Circ Low
+ //P2171 Exhaust Press Reg Vent Solenoid Ctrl Circ High
+ //P2172 Throttle Actuator Ctrl Sys-Sudden High Airflow Detected
+ //P2173 Throttle Actuator Ctrl Sys-High Airflow Detected
+ //P2174 Throttle Actuator Ctrl Sys-Sudden Low Airflow Detected
+ //P2175 Throttle Actuator Ctrl Sys-Low Airflow Detected
+ //P2176 Throttle Actuator Ctrl Sys-Idle Pos Not Learned
+ //P2177 Sys Too Lean Off Idle Bank1
+ //P2178 Sys Too Rich Off Idle Bank1
+ //P2179 Sys Too Lean Off Idle Bank2
+ //P2180 Sys Too Rich Off Idle Bank2
+ //P2181 Cooling System Performance
+ //P2182 Engine Coolant Temp Sensor 2 Circ
+ //P2183 Engine Coolant Temp Sensor 2 Circ Range/Perf
+ //P2184 Engine Coolant Temp Sensor 2 Circ Low
+ //P2185 Engine Coolant Temp Sensor 2 Circ High
+ //P2186 Engine Coolant Temp Sensor 2 Circ Interm/Erratic
+ //P2187 Sys Too Lean at Idle Bank1
+ //P2188 Sys Too Rich at Idle Bank1
+ //P2189 Sys Too Lean at Idle Bank2
+ //P2190 Sys Too Rich at Idle Bank2
+ //P2191 Sys Too Lean at Higher Load Bank1
+ //P2192 Sys Too Rich at Higher Load Bank1
+ //P2193 Sys Too Lean at Higher Load Bank2
+ //P2194 Sys Too Rich at Higher Load Bank2
+ //P2195 O2 Sensor Signal Stuck Lean Bank1 Sensor 1
+ //P2196 O2 Sensor Signal Stuck Rich Bank1 Sensor 1
+ //P2197 O2 Sensor Signal Stuck Lean Bank2 Sensor 1
+ //P2198 O2 Sensor Signal Stuck Rich Bank2 Sensor 1
+ //P2199 Intake Air Temp Sensor 1 / 2 Correlation
+ //P2200 NOx Sensor Circ Bank1
+ //P2201 NOx Sensor Circ Range/Perf Bank1
+ //P2202 NOx Sensor Circ Low Input Bank1
+ //P2203 NOx Sensor Circ High Input Bank1
+ //P2204 NOx Sensor Circ Interm Input Bank1
+ //P2205 NOx Sensor Heater Ctrl Circ/Open Bank1
+ //P2206 NOx Sensor Heater Ctrl Circ Low Bank1
+ //P2207 NOx Sensor Heater Ctrl Circ High Bank1
+ //P2208 NOx Sensor Heater Sense Circ Bank1
+ //P2209 NOx Sensor Heater Sense Circ Range/Perf Bank1
+ //P2210 NOx Sensor Heater Sense Circ Low Input Bank1
+ //P2211 NOx Sensor Heater Sense Circ High Input Bank1
+ //P2212 NOx Sensor Heater Sense Circ Interm Bank1
+ //P2213 NOx Sensor Circ Bank2
+ //P2214 NOx Sensor Circ Range/Perf Bank2
+ //P2215 NOx Sensor Circ Low Input Bank2
+ //P2216 NOx Sensor Circ High Input Bank2
+ //P2217 NOx Sensor Circ Interm Input Bank2
+ //P2218 NOx Sensor Heater Ctrl Circ/Open Bank2
+ //P2219 NOx Sensor Heater Ctrl Circ Low Bank2
+ //P2220 NOx Sensor Heater Ctrl Circ High Bank2
+ //P2221 NOx Sensor Heater Sense Circ Bank2
+ //P2222 NOx Sensor Heater Sense Circ Range/Perf Bank2
+ //P2223 NOx Sensor Heater Sense Circ Low Bank2
+ //P2224 NOx Sensor Heater Sense Circ High Bank2
+ //P2225 NOx Sensor Heater Sense Circ Interm Bank2
+ //P2226 Barometric Press Circ
+ //P2227 Barometric Press Circ Range/Perf
+ //P2228 Barometric Press Circ Low
+ //P2229 Barometric Press Circ High
+ //P2230 Barometric Press Circ Interm
+ //P2231 O2 Sensor Signal Circ Shorted to Heater Circ Bank1 Sensor 1
+ //P2232 O2 Sensor Signal Circ Shorted to Heater Circ Bank1 Sensor 2
+ //P2233 O2 Sensor Signal Circ Shorted to Heater Circ Bank1 Sensor 3
+ //P2234 O2 Sensor Signal Circ Shorted to Heater Circ Bank2 Sensor 1
+ //P2235 O2 Sensor Signal Circ Shorted to Heater Circ Bank2 Sensor 2
+ //P2236 O2 Sensor Signal Circ Shorted to Heater Circ Bank2 Sensor 3
+ //P2237 O2 Sensor Positive Current Ctrl Circ/Open Bank1 Sensor 1
+ //P2238 O2 Sensor Positive Current Ctrl Circ Low Bank1 Sensor 1
+ //P2239 O2 Sensor Positive Current Ctrl Circ High Bank1 Sensor 1
+ //P2240 O2 Sensor Positive Current Ctrl Circ/Open Bank2 Sensor 1
+ //P2241 O2 Sensor Positive Current Ctrl Circ Low Bank2 Sensor 1
+ //P2242 O2 Sensor Positive Current Ctrl Circ High Bank2 Sensor 1
+ //P2243 O2 Sensor Ref Voltage Circ/Open Bank1 Sensor 1
+ //P2244 O2 Sensor Ref Voltage Perf Bank1 Sensor 1
+ //P2245 O2 Sensor Ref Voltage Circ Low Bank1 Sensor 1
+ //P2246 O2 Sensor Ref Voltage Circ High Bank1 Sensor 1
+ //P2247 O2 Sensor Ref Voltage Circ/Open Bank2 Sensor 1
+ //P2248 O2 Sensor Ref Voltage Perf Bank2 Sensor 1
+ //P2249 O2 Sensor Ref Voltage Circ Low Bank2 Sensor 1
+ //P2250 O2 Sensor Ref Voltage Circ High Bank2 Sensor 1
+ //P2251 O2 Sensor Negative Current Ctrl Circ/Open Bank1 Sensor 1
+ //P2252 O2 Sensor Negative Current Ctrl Circ Low Bank1 Sensor 1
+ //P2253 O2 Sensor Negative Current Ctrl Circ High Bank1 Sensor 1
+ //P2254 O2 Sensor Negative Current Ctrl Circ/Open Bank2 Sensor 1
+ //P2255 O2 Sensor Negative Current Ctrl Circ Low Bank2 Sensor 1
+ //P2256 O2 Sensor Negative Current Ctrl Circ High Bank2 Sensor 1
+ //P2257 Sec Air Inj Sys Ctrl A Circ Low
+ //P2258 Sec Air Inj Sys Ctrl A Circ High
+ //P2259 Sec Air Inj Sys Ctrl B Circ Low
+ //P2260 Sec Air Inj Sys Ctrl B Circ High
+ //P2261 T/S Charger Bypass Valve-Mechanical
+ //P2262 Turbo Boost Press Not Detected-Mechanical
+ //P2263 T/S Charger Boost Sys Perf
+ //P2264 Water in Fuel Sensor Circ
+ //P2265 Water in Fuel Sensor Circ Range/Perf
+ //P2266 Water in Fuel Sensor Circ Low
+ //P2267 Water in Fuel Sensor Circ High
+ //P2268 Water in Fuel Sensor Circ Interm
+ //P2269 Water in Fuel Condition
+ //P2270 O2 Sensor Signal Stuck Lean Bank1 Sensor 2
+ //P2271 O2 Sensor Signal Stuck Rich Bank1 Sensor 2
+ //P2272 O2 Sensor Signal Stuck Lean Bank2 Sensor 2
+ //P2273 O2 Sensor Signal Stuck Rich Bank2 Sensor 2
+ //P2274 O2 Sensor Signal Stuck Lean Bank1 Sensor 3
+ //P2275 O2 Sensor Signal Stuck Rich Bank1 Sensor 3
+ //P2276 O2 Sensor Signal Stuck Lean Bank2 Sensor 3
+ //P2277 O2 Sensor Signal Stuck Rich Bank2 Sensor 3
+ //P2278 O2 Sensor Signals Swapped Bank1 Sensor 3 / Bank2 Sensor 3
+ //P2279 Intake Air Sys Leak
+ //P2280 Air Flow Restriction / Air Leak Between Air Filter and MAF
+ //P2281 Air Leak Between MAF and Throttle Body
+ //P2282 Air Leak Between Throttle Body and Intake Valves
+ //P2283 Injector Ctrl Press Sensor Circ
+ //P2284 Injector Ctrl Press Sensor Circ Range/Perf
+ //P2285 Injector Ctrl Press Sensor Circ Low
+ //P2286 Injector Ctrl Press Sensor Circ High
+ //P2287 Injector Ctrl Press Sensor Circ Interm
+ //P2288 Injector Ctrl Press Too High
+ //P2289 Injector Ctrl Press Too High-Engine Off
+ //P2290 Injector Ctrl Press Too Low
+ //P2291 Injector Ctrl Press Too Low-Engine Cranking
+ //P2292 Injector Ctrl Press Erratic
+ //P2293 Fuel Press Reg 2 Perf
+ //P2294 Fuel Press Reg 2 Ctrl Circ
+ //P2295 Fuel Press Reg 2 Ctrl Circ Low
+ //P2296 Fuel Press Reg 2 Ctrl Circ High
+ //P2297 O2 Sensor Out of Range During Deceleration Bank1 Sensor 1
+ //P2298 O2 Sensor Out of Range During Deceleration Bank2 Sensor 1
+ //P2299 Brake Pedal Pos / Accelerator Pedal Pos Incompatible
+ //P2300 Ignition Coil A Pri Ctrl Circ Low
+ //P2301 Ignition Coil A Pri Ctrl Circ High
+ //P2302 Ignition Coil A Sec Circ
+ //P2303 Ignition Coil B Pri Ctrl Circ Low
+ //P2304 Ignition Coil B Pri Ctrl Circ High
+ //P2305 Ignition Coil B Sec Circ
+ //P2306 Ignition Coil C Pri Ctrl Circ Low
+ //P2307 Ignition Coil C Pri Ctrl Circ High
+ //P2308 Ignition Coil C Sec Circ
+ //P2309 Ignition Coil D Pri Ctrl Circ Low
+ //P2310 Ignition Coil D Pri Ctrl Circ High
+ //P2311 Ignition Coil D Sec Circ
+ //P2312 Ignition Coil E Pri Ctrl Circ Low
+ //P2313 Ignition Coil E Pri Ctrl Circ High
+ //P2314 Ignition Coil E Sec Circ
+ //P2315 Ignition Coil F Pri Ctrl Circ Low
+ //P2316 Ignition Coil F Pri Ctrl Circ High
+ //P2317 Ignition Coil F Sec Circ
+ //P2318 Ignition Coil G Pri Ctrl Circ Low
+ //P2319 Ignition Coil G Pri Ctrl Circ High
+ //P2320 Ignition Coil G Sec Circ
+ //P2321 Ignition Coil H Pri Ctrl Circ Low
+ //P2322 Ignition Coil H Pri Ctrl Circ High
+ //P2323 Ignition Coil H Sec Circ
+ //P2324 Ignition Coil I Pri Ctrl Circ Low
+ //P2325 Ignition Coil I Pri Ctrl Circ High
+ //P2326 Ignition Coil I Sec Circ
+ //P2327 Ignition Coil J Pri Ctrl Circ Low
+ //P2328 Ignition Coil J Pri Ctrl Circ High
+ //P2329 Ignition Coil J Sec Circ
+ //P2330 Ignition Coil K Pri Ctrl Circ Low
+ //P2331 Ignition Coil K Pri Ctrl Circ High
+ //P2332 Ignition Coil K Sec Circ
+ //P2333 Ignition Coil L Pri Ctrl Circ Low
+ //P2334 Ignition Coil L Pri Ctrl Circ High
+ //P2335 Ignition Coil L Sec Circ
+ //P2336 Cylinder #1 Above Knock Threshold
+ //P2337 Cylinder #2 Above Knock Threshold
+ //P2338 Cylinder #3 Above Knock Threshold
+ //P2339 Cylinder #4 Above Knock Threshold
+ //P2340 Cylinder #5 Above Knock Threshold
+ //P2341 Cylinder #6 Above Knock Threshold
+ //P2342 Cylinder #7 Above Knock Threshold
+ //P2343 Cylinder #8 Above Knock Threshold
+ //P2344 Cylinder #9 Above Knock Threshold
+ //P2345 Cylinder #10 Above Knock Threshold
+ //P2346 Cylinder #11 Above Knock Threshold
+ //P2347 Cylinder #12 Above Knock Threshold
+ //P2400 EVAP Leak Detection Pump Ctrl Circ/Open
+ //P2401 EVAP Leak Detection Pump Ctrl Circ Low
+ //P2402 EVAP Leak Detection Pump Ctrl Circ High
+ //P2403 EVAP Leak Detection Pump Sense Circ/Open
+ //P2404 EVAP Leak Detection Pump Sense Circ Range/Perf
+ //P2405 EVAP Leak Detection Pump Sense Circ Low
+ //P2406 EVAP Leak Detection Pump Sense Circ High
+ //P2407 EVAP Leak Detection Pump Sense Circ Interm/Erratic
+ //P2408 Fuel Cap Sensor/Switch Circ
+ //P2409 Fuel Cap Sensor/Switch Circ Range/Perf
+ //P2410 Fuel Cap Sensor/Switch Circ Low
+ //P2411 Fuel Cap Sensor/Switch Circ High
+ //P2412 Fuel Cap Sensor/Switch Circ Interm/Erratic
+ //P2413 Exhaust Gas Recirculation Sys Perf
+ //P2414 O2 Sensor Exhaust Sample Error Bank1 Sensor 1
+ //P2415 O2 Sensor Exhaust Sample Error Bank2 Sensor 1
+ //P2416 O2 Sensor Signals Swapped Bank1 Sensor 2 / Bank1 Sensor 3
+ //P2417 O2 Sensor Signals Swapped Bank2 Sensor 2 / Bank2 Sensor 3
+ //P2418 EVAP Switching Valve Ctrl Circ /Open
+ //P2419 EVAP Switching Valve Ctrl Circ Low
+ //P2420 EVAP Switching Valve Ctrl Circ High
+ //P2421 EVAP Vent Valve Stuck Open
+ //P2422 EVAP Vent Valve Stuck Closed
+ //P2423 HC Adsorption Catalyst Efficiency Below Threshold Bank1
+ //P2424 HC Adsorption Catalyst Efficiency Below Threshold Bank2
+ //P2425 Exhaust Gas Recirculation Cooling Valve Ctrl Circ/Open
+ //P2426 Exhaust Gas Recirculation Cooling Valve Ctrl Circ Low
+ //P2427 Exhaust Gas Recirculation Cooling Valve Ctrl Circ High
+ //P2428 Exhaust Gas Temp Too High Bank1
+ //P2429 Exhaust Gas Temp Too High Bank2
+ //P2430 Sec Air Inj Sys Air Flow/Press Sensor Circ Bank1
+ //P2431 Sec Air Inj Sys Air Flow/Press Sensor Circ Range/Perf Bank1
+ //P2432 Sec Air Inj Sys Air Flow/Press Sensor Circ Low Bank1
+ //P2433 Sec Air Inj Sys Air Flow/Press Sensor Circ High Bank1
+ //P2434 Sec Air Inj Sys Air Flow/Press Sensor Circ Interm/Erratic Bank1
+ //P2435 Sec Air Inj Sys Air Flow/Press Sensor Circ Bank2
+ //P2436 Sec Air Inj Sys Air Flow/Press Sensor Circ Range/Perf Bank2
+ //P2437 Sec Air Inj Sys Air Flow/Press Sensor Circ Low Bank2
+ //P2438 Sec Air Inj Sys Air Flow/Press Sensor Circ High Bank2
+ //P2439 Sec Air Inj Sys Air Flow/Press Sensor Circ Interm/Eratic Bank2
+ //P2440 Sec Air Inj Sys Switching Valve Stuck Open Bank1
+ //P2441 Sec Air Inj Sys Switching Valve Stuck Closed Bank1
+ //P2442 Sec Air Inj Sys Switching Valve Stuck Open Bank2
+ //P2443 Sec Air Inj Sys Switching Valve Stuck Closed Bank2
+ //P2444 Sec Air Inj Sys Pump Stuck On Bank1
+ //P2445 Sec Air Inj Sys Pump Stuck Off Bank1
+ //P2446 Sec Air Inj Sys Pump Stuck On Bank2
+ //P2447 Sec Air Inj Sys Pump Stuck Off Bank2
+ //P2453 Particulate Matter Trap Differential Pressure Sensor Signal Performance
+ //P2455 DPF Differential Pressure Sensor Short to Voltage
+ //P2500 Generator Lamp/L-Terminal Circ Low
+ //P2501 Generator Lamp/L-Terminal Circ High
+ //P2502 Charging Sys Voltage
+ //P2503 Charging Sys Voltage Low
+ //P2504 Charging Sys Voltage High
+ //P2505 ECM/PCM Power Input Signal
+ //P2506 ECM/PCM Power Input Signal Range/Perf
+ //P2507 ECM/PCM Power Input Signal Low
+ //P2508 ECM/PCM Power Input Signal High
+ //P2509 ECM/PCM Power Input Signal Interm
+ //P2510 ECM/PCM Power Relay Sense Circ Range/Perf
+ //P2511 ECM/PCM Power Relay Sense Circ Interm
+ //P2512 Event Data Recorder Request Circ/ Open
+ //P2513 Event Data Recorder Request Circ Low
+ //P2514 Event Data Recorder Request Circ High
+ //P2515 A/C Refrigerant Press SensorB Circ
+ //P2516 A/C Refrigerant Press SensorB Circ Range/Perf
+ //P2517 A/C Refrigerant Press SensorB Circ Low
+ //P2518 A/C Refrigerant Press SensorB Circ High
+ //P2519 A/C Request A Circ
+ //P2520 A/C Request A Circ Low
+ //P2521 A/C Request A Circ High
+ //P2522 A/C Request B Circ
+ //P2523 A/C Request B Circ Low
+ //P2524 A/C Request B Circ High
+ //P2525 Vacuum Reservoir Press Sensor Circ
+ //P2526 Vacuum Reservoir Press Sensor Circ Range/Perf
+ //P2527 Vacuum Reservoir Press Sensor Circ Low
+ //P2528 Vacuum Reservoir Press Sensor Circ High
+ //P2529 Vacuum Reservoir Press Sensor Circ Interm
+ //P2530 Ignition Switch Run Pos Circ
+ //P2531 Ignition Switch Run Pos Circ Low
+ //P2532 Ignition Switch Run Pos Circ High
+ //P2533 Ignition Switch Run/Start Pos Circ
+ //P2534 Ignition Switch Run/Start Pos Circ Low
+ //P2535 Ignition Switch Run/Start Pos Circ High
+ //P2536 Ignition Switch Accessory Pos Circ
+ //P2537 Ignition Switch Accessory Pos Circ Low
+ //P2538 Ignition Switch Accessory Pos Circ High
+ //P2539 Low Press Fuel Sys Sensor Circ
+ //P2540 Low Press Fuel Sys Sensor Circ Range/Perf
+ //P2541 Low Press Fuel Sys Sensor Circ Low
+ //P2542 Low Press Fuel Sys Sensor Circ High
+ //P2543 Low Press Fuel Sys Sensor Circ Interm
+ //P2544 Torque Mgmt Request Input Signal A
+ //P2545 Torque Mgmt Request Input Signal A Range/Perf
+ //P2546 Torque Mgmt Request Input Signal A Low
+ //P2547 Torque Mgmt Request Input Signal A High
+ //P2548 Torque Mgmt Request Input Signal B
+ //P2549 Torque Mgmt Request Input Signal B Range/Perf
+ //P2550 Torque Mgmt Request Input Signal B Low
+ //P2551 Torque Mgmt Request Input Signal B High
+ //P2552 Throttle/Fuel Inhibit Circ
+ //P2553 Throttle/Fuel Inhibit Circ Range/Perf
+ //P2554 Throttle/Fuel Inhibit Circ Low
+ //P2555 Throttle/Fuel Inhibit Circ High
+ //P2556 Engine Coolant Level Sensor/Switch Circ
+ //P2557 Engine Coolant Level Sensor/Switch Circ Range/Perf
+ //P2558 Engine Coolant Level Sensor/Switch Circ Low
+ //P2559 Engine Coolant Level Sensor/Switch Circ High
+ //P2560 Engine Coolant Level Low
+ //P2561 A/C Ctrl Mod Requested MIL Illumination
+ //P2562 Turbocharger Boost Ctrl Pos Sensor Circ
+ //P2563 Turbocharger Boost Ctrl Pos Sensor Circ Range/Perf
+ //P2564 Turbocharger Boost Ctrl Pos Sensor Circ Low
+ //P2565 Turbocharger Boost Ctrl Pos Sensor Circ High
+ //P2566 Turbocharger Boost Ctrl Pos Sensor Circ Interm
+ //P2567 Direct Ozone Reduction Catalyst Temp Sensor Circ
+ //P2568 Direct Ozone Reduction Catalyst Temp Sensor Circ Range/Perf
+ //P2569 Direct Ozone Reduction Catalyst Temp Sensor Circ Low
+ //P2570 Direct Ozone Reduction Catalyst Temp Sensor Circ High
+ //P2571 Direct Ozone Reduction Catalyst Temp Sensor Circ Interm/Erratic4
+ //P2572 Direct Ozone Reduction Catalyst Deterioration Sensor Circ4
+ //P2573 Direct Ozone Reduction Catalyst Deterioration Sensor Circ Range/Perf4
+ //P2574 Direct Ozone Reduction Catalyst Deterioration Sensor Circ Low4
+ //P2575 Direct Ozone Reduction Catalyst Deterioration Sensor Circ High4
+ //P2576 Direct Ozone Reduction Catalyst Deterioration Sensor Circ Interm/Erratic
+ //P2577 Direct Ozone Reduction Catalyst Efficiency Below Threshold
+ //P2600 Coolant Pump Ctrl Circ/Open
+ //P2601 Coolant Pump Ctrl Circ Range/Perf
+ //P2602 Coolant Pump Ctrl Circ Low
+ //P2603 Coolant Pump Ctrl Circ High
+ //P2604 Intake Air Heater A Circ Range/Perf
+ //P2605 Intake Air Heater A Circ/Open
+ //P2606 Intake Air Heater B Circ Range/Perf
+ //P2607 Intake Air Heater B Circ Low
+ //P2608 Intake Air Heater B Circ High
+ //P2609 Intake Air Heater Sys Perf
+ //P2610 ECM/PCM Internal Engine Off Timer Perf
+ //P2611 A/C Refrigerant Distribution Valve Ctrl Circ/Open
+ //P2612 A/C Refrigerant Distribution Valve Ctrl Circ Low
+ //P2613 A/C Refrigerant Distribution Valve Ctrl Circ High
+ //P2614 Camshaft Pos Signal Output Circ/Open
+ //P2615 Camshaft Pos Signal Output Circ Low
+ //P2616 Camshaft Pos Signal Output Circ High
+ //P2617 Crank Pos Signal Output Circ/Open
+ //P2618 Crank Pos Signal Output Circ Low
+ //P2619 Crank Pos Signal Output Circ High
+ //P2620 Throttle Pos Output Circ/Open
+ //P2621 Throttle Pos Output Circ Low
+ //P2622 Throttle Pos Output Circ High
+ //P2623 Injector Ctrl Press Reg Circ/Open
+ //P2624 Injector Ctrl Press Reg Circ Low
+ //P2625 Injector Ctrl Press Reg Circ High
+ //P2626 O2 Sensor Pumping Current Trim Circ/Open Bank1 Sensor 1
+ //P2627 O2 Sensor Pumping Current Trim Circ Low Bank1 Sensor 1
+ //P2628 O2 Sensor Pumping Current Trim Circ High Bank1 Sensor 1
+ //P2629 O2 Sensor Pumping Current Trim Circ/Open Bank2 Sensor 1
+ //P2630 O2 Sensor Pumping Current Trim Circ Low Bank2 Sensor 1
+ //P2631 O2 Sensor Pumping Current Trim Circ High Bank2 Sensor 1
+ //P2632 Fuel Pump B Ctrl Circ /Open
+ //P2633 Fuel Pump B Ctrl Circ Low
+ //P2634 Fuel Pump B Ctrl Circ High
+ //P2635 Fuel Pump A Low Flow / Perf
+ //P2636 Fuel Pump B Low Flow / Perf
+ //P2637 Torque Mgmt Feedback Signal A
+ //P2638 Torque Mgmt Feedback Signal A Range/Perf
+ //P2639 Torque Mgmt Feedback Signal A Low
+ //P2640 Torque Mgmt Feedback Signal A High
+ //P2641 Torque Mgmt Feedback Signal B
+ //P2642 Torque Mgmt Feedback Signal B Range/Perf
+ //P2643 Torque Mgmt Feedback Signal B Low
+ //P2644 Torque Mgmt Feedback Signal B High
+ //P2645 A Rocker Arm Actuator Ctrl Circ/Open Bank1
+ //P2646 A Rocker Arm Actuator Sys Perf or Stuck Off Bank1
+ //P2647 A Rocker Arm Actuator Sys Stuck On Bank1
+ //P2648 A Rocker Arm Actuator Ctrl Circ Low Bank1
+ //P2649 A Rocker Arm Actuator Ctrl Circ High Bank1
+ //P2650 B Rocker Arm Actuator Ctrl Circ/Open Bank1
+ //P2651 B Rocker Arm Actuator Sys Perf or Stuck Off Bank1
+ //P2652 B Rocker Arm Actuator Sys Stuck On Bank1
+ //P2653 B Rocker Arm Actuator Ctrl Circ Low Bank1
+ //P2654 B Rocker Arm Actuator Ctrl Circ High Bank1
+ //P2655 A Rocker Arm Actuator Ctrl Circ/Open Bank2
+ //P2656 A Rocker Arm Actuator Sys Perf or Stuck Off Bank2
+ //P2657 A Rocker Arm Actuator Sys Stuck On Bank2
+ //P2658 A Rocker Arm Actuator Ctrl Circ Low Bank2
+ //P2659 A Rocker Arm Actuator Ctrl Circ High Bank2
+ //P2660 B Rocker Arm Actuator Ctrl Circ/Open Bank2
+ //P2661 B Rocker Arm Actuator Sys Perf or Stuck Off Bank2
+ //P2662 B Rocker Arm Actuator Sys Stuck On Bank2
+ //P2663 B Rocker Arm Actuator Ctrl Circ Low Bank2
+ //P2664 B Rocker Arm Actuator Ctrl Circ High Bank2
+ //P2665 Fuel Shutoff Valve B Ctrl Circ/Open
+ //P2666 Fuel Shutoff Valve B Ctrl Circ Low
+ //P2667 Fuel Shutoff Valve B Ctrl Circ High
+ //P2668 Fuel Mode Indicator Lamp Ctrl Circ
+ //P2669 Actuator Supply Voltage B Circ /Open
+
+
+
+ //P2670 Actuator Supply Voltage B Circ Low
+ //P2671 Actuator Supply Voltage B Circ High
+ //P2700 Trans Friction Element A Apply Time Range/Perf
+ //P2701 Trans Friction Element B Apply Time Range/Perf
+ //P2702 Trans Friction Element C Apply Time Range/Perf
+ //P2703 Trans Friction Element D Apply Time Range/Perf
+ //P2704 Trans Friction Element E Apply Time Range/Perf
+ //P2705 Trans Friction Element F Apply Time Range/Perf
+ //P2706 Shift Solenoid F
+ //P2707 Shift Solenoid F Perf or Stuck Off
+ //P2708 Shift Solenoid F Stuck On
+ //P2709 Shift Solenoid F Electrical
+ //P2710 Shift Solenoid F Interm
+ //P2711 Unexpected Mechanical Gear Disengagement
+ //P2712 Hydraulic Power Unit Leakage
+ //P2713 Press Ctrl Solenoid D
+ //P2714 Press Ctrl Solenoid D Perf or Stuck Off
+ //P2715 Press Ctrl Solenoid D Stuck On
+ //P2716 Press Ctrl Solenoid D Electrical
+ //P2717 Press Ctrl Solenoid D Interm
+ //P2718 Press Ctrl Solenoid D Ctrl Circ / Open
+ //P2719 Press Ctrl Solenoid D Ctrl Circ Range/Perf
+ //P2720 Press Ctrl Solenoid D Ctrl Circ Low
+ //P2721 Press Ctrl Solenoid D Ctrl Circ High
+ //P2722 Press Ctrl Solenoid E
+ //P2723 Press Ctrl Solenoid E Perf or Stuck Off
+ //P2724 Press Ctrl Solenoid E Stuck On
+ //P2725 Press Ctrl Solenoid E Electrical
+ //P2726 Press Ctrl Solenoid E Interm
+ //P2727 Press Ctrl Solenoid E Ctrl Circ / Open
+ //P2728 Press Ctrl Solenoid E Ctrl Circ Range/Perf
+ //P2729 Press Ctrl Solenoid E Ctrl Circ Low
+ //P2730 Press Ctrl Solenoid E Ctrl Circ High
+ //P2731 Press Ctrl Solenoid F
+ //P2732 Press Ctrl Solenoid F Perf or Stuck Off
+ //P2733 Press Ctrl Solenoid F Stuck On
+ //P2734 Press Ctrl Solenoid F Electrical
+ //P2735 Press Ctrl Solenoid F Interm
+ //P2736 Press Ctrl Solenoid F Ctrl Circ/Open
+ //P2737 Press Ctrl Solenoid F Ctrl Circ Range/Perf
+ //P2738 Press Ctrl Solenoid F Ctrl Circ Low
+ //P2739 Press Ctrl Solenoid F Ctrl Circ High
+ //P2740 Trans Fluid Temp SensorB Circ
+ //P2741 Trans Fluid Temp SensorB Circ Range Perf
+ //P2742 Trans Fluid Temp SensorB Circ Low
+ //P2743 Trans Fluid Temp SensorB Circ High
+ //P2744 Trans Fluid Temp SensorB Circ Interm
+ //P2745 Intermediate Shaft Speed SensorB Circ
+ //P2746 Intermediate Shaft Speed SensorB Circ Range/Perf
+ //P2747 Intermediate Shaft Speed SensorB Circ No Signal
+ //P2748 Intermediate Shaft Speed SensorB Circ Interm
+ //P2749 Intermediate Shaft Speed Sensor C Circ
+ //P2750 Intermediate Shaft Speed Sensor C Circ Range/Perf
+ //P2751 Intermediate Shaft Speed Sensor C Circ No Signal
+ //P2752 Intermediate Shaft Speed Sensor C Circ Interm
+ //P2753 Trans Fluid Cooler Ctrl Circ/Open
+ //P2754 Trans Fluid Cooler Ctrl Circ Low
+ //P2755 Trans Fluid Cooler Ctrl Circ High
+ //P2756 Torq Conv Clutch Press Ctrl Solenoid
+ //P2757 Torq Conv Clutch Press Ctrl Solenoid Ctrl Circ Perf or Stuck Off
+ //P2758 Torq Conv Clutch Press Ctrl Solenoid Ctrl Circ Stuck On
+ //P2759 Torq Conv Clutch Press Ctrl Solenoid Ctrl Circ Electrical
+ //P2760 Torq Conv Clutch Press Ctrl Solenoid Ctrl Circ Interm
+ //P2761 Torq Conv Clutch Press Ctrl Solenoid Ctrl Circ/Open
+ //P2762 Torq Conv Clutch Press Ctrl Solenoid Ctrl Circ Range/Perf
+ //P2763 Torq Conv Clutch Press Ctrl Solenoid Ctrl Circ High
+ //P2764 Torq Conv Clutch Press Ctrl Solenoid Ctrl Circ Low
+ //P2765 Input/Turbine Speed SensorB Circ
+ //P2766 Input/Turbine Speed SensorB Circ Range/Perf
+ //P2767 Input/Turbine Speed SensorB Circ No Signal
+ //P2768 Input/Turbine Speed SensorB Circ Interm
+ //P2769 Torq Conv Clutch Circ Low
+ //P2770 Torq Conv Clutch Circ High
+ //P2771 4WD Low Switch Circ
+ //P2772 4WD Low Switch Circ Range/Perf
+ //P2773 4WD Low Switch Circ Low
+ //P2774 4WD Low Switch Circ High
+ //P2775 Upshift Switch Circ Range/Perf
+ //P2776 Upshift Switch Circ Low
+ //P2777 Upshift Switch Circ High
+ //P2778 Upshift Switch Circ Interm/Erratic
+ //P2779 Downshift Switch Circ Range/Perf
+ //P2780 Downshift Switch Circ Low
+ //P2781 Downshift Switch Circ High
+ //P2782 Downshift Switch Circ Interm/Erratic
+ //P2783 Torq Conv Temp Too High
+ //P2784 Input/Turbine Speed SensorA/B Correlation
+ //P2785 Clutch Actuator Temp Too High
+ //P2786 Gear Shift Actuator Temp Too High
+ //P2787 Clutch Temp Too High
+ //P2788 Auto Shift Manual Adaptive Learning at Limit
+ //P2789 Clutch Adaptive Learning at Limit
+ //P2790 Gate Select Direction Circ
+ //P2791 Gate Select Direction Circ Low
+ //P2792 Gate Select Direction Circ High
+ //P2793 Gear Shift Direction Circ
+ //P2794 Gear Shift Direction Circ Low
+ //P2795 Gear Shift Direction Circ High
+ //P2A00 O2 Sensor Circ Range/Perf Bank1 Sensor 1
+ //P2A01 O2 Sensor Circ Range/Perf Bank1 Sensor 2
+ //P2A02 O2 Sensor Circ Range/Perf Bank1 Sensor 3
+ //P2A03 O2 Sensor Circ Range/Perf Bank2 Sensor 1
+ //P2A04 O2 Sensor Circ Range/Perf Bank2 Sensor 2
+ //P2A05 O2 Sensor Circ Range/Perf Bank2 Sensor 3
+ //P3xxx Generic Powertrain Diagnostic Codes DTC
+ //P3400 Cylinder Deactivation Sys Bank1
+ //P3401 Cyl1 Deactivation/Intake Valve Ctrl Circ/Open
+ //P3402 Cyl1 Deactivation/Intake Valve Ctrl Perf
+ //P3403 Cyl1 Deactivation/Intake Valve Ctrl Circ Low
+ //P3404 Cyl1 Deactivation/Intake Valve Ctrl Circ High
+ //P3405 Cyl1 Exhaust Valve Ctrl Circ/Open
+ //P3406 Cyl1 Exhaust Valve Ctrl Perf
+ //P3407 Cyl1 Exhaust Valve Ctrl Circ Low
+ //P3408 Cyl1 Exhaust Valve Ctrl Circ High
+ //P3409 Cyl2 Deactivation/Intake Valve Ctrl Circ/Open
+ //P3410 Cyl2 Deactivation/Intake Valve Ctrl Perf
+ //P3411 Cyl2 Deactivation/Intake Valve Ctrl Circ Low
+ //P3412 Cyl2 Deactivation/Intake Valve Ctrl Circ High
+ //P3413 Cyl2 Exhaust Valve Ctrl Circ/Open
+ //P3414 Cyl2 Exhaust Valve Ctrl Perf
+ //P3415 Cyl2 Exhaust Valve Ctrl Circ Low
+ //P3416 Cyl2 Exhaust Valve Ctrl Circ High
+ //P3417 Cyl3 Deactivation/Intake Valve Ctrl Circ/Open
+ //P3418 Cyl3 Deactivation/Intake Valve Ctrl Perf
+ //P3419 Cyl3 Deactivation/Intake Valve Ctrl Circ Low
+ //P3420 Cyl3 Deactivation/Intake Valve Ctrl Circ High
+ //P3421 Cyl3 Exhaust Valve Ctrl Circ/Open
+ //P3422 Cyl3 Exhaust Valve Ctrl Perf
+ //P3423 Cyl3 Exhaust Valve Ctrl Circ Low
+ //P3424 Cyl3 Exhaust Valve Ctrl Circ High
+ //P3425 Cyl4 Deactivation/Intake Valve Ctrl Circ/Open
+ //P3426 Cyl4 Deactivation/Intake Valve Ctrl Perf
+ //P3427 Cyl4 Deactivation/Intake Valve Ctrl Circ Low
+ //P3428 Cyl4 Deactivation/Intake Valve Ctrl Circ High
+ //P3429 Cyl4 Exhaust Valve Ctrl Circ/Open
+ //P3430 Cyl4 Exhaust Valve Ctrl Perf
+ //P3431 Cyl4 Exhaust Valve Ctrl Circ Low
+ //P3432 Cyl4 Exhaust Valve Ctrl Circ High
+ //P3433 Cyl5 Deactivation/Intake Valve Ctrl Circ/Open
+ //P3434 Cyl5 Deactivation/Intake Valve Ctrl Perf
+ //P3435 Cyl5 Deactivation/Intake Valve Ctrl Circ Low
+ //P3436 Cyl5 Deactivation/Intake Valve Ctrl Circ High
+ //P3437 Cyl5 Exhaust Valve Ctrl Circ/Open
+ //P3438 Cyl5 Exhaust Valve Ctrl Perf
+ //P3439 Cyl5 Exhaust Valve Ctrl Circ Low
+ //P3440 Cyl5 Exhaust Valve Ctrl Circ High
+ //P3441 Cyl6 Deactivation/Intake Valve Ctrl Circ/Open
+ //P3442 Cyl6 Deactivation/Intake Valve Ctrl Perf
+ //P3443 Cyl6 Deactivation/Intake Valve Ctrl Circ Low
+ //P3444 Cyl6 Deactivation/Intake Valve Ctrl Circ High
+ //P3445 Cyl6 Exhaust Valve Ctrl Circ/Open
+ //P3446 Cyl6 Exhaust Valve Ctrl Perf
+ //P3447 Cyl6 Exhaust Valve Ctrl Circ Low
+ //P3448 Cyl6 Exhaust Valve Ctrl Circ High
+ //P3449 Cyl7 Deactivation/Intake Valve Ctrl Circ/Open
+ //P3450 Cyl7 Deactivation/Intake Valve Ctrl Perf
+ //P3451 Cyl7 Deactivation/Intake Valve Ctrl Circ Low
+ //P3452 Cyl7 Deactivation/Intake Valve Ctrl Circ High
+ //P3453 Cyl7 Exhaust Valve Ctrl Circ/Open
+ //P3454 Cyl7 Exhaust Valve Ctrl Perf
+ //P3455 Cyl7 Exhaust Valve Ctrl Circ Low
+ //P3456 Cyl7 Exhaust Valve Ctrl Circ High
+ //P3457 Cyl8 Deactivation/Intake Valve Ctrl Circ/Open
+ //P3458 Cyl8 Deactivation/Intake Valve Ctrl Perf
+ //P3459 Cyl8 Deactivation/Intake Valve Ctrl Circ Low
+ //P3460 Cyl8 Deactivation/Intake Valve Ctrl Circ High
+ //P3461 Cyl8 Exhaust Valve Ctrl Circ/Open
+ //P3462 Cyl8 Exhaust Valve Ctrl Perf
+ //P3463 Cyl8 Exhaust Valve Ctrl Circ Low
+ //P3464 Cyl8 Exhaust Valve Ctrl Circ High
+ //P3465 Cyl9 Deactivation/Intake Valve Ctrl Circ/Open
+ //P3466 Cyl9 Deactivation/Intake Valve Ctrl Perf
+ //P3467 Cyl9 Deactivation/Intake Valve Ctrl Circ Low
+ //P3468 Cyl9 Deactivation/Intake Valve Ctrl Circ High
+ //P3469 Cyl9 Exhaust Valve Ctrl Circ/Open
+ //P3470 Cyl9 Exhaust Valve Ctrl Perf
+ //P3471 Cyl9 Exhaust Valve Ctrl Circ Low
+ //P3472 Cyl9 Exhaust Valve Ctrl Circ High
+ //P3473 Cyl10 Deactivation/Intake Valve Ctrl Circ/Open
+ //P3474 Cyl10 Deactivation/Intake Valve Ctrl Perf
+ //P3475 Cyl10 Deactivation/Intake Valve Ctrl Circ Low
+ //P3476 Cyl10 Deactivation/Intake Valve Ctrl Circ High
+ //P3477 Cyl10 Exhaust Valve Ctrl Circ/Open
+ //P3478 Cyl10 Exhaust Valve Ctrl Perf
+ //P3479 Cyl10 Exhaust Valve Ctrl Circ Low
+ //P3480 Cyl10 Exhaust Valve Ctrl Circ High
+ //P3481 Cyl11 Deactivation/Intake Valve Ctrl Circ/Open
+ //P3482 Cyl11 Deactivation/Intake Valve Ctrl Perf
+ //P3483 Cyl11 Deactivation/Intake Valve Ctrl Circ Low
+ //P3484 Cyl11 Deactivation/Intake Valve Ctrl Circ High
+ //P3485 Cyl11 Exhaust Valve Ctrl Circ/Open
+ //P3486 Cyl11 Exhaust Valve Ctrl Perf
+ //P3487 Cyl11 Exhaust Valve Ctrl Circ Low
+ //P3488 Cyl11 Exhaust Valve Ctrl Circ High
+ //P3489 Cyl12 Deactivation/Intake Valve Ctrl Circ/Open
+ //P3490 Cyl12 Deactivation/Intake Valve Ctrl Perf
+ //P3491 Cyl12 Deactivation/Intake Valve Ctrl Circ Low
+ //P3492 Cyl12 Deactivation/Intake Valve Ctrl Circ High
+ //P3493 Cyl12 Exhaust Valve Ctrl Circ/Open
+
+ // this is needed for proper enum size, this matters for malfunction_central
+ Internal_ForceMyEnumIntSize_cranking_obd_code = ENUM_SIZE_HACK,
+} obd_code_e;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+
+#endif /* OBD_ERROR_CODES_H_ */
diff --git a/firmware/controllers/algo/rusefi_enums.h b/firmware/controllers/algo/rusefi_enums.h
new file mode 100644
index 0000000000..4695f5a47f
--- /dev/null
+++ b/firmware/controllers/algo/rusefi_enums.h
@@ -0,0 +1,429 @@
+/**
+ * @file rusefi_enums.h
+ * @brief Fundamental rusEfi enumerable types live here
+ *
+ * @note this file should probably not include any other files
+ *
+ * @date Jan 14, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef RUSEFI_ENUMS_H_
+#define RUSEFI_ENUMS_H_
+
+#include "efifeatures.h"
+
+// for now I want all enums to be 32 bit integers. At some point maybe we will make the one-byte
+// this is about offsets and sizes in TunerStudio
+#define ENUM_SIZE_HACK 2000000000
+
+#define DIGIPOT_COUNT 4
+
+typedef enum {
+ AUDI_AAN = 1,
+#if EFI_SUPPORT_DODGE_NEON
+ /**
+ * 1995 Dodge Neon
+ * http://rusefi.com/forum/viewtopic.php?t=360
+ */
+ DODGE_NEON_1995 = 2,
+#endif /* EFI_SUPPORT_DODGE_NEON */
+#if EFI_SUPPORT_FORD_ASPIRE
+ /**
+ * 1996 1.3 Ford Aspire
+ * http://rusefi.com/forum/viewtopic.php?t=375
+ */
+ FORD_ASPIRE_1996 = 3,
+#endif /* EFI_SUPPORT_FORD_ASPIRE */
+#if EFI_SUPPORT_FORD_FIESTA
+ /**
+ * 36-1 toothed wheel engine
+ * http://rusefi.com/forum/viewtopic.php?t=282
+ */
+ FORD_FIESTA = 4,
+#endif /* EFI_SUPPORT_FORD_FIESTA */
+#if EFI_SUPPORT_NISSAN_PRIMERA
+ NISSAN_PRIMERA = 5,
+#endif /* EFI_SUPPORT_NISSAN_PRIMERA */
+
+ HONDA_ACCORD_CD = 6,
+
+ FORD_INLINE_6_1995 = 7,
+/**
+ * one cylinder engine
+ * 139qmb 50-90cc
+ * http://rusefi.com/forum/viewtopic.php?f=3&t=332
+ */
+ GY6_139QMB = 8,
+
+ MAZDA_MIATA_NB = 9,
+
+ ROVER_V8 = 10,
+
+ MAZDA_323 = 11,
+
+ SATURN_ION_2004 = 12,
+
+ MINI_COOPER_R50 = 13,
+
+ FORD_ESCORT_GT = 14,
+
+ CITROEN_TU3JP = 15,
+
+ MITSU_4G93 = 16,
+
+ /**
+ * a version of HONDA_ACCORD_CD which only uses two of three trigger input sensors
+ */
+ HONDA_ACCORD_CD_TWO_WIRES = 17,
+
+ HONDA_ACCORD_CD_DIP = 18,
+
+ MIATA_1990 = 19,
+ MIATA_1994 = 20,
+ MIATA_1996 = 21,
+
+ Force_4b_engine_type = ENUM_SIZE_HACK,
+} engine_type_e;
+
+typedef enum {
+ TT_TOOTHED_WHEEL = 0,
+ TT_FORD_ASPIRE = 1,
+ TT_DODGE_NEON = 2,
+ TT_MAZDA_MIATA_NA = 3,
+ TT_MAZDA_MIATA_NB = 4,
+ TT_GM_7X = 5,
+ TT_MINI_COOPER_R50 = 6,
+ TT_FORD_ESCORT_GT = 7,
+ TT_TOOTHED_WHEEL_60_2 = 8,
+ TT_TOOTHED_WHEEL_36_1 = 9,
+
+ TT_HONDA_ACCORD_CD = 10,
+
+ TT_MITSU = 11,
+
+ TT_HONDA_ACCORD_CD_TWO_WIRES = 12,
+
+ TT_HONDA_ACCORD_CD_DIP = 13,
+
+ Force_4b_trigger_type = ENUM_SIZE_HACK,
+} trigger_type_e;
+
+typedef enum {
+ ADC_OFF = 0,
+ ADC_SLOW = 1,
+ ADC_FAST = 2,
+
+ Force_4b_adc_channel_mode = ENUM_SIZE_HACK,
+} adc_channel_mode_e;
+
+// todo: better names?
+typedef enum {
+ TV_LOW = 0,
+ TV_HIGH = 1
+} trigger_value_e;
+
+// todo: better names?
+typedef enum {
+ T_PRIMARY = 0,
+ T_SECONDARY = 1,
+ // todo: I really do not want to call this 'tertiary'. maybe we should rename all of these?
+ T_CHANNEL_3 = 2
+} trigger_wheel_e;
+
+// todo: better names?
+typedef enum {
+ SHAFT_PRIMARY_DOWN = 0,
+ SHAFT_PRIMARY_UP = 1,
+ SHAFT_SECONDARY_DOWN = 2,
+ SHAFT_SECONDARY_UP = 3,
+ SHAFT_3RD_DOWN = 4,
+ SHAFT_3RD_UP = 5,
+} trigger_event_e;
+
+/**
+ * This enum is used to select your desired Engine Load calculation algorithm
+ */
+typedef enum {
+ /**
+ * raw Mass Air Flow sensor value algorithm. http://en.wikipedia.org/wiki/Mass_flow_sensor
+ */
+ LM_MAF = 0,
+ /**
+ * Throttle Position Sensor value is used as engine load. http://en.wikipedia.org/wiki/Throttle_position_sensor
+ * That's know as Alpha N
+ */
+ LM_TPS = 1,
+ /**
+ * raw Manifold Absolute Pressure sensor value is used as engine load http://en.wikipedia.org/wiki/MAP_sensor
+ */
+ LM_MAP = 2,
+ /**
+ * Speed Density algorithm - Engile Load is a function of MAP and ... TODO
+ * http://articles.sae.org/8539/
+ */
+ LM_SPEED_DENSITY = 3,
+
+ Force_4b_engine_load_mode = ENUM_SIZE_HACK,
+} engine_load_mode_e;
+
+typedef enum {
+ DM_NONE = 0,
+ DM_HD44780 = 1,
+ DM_HD44780_OVER_PCF8574 = 2,
+
+ Force_4b_display_mode = ENUM_SIZE_HACK,
+
+} display_mode_e;
+
+typedef enum {
+ LF_NATIVE = 0,
+ /**
+ * http://www.efianalytics.com/MegaLogViewer/
+ * log example: http://svn.code.sf.net/p/rusefi/code/trunk/misc/ms_logs/
+ */
+ LM_MLV = 1,
+
+ Force_4b_log_format = ENUM_SIZE_HACK,
+} log_format_e;
+
+
+typedef enum {
+ IM_AUTO = 0,
+ IM_MANUAL = 1,
+ Force_4b_idle_mode = ENUM_SIZE_HACK,
+} idle_mode_e;
+
+typedef enum {
+ /**
+ * GND for logical OFF, VCC for logical ON
+ */
+ OM_DEFAULT = 0,
+ /**
+ * GND for logical ON, VCC for logical OFF
+ */
+ OM_INVERTED = 1,
+ /**
+ * logical OFF is floating, logical ON is GND
+ */
+ OM_OPENDRAIN = 2,
+ OM_OPENDRAIN_INVERTED = 3,
+
+ Force_4b_pin_output_mode = ENUM_SIZE_HACK,
+} pin_output_mode_e;
+
+typedef enum {
+ PI_DEFAULT = 0,
+
+ Force_4b_pin_input_mode = ENUM_SIZE_HACK,
+} pin_input_mode_e;
+
+typedef enum {
+ FO_ONE_CYLINDER = 0,
+ FO_1_THEN_3_THEN_4_THEN2 = 1,
+ FO_1_THEN_2_THEN_4_THEN3 = 2,
+ FO_1_THEN_3_THEN_2_THEN4 = 3,
+ FO_1_THEN_5_THEN_3_THEN_6_THEN_2_THEN_4 = 4,
+ FO_1_8_4_3_6_5_7_2 = 5,
+ Force_4b_firing_order = ENUM_SIZE_HACK,
+} firing_order_e;
+
+// todo: better enum name
+typedef enum {
+ OM_NONE = 0,
+ FOUR_STROKE_CRANK_SENSOR = 1,
+ FOUR_STROKE_CAM_SENSOR = 2,
+
+ Force_4b_operation_mode_e = ENUM_SIZE_HACK,
+} operation_mode_e;
+
+/**
+ * @brief Ignition Mode
+ */
+typedef enum {
+ /**
+ * in this mode only SPARKOUT_1_OUTPUT is used
+ */
+ IM_ONE_COIL = 0,
+ /**
+ * in this mode we use as many coils as we have cylinders
+ */
+ IM_INDIVIDUAL_COILS = 1,
+ IM_WASTED_SPARK = 2,
+
+ Force_4b_ignition_mode = ENUM_SIZE_HACK,
+} ignition_mode_e;
+
+typedef enum {
+ IM_SIMULTANEOUS = 0,
+ IM_SEQUENTIAL = 1,
+ IM_BATCH = 2,
+
+ Force_4b_injection_mode = ENUM_SIZE_HACK,
+} injection_mode_e;
+
+/**
+ * @brief Ignition Mode while cranking
+ */
+typedef enum {
+ CIM_DEFAULT = 0,
+ CIM_FIXED_ANGLE = 1,
+
+ Force_4b_cranking_ignition_mode = ENUM_SIZE_HACK,
+} cranking_ignition_mode_e;
+
+typedef enum {
+ SPI_NONE = 0,
+ SPI_DEVICE_1 = 1,
+ SPI_DEVICE_2 = 2,
+ SPI_DEVICE_3 = 3,
+ SPI_DEVICE_4 = 4,
+
+ Force_4b_spi_device = ENUM_SIZE_HACK,
+} spi_device_e;
+
+typedef enum {
+ IE_NO_ERROR = 0,
+ IE_UNEXPECTED_FIRING_ORDER = 1,
+
+ Force_4b_cranking_internal_error = ENUM_SIZE_HACK,
+} internal_error_e;
+
+typedef enum {
+ EFI_ADC_0 = 0,
+ EFI_ADC_1 = 1,
+ EFI_ADC_2 = 2,
+ EFI_ADC_3 = 3,
+ EFI_ADC_4 = 4,
+ EFI_ADC_5 = 5,
+ EFI_ADC_6 = 6,
+ EFI_ADC_7 = 7,
+ EFI_ADC_8 = 8,
+ EFI_ADC_9 = 9,
+ EFI_ADC_10 = 10,
+ EFI_ADC_11 = 11,
+ EFI_ADC_12 = 12,
+ EFI_ADC_13 = 13,
+ EFI_ADC_14 = 14,
+ EFI_ADC_15 = 15,
+
+ EFI_ADC_ERROR = 999,
+
+ Force_4b_cranking_adc_channel = ENUM_SIZE_HACK,
+} adc_channel_e;
+
+
+typedef enum {
+ ES_BPSX_D1 = 0,
+ ES_Innovate_MTX_L = 1,
+
+ Force_4b_ego_sensor = ENUM_SIZE_HACK,
+} ego_sensor_e;
+
+
+
+/**
+ * Hardware pin. This enum is platform-specific.
+ */
+typedef enum {
+ GPIOA_0 = 0,
+ GPIOA_1 = 1,
+ GPIOA_2 = 2,
+ GPIOA_3 = 3,
+ GPIOA_4 = 4,
+ GPIOA_5 = 5,
+ GPIOA_6 = 6,
+ GPIOA_7 = 7,
+ GPIOA_8 = 8,
+ GPIOA_9 = 9,
+ GPIOA_10 = 10,
+ GPIOA_11 = 11,
+ GPIOA_12 = 12,
+ GPIOA_13 = 13,
+ GPIOA_14 = 14,
+ GPIOA_15 = 15,
+
+ GPIOB_0 = 16,
+ GPIOB_1 = 17,
+ GPIOB_2 = 18,
+ GPIOB_3 = 19,
+ GPIOB_4 = 20,
+ GPIOB_5 = 21,
+ GPIOB_6 = 22,
+ GPIOB_7 = 23,
+ GPIOB_8 = 24,
+ GPIOB_9 = 25,
+ GPIOB_10 = 26,
+ GPIOB_11 = 27,
+ GPIOB_12 = 28,
+ GPIOB_13 = 29,
+ GPIOB_14 = 30,
+ GPIOB_15 = 31,
+
+ GPIOC_0 = 32,
+ GPIOC_1 = 33,
+ GPIOC_2 = 34,
+ GPIOC_3 = 35,
+ GPIOC_4 = 36,
+ GPIOC_5 = 37,
+ GPIOC_6 = 38,
+ GPIOC_7 = 39,
+ GPIOC_8 = 40,
+ GPIOC_9 = 41,
+ GPIOC_10 = 42,
+ GPIOC_11 = 43,
+ GPIOC_12 = 44,
+ GPIOC_13 = 45,
+ GPIOC_14 = 46,
+ GPIOC_15 = 47,
+
+ GPIOD_0 = 48,
+ GPIOD_1 = 49,
+ GPIOD_2 = 50,
+ GPIOD_3 = 51,
+ GPIOD_4 = 52,
+ GPIOD_5 = 53,
+ GPIOD_6 = 54,
+ GPIOD_7 = 55,
+ GPIOD_8 = 56,
+ GPIOD_9 = 57,
+ GPIOD_10 = 58,
+ GPIOD_11 = 59,
+ GPIOD_12 = 60,
+ GPIOD_13 = 61,
+ GPIOD_14 = 62,
+ GPIOD_15 = 63,
+
+ GPIOE_0 = 64,
+ GPIOE_1 = 65,
+ GPIOE_2 = 66,
+ GPIOE_3 = 67,
+ GPIOE_4 = 68,
+ GPIOE_5 = 69,
+ GPIOE_6 = 70,
+ GPIOE_7 = 71,
+ GPIOE_8 = 72,
+ GPIOE_9 = 73,
+ GPIOE_10 = 74,
+ GPIOE_11 = 75,
+ GPIOE_12 = 76,
+ GPIOE_13 = 77,
+ GPIOE_14 = 78,
+ GPIOE_15 = 79,
+
+ GPIO_NONE = 80,
+ GPIO_INVALID = 81,
+
+ Force_4b_cranking_brain_pin = ENUM_SIZE_HACK,
+} brain_pin_e;
+
+typedef enum {
+ MT_CUSTOM = 0,
+ MT_DENSO183 = 1,
+ MT_MPX4250 = 2,
+ MT_HONDA3BAR = 3,
+
+ Force_4b_cranking_map_type = ENUM_SIZE_HACK,
+} air_pressure_sensor_type_e;
+
+#endif /* RUSEFI_ENUMS_H_ */
diff --git a/firmware/controllers/algo/signal_executor.c b/firmware/controllers/algo/signal_executor.c
new file mode 100644
index 0000000000..f33cc7a520
--- /dev/null
+++ b/firmware/controllers/algo/signal_executor.c
@@ -0,0 +1,177 @@
+/**
+ * @file signal_executor.c
+ *
+ * todo: we should split this file into two:
+ * one for pure scheduling and another one for signal output which would
+ * use the scheduling
+ *
+ * @date Dec 4, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ */
+
+#include "main.h"
+#include "signal_executor.h"
+
+#if EFI_WAVE_CHART
+#include "rpm_calculator.h"
+#endif
+
+#if EFI_WAVE_ANALYZER
+
+/**
+ * Signal executors feed digital events right into WaveChart used by Sniffer tab of Dev Console
+ */
+#include "wave_analyzer.h"
+
+#endif /* EFI_WAVE_ANALYZER */
+
+#if EFI_PROD_CODE || EFI_SIMULATOR
+static Logging logger;
+#endif
+
+void initSignalExecutor(void) {
+#if EFI_PROD_CODE || EFI_SIMULATOR
+ initLogging(&logger, "s exec");
+#endif
+ initSignalExecutorImpl();
+}
+
+void initOutputSignal(OutputSignal *signal, io_pin_e ioPin) {
+ signal->io_pin = ioPin;
+}
+
+void turnPinHigh(io_pin_e pin) {
+#if EFI_DEFAILED_LOGGING
+// signal->hi_time = hTimeNow();
+#endif /* EFI_DEFAILED_LOGGING */
+ // turn the output level ACTIVE
+ // todo: this XOR should go inside the setOutputPinValue method
+ setOutputPinValue(pin, TRUE);
+ // sleep for the needed duration
+
+#if EFI_PROD_CODE || EFI_SIMULATOR
+ if (pin == SPARKOUT_1_OUTPUT || pin == SPARKOUT_3_OUTPUT) {
+// time_t now = hTimeNow();
+// float an = getCrankshaftAngle(now);
+// scheduleMsg(&logger, "spark up%d %d", pin, now);
+// scheduleMsg(&logger, "spark angle %d %f", (int)an, an);
+ }
+#endif
+
+#if EFI_WAVE_CHART
+ addWaveChartEvent(getPinName(pin), WC_UP, "");
+#endif /* EFI_WAVE_ANALYZER */
+}
+
+void turnPinLow(io_pin_e pin) {
+ // turn off the output
+ // todo: this XOR should go inside the setOutputPinValue method
+ setOutputPinValue(pin, false);
+
+#if EFI_DEFAILED_LOGGING
+ systime_t after = hTimeNow();
+ debugInt(&signal->logging, "a_time", after - signal->hi_time);
+ scheduleLogging(&signal->logging);
+#endif /* EFI_DEFAILED_LOGGING */
+
+#if EFI_WAVE_CHART
+ addWaveChartEvent(getPinName(pin), WC_DOWN, "");
+#endif /* EFI_WAVE_ANALYZER */
+}
+
+int getRevolutionCounter(void);
+
+/**
+ *
+ * @param delay the number of ticks before the output signal
+ * immediate output if delay is zero
+ * @param dwell the number of ticks of output duration
+ *
+ */
+void scheduleOutput(OutputSignal *signal, float delayMs, float durationMs) {
+ if (durationMs < 0) {
+ firmwareError("duration cannot be negative: %d", durationMs);
+ return;
+ }
+ if (cisnan(durationMs)) {
+ firmwareError("NaN in scheduleOutput", durationMs);
+ return;
+ }
+
+ int index = getRevolutionCounter() % 2;
+ scheduling_s * sUp = &signal->signalTimerUp[index];
+ scheduling_s * sDown = &signal->signalTimerDown[index];
+
+ scheduleTask("out up", sUp, (int)MS2US(delayMs), (schfunc_t) &turnPinHigh, (void *) signal->io_pin);
+ scheduleTask("out down", sDown, (int)MS2US(delayMs + durationMs), (schfunc_t) &turnPinLow, (void*) signal->io_pin);
+}
+
+const char *getPinName(io_pin_e io_pin) {
+ switch (io_pin) {
+ // todo: refactor this hell - introduce arrays & checks?
+ case SPARKOUT_1_OUTPUT:
+ return "spa1";
+ case SPARKOUT_2_OUTPUT:
+ return "spa2";
+ case SPARKOUT_3_OUTPUT:
+ return "spa3";
+ case SPARKOUT_4_OUTPUT:
+ return "spa4";
+ case SPARKOUT_5_OUTPUT:
+ return "spa5";
+ case SPARKOUT_6_OUTPUT:
+ return "spa6";
+ case SPARKOUT_7_OUTPUT:
+ return "spa7";
+ case SPARKOUT_8_OUTPUT:
+ return "spa8";
+ case SPARKOUT_9_OUTPUT:
+ return "spa9";
+ case SPARKOUT_10_OUTPUT:
+ return "spa10";
+ case SPARKOUT_11_OUTPUT:
+ return "spa11";
+ case SPARKOUT_12_OUTPUT:
+ return "spa12";
+
+ case INJECTOR_1_OUTPUT:
+ return "inj1";
+ case INJECTOR_2_OUTPUT:
+ return "inj2";
+ case INJECTOR_3_OUTPUT:
+ return "inj3";
+ case INJECTOR_4_OUTPUT:
+ return "inj4";
+ case INJECTOR_5_OUTPUT:
+ return "inj5";
+ case INJECTOR_6_OUTPUT:
+ return "inj6";
+ case INJECTOR_7_OUTPUT:
+ return "inj7";
+ case INJECTOR_8_OUTPUT:
+ return "inj8";
+ case INJECTOR_9_OUTPUT:
+ return "inj9";
+ case INJECTOR_10_OUTPUT:
+ return "inj10";
+ case INJECTOR_11_OUTPUT:
+ return "inj11";
+ case INJECTOR_12_OUTPUT:
+ return "inj12";
+ default:
+ return "Pin needs name";
+ }
+}
diff --git a/firmware/controllers/algo/signal_executor.h b/firmware/controllers/algo/signal_executor.h
new file mode 100644
index 0000000000..6ec98b4e5b
--- /dev/null
+++ b/firmware/controllers/algo/signal_executor.h
@@ -0,0 +1,62 @@
+/**
+ * @file signal_executor.h
+ * @brief Asynchronous output signal header
+ *
+ * @date Feb 10, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef SPARKOUT_H_
+#define SPARKOUT_H_
+
+#include "rusefi_enums.h"
+#include "global.h"
+#include "efifeatures.h"
+#include "io_pins.h"
+#include "scheduler.h"
+
+#if EFI_PROD_CODE
+#include "datalogging.h"
+#endif /* EFI_PROD_CODE */
+
+#if EFI_SIGNAL_EXECUTOR_SLEEP
+#include "signal_executor_sleep.h"
+#endif /* EFI_SIGNAL_EXECUTOR_SLEEP */
+
+/**
+ * @brief Asynchronous output signal data structure
+ */
+typedef struct OutputSignal_struct OutputSignal;
+struct OutputSignal_struct {
+ io_pin_e io_pin;
+
+ /**
+ * We are alternating instances so that events which extend into next revolution are not reused while
+ * scheduling next revolution events
+ */
+ scheduling_s signalTimerUp[2];
+ scheduling_s signalTimerDown[2];
+};
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void initOutputSignal(OutputSignal *signal, io_pin_e ioPin);
+void scheduleOutput(OutputSignal *signal, float delayMs, float durationMs);
+void initOutputSignalBase(OutputSignal *signal);
+void scheduleOutputBase(OutputSignal *signal, float delayMs, float durationMs);
+
+void turnPinHigh(io_pin_e pin);
+void turnPinLow(io_pin_e pin);
+
+void initSignalExecutor(void);
+void initSignalExecutorImpl(void);
+void scheduleByAngle(scheduling_s *timer, float angle, schfunc_t callback, void *param);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SPARKOUT_H_ */
diff --git a/firmware/controllers/algo/wave_chart.c b/firmware/controllers/algo/wave_chart.c
new file mode 100644
index 0000000000..ac2fdcad9a
--- /dev/null
+++ b/firmware/controllers/algo/wave_chart.c
@@ -0,0 +1,212 @@
+/**
+ * @file wave_chart.c
+ * @brief Dev console wave sniffer logic
+ *
+ * Here we have our own build-in logic analyzer. The data we aggregate here is sent to the
+ * java UI Dev Console so that it can be displayed nicely in the Sniffer tab.
+ *
+ * Both external events (see wave_analyzer.c) and internal (see signal executors) are supported
+ *
+ * @date Jun 23, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ */
+
+#include "wave_chart.h"
+#include "main.h"
+
+#if EFI_WAVE_CHART
+
+#include "eficonsole.h"
+#include "status_loop.h"
+
+#define CHART_DELIMETER "!"
+
+#if EFI_HISTOGRAMS
+#include "rfiutil.h"
+#include "histogram.h"
+static histogram_s waveChartHisto;
+#endif
+
+/**
+ * This is the number of events in the digital chart which would be displayed
+ * on the 'digital sniffer' pane
+ */
+#if EFI_PROD_CODE
+// todo: does it really need to be a variable? maybe a constant should be enough?
+static volatile int chartSize = 300;
+#define WAVE_LOGGING_SIZE 5000
+#else
+// need more events for automated test
+static volatile int chartSize = 400;
+#define WAVE_LOGGING_SIZE 35000
+#endif
+
+int waveChartUsedSize;
+
+static int isChartActive = TRUE;
+//static int isChartActive = FALSE;
+
+//#define DEBUG_WAVE 1
+
+#if DEBUG_WAVE
+static Logging debugLogging;
+#endif
+
+static Logging logger;
+
+void resetWaveChart(WaveChart *chart) {
+#if DEBUG_WAVE
+ scheduleSimpleMsg(&debugLogging, "reset while at ", chart->counter);
+#endif
+ resetLogging(&chart->logging);
+ chart->counter = 0;
+ appendPrintf(&chart->logging, "wave_chart%s", DELIMETER);
+}
+
+static char WAVE_LOGGING_BUFFER[WAVE_LOGGING_SIZE] CCM_OPTIONAL
+;
+
+static int isWaveChartFull(WaveChart *chart) {
+ return chart->counter >= chartSize;
+}
+
+static void printStatus(void) {
+ scheduleIntValue(&logger, "chart", isChartActive);
+ scheduleIntValue(&logger, "chartsize", chartSize);
+}
+
+static void setChartActive(int value) {
+ isChartActive = value;
+ printStatus();
+}
+
+void setChartSize(int newSize) {
+ if (newSize < 5) {
+ return;
+ }
+ chartSize = newSize;
+ printStatus();
+}
+
+void publishChartIfFull(WaveChart *chart) {
+ if (isWaveChartFull(chart)) {
+ publishChart(chart);
+ resetWaveChart(chart);
+ }
+}
+
+void publishChart(WaveChart *chart) {
+ appendPrintf(&chart->logging, DELIMETER);
+ waveChartUsedSize = loggingSize(&chart->logging);
+#if DEBUG_WAVE
+ Logging *l = &chart->logging;
+ scheduleSimpleMsg(&debugLogging, "IT'S TIME", strlen(l->buffer));
+#endif
+ bool isFullLog = getFullLog();
+ if (isChartActive && isFullLog) {
+ scheduleLogging(&chart->logging);
+ }
+}
+
+static char timeBuffer[10];
+
+/**
+ * @brief Register an event for digital sniffer
+ */
+void addWaveChartEvent3(WaveChart *chart, const char *name, const char * msg, const char * msg2) {
+ efiAssertVoid(chart->isInitialized, "chart not initialized");
+#if DEBUG_WAVE
+ scheduleSimpleMsg(&debugLogging, "current", chart->counter);
+#endif
+ if (isWaveChartFull(chart)) {
+ return;
+ }
+
+#if EFI_HISTOGRAMS && EFI_PROD_CODE
+ int beforeCallback = hal_lld_get_counter_value();
+#endif
+
+
+ int time100 = getTimeNowUs() / 10;
+
+ bool alreadyLocked = lockOutputBuffer(); // we have multiple threads writing to the same output buffer
+
+ if (chart->counter == 0) {
+ chart->startTime = time100;
+ }
+ chart->counter++;
+ if (remainingSize(&chart->logging) > 30) {
+ /**
+ * printf is a heavy method, append is used here as a performance optimization
+ */
+ appendFast(&chart->logging, name);
+ appendFast(&chart->logging, CHART_DELIMETER);
+ appendFast(&chart->logging, msg);
+ appendFast(&chart->logging, CHART_DELIMETER);
+ /**
+ * We want smaller times within a chart in order to reduce packet size.
+ */
+ time100 -= chart->startTime;
+
+ itoa10(timeBuffer, time100);
+ appendFast(&chart->logging, timeBuffer);
+ appendFast(&chart->logging, msg2);
+ appendFast(&chart->logging, CHART_DELIMETER);
+ }
+ if (!alreadyLocked) {
+ unlockOutputBuffer();
+ }
+
+#if EFI_HISTOGRAMS && EFI_PROD_CODE
+ int64_t diff = hal_lld_get_counter_value() - beforeCallback;
+ if (diff > 0) {
+ hsAdd(&waveChartHisto, diff);
+ }
+#endif /* EFI_HISTOGRAMS */
+
+}
+
+void showWaveChartHistogram(void) {
+#if EFI_PROD_CODE
+ printHistogram(&logger, &waveChartHisto);
+#endif
+}
+
+void initWaveChart(WaveChart *chart) {
+ initLogging(&logger, "wave info");
+
+ if (!isChartActive) {
+ printMsg(&logger, "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! chart disabled");
+ }
+
+ printStatus();
+
+ initLoggingExt(&chart->logging, "wave chart", WAVE_LOGGING_BUFFER, sizeof(WAVE_LOGGING_BUFFER));
+ chart->isInitialized = TRUE;
+#if DEBUG_WAVE
+ initLoggingExt(&debugLogging, "wave chart debug", &debugLogging.DEFAULT_BUFFER, sizeof(debugLogging.DEFAULT_BUFFER));
+#endif
+
+#if EFI_HISTOGRAMS
+ initHistogram(&waveChartHisto, "wave chart");
+#endif /* EFI_HISTOGRAMS */
+
+ resetWaveChart(chart);
+ addConsoleActionI("chartsize", setChartSize);
+ addConsoleActionI("chart", setChartActive);
+}
+
+#endif /* EFI_WAVE_CHART */
diff --git a/firmware/controllers/algo/wave_chart.h b/firmware/controllers/algo/wave_chart.h
new file mode 100644
index 0000000000..c72b6509ed
--- /dev/null
+++ b/firmware/controllers/algo/wave_chart.h
@@ -0,0 +1,48 @@
+/**
+ * @file wave_chart.h
+ * @brief Dev console wave sniffer
+ *
+ * @date Jun 23, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef WAVE_CHART_H_
+#define WAVE_CHART_H_
+
+#include "global.h"
+
+#if EFI_WAVE_CHART
+#include "datalogging.h"
+#endif /* EFI_WAVE_CHART */
+
+/**
+ * @brief Dev console sniffer data buffer
+ */
+typedef struct {
+#if EFI_WAVE_CHART
+ Logging logging;
+#endif /* EFI_WAVE_CHART */
+ int counter;
+ int startTime;
+ volatile int isInitialized;
+} WaveChart;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void addWaveChartEvent3(WaveChart *chart, const char *name, const char *msg, const char *msg2);
+void publishChart(WaveChart *chart);
+void initWaveChart(WaveChart *chart);
+void showWaveChartHistogram(void);
+void resetWaveChart(WaveChart *chart);
+void setChartSize(int newSize);
+//int isWaveChartFull(WaveChart *chart);
+void publishChartIfFull(WaveChart *chart);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* WAVE_CHART_H_ */
diff --git a/firmware/controllers/alternatorController.cpp b/firmware/controllers/alternatorController.cpp
new file mode 100644
index 0000000000..a5a260f841
--- /dev/null
+++ b/firmware/controllers/alternatorController.cpp
@@ -0,0 +1,61 @@
+/**
+ * @file alternatorController.cpp
+ * @brief alternator controller - turn alternator off if you do not really need it
+ *
+ * @date Apr 6, 2014
+ * @author Dmitry Sidin
+ * @author Andrey Belomutskiy (c) 2012-2014
+ */
+
+#include "main.h"
+#include "rpm_calculator.h"
+#include "pwm_generator.h"
+#include "alternatorController.h"
+#include "pin_repository.h"
+#include "engine_configuration.h"
+#include "voltage.h"
+
+#if 0
+
+extern board_configuration_s *boardConfiguration;
+
+#define ALTERNATOR_VALVE_PWM_FREQUENCY 30000
+
+static PwmConfig alternatorControl;
+
+static THD_WORKING_AREA(ivThreadStack, UTILITY_THREAD_STACK_SIZE);
+
+static msg_t AltCtrlThread(int param) {
+ chRegSetThreadName("AlternatorController");
+ int alternatorDutyCycle = 500;
+ while (TRUE) {
+ chThdSleepMilliseconds(10);
+
+ if ( getVBatt() > 14.2 )
+ alternatorDutyCycle = alternatorDutyCycle + 1 ;
+ else
+ alternatorDutyCycle = alternatorDutyCycle - 1;
+
+
+ if (alternatorDutyCycle < 150 )
+ alternatorDutyCycle = 150;
+ if (alternatorDutyCycle > 950)
+ alternatorDutyCycle = 950;
+ setSimplePwmDutyCycle(&alternatorControl, 0.001 * alternatorDutyCycle);
+ }
+#if defined __GNUC__
+ return -1;
+#endif
+}
+
+void initAlternatorCtrl() {
+ startSimplePwm(&alternatorControl, "Alternator control",
+ boardConfiguration->alternatorControlPin,
+ 0.5,
+ ALTERNATOR_VALVE_PWM_FREQUENCY,
+ ALTERNATOR_SWITCH
+ );
+ chThdCreateStatic(ivThreadStack, sizeof(ivThreadStack), LOWPRIO, (tfunc_t)AltCtrlThread, NULL);
+}
+
+#endif
diff --git a/firmware/controllers/alternatorController.h b/firmware/controllers/alternatorController.h
new file mode 100644
index 0000000000..5302be121d
--- /dev/null
+++ b/firmware/controllers/alternatorController.h
@@ -0,0 +1,15 @@
+/**
+ * @file alternatorController.h
+ * @brief alternator controller
+ *
+ * @date Apr 6, 2014
+ * @author Dmitry Sidin
+ * @author Andrey Belomutskiy (c) 2012-2014
+ *
+ */
+#ifndef ALTERNATORCONTROLLER_H_
+#define ALTERNATORCONTROLLER_H_
+
+void initAlternatorCtrl(void);
+
+#endif /* ALTERNATORCONTROLLER_H_ */
diff --git a/firmware/controllers/controllers.mk b/firmware/controllers/controllers.mk
new file mode 100644
index 0000000000..b114c8904b
--- /dev/null
+++ b/firmware/controllers/controllers.mk
@@ -0,0 +1,17 @@
+
+CONTROLLERSSRC = \
+ controllers/ignition_central.c \
+ $(PROJECT_DIR)/controllers/malfunction_indicator.c \
+ $(PROJECT_DIR)/controllers/error_handling.c
+
+CONTROLLERS_SRC_CPP = $(PROJECT_DIR)/controllers/settings.cpp \
+ controllers/electronic_throttle.cpp \
+ controllers/map_averaging.cpp \
+ controllers/map_multiplier_thread.cpp \
+ controllers/flash_main.cpp \
+ controllers/injector_central.cpp \
+ controllers/idle_thread.cpp \
+ controllers/PwmTester.cpp \
+ $(PROJECT_DIR)/controllers/alternatorController.cpp \
+ $(PROJECT_DIR)/controllers/lcd_controller.cpp \
+ $(PROJECT_DIR)/controllers/engine_controller.cpp
diff --git a/firmware/controllers/core/EfiWave.cpp b/firmware/controllers/core/EfiWave.cpp
new file mode 100644
index 0000000000..bd79907ed4
--- /dev/null
+++ b/firmware/controllers/core/EfiWave.cpp
@@ -0,0 +1,50 @@
+/**
+ * @file EfiWave.cpp
+ *
+ * @date May 18, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+#include "EfiWave.h"
+#include "trigger_structure.h"
+
+single_wave_s::single_wave_s() {
+ init(NULL);
+}
+
+single_wave_s::single_wave_s(int *ps) {
+ init(ps);
+}
+
+void single_wave_s::init(int *pinStates) {
+ this->pinStates = pinStates;
+}
+
+multi_wave_s::multi_wave_s() {
+}
+
+multi_wave_s::multi_wave_s(float *switchTimes, single_wave_s *waves) {
+ init(switchTimes, waves);
+}
+
+void multi_wave_s::init(float *switchTimes, single_wave_s *waves) {
+ this->switchTimes = switchTimes;
+ this->waves = waves;
+}
+
+void multi_wave_s::reset(void) {
+ waveCount = 0;
+}
+
+float multi_wave_s::getSwitchTime(int index) const {
+ return switchTimes[index];
+}
+
+void checkSwitchTimes2(int size, float *switchTimes) {
+ for (int i = 0; i < size - 1; i++) {
+ if (switchTimes[i] >= switchTimes[i + 1]) {
+ firmwareError("invalid switchTimes @%d: %f/%f", i, switchTimes[i], switchTimes[i + 1]);
+ }
+ }
+}
diff --git a/firmware/controllers/core/EfiWave.h b/firmware/controllers/core/EfiWave.h
new file mode 100644
index 0000000000..182a52d47b
--- /dev/null
+++ b/firmware/controllers/core/EfiWave.h
@@ -0,0 +1,59 @@
+/**
+ * @file EfiWave.h
+ *
+ * @date May 18, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+#ifndef EFI_WAVE_H_
+#define EFI_WAVE_H_
+
+#include "engine_configuration.h"
+
+#define PWM_PHASE_MAX_COUNT 250
+#define PWM_PHASE_MAX_WAVE_PER_PWM 3
+
+/**
+ * @brief PWM configuration for the specific output pin
+ */
+class single_wave_s {
+public:
+ single_wave_s();
+ single_wave_s(int *pinStates);
+ void init(int *pinStates);
+ int *pinStates;
+};
+
+class trigger_shape_s;
+
+class multi_wave_s {
+public:
+ multi_wave_s();
+ multi_wave_s(float *st, single_wave_s *waves);
+ void init(float *st, single_wave_s *waves);
+ void reset(void);
+ float getSwitchTime(int phaseIndex) const;
+ void setSwitchTime(int phaseIndex, float value);
+ void checkSwitchTimes(int size);
+ int getChannelState(int channelIndex, int phaseIndex) const;
+
+ int findAngleMatch(float angle, int size) const;
+ int waveIndertionAngle(float angle, int size) const;
+
+ /**
+ * Number of signal wires
+ */
+ int waveCount;
+ single_wave_s *waves;
+//private:
+ /**
+ * values in the (0..1] range which refer to points within the period at at which pin state should be changed
+ * So, in the simplest case we turn pin off at 0.3 and turn it on at 1 - that would give us a 70% duty cycle PWM
+ */
+ float *switchTimes;
+};
+
+void checkSwitchTimes2(int size, float *switchTimes);
+void configureHondaAccordCD(trigger_shape_s *s, bool with3rdSignal);
+void configureHondaAccordCDDip(trigger_shape_s *s);
+
+#endif /* EFI_WAVE_H_ */
diff --git a/firmware/controllers/core/avg_values.c b/firmware/controllers/core/avg_values.c
new file mode 100644
index 0000000000..2794e5e4a3
--- /dev/null
+++ b/firmware/controllers/core/avg_values.c
@@ -0,0 +1,60 @@
+/*
+ * avg_values.c
+ *
+ * @date Jul 23, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "avg_values.h"
+#include
+
+void avgFill(AvgTable *table, int count, float value) {
+ for (int i = 0; i < AVG_TAB_SIZE; i++) {
+ for (int j = 0; j < AVG_TAB_SIZE; j++) {
+ table->counts[i][j] = count;
+ table->values[i][j] = value;
+ }
+ }
+}
+
+void avgReset(AvgTable *table) {
+ avgFill(table, 0, 0);
+}
+
+void avgAddValue(AvgTable *table, int rpm, float key, float value) {
+ if (rpm >= MAX_RPM || key >= MAX_KEY) {
+ return;
+ }
+ int i = (int)(AVG_TAB_SIZE * rpm / MAX_RPM);
+ int j = (int)(AVG_TAB_SIZE * key / MAX_KEY);
+
+ table->values[i][j] += value;
+ table->counts[i][j]++;
+}
+
+float avgGetValueByIndexes(AvgTable *table, int i, int j) {
+ int count = table->counts[i][j];
+ if (count == 0) {
+ return NAN;
+ }
+ return table->values[i][j] / count;
+}
+
+float avgGetValue(AvgTable *table, int rpm, float key) {
+ if (rpm >= MAX_RPM || key >= MAX_KEY) {
+ return NAN;
+ }
+ int i = (int)(AVG_TAB_SIZE * rpm / MAX_RPM);
+ int j = (int)(AVG_TAB_SIZE * key / MAX_KEY);
+ return avgGetValueByIndexes(table, i, j);
+}
+
+int avgGetValuesCount(AvgTable *table, int rpm, float key) {
+ if (rpm >= MAX_RPM || key >= MAX_KEY) {
+ return 0;
+ }
+ int i = (int)(AVG_TAB_SIZE * rpm / MAX_RPM);
+ int j = (int)(AVG_TAB_SIZE * key / MAX_KEY);
+
+ return table->counts[i][j];
+}
diff --git a/firmware/controllers/core/avg_values.h b/firmware/controllers/core/avg_values.h
new file mode 100644
index 0000000000..8c80237284
--- /dev/null
+++ b/firmware/controllers/core/avg_values.h
@@ -0,0 +1,30 @@
+/*
+ * avg_values.h
+ *
+ * @date Jul 23, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef AVG_VALUES_H_
+#define AVG_VALUES_H_
+
+#define AVG_TAB_SIZE 48
+
+#define MAX_RPM 8000
+#define MAX_KEY 5
+
+typedef struct {
+ float values[AVG_TAB_SIZE][AVG_TAB_SIZE];
+ int counts[AVG_TAB_SIZE][AVG_TAB_SIZE];
+} AvgTable;
+
+
+void avgReset(AvgTable *table);
+void avgFill(AvgTable *table, int count, float value);
+
+void avgAddValue(AvgTable *table, int rpm, float key, float value);
+float avgGetValueByIndexes(AvgTable *table, int i, int j);
+float avgGetValue(AvgTable *table, int rpm, float key);
+int avgGetValuesCount(AvgTable *table, int rpm, float key);
+
+#endif /* AVG_VALUES_H_ */
diff --git a/firmware/controllers/core/core.mk b/firmware/controllers/core/core.mk
new file mode 100644
index 0000000000..52b47c90a5
--- /dev/null
+++ b/firmware/controllers/core/core.mk
@@ -0,0 +1,7 @@
+
+CONTROLLERS_CORE_SRC = $(PROJECT_DIR)/controllers/core/avg_values.c
+
+CONTROLLERS_CORE_SRC_CPP = $(PROJECT_DIR)/controllers/core/EfiWave.cpp \
+ $(PROJECT_DIR)/controllers/core/table_helper.cpp \
+ $(PROJECT_DIR)/controllers/core/interpolation.cpp \
+
diff --git a/firmware/controllers/core/fl_stack.h b/firmware/controllers/core/fl_stack.h
new file mode 100644
index 0000000000..42edd42915
--- /dev/null
+++ b/firmware/controllers/core/fl_stack.h
@@ -0,0 +1,45 @@
+/**
+ * @file fl_stack.h
+ * @brief Fixed-length stack
+ *
+ * @date Jul 9, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef FL_STACK_H_
+#define FL_STACK_H_
+
+template
+class FLStack {
+public:
+ FLStack();
+ void push(T value);
+ T pop();
+ int size();
+ bool isEmpty();
+private:
+ int index;
+ T values[MAXSIZE];
+};
+
+template
+FLStack::FLStack() {
+ index = 0;
+}
+
+template
+bool FLStack::isEmpty() {
+ return index == 0;
+}
+
+template
+void FLStack::push(T value) {
+ values[index++] = value;
+}
+
+template
+int FLStack::size() {
+ return index;
+}
+
+#endif /* FL_STACK_H_ */
diff --git a/firmware/controllers/core/interpolation.cpp b/firmware/controllers/core/interpolation.cpp
new file mode 100644
index 0000000000..d8aaa87357
--- /dev/null
+++ b/firmware/controllers/core/interpolation.cpp
@@ -0,0 +1,241 @@
+/**
+ * @file interpolation.cpp
+ * @brief Linear interpolation algorithms
+ *
+ * @date Oct 17, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#if DEBUG_FUEL
+#include
+#endif
+
+#include
+
+#include "main.h"
+#include "interpolation.h"
+
+#define INTERPOLATION_A(x1, y1, x2, y2) ((y1 - y2) / (x1 - x2))
+
+int needInterpolationLogging = TRUE;
+
+
+FastInterpolation::FastInterpolation(float x1, float y1, float x2, float y2) {
+ init(x1, y1, x2, y2);
+}
+
+void FastInterpolation::init(float x1, float y1, float x2, float y2) {
+ if (x1 == x2) {
+ firmwareError("Same x1 and x2 in interpolate: %f/%f", x1, x2);
+ return;
+ }
+ a = INTERPOLATION_A(x1, y1, x2, y2);
+ b = y1 - a * x1;
+}
+
+float FastInterpolation::getValue(float x) {
+ return a * x + b;
+}
+
+
+/** @brief Linear interpolation by two points
+ *
+ * @param x1 key of the first point
+ * @param y1 value of the first point
+ * @param x2 key of the second point
+ * @param y2 value of the second point
+ * @param X key to be interpolated
+ *
+ * @note For example, "interpolate(engineConfiguration.tpsMin, 0, engineConfiguration.tpsMax, 100, adc);"
+ */
+float interpolate(float x1, float y1, float x2, float y2, float x) {
+ // todo: double comparison using EPS
+ if (x1 == x2) {
+ firmwareError("Same x1 and x2 in interpolate: %f/%f", x1, x2);
+ return NAN;
+ }
+
+ // a*x1 + b = y1
+ // a*x2 + b = y2
+// efiAssertVoid(x1 != x2, "no way we can interpolate");
+ float a = INTERPOLATION_A(x1, y1, x2, y2);
+ float b = y1 - a * x1;
+ float result = a * x + b;
+#if DEBUG_FUEL
+ printf("x1=%f y1=%f x2=%f y2=%f\r\n", x1, y1, x2, y2);
+ printf("a=%f b=%f result=%f\r\n", a, b, result);
+#endif
+ return result;
+}
+
+/** @brief Binary search
+ * @returns the highest index within sorted array such that array[i] is greater than or equal to the parameter
+ * @note If the parameter is smaller than the first element of the array, -1 is returned.
+ */
+int findIndex(float array[], int size, float value) {
+ efiAssert(!cisnan(value), "NaN in findIndex", 0);
+
+ if (value < array[0])
+ return -1;
+ int middle;
+
+ int left = 0;
+ int right = size;
+
+ // todo: extract binary search as template method?
+ while (true) {
+#if 0
+ // that's an assertion to make sure we do not loop here
+ size--;
+ efiAssert(size > 0, "Unexpected state in binary search", 0);
+#endif
+
+ middle = (left + right) / 2;
+
+// print("left=%d middle=%d right=%d: %f\r\n", left, middle, right, array[middle]);
+
+ if (middle == left)
+ break;
+
+ if (value < array[middle]) {
+ right = middle;
+ } else if (value > array[middle]) {
+ left = middle;
+ } else {
+ break;
+ }
+ }
+
+ return middle;
+}
+
+/**
+ * @brief One-dimensional table lookup with linear interpolation
+ */
+float interpolate2d(float value, float bin[], float values[], int size) {
+ int index = findIndex(bin, size, value);
+
+ if (index == -1)
+ return values[0];
+ if (index == size - 1)
+ return values[size - 1];
+
+ return interpolate(bin[index], values[index], bin[index + 1], values[index + 1], value);
+}
+
+/**
+ * @brief Two-dimensional table lookup with linear interpolation
+ */
+float interpolate3d(float x, float xBin[], int xBinSize, float y, float yBin[], int yBinSize, float* map[]) {
+ if (cisnan(y)) {
+ warning(OBD_PCM_Processor_Fault, "%f: x is NaN in interpolate3d", x);
+ return NAN;
+ }
+ if (cisnan(y)) {
+ warning(OBD_PCM_Processor_Fault, "%f: y is NaN in interpolate3d", y);
+ return NAN;
+ }
+
+ int xIndex = findIndex(xBin, xBinSize, x);
+#if DEBUG_INTERPOLATION
+ if (needInterpolationLogging)
+ printf("X index=%d\r\n", xIndex);
+#endif
+ int yIndex = findIndex(yBin, yBinSize, y);
+ if (xIndex < 0 && yIndex < 0) {
+#if DEBUG_INTERPOLATION
+ if (needInterpolationLogging)
+ printf("X and Y are smaller than smallest cell in table: %d\r\n", xIndex);
+#endif
+ return map[0][0];
+ }
+
+ if (xIndex < 0) {
+#if DEBUG_INTERPOLATION
+ if (needInterpolationLogging)
+ printf("X is smaller than smallest cell in table: %dr\n", xIndex);
+#endif
+ // no interpolation should be fine here.
+ return map[0][yIndex];
+ }
+
+ if (yIndex < 0) {
+#if DEBUG_INTERPOLATION
+ if (needInterpolationLogging)
+ printf("Y is smaller than smallest cell in table: %d\r\n", yIndex);
+#endif
+ // no interpolation should be fine here.
+ return map[xIndex][0];
+ }
+
+ if (xIndex == xBinSize - 1 && yIndex == yBinSize - 1) {
+#if DEBUG_INTERPOLATION
+ if (needInterpolationLogging)
+ printf("X and Y are larger than largest cell in table: %d %d\r\n", xIndex, yIndex);
+#endif
+ return map[xBinSize - 1][yBinSize - 1];
+ }
+
+ if (xIndex == xBinSize - 1) {
+#if DEBUG_INTERPOLATION
+ if (needInterpolationLogging)
+ printf("TODO BETTER LOGGING x overflow %d\r\n", yIndex);
+#endif
+ // todo: implement better handling - y interpolation
+ return map[xBinSize - 1][yIndex];
+ }
+
+ if (yIndex == yBinSize - 1) {
+#if DEBUG_INTERPOLATION
+ if (needInterpolationLogging)
+ printf("Y is larger than largest cell in table: %d\r\n", yIndex);
+#endif
+ // todo: implement better handling - x interpolation
+ return map[xIndex][yBinSize - 1];
+ }
+
+ /*
+ * first we find the interpolated value for this RPM
+ */
+ int rpmMaxIndex = xIndex + 1;
+
+ float xMin = xBin[xIndex];
+ float xMax = xBin[xIndex + 1];
+ float rpmMinKeyMinValue = map[xIndex][yIndex];
+ float rpmMaxKeyMinValue = map[xIndex + 1][yIndex];
+
+ float keyMinValue = interpolate(xMin, rpmMinKeyMinValue, xMax, rpmMaxKeyMinValue, x);
+
+#if DEBUG_INTERPOLATION
+ if (needInterpolationLogging) {
+ printf("X=%f:\r\nrange %f - %f\r\n", x, xMin, xMax);
+ printf("X interpolation range %f %f result %f\r\n", rpmMinKeyMinValue, rpmMaxKeyMinValue, keyMinValue);
+ }
+#endif
+
+ int keyMaxIndex = yIndex + 1;
+ float keyMin = yBin[yIndex];
+ float keyMax = yBin[keyMaxIndex];
+ float rpmMinKeyMaxValue = map[xIndex][keyMaxIndex];
+ float rpmMaxKeyMaxValue = map[rpmMaxIndex][keyMaxIndex];
+
+ float keyMaxValue = interpolate(xMin, rpmMinKeyMaxValue, xMax, rpmMaxKeyMaxValue, x);
+
+#if DEBUG_INTERPOLATION
+ if (needInterpolationLogging) {
+ printf("key=%f:\r\nrange %f - %f\r\n", y, keyMin, keyMax);
+ printf("key interpolation range %f %f result %f\r\n", rpmMinKeyMaxValue, rpmMaxKeyMaxValue, keyMaxValue);
+ }
+#endif
+
+ float result = interpolate(keyMin, keyMinValue, keyMax, keyMaxValue, y);
+ return result;
+}
+
+void setTableValue(float bins[], float values[], int size, float key, float value) {
+ int index = findIndex(bins, size, key);
+ if (index == -1)
+ index = 0;
+ values[index] = value;
+}
+
diff --git a/firmware/controllers/core/interpolation.h b/firmware/controllers/core/interpolation.h
new file mode 100644
index 0000000000..856705830b
--- /dev/null
+++ b/firmware/controllers/core/interpolation.h
@@ -0,0 +1,26 @@
+/**
+ * @file interpolation.h
+ *
+ * @date Oct 17, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef INTERPOLATION_3D_H_
+#define INTERPOLATION_3D_H_
+
+int findIndex(float array[], int size, float value);
+float interpolate(float x1, float y1, float x2, float y2, float x);
+float interpolate2d(float value, float bin[], float values[], int size);
+float interpolate3d(float x, float xBin[], int xBinSize, float y, float yBin[], int yBinSize, float* map[]);
+void setTableValue(float bins[], float values[], int size, float key, float value);
+
+class FastInterpolation {
+public:
+ FastInterpolation(float x1, float y1, float x2, float y2);
+ void init(float x1, float y1, float x2, float y2);
+ float getValue(float x);
+private:
+ float a, b;
+};
+
+#endif /* INTERPOLATION_3D_H_ */
diff --git a/firmware/controllers/core/signal_filtering.c b/firmware/controllers/core/signal_filtering.c
new file mode 100644
index 0000000000..2daf480c17
--- /dev/null
+++ b/firmware/controllers/core/signal_filtering.c
@@ -0,0 +1,64 @@
+/*
+ * signal_filtering.c
+ *
+ * @date Aug 5, 2013
+ * @author pc
+ */
+
+#ifndef SIGNAL_FILTERING_C_
+#define SIGNAL_FILTERING_C_
+
+#include "signal_filtering.h"
+
+void sfInit(SignalFiltering *fs, float K, float initialValue) {
+ fs->pointer = 0;
+ fs->K = K;
+ fs->Vout = initialValue;
+ fs->Vacc = initialValue * K;
+}
+
+static void addCopyAndSort(SignalFiltering *fs, float value) {
+ fs->values[fs->pointer] = value;
+ fs->pointer = ++fs->pointer == FILTER_SIZE ? 0 : fs->pointer;
+
+ for (int i = 0; i < FILTER_SIZE; i++)
+ fs->sorted[i] = fs->values[i];
+
+ for (int i = 0; i < FILTER_SIZE; i++)
+ for (int j = i + 1; j < FILTER_SIZE; j++)
+ if (fs->sorted[i] < fs->sorted[j]) {
+ float temp = fs->sorted[i];
+ fs->sorted[i] = fs->sorted[j];
+ fs->sorted[j] = temp;
+ }
+}
+
+void sfAddValue(SignalFiltering *fs, float value) {
+ addCopyAndSort(fs, value);
+ float Vin = fs->sorted[FILTER_SIZE / 2];
+
+ fs->Vacc += Vin - fs->Vout;
+ fs->Vout = fs->Vacc / fs->K;
+}
+
+void sfAddValue2(SignalFiltering *fs, float value) {
+ addCopyAndSort(fs, value);
+
+ int fromIndex = FILTER_SIZE / 4;
+ int toIndex = FILTER_SIZE / 4 + FILTER_SIZE / 2;
+
+ /**
+ * this implementation takes the average of the middle hald of the sorted values
+ */
+ float result = 0;
+ for (int i = fromIndex; i < toIndex; i++)
+ result += fs->sorted[i];
+
+ fs->Vout = result / (FILTER_SIZE / 2);
+}
+
+float sfGetValue(SignalFiltering *fs) {
+ return fs->Vout;
+}
+
+#endif /* SIGNAL_FILTERING_C_ */
diff --git a/firmware/controllers/core/signal_filtering.h b/firmware/controllers/core/signal_filtering.h
new file mode 100644
index 0000000000..63ea15592f
--- /dev/null
+++ b/firmware/controllers/core/signal_filtering.h
@@ -0,0 +1,25 @@
+/*
+ * signal_filtering.h
+ *
+ * @date Aug 5, 2013
+ * @author pc
+ */
+
+#ifndef SIGNAL_FILTERING_H_
+#define SIGNAL_FILTERING_H_
+
+#define FILTER_SIZE 5
+
+typedef struct {
+ float values[FILTER_SIZE];
+ float sorted[FILTER_SIZE];
+ int pointer;
+ float K, Vacc, Vout;
+} SignalFiltering;
+
+void sfInit(SignalFiltering *fs, float K, float initialValue);
+void sfAddValue(SignalFiltering *fs, float value);
+void sfAddValue2(SignalFiltering *fs, float value);
+float sfGetValue(SignalFiltering *fs);
+
+#endif /* SIGNAL_FILTERING_H_ */
diff --git a/firmware/controllers/core/table_helper.cpp b/firmware/controllers/core/table_helper.cpp
new file mode 100644
index 0000000000..71615930ff
--- /dev/null
+++ b/firmware/controllers/core/table_helper.cpp
@@ -0,0 +1,29 @@
+/**
+ * @file table_helper.cpp
+ * @brief Helper methods related to 3D & 2D tables manipulation (maps and curves)
+ *
+ * @date Jul 6, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "table_helper.h"
+#include "efilib.h"
+#include "interpolation.h"
+
+void setTableBin2(float array[], int size, float l, float r, float precision) {
+ for (int i = 0; i < size; i++) {
+ float value = interpolate(0, l, size - 1, r, i);
+ /**
+ * rounded values look nicer, also we want to avoid precision mismatch with Tuner Studio
+ */
+ array[i] = efiRound(value, precision);
+ }
+}
+
+void setTableBin(float array[], int size, float l, float r) {
+ setTableBin2(array, size, l, r, 0.01);
+}
+
+void setRpmTableBin(float array[], int size) {
+ setTableBin2(array, size, 800, 7000, 1);
+}
diff --git a/firmware/controllers/core/table_helper.h b/firmware/controllers/core/table_helper.h
new file mode 100644
index 0000000000..c4367fdec6
--- /dev/null
+++ b/firmware/controllers/core/table_helper.h
@@ -0,0 +1,57 @@
+/**
+ * @file table_helper.h
+ *
+ * @date Jul 6, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+#ifndef TABLE_HELPER_H_
+#define TABLE_HELPER_H_
+
+#include
+#include "error_handling.h"
+
+// 'random' value to be sure we are not treating any non-zero trash as TRUE
+#define MAGIC_TRUE_VALUE 153351512
+
+template
+class Map3D {
+public:
+ void init(float table[RPM_BIN_SIZE][LOAD_BIN_SIZE]);
+ float getValue(float x, float xBin[], float y, float yBin[]);
+ void setAll(float value);
+private:
+ float *pointers[LOAD_BIN_SIZE];
+ int initialized;
+};
+
+template
+void Map3D::init(float table[RPM_BIN_SIZE][LOAD_BIN_SIZE]) {
+ for (int k = 0; k < LOAD_BIN_SIZE; k++) {
+ pointers[k] = table[k];
+ }
+ initialized = MAGIC_TRUE_VALUE;
+}
+
+template
+float Map3D::getValue(float x, float xBin[], float y, float yBin[]) {
+ efiAssert(initialized == MAGIC_TRUE_VALUE, "map not initialized", NAN);
+ return interpolate3d(x, xBin, LOAD_BIN_SIZE, y, yBin, RPM_BIN_SIZE, pointers);
+}
+
+template
+void Map3D::setAll(float value) {
+ efiAssertVoid(initialized == MAGIC_TRUE_VALUE, "map not initialized");
+ for (int l = 0; l < LOAD_BIN_SIZE; l++) {
+ for (int r = 0; r < RPM_BIN_SIZE; r++) {
+ pointers[l][r] = value;
+ }
+ }
+}
+
+typedef Map3D<16, 16> Map3D1616;
+
+void setTableBin(float array[], int size, float l, float r);
+void setTableBin2(float array[], int size, float l, float r, float precision);
+void setRpmTableBin(float array[], int size);
+
+#endif /* TABLE_HELPER_H_ */
diff --git a/firmware/controllers/electronic_throttle.cpp b/firmware/controllers/electronic_throttle.cpp
new file mode 100644
index 0000000000..f8024535bc
--- /dev/null
+++ b/firmware/controllers/electronic_throttle.cpp
@@ -0,0 +1,97 @@
+/**
+ * @file electronic_throttle.cpp
+ * @brief Electronic Throttle Module driver L298N
+ *
+ * todo: make this more universal if/when we get other hardware options
+ *
+ * @date Dec 7, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ */
+
+#include "main.h"
+#include "electronic_throttle.h"
+#include "boards.h"
+#include "pin_repository.h"
+#include "idle_controller.h"
+#include "tps.h"
+#include "io_pins.h"
+#include "engine_configuration.h"
+#include "pwm_generator.h"
+#include "pwm_generator_logic.h"
+
+#if EFI_ELECTRONIC_THROTTLE_BODY
+
+static Logging logger;
+/**
+ * @brief Control Thread stack
+ */
+static THD_WORKING_AREA(etbTreadStack, UTILITY_THREAD_STACK_SIZE);
+/**
+ * @brief Pulse-Width Modulation state
+ */
+static SimplePwm etbPwm;
+
+static float prevTps;
+
+extern engine_configuration_s *engineConfiguration;
+extern board_configuration_s *boardConfiguration;
+
+static msg_t etbThread(void *arg) {
+ while (TRUE) {
+ int tps = (int)getTPS();
+
+ if (tps != prevTps) {
+ prevTps = tps;
+ scheduleMsg(&logger, "tps=%d", (int) tps);
+ }
+
+ // this thread is activated 10 times per second
+ chThdSleepMilliseconds(100);
+ }
+#if defined __GNUC__
+ return -1;
+#endif
+}
+
+static void setThrottleConsole(int level) {
+ scheduleMsg(&logger, "setting throttle=%d", level);
+
+ etbPwm.multiWave.switchTimes[0] = 0.01 + (minI(level, 98)) / 100.0;
+ print("st = %f\r\n", etbPwm.multiWave.switchTimes[0]);
+}
+
+void initElectronicThrottle(void) {
+ initLogging(&logger, "Electronic Throttle");
+
+ engineConfiguration->tpsMin = 140;
+ engineConfiguration->tpsMax = 898;
+
+ // these two lines are controlling direction
+// outputPinRegister("etb1", ELECTRONIC_THROTTLE_CONTROL_1, ETB_CONTROL_LINE_1_PORT, ETB_CONTROL_LINE_1_PIN);
+// outputPinRegister("etb2", ELECTRONIC_THROTTLE_CONTROL_2, ETB_CONTROL_LINE_2_PORT, ETB_CONTROL_LINE_2_PIN);
+
+ // this line used for PWM
+ startSimplePwmExt(&etbPwm, "etb",
+ boardConfiguration->electronicThrottlePin1,
+ ELECTRONIC_THROTTLE_CONTROL_1,
+ 500,
+ 0.80);
+
+ addConsoleActionI("e", setThrottleConsole);
+ chThdCreateStatic(etbTreadStack, sizeof(etbTreadStack), NORMALPRIO, (tfunc_t) etbThread, NULL);
+}
+#endif /* EFI_ELECTRONIC_THROTTLE_BODY */
+
diff --git a/firmware/controllers/electronic_throttle.h b/firmware/controllers/electronic_throttle.h
new file mode 100644
index 0000000000..3bc57a30e1
--- /dev/null
+++ b/firmware/controllers/electronic_throttle.h
@@ -0,0 +1,13 @@
+/**
+ * @file electronic_throttle.h
+ *
+ * @date Dec 7, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef ELECTRONIC_THROTTLE_H_
+#define ELECTRONIC_THROTTLE_H_
+
+void initElectronicThrottle(void);
+
+#endif /* ELECTRONIC_THROTTLE_H_ */
diff --git a/firmware/controllers/engine_controller.cpp b/firmware/controllers/engine_controller.cpp
new file mode 100644
index 0000000000..b0c248068a
--- /dev/null
+++ b/firmware/controllers/engine_controller.cpp
@@ -0,0 +1,311 @@
+/**
+ * @file engine_controller.cpp
+ * @brief Controllers package entry point code
+ *
+ *
+ *
+ * @date Feb 7, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ */
+
+#include "main.h"
+#include "trigger_central.h"
+#include "engine_controller.h"
+#include "idle_thread.h"
+#include "rpm_calculator.h"
+#include "signal_executor.h"
+#include "main_trigger_callback.h"
+#include "map_multiplier_thread.h"
+#include "io_pins.h"
+#include "flash_main.h"
+#include "tunerstudio.h"
+#include "injector_central.h"
+#include "ignition_central.h"
+#include "rfiutil.h"
+#include "engine_configuration.h"
+#include "engine_math.h"
+#include "wave_analyzer.h"
+#include "allsensors.h"
+#include "analog_chart.h"
+#include "electronic_throttle.h"
+#include "malfunction_indicator.h"
+#include "map_averaging.h"
+#include "malfunction_central.h"
+#include "pin_repository.h"
+#include "pwm_generator.h"
+#include "adc_inputs.h"
+#include "algo.h"
+#include "efilib2.h"
+#include "ec2.h"
+#include "PwmTester.h"
+#include "engine.h"
+
+extern board_configuration_s *boardConfiguration;
+
+persistent_config_container_s persistentState CCM_OPTIONAL
+;
+
+engine_configuration_s *engineConfiguration = &persistentState.persistentConfiguration.engineConfiguration;
+board_configuration_s *boardConfiguration = &persistentState.persistentConfiguration.engineConfiguration.bc;
+
+/**
+ * CH_FREQUENCY is the number of system ticks in a second
+ */
+#define FUEL_PUMP_DELAY (4 * CH_FREQUENCY)
+
+static VirtualTimer everyMsTimer;
+static VirtualTimer fuelPumpTimer;
+
+static Logging logger;
+
+static engine_configuration2_s ec2 CCM_OPTIONAL
+;
+engine_configuration2_s * engineConfiguration2 = &ec2;
+
+static configuration_s cfg = { &persistentState.persistentConfiguration.engineConfiguration, &ec2 };
+
+configuration_s * configuration = &cfg;
+
+Engine engine;
+
+static msg_t csThread(void) {
+ chRegSetThreadName("status");
+#if EFI_SHAFT_POSITION_INPUT
+ while (TRUE) {
+ int is_cranking = isCranking();
+ int is_running = getRpm() > 0 && !is_cranking;
+ if (is_running) {
+ // blinking while running
+ setOutputPinValue(LED_RUNNING, 0);
+ chThdSleepMilliseconds(50);
+ setOutputPinValue(LED_RUNNING, 1);
+ chThdSleepMilliseconds(50);
+ } else {
+ // constant on while cranking and off if engine is stopped
+ setOutputPinValue(LED_RUNNING, is_cranking);
+ chThdSleepMilliseconds(100);
+ }
+ }
+#endif /* EFI_SHAFT_POSITION_INPUT */
+ return -1;
+}
+
+static void updateErrorCodes(void) {
+ /**
+ * technically we can set error codes right inside the getMethods, but I a bit on a fence about it
+ */
+ setError(isValidIntakeAirTemperature(getIntakeAirTemperature()), OBD_Intake_Air_Temperature_Circuit_Malfunction);
+ setError(isValidCoolantTemperature(getCoolantTemperature()), OBD_Engine_Coolant_Temperature_Circuit_Malfunction);
+}
+
+static void fanRelayControl(void) {
+ if (boardConfiguration->fanPin == GPIO_NONE)
+ return;
+
+ int isCurrentlyOn = getOutputPinValue(FAN_RELAY);
+ int newValue;
+ if (isCurrentlyOn) {
+ // if the fan is already on, we keep it on till the 'fanOff' temperature
+ newValue = getCoolantTemperature() > engineConfiguration->fanOffTemperature;
+ } else {
+ newValue = getCoolantTemperature() > engineConfiguration->fanOnTemperature;
+ }
+
+ if (isCurrentlyOn != newValue) {
+ scheduleMsg(&logger, "FAN relay: %s", newValue ? "ON" : "OFF");
+ setOutputPinValue(FAN_RELAY, newValue);
+ }
+}
+
+Overflow64Counter halTime;
+
+uint64_t getTimeNowUs(void) {
+ return halTime.get(hal_lld_get_counter_value(), false) / (CORE_CLOCK / 1000000);
+}
+
+//uint64_t getHalTimer(void) {
+// return halTime.get();
+//}
+
+efitimems_t currentTimeMillis(void) {
+ // todo: migrate to getTimeNowUs? or not?
+ return chTimeNow() / TICKS_IN_MS;
+}
+
+int getTimeNowSeconds(void) {
+ return chTimeNow() / CH_FREQUENCY;
+}
+
+static void onEvenyGeneralMilliseconds(void *arg) {
+ /**
+ * We need to push current value into the 64 bit counter often enough so that we do not miss an overflow
+ */
+ halTime.get(hal_lld_get_counter_value(), true);
+
+ if (!engine.rpmCalculator->isRunning())
+ writeToFlashIfPending();
+
+ engine.updateSlowSensors();
+
+ updateErrorCodes();
+
+ fanRelayControl();
+
+ setOutputPinValue(O2_HEATER, engine.rpmCalculator->isRunning());
+
+ // schedule next invocation
+ chVTSetAny(&everyMsTimer, boardConfiguration->generalPeriodicThreadPeriod * TICKS_IN_MS,
+ &onEvenyGeneralMilliseconds, 0);
+}
+
+static void initPeriodicEvents(void) {
+ // schedule first invocation
+ chVTSetAny(&everyMsTimer, boardConfiguration->generalPeriodicThreadPeriod * TICKS_IN_MS,
+ &onEvenyGeneralMilliseconds, 0);
+}
+
+static void fuelPumpOff(void *arg) {
+ if (getOutputPinValue(FUEL_PUMP_RELAY))
+ scheduleMsg(&logger, "fuelPump OFF at %s%d", hwPortname(boardConfiguration->fuelPumpPin));
+ turnOutputPinOff(FUEL_PUMP_RELAY);
+}
+
+static void fuelPumpOn(trigger_event_e signal, int index, void *arg) {
+ if (index != 0)
+ return; // let's not abuse the timer - one time per revolution would be enough
+ // todo: the check about GPIO_NONE should be somewhere else!
+ if (!getOutputPinValue(FUEL_PUMP_RELAY) && boardConfiguration->fuelPumpPin != GPIO_NONE)
+ scheduleMsg(&logger, "fuelPump ON at %s", hwPortname(boardConfiguration->fuelPumpPin));
+ turnOutputPinOn(FUEL_PUMP_RELAY);
+ /**
+ * the idea of this implementation is that we turn the pump when the ECU turns on or
+ * if the shafts are spinning and then we are constantly postponing the time when we
+ * will turn it off. Only if the shafts stop the turn off would actually happen.
+ */
+ chVTSetAny(&fuelPumpTimer, FUEL_PUMP_DELAY, &fuelPumpOff, 0);
+}
+
+static void initFuelPump(void) {
+ addTriggerEventListener(&fuelPumpOn, "fuel pump", NULL);
+ fuelPumpOn(SHAFT_PRIMARY_UP, 0, NULL);
+}
+
+char * getPinNameByAdcChannel(adc_channel_e hwChannel, char *buffer) {
+ strcpy((char*) buffer, portname(getAdcChannelPort(hwChannel)));
+ itoa10(&buffer[2], getAdcChannelPin(hwChannel));
+ return (char*) buffer;
+}
+
+static char pinNameBuffer[16];
+
+static void printAnalogChannelInfoExt(const char *name, adc_channel_e hwChannel, float adcVoltage) {
+ float voltage = adcVoltage * engineConfiguration->analogInputDividerCoefficient;
+ scheduleMsg(&logger, "%s ADC%d %s %s rawValue=%f/divided=%fv", name, hwChannel,
+ getAdcMode(hwChannel),
+ getPinNameByAdcChannel(hwChannel, pinNameBuffer), adcVoltage, voltage);
+}
+
+static void printAnalogChannelInfo(const char *name, adc_channel_e hwChannel) {
+ printAnalogChannelInfoExt(name, hwChannel, getVoltage(hwChannel));
+}
+
+static void printAnalogInfo(void) {
+ printAnalogChannelInfo("TPS", engineConfiguration->tpsAdcChannel);
+ printAnalogChannelInfo("CLT", engineConfiguration->cltAdcChannel);
+ printAnalogChannelInfo("IAT", engineConfiguration->iatAdcChannel);
+ printAnalogChannelInfo("MAF", engineConfiguration->mafAdcChannel);
+ printAnalogChannelInfo("AFR", engineConfiguration->afrSensor.afrAdcChannel);
+ printAnalogChannelInfo("MAP", engineConfiguration->map.sensor.hwChannel);
+ printAnalogChannelInfo("BARO", engineConfiguration->baroSensor.hwChannel);
+ printAnalogChannelInfoExt("Vbatt", engineConfiguration->vBattAdcChannel, getVBatt());
+}
+
+static THD_WORKING_AREA(csThreadStack, UTILITY_THREAD_STACK_SIZE); // declare thread stack
+
+void initEngineContoller(void) {
+ if (hasFirmwareError())
+ return;
+ initLogging(&logger, "Engine Controller");
+
+ engine.engineConfiguration = engineConfiguration;
+
+ initSensors();
+
+ initPwmGenerator();
+
+#if EFI_ANALOG_CHART
+ initAnalogChart();
+#endif /* EFI_ANALOG_CHART */
+
+ initAlgo(engineConfiguration);
+
+#if EFI_WAVE_ANALYZER
+ initWaveAnalyzer();
+#endif /* EFI_WAVE_ANALYZER */
+
+#if EFI_SHAFT_POSITION_INPUT
+ /**
+ * there is an implicit dependency on the fact that 'tachometer' listener is the 1st listener - this case
+ * other listeners can access current RPM value
+ */
+ initRpmCalculator();
+#endif /* EFI_SHAFT_POSITION_INPUT */
+
+#if EFI_TUNER_STUDIO
+ startTunerStudioConnectivity();
+#endif
+
+// multiple issues with this initMapAdjusterThread();
+ initPeriodicEvents();
+
+ chThdCreateStatic(csThreadStack, sizeof(csThreadStack), LOWPRIO, (tfunc_t) csThread, NULL);
+
+ initInjectorCentral();
+ initPwmTester();
+ initIgnitionCentral();
+ initMalfunctionCentral();
+
+#if EFI_ELECTRONIC_THROTTLE_BODY
+ initElectronicThrottle();
+#endif /* EFI_ELECTRONIC_THROTTLE_BODY */
+
+#if EFI_MALFUNCTION_INDICATOR
+ initMalfunctionIndicator();
+#endif /* EFI_MALFUNCTION_INDICATOR */
+
+#if EFI_MAP_AVERAGING
+ initMapAveraging();
+#endif /* EFI_MAP_AVERAGING */
+
+#if EFI_ENGINE_CONTROL
+ /**
+ * This method initialized the main listener which actually runs injectors & ignition
+ */
+ initMainEventListener(&engine, engineConfiguration2);
+#endif /* EFI_ENGINE_CONTROL */
+
+#if EFI_IDLE_CONTROL
+ startIdleThread();
+#else
+ scheduleMsg(&logger, "no idle control");
+#endif
+
+#if EFI_FUEL_PUMP
+ initFuelPump();
+#endif
+
+ addConsoleAction("analoginfo", printAnalogInfo);
+}
diff --git a/firmware/controllers/engine_controller.h b/firmware/controllers/engine_controller.h
new file mode 100644
index 0000000000..027c57d6e4
--- /dev/null
+++ b/firmware/controllers/engine_controller.h
@@ -0,0 +1,19 @@
+/**
+ * @file engine_controller.h
+ * @brief Controllers package entry point header
+ *
+ * @date Feb 7, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef ENGINE_STATUS_H_
+#define ENGINE_STATUS_H_
+
+#include "global.h"
+#include "signal_executor.h"
+#include "engine_configuration.h"
+
+char * getPinNameByAdcChannel(adc_channel_e hwChannel, char *buffer);
+void initEngineContoller(void);
+
+#endif /* ENGINE_STATUS_H_ */
diff --git a/firmware/controllers/error_handling.c b/firmware/controllers/error_handling.c
new file mode 100644
index 0000000000..17a78dda39
--- /dev/null
+++ b/firmware/controllers/error_handling.c
@@ -0,0 +1,89 @@
+/**
+ * @file error_handling.c
+ *
+ * @date Apr 1, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+#include "error_handling.h"
+#include "io_pins.h"
+#include "memstreams.h"
+
+#if EFI_HD44780_LCD
+#include "lcd_HD44780.h"
+#endif /* EFI_HD44780_LCD */
+
+static time_t timeOfPreviousWarning = -10;
+static Logging logger;
+
+#define WARNING_PREFIX "WARNING: "
+
+extern int warningEnabled;
+extern int main_loop_started;
+
+const char *dbg_panic_file;
+int dbg_panic_line;
+
+void chDbgPanic3(const char *msg, const char * file, int line) {
+ if (hasFatalError())
+ return;
+ dbg_panic_file = file;
+ dbg_panic_line = line;
+#if CH_DBG_SYSTEM_STATE_CHECK
+ dbg_panic_msg = msg;
+#endif /* CH_DBG_SYSTEM_STATE_CHECK */
+
+ /**
+ * low-level function is used here to reduce stack usage
+ */
+ palWritePad(LED_ERROR_PORT, LED_ERROR_PIN, 1);
+#if EFI_HD44780_LCD
+ lcdShowFatalMessage((char *) msg);
+#endif /* EFI_HD44780_LCD */
+ if (!main_loop_started) {
+ print("fatal %s %s:%d\r\n", msg, file, line);
+ chThdSleepSeconds(1);
+ chSysHalt();
+ }
+}
+
+#define WARNING_BUFFER_SIZE 80
+static char warningBuffer[WARNING_BUFFER_SIZE];
+static MemoryStream warningStream;
+
+/**
+ * @returns TRUE in case there are too many warnings
+ */
+int warning(obd_code_e code, const char *fmt, ...) {
+ int now = getTimeNowSeconds();
+ if (absI(now - timeOfPreviousWarning) < 10 || !warningEnabled)
+ return TRUE; // we just had another warning, let's not spam
+ timeOfPreviousWarning = now;
+
+ resetLogging(&logger); // todo: is 'reset' really needed here?
+ appendMsgPrefix(&logger);
+
+ va_list ap;
+ va_start(ap, fmt);
+ append(&logger, WARNING_PREFIX);
+ warningStream.eos = 0; // reset
+ chvprintf((BaseSequentialStream *) &warningStream, fmt, ap);
+ warningStream.buffer[warningStream.eos] = 0;
+ va_end(ap);
+
+ append(&logger, warningBuffer);
+ append(&logger, DELIMETER);
+ scheduleLogging(&logger);
+
+ return FALSE;
+}
+
+char *getWarninig(void) {
+ return warningBuffer;
+}
+
+void initErrorHandling(void) {
+ initLogging(&logger, "error handling");
+ msObjectInit(&warningStream, (uint8_t *)warningBuffer, WARNING_BUFFER_SIZE, 0);
+}
diff --git a/firmware/controllers/flash_main.cpp b/firmware/controllers/flash_main.cpp
new file mode 100644
index 0000000000..a679360d21
--- /dev/null
+++ b/firmware/controllers/flash_main.cpp
@@ -0,0 +1,124 @@
+/**
+ * @file flash_main.cpp
+ * @brief Higher-level logic of saving data into internal flash memory
+ *
+ *
+ * @date Sep 19, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include
+#include
+#include
+#include "flash_main.h"
+#include "eficonsole.h"
+#include "flash.h"
+#include "rusefi.h"
+
+#include "engine_controller.h"
+
+#include "datalogging.h"
+
+#include "ec2.h"
+
+static engine_type_e defaultEngineType = FORD_ASPIRE_1996;
+
+static bool needToWriteConfiguration = false;
+
+static Logging logger;
+
+extern persistent_config_container_s persistentState;
+
+extern engine_configuration_s *engineConfiguration;
+extern board_configuration_s *boardConfiguration;
+
+extern engine_configuration2_s * engineConfiguration2;
+
+#define FLASH_ADDR 0x08060000
+
+#define PERSISTENT_SIZE sizeof(persistent_config_container_s)
+
+crc_t flashStateCrc(persistent_config_container_s *state) {
+ return calc_crc((const crc_t*) &state->persistentConfiguration, sizeof(persistent_config_s));
+}
+
+void setNeedToWriteConfiguration(void) {
+ scheduleMsg(&logger, "Scheduling configuration write");
+ needToWriteConfiguration = true;
+}
+
+bool getNeedToWriteConfiguration(void) {
+ return needToWriteConfiguration;
+}
+
+void writeToFlashIfPending() {
+ if (!getNeedToWriteConfiguration()) {
+ return;
+ }
+ // todo: technically we need a lock here, realistically we should be fine.
+ needToWriteConfiguration = false;
+ scheduleMsg(&logger, "Writing pending configuration");
+ writeToFlash();
+}
+
+void writeToFlash(void) {
+#if EFI_INTERNAL_FLASH
+ persistentState.size = PERSISTENT_SIZE;
+ persistentState.version = FLASH_DATA_VERSION;
+ scheduleMsg(&logger, "flash compatible with %d", persistentState.version);
+ crc_t result = flashStateCrc(&persistentState);
+ persistentState.value = result;
+ scheduleMsg(&logger, "Reseting flash: size=%d", PERSISTENT_SIZE);
+ flashErase(FLASH_ADDR, PERSISTENT_SIZE);
+ scheduleMsg(&logger, "Flashing with CRC=%d", result);
+ efitimems_t nowMs = currentTimeMillis();
+ result = flashWrite(FLASH_ADDR, (const char *) &persistentState, PERSISTENT_SIZE);
+ scheduleMsg(&logger, "Flash programmed in (ms): %d", currentTimeMillis() - nowMs);
+ scheduleMsg(&logger, "Flashing result: %d", result);
+#endif /* EFI_INTERNAL_FLASH */
+}
+
+static int isValidCrc(persistent_config_container_s *state) {
+ if (state->version != FLASH_DATA_VERSION) {
+ scheduleMsg(&logger, "Unexpected flash version: %d", state->version);
+ return FALSE;
+ }
+ crc_t result = flashStateCrc(state);
+ int isValidCrc_b = result == state->value;
+ if (!isValidCrc_b) {
+ scheduleMsg(&logger, "CRC got %d while %d expected", result, state->value);
+ }
+ return isValidCrc_b;
+}
+
+static void doResetConfiguration(void) {
+ resetConfigurationExt(&logger, engineConfiguration->engineType, engineConfiguration, engineConfiguration2,
+ boardConfiguration);
+}
+
+void readFromFlash(void) {
+ printMsg(&logger, "readFromFlash()");
+
+ flashRead(FLASH_ADDR, (char *) &persistentState, PERSISTENT_SIZE);
+
+ //setDefaultNonPersistentConfiguration(engineConfiguration2);
+
+ if (!isValidCrc(&persistentState) || persistentState.size != PERSISTENT_SIZE) {
+ printMsg(&logger, "Need to reset flash to default");
+ resetConfigurationExt(&logger, defaultEngineType, engineConfiguration, engineConfiguration2,
+ boardConfiguration);
+ } else {
+ printMsg(&logger, "Got valid configuration from flash!");
+ applyNonPersistentConfiguration(&logger, engineConfiguration, engineConfiguration2);
+ }
+ // we can only change the state after the CRC check
+ engineConfiguration->firmwareVersion = getRusEfiVersion();
+}
+
+void initFlash(void) {
+ initLogging(&logger, "Flash memory");
+
+ addConsoleAction("readconfig", readFromFlash);
+ addConsoleAction("writeconfig", writeToFlash);
+ addConsoleAction("resetconfig", doResetConfiguration);
+}
diff --git a/firmware/controllers/flash_main.h b/firmware/controllers/flash_main.h
new file mode 100644
index 0000000000..0e25a96a93
--- /dev/null
+++ b/firmware/controllers/flash_main.h
@@ -0,0 +1,41 @@
+/**
+ * @file flash_main.h
+ * @brief
+ *
+ * @date Sep 19, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef FLASH_MAIN_H_
+#define FLASH_MAIN_H_
+
+#include "engine_configuration.h"
+
+#define FLASH_DATA_VERSION 4320
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void readFromFlash(void);
+void initFlash(void);
+/**
+ * Because of hardware-related issues, stm32f4 chip is totally
+ * frozen while we are writing to internal flash. Writing the configuration takes
+ * about 1-2 seconds, we cannot afford to do that while the engine is
+ * running so we postpone the write until the engine is stopped.
+ */
+void writeToFlash(void);
+void setNeedToWriteConfiguration(void);
+/**
+ * @return true if an flash write is pending
+ */
+bool getNeedToWriteConfiguration(void);
+void writeToFlashIfPending(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* FLASH_MAIN_H_ */
diff --git a/firmware/controllers/idle_thread.cpp b/firmware/controllers/idle_thread.cpp
new file mode 100644
index 0000000000..324d761565
--- /dev/null
+++ b/firmware/controllers/idle_thread.cpp
@@ -0,0 +1,139 @@
+/**
+ * @file idle_thread.cpp
+ * @brief Idle Air Control valve thread.
+ *
+ * This thread looks at current RPM and decides if it should increase or decrease IAC duty cycle.
+ * This file is has the hardware & scheduling logic, desired idle level lives separately
+ *
+ *
+ * @date May 23, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ */
+
+#include "main.h"
+#include "idle_controller.h"
+#include "rpm_calculator.h"
+#include "pwm_generator.h"
+#include "idle_thread.h"
+#include "pin_repository.h"
+#include "engine_configuration.h"
+#include "engine.h"
+
+static THD_WORKING_AREA(ivThreadStack, UTILITY_THREAD_STACK_SIZE);
+
+extern board_configuration_s *boardConfiguration;
+extern engine_configuration_s *engineConfiguration;
+
+/**
+ * here we keep the value we got from IDLE SWITCH input
+ */
+static volatile int idleSwitchState;
+
+static Logging logger;
+extern Engine engine;
+
+static SimplePwm idleValvePwm;
+
+/**
+ * Idle level calculation algorithm lives in idle_controller.c
+ */
+static IdleValveState idle;
+
+int getIdleSwitch() {
+ return idleSwitchState;
+}
+
+void idleDebug(char *msg, int value) {
+ printMsg(&logger, "%s%d", msg, value);
+ scheduleLogging(&logger);
+}
+
+static void setIdleControlEnabled(int value) {
+ engineConfiguration->idleMode = value ? IM_MANUAL : IM_AUTO;
+ scheduleMsg(&logger, "isIdleControlActive=%d", engineConfiguration->idleMode);
+}
+
+static void setIdleValvePwm(int value) {
+ // todo: change parameter type, maybe change parameter validation?
+ if (value < 1 || value > 999)
+ return;
+ scheduleMsg(&logger, "setting idle valve PWM %d @%d on %s", value, boardConfiguration->idleSolenoidFrequency,
+ hwPortname(boardConfiguration->idleValvePin));
+ /**
+ * currently idle level is an integer per mil (0-1000 range), and PWM takes a float in the 0..1 range
+ * todo: unify?
+ */
+ idleValvePwm.setSimplePwmDutyCycle(0.001 * value);
+}
+
+static msg_t ivThread(int param) {
+ chRegSetThreadName("IdleValve");
+
+ int currentIdleValve = -1;
+ while (TRUE) {
+ chThdSleepMilliseconds(boardConfiguration->idleThreadPeriod);
+
+ // this value is not used yet
+ idleSwitchState = palReadPad(getHwPort(boardConfiguration->idleSwitchPin), getHwPin(boardConfiguration->idleSwitchPin));
+
+ if (engineConfiguration->idleMode != IM_AUTO)
+ continue;
+
+ int nowSec = getTimeNowSeconds();
+
+ int newValue = getIdle(&idle, getRpm(), nowSec);
+
+ if (currentIdleValve != newValue) {
+ currentIdleValve = newValue;
+
+ setIdleValvePwm(newValue);
+ }
+ }
+#if defined __GNUC__
+ return -1;
+#endif
+}
+
+static void setIdleRpmAction(int value) {
+ setIdleRpm(&idle, value);
+ scheduleMsg(&logger, "target idle RPM %d", value);
+}
+
+void startIdleThread() {
+ initLogging(&logger, "Idle Valve Control");
+
+ /**
+ * Start PWM for IDLE_VALVE logical / idleValvePin physical
+ */
+ startSimplePwmExt(&idleValvePwm, "Idle Valve",
+ boardConfiguration->idleValvePin,
+ IDLE_VALVE,
+ boardConfiguration->idleSolenoidFrequency,
+ 0.5);
+
+ idleInit(&idle);
+ scheduleMsg(&logger, "initial idle %d", idle.value);
+
+ chThdCreateStatic(ivThreadStack, sizeof(ivThreadStack), NORMALPRIO, (tfunc_t)ivThread, NULL);
+
+ // this is idle switch INPUT - sometimes there is a switch on the throttle pedal
+ // this switch is not used yet
+ mySetPadMode("idle switch", getHwPort(boardConfiguration->idleSwitchPin), getHwPin(boardConfiguration->idleSwitchPin), PAL_MODE_INPUT);
+
+ addConsoleActionI("set_idle_rpm", setIdleRpmAction);
+ addConsoleActionI("set_idle_pwm", setIdleValvePwm);
+ addConsoleActionI("set_idle_enabled", setIdleControlEnabled);
+}
diff --git a/firmware/controllers/idle_thread.h b/firmware/controllers/idle_thread.h
new file mode 100644
index 0000000000..699307af55
--- /dev/null
+++ b/firmware/controllers/idle_thread.h
@@ -0,0 +1,15 @@
+/**
+ * @file idle_thread.h
+ * @brief Idle Valve Control thread
+ *
+ * @date May 23, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef IDLE_THREAD_H_
+#define IDLE_THREAD_H_
+
+void startIdleThread(void);
+int getIdleSwitch(void);
+
+#endif /* IDLE_THREAD_H_ */
diff --git a/firmware/controllers/ignition_central.c b/firmware/controllers/ignition_central.c
new file mode 100644
index 0000000000..3b5f26ff9c
--- /dev/null
+++ b/firmware/controllers/ignition_central.c
@@ -0,0 +1,41 @@
+/**
+ * @file ignition_central.c
+ * @brief TODO
+ *
+ * @date Nov 15, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ */
+
+#include "main.h"
+#include "ignition_central.h"
+#include "io_pins.h"
+#include "signal_executor.h"
+#include "main_trigger_callback.h"
+#include "engine_configuration.h"
+
+static Logging logger;
+
+extern engine_configuration_s *engineConfiguration;
+extern board_configuration_s *boardConfiguration;
+
+void initIgnitionCentral(void) {
+ initLogging(&logger, "IgnitionCentral");
+
+ for (int i = 0; i < engineConfiguration->cylindersCount; i++) {
+ io_pin_e pin = (io_pin_e)((int)SPARKOUT_1_OUTPUT + i);
+ outputPinRegisterExt2(getPinName(pin), pin, boardConfiguration->ignitionPins[i], &boardConfiguration->ignitionPinMode);
+ }
+}
diff --git a/firmware/controllers/ignition_central.h b/firmware/controllers/ignition_central.h
new file mode 100644
index 0000000000..4dd21f47a6
--- /dev/null
+++ b/firmware/controllers/ignition_central.h
@@ -0,0 +1,25 @@
+/**
+ * @file ignition_central.h
+ * @brief TODO
+ *
+ * @date Nov 15, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef IGNITION_CENTRAL_H_
+#define IGNITION_CENTRAL_H_
+
+#include "signal_executor.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void initIgnitionCentral(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* IGNITION_CENTRAL_H_ */
diff --git a/firmware/controllers/injector_central.cpp b/firmware/controllers/injector_central.cpp
new file mode 100644
index 0000000000..8afafe746b
--- /dev/null
+++ b/firmware/controllers/injector_central.cpp
@@ -0,0 +1,198 @@
+/**
+ * @file injector_central.cpp
+ * @brief Utility methods related to fuel injection.
+ *
+ *
+ * @date Sep 8, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ */
+
+#include "injector_central.h"
+#include "main.h"
+#include "io_pins.h"
+#include "signal_executor.h"
+#include "main_trigger_callback.h"
+#include "engine_configuration.h"
+#include "pin_repository.h"
+#include "ec2.h"
+
+static Logging logger;
+
+extern engine_configuration_s *engineConfiguration;
+extern board_configuration_s *boardConfiguration;
+
+static int is_injector_enabled[MAX_INJECTOR_COUNT];
+
+void assertCylinderId(int cylinderId, const char *msg) {
+ int isValid = cylinderId >= 1 && cylinderId <= engineConfiguration->cylindersCount;
+ if (!isValid) {
+ // we are here only in case of a fatal issue - at this point it is fine to make some blocking i-o
+ //scheduleSimpleMsg(&logger, "cid=", cylinderId);
+ print("ERROR [%s] cid=%d\r\n", msg, cylinderId);
+ efiAssertVoid(FALSE, "Cylinder ID");
+ }
+}
+
+///**
+// * This method schedules asynchronous fuel squirt
+// */
+
+/**
+ * @param cylinderId - from 1 to NUMBER_OF_CYLINDERS
+ */
+int isInjectorEnabled(int cylinderId) {
+ assertCylinderId(cylinderId, "isInjectorEnabled");
+ return is_injector_enabled[cylinderId - 1];
+}
+
+static void printStatus(void) {
+ for (int id = 1; id <= engineConfiguration->cylindersCount; id++) {
+ resetLogging(&logger);
+
+ appendPrintf(&logger, "injector%d%s", id, DELIMETER);
+ appendPrintf(&logger, "%d", isInjectorEnabled(id));
+ appendPrintf(&logger, DELIMETER);
+ scheduleLogging(&logger);
+ }
+}
+
+static void setInjectorEnabled(int id, int value) {
+ efiAssertVoid(id >= 0 && id < engineConfiguration->cylindersCount, "injector id");
+ is_injector_enabled[id] = value;
+ printStatus();
+}
+
+static void runBench(brain_pin_e brainPin, io_pin_e pin, float delayMs, float onTimeMs, float offTimeMs, int count) {
+ scheduleMsg(&logger, "Running bench: ON_TIME=%f ms OFF_TIME=%fms Counter=%d", onTimeMs, offTimeMs, count);
+ scheduleMsg(&logger, "output on %s", hwPortname(brainPin));
+
+ int delaySt = (int) (delayMs * CH_FREQUENCY / 1000);
+ if (delaySt != 0) {
+ chThdSleep(delaySt);
+ }
+
+ for (int i = 0; i < count; i++) {
+ setOutputPinValue(pin, TRUE);
+ chThdSleep((int) (onTimeMs * CH_FREQUENCY / 1000));
+ setOutputPinValue(pin, FALSE);
+ int offTimeSt = (int) (offTimeMs * CH_FREQUENCY / 1000);
+ if (offTimeSt > 0) {
+ chThdSleep(offTimeSt);
+ }
+ }
+ scheduleMsg(&logger, "Done!");
+}
+
+static volatile int needToRunBench = FALSE;
+static float onTime;
+static float offTime;
+static float delayMs;
+static int count;
+static brain_pin_e brainPin;
+static io_pin_e pinX;
+
+static void pinbench(const char *delayStr, const char *onTimeStr, const char *offTimeStr, const char *countStr,
+ io_pin_e pinParam, brain_pin_e brainPinParam) {
+ delayMs = atoff(delayStr);
+ onTime = atoff(onTimeStr);
+ offTime = atoff(offTimeStr);
+ count = atoi(countStr);
+
+ brainPin = brainPinParam;
+ pinX = pinParam;
+ needToRunBench = TRUE;
+}
+
+static void fuelbench2(const char *delayStr, const char *indexStr, const char * onTimeStr, const char *offTimeStr,
+ const char *countStr) {
+ int index = atoi(indexStr);
+ brain_pin_e b = boardConfiguration->injectionPins[index - 1];
+ io_pin_e p = (io_pin_e) ((int) INJECTOR_1_OUTPUT - 1 + index);
+ pinbench(delayStr, onTimeStr, offTimeStr, countStr, p, b);
+}
+
+static void fuelpumpbench(int delayParam, int onTimeParam) {
+ brainPin = boardConfiguration->fuelPumpPin;
+ pinX = FUEL_PUMP_RELAY;
+
+ delayMs = delayParam;
+ onTime = onTimeParam;
+ offTime = 0;
+ count = 1;
+
+ needToRunBench = TRUE;
+}
+
+static void fuelbench(const char * onTimeStr, const char *offTimeStr, const char *countStr) {
+ fuelbench2("0", "1", onTimeStr, offTimeStr, countStr);
+}
+
+static void sparkbench2(const char *delayStr, const char *indexStr, const char * onTimeStr, const char *offTimeStr,
+ const char *countStr) {
+ int index = atoi(indexStr);
+ brain_pin_e b = boardConfiguration->ignitionPins[index - 1];
+ io_pin_e p = (io_pin_e) ((int) SPARKOUT_1_OUTPUT - 1 + index);
+ pinbench(delayStr, onTimeStr, offTimeStr, countStr, p, b);
+}
+
+static void sparkbench(const char * onTimeStr, const char *offTimeStr, const char *countStr) {
+ sparkbench2("0", "1", onTimeStr, offTimeStr, countStr);
+}
+
+static THD_WORKING_AREA(benchThreadStack, UTILITY_THREAD_STACK_SIZE);
+
+static msg_t benchThread(int param) {
+ chRegSetThreadName("BenchThread");
+
+ while (TRUE) {
+ while (!needToRunBench) {
+ chThdSleepMilliseconds(200);
+ }
+ needToRunBench = FALSE;
+ runBench(brainPin, pinX, delayMs, onTime, offTime, count);
+ }
+#if defined __GNUC__
+ return 0;
+#endif
+}
+
+void initInjectorCentral(void) {
+ initLogging(&logger, "InjectorCentral");
+ chThdCreateStatic(benchThreadStack, sizeof(benchThreadStack), NORMALPRIO, (tfunc_t) benchThread, NULL);
+
+ for (int i = 0; i < engineConfiguration->cylindersCount; i++) {
+ is_injector_enabled[i] = true;
+ }
+
+ // todo: should we move this code closer to the injection logic?
+ for (int i = 0; i < engineConfiguration->cylindersCount; i++) {
+ io_pin_e pin = (io_pin_e) ((int) INJECTOR_1_OUTPUT + i);
+
+ outputPinRegisterExt2(getPinName(pin), pin, boardConfiguration->injectionPins[i],
+ &boardConfiguration->injectionPinMode);
+ }
+
+ printStatus();
+ addConsoleActionII("injector", setInjectorEnabled);
+
+ addConsoleActionII("fuelpumpbench", &fuelpumpbench);
+
+ addConsoleActionSSS("fuelbench", &fuelbench);
+ addConsoleActionSSS("sparkbench", &sparkbench);
+
+ addConsoleActionSSSSS("fuelbench2", &fuelbench2);
+ addConsoleActionSSSSS("sparkbench2", &sparkbench2);
+}
diff --git a/firmware/controllers/injector_central.h b/firmware/controllers/injector_central.h
new file mode 100644
index 0000000000..450b4fb5f8
--- /dev/null
+++ b/firmware/controllers/injector_central.h
@@ -0,0 +1,29 @@
+/**
+ * @file injector_central.h
+ * @brief Utility methods related to fuel injection.
+ *
+ *
+ * @date Sep 8, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+
+#ifndef INJECTOR_CENTRAL_H_
+#define INJECTOR_CENTRAL_H_
+
+#include "signal_executor.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void initInjectorCentral(void);
+int isInjectorEnabled(int cylinderId);
+void assertCylinderId(int cylinderId, const char *msg);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* INJECTOR_CENTRAL_H_ */
diff --git a/firmware/controllers/lcd_controller.cpp b/firmware/controllers/lcd_controller.cpp
new file mode 100644
index 0000000000..ec47891070
--- /dev/null
+++ b/firmware/controllers/lcd_controller.cpp
@@ -0,0 +1,196 @@
+/**
+ * @file lcd_controller.cpp
+ *
+ * @date Aug 14, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+#include "lcd_controller.h"
+#include "lcd_HD44780.h"
+#include "efilib.h"
+#include "rpm_calculator.h"
+#include "allsensors.h"
+#include "engine.h"
+#include "rtc_helper.h"
+#include "io_pins.h"
+
+extern Engine engine;
+extern engine_configuration_s *engineConfiguration;
+
+#define LCD_WIDTH 20
+// this value should be even
+#define NUMBER_OF_DIFFERENT_LINES 4
+
+char * appendStr(char *ptr, const char *suffix) {
+ for (uint32_t i = 0; i < strlen(suffix); i++) {
+ *ptr++ = suffix[i];
+ }
+ return ptr;
+}
+
+static char * prepareVBattMapLine(char *buffer) {
+ char *ptr = buffer;
+ *ptr++ = 'V';
+ ptr = ftoa(ptr, getVBatt(), 10.0f);
+
+ ptr = appendStr(ptr, " M");
+ ptr = ftoa(ptr, getRawMap(), 10.0f);
+ return ptr;
+}
+
+static char * prepareCltIatTpsLine(char *buffer) {
+ char *ptr = buffer;
+ *ptr++ = 'C';
+ ptr = ftoa(ptr, getCoolantTemperature(), 10.0f);
+
+ ptr = appendStr(ptr, " C");
+ ptr = ftoa(ptr, getIntakeAirTemperature(), 10.0f);
+
+ ptr = appendStr(ptr, " TP");
+ ptr = itoa10(ptr, (int) getTPS());
+ return ptr;
+}
+
+static const char* algorithmStr[] = { "MAF", "TPS", "MAP", "SD" };
+static const char* ignitionModeStr[] = { "1C", "IND", "WS" };
+static const char* injectionModeStr[] = { "Sim", "Seq", "Bch" };
+static const char* idleModeStr[] = { "I:A", "I:M" };
+
+static const char *getPinShortName(io_pin_e pin) {
+ switch (pin) {
+ case ALTERNATOR_SWITCH:
+ return "AL";
+ case FUEL_PUMP_RELAY:
+ return "FP";
+ case FAN_RELAY:
+ return "FN";
+ case O2_HEATER:
+ return "O2H";
+ default:
+ firmwareError("No short name for %d", (int) pin);
+ return "";
+ }
+}
+
+char * appendPinStatus(char *buffer, io_pin_e pin) {
+ char *ptr = appendStr(buffer, getPinShortName(pin));
+ int state = getOutputPinValue(pin);
+ // todo: should we handle INITIAL_PIN_STATE?
+ if (state) {
+ return appendStr(ptr, ":Y ");
+ } else {
+ return appendStr(ptr, ":n ");
+ }
+}
+
+static char * prepareInfoLine(char *buffer) {
+ char *ptr = buffer;
+
+ ptr = appendStr(ptr, algorithmStr[engineConfiguration->algorithm]);
+
+ ptr = appendStr(ptr, " ");
+ ptr = appendStr(ptr, ignitionModeStr[engineConfiguration->ignitionMode]);
+
+ ptr = appendStr(ptr, " ");
+ ptr = appendStr(ptr, injectionModeStr[engineConfiguration->injectionMode]);
+
+ ptr = appendStr(ptr, " ");
+ ptr = appendStr(ptr, idleModeStr[engineConfiguration->idleMode]);
+
+ ptr = appendStr(ptr, " ");
+ return ptr;
+}
+
+static char * prepareStatusLine(char *buffer) {
+ char *ptr = buffer;
+
+ ptr = appendPinStatus(ptr, FUEL_PUMP_RELAY);
+ ptr = appendPinStatus(ptr, FAN_RELAY);
+ ptr = appendPinStatus(ptr, O2_HEATER);
+ return ptr;
+}
+
+static char buffer[LCD_WIDTH + 4];
+static char dateBuffer[30];
+
+static void prepareCurrentSecondLine(int index) {
+ memset(buffer, ' ', LCD_WIDTH);
+ char *ptr;
+ switch (index) {
+ case 0:
+ ptr = prepareCltIatTpsLine(buffer);
+ break;
+ case 1:
+ ptr = prepareInfoLine(buffer);
+ break;
+ case 2:
+ ptr = prepareVBattMapLine(buffer);
+ break;
+ case 3:
+ ptr = prepareStatusLine(buffer);
+ break;
+ default:
+ firmwareError("unexpected case");
+ return;
+ }
+ *ptr = ' ';
+}
+
+void updateHD44780lcd(void) {
+
+ lcd_HD44780_set_position(0, 9);
+ /**
+ * this would blink so that we know the LCD is alive
+ */
+ if (getTimeNowSeconds() % 2 == 0) {
+ lcd_HD44780_print_char('R');
+ } else {
+ lcd_HD44780_print_char(' ');
+ }
+ lcd_HD44780_set_position(0, 10);
+
+ char * ptr = itoa10(buffer, getRpm());
+ ptr[0] = 0;
+ int len = ptr - buffer;
+ for (int i = 0; i < 6 - len; i++) {
+ lcd_HD44780_print_char(' ');
+ }
+ lcd_HD44780_print_string(buffer);
+
+ if (hasFirmwareError()) {
+ memcpy(buffer, getFirmwareError(), LCD_WIDTH);
+ buffer[LCD_WIDTH] = 0;
+ lcd_HD44780_set_position(1, 0);
+ lcd_HD44780_print_string(buffer);
+ return;
+ }
+
+ lcd_HD44780_set_position(1, 0);
+ memset(buffer, ' ', LCD_WIDTH);
+ memcpy(buffer, getWarninig(), LCD_WIDTH);
+ buffer[LCD_WIDTH] = 0;
+ lcd_HD44780_print_string(buffer);
+
+ if (engineConfiguration->HD44780height < 3) {
+ return;
+ }
+
+ int index = (getTimeNowSeconds() / 2) % (NUMBER_OF_DIFFERENT_LINES / 2);
+
+ prepareCurrentSecondLine(index);
+ buffer[LCD_WIDTH] = 0;
+ lcd_HD44780_set_position(2, 0);
+ lcd_HD44780_print_string(buffer);
+
+ prepareCurrentSecondLine(index + NUMBER_OF_DIFFERENT_LINES / 2);
+ buffer[LCD_WIDTH] = 0;
+ lcd_HD44780_set_position(3, 0);
+ lcd_HD44780_print_string(buffer);
+
+#if EFI_PROD_CODE
+ dateToString(dateBuffer);
+ lcd_HD44780_set_position(1, 0);
+ lcd_HD44780_print_string(dateBuffer);
+#endif /* EFI_PROD_CODE */
+}
diff --git a/firmware/controllers/lcd_controller.h b/firmware/controllers/lcd_controller.h
new file mode 100644
index 0000000000..cee3d9c48a
--- /dev/null
+++ b/firmware/controllers/lcd_controller.h
@@ -0,0 +1,13 @@
+/**
+ * @file lcd_controller.h
+ *
+ * @date Aug 14, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef LCD_CONTROLLER_H_
+#define LCD_CONTROLLER_H_
+
+void updateHD44780lcd(void);
+
+#endif /* LCD_CONTROLLER_H_ */
diff --git a/firmware/controllers/malfunction_indicator.c b/firmware/controllers/malfunction_indicator.c
new file mode 100644
index 0000000000..0359e737a4
--- /dev/null
+++ b/firmware/controllers/malfunction_indicator.c
@@ -0,0 +1,108 @@
+/**
+ * @file malfunction_indicator.c
+ * @brief We can blink out OBD-II error codes using Malfunction Indicator Light (MIL)
+ *
+ *
+ * @date Dec 20, 2013
+ * @author Konstantin Nikonenko
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ * we show 4 digit error code - 1,5sec * (4xxx+1) digit + 0,4sec * (x3xxx+1) + ....
+ * ATTENTION!!! 0 = 1 blink, 1 = 2 blinks, ...., 9 = 10 blinks
+ * sequence is the constant!!!
+ *
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ */
+
+#include "main.h"
+#include "io_pins.h"
+#include "malfunction_central.h"
+#include "malfunction_indicator.h"
+
+#if EFI_MALFUNCTION_INDICATOR
+
+#define MFI_LONG_BLINK 1500
+#define MFI_SHORT_BLINK 400
+#define MFI_BLINK_SEPARATOR 400
+#define MFI_CHECKENGINE_LIGHT 10000
+
+static THD_WORKING_AREA(mfiThreadStack, UTILITY_THREAD_STACK_SIZE); // declare thread
+
+static void blink_digits(int digit, int duration) {
+ for (int iter = 0; iter < digit; iter++) {
+ turnOutputPinOn(LED_CHECK_ENGINE);
+ chThdSleepMilliseconds(duration);
+ turnOutputPinOff(LED_CHECK_ENGINE);
+ chThdSleepMilliseconds(MFI_BLINK_SEPARATOR);
+ }
+}
+
+// calculate how many digits our code have
+static int DigitLength(int digit) {
+ int i = 0;
+ while (digit > 0) {
+ digit = digit / 10;
+ ++i;
+ }
+ return i;
+}
+
+// display code
+static void DisplayErrorCode(int length, int code) {
+ // todo: I suggest we use 'itoa' method to simplify this logic
+ for (int iter = length - 1; iter >= 0; iter--) {
+ int ourDigit = (int)pow(10, iter); // 10^0 = 1, 10^1 = 10, 10^2=100, 10^3 = 1000, ....
+ int digit = 1; // as we remember "0" we show as one blink
+ while (code >= ourDigit) {
+ code = code - ourDigit;
+ digit++;
+ }
+ if (iter % 2 == 0)
+ blink_digits(digit, MFI_SHORT_BLINK); // even 2,0 - long blink
+ else
+ blink_digits(digit, MFI_LONG_BLINK); // odd 3,1 - short blink
+ }
+}
+
+// our main thread for show check engine error
+#if defined __GNUC__
+__attribute__((noreturn)) static msg_t mfiThread(void)
+#else
+static msg_t mfiThread(void)
+#endif
+{
+ chRegSetThreadName("MFIndicator");
+ error_codes_set_s localErrorCopy;
+
+ while (TRUE) {
+ chThdSleepSeconds(10);
+
+ getErrorCodes(&localErrorCopy);
+ for (int p = 0; p < localErrorCopy.count; p++) {
+ // Calculate how many digits in this integer and display error code from start to end
+ int code = localErrorCopy.error_codes[p];
+ DisplayErrorCode(DigitLength(code), code);
+ }
+ }
+}
+
+void initMalfunctionIndicator(void) {
+ // create static thread
+ chThdCreateStatic(mfiThreadStack, sizeof(mfiThreadStack), LOWPRIO, (tfunc_t) mfiThread, NULL);
+ // only for debug
+ addError(OBD_Engine_Coolant_Temperature_Circuit_Malfunction);
+ addError(OBD_Intake_Air_Temperature_Circuit_Malfunction);
+}
+
+#endif /* EFI_MALFUNCTION_INDICATOR */
diff --git a/firmware/controllers/malfunction_indicator.h b/firmware/controllers/malfunction_indicator.h
new file mode 100644
index 0000000000..5fd3706e52
--- /dev/null
+++ b/firmware/controllers/malfunction_indicator.h
@@ -0,0 +1,32 @@
+/**
+ * @file malfunction_indicator.h
+ * @brief We can blink out OBD-II error codes using Malfunction Indicator Light (MIL)
+ *
+ *
+ * @date Dec 20, 2013
+ * @author Konstantin Nikonenko
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef MALFUNCTION_INDICATOR_H_
+#define MALFUNCTION_INDICATOR_H_
+
+#include "main.h"
+
+#if EFI_MALFUNCTIONAL_INDICATOR
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void initMalfunctionIndicator(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif
+
+
+#endif /* MALFUNCTION_INDICATOR_H_ */
diff --git a/firmware/controllers/map_averaging.cpp b/firmware/controllers/map_averaging.cpp
new file mode 100644
index 0000000000..84ace3edfc
--- /dev/null
+++ b/firmware/controllers/map_averaging.cpp
@@ -0,0 +1,189 @@
+/**
+ * @file map_averaging.cpp
+ *
+ * @date Dec 11, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ */
+
+#include "main.h"
+
+#include "map.h"
+
+#if EFI_MAP_AVERAGING || defined(__DOXYGEN__)
+
+#include "map_averaging.h"
+#include "trigger_central.h"
+#include "adc_inputs.h"
+#include "engine_state.h"
+#include "engine_configuration.h"
+#include "interpolation.h"
+#include "signal_executor.h"
+#include "engine.h"
+
+#if EFI_ANALOG_CHART
+#include "analog_chart.h"
+#endif /* EFI_ANALOG_CHART */
+
+#define FAST_MAP_CHART_SKIP_FACTOR 16
+
+static Logging logger;
+
+/**
+ * Running counter of measurements per revolution
+ */
+static volatile int perRevolutionCounter = 0;
+/**
+ * Number of measurements in previous shaft revolution
+ */
+static volatile int perRevolution = 0;
+/**
+ * Running MAP accumulator
+ * v_ for Voltage
+ */
+static volatile float v_mapAccumulator = 0;
+/**
+ * Running counter of measurements to consider for averaging
+ */
+static volatile int mapMeasurementsCounter = 0;
+
+/**
+ * v_ for Voltage
+ */
+static float v_averagedMapValue;
+
+extern Engine engine;
+
+extern engine_configuration_s *engineConfiguration;
+
+static scheduling_s startTimer[2];
+static scheduling_s endTimer[2];
+
+static void startAveraging(void*arg) {
+ bool wasLocked = lockAnyContext();
+ ;
+ // with locking we would have a consistent state
+ v_mapAccumulator = 0;
+ mapMeasurementsCounter = 0;
+ if (!wasLocked)
+ chSysUnlockFromIsr()
+ ;
+}
+
+/**
+ * This method is invoked from ADC callback.
+ * @note This method is invoked OFTEN, this method is a potential bottle-next - the implementation should be
+ * as fast as possible
+ */
+void mapAveragingCallback(adcsample_t value) {
+ /* Calculates the average values from the ADC samples.*/
+ perRevolutionCounter++;
+
+ float voltage = adcToVoltsDivided(value);
+ float currentPressure = getMapByVoltage(voltage);
+
+#if EFI_ANALOG_CHART
+ if (engineConfiguration->analogChartMode == AC_MAP)
+ if (perRevolutionCounter % FAST_MAP_CHART_SKIP_FACTOR == 0)
+ acAddData(getCrankshaftAngle(getTimeNowUs()), currentPressure);
+#endif /* EFI_ANALOG_CHART */
+
+ chSysLockFromIsr()
+ ;
+ // with locking we would have a consistent state
+
+ v_mapAccumulator += voltage;
+ mapMeasurementsCounter++;
+ chSysUnlockFromIsr()
+ ;
+}
+
+static void endAveraging(void *arg) {
+ bool wasLocked = lockAnyContext();
+ // with locking we would have a consistent state
+ v_averagedMapValue = v_mapAccumulator / mapMeasurementsCounter;
+ if (!wasLocked)
+ chSysUnlockFromIsr()
+ ;
+}
+
+/**
+ * Shaft Position callback used to schedule start and end of MAP averaging
+ */
+static void shaftPositionCallback(trigger_event_e ckpEventType, int index, void *arg) {
+ // this callback is invoked on interrupt thread
+
+ if (index != 0)
+ return;
+
+ int rpm = getRpm();
+ if (!isValidRpm(rpm))
+ return;
+
+ perRevolution = perRevolutionCounter;
+ perRevolutionCounter = 0;
+
+ MAP_sensor_config_s * config = &engineConfiguration->map;
+
+ float startAngle = interpolate2d(rpm, config->samplingAngleBins, config->samplingAngle, MAP_ANGLE_SIZE);
+ float windowAngle = interpolate2d(rpm, config->samplingWindowBins, config->samplingWindow, MAP_WINDOW_SIZE);
+ if (windowAngle <= 0) {
+ firmwareError("map sampling angle should be positive");
+ return;
+ }
+
+ int structIndex = getRevolutionCounter() % 2;
+ // todo: schedule this based on closest trigger event, same as ignition works
+ scheduleByAngle(&startTimer[structIndex], startAngle, startAveraging, NULL);
+ scheduleByAngle(&endTimer[structIndex], startAngle + windowAngle, endAveraging, NULL);
+}
+
+static void showMapStats(void) {
+ scheduleMsg(&logger, "per revolution %d", perRevolution);
+}
+
+float getMapVoltage(void) {
+ return v_averagedMapValue;
+}
+
+/**
+ * Because of MAP window averaging, MAP is only available while engine is spinning
+ * @return Manifold Absolute Pressure, in kPa
+ */
+float getMap(void) {
+ if (getRpm() == 0)
+ return getRawMap(); // maybe return NaN in case of stopped engine?
+ return getMapByVoltage(v_averagedMapValue);
+}
+
+void initMapAveraging(void) {
+ initLogging(&logger, "Map Averaging");
+
+ startTimer[0].name = "map start0";
+ startTimer[1].name = "map start1";
+ endTimer[0].name = "map end0";
+ endTimer[1].name = "map end1";
+
+ addTriggerEventListener(&shaftPositionCallback, "rpm reporter", NULL);
+ addConsoleAction("faststat", showMapStats);
+}
+
+#else
+
+float getMap(void) {
+ return getRawMap();
+}
+
+#endif /* EFI_MAP_AVERAGING */
diff --git a/firmware/controllers/map_averaging.h b/firmware/controllers/map_averaging.h
new file mode 100644
index 0000000000..bd52af40a3
--- /dev/null
+++ b/firmware/controllers/map_averaging.h
@@ -0,0 +1,23 @@
+/**
+ * @file map_averaging.h
+ *
+ * @date Dec 11, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef ADC_AVERAGING_H_
+#define ADC_AVERAGING_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void mapAveragingCallback(adcsample_t newValue);
+void initMapAveraging(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* ADC_AVERAGING_H_ */
diff --git a/firmware/controllers/map_multiplier_thread.cpp b/firmware/controllers/map_multiplier_thread.cpp
new file mode 100644
index 0000000000..55d94830be
--- /dev/null
+++ b/firmware/controllers/map_multiplier_thread.cpp
@@ -0,0 +1,85 @@
+/*
+ * @brief dead code
+ *
+ *
+ * map_multiplier.cpp
+ *
+ * @date Jul 23, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+#include "map_multiplier_thread.h"
+#include "map_adjuster.h"
+#include "rpm_calculator.h"
+#include "main_trigger_callback.h"
+#include "allsensors.h"
+#include "engine_math.h"
+#include "engine.h"
+
+extern engine_configuration_s *engineConfiguration;
+
+static Logging logger;
+
+extern Engine engine;
+
+static THD_WORKING_AREA(maThreadStack, UTILITY_THREAD_STACK_SIZE);
+
+static void mapCallback(int rpm, float key, float value) {
+ Logging *logging = &logger;
+ appendPrintf(logging, "msg%s", DELIMETER);
+
+ appendPrintf(logging, "map_adjusted: ");
+ appendPrintf(logging, "%d", rpm);
+ appendPrintf(logging, " ");
+ appendPrintf(logging, "%d", 100 * key);
+ appendPrintf(logging, " ");
+ appendPrintf(logging, "%d", 100 * value);
+
+ appendMsgPostfix(logging);
+ scheduleLogging(logging);
+}
+
+static int timeAtNotRunning = 0;
+
+static int isNewState = TRUE;
+
+static void maThread(int param) {
+ chRegSetThreadName("map adjustment");
+
+ while (TRUE) {
+ chThdSleepMilliseconds(100);
+
+ systime_t now = chTimeNow();
+ if (!isRunning()) {
+ timeAtNotRunning = now;
+ continue;
+ }
+
+ int wasNotRunningRecently = overflowDiff(now, timeAtNotRunning) < 60 * CH_FREQUENCY;
+ if (!wasNotRunningRecently)
+ continue;
+ if (isNewState)
+ scheduleMsg(&logger, "starting fuel map adjustment at %d", now);
+ isNewState = FALSE;
+
+ // ideally this should be atomic, but hopefully it's good enough
+ int rpm = getRpm();
+ float load = getEngineLoad();
+ float afr = getAfr();
+
+ addAfr(rpm, load, afr);
+ int total = runMapAdjustments(mapCallback);
+ if (total > 0) {
+// scheduleSimpleMsg(&logger, "map adjusted for maf ", 100 * key);
+ }
+ }
+}
+
+void initMapAdjusterThread(void) {
+ initLogging(&logger, "Map self learning thread");
+
+ initMapAdjuster();
+
+ chThdCreateStatic(maThreadStack, sizeof(maThreadStack), NORMALPRIO, (tfunc_t)maThread, NULL);
+}
diff --git a/firmware/controllers/map_multiplier_thread.h b/firmware/controllers/map_multiplier_thread.h
new file mode 100644
index 0000000000..4ee5d00149
--- /dev/null
+++ b/firmware/controllers/map_multiplier_thread.h
@@ -0,0 +1,15 @@
+/**
+ * @file map_multiplier.h
+ * @brief dead code
+ *
+ *
+ * @date Jul 23, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef MAP_MULTIPLIER_H_
+#define MAP_MULTIPLIER_H_
+
+void initMapAdjusterThread(void);
+
+#endif /* MAP_MULTIPLIER_H_ */
diff --git a/firmware/controllers/math/efitime.h b/firmware/controllers/math/efitime.h
new file mode 100644
index 0000000000..d39a9aa34c
--- /dev/null
+++ b/firmware/controllers/math/efitime.h
@@ -0,0 +1,61 @@
+/**
+ * @file efitime.h
+ *
+ * By the way, there are 86400000 milliseconds in a day
+ *
+ * @date Apr 14, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef EFITIME_H_
+#define EFITIME_H_
+
+#include
+#include "efifeatures.h"
+
+/**
+ * integer time in milliseconds
+ * 32 bit 4B / 1000 = 4M seconds = 1111.11 hours = 46 days.
+ * Please restart your ECU every 46 days? :)
+ */
+typedef uint32_t efitimems_t;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+#define US_PER_SECOND 1000000
+
+#define MS2US(MS_TIME) ((MS_TIME) * 1000)
+
+#define US_TO_TI_TEMP 10
+
+// todo: implement a function to work with times considering counter overflow
+#define overflowDiff(now, time) ((now) - (time))
+
+/**
+ * 64-bit counter of microseconds (1/1 000 000 of a second) since MCU reset
+ *
+ * By using 64 bit, we can achive a very precise timestamp which does not overflow.
+ * The primary implementation counts the number of CPU cycles from MCU reset.
+ */
+uint64_t getTimeNowUs(void);
+
+uint64_t getHalTimer(void);
+
+/**
+ * @brief Returns the number of milliseconds since the board initialization.
+ */
+efitimems_t currentTimeMillis(void);
+
+/**
+ * @brief Current system time in seconds.
+ */
+int getTimeNowSeconds(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* EFITIME_H_ */
diff --git a/firmware/controllers/math/engine_math.cpp b/firmware/controllers/math/engine_math.cpp
new file mode 100644
index 0000000000..62f211aabd
--- /dev/null
+++ b/firmware/controllers/math/engine_math.cpp
@@ -0,0 +1,394 @@
+/**
+ * @file engine_math.cpp
+ * @brief
+ *
+ * @date Jul 13, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ */
+
+#include "main.h"
+#include "engine_math.h"
+#include "engine_configuration.h"
+#include "interpolation.h"
+#include "allsensors.h"
+#include "io_pins.h"
+#include "OutputSignalList.h"
+#include "trigger_decoder.h"
+#include "event_registry.h"
+
+/*
+ * default Volumetric Efficiency
+ */
+//float getDefaultVE(int rpm) {
+// if (rpm > 5000)
+// return interpolate(5000, 1.1, 8000, 1, rpm);
+// return interpolate(500, 0.5, 5000, 1.1, rpm);
+//}
+/**
+ * @return time needed to rotate crankshaft by one degree, in milliseconds.
+ * @deprecated
+ */
+float getOneDegreeTimeMs(int rpm) {
+ return 1000.0f * 60 / 360 / rpm;
+}
+
+/**
+ * @return time needed to rotate crankshaft by one degree, in microseconds.
+ */
+float getOneDegreeTimeUs(int rpm) {
+ return 1000000.0f * 60 / 360 / rpm;
+}
+
+/**
+ * @return number of milliseconds in one crankshaft revolution
+ */
+float getCrankshaftRevolutionTimeMs(int rpm) {
+ return 360 * getOneDegreeTimeMs(rpm);
+}
+
+/**
+ * @brief Shifts angle into the [0..720) range
+ * TODO: should be 'crankAngleRange' range?
+ */
+float fixAngle(float angle) {
+ // I guess this implementation would be faster than 'angle % 720'
+ while (angle < 0)
+ angle += 720;
+ while (angle >= 720)
+ angle -= 720;
+ return angle;
+}
+
+/**
+ * @brief Returns engine load according to selected engine_load_mode
+ *
+ */
+float getEngineLoadT(Engine *engine) {
+ efiAssert(engine!=NULL, "engine 2NULL", NAN);
+ engine_configuration_s *engineConfiguration = engine->engineConfiguration;
+ efiAssert(engineConfiguration!=NULL, "engineConfiguration 2NULL", NAN);
+ switch (engineConfiguration->algorithm) {
+ case LM_MAF:
+ return getMafT(engineConfiguration);
+ case LM_SPEED_DENSITY:
+ // SD engine load is used for timing lookup but not for fuel calculation
+ case LM_MAP:
+ return getMap();
+ case LM_TPS:
+ return getTPS();
+ default:
+ firmwareError("Unexpected engine load parameter: %d", engineConfiguration->algorithm);
+ return -1;
+ }
+}
+
+void setSingleCoilDwell(engine_configuration_s *engineConfiguration) {
+ for (int i = 0; i < DWELL_CURVE_SIZE; i++) {
+ engineConfiguration->sparkDwellBins[i] = 0;
+ engineConfiguration->sparkDwell[i] = -1;
+ }
+
+ engineConfiguration->sparkDwellBins[5] = 1;
+ engineConfiguration->sparkDwell[5] = 4;
+
+ engineConfiguration->sparkDwellBins[6] = 4500;
+ engineConfiguration->sparkDwell[6] = 4;
+
+ engineConfiguration->sparkDwellBins[7] = 12500;
+ engineConfiguration->sparkDwell[7] = 0;
+}
+
+int isCrankingRT(engine_configuration_s *engineConfiguration, int rpm) {
+ return rpm > 0 && rpm < engineConfiguration->crankingSettings.crankingRpm;
+}
+
+OutputSignalList injectonSignals CCM_OPTIONAL;
+
+static void registerSparkEvent(engine_configuration_s const *engineConfiguration, trigger_shape_s * s,
+ IgnitionEventList *list, io_pin_e pin, float localAdvance, float dwell) {
+
+ IgnitionEvent *event = list->getNextActuatorEvent();
+ if (event == NULL)
+ return; // error already reported
+
+ event->io_pin = pin;
+
+ event->advance = localAdvance;
+
+ findTriggerPosition(engineConfiguration, s, &event->dwellPosition, localAdvance - dwell);
+}
+
+void initializeIgnitionActions(float advance, float dwellAngle, engine_configuration_s *engineConfiguration,
+ engine_configuration2_s *engineConfiguration2, IgnitionEventList *list) {
+
+ efiAssertVoid(engineConfiguration->cylindersCount > 0, "cylindersCount");
+
+ list->resetEventList();
+
+ switch (engineConfiguration->ignitionMode) {
+ case IM_ONE_COIL:
+ for (int i = 0; i < engineConfiguration->cylindersCount; i++) {
+ // todo: extract method
+ float localAdvance = advance + 720.0f * i / engineConfiguration->cylindersCount;
+
+ registerSparkEvent(engineConfiguration, &engineConfiguration2->triggerShape, list,
+ SPARKOUT_1_OUTPUT, localAdvance, dwellAngle);
+ }
+ break;
+ case IM_WASTED_SPARK:
+ for (int i = 0; i < engineConfiguration->cylindersCount; i++) {
+ float localAdvance = advance + 720.0f * i / engineConfiguration->cylindersCount;
+
+ int wastedIndex = i % (engineConfiguration->cylindersCount / 2);
+
+ int id = getCylinderId(engineConfiguration->firingOrder, wastedIndex) - 1;
+ io_pin_e ioPin = (io_pin_e) (SPARKOUT_1_OUTPUT + id);
+
+ registerSparkEvent(engineConfiguration, &engineConfiguration2->triggerShape, list,
+ ioPin, localAdvance, dwellAngle);
+
+ }
+
+ break;
+ case IM_INDIVIDUAL_COILS:
+ for (int i = 0; i < engineConfiguration->cylindersCount; i++) {
+ float localAdvance = advance + 720.0f * i / engineConfiguration->cylindersCount;
+
+ io_pin_e pin = (io_pin_e) ((int) SPARKOUT_1_OUTPUT + getCylinderId(engineConfiguration->firingOrder, i) - 1);
+ registerSparkEvent(engineConfiguration, &engineConfiguration2->triggerShape, list, pin,
+ localAdvance, dwellAngle);
+ }
+ break;
+
+ default:
+ firmwareError("unsupported ignitionMode %d in initializeIgnitionActions()", engineConfiguration->ignitionMode);
+ }
+}
+
+static void registerInjectionEvent(engine_configuration_s const *e,
+ trigger_shape_s *s,
+ ActuatorEventList *list,
+ io_pin_e pin,
+ float angle
+ ) {
+ registerActuatorEventExt(e, s, list->getNextActuatorEvent(), injectonSignals.add(pin), angle);
+
+}
+
+float getFuelMultiplier(engine_configuration_s const *e, injection_mode_e mode) {
+ switch(mode) {
+ case IM_SEQUENTIAL:
+ return 1;
+ case IM_SIMULTANEOUS:
+ // todo: pre-calculate and save into ec2?
+ return 1.0 / e->cylindersCount;
+ case IM_BATCH:
+ return 2.0 / e->cylindersCount;
+ default:
+ firmwareError("Unexpected getFuelMultiplier %d", mode);
+ return NAN;
+ }
+}
+
+void addFuelEvents(engine_configuration_s const *e, engine_configuration2_s *engineConfiguration2,
+ ActuatorEventList *list, injection_mode_e mode) {
+ list->resetEventList();
+
+ trigger_shape_s *s = &engineConfiguration2->triggerShape;
+
+ float baseAngle = e->globalTriggerAngleOffset + e->injectionOffset;
+
+ switch (mode) {
+ case IM_SEQUENTIAL:
+ for (int i = 0; i < e->cylindersCount; i++) {
+ io_pin_e pin = (io_pin_e) ((int) INJECTOR_1_OUTPUT + getCylinderId(e->firingOrder, i) - 1);
+ float angle = baseAngle + i * 720.0 / e->cylindersCount;
+ registerInjectionEvent(e, s, list, pin, angle);
+ }
+ break;
+ case IM_SIMULTANEOUS:
+ for (int i = 0; i < e->cylindersCount; i++) {
+ float angle = baseAngle + i * 720.0 / e->cylindersCount;
+
+ for (int j = 0; j < e->cylindersCount; j++) {
+ io_pin_e pin = (io_pin_e) ((int) INJECTOR_1_OUTPUT + j);
+ registerInjectionEvent(e, s, list, pin, angle);
+ }
+ }
+ break;
+ case IM_BATCH:
+ for (int i = 0; i < e->cylindersCount; i++) {
+ int index = i % (e->cylindersCount / 2);
+ io_pin_e pin = (io_pin_e) ((int) INJECTOR_1_OUTPUT + index);
+ float angle = baseAngle + i * 720.0 / e->cylindersCount;
+ registerInjectionEvent(e, s, list, pin, angle);
+
+ /**
+ * also fire the 2nd half of the injectors so that we can implement a batch mode on individual wires
+ */
+ pin = (io_pin_e) ((int) INJECTOR_1_OUTPUT + index + (e->cylindersCount / 2));
+ registerInjectionEvent(e, s, list, pin, angle);
+ }
+ break;
+ default:
+ firmwareError("Unexpected injection mode %d", mode);
+ }
+}
+
+/**
+ * @return Spark dwell time, in milliseconds.
+ */
+float getSparkDwellMsT(engine_configuration_s *engineConfiguration, int rpm) {
+ if (isCrankingR(rpm)) {
+ // technically this could be implemented via interpolate2d
+ float angle = engineConfiguration->crankingChargeAngle;
+ return getOneDegreeTimeMs(rpm) * angle;
+ }
+ efiAssert(!cisnan(rpm), "invalid rpm", NAN);
+
+ return interpolate2d(rpm, engineConfiguration->sparkDwellBins, engineConfiguration->sparkDwell, DWELL_CURVE_SIZE);
+}
+
+int getEngineCycleEventCount2(operation_mode_e mode, trigger_shape_s * s) {
+ return mode == FOUR_STROKE_CAM_SENSOR ? s->getSize() : 2 * s->getSize();
+}
+
+/**
+ * Trigger event count equals engine cycle event count if we have a cam sensor.
+ * Two trigger cycles make one engine cycle in case of a four stroke engine If we only have a cranksensor.
+ */
+int getEngineCycleEventCount(engine_configuration_s const *engineConfiguration, trigger_shape_s * s) {
+ return getEngineCycleEventCount2(getOperationMode(engineConfiguration), s);
+}
+
+void findTriggerPosition(engine_configuration_s const *engineConfiguration, trigger_shape_s * s,
+ event_trigger_position_s *position, float angleOffset) {
+
+ angleOffset = fixAngle(angleOffset + engineConfiguration->globalTriggerAngleOffset);
+
+ int engineCycleEventCount = getEngineCycleEventCount(engineConfiguration, s);
+
+ int middle;
+ int left = 0;
+ int right = engineCycleEventCount - 1;
+
+ /**
+ * Let's find the last trigger angle which is less or equal to the desired angle
+ * todo: extract binary search as template method?
+ */
+ while (true) {
+ middle = (left + right) / 2;
+
+ if (middle == left) {
+ break;
+ }
+
+ if (angleOffset < s->eventAngles[middle]) {
+ right = middle;
+ } else if (angleOffset > s->eventAngles[middle]) {
+ left = middle;
+ } else {
+ break;
+ }
+
+ }
+
+ float eventAngle = s->eventAngles[middle];
+
+ if (angleOffset < eventAngle) {
+ firmwareError("angle constraint violation in registerActuatorEventExt(): %f/%f", angleOffset, eventAngle);
+ return;
+ }
+
+ position->eventIndex = middle;
+ position->eventAngle = eventAngle;
+ position->angleOffset = angleOffset - eventAngle;
+}
+
+void registerActuatorEventExt(engine_configuration_s const *engineConfiguration, trigger_shape_s * s, ActuatorEvent *e,
+ OutputSignal *actuator, float angleOffset) {
+ efiAssertVoid(s->getSize() > 0, "uninitialized trigger_shape_s");
+
+ if (e == NULL) {
+ // error already reported
+ return;
+ }
+ e->actuator = actuator;
+
+ findTriggerPosition(engineConfiguration, s, &e->position, angleOffset);
+}
+
+static int order_1_THEN_3_THEN_4_THEN2[] = { 1, 3, 4, 2 };
+
+static int order_1_THEN_5_THEN_3_THEN_6_THEN_2_THEN_4[] = { 1, 5, 3, 6, 2, 4 };
+
+static int order_1_8_4_3_6_5_7_2[] = {1, 8, 4, 3, 6, 5, 7, 2};
+
+/**
+ * @param index from zero to cylindersCount - 1
+ * @return cylinderId from one to cylindersCount
+ */
+int getCylinderId(firing_order_e firingOrder, int index) {
+
+ switch (firingOrder) {
+ case FO_ONE_CYLINDER:
+ return 1;
+ case FO_1_THEN_3_THEN_4_THEN2:
+ return order_1_THEN_3_THEN_4_THEN2[index];
+ case FO_1_THEN_5_THEN_3_THEN_6_THEN_2_THEN_4:
+ return order_1_THEN_5_THEN_3_THEN_6_THEN_2_THEN_4[index];
+ case FO_1_8_4_3_6_5_7_2:
+ return order_1_8_4_3_6_5_7_2[index];
+
+ default:
+ firmwareError("getCylinderId not supported for %d", firingOrder);
+ }
+ return -1;
+}
+
+void prepareOutputSignals(engine_configuration_s *engineConfiguration, engine_configuration2_s *engineConfiguration2) {
+
+ // todo: move this reset into decoder
+ engineConfiguration2->triggerShape.calculateTriggerSynchPoint(&engineConfiguration->triggerConfig);
+
+ injectonSignals.clear();
+ EventHandlerConfiguration *config = &engineConfiguration2->engineEventConfiguration;
+ addFuelEvents(engineConfiguration, engineConfiguration2, &config->crankingInjectionEvents,
+ engineConfiguration->crankingInjectionMode);
+ addFuelEvents(engineConfiguration, engineConfiguration2, &config->injectionEvents,
+ engineConfiguration->injectionMode);
+}
+
+void setFuelRpmBin(engine_configuration_s *engineConfiguration, float l, float r) {
+ setTableBin(engineConfiguration->fuelRpmBins, FUEL_RPM_COUNT, l, r);
+}
+
+void setFuelLoadBin(engine_configuration_s *engineConfiguration, float l, float r) {
+ setTableBin(engineConfiguration->fuelLoadBins, FUEL_LOAD_COUNT, l, r);
+}
+
+void setTimingRpmBin(engine_configuration_s *engineConfiguration, float l, float r) {
+ setTableBin(engineConfiguration->ignitionRpmBins, IGN_RPM_COUNT, l, r);
+}
+
+void setTimingLoadBin(engine_configuration_s *engineConfiguration, float l, float r) {
+ setTableBin(engineConfiguration->ignitionLoadBins, IGN_LOAD_COUNT, l, r);
+}
+
+int isInjectionEnabled(engine_configuration_s *engineConfiguration) {
+ // todo: is this worth a method? should this be inlined?
+ return engineConfiguration->isInjectionEnabled;
+}
diff --git a/firmware/controllers/math/engine_math.h b/firmware/controllers/math/engine_math.h
new file mode 100644
index 0000000000..4f3a19dbd7
--- /dev/null
+++ b/firmware/controllers/math/engine_math.h
@@ -0,0 +1,61 @@
+/**
+ * @file engine_math.h
+ *
+ * @date Jul 13, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef ENGINE_MATH_H_
+#define ENGINE_MATH_H_
+
+#include "engine_configuration.h"
+
+#ifdef __cplusplus
+#include "ec2.h"
+#include "trigger_structure.h"
+#include "table_helper.h"
+#include "engine.h"
+
+void findTriggerPosition(engine_configuration_s const *engineConfiguration, trigger_shape_s * s,
+ event_trigger_position_s *position, float angleOffset);
+
+int isInjectionEnabled(engine_configuration_s *engineConfiguration);
+
+float fixAngle(float angle);
+
+#endif
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+float getDefaultFuel(int rpm, float map);
+
+float getOneDegreeTimeMs(int rpm);
+float getOneDegreeTimeUs(int rpm);
+float getCrankshaftRevolutionTimeMs(int rpm);
+
+int isCrankingRT(engine_configuration_s *engineConfiguration, int rpm);
+#define isCrankingR(rpm) isCrankingRT(engineConfiguration, rpm)
+
+float getEngineLoadT(Engine *engine);
+#define getEngineLoad() getEngineLoadT(&engine)
+
+float getSparkDwellMsT(engine_configuration_s *engineConfiguration, int rpm);
+#define getSparkDwellMs(rpm) getSparkDwellMsT(engineConfiguration, rpm)
+
+int getCylinderId(firing_order_e firingOrder, int index);
+
+void setFuelRpmBin(engine_configuration_s *engineConfiguration, float l, float r);
+void setFuelLoadBin(engine_configuration_s *engineConfiguration, float l, float r);
+void setTimingRpmBin(engine_configuration_s *engineConfiguration, float l, float r);
+void setTimingLoadBin(engine_configuration_s *engineConfiguration, float l, float r);
+
+void setSingleCoilDwell(engine_configuration_s *engineConfiguration);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* ENGINE_MATH_H_ */
diff --git a/firmware/controllers/math/math.mk b/firmware/controllers/math/math.mk
new file mode 100644
index 0000000000..b28c1311f6
--- /dev/null
+++ b/firmware/controllers/math/math.mk
@@ -0,0 +1,5 @@
+
+CONTROLLERS_MATH_SRC =
+
+CONTROLLERS_MATH_SRC_CPP = $(PROJECT_DIR)/controllers/math/engine_math.cpp \
+ $(PROJECT_DIR)/controllers/math/speed_density.cpp
diff --git a/firmware/controllers/math/speed_density.cpp b/firmware/controllers/math/speed_density.cpp
new file mode 100644
index 0000000000..1aecf8a828
--- /dev/null
+++ b/firmware/controllers/math/speed_density.cpp
@@ -0,0 +1,96 @@
+/**
+ * @file speed_density.cpp
+ *
+ * @date May 29, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+#include "speed_density.h"
+#include "interpolation.h"
+#include "rpm_calculator.h"
+#include "engine_math.h"
+#include "engine_state.h"
+
+#define K_AT_MIN_RPM_MIN_TPS 0.25
+#define K_AT_MIN_RPM_MAX_TPS 0.25
+#define K_AT_MAX_RPM_MIN_TPS 0.25
+#define K_AT_MAX_RPM_MAX_TPS 0.9
+
+#define rpmMin 500
+#define rpmMax 8000
+
+static Map3D1616 veMap;
+static Map3D1616 afrMap;
+
+#define tpMin 0
+#define tpMax 100
+// http://rusefi.com/math/t_charge.html
+float getTCharge(int rpm, float tps, float coolantTemp, float airTemp) {
+ float minRpmKcurrentTPS = interpolate(tpMin, K_AT_MIN_RPM_MIN_TPS, tpMax,
+ K_AT_MIN_RPM_MAX_TPS, tps);
+ float maxRpmKcurrentTPS = interpolate(tpMin, K_AT_MAX_RPM_MIN_TPS, tpMax,
+ K_AT_MAX_RPM_MAX_TPS, tps);
+
+ float Tcharge_coff = interpolate(rpmMin, minRpmKcurrentTPS, rpmMax, maxRpmKcurrentTPS, rpm);
+
+ float Tcharge = coolantTemp * (1 - Tcharge_coff) + airTemp * Tcharge_coff;
+
+ return Tcharge;
+}
+
+/**
+ * is J/g*K
+ */
+#define GAS_R 0.28705
+
+/**
+ * @return value in seconds
+ */
+float sdMath(engine_configuration_s *engineConfiguration, float VE, float MAP, float AFR, float temp) {
+ if (MAP < 0.001 || cisnan(MAP)) {
+ warning(OBD_PCM_Processor_Fault, "invalid MAP value");
+ return 0;
+ }
+
+ float injectorFlowRate = cc_minute_to_gramm_second(engineConfiguration->injectorFlow);
+ float Vol = engineConfiguration->displacement / engineConfiguration->cylindersCount;
+ return (Vol * VE * MAP) / (AFR * injectorFlowRate * GAS_R * temp);
+}
+
+/**
+ * @return value in Milliseconds
+ */
+float getSpeedDensityFuel(Engine *engine, int rpm) {
+ //int rpm = engine->rpmCalculator->rpm();
+
+ engine_configuration_s *engineConfiguration = engine->engineConfiguration;
+
+ float tps = getTPS();
+ float coolantC = getCoolantTemperature();
+ float intakeC = getIntakeAirTemperature();
+ float tChargeK = convertCelsiusToKelvin(getTCharge(rpm, tps, coolantC, intakeC));
+ float map = getMap();
+ float VE = veMap.getValue(map, engineConfiguration->veLoadBins, rpm,
+ engineConfiguration->veRpmBins);
+ float AFR = afrMap.getValue(map, engineConfiguration->afrLoadBins, rpm,
+ engineConfiguration->afrRpmBins);
+
+ return sdMath(engine->engineConfiguration, VE, map, AFR, tChargeK) * 1000;
+}
+
+void setDetaultVETable(engine_configuration_s *engineConfiguration) {
+ setRpmTableBin(engineConfiguration->veRpmBins, VE_RPM_COUNT);
+ setTableBin2(engineConfiguration->veLoadBins, VE_LOAD_COUNT, 10, 300, 1);
+
+ setRpmTableBin(engineConfiguration->afrRpmBins, AFR_RPM_COUNT);
+ setTableBin2(engineConfiguration->afrLoadBins, VE_LOAD_COUNT, 10, 300, 1);
+
+ veMap.setAll(0.8);
+ afrMap.setAll(14.7);
+}
+
+void initSpeedDensity(engine_configuration_s *engineConfiguration) {
+ veMap.init(engineConfiguration->veTable);
+ afrMap.init(engineConfiguration->afrTable);
+}
diff --git a/firmware/controllers/math/speed_density.h b/firmware/controllers/math/speed_density.h
new file mode 100644
index 0000000000..7f10c30531
--- /dev/null
+++ b/firmware/controllers/math/speed_density.h
@@ -0,0 +1,26 @@
+/**
+ * @file speed_density.h
+ *
+ * @date May 29, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+#ifndef SPEED_DENSITY_H_
+#define SPEED_DENSITY_H_
+
+#include "engine_configuration.h"
+#include "ec2.h"
+#include "engine.h"
+
+float getTCharge(int rpm, float tps, float coolantTemp, float airTemp);
+void setDetaultVETable(engine_configuration_s *engineConfiguration);
+float sdMath(engine_configuration_s *engineConfiguration, float VE, float MAP, float AFR, float temp);
+
+#define gramm_second_to_cc_minute(gs) ((gs) / 0.0119997981)
+
+#define cc_minute_to_gramm_second(ccm) ((ccm) * 0.0119997981)
+
+void setDetaultVETable(engine_configuration_s *engineConfiguration);
+void initSpeedDensity(engine_configuration_s *engineConfiguration);
+float getSpeedDensityFuel(Engine *engine, int rpm);
+
+#endif /* SPEED_DENSITY_H_ */
diff --git a/firmware/controllers/obd2viaCAN.c b/firmware/controllers/obd2viaCAN.c
new file mode 100644
index 0000000000..718c2e4897
--- /dev/null
+++ b/firmware/controllers/obd2viaCAN.c
@@ -0,0 +1,14 @@
+/**
+ * @file obd2viaCAN.c
+ *
+ *
+ * http://en.wikipedia.org/wiki/OBD-II_PIDs#CAN_.2811-bit.29_Bus_format
+ * standards.sae.org/j2284/
+ *
+ * @date Feb 14, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+#include "can_hw.h"
+
diff --git a/firmware/controllers/sensors/allsensors.c b/firmware/controllers/sensors/allsensors.c
new file mode 100644
index 0000000000..2a43759510
--- /dev/null
+++ b/firmware/controllers/sensors/allsensors.c
@@ -0,0 +1,14 @@
+/**
+ * @file allsensors.h
+ * @brief
+ *
+ *
+ * @date Nov 15, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "allsensors.h"
+
+void initSensors(void) {
+ initThermistors();
+}
diff --git a/firmware/controllers/sensors/allsensors.h b/firmware/controllers/sensors/allsensors.h
new file mode 100644
index 0000000000..9d9a41ed2d
--- /dev/null
+++ b/firmware/controllers/sensors/allsensors.h
@@ -0,0 +1,40 @@
+/**
+ * @file allsensors.h
+ * @brief
+ *
+ * This file should have been called 'sensors.h' but there is some conflict
+ * with standard win32 header :(
+ *
+ *
+ * @date Nov 15, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef SENSORS_H_
+#define SENSORS_H_
+
+#include "tps.h"
+#include "map.h"
+#include "maf.h"
+#include "ego.h"
+#include "voltage.h"
+#include "thermistors.h"
+#include "adc_inputs.h"
+
+#if EFI_PROD_CODE || EFI_SIMULATOR
+#include "adc_math.h"
+#endif
+
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void initSensors(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /*SENSORS_H_*/
diff --git a/firmware/controllers/sensors/ego.cpp b/firmware/controllers/sensors/ego.cpp
new file mode 100644
index 0000000000..d15bfd83a8
--- /dev/null
+++ b/firmware/controllers/sensors/ego.cpp
@@ -0,0 +1,40 @@
+#include "main.h"
+#include "engine_configuration.h"
+#include "interpolation.h"
+#include "boards.h"
+#include "adc_inputs.h"
+
+extern engine_configuration_s *engineConfiguration;
+
+float getAfr(void) {
+ afr_sensor_s * sensor = &engineConfiguration->afrSensor;
+
+ float volts = getVoltageDivided(sensor->afrAdcChannel);
+
+ return interpolate(sensor->v1, sensor->value1, sensor->v2, sensor->value2, volts);
+}
+
+void initEgoSensor(afr_sensor_s *sensor, ego_sensor_e type) {
+
+ switch (type) {
+ case ES_BPSX_D1:
+ /**
+ * This decodes BPSX D1 Wideband Controller analog signal
+ */
+ sensor->v1 = 0;
+ sensor->value1 = 9;
+ sensor->v2 = 5;
+ sensor->value2 = 19;
+ break;
+
+ case ES_Innovate_MTX_L:
+ sensor->v1 = 0;
+ sensor->value1 = 7.35;
+ sensor->v2 = 5;
+ sensor->value2 = 22.39;
+ break;
+ default:
+ firmwareError("Unexpected EGO %d", type);
+ break;
+ }
+}
diff --git a/firmware/controllers/sensors/ego.h b/firmware/controllers/sensors/ego.h
new file mode 100644
index 0000000000..606b56ab99
--- /dev/null
+++ b/firmware/controllers/sensors/ego.h
@@ -0,0 +1,20 @@
+/**
+ * @file ego.h
+ * @brief
+ *
+ *
+ * @date Nov 15, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef EGO_H_
+#define EGO_H_
+
+#include "main.h"
+#include "rusefi_enums.h"
+#include "engine_configuration.h"
+
+float getAfr(void);
+void initEgoSensor(afr_sensor_s *sensor, ego_sensor_e type);
+
+#endif
diff --git a/firmware/controllers/sensors/maf.c b/firmware/controllers/sensors/maf.c
new file mode 100644
index 0000000000..fe84c4541f
--- /dev/null
+++ b/firmware/controllers/sensors/maf.c
@@ -0,0 +1,11 @@
+#include "main.h"
+#include "boards.h"
+#include "engine_configuration.h"
+#include "adc_inputs.h"
+#include "maf.h"
+
+extern engine_configuration_s *engineConfiguration;
+
+float getMaf(void) {
+ return getMafT(engineConfiguration);
+}
diff --git a/firmware/controllers/sensors/maf.h b/firmware/controllers/sensors/maf.h
new file mode 100644
index 0000000000..2a602c26cf
--- /dev/null
+++ b/firmware/controllers/sensors/maf.h
@@ -0,0 +1,28 @@
+/**
+ * @file maf.h
+ * @brief
+ *
+ *
+ * @date Nov 15, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef MAF_H_
+#define MAF_H_
+
+#include "main.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+#define getMafT(ec) (getVoltageDivided(ec->mafAdcChannel))
+
+float getMaf(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif
diff --git a/firmware/controllers/sensors/map.cpp b/firmware/controllers/sensors/map.cpp
new file mode 100644
index 0000000000..3e1539393e
--- /dev/null
+++ b/firmware/controllers/sensors/map.cpp
@@ -0,0 +1,66 @@
+#include "main.h"
+#include "boards.h"
+#include "engine_configuration.h"
+#include "engine_math.h"
+#include "adc_inputs.h"
+#include "interpolation.h"
+#include "error_handling.h"
+#include "map.h"
+
+extern engine_configuration_s * engineConfiguration;
+
+/**
+ * @brief MAP value decoded for a 1.83 Honda sensor
+ * -6.64kPa at zero volts
+ * 182.78kPa at 5 volts
+ *
+ * @returns kPa value
+ */
+static FastInterpolation denso183(0, -6.64, 5, 182.78);
+
+/**
+ * MAP sensor output voltage of 3.0v = a gauge reading of 0 in. Hg
+ * MAP sensor output voltage of 0.5v = a gauge reading of 27 in. Hg
+ */
+static FastInterpolation honda3bar(0.5, 91.422, 3.0, 0);
+
+static FastInterpolation mpx4250(0, 8, 5, 260);
+
+float decodePressure(float voltage, air_pressure_sensor_config_s * config) {
+ switch (config->sensorType) {
+ case MT_CUSTOM:
+ // todo: introduce 'FastInterpolation customMap'
+ return interpolate(0, config->Min, 5, config->Max, voltage);
+ case MT_DENSO183:
+ return denso183.getValue(voltage);
+ case MT_MPX4250:
+ return mpx4250.getValue(voltage);
+ case MT_HONDA3BAR:
+ return honda3bar.getValue(voltage);
+ default:
+ firmwareError("Unknown MAP type: %d", config->sensorType);
+ return NAN;
+ }
+}
+
+/**
+ * @brief MAP value decoded according to current settings
+ * @returns kPa value
+ */
+float getMapByVoltage(float voltage) {
+ air_pressure_sensor_config_s * config = &engineConfiguration->map.sensor;
+ return decodePressure(voltage, config);
+}
+
+/**
+ * @return Manifold Absolute Pressure, in kPa
+ */
+float getRawMap(void) {
+ float voltage = getVoltageDivided(engineConfiguration->map.sensor.hwChannel);
+ return getMapByVoltage(voltage);
+}
+
+float getBaroPressure(void) {
+ float voltage = getVoltageDivided(engineConfiguration->baroSensor.hwChannel);
+ return decodePressure(voltage, &engineConfiguration->baroSensor);
+}
diff --git a/firmware/controllers/sensors/map.h b/firmware/controllers/sensors/map.h
new file mode 100644
index 0000000000..8d1be1d654
--- /dev/null
+++ b/firmware/controllers/sensors/map.h
@@ -0,0 +1,28 @@
+#ifndef MAP_H_
+#define MAP_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+#include "sensor_types.h"
+
+/**
+ * @return Raw MAP sensor value right now
+ */
+float getRawMap(void);
+float getBaroPressure(void);
+/**
+ * @return MAP value averaged within a window of measurement
+ */
+float getMap(void);
+float getMapVoltage(void);
+float getMapByVoltage(float voltage);
+float decodePressure(float voltage, air_pressure_sensor_config_s * config);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif
diff --git a/firmware/controllers/sensors/sensor_types.h b/firmware/controllers/sensors/sensor_types.h
new file mode 100644
index 0000000000..675fbff73e
--- /dev/null
+++ b/firmware/controllers/sensors/sensor_types.h
@@ -0,0 +1,71 @@
+/**
+ * @file sensor_types.h
+ *
+ * @date Dec 24, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef THERMISTOR_TYPES_H_
+#define THERMISTOR_TYPES_H_
+
+#define MAP_ANGLE_SIZE 8
+#define MAP_WINDOW_SIZE 8
+
+#include "rusefi_enums.h"
+
+typedef struct {
+ /**
+ * kPa value at zero volts
+ */
+ float Min;
+ float Max;
+ air_pressure_sensor_type_e sensorType;
+ adc_channel_e hwChannel;
+} air_pressure_sensor_config_s;
+
+/**
+ * @brief MAP averaging configuration
+ */
+typedef struct {
+ float samplingAngleBins[MAP_ANGLE_SIZE];
+ /**
+ * @brief MAP averaging sampling start angle, by RPM
+ */
+ float samplingAngle[MAP_ANGLE_SIZE];
+
+ float samplingWindowBins[MAP_WINDOW_SIZE];
+ /**
+ * @brief MAP averaging angle duration, by RPM
+ */
+ float samplingWindow[MAP_WINDOW_SIZE];
+
+ air_pressure_sensor_config_s sensor;
+
+} MAP_sensor_config_s;
+
+
+/**
+ * @brief Thermistor curve parameters
+ */
+typedef struct {
+ // these values is in Celcuus
+ float tempC_1;
+ float tempC_2;
+ float tempC_3;
+ float resistance_1;
+ float resistance_2;
+ float resistance_3;
+
+ float bias_resistor;
+
+ float s_h_a;
+ float s_h_b;
+ float s_h_c;
+} ThermistorConf;
+
+typedef struct {
+ ThermistorConf *config;
+ adc_channel_e channel;
+} Thermistor;
+
+#endif /* THERMISTOR_TYPES_H_ */
diff --git a/firmware/controllers/sensors/sensors.mk b/firmware/controllers/sensors/sensors.mk
new file mode 100644
index 0000000000..39d5397340
--- /dev/null
+++ b/firmware/controllers/sensors/sensors.mk
@@ -0,0 +1,9 @@
+
+CONTROLLERS_SENSORS_SRC = $(PROJECT_DIR)/controllers/sensors/allsensors.c \
+ $(PROJECT_DIR)/controllers/sensors/maf.c \
+ $(PROJECT_DIR)/controllers/sensors/voltage.c
+
+CONTROLLERS_SENSORS_SRC_CPP = $(PROJECT_DIR)/controllers/sensors/thermistors.cpp \
+ $(PROJECT_DIR)/controllers/sensors/map.cpp \
+ $(PROJECT_DIR)/controllers/sensors/tps.cpp \
+ $(PROJECT_DIR)/controllers/sensors/ego.cpp
diff --git a/firmware/controllers/sensors/thermistors.cpp b/firmware/controllers/sensors/thermistors.cpp
new file mode 100644
index 0000000000..705d0cea46
--- /dev/null
+++ b/firmware/controllers/sensors/thermistors.cpp
@@ -0,0 +1,176 @@
+/**
+ * @file thermistors.cpp
+ *
+ * @date Feb 17, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+/**
+ * http://en.wikipedia.org/wiki/Thermistor
+ * http://en.wikipedia.org/wiki/Steinhart%E2%80%93Hart_equation
+ */
+
+#include "main.h"
+#include "thermistors.h"
+#include "adc_inputs.h"
+#include "engine_configuration.h"
+#include "engine_math.h"
+#include "ec2.h"
+
+// Celsius
+#define LIMPING_MODE_IAT_TEMPERATURE 30.0f
+#define LIMPING_MODE_CLT_TEMPERATURE 70.0f
+
+extern engine_configuration_s *engineConfiguration;
+extern engine_configuration2_s *engineConfiguration2;
+
+static bool initialized = false;
+
+/**
+ * http://en.wikipedia.org/wiki/Voltage_divider
+ */
+float getR1InVoltageDividor(float Vout, float Vin, float r2) {
+ return r2 * Vin / Vout - r2;
+}
+
+float getR2InVoltageDividor(float Vout, float Vin, float r1) {
+ if (Vout == 0) {
+ return NAN;
+ }
+ return r1 / (Vin / Vout - 1);
+}
+
+float getVoutInVoltageDividor(float Vin, float r1, float r2) {
+ return r2 * Vin / (r1 + r2);
+}
+
+float convertResistanceToKelvinTemperature(float resistance, ThermistorConf *thermistor) {
+ if (resistance <= 0) {
+ //warning("Invalid resistance in convertResistanceToKelvinTemperature=", resistance);
+ return 0.0f;
+ }
+ float logR = logf(resistance);
+ return 1 / (thermistor->s_h_a + thermistor->s_h_b * logR + thermistor->s_h_c * logR * logR * logR);
+}
+
+float convertCelsiustoF(float tempC) {
+ return tempC * 9 / 5 + 32;
+}
+
+float convertFtoCelsius(float tempF) {
+ return (tempF - 32) / 9 * 5;
+}
+
+float convertKelvinToFahrenheit(float kelvin) {
+ float tempC = convertKelvinToCelcius(kelvin);
+ return convertCelsiustoF(tempC);
+}
+
+float getKelvinTemperature(float resistance, ThermistorConf *thermistor) {
+ if (thermistor == NULL) {
+ firmwareError("thermistor pointer is NULL");
+ return NAN;
+ }
+
+ float kelvinTemperature = convertResistanceToKelvinTemperature(resistance, thermistor);
+ return kelvinTemperature;
+}
+
+float getResistance(Thermistor *thermistor) {
+ float voltage = getVoltageDivided(thermistor->channel);
+ efiAssert(thermistor->config != NULL, "config is null", NAN);
+ float resistance = getR2InVoltageDividor(voltage, _5_VOLTS, thermistor->config->bias_resistor);
+ return resistance;
+}
+
+float getTemperatureC(Thermistor *thermistor) {
+ if (!initialized) {
+ firmwareError("thermstr not initialized");
+ return NAN;
+ }
+ float resistance = getResistance(thermistor);
+
+ float kelvinTemperature = getKelvinTemperature(resistance, thermistor->config);
+ return convertKelvinToCelcius(kelvinTemperature);
+}
+
+bool isValidCoolantTemperature(float temperature) {
+ // I hope magic constants are appropriate here
+ return !cisnan(temperature) && temperature > -50 && temperature < 250;
+}
+
+bool isValidIntakeAirTemperature(float temperature) {
+ // I hope magic constants are appropriate here
+ return !cisnan(temperature) && temperature > -50 && temperature < 100;
+}
+
+/**
+ * @return coolant temperature, in Celsius
+ */
+float getCoolantTemperature(void) {
+ float temperature = getTemperatureC(&engineConfiguration2->clt);
+ if (!isValidCoolantTemperature(temperature)) {
+ warning(OBD_PCM_Processor_Fault, "unrealistic CLT %f", temperature);
+ return LIMPING_MODE_CLT_TEMPERATURE;
+ }
+ return temperature;
+}
+
+void setThermistorConfiguration(ThermistorConf * tc, float tempC1, float r1, float tempC2, float r2, float tempC3,
+ float r3) {
+ tc->tempC_1 = tempC1;
+ tc->resistance_1 = r1;
+
+ tc->tempC_2 = tempC2;
+ tc->resistance_2 = r2;
+
+ tc->tempC_3 = tempC3;
+ tc->resistance_3 = r3;
+}
+
+void prepareThermistorCurve(ThermistorConf * config) {
+ float T1 = config->tempC_1 + KELV;
+ float T2 = config->tempC_2 + KELV;
+ float T3 = config->tempC_3 + KELV;
+
+ float L1 = logf(config->resistance_1);
+ float L2 = logf(config->resistance_2);
+ float L3 = logf(config->resistance_3);
+
+ float Y1 = 1 / T1;
+ float Y2 = 1 / T2;
+ float Y3 = 1 / T3;
+
+ float U2 = (Y2 - Y1) / (L2 - L1);
+ float U3 = (Y3 - Y1) / (L3 - L1);
+
+ config->s_h_c = (U3 - U2) / (L3 - L2) * pow(L1 + L2 + L3, -1);
+ config->s_h_b = U2 - config->s_h_c * (L1 * L1 + L1 * L2 + L2 * L2);
+ config->s_h_a = Y1 - (config->s_h_b + L1 * L1 * config->s_h_c) * L1;
+}
+
+/**
+ * @return Celsius value
+ */
+float getIntakeAirTemperature(void) {
+ float temperature = getTemperatureC(&engineConfiguration2->iat);
+ if (!isValidIntakeAirTemperature(temperature)) {
+ warning(OBD_PCM_Processor_Fault, "unrealistic IAT %f", temperature);
+ return LIMPING_MODE_IAT_TEMPERATURE;
+ }
+ return temperature;
+}
+
+static void initThermistorCurve(Thermistor * t, ThermistorConf *config, adc_channel_e channel) {
+ prepareThermistorCurve(config);
+ t->config = config;
+ t->channel = channel;
+}
+
+void initThermistors(void) {
+ initThermistorCurve(&engineConfiguration2->clt, &engineConfiguration->cltThermistorConf,
+ engineConfiguration->cltAdcChannel);
+ initThermistorCurve(&engineConfiguration2->iat, &engineConfiguration->iatThermistorConf,
+ engineConfiguration->iatAdcChannel);
+ initialized = TRUE;
+}
diff --git a/firmware/controllers/sensors/thermistors.h b/firmware/controllers/sensors/thermistors.h
new file mode 100644
index 0000000000..fe9b2a8a55
--- /dev/null
+++ b/firmware/controllers/sensors/thermistors.h
@@ -0,0 +1,56 @@
+/**
+ * @file thermistors.h
+ *
+ * @date Feb 17, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef THERMISTORS_H_
+#define THERMISTORS_H_
+
+#include "main.h"
+
+#define _5_VOLTS 5.0
+#define KELV 273.15
+
+#include "sensor_types.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+/**
+ * Vout=r2/(r1+r2)*Vin
+ */
+float getR1InVoltageDividor(float Vout, float Vin, float r2);
+float getR2InVoltageDividor(float Vout, float Vin, float r1);
+float getTempK(float resistance);
+/**
+ * converts Kelvin temperature into Celsius temperature
+ */
+#define convertKelvinToCelcius(tempK) ((tempK) - KELV)
+#define convertCelsiusToKelvin(tempC) ((tempC) + KELV)
+
+float convertCelciustoF(float tempC);
+float convertFtoCelcius(float tempF);
+
+float getKelvinTemperature(float resistance, ThermistorConf *thermistor);
+float getResistance(Thermistor *thermistor);
+float getTemperatureC(Thermistor *thermistor);
+float getCoolantTemperature(void);
+bool isValidCoolantTemperature(float temperature);
+float getIntakeAirTemperature(void);
+bool isValidIntakeAirTemperature(float temperature);
+
+float convertResistanceToKelvinTemperature(float resistance,
+ ThermistorConf *thermistor);
+void setThermistorConfiguration(ThermistorConf * tc, float temp1, float r1, float temp2, float r2, float temp3,
+ float r3);
+void prepareThermistorCurve(ThermistorConf * config);
+void initThermistors(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* THERMISTORS_H_ */
diff --git a/firmware/controllers/sensors/tps.cpp b/firmware/controllers/sensors/tps.cpp
new file mode 100644
index 0000000000..4f2d7bcb15
--- /dev/null
+++ b/firmware/controllers/sensors/tps.cpp
@@ -0,0 +1,110 @@
+#include "main.h"
+#include "boards.h"
+#include "tps.h"
+#include "engine_configuration.h"
+#include "interpolation.h"
+#include "adc_inputs.h"
+
+extern engine_configuration_s *engineConfiguration;
+
+/**
+ * We are using one instance for read and another for modification, this is how we get lock-free thread-safety
+ *
+ */
+static tps_roc_s states[2];
+
+static volatile int tpsRocIndex = 0;
+
+/**
+ * this method is lock-free thread-safe if invoked only from one thread
+ */
+void saveTpsState(time_t now, float curValue) {
+ int tpsNextIndex = (tpsRocIndex + 1) % 2;
+ tps_roc_s *cur = &states[tpsRocIndex];
+ tps_roc_s *next = &states[tpsNextIndex];
+
+ next->prevTime = cur->curTime;
+ next->prevValue = cur->curValue;
+ next->curTime = now;
+ next->curValue = curValue;
+
+ int diffSysticks = overflowDiff(now, cur->curTime);
+ float diffSeconds = diffSysticks * 1.0 / CH_FREQUENCY;
+ next->rateOfChange = (curValue - cur->curValue) / diffSeconds;
+
+ // here we update volatile index
+ tpsRocIndex = tpsNextIndex;
+}
+
+/**
+ * this read-only method is lock-free thread-safe
+ */
+float getTpsRateOfChange(void) {
+ return states[tpsRocIndex].rateOfChange;
+}
+
+/*
+ * Return current TPS position based on configured ADC levels, and adc
+ *
+ * */
+float getTpsValue(int adc) {
+ if (adc < engineConfiguration->tpsMin) {
+ return 0.0f;
+ }
+ if (adc > engineConfiguration->tpsMax) {
+ return 100.0f;
+ }
+ // todo: double comparison using EPS
+ if (engineConfiguration->tpsMin == engineConfiguration->tpsMax) {
+ firmwareError("Invalid TPS configuration: same value");
+ return 0.0f;
+ }
+ return interpolate(engineConfiguration->tpsMin, 0, engineConfiguration->tpsMax, 100, adc);
+}
+
+/*
+ * Return voltage on TPS AND channel
+ * */
+float getTPSVoltage(void) {
+ return getVoltageDivided(engineConfiguration->tpsAdcChannel);
+}
+
+/*
+ * Return TPS ADC readings.
+ * We need ADC value because TunerStudio has a nice TPS configuration wizard, and this wizard
+ * wants a TPS value.
+ */
+int getTPS10bitAdc(void) {
+ int adc = getAdcValue(engineConfiguration->tpsAdcChannel);
+ return (int) adc / 4; // Only for TunerStudio compatibility. Max TS adc value in 1023
+}
+
+/**
+ * @brief Position on physical primary TPS
+ */
+static float getPrimatyRawTPS(void) {
+ // blue, 1st board
+ /* PA7 - blue TP */
+ float tpsValue = getTpsValue(getTPS10bitAdc());
+ return tpsValue;
+}
+
+// todo: static float getSecondaryRawTPS
+
+/*
+ * In case of dual TPS this function would return logical TPS position
+ *
+ * @return Current TPS position, percent of WOT. 0 means idle and 100 means Wide Open Throttle
+ */
+float getTPS(void) {
+ // todo: if (config->isDualTps)
+ // todo: blah blah
+ // todo: if two TPS do not match - show OBD code via malfunction_central.c
+
+ return getPrimatyRawTPS();
+}
+
+int convertVoltageTo10bitADC(float voltage) {
+ // divided by 2 because of voltage divider, then converted into 10bit ADC value (TunerStudio format)
+ return (int)(voltage / 2 * 1024 / 3.3);
+}
diff --git a/firmware/controllers/sensors/tps.h b/firmware/controllers/sensors/tps.h
new file mode 100644
index 0000000000..3565f2c0c8
--- /dev/null
+++ b/firmware/controllers/sensors/tps.h
@@ -0,0 +1,37 @@
+/**
+ * @file tps.h
+ * @brief
+ *
+ *
+ * @date Nov 15, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef TPS_H_
+#define TPS_H_
+
+#include "global.h"
+
+float getTPS(void);
+int convertVoltageTo10bitADC(float voltage);
+int getTPS10bitAdc(void);
+float getTPSVoltage(void);
+
+typedef struct {
+ // time in systicks
+ // todo: one day we should migrate all times to float seconds or milliseconds?
+ time_t prevTime;
+ // value 0-100%
+ float prevValue;
+ // time in systicks
+ time_t curTime;
+ // value 0-100%
+ float curValue;
+ // % per second
+ float rateOfChange;
+} tps_roc_s;
+
+void saveTpsState(time_t now, float curValue);
+float getTpsRateOfChange(void);
+
+#endif
diff --git a/firmware/controllers/sensors/voltage.c b/firmware/controllers/sensors/voltage.c
new file mode 100644
index 0000000000..1ebdca7396
--- /dev/null
+++ b/firmware/controllers/sensors/voltage.c
@@ -0,0 +1,25 @@
+/**
+ * @file voltage.c
+ * @brief
+ *
+ *
+ * @date Nov 15, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+#include "boards.h"
+#include "engine_configuration.h"
+#include "adc_inputs.h"
+#include "voltage.h"
+
+extern engine_configuration_s *engineConfiguration;
+
+float getVRef(void) {
+// return getAdcValue(ADC_CHANNEL_VREF);
+ return getVoltageDivided(ADC_CHANNEL_VREF);
+}
+
+float getVBatt(void) {
+ return getVoltage(engineConfiguration->vBattAdcChannel) * engineConfiguration->vbattDividerCoeff;
+}
diff --git a/firmware/controllers/sensors/voltage.h b/firmware/controllers/sensors/voltage.h
new file mode 100644
index 0000000000..13e1ffe612
--- /dev/null
+++ b/firmware/controllers/sensors/voltage.h
@@ -0,0 +1,27 @@
+/**
+ * @file voltage.h
+ * @brief
+ *
+ *
+ * @date Nov 15, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef VOLTAGE_H_
+#define VOLTAGE_H_
+
+#include "main.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+float getVRef(void);
+float getVBatt(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif
diff --git a/firmware/controllers/settings.cpp b/firmware/controllers/settings.cpp
new file mode 100644
index 0000000000..756a61082f
--- /dev/null
+++ b/firmware/controllers/settings.cpp
@@ -0,0 +1,753 @@
+/**
+ * @file settings.cpp
+ * @brief This file is about configuring engine via the human-readable protocol
+ *
+ * @date Dec 30, 2012
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+#include "settings.h"
+#include "eficonsole.h"
+#include "engine_configuration.h"
+#include "adc_inputs.h"
+#include "engine_controller.h"
+#include "thermistors.h"
+#include "adc_inputs.h"
+#include "interpolation.h"
+#include "tps.h"
+#include "ec2.h"
+#include "map.h"
+#include "trigger_decoder.h"
+#include "console_io.h"
+
+#if EFI_PROD_CODE
+#include "rusefi.h"
+#include "pin_repository.h"
+#include "hardware.h"
+#endif /* EFI_PROD_CODE */
+
+#if EFI_INTERNAL_FLASH
+#include "flash_main.h"
+#endif /* EFI_INTERNAL_FLASH */
+
+static Logging logger;
+
+static char LOGGING_BUFFER[1000];
+
+/*
+ static void printIntArray(int array[], int size) {
+ for (int j = 0; j < size; j++) {
+ print("%d ", array[j]);
+ }
+ print("\r\n");
+ }
+ */
+
+void printFloatArray(const char *prefix, float array[], int size) {
+ appendMsgPrefix(&logger);
+ appendPrintf(&logger, prefix);
+ for (int j = 0; j < size; j++) {
+ appendPrintf(&logger, "%f ", array[j]);
+ }
+ appendMsgPostfix(&logger);
+ scheduleLogging(&logger);
+}
+
+const char* getConfigurationName(engine_configuration_s *engineConfiguration) {
+ switch (engineConfiguration->engineType) {
+#if EFI_SUPPORT_DODGE_NEON
+ case DODGE_NEON_1995:
+ return "Dodge Neon";
+#endif /* EFI_SUPPORT_DODGE_NEON */
+#if EFI_SUPPORT_FORD_ASPIRE
+ case FORD_ASPIRE_1996:
+ return "Ford Aspire";
+#endif /* EFI_SUPPORT_FORD_ASPIRE */
+#if EFI_SUPPORT_FORD_FIESTA
+ case FORD_FIESTA:
+ return "Ford Fiesta";
+#endif /* EFI_SUPPORT_FORD_FIESTA */
+#if EFI_SUPPORT_NISSAN_PRIMERA
+ case NISSAN_PRIMERA:
+ return "Nissan Primera";
+#endif /* EFI_SUPPORT_NISSAN_PRIMERA */
+ case HONDA_ACCORD_CD:
+ return "Honda Accord 3w";
+ case HONDA_ACCORD_CD_TWO_WIRES:
+ return "Honda Accord 2w";
+ case HONDA_ACCORD_CD_DIP:
+ return "Honda Dip";
+ case FORD_INLINE_6_1995:
+ return "Ford 1995 inline 6";
+ case GY6_139QMB:
+ return "Gy6 139qmb";
+ case MAZDA_MIATA_NB:
+ return "Mazda Miata NB";
+ case MAZDA_323:
+ return "Mazda 323";
+ case SATURN_ION_2004:
+ return "Saturn Ion";
+ case MINI_COOPER_R50:
+ return "Mini Cooper R50";
+ case FORD_ESCORT_GT:
+ return "Ford Escort GT";
+ case CITROEN_TU3JP:
+ return "Citroen TU3JP";
+ case ROVER_V8:
+ return "Rover v8";
+ case MITSU_4G93:
+ return "Mitsu 4G93";
+ case MIATA_1990:
+ return "Miata 1990";
+ case MIATA_1994:
+ return "Miata 1994";
+ case MIATA_1996:
+ return "Miata 1996";
+ default:
+ firmwareError("Unexpected: engineType %d", engineConfiguration->engineType);
+ return NULL;
+ }
+}
+
+static const char * pinModeToString(pin_output_mode_e mode) {
+ switch (mode) {
+ case OM_DEFAULT:
+ return "default";
+ case OM_INVERTED:
+ return "inverted";
+ case OM_OPENDRAIN:
+ return "open drain";
+ case OM_OPENDRAIN_INVERTED:
+ return "open drain inverted";
+ default:
+ return "unexpected";
+ }
+}
+
+static const char * boolToString(bool value) {
+ return value ? "Yes" : "No";
+}
+
+extern board_configuration_s *boardConfiguration;
+
+/**
+ * @brief Prints current engine configuration to human-readable console.
+ */
+void printConfiguration(engine_configuration_s *engineConfiguration, engine_configuration2_s *engineConfiguration2) {
+
+ scheduleMsg(&logger, "Template %s/%d trigger %d", getConfigurationName(engineConfiguration),
+ engineConfiguration->engineType, engineConfiguration->triggerConfig.triggerType);
+
+ scheduleMsg(&logger, "configurationVersion=%d", getGlobalConfigurationVersion());
+
+ for (int k = 0; k < FUEL_LOAD_COUNT; k++) {
+// print("line %d (%f): ", k, engineConfiguration->fuelKeyBins[k]);
+// for (int r = 0; r < FUEL_RPM_COUNT; r++) {
+// print("%f ", engineConfiguration->fuelTable[k][r]);
+// }
+// print("\r\n");
+ }
+
+ printFloatArray("RPM bin: ", engineConfiguration->fuelRpmBins, FUEL_RPM_COUNT);
+
+ printFloatArray("Y bin: ", engineConfiguration->fuelLoadBins, FUEL_LOAD_COUNT);
+
+ printFloatArray("CLT: ", engineConfiguration->cltFuelCorr, CLT_CURVE_SIZE);
+ printFloatArray("CLT bins: ", engineConfiguration->cltFuelCorrBins, CLT_CURVE_SIZE);
+
+ printFloatArray("IAT: ", engineConfiguration->iatFuelCorr, IAT_CURVE_SIZE);
+ printFloatArray("IAT bins: ", engineConfiguration->iatFuelCorrBins, IAT_CURVE_SIZE);
+
+ printFloatArray("vBatt: ", engineConfiguration->battInjectorLagCorr, VBAT_INJECTOR_CURVE_SIZE);
+ printFloatArray("vBatt bins: ", engineConfiguration->battInjectorLagCorrBins, VBAT_INJECTOR_CURVE_SIZE);
+
+// appendMsgPrefix(&logger);
+
+ scheduleMsg(&logger, "rpmHardLimit: %d/rpmMultiplier=%f", engineConfiguration->rpmHardLimit,
+ engineConfiguration->rpmMultiplier);
+
+ scheduleMsg(&logger, "tpsMin: %d/tpsMax: %d", engineConfiguration->tpsMin, engineConfiguration->tpsMax);
+
+ scheduleMsg(&logger, "ignitionMode: %d/enabled=%s", engineConfiguration->ignitionMode,
+ boolToString(engineConfiguration->isIgnitionEnabled));
+ scheduleMsg(&logger, "timingMode: %d", engineConfiguration->timingMode);
+ scheduleMsg(&logger, "fixedModeTiming: %d", (int) engineConfiguration->fixedModeTiming);
+ scheduleMsg(&logger, "ignitionOffset=%f", engineConfiguration->ignitionOffset);
+ scheduleMsg(&logger, "injectionOffset=%f/enabled=%s", (double) engineConfiguration->injectionOffset,
+ boolToString(engineConfiguration->isInjectionEnabled));
+
+ scheduleMsg(&logger, "crankingChargeAngle=%f", engineConfiguration->crankingChargeAngle);
+ scheduleMsg(&logger, "crankingTimingAngle=%f", engineConfiguration->crankingTimingAngle);
+ scheduleMsg(&logger, "globalTriggerAngleOffset=%f", engineConfiguration->globalTriggerAngleOffset);
+
+// scheduleMsg(&logger, "analogChartMode: %d", engineConfiguration->analogChartMode);
+
+ scheduleMsg(&logger, "crankingRpm: %d", engineConfiguration->crankingSettings.crankingRpm);
+
+ scheduleMsg(&logger, "idlePinMode: %s", pinModeToString(boardConfiguration->idleValvePinMode));
+ scheduleMsg(&logger, "malfunctionIndicatorPinMode: %s",
+ pinModeToString(boardConfiguration->malfunctionIndicatorPinMode));
+ scheduleMsg(&logger, "analogInputDividerCoefficient: %f", engineConfiguration->analogInputDividerCoefficient);
+
+ scheduleMsg(&logger, "needSecondTriggerInput: %s",
+ boolToString(engineConfiguration->needSecondTriggerInput));
+
+#if EFI_PROD_CODE
+ scheduleMsg(&logger, "idleValvePin: %s", hwPortname(boardConfiguration->idleValvePin));
+ scheduleMsg(&logger, "fuelPumpPin: mode %s @ %s", pinModeToString(boardConfiguration->fuelPumpPinMode),
+ hwPortname(boardConfiguration->fuelPumpPin));
+
+ scheduleMsg(&logger, "injectionPins: mode %s", pinModeToString(boardConfiguration->injectionPinMode));
+ for (int i = 0; i < engineConfiguration->cylindersCount; i++) {
+ brain_pin_e brainPin = boardConfiguration->injectionPins[i];
+
+ scheduleMsg(&logger, "injection %d @ %s", i, hwPortname(brainPin));
+ }
+
+ scheduleMsg(&logger, "ignitionPins: mode %s", pinModeToString(boardConfiguration->ignitionPinMode));
+ // todo: calculate coils count based on ignition mode
+ for (int i = 0; i < 4; i++) {
+ brain_pin_e brainPin = boardConfiguration->ignitionPins[i];
+ scheduleMsg(&logger, "ignition %d @ %s", i, hwPortname(brainPin));
+ }
+
+ scheduleMsg(&logger, "primary trigger simulator: %s %s", hwPortname(boardConfiguration->triggerSimulatorPins[0]),
+ pinModeToString(boardConfiguration->triggerSimulatorPinModes[0]));
+ scheduleMsg(&logger, "secondary trigger simulator: %s %s", hwPortname(boardConfiguration->triggerSimulatorPins[1]),
+ pinModeToString(boardConfiguration->triggerSimulatorPinModes[1]));
+ scheduleMsg(&logger, "3rd trigger simulator: %s %s", hwPortname(boardConfiguration->triggerSimulatorPins[2]),
+ pinModeToString(boardConfiguration->triggerSimulatorPinModes[2]));
+
+ scheduleMsg(&logger, "primary trigger input: %s", hwPortname(boardConfiguration->triggerInputPins[0]));
+ scheduleMsg(&logger, "secondary trigger input: %s", hwPortname(boardConfiguration->triggerInputPins[1]));
+ scheduleMsg(&logger, "primary logic input: %s", hwPortname(boardConfiguration->logicAnalyzerPins[0]));
+ scheduleMsg(&logger, "secondary logic input: %s", hwPortname(boardConfiguration->logicAnalyzerPins[1]));
+
+ scheduleMsg(&logger, "boardTestModeJumperPin: %s", hwPortname(boardConfiguration->boardTestModeJumperPin));
+
+ scheduleMsg(&logger, "digitalPotentiometerSpiDevice %d", boardConfiguration->digitalPotentiometerSpiDevice);
+
+ for (int i = 0; i < DIGIPOT_COUNT; i++) {
+ scheduleMsg(&logger, "digitalPotentiometer CS%d %s", i,
+ hwPortname(boardConfiguration->digitalPotentiometerChipSelect[i]));
+ }
+
+ scheduleMsg(&logger, "spi 1=%s/2=%s/3=%s", boolToString(boardConfiguration->is_enabled_spi_1),
+ boolToString(boardConfiguration->is_enabled_spi_2), boolToString(boardConfiguration->is_enabled_spi_3));
+
+#endif /* EFI_PROD_CODE */
+}
+
+extern engine_configuration_s *engineConfiguration;
+extern engine_configuration2_s *engineConfiguration2;
+
+static void doPrintConfiguration(void) {
+ printConfiguration(engineConfiguration, engineConfiguration2);
+}
+
+static void setFixedModeTiming(int value) {
+ engineConfiguration->fixedModeTiming = value;
+ doPrintConfiguration();
+ incrementGlobalConfigurationVersion();
+}
+
+static void setTimingMode(int value) {
+ engineConfiguration->timingMode = (timing_mode_e) value;
+ doPrintConfiguration();
+ incrementGlobalConfigurationVersion();
+}
+
+static void setIdleMode(int mode) {
+ engineConfiguration->idleMode = (idle_mode_e) mode;
+}
+
+void setEngineType(int value) {
+ engineConfiguration->engineType = (engine_type_e) value;
+ resetConfigurationExt(&logger, (engine_type_e) value, engineConfiguration, engineConfiguration2,
+ boardConfiguration);
+#if EFI_INTERNAL_FLASH
+ writeToFlash();
+// scheduleReset();
+#endif /* EFI_PROD_CODE */
+ incrementGlobalConfigurationVersion();
+ doPrintConfiguration();
+}
+
+static void setInjectionPinMode(int value) {
+ boardConfiguration->injectionPinMode = (pin_output_mode_e) value;
+ doPrintConfiguration();
+}
+
+static void setIgnitionPinMode(int value) {
+ boardConfiguration->ignitionPinMode = (pin_output_mode_e) value;
+ doPrintConfiguration();
+}
+
+static void setIdlePin(int value) {
+ boardConfiguration->idleValvePin = (brain_pin_e) value;
+ doPrintConfiguration();
+}
+
+static void setIdlePinMode(int value) {
+ boardConfiguration->idleValvePinMode = (pin_output_mode_e) value;
+ doPrintConfiguration();
+}
+
+static void setInjectionOffset(int value) {
+ engineConfiguration->injectionOffset = value;
+ doPrintConfiguration();
+ incrementGlobalConfigurationVersion();
+}
+
+static void setIgnitionOffset(int value) {
+ engineConfiguration->ignitionOffset = value;
+ doPrintConfiguration();
+ incrementGlobalConfigurationVersion();
+}
+
+static void setFuelPumpPinMode(int value) {
+ boardConfiguration->fuelPumpPinMode = (pin_output_mode_e) value;
+ doPrintConfiguration();
+}
+
+static void setMalfunctionIndicatorPinMode(int value) {
+ boardConfiguration->malfunctionIndicatorPinMode = (pin_output_mode_e) value;
+ doPrintConfiguration();
+}
+
+static void setAnalogChartMode(int value) {
+ engineConfiguration->analogChartMode = value;
+ doPrintConfiguration();
+}
+
+static void setRpmMultiplier(int value) {
+ engineConfiguration->rpmMultiplier = value;
+ doPrintConfiguration();
+}
+
+static char pinNameBuffer[16];
+
+static void printThermistor(const char *msg, Thermistor *thermistor) {
+ adc_channel_e adcChannel = thermistor->channel;
+ float voltage = getVoltageDivided(adcChannel);
+ float r = getResistance(thermistor);
+
+ float t = getTemperatureC(thermistor);
+
+ scheduleMsg(&logger, "%s v=%f C=%f R=%f on channel %d", msg, voltage, t, r, adcChannel);
+ scheduleMsg(&logger, "bias=%f A=%f B=%f C=%f", thermistor->config->bias_resistor, thermistor->config->s_h_a,
+ thermistor->config->s_h_b, thermistor->config->s_h_c);
+#if EFI_ANALOG_INPUTS
+ scheduleMsg(&logger, "@%s", getPinNameByAdcChannel(adcChannel, pinNameBuffer));
+#endif
+}
+
+#if EFI_PROD_CODE
+static void printMAPInfo(void) {
+ scheduleMsg(&logger, "map type=%d raw=%f MAP=%f", engineConfiguration->map.sensor.sensorType, getRawMap(),
+ getMap());
+ if (engineConfiguration->map.sensor.sensorType == MT_CUSTOM) {
+ scheduleMsg(&logger, "min=%f max=%f", engineConfiguration->map.sensor.Min, engineConfiguration->map.sensor.Max);
+ }
+
+ scheduleMsg(&logger, "baro type=%d value=%f", engineConfiguration->baroSensor.sensorType, getBaroPressure());
+ if (engineConfiguration->baroSensor.sensorType == MT_CUSTOM) {
+ scheduleMsg(&logger, "min=%f max=%f", engineConfiguration->baroSensor.Min, engineConfiguration->baroSensor.Max);
+ }
+}
+#endif
+
+static void printTPSInfo(void) {
+#if EFI_PROD_CODE
+ GPIO_TypeDef* port = getAdcChannelPort(engineConfiguration->tpsAdcChannel);
+ int pin = getAdcChannelPin(engineConfiguration->tpsAdcChannel);
+
+ scheduleMsg(&logger, "tps min %d/max %d v=%f @%s%d", engineConfiguration->tpsMin, engineConfiguration->tpsMax,
+ getTPSVoltage(), portname(port), pin);
+#endif
+ scheduleMsg(&logger, "current 10bit=%d value=%f rate=%f", getTPS10bitAdc(), getTPS(), getTpsRateOfChange());
+}
+
+static void printTemperatureInfo(void) {
+ printThermistor("CLT", &engineConfiguration2->clt);
+ if (!isValidCoolantTemperature(getCoolantTemperature())) {
+ scheduleMsg(&logger, "CLT sensing error");
+ }
+ printThermistor("IAT", &engineConfiguration2->iat);
+ if (!isValidIntakeAirTemperature(getIntakeAirTemperature())) {
+ scheduleMsg(&logger, "IAT sensing error");
+ }
+
+ float rClt = getResistance(&engineConfiguration2->clt);
+ float rIat = getResistance(&engineConfiguration2->iat);
+
+#if EFI_ANALOG_INPUTS
+ adc_channel_e cltChannel = engineConfiguration2->clt.channel;
+ scheduleMsg(&logger, "CLT R=%f on channel %d@%s", rClt, cltChannel,
+ getPinNameByAdcChannel(cltChannel, pinNameBuffer));
+ adc_channel_e iatChannel = engineConfiguration2->iat.channel;
+ scheduleMsg(&logger, "IAT R=%f on channel %d@%s", rIat, iatChannel,
+ getPinNameByAdcChannel(iatChannel, pinNameBuffer));
+
+ scheduleMsg(&logger, "cranking fuel %fms @ %fC", engineConfiguration->crankingSettings.fuelAtMinTempMs,
+ engineConfiguration->crankingSettings.coolantTempMinC);
+ scheduleMsg(&logger, "cranking fuel %fms @ %fC", engineConfiguration->crankingSettings.fuelAtMaxTempMs,
+ engineConfiguration->crankingSettings.coolantTempMaxC);
+#endif
+}
+
+/**
+ * For example
+ * set_cranking_fuel_min 15 0
+ * would be 15ms @ 0C
+ */
+static void setCrankingFuelMin(int timeMs, int tempC) {
+ engineConfiguration->crankingSettings.coolantTempMinC = tempC;
+ engineConfiguration->crankingSettings.fuelAtMinTempMs = timeMs;
+ printTemperatureInfo();
+}
+
+static void setCrankingRpm(int value) {
+ engineConfiguration->crankingSettings.crankingRpm = value;
+ doPrintConfiguration();
+}
+
+static void setAlgorithm(int value) {
+ engineConfiguration->algorithm = (engine_load_mode_e) value;
+ doPrintConfiguration();
+}
+
+static void setFiringOrder(int value) {
+ engineConfiguration->firingOrder = (firing_order_e) value;
+ doPrintConfiguration();
+}
+
+static void setRpmHardLimit(int value) {
+ engineConfiguration->rpmHardLimit = value;
+ doPrintConfiguration();
+}
+
+static void setCrankingFuelMax(int timeMs, int tempC) {
+ engineConfiguration->crankingSettings.coolantTempMaxC = tempC;
+ engineConfiguration->crankingSettings.fuelAtMaxTempMs = timeMs;
+ printTemperatureInfo();
+}
+
+static void setGlobalTriggerAngleOffset(int value) {
+ engineConfiguration->globalTriggerAngleOffset = value;
+ incrementGlobalConfigurationVersion();
+ doPrintConfiguration();
+}
+
+static void setCrankingTimingAngle(float value) {
+ engineConfiguration->crankingTimingAngle = value;
+ incrementGlobalConfigurationVersion();
+ doPrintConfiguration();
+}
+
+static void setCrankingInjectionMode(int value) {
+ engineConfiguration->crankingInjectionMode = (injection_mode_e) value;
+ incrementGlobalConfigurationVersion();
+ doPrintConfiguration();
+}
+
+static void setInjectionMode(int value) {
+ engineConfiguration->injectionMode = (injection_mode_e) value;
+ incrementGlobalConfigurationVersion();
+ doPrintConfiguration();
+}
+
+static void setIgnitionMode(int value) {
+ engineConfiguration->ignitionMode = (ignition_mode_e) value;
+ incrementGlobalConfigurationVersion();
+ doPrintConfiguration();
+}
+
+static void setTriggerType(int value) {
+ engineConfiguration->triggerConfig.triggerType = (trigger_type_e) value;
+ incrementGlobalConfigurationVersion();
+ doPrintConfiguration();
+}
+
+static void setToothedWheel(int total, int skipped) {
+ setToothedWheelConfiguration(&engineConfiguration2->triggerShape, total, skipped, engineConfiguration);
+// initializeTriggerShape(&logger, engineConfiguration, engineConfiguration2);
+ incrementGlobalConfigurationVersion();
+ doPrintConfiguration();
+}
+
+static void setCrankingChargeAngle(float value) {
+ engineConfiguration->crankingChargeAngle = value;
+ incrementGlobalConfigurationVersion();
+ doPrintConfiguration();
+}
+
+static void setGlobalFuelCorrection(float value) {
+ if (value < 0.01 || value > 50)
+ return;
+ scheduleMsg(&logger, "setting fuel mult=%f", value);
+ engineConfiguration->globalFuelCorrection = value;
+}
+
+static void setVBattDivider(float value) {
+ engineConfiguration->vbattDividerCoeff = value;
+}
+
+static void setWholeTimingMap(float value) {
+ // todo: table helper?
+ scheduleMsg(&logger, "Setting whole timing map to %f", value);
+ for (int l = 0; l < IGN_LOAD_COUNT; l++) {
+ for (int r = 0; r < IGN_RPM_COUNT; r++) {
+ engineConfiguration->ignitionTable[l][r] = value;
+ }
+ }
+}
+
+static void setWholeFuelMapCmd(float value) {
+ scheduleMsg(&logger, "Setting whole fuel map to %f", value);
+ if (engineConfiguration->algorithm == LM_SPEED_DENSITY) {
+ scheduleMsg(&logger, "WARNING: setting fuel map in SD mode is pointless");
+ }
+ setWholeFuelMap(engineConfiguration, value);
+}
+
+#if EFI_PROD_CODE
+static void setTriggerInputPin(const char *indexStr, const char *pinName) {
+ int index = atoi(indexStr);
+ if (index < 0 || index > 2)
+ return;
+ brain_pin_e pin = parseBrainPin(pinName);
+ scheduleMsg(&logger, "setting trigger pin[%d] to %s please save&restart", index, hwPortname(pin));
+ boardConfiguration->triggerInputPins[index] = pin;
+}
+
+static void setTriggerSimulatorMode(const char *indexStr, const char *modeCode) {
+ int index = atoi(indexStr);
+ if (index < 0 || index > 2 || absI(index) == ERROR_CODE) {
+ return;
+ }
+ int mode = atoi(modeCode);
+ if (absI(mode) == ERROR_CODE) {
+ return;
+ }
+ boardConfiguration->triggerSimulatorPinModes[index] = (pin_output_mode_e) mode;
+}
+
+static void setTriggerSimulatorPin(const char *indexStr, const char *pinName) {
+ int index = atoi(indexStr);
+ if (index < 0 || index > 2)
+ return;
+ brain_pin_e pin = parseBrainPin(pinName);
+ scheduleMsg(&logger, "setting trigger simulator pin[%d] to %s please save&restart", index, hwPortname(pin));
+ boardConfiguration->triggerSimulatorPins[index] = pin;
+}
+
+static void setAnalogInputPin(const char *sensorStr, const char *pinName) {
+ brain_pin_e pin = parseBrainPin(pinName);
+ adc_channel_e channel = getAdcChannel(pin);
+ if (channel == EFI_ADC_ERROR) {
+ scheduleMsg(&logger, "Error with [%s]", pinName);
+ return;
+ }
+ if (strEqual("map", sensorStr)) {
+ engineConfiguration->map.sensor.hwChannel = channel;
+ scheduleMsg(&logger, "setting MAP to %s/%d", pinName, channel);
+ } else if (strEqual("clt", sensorStr)) {
+ engineConfiguration->cltAdcChannel = channel;
+ scheduleMsg(&logger, "setting CLT to %s/%d", pinName, channel);
+ } else if (strEqual("iat", sensorStr)) {
+ engineConfiguration->iatAdcChannel = channel;
+ scheduleMsg(&logger, "setting IAT to %s/%d", pinName, channel);
+ } else if (strEqual("tps", sensorStr)) {
+ engineConfiguration->tpsAdcChannel = channel;
+ scheduleMsg(&logger, "setting TPS to %s/%d", pinName, channel);
+ }
+}
+
+static void setLogicInputPin(const char *indexStr, const char *pinName) {
+ int index = atoi(indexStr);
+ if (index < 0 || index > 2)
+ return;
+ brain_pin_e pin = parseBrainPin(pinName);
+ scheduleMsg(&logger, "setting logic input pin[%d] to %s please save&restart", index, hwPortname(pin));
+ boardConfiguration->logicAnalyzerPins[index] = pin;
+}
+#endif /* EFI_PROD_CODE */
+
+static void setTimingMap(const char * rpmStr, const char *loadStr, const char *valueStr) {
+ float rpm = atoff(rpmStr);
+ float engineLoad = atoff(loadStr);
+ float value = atoff(valueStr);
+
+ int rpmIndex = findIndex(engineConfiguration->ignitionRpmBins, IGN_RPM_COUNT, rpm);
+ rpmIndex = rpmIndex < 0 ? 0 : rpmIndex;
+ int loadIndex = findIndex(engineConfiguration->ignitionLoadBins, IGN_LOAD_COUNT, engineLoad);
+ loadIndex = loadIndex < 0 ? 0 : loadIndex;
+
+ engineConfiguration->ignitionTable[loadIndex][rpmIndex] = value;
+ scheduleMsg(&logger, "Setting timing map entry %d:%d to %f", rpmIndex, loadIndex, value);
+}
+
+static void setFuelMap(const char * rpmStr, const char *loadStr, const char *valueStr) {
+ float rpm = atoff(rpmStr);
+ float engineLoad = atoff(loadStr);
+ float value = atoff(valueStr);
+
+ int rpmIndex = findIndex(engineConfiguration->fuelRpmBins, FUEL_RPM_COUNT, rpm);
+ rpmIndex = rpmIndex < 0 ? 0 : rpmIndex;
+ int loadIndex = findIndex(engineConfiguration->fuelLoadBins, FUEL_LOAD_COUNT, engineLoad);
+ loadIndex = loadIndex < 0 ? 0 : loadIndex;
+
+ engineConfiguration->fuelTable[loadIndex][rpmIndex] = value;
+ scheduleMsg(&logger, "Setting fuel map entry %d:%d to %f", rpmIndex, loadIndex, value);
+}
+
+static void setSpiMode(int index, bool mode) {
+ switch(index) {
+ case 1:
+ boardConfiguration->is_enabled_spi_1 = mode;
+ break;
+ case 2:
+ boardConfiguration->is_enabled_spi_2 = mode;
+ break;
+ case 3:
+ boardConfiguration->is_enabled_spi_3 = mode;
+ break;
+ default:
+ scheduleMsg(&logger, "invalid spi index %d", index);
+ return;
+ }
+ scheduleMsg(&logger, "spi %d mode: %s", index, boolToString(mode));
+}
+
+static void enableSpi(int index) {
+ setSpiMode(index, true);
+}
+
+static void disableSpi(int index) {
+ setSpiMode(index, false);
+}
+
+static void enableInjection(void) {
+ engineConfiguration->isInjectionEnabled = true;
+ scheduleMsg(&logger, "injection enabled");
+}
+
+static void disableInjection(void) {
+ engineConfiguration->isInjectionEnabled = false;
+ scheduleMsg(&logger, "injection disabled");
+}
+
+static void enableIgnition(void) {
+ engineConfiguration->isIgnitionEnabled = true;
+ scheduleMsg(&logger, "ignition enabled");
+}
+
+static void disableIgnition(void) {
+ engineConfiguration->isIgnitionEnabled = false;
+ scheduleMsg(&logger, "ignition disabled");
+}
+
+static void enableSelfStimulation(void) {
+ engineConfiguration->directSelfStimulation = true;
+ scheduleMsg(&logger, "self stimulation enabled");
+}
+
+static void disableSelfStimulation(void) {
+ engineConfiguration->directSelfStimulation = false;
+ scheduleMsg(&logger, "self stimulation disabled");
+}
+
+#if EFI_WAVE_CHART
+extern int waveChartUsedSize;
+#endif
+
+static void printAllInfo(void) {
+ printTemperatureInfo();
+ printTPSInfo();
+#if EFI_WAVE_CHART
+ scheduleMsg(&logger, "waveChartUsedSize=%d", waveChartUsedSize);
+#endif
+#if EFI_PROD_CODE
+ printMAPInfo();
+ scheduleMsg(&logger, "console mode jumper: %s", boolToString(!GET_CONSOLE_MODE_VALUE()));
+ scheduleMsg(&logger, "board test mode jumper: %s", boolToString(GET_BOARD_TEST_MODE_VALUE()));
+#endif
+}
+
+void initSettings(void) {
+ initLoggingExt(&logger, "settings control", LOGGING_BUFFER, sizeof(LOGGING_BUFFER));
+
+ addConsoleAction("showconfig", doPrintConfiguration);
+ addConsoleAction("tempinfo", printTemperatureInfo);
+ addConsoleAction("tpsinfo", printTPSInfo);
+ addConsoleAction("info", printAllInfo);
+
+ addConsoleActionI("set_ignition_offset", setIgnitionOffset);
+ addConsoleActionI("set_injection_offset", setInjectionOffset);
+ addConsoleActionI("set_global_trigger_offset_angle", setGlobalTriggerAngleOffset);
+ addConsoleActionI("set_analog_chart_mode", setAnalogChartMode);
+ addConsoleActionI("set_fixed_mode_timing", setFixedModeTiming);
+ addConsoleActionI("set_timing_mode", setTimingMode);
+ addConsoleActionI("set_engine_type", setEngineType);
+ addConsoleActionI("set_idle_mode", setIdleMode);
+
+ addConsoleActionI("set_injection_pin_mode", setInjectionPinMode);
+ addConsoleActionI("set_ignition_pin_mode", setIgnitionPinMode);
+ addConsoleActionI("set_idle_pin", setIdlePin);
+ addConsoleActionI("set_idle_pin_mode", setIdlePinMode);
+ addConsoleActionI("set_fuel_pump_pin_mode", setFuelPumpPinMode);
+ addConsoleActionI("set_malfunction_indicator_pin_mode", setMalfunctionIndicatorPinMode);
+ addConsoleActionI("set_rpm_multiplier", setRpmMultiplier);
+ // todo: start saving values into flash right away?
+
+ addConsoleActionF("set_global_fuel_correction", setGlobalFuelCorrection);
+
+ addConsoleActionII("set_cranking_fuel_min", setCrankingFuelMin);
+ addConsoleActionII("set_cranking_fuel_max", setCrankingFuelMax);
+ addConsoleActionI("set_cranking_rpm", setCrankingRpm);
+ addConsoleActionF("set_cranking_timing_angle", setCrankingTimingAngle);
+ addConsoleActionF("set_cranking_charge_angle", setCrankingChargeAngle);
+ addConsoleActionI("set_ignition_mode", setIgnitionMode);
+ addConsoleActionI("set_cranking_injection_mode", setCrankingInjectionMode);
+ addConsoleActionI("set_injection_mode", setInjectionMode);
+
+ addConsoleActionF("set_whole_fuel_map", setWholeFuelMapCmd);
+ addConsoleActionSSS("set_fuel_map", setFuelMap);
+
+ addConsoleActionF("set_whole_timing_map", setWholeTimingMap);
+ addConsoleActionSSS("set_timing_map", setTimingMap);
+
+ addConsoleActionI("set_rpm_hard_limit", setRpmHardLimit);
+ addConsoleActionI("set_firing_order", setFiringOrder);
+ addConsoleActionI("set_algorithm", setAlgorithm);
+
+ // todo: refactor this - looks like all boolean flags should be controlled with less code duplication
+ addConsoleAction("enable_injection", enableInjection);
+ addConsoleAction("disable_injection", disableInjection);
+ addConsoleAction("enable_ignition", enableIgnition);
+ addConsoleAction("disable_ignition", disableIgnition);
+ addConsoleAction("enable_self_stimulation", enableSelfStimulation);
+ addConsoleAction("disable_self_stimulation", disableSelfStimulation);
+
+ addConsoleActionI("enable_spi", enableSpi);
+ addConsoleActionI("disable_spi", disableSpi);
+
+ addConsoleActionII("set_toothed_wheel", setToothedWheel);
+ addConsoleActionI("set_trigger_type", setTriggerType);
+
+
+ addConsoleActionF("set_vbatt_divider", setVBattDivider);
+
+#if EFI_PROD_CODE
+ addConsoleActionSS("set_trigger_input_pin", setTriggerInputPin);
+ addConsoleActionSS("set_trigger_simulator_pin", setTriggerSimulatorPin);
+ addConsoleActionSS("set_trigger_simulator_mode", setTriggerSimulatorMode);
+
+ addConsoleAction("mapinfo", printMAPInfo);
+ addConsoleActionSS("set_analog_input_pin", setAnalogInputPin);
+ addConsoleActionSS("set_logic_input_pin", setLogicInputPin);
+#endif /* EFI_PROD_CODE */
+}
+
diff --git a/firmware/controllers/settings.h b/firmware/controllers/settings.h
new file mode 100644
index 0000000000..87e82881c4
--- /dev/null
+++ b/firmware/controllers/settings.h
@@ -0,0 +1,25 @@
+/**
+ * @file settings.h
+ * @brief This file is about configuring engine via the human-readable protocol
+ *
+ * @date Dec 30, 2012
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef INJECTOR_CONTROL_H_
+#define INJECTOR_CONTROL_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void initSettings(void);
+void pokeControl(void);
+void setEngineType(int value);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* INJECTOR_CONTROL_H_ */
diff --git a/firmware/controllers/system/SingleTimerExecutor.cpp b/firmware/controllers/system/SingleTimerExecutor.cpp
new file mode 100644
index 0000000000..860cd72504
--- /dev/null
+++ b/firmware/controllers/system/SingleTimerExecutor.cpp
@@ -0,0 +1,119 @@
+/**
+ * @file SingleTimerExecutor.cpp
+ *
+ * This class combines the powers of a 1MHz hardware timer from microsecond_timer.c
+ * and pending events queue event_queue.cpp
+ *
+ * As of version 2.6.x, ChibiOS tick-based kernel is not capable of scheduling events
+ * with the level of precision we need, and realistically it should not.
+ *
+ * http://sourceforge.net/p/rusefi/tickets/24/
+ *
+ * @date: Apr 18, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "SingleTimerExecutor.h"
+#include "efitime.h"
+#include "rfiutil.h"
+
+#if EFI_PROD_CODE
+#include "microsecond_timer.h"
+#endif
+
+#if EFI_SIGNAL_EXECUTOR_ONE_TIMER || defined(__DOXYGEN__)
+
+static Executor instance;
+
+extern schfunc_t globalTimerCallback;
+
+static void executorCallback(void *arg) {
+ instance.execute(getTimeNowUs());
+}
+
+Executor::Executor() {
+ reentrantLock = false;
+}
+
+void Executor::lock(void) {
+ lockAnyContext();
+}
+
+void Executor::unlock(void) {
+ unlockAnyContext();
+}
+
+void Executor::schedule(scheduling_s *scheduling, uint64_t nowUs, int delayUs, schfunc_t callback, void *param) {
+ if (!reentrantLock) {
+ // this would guard the queue and disable interrupts
+ lock();
+ }
+ queue.insertTask(scheduling, nowUs, delayUs, callback, param);
+ if (!reentrantLock) {
+ doExecute(nowUs);
+ unlock();
+ }
+}
+
+void Executor::execute(uint64_t nowUs) {
+ lock();
+ doExecute(nowUs);
+ unlock();
+}
+
+/*
+ * this private method is executed under lock
+ */
+void Executor::doExecute(uint64_t nowUs) {
+ /**
+ * Let's execute actions we should execute at this point.
+ * reentrantLock takes care of the use case where the actions we are executing are scheduling
+ * further invocations
+ */
+ reentrantLock = TRUE;
+ queue.executeAll(nowUs);
+ if (!isLocked()) {
+ firmwareError("Someone has stolen my lock");
+ return;
+ }
+ reentrantLock = false;
+ /**
+ * Let's set up the timer for the next execution
+ */
+ uint64_t nextEventTime = queue.getNextEventTime(nowUs);
+ efiAssertVoid(nextEventTime > nowUs, "setTimer constraint");
+ if (nextEventTime == EMPTY_QUEUE)
+ return; // no pending events in the queue
+ setHardwareUsTimer(nextEventTime - nowUs);
+}
+
+/**
+ * @brief Schedule an event
+ *
+ * Invokes event callback after the specified amount of time.
+ *
+ * @param [in, out] scheduling Data structure to keep this event in the collection.
+ * @param [in] delayUs the number of microseconds before the output signal immediate output if delay is zero.
+ * @param [in] dwell the number of ticks of output duration.
+ */
+void scheduleTask(const char *prefix, scheduling_s *scheduling, int delayUs, schfunc_t callback, void *param) {
+ if (delayUs < 0) {
+ firmwareError("Negative delayUs %s: %d", prefix, delayUs);
+ return;
+ }
+ if (delayUs == 0) {
+ callback(param);
+ return;
+ }
+ instance.schedule(scheduling, getTimeNowUs(), delayUs, callback, param);
+}
+
+void initSignalExecutorImpl(void) {
+ globalTimerCallback = executorCallback;
+#if EFI_PROD_CODE
+ initMicrosecondTimer();
+#endif /* EFI_PROD_CODE */
+}
+
+#endif
+
diff --git a/firmware/controllers/system/SingleTimerExecutor.h b/firmware/controllers/system/SingleTimerExecutor.h
new file mode 100644
index 0000000000..6c537580d4
--- /dev/null
+++ b/firmware/controllers/system/SingleTimerExecutor.h
@@ -0,0 +1,38 @@
+/**
+ * @file SingleTimerExecutor.h
+ *
+ * @date: Apr 18, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef SINGLETIMEREXECUTOR_H_
+#define SINGLETIMEREXECUTOR_H_
+
+#include "scheduler.h"
+#include "event_queue.h"
+
+class Executor {
+public:
+ Executor();
+ void schedule(scheduling_s *scheduling, uint64_t nowUs, int delayUs, schfunc_t callback, void *param);
+ void execute(uint64_t nowUs);
+private:
+ EventQueue queue;
+ bool reentrantLock;
+ void doExecute(uint64_t nowUs);
+ void lock(void);
+ void unlock(void);
+};
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void initSignalExecutorImpl(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SINGLETIMEREXECUTOR_H_ */
diff --git a/firmware/controllers/system/event_queue.cpp b/firmware/controllers/system/event_queue.cpp
new file mode 100644
index 0000000000..aa88621c5f
--- /dev/null
+++ b/firmware/controllers/system/event_queue.cpp
@@ -0,0 +1,119 @@
+/**
+ * @file event_queue.cpp
+ * This is a data structure which keeps track of all pending events
+ * Implemented as a linked list, which is fine since the number of
+ * pending events is pretty low
+ * todo: MAYBE migrate to a better data structure, but that's low priority
+ *
+ * this data structure is NOT thread safe
+ *
+ * @date Apr 17, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "event_queue.h"
+#include "efitime.h"
+
+EventQueue::EventQueue() {
+ head = NULL;
+}
+
+bool EventQueue::checkIfPending(scheduling_s *scheduling) {
+ return assertNotInList(head, scheduling);
+}
+
+void EventQueue::insertTask(scheduling_s *scheduling, uint64_t nowUs, int delayUs, schfunc_t callback, void *param) {
+ if (callback == NULL)
+ firmwareError("NULL callback");
+ uint64_t time = nowUs + delayUs;
+
+ int alreadyPending = checkIfPending(scheduling);
+ if (alreadyPending || hasFirmwareError())
+ return;
+
+ scheduling->momentUs = time;
+ scheduling->callback = callback;
+ scheduling->param = param;
+
+ LL_PREPEND(head, scheduling);
+}
+
+//void EventQueue::insertTask(scheduling_s *scheduling, int delayUs, schfunc_t callback, void *param) {
+// insertTask(scheduling, getTimeNowUs(), delayUs, callback, param);
+//}
+
+/**
+ * Get the timestamp of the soonest pending action
+ */
+uint64_t EventQueue::getNextEventTime(uint64_t nowUs) {
+ scheduling_s * current;
+ // this is a large value which is expected to be larger than any real time
+ uint64_t result = EMPTY_QUEUE;
+
+ int counter = 0;
+ LL_FOREACH(head, current)
+ {
+ if (++counter > QUEUE_LENGTH_LIMIT) {
+ firmwareError("Is this list looped #2?");
+ return EMPTY_QUEUE;
+ }
+ efiAssert(current->momentUs > nowUs, "executeAll should have been called", EMPTY_QUEUE);
+ if (current->momentUs < result)
+ result = current->momentUs;
+ }
+ return result;
+}
+
+/**
+ * Invoke all pending actions prior to specified timestamp
+ */
+void EventQueue::executeAll(uint64_t now) {
+ scheduling_s * current, *tmp;
+
+ scheduling_s * executionList = NULL;
+
+ int counter = 0;
+
+ // we need safe iteration because we are removing elements inside the loop
+ LL_FOREACH_SAFE(head, current, tmp)
+ {
+ if (++counter > QUEUE_LENGTH_LIMIT) {
+ firmwareError("Is this list looped?");
+ return;
+ }
+ if (current->momentUs <= now) {
+ LL_DELETE(head, current);
+ LL_PREPEND(executionList, current);
+ }
+ }
+
+ /*
+ * we need safe iteration here because 'callback' might change change 'current->next'
+ * while re-inserting it into the queue from within the callback
+ */
+ LL_FOREACH_SAFE(executionList, current, tmp)
+ current->callback(current->param);
+}
+
+int EventQueue::size(void) {
+ scheduling_s *tmp;
+ int result;
+ LL_COUNT(head, tmp, result);
+ return result;
+}
+
+scheduling_s *EventQueue::getForUnitText(int index) {
+ scheduling_s * current;
+
+ LL_FOREACH(head, current)
+ {
+ if (index == 0)
+ return current;
+ index--;
+ }
+ return NULL;
+}
+
+void EventQueue::clear(void) {
+ head = NULL;
+}
diff --git a/firmware/controllers/system/event_queue.h b/firmware/controllers/system/event_queue.h
new file mode 100644
index 0000000000..e46fa16326
--- /dev/null
+++ b/firmware/controllers/system/event_queue.h
@@ -0,0 +1,55 @@
+/**
+ * @file event_queue.h
+ *
+ * @date Apr 17, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "scheduler.h"
+#include "utlist.h"
+
+#ifndef EVENT_SCHEDULER_H_
+#define EVENT_SCHEDULER_H_
+
+#define EMPTY_QUEUE 0x0FFFFFFFFFFFFFFFLL
+
+#define QUEUE_LENGTH_LIMIT 1000
+
+template
+bool assertNotInList(T *head, T*element) {
+ // this code is just to validate state, no functional load
+ T * current;
+ int counter = 0;
+ LL_FOREACH(head, current)
+ {
+ if (++counter > QUEUE_LENGTH_LIMIT) {
+ firmwareError("Looped queue?");
+ return FALSE;
+ }
+ if (current == element) {
+ warning(OBD_PCM_Processor_Fault, "re-adding element into event_queue: [%s]", element->name);
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+class EventQueue {
+public:
+ EventQueue();
+
+// void insertTask(scheduling_s *scheduling, int delayUs, schfunc_t callback, void *param);
+ void insertTask(scheduling_s *scheduling, uint64_t nowUs, int delayUs, schfunc_t callback, void *param);
+
+ void executeAll(uint64_t now);
+
+ uint64_t getNextEventTime(uint64_t nowUs);
+ void clear(void);
+ int size(void);
+ scheduling_s *getForUnitText(int index);
+private:
+ bool checkIfPending(scheduling_s *scheduling);
+ scheduling_s *head;
+};
+
+#endif /* EVENT_SCHEDULER_H_ */
diff --git a/firmware/controllers/system/pwm_generator_logic.cpp b/firmware/controllers/system/pwm_generator_logic.cpp
new file mode 100644
index 0000000000..df37fb13cf
--- /dev/null
+++ b/firmware/controllers/system/pwm_generator_logic.cpp
@@ -0,0 +1,198 @@
+/**
+ * @file pwm_generator_logic.cpp
+ *
+ * This PWM implementation keep track of when it would be the next time to toggle the signal.
+ * It constantly sets timer to that next toggle time, then sets the timer again from the callback, and so on.
+ *
+ * @date Mar 2, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+#include "pwm_generator_logic.h"
+
+/**
+ * We need to limit the number of iterations in order to avoid precision loss while calculating
+ * next toggle time
+ */
+#define ITERATION_LIMIT 10000
+
+SimplePwm::SimplePwm() {
+ wave.init(pinStates);
+ sr[0] = wave;
+ init(_switchTimes, sr);
+}
+
+PwmConfig::PwmConfig() {
+ scheduling.name = "PwmConfig";
+}
+
+PwmConfig::PwmConfig(float *st, single_wave_s *waves) {
+ multiWave.init(st, waves);
+ scheduling.name = "PwmConfig";
+}
+
+void PwmConfig::init(float *st, single_wave_s *waves) {
+ multiWave.init(st, waves);
+}
+
+/**
+ * @param dutyCycle value between 0 and 1
+ */
+void SimplePwm::setSimplePwmDutyCycle(float dutyCycle) {
+ multiWave.setSwitchTime(0, dutyCycle);
+}
+
+static uint64_t getNextSwitchTimeUs(PwmConfig *state) {
+ efiAssert(state->safe.phaseIndex < PWM_PHASE_MAX_COUNT, "phaseIndex range", 0);
+ int iteration = state->safe.iteration;
+ float switchTime = state->multiWave.getSwitchTime(state->safe.phaseIndex);
+ float periodUs = state->safe.periodUs;
+#if DEBUG_PWM
+ scheduleMsg(&logger, "iteration=%d switchTime=%f period=%f", iteration, switchTime, period);
+#endif
+
+ /**
+ * Once 'iteration' gets relatively high, we might lose calculation precision here.
+ * This is addressed by ITERATION_LIMIT
+ */
+ uint64_t timeToSwitchUs = (uint64_t)((iteration + switchTime) * periodUs);
+
+#if DEBUG_PWM
+ scheduleMsg(&logger, "start=%d timeToSwitch=%d", state->safe.start, timeToSwitch);
+#endif
+ return state->safe.startUs + timeToSwitchUs;
+}
+
+void PwmConfig::handleCycleStart() {
+ if (safe.phaseIndex == 0) {
+ if (cycleCallback != NULL) {
+ cycleCallback(this);
+ }
+ efiAssertVoid(periodUs != 0, "period not initialized");
+ if (safe.periodUs != periodUs || safe.iteration == ITERATION_LIMIT) {
+ /**
+ * period length has changed - we need to reset internal state
+ */
+ safe.startUs = getTimeNowUs();
+ safe.iteration = 0;
+ safe.periodUs = periodUs;
+#if DEBUG_PWM
+ scheduleMsg(&logger, "state reset start=%d iteration=%d", state->safe.start, state->safe.iteration);
+#endif
+ }
+ }
+}
+
+/**
+ * @return Next time for signal toggle
+ */
+static uint64_t togglePwmState(PwmConfig *state) {
+#if DEBUG_PWM
+ scheduleMsg(&logger, "togglePwmState phaseIndex=%d iteration=%d", state->safe.phaseIndex, state->safe.iteration);
+ scheduleMsg(&logger, "state->period=%f state->safe.period=%f", state->period, state->safe.period);
+#endif
+
+ if (cisnan(state->periodUs)) {
+ /**
+ * zero period means PWM is paused
+ */
+ return 1;
+ }
+
+ state->handleCycleStart();
+
+ /**
+ * Here is where the 'business logic' - the actual pin state change is happening
+ */
+ // callback state index is offset by one. todo: why? can we simplify this?
+ int cbStateIndex = state->safe.phaseIndex == 0 ? state->phaseCount - 1 : state->safe.phaseIndex - 1;
+ state->stateChangeCallback(state, cbStateIndex);
+
+ uint64_t nextSwitchTimeUs = getNextSwitchTimeUs(state);
+#if DEBUG_PWM
+ scheduleMsg(&logger, "%s: nextSwitchTime %d", state->name, nextSwitchTime);
+#endif
+ // signed value is needed here
+ int64_t timeToSwitch = nextSwitchTimeUs - getTimeNowUs();
+ if (timeToSwitch < 1) {
+ /**
+ * We are here if we are late for a state transition.
+ * At 12000RPM=200Hz with a 60 toothed wheel we need to change state every
+ * 1000000 / 200 / 120 = ~41 uS. We are kind of OK.
+ *
+ * We are also here after a flash write. Flash write freezes the whole chip for a couple of seconds,
+ * so PWM generation and trigger simulation generation would have to recover from this time lag.
+ */
+ //todo: introduce error and test this error handling warning(OBD_PCM_Processor_Fault, "PWM: negative switch time");
+ timeToSwitch = 10;
+ }
+
+ state->safe.phaseIndex++;
+ if (state->safe.phaseIndex == state->phaseCount) {
+ state->safe.phaseIndex = 0; // restart
+ state->safe.iteration++;
+ }
+ return timeToSwitch;
+}
+
+/**
+ * Main PWM loop: toggle pin & schedule next invocation
+ */
+static void timerCallback(PwmConfig *state) {
+ time_t timeToSleepUs = togglePwmState(state);
+ scheduleTask("pwm", &state->scheduling, timeToSleepUs, (schfunc_t) timerCallback, state);
+}
+
+/**
+ * Incoming parameters are potentially just values on current stack, so we have to copy
+ * into our own permanent storage, right?
+ */
+void copyPwmParameters(PwmConfig *state, int phaseCount, float *switchTimes, int waveCount, int **pinStates) {
+ state->phaseCount = phaseCount;
+
+ for (int phaseIndex = 0; phaseIndex < phaseCount; phaseIndex++) {
+ state->multiWave.setSwitchTime(phaseIndex, switchTimes[phaseIndex]);
+
+ for (int waveIndex = 0; waveIndex < waveCount; waveIndex++) {
+// print("output switch time index (%d/%d) at %f to %d\r\n", phaseIndex,waveIndex,
+// switchTimes[phaseIndex], pinStates[waveIndex][phaseIndex]);
+ state->multiWave.waves[waveIndex].pinStates[phaseIndex] = pinStates[waveIndex][phaseIndex];
+ }
+ }
+}
+
+void weComplexInit(const char *msg, PwmConfig *state, int phaseCount, float *switchTimes, int waveCount,
+ int **pinStates, pwm_cycle_callback *cycleCallback, pwm_gen_callback *stateChangeCallback) {
+
+ efiAssertVoid(state->periodUs != 0, "period is not initialized");
+ if (phaseCount == 0) {
+ firmwareError("signal length cannot be zero");
+ return;
+ }
+ if (phaseCount > PWM_PHASE_MAX_COUNT) {
+ firmwareError("too many phases in PWM");
+ return;
+ }
+ if (switchTimes[phaseCount - 1] != 1) {
+ firmwareError("last switch time has to be 1");
+ return;
+ }
+ efiAssertVoid(waveCount > 0, "waveCount should be positive");
+ checkSwitchTimes2(phaseCount, switchTimes);
+
+ state->multiWave.waveCount = waveCount;
+
+ copyPwmParameters(state, phaseCount, switchTimes, waveCount, pinStates);
+
+ state->cycleCallback = cycleCallback;
+ state->stateChangeCallback = stateChangeCallback;
+
+ state->safe.phaseIndex = 0;
+ state->safe.periodUs = -1;
+ state->safe.iteration = -1;
+ state->name = msg;
+
+ // let's start the indefinite callback loop of PWM generation
+ timerCallback(state);
+}
diff --git a/firmware/controllers/system/pwm_generator_logic.h b/firmware/controllers/system/pwm_generator_logic.h
new file mode 100644
index 0000000000..7c8076509f
--- /dev/null
+++ b/firmware/controllers/system/pwm_generator_logic.h
@@ -0,0 +1,107 @@
+/**
+ * @file pwm_generator_logic.h
+ *
+ * @date Mar 2, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef PWM_GENERATOR_LOGIC_H_
+#define PWM_GENERATOR_LOGIC_H_
+
+#include "global.h"
+#include "EfiWave.h"
+#include "io_pins.h"
+#include "scheduler.h"
+
+typedef struct {
+ /**
+ * a copy so that all phases are executed on the same period, even if another thread
+ * would be adjusting PWM parameters
+ */
+ float periodUs;
+ /**
+ * Iteration counter
+ */
+ int iteration;
+ /**
+ * Start time of current iteration
+ */
+ uint64_t startUs;
+ int phaseIndex;
+} pwm_config_safe_state_s;
+
+class PwmConfig;
+
+typedef void (pwm_cycle_callback)(PwmConfig *state);
+typedef void (pwm_gen_callback)(PwmConfig *state, int stateIndex);
+
+/**
+ * @brief Multi-channel software PWM output configuration
+ */
+class PwmConfig {
+public:
+ PwmConfig();
+ PwmConfig(float *switchTimes, single_wave_s *waves);
+ void init(float *switchTimes, single_wave_s *waves);
+
+ void handleCycleStart();
+
+
+ io_pin_e outputPins[PWM_PHASE_MAX_WAVE_PER_PWM];
+ multi_wave_s multiWave;
+ const char *name;
+ /**
+ * float value of PWM period
+ * PWM generation is not happening while this value is zero
+ */
+ float periodUs;
+
+ scheduling_s scheduling;
+
+ pwm_config_safe_state_s safe;
+ /**
+ * Number of events in the cycle
+ */
+ int phaseCount;
+
+ /**
+ * this callback is invoked before each wave generation cycle
+ */
+ pwm_cycle_callback *cycleCallback;
+
+ /**
+ * this main callback is invoked when it's time to switch level on amy of the output channels
+ */
+ pwm_gen_callback *stateChangeCallback;
+};
+
+
+class SimplePwm : public PwmConfig {
+public:
+ SimplePwm();
+ void setSimplePwmDutyCycle(float dutyCycle);
+ int pinStates[2];
+ single_wave_s wave;
+ single_wave_s sr[1];
+ float _switchTimes[2];
+};
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void copyPwmParameters(PwmConfig *state, int phaseCount, float *switchTimes,
+ int waveCount, int **pinStates);
+
+void weComplexInit(const char *msg, PwmConfig *state,
+ int phaseCount, float *swithcTimes, int waveCount, int **pinStates,
+ pwm_cycle_callback *cycleCallback,
+ pwm_gen_callback *callback);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+
+#endif /* PWM_GENERATOR_LOGIC_H_ */
diff --git a/firmware/controllers/system/scheduler.h b/firmware/controllers/system/scheduler.h
new file mode 100644
index 0000000000..4651924a44
--- /dev/null
+++ b/firmware/controllers/system/scheduler.h
@@ -0,0 +1,38 @@
+/**
+ * @file scheduler.h
+ *
+ * @date May 18, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+#ifndef SCHEDULER_H_
+#define SCHEDULER_H_
+
+#include "main.h"
+
+typedef void (*schfunc_t)(void *);
+
+typedef struct scheduling_struct scheduling_s;
+struct scheduling_struct {
+#if EFI_SIGNAL_EXECUTOR_SLEEP
+ VirtualTimer timer;
+#endif /* EFI_SIGNAL_EXECUTOR_SLEEP */
+
+ volatile uint64_t momentUs;
+ schfunc_t callback;
+ void *param;
+ scheduling_s *next;
+ const char *name;
+};
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void scheduleTask(const char *prefix, scheduling_s *scheduling, int delayUs, schfunc_t callback, void *param);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SCHEDULER_H_ */
diff --git a/firmware/controllers/system/signal_executor_sleep.c b/firmware/controllers/system/signal_executor_sleep.c
new file mode 100644
index 0000000000..4d67c31d49
--- /dev/null
+++ b/firmware/controllers/system/signal_executor_sleep.c
@@ -0,0 +1,54 @@
+/**
+ * @file signal_executor_sleep.c
+ * @brief Asynchronous output signal code
+ *
+ * Here we have the simplest, thread-based implementation of signal executor.
+ * TODO: https://sourceforge.net/p/rusefi/tickets/6/
+ *
+ * @date Feb 10, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ */
+
+#include "main.h"
+#include "scheduler.h"
+#include "datalogging.h"
+
+#if EFI_SIGNAL_EXECUTOR_SLEEP || defined(__DOXYGEN__)
+
+void scheduleTask(const char *prefix, scheduling_s *scheduling, int delayUs, schfunc_t callback, void *param) {
+ int delaySt = delayUs * CH_FREQUENCY / 1000000;
+ if (delaySt == 0) {
+ /**
+ * in case of zero delay, we should invoke the callback
+ */
+ callback(param);
+ return;
+ }
+
+ lockAnyContext();
+ int isArmed = chVTIsArmedI(&scheduling->timer);
+ if (isArmed)
+ chVTResetI(&scheduling->timer);
+
+ chVTSetI(&scheduling->timer, delaySt, (vtfunc_t)callback, param);
+ unlockAnyContext();
+}
+
+void initSignalExecutorImpl(void) {
+
+}
+
+#endif /* EFI_SIGNAL_EXECUTOR_SLEEP */
diff --git a/firmware/controllers/system/signal_executor_sleep.h b/firmware/controllers/system/signal_executor_sleep.h
new file mode 100644
index 0000000000..a01723d0d1
--- /dev/null
+++ b/firmware/controllers/system/signal_executor_sleep.h
@@ -0,0 +1,11 @@
+/*
+ * @file signal_executir_sleep.h
+
+ * @date Oct 28, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef SIGNAL_EXECUTOR_SLEEP_H_
+#define SIGNAL_EXECUTOR_SLEEP_H_
+
+#endif /* SIGNAL_EXECUTOR_SLEEP_H_ */
diff --git a/firmware/controllers/system/system.mk b/firmware/controllers/system/system.mk
new file mode 100644
index 0000000000..7fa596a215
--- /dev/null
+++ b/firmware/controllers/system/system.mk
@@ -0,0 +1,7 @@
+
+SYSTEMSRC = \
+ $(PROJECT_DIR)/controllers/system/signal_executor_sleep.c
+
+SYSTEMSRC_CPP = $(PROJECT_DIR)/controllers/system/pwm_generator_logic.cpp \
+ $(PROJECT_DIR)/controllers/system/event_queue.cpp \
+ $(PROJECT_DIR)/controllers/system/SingleTimerExecutor.cpp
\ No newline at end of file
diff --git a/firmware/controllers/trigger/main_trigger_callback.cpp b/firmware/controllers/trigger/main_trigger_callback.cpp
new file mode 100644
index 0000000000..789d354af7
--- /dev/null
+++ b/firmware/controllers/trigger/main_trigger_callback.cpp
@@ -0,0 +1,359 @@
+/**
+ * @file main_trigger_callback.cpp
+ * @brief Main logic is here!
+ *
+ * See http://rusefi.com/docs/html/
+ *
+ * @date Feb 7, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ */
+
+#include "main.h"
+
+#if !EFI_PROD_CODE && !EFI_SIMULATOR
+
+#define chThdSelf() 0
+#define getRemainingStack(x) (999999)
+
+#endif
+
+#if EFI_ENGINE_CONTROL || defined(__DOXYGEN__)
+
+#include "main_trigger_callback.h"
+#include "ec2.h"
+
+#include "engine_math.h"
+#include "trigger_central.h"
+#include "rpm_calculator.h"
+#include "signal_executor.h"
+#include "engine_configuration.h"
+#include "interpolation.h"
+#include "advance_map.h"
+#include "allsensors.h"
+#include "cyclic_buffer.h"
+#include "histogram.h"
+#include "fuel_math.h"
+#include "histogram.h"
+#if EFI_PROD_CODE
+#include "rfiutil.h"
+#endif /* EFI_HISTOGRAMS */
+#include "LocalVersionHolder.h"
+#include "event_queue.h"
+#include "engine.h"
+
+static LocalVersionHolder localVersion;
+
+
+static MainTriggerCallback mainTriggerCallbackInstance;
+
+/**
+ * That's the list of pending spark firing events
+ */
+static IgnitionEvent *iHead = NULL;
+
+/**
+ * In order to archive higher event precision, we are using a hybrid approach
+ * where we are scheduling events based on the closest trigger event with a time offset.
+ *
+ * This queue is using global trigger event index as 'time'
+ */
+static EventQueue triggerEventsQueue;
+
+static cyclic_buffer ignitionErrorDetection;
+
+static Logging logger;
+
+static void handleFuelInjectionEvent(MainTriggerCallback *mainTriggerCallback, ActuatorEvent *event, int rpm) {
+ float fuelMs = getFuelMs(rpm, mainTriggerCallback->engine) * mainTriggerCallback->engineConfiguration->globalFuelCorrection;
+ if (cisnan(fuelMs)) {
+ warning(OBD_PCM_Processor_Fault, "NaN injection pulse");
+ return;
+ }
+ if (fuelMs < 0) {
+ warning(OBD_PCM_Processor_Fault, "Negative injection pulse %f", fuelMs);
+ return;
+ }
+
+ float delay = getOneDegreeTimeMs(rpm) * event->position.angleOffset;
+
+// if (isCranking())
+// scheduleMsg(&logger, "crankingFuel=%f for CLT=%fC", fuelMs, getCoolantTemperature());
+
+ scheduleOutput(event->actuator, delay, fuelMs);
+}
+
+static void handleFuel(Engine *engine, MainTriggerCallback *mainTriggerCallback, int eventIndex, int rpm) {
+ if (!isInjectionEnabled(mainTriggerCallback->engineConfiguration))
+ return;
+ efiAssertVoid(getRemainingStack(chThdSelf()) > 16, "stack#3");
+ efiAssertVoid(eventIndex < mainTriggerCallback->engineConfiguration2->triggerShape.getLength(),
+ "event index");
+
+ /**
+ * Ignition events are defined by addFuelEvents() according to selected
+ * fueling strategy
+ */
+ ActuatorEventList *source =
+ isCranking() ?
+ &mainTriggerCallback->engineConfiguration2->engineEventConfiguration.crankingInjectionEvents :
+ &mainTriggerCallback->engineConfiguration2->engineEventConfiguration.injectionEvents;
+
+ for (int i = 0; i < source->size; i++) {
+ ActuatorEvent *event = &source->events[i];
+ if (event->position.eventIndex != eventIndex)
+ continue;
+ handleFuelInjectionEvent(mainTriggerCallback, event, rpm);
+ }
+}
+
+static void handleSparkEvent(MainTriggerCallback *mainTriggerCallback, int eventIndex, IgnitionEvent *iEvent, int rpm) {
+ engine_configuration_s *engineConfiguration = mainTriggerCallback->engineConfiguration;
+ engine_configuration2_s *engineConfiguration2 = mainTriggerCallback->engineConfiguration2;
+
+ float dwellMs = getSparkDwellMsT(engineConfiguration, rpm);
+ if (cisnan(dwellMs) || dwellMs < 0) {
+ firmwareError("invalid dwell: %f at %d", dwellMs, rpm);
+ return;
+ }
+
+ float sparkDelay = getOneDegreeTimeMs(rpm) * iEvent->dwellPosition.angleOffset;
+ int isIgnitionError = sparkDelay < 0;
+ ignitionErrorDetection.add(isIgnitionError);
+ if (isIgnitionError) {
+#if EFI_PROD_CODE
+ scheduleMsg(&logger, "Negative spark delay=%f", sparkDelay);
+#endif
+ sparkDelay = 0;
+ return;
+ }
+
+ if (cisnan(dwellMs)) {
+ firmwareError("NaN in scheduleOutput", dwellMs);
+ return;
+ }
+
+ /**
+ * We are alternating two event lists in order to avoid a potential issue around revolution boundary
+ * when an event is scheduled within the next revolution.
+ */
+ scheduling_s * sUp = &iEvent->signalTimerUp;
+ scheduling_s * sDown = &iEvent->signalTimerDown;
+
+ /**
+ * The start of charge is always within the current trigger event range, so just plain time-based scheduling
+ */
+ scheduleTask("spark up", sUp, (int) MS2US(sparkDelay), (schfunc_t) &turnPinHigh, (void *) iEvent->io_pin);
+ /**
+ * Spark event is often happening during a later trigger event timeframe
+ * TODO: improve precision
+ */
+
+ findTriggerPosition(engineConfiguration, &engineConfiguration2->triggerShape, &iEvent->sparkPosition,
+ iEvent->advance);
+
+ if (iEvent->sparkPosition.eventIndex == eventIndex) {
+ /**
+ * Spark should be fired before the next trigger event - time-based delay is best precision possible
+ */
+ float timeTillIgnitionUs = getOneDegreeTimeUs(rpm) * iEvent->sparkPosition.angleOffset;
+
+ scheduleTask("spark 1down", sDown, (int) timeTillIgnitionUs, (schfunc_t) &turnPinLow, (void*) iEvent->io_pin);
+ } else {
+ /**
+ * Spark should be scheduled in relation to some future trigger event, this way we get better firing precision
+ */
+ bool isPending = assertNotInList(iHead, iEvent);
+ if (isPending)
+ return;
+
+ LL_APPEND(iHead, iEvent);
+ }
+}
+
+static void handleSpark(MainTriggerCallback *mainTriggerCallback, int eventIndex, int rpm, IgnitionEventList *list) {
+ if (!isValidRpm(rpm) || !mainTriggerCallback->engineConfiguration->isIgnitionEnabled)
+ return; // this might happen for instance in case of a single trigger event after a pause
+
+ /**
+ * Ignition schedule is defined once per revolution
+ * See initializeIgnitionActions()
+ */
+
+ IgnitionEvent *current, *tmp;
+
+ LL_FOREACH_SAFE(iHead, current, tmp)
+ {
+ if (current->sparkPosition.eventIndex == eventIndex) {
+ // time to fire a spark which was scheduled previously
+ LL_DELETE(iHead, current);
+
+ scheduling_s * sDown = ¤t->signalTimerDown;
+
+ float timeTillIgnitionUs = getOneDegreeTimeUs(rpm) * current->sparkPosition.angleOffset;
+ scheduleTask("spark 2down", sDown, (int) timeTillIgnitionUs, (schfunc_t) &turnPinLow, (void*) current->io_pin);
+ }
+ }
+
+// scheduleSimpleMsg(&logger, "eventId spark ", eventIndex);
+ for (int i = 0; i < list->size; i++) {
+ IgnitionEvent *event = &list->events[i];
+ if (event->dwellPosition.eventIndex != eventIndex)
+ continue;
+ handleSparkEvent(mainTriggerCallback, eventIndex, event, rpm);
+ }
+}
+
+static histogram_s mainLoopHisto;
+
+void showMainHistogram(void) {
+#if EFI_PROD_CODE
+ printHistogram(&logger, &mainLoopHisto);
+#endif
+}
+
+extern Engine engine;
+
+/**
+ * This is the main trigger event handler.
+ * Both injection and ignition are controlled from this method.
+ */
+void onTriggerEvent(trigger_event_e ckpSignalType, int eventIndex, MainTriggerCallback *mainTriggerCallback) {
+ efiAssertVoid(eventIndex < 2 * mainTriggerCallback->engineConfiguration2->triggerShape.shaftPositionEventCount,
+ "event index");
+ efiAssertVoid(getRemainingStack(chThdSelf()) > 16, "stack#3");
+
+// todo int rpm = getRpmE(mainTriggerCallback->engine);
+ int rpm = getRpmE(&engine);
+ if (rpm == 0) {
+ // this happens while we just start cranking
+ // todo: check for 'trigger->is_synchnonized?'
+ return;
+ }
+ if (rpm == NOISY_RPM) {
+ warning(OBD_Camshaft_Position_Sensor_Circuit_Range_Performance, "noisy trigger");
+ return;
+ }
+ if (rpm > mainTriggerCallback->engineConfiguration->rpmHardLimit) {
+ warning(OBD_PCM_Processor_Fault, "skipping stroke due to rpm=%d", rpm);
+ return;
+ }
+
+#if EFI_HISTOGRAMS && EFI_PROD_CODE
+ int beforeCallback = hal_lld_get_counter_value();
+#endif
+
+ int revolutionIndex = getRevolutionCounter() % 2;
+
+ if (eventIndex == 0) {
+ if (localVersion.isOld())
+ prepareOutputSignals(mainTriggerCallback->engineConfiguration, mainTriggerCallback->engineConfiguration2);
+
+ /**
+ * TODO: warning. there is a bit of a hack here, todo: improve.
+ * currently output signals/times signalTimerUp from the previous revolutions could be
+ * still used because they have crossed the revolution boundary
+ * but we are already repurposing the output signals, but everything works because we
+ * are not affecting that space in memory. todo: use two instances of 'ignitionSignals'
+ */
+
+ /**
+ * Within one engine cycle all cylinders are fired with same timing advance.
+ * todo: one day we can control cylinders individually
+ */
+ float dwellMs = getSparkDwellMsT(mainTriggerCallback->engineConfiguration, rpm);
+ if (cisnan(dwellMs) || dwellMs < 0) {
+ firmwareError("invalid dwell: %f at %d", dwellMs, rpm);
+ return;
+ }
+ float advance = getAdvance(rpm, getEngineLoadT(mainTriggerCallback->engine));
+
+ float dwellAngle = dwellMs / getOneDegreeTimeMs(rpm);
+
+ initializeIgnitionActions(advance, dwellAngle, mainTriggerCallback->engineConfiguration,
+ mainTriggerCallback->engineConfiguration2,
+ &mainTriggerCallback->engineConfiguration2->engineEventConfiguration.ignitionEvents[revolutionIndex]);
+ }
+
+ triggerEventsQueue.executeAll(getCrankEventCounter());
+
+//todo handleFuel(mainTriggerCallback->engine, mainTriggerCallback, eventIndex, rpm);
+ handleFuel(&engine, mainTriggerCallback, eventIndex, rpm);
+ handleSpark(mainTriggerCallback, eventIndex, rpm,
+ &mainTriggerCallback->engineConfiguration2->engineEventConfiguration.ignitionEvents[revolutionIndex]);
+#if EFI_HISTOGRAMS && EFI_PROD_CODE
+ int diff = hal_lld_get_counter_value() - beforeCallback;
+ if (diff > 0)
+ hsAdd(&mainLoopHisto, diff);
+#endif /* EFI_HISTOGRAMS */
+}
+
+#include "wave_chart.h"
+
+static void showTriggerHistogram(void) {
+ printAllCallbacksHistogram();
+ showMainHistogram();
+#if EFI_PROD_CODE
+ showWaveChartHistogram();
+#endif
+}
+
+void MainTriggerCallback::init(Engine *engine, engine_configuration2_s *engineConfiguration2) {
+ efiAssertVoid(engine!=NULL, "engine NULL");
+ this->engine = engine;
+ this->engineConfiguration = engine->engineConfiguration;
+ efiAssertVoid(engineConfiguration!=NULL, "engineConfiguration NULL");
+ this->engineConfiguration2 = engineConfiguration2;
+}
+
+
+static void showMainInfo(void) {
+ int rpm = getRpm();
+ float el = getEngineLoadT(mainTriggerCallbackInstance.engine);
+#if EFI_PROD_CODE
+ scheduleMsg(&logger, "rpm %d engine_load %f", rpm, el);
+ scheduleMsg(&logger, "fuel %fms timing %f", getFuelMs(rpm, mainTriggerCallbackInstance.engine), getAdvance(rpm, el));
+#endif
+}
+
+void initMainEventListener(Engine *engine, engine_configuration2_s *engineConfiguration2) {
+ efiAssertVoid(engine!=NULL, "null engine");
+
+ mainTriggerCallbackInstance.init(engine, engineConfiguration2);
+
+#if EFI_PROD_CODE || defined(__DOXYGEN__)
+ addConsoleAction("performanceinfo", showTriggerHistogram);
+ addConsoleAction("maininfo", showMainInfo);
+
+ initLogging(&logger, "main event handler");
+ printMsg(&logger, "initMainLoop: %d", currentTimeMillis());
+ if (!isInjectionEnabled(mainTriggerCallbackInstance.engineConfiguration))
+ printMsg(&logger, "!!!!!!!!!!!!!!!!!!! injection disabled");
+#endif
+
+#if EFI_HISTOGRAMS
+ initHistogram(&mainLoopHisto, "main callback");
+#endif /* EFI_HISTOGRAMS */
+
+ addTriggerEventListener((ShaftPositionListener) &onTriggerEvent, "main loop", &mainTriggerCallbackInstance);
+}
+
+int isIgnitionTimingError(void) {
+ return ignitionErrorDetection.sum(6) > 4;
+}
+
+
+
+#endif /* EFI_ENGINE_CONTROL */
diff --git a/firmware/controllers/trigger/rpm_calculator.cpp b/firmware/controllers/trigger/rpm_calculator.cpp
new file mode 100644
index 0000000000..f44ad0e1f0
--- /dev/null
+++ b/firmware/controllers/trigger/rpm_calculator.cpp
@@ -0,0 +1,258 @@
+/**
+ * @file rpm_calculator.c
+ * @brief RPM calculator
+ *
+ * Here we listen to position sensor events in order to figure our if engine is currently running or not.
+ * Actual getRpm() is calculated once per crankshaft revolution, based on the amount of time passed
+ * since the start of previous shaft revolution.
+ *
+ * @date Jan 1, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+
+#include "rpm_calculator.h"
+
+#if EFI_WAVE_CHART
+#include "wave_chart.h"
+extern WaveChart waveChart;
+#endif /* EFI_WAVE_CHART */
+
+#if EFI_SHAFT_POSITION_INPUT || defined(__DOXYGEN__)
+
+#include "trigger_central.h"
+#include "engine_configuration.h"
+#include "ec2.h"
+#include "engine_math.h"
+#if EFI_PROD_CODE
+#include "rfiutil.h"
+#include "engine.h"
+#endif
+
+#if EFI_ANALOG_CHART
+#include "analog_chart.h"
+#endif /* EFI_PROD_CODE */
+
+#define UNREALISTIC_RPM 30000
+
+#define TOP_DEAD_CENTER_MESSAGE "r"
+
+
+/**
+ * @return -1 in case of isNoisySignal(), current RPM otherwise
+ */
+int getRpmE(Engine *engine) {
+ efiAssert(engine->rpmCalculator!=NULL, "rpmCalculator not assigned", -1);
+ return engine->rpmCalculator->rpm();
+}
+
+extern engine_configuration_s *engineConfiguration;
+extern engine_configuration2_s *engineConfiguration2;
+
+#if EFI_PROD_CODE || EFI_SIMULATOR
+static Logging logger;
+extern Engine engine;
+#endif
+
+RpmCalculator::RpmCalculator() {
+ rpmValue = 0;
+
+ // we need this initial to have not_running at first invocation
+ lastRpmEventTimeUs = (uint64_t) -10 * US_PER_SECOND;
+}
+
+/**
+ * @return true if there was a full shaft revolution within the last second
+ */
+bool RpmCalculator::isRunning(void) {
+ uint64_t nowUs = getTimeNowUs();
+ return nowUs - lastRpmEventTimeUs < US_PER_SECOND;
+}
+
+int RpmCalculator::rpm(void) {
+ if (!this->isRunning())
+ return 0;
+ return rpmValue;
+}
+
+bool isValidRpm(int rpm) {
+ return rpm > 0 && rpm < UNREALISTIC_RPM;
+}
+
+#if (EFI_PROD_CODE || EFI_SIMULATOR) || defined(__DOXYGEN__)
+bool isCranking(void) {
+ int rpm = getRpm();
+ return isCrankingR(rpm);
+}
+#endif
+
+/**
+ * Checks for noise on the trigger input line. Noise is detected by an unexpectedly small time gap between
+ * current and previous trigger input events.
+ *
+ * @return TRUE if noise is detected
+ */
+static int isNoisySignal(RpmCalculator * rpmState, uint64_t nowUs) {
+ uint64_t diff = nowUs - rpmState->lastRpmEventTimeUs;
+ /**
+ * 60/2 wheel at 8000 rpm
+ * 60000000us / 8000 / 120 = 62us
+ */
+ return diff < 40; // that's 40us
+}
+
+static char shaft_signal_msg_index[15];
+
+/**
+ * @brief Shaft position callback used by RPM calculation logic.
+ *
+ * This callback should always be the first of trigger callbacks because other callbacks depend of values
+ * updated here.
+ * This callback is invoked on interrupt thread.
+ */
+void rpmShaftPositionCallback(trigger_event_e ckpSignalType, int index, RpmCalculator *rpmState) {
+ itoa10(&shaft_signal_msg_index[1], index);
+ if (ckpSignalType == SHAFT_PRIMARY_UP) {
+ addWaveChartEvent(WC_CRANK1, WC_UP, (char*) shaft_signal_msg_index);
+ } else if (ckpSignalType == SHAFT_PRIMARY_DOWN) {
+ addWaveChartEvent(WC_CRANK1, WC_DOWN, (char*) shaft_signal_msg_index);
+ } else if (ckpSignalType == SHAFT_SECONDARY_UP) {
+ addWaveChartEvent(WC_CRANK2, WC_UP, (char*) shaft_signal_msg_index);
+ } else if (ckpSignalType == SHAFT_SECONDARY_DOWN) {
+ addWaveChartEvent(WC_CRANK2, WC_DOWN, (char*) shaft_signal_msg_index);
+ } else if (ckpSignalType == SHAFT_3RD_UP) {
+ addWaveChartEvent(WC_CRANK3, WC_UP, (char*) shaft_signal_msg_index);
+ } else if (ckpSignalType == SHAFT_3RD_DOWN) {
+ addWaveChartEvent(WC_CRANK3, WC_DOWN, (char*) shaft_signal_msg_index);
+ }
+
+ if (index != 0) {
+#if EFI_ANALOG_CHART || defined(__DOXYGEN__)
+ if (engineConfiguration->analogChartMode == AC_TRIGGER)
+ acAddData(getCrankshaftAngle(getTimeNowUs()), 1000 * ckpSignalType + index);
+#endif
+ return;
+ }
+ rpmState->revolutionCounter++;
+
+ uint64_t nowUs = getTimeNowUs();
+
+ bool hadRpmRecently = rpmState->isRunning();
+
+ if (hadRpmRecently) {
+ if (isNoisySignal(rpmState, nowUs)) {
+ // unexpected state. Noise?
+ rpmState->rpmValue = NOISY_RPM;
+ } else {
+ uint64_t diff = nowUs - rpmState->lastRpmEventTimeUs;
+ // 60000 because per minute
+ // * 2 because each revolution of crankshaft consists of two camshaft revolutions
+ // need to measure time from the previous non-skipped event
+ /**
+ * Four stroke cycle is two crankshaft revolutions
+ */
+
+ int rpm = (int) (60 * US_PER_SECOND * 2 / diff);
+ rpmState->rpmValue = rpm > UNREALISTIC_RPM ? NOISY_RPM : rpm;
+ }
+ }
+ rpmState->lastRpmEventTimeUs = nowUs;
+#if EFI_ANALOG_CHART || defined(__DOXYGEN__)
+ if (engineConfiguration->analogChartMode == AC_TRIGGER)
+ acAddData(getCrankshaftAngle(nowUs), index);
+#endif
+}
+
+static scheduling_s tdcScheduler[2];
+
+static char rpmBuffer[10];
+
+#if (EFI_PROD_CODE || EFI_SIMULATOR) || defined(__DOXYGEN__)
+/**
+ * This callback has nothing to do with actual engine control, it just sends a Top Dead Center mark to the dev console
+ * digital sniffer.
+ */
+static void onTdcCallback(void) {
+ itoa10(rpmBuffer, getRpm());
+ addWaveChartEvent(TOP_DEAD_CENTER_MESSAGE, (char*) rpmBuffer, "");
+}
+
+/**
+ * This trigger callback schedules the actual physical TDC callback in relation to trigger synchronization point.
+ */
+static void tdcMarkCallback(trigger_event_e ckpSignalType, int index0, void *arg) {
+ bool isTriggerSynchronizationPoint = index0 == 0;
+ if (isTriggerSynchronizationPoint) {
+ int revIndex2 = getRevolutionCounter() % 2;
+ // todo: use event-based scheduling, not just time-based scheduling
+ scheduleByAngle(&tdcScheduler[revIndex2], engineConfiguration->globalTriggerAngleOffset, (schfunc_t) onTdcCallback, NULL);
+ }
+}
+#endif
+
+static RpmCalculator rpmState;
+
+uint64_t getLastRpmEventTime(void) {
+ return rpmState.lastRpmEventTimeUs;
+}
+
+int getRevolutionCounter(void) {
+ return rpmState.revolutionCounter;
+}
+
+/**
+ * @return Current crankshaft angle, 0 to 720 for four-stroke
+ */
+float getCrankshaftAngle(uint64_t timeUs) {
+ uint64_t timeSinceZeroAngle = timeUs - rpmState.lastRpmEventTimeUs;
+
+ float cRevolutionTimeMs = getCrankshaftRevolutionTimeMs(rpmState.rpm());
+
+ return 360.0 * timeSinceZeroAngle / cRevolutionTimeMs / 1000;
+}
+
+void initRpmCalculator(void) {
+#if (EFI_PROD_CODE || EFI_SIMULATOR) || defined(__DOXYGEN__)
+ initLogging(&logger, "rpm calc");
+ engine.rpmCalculator = &rpmState;
+
+ tdcScheduler[0].name = "tdc0";
+ tdcScheduler[1].name = "tdc1";
+ addTriggerEventListener(&tdcMarkCallback, "chart TDC mark", NULL);
+#endif
+
+ strcpy((char*) shaft_signal_msg_index, "_");
+
+ addTriggerEventListener((ShaftPositionListener)&rpmShaftPositionCallback, "rpm reporter", &rpmState);
+}
+
+#if (EFI_PROD_CODE || EFI_SIMULATOR) || defined(__DOXYGEN__)
+/**
+ * Schedules a callback 'angle' degree of crankshaft from now.
+ * The callback would be executed once after the duration of time which
+ * it takes the crankshaft to rotate to the specified angle.
+ */
+void scheduleByAngle(scheduling_s *timer, float angle, schfunc_t callback, void *param) {
+ int rpm = getRpm();
+ if (!isValidRpm(rpm)) {
+ // this might happen in case of a single trigger event after a pause
+ warning(OBD_PCM_Processor_Fault, "Invalid rpm: %d", rpm);
+ return;
+ }
+ float delayMs = getOneDegreeTimeMs(rpm) * angle;
+ if (cisnan(delayMs)) {
+ firmwareError("NaN delay?");
+ return;
+ }
+ scheduleTask("by angle", timer, (int)MS2US(delayMs), callback, param);
+}
+#endif
+
+#endif /* EFI_SHAFT_POSITION_INPUT */
+
+void addWaveChartEvent(const char *name, const char * msg, const char *msg2) {
+#if EFI_WAVE_CHART
+ addWaveChartEvent3(&waveChart, name, msg, msg2);
+#endif /* EFI_WAVE_CHART */
+}
diff --git a/firmware/controllers/trigger/rpm_calculator.h b/firmware/controllers/trigger/rpm_calculator.h
new file mode 100644
index 0000000000..c014c676f9
--- /dev/null
+++ b/firmware/controllers/trigger/rpm_calculator.h
@@ -0,0 +1,73 @@
+/**
+ * @file rpm_calculator.h
+ * @brief Shaft position sensor(s) decoder header
+ *
+ * @date Jan 1, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef RPM_REPORTER_H_
+#define RPM_REPORTER_H_
+
+#include
+
+#define WC_DOWN "d"
+#define WC_UP "u"
+#define WC_CRANK1 "c1"
+#define WC_CRANK2 "c2"
+#define WC_CRANK3 "c3"
+
+#define NOISY_RPM -1
+
+#ifdef __cplusplus
+#include "engine.h"
+
+class RpmCalculator {
+public:
+ RpmCalculator();
+ int rpm(void);
+ volatile int rpmValue;
+ volatile uint64_t lastRpmEventTimeUs;
+ /**
+ * This counter is incremented with each revolution of one of the shafts. Could be
+ * crankshaft could be camshaft.
+ */
+ volatile int revolutionCounter;
+ bool isRunning(void);
+};
+
+#define getRpm() getRpmE(&engine)
+
+/**
+ * @brief Current RPM
+ */
+int getRpmE(Engine *engine);
+void rpmShaftPositionCallback(trigger_event_e ckpSignalType, int index, RpmCalculator *rpmState);
+#endif
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+/**
+ * @brief Initialize RPM calculator
+ */
+void initRpmCalculator(void);
+bool isCranking(void);
+uint64_t getLastRpmEventTime(void);
+
+int getRevolutionCounter(void);
+float getCrankshaftAngle(uint64_t timeUs);
+/**
+ * @return true if engine is running
+ */
+bool isRunning(void);
+bool isValidRpm(int rpm);
+void addWaveChartEvent(const char *name, const char *msg, const char *msg2);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* RPM_REPORTER_H_ */
diff --git a/firmware/controllers/trigger/trigger.mk b/firmware/controllers/trigger/trigger.mk
new file mode 100644
index 0000000000..ce14acdf16
--- /dev/null
+++ b/firmware/controllers/trigger/trigger.mk
@@ -0,0 +1,17 @@
+
+TRIGGER_SRC =
+
+TRIGGER_DECODERS_SRC_CPP = \
+ $(PROJECT_DIR)/controllers/trigger/trigger_bmw.cpp \
+ $(PROJECT_DIR)/controllers/trigger/trigger_mazda.cpp \
+ $(PROJECT_DIR)/controllers/trigger/trigger_chrysler.cpp \
+ $(PROJECT_DIR)/controllers/trigger/trigger_structure.cpp \
+ $(PROJECT_DIR)/controllers/trigger/trigger_decoder.cpp \
+ $(PROJECT_DIR)/controllers/trigger/trigger_mitsubishi.cpp \
+ $(PROJECT_DIR)/controllers/trigger/trigger_gm.cpp
+
+TRIGGER_SRC_CPP = \
+ $(PROJECT_DIR)/controllers/trigger/trigger_emulator_algo.cpp \
+ $(PROJECT_DIR)/controllers/trigger/rpm_calculator.cpp \
+ $(PROJECT_DIR)/controllers/trigger/trigger_central.cpp \
+ $(PROJECT_DIR)/controllers/trigger/main_trigger_callback.cpp
diff --git a/firmware/controllers/trigger/trigger_bmw.cpp b/firmware/controllers/trigger/trigger_bmw.cpp
new file mode 100644
index 0000000000..6e18bf1555
--- /dev/null
+++ b/firmware/controllers/trigger/trigger_bmw.cpp
@@ -0,0 +1,74 @@
+/**
+ * @file trigger_bmw.cpp
+ *
+ * @date May 11, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "trigger_bmw.h"
+
+static inline float addPair(trigger_shape_s *s, float a, float w) {
+ s->addEvent(a, T_SECONDARY, TV_HIGH);
+ a += w;
+ s->addEvent(a, T_SECONDARY, TV_LOW);
+ a += w;
+ return a;
+}
+
+void configureMiniCooperTriggerShape(trigger_shape_s *s) {
+
+ s->reset(FOUR_STROKE_CAM_SENSOR);
+
+// s->initialState[0] = 1;
+
+ float w = 360.0 / 121;
+ float a = w / 2;
+ s->addEvent(a, T_SECONDARY, TV_LOW);
+ a += w;
+
+ for (int i = 0; i <= 22; i++) {
+ a = addPair(s, a, w);
+ }
+
+ a += 3 * w;
+
+ float firstGapAngle = a;
+
+ s->addEvent(a, T_SECONDARY, TV_HIGH);
+ a += 3 * w;
+ s->addEvent(a, T_SECONDARY, TV_LOW);
+ a += w;
+
+ for (int i = 0; i < 36; i++) {
+ a = addPair(s, a, w);
+ }
+
+ s->addEvent(376, T_PRIMARY, TV_HIGH);
+
+ for (int i = 0; i < 21; i++) {
+ a = addPair(s, a, w);
+ }
+ a += 3 * w;
+
+ efiAssertVoid(absF(firstGapAngle + 360 - a) < 0.1, "shape constraint");
+
+ s->addEvent(a, T_SECONDARY, TV_HIGH);
+ a += 3 * w;
+ s->addEvent(a, T_SECONDARY, TV_LOW);
+ a += w;
+
+ for (int i = 0; i < 33; i++) {
+ a = addPair(s, a, w);
+ }
+
+ efiAssertVoid(absF(720 - w / 2 - a) < 0.1, "shape constraint");
+ s->addEvent(a, T_SECONDARY, TV_HIGH);
+
+ s->addEvent(720.0, T_PRIMARY, TV_LOW);
+
+ s->shaftPositionEventCount = s->getSize();
+ /**
+ * With just one tooth on camshaft synchronization is not needed
+ */
+ s->isSynchronizationNeeded = FALSE;
+}
diff --git a/firmware/controllers/trigger/trigger_bmw.h b/firmware/controllers/trigger/trigger_bmw.h
new file mode 100644
index 0000000000..5d0fe6fbf9
--- /dev/null
+++ b/firmware/controllers/trigger/trigger_bmw.h
@@ -0,0 +1,15 @@
+/**
+ * @file trigger_bmw.h
+ *
+ * @date May 11, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+#ifndef TRIGGER_BMW_H_
+#define TRIGGER_BMW_H_
+
+#include "engine_configuration.h"
+#include "ec2.h"
+
+void configureMiniCooperTriggerShape(trigger_shape_s *s);
+
+#endif /* TRIGGER_BMW_H_ */
diff --git a/firmware/controllers/trigger/trigger_central.cpp b/firmware/controllers/trigger/trigger_central.cpp
new file mode 100644
index 0000000000..71d21281a7
--- /dev/null
+++ b/firmware/controllers/trigger/trigger_central.cpp
@@ -0,0 +1,181 @@
+/*
+ * @file trigger_central.cpp
+ *
+ * @date Feb 23, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+#include "trigger_central.h"
+#include "trigger_decoder.h"
+#include "main_trigger_callback.h"
+#include "engine_configuration.h"
+#include "listener_array.h"
+#include "data_buffer.h"
+#include "histogram.h"
+#if EFI_PROD_CODE
+#include "rfiutil.h"
+#endif
+
+static histogram_s triggerCallback;
+
+// we need this initial to have not_running at first invocation
+static volatile uint64_t previousShaftEventTime = (efitimems_t) -10 * US_PER_SECOND;
+
+TriggerCentral triggerCentral;
+
+static Logging logging;
+
+uint64_t getCrankEventCounter() {
+ return triggerCentral.triggerState.getTotalEventCounter();
+}
+
+uint64_t getStartOfRevolutionIndex() {
+ return triggerCentral.triggerState.getStartOfRevolutionIndex();
+}
+
+void TriggerCentral::addEventListener(ShaftPositionListener listener, const char *name, void *arg) {
+ print("registerCkpListener: %s\r\n", name);
+ registerCallback(&triggerListeneres, (IntListener) listener, arg);
+}
+
+/**
+ * @brief Adds a trigger event listener
+ *
+ * Trigger event listener would be invoked on each trigger event. For example, for a 60/2 wheel
+ * that would be 116 events: 58 SHAFT_PRIMARY_UP and 58 SHAFT_PRIMARY_DOWN events.
+ */
+void addTriggerEventListener(ShaftPositionListener listener, const char *name, void *arg) {
+ triggerCentral.addEventListener(listener, name, arg);
+}
+
+#if EFI_PROD_CODE || EFI_SIMULATOR
+extern configuration_s *configuration;
+
+void hwHandleShaftSignal(trigger_event_e signal) {
+ triggerCentral.handleShaftSignal(configuration, signal, getTimeNowUs());
+}
+#endif /* EFI_PROD_CODE */
+
+TriggerCentral::TriggerCentral() {
+ memset(hwEventCounters, 0, sizeof(hwEventCounters));
+ clearCallbacks(&triggerListeneres);
+}
+
+int TriggerCentral::getHwEventCounter(int index) {
+ return hwEventCounters[index];
+}
+
+void TriggerCentral::handleShaftSignal(configuration_s *configuration, trigger_event_e signal, uint64_t nowUs) {
+ efiAssertVoid(configuration!=NULL, "configuration");
+
+ efiAssertVoid(configuration->engineConfiguration!=NULL, "engineConfiguration");
+ efiAssertVoid(configuration->engineConfiguration2!=NULL, "engineConfiguration2");
+
+#if EFI_HISTOGRAMS && EFI_PROD_CODE
+ int beforeCallback = hal_lld_get_counter_value();
+#endif
+ int eventIndex = (int) signal;
+ efiAssertVoid(eventIndex >= 0 && eventIndex < HW_EVENT_TYPES, "signal type");
+ hwEventCounters[eventIndex]++;
+
+ if (nowUs - previousShaftEventTime > US_PER_SECOND) {
+ /**
+ * We are here if there is a time gap between now and previous shaft event - that means the engine is not runnig.
+ * That means we have lost synchronization since the engine is not running :)
+ */
+ triggerState.shaft_is_synchronized = false;
+ }
+ previousShaftEventTime = nowUs;
+
+ trigger_shape_s * triggerShape = &configuration->engineConfiguration2->triggerShape;
+
+ /**
+ * This invocation changes the state of triggerState
+ */
+ triggerState.decodeTriggerEvent(triggerShape, &configuration->engineConfiguration->triggerConfig, signal, nowUs);
+
+ if (!triggerState.shaft_is_synchronized) {
+ // we should not propagate event if we do not know where we are
+ return;
+ }
+
+ if (triggerState.getCurrentIndex() >= configuration->engineConfiguration2->triggerShape.shaftPositionEventCount) {
+ int f = warning(OBD_PCM_Processor_Fault, "unexpected eventIndex=%d", triggerState.getCurrentIndex());
+ if (!f) {
+#if EFI_PROD_CODE
+ // this temporary code is about trigger noise debugging
+ for (int i = 0; i < HW_EVENT_TYPES; i++) {
+ scheduleMsg(&logging, "event type: %d count=%d", i, hwEventCounters[i]);
+ }
+#endif
+ }
+ } else {
+ /**
+ * If we only have a crank position sensor, here we are extending crank revolutions with a 360 degree
+ * cycle into a four stroke, 720 degrees cycle. TODO
+ */
+ int triggerIndexForListeners;
+ if (getOperationMode(configuration->engineConfiguration) == FOUR_STROKE_CAM_SENSOR) {
+ // That's easy - trigger cycle matches engine cycle
+ triggerIndexForListeners = triggerState.getCurrentIndex();
+ } else {
+ bool isEven = (triggerState.getTotalRevolutionCounter() & 1) == 0;
+
+ triggerIndexForListeners = triggerState.getCurrentIndex() + (isEven ? 0 : triggerShape->getSize());
+ }
+
+ /**
+ * Here we invoke all the listeners - the main engine control logic is inside these listeners
+ */
+ invokeIntIntVoidCallbacks(&triggerListeneres, signal, triggerIndexForListeners);
+ }
+#if EFI_HISTOGRAMS && EFI_PROD_CODE
+ int afterCallback = hal_lld_get_counter_value();
+ int diff = afterCallback - beforeCallback;
+ // this counter is only 32 bits so it overflows every minute, let's ignore the value in case of the overflow for simplicity
+ if (diff > 0)
+ hsAdd(&triggerCallback, diff);
+#endif /* EFI_HISTOGRAMS */
+}
+
+void printAllCallbacksHistogram(void) {
+#if EFI_PROD_CODE
+ printHistogram(&logging, &triggerCallback);
+#endif
+}
+
+#if EFI_PROD_CODE || EFI_SIMULATOR
+// todo: eliminate this extern which is needed by 'triggerInfo'
+extern engine_configuration_s *engineConfiguration;
+extern engine_configuration2_s * engineConfiguration2;
+#endif
+
+static void triggerInfo() {
+#if EFI_PROD_CODE || EFI_SIMULATOR
+ scheduleMsg(&logging, "trigger event counters %d/%d/%d/%d", triggerCentral.getHwEventCounter(0),
+ triggerCentral.getHwEventCounter(1), triggerCentral.getHwEventCounter(2),
+ triggerCentral.getHwEventCounter(3));
+ scheduleMsg(&logging, "trigger type=%d/need2ndChannel=%d",
+ engineConfiguration->triggerConfig.triggerType,
+ engineConfiguration->needSecondTriggerInput);
+ scheduleMsg(&logging, "expected duty #0=%f/#1=%f", engineConfiguration2->triggerShape.dutyCycle[0],
+ engineConfiguration2->triggerShape.dutyCycle[1]);
+#endif
+}
+
+float getTriggerDutyCycle(int index) {
+ return triggerCentral.triggerState.getTriggerDutyCycle(index);
+}
+
+void initTriggerCentral(void) {
+#if EFI_PROD_CODE || EFI_SIMULATOR
+ initLogging(&logging, "ShaftPosition");
+ addConsoleAction("triggerinfo", triggerInfo);
+#endif
+
+#if EFI_HISTOGRAMS
+ initHistogram(&triggerCallback, "all callbacks");
+#endif /* EFI_HISTOGRAMS */
+ initTriggerDecoder();
+}
diff --git a/firmware/controllers/trigger/trigger_central.h b/firmware/controllers/trigger/trigger_central.h
new file mode 100644
index 0000000000..765a480f0a
--- /dev/null
+++ b/firmware/controllers/trigger/trigger_central.h
@@ -0,0 +1,53 @@
+/*
+ * @file trigger_central.h
+ *
+ * @date Feb 23, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef TRIGGER_CENTRAL_H_
+#define TRIGGER_CENTRAL_H_
+
+#include "rusefi_enums.h"
+#include "listener_array.h"
+#include "trigger_decoder.h"
+
+typedef void (*ShaftPositionListener)(trigger_event_e signal, int index, void *arg);
+
+#ifdef __cplusplus
+#include "ec2.h"
+
+#define HW_EVENT_TYPES 6
+
+class TriggerCentral {
+public:
+ TriggerCentral();
+ void addEventListener(ShaftPositionListener handler, const char *name, void *arg);
+ void handleShaftSignal(configuration_s *configuration, trigger_event_e signal, uint64_t nowUs);
+ int getHwEventCounter(int index);
+ TriggerState triggerState;
+private:
+ IntListenerArray triggerListeneres;
+ int hwEventCounters[HW_EVENT_TYPES];
+};
+#endif
+
+uint64_t getCrankEventCounter(void);
+uint64_t getStartOfRevolutionIndex(void);
+void hwHandleShaftSignal(trigger_event_e signal);
+float getTriggerDutyCycle(int index);
+void initTriggerCentral(void);
+void printAllCallbacksHistogram(void);
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+void addTriggerEventListener(ShaftPositionListener handler, const char *name, void *arg);
+int isSignalDecoderError(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* TRIGGER_CENTRAL_H_ */
diff --git a/firmware/controllers/trigger/trigger_chrysler.cpp b/firmware/controllers/trigger/trigger_chrysler.cpp
new file mode 100644
index 0000000000..6250b360f4
--- /dev/null
+++ b/firmware/controllers/trigger/trigger_chrysler.cpp
@@ -0,0 +1,71 @@
+/**
+ * @file trigger_chrysler.cpp
+ *
+ * @date Mar 24, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "trigger_chrysler.h"
+
+void configureNeonTriggerShape(trigger_shape_s *s) {
+ s->reset(FOUR_STROKE_CAM_SENSOR);
+
+ setTriggerSynchronizationGap(s, 0.72);
+
+ s->useRiseEdge = false;
+
+
+ // voodoo magic - we always need 720 at the end
+ int base = 720 - 560;
+
+ s->initialState[T_PRIMARY] = 1;
+
+ s->addEvent(base - 720 + 600, T_SECONDARY, TV_HIGH);
+ s->addEvent(base - 720 + 604, T_SECONDARY, TV_LOW);
+ s->addEvent(base - 720 + 616, T_SECONDARY, TV_HIGH);
+ s->addEvent(base - 720 + 620, T_SECONDARY, TV_LOW);
+ s->addEvent(base - 720 + 643, T_SECONDARY, TV_HIGH);
+ s->addEvent(base - 720 + 648, T_SECONDARY, TV_LOW);
+ s->addEvent(base - 720 + 671, T_SECONDARY, TV_HIGH);
+ s->addEvent(base - 720 + 676, T_SECONDARY, TV_LOW);
+
+ s->addEvent(base + 0, T_PRIMARY, TV_LOW);
+
+ s->addEvent(base + 20, T_SECONDARY, TV_HIGH);
+ s->addEvent(base + 60, T_SECONDARY, TV_LOW);
+ s->addEvent(base + 75, T_SECONDARY, TV_HIGH);
+ s->addEvent(base + 79, T_SECONDARY, TV_LOW);
+ s->addEvent(base + 101, T_SECONDARY, TV_HIGH);
+ s->addEvent(base + 106, T_SECONDARY, TV_LOW);
+ s->addEvent(base + 130, T_SECONDARY, TV_HIGH);
+ s->addEvent(base + 135, T_SECONDARY, TV_LOW);
+
+ s->addEvent(base + 200, T_PRIMARY, TV_HIGH); // width = 150
+
+ s->addEvent(base + 236, T_SECONDARY, TV_HIGH);
+ s->addEvent(base + 239, T_SECONDARY, TV_LOW);
+ s->addEvent(base + 250, T_SECONDARY, TV_HIGH);
+ s->addEvent(base + 255, T_SECONDARY, TV_LOW);
+ s->addEvent(base + 277, T_SECONDARY, TV_HIGH);
+ s->addEvent(base + 282, T_SECONDARY, TV_LOW);
+ s->addEvent(base + 305, T_SECONDARY, TV_HIGH);
+ s->addEvent(base + 310, T_SECONDARY, TV_LOW);
+
+ s->addEvent(base + 374, T_SECONDARY, TV_HIGH);
+
+ s->addEvent(base + 395, T_PRIMARY, TV_LOW); // width =
+
+ s->addEvent(base + 418, T_SECONDARY, TV_LOW);
+ s->addEvent(base + 436, T_SECONDARY, TV_HIGH);
+ s->addEvent(base + 441, T_SECONDARY, TV_LOW);
+ s->addEvent(base + 463, T_SECONDARY, TV_HIGH);
+ s->addEvent(base + 468, T_SECONDARY, TV_LOW);
+ s->addEvent(base + 492, T_SECONDARY, TV_HIGH);
+ s->addEvent(base + 497, T_SECONDARY, TV_LOW);
+
+
+ s->addEvent(base + 560, T_PRIMARY, TV_HIGH); // width =
+
+ s->shaftPositionEventCount = 4 + 8 + 8 + 8 + 8;
+}
+
diff --git a/firmware/controllers/trigger/trigger_chrysler.h b/firmware/controllers/trigger/trigger_chrysler.h
new file mode 100644
index 0000000000..adbf3339ea
--- /dev/null
+++ b/firmware/controllers/trigger/trigger_chrysler.h
@@ -0,0 +1,15 @@
+/**
+ * @file trigger_chrysler.h
+ *
+ * @date Mar 24, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef TRIGGER_CHRYSLER_H_
+#define TRIGGER_CHRYSLER_H_
+
+#include "trigger_structure.h"
+
+void configureNeonTriggerShape(trigger_shape_s *s);
+
+#endif /* TRIGGER_CHRYSLER_H_ */
diff --git a/firmware/controllers/trigger/trigger_decoder.cpp b/firmware/controllers/trigger/trigger_decoder.cpp
new file mode 100644
index 0000000000..a23625bef1
--- /dev/null
+++ b/firmware/controllers/trigger/trigger_decoder.cpp
@@ -0,0 +1,384 @@
+/**
+ * @file trigger_decoder.cpp
+ *
+ * @date Dec 24, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ */
+
+#include "main.h"
+#include "obd_error_codes.h"
+#include "trigger_decoder.h"
+#include "cyclic_buffer.h"
+#include "trigger_mazda.h"
+#include "trigger_chrysler.h"
+#include "trigger_gm.h"
+#include "trigger_bmw.h"
+#include "trigger_mitsubishi.h"
+
+#include "trigger_structure.h"
+
+// todo: better name for this constant
+#define HELPER_PERIOD 100000
+
+static cyclic_buffer errorDetection;
+
+/**
+ * @return TRUE is something is wrong with trigger decoding
+ */
+int isTriggerDecoderError(void) {
+ return errorDetection.sum(6) > 4;
+}
+
+static inline bool isSynchronizationGap(TriggerState const *shaftPositionState, trigger_shape_s const *triggerShape,
+ const int currentDuration) {
+ if (!triggerShape->isSynchronizationNeeded) {
+ return false;
+ }
+
+ return currentDuration > shaftPositionState->toothed_previous_duration * triggerShape->syncRatioFrom
+ && currentDuration < shaftPositionState->toothed_previous_duration * triggerShape->syncRatioTo;
+}
+
+static inline bool noSynchronizationResetNeeded(TriggerState *shaftPositionState, trigger_shape_s const *triggerShape) {
+ if (triggerShape->isSynchronizationNeeded) {
+ return false;
+ }
+ if (!shaftPositionState->shaft_is_synchronized) {
+ return true;
+ }
+ /**
+ * in case of noise the counter could be above the expected number of events
+ */
+ return shaftPositionState->getCurrentIndex() >= triggerShape->shaftPositionEventCount - 1;
+}
+
+float TriggerState::getTriggerDutyCycle(int index) {
+ float time = prevTotalTime[index];
+
+ return 100 * time / prevCycleDuration;
+}
+
+static trigger_wheel_e eventIndex[6] = { T_PRIMARY, T_PRIMARY, T_SECONDARY, T_SECONDARY, T_CHANNEL_3, T_CHANNEL_3 };
+static trigger_value_e eventType[6] = { TV_LOW, TV_HIGH, TV_LOW, TV_HIGH, TV_LOW, TV_HIGH };
+
+/**
+ * @brief Trigger decoding happens here
+ * This method changes the state of trigger_state_s data structure according to the trigger event
+ */
+void TriggerState::decodeTriggerEvent(trigger_shape_s const*triggerShape, trigger_config_s const*triggerConfig,
+ trigger_event_e const signal, uint64_t nowUs) {
+ efiAssertVoid(signal <= SHAFT_3RD_UP, "unexpected signal");
+
+ trigger_wheel_e triggerWheel = eventIndex[signal];
+
+ eventCount[triggerWheel]++;
+
+ int isLessImportant = (triggerShape->useRiseEdge && signal != SHAFT_PRIMARY_UP)
+ || (!triggerShape->useRiseEdge && signal != SHAFT_PRIMARY_DOWN);
+
+ if (isLessImportant) {
+ /**
+ * For less important events we simply increment the index.
+ */
+ nextTriggerEvent(triggerWheel, nowUs);
+ return;
+ }
+
+ int64_t currentDuration = isFirstEvent ? 0 : nowUs - toothed_previous_time;
+ isFirstEvent = false;
+ efiAssertVoid(currentDuration >= 0, "decode: negative duration?");
+
+// todo: skip a number of signal from the beginning
+
+#if EFI_PROD_CODE
+// scheduleMsg(&logger, "from %f to %f %d %d", triggerConfig->syncRatioFrom, triggerConfig->syncRatioTo, currentDuration, shaftPositionState->toothed_previous_duration);
+// scheduleMsg(&logger, "ratio %f", 1.0 * currentDuration/ shaftPositionState->toothed_previous_duration);
+#else
+ if (toothed_previous_duration != 0) {
+// printf("ratio %f: cur=%d pref=%d\r\n", 1.0 * currentDuration / shaftPositionState->toothed_previous_duration,
+// currentDuration, shaftPositionState->toothed_previous_duration);
+ }
+#endif
+
+ if (noSynchronizationResetNeeded(this, triggerShape) || isSynchronizationGap(this, triggerShape, currentDuration)) {
+ /**
+ * We can check if things are fine by comparing the number of events in a cycle with the expected number of event.
+ */
+ bool isDecodingError = eventCount[0] != triggerShape->expectedEventCount[0]
+ || eventCount[1] != triggerShape->expectedEventCount[1]
+ || eventCount[2] != triggerShape->expectedEventCount[2];
+
+ errorDetection.add(isDecodingError);
+
+ if (isTriggerDecoderError()) {
+ warning(OBD_PCM_Processor_Fault, "trigger decoding issue. expected %d/%d/%d got %d/%d/%d",
+ triggerShape->expectedEventCount[0], triggerShape->expectedEventCount[1],
+ triggerShape->expectedEventCount[2], eventCount[0], eventCount[1], eventCount[2]);
+ }
+
+ shaft_is_synchronized = true;
+ // this call would update duty cycle values
+ nextTriggerEvent(triggerWheel, nowUs);
+
+ nextRevolution(triggerShape->shaftPositionEventCount, nowUs);
+ } else {
+ nextTriggerEvent(triggerWheel, nowUs);
+ }
+
+ toothed_previous_duration = currentDuration;
+ toothed_previous_time = nowUs;
+}
+
+static void initializeSkippedToothTriggerShape(trigger_shape_s *s, int totalTeethCount, int skippedCount,
+ operation_mode_e operationMode) {
+ efiAssertVoid(s != NULL, "trigger_shape_s is NULL");
+ s->reset(operationMode);
+
+ float toothWidth = 0.5;
+
+ for (int i = 0; i < totalTeethCount - skippedCount - 1; i++) {
+ float angleDown = 720.0 / totalTeethCount * (i + toothWidth);
+ float angleUp = 720.0 / totalTeethCount * (i + 1);
+ s->addEvent(angleDown, T_PRIMARY, TV_HIGH);
+ s->addEvent(angleUp, T_PRIMARY, TV_LOW);
+ }
+
+ float angleDown = 720.0 / totalTeethCount * (totalTeethCount - skippedCount - 1 + toothWidth);
+ s->addEvent(angleDown, T_PRIMARY, TV_HIGH);
+ s->addEvent(720, T_PRIMARY, TV_LOW);
+}
+
+void initializeSkippedToothTriggerShapeExt(trigger_shape_s *s, int totalTeethCount, int skippedCount,
+ operation_mode_e operationMode) {
+ efiAssertVoid(totalTeethCount > 0, "totalTeethCount is zero");
+
+ s->totalToothCount = totalTeethCount;
+ s->skippedToothCount = skippedCount;
+ initializeSkippedToothTriggerShape(s, totalTeethCount, skippedCount, operationMode);
+
+ s->shaftPositionEventCount = ((totalTeethCount - skippedCount) * 2);
+ s->wave.checkSwitchTimes(s->getSize());
+}
+
+static void configureFordAspireTriggerShape(trigger_config_s *triggerConfig, trigger_shape_s * s) {
+ s->isSynchronizationNeeded = false;
+ s->reset(FOUR_STROKE_CAM_SENSOR);
+
+ s->shaftPositionEventCount = 10;
+
+ s->addEvent(53.747, T_SECONDARY, TV_HIGH);
+ s->addEvent(121.90, T_SECONDARY, TV_LOW); // delta = 68.153
+ s->addEvent(232.76, T_SECONDARY, TV_HIGH); // delta = 110.86
+ s->addEvent(300.54, T_SECONDARY, TV_LOW); // delta = 67.78
+ s->addEvent(360, T_PRIMARY, TV_HIGH);
+
+ s->addEvent(409.8412, T_SECONDARY, TV_HIGH); // delta = 49.8412
+ s->addEvent(478.6505, T_SECONDARY, TV_LOW); // delta = 68.8093
+ s->addEvent(588.045, T_SECONDARY, TV_HIGH); // delta = 109.3945
+ s->addEvent(657.03, T_SECONDARY, TV_LOW);
+ s->addEvent(720, T_PRIMARY, TV_LOW);
+}
+
+/**
+ * External logger is needed because at this point our logger is not yet initialized
+ */
+void initializeTriggerShape(Logging *logger, engine_configuration_s *engineConfiguration,
+ engine_configuration2_s *engineConfiguration2) {
+#if EFI_PROD_CODE
+ scheduleMsg(logger, "initializeTriggerShape()");
+#endif
+ trigger_config_s *triggerConfig = &engineConfiguration->triggerConfig;
+ trigger_shape_s *triggerShape = &engineConfiguration2->triggerShape;
+
+ setTriggerSynchronizationGap(triggerShape, 2);
+ triggerShape->useRiseEdge = TRUE;
+
+ switch (triggerConfig->triggerType) {
+
+ case TT_TOOTHED_WHEEL:
+ // todo: move to into configuration definition engineConfiguration2->triggerShape.needSecondTriggerInput = false;
+
+ engineConfiguration2->triggerShape.isSynchronizationNeeded =
+ engineConfiguration->triggerConfig.customIsSynchronizationNeeded;
+
+ initializeSkippedToothTriggerShapeExt(triggerShape, triggerConfig->customTotalToothCount,
+ triggerConfig->customSkippedToothCount, getOperationMode(engineConfiguration));
+ return;
+
+ case TT_MAZDA_MIATA_NA:
+ initializeMazdaMiataNaShape(triggerShape);
+ return;
+
+ case TT_MAZDA_MIATA_NB:
+ initializeMazdaMiataNbShape(triggerShape);
+ return;
+
+ case TT_DODGE_NEON:
+ configureNeonTriggerShape(triggerShape);
+ return;
+
+ case TT_FORD_ASPIRE:
+ configureFordAspireTriggerShape(triggerConfig, triggerShape);
+ return;
+
+ case TT_GM_7X:
+ configureGmTriggerShape(triggerConfig, triggerShape);
+ return;
+
+ case TT_FORD_ESCORT_GT:
+ configureMazdaProtegeLx(triggerShape);
+ return;
+
+ case TT_MINI_COOPER_R50:
+ configureMiniCooperTriggerShape(triggerShape);
+ return;
+
+ case TT_TOOTHED_WHEEL_60_2:
+ setToothedWheelConfiguration(triggerShape, 60, 2, engineConfiguration);
+ setTriggerSynchronizationGap(triggerShape, 2.5);
+ return;
+
+ case TT_TOOTHED_WHEEL_36_1:
+ setToothedWheelConfiguration(triggerShape, 36, 1, engineConfiguration);
+ return;
+
+ case TT_HONDA_ACCORD_CD_TWO_WIRES:
+ configureHondaAccordCD(triggerShape, false);
+ return;
+
+ case TT_HONDA_ACCORD_CD:
+ configureHondaAccordCD(triggerShape, true);
+ return;
+
+ case TT_HONDA_ACCORD_CD_DIP:
+ configureHondaAccordCDDip(triggerShape);
+ return;
+
+ case TT_MITSU:
+ initializeMitsubishi4g18(triggerShape);
+ return;
+
+ default:
+ firmwareError("initializeTriggerShape() not implemented: %d", triggerConfig->triggerType);
+ ;
+ }
+ if (engineConfiguration2->triggerShape.shaftPositionEventCount != engineConfiguration2->triggerShape.getSize())
+ firmwareError("trigger size or shaftPositionEventCount?");
+}
+
+TriggerStimulatorHelper::TriggerStimulatorHelper() {
+ primaryWheelState = false;
+ secondaryWheelState = false;
+ thirdWheelState = false;
+}
+
+void TriggerStimulatorHelper::nextStep(TriggerState *state, trigger_shape_s * shape, int i,
+ trigger_config_s const*triggerConfig) {
+ int stateIndex = i % shape->getSize();
+
+ int loopIndex = i / shape->getSize();
+
+ int time = (int) (HELPER_PERIOD * (loopIndex + shape->wave.getSwitchTime(stateIndex)));
+
+ bool newPrimaryWheelState = shape->wave.getChannelState(0, stateIndex);
+ bool newSecondaryWheelState = shape->wave.getChannelState(1, stateIndex);
+ bool new3rdWheelState = shape->wave.getChannelState(2, stateIndex);
+
+ if (primaryWheelState != newPrimaryWheelState) {
+ primaryWheelState = newPrimaryWheelState;
+ trigger_event_e s = primaryWheelState ? SHAFT_PRIMARY_UP : SHAFT_PRIMARY_DOWN;
+ state->decodeTriggerEvent(shape, triggerConfig, s, time);
+ }
+
+ if (secondaryWheelState != newSecondaryWheelState) {
+ secondaryWheelState = newSecondaryWheelState;
+ trigger_event_e s = secondaryWheelState ? SHAFT_SECONDARY_UP : SHAFT_SECONDARY_DOWN;
+ state->decodeTriggerEvent(shape, triggerConfig, s, time);
+ }
+
+ if (thirdWheelState != new3rdWheelState) {
+ thirdWheelState = new3rdWheelState;
+ trigger_event_e s = thirdWheelState ? SHAFT_3RD_UP : SHAFT_3RD_DOWN;
+ state->decodeTriggerEvent(shape, triggerConfig, s, time);
+ }
+}
+
+static void onFindIndex(TriggerState *state) {
+ for (int i = 0; i < PWM_PHASE_MAX_WAVE_PER_PWM; i++) {
+ // todo: that's not the best place for this intermediate data storage, fix it!
+ state->expectedTotalTime[i] = state->totalTime[i];
+ }
+}
+
+static uint32_t doFindTrigger(TriggerStimulatorHelper *helper, trigger_shape_s * shape,
+ trigger_config_s const*triggerConfig, TriggerState *state) {
+ for (int i = 0; i < 4 * PWM_PHASE_MAX_COUNT; i++) {
+ helper->nextStep(state, shape, i, triggerConfig);
+
+ if (state->shaft_is_synchronized)
+ return i;
+ }
+ firmwareError("findTriggerZeroEventIndex() failed");
+ return EFI_ERROR_CODE;
+}
+
+/**
+ * Trigger shape is defined in a way which is convenient for trigger shape definition
+ * On the other hand, trigger decoder indexing begins from synchronization event.
+ *
+ * This function finds the index of synchronization event within trigger_shape_s
+ */
+uint32_t findTriggerZeroEventIndex(trigger_shape_s * shape, trigger_config_s const*triggerConfig) {
+
+ TriggerState state;
+ errorDetection.clear();
+
+ TriggerStimulatorHelper helper;
+
+ uint32_t index = doFindTrigger(&helper, shape, triggerConfig, &state);
+ if (index == EFI_ERROR_CODE) {
+ return index;
+ }
+ efiAssert(state.getTotalRevolutionCounter() == 1, "totalRevolutionCounter", EFI_ERROR_CODE);
+
+ /**
+ * Now that we have just located the synch point, we can simulate the whole cycle
+ * in order to calculate expected duty cycle
+ */
+ state.cycleCallback = onFindIndex;
+ for (uint32_t i = index + 1; i <= index + 2 * shape->getSize(); i++) {
+ helper.nextStep(&state, shape, i, triggerConfig);
+ }
+ efiAssert(state.getTotalRevolutionCounter() > 1, "totalRevolutionCounter2", EFI_ERROR_CODE);
+
+ for (int i = 0; i < PWM_PHASE_MAX_WAVE_PER_PWM; i++) {
+ shape->dutyCycle[i] = 1.0 * state.expectedTotalTime[i] / HELPER_PERIOD;
+ }
+
+ return index % shape->getSize();
+}
+
+#if (EFI_PROD_CODE || EFI_SIMULATOR) || defined(__DOXYGEN__)
+//static Logging logger;
+#endif
+
+
+void initTriggerDecoder(void) {
+#if EFI_PROD_CODE || EFI_SIMULATOR
+// initLogging(&logger, "trigger decoder");
+#endif
+}
+
diff --git a/firmware/controllers/trigger/trigger_decoder.h b/firmware/controllers/trigger/trigger_decoder.h
new file mode 100644
index 0000000000..4cd137d5ae
--- /dev/null
+++ b/firmware/controllers/trigger/trigger_decoder.h
@@ -0,0 +1,96 @@
+/**
+ * @file trigger_decoder.h
+ *
+ * @date Dec 24, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef TRIGGER_DECODER_H_
+#define TRIGGER_DECODER_H_
+
+#include "trigger_structure.h"
+#include "engine_configuration.h"
+#include "ec2.h"
+
+class TriggerState;
+
+typedef void (*TriggerStateCallback)(TriggerState *);
+
+class TriggerState {
+public:
+ TriggerState();
+ int getCurrentIndex();
+ int getTotalRevolutionCounter();
+ uint64_t getTotalEventCounter();
+ uint64_t getStartOfRevolutionIndex();
+ void nextRevolution(int triggerEventCount, uint64_t nowUs);
+ void nextTriggerEvent(trigger_wheel_e triggerWheel, uint64_t nowUs);
+ void decodeTriggerEvent(trigger_shape_s const*triggerShape, trigger_config_s const*triggerConfig, trigger_event_e const signal, uint64_t nowUs);
+
+ float getTriggerDutyCycle(int index);
+ TriggerStateCallback cycleCallback;
+
+ /**
+ * TRUE if we know where we are
+ */
+ bool shaft_is_synchronized;
+
+ uint64_t toothed_previous_duration;
+ uint64_t toothed_previous_time;
+
+ /**
+ * Here we accumulate the amount of time this signal was ON within current trigger cycle
+ */
+ int totalTime[PWM_PHASE_MAX_WAVE_PER_PWM];
+ /**
+ * Total time result for previous trigger cycle
+ */
+ int prevTotalTime[PWM_PHASE_MAX_WAVE_PER_PWM];
+ int expectedTotalTime[PWM_PHASE_MAX_WAVE_PER_PWM];
+
+private:
+ void clear();
+ /**
+ * index within trigger revolution, from 0 to trigger event count
+ */
+ int current_index;
+ /**
+ * Number of actual events within current trigger cycle
+ * see trigger_shape_s
+ */
+ int eventCount[PWM_PHASE_MAX_WAVE_PER_PWM];
+ uint64_t timeOfPreviousEvent[PWM_PHASE_MAX_WAVE_PER_PWM];
+ uint64_t totalEventCountBase;
+ int totalRevolutionCounter;
+ bool isFirstEvent;
+ uint64_t prevCycleDuration;
+ uint64_t startOfCycle;
+};
+
+class TriggerStimulatorHelper {
+public:
+ TriggerStimulatorHelper();
+ void nextStep(TriggerState *state, trigger_shape_s * shape, int i, trigger_config_s const*triggerConfig);
+private:
+ bool primaryWheelState;
+ bool secondaryWheelState;
+ bool thirdWheelState;
+};
+
+void initializeSkippedToothTriggerShapeExt(trigger_shape_s *s, int totalTeethCount, int skippedCount, operation_mode_e operationMode);
+uint32_t findTriggerZeroEventIndex(trigger_shape_s * shape, trigger_config_s const*triggerConfig);
+void initializeTriggerShape(Logging *logger, engine_configuration_s *engineConfiguration, engine_configuration2_s *engineConfiguration2);
+void initTriggerDecoder(void);
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+int isTriggerDecoderError(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TRIGGER_DECODER_H_ */
diff --git a/firmware/controllers/trigger/trigger_emulator_algo.cpp b/firmware/controllers/trigger/trigger_emulator_algo.cpp
new file mode 100644
index 0000000000..a4250adff1
--- /dev/null
+++ b/firmware/controllers/trigger/trigger_emulator_algo.cpp
@@ -0,0 +1,73 @@
+/**
+ * @file trigger_emulator_algo.cpp
+ *
+ * @date Mar 3, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+#include "main.h"
+#include "trigger_emulator_algo.h"
+#include "engine_configuration.h"
+#include "LocalVersionHolder.h"
+#include "ec2.h"
+
+extern engine_configuration_s *engineConfiguration;
+extern engine_configuration2_s *engineConfiguration2;
+
+/*
+ * todo: should we simply re-use instances used by trigger_decoder?
+ * todo: since we are emulating same shape we are decoding
+ */
+static int pinStates1[PWM_PHASE_MAX_COUNT];
+static int pinStates2[PWM_PHASE_MAX_COUNT];
+static int pinStates3[PWM_PHASE_MAX_COUNT];
+static single_wave_s waves[PWM_PHASE_MAX_WAVE_PER_PWM] = {single_wave_s(pinStates1), single_wave_s(pinStates2), single_wave_s(pinStates3)};
+static single_wave_s sr[PWM_PHASE_MAX_WAVE_PER_PWM] = {waves[0], waves[1], waves[2]};
+
+static float swtchTms[PWM_PHASE_MAX_COUNT];
+
+PwmConfig triggerSignal(swtchTms, sr);
+
+static Logging logger;
+static LocalVersionHolder localVersion;
+
+void setTriggerEmulatorRPM(int rpm) {
+ /**
+ * All we need to do here is to change the periodMs
+ * togglePwmState() would see that the periodMs has changed and act accordingly
+ */
+ if (rpm == 0) {
+ triggerSignal.periodUs = NAN;
+ } else {
+ float gRpm = rpm * engineConfiguration->rpmMultiplier / 60.0; // per minute converted to per second
+ triggerSignal.periodUs = frequency2periodUs(gRpm);
+ }
+ scheduleMsg(&logger, "Emulating position sensor(s). RPM=%d", rpm);
+}
+
+static void updateTriggerShapeIfNeeded(PwmConfig *state) {
+ if (localVersion.isOld()) {
+ scheduleMsg(&logger, "Stimulator: updating trigger shape: %d/%d %d", localVersion.getVersion(), getGlobalConfigurationVersion(), currentTimeMillis());
+
+
+ applyNonPersistentConfiguration(&logger, engineConfiguration, engineConfiguration2);
+
+ trigger_shape_s *s = &engineConfiguration2->triggerShape;
+ int *pinStates[PWM_PHASE_MAX_WAVE_PER_PWM] = {s->wave.waves[0].pinStates, s->wave.waves[1].pinStates, s->wave.waves[2].pinStates};
+ copyPwmParameters(state, s->getSize(), s->wave.switchTimes, PWM_PHASE_MAX_WAVE_PER_PWM, pinStates);
+ state->safe.periodUs = -1; // this would cause loop re-initialization
+ }
+}
+
+void initTriggerEmulatorLogic(pwm_gen_callback *stateChangeCallback) {
+ initLogging(&logger, "position sensor(s) emulator");
+
+
+ trigger_shape_s *s = &engineConfiguration2->triggerShape;
+ setTriggerEmulatorRPM(DEFAULT_EMULATION_RPM);
+ int *pinStates[PWM_PHASE_MAX_WAVE_PER_PWM] = { s->wave.waves[0].pinStates, s->wave.waves[1].pinStates, s->wave.waves[2].pinStates};
+ weComplexInit("position sensor", &triggerSignal, s->getSize(), s->wave.switchTimes, PWM_PHASE_MAX_WAVE_PER_PWM, pinStates,
+ updateTriggerShapeIfNeeded, stateChangeCallback);
+
+ addConsoleActionI("rpm", &setTriggerEmulatorRPM);
+
+}
diff --git a/firmware/controllers/trigger/trigger_emulator_algo.h b/firmware/controllers/trigger/trigger_emulator_algo.h
new file mode 100644
index 0000000000..e5f884fbb3
--- /dev/null
+++ b/firmware/controllers/trigger/trigger_emulator_algo.h
@@ -0,0 +1,28 @@
+/**
+ * @file trigger_emulator_algo.h
+ *
+ * @date Mar 3, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef TRIGGER_EMULATOR_ALGO_H_
+#define TRIGGER_EMULATOR_ALGO_H_
+
+#include "pwm_generator_logic.h"
+
+/**
+ * this is RPM. 10000 rpm is only 166Hz, 800 rpm is 13Hz
+ */
+#define DEFAULT_EMULATION_RPM 1200
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void initTriggerEmulatorLogic(pwm_gen_callback *callback);
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* TRIGGER_EMULATOR_ALGO_H_ */
diff --git a/firmware/controllers/trigger/trigger_gm.cpp b/firmware/controllers/trigger/trigger_gm.cpp
new file mode 100644
index 0000000000..5eeae80b90
--- /dev/null
+++ b/firmware/controllers/trigger/trigger_gm.cpp
@@ -0,0 +1,40 @@
+/**
+ * @file trigger_gm.cpp
+ *
+ * @date Mar 28, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "trigger_gm.h"
+
+void configureGmTriggerShape(trigger_config_s *triggerConfig, trigger_shape_s *s) {
+ s->reset(FOUR_STROKE_CAM_SENSOR);
+
+ // all angles are x2 here - so, 5 degree width is 10
+ float w = 10;
+
+ s->shaftPositionEventCount = 14;
+
+ s->addEvent(120 - w, T_PRIMARY, TV_HIGH);
+ s->addEvent(120.0, T_PRIMARY, TV_LOW);
+
+ s->addEvent(240 - w, T_PRIMARY, TV_HIGH);
+ s->addEvent(240.0, T_PRIMARY, TV_LOW);
+
+ s->addEvent(360 - w, T_PRIMARY, TV_HIGH);
+ s->addEvent(360.0, T_PRIMARY, TV_LOW);
+
+ s->addEvent(480 - w, T_PRIMARY, TV_HIGH);
+ s->addEvent(480.0, T_PRIMARY, TV_LOW);
+
+ s->addEvent(600 - w, T_PRIMARY, TV_HIGH);
+ s->addEvent(600.0, T_PRIMARY, TV_LOW);
+
+ s->addEvent(700 - w, T_PRIMARY, TV_HIGH);
+ s->addEvent(700.0, T_PRIMARY, TV_LOW);
+
+ s->addEvent(720 - w, T_PRIMARY, TV_HIGH);
+ s->addEvent(720.0, T_PRIMARY, TV_LOW);
+
+}
+
diff --git a/firmware/controllers/trigger/trigger_gm.h b/firmware/controllers/trigger/trigger_gm.h
new file mode 100644
index 0000000000..6ac6477211
--- /dev/null
+++ b/firmware/controllers/trigger/trigger_gm.h
@@ -0,0 +1,15 @@
+/**
+ * @file trigger_gm.h
+ *
+ * @date Mar 28, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef TRIGGER_GM_H_
+#define TRIGGER_GM_H_
+
+#include "trigger_structure.h"
+
+void configureGmTriggerShape(trigger_config_s *triggerConfig, trigger_shape_s *s);
+
+#endif /* TRIGGER_GM_H_ */
diff --git a/firmware/controllers/trigger/trigger_mazda.cpp b/firmware/controllers/trigger/trigger_mazda.cpp
new file mode 100644
index 0000000000..5b06885ba6
--- /dev/null
+++ b/firmware/controllers/trigger/trigger_mazda.cpp
@@ -0,0 +1,136 @@
+/**
+ * @file trigger_mazda.cpp
+ *
+ * @date Feb 18, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ */
+
+#include "trigger_mazda.h"
+
+void initializeMazdaMiataNaShape(trigger_shape_s *s) {
+ s->reset(FOUR_STROKE_CAM_SENSOR);
+ setTriggerSynchronizationGap(s, 1.68f);
+ float z = 0.093;
+ s->useRiseEdge = false;
+
+ s->isSynchronizationNeeded = true;
+
+ s->addEvent(180.0f - 1.75 * z * 720, T_SECONDARY, TV_HIGH);
+ s->addEvent(180.0f - 0.75 * z * 720, T_SECONDARY, TV_LOW);
+
+
+ s->addEvent(360.0f - 2 * z * 720, T_PRIMARY, TV_HIGH);
+ s->addEvent(360.0f - 1.75 * z * 720, T_SECONDARY, TV_HIGH);
+ s->addEvent(360.0f - z * 720, T_PRIMARY, TV_LOW);
+ s->addEvent(360.0f - 0.75 * z * 720, T_SECONDARY, TV_LOW);
+
+ s->addEvent(540.0f - 1.75 * z * 720, T_SECONDARY, TV_HIGH);
+ s->addEvent(540.0f - 0.75 * z * 720, T_SECONDARY, TV_LOW);
+
+ s->addEvent(720.0f - 2 * z * 720, T_PRIMARY, TV_HIGH);
+ s->addEvent(720.0f - 1.75 * z * 720, T_SECONDARY, TV_HIGH);
+ s->addEvent(720.0f - 0.75 * z * 720, T_SECONDARY, TV_LOW);
+ s->addEvent(720.0f, T_PRIMARY, TV_LOW);
+
+ s->shaftPositionEventCount = s->getSize();
+
+}
+
+void initializeMazdaMiataNbShape(trigger_shape_s *s) {
+ setTriggerSynchronizationGap(s, 0.11f);
+ s->useRiseEdge = false;
+
+ s->reset(FOUR_STROKE_CAM_SENSOR);
+
+ /**
+ * cam sensor is primary, crank sensor is secondary
+ */
+ s->addEvent(20.0f, T_PRIMARY, TV_HIGH);
+
+ s->addEvent(66.0f, T_SECONDARY, TV_LOW);
+ s->addEvent(70.0f, T_SECONDARY, TV_HIGH);
+ s->addEvent(136.0f, T_SECONDARY, TV_LOW);
+ s->addEvent(140.0f, T_SECONDARY, TV_HIGH);
+ s->addEvent(246.0f, T_SECONDARY, TV_LOW);
+ s->addEvent(250.0f, T_SECONDARY, TV_HIGH);
+ s->addEvent(316.0f, T_SECONDARY, TV_LOW);
+ s->addEvent(320.0f, T_SECONDARY, TV_HIGH);
+
+ s->addEvent(340.0f, T_PRIMARY, TV_LOW);
+ s->addEvent(360.0f, T_PRIMARY, TV_HIGH);
+
+ s->addEvent(380.0f, T_PRIMARY, TV_LOW);
+ s->addEvent(400.0f, T_PRIMARY, TV_HIGH);
+
+ s->addEvent(426.0f, T_SECONDARY, TV_LOW);
+ s->addEvent(430.0f, T_SECONDARY, TV_HIGH);
+ s->addEvent(496.0f, T_SECONDARY, TV_LOW);
+ s->addEvent(500.0f, T_SECONDARY, TV_HIGH);
+ s->addEvent(606.0f, T_SECONDARY, TV_LOW);
+ s->addEvent(610.0f, T_SECONDARY, TV_HIGH);
+ s->addEvent(676.0f, T_SECONDARY, TV_LOW);
+ s->addEvent(680.0f, T_SECONDARY, TV_HIGH);
+
+ s->addEvent(720.0f, T_PRIMARY, TV_LOW);
+
+ s->shaftPositionEventCount = 6 + 16;
+}
+
+void configureMazdaProtegeLx(trigger_shape_s *s) {
+
+ // todo: move to into configuration definition s->needSecondTriggerInput = FALSE;
+
+
+ s->reset(FOUR_STROKE_CAM_SENSOR);
+// s->initialState[0] = 1;
+
+// float w = 720 / 4 * 0.215;
+ float a = 5;
+// s->addEvent(a, T_SECONDARY, TV_LOW);
+// s->addEvent(a + w, T_SECONDARY, TV_HIGH);
+// a += 180;
+// s->addEvent(a, T_SECONDARY, TV_LOW);
+// s->addEvent(a + w, T_SECONDARY, TV_HIGH);
+// a += 180;
+// s->addEvent(a, T_SECONDARY, TV_LOW);
+// s->addEvent(a + w, T_SECONDARY, TV_HIGH);
+// a += 180;
+// s->addEvent(a, T_SECONDARY, TV_LOW);
+// s->addEvent(a + w, T_SECONDARY, TV_HIGH);
+
+ float z = 0.093;
+
+ a = 180;
+ s->addEvent(a - z * 720, T_PRIMARY, TV_HIGH);
+ s->addEvent(a, T_PRIMARY, TV_LOW);
+
+ a += 180;
+ s->addEvent(a - z * 720, T_PRIMARY, TV_HIGH);
+ s->addEvent(a, T_PRIMARY, TV_LOW);
+
+ a += 180;
+ s->addEvent(a - z * 720, T_PRIMARY, TV_HIGH);
+ s->addEvent(a, T_PRIMARY, TV_LOW);
+
+ a += 180;
+ s->addEvent(a - z * 720, T_PRIMARY, TV_HIGH);
+ s->addEvent(a, T_PRIMARY, TV_LOW);
+
+
+// s->shaftPositionEventCount = 2 + 8;
+ s->shaftPositionEventCount = 8;
+ s->isSynchronizationNeeded = false;
+}
diff --git a/firmware/controllers/trigger/trigger_mazda.h b/firmware/controllers/trigger/trigger_mazda.h
new file mode 100644
index 0000000000..9d023cd58d
--- /dev/null
+++ b/firmware/controllers/trigger/trigger_mazda.h
@@ -0,0 +1,19 @@
+/**
+ * @file trigger_mazda.h
+ *
+ * @date Feb 18, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef TRIGGER_MAZDA_H_
+#define TRIGGER_MAZDA_H_
+
+#include "trigger_structure.h"
+#include "engine_configuration.h"
+#include "ec2.h"
+
+void initializeMazdaMiataNaShape(trigger_shape_s *s);
+void initializeMazdaMiataNbShape(trigger_shape_s *s);
+void configureMazdaProtegeLx(trigger_shape_s *s);
+
+#endif /* TRIGGER_MAZDA_H_ */
diff --git a/firmware/controllers/trigger/trigger_mitsubishi.cpp b/firmware/controllers/trigger/trigger_mitsubishi.cpp
new file mode 100644
index 0000000000..5ffcfa3aef
--- /dev/null
+++ b/firmware/controllers/trigger/trigger_mitsubishi.cpp
@@ -0,0 +1,35 @@
+/**
+ * @file trigger_mitsubishi.cpp
+ *
+ * @date Aug 5, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "trigger_mitsubishi.h"
+
+void initializeMitsubishi4g18(trigger_shape_s *s) {
+ s->reset(FOUR_STROKE_CAM_SENSOR);
+ s->useRiseEdge = false;
+
+ setTriggerSynchronizationGap(s, 1.6666);
+
+ int secondaryWidth = 70;
+
+ s->addEvent(180.0 - 131 + 5, T_PRIMARY, TV_HIGH);
+ s->addEvent(180.0 - secondaryWidth, T_SECONDARY, TV_HIGH);
+ s->addEvent(180.0, T_SECONDARY, TV_LOW);
+ s->addEvent(180.0 + 49 + 5, T_PRIMARY, TV_LOW);
+
+ s->addEvent(360.0 - secondaryWidth, T_SECONDARY, TV_HIGH);
+ s->addEvent(360.0, T_SECONDARY, TV_LOW);
+
+ s->addEvent(540.0 - 131 + 5, T_PRIMARY, TV_HIGH);
+ s->addEvent(540.0 - secondaryWidth, T_SECONDARY, TV_HIGH);
+ s->addEvent(540.0 - 131 + 5 + 90, T_PRIMARY, TV_LOW);
+ s->addEvent(540.0, T_SECONDARY, TV_LOW);
+
+ s->addEvent(720.0 - secondaryWidth, T_SECONDARY, TV_HIGH);
+ s->addEvent(720.0, T_SECONDARY, TV_LOW);
+
+ s->shaftPositionEventCount = s->getSize();
+}
diff --git a/firmware/controllers/trigger/trigger_mitsubishi.h b/firmware/controllers/trigger/trigger_mitsubishi.h
new file mode 100644
index 0000000000..3d72956fbe
--- /dev/null
+++ b/firmware/controllers/trigger/trigger_mitsubishi.h
@@ -0,0 +1,14 @@
+/**
+ * @file trigger_mitsubishi.h
+ *
+ * @date Aug 5, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+#ifndef TRIGGER_MITSUBISHI_H_
+#define TRIGGER_MITSUBISHI_H_
+
+#include "trigger_structure.h"
+
+void initializeMitsubishi4g18(trigger_shape_s *s);
+
+#endif /* TRIGGER_MITSUBISHI_H_ */
diff --git a/firmware/controllers/trigger/trigger_structure.cpp b/firmware/controllers/trigger/trigger_structure.cpp
new file mode 100644
index 0000000000..0441272ca8
--- /dev/null
+++ b/firmware/controllers/trigger/trigger_structure.cpp
@@ -0,0 +1,424 @@
+/**
+ * @file trigger_structure.cpp
+ *
+ * @date Jan 20, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ */
+
+#include "main.h"
+#include "trigger_structure.h"
+#include "error_handling.h"
+#include "trigger_decoder.h"
+
+trigger_shape_helper::trigger_shape_helper() {
+ for (int i = 0; i < TRIGGER_CHANNEL_COUNT; i++) {
+ waves[i].init(pinStates[i]);
+ }
+}
+
+trigger_shape_s::trigger_shape_s() :
+ wave(switchTimesBuffer, NULL) {
+ reset(OM_NONE);
+ wave.waves = h.waves;
+}
+
+int trigger_shape_s::getSize() const {
+ return size;
+}
+
+int trigger_shape_s::getTriggerShapeSynchPointIndex() {
+ return triggerShapeSynchPointIndex;
+}
+
+// todo: clean-up!
+int getEngineCycleEventCount2(operation_mode_e mode, trigger_shape_s * s);
+float fixAngle(float angle);
+
+void trigger_shape_s::calculateTriggerSynchPoint(trigger_config_s const*triggerConfig) {
+ setTriggerShapeSynchPointIndex(findTriggerZeroEventIndex(this, triggerConfig));
+}
+
+void trigger_shape_s::setTriggerShapeSynchPointIndex(int triggerShapeSynchPointIndex) {
+ this->triggerShapeSynchPointIndex = triggerShapeSynchPointIndex;
+
+ int engineCycleEventCount = getEngineCycleEventCount2(operationMode, this);
+
+ float firstAngle = getAngle(triggerShapeSynchPointIndex);
+
+ for (int i = 0; i < engineCycleEventCount; i++) {
+ if (i == 0) {
+ // explicit check for zero to avoid issues where logical zero is not exactly zero due to float nature
+ eventAngles[i] = 0;
+ } else {
+ eventAngles[i] = fixAngle(getAngle((triggerShapeSynchPointIndex + i) % engineCycleEventCount) - firstAngle);
+ }
+ }
+}
+
+void trigger_shape_s::reset(operation_mode_e operationMode) {
+ this->operationMode = operationMode;
+ size = 0;
+ shaftPositionEventCount = 0;
+ triggerShapeSynchPointIndex = 0;
+ memset(initialState, 0, sizeof(initialState));
+ memset(switchTimesBuffer, 0, sizeof(switchTimesBuffer));
+ memset(expectedEventCount, 0, sizeof(expectedEventCount));
+ wave.reset();
+ previousAngle = 0;
+}
+
+int multi_wave_s::getChannelState(int channelIndex, int phaseIndex) const {
+ return waves[channelIndex].pinStates[phaseIndex];
+}
+
+int multi_wave_s::waveIndertionAngle(float angle, int size) const {
+ for (int i = size - 1; i >= 0; i--) {
+ if (angle > switchTimes[i])
+ return i + 1;
+ }
+ return 0;
+}
+
+int multi_wave_s::findAngleMatch(float angle, int size) const {
+ for (int i = 0; i < size; i++) {
+ if (isSameF(switchTimes[i], angle))
+ return i;
+ }
+ return EFI_ERROR_CODE;
+}
+
+void multi_wave_s::setSwitchTime(int index, float value) {
+ switchTimes[index] = value;
+}
+
+TriggerState::TriggerState() {
+ cycleCallback = NULL;
+ shaft_is_synchronized = false;
+ toothed_previous_time = 0;
+ toothed_previous_duration = 0;
+ totalRevolutionCounter = 0;
+ clear();
+ memset(expectedTotalTime, 0, sizeof(expectedTotalTime));
+ totalEventCountBase = 0;
+ isFirstEvent = true;
+}
+
+int TriggerState::getCurrentIndex() {
+ return current_index;
+}
+
+uint64_t TriggerState::getStartOfRevolutionIndex() {
+ return totalEventCountBase;
+}
+
+uint64_t TriggerState::getTotalEventCounter() {
+ return totalEventCountBase + current_index;
+}
+
+void TriggerState::nextRevolution(int triggerEventCount, uint64_t nowUs) {
+ if (cycleCallback != NULL) {
+ cycleCallback(this);
+ }
+ memcpy(prevTotalTime, totalTime, sizeof(prevTotalTime));
+ prevCycleDuration = nowUs - startOfCycle;
+ startOfCycle = nowUs;
+ clear();
+ totalRevolutionCounter++;
+ totalEventCountBase += triggerEventCount;
+}
+
+int TriggerState::getTotalRevolutionCounter() {
+ return totalRevolutionCounter;
+}
+
+void TriggerState::nextTriggerEvent(trigger_wheel_e triggerWheel, uint64_t nowUs) {
+ uint64_t prevTime = timeOfPreviousEvent[triggerWheel];
+ if (prevTime != 0) {
+ // even event - apply the value
+ totalTime[triggerWheel] += (nowUs - prevTime);
+ timeOfPreviousEvent[triggerWheel] = 0;
+ } else {
+ // odd event - start accumulation
+ timeOfPreviousEvent[triggerWheel] = nowUs;
+ }
+
+ current_index++;
+}
+
+void TriggerState::clear() {
+ memset(eventCount, 0, sizeof(eventCount));
+ memset(timeOfPreviousEvent, 0, sizeof(timeOfPreviousEvent));
+ memset(totalTime, 0, sizeof(totalTime));
+ current_index = 0;
+}
+
+uint32_t trigger_shape_s::getLength() const {
+ return operationMode == FOUR_STROKE_CAM_SENSOR ? shaftPositionEventCount : 2 * shaftPositionEventCount;
+}
+
+float trigger_shape_s::getAngle(int index) const {
+ if (operationMode == FOUR_STROKE_CAM_SENSOR) {
+ return getSwitchAngle(index);
+ }
+ /**
+ * FOUR_STROKE_CRANK_SENSOR magic:
+ * We have two crank shaft revolutions for each engine cycle
+ * See also trigger_central.cpp
+ * See also getEngineCycleEventCount()
+ */
+ int triggerEventCounter = size;
+
+ if (index < triggerEventCounter) {
+ return getSwitchAngle(index);
+ } else {
+ return 360 + getSwitchAngle(index - triggerEventCounter);
+ }
+}
+
+void trigger_shape_s::addEvent(float angle, trigger_wheel_e const waveIndex, trigger_value_e const state) {
+ efiAssertVoid(operationMode != OM_NONE, "operationMode not set");
+ /**
+ * While '720' value works perfectly it has not much sense for crank sensor-only scenario.
+ * todo: accept angle as a value in the 0..1 range?
+ */
+ angle /= 720;
+
+ expectedEventCount[waveIndex]++;
+
+ efiAssertVoid(angle > 0, "angle should be positive");
+ if (size > 0) {
+ if (angle <= previousAngle) {
+ firmwareError("invalid angle order: %f and %f", angle, previousAngle);
+ return;
+ }
+ }
+ previousAngle = angle;
+ if (size == 0) {
+ size = 1;
+ for (int i = 0; i < PWM_PHASE_MAX_WAVE_PER_PWM; i++) {
+ single_wave_s *wave = &this->wave.waves[i];
+
+ if (wave == NULL) {
+ firmwareError("wave is NULL");
+ return;
+ }
+ if (wave->pinStates == NULL) {
+ firmwareError("wave pinStates is NULL");
+ return;
+ }
+ wave->pinStates[0] = initialState[i];
+ }
+
+ wave.setSwitchTime(0, angle);
+ wave.waves[waveIndex].pinStates[0] = state;
+ return;
+ }
+
+ int exactMatch = wave.findAngleMatch(angle, size);
+ if (exactMatch != EFI_ERROR_CODE) {
+ firmwareError("same angle: not supported");
+ return;
+ }
+
+ int index = wave.waveIndertionAngle(angle, size);
+
+ // shifting existing data
+ for (int i = size - 1; i >= index; i--) {
+ for (int j = 0; j < PWM_PHASE_MAX_WAVE_PER_PWM; j++) {
+ wave.waves[j].pinStates[i + 1] = wave.getChannelState(j, index);
+ }
+ wave.setSwitchTime(i + 1, wave.getSwitchTime(i));
+ }
+
+// int index = size;
+ size++;
+
+ for (int i = 0; i < PWM_PHASE_MAX_WAVE_PER_PWM; i++) {
+ wave.waves[i].pinStates[index] = wave.getChannelState(i, index - 1);
+ }
+ wave.setSwitchTime(index, angle);
+ wave.waves[waveIndex].pinStates[index] = state;
+}
+
+int trigger_shape_s::getCycleDuration() const {
+ return (operationMode == FOUR_STROKE_CAM_SENSOR) ? 720 : 360;
+}
+
+float trigger_shape_s::getSwitchAngle(int index) const {
+ return getCycleDuration() * wave.getSwitchTime(index);
+}
+
+void multi_wave_s::checkSwitchTimes(int size) {
+ checkSwitchTimes2(size, switchTimes);
+}
+
+void setToothedWheelConfiguration(trigger_shape_s *s, int total, int skipped,
+ engine_configuration_s const *engineConfiguration) {
+ s->isSynchronizationNeeded = (skipped != 0);
+
+ s->totalToothCount = total;
+ s->skippedToothCount = skipped;
+ // todo: move to into configuration definition s->needSecondTriggerInput = false;
+ s->useRiseEdge = true;
+
+ initializeSkippedToothTriggerShapeExt(s, s->totalToothCount, s->skippedToothCount,
+ getOperationMode(engineConfiguration));
+}
+
+void setTriggerSynchronizationGap(trigger_shape_s *s, float synchGap) {
+ s->isSynchronizationNeeded = true;
+ s->syncRatioFrom = synchGap * 0.75;
+ s->syncRatioTo = synchGap * 1.25;
+}
+
+#define S24 (720.0f / 24 / 2)
+
+static float addAccordPair(trigger_shape_s *s, float sb) {
+ s->addEvent(sb, T_SECONDARY, TV_HIGH);
+ sb += S24;
+ s->addEvent(sb, T_SECONDARY, TV_LOW);
+ sb += S24;
+
+ return sb;
+}
+
+#define DIP 7.5f
+static float addAccordPair3(trigger_shape_s *s, float sb) {
+ sb += DIP;
+ s->addEvent(sb, T_CHANNEL_3, TV_HIGH);
+ sb += DIP;
+ s->addEvent(sb, T_CHANNEL_3, TV_LOW);
+ sb += 2 * DIP;
+ return sb;
+}
+
+/**
+ * Thank you Dip!
+ * http://forum.pgmfi.org/viewtopic.php?f=2&t=15570start=210#p139007
+ */
+void configureHondaAccordCDDip(trigger_shape_s *s) {
+ s->reset(FOUR_STROKE_CAM_SENSOR);
+
+ s->initialState[T_SECONDARY] = TV_HIGH;
+ float sb = 0;
+ sb = addAccordPair3(s, sb);
+ sb = addAccordPair3(s, sb);
+ sb = addAccordPair3(s, sb);
+
+ s->addEvent(90, T_SECONDARY, TV_LOW);
+ sb = 90;
+ sb = addAccordPair3(s, sb);
+ sb = addAccordPair3(s, sb);
+ sb = addAccordPair3(s, sb);
+
+ s->addEvent(180, T_SECONDARY, TV_HIGH);
+ sb = 180;
+ sb = addAccordPair3(s, sb);
+ sb = addAccordPair3(s, sb);
+ sb = addAccordPair3(s, sb);
+
+ s->addEvent(270, T_SECONDARY, TV_LOW);
+ sb = 270;
+ sb = addAccordPair3(s, sb);
+ sb = addAccordPair3(s, sb);
+ sb = addAccordPair3(s, sb);
+
+
+ s->addEvent(360.0f - DIP, T_PRIMARY, TV_HIGH);
+ s->addEvent(360, T_SECONDARY, TV_HIGH);
+ sb = 360;
+ sb = addAccordPair3(s, sb);
+ sb = addAccordPair3(s, sb);
+ sb = addAccordPair3(s, sb);
+
+ s->addEvent(450, T_SECONDARY, TV_LOW);
+ sb = 450;
+ sb = addAccordPair3(s, sb);
+ sb = addAccordPair3(s, sb);
+ sb = addAccordPair3(s, sb);
+
+ s->addEvent(540, T_SECONDARY, TV_HIGH);
+ sb = 540;
+ sb = addAccordPair3(s, sb);
+ sb = addAccordPair3(s, sb);
+ sb = addAccordPair3(s, sb);
+
+ s->addEvent(630, T_SECONDARY, TV_LOW);
+ sb = 630;
+ sb = addAccordPair3(s, sb);
+ sb = addAccordPair3(s, sb);
+ sb = addAccordPair3(s, sb);
+
+ s->addEvent(720.0f - DIP, T_PRIMARY, TV_LOW);
+
+// s->addEvent(720.0f - 12 * sb, T_SECONDARY, TV_LOW);
+// s->addEvent(720.0f, T_SECONDARY, TV_LOW);
+
+ s->addEvent(720.0f, T_SECONDARY, TV_HIGH);
+
+ s->isSynchronizationNeeded = false;
+
+ s->shaftPositionEventCount = s->getSize();
+}
+
+void configureHondaAccordCD(trigger_shape_s *s, bool with3rdSignal) {
+ s->reset(FOUR_STROKE_CAM_SENSOR);
+
+ float sb = 5.0f;
+
+ float tdcWidth = 0.1854 * 720 / 4;
+
+ s->isSynchronizationNeeded = false;
+
+ sb = addAccordPair(s, sb);
+
+ if (with3rdSignal)
+ s->addEvent(sb - S24 / 2, T_CHANNEL_3, TV_HIGH);
+
+ sb = addAccordPair(s, sb);
+ sb = addAccordPair(s, sb);
+ if (with3rdSignal)
+ s->addEvent(sb - S24 / 2, T_CHANNEL_3, TV_LOW);
+ sb = addAccordPair(s, sb);
+ sb = addAccordPair(s, sb);
+ s->addEvent(1 * 180.0f - tdcWidth, T_PRIMARY, TV_HIGH);
+ sb = addAccordPair(s, sb);
+ s->addEvent(1 * 180.0f, T_PRIMARY, TV_LOW);
+
+ sb = addAccordPair(s, sb);
+ sb = addAccordPair(s, sb);
+ sb = addAccordPair(s, sb);
+ sb = addAccordPair(s, sb);
+ sb = addAccordPair(s, sb);
+
+ s->addEvent(2 * 180.0f - tdcWidth, T_PRIMARY, TV_HIGH);
+ sb = addAccordPair(s, sb);
+ s->addEvent(2 * 180.0f, T_PRIMARY, TV_LOW);
+
+ for (int i = 3; i <= 4; i++) {
+ sb = addAccordPair(s, sb);
+ sb = addAccordPair(s, sb);
+ sb = addAccordPair(s, sb);
+ sb = addAccordPair(s, sb);
+ sb = addAccordPair(s, sb);
+
+ s->addEvent(i * 180.0f - tdcWidth, T_PRIMARY, TV_HIGH);
+ sb = addAccordPair(s, sb);
+ s->addEvent(i * 180.0f, T_PRIMARY, TV_LOW);
+ }
+
+ s->shaftPositionEventCount = s->getSize();
+}
diff --git a/firmware/controllers/trigger/trigger_structure.h b/firmware/controllers/trigger/trigger_structure.h
new file mode 100644
index 0000000000..e4f38c6f7b
--- /dev/null
+++ b/firmware/controllers/trigger/trigger_structure.h
@@ -0,0 +1,116 @@
+/**
+ * @file trigger_structure.h
+ *
+ * @date Dec 22, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef TRIGGER_STRUCTURE_H_
+#define TRIGGER_STRUCTURE_H_
+
+#include
+
+#include "rusefi_enums.h"
+#include "EfiWave.h"
+#include "engine_configuration.h"
+
+class trigger_shape_s;
+
+#define TRIGGER_CHANNEL_COUNT 3
+
+class trigger_shape_helper {
+ int pinStates[TRIGGER_CHANNEL_COUNT][PWM_PHASE_MAX_COUNT];
+public:
+ trigger_shape_helper();
+
+ single_wave_s waves[TRIGGER_CHANNEL_COUNT];
+};
+
+class trigger_shape_s {
+public:
+ trigger_shape_s();
+ int isSynchronizationNeeded;
+
+ int totalToothCount;
+ int skippedToothCount;
+
+ float dutyCycle[PWM_PHASE_MAX_WAVE_PER_PWM];
+
+ float syncRatioFrom;
+ float syncRatioTo;
+
+ int useRiseEdge;
+
+ /**
+ * This is used for signal validation
+ */
+ int expectedEventCount[PWM_PHASE_MAX_WAVE_PER_PWM];
+
+ void addEvent(float angle, trigger_wheel_e const waveIndex, trigger_value_e const state);
+ void reset(operation_mode_e operationMode);
+ int getSize() const;
+ multi_wave_s wave;
+
+ /**
+ * Total count of shaft events per CAM or CRANK shaft revolution.
+ * TODO this should be migrated to CRANKshaft revolution, this would go together
+ * TODO with eliminating RPM_MULT magic constant
+ */
+ int shaftPositionEventCount;
+
+ /**
+ * this one is per CRANKshaft revolution
+ */
+ uint32_t getLength() const;
+
+ // todo: add a runtime validation which would verify that this field was set properly
+ // tood: maybe even automate this flag calculation?
+ int initialState[PWM_PHASE_MAX_WAVE_PER_PWM];
+
+ int getTriggerShapeSynchPointIndex();
+
+ void calculateTriggerSynchPoint(trigger_config_s const*triggerConfig);
+
+ void setTriggerShapeSynchPointIndex(int triggerShapeSynchPointIndex);
+ /**
+ * These angles are in event coordinates - with synchronization point located at angle zero.
+ * These values are pre-calculated for performance reasons.
+ */
+ float eventAngles[PWM_PHASE_MAX_COUNT];
+
+private:
+ trigger_shape_helper h;
+ int size;
+ /**
+ * index of synchronization event within trigger_shape_s
+ * See findTriggerZeroEventIndex()
+ */
+ int triggerShapeSynchPointIndex;
+ /**
+ * Values are in the 0..1 range
+ */
+ float switchTimesBuffer[PWM_PHASE_MAX_COUNT];
+ /**
+ * These angles are in trigger DESCRIPTION coordinates - i.e. the way you add events while declaring trigger shape
+ */
+ float getSwitchAngle(int index) const;
+
+ float previousAngle;
+ /**
+ * this is part of performance optimization
+ */
+ operation_mode_e operationMode;
+
+ /**
+ * This private method should only be used to prepare the array of pre-calculated values
+ * See eventAngles array
+ */
+ float getAngle(int phaseIndex) const;
+
+ int getCycleDuration() const;
+};
+
+void setTriggerSynchronizationGap(trigger_shape_s *s, float synchGap);
+void setToothedWheelConfiguration(trigger_shape_s *s, int total, int skipped, engine_configuration_s const *engineConfiguration);
+
+#endif /* TRIGGER_STRUCTURE_H_ */
diff --git a/firmware/dump.bat b/firmware/dump.bat
new file mode 100644
index 0000000000..af791acb5a
--- /dev/null
+++ b/firmware/dump.bat
@@ -0,0 +1 @@
+arm-none-eabi-objdump -S build/rusefi.elf > build.dump
\ No newline at end of file
diff --git a/firmware/dump_debug.bat b/firmware/dump_debug.bat
new file mode 100644
index 0000000000..d19e8cd431
--- /dev/null
+++ b/firmware/dump_debug.bat
@@ -0,0 +1 @@
+arm-none-eabi-objdump -S debug/rusefi.elf > debug.dump
\ No newline at end of file
diff --git a/firmware/dump_release.bat b/firmware/dump_release.bat
new file mode 100644
index 0000000000..ab2ea8ffc7
--- /dev/null
+++ b/firmware/dump_release.bat
@@ -0,0 +1 @@
+arm-none-eabi-objdump -S release/rusefi.elf > release.dump
\ No newline at end of file
diff --git a/firmware/emulation/analog_chart.c b/firmware/emulation/analog_chart.c
new file mode 100644
index 0000000000..82262f3e02
--- /dev/null
+++ b/firmware/emulation/analog_chart.c
@@ -0,0 +1,63 @@
+/**
+ * @file analog_chart.c
+ *
+ * @date Dec 20, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+#include "rpm_calculator.h"
+#include "analog_chart.h"
+#include "status_loop.h"
+#include "engine_configuration.h"
+
+#if EFI_ANALOG_CHART || defined(__DOXYGEN__)
+
+static char LOGGING_BUFFER[5000];
+static Logging logging;
+
+static int pendingData = FALSE;
+static int initialized = FALSE;
+
+extern engine_configuration_s *engineConfiguration;
+
+void acAddData(float angle, float value) {
+ if (!initialized) {
+ return; // this is possible because of initialization sequence
+ }
+
+ if( engineConfiguration->analogChartFrequency < 1) {
+ //todofirmwareError()
+ return;
+ }
+
+ if (getRevolutionCounter() % engineConfiguration->analogChartFrequency != 0) {
+ if (pendingData) {
+ // message terminator
+ appendPrintf(&logging, DELIMETER);
+ // output pending data
+ if (getFullLog()) {
+ scheduleLogging(&logging);
+ }
+ pendingData = FALSE;
+ }
+ return;
+ }
+ if (!pendingData) {
+ pendingData = TRUE;
+ resetLogging(&logging);
+ // message header
+ appendPrintf(&logging, "analog_chart%s", DELIMETER);
+ }
+
+ if (remainingSize(&logging) > 100) {
+ appendPrintf(&logging, "%f|%f|", angle, value);
+ }
+}
+
+void initAnalogChart(void) {
+ initLoggingExt(&logging, "analog chart", LOGGING_BUFFER, sizeof(LOGGING_BUFFER));
+ initialized = TRUE;
+}
+
+#endif /* EFI_ANALOG_CHART */
diff --git a/firmware/emulation/analog_chart.h b/firmware/emulation/analog_chart.h
new file mode 100644
index 0000000000..e7cc4a6513
--- /dev/null
+++ b/firmware/emulation/analog_chart.h
@@ -0,0 +1,23 @@
+/**
+ * @file analog_chart.h
+ *
+ * @date Dec 20, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef ANALOG_CHART_H_
+#define ANALOG_CHART_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void acAddData(float angle, float value);
+void initAnalogChart(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* ANALOG_CHART_H_ */
diff --git a/firmware/emulation/emulation.mk b/firmware/emulation/emulation.mk
new file mode 100644
index 0000000000..187a828040
--- /dev/null
+++ b/firmware/emulation/emulation.mk
@@ -0,0 +1,11 @@
+
+EMULATIONSRC = emulation/hw_layer/poten.c \
+ emulation/analog_chart.c \
+ emulation/test/test.c \
+ emulation/test/testbmk.c
+
+EMULATIONSRC_CPP = emulation/trigger_emulator.cpp \
+ emulation/rfi_perftest.cpp \
+ emulation/engine_emulator.cpp \
+ emulation/wave_analyzer.cpp
+
diff --git a/firmware/emulation/engine_emulator.cpp b/firmware/emulation/engine_emulator.cpp
new file mode 100644
index 0000000000..eaac2d8e0b
--- /dev/null
+++ b/firmware/emulation/engine_emulator.cpp
@@ -0,0 +1,105 @@
+/**
+ * @file engine_emulator.cpp
+ * @brief Entry point for all the emulation and analysis code
+ *
+ * @date Mar 15, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+#include "engine_emulator.h"
+
+extern "C" {
+
+#include "status_loop.h"
+#include "advance_map.h"
+#include "wave_analyzer.h"
+#include "fuel_math.h"
+#include "pin_repository.h"
+#include "poten.h"
+}
+#include "trigger_emulator.h"
+
+
+static THD_WORKING_AREA(eeThreadStack, UTILITY_THREAD_STACK_SIZE);
+
+#define DIAG_PORT GPIOD
+#define DIAG_PIN 0
+
+void setDiag(int value) {
+ print("Setting diag: %d\r\n", value);
+ palWritePad(DIAG_PORT, DIAG_PIN, value);
+}
+
+#define PERIOD 3000
+
+void emulate(void) {
+ print("Emulating...\r\n");
+ setDiag(1);
+ chThdSleep(1);
+ setFullLog(1);
+
+ for (int i = 400; i <= 1300; i++) {
+ if (i % 50 != 0)
+ continue;
+ setTriggerEmulatorRPM(i);
+ chThdSleepMilliseconds(PERIOD);
+ }
+
+ setTriggerEmulatorRPM(0);
+
+ setFullLog(0);
+ setDiag(0);
+ chThdSleep(1);
+ print("Emulation DONE!\r\n");
+}
+
+static int flag = FALSE;
+
+static msg_t eeThread(void *arg) {
+ chRegSetThreadName("Engine");
+
+ while (TRUE) {
+ while (!flag)
+ chThdSleepMilliseconds(200);
+ flag = FALSE;
+ emulate();
+ }
+#if defined __GNUC__
+ return (msg_t)NULL;
+#endif
+}
+
+void startEmulator(void) {
+ flag = TRUE;
+}
+
+static void printAdvance(int rpm, int maf100) {
+ float advance = getAdvance(rpm, maf100 / 100.0);
+ print("advance for %d rpm %d maf100: %f\r\n", rpm, maf100, advance);
+}
+
+static void initECUstimulator(void) {
+ mySetPadMode("TEN", DIAG_PORT, DIAG_PIN,
+ PAL_MODE_OUTPUT_PUSHPULL);
+
+ addConsoleActionI("diag", setDiag);
+ addConsoleAction("emu", startEmulator);
+ addConsoleActionII("ad", printAdvance);
+
+ setDiag(1);
+
+ chThdCreateStatic(eeThreadStack, sizeof(eeThreadStack), NORMALPRIO, (tfunc_t) eeThread, NULL);
+}
+
+void initEngineEmulator(board_configuration_s *boardConfiguration) {
+ if (hasFirmwareError())
+ return;
+
+#if EFI_POTENTIOMETER
+ initPotentiometers(boardConfiguration);
+#endif /* EFI_POTENTIOMETER */
+
+ //initECUstimulator();
+ initTriggerEmulator();
+}
diff --git a/firmware/emulation/engine_emulator.h b/firmware/emulation/engine_emulator.h
new file mode 100644
index 0000000000..b26cb557ec
--- /dev/null
+++ b/firmware/emulation/engine_emulator.h
@@ -0,0 +1,14 @@
+/*
+ * engine_emulator.h
+ *
+ * @date Mar 15, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef ENGINE_EMULATOR_H_
+#define ENGINE_EMULATOR_H_
+
+#include "engine_configuration.h"
+void initEngineEmulator(board_configuration_s *boardConfiguration);
+
+#endif /* ENGINE_EMULATOR_H_ */
diff --git a/firmware/emulation/hw_layer/poten.c b/firmware/emulation/hw_layer/poten.c
new file mode 100644
index 0000000000..864203e448
--- /dev/null
+++ b/firmware/emulation/hw_layer/poten.c
@@ -0,0 +1,146 @@
+/**
+ * @file poten.c
+ * @brief MCP42010 digital potentiometer driver
+ *
+ * @date Mar 16, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+#include "poten.h"
+#include "eficonsole.h"
+#include "pin_repository.h"
+#include "engine_configuration.h"
+#include "hardware.h"
+
+/**
+ * MCP42010 digital potentiometer driver
+ *
+ *
+ * 1 CS pin select PB12 PA10
+ * 2 SCK serial clock PA5 PC10
+ * 3 SI serial input (MOSI) PA7 PC12
+ * 4 Vss ground
+ * 5 PB1
+ * 6 PW1
+ * 7 PA1
+ * 8 PA0
+ * 9 PW0
+ * 10 PB0
+ * 11 RS Reset
+ *
+ * 14 Vdd V input
+ *
+ * Rwa = 10000 * (256 - d) / 256 + 52
+ * d = 256 - (Rwa - 52) * 256 / 10000
+ *
+ */
+
+SPIDriver * getDigiralPotDevice(spi_device_e spiDevice) {
+#if STM32_SPI_USE_SPI1 || defined(__DOXYGEN__)
+ if (spiDevice == SPI_DEVICE_1) {
+ return &SPID1;
+ }
+#endif
+#if STM32_SPI_USE_SPI2 || defined(__DOXYGEN__)
+ if (spiDevic e== SPI_DEVICE_2) {
+ return &SPID2;
+ }
+#endif
+#if STM32_SPI_USE_SPI3 || defined(__DOXYGEN__)
+ if (spiDevice == SPI_DEVICE_3) {
+ return &SPID3;
+ }
+#endif
+ firmwareError("Unexpected SPI device: %d", spiDevice);
+ return NULL;
+}
+
+/* Low speed SPI configuration (281.250kHz, CPHA=0, CPOL=0, MSb first).*/
+#define SPI_POT_CONFIG SPI_CR1_BR_2 | SPI_CR1_BR_1 | SPI_CR1_DFF
+
+static Logging logger;
+
+#if EFI_POTENTIOMETER || defined(__DOXYGEN__)
+Mcp42010Driver config[DIGIPOT_COUNT];
+
+void initPotentiometer(Mcp42010Driver *driver, SPIDriver *spi, ioportid_t port, ioportmask_t pin) {
+ driver->spiConfig.end_cb = NULL;
+ driver->spiConfig.ssport = port;
+ driver->spiConfig.sspad = pin;
+ driver->spiConfig.cr1 = SPI_POT_CONFIG;
+ driver->spi = spi;
+ mySetPadMode("pot chip select", port, pin, PAL_STM32_MODE_OUTPUT);
+}
+
+static int getPotStep(int resistanceWA) {
+ return 256 - (int) ((resistanceWA - 52) * 256 / 10000);
+}
+
+static void sendToPot(Mcp42010Driver *driver, int channel, int value) {
+ lockSpi(SPI_NONE);
+ spiStart(driver->spi, &driver->spiConfig);
+ spiSelect(driver->spi);
+ int word = (17 + channel) * 256 + value;
+ spiSend(driver->spi, 1, &word);
+ spiUnselect(driver->spi);
+ spiStop(driver->spi);
+ unlockSpi();
+}
+
+void setPotResistance(Mcp42010Driver *driver, int channel, int resistance) {
+ int value = getPotStep(resistance);
+
+ Logging *logging = &logger;
+ resetLogging(logging);
+ appendPrintf(logging, "msg");
+ appendPrintf(logging, DELIMETER);
+ appendPrintf(logging, "Sending to potentiometer%d", channel);
+ appendPrintf(&logger, ": ");
+ appendPrintf(&logger, "%d for R=%d", value, resistance);
+ appendMsgPostfix(logging);
+
+ scheduleLogging(logging);
+
+ sendToPot(driver, channel, value);
+}
+
+static void setPotResistanceCommand(int index, int value) {
+ setPotResistance(&config[index / 2], index % 2, value);
+}
+
+static void setPotValue1(int value) {
+ sendToPot(&config[0], 1, value);
+}
+
+#endif /* EFI_POTENTIOMETER */
+
+void initPotentiometers(board_configuration_s *boardConfiguration) {
+#if EFI_POTENTIOMETER
+ initLogging(&logger, "potentiometer");
+ if (boardConfiguration->digitalPotentiometerSpiDevice == SPI_NONE) {
+ scheduleMsg(&logger, "digiPot spi disabled");
+ return;
+ }
+ turnOnSpi(boardConfiguration->digitalPotentiometerSpiDevice);
+
+ for (int i = 0; i < DIGIPOT_COUNT; i++) {
+ brain_pin_e csPin = boardConfiguration->digitalPotentiometerChipSelect[i];
+ if (csPin == GPIO_NONE) {
+ continue;
+ }
+
+ initPotentiometer(&config[i], getDigiralPotDevice(boardConfiguration->digitalPotentiometerSpiDevice),
+ getHwPort(csPin), getHwPin(csPin));
+ }
+
+ addConsoleActionII("pot", setPotResistanceCommand);
+
+ addConsoleActionI("potd1", setPotValue1);
+
+ setPotResistance(&config[0], 0, 3000);
+ setPotResistance(&config[0], 1, 7000);
+#else
+ print("digiPot logic disabled\r\n");
+#endif
+}
diff --git a/firmware/emulation/hw_layer/poten.h b/firmware/emulation/hw_layer/poten.h
new file mode 100644
index 0000000000..39d0898a9e
--- /dev/null
+++ b/firmware/emulation/hw_layer/poten.h
@@ -0,0 +1,24 @@
+/**
+ * @file poten.h
+ * @brief MCP42010 digital potentiometer driver
+ *
+ * @date Mar 16, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef POTEN_H_
+#define POTEN_H_
+
+#include "main.h"
+#include "engine_configuration.h"
+
+typedef struct {
+ SPIDriver *spi;
+ SPIConfig spiConfig;
+} Mcp42010Driver;
+
+void initPotentiometer(Mcp42010Driver *driver, SPIDriver *spi, ioportid_t port, ioportmask_t pin);
+void initPotentiometers(board_configuration_s *boardConfiguration);
+void setPotResistance(Mcp42010Driver *driver, int channel, int resistance);
+
+#endif /* POTEN_H_ */
diff --git a/firmware/emulation/readme.txt b/firmware/emulation/readme.txt
new file mode 100644
index 0000000000..bd00af6d04
--- /dev/null
+++ b/firmware/emulation/readme.txt
@@ -0,0 +1,2 @@
+All the stuff in this folder is optional and is not required
+to run the engine.
diff --git a/firmware/emulation/rfi_perftest.cpp b/firmware/emulation/rfi_perftest.cpp
new file mode 100644
index 0000000000..96d1885918
--- /dev/null
+++ b/firmware/emulation/rfi_perftest.cpp
@@ -0,0 +1,271 @@
+/**
+ * @file rfi_perftest.cpp
+ *
+ * @date Nov 30, 2012
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+#include "rfi_perftest.h"
+#include "fuel_math.h"
+
+#include "test.h"
+#include "eficonsole.h"
+#include "time.h"
+#include "engine_math.h"
+#include "gpio_helper.h"
+#include "efilib2.h"
+#include "console_io.h"
+#include "engine.h"
+
+#if EFI_PERF_METRICS || defined(__DOXYGEN__)
+
+static Logging logger;
+
+static void testSystemCalls(const int count) {
+ time_t start, time;
+ long result = 0;
+
+ start = currentTimeMillis();
+ for (int i = 0; i < count / 2; i++) {
+// setPinValue(&testOutput, 0);
+// setPinValue(&testOutput, 1);
+ }
+
+ time = currentTimeMillis() - start;
+ // Finished 100000 iterations of 'setPinValue()' in 120ms
+// prin("Finished %d iterations of 'setPinValue()' in %dms\r\n", count, time);
+
+ start = currentTimeMillis();
+ for (int i = 0; i < count; i++)
+ result += chTimeNow();
+ time = currentTimeMillis() - start;
+ if (result != 0) {
+ // Finished 100000 iterations of 'chTimeNow()' in 33ms
+ scheduleMsg(&logger, "Finished %d iterations of 'chTimeNow()' in %dms", count, time);
+ }
+
+ start = currentTimeMillis();
+ for (int i = 0; i < count; i++) {
+ chSysLock()
+ ;
+ result += chTimeNow();
+ chSysUnlock()
+ ;
+ }
+ time = currentTimeMillis() - start;
+ if (result != 0) {
+ // Finished 100000 iterations of 'chTimeNow()' with chSysLock in 144ms
+ scheduleMsg(&logger, "Finished %d iterations of 'chTimeNow()' with chSysLock in %dms", count, time);
+ }
+
+ start = currentTimeMillis();
+ for (int i = 0; i < count; i++)
+ result += currentTimeMillis();
+ time = currentTimeMillis() - start;
+ if (result != 0)
+ scheduleMsg(&logger, "Finished %d iterations of 'currentTimeMillis' in %dms", count, time);
+}
+
+static Engine testEngine;
+
+static void testRusefiMethods(const int count) {
+ time_t start, time;
+ int tempi = 1;
+
+ start = currentTimeMillis();
+
+ for (int i = 0; i < count; i++)
+ tempi += getBaseTableFuel(4020, 2.21111);
+ time = currentTimeMillis() - start;
+ if (tempi != 0)
+ scheduleMsg(&logger, "Finished %d iterations of getBaseFuel in %dms", count, time);
+
+// start = currentTimeMillis();
+// for (int i = 0; i < count; i++)
+// tempi += getFuelMs(1200, NULL); // todo
+// time = currentTimeMillis() - start;
+// if (tempi != 0)
+// scheduleMsg(&logger, "Finished %d iterations of getFuelMs in %dms", count, time);
+
+ start = currentTimeMillis();
+ for (int i = 0; i < count; i++) {
+ testEngine.updateSlowSensors();
+ tempi += testEngine.engineState.clt;
+ }
+ time = currentTimeMillis() - start;
+ if (tempi != 0)
+ scheduleMsg(&logger, "Finished %d iterations of updateSlowSensors in %dms", count, time);
+}
+
+static void testMath(const int count) {
+ time_t start, time;
+
+ int64_t temp64 = 0;
+ start = currentTimeMillis();
+ for (int64_t i = 0; i < count; i++)
+ temp64 += i;
+ time = currentTimeMillis() - start;
+ if (temp64 != 0)
+ scheduleMsg(&logger, "Finished %d iterations of int64_t summation in %dms", count, time);
+
+ temp64 = 1;
+ start = currentTimeMillis();
+ for (int64_t i = 0; i < count; i++)
+ temp64 *= i;
+ time = currentTimeMillis() - start;
+ if (temp64 == 0)
+ scheduleMsg(&logger, "Finished %d iterations of int64_t multiplication in %dms", count, time);
+
+
+ start = currentTimeMillis();
+ for (int i = 0; i < count; i++)
+ ;
+ time = currentTimeMillis() - start;
+ scheduleMsg(&logger, "Finished %d iterations of empty loop in %dms", count, time);
+
+ int tempi = 1;
+ start = currentTimeMillis();
+ for (int i = 0; i < count; i++)
+ tempi += tempi;
+ time = currentTimeMillis() - start;
+ if (tempi == 0)
+ scheduleMsg(&logger, "Finished %d iterations of int summation in %dms", count, time);
+
+ start = currentTimeMillis();
+ tempi = 1;
+ for (int i = 0; i < count; i++)
+ tempi += (tempi + 100) / 130;
+ time = currentTimeMillis() - start;
+ if (tempi != 0)
+ scheduleMsg(&logger, "Finished %d iterations of int division in %dms", count, time);
+
+ start = currentTimeMillis();
+ long templ = 1;
+ for (int i = 0; i < count; i++)
+ templ += templ;
+ time = currentTimeMillis() - start;
+ if (templ == 0)
+ scheduleMsg(&logger, "Finished %d iterations of long summation in %dms", count, time);
+
+ start = currentTimeMillis();
+ templ = 1;
+ for (int i = 0; i < count; i++)
+ templ += (templ + 100) / 130;
+ time = currentTimeMillis() - start;
+ if (templ != 0) {
+ // Finished 100000 iterations of long division in 45ms
+ scheduleMsg(&logger, "Finished %d iterations of long division in %dms", count, time);
+ }
+
+ start = currentTimeMillis();
+ float tempf = 1;
+ for (int i = 0; i < count; i++)
+ tempf += tempf;
+ time = currentTimeMillis() - start;
+ if (tempf != 0)
+ scheduleMsg(&logger, "Finished %d iterations of float summation in %dms", count, time);
+
+ start = currentTimeMillis();
+ tempf = 1;
+ for (int i = 0; i < count; i++)
+ tempf += (tempf + 100) / 130.0;
+ time = currentTimeMillis() - start;
+ if (tempf != 0) {
+ // Finished 100000 iterations of float division in 65ms
+ scheduleMsg(&logger, "Finished %d iterations of float division in %dms", count, time);
+ }
+
+ start = currentTimeMillis();
+ tempf = 1;
+ for (int i = 0; i < count; i++)
+ tempf += logf(tempf);
+ time = currentTimeMillis() - start;
+ if (tempf != 0)
+ scheduleMsg(&logger, "Finished %d iterations of float log in %dms", count, time);
+
+ start = currentTimeMillis();
+ double tempd = 1;
+ for (int i = 0; i < count; i++)
+ tempd += tempd / 2;
+ time = currentTimeMillis() - start;
+ if (tempd != 0)
+ scheduleMsg(&logger, "Finished %d iterations of double summation in %dms", count, time);
+
+ start = currentTimeMillis();
+ tempd = 1;
+ for (int i = 0; i < count; i++)
+ tempd += (tempd + 100) / 130.0;
+ time = currentTimeMillis() - start;
+ if (tempd != 0)
+ scheduleMsg(&logger, "Finished %d iterations of double division in %dms", count, time);
+
+ start = currentTimeMillis();
+ tempd = 1;
+ for (int i = 0; i < count; i++)
+ tempd += log(tempd);
+ time = currentTimeMillis() - start;
+ if (tempd != 0)
+ scheduleMsg(&logger, "Finished %d iterations of double log in %dms", count, time);
+}
+
+static void runTests(const int count) {
+ scheduleMsg(&logger, "Running tests: %d", count);
+ testRusefiMethods(count / 10);
+ testSystemCalls(count);
+ testMath(count);
+}
+
+extern Overflow64Counter halTime;
+
+#if EFI_RTC
+static int rtcStartTime;
+#endif
+
+#include "chrtclib.h"
+
+static void timeInfo(void) {
+ scheduleMsg(&logger, "chTimeNow as seconds = %d", getTimeNowSeconds());
+ scheduleMsg(&logger, "hal seconds = %d", halTime.get(hal_lld_get_counter_value(), false) / 168000000LL);
+
+#if EFI_RTC
+ int unix = rtcGetTimeUnixSec(&RTCD1) - rtcStartTime;
+ scheduleMsg(&logger, "unix seconds = %d", unix);
+#endif
+}
+
+static void runChibioTest(void) {
+ print("EFI_SHAFT_POSITION_INPUT=%d\r\n", EFI_SHAFT_POSITION_INPUT);
+ print("EFI_EMULATE_POSITION_SENSORS=%d\r\n", EFI_EMULATE_POSITION_SENSORS);
+ print("EFI_ANALOG_INPUTS=%d\r\n", EFI_ANALOG_INPUTS);
+ print("EFI_INTERNAL_ADC=%d\r\n", EFI_INTERNAL_ADC);
+ print("EFI_HD44780_LCD=%d\r\n", EFI_HD44780_LCD);
+ print("EFI_MAP_AVERAGING=%d\r\n", EFI_MAP_AVERAGING);
+ print("EFI_WAVE_ANALYZER=%d\r\n", EFI_WAVE_ANALYZER);
+ print("EFI_WAVE_CHART=%d\r\n", EFI_WAVE_CHART);
+ print("EFI_ANALOG_CHART=%d\r\n", EFI_ANALOG_CHART);
+ print("EFI_SHAFT_POSITION_INPUT=%d\r\n", EFI_SHAFT_POSITION_INPUT);
+ print("EFI_ENGINE_CONTROL=%d\r\n", EFI_ENGINE_CONTROL);
+ print("CH_DBG_SYSTEM_STATE_CHECK=%d\r\n", CH_DBG_SYSTEM_STATE_CHECK);
+ print("CH_DBG_ENABLE_CHECKS=%d\r\n", CH_DBG_ENABLE_CHECKS);
+ print("CH_DBG_ENABLE_ASSERTS=%d\r\n", CH_DBG_ENABLE_ASSERTS);
+ print("CH_DBG_ENABLE_STACK_CHECK=%d\r\n", CH_DBG_ENABLE_STACK_CHECK);
+ print("CH_DBG_THREADS_PROFILING=%d\r\n", CH_DBG_THREADS_PROFILING);
+ TestThread(getConsoleChannel());
+}
+
+void initTimePerfActions() {
+#if EFI_RTC
+ rtcStartTime = rtcGetTimeUnixSec(&RTCD1);
+#endif
+
+
+ initLogging(&logger, "perftest");
+// initOutputPin("test pad", &testOutput, TEST_PORT, TEST_PIN);
+ addConsoleActionI("perftest", runTests);
+
+ addConsoleAction("timeinfo", timeInfo);
+ addConsoleAction("chtest", runChibioTest);
+}
+
+#endif /* EFI_PERF_METRICS */
diff --git a/firmware/emulation/rfi_perftest.h b/firmware/emulation/rfi_perftest.h
new file mode 100644
index 0000000000..ef6b4e1964
--- /dev/null
+++ b/firmware/emulation/rfi_perftest.h
@@ -0,0 +1,24 @@
+/**
+ * @file rfi_perftest.h
+ *
+ * @date Nov 30, 2012
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef RFI_PERFTEST_H_
+#define RFI_PERFTEST_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+//void testMath(int count);
+//void testSystemCalls(int count);
+void initTimePerfActions(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* RFI_PERFTEST_H_ */
diff --git a/firmware/emulation/test/test.c b/firmware/emulation/test/test.c
new file mode 100644
index 0000000000..4a89322fb0
--- /dev/null
+++ b/firmware/emulation/test/test.c
@@ -0,0 +1,372 @@
+/*
+ ChibiOS/RT - Copyright (C) 2006-2013 Giovanni Di Sirio
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+/**
+ * @file test.c
+ * @brief Tests support code.
+ *
+ * @addtogroup test
+ * @{
+ */
+
+#include "main.h"
+
+#if EFI_PERF_METRICS
+
+#include "test.h"
+#include "testbmk.h"
+
+/*
+ * Array of all the test patterns.
+ */
+static ROMCONST struct testcase * ROMCONST *patterns[] = {
+ patternbmk,
+ NULL
+};
+
+static bool_t local_fail, global_fail;
+static unsigned failpoint;
+static char tokens_buffer[MAX_TOKENS];
+static char *tokp;
+
+/*
+ * Static working areas, the following areas can be used for threads or
+ * used as temporary buffers.
+ */
+union test_buffers test;
+
+/*
+ * Pointers to the spawned threads.
+ */
+Thread *threads[MAX_THREADS];
+
+/*
+ * Pointers to the working areas.
+ */
+void * ROMCONST wa[5] = {test.wa.T0, test.wa.T1, test.wa.T2,
+ test.wa.T3, test.wa.T4};
+
+/*
+ * Console output.
+ */
+static BaseSequentialStream *chp;
+
+/**
+ * @brief Prints a decimal unsigned number.
+ *
+ * @param[in] n the number to be printed
+ */
+void test_printn(uint32_t n) {
+ char buf[16], *p;
+
+ if (!n)
+ chSequentialStreamPut(chp, '0');
+ else {
+ p = buf;
+ while (n)
+ *p++ = (n % 10) + '0', n /= 10;
+ while (p > buf)
+ chSequentialStreamPut(chp, *--p);
+ }
+}
+
+/**
+ * @brief Prints a line without final end-of-line.
+ *
+ * @param[in] msgp the message
+ */
+void test_print(const char *msgp) {
+
+ while (*msgp)
+ chSequentialStreamPut(chp, *msgp++);
+}
+
+/**
+ * @brief Prints a line.
+ *
+ * @param[in] msgp the message
+ */
+void test_println(const char *msgp) {
+
+ test_print(msgp);
+ chSequentialStreamWrite(chp, (const uint8_t *)"\r\n", 2);
+}
+
+/*
+ * Tokens.
+ */
+static void clear_tokens(void) {
+
+ tokp = tokens_buffer;
+}
+
+static void print_tokens(void) {
+ char *cp = tokens_buffer;
+
+ while (cp < tokp)
+ chSequentialStreamPut(chp, *cp++);
+}
+
+/**
+ * @brief Emits a token into the tokens buffer.
+ *
+ * @param[in] token the token as a char
+ */
+void test_emit_token(char token) {
+
+ chSysLock();
+ *tokp++ = token;
+ chSysUnlock();
+}
+
+/*
+ * Assertions.
+ */
+bool_t _test_fail(unsigned point) {
+
+ local_fail = TRUE;
+ global_fail = TRUE;
+ failpoint = point;
+ return TRUE;
+}
+
+bool_t _test_assert(unsigned point, bool_t condition) {
+
+ if (!condition)
+ return _test_fail(point);
+ return FALSE;
+}
+
+bool_t _test_assert_sequence(unsigned point, char *expected) {
+ char *cp = tokens_buffer;
+ while (cp < tokp) {
+ if (*cp++ != *expected++)
+ return _test_fail(point);
+ }
+ if (*expected)
+ return _test_fail(point);
+ clear_tokens();
+ return FALSE;
+}
+
+bool_t _test_assert_time_window(unsigned point, systime_t start, systime_t end) {
+
+ return _test_assert(point, chTimeIsWithin(start, end));
+}
+
+/*
+ * Threads utils.
+ */
+
+/**
+ * @brief Sets a termination request in all the test-spawned threads.
+ */
+void test_terminate_threads(void) {
+ int i;
+
+ for (i = 0; i < MAX_THREADS; i++)
+ if (threads[i])
+ chThdTerminate(threads[i]);
+}
+
+/**
+ * @brief Waits for the completion of all the test-spawned threads.
+ */
+void test_wait_threads(void) {
+ int i;
+
+ for (i = 0; i < MAX_THREADS; i++)
+ if (threads[i] != NULL) {
+ chThdWait(threads[i]);
+ threads[i] = NULL;
+ }
+}
+
+#if CH_DBG_THREADS_PROFILING
+/**
+ * @brief CPU pulse.
+ * @note The current implementation is not totally reliable.
+ *
+ * @param[in] duration CPU pulse duration in milliseconds
+ */
+void test_cpu_pulse(unsigned duration) {
+ systime_t start, end, now;
+
+ start = chThdSelf()->p_time;
+ end = start + MS2ST(duration);
+ do {
+ now = chThdSelf()->p_time;
+#if defined(SIMULATOR)
+ ChkIntSources();
+#endif
+ }
+ while (end > start ? (now >= start) && (now < end) :
+ (now >= start) || (now < end));
+}
+#endif
+
+/**
+ * @brief Delays execution until next system time tick.
+ *
+ * @return The system time.
+ */
+systime_t test_wait_tick(void) {
+
+ chThdSleep(1);
+ return chTimeNow();
+}
+
+/*
+ * Timer utils.
+ */
+
+/**
+ * @brief Set to @p TRUE when the test timer reaches its deadline.
+ */
+bool_t test_timer_done;
+
+static VirtualTimer vt;
+static void tmr(void *p) {
+ (void)p;
+
+ test_timer_done = TRUE;
+}
+
+/**
+ * @brief Starts the test timer.
+ *
+ * @param[in] ms time in milliseconds
+ */
+void test_start_timer(unsigned ms) {
+
+ systime_t duration = MS2ST(ms);
+ test_timer_done = FALSE;
+ chVTSet(&vt, duration, tmr, NULL);
+}
+
+/*
+ * Test suite execution.
+ */
+static void execute_test(const struct testcase *tcp) {
+ int i;
+
+ /* Initialization */
+ clear_tokens();
+ local_fail = FALSE;
+ for (i = 0; i < MAX_THREADS; i++)
+ threads[i] = NULL;
+
+ if (tcp->setup != NULL)
+ tcp->setup();
+ tcp->execute();
+ if (tcp->teardown != NULL)
+ tcp->teardown();
+
+ test_wait_threads();
+}
+
+static void print_line(void) {
+ unsigned i;
+
+ for (i = 0; i < 76; i++)
+ chSequentialStreamPut(chp, '-');
+ chSequentialStreamWrite(chp, (const uint8_t *)"\r\n", 2);
+}
+
+/**
+ * @brief Test execution thread function.
+ *
+ * @param[in] p pointer to a @p BaseChannel object for test output
+ * @return A failure boolean value.
+ */
+msg_t TestThread(void *p) {
+ int i, j;
+
+ chp = p;
+ test_println("");
+ test_println("*** ChibiOS/RT test suite");
+ test_println("***");
+ test_print("*** Kernel: ");
+ test_println(CH_KERNEL_VERSION);
+ test_print("*** Compiled: ");
+ test_println(__DATE__ " - " __TIME__);
+#ifdef CH_COMPILER_NAME
+ test_print("*** Compiler: ");
+ test_println(CH_COMPILER_NAME);
+#endif
+ test_print("*** Architecture: ");
+ test_println(CH_ARCHITECTURE_NAME);
+#ifdef CH_CORE_VARIANT_NAME
+ test_print("*** Core Variant: ");
+ test_println(CH_CORE_VARIANT_NAME);
+#endif
+#ifdef CH_PORT_INFO
+ test_print("*** Port Info: ");
+ test_println(CH_PORT_INFO);
+#endif
+#ifdef PLATFORM_NAME
+ test_print("*** Platform: ");
+ test_println(PLATFORM_NAME);
+#endif
+#ifdef BOARD_NAME
+ test_print("*** Test Board: ");
+ test_println(BOARD_NAME);
+#endif
+ test_println("");
+
+ global_fail = FALSE;
+ i = 0;
+ while (patterns[i]) {
+ j = 0;
+ while (patterns[i][j]) {
+ print_line();
+ test_print("--- Test Case ");
+ test_printn(i + 1);
+ test_print(".");
+ test_printn(j + 1);
+ test_print(" (");
+ test_print(patterns[i][j]->name);
+ test_println(")");
+#if DELAY_BETWEEN_TESTS > 0
+ chThdSleepMilliseconds(DELAY_BETWEEN_TESTS);
+#endif
+ execute_test(patterns[i][j]);
+ if (local_fail) {
+ test_print("--- Result: FAILURE (#");
+ test_printn(failpoint);
+ test_print(" [");
+ print_tokens();
+ test_println("])");
+ }
+ else
+ test_println("--- Result: SUCCESS");
+ j++;
+ }
+ i++;
+ }
+ print_line();
+ test_println("");
+ test_print("Final result: ");
+ if (global_fail)
+ test_println("FAILURE");
+ else
+ test_println("SUCCESS");
+
+ return (msg_t)global_fail;
+}
+
+/** @} */
+#endif /* EFI_PERF_METRICS */
diff --git a/firmware/emulation/test/test.h b/firmware/emulation/test/test.h
new file mode 100644
index 0000000000..e5778177bd
--- /dev/null
+++ b/firmware/emulation/test/test.h
@@ -0,0 +1,173 @@
+/*
+ ChibiOS/RT - Copyright (C) 2006-2013 Giovanni Di Sirio
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+/**
+ * @file test.h
+ * @brief Tests support header.
+ *
+ * @addtogroup test
+ * @{
+ */
+
+#ifndef _TEST_H_
+#define _TEST_H_
+
+/**
+ * @brief Delay inserted between test cases.
+ */
+#if !defined(DELAY_BETWEEN_TESTS) || defined(__DOXYGEN__)
+#define DELAY_BETWEEN_TESTS 200
+#endif
+
+/**
+ * @brief If @p TRUE then benchmarks are not included.
+ */
+#if !defined(TEST_NO_BENCHMARKS) || defined(__DOXYGEN__)
+#define TEST_NO_BENCHMARKS FALSE
+#endif
+
+#define MAX_THREADS 5
+#define MAX_TOKENS 16
+
+#if defined(CH_ARCHITECTURE_AVR) || defined(CH_ARCHITECTURE_MSP430)
+#define THREADS_STACK_SIZE 48
+#elif defined(CH_ARCHITECTURE_STM8)
+#define THREADS_STACK_SIZE 64
+#elif defined(CH_ARCHITECTURE_SIMIA32)
+#define THREADS_STACK_SIZE 512
+#else
+#define THREADS_STACK_SIZE 128
+#endif
+#define WA_SIZE THD_WA_SIZE(THREADS_STACK_SIZE)
+
+/**
+ * @brief Structure representing a test case.
+ */
+struct testcase {
+ const char *name; /**< @brief Test case name. */
+ void (*setup)(void); /**< @brief Test case preparation function. */
+ void (*teardown)(void); /**< @brief Test case clean up function. */
+ void (*execute)(void); /**< @brief Test case execution function. */
+};
+
+#ifndef __DOXYGEN__
+union test_buffers {
+ struct {
+ WORKING_AREA(T0, THREADS_STACK_SIZE);
+ WORKING_AREA(T1, THREADS_STACK_SIZE);
+ WORKING_AREA(T2, THREADS_STACK_SIZE);
+ WORKING_AREA(T3, THREADS_STACK_SIZE);
+ WORKING_AREA(T4, THREADS_STACK_SIZE);
+ } wa;
+ uint8_t buffer[WA_SIZE * 5];
+};
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+ msg_t TestThread(void *p);
+ void test_printn(uint32_t n);
+ void test_print(const char *msgp);
+ void test_println(const char *msgp);
+ void test_emit_token(char token);
+ bool_t _test_fail(unsigned point);
+ bool_t _test_assert(unsigned point, bool_t condition);
+ bool_t _test_assert_sequence(unsigned point, char *expected);
+ bool_t _test_assert_time_window(unsigned point, systime_t start, systime_t end);
+ void test_terminate_threads(void);
+ void test_wait_threads(void);
+ systime_t test_wait_tick(void);
+ void test_start_timer(unsigned ms);
+#if CH_DBG_THREADS_PROFILING
+ void test_cpu_pulse(unsigned duration);
+#endif
+#if defined(WIN32)
+ void ChkIntSources(void);
+#endif
+#ifdef __cplusplus
+}
+#endif
+
+/**
+ * @brief Test failure enforcement.
+ */
+#define test_fail(point) { \
+ _test_fail(point); \
+ return; \
+}
+
+/**
+ * @brief Test assertion.
+ *
+ * @param[in] point numeric assertion identifier
+ * @param[in] condition a boolean expression that must be verified to be true
+ * @param[in] msg failure message
+ */
+#define test_assert(point, condition, msg) { \
+ if (_test_assert(point, condition)) \
+ return; \
+}
+
+/**
+ * @brief Test assertion with lock.
+ *
+ * @param[in] point numeric assertion identifier
+ * @param[in] condition a boolean expression that must be verified to be true
+ * @param[in] msg failure message
+ */
+#define test_assert_lock(point, condition, msg) { \
+ chSysLock(); \
+ if (_test_assert(point, condition)) { \
+ chSysUnlock(); \
+ return; \
+ } \
+ chSysUnlock(); \
+}
+
+/**
+ * @brief Test sequence assertion.
+ *
+ * @param[in] point numeric assertion identifier
+ * @param[in] expected string to be matched with the tokens buffer
+ */
+#define test_assert_sequence(point, expected) { \
+ if (_test_assert_sequence(point, expected)) \
+ return; \
+}
+
+/**
+ * @brief Test time window assertion.
+ *
+ * @param[in] point numeric assertion identifier
+ * @param[in] start initial time in the window (included)
+ * @param[in] end final time in the window (not included)
+ */
+#define test_assert_time_window(point, start, end) { \
+ if (_test_assert_time_window(point, start, end)) \
+ return; \
+}
+
+#if !defined(__DOXYGEN__)
+extern Thread *threads[MAX_THREADS];
+extern union test_buffers test;
+extern void * ROMCONST wa[];
+extern bool_t test_timer_done;
+#endif
+
+#endif /* _TEST_H_ */
+
+/** @} */
diff --git a/firmware/emulation/test/testbmk.c b/firmware/emulation/test/testbmk.c
new file mode 100644
index 0000000000..069dff0590
--- /dev/null
+++ b/firmware/emulation/test/testbmk.c
@@ -0,0 +1,724 @@
+/*
+ ChibiOS/RT - Copyright (C) 2006-2013 Giovanni Di Sirio
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+#include "main.h"
+
+#if EFI_PERF_METRICS
+#include "test.h"
+
+/**
+ * @page test_benchmarks Kernel Benchmarks
+ *
+ * File: @ref testbmk.c
+ *
+ * Description
+ * This module implements a series of system benchmarks. The benchmarks are
+ * useful as a stress test and as a reference when comparing ChibiOS/RT
+ * with similar systems.
+ *
+ * Objective
+ * Objective of the test module is to provide a performance index for the
+ * most critical system subsystems. The performance numbers allow to
+ * discover performance regressions between successive ChibiOS/RT releases.
+ *
+ * Preconditions
+ * None.
+ *
+ * Test Cases
+ * - @subpage test_benchmarks_001
+ * - @subpage test_benchmarks_002
+ * - @subpage test_benchmarks_003
+ * - @subpage test_benchmarks_004
+ * - @subpage test_benchmarks_005
+ * - @subpage test_benchmarks_006
+ * - @subpage test_benchmarks_007
+ * - @subpage test_benchmarks_008
+ * - @subpage test_benchmarks_009
+ * - @subpage test_benchmarks_010
+ * - @subpage test_benchmarks_011
+ * - @subpage test_benchmarks_012
+ * - @subpage test_benchmarks_013
+ * .
+ * @file testbmk.c Kernel Benchmarks
+ * @brief Kernel Benchmarks source file
+ * @file testbmk.h
+ * @brief Kernel Benchmarks header file
+ */
+
+static Semaphore sem1;
+#if CH_USE_MUTEXES || defined(__DOXYGEN__)
+static Mutex mtx1;
+#endif
+
+static msg_t thread1(void *p) {
+ Thread *tp;
+ msg_t msg;
+
+ (void)p;
+ do {
+ tp = chMsgWait();
+ msg = chMsgGet(tp);
+ chMsgRelease(tp, msg);
+ } while (msg);
+ return 0;
+}
+
+#ifdef __GNUC__
+__attribute__((noinline))
+#endif
+static unsigned int msg_loop_test(Thread *tp) {
+
+ uint32_t n = 0;
+ test_wait_tick();
+ test_start_timer(1000);
+ do {
+ (void)chMsgSend(tp, 1);
+ n++;
+#if defined(SIMULATOR)
+ ChkIntSources();
+#endif
+ } while (!test_timer_done);
+ (void)chMsgSend(tp, 0);
+ return n;
+}
+
+/**
+ * @page test_benchmarks_001 Messages performance #1
+ *
+ * Description
+ * A message server thread is created with a lower priority than the client
+ * thread, the messages throughput per second is measured and the result
+ * printed in the output log.
+ */
+
+static void bmk1_execute(void) {
+ uint32_t n;
+
+ threads[0] = chThdCreateStatic(wa[0], WA_SIZE, chThdGetPriority()-1, thread1, NULL);
+ n = msg_loop_test(threads[0]);
+ test_wait_threads();
+ test_print("--- Score : ");
+ test_printn(n);
+ test_print(" msgs/S, ");
+ test_printn(n << 1);
+ test_println(" ctxswc/S");
+}
+
+ROMCONST struct testcase testbmk1 = {
+ "Benchmark, messages #1",
+ NULL,
+ NULL,
+ bmk1_execute
+};
+
+/**
+ * @page test_benchmarks_002 Messages performance #2
+ *
+ * Description
+ * A message server thread is created with an higher priority than the client
+ * thread, the messages throughput per second is measured and the result
+ * printed in the output log.
+ */
+
+static void bmk2_execute(void) {
+ uint32_t n;
+
+ threads[0] = chThdCreateStatic(wa[0], WA_SIZE, chThdGetPriority()+1, thread1, NULL);
+ n = msg_loop_test(threads[0]);
+ test_wait_threads();
+ test_print("--- Score : ");
+ test_printn(n);
+ test_print(" msgs/S, ");
+ test_printn(n << 1);
+ test_println(" ctxswc/S");
+}
+
+ROMCONST struct testcase testbmk2 = {
+ "Benchmark, messages #2",
+ NULL,
+ NULL,
+ bmk2_execute
+};
+
+static msg_t thread2(void *p) {
+
+ return (msg_t)p;
+}
+
+/**
+ * @page test_benchmarks_003 Messages performance #3
+ *
+ * Description
+ * A message server thread is created with an higher priority than the client
+ * thread, four lower priority threads crowd the ready list, the messages
+ * throughput per second is measured while the ready list and the result
+ * printed in the output log.
+ */
+
+static void bmk3_execute(void) {
+ uint32_t n;
+
+ threads[0] = chThdCreateStatic(wa[0], WA_SIZE, chThdGetPriority()+1, thread1, NULL);
+ threads[1] = chThdCreateStatic(wa[1], WA_SIZE, chThdGetPriority()-2, thread2, NULL);
+ threads[2] = chThdCreateStatic(wa[2], WA_SIZE, chThdGetPriority()-3, thread2, NULL);
+ threads[3] = chThdCreateStatic(wa[3], WA_SIZE, chThdGetPriority()-4, thread2, NULL);
+ threads[4] = chThdCreateStatic(wa[4], WA_SIZE, chThdGetPriority()-5, thread2, NULL);
+ n = msg_loop_test(threads[0]);
+ test_wait_threads();
+ test_print("--- Score : ");
+ test_printn(n);
+ test_print(" msgs/S, ");
+ test_printn(n << 1);
+ test_println(" ctxswc/S");
+}
+
+ROMCONST struct testcase testbmk3 = {
+ "Benchmark, messages #3",
+ NULL,
+ NULL,
+ bmk3_execute
+};
+
+/**
+ * @page test_benchmarks_004 Context Switch performance
+ *
+ * Description
+ * A thread is created that just performs a @p chSchGoSleepS() into a loop,
+ * the thread is awakened as fast is possible by the tester thread.
+ * The Context Switch performance is calculated by measuring the number of
+ * iterations after a second of continuous operations.
+ */
+
+msg_t thread4(void *p) {
+ msg_t msg;
+ Thread *self = chThdSelf();
+
+ (void)p;
+ chSysLock();
+ do {
+ chSchGoSleepS(THD_STATE_SUSPENDED);
+ msg = self->p_u.rdymsg;
+ } while (msg == RDY_OK);
+ chSysUnlock();
+ return 0;
+}
+
+static void bmk4_execute(void) {
+ Thread *tp;
+ uint32_t n;
+
+ tp = threads[0] = chThdCreateStatic(wa[0], WA_SIZE, chThdGetPriority()+1, thread4, NULL);
+ n = 0;
+ test_wait_tick();
+ test_start_timer(1000);
+ do {
+ chSysLock();
+ chSchWakeupS(tp, RDY_OK);
+ chSchWakeupS(tp, RDY_OK);
+ chSchWakeupS(tp, RDY_OK);
+ chSchWakeupS(tp, RDY_OK);
+ chSysUnlock();
+ n += 4;
+#if defined(SIMULATOR)
+ ChkIntSources();
+#endif
+ } while (!test_timer_done);
+ chSysLock();
+ chSchWakeupS(tp, RDY_TIMEOUT);
+ chSysUnlock();
+
+ test_wait_threads();
+ test_print("--- Score : ");
+ test_printn(n * 2);
+ test_println(" ctxswc/S");
+}
+
+ROMCONST struct testcase testbmk4 = {
+ "Benchmark, context switch",
+ NULL,
+ NULL,
+ bmk4_execute
+};
+
+/**
+ * @page test_benchmarks_005 Threads performance, full cycle
+ *
+ * Description
+ * Threads are continuously created and terminated into a loop. A full
+ * @p chThdCreateStatic() / @p chThdExit() / @p chThdWait() cycle is performed
+ * in each iteration.
+ * The performance is calculated by measuring the number of iterations after
+ * a second of continuous operations.
+ */
+
+static void bmk5_execute(void) {
+
+ uint32_t n = 0;
+ void *wap = wa[0];
+ tprio_t prio = chThdGetPriority() - 1;
+ test_wait_tick();
+ test_start_timer(1000);
+ do {
+ chThdWait(chThdCreateStatic(wap, WA_SIZE, prio, thread2, NULL));
+ n++;
+#if defined(SIMULATOR)
+ ChkIntSources();
+#endif
+ } while (!test_timer_done);
+ test_print("--- Score : ");
+ test_printn(n);
+ test_println(" threads/S");
+}
+
+ROMCONST struct testcase testbmk5 = {
+ "Benchmark, threads, full cycle",
+ NULL,
+ NULL,
+ bmk5_execute
+};
+
+/**
+ * @page test_benchmarks_006 Threads performance, create/exit only
+ *
+ * Description
+ * Threads are continuously created and terminated into a loop. A partial
+ * @p chThdCreateStatic() / @p chThdExit() cycle is performed in each
+ * iteration, the @p chThdWait() is not necessary because the thread is
+ * created at an higher priority so there is no need to wait for it to
+ * terminate.
+ * The performance is calculated by measuring the number of iterations after
+ * a second of continuous operations.
+ */
+
+static void bmk6_execute(void) {
+
+ uint32_t n = 0;
+ void *wap = wa[0];
+ tprio_t prio = chThdGetPriority() + 1;
+ test_wait_tick();
+ test_start_timer(1000);
+ do {
+ chThdCreateStatic(wap, WA_SIZE, prio, thread2, NULL);
+ n++;
+#if defined(SIMULATOR)
+ ChkIntSources();
+#endif
+ } while (!test_timer_done);
+ test_print("--- Score : ");
+ test_printn(n);
+ test_println(" threads/S");
+}
+
+ROMCONST struct testcase testbmk6 = {
+ "Benchmark, threads, create only",
+ NULL,
+ NULL,
+ bmk6_execute
+};
+
+/**
+ * @page test_benchmarks_007 Mass reschedule performance
+ *
+ * Description
+ * Five threads are created and atomically rescheduled by resetting the
+ * semaphore where they are waiting on. The operation is performed into a
+ * continuous loop.
+ * The performance is calculated by measuring the number of iterations after
+ * a second of continuous operations.
+ */
+
+static msg_t thread3(void *p) {
+
+ (void)p;
+ while (!chThdShouldTerminate())
+ chSemWait(&sem1);
+ return 0;
+}
+
+static void bmk7_setup(void) {
+
+ chSemInit(&sem1, 0);
+}
+
+static void bmk7_execute(void) {
+ uint32_t n;
+
+ threads[0] = chThdCreateStatic(wa[0], WA_SIZE, chThdGetPriority()+5, thread3, NULL);
+ threads[1] = chThdCreateStatic(wa[1], WA_SIZE, chThdGetPriority()+4, thread3, NULL);
+ threads[2] = chThdCreateStatic(wa[2], WA_SIZE, chThdGetPriority()+3, thread3, NULL);
+ threads[3] = chThdCreateStatic(wa[3], WA_SIZE, chThdGetPriority()+2, thread3, NULL);
+ threads[4] = chThdCreateStatic(wa[4], WA_SIZE, chThdGetPriority()+1, thread3, NULL);
+
+ n = 0;
+ test_wait_tick();
+ test_start_timer(1000);
+ do {
+ chSemReset(&sem1, 0);
+ n++;
+#if defined(SIMULATOR)
+ ChkIntSources();
+#endif
+ } while (!test_timer_done);
+ test_terminate_threads();
+ chSemReset(&sem1, 0);
+ test_wait_threads();
+
+ test_print("--- Score : ");
+ test_printn(n);
+ test_print(" reschedules/S, ");
+ test_printn(n * 6);
+ test_println(" ctxswc/S");
+}
+
+ROMCONST struct testcase testbmk7 = {
+ "Benchmark, mass reschedule, 5 threads",
+ bmk7_setup,
+ NULL,
+ bmk7_execute
+};
+
+/**
+ * @page test_benchmarks_008 I/O Round-Robin voluntary reschedule.
+ *
+ * Description
+ * Five threads are created at equal priority, each thread just increases a
+ * variable and yields.
+ * The performance is calculated by measuring the number of iterations after
+ * a second of continuous operations.
+ */
+
+static msg_t thread8(void *p) {
+
+ do {
+ chThdYield();
+ chThdYield();
+ chThdYield();
+ chThdYield();
+ (*(uint32_t *)p) += 4;
+#if defined(SIMULATOR)
+ ChkIntSources();
+#endif
+ } while(!chThdShouldTerminate());
+ return 0;
+}
+
+static void bmk8_execute(void) {
+ uint32_t n;
+
+ n = 0;
+ test_wait_tick();
+
+ threads[0] = chThdCreateStatic(wa[0], WA_SIZE, chThdGetPriority()-1, thread8, (void *)&n);
+ threads[1] = chThdCreateStatic(wa[1], WA_SIZE, chThdGetPriority()-1, thread8, (void *)&n);
+ threads[2] = chThdCreateStatic(wa[2], WA_SIZE, chThdGetPriority()-1, thread8, (void *)&n);
+ threads[3] = chThdCreateStatic(wa[3], WA_SIZE, chThdGetPriority()-1, thread8, (void *)&n);
+ threads[4] = chThdCreateStatic(wa[4], WA_SIZE, chThdGetPriority()-1, thread8, (void *)&n);
+
+ chThdSleepSeconds(1);
+ test_terminate_threads();
+ test_wait_threads();
+
+ test_print("--- Score : ");
+ test_printn(n);
+ test_println(" ctxswc/S");
+}
+
+ROMCONST struct testcase testbmk8 = {
+ "Benchmark, round robin context switching",
+ NULL,
+ NULL,
+ bmk8_execute
+};
+
+#if CH_USE_QUEUES || defined(__DOXYGEN__)
+/**
+ * @page test_benchmarks_009 I/O Queues throughput
+ *
+ * Description
+ * Four bytes are written and then read from an @p InputQueue into a continuous
+ * loop.
+ * The performance is calculated by measuring the number of iterations after
+ * a second of continuous operations.
+ */
+
+static void bmk9_execute(void) {
+ uint32_t n;
+ static uint8_t ib[16];
+ static InputQueue iq;
+
+ chIQInit(&iq, ib, sizeof(ib), NULL, NULL);
+ n = 0;
+ test_wait_tick();
+ test_start_timer(1000);
+ do {
+ chSysLock();
+ chIQPutI(&iq, 0);
+ chIQPutI(&iq, 1);
+ chIQPutI(&iq, 2);
+ chIQPutI(&iq, 3);
+ chSysUnlock();
+ (void)chIQGet(&iq);
+ (void)chIQGet(&iq);
+ (void)chIQGet(&iq);
+ (void)chIQGet(&iq);
+ n++;
+#if defined(SIMULATOR)
+ ChkIntSources();
+#endif
+ } while (!test_timer_done);
+ test_print("--- Score : ");
+ test_printn(n * 4);
+ test_println(" bytes/S");
+}
+
+ROMCONST struct testcase testbmk9 = {
+ "Benchmark, I/O Queues throughput",
+ NULL,
+ NULL,
+ bmk9_execute
+};
+#endif /* CH_USE_QUEUES */
+
+/**
+ * @page test_benchmarks_010 Virtual Timers set/reset performance
+ *
+ * Description
+ * A virtual timer is set and immediately reset into a continuous loop.
+ * The performance is calculated by measuring the number of iterations after
+ * a second of continuous operations.
+ */
+
+static void tmo(void *param) {(void)param;}
+
+static void bmk10_execute(void) {
+ static VirtualTimer vt1, vt2;
+ uint32_t n = 0;
+
+ test_wait_tick();
+ test_start_timer(1000);
+ do {
+ chSysLock();
+ chVTSetI(&vt1, 1, tmo, NULL);
+ chVTSetI(&vt2, 10000, tmo, NULL);
+ chVTResetI(&vt1);
+ chVTResetI(&vt2);
+ chSysUnlock();
+ n++;
+#if defined(SIMULATOR)
+ ChkIntSources();
+#endif
+ } while (!test_timer_done);
+ test_print("--- Score : ");
+ test_printn(n * 2);
+ test_println(" timers/S");
+}
+
+ROMCONST struct testcase testbmk10 = {
+ "Benchmark, virtual timers set/reset",
+ NULL,
+ NULL,
+ bmk10_execute
+};
+
+#if CH_USE_SEMAPHORES || defined(__DOXYGEN__)
+/**
+ * @page test_benchmarks_011 Semaphores wait/signal performance
+ *
+ * Description
+ * A counting semaphore is taken/released into a continuous loop, no Context
+ * Switch happens because the counter is always non negative.
+ * The performance is calculated by measuring the number of iterations after
+ * a second of continuous operations.
+ */
+
+static void bmk11_setup(void) {
+
+ chSemInit(&sem1, 1);
+}
+
+static void bmk11_execute(void) {
+ uint32_t n = 0;
+
+ test_wait_tick();
+ test_start_timer(1000);
+ do {
+ chSemWait(&sem1);
+ chSemSignal(&sem1);
+ chSemWait(&sem1);
+ chSemSignal(&sem1);
+ chSemWait(&sem1);
+ chSemSignal(&sem1);
+ chSemWait(&sem1);
+ chSemSignal(&sem1);
+ n++;
+#if defined(SIMULATOR)
+ ChkIntSources();
+#endif
+ } while (!test_timer_done);
+ test_print("--- Score : ");
+ test_printn(n * 4);
+ test_println(" wait+signal/S");
+}
+
+ROMCONST struct testcase testbmk11 = {
+ "Benchmark, semaphores wait/signal",
+ bmk11_setup,
+ NULL,
+ bmk11_execute
+};
+#endif /* CH_USE_SEMAPHORES */
+
+#if CH_USE_MUTEXES || defined(__DOXYGEN__)
+/**
+ * @page test_benchmarks_012 Mutexes lock/unlock performance
+ *
+ * Description
+ * A mutex is locked/unlocked into a continuous loop, no Context Switch happens
+ * because there are no other threads asking for the mutex.
+ * The performance is calculated by measuring the number of iterations after
+ * a second of continuous operations.
+ */
+
+static void bmk12_setup(void) {
+
+ chMtxInit(&mtx1);
+}
+
+static void bmk12_execute(void) {
+ uint32_t n = 0;
+
+ test_wait_tick();
+ test_start_timer(1000);
+ do {
+ chMtxLock(&mtx1);
+ chMtxUnlock();
+ chMtxLock(&mtx1);
+ chMtxUnlock();
+ chMtxLock(&mtx1);
+ chMtxUnlock();
+ chMtxLock(&mtx1);
+ chMtxUnlock();
+ n++;
+#if defined(SIMULATOR)
+ ChkIntSources();
+#endif
+ } while (!test_timer_done);
+ test_print("--- Score : ");
+ test_printn(n * 4);
+ test_println(" lock+unlock/S");
+}
+
+ROMCONST struct testcase testbmk12 = {
+ "Benchmark, mutexes lock/unlock",
+ bmk12_setup,
+ NULL,
+ bmk12_execute
+};
+#endif
+
+/**
+ * @page test_benchmarks_013 RAM Footprint
+ *
+ * Description
+ * The memory size of the various kernel objects is printed.
+ */
+
+static void bmk13_execute(void) {
+
+ test_print("--- System: ");
+ test_printn(sizeof(ReadyList) + sizeof(VTList) +
+ PORT_IDLE_THREAD_STACK_SIZE +
+ (sizeof(Thread) + sizeof(struct intctx) +
+ sizeof(struct extctx) +
+ PORT_INT_REQUIRED_STACK) * 2);
+ test_println(" bytes");
+ test_print("--- Thread: ");
+ test_printn(sizeof(Thread));
+ test_println(" bytes");
+ test_print("--- Timer : ");
+ test_printn(sizeof(VirtualTimer));
+ test_println(" bytes");
+#if CH_USE_SEMAPHORES || defined(__DOXYGEN__)
+ test_print("--- Semaph: ");
+ test_printn(sizeof(Semaphore));
+ test_println(" bytes");
+#endif
+#if CH_USE_EVENTS || defined(__DOXYGEN__)
+ test_print("--- EventS: ");
+ test_printn(sizeof(EventSource));
+ test_println(" bytes");
+ test_print("--- EventL: ");
+ test_printn(sizeof(EventListener));
+ test_println(" bytes");
+#endif
+#if CH_USE_MUTEXES || defined(__DOXYGEN__)
+ test_print("--- Mutex : ");
+ test_printn(sizeof(Mutex));
+ test_println(" bytes");
+#endif
+#if CH_USE_CONDVARS || defined(__DOXYGEN__)
+ test_print("--- CondV.: ");
+ test_printn(sizeof(CondVar));
+ test_println(" bytes");
+#endif
+#if CH_USE_QUEUES || defined(__DOXYGEN__)
+ test_print("--- Queue : ");
+ test_printn(sizeof(GenericQueue));
+ test_println(" bytes");
+#endif
+#if CH_USE_MAILBOXES || defined(__DOXYGEN__)
+ test_print("--- MailB.: ");
+ test_printn(sizeof(Mailbox));
+ test_println(" bytes");
+#endif
+}
+
+ROMCONST struct testcase testbmk13 = {
+ "Benchmark, RAM footprint",
+ NULL,
+ NULL,
+ bmk13_execute
+};
+
+/**
+ * @brief Test sequence for benchmarks.
+ */
+ROMCONST struct testcase * ROMCONST patternbmk[] = {
+#if !TEST_NO_BENCHMARKS
+ &testbmk1,
+ &testbmk2,
+ &testbmk3,
+ &testbmk4,
+ &testbmk5,
+ &testbmk6,
+ &testbmk7,
+ &testbmk8,
+#if CH_USE_QUEUES || defined(__DOXYGEN__)
+ &testbmk9,
+#endif
+ &testbmk10,
+#if CH_USE_SEMAPHORES || defined(__DOXYGEN__)
+ &testbmk11,
+#endif
+#if CH_USE_MUTEXES || defined(__DOXYGEN__)
+ &testbmk12,
+#endif
+ &testbmk13,
+#endif
+ NULL
+};
+
+#endif /* EFI_PERF_METRICS */
diff --git a/firmware/emulation/test/testbmk.h b/firmware/emulation/test/testbmk.h
new file mode 100644
index 0000000000..35fc6b7ede
--- /dev/null
+++ b/firmware/emulation/test/testbmk.h
@@ -0,0 +1,22 @@
+/*
+ ChibiOS/RT - Copyright (C) 2006-2013 Giovanni Di Sirio
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+#ifndef _TESTBMK_H_
+#define _TESTBMK_H_
+
+extern ROMCONST struct testcase * ROMCONST patternbmk[];
+
+#endif /* _TESTBMK_H_ */
diff --git a/firmware/emulation/trigger_emulator.cpp b/firmware/emulation/trigger_emulator.cpp
new file mode 100644
index 0000000000..0abe5ed07a
--- /dev/null
+++ b/firmware/emulation/trigger_emulator.cpp
@@ -0,0 +1,95 @@
+/**
+ * @file trigger_emulator.cpp
+ * @brief Position sensor(s) emulation code
+ *
+ * This file is mostly about initialization, the configuration is
+ * provided by the configureShaftPositionEmulatorShape method
+ *
+ * @date Dec 9, 2012
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "trigger_emulator.h"
+#include "eficonsole.h"
+#include "main_trigger_callback.h"
+#include "datalogging.h"
+#include "engine_configuration.h"
+#if EFI_PROD_CODE
+#include "pwm_generator.h"
+#include "pin_repository.h"
+#endif
+#include "io_pins.h"
+#include "trigger_emulator_algo.h"
+#include "trigger_central.h"
+
+extern engine_configuration_s *engineConfiguration;
+extern board_configuration_s *boardConfiguration;
+
+extern PwmConfig triggerSignal;
+
+TriggerEmulatorHelper::TriggerEmulatorHelper() {
+ primaryWheelState = false;
+ secondaryWheelState = false;
+ thirdWheelState = false;
+}
+
+void TriggerEmulatorHelper::handleEmulatorCallback(PwmConfig *state, int stateIndex) {
+ int newPrimaryWheelState = state->multiWave.waves[0].pinStates[stateIndex];
+ int newSecondaryWheelState = state->multiWave.waves[1].pinStates[stateIndex];
+ int new3rdWheelState = state->multiWave.waves[2].pinStates[stateIndex];
+
+ if (primaryWheelState != newPrimaryWheelState) {
+ primaryWheelState = newPrimaryWheelState;
+ hwHandleShaftSignal(primaryWheelState ? SHAFT_PRIMARY_UP : SHAFT_PRIMARY_DOWN);
+ }
+
+ if (secondaryWheelState != newSecondaryWheelState) {
+ secondaryWheelState = newSecondaryWheelState;
+ hwHandleShaftSignal(secondaryWheelState ? SHAFT_SECONDARY_UP : SHAFT_SECONDARY_DOWN);
+ }
+
+ if (thirdWheelState != new3rdWheelState) {
+ thirdWheelState = new3rdWheelState;
+ hwHandleShaftSignal(thirdWheelState ? SHAFT_3RD_UP : SHAFT_3RD_DOWN);
+ }
+
+ // print("hello %d\r\n", chTimeNow());
+}
+
+static TriggerEmulatorHelper helper;
+
+#if EFI_EMULATE_POSITION_SENSORS || defined(__DOXYGEN__)
+
+static void emulatorApplyPinState(PwmConfig *state, int stateIndex) {
+ if (engineConfiguration->directSelfStimulation) {
+ helper.handleEmulatorCallback(state, stateIndex);
+ }
+
+ applyPinState(state, stateIndex);
+}
+#endif /* EFI_EMULATE_POSITION_SENSORS */
+
+void initTriggerEmulator(void) {
+#if EFI_EMULATE_POSITION_SENSORS || defined(__DOXYGEN__)
+ print("Emulating %s\r\n", getConfigurationName(engineConfiguration));
+
+ triggerSignal.outputPins[0] = TRIGGER_EMULATOR_PRIMARY;
+ triggerSignal.outputPins[1] = TRIGGER_EMULATOR_SECONDARY;
+ triggerSignal.outputPins[2] = TRIGGER_EMULATOR_3RD;
+
+ // todo: refactor, make this a loop
+ outputPinRegisterExt2("distributor ch1", triggerSignal.outputPins[0], boardConfiguration->triggerSimulatorPins[0],
+ &boardConfiguration->triggerSimulatorPinModes[0]);
+
+ outputPinRegisterExt2("distributor ch2", triggerSignal.outputPins[1], boardConfiguration->triggerSimulatorPins[1],
+ &boardConfiguration->triggerSimulatorPinModes[1]);
+
+ outputPinRegisterExt2("distributor ch3", triggerSignal.outputPins[2], boardConfiguration->triggerSimulatorPins[2],
+ &boardConfiguration->triggerSimulatorPinModes[2]);
+
+
+ initTriggerEmulatorLogic(emulatorApplyPinState);
+#else
+ print("No position sensor(s) emulation\r\n");
+#endif /* EFI_EMULATE_POSITION_SENSORS */
+}
diff --git a/firmware/emulation/trigger_emulator.h b/firmware/emulation/trigger_emulator.h
new file mode 100644
index 0000000000..8c7ed5b11c
--- /dev/null
+++ b/firmware/emulation/trigger_emulator.h
@@ -0,0 +1,34 @@
+/**
+ * @file trigger_emulator.h
+ * @brief Position sensor(s) emulation header
+ *
+ * @date Dec 9, 2012
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef DIST_EMULATOR_H_
+#define DIST_EMULATOR_H_
+
+#include "main.h"
+
+#include "trigger_structure.h"
+#include "engine_configuration.h"
+#include "pwm_generator_logic.h"
+
+class TriggerEmulatorHelper {
+public:
+ bool primaryWheelState;
+ bool secondaryWheelState;
+ bool thirdWheelState;
+
+ TriggerEmulatorHelper();
+
+ void handleEmulatorCallback(PwmConfig *state, int stateIndex);
+
+};
+
+
+void initTriggerEmulator(void);
+void setTriggerEmulatorRPM(int value);
+
+#endif /* DIST_EMULATOR_H_ */
diff --git a/firmware/emulation/wave_analyzer.cpp b/firmware/emulation/wave_analyzer.cpp
new file mode 100644
index 0000000000..a1c35998f5
--- /dev/null
+++ b/firmware/emulation/wave_analyzer.cpp
@@ -0,0 +1,262 @@
+/**
+ * @file wave_analyzer.cpp
+ * @brief Initialization of Input Capture pins used for dev console sniffer
+ *
+ * This file is responsible for sniffing of external digital signals and registering
+ * these digital events in WaveChart used by the Sniffer tab of Dev Console.
+ *
+ *
+ * @date Jan 7, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+#include "wave_analyzer.h"
+#include "eficonsole.h"
+#include "data_buffer.h"
+#include "pin_repository.h"
+#include "engine_state.h"
+#include "signal_executor.h"
+#include "engine_configuration.h"
+#include "trigger_central.h"
+#include "rfiutil.h"
+#include "engine_math.h"
+#include "engine.h"
+
+extern Engine engine;
+
+#define CHART_RESET_DELAY 1
+
+extern board_configuration_s *boardConfiguration;
+
+extern engine_configuration_s *engineConfiguration;
+
+static volatile uint32_t ckpPeriodUs; // difference between current crank signal and previous crank signal
+static volatile uint64_t previousCrankSignalStart = 0;
+
+#define MAX_ICU_COUNT 5
+
+static int waveReaderCount = 0;
+static WaveReader readers[MAX_ICU_COUNT];
+extern WaveChart waveChart;
+
+static Logging logger;
+
+static void ensureInitialized(WaveReader *reader) {
+ efiAssertVoid(reader->hw.started, "wave analyzer NOT INITIALIZED");
+}
+
+#if EFI_WAVE_ANALYZER || defined(__DOXYGEN__)
+
+static void waAnaWidthCallback(WaveReader *reader) {
+ uint64_t nowUs = getTimeNowUs();
+ reader->eventCounter++;
+ reader->lastActivityTimeUs = nowUs;
+ addWaveChartEvent(reader->name, WC_UP, "");
+
+ uint32_t width = nowUs - reader->periodEventTimeUs;
+ reader->last_wave_low_widthUs = width;
+
+ reader->signalPeriodUs = nowUs - reader->widthEventTimeUs;
+ reader->widthEventTimeUs = nowUs;
+}
+
+static void waIcuPeriodCallback(WaveReader *reader) {
+ uint64_t nowUs = getTimeNowUs();
+ reader->eventCounter++;
+ reader->lastActivityTimeUs = nowUs;
+ addWaveChartEvent(reader->name, WC_DOWN, "");
+
+ uint64_t width = nowUs - reader->widthEventTimeUs;
+ reader->last_wave_high_widthUs = width;
+
+ reader->periodEventTimeUs = nowUs;
+
+ //scheduleSimpleMsg(&irqLogging, "co", reader->chart.counter);
+
+// dbAdd(&wavePeriodTime, now);
+
+ uint32_t period = ckpPeriodUs; // local copy of volatile variable
+
+ uint32_t offset = nowUs - previousCrankSignalStart;
+
+ if (offset > period / 2) {
+ /**
+ * here we calculate the offset in relation to future cranking signal
+ */
+ offset -= period;
+ }
+ reader->waveOffsetUs = offset;
+
+ // we want only the low phase length, so we subsctract high width from period
+// processSignal(1, &dataPinReader, last_period - last_adc_response_width);
+}
+
+static void setWaveModeSilent(int index, int mode) {
+ WaveReader *reader = &readers[index];
+
+ setWaveReaderMode(&reader->hw, mode);
+}
+
+//void setWaveMode(int index, int mode) {
+// setWaveModeSilent(index, mode);
+// print("wavemode%d:%d\r\n", index, mode);
+//}
+
+int getWaveMode(int index) {
+ WaveReader *reader = &readers[index];
+ ensureInitialized(reader);
+ return reader->hw.activeMode;
+}
+
+int getEventCounter(int index) {
+ WaveReader *reader = &readers[index];
+ ensureInitialized(reader);
+ return reader->eventCounter;
+}
+
+static void initWave(const char *name, int index, ICUDriver *driver, ioportid_t port, ioportmask_t pin, int mode) {
+ waveReaderCount++;
+ efiAssertVoid(index < MAX_ICU_COUNT, "too many ICUs");
+ WaveReader *reader = &readers[index];
+ WaveReaderHw *hw = &reader->hw;
+
+ reader->name = name;
+
+ registerCallback(&hw->widthListeners, (IntListener) waAnaWidthCallback, (void*)reader);
+
+ registerCallback(&hw->periodListeners, (IntListener) waIcuPeriodCallback, (void*)reader);
+
+ initWaveAnalyzerDriver(hw, driver, port, pin);
+
+ print("wave%d input on %s%d\r\n", index, portname(reader->hw.port), reader->hw.pin);
+ setWaveReaderMode(hw, mode);
+}
+#endif
+
+//int getCrankStart() {
+// return previousCrankSignalStart;
+//}
+
+//static int getCrankPeriod(void) {
+// return ckpPeriod;
+//}
+
+static void onWaveShaftSignal(trigger_event_e ckpSignalType, int index, void *arg) {
+ if (index != 0) {
+ return;
+ }
+ uint64_t nowUs = getTimeNowUs();
+ ckpPeriodUs = nowUs - previousCrankSignalStart;
+ previousCrankSignalStart = nowUs;
+}
+
+static THD_WORKING_AREA(waThreadStack, UTILITY_THREAD_STACK_SIZE);
+
+//static Logging logger;
+
+static msg_t waThread(void *arg) {
+ chRegSetThreadName("Wave Analyzer");
+#if EFI_WAVE_CHART
+ while (TRUE) {
+ chThdSleepSeconds(CHART_RESET_DELAY);
+
+ publishChartIfFull(&waveChart);
+ }
+#endif /* EFI_WAVE_CHART */
+#if defined __GNUC__
+ return -1;
+#endif
+}
+
+uint32_t getWaveLowWidth(int index) {
+ WaveReader *reader = &readers[index];
+ ensureInitialized(reader);
+ return reader->last_wave_low_widthUs;
+}
+
+float getWaveHighWidthMs(int index) {
+ WaveReader *reader = &readers[index];
+ ensureInitialized(reader);
+ if (getTimeNowUs() - reader->lastActivityTimeUs > 4 * US_PER_SECOND) {
+ return 0.0f; // dwell time has expired
+ }
+ return reader->last_wave_high_widthUs / 1000.0f;
+}
+
+uint64_t getWaveOffset(int index) {
+ WaveReader *reader = &readers[index];
+ ensureInitialized(reader);
+ return reader->waveOffsetUs;
+}
+
+float getSignalPeriodMs(int index) {
+ WaveReader *reader = &readers[index];
+ ensureInitialized(reader);
+ return reader->signalPeriodUs / 1000.0f;
+}
+
+uint64_t getWidthEventTime(int index) {
+ WaveReader *reader = &readers[index];
+ ensureInitialized(reader);
+ return reader->widthEventTimeUs;
+}
+
+uint64_t getPeriodEventTime(int index) {
+ WaveReader *reader = &readers[index];
+ ensureInitialized(reader);
+ return reader->periodEventTimeUs;
+}
+
+int waveBufferReported = 0;
+
+static void reportWave(Logging *logging, int index) {
+// int counter = getEventCounter(index);
+// debugInt2(logging, "ev", index, counter);
+
+ float dwellMs = getWaveHighWidthMs(index);
+ float periodMs = getSignalPeriodMs(index);
+
+ appendPrintf(logging, "duty%d%s", index, DELIMETER);
+ appendFloat(logging, 100.0f * dwellMs / periodMs, 2);
+ appendPrintf(logging, "%s", DELIMETER);
+
+ appendPrintf(logging, "dwell%d%s", index, DELIMETER);
+ appendFloat(logging, dwellMs, 2);
+ appendPrintf(logging, "%s", DELIMETER);
+
+ appendPrintf(logging, "period%d%s", index, DELIMETER);
+ appendFloat(logging, periodMs, 2);
+ appendPrintf(logging, "%s", DELIMETER);
+
+ int offset = getWaveOffset(index);
+ int crank = getOneDegreeTimeMs(getRpm()) * 360;
+
+ appendPrintf(logging, "advance%d%s", index, DELIMETER);
+ appendFloat(logging, 90.0 * offset / crank, 3);
+ appendPrintf(logging, "%s", DELIMETER);
+}
+
+void printWave(Logging *logging) {
+ reportWave(logging, 0);
+ reportWave(logging, 1);
+}
+
+void initWaveAnalyzer(void) {
+#if EFI_WAVE_ANALYZER || defined(__DOXYGEN__)
+ initLogging(&logger, "wave");
+
+ initWave(WA_CHANNEL_1, 0, getInputCaptureDriver(boardConfiguration->logicAnalyzerPins[0]), getHwPort(boardConfiguration->logicAnalyzerPins[0]), getHwPin(boardConfiguration->logicAnalyzerPins[0]), 1);
+ initWave(WA_CHANNEL_2, 1, getInputCaptureDriver(boardConfiguration->logicAnalyzerPins[1]), getHwPort(boardConfiguration->logicAnalyzerPins[1]), getHwPin(boardConfiguration->logicAnalyzerPins[1]), 1);
+ // initWave("input0 C6", 2, &WAVE_TIMER, WAVE_INPUT_PORT, WAVE_INPUT_PIN, 0);
+
+ addTriggerEventListener(&onWaveShaftSignal, "wave analyzer", (void*)NULL);
+
+ addConsoleActionII("wm", setWaveModeSilent);
+
+ chThdCreateStatic(waThreadStack, sizeof(waThreadStack), NORMALPRIO, waThread, (void*)NULL);
+
+#else
+ print("wave disabled\r\n");
+#endif
+}
diff --git a/firmware/emulation/wave_analyzer.h b/firmware/emulation/wave_analyzer.h
new file mode 100644
index 0000000000..23aaeef23d
--- /dev/null
+++ b/firmware/emulation/wave_analyzer.h
@@ -0,0 +1,66 @@
+/**
+ * @file wave_analyzer.h
+ *
+ *
+ * @date Jan 7, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef WAVE_ANALYZER_H_
+#define WAVE_ANALYZER_H_
+
+#include "main.h"
+#include "datalogging.h"
+
+#include "wave_analyzer_hw.h"
+#include "wave_chart.h"
+
+#define WA_CHANNEL_1 "input1"
+#define WA_CHANNEL_2 "input2"
+
+typedef struct {
+ WaveReaderHw hw;
+ const char *name;
+ volatile int eventCounter;
+
+ volatile uint64_t lastActivityTimeUs; // timestamp in microseconds ticks
+ volatile uint64_t periodEventTimeUs; // time of signal fall in microseconds
+ volatile uint64_t widthEventTimeUs; // time of signal rise in microseconds
+
+ volatile uint32_t signalPeriodUs; // period between two signal rises in microseconds
+
+ volatile uint64_t waveOffsetUs; // offset from CKP signal in systimer ticks
+ volatile uint32_t last_wave_low_widthUs; // time period in systimer ticks
+ volatile uint64_t last_wave_high_widthUs; // time period in systimer ticks
+} WaveReader;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void initWaveAnalyzer(void);
+void pokeWaveInfo(void);
+void reportWaveInfo(void);
+uint32_t getWaveLowWidth(int index);
+float getWaveHighWidthMs(int index);
+uint64_t getWaveOffset(int index);
+
+int getWaveMode(int index);
+
+int getEventCounter(int index);
+
+float getSignalPeriodMs(int index);
+uint64_t getWidthEventTime(int index);
+uint64_t getPeriodEventTime(int index);
+
+//int getCrankStart();
+//int getCrankPeriod();
+
+void printWave(Logging *logging);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* WAVE_ANALYZER_H_ */
diff --git a/firmware/exception.txt b/firmware/exception.txt
new file mode 100644
index 0000000000..d74e8aeadc
--- /dev/null
+++ b/firmware/exception.txt
@@ -0,0 +1,48 @@
+ GPL Exception Text
+
+ In addition, as a special exception, the copyright holder of ChibiOS/RT,
+gives You the additional right to link the unmodified code of this Program with
+code not covered under the GNU General Public License ("Non-GPL Code") and to
+distribute linked combinations including the two, subject to the limitations
+in this paragraph.
+
+ 1. Non-GPL Code permitted under this exception must only link to the
+ unmodified code of this Program through those well defined interfaces
+ identified as "Approved Interfaces".
+ 2. Every copy of the combined work is accompanied by a written statement
+ that details to the recipient the version of ChibiOS/RT used and an
+ offer by yourself to provide the ChibiOS/RT source code should the
+ recipient request it.
+ 3. The combined work is not itself an RTOS, scheduler, kernel or related
+ product.
+ 4. The combined work is not itself a binary library intended for linking
+ into other software applications.
+
+ The Approved Interfaces
+
+ 1. The files of Non-GPL Code may include the unmodified ChibiOS/RT
+ distribution header files contained under:
+ ./os/kernel/include
+ ./os/ports
+ ./os/hal/include
+ without causing the resulting work to be covered by the GNU General
+ Public License.
+ 2. The files of Non-GPL Code may link to the unmodified ChibiOS/RT
+ distribution files contained under:
+ ./os/kernel/src
+ ./os/ports
+ ./os/hal/src
+ without causing the resulting work to be covered by the GNU General
+ Public License.
+ 3. The files of Non-GPL Code may link to, or include, the modified or
+ unmodified ChibiOS/RT distribution files contained under:
+ ./os/kernel/templates
+ without causing the resulting work to be covered by the GNU General
+ Public License.
+
+ Only the copyright holder of ChibiOS/RT may make changes or additions to the
+list of Approved Interfaces.
+
+ You must obey the GNU General Public License in all respects for all of the
+Program code and other code used in conjunction with the Program except the
+Non-GPL Code covered by this exception.
diff --git a/firmware/ext/diskio.h b/firmware/ext/diskio.h
new file mode 100644
index 0000000000..9573e6ecc7
--- /dev/null
+++ b/firmware/ext/diskio.h
@@ -0,0 +1,78 @@
+/*-----------------------------------------------------------------------
+/ Low level disk interface modlue include file
+/-----------------------------------------------------------------------*/
+
+#ifndef _DISKIO
+
+#define _READONLY 0 /* 1: Remove write functions */
+#define _USE_IOCTL 1 /* 1: Use disk_ioctl fucntion */
+
+#include "integer.h"
+
+
+/* Status of Disk Functions */
+typedef BYTE DSTATUS;
+
+/* Results of Disk Functions */
+typedef enum {
+ RES_OK = 0, /* 0: Successful */
+ RES_ERROR, /* 1: R/W Error */
+ RES_WRPRT, /* 2: Write Protected */
+ RES_NOTRDY, /* 3: Not Ready */
+ RES_PARERR /* 4: Invalid Parameter */
+} DRESULT;
+
+
+/*---------------------------------------*/
+/* Prototypes for disk control functions */
+
+int assign_drives (int, int);
+DSTATUS disk_initialize (BYTE);
+DSTATUS disk_status (BYTE);
+DRESULT disk_read (BYTE, BYTE*, DWORD, BYTE);
+#if _READONLY == 0
+DRESULT disk_write (BYTE, const BYTE*, DWORD, BYTE);
+#endif
+DRESULT disk_ioctl (BYTE, BYTE, void*);
+
+
+
+/* Disk Status Bits (DSTATUS) */
+
+#define STA_NOINIT 0x01 /* Drive not initialized */
+#define STA_NODISK 0x02 /* No medium in the drive */
+#define STA_PROTECT 0x04 /* Write protected */
+
+
+/* Command code for disk_ioctrl fucntion */
+
+/* Generic command (defined for FatFs) */
+#define CTRL_SYNC 0 /* Flush disk cache (for write functions) */
+#define GET_SECTOR_COUNT 1 /* Get media size (for only f_mkfs()) */
+#define GET_SECTOR_SIZE 2 /* Get sector size (for multiple sector size (_MAX_SS >= 1024)) */
+#define GET_BLOCK_SIZE 3 /* Get erase block size (for only f_mkfs()) */
+#define CTRL_ERASE_SECTOR 4 /* Force erased a block of sectors (for only _USE_ERASE) */
+
+/* Generic command */
+#define CTRL_POWER 5 /* Get/Set power status */
+#define CTRL_LOCK 6 /* Lock/Unlock media removal */
+#define CTRL_EJECT 7 /* Eject media */
+
+/* MMC/SDC specific ioctl command */
+#define MMC_GET_TYPE 10 /* Get card type */
+#define MMC_GET_CSD 11 /* Get CSD */
+#define MMC_GET_CID 12 /* Get CID */
+#define MMC_GET_OCR 13 /* Get OCR */
+#define MMC_GET_SDSTAT 14 /* Get SD status */
+
+/* ATA/CF specific ioctl command */
+#define ATA_GET_REV 20 /* Get F/W revision */
+#define ATA_GET_MODEL 21 /* Get model name */
+#define ATA_GET_SN 22 /* Get serial number */
+
+/* NAND specific ioctl command */
+#define NAND_FORMAT 30 /* Create physical format */
+
+
+#define _DISKIO
+#endif
diff --git a/firmware/ext/ext.mk b/firmware/ext/ext.mk
new file mode 100644
index 0000000000..d934bbe50f
--- /dev/null
+++ b/firmware/ext/ext.mk
@@ -0,0 +1 @@
+#EXTSRC = ext/ccsbcs.c \
diff --git a/firmware/ext/ff.h b/firmware/ext/ff.h
new file mode 100644
index 0000000000..b1ff46527c
--- /dev/null
+++ b/firmware/ext/ff.h
@@ -0,0 +1,337 @@
+/*---------------------------------------------------------------------------/
+/ FatFs - FAT file system module include file R0.09 (C)ChaN, 2011
+/----------------------------------------------------------------------------/
+/ FatFs module is a generic FAT file system module for small embedded systems.
+/ This is a free software that opened for education, research and commercial
+/ developments under license policy of following trems.
+/
+/ Copyright (C) 2011, ChaN, all right reserved.
+/
+/ * The FatFs module is a free software and there is NO WARRANTY.
+/ * No restriction on use. You can use, modify and redistribute it for
+/ personal, non-profit or commercial product UNDER YOUR RESPONSIBILITY.
+/ * Redistributions of source code must retain the above copyright notice.
+/
+/----------------------------------------------------------------------------*/
+
+#ifndef _FATFS
+#define _FATFS 6502 /* Revision ID */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "integer.h" /* Basic integer types */
+#include "ffconf.h" /* FatFs configuration options */
+
+#if _FATFS != _FFCONF
+#error Wrong configuration file (ffconf.h).
+#endif
+
+
+
+/* Definitions of volume management */
+
+#if _MULTI_PARTITION /* Multiple partition configuration */
+typedef struct {
+ BYTE pd; /* Physical drive number */
+ BYTE pt; /* Partition: 0:Auto detect, 1-4:Forced partition) */
+} PARTITION;
+extern PARTITION VolToPart[]; /* Volume - Partition resolution table */
+#define LD2PD(vol) (VolToPart[vol].pd) /* Get physical drive number */
+#define LD2PT(vol) (VolToPart[vol].pt) /* Get partition index */
+
+#else /* Single partition configuration */
+#define LD2PD(vol) (vol) /* Each logical drive is bound to the same physical drive number */
+#define LD2PT(vol) 0 /* Always mounts the 1st partition or in SFD */
+
+#endif
+
+
+
+/* Type of path name strings on FatFs API */
+
+#if _LFN_UNICODE /* Unicode string */
+#if !_USE_LFN
+#error _LFN_UNICODE must be 0 in non-LFN cfg.
+#endif
+#ifndef _INC_TCHAR
+typedef WCHAR TCHAR;
+#define _T(x) L ## x
+#define _TEXT(x) L ## x
+#endif
+
+#else /* ANSI/OEM string */
+#ifndef _INC_TCHAR
+typedef char TCHAR;
+#define _T(x) x
+#define _TEXT(x) x
+#endif
+
+#endif
+
+
+
+/* File system object structure (FATFS) */
+
+typedef struct {
+ BYTE fs_type; /* FAT sub-type (0:Not mounted) */
+ BYTE drv; /* Physical drive number */
+ BYTE csize; /* Sectors per cluster (1,2,4...128) */
+ BYTE n_fats; /* Number of FAT copies (1,2) */
+ BYTE wflag; /* win[] dirty flag (1:must be written back) */
+ BYTE fsi_flag; /* fsinfo dirty flag (1:must be written back) */
+ WORD id; /* File system mount ID */
+ WORD n_rootdir; /* Number of root directory entries (FAT12/16) */
+#if _MAX_SS != 512
+ WORD ssize; /* Bytes per sector (512, 1024, 2048 or 4096) */
+#endif
+#if _FS_REENTRANT
+ _SYNC_t sobj; /* Identifier of sync object */
+#endif
+#if !_FS_READONLY
+ DWORD last_clust; /* Last allocated cluster */
+ DWORD free_clust; /* Number of free clusters */
+ DWORD fsi_sector; /* fsinfo sector (FAT32) */
+#endif
+#if _FS_RPATH
+ DWORD cdir; /* Current directory start cluster (0:root) */
+#endif
+ DWORD n_fatent; /* Number of FAT entries (= number of clusters + 2) */
+ DWORD fsize; /* Sectors per FAT */
+ DWORD fatbase; /* FAT start sector */
+ DWORD dirbase; /* Root directory start sector (FAT32:Cluster#) */
+ DWORD database; /* Data start sector */
+ DWORD winsect; /* Current sector appearing in the win[] */
+ BYTE win[_MAX_SS]; /* Disk access window for Directory, FAT (and Data on tiny cfg) */
+} FATFS;
+
+
+
+/* File object structure (FIL) */
+
+typedef struct {
+ FATFS* fs; /* Pointer to the owner file system object */
+ WORD id; /* Owner file system mount ID */
+ BYTE flag; /* File status flags */
+ BYTE pad1;
+ DWORD fptr; /* File read/write pointer (0 on file open) */
+ DWORD fsize; /* File size */
+ DWORD sclust; /* File start cluster (0 when fsize==0) */
+ DWORD clust; /* Current cluster */
+ DWORD dsect; /* Current data sector */
+#if !_FS_READONLY
+ DWORD dir_sect; /* Sector containing the directory entry */
+ BYTE* dir_ptr; /* Ponter to the directory entry in the window */
+#endif
+#if _USE_FASTSEEK
+ DWORD* cltbl; /* Pointer to the cluster link map table (null on file open) */
+#endif
+#if _FS_SHARE
+ UINT lockid; /* File lock ID (index of file semaphore table) */
+#endif
+#if !_FS_TINY
+ BYTE buf[_MAX_SS]; /* File data read/write buffer */
+#endif
+} FIL;
+
+
+
+/* Directory object structure (DIR) */
+
+typedef struct {
+ FATFS* fs; /* Pointer to the owner file system object */
+ WORD id; /* Owner file system mount ID */
+ WORD index; /* Current read/write index number */
+ DWORD sclust; /* Table start cluster (0:Root dir) */
+ DWORD clust; /* Current cluster */
+ DWORD sect; /* Current sector */
+ BYTE* dir; /* Pointer to the current SFN entry in the win[] */
+ BYTE* fn; /* Pointer to the SFN (in/out) {file[8],ext[3],status[1]} */
+#if _USE_LFN
+ WCHAR* lfn; /* Pointer to the LFN working buffer */
+ WORD lfn_idx; /* Last matched LFN index number (0xFFFF:No LFN) */
+#endif
+} DIR;
+
+
+
+/* File status structure (FILINFO) */
+
+typedef struct {
+ DWORD fsize; /* File size */
+ WORD fdate; /* Last modified date */
+ WORD ftime; /* Last modified time */
+ BYTE fattrib; /* Attribute */
+ TCHAR fname[13]; /* Short file name (8.3 format) */
+#if _USE_LFN
+ TCHAR* lfname; /* Pointer to the LFN buffer */
+ UINT lfsize; /* Size of LFN buffer in TCHAR */
+#endif
+} FILINFO;
+
+
+
+/* File function return code (FRESULT) */
+
+typedef enum {
+ FR_OK = 0, /* (0) Succeeded */
+ FR_DISK_ERR, /* (1) A hard error occured in the low level disk I/O layer */
+ FR_INT_ERR, /* (2) Assertion failed */
+ FR_NOT_READY, /* (3) The physical drive cannot work */
+ FR_NO_FILE, /* (4) Could not find the file */
+ FR_NO_PATH, /* (5) Could not find the path */
+ FR_INVALID_NAME, /* (6) The path name format is invalid */
+ FR_DENIED, /* (7) Acces denied due to prohibited access or directory full */
+ FR_EXIST, /* (8) Acces denied due to prohibited access */
+ FR_INVALID_OBJECT, /* (9) The file/directory object is invalid */
+ FR_WRITE_PROTECTED, /* (10) The physical drive is write protected */
+ FR_INVALID_DRIVE, /* (11) The logical drive number is invalid */
+ FR_NOT_ENABLED, /* (12) The volume has no work area */
+ FR_NO_FILESYSTEM, /* (13) There is no valid FAT volume */
+ FR_MKFS_ABORTED, /* (14) The f_mkfs() aborted due to any parameter error */
+ FR_TIMEOUT, /* (15) Could not get a grant to access the volume within defined period */
+ FR_LOCKED, /* (16) The operation is rejected according to the file shareing policy */
+ FR_NOT_ENOUGH_CORE, /* (17) LFN working buffer could not be allocated */
+ FR_TOO_MANY_OPEN_FILES, /* (18) Number of open files > _FS_SHARE */
+ FR_INVALID_PARAMETER /* (19) Given parameter is invalid */
+} FRESULT;
+
+
+
+/*--------------------------------------------------------------*/
+/* FatFs module application interface */
+
+FRESULT f_mount (BYTE, FATFS*); /* Mount/Unmount a logical drive */
+FRESULT f_open (FIL*, const TCHAR*, BYTE); /* Open or create a file */
+FRESULT f_read (FIL*, void*, UINT, UINT*); /* Read data from a file */
+FRESULT f_lseek (FIL*, DWORD); /* Move file pointer of a file object */
+FRESULT f_close (FIL*); /* Close an open file object */
+FRESULT f_opendir (DIR*, const TCHAR*); /* Open an existing directory */
+FRESULT f_readdir (DIR*, FILINFO*); /* Read a directory item */
+FRESULT f_stat (const TCHAR*, FILINFO*); /* Get file status */
+FRESULT f_write (FIL*, const void*, UINT, UINT*); /* Write data to a file */
+FRESULT f_getfree (const TCHAR*, DWORD*, FATFS**); /* Get number of free clusters on the drive */
+FRESULT f_truncate (FIL*); /* Truncate file */
+FRESULT f_sync (FIL*); /* Flush cached data of a writing file */
+FRESULT f_unlink (const TCHAR*); /* Delete an existing file or directory */
+FRESULT f_mkdir (const TCHAR*); /* Create a new directory */
+FRESULT f_chmod (const TCHAR*, BYTE, BYTE); /* Change attriburte of the file/dir */
+FRESULT f_utime (const TCHAR*, const FILINFO*); /* Change timestamp of the file/dir */
+FRESULT f_rename (const TCHAR*, const TCHAR*); /* Rename/Move a file or directory */
+FRESULT f_chdrive (BYTE); /* Change current drive */
+FRESULT f_chdir (const TCHAR*); /* Change current directory */
+FRESULT f_getcwd (TCHAR*, UINT); /* Get current directory */
+FRESULT f_forward (FIL*, UINT(*)(const BYTE*,UINT), UINT, UINT*); /* Forward data to the stream */
+FRESULT f_mkfs (BYTE, BYTE, UINT); /* Create a file system on the drive */
+FRESULT f_fdisk (BYTE, const DWORD[], void*); /* Divide a physical drive into some partitions */
+int f_putc (TCHAR, FIL*); /* Put a character to the file */
+int f_puts (const TCHAR*, FIL*); /* Put a string to the file */
+int f_printf (FIL*, const TCHAR*, ...); /* Put a formatted string to the file */
+TCHAR* f_gets (TCHAR*, int, FIL*); /* Get a string from the file */
+
+#define f_eof(fp) (((fp)->fptr == (fp)->fsize) ? 1 : 0)
+#define f_error(fp) (((fp)->flag & FA__ERROR) ? 1 : 0)
+#define f_tell(fp) ((fp)->fptr)
+#define f_size(fp) ((fp)->fsize)
+
+#ifndef EOF
+#define EOF (-1)
+#endif
+
+
+
+
+/*--------------------------------------------------------------*/
+/* Additional user defined functions */
+
+/* RTC function */
+#if !_FS_READONLY
+DWORD get_fattime (void);
+#endif
+
+/* Unicode support functions */
+#if _USE_LFN /* Unicode - OEM code conversion */
+WCHAR ff_convert (WCHAR, UINT); /* OEM-Unicode bidirectional conversion */
+WCHAR ff_wtoupper (WCHAR); /* Unicode upper-case conversion */
+#if _USE_LFN == 3 /* Memory functions */
+void* ff_memalloc (UINT); /* Allocate memory block */
+void ff_memfree (void*); /* Free memory block */
+#endif
+#endif
+
+/* Sync functions */
+#if _FS_REENTRANT
+int ff_cre_syncobj (BYTE, _SYNC_t*);/* Create a sync object */
+int ff_req_grant (_SYNC_t); /* Lock sync object */
+void ff_rel_grant (_SYNC_t); /* Unlock sync object */
+int ff_del_syncobj (_SYNC_t); /* Delete a sync object */
+#endif
+
+
+
+
+/*--------------------------------------------------------------*/
+/* Flags and offset address */
+
+
+/* File access control and file status flags (FIL.flag) */
+
+#define FA_READ 0x01
+#define FA_OPEN_EXISTING 0x00
+#define FA__ERROR 0x80
+
+#if !_FS_READONLY
+#define FA_WRITE 0x02
+#define FA_CREATE_NEW 0x04
+#define FA_CREATE_ALWAYS 0x08
+#define FA_OPEN_ALWAYS 0x10
+#define FA__WRITTEN 0x20
+#define FA__DIRTY 0x40
+#endif
+
+
+/* FAT sub type (FATFS.fs_type) */
+
+#define FS_FAT12 1
+#define FS_FAT16 2
+#define FS_FAT32 3
+
+
+/* File attribute bits for directory entry */
+
+#define AM_RDO 0x01 /* Read only */
+#define AM_HID 0x02 /* Hidden */
+#define AM_SYS 0x04 /* System */
+#define AM_VOL 0x08 /* Volume label */
+#define AM_LFN 0x0F /* LFN entry */
+#define AM_DIR 0x10 /* Directory */
+#define AM_ARC 0x20 /* Archive */
+#define AM_MASK 0x3F /* Mask of defined bits */
+
+
+/* Fast seek feature */
+#define CREATE_LINKMAP 0xFFFFFFFF
+
+
+
+/*--------------------------------*/
+/* Multi-byte word access macros */
+
+#if _WORD_ACCESS == 1 /* Enable word access to the FAT structure */
+#define LD_WORD(ptr) (WORD)(*(WORD*)(BYTE*)(ptr))
+#define LD_DWORD(ptr) (DWORD)(*(DWORD*)(BYTE*)(ptr))
+#define ST_WORD(ptr,val) *(WORD*)(BYTE*)(ptr)=(WORD)(val)
+#define ST_DWORD(ptr,val) *(DWORD*)(BYTE*)(ptr)=(DWORD)(val)
+#else /* Use byte-by-byte access to the FAT structure */
+#define LD_WORD(ptr) (WORD)(((WORD)*((BYTE*)(ptr)+1)<<8)|(WORD)*(BYTE*)(ptr))
+#define LD_DWORD(ptr) (DWORD)(((DWORD)*((BYTE*)(ptr)+3)<<24)|((DWORD)*((BYTE*)(ptr)+2)<<16)|((WORD)*((BYTE*)(ptr)+1)<<8)|*(BYTE*)(ptr))
+#define ST_WORD(ptr,val) *(BYTE*)(ptr)=(BYTE)(val); *((BYTE*)(ptr)+1)=(BYTE)((WORD)(val)>>8)
+#define ST_DWORD(ptr,val) *(BYTE*)(ptr)=(BYTE)(val); *((BYTE*)(ptr)+1)=(BYTE)((WORD)(val)>>8); *((BYTE*)(ptr)+2)=(BYTE)((DWORD)(val)>>16); *((BYTE*)(ptr)+3)=(BYTE)((DWORD)(val)>>24)
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FATFS */
diff --git a/firmware/ext/ffconf.h b/firmware/ext/ffconf.h
new file mode 100644
index 0000000000..64704a4287
--- /dev/null
+++ b/firmware/ext/ffconf.h
@@ -0,0 +1,193 @@
+/* CHIBIOS FIX */
+#include "ch.h"
+
+/*---------------------------------------------------------------------------/
+/ FatFs - FAT file system module configuration file R0.09 (C)ChaN, 2011
+/----------------------------------------------------------------------------/
+/
+/ CAUTION! Do not forget to make clean the project after any changes to
+/ the configuration options.
+/
+/----------------------------------------------------------------------------*/
+#ifndef _FFCONF
+#define _FFCONF 6502 /* Revision ID */
+
+
+/*---------------------------------------------------------------------------/
+/ Functions and Buffer Configurations
+/----------------------------------------------------------------------------*/
+
+#define _FS_TINY 0 /* 0:Normal or 1:Tiny */
+/* When _FS_TINY is set to 1, FatFs uses the sector buffer in the file system
+/ object instead of the sector buffer in the individual file object for file
+/ data transfer. This reduces memory consumption 512 bytes each file object. */
+
+
+#define _FS_READONLY 0 /* 0:Read/Write or 1:Read only */
+/* Setting _FS_READONLY to 1 defines read only configuration. This removes
+/ writing functions, f_write, f_sync, f_unlink, f_mkdir, f_chmod, f_rename,
+/ f_truncate and useless f_getfree. */
+
+
+#define _FS_MINIMIZE 1 /* 0 to 3 */
+/* The _FS_MINIMIZE option defines minimization level to remove some functions.
+/
+/ 0: Full function.
+/ 1: f_stat, f_getfree, f_unlink, f_mkdir, f_chmod, f_truncate and f_rename
+/ are removed.
+/ 2: f_opendir and f_readdir are removed in addition to 1.
+/ 3: f_lseek is removed in addition to 2. */
+
+
+#define _USE_STRFUNC 0 /* 0:Disable or 1-2:Enable */
+/* To enable string functions, set _USE_STRFUNC to 1 or 2. */
+
+
+#define _USE_MKFS 0 /* 0:Disable or 1:Enable */
+/* To enable f_mkfs function, set _USE_MKFS to 1 and set _FS_READONLY to 0 */
+
+
+#define _USE_FORWARD 1 /* 0:Disable or 1:Enable */
+/* To enable f_forward function, set _USE_FORWARD to 1 and set _FS_TINY to 1. */
+
+
+#define _USE_FASTSEEK 0 /* 0:Disable or 1:Enable */
+/* To enable fast seek feature, set _USE_FASTSEEK to 1. */
+
+
+
+/*---------------------------------------------------------------------------/
+/ Locale and Namespace Configurations
+/----------------------------------------------------------------------------*/
+
+#define _CODE_PAGE 1251
+/* The _CODE_PAGE specifies the OEM code page to be used on the target system.
+/ Incorrect setting of the code page can cause a file open failure.
+/
+/ 932 - Japanese Shift-JIS (DBCS, OEM, Windows)
+/ 936 - Simplified Chinese GBK (DBCS, OEM, Windows)
+/ 949 - Korean (DBCS, OEM, Windows)
+/ 950 - Traditional Chinese Big5 (DBCS, OEM, Windows)
+/ 1250 - Central Europe (Windows)
+/ 1251 - Cyrillic (Windows)
+/ 1252 - Latin 1 (Windows)
+/ 1253 - Greek (Windows)
+/ 1254 - Turkish (Windows)
+/ 1255 - Hebrew (Windows)
+/ 1256 - Arabic (Windows)
+/ 1257 - Baltic (Windows)
+/ 1258 - Vietnam (OEM, Windows)
+/ 437 - U.S. (OEM)
+/ 720 - Arabic (OEM)
+/ 737 - Greek (OEM)
+/ 775 - Baltic (OEM)
+/ 850 - Multilingual Latin 1 (OEM)
+/ 858 - Multilingual Latin 1 + Euro (OEM)
+/ 852 - Latin 2 (OEM)
+/ 855 - Cyrillic (OEM)
+/ 866 - Russian (OEM)
+/ 857 - Turkish (OEM)
+/ 862 - Hebrew (OEM)
+/ 874 - Thai (OEM, Windows)
+/ 1 - ASCII only (Valid for non LFN cfg.)
+*/
+
+
+#define _USE_LFN 3 /* 0 to 3 */
+#define _MAX_LFN 255 /* Maximum LFN length to handle (12 to 255) */
+/* The _USE_LFN option switches the LFN support.
+/
+/ 0: Disable LFN feature. _MAX_LFN and _LFN_UNICODE have no effect.
+/ 1: Enable LFN with static working buffer on the BSS. Always NOT reentrant.
+/ 2: Enable LFN with dynamic working buffer on the STACK.
+/ 3: Enable LFN with dynamic working buffer on the HEAP.
+/
+/ The LFN working buffer occupies (_MAX_LFN + 1) * 2 bytes. To enable LFN,
+/ Unicode handling functions ff_convert() and ff_wtoupper() must be added
+/ to the project. When enable to use heap, memory control functions
+/ ff_memalloc() and ff_memfree() must be added to the project. */
+
+
+#define _LFN_UNICODE 0 /* 0:ANSI/OEM or 1:Unicode */
+/* To switch the character code set on FatFs API to Unicode,
+/ enable LFN feature and set _LFN_UNICODE to 1. */
+
+
+#define _FS_RPATH 0 /* 0 to 2 */
+/* The _FS_RPATH option configures relative path feature.
+/
+/ 0: Disable relative path feature and remove related functions.
+/ 1: Enable relative path. f_chdrive() and f_chdir() are available.
+/ 2: f_getcwd() is available in addition to 1.
+/
+/ Note that output of the f_readdir fnction is affected by this option. */
+
+
+
+/*---------------------------------------------------------------------------/
+/ Physical Drive Configurations
+/----------------------------------------------------------------------------*/
+
+#define _VOLUMES 1
+/* Number of volumes (logical drives) to be used. */
+
+
+#define _MAX_SS 512 /* 512, 1024, 2048 or 4096 */
+/* Maximum sector size to be handled.
+/ Always set 512 for memory card and hard disk but a larger value may be
+/ required for on-board flash memory, floppy disk and optical disk.
+/ When _MAX_SS is larger than 512, it configures FatFs to variable sector size
+/ and GET_SECTOR_SIZE command must be implememted to the disk_ioctl function. */
+
+
+#define _MULTI_PARTITION 0 /* 0:Single partition, 1/2:Enable multiple partition */
+/* When set to 0, each volume is bound to the same physical drive number and
+/ it can mount only first primaly partition. When it is set to 1, each volume
+/ is tied to the partitions listed in VolToPart[]. */
+
+
+#define _USE_ERASE 0 /* 0:Disable or 1:Enable */
+/* To enable sector erase feature, set _USE_ERASE to 1. CTRL_ERASE_SECTOR command
+/ should be added to the disk_ioctl functio. */
+
+
+
+/*---------------------------------------------------------------------------/
+/ System Configurations
+/----------------------------------------------------------------------------*/
+
+#define _WORD_ACCESS 0 /* 0 or 1 */
+/* Set 0 first and it is always compatible with all platforms. The _WORD_ACCESS
+/ option defines which access method is used to the word data on the FAT volume.
+/
+/ 0: Byte-by-byte access.
+/ 1: Word access. Do not choose this unless following condition is met.
+/
+/ When the byte order on the memory is big-endian or address miss-aligned word
+/ access results incorrect behavior, the _WORD_ACCESS must be set to 0.
+/ If it is not the case, the value can also be set to 1 to improve the
+/ performance and code size.
+*/
+
+
+/* A header file that defines sync object types on the O/S, such as
+/ windows.h, ucos_ii.h and semphr.h, must be included prior to ff.h. */
+
+#define _FS_REENTRANT 0 /* 0:Disable or 1:Enable */
+#define _FS_TIMEOUT 1000 /* Timeout period in unit of time ticks */
+#define _SYNC_t Semaphore * /* O/S dependent type of sync object. e.g. HANDLE, OS_EVENT*, ID and etc.. */
+
+/* The _FS_REENTRANT option switches the reentrancy (thread safe) of the FatFs module.
+/
+/ 0: Disable reentrancy. _SYNC_t and _FS_TIMEOUT have no effect.
+/ 1: Enable reentrancy. Also user provided synchronization handlers,
+/ ff_req_grant, ff_rel_grant, ff_del_syncobj and ff_cre_syncobj
+/ function must be added to the project. */
+
+
+#define _FS_SHARE 0 /* 0:Disable or >=1:Enable */
+/* To enable file shareing feature, set _FS_SHARE to 1 or greater. The value
+ defines how many files can be opened simultaneously. */
+
+
+#endif /* _FFCONFIG */
diff --git a/firmware/ext/integer.h b/firmware/ext/integer.h
new file mode 100644
index 0000000000..16ad408233
--- /dev/null
+++ b/firmware/ext/integer.h
@@ -0,0 +1,37 @@
+/*-------------------------------------------*/
+/* Integer type definitions for FatFs module */
+/*-------------------------------------------*/
+
+#ifndef _INTEGER
+#define _INTEGER
+
+#ifdef _WIN32 /* FatFs development platform */
+
+#include
+#include
+
+#else /* Embedded platform */
+
+/* These types must be 16-bit, 32-bit or larger integer */
+typedef int INT;
+typedef unsigned int UINT;
+
+/* These types must be 8-bit integer */
+typedef char CHAR;
+typedef unsigned char UCHAR;
+typedef unsigned char BYTE;
+
+/* These types must be 16-bit integer */
+typedef short SHORT;
+typedef unsigned short USHORT;
+typedef unsigned short WORD;
+typedef unsigned short WCHAR;
+
+/* These types must be 32-bit integer */
+typedef long LONG;
+typedef unsigned long ULONG;
+typedef unsigned long DWORD;
+
+#endif
+
+#endif
diff --git a/firmware/ext/readme.txt b/firmware/ext/readme.txt
new file mode 100644
index 0000000000..fa761829a5
--- /dev/null
+++ b/firmware/ext/readme.txt
@@ -0,0 +1 @@
+In this folder we have 3rd party code
\ No newline at end of file
diff --git a/firmware/ext_algo/readme.txt b/firmware/ext_algo/readme.txt
new file mode 100644
index 0000000000..fa761829a5
--- /dev/null
+++ b/firmware/ext_algo/readme.txt
@@ -0,0 +1 @@
+In this folder we have 3rd party code
\ No newline at end of file
diff --git a/firmware/ext_algo/utlist.h b/firmware/ext_algo/utlist.h
new file mode 100644
index 0000000000..c82dd916e2
--- /dev/null
+++ b/firmware/ext_algo/utlist.h
@@ -0,0 +1,757 @@
+/*
+Copyright (c) 2007-2013, Troy D. Hanson http://troydhanson.github.com/uthash/
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef UTLIST_H
+#define UTLIST_H
+
+#define UTLIST_VERSION 1.9.8
+
+#include
+
+/*
+ * This file contains macros to manipulate singly and doubly-linked lists.
+ *
+ * 1. LL_ macros: singly-linked lists.
+ * 2. DL_ macros: doubly-linked lists.
+ * 3. CDL_ macros: circular doubly-linked lists.
+ *
+ * To use singly-linked lists, your structure must have a "next" pointer.
+ * To use doubly-linked lists, your structure must "prev" and "next" pointers.
+ * Either way, the pointer to the head of the list must be initialized to NULL.
+ *
+ * ----------------.EXAMPLE -------------------------
+ * struct item {
+ * int id;
+ * struct item *prev, *next;
+ * }
+ *
+ * struct item *list = NULL:
+ *
+ * int main() {
+ * struct item *item;
+ * ... allocate and populate item ...
+ * DL_APPEND(list, item);
+ * }
+ * --------------------------------------------------
+ *
+ * For doubly-linked lists, the append and delete macros are O(1)
+ * For singly-linked lists, append and delete are O(n) but prepend is O(1)
+ * The sort macro is O(n log(n)) for all types of single/double/circular lists.
+ */
+
+/* These macros use decltype or the earlier __typeof GNU extension.
+ As decltype is only available in newer compilers (VS2010 or gcc 4.3+
+ when compiling c++ code), this code uses whatever method is needed
+ or, for VS2008 where neither is available, uses casting workarounds. */
+#ifdef _MSC_VER /* MS compiler */
+#if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */
+#define LDECLTYPE(x) decltype(x)
+#else /* VS2008 or older (or VS2010 in C mode) */
+#define NO_DECLTYPE
+#define LDECLTYPE(x) char*
+#endif
+#elif defined(__ICCARM__)
+#define NO_DECLTYPE
+#define LDECLTYPE(x) char*
+#else /* GNU, Sun and other compilers */
+#define LDECLTYPE(x) __typeof(x)
+#endif
+
+/* for VS2008 we use some workarounds to get around the lack of decltype,
+ * namely, we always reassign our tmp variable to the list head if we need
+ * to dereference its prev/next pointers, and save/restore the real head.*/
+#ifdef NO_DECLTYPE
+#define _SV(elt,list) _tmp = (char*)(list); {char **_alias = (char**)&(list); *_alias = (elt); }
+#define _NEXT(elt,list,next) ((char*)((list)->next))
+#define _NEXTASGN(elt,list,to,next) { char **_alias = (char**)&((list)->next); *_alias=(char*)(to); }
+/* #define _PREV(elt,list,prev) ((char*)((list)->prev)) */
+#define _PREVASGN(elt,list,to,prev) { char **_alias = (char**)&((list)->prev); *_alias=(char*)(to); }
+#define _RS(list) { char **_alias = (char**)&(list); *_alias=_tmp; }
+#define _CASTASGN(a,b) { char **_alias = (char**)&(a); *_alias=(char*)(b); }
+#else
+#define _SV(elt,list)
+#define _NEXT(elt,list,next) ((elt)->next)
+#define _NEXTASGN(elt,list,to,next) ((elt)->next)=(to)
+/* #define _PREV(elt,list,prev) ((elt)->prev) */
+#define _PREVASGN(elt,list,to,prev) ((elt)->prev)=(to)
+#define _RS(list)
+#define _CASTASGN(a,b) (a)=(b)
+#endif
+
+/******************************************************************************
+ * The sort macro is an adaptation of Simon Tatham's O(n log(n)) mergesort *
+ * Unwieldy variable names used here to avoid shadowing passed-in variables. *
+ *****************************************************************************/
+#define LL_SORT(list, cmp) \
+ LL_SORT2(list, cmp, next)
+
+#define LL_SORT2(list, cmp, next) \
+do { \
+ LDECLTYPE(list) _ls_p; \
+ LDECLTYPE(list) _ls_q; \
+ LDECLTYPE(list) _ls_e; \
+ LDECLTYPE(list) _ls_tail; \
+ int _ls_insize, _ls_nmerges, _ls_psize, _ls_qsize, _ls_i, _ls_looping; \
+ if (list) { \
+ _ls_insize = 1; \
+ _ls_looping = 1; \
+ while (_ls_looping) { \
+ _CASTASGN(_ls_p,list); \
+ list = NULL; \
+ _ls_tail = NULL; \
+ _ls_nmerges = 0; \
+ while (_ls_p) { \
+ _ls_nmerges++; \
+ _ls_q = _ls_p; \
+ _ls_psize = 0; \
+ for (_ls_i = 0; _ls_i < _ls_insize; _ls_i++) { \
+ _ls_psize++; \
+ _SV(_ls_q,list); _ls_q = _NEXT(_ls_q,list,next); _RS(list); \
+ if (!_ls_q) break; \
+ } \
+ _ls_qsize = _ls_insize; \
+ while (_ls_psize > 0 || (_ls_qsize > 0 && _ls_q)) { \
+ if (_ls_psize == 0) { \
+ _ls_e = _ls_q; _SV(_ls_q,list); _ls_q = \
+ _NEXT(_ls_q,list,next); _RS(list); _ls_qsize--; \
+ } else if (_ls_qsize == 0 || !_ls_q) { \
+ _ls_e = _ls_p; _SV(_ls_p,list); _ls_p = \
+ _NEXT(_ls_p,list,next); _RS(list); _ls_psize--; \
+ } else if (cmp(_ls_p,_ls_q) <= 0) { \
+ _ls_e = _ls_p; _SV(_ls_p,list); _ls_p = \
+ _NEXT(_ls_p,list,next); _RS(list); _ls_psize--; \
+ } else { \
+ _ls_e = _ls_q; _SV(_ls_q,list); _ls_q = \
+ _NEXT(_ls_q,list,next); _RS(list); _ls_qsize--; \
+ } \
+ if (_ls_tail) { \
+ _SV(_ls_tail,list); _NEXTASGN(_ls_tail,list,_ls_e,next); _RS(list); \
+ } else { \
+ _CASTASGN(list,_ls_e); \
+ } \
+ _ls_tail = _ls_e; \
+ } \
+ _ls_p = _ls_q; \
+ } \
+ if (_ls_tail) { \
+ _SV(_ls_tail,list); _NEXTASGN(_ls_tail,list,NULL,next); _RS(list); \
+ } \
+ if (_ls_nmerges <= 1) { \
+ _ls_looping=0; \
+ } \
+ _ls_insize *= 2; \
+ } \
+ } \
+} while (0)
+
+
+#define DL_SORT(list, cmp) \
+ DL_SORT2(list, cmp, prev, next)
+
+#define DL_SORT2(list, cmp, prev, next) \
+do { \
+ LDECLTYPE(list) _ls_p; \
+ LDECLTYPE(list) _ls_q; \
+ LDECLTYPE(list) _ls_e; \
+ LDECLTYPE(list) _ls_tail; \
+ int _ls_insize, _ls_nmerges, _ls_psize, _ls_qsize, _ls_i, _ls_looping; \
+ if (list) { \
+ _ls_insize = 1; \
+ _ls_looping = 1; \
+ while (_ls_looping) { \
+ _CASTASGN(_ls_p,list); \
+ list = NULL; \
+ _ls_tail = NULL; \
+ _ls_nmerges = 0; \
+ while (_ls_p) { \
+ _ls_nmerges++; \
+ _ls_q = _ls_p; \
+ _ls_psize = 0; \
+ for (_ls_i = 0; _ls_i < _ls_insize; _ls_i++) { \
+ _ls_psize++; \
+ _SV(_ls_q,list); _ls_q = _NEXT(_ls_q,list,next); _RS(list); \
+ if (!_ls_q) break; \
+ } \
+ _ls_qsize = _ls_insize; \
+ while (_ls_psize > 0 || (_ls_qsize > 0 && _ls_q)) { \
+ if (_ls_psize == 0) { \
+ _ls_e = _ls_q; _SV(_ls_q,list); _ls_q = \
+ _NEXT(_ls_q,list,next); _RS(list); _ls_qsize--; \
+ } else if (_ls_qsize == 0 || !_ls_q) { \
+ _ls_e = _ls_p; _SV(_ls_p,list); _ls_p = \
+ _NEXT(_ls_p,list,next); _RS(list); _ls_psize--; \
+ } else if (cmp(_ls_p,_ls_q) <= 0) { \
+ _ls_e = _ls_p; _SV(_ls_p,list); _ls_p = \
+ _NEXT(_ls_p,list,next); _RS(list); _ls_psize--; \
+ } else { \
+ _ls_e = _ls_q; _SV(_ls_q,list); _ls_q = \
+ _NEXT(_ls_q,list,next); _RS(list); _ls_qsize--; \
+ } \
+ if (_ls_tail) { \
+ _SV(_ls_tail,list); _NEXTASGN(_ls_tail,list,_ls_e,next); _RS(list); \
+ } else { \
+ _CASTASGN(list,_ls_e); \
+ } \
+ _SV(_ls_e,list); _PREVASGN(_ls_e,list,_ls_tail,prev); _RS(list); \
+ _ls_tail = _ls_e; \
+ } \
+ _ls_p = _ls_q; \
+ } \
+ _CASTASGN(list->prev, _ls_tail); \
+ _SV(_ls_tail,list); _NEXTASGN(_ls_tail,list,NULL,next); _RS(list); \
+ if (_ls_nmerges <= 1) { \
+ _ls_looping=0; \
+ } \
+ _ls_insize *= 2; \
+ } \
+ } \
+} while (0)
+
+#define CDL_SORT(list, cmp) \
+ CDL_SORT2(list, cmp, prev, next)
+
+#define CDL_SORT2(list, cmp, prev, next) \
+do { \
+ LDECLTYPE(list) _ls_p; \
+ LDECLTYPE(list) _ls_q; \
+ LDECLTYPE(list) _ls_e; \
+ LDECLTYPE(list) _ls_tail; \
+ LDECLTYPE(list) _ls_oldhead; \
+ LDECLTYPE(list) _tmp; \
+ int _ls_insize, _ls_nmerges, _ls_psize, _ls_qsize, _ls_i, _ls_looping; \
+ if (list) { \
+ _ls_insize = 1; \
+ _ls_looping = 1; \
+ while (_ls_looping) { \
+ _CASTASGN(_ls_p,list); \
+ _CASTASGN(_ls_oldhead,list); \
+ list = NULL; \
+ _ls_tail = NULL; \
+ _ls_nmerges = 0; \
+ while (_ls_p) { \
+ _ls_nmerges++; \
+ _ls_q = _ls_p; \
+ _ls_psize = 0; \
+ for (_ls_i = 0; _ls_i < _ls_insize; _ls_i++) { \
+ _ls_psize++; \
+ _SV(_ls_q,list); \
+ if (_NEXT(_ls_q,list,next) == _ls_oldhead) { \
+ _ls_q = NULL; \
+ } else { \
+ _ls_q = _NEXT(_ls_q,list,next); \
+ } \
+ _RS(list); \
+ if (!_ls_q) break; \
+ } \
+ _ls_qsize = _ls_insize; \
+ while (_ls_psize > 0 || (_ls_qsize > 0 && _ls_q)) { \
+ if (_ls_psize == 0) { \
+ _ls_e = _ls_q; _SV(_ls_q,list); _ls_q = \
+ _NEXT(_ls_q,list,next); _RS(list); _ls_qsize--; \
+ if (_ls_q == _ls_oldhead) { _ls_q = NULL; } \
+ } else if (_ls_qsize == 0 || !_ls_q) { \
+ _ls_e = _ls_p; _SV(_ls_p,list); _ls_p = \
+ _NEXT(_ls_p,list,next); _RS(list); _ls_psize--; \
+ if (_ls_p == _ls_oldhead) { _ls_p = NULL; } \
+ } else if (cmp(_ls_p,_ls_q) <= 0) { \
+ _ls_e = _ls_p; _SV(_ls_p,list); _ls_p = \
+ _NEXT(_ls_p,list,next); _RS(list); _ls_psize--; \
+ if (_ls_p == _ls_oldhead) { _ls_p = NULL; } \
+ } else { \
+ _ls_e = _ls_q; _SV(_ls_q,list); _ls_q = \
+ _NEXT(_ls_q,list,next); _RS(list); _ls_qsize--; \
+ if (_ls_q == _ls_oldhead) { _ls_q = NULL; } \
+ } \
+ if (_ls_tail) { \
+ _SV(_ls_tail,list); _NEXTASGN(_ls_tail,list,_ls_e,next); _RS(list); \
+ } else { \
+ _CASTASGN(list,_ls_e); \
+ } \
+ _SV(_ls_e,list); _PREVASGN(_ls_e,list,_ls_tail,prev); _RS(list); \
+ _ls_tail = _ls_e; \
+ } \
+ _ls_p = _ls_q; \
+ } \
+ _CASTASGN(list->prev,_ls_tail); \
+ _CASTASGN(_tmp,list); \
+ _SV(_ls_tail,list); _NEXTASGN(_ls_tail,list,_tmp,next); _RS(list); \
+ if (_ls_nmerges <= 1) { \
+ _ls_looping=0; \
+ } \
+ _ls_insize *= 2; \
+ } \
+ } \
+} while (0)
+
+/******************************************************************************
+ * singly linked list macros (non-circular) *
+ *****************************************************************************/
+#define LL_PREPEND(head,add) \
+ LL_PREPEND2(head,add,next)
+
+#define LL_PREPEND2(head,add,next) \
+do { \
+ (add)->next = head; \
+ head = add; \
+} while (0)
+
+#define LL_CONCAT(head1,head2) \
+ LL_CONCAT2(head1,head2,next)
+
+#define LL_CONCAT2(head1,head2,next) \
+do { \
+ LDECLTYPE(head1) _tmp; \
+ if (head1) { \
+ _tmp = head1; \
+ while (_tmp->next) { _tmp = _tmp->next; } \
+ _tmp->next=(head2); \
+ } else { \
+ (head1)=(head2); \
+ } \
+} while (0)
+
+#define LL_APPEND(head,add) \
+ LL_APPEND2(head,add,next)
+
+#define LL_APPEND2(head,add,next) \
+do { \
+ LDECLTYPE(head) _tmp; \
+ (add)->next=NULL; \
+ if (head) { \
+ _tmp = head; \
+ while (_tmp->next) { _tmp = _tmp->next; } \
+ _tmp->next=(add); \
+ } else { \
+ (head)=(add); \
+ } \
+} while (0)
+
+#define LL_DELETE(head,del) \
+ LL_DELETE2(head,del,next)
+
+#define LL_DELETE2(head,del,next) \
+do { \
+ LDECLTYPE(head) _tmp; \
+ if ((head) == (del)) { \
+ (head)=(head)->next; \
+ } else { \
+ _tmp = head; \
+ while (_tmp->next && (_tmp->next != (del))) { \
+ _tmp = _tmp->next; \
+ } \
+ if (_tmp->next) { \
+ _tmp->next = ((del)->next); \
+ } \
+ } \
+} while (0)
+
+/* Here are VS2008 replacements for LL_APPEND and LL_DELETE */
+#define LL_APPEND_VS2008(head,add) \
+ LL_APPEND2_VS2008(head,add,next)
+
+#define LL_APPEND2_VS2008(head,add,next) \
+do { \
+ if (head) { \
+ (add)->next = head; /* use add->next as a temp variable */ \
+ while ((add)->next->next) { (add)->next = (add)->next->next; } \
+ (add)->next->next=(add); \
+ } else { \
+ (head)=(add); \
+ } \
+ (add)->next=NULL; \
+} while (0)
+
+#define LL_DELETE_VS2008(head,del) \
+ LL_DELETE2_VS2008(head,del,next)
+
+#define LL_DELETE2_VS2008(head,del,next) \
+do { \
+ if ((head) == (del)) { \
+ (head)=(head)->next; \
+ } else { \
+ char *_tmp = (char*)(head); \
+ while ((head)->next && ((head)->next != (del))) { \
+ head = (head)->next; \
+ } \
+ if ((head)->next) { \
+ (head)->next = ((del)->next); \
+ } \
+ { \
+ char **_head_alias = (char**)&(head); \
+ *_head_alias = _tmp; \
+ } \
+ } \
+} while (0)
+#ifdef NO_DECLTYPE
+#undef LL_APPEND
+#define LL_APPEND LL_APPEND_VS2008
+#undef LL_DELETE
+#define LL_DELETE LL_DELETE_VS2008
+#undef LL_DELETE2
+#define LL_DELETE2 LL_DELETE2_VS2008
+#undef LL_APPEND2
+#define LL_APPEND2 LL_APPEND2_VS2008
+#undef LL_CONCAT /* no LL_CONCAT_VS2008 */
+#undef DL_CONCAT /* no DL_CONCAT_VS2008 */
+#endif
+/* end VS2008 replacements */
+
+#define LL_COUNT(head,el,counter) \
+ LL_COUNT2(head,el,counter,next) \
+
+#define LL_COUNT2(head,el,counter,next) \
+{ \
+ counter = 0; \
+ LL_FOREACH2(head,el,next){ ++counter; } \
+}
+
+#define LL_FOREACH(head,el) \
+ LL_FOREACH2(head,el,next)
+
+#define LL_FOREACH2(head,el,next) \
+ for(el=head;el;el=(el)->next)
+
+#define LL_FOREACH_SAFE(head,el,tmp) \
+ LL_FOREACH_SAFE2(head,el,tmp,next)
+
+#define LL_FOREACH_SAFE2(head,el,tmp,next) \
+ for((el)=(head);(el) && (tmp = (el)->next, 1); (el) = tmp)
+
+#define LL_SEARCH_SCALAR(head,out,field,val) \
+ LL_SEARCH_SCALAR2(head,out,field,val,next)
+
+#define LL_SEARCH_SCALAR2(head,out,field,val,next) \
+do { \
+ LL_FOREACH2(head,out,next) { \
+ if ((out)->field == (val)) break; \
+ } \
+} while(0)
+
+#define LL_SEARCH(head,out,elt,cmp) \
+ LL_SEARCH2(head,out,elt,cmp,next)
+
+#define LL_SEARCH2(head,out,elt,cmp,next) \
+do { \
+ LL_FOREACH2(head,out,next) { \
+ if ((cmp(out,elt))==0) break; \
+ } \
+} while(0)
+
+#define LL_REPLACE_ELEM(head, el, add) \
+do { \
+ LDECLTYPE(head) _tmp; \
+ assert(head != NULL); \
+ assert(el != NULL); \
+ assert(add != NULL); \
+ (add)->next = (el)->next; \
+ if ((head) == (el)) { \
+ (head) = (add); \
+ } else { \
+ _tmp = head; \
+ while (_tmp->next && (_tmp->next != (el))) { \
+ _tmp = _tmp->next; \
+ } \
+ if (_tmp->next) { \
+ _tmp->next = (add); \
+ } \
+ } \
+} while (0)
+
+#define LL_PREPEND_ELEM(head, el, add) \
+do { \
+ LDECLTYPE(head) _tmp; \
+ assert(head != NULL); \
+ assert(el != NULL); \
+ assert(add != NULL); \
+ (add)->next = (el); \
+ if ((head) == (el)) { \
+ (head) = (add); \
+ } else { \
+ _tmp = head; \
+ while (_tmp->next && (_tmp->next != (el))) { \
+ _tmp = _tmp->next; \
+ } \
+ if (_tmp->next) { \
+ _tmp->next = (add); \
+ } \
+ } \
+} while (0) \
+
+
+/******************************************************************************
+ * doubly linked list macros (non-circular) *
+ *****************************************************************************/
+#define DL_PREPEND(head,add) \
+ DL_PREPEND2(head,add,prev,next)
+
+#define DL_PREPEND2(head,add,prev,next) \
+do { \
+ (add)->next = head; \
+ if (head) { \
+ (add)->prev = (head)->prev; \
+ (head)->prev = (add); \
+ } else { \
+ (add)->prev = (add); \
+ } \
+ (head) = (add); \
+} while (0)
+
+#define DL_APPEND(head,add) \
+ DL_APPEND2(head,add,prev,next)
+
+#define DL_APPEND2(head,add,prev,next) \
+do { \
+ if (head) { \
+ (add)->prev = (head)->prev; \
+ (head)->prev->next = (add); \
+ (head)->prev = (add); \
+ (add)->next = NULL; \
+ } else { \
+ (head)=(add); \
+ (head)->prev = (head); \
+ (head)->next = NULL; \
+ } \
+} while (0)
+
+#define DL_CONCAT(head1,head2) \
+ DL_CONCAT2(head1,head2,prev,next)
+
+#define DL_CONCAT2(head1,head2,prev,next) \
+do { \
+ LDECLTYPE(head1) _tmp; \
+ if (head2) { \
+ if (head1) { \
+ _tmp = (head2)->prev; \
+ (head2)->prev = (head1)->prev; \
+ (head1)->prev->next = (head2); \
+ (head1)->prev = _tmp; \
+ } else { \
+ (head1)=(head2); \
+ } \
+ } \
+} while (0)
+
+#define DL_DELETE(head,del) \
+ DL_DELETE2(head,del,prev,next)
+
+#define DL_DELETE2(head,del,prev,next) \
+do { \
+ assert((del)->prev != NULL); \
+ if ((del)->prev == (del)) { \
+ (head)=NULL; \
+ } else if ((del)==(head)) { \
+ (del)->next->prev = (del)->prev; \
+ (head) = (del)->next; \
+ } else { \
+ (del)->prev->next = (del)->next; \
+ if ((del)->next) { \
+ (del)->next->prev = (del)->prev; \
+ } else { \
+ (head)->prev = (del)->prev; \
+ } \
+ } \
+} while (0)
+
+#define DL_COUNT(head,el,counter) \
+ DL_COUNT2(head,el,counter,next) \
+
+#define DL_COUNT2(head,el,counter,next) \
+{ \
+ counter = 0; \
+ DL_FOREACH2(head,el,next){ ++counter; } \
+}
+
+#define DL_FOREACH(head,el) \
+ DL_FOREACH2(head,el,next)
+
+#define DL_FOREACH2(head,el,next) \
+ for(el=head;el;el=(el)->next)
+
+/* this version is safe for deleting the elements during iteration */
+#define DL_FOREACH_SAFE(head,el,tmp) \
+ DL_FOREACH_SAFE2(head,el,tmp,next)
+
+#define DL_FOREACH_SAFE2(head,el,tmp,next) \
+ for((el)=(head);(el) && (tmp = (el)->next, 1); (el) = tmp)
+
+/* these are identical to their singly-linked list counterparts */
+#define DL_SEARCH_SCALAR LL_SEARCH_SCALAR
+#define DL_SEARCH LL_SEARCH
+#define DL_SEARCH_SCALAR2 LL_SEARCH_SCALAR2
+#define DL_SEARCH2 LL_SEARCH2
+
+#define DL_REPLACE_ELEM(head, el, add) \
+do { \
+ assert(head != NULL); \
+ assert(el != NULL); \
+ assert(add != NULL); \
+ if ((head) == (el)) { \
+ (head) = (add); \
+ (add)->next = (el)->next; \
+ if ((el)->next == NULL) { \
+ (add)->prev = (add); \
+ } else { \
+ (add)->prev = (el)->prev; \
+ (add)->next->prev = (add); \
+ } \
+ } else { \
+ (add)->next = (el)->next; \
+ (add)->prev = (el)->prev; \
+ (add)->prev->next = (add); \
+ if ((el)->next == NULL) { \
+ (head)->prev = (add); \
+ } else { \
+ (add)->next->prev = (add); \
+ } \
+ } \
+} while (0)
+
+#define DL_PREPEND_ELEM(head, el, add) \
+do { \
+ assert(head != NULL); \
+ assert(el != NULL); \
+ assert(add != NULL); \
+ (add)->next = (el); \
+ (add)->prev = (el)->prev; \
+ (el)->prev = (add); \
+ if ((head) == (el)) { \
+ (head) = (add); \
+ } else { \
+ (add)->prev->next = (add); \
+ } \
+} while (0) \
+
+
+/******************************************************************************
+ * circular doubly linked list macros *
+ *****************************************************************************/
+#define CDL_PREPEND(head,add) \
+ CDL_PREPEND2(head,add,prev,next)
+
+#define CDL_PREPEND2(head,add,prev,next) \
+do { \
+ if (head) { \
+ (add)->prev = (head)->prev; \
+ (add)->next = (head); \
+ (head)->prev = (add); \
+ (add)->prev->next = (add); \
+ } else { \
+ (add)->prev = (add); \
+ (add)->next = (add); \
+ } \
+(head)=(add); \
+} while (0)
+
+#define CDL_DELETE(head,del) \
+ CDL_DELETE2(head,del,prev,next)
+
+#define CDL_DELETE2(head,del,prev,next) \
+do { \
+ if ( ((head)==(del)) && ((head)->next == (head))) { \
+ (head) = 0L; \
+ } else { \
+ (del)->next->prev = (del)->prev; \
+ (del)->prev->next = (del)->next; \
+ if ((del) == (head)) (head)=(del)->next; \
+ } \
+} while (0)
+
+#define CDL_COUNT(head,el,counter) \
+ CDL_COUNT2(head,el,counter,next) \
+
+#define CDL_COUNT2(head, el, counter,next) \
+{ \
+ counter = 0; \
+ CDL_FOREACH2(head,el,next){ ++counter; } \
+}
+
+#define CDL_FOREACH(head,el) \
+ CDL_FOREACH2(head,el,next)
+
+#define CDL_FOREACH2(head,el,next) \
+ for(el=head;el;el=((el)->next==head ? 0L : (el)->next))
+
+#define CDL_FOREACH_SAFE(head,el,tmp1,tmp2) \
+ CDL_FOREACH_SAFE2(head,el,tmp1,tmp2,prev,next)
+
+#define CDL_FOREACH_SAFE2(head,el,tmp1,tmp2,prev,next) \
+ for((el)=(head), ((tmp1)=(head)?((head)->prev):NULL); \
+ (el) && ((tmp2)=(el)->next, 1); \
+ ((el) = (((el)==(tmp1)) ? 0L : (tmp2))))
+
+#define CDL_SEARCH_SCALAR(head,out,field,val) \
+ CDL_SEARCH_SCALAR2(head,out,field,val,next)
+
+#define CDL_SEARCH_SCALAR2(head,out,field,val,next) \
+do { \
+ CDL_FOREACH2(head,out,next) { \
+ if ((out)->field == (val)) break; \
+ } \
+} while(0)
+
+#define CDL_SEARCH(head,out,elt,cmp) \
+ CDL_SEARCH2(head,out,elt,cmp,next)
+
+#define CDL_SEARCH2(head,out,elt,cmp,next) \
+do { \
+ CDL_FOREACH2(head,out,next) { \
+ if ((cmp(out,elt))==0) break; \
+ } \
+} while(0)
+
+#define CDL_REPLACE_ELEM(head, el, add) \
+do { \
+ assert(head != NULL); \
+ assert(el != NULL); \
+ assert(add != NULL); \
+ if ((el)->next == (el)) { \
+ (add)->next = (add); \
+ (add)->prev = (add); \
+ (head) = (add); \
+ } else { \
+ (add)->next = (el)->next; \
+ (add)->prev = (el)->prev; \
+ (add)->next->prev = (add); \
+ (add)->prev->next = (add); \
+ if ((head) == (el)) { \
+ (head) = (add); \
+ } \
+ } \
+} while (0)
+
+#define CDL_PREPEND_ELEM(head, el, add) \
+do { \
+ assert(head != NULL); \
+ assert(el != NULL); \
+ assert(add != NULL); \
+ (add)->next = (el); \
+ (add)->prev = (el)->prev; \
+ (el)->prev = (add); \
+ (add)->prev->next = (add); \
+ if ((head) == (el)) { \
+ (head) = (add); \
+ } \
+} while (0) \
+
+#endif /* UTLIST_H */
+
diff --git a/firmware/flash.bat b/firmware/flash.bat
new file mode 100644
index 0000000000..13f8977169
--- /dev/null
+++ b/firmware/flash.bat
@@ -0,0 +1 @@
+st-link_cli -c SWD ur -P build\rusefi.hex -Rst -Run
diff --git a/firmware/flash_debug.bat b/firmware/flash_debug.bat
new file mode 100644
index 0000000000..db50c2f7b1
--- /dev/null
+++ b/firmware/flash_debug.bat
@@ -0,0 +1 @@
+st-link_cli -c SWD ur -P Debug\rusefi.hex -Rst -Run
diff --git a/firmware/flash_release.bat b/firmware/flash_release.bat
new file mode 100644
index 0000000000..296da9e5aa
--- /dev/null
+++ b/firmware/flash_release.bat
@@ -0,0 +1 @@
+st-link_cli -c SWD ur -P Release\rusefi.hex -Rst -Run
diff --git a/firmware/gen_upload_docs.bat b/firmware/gen_upload_docs.bat
new file mode 100644
index 0000000000..794d8c3e01
--- /dev/null
+++ b/firmware/gen_upload_docs.bat
@@ -0,0 +1,7 @@
+svn up
+call generate_docs
+
+rem http://www.ncftp.com/download/
+cd ../doxygen
+ncftpput -u u71977750-docs -p docspass rusefi.com /html html/*
+
diff --git a/firmware/generate_docs.bat b/firmware/generate_docs.bat
new file mode 100644
index 0000000000..a761032164
--- /dev/null
+++ b/firmware/generate_docs.bat
@@ -0,0 +1 @@
+doxygen
\ No newline at end of file
diff --git a/firmware/global.h b/firmware/global.h
new file mode 100644
index 0000000000..eade52a7cc
--- /dev/null
+++ b/firmware/global.h
@@ -0,0 +1,51 @@
+/*
+ * @file global.h
+ *
+ * @date May 27, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef GLOBAL_H_
+#define GLOBAL_H_
+
+#include
+#include
+#include
+
+// this is about MISRA not liking 'time.h'. todo: figure out something
+#if defined __GNUC__
+#include
+#else
+typedef unsigned int time_t;
+#endif
+
+#include "efifeatures.h"
+#include "rusefi_enums.h"
+#include "obd_error_codes.h"
+#include "error_handling.h"
+
+/* definition to expand macro then apply to pragma message */
+#define VALUE_TO_STRING(x) #x
+#define VALUE(x) VALUE_TO_STRING(x)
+#define VAR_NAME_VALUE(var) #var "=" VALUE(var)
+
+// project-wide default thread stack size
+// see also PORT_INT_REQUIRED_STACK
+#define UTILITY_THREAD_STACK_SIZE 128
+
+#define EFI_ERROR_CODE 0xffffffff
+
+#if EFI_USE_CCM && defined __GNUC__
+#define CCM_OPTIONAL __attribute__((section(".ccm")));
+#else
+#define CCM_OPTIONAL @ ".ccm"
+#endif
+
+// this stuff is about ChibiOS 2.6 > Migration
+typedef VirtualTimer virtual_timer_t;
+typedef EventListener event_listener_t;
+typedef Thread thread_t;
+
+#define THD_WORKING_AREA WORKING_AREA
+
+#endif /* GLOBAL_H_ */
diff --git a/firmware/hw_layer/AdcConfiguration.h b/firmware/hw_layer/AdcConfiguration.h
new file mode 100644
index 0000000000..b0a1848b35
--- /dev/null
+++ b/firmware/hw_layer/AdcConfiguration.h
@@ -0,0 +1,35 @@
+/**
+ * @file AdcConfiguration.h
+ *
+ * @date May 3, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+#ifndef ADCCONFIGURATION_H_
+#define ADCCONFIGURATION_H_
+
+class AdcConfiguration {
+public:
+ AdcConfiguration(ADCConversionGroup* hwConfig);
+ void addChannel(adc_channel_e hwChannelIndex);
+ adc_channel_e getAdcHardwareIndexByInternalIndex(int index);
+ int internalAdcIndexByHardwareIndex[20];
+ bool isHwUsed(adc_channel_e hwChannel);
+ int size();
+ void init(void);
+ int conversionCount;
+ int errorsCount;
+ int getAdcValueByIndex(int internalIndex);
+
+ adc_state values;
+private:
+ ADCConversionGroup* hwConfig;
+ /**
+ * Number of ADC channels in use
+ */
+ int channelCount;
+
+ adc_channel_e hardwareIndexByIndernalAdcIndex[20];
+};
+
+
+#endif /* ADCCONFIGURATION_H_ */
diff --git a/firmware/hw_layer/HIP9011.c b/firmware/hw_layer/HIP9011.c
new file mode 100644
index 0000000000..2e062294ae
--- /dev/null
+++ b/firmware/hw_layer/HIP9011.c
@@ -0,0 +1,94 @@
+/**
+ * @file HIP9011.c
+ * @brief HIP9011/TPIC8101 driver
+ *
+ * pin1 VDD
+ * pin2 GND
+ *
+ * pin8 Chip Select - CS
+ * pin11 Slave Data Out - MISO-
+ * pin12 Slave Data In - MOSI
+ * pin13 SPI clock - SCLK
+ *
+ *
+ * SPI frequency: 5MHz
+ *
+ * @date Nov 27, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+
+#if EFI_HIP_9011
+
+#define HIP9011_CS_PORT GPIOE
+#define HIP9011_CS_PIN 11
+
+static Logging logger;
+
+static THD_WORKING_AREA(htThreadStack, UTILITY_THREAD_STACK_SIZE);
+
+static int callbackc = 0;
+
+static void spiCallback(SPIDriver *spip) {
+ spiUnselectI(spip);
+
+ scheduleSimpleMsg(&logger, "spiCallback HIP=", callbackc++);
+
+}
+
+// SPI_CR1_BR_1 // 5MHz
+
+static const SPIConfig spicfg = { spiCallback,
+/* HW dependent part.*/
+HIP9011_CS_PORT,
+HIP9011_CS_PIN,
+//SPI_CR1_MSTR |
+//SPI_CR1_BR_1 // 5MHz
+ SPI_CR1_BR_0 | SPI_CR1_BR_1 | SPI_CR1_BR_2 };
+
+static unsigned char tx_buff[8];
+static unsigned char rx_buff[8];
+
+static SPIDriver *driver = &SPID2;
+
+// 0b01110001
+#define HIP_ADVANCED_MODE 0x71
+
+static msg_t ivThread(int param) {
+ chRegSetThreadName("HIP");
+
+ int counter = 0;
+
+// tx_buff[0] = 0b11100001;
+
+ tx_buff[0] = HIP_ADVANCED_MODE;
+
+ tx_buff[4] = 0xF8;// 0b11111000;
+
+ while (TRUE) {
+ chThdSleepMilliseconds(10);
+
+ scheduleSimpleMsg(&logger, "poking HIP=", counter++);
+
+ spiSelect(driver);
+
+ spiStartExchange(driver, 8, tx_buff, rx_buff);
+// spiUnselect(driver);
+
+ }
+#if defined __GNUC__
+ return 0;
+#endif
+}
+
+void initHip9011(void) {
+ initLogging(&logger, "HIP driver");
+
+ print("Starting HIP9011/TPIC8101 driver\r\n");
+ spiStart(driver, &spicfg);
+
+ chThdCreateStatic(htThreadStack, sizeof(htThreadStack), NORMALPRIO, (tfunc_t) ivThread, NULL);
+}
+
+#endif
diff --git a/firmware/hw_layer/HIP9011.h b/firmware/hw_layer/HIP9011.h
new file mode 100644
index 0000000000..dcb72016e7
--- /dev/null
+++ b/firmware/hw_layer/HIP9011.h
@@ -0,0 +1,14 @@
+/**
+ * @file HIP9011.h
+ * @brief HIP9011/TPIC8101 driver
+ *
+ * @date Nov 27, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef HIP9011_H_
+#define HIP9011_H_
+
+void initHip9011(void);
+
+#endif /* HIP9011_H_ */
diff --git a/firmware/hw_layer/adc_external.h b/firmware/hw_layer/adc_external.h
new file mode 100644
index 0000000000..b9932b2191
--- /dev/null
+++ b/firmware/hw_layer/adc_external.h
@@ -0,0 +1,18 @@
+/*
+ * @file adc_external.h
+ *
+ *
+ * @date Aug 18, 2013
+ * @author pc
+ */
+
+#ifndef ADC_EXTERNAL_H_
+#define ADC_EXTERNAL_H_
+
+#include "mcp3208.h"
+
+#define getAdcValue(channel) getMcp3208adc(channel)
+#define adcToVoltsDivided(adc) (5.0f / 4095 * adc)
+#define getVoltageDivided(channel) adcToVoltsDivided(getAdcValue(channel))
+
+#endif /* ADC_EXTERNAL_H_ */
diff --git a/firmware/hw_layer/adc_inputs.cpp b/firmware/hw_layer/adc_inputs.cpp
new file mode 100644
index 0000000000..d853fa45a8
--- /dev/null
+++ b/firmware/hw_layer/adc_inputs.cpp
@@ -0,0 +1,543 @@
+/**
+ * @file adc_inputs.cpp
+ * @brief Low level ADC code
+ *
+ * @date Jan 14, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+#include "engine_configuration.h"
+#include "adc_inputs.h"
+#include "AdcConfiguration.h"
+
+#include "pin_repository.h"
+#include "engine_math.h"
+
+#if EFI_SPEED_DENSITY
+#include "map_averaging.h"
+#endif /* EFI_SPEED_DENSITY */
+
+AdcConfiguration::AdcConfiguration(ADCConversionGroup* hwConfig) {
+ this->hwConfig = hwConfig;
+ channelCount = 0;
+ conversionCount = 0;
+
+ hwConfig->sqr1 = 0;
+ hwConfig->sqr2 = 0;
+ hwConfig->sqr3 = 0;
+ memset(internalAdcIndexByHardwareIndex, 0xFFFFFFFF, sizeof(internalAdcIndexByHardwareIndex));
+}
+
+#define ADC_GRP1_BUF_DEPTH_FAST 1
+
+#define ADC_NUMBER_CHANNELS_FAST 1
+
+// todo: migrate from hardware timer to software ADC conversion triggering
+// todo: I guess we would have to use ChibiOS timer and not our own timer because
+// todo: adcStartConversionI requires OS lock. currently slow ADC is 10Hz (?)
+#define PWM_FREQ_SLOW 5000 /* PWM clock frequency. I wonder what does this setting mean? */
+#define PWM_PERIOD_SLOW 500 /* PWM period (in PWM ticks). */
+
+/**
+ * 8000 RPM is 133Hz
+ * If we want to sample MAP once per 5 degrees we need 133Hz * (360 / 5) = 9576Hz of fast ADC
+ */
+// todo: migrate to continues ADC mode? probably not - we cannot afford the callback in
+// todo: continues mode. todo: look into our options
+#define PWM_FREQ_FAST 100000 /* PWM clock frequency. I wonder what does this setting mean? */
+#define PWM_PERIOD_FAST 10 /* PWM period (in PWM ticks). */
+
+#define ADC_SLOW_DEVICE ADCD1
+
+#define ADC_FAST_DEVICE ADCD2
+
+#define ADC_DEBUG_KEY "adcDebug"
+
+static char LOGGING_BUFFER[500];
+static Logging logger;
+static int adcCallbackCounter_slow = 0;
+
+static int adcDebugReporting = FALSE;
+
+static int fastAdcValue;
+extern engine_configuration_s *engineConfiguration;
+extern board_configuration_s *boardConfiguration;
+
+static adc_hw_helper_s slowAdcState;
+
+/*
+ * ADC samples buffer.
+ */
+static adcsample_t samples_fast[ADC_NUMBER_CHANNELS_FAST * ADC_GRP1_BUF_DEPTH_FAST];
+
+static adcsample_t getAvgAdcValue(int index, adcsample_t *samples, int bufDepth, int numChannels) {
+ adcsample_t result = 0;
+ int i;
+ for (i = 0; i < bufDepth; i++) {
+ result += samples[index];
+ index += numChannels;
+ }
+ return result / bufDepth;
+}
+
+static void adc_callback_slow(ADCDriver *adcp, adcsample_t *buffer, size_t n);
+static void adc_callback_fast(ADCDriver *adcp, adcsample_t *buffer, size_t n);
+
+#define MY_SAMPLING_SLOW ADC_SAMPLE_480
+#define MY_SAMPLING_FAST ADC_SAMPLE_28
+
+/*
+ * ADC conversion group.
+ */
+static ADCConversionGroup adcgrpcfgSlow = { FALSE, 0, adc_callback_slow, NULL,
+/* HW dependent part.*/
+ADC_TwoSamplingDelay_20Cycles, // cr1
+ ADC_CR2_SWSTART, // cr2
+
+ ADC_SMPR1_SMP_AN10(MY_SAMPLING_SLOW) |
+ ADC_SMPR1_SMP_AN11(MY_SAMPLING_SLOW) |
+ ADC_SMPR1_SMP_AN12(MY_SAMPLING_SLOW) |
+ ADC_SMPR1_SMP_AN13(MY_SAMPLING_SLOW), // sample times for channels 10...18
+ ADC_SMPR2_SMP_AN0(MY_SAMPLING_SLOW) |
+ ADC_SMPR2_SMP_AN1(MY_SAMPLING_SLOW) |
+ ADC_SMPR2_SMP_AN3(MY_SAMPLING_SLOW) |
+ ADC_SMPR2_SMP_AN4(MY_SAMPLING_SLOW) |
+ ADC_SMPR2_SMP_AN5(MY_SAMPLING_SLOW) |
+ ADC_SMPR2_SMP_AN6(MY_SAMPLING_SLOW) |
+ ADC_SMPR2_SMP_AN7(MY_SAMPLING_SLOW) |
+ ADC_SMPR2_SMP_AN8(MY_SAMPLING_SLOW) |
+ ADC_SMPR2_SMP_AN9(MY_SAMPLING_SLOW)
+
+ , // In this field must be specified the sample times for channels 0...9
+
+ 0, // Conversion group sequence 13...16 + sequence length
+
+ 0
+// | ADC_SQR2_SQ7_N(ADC_CHANNEL_IN12) /* PC2 - green */
+// | ADC_SQR2_SQ8_N(ADC_CHANNEL_IN13) /* PC3 - yellow maf? */
+
+ ,// Conversion group sequence 7...12
+ 0
+// | ADC_SQR3_SQ1_N(ADC_CHANNEL_IN6) /* PA6 - white */
+// | ADC_SQR3_SQ2_N(ADC_CHANNEL_IN7) /* PA7 - blue */
+// | ADC_SQR3_SQ3_N(ADC_CHANNEL_IN14) /* PC4 - green */
+// | ADC_SQR3_SQ4_N(ADC_CHANNEL_IN15) /* PC5 - yellow */
+// | ADC_SQR3_SQ5_N(ADC_CHANNEL_IN8) /* PB0 - blue */
+// | ADC_SQR3_SQ6_N(ADC_CHANNEL_IN9) /* PB1 - white */
+// Conversion group sequence 1...6
+ };
+
+AdcConfiguration slowAdc(&adcgrpcfgSlow);
+
+static ADCConversionGroup adcgrpcfg_fast = { FALSE, 0 /* num_channels */, adc_callback_fast, NULL,
+/* HW dependent part.*/
+ADC_TwoSamplingDelay_5Cycles, // cr1
+ ADC_CR2_SWSTART, // cr2
+
+ 0, // sample times for channels 10...18
+ ADC_SMPR2_SMP_AN0(MY_SAMPLING_FAST), // In this field must be specified the sample times for channels 0...9
+
+ ADC_SQR1_NUM_CH(ADC_NUMBER_CHANNELS_FAST), // Conversion group sequence 13...16 + sequence length
+
+ 0, // Conversion group sequence 7...12
+ 0
+
+// Conversion group sequence 1...6
+ };
+
+AdcConfiguration fastAdc(&adcgrpcfg_fast);
+
+static void pwmpcb_slow(PWMDriver *pwmp) {
+#if EFI_INTERNAL_ADC
+ (void) pwmp;
+
+ /* Starts an asynchronous ADC conversion operation, the conversion
+ will be executed in parallel to the current PWM cycle and will
+ terminate before the next PWM cycle.*/
+ chSysLockFromIsr()
+ ;
+ if (ADC_FAST_DEVICE.state != ADC_READY &&
+ ADC_FAST_DEVICE.state != ADC_COMPLETE &&
+ ADC_FAST_DEVICE.state != ADC_ERROR) {
+ // todo: why and when does this happen? firmwareError("ADC slow not ready?");
+ slowAdc.errorsCount++;
+ chSysUnlockFromIsr()
+ ;
+ return;
+ }
+ slowAdc.errorsCount++;
+ adcStartConversionI(&ADC_SLOW_DEVICE, &adcgrpcfgSlow, slowAdcState.samples, ADC_GRP1_BUF_DEPTH_SLOW);
+ chSysUnlockFromIsr()
+ ;
+ slowAdc.conversionCount++;
+#endif
+}
+
+static void pwmpcb_fast(PWMDriver *pwmp) {
+#if EFI_INTERNAL_ADC
+ (void) pwmp;
+
+ /*
+ * Starts an asynchronous ADC conversion operation, the conversion
+ * will be executed in parallel to the current PWM cycle and will
+ * terminate before the next PWM cycle.
+ */
+ chSysLockFromIsr()
+ ;
+ if (ADC_FAST_DEVICE.state != ADC_READY &&
+ ADC_FAST_DEVICE.state != ADC_COMPLETE &&
+ ADC_FAST_DEVICE.state != ADC_ERROR) {
+ fastAdc.errorsCount++;
+ // todo: when? why? firmwareError("ADC fast not ready?");
+ chSysUnlockFromIsr()
+ ;
+ return;
+ }
+ adcStartConversionI(&ADC_FAST_DEVICE, &adcgrpcfg_fast, samples_fast, ADC_GRP1_BUF_DEPTH_FAST);
+ chSysUnlockFromIsr()
+ ;
+ fastAdc.conversionCount++;
+#endif
+}
+
+int getInternalAdcValue(adc_channel_e hwChannel) {
+ if (boardConfiguration->adcHwChannelEnabled[hwChannel] == ADC_FAST)
+ return fastAdcValue;
+
+ int internalIndex = slowAdc.internalAdcIndexByHardwareIndex[hwChannel];
+ return slowAdc.getAdcValueByIndex(internalIndex);
+}
+
+static PWMConfig pwmcfg_slow = { PWM_FREQ_SLOW, PWM_PERIOD_SLOW, pwmpcb_slow, { {
+PWM_OUTPUT_DISABLED, NULL }, { PWM_OUTPUT_DISABLED, NULL }, {
+PWM_OUTPUT_DISABLED, NULL }, { PWM_OUTPUT_DISABLED, NULL } },
+/* HW dependent part.*/
+0, 0 };
+
+static PWMConfig pwmcfg_fast = { PWM_FREQ_FAST, PWM_PERIOD_FAST, pwmpcb_fast, { {
+PWM_OUTPUT_DISABLED, NULL }, { PWM_OUTPUT_DISABLED, NULL }, {
+PWM_OUTPUT_DISABLED, NULL }, { PWM_OUTPUT_DISABLED, NULL } },
+/* HW dependent part.*/
+0, 0 };
+
+static void initAdcPin(ioportid_t port, int pin, const char *msg) {
+ print("adc %s\r\n", msg);
+ mySetPadMode("adc input", port, pin, PAL_MODE_INPUT_ANALOG);
+}
+
+adc_channel_e getAdcChannel(brain_pin_e pin) {
+ switch(pin) {
+ case GPIOA_0:
+ return EFI_ADC_0;
+ case GPIOA_1:
+ return EFI_ADC_1;
+ case GPIOA_2:
+ return EFI_ADC_2;
+ case GPIOA_3:
+ return EFI_ADC_3;
+ case GPIOA_4:
+ return EFI_ADC_4;
+ case GPIOA_5:
+ return EFI_ADC_5;
+ case GPIOA_6:
+ return EFI_ADC_6;
+ case GPIOA_7:
+ return EFI_ADC_7;
+ case GPIOB_0:
+ return EFI_ADC_8;
+ case GPIOB_1:
+ return EFI_ADC_9;
+ case GPIOC_0:
+ return EFI_ADC_10;
+ case GPIOC_1:
+ return EFI_ADC_11;
+ case GPIOC_2:
+ return EFI_ADC_12;
+ case GPIOC_3:
+ return EFI_ADC_13;
+ case GPIOC_4:
+ return EFI_ADC_14;
+ case GPIOC_5:
+ return EFI_ADC_15;
+ default:
+ return EFI_ADC_ERROR;
+ }
+}
+
+GPIO_TypeDef* getAdcChannelPort(adc_channel_e hwChannel) {
+ // todo: replace this with an array :)
+ switch (hwChannel) {
+ case ADC_CHANNEL_IN0:
+ return GPIOA;
+ case ADC_CHANNEL_IN1:
+ return GPIOA;
+ case ADC_CHANNEL_IN2:
+ return GPIOA;
+ case ADC_CHANNEL_IN3:
+ return GPIOA;
+ case ADC_CHANNEL_IN4:
+ return GPIOA;
+ case ADC_CHANNEL_IN5:
+ return GPIOA;
+ case ADC_CHANNEL_IN6:
+ return GPIOA;
+ case ADC_CHANNEL_IN7:
+ return GPIOA;
+ case ADC_CHANNEL_IN8:
+ return GPIOB;
+ case ADC_CHANNEL_IN9:
+ return GPIOB;
+ case ADC_CHANNEL_IN10:
+ return GPIOC;
+ case ADC_CHANNEL_IN11:
+ return GPIOC;
+ case ADC_CHANNEL_IN12:
+ return GPIOC;
+ case ADC_CHANNEL_IN13:
+ return GPIOC;
+ case ADC_CHANNEL_IN14:
+ return GPIOC;
+ case ADC_CHANNEL_IN15:
+ return GPIOC;
+ default:
+ firmwareError("Unknown hw channel");
+ return NULL;
+ }
+}
+
+const char * getAdcMode(adc_channel_e hwChannel) {
+ if (slowAdc.isHwUsed(hwChannel)) {
+ return "slow";
+ }
+ if (fastAdc.isHwUsed(hwChannel)) {
+ return "fast";
+ }
+ return "INACTIVE";
+}
+
+int getAdcChannelPin(adc_channel_e hwChannel) {
+ // todo: replace this with an array :)
+ switch (hwChannel) {
+ case ADC_CHANNEL_IN0:
+ return 0;
+ case ADC_CHANNEL_IN1:
+ return 1;
+ case ADC_CHANNEL_IN2:
+ return 2;
+ case ADC_CHANNEL_IN3:
+ return 3;
+ case ADC_CHANNEL_IN4:
+ return 4;
+ case ADC_CHANNEL_IN5:
+ return 5;
+ case ADC_CHANNEL_IN6:
+ return 6;
+ case ADC_CHANNEL_IN7:
+ return 7;
+ case ADC_CHANNEL_IN8:
+ return 0;
+ case ADC_CHANNEL_IN9:
+ return 1;
+ case ADC_CHANNEL_IN10:
+ return 0;
+ case ADC_CHANNEL_IN11:
+ return 1;
+ case ADC_CHANNEL_IN12:
+ return 2;
+ case ADC_CHANNEL_IN13:
+ return 3;
+ case ADC_CHANNEL_IN14:
+ return 4;
+ case ADC_CHANNEL_IN15:
+ return 5;
+ default:
+ firmwareError("Unknown hw channel");
+ return -1;
+ }
+}
+
+static void initAdcHwChannel(adc_channel_e hwChannel) {
+ GPIO_TypeDef* port = getAdcChannelPort(hwChannel);
+ int pin = getAdcChannelPin(hwChannel);
+
+ initAdcPin(port, pin, "hw");
+}
+
+int AdcConfiguration::size() {
+ return channelCount;
+}
+
+int AdcConfiguration::getAdcValueByIndex(int internalIndex) {
+ return values.adc_data[internalIndex];
+}
+
+void AdcConfiguration::init(void) {
+ hwConfig->num_channels = size();
+ hwConfig->sqr1 += ADC_SQR1_NUM_CH(size());
+}
+
+bool AdcConfiguration::isHwUsed(adc_channel_e hwChannelIndex) {
+ for (int i = 0; i < channelCount; i++) {
+ if (hardwareIndexByIndernalAdcIndex[i] == hwChannelIndex) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void AdcConfiguration::addChannel(adc_channel_e hwChannel) {
+ int logicChannel = channelCount++;
+
+ internalAdcIndexByHardwareIndex[hwChannel] = logicChannel;
+ hardwareIndexByIndernalAdcIndex[logicChannel] = hwChannel;
+ if (logicChannel < 6) {
+ hwConfig->sqr3 += (hwChannel) << (5 * logicChannel);
+ } else {
+ hwConfig->sqr2 += (hwChannel) << (5 * (logicChannel - 6));
+ }
+ // todo: support for more then 12 channels? not sure how needed it would be
+
+ initAdcHwChannel(hwChannel);
+}
+
+static void printAdcValue(adc_channel_e channel) {
+ int value = getAdcValue(channel);
+ float volts = adcToVoltsDivided(value);
+ scheduleMsg(&logger, "adc voltage : %f", volts);
+}
+
+adc_channel_e AdcConfiguration::getAdcHardwareIndexByInternalIndex(int index) {
+ return hardwareIndexByIndernalAdcIndex[index];
+}
+
+static void printFullAdcReport(void) {
+ scheduleMsg(&logger, "fast %d slow %d", fastAdc.conversionCount, slowAdc.conversionCount);
+
+ for (int index = 0; index < slowAdc.size(); index++) {
+ appendMsgPrefix(&logger);
+
+ adc_channel_e hwIndex = slowAdc.getAdcHardwareIndexByInternalIndex(index);
+ GPIO_TypeDef* port = getAdcChannelPort(hwIndex);
+ int pin = getAdcChannelPin(hwIndex);
+
+ int adcValue = slowAdc.getAdcValueByIndex(index);
+ appendPrintf(&logger, " ch%d %s%d", index, portname(port), pin);
+ appendPrintf(&logger, " ADC%d 12bit=%d", hwIndex, adcValue);
+ float volts = adcToVolts(adcValue);
+ appendPrintf(&logger, " v=%f", volts);
+
+ appendMsgPostfix(&logger);
+ scheduleLogging(&logger);
+ }
+}
+
+static void printStatus(void) {
+ scheduleIntValue(&logger, ADC_DEBUG_KEY, adcDebugReporting);
+}
+
+static void setAdcDebugReporting(int value) {
+ adcDebugReporting = value;
+ printStatus();
+}
+
+static void adc_callback_slow(ADCDriver *adcp, adcsample_t *buffer, size_t n) {
+ (void) buffer;
+ (void) n;
+ /* Note, only in the ADC_COMPLETE state because the ADC driver fires
+ * an intermediate callback when the buffer is half full. */
+ if (adcp->state == ADC_COMPLETE) {
+ /* Calculates the average values from the ADC samples.*/
+
+ adcCallbackCounter_slow++;
+
+// newState.time = chimeNow();
+ for (int i = 0; i < slowAdc.size(); i++) {
+ int value = getAvgAdcValue(i, slowAdcState.samples, ADC_GRP1_BUF_DEPTH_SLOW, slowAdc.size());
+ slowAdc.values.adc_data[i] = value;
+ }
+ }
+}
+
+static void adc_callback_fast(ADCDriver *adcp, adcsample_t *buffer, size_t n) {
+ (void) buffer;
+ (void) n;
+// /* Note, only in the ADC_COMPLETE state because the ADC driver fires an
+// intermediate callback when the buffer is half full.*/
+ if (adcp->state == ADC_COMPLETE) {
+ fastAdcValue = getAvgAdcValue(0, samples_fast, ADC_GRP1_BUF_DEPTH_FAST, fastAdc.size());
+
+ fastAdc.values.adc_data[0] = fastAdcValue;
+
+#if EFI_MAP_AVERAGING
+ mapAveragingCallback(fastAdcValue);
+#endif /* EFI_MAP_AVERAGING */
+ }
+}
+
+void initAdcInputs(void) {
+
+ initLoggingExt(&logger, "ADC", LOGGING_BUFFER, sizeof(LOGGING_BUFFER));
+ printMsg(&logger, "initAdcInputs()");
+
+ printStatus();
+
+ addConsoleActionI(ADC_DEBUG_KEY, &setAdcDebugReporting);
+
+#if EFI_INTERNAL_ADC
+ /*
+ * Initializes the ADC driver.
+ */
+ adcStart(&ADC_SLOW_DEVICE, NULL);
+ adcStart(&ADC_FAST_DEVICE, NULL);
+
+ for (int adc = 0; adc < HW_MAX_ADC_INDEX; adc++) {
+ adc_channel_mode_e mode = boardConfiguration->adcHwChannelEnabled[adc];
+
+ if (mode == ADC_SLOW) {
+ slowAdc.addChannel((adc_channel_e) (ADC_CHANNEL_IN0 + adc));
+ } else if (mode == ADC_FAST) {
+ fastAdc.addChannel((adc_channel_e) (ADC_CHANNEL_IN0 + adc));
+ }
+ }
+
+ slowAdc.init();
+ pwmStart(EFI_INTERNAL_SLOW_ADC_PWM, &pwmcfg_slow);
+ fastAdc.init();
+ /*
+ * Initializes the PWM driver.
+ */
+ pwmStart(EFI_INTERNAL_FAST_ADC_PWM, &pwmcfg_fast);
+
+ // ADC_CHANNEL_IN0 // PA0
+ // ADC_CHANNEL_IN1 // PA1
+ // ADC_CHANNEL_IN2 // PA2
+ // ADC_CHANNEL_IN3 // PA3
+ // ADC_CHANNEL_IN4 // PA4
+ // ADC_CHANNEL_IN5 // PA5 - this is also TIM2_CH1
+ // ADC_CHANNEL_IN6 // PA6
+ // ADC_CHANNEL_IN7 // PA7
+ // ADC_CHANNEL_IN8 // PB0
+ // ADC_CHANNEL_IN9 // PB1
+ // ADC_CHANNEL_IN10 // PC0
+ // ADC_CHANNEL_IN11 // PC1
+ // ADC_CHANNEL_IN12 // PC2
+ // ADC_CHANNEL_IN13 // PC3
+ // ADC_CHANNEL_IN14 // PC4
+ // ADC_CHANNEL_IN15 // PC5
+
+ //if(slowAdcChannelCount > ADC_MAX_SLOW_CHANNELS_COUNT) // todo: do we need this logic? do we need this check
+
+ addConsoleActionI("adc", (VoidInt) printAdcValue);
+ addConsoleAction("fadc", printFullAdcReport);
+#else
+ printMsg(&logger, "ADC disabled");
+#endif
+}
+
+void pokeAdcInputs() {
+ if (!adcDebugReporting)
+ return;
+ printFullAdcReport();
+}
+
diff --git a/firmware/hw_layer/adc_inputs.h b/firmware/hw_layer/adc_inputs.h
new file mode 100644
index 0000000000..2c92591a78
--- /dev/null
+++ b/firmware/hw_layer/adc_inputs.h
@@ -0,0 +1,58 @@
+/**
+ * @file adc_inputs.h
+ * @brief Low level ADC code
+ *
+ * @date Jan 14, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef ADC_INPUTS_H_
+#define ADC_INPUTS_H_
+
+#include "main.h"
+#include "adc_math.h"
+
+const char * getAdcMode(adc_channel_e hwChannel);
+int getAdcChannelPin(adc_channel_e hwChannel);
+void initAdcInputs(void);
+GPIO_TypeDef* getAdcChannelPort(adc_channel_e hwChannel);
+adc_channel_e getAdcChannel(brain_pin_e pin);
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+int getAdcHardwareIndexByInternalIndex(int index);
+void pokeAdcInputs(void);
+int getInternalAdcValue(adc_channel_e index);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+/* Depth of the conversion buffer, channels are sampled X times each.*/
+#define ADC_GRP1_BUF_DEPTH_SLOW 1
+#define ADC_MAX_CHANNELS_COUNT 16
+
+//typedef struct
+
+// this structure contains one multi-channel ADC state snapshot
+typedef struct {
+ volatile adcsample_t adc_data[ADC_MAX_CHANNELS_COUNT];
+// time_t time;
+} adc_state;
+
+typedef struct {
+ adcsample_t samples[ADC_MAX_CHANNELS_COUNT * ADC_GRP1_BUF_DEPTH_SLOW];
+
+
+} adc_hw_helper_s;
+
+
+#define getAdcValue(hwChannel) getInternalAdcValue(hwChannel)
+
+// todo: migrate to adcToVoltageInputDividerCoefficient
+#define adcToVoltsDivided(adc) (adcToVolts(adc) * engineConfiguration->analogInputDividerCoefficient)
+
+#endif /* ADC_INPUTS_H_ */
diff --git a/firmware/hw_layer/algo/adc_math.h b/firmware/hw_layer/algo/adc_math.h
new file mode 100644
index 0000000000..94f4c38c1b
--- /dev/null
+++ b/firmware/hw_layer/algo/adc_math.h
@@ -0,0 +1,23 @@
+/**
+ * @file adc_math.h
+ *
+ * @date Mar 18, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef ADC_MATH_H_
+#define ADC_MATH_H_
+
+// it is important that this constant is a float, not double literal
+#define ADC_VCC 3.0f
+#define ADC_MAX_VALUE 4095
+
+#define adcToVolts(adc) (ADC_VCC / ADC_MAX_VALUE * (adc))
+
+#define voltsToAdc(volts) ((volts) * (ADC_MAX_VALUE / ADC_VCC))
+
+#define getVoltage(hwChannel) (adcToVolts(getAdcValue(hwChannel)))
+
+#define getVoltageDivided(hwChannel) (getVoltage(hwChannel) * engineConfiguration->analogInputDividerCoefficient)
+
+#endif /* ADC_MATH_H_ */
diff --git a/firmware/hw_layer/board_test.cpp b/firmware/hw_layer/board_test.cpp
new file mode 100644
index 0000000000..113098a2b8
--- /dev/null
+++ b/firmware/hw_layer/board_test.cpp
@@ -0,0 +1,169 @@
+/**
+ * @file board_test.cpp
+ * @brief This is a simple board testing utility
+ *
+ * By default this is enabled by grounding PB0
+ *
+ * @date Mar 12, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ */
+
+#include "main.h"
+#include "board_test.h"
+#include "rusefi_enums.h"
+#include "pin_repository.h"
+#include "gpio_helper.h"
+#include "adc_inputs.h"
+#include "AdcConfiguration.h"
+
+static volatile int stepCoutner = 0;
+static volatile brain_pin_e currentPin = GPIO_NONE;
+
+extern AdcConfiguration slowAdc;
+extern AdcConfiguration fastAdc;
+
+static bool isTimeForNextStep(int copy) {
+ return copy != stepCoutner;
+}
+
+static void processAdcPin(AdcConfiguration *adc, int index, const char *prefix) {
+ adc_channel_e hwIndex = adc->getAdcHardwareIndexByInternalIndex(index);
+ GPIO_TypeDef* port = getAdcChannelPort(hwIndex);
+ int pin = getAdcChannelPin(hwIndex);
+
+ int copy = stepCoutner;
+
+ int c = 0;
+
+ while (!isTimeForNextStep(copy)) {
+ print("%s ch%d hwIndex=%d %s%d\r\n", prefix, index, hwIndex, portname(port), pin);
+ int adcValue = adc->getAdcValueByIndex(index);
+
+// print("ADC%d val= %d%s", hwIndex, value, DELIMETER);
+ float volts = adcToVolts(adcValue) * 2;
+ print("v=%f adc=%d c=%d (hit 'n' for next step\r\n", volts, adcValue, c++);
+
+ chThdSleepMilliseconds(300);
+
+ }
+}
+
+static volatile int currentIndex = 0;
+
+static void waitForKey(void) {
+ print("Please hit N to continue\r\n");
+ int copy = stepCoutner;
+ while (!isTimeForNextStep(copy)) {
+ chThdSleepMilliseconds(200);
+ }
+}
+
+static void nextStep(void) {
+ stepCoutner++;
+}
+
+static void setIndex(int index) {
+ currentIndex = index;
+ nextStep();
+}
+
+static brain_pin_e BLINK_PINS[] = { GPIOE_8, // HIGH DRIVER 1
+ GPIOE_10, // HIGH DRIVER 2
+ GPIOE_12, // HIGH DRIVER 3
+ GPIOE_14, // HIGH DRIVER 4
+ GPIOC_9, // HIGH DRIVER 5
+ GPIOC_7, // HIGH DRIVER 6
+ // index = 6
+ GPIOC_14, // OUT 1
+ GPIOC_15, // OUT2
+ GPIOE_6, // OUT3
+ GPIOC_13, // OUT4
+ GPIOE_4, // OUT5
+ GPIOE_5, // OUT6
+ GPIOE_2, // OUT7
+ GPIOE_3, // OUT8
+ GPIOE_0, // OUT9
+ GPIOE_1, // OUT10
+ GPIOB_8, // OUT11
+ GPIOB_9, // OUT12
+ };
+
+static THD_WORKING_AREA(btThreadStack, UTILITY_THREAD_STACK_SIZE);
+
+static msg_t ivThread(int param) {
+ chRegSetThreadName("board test blinking");
+
+ int value = 0;
+
+ while (TRUE) {
+ chThdSleepMilliseconds(250);
+ value = 1 - value;
+ GPIO_TypeDef *hwPort = getHwPort(currentPin);
+ uint32_t hwPin = getHwPin(currentPin);
+ palWritePad(hwPort, hwPin, value);
+ }
+#if defined __GNUC__
+ return 0;
+#endif
+}
+
+static bool is_board_test_mode = false;
+
+bool isBoardTestMode(void) {
+ return is_board_test_mode;
+}
+
+void printBoardTestState(void) {
+ print("Current index=%d\r\n", currentIndex);
+ print("'n' for next step and 'set X' to return to step X\r\n");
+ print("ADC count: slow %d/fast %d\r\n", slowAdc.size(), fastAdc.size());
+
+ if (currentPin != GPIO_NONE) {
+ print("Blinking %s\r\n", hwPortname(currentPin));
+ }
+}
+
+void initBoardTest(void) {
+ is_board_test_mode = true;
+ addConsoleAction("n", nextStep);
+ addConsoleActionI("set", setIndex);
+
+ chThdCreateStatic(btThreadStack, sizeof(btThreadStack), NORMALPRIO, (tfunc_t) ivThread, NULL);
+
+ // this code is ugly as hell, I had no time to think. Todo: refactor
+
+ processAdcPin(&fastAdc, 0, "fast");
+ while (currentIndex < slowAdc.size()) {
+ processAdcPin(&slowAdc, currentIndex, "slow");
+ currentIndex++;
+ }
+
+ currentIndex = 0;
+
+ int pinsCount = sizeof(BLINK_PINS) / sizeof(brain_pin_e);
+ while (currentIndex < pinsCount) {
+ currentPin = BLINK_PINS[currentIndex];
+
+ GPIO_TypeDef *hwPort = getHwPort(currentPin);
+ uint32_t hwPin = getHwPin(currentPin);
+
+ printBoardTestState();
+ mySetPadMode("test", hwPort, hwPin, PAL_STM32_MODE_OUTPUT);
+
+ currentIndex++;
+ waitForKey();
+ }
+}
diff --git a/firmware/hw_layer/board_test.h b/firmware/hw_layer/board_test.h
new file mode 100644
index 0000000000..a129fbca03
--- /dev/null
+++ b/firmware/hw_layer/board_test.h
@@ -0,0 +1,38 @@
+/**
+ * @file board_test.h
+ *
+ * @date Mar 12, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ *
+ */
+
+#ifndef BOARD_TEST_H_
+#define BOARD_TEST_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void initBoardTest(void);
+bool isBoardTestMode(void);
+void printBoardTestState(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BOARD_TEST_H_ */
diff --git a/firmware/hw_layer/can_hw.cpp b/firmware/hw_layer/can_hw.cpp
new file mode 100644
index 0000000000..23fd72a067
--- /dev/null
+++ b/firmware/hw_layer/can_hw.cpp
@@ -0,0 +1,205 @@
+/**
+ * @file can_hw.cpp
+ * @brief CAN bus low level code
+ *
+ * todo: this file should be split into two - one for CAN transport level ONLY and
+ * another one with actual messages
+ *
+ * @date Dec 11, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+#include "can_hw.h"
+#include "pin_repository.h"
+#include "string.h"
+#include "engine_state.h"
+#include "can_header.h"
+#include "engine_configuration.h"
+
+#if EFI_CAN_SUPPORT || defined(__DOXYGEN__)
+
+static int canReadCounter = 0;
+static Logging logger;
+static THD_WORKING_AREA(canTreadStack, UTILITY_THREAD_STACK_SIZE);
+
+extern engine_configuration_s *engineConfiguration;
+extern engine_configuration2_s *engineConfiguration2;
+extern board_configuration_s *board_configuration;
+
+/*
+ * 500KBaud
+ * automatic wakeup
+ * automatic recover from abort mode
+ * See section 22.7.7 on the STM32 reference manual.
+ *
+ * speed = 42000000 / (BRP + 1) / (1 + TS1 + 1 + TS2 + 1)
+ * 42000000 / 7 / 12 = 500000
+ *
+ *
+ */
+static const CANConfig canConfig = {
+CAN_MCR_ABOM | CAN_MCR_AWUM | CAN_MCR_TXFP,
+CAN_BTR_SJW(0) | CAN_BTR_TS2(1) |
+CAN_BTR_TS1(8) | CAN_BTR_BRP(6) };
+
+static CANRxFrame rxBuffer;
+static CANTxFrame txmsg;
+
+// todo: we would need a data structure here
+static int engine_rpm = 0;
+static float engine_clt = 0;
+
+static void printPacket(CANRxFrame *rx) {
+// scheduleMsg(&logger, "CAN FMI %x", rx->FMI);
+// scheduleMsg(&logger, "TIME %x", rx->TIME);
+ scheduleMsg(&logger, "SID %x/%x %x %x %x %x %x %x %x %x", rx->SID, rx->DLC,
+ rx->data8[0], rx->data8[1], rx->data8[2], rx->data8[3],
+ rx->data8[4], rx->data8[5], rx->data8[6], rx->data8[7]);
+
+ if (rx->SID == CAN_BMW_E46_CLUSTER_STATUS) {
+ int odometerKm = 10 * (rx->data8[1] << 8) + rx->data8[0];
+ int odometerMi = (int)(odometerKm * 0.621371);
+ scheduleMsg(&logger, "GOT odometerKm %d", odometerKm);
+ scheduleMsg(&logger, "GOT odometerMi %d", odometerMi);
+ int timeValue = (rx->data8[4] << 8) + rx->data8[3];
+ scheduleMsg(&logger, "GOT time %d", timeValue);
+ }
+}
+
+static void setShortValue(CANTxFrame *txmsg, int value, int offset) {
+ txmsg->data8[offset] = value;
+ txmsg->data8[offset + 1] = value >> 8;
+}
+
+static void commonTxInit(int eid) {
+ memset(&txmsg, 0, sizeof(txmsg));
+ txmsg.IDE = CAN_IDE_STD;
+ txmsg.EID = eid;
+ txmsg.RTR = CAN_RTR_DATA;
+ txmsg.DLC = 8;
+}
+
+static void canDashboardBMW(void) {
+ //BMW Dashboard
+ commonTxInit(CAN_BMW_E46_SPEED);
+ setShortValue(&txmsg, 10 * 8, 1);
+ canTransmit(&EFI_CAN_DEVICE, CAN_ANY_MAILBOX, &txmsg, TIME_INFINITE );
+
+ commonTxInit(CAN_BMW_E46_RPM);
+ setShortValue(&txmsg, (int)(engine_rpm * 6.4), 2);
+ canTransmit(&EFI_CAN_DEVICE, CAN_ANY_MAILBOX, &txmsg, TIME_INFINITE );
+
+ commonTxInit(CAN_BMW_E46_DME2);
+ setShortValue(&txmsg, (int)((engine_clt + 48.373) / 0.75), 1);
+ canTransmit(&EFI_CAN_DEVICE, CAN_ANY_MAILBOX, &txmsg, TIME_INFINITE );
+}
+
+static void canDashboardFiat(void) {
+ //Fiat Dashboard
+ commonTxInit(CAN_FIAT_MOTOR_INFO);
+ setShortValue(&txmsg, (int)(engine_clt - 40), 3); //Coolant Temp
+ setShortValue(&txmsg, engine_rpm / 32, 6); //RPM
+ canTransmit(&EFI_CAN_DEVICE, CAN_ANY_MAILBOX, &txmsg, TIME_INFINITE );
+}
+
+static void canDashboardVAG(void) {
+ //VAG Dashboard
+ commonTxInit(CAN_VAG_RPM);
+ setShortValue(&txmsg, engine_rpm * 4, 2); //RPM
+ canTransmit(&EFI_CAN_DEVICE, CAN_ANY_MAILBOX, &txmsg, TIME_INFINITE );
+
+ commonTxInit(CAN_VAG_CLT);
+ setShortValue(&txmsg, (int)((engine_clt + 48.373) / 0.75), 1); //Coolant Temp
+ canTransmit(&EFI_CAN_DEVICE, CAN_ANY_MAILBOX, &txmsg, TIME_INFINITE );
+}
+
+static void canInfoNBCBroadcast(can_nbc_e typeOfNBC) {
+ switch (typeOfNBC) {
+ case CAN_BUS_NBC_BMW:
+ canDashboardBMW();
+ break;
+ case CAN_BUS_NBC_FIAT:
+ canDashboardFiat();
+ break;
+ case CAN_BUS_NBC_VAG:
+ canDashboardVAG();
+ break;
+ default:
+ break;
+ }
+}
+
+static void enableCanRead(int value) {
+ engineConfiguration->canReadEnabled = value;
+}
+
+static void canRead(void) {
+ scheduleMsg(&logger, "waiting for CAN");
+ canReceive(&EFI_CAN_DEVICE, CAN_ANY_MAILBOX, &rxBuffer, TIME_INFINITE );
+
+ canReadCounter++;
+ printPacket(&rxBuffer);
+}
+
+
+static void writeStateToCan(void) {
+ engine_rpm = getRpm();
+ engine_clt = getCoolantTemperature();
+
+ canInfoNBCBroadcast(engineConfiguration->can_nbc_type);
+
+}
+
+static msg_t canThread(void *arg) {
+ chRegSetThreadName("CAN");
+ while (TRUE) {
+ if(engineConfiguration->canWriteEnabled)
+ writeStateToCan();
+
+ if(engineConfiguration->canReadEnabled)
+ canRead(); // todo: since this is a blocking operation, do we need a separate thread for 'write'?
+
+
+ chThdSleepMilliseconds(engineConfiguration->can_sleep_period);
+ }
+#if defined __GNUC__
+ return -1;
+#endif
+}
+
+static void canInfo(void) {
+ scheduleMsg(&logger, "CAN TX %s:%d", portname(EFI_CAN_TX_PORT), EFI_CAN_TX_PIN);
+ scheduleMsg(&logger, "CAN RX %s:%d", portname(EFI_CAN_RX_PORT), EFI_CAN_RX_PIN);
+ scheduleMsg(&logger, "canReadEnabled=%d canWriteEnabled=%d", engineConfiguration->canReadEnabled, engineConfiguration->canWriteEnabled);
+
+ scheduleMsg(&logger, "CAN rx count %d", canReadCounter);
+}
+
+static CANDriver *getCanDevice() {
+ if(board)
+}
+
+void initCan(void) {
+ initLogging(&logger, "CAN driver");
+
+#if STM32_CAN_USE_CAN2
+ // CAN1 is required for CAN2
+ canStart(&CAND1, &canConfig);
+ canStart(&CAND2, &canConfig);
+#else
+ canStart(&CAND1, &canConfig);
+#endif
+
+ canStart(&EFI_CAN_DEVICE, &canConfig);
+ chThdCreateStatic(canTreadStack, sizeof(canTreadStack), NORMALPRIO, (tfunc_t) canThread, NULL );
+
+ mySetPadMode("CAN TX", EFI_CAN_TX_PORT, EFI_CAN_TX_PIN, PAL_MODE_ALTERNATE(EFI_CAN_TX_AF));
+ mySetPadMode("CAN RX", EFI_CAN_RX_PORT, EFI_CAN_RX_PIN, PAL_MODE_ALTERNATE(EFI_CAN_RX_AF));
+
+ addConsoleActionI("enable_can_read", enableCanRead);
+
+ addConsoleAction("caninfo", canInfo);
+}
+
+#endif /* EFI_CAN_SUPPORT */
diff --git a/firmware/hw_layer/can_hw.h b/firmware/hw_layer/can_hw.h
new file mode 100644
index 0000000000..223c954bd1
--- /dev/null
+++ b/firmware/hw_layer/can_hw.h
@@ -0,0 +1,25 @@
+/**
+ * @file can_hw.h
+ *
+ * @date Dec 11, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef CAN_HW_H_
+#define CAN_HW_H_
+
+// CAN Bus ID for broadcast
+/**
+ * e46 data is from http://forums.bimmerforums.com/forum/showthread.php?1887229
+ */
+#define CAN_BMW_E46_SPEED 0x153
+#define CAN_BMW_E46_RPM 0x316
+#define CAN_BMW_E46_DME2 0x329
+#define CAN_BMW_E46_CLUSTER_STATUS 0x613
+#define CAN_FIAT_MOTOR_INFO 0x561
+#define CAN_VAG_RPM 0x280
+#define CAN_VAG_CLT 0x289
+
+void initCan(void);
+
+#endif /* CAN_HW_H_ */
diff --git a/firmware/hw_layer/flash.c b/firmware/hw_layer/flash.c
new file mode 100644
index 0000000000..ff0e284a21
--- /dev/null
+++ b/firmware/hw_layer/flash.c
@@ -0,0 +1,268 @@
+/**
+ * @file flash_main.c
+ * @brief Lower-level code related to internal flash memory
+ */
+
+#include "main.h"
+
+#if EFI_INTERNAL_FLASH || defined(__DOXYGEN__)
+
+#include "flash.h"
+#include
+
+size_t flashSectorSize(flashsector_t sector) {
+ if (sector <= 3)
+ return 16 * 1024;
+ else if (sector == 4)
+ return 64 * 1024;
+ else if (sector >= 5 && sector <= 11)
+ return 128 * 1024;
+ return 0;
+}
+
+flashaddr_t flashSectorBegin(flashsector_t sector) {
+ flashaddr_t address = FLASH_BASE;
+ while (sector > 0) {
+ --sector;
+ address += flashSectorSize(sector);
+ }
+ return address;
+}
+
+flashaddr_t flashSectorEnd(flashsector_t sector) {
+ return flashSectorBegin(sector + 1);
+}
+
+flashsector_t flashSectorAt(flashaddr_t address) {
+ flashsector_t sector = 0;
+ while (address >= flashSectorEnd(sector))
+ ++sector;
+ return sector;
+}
+
+/**
+ * @brief Wait for the flash operation to finish.
+ */
+#define flashWaitWhileBusy() { while (FLASH->SR & FLASH_SR_BSY) {} }
+
+/**
+ * @brief Unlock the flash memory for write access.
+ * @return CH_SUCCESS Unlock was successful.
+ * @return CH_FAILED Unlock failed.
+ */
+static bool flashUnlock(void) {
+ /* Check if unlock is really needed */
+ if (!(FLASH->CR & FLASH_CR_LOCK))
+ return CH_SUCCESS;
+
+ /* Write magic unlock sequence */
+ FLASH->KEYR = 0x45670123;
+ FLASH->KEYR = 0xCDEF89AB;
+
+ /* Check if unlock was successful */
+ if (FLASH->CR & FLASH_CR_LOCK)
+ return CH_FAILED;
+ return CH_SUCCESS;
+}
+
+/**
+ * @brief Lock the flash memory for write access.
+ */
+#define flashLock() { FLASH->CR |= FLASH_CR_LOCK; }
+
+int flashSectorErase(flashsector_t sector) {
+ /* Unlock flash for write access */
+ if (flashUnlock() == CH_FAILED)
+ return FLASH_RETURN_NO_PERMISSION;
+
+ /* Wait for any busy flags. */
+ flashWaitWhileBusy();
+
+ /* Setup parallelism before any program/erase */
+ FLASH->CR &= ~FLASH_CR_PSIZE_MASK;
+ FLASH->CR |= FLASH_CR_PSIZE_VALUE;
+
+ /* Start deletion of sector.
+ * SNB(3:1) is defined as:
+ * 0000 sector 0
+ * 0001 sector 1
+ * ...
+ * 1011 sector 11
+ * others not allowed */
+ FLASH->CR &= ~(FLASH_CR_SNB_0 | FLASH_CR_SNB_1 | FLASH_CR_SNB_2 | FLASH_CR_SNB_3);
+ if (sector & 0x1)
+ FLASH->CR |= FLASH_CR_SNB_0;
+ if (sector & 0x2)
+ FLASH->CR |= FLASH_CR_SNB_1;
+ if (sector & 0x4)
+ FLASH->CR |= FLASH_CR_SNB_2;
+ if (sector & 0x8)
+ FLASH->CR |= FLASH_CR_SNB_3;
+ FLASH->CR |= FLASH_CR_SER;
+ FLASH->CR |= FLASH_CR_STRT;
+
+ /* Wait until it's finished. */
+ flashWaitWhileBusy();
+
+ /* Sector erase flag does not clear automatically. */
+ FLASH->CR &= ~FLASH_CR_SER;
+
+ /* Lock flash again */
+ flashLock()
+ ;
+
+ /* Check deleted sector for errors */
+ if (flashIsErased(flashSectorBegin(sector), flashSectorSize(sector)) == FALSE)
+ return FLASH_RETURN_BAD_FLASH; /* Sector is not empty despite the erase cycle! */
+
+ /* Successfully deleted sector */
+ return FLASH_RETURN_SUCCESS;
+}
+
+int flashErase(flashaddr_t address, size_t size) {
+ while (size > 0) {
+ flashsector_t sector = flashSectorAt(address);
+ int err = flashSectorErase(sector);
+ if (err != FLASH_RETURN_SUCCESS)
+ return err;
+ address = flashSectorEnd(sector);
+ size_t sector_size = flashSectorSize(sector);
+ if (sector_size >= size)
+ break;
+ size -= sector_size;
+ }
+
+ return FLASH_RETURN_SUCCESS;
+}
+
+bool flashIsErased(flashaddr_t address, size_t size) {
+ /* Check for default set bits in the flash memory
+ * For efficiency, compare flashdata_t values as much as possible,
+ * then, fallback to byte per byte comparison. */
+ while (size >= sizeof(flashdata_t)) {
+ if (*(volatile flashdata_t*) address != (flashdata_t) (-1)) // flashdata_t being unsigned, -1 is 0xFF..FF
+ return false;
+ address += sizeof(flashdata_t);
+ size -= sizeof(flashdata_t);
+ }
+ while (size > 0) {
+ if (*(char*) address != 0xff)
+ return false;
+ ++address;
+ --size;
+ }
+
+ return TRUE;
+}
+
+bool flashCompare(flashaddr_t address, const char* buffer, size_t size) {
+ /* For efficiency, compare flashdata_t values as much as possible,
+ * then, fallback to byte per byte comparison. */
+ while (size >= sizeof(flashdata_t)) {
+ if (*(volatile flashdata_t*) address != *(flashdata_t*) buffer)
+ return FALSE;
+ address += sizeof(flashdata_t);
+ buffer += sizeof(flashdata_t);
+ size -= sizeof(flashdata_t);
+ }
+ while (size > 0) {
+ if (*(volatile char*) address != *buffer)
+ return FALSE;
+ ++address;
+ ++buffer;
+ --size;
+ }
+
+ return TRUE;
+}
+
+int flashRead(flashaddr_t address, char* buffer, size_t size) {
+ memcpy(buffer, (char*) address, size);
+ return FLASH_RETURN_SUCCESS;
+}
+
+static void flashWriteData(flashaddr_t address, const flashdata_t data) {
+ /* Enter flash programming mode */
+ FLASH->CR |= FLASH_CR_PG;
+
+ /* Write the data */
+ *(flashdata_t*) address = data;
+
+ /* Wait for completion */
+ flashWaitWhileBusy();
+
+ /* Exit flash programming mode */
+ FLASH->CR &= ~FLASH_CR_PG;
+}
+
+int flashWrite(flashaddr_t address, const char* buffer, size_t size) {
+ /* Unlock flash for write access */
+ if (flashUnlock() == CH_FAILED)
+ return FLASH_RETURN_NO_PERMISSION;
+
+ /* Wait for any busy flags */
+ flashWaitWhileBusy();
+
+ /* Setup parallelism before any program/erase */
+ FLASH->CR &= ~FLASH_CR_PSIZE_MASK;
+ FLASH->CR |= FLASH_CR_PSIZE_VALUE;
+
+ /* Check if the flash address is correctly aligned */
+ size_t alignOffset = address % sizeof(flashdata_t);
+// print("flash alignOffset=%d\r\n", alignOffset);
+ if (alignOffset != 0) {
+ /* Not aligned, thus we have to read the data in flash already present
+ * and update them with buffer's data */
+
+ /* Align the flash address correctly */
+ flashaddr_t alignedFlashAddress = address - alignOffset;
+
+ /* Read already present data */
+ flashdata_t tmp = *(volatile flashdata_t*) alignedFlashAddress;
+
+ /* Compute how much bytes one must update in the data read */
+ size_t chunkSize = sizeof(flashdata_t) - alignOffset;
+ if (chunkSize > size)
+ chunkSize = size; // this happens when both address and address + size are not aligned
+
+ /* Update the read data with buffer's data */
+ memcpy((char*) &tmp + alignOffset, buffer, chunkSize);
+
+ /* Write the new data in flash */
+ flashWriteData(alignedFlashAddress, tmp);
+
+ /* Advance */
+ address += chunkSize;
+ buffer += chunkSize;
+ size -= chunkSize;
+ }
+
+ /* Now, address is correctly aligned. One can copy data directly from
+ * buffer's data to flash memory until the size of the data remaining to be
+ * copied requires special treatment. */
+ while (size >= sizeof(flashdata_t)) {
+// print("flash write size=%d\r\n", size);
+ flashWriteData(address, *(const flashdata_t*) buffer);
+ address += sizeof(flashdata_t);
+ buffer += sizeof(flashdata_t);
+ size -= sizeof(flashdata_t);
+ }
+
+ /* Now, address is correctly aligned, but the remaining data are to
+ * small to fill a entier flashdata_t. Thus, one must read data already
+ * in flash and update them with buffer's data before writing an entire
+ * flashdata_t to flash memory. */
+ if (size > 0) {
+ flashdata_t tmp = *(volatile flashdata_t*) address;
+ memcpy(&tmp, buffer, size);
+ flashWriteData(address, tmp);
+ }
+
+ /* Lock flash again */
+ flashLock()
+ ;
+
+ return FLASH_RETURN_SUCCESS;
+}
+
+#endif /* EFI_INTERNAL_FLASH */
diff --git a/firmware/hw_layer/flash.h b/firmware/hw_layer/flash.h
new file mode 100644
index 0000000000..19ce09f5db
--- /dev/null
+++ b/firmware/hw_layer/flash.h
@@ -0,0 +1,169 @@
+#ifndef FLASH_H
+#define FLASH_H
+
+#include "main.h"
+#include
+
+/**
+ * @brief Number of sectors in the flash memory.
+ */
+#if !defined(FLASH_SECTOR_COUNT) || defined(__DOXYGEN__)
+#define FLASH_SECTOR_COUNT 12
+#endif
+
+/* Error codes */
+
+/** @brief Flash operation successful */
+#define FLASH_RETURN_SUCCESS CH_SUCCESS
+
+/** @brief Flash operation error because of denied access, corrupted memory.*/
+#define FLASH_RETURN_NO_PERMISSION -1
+
+/** @brief Flash operation error because of bad flash, corrupted memory */
+#define FLASH_RETURN_BAD_FLASH -11
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Maximum program/erase parallelism
+ *
+ * FLASH_CR_PSIZE_MASK is the mask to configure the parallelism value.
+ * FLASH_CR_PSIZE_VALUE is the parallelism value suitable for the voltage range.
+ *
+ * PSIZE(1:0) is defined as:
+ * 00 to program 8 bits per step
+ * 01 to program 16 bits per step
+ * 10 to program 32 bits per step
+ * 11 to program 64 bits per step
+ */
+// Warning, flashdata_t must be unsigned!!!
+#if defined(STM32F4XX) || defined(__DOXYGEN__)
+#define FLASH_CR_PSIZE_MASK FLASH_CR_PSIZE_0 | FLASH_CR_PSIZE_1
+#if ((STM32_VDD >= 270) && (STM32_VDD <= 360)) || defined(__DOXYGEN__)
+#define FLASH_CR_PSIZE_VALUE FLASH_CR_PSIZE_1
+typedef uint32_t flashdata_t;
+#elif (STM32_VDD >= 240) && (STM32_VDD < 270)
+#define FLASH_CR_PSIZE_VALUE FLASH_CR_PSIZE_0
+typedef uint16_t flashdata_t;
+#elif (STM32_VDD >= 210) && (STM32_VDD < 240)
+#define FLASH_CR_PSIZE_VALUE FLASH_CR_PSIZE_0
+typedef uint16_t flashdata_t;
+#elif (STM32_VDD >= 180) && (STM32_VDD < 210)
+#define FLASH_CR_PSIZE_VALUE ((uint32_t)0x00000000)
+typedef uint8_t flashdata_t;
+#else
+#error "invalid VDD voltage specified"
+#endif
+#endif /* defined(STM32F4XX) */
+
+/** @brief Address in the flash memory */
+typedef uintptr_t flashaddr_t;
+
+/** @brief Index of a sector */
+typedef uint8_t flashsector_t;
+
+/**
+ * @brief Get the size of @p sector.
+ * @return @p sector size in bytes.
+ */
+size_t flashSectorSize(flashsector_t sector);
+
+/**
+ * @brief Get the beginning address of @p sector.
+ * @param sector Sector to retrieve the beginning address of.
+ * @return First address (inclusive) of @p sector.
+ */
+flashaddr_t flashSectorBegin(flashsector_t sector);
+
+/**
+ * @brief Get the end address of @p sector.
+ * @param sector Sector to retrieve the end address of.
+ * @return End address (exclusive) of @p sector (i.e. beginning address of the next sector).
+ */
+flashaddr_t flashSectorEnd(flashsector_t sector);
+
+/**
+ * @brief Get the sector containing @p address.
+ * @warning @p address must be in the flash addresses range.
+ * @param address Address to be searched for.
+ * @return Sector containing @p address.
+ */
+flashsector_t flashSectorAt(flashaddr_t address);
+
+/**
+ * @brief Erase the flash @p sector.
+ * @details The sector is checked for errors after erase.
+ * @note The sector is deleted regardless of its current state.
+ *
+ * @param sector Sector which is going to be erased.
+ * @return FLASH_RETURN_SUCCESS No error erasing the sector.
+ * @return FLASH_RETURN_BAD_FLASH Flash cell error.
+ * @return FLASH_RETURN_NO_PERMISSION Access denied.
+ */
+int flashSectorErase(flashsector_t sector);
+
+/**
+ * @brief Erase the sectors containing the span of @p size bytes starting at @p address.
+ *
+ * @warning If @p address doesn't match the beginning of a sector, the
+ * data contained between the beginning of the sector and @p address will
+ * be erased too. The same applies for data contained at @p address + @p size
+ * up to the end of the sector.
+ *
+ * @param address Starting address of the span in flash memory.
+ * @param size Size of the span in bytes.
+ * @return FLASH_RETURN_SUCCESS No error erasing the flash memory.
+ * @return FLASH_RETURN_BAD_FLASH Flash cell error.
+ * @return FLASH_RETURN_NO_PERMISSION Access denied.
+ */
+int flashErase(flashaddr_t address, size_t size);
+
+/**
+ * @brief Check if the @p size bytes of flash memory starting at @p address are erased.
+ * @note If the memory is erased, one can write data into it safely.
+ * @param address First address in flash memory to be checked.
+ * @param size Size of the memory space to be checked in bytes.
+ * @return TRUE Memory is already erased.
+ * @return FALSE Memory is not erased.
+ */
+bool flashIsErased(flashaddr_t address, size_t size);
+
+/**
+ * @brief Check if the data in @p buffer are identical to the one in flash memory.
+ * @param address First address in flash memory to be checked.
+ * @param buffer Buffer containing the data to compare.
+ * @param size Size of @p buffer in bytes.
+ * @return TRUE if the flash memory and the buffer contain identical data.
+ * @return FALSE if the flash memory and the buffer don't contain identical data.
+ */
+bool flashCompare(flashaddr_t address, const char* buffer, size_t size);
+
+/**
+ * @brief Copy data from the flash memory to a @p buffer.
+ * @warning The @p buffer must be at least @p size bytes long.
+ * @param address First address of the flash memory to be copied.
+ * @param buffer Buffer to copy to.
+ * @param size Size of the data to be copied in bytes.
+ * @return FLASH_RETURN_SUCCESS if successfully copied.
+ */
+int flashRead(flashaddr_t address, char* buffer, size_t size);
+
+/**
+ * @brief Copy data from a @p buffer to the flash memory.
+ * @warning The flash memory area receiving the data must be erased.
+ * @warning The @p buffer must be at least @p size bytes long.
+ * @param address First address in the flash memory where to copy the data to.
+ * @param buffer Buffer containing the data to copy.
+ * @param size Size of the data to be copied in bytes.
+ * @return FLASH_RETURN_SUCCESS No error.
+ * @return FLASH_RETURN_NO_PERMISSION Access denied.
+ */
+int flashWrite(flashaddr_t address, const char* buffer, size_t size);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FLASH_H */
diff --git a/firmware/hw_layer/gpio_helper.c b/firmware/hw_layer/gpio_helper.c
new file mode 100644
index 0000000000..b3735f5c60
--- /dev/null
+++ b/firmware/hw_layer/gpio_helper.c
@@ -0,0 +1,65 @@
+/**
+ * @file gpio_helper.c
+ * @brief General I/O helper
+ *
+ * @date Aug 25, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ */
+
+#ifndef GPIO_HELPER_C_
+#define GPIO_HELPER_C_
+
+#include
+
+#include "pin_repository.h"
+#include "gpio_helper.h"
+
+/**
+ * @brief Initialize the hardware output pin while also assigning it a logical name
+ */
+void initOutputPinExt(const char *msg, OutputPin *outputPin, GPIO_TypeDef *port, uint32_t pinNumber, iomode_t mode) {
+// if (outputPin->port != NULL) {
+// todo: need to clear '&outputs' in io_pins.c
+// firmwareError("outputPin already assigned to %x%d", outputPin->port, outputPin->pin);
+// return;
+// }
+ outputPin->currentLogicValue = INITIAL_PIN_STATE;
+ outputPin->port = port;
+ outputPin->pin = pinNumber;
+
+ mySetPadMode(msg, port, pinNumber, mode);
+}
+
+void initOutputPin(const char *msg, OutputPin *outputPin, GPIO_TypeDef *port, uint32_t pinNumber) {
+ initOutputPinExt(msg, outputPin, port, pinNumber, PAL_MODE_OUTPUT_PUSHPULL);
+}
+
+int getLogicPinValue(OutputPin * outputPin) {
+ return outputPin->currentLogicValue;
+}
+
+/**
+ * Set's the value of the pin. On this layer the value is assigned as is, without any conversion.
+ */
+void setPinValue(OutputPin * outputPin, int electricalValue, int logicValue) {
+ if (getLogicPinValue(outputPin) == logicValue)
+ return;
+
+ palWritePad(outputPin->port, outputPin->pin, electricalValue);
+ outputPin->currentLogicValue = logicValue;
+}
+
+#endif /* GPIO_HELPER_C_ */
diff --git a/firmware/hw_layer/gpio_helper.h b/firmware/hw_layer/gpio_helper.h
new file mode 100644
index 0000000000..827634b2d9
--- /dev/null
+++ b/firmware/hw_layer/gpio_helper.h
@@ -0,0 +1,33 @@
+/*
+ * @file gpio_helper.h
+ * @brief General I/O helper
+ *
+ * @date Aug 25, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef GPIO_HELPER_H_
+#define GPIO_HELPER_H_
+
+#define INITIAL_PIN_STATE -1
+
+
+/**
+ * @brief Single output pin reference and state
+ */
+typedef struct {
+ GPIO_TypeDef *port;
+ int pin;
+ /**
+ * we track current pin status so that we do not touch the actual hardware if we want to write new pin bit
+ * which is same as current pin value. This maybe helps in case of status leds, but maybe it's a total over-engineering
+ */
+ int currentLogicValue;
+} OutputPin;
+
+void initOutputPin(const char *msg, OutputPin *outputPin, GPIO_TypeDef *port, uint32_t pinNumber);
+void initOutputPinExt(const char *msg, OutputPin *outputPin, GPIO_TypeDef *port, uint32_t pinNumber, iomode_t mode);
+int getLogicPinValue(OutputPin * outputPin);
+void setPinValue(OutputPin * outputPin, int electricalValue, int logicValue);
+
+#endif /* GPIO_HELPER_H_ */
diff --git a/firmware/hw_layer/hardware.cpp b/firmware/hw_layer/hardware.cpp
new file mode 100644
index 0000000000..691011a22b
--- /dev/null
+++ b/firmware/hw_layer/hardware.cpp
@@ -0,0 +1,262 @@
+/**
+ * @file hardware.cpp
+ * @brief Hardware package entry point
+ *
+ * @date May 27, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+#include "hardware.h"
+#include "pin_repository.h"
+#include "io_pins.h"
+#include "rtc_helper.h"
+#include "rfiutil.h"
+#include "console_io.h"
+
+#include "adc_inputs.h"
+
+#include "trigger_input.h"
+#include "eficonsole.h"
+#include "board_test.h"
+
+#include "mcp3208.h"
+#include "HIP9011.h"
+#include "can_hw.h"
+#include "histogram.h"
+#include "mmc_card.h"
+#include "neo6m.h"
+#include "lcd_HD44780.h"
+#include "settings.h"
+#include "algo.h"
+
+#if EFI_INTERNAL_FLASH
+#include "flash_main.h"
+#endif /* EFI_INTERNAL_FLASH */
+
+#include "trigger_central.h"
+#include "svnversion.h"
+#include "engine_configuration.h"
+#include "ec2.h"
+
+extern engine_configuration_s *engineConfiguration;
+extern engine_configuration2_s * engineConfiguration2;
+extern board_configuration_s *boardConfiguration;
+
+static bool isSpiInitialized[5] = { false, false, false, false, false };
+
+static void initSpiModule(SPIDriver *driver, ioportid_t sckPort, ioportmask_t sckPin, ioportid_t misoPort,
+ ioportmask_t misoPin, ioportid_t mosiPort, ioportmask_t mosiPin, int af) {
+ mySetPadMode("SPI clock", sckPort, sckPin, PAL_MODE_ALTERNATE(af));
+
+ mySetPadMode("SPI master out", mosiPort, mosiPin, PAL_MODE_ALTERNATE(af));
+ mySetPadMode("SPI master in ", misoPort, misoPin, PAL_MODE_ALTERNATE(af));
+}
+
+static Mutex spiMtx;
+
+/**
+ * Only one consumer can use SPI bus at a given time
+ */
+void lockSpi(spi_device_e device) {
+ // todo: different locks for different SPI devices!
+ chMtxLock(&spiMtx);
+}
+
+void unlockSpi(void) {
+ chMtxUnlock();
+}
+
+void turnOnSpi(spi_device_e device) {
+ if (isSpiInitialized[device])
+ return; // already initialized
+ isSpiInitialized[device] = true;
+ if (device == SPI_DEVICE_1) {
+#if STM32_SPI_USE_SPI1
+// scheduleMsg(&logging, "Turning on SPI1 pins");
+ initSpiModule(&SPID1,
+ EFI_SPI1_SCK_PORT, EFI_SPI1_SCK_PIN,
+ EFI_SPI1_MISO_PORT, EFI_SPI1_MISO_PIN,
+ EFI_SPI1_MOSI_PORT, EFI_SPI1_MOSI_PIN,
+ EFI_SPI1_AF);
+#endif
+ }
+ if (device == SPI_DEVICE_2) {
+#if STM32_SPI_USE_SPI2
+// scheduleMsg(&logging, "Turning on SPI2 pins");
+ initSpiModule(&SPID2,
+ EFI_SPI2_SCK_PORT, EFI_SPI2_SCK_PIN,
+ EFI_SPI2_MISO_PORT, EFI_SPI2_MISO_PIN,
+ EFI_SPI2_MOSI_PORT, EFI_SPI2_MOSI_PIN,
+ EFI_SPI2_AF);
+#endif
+ }
+ if (device == SPI_DEVICE_3) {
+#if STM32_SPI_USE_SPI3
+// scheduleMsg(&logging, "Turning on SPI3 pins");
+ initSpiModule(&SPID3,
+ EFI_SPI3_SCK_PORT, EFI_SPI3_SCK_PIN,
+ EFI_SPI3_MISO_PORT, EFI_SPI3_MISO_PIN,
+ EFI_SPI3_MOSI_PORT, EFI_SPI3_MOSI_PIN,
+ EFI_SPI3_AF);
+#endif
+ }
+}
+
+void initSpiModules(void) {
+ if (boardConfiguration->is_enabled_spi_2) {
+ turnOnSpi(SPI_DEVICE_2);
+ }
+ if (boardConfiguration->is_enabled_spi_3) {
+ turnOnSpi(SPI_DEVICE_3);
+ }
+}
+
+static I2CConfig i2cfg = { OPMODE_I2C, 100000, STD_DUTY_CYCLE, };
+
+void initI2Cmodule(void) {
+ print("Starting I2C module\r\n");
+ i2cInit();
+ i2cStart(&I2CD1, &i2cfg);
+
+ mySetPadMode("I2C clock", EFI_I2C_SCL_PORT, EFI_I2C_SCL_PIN,
+ PAL_MODE_ALTERNATE(EFI_I2C_AF) | PAL_STM32_OTYPE_OPENDRAIN);
+ mySetPadMode("I2C data", EFI_I2C_SDA_PORT, EFI_I2C_SDA_PIN,
+ PAL_MODE_ALTERNATE(EFI_I2C_AF) | PAL_STM32_OTYPE_OPENDRAIN);
+}
+
+//static char txbuf[1];
+
+static void sendI2Cbyte(int addr, int data) {
+// i2cAcquireBus(&I2CD1);
+// txbuf[0] = data;
+// i2cMasterTransmit(&I2CD1, addr, txbuf, 1, NULL, 0);
+// i2cReleaseBus(&I2CD1);
+}
+
+void initHardware(Logging *logger) {
+ printMsg(logger, "initHardware()");
+ // todo: enable protection. it's disabled because it takes
+ // 10 extra seconds to re-flash the chip
+ //flashProtect();
+
+ chMtxInit(&spiMtx);
+
+#if EFI_HISTOGRAMS
+ /**
+ * histograms is a data structure for CPU monitor, it does not depend on configuration
+ */
+ initHistogramsModule();
+#endif /* EFI_HISTOGRAMS */
+
+ /**
+ * We need the LED_ERROR pin even before we read configuration
+ */
+ initPrimaryPins();
+
+ if (hasFirmwareError())
+ return;
+
+ initDataStructures(engineConfiguration);
+
+#if EFI_INTERNAL_FLASH
+
+ palSetPadMode(CONFIG_RESET_SWITCH_PORT, CONFIG_RESET_SWITCH_PIN, PAL_MODE_INPUT_PULLUP);
+
+ initFlash();
+ /**
+ * this call reads configuration from flash memory or sets default configuration
+ * if flash state does not look right.
+ */
+ if (SHOULD_INGORE_FLASH()) {
+ engineConfiguration->engineType = FORD_ASPIRE_1996;
+ resetConfigurationExt(logger, engineConfiguration->engineType, engineConfiguration, engineConfiguration2,
+ boardConfiguration);
+ writeToFlash();
+ } else {
+ readFromFlash();
+ }
+#else
+ engineConfiguration->engineType = FORD_ASPIRE_1996;
+ resetConfigurationExt(logger, engineConfiguration->engineType, engineConfiguration, engineConfiguration2, boardConfiguration);
+#endif /* EFI_INTERNAL_FLASH */
+
+ if (hasFirmwareError())
+ return;
+
+ mySetPadMode("board test", getHwPort(boardConfiguration->boardTestModeJumperPin),
+ getHwPin(boardConfiguration->boardTestModeJumperPin), PAL_MODE_INPUT_PULLUP);
+ bool isBoardTestMode_b = GET_BOARD_TEST_MODE_VALUE();
+
+ initAdcInputs();
+
+ if (isBoardTestMode_b) {
+ initBoardTest();
+ efiAssertVoid(FALSE, "board test done");
+ }
+
+ initRtc();
+
+ initOutputPins();
+
+#if EFI_HIP_9011
+ initHip9011();
+#endif /* EFI_HIP_9011 */
+
+#if EFI_CAN_SUPPORT
+ initCan();
+#endif /* EFI_CAN_SUPPORT */
+
+// init_adc_mcp3208(&adcState, &SPID2);
+// requestAdcValue(&adcState, 0);
+
+ // todo: figure out better startup logic
+ initTriggerCentral();
+
+#if EFI_SHAFT_POSITION_INPUT
+ initShaftPositionInputCapture();
+#endif /* EFI_SHAFT_POSITION_INPUT */
+
+ initSpiModules();
+
+#if EFI_FILE_LOGGING
+ initMmcCard();
+#endif /* EFI_FILE_LOGGING */
+
+// initFixedLeds();
+
+ // initBooleanInputs();
+
+#if EFI_UART_GPS
+ initGps();
+#endif
+
+#if ADC_SNIFFER
+ initAdcDriver();
+#endif
+
+#if EFI_HD44780_LCD
+// initI2Cmodule();
+ lcd_HD44780_init();
+ if (hasFirmwareError())
+ return;
+
+ lcd_HD44780_print_string(VCS_VERSION);
+
+#endif /* EFI_HD44780_LCD */
+
+ addConsoleActionII("i2c", sendI2Cbyte);
+
+// while (true) {
+// for (int addr = 0x20; addr < 0x28; addr++) {
+// sendI2Cbyte(addr, 0);
+// int err = i2cGetErrors(&I2CD1);
+// print("I2C: err=%x from %d\r\n", err, addr);
+// chThdSleepMilliseconds(5);
+// sendI2Cbyte(addr, 255);
+// chThdSleepMilliseconds(5);
+// }
+// }
+
+ printMsg(logger, "initHardware() OK!");
+}
diff --git a/firmware/hw_layer/hardware.h b/firmware/hw_layer/hardware.h
new file mode 100644
index 0000000000..7b53155820
--- /dev/null
+++ b/firmware/hw_layer/hardware.h
@@ -0,0 +1,30 @@
+/*
+ * hardware.h
+ *
+ * @date May 27, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef HARDWARE_H_
+#define HARDWARE_H_
+
+#include "main.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void turnOnSpi(spi_device_e device);
+void lockSpi(spi_device_e device);
+void unlockSpi(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#define GET_BOARD_TEST_MODE_VALUE() (!palReadPad(getHwPort(boardConfiguration->boardTestModeJumperPin), getHwPin(boardConfiguration->boardTestModeJumperPin)))
+
+void initHardware(Logging *logging);
+
+#endif /* HARDWARE_H_ */
diff --git a/firmware/hw_layer/hw_layer.mk b/firmware/hw_layer/hw_layer.mk
new file mode 100644
index 0000000000..89c54dec85
--- /dev/null
+++ b/firmware/hw_layer/hw_layer.mk
@@ -0,0 +1,24 @@
+
+HW_LAYERSRC = $(PROJECT_DIR)/hw_layer/pin_repository.c \
+ $(PROJECT_DIR)/hw_layer/io_pins.c \
+ $(PROJECT_DIR)/hw_layer/mcp3208.c \
+ $(PROJECT_DIR)/hw_layer/lcd/lcd_HD44780.c \
+ $(PROJECT_DIR)/hw_layer/HIP9011.c \
+ $(PROJECT_DIR)/hw_layer/microsecond_timer.c \
+ $(PROJECT_DIR)/hw_layer/serial_over_usb/usbcfg.c \
+ $(PROJECT_DIR)/hw_layer/serial_over_usb/usbconsole.c \
+ $(PROJECT_DIR)/hw_layer/flash.c \
+ $(PROJECT_DIR)/hw_layer/rtc_helper.c \
+ $(PROJECT_DIR)/hw_layer/mmc_card.c \
+ $(PROJECT_DIR)/hw_layer/neo6m.c \
+ $(PROJECT_DIR)/hw_layer/gpio_helper.c \
+ $(PROJECT_DIR)/hw_layer/wave_analyzer_hw.c
+
+HW_LAYER_SRC_CPP = $(PROJECT_DIR)/hw_layer/hardware.cpp \
+ $(PROJECT_DIR)/hw_layer/can_hw.cpp \
+ $(PROJECT_DIR)/hw_layer/adc_inputs.cpp \
+ $(PROJECT_DIR)/hw_layer/board_test.cpp \
+ $(PROJECT_DIR)/hw_layer/pwm_generator.cpp \
+ $(PROJECT_DIR)/hw_layer/trigger_input.cpp \
+ $(PROJECT_DIR)/hw_layer/stm32f4/mpu_util.cpp
+
\ No newline at end of file
diff --git a/firmware/hw_layer/io_pins.c b/firmware/hw_layer/io_pins.c
new file mode 100644
index 0000000000..cff56f98db
--- /dev/null
+++ b/firmware/hw_layer/io_pins.c
@@ -0,0 +1,257 @@
+/**
+ * @file io_pins.c
+ * @brief It could be that the main purpose of this file is the status LED blinking
+ *
+ * @date Jan 24, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include
+#include "main.h"
+#include "io_pins.h"
+
+#include "pin_repository.h"
+#include "gpio_helper.h"
+#include "status_loop.h"
+#include "engine_configuration.h"
+#include "console_io.h"
+#include "flash_main.h"
+
+#if EFI_ENGINE_CONTROL
+#include "main_trigger_callback.h"
+#endif /* EFI_ENGINE_CONTROL */
+
+extern board_configuration_s *boardConfiguration;
+
+static pin_output_mode_e *pinDefaultState[IO_PIN_COUNT];
+static OutputPin outputs[IO_PIN_COUNT];
+static io_pin_e leds[] = { LED_WARNING, LED_RUNNING, LED_ERROR, LED_COMMUNICATION_1, LED_DEBUG, LED_EXT_1,
+ LED_CHECK_ENGINE };
+
+static GPIO_TypeDef *PORTS[] = { GPIOA, GPIOB, GPIOC, GPIOD, GPIOE, GPIOF, GPIOG, GPIOH };
+
+static pin_output_mode_e DEFAULT_OUTPUT = OM_DEFAULT;
+
+/**
+ * blinking thread to show that we are alive
+ */
+static THD_WORKING_AREA(comBlinkingStack, UTILITY_THREAD_STACK_SIZE);
+
+/**
+ * error thread to show error condition (blinking LED means non-fatal error)
+ */
+static THD_WORKING_AREA(errBlinkingStack, UTILITY_THREAD_STACK_SIZE);
+
+void turnOutputPinOn(io_pin_e pin) {
+ setOutputPinValue(pin, TRUE);
+}
+
+void turnOutputPinOff(io_pin_e pin) {
+ setOutputPinValue(pin, FALSE);
+}
+
+inline static void assertOMode(pin_output_mode_e mode) {
+ // mode >= 0 is always true since that's an unsigned
+ efiAssertVoid(mode <= OM_OPENDRAIN_INVERTED, "invalid pin_output_mode_e");
+}
+
+/**
+ * @brief Sets the value according to current electrical settings
+ */
+void setOutputPinValue(io_pin_e pin, int logicValue) {
+ if (outputs[pin].port == GPIO_NULL )
+ return;
+ efiAssertVoid(pinDefaultState[pin]!=NULL, "pin mode not initialized");
+ pin_output_mode_e mode = *pinDefaultState[pin];
+ setPinValue(&outputs[pin], getElectricalValue(logicValue, mode), logicValue);
+}
+
+int getOutputPinValue(io_pin_e pin) {
+ return getLogicPinValue(&outputs[pin]);
+}
+
+void setDefaultPinState(io_pin_e pin, pin_output_mode_e *outputMode) {
+ pin_output_mode_e mode = *outputMode;
+ assertOMode(mode);
+ pinDefaultState[pin] = outputMode;
+ setOutputPinValue(pin, FALSE); // initial state
+}
+
+static void comBlinkingThread(void *arg) {
+ chRegSetThreadName("communication blinking");
+ while (TRUE) {
+ int delay;
+ if (getNeedToWriteConfiguration()) {
+ delay = isConsoleReady() ? 200 : 66;
+ } else {
+ delay = isConsoleReady() ? 100 : 33;
+ }
+
+ setOutputPinValue(LED_COMMUNICATION_1, 0);
+ setOutputPinValue(LED_EXT_1, 1);
+// setOutputPinValue(LED_EXT_2, 1);
+// setOutputPinValue(LED_EXT_3, 1);
+ chThdSleepMilliseconds(delay);
+
+ setOutputPinValue(LED_COMMUNICATION_1, 1);
+ setOutputPinValue(LED_EXT_1, 0);
+// setOutputPinValue(LED_EXT_2, 0);
+// setOutputPinValue(LED_EXT_3, 0);
+ chThdSleepMilliseconds(delay);
+ }
+}
+
+// todo: fix this, should be a proper declaration in a .h file
+int isTriggerDecoderError(void);
+
+static void errBlinkingThread(void *arg) {
+ chRegSetThreadName("err blinking");
+#if EFI_ENGINE_CONTROL
+ while (TRUE) {
+ int delay = 33;
+ if (isTriggerDecoderError() || isIgnitionTimingError())
+ setOutputPinValue(LED_WARNING, 1);
+ chThdSleepMilliseconds(delay);
+ setOutputPinValue(LED_WARNING, 0);
+ chThdSleepMilliseconds(delay);
+ }
+#endif /* EFI_ENGINE_CONTROL */
+}
+
+static void outputPinRegisterExt(const char *msg, io_pin_e ioPin, GPIO_TypeDef *port, uint32_t pin,
+ pin_output_mode_e *outputMode) {
+ efiAssertVoid((int)ioPin < IO_PIN_COUNT, "io pin out of range");
+ if (port == GPIO_NULL ) {
+ // that's for GRIO_NONE
+ outputs[ioPin].port = port;
+ return;
+ }
+
+ assertOMode(*outputMode);
+ iomode_t mode =
+ (*outputMode == OM_DEFAULT || *outputMode == OM_INVERTED) ?
+ PAL_MODE_OUTPUT_PUSHPULL : PAL_MODE_OUTPUT_OPENDRAIN;
+
+ initOutputPinExt(msg, &outputs[ioPin], port, pin, mode);
+
+ setDefaultPinState(ioPin, outputMode);
+}
+
+GPIO_TypeDef * getHwPort(brain_pin_e brainPin) {
+ if (brainPin == GPIO_NONE)
+ return GPIO_NULL ;
+ if (brainPin > GPIO_NONE) {
+ firmwareError("Invalid brain_pin_e: %d", brainPin);
+ return GPIO_NULL ;
+ }
+ return PORTS[brainPin / 16];
+}
+
+ioportmask_t getHwPin(brain_pin_e brainPin) {
+ if (brainPin == GPIO_NONE)
+ return EFI_ERROR_CODE;
+ if (brainPin > GPIO_NONE) {
+ firmwareError("Invalid brain_pin_e: %d", brainPin);
+ return EFI_ERROR_CODE;
+ }
+ return brainPin % 16;
+}
+
+void outputPinRegisterExt2(const char *msg, io_pin_e ioPin, brain_pin_e brainPin, pin_output_mode_e *outputMode) {
+ GPIO_TypeDef *hwPort = getHwPort(brainPin);
+ int hwPin = getHwPin(brainPin);
+
+ outputPinRegisterExt(msg, ioPin, hwPort, hwPin, outputMode);
+}
+
+void outputPinRegister(const char *msg, io_pin_e ioPin, GPIO_TypeDef *port, uint32_t pin) {
+ outputPinRegisterExt(msg, ioPin, port, pin, &DEFAULT_OUTPUT);
+}
+
+/**
+ * This method would blink all the LEDs just to test them
+ */
+static void initialLedsBlink(void) {
+ int size = sizeof(leds) / sizeof(leds[0]);
+ for (int i = 0; i < size; i++)
+ setOutputPinValue(leds[i], 1);
+
+ chThdSleepMilliseconds(100);
+
+ for (int i = 0; i < size; i++)
+ setOutputPinValue(leds[i], 0);
+}
+
+void initPrimaryPins(void) {
+ outputPinRegister("LED_ERROR", LED_ERROR, LED_ERROR_PORT, LED_ERROR_PIN);
+}
+
+void initOutputPins(void) {
+ outputPinRegister("warning", LED_WARNING, LED_WARNING_PORT, LED_WARNING_PIN);
+ outputPinRegister("is running status", LED_RUNNING, LED_RUNNING_STATUS_PORT, LED_RUNNING_STATUS_PIN);
+ outputPinRegister("communication status 1", LED_COMMUNICATION_1, LED_COMMUNICATION_PORT, LED_COMMUNICATION_PIN);
+
+ /**
+ * want to make sure it's all zeros so that we can compare in initOutputPinExt() method
+ */
+// todo: it's too late to clear now? this breaks default status LEDs
+// todo: fix this?
+// memset(&outputs, 0, sizeof(outputs));
+// outputPinRegister("ext led 1", LED_EXT_1, EXTRA_LED_1_PORT, EXTRA_LED_1_PIN);
+// outputPinRegister("ext led 2", LED_EXT_2, EXTRA_LED_2_PORT, EXTRA_LED_2_PIN);
+// outputPinRegister("ext led 3", LED_EXT_3, EXTRA_LED_2_PORT, EXTRA_LED_3_PIN);
+// outputPinRegister("alive1", LED_DEBUG, GPIOD, 6);
+ outputPinRegister("MalfunctionIndicator", LED_CHECK_ENGINE, getHwPort(boardConfiguration->malfunctionIndicatorPin),
+ getHwPin(boardConfiguration->malfunctionIndicatorPin));
+
+// todo: are these needed here? todo: make configurable
+// outputPinRegister("spi CS1", SPI_CS_1, SPI_CS1_PORT, SPI_CS1_PIN);
+// outputPinRegister("spi CS2", SPI_CS_2, SPI_CS2_PORT, SPI_CS2_PIN);
+// outputPinRegister("spi CS3", SPI_CS_3, SPI_CS3_PORT, SPI_CS3_PIN);
+// outputPinRegister("spi CS4", SPI_CS_4, SPI_CS4_PORT, SPI_CS4_PIN);
+ outputPinRegister("spi CS5", SPI_CS_SD_MODULE, SPI_SD_MODULE_PORT, SPI_SD_MODULE_PIN);
+
+ // todo: should we move this code closer to the fuel pump logic?
+ outputPinRegister("fuel pump relay", FUEL_PUMP_RELAY, getHwPort(boardConfiguration->fuelPumpPin),
+ getHwPin(boardConfiguration->fuelPumpPin));
+
+ outputPinRegister("fan relay", FAN_RELAY, getHwPort(boardConfiguration->fanPin),
+ getHwPin(boardConfiguration->fanPin));
+
+ outputPinRegister("o2 heater", O2_HEATER, getHwPort(boardConfiguration->o2heaterPin),
+ getHwPin(boardConfiguration->o2heaterPin));
+
+ initialLedsBlink();
+
+ // digit 1
+ /*
+ ledRegister(LED_HUGE_0, GPIOB, 2);
+ ledRegister(LED_HUGE_1, GPIOE, 7);
+ ledRegister(LED_HUGE_2, GPIOE, 8);
+ ledRegister(LED_HUGE_3, GPIOE, 9);
+ ledRegister(LED_HUGE_4, GPIOE, 10);
+ ledRegister(LED_HUGE_5, GPIOE, 11);
+ ledRegister(LED_HUGE_6, GPIOE, 12);
+
+ // digit 2
+ ledRegister(LED_HUGE_7, GPIOE, 13);
+ ledRegister(LED_HUGE_8, GPIOE, 14);
+ ledRegister(LED_HUGE_9, GPIOE, 15);
+ ledRegister(LED_HUGE_10, GPIOB, 10);
+ ledRegister(LED_HUGE_11, GPIOB, 11);
+ ledRegister(LED_HUGE_12, GPIOB, 12);
+ ledRegister(LED_HUGE_13, GPIOB, 13);
+
+ // digit 3
+ ledRegister(LED_HUGE_14, GPIOE, 0);
+ ledRegister(LED_HUGE_15, GPIOE, 2);
+ ledRegister(LED_HUGE_16, GPIOE, 4);
+ ledRegister(LED_HUGE_17, GPIOE, 6);
+ ledRegister(LED_HUGE_18, GPIOE, 5);
+ ledRegister(LED_HUGE_19, GPIOE, 3);
+ ledRegister(LED_HUGE_20, GPIOE, 1);
+ */
+
+ chThdCreateStatic(comBlinkingStack, sizeof(comBlinkingStack), NORMALPRIO, (tfunc_t) comBlinkingThread, NULL );
+ chThdCreateStatic(errBlinkingStack, sizeof(errBlinkingStack), NORMALPRIO, (tfunc_t) errBlinkingThread, NULL );
+}
diff --git a/firmware/hw_layer/lcd/lcd_HD44780.c b/firmware/hw_layer/lcd/lcd_HD44780.c
new file mode 100644
index 0000000000..a4eb897332
--- /dev/null
+++ b/firmware/hw_layer/lcd/lcd_HD44780.c
@@ -0,0 +1,234 @@
+/**
+ * @file lcd_HD44780.c
+ * @brief HD44780 character display driver
+ *
+ *
+ * see http://joshuagalloway.com/lcd.html
+ * @date 13.12.2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+
+#if EFI_HD44780_LCD
+
+#include "lcd_HD44780.h"
+#include "pin_repository.h"
+#include "string.h"
+
+#include "engine_configuration.h"
+
+extern engine_configuration_s *engineConfiguration;
+extern board_configuration_s *boardConfiguration;
+
+static Logging logger;
+
+enum {
+ LCD_HD44780_DISPLAY_CLEAR = 0x01,
+ LCD_HD44780_SHIFT_CURSOR_RIGHT = 0x06,
+ LCD_HD44780_DISPLAY_ON = 0x0C,
+ LCD_HD44780_4_BIT_BUS = 0x20,
+ LCD_HD44780_RESET = 0x30,
+ LCD_HD44780_DDRAM_ADDR = 0x80,
+
+// LCD_2X16_8_BIT_BUS = 0x30,
+// LCD_2X16_LINE_ONE = 0x20,
+// LCD_2X16_LINES_TWO = 0x28,
+// LCD_2X16_FONT_5X8 = 0x20,
+// LCD_2X16_FONT_5X10 = 0x24,
+// LCD_2X16_DISPLAY_HOME = 0x02,
+// LCD_2X16_DISPLAY_RIGHT = 0x1C,
+// LCD_2X16_DISPLAY_LEFT = 0x18,
+// LCD_2X16_DISPLAY_SHIFT = 0x05,
+// LCD_2X16_CURSOR_ON = 0x0A,
+// LCD_2X16_CURSOR_BLINK = 0x09,
+// LCD_2X16_CURSOR_RIGHT = 0x14,
+// LCD_2X16_CURSOR_LEFT = 0x10,
+// LCD_2X16_SHIFT_LEFT = 0x04,
+// LCD_2X16_CGRAM_ADDR = 0x40,
+// LCD_2X16_BUSY_FLAG = 0x80,
+// LCD_2X16_COMMAND = 0x01,
+// LCD_2X16_DATA = 0x00,
+} lcd_HD44780_command;
+
+// http://web.alfredstate.edu/weimandn/lcd/lcd_addressing/lcd_addressing_index.html
+static const int lineStart[] = { 0, 0x40, 0x14, 0x54 };
+
+static int BUSY_WAIT_DELAY = FALSE;
+static int currentRow = 0;
+
+static void lcdSleep(int period) {
+ if (BUSY_WAIT_DELAY) {
+ // this mode is useful for displaying messages to report OS fatal issues
+
+ int ticks = 168000000 / 1000000 * period;
+ int a = 0;
+ for (int i = 0; i < ticks; i++)
+ a += i;
+ // the purpose of this code is to fool the compiler so that the loop is not optimized away
+ efiAssertVoid(a != 0, "true");
+
+ } else {
+ chThdSleepMicroseconds(period);
+ }
+}
+
+//static char txbuf[1];
+#define LCD_PORT_EXP_ADDR 0x20
+
+//-----------------------------------------------------------------------------
+static void lcd_HD44780_write(uint8_t data) {
+ if (engineConfiguration->displayMode == DM_HD44780) {
+ palWritePad(getHwPort(boardConfiguration->HD44780_db7), getHwPin(boardConfiguration->HD44780_db7),
+ data & 0x80 ? 1 : 0);
+ palWritePad(getHwPort(boardConfiguration->HD44780_db6), getHwPin(boardConfiguration->HD44780_db6),
+ data & 0x40 ? 1 : 0);
+ palWritePad(getHwPort(boardConfiguration->HD44780_db5), getHwPin(boardConfiguration->HD44780_db5),
+ data & 0x20 ? 1 : 0);
+ palWritePad(getHwPort(boardConfiguration->HD44780_db4), getHwPin(boardConfiguration->HD44780_db4),
+ data & 0x10 ? 1 : 0);
+
+ palSetPad(getHwPort(boardConfiguration->HD44780_e), getHwPin(boardConfiguration->HD44780_e)); // En high
+ lcdSleep(10); // enable pulse must be >450ns
+ palClearPad(getHwPort(boardConfiguration->HD44780_e), getHwPin(boardConfiguration->HD44780_e)); // En low
+ lcdSleep(40); // commands need > 37us to settle
+ } else {
+
+ // LCD D4_pin -> P4
+ // LCD D5_pin -> P5
+ // LCD D6_pin -> P6
+ // LCD D7_pin -> P7
+ // LCD Pin RS -> P0
+ // LCD Pin RW -> P1
+ // LCD Pin E -> P2
+
+ // todo: finish all this stuff
+// i2cAcquireBus(&I2CD1);
+//
+// txbuf[0] = 4;
+// i2cMasterTransmit(&I2CD1, LCD_PORT_EXP_ADDR, txbuf, 1, NULL, 0);
+// lcdSleep(10); // enable pulse must be >450ns
+//
+// txbuf[0] = 0;
+// i2cMasterTransmit(&I2CD1, LCD_PORT_EXP_ADDR, txbuf, 1, NULL, 0);
+//
+// i2cReleaseBus(&I2CD1);
+
+ }
+}
+
+//-----------------------------------------------------------------------------
+void lcd_HD44780_write_command(uint8_t data) {
+ palClearPad(getHwPort(boardConfiguration->HD44780_rs), getHwPin(boardConfiguration->HD44780_rs));
+
+ lcd_HD44780_write(data);
+ lcd_HD44780_write(data << 4);
+}
+
+//-----------------------------------------------------------------------------
+void lcd_HD44780_write_data(uint8_t data) {
+ palSetPad(getHwPort(boardConfiguration->HD44780_rs), getHwPin(boardConfiguration->HD44780_rs));
+
+ lcd_HD44780_write(data);
+ lcd_HD44780_write(data << 4);
+
+ palClearPad(getHwPort(boardConfiguration->HD44780_rs), getHwPin(boardConfiguration->HD44780_rs));
+}
+
+//-----------------------------------------------------------------------------
+void lcd_HD44780_set_position(uint8_t row, uint8_t column) {
+ efiAssertVoid(row <= engineConfiguration->HD44780height, "invalid row");
+ currentRow = row;
+ lcd_HD44780_write_command(LCD_HD44780_DDRAM_ADDR + lineStart[row] + column);
+}
+
+void lcd_HD44780_print_char(char data) {
+ if (data == '\n') {
+ lcd_HD44780_set_position(++currentRow, 0);
+ } else {
+ lcd_HD44780_write_data(data);
+ }
+}
+
+void lcd_HD44780_print_string(const char* string) {
+ while (*string != 0x00)
+ lcd_HD44780_print_char(*string++);
+}
+//getHwPin(boardConfiguration->HD44780_db7)
+static void lcdInfo(void) {
+ scheduleMsg(&logger, "HD44780 RS=%s E=%s", hwPortname(boardConfiguration->HD44780_rs), hwPortname(boardConfiguration->HD44780_e));
+ scheduleMsg(&logger, "HD44780 D4=%s D5=%s", hwPortname(boardConfiguration->HD44780_db4), hwPortname(boardConfiguration->HD44780_db5));
+ scheduleMsg(&logger, "HD44780 D6=%s D7=%s", hwPortname(boardConfiguration->HD44780_db6), hwPortname(boardConfiguration->HD44780_db7));
+}
+
+void lcd_HD44780_init(void) {
+ initLogging(&logger, "HD44780 driver");
+
+ addConsoleAction("lcdinfo", lcdInfo);
+
+ if (engineConfiguration->displayMode > DM_HD44780_OVER_PCF8574) {
+ firmwareError("Unexpected displayMode %d", engineConfiguration->displayMode);
+ return;
+ }
+
+ printMsg(&logger, "lcd_HD44780_init %d", engineConfiguration->displayMode);
+
+ if (engineConfiguration->displayMode == DM_HD44780) {
+ // initialize hardware lines
+ mySetPadMode("lcd RS", getHwPort(boardConfiguration->HD44780_rs), getHwPin(boardConfiguration->HD44780_rs),
+ PAL_MODE_OUTPUT_PUSHPULL);
+ mySetPadMode("lcd E", getHwPort(boardConfiguration->HD44780_e), getHwPin(boardConfiguration->HD44780_e),
+ PAL_MODE_OUTPUT_PUSHPULL);
+ mySetPadMode("lcd DB4", getHwPort(boardConfiguration->HD44780_db4), getHwPin(boardConfiguration->HD44780_db4),
+ PAL_MODE_OUTPUT_PUSHPULL);
+ mySetPadMode("lcd DB6", getHwPort(boardConfiguration->HD44780_db5), getHwPin(boardConfiguration->HD44780_db5),
+ PAL_MODE_OUTPUT_PUSHPULL);
+ mySetPadMode("lcd DB7", getHwPort(boardConfiguration->HD44780_db6), getHwPin(boardConfiguration->HD44780_db6),
+ PAL_MODE_OUTPUT_PUSHPULL);
+ mySetPadMode("lcd DB8", getHwPort(boardConfiguration->HD44780_db7), getHwPin(boardConfiguration->HD44780_db7),
+ PAL_MODE_OUTPUT_PUSHPULL);
+ // and zero values
+ palWritePad(getHwPort(boardConfiguration->HD44780_rs), getHwPin(boardConfiguration->HD44780_rs), 0);
+ palWritePad(getHwPort(boardConfiguration->HD44780_e), getHwPin(boardConfiguration->HD44780_e), 0);
+ palWritePad(getHwPort(boardConfiguration->HD44780_db4), getHwPin(boardConfiguration->HD44780_db4), 0);
+ palWritePad(getHwPort(boardConfiguration->HD44780_db5), getHwPin(boardConfiguration->HD44780_db5), 0);
+ palWritePad(getHwPort(boardConfiguration->HD44780_db6), getHwPin(boardConfiguration->HD44780_db6), 0);
+ palWritePad(getHwPort(boardConfiguration->HD44780_db7), getHwPin(boardConfiguration->HD44780_db7), 0);
+ }
+
+ chThdSleepMilliseconds(20); // LCD needs some time to wake up
+ lcd_HD44780_write(LCD_HD44780_RESET); // reset 1x
+ chThdSleepMilliseconds(1);
+ lcd_HD44780_write(LCD_HD44780_RESET); // reset 2x
+ lcd_HD44780_write(LCD_HD44780_RESET); // reset 3x
+
+ lcd_HD44780_write(LCD_HD44780_4_BIT_BUS); // 4 bit, 2 line
+ chThdSleepMicroseconds(40);
+
+ lcd_HD44780_write(LCD_HD44780_4_BIT_BUS); // 4 bit, 2 line
+ lcd_HD44780_write(0x80);
+ chThdSleepMicroseconds(40);
+
+ lcd_HD44780_write_command(0x08); // display and cursor control
+ chThdSleepMicroseconds(40);
+
+ lcd_HD44780_write_command(LCD_HD44780_DISPLAY_CLEAR);
+ chThdSleepMilliseconds(2);
+
+ lcd_HD44780_write_command(LCD_HD44780_SHIFT_CURSOR_RIGHT);
+ chThdSleepMilliseconds(2);
+
+ lcd_HD44780_write_command(LCD_HD44780_DISPLAY_ON);
+
+ lcd_HD44780_set_position(0, 0);
+ printMsg(&logger, "lcd_HD44780_init() done");
+}
+
+void lcdShowFatalMessage(char *message) {
+ BUSY_WAIT_DELAY = TRUE;
+ lcd_HD44780_set_position(0, 0);
+ lcd_HD44780_print_string("fatal\n");
+ lcd_HD44780_print_string(message);
+}
+
+#endif /* EFI_HD44780_LCD */
diff --git a/firmware/hw_layer/lcd/lcd_HD44780.h b/firmware/hw_layer/lcd/lcd_HD44780.h
new file mode 100644
index 0000000000..7572ba619d
--- /dev/null
+++ b/firmware/hw_layer/lcd/lcd_HD44780.h
@@ -0,0 +1,27 @@
+/**
+ * @file lcd_HD44780.h
+ *
+ * @date 13.12.2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef LCD_HD44780_H_
+#define LCD_HD44780_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void lcd_HD44780_init(void);
+void lcd_HD44780_set_position(uint8_t row, uint8_t column);
+void lcd_HD44780_print_char(char data);
+void lcd_HD44780_print_string(const char *string);
+
+void lcdShowFatalMessage(char *message);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* LCD_HD44780_H_ */
diff --git a/firmware/hw_layer/mcp3208.c b/firmware/hw_layer/mcp3208.c
new file mode 100644
index 0000000000..f39890c58a
--- /dev/null
+++ b/firmware/hw_layer/mcp3208.c
@@ -0,0 +1,140 @@
+/*
+ * @file mcp3208.c
+ * @brief MCP3208 external ADC chip implementation. Not really used right now.
+ *
+ * @date Aug 12, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include
+#include
+
+#include "mcp3208.h"
+#include "eficonsole.h"
+#include "pin_repository.h"
+
+McpAdcState *hack;
+
+static int adcEventCounter = 0;
+static int adcErrorCounter = 0;
+
+static int getValue(McpAdcState *state, int channel) {
+ return state->results[channel] & 4095;
+}
+
+int getMcp3208adc(int channel) {
+ return getValue(hack, channel);
+}
+
+static int getNextChannel(void) {
+ return adcEventCounter % 2;
+}
+
+static void spiCallback(SPIDriver *spip) {
+ spiUnselectI(spip);
+ adcEventCounter++;
+
+ McpAdcState *state = hack;
+
+ int withError = 0;
+
+ if (state->rx_buff[0] != 255) {
+ withError = 1;
+ //fatal("ADC: first byte");
+ }
+
+ if ((state->rx_buff[1] & 0xE0) != 0xE0) {
+ withError = 1;
+ //fatal("ADC: second byte");
+ }
+
+ if (!withError) {
+// unsigned char upperByte = state->rx_buff[1] & 0b00001111;
+ int result = (state->rx_buff[0] << 16) + (state->rx_buff[1] << 8) + state->rx_buff[2];
+ state->results[state->requestedChannel] = result;
+ } else {
+ adcErrorCounter++;
+ }
+
+ requestAdcValueI(state, getNextChannel());
+}
+
+static const SPIConfig spicfg = { spiCallback,
+/* HW dependent part.*/
+MCP3208_CS_PORT,
+MCP3208_CS_PIN,
+//SPI_CR1_MSTR |
+ SPI_CR1_BR_0 | SPI_CR1_BR_1 | SPI_CR1_BR_2
+// SPI_CR1_BR_1 | SPI_CR1_BR_2
+ };
+
+static void createRequest(McpAdcState *state, int channel) {
+ efiAssertVoid(channel < 8, "Invalid ADC channel");
+
+ state->requestedChannel = channel;
+
+ state->tx_buff[0] = 0x06 + (channel >> 2);
+ state->tx_buff[1] = (channel & 3) << 6;
+}
+
+void requestAdcValue(McpAdcState *state, int channel) {
+ createRequest(state, channel);
+
+ spiSelect(state->driver);
+ spiStartExchange(state->driver, 3, state->tx_buff, state->rx_buff);
+ // SPI unselect is in the callback
+}
+
+void requestAdcValueI(McpAdcState *state, int channel) {
+ createRequest(state, channel);
+
+ spiSelectI(state->driver);
+ spiStartExchangeI(state->driver, 3, state->tx_buff, state->rx_buff);
+ // SPI unselect is in the callback
+}
+
+void adc_in_out(McpAdcState *state) {
+
+// chThdSleepMilliseconds(10);
+
+ int result = state->results[0];
+
+// int v = result;
+ unsigned r = 0;
+//
+// while (v >>= 1)
+// r++;
+//
+// unsigned int f;
+// if (r < 12) {
+// f = -1;
+// } else {
+// f = result >> (r - 12);
+// f = f & 4095;
+// }
+
+ int errRatio = 1000 * adcErrorCounter / adcEventCounter;
+
+ print("c/e %7d/%7d/%4d result %5d r=%d ", adcEventCounter, adcErrorCounter, errRatio, result, r);
+
+ unsigned int f0 = getValue(state, 0);
+ print("ch0=%d adj %d ", f0, f0 * 5000 / 4096);
+ unsigned int f1 = getValue(state, 1);
+ print("ch1=%d adj %d\r\n", f1, f1 * 5000 / 4096);
+}
+
+void init_adc_mcp3208(McpAdcState *state, SPIDriver *driver) {
+
+// initSpiModules();
+
+ state->driver = driver;
+ state->tx_buff[2] = 0;
+
+ hack = state;
+
+ mySetPadMode("ext adc chip select", MCP3208_CS_PORT, MCP3208_CS_PIN, PAL_STM32_MODE_OUTPUT);
+
+
+ spiStart(driver, &spicfg);
+}
+
diff --git a/firmware/hw_layer/mcp3208.h b/firmware/hw_layer/mcp3208.h
new file mode 100644
index 0000000000..f5bd6c7b70
--- /dev/null
+++ b/firmware/hw_layer/mcp3208.h
@@ -0,0 +1,52 @@
+/*
+ * @file mcp3208.h
+ *
+ * @date Aug 12, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ *
+ * MCP3208 pins:
+ * pin10 CS Chip Select
+ * pin11 Din Data In (MOSI)
+ * pin12 Dout Data Out (MISO)
+ * pin13 CLK spi CLoCk
+ *
+ */
+
+#ifndef MCP3208_H_
+#define MCP3208_H_
+
+#define MCP3208_CS_PORT GPIOD
+#define MCP3208_CS_PIN 11
+
+/**
+ * @brief MCP3208 ADC chip driver structure
+ */
+typedef struct {
+ SPIDriver *driver;
+ unsigned char tx_buff[3];
+ unsigned char rx_buff[3];
+ int requestedChannel;
+ int results[8];
+} McpAdcState;
+
+void init_adc_mcp3208(McpAdcState *state, SPIDriver *driver);
+void requestAdcValue(McpAdcState *state, int channel);
+void requestAdcValueI(McpAdcState *state, int channel);
+void adc_in_out(McpAdcState *state);
+
+int getMcp3208adc(int channel);
+
+
+// Peripherial Clock 84MHz SPI1 SPI1 SPI2/3
+// Peripherial Clock 42MHz SPI2 SPI3
+#define SPI_BaudRatePrescaler_2 ((uint16_t)0x0000) // 42 MHz 21 MHZ
+#define SPI_BaudRatePrescaler_4 ((uint16_t)0x0008) // 21 MHz 10.5 MHz
+#define SPI_BaudRatePrescaler_8 ((uint16_t)0x0010) // 10.5 MHz 5.25 MHz
+#define SPI_BaudRatePrescaler_16 ((uint16_t)0x0018) // 5.25 MHz 2.626 MHz
+#define SPI_BaudRatePrescaler_32 ((uint16_t)0x0020) // 2.626 MHz 1.3125 MHz
+#define SPI_BaudRatePrescaler_64 ((uint16_t)0x0028) // 1.3125 MHz 656.25 KHz
+#define SPI_BaudRatePrescaler_128 ((uint16_t)0x0030) // 656.25 KHz 328.125 KHz
+#define SPI_BaudRatePrescaler_256 ((uint16_t)0x0038) // 328.125 KHz 164.06 KHz
+
+#endif /* MCP3208_H_ */
diff --git a/firmware/hw_layer/microsecond_timer.c b/firmware/hw_layer/microsecond_timer.c
new file mode 100644
index 0000000000..c9041d9e97
--- /dev/null
+++ b/firmware/hw_layer/microsecond_timer.c
@@ -0,0 +1,122 @@
+/**
+ * @file microsecond_timer.c
+ *
+ * Here we have a 1MHz timer dedicated to event scheduling. We are using one of the 32-bit timers here,
+ * so this timer can schedule events up to 4B/100M ~ 4000 seconds ~ 1 hour from current time.
+ *
+ * @date Apr 14, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+#include "signal_executor.h"
+#include "microsecond_timer.h"
+#include "rfiutil.h"
+
+// https://my.st.com/public/STe2ecommunities/mcu/Lists/cortex_mx_stm32/Flat.aspx?RootFolder=https%3a%2f%2fmy.st.com%2fpublic%2fSTe2ecommunities%2fmcu%2fLists%2fcortex_mx_stm32%2fInterrupt%20on%20CEN%20bit%20setting%20in%20TIM7&FolderCTID=0x01200200770978C69A1141439FE559EB459D7580009C4E14902C3CDE46A77F0FFD06506F5B¤tviews=474
+
+#if EFI_PROD_CODE
+
+#define GPTDEVICE GPTD5
+
+static volatile uint64_t lastSetTimerTime;
+static int lastSetTimerValue;
+static volatile bool isTimerPending = FALSE;
+
+static volatile int timerCallbackCounter = 0;
+static volatile int timerRestartCounter = 0;
+
+schfunc_t globalTimerCallback;
+
+/**
+ * sets the alarm to the specified number of microseconds from now.
+ * This function should be invoked under kernel lock which would disable interrupts.
+ */
+void setHardwareUsTimer(int32_t timeUs) {
+ if (timeUs == 1)
+ timeUs = 2; // for some reason '1' does not really work
+ efiAssertVoid(timeUs > 0, "neg timeUs");
+ efiAssertVoid(timeUs < 10 * US_PER_SECOND, "setHardwareUsTimer() too large");
+
+ if (GPTDEVICE.state == GPT_ONESHOT)
+ gptStopTimerI(&GPTDEVICE);
+ gptStartOneShotI(&GPTDEVICE, timeUs);
+
+ lastSetTimerTime = getTimeNowUs();
+ lastSetTimerValue = timeUs;
+ isTimerPending = TRUE;
+ timerRestartCounter++;
+}
+
+static void callback(GPTDriver *gptp) {
+ timerCallbackCounter++;
+ if (globalTimerCallback == NULL) {
+ firmwareError("NULL globalTimerCallback");
+ return;
+ }
+ isTimerPending = false;
+
+// // test code
+// setOutputPinValue(LED_CRANKING, timerCallbackCounter % 2);
+// int mod = timerCallbackCounter % 400;
+// chSysLockFromIsr()
+// ;
+// setHardwareUsTimer(400 - mod);
+// chSysUnlockFromIsr()
+// ;
+
+ globalTimerCallback(NULL);
+}
+
+static THD_WORKING_AREA(mwThreadStack, UTILITY_THREAD_STACK_SIZE);
+
+static const char * msg;
+
+static char buff[32];
+
+static msg_t mwThread(int param) {
+ chRegSetThreadName("timer watchdog");
+
+ while (TRUE) {
+ chThdSleepMilliseconds(1000); // once a second is enough
+
+ if (getTimeNowUs() >= lastSetTimerTime + 2 * US_PER_SECOND) {
+ strcpy(buff, "no_event");
+ itoa10(&buff[8], lastSetTimerValue);
+ firmwareError(buff);
+ return -1;
+ }
+
+ msg = isTimerPending ? "No_cb too long" : "Timer not awhile";
+ // 2 seconds of inactivity would not look right
+ efiAssert(getTimeNowUs() < lastSetTimerTime + 2 * US_PER_SECOND, msg, -1);
+ }
+#if defined __GNUC__
+ return -1;
+#endif
+}
+
+//static const GPTConfig gpt5cfg;
+
+static const GPTConfig gpt5cfg = { 1000000, /* 1 MHz timer clock.*/
+callback, /* Timer callback.*/
+0 };
+
+void initMicrosecondTimer(void) {
+
+ gptStart(&GPTDEVICE, &gpt5cfg);
+
+ lastSetTimerTime = getTimeNowUs();
+#if EFI_EMULATE_POSITION_SENSORS
+ chThdCreateStatic(mwThreadStack, sizeof(mwThreadStack), NORMALPRIO, (tfunc_t) mwThread, NULL);
+#endif /* EFI_ENGINE_EMULATOR */
+
+// // test code
+// chSysLock()
+// ;
+// setHardwareUsTimer(300);
+// chSysUnlock()
+// ;
+}
+
+#endif /* EFI_PROD_CODE */
diff --git a/firmware/hw_layer/microsecond_timer.h b/firmware/hw_layer/microsecond_timer.h
new file mode 100644
index 0000000000..ab226d5e3b
--- /dev/null
+++ b/firmware/hw_layer/microsecond_timer.h
@@ -0,0 +1,23 @@
+/**
+ * @file microsecond_timer.h
+ *
+ * @date Apr 14, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef SIGNAL_TEMP_H_
+#define SIGNAL_TEMP_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void initMicrosecondTimer(void);
+void setHardwareUsTimer(int32_t timeUs);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SIGNAL_TEMP_H_ */
diff --git a/firmware/hw_layer/mmc_card.c b/firmware/hw_layer/mmc_card.c
new file mode 100644
index 0000000000..8b7869ca09
--- /dev/null
+++ b/firmware/hw_layer/mmc_card.c
@@ -0,0 +1,275 @@
+/**
+ * @file mmc_card.c
+ *
+ * @date Dec 28, 2013
+ * @author Kot_dnz
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ * default pinouts in case of SPI2 connected to MMC: PB13 - SCK, PB14 - MISO, PB15 - MOSI, PD4 - CS, 3.3v
+ * default pinouts in case of SPI3 connected to MMC: PB3 - SCK, PB4 - MISO, PB5 - MOSI, PD4 - CS, 3.3v
+ *
+ */
+
+#include "main.h"
+
+#if EFI_FILE_LOGGING || defined(__DOXYGEN__)
+
+#include
+#include
+#include "mmc_card.h"
+#include "pin_repository.h"
+#include "ff.h"
+#include "hardware.h"
+
+#define PUSHPULLDELAY 500
+
+static THD_WORKING_AREA(tp_MMC_Monitor,UTILITY_THREAD_STACK_SIZE); // MMC monitor thread
+
+/**
+ * MMC driver instance.
+ */
+MMCDriver MMCD1;
+
+// Peripherial Clock 42MHz SPI2 SPI3
+// Peripherial Clock 84MHz SPI1 SPI1 SPI2/3
+#define SPI_BaudRatePrescaler_2 ((uint16_t)0x0000) // 42 MHz 21 MHZ
+#define SPI_BaudRatePrescaler_4 ((uint16_t)0x0008) // 21 MHz 10.5 MHz
+#define SPI_BaudRatePrescaler_8 ((uint16_t)0x0010) // 10.5 MHz 5.25 MHz
+#define SPI_BaudRatePrescaler_16 ((uint16_t)0x0018) // 5.25 MHz 2.626 MHz
+#define SPI_BaudRatePrescaler_32 ((uint16_t)0x0020) // 2.626 MHz 1.3125 MHz
+#define SPI_BaudRatePrescaler_64 ((uint16_t)0x0028) // 1.3125 MHz 656.25 KHz
+#define SPI_BaudRatePrescaler_128 ((uint16_t)0x0030) // 656.25 KHz 328.125 KHz
+#define SPI_BaudRatePrescaler_256 ((uint16_t)0x0038) // 328.125 KHz 164.06 KHz
+static SPIConfig hs_spicfg = { NULL, SPI_SD_MODULE_PORT, SPI_SD_MODULE_PIN,
+SPI_BaudRatePrescaler_8 };
+static SPIConfig ls_spicfg = { NULL, SPI_SD_MODULE_PORT, SPI_SD_MODULE_PIN,
+SPI_BaudRatePrescaler_256 };
+
+/* MMC/SD over SPI driver configuration.*/
+// don't forget check if STM32_SPI_USE_SPI2 defined and spi has init with correct GPIO in hardware.c
+static MMCConfig mmccfg = { &MMC_CARD_SPI, &ls_spicfg, &hs_spicfg };
+
+static bool fs_ready = false;
+
+#define PUSHPULLDELAY 500
+
+/**
+ * fatfs MMC/SPI
+ */
+static FATFS MMC_FS;
+
+static Logging logger;
+
+// print FAT error function
+static void printError(char *str, FRESULT f_error) {
+ scheduleMsg(&logger, "FATfs Error \"%s\" %d", str, f_error);
+}
+
+static FIL FDLogFile;
+
+static int totalLoggedBytes = 0;
+
+static void printMmcPinout(void) {
+ scheduleMsg(&logger, "MMC CS %s:%d", portname(SPI_SD_MODULE_PORT), SPI_SD_MODULE_PIN);
+ // todo: we need to figure out the right SPI pinout, not just SPI2
+// scheduleMsg(&logger, "MMC SCK %s:%d", portname(EFI_SPI2_SCK_PORT), EFI_SPI2_SCK_PIN);
+// scheduleMsg(&logger, "MMC MISO %s:%d", portname(EFI_SPI2_MISO_PORT), EFI_SPI2_MISO_PIN);
+// scheduleMsg(&logger, "MMC MOSI %s:%d", portname(EFI_SPI2_MOSI_PORT), EFI_SPI2_MOSI_PIN);
+}
+
+static void sdStatistics(void) {
+ printMmcPinout();
+ scheduleMsg(&logger, "fs_ready=%d totalLoggedBytes=%d", fs_ready, totalLoggedBytes);
+}
+
+/**
+ * @brief Create a new file with the specified name
+ *
+ * This function saves the name of the file in a global variable
+ * so that we can later append to that file
+ */
+static void createLogFile(void) {
+ lockSpi(SPI_NONE);
+ memset(&FDLogFile, 0, sizeof(FIL)); // clear the memory
+ FRESULT err = f_open(&FDLogFile, "rusefi.log", FA_OPEN_ALWAYS | FA_WRITE); // Create new file
+ if (err != FR_OK && err != FR_EXIST) {
+ unlockSpi();
+ printError("Card mounted...\r\nCan't create Log file, check your SD.\r\nFS mount failed", err); // else - show error
+ return;
+ }
+
+ err = f_lseek(&FDLogFile, f_size(&FDLogFile)); // Move to end of the file to append data
+ if (err) {
+ unlockSpi();
+ printError("Seek error", err);
+ return;
+ }
+ f_sync(&FDLogFile);
+ fs_ready = true; // everything Ok
+ unlockSpi();
+}
+
+static void ff_cmd_dir(char *path) {
+ DIR dir;
+ FILINFO fno;
+ char *fn;
+
+ if (!fs_ready) {
+ scheduleMsg(&logger, "Error: No File system is mounted");
+ return;
+ }
+
+ FRESULT res = f_opendir(&dir, path);
+
+ if (res != FR_OK) {
+ scheduleMsg(&logger, "Error opening directory %s", path);
+ return;
+ }
+
+ int i = strlen(path);
+ for (;;) {
+ res = f_readdir(&dir, &fno);
+ if (res != FR_OK || fno.fname[0] == 0)
+ break;
+ if (fno.lfname[0] == '.')
+ continue;
+ fn = fno.lfname;
+ if (fno.fattrib & AM_DIR) {
+ // TODO: WHAT? WE ARE APPENDING FILE NAME TO PARAMETER??? WEIRD!!!
+ path[i++] = '/';
+ strcpy(&path[i], fn);
+ // res = ff_cmd_ls(path);
+ if (res != FR_OK)
+ break;
+ path[i] = 0;
+ } else {
+ scheduleMsg(&logger, "%c%c%c%c%c %u/%02u/%02u %02u:%02u %9lu %-12s", (fno.fattrib & AM_DIR) ? 'D' : '-',
+ (fno.fattrib & AM_RDO) ? 'R' : '-', (fno.fattrib & AM_HID) ? 'H' : '-',
+ (fno.fattrib & AM_SYS) ? 'S' : '-', (fno.fattrib & AM_ARC) ? 'A' : '-', (fno.fdate >> 9) + 1980,
+ (fno.fdate >> 5) & 15, fno.fdate & 31, (fno.ftime >> 11), (fno.ftime >> 5) & 63, fno.fsize,
+ fno.fname);
+ }
+ }
+}
+
+static int errorReported = FALSE; // this is used to report the error only once
+
+/**
+ * @brief Appends specified line to the current log file
+ */
+void appendToLog(char *line) {
+ UINT bytesWrited;
+
+ if (!fs_ready) {
+ if (!errorReported)
+ scheduleMsg(&logger, "appendToLog Error: No File system is mounted");
+ errorReported = TRUE;
+ return;
+ }
+ UINT lineLength = strlen(line);
+ totalLoggedBytes += lineLength;
+ lockSpi(SPI_NONE);
+ FRESULT err = f_write(&FDLogFile, line, lineLength, &bytesWrited);
+ if (bytesWrited < lineLength) {
+ printError("write error or disk full", err); // error or disk full
+ }
+ f_sync(&FDLogFile);
+ unlockSpi();
+}
+
+/*
+ * MMC card umount.
+ */
+static void MMCumount(void) {
+ if (!fs_ready) {
+ scheduleMsg(&logger, "Error: No File system is mounted. \"mountsd\" first");
+ return;
+ }
+ f_close(&FDLogFile); // close file
+ f_sync(&FDLogFile); // sync ALL
+ mmcDisconnect(&MMCD1); // Brings the driver in a state safe for card removal.
+ mmcStop(&MMCD1); // Disables the MMC peripheral.
+ f_mount(0, NULL); // FATFS: Unregister work area prior to discard it
+ memset(&FDLogFile, 0, sizeof(FIL)); // clear FDLogFile
+ fs_ready = false; // status = false
+ scheduleMsg(&logger, "MMC/SD card removed");
+}
+
+/*
+ * MMC card mount.
+ */
+static void MMCmount(void) {
+// printMmcPinout();
+
+ if (fs_ready) {
+ scheduleMsg(&logger, "Error: Already mounted. \"umountsd\" first");
+ return;
+ }
+ // start to initialize MMC/SD
+ mmcObjectInit(&MMCD1); // Initializes an instance.
+ mmcStart(&MMCD1, &mmccfg); // Configures and activates the MMC peripheral.
+
+ // Performs the initialization procedure on the inserted card.
+ lockSpi(SPI_NONE);
+ if (mmcConnect(&MMCD1) != CH_SUCCESS) {
+ warning(OBD_PCM_Processor_Fault, "Can't connect or mount MMC/SD");
+ unlockSpi();
+ return;
+
+ }
+ unlockSpi();
+ // if Ok - mount FS now
+ memset(&MMC_FS, 0, sizeof(FATFS)); // reserve the memory
+ if (f_mount(0, &MMC_FS) == FR_OK) {
+ createLogFile();
+ scheduleMsg(&logger, "MMC/SD mounted!\r\nDon't forget umountsd before remove to prevent lost your data");
+ }
+}
+
+#if defined __GNUC__
+__attribute__((noreturn)) static msg_t MMCmonThread(void)
+#else
+static msg_t MMCmonThread(void)
+#endif
+{
+ chRegSetThreadName("MMC_Monitor");
+
+ while (true) {
+ // this returns TRUE if SD module is there, even without an SD card?
+ if (blkIsInserted(&MMCD1)) {
+
+ if (!fs_ready) {
+ MMCmount();
+ }
+ }
+
+ // this thread is activated 10 times per second
+ chThdSleepMilliseconds(PUSHPULLDELAY);
+ }
+}
+
+bool isSdCardAlive(void) {
+ return fs_ready;
+}
+
+void initMmcCard(void) {
+ initLogging(&logger, "mmcCard");
+
+ /**
+ * FYI: SPI does not work with CCM memory, be sure to have main() stack in RAM, not in CCMRAM
+ */
+
+ // start to initialize MMC/SD
+ mmcObjectInit(&MMCD1);
+ mmcStart(&MMCD1, &mmccfg);
+
+ chThdCreateStatic(tp_MMC_Monitor, sizeof(tp_MMC_Monitor), LOWPRIO, (tfunc_t) MMCmonThread, NULL);
+
+ addConsoleAction("sdstat", sdStatistics);
+ addConsoleAction("mountsd", MMCmount);
+ addConsoleActionS("appendToLog", appendToLog);
+ addConsoleAction("umountsd", MMCumount);
+ addConsoleActionS("ls", ff_cmd_dir);
+}
+
+#endif /* EFI_FILE_LOGGING */
diff --git a/firmware/hw_layer/mmc_card.h b/firmware/hw_layer/mmc_card.h
new file mode 100644
index 0000000000..13106e4d61
--- /dev/null
+++ b/firmware/hw_layer/mmc_card.h
@@ -0,0 +1,26 @@
+/*
+ * @file mmc_card.h
+ *
+ *
+ * @date Dec 30, 2013
+ * @author Kot_dnz
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef MMC_CARD_H_
+#define MMC_CARD_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void initMmcCard(void);
+bool isSdCardAlive(void);
+void appendToLog(char *line);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* MMC_CARD_H_ */
diff --git a/firmware/hw_layer/neo6m.c b/firmware/hw_layer/neo6m.c
new file mode 100644
index 0000000000..e2945415de
--- /dev/null
+++ b/firmware/hw_layer/neo6m.c
@@ -0,0 +1,126 @@
+/**
+ * @file neo6m.c
+ * @brief Ublox 6M hardware UART driver
+ *
+ * http://www.u-blox.com/en/gps-modules/pvt-modules/previous-generations/neo-6-family.html
+ *
+ * Technically any UART GPS should work with this driver since NMEA protocol is pretty common anyway
+ *
+ * @date Dec 28, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ * Kot_dnz 2014
+ */
+
+#include
+// todo: MISRA does not like time.h
+#include
+#include "main.h"
+
+#if EFI_UART_GPS || defined(__DOXYGEN__)
+
+#include "console_io.h"
+#include "eficonsole.h"
+#include "pin_repository.h"
+#include "nmea.h"
+#include "neo6m.h"
+#include "rtc_helper.h"
+#include "engine_configuration.h"
+
+extern board_configuration_s *boardConfiguration;
+
+static Logging logging;
+
+static SerialConfig GPSserialConfig = { GPS_SERIAL_SPEED, 0, USART_CR2_STOP1_BITS | USART_CR2_LINEN, 0 };
+static THD_WORKING_AREA(GPS_WORKING_AREA, UTILITY_THREAD_STACK_SIZE);
+
+// this field holds our current state
+static loc_t GPSdata;
+
+static int gpsMesagesCount = 0;
+static int uartErrors = 0;
+
+// todo: some data structure for coordinates location
+// todo:
+float getCurrentSpeed(void) {
+ return GPSdata.speed;
+}
+
+static void printGpsInfo(void) {
+ // todo: scheduleMsg()
+
+ scheduleMsg(&logging, "GPS RX %s", hwPortname(boardConfiguration->gps_rx_pin));
+ scheduleMsg(&logging, "GPS TX %s", hwPortname(boardConfiguration->gps_tx_pin));
+
+ scheduleMsg(&logging, "m=%d,e=%d: vehicle speed = %f\r\n", gpsMesagesCount, uartErrors, getCurrentSpeed());
+
+ float sec = currentTimeMillis() / 1000.0;
+ scheduleMsg(&logging, "communication speed: %f", gpsMesagesCount / sec);
+
+ print("GPS latitude = %f\r\n", GPSdata.latitude);
+ print("GPS longitude = %f\r\n", GPSdata.longitude);
+}
+
+static struct tm curTm;
+
+static void onGpsMessage(char *buffer) {
+
+ gps_location(&GPSdata, buffer);
+ date_get_tm(&curTm);
+
+ if (GPSdata.quality == 4 && GPSdata.GPStm.tm_year > 0 && GPSdata.GPStm.tm_sec != curTm.tm_sec) {
+ // quality =4 (valis GxRMC), year > 0, and difference more then second
+ date_set_tm(&GPSdata.GPStm); // set GPS time
+ //}
+ }
+ gpsMesagesCount++;
+}
+
+// we do not want this on stack, right?
+static char gps_str[GPS_MAX_STRING];
+
+static msg_t GpsThreadEntryPoint(void *arg) {
+ (void) arg;
+ chRegSetThreadName("GPS thread");
+
+ int count = 0;
+
+ while (TRUE) {
+ msg_t charbuf = chSequentialStreamGet(GPS_SERIAL_DEVICE);
+ if (charbuf == 10 || count == GPS_MAX_STRING) { // if 0xD,0xA or limit
+ if (count >= 1)
+ gps_str[--count] = '\0'; // delete 0xD
+
+// scheduleMsg(&logger, "got GPS [%s]", gps_str);
+
+ // 'gps_str' string completed
+ onGpsMessage(gps_str);
+ memset(&gps_str, '\0', GPS_MAX_STRING); // clear buffer
+ count = 0;
+ } else {
+ gps_str[count++] = charbuf;
+ }
+ }
+#if defined __GNUC__
+ return 0;
+#endif
+}
+
+void initGps(void) {
+ if (boardConfiguration->gps_rx_pin == GPIO_NONE || boardConfiguration->gps_tx_pin == GPIO_NONE) {
+ return;
+ }
+
+ initLogging(&logging, "uart gps");
+
+ sdStart(GPS_SERIAL_DEVICE, &GPSserialConfig);
+// GPS we have USART1: PB7 -> USART1_RX and PB6 -> USART1_TX
+ mySetPadMode("GPS tx", getHwPort(boardConfiguration->gps_tx_pin), getHwPin(boardConfiguration->gps_tx_pin), PAL_MODE_ALTERNATE(7));
+ mySetPadMode("GPS rx", getHwPort(boardConfiguration->gps_rx_pin), getHwPin(boardConfiguration->gps_rx_pin), PAL_MODE_ALTERNATE(7));
+
+// todo: add a thread which would save location. If the GPS 5Hz - we should save the location each 200 ms
+ chThdCreateStatic(GPS_WORKING_AREA, sizeof(GPS_WORKING_AREA), LOWPRIO, GpsThreadEntryPoint, NULL);
+
+ addConsoleAction("gpsinfo", &printGpsInfo);
+}
+
+#endif /* EFI_UART_GPS */
diff --git a/firmware/hw_layer/neo6m.h b/firmware/hw_layer/neo6m.h
new file mode 100644
index 0000000000..71ccb22eae
--- /dev/null
+++ b/firmware/hw_layer/neo6m.h
@@ -0,0 +1,25 @@
+/*
+ * @file neo6m.h
+ *
+ *
+ * @date Dec 30, 2013
+ * @author Kot_dnz
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef NEO6M_H_
+#define NEO6M_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void initGps(void);
+float getCurrentSpeed(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* NEO6M_H_ */
diff --git a/firmware/hw_layer/pin_repository.c b/firmware/hw_layer/pin_repository.c
new file mode 100644
index 0000000000..ad132dbdd4
--- /dev/null
+++ b/firmware/hw_layer/pin_repository.c
@@ -0,0 +1,178 @@
+/**
+ * @file pin_repository.c
+ * @brief I/O pin registry code
+ *
+ * This job of this class is to make sure that we are not using same hardware pin for two
+ * different purposes.
+ *
+ * @date Jan 15, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+#include "pin_repository.h"
+#include "eficonsole.h"
+#include "memstreams.h"
+#include "chprintf.h"
+#include "rusefi.h"
+
+#define PIN_REPO_SIZE 7 * 16
+const char *PIN_USED[PIN_REPO_SIZE];
+static int initialized = FALSE;
+
+static Logging logger;
+static int totalPinsUsed = 0;
+
+/**
+ * @deprecated - use hwPortname() instead
+ */
+char *portname(GPIO_TypeDef* GPIOx) {
+ if (GPIOx == GPIOA)
+ return "PA";
+ if (GPIOx == GPIOB)
+ return "PB";
+ if (GPIOx == GPIOC)
+ return "PC";
+ if (GPIOx == GPIOD)
+ return "PD";
+ if (GPIOx == GPIOE)
+ return "PE";
+ if (GPIOx == GPIOF)
+ return "PF";
+ if (GPIOx == GPIOH)
+ return "PH";
+ return "unknown";
+}
+
+static int getPortIndex(GPIO_TypeDef* port) {
+ if (port == GPIOA)
+ return 0;
+ if (port == GPIOB)
+ return 1;
+ if (port == GPIOC)
+ return 2;
+ if (port == GPIOD)
+ return 3;
+ if (port == GPIOE)
+ return 4;
+ if (port == GPIOF)
+ return 5;
+ if (port == GPIOH)
+ return 6;
+ firmwareError("portindex");
+ return -1;
+}
+
+static void reportPins(void) {
+ for (int i = 0; i < PIN_REPO_SIZE; i++) {
+ const char *name = PIN_USED[i];
+ if (name != NULL)
+ print("ping %d: %s\r\n", i, name);
+ }
+
+ print("Total pins count: %d\r\n", totalPinsUsed);
+}
+
+static MemoryStream portNameStream;
+static char portNameBuffer[20];
+
+brain_pin_e parseBrainPin(const char *str) {
+ if (strEqual(str, "none"))
+ return GPIO_NONE;
+ // todo: create method toLowerCase?
+ if (str[0] != 'p' && str[0] != 'p') {
+ return GPIO_INVALID;
+ }
+ char port = str[1];
+ brain_pin_e basePin;
+ if (port >= 'a' && port <= 'z') {
+ basePin = (brain_pin_e) ((int) GPIOA_0 + 16 * (port - 'a'));
+ } else if (port >= 'A' && port <= 'Z') {
+ basePin = (brain_pin_e) ((int) GPIOA_0 + 16 * (port - 'A'));
+ } else {
+ return GPIO_INVALID;
+ }
+ const char *pinStr = str + 2;
+ int pin = atoi(pinStr);
+ return basePin + pin;
+}
+
+char *hwPortname(brain_pin_e brainPin) {
+ if (brainPin == GPIO_INVALID) {
+ return "INVALID";
+ }
+ GPIO_TypeDef *hwPort = getHwPort(brainPin);
+ if (hwPort == GPIO_NULL) {
+ return "NONE";
+ }
+ int hwPin = getHwPin(brainPin);
+ portNameStream.eos = 0; // reset
+ chprintf((BaseSequentialStream *) &portNameStream, "%s%d", portname(hwPort), hwPin);
+ portNameStream.buffer[portNameStream.eos] = 0; // need to terminate explicitly
+ return portNameBuffer;
+}
+
+void initPinRepository(void) {
+ /**
+ * this method cannot use console because this method is invoked before console is initialized
+ */
+ initLogging(&logger, "pin repos");
+
+ msObjectInit(&portNameStream, (uint8_t*)portNameBuffer, sizeof(portNameBuffer), 0);
+
+ for (int i = 0; i < PIN_REPO_SIZE; i++)
+ PIN_USED[i] = 0;
+ initialized = TRUE;
+ addConsoleAction("pins", reportPins);
+}
+
+static inline void markUsed(int index, const char *msg) {
+ PIN_USED[index] = msg;
+ totalPinsUsed++;
+}
+
+/**
+ * This method would set an error condition if pin is already used
+ */
+void mySetPadMode(const char *msg, ioportid_t port, ioportmask_t pin, iomode_t mode) {
+ if (!initialized) {
+ firmwareError("repository not initialized");
+ return;
+ }
+ print("%s on %s:%d\r\n", msg, portname(port), pin);
+
+ appendPrintf(&logger, "msg,%s", msg);
+ appendPrintf(&logger, " on %s%d%s", portname(port), pin, DELIMETER);
+ printLine(&logger);
+
+ int portIndex = getPortIndex(port);
+ int index = portIndex * 16 + pin;
+
+ if (PIN_USED[index] != NULL) {
+ firmwareError("%s%d req by %s used by %s", portname(port), pin, msg, PIN_USED[index]);
+ return;
+ }
+ markUsed(index, msg);
+
+ palSetPadMode(port, pin, mode);
+}
+
+/**
+ * This method would crash the program if pin is already in use
+ */
+void registedFundamentralIoPin(char *msg, ioportid_t port, ioportmask_t pin, iomode_t mode) {
+ efiAssertVoid(initialized, "repo not initialized");
+
+ int portIndex = getPortIndex(port);
+ int index = portIndex * 16 + pin;
+
+ if (PIN_USED[index] != NULL) {
+ print("!!!!!!!!!!!!! Already used [%s] %d\r\n", msg, pin);
+ print("!!!!!!!!!!!!! Already used by [%s]\r\n", PIN_USED[index]);
+ firmwareError("pin already used");
+ return;
+ }
+ markUsed(index, msg);
+ palSetPadMode(port, pin, mode);
+}
+
diff --git a/firmware/hw_layer/pin_repository.h b/firmware/hw_layer/pin_repository.h
new file mode 100644
index 0000000000..0079d82126
--- /dev/null
+++ b/firmware/hw_layer/pin_repository.h
@@ -0,0 +1,41 @@
+/*
+ * pin_repository.h
+ *
+ * @date Jan 15, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+/**
+ * @file pin_repository.h
+ * @brief I/O pin registry header
+ */
+
+#ifndef PIN_REPOSITORY_H_
+#define PIN_REPOSITORY_H_
+
+#include "ch.h"
+#include "hal.h"
+#include "io_pins.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void initPinRepository(void);
+char *hwPortname(brain_pin_e brainPin);
+brain_pin_e parseBrainPin(const char *str);
+void mySetPadMode(const char *msg, ioportid_t port, ioportmask_t pin, iomode_t mode);
+char *portname(GPIO_TypeDef* GPIOx);
+// does not exactly belong here, but that works better for tests
+void outputPinRegister(const char *msg, io_pin_e ioPin, GPIO_TypeDef *port, uint32_t pin);
+
+ioportmask_t getHwPin(brain_pin_e brainPin);
+GPIO_TypeDef * getHwPort(brain_pin_e brainPin);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+
+#endif /* PIN_REPOSITORY_H_ */
diff --git a/firmware/hw_layer/pwm_generator.cpp b/firmware/hw_layer/pwm_generator.cpp
new file mode 100644
index 0000000000..100e9e40e4
--- /dev/null
+++ b/firmware/hw_layer/pwm_generator.cpp
@@ -0,0 +1,63 @@
+/**
+ * @file pwm_generator.cpp
+ * @brief software PWM generator
+ *
+ * Software PWM implementation. Considering how low all frequencies are, we can totally afford a couple of float multiplications.
+ * By generating PWM programmatically we are saving the timers for better purposes. This implementation also supports generating
+ * synchronized waves as needed for example to emulate dual Hall-effect crankshaft position sensors.
+ *
+ *
+ * @date May 28, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ */
+
+#include "pwm_generator.h"
+
+#include "pin_repository.h"
+#include "datalogging.h"
+
+static Logging logger;
+
+/**
+ * This method controls the actual hardware pins
+ */
+void applyPinState(PwmConfig *state, int stateIndex) {
+ efiAssertVoid(state->multiWave.waveCount <= PWM_PHASE_MAX_WAVE_PER_PWM, "invalid waveCount");
+ for (int waveIndex = 0; waveIndex < state->multiWave.waveCount; waveIndex++) {
+ io_pin_e ioPin = state->outputPins[waveIndex];
+ efiAssertVoid(stateIndex < PWM_PHASE_MAX_COUNT, "invalid stateIndex");
+ int value = state->multiWave.waves[waveIndex].pinStates[stateIndex];
+ setOutputPinValue(ioPin, value);
+ }
+}
+
+void startSimplePwm(PwmConfig *state, const char *msg, io_pin_e ioPin,
+ float frequency, float dutyCycle) {
+ efiAssertVoid(dutyCycle >= 0 && dutyCycle <= 1, "dutyCycle");
+
+ float switchTimes[] = { dutyCycle, 1 };
+ int pinStates0[] = { 0, 1 };
+
+ int *pinStates[1] = { pinStates0 };
+
+ state->outputPins[0] = ioPin;
+
+ state->periodUs = frequency2periodUs(frequency);
+ weComplexInit(msg, state, 2, switchTimes, 1, pinStates, NULL, applyPinState);
+}
+
+void startSimplePwmExt(PwmConfig *state, const char *msg, brain_pin_e brainPin, io_pin_e ioPin,
+ float frequency, float dutyCycle) {
+
+ GPIO_TypeDef * port = getHwPort(brainPin);
+ int pin = getHwPin(brainPin);
+ outputPinRegister(msg, ioPin, port, pin);
+
+ startSimplePwm(state, msg, ioPin, frequency, dutyCycle);
+}
+
+void initPwmGenerator(void) {
+ initLogging(&logger, "PWM gen");
+}
+
diff --git a/firmware/hw_layer/pwm_generator.h b/firmware/hw_layer/pwm_generator.h
new file mode 100644
index 0000000000..6c123ad0ae
--- /dev/null
+++ b/firmware/hw_layer/pwm_generator.h
@@ -0,0 +1,36 @@
+/**
+ * @file pwm_generator.h
+ *
+ * @date May 28, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef PWM_GENERATOR_H_
+#define PWM_GENERATOR_H_
+
+#include "global.h"
+#include "pwm_generator_logic.h"
+
+#define DEBUG_PWM FALSE
+
+#include "gpio_helper.h"
+
+void startSimplePwm(PwmConfig *state, const char *msg, io_pin_e ioPin,
+ float dutyCycle, float frequency);
+void applyPinState(PwmConfig *state, int stateIndex);
+
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void startSimplePwmExt(PwmConfig *state, const char *msg, brain_pin_e brainPin, io_pin_e ioPin,
+ float frequency, float dutyCycle);
+
+void initPwmGenerator(void);
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* PWM_GENERATOR_H_ */
diff --git a/firmware/hw_layer/rtc_helper.c b/firmware/hw_layer/rtc_helper.c
new file mode 100644
index 0000000000..72402101a6
--- /dev/null
+++ b/firmware/hw_layer/rtc_helper.c
@@ -0,0 +1,141 @@
+/**
+ * @file rtc_helper.c
+ * @brief Real Time Clock helper
+ *
+ * @date Feb 5, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include
+#include
+#include "main.h"
+#include "rfiutil.h"
+#include "chrtclib.h"
+#include "rtc_helper.h"
+
+static Logging logger;
+
+#if EFI_RTC
+static void date_help(void) {
+ scheduleMsg(&logger, "Usage: date_help");
+ scheduleMsg(&logger, " date_get");
+ scheduleMsg(&logger, " date_set N");
+ scheduleMsg(&logger, "where N is time in seconds sins Unix epoch - see http://www.epochconverter.com");
+}
+
+#endif /* EFI_RTC */
+
+void date_set_tm(struct tm *timp) {
+#if EFI_RTC
+ rtcSetTimeTm(&RTCD1, timp);
+#endif /* EFI_RTC */
+}
+
+void date_get_tm(struct tm *timp) {
+#if EFI_RTC
+ rtcGetTimeTm(&RTCD1, timp);
+#endif /* EFI_RTC */
+}
+
+void dateToString(char *lcd_str) {
+#if EFI_RTC
+ // todo:
+ // re-implement this along the lines of chvprintf("%04u-%02u-%02u %02u:%02u:%02u\r\n", timp.tm_year + 1900, timp.tm_mon + 1, timp.tm_mday, timp.tm_hour,
+ // timp.tm_min, timp.tm_sec);
+ // this would require a temporary mem stream - see datalogging and other existing usages
+
+ strcpy(lcd_str, "00/00 00:00:00\0");
+ static char buff[4];
+ struct tm timp;
+ rtcGetTimeTm(&RTCD1, &timp); // get RTC date/time
+
+ itoa10(buff, timp.tm_mon + 1);
+ if(timp.tm_mon < 9) {
+ lcd_str[0] = '0';
+ lcd_str[1] = buff[0];
+ } else {
+ lcd_str[0] = buff[0];
+ lcd_str[1] = buff[1];
+ }
+ itoa10(buff, timp.tm_mday);
+ if(timp.tm_mday < 10) {
+ lcd_str[3] = '0';
+ lcd_str[4] = buff[0];
+ } else {
+ lcd_str[3] = buff[0];
+ lcd_str[4] = buff[1];
+ }
+ itoa10(buff, timp.tm_hour);
+ if(timp.tm_hour < 10) {
+ lcd_str[6] = '0';
+ lcd_str[7] = buff[0];
+ } else {
+ lcd_str[6] = buff[0];
+ lcd_str[7] = buff[1];
+ }
+ itoa10(buff, timp.tm_min);
+ if(timp.tm_min < 10) {
+ lcd_str[9] = '0';
+ lcd_str[10] = buff[0];
+ } else {
+ lcd_str[9] = buff[0];
+ lcd_str[10] = buff[1];
+ }
+ itoa10(buff, timp.tm_sec);
+ if(timp.tm_sec < 10) {
+ lcd_str[12] = '0';
+ lcd_str[13] = buff[0];
+ } else {
+ lcd_str[12] = buff[0];
+ lcd_str[13] = buff[1];
+ }
+#else
+ lcd_str[0] = 0;
+#endif /* EFI_RTC */
+}
+
+#if EFI_RTC
+static void date_get(void) {
+ static time_t unix_time;
+ struct tm timp;
+
+ unix_time = rtcGetTimeUnixSec(&RTCD1);
+ if (unix_time == -1) {
+ scheduleMsg(&logger, "incorrect time in RTC cell");
+ } else {
+ scheduleMsg(&logger, "%D - unix time", unix_time);
+ rtcGetTimeTm(&RTCD1, &timp);
+
+ appendMsgPrefix(&logger);
+ appendPrintf(&logger, "Current RTC time in GMT is: %04u-%02u-%02u %02u:%02u:%02u", timp.tm_year + 1900, timp.tm_mon + 1, timp.tm_mday, timp.tm_hour,
+ timp.tm_min, timp.tm_sec);
+ appendMsgPostfix(&logger);
+ scheduleLogging(&logger);
+ }
+}
+
+static void date_set(char *strDate) {
+ if (strlen(strDate) > 0) {
+ time_t unix_time = (double) atoff(strDate);
+ if (unix_time > 0) {
+ rtcSetTimeUnixSec(&RTCD1, unix_time);
+ date_get();
+ return;
+ }
+ }
+ scheduleMsg(&logger, "date_set Date parameter %s is wrong\r\n", strDate);
+}
+#endif /* EFI_RTC */
+
+void initRtc(void) {
+ initLogging(&logger, "rtc");
+#if EFI_RTC
+ printMsg(&logger, "initRtc()");
+
+ // yes, it's my begin time and we always start from this one 1391894433 - 2014-02-08 21:20:03
+ rtcSetTimeUnixSec(&RTCD1, 1391894433);
+ addConsoleAction("date_get", date_get);
+ addConsoleActionS("date_set", date_set);
+ addConsoleAction("date_help", date_help);
+#endif
+}
diff --git a/firmware/hw_layer/rtc_helper.h b/firmware/hw_layer/rtc_helper.h
new file mode 100644
index 0000000000..e08d1933e5
--- /dev/null
+++ b/firmware/hw_layer/rtc_helper.h
@@ -0,0 +1,27 @@
+/**
+ * @file rtc_helper.h
+ * @brief Real Time Clock helper
+ *
+ * @date Feb 5, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef RTC_HELPER_H_
+#define RTC_HELPER_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void initRtc(void);
+void date_set_tm(struct tm *);
+void date_get_tm(struct tm *);
+
+void dateToString(char *buffer);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* RTC_HELPER_H_ */
diff --git a/firmware/hw_layer/serial_over_usb/usbcfg.c b/firmware/hw_layer/serial_over_usb/usbcfg.c
new file mode 100644
index 0000000000..b13196f2c6
--- /dev/null
+++ b/firmware/hw_layer/serial_over_usb/usbcfg.c
@@ -0,0 +1,319 @@
+/*
+ ChibiOS/RT - Copyright (C) 2006-2013 Giovanni Di Sirio
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+#include "main.h"
+
+#if HAL_USE_SERIAL_USB || defined(__DOXYGEN__)
+
+/*
+ * Endpoints to be used for USBD1.
+ */
+#define USBD1_DATA_REQUEST_EP 1
+#define USBD1_DATA_AVAILABLE_EP 1
+#define USBD1_INTERRUPT_REQUEST_EP 2
+
+/*
+ * USB Device Descriptor.
+ */
+static const uint8_t vcom_device_descriptor_data[18] = {
+ USB_DESC_DEVICE (0x0110, /* bcdUSB (1.1). */
+ 0x02, /* bDeviceClass (CDC). */
+ 0x00, /* bDeviceSubClass. */
+ 0x00, /* bDeviceProtocol. */
+ 0x40, /* bMaxPacketSize. */
+ 0x0483, /* idVendor (ST). */
+ 0x5740, /* idProduct. */
+ 0x0200, /* bcdDevice. */
+ 1, /* iManufacturer. */
+ 2, /* iProduct. */
+ 3, /* iSerialNumber. */
+ 1) /* bNumConfigurations. */
+};
+
+/*
+ * Device Descriptor wrapper.
+ */
+static const USBDescriptor vcom_device_descriptor = {
+ sizeof vcom_device_descriptor_data,
+ vcom_device_descriptor_data
+};
+
+/* Configuration Descriptor tree for a CDC.*/
+static const uint8_t vcom_configuration_descriptor_data[67] = {
+ /* Configuration Descriptor.*/
+ USB_DESC_CONFIGURATION(67, /* wTotalLength. */
+ 0x02, /* bNumInterfaces. */
+ 0x01, /* bConfigurationValue. */
+ 0, /* iConfiguration. */
+ 0xC0, /* bmAttributes (self powered). */
+ 50), /* bMaxPower (100mA). */
+ /* Interface Descriptor.*/
+ USB_DESC_INTERFACE (0x00, /* bInterfaceNumber. */
+ 0x00, /* bAlternateSetting. */
+ 0x01, /* bNumEndpoints. */
+ 0x02, /* bInterfaceClass (Communications
+ Interface Class, CDC section
+ 4.2). */
+ 0x02, /* bInterfaceSubClass (Abstract
+ Control Model, CDC section 4.3). */
+ 0x01, /* bInterfaceProtocol (AT commands,
+ CDC section 4.4). */
+ 0), /* iInterface. */
+ /* Header Functional Descriptor (CDC section 5.2.3).*/
+ USB_DESC_BYTE (5), /* bLength. */
+ USB_DESC_BYTE (0x24), /* bDescriptorType (CS_INTERFACE). */
+ USB_DESC_BYTE (0x00), /* bDescriptorSubtype (Header
+ Functional Descriptor. */
+ USB_DESC_BCD (0x0110), /* bcdCDC. */
+ /* Call Management Functional Descriptor. */
+ USB_DESC_BYTE (5), /* bFunctionLength. */
+ USB_DESC_BYTE (0x24), /* bDescriptorType (CS_INTERFACE). */
+ USB_DESC_BYTE (0x01), /* bDescriptorSubtype (Call Management
+ Functional Descriptor). */
+ USB_DESC_BYTE (0x00), /* bmCapabilities (D0+D1). */
+ USB_DESC_BYTE (0x01), /* bDataInterface. */
+ /* ACM Functional Descriptor.*/
+ USB_DESC_BYTE (4), /* bFunctionLength. */
+ USB_DESC_BYTE (0x24), /* bDescriptorType (CS_INTERFACE). */
+ USB_DESC_BYTE (0x02), /* bDescriptorSubtype (Abstract
+ Control Management Descriptor). */
+ USB_DESC_BYTE (0x02), /* bmCapabilities. */
+ /* Union Functional Descriptor.*/
+ USB_DESC_BYTE (5), /* bFunctionLength. */
+ USB_DESC_BYTE (0x24), /* bDescriptorType (CS_INTERFACE). */
+ USB_DESC_BYTE (0x06), /* bDescriptorSubtype (Union
+ Functional Descriptor). */
+ USB_DESC_BYTE (0x00), /* bMasterInterface (Communication
+ Class Interface). */
+ USB_DESC_BYTE (0x01), /* bSlaveInterface0 (Data Class
+ Interface). */
+ /* Endpoint 2 Descriptor.*/
+ USB_DESC_ENDPOINT (USBD1_INTERRUPT_REQUEST_EP|0x80,
+ 0x03, /* bmAttributes (Interrupt). */
+ 0x0008, /* wMaxPacketSize. */
+ 0xFF), /* bInterval. */
+ /* Interface Descriptor.*/
+ USB_DESC_INTERFACE (0x01, /* bInterfaceNumber. */
+ 0x00, /* bAlternateSetting. */
+ 0x02, /* bNumEndpoints. */
+ 0x0A, /* bInterfaceClass (Data Class
+ Interface, CDC section 4.5). */
+ 0x00, /* bInterfaceSubClass (CDC section
+ 4.6). */
+ 0x00, /* bInterfaceProtocol (CDC section
+ 4.7). */
+ 0x00), /* iInterface. */
+ /* Endpoint 3 Descriptor.*/
+ USB_DESC_ENDPOINT (USBD1_DATA_AVAILABLE_EP, /* bEndpointAddress.*/
+ 0x02, /* bmAttributes (Bulk). */
+ 0x0040, /* wMaxPacketSize. */
+ 0x00), /* bInterval. */
+ /* Endpoint 1 Descriptor.*/
+ USB_DESC_ENDPOINT (USBD1_DATA_REQUEST_EP|0x80, /* bEndpointAddress.*/
+ 0x02, /* bmAttributes (Bulk). */
+ 0x0040, /* wMaxPacketSize. */
+ 0x00) /* bInterval. */
+};
+
+/*
+ * Configuration Descriptor wrapper.
+ */
+static const USBDescriptor vcom_configuration_descriptor = {
+ sizeof vcom_configuration_descriptor_data,
+ vcom_configuration_descriptor_data
+};
+
+/*
+ * U.S. English language identifier.
+ */
+static const uint8_t vcom_string0[] = {
+ USB_DESC_BYTE(4), /* bLength. */
+ USB_DESC_BYTE(USB_DESCRIPTOR_STRING), /* bDescriptorType. */
+ USB_DESC_WORD(0x0409) /* wLANGID (U.S. English). */
+};
+
+/*
+ * Vendor string.
+ */
+static const uint8_t vcom_string1[] = {
+ USB_DESC_BYTE(38), /* bLength. */
+ USB_DESC_BYTE(USB_DESCRIPTOR_STRING), /* bDescriptorType. */
+ 'S', 0, 'T', 0, 'M', 0, 'i', 0, 'c', 0, 'r', 0, 'o', 0, 'e', 0,
+ 'l', 0, 'e', 0, 'c', 0, 't', 0, 'r', 0, 'o', 0, 'n', 0, 'i', 0,
+ 'c', 0, 's', 0
+};
+
+/*
+ * Device Description string.
+ */
+static const uint8_t vcom_string2[] = {
+ USB_DESC_BYTE(56), /* bLength. */
+ USB_DESC_BYTE(USB_DESCRIPTOR_STRING), /* bDescriptorType. */
+ 'C', 0, 'h', 0, 'i', 0, 'b', 0, 'i', 0, 'O', 0, 'S', 0, '/', 0,
+ 'R', 0, 'T', 0, ' ', 0, 'V', 0, 'i', 0, 'r', 0, 't', 0, 'u', 0,
+ 'a', 0, 'l', 0, ' ', 0, 'C', 0, 'O', 0, 'M', 0, ' ', 0, 'P', 0,
+ 'o', 0, 'r', 0, 't', 0
+};
+
+/*
+ * Serial Number string.
+ */
+static const uint8_t vcom_string3[] = {
+ USB_DESC_BYTE(8), /* bLength. */
+ USB_DESC_BYTE(USB_DESCRIPTOR_STRING), /* bDescriptorType. */
+ '0' + CH_KERNEL_MAJOR, 0,
+ '0' + CH_KERNEL_MINOR, 0,
+ '0' + CH_KERNEL_PATCH, 0
+};
+
+/*
+ * Strings wrappers array.
+ */
+static const USBDescriptor vcom_strings[] = {
+ {sizeof vcom_string0, vcom_string0},
+ {sizeof vcom_string1, vcom_string1},
+ {sizeof vcom_string2, vcom_string2},
+ {sizeof vcom_string3, vcom_string3}
+};
+
+/*
+ * Handles the GET_DESCRIPTOR callback. All required descriptors must be
+ * handled here.
+ */
+static const USBDescriptor *get_descriptor(USBDriver *usbp,
+ uint8_t dtype,
+ uint8_t dindex,
+ uint16_t lang) {
+
+ (void)usbp;
+ (void)lang;
+ switch (dtype) {
+ case USB_DESCRIPTOR_DEVICE:
+ return &vcom_device_descriptor;
+ case USB_DESCRIPTOR_CONFIGURATION:
+ return &vcom_configuration_descriptor;
+ case USB_DESCRIPTOR_STRING:
+ if (dindex < 4)
+ return &vcom_strings[dindex];
+ }
+ return NULL;
+}
+
+/**
+ * @brief IN EP1 state.
+ */
+static USBInEndpointState ep1instate;
+
+/**
+ * @brief OUT EP1 state.
+ */
+static USBOutEndpointState ep1outstate;
+
+/**
+ * @brief EP1 initialization structure (both IN and OUT).
+ */
+static const USBEndpointConfig ep1config = {
+ USB_EP_MODE_TYPE_BULK,
+ NULL,
+ sduDataTransmitted,
+ sduDataReceived,
+ 0x0040,
+ 0x0040,
+ &ep1instate,
+ &ep1outstate,
+ 2,
+ NULL
+};
+
+/**
+ * @brief IN EP2 state.
+ */
+static USBInEndpointState ep2instate;
+
+/**
+ * @brief EP2 initialization structure (IN only).
+ */
+static const USBEndpointConfig ep2config = {
+ USB_EP_MODE_TYPE_INTR,
+ NULL,
+ sduInterruptTransmitted,
+ NULL,
+ 0x0010,
+ 0x0000,
+ &ep2instate,
+ NULL,
+ 1,
+ NULL
+};
+
+/*
+ * Handles the USB driver global events.
+ */
+static void usb_event(USBDriver *usbp, usbevent_t event) {
+ extern SerialUSBDriver SDU1;
+
+ switch (event) {
+ case USB_EVENT_RESET:
+ return;
+ case USB_EVENT_ADDRESS:
+ return;
+ case USB_EVENT_CONFIGURED:
+ chSysLockFromIsr();
+
+ /* Enables the endpoints specified into the configuration.
+ Note, this callback is invoked from an ISR so I-Class functions
+ must be used.*/
+ usbInitEndpointI(usbp, USBD1_DATA_REQUEST_EP, &ep1config);
+ usbInitEndpointI(usbp, USBD1_INTERRUPT_REQUEST_EP, &ep2config);
+
+ /* Resetting the state of the CDC subsystem.*/
+ sduConfigureHookI(&SDU1);
+
+ chSysUnlockFromIsr();
+ return;
+ case USB_EVENT_SUSPEND:
+ return;
+ case USB_EVENT_WAKEUP:
+ return;
+ case USB_EVENT_STALLED:
+ return;
+ }
+ return;
+}
+
+/*
+ * USB driver configuration.
+ */
+const USBConfig usbcfg = {
+ usb_event,
+ get_descriptor,
+ sduRequestsHook,
+ NULL
+};
+
+/*
+ * Serial over USB driver configuration.
+ */
+const SerialUSBConfig serusbcfg = {
+ &USBD1,
+ USBD1_DATA_REQUEST_EP,
+ USBD1_DATA_AVAILABLE_EP,
+ USBD1_INTERRUPT_REQUEST_EP
+};
+
+/* Virtual serial port over USB.*/
+SerialUSBDriver SDU1;
+#endif
diff --git a/firmware/hw_layer/serial_over_usb/usbcfg.h b/firmware/hw_layer/serial_over_usb/usbcfg.h
new file mode 100644
index 0000000000..845c404b65
--- /dev/null
+++ b/firmware/hw_layer/serial_over_usb/usbcfg.h
@@ -0,0 +1,28 @@
+/*
+ ChibiOS/RT - Copyright (C) 2006-2013 Giovanni Di Sirio
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+/**
+ * @file usbcfg.h
+ * @brief Serial-over-USB header
+ */
+
+#ifndef _USBCFG_H_
+#define _USBCFG_H_
+
+
+#endif /* _USBCFG_H_ */
+
+/** @} */
diff --git a/firmware/hw_layer/serial_over_usb/usbconsole.c b/firmware/hw_layer/serial_over_usb/usbconsole.c
new file mode 100644
index 0000000000..9cdbac5014
--- /dev/null
+++ b/firmware/hw_layer/serial_over_usb/usbconsole.c
@@ -0,0 +1,51 @@
+/**
+ * @file usbconsole.c
+ * @brief USB-over-serial configuration
+ *
+ * @date Oct 14, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+
+#if EFI_PROD_CODE
+
+#include "usbconsole.h"
+#include "usbcfg.h"
+#include "efifeatures.h"
+
+extern SerialUSBDriver SDU1;
+extern const SerialUSBConfig serusbcfg;
+extern const USBConfig usbcfg;
+
+void usb_serial_start(void) {
+ /*
+ * Initializes a serial-over-USB CDC driver.
+ */
+ sduObjectInit(&SDU1);
+ sduStart(&SDU1, &serusbcfg);
+
+ /*
+ * Activates the USB driver and then the USB bus pull-up on D+.
+ * Note, a delay is inserted in order to not have to disconnect the cable
+ * after a reset.
+ */
+ usbDisconnectBus(serusbcfg.usbp);
+ chThdSleepMilliseconds(1000);
+ usbStart(serusbcfg.usbp, &usbcfg);
+ usbConnectBus(serusbcfg.usbp);
+
+ /*
+ * Activates the serial driver 2 using the driver default configuration.
+ * PA2(TX) and PA3(RX) are routed to USART2.
+ */
+ sdStart(&SD2, NULL);
+ palSetPadMode(GPIOA, 2, PAL_MODE_ALTERNATE(7));
+ palSetPadMode(GPIOA, 3, PAL_MODE_ALTERNATE(7));
+}
+
+bool is_usb_serial_ready(void) {
+ return SDU1.config->usbp->state == USB_ACTIVE;
+}
+
+#endif
diff --git a/firmware/hw_layer/serial_over_usb/usbconsole.h b/firmware/hw_layer/serial_over_usb/usbconsole.h
new file mode 100644
index 0000000000..37dd75d5b3
--- /dev/null
+++ b/firmware/hw_layer/serial_over_usb/usbconsole.h
@@ -0,0 +1,23 @@
+/**
+ * @file usbconsole.h
+ *
+ * @date Oct 14, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef USBCONSOLE_H_
+#define USBCONSOLE_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void usb_serial_start(void);
+bool is_usb_serial_ready(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* USBCONSOLE_H_ */
diff --git a/firmware/hw_layer/stm32f4/mpu_util.cpp b/firmware/hw_layer/stm32f4/mpu_util.cpp
new file mode 100644
index 0000000000..28a1b7d2a2
--- /dev/null
+++ b/firmware/hw_layer/stm32f4/mpu_util.cpp
@@ -0,0 +1,50 @@
+/**
+ * @file mpu_util.cpp
+ *
+ * @date Jul 27, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+#include "mpu_util.h"
+#include "error_handling.h"
+
+void baseHardwareInit(void) {
+ // looks like this holds a random value on start? Let's set a nice clean zero
+ DWT_CYCCNT = 0;
+}
+
+void DebugMonitorVector(void) {
+
+ chDbgPanic3("DebugMonitorVector", __FILE__, __LINE__);
+
+ while (TRUE)
+ ;
+}
+
+void UsageFaultVector(void) {
+
+ chDbgPanic3("UsageFaultVector", __FILE__, __LINE__);
+
+ while (TRUE)
+ ;
+}
+
+void BusFaultVector(void) {
+
+ chDbgPanic3("BusFaultVector", __FILE__, __LINE__);
+
+ while (TRUE) {
+ }
+}
+
+void HardFaultVector(void) {
+
+ chDbgPanic3("HardFaultVector", __FILE__, __LINE__);
+
+ while (TRUE) {
+ }
+}
+
+
+
diff --git a/firmware/hw_layer/stm32f4/mpu_util.h b/firmware/hw_layer/stm32f4/mpu_util.h
new file mode 100644
index 0000000000..4228b6fd2f
--- /dev/null
+++ b/firmware/hw_layer/stm32f4/mpu_util.h
@@ -0,0 +1,29 @@
+/**
+ * @file mpu_util.h
+ *
+ * @date Jul 27, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+#ifndef MPU_UTIL_H_
+#define MPU_UTIL_H_
+
+void baseHardwareInit(void);
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+// these need to be declared C style for the linker magic to work
+
+void DebugMonitorVector(void);
+void UsageFaultVector(void);
+void BusFaultVector(void);
+void HardFaultVector(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+
+#endif /* MPU_UTIL_H_ */
diff --git a/firmware/hw_layer/stm32f4xx_specific.h b/firmware/hw_layer/stm32f4xx_specific.h
new file mode 100644
index 0000000000..91e6bbf12e
--- /dev/null
+++ b/firmware/hw_layer/stm32f4xx_specific.h
@@ -0,0 +1,51 @@
+/*
+ * stm32f4xx_specific.h
+ *
+ * @date Jun 16, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef STM32F4XX_SPECIFIC_H_
+#define STM32F4XX_SPECIFIC_H_
+
+#ifndef GPIO_AF_TIM1
+#define GPIO_AF_TIM1 1
+#endif
+
+#ifndef GPIO_AF_TIM2
+#define GPIO_AF_TIM2 1
+#endif
+
+#ifndef GPIO_AF_TIM3
+#define GPIO_AF_TIM3 2
+#endif
+
+#ifndef GPIO_AF_TIM4
+#define GPIO_AF_TIM4 2
+#endif
+
+#ifndef GPIO_AF_TIM5
+#define GPIO_AF_TIM5 2
+#endif
+
+#ifndef GPIO_AF_TIM9
+#define GPIO_AF_TIM9 3
+#endif
+
+#ifndef ADC_TwoSamplingDelay_5Cycles
+#define ADC_TwoSamplingDelay_5Cycles ((uint32_t)0x00000000)
+#endif
+
+#ifndef ADC_TwoSamplingDelay_20Cycles
+#define ADC_TwoSamplingDelay_20Cycles ((uint32_t)0x00000F00)
+#endif
+
+
+
+
+
+#ifndef ADC_CR2_SWSTART
+#define ADC_CR2_SWSTART ((uint32_t)0x40000000)
+#endif
+
+#endif /* STM32F4XX_SPECIFIC_H_ */
diff --git a/firmware/hw_layer/trigger_input.cpp b/firmware/hw_layer/trigger_input.cpp
new file mode 100644
index 0000000000..0a732fdce4
--- /dev/null
+++ b/firmware/hw_layer/trigger_input.cpp
@@ -0,0 +1,107 @@
+/**
+ * @file trigger_input.cpp
+ * @brief Position sensor hardware layer
+ *
+ * @date Dec 30, 2012
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+
+#if EFI_SHAFT_POSITION_INPUT
+
+#include "trigger_input.h"
+#include "wave_analyzer_hw.h"
+#include "pin_repository.h"
+#include "trigger_structure.h"
+#include "trigger_central.h"
+#include "engine_configuration.h"
+#include "wave_analyzer_hw.h"
+
+static WaveReaderHw primaryCrankInput;
+static WaveReaderHw secondaryCrankInput;
+
+extern engine_configuration_s *engineConfiguration;
+extern engine_configuration2_s *engineConfiguration2;
+extern board_configuration_s *boardConfiguration;
+
+static inline ICUDriver *getPrimaryInputCaptureDriver(void) {
+ return getInputCaptureDriver(boardConfiguration->triggerInputPins[0]);
+}
+
+static inline ICUDriver *getSecondaryInputCaptureDriver(void) {
+ return getInputCaptureDriver(boardConfiguration->triggerInputPins[1]);
+}
+
+/**
+ * that's hardware timer input capture IRQ entry point
+ * 'width' events happens before the 'period' event
+ */
+static void shaft_icu_width_callback(ICUDriver *icup) {
+// todo: support for 3rd trigger input channel
+// todo: start using real event time from HW event, not just software timer?
+ int isPrimary = icup == getPrimaryInputCaptureDriver();
+ if (!isPrimary && !engineConfiguration->needSecondTriggerInput) {
+ return;
+ }
+ // icucnt_t last_width = icuGetWidth(icup); so far we are fine with system time
+ // todo: add support for 3rd channel
+ trigger_event_e signal = isPrimary ? SHAFT_PRIMARY_UP : SHAFT_SECONDARY_UP;
+
+ hwHandleShaftSignal(signal);
+}
+
+static void shaft_icu_period_callback(ICUDriver *icup) {
+ int isPrimary = icup == getPrimaryInputCaptureDriver();
+ if (!isPrimary && !engineConfiguration->needSecondTriggerInput) {
+ return;
+ }
+
+ // todo: add support for 3rd channel
+ // icucnt_t last_period = icuGetPeriod(icup); so far we are fine with system time
+ trigger_event_e signal = isPrimary ? SHAFT_PRIMARY_DOWN : SHAFT_SECONDARY_DOWN;
+ hwHandleShaftSignal(signal);
+}
+
+/**
+ * the main purpose of this configuration structure is to specify the input interrupt callbacks
+ */
+static ICUConfig shaft_icucfg = { ICU_INPUT_ACTIVE_LOW, 100000, /* 100kHz ICU clock frequency. */
+shaft_icu_width_callback, shaft_icu_period_callback };
+
+void initShaftPositionInputCapture(void) {
+
+ ICUDriver *driver;
+
+ driver = getInputCaptureDriver(boardConfiguration->triggerInputPins[0]);
+
+ // todo: extract method!
+ // initialize primary Input Capture Unit pin
+ initWaveAnalyzerDriver(&primaryCrankInput, driver, getHwPort(boardConfiguration->triggerInputPins[0]),
+ getHwPin(boardConfiguration->triggerInputPins[0]));
+ /**
+ * Start primary Input Capture Unit using given configuration
+ * @see shaft_icucfg for callback entry points
+ */
+ shaft_icucfg.channel = ICU_CHANNEL_1;
+ print("initShaftPositionInputCapture 1 %s\r\n", hwPortname(boardConfiguration->triggerInputPins[0]));
+ if (driver != NULL) {
+ icuStart(driver, &shaft_icucfg);
+ icuEnable(driver);
+ }
+
+ driver = getInputCaptureDriver(boardConfiguration->triggerInputPins[1]);
+ // initialize secondary Input Capture Unit pin
+ initWaveAnalyzerDriver(&secondaryCrankInput, getSecondaryInputCaptureDriver(),
+ getHwPort(boardConfiguration->triggerInputPins[1]), getHwPin(boardConfiguration->triggerInputPins[1]));
+ shaft_icucfg.channel = ICU_CHANNEL_1;
+ print("initShaftPositionInputCapture 2 %s\r\n", hwPortname(boardConfiguration->triggerInputPins[1]));
+ if (driver != NULL) {
+ icuStart(driver, &shaft_icucfg);
+ icuEnable(driver);
+ }
+
+ print("crank input disabled\r\n");
+}
+
+#endif /* EFI_SHAFT_POSITION_INPUT */
diff --git a/firmware/hw_layer/trigger_input.h b/firmware/hw_layer/trigger_input.h
new file mode 100644
index 0000000000..88038bd95c
--- /dev/null
+++ b/firmware/hw_layer/trigger_input.h
@@ -0,0 +1,23 @@
+/**
+ * @file trigger_input.h
+ * @brief Position sensor hardware layer
+ *
+ * @date Dec 30, 2012
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef CRANK_INPUT_H_
+#define CRANK_INPUT_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void initShaftPositionInputCapture(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* CRANK_INPUT_H_ */
diff --git a/firmware/hw_layer/wave_analyzer_hw.c b/firmware/hw_layer/wave_analyzer_hw.c
new file mode 100644
index 0000000000..767a90d403
--- /dev/null
+++ b/firmware/hw_layer/wave_analyzer_hw.c
@@ -0,0 +1,147 @@
+/*
+ * @file wave_analyzer_hw.c
+ * @brief Helper methods related to Input Capture Unit (ICU)
+ *
+ * @date Jun 23, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "wave_analyzer_hw.h"
+#include "eficonsole.h"
+#include "pin_repository.h"
+
+static void icuWidthCallback(ICUDriver *driver);
+static void icuPeriordCallBack(ICUDriver *driver);
+
+/*
+ * 30ms seems like width maximum, at 16bit precision that means
+ * CORE_CLOCK / 33.33333 = TICKS * 65536
+ * 168000000 / 33.333333 / 65536 = 76.90
+ */
+static ICUConfig wave_icucfg = { ICU_INPUT_ACTIVE_LOW, CORE_CLOCK / 100, icuWidthCallback, icuPeriordCallBack, 0,
+ ICU_CHANNEL_1, 0 };
+
+static int registeredIcuCount = 0;
+static WaveReaderHw* registeredIcus[8];
+
+static WaveReaderHw * findWaveReaderHw(ICUDriver *driver) {
+ for (int i = 0; i < registeredIcuCount; i++) {
+ if (registeredIcus[i]->driver == driver) {
+ return registeredIcus[i];
+ }
+ }
+ firmwareError("reader not found");
+ return (WaveReaderHw *) NULL;
+}
+
+static void icuWidthCallback(ICUDriver *driver) {
+ /*
+ * see comment in icuPeriordCallBack
+ int rowWidth = icuGetWidth(driver);
+ */
+ WaveReaderHw * hw = findWaveReaderHw(driver);
+ invokeJustArgCallbacks(&hw->widthListeners);
+}
+
+static void icuPeriordCallBack(ICUDriver *driver) {
+ /*
+ * we do not use timer period at all - we just need the event. For all time characteristics,
+ * we use system time
+ * int period = icuGetPeriod(driver);
+ */
+
+ WaveReaderHw * hw = findWaveReaderHw(driver);
+ invokeJustArgCallbacks(&hw->periodListeners);
+}
+
+static uint32_t getAlternateFunctions(ICUDriver *driver) {
+ if (driver == NULL) {
+ firmwareError("getAlternateFunctions(NULL)");
+ return 0xffffffff;
+ }
+#if STM32_ICU_USE_TIM1
+ if (driver == &ICUD1) {
+ return GPIO_AF_TIM1;
+ }
+#endif
+#if STM32_ICU_USE_TIM2
+ if (driver == &ICUD2) {
+ return GPIO_AF_TIM2;
+ }
+#endif
+#if STM32_ICU_USE_TIM3
+ if (driver == &ICUD3) {
+ return GPIO_AF_TIM3;
+ }
+#endif
+#if STM32_ICU_USE_TIM4
+ if (driver == &ICUD4) {
+ return GPIO_AF_TIM4;
+ }
+#endif
+#if STM32_ICU_USE_TIM9
+ if (driver == &ICUD9) {
+ return GPIO_AF_TIM9;
+ }
+#endif
+ firmwareError("No such driver");
+ return 0xffffffff;
+}
+
+ICUDriver * getInputCaptureDriver(brain_pin_e hwPin) {
+#if STM32_ICU_USE_TIM1
+ if (hwPin == GPIOA_8) {
+ return &ICUD1;
+ }
+#endif
+#if STM32_ICU_USE_TIM2
+ if (hwPin == GPIOA_5) {
+ return &ICUD2;
+ }
+#endif
+#if STM32_ICU_USE_TIM3
+ if (hwPin == GPIOC_6) {
+ return &ICUD3;
+ }
+#endif
+#if STM32_ICU_USE_TIM9
+ if (hwPin == GPIOE_7) {
+ return &ICUD9;
+ }
+#endif
+ return (ICUDriver *) NULL;
+}
+
+void initWaveAnalyzerDriver(WaveReaderHw *hw, ICUDriver *driver, ioportid_t port, ioportmask_t pin) {
+ hw->driver = driver;
+ hw->port = port;
+ hw->pin = pin;
+ if (driver != NULL) {
+ iomode_t mode = (iomode_t) PAL_MODE_ALTERNATE(getAlternateFunctions(driver));
+ mySetPadMode("wave input", port, pin, mode);
+
+// hw->widthListeners.currentListenersCount = 0;
+
+ registeredIcus[registeredIcuCount++] = hw;
+ }
+}
+
+void setWaveReaderMode(WaveReaderHw *hw, int mode) {
+ hw->activeMode = mode;
+ if (hw->activeMode) {
+ wave_icucfg.mode = ICU_INPUT_ACTIVE_HIGH;
+ } else {
+ wave_icucfg.mode = ICU_INPUT_ACTIVE_LOW;
+ }
+ ICUDriver *driver = hw->driver;
+
+ if (driver != NULL) {
+ if (hw->started) {
+ icuDisable(driver);
+ icuStop(driver);
+ }
+ icuStart(driver, &wave_icucfg);
+ icuEnable(driver);
+ }
+ hw->started = TRUE;
+}
diff --git a/firmware/hw_layer/wave_analyzer_hw.h b/firmware/hw_layer/wave_analyzer_hw.h
new file mode 100644
index 0000000000..6e54769bfd
--- /dev/null
+++ b/firmware/hw_layer/wave_analyzer_hw.h
@@ -0,0 +1,39 @@
+/*
+ * wave_analyzer_hw.h
+ *
+ * @date Jun 23, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef WAVE_ANALYZER_HW_H_
+#define WAVE_ANALYZER_HW_H_
+
+#include "main.h"
+#include "listener_array.h"
+
+typedef struct {
+ ICUDriver *driver;
+ GPIO_TypeDef *port;
+ int pin;
+ int activeMode; // 0 for ICU_INPUT_ACTIVE_LOW, 1 for ICU_INPUT_ACTIVE_HIGH
+ volatile int started;
+
+ IntListenerArray widthListeners;
+ IntListenerArray periodListeners;
+} WaveReaderHw;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void initWaveAnalyzerDriver(WaveReaderHw *hw, ICUDriver *driver,
+ ioportid_t port, ioportmask_t pin);
+void setWaveReaderMode(WaveReaderHw *hw, int mode);
+ICUDriver * getInputCaptureDriver(brain_pin_e hwPin);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* WAVE_ANALYZER_HW_H_ */
diff --git a/firmware/license.txt b/firmware/license.txt
new file mode 100644
index 0000000000..94a9ed024d
--- /dev/null
+++ b/firmware/license.txt
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+ .
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/firmware/main.cpp b/firmware/main.cpp
new file mode 100644
index 0000000000..8d880181f1
--- /dev/null
+++ b/firmware/main.cpp
@@ -0,0 +1,26 @@
+/**
+ * @file main.cpp
+ * @brief C++ main entry point
+ *
+ * @date Nov 29, 2012
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ * http://rusefi.com/
+ */
+
+#include "main.h"
+#include "rusefi.h"
+#include "mpu_util.h"
+
+int main(void) {
+ /*
+ * ChibiOS/RT initialization
+ */
+ halInit();
+ chSysInit();
+
+ baseHardwareInit();
+
+ runRusEfi();
+ return 0;
+}
+
diff --git a/firmware/main.h b/firmware/main.h
new file mode 100644
index 0000000000..d56f738481
--- /dev/null
+++ b/firmware/main.h
@@ -0,0 +1,62 @@
+/**
+ * @file main.h
+ *
+ * @date Nov 29, 2012
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+#pragma once
+#ifndef MAIN_H_
+#define MAIN_H_
+
+#include
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+#include "global.h"
+
+#include "eficonsole.h"
+#include "cli_registry.h"
+#include "efilib.h"
+#include "rusefi.h"
+
+#include "efifeatures.h"
+#include "efitime.h"
+#include "boards.h"
+#include "rusefi_enums.h"
+
+#include "datalogging.h"
+#include "chprintf.h"
+
+#include "stm32f4xx_specific.h"
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+// todo: access some existing configuration field
+#define CORE_CLOCK 168000000
+
+/**
+ * number of SysClock ticks in one ms
+ */
+#define TICKS_IN_MS (CH_FREQUENCY / 1000)
+
+#define Delay(ms) chThdSleepMilliseconds(ms)
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+int systicks2ms(int systicks);
+
+bool lockAnyContext(void);
+void unlockAnyContext(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif /* MAIN_H_ */
diff --git a/firmware/readme.txt b/firmware/readme.txt
new file mode 100644
index 0000000000..5b1ad57d1b
--- /dev/null
+++ b/firmware/readme.txt
@@ -0,0 +1,5 @@
+rusEfi engine control unit firmware
+
+Wiki http://rusefi.com/
+Doxygen documentation is availble at http://rusefi.com/docs/html/
+General source code Q&A is at http://rusefi.com/forum/viewtopic.php?f=5&t=10
diff --git a/firmware/rules.mk b/firmware/rules.mk
new file mode 100644
index 0000000000..e65d4fa012
--- /dev/null
+++ b/firmware/rules.mk
@@ -0,0 +1,220 @@
+# ARM Cortex-Mx common makefile scripts and rules.
+
+# Output directory and files
+ifeq ($(BUILDDIR),)
+ BUILDDIR = build
+endif
+ifeq ($(BUILDDIR),.)
+ BUILDDIR = build
+endif
+OUTFILES = $(BUILDDIR)/$(PROJECT).elf $(BUILDDIR)/$(PROJECT).hex \
+ $(BUILDDIR)/$(PROJECT).bin $(BUILDDIR)/$(PROJECT).dmp
+
+# Automatic compiler options
+OPT = $(USE_OPT)
+COPT = $(USE_COPT)
+CPPOPT = $(USE_CPPOPT)
+ifeq ($(USE_LINK_GC),yes)
+ OPT += -ffunction-sections -fdata-sections -fno-common
+endif
+
+# Source files groups and paths
+ifeq ($(USE_THUMB),yes)
+ TCSRC += $(CSRC)
+ TCPPSRC += $(CPPSRC)
+else
+ ACSRC += $(CSRC)
+ ACPPSRC += $(CPPSRC)
+endif
+ASRC = $(ACSRC)$(ACPPSRC)
+TSRC = $(TCSRC)$(TCPPSRC)
+SRCPATHS = $(sort $(dir $(ASMXSRC)) $(dir $(ASMSRC)) $(dir $(ASRC)) $(dir $(TSRC)))
+
+# Various directories
+OBJDIR = $(BUILDDIR)/obj
+LSTDIR = $(BUILDDIR)/lst
+
+# Object files groups
+ACOBJS = $(addprefix $(OBJDIR)/, $(notdir $(ACSRC:.c=.o)))
+ACPPOBJS = $(addprefix $(OBJDIR)/, $(notdir $(ACPPSRC:.cpp=.o)))
+TCOBJS = $(addprefix $(OBJDIR)/, $(notdir $(TCSRC:.c=.o)))
+TCPPOBJS = $(addprefix $(OBJDIR)/, $(notdir $(TCPPSRC:.cpp=.o)))
+ASMOBJS = $(addprefix $(OBJDIR)/, $(notdir $(ASMSRC:.s=.o)))
+ASMXOBJS = $(addprefix $(OBJDIR)/, $(notdir $(ASMXSRC:.S=.o)))
+OBJS = $(ASMXOBJS) $(ASMOBJS) $(ACOBJS) $(TCOBJS) $(ACPPOBJS) $(TCPPOBJS)
+
+# Paths
+IINCDIR = $(patsubst %,-I%,$(INCDIR) $(DINCDIR) $(UINCDIR))
+LLIBDIR = $(patsubst %,-L%,$(DLIBDIR) $(ULIBDIR))
+
+# Macros
+DEFS = $(DDEFS) $(UDEFS)
+ADEFS = $(DADEFS) $(UADEFS)
+
+# Libs
+LIBS = $(DLIBS) $(ULIBS)
+
+# Various settings
+MCFLAGS = -mcpu=$(MCU)
+ODFLAGS = -x --syms
+ASFLAGS = $(MCFLAGS) -Wa,-amhls=$(LSTDIR)/$(notdir $(<:.s=.lst)) $(ADEFS)
+ASXFLAGS = $(MCFLAGS) -Wa,-amhls=$(LSTDIR)/$(notdir $(<:.S=.lst)) $(ADEFS)
+CFLAGS = $(MCFLAGS) $(OPT) $(COPT) $(CWARN) -Wa,-alms=$(LSTDIR)/$(notdir $(<:.c=.lst)) $(DEFS)
+CPPFLAGS = $(MCFLAGS) $(CPPOPT) $(CPPWARN) -Wa,-alms=$(LSTDIR)/$(notdir $(<:.cpp=.lst)) $(DEFS)
+ifeq ($(USE_LINK_GC),yes)
+ LDFLAGS = $(MCFLAGS) -nostartfiles -T$(LDSCRIPT) -Wl,-Map=$(BUILDDIR)/$(PROJECT).map,--cref,--no-warn-mismatch,--gc-sections $(LLIBDIR)
+else
+ LDFLAGS = $(MCFLAGS) -nostartfiles -T$(LDSCRIPT) -Wl,-Map=$(BUILDDIR)/$(PROJECT).map,--cref,--no-warn-mismatch $(LLIBDIR)
+endif
+
+# Thumb interwork enabled only if needed because it kills performance.
+ifneq ($(TSRC),)
+ CFLAGS += -DTHUMB_PRESENT
+ CPPFLAGS += -DTHUMB_PRESENT
+ ASFLAGS += -DTHUMB_PRESENT
+ ifneq ($(ASRC),)
+ # Mixed ARM and THUMB mode.
+ CFLAGS += -mthumb-interwork
+ CPPFLAGS += -mthumb-interwork
+ ASFLAGS += -mthumb-interwork
+ LDFLAGS += -mthumb-interwork
+ else
+ # Pure THUMB mode, THUMB C code cannot be called by ARM asm code directly.
+ CFLAGS += -mno-thumb-interwork -DTHUMB_NO_INTERWORKING
+ CPPFLAGS += -mno-thumb-interwork -DTHUMB_NO_INTERWORKING
+ ASFLAGS += -mno-thumb-interwork -DTHUMB_NO_INTERWORKING -mthumb
+ LDFLAGS += -mno-thumb-interwork -mthumb
+ endif
+else
+ # Pure ARM mode
+ CFLAGS += -mno-thumb-interwork
+ CPPFLAGS += -mno-thumb-interwork
+ ASFLAGS += -mno-thumb-interwork
+ LDFLAGS += -mno-thumb-interwork
+endif
+
+# Generate dependency information
+CFLAGS += -MD -MP -MF .dep/$(@F).d
+CPPFLAGS += -MD -MP -MF .dep/$(@F).d
+
+# Paths where to search for sources
+VPATH = $(SRCPATHS)
+
+#
+# Makefile rules
+#
+
+all: $(OBJS) $(OUTFILES) MAKE_ALL_RULE_HOOK
+
+MAKE_ALL_RULE_HOOK:
+
+$(OBJS): | $(BUILDDIR)
+
+$(BUILDDIR) $(OBJDIR) $(LSTDIR):
+ifneq ($(USE_VERBOSE_COMPILE),yes)
+ @echo Compiler Options
+ @echo $(CPPC) -c $(CPPFLAGS) -I. $(IINCDIR) main.cpp -o main.o
+ @echo
+endif
+ mkdir -p $(OBJDIR)
+ mkdir -p $(LSTDIR)
+
+$(ACPPOBJS) : $(OBJDIR)/%.o : %.cpp Makefile
+ifeq ($(USE_VERBOSE_COMPILE),yes)
+ @echo
+ $(CPPC) -c $(CPPFLAGS) $(AOPT) -I. $(IINCDIR) $< -o $@
+else
+ @echo Compiling $( $@
+else
+ @echo Creating $@
+ @$(OD) $(ODFLAGS) $< > $@
+ @echo Done
+endif
+
+clean:
+ @echo Cleaning
+ -rm -fR .dep $(BUILDDIR)
+ @echo Done
+
+#
+# Include the dependency files, should be the last of the makefile
+#
+-include $(shell mkdir .dep 2>/dev/null) $(wildcard .dep/*)
+
+# *** EOF ***
diff --git a/firmware/rusefi.cpp b/firmware/rusefi.cpp
new file mode 100644
index 0000000000..e3ad9aa08c
--- /dev/null
+++ b/firmware/rusefi.cpp
@@ -0,0 +1,233 @@
+/**
+ * @file rusefi.cpp
+ * @brief Initialization code and main status reporting look
+ *
+ * @date Dec 25, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+/**
+ * @mainpage
+ *
+ * @section sec_into
+ *
+ * rusEfi is implemented based on the idea that with modern 100+ MHz microprocessors the relatively
+ * undemanding task of internal combustion engine control could be implemented in a high-level, processor-independent
+ * (to some extent) manner. Thus the key concepts of rusEfi: dependency on high-level hardware abstraction layer, software-based PWM etc.
+ *
+ * @section sec_main Brief overview
+ *
+ * rusEfi runs on crankshaft or camshaft ('trigger') position sensor events.
+ * Once per crankshaft revolution we evaluate the amount of needed fuel and
+ * the spark timing. Once we have decided on the parameters for this revolution
+ * we schedule all the actions to be triggered by the closest trigger event.
+ *
+ * We also have some utility threads like idle control thread and communication threads.
+ *
+ *
+ *
+ * @section sec_trigger Trigger Decoding
+ *
+ * Our primary trigger decoder is based on the idea of synchronizing the primary shaft signal and simply counting events on
+ * the secondary signal. A typical scenario would be when camshaft positions sensor is the primary signal and crankshaft is secondary,
+ * but sometimes there would be two signals generated by two camshaft sensors.
+ * Another scenario is when we only have crankshaft position sensor, this would make it the primary signal and there would be no secondary signal.
+ *
+ * There is no software filtering so the signals are expected to be valid. TODO: in reality we are still catching engine stop noise as unrealisticly high RPM.
+ *
+ * The decoder is configured to act either on the primary signal rise or on the primary signal fall. It then compares the duration
+ * of time from the previous signal to the duration of time from the signal before previous, and if the ratio falls into the configurable
+ * range between 'syncRatioFrom' and 'syncRatioTo' this is assumed to be the synchronizing event.
+ *
+ * For instance, for a 36/1 skipped tooth wheel the ratio range for synchronization is from 1.5 to 3
+ *
+ * Some triggers do not require synchronization, this case we just count signals.
+ * A single tooth primary signal would be a typical example when synchronization is not needed.
+ *
+ *
+ *
+ *
+ *
+ * @section sec_scheduler Event Scheduler
+ *
+ * It is a general agreement to measure all angles in crankshaft angles. In a four stroke
+ * engine, a full cycle consists of two revolutions of the crankshaft, so all the angles are
+ * running between 0 and 720 degrees.
+ *
+ * Ignition timing is a great example of a process which highlights the need of a hybrid
+ * approach to event scheduling.
+ * The most important part of controlling ignition
+ * is firing up the spark at the right moment - so, for this job we need 'angle-based' timing,
+ * for example we would need to fire up the spark at 700 degrees. Before we can fire up the spark
+ * at 700 degrees, we need to charge the ignition coil, for example this dwell time is 4ms - that
+ * means we need to turn on the coil at '4 ms before 700 degrees'. Let's assume that the engine is
+ * current at 600 RPM - that means 360 degrees would take 100ms so 4ms is 14.4 degrees at current RPM which
+ * means we need to start charting the coil at 685.6 degrees.
+ *
+ * The position sensors at our disposal are not providing us the current position at any moment of time -
+ * all we've got is a set of events which are happening at the knows positions. For instance, let's assume that
+ * our sensor sends as an event at 0 degrees, at 90 degrees, at 600 degrees and and 690 degrees.
+ *
+ * So, for this particular sensor the most precise scheduling would be possible if we schedule coil charting
+ * as '85.6 degrees after the 600 degrees position sensor event', and spark firing as
+ * '10 degrees after the 690 position sensor event'. Considering current RPM, we calculate that '10 degress after' is
+ * 2.777ms, so we schedule spark firing at '2.777ms after the 690 position sensor event', thus combining trigger events
+ * with time-based offset.
+ *
+ *
+ * @section sec_fuel_injection Fuel Injection
+ *
+ *
+ * @sectuion sec_misc
+ *
+ * See main_trigger_callback.cpp for main trigger event handler
+ * See fuel_math.cpp for details on fuel amount logic
+ * See rpm_calculator.cpp for details on how getRpm() is calculated
+ *
+ */
+
+#include "main.h"
+#include "trigger_structure.h"
+#include "ec2.h"
+#include "hardware.h"
+#include "engine_controller.h"
+
+#include "global.h"
+extern "C" {
+
+#include "rfi_perftest.h"
+#include "rusefi.h"
+#include "memstreams.h"
+}
+
+#include "eficonsole.h"
+#include "status_loop.h"
+#include "pin_repository.h"
+
+#if EFI_HD44780_LCD
+#include "lcd_HD44780.h"
+#endif /* EFI_HD44780_LCD */
+
+#if EFI_ENGINE_EMULATOR
+#include "engine_emulator.h"
+#endif /* EFI_ENGINE_EMULATOR */
+
+static Logging logging;
+
+int main_loop_started = FALSE;
+
+static MemoryStream firmwareErrorMessageStream;
+uint8_t errorMessageBuffer[200];
+static bool hasFirmwareErrorFlag = FALSE;
+extern board_configuration_s *boardConfiguration;
+
+char *getFirmwareError(void) {
+ return (char*)errorMessageBuffer;
+}
+
+void runRusEfi(void) {
+ msObjectInit(&firmwareErrorMessageStream, errorMessageBuffer, sizeof(errorMessageBuffer), 0);
+
+ initErrorHandling();
+
+ /**
+ * First data structure keeps track of which hardware I/O pins are used by whom
+ */
+ initPinRepository();
+
+ /**
+ * Next we should initialize serial port console, it's important to know what's going on
+ */
+ initializeConsole();
+ initLogging(&logging, "main");
+
+ addConsoleAction("reset", scheduleReset);
+
+ /**
+ * Initialize hardware drivers
+ */
+ initHardware(&logging);
+
+ initStatusLoop();
+ /**
+ * Now let's initialize actual engine control logic
+ * todo: should we initialize some? most? controllers before hardware?
+ */
+ initEngineContoller();
+
+#if EFI_PERF_METRICS
+ initTimePerfActions();
+#endif
+
+#if EFI_ENGINE_EMULATOR
+ initEngineEmulator(boardConfiguration);
+#endif
+ startStatusThreads();
+
+ print("Running main loop\r\n");
+ main_loop_started = TRUE;
+ /**
+ * This loop is the closes we have to 'main loop' - but here we only publish the status. The main logic of engine
+ * control is around main_trigger_callback
+ */
+ while (TRUE) {
+ efiAssertVoid(getRemainingStack(chThdSelf()) > 100, "stack#1");
+
+#if EFI_CLI_SUPPORT && !EFI_UART_ECHO_TEST_MODE
+ // sensor state + all pending messages for our own dev console
+ updateDevConsoleState();
+#endif /* EFI_CLI_SUPPORT */
+
+ chThdSleepMilliseconds(boardConfiguration->consoleLoopPeriod);
+ }
+}
+
+static virtual_timer_t resetTimer;
+
+static void rebootNow(void) {
+ NVIC_SystemReset();
+}
+/**
+ * Some configuration changes require full firmware reset.
+ * Once day we will write graceful shutdown, but that would be one day.
+ */
+void scheduleReset(void) {
+ scheduleMsg(&logging, "Rebooting in 5 seconds...");
+ lockAnyContext();
+ chVTSetI(&resetTimer, 5 * CH_FREQUENCY, (vtfunc_t) rebootNow, NULL);
+ unlockAnyContext();
+}
+
+extern int main_loop_started;
+
+static char panicMessage[200];
+
+void chDbgStackOverflowPanic(Thread *otp) {
+ strcpy(panicMessage, "stack overflow: ");
+#ifdef CH_USE_REGISTRY
+ strcat(panicMessage, otp->p_name);
+#endif
+ chDbgPanic3(panicMessage, __FILE__, __LINE__);
+}
+
+bool hasFirmwareError(void) {
+ return hasFirmwareErrorFlag;
+}
+
+void firmwareError(const char *fmt, ...) {
+ if (hasFirmwareErrorFlag)
+ return;
+ setOutputPinValue(LED_ERROR, 1);
+ hasFirmwareErrorFlag = TRUE;
+ firmwareErrorMessageStream.eos = 0; // reset
+ va_list ap;
+ va_start(ap, fmt);
+ chvprintf((BaseSequentialStream *) &firmwareErrorMessageStream, fmt, ap);
+ va_end(ap);
+
+ firmwareErrorMessageStream.buffer[firmwareErrorMessageStream.eos] = 0; // need to terminate explicitly
+}
+
+int getRusEfiVersion(void) {
+ return 20140829;
+}
diff --git a/firmware/rusefi.h b/firmware/rusefi.h
new file mode 100644
index 0000000000..bed7895386
--- /dev/null
+++ b/firmware/rusefi.h
@@ -0,0 +1,14 @@
+/**
+ * @file rusefi.h
+ *
+ * @date Dec 25, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef RUSEFI_H_
+#define RUSEFI_H_
+
+void runRusEfi(void);
+void scheduleReset(void);
+
+#endif /* RUSEFI_H_ */
diff --git a/firmware/svnversion.h b/firmware/svnversion.h
new file mode 100644
index 0000000000..868a61b6e3
--- /dev/null
+++ b/firmware/svnversion.h
@@ -0,0 +1,5 @@
+// This file was generated by Version2Header
+// Thu Aug 21 13:54:37 EDT 2014
+#ifndef VCS_VERSION
+#define VCS_VERSION "4372"
+#endif
diff --git a/firmware/update_version.bat b/firmware/update_version.bat
new file mode 100644
index 0000000000..2dd3705dab
--- /dev/null
+++ b/firmware/update_version.bat
@@ -0,0 +1,3 @@
+rem Let's regemerate 'svnversion.h'
+rem TODO: handle std err - for example, in case svn needs upgrade
+java -jar ../java_tools/version2header.jar
\ No newline at end of file
diff --git a/firmware/util/LocalVersionHolder.cpp b/firmware/util/LocalVersionHolder.cpp
new file mode 100644
index 0000000000..43d6f6557f
--- /dev/null
+++ b/firmware/util/LocalVersionHolder.cpp
@@ -0,0 +1,30 @@
+/**
+ * @file LocalVersionHolder.cpp
+ *
+ * @date Mar 19, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "LocalVersionHolder.h"
+
+extern "C" {
+int getGlobalConfigurationVersion(void);
+}
+
+//ctor
+LocalVersionHolder::LocalVersionHolder() {
+ localVersion = 0;
+}
+
+int LocalVersionHolder::getVersion() {
+ return localVersion;
+}
+
+bool LocalVersionHolder::isOld() {
+ int global = getGlobalConfigurationVersion();
+ if (global > localVersion) {
+ localVersion = global;
+ return true;
+ }
+ return false;
+}
diff --git a/firmware/util/LocalVersionHolder.h b/firmware/util/LocalVersionHolder.h
new file mode 100644
index 0000000000..63e0ec12d0
--- /dev/null
+++ b/firmware/util/LocalVersionHolder.h
@@ -0,0 +1,25 @@
+/**
+ * @file LocalVersionHolder.h
+ *
+ * @date Mar 19, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef LOCALVERSIONHOLDER_H_
+#define LOCALVERSIONHOLDER_H_
+
+#include "stdbool.h"
+
+class LocalVersionHolder {
+public:
+ LocalVersionHolder();
+
+ bool isOld();
+ int getVersion();
+
+private:
+ int localVersion;
+
+};
+
+#endif /* LOCALVERSIONHOLDER_H_ */
diff --git a/firmware/util/cli_registry.c b/firmware/util/cli_registry.c
new file mode 100644
index 0000000000..6952d96ae8
--- /dev/null
+++ b/firmware/util/cli_registry.c
@@ -0,0 +1,405 @@
+/**
+ * @file cli_registry.c
+ * @brief Command-line interface commands registry
+ *
+ * Here we have a data structure which holds all the dynamically-registered
+ * command line interface action names & callback. This logic is invoked in
+ * user context by the console thread - see consoleThreadThreadEntryPoint
+ *
+ * TODO: there is too much copy-paste here, this class needs some refactoring :)
+ *
+ * @date Nov 15, 2012
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include
+#include
+#include
+#include "main.h"
+#include "cli_registry.h"
+#include "efilib.h"
+
+#if EFI_PROD_CODE
+#include "board_test.h"
+#endif
+
+#if EFI_PROD_CODE || EFI_SIMULATOR
+#include "eficonsole.h"
+static Logging logging;
+#endif /* EFI_PROD_CODE */
+
+static int consoleActionCount = 0;
+static TokenCallback consoleActions[CONSOLE_MAX_ACTIONS];
+
+#define SECURE_LINE_PREFIX "sec!"
+
+void resetConsoleActions(void) {
+ consoleActionCount = 0;
+}
+
+static void doAddAction(const char *token, int type, Void callback) {
+ efiAssertVoid(consoleActionCount < CONSOLE_MAX_ACTIONS, "Too many console actions");
+ TokenCallback *current = &consoleActions[consoleActionCount++];
+ current->token = token;
+ current->parameterType = type;
+ current->callback = callback;
+}
+
+/**
+ * @brief Register console action without parameters
+ */
+void addConsoleAction(const char *token, Void callback) {
+ doAddAction(token, NO_PARAMETER, callback);
+}
+
+/**
+ * @brief Register a console command with one Integer parameter
+ */
+void addConsoleActionI(const char *token, VoidInt callback) {
+ doAddAction(token, ONE_PARAMETER, (Void) callback);
+}
+
+/**
+ * @brief Register a console command with two Integer parameters
+ */
+void addConsoleActionII(const char *token, VoidIntInt callback) {
+ doAddAction(token, TWO_INTS_PARAMETER, (Void) callback);
+}
+
+void addConsoleActionS(const char *token, VoidCharPtr callback) {
+ doAddAction(token, STRING_PARAMETER, (Void) callback);
+}
+
+void addConsoleActionSS(const char *token, VoidCharPtrCharPtr callback) {
+ doAddAction(token, STRING2_PARAMETER, (Void) callback);
+}
+
+void addConsoleActionSSS(const char *token, VoidCharPtrCharPtrCharPtr callback) {
+ doAddAction(token, STRING3_PARAMETER, (Void) callback);
+}
+
+void addConsoleActionSSSSS(const char *token, VoidCharPtrCharPtrCharPtrCharPtrCharPtr callback) {
+ doAddAction(token, STRING5_PARAMETER, (Void) callback);
+}
+
+void addConsoleActionF(const char *token, VoidFloat callback) {
+ doAddAction(token, FLOAT_PARAMETER, (Void) callback);
+}
+
+void addConsoleActionFF(const char *token, VoidFloatFloat callback) {
+ doAddAction(token, FLOAT_FLOAT_PARAMETER, (Void) callback);
+}
+
+/**
+ * @brief This function prints out a list of all available commands
+ */
+void helpCommand(void) {
+#if EFI_PROD_CODE
+ if (isBoardTestMode()) {
+ printBoardTestState();
+ return;
+ }
+#endif /* EFI_PROD_CODE */
+
+#if (EFI_PROD_CODE || EFI_SIMULATOR) || defined(__DOXYGEN__)
+ scheduleMsg(&logging, "%d actions available", consoleActionCount);
+ for (int i = 0; i < consoleActionCount; i++) {
+ TokenCallback *current = &consoleActions[i];
+ scheduleMsg(&logging, " %s: %d parameters", current->token, current->parameterType);
+ }
+#endif
+}
+
+/**
+ * @brief This is just a test function
+ */
+static void echo(int value) {
+ print("got value: %d\r\n", value);
+}
+
+void handleActionWithParameter(TokenCallback *current, char *parameter) {
+ if (current->parameterType == STRING_PARAMETER) {
+ VoidCharPtr callbackS = (VoidCharPtr) current->callback;
+ (*callbackS)(parameter);
+ return;
+ }
+
+ // todo: refactor this hell!
+ if (current->parameterType == STRING2_PARAMETER) {
+ int spaceIndex = indexOf(parameter, ' ');
+ if (spaceIndex == -1) {
+ return;
+ }
+ parameter[spaceIndex] = 0;
+ char * param0 = parameter;
+
+ parameter += spaceIndex + 1;
+ char * param1 = parameter;
+
+ VoidCharPtrCharPtr callbackS = (VoidCharPtrCharPtr) current->callback;
+ (*callbackS)(param0, param1);
+ return;
+
+ }
+
+ if (current->parameterType == STRING3_PARAMETER) {
+ int spaceIndex = indexOf(parameter, ' ');
+ if (spaceIndex == -1) {
+ return;
+ }
+ parameter[spaceIndex] = 0;
+ char * param0 = parameter;
+
+ parameter += spaceIndex + 1;
+ spaceIndex = indexOf(parameter, ' ');
+ if (spaceIndex == -1)
+ return;
+ parameter[spaceIndex] = 0;
+ char * param1 = parameter;
+ parameter += spaceIndex + 1;
+ char * param2 = parameter;
+
+ VoidCharPtrCharPtrCharPtr callbackS = (VoidCharPtrCharPtrCharPtr) current->callback;
+ (*callbackS)(param0, param1, param2);
+ return;
+
+ }
+
+ // todo: refactor this hell!
+ if (current->parameterType == STRING5_PARAMETER) {
+ int spaceIndex = indexOf(parameter, ' ');
+ if (spaceIndex == -1) {
+ return;
+ }
+ parameter[spaceIndex] = 0;
+ char * param0 = parameter;
+
+ parameter += spaceIndex + 1;
+ spaceIndex = indexOf(parameter, ' ');
+ if (spaceIndex == -1)
+ return;
+ parameter[spaceIndex] = 0;
+ char * param1 = parameter;
+
+ parameter += spaceIndex + 1;
+ spaceIndex = indexOf(parameter, ' ');
+ if (spaceIndex == -1)
+ return;
+ parameter[spaceIndex] = 0;
+ char * param2 = parameter;
+
+ parameter += spaceIndex + 1;
+ spaceIndex = indexOf(parameter, ' ');
+ if (spaceIndex == -1)
+ return;
+ parameter[spaceIndex] = 0;
+ char * param3 = parameter;
+
+ parameter += spaceIndex + 1;
+ char * param4 = parameter;
+
+ VoidCharPtrCharPtrCharPtrCharPtrCharPtr callbackS = (VoidCharPtrCharPtrCharPtrCharPtrCharPtr) current->callback;
+ (*callbackS)(param0, param1, param2, param3, param4);
+ return;
+
+ }
+
+ if (current->parameterType == TWO_INTS_PARAMETER) {
+ int spaceIndex = indexOf(parameter, ' ');
+ if (spaceIndex == -1)
+ return;
+ parameter[spaceIndex] = 0;
+ int value1 = atoi(parameter);
+ if (absI(value1) == absI(ERROR_CODE)) {
+#if (EFI_PROD_CODE || EFI_SIMULATOR) || defined(__DOXYGEN__)
+ scheduleMsg(&logging, "not an integer [%s]", parameter);
+#endif
+ return;
+ }
+ parameter += spaceIndex + 1;
+ int value2 = atoi(parameter);
+ if (absI(value2) == absI(ERROR_CODE)) {
+#if (EFI_PROD_CODE || EFI_SIMULATOR) || defined(__DOXYGEN__)
+ scheduleMsg(&logging, "not an integer [%s]", parameter);
+#endif
+ return;
+ }
+ VoidIntInt callbackS = (VoidIntInt) current->callback;
+ (*callbackS)(value1, value2);
+ return;
+ }
+
+ if (current->parameterType == FLOAT_PARAMETER) {
+ float value = atoff(parameter);
+ VoidFloat callbackF = (VoidFloat) current->callback;
+
+ // invoke callback function by reference
+ (*callbackF)(value);
+ return;
+ }
+
+ if (current->parameterType == FLOAT_FLOAT_PARAMETER) {
+ int spaceIndex = indexOf(parameter, ' ');
+ if (spaceIndex == -1)
+ return;
+ parameter[spaceIndex] = 0;
+ float value1 = atoff(parameter);
+ parameter += spaceIndex + 1;
+ float value2 = atoff(parameter);
+ VoidFloatFloat callbackS = (VoidFloatFloat) current->callback;
+ (*callbackS)(value1, value2);
+ return;
+ }
+
+ int value = atoi(parameter);
+ if (value == ERROR_CODE) {
+ print("invalid integer [%s]\r\n", parameter);
+ return;
+ }
+
+ VoidInt callback1 = (VoidInt) current->callback;
+
+ // invoke callback function by reference
+ (*callback1)(value);
+
+}
+
+/**
+ * @return Number of space-separated tokens in the string
+ */
+int tokenLength(const char *msgp) {
+ int result = 0;
+ while (*msgp) {
+ char ch = *msgp++;
+ if (ch == ' ') {
+ break;
+ }
+ result++;
+ }
+ return result;
+}
+
+int strEqual(const char *str1, const char *str2) {
+ // todo: there must be a standard function?!
+ int len1 = strlen(str1);
+ int len2 = strlen(str2);
+ if (len1 != len2) {
+ return false;
+ }
+ for (int i = 0; i < len1; i++)
+ if (str1[i] != str2[i])
+ return false;
+ return TRUE;
+}
+
+void initConsoleLogic() {
+#if EFI_PROD_CODE || EFI_SIMULATOR
+ initLogging(&logging, "rfi console");
+#endif /* EFI_PROD_CODE */
+ resetConsoleActions();
+ addConsoleAction("help", helpCommand);
+ addConsoleActionI("echo", echo);
+}
+
+/**
+ * @return NULL if input line validation failed, reference to line payload if validation succeeded.
+ * @see sendOutConfirmation() for command confirmation processing.
+ */
+char *validateSecureLine(char *line) {
+ if (line == NULL)
+ return NULL;
+ if (strncmp(SECURE_LINE_PREFIX, line, 4) == 0) {
+ // COM protocol looses bytes, this is a super-naive error detection
+
+// print("Got secure mode request header [%s]\r\n", line);
+ line += 4;
+// print("Got secure mode request command [%s]\r\n", line);
+
+ char *divider = line;
+ while (*divider != '!') {
+ if (*divider == '\0') {
+ print("Divider not found [%s]\r\n", line);
+ return NULL;
+ }
+ divider++;
+ }
+ *divider++ = 0; // replacing divider symbol with zero
+ int expectedLength = atoi(line);
+ line = divider;
+ int actualLength = strlen(line);
+ if (expectedLength != actualLength) {
+ print("Error detected: expected %d but got %d in [%s]\r\n", expectedLength, actualLength, line);
+ return NULL;
+ }
+ }
+ return line;
+}
+
+static char confirmation[200];
+
+static bool handleConsoleLineInternal(char *line, int lineLength) {
+ int firstTokenLength = tokenLength(line);
+
+// print("processing [%s] with %d actions\r\n", line, consoleActionCount);
+
+ if (firstTokenLength == lineLength) {
+ // no-param actions are processed here
+ for (int i = 0; i < consoleActionCount; i++) {
+ TokenCallback *current = &consoleActions[i];
+ if (strEqual(line, current->token)) {
+ // invoke callback function by reference
+ (*current->callback)();
+ return true;
+ }
+ }
+ } else {
+ char *ptr = line + firstTokenLength;
+ ptr[0] = 0; // change space into line end
+ ptr++; // start from next symbol
+
+ for (int i = 0; i < consoleActionCount; i++) {
+ TokenCallback *current = &consoleActions[i];
+ if (strEqual(line, current->token)) {
+ handleActionWithParameter(current, ptr);
+ return TRUE;
+ }
+ }
+ }
+ return false;
+}
+
+#if (EFI_PROD_CODE || EFI_SIMULATOR) || defined(__DOXYGEN__)
+static void sendOutConfirmation(const char *command, int length) {
+ scheduleMsg(&logging, "%s%d", command, length);
+}
+#endif
+
+/**
+ * @brief This function takes care of one command line once we have it
+ */
+void handleConsoleLine(char *line) {
+ line = validateSecureLine(line);
+ if (line == NULL)
+ return; // error detected
+
+ int lineLength = strlen(line);
+ if (lineLength > 100) {
+ // todo: better max size logic
+ // todo: better reaction to excessive line
+ print("Long line?\r\n");
+ return;
+ }
+
+ strcpy(confirmation, "confirmation_");
+ strcat(confirmation, line);
+ strcat(confirmation, ":");
+
+#if EFI_PROD_CODE || EFI_SIMULATOR
+ sendOutConfirmation(confirmation, lineLength);
+#endif
+
+ bool isKnownComman = handleConsoleLineInternal(line, lineLength);
+
+ if (!isKnownComman)
+ helpCommand();
+}
diff --git a/firmware/util/cli_registry.h b/firmware/util/cli_registry.h
new file mode 100644
index 0000000000..2f06662389
--- /dev/null
+++ b/firmware/util/cli_registry.h
@@ -0,0 +1,71 @@
+/**
+ * @file cli_registry.h
+ * @brief Command-line interface commands registry
+ *
+ * @date Nov 15, 2012
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef RFICONSOLE_LOGIC_H_
+#define RFICONSOLE_LOGIC_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+#define CONSOLE_MAX_ACTIONS 128
+
+typedef enum {
+ NO_PARAMETER,
+ ONE_PARAMETER,
+ FLOAT_PARAMETER,
+ STRING_PARAMETER,
+ STRING2_PARAMETER,
+ STRING3_PARAMETER,
+ STRING5_PARAMETER,
+ TWO_INTS_PARAMETER,
+ FLOAT_FLOAT_PARAMETER
+} ACTION_PARAMETER_TYPE;
+
+typedef struct {
+ const char *token;
+ int parameterType;
+ void (*callback)(void);
+} TokenCallback;
+
+//void addDefaultConsoleActions(void);
+//void handleActionWithParameter(TokenCallback *current, char *parameter);
+int tokenLength(const char *msgp);
+
+typedef void (*Void)(void);
+typedef void (*VoidInt)(int);
+typedef void (*VoidFloat)(float);
+typedef void (*VoidFloatFloat)(float, float);
+typedef void (*VoidIntInt)(int, int);
+typedef void (*VoidCharPtr)(char *);
+typedef void (*VoidCharPtrCharPtr)(const char *, const char *);
+typedef void (*VoidCharPtrCharPtrCharPtr)(const char *, const char *, const char *);
+typedef void (*VoidCharPtrCharPtrCharPtrCharPtrCharPtr)(const char *, const char *, const char *, const char *, const char *);
+
+char *validateSecureLine(char *line);
+int strEqual(const char *str1, const char *str2);
+void resetConsoleActions(void);
+void helpCommand(void);
+void initConsoleLogic(void);
+void handleConsoleLine(char *line);
+void addConsoleAction(const char *token, Void callback);
+void addConsoleActionI(const char *token, VoidInt callback);
+void addConsoleActionII(const char *token, VoidIntInt callback);
+void addConsoleActionF(const char *token, VoidFloat callback);
+void addConsoleActionFF(const char *token, VoidFloatFloat callback);
+void addConsoleActionS(const char *token, VoidCharPtr callback);
+void addConsoleActionSS(const char *token, VoidCharPtrCharPtr callback);
+void addConsoleActionSSS(const char *token, VoidCharPtrCharPtrCharPtr callback);
+void addConsoleActionSSSSS(const char *token, VoidCharPtrCharPtrCharPtrCharPtrCharPtr callback);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* RFICONSOLE_H_ */
diff --git a/firmware/util/crc.c b/firmware/util/crc.c
new file mode 100644
index 0000000000..11fa67c4c3
--- /dev/null
+++ b/firmware/util/crc.c
@@ -0,0 +1,92 @@
+/**
+ * @file crc.c
+ * @date Sep 20, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "crc.h"
+
+#define WIDTH (8)
+#define TOPBIT (1 << (WIDTH - 1))
+#define POLYNOMIAL 0xD8
+
+crc_t calc_crc(const crc_t message[], int nBytes) {
+ crc_t remainder = 0;
+
+ /*
+ * Perform modulo-2 division, a byte at a time.
+ */
+ for (int byte = 0; byte < nBytes; ++byte) {
+ /*
+ * Bring the next byte into the remainder.
+ */
+ remainder ^= (message[byte] << (WIDTH - 8));
+
+ /*
+ * Perform modulo-2 division, a bit at a time.
+ */
+ for (unsigned char bit = 8; bit > 0; --bit) {
+ /*
+ * Try to divide the current data bit.
+ */
+ if (remainder & TOPBIT) {
+ remainder = (remainder << 1) ^ POLYNOMIAL;
+ } else {
+ remainder = (remainder << 1);
+ }
+ }
+ }
+
+ /*
+ * The final remainder is the CRC result.
+ */
+ return remainder;
+}
+
+static uint32_t crc32_tab[] = { 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535,
+ 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
+ 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856,
+ 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e,
+ 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6,
+ 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
+ 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87,
+ 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f,
+ 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97,
+ 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
+ 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158,
+ 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc,
+ 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010,
+ 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
+ 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739,
+ 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d,
+ 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671,
+ 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
+ 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda,
+ 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a,
+ 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92,
+ 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
+ 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b,
+ 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b,
+ 0x6fb077e1, 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3,
+ 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
+ 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c,
+ 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8,
+ 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d };
+
+/**
+ * Online CRC calculator:
+ * http://www.zorc.breitbandkatze.de/crc.html
+ */
+uint32_t crc32(const void *buf, uint32_t size) {
+ const uint8_t *p;
+ uint32_t crc = 0;
+
+ p = buf;
+ crc = crc ^ 0xFFFFFFFF;
+
+ while (size--) {
+ crc = crc32_tab[(crc ^ *p++) & 0xFF] ^ (crc >> 8);
+ }
+
+ return crc ^ 0xFFFFFFFF;
+}
diff --git a/firmware/util/crc.h b/firmware/util/crc.h
new file mode 100644
index 0000000000..a189ad896c
--- /dev/null
+++ b/firmware/util/crc.h
@@ -0,0 +1,26 @@
+/**
+ * @file crc.h
+ *
+ * @date Sep 20, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef CRC_H_
+#define CRC_H_
+
+#include "stdint.h"
+
+typedef unsigned char crc_t;
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+crc_t calc_crc(const crc_t message[], int nBytes);
+uint32_t crc32(const void *buf, uint32_t size);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* CRC_H_ */
diff --git a/firmware/util/cyclic_buffer.cpp b/firmware/util/cyclic_buffer.cpp
new file mode 100644
index 0000000000..36392b5be0
--- /dev/null
+++ b/firmware/util/cyclic_buffer.cpp
@@ -0,0 +1,94 @@
+/**
+ * @file cyclic_buffer.c
+ * @brief A cyclic buffer is a data structure that uses a single, fixed-size buffer as if it were connected end-to-end.
+ *
+ * http://en.wikipedia.org/wiki/Circular_buffer
+ *
+ * @date Dec 8, 2013
+ * @author Andrey Belomutskiy, Daniel Hill (c) 2012-2014
+ * @author Daniel Hill - Modified to use C++ - Mar 2, 2014
+ */
+
+
+#include "cyclic_buffer.h"
+#include
+
+//ctor
+cyclic_buffer::cyclic_buffer()
+{
+ memset((void*)elements, 0, sizeof(elements)); // I would usually use static_cast, but due to the elements being volatile we cannot.
+ currentIndex = 0;
+ count = 0;
+}
+
+//cpctor
+cyclic_buffer::cyclic_buffer(const cyclic_buffer& cb)
+{
+ //Deep copy the data
+ currentIndex = cb.currentIndex;
+ count = cb.count;
+ for(int i = 0; i < CB_MAX_SIZE; ++i)
+ {
+ elements[i] = cb.elements[i];
+ }
+}
+
+//dtor
+cyclic_buffer::~cyclic_buffer()
+{
+ //No dynamic allocation - safe to leave
+}
+
+//overloaded =operator
+cyclic_buffer& cyclic_buffer::operator=(const cyclic_buffer& rhCb)
+{
+ //Deep copy
+ currentIndex = rhCb.currentIndex;
+ count = rhCb.count;
+ for(int i = 0; i < CB_MAX_SIZE; ++i)
+ {
+ elements[i] = rhCb.elements[i];
+ }
+ return *this;
+}
+
+void cyclic_buffer::add(int value)
+{
+ ++currentIndex;
+ if(currentIndex == CB_MAX_SIZE)
+ {
+ currentIndex = 0;
+ }
+ elements[currentIndex] = value;
+
+ ++count;
+}
+
+void cyclic_buffer::clear() {
+ count = 0;
+}
+
+int cyclic_buffer::sum(int length)
+{
+ if(length > count)
+ {
+ length = count;
+ }
+
+ int ci = currentIndex; // local copy to increase thread-safety
+ int result = 0;
+
+ for(int i = 0; i < length; ++i)
+ {
+ int index = ci - i;
+ while (index < 0)
+ {
+ index += CB_MAX_SIZE;
+ }
+
+ result += elements[index];
+ }
+
+ return result;
+}
+
diff --git a/firmware/util/cyclic_buffer.h b/firmware/util/cyclic_buffer.h
new file mode 100644
index 0000000000..0a1ba8f4f4
--- /dev/null
+++ b/firmware/util/cyclic_buffer.h
@@ -0,0 +1,41 @@
+/**
+ * @file cyclic_buffer.h
+ *
+ * @date Dec 8, 2013
+ * @author Andrey Belomutskiy, Daniel Hill
+ *
+ * Daniel Hill - Modified to use C++ - Mar 2, 2014
+*/
+
+#ifndef CYCLIC_BUFFER_H
+#define CYCLIC_BUFFER_H
+
+static const short CB_MAX_SIZE = 16;
+
+class cyclic_buffer
+{
+ public:
+ //ctor
+ cyclic_buffer();
+ //cpctor
+ cyclic_buffer(const cyclic_buffer& cb);
+ //dtor
+ ~cyclic_buffer();
+
+ public:
+ //overloaded =operator
+ cyclic_buffer& operator=(const cyclic_buffer& rhCb);
+
+ public:
+ void add(int value);
+ int sum(int length);
+ void clear();
+
+
+ private:
+ volatile int elements[CB_MAX_SIZE];
+ volatile int currentIndex;
+ volatile int count;
+};
+
+#endif //CYCLIC_BUFFER_H
diff --git a/firmware/util/data_buffer.c b/firmware/util/data_buffer.c
new file mode 100644
index 0000000000..5aaaadbdbb
--- /dev/null
+++ b/firmware/util/data_buffer.c
@@ -0,0 +1,70 @@
+/*@
+ * @file data_buffer.c
+ *
+ * @date Dec 8, 2012
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "data_buffer.h"
+#include "main.h"
+#if EFI_PROD_CODE
+#include "eficonsole.h"
+#endif /* EFI_PROD_CODE */
+
+int dbIsFull(data_buffer_s *db) {
+ return db->size == DB_MAX_SIZE;
+}
+
+void dbClear(data_buffer_s *db) {
+ db->size = 0;
+}
+
+void dbCopy(data_buffer_s *source, data_buffer_s *target) {
+ int s = source->size;
+ target->size = s;
+ for (int i = 0; i < s; i++)
+ target->elements[i] = source->elements[i];
+}
+
+void dbAdd(data_buffer_s *db, int value) {
+ if (db->size == DB_MAX_SIZE)
+ return;
+ int s = db->size;
+ db->elements[s] = value;
+ db->size = s + 1;
+}
+
+int modp(int param) {
+ return param > EF_PERIOD ? param - EF_PERIOD : param;
+}
+
+void dbPrint(data_buffer_s *db, char *message, int withDiff) {
+ int s = db->size;
+ print("buffer [%s] size=%d\r\n", message, s);
+ int range = db->elements[s - 1] - db->elements[0];
+ print("range %d\r\n", range);
+
+ for (int i = 0; i < s; i++) {
+ print("%d: %d", i, db->elements[i]);
+ if (withDiff && i > 0) {
+ int diff = modp(db->elements[i]) - modp(db->elements[i - 1]);
+ print(" t=%d", diff);
+ }
+ print("\r\n");
+ }
+}
+
+void dbPrintTable(data_buffer_s *table[], char *caption[], int columns) {
+ for (int c = 0; c < columns; c++)
+ print("%7s", caption[c]);
+ print("\r\n");
+
+ for (int r = 0; r < DB_MAX_SIZE; r++) {
+ for (int c = 0; c < columns; c++) {
+ data_buffer_s *buf = table[c];
+ print("%7d", buf->elements[r]);
+ }
+ print("\r\n");
+ }
+}
+
diff --git a/firmware/util/data_buffer.h b/firmware/util/data_buffer.h
new file mode 100644
index 0000000000..8b183777b6
--- /dev/null
+++ b/firmware/util/data_buffer.h
@@ -0,0 +1,27 @@
+/*
+ * data_buffer.h
+ *
+ * @date Dec 8, 2012
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef DATA_BUFFER_H_
+#define DATA_BUFFER_H_
+
+#define DB_MAX_SIZE 1024
+
+#define EF_PERIOD 100000000
+
+typedef struct {
+ int elements[DB_MAX_SIZE];
+ int size;
+} data_buffer_s;
+
+int dbIsFull(data_buffer_s *db);
+void dbClear(data_buffer_s *db);
+void dbAdd(data_buffer_s *db, int value);
+void dbCopy(data_buffer_s *source, data_buffer_s *target);
+void dbPrint(data_buffer_s *db, char *message, int withDiff);
+void dbPrintTable(data_buffer_s *table[], char *caption[], int columns);
+
+#endif /* DATA_BUFFER_H_ */
diff --git a/firmware/util/efilib.cpp b/firmware/util/efilib.cpp
new file mode 100644
index 0000000000..0c0e01e95d
--- /dev/null
+++ b/firmware/util/efilib.cpp
@@ -0,0 +1,160 @@
+/**
+ * @file efilib.c
+ *
+ * We cannot use stdlib because we do not have malloc - so, we have to implement these functions
+ *
+ * @date Feb 21, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include
+#include
+#include "efilib.h"
+
+#define _MAX_FILLER 11
+
+/**
+ * there is some BS related to isnan in MinGW, so let's have all the issues in one place
+ */
+bool cisnan(float f) {
+ return *(((int*) (&f))) == 0x7FC00000;
+}
+
+int minI(int i1, int i2) {
+ return i1 < i2 ? i1 : i2;
+}
+
+float efiRound(float value, float precision) {
+ int a = (int)(value / precision);
+ return a * precision;
+}
+
+float absF(float value) {
+ return value > 0 ? value : -value;
+}
+
+int absI(int32_t value) {
+ return value >= 0 ? value : -value;
+}
+
+int maxI(int i1, int i2) {
+ return i1 > i2 ? i1 : i2;
+}
+
+float maxF(float i1, float i2) {
+ return i1 > i2 ? i1 : i2;
+}
+
+int indexOf(const char *string, char ch) {
+ // todo: there should be a standard function for this
+ int len = strlen(string);
+ for (int i = 0; i < len; i++) {
+ if (string[i] == ch) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+// string to integer
+int atoi(const char *string) {
+ // todo: use stdlib '#include '
+ int len = strlen(string);
+ if (len == 0) {
+ return -ERROR_CODE;
+ }
+ if (string[0] == '-') {
+ return -atoi(string + 1);
+ }
+ int result = 0;
+
+ for (int i = 0; i < len; i++) {
+ char ch = string[i];
+ if (ch < '0' || ch > '9') {
+ return ERROR_CODE;
+ }
+ int c = ch - '0';
+ result = result * 10 + c;
+ }
+
+ return result;
+}
+
+static char todofixthismesswithcopy[100];
+
+static char *ltoa_internal(char *p, long num, unsigned radix) {
+ int i;
+ char *q;
+
+ q = p + _MAX_FILLER;
+ do {
+ i = (int) (num % radix);
+ i += '0';
+ if (i > '9')
+ i += 'A' - '0' - 10;
+ *--q = i;
+ } while ((num /= radix) != 0);
+
+ i = (int) (p + _MAX_FILLER - q);
+ do {
+ *p++ = *q++;
+ } while (--i);
+
+ return p;
+}
+
+static char* itoa_signed(char *p, int num, unsigned radix) {
+ if (num < 0) {
+ *p++ = '-';
+ char *end = ltoa_internal(p, -num, radix);
+ *end = 0;
+ return end;
+ }
+ char *end = ltoa_internal(p, num, radix);
+ *end = 0;
+ return end;
+}
+
+/**
+ * Integer to string
+ */
+char* itoa10(char *p, int num) {
+// todo: unit test
+ return itoa_signed(p, num, 10);
+}
+
+#define EPS 0.0001
+
+bool isSameF(float v1, float v2) {
+ return absF(v1 - v2) < EPS;
+}
+
+// string to float
+float atoff(const char *param) {
+ uint32_t totallen = strlen(param);
+ if (totallen > sizeof(todofixthismesswithcopy) - 1)
+ return (float) NAN;
+ strcpy(todofixthismesswithcopy, param);
+ char *string = todofixthismesswithcopy;
+
+ // todo: is there a standard function?
+ // todo: create a unit test
+ int dotIndex = indexOf(string, '.');
+ if (dotIndex == -1) {
+ // just an integer
+ int result = atoi(string);
+ return (float) result;
+ }
+ // todo: this needs to be fixed
+ string[dotIndex] = 0;
+ int integerPart = atoi(string);
+ string += (dotIndex + 1);
+ int decimalLen = strlen(string);
+ int decimal = atoi(string);
+ float divider = 1.0;
+ // todo: reuse 'pow10' function which we have anyway
+ for (int i = 0; i < decimalLen; i++) {
+ divider = divider * 10.0;
+ }
+ return integerPart + decimal / divider;
+}
diff --git a/firmware/util/efilib.h b/firmware/util/efilib.h
new file mode 100644
index 0000000000..c5fd2204e0
--- /dev/null
+++ b/firmware/util/efilib.h
@@ -0,0 +1,52 @@
+/**
+ * @file efilib.h
+ *
+ * @date Feb 21, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef EFILIB_H_
+#define EFILIB_H_
+
+#include
+#include "global.h"
+
+// number of milliseconds in one period of given frequency (per second)
+#define frequency2periodMs(freq) ((1000.0f) / (freq))
+
+// number of microseconds in one period of given frequency (per second)
+#define frequency2periodUs(freq) ((1000000.0f) / (freq))
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+#ifndef TRUE
+#define TRUE (!FALSE)
+#endif
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+#define ERROR_CODE -11223344
+
+int indexOf(const char *string, char ch);
+float atoff(const char *string);
+int atoi(const char *string);
+bool cisnan(float f);
+
+int absI(int32_t value);
+float absF(float value);
+float efiRound(float value, float precision);
+int maxI(int i1, int i2);
+int minI(int i1, int i2);
+float maxF(float i1, float i2);
+char* itoa10(char *p, int num);
+bool isSameF(float v1, float v2);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* EFILIB_H_ */
diff --git a/firmware/util/efilib2.cpp b/firmware/util/efilib2.cpp
new file mode 100644
index 0000000000..2145009c0b
--- /dev/null
+++ b/firmware/util/efilib2.cpp
@@ -0,0 +1,41 @@
+/**
+ * @file efilib2.cpp
+ *
+ * @date Apr 14, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "efilib2.h"
+
+/**
+ * The main use-case of this class is to keep track of a 64-bit global number of CPU ticks from reset.
+ *
+ * stm32f4 hardware has a 32-bit Cycle Count Register (CYCCNT), which is incremented with every CPU cycle.
+ * With 32 bits and 168MHz speed this counter overflows every 4B/168M = 23 seconds. The job of this class is to
+ * keep track of the current CYCCNT value, detect these overflows, and provide a nice,
+ * clean 64 bit global cycle counter.
+ *
+ * In order for this to function, it's your responsibility to invoke offer() method at least once a second.
+ */
+Overflow64Counter::Overflow64Counter() {
+ currentBase = 0;
+ currentValue = 0;
+}
+
+uint64_t Overflow64Counter::get(uint32_t value, int isPrimaryThread) {
+ // this method is lock-free, only one thread is allowed to commit state
+ // these are local copies for thread-safery
+ // todo: this is still not atomic, so technically not thread safe.
+ uint32_t localValue = currentValue;
+ uint64_t localBase = currentBase;
+ if (value < localValue) {
+ // new value less than previous value means there was an overflow in that 32 bit counter
+ localBase += 0x100000000LL;
+ }
+ if (isPrimaryThread) {
+ currentValue = value;
+ currentBase = localBase;
+ }
+
+ return localBase + value;
+}
diff --git a/firmware/util/efilib2.h b/firmware/util/efilib2.h
new file mode 100644
index 0000000000..e2e76b60ee
--- /dev/null
+++ b/firmware/util/efilib2.h
@@ -0,0 +1,27 @@
+/**
+ * @file efilib2.h
+ *
+ * @date Apr 14, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef EFILIB2_H_
+#define EFILIB2_H_
+
+#include
+
+
+class Overflow64Counter
+{
+ public:
+ Overflow64Counter();
+
+ uint64_t get(uint32_t value, int isPrimaryThread);
+
+ private:
+ uint64_t currentBase;
+ uint32_t currentValue;
+};
+
+
+#endif /* EFILIB2_H_ */
diff --git a/firmware/util/histogram.c b/firmware/util/histogram.c
new file mode 100644
index 0000000000..b4fa5d76bf
--- /dev/null
+++ b/firmware/util/histogram.c
@@ -0,0 +1,163 @@
+/**
+ * @file histogram.c
+ * @brief This data structure is used to analyze CPU performance
+ *
+ * Histogram is a data structure which simplifies CPU performance monitoring and trobleshooting by tracking the min, max
+ * and a couple of median values for a series of measurments.
+ *
+ * @date Dec 18, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include
+#include "histogram.h"
+#include "main.h"
+
+#if EFI_HISTOGRAMS
+
+#define H_ACCURACY 0.05
+#define H_CONFIDENCE 0.8
+#define LONG_MAX_INT 0x7fffffffffffffffL
+#define SBI_SIZE 1000
+
+static float confidence_bounds[] = { 0.5 - H_CONFIDENCE * 0.5, 0.5, 0.5 + H_CONFIDENCE * 0.5 };
+
+/**
+ * magic curve lookup table
+ */
+static int64_t bounds[BOUND_LENGTH];
+/**
+ * just an optimization - faster lookup for small values
+ */
+static int small_bounds_index[SBI_SIZE];
+
+static int initialized = FALSE;
+
+/**
+ * @breif Internal histogram data structure
+ */
+void initHistogramsModule(void) {
+ bounds[0] = 0;
+ for (int i = 1; i < BOUND_LENGTH; i++) {
+ int64_t prev = bounds[i - 1];
+ int64_t next = prev + (int64_t) ((double) prev * H_ACCURACY);
+ if (next == prev) // Ensure minimum step for small numbers.
+ next = prev + 1;
+ if (next < prev) // Overflow over Long.MAX_VALUE occurred.
+ next = LONG_MAX_INT;
+ bounds[i] = next;
+ }
+ bounds[BOUND_LENGTH - 1] = LONG_MAX_INT;
+
+ for (int i = 0, j = 0; j < SBI_SIZE; i++)
+ while (j < bounds[i + 1] && j < SBI_SIZE)
+ small_bounds_index[j++] = i;
+ initialized = TRUE;
+}
+
+/**
+ * @brief This internal method is only public so that we can test it.
+ */
+int histogramGetIndex(int64_t value) {
+ efiAssert(initialized, "histo initialized", 0);
+ if (value < 0)
+ return 0;
+ if (value < SBI_SIZE)
+ return small_bounds_index[(int) value];
+ int l = small_bounds_index[SBI_SIZE - 1];
+ int r = BOUND_LENGTH - 1;
+ while (l < r) {
+ int m = (l + r) >> 1;
+ if (bounds[m] > value)
+ r = m - 1;
+ else if (bounds[m + 1] <= value)
+ l = m + 1;
+ else
+ return m;
+ }
+ return l;
+}
+
+/**
+ * @brief Reset histogram_s to orignal state
+ */
+void initHistogram(histogram_s *h, const char *name) {
+ if(strlen(name) > sizeof(h->name) - 1) {
+ firmwareError("Histogram name [%s] too long", name);
+ }
+ strcpy(h->name, name);
+ h->total_value = 0;
+ h->total_count = 0;
+ memset(h->values, 0, sizeof(h->values));
+}
+
+/**
+ * @breif Add a new value into histogram_s
+ */
+void hsAdd(histogram_s *h, int64_t value) {
+ int index = histogramGetIndex(value);
+ int count = 1;
+ h->total_value += value;
+ h->total_count += count;
+ efiAssertVoid(index < BOUND_LENGTH, "histogram issue" );
+
+ h->values[index] += count;
+}
+
+/**
+ * @brief Prepare histogram report
+ * @note This report should be displayed using 'printHistogram' method
+ */
+int hsReport(histogram_s *h, int* report) {
+ int index = 0;
+
+ if (h->total_count <= 5) {
+ for (int j = 0; j < BOUND_LENGTH; j++) {
+ for (int k = 0; k < h->values[j]; k++) {
+ report[index++] = (bounds[j] + bounds[j + 1]) / 2;
+ }
+ }
+ return index;
+ }
+
+ int minIndex = 0;
+ while (h->values[minIndex] == 0) {
+ minIndex++;
+ }
+ report[index++] = h->values[minIndex];
+
+ int64_t acc = 0;
+ // 'acc' is accumulated number of samples in [0, min - 1].
+ for (int j = 0; j < 3; j++) {
+ int64_t k = confidence_bounds[j] * h->total_count;
+ // Always drop at least 1 'non-confident' sample...
+ if (k == 0) {
+ k = 1;
+ }
+ if (k == h->total_count) {
+ k = h->total_count - 1;
+ }
+ // 'k' is desired number of samples.
+ while (acc + h->values[minIndex] < k)
+ acc += h->values[minIndex++];
+ if (k < h->total_count / 2) // Converge to median (from left).
+ while (acc + h->values[minIndex] <= k)
+ acc += h->values[minIndex++];
+ // Now: acc <= k <= acc + st.histogram[min]
+ // And desired number of samples is within [min, min + 1)
+ float d = bounds[minIndex];
+ if (acc != k)
+ d += (bounds[minIndex + 1] - 1 - bounds[minIndex]) * (k - acc) / h->values[minIndex];
+ report[index++] = (int)d;
+ }
+
+ int maxIndex = BOUND_LENGTH - 1;
+ while (h->values[maxIndex] == 0)
+ maxIndex--;
+ int64_t maxValue = bounds[maxIndex + 1] - 1;
+ report[index++] = maxValue;
+
+ return index;
+}
+
+#endif /* EFI_HISTOGRAMS */
diff --git a/firmware/util/histogram.h b/firmware/util/histogram.h
new file mode 100644
index 0000000000..725150b950
--- /dev/null
+++ b/firmware/util/histogram.h
@@ -0,0 +1,49 @@
+/**
+ * @file histogram.h
+ * @brief This data structure is used to analyze CPU performance
+ *
+ * User can report data into a histogram and later get an aggregated represenation, i.e.
+ * a histogram, of these values.
+ *
+ * A typicsl use-case would be reporting the times it took to execure a particular section of
+ * code - and later analyzing the histogram.
+ *
+ *
+ * @date Dec 18, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef HISTOGRAM_H_
+#define HISTOGRAM_H_
+
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+
+
+#include
+
+#define BOUND_LENGTH 895
+
+typedef struct {
+ char name[16];
+ int64_t total_value;
+ int64_t total_count;
+ int values[BOUND_LENGTH];
+} histogram_s;
+
+void initHistogramsModule(void);
+int histogramGetIndex(int64_t value);
+void initHistogram(histogram_s *h, const char *name);
+void hsAdd(histogram_s *h, int64_t value);
+int hsReport(histogram_s *h, int* report);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+
+#endif /* HISTOGRAM_H_ */
diff --git a/firmware/util/listener_array.c b/firmware/util/listener_array.c
new file mode 100644
index 0000000000..663b819a52
--- /dev/null
+++ b/firmware/util/listener_array.c
@@ -0,0 +1,55 @@
+/*
+ * listener_array.c
+ *
+ * @date Jan 1, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "listener_array.h"
+#include "main.h"
+
+void registerCallback(IntListenerArray *array, IntListener handler, void *arg) {
+ efiAssertVoid(array->currentListenersCount < MAX_INT_LISTENER_COUNT, "Too many callbacks");
+ int index = array->currentListenersCount++;
+ array->callbacks[index] = handler;
+ array->args[index] = arg;
+}
+
+void invokeCallbacks(IntListenerArray *array, int value) {
+ for (int i = 0; i < array->currentListenersCount; i++)
+ (array->callbacks[i])(value);
+}
+
+void invokeJustArgCallbacks(IntListenerArray *array) {
+ for (int i = 0; i < array->currentListenersCount; i++) {
+ ArgListener listener = (ArgListener)array->callbacks[i];
+ void *arg = array->args[i];
+ (listener)(arg);
+ }
+}
+
+void invokeArgIntCallbacks(IntListenerArray *array, int value) {
+ for (int i = 0; i < array->currentListenersCount; i++) {
+ ArgIntListener listener = (ArgIntListener)array->callbacks[i];
+ void *arg = array->args[i];
+ (listener)(arg, value);
+ }
+}
+
+void invokeIntIntCallbacks(IntListenerArray *array, int value, int value2) {
+ for (int i = 0; i < array->currentListenersCount; i++) {
+ IntIntListener listener = (IntIntListener)array->callbacks[i];
+ (listener)(value, value2);
+ }
+}
+
+void invokeIntIntVoidCallbacks(IntListenerArray *array, int value, int value2) {
+ for (int i = 0; i < array->currentListenersCount; i++) {
+ IntIntVoidListener listener = (IntIntVoidListener)array->callbacks[i];
+ (listener)(value, value2, array->args[i]);
+ }
+}
+
+void clearCallbacks(IntListenerArray *array) {
+ array->currentListenersCount = 0;
+}
diff --git a/firmware/util/listener_array.h b/firmware/util/listener_array.h
new file mode 100644
index 0000000000..23dbfb107a
--- /dev/null
+++ b/firmware/util/listener_array.h
@@ -0,0 +1,45 @@
+/*
+ * listener_array.h
+ *
+ * @date Jan 1, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef LISTENER_ARRAY_H_
+#define LISTENER_ARRAY_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+#define MAX_INT_LISTENER_COUNT 15
+
+typedef void (*IntListener)(int value);
+typedef void (*IntIntListener)(int value1, int value2);
+// todo: reorder parameters for consistency?
+typedef void (*IntIntVoidListener)(int value1, int value2, void *arg);
+
+// todo: rename 'Arg' to 'Void'?
+typedef void (*ArgListener)(void *arg);
+typedef void (*ArgIntListener)(void *arg, int value);
+
+typedef struct {
+ int currentListenersCount;
+ IntListener callbacks[MAX_INT_LISTENER_COUNT];
+ void * args[MAX_INT_LISTENER_COUNT];
+} IntListenerArray;
+
+void registerCallback(IntListenerArray *array, IntListener handler, void *arg);
+void invokeCallbacks(IntListenerArray *array, int value);
+void invokeJustArgCallbacks(IntListenerArray *array);
+void invokeArgIntCallbacks(IntListenerArray *array, int value);
+void invokeIntIntCallbacks(IntListenerArray *array, int value, int value2);
+void invokeIntIntVoidCallbacks(IntListenerArray *array, int value, int value2);
+void clearCallbacks(IntListenerArray *array);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* LISTENER_ARRAY_H_ */
diff --git a/firmware/util/readme.txt b/firmware/util/readme.txt
new file mode 100644
index 0000000000..6588b85056
--- /dev/null
+++ b/firmware/util/readme.txt
@@ -0,0 +1,3 @@
+In this folder we have all the tiny utility stuff.
+
+Ideally, code from this fould should not depend on code from any other folder.
\ No newline at end of file
diff --git a/firmware/util/util.mk b/firmware/util/util.mk
new file mode 100644
index 0000000000..ca11372772
--- /dev/null
+++ b/firmware/util/util.mk
@@ -0,0 +1,11 @@
+
+UTILSRC = $(PROJECT_DIR)/util/data_buffer.c \
+ $(PROJECT_DIR)/util/listener_array.c \
+ $(PROJECT_DIR)/util/crc.c \
+ $(PROJECT_DIR)/util/histogram.c \
+ $(PROJECT_DIR)/util/cli_registry.c
+
+UTILSRC_CPP = $(PROJECT_DIR)/util/cyclic_buffer.cpp \
+ $(PROJECT_DIR)/util/efilib.cpp \
+ $(PROJECT_DIR)/util/efilib2.cpp \
+ $(PROJECT_DIR)/util/LocalVersionHolder.cpp
diff --git a/unit_tests/.cproject b/unit_tests/.cproject
new file mode 100644
index 0000000000..379751c48a
--- /dev/null
+++ b/unit_tests/.cproject
@@ -0,0 +1,160 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/unit_tests/.project b/unit_tests/.project
new file mode 100644
index 0000000000..3238f268dc
--- /dev/null
+++ b/unit_tests/.project
@@ -0,0 +1,27 @@
+
+
+ unit_tests
+
+
+
+
+
+ org.eclipse.cdt.managedbuilder.core.genmakebuilder
+ clean,full,incremental,
+
+
+
+
+ org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder
+ full,incremental,
+
+
+
+
+
+ org.eclipse.cdt.core.cnature
+ org.eclipse.cdt.core.ccnature
+ org.eclipse.cdt.managedbuilder.core.managedBuildNature
+ org.eclipse.cdt.managedbuilder.core.ScannerConfigNature
+
+
diff --git a/unit_tests/Makefile b/unit_tests/Makefile
new file mode 100644
index 0000000000..d3ef9eff8f
--- /dev/null
+++ b/unit_tests/Makefile
@@ -0,0 +1,236 @@
+##############################################################################
+# Build global options
+# NOTE: Can be overridden externally.
+#
+
+PROJECT_DIR = ../firmware
+#CHIBIOS = $(PROJECT_DIR)/chibios
+
+# Compiler options here.
+ifeq ($(USE_OPT),)
+# -O2 is needed for mingw, without it there is a linking issue to isnanf?!?!
+ #USE_OPT = $(RFLAGS) -O2 -fgnu89-inline -ggdb -fomit-frame-pointer -falign-functions=16 -std=gnu99 -Werror-implicit-function-declaration -Werror -Wno-error=pointer-sign -Wno-error=unused-function -Wno-error=unused-variable -Wno-error=sign-compare -Wno-error=unused-parameter -Wno-error=missing-field-initializers
+ USE_OPT = -c -Wall -O0 -ggdb -g3
+endif
+
+# C specific options here (added to USE_OPT).
+ifeq ($(USE_COPT),)
+ USE_COPT = -std=gnu99 -fgnu89-inline
+endif
+
+# C++ specific options here (added to USE_OPT).
+ifeq ($(USE_CPPOPT),)
+ USE_CPPOPT = -fno-rtti -fpermissive -fno-exceptions -fno-use-cxa-atexit
+endif
+
+# Enable this if you want the linker to remove unused code and data
+ifeq ($(USE_LINK_GC),)
+ USE_LINK_GC = yes
+endif
+
+# If enabled, this option allows to compile the application in THUMB mode.
+ifeq ($(USE_THUMB),)
+ USE_THUMB = no
+endif
+
+# Enable this if you want to see the full log while compiling.
+ifeq ($(USE_VERBOSE_COMPILE),)
+ USE_VERBOSE_COMPILE = no
+endif
+
+#
+# Build global options
+##############################################################################
+
+##############################################################################
+# Architecture or project specific options
+#
+
+
+# List all default C defines here, like -D_DEBUG=1
+DDEFS =
+
+#
+# Architecture or project specific options
+##############################################################################
+
+##############################################################################
+# Project, sources and paths
+#
+
+# Define project name here
+PROJECT = rusefi_test
+
+#PROJECT_BOARD = OLIMEX_STM32_E407
+#ifneq ($(PROJECT_BOARD),OLIMEX_STM32_E407)
+# PROJECT_BOARD = ST_STM32F4_DISCOVERY
+#endif
+#DDEFS += -D$(PROJECT_BOARD)
+
+# Imported source files and paths
+include $(PROJECT_DIR)/util/util.mk
+include $(PROJECT_DIR)/config/engines/engines.mk
+include $(PROJECT_DIR)/controllers/algo/algo.mk
+include $(PROJECT_DIR)/controllers/core/core.mk
+include $(PROJECT_DIR)/controllers/math/math.mk
+include $(PROJECT_DIR)/controllers/system/system.mk
+include $(PROJECT_DIR)/controllers/sensors/sensors.mk
+include $(PROJECT_DIR)/controllers/trigger/trigger.mk
+include test.mk
+
+# Define linker script file here
+#LDSCRIPT= config/system/STM32F407xG.ld
+#LDSCRIPT= $(PORTLD)/STM32F407xG_CCM.ld
+
+# C sources that can be compiled in ARM or THUMB mode depending on the global
+# setting.
+CSRC = $(UTILSRC) \
+ $(CONTROLLERS_ALGO_SRC) \
+ $(CONTROLLERS_CORE_SRC) \
+ $(CONTROLLERS_MATH_SRC) \
+ $(CONTROLLERS_SENSORS_SRC) \
+ $(ENGINES_SRC) \
+ $(TEST_SRC_C)
+
+# C++ sources that can be compiled in ARM or THUMB mode depending on the global
+# setting.
+CPPSRC = $(UTILSRC_CPP) \
+ $(CONTROLLERS_ALGO_SRC_CPP) \
+ $(TRIGGER_DECODERS_SRC_CPP) \
+ $(ENGINES_SRC_CPP) \
+ $(CONTROLLERS_CORE_SRC_CPP) \
+ $(CONTROLLERS_MATH_SRC_CPP) \
+ $(CONTROLLERS_SENSORS_SRC_CPP) \
+ $(TEST_SRC_CPP) \
+ $(PROJECT_DIR)/controllers/trigger/trigger_central.cpp \
+ $(PROJECT_DIR)/controllers/trigger/rpm_calculator.cpp \
+ $(PROJECT_DIR)/controllers/trigger/main_trigger_callback.cpp \
+ $(PROJECT_DIR)/controllers/system/event_queue.cpp \
+ main.cpp
+
+# C sources to be compiled in ARM mode regardless of the global setting.
+# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
+# option that results in lower performance and larger code size.
+ACSRC =
+
+# C++ sources to be compiled in ARM mode regardless of the global setting.
+# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
+# option that results in lower performance and larger code size.
+ACPPSRC =
+
+# C sources to be compiled in THUMB mode regardless of the global setting.
+# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
+# option that results in lower performance and larger code size.
+TCSRC =
+
+# C sources to be compiled in THUMB mode regardless of the global setting.
+# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
+# option that results in lower performance and larger code size.
+TCPPSRC =
+
+# List ASM source files here
+ASMSRC = $(PORTASM)
+
+INCDIR = . \
+ $(PROJECT_DIR)/util \
+ $(PROJECT_DIR)/config/engines \
+ $(PROJECT_DIR)/controllers/sensors \
+ $(PROJECT_DIR)/controllers/algo \
+ $(PROJECT_DIR)/controllers/core \
+ $(PROJECT_DIR)/controllers/math \
+ $(PROJECT_DIR)/controllers/system \
+ $(PROJECT_DIR)/controllers/trigger \
+ $(PROJECT_DIR)/ext_algo \
+ test_data_structures \
+ test_basic_math
+
+
+#
+# Project, sources and paths
+##############################################################################
+
+##############################################################################
+# Compiler settings
+#
+
+#MCU = cortex-m4
+
+ifeq ($(OS),Windows_NT)
+ TRGT = i686-pc-mingw32-
+else
+ TRGT =
+endif
+
+CC = $(TRGT)gcc
+CPPC = $(TRGT)g++
+# Enable loading with g++ only if you need C++ runtime support.
+# NOTE: You can use C++ even without C++ support if you are careful. C++
+# runtime support makes code size explode.
+#LD = $(TRGT)gcc
+LD = $(TRGT)g++
+CP = $(TRGT)objcopy
+AS = $(TRGT)gcc -x assembler-with-cpp
+OD = $(TRGT)objdump
+HEX = $(CP) -O ihex
+BIN = $(CP) -O binary
+
+# ARM-specific options here
+AOPT =
+
+# THUMB-specific options here
+TOPT = -mthumb -DTHUMB
+
+# Define C warning options here
+CWARN = -Wall -Wextra -Wstrict-prototypes
+
+# Define C++ warning options here
+CPPWARN = -Wall -Wextra
+
+#
+# Compiler settings
+##############################################################################
+
+##############################################################################
+# Start of default section
+#
+
+# List all default ASM defines here, like -D_DEBUG=1
+DADEFS =
+
+# List all default directories to look for include files here
+DINCDIR =
+
+# List the default directory to look for the libraries here
+DLIBDIR =
+
+# List all default libraries here
+DLIBS = -static-libgcc -static-libstdc++
+
+#
+# End of default section
+##############################################################################
+
+##############################################################################
+# Start of user section
+#
+
+# List all user C define here, like -D_DEBUG=1
+UDEFS =
+
+# Define ASM defines here
+UADEFS =
+
+# List all user directories here
+UINCDIR =
+
+# List the user directory to look for the libraries here
+ULIBDIR =
+
+# List all user libraries here
+ULIBS = -lm
+
+#
+# End of user defines
+##############################################################################
+
+include rules.mk
diff --git a/unit_tests/adc_inputs.h b/unit_tests/adc_inputs.h
new file mode 100644
index 0000000000..5c526173a7
--- /dev/null
+++ b/unit_tests/adc_inputs.h
@@ -0,0 +1,11 @@
+/**
+ * @file adc_inputs.h
+ *
+ * @date Dec 7, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#ifndef ADC_INPUTS_H_
+#define ADC_INPUTS_H_
+
+#endif /* ADC_INPUTS_H_ */
diff --git a/unit_tests/boards.c b/unit_tests/boards.c
new file mode 100644
index 0000000000..35bced13f1
--- /dev/null
+++ b/unit_tests/boards.c
@@ -0,0 +1,28 @@
+/**
+ * @file board.c
+ *
+ * @date Nov 15, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#include "boards.h"
+
+float testMafValue = 0;
+
+float getVoltageDivided(int channel) {
+ switch(channel) {
+ case TEST_MAF_CHANNEL:
+ return testMafValue;
+ }
+ return 0;
+}
+
+float getVoltage(int channel) {
+ return 0;
+}
+
+int getAdcValue(int channel) {
+ return 0;
+}
+
+
diff --git a/unit_tests/boards.h b/unit_tests/boards.h
new file mode 100644
index 0000000000..25c9f99263
--- /dev/null
+++ b/unit_tests/boards.h
@@ -0,0 +1,19 @@
+/*
+ * boards.h
+ *
+ * Created on: Nov 15, 2013
+ * Author: Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#ifndef BOARDS_H_
+#define BOARDS_H_
+
+#define ADC_CHANNEL_VREF 0
+
+#define TEST_MAF_CHANNEL 10000013
+
+float getVoltageDivided(int);
+float getVoltage(int channel);
+int getAdcValue(int channel);
+
+#endif /* BOARDS_H_ */
diff --git a/unit_tests/compile.bat b/unit_tests/compile.bat
new file mode 100644
index 0000000000..1c9d2c2e35
--- /dev/null
+++ b/unit_tests/compile.bat
@@ -0,0 +1,3 @@
+rm -rf .dep/
+rm -rf build/
+make
diff --git a/unit_tests/efifeatures.h b/unit_tests/efifeatures.h
new file mode 100644
index 0000000000..810cff8dc0
--- /dev/null
+++ b/unit_tests/efifeatures.h
@@ -0,0 +1,26 @@
+/**
+ * @file efifeatures.h
+ *
+ * Created on: Mar 7, 2014
+ * Author: Andrey
+ */
+
+#ifndef EFIFEATURES_H_
+#define EFIFEATURES_H_
+
+#define EFI_HISTOGRAMS TRUE
+
+#define EFI_CLI_SUPPORT FALSE
+
+#define EFI_SUPPORT_FORD_ASPIRE TRUE
+#define EFI_SUPPORT_DODGE_NEON TRUE
+#define EFI_SUPPORT_1995_FORD_INLINE_6 TRUE
+#define EFI_SUPPORT_FORD_FIESTA TRUE
+#define EFI_SUPPORT_NISSAN_PRIMERA TRUE
+
+#define EFI_SIGNAL_EXECUTOR_ONE_TIMER TRUE
+
+#define EFI_SHAFT_POSITION_INPUT TRUE
+#define EFI_ENGINE_CONTROL TRUE
+
+#endif /* EFIFEATURES_H_ */
diff --git a/unit_tests/engine_test_helper.cpp b/unit_tests/engine_test_helper.cpp
new file mode 100644
index 0000000000..1cbae0c28d
--- /dev/null
+++ b/unit_tests/engine_test_helper.cpp
@@ -0,0 +1,45 @@
+/**
+ * @file engine_test_helper.cpp
+ *
+ * @date Jun 26, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "engine_test_helper.h"
+#include "stddef.h"
+#include "trigger_decoder.h"
+#include "speed_density.h"
+
+extern int timeNow;
+
+EngineTestHelper::EngineTestHelper(engine_type_e engineType) {
+ ec = &persistentConfig.engineConfiguration;
+
+ engine.engineConfiguration = ec;
+
+ configuration.engineConfiguration = ec;
+ configuration.engineConfiguration2 = &ec2;
+
+
+ initSpeedDensity(ec);
+
+ resetConfigurationExt(NULL, FORD_INLINE_6_1995, ec, &ec2, &persistentConfig.engineConfiguration.bc);
+ ec->mafAdcChannel = (adc_channel_e)TEST_MAF_CHANNEL;
+}
+
+void EngineTestHelper::fireTriggerEvents() {
+ for (int i = 0; i < 24; i++) {
+ timeNow += 5000; // 5ms
+ triggerCentral.handleShaftSignal(&configuration, SHAFT_PRIMARY_UP, timeNow);
+ timeNow += 5000;
+ triggerCentral.handleShaftSignal(&configuration, SHAFT_PRIMARY_DOWN, timeNow);
+ }
+}
+
+void EngineTestHelper::initTriggerShapeAndRpmCalculator() {
+ initializeTriggerShape(NULL, ec, &ec2);
+ incrementGlobalConfigurationVersion();
+
+ triggerCentral.addEventListener((ShaftPositionListener) &rpmShaftPositionCallback, "rpm reporter", &rpmState);
+
+}
diff --git a/unit_tests/engine_test_helper.h b/unit_tests/engine_test_helper.h
new file mode 100644
index 0000000000..a3c7aff8d8
--- /dev/null
+++ b/unit_tests/engine_test_helper.h
@@ -0,0 +1,33 @@
+/**
+ * @file engine_test_helper.h
+ *
+ * @date Jun 26, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+#ifndef ENGINE_TEST_HELPER_H_
+#define ENGINE_TEST_HELPER_H_
+
+#include "engine_configuration.h"
+#include "ec2.h"
+#include "trigger_central.h"
+#include "rpm_calculator.h"
+
+class EngineTestHelper {
+public:
+ EngineTestHelper(engine_type_e engineType);
+ void initTriggerShapeAndRpmCalculator();
+ void fireTriggerEvents();
+
+ persistent_config_s persistentConfig;
+ engine_configuration2_s ec2;
+ configuration_s configuration;
+ Engine engine;
+
+ engine_configuration_s *ec;
+
+ TriggerCentral triggerCentral;
+ RpmCalculator rpmState;
+
+};
+
+#endif /* ENGINE_TEST_HELPER_H_ */
diff --git a/unit_tests/global.h b/unit_tests/global.h
new file mode 100644
index 0000000000..a4bdfba69e
--- /dev/null
+++ b/unit_tests/global.h
@@ -0,0 +1,27 @@
+/*
+ * @file global.h
+ *
+ * @date Nov 28, 2013
+ * @author pc
+ */
+
+#ifndef GLOBAL_H_
+#define GLOBAL_H_
+
+#include
+#include
+#include
+
+#include "efitime.h"
+
+typedef int bool_t;
+
+typedef void * Logging;
+
+#define EFI_ERROR_CODE 0xffffffff
+#define TRUE 1
+#define FALSE 0
+
+#define CCM_OPTIONAL
+
+#endif /* GLOBAL_H_ */
diff --git a/unit_tests/jenkins.sh b/unit_tests/jenkins.sh
new file mode 100644
index 0000000000..67df86f044
--- /dev/null
+++ b/unit_tests/jenkins.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+ echo "The PATH is ${PATH}"
+ cd "${WORKSPACE}"
+ echo "start in workspace ${PWD}"
+
+ cd firmware
+ echo "CD to ${PWD}"
+
+ rm -fR .dep
+ rm -fR build
+ make
+
+ if [ ! -f build/rusefi.hex ]; then
+ echo "Firmware compilation failed"
+ exit -1
+ fi
+
+ cd "${WORKSPACE}/win32_algo_tests"
+ echo "CD to ${PWD}"
+
+ rm -fR .dep
+ rm -fR build
+ make
+ if [ ! -f build/rusefi_test ]; then
+ echo "test compilation failed"
+ exit -1
+ fi
+
+# we want to terminate if test fails
+set -e
+
+ # invoke the tests - hopefully error code would be propagated?
+ build/rusefi_test
+
+cd "${WORKSPACE}/java_console"
+echo "CD to ${PWD}"
+
+#JAVA_HOME=/usr/lib/jvm/java-6-openjdk-amd64
+#ant
+
diff --git a/unit_tests/main.cpp b/unit_tests/main.cpp
new file mode 100644
index 0000000000..a9cb823bb9
--- /dev/null
+++ b/unit_tests/main.cpp
@@ -0,0 +1,165 @@
+/**
+ * @file main.cpp
+ * @file First step towards unit-testing rusEfi algorithms
+ *
+ * @author Andrey Belomutskiy (c) 2012-2014
+ */
+
+#include
+#include
+#include
+#include
+
+#include "main.h"
+#include "error_handling.h"
+#include "ec2.h"
+#include "test_accel_enrichment.h"
+#include "test_interpolation_3d.h"
+#include "test_find_index.h"
+#include "test_sensors.h"
+#include "test_speed_density.h"
+
+#include "test_fuel_map.h"
+#include "fuel_math.h"
+
+extern "C"
+{
+#include "map_resize.h"
+#include "test_idle_controller.h"
+#include "test_event_registry.h"
+#include "test_signal_executor.h"
+#include "test_util.h"
+#include "engine_configuration.h"
+}
+
+#include "engine_math.h"
+#include "test_engine_math.h"
+#include "test_trigger_decoder.h"
+
+static engine_configuration_s ec;
+engine_configuration_s *engineConfiguration = &ec;
+
+int timeNow = 0;
+
+uint64_t getTimeNowUs(void) {
+ return timeNow;
+}
+
+void assertEqualsM(const char *msg, float expected, float actual) {
+ if (cisnan(actual) && !cisnan(expected)) {
+ printf("Unexpected: %s %.4f while expected %.4f\r\n", msg, actual, expected);
+ exit(-1);
+ }
+
+ float delta = absF(actual - expected);
+ if (delta > 0.0001) {
+ printf("delta: %.7f\r\n", delta);
+ printf("Unexpected: %s %.4f while expected %.4f\r\n", msg, actual, expected);
+ exit(-1);
+ }
+ printf("Validated %s: %f\r\n", msg, expected);
+}
+
+void assertEquals(float expected, float actual) {
+ assertEqualsM("", expected, actual);
+}
+
+void assertTrueM(const char *msg, float actual) {
+ assertEqualsM(msg, TRUE, actual);
+}
+
+void assertTrue(float actual) {
+ assertTrueM("", actual);
+}
+
+void assertFalseM(const char *msg, float actual) {
+ assertEqualsM(msg, FALSE, actual);
+}
+
+void assertFalse(float actual) {
+ assertFalseM("", actual);
+}
+
+void chDbgAssert(int c, char *msg, void *arg) {
+ if (!c) {
+ printf("assert failed: %s\r\n", msg);
+ exit(-1);
+ }
+}
+
+static engine_configuration2_s ec2;
+engine_configuration2_s *engineConfiguration2 = &ec2;
+
+int main(void) {
+ testOverflow64Counter();
+ testInterpolate3d();
+ testFindIndex();
+ testInterpolate2d();
+ testGpsParser();
+ prepareFuelMap();
+ testFuelMap();
+ testEngineMath();
+ testEventRegistry();
+ testSensors();
+ testCyclicBuffer();
+ testCrc();
+
+ testSignalExecutor();
+
+ testHistogram();
+
+ testTriggerDecoder();
+
+ testMalfunctionCentral();
+
+ testConsoleLogic();
+
+ testAngleResolver();
+
+ testPinHelper();
+ testSetTableValue();
+
+ testAccelEnrichment();
+
+ testSpeedDensity();
+
+ testFLStack();
+
+ // resizeMap();
+ printf("Success 20130829\r\n");
+ return EXIT_SUCCESS;
+}
+
+int warning(obd_code_e code, const char *fmt, ...) {
+ printf("Warning: %s\r\n", fmt);
+}
+
+bool hasFirmwareError(void) {
+ return false;
+}
+
+void firmwareError(const char *fmt, ...) {
+ printf(fmt);
+ exit(-1);
+}
+
+void print(const char *format, ...) {
+ va_list ap;
+ va_start(ap, format);
+ vprintf(format, ap);
+ va_end(ap);
+}
+
+void fatal3(char *msg, char *file, int line) {
+ printf(msg);
+ exit(-1);
+}
+
+int warning(const char *fmt, ...) {
+ printf(fmt);
+ exit(-1);
+}
+
+bool_t isCranking(void) {
+ return 0;
+}
diff --git a/unit_tests/main.h b/unit_tests/main.h
new file mode 100644
index 0000000000..ef508a1912
--- /dev/null
+++ b/unit_tests/main.h
@@ -0,0 +1,61 @@
+/**
+ * @file main.h
+ * @brief Test version of main.h
+ *
+ * Created on: Oct 17, 2013
+ * Author: Andrey Belomutskiy (C) 2012-2013
+ */
+
+#ifndef MAIN_H_
+#define MAIN_H_
+
+#include
+#include
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#define CH_FREQUENCY 1000
+
+#include "global.h"
+#include "error_handling.h"
+
+#include "efilib.h"
+#include "efitime.h"
+
+#include "boards.h"
+
+typedef int bool_t;
+
+void chDbgAssert(int c, char *msg, void *arg);
+
+void print(const char *fmt, ...);
+
+#define TICKS_IN_MS 100
+
+#define DEBUG_INTERPOLATION 1
+
+#define chDbgCheck(x, y) chDbgAssert(x, y, NULL)
+
+void assertEqualsM(const char *msg, float expected, float actual);
+void assertEquals(float expected, float actual);
+void assertTrue(float actual);
+void assertTrueM(const char *msg, float actual);
+void assertFalse(float actual);
+void assertFalseM(const char *msg, float actual);
+
+float getIntakeAirTemperature(void);
+float getCoolantTemperature(void);
+float getVBatt(void);
+float getMaf(void);
+
+#define systicks2ms(x) (0)
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif /* MAIN_H_ */
diff --git a/unit_tests/makelinks.bat b/unit_tests/makelinks.bat
new file mode 100644
index 0000000000..649ead916b
--- /dev/null
+++ b/unit_tests/makelinks.bat
@@ -0,0 +1,11 @@
+
+junction controllers_algo ..\firmware\controllers\algo
+junction controllers_core ..\firmware\controllers\core
+junction controllers_math ..\firmware\controllers\math
+junction controllers_system ..\firmware\controllers\system
+junction controllers_trigger ..\firmware\controllers\trigger
+junction engines ..\firmware\config\engines
+junction controllers_sensors ..\firmware\controllers\sensors
+junction util ..\firmware\util
+junction ext_algo ..\firmware\ext_algo
+
diff --git a/unit_tests/map_resize.cpp b/unit_tests/map_resize.cpp
new file mode 100644
index 0000000000..6addb2b876
--- /dev/null
+++ b/unit_tests/map_resize.cpp
@@ -0,0 +1,175 @@
+/**
+ * @file map_resize.c
+ *
+ * @date Jan 12, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#include
+#include "fuel_math.h"
+#include "interpolation.h"
+#include "engine_configuration.h"
+#include "main.h"
+#include "idle_controller.h"
+
+#if 1
+
+#define AD_LOAD_COUNT 16
+#define AD_RPM_COUNT 16
+
+
+static float ad_rpm_table[] = {/*0*/ 800.000000,
+ /*1*/ 1213.333374,
+ /*2*/ 1626.666748,
+ /*3*/ 2040.000000,
+ /*4*/ 2453.333496,
+ /*5*/ 2866.666748,
+ /*6*/ 3280.000000,
+ /*7*/ 3693.333496,
+ /*8*/ 4106.666992,
+ /*9*/ 4520.000000,
+ /*10*/ 4933.333496,
+ /*11*/ 5346.666992,
+ /*12*/ 5760.000000,
+ /*13*/ 6173.333496,
+ /*14*/ 6586.666992,
+ /*15*/ 7000.000000,
+ };;
+
+static float ad_maf_table[] =
+{/*0*/ 1.200000,
+/*1*/ 1.413333,
+/*2*/ 1.626667,
+/*3*/ 1.840000,
+/*4*/ 2.053333,
+/*5*/ 2.266667,
+/*6*/ 2.480000,
+/*7*/ 2.693333,
+/*8*/ 2.906667,
+/*9*/ 3.120000,
+/*10*/ 3.333333,
+/*11*/ 3.546667,
+/*12*/ 3.760000,
+/*13*/ 3.973333,
+/*14*/ 4.186667,
+/*15*/ 4.400000,
+};
+
+static float ad_table[AD_LOAD_COUNT][AD_RPM_COUNT] = {
+ /* RPM 800.000000 1213.333374 1626.666748 2040.000000 2453.333496 2866.666748 3280.000000 3693.333496 4106.666992 4520.000000 4933.333496 5346.666992 5760.000000 6173.333496 6586.666992 7000.000000*/
+ /* Load 1.200000 */{ 0.662000, -7.730000, -16.722000, -23.139999, -29.398001, -31.268000, -32.108002, -30.436001, -30.896000, -26.656000, -24.704000, -25.108000, -25.132000, -25.459999, -25.459999, -25.459999},
+ /* Load 1.413333 */{ 0.546000, -7.662000, -16.882000, -23.482000, -29.520000, -31.323999, -32.108002, -30.656000, -30.468000, -26.879999, -24.746000, -24.742001, -29.032000, -25.562000, -25.562000, -25.562000},
+ /* Load 1.626667 */{ 0.584000, -7.870000, -16.714001, -23.025999, -29.542000, -31.166000, -32.175999, -30.540001, -30.268000, -26.416000, -24.134001, -25.007999, -24.698000, -26.167999, -26.167999, -26.167999},
+ /* Load 1.840000 */{ 0.584000, -7.658000, -16.714001, -23.254000, -29.351999, -30.978001, -32.141998, -30.874001, -30.896000, -26.507999, -24.558001, -24.389999, -25.761999, -35.492001, -35.492001, -35.492001},
+ /* Load 2.053333 */{ 0.584000, -7.862000, -16.538000, -23.254000, -29.232000, -31.296000, -32.520000, -30.142000, -30.388000, -25.903999, -24.370001, -24.082001, -24.792000, -24.351999, -24.351999, -24.351999},
+ /* Load 2.266667 */{ -1.364000, -7.726000, -16.806000, -23.254000, -29.639999, -31.006001, -32.298000, -30.912001, -29.882000, -26.392000, -24.664000, -27.233999, -25.374001, -25.417999, -25.417999, -25.417999},
+ /* Load 2.480000 */{ 1.364000, -10.490000, -16.705999, -22.441999, -28.101999, -30.238001, -32.363998, -30.719999, -30.896000, -26.608000, -24.664000, -24.431999, -24.500000, -25.510000, -25.510000, -25.510000},
+ /* Load 2.693333 */{ 9.864000, -10.416000, -11.680000, -19.150000, -25.754000, -27.936001, -32.554001, -30.656000, -30.153999, -27.184000, -25.252001, -22.812000, -24.452000, -25.219999, -25.219999, -25.219999},
+ /* Load 2.906667 */{ 9.866000, 5.452000, 2.854000, -17.212000, -17.552000, -20.688000, -25.660000, -27.809999, -27.691999, -27.224001, -25.882000, -25.360001, -26.100000, -27.992001, -27.992001, -27.992001},
+ /* Load 3.120000 */{ 9.864000, 5.452000, 2.854000, -0.342000, -12.526000, -16.218000, -21.364000, -27.590000, -25.780001, -24.170000, -24.664000, -25.584000, -26.490000, -31.968000, -31.968000, -31.968000},
+ /* Load 3.333333 */{ 9.864000, 5.516000, 2.854000, -0.226000, -2.738000, -3.816000, -11.924000, -18.808001, -21.038000, -21.538000, -21.209999, -22.228001, -25.046000, -25.156000, -25.156000, -25.156000},
+ /* Load 3.546667 */{ 9.866000, 5.518000, 2.854000, 0.000000, -3.022000, -3.816000, -6.428000, -7.788000, -19.426001, -20.860001, -19.966000, -21.030001, -21.396000, -21.570000, -21.570000, -21.570000},
+ /* Load 3.760000 */{ 9.864000, 5.516000, 2.772000, -0.226000, -2.732000, -3.500000, -6.798000, -8.102000, -8.660000, -9.500000, -11.788000, -20.132000, -20.072001, -20.510000, -20.510000, -20.510000},
+ /* Load 3.973333 */{ 9.864000, 5.518000, 2.854000, 0.000000, -2.880000, -3.816000, -6.420000, -8.320000, -8.426000, -8.532000, -11.470000, -11.442000, -13.610000, -12.022000, -12.022000, -12.022000},
+ /* Load 4.186667 */{ 9.750000, 5.518000, 2.604000, 0.000000, -2.880000, -3.654000, -6.050000, -6.888000, -8.372000, -9.364000, -11.764000, -11.732000, -11.864000, -12.376000, -12.376000, -12.376000},
+ /* Load 4.400000 */{ 0.350000, 5.590000, 0.502000, 0.910000, 0.864000, 0.954000, 1.324000, -7.436000, 1.170000, 1.054000, 2.058000, 2.098000, 2.636000, -12.352000, -12.352000, -12.352000}
+ };
+
+//float getBaseAdvance(int rpm, float key) {
+// // todo: use interpolation
+// int rpm_index = findIndex(ad_rpm_table, AD_RPM_COUNT, rpm);
+// rpm_index = max(rpm_index, 0);
+// int maf_index = findIndex(ad_maf_table, AD_LOAD_COUNT, key);
+// maf_index = max(maf_index, 0);
+//
+// return ad_table[rpm_index][maf_index];
+//}
+
+
+
+#define newRpmSize 16
+#define newKeySize 16
+
+static float newRpmBin[newRpmSize];
+static float newKeyBin[newKeySize];
+
+//static float *fuel_ptrs[FUEL_LOAD_COUNT];
+
+//EngineConfiguration *engineConfiguration;
+
+extern int needInterpolationLogging;
+
+void resizeMap(void) {
+// float keyMin = 1.2;
+// float keyMax = 4.4;
+//
+// float rpmMin = 800;
+// float rpmMax = 7000;
+
+// for (int k = 0; k < FUEL_LOAD_COUNT; k++)
+// fuel_ptrs[k] = engineConfiguration->fuelTable[k];
+
+// for (int i = 0; i < FUEL_MAF_COUNT; i++)
+// engineConfiguration->fuelKeyBins[i] = default_fuel_maf_bins[i];
+// for (int i = 0; i < FUEL_RPM_COUNT; i++)
+// engineConfiguration->fuelRpmBins[i] = default_fuel_rpm_bins[i];
+// for (int k = 0; k < FUEL_MAF_COUNT; k++) {
+// for (int r = 0; r < FUEL_RPM_COUNT; r++) {
+// // todo: this is BAD, this needs to be fixed - TS table indexes are different from default indexes
+// engineConfiguration->fuelTable[k][r] = default_fuel_table[r][k];
+// }
+// }
+
+// assertEquals(15, interpolate3d(1.2, engineConfiguration->fuelKeyBins, FUEL_MAF_COUNT, 8000,
+// engineConfiguration->fuelRpmBins,
+// FUEL_RPM_COUNT, fuel_ptrs));
+
+ needInterpolationLogging = 0;
+
+// printf("static float ad_maf_table[AD_LOAD_COUNT] = {");
+// for (int i = 0; i < newKeySize; i++) {
+// newKeyBin[i] = interpolate(0, keyMin, newKeySize - 1, keyMax, i);
+// printf("/*%d*/ %f,\r\n", i, newKeyBin[i]);
+// }
+// printf("};\r\n");
+//
+// printf("static float ad_rpm_table[AD_RPM_COUNT] = {");
+// for (int i = 0; i < newRpmSize; i++) {
+// newRpmBin[i] = interpolate(0, rpmMin, newRpmSize - 1, rpmMax, i);
+// printf("/*%d*/ %f,\r\n", i, newRpmBin[i]);
+// }
+// printf("};\r\n");
+
+ printf("static float ad_table[AD_RPM_COUNT][AD_LOAD_COUNT] = {\r\n");
+
+ printf("/* RPM\t\t");
+ for (int r = 0; r < newRpmSize; r++) {
+ float rpm = newRpmBin[r];
+ printf("\t%f", rpm);
+ }
+ printf("*/\r\n");
+
+ for (int k = 0; k < newKeySize; k++) {
+ float load = newKeyBin[k];
+ printf("/* Load %f */{", load);
+
+ for (int r = 0; r < newRpmSize; r++) {
+ float rpm = newRpmBin[r];
+
+ float v = ad_table[k][r];
+
+ printf("\t%f", v);
+ if (r != newRpmSize - 1)
+ printf(",");
+
+ }
+ printf("}");
+ if (k != newKeySize - 1)
+ printf(",");
+ printf("\r\n");
+ }
+ printf("};\r\n");
+
+}
+
+#endif
diff --git a/unit_tests/map_resize.h b/unit_tests/map_resize.h
new file mode 100644
index 0000000000..3d105a434e
--- /dev/null
+++ b/unit_tests/map_resize.h
@@ -0,0 +1,13 @@
+/**
+ * @file map_resize.h
+ *
+ * @date Jan 12, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#ifndef MAP_RESIZE_H_
+#define MAP_RESIZE_H_
+
+void resizeMap(void);
+
+#endif /* MAP_RESIZE_H_ */
diff --git a/unit_tests/readme.txt b/unit_tests/readme.txt
new file mode 100644
index 0000000000..621959dacd
--- /dev/null
+++ b/unit_tests/readme.txt
@@ -0,0 +1,6 @@
+In this folder we have a naive test set. I am not sure what is the best approach to tests in C (someone can educate me), but my approach is:
+
+1) with a symlink linking some (only some, not all) folders of the firmware implementation, we can compile some files of the firmware as win32 code.
+Please execute the 'makelinks.bat' file to get the links. On XP you might need to execute 'junction.exe' before exuting the .bat file.
+
+2) we then compile & run the .exe, which is expected to say SUCCESS and not fail :)
\ No newline at end of file
diff --git a/unit_tests/rules.mk b/unit_tests/rules.mk
new file mode 100644
index 0000000000..5e93b3e311
--- /dev/null
+++ b/unit_tests/rules.mk
@@ -0,0 +1,168 @@
+# ARM Cortex-Mx common makefile scripts and rules.
+
+# Output directory and files
+ifeq ($(BUILDDIR),)
+ BUILDDIR = build
+endif
+ifeq ($(BUILDDIR),.)
+ BUILDDIR = build
+endif
+OUTFILES = $(BUILDDIR)/$(PROJECT)
+
+# Automatic compiler options
+OPT = $(USE_OPT)
+COPT = $(USE_COPT)
+CPPOPT = $(USE_CPPOPT)
+ifeq ($(USE_LINK_GC),yes)
+ OPT += -ffunction-sections -fdata-sections -fno-common
+endif
+
+# Source files groups and paths
+ifeq ($(USE_THUMB),yes)
+ TCSRC += $(CSRC)
+ TCPPSRC += $(CPPSRC)
+else
+ ACSRC += $(CSRC)
+ ACPPSRC += $(CPPSRC)
+endif
+ASRC = $(ACSRC)$(ACPPSRC)
+TSRC = $(TCSRC)$(TCPPSRC)
+SRCPATHS = $(sort $(dir $(ASMXSRC)) $(dir $(ASMSRC)) $(dir $(ASRC)) $(dir $(TSRC)))
+
+# Various directories
+OBJDIR = $(BUILDDIR)/obj
+LSTDIR = $(BUILDDIR)/lst
+
+# Object files groups
+ACOBJS = $(addprefix $(OBJDIR)/, $(notdir $(ACSRC:.c=.o)))
+ACPPOBJS = $(addprefix $(OBJDIR)/, $(notdir $(ACPPSRC:.cpp=.o)))
+TCOBJS = $(addprefix $(OBJDIR)/, $(notdir $(TCSRC:.c=.o)))
+TCPPOBJS = $(addprefix $(OBJDIR)/, $(notdir $(TCPPSRC:.cpp=.o)))
+ASMOBJS = $(addprefix $(OBJDIR)/, $(notdir $(ASMSRC:.s=.o)))
+ASMXOBJS = $(addprefix $(OBJDIR)/, $(notdir $(ASMXSRC:.S=.o)))
+OBJS = $(ASMXOBJS) $(ASMOBJS) $(ACOBJS) $(TCOBJS) $(ACPPOBJS) $(TCPPOBJS)
+
+# Paths
+IINCDIR = $(patsubst %,-I%,$(INCDIR) $(DINCDIR) $(UINCDIR))
+LLIBDIR = $(patsubst %,-L%,$(DLIBDIR) $(ULIBDIR))
+
+# Macros
+DEFS = $(DDEFS) $(UDEFS)
+ADEFS = $(DADEFS) $(UADEFS)
+
+# Libs
+LIBS = $(DLIBS) $(ULIBS)
+
+# Various settings
+#MCFLAGS = -mcpu=$(MCU)
+ODFLAGS = -x --syms
+ASFLAGS = $(MCFLAGS) -Wa,-amhls=$(LSTDIR)/$(notdir $(<:.s=.lst)) $(ADEFS)
+ASXFLAGS = $(MCFLAGS) -Wa,-amhls=$(LSTDIR)/$(notdir $(<:.S=.lst)) $(ADEFS)
+CFLAGS = $(MCFLAGS) $(OPT) $(COPT) $(CWARN) -Wa,-alms=$(LSTDIR)/$(notdir $(<:.c=.lst)) $(DEFS)
+CPPFLAGS = $(MCFLAGS) $(OPT) $(CPPOPT) $(CPPWARN) -Wa,-alms=$(LSTDIR)/$(notdir $(<:.cpp=.lst)) $(DEFS)
+ifeq ($(USE_LINK_GC),yes)
+ LDFLAGS = $(MCFLAGS) -Wl,-Map=$(BUILDDIR)/$(PROJECT).map,--cref,--no-warn-mismatch,--gc-sections $(LLIBDIR)
+else
+ LDFLAGS = $(MCFLAGS) -Wl,-Map=$(BUILDDIR)/$(PROJECT).map,--cref,--no-warn-mismatch $(LLIBDIR)
+endif
+
+# Generate dependency information
+CFLAGS += -MD -MP -MF .dep/$(@F).d
+CPPFLAGS += -MD -MP -MF .dep/$(@F).d
+
+# Paths where to search for sources
+VPATH = $(SRCPATHS)
+
+#
+# Makefile rules
+#
+
+all: $(OBJS) $(OUTFILES) MAKE_ALL_RULE_HOOK
+
+MAKE_ALL_RULE_HOOK:
+
+$(OBJS): | $(BUILDDIR)
+
+$(BUILDDIR) $(OBJDIR) $(LSTDIR):
+ifneq ($(USE_VERBOSE_COMPILE),yes)
+ @echo Compiler Options
+ @echo $(CPPC) -c $(CPPFLAGS) -I. $(IINCDIR) main.cpp -o main.o
+ @echo
+endif
+ mkdir -p $(OBJDIR)
+ mkdir -p $(LSTDIR)
+
+$(ACPPOBJS) : $(OBJDIR)/%.o : %.cpp Makefile
+ifeq ($(USE_VERBOSE_COMPILE),yes)
+ @echo
+ $(CPPC) -c $(CPPFLAGS) $(AOPT) -I. $(IINCDIR) $< -o $@
+else
+ @echo Compiling $(/dev/null) $(wildcard .dep/*)
+
+# *** EOF ***
diff --git a/unit_tests/settings.h b/unit_tests/settings.h
new file mode 100644
index 0000000000..31327e150e
--- /dev/null
+++ b/unit_tests/settings.h
@@ -0,0 +1,13 @@
+/**
+ * @file settings.h
+ *
+ * @date Feb 13, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef SETTINGS_H_
+#define SETTINGS_H_
+
+
+
+#endif /* SETTINGS_H_ */
diff --git a/unit_tests/test.mk b/unit_tests/test.mk
new file mode 100644
index 0000000000..58e0cecc69
--- /dev/null
+++ b/unit_tests/test.mk
@@ -0,0 +1,18 @@
+TEST_SRC_C = boards.c \
+ test_idle_controller.c
+
+TEST_SRC_CPP = test_util.cpp \
+ test_data_structures/test_event_registry.cpp \
+ test_basic_math/test_find_index.cpp \
+ test_basic_math/test_interpolation_3d.cpp \
+ test_data_structures/test_engine_math.cpp \
+ test_trigger_decoder.cpp \
+ test_fuel_map.cpp \
+ engine_test_helper.cpp \
+ test_speed_density.cpp \
+ test_signal_executor.cpp \
+ test_sensors.cpp \
+ test_accel_enrichment.cpp
+
+
+
diff --git a/unit_tests/test_accel_enrichment.cpp b/unit_tests/test_accel_enrichment.cpp
new file mode 100644
index 0000000000..2b1f070d22
--- /dev/null
+++ b/unit_tests/test_accel_enrichment.cpp
@@ -0,0 +1,35 @@
+/**
+ * @file test_accel_enrichment.cpp
+ *
+ * Created on: apr 29, 2014
+ * Author: Dmitry Sidin
+ * Author: Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#include "main.h"
+#include "accel_enrichment.h"
+#include "test_accel_enrichment.h"
+#include "engine_configuration.h"
+
+void testAccelEnrichment(void) {
+
+ engine_configuration_s ec;
+ engine_configuration_s *engineConfiguration = &ec;
+
+ printf("*************************************************** testAccelEnrichment\r\n");
+ engineConfiguration->diffLoadEnrichmentCoef = 1;
+
+ // todo: fix the implementation
+ // todo: add constructor which would reset state properly
+ AccelEnrichmemnt instance;
+
+ for (int i = 0; i == 3; i++)
+ instance.updateDiffEnrichment(engineConfiguration, i);
+// assertEqualsM("diff=1", 1, instance.getDiffEnrichment());
+
+ for (int i = 3; i == 0; i++)
+ instance.updateDiffEnrichment(engineConfiguration, i);
+
+// assertEqualsM("diff=1", 1, instance.getDiffEnrichment());
+
+}
diff --git a/unit_tests/test_accel_enrichment.h b/unit_tests/test_accel_enrichment.h
new file mode 100644
index 0000000000..225a846664
--- /dev/null
+++ b/unit_tests/test_accel_enrichment.h
@@ -0,0 +1,17 @@
+/**
+ * @file test_accel_enrichment.h
+ *
+ * Created on: apr 29, 2014
+ * Author: Dmitry Sidin
+ * Author: Andrey Belomutskiy, (c) 2012-2013
+ */
+
+
+#ifndef TEST_ACCEL_ENRICHMENT_H_
+#define TEST_ACCEL_ENRICHMENT_H_
+
+void testAccelEnrichment(void);
+
+
+
+#endif /* TEST_ACCEL_ENRICHMENT_H_ */
diff --git a/unit_tests/test_basic_math/test_find_index.cpp b/unit_tests/test_basic_math/test_find_index.cpp
new file mode 100644
index 0000000000..47d34d851a
--- /dev/null
+++ b/unit_tests/test_basic_math/test_find_index.cpp
@@ -0,0 +1,120 @@
+/*
+ * test_find_index.c
+ *
+ * Created on: Oct 30, 2013
+ * Author: Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#include "engine_math.h"
+#include "main.h"
+#include "interpolation.h"
+#include
+#include "engine_configuration.h"
+
+void testFindIndex(void) {
+ printf("*************************************************** testFindIndex\r\n");
+
+ float array[] = { 1, 2, 3, 4, 5 };
+ int size = 4;
+ int result;
+
+ printf("To the left\r\n");
+ result = findIndex(array, size, -1.0);
+ assertEquals(-1, result);
+
+ printf("To the right4\r\n");
+ result = findIndex(array, size, 10.0);
+ assertEquals(3, result);
+
+ printf("To the right5\r\n");
+ result = findIndex(array, 5, 10.0);
+ assertEquals(4, result);
+
+ printf("On the edge\r\n");
+ result = findIndex(array, size, 4.0);
+ assertEquals(3, result);
+
+ printf("Another1\r\n");
+ result = findIndex(array, size, 3.9);
+ assertEquals(2, result);
+
+ printf("Another2\r\n");
+ result = findIndex(array, size, 4.1);
+ assertEquals(3, result);
+
+ printf("Another3\r\n");
+ result = findIndex(array, size, 2);
+ assertEquals(1, result);
+
+ printf("Left edge1\r\n");
+ result = findIndex(array, size, 1);
+ assertEquals(0, result);
+
+ printf("Left edge2\r\n");
+ result = findIndex(array, size, 1.1);
+ assertEquals(0, result);
+
+ printf("Middle\r\n");
+ result = findIndex(array, size, 3);
+ assertEquals(2, result);
+
+ size = 5; // now test with off array size
+
+ printf("Middle2\r\n");
+ result = findIndex(array, size, 4);
+ assertEquals(3, result);
+
+ printf("Middle2\r\n");
+ result = findIndex(array, size, 3.1);
+ assertEquals(2, result);
+}
+
+//static float getValue2(float key, float maf) {
+//
+//}
+
+void testInterpolate2d(void) {
+ printf("*************************************************** testInterpolate2d\r\n");
+
+ float bins4[] = { 1, 2, 3, 4 };
+ float values4[] = { 1, 20, 30, 400 };
+ int size = 4;
+
+ int result;
+
+ printf("Left size\r\n");
+ result = interpolate2d(0, bins4, values4, size);
+ assertEquals(1, result);
+
+ printf("Right size\r\n");
+ result = interpolate2d(10, bins4, values4, size);
+ assertEquals(400, result);
+
+ printf("Middle1\r\n");
+ result = interpolate2d(3, bins4, values4, size);
+ assertEquals(30, result);
+
+ printf("Middle1\r\n");
+ result = interpolate2d(3.5, bins4, values4, size);
+ assertEquals(215, result);
+}
+
+static engine_configuration_s engineConfiguration;
+
+void testSetTableValue(void) {
+ printf("*************************************************** testSetTableValue\r\n");
+
+ for (int i = 0; i < CLT_CURVE_SIZE; i++) {
+ engineConfiguration.cltFuelCorrBins[i] = -40 + i * 10;
+ engineConfiguration.cltFuelCorr[i] = 1;
+ }
+
+ assertEquals(1, engineConfiguration.cltFuelCorr[0]);
+
+ setTableValue(engineConfiguration.cltFuelCorrBins, engineConfiguration.cltFuelCorr, CLT_CURVE_SIZE, -40, 1.5);
+ assertEquals(1.5, engineConfiguration.cltFuelCorr[0]);
+
+ setTableValue(engineConfiguration.cltFuelCorrBins, engineConfiguration.cltFuelCorr, CLT_CURVE_SIZE, -50, 1.4);
+ assertEquals(1.4, engineConfiguration.cltFuelCorr[0]);
+
+}
diff --git a/unit_tests/test_basic_math/test_find_index.h b/unit_tests/test_basic_math/test_find_index.h
new file mode 100644
index 0000000000..3a611751c1
--- /dev/null
+++ b/unit_tests/test_basic_math/test_find_index.h
@@ -0,0 +1,15 @@
+/*
+ * test_find_index.h
+ *
+ * Created on: Oct 30, 2013
+ * Author: Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#ifndef TEST_FIND_INDEX_H_
+#define TEST_FIND_INDEX_H_
+
+void testFindIndex(void);
+void testInterpolate2d(void);
+void testSetTableValue(void);
+
+#endif /* TEST_FIND_INDEX_H_ */
diff --git a/unit_tests/test_basic_math/test_interpolation_3d.cpp b/unit_tests/test_basic_math/test_interpolation_3d.cpp
new file mode 100644
index 0000000000..6fa875b94c
--- /dev/null
+++ b/unit_tests/test_basic_math/test_interpolation_3d.cpp
@@ -0,0 +1,70 @@
+/*
+ * Created on: Oct 17, 2013
+ * Author: Andrey Belomutskiy, (c) 2012-2013
+ */
+
+/**
+ * @file test_interpolation_3d.c
+ */
+
+#include "test_interpolation_3d.h"
+#include
+#include
+
+#include "interpolation.h"
+#include "main.h"
+
+float rpmBins[5] = { 100, 200, 300, 400, 500 };
+float mafBins[4] = { 1, 2, 3, 4 };
+
+float map0[4] = { 1, 2, 3, 4 };
+float map1[4] = { 2, 3, 4, 5 };
+float map2[4] = { 3, 4, 200, 300 };
+float map3[4] = { 4, 200, 500, 600 };
+float map4[4] = { 4, 200, 500, 600 };
+
+float *map[5] = { map0, map1, map2, map3, map4 };
+
+
+static float getValue(float rpm, float maf) {
+ return interpolate3d(rpm, rpmBins, 5, maf, mafBins, 4, map);
+}
+
+void testInterpolate3d(void) {
+ printf("*************************************************** testInterpolate3d\r\n");
+ float dwell;
+ printf("*** no interpolation here 1\r\n");
+ dwell = getValue(100, 2);
+ assertEquals(2, dwell);
+
+ printf("*** no interpolation here 2\r\n");
+ dwell = getValue(200, 4);
+ assertEquals(5, dwell);
+
+ printf("*** rpm interpolated value expected1\r\n");
+ dwell = getValue(150, 2);
+ assertEquals(2.5, dwell);
+
+ printf("*** rpm interpolated value expected2\r\n");
+ dwell = getValue(250, 3);
+ assertEquals(102, dwell);
+
+ printf("*** both rpm and maf interpolated value expected\r\n");
+ dwell = getValue(335.3, 3.551);
+ assertEquals(361, dwell);
+
+ printf("*** both rpm and maf interpolated value expected 2\r\n");
+ dwell = getValue(410.01, 2.012);
+ assertEquals(203.6, dwell);
+
+ printf("*** both rpm and maf interpolated value expected 3\r\n");
+ dwell = getValue(1000000, 1000);
+ assertEquals(600, dwell);
+
+ printf("*** both rpm and maf interpolated value expected 4\r\n");
+ dwell = getValue(410.01, -1);
+ assertEquals(4, dwell);
+
+ dwell = getValue(-1, -1);
+ assertEquals(1, dwell);
+}
diff --git a/unit_tests/test_basic_math/test_interpolation_3d.h b/unit_tests/test_basic_math/test_interpolation_3d.h
new file mode 100644
index 0000000000..684c26ba74
--- /dev/null
+++ b/unit_tests/test_basic_math/test_interpolation_3d.h
@@ -0,0 +1,16 @@
+/*
+ * Created on: Oct 17, 2013
+ * Author: Andrey Belomutskiy, (c) 2012-2013
+ */
+
+/**
+ * @file test_interpolation_3d.h
+ */
+
+
+#ifndef TEST_INTERPOLATION_3D_H_
+#define TEST_INTERPOLATION_3D_H_
+
+void testInterpolate3d(void);
+
+#endif /* TEST_INTERPOLATION_3D_H_ */
diff --git a/unit_tests/test_data_structures/test_event_registry.cpp b/unit_tests/test_data_structures/test_event_registry.cpp
new file mode 100644
index 0000000000..8735929320
--- /dev/null
+++ b/unit_tests/test_data_structures/test_event_registry.cpp
@@ -0,0 +1,21 @@
+/*
+ * test_event_registry.cpp
+ *
+ * Created on: Nov 27, 2013
+ * Author: Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#include "event_registry.h"
+#include "test_event_registry.h"
+#include "main.h"
+#include "OutputSignalList.h"
+
+static ActuatorEventList eventList;
+static ActuatorEventList result;
+
+int pinDefaultState[IO_PIN_COUNT];
+
+extern int outputSignalCount;
+
+void testEventRegistry(void) {
+}
diff --git a/unit_tests/test_data_structures/test_event_registry.h b/unit_tests/test_data_structures/test_event_registry.h
new file mode 100644
index 0000000000..fd341bd372
--- /dev/null
+++ b/unit_tests/test_data_structures/test_event_registry.h
@@ -0,0 +1,22 @@
+/*
+ * test_event_registry.h
+ *
+ * Created on: Nov 27, 2013
+ * Author: Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#ifndef TEST_EVENT_REGISTRY_H_
+#define TEST_EVENT_REGISTRY_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void testEventRegistry(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* TEST_EVENT_REGISTRY_H_ */
diff --git a/unit_tests/test_engine_math.cpp b/unit_tests/test_engine_math.cpp
new file mode 100644
index 0000000000..86739b195f
--- /dev/null
+++ b/unit_tests/test_engine_math.cpp
@@ -0,0 +1,41 @@
+/*
+ * @file test_engine_math.c
+ *
+ * Created on: Nov 14, 2013
+ * Author: Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#include "test_engine_math.h"
+#include "main.h"
+#include "engine_math.h"
+#include "engine_configuration.h"
+#include "ec2.h"
+#include "map.h"
+#include "speed_density.h"
+
+extern engine_configuration_s *engineConfiguration;
+extern engine_configuration2_s *engineConfiguration2;
+
+void testEngineMath(void) {
+ printf("*************************************************** testEngineMath\r\n");
+
+ engineConfiguration->rpmMultiplier = 0.5;
+
+ assertEqualsM("600 RPM", 50, getOneDegreeTimeMs(600) * 180);
+ assertEqualsM("6000 RPM", 5, getOneDegreeTimeMs(6000) * 180);
+
+
+ assertEquals(312.5, getTCharge(1000, 0, 300, 350));
+ assertEquals(313.5833, getTCharge(1000, 50, 300, 350));
+ assertEquals(314.6667, getTCharge(1000, 100, 300, 350));
+
+
+ assertEquals(312.5, getTCharge(4000, 0, 300, 350));
+ assertEquals(320.0833, getTCharge(4000, 50, 300, 350));
+ assertEquals(327.6667, getTCharge(4000, 100, 300, 350));
+
+}
+
+float getMap(void) {
+ return 0;
+}
diff --git a/unit_tests/test_engine_math.h b/unit_tests/test_engine_math.h
new file mode 100644
index 0000000000..8f80cc7f41
--- /dev/null
+++ b/unit_tests/test_engine_math.h
@@ -0,0 +1,13 @@
+/*
+ * @file test_engine_math.h
+ *
+ * Created on: Nov 14, 2013
+ * Author: Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#ifndef TEST_ENGINE_MATH_H_
+#define TEST_ENGINE_MATH_H_
+
+void testEngineMath(void);
+
+#endif /* TEST_ENGINE_MATH_H_ */
diff --git a/unit_tests/test_fuel_map.cpp b/unit_tests/test_fuel_map.cpp
new file mode 100644
index 0000000000..cb45ee8cb7
--- /dev/null
+++ b/unit_tests/test_fuel_map.cpp
@@ -0,0 +1,197 @@
+/**
+ * @file test_fuel_map.cpp
+ *
+ * Created on: Nov 6, 2013
+ * Author: Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#include "test_fuel_map.h"
+#include "main.h"
+#include "engine_configuration.h"
+#include "fuel_math.h"
+#include "trigger_structure.h"
+#include "allsensors.h"
+#include "engine_math.h"
+#include "OutputSignalList.h"
+#include "ec2.h"
+#include "trigger_decoder.h"
+#include "engine_test_helper.h"
+
+extern float testMafValue;
+
+
+extern engine_configuration_s *engineConfiguration;
+extern engine_configuration2_s *engineConfiguration2;
+
+void testFuelMap(void) {
+ chDbgCheck(engineConfiguration!=NULL, "engineConfiguration");
+
+ printf("*************************************************** testFuelMap\r\n");
+
+ for (int k = 0; k < FUEL_LOAD_COUNT; k++) {
+ for (int r = 0; r < FUEL_RPM_COUNT; r++) {
+ engineConfiguration->fuelTable[k][r] = k * 200 + r;
+ }
+ }
+ printf("*************************************************** initThermistors\r\n");
+
+ initThermistors();
+
+ printf("*** getInjectorLag\r\n");
+ assertEquals(0, getInjectorLag(12));
+
+ for (int i = 0; i < FUEL_LOAD_COUNT; i++)
+ engineConfiguration->fuelLoadBins[i] = i;
+ for (int i = 0; i < FUEL_RPM_COUNT; i++)
+ engineConfiguration->fuelRpmBins[i] = i;
+
+ printf("*************************************************** prepareFuelMap\r\n");
+ assertEquals(1005, getBaseTableFuel(5, 5));
+
+ engineConfiguration->injectorLag = 0.5;
+
+ for (int i = 0; i < VBAT_INJECTOR_CURVE_SIZE; i++) {
+ engineConfiguration->battInjectorLagCorrBins[i] = i;
+ engineConfiguration->battInjectorLagCorr[i] = 2 * i;
+ }
+
+ EngineTestHelper eth(FORD_ASPIRE_1996);
+
+ // because all the correction tables are zero
+ printf("*************************************************** getRunningFuel\r\n");
+ float baseFuel = getBaseTableFuel(5, getEngineLoadT(ð.engine));
+ assertEqualsM("value", 0.5, getRunningFuel(baseFuel, ð.engine, 5));
+
+ printf("*************************************************** setting IAT table\r\n");
+ for (int i = 0; i < IAT_CURVE_SIZE; i++) {
+ engineConfiguration->iatFuelCorrBins[i] = i;
+ engineConfiguration->iatFuelCorr[i] = 2 * i;
+ }
+ engineConfiguration->iatFuelCorr[0] = 2;
+
+ printf("*************************************************** setting CLT table\r\n");
+ for (int i = 0; i < CLT_CURVE_SIZE; i++) {
+ engineConfiguration->cltFuelCorrBins[i] = i;
+ engineConfiguration->cltFuelCorr[i] = 1;
+ }
+ engineConfiguration->injectorLag = 0;
+
+ assertEquals(NAN, getIntakeAirTemperature());
+ float iatCorrection = getIatCorrection(-KELV);
+ assertEqualsM("IAT", 2, iatCorrection);
+ float cltCorrection = getCltCorrection(getCoolantTemperature());
+ assertEqualsM("CLT", 1, cltCorrection);
+ float injectorLag = getInjectorLag(getVBatt());
+ assertEquals(0, injectorLag);
+
+ testMafValue = 5;
+
+ // 1005 * 2 for IAT correction
+ printf("*************************************************** getRunningFuel\r\n");
+ baseFuel = getBaseTableFuel(5, getEngineLoadT(ð.engine));
+ assertEqualsM("v1", 30150, getRunningFuel(baseFuel, ð.engine, 5));
+
+ testMafValue = 0;
+
+ engineConfiguration->crankingSettings.coolantTempMaxC = 65; // 8ms at 65C
+ engineConfiguration->crankingSettings.fuelAtMaxTempMs = 8;
+
+ engineConfiguration->crankingSettings.coolantTempMinC = 0; // 20ms at 0C
+ engineConfiguration->crankingSettings.fuelAtMinTempMs = 20;
+
+ printf("*************************************************** getStartingFuel\r\n");
+ // NAN in case we have issues with the CLT sensor
+// assertEquals(16, getStartingFuel(NAN));
+ assertEquals(20, getStartingFuel(0));
+ assertEquals(18.5231, getStartingFuel(8));
+ assertEquals(8, getStartingFuel(70));
+}
+
+static void confgiureFordAspireTriggerShape(trigger_shape_s * s) {
+ s->reset(FOUR_STROKE_CAM_SENSOR);
+
+ s->addEvent(53.747, T_SECONDARY, TV_HIGH);
+ s->addEvent(121.90, T_SECONDARY, TV_LOW);
+ s->addEvent(232.76, T_SECONDARY, TV_HIGH);
+ s->addEvent(300.54, T_SECONDARY, TV_LOW);
+ s->addEvent(360, T_PRIMARY, TV_HIGH);
+
+ s->addEvent(409.8412, T_SECONDARY, TV_HIGH);
+ s->addEvent(478.6505, T_SECONDARY, TV_LOW);
+ s->addEvent(588.045, T_SECONDARY, TV_HIGH);
+ s->addEvent(657.03, T_SECONDARY, TV_LOW);
+ s->addEvent(720, T_PRIMARY, TV_LOW);
+
+ assertEquals(53.747 / 720, s->wave.getSwitchTime(0));
+ assertEqualsM("@0", 1, s->wave.getChannelState(1, 0));
+ assertEqualsM("@0", 1, s->wave.getChannelState(1, 0));
+
+ assertEqualsM("@1", 0, s->wave.getChannelState(0, 1));
+ assertEqualsM("@1", 0, s->wave.getChannelState(1, 1));
+
+ assertEqualsM("@2", 0, s->wave.getChannelState(0, 2));
+ assertEqualsM("@2", 1, s->wave.getChannelState(1, 2));
+
+ assertEqualsM("@3", 0, s->wave.getChannelState(0, 3));
+ assertEqualsM("@3", 0, s->wave.getChannelState(1, 3));
+
+ assertEqualsM("@4", 1, s->wave.getChannelState(0, 4));
+ assertEqualsM("@5", 1, s->wave.getChannelState(1, 5));
+ assertEqualsM("@8", 0, s->wave.getChannelState(1, 8));
+ assertEquals(121.90 / 720, s->wave.getSwitchTime(1));
+ assertEquals(657.03 / 720, s->wave.getSwitchTime(8));
+
+ assertEqualsM("expecting 0", 0, s->wave.findAngleMatch(53.747 / 720.0, s->getSize()));
+ assertEqualsM("expecting not found", -1, s->wave.findAngleMatch(53 / 720.0, s->getSize()));
+ assertEquals(7, s->wave.findAngleMatch(588.045 / 720.0, s->getSize()));
+
+ assertEqualsM("expecting 0", 0, s->wave.waveIndertionAngle(23.747 / 720.0, s->getSize()));
+ assertEqualsM("expecting 1", 1, s->wave.waveIndertionAngle(63.747 / 720.0, s->getSize()));
+}
+
+static ActuatorEventList ae;
+
+void testAngleResolver(void) {
+ printf("*************************************************** testAngleResolver\r\n");
+
+ engineConfiguration->globalTriggerAngleOffset = 175;
+ trigger_shape_s * ts = &engineConfiguration2->triggerShape;
+
+ confgiureFordAspireTriggerShape(ts);
+
+ ts->calculateTriggerSynchPoint(&engineConfiguration->triggerConfig);
+
+ assertEqualsM("index 2", 232.76, ts->eventAngles[3]); // this angle is relation to synch point
+ assertEqualsM("time 2", 0.3233, ts->wave.getSwitchTime(2));
+ assertEqualsM("index 5", 409.8412, ts->eventAngles[6]);
+ assertEqualsM("time 5", 0.5692, ts->wave.getSwitchTime(5));
+
+ assertEquals(9, ts->getTriggerShapeSynchPointIndex());
+
+ assertEqualsM("shape size", 10, ts->getSize());
+
+ OutputSignalList list;
+
+ ae.resetEventList();
+ printf("*************************************************** testAngleResolver 0\r\n");
+ registerActuatorEventExt(engineConfiguration, &engineConfiguration2->triggerShape, ae.getNextActuatorEvent(), list.add(INJECTOR_1_OUTPUT), 53 - 175);
+ assertEqualsM("size", 1, ae.size);
+ assertEquals(1, list.getSize());
+ assertEquals(0, ae.events[0].position.eventIndex);
+ assertEquals(53, ae.events[0].position.angleOffset);
+
+ printf("*************************************************** testAngleResolver 2\r\n");
+ ae.resetEventList();
+ registerActuatorEventExt(engineConfiguration, &engineConfiguration2->triggerShape, ae.getNextActuatorEvent(), list.add(INJECTOR_1_OUTPUT), 51 + 180 - 175);
+ assertEquals(2, ae.events[0].position.eventIndex);
+ assertEquals(109.1, ae.events[0].position.angleOffset);
+}
+
+void testPinHelper(void) {
+ printf("*************************************************** testPinHelper\r\n");
+ assertEquals(0, getElectricalValue(0, OM_DEFAULT));
+ assertEquals(1, getElectricalValue(1, OM_DEFAULT));
+
+ assertEquals(0, getElectricalValue(1, OM_INVERTED));
+ assertEquals(1, getElectricalValue(0, OM_INVERTED));
+}
diff --git a/unit_tests/test_fuel_map.h b/unit_tests/test_fuel_map.h
new file mode 100644
index 0000000000..331c753274
--- /dev/null
+++ b/unit_tests/test_fuel_map.h
@@ -0,0 +1,24 @@
+/*
+ * test_fuel_map.h
+ *
+ * Created on: Nov 6, 2013
+ * Author: pc
+ */
+
+#ifndef TEST_FUEL_MAP_H_
+#define TEST_FUEL_MAP_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void testFuelMap(void);
+void testAngleResolver(void);
+void testPinHelper(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* TEST_FUEL_MAP_H_ */
diff --git a/unit_tests/test_idle_controller.c b/unit_tests/test_idle_controller.c
new file mode 100644
index 0000000000..4527e24df6
--- /dev/null
+++ b/unit_tests/test_idle_controller.c
@@ -0,0 +1,12 @@
+/*
+ * test_idle_controller.c
+ *
+ * Created on: Oct 17, 2013
+ * Author: Andrey
+ */
+
+#include
+
+void idleDebug(char *msg, int value) {
+ printf("%s\r\n", msg);
+}
diff --git a/unit_tests/test_idle_controller.h b/unit_tests/test_idle_controller.h
new file mode 100644
index 0000000000..0d0838499c
--- /dev/null
+++ b/unit_tests/test_idle_controller.h
@@ -0,0 +1,7 @@
+#ifndef TEST_IDLE_CONTROLLER_H
+#define TEST_IDLE_CONTROLLER_H
+
+void idleDebug(char *msg, int value);
+bool_t isCranking(void);
+
+#endif
diff --git a/unit_tests/test_sensors.cpp b/unit_tests/test_sensors.cpp
new file mode 100644
index 0000000000..f6f5ee5d5e
--- /dev/null
+++ b/unit_tests/test_sensors.cpp
@@ -0,0 +1,57 @@
+/**
+ * @file test_sensors.c
+ *
+ * @date Dec 7, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#include "main.h"
+#include "thermistors.h"
+#include "allsensors.h"
+
+static ThermistorConf tc;
+
+static void testMapDecoding(void) {
+
+ air_pressure_sensor_config_s s;
+ s.sensorType = MT_DENSO183;
+
+ assertEqualsM("denso 0 volts", -6.64, decodePressure(0, &s));
+ assertEquals(31.244, decodePressure(1, &s));
+
+ s.sensorType = MT_MPX4250;
+ assertEqualsM("MPX_4250 0 volts", 8, decodePressure(0, &s));
+ assertEquals(58.4, decodePressure(1, &s));
+}
+
+void testTpsRateOfChange(void) {
+ print("************************************************** testTpsRateOfChange\r\n");
+ saveTpsState(0, 0);
+ saveTpsState(CH_FREQUENCY, 50);
+ assertEquals(50, getTpsRateOfChange());
+
+ saveTpsState(2 * CH_FREQUENCY, 50);
+ assertEquals(0, getTpsRateOfChange());
+
+ saveTpsState(3 * CH_FREQUENCY, 75);
+ assertEquals(25, getTpsRateOfChange());
+
+
+}
+
+void testSensors(void) {
+ print("************************************************** testSensors\r\n");
+ testMapDecoding();
+ testTpsRateOfChange();
+
+ setThermistorConfiguration(&tc, 32, 9500, 75, 2100, 120, 1000);
+
+ prepareThermistorCurve(&tc);
+
+ assertEquals(-0.003, tc.s_h_a);
+ assertEquals(0.001, tc.s_h_b);
+ assertEquals(0.0, tc.s_h_c);
+
+ float t = convertResistanceToKelvinTemperature(2100, &tc);
+ assertEquals(75 + KELV, t);
+}
diff --git a/unit_tests/test_sensors.h b/unit_tests/test_sensors.h
new file mode 100644
index 0000000000..b59fd418fd
--- /dev/null
+++ b/unit_tests/test_sensors.h
@@ -0,0 +1,13 @@
+/**
+ * @file test_sensors.h
+ *
+ * @date Dec 7, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#ifndef TEST_SENSORS_H_
+#define TEST_SENSORS_H_
+
+void testSensors(void);
+
+#endif /* TEST_SENSORS_H_ */
diff --git a/unit_tests/test_signal_executor.cpp b/unit_tests/test_signal_executor.cpp
new file mode 100644
index 0000000000..a7e86cc518
--- /dev/null
+++ b/unit_tests/test_signal_executor.cpp
@@ -0,0 +1,141 @@
+/**
+ * @file test_signal_executor.cpp
+ *
+ * @date Nov 28, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#include
+#include "main.h"
+
+#include "signal_executor.h"
+#include "test_signal_executor.h"
+#include "io_pins.h"
+#include "utlist.h"
+#include "event_queue.h"
+
+static io_pin_e testLastToggledPin;
+static int testToggleCounter;
+
+void setOutputPinValue(io_pin_e pin, int value) {
+ // this is a test implementation of the method - we use it to see what's going on
+ testLastToggledPin = pin;
+ testToggleCounter++;
+}
+
+EventQueue schedulingQueue;
+
+void scheduleTask(const char *msg, scheduling_s *scheduling, int delayUs, schfunc_t callback, void *param) {
+ schedulingQueue.insertTask(scheduling, getTimeNowUs(), delayUs, callback, param);
+}
+
+void initSignalExecutorImpl(void) {
+}
+
+static EventQueue eq;
+
+static int callbackCounter = 0;
+
+static void callback(void *a) {
+ callbackCounter++;
+}
+
+static int complexTestNow;
+
+typedef struct {
+ scheduling_s s;
+ int period;
+} TestPwm;
+
+static void complexCallback(TestPwm *testPwm) {
+ callbackCounter++;
+
+ eq.insertTask(&testPwm->s, complexTestNow, testPwm->period, (schfunc_t)complexCallback, testPwm);
+}
+
+static void testSignalExecutor2(void) {
+ print("*************************************** testSignalExecutor2\r\n");
+ eq.clear();
+ TestPwm p1;
+ TestPwm p2;
+ p1.period = 2;
+ p2.period = 3;
+
+ complexTestNow = 0;
+ callbackCounter = 0;
+ eq.insertTask(&p1.s, 0, 0, (schfunc_t)complexCallback, &p1);
+ eq.insertTask(&p2.s, 0, 0, (schfunc_t)complexCallback, &p2);
+ eq.executeAll(complexTestNow);
+ assertEqualsM("callbackCounter #1", 2, callbackCounter);
+ assertEquals(2, eq.size());
+
+ eq.executeAll(complexTestNow = 2);
+ assertEqualsM("callbackCounter #2", 3, callbackCounter);
+ assertEquals(2, eq.size());
+
+ eq.executeAll(complexTestNow = 3);
+ assertEqualsM("callbackCounter #3", 4, callbackCounter);
+ assertEquals(2, eq.size());
+
+}
+
+void testSignalExecutor(void) {
+ print("*************************************** testSignalExecutor\r\n");
+
+ assertEquals(EMPTY_QUEUE, eq.getNextEventTime(0));
+ scheduling_s s1;
+ scheduling_s s2;
+ scheduling_s s3;
+
+ eq.insertTask(&s1, 0, 10, callback, NULL);
+ eq.insertTask(&s2, 0, 11, callback, NULL);
+ eq.insertTask(&s3, 0, 12, callback, NULL);
+ callbackCounter = 0;
+ eq.executeAll(10);
+ assertEquals(1, callbackCounter);
+ callbackCounter = 0;
+ eq.executeAll(11);
+ assertEquals(1, callbackCounter);
+ eq.clear();
+
+ eq.insertTask(&s1, 0, 12, callback, NULL);
+ eq.insertTask(&s2, 0, 11, callback, NULL);
+ eq.insertTask(&s3, 0, 10, callback, NULL);
+ callbackCounter = 0;
+ eq.executeAll(10);
+ assertEquals(1, callbackCounter);
+ callbackCounter = 0;
+ eq.executeAll(11);
+ assertEquals(1, callbackCounter);
+ eq.clear();
+
+ callbackCounter = 0;
+ eq.insertTask(&s1, 0, 10, callback, NULL);
+ assertEquals(10, eq.getNextEventTime(0));
+
+ eq.executeAll(1);
+ assertEqualsM("callbacks not expected", 0, callbackCounter);
+
+ eq.executeAll(11);
+ assertEquals(1, callbackCounter);
+
+ assertEquals(EMPTY_QUEUE, eq.getNextEventTime(0));
+
+ eq.insertTask(&s1, 0, 10, callback, NULL);
+ eq.insertTask(&s2, 0, 13, callback, NULL);
+ assertEquals(10, eq.getNextEventTime(0));
+
+ eq.executeAll(1);
+ assertEquals(10, eq.getNextEventTime(0));
+
+ eq.clear();
+ callbackCounter = 0;
+ // both events are scheduled for the same time
+ eq.insertTask(&s1, 0, 10, callback, NULL);
+ eq.insertTask(&s2, 0, 10, callback, NULL);
+
+ eq.executeAll(11);
+
+ assertEquals(2, callbackCounter);
+ testSignalExecutor2();
+}
diff --git a/unit_tests/test_signal_executor.h b/unit_tests/test_signal_executor.h
new file mode 100644
index 0000000000..5cf1c8c26c
--- /dev/null
+++ b/unit_tests/test_signal_executor.h
@@ -0,0 +1,23 @@
+/**
+ * @file test_signal_executor.h
+ *
+ * @date Nov 28, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#ifndef TEST_SIGNAL_EXECUTOR_H_
+#define TEST_SIGNAL_EXECUTOR_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void testSignalExecutor(void);
+int getRevolutionCounter(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* TEST_SIGNAL_EXECUTOR_H_ */
diff --git a/unit_tests/test_speed_density.cpp b/unit_tests/test_speed_density.cpp
new file mode 100644
index 0000000000..2aa2571c5e
--- /dev/null
+++ b/unit_tests/test_speed_density.cpp
@@ -0,0 +1,31 @@
+/**
+ * @file test_speed_density.cpp
+ *
+ * @date Jun 26, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+#include "engine_test_helper.h"
+#include "speed_density.h"
+#include "test_speed_density.h"
+
+void testSpeedDensity(void) {
+ printf("*************************************************** testSpeedDensity\r\n");
+ EngineTestHelper eth(FORD_INLINE_6_1995);
+
+ eth.ec->triggerConfig.customTotalToothCount = 8;
+ eth.initTriggerShapeAndRpmCalculator();
+
+ eth.fireTriggerEvents();
+ assertEqualsM("RPM", 1500, eth.rpmState.rpm());
+
+ // 427 cubic inches, that's a LOT of engine
+ eth.ec->displacement = 6.99728;
+ eth.ec->cylindersCount = 8;
+
+ eth.ec->injectorFlow = gramm_second_to_cc_minute(5.303);
+
+ // 0.01414 sec or 14.14 ms
+ assertEquals(0.01414, sdMath(eth.ec, 0.92, 98, 12.5, 293.16));
+}
diff --git a/unit_tests/test_speed_density.h b/unit_tests/test_speed_density.h
new file mode 100644
index 0000000000..e4dd8cd546
--- /dev/null
+++ b/unit_tests/test_speed_density.h
@@ -0,0 +1,13 @@
+/**
+ * @file test_speed_density.h
+ *
+ * @date Jun 26, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef TEST_SPEED_DENSITY_H_
+#define TEST_SPEED_DENSITY_H_
+
+void testSpeedDensity(void);
+
+#endif /* TEST_SPEED_DENSITY_H_ */
diff --git a/unit_tests/test_trigger_decoder.cpp b/unit_tests/test_trigger_decoder.cpp
new file mode 100644
index 0000000000..9bf784515a
--- /dev/null
+++ b/unit_tests/test_trigger_decoder.cpp
@@ -0,0 +1,480 @@
+/**
+ * @file test_trigger_decoder.cpp
+ *
+ * @date Dec 24, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#include "main.h"
+#include "test_trigger_decoder.h"
+#include "trigger_decoder.h"
+#include "engine_math.h"
+#include "thermistors.h"
+
+#include "ford_aspire.h"
+#include "dodge_neon.h"
+#include "ford_1995_inline_6.h"
+#include "mazda_323.h"
+#include "rpm_calculator.h"
+#include "event_queue.h"
+#include "algo.h"
+
+#include "trigger_central.h"
+#include "main_trigger_callback.h"
+#include "engine.h"
+#include "advance_map.h"
+#include "engine_test_helper.h"
+#include "speed_density.h"
+
+Engine engine;
+
+extern int timeNow;
+
+extern "C" {
+void sendOutConfirmation(char *value, int i);
+}
+
+void sendOutConfirmation(char *value, int i) {
+ // test implementation
+}
+
+int getTheAngle(engine_type_e engineType) {
+ persistent_config_s persistentConfig;
+ engine_configuration_s *ec = &persistentConfig.engineConfiguration;
+ engine_configuration2_s ec2;
+
+ initDataStructures(ec);
+ resetConfigurationExt(NULL, engineType, ec, &ec2, &persistentConfig.engineConfiguration.bc);
+
+ trigger_shape_s * shape = &ec2.triggerShape;
+ return findTriggerZeroEventIndex(shape, &ec->triggerConfig);
+}
+
+static void testDodgeNeonDecoder(void) {
+ printf("*************************************************** testDodgeNeonDecoder\r\n");
+ initTriggerDecoder();
+
+ assertEqualsM("trigger zero index", 8, getTheAngle(DODGE_NEON_1995));
+
+ persistent_config_s persistentConfig;
+ engine_configuration_s *ec = &persistentConfig.engineConfiguration;
+ engine_configuration2_s ec2;
+
+ resetConfigurationExt(NULL, DODGE_NEON_1995, ec, &ec2, &persistentConfig.engineConfiguration.bc);
+ assertEquals(8, ec2.triggerShape.getTriggerShapeSynchPointIndex());
+
+ trigger_shape_s * shape = &ec2.triggerShape;
+ TriggerState state;
+
+ assertFalseM("1 shaft_is_synchronized", state.shaft_is_synchronized);
+
+ int r = 0;
+// processTriggerEvent(&state, shape, &ec->triggerConfig, SHAFT_PRIMARY_UP, r + 60);
+// assertFalseM("2 shaft_is_synchronized", state.shaft_is_synchronized); // still no synchronization
+
+// processTriggerEvent(&state, shape, &ec->triggerConfig, SHAFT_PRIMARY_DOWN, r + 210);
+// assertFalseM("3 shaft_is_synchronized", state.shaft_is_synchronized); // still no synchronization
+//
+// processTriggerEvent(&state, shape, &ec->triggerConfig, SHAFT_PRIMARY_UP, r + 420);
+// assertFalseM("4 shaft_is_synchronized", state.shaft_is_synchronized); // still no synchronization
+//
+// processTriggerEvent(&state, shape, &ec->triggerConfig, SHAFT_PRIMARY_DOWN, r + 630);
+// assertFalse(state.shaft_is_synchronized); // still no synchronization
+//
+// printf("2nd camshaft revolution\r\n");
+// r = 720;
+// processTriggerEvent(&state, shape, &ec->triggerConfig, SHAFT_PRIMARY_UP, r + 60);
+// processTriggerEvent(&state, shape, &ec->triggerConfig, SHAFT_PRIMARY_DOWN, r + 210);
+// assertTrue(state.shaft_is_synchronized);
+// assertEquals(0, state.current_index);
+// processTriggerEvent(&state, shape, &ec->triggerConfig, SHAFT_PRIMARY_UP, r + 420);
+// assertEquals(1, state.current_index);
+// processTriggerEvent(&state, shape, &ec->triggerConfig, SHAFT_PRIMARY_DOWN, r + 630);
+// assertEquals(2, state.current_index);
+//
+// printf("3rd camshaft revolution\r\n");
+// r = 2 * 720;
+// processTriggerEvent(&state, shape, &ec->triggerConfig, SHAFT_PRIMARY_UP, r + 60);
+// assertEqualsM("current index", 3, state.current_index);
+// processTriggerEvent(&state, shape, &ec->triggerConfig, SHAFT_PRIMARY_DOWN, r + 210);
+// assertTrue(state.shaft_is_synchronized);
+// assertEqualsM("current index", 0, state.current_index);
+// processTriggerEvent(&state, shape, &ec->triggerConfig, SHAFT_PRIMARY_UP, r + 420);
+// processTriggerEvent(&state, shape, &ec->triggerConfig, SHAFT_PRIMARY_DOWN, r + 630);
+}
+
+static void assertTriggerPosition(event_trigger_position_s *position, int eventIndex, float angleOffset) {
+ assertEqualsM("eventIndex", eventIndex, position->eventIndex);
+ assertEqualsM("angleOffset", angleOffset, position->angleOffset);
+}
+
+static void test1995FordInline6TriggerDecoder(void) {
+ printf("*************************************************** test1995FordInline6TriggerDecoder\r\n");
+
+ assertEqualsM("triggerIndex ", 0, getTheAngle(FORD_INLINE_6_1995));
+
+ initTriggerDecoder();
+
+ persistent_config_s persistentConfig;
+ engine_configuration_s *ec = &persistentConfig.engineConfiguration;
+ engine_configuration2_s ec2;
+
+ resetConfigurationExt(NULL, FORD_INLINE_6_1995, ec, &ec2, &persistentConfig.engineConfiguration.bc);
+ assertEqualsM("triggerShapeSynchPointIndex", 0, ec2.triggerShape.getTriggerShapeSynchPointIndex());
+
+ trigger_shape_s * shape = &ec2.triggerShape;
+ event_trigger_position_s position;
+ assertEqualsM("globalTriggerAngleOffset", 0, ec->globalTriggerAngleOffset);
+ findTriggerPosition(ec, shape, &position, 0);
+ assertTriggerPosition(&position, 0, 0);
+
+ findTriggerPosition(ec, shape, &position, 200);
+ assertTriggerPosition(&position, 3, 20);
+
+ findTriggerPosition(ec, shape, &position, 360);
+ assertTriggerPosition(&position, 6, 0);
+
+
+ IgnitionEventList *ecl = &ec2.engineEventConfiguration.ignitionEvents[0];
+ assertEqualsM("ignition events size", 6, ecl->size);
+ assertEqualsM("event index", 0, ecl->events[0].dwellPosition.eventIndex);
+ assertEquals(0, ecl->events[0].dwellPosition.angleOffset);
+
+ assertEqualsM("event index", 10, ecl->events[5].dwellPosition.eventIndex);
+ assertEquals(0, ecl->events[5].dwellPosition.angleOffset);
+
+ TriggerState state;
+
+ assertFalseM("shaft_is_synchronized", state.shaft_is_synchronized);
+ int r = 10;
+ state.decodeTriggerEvent(shape, &ec->triggerConfig, SHAFT_PRIMARY_DOWN, r);
+ assertFalseM("shaft_is_synchronized", state.shaft_is_synchronized); // still no synchronization
+ state.decodeTriggerEvent(shape, &ec->triggerConfig, SHAFT_PRIMARY_UP, ++r);
+ assertTrue(state.shaft_is_synchronized); // first signal rise synchronize
+ assertEquals(0, state.getCurrentIndex());
+ state.decodeTriggerEvent(shape, &ec->triggerConfig, SHAFT_PRIMARY_DOWN, r++);
+ assertEquals(1, state.getCurrentIndex());
+
+ for (int i = 2; i < 10;) {
+ state.decodeTriggerEvent(shape, &ec->triggerConfig, SHAFT_PRIMARY_UP, r++);
+ assertEqualsM("even", i++, state.getCurrentIndex());
+ state.decodeTriggerEvent(shape, &ec->triggerConfig, SHAFT_PRIMARY_DOWN, r++);
+ assertEqualsM("odd", i++, state.getCurrentIndex());
+ }
+
+ state.decodeTriggerEvent(shape, &ec->triggerConfig, SHAFT_PRIMARY_UP, r++);
+ assertEquals(10, state.getCurrentIndex());
+
+ state.decodeTriggerEvent(shape, &ec->triggerConfig, SHAFT_PRIMARY_DOWN, r++);
+ assertEquals(11, state.getCurrentIndex());
+
+ state.decodeTriggerEvent(shape, &ec->triggerConfig, SHAFT_PRIMARY_UP, r++);
+ assertEquals(0, state.getCurrentIndex()); // new revolution
+
+ assertEqualsM("running dwell", 0.5, getSparkDwellMsT(ec, 2000));
+}
+
+void testFordAspire(void) {
+ printf("*************************************************** testFordAspire\r\n");
+
+ assertEquals(4, getTheAngle(FORD_ASPIRE_1996));
+
+ persistent_config_s persistentConfig;
+ engine_configuration_s *ec = &persistentConfig.engineConfiguration;
+ engine_configuration2_s ec2;
+ resetConfigurationExt(NULL, FORD_ASPIRE_1996, ec, &ec2, &persistentConfig.engineConfiguration.bc);
+ assertEquals(4, ec2.triggerShape.getTriggerShapeSynchPointIndex());
+
+ assertEquals(800, ec->fuelRpmBins[0]);
+ assertEquals(7000, ec->fuelRpmBins[15]);
+
+ ec->crankingChargeAngle = 65;
+ ec->crankingTimingAngle = 31;
+
+ assertEqualsM("cranking dwell", 54.166670, getSparkDwellMsT(ec, 200));
+ assertEqualsM("running dwell", 4, getSparkDwellMsT(ec, 2000));
+
+ assertEqualsM("higher rpm dwell", 3.25, getSparkDwellMsT(ec, 6000));
+}
+
+void testMazda323(void) {
+ printf("*************************************************** testMazda323\r\n");
+
+ persistent_config_s persistentConfig;
+ engine_configuration_s *ec = &persistentConfig.engineConfiguration;
+ engine_configuration2_s ec2;
+ resetConfigurationExt(NULL, MAZDA_323, ec, &ec2, &persistentConfig.engineConfiguration.bc);
+ assertEquals(0, ec2.triggerShape.getTriggerShapeSynchPointIndex());
+}
+
+void testMazdaMianaNbDecoder(void) {
+ printf("*************************************************** testMazdaMianaNbDecoder\r\n");
+
+ persistent_config_s persistentConfig;
+ engine_configuration_s *ec = &persistentConfig.engineConfiguration;
+ engine_configuration2_s ec2;
+ resetConfigurationExt(NULL, MAZDA_MIATA_NB, ec, &ec2, &persistentConfig.engineConfiguration.bc);
+ assertEquals(11, ec2.triggerShape.getTriggerShapeSynchPointIndex());
+
+ TriggerState state;
+ trigger_shape_s * shape = &ec2.triggerShape;
+
+ int a = 0;
+ state.decodeTriggerEvent(shape, &ec->triggerConfig, SHAFT_PRIMARY_DOWN, a + 20);
+ assertFalseM("0a shaft_is_synchronized", state.shaft_is_synchronized);
+ state.decodeTriggerEvent(shape, &ec->triggerConfig, SHAFT_PRIMARY_UP, a + 340);
+ assertFalseM("0b shaft_is_synchronized", state.shaft_is_synchronized);
+
+ state.decodeTriggerEvent(shape, &ec->triggerConfig, SHAFT_PRIMARY_DOWN, a + 360);
+ assertFalseM("0c shaft_is_synchronized", state.shaft_is_synchronized);
+ state.decodeTriggerEvent(shape, &ec->triggerConfig, SHAFT_PRIMARY_UP, a + 380);
+ assertFalseM("0d shaft_is_synchronized", state.shaft_is_synchronized);
+ state.decodeTriggerEvent(shape, &ec->triggerConfig, SHAFT_PRIMARY_DOWN, a + 400);
+ assertTrueM("0e shaft_is_synchronized", state.shaft_is_synchronized);
+
+ state.decodeTriggerEvent(shape, &ec->triggerConfig, SHAFT_PRIMARY_UP, a + 720);
+ assertTrueM("0f shaft_is_synchronized", state.shaft_is_synchronized);
+
+ a = 720;
+ state.decodeTriggerEvent(shape, &ec->triggerConfig, SHAFT_PRIMARY_DOWN, a + 20);
+ assertTrueM("1a shaft_is_synchronized", state.shaft_is_synchronized);
+ state.decodeTriggerEvent(shape, &ec->triggerConfig, SHAFT_PRIMARY_UP, a + 340);
+ assertTrueM("1b shaft_is_synchronized", state.shaft_is_synchronized);
+
+ state.decodeTriggerEvent(shape, &ec->triggerConfig, SHAFT_PRIMARY_DOWN, a + 360);
+ assertTrueM("1c shaft_is_synchronized", state.shaft_is_synchronized);
+ state.decodeTriggerEvent(shape, &ec->triggerConfig, SHAFT_PRIMARY_UP, a + 380);
+ assertTrueM("1d shaft_is_synchronized", state.shaft_is_synchronized);
+ assertEquals(5, state.getCurrentIndex());
+
+ state.decodeTriggerEvent(shape, &ec->triggerConfig, SHAFT_PRIMARY_DOWN, a + 400);
+ assertTrueM("1e shaft_is_synchronized", state.shaft_is_synchronized);
+ assertEquals(0, state.getCurrentIndex());
+
+ state.decodeTriggerEvent(shape, &ec->triggerConfig, SHAFT_PRIMARY_UP, a + 720);
+ assertTrueM("1f shaft_is_synchronized", state.shaft_is_synchronized);
+
+ event_trigger_position_s position;
+ assertEqualsM("globalTriggerAngleOffset", 276, ec->globalTriggerAngleOffset);
+ findTriggerPosition(ec, shape, &position, 0);
+ assertTriggerPosition(&position, 7, 46);
+
+ findTriggerPosition(ec, shape, &position, 180);
+ assertTriggerPosition(&position, 13, 46);
+
+ findTriggerPosition(ec, shape, &position, 360);
+ assertTriggerPosition(&position, 17, 46);
+
+ findTriggerPosition(ec, shape, &position, 444);
+ assertTriggerPosition(&position, 0, 0);
+
+ findTriggerPosition(ec, shape, &position, 444.1);
+ assertTriggerPosition(&position, 0, 0.1);
+
+ findTriggerPosition(ec, shape, &position, 445);
+ assertTriggerPosition(&position, 0, 1);
+
+ findTriggerPosition(ec, shape, &position, 494);
+ assertTriggerPosition(&position, 3, 0);
+
+ findTriggerPosition(ec, shape, &position, 719);
+ assertTriggerPosition(&position, 7, 45);
+
+ ec->globalTriggerAngleOffset = 0;
+ findTriggerPosition(ec, shape, &position, 0);
+ assertTriggerPosition(&position, 0, 0);
+
+ ec->globalTriggerAngleOffset = 10;
+ findTriggerPosition(ec, shape, &position, 0);
+ assertTriggerPosition(&position, 0, 10);
+
+ findTriggerPosition(ec, shape, &position, -10);
+ assertTriggerPosition(&position, 0, 0);
+}
+
+static void testTriggerDecoder2(const char *msg, engine_type_e type, int synchPointIndex, float channel1duty, float channel2duty) {
+ printf("*************************************************** %s\r\n", msg);
+
+ persistent_config_s persistentConfig;
+ engine_configuration_s *ec = &persistentConfig.engineConfiguration;
+ engine_configuration2_s ec2;
+ assertEquals(0, ec2.triggerShape.getTriggerShapeSynchPointIndex());
+
+ initSpeedDensity(ec);
+ resetConfigurationExt(NULL, type, ec, &ec2, &persistentConfig.engineConfiguration.bc);
+
+ assertEqualsM("synchPointIndex", synchPointIndex, ec2.triggerShape.getTriggerShapeSynchPointIndex());
+
+ assertEqualsM("channel1duty", channel1duty, ec2.triggerShape.dutyCycle[0]);
+ assertEqualsM("channel2duty", channel2duty, ec2.triggerShape.dutyCycle[1]);
+}
+
+void testGY6_139QMB(void) {
+ printf("*************************************************** testGY6_139QMB\r\n");
+
+ persistent_config_s persistentConfig;
+ engine_configuration_s *ec = &persistentConfig.engineConfiguration;
+ engine_configuration2_s ec2;
+ resetConfigurationExt(NULL, GY6_139QMB, ec, &ec2, &persistentConfig.engineConfiguration.bc);
+
+ TriggerState state;
+ assertFalseM("shaft_is_synchronized", state.shaft_is_synchronized);
+
+ trigger_shape_s * shape = &ec2.triggerShape;
+
+ assertFalseM("shaft_is_synchronized", state.shaft_is_synchronized);
+ assertEquals(0, state.getCurrentIndex());
+
+ int now = 0;
+ state.decodeTriggerEvent(shape, &ec->triggerConfig, SHAFT_PRIMARY_UP, now++);
+ assertTrueM("shaft_is_synchronized", state.shaft_is_synchronized);
+ assertEquals(0, state.getCurrentIndex());
+
+ state.decodeTriggerEvent(shape, &ec->triggerConfig, SHAFT_PRIMARY_DOWN, now++);
+ assertTrueM("shaft_is_synchronized", state.shaft_is_synchronized);
+ assertEquals(1, state.getCurrentIndex());
+}
+
+extern EventQueue schedulingQueue;
+
+static void testRpmCalculator(void) {
+ printf("*************************************************** testRpmCalculator\r\n");
+
+ initThermistors();
+
+ EngineTestHelper eth(FORD_INLINE_6_1995);
+
+ engine_configuration_s *ec = ð.persistentConfig.engineConfiguration;
+
+ engine_configuration2_s *ec2 = ð.ec2;
+
+ ec->triggerConfig.customTotalToothCount = 8;
+ eth.initTriggerShapeAndRpmCalculator();
+
+ configuration_s configuration = { ec, ec2 };
+ timeNow = 0;
+ assertEquals(0, eth.rpmState.rpm());
+
+ eth.fireTriggerEvents();
+ assertEqualsM("RPM", 1500, eth.rpmState.rpm());
+
+ assertEqualsM("index #1", 15, eth.triggerCentral.triggerState.getCurrentIndex());
+
+
+ static MainTriggerCallback triggerCallbackInstance;
+ triggerCallbackInstance.init(ð.engine, ec2);
+ eth.triggerCentral.addEventListener((ShaftPositionListener)&onTriggerEvent, "main loop", &triggerCallbackInstance);
+
+ engine.rpmCalculator = ð.rpmState;
+ prepareTimingMap();
+
+ timeNow += 5000; // 5ms
+ eth.triggerCentral.handleShaftSignal(&configuration, SHAFT_PRIMARY_UP, timeNow);
+ assertEqualsM("index #2", 0, eth.triggerCentral.triggerState.getCurrentIndex());
+ assertEqualsM("queue size", 4, schedulingQueue.size());
+ assertEqualsM("ev 1", 695000, schedulingQueue.getForUnitText(0)->momentUs);
+ assertEqualsM("ev 2", 245000, schedulingQueue.getForUnitText(1)->momentUs);
+ schedulingQueue.clear();
+
+ timeNow += 5000;
+ eth.triggerCentral.handleShaftSignal(&configuration, SHAFT_PRIMARY_DOWN, timeNow);
+ timeNow += 5000; // 5ms
+ eth.triggerCentral.handleShaftSignal(&configuration, SHAFT_PRIMARY_UP, timeNow);
+ timeNow += 5000;
+ eth.triggerCentral.handleShaftSignal(&configuration, SHAFT_PRIMARY_DOWN, timeNow);
+ assertEqualsM("index #3", 3, eth.triggerCentral.triggerState.getCurrentIndex());
+ assertEqualsM("queue size 3", 6, schedulingQueue.size());
+ assertEquals(258333, schedulingQueue.getForUnitText(0)->momentUs);
+ assertEquals(257833, schedulingQueue.getForUnitText(1)->momentUs);
+ assertEqualsM("ev 5", 708333, schedulingQueue.getForUnitText(2)->momentUs);
+ assertEqualsM("3/3", 258333, schedulingQueue.getForUnitText(3)->momentUs);
+ schedulingQueue.clear();
+
+ timeNow += 5000;
+ eth.triggerCentral.handleShaftSignal(&configuration, SHAFT_PRIMARY_DOWN, timeNow);
+ timeNow += 5000; // 5ms
+ eth.triggerCentral.handleShaftSignal(&configuration, SHAFT_PRIMARY_UP, timeNow);
+ timeNow += 5000; // 5ms
+ eth.triggerCentral.handleShaftSignal(&configuration, SHAFT_PRIMARY_UP, timeNow);
+ assertEqualsM("index #4", 6, eth.triggerCentral.triggerState.getCurrentIndex());
+ assertEqualsM("queue size 4", 6, schedulingQueue.size());
+ assertEqualsM("4/0", 271666, schedulingQueue.getForUnitText(0)->momentUs);
+ schedulingQueue.clear();
+
+ timeNow += 5000;
+ eth.triggerCentral.handleShaftSignal(&configuration, SHAFT_PRIMARY_DOWN, timeNow);
+ assertEqualsM("queue size 5", 1, schedulingQueue.size());
+ assertEqualsM("5/1", 284500, schedulingQueue.getForUnitText(0)->momentUs);
+ schedulingQueue.clear();
+
+ timeNow += 5000; // 5ms
+ eth.triggerCentral.handleShaftSignal(&configuration, SHAFT_PRIMARY_UP, timeNow);
+ assertEqualsM("queue size 6", 5, schedulingQueue.size());
+ assertEqualsM("6/0", 285000, schedulingQueue.getForUnitText(0)->momentUs);
+ assertEqualsM("6/1", 735000, schedulingQueue.getForUnitText(1)->momentUs);
+ assertEqualsM("6/0", 285000, schedulingQueue.getForUnitText(2)->momentUs);
+ schedulingQueue.clear();
+
+ timeNow += 5000;
+ eth.triggerCentral.handleShaftSignal(&configuration, SHAFT_PRIMARY_DOWN, timeNow);
+ assertEqualsM("queue size 7", 0, schedulingQueue.size());
+ schedulingQueue.clear();
+
+ timeNow += 5000; // 5ms
+ eth.triggerCentral.handleShaftSignal(&configuration, SHAFT_PRIMARY_UP, timeNow);
+ assertEqualsM("queue size 8", 6, schedulingQueue.size());
+ assertEqualsM("8/0", 298333, schedulingQueue.getForUnitText(0)->momentUs);
+ assertEqualsM("8/1", 297833, schedulingQueue.getForUnitText(1)->momentUs);
+ assertEqualsM("8/2", 748333, schedulingQueue.getForUnitText(2)->momentUs);
+ assertEqualsM("8/3", 298333, schedulingQueue.getForUnitText(3)->momentUs);
+ schedulingQueue.clear();
+
+ timeNow += 5000;
+ eth.triggerCentral.handleShaftSignal(&configuration, SHAFT_PRIMARY_DOWN, timeNow);
+ assertEqualsM("queue size 9", 0, schedulingQueue.size());
+ schedulingQueue.clear();
+
+ timeNow += 5000; // 5ms
+ eth.triggerCentral.handleShaftSignal(&configuration, SHAFT_PRIMARY_UP, timeNow);
+ assertEqualsM("queue size 10", 0, schedulingQueue.size());
+ schedulingQueue.clear();
+}
+
+void testTriggerDecoder(void) {
+ printf("*************************************************** testTriggerDecoder\r\n");
+
+ engine_configuration2_s ec2;
+
+ initializeSkippedToothTriggerShapeExt(&ec2.triggerShape, 2, 0, FOUR_STROKE_CAM_SENSOR);
+ assertEqualsM("shape size", ec2.triggerShape.getSize(), 4);
+ assertEquals(ec2.triggerShape.wave.switchTimes[0], 0.25);
+ assertEquals(ec2.triggerShape.wave.switchTimes[1], 0.5);
+ assertEquals(ec2.triggerShape.wave.switchTimes[2], 0.75);
+ assertEquals(ec2.triggerShape.wave.switchTimes[3], 1);
+
+ testDodgeNeonDecoder();
+ testTriggerDecoder2("dodge neon", DODGE_NEON_1995, 8, 0.4931, 0.2070);
+
+ testFordAspire();
+ // lame duty cycle implementation!
+ testTriggerDecoder2("ford aspire", FORD_ASPIRE_1996, 4, 0.5, 0.3802);
+
+ test1995FordInline6TriggerDecoder();
+ testMazdaMianaNbDecoder();
+ testGY6_139QMB();
+ testTriggerDecoder2("testFordEscortGt", FORD_ESCORT_GT, 0, 0.6280, 0);
+ testTriggerDecoder2("testMiniCooper", MINI_COOPER_R50, 121, 0.5222, 0.4959);
+ testTriggerDecoder2("testRoverV8", ROVER_V8, 0, 0.4861, 0);
+ testTriggerDecoder2("testCitroen", CITROEN_TU3JP, 0, 0.4833, 0);
+ testTriggerDecoder2("testAccordCd 3w", HONDA_ACCORD_CD, 12, 0.8146, 0.5000);
+ testTriggerDecoder2("testAccordCd 2w", HONDA_ACCORD_CD_TWO_WIRES, 10, 0.8146, 0.5);
+ testTriggerDecoder2("testAccordCdDip", HONDA_ACCORD_CD_DIP, 27, 0.5000, 0.5000);
+
+ testTriggerDecoder2("testMitsu", MITSU_4G93, 3, 0.3750, 0.3889);
+// testTriggerDecoder2("miata 1990", MIATA_1990, 0, 0.6280, 0.0);
+ testTriggerDecoder2("miata 1994", MIATA_1994, 11, 0.2790, 0.3720);
+
+ testMazda323();
+
+ testRpmCalculator();
+}
diff --git a/unit_tests/test_trigger_decoder.h b/unit_tests/test_trigger_decoder.h
new file mode 100644
index 0000000000..916227865c
--- /dev/null
+++ b/unit_tests/test_trigger_decoder.h
@@ -0,0 +1,13 @@
+/**
+ * @file test_trigger_decoder.h
+ *
+ * @date Dec 24, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#ifndef TEST_TRIGGER_DECODER_H_
+#define TEST_TRIGGER_DECODER_H_
+
+void testTriggerDecoder(void);
+
+#endif /* TEST_TRIGGER_DECODER_H_ */
diff --git a/unit_tests/test_util.cpp b/unit_tests/test_util.cpp
new file mode 100644
index 0000000000..77980e13cd
--- /dev/null
+++ b/unit_tests/test_util.cpp
@@ -0,0 +1,315 @@
+/**
+ * @file test_util.c
+ *
+ * @date Dec 8, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#include
+
+#include "test_util.h"
+#include "cyclic_buffer.h"
+#include "main.h"
+#include "histogram.h"
+
+#include "malfunction_central.h"
+#include "cli_registry.h"
+
+#include "nmea.h"
+#include "efilib2.h"
+#include "crc.h"
+#include "fl_stack.h"
+
+void testCrc(void) {
+ assertEquals(4, efiRound(4.4, 1));
+ assertEquals(1.2, efiRound(1.2345, 0.1));
+
+ print("*************************************** testCrc\r\n");
+
+ const char * A = "A";
+
+ assertEqualsM("crc8", 168, calc_crc((const crc_t *)A, 1));
+ int c = crc32(A, 1);
+ printf("crc32(A)=%x\r\n", c);
+ assertEqualsM("crc32", 0xd3d99e8b, crc32(A, 1));
+}
+
+static cyclic_buffer sb;
+
+void testOverflow64Counter(void) {
+ print("*************************************** testOverflow64Counter\r\n");
+
+ Overflow64Counter o;
+ assertEquals(0, o.get(0, true));
+ assertEquals(10, o.get(10, true));
+
+ assertEquals(20, o.get(20, true));
+
+ // overflow
+ assertEquals(4294967296, o.get(0, true));
+}
+
+void testCyclicBuffer(void) {
+ print("*************************************** testCyclicBuffer\r\n");
+
+ sb.add(10);
+
+ assertEquals(10, sb.sum(3));
+
+ sb.add(2);
+ assertEquals(12, sb.sum(2));
+}
+
+void testHistogram(void) {
+ print("******************************************* testHistogram\r\n");
+
+ initHistogramsModule();
+
+ assertEquals(80, histogramGetIndex(239));
+ assertEquals(223, histogramGetIndex(239239));
+ assertEquals(364, histogramGetIndex(239239239));
+
+ histogram_s h;
+
+ initHistogram(&h, "test");
+
+ int result[5];
+ assertEquals(0, hsReport(&h, result));
+
+ hsAdd(&h, 10);
+ assertEquals(1, hsReport(&h, result));
+ assertEquals(10, result[0]);
+
+ // let's add same value one more time
+ hsAdd(&h, 10);
+ assertEquals(2, hsReport(&h, result));
+ assertEquals(10, result[0]);
+ assertEquals(10, result[1]);
+
+ hsAdd(&h, 10);
+ hsAdd(&h, 10);
+ hsAdd(&h, 10);
+
+ hsAdd(&h, 1000);
+ hsAdd(&h, 100);
+
+ assertEquals(5, hsReport(&h, result));
+
+ assertEquals(5, result[0]);
+ assertEquals(10, result[1]);
+ assertEquals(10, result[2]);
+ assertEquals(100, result[3]);
+ // values are not expected to be exactly the same, it's the shape what matters
+ assertEquals(1011, result[4]);
+}
+
+static void testMalfunctionCentralRemoveNonExistent() {
+ print("******************************************* testMalfunctionCentralRemoveNonExistent\r\n");
+ initMalfunctionCentral();
+
+ // this should not crash
+ removeError(OBD_Engine_Coolant_Temperature_Circuit_Malfunction);
+}
+
+static void testMalfunctionCentralSameElementAgain() {
+ initMalfunctionCentral();
+ print("******************************************* testMalfunctionCentralSameElementAgain\r\n");
+ error_codes_set_s localCopy;
+
+ addError(OBD_Engine_Coolant_Temperature_Circuit_Malfunction);
+ addError(OBD_Engine_Coolant_Temperature_Circuit_Malfunction);
+ getErrorCodes(&localCopy);
+ assertEquals(1, localCopy.count);
+}
+
+static void testMalfunctionCentralRemoveFirstElement() {
+ initMalfunctionCentral();
+ print("******************************************* testMalfunctionCentralRemoveFirstElement\r\n");
+ error_codes_set_s localCopy;
+
+ obd_code_e firstElement = OBD_Engine_Coolant_Temperature_Circuit_Malfunction;
+ addError(firstElement);
+
+ obd_code_e secondElement = OBD_Intake_Air_Temperature_Circuit_Malfunction;
+ addError(secondElement);
+ getErrorCodes(&localCopy);
+ assertEquals(2, localCopy.count);
+
+ // let's remove first element - code
+ removeError(firstElement);
+
+ getErrorCodes(&localCopy);
+ assertEquals(1, localCopy.count);
+ assertEquals(secondElement, localCopy.error_codes[0]);
+}
+
+void testMalfunctionCentral(void) {
+ testMalfunctionCentralRemoveNonExistent();
+ testMalfunctionCentralSameElementAgain();
+ testMalfunctionCentralRemoveFirstElement();
+
+ print("******************************************* testMalfunctionCentral\r\n");
+ initMalfunctionCentral();
+
+ error_codes_set_s localCopy;
+
+ // on start-up error storage should be empty
+ getErrorCodes(&localCopy);
+ assertEquals(0, localCopy.count);
+
+ obd_code_e code = OBD_Engine_Coolant_Temperature_Circuit_Malfunction;
+ // let's add one error and validate
+ addError(code);
+
+ getErrorCodes(&localCopy);
+ assertEqualsM("count #1", 1, localCopy.count);
+ assertEquals(code, localCopy.error_codes[0]);
+
+ // let's remove value which is not in the collection
+ removeError((obd_code_e)22);
+ // element not present - nothing to removed
+ assertEquals(1, localCopy.count);
+ assertEquals(code, localCopy.error_codes[0]);
+
+ code = OBD_Intake_Air_Temperature_Circuit_Malfunction;
+ addError(code);
+ getErrorCodes(&localCopy);
+ // todo: assertEquals(2, localCopy.count);
+
+ for (int code = 0; code < 100; code++) {
+ addError((obd_code_e) code);
+ }
+ getErrorCodes(&localCopy);
+ assertEquals(MAX_ERROR_CODES_COUNT, localCopy.count);
+
+ // now we have full array and code below present
+ removeError(code);
+ getErrorCodes(&localCopy);
+ assertEquals(MAX_ERROR_CODES_COUNT - 1, localCopy.count);
+}
+
+static int lastInteger = -1;
+static int lastInteger2 = -1;
+
+static void testEchoI(int param) {
+ lastInteger = param;
+}
+
+static void testEchoII(int param, int param2) {
+ lastInteger = param;
+ lastInteger2 = param2;
+}
+
+static const char *lastFirst = NULL;
+static const char *lastThird = NULL;
+
+static void testEchoSSS(const char *first, const char *second, const char *third) {
+ lastFirst = first;
+ lastThird = third;
+}
+
+#define UNKNOWN_COMMAND "dfadasdasd"
+
+static loc_t GPSdata;
+
+static char nmeaMessage[1000];
+
+void testGpsParser(void) {
+ print("******************************************* testGpsParser\r\n");
+
+ strcpy(nmeaMessage, "");
+ gps_location(&GPSdata, nmeaMessage);
+
+ // we need to pass a mutable string, not a constant because the parser would be modifying the string
+ strcpy(nmeaMessage, "$GPRMC,173843,A,3349.896,N,11808.521,W,000.0,360.0,230108,013.4,E*69");
+ gps_location(&GPSdata, nmeaMessage);
+ assertEqualsM("1 valid", 4, GPSdata.quality);
+ assertEqualsM("1 latitude", 3349.896, GPSdata.latitude);
+ assertEqualsM("1 longitude", 11808.521, GPSdata.longitude);
+ assertEqualsM("1 speed", 0, GPSdata.speed);
+// assertEqualsM("1 altitude", 0, GPSdata.altitude); // GPRMC not overwrite altitude
+ assertEqualsM("1 course", 360, GPSdata.course);
+
+ strcpy(nmeaMessage, "$GPGGA,111609.14,5001.27,N,3613.06,E,3,08,0.0,10.2,M,0.0,M,0.0,0000*70");
+ gps_location(&GPSdata, nmeaMessage);
+ assertEqualsM("2 valid", 3, GPSdata.quality); // see field details
+ assertEqualsM("2 latitude", 50.0212, GPSdata.latitude);
+ assertEqualsM("2 longitude", 36.2177, GPSdata.longitude);
+ assertEqualsM("2 speed", 0, GPSdata.speed);
+ assertEqualsM("2 altitude", 10.2, GPSdata.altitude);
+// assertEqualsM("2 course", 0, GPSdata.course); // GPGGA not overwrite course
+
+ strcpy(nmeaMessage, "$GPRMC,111609.14,A,5001.27,N,3613.06,E,11.2,0.0,261206,0.0,E*50");
+ gps_location(&GPSdata, nmeaMessage);
+ assertEqualsM("3 valid", 4, GPSdata.quality);
+ assertEqualsM("3 latitude", 5001.27, GPSdata.latitude);
+ assertEqualsM("3 longitude", 3613.06, GPSdata.longitude);
+ assertEqualsM("3 speed", 11.2, GPSdata.speed);
+// assertEqualsM("3 altitude", 0, GPSdata.altitude); // GPRMC not overwrite altitude
+ assertEqualsM("3 course", 0, GPSdata.course);
+ assertEqualsM("3 GPS yy",2006, GPSdata.GPStm.tm_year+1900);
+ assertEqualsM("3 GPS mm",12, GPSdata.GPStm.tm_mon);
+ assertEqualsM("3 GPS yy",26, GPSdata.GPStm.tm_mday);
+ assertEqualsM("3 GPS hh",11, GPSdata.GPStm.tm_hour);
+ assertEqualsM("3 GPS mm",16, GPSdata.GPStm.tm_min);
+ assertEqualsM("3 GPS ss",9, GPSdata.GPStm.tm_sec);
+
+ // check again first one
+ // we need to pass a mutable string, not a constant because the parser would be modifying the string
+ strcpy(nmeaMessage, "$GPRMC,173843,A,3349.896,N,11808.521,W,000.0,360.0,230108,013.4,E*69");
+ gps_location(&GPSdata, nmeaMessage);
+ assertEqualsM("4 valid", 4, GPSdata.quality);
+ assertEqualsM("4 latitude", 3349.896, GPSdata.latitude);
+ assertEqualsM("4 longitude", 11808.521, GPSdata.longitude);
+ assertEqualsM("4 speed", 0, GPSdata.speed);
+ assertEqualsM("4 course", 360, GPSdata.course);
+}
+
+// this buffer is needed because on Unix you would not be able to change static char constants
+static char buffer[300];
+
+void testConsoleLogic(void) {
+ print("******************************************* testConsoleLogic\r\n");
+ resetConsoleActions();
+
+ helpCommand();
+
+ char *ptr = validateSecureLine(UNKNOWN_COMMAND);
+ assertEquals(0, strcmp(UNKNOWN_COMMAND, ptr));
+ assertEquals(10, tokenLength(UNKNOWN_COMMAND));
+
+ // handling invalid token should work
+ strcpy(buffer, "sdasdafasd asd");
+ handleConsoleLine(buffer);
+
+ print("\r\naddConsoleActionI\r\n");
+ addConsoleActionI("echoi", testEchoI);
+ strcpy(buffer, "echoi 239");
+ handleConsoleLine(buffer);
+ assertEquals(239, lastInteger);
+
+ print("\r\naddConsoleActionII\r\n");
+ addConsoleActionII("echoii", testEchoII);
+ strcpy(buffer, "echoii 22 239");
+ handleConsoleLine(buffer);
+ assertEquals(22, lastInteger);
+ assertEquals(239, lastInteger2);
+
+ print("\r\addConsoleActionSSS\r\n");
+ addConsoleActionSSS("echosss", testEchoSSS);
+ strcpy(buffer, "echosss 111 222 333");
+ handleConsoleLine(buffer);
+ assertEquals(111, atoi(lastFirst));
+ assertEquals(333, atoi(lastThird));
+
+ //addConsoleActionSSS("GPS", testGpsParser);
+}
+
+void testFLStack(void) {
+ print("******************************************* testFLStack\r\n");
+
+ FLStack stack;
+ assertEquals(0, stack.size());
+
+
+}
diff --git a/unit_tests/test_util.h b/unit_tests/test_util.h
new file mode 100644
index 0000000000..72d24a8ff9
--- /dev/null
+++ b/unit_tests/test_util.h
@@ -0,0 +1,30 @@
+/**
+ * @file test_cyclic_buffer.h
+ *
+ * @date Dec 8, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#ifndef TEST_CYCLIC_BUFFER_H_
+#define TEST_CYCLIC_BUFFER_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void testCrc(void);
+void testCyclicBuffer(void);
+void testOverflow64Counter(void);
+void testHistogram(void);
+void testMalfunctionCentral(void);
+void testConsoleLogic(void);
+void testGpsParser(void);
+void testFLStack(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+
+#endif /* TEST_CYCLIC_BUFFER_H_ */