Compare commits
205 Commits
45c0625864
...
8576bf4231
Author | SHA1 | Date | |
---|---|---|---|
8576bf4231 | |||
6ebd74cb31 | |||
46125ba752 | |||
6c9f90c986 | |||
74defd5384 | |||
5deac33949 | |||
666353e3b7 | |||
df40ab1be7 | |||
4c3574c2e2 | |||
289f49204d | |||
4bc98e6baf | |||
e50ce0d541 | |||
78b63c853f | |||
a25b249d77 | |||
e815442617 | |||
60104df30e | |||
5ab911b4b6 | |||
9c37e083a0 | |||
08427cc589 | |||
62d745bd3a | |||
ba6e0880b4 | |||
ec2c23c9f7 | |||
6a71416d2a | |||
1ecd5edd93 | |||
1b4eba1871 | |||
13e7cbfd81 | |||
857838f293 | |||
ae97a69d26 | |||
001f2be176 | |||
1797dac60b | |||
e3e4a6d926 | |||
dca696cd6a | |||
93ff4959a2 | |||
b1a7af28a2 | |||
b560a91673 | |||
64a97fa048 | |||
d1b8e91674 | |||
0fc35db53d | |||
8a764f599b | |||
b9dfe35652 | |||
694a78982a | |||
59ff842f56 | |||
7c5b60ec6b | |||
e614eaa23b | |||
50ad31d58a | |||
64bb06882f | |||
0b01be9840 | |||
ba9247be69 | |||
e7aa714976 | |||
125b41de44 | |||
4c1797aa24 | |||
20a65fd7f9 | |||
67b079fe33 | |||
a5c9350835 | |||
cc545bfc69 | |||
f6fb541924 | |||
86a0b2087e | |||
55f35a5009 | |||
870c228e37 | |||
2fcc5d6a9f | |||
52272938b7 | |||
58c72fb2bc | |||
99d96fb426 | |||
24651fa74d | |||
56b5ddc209 | |||
94726e1466 | |||
d6356de1b6 | |||
69ff13a991 | |||
bbfcd429fe | |||
3a07347f48 | |||
0e233a257c | |||
95bd606dd8 | |||
6e3f90d38e | |||
82cab98f59 | |||
2d29857c2f | |||
bea8e2df74 | |||
7c6205d20a | |||
0e114d1344 | |||
2c3c1c9861 | |||
75f9c58c54 | |||
44c861a245 | |||
c01b7a9825 | |||
87c372d871 | |||
3125e34922 | |||
fed18f1c24 | |||
caabde39d2 | |||
1167358c5a | |||
444fded972 | |||
963b1e106c | |||
0d861b1aed | |||
9226f43cad | |||
2ff2d963c8 | |||
d353183826 | |||
a3e652ddb8 | |||
c67298118e | |||
abb333cfe7 | |||
5a41e7815f | |||
8a8004e187 | |||
b1271cee43 | |||
daaf848e0c | |||
ef8e6231ff | |||
cd35f9e694 | |||
91d9db6a4e | |||
898feac168 | |||
0b1d872ed0 | |||
687c72bde7 | |||
b6d4a2e35e | |||
a474111f36 | |||
ed4c18f2be | |||
f4d6f5a1ae | |||
32da2a5fa6 | |||
ec66814184 | |||
37c68809d1 | |||
67d59928c9 | |||
37bc397e9a | |||
d176389711 | |||
e60da5b23f | |||
8603f84142 | |||
d51c73d694 | |||
8f6462bcc9 | |||
c02d988350 | |||
11b84afca0 | |||
a858223c35 | |||
f2405e23b4 | |||
2e640fa7fa | |||
1a3889b72d | |||
a7e376deab | |||
bb39c4b6e0 | |||
7b1ae3bdc6 | |||
e37001e3c4 | |||
09ea84beaf | |||
dc9fc2f814 | |||
32c8e2e937 | |||
88360fe307 | |||
57308e18e3 | |||
731cb4fec3 | |||
8ff276e30d | |||
1166477a6c | |||
7aa0b62012 | |||
10596cdbf0 | |||
fc6fb1aee0 | |||
1eeaf3d892 | |||
a3778fcb6e | |||
86f153bf69 | |||
3ca5e41602 | |||
dcec366b0a | |||
14b7bdbf19 | |||
af9845cbba | |||
034ecaa60f | |||
ae91affc30 | |||
5fd2db319d | |||
eec15df271 | |||
84c747c81c | |||
b869ed9c45 | |||
42f6d0270d | |||
e8f59b6dc6 | |||
d91a1b1da0 | |||
004be4ea5c | |||
a838bf3af8 | |||
1c1d1c4c97 | |||
e0f61af709 | |||
b619fc5600 | |||
2f6590416d | |||
a877ef5f28 | |||
af555aba21 | |||
dd0ee47d86 | |||
452abfdd5c | |||
ba41c0911d | |||
cbbd97e1bd | |||
ab8228f712 | |||
20e2a2b84b | |||
039a35522e | |||
72668472a0 | |||
569d42bbe9 | |||
c7ebe441c7 | |||
403786e0c6 | |||
192bcf01f6 | |||
9880c701b1 | |||
910037a562 | |||
6232e2f330 | |||
7ea0e73869 | |||
c4fe006efa | |||
b2b1702670 | |||
1f8a6347e9 | |||
d0cf95db49 | |||
331b049868 | |||
e50602611c | |||
95382d9ab8 | |||
e96a710576 | |||
2673112a9c | |||
b8b8e19206 | |||
325fb24ed8 | |||
ea26f56545 | |||
77c88c69cd | |||
e85a85d9c3 | |||
3df0631ffc | |||
7434554319 | |||
c9a5a2c2ff | |||
04008a07c0 | |||
928dbfb9f3 | |||
0f0afcf359 | |||
5d437f3a9f | |||
cb3b42aece | |||
a12648ff7a | |||
d3c4e1bffc |
4
.gitmodules
vendored
4
.gitmodules
vendored
@ -6,3 +6,7 @@
|
||||
url = https://git.shimatta.de/mhu/c-style-checker.git
|
||||
branch = master
|
||||
|
||||
[submodule "stm-firmware/base64-lib"]
|
||||
path = stm-firmware/base64-lib
|
||||
url = https://git.shimatta.de/mhu/base64-lib.git
|
||||
branch = master
|
||||
|
2
doc/.gitignore
vendored
2
doc/.gitignore
vendored
@ -0,0 +1,2 @@
|
||||
build/*
|
||||
venv
|
21
doc/make-with-virtualenv.sh
Executable file
21
doc/make-with-virtualenv.sh
Executable file
@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
SOURCE="${BASH_SOURCE[0]}"
|
||||
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
|
||||
DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
|
||||
SOURCE="$(readlink "$SOURCE")"
|
||||
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
|
||||
done
|
||||
DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
|
||||
|
||||
cd "$DIR"
|
||||
|
||||
if [[ ! -d "venv" ]]; then
|
||||
virtualenv venv
|
||||
source ./venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
else
|
||||
source ./venv/bin/activate
|
||||
fi
|
||||
|
||||
make SPHINXBUILD='venv/bin/python venv/bin/sphinx-build' $@
|
32
doc/requirements.txt
Normal file
32
doc/requirements.txt
Normal file
@ -0,0 +1,32 @@
|
||||
alabaster==0.7.12
|
||||
Babel==2.9.0
|
||||
blockdiag==2.0.1
|
||||
breathe==4.24.1
|
||||
certifi==2020.11.8
|
||||
chardet==3.0.4
|
||||
docutils==0.16
|
||||
funcparserlib==0.3.6
|
||||
idna==2.10
|
||||
imagesize==1.2.0
|
||||
Jinja2==2.11.2
|
||||
MarkupSafe==1.1.1
|
||||
packaging==20.7
|
||||
Pillow==8.0.1
|
||||
Pygments==2.7.2
|
||||
pyparsing==2.4.7
|
||||
pytz==2020.4
|
||||
requests==2.25.0
|
||||
six==1.15.0
|
||||
snowballstemmer==2.0.0
|
||||
Sphinx==3.3.1
|
||||
sphinx-rtd-theme==0.5.0
|
||||
sphinxcontrib-applehelp==1.0.2
|
||||
sphinxcontrib-blockdiag==2.0.0
|
||||
sphinxcontrib-devhelp==1.0.2
|
||||
sphinxcontrib-drawio==0.0.12
|
||||
sphinxcontrib-htmlhelp==1.0.3
|
||||
sphinxcontrib-jsmath==1.0.1
|
||||
sphinxcontrib-qthelp==1.0.3
|
||||
sphinxcontrib-serializinghtml==1.1.4
|
||||
urllib3==1.26.2
|
||||
webcolors==1.11.1
|
2
doc/source/.gitignore
vendored
Normal file
2
doc/source/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
.drawio/*
|
||||
.drawio
|
@ -13,6 +13,10 @@
|
||||
# TAG += value [value, ...]
|
||||
# Values that contain spaces should be placed between quotes (\" \").
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Project related configuration options
|
||||
#---------------------------------------------------------------------------
|
||||
@ -869,34 +873,7 @@ FILE_PATTERNS = *.c \
|
||||
*.hxx \
|
||||
*.hpp \
|
||||
*.h++ \
|
||||
*.cs \
|
||||
*.d \
|
||||
*.php \
|
||||
*.php4 \
|
||||
*.php5 \
|
||||
*.phtml \
|
||||
*.inc \
|
||||
*.m \
|
||||
*.markdown \
|
||||
*.md \
|
||||
*.mm \
|
||||
*.dox \
|
||||
*.doc \
|
||||
*.txt \
|
||||
*.py \
|
||||
*.pyw \
|
||||
*.f90 \
|
||||
*.f95 \
|
||||
*.f03 \
|
||||
*.f08 \
|
||||
*.f18 \
|
||||
*.f \
|
||||
*.for \
|
||||
*.vhd \
|
||||
*.vhdl \
|
||||
*.ucf \
|
||||
*.qsf \
|
||||
*.ice
|
||||
*.dox
|
||||
|
||||
# The RECURSIVE tag can be used to specify whether or not subdirectories should
|
||||
# be searched for input files as well.
|
||||
@ -911,10 +888,7 @@ RECURSIVE = YES
|
||||
# Note that relative paths are relative to the directory from which doxygen is
|
||||
# run.
|
||||
|
||||
EXCLUDE = ../../stm-firmware/include/stm32 \
|
||||
../../stm-firmware/include/cmsis \
|
||||
../../stm-firmware/shellmatta/test \
|
||||
../../stm-firmware/shellmatta/example
|
||||
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
|
||||
@ -930,7 +904,12 @@ EXCLUDE_SYMLINKS = NO
|
||||
# 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 =
|
||||
EXCLUDE_PATTERNS = */include/stm32/* \
|
||||
*/include/cmsis/* \
|
||||
*/shellmatta/test/* \
|
||||
*/shellmatta/example/* \
|
||||
*/base64-lib/test/* \
|
||||
*/updater/ram-code/*
|
||||
|
||||
# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
|
||||
# (namespaces, classes, functions, etc.) that should be excluded from the
|
||||
@ -2138,7 +2117,7 @@ ENABLE_PREPROCESSING = YES
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
|
||||
|
||||
MACRO_EXPANSION = NO
|
||||
MACRO_EXPANSION = YES
|
||||
|
||||
# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
|
||||
# the macro expansion is limited to the macros specified with the PREDEFINED and
|
||||
@ -2146,7 +2125,7 @@ MACRO_EXPANSION = NO
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
|
||||
|
||||
EXPAND_ONLY_PREDEF = NO
|
||||
EXPAND_ONLY_PREDEF = YES
|
||||
|
||||
# If the SEARCH_INCLUDES tag is set to YES, the include files in the
|
||||
# INCLUDE_PATH will be searched if a #include is found.
|
||||
@ -2178,7 +2157,8 @@ INCLUDE_FILE_PATTERNS =
|
||||
# recursively expanded use the := operator instead of the = operator.
|
||||
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
|
||||
|
||||
PREDEFINED =
|
||||
PREDEFINED = __atribute(x)__= \
|
||||
IN_SECTION(x)=
|
||||
|
||||
# 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
|
||||
|
@ -32,6 +32,7 @@ try:
|
||||
os.mkdir('../build/_doxygen')
|
||||
except FileExistsError:
|
||||
pass
|
||||
|
||||
subprocess.call('doxygen Doxyfile.in', shell=True)
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
@ -44,6 +45,7 @@ extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx.ext.todo',
|
||||
'sphinxcontrib.blockdiag',
|
||||
'sphinxcontrib.drawio',
|
||||
'breathe'
|
||||
]
|
||||
|
||||
|
@ -1,9 +0,0 @@
|
||||
.. _safety_handling:
|
||||
|
||||
Error Handling
|
||||
==============
|
||||
|
||||
.. _safety_panic:
|
||||
|
||||
Panic Mode
|
||||
----------
|
@ -1,75 +0,0 @@
|
||||
.. _safety_flags:
|
||||
|
||||
Safety Flags
|
||||
============
|
||||
|
||||
The safety flags are represented in software by the following enums
|
||||
|
||||
.. doxygenenum:: safety_flag
|
||||
|
||||
The safety flags can be temporarily or permanent. Some temporary flags are reset automatically, once the error condition disappears. Others have to be explicitly cleared.
|
||||
|
||||
----------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
.. _safety_flags_adc_overflow:
|
||||
|
||||
ERR_FLAG_MEAS_ADC_OVERFLOW
|
||||
--------------------------
|
||||
|
||||
``ERR_FLAG_MEAS_ADC_OVERFLOW`` is triggered in case of an overflow in the signal path of the measurement ADC. This should never happen unless there is a bug in the software.
|
||||
|
||||
========== ============= ============= ===========
|
||||
persistent self-clearing Stops PID Panic Mode
|
||||
========== ============= ============= ===========
|
||||
yes no yes no
|
||||
========== ============= ============= ===========
|
||||
|
||||
----------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
.. _safety_flags_adc_off:
|
||||
|
||||
ERR_FLAG_MEAS_ADC_OFF
|
||||
---------------------
|
||||
|
||||
``ERR_FLAG_MEAS_ADC_OFF`` signals that the measurement ADC for the PT1000 sensor is deactivated. This flag is automatically cleared by the firmware
|
||||
once the ADC is started.
|
||||
|
||||
========== ============= ============= ===========
|
||||
persistent self-clearing Stops PID Panic Mode
|
||||
========== ============= ============= ===========
|
||||
no yes yes no
|
||||
========== ============= ============= ===========
|
||||
|
||||
----------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
.. _safety_flags_adc_watchdog:
|
||||
|
||||
ERR_FLAG_MEAS_ADC_WATCHDOG
|
||||
--------------------------
|
||||
|
||||
``ERR_FLAG_MEAS_ADC_WATCHDOG`` is used as a wire break detection mechanism. This flag is set when the PT1000 measurement ADC detects an invalid resistance measurement.
|
||||
|
||||
.. seealso:: :ref:`ADC Watchdog<firmware_meas_adc_watchdog>`
|
||||
|
||||
========== ============= ============= ===========
|
||||
persistent self-clearing Stops PID Panic Mode
|
||||
========== ============= ============= ===========
|
||||
no no yes no
|
||||
========== ============= ============= ===========
|
||||
|
||||
----------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
.. _safety_flags_adc_unstable:
|
||||
|
||||
ERR_FLAG_MEAS_ADC_UNSTABLE
|
||||
--------------------------
|
||||
|
||||
``ERR_FLAG_MEAS_ADC_UNSTABLE`` is set after startup of the PT1000 measuremnt or after reconfiguring the filter settings.
|
||||
|
||||
.. seealso:: :ref:`firmware_meas_adc_filter`
|
||||
|
||||
========== ============= ============= ===========
|
||||
persistent self-clearing Stops PID Panic Mode
|
||||
========== ============= ============= ===========
|
||||
no yes no no
|
||||
========== ============= ============= ===========
|
@ -11,6 +11,6 @@ mechanisms and the behavior. For a detailed code documentation see the doxygen o
|
||||
:maxdepth: 2
|
||||
|
||||
pt1000-processing
|
||||
safety
|
||||
safety/index
|
||||
code/index
|
||||
|
||||
|
@ -12,30 +12,7 @@ PT1000 Value Sampling
|
||||
|
||||
The following block diagram shows the processing chain of the temperature signal.
|
||||
|
||||
.. blockdiag::
|
||||
:desctable:
|
||||
|
||||
blockdiag {
|
||||
orientation = portrait;
|
||||
|
||||
FRONTEND[description=":ref:`hw_analog_fe`", label="Frontend"];
|
||||
ADC[description="`Analog to Digital Converter <ADC_>`_"];
|
||||
WATCHDOG [label = "WDT", shape=endpoint, description="`Hardware Value Watchdog <Watchdog_>`_"];
|
||||
PREFILTER [label=Prefilter, description="`Prefiltering and Downsampling <Prefilter_>`_"];
|
||||
ADC2RES [label= "Val -> Ohm", description="`Conversion from ADC value to resistance in Ohms <ADC Value to Ohm_>`_"]
|
||||
MAVG [label="MAVG Filter", description="`Exponential Moving Average Filter`_"];
|
||||
RAW_HF [label="HF", shape = endpoint, description="High Frequency raw value reading"];
|
||||
PT1000 [label = "LF", shape = endpoint, description="Low Frequency PT1000 resistance value (see: `MAVG Filter <Exponential Moving Average Filter_>`_)"]
|
||||
RAW_STREAM [label = "Stream", shape = endpoint, description="Raw value streaming"];
|
||||
|
||||
FRONTEND -> ADC -> WATCHDOG;
|
||||
ADC -> PREFILTER [label="1 kHz"];
|
||||
PREFILTER -> ADC2RES [label="1/6 kHz"];
|
||||
ADC2RES -> MAVG;
|
||||
MAVG -> PT1000 [label="1/6 kHz"];
|
||||
PREFILTER -> RAW_HF [label="1/6 kHz"];
|
||||
PREFILTER -> RAW_STREAM [label="1/6 kHz"];
|
||||
}
|
||||
.. drawio-image:: pt1000.drawio
|
||||
|
||||
ADC
|
||||
~~~
|
||||
@ -110,9 +87,16 @@ and can be changed in code using
|
||||
|
||||
.. doxygenfunction:: adc_pt1000_set_moving_average_filter_param
|
||||
|
||||
After initial startup and after each change of the filter constant, the filter will set the :ref:`safety_flags_adc_unstable` flag for a defined sample count of:
|
||||
The moving average filter is considered unstable, if the input to output difference is greater than
|
||||
|
||||
.. doxygendefine:: ADC_FILTER_STARTUP_CYCLES
|
||||
.. doxygendefine:: ADC_PT1000_FILTER_UNSTABLE_DIFF
|
||||
|
||||
In this case, the :ref:`safety_flags_adc_unstable` flag will be set until the filter output converges within the range for at least
|
||||
|
||||
.. doxygendefine:: ADC_PT1000_FILTER_STABLE_SAMPLE_COUNT
|
||||
|
||||
samples.
|
||||
If the input value keeps changing or oscillating, the error flag will be permanently set.
|
||||
|
||||
The moving average filter's output signal is the Low Frequency (LF) PT1000 resistance signal used for internal PT1000 measurements.
|
||||
|
||||
@ -132,7 +116,7 @@ and
|
||||
.. doxygenfunction:: adc_pt1000_get_resistance_calibration
|
||||
:outline:
|
||||
|
||||
are used to set the reistance calibration internally. For a guide on how to calibrate the deivce, see the corresponding :ref:`usage_calibration` usage page.
|
||||
are used to set the resistance calibration internally. For a guide on how to calibrate the deivce, see the corresponding :ref:`usage_calibration` usage page.
|
||||
|
||||
The calibration is calculated the following way:
|
||||
|
||||
@ -147,13 +131,13 @@ The calibration is calculated the following way:
|
||||
OFFSET [label="Offset", description="Offset Correction :math:`O`"];
|
||||
OUT [shape=endpoint, description="Corrected Value"];
|
||||
|
||||
LF -> SENS -> OFFSET -> OUT
|
||||
LF -> OFFSET -> SENS -> OUT
|
||||
}
|
||||
|
||||
The final calibrated PT1000 resistance is calculated as:
|
||||
|
||||
.. math::
|
||||
R_{PT1000_{corr}} = R_{PT1000_{LF}} \cdot (1 + \sigma) + O
|
||||
R_{PT1000_{corr}} = (R_{PT1000_{LF}} - O) \cdot (1 + \sigma)
|
||||
|
||||
The default values, if no calibration is loaded / executed, are:
|
||||
|
||||
|
1
doc/source/firmware/pt1000.drawio
Normal file
1
doc/source/firmware/pt1000.drawio
Normal file
@ -0,0 +1 @@
|
||||
<mxfile host="Electron" modified="2021-01-26T21:12:06.071Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/14.1.8 Chrome/87.0.4280.88 Electron/11.1.1 Safari/537.36" etag="PrKjQ8DyGBUrBwv3aoCi" version="14.1.8" type="device"><diagram name="Page-1" id="099fd60b-425b-db50-ffb0-6b813814b670">7Vxbd6M4Ev41Pmf3IT7cL49ObHfmTHqS7cx07+yLj4xlWx2MHJAvya9fCQQGJGPHBl/S0w9pKGQJqr66qKqgpd/N1l9CMJ9+xSPotzRltG7p3ZamuY5K/zLCW0JQLUtPKJMQjThtQ3hG75ATFU5doBGMCgMJxj5B8yLRw0EAPVKggTDEq+KwMfaLq87BBAqEZw/4IvUHGpFpQnVMZUO/h2gyTVdWFX5lCLyXSYgXAV+vpenj+F9yeQbSufj4aApGeJUj6b2WfhdiTJKj2foO+oy3KduS3/W3XM3uO4QB2ecH8N5wZ2TQ/+PnqoPhj3Xn0fp5o2r85shbyhA4ovzhpzgkUzzBAfB7G+pt/NCQTavQs82YB4znlKhS4k9IyBsXNlgQTElTMvP5VbhG5L/85+z4b3bcNvlZd5271H3LnTzBEM0ggSGnRSTEL/AO+ziM7113tKFuWWx0QMK3bAl2kluDnW4Wic/SVcbI93PzjUzojAxKF5nN+R/hRehxzv3UX5V38HU98L4twG/6+rszgTdcGwgIJ5BUSMJOxjHu5xbgovwCMX3u8I0OCKEPCFoW0Qu4EkyycdlPnzCit6wpXGF1i8Mx1dcU6OkUyY3yX23gRA9yt7EhxSD7CODsawbcobCKz/ZH7/lQqFp1w7AAoAq0VN32EvgLvlKnS2eyfPoMt8OQHk1IxpccpJYwJIja+AcwhP4TjhBBOKCXhpgQPGO8T40xE8QIRNMMXcBHEzbUozxmosrDKJ22w8cQBj8215ytO1tPmItsQ5/6qZCNa4MhFTLwSHsEvBjHBPA7iddFIR2ZnK9gxJ6EzjBCdOWueKkECqhSWNgyGLmWrQMrN1nuWtfsOd1KKLFnhOtK2fOrmlk0JS5X7dXGjdp8xDTnQQ2rIbCo5uWYFkc/xri03RrMCwxGHRYf0dMABzCh9BHjqczV7YGnw82PIZof6bjLsj6pMbwEQKnWIYCqRkCt3uyXgJM8pnJKIZW9X0hFZQPecsPmbEC0fRlXHrn19xxuKyWFSNY/NLqTa4xzTo1RCxpzhAFWf1Hzq1+U+TWE4K+LxmNIH58+rKZ0ZnMfjREMxXjwC0ABHRH/SEkTDZccHtK4cMBmGPtofp8ChB5/5zPXCJb94ztVLVoQ2xTiuywZkg/wXKUhQJgCIATJxjYUhr0lZKa0KJZzSzui44A/SLQ1anuLkAlnkGrveYSslbyEIsjYkIjYakrE1f6DG9KLSQec0oafKJYxaKhpaI5p879qAR4ZPurPFlU99LVqfAgjFBEcRu0lCBEY+nCQkmJzn08ABAzHYo7gPJa/ZPhFo2BJjELmDGq3Cu5uGOTCLc8HUYS8ouy2cmWnQuWe2pQ8dUo7Ni/rtm1Lyf0zi4pXYm1iLgS9++i0zSV/5duDq642HG27ry39Iaqd1tYogJT7d/rn4eli42ofT5A3mABCwywfr+bUHgyoIU0mPo9FdXfmSk1NNC515EqleX79WhRxWylke/FkLzWVFz/2VNPat8lbbLdSwsyJK3WC9j8DuuVmW+8/qV2km23lX3SM8nL//m8BTTmpF9R8NUVUKecglsOK6m29It5fIXWjyFxNjHEynS1oZA0xjpTdZ801H5s5+3gpM8u13ShtRc0oyQ0Yrn5Iyu1wzZckyKTjNPc0qm+XInC1HP/VlFK2DUO6Tl1J4qGrWl9ty/vr5vX19jnovg6Wy5uzdgAcnSP2h3EAk8YkeSjnMsV/t3JZ5CPKKHsbwxIia1MDqQT1RkBv2UXQ66f1d2K6ubee44AyEVExCznmr3iJggnLQ1MosB4zTeknIeZniIpP44Qt3bqoqFg1zmKaDizDNtQ/VNWdtrt/6GwlpKrbzvcPUclgprbfY5qm/ADEm44Y6epD2BS++RhWlr0vZ33q0x+xX6v37ZvA2dRG0RnRPIJFbdjJ63nOY/IZck60LIrxeKx5nkwUI2tomTWJwilt1WTbCd0RJZHSapeEVh1kxVHMRcZXpzRq9Tc7busPKe81m4nmjXIjilVvNC/PJKmfDmlnaR6vROhOv5vGyk1D2TQP25jWlpQSK4JPIRz/E3h/MHRw3csKvCUVPkPh2UVBrvQhSVEkgrjKMpqh0SgxPzBC76wSywXNbSqd17xtmV02F7U4UWJ8KguHH+C2vjP5L6unllWrvuBA2cLtvvVJOG5YOzkua01vjuOS1qXPxfFyxvLsHHcEjt/36XkXEED/a7F90eUI4CI2LVm9I79/1E4qNNENPPwjtGqhSTr2Tis0vbqWfCEbgCt4ca8ysN+5A+C5nwtJvOknzz/k64l2qYHfcM8s5t37N7Nu8W2psVjFBrDUTe/oK/toJkJYpvpVlfJwp3q4WSoUWW4Jr02kOUTX1A/ha5uS7qbQe5HCPd54NuePRLhV6qLgZLKPKvBVWvkPE8icD9UtzdRLJePk7Ni8gt5WiltStW0UJ8HjcQSPTSpIy5lXVfW5vKqxzEU1Vzb+sCkqv+FQ/kzGjvHp4zVrXcQdeVqfumlpFpixDBN/1V15nM4EwBbhGJdNnotlkxz0zpJyMktJkKxbPBe4OpK4tbGGK10s+GfFwU1RUOn7QKwNfv59hl1K9GqmI4hLWlxsbp8h5lB+zeKiKSqOrLbYnOKIOfj4qxysLTRpiflVdaYUxuqSrbnakMr8rvXu4X/Cx5fftYe38Y8/jf8tHUkLb+ePzsPjlwr3oX7YfZymgb7UP6/tm6uq401FKW81gbddNEGEtYopbGZNecZjsqLPXCu3T1Qfss0LY7fYWfK4IPMFC9zS7CBjerlLrxeGlD+JRYpqFcSJTIrQE3lCQUiDeIlNiY3/X0GU7CNT88+j1mAYzTNeXJs7kGyZBXFWuIPSTkLyHmVjqVqp7ESbdTUR1FGScJTia8w3JwyhpIK46s+knCrZcMLW83KIfaO5bce1TFO1NUPVbVcpNerV1+ay/SmrX3i+lOaWzSfuoE/98OHwOErDs6/OpgI0xI+V6Jqo4eV3DGrTcDFd/FuQhCtpYueZukvkI8KeokvVK/kQwFndZA2CMJ1i5Ch1ek11AsgDFjG3FueelNad1ro1O/ToLvkUy/Uzf/f+U/bBngNYT083Xy5OrN7m89B67/8=</diagram></mxfile>
|
112
doc/source/firmware/safety/backup-ram.rst
Normal file
112
doc/source/firmware/safety/backup-ram.rst
Normal file
@ -0,0 +1,112 @@
|
||||
.. _backup_ram:
|
||||
|
||||
Safety Backup RAM
|
||||
=================
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
The STM controller's backup RAM is used to store different kinds of information that shall be preserved if the controller resets.
|
||||
The hardware setup is missing a separate powersupply for the controller's backup domain. Therefore, the backup RAM is cleared, when the power is cut.
|
||||
|
||||
The backup RAM is used to store permanent error flags (See :ref:`safety_flags`). This ensures the flags that trigger hard faults / the panic mode, can be identified, although the wathcoog resets the controller. The only way to clear them is by cutting the power.
|
||||
Because cutting the power is a way to clear the backup RAM, no separate method for clearing the error entries in the backup RAM is defined.
|
||||
|
||||
The backup RAM contents are protected by a `CRC Checksum`_.
|
||||
|
||||
The backup RAM is initialized and checked after boot. If the controller starts from a powered down state,
|
||||
the backup RAM is empty. This is detected by an invalid `Header`_ at the beginning of the backup RAM. If this is the case, the safety ocntoller
|
||||
will create a valid backup RAM image with a `Header`_, empty `Boot Status Flag Entries`_, empty `Config Overrides`_, an empty `Error Memory`_, and a valid `CRC Checksum`_.
|
||||
|
||||
If the Header is valid during boot (verified by plausible values and correct magic numbers), the backup RAM is CRC checked and the error memory is
|
||||
checked for valid entries.
|
||||
In case of a CRC error or invalid entries in the error memory, the Backup RAM is wiped and reinitialized. On top of that, the error flag :ref:`safety_flags_safety_mem_corrupt` is set.
|
||||
|
||||
.. note:: It may be possible that future versions of the hardware include a backup RAM battery / Goldcap. In this case, a way to clear the error memory will be implemented,
|
||||
because it will no longer be possible to clear the error memory by cutting the power.
|
||||
On top of that, the backup memory will also contain the calibration data.
|
||||
|
||||
.. note:: The firmware will not use the ``NOP`` entries of the error memory by default, but they will be respected by the validity checker.
|
||||
|
||||
Partitioning and Entries
|
||||
------------------------
|
||||
|
||||
The backup RAM consists of multiple sections. The memory section are listed below.
|
||||
|
||||
Header
|
||||
~~~~~~
|
||||
|
||||
The backup memory header is located at offset address:
|
||||
|
||||
.. doxygendefine:: SAFETY_MEMORY_HEADER_ADDRESS
|
||||
|
||||
The header is defined by the following structure:
|
||||
|
||||
.. doxygenstruct:: safety_memory_header
|
||||
|
||||
The validity of the header is checked, if the magic and inverse amgic fields contain the correct values, and if the offset address pointers
|
||||
have values that are located inside the error memory and are not ``0`` or the same value.
|
||||
|
||||
The safety memory header magic is:
|
||||
|
||||
.. doxygendefine:: SAFETY_MEMORY_MAGIC
|
||||
|
||||
|
||||
.. _backup_ram_boot_flags:
|
||||
|
||||
Boot Status Flag Entries
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The boot status flag entries are use to store system states over resets.
|
||||
The flags are stored in memory using the follwoing structure:
|
||||
|
||||
.. doxygenstruct:: safety_memory_boot_status
|
||||
|
||||
Flags are evaluated active, if the corresponding word is unequal to ``0``.
|
||||
|
||||
Config Overrides
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Config overrides are used to override persistance and flag weights dynamically. The safety controller will parse the entries on
|
||||
startup.
|
||||
|
||||
======================= ============ ================= ===================== =====================================
|
||||
Entry Byte 1 (LSB) Byte 2 Byte 3 Byte 4 (MSB)
|
||||
======================= ============ ================= ===================== =====================================
|
||||
Weight override ``0xA2`` ``Weight`` ``Flag Number`` reserved don't care (written as 0xAA)
|
||||
Persistance override ``0x8E`` ``Persistance`` ``Flag Number`` reserved don't care (written as 0xBB)
|
||||
======================= ============ ================= ===================== =====================================
|
||||
|
||||
All words, not matching the table above are ignored and do not cause an error. By default the firmware fills this memory area with zeroes.
|
||||
|
||||
Error Memory
|
||||
~~~~~~~~~~~~
|
||||
|
||||
The error memory contains error entries in form of 32 bit words. The entries are coded as stated below.
|
||||
|
||||
``Error Flag`` entries are used to restore error flags after boot. In theory, all flags can be set using this entry type.
|
||||
However, only persistent flags are stored in the error memory by the firmware.
|
||||
|
||||
``NOP`` entries have no meaning. They are used as a filler. When adding a new error memory entry, the error memory is scanned until the first ``NOP`` entry is found.
|
||||
It is replaced with a valid entry. If the error memory contains a word, that is not defined below, it is considered invalid and will trigger the RAM checker on boot.
|
||||
``NOP`` entries can be used to preallocate the error memory in advance. if the end of the error memory is reached, it is expanded by 1 word to first
|
||||
the new error entry, until the backup RAM is full. After this, no further errors are stored.
|
||||
|
||||
If the same persistent error is triggered mutliple times, the ``COUNTER`` in the error entry is incremented.
|
||||
|
||||
======================= ============ ================= ===================== =====================================
|
||||
Entry Byte 1 (LSB) Byte 2 Byte 3 Byte 4 (MSB)
|
||||
======================= ============ ================= ===================== =====================================
|
||||
Error Flag ``0x51`` ``Flag Number`` ``COUNTER 7:0`` ``COUNTER 15:8``
|
||||
NOP Entry ``0x22`` ``0x12`` ``0xAA`` ``0xC1``
|
||||
======================= ============ ================= ===================== =====================================
|
||||
|
||||
CRC Checksum
|
||||
~~~~~~~~~~~~
|
||||
|
||||
The CRC checksum is located after the error memory. The checksum is calculated by the internal peripheral module of the STM32F4 controller.
|
||||
Therefore, the CRC calculation is fixed.
|
||||
|
||||
The polynomial is ``0x4C11DB7`` (Ethernet CRC32):
|
||||
|
||||
.. math:: P_{CRC}(x) = x^{32}+x^{26}+x^{23}+x^{22}+x^{16}+x^{12}+x^{11}+x^{10}+x^{8}+x^{7}+x^{5}+x^{4}+x^{2}+x+1
|
20
doc/source/firmware/safety/error-handling.rst
Normal file
20
doc/source/firmware/safety/error-handling.rst
Normal file
@ -0,0 +1,20 @@
|
||||
.. _safety_handling:
|
||||
|
||||
Error Handling
|
||||
==============
|
||||
|
||||
.. _safety_panic:
|
||||
|
||||
Panic Mode
|
||||
----------
|
||||
|
||||
|
||||
.. _safety_error_mem:
|
||||
|
||||
Error memory
|
||||
------------
|
||||
|
||||
Permanent errors are stored in the backup RAM of the STM. This ensures, that errors can be read even after a full system reset has occured.
|
||||
|
||||
.. seealso:: :ref:`backup_ram`
|
||||
|
150
doc/source/firmware/safety/flags.rst
Normal file
150
doc/source/firmware/safety/flags.rst
Normal file
@ -0,0 +1,150 @@
|
||||
.. _safety_flags:
|
||||
|
||||
Safety Flags
|
||||
============
|
||||
|
||||
The safety flags are represented in software by the following enums
|
||||
|
||||
.. doxygenenum:: safety_flag
|
||||
|
||||
The safety flags can be temporarily or permanent. Some temporary flags are reset automatically, once the error condition disappears. Others have to be explicitly cleared.
|
||||
The safety weights (if a flag stops the PID controller, or triggers the panic mode) are configured by default as described below. However, it will be possible to override these weights by
|
||||
setting config entries in the safety memory.
|
||||
|
||||
.. todo:: Change docu of config entires in memory
|
||||
|
||||
----------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
.. _safety_flags_adc_overflow:
|
||||
|
||||
ERR_FLAG_MEAS_ADC_OVERFLOW
|
||||
--------------------------
|
||||
|
||||
``ERR_FLAG_MEAS_ADC_OVERFLOW`` is triggered in case of an overflow in the signal path of the measurement ADC. This should never happen unless there is a bug in the software.
|
||||
|
||||
========== ============= ============= ===========
|
||||
persistent self-clearing Stops PID Panic Mode
|
||||
========== ============= ============= ===========
|
||||
yes no yes no
|
||||
========== ============= ============= ===========
|
||||
|
||||
----------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
.. _safety_flags_adc_off:
|
||||
|
||||
ERR_FLAG_MEAS_ADC_OFF
|
||||
---------------------
|
||||
|
||||
``ERR_FLAG_MEAS_ADC_OFF`` signals that the measurement ADC for the PT1000 sensor is deactivated. This flag is automatically cleared by the firmware
|
||||
once the ADC is started.
|
||||
|
||||
========== ============= ============= ===========
|
||||
persistent self-clearing Stops PID Panic Mode
|
||||
========== ============= ============= ===========
|
||||
no yes yes no
|
||||
========== ============= ============= ===========
|
||||
|
||||
----------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
.. _safety_flags_adc_watchdog:
|
||||
|
||||
ERR_FLAG_MEAS_ADC_WATCHDOG
|
||||
--------------------------
|
||||
|
||||
``ERR_FLAG_MEAS_ADC_WATCHDOG`` is used as a wire break detection mechanism. This flag is set when the PT1000 measurement ADC detects an invalid resistance measurement.
|
||||
|
||||
.. seealso:: :ref:`ADC Watchdog<firmware_meas_adc_watchdog>`
|
||||
|
||||
========== ============= ============= ===========
|
||||
persistent self-clearing Stops PID Panic Mode
|
||||
========== ============= ============= ===========
|
||||
no no yes no
|
||||
========== ============= ============= ===========
|
||||
|
||||
----------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
.. _safety_flags_adc_unstable:
|
||||
|
||||
ERR_FLAG_MEAS_ADC_UNSTABLE
|
||||
--------------------------
|
||||
|
||||
``ERR_FLAG_MEAS_ADC_UNSTABLE`` is set if the Moving Average Filter of the PT1000 ADC detects a bigger deviation between input and filtered output value.
|
||||
The flag is automatically cleared by the controller as soon as the unstable condition is not met anymore. For this, the filter input to filter output difference has to stay below a certain threshold for a defined amount of samples.
|
||||
|
||||
.. seealso:: :ref:`firmware_meas_adc_filter`
|
||||
|
||||
========== ============= ============= ===========
|
||||
persistent self-clearing Stops PID Panic Mode
|
||||
========== ============= ============= ===========
|
||||
no yes no no
|
||||
========== ============= ============= ===========
|
||||
|
||||
|
||||
.. _safety_flags_safety_mem_corrupt:
|
||||
|
||||
ERR_FLAG_SAFETY_MEM_CORRUPT
|
||||
---------------------------
|
||||
|
||||
``ERR_FLAG_SAFETY_MEM_CORRUPT`` is set during the initialization of the controller, in case a corrupted safety memory is encountered.
|
||||
In this case the error memory is reinitialized and the flag is set in the error memory. Afer a reboot it will stay asserted until the
|
||||
safety backup memory is cleared.
|
||||
|
||||
.. seealso:: :ref:`backup_ram`
|
||||
|
||||
========== ============= ============= ===========
|
||||
persistent self-clearing Stops PID Panic Mode
|
||||
========== ============= ============= ===========
|
||||
yes no yes no
|
||||
========== ============= ============= ===========
|
||||
|
||||
|
||||
.. _safety_flags_stack:
|
||||
|
||||
ERR_FLAG_STACK
|
||||
---------------------------
|
||||
|
||||
``ERR_FLAG_STACK`` Shutdown of the controller, in case a corrupted safety memory is encountered.
|
||||
This error is not recoverable and will trigger the panic mode.
|
||||
|
||||
.. seealso:: :ref:`safety_stack_checking`
|
||||
|
||||
========== ============= ============= ===========
|
||||
persistent self-clearing Stops PID Panic Mode
|
||||
========== ============= ============= ===========
|
||||
yes no yes yes
|
||||
========== ============= ============= ===========
|
||||
|
||||
.. _safety_flags_timing_pid:
|
||||
|
||||
ERR_FLAG_TIMING_PID
|
||||
---------------------------
|
||||
|
||||
``ERR_FLAG_TIMING_PID`` is set if the timing monitor of the PID controller detects a violation in the sample frequency.
|
||||
The flag is recoverable and only shuts down the PID and therefore the oven control output.
|
||||
|
||||
.. seealso:: Timing Monitor
|
||||
|
||||
========== ============= ============= ===========
|
||||
persistent self-clearing Stops PID Panic Mode
|
||||
========== ============= ============= ===========
|
||||
no no yes no
|
||||
========== ============= ============= ===========
|
||||
|
||||
.. _safety_flags_overtemp:
|
||||
|
||||
ERR_FLAG_OVERTEMP
|
||||
---------------------------
|
||||
|
||||
``ERR_FLAG_OVERTEMP`` is set if the :ref:`pt1000_processing` detects an overtemperature.
|
||||
The default limit is set to:
|
||||
|
||||
.. doxygendefine:: SAFETY_DEFAULT_OVERTEMP_LIMIT_DEGC
|
||||
|
||||
However, it is possible to configure the limit and permanenty store it in the EEPROM since hardware version v1.3.
|
||||
|
||||
|
||||
========== ============= ============= ===========
|
||||
persistent self-clearing Stops PID Panic Mode
|
||||
========== ============= ============= ===========
|
||||
no no yes no
|
||||
========== ============= ============= ===========
|
@ -10,10 +10,15 @@ Severe error flags, like a drifting reference voltage, stop the PID controller a
|
||||
The controller stays in a usable state. After the errors have been cleared, normal operation may continue.
|
||||
|
||||
On the other hand, fatal errors like an over-temperature error, or memory problem, lead to the activation of the :ref:`safety_panic`,
|
||||
which forces the output zero, but does not allow any more interaction.
|
||||
which forces the output zero, but does not allow any further interaction.
|
||||
|
||||
On top of this, a :ref:`backup_ram` is implemented. It stores permantent errors, which are reset at a restart. On top of that, it stores the :ref:`backup_ram_boot_flags`,
|
||||
which are used to retain boot information across resets, for example to communicate with the firmware updater etc. The RAM also contains entries, that allow overrides of flag weights and persistance.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:maxdepth: 3
|
||||
|
||||
flags
|
||||
backup-ram
|
||||
error-handling
|
||||
stack-checking
|
39
doc/source/firmware/safety/stack-checking.rst
Normal file
39
doc/source/firmware/safety/stack-checking.rst
Normal file
@ -0,0 +1,39 @@
|
||||
.. _safety_stack_checking:
|
||||
|
||||
Safety Stack Checking
|
||||
=====================
|
||||
|
||||
To ensure correct operation of the controller, the stack is continuously monitored. For this, the :ref:`firmware_safety` checks the stack in each run.
|
||||
These checks include:
|
||||
|
||||
1. Checking of used stack space and limit to end of stack
|
||||
2. Checking a protection area between heap and stack for memory corruption
|
||||
|
||||
Any detected error will set the :ref:`safety_flags_stack` error flag.
|
||||
|
||||
Stack Pointer Checking
|
||||
----------------------
|
||||
|
||||
The stack pointer is checked using :c:func:`stack_check_get_free`. The returned value for the remaining stack space is checked against
|
||||
|
||||
.. doxygendefine:: SAFETY_MIN_STACK_FREE
|
||||
|
||||
.. doxygenfunction:: stack_check_get_free
|
||||
|
||||
|
||||
Stack and Heap Corruption Checking
|
||||
----------------------------------
|
||||
|
||||
A section of memory is located between the stack and the heap. It is defined inside the linker script. It's size is configured by the linker script parameter ``__stack_corruption_area_size``, which is set to ``128`` by default.
|
||||
This section is filled at the initializazion of the safety controller by a call to
|
||||
|
||||
.. doxygenfunction:: stack_check_init_corruption_detect_area
|
||||
|
||||
On each run of the safety controller's handling function (:c:func:`safety_controller_handle`) the following function is called:
|
||||
|
||||
.. doxygenfunction:: stack_check_corruption_detect_area
|
||||
|
||||
|
||||
This function checks the memory area for write modifications, and therefore detects, if the stack or heap have grown outside their boundaries. This canary approach does, however, not guarantee a full protection against heap or stack overflows.
|
||||
|
||||
|
BIN
doc/source/hardware/hw_watchdog_delay.png
Normal file
BIN
doc/source/hardware/hw_watchdog_delay.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
BIN
doc/source/hardware/hw_watchdog_startup.png
Normal file
BIN
doc/source/hardware/hw_watchdog_startup.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
3
error-mem-viewer/.gitignore
vendored
Normal file
3
error-mem-viewer/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
*.ui~
|
||||
*.user
|
||||
*.user*
|
26
error-mem-viewer/CMakeLists.txt
Normal file
26
error-mem-viewer/CMakeLists.txt
Normal file
@ -0,0 +1,26 @@
|
||||
project(error-mem-viewer)
|
||||
cmake_minimum_required(VERSION 2.8)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
|
||||
pkg_search_module(GLIB REQUIRED glib-2.0)
|
||||
pkg_check_modules(GTK3 REQUIRED gtk+-3.0)
|
||||
pkg_check_modules(GTK_MOD REQUIRED gmodule-2.0)
|
||||
|
||||
aux_source_directory("src" SRCES)
|
||||
include_directories("include" ${GLIB_INCLUDE_DIRS} ${GTK3_INCLUDE_DIRS} ${GTK_MOD_INCLUDE_DIRS})
|
||||
|
||||
add_subdirectory(resources)
|
||||
|
||||
set(SOURCE_GENERATED
|
||||
${CMAKE_CURRENT_BINARY_DIR}/resources/resources.c
|
||||
)
|
||||
SET_SOURCE_FILES_PROPERTIES(${SOURCE_GENERATED} PROPERTIES GENERATED 1)
|
||||
|
||||
add_compile_options(-Wall -Wextra -Wold-style-declaration -Wuninitialized -Wmaybe-uninitialized -Wunused-parameter)
|
||||
|
||||
link_directories(${GLIB_LINK_DIRS} ${GTK3_LINK_DIRS} ${GTK_MOD_LINK_DIRS})
|
||||
add_definitions(${GLIB2_CFLAGS_OTHER} ${GTK_MOD_CFLAGS_OTHER})
|
||||
|
||||
add_executable(${PROJECT_NAME} ${SRCES} ${SOURCE_GENERATED})
|
||||
target_link_libraries(${PROJECT_NAME} ${GLIB_LDFLAGS} ${GTK3_LDFLAGS} ${GTK_MOD_LDFLAGS})
|
||||
add_dependencies(${PROJECT_NAME} glib-resources)
|
10
error-mem-viewer/include/error-mem-viewer/crc.h
Normal file
10
error-mem-viewer/include/error-mem-viewer/crc.h
Normal file
@ -0,0 +1,10 @@
|
||||
#ifndef _CRC_H_
|
||||
#define _CRC_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
uint32_t calculate_stm_crc(uint32_t *data, size_t len);
|
||||
|
||||
|
||||
#endif /* _CRC_H_ */
|
@ -0,0 +1,15 @@
|
||||
#ifndef _ERR_MEM_VIEWER_ADDRESS_CELL_RENDERER_H_
|
||||
#define _ERR_MEM_VIEWER_ADDRESS_CELL_RENDERER_H_
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
G_DECLARE_FINAL_TYPE(ErrMemViewHexCellRenderer, err_mem_view_hex_cell_renderer, ERR_MEM_VIEW, HEX_CELL_RENDERER, GtkCellRendererText)
|
||||
#define ERR_MEM_VIEW_TYPE_HEX_CELL_RENDERER (err_mem_view_address_cell_renderer_get_type())
|
||||
|
||||
GtkCellRenderer *err_mem_view_hex_cell_renderer_new();
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* _ERR_MEM_VIEWER_ADDRESS_CELL_RENDERER_H_ */
|
@ -0,0 +1,57 @@
|
||||
#ifndef _SAFETY_MEM_TYPES_H_
|
||||
#define _SAFETY_MEM_TYPES_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @brief Magic number to signal a valid safety memory header.
|
||||
*/
|
||||
#define SAFETY_MEMORY_MAGIC 0x12AA5CB7
|
||||
|
||||
/**
|
||||
* @brief Error memory NOP entry
|
||||
*/
|
||||
#define SAFETY_MEMORY_NOP_ENTRY 0xC1AA1222
|
||||
|
||||
/**
|
||||
* @brief Safety memory header
|
||||
*/
|
||||
struct safety_memory_header {
|
||||
uint32_t magic; /**< @brief Magic. Set to @ref SAFETY_MEMORY_MAGIC */
|
||||
uint32_t boot_status_offset; /**< @brief Offset of the safety_memory_boot_status struct (in 32 bit words)*/
|
||||
uint32_t config_overrides_offset; /**< @brief Offset address of override entries */
|
||||
uint32_t config_overrides_len; /**< @brief Length of override entries in words */
|
||||
uint32_t err_memory_offset; /**< @brief Offset of the error memory */
|
||||
uint32_t err_memory_end; /**< @brief End of the error memory. This points to the word after the error memory, containing the CRC of the whole backup RAM. */
|
||||
uint32_t crc; /**< @brief CRC of the header */
|
||||
};
|
||||
|
||||
struct safety_memory_boot_status {
|
||||
/**
|
||||
* @brief Reboot into the bootloader
|
||||
*
|
||||
* When this flag is set, the controller will load the bootloader to
|
||||
* memory and execute it.
|
||||
*/
|
||||
uint32_t reboot_to_bootloader;
|
||||
|
||||
/**
|
||||
* @brief Bootloader has updated the code
|
||||
*
|
||||
* This flag is set, if the firmware ahs been updated successfully
|
||||
*/
|
||||
uint32_t code_updated;
|
||||
|
||||
/**
|
||||
* @brief reset_from_panic
|
||||
*
|
||||
* This flag is set, when entering the panic mode.
|
||||
* Because the panic mode is reset by a watchdog reset,
|
||||
* this flag is needed, in order to ensure, that the panic is handled correcly after
|
||||
* the watchdog reset.
|
||||
*/
|
||||
uint32_t reset_from_panic;
|
||||
};
|
||||
|
||||
|
||||
#endif /* _SAFETY_MEM_TYPES_H_ */
|
9
error-mem-viewer/resources/CMakeLists.txt
Normal file
9
error-mem-viewer/resources/CMakeLists.txt
Normal file
@ -0,0 +1,9 @@
|
||||
add_custom_target(glib-resources DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/resources.c")
|
||||
add_custom_command(DEPENDS
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/*.ui"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/resources.xml"
|
||||
OUTPUT
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/resources.c"
|
||||
COMMAND
|
||||
glib-compile-resources --target="${CMAKE_CURRENT_BINARY_DIR}/resources.c" --sourcedir="${CMAKE_CURRENT_SOURCE_DIR}" --generate-source "${CMAKE_CURRENT_SOURCE_DIR}/resources.xml"
|
||||
)
|
120
error-mem-viewer/resources/main-window.ui
Normal file
120
error-mem-viewer/resources/main-window.ui
Normal file
@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.38.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.24"/>
|
||||
<object class="GtkWindow" id="main-window">
|
||||
<property name="name">main-window</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkInfoBar" id="info-bar">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="message-type">warning</property>
|
||||
<property name="show-close-button">True</property>
|
||||
<property name="revealed">False</property>
|
||||
<signal name="response" handler="info_bar_close_cb" swapped="no"/>
|
||||
<child internal-child="action_area">
|
||||
<object class="GtkButtonBox">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="spacing">6</property>
|
||||
<property name="layout-style">end</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child internal-child="content_area">
|
||||
<object class="GtkBox">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="spacing">16</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="error-mem-tree-view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection"/>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="titlebar">
|
||||
<object class="GtkHeaderBar" id="header-bar">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="title" translatable="yes">Error Memory Viewer</property>
|
||||
<property name="subtitle" translatable="yes">Reflow Controller Error Memory Viewer</property>
|
||||
<property name="show-close-button">True</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="open-button">
|
||||
<property name="label">gtk-open</property>
|
||||
<property name="name">open-button</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="use-stock">True</property>
|
||||
<property name="always-show-image">True</property>
|
||||
<signal name="clicked" handler="open_button_clicked_cb" swapped="no"/>
|
||||
<style>
|
||||
<class name="suggested-action"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
6
error-mem-viewer/resources/resources.xml
Normal file
6
error-mem-viewer/resources/resources.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gresources>
|
||||
<gresource prefix="/gui">
|
||||
<file compressed="true" alias="main">main-window.ui</file>
|
||||
</gresource>
|
||||
</gresources>
|
26
error-mem-viewer/src/crc.c
Normal file
26
error-mem-viewer/src/crc.c
Normal file
@ -0,0 +1,26 @@
|
||||
#include <error-mem-viewer/crc.h>
|
||||
|
||||
uint32_t do_crc(uint32_t init, uint32_t data)
|
||||
{
|
||||
uint32_t crc = init;
|
||||
uint32_t cnt;
|
||||
|
||||
for(cnt=0; cnt < 32; cnt++)
|
||||
{
|
||||
crc = ((int32_t)(crc ^ data))<0 ? (crc << 1) ^ 0x04C11DB7 : crc << 1;
|
||||
data <<=1;
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
uint32_t calculate_stm_crc(uint32_t *data, size_t len)
|
||||
{
|
||||
uint32_t crc = ~0U;
|
||||
|
||||
while(len--) {
|
||||
crc = do_crc(crc, *data++);
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
81
error-mem-viewer/src/err-mem-viewer-hex-cell-renderer.c
Normal file
81
error-mem-viewer/src/err-mem-viewer-hex-cell-renderer.c
Normal file
@ -0,0 +1,81 @@
|
||||
#include <error-mem-viewer/err-mem-viewer-hex-cell-renderer.h>
|
||||
#include <string.h>
|
||||
|
||||
struct _ErrMemViewHexCellRenderer {
|
||||
GtkCellRendererText base;
|
||||
};
|
||||
|
||||
enum {
|
||||
PROP_HEXNUM = 1,
|
||||
PROP_COUNT
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(ErrMemViewHexCellRenderer, err_mem_view_address_cell_renderer, GTK_TYPE_CELL_RENDERER_TEXT)
|
||||
|
||||
static void err_mem_view_address_cell_renderer_init(ErrMemViewHexCellRenderer *self)
|
||||
{
|
||||
(void)self;
|
||||
}
|
||||
|
||||
static void convert_gvalue_uint_to_string(const GValue *in, GValue *out)
|
||||
{
|
||||
uint32_t val;
|
||||
char text[32];
|
||||
|
||||
if (!in || !out)
|
||||
return;
|
||||
|
||||
g_value_init(out, G_TYPE_STRING);
|
||||
|
||||
val = g_value_get_uint(in);
|
||||
snprintf(text, sizeof(text), "0x%08X", val);
|
||||
g_value_set_string(out, text);
|
||||
}
|
||||
|
||||
static void err_mem_view_address_cell_renderer_set_property(GObject *obj, guint param_id, const GValue *value, GParamSpec *pspec)
|
||||
{
|
||||
GValue val = G_VALUE_INIT;
|
||||
|
||||
switch (param_id) {
|
||||
case PROP_HEXNUM:
|
||||
convert_gvalue_uint_to_string(value, &val);
|
||||
g_object_set_property(obj, "text", &val);
|
||||
g_value_unset(&val);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void err_mem_view_address_cell_renderer_get_property(GObject *object,
|
||||
guint param_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
(void)value;
|
||||
|
||||
switch (param_id) {
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static GParamSpec *properties[PROP_COUNT];
|
||||
|
||||
void err_mem_view_address_cell_renderer_class_init(ErrMemViewHexCellRendererClass *klass)
|
||||
{
|
||||
GObjectClass *oclass = G_OBJECT_CLASS(klass);
|
||||
|
||||
oclass->set_property = err_mem_view_address_cell_renderer_set_property;
|
||||
oclass->get_property = err_mem_view_address_cell_renderer_get_property;
|
||||
|
||||
properties[PROP_HEXNUM] = g_param_spec_uint("hex-num", "hex-num", "Hex number to display", 0U, UINT_MAX, 0U, G_PARAM_WRITABLE);
|
||||
g_object_class_install_properties(oclass, PROP_COUNT, properties);
|
||||
}
|
||||
|
||||
GtkCellRenderer *err_mem_view_hex_cell_renderer_new()
|
||||
{
|
||||
return GTK_CELL_RENDERER(g_object_new(ERR_MEM_VIEW_TYPE_HEX_CELL_RENDERER, NULL));
|
||||
}
|
537
error-mem-viewer/src/main.c
Normal file
537
error-mem-viewer/src/main.c
Normal file
@ -0,0 +1,537 @@
|
||||
#include <gtk/gtk.h>
|
||||
#include <glib.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <error-mem-viewer/err-mem-viewer-hex-cell-renderer.h>
|
||||
#include <error-mem-viewer/safety-memory-types.h>
|
||||
#include <error-mem-viewer/crc.h>
|
||||
|
||||
enum memory_view_columns {
|
||||
MEM_VIEW_COL_ADDRESS = 0,
|
||||
MEM_VIEW_COL_DATA,
|
||||
MEM_VIEW_COL_INTERPRETATION,
|
||||
MEM_VIEW_COLOR,
|
||||
MEM_VIEW_COLUMN_COUNT
|
||||
};
|
||||
|
||||
struct application_data {
|
||||
GtkWidget *borrowed_main_window;
|
||||
GtkTreeView *borrowed_tree_view;
|
||||
GtkHeaderBar *borrowed_header_bar;
|
||||
GtkInfoBar *borrowed_info_bar;
|
||||
const char *error_memory_data;
|
||||
size_t file_size;
|
||||
};
|
||||
|
||||
enum entry_state {
|
||||
ENTRY_STATE_VALID = 0,
|
||||
ENTRY_STATE_INVALID,
|
||||
ENTRY_STATE_NOT_PARSED,
|
||||
ENTRY_STATE_BLOCK
|
||||
};
|
||||
|
||||
static void append_data(GtkTreeStore *store, GtkTreeIter *parent, GtkTreeIter *iter, GString *interpretation, enum entry_state state, uint32_t addr, uint32_t val)
|
||||
{
|
||||
const GdkRGBA invalid_color = {
|
||||
.red = 1.0,
|
||||
.alpha = 1.0,
|
||||
.green = 0.0,
|
||||
.blue = 0.0,
|
||||
};
|
||||
|
||||
const GdkRGBA valid_color = {
|
||||
.red = 0.0,
|
||||
.alpha = 1.0,
|
||||
.green = 1.0,
|
||||
.blue = 0.0,
|
||||
};
|
||||
|
||||
const GdkRGBA warn_color = {
|
||||
.red = 1.0,
|
||||
.alpha = 1.0,
|
||||
.green = 1.0,
|
||||
.blue = 0.0,
|
||||
};
|
||||
|
||||
const GdkRGBA block_color = {
|
||||
.red = 1.0,
|
||||
.alpha = 1.0,
|
||||
.green = 0.3,
|
||||
.blue = 0.5,
|
||||
};
|
||||
|
||||
const GdkRGBA *col;
|
||||
|
||||
switch (state) {
|
||||
case ENTRY_STATE_VALID:
|
||||
col = &valid_color;
|
||||
break;
|
||||
case ENTRY_STATE_NOT_PARSED:
|
||||
col = &warn_color;
|
||||
break;
|
||||
case ENTRY_STATE_BLOCK:
|
||||
col = &block_color;
|
||||
break;
|
||||
case ENTRY_STATE_INVALID:
|
||||
default:
|
||||
col = &invalid_color;
|
||||
break;
|
||||
}
|
||||
|
||||
gtk_tree_store_append(store, iter, parent);
|
||||
gtk_tree_store_set(store, iter, MEM_VIEW_COL_ADDRESS, addr, MEM_VIEW_COLOR, col, MEM_VIEW_COL_INTERPRETATION, (interpretation ? interpretation->str : ""), MEM_VIEW_COL_DATA, val, -1);
|
||||
if (interpretation)
|
||||
g_string_free(interpretation, TRUE);
|
||||
}
|
||||
|
||||
static void info_bar_show_message(GtkInfoBar *info_bar, GtkMessageType type, const char *format, ...)
|
||||
{
|
||||
GString *string;
|
||||
va_list args;
|
||||
GtkWidget *content;
|
||||
GList *children, *iter;
|
||||
GtkWidget *label;
|
||||
|
||||
va_start(args, format);
|
||||
string = g_string_new(NULL);
|
||||
|
||||
g_string_vprintf(string, format, args);
|
||||
va_end(args);
|
||||
|
||||
content = gtk_info_bar_get_content_area(info_bar);
|
||||
children = gtk_container_get_children(GTK_CONTAINER(content));
|
||||
for (iter = children; iter; iter = g_list_next(iter))
|
||||
gtk_widget_destroy(GTK_WIDGET(iter->data));
|
||||
g_list_free(children);
|
||||
|
||||
label = gtk_label_new(string->str);
|
||||
gtk_container_add(GTK_CONTAINER(content), label);
|
||||
gtk_widget_show(label);
|
||||
gtk_info_bar_set_message_type(info_bar, type);
|
||||
gtk_widget_show(GTK_WIDGET(info_bar));
|
||||
gtk_info_bar_set_revealed(info_bar, TRUE);
|
||||
|
||||
g_string_free(string, TRUE);
|
||||
}
|
||||
|
||||
static bool check_err_mem_header(struct safety_memory_header *header, uint32_t *expected)
|
||||
{
|
||||
uint32_t crc;
|
||||
|
||||
crc = calculate_stm_crc((uint32_t *)header, sizeof(*header) / sizeof(uint32_t) - 1);
|
||||
if (expected)
|
||||
*expected = crc;
|
||||
if (crc == header->crc)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
static GString *new_string_printf(const char *fmt, ...)
|
||||
{
|
||||
GString *string;
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
|
||||
string = g_string_new(NULL);
|
||||
g_string_vprintf(string, fmt, args);
|
||||
|
||||
va_end(args);
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
static void show_error_memory(GtkTreeView *tree_view, const unsigned char *memory, size_t len)
|
||||
{
|
||||
GtkTreeStore *store;
|
||||
GtkTreeIter iter;
|
||||
GtkTreeIter iter2;
|
||||
GtkTreeIter *parent_iter, *child_iter;
|
||||
enum entry_state state;
|
||||
GString *interpret;
|
||||
bool valid;
|
||||
bool analyze_further = true;
|
||||
struct safety_memory_header header;
|
||||
unsigned int i;
|
||||
uint32_t dat, expected_value;
|
||||
|
||||
store = GTK_TREE_STORE(gtk_tree_view_get_model(tree_view));
|
||||
gtk_tree_store_clear(store);
|
||||
|
||||
for (i = 0; i < len / 4; i++) {
|
||||
valid = false;
|
||||
interpret = NULL;
|
||||
dat = memory[i*4];
|
||||
dat |= ((uint32_t)memory[i*4+1]) << 8;
|
||||
dat |= ((uint32_t)memory[i*4+2]) << 16;
|
||||
dat |= ((uint32_t)memory[i*4+3]) << 24;
|
||||
|
||||
state = ENTRY_STATE_NOT_PARSED;
|
||||
|
||||
if (!analyze_further) {
|
||||
interpret = g_string_new("Not analyzed due to previous error");
|
||||
state = ENTRY_STATE_NOT_PARSED;
|
||||
parent_iter = NULL;
|
||||
child_iter = &iter;
|
||||
goto print;
|
||||
}
|
||||
/* Parse header */
|
||||
switch (i) {
|
||||
case 0: /* Magic */
|
||||
parent_iter = NULL;
|
||||
child_iter = &iter;
|
||||
append_data(store, parent_iter, child_iter, new_string_printf("Header"), ENTRY_STATE_BLOCK, i * 4, dat);
|
||||
parent_iter = child_iter;
|
||||
child_iter = &iter2;
|
||||
if (dat == SAFETY_MEMORY_MAGIC) {
|
||||
valid = true;
|
||||
interpret = g_string_new("Valid Header Magic");
|
||||
} else {
|
||||
interpret = g_string_new("Invalid Header Magic");
|
||||
analyze_further = false;
|
||||
}
|
||||
header.magic = dat;
|
||||
break;
|
||||
case 1:
|
||||
header.boot_status_offset = dat;
|
||||
|
||||
valid = true;
|
||||
interpret = new_string_printf("Boot status offset addr: %u", dat);
|
||||
break;
|
||||
case 2:
|
||||
header.config_overrides_offset = dat;
|
||||
interpret = new_string_printf("Config override offset addr: %u", dat);
|
||||
valid = true;
|
||||
break;
|
||||
case 3:
|
||||
header.config_overrides_len = dat;
|
||||
interpret = new_string_printf("Config override length: %u", dat);
|
||||
valid = true;
|
||||
break;
|
||||
case 4:
|
||||
header.err_memory_offset = dat;
|
||||
interpret = new_string_printf("Error memory offset addr: %u", dat);
|
||||
valid = true;
|
||||
break;
|
||||
case 5:
|
||||
header.err_memory_end = dat;
|
||||
interpret = new_string_printf("Error memory end ptr: %u", dat);
|
||||
valid = true;
|
||||
break;
|
||||
case 6:
|
||||
header.crc = dat;
|
||||
valid = check_err_mem_header(&header, &expected_value);
|
||||
if (valid) {
|
||||
interpret = new_string_printf("Header CRC: 0x%08X", dat);
|
||||
} else {
|
||||
|
||||
interpret = new_string_printf("Invalid CRC: 0x%08X | Expected: 0x%08X", dat, expected_value);
|
||||
analyze_further = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (i <= 6) {
|
||||
state = ENTRY_STATE_INVALID;
|
||||
goto print;
|
||||
}
|
||||
|
||||
/* Check if we're in the boot status structure */
|
||||
if (i >= header.boot_status_offset && i < header.boot_status_offset + sizeof(struct safety_memory_boot_status) / 4) {
|
||||
if (i == header.boot_status_offset) {
|
||||
/* First entry of boot status struct */
|
||||
parent_iter = NULL;
|
||||
child_iter = &iter;
|
||||
interpret = new_string_printf("Boot Status Struct");
|
||||
append_data(store, parent_iter, child_iter, interpret, ENTRY_STATE_BLOCK, i * 4, dat);
|
||||
parent_iter = &iter;
|
||||
child_iter = &iter2;
|
||||
interpret = NULL;
|
||||
}
|
||||
switch (i - header.boot_status_offset) {
|
||||
case 0:
|
||||
interpret = new_string_printf("%s into bootloader", (dat ? "Boot" : "Do not boot"));
|
||||
valid = true;
|
||||
break;
|
||||
case 1:
|
||||
interpret = new_string_printf("Code %s been updated", (dat ? "has" : "hasn't"));
|
||||
valid = true;
|
||||
break;
|
||||
case 2:
|
||||
interpret = new_string_printf("Panic %s", (dat ? "occured" : "did not occur"));
|
||||
valid = true;
|
||||
break;
|
||||
default:
|
||||
valid = false;
|
||||
interpret = new_string_printf("Value undefined!");
|
||||
break;
|
||||
}
|
||||
state = ENTRY_STATE_INVALID;
|
||||
goto print;
|
||||
}
|
||||
|
||||
if (i >= header.config_overrides_offset && i < (header.config_overrides_offset + header.config_overrides_len)) {
|
||||
if (i == header.config_overrides_offset) {
|
||||
parent_iter = NULL;
|
||||
child_iter = &iter;
|
||||
append_data(store, parent_iter, child_iter,
|
||||
new_string_printf("Config Overrides"),
|
||||
ENTRY_STATE_BLOCK, i * 4, dat);
|
||||
parent_iter = child_iter;
|
||||
child_iter = &iter2;
|
||||
}
|
||||
|
||||
interpret = new_string_printf("Config Overrides not yet implemented");
|
||||
valid = true;
|
||||
state = ENTRY_STATE_INVALID;
|
||||
goto print;
|
||||
}
|
||||
|
||||
if (i >= header.err_memory_offset && i < header.err_memory_end) {
|
||||
if (i == header.err_memory_offset) {
|
||||
parent_iter = NULL;
|
||||
child_iter = &iter;
|
||||
append_data(store, parent_iter, child_iter,
|
||||
new_string_printf("Error Memory (length: %d)", header.err_memory_end - header.err_memory_offset),
|
||||
ENTRY_STATE_BLOCK, i * 4, dat);
|
||||
parent_iter = child_iter;
|
||||
child_iter = &iter2;
|
||||
}
|
||||
|
||||
switch (dat) {
|
||||
case SAFETY_MEMORY_NOP_ENTRY:
|
||||
valid = true;
|
||||
interpret = new_string_printf("Error Memory NOP");
|
||||
break;
|
||||
default:
|
||||
if ((dat & 0xFFU) == 0x51U) {
|
||||
valid = true;
|
||||
interpret = new_string_printf("Err memory Entry");
|
||||
} else
|
||||
interpret = new_string_printf("Invalid error memory entry");
|
||||
break;
|
||||
}
|
||||
state = ENTRY_STATE_INVALID;
|
||||
goto print;
|
||||
}
|
||||
|
||||
if (i == header.err_memory_end) {
|
||||
expected_value = calculate_stm_crc((uint32_t *)memory, header.err_memory_end);
|
||||
if (expected_value == dat) {
|
||||
interpret = new_string_printf("Valid Memory CRC");
|
||||
valid = true;
|
||||
} else {
|
||||
interpret = new_string_printf("Invalid CRC. Expected: 0x%08X", expected_value);
|
||||
}
|
||||
state = ENTRY_STATE_INVALID;
|
||||
parent_iter = NULL;
|
||||
child_iter = &iter;
|
||||
goto print;
|
||||
}
|
||||
|
||||
parent_iter = NULL;
|
||||
child_iter = &iter;
|
||||
print:
|
||||
if (state != ENTRY_STATE_NOT_PARSED) {
|
||||
state = (valid ? ENTRY_STATE_VALID : ENTRY_STATE_INVALID);
|
||||
}
|
||||
append_data(store, parent_iter, child_iter, interpret, state, i * 4, dat);
|
||||
}
|
||||
|
||||
|
||||
if (!store)
|
||||
return;
|
||||
}
|
||||
|
||||
void setup_tree_view(GtkTreeView *tree_view)
|
||||
{
|
||||
GtkTreeStore *tree_store;
|
||||
GtkCellRenderer *string_renderer;
|
||||
GtkCellRenderer *hex_renderer;
|
||||
GtkTreeViewColumn *column;
|
||||
|
||||
g_return_if_fail(GTK_IS_TREE_VIEW(tree_view));
|
||||
|
||||
tree_store = gtk_tree_store_new(MEM_VIEW_COLUMN_COUNT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING, GDK_TYPE_RGBA);
|
||||
gtk_tree_view_set_model(tree_view, GTK_TREE_MODEL(tree_store));
|
||||
|
||||
string_renderer = gtk_cell_renderer_text_new();
|
||||
hex_renderer = err_mem_view_hex_cell_renderer_new();
|
||||
|
||||
column = gtk_tree_view_column_new_with_attributes("Address", hex_renderer, "hex-num", MEM_VIEW_COL_ADDRESS, "foreground-rgba", MEM_VIEW_COLOR, NULL);
|
||||
gtk_tree_view_append_column(tree_view, column);
|
||||
column = gtk_tree_view_column_new_with_attributes("Data", hex_renderer, "hex-num", MEM_VIEW_COL_DATA, "foreground-rgba", MEM_VIEW_COLOR, NULL);
|
||||
gtk_tree_view_append_column(tree_view, column);
|
||||
column = gtk_tree_view_column_new_with_attributes("Interpretation", string_renderer, "text", MEM_VIEW_COL_INTERPRETATION, "foreground-rgba", MEM_VIEW_COLOR, NULL);
|
||||
gtk_tree_view_append_column(tree_view, column);
|
||||
}
|
||||
|
||||
static ptrdiff_t get_file_size(const char *filename)
|
||||
{
|
||||
struct stat file_stat;
|
||||
int res;
|
||||
ptrdiff_t size;
|
||||
|
||||
res = stat(filename, &file_stat);
|
||||
|
||||
if (res)
|
||||
size = -1UL;
|
||||
else
|
||||
size = (ptrdiff_t)file_stat.st_size;
|
||||
|
||||
return size;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Loads a file to memory and decodes it using base64
|
||||
* @param name Filename
|
||||
* @param detected_size detected Size of the ouput generated
|
||||
* @return Buffer of decoded data with size \p detected_size, NULL in case of an error.
|
||||
* @note Return buffer is allocated on heap. Use free() to free the memory.
|
||||
*/
|
||||
static char *load_file_to_memory(const char *name, size_t *detected_size)
|
||||
{
|
||||
ptrdiff_t size;
|
||||
FILE *f;
|
||||
char *ret = NULL;
|
||||
size_t read_bytes;
|
||||
gsize decoded_size;
|
||||
|
||||
size = get_file_size(name);
|
||||
if (size <= 0)
|
||||
goto exit;
|
||||
|
||||
f = fopen(name, "rb");
|
||||
ret = (char *)malloc(size + 1);
|
||||
if (!ret)
|
||||
goto exit_close_file;
|
||||
|
||||
read_bytes = fread(ret, 1UL, (size_t)size, f);
|
||||
if (read_bytes != (size_t)size) {
|
||||
free(ret);
|
||||
ret = NULL;
|
||||
goto exit_close_file;
|
||||
}
|
||||
|
||||
ret[size] = '\0';
|
||||
|
||||
g_base64_decode_inplace(ret, &decoded_size);
|
||||
if (decoded_size == 0) {
|
||||
free(ret);
|
||||
ret = NULL;
|
||||
goto exit_close_file;
|
||||
}
|
||||
|
||||
if (detected_size)
|
||||
*detected_size = (size_t)decoded_size;
|
||||
exit_close_file:
|
||||
fclose(f);
|
||||
exit:
|
||||
return ret;
|
||||
}
|
||||
|
||||
G_MODULE_EXPORT
|
||||
void info_bar_close_cb(GtkInfoBar *info_bar, gpointer user_data)
|
||||
{
|
||||
(void)user_data;
|
||||
|
||||
gtk_info_bar_set_revealed(info_bar, FALSE);
|
||||
}
|
||||
|
||||
G_MODULE_EXPORT
|
||||
void open_button_clicked_cb(GtkButton *button, gpointer *user_data)
|
||||
{
|
||||
struct application_data *data = (struct application_data *)user_data;
|
||||
(void)button;
|
||||
GtkDialog *dialog;
|
||||
gint res;
|
||||
gchar *filename;
|
||||
|
||||
|
||||
dialog = GTK_DIALOG(gtk_file_chooser_dialog_new("Open File", GTK_WINDOW(data->borrowed_main_window),
|
||||
GTK_FILE_CHOOSER_ACTION_OPEN,
|
||||
"Cancel", GTK_RESPONSE_CANCEL,
|
||||
"Open", GTK_RESPONSE_ACCEPT,
|
||||
NULL));
|
||||
res = gtk_dialog_run(dialog);
|
||||
if (res == GTK_RESPONSE_ACCEPT) {
|
||||
filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
|
||||
if (data->error_memory_data)
|
||||
free((void *)data->error_memory_data);
|
||||
data->error_memory_data = load_file_to_memory(filename, &data->file_size);
|
||||
if (!data->error_memory_data) {
|
||||
g_warning("File could not be loaded");
|
||||
g_free(filename);
|
||||
goto ret_destroy_dialog;
|
||||
}
|
||||
|
||||
gtk_header_bar_set_subtitle(data->borrowed_header_bar, filename);
|
||||
g_free(filename);
|
||||
} else {
|
||||
goto ret_destroy_dialog;
|
||||
}
|
||||
|
||||
if (data->file_size % 4 || data->file_size == 0) {
|
||||
free((void *)data->error_memory_data);
|
||||
data->error_memory_data = NULL;
|
||||
info_bar_show_message(data->borrowed_info_bar, GTK_MESSAGE_WARNING,
|
||||
"Data must be base64 encoded and must contain full 32 bit words of data.");
|
||||
goto ret_destroy_dialog;
|
||||
}
|
||||
|
||||
show_error_memory(data->borrowed_tree_view, (const unsigned char *)data->error_memory_data, data->file_size);
|
||||
|
||||
ret_destroy_dialog:
|
||||
gtk_widget_destroy(GTK_WIDGET(dialog));
|
||||
}
|
||||
|
||||
static void app_activated(GApplication *app, gpointer user_data)
|
||||
{
|
||||
struct application_data *data = (struct application_data *)user_data;
|
||||
GtkBuilder *builder;
|
||||
GtkWindow *main_window;
|
||||
|
||||
builder = gtk_builder_new_from_resource("/gui/main");
|
||||
main_window = GTK_WINDOW(gtk_builder_get_object(builder, "main-window"));
|
||||
data->borrowed_main_window = GTK_WIDGET(main_window);
|
||||
data->error_memory_data = NULL;
|
||||
data->borrowed_tree_view = GTK_TREE_VIEW(gtk_builder_get_object(builder, "error-mem-tree-view"));
|
||||
data->borrowed_header_bar = GTK_HEADER_BAR(gtk_builder_get_object(builder, "header-bar"));
|
||||
data->borrowed_info_bar = GTK_INFO_BAR(gtk_builder_get_object(builder, "info-bar"));
|
||||
|
||||
setup_tree_view(data->borrowed_tree_view);
|
||||
|
||||
gtk_builder_connect_signals(builder, data);
|
||||
g_object_unref(builder);
|
||||
gtk_application_add_window(GTK_APPLICATION(app), main_window);
|
||||
gtk_widget_show(GTK_WIDGET(main_window));
|
||||
}
|
||||
|
||||
static int start_gui(int argc, char **argv)
|
||||
{
|
||||
int ret = 0;
|
||||
GtkApplication *g_app;
|
||||
static struct application_data data;
|
||||
|
||||
/* Create a new G application which will start a completely new process for each call to the program (NON_UNIQUE) instead
|
||||
* of creating an additional window in the same process
|
||||
*/
|
||||
g_app = gtk_application_new("de.shimatta.reflow.error-mem-viewer", G_APPLICATION_NON_UNIQUE);
|
||||
g_signal_connect(g_app, "activate", G_CALLBACK(app_activated), &data);
|
||||
ret = g_application_run(G_APPLICATION(g_app), argc, argv);
|
||||
g_object_unref(g_app);
|
||||
if (data.error_memory_data)
|
||||
free((void *)data.error_memory_data);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
return start_gui(argc, argv);
|
||||
}
|
5626
measurement-data/1000OhmSampling-v1.3.csv
Normal file
5626
measurement-data/1000OhmSampling-v1.3.csv
Normal file
File diff suppressed because it is too large
Load Diff
@ -384,6 +384,30 @@
|
||||
"plt.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"fig, axes = plt.subplots(nrows=1, ncols=2, sharex='col', figsize=(28, 8))\n",
|
||||
"v12_df = pd.read_csv(r'hw-v12-1000Ohm.csv') \n",
|
||||
"plot_histogram(axes[0], v12_df['pt1000_res_raw_lf'], 21, 'HW v1.2 1k Ohm Sampling', '1k Resistance')\n",
|
||||
"plot_histogram(axes[1], v12_df['adc_pt1000_raw_reading_hf'], 21, 'HW v1.2 1k Ohm Sampling', '1k Resistance HF RAW')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"fig, axes = plt.subplots(nrows=1, ncols=2, sharex='col', figsize=(28, 8))\n",
|
||||
"v13_df = pd.read_csv(r'1000OhmSampling-v1.3.csv') \n",
|
||||
"plot_histogram(axes[0], v13_df['pt1000_res_raw_lf'], 21, 'HW v1.3 1k Ohm Sampling', '1k Resistance')\n",
|
||||
"plot_histogram(axes[1], v13_df['adc_pt1000_raw_reading_hf'], 21, 'HW v1.3 1k Ohm Sampling', '1k Resistance')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
@ -416,7 +440,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.8.2"
|
||||
"version": "3.9.1"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
5396
measurement-data/hw-v12-1000Ohm.csv
Normal file
5396
measurement-data/hw-v12-1000Ohm.csv
Normal file
File diff suppressed because it is too large
Load Diff
2
stm-firmware/.gitignore
vendored
2
stm-firmware/.gitignore
vendored
@ -14,3 +14,5 @@ reflow-controller.includes
|
||||
*.includes
|
||||
*.config
|
||||
*.files
|
||||
*.user.*
|
||||
*.user
|
||||
|
86
stm-firmware/CMakeLists.txt
Normal file
86
stm-firmware/CMakeLists.txt
Normal file
@ -0,0 +1,86 @@
|
||||
set(CMAKE_SYSTEM_NAME Generic)
|
||||
set(CMAKE_SYSTEM_PROCESSOR arm)
|
||||
set(CMAKE_CROSSCOMPILING 1)
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
|
||||
set(CMAKE_TOOLCHAIN_FILE "arm-none-eabi-gcc.cmake")
|
||||
|
||||
project(reflow-controller)
|
||||
|
||||
if(NOT WIN32)
|
||||
string(ASCII 27 Esc)
|
||||
set(ColorReset "${Esc}[m")
|
||||
set(ColorBold "${Esc}[1m")
|
||||
set(Red "${Esc}[31m")
|
||||
set(Green "${Esc}[32m")
|
||||
set(Yellow "${Esc}[33m")
|
||||
set(Blue "${Esc}[34m")
|
||||
set(Magenta "${Esc}[35m")
|
||||
set(Cyan "${Esc}[36m")
|
||||
set(White "${Esc}[37m")
|
||||
set(BoldRed "${Esc}[1;31m")
|
||||
set(BoldGreen "${Esc}[1;32m")
|
||||
set(BoldYellow "${Esc}[1;33m")
|
||||
set(BoldBlue "${Esc}[1;34m")
|
||||
set(BoldMagenta "${Esc}[1;35m")
|
||||
set(BoldCyan "${Esc}[1;36m")
|
||||
set(BoldWhite "${Esc}[1;37m")
|
||||
endif()
|
||||
|
||||
execute_process(COMMAND bash -c "echo -n $(git describe --always --tags --dirty)"
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE GIT_DESCRIBE)
|
||||
|
||||
set(ELFFILE ${PROJECT_NAME}.elf)
|
||||
set(LINKER_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/stm32f407vet6_flash.ld)
|
||||
|
||||
add_compile_options(-Wall -Wextra -Wold-style-declaration -Wuninitialized -Wmaybe-uninitialized -Wunused-parameter)
|
||||
add_compile_options(-mlittle-endian -mthumb -mcpu=cortex-m4 -mthumb-interwork -mfloat-abi=hard -mfpu=fpv4-sp-d16 -nostartfiles -Wimplicit-fallthrough=3 -Wsign-compare)
|
||||
|
||||
|
||||
set(GIT_DESCRIBE "${GIT_DESCRIBE}")
|
||||
add_definitions(-DBASE64_LOOKUP_TABLE_SECTION=\".ccm.bss\" -DSHELLMATTA_HELP_ALIAS=\"?\" -DGIT_VER=${GIT_DESCRIBE} -DHSE_VALUE=8000000UL -DSTM32F407xx -DSTM32F4XX -DARM_MATH_CM4)
|
||||
|
||||
add_subdirectory(updater/ram-code)
|
||||
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
|
||||
message("${BoldGreen}Version: ${GIT_DESCRIBE}${ColorReset}")
|
||||
|
||||
IF(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
add_definitions(-DDEBUGBUILD)
|
||||
add_compile_options(-O0 -g)
|
||||
ELSE()
|
||||
add_definitions(-DDEBUGBUILD)
|
||||
add_compile_options(-O3 -g)
|
||||
add_link_options(-Wl,--gc-sections)
|
||||
ENDIF(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
|
||||
add_subdirectory(base64-lib)
|
||||
|
||||
|
||||
aux_source_directory("." MAIN_SOURCES)
|
||||
aux_source_directory("config-parser" CFG_PARSER_SRCS)
|
||||
aux_source_directory("ui" UI_SRCS)
|
||||
aux_source_directory("fatfs" FAT_SRCS)
|
||||
aux_source_directory("fatfs/shimatta_sdio_driver" SDIO_SRCS)
|
||||
aux_source_directory("boot" BOOT_SRCS)
|
||||
aux_source_directory("setup" SETUP_SRCS)
|
||||
aux_source_directory("stm-periph" STM_PERIPH_SRCS)
|
||||
aux_source_directory("settings" SETTINGS_SRCS)
|
||||
aux_source_directory("safety" SAFETY_SRCS)
|
||||
aux_source_directory("shellmatta/src" SHELLMATTA_SRCS)
|
||||
aux_source_directory("updater" UPDATER_SRCS)
|
||||
|
||||
add_executable(${ELFFILE} ${MAIN_SOURCES} ${CFG_PARSER_SRCS} ${UI_SRCS}
|
||||
${FAT_SRCS} ${SDIO_SRCS} ${BOOT_SRCS} ${SETUP_SRCS}
|
||||
${STM_PERIPH_SRCS} ${SETTINGS_SRCS} ${SAFETY_SRCS} ${SHELLMATTA_SRCS} ${UPDATER_SRCS}
|
||||
)
|
||||
|
||||
add_dependencies(${ELFFILE} updater-ram-code-header-blob)
|
||||
|
||||
target_include_directories(${ELFFILE} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/shellmatta/api ${CMAKE_CURRENT_SOURCE_DIR}/config-parser/include)
|
||||
target_link_options(${ELFFILE} PRIVATE -mlittle-endian -mthumb -mcpu=cortex-m4 -mthumb-interwork -mfloat-abi=hard -mfpu=fpv4-sp-d16 --disable-newlib-supplied-syscalls -nostartfiles -T${LINKER_SCRIPT} -Wl,--print-memory-usage)
|
||||
target_link_libraries(${ELFFILE} base64-lib)
|
||||
target_include_directories(${ELFFILE} PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/updater/ram-code/include/")
|
||||
|
@ -1,208 +0,0 @@
|
||||
################################Shimatta Makefile####################################
|
||||
#CPU: STM32F407VET6
|
||||
#Compiler: arm-none-eabi
|
||||
#####################################################################################
|
||||
#Add Files and Folders below#########################################################
|
||||
CFILES = main.c syscalls.c setup/system_stm32f4xx.c systick.c boot/startup_stm32f407vx.c
|
||||
ASFILES =
|
||||
INCLUDEPATH = -Iinclude
|
||||
|
||||
OBJDIR_BASE = obj
|
||||
TARGET_BASE = reflow-controller
|
||||
LIBRARYPATH = -L. -Lmathlib
|
||||
LIBRARIES = -larm_cortexM4lf_math -lm
|
||||
|
||||
DEFINES = -DSTM32F407xx -DSTM32F4XX -DARM_MATH_CM4 -DHSE_VALUE=8000000UL
|
||||
MAPFILE_BASE = memory-mapping
|
||||
LINKER_SCRIPT=stm32f407vet6_flash.ld
|
||||
|
||||
export GIT_VER = $(shell git describe --always --dirty --tags)
|
||||
DEFINES += -DGIT_VER=$(GIT_VER)
|
||||
|
||||
ifneq ($(VERBOSE),true)
|
||||
QUIET=@
|
||||
else
|
||||
QUIET=
|
||||
endif
|
||||
|
||||
##Custom Files###
|
||||
CFILES += adc-meas.c
|
||||
|
||||
# Shellmatta
|
||||
CFILES += shellmatta/src/shellmatta.c shellmatta/src/shellmatta_autocomplete.c shellmatta/src/shellmatta_escape.c shellmatta/src/shellmatta_history.c shellmatta/src/shellmatta_utils.c shellmatta/src/shellmatta_opt.c shell.c
|
||||
INCLUDEPATH += -Ishellmatta/api
|
||||
DEFINES += -DSHELLMATTA_HELP_ALIAS=\"?\"
|
||||
|
||||
# RCC Manager
|
||||
CFILES += stm-periph/clock-enable-manager.c
|
||||
CFILES += stm-periph/uart.c stm-periph/dma-ring-buffer.c stm-periph/backup-ram.c
|
||||
CFILES += digio.c
|
||||
CFILES += stm-periph/unique-id.c
|
||||
CFILES += calibration.c
|
||||
CFILES += temp-converter.c
|
||||
CFILES += rotary-encoder.c button.c
|
||||
CFILES += stack-check.c
|
||||
CFILES += ui/lcd.c ui/menu.c reflow-menu.c
|
||||
CFILES += fatfs/diskio.c fatfs/ff.c fatfs/ffsystem.c fatfs/ffunicode.c fatfs/shimatta_sdio_driver/shimatta_sdio.c
|
||||
CFILES += pid-controller.c oven-driver.c
|
||||
CFILES += settings/settings.c settings/settings-sd-card.c
|
||||
|
||||
CFILES += safety/safety-adc.c safety/safety-controller.c safety/watchdog.c safety/fault.c
|
||||
|
||||
DEBUG_DEFINES = -DDEBUGBUILD
|
||||
RELEASE_DEFINES =
|
||||
|
||||
###################################################################################
|
||||
ifeq ($(CROSS_COMPILE),)
|
||||
CROSS_COMPILE=arm-none-eabi-
|
||||
endif
|
||||
|
||||
CC=$(CROSS_COMPILE)gcc
|
||||
OBJCOPY=$(CROSS_COMPILE)objcopy
|
||||
OBJDUMP=$(CROSS_COMPILE)objdump
|
||||
SIZE=$(CROSS_COMPILE)size
|
||||
|
||||
CFLAGS_RELEASE = -O3 -g
|
||||
CFLAGS_DEBUG = -O0 -g
|
||||
|
||||
LFLAGS_RELEASE = -Wl,--gc-sections
|
||||
LFLAGS_DEBUG =
|
||||
|
||||
CFLAGS =
|
||||
LFLAGS =
|
||||
|
||||
ifneq ($(DEBUGBUILD),true)
|
||||
DEFINES += $(RELEASE_DEFINES)
|
||||
CFLAGS += $(CFLAGS_RELEASE)
|
||||
LFLAGS += $(LFLAGS_RELEASE)
|
||||
target = $(TARGET_BASE)-release
|
||||
OBJDIR = $(OBJDIR_BASE)/release
|
||||
MAPFILE = $(MAPFILE_BASE)-release
|
||||
else
|
||||
DEFINES += $(DEBUG_DEFINES)
|
||||
target = $(TARGET_BASE)-debug
|
||||
CFLAGS += $(CFLAGS_DEBUG)
|
||||
LFLAGS += $(LFLAGS_DEBUG)
|
||||
OBJDIR = $(OBJDIR_BASE)/debug
|
||||
MAPFILE = $(MAPFILE_BASE)-debug
|
||||
endif
|
||||
|
||||
LFLAGS += -mlittle-endian -mthumb -mcpu=cortex-m4 -mthumb-interwork
|
||||
LFLAGS += -mfloat-abi=hard -mfpu=fpv4-sp-d16 --disable-newlib-supplied-syscalls -nostartfiles
|
||||
LFLAGS += -T$(LINKER_SCRIPT) -Wl,-Map=$(MAPFILE).map -Wl,--print-memory-usage
|
||||
|
||||
CFLAGS += -c -mlittle-endian -mthumb -mcpu=cortex-m4 -mthumb-interwork
|
||||
CFLAGS += -mfloat-abi=hard -mfpu=fpv4-sp-d16 -nostartfiles
|
||||
CFLAGS += -Wall -Wextra -Wold-style-declaration -Wuninitialized -Wmaybe-uninitialized -Wunused-parameter -Wimplicit-fallthrough=3 -Wsign-compare
|
||||
|
||||
####################################################################################
|
||||
|
||||
OBJ = $(CFILES:%.c=$(OBJDIR)/%.c.o)
|
||||
ASOBJ += $(ASFILES:%.S=$(OBJDIR)/%.S.o)
|
||||
|
||||
default: $(target).elf
|
||||
|
||||
all: debug release
|
||||
|
||||
release:
|
||||
$(QUIET)$(MAKE) DEBUGBUILD=false
|
||||
|
||||
debug:
|
||||
$(QUIET)$(MAKE) DEBUGBUILD=true
|
||||
|
||||
%.bin: %.elf
|
||||
$(QUIET)$(OBJCOPY) -O binary $^ $@
|
||||
%.hex: %.elf
|
||||
$(QUIET)$(OBJCOPY) -O ihex $^ $@
|
||||
|
||||
#Linking
|
||||
$(target).elf: $(OBJ) $(ASOBJ) $(LINKER_SCRIPT)
|
||||
@echo [LD] $@
|
||||
$(QUIET)$(CC) $(LFLAGS) $(LIBRARYPATH) -o $@ $(OBJ) $(ASOBJ) $(LIBRARIES)
|
||||
$(QUIET)$(SIZE) $@
|
||||
@echo "Built Version $(GIT_VER)"
|
||||
|
||||
#Compiling
|
||||
$(OBJ):
|
||||
@echo [CC] $@
|
||||
$(eval OUTPATH=$(dir $@))
|
||||
@mkdir -p $(OUTPATH)
|
||||
$(QUIET)$(CC) $(CFLAGS) -MMD -MT $@ $(INCLUDEPATH) $(DEFINES) -o $@ $(@:$(OBJDIR)/%.c.o=%.c)
|
||||
$(ASOBJ):
|
||||
@echo [AS] $@
|
||||
$(eval OUTPATH=$(dir $@))
|
||||
@mkdir -p $(OUTPATH)
|
||||
$(QUIET)$(CC) $(CFLAGS) -MMD -MT $@ $(INCLUDEPATH) $(DEFINES) -o $@ $(@:$(OBJDIR)/%.S.o=%.S)
|
||||
|
||||
|
||||
.PHONY: qtproject-legacy qtproject qtproject-debug clean mrproper objcopy disassemble program program-debug
|
||||
|
||||
program-debug:
|
||||
$(QUIET)$(MAKE) DEBUGBUILD=true program
|
||||
|
||||
program: $(target).elf
|
||||
./program-device.sh $<
|
||||
|
||||
disassemble: $(target).elf
|
||||
$(QUIET)$(OBJDUMP) -D -s $< > $(target).lss
|
||||
|
||||
objcopy: $(target).bin $(target).hex
|
||||
|
||||
mrproper: clean
|
||||
ifneq ($(DEBUGBUILD),true)
|
||||
@echo "Purging RELEASE project files"
|
||||
else
|
||||
@echo "Purging DEBUG project files"
|
||||
endif
|
||||
$(QUIET)rm -f $(target).pro $(target).creator $(target).files $(target).cflags $(target).cxxflags $(target).includes $(target).config
|
||||
ifneq ($(DEBUGBUILD),true)
|
||||
$(QUIET)$(MAKE) DEBUGBUILD=true mrproper
|
||||
endif
|
||||
|
||||
|
||||
clean:
|
||||
@echo -n "Cleaning up derived files for "
|
||||
ifneq ($(DEBUGBUILD),true)
|
||||
@echo "RELEASE build"
|
||||
else
|
||||
@echo "DEBUG build"
|
||||
endif
|
||||
$(QUIET)rm -f $(target).elf $(target).bin $(target).hex $(OBJ) $(ASOBJ) $(mapfile).map $(CFILES:%.c=$(OBJDIR)/%.c.d) $(ASFILES:%.S=$(OBJDIR)/%.S.d)
|
||||
$(QUIET)rm -rf $(OBJDIR)/*
|
||||
ifneq ($(DEBUGBUILD),true)
|
||||
$(QUIET)$(MAKE) DEBUGBUILD=true clean
|
||||
endif
|
||||
|
||||
qtproject-legacy:
|
||||
echo -e "TEMPLATE = app\nCONFIG -= console app_bundle qt" > $(target).pro
|
||||
echo -e "SOURCES += $(CFILES) $(ASFILES)" >> $(target).pro
|
||||
echo -ne "INCLUDEPATH += " >> $(target).pro
|
||||
echo "$(INCLUDEPATH)" | sed "s!-I!./!g" >> $(target).pro
|
||||
echo -ne "HEADERS += " >> $(target).pro
|
||||
find -name "*.h" | tr "\\n" " " >> $(target).pro
|
||||
echo -ne "\nDEFINES += " >> $(target).pro
|
||||
echo "$(DEFINES)" | sed "s/-D//g" >> $(target).pro
|
||||
|
||||
qtproject-debug:
|
||||
@echo "Generating debug build project"
|
||||
$(QUIET)$(MAKE) DEBUGBUILD=true qtproject
|
||||
|
||||
qtproject:
|
||||
$(QUIET)rm -f $(target).files $(target).cflags $(target).config $(target).creator $(target).includes $(target).creator.user
|
||||
@echo "Generating source file list"
|
||||
$(QUIET)echo "$(CFILES)" | tr ' ' '\n' > $(target).files
|
||||
@echo -n "Appending found header files from folders "
|
||||
@echo `echo $(INCLUDEPATH) | sed "s/-I//g"`
|
||||
$(QUIET)for dir in `echo $(INCLUDEPATH) | sed "s/-I//g"`; do \
|
||||
find `echo "$${dir}"` -name "*.h" >> $(target).files; \
|
||||
done
|
||||
@echo "Generating $(target).cflags"
|
||||
$(QUIET)echo "" > $(target).cflags
|
||||
@echo "Generating $(target).includes"
|
||||
$(QUIET)echo $(INCLUDEPATH) | sed "s/-I/,/g" | tr , '\n' | sed "/^$$/d" > $(target).includes;
|
||||
@echo "Generating $(target).config"
|
||||
$(QUIET)echo $(DEFINES) | sed "s/-D/,#define /g" | tr , '\n' | sed "/^$$/d" > $(target).config
|
||||
@echo "Generating $(target).creator"
|
||||
$(QUIET)echo "[GENERAL]" > $(target).creator
|
||||
|
||||
-include $(CFILES:%.c=$(OBJDIR)/%.c.d) $(ASFILES:%.S=$(OBJDIR)/%.S.d)
|
@ -29,7 +29,7 @@
|
||||
#include <stm-periph/stm32-gpio-macros.h>
|
||||
#include <stdlib.h>
|
||||
#include <helper-macros/helper-macros.h>
|
||||
#include <stm-periph/clock-enable-manager.h>
|
||||
#include <stm-periph/rcc-manager.h>
|
||||
#include <reflow-controller/safety/safety-controller.h>
|
||||
|
||||
static float IN_SECTION(.ccm.bss) pt1000_offset;
|
||||
@ -45,7 +45,6 @@ static float IN_SECTION(.ccm.bss) filter_alpha;
|
||||
static volatile float IN_SECTION(.ccm.bss) pt1000_res_raw_lf;
|
||||
|
||||
static volatile int * volatile streaming_flag_ptr;
|
||||
static uint32_t IN_SECTION(.ccm.bss) filter_startup_cnt;
|
||||
static volatile float IN_SECTION(.ccm.bss) adc_pt1000_raw_reading_hf;
|
||||
static volatile uint16_t dma_sample_buffer[ADC_PT1000_DMA_AVG_SAMPLES];
|
||||
static volatile uint32_t IN_SECTION(.ccm.bss) adc_watchdog_counter;
|
||||
@ -188,7 +187,6 @@ void adc_pt1000_set_moving_average_filter_param(float alpha)
|
||||
{
|
||||
filter_alpha = alpha;
|
||||
safety_controller_report_error_with_key(ERR_FLAG_MEAS_ADC_UNSTABLE, MEAS_ADC_SAFETY_FLAG_KEY);
|
||||
filter_startup_cnt = ADC_FILTER_STARTUP_CYCLES;
|
||||
}
|
||||
|
||||
void adc_pt1000_set_resistance_calibration(float offset, float sensitivity_deviation, bool active)
|
||||
@ -216,7 +214,7 @@ void adc_pt1000_get_resistance_calibration(float *offset, float *sensitivity_dev
|
||||
static inline float adc_pt1000_apply_calibration(float raw_resistance)
|
||||
{
|
||||
if (calibration_active)
|
||||
return pt1000_res_raw_lf * (1.0f + pt1000_sens_dev) + pt1000_offset;
|
||||
return (raw_resistance - pt1000_offset) * (1.0f + pt1000_sens_dev);
|
||||
else
|
||||
return raw_resistance;
|
||||
|
||||
@ -234,7 +232,7 @@ int adc_pt1000_get_current_resistance(float *resistance)
|
||||
*resistance = adc_pt1000_apply_calibration(pt1000_res_raw_lf);
|
||||
|
||||
if (safety_controller_get_flags_by_mask(ERR_FLAG_MEAS_ADC_OFF | ERR_FLAG_MEAS_ADC_OVERFLOW |
|
||||
ERR_FLAG_MEAS_ADC_WATCHDOG)) {
|
||||
ERR_FLAG_MEAS_ADC_WATCHDOG | ERR_FLAG_TIMING_MEAS_ADC)) {
|
||||
ret_val = -100;
|
||||
goto return_value;
|
||||
}
|
||||
@ -303,13 +301,33 @@ void adc_pt1000_disable(void)
|
||||
|
||||
static inline __attribute__((optimize("O3"))) void adc_pt1000_filter(float adc_prefiltered_value)
|
||||
{
|
||||
if (filter_startup_cnt > 0) {
|
||||
filter_startup_cnt--;
|
||||
if (filter_startup_cnt == 0)
|
||||
safety_controller_ack_flag_with_key(ERR_FLAG_MEAS_ADC_UNSTABLE, MEAS_ADC_SAFETY_FLAG_KEY);
|
||||
float alpha;
|
||||
float res;
|
||||
static uint8_t old_state = 0;
|
||||
static uint32_t stable_sample_counter = 0;
|
||||
|
||||
res = ADC_TO_RES(adc_prefiltered_value);
|
||||
if (ABS(res - pt1000_res_raw_lf) >= ADC_PT1000_FILTER_UNSTABLE_DIFF) {
|
||||
stable_sample_counter = 0;
|
||||
alpha = ADC_PT1000_FILTER_WEIGHT_FAST;
|
||||
if (old_state != 1) {
|
||||
safety_controller_report_error_with_key(ERR_FLAG_MEAS_ADC_UNSTABLE, MEAS_ADC_SAFETY_FLAG_KEY);
|
||||
old_state = 1;
|
||||
}
|
||||
} else {
|
||||
alpha = filter_alpha;
|
||||
if (old_state != 2) {
|
||||
stable_sample_counter++;
|
||||
if (stable_sample_counter >= ADC_PT1000_FILTER_STABLE_SAMPLE_COUNT) {
|
||||
safety_controller_ack_flag_with_key(ERR_FLAG_MEAS_ADC_UNSTABLE, MEAS_ADC_SAFETY_FLAG_KEY);
|
||||
old_state = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pt1000_res_raw_lf = (1.0f-filter_alpha) * pt1000_res_raw_lf + filter_alpha * ADC_TO_RES(adc_prefiltered_value);
|
||||
pt1000_res_raw_lf = (1.0f - alpha) * pt1000_res_raw_lf +
|
||||
alpha * res;
|
||||
|
||||
safety_controller_report_timing(ERR_TIMING_MEAS_ADC);
|
||||
}
|
||||
|
||||
@ -380,11 +398,11 @@ void DMA2_Stream0_IRQHandler(void)
|
||||
uint32_t lisr;
|
||||
float adc_val;
|
||||
|
||||
lisr = DMA2->LISR;
|
||||
lisr = DMA2->LISR & (0x3F);
|
||||
DMA2->LIFCR = lisr;
|
||||
|
||||
if (lisr & DMA_LISR_TCIF0) {
|
||||
/* Samples Transfered */
|
||||
/* Samples transferred */
|
||||
adc_val = adc_pt1000_dma_avg_pre_filter();
|
||||
adc_pt1000_raw_reading_hf = adc_val;
|
||||
|
||||
|
28
stm-firmware/arm-none-eabi-gcc.cmake
Normal file
28
stm-firmware/arm-none-eabi-gcc.cmake
Normal file
@ -0,0 +1,28 @@
|
||||
if(MINGW OR CYGWIN OR WIN32)
|
||||
set(UTIL_SEARCH_CMD where)
|
||||
elseif(UNIX OR APPLE)
|
||||
set(UTIL_SEARCH_CMD which)
|
||||
endif()
|
||||
|
||||
set(TOOLCHAIN_PREFIX arm-none-eabi-)
|
||||
|
||||
execute_process(
|
||||
COMMAND ${UTIL_SEARCH_CMD} ${TOOLCHAIN_PREFIX}gcc
|
||||
OUTPUT_VARIABLE BINUTILS_PATH
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
|
||||
get_filename_component(ARM_TOOLCHAIN_DIR ${BINUTILS_PATH} DIRECTORY)
|
||||
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
|
||||
|
||||
set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}gcc)
|
||||
set(CMAKE_ASM_COMPILER ${CMAKE_C_COMPILER})
|
||||
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}g++)
|
||||
|
||||
set(CMAKE_OBJCOPY ${ARM_TOOLCHAIN_DIR}/${TOOLCHAIN_PREFIX}objcopy CACHE INTERNAL "objcopy tool")
|
||||
set(CMAKE_SIZE_UTIL ${ARM_TOOLCHAIN_DIR}/${TOOLCHAIN_PREFIX}size CACHE INTERNAL "size tool")
|
||||
|
||||
set(CMAKE_FIND_ROOT_PATH ${BINUTILS_PATH})
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
1
stm-firmware/base64-lib
Submodule
1
stm-firmware/base64-lib
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 251e90abf349d05bdb0d63afa86c478d8b0ebb98
|
@ -21,7 +21,7 @@
|
||||
#include <stm32/stm32f4xx.h>
|
||||
#include <reflow-controller/button.h>
|
||||
#include <stm-periph/stm32-gpio-macros.h>
|
||||
#include <stm-periph/clock-enable-manager.h>
|
||||
#include <stm-periph/rcc-manager.h>
|
||||
#include <stdint.h>
|
||||
#include <helper-macros/helper-macros.h>
|
||||
#include <cmsis/core_cm4.h>
|
||||
|
@ -23,7 +23,6 @@
|
||||
#include <reflow-controller/adc-meas.h>
|
||||
#include <stm-periph/uart.h>
|
||||
#include <helper-macros/helper-macros.h>
|
||||
#include <arm_math.h>
|
||||
#include <stdlib.h>
|
||||
#include <float.h>
|
||||
#include <reflow-controller/safety/safety-controller.h>
|
||||
@ -31,9 +30,9 @@
|
||||
enum calibration_shell_state {CAL_START = 0, CAL_WAIT_RES1, CAL_MEAS_RES1, CAL_WAIT_RES2, CAL_MEAS_RES2};
|
||||
|
||||
void calibration_calculate(float low_measured, float low_setpoint, float high_measured, float high_setpoint,
|
||||
float *sens_deviation, float *sens_corrected_offset)
|
||||
float *sens_deviation, float *offset)
|
||||
{
|
||||
if (!sens_deviation || !sens_corrected_offset)
|
||||
if (!sens_deviation || !offset)
|
||||
return;
|
||||
|
||||
float delta_y;
|
||||
@ -46,11 +45,9 @@ void calibration_calculate(float low_measured, float low_setpoint, float high_me
|
||||
sens_corr_mult = delta_x / delta_y;
|
||||
*sens_deviation = sens_corr_mult - 1.0f;
|
||||
|
||||
*sens_corrected_offset = low_setpoint - low_measured * sens_corr_mult;
|
||||
*offset = (high_setpoint * low_measured - low_setpoint * high_measured) / (high_setpoint - low_setpoint);
|
||||
}
|
||||
|
||||
|
||||
|
||||
float *calibration_acquire_data_start(uint32_t count, volatile int *flag)
|
||||
{
|
||||
int status;
|
||||
@ -75,14 +72,77 @@ free_mem:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int calibration_poll_data_acquisition(float *mem_array, uint32_t count, volatile int *flag, float *mu, float *max_dev)
|
||||
static float calculate_mean(float *values, uint32_t count)
|
||||
{
|
||||
uint32_t loop_cnt = (count + 7) / 8;
|
||||
uint32_t remainder = count % 8;
|
||||
float sum = 0;
|
||||
|
||||
switch (remainder) {
|
||||
case 0: do { sum += *values++; /* FALLTHRU */
|
||||
case 7: sum += *values++; /* FALLTHRU */
|
||||
case 6: sum += *values++; /* FALLTHRU */
|
||||
case 5: sum += *values++; /* FALLTHRU */
|
||||
case 4: sum += *values++; /* FALLTHRU */
|
||||
case 3: sum += *values++; /* FALLTHRU */
|
||||
case 2: sum += *values++; /* FALLTHRU */
|
||||
case 1: sum += *values++;
|
||||
} while (--loop_cnt > 0);
|
||||
}
|
||||
|
||||
return sum / (float)count;
|
||||
}
|
||||
|
||||
static float calculate_standard_deviation(float *values, uint32_t count, float mean)
|
||||
{
|
||||
uint32_t loop_cnt = (count + 7) / 8;
|
||||
uint32_t remainder = count % 8;
|
||||
float sum = 0;
|
||||
float res;
|
||||
|
||||
switch (remainder) {
|
||||
case 0: do {
|
||||
sum += (*values - mean) * (*values - mean);
|
||||
values++;
|
||||
/* FALLTHRU */
|
||||
case 7: sum += (*values - mean) * (*values - mean);
|
||||
values++;
|
||||
/* FALLTHRU */
|
||||
case 6: sum += (*values - mean) * (*values - mean);
|
||||
values++;
|
||||
/* FALLTHRU */
|
||||
case 5: sum += (*values - mean) * (*values - mean);
|
||||
values++;
|
||||
/* FALLTHRU */
|
||||
case 4: sum += (*values - mean) * (*values - mean);
|
||||
values++;
|
||||
/* FALLTHRU */
|
||||
case 3: sum += (*values - mean) * (*values - mean);
|
||||
values++;
|
||||
/* FALLTHRU */
|
||||
case 2: sum += (*values - mean) * (*values - mean);
|
||||
values++;
|
||||
/* FALLTHRU */
|
||||
case 1: sum += (*values - mean) * (*values - mean);
|
||||
values++;
|
||||
/* FALLTHRU */
|
||||
} while (--loop_cnt > 0);
|
||||
}
|
||||
|
||||
sum /= (float)count;
|
||||
/* Compute the square root using the FPU.
|
||||
* The constraint 't' tells GCC to use a floating point register
|
||||
*/
|
||||
__asm__ __volatile__("vsqrt.f32 %0, %1" : "=t"(res) : "t"(sum));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int calibration_poll_data_acquisition(float *mem_array, uint32_t count, volatile int *flag, float *mu, float *std_dev)
|
||||
{
|
||||
int ret_val = 0;
|
||||
float min_val = FLT_MAX;
|
||||
float max_val = -FLT_MAX;
|
||||
uint32_t i;
|
||||
|
||||
if (!flag || !mem_array || !mu || !max_dev)
|
||||
if (!flag || !mem_array || !mu || !std_dev)
|
||||
return -1000;
|
||||
|
||||
if (*flag == 0) {
|
||||
@ -99,19 +159,9 @@ static int calibration_poll_data_acquisition(float *mem_array, uint32_t count, v
|
||||
/* Convert the stream memory to Ohm readings */
|
||||
adc_pt1000_convert_raw_value_array_to_resistance(NULL, mem_array, count);
|
||||
|
||||
/* Do not compute std-deviation. Too imprecise
|
||||
* arm_std_f32(stream_mem, count, sigma);
|
||||
*/
|
||||
arm_mean_f32(mem_array, count, mu);
|
||||
|
||||
/* Find min and max values of array */
|
||||
for (i = 0U; i < count; i++) {
|
||||
min_val = MIN(min_val, mem_array[i]);
|
||||
max_val = MAX(max_val, mem_array[i]);
|
||||
}
|
||||
|
||||
/* Compute maximum deviation range */
|
||||
*max_dev = max_val - min_val;
|
||||
/* Do not compute std-deviation. Too imprecise */
|
||||
*mu = calculate_mean(mem_array, count);
|
||||
*std_dev = calculate_standard_deviation(mem_array, count, *mu);
|
||||
|
||||
ret_free_mem:
|
||||
free(mem_array);
|
||||
@ -137,12 +187,18 @@ shellmatta_retCode_t calibration_sequence_shell_cmd(shellmatta_handle_t shell, c
|
||||
static volatile int flag;
|
||||
char *stdin_data;
|
||||
uint32_t stdin_len;
|
||||
|
||||
bool cal_active;
|
||||
|
||||
switch (cal_state) {
|
||||
case CAL_START:
|
||||
/* Clear errors of PT1000 reading */
|
||||
safety_controller_ack_flag(ERR_FLAG_MEAS_ADC_WATCHDOG);
|
||||
safety_controller_ack_flag(ERR_FLAG_OVERTEMP);
|
||||
adc_pt1000_get_resistance_calibration(&offset, &sens_dev, &cal_active);
|
||||
if (cal_active) {
|
||||
shellmatta_printf(shell, "Already calibrated.\r\n\tOffset: %f\r\n\tSens: %f\r\n", offset, sens_dev);
|
||||
shellmatta_printf(shell, "Press CTRL-C to abort new calibration.\r\n");
|
||||
}
|
||||
shellmatta_printf(shell, "Starting calibration: Insert 1000 Ohm calibration resistor and press ENTER\r\n");
|
||||
cal_state = CAL_WAIT_RES1;
|
||||
ret_val = SHELLMATTA_CONTINUE;
|
||||
@ -158,6 +214,7 @@ shellmatta_retCode_t calibration_sequence_shell_cmd(shellmatta_handle_t shell, c
|
||||
ret_val = SHELLMATTA_BUSY;
|
||||
shellmatta_printf(shell, "Measurement...\r\n");
|
||||
safety_controller_ack_flag(ERR_FLAG_MEAS_ADC_WATCHDOG);
|
||||
safety_controller_ack_flag(ERR_FLAG_OVERTEMP);
|
||||
data_buffer = calibration_acquire_data_start(512UL, &flag);
|
||||
break;
|
||||
} else if (stdin_data[i] == '\x03') {
|
||||
@ -181,7 +238,7 @@ shellmatta_retCode_t calibration_sequence_shell_cmd(shellmatta_handle_t shell, c
|
||||
ret_val = SHELLMATTA_BUSY;
|
||||
cal_state = CAL_MEAS_RES1;
|
||||
} else if (res == 0) {
|
||||
shellmatta_printf(shell, "R=%.2f, Noise peak-peak: %.2f\r\n", mu, dev);
|
||||
shellmatta_printf(shell, "R=%.2f, Std-Dev: %.2f\r\n", mu, dev);
|
||||
error_occured = safety_controller_get_flags_by_mask(meas_adc_err_mask);
|
||||
if (error_occured) {
|
||||
shellmatta_printf(shell, "Error in resistance measurement");
|
||||
@ -209,6 +266,7 @@ shellmatta_retCode_t calibration_sequence_shell_cmd(shellmatta_handle_t shell, c
|
||||
ret_val = SHELLMATTA_BUSY;
|
||||
shellmatta_printf(shell, "Measurement...\r\n");
|
||||
safety_controller_ack_flag(ERR_FLAG_MEAS_ADC_WATCHDOG);
|
||||
safety_controller_ack_flag(ERR_FLAG_OVERTEMP);
|
||||
data_buffer = calibration_acquire_data_start(512UL, &flag);
|
||||
break;
|
||||
} else if (stdin_data[i] == '\x03') {
|
||||
@ -232,7 +290,7 @@ shellmatta_retCode_t calibration_sequence_shell_cmd(shellmatta_handle_t shell, c
|
||||
ret_val = SHELLMATTA_BUSY;
|
||||
cal_state = CAL_MEAS_RES2;
|
||||
} else if (res == 0) {
|
||||
shellmatta_printf(shell, "R=%.2f, Noise peak-peak: %.2f\r\n", mu2, dev2);
|
||||
shellmatta_printf(shell, "R=%.2f, Std-Dev: %.2f\r\n", mu2, dev2);
|
||||
error_occured = safety_controller_get_flags_by_mask(meas_adc_err_mask);
|
||||
if (error_occured) {
|
||||
shellmatta_printf(shell, "Error in resistance measurement");
|
||||
@ -242,8 +300,8 @@ shellmatta_retCode_t calibration_sequence_shell_cmd(shellmatta_handle_t shell, c
|
||||
ret_val = SHELLMATTA_OK;
|
||||
cal_state = CAL_START;
|
||||
|
||||
if (dev > CALIBRATION_MAX_PEAK_PEAK_NOISE_OHM ||
|
||||
dev2 > CALIBRATION_MAX_PEAK_PEAK_NOISE_OHM) {
|
||||
if (dev > CALIBRATION_MAX_NOISE_OHM ||
|
||||
dev2 > CALIBRATION_MAX_NOISE_OHM) {
|
||||
shellmatta_printf(shell, "Calibration failed! Too much noise. Check your hardware.\r\n");
|
||||
break;
|
||||
}
|
||||
|
202
stm-firmware/config-parser/config-parser.c
Normal file
202
stm-firmware/config-parser/config-parser.c
Normal file
@ -0,0 +1,202 @@
|
||||
/* Reflow Oven Controller
|
||||
*
|
||||
* Copyright (C) 2020 Mario Hüttel <mario.huettel@gmx.net>
|
||||
*
|
||||
* This file is part of the Reflow Oven Controller Project.
|
||||
*
|
||||
* The reflow oven controller is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* The Reflow Oven Control Firmware 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 the reflow oven controller project.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup config-parser
|
||||
* @{
|
||||
*/
|
||||
|
||||
#include <config-parser/config-parser.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define CONFIG_PARSER_MAGIC 0x464a6e2bUL
|
||||
#define CONFIG_PARSER(p) ((struct config_parser *)(p))
|
||||
|
||||
#define config_parser_check_handle(handle) do { if (!(handle) || \
|
||||
((struct config_parser *)(handle))->magic != CONFIG_PARSER_MAGIC) \
|
||||
return CONFIG_PARSER_PARAM_ERR; \
|
||||
} while (0)
|
||||
|
||||
config_parser_handle_t config_parser_open_file(struct config_parser *config_parser, bool write, const char *file_name,
|
||||
char *working_buffer, size_t buff_size)
|
||||
{
|
||||
FRESULT res;
|
||||
|
||||
if (!config_parser || !file_name || !working_buffer)
|
||||
return NULL;
|
||||
|
||||
config_parser->magic = CONFIG_PARSER_MAGIC;
|
||||
config_parser->write = write;
|
||||
config_parser->buffer = working_buffer;
|
||||
config_parser->buff_size = buff_size;
|
||||
|
||||
res = f_open(&config_parser->file, file_name, (write ? FA_CREATE_ALWAYS | FA_WRITE : FA_READ));
|
||||
if (res != FR_OK)
|
||||
return NULL;
|
||||
|
||||
return (config_parser_handle_t)config_parser;
|
||||
}
|
||||
|
||||
static const char * const token_delim = " \t";
|
||||
|
||||
static int parse_value(struct config_parser_entry *entry, char *value_start_token)
|
||||
{
|
||||
char *dot;
|
||||
char *endptr;
|
||||
|
||||
/* Check if token is a float number */
|
||||
dot = strstr(value_start_token, ".");
|
||||
if (dot) {
|
||||
/* Try parsing as float */
|
||||
entry->value.float_val = strtof(value_start_token, &endptr);
|
||||
if (endptr == value_start_token)
|
||||
return -1;
|
||||
entry->type = CONFIG_PARSER_TYPE_FLOAT;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (value_start_token[0] != '-') {
|
||||
/* Try parsing as ul */
|
||||
/* Try parsing as int */
|
||||
entry->value.uint_val = strtoul(value_start_token, &endptr, 0);
|
||||
if (endptr == value_start_token) {
|
||||
return -1;
|
||||
}
|
||||
entry->type = CONFIG_PARSER_TYPE_UINT;
|
||||
goto exit;
|
||||
} else {
|
||||
/* Try parsing as int */
|
||||
entry->value.int_val = strtod(value_start_token, &endptr);
|
||||
if (endptr == value_start_token) {
|
||||
return -1;
|
||||
}
|
||||
entry->type = CONFIG_PARSER_TYPE_INT;
|
||||
}
|
||||
|
||||
exit:
|
||||
return 0;
|
||||
}
|
||||
|
||||
enum config_parser_ret config_parser_get_line(config_parser_handle_t handle, struct config_parser_entry *entry, bool force_float)
|
||||
{
|
||||
struct config_parser *p;
|
||||
config_parser_check_handle(handle);
|
||||
p = CONFIG_PARSER(handle);
|
||||
char *token;
|
||||
int token_round = 0;
|
||||
|
||||
if (!entry)
|
||||
return CONFIG_PARSER_PARAM_ERR;
|
||||
|
||||
p->buffer[0] = '\0';
|
||||
if (f_gets(p->buffer, (int)p->buff_size, &p->file) == NULL)
|
||||
return CONFIG_PARSER_IOERR;
|
||||
|
||||
token = strtok(p->buffer, token_delim);
|
||||
while (token != NULL) {
|
||||
/* Check for comment */
|
||||
if (token[0] == '#') {
|
||||
if (token_round == 0)
|
||||
return CONFIG_PARSER_LINE_COMMENT;
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
switch (token_round) {
|
||||
case 0: /* KEY */
|
||||
entry->name = token;
|
||||
break;
|
||||
case 1: /* = Symbol */
|
||||
if (strcmp(token, "=")) {
|
||||
return CONFIG_PARSER_LINE_MALFORM;
|
||||
}
|
||||
break;
|
||||
case 2: /* VALUE */
|
||||
if (parse_value(entry, token))
|
||||
return CONFIG_PARSER_LINE_MALFORM;
|
||||
break;
|
||||
default:
|
||||
return CONFIG_PARSER_LINE_MALFORM;
|
||||
}
|
||||
|
||||
token_round++;
|
||||
token = strtok(NULL, token_delim);
|
||||
}
|
||||
|
||||
if (force_float) {
|
||||
if (entry->type == CONFIG_PARSER_TYPE_INT)
|
||||
entry->value.float_val = (float)entry->value.int_val;
|
||||
if (entry->type == CONFIG_PARSER_TYPE_UINT)
|
||||
entry->value.float_val = (float)entry->value.uint_val;
|
||||
|
||||
entry->type = CONFIG_PARSER_TYPE_FLOAT;
|
||||
}
|
||||
|
||||
return CONFIG_PARSER_OK;
|
||||
}
|
||||
|
||||
enum config_parser_ret config_parser_reset_to_start(config_parser_handle_t handle)
|
||||
{
|
||||
FRESULT res;
|
||||
struct config_parser *p;
|
||||
config_parser_check_handle(handle);
|
||||
p = CONFIG_PARSER(handle);
|
||||
|
||||
res = f_rewind(&p->file);
|
||||
if (res != FR_OK)
|
||||
return CONFIG_PARSER_IOERR;
|
||||
|
||||
return CONFIG_PARSER_OK;
|
||||
}
|
||||
|
||||
enum config_parser_ret config_parser_write_entry(config_parser_handle_t handle, const struct config_parser_entry *entry)
|
||||
{
|
||||
(void)entry;
|
||||
config_parser_check_handle(handle);
|
||||
|
||||
return CONFIG_PARSER_OK;
|
||||
}
|
||||
|
||||
enum config_parser_ret config_parser_close_file(config_parser_handle_t handle)
|
||||
{
|
||||
struct config_parser *p;
|
||||
FRESULT res;
|
||||
config_parser_check_handle(handle);
|
||||
p = CONFIG_PARSER(handle);
|
||||
|
||||
res = f_close(&p->file);
|
||||
|
||||
return (res == FR_OK ? CONFIG_PARSER_OK : CONFIG_PARSER_IOERR);
|
||||
}
|
||||
|
||||
bool config_parser_ret_is_abort_condition(enum config_parser_ret return_val)
|
||||
{
|
||||
if (return_val == CONFIG_PARSER_END_REACHED ||
|
||||
return_val == CONFIG_PARSER_GENERIC_ERR ||
|
||||
return_val == CONFIG_PARSER_IOERR ||
|
||||
return_val == CONFIG_PARSER_PARAM_ERR)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @} */
|
@ -0,0 +1,97 @@
|
||||
/* Reflow Oven Controller
|
||||
*
|
||||
* Copyright (C) 2020 Mario Hüttel <mario.huettel@gmx.net>
|
||||
*
|
||||
* This file is part of the Reflow Oven Controller Project.
|
||||
*
|
||||
* The reflow oven controller is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* The Reflow Oven Control Firmware 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 the reflow oven controller project.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file config-parser.h
|
||||
* @brief Header file for the key-value pair config parser
|
||||
* @addtogroup config-parser
|
||||
* @{
|
||||
*/
|
||||
|
||||
#ifndef _CONFIG_PARSER_H_
|
||||
#define _CONFIG_PARSER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <fatfs/ff.h>
|
||||
|
||||
struct config_parser {
|
||||
uint32_t magic;
|
||||
bool write;
|
||||
FIL file;
|
||||
char *buffer;
|
||||
size_t buff_size;
|
||||
};
|
||||
|
||||
typedef void * config_parser_handle_t;
|
||||
|
||||
enum config_parser_value_type {
|
||||
CONFIG_PARSER_TYPE_UINT = 0,
|
||||
CONFIG_PARSER_TYPE_INT,
|
||||
CONFIG_PARSER_TYPE_FLOAT,
|
||||
};
|
||||
|
||||
enum config_parser_ret {
|
||||
CONFIG_PARSER_OK = 0,
|
||||
CONFIG_PARSER_PARAM_ERR,
|
||||
CONFIG_PARSER_GENERIC_ERR,
|
||||
CONFIG_PARSER_IOERR,
|
||||
CONFIG_PARSER_LINE_COMMENT,
|
||||
CONFIG_PARSER_LINE_TOO_LONG,
|
||||
CONFIG_PARSER_LINE_MALFORM,
|
||||
CONFIG_PARSER_END_REACHED,
|
||||
CONFIG_PARSER_WRONG_MODE,
|
||||
};
|
||||
|
||||
struct config_parser_entry {
|
||||
const char *name;
|
||||
enum config_parser_value_type type;
|
||||
union {
|
||||
uint32_t uint_val;
|
||||
int32_t int_val;
|
||||
float float_val;
|
||||
} value;
|
||||
};
|
||||
|
||||
config_parser_handle_t config_parser_open_file(struct config_parser *config_parser, bool write, const char *file_name,
|
||||
char *working_buffer, size_t buff_size);
|
||||
|
||||
/**
|
||||
* @brief Parse the current line in the config file.
|
||||
* @param handle Config parser handle
|
||||
* @param[out] entry Entry read from config file.
|
||||
* @warning \p entry is only valid as long as no other function was called on the same \p handle. If necessary, the values
|
||||
* have to be copied
|
||||
* @return Config parser error
|
||||
*/
|
||||
enum config_parser_ret config_parser_get_line(config_parser_handle_t handle, struct config_parser_entry *entry, bool force_float);
|
||||
|
||||
enum config_parser_ret config_parser_reset_to_start(config_parser_handle_t handle);
|
||||
|
||||
enum config_parser_ret config_parser_write_entry(config_parser_handle_t handle, const struct config_parser_entry *entry);
|
||||
|
||||
enum config_parser_ret config_parser_close_file(config_parser_handle_t handle);
|
||||
|
||||
bool config_parser_ret_is_abort_condition(enum config_parser_ret return_val);
|
||||
|
||||
#endif /* _CONFIG_PARSER_H_ */
|
||||
|
||||
/** @} */
|
214
stm-firmware/config-parser/temp-profile-parser.c
Normal file
214
stm-firmware/config-parser/temp-profile-parser.c
Normal file
@ -0,0 +1,214 @@
|
||||
/* Reflow Oven Controller
|
||||
*
|
||||
* Copyright (C) 2021 Mario Hüttel <mario.huettel@gmx.net>
|
||||
*
|
||||
* This file is part of the Reflow Oven Controller Project.
|
||||
*
|
||||
* The reflow oven controller is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* The Reflow Oven Control Firmware 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 the reflow oven controller project.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <reflow-controller/config-parser/temp-profile-parser.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <fatfs/ff.h>
|
||||
|
||||
struct pl_command_list_map {
|
||||
enum pl_command_type command;
|
||||
const char * const token;
|
||||
uint8_t expected_param_count;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This list stores the command tokens and the expected number of arguments for each command
|
||||
*/
|
||||
static const struct pl_command_list_map cmd_list_map[_PL_NUM_CMDS] = {
|
||||
{PL_PID_CONF, "pid_conf", 6u},
|
||||
{PL_SET_TEMP, "temp_set", 1u},
|
||||
{PL_WAIT_FOR_TEMP, "wait_temp", 1u},
|
||||
{PL_WAIT_FOR_TIME, "wait_time", 1u},
|
||||
{PL_SET_RAMP, "temp_ramp", 2u},
|
||||
{PL_LOUDSPEAKER_SET, "beep", 1u},
|
||||
{PL_OFF, "temp_off", 0u}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Read a line in the file until a line break is detected or a comment is started.
|
||||
*
|
||||
* In case a comment is detected, it is still read form the file to ensure
|
||||
* the next line is read afterwards
|
||||
*
|
||||
* @param f File to read from
|
||||
* @param buffer Buffer to read in
|
||||
* @param buffsize buffer size
|
||||
* @return 0 if successful, -1 if disk error,
|
||||
*/
|
||||
static int read_line_until_comment(FIL *f, char *buffer, uint32_t buffsize)
|
||||
{
|
||||
char *ptr;
|
||||
uint32_t i;
|
||||
|
||||
if (!f || !buffsize || !buffer)
|
||||
return -1000;
|
||||
|
||||
buffer[0] = 0;
|
||||
/* Read the line from file */
|
||||
ptr = f_gets(buffer, (int)buffsize, f);
|
||||
if (!ptr) {
|
||||
buffer[0] = 0;
|
||||
return -2;
|
||||
}
|
||||
|
||||
/* Go through the line until we encounter a # sign or the end of line*/
|
||||
for (i = 0; i < buffsize; i++) {
|
||||
if (buffer[i] == '\n' || buffer[i] == '#') {
|
||||
buffer[i] = 0;
|
||||
break;
|
||||
} else if (buffer[i] == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct pl_command_list_map *string_to_command(const char *str)
|
||||
{
|
||||
uint32_t i;
|
||||
const struct pl_command_list_map *ret = NULL;
|
||||
|
||||
if (!str)
|
||||
return NULL;
|
||||
|
||||
for (i = 0u; i < _PL_NUM_CMDS; i++) {
|
||||
if (!strcmp(str, cmd_list_map[i].token)) {
|
||||
ret = &cmd_list_map[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int parse_line(char *line, struct pl_command *cmd)
|
||||
{
|
||||
uint8_t token_idx = 0;
|
||||
char *token;
|
||||
const char * const delim = " \t";
|
||||
const struct pl_command_list_map *map;
|
||||
char *endptr;
|
||||
struct pl_command c;
|
||||
|
||||
if (!line || !cmd)
|
||||
return -1000;
|
||||
|
||||
token = strtok(line, delim);
|
||||
|
||||
if (!token) {
|
||||
/* Empty line or command line */
|
||||
return 1;
|
||||
}
|
||||
|
||||
while (token && token_idx <= PROFILE_LANG_MAX_NUM_ARGS) {
|
||||
switch (token_idx) {
|
||||
case 0:
|
||||
map = string_to_command(token);
|
||||
c.cmd = map->command;
|
||||
break;
|
||||
default:
|
||||
if (!map) {
|
||||
/* No valid command found */
|
||||
return -1;
|
||||
}
|
||||
c.params[token_idx - 1] = strtof(token, &endptr);
|
||||
if (endptr == token) {
|
||||
/* Invalid parameter */
|
||||
return -2;
|
||||
}
|
||||
if (token_idx > map->expected_param_count)
|
||||
return -3;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
token = strtok(NULL, delim);
|
||||
token_idx++;
|
||||
}
|
||||
|
||||
if (token_idx - 1 < map->expected_param_count) {
|
||||
return -3;
|
||||
}
|
||||
|
||||
memcpy(cmd, &c, sizeof(struct pl_command));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
enum pl_ret_val temp_profile_parse_from_file(const char *filename,
|
||||
struct pl_command *cmd_list,
|
||||
uint32_t cmd_list_length,
|
||||
uint32_t *cmds_parsed)
|
||||
{
|
||||
FIL script_file;
|
||||
FRESULT fres;
|
||||
int res;
|
||||
enum pl_ret_val ret = PL_RET_SUCCESS;
|
||||
char workbuff[256];
|
||||
uint32_t cmd_idx;
|
||||
|
||||
if (!filename || !cmd_list || !cmd_list_length || !cmds_parsed)
|
||||
return PL_RET_PARAM_ERR;
|
||||
|
||||
|
||||
fres = f_open(&script_file, filename, FA_READ);
|
||||
if (fres != FR_OK) {
|
||||
ret = PL_RET_DISK_ERR;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
cmd_idx = 0;
|
||||
*cmds_parsed = 0;
|
||||
|
||||
do {
|
||||
/* read in the line */
|
||||
res = read_line_until_comment(&script_file, workbuff, sizeof(workbuff));
|
||||
if (res < 0) {
|
||||
ret = PL_RET_DISK_ERR;
|
||||
goto exit_close;
|
||||
}
|
||||
|
||||
/* Check if list already full */
|
||||
if (cmd_idx >= cmd_list_length) {
|
||||
ret = PL_RET_LIST_FULL;
|
||||
goto exit_close;
|
||||
}
|
||||
|
||||
/* Parse the line */
|
||||
res = parse_line(workbuff, &cmd_list[cmd_idx]);
|
||||
if (res < 0) {
|
||||
ret = PL_RET_SCRIPT_ERR;
|
||||
goto exit_close;
|
||||
} else if (res == 0) {
|
||||
cmd_idx++;
|
||||
*cmds_parsed= cmd_idx;
|
||||
}
|
||||
|
||||
|
||||
} while (!f_eof(&script_file));
|
||||
|
||||
exit_close:
|
||||
(void)f_close(&script_file);
|
||||
exit:
|
||||
return ret;
|
||||
}
|
@ -20,7 +20,7 @@
|
||||
|
||||
#include <reflow-controller/digio.h>
|
||||
#include <stm32/stm32f4xx.h>
|
||||
#include <stm-periph/clock-enable-manager.h>
|
||||
#include <stm-periph/rcc-manager.h>
|
||||
#include <stm-periph/stm32-gpio-macros.h>
|
||||
#include <helper-macros/helper-macros.h>
|
||||
|
||||
@ -146,7 +146,7 @@ void loudspeaker_setup(void)
|
||||
static void loudspeaker_start_beep(uint16_t val)
|
||||
{
|
||||
#if LOUDSPEAKER_MULTIFREQ
|
||||
TIM7->ARR = val;
|
||||
TIM7->ARR = (val == 1 ? LOUDSPEAKER_MULTIFREQ_DEFAULT : val);
|
||||
TIM7->CNT = 0UL;
|
||||
TIM7->CR1 |= TIM_CR1_CEN;
|
||||
#else
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Doxyfile 1.8.18
|
||||
# Doxyfile 1.8.20
|
||||
|
||||
# This file describes the settings to be used by the documentation system
|
||||
# doxygen (www.doxygen.org) for a project.
|
||||
@ -186,7 +186,7 @@ STRIP_FROM_INC_PATH =
|
||||
# support long names like on DOS, Mac, or CD-ROM.
|
||||
# The default value is: NO.
|
||||
|
||||
SHORT_NAMES = NO
|
||||
SHORT_NAMES = YES
|
||||
|
||||
# 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
|
||||
@ -227,6 +227,14 @@ QT_AUTOBRIEF = NO
|
||||
|
||||
MULTILINE_CPP_IS_BRIEF = NO
|
||||
|
||||
# By default Python docstrings are displayed as preformatted text and doxygen's
|
||||
# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the
|
||||
# doxygen's special commands can be used and the contents of the docstring
|
||||
# documentation blocks is shown as doxygen documentation.
|
||||
# The default value is: YES.
|
||||
|
||||
PYTHON_DOCSTRING = YES
|
||||
|
||||
# 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.
|
||||
@ -449,6 +457,19 @@ TYPEDEF_HIDES_STRUCT = NO
|
||||
|
||||
LOOKUP_CACHE_SIZE = 0
|
||||
|
||||
# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use
|
||||
# during processing. When set to 0 doxygen will based this on the number of
|
||||
# cores available in the system. You can set it explicitly to a value larger
|
||||
# than 0 to get more control over the balance between CPU load and processing
|
||||
# speed. At this moment only the input processing can be done using multiple
|
||||
# threads. Since this is still an experimental feature the default is set to 1,
|
||||
# which efficively disables parallel processing. Please report any issues you
|
||||
# encounter. Generating dot graphs in parallel is controlled by the
|
||||
# DOT_NUM_THREADS setting.
|
||||
# Minimum value: 0, maximum value: 32, default value: 1.
|
||||
|
||||
NUM_PROC_THREADS = 1
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Build related configuration options
|
||||
#---------------------------------------------------------------------------
|
||||
@ -553,7 +574,7 @@ INTERNAL_DOCS = NO
|
||||
# names in lower-case letters. If set to YES, upper-case letters are also
|
||||
# allowed. This is useful if you have classes or files whose names only differ
|
||||
# in case and if your file system supports case sensitive file names. Windows
|
||||
# (including Cygwin) ands Mac users are advised to set this option to NO.
|
||||
# (including Cygwin) and Mac users are advised to set this option to NO.
|
||||
# The default value is: system dependent.
|
||||
|
||||
CASE_SENSE_NAMES = NO
|
||||
@ -1375,7 +1396,7 @@ CHM_FILE =
|
||||
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).
|
||||
# (YES) or that it should be included in the main .chm file (NO).
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
|
||||
|
||||
@ -1541,8 +1562,8 @@ EXT_LINKS_IN_WINDOW = NO
|
||||
# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see
|
||||
# https://inkscape.org) to generate formulas as SVG images instead of PNGs for
|
||||
# the HTML output. These images will generally look nicer at scaled resolutions.
|
||||
# Possible values are: png The default and svg Looks nicer but requires the
|
||||
# pdf2svg tool.
|
||||
# Possible values are: png (the default) and svg (looks nicer but requires the
|
||||
# pdf2svg or inkscape tool).
|
||||
# The default value is: png.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
@ -1842,9 +1863,11 @@ LATEX_EXTRA_FILES =
|
||||
|
||||
PDF_HYPERLINKS = YES
|
||||
|
||||
# If the USE_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.
|
||||
# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as
|
||||
# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX
|
||||
# files. Set this option to YES, to get a higher quality PDF documentation.
|
||||
#
|
||||
# See also section LATEX_CMD_NAME for selecting the engine.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
||||
|
||||
@ -2083,6 +2106,10 @@ DOCBOOK_PROGRAMLISTING = NO
|
||||
|
||||
GENERATE_AUTOGEN_DEF = NO
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to Sqlite3 output
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to the Perl module output
|
||||
#---------------------------------------------------------------------------
|
||||
@ -2138,7 +2165,7 @@ ENABLE_PREPROCESSING = YES
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
|
||||
|
||||
MACRO_EXPANSION = NO
|
||||
MACRO_EXPANSION = YES
|
||||
|
||||
# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
|
||||
# the macro expansion is limited to the macros specified with the PREDEFINED and
|
||||
@ -2146,7 +2173,7 @@ MACRO_EXPANSION = NO
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
|
||||
|
||||
EXPAND_ONLY_PREDEF = NO
|
||||
EXPAND_ONLY_PREDEF = YES
|
||||
|
||||
# If the SEARCH_INCLUDES tag is set to YES, the include files in the
|
||||
# INCLUDE_PATH will be searched if a #include is found.
|
||||
@ -2178,7 +2205,8 @@ INCLUDE_FILE_PATTERNS =
|
||||
# recursively expanded use the := operator instead of the = operator.
|
||||
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
|
||||
|
||||
PREDEFINED =
|
||||
PREDEFINED = __attribute__(x)= \
|
||||
IN_SECTION(x)=
|
||||
|
||||
# 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
|
||||
|
@ -139,7 +139,8 @@ static int sdio_switch_appmode_cmd55()
|
||||
return -1;
|
||||
}
|
||||
|
||||
enum acmd41_ret sdio_init_card_acmd41(uint8_t HCS){
|
||||
enum acmd41_ret sdio_init_card_acmd41(uint8_t HCS)
|
||||
{
|
||||
uint32_t response;
|
||||
int retry = 0x20;
|
||||
if (sdio_switch_appmode_cmd55())
|
||||
@ -184,12 +185,13 @@ static int sdio_send_csd_cmd9(uint16_t rca, uint32_t *response_buffer) {
|
||||
* @param blklen Log2 of block length (9 in case of 512 byte block)
|
||||
* @param buff Buffer to send
|
||||
*/
|
||||
static void sdio_write_buffer(uint32_t dlen, uint32_t log_blklen, const unsigned char *buff)
|
||||
static int sdio_write_buffer(uint32_t dlen, uint32_t log_blklen, const unsigned char *buff)
|
||||
{
|
||||
uint32_t count;
|
||||
int byte_count;
|
||||
int byte_max;
|
||||
uint32_t fifo;
|
||||
uint32_t status_reg;
|
||||
|
||||
SDIO->DLEN = dlen;
|
||||
|
||||
@ -212,6 +214,8 @@ static void sdio_write_buffer(uint32_t dlen, uint32_t log_blklen, const unsigned
|
||||
fifo |= (((uint32_t)*(buff++)) << 24) & 0xFF000000;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Wait as long as FIFO is full */
|
||||
while (SDIO->STA & SDIO_STA_TXFIFOF);
|
||||
|
||||
@ -221,6 +225,14 @@ static void sdio_write_buffer(uint32_t dlen, uint32_t log_blklen, const unsigned
|
||||
|
||||
/* Wait for TX to complete */
|
||||
while (SDIO->STA & SDIO_STA_TXACT);
|
||||
|
||||
status_reg = SDIO->STA;
|
||||
if (status_reg & (SDIO_STA_DTIMEOUT | SDIO_STA_TXUNDERR | SDIO_STA_DCRCFAIL)) {
|
||||
SDIO->DCTRL = 0UL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sdio_send_write_block_cmd24(uint32_t addr)
|
||||
@ -778,17 +790,11 @@ DRESULT sdio_disk_write(const BYTE *buff, DWORD sector, UINT count)
|
||||
if (ret)
|
||||
return RES_ERROR;
|
||||
|
||||
while (count) {
|
||||
|
||||
sdio_write_buffer(512, 9, &buff[buff_offset]);
|
||||
|
||||
buff_offset += 512;
|
||||
addr += (card_info.type == SD_V2_HC ? 1 : 512);
|
||||
count--;
|
||||
}
|
||||
ret = 0;
|
||||
ret = sdio_write_buffer((count * 512UL), 9, &buff[buff_offset]);
|
||||
|
||||
if (count_backup > 1)
|
||||
(void)sdio_send_stop_transmission_cmd12();
|
||||
|
||||
return RES_OK;
|
||||
return (ret ? RES_ERROR : RES_OK);
|
||||
}
|
||||
|
@ -3,6 +3,8 @@
|
||||
|
||||
#include <stm32/stm32f4xx.h>
|
||||
|
||||
#define SDIO_CLOCK_FREQ 42000000UL
|
||||
|
||||
//General Definitions
|
||||
//Blocksize: 512 = 2^9 => 9
|
||||
#define BLOCKSIZE 9 //9
|
||||
@ -12,11 +14,14 @@
|
||||
//4 bit: 4
|
||||
#define BUSWIDTH 4 //4
|
||||
//Initial Transfer CLK (ca. 400kHz)
|
||||
#define INITCLK 140 //120
|
||||
#define INITCLK 140UL //120
|
||||
//Working CLK (Maximum)
|
||||
#define WORKCLK 8 //0
|
||||
#define WORKCLK 30UL //0
|
||||
//Data Timeout in CLK Cycles
|
||||
#define DTIMEOUT 0x6000 //150
|
||||
|
||||
#define DATA_TIMEOUT_MS 250UL // 250
|
||||
|
||||
#define DTIMEOUT (((SDIO_CLOCK_FREQ / (WORKCLK+2))) * DATA_TIMEOUT_MS / 1000UL)
|
||||
//DMA Stream used for TX and RX DMA2 Stream 3 or 6 possible
|
||||
// Currently not used due to possible misalignment of the data buffer.
|
||||
//#define DMASTREAM DMA2_Stream6
|
||||
|
48
stm-firmware/hw-version-detect.c
Normal file
48
stm-firmware/hw-version-detect.c
Normal file
@ -0,0 +1,48 @@
|
||||
#include <reflow-controller/hw-version-detect.h>
|
||||
#include <stm-periph/rcc-manager.h>
|
||||
#include <stm32/stm32f4xx.h>
|
||||
|
||||
#define HW_REV_DETECT_GPIO GPIOE
|
||||
#define HW_REV_DETECT_RCC_FIELD RCC_AHB1ENR_GPIOEEN
|
||||
#define HW_REV_DETECT_PIN_LOW (8U)
|
||||
#define HW_REV_DETECT_PIN_HIGH (15U)
|
||||
|
||||
enum hw_revision get_pcb_hardware_version(void)
|
||||
{
|
||||
uint8_t current_pin;
|
||||
uint16_t port_bitmask = 0U;
|
||||
static enum hw_revision revision = HW_REV_NOT_DETECTED;
|
||||
|
||||
if (revision != HW_REV_NOT_DETECTED)
|
||||
return revision;
|
||||
|
||||
rcc_manager_enable_clock(&RCC->AHB1ENR, BITMASK_TO_BITNO(HW_REV_DETECT_RCC_FIELD));
|
||||
|
||||
/* Setup the pins as input with pull up */
|
||||
for (current_pin = HW_REV_DETECT_PIN_LOW; current_pin <= HW_REV_DETECT_PIN_HIGH; current_pin++) {
|
||||
HW_REV_DETECT_GPIO->MODER &= MODER_DELETE(current_pin);
|
||||
HW_REV_DETECT_GPIO->PUPDR &= PUPDR_DELETE(current_pin);
|
||||
HW_REV_DETECT_GPIO->PUPDR |= PULLUP(current_pin);
|
||||
}
|
||||
|
||||
/* Loop again and read in the pin mask */
|
||||
for (current_pin = HW_REV_DETECT_PIN_LOW; current_pin <= HW_REV_DETECT_PIN_HIGH; current_pin++) {
|
||||
port_bitmask >>= 1;
|
||||
port_bitmask |= (HW_REV_DETECT_GPIO->IDR & (1 << current_pin)) ? 0x0 : 0x80;
|
||||
}
|
||||
|
||||
switch (port_bitmask) {
|
||||
case 0U:
|
||||
revision = HW_REV_V1_2;
|
||||
break;
|
||||
case 1U:
|
||||
revision = HW_REV_V1_3;
|
||||
break;
|
||||
default:
|
||||
revision = HW_REV_ERROR;
|
||||
}
|
||||
|
||||
rcc_manager_disable_clock(&RCC->AHB1ENR, BITMASK_TO_BITNO(HW_REV_DETECT_RCC_FIELD));
|
||||
|
||||
return revision;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -33,7 +33,7 @@
|
||||
/ 2: Enable with LF-CRLF conversion. */
|
||||
|
||||
|
||||
#define FF_USE_FIND 0
|
||||
#define FF_USE_FIND 1
|
||||
/* This option switches filtered directory read functions, f_findfirst() and
|
||||
/ f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */
|
||||
|
||||
|
@ -30,6 +30,7 @@
|
||||
#define CONCAT(x,y) x##y
|
||||
|
||||
#define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x])))))
|
||||
#define wordsize_of(x) ((sizeof(x) / 4U) / ((sizeof(x) % 4U) ? 0U : 1U))
|
||||
|
||||
#define MIN(a,b) (((a) < (b)) ? (a) : (b))
|
||||
#define MAX(a,b) (((a) > (b)) ? (a) : (b))
|
||||
|
@ -38,6 +38,23 @@
|
||||
*/
|
||||
#define ADC_PT1000_FILTER_WEIGHT 0.005f
|
||||
|
||||
/**
|
||||
* @brief Moving average filter weight used for fast regaulation. This is used when the measured resistance
|
||||
* is more than @ref ADC_PT1000_FILTER_UNSTABLE_DIFF Ohms away from the current averaged value.
|
||||
*/
|
||||
#define ADC_PT1000_FILTER_WEIGHT_FAST 0.05
|
||||
|
||||
/**
|
||||
* @brief Difference in Ohm between filter input and output that determines if the filter is stable or unstable.
|
||||
*/
|
||||
#define ADC_PT1000_FILTER_UNSTABLE_DIFF 20
|
||||
|
||||
/**
|
||||
* @brief Sample count, the moving average filter has to be within @ref ADC_PT1000_FILTER_UNSTABLE_DIFF for the filter
|
||||
* to be considered stable
|
||||
*/
|
||||
#define ADC_PT1000_FILTER_STABLE_SAMPLE_COUNT 200
|
||||
|
||||
/**
|
||||
* @brief ADC channel number of PT1000 sensor input
|
||||
*/
|
||||
@ -58,12 +75,6 @@
|
||||
*/
|
||||
#define ADC_PT1000_PIN 2U
|
||||
|
||||
/**
|
||||
* @brief The cycle count the moving average filter is labeled 'instable' after startup of the measurement or changing
|
||||
* the alpha value @ref ADC_PT1000_FILTER_WEIGHT
|
||||
*/
|
||||
#define ADC_FILTER_STARTUP_CYCLES 800U
|
||||
|
||||
/**
|
||||
* @brief The delay value programmed into the sample timer
|
||||
*/
|
||||
@ -99,6 +110,8 @@
|
||||
*/
|
||||
#define ADC_TO_RES(adc) ((float)(adc) / 4096.0f * 2500.0f)
|
||||
|
||||
#define RES_TO_ADC(res) ((float)(res) / 2500.0f * 4096.0f)
|
||||
|
||||
/**
|
||||
* @brief This function sets up the ADC measurement fo the external PT1000 temperature sensor
|
||||
*
|
||||
@ -131,6 +144,7 @@ void adc_pt1000_set_moving_average_filter_param(float alpha);
|
||||
*
|
||||
* @param offset Offset \f$O\f$
|
||||
* @param sensitivity_deviation Sensitivity Deviation \f$\sigma\f$ after offset correction
|
||||
* @param active Calibration is active
|
||||
*/
|
||||
void adc_pt1000_set_resistance_calibration(float offset, float sensitivity_deviation, bool active);
|
||||
|
||||
|
@ -21,13 +21,13 @@
|
||||
#ifndef __CALIBRATION_H__
|
||||
#define __CALIBRATION_H__
|
||||
|
||||
#define CALIBRATION_MAX_PEAK_PEAK_NOISE_OHM 8.0f
|
||||
#define CALIBRATION_MAX_NOISE_OHM 3.0f
|
||||
|
||||
#include <stdint.h>
|
||||
#include <shellmatta.h>
|
||||
|
||||
void calibration_calculate(float low_measured, float low_setpoint, float high_measured, float high_setpoint,
|
||||
float *sens_deviation, float *sens_corrected_offset);
|
||||
float *sens_deviation, float *offset);
|
||||
|
||||
float *calibration_acquire_data_start(uint32_t count, volatile int *flag);
|
||||
|
||||
|
@ -0,0 +1,57 @@
|
||||
/* Reflow Oven Controller
|
||||
*
|
||||
* Copyright (C) 2021 Mario Hüttel <mario.huettel@gmx.net>
|
||||
*
|
||||
* This file is part of the Reflow Oven Controller Project.
|
||||
*
|
||||
* The reflow oven controller is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* The Reflow Oven Control Firmware 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 the reflow oven controller project.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __CONFIG_PARSER_TEMP_PROFILE_PARSER_H__
|
||||
#define __CONFIG_PARSER_TEMP_PROFILE_PARSER_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
enum pl_command_type {
|
||||
PL_PID_CONF = 0,
|
||||
PL_SET_TEMP,
|
||||
PL_SET_RAMP,
|
||||
PL_WAIT_FOR_TEMP,
|
||||
PL_WAIT_FOR_TIME,
|
||||
PL_LOUDSPEAKER_SET,
|
||||
PL_OFF,
|
||||
_PL_NUM_CMDS,
|
||||
};
|
||||
|
||||
enum pl_ret_val {
|
||||
PL_RET_SUCCESS = 0,
|
||||
PL_RET_DISK_ERR,
|
||||
PL_RET_PARAM_ERR,
|
||||
PL_RET_LIST_FULL,
|
||||
PL_RET_SCRIPT_ERR,
|
||||
};
|
||||
|
||||
#define PROFILE_LANG_MAX_NUM_ARGS (8)
|
||||
|
||||
struct pl_command {
|
||||
enum pl_command_type cmd;
|
||||
float params[PROFILE_LANG_MAX_NUM_ARGS];
|
||||
};
|
||||
|
||||
enum pl_ret_val temp_profile_parse_from_file(const char *filename,
|
||||
struct pl_command *cmd_list,
|
||||
uint32_t cmd_list_length,
|
||||
uint32_t *cmds_parsed);
|
||||
|
||||
#endif /* __CONFIG_PARSER_TEMP_PROFILE_PARSER_H__ */
|
@ -29,7 +29,7 @@
|
||||
#define DIGIO_RCC_MASK RCC_AHB1ENR_GPIOBEN
|
||||
#define DIGIO_PINS 4,5,6,7
|
||||
|
||||
#ifdef DEBUGBUILD
|
||||
#if defined(DEBUGBUILD) || defined(UART_ON_DEBUG_HEADER)
|
||||
#define DIGIO_INOUT_DEFAULT 0,0,0,0
|
||||
#define DIGIO_ALTFUNC_DEFAULT 0,0,0,0
|
||||
#else
|
||||
@ -41,7 +41,7 @@
|
||||
#define BEEPER_RCC_MASK RCC_AHB1ENR_GPIOBEN
|
||||
|
||||
|
||||
void digio_setup_default_all();
|
||||
void digio_setup_default_all(void);
|
||||
|
||||
void digio_setup_pin(uint8_t num, uint8_t in_out, uint8_t alt_func);
|
||||
void digio_set(uint8_t num, int val);
|
||||
@ -51,7 +51,7 @@ int digio_get(uint8_t num);
|
||||
#define LED_RCC_MASK RCC_AHB1ENR_GPIOBEN
|
||||
#define LED_PINS 2,3
|
||||
|
||||
void led_setup();
|
||||
void led_setup(void);
|
||||
void led_set(uint8_t num, int val);
|
||||
int led_get(uint8_t num);
|
||||
|
||||
@ -59,10 +59,11 @@ int led_get(uint8_t num);
|
||||
#define LOUDSPEAKER_RCC_MASK RCC_AHB1ENR_GPIOBEN
|
||||
#define LOUDSPEAKER_PIN 1
|
||||
#define LOUDSPEAKER_MULTIFREQ 1
|
||||
#define LOUDSPEAKER_MULTIFREQ_DEFAULT 9
|
||||
|
||||
void loudspeaker_setup();
|
||||
void loudspeaker_setup(void);
|
||||
void loudspeaker_set(uint16_t val);
|
||||
uint16_t loudspeaker_get();
|
||||
uint16_t loudspeaker_get(void);
|
||||
|
||||
|
||||
#endif /* __DIGIO_H__ */
|
||||
|
34
stm-firmware/include/reflow-controller/hw-version-detect.h
Normal file
34
stm-firmware/include/reflow-controller/hw-version-detect.h
Normal file
@ -0,0 +1,34 @@
|
||||
#ifndef _HW_VERSION_DETECT_H_
|
||||
#define _HW_VERSION_DETECT_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @brief PCB/Hardware Revision Type
|
||||
*/
|
||||
enum hw_revision {
|
||||
HW_REV_NOT_DETECTED = 0, /**< @brief The hardware has'nt been detected (yet) */
|
||||
HW_REV_ERROR = 1, /**< @brief The hardware revision could not be detected due to an internal error */
|
||||
HW_REV_V1_2 = 120, /**< @brief Hardware Revision v1.2 */
|
||||
HW_REV_V1_3 = 130, /**< @brief Hardware Revision v1.3 */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This function returns the hardware version of the PCB.
|
||||
*
|
||||
* This is used to
|
||||
* determine the feature set of the hardware. So this firmware can be used on all hardwares.
|
||||
*
|
||||
* The first hardware revision supported, is: v1.2
|
||||
*
|
||||
* The function returns the HW revision as an enum hw_revision.
|
||||
* For v1.2 the return value is 120 (HW_REV_V1_2).
|
||||
* For v1.3 the return value is 130 (HW_REV_V1_3).
|
||||
*
|
||||
* Other return values are not defined yet.
|
||||
*
|
||||
* @return Harware revision
|
||||
*/
|
||||
enum hw_revision get_pcb_hardware_version(void);
|
||||
|
||||
#endif /* _HW_VERSION_DETECT_H_ */
|
@ -35,14 +35,16 @@ void oven_driver_set_power(uint8_t power);
|
||||
|
||||
void oven_driver_disable(void);
|
||||
|
||||
void oven_pid_ack_errors(void);
|
||||
|
||||
void oven_pid_init(struct pid_controller *controller_to_copy);
|
||||
|
||||
void oven_pid_handle(float target_temp);
|
||||
void oven_pid_handle(void);
|
||||
|
||||
void oven_pid_stop(void);
|
||||
|
||||
void oven_pid_abort(void);
|
||||
|
||||
void oven_pid_set_target_temperature(float temp);
|
||||
|
||||
void oven_driver_apply_power_level(void);
|
||||
|
||||
enum oven_pid_status oven_pid_get_status(void);
|
||||
|
@ -18,16 +18,53 @@
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file oven-driver-hwcfg.h
|
||||
* @brief hardware configuration for oven PWM driver
|
||||
*/
|
||||
|
||||
#ifndef __OVEN_DRIVER_HWCFG_H__
|
||||
#define __OVEN_DRIVER_HWCFG_H__
|
||||
|
||||
#include <stm32/stm32f4xx.h>
|
||||
|
||||
/**
|
||||
* @brief Timer to use to generate PWM for oven
|
||||
*/
|
||||
#define OVEN_CONTROLLER_PWM_TIMER TIM3
|
||||
|
||||
/**
|
||||
* @brief Port the oven control output is connected to
|
||||
*/
|
||||
#define OVEN_CONTROLLER_PORT GPIOB
|
||||
|
||||
/**
|
||||
* @brief The Pin number the oven control output is located at
|
||||
*/
|
||||
#define OVEN_CONTROLLER_PIN (0)
|
||||
|
||||
/**
|
||||
* @brief The AHB1ENR Bitmask to enable the clock to the output port #OVEN_CONTROLLER_PORT
|
||||
*/
|
||||
#define OVEN_CONTROLLER_PORT_RCC_MASK RCC_AHB1ENR_GPIOBEN
|
||||
|
||||
/**
|
||||
* @brief The APB1ENR bitmask to enable the #OVEN_CONTROLLER_PWM_TIMER
|
||||
*/
|
||||
#define OVEN_CONTROLLER_TIM_RCC_MASK RCC_APB1ENR_TIM3EN
|
||||
|
||||
/**
|
||||
* @brief The alternate function #OVEN_CONTROLLER_PWM_TIMER's pwm output is located on #OVEN_CONTROLLER_PORT
|
||||
*/
|
||||
#define OVEN_CONTROLLER_PIN_ALTFUNC (2)
|
||||
|
||||
/**
|
||||
* @brief GPIO Port for the safety enable line used by PCB versions > v1.3
|
||||
*/
|
||||
#define SSR_SAFETY_EN_PORT GPIOA
|
||||
|
||||
#define SSR_SAFETY_EN_PIN (3)
|
||||
|
||||
#define SSR_SAFETY_EN_PORT_RCC_MASK RCC_AHB1ENR_GPIOAEN
|
||||
|
||||
#endif /* __OVEN_DRIVER_HWCFG_H__ */
|
||||
|
@ -21,18 +21,90 @@
|
||||
#ifndef __SAFETY_ADC_HWCFG_H__
|
||||
#define __SAFETY_ADC_HWCFG_H__
|
||||
|
||||
/**
|
||||
* @file safety-adc-hwcfg.h
|
||||
* @brief Safety ADC configuration settings
|
||||
*/
|
||||
|
||||
#include <stm32/stm32f4xx.h>
|
||||
|
||||
/**
|
||||
* @brief Peripheral to use as safety ADC.
|
||||
* @note This must be ADC1, because it is the only ADC with access to the internal temperature sensor
|
||||
*/
|
||||
#define SAFETY_ADC_ADC_PERIPHERAL ADC1
|
||||
|
||||
/**
|
||||
* @brief Clock enable mask in RCC->APB2ENR to enable the #SAFETY_ADC_ADC_PERIPHERAL
|
||||
*/
|
||||
#define SAFETY_ADC_ADC_RCC_MASK RCC_APB2ENR_ADC1EN
|
||||
|
||||
/**
|
||||
* @brief ADC channel number of the internal temperature sensor
|
||||
*/
|
||||
#define TEMP_CHANNEL_NUM (16)
|
||||
|
||||
/**
|
||||
* @brief ADC channel number of the internal reference voltage
|
||||
*/
|
||||
#define INT_REF_CHANNEL_NUM (17)
|
||||
|
||||
#define SAFETY_ADC_SUPPLY_VOLTAGE_MONITOR_CHANNEL_NUM (15)
|
||||
#define SAFETY_ADC_SUPPLY_VOLTAGE_MONITOR_PIN (5)
|
||||
#define SAFETY_ADC_SUPPLY_VOLTAGE_MONITOR_PORT GPIOC
|
||||
#define SAFETY_ADC_SUPPLY_VOLTAGE_MONITOR_PORT_RCC_MASK RCC_AHB1ENR_GPIOCEN
|
||||
|
||||
|
||||
/**
|
||||
* @brief Nominal value of the internal reference voltage.
|
||||
*
|
||||
* This is used to compare to the external reference voltage.
|
||||
*/
|
||||
#define SAFETY_ADC_INT_REF_MV 1210.0f
|
||||
|
||||
/**
|
||||
* @brief Nominal temperature in degrees celsius of the internal temperature sensor at which it reads
|
||||
* #SAFETY_ADC_TEMP_NOM_MV millivolts.
|
||||
*
|
||||
*/
|
||||
#define SAFETY_ADC_TEMP_NOM 25.0f
|
||||
|
||||
/**
|
||||
* @brief Nominal internal temperature sensor voltage at temperature #SAFETY_ADC_TEMP_NOM
|
||||
*/
|
||||
#define SAFETY_ADC_TEMP_NOM_MV 760.0f
|
||||
|
||||
/**
|
||||
* @brief Sensitivity of the internal temperature sensor in millivolts per Kelvin
|
||||
*/
|
||||
#define SAFETY_ADC_TEMP_MV_SLOPE 2.5f
|
||||
|
||||
/**
|
||||
* @brief Number of channels to handle for the SAFETY ADC.
|
||||
* @note The maximum amount of channels is limited to 16
|
||||
*/
|
||||
#define SAFETY_ADC_NUM_OF_CHANNELS 12
|
||||
|
||||
/**
|
||||
* @brief Channel numbers to sample with the SAFETY ADC
|
||||
*
|
||||
* - The first 4 values are the internal temperature sensor
|
||||
* - The next 4 values are the internal reference voltage
|
||||
*
|
||||
* The values are samples 4 times each to allow averaging them to get
|
||||
* more precise readings. Check the maximum value of #SAFETY_ADC_NUM_OF_CHANNELS
|
||||
* when adding more channels.
|
||||
* @warning Safety controller expects the current measurment list. If you change it, check @ref safety_controller_handle_safety_adc
|
||||
*/
|
||||
#define SAFETY_ADC_CHANNELS TEMP_CHANNEL_NUM, TEMP_CHANNEL_NUM, TEMP_CHANNEL_NUM, TEMP_CHANNEL_NUM, \
|
||||
INT_REF_CHANNEL_NUM, INT_REF_CHANNEL_NUM, INT_REF_CHANNEL_NUM, INT_REF_CHANNEL_NUM, \
|
||||
SAFETY_ADC_SUPPLY_VOLTAGE_MONITOR_CHANNEL_NUM, SAFETY_ADC_SUPPLY_VOLTAGE_MONITOR_CHANNEL_NUM, \
|
||||
SAFETY_ADC_SUPPLY_VOLTAGE_MONITOR_CHANNEL_NUM, SAFETY_ADC_SUPPLY_VOLTAGE_MONITOR_CHANNEL_NUM
|
||||
|
||||
|
||||
/* Check the channel count of the safety ADC */
|
||||
#if SAFETY_ADC_NUM_OF_CHANNELS > 16 || SAFETY_ADC_NUM_OF_CHANNELS < 0
|
||||
#error "Invlaid count of channels for safety ADC"
|
||||
#endif
|
||||
|
||||
#endif /* __SAFETY_ADC_HWCFG_H__ */
|
||||
|
@ -0,0 +1,36 @@
|
||||
/* Reflow Oven Controller
|
||||
*
|
||||
* Copyright (C) 2021 Mario Hüttel <mario.huettel@gmx.net>
|
||||
*
|
||||
* This file is part of the Reflow Oven Controller Project.
|
||||
*
|
||||
* The reflow oven controller is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* The Reflow Oven Control Firmware 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 the reflow oven controller project.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _SPI_EEPROM_HWCFG_H_
|
||||
#define _SPI_EEPROM_HWCFG_H_
|
||||
|
||||
#include <stm32/stm32f4xx.h>
|
||||
|
||||
#define SPI_EEPROM_SPI_PORT GPIOA
|
||||
#define SPI_EEPROM_SPI_PORT_RCC_REG RCC->AHB1ENR
|
||||
#define SPI_EEPROM_SPI_PORT_RCC_MASK RCC_AHB1ENR_GPIOAEN
|
||||
|
||||
#define SPI_EEPROM_SPI_ALTFUNC_NO (5)
|
||||
#define SPI_EEPROM_MISO_PIN (6)
|
||||
#define SPI_EEPROM_MOSI_PIN (7)
|
||||
#define SPI_EEPROM_SCK_PIN (5)
|
||||
#define SPI_EEPROM_CS_PIN (4)
|
||||
|
||||
#endif /* _SPI_EEPROM_HWCFG_H_ */
|
@ -27,6 +27,7 @@ struct pid_controller {
|
||||
float k_p;
|
||||
float k_int_t;
|
||||
float k_deriv_t;
|
||||
float k_inv_deriv_t;
|
||||
float output_sat_max;
|
||||
float output_sat_min;
|
||||
float integral_max;
|
||||
@ -37,7 +38,8 @@ struct pid_controller {
|
||||
volatile float derivate;
|
||||
};
|
||||
|
||||
void pid_init(struct pid_controller *pid, float k_deriv, float k_int, float k_p, float output_sat_min, float output_sat_max, float integral_max, float sample_period);
|
||||
void pid_init(struct pid_controller *pid, float k_deriv, float k_int, float k_p,
|
||||
float output_sat_min, float output_sat_max, float integral_max, float kd_tau, float sample_period);
|
||||
|
||||
void pid_zero(struct pid_controller *pid);
|
||||
|
||||
|
@ -29,23 +29,55 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
enum safety_adc_meas_channel {SAFETY_ADC_MEAS_VREF, SAFETY_ADC_MEAS_TEMP};
|
||||
/**
|
||||
* @brief The safety ADC's measure channel
|
||||
*/
|
||||
enum safety_adc_meas_channel {
|
||||
SAFETY_ADC_MEAS_VREF, /**< @brief Internal reference voltage @note This will not output the internal reference voltage but the recalculated external voltage! */
|
||||
SAFETY_ADC_MEAS_TEMP, /**< @brief Internal temperature sensor */
|
||||
SAFETY_ADC_MEAS_SUPPLY, /**< @brief Supply voltage */
|
||||
};
|
||||
|
||||
void safety_adc_init();
|
||||
/**
|
||||
* @brief Initialize safety ADC. Only call this function once!
|
||||
*/
|
||||
void safety_adc_init(void);
|
||||
|
||||
void safety_adc_deinit();
|
||||
/**
|
||||
* @brief safety_adc_deinit Deactivate safety ADC
|
||||
* @warning For completeness. Do not call! the safety ADC is a vital component. It's malfunction will trigger a critical safety flag.
|
||||
*/
|
||||
void safety_adc_deinit(void);
|
||||
|
||||
void safety_adc_trigger_meas(enum safety_adc_meas_channel measurement);
|
||||
/**
|
||||
* @brief safety_adc_trigger_meas
|
||||
*/
|
||||
void safety_adc_trigger_meas(void);
|
||||
|
||||
/**
|
||||
* @brief Poll ADC result.
|
||||
* @param results adc results
|
||||
* @return 1 if measurement successful, 0 if not ready, -1 if ADC aborted or not started
|
||||
*/
|
||||
int safety_adc_poll_result(uint16_t *adc_result);
|
||||
int safety_adc_poll_result(void);
|
||||
|
||||
enum safety_adc_check_result handle_safety_adc();
|
||||
/**
|
||||
* @brief Get the sampled signal values of the safety ADC.
|
||||
*
|
||||
* Use #safety_adc_poll_result to poll for a finished conversion of the safety ADC.
|
||||
* After that, it is safe to use the output values until a new conversion is triggered using #safety_adc_trigger_meas
|
||||
*
|
||||
* @warning This function return a constant buffer, that is directly written on by the DMA! Check #safety_adc_poll_result to prevent race conditions.
|
||||
* @return Array of raw ADC readings with lenght of #SAFETY_ADC_NUM_OF_CHANNELS
|
||||
*/
|
||||
const uint16_t *safety_adc_get_values(void);
|
||||
|
||||
/**
|
||||
* @brief Convert a safety ADC raw value to an actual signal value
|
||||
* @param channel Measurment channel to convert voltage for
|
||||
* @param analog_value Raw value of the ADC to convert
|
||||
* @warning Double check @ref safety_adc_meas_channel to make sure you understand the output of this function correctly.
|
||||
* @return
|
||||
*/
|
||||
float safety_adc_convert_channel(enum safety_adc_meas_channel channel, uint16_t analog_value);
|
||||
|
||||
#endif /* __SAFETY_ADC_H__ */
|
||||
|
@ -18,11 +18,25 @@
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file safety-config.h
|
||||
* @brief Safety Controller Configuration.
|
||||
* @warning MAKE SURE XOU UNDERSTAND WHAT YOU ARE DOING IN HERE
|
||||
*/
|
||||
|
||||
#ifndef __SAFETY_CONFIG_H__
|
||||
#define __SAFETY_CONFIG_H__
|
||||
|
||||
|
||||
/**
|
||||
* @brief Enum type representing safety flags.
|
||||
*
|
||||
* The enum type is binary or'able to allow combining
|
||||
* multiple safety flags.
|
||||
*
|
||||
* @note For a more detailed explaination of the flags, check out the Sphinx documentation.
|
||||
*/
|
||||
enum safety_flag {
|
||||
ERR_FLAG_NO_FLAG = 0,
|
||||
ERR_FLAG_MEAS_ADC_OFF = (1<<0),
|
||||
ERR_FLAG_MEAS_ADC_OVERFLOW = (1<<1),
|
||||
ERR_FLAG_MEAS_ADC_WATCHDOG = (1<<2),
|
||||
@ -38,8 +52,18 @@ enum safety_flag {
|
||||
ERR_FLAG_UNCAL = (1<<12),
|
||||
ERR_FLAG_DEBUG = (1<<13),
|
||||
ERR_FLAG_TIMING_MAIN_LOOP = (1<<14),
|
||||
ERR_FLAG_SAFETY_MEM_CORRUPT = (1<<15),
|
||||
ERR_FLAG_SAFETY_TAB_CORRUPT = (1<<16),
|
||||
ERR_FLAG_AMON_SUPPLY_VOLT = (1<<17),
|
||||
ERR_FLAG_OVERTEMP = (1<<18),
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Enum Type representing timing monitors.
|
||||
*
|
||||
* The enum type is binary or'able to allow combining
|
||||
* multiple safety flags.
|
||||
*/
|
||||
enum timing_monitor {
|
||||
ERR_TIMING_PID = (1<<0),
|
||||
ERR_TIMING_MEAS_ADC = (1<<1),
|
||||
@ -47,11 +71,23 @@ enum timing_monitor {
|
||||
ERR_TIMING_MAIN_LOOP = (1<<3),
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Enum Type representing analog value monitors.
|
||||
*
|
||||
* The enum type is binary or'able to allow combining
|
||||
* multiple safety flags.
|
||||
*/
|
||||
enum analog_value_monitor {
|
||||
ERR_AMON_VREF = (1<<0),
|
||||
ERR_AMON_UC_TEMP = (1<<1),
|
||||
ERR_AMON_SUPPLY_VOLT = (1<<2),
|
||||
};
|
||||
|
||||
#define ERR_FLAG_ENTRY(errflag) {.name=#errflag, .flag = (errflag), .error_state = false, .error_state_inv = true, .key = 0UL, .weight = NULL, .persistence = NULL}
|
||||
#define TIM_MON_ENTRY(mon, min, max, flag) {.name=#mon, .monitor = (mon), .associated_flag=(flag), .min_delta = (min), .max_delta = (max), .last = 0ULL, .enabled= false}
|
||||
#define ANA_MON_ENTRY(mon, min_value, max_value, flag) {.name=#mon, .monitor = (mon), .associated_flag=(flag), .min = (min_value), .max = (max_value), .value = 0.0f, .valid = false}
|
||||
#define ERR_FLAG_WEIGHT_ENTRY(_flag, _weight) {.flag = (_flag), .flag_ptr = NULL, .weight = (_weight), .start_dummy = 0x11823344, .end_dummy = 0xAABBCCFD}
|
||||
#define ERR_FLAG_PERSIST_ENTRY(_flag, _persist) {.flag = (_flag), .flag_ptr = NULL, .persistence = (_persist), .start_dummy = 0xFF1100BB, .end_dummy = 0xEBB439A2}
|
||||
|
||||
/**
|
||||
* @brief Magic key used to reset the watchdog using the @ref watchdog_ack function
|
||||
@ -69,8 +105,11 @@ enum analog_value_monitor {
|
||||
#define WATCHDOG_HALT_DEBUG (0)
|
||||
#endif
|
||||
|
||||
#define WATCHDOG_PRESCALER 8
|
||||
#define WATCHDOG_PRESCALER 16
|
||||
|
||||
/**
|
||||
* @brief Minimum number of bytes that have to be free on the stack. If this is not the case, an error is detected
|
||||
*/
|
||||
#define SAFETY_MIN_STACK_FREE 0x100
|
||||
|
||||
#define PID_CONTROLLER_ERR_CAREMASK (ERR_FLAG_STACK | ERR_FLAG_AMON_UC_TEMP | ERR_FLAG_AMON_VREF | \
|
||||
@ -83,13 +122,68 @@ enum analog_value_monitor {
|
||||
#define SAFETY_ADC_VREF_TOL_MVOLT (100.0f)
|
||||
#define SAFETY_ADC_TEMP_LOW_LIM (0.0f)
|
||||
#define SAFETY_ADC_TEMP_HIGH_LIM (65.0f)
|
||||
#define SAFETY_ADC_SUPPLY_MVOLT (3300.0f)
|
||||
#define SAFETY_ADC_SUPPLY_TOL_MVOLT (150.0f)
|
||||
|
||||
#define SAFETY_EXT_WATCHDOG_PORT GPIOD
|
||||
#define SAFETY_EXT_WATCHDOG_RCC_MASK RCC_AHB1ENR_GPIODEN
|
||||
#define SAFETY_EXT_WATCHDOG_PIN (12)
|
||||
|
||||
/**
|
||||
* @brief Key used to lock the safety flags from external ack'ing
|
||||
* @brief Default Limit of the overtemperature detection in degrees celsius
|
||||
*/
|
||||
#define SAFETY_DEFAULT_OVERTEMP_LIMIT_DEGC (260.0f)
|
||||
|
||||
/**
|
||||
* @brief Key used to lock the safety flags coming from the measurment ADC from external ack'ing
|
||||
*/
|
||||
#define MEAS_ADC_SAFETY_FLAG_KEY 0xe554dac3UL
|
||||
|
||||
#define SAFETY_CONTROLLER_ADC_DELAY_MS 120
|
||||
/**
|
||||
* @brief Safety ADC trigger interval
|
||||
*/
|
||||
#define SAFETY_CONTROLLER_ADC_DELAY_MS 250
|
||||
|
||||
#define SAFETY_CONFIG_DEFAULT_PERSIST ERR_FLAG_PERSIST_ENTRY(ERR_FLAG_MEAS_ADC_OFF, false), \
|
||||
ERR_FLAG_PERSIST_ENTRY(ERR_FLAG_MEAS_ADC_WATCHDOG, false), \
|
||||
ERR_FLAG_PERSIST_ENTRY(ERR_FLAG_MEAS_ADC_UNSTABLE, false), \
|
||||
ERR_FLAG_PERSIST_ENTRY(ERR_FLAG_MEAS_ADC_OVERFLOW, true), \
|
||||
ERR_FLAG_PERSIST_ENTRY(ERR_FLAG_TIMING_MEAS_ADC, false), \
|
||||
ERR_FLAG_PERSIST_ENTRY(ERR_FLAG_TIMING_PID, false), \
|
||||
ERR_FLAG_PERSIST_ENTRY(ERR_FLAG_AMON_UC_TEMP, true), \
|
||||
ERR_FLAG_PERSIST_ENTRY(ERR_FLAG_AMON_VREF, false), \
|
||||
ERR_FLAG_PERSIST_ENTRY(ERR_FLAG_STACK, true), \
|
||||
ERR_FLAG_PERSIST_ENTRY(ERR_FLAG_SAFETY_ADC, true), \
|
||||
ERR_FLAG_PERSIST_ENTRY(ERR_FLAG_SYSTICK, true), \
|
||||
ERR_FLAG_PERSIST_ENTRY(ERR_FLAG_WTCHDG_FIRED, true), \
|
||||
ERR_FLAG_PERSIST_ENTRY(ERR_FLAG_UNCAL, false), \
|
||||
ERR_FLAG_PERSIST_ENTRY(ERR_FLAG_DEBUG, true), \
|
||||
ERR_FLAG_PERSIST_ENTRY(ERR_FLAG_TIMING_MAIN_LOOP, true), \
|
||||
ERR_FLAG_PERSIST_ENTRY(ERR_FLAG_SAFETY_MEM_CORRUPT, true), \
|
||||
ERR_FLAG_PERSIST_ENTRY(ERR_FLAG_SAFETY_TAB_CORRUPT, true), \
|
||||
ERR_FLAG_PERSIST_ENTRY(ERR_FLAG_AMON_SUPPLY_VOLT, false), \
|
||||
ERR_FLAG_PERSIST_ENTRY(ERR_FLAG_OVERTEMP, false), \
|
||||
|
||||
#define SAFETY_CONFIG_DEFAULT_WEIGHTS ERR_FLAG_WEIGHT_ENTRY(ERR_FLAG_MEAS_ADC_OFF, SAFETY_FLAG_CONFIG_WEIGHT_PID), \
|
||||
ERR_FLAG_WEIGHT_ENTRY(ERR_FLAG_MEAS_ADC_WATCHDOG, SAFETY_FLAG_CONFIG_WEIGHT_PID), \
|
||||
ERR_FLAG_WEIGHT_ENTRY(ERR_FLAG_MEAS_ADC_UNSTABLE, SAFETY_FLAG_CONFIG_WEIGHT_NONE), \
|
||||
ERR_FLAG_WEIGHT_ENTRY(ERR_FLAG_MEAS_ADC_OVERFLOW, SAFETY_FLAG_CONFIG_WEIGHT_PID), \
|
||||
ERR_FLAG_WEIGHT_ENTRY(ERR_FLAG_TIMING_MEAS_ADC, SAFETY_FLAG_CONFIG_WEIGHT_PID), \
|
||||
ERR_FLAG_WEIGHT_ENTRY(ERR_FLAG_TIMING_PID, SAFETY_FLAG_CONFIG_WEIGHT_PID), \
|
||||
ERR_FLAG_WEIGHT_ENTRY(ERR_FLAG_AMON_UC_TEMP, SAFETY_FLAG_CONFIG_WEIGHT_PANIC), \
|
||||
ERR_FLAG_WEIGHT_ENTRY(ERR_FLAG_AMON_VREF, SAFETY_FLAG_CONFIG_WEIGHT_PID), \
|
||||
ERR_FLAG_WEIGHT_ENTRY(ERR_FLAG_STACK, SAFETY_FLAG_CONFIG_WEIGHT_PANIC), \
|
||||
ERR_FLAG_WEIGHT_ENTRY(ERR_FLAG_SAFETY_ADC, SAFETY_FLAG_CONFIG_WEIGHT_PANIC), \
|
||||
ERR_FLAG_WEIGHT_ENTRY(ERR_FLAG_SYSTICK, SAFETY_FLAG_CONFIG_WEIGHT_PANIC), \
|
||||
/* Watchdog timeout is not handled periodically, but only on startup.
|
||||
* Therefore, it is not listed here */\
|
||||
ERR_FLAG_WEIGHT_ENTRY(ERR_FLAG_WTCHDG_FIRED, SAFETY_FLAG_CONFIG_WEIGHT_NONE), \
|
||||
ERR_FLAG_WEIGHT_ENTRY(ERR_FLAG_UNCAL, SAFETY_FLAG_CONFIG_WEIGHT_NONE), \
|
||||
ERR_FLAG_WEIGHT_ENTRY(ERR_FLAG_DEBUG, SAFETY_FLAG_CONFIG_WEIGHT_NONE), \
|
||||
ERR_FLAG_WEIGHT_ENTRY(ERR_FLAG_TIMING_MAIN_LOOP, SAFETY_FLAG_CONFIG_WEIGHT_PANIC), \
|
||||
ERR_FLAG_WEIGHT_ENTRY(ERR_FLAG_SAFETY_MEM_CORRUPT, SAFETY_FLAG_CONFIG_WEIGHT_PID), \
|
||||
ERR_FLAG_WEIGHT_ENTRY(ERR_FLAG_SAFETY_TAB_CORRUPT, SAFETY_FLAG_CONFIG_WEIGHT_PANIC), \
|
||||
ERR_FLAG_WEIGHT_ENTRY(ERR_FLAG_AMON_SUPPLY_VOLT, SAFETY_FLAG_CONFIG_WEIGHT_PID), \
|
||||
ERR_FLAG_WEIGHT_ENTRY(ERR_FLAG_OVERTEMP, SAFETY_FLAG_CONFIG_WEIGHT_PID), \
|
||||
|
||||
#endif /* __SAFETY_CONFIG_H__ */
|
||||
|
@ -23,7 +23,6 @@
|
||||
* @{
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __SAFETY_CONTROLLER_H__
|
||||
#define __SAFETY_CONTROLLER_H__
|
||||
|
||||
@ -32,30 +31,40 @@
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
enum analog_monitor_status {ANALOG_MONITOR_OK = 0,
|
||||
ANALOG_MONITOR_ERROR,
|
||||
ANALOG_MONITOR_INACTIVE,
|
||||
ANALOG_MONITOR_OVER,
|
||||
ANALOG_MONITOR_UNDER};
|
||||
/**
|
||||
* @brief State of an analog monitor
|
||||
*/
|
||||
enum analog_monitor_status {ANALOG_MONITOR_OK = 0, /**< @brief Monitor set up and ok */
|
||||
ANALOG_MONITOR_ERROR, /**< @brief An internal error occured */
|
||||
ANALOG_MONITOR_INACTIVE, /**< @brief Monitor inactive. Reading is not valid */
|
||||
ANALOG_MONITOR_OVER, /**< @brief Value too high */
|
||||
ANALOG_MONITOR_UNDER}; /**< @brief Value too low */
|
||||
|
||||
/**
|
||||
* @brief Info structure describing an analog monitor
|
||||
*/
|
||||
struct analog_monitor_info {
|
||||
float value;
|
||||
float min;
|
||||
float max;
|
||||
enum analog_monitor_status status;
|
||||
uint64_t timestamp;
|
||||
};
|
||||
|
||||
struct timing_monitor_info {
|
||||
uint64_t last_run;
|
||||
uint64_t min;
|
||||
uint64_t max;
|
||||
bool enabled;
|
||||
uint64_t delta;
|
||||
float value; /**< @brief Current analog value */
|
||||
float min; /**< @brief Minumum value allowed */
|
||||
float max; /**< @brief Maximum value allowed */
|
||||
enum analog_monitor_status status; /**< @brief Current monitor status */
|
||||
enum safety_flag associated_flag; /**< @brief Associated safety flag, that will be set, if monitor out of range */
|
||||
uint64_t timestamp; /**< @brief ms timestamp when @ref analog_monitor_info::value was taken. */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Initialize the safety controller
|
||||
* @brief Info structure describing a timing monitor
|
||||
*/
|
||||
struct timing_monitor_info {
|
||||
uint64_t last_run; /**< @brief Timestamp, when the monitor was last triggered */
|
||||
uint64_t min; /**< @brief Minimum delay between two activations in ms */
|
||||
uint64_t max; /**< @brief Maximum delay between two activations in ms */
|
||||
bool enabled; /**< @brief Monitor enabled */
|
||||
uint64_t delta; /**< @brief Last delta between two activations */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Initialize the safety controller.
|
||||
*
|
||||
* After a call to this function the controller is iniotlaized and the watchdog is set up.
|
||||
* You have to call safety_controller_handle
|
||||
@ -70,43 +79,193 @@ void safety_controller_init();
|
||||
*/
|
||||
int safety_controller_handle();
|
||||
|
||||
/**
|
||||
* @brief Report one or multiple errors to the safety controller
|
||||
*
|
||||
* When passing multipe error glags, the flags have to be ORed together.
|
||||
*
|
||||
* @param flag Error flag to report
|
||||
* @return 0 if successful.
|
||||
*/
|
||||
int safety_controller_report_error(enum safety_flag flag);
|
||||
|
||||
/**
|
||||
* @brief Report one or multiple error flags with a key.
|
||||
*
|
||||
* When setting a \p key on an error flag. The error flag can only be cleared,
|
||||
* by passing the same key value to the @ref safety_controller_ack_flag_with_key function.
|
||||
*
|
||||
* @param flag Error flag to report
|
||||
* @param key Key
|
||||
* @return 0 if successful
|
||||
*/
|
||||
int safety_controller_report_error_with_key(enum safety_flag flag, uint32_t key);
|
||||
|
||||
/**
|
||||
* @brief Report timing to a timing monitor.
|
||||
* @param monitor Monitor to report
|
||||
*/
|
||||
void safety_controller_report_timing(enum timing_monitor monitor);
|
||||
|
||||
/**
|
||||
* @brief Report an analog value to an analog value monitor
|
||||
* @param monitor Monitor to report
|
||||
* @param value Analog value
|
||||
*/
|
||||
void safety_controller_report_analog_value(enum analog_value_monitor monitor, float value);
|
||||
|
||||
/**
|
||||
* @brief Enable or disable a timing monitor.
|
||||
* @param monitor Monitor to enable
|
||||
* @param enable State to set the monitor to.
|
||||
* @return 0 if successful.
|
||||
*/
|
||||
int safety_controller_enable_timing_mon(enum timing_monitor monitor, bool enable);
|
||||
|
||||
/**
|
||||
* @brief Get the value of an analog monitor.
|
||||
* @param monitor Monitor to get value from
|
||||
* @param[out] value The analog value
|
||||
* @returns Status of the analog monitor. \p value only valid, if return value does not indicate an internal error,
|
||||
* or an inactive monitor.
|
||||
*/
|
||||
enum analog_monitor_status safety_controller_get_analog_mon_value(enum analog_value_monitor monitor, float *value);
|
||||
|
||||
/**
|
||||
* @brief Get error flag state and optionally acknowledge the flag
|
||||
*
|
||||
* If the flag is persistent, it cannot be ack'ed. In this case this function
|
||||
* does not return an error code.
|
||||
*
|
||||
* @param flag Error flag
|
||||
* @param[out] status state of the flag.
|
||||
* @param try_ack Try to ack the flag. This might fail, if the flag is persistent.
|
||||
* @return 0 if successful.
|
||||
*/
|
||||
int safety_controller_get_flag(enum safety_flag flag, bool *status, bool try_ack);
|
||||
|
||||
/**
|
||||
* @brief Ack an error flag
|
||||
* @param flag Error flag to ack
|
||||
* @return 0 if successful, -2 if flag is persistent or keyed. All other values: Errors
|
||||
*/
|
||||
int safety_controller_ack_flag(enum safety_flag flag);
|
||||
|
||||
/**
|
||||
* @brief Acknowledge error flag with a key
|
||||
* @param flag Error flag
|
||||
* @param key Key
|
||||
* @return 0 if successful, -2 if flag is persistent or key wrong. All other values: Errors
|
||||
*/
|
||||
int safety_controller_ack_flag_with_key(enum safety_flag flag, uint32_t key);
|
||||
|
||||
/**
|
||||
* @brief Get an ored status of multiple flags.
|
||||
* @param mask Flags to check
|
||||
* @return True if errors. False if no errors.
|
||||
*/
|
||||
bool safety_controller_get_flags_by_mask(enum safety_flag mask);
|
||||
|
||||
/**
|
||||
* @brief Get the count of error flags
|
||||
* @return Error flag count
|
||||
*/
|
||||
uint32_t safety_controller_get_flag_count();
|
||||
|
||||
/**
|
||||
* @brief Get the count of analog monitors
|
||||
* @return Analog monitor count
|
||||
*/
|
||||
uint32_t safety_controller_get_analog_monitor_count();
|
||||
|
||||
/**
|
||||
* @brief Get an error flag's name by its index.
|
||||
*
|
||||
* The name of the flag will be cropped, if the buffersize is too small.
|
||||
* Paramter \p buffsize may not be zero.
|
||||
*
|
||||
* @param index 0 based index.
|
||||
* @param[out] buffer Buffer to write the name to.
|
||||
* @param buffsize Buffer size. This has to be big enough to hold the name and the \0-terminator
|
||||
* @return 0 of successful
|
||||
*/
|
||||
int safety_controller_get_flag_name_by_index(uint32_t index, char *buffer, size_t buffsize);
|
||||
|
||||
/**
|
||||
* @brief Get the safety flag by its internal index.
|
||||
* @param index 0 based index.
|
||||
* @param[out] status Current flag state. May be NULL.
|
||||
* @param[out] flag_enum Flag enum used in SW. May be NULL.
|
||||
* @return 0 if successful; else: negative
|
||||
*/
|
||||
int safety_controller_get_flag_by_index(uint32_t index, bool *status, enum safety_flag *flag_enum);
|
||||
|
||||
/**
|
||||
* @brief Get an analog monitor info by the monitor's index
|
||||
* @param index 0 based index
|
||||
* @param[out] info Info structure.
|
||||
* @return 0 if successful.
|
||||
* -1001, if \p index out of range
|
||||
* -1002, if \p info is NULL
|
||||
*/
|
||||
int safety_controller_get_analog_mon_by_index(uint32_t index, struct analog_monitor_info *info);
|
||||
|
||||
/**
|
||||
* @brief Get the name of an analog monitor by its index
|
||||
*
|
||||
* The buffer has to be large enough to hold the name plus a null terminator.
|
||||
* If the buffer is not large enough, The name will be cropped.
|
||||
* Parameter \p buffsize may not be zero.
|
||||
*
|
||||
* @param index 0 based index
|
||||
* @param buffer Buffer to write name to
|
||||
* @param buffsize Buffer size
|
||||
* @return
|
||||
*/
|
||||
int safety_controller_get_analog_mon_name_by_index(uint32_t index, char *buffer, size_t buffsize);
|
||||
|
||||
/**
|
||||
* @brief Get timing monitor information by index
|
||||
* @param index 0 based index
|
||||
* @param[out] info Info
|
||||
* @return 0 if successful
|
||||
*/
|
||||
int safety_controller_get_timing_mon_by_index(uint32_t index, struct timing_monitor_info *info);
|
||||
|
||||
/**
|
||||
* @brief Get the name of a timing monitor by its index
|
||||
*
|
||||
* The buffer has to be large enough to hold the name plus a null terminator.
|
||||
* If the buffer is not large enough, The name will be cropped.
|
||||
* Parameter \p buffsize may not be zero.
|
||||
*
|
||||
* @param index 0 based index
|
||||
* @param buffer Buffer to write name to.
|
||||
* @param buffsize Buffer size
|
||||
* @return 0 if successful
|
||||
*/
|
||||
int safety_controller_get_timing_mon_name_by_index(uint32_t index, char *buffer, size_t buffsize);
|
||||
|
||||
uint32_t safety_controller_get_timing_monitor_count();
|
||||
/**
|
||||
* @brief Get the count of timing monitors
|
||||
* @return Timing monitor count
|
||||
*/
|
||||
uint32_t safety_controller_get_timing_monitor_count(void);
|
||||
|
||||
/**
|
||||
* @brief Set the overtemperature limit and store it permanently in the EEPROM
|
||||
*
|
||||
* If no EEPROM is present, this will fail. The default value @ref SAFETY_DEFAULT_OVERTEMP_LIMIT_DEGC will be used.
|
||||
* @param over_temperature Over temperature to set
|
||||
* @return 0 if successfully saved and applied, negative if error
|
||||
*/
|
||||
int safety_controller_set_overtemp_limit(float over_temperature);
|
||||
|
||||
/**
|
||||
* @brief Read the current overtemperature limit.
|
||||
* @return Over temperature limit
|
||||
*/
|
||||
float safety_controller_get_overtemp_limit(void);
|
||||
|
||||
#endif /* __SAFETY_CONTROLLER_H__ */
|
||||
|
||||
|
261
stm-firmware/include/reflow-controller/safety/safety-memory.h
Normal file
261
stm-firmware/include/reflow-controller/safety/safety-memory.h
Normal file
@ -0,0 +1,261 @@
|
||||
/* Reflow Oven Controller
|
||||
*
|
||||
* Copyright (C) 2020 Mario Hüttel <mario.huettel@gmx.net>
|
||||
*
|
||||
* This file is part of the Reflow Oven Controller Project.
|
||||
*
|
||||
* The reflow oven controller is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* The Reflow Oven Control Firmware 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 the reflow oven controller project.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __SAFETY_MEMORY_H__
|
||||
#define __SAFETY_MEMORY_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/** @addtogroup safety-memory
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Magic number to signal a valid safety memory header.
|
||||
*/
|
||||
#define SAFETY_MEMORY_MAGIC 0x12AA5CB7
|
||||
|
||||
/**
|
||||
* @brief Error memory NOP entry
|
||||
*/
|
||||
#define SAFETY_MEMORY_NOP_ENTRY 0xC1AA1222
|
||||
|
||||
/**
|
||||
* @brief Offset address for the safety_memory_header.
|
||||
* @note Any other value than 0UL doesn't really make sense. Therfore, this should not be changed.
|
||||
*/
|
||||
#define SAFETY_MEMORY_HEADER_ADDRESS 0UL
|
||||
|
||||
#define SAFETY_MEMORY_CONFIG_OVERRIDE_COUNT 32UL
|
||||
|
||||
/**
|
||||
* @brief Safety memory header
|
||||
*/
|
||||
struct safety_memory_header {
|
||||
uint32_t magic; /**< @brief Magic. Set to @ref SAFETY_MEMORY_MAGIC */
|
||||
uint32_t boot_status_offset; /**< @brief Offset of the safety_memory_boot_status struct (in 32 bit words)*/
|
||||
uint32_t config_overrides_offset; /**< @brief Offset address of override entries */
|
||||
uint32_t config_overrides_len; /**< @brief Length of override entries in words */
|
||||
uint32_t err_memory_offset; /**< @brief Offset of the error memory */
|
||||
uint32_t err_memory_end; /**< @brief End of the error memory. This points to the word after the error memory, containing the CRC of the whole backup RAM. */
|
||||
uint32_t crc; /**< @brief CRC of the header */
|
||||
};
|
||||
|
||||
struct safety_memory_boot_status {
|
||||
/**
|
||||
* @brief Reboot into the bootloader
|
||||
*
|
||||
* When this flag is set, the controller will load the bootloader to
|
||||
* memory and execute it.
|
||||
*/
|
||||
uint32_t reboot_to_bootloader;
|
||||
|
||||
/**
|
||||
* @brief Bootloader has updated the code
|
||||
*
|
||||
* This flag is set, if the firmware ahs been updated successfully
|
||||
*/
|
||||
uint32_t code_updated;
|
||||
|
||||
/**
|
||||
* @brief reset_from_panic
|
||||
*
|
||||
* This flag is set, when entering the panic mode.
|
||||
* Because the panic mode is reset by a watchdog reset,
|
||||
* this flag is needed, in order to ensure, that the panic is handled correcly after
|
||||
* the watchdog reset.
|
||||
*/
|
||||
uint32_t reset_from_panic;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The state of the safety memory
|
||||
*
|
||||
* This is returned by certain functions in order to signal, if the header and CRC infos are valid.
|
||||
*/
|
||||
enum safety_memory_state {
|
||||
SAFETY_MEMORY_INIT_FRESH = 0, /**< @brief Memory header not found */
|
||||
SAFETY_MEMORY_INIT_CORRUPTED = 1, /**< @brief Header found, but corrupt memory */
|
||||
SAFETY_MEMORY_INIT_VALID_MEMORY = 2, /**< @brief Valid header found and CRC check is valid */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Types of error memory entries
|
||||
*/
|
||||
enum safety_memory_error_entry_type {
|
||||
SAFETY_MEMORY_ERR_ENTRY_FLAG = 1, /**< @brief Flag error entry. Logs a flag */
|
||||
SAFETY_MEMORY_ERR_ENTRY_NOP = 2, /**< @brief NOP entry. Has no meaning, but will be treated as a valid entry */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Firmware internal representation of an error memory entry.
|
||||
*/
|
||||
struct error_memory_entry {
|
||||
enum safety_memory_error_entry_type type;
|
||||
uint8_t flag_num;
|
||||
uint16_t counter;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Types of conig override entries
|
||||
*/
|
||||
enum config_override_entry_type {
|
||||
SAFETY_MEMORY_CONFIG_OVERRIDE_WEIGHT = 1,
|
||||
SAFETY_MEMORY_CONFIG_OVERRIDE_PERSISTENCE = 2,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Weights of error flags.
|
||||
*/
|
||||
enum config_weight {
|
||||
SAFETY_FLAG_CONFIG_WEIGHT_NONE = 0, /**< @brief This flag has no global error consequence, but might be respected by certain software modules. */
|
||||
SAFETY_FLAG_CONFIG_WEIGHT_PID = 1, /**< @brief This flag will force a stop of the temperature PID controller */
|
||||
SAFETY_FLAG_CONFIG_WEIGHT_PANIC = 2, /**< @brief This flag will trigger the panic mode */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief representation of a config override memory entry
|
||||
*/
|
||||
struct config_override {
|
||||
enum config_override_entry_type type;
|
||||
union {
|
||||
struct {
|
||||
uint8_t flag;
|
||||
enum config_weight weight;
|
||||
} weight_override;
|
||||
struct {
|
||||
uint8_t flag;
|
||||
bool persistence;
|
||||
} persistence_override;
|
||||
} entry;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief First time init the safety memory. This requests all clocks etc.
|
||||
*
|
||||
* The error memory is always vlaid after this function. At least, if it returns without error.
|
||||
* The \p found_state output tells the caller, in which state the memory was found. If it was uninitialized,
|
||||
* or corrupted, it is completely wiped and a fresh memory structure is written.
|
||||
*
|
||||
* @param[out] found_state State the error memory was found in
|
||||
* @return 0 if successful
|
||||
* @warning Also check @ref safety_memory_reinit
|
||||
*/
|
||||
int safety_memory_init(enum safety_memory_state *found_state);
|
||||
|
||||
/**
|
||||
* @brief Same as @ref safety_memory_init, but without specifically requesting the clock modules.
|
||||
*
|
||||
* Use this, if a call to @ref safety_memory_init has already been done.
|
||||
*
|
||||
* @param[out] found_state State the error memory was found in
|
||||
* @return 0 if successful
|
||||
*/
|
||||
int safety_memory_reinit(enum safety_memory_state *found_state);
|
||||
|
||||
/**
|
||||
* @brief Get the boot status structure from safety memory
|
||||
* @param[out] status Status read from memory.
|
||||
* @return 0 if successful
|
||||
*/
|
||||
int safety_memory_get_boot_status(struct safety_memory_boot_status *status);
|
||||
|
||||
/**
|
||||
* @brief Write the boot status structure to safety memory
|
||||
* @param[in] status Status to write
|
||||
* @return 0 if successful
|
||||
*/
|
||||
int safety_memory_set_boot_status(const struct safety_memory_boot_status *status);
|
||||
|
||||
/**
|
||||
* @brief Get the amout of error entries in the error memory. This also includes NOP entries.
|
||||
* @param[out] count Count
|
||||
* @return 0 if successful
|
||||
*/
|
||||
int safety_memory_get_error_entry_count(uint32_t *count);
|
||||
|
||||
/**
|
||||
* @brief Check the header and CRC of the safety memory.
|
||||
* @return 0 if all checks pass
|
||||
*/
|
||||
int safety_memory_check(void);
|
||||
|
||||
/**
|
||||
* @brief Read an error entry from the error memory
|
||||
* @param idx Index of the entry
|
||||
* @param[out] entry Error entry
|
||||
* @return 0 if successful
|
||||
*/
|
||||
int safety_memory_get_error_entry(uint32_t idx, struct error_memory_entry *entry);
|
||||
|
||||
/**
|
||||
* @brief Insert an error entry
|
||||
*
|
||||
* This function inserts an error entry on the first NOP entry found in the error memory.
|
||||
* If an entry is found with the same flag number, its counter is incremented by the counter value of the
|
||||
* element to insert.
|
||||
*
|
||||
* If there are no NOPs or fitting entries in the error memory, error memory is expanded until it hits the memory
|
||||
* boundary.
|
||||
*
|
||||
* @param entry Error entry to insert
|
||||
* @returns 0 if successful, -3 if out of memory, and other negative error codes
|
||||
*/
|
||||
int safety_memory_insert_error_entry(struct error_memory_entry *entry);
|
||||
|
||||
/**
|
||||
* @brief Insert a config override entry at the first free location.
|
||||
*
|
||||
* Free locations are entries containing 0x00000000
|
||||
*
|
||||
* @param config_override Config to write
|
||||
* @return 0 if successful
|
||||
*/
|
||||
int safety_memory_insert_config_override(struct config_override *config_override);
|
||||
|
||||
/**
|
||||
* @brief Get count of config overrides
|
||||
* @param[out] count Number of overrides
|
||||
* @return 0 if successful
|
||||
*/
|
||||
int safety_memory_get_config_override_count(uint32_t *count);
|
||||
|
||||
/**
|
||||
* @brief Get a config ovveide entry
|
||||
* @param idx Index of the requested entry
|
||||
* @param[out] config_override READ override
|
||||
* @return 0 if successful
|
||||
*/
|
||||
int safety_memory_get_config_override(uint32_t idx, struct config_override *config_override);
|
||||
|
||||
/**
|
||||
* @brief Get a base64 dump of the whole safety memory.
|
||||
* @param[out] buffer Buffer to write the base 64 dump into.
|
||||
* @param buffsize Size of buffer. Must be large enough to hold the data plus a '\0' terminator
|
||||
* @param[out] used_size Number of written bytes including the '\0' terminator. May be NULL.
|
||||
* @return 0 if successful
|
||||
*/
|
||||
int safety_memory_dump_base64(char *buffer, size_t buffsize, size_t *used_size);
|
||||
|
||||
#endif /* __SAFETY_MEMORY_H__ */
|
||||
|
||||
/** @} */
|
97
stm-firmware/include/reflow-controller/safety/stack-check.h
Normal file
97
stm-firmware/include/reflow-controller/safety/stack-check.h
Normal file
@ -0,0 +1,97 @@
|
||||
/* Reflow Oven Controller
|
||||
*
|
||||
* Copyright (C) 2020 Mario Hüttel <mario.huettel@gmx.net>
|
||||
*
|
||||
* This file is part of the Reflow Oven Controller Project.
|
||||
*
|
||||
* The reflow oven controller is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* GDSII-Converter 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 the reflow oven controller project.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __STACK_CHECK_H__
|
||||
#define __STACK_CHECK_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define STACK_CHECK_MIN_HEAP_GAP 16UL
|
||||
|
||||
/**
|
||||
* @brief Get usage of the stack
|
||||
* @return Usage of the stack in bytes
|
||||
*/
|
||||
int32_t stack_check_get_usage();
|
||||
|
||||
/**
|
||||
* @brief Get free stack space
|
||||
* @return free stack space in bytes. If negative, a stack overflow occured
|
||||
*/
|
||||
int32_t stack_check_get_free();
|
||||
|
||||
/**
|
||||
* @brief Check if the current free stack space is bigger than @ref STACK_CHECK_MIN_HEAP_GAP
|
||||
* @return 0: enough space available, -1: stack space low
|
||||
*/
|
||||
static inline int stack_check_collision()
|
||||
{
|
||||
int ret = 0;
|
||||
int32_t free_space = stack_check_get_free();
|
||||
|
||||
if ((unsigned int)free_space < STACK_CHECK_MIN_HEAP_GAP) {
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the current stack pointer value
|
||||
* @return
|
||||
*/
|
||||
static inline uint32_t read_stack_pointer()
|
||||
{
|
||||
uint32_t stack_pointer;
|
||||
|
||||
__asm__ __volatile__ ("mov %0, sp\n\t" : "=r"(stack_pointer) : : );
|
||||
|
||||
return stack_pointer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Init the stack corruption detection area.
|
||||
*
|
||||
* This function initializes the memory area between heap and stack with random values generated by the
|
||||
* STM's random number generator. A 32 bit CRC generated by the CRC unit of the STM is appended for verification of the
|
||||
* area.
|
||||
*
|
||||
*
|
||||
* @return 0 if successful, else an error has occured in generating a random number. This should never happen
|
||||
* @note This function turns on the CRC unit but does not disable it afterwards. Therefore, the CRC unit does not have
|
||||
* to be explicitly initialized before calling @ref stack_check_corruption_detect_area.
|
||||
*/
|
||||
int stack_check_init_corruption_detect_area(void);
|
||||
|
||||
/**
|
||||
* @brief Check the CRC of the stack corruption detection area
|
||||
*
|
||||
* This function checks the stack corruption detection area, which must be initialized by
|
||||
* @ref stack_check_init_corruption_detect_area beforehand.
|
||||
*
|
||||
* The CRC unit must be enabled for this function to work properly.
|
||||
* After calling @ref stack_check_init_corruption_detect_area, this is the case.
|
||||
*
|
||||
* @return 0 if no error is detected, all other values are an error.
|
||||
* @note Make sure CRC unit is enabled.
|
||||
*/
|
||||
int stack_check_corruption_detect_area(void);
|
||||
|
||||
#endif /* __STACK_CHECK_H__ */
|
@ -40,11 +40,4 @@ int watchdog_setup(uint8_t prescaler);
|
||||
*/
|
||||
int watchdog_ack(uint32_t magic);
|
||||
|
||||
/**
|
||||
* @brief Check if reset was generated by the watchdog.
|
||||
* @note This also clears the relevant flag, so the function will reutrn false when called a second time
|
||||
* @return
|
||||
*/
|
||||
bool watchdog_check_reset_source(void);
|
||||
|
||||
#endif /* __WATCHDOG_H__ */
|
||||
|
@ -18,30 +18,19 @@
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <reflow-controller/stack-check.h>
|
||||
#include <stdint.h>
|
||||
#ifndef __SETTINGS_SETTINGS_EEPROM_H__
|
||||
#define __SETTINGS_SETTINGS_EEPROM_H__
|
||||
|
||||
extern char __ld_top_of_stack;
|
||||
extern char __ld_eheap;
|
||||
#include <stdbool.h>
|
||||
|
||||
int32_t stack_check_get_usage()
|
||||
{
|
||||
uint32_t stack_top;
|
||||
uint32_t stack_ptr;
|
||||
bool settings_eeprom_detect_and_prepare(void);
|
||||
|
||||
stack_ptr = read_stack_pointer();
|
||||
stack_top = (uint32_t)&__ld_top_of_stack;
|
||||
int settings_eeprom_save_calibration(float sens, float offset, bool active);
|
||||
|
||||
return stack_top - stack_ptr;
|
||||
}
|
||||
int settings_eeprom_load_calibration(float *sens, float *offset, bool *active);
|
||||
|
||||
int32_t stack_check_get_free()
|
||||
{
|
||||
uint32_t upper_heap_boundary;
|
||||
uint32_t stack_ptr;
|
||||
int settings_eeprom_save_overtemp_limit(float overtemp_limit, bool active);
|
||||
|
||||
stack_ptr = read_stack_pointer();
|
||||
upper_heap_boundary = (uint32_t)&__ld_eheap;
|
||||
int settings_eeprom_load_overtemp_limit(float *overtemp_limit);
|
||||
|
||||
return stack_ptr - upper_heap_boundary;
|
||||
}
|
||||
#endif /* __SETTINGS_SETTINGS_EEPROM_H__ */
|
@ -22,8 +22,7 @@
|
||||
#define __SETTINGS_SETTINGS_SD_CARD_H__
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#define CALIBRATION_FILE_NAME "settings.ini"
|
||||
#include <reflow-controller/settings/settings.h>
|
||||
|
||||
int sd_card_settings_save_calibration(float sens_deviation, float offset, bool active);
|
||||
|
||||
@ -35,4 +34,6 @@ int sd_card_settings_save_calibration(float sens_deviation, float offset, bool a
|
||||
*/
|
||||
int sd_card_settings_try_load_calibration(float *sens_deviation, float *offset);
|
||||
|
||||
enum settings_load_result sd_card_settings_load_pid_oven_parameters(struct oven_pid_settings *settings);
|
||||
|
||||
#endif /* __SETTINGS_SETTINGS_SD_CARD_H__ */
|
||||
|
@ -24,6 +24,24 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
struct oven_pid_settings {
|
||||
float kd;
|
||||
float kp;
|
||||
float ki;
|
||||
float kd_tau;
|
||||
float t_sample;
|
||||
float max_integral;
|
||||
};
|
||||
|
||||
enum settings_load_result {
|
||||
SETT_LOAD_SUCCESS = 0,
|
||||
SETT_LOAD_FILE_NOT_FOUND,
|
||||
SETT_LOAD_ERR,
|
||||
SETT_LOAD_DISK_ERR
|
||||
};
|
||||
|
||||
#define SETTINGS_PID_PARAMETER_FILE "pid.conf"
|
||||
|
||||
/**
|
||||
* @brief Save the calibration
|
||||
* @param sens_deviation
|
||||
@ -34,4 +52,12 @@ int settings_save_calibration(float sens_deviation, float offset, bool active);
|
||||
|
||||
int settings_load_calibration(float *sens_dev, float *offset);
|
||||
|
||||
enum settings_load_result settings_load_pid_oven_parameters(struct oven_pid_settings *settings);
|
||||
|
||||
enum settings_load_result settings_load_overtemp_limit(float *over_temp_limit);
|
||||
|
||||
int settings_save_overtemp_limit(float over_temp_limit, bool active);
|
||||
|
||||
void settings_setup(void);
|
||||
|
||||
#endif /* __SETTINGS_SETTINGS_H__ */
|
||||
|
42
stm-firmware/include/reflow-controller/settings/spi-eeprom.h
Normal file
42
stm-firmware/include/reflow-controller/settings/spi-eeprom.h
Normal file
@ -0,0 +1,42 @@
|
||||
/* Reflow Oven Controller
|
||||
*
|
||||
* Copyright (C) 2021 Mario Hüttel <mario.huettel@gmx.net>
|
||||
*
|
||||
* This file is part of the Reflow Oven Controller Project.
|
||||
*
|
||||
* The reflow oven controller is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* The Reflow Oven Control Firmware 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 the reflow oven controller project.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __SETTINGS_SPI_EEPROM_H__
|
||||
#define __SETTINGS_SPI_EEPROM_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
|
||||
int spi_eeprom_init();
|
||||
|
||||
void spi_eeprom_deinit();
|
||||
|
||||
int spi_eeprom_read(uint32_t addr, uint8_t *rx_buff, uint32_t count);
|
||||
|
||||
bool spi_eeprom_write_in_progress(void);
|
||||
|
||||
int spi_eeprom_write(uint32_t addr, const uint8_t *data, uint32_t count);
|
||||
|
||||
uint8_t spi_eeprom_read_status_reg(void);
|
||||
|
||||
bool spi_eeprom_check_connected(void);
|
||||
|
||||
#endif /* __SETTINGS_SPI_EEPROM_H__ */
|
@ -12,7 +12,7 @@
|
||||
#define SHELL_UART_RX_DMA_TRIGGER 4U
|
||||
#define SHELL_UART_TX_DMA_TRIGGER 4U
|
||||
|
||||
#ifdef DEBUGBUILD
|
||||
#if defined(DEBUGBUILD) || defined(UART_ON_DEBUG_HEADER)
|
||||
|
||||
#define SHELL_UART_PORT GPIOA
|
||||
#define SHELL_UART_PORT_RCC_MASK RCC_AHB1ENR_GPIOAEN
|
||||
|
@ -31,7 +31,7 @@
|
||||
/**
|
||||
* @brief Reload value for the systick timer.
|
||||
*
|
||||
* This value has to be configured to set the systick to a one milliscond tick interval
|
||||
* This value has to be configured to set the systick to a 100 us tick interval
|
||||
* The default value is 16800, which results in a 100us tick for 168 MHz CPU speed
|
||||
*/
|
||||
#define SYSTICK_RELOAD (16800UL)
|
||||
@ -55,7 +55,7 @@ extern volatile uint64_t global_tick_ms;
|
||||
extern volatile uint32_t lcd_tick_100us;
|
||||
|
||||
/**
|
||||
* @brief Setup the Systick timer to generate a 1 ms tick
|
||||
* @brief Setup the Systick timer to generate a 100 us tick
|
||||
*/
|
||||
void systick_setup(void);
|
||||
|
||||
|
@ -29,4 +29,13 @@
|
||||
*/
|
||||
int temp_converter_convert_resistance_to_temp(float resistance, float *temp_out);
|
||||
|
||||
/**
|
||||
* @brief Convert temperature to PT1000 resistance
|
||||
* @param temp Temperature in degrees celsius
|
||||
* @param[out] resistance_out Resistance value
|
||||
* @return 0 if ok, -1 if tmeperature is below the lookup table, 1 if value is above the lookup table, -1000 in case of a pointer error,
|
||||
* -100 if an internal error occured. This should never happen, if the lookup table is correct
|
||||
*/
|
||||
int temp_convertet_convert_temp_to_resistance(float temp, float *resistance_out);
|
||||
|
||||
#endif /* __TEMP_CONVERTER_H__ */
|
||||
|
@ -0,0 +1,52 @@
|
||||
/* Reflow Oven Controller
|
||||
*
|
||||
* Copyright (C) 2021 Mario Hüttel <mario.huettel@gmx.net>
|
||||
*
|
||||
* This file is part of the Reflow Oven Controller Project.
|
||||
*
|
||||
* The reflow oven controller is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* The Reflow Oven Control Firmware 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 the reflow oven controller project.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __TEMP_PROFILE_EXECUTER_H__
|
||||
#define __TEMP_PROFILE_EXECUTER_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <reflow-controller/config-parser/temp-profile-parser.h>
|
||||
|
||||
#define MAX_PROFILE_LENGTH 50
|
||||
|
||||
enum tpe_status {
|
||||
TPE_OFF,
|
||||
TPE_RUNNING,
|
||||
TPE_ABORT,
|
||||
};
|
||||
|
||||
struct tpe_current_state {
|
||||
enum tpe_status status;
|
||||
float setpoint;
|
||||
uint64_t start_timestamp;
|
||||
uint32_t step;
|
||||
uint32_t profile_steps;
|
||||
enum pl_command_type current_command;
|
||||
};
|
||||
|
||||
enum pl_ret_val temp_profile_executer_start(const char *filename);
|
||||
|
||||
int temp_profile_executer_handle(void);
|
||||
|
||||
const struct tpe_current_state *temp_profile_executer_status(void);
|
||||
|
||||
int temp_profile_executer_stop(void);
|
||||
|
||||
#endif /* __TEMP_PROFILE_EXECUTER_H__ */
|
7
stm-firmware/include/reflow-controller/ui/gui-config.h
Normal file
7
stm-firmware/include/reflow-controller/ui/gui-config.h
Normal file
@ -0,0 +1,7 @@
|
||||
#ifndef _GUI_CONFIG_H_
|
||||
#define _GUI_CONFIG_H_
|
||||
|
||||
#define GUI_MONITORING_INTERVAL_MS 500U
|
||||
#define GUI_TEMP_DRIVER_REFRESH_MS 750U
|
||||
|
||||
#endif /* _GUI_CONFIG_H_ */
|
@ -18,15 +18,15 @@
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __REFLOW_MENU_H__
|
||||
#define __REFLOW_MENU_H__
|
||||
#ifndef _GUI_H_
|
||||
#define _GUI_H_
|
||||
|
||||
/**
|
||||
* @brief Handle the reflow controller's LCD Menu
|
||||
* @return 0 if no delay is requested, 1 if delay is requested
|
||||
*/
|
||||
int reflow_menu_handle(void);
|
||||
int gui_handle(void);
|
||||
|
||||
void reflow_menu_init(void);
|
||||
void gui_init(void);
|
||||
|
||||
#endif /* __REFLOW_MENU_H__ */
|
||||
#endif /* _GUI_H_ */
|
@ -35,6 +35,7 @@ typedef void (*menu_func_t)(struct lcd_menu *menu, enum menu_entry_func_entry en
|
||||
struct menu_inputs {
|
||||
int16_t rotary_encoder_delta;
|
||||
enum button_state push_button;
|
||||
bool button_ready;
|
||||
};
|
||||
|
||||
struct lcd_menu {
|
||||
@ -76,6 +77,8 @@ int16_t menu_get_rotary_delta(const struct lcd_menu *menu);
|
||||
|
||||
enum button_state menu_get_button_state(const struct lcd_menu *menu);
|
||||
|
||||
bool menu_get_button_ready_state(const struct lcd_menu *menu);
|
||||
|
||||
void menu_list_compute_count(struct menu_list *list);
|
||||
|
||||
void menu_list_scroll_down(struct menu_list *list);
|
||||
@ -84,4 +87,6 @@ void menu_list_scroll_up(struct menu_list *list);
|
||||
|
||||
void menu_list_enter_selected_entry(struct menu_list *list, struct lcd_menu *menu);
|
||||
|
||||
uint32_t menu_list_get_currently_selected(struct menu_list *list);
|
||||
|
||||
#endif /* __MENU_H__ */
|
||||
|
@ -8,7 +8,7 @@
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* GDSII-Converter is distributed in the hope that it will be useful,
|
||||
* The Reflow Oven Control Firmware 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.
|
||||
@ -18,36 +18,14 @@
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __STACK_CHECK_H__
|
||||
#define __STACK_CHECK_H__
|
||||
#ifndef __UPDATER_UPDATER_H__
|
||||
#define __UPDATER_UPDATER_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#define UPDATER_RAM_CODE_BASE_ADDRESS (0x20000000UL)
|
||||
|
||||
#define STACK_CHECK_MIN_HEAP_GAP 16UL
|
||||
/**
|
||||
* @brief Start the RAM Code of the updater. This function will never return!
|
||||
*/
|
||||
void __attribute__((noreturn)) start_updater(void);
|
||||
|
||||
int32_t stack_check_get_usage();
|
||||
|
||||
int32_t stack_check_get_free();
|
||||
|
||||
static inline int stack_check_collision()
|
||||
{
|
||||
int ret = 0;
|
||||
int32_t free_space = stack_check_get_free();
|
||||
|
||||
if ((unsigned int)free_space < STACK_CHECK_MIN_HEAP_GAP) {
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline uint32_t read_stack_pointer()
|
||||
{
|
||||
uint32_t stack_pointer;
|
||||
|
||||
__asm__ __volatile__ ("mov %0, sp\n\t" : "=r"(stack_pointer) : : );
|
||||
|
||||
return stack_pointer;
|
||||
}
|
||||
|
||||
#endif /* __STACK_CHECK_H__ */
|
||||
#endif /* __UPDATER_UPDATER_H__ */
|
@ -19,33 +19,41 @@
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* @brief Init the backup ram and make it accesible
|
||||
* @param use_backup_regulator Enable the Backup VBAT regulator. It will be used, when VDD is powered off
|
||||
*/
|
||||
void backup_ram_init();
|
||||
void backup_ram_init(bool use_backup_regulator);
|
||||
|
||||
/**
|
||||
* @brief Disable access to the backup RAM. This saves power
|
||||
*/
|
||||
void backup_ram_disable();
|
||||
void backup_ram_disable(void);
|
||||
|
||||
/**
|
||||
* @brief Whis function overwrites the backup RAM with 0x00
|
||||
* @brief Whis function overwrites the backup RAM with 0x00000000
|
||||
*/
|
||||
void backup_ram_wipe();
|
||||
void backup_ram_wipe(void);
|
||||
|
||||
/**
|
||||
* @brief Read data from the backup RAM
|
||||
* @param addr Address offset inside memory
|
||||
* @param data read 32bit data
|
||||
* @param data Read data
|
||||
* @param count amount of 32 bit words to read
|
||||
* @return 0 if successful
|
||||
*/
|
||||
int backup_ram_get_data(uint32_t addr, uint32_t *data);
|
||||
int backup_ram_get_data(uint32_t addr, uint32_t *data, uint32_t count);
|
||||
|
||||
/**
|
||||
* @brief Write data structure to backup RAM
|
||||
* @param data
|
||||
* @return
|
||||
* @param[in] data Data to write.
|
||||
* @param count Count of 32 bit words to write
|
||||
* @return 0 if successful
|
||||
*/
|
||||
int backup_ram_write_data(uint32_t addr, uint32_t data);
|
||||
int backup_ram_write_data(uint32_t addr, const uint32_t *data, uint32_t count);
|
||||
|
||||
uint32_t backup_ram_get_size_in_words(void);
|
||||
|
||||
volatile void *backup_ram_get_base_ptr(void);
|
||||
|
38
stm-firmware/include/stm-periph/crc-unit.h
Normal file
38
stm-firmware/include/stm-periph/crc-unit.h
Normal file
@ -0,0 +1,38 @@
|
||||
/* Reflow Oven Controller
|
||||
*
|
||||
* Copyright (C) 2020 Mario Hüttel <mario.huettel@gmx.net>
|
||||
*
|
||||
* This file is part of the Reflow Oven Controller Project.
|
||||
*
|
||||
* The reflow oven controller is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* GDSII-Converter 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 the reflow oven controller project.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __CRC_UNIT_H__
|
||||
#define __CRC_UNIT_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
void crc_unit_init(void);
|
||||
|
||||
void crc_unit_deinit(void);
|
||||
|
||||
void crc_unit_reset(void);
|
||||
|
||||
uint32_t crc_unit_get_crc(void);
|
||||
|
||||
void crc_unit_input(uint32_t data);
|
||||
|
||||
void crc_unit_input_array(const uint32_t *data, uint32_t len);
|
||||
|
||||
#endif /* __CRC_UNIT_H__ */
|
@ -18,11 +18,12 @@
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __CLOCK_ENABLE_MANAGER_H__
|
||||
#define __CLOCK_ENABLE_MANAGER_H__
|
||||
#ifndef __RCC_MANAGER_H__
|
||||
#define __RCC_MANAGER_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stm-periph/stm32-gpio-macros.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* @brief The RCC Enable Manager uses static memory with a fixed maximum
|
||||
@ -35,6 +36,19 @@
|
||||
#error "RCC Enable Manager not yet implemented with dynamic memory"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief The source/cause of a system reset. The values can be or'ed together as multiple flags may be possible at the same time.
|
||||
*/
|
||||
enum rcc_reset_source {
|
||||
RCC_RESET_SOURCE_LOW_POWER = (1<<0), /**< @brief System reset caused by low power reset */
|
||||
RCC_RESET_SOURCE_SOFTWARE = (1<<1), /**< @brief System reset caused by software */
|
||||
RCC_RESET_SOURCE_WWD = (1<<2), /**< @brief System reset caused by window watchdog */
|
||||
RCC_RESET_SOURCE_IWDG = (1<<3), /**< @brief System reset caused by independent watchdog */
|
||||
RCC_RESET_SOURCE_POWER_ON = (1<<4), /**< @brief System reset caused by power on reset (POR) */
|
||||
RCC_RESET_SOURCE_PIN = (1<<5), /**< @brief System reset caused by NRST pin */
|
||||
RCC_RESET_BOR_POR = (1<<6), /**< @brief System reset caused by eitehr brown out or POR */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Enable Clock for peripheral by setting the corresponding bit (@p bit_no) to one
|
||||
*
|
||||
@ -64,4 +78,11 @@ int rcc_manager_enable_clock(volatile uint32_t *rcc_enable_register, uint8_t bit
|
||||
*/
|
||||
int rcc_manager_disable_clock(volatile uint32_t *rcc_enable_register, uint8_t bit_no);
|
||||
|
||||
#endif /* __CLOCK_ENABLE_MANAGER_H__ */
|
||||
/**
|
||||
* @brief Get the causes of the last system reset.
|
||||
* @param clear_flags Clear the reset cause
|
||||
* @return Reset cause
|
||||
*/
|
||||
enum rcc_reset_source rcc_manager_get_reset_cause(bool clear_flags);
|
||||
|
||||
#endif /* __RCC_MANAGER_H__ */
|
42
stm-firmware/include/stm-periph/rng.h
Normal file
42
stm-firmware/include/stm-periph/rng.h
Normal file
@ -0,0 +1,42 @@
|
||||
/* Reflow Oven Controller
|
||||
*
|
||||
* Copyright (C) 2020 Mario Hüttel <mario.huettel@gmx.net>
|
||||
*
|
||||
* This file is part of the Reflow Oven Controller Project.
|
||||
*
|
||||
* The reflow oven controller is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* GDSII-Converter 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 the reflow oven controller project.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __STM_RNG_H__
|
||||
#define __STM_RNG_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
enum random_number_error {
|
||||
RNG_ERROR_OK = 0,
|
||||
RNG_ERROR_INACT,
|
||||
RNG_ERROR_INTERNAL_ERROR,
|
||||
RNG_ERROR_NOT_READY
|
||||
};
|
||||
|
||||
void random_number_gen_init(bool int_enable);
|
||||
|
||||
void random_number_gen_deinit();
|
||||
|
||||
void random_number_gen_reset(bool int_en);
|
||||
|
||||
enum random_number_error random_number_gen_get_number(uint32_t *random_number, bool wait_for_valid_value);
|
||||
|
||||
#endif /* __STM_RNG_H__ */
|
62
stm-firmware/include/stm-periph/spi.h
Normal file
62
stm-firmware/include/stm-periph/spi.h
Normal file
@ -0,0 +1,62 @@
|
||||
/* Reflow Oven Controller
|
||||
*
|
||||
* Copyright (C) 2021 Mario Hüttel <mario.huettel@gmx.net>
|
||||
*
|
||||
* This file is part of the Reflow Oven Controller Project.
|
||||
*
|
||||
* The reflow oven controller is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* The Reflow Oven Control Firmware 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 the reflow oven controller project.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __SPI_H__
|
||||
#define __SPI_H__
|
||||
|
||||
#include <stm32/stm32f4xx.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
enum stm_spi_prescaler {
|
||||
SPI_PRSC_DIV2 = 0,
|
||||
SPI_PRSC_DIV4 = 1,
|
||||
SPI_PRSC_DIV8 = 2,
|
||||
SPI_PRSC_DIV16 = 3,
|
||||
SPI_PRSC_DIV32 = 4,
|
||||
SPI_PRSC_DIV64 = 5,
|
||||
SPI_PRSC_DIV128 = 6,
|
||||
SPI_PRSC_DIV256 = 7,
|
||||
};
|
||||
|
||||
struct stm_spi_settings {
|
||||
void (*cs_activate)(void);
|
||||
void (*cs_deactivate)(void);
|
||||
bool cpol;
|
||||
bool cpha;
|
||||
bool master;
|
||||
bool msb_first;
|
||||
enum stm_spi_prescaler prescaler;
|
||||
};
|
||||
|
||||
struct stm_spi_dev {
|
||||
uint32_t magic;
|
||||
SPI_TypeDef *spi_regs;
|
||||
struct stm_spi_settings settings;
|
||||
};
|
||||
|
||||
typedef void * stm_spi_handle;
|
||||
|
||||
stm_spi_handle spi_init(struct stm_spi_dev *spi_dev_struct, SPI_TypeDef *spi_regs, const struct stm_spi_settings *settings);
|
||||
|
||||
void spi_deinit(stm_spi_handle handle);
|
||||
|
||||
int spi_transfer(stm_spi_handle handle, const uint8_t *tx, uint8_t *rx, uint32_t count, bool handle_cs);
|
||||
|
||||
#endif /* __SPI_H__ */
|
@ -35,14 +35,19 @@
|
||||
#include <reflow-controller/digio.h>
|
||||
#include "fatfs/shimatta_sdio_driver/shimatta_sdio.h"
|
||||
#include <stm-periph/stm32-gpio-macros.h>
|
||||
#include <stm-periph/clock-enable-manager.h>
|
||||
#include <stm-periph/rcc-manager.h>
|
||||
#include <stm-periph/uart.h>
|
||||
#include <reflow-controller/shell-uart-config.h>
|
||||
#include <reflow-controller/oven-driver.h>
|
||||
#include <fatfs/ff.h>
|
||||
#include <reflow-controller/reflow-menu.h>
|
||||
#include <reflow-controller/ui/gui.h>
|
||||
#include <reflow-controller/safety/safety-controller.h>
|
||||
#include <reflow-controller/settings/settings.h>
|
||||
#include <reflow-controller/safety/safety-memory.h>
|
||||
#include <reflow-controller/safety/fault.h>
|
||||
#include <reflow-controller/updater/updater.h>
|
||||
#include <reflow-controller/temp-profile-executer.h>
|
||||
#include <reflow-controller/settings/spi-eeprom.h>
|
||||
|
||||
static void setup_nvic_priorities(void)
|
||||
{
|
||||
@ -51,8 +56,11 @@ static void setup_nvic_priorities(void)
|
||||
|
||||
/* Setup Priorities */
|
||||
NVIC_SetPriority(ADC_IRQn, 2);
|
||||
/* Measurement ADC DMA */
|
||||
NVIC_SetPriority(DMA2_Stream0_IRQn, 1);
|
||||
/* Shelmatta UART TX */
|
||||
NVIC_SetPriority(DMA2_Stream7_IRQn, 3);
|
||||
NVIC_SetPriority(DMA2_Stream4_IRQn, 2);
|
||||
}
|
||||
|
||||
FATFS fs;
|
||||
@ -65,7 +73,7 @@ static inline void uart_gpio_config(void)
|
||||
* else the Pins on the DIGIO header are configured in the digio module
|
||||
*/
|
||||
|
||||
#ifdef DEBUGBUILD
|
||||
#if defined(DEBUGBUILD) || defined(UART_ON_DEBUG_HEADER)
|
||||
rcc_manager_enable_clock(&RCC->AHB1ENR, BITMASK_TO_BITNO(SHELL_UART_PORT_RCC_MASK));
|
||||
SHELL_UART_PORT->MODER &= MODER_DELETE(SHELL_UART_TX_PIN) & MODER_DELETE(SHELL_UART_RX_PIN);
|
||||
SHELL_UART_PORT->MODER |= ALTFUNC(SHELL_UART_RX_PIN) | ALTFUNC(SHELL_UART_TX_PIN);
|
||||
@ -78,7 +86,7 @@ static inline void uart_gpio_config(void)
|
||||
}
|
||||
|
||||
|
||||
static char shell_uart_tx_buff[128];
|
||||
static char shell_uart_tx_buff[256];
|
||||
static char shell_uart_rx_buff[48];
|
||||
struct stm_uart shell_uart;
|
||||
|
||||
@ -139,35 +147,51 @@ static bool mount_sd_card_if_avail(bool mounted)
|
||||
return mounted;
|
||||
}
|
||||
|
||||
static void setup_unused_pins(void)
|
||||
static inline void handle_boot_status(void)
|
||||
{
|
||||
int i;
|
||||
struct safety_memory_boot_status status;
|
||||
int res;
|
||||
|
||||
rcc_manager_enable_clock(&RCC->AHB1ENR, BITMASK_TO_BITNO(RCC_AHB1ENR_GPIOEEN));
|
||||
GPIOE->MODER = 0UL;
|
||||
for (i = 0; i < 16; i++)
|
||||
GPIOE->PUPDR |= PULLDOWN(i);
|
||||
res = safety_memory_get_boot_status(&status);
|
||||
if (res != 0)
|
||||
panic_mode();
|
||||
if (status.reboot_to_bootloader) {
|
||||
status.reboot_to_bootloader = 0UL;
|
||||
safety_memory_set_boot_status(&status);
|
||||
|
||||
led_set(0, 1);
|
||||
led_set(1, 1);
|
||||
|
||||
start_updater();
|
||||
}
|
||||
}
|
||||
|
||||
static inline void setup_system(void)
|
||||
{
|
||||
float tmp;
|
||||
|
||||
setup_nvic_priorities();
|
||||
|
||||
/* Init safety controller and safety memory */
|
||||
safety_controller_init();
|
||||
|
||||
systick_setup();
|
||||
|
||||
|
||||
oven_driver_init();
|
||||
digio_setup_default_all();
|
||||
led_setup();
|
||||
loudspeaker_setup();
|
||||
reflow_menu_init();
|
||||
|
||||
|
||||
gui_init();
|
||||
uart_gpio_config();
|
||||
settings_setup();
|
||||
|
||||
/* Load the overtemperature limit from eeprom if available. Otherwise the default value will be used */
|
||||
if (settings_load_overtemp_limit(&tmp) == SETT_LOAD_SUCCESS) {
|
||||
safety_controller_set_overtemp_limit(tmp);
|
||||
}
|
||||
|
||||
handle_boot_status();
|
||||
|
||||
setup_shell_uart(&shell_uart);
|
||||
|
||||
setup_unused_pins();
|
||||
|
||||
safety_controller_init();
|
||||
adc_pt1000_setup_meas();
|
||||
}
|
||||
|
||||
@ -194,8 +218,16 @@ int main(void)
|
||||
shellmatta_handle_t shell_handle;
|
||||
int menu_wait_request;
|
||||
uint64_t quarter_sec_timestamp = 0ULL;
|
||||
static uint64_t IN_SECTION(.ccm.bss) main_loop_iter_count;
|
||||
|
||||
setup_system();
|
||||
|
||||
/* Try load the calibration. This will only succeed if there's an EEPROM */
|
||||
status = settings_load_calibration(&sens, &offset);
|
||||
if (!status) {
|
||||
adc_pt1000_set_resistance_calibration(offset, sens, true);
|
||||
}
|
||||
|
||||
shell_handle = shell_init(write_shell_callback);
|
||||
shell_print_motd(shell_handle);
|
||||
|
||||
@ -219,20 +251,24 @@ int main(void)
|
||||
quarter_sec_timestamp = systick_get_global_tick();
|
||||
}
|
||||
|
||||
menu_wait_request = reflow_menu_handle();
|
||||
menu_wait_request = gui_handle();
|
||||
handle_shell_uart_input(shell_handle);
|
||||
|
||||
/* Execute current profile step, if a profile is active */
|
||||
temp_profile_executer_handle();
|
||||
|
||||
safety_controller_handle();
|
||||
oven_driver_set_power(0);
|
||||
if (oven_pid_get_status() == OVEN_PID_RUNNING) {
|
||||
oven_pid_handle();
|
||||
}
|
||||
oven_driver_apply_power_level();
|
||||
|
||||
safety_controller_report_timing(ERR_TIMING_MAIN_LOOP);
|
||||
|
||||
if (menu_wait_request)
|
||||
__WFI();
|
||||
else
|
||||
__NOP();
|
||||
|
||||
main_loop_iter_count++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -243,9 +279,12 @@ void sdio_wait_ms(uint32_t ms)
|
||||
systick_wait_ms(ms);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handles the TX of UART1 (Shellmatta)
|
||||
*/
|
||||
void DMA2_Stream7_IRQHandler(void)
|
||||
{
|
||||
uint32_t hisr = DMA2->HISR;
|
||||
uint32_t hisr = DMA2->HISR & (0x3F << 22);
|
||||
|
||||
DMA2->HIFCR = hisr;
|
||||
|
||||
|
Binary file not shown.
3
stm-firmware/obj/.gitignore
vendored
3
stm-firmware/obj/.gitignore
vendored
@ -1,3 +0,0 @@
|
||||
*
|
||||
*.*
|
||||
!.gitignore
|
@ -20,17 +20,31 @@
|
||||
|
||||
#include <reflow-controller/oven-driver.h>
|
||||
#include <reflow-controller/periph-config/oven-driver-hwcfg.h>
|
||||
#include <stm-periph/clock-enable-manager.h>
|
||||
#include <stm-periph/rcc-manager.h>
|
||||
#include <reflow-controller/systick.h>
|
||||
#include <reflow-controller/adc-meas.h>
|
||||
#include <reflow-controller/temp-converter.h>
|
||||
#include <helper-macros/helper-macros.h>
|
||||
#include <reflow-controller/safety/safety-controller.h>
|
||||
#include <reflow-controller/hw-version-detect.h>
|
||||
|
||||
static struct pid_controller IN_SECTION(.ccm.bss) oven_pid;
|
||||
static bool oven_pid_running;
|
||||
static bool oven_pid_aborted;
|
||||
static uint8_t IN_SECTION(.ccm.bss) oven_driver_power_level;
|
||||
static float IN_SECTION(.ccm.bss) target_temp;
|
||||
static uint64_t IN_SECTION(.ccm.bss) timestamp_last_run;
|
||||
|
||||
static void ssr_safety_en(bool enable)
|
||||
{
|
||||
if (get_pcb_hardware_version() >= HW_REV_V1_3) {
|
||||
if (enable)
|
||||
SSR_SAFETY_EN_PORT->ODR |= (1<<SSR_SAFETY_EN_PIN);
|
||||
else
|
||||
SSR_SAFETY_EN_PORT->ODR &= ~(1<<SSR_SAFETY_EN_PIN);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void oven_driver_init(void)
|
||||
{
|
||||
@ -55,7 +69,16 @@ void oven_driver_init(void)
|
||||
oven_pid_aborted = false;
|
||||
oven_pid_running = false;
|
||||
|
||||
if (get_pcb_hardware_version() >= HW_REV_V1_3) {
|
||||
/* Init the safety SSR enable signal */
|
||||
rcc_manager_enable_clock(&RCC->AHB1ENR, BITMASK_TO_BITNO(SSR_SAFETY_EN_PORT_RCC_MASK));
|
||||
SSR_SAFETY_EN_PORT->MODER &= MODER_DELETE(SSR_SAFETY_EN_PIN);
|
||||
SSR_SAFETY_EN_PORT->MODER |= OUTPUT(SSR_SAFETY_EN_PIN);
|
||||
ssr_safety_en(false);
|
||||
}
|
||||
|
||||
oven_driver_set_power(0U);
|
||||
oven_driver_apply_power_level();
|
||||
}
|
||||
|
||||
void oven_driver_set_power(uint8_t power)
|
||||
@ -88,13 +111,19 @@ void oven_pid_init(struct pid_controller *controller_to_copy)
|
||||
oven_pid_running = true;
|
||||
oven_pid_aborted = false;
|
||||
safety_controller_report_timing(ERR_TIMING_PID);
|
||||
timestamp_last_run = systick_get_global_tick();
|
||||
ssr_safety_en(true);
|
||||
}
|
||||
|
||||
void oven_pid_handle(float target_temp)
|
||||
void oven_pid_set_target_temperature(float temp)
|
||||
{
|
||||
target_temp = temp;
|
||||
}
|
||||
|
||||
void oven_pid_handle(void)
|
||||
{
|
||||
float pid_out;
|
||||
float current_temp;
|
||||
static uint64_t timestamp_last_run;
|
||||
|
||||
if (oven_pid_running && !oven_pid_aborted) {
|
||||
if (systick_ticks_have_passed(timestamp_last_run, (uint64_t)(oven_pid.sample_period * 1000))) {
|
||||
@ -116,6 +145,13 @@ void oven_pid_stop(void)
|
||||
oven_pid_running = false;
|
||||
oven_driver_set_power(0U);
|
||||
safety_controller_enable_timing_mon(ERR_TIMING_PID, false);
|
||||
ssr_safety_en(false);
|
||||
}
|
||||
|
||||
void oven_pid_abort(void)
|
||||
{
|
||||
oven_pid_aborted = true;
|
||||
oven_pid_stop();
|
||||
}
|
||||
|
||||
enum oven_pid_status oven_pid_get_status(void)
|
||||
|
@ -22,7 +22,7 @@
|
||||
#include <string.h>
|
||||
|
||||
void pid_init(struct pid_controller *pid, float k_deriv, float k_int, float k_p, float output_sat_min,
|
||||
float output_sat_max, float integral_max, float sample_period)
|
||||
float output_sat_max, float integral_max, float kd_tau, float sample_period)
|
||||
{
|
||||
if (!pid)
|
||||
return;
|
||||
@ -32,7 +32,8 @@ void pid_init(struct pid_controller *pid, float k_deriv, float k_int, float k_p,
|
||||
pid->k_int = k_int;
|
||||
pid->k_deriv = k_deriv;
|
||||
pid->k_int_t = pid->k_int * pid->sample_period * 0.5f;
|
||||
pid->k_deriv_t = pid->k_deriv * 2.0f / pid->sample_period;
|
||||
pid->k_deriv_t = pid->k_deriv * 2.0f / (pid->sample_period + 2.0f * kd_tau);
|
||||
pid->k_inv_deriv_t = (2.0f * kd_tau - sample_period) / (2.0f * kd_tau + sample_period);
|
||||
pid->output_sat_max = output_sat_max;
|
||||
pid->output_sat_min = output_sat_min;
|
||||
pid->integral_max = integral_max;
|
||||
@ -61,11 +62,11 @@ static void calculate_integral(struct pid_controller *pid, float deviation)
|
||||
{
|
||||
pid->integral = pid->integral + pid->k_int_t * (deviation + pid->last_in);
|
||||
|
||||
/* Saturate integral term to spoecified maximum */
|
||||
/* Saturate integral term to specified maximum */
|
||||
if (pid->integral > pid->integral_max)
|
||||
pid->integral = pid->integral_max;
|
||||
else if (pid->integral < -pid->integral_max)
|
||||
pid->integral = -pid->integral_max;
|
||||
else if (pid->integral < 0.0f)
|
||||
pid->integral = 0.0f;
|
||||
}
|
||||
|
||||
float pid_sample(struct pid_controller *pid, float deviation)
|
||||
@ -73,17 +74,18 @@ float pid_sample(struct pid_controller *pid, float deviation)
|
||||
float output;
|
||||
|
||||
if (!pid)
|
||||
return 0.0;
|
||||
return 0.0f;
|
||||
|
||||
output = deviation * pid->k_p;
|
||||
|
||||
if (!(deviation > 0.0f && pid->control_output > pid->output_sat_max - 0.5) &&
|
||||
!(deviation < 0.0f && pid->control_output < pid->output_sat_min + 0.5)) {
|
||||
/* PID runaway compensation */
|
||||
if (!(deviation > 0.0f && pid->control_output > pid->output_sat_max - 0.5f && pid->integral > 0) &&
|
||||
!(deviation < 0.0f && pid->control_output < pid->output_sat_min + 0.5f && pid->integral < 0)) {
|
||||
calculate_integral(pid, deviation);
|
||||
}
|
||||
|
||||
/* Calculate derivative part */
|
||||
pid->derivate = pid->k_deriv_t * (deviation - pid->last_in) - pid->derivate;
|
||||
pid->derivate = pid->k_deriv_t * (deviation - pid->last_in) - pid->k_inv_deriv_t * pid->derivate;
|
||||
|
||||
output += pid->derivate;
|
||||
output += pid->integral;
|
||||
|
@ -1,290 +0,0 @@
|
||||
/* Reflow Oven Controller
|
||||
*
|
||||
* Copyright (C) 2020 Mario Hüttel <mario.huettel@gmx.net>
|
||||
*
|
||||
* This file is part of the Reflow Oven Controller Project.
|
||||
*
|
||||
* The reflow oven controller is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* The Reflow Oven Control Firmware 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 the reflow oven controller project.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <reflow-controller/reflow-menu.h>
|
||||
#include <reflow-controller/ui/menu.h>
|
||||
#include <reflow-controller/ui/lcd.h>
|
||||
#include <reflow-controller/rotary-encoder.h>
|
||||
#include <reflow-controller/systick.h>
|
||||
#include <reflow-controller/adc-meas.h>
|
||||
#include <reflow-controller/safety/safety-controller.h>
|
||||
#include <reflow-controller/temp-converter.h>
|
||||
#include <helper-macros/helper-macros.h>
|
||||
#include <stm-periph/unique-id.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
static char IN_SECTION(.ccm.bss) display_buffer[4][21] = {0};
|
||||
static struct lcd_menu IN_SECTION(.ccm.bss) reflow_menu;
|
||||
#define reflow_menu_ptr (&reflow_menu)
|
||||
|
||||
static void update_display_buffer(uint8_t row, const char *data)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (row > 4)
|
||||
return;
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
for (i = 0; data[i] && i < LCD_CHAR_WIDTH; i++) {
|
||||
display_buffer[row][i] = data[i];
|
||||
}
|
||||
|
||||
display_buffer[row][i] = 0;
|
||||
}
|
||||
|
||||
static void reflow_menu_monitor(struct lcd_menu *menu, enum menu_entry_func_entry entry_type, void *parent)
|
||||
{
|
||||
static void *my_parent;
|
||||
static uint64_t my_timestamp = 0;
|
||||
char line[17];
|
||||
float tmp;
|
||||
int res;
|
||||
const char *prefix;
|
||||
|
||||
if (entry_type == MENU_ENTRY_FIRST_ENTER) {
|
||||
my_parent = parent;
|
||||
menu_display_clear(menu);
|
||||
}
|
||||
|
||||
if (systick_ticks_have_passed(my_timestamp, 250)) {
|
||||
my_timestamp = systick_get_global_tick();
|
||||
adc_pt1000_get_current_resistance(&tmp);
|
||||
snprintf(line, sizeof(line), "Res: %.1f " LCD_OHM_SYMBOL_STRING, tmp);
|
||||
menu->update_display(0, line);
|
||||
|
||||
res = temp_converter_convert_resistance_to_temp(tmp, &tmp);
|
||||
switch (res) {
|
||||
case -1:
|
||||
prefix = "<";
|
||||
break;
|
||||
case 1:
|
||||
prefix = ">";
|
||||
break;
|
||||
default:
|
||||
prefix = "";
|
||||
break;
|
||||
}
|
||||
|
||||
snprintf(line, sizeof(line), "Temp: %s%.1f " LCD_DEGREE_SYMBOL_STRING "C", prefix, tmp);
|
||||
menu->update_display(1, line);
|
||||
|
||||
(void)safety_controller_get_analog_mon_value(ERR_AMON_UC_TEMP, &tmp);
|
||||
snprintf(line, sizeof(line), "Tj: %.1f " LCD_DEGREE_SYMBOL_STRING "C", tmp);
|
||||
menu->update_display(2, line);
|
||||
|
||||
(void)safety_controller_get_analog_mon_value(ERR_AMON_VREF, &tmp);
|
||||
snprintf(line, sizeof(line), "Vref: %.1f mV", tmp);
|
||||
menu->update_display(3, line);
|
||||
}
|
||||
|
||||
if (menu->inputs.push_button == BUTTON_SHORT_RELEASED || menu->inputs.push_button == BUTTON_LONG) {
|
||||
menu_entry_dropback(menu, my_parent);
|
||||
}
|
||||
}
|
||||
|
||||
static void reflow_menu_about(struct lcd_menu *menu, enum menu_entry_func_entry entry_type, void *parent)
|
||||
{
|
||||
static void *my_parent;
|
||||
static bool button_ready;
|
||||
static int page = 0;
|
||||
static uint32_t uptime_secs;
|
||||
uint32_t new_uptime_secs;
|
||||
uint32_t uptime_mins;
|
||||
uint32_t uptime_hours;
|
||||
uint32_t uptime_days;
|
||||
int16_t rot_delta;
|
||||
uint32_t ser1, ser2, ser3;
|
||||
enum button_state push_button;
|
||||
|
||||
if (entry_type == MENU_ENTRY_FIRST_ENTER) {
|
||||
uptime_secs = 0ULL;
|
||||
page = 0;
|
||||
my_parent = parent;
|
||||
button_ready = false;
|
||||
menu_display_clear(menu);
|
||||
menu_ack_rotary_delta(menu);
|
||||
}
|
||||
|
||||
rot_delta = menu_get_rotary_delta(menu);
|
||||
if (rot_delta >= 4) {
|
||||
menu_ack_rotary_delta(menu);
|
||||
if (page < 4) {
|
||||
page++;
|
||||
menu_display_clear(menu);
|
||||
}
|
||||
} else if (rot_delta <= -4) {
|
||||
menu_ack_rotary_delta(menu);
|
||||
if (page > 0) {
|
||||
page--;
|
||||
menu_display_clear(menu);
|
||||
}
|
||||
}
|
||||
|
||||
switch (page) {
|
||||
case 0:
|
||||
menu_lcd_output(menu, 0, LCD_SHIMATTA_STRING " Shimatta");
|
||||
menu_lcd_output(menu, 1, "Oven Controller");
|
||||
menu_lcd_output(menu, 2, "(c) Mario H\xF5ttel");
|
||||
menu_lcd_output(menu, 3, "Page 1/5");
|
||||
break;
|
||||
case 1:
|
||||
menu_lcd_output(menu, 0, "Version Number:");
|
||||
menu_lcd_outputf(menu, 1, "%.*s", LCD_CHAR_WIDTH, xstr(GIT_VER));
|
||||
if (strlen(xstr(GIT_VER)) > LCD_CHAR_WIDTH) {
|
||||
menu_lcd_outputf(menu, 2, "%s", &xstr(GIT_VER)[LCD_CHAR_WIDTH]);
|
||||
}
|
||||
#ifdef DEBUGBUILD
|
||||
menu_lcd_output(menu, 3, "Page 2/5 [DEBUG]");
|
||||
#else
|
||||
menu_lcd_output(menu, 3, "Page 2/5");
|
||||
#endif
|
||||
break;
|
||||
case 2:
|
||||
menu_lcd_output(menu, 0, "Compile Info");
|
||||
menu_lcd_output(menu, 1, __DATE__);
|
||||
menu_lcd_output(menu, 2, __TIME__);
|
||||
menu_lcd_output(menu, 3, "Page 3/5");
|
||||
break;
|
||||
case 3:
|
||||
unique_id_get(&ser1, &ser2, &ser3);
|
||||
|
||||
menu_lcd_outputf(menu, 0, "Serial: %08X", ser1);
|
||||
menu_lcd_outputf(menu, 1, " %08X", ser2);
|
||||
menu_lcd_outputf(menu, 2, " %08X", ser3);
|
||||
menu_lcd_output(menu, 3, "Page 4/5");
|
||||
break;
|
||||
case 4:
|
||||
systick_get_uptime_from_tick(&uptime_days, &uptime_hours, &uptime_mins, &new_uptime_secs);
|
||||
if (new_uptime_secs != uptime_secs) {
|
||||
uptime_secs = new_uptime_secs;
|
||||
menu_lcd_output(menu, 0, "Uptime:");
|
||||
menu_lcd_outputf(menu, 1, "%lu day%s %02lu:%02lu:%02lu",
|
||||
uptime_days, (uptime_days == 1 ? "" : "s"), uptime_hours, uptime_mins, uptime_secs);
|
||||
menu_lcd_output(menu, 3, "Page 5/5");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
page = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
push_button = menu_get_button_state(menu);
|
||||
|
||||
if (push_button == BUTTON_IDLE)
|
||||
button_ready = true;
|
||||
|
||||
if (button_ready &&
|
||||
(push_button == BUTTON_SHORT_RELEASED || push_button == BUTTON_LONG)) {
|
||||
menu_entry_dropback(menu, my_parent);
|
||||
}
|
||||
}
|
||||
|
||||
static void reflow_menu_root_entry(struct lcd_menu *menu, enum menu_entry_func_entry entry_type, void *parent)
|
||||
{
|
||||
(void)parent;
|
||||
static struct menu_list list;
|
||||
static bool button_valid;
|
||||
static bool menu_changed = true;
|
||||
static const char * const root_entry_names[] = {
|
||||
"About",
|
||||
"Monitoring",
|
||||
NULL
|
||||
};
|
||||
static const menu_func_t root_entry_funcs[] = {
|
||||
reflow_menu_about,
|
||||
reflow_menu_monitor
|
||||
};
|
||||
enum button_state push_button;
|
||||
int16_t rot_delta;
|
||||
|
||||
if (entry_type != MENU_ENTRY_CONTINUE) {
|
||||
menu_changed = true;
|
||||
menu_display_clear(menu);
|
||||
update_display_buffer(0, "Main Menu");
|
||||
menu_ack_rotary_delta(menu);
|
||||
if (entry_type == MENU_ENTRY_FIRST_ENTER) {
|
||||
button_valid = false;
|
||||
list.entry_names = root_entry_names;
|
||||
list.submenu_list = root_entry_funcs;
|
||||
list.update_display = menu->update_display;
|
||||
list.currently_selected = 0;
|
||||
menu_list_compute_count(&list);
|
||||
}
|
||||
}
|
||||
|
||||
push_button = menu_get_button_state(menu);
|
||||
rot_delta = menu_get_rotary_delta(menu);
|
||||
|
||||
if (push_button == BUTTON_IDLE) {
|
||||
button_valid = true;
|
||||
} else if (button_valid && push_button == BUTTON_SHORT_RELEASED) {
|
||||
/* Enter currently selected menu_entry */
|
||||
menu_list_enter_selected_entry(&list, menu);
|
||||
}
|
||||
|
||||
if (rot_delta >= 4) {
|
||||
menu_list_scroll_down(&list);
|
||||
menu_ack_rotary_delta(menu);
|
||||
menu_changed = true;
|
||||
} else if (rot_delta <= -4) {
|
||||
menu_list_scroll_up(&list);
|
||||
menu_ack_rotary_delta(menu);
|
||||
menu_changed = true;
|
||||
}
|
||||
|
||||
if (menu_changed)
|
||||
menu_list_display(&list, 1, 3);
|
||||
}
|
||||
|
||||
int reflow_menu_handle()
|
||||
{
|
||||
int32_t rot_delta;
|
||||
enum button_state button;
|
||||
static enum lcd_fsm_ret lcd_ret = LCD_FSM_NOP;
|
||||
|
||||
rot_delta = rotary_encoder_get_change_val();
|
||||
button = button_read_event();
|
||||
|
||||
menu_handle(reflow_menu_ptr, (int16_t)rot_delta, button);
|
||||
|
||||
if (lcd_ret == LCD_FSM_CALL_AGAIN || lcd_tick_100us >= 5) {
|
||||
lcd_ret = lcd_fsm_write_buffer(display_buffer);
|
||||
lcd_tick_100us = 0UL;
|
||||
}
|
||||
|
||||
if (lcd_ret == LCD_FSM_CALL_AGAIN)
|
||||
return 0;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
|
||||
void reflow_menu_init()
|
||||
{
|
||||
rotary_encoder_setup();
|
||||
button_init();
|
||||
lcd_init();
|
||||
|
||||
menu_init(reflow_menu_ptr, reflow_menu_root_entry, update_display_buffer);
|
||||
}
|
@ -19,7 +19,7 @@
|
||||
*/
|
||||
|
||||
#include <reflow-controller/rotary-encoder.h>
|
||||
#include <stm-periph/clock-enable-manager.h>
|
||||
#include <stm-periph/rcc-manager.h>
|
||||
#include <stm-periph/stm32-gpio-macros.h>
|
||||
#include <helper-macros/helper-macros.h>
|
||||
|
||||
|
@ -21,29 +21,42 @@
|
||||
#include <reflow-controller/oven-driver.h>
|
||||
#include <reflow-controller/digio.h>
|
||||
#include <reflow-controller/safety/fault.h>
|
||||
#include <reflow-controller/safety/safety-memory.h>
|
||||
#include <helper-macros/helper-macros.h>
|
||||
|
||||
void HardFault_Handler(void)
|
||||
{
|
||||
/* This is a non recoverable fault. Hang here */
|
||||
/* This is a non recoverable fault. Stop the oven */
|
||||
|
||||
oven_driver_set_power(0);
|
||||
oven_driver_apply_power_level();
|
||||
|
||||
/* Set the error led */
|
||||
led_set(0, 1);
|
||||
|
||||
while (1);
|
||||
/* Try the real panic mode */
|
||||
panic_mode();
|
||||
}
|
||||
|
||||
/* Overwrite default handler. Go to panic mode */
|
||||
void __int_default_handler(void)
|
||||
{
|
||||
panic_mode();
|
||||
}
|
||||
|
||||
void panic_mode(void)
|
||||
{
|
||||
/* Panic mode is esentially the same as a hardfault,
|
||||
* but it can be expected, that more functionality is still usable
|
||||
*/
|
||||
/* This variable is static, because I don't want it to be on the stack */
|
||||
static struct safety_memory_boot_status IN_SECTION(.ccm.bss) boot_status;
|
||||
|
||||
oven_driver_set_power(0);
|
||||
oven_driver_apply_power_level();
|
||||
|
||||
/* TODO: implement panic mode */
|
||||
if (!safety_memory_get_boot_status(&boot_status)) {
|
||||
boot_status.reset_from_panic = 0xFFFFFFFF;
|
||||
(void)safety_memory_set_boot_status(&boot_status);
|
||||
}
|
||||
|
||||
/* Let the watchdog do the rest */
|
||||
while (1);
|
||||
}
|
||||
|
@ -26,42 +26,99 @@
|
||||
#include <reflow-controller/safety/safety-adc.h>
|
||||
#include <reflow-controller/periph-config/safety-adc-hwcfg.h>
|
||||
#include <helper-macros/helper-macros.h>
|
||||
#include <stm-periph/clock-enable-manager.h>
|
||||
#include <stm-periph/rcc-manager.h>
|
||||
#include <reflow-controller/hw-version-detect.h>
|
||||
|
||||
void safety_adc_init()
|
||||
static const uint8_t safety_adc_channels[SAFETY_ADC_NUM_OF_CHANNELS] = {SAFETY_ADC_CHANNELS};
|
||||
static volatile uint8_t safety_adc_conversion_complete;
|
||||
static volatile uint8_t safety_adc_triggered;
|
||||
static volatile uint16_t safety_adc_conversions[SAFETY_ADC_NUM_OF_CHANNELS];
|
||||
|
||||
void safety_adc_init(void)
|
||||
{
|
||||
enum hw_revision hw_rev;
|
||||
int i;
|
||||
|
||||
hw_rev = get_pcb_hardware_version();
|
||||
|
||||
rcc_manager_enable_clock(&RCC->APB2ENR, BITMASK_TO_BITNO(SAFETY_ADC_ADC_RCC_MASK));
|
||||
rcc_manager_enable_clock(&RCC->AHB1ENR, BITMASK_TO_BITNO(RCC_AHB1ENR_DMA2EN));
|
||||
|
||||
if (hw_rev != HW_REV_V1_2) {
|
||||
rcc_manager_enable_clock(&RCC->AHB1ENR, BITMASK_TO_BITNO(SAFETY_ADC_SUPPLY_VOLTAGE_MONITOR_PORT_RCC_MASK));
|
||||
SAFETY_ADC_SUPPLY_VOLTAGE_MONITOR_PORT->MODER &= MODER_DELETE(SAFETY_ADC_SUPPLY_VOLTAGE_MONITOR_PIN);
|
||||
SAFETY_ADC_SUPPLY_VOLTAGE_MONITOR_PORT->MODER |= ANALOG(SAFETY_ADC_SUPPLY_VOLTAGE_MONITOR_PIN);
|
||||
}
|
||||
|
||||
/* Enable temperature and VREFINT measurement */
|
||||
ADC->CCR |= ADC_CCR_TSVREFE;
|
||||
|
||||
/* Set sample time for channels 16 and 17 */
|
||||
SAFETY_ADC_ADC_PERIPHERAL->SMPR1 |= ADC_SMPR1_SMP17 | ADC_SMPR1_SMP16;
|
||||
/* Set sample time for channels 16 and 17 and 15 */
|
||||
SAFETY_ADC_ADC_PERIPHERAL->SMPR1 |= ADC_SMPR1_SMP17 | ADC_SMPR1_SMP16 | ADC_SMPR1_SMP15;
|
||||
|
||||
/* Standard sequence. One measurement */
|
||||
SAFETY_ADC_ADC_PERIPHERAL->SQR1 = 0UL;
|
||||
/* Standard sequence. Measure all channels in one sequence */
|
||||
SAFETY_ADC_ADC_PERIPHERAL->SQR1 = (SAFETY_ADC_NUM_OF_CHANNELS - 1) << 20 ;
|
||||
SAFETY_ADC_ADC_PERIPHERAL->SQR2 = 0UL;
|
||||
SAFETY_ADC_ADC_PERIPHERAL->SQR3 = 0UL;
|
||||
|
||||
for (i = 0; i < SAFETY_ADC_NUM_OF_CHANNELS; i++) {
|
||||
switch (i) {
|
||||
case 0 ... 5:
|
||||
SAFETY_ADC_ADC_PERIPHERAL->SQR3 |= safety_adc_channels[i] << (i * 5);
|
||||
break;
|
||||
case 6 ... 11:
|
||||
SAFETY_ADC_ADC_PERIPHERAL->SQR2 |= safety_adc_channels[i] << ((i-6) * 5);
|
||||
break;
|
||||
case 12 ... 15:
|
||||
SAFETY_ADC_ADC_PERIPHERAL->SQR1 |= safety_adc_channels[i] << ((i-12) * 5);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
safety_adc_conversion_complete = 0;
|
||||
safety_adc_triggered = 0;
|
||||
|
||||
/* Setup the DMA to move the data */
|
||||
DMA2_Stream4->PAR = (uint32_t)&SAFETY_ADC_ADC_PERIPHERAL->DR;
|
||||
DMA2_Stream4->M0AR = (uint32_t)safety_adc_conversions;
|
||||
DMA2_Stream4->NDTR = SAFETY_ADC_NUM_OF_CHANNELS;
|
||||
DMA2_Stream4->CR = DMA_SxCR_PL_0 | DMA_SxCR_MSIZE_0 | DMA_SxCR_PSIZE_0 | DMA_SxCR_MINC | DMA_SxCR_CIRC | DMA_SxCR_TCIE | DMA_SxCR_EN;
|
||||
NVIC_EnableIRQ(DMA2_Stream4_IRQn);
|
||||
|
||||
/* Enable ADC */
|
||||
SAFETY_ADC_ADC_PERIPHERAL->CR2 = ADC_CR2_ADON | ADC_CR2_DMA | ADC_CR2_DDS;
|
||||
}
|
||||
|
||||
|
||||
void safety_adc_deinit()
|
||||
void safety_adc_deinit(void)
|
||||
{
|
||||
SAFETY_ADC_ADC_PERIPHERAL->CR1 = 0UL;
|
||||
SAFETY_ADC_ADC_PERIPHERAL->CR2 = 0UL;
|
||||
SAFETY_ADC_ADC_PERIPHERAL->SMPR1 = 0UL;
|
||||
rcc_manager_enable_clock(&RCC->APB1ENR, BITMASK_TO_BITNO(RCC_APB2ENR_ADC2EN));
|
||||
rcc_manager_disable_clock(&RCC->APB1ENR, BITMASK_TO_BITNO(RCC_APB2ENR_ADC2EN));
|
||||
DMA2_Stream4->CR = 0;
|
||||
rcc_manager_disable_clock(&RCC->AHB1ENR, BITMASK_TO_BITNO(RCC_AHB1ENR_DMA2EN));
|
||||
}
|
||||
|
||||
float safety_adc_convert_channel(enum safety_adc_meas_channel channel, uint16_t analog_value)
|
||||
{
|
||||
float converted_val;
|
||||
enum hw_revision hw_rev;
|
||||
|
||||
switch (channel) {
|
||||
case SAFETY_ADC_MEAS_TEMP:
|
||||
converted_val = (((float)analog_value / 4095.0f * 2500.0f - SAFETY_ADC_TEMP_NOM_MV) /
|
||||
converted_val = (((float)analog_value / 4096.0f * 2500.0f - SAFETY_ADC_TEMP_NOM_MV) /
|
||||
SAFETY_ADC_TEMP_MV_SLOPE) + SAFETY_ADC_TEMP_NOM;
|
||||
break;
|
||||
case SAFETY_ADC_MEAS_VREF:
|
||||
converted_val = (SAFETY_ADC_INT_REF_MV * 4095.0f) / (float)analog_value;
|
||||
converted_val = (SAFETY_ADC_INT_REF_MV * 4096.0f) / (float)analog_value;
|
||||
break;
|
||||
case SAFETY_ADC_MEAS_SUPPLY:
|
||||
hw_rev = get_pcb_hardware_version();
|
||||
if (hw_rev >= HW_REV_V1_3)
|
||||
converted_val = ((float)analog_value) / 4096.0f * 2500.0f * 2.0f;
|
||||
else
|
||||
converted_val = 3300.0f;
|
||||
break;
|
||||
default:
|
||||
/* Generate NaN value as default return */
|
||||
@ -72,42 +129,44 @@ float safety_adc_convert_channel(enum safety_adc_meas_channel channel, uint16_t
|
||||
return converted_val;
|
||||
}
|
||||
|
||||
int safety_adc_poll_result(uint16_t *adc_result)
|
||||
int safety_adc_poll_result(void)
|
||||
{
|
||||
int ret = 0;
|
||||
if (safety_adc_triggered)
|
||||
return 0;
|
||||
|
||||
if (!adc_result)
|
||||
return -1000;
|
||||
|
||||
if (!(SAFETY_ADC_ADC_PERIPHERAL->CR2 & ADC_CR2_ADON)) {
|
||||
if (safety_adc_conversion_complete)
|
||||
return 1;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (SAFETY_ADC_ADC_PERIPHERAL->SR & ADC_SR_EOC) {
|
||||
*adc_result = (uint16_t)SAFETY_ADC_ADC_PERIPHERAL->DR;
|
||||
SAFETY_ADC_ADC_PERIPHERAL->CR2 &= ~ADC_CR2_ADON;
|
||||
ret = 1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void safety_adc_trigger_meas(enum safety_adc_meas_channel measurement)
|
||||
const uint16_t *safety_adc_get_values(void)
|
||||
{
|
||||
switch (measurement) {
|
||||
case SAFETY_ADC_MEAS_TEMP:
|
||||
SAFETY_ADC_ADC_PERIPHERAL->SQR3 = TEMP_CHANNEL_NUM;
|
||||
break;
|
||||
case SAFETY_ADC_MEAS_VREF:
|
||||
SAFETY_ADC_ADC_PERIPHERAL->SQR3 = INT_REF_CHANNEL_NUM;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
safety_adc_conversion_complete = 0;
|
||||
return (const uint16_t *)safety_adc_conversions;
|
||||
}
|
||||
|
||||
void safety_adc_trigger_meas(void)
|
||||
{
|
||||
safety_adc_conversion_complete = 0;
|
||||
|
||||
SAFETY_ADC_ADC_PERIPHERAL->CR1 |= ADC_CR1_SCAN;
|
||||
SAFETY_ADC_ADC_PERIPHERAL->CR2 |= ADC_CR2_ADON;
|
||||
SAFETY_ADC_ADC_PERIPHERAL->CR2 |= ADC_CR2_SWSTART;
|
||||
safety_adc_triggered = 1;
|
||||
}
|
||||
|
||||
void DMA2_Stream4_IRQHandler()
|
||||
{
|
||||
uint32_t hisr;
|
||||
|
||||
hisr = DMA2->HISR & 0x3F;
|
||||
DMA2->HIFCR = hisr;
|
||||
|
||||
if (hisr & DMA_HISR_TCIF4) {
|
||||
safety_adc_triggered = 0;
|
||||
safety_adc_conversion_complete = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/** @} */
|
||||
|
File diff suppressed because it is too large
Load Diff
635
stm-firmware/safety/safety-memory.c
Normal file
635
stm-firmware/safety/safety-memory.c
Normal file
@ -0,0 +1,635 @@
|
||||
/* Reflow Oven Controller
|
||||
*
|
||||
* Copyright (C) 2020 Mario Hüttel <mario.huettel@gmx.net>
|
||||
*
|
||||
* This file is part of the Reflow Oven Controller Project.
|
||||
*
|
||||
* The reflow oven controller is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* The Reflow Oven Control Firmware 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 the reflow oven controller project.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <reflow-controller/safety/safety-memory.h>
|
||||
#include <helper-macros/helper-macros.h>
|
||||
#include <stm-periph/crc-unit.h>
|
||||
#include <stm-periph/backup-ram.h>
|
||||
#include <base64-lib/base64-lib.h>
|
||||
|
||||
static int word_to_error_memory_entry(uint32_t entry_data, struct error_memory_entry *out)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if (!out)
|
||||
return -1002;
|
||||
|
||||
if (entry_data == SAFETY_MEMORY_ERR_ENTRY_NOP) {
|
||||
out->flag_num = 0U;
|
||||
out->type = SAFETY_MEMORY_ERR_ENTRY_NOP;
|
||||
out->counter = 0U;
|
||||
} else if ((entry_data & 0xFFU) == 0x51U) {
|
||||
out->flag_num = (uint8_t)((entry_data >> 8U) & 0xFFU);
|
||||
out->type = SAFETY_MEMORY_ERR_ENTRY_FLAG;
|
||||
out->counter = (uint16_t)((entry_data >> 16U) & 0xFFFF);
|
||||
} else {
|
||||
/* Invalid entry */
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static uint32_t error_memory_entry_to_word(const struct error_memory_entry *entry)
|
||||
{
|
||||
uint32_t word = 0;
|
||||
|
||||
switch (entry->type) {
|
||||
case SAFETY_MEMORY_ERR_ENTRY_NOP:
|
||||
word = SAFETY_MEMORY_NOP_ENTRY;
|
||||
break;
|
||||
case SAFETY_MEMORY_ERR_ENTRY_FLAG:
|
||||
word = 0x51UL | ((uint32_t)entry->flag_num << 8U) |
|
||||
((uint32_t)entry->counter << 16U);
|
||||
break;
|
||||
}
|
||||
|
||||
return word;
|
||||
}
|
||||
|
||||
static enum safety_memory_state safety_memory_get_header(struct safety_memory_header *header)
|
||||
{
|
||||
|
||||
int res;
|
||||
enum safety_memory_state ret;
|
||||
|
||||
if (!header)
|
||||
return SAFETY_MEMORY_INIT_CORRUPTED;
|
||||
|
||||
res = backup_ram_get_data(0UL, (uint32_t *)header, wordsize_of(struct safety_memory_header));
|
||||
if (res)
|
||||
return SAFETY_MEMORY_INIT_CORRUPTED;
|
||||
|
||||
/* Check magic */
|
||||
if (header->magic != SAFETY_MEMORY_MAGIC) {
|
||||
/* Magic invalid */
|
||||
ret = SAFETY_MEMORY_INIT_FRESH;
|
||||
goto return_val;
|
||||
}
|
||||
|
||||
/* Check the header crc */
|
||||
crc_unit_reset();
|
||||
crc_unit_input_array((uint32_t *)header, wordsize_of(struct safety_memory_header));
|
||||
if (crc_unit_get_crc() != 0UL) {
|
||||
ret = SAFETY_MEMORY_INIT_CORRUPTED;
|
||||
goto return_val;
|
||||
}
|
||||
|
||||
res = 0;
|
||||
if (header->boot_status_offset < wordsize_of(struct safety_memory_header))
|
||||
res++;
|
||||
if (header->config_overrides_offset < header->boot_status_offset + wordsize_of(struct safety_memory_boot_status))
|
||||
res++;
|
||||
if (header->config_overrides_len > SAFETY_MEMORY_CONFIG_OVERRIDE_COUNT)
|
||||
res++;
|
||||
if (header->err_memory_offset < header->config_overrides_offset + header->config_overrides_len)
|
||||
res++;
|
||||
if (header->err_memory_end >= backup_ram_get_size_in_words() || header->err_memory_end < header->err_memory_offset)
|
||||
res++;
|
||||
|
||||
if (res) {
|
||||
/* Error detected: Write new header */
|
||||
ret = SAFETY_MEMORY_INIT_CORRUPTED;
|
||||
} else {
|
||||
ret = SAFETY_MEMORY_INIT_VALID_MEMORY;
|
||||
}
|
||||
|
||||
return_val:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void safety_memory_write_and_patch_header(struct safety_memory_header *header)
|
||||
{
|
||||
/* Patch the CRC */
|
||||
crc_unit_reset();
|
||||
crc_unit_input_array((uint32_t *)header, wordsize_of(struct safety_memory_header) - 1U);
|
||||
header->crc = crc_unit_get_crc();
|
||||
|
||||
/* Write to memory */
|
||||
backup_ram_write_data(0UL, (uint32_t *)header, wordsize_of(*header));
|
||||
}
|
||||
|
||||
static void safety_memory_write_new_header(void)
|
||||
{
|
||||
struct safety_memory_header header;
|
||||
|
||||
header.boot_status_offset = wordsize_of(struct safety_memory_header);
|
||||
header.config_overrides_len = SAFETY_MEMORY_CONFIG_OVERRIDE_COUNT;
|
||||
header.config_overrides_offset = header.boot_status_offset + wordsize_of(struct safety_memory_boot_status);
|
||||
header.err_memory_offset = header.config_overrides_offset + SAFETY_MEMORY_CONFIG_OVERRIDE_COUNT;
|
||||
header.err_memory_end = header.err_memory_offset;
|
||||
header.magic = SAFETY_MEMORY_MAGIC;
|
||||
|
||||
backup_ram_wipe();
|
||||
safety_memory_write_and_patch_header(&header);
|
||||
}
|
||||
|
||||
static int safety_memory_check_crc()
|
||||
{
|
||||
struct safety_memory_header header;
|
||||
enum safety_memory_state state = safety_memory_get_header(&header);
|
||||
uint32_t crc_offset;
|
||||
uint32_t data;
|
||||
uint32_t addr;
|
||||
int res;
|
||||
|
||||
if (state != SAFETY_MEMORY_INIT_VALID_MEMORY)
|
||||
return -1;
|
||||
|
||||
crc_offset = header.err_memory_end;
|
||||
|
||||
crc_unit_reset();
|
||||
|
||||
for (addr = 0; addr < crc_offset; addr++) {
|
||||
res = backup_ram_get_data(addr, &data, 1UL);
|
||||
if (res)
|
||||
return -2000;
|
||||
crc_unit_input(data);
|
||||
}
|
||||
|
||||
res = backup_ram_get_data(crc_offset, &data, 1UL);
|
||||
if (res)
|
||||
return -2001;
|
||||
|
||||
if (crc_unit_get_crc() != data)
|
||||
return -3000;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int safety_memory_gen_crc()
|
||||
{
|
||||
struct safety_memory_header header;
|
||||
uint32_t word_addr;
|
||||
uint32_t data;
|
||||
int res;
|
||||
|
||||
if (safety_memory_get_header(&header) != SAFETY_MEMORY_INIT_VALID_MEMORY)
|
||||
return -1;
|
||||
|
||||
crc_unit_reset();
|
||||
for (word_addr = 0; word_addr < header.err_memory_end; word_addr++) {
|
||||
res = backup_ram_get_data(word_addr, &data, 1);
|
||||
if (res)
|
||||
return -2;
|
||||
crc_unit_input(data);
|
||||
}
|
||||
|
||||
/* Write CRC */
|
||||
data = crc_unit_get_crc();
|
||||
res = backup_ram_write_data(header.err_memory_end, &data, 1UL);
|
||||
if (res)
|
||||
return -3;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int safety_memory_reinit(enum safety_memory_state *found_state)
|
||||
{
|
||||
struct safety_memory_header header;
|
||||
int res;
|
||||
int ret = -1;
|
||||
|
||||
if (!found_state)
|
||||
return -1001;
|
||||
|
||||
*found_state = safety_memory_get_header(&header);
|
||||
|
||||
switch (*found_state) {
|
||||
case SAFETY_MEMORY_INIT_VALID_MEMORY:
|
||||
/* Valid memory detected. Check CRC and error entries */
|
||||
res = safety_memory_check();
|
||||
if (res)
|
||||
*found_state = SAFETY_MEMORY_INIT_CORRUPTED;
|
||||
break;
|
||||
case SAFETY_MEMORY_INIT_FRESH:
|
||||
break;
|
||||
case SAFETY_MEMORY_INIT_CORRUPTED:
|
||||
break;
|
||||
default:
|
||||
*found_state = SAFETY_MEMORY_INIT_CORRUPTED;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Check if memory header has to be written */
|
||||
if (*found_state != SAFETY_MEMORY_INIT_VALID_MEMORY) {
|
||||
safety_memory_write_new_header();
|
||||
/* If yes, generate new CRC checksum */
|
||||
res = safety_memory_gen_crc();
|
||||
if (res)
|
||||
ret = -100;
|
||||
else
|
||||
ret = 0;
|
||||
} else {
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int safety_memory_init(enum safety_memory_state *found_state)
|
||||
{
|
||||
|
||||
crc_unit_init();
|
||||
backup_ram_init(true);
|
||||
|
||||
return safety_memory_reinit(found_state);
|
||||
}
|
||||
|
||||
int safety_memory_get_boot_status(struct safety_memory_boot_status *status)
|
||||
{
|
||||
struct safety_memory_header header;
|
||||
int res;
|
||||
|
||||
if (!status)
|
||||
return -1001;
|
||||
|
||||
if (safety_memory_get_header(&header) != SAFETY_MEMORY_INIT_VALID_MEMORY) {
|
||||
return -2000;
|
||||
}
|
||||
|
||||
if (safety_memory_check_crc())
|
||||
return -2001;
|
||||
|
||||
res = backup_ram_get_data(header.boot_status_offset, (uint32_t *)status, wordsize_of(*status));
|
||||
if (res)
|
||||
return -3000;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int safety_memory_set_boot_status(const struct safety_memory_boot_status *status)
|
||||
{
|
||||
struct safety_memory_header header;
|
||||
int res;
|
||||
|
||||
if (!status)
|
||||
return -1001;
|
||||
|
||||
if (safety_memory_get_header(&header) != SAFETY_MEMORY_INIT_VALID_MEMORY) {
|
||||
return -2000;
|
||||
}
|
||||
|
||||
if (safety_memory_check_crc())
|
||||
return -2001;
|
||||
|
||||
res = backup_ram_write_data(header.boot_status_offset, (uint32_t *)status, wordsize_of(*status));
|
||||
res |= safety_memory_gen_crc();
|
||||
if (res)
|
||||
return -3000;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int safety_memory_check_error_entries()
|
||||
{
|
||||
struct safety_memory_header header;
|
||||
uint32_t addr;
|
||||
uint32_t data;
|
||||
int ret = 0;
|
||||
int res;
|
||||
|
||||
if (safety_memory_get_header(&header) != SAFETY_MEMORY_INIT_VALID_MEMORY) {
|
||||
return -2000;
|
||||
}
|
||||
|
||||
for (addr = header.err_memory_offset; addr < header.err_memory_end; addr++) {
|
||||
res = backup_ram_get_data(addr, &data, 1UL);
|
||||
if (res)
|
||||
return -100;
|
||||
|
||||
/* Valid flag entry */
|
||||
if ((data & 0xFF) == 0x51)
|
||||
continue;
|
||||
if (data == SAFETY_MEMORY_NOP_ENTRY)
|
||||
continue;
|
||||
|
||||
ret--;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int safety_memory_get_error_entry_count(uint32_t *count)
|
||||
{
|
||||
struct safety_memory_header header;
|
||||
|
||||
if (!count)
|
||||
return -1001;
|
||||
|
||||
if (safety_memory_get_header(&header) != SAFETY_MEMORY_INIT_VALID_MEMORY) {
|
||||
return -2000;
|
||||
}
|
||||
|
||||
*count = header.err_memory_end - header.err_memory_offset;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int safety_memory_check(void)
|
||||
{
|
||||
int res;
|
||||
|
||||
res = safety_memory_check_crc();
|
||||
if (!res) {
|
||||
res |= safety_memory_check_error_entries();
|
||||
}
|
||||
|
||||
return -!!res;
|
||||
}
|
||||
|
||||
int safety_memory_get_error_entry(uint32_t idx, struct error_memory_entry *entry)
|
||||
{
|
||||
struct safety_memory_header header;
|
||||
uint32_t err_mem_count;
|
||||
int ret = -1;
|
||||
int res;
|
||||
uint32_t data;
|
||||
|
||||
if (!entry)
|
||||
return -1001;
|
||||
|
||||
if (safety_memory_get_header(&header) != SAFETY_MEMORY_INIT_VALID_MEMORY) {
|
||||
return -2000;
|
||||
}
|
||||
|
||||
err_mem_count = header.err_memory_end - header.err_memory_offset;
|
||||
if (idx < err_mem_count && err_mem_count > 0) {
|
||||
res = backup_ram_get_data(header.err_memory_offset + idx, &data, 1UL);
|
||||
if (res)
|
||||
goto return_value;
|
||||
|
||||
res = word_to_error_memory_entry(data, entry);
|
||||
if (res)
|
||||
goto return_value;
|
||||
ret = 0;
|
||||
|
||||
} else {
|
||||
/* out of range */
|
||||
ret = -1001;
|
||||
}
|
||||
|
||||
return_value:
|
||||
return ret;
|
||||
}
|
||||
|
||||
int safety_memory_insert_error_entry(struct error_memory_entry *entry)
|
||||
{
|
||||
int res;
|
||||
int ret = -0xFFFF;
|
||||
uint32_t addr;
|
||||
uint32_t data;
|
||||
bool found;
|
||||
uint32_t input_data;
|
||||
struct error_memory_entry current_entry;
|
||||
|
||||
struct safety_memory_header header;
|
||||
|
||||
input_data = error_memory_entry_to_word(entry);
|
||||
|
||||
if (safety_memory_get_header(&header) != SAFETY_MEMORY_INIT_VALID_MEMORY) {
|
||||
return -2000;
|
||||
}
|
||||
|
||||
if (entry->type == SAFETY_MEMORY_ERR_ENTRY_NOP) {
|
||||
/* Append to end */
|
||||
if ((header.err_memory_end + 1U) < backup_ram_get_size_in_words()) {
|
||||
/* Still fits in memory */
|
||||
backup_ram_write_data(header.err_memory_end, &input_data, 1UL);
|
||||
header.err_memory_end++;
|
||||
safety_memory_write_and_patch_header(&header);
|
||||
safety_memory_gen_crc();
|
||||
ret = 0;
|
||||
}
|
||||
} else if (entry->type == SAFETY_MEMORY_ERR_ENTRY_FLAG) {
|
||||
found = false;
|
||||
for (addr = header.err_memory_offset; addr < header.err_memory_end; addr++) {
|
||||
res = backup_ram_get_data(addr, &data, 1UL);
|
||||
if (res) {
|
||||
ret = -1;
|
||||
goto return_value;
|
||||
}
|
||||
res = word_to_error_memory_entry(data, ¤t_entry);
|
||||
if (res) {
|
||||
ret = -2;
|
||||
goto return_value;
|
||||
}
|
||||
|
||||
if (current_entry.type == SAFETY_MEMORY_ERR_ENTRY_FLAG &&
|
||||
current_entry.flag_num == entry->flag_num) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (current_entry.type == SAFETY_MEMORY_ERR_ENTRY_NOP) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
/* No suitable place found in memory. Append */
|
||||
|
||||
if ((addr + 1) < backup_ram_get_size_in_words()) {
|
||||
backup_ram_write_data(addr, &input_data, 1UL);
|
||||
header.err_memory_end++;
|
||||
safety_memory_write_and_patch_header(&header);
|
||||
} else {
|
||||
ret = -3;
|
||||
goto return_value;
|
||||
}
|
||||
} else {
|
||||
if (current_entry.type == SAFETY_MEMORY_ERR_ENTRY_NOP) {
|
||||
backup_ram_write_data(addr, &input_data, 1UL);
|
||||
} else {
|
||||
current_entry.counter += entry->counter;
|
||||
if (current_entry.counter < entry->counter)
|
||||
current_entry.counter = 0xFFFF;
|
||||
data = error_memory_entry_to_word(¤t_entry);
|
||||
backup_ram_write_data(addr, &data, 1UL);
|
||||
}
|
||||
}
|
||||
|
||||
safety_memory_gen_crc();
|
||||
ret = 0;
|
||||
|
||||
} else {
|
||||
ret = -1001;
|
||||
}
|
||||
|
||||
return_value:
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static uint32_t convert_config_override_to_word(const struct config_override *conf_override)
|
||||
{
|
||||
uint32_t data = 0;
|
||||
|
||||
if (conf_override->type == SAFETY_MEMORY_CONFIG_OVERRIDE_WEIGHT) {
|
||||
data |= 0xAA0000A2UL;
|
||||
data |= ((uint32_t)conf_override->entry.weight_override.flag) << 16;
|
||||
data |= ((uint32_t)conf_override->entry.weight_override.weight) << 8;
|
||||
} else if (conf_override->type == SAFETY_MEMORY_CONFIG_OVERRIDE_PERSISTENCE) {
|
||||
data |= 0xBB00008EUL;
|
||||
data |= ((uint32_t)conf_override->entry.persistence_override.flag) << 16;
|
||||
data |= ((uint32_t)(conf_override->entry.persistence_override.persistence ? 1UL : 0UL)) << 8;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
int safety_memory_insert_config_override(struct config_override *config_override)
|
||||
{
|
||||
struct safety_memory_header header;
|
||||
uint32_t idx;
|
||||
uint32_t data;
|
||||
int res;
|
||||
int ret = -3;
|
||||
|
||||
if (safety_memory_get_header(&header) != SAFETY_MEMORY_INIT_VALID_MEMORY) {
|
||||
return -2000;
|
||||
}
|
||||
|
||||
if (header.config_overrides_len == 0)
|
||||
return -1;
|
||||
|
||||
for (idx = 0; idx < header.config_overrides_len; idx++) {
|
||||
res = backup_ram_get_data(header.config_overrides_offset + idx, &data, 1UL);
|
||||
if (res)
|
||||
return -2;
|
||||
if (data == 0UL) {
|
||||
data = convert_config_override_to_word(config_override);
|
||||
res = backup_ram_write_data(header.config_overrides_offset + idx, &data, 1UL);
|
||||
if (res)
|
||||
return -4;
|
||||
res = safety_memory_gen_crc();
|
||||
if (res)
|
||||
return -5;
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int safety_memory_get_config_override_count(uint32_t *count)
|
||||
{
|
||||
struct safety_memory_header header;
|
||||
uint32_t iter;
|
||||
uint32_t valid_count;
|
||||
uint32_t data;
|
||||
int res;
|
||||
|
||||
if (!count)
|
||||
return -1001;
|
||||
|
||||
*count = 0UL;
|
||||
|
||||
if (safety_memory_get_header(&header) != SAFETY_MEMORY_INIT_VALID_MEMORY) {
|
||||
return -2000;
|
||||
}
|
||||
|
||||
if (header.config_overrides_len == 0)
|
||||
return 0;
|
||||
|
||||
valid_count = 0;
|
||||
for (iter = 0; iter < header.config_overrides_len; iter++) {
|
||||
res = backup_ram_get_data(header.config_overrides_offset + iter, &data, 1UL);
|
||||
if (res)
|
||||
return -2;
|
||||
|
||||
if (data != 0)
|
||||
valid_count++;
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
*count = valid_count;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int safety_memory_get_config_override(uint32_t idx, struct config_override *config_override)
|
||||
{
|
||||
struct safety_memory_header header;
|
||||
uint32_t data;
|
||||
int res;
|
||||
|
||||
if (!config_override)
|
||||
return -1002;
|
||||
|
||||
if (safety_memory_get_header(&header) != SAFETY_MEMORY_INIT_VALID_MEMORY) {
|
||||
return -2000;
|
||||
}
|
||||
|
||||
if (idx >= header.config_overrides_len) {
|
||||
return -1001;
|
||||
}
|
||||
|
||||
res = backup_ram_get_data(header.config_overrides_offset + idx, &data, 1UL);
|
||||
if (res) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch (data & 0xFF) {
|
||||
case 0xA2:
|
||||
/* Weight override */
|
||||
config_override->type = SAFETY_MEMORY_CONFIG_OVERRIDE_WEIGHT;
|
||||
config_override->entry.weight_override.flag = (data & 0xFF0000UL) >> 16;
|
||||
config_override->entry.weight_override.weight = (data & 0xFF00UL) >> 8;
|
||||
break;
|
||||
case 0x8E:
|
||||
/* persistence override */
|
||||
config_override->type = SAFETY_MEMORY_CONFIG_OVERRIDE_PERSISTENCE;
|
||||
config_override->entry.persistence_override.flag = (data & 0xFF0000UL) >> 16;
|
||||
config_override->entry.persistence_override.persistence = ((data & 0xFF00UL) >> 8) ? true : false;
|
||||
break;
|
||||
default:
|
||||
return -2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int safety_memory_dump_base64(char *buffer, size_t buffsize, size_t *used_size)
|
||||
{
|
||||
uint32_t safety_mem_size;
|
||||
size_t output_size;
|
||||
const char *backup_mem_ptr;
|
||||
int res;
|
||||
|
||||
if (!buffer)
|
||||
return -1000;
|
||||
|
||||
safety_mem_size = backup_ram_get_size_in_words() * 4U;
|
||||
output_size = base64_calculate_encoded_size(safety_mem_size);
|
||||
|
||||
if (output_size + 1 > buffsize)
|
||||
return -1001;
|
||||
|
||||
backup_mem_ptr = (const char *)backup_ram_get_base_ptr();
|
||||
res = base64_encode(backup_mem_ptr, buffer, safety_mem_size, buffsize, &output_size);
|
||||
if (res)
|
||||
return -1;
|
||||
buffer[output_size] = '\0';
|
||||
if (used_size)
|
||||
*used_size = output_size + 1u;
|
||||
return 0;
|
||||
}
|
110
stm-firmware/safety/stack-check.c
Normal file
110
stm-firmware/safety/stack-check.c
Normal file
@ -0,0 +1,110 @@
|
||||
/* Reflow Oven Controller
|
||||
*
|
||||
* Copyright (C) 2020 Mario Hüttel <mario.huettel@gmx.net>
|
||||
*
|
||||
* This file is part of the Reflow Oven Controller Project.
|
||||
*
|
||||
* The reflow oven controller is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* The Reflow Oven Control Firmware 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 the reflow oven controller project.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <reflow-controller/safety/stack-check.h>
|
||||
#include <stdint.h>
|
||||
#include <stm-periph/rng.h>
|
||||
#include <stm-periph/crc-unit.h>
|
||||
|
||||
extern char __ld_top_of_stack;
|
||||
extern char __ld_end_stack;
|
||||
|
||||
int32_t stack_check_get_usage()
|
||||
{
|
||||
uint32_t stack_top;
|
||||
uint32_t stack_ptr;
|
||||
|
||||
stack_ptr = read_stack_pointer();
|
||||
stack_top = (uint32_t)&__ld_top_of_stack;
|
||||
|
||||
return stack_top - stack_ptr;
|
||||
}
|
||||
|
||||
int32_t stack_check_get_free()
|
||||
{
|
||||
uint32_t upper_heap_boundary;
|
||||
uint32_t stack_ptr;
|
||||
|
||||
stack_ptr = read_stack_pointer();
|
||||
upper_heap_boundary = (uint32_t)&__ld_end_stack;
|
||||
|
||||
return stack_ptr - upper_heap_boundary;
|
||||
}
|
||||
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Warray-bounds"
|
||||
|
||||
extern uint32_t __ld_start_stack_corruption_detect_area;
|
||||
extern uint32_t __ld_end_stack_corruption_detect_area;
|
||||
|
||||
int stack_check_init_corruption_detect_area(void)
|
||||
{
|
||||
uint32_t *ptr = &__ld_start_stack_corruption_detect_area;
|
||||
uint32_t *end_ptr = &__ld_end_stack_corruption_detect_area;
|
||||
const uint32_t area_size_in_words = &__ld_end_stack_corruption_detect_area -
|
||||
&__ld_start_stack_corruption_detect_area;
|
||||
enum random_number_error rng_stat;
|
||||
uint32_t rng_number;
|
||||
uint32_t crc_val;
|
||||
int ret = 0;
|
||||
|
||||
random_number_gen_init(false);
|
||||
|
||||
while (ptr < &end_ptr[-1]) {
|
||||
rng_stat = random_number_gen_get_number(&rng_number, true);
|
||||
|
||||
if (rng_stat != RNG_ERROR_OK) {
|
||||
ret = -1;
|
||||
goto exit_deinit_rng;
|
||||
}
|
||||
|
||||
*ptr = rng_number;
|
||||
ptr++;
|
||||
}
|
||||
|
||||
/* Init CRC unit and leave it on */
|
||||
crc_unit_init();
|
||||
crc_unit_reset();
|
||||
|
||||
crc_unit_input_array(&__ld_start_stack_corruption_detect_area, area_size_in_words - 1);
|
||||
crc_val = crc_unit_get_crc();
|
||||
end_ptr[-1] = crc_val;
|
||||
|
||||
exit_deinit_rng:
|
||||
random_number_gen_deinit();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
int stack_check_corruption_detect_area(void)
|
||||
{
|
||||
const uint32_t area_size_in_words = &__ld_end_stack_corruption_detect_area -
|
||||
&__ld_start_stack_corruption_detect_area;
|
||||
crc_unit_reset();
|
||||
crc_unit_input_array(&__ld_start_stack_corruption_detect_area, area_size_in_words);
|
||||
if (crc_unit_get_crc() == 0UL) {
|
||||
return 0;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
@ -52,17 +52,17 @@ int watchdog_setup(uint8_t prescaler)
|
||||
/** - Wait for the oscillator to be ready */
|
||||
while (!(RCC->CSR & RCC_CSR_LSIRDY));
|
||||
|
||||
if (prescaler == 4)
|
||||
if (prescaler == 4U)
|
||||
prescaler_reg_val = 0UL;
|
||||
else if (prescaler == 8)
|
||||
else if (prescaler == 8U)
|
||||
prescaler_reg_val = 1UL;
|
||||
else if (prescaler == 16)
|
||||
else if (prescaler == 16U)
|
||||
prescaler_reg_val = 2UL;
|
||||
else if (prescaler == 32)
|
||||
else if (prescaler == 32U)
|
||||
prescaler_reg_val = 3UL;
|
||||
else if (prescaler == 64)
|
||||
else if (prescaler == 64U)
|
||||
prescaler_reg_val = 4UL;
|
||||
else if (prescaler == 128)
|
||||
else if (prescaler == 128U)
|
||||
prescaler_reg_val = 5UL;
|
||||
else
|
||||
prescaler_reg_val = 6UL;
|
||||
@ -105,16 +105,4 @@ int watchdog_ack(uint32_t magic)
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool watchdog_check_reset_source(void)
|
||||
{
|
||||
bool ret;
|
||||
|
||||
ret = !!(RCC->CSR & RCC_CSR_WDGRSTF);
|
||||
|
||||
if (ret)
|
||||
RCC->CSR |= RCC_CSR_RMVF;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/** @} */
|
||||
|
190
stm-firmware/settings/settings-eeprom.c
Normal file
190
stm-firmware/settings/settings-eeprom.c
Normal file
@ -0,0 +1,190 @@
|
||||
/* Reflow Oven Controller
|
||||
*
|
||||
* Copyright (C) 2020 Mario Hüttel <mario.huettel@gmx.net>
|
||||
*
|
||||
* This file is part of the Reflow Oven Controller Project.
|
||||
*
|
||||
* The reflow oven controller is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* The Reflow Oven Control Firmware 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 the reflow oven controller project.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <reflow-controller/settings/settings-eeprom.h>
|
||||
#include <reflow-controller/settings/spi-eeprom.h>
|
||||
#include <stm-periph/crc-unit.h>
|
||||
#include <string.h>
|
||||
|
||||
#define EEPROM_HEADER_MAGIC 0xC5
|
||||
#define EEPROM_HEADER_COMP_VER 0x01
|
||||
|
||||
static const uint8_t expected_header[2] = {EEPROM_HEADER_MAGIC, EEPROM_HEADER_COMP_VER};
|
||||
|
||||
#define EEPROM_CALIBRATION_BASE_ADDR 0x2
|
||||
struct eeprom_calibration_settings {
|
||||
uint32_t active;
|
||||
float offset;
|
||||
float sens_dev;
|
||||
uint32_t crc;
|
||||
};
|
||||
|
||||
#define EEPROM_OVER_TEMP_CONFIG_BASE_ADDR (EEPROM_CALIBRATION_BASE_ADDR + sizeof(struct eeprom_calibration_settings))
|
||||
struct eeprom_over_temp_config {
|
||||
float over_temperature;
|
||||
uint32_t over_temp_crc;
|
||||
};
|
||||
|
||||
static bool check_eeprom_header(void)
|
||||
{
|
||||
uint8_t header[2] = {0};
|
||||
|
||||
/* Try to read the magic header and the compatible version */
|
||||
spi_eeprom_read(0x0, header, 2);
|
||||
if (memcmp(header, expected_header, 2))
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
static void settings_eeprom_zero()
|
||||
{
|
||||
settings_eeprom_save_calibration(0.0f, 0.0f, false);
|
||||
settings_eeprom_save_overtemp_limit(0.0, false);
|
||||
}
|
||||
|
||||
bool settings_eeprom_detect_and_prepare(void)
|
||||
{
|
||||
bool eeprom_ready = false;;
|
||||
|
||||
int res;
|
||||
|
||||
crc_unit_init();
|
||||
res = spi_eeprom_init();
|
||||
if (res)
|
||||
goto ret_deinit_crc;
|
||||
|
||||
if (!spi_eeprom_check_connected())
|
||||
goto ret_deinit_crc;
|
||||
|
||||
if (check_eeprom_header() == false) {
|
||||
/* Try to write a new header and check it again */
|
||||
spi_eeprom_write(0, expected_header, sizeof(expected_header));
|
||||
while (spi_eeprom_write_in_progress());
|
||||
if (check_eeprom_header() == false) {
|
||||
goto ret_deinit_crc;
|
||||
} else {
|
||||
/* Sucessfully written new header
|
||||
* Zero out the rest of the settings
|
||||
*/
|
||||
settings_eeprom_zero();
|
||||
eeprom_ready = true;
|
||||
}
|
||||
} else {
|
||||
eeprom_ready = true;
|
||||
}
|
||||
|
||||
goto exit;
|
||||
|
||||
ret_deinit_crc:
|
||||
crc_unit_deinit();
|
||||
|
||||
exit:
|
||||
return eeprom_ready;
|
||||
}
|
||||
|
||||
int settings_eeprom_save_calibration(float sens, float offset, bool active)
|
||||
{
|
||||
int res;
|
||||
|
||||
struct eeprom_calibration_settings sett;
|
||||
|
||||
sett.active = (active ? 0xAABBCCDD : 0);
|
||||
sett.offset = offset;
|
||||
sett.sens_dev = sens;
|
||||
|
||||
crc_unit_reset();
|
||||
crc_unit_input_array((const uint32_t *)&sett, (sizeof(sett) / 4)-1);
|
||||
sett.crc = crc_unit_get_crc();
|
||||
|
||||
res = spi_eeprom_write(EEPROM_CALIBRATION_BASE_ADDR, (uint8_t *)&sett, sizeof(sett));
|
||||
|
||||
if (res)
|
||||
return -2;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
int settings_eeprom_load_calibration(float *sens, float *offset, bool *active)
|
||||
{
|
||||
struct eeprom_calibration_settings sett;
|
||||
int res;
|
||||
|
||||
res = spi_eeprom_read(EEPROM_CALIBRATION_BASE_ADDR, (uint8_t *)&sett, sizeof(sett));
|
||||
if (res)
|
||||
return -2;
|
||||
|
||||
crc_unit_reset();
|
||||
crc_unit_input_array((const uint32_t *)&sett, sizeof(sett) / 4 - 1);
|
||||
if (crc_unit_get_crc() == sett.crc) {
|
||||
if (sens)
|
||||
*sens = sett.sens_dev;
|
||||
if (offset)
|
||||
*offset = sett.offset;
|
||||
if (active)
|
||||
*active = (sett.active ? true : false);
|
||||
return 0;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int settings_eeprom_save_overtemp_limit(float overtemp_limit, bool active)
|
||||
{
|
||||
int res;
|
||||
struct eeprom_over_temp_config over_temp_conf_entry;
|
||||
const uint8_t zero_vals[sizeof(over_temp_conf_entry)] = {0};
|
||||
|
||||
if (!active) {
|
||||
res = spi_eeprom_write(EEPROM_OVER_TEMP_CONFIG_BASE_ADDR, (const uint8_t *)zero_vals,
|
||||
sizeof(zero_vals));
|
||||
return res ? -1 : 0;
|
||||
}
|
||||
|
||||
over_temp_conf_entry.over_temperature = overtemp_limit;
|
||||
crc_unit_reset();
|
||||
crc_unit_input_array((uint32_t *)&over_temp_conf_entry, 1);
|
||||
over_temp_conf_entry.over_temp_crc = crc_unit_get_crc();
|
||||
res = spi_eeprom_write(EEPROM_OVER_TEMP_CONFIG_BASE_ADDR, (const uint8_t *)&over_temp_conf_entry,
|
||||
sizeof(over_temp_conf_entry));
|
||||
|
||||
return res ? -1 : 0;
|
||||
}
|
||||
|
||||
int settings_eeprom_load_overtemp_limit(float *overtemp_limit)
|
||||
{
|
||||
int res;
|
||||
struct eeprom_over_temp_config over_temp_conf_entry;
|
||||
|
||||
if (!overtemp_limit)
|
||||
return -1001;
|
||||
res = spi_eeprom_read(EEPROM_OVER_TEMP_CONFIG_BASE_ADDR, (uint8_t *)&over_temp_conf_entry,
|
||||
sizeof(struct eeprom_over_temp_config));
|
||||
if (res)
|
||||
return -2;
|
||||
|
||||
crc_unit_reset();
|
||||
crc_unit_input_array((uint32_t *)&over_temp_conf_entry, 1);
|
||||
if (crc_unit_get_crc() != over_temp_conf_entry.over_temp_crc)
|
||||
return -1;
|
||||
|
||||
*overtemp_limit = over_temp_conf_entry.over_temperature;
|
||||
return 0;
|
||||
}
|
@ -25,6 +25,9 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <config-parser/config-parser.h>
|
||||
|
||||
static char workbuff[256];
|
||||
|
||||
static void get_controller_folder_path(char *path, size_t size)
|
||||
{
|
||||
@ -46,7 +49,7 @@ static void get_controller_settings_path(char *path, size_t size, const char *se
|
||||
char folder[48];
|
||||
|
||||
get_controller_folder_path(folder, sizeof(folder));
|
||||
snprintf(path, size, "%s/%s.dat", folder, setting);
|
||||
snprintf(path, size, "%s/%s.conf", folder, setting);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -79,104 +82,137 @@ static int create_controller_folder(void)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int read_settings_file_float(const char *path, float *value)
|
||||
{
|
||||
FRESULT res;
|
||||
FIL file;
|
||||
int ret = 0;
|
||||
char buff[32];
|
||||
UINT read_count;
|
||||
|
||||
if (!value)
|
||||
return -1002;
|
||||
|
||||
res = f_open(&file, path, FA_READ);
|
||||
if (res == FR_OK) {
|
||||
memset(buff, 0, sizeof(buff));
|
||||
res = f_read(&file, buff, sizeof(buff)-1, &read_count);
|
||||
if (res != FR_OK) {
|
||||
ret = -1;
|
||||
goto close_file;
|
||||
}
|
||||
*value = strtof(buff, NULL);
|
||||
close_file:
|
||||
f_close(&file);
|
||||
} else {
|
||||
ret = -2;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int sd_card_settings_save_calibration(float sens_deviation, float offset, bool active)
|
||||
{
|
||||
int status;
|
||||
char path[128];
|
||||
char buff[64];
|
||||
UINT bw;
|
||||
FIL file;
|
||||
FRESULT res;
|
||||
char path[200];
|
||||
FRESULT res = FR_OK;
|
||||
int ret = 0;
|
||||
FIL file;
|
||||
|
||||
status = create_controller_folder();
|
||||
if (status < 0)
|
||||
get_controller_settings_path(path, sizeof(path), "calibration");
|
||||
|
||||
if (create_controller_folder() < 0)
|
||||
return -2;
|
||||
|
||||
get_controller_settings_path(path, sizeof(path), "offset");
|
||||
if (active) {
|
||||
res = f_open(&file, path, FA_CREATE_ALWAYS | FA_WRITE);
|
||||
if (res != FR_OK) {
|
||||
ret = -2;
|
||||
goto exit_offset;
|
||||
}
|
||||
|
||||
status = snprintf(buff, sizeof(buff), "%f\n", offset);
|
||||
f_write(&file, buff, status, &bw);
|
||||
f_close(&file);
|
||||
} else {
|
||||
f_unlink(path);
|
||||
if (!active) {
|
||||
res = f_unlink(path);
|
||||
goto check_fresult;
|
||||
}
|
||||
exit_offset:
|
||||
get_controller_settings_path(path, sizeof(path), "sens");
|
||||
if (active) {
|
||||
res = f_open(&file, path, FA_CREATE_ALWAYS | FA_WRITE);
|
||||
if (res != FR_OK) {
|
||||
ret = -2;
|
||||
goto exit_sens;
|
||||
}
|
||||
|
||||
status = snprintf(buff, sizeof(buff), "%f\n", sens_deviation);
|
||||
f_write(&file, buff, status, &bw);
|
||||
f_close(&file);
|
||||
} else {
|
||||
f_unlink(path);
|
||||
}
|
||||
exit_sens:
|
||||
res = f_open(&file, path, FA_CREATE_ALWAYS | FA_WRITE);
|
||||
if (res != FR_OK)
|
||||
goto check_fresult;
|
||||
|
||||
snprintf(path, sizeof(path), "offset = %f\nsensitivity = %f\n", offset, sens_deviation);
|
||||
ret = f_puts(path, &file);
|
||||
if (ret < 0)
|
||||
goto close_file;
|
||||
|
||||
ret = 0;
|
||||
|
||||
close_file:
|
||||
res = f_close(&file);
|
||||
check_fresult:
|
||||
if (res != FR_OK)
|
||||
return -2;
|
||||
return ret;
|
||||
}
|
||||
|
||||
int sd_card_settings_try_load_calibration(float *sens_deviation, float *offset)
|
||||
{
|
||||
char path[128];
|
||||
int status;
|
||||
int ret = 0;
|
||||
int status = -1;
|
||||
struct config_parser parser;
|
||||
config_parser_handle_t p;
|
||||
enum config_parser_ret res;
|
||||
struct config_parser_entry entry;
|
||||
bool sens_loaded = false;
|
||||
bool offset_loaded = false;
|
||||
|
||||
if (!sens_deviation || !offset)
|
||||
return -1000;
|
||||
|
||||
get_controller_settings_path(path, sizeof(path), "offset");
|
||||
status = read_settings_file_float(path, offset);
|
||||
if (status) {
|
||||
ret = status;
|
||||
get_controller_settings_path(path, sizeof(path), "calibration");
|
||||
p = config_parser_open_file(&parser, false, path, workbuff, sizeof(workbuff));
|
||||
|
||||
if (!p)
|
||||
return status;
|
||||
do {
|
||||
res = config_parser_get_line(p, &entry, true);
|
||||
if (res == CONFIG_PARSER_OK) {
|
||||
if (!strcmp(entry.name, "offset") && entry.type == CONFIG_PARSER_TYPE_FLOAT) {
|
||||
offset_loaded = true;
|
||||
*offset = entry.value.float_val;
|
||||
} else if (!strcmp(entry.name, "sensitivity") && entry.type == CONFIG_PARSER_TYPE_FLOAT) {
|
||||
sens_loaded = true;
|
||||
*sens_deviation = entry.value.float_val;
|
||||
}
|
||||
}
|
||||
|
||||
} while (!config_parser_ret_is_abort_condition(res));
|
||||
|
||||
config_parser_close_file(p);
|
||||
|
||||
if (sens_loaded && offset_loaded)
|
||||
status = 0;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
enum settings_load_result sd_card_settings_load_pid_oven_parameters(struct oven_pid_settings *settings)
|
||||
{
|
||||
enum settings_load_result ret = SETT_LOAD_ERR;
|
||||
const char * const file_name = SETTINGS_PID_PARAMETER_FILE;
|
||||
struct config_parser parser;
|
||||
config_parser_handle_t p;
|
||||
enum config_parser_ret parse_result;
|
||||
struct config_parser_entry entry;
|
||||
bool kp_loaded = false, kd_loaded = false, ki_loaded = false, int_max_loaded = false, t_sample = false;
|
||||
bool kd_tau_loaded = false;
|
||||
|
||||
if (!settings) {
|
||||
ret = SETT_LOAD_ERR;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
get_controller_settings_path(path, sizeof(path), "sens");
|
||||
status = read_settings_file_float(path, sens_deviation);
|
||||
if (status) {
|
||||
ret = status;
|
||||
workbuff[0] = 0;
|
||||
p = config_parser_open_file(&parser, false, file_name, workbuff, sizeof(workbuff));
|
||||
if (!p)
|
||||
goto exit;
|
||||
}
|
||||
do {
|
||||
parse_result = config_parser_get_line(p, &entry, true);
|
||||
if (parse_result == CONFIG_PARSER_OK) {
|
||||
if (!strcmp(entry.name, "kp")) {
|
||||
kp_loaded = true;
|
||||
settings->kp = entry.value.float_val;
|
||||
} else if (!strcmp(entry.name, "kd")) {
|
||||
kd_loaded = true;
|
||||
settings->kd = entry.value.float_val;
|
||||
} else if (!strcmp(entry.name, "ki")) {
|
||||
ki_loaded = true;
|
||||
settings->ki = entry.value.float_val;
|
||||
} else if (!strcmp(entry.name, "max_int")) {
|
||||
int_max_loaded = true;
|
||||
settings->max_integral = entry.value.float_val;
|
||||
} else if (!strcmp(entry.name, "sample_period")) {
|
||||
t_sample = true;
|
||||
settings->t_sample = entry.value.float_val / 1000.0f;
|
||||
} else if (!strcmp(entry.name, "kd_tau")) {
|
||||
settings->kd_tau = entry.value.float_val;
|
||||
kd_tau_loaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
} while (!config_parser_ret_is_abort_condition(parse_result));
|
||||
|
||||
|
||||
if (kp_loaded && kd_loaded && ki_loaded && t_sample && int_max_loaded && kd_tau_loaded)
|
||||
ret = SETT_LOAD_SUCCESS;
|
||||
|
||||
config_parser_close_file(p);
|
||||
|
||||
exit:
|
||||
return ret;
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user