127 Commits

Author SHA1 Message Date
8576bf4231 Merge branch 'dev' into feature/28-profile-parser 2021-04-04 19:35:52 +02:00
6ebd74cb31 Safety Bug: Add meas ADC timing flag to output of the pt1000 read function so it becomes invalid in this case. 2021-04-04 19:35:32 +02:00
46125ba752 Issue #28: Implement first working draft of temperature profile GUI 2021-04-04 19:34:13 +02:00
6c9f90c986 Issue #28: Fix buf in profile executer 2021-04-04 19:33:33 +02:00
74defd5384 Add main loop cycle counter for debugging 2021-04-04 19:32:44 +02:00
5deac33949 Implement basic gui for profile selection. Still doesn't do anything 2021-04-04 17:43:31 +02:00
666353e3b7 Merge branch 'dev' into feature/28-profile-parser 2021-04-04 17:01:51 +02:00
df40ab1be7 Update base64 lib submodule 2021-03-23 22:19:43 +01:00
4c3574c2e2 Make project Cmake ready 2021-03-23 22:15:11 +01:00
289f49204d fisr draft of cmake buidl system. Update generation still missing 2021-03-21 22:38:22 +01:00
4bc98e6baf Merge branch 'dev' into feature/28-profile-parser 2021-03-21 21:08:38 +01:00
e50ce0d541 Add pictures of external watchdog 2021-03-21 21:08:19 +01:00
78b63c853f Issue #28: Start GUI for Temp Profile execution 2021-03-21 21:07:54 +01:00
a25b249d77 Issue #28: Improve script handling 2021-03-21 19:21:36 +01:00
e815442617 Pid Controller: Set integral term lower boundary to 0 2021-03-21 19:18:08 +01:00
60104df30e Issue #28: Write temperture ramp command 2021-03-20 01:02:33 +01:00
5ab911b4b6 Merge branch 'dev' into feature/28-profile-parser 2021-03-19 20:55:00 +01:00
9c37e083a0 Merge branch 'error-mem-viewer' into dev
* This is a preliminary version of hte error memory viewer
2021-03-19 20:54:32 +01:00
08427cc589 Merge branch 'dev' into feature/28-profile-parser 2021-03-19 20:53:58 +01:00
62d745bd3a Fix doxygen header 2021-03-19 20:53:46 +01:00
ba6e0880b4 Merge branch 'dev' into feature/28-profile-parser 2021-03-19 20:52:13 +01:00
ec2c23c9f7 Change unstbale detection range from 10 Ohm to 20 Ohm 2021-03-19 20:51:54 +01:00
6a71416d2a Add void casts to unused parameters 2021-03-19 20:24:49 +01:00
1ecd5edd93 Add temperature profile executer and add shell command 2021-03-19 20:19:37 +01:00
1b4eba1871 Write parser for temp profile language. Not yet tested. 2021-03-19 16:58:14 +01:00
13e7cbfd81 Documentation: Fix docu of overtemp flag 2021-03-18 23:21:17 +01:00
857838f293 Docu: Improvements 2021-03-18 23:18:18 +01:00
ae97a69d26 documentation 2021-03-18 23:15:13 +01:00
001f2be176 Add a few flags to the documentation 2021-03-18 23:12:44 +01:00
1797dac60b Systick: Correct comments 2021-03-18 22:53:11 +01:00
e3e4a6d926 Merge branch 'dev' into feature/28-profile-parser 2021-03-18 22:50:28 +01:00
dca696cd6a Safety: Fix different spelling mistakes for persistence 2021-03-18 22:50:13 +01:00
93ff4959a2 Issue #28: Start Temp profile parser 2021-03-18 22:44:05 +01:00
b1a7af28a2 Merge branch 'dev' into error-mem-viewer 2021-03-15 21:24:13 +01:00
b560a91673 Merge branch 'issue/26-overtemp-flag' of mhu/reflow-oven-control-sw into dev 2021-03-14 22:01:06 +01:00
64a97fa048 Issue #26: Add overtemp function to shell 2021-03-14 21:57:33 +01:00
d1b8e91674 Fix typo in linker script 2021-03-14 20:08:52 +01:00
0fc35db53d Merge branch 'dev' into issue/26-overtemp-flag 2021-02-15 20:36:13 +01:00
8a764f599b Remove cat command from shell 2021-02-15 20:35:54 +01:00
b9dfe35652 Issue #26: Add overtemp limit setting to settings module and load it from EEPROM at startup 2021-02-15 20:28:45 +01:00
694a78982a Add missing parameter to doxygen header of dc_pt1000_set_resistance_calibration() 2021-02-14 20:11:21 +01:00
59ff842f56 Merge branch 'dev' into error-mem-viewer 2021-02-14 20:09:20 +01:00
7c5b60ec6b #26: Add over temperature monitor settings to eeprom 2021-02-14 20:06:28 +01:00
e614eaa23b Correct version of EEPROM header for settings and write check for overtemperature 2021-02-02 20:50:30 +01:00
50ad31d58a Issue #26: Add configuration for overtemperature flag to safety controller and include the config in the memory checking 2021-02-02 20:35:45 +01:00
64bb06882f Issue #26: Add reverse lookup function to temp converter that converts a temperature to a PT1000 resistance value 2021-02-02 19:32:12 +01:00
0b01be9840 Issue #26: Add ERR_FLAG_OVERTEMP Error flag
* Add error flag
* Set default persistence to false
* Set default flag weight to stop PID controller
2021-02-02 18:40:52 +01:00
ba9247be69 Merge branch 'issue/24-Change-ERR_FLAG_MEAS_ADC_UNSTABLE' of mhu/reflow-oven-control-sw into dev 2021-01-26 22:49:54 +01:00
e7aa714976 Update documentation regarding Unstable flag 2021-01-26 22:48:19 +01:00
125b41de44 Fix Doxyfile 2021-01-26 22:46:44 +01:00
4c1797aa24 Issue #24: Make UNSTABLE flag only go away, if the PT1000 value stays within the stable region for a given amount of samples 2021-01-26 21:59:41 +01:00
20a65fd7f9 Issue #24: Implement new handling for Unstable flag.
* Unstable flag will now be set, whenever the output of the moving average filter
  differs more than 10 Ohms from the measured prefilterd input.
2021-01-26 21:46:44 +01:00
67b079fe33 Improve rport error function of safety controller 2021-01-26 21:46:33 +01:00
a5c9350835 Fix typo 2021-01-26 21:26:58 +01:00
cc545bfc69 Doxygen: use short names 2021-01-26 21:24:11 +01:00
f6fb541924 Update doxygen config 2021-01-25 22:31:00 +01:00
86a0b2087e Fix bug in weight handling of error flags. 2021-01-25 21:07:09 +01:00
55f35a5009 Edit doxygen headers and improve flag weight handling in safety controller 2021-01-25 20:59:48 +01:00
870c228e37 update shellmatta tonewest version with fixed history 2021-01-24 20:59:07 +01:00
2fcc5d6a9f Add further doxygen headers 2021-01-24 20:31:01 +01:00
52272938b7 update handling of analog monitors. Manually adding analog monotprs to the checking function is not necessary anymore 2021-01-24 20:27:07 +01:00
58c72fb2bc Add documentation to safety controller code 2021-01-24 20:17:53 +01:00
99d96fb426 Add kd_tau to oven PID parameters 2021-01-24 19:56:00 +01:00
24651fa74d Remove empty lines 2021-01-24 19:55:46 +01:00
56b5ddc209 Fix spelling and underline in shell command for error flags 2021-01-09 23:44:25 +01:00
94726e1466 Fix float notation 2021-01-09 23:44:15 +01:00
d6356de1b6 Fix calculation of safety ADC monitors. 2021-01-09 22:28:22 +01:00
69ff13a991 Add fast moving average filter for faster startup 2021-01-09 22:26:31 +01:00
bbfcd429fe Add SSR safety enable to oven driver 2021-01-09 21:59:59 +01:00
3a07347f48 Implement EEPROM and use it for saving the calibration 2021-01-08 18:39:54 +01:00
0e233a257c Initialize EEPROM in at start of program 2021-01-02 23:04:57 +01:00
95bd606dd8 Add functions for eeprom. Not yet implemented 2021-01-02 23:03:59 +01:00
6e3f90d38e Fix missing magic in SPI driver 2021-01-02 23:03:22 +01:00
82cab98f59 Update SPI driver 2021-01-02 20:28:01 +01:00
2d29857c2f Add SPI driver 2021-01-02 18:56:29 +01:00
bea8e2df74 Store error flags in error memory if they cause a panic event. 2021-01-02 02:37:59 +01:00
7c6205d20a Add supply voltage monitor to safety adc and safety controller 2021-01-01 19:48:53 +01:00
0e114d1344 Add default frequency to loudspeaker set 2021-01-01 19:48:31 +01:00
2c3c1c9861 Add external watchdog to safety controller for HW revision > 1.3 and Release Build 2021-01-01 18:04:14 +01:00
75f9c58c54 Fix bug: Invalid calibration loaded if no calibration is present on sd card 2021-01-01 17:31:35 +01:00
44c861a245 Merge branch 'updater' into dev 2021-01-01 17:29:46 +01:00
c01b7a9825 Fix broken hardware version detect. v1.3 is now correctly detected. 2021-01-01 17:28:38 +01:00
87c372d871 Add measurement results for v1.3 HW revision 2021-01-01 17:27:58 +01:00
3125e34922 Update jupyter notebook 2020-12-21 17:23:48 +01:00
fed18f1c24 Ram code: Update gitignore 2020-12-21 17:23:19 +01:00
caabde39d2 Updater RAM Code
* Add Fatfs for reading from SD card
* Add structure of Hex parser
2020-12-21 17:21:04 +01:00
1167358c5a Add .bin.h files to ram code's gitignore 2020-12-14 20:32:41 +01:00
444fded972 Delete derived ram code include file 2020-12-14 20:31:53 +01:00
963b1e106c Updater: Write functional ram code loader
Updater RAM Code
----------------
* Ack running watchdog every 250 ms
* Blink green LED every 250 ms

Firmware
--------
* Add RAM code loader
* Reorganize initialization
* Add update command to shell
2020-12-14 20:29:51 +01:00
0d861b1aed Safety Controller: Fix return type of function 2020-12-14 20:29:21 +01:00
9226f43cad Merge branch 'updater' into dev 2020-12-13 22:51:36 +01:00
2ff2d963c8 Add measuremnts of v1.2 hardware 2020-12-13 22:49:25 +01:00
d353183826 updater: Add script for converting bin file to C array 2020-12-07 22:35:52 +01:00
a3e652ddb8 Start RAM code for updater 2020-12-07 21:39:14 +01:00
c67298118e Improve GUI 2020-12-07 00:19:42 +01:00
abb333cfe7 Add drawio image for PT1000 processing 2020-12-04 20:55:57 +01:00
5a41e7815f Add script that allows building the sphinx documentation using a virtualenv 2020-12-04 19:42:53 +01:00
8a8004e187 Add generic function to detzermine reset cuase to rcc manager 2020-12-01 21:24:59 +01:00
b1271cee43 Doxygen 2020-12-01 21:03:05 +01:00
daaf848e0c Renamed clock-enable manager to rcc manager and improve some header files with doxygen comments 2020-12-01 21:00:23 +01:00
ef8e6231ff Issue #4: Implement Constant temperature function in GUI. This is useful to verify the oven parameters 2020-11-30 21:43:38 +01:00
cd35f9e694 Add low pass for Derviative part in PID controller 2020-11-30 21:40:28 +01:00
91d9db6a4e Make Safety ADC use sequnece feature of ADC and use DMA to write data 2020-11-30 00:01:26 +01:00
898feac168 increase shell tx buffer 2020-11-29 20:00:16 +01:00
0b1d872ed0 Make safety controller use config overrides from backup ram 2020-11-29 19:59:58 +01:00
687c72bde7 Use new gui module in main 2020-11-29 19:04:10 +01:00
b6d4a2e35e Implement safety memory functions for config overrides 2020-11-29 19:03:42 +01:00
a474111f36 Use error checking function of config parser in settings module 2020-11-29 19:03:17 +01:00
ed4c18f2be Add error checking function for config parser 2020-11-29 19:02:52 +01:00
f4d6f5a1ae Add PCB/Hardware version detection 2020-11-29 19:02:30 +01:00
32da2a5fa6 Reflow Controller GUI: Move gui to gui.c file in UI subfolder 2020-11-29 19:01:24 +01:00
ec66814184 Decrease history buffer of shellmatta 2020-11-29 17:19:40 +01:00
37c68809d1 Error mem viewer almost finished 2020-11-29 17:18:28 +01:00
67d59928c9 Add unfinished construct of error memory viewer 2020-11-19 22:36:54 +01:00
37bc397e9a Add reset-cal shell command and modify cal command to show existing calibration 2020-11-17 00:53:21 +01:00
d176389711 Add limit to scrolling of flag list in LCD gui 2020-11-17 00:31:24 +01:00
e60da5b23f Merge branch 'issue/6-safety-flag-gui' of mhu/reflow-oven-control-sw into dev 2020-11-16 20:03:07 +01:00
8603f84142 Issue #6: Improve LCD menu and implement function to see safety flags 2020-11-16 20:01:15 +01:00
d51c73d694 Merge branch 'issue/21-calibration' of mhu/reflow-oven-control-sw into dev 2020-11-16 18:44:59 +01:00
8f6462bcc9 Issue 21: Change documentation for calibration path 2020-11-16 18:38:50 +01:00
c02d988350 Issue #21: Adapt calibration routine to new calibration format 2020-11-15 23:36:52 +01:00
11b84afca0 Issue #21: Change calibration apply function 2020-11-15 23:10:19 +01:00
a858223c35 Fix #3: Merge branch 'issue/3-load-oven-pid-params' into dev 2020-11-08 21:38:21 +01:00
f2405e23b4 Add settings load function for PID parameters 2020-11-08 21:35:41 +01:00
2e640fa7fa Update base 64 library 2020-11-02 23:47:32 +01:00
1a3889b72d Update base64 lib to newest version. Now supports decoding 2020-11-02 23:44:37 +01:00
a7e376deab Add safety memory dump to file functionality 2020-11-02 18:21:24 +01:00
124 changed files with 53215 additions and 986 deletions

2
doc/.gitignore vendored
View File

@@ -0,0 +1,2 @@
build/*
venv

21
doc/make-with-virtualenv.sh Executable file
View 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
View 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
View File

@@ -0,0 +1,2 @@
.drawio/*
.drawio

View File

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

View File

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

View File

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

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

View File

@@ -68,7 +68,8 @@ no no yes no
ERR_FLAG_MEAS_ADC_UNSTABLE
--------------------------
``ERR_FLAG_MEAS_ADC_UNSTABLE`` is set after startup of the PT1000 measuremnt or after reconfiguring the filter settings.
``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`
@@ -86,7 +87,7 @@ 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
safety backup memory is cleared.
.. seealso:: :ref:`backup_ram`
@@ -102,7 +103,7 @@ yes no yes no
ERR_FLAG_STACK
---------------------------
``ERR_FLAG_STACK`` ialization of the controller, in case a corrupted safety memory is encountered.
``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`
@@ -112,3 +113,38 @@ 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
========== ============= ============= ===========

View File

@@ -34,6 +34,6 @@ On each run of the safety controller's handling function (:c:func:`safety_contro
.. doxygenfunction:: stack_check_corruption_detect_area
This function constantly checks the memory area for write modifications, and therefore detects, if the stack or heap have grown outside their boundaries.
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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

3
error-mem-viewer/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
*.ui~
*.user
*.user*

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

View 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_ */

View File

@@ -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_ */

View File

@@ -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_ */

View 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"
)

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

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

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

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

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -14,3 +14,5 @@ reflow-controller.includes
*.includes
*.config
*.files
*.user.*
*.user

View 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/")

View File

@@ -1,216 +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.
LIBRARIES =
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 += stm-periph/rng.c
CFILES += digio.c
CFILES += stm-periph/unique-id.c
CFILES += calibration.c
CFILES += temp-converter.c
CFILES += rotary-encoder.c button.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 += stm-periph/crc-unit.c
CFILES += safety/safety-adc.c safety/safety-controller.c safety/watchdog.c safety/fault.c safety/safety-memory.c safety/stack-check.c
CFILES += config-parser/config-parser.c
INCLUDEPATH += -Iconfig-parser/include
CFILES += base64-lib/src/base64-lib.c
INCLUDEPATH += -Ibase64-lib/include
DEFINES += -DBASE64_LOOKUP_TABLE_SECTION="\".ccm.bss\""
DEBUG_DEFINES = -DDEBUGBUILD
RELEASE_DEFINES = -DUART_ON_DEBUG_HEADER
###################################################################################
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)

View File

@@ -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,14 +301,32 @@ 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);
}
@@ -382,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;

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

View File

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

View File

@@ -30,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;
@@ -45,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;
@@ -189,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;
@@ -210,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') {
@@ -261,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') {

View File

@@ -96,7 +96,7 @@ exit:
return 0;
}
enum config_parser_ret config_parser_get_line(config_parser_handle_t handle, struct config_parser_entry *entry)
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);
@@ -142,6 +142,15 @@ enum config_parser_ret config_parser_get_line(config_parser_handle_t handle, str
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;
}
@@ -179,4 +188,15 @@ enum config_parser_ret config_parser_close_file(config_parser_handle_t handle)
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;
}
/** @} */

View File

@@ -82,7 +82,7 @@ config_parser_handle_t config_parser_open_file(struct config_parser *config_pars
* 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);
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);
@@ -90,6 +90,8 @@ enum config_parser_ret config_parser_write_entry(config_parser_handle_t handle,
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_ */
/** @} */

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

View File

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

View File

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

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

View File

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

View File

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

View File

@@ -27,7 +27,7 @@
#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);

View File

@@ -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__ */

View File

@@ -59,6 +59,7 @@ 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);
void loudspeaker_set(uint16_t val);

View 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_ */

View File

@@ -35,16 +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);

View File

@@ -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__ */

View File

@@ -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__ */

View File

@@ -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_ */

View File

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

View File

@@ -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__ */

View File

@@ -18,10 +18,23 @@
* 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),
@@ -41,8 +54,16 @@ enum safety_flag {
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),
@@ -50,16 +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, .persistency = NULL}
#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, .persistency = (_persist), .start_dummy = 0xFF1100BB, .end_dummy = 0xEBB439A2}
#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
@@ -94,14 +122,27 @@ 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), \
@@ -119,7 +160,9 @@ enum analog_value_monitor {
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_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), \
@@ -132,13 +175,15 @@ enum analog_value_monitor {
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 perioodically, but only on startup.
/* 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_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__ */

View File

@@ -48,6 +48,7 @@ struct analog_monitor_info {
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. */
};
@@ -249,7 +250,22 @@ int safety_controller_get_timing_mon_name_by_index(uint32_t index, char *buffer,
* @brief Get the count of timing monitors
* @return Timing monitor count
*/
uint32_t safety_controller_get_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__ */

View File

@@ -23,6 +23,7 @@
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
/** @addtogroup safety-memory
* @{
@@ -119,7 +120,7 @@ struct error_memory_entry {
*/
enum config_override_entry_type {
SAFETY_MEMORY_CONFIG_OVERRIDE_WEIGHT = 1,
SAFETY_MEMORY_CONFIG_OVERRIDE_PERSISTANCE = 2,
SAFETY_MEMORY_CONFIG_OVERRIDE_PERSISTENCE = 2,
};
/**
@@ -143,8 +144,8 @@ struct config_override {
} weight_override;
struct {
uint8_t flag;
uint8_t persistance;
} persistance_override;
bool persistence;
} persistence_override;
} entry;
};

View File

@@ -87,7 +87,7 @@ int stack_check_init_corruption_detect_area(void);
* @ref stack_check_init_corruption_detect_area beforehand.
*
* The CRC unit must be enabled for this function to work properly.
* After calling @stack_check_init_corruption_detect_area, this is the case.
* 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.

View File

@@ -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 return false when called a second time
* @return true, if reset was generated by the watchdog
*/
bool watchdog_check_reset_source(void);
#endif /* __WATCHDOG_H__ */

View File

@@ -0,0 +1,36 @@
/* 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 __SETTINGS_SETTINGS_EEPROM_H__
#define __SETTINGS_SETTINGS_EEPROM_H__
#include <stdbool.h>
bool settings_eeprom_detect_and_prepare(void);
int settings_eeprom_save_calibration(float sens, float offset, bool active);
int settings_eeprom_load_calibration(float *sens, float *offset, bool *active);
int settings_eeprom_save_overtemp_limit(float overtemp_limit, bool active);
int settings_eeprom_load_overtemp_limit(float *overtemp_limit);
#endif /* __SETTINGS_SETTINGS_EEPROM_H__ */

View File

@@ -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__ */

View File

@@ -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__ */

View 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__ */

View File

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

View File

@@ -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__ */

View File

@@ -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__ */

View 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_ */

View File

@@ -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_ */

View File

@@ -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__ */

View File

@@ -0,0 +1,31 @@
/* 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 __UPDATER_UPDATER_H__
#define __UPDATER_UPDATER_H__
#define UPDATER_RAM_CODE_BASE_ADDRESS (0x20000000UL)
/**
* @brief Start the RAM Code of the updater. This function will never return!
*/
void __attribute__((noreturn)) start_updater(void);
#endif /* __UPDATER_UPDATER_H__ */

View File

@@ -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__ */

View 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__ */

View File

@@ -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)
{
@@ -55,6 +60,7 @@ static void setup_nvic_priorities(void)
NVIC_SetPriority(DMA2_Stream0_IRQn, 1);
/* Shelmatta UART TX */
NVIC_SetPriority(DMA2_Stream7_IRQn, 3);
NVIC_SetPriority(DMA2_Stream4_IRQn, 2);
}
FATFS fs;
@@ -80,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;
@@ -141,34 +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);
rcc_manager_disable_clock(&RCC->AHB1ENR, BITMASK_TO_BITNO(RCC_AHB1ENR_GPIOEEN));
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)
{
setup_nvic_priorities();
systick_setup();
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();
}
@@ -195,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);
@@ -220,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);
safety_controller_handle();
/* Todo: Remove this */
oven_driver_set_power(0);
oven_driver_apply_power_level();
/* Execute current profile step, if a profile is active */
temp_profile_executer_handle();
safety_controller_handle();
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;
@@ -249,7 +284,7 @@ void sdio_wait_ms(uint32_t ms)
*/
void DMA2_Stream7_IRQHandler(void)
{
uint32_t hisr = DMA2->HISR;
uint32_t hisr = DMA2->HISR & (0x3F << 22);
DMA2->HIFCR = hisr;

View File

@@ -1,3 +0,0 @@
*
*.*
!.gitignore

View File

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

View File

@@ -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)
@@ -78,13 +79,13 @@ float pid_sample(struct pid_controller *pid, float deviation)
output = deviation * pid->k_p;
/* PID runaway compensation */
if (!(deviation > 0.0f && pid->control_output > pid->output_sat_max - 0.5f) &&
!(deviation < 0.0f && pid->control_output < pid->output_sat_min + 0.5f)) {
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;

View File

@@ -1,305 +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 int last_page = -1;
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;
last_page = -1;
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:
if (last_page == 0)
break;
last_page = 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:
if (last_page == 1)
break;
last_page = 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:
if (last_page == 2)
break;
last_page = 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:
if (last_page == 3)
break;
last_page = 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;
last_page = -1;
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;
bool menu_changed = false;
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);
}

View File

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

View File

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

View File

@@ -28,6 +28,7 @@
#include <reflow-controller/safety/watchdog.h>
#include <reflow-controller/safety/safety-adc.h>
#include <reflow-controller/safety/stack-check.h>
#include <reflow-controller/hw-version-detect.h>
#include <helper-macros/helper-macros.h>
#include <stm-periph/crc-unit.h>
#include <reflow-controller/systick.h>
@@ -39,58 +40,122 @@
#include <reflow-controller/safety/safety-memory.h>
#include <reflow-controller/oven-driver.h>
#include <helper-macros/helper-macros.h>
#include <stm-periph/rcc-manager.h>
#include <reflow-controller/temp-converter.h>
#include <reflow-controller/adc-meas.h>
#define check_flag_persistent(flag) ((flag)->persistency && (flag)->persistency->persistency)
#define get_flag_weight(flag) ((flag)->weight ? (flag->weight->weight) : SAFETY_FLAG_CONFIG_WEIGHT_NONE)
/**
* @brief Macro that checks if a given @ref error_flag is persistent
*/
#define check_flag_persistent(flag) ((flag)->persistence && (flag)->persistence->persistence)
/**
* @brief Macro that retrieves the flag weight of a given @ref error_flag
*
* If no flag weight table is present, the flag is evaluated as SAFETY_FLAG_CONFIG_WEIGHT_PANIC
*/
#define get_flag_weight(flag) ((flag)->weight ? ((flag)->weight->weight) : SAFETY_FLAG_CONFIG_WEIGHT_PANIC)
/**
* @brief Safety controller internal structure implementing a safety flag weight.
*/
struct safety_weight {
uint32_t start_dummy;
enum config_weight weight;
enum safety_flag flag;
volatile struct error_flag *flag_ptr;
uint32_t end_dummy;
/** @brief Dummy value. This seeds the CRC */
uint32_t start_dummy;
/** @brief The safety flag's weight */
enum config_weight weight;
/** @brief The enum value of the flag this weight corresponds to */
enum safety_flag flag;
/** @brief the flag, this weight corresponds to */
volatile struct error_flag *flag_ptr;
/** @brief Dummy value. This seeds the CRC */
uint32_t end_dummy;
};
struct safety_persistency {
uint32_t start_dummy;
bool persistency;
enum safety_flag flag;
volatile struct error_flag *flag_ptr;
uint32_t end_dummy;
/**
* @brief Safety controller internal struct implementing a flag persistence entry
*/
struct safety_persistence {
/** @brief Dummy value. This seeds the CRC */
uint32_t start_dummy;
/** @brief Corresponding flag is persistent and cannot be cleared */
bool persistence;
/** @brief Corresponding safety flag's enum value */
enum safety_flag flag;
/** @brief Corresponding safety error flag */
volatile struct error_flag *flag_ptr;
/** @brief Dummy value. This seeds the CRC */
uint32_t end_dummy;
};
/**
* @brief Safety controller internal struct implementing an error flag
*/
struct error_flag {
const char *name;
enum safety_flag flag;
bool error_state;
bool error_state_inv;
volatile struct safety_persistency *persistency;
volatile struct safety_weight *weight;
uint32_t key;
/** @brief Name of the error flag */
const char *name;
/** @brief Enum value of this safety flag */
enum safety_flag flag;
/** @brief The flag's state. True is errorneous. */
bool error_state;
/** @brief Not the flag's state. This always has to be inverted to @ref error_flag::error_state */
bool error_state_inv;
/** @brief Persistence entry of this flag */
volatile struct safety_persistence *persistence;
/** @brief Weight entry of this flag */
volatile struct safety_weight *weight;
/** @brief Key needed to remove this safety flag. If key == 0, no key is set and
* the flag can be cleared by all code
*/
uint32_t key;
};
struct timing_mon {
const char *name;
enum timing_monitor monitor;
enum safety_flag associated_flag;
uint64_t min_delta;
uint64_t max_delta;
uint64_t last;
uint64_t calculated_delta;
bool enabled;
const char *name;
enum timing_monitor monitor;
enum safety_flag associated_flag;
uint64_t min_delta;
uint64_t max_delta;
uint64_t last;
uint64_t calculated_delta;
bool enabled;
};
struct analog_mon {
const char *name;
enum analog_value_monitor monitor;
enum safety_flag associated_flag;
float min;
float max;
float value;
bool valid;
uint64_t timestamp;
const char *name;
enum analog_value_monitor monitor;
enum safety_flag associated_flag;
float min;
float max;
float value;
bool valid;
uint64_t timestamp;
};
struct overtemp_config {
uint32_t crc_dummy_seed;
float overtemp_deg_celsius;
float overtemp_equiv_resistance;
uint32_t crc;
};
/**
* @brief All safety error flags.
*/
static volatile struct error_flag IN_SECTION(.ccm.data) flags[] = {
ERR_FLAG_ENTRY(ERR_FLAG_MEAS_ADC_OFF),
ERR_FLAG_ENTRY(ERR_FLAG_MEAS_ADC_WATCHDOG),
@@ -109,31 +174,161 @@ static volatile struct error_flag IN_SECTION(.ccm.data) flags[] = {
ERR_FLAG_ENTRY(ERR_FLAG_TIMING_MAIN_LOOP),
ERR_FLAG_ENTRY(ERR_FLAG_SAFETY_MEM_CORRUPT),
ERR_FLAG_ENTRY(ERR_FLAG_SAFETY_TAB_CORRUPT),
ERR_FLAG_ENTRY(ERR_FLAG_AMON_SUPPLY_VOLT),
ERR_FLAG_ENTRY(ERR_FLAG_OVERTEMP),
};
/**
* @brief All timing monitors
*/
static volatile struct timing_mon IN_SECTION(.ccm.data) timings[] = {
TIM_MON_ENTRY(ERR_TIMING_PID, 2, 1000, ERR_FLAG_TIMING_PID),
TIM_MON_ENTRY(ERR_TIMING_PID, 2, 5000, ERR_FLAG_TIMING_PID),
TIM_MON_ENTRY(ERR_TIMING_MEAS_ADC, 0, 50, ERR_FLAG_TIMING_MEAS_ADC),
TIM_MON_ENTRY(ERR_TIMING_SAFETY_ADC, 10, SAFETY_CONTROLLER_ADC_DELAY_MS + 1000, ERR_FLAG_SAFETY_ADC),
TIM_MON_ENTRY(ERR_TIMING_MAIN_LOOP, 0, 1000, ERR_FLAG_TIMING_MAIN_LOOP),
};
/**
* @brief All analog value monitors
*/
static volatile struct analog_mon IN_SECTION(.ccm.data) analog_mons[] = {
ANA_MON_ENTRY(ERR_AMON_VREF, SAFETY_ADC_VREF_MVOLT - SAFETY_ADC_VREF_TOL_MVOLT,
SAFETY_ADC_VREF_MVOLT + SAFETY_ADC_VREF_TOL_MVOLT, ERR_FLAG_AMON_VREF),
ANA_MON_ENTRY(ERR_AMON_UC_TEMP, SAFETY_ADC_TEMP_LOW_LIM, SAFETY_ADC_TEMP_HIGH_LIM,
ERR_FLAG_AMON_UC_TEMP),
ERR_FLAG_AMON_UC_TEMP),
ANA_MON_ENTRY(ERR_AMON_SUPPLY_VOLT, SAFETY_ADC_SUPPLY_MVOLT - SAFETY_ADC_SUPPLY_TOL_MVOLT,
SAFETY_ADC_SUPPLY_MVOLT + SAFETY_ADC_SUPPLY_TOL_MVOLT,
ERR_FLAG_AMON_SUPPLY_VOLT),
};
/**
* @brief The default flag weights, that are loaded on boot.
*/
static const struct safety_weight default_flag_weights[] = { SAFETY_CONFIG_DEFAULT_WEIGHTS };
static const struct safety_persistency default_flag_persistencies[] = {SAFETY_CONFIG_DEFAULT_PERSIST};
static volatile struct safety_persistency IN_SECTION(.ccm.bss) flag_persistencies[COUNT_OF(default_flag_persistencies)];
/**
* @brief The default flag persistencies, that are loaded on boot.
*/
static const struct safety_persistence default_flag_persistencies[] = {SAFETY_CONFIG_DEFAULT_PERSIST};
/**
* @brief The working copy of the flag persistence table. It is protected by the @ref flag_persistencies_crc
* @note This is stored in CCM RAM
*/
static volatile struct safety_persistence IN_SECTION(.ccm.bss) flag_persistencies[COUNT_OF(default_flag_persistencies)];
/**
* @brief The CRC of the flag weight table @ref flag_persistencies.
*
* The CRC is calculated using the internal CRC module of the STM32F407 controller.
* See the refernece manual for the polynomial.
*
* @note This is stored in CCM RAM.
*/
static uint32_t IN_SECTION(.ccm.bss) flag_persistencies_crc;
/**
* @brief The working copy of the flag weight table. It is protected by the @ref flag_weight_crc.
* @note This is stored in CCM RAM
*/
static volatile struct safety_weight IN_SECTION(.ccm.bss) flag_weights[COUNT_OF(default_flag_weights)];
/**
* @brief The CRC of the flag weight table @ref flag_weights.
*
* The CRC is calculated using the internal CRC module of the STM32F407 controller.
* See the refernece manual for the polynomial.
*
* @note This is stored in CCM RAM.
*/
static uint32_t IN_SECTION(.ccm.bss) flag_weight_crc;
/**
* @brief Configuration struct containing the overtemperature flag configuration
*/
static struct overtemp_config IN_SECTION(.ccm.bss) safety_controller_overtemp_config;
/**
* @brief Configure the overtemperature flag's settings
* @param over_temperature Temperature to set the limit to.
*/
static void set_overtemp_config(float over_temperature)
{
int result;
float resistance;
result = temp_convertet_convert_temp_to_resistance(over_temperature, &resistance);
/* An error in this function is really bad... */
if (result < -1)
panic_mode();
crc_unit_reset();
safety_controller_overtemp_config.crc_dummy_seed = 0xA4F5C7E6UL;
safety_controller_overtemp_config.overtemp_deg_celsius = over_temperature;
safety_controller_overtemp_config.overtemp_equiv_resistance = resistance;
crc_unit_input_array((const uint32_t *)&safety_controller_overtemp_config, wordsize_of(struct overtemp_config) - 1);
safety_controller_overtemp_config.crc = crc_unit_get_crc();
}
static bool over_temperature_config_check(void)
{
if (safety_controller_overtemp_config.crc_dummy_seed != 0xA4F5C7E6UL)
return true;
crc_unit_reset();
crc_unit_input_array((const uint32_t *)&safety_controller_overtemp_config, wordsize_of(struct overtemp_config) - 1);
if (crc_unit_get_crc() != safety_controller_overtemp_config.crc)
return true;
return false;
}
/**
* @brief Convert a flag enum to the flag number.
*
* Flag numbers are used by the error memory to store flags.
* This function will fail and return 0xFF if multiple flags are ORed and
* passed as flag parameter.
*
* @param flag Flag enum
* @return Flag number or 0xFF in case of an error
*/
static uint8_t flag_enum_to_flag_no(enum safety_flag flag)
{
uint32_t flag_mask;
uint8_t i;
if (!is_power_of_two(flag))
return 0xFF;
flag_mask = (uint32_t)flag;
for (i = 0; i < 32; i++) {
if ((flag_mask >> i) & 0x1U)
break;
}
return i;
}
/**
* @brief Convert a safety flag's number to its enum value.
*
* Flag numbers are used by the error memory to store flags.
*
* @param no The flag number.
* @return Flag enum
*/
static enum safety_flag flag_no_to_flag_enum(uint8_t no)
{
if (no >= COUNT_OF(flags))
return ERR_FLAG_NO_FLAG;
return (1U << no);
}
/**
* @brief Check the CRC chacksum of the flag weight table
* @return 0 if CRC is valid, else -1;
*/
static int flag_weight_table_crc_check(void)
{
/* Check the flag weight table */
@@ -146,7 +341,11 @@ static int flag_weight_table_crc_check(void)
return 0;
}
static int flag_persistency_table_crc_check(void)
/**
* @brief Check the CRC chacksum of the flag persistence table
* @return 0 if CRC is valid, else -1.
*/
static int flag_persistence_table_crc_check(void)
{
crc_unit_reset();
crc_unit_input_array((uint32_t*)flag_persistencies, wordsize_of(flag_persistencies));
@@ -157,6 +356,15 @@ static int flag_persistency_table_crc_check(void)
return 0;
}
/**
* @brief Find the error flag structure for a given safety_flag enum
*
* Only one flag can be given at a time. Giving multiple flags by ORing them
* together, will not math any flag at all.
*
* @param flag Enum defining the flag.
* @return NULL in case nothing matched. Pointer otherwise.
*/
static volatile struct error_flag *find_error_flag(enum safety_flag flag)
{
uint32_t i;
@@ -194,20 +402,26 @@ static void init_safety_flag_weight_table_from_default(void)
flag_weight_crc = crc_unit_get_crc();
}
/**
* @brief Initialize the default persistence settings of all safety flags.
*
* This function copies the default persistence settings of the safety flags defined in
* @ref SAFETY_CONFIG_DEFAULT_PERSIST and computes the protection CRC over the settings.
*/
static void init_safety_flag_persistencies_from_default(void)
{
uint32_t index;
volatile struct safety_persistency *current_persistency;
volatile struct safety_persistence *current_persistence;
/* Copy values */
memcpy((void *)flag_persistencies, default_flag_persistencies, sizeof(flag_persistencies));
/* Fill in flag pointers */
for (index = 0; index < COUNT_OF(flag_persistencies); index++) {
current_persistency = &flag_persistencies[index];
current_persistency->flag_ptr = find_error_flag(current_persistency->flag);
if (current_persistency->flag_ptr)
current_persistency->flag_ptr->persistency = current_persistency;
current_persistence = &flag_persistencies[index];
current_persistence->flag_ptr = find_error_flag(current_persistence->flag);
if (current_persistence->flag_ptr)
current_persistence->flag_ptr->persistence = current_persistence;
}
crc_unit_reset();
@@ -215,6 +429,69 @@ static void init_safety_flag_persistencies_from_default(void)
flag_persistencies_crc = crc_unit_get_crc();
}
/**
* @brief Apply the config overrrides stored in the safety memory.
*
* The config overrides are read from the safety memory and applied.
* The config overrides can override the following things:
*
* 1) Safety Weights (See @ref config_weight)
* 2) Flag Persistence
*/
static void apply_config_overrides(void)
{
uint32_t count;
uint32_t idx;
struct config_override override;
int res;
enum safety_flag flag_enum;
volatile struct error_flag *flag;
res = safety_memory_get_config_override_count(&count);
if (res)
return;
for (idx = 0; idx < count; idx++) {
res = safety_memory_get_config_override(idx, &override);
if (res)
continue;
switch (override.type) {
case SAFETY_MEMORY_CONFIG_OVERRIDE_WEIGHT:
flag_enum = flag_no_to_flag_enum(override.entry.weight_override.flag);
flag = find_error_flag(flag_enum);
if (flag && flag->weight) {
flag->weight->weight = override.entry.weight_override.weight;
}
break;
case SAFETY_MEMORY_CONFIG_OVERRIDE_PERSISTENCE:
flag_enum = flag_no_to_flag_enum(override.entry.persistence_override.flag);
flag = find_error_flag(flag_enum);
if (flag && flag->persistence) {
flag->persistence->persistence = override.entry.persistence_override.persistence;
}
break;
default:
continue;
}
}
/* Patch new CRCs */
crc_unit_reset();
crc_unit_input_array((uint32_t *)flag_persistencies, wordsize_of(flag_persistencies));
flag_persistencies_crc = crc_unit_get_crc();
crc_unit_reset();
crc_unit_input_array((uint32_t*)flag_weights, wordsize_of(flag_weights));
flag_weight_crc = crc_unit_get_crc();
}
/**
* @brief Get the error state of a flag.
*
* This function takes inbto account that the error_flag::error_state and
* error_flag::error_state_inv fileds must never be the same value. In case they are,
* the flag is treated as errorneous.
* @param flag Flag to check
* @return The error state
*/
static bool error_flag_get_status(const volatile struct error_flag *flag)
{
if (!flag)
@@ -227,6 +504,11 @@ static bool error_flag_get_status(const volatile struct error_flag *flag)
}
}
/**
* @brief Find a analog value monitor structure by its enum number
* @param mon Enum representing the analog monitor
* @return NULL incase nothing is found, else pointer to structure.
*/
static volatile struct analog_mon *find_analog_mon(enum analog_value_monitor mon)
{
uint32_t i;
@@ -240,6 +522,11 @@ static volatile struct analog_mon *find_analog_mon(enum analog_value_monitor mon
return ret;
}
/**
* @brief Find a timing monitor structure by its enum number
* @param mon Enum representing the timing monitor
* @return NULL incase nothing is found, else pointer to structure.
*/
static volatile struct timing_mon *find_timing_mon(enum timing_monitor mon)
{
uint32_t i;
@@ -253,6 +540,9 @@ static volatile struct timing_mon *find_timing_mon(enum timing_monitor mon)
return ret;
}
/**
* @brief Check the active timing monitors and set the appropriate flags in case of an error.
*/
static void safety_controller_process_active_timing_mons()
{
uint32_t i;
@@ -271,52 +561,57 @@ static void safety_controller_process_active_timing_mons()
}
}
static void safety_controller_process_monitor_checks()
/**
* @brief safety_controller_process_monitor_checks
* Process the analog and timing monitors and set the relevant flags in case of a monitor outside its limits.
* Furthermore, the PT1000 resistance is checked for overtemperature
*
* The checking of the analog monitors will only be armed after a startup delay of 1000 ms to allow the values to stabilize.
*/
static void safety_controller_process_monitor_checks(void)
{
static bool startup_completed = false;
enum analog_monitor_status amon_state;
float amon_value;
struct analog_monitor_info amon_info;
uint32_t idx;
uint32_t analog_mon_count;
float pt1000_val = 1000000.0f;
if (!startup_completed && systick_get_global_tick() >= 1000)
startup_completed = true;
if (startup_completed) {
amon_state = safety_controller_get_analog_mon_value(ERR_AMON_VREF, &amon_value);
if (amon_state != ANALOG_MONITOR_OK)
safety_controller_report_error(ERR_FLAG_AMON_VREF);
amon_state = safety_controller_get_analog_mon_value(ERR_AMON_UC_TEMP, &amon_value);
if (amon_state != ANALOG_MONITOR_OK)
safety_controller_report_error(ERR_FLAG_AMON_UC_TEMP);
analog_mon_count = safety_controller_get_analog_monitor_count();
for (idx = 0; idx < analog_mon_count; idx++) {
if (safety_controller_get_analog_mon_by_index(idx, &amon_info)) {
panic_mode();
}
if (amon_info.status != ANALOG_MONITOR_OK) {
safety_controller_report_error(amon_info.associated_flag);
}
}
}
adc_pt1000_get_current_resistance(&pt1000_val);
if (pt1000_val > safety_controller_overtemp_config.overtemp_equiv_resistance) {
safety_controller_report_error(ERR_FLAG_OVERTEMP);
}
safety_controller_process_active_timing_mons();
}
static uint8_t flag_enum_to_flag_no(enum safety_flag flag)
{
uint32_t flag_mask;
uint8_t i;
if (!is_power_of_two(flag))
return 0xFF;
flag_mask = (uint32_t)flag;
for (i = 0; i < 32; i++) {
if ((flag_mask >> i) & 0x1U)
break;
}
return i;
}
static enum safety_flag flag_no_to_flag_enum(uint8_t no)
{
if (no >= COUNT_OF(flags))
return ERR_FLAG_NO_FLAG;
return (1U << no);
}
/**
* @brief Internal function for setting an error flag
*
* Multiple flags can be ored together to set them in one go.
* The provided key will be set on all of the flags in order to prevent them from being reset by
* unauthorized code. If nop key shall be used, set key to zero.
*
* @param flag Enum of the flags to set. This can be an ORed value of multiple error flags.
* @param key The kex to set on the flag.
* @param prevent_error_mem_enty Prevent the flag from being stored in the error memory.
* @return 0 if successful.
*/
static int report_error(enum safety_flag flag, uint32_t key, bool prevent_error_mem_enty)
{
uint32_t i;
@@ -332,7 +627,8 @@ static int report_error(enum safety_flag flag, uint32_t key, bool prevent_error_
flags[i].error_state_inv = !flags[i].error_state;
flags[i].key = key;
if (check_flag_persistent(&flags[i]) && !old_state && !prevent_error_mem_enty) {
if ((check_flag_persistent(&flags[i]) && !old_state && !prevent_error_mem_enty) ||
get_flag_weight(&flags[i]) == SAFETY_FLAG_CONFIG_WEIGHT_PANIC) {
err_mem_entry.counter = 1;
err_mem_entry.flag_num = flag_enum_to_flag_no(flags[i].flag);
err_mem_entry.type = SAFETY_MEMORY_ERR_ENTRY_FLAG;
@@ -342,6 +638,10 @@ static int report_error(enum safety_flag flag, uint32_t key, bool prevent_error_
} else {
ret = 0;
}
flag &= ~flags[i].flag;
if (!flag)
break;
}
}
@@ -401,7 +701,7 @@ void safety_controller_report_analog_value(enum analog_value_monitor monitor, fl
* @param flags Flags read from error memory
* @return 0 if ok, != 0 if error
*/
static enum safety_flag get_safety_flags_from_error_mem(enum safety_flag *flags)
static int get_safety_flags_from_error_mem(enum safety_flag *flags)
{
uint32_t count;
uint32_t idx;
@@ -427,10 +727,32 @@ static enum safety_flag get_safety_flags_from_error_mem(enum safety_flag *flags)
return 0;
}
/**
* @brief Initialize the GPIOs for the external hardware watchdog.
*
* The external harware watchdog has to be periodically reset or it will reset hte controller.
* Because debugging is not possible, when the watchdog is active, it is only activated, if the application is
* compiled in release mode. Any interruption of the main programm will then trigger the internal and/or the external watchdog.
*
* @note When enabled, execute the @ref external_watchdog_toggle function to reset the external watchdog.
*/
static void safety_controller_init_external_watchdog()
{
rcc_manager_enable_clock(&RCC->AHB1ENR, BITMASK_TO_BITNO(SAFETY_EXT_WATCHDOG_RCC_MASK));
SAFETY_EXT_WATCHDOG_PORT->MODER &= MODER_DELETE(SAFETY_EXT_WATCHDOG_PIN);
#ifndef DEBUGBUILD
SAFETY_EXT_WATCHDOG_PORT->MODER |= OUTPUT(SAFETY_EXT_WATCHDOG_PIN);
SAFETY_EXT_WATCHDOG_PORT->ODR |= (1<<SAFETY_EXT_WATCHDOG_PIN);
#endif
__DSB();
}
void safety_controller_init()
{
enum safety_memory_state found_memory_state;
enum safety_flag flags_in_err_mem = ERR_FLAG_NO_FLAG;
enum hw_revision hw_rev;
int res;
/* Init the safety memory */
@@ -444,8 +766,18 @@ void safety_controller_init()
stack_check_init_corruption_detect_area();
hw_rev = get_pcb_hardware_version();
if (hw_rev == HW_REV_ERROR)
panic_mode();
if (hw_rev != HW_REV_V1_2)
safety_controller_init_external_watchdog();
init_safety_flag_weight_table_from_default();
init_safety_flag_persistencies_from_default();
apply_config_overrides();
/* Set the default limit of the overtemperature check */
set_overtemp_config(SAFETY_DEFAULT_OVERTEMP_LIMIT_DEGC);
if (found_memory_state == SAFETY_MEMORY_INIT_CORRUPTED)
safety_controller_report_error(ERR_FLAG_SAFETY_MEM_CORRUPT);
@@ -465,7 +797,7 @@ void safety_controller_init()
safety_adc_init();
watchdog_setup(WATCHDOG_PRESCALER);
if (watchdog_check_reset_source())
if (rcc_manager_get_reset_cause(false) & RCC_RESET_SOURCE_IWDG)
safety_controller_report_error(ERR_FLAG_WTCHDG_FIRED);
#ifdef DEBUGBUILD
@@ -473,6 +805,15 @@ void safety_controller_init()
#endif
}
/**
* @brief Check the processor's stack
*
* This function checks the Stack of the application.
* The check consists of 2 parts:
*
* 1) Checking the remaining free space at the moment between stack pointer and top of heap.
* 2) Checking The CRC of the corruption detect area between heap and stack
*/
static void safety_controller_check_stack()
{
int32_t free_stack;
@@ -486,52 +827,80 @@ static void safety_controller_check_stack()
}
}
/**
* @brief Handle the Safety ADC
*
* This function handles the safety ADC.
* If the safety ADC ius not executing a measurment and the time since the last measurement has
* passed @ref SAFETY_CONTROLLER_ADC_DELAY_MS, the safety ADC is retriggered and will automatically perform a measurement
* on all of its channels.
* When called again, this function will retrieve the data from the safety ADC and converts it into the
* appropriate analog values for the analog value monitors.
*
* The safety ADC is configured to perform multiple measurmeents of each physical channel. Therefore, this function
* fist calculated the mean value before converting them into the physical values.
*
* The channels, the ssafety ADC will convert is defined in its header file using the define @ref SAFETY_ADC_CHANNELS.
*/
static void safety_controller_handle_safety_adc()
{
static enum safety_adc_meas_channel current_channel = SAFETY_ADC_MEAS_TEMP;
static uint64_t last_result_timestamp = 0;
const uint16_t *channels;
uint32_t sum;
int poll_result;
uint16_t result;
float analog_value;
poll_result = safety_adc_poll_result(&result);
if (!systick_ticks_have_passed(last_result_timestamp, SAFETY_CONTROLLER_ADC_DELAY_MS) && poll_result != 1)
return;
poll_result = safety_adc_poll_result();
if (poll_result) {
if (poll_result == -1) {
switch (current_channel) {
case SAFETY_ADC_MEAS_TEMP:
current_channel = SAFETY_ADC_MEAS_VREF;
break;
case SAFETY_ADC_MEAS_VREF:
/* Expected fallthru */
default:
current_channel = SAFETY_ADC_MEAS_TEMP;
break;
}
safety_adc_trigger_meas(current_channel);
} else if (poll_result == 1) {
last_result_timestamp = systick_get_global_tick();
analog_value = safety_adc_convert_channel(current_channel, result);
safety_controller_report_timing(ERR_TIMING_SAFETY_ADC);
switch (current_channel) {
case SAFETY_ADC_MEAS_TEMP:
safety_controller_report_analog_value(ERR_AMON_UC_TEMP, analog_value);
break;
case SAFETY_ADC_MEAS_VREF:
safety_controller_report_analog_value(ERR_AMON_VREF, analog_value);
break;
default:
safety_controller_report_error(ERR_FLAG_SAFETY_ADC);
break;
}
}
if (poll_result == 1) {
/* Data available */
channels = safety_adc_get_values();
/* Compute average of temp readings */
sum = channels[0] + channels[1] + channels[2] + channels[3];
sum /= 4;
analog_value = safety_adc_convert_channel(SAFETY_ADC_MEAS_TEMP, (uint16_t)sum);
safety_controller_report_analog_value(ERR_AMON_UC_TEMP, analog_value);
/* Average VREF readings */
sum = channels[4] + channels[5] + channels[6] + channels[7];
sum /= 4;
analog_value = safety_adc_convert_channel(SAFETY_ADC_MEAS_VREF, (uint16_t)sum);
safety_controller_report_analog_value(ERR_AMON_VREF, analog_value);
/* Compute supply voltage reading */
sum = channels[8] + channels[9] + channels[10] + channels[11];
sum /= 4;
analog_value = safety_adc_convert_channel(SAFETY_ADC_MEAS_SUPPLY, (uint16_t)sum);
safety_controller_report_analog_value(ERR_AMON_SUPPLY_VOLT, analog_value);
last_result_timestamp = systick_get_global_tick();
safety_controller_report_timing(ERR_TIMING_SAFETY_ADC);
}
if (systick_ticks_have_passed(last_result_timestamp, SAFETY_CONTROLLER_ADC_DELAY_MS)) {
if (poll_result != 1 && poll_result != 0)
safety_adc_trigger_meas();
}
}
/**
* @brief Check the memory structures.
* @brief Check the memory structures
*
* This function checks multiple memory structures.
*
* 1) The safety memory in the backup RAM is checked using @ref safety_memory_check.
* In case of an error, the safety memory is reinitialized and the @ref ERR_FLAG_SAFETY_MEM_CORRUPT
* flag is set.
* 2) The flag weight table is CRC checked. In case of an error, the @ref ERR_FLAG_SAFETY_TAB_CORRUPT flag is set.
* Aditionally, the default flag weights are restored from Flash.
* 3) The flag persistence table is CRC checked. In case of an error, the @ref ERR_FLAG_SAFETY_TAB_CORRUPT flag is set.
* Aditionally, the default values of the flag persistence is restored from Flash.
* 4) Check the Overtemperature flag configuration structure
*/
static void safety_controller_handle_memory_checks(void)
{
@@ -555,14 +924,26 @@ static void safety_controller_handle_memory_checks(void)
init_safety_flag_weight_table_from_default();
}
/* If persistency table is broken, reinit to default and set flag */
if(flag_persistency_table_crc_check()) {
/* If persistence table is broken, reinit to default and set flag */
if(flag_persistence_table_crc_check()) {
safety_controller_report_error(ERR_FLAG_SAFETY_TAB_CORRUPT);
init_safety_flag_persistencies_from_default();
}
/* check overtemp struct */
if (over_temperature_config_check()) {
safety_controller_report_error(ERR_FLAG_SAFETY_TAB_CORRUPT);
set_overtemp_config(SAFETY_DEFAULT_OVERTEMP_LIMIT_DEGC);
}
}
}
/**
* @brief Check if the systick is ticking.
*
* If the systick stays constant for more than 1000 calls of this function,
* the @ref ERR_FLAG_SYSTICK flag is set.
*/
static void safety_controller_do_systick_checking()
{
static uint64_t last_systick;
@@ -580,34 +961,59 @@ static void safety_controller_do_systick_checking()
last_systick = systick;
}
/**
* @brief Handle weightet flags.
*
* This functions loops over all error flags and checks the weights. If a flag
* is set, the appropriate action defined by the flag weight is executed.
* @note If no flag weigth is present for a given error flag, it is treated as the most critical category
* (@ref SAFETY_FLAG_CONFIG_WEIGHT_PANIC)
*/
static void safety_controller_handle_weighted_flags()
{
uint32_t weight_index;
volatile struct safety_weight *current_weight;
uint32_t flag_index;
volatile struct error_flag *current_flag;
enum config_weight flag_weigth;
for (weight_index = 0; weight_index < COUNT_OF(flag_weights); weight_index++) {
current_weight = &flag_weights[weight_index];
if (error_flag_get_status(current_weight->flag_ptr)) {
switch (current_weight->weight) {
case SAFETY_FLAG_CONFIG_WEIGHT_NONE:
break;
case SAFETY_FLAG_CONFIG_WEIGHT_PID:
oven_pid_abort();
break;
case SAFETY_FLAG_CONFIG_WEIGHT_PANIC:
/* Expected fallthrough */
default:
oven_pid_abort();
panic_mode();
break;
}
for (flag_index = 0u; flag_index < COUNT_OF(flags); flag_index++) {
current_flag = &flags[flag_index];
/* Continue if this flag is not set */
if (!error_flag_get_status(current_flag)) {
continue;
}
flag_weigth = get_flag_weight(current_flag);
switch (flag_weigth) {
case SAFETY_FLAG_CONFIG_WEIGHT_NONE:
break;
case SAFETY_FLAG_CONFIG_WEIGHT_PID:
oven_pid_abort();
break;
case SAFETY_FLAG_CONFIG_WEIGHT_PANIC:
/* EXPECTED FALLTHRU */
default:
oven_pid_abort();
panic_mode();
break;
}
}
}
#ifndef DEBUGBUILD
static void external_watchdog_toggle()
{
SAFETY_EXT_WATCHDOG_PORT->ODR ^= (1<<SAFETY_EXT_WATCHDOG_PIN);
}
#endif
int safety_controller_handle()
{
int ret = 0;
#ifndef DEBUGBUILD
static uint32_t watchdog_counter = 0UL;
#endif
safety_controller_check_stack();
safety_controller_handle_safety_adc();
@@ -618,6 +1024,15 @@ int safety_controller_handle()
ret |= watchdog_ack(WATCHDOG_MAGIC_KEY);
#ifndef DEBUGBUILD
if (get_pcb_hardware_version() != HW_REV_V1_2) {
watchdog_counter++;
if (watchdog_counter > 30) {
external_watchdog_toggle();
watchdog_counter = 0UL;
}
}
#endif
return (ret ? -1 : 0);
}
@@ -832,6 +1247,7 @@ int safety_controller_get_analog_mon_by_index(uint32_t index, struct analog_moni
info->min = mon->min;
info->value = mon->value;
info->timestamp = mon->timestamp;
info->associated_flag = mon->associated_flag;
if (!mon->valid) {
info->status = ANALOG_MONITOR_INACTIVE;
@@ -869,4 +1285,15 @@ int safety_controller_get_timing_mon_by_index(uint32_t index, struct timing_moni
return 0;
}
int safety_controller_set_overtemp_limit(float over_temperature)
{
set_overtemp_config(over_temperature);
return 0;
}
float safety_controller_get_overtemp_limit(void)
{
return safety_controller_overtemp_config.overtemp_deg_celsius;
}
/** @} */

View File

@@ -477,11 +477,136 @@ return_value:
return ret;
}
int safety_memory_insert_config_override(struct config_override *config_override);
int safety_memory_get_config_override_count(uint32_t *count);
static uint32_t convert_config_override_to_word(const struct config_override *conf_override)
{
uint32_t data = 0;
int safety_memory_get_config_override(uint32_t idx, struct config_override *config_override);
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)
{

View File

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

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

View File

@@ -136,9 +136,11 @@ int sd_card_settings_try_load_calibration(float *sens_deviation, float *offset)
get_controller_settings_path(path, sizeof(path), "calibration");
p = config_parser_open_file(&parser, false, path, workbuff, sizeof(workbuff));
status = 0;
if (!p)
return status;
do {
res = config_parser_get_line(p, &entry);
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;
@@ -149,10 +151,7 @@ int sd_card_settings_try_load_calibration(float *sens_deviation, float *offset)
}
}
} while (res != CONFIG_PARSER_END_REACHED &&
res != CONFIG_PARSER_GENERIC_ERR &&
res != CONFIG_PARSER_IOERR &&
res != CONFIG_PARSER_PARAM_ERR);
} while (!config_parser_ret_is_abort_condition(res));
config_parser_close_file(p);
@@ -161,3 +160,59 @@ int sd_card_settings_try_load_calibration(float *sens_deviation, float *offset)
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;
}
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;
}

View File

@@ -20,14 +20,74 @@
#include <reflow-controller/settings/settings.h>
#include <reflow-controller/settings/settings-sd-card.h>
#include <reflow-controller/settings/settings-eeprom.h>
#include <reflow-controller/hw-version-detect.h>
bool settings_use_eeprom;
int settings_save_calibration(float sens_deviation, float offset, bool active)
{
/* There is no other configuration location besides the SD card (yet) */
return sd_card_settings_save_calibration(sens_deviation, offset, active);
if (settings_use_eeprom)
return settings_eeprom_save_calibration(sens_deviation, offset, active);
else
return sd_card_settings_save_calibration(sens_deviation, offset, active);
}
int settings_load_calibration(float *sens_dev, float *offset)
{
return sd_card_settings_try_load_calibration(sens_dev, offset);
bool active;
int res;
if (settings_use_eeprom) {
res =settings_eeprom_load_calibration(sens_dev, offset, &active);
if (!res && !active)
res = -1;
} else {
res = -1;
}
if (res)
res = sd_card_settings_try_load_calibration(sens_dev, offset);
return res;
}
enum settings_load_result settings_load_pid_oven_parameters(struct oven_pid_settings *settings)
{
return sd_card_settings_load_pid_oven_parameters(settings);
}
void settings_setup(void)
{
if (get_pcb_hardware_version() >= HW_REV_V1_3)
settings_use_eeprom = settings_eeprom_detect_and_prepare();
else
settings_use_eeprom = false;
}
enum settings_load_result settings_load_overtemp_limit(float *over_temp_limit)
{
int res;
float tmp;
if (!over_temp_limit)
return SETT_LOAD_ERR;
if (!settings_use_eeprom)
return SETT_LOAD_FILE_NOT_FOUND;
res = settings_eeprom_load_overtemp_limit(&tmp);
if (res)
return SETT_LOAD_DISK_ERR;
*over_temp_limit = tmp;
return SETT_LOAD_SUCCESS;
}
int settings_save_overtemp_limit(float over_temp_limit, bool active)
{
if (settings_use_eeprom)
return settings_eeprom_save_overtemp_limit(over_temp_limit, active);
else
return -100;
}

View File

@@ -0,0 +1,228 @@
/* 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/settings/spi-eeprom.h>
#include <stm-periph/spi.h>
#include <reflow-controller/periph-config/spi-eeprom-hwcfg.h>
#include <stm-periph/rcc-manager.h>
#include <stm-periph/stm32-gpio-macros.h>
#include <stddef.h>
#define EEPROM_SIZE 0x200
#define EEPROM_PAGE_SIZE 16
#define EEPROM_STATUS_REG_WIP (0x1U)
#define EEPROM_STATUS_REG_WEL (0x1U << 1)
#define EEPROM_STATUS_REG_BP0 (0x1U << 2)
#define EEPROM_STATUS_REG_BP1 (0x1U << 3)
#define EEPROM_CMD_READ_STATUS 0x05
#define EEPROM_CMD_WRITE_STATUS 0x01
#define EEPROM_CMD_READ_DATA 0x3
#define EEPROM_CMD_WRITE_DATA 0x2
#define EEPROM_CMD_WREN 0x6
#define EEPROM_CMD_WRDI 0x4
static stm_spi_handle eeprom_spi_handle;
static void eeprom_cs_activate(void)
{
SPI_EEPROM_SPI_PORT->ODR &= ~(1<<SPI_EEPROM_CS_PIN);
}
static void eeprom_cs_deactivate(void)
{
SPI_EEPROM_SPI_PORT->ODR |= (1<<SPI_EEPROM_CS_PIN);
}
int spi_eeprom_init()
{
static struct stm_spi_dev spi_dev;
struct stm_spi_settings settings;
rcc_manager_enable_clock(&SPI_EEPROM_SPI_PORT_RCC_REG, BITMASK_TO_BITNO(SPI_EEPROM_SPI_PORT_RCC_MASK));
SPI_EEPROM_SPI_PORT->MODER &= MODER_DELETE(SPI_EEPROM_CS_PIN) & MODER_DELETE(SPI_EEPROM_MISO_PIN) &
MODER_DELETE(SPI_EEPROM_MOSI_PIN) & MODER_DELETE(SPI_EEPROM_SCK_PIN);
SPI_EEPROM_SPI_PORT->MODER |= ALTFUNC(SPI_EEPROM_MISO_PIN) | ALTFUNC(SPI_EEPROM_SCK_PIN) | ALTFUNC(SPI_EEPROM_MOSI_PIN);
SPI_EEPROM_SPI_PORT->MODER |= OUTPUT(SPI_EEPROM_CS_PIN);
SETAF(SPI_EEPROM_SPI_PORT, SPI_EEPROM_MISO_PIN, SPI_EEPROM_SPI_ALTFUNC_NO);
SETAF(SPI_EEPROM_SPI_PORT, SPI_EEPROM_MOSI_PIN, SPI_EEPROM_SPI_ALTFUNC_NO);
SETAF(SPI_EEPROM_SPI_PORT, SPI_EEPROM_SCK_PIN, SPI_EEPROM_SPI_ALTFUNC_NO);
eeprom_cs_deactivate();
settings.cpha = false;
settings.cpol = false;
settings.cs_activate = eeprom_cs_activate;
settings.cs_deactivate = eeprom_cs_deactivate;
settings.master = true;
settings.msb_first = true;
settings.prescaler = SPI_PRSC_DIV16;
eeprom_spi_handle = spi_init(&spi_dev, SPI1, &settings);
if (eeprom_spi_handle)
return 0;
else
return -1;
}
void spi_eeprom_deinit()
{
spi_deinit(eeprom_spi_handle);
SPI_EEPROM_SPI_PORT->MODER &= MODER_DELETE(SPI_EEPROM_CS_PIN) & MODER_DELETE(SPI_EEPROM_MISO_PIN) &
MODER_DELETE(SPI_EEPROM_MOSI_PIN) & MODER_DELETE(SPI_EEPROM_SCK_PIN);
rcc_manager_disable_clock(&SPI_EEPROM_SPI_PORT_RCC_REG, BITMASK_TO_BITNO(SPI_EEPROM_SPI_PORT_RCC_MASK));
}
uint8_t spi_eeprom_read_status_reg(void)
{
uint8_t buff[2] = {0x05, 0x00};
(void)spi_transfer(eeprom_spi_handle, buff, buff, 2, true);
return buff[1];
}
static void spi_eeprom_set_write_enable_latch(bool status)
{
uint8_t cmd;
if (status)
cmd = EEPROM_CMD_WREN;
else
cmd = EEPROM_CMD_WRDI;
(void)spi_transfer(eeprom_spi_handle, &cmd, NULL, 1, true);
}
int spi_eeprom_read(uint32_t addr, uint8_t *rx_buff, uint32_t count)
{
int ret = 0;
int retry = 250;
uint8_t status_reg;
uint8_t cmd[2];
if (!rx_buff || !count)
return -1000;
if (addr >= EEPROM_SIZE)
return -1001;
do {
status_reg = spi_eeprom_read_status_reg();
} while ((status_reg & EEPROM_STATUS_REG_WIP) && retry--);
if (status_reg & EEPROM_STATUS_REG_WIP)
return -1;
cmd[0] = EEPROM_CMD_READ_DATA;
if (addr & (1<<8))
cmd[0] |= (1U<<3);
cmd[1] = (uint8_t)(addr & 0xFF);
eeprom_cs_activate();
spi_transfer(eeprom_spi_handle, cmd, NULL, 2, false);
spi_transfer(eeprom_spi_handle, NULL, rx_buff, count, false);
eeprom_cs_deactivate();
return ret;
}
bool spi_eeprom_write_in_progress(void)
{
uint8_t status_reg;
status_reg = spi_eeprom_read_status_reg();
if (status_reg & EEPROM_STATUS_REG_WIP)
return true;
else
return false;
}
static void spi_eeprom_do_write_page(uint32_t addr, const uint8_t *data, uint8_t len)
{
uint8_t cmd[2];
/* Wait for the previous write to finish */
while (spi_eeprom_write_in_progress());
/* Set the write enable latch */
spi_eeprom_set_write_enable_latch(true);
cmd[0] = EEPROM_CMD_WRITE_DATA | ((addr & (1<<8)) ? (1<<4) : 0);
cmd[1] = (uint8_t)(addr & 0xFF);
eeprom_cs_activate();
spi_transfer(eeprom_spi_handle, cmd, NULL, 2, false);
spi_transfer(eeprom_spi_handle, data, NULL, len, false);
eeprom_cs_deactivate();
}
int spi_eeprom_write(uint32_t addr, const uint8_t *data, uint32_t count)
{
const uint8_t *ptr = data;
uint32_t transfer_len;
if (!data || !count)
return -1000;
if (addr >= EEPROM_SIZE)
return -1001;
while (count > 0) {
/* Calculate size for current page transfer */
transfer_len = EEPROM_PAGE_SIZE - (addr % EEPROM_PAGE_SIZE);
if (transfer_len > count)
transfer_len = count;
spi_eeprom_do_write_page(addr, ptr, transfer_len);
count -= transfer_len;
addr += transfer_len;
ptr += transfer_len;
}
return 0;
}
bool spi_eeprom_check_connected(void)
{
uint8_t status_reg;
/* Try to set write enable latch */
spi_eeprom_set_write_enable_latch(true);
/* Read back status register */
status_reg = spi_eeprom_read_status_reg();
if (!(status_reg & EEPROM_STATUS_REG_WEL))
return false;
/* Clear the latch */
spi_eeprom_set_write_enable_latch(false);
/* Read back status register */
status_reg = spi_eeprom_read_status_reg();
if ((status_reg & EEPROM_STATUS_REG_WEL))
return false;
return true;
}

View File

@@ -40,6 +40,8 @@
#include <reflow-controller/button.h>
#include <reflow-controller/safety/fault.h>
#include <reflow-controller/safety/safety-memory.h>
#include <reflow-controller/hw-version-detect.h>
#include <reflow-controller/temp-profile-executer.h>
#ifndef GIT_VER
#define GIT_VER "VERSION NOT SET"
@@ -48,7 +50,7 @@
extern struct stm_uart shell_uart;
static shellmatta_instance_t shell;
static char shell_buffer[512];
static char IN_SECTION(.ccm.bss) history_buffer[600];
static char IN_SECTION(.ccm.bss) history_buffer[512];
static bool check_opt(const char *args, uint32_t len, const char *opt_to_check)
{
@@ -81,12 +83,28 @@ static shellmatta_retCode_t shell_cmd_ver(const shellmatta_handle_t handle,
uint32_t low_id;
uint32_t mid_id;
uint32_t high_id;
const char *hw_rev_str;
enum hw_revision pcb_rev;
unique_id_get(&high_id, &mid_id, &low_id);
shellmatta_printf(handle, "Reflow Oven Controller Firmware " xstr(GIT_VER) "\r\n"
"Compiled: " __DATE__ " at " __TIME__ "\r\n");
shellmatta_printf(handle, "Serial: %08X-%08X-%08X", high_id, mid_id, low_id);
shellmatta_printf(handle, "Serial: %08X-%08X-%08X\r\n", high_id, mid_id, low_id);
pcb_rev = get_pcb_hardware_version();
switch (pcb_rev) {
case HW_REV_V1_2:
hw_rev_str = "Hardware: v1.2";
break;
case HW_REV_V1_3:
hw_rev_str = "Hardware: v1.3";
break;
default:
hw_rev_str = "Hardware: Unknown Revision. You might have to update the firmware!";
break;
}
shellmatta_printf(handle, "%s", hw_rev_str);
return SHELLMATTA_OK;
}
@@ -317,57 +335,6 @@ static shellmatta_retCode_t shell_cmd_reset(const shellmatta_handle_t handle, co
return SHELLMATTA_BUSY;
}
static shellmatta_retCode_t shell_cmd_cat(const shellmatta_handle_t handle, const char *arguments,
uint32_t length)
{
#ifdef IMPLEMENT_SHELL_CAT
FIL file;
char path_buff[256];
const char *path;
UINT bytes_read;
FRESULT res;
strncpy(path_buff, arguments, MIN(sizeof(path_buff), length));
path_buff[MIN(length, sizeof(path_buff)-1)] = 0;
path = strtok(path_buff, " ");
path = strtok(NULL, " ");
if (strlen(path) == 0) {
shellmatta_printf(handle, "Specify path!\r\n");
return SHELLMATTA_OK;
}
res = f_open(&file, path, FA_READ);
if (res == FR_OK) {
shellmatta_write(handle, "\r\n", 2U);
do {
res = f_read(&file, path_buff, sizeof(path_buff), &bytes_read);
if (bytes_read > 0)
shellmatta_write(handle, path_buff, bytes_read);
else
break;
} while (res == FR_OK);
shellmatta_write(handle, "\r\n", 2U);
}
if (res != FR_OK) {
shellmatta_printf(handle, "Error reading file\r\n");
}
f_close(&file);
#else
(void)length;
(void)arguments;
shellmatta_printf(handle, "cat not implemented!\r\n");
#endif
return SHELLMATTA_OK;
}
static shellmatta_retCode_t shell_cmd_read_flags(const shellmatta_handle_t handle, const char *arguments,
uint32_t length)
{
@@ -386,7 +353,7 @@ static shellmatta_retCode_t shell_cmd_read_flags(const shellmatta_handle_t handl
/* Check for the --ack option */
tryack = check_opt(arguments, length, "--ack");
shellmatta_printf(handle, "Error flags\r\n"
shellmatta_printf(handle, "Error Flags\r\n"
"-----------\r\n");
count = safety_controller_get_flag_count();
@@ -440,7 +407,7 @@ static shellmatta_retCode_t shell_cmd_read_flags(const shellmatta_handle_t handl
}
shellmatta_printf(handle, "\r\nTiming Monitors\r\n"
"--------------\r\n");
"---------------\r\n");
count = safety_controller_get_timing_monitor_count();
@@ -567,8 +534,6 @@ static char *get_safety_mem_dump(size_t *used_bytes)
static shellmatta_retCode_t shell_cmd_dump_safety_mem(const shellmatta_handle_t handle, const char *arguments,
uint32_t length)
{
(void)arguments;
(void)length;
static char *buffer;
static const char *ptr;
size_t used_bytes;
@@ -576,6 +541,45 @@ static shellmatta_retCode_t shell_cmd_dump_safety_mem(const shellmatta_handle_t
static size_t current_line;
size_t remainder;
static const char *hline = "----------------------------------------------------------------";
char string[200];
const char *token;
const char * const token_delim = "\t ";
FRESULT fres;
FIL file;
UINT bw;
/* Check if the dump shall be stored to disk */
strncpy(string, arguments, MIN(sizeof(string), length+1));
string[sizeof(string) - 1] = '\0';
token = strtok(string, token_delim);
token = strtok(NULL, token_delim);
if (token) {
buffer = get_safety_mem_dump(&used_bytes);
if (!buffer) {
shellmatta_printf(handle, "Error generating dump");
return SHELLMATTA_OK;
}
fres = f_open(&file, token, FA_CREATE_NEW | FA_WRITE);
if (fres == FR_EXIST) {
free(buffer);
shellmatta_printf(handle, "File already esists!\r\n");
return SHELLMATTA_OK;
} else if (fres != FR_OK) {
free(buffer);
shellmatta_printf(handle, "Error opening file %s\r\n", token);
return SHELLMATTA_OK;
}
fres = f_write(&file, buffer, used_bytes - 1, &bw);
if (fres != FR_OK) {
shellmatta_printf(handle, "Error writing to file %s\r\n", token);
}
free(buffer);
f_close(&file);
return SHELLMATTA_OK;
}
if (full_lines == 0) {
shellmatta_printf(handle, "Safety memory content\r\n%s\r\n", hline);
@@ -611,6 +615,158 @@ static shellmatta_retCode_t shell_cmd_dump_safety_mem(const shellmatta_handle_t
return SHELLMATTA_BUSY;
}
shellmatta_retCode_t shell_cmd_reset_cal(const shellmatta_handle_t handle, const char *arguments, uint32_t length)
{
(void)handle;
(void)arguments;
(void)length;
adc_pt1000_set_resistance_calibration(0.0f, 0.0f, false);
return SHELLMATTA_OK;
}
shellmatta_retCode_t shell_cmd_update(const shellmatta_handle_t handle, const char *arguments, uint32_t length)
{
(void)handle;
(void)arguments;
(void)length;
struct safety_memory_boot_status status;
safety_memory_get_boot_status(&status);
status.reboot_to_bootloader = 0xFFFFFFFFUL;
safety_memory_set_boot_status(&status);
NVIC_SystemReset();
return SHELLMATTA_OK;
}
shellmatta_retCode_t shell_cmd_overtemp_cfg(const shellmatta_handle_t handle, const char *args, uint32_t len)
{
float overtemp_lim;
shellmatta_retCode_t ret;
char *argument;
uint32_t arg_length;
char option;
bool temp_passed = false;
bool persistent = false;
(void)args;
(void)len;
static const shellmatta_opt_long_t options[] = {
{"persistent", 'p', SHELLMATTA_OPT_ARG_NONE},
{NULL, '\0', SHELLMATTA_OPT_ARG_NONE},
};
do {
ret = shellmatta_opt_long(handle, options, &option, &argument, &arg_length);
if (ret != SHELLMATTA_OK)
break;
switch (option) {
case 'p':
persistent = true;
break;
case '\0':
temp_passed = true;
overtemp_lim = strtof(argument, NULL);
break;
}
} while (1);
if (temp_passed) {
safety_controller_set_overtemp_limit(overtemp_lim);
if (persistent)
settings_save_overtemp_limit(overtemp_lim, true);
}
overtemp_lim = safety_controller_get_overtemp_limit();
shellmatta_printf(handle, "Overtemperature configured for: %.1f °C\r\n", overtemp_lim);
return ret;
}
shellmatta_retCode_t shell_cmd_execute(const shellmatta_handle_t handle, const char *args, uint32_t len)
{
enum pl_ret_val res;
const struct tpe_current_state *state;
static bool running = false;
char *data;
uint32_t dlen;
(void)args;
(void)len;
int opt_stat;
char option;
char *argument;
uint32_t arg_len;
const char *script_name = NULL;
const shellmatta_opt_long_t options[] = {
{NULL, '\0', SHELLMATTA_OPT_ARG_NONE},
};
while (1) {
opt_stat = shellmatta_opt_long(handle, options, &option, &argument, &arg_len);
if (opt_stat != SHELLMATTA_OK)
break;
switch (option) {
case '\0':
script_name = argument;
break;
default:
break;
}
}
if (!script_name) {
shellmatta_printf(handle, "No script name specified!\r\n");
return SHELLMATTA_ERROR;
}
shellmatta_read(handle, &data, &dlen);
if (!running) {
res = temp_profile_executer_start(script_name);
if (res != PL_RET_SUCCESS) {
switch (res) {
case PL_RET_DISK_ERR:
shellmatta_printf(handle, "Error reading file\r\n");
break;
case PL_RET_LIST_FULL:
shellmatta_printf(handle, "Script too long!\r\n");
break;
case PL_RET_SCRIPT_ERR:
shellmatta_printf(handle, "Error in script\r\n");
break;
default:
shellmatta_printf(handle, "Unspecified error occured\r\n");
break;
}
return SHELLMATTA_ERROR;
}
running = true;
} else {
state = temp_profile_executer_status();
if (state->status != TPE_RUNNING) {
shellmatta_printf(handle, "Profile executed.\r\n");
running = false;
if(state->status == TPE_ABORT) {
shellmatta_printf(handle, "Profile execution aborted!\r\n");
}
return SHELLMATTA_OK;
}
if (dlen > 0 && *data == '\x03') {
temp_profile_executer_stop();
running = false;
return SHELLMATTA_OK;
}
}
return SHELLMATTA_CONTINUE;
}
//typedef struct shellmatta_cmd
//{
// char *cmd; /**< command name */
@@ -620,7 +776,7 @@ static shellmatta_retCode_t shell_cmd_dump_safety_mem(const shellmatta_handle_t
// shellmatta_cmdFct_t cmdFct; /**< pointer to the cmd callack function */
// struct shellmatta_cmd *next; /**< pointer to next command or NULL */
//} shellmatta_cmd_t;
static shellmatta_cmd_t cmd[18] = {
static shellmatta_cmd_t cmd[21] = {
{
.cmd = "version",
.cmdAlias = "ver",
@@ -709,21 +865,13 @@ static shellmatta_cmd_t cmd[18] = {
.cmdFct = shell_cmd_reset,
.next = &cmd[11],
},
{
.cmd = "cat",
.cmdAlias = NULL,
.helpText = "Print file contents",
.usageText = "cat <path>",
.cmdFct = shell_cmd_cat,
.next = &cmd[12],
},
{
.cmd = "safety-flags",
.cmdAlias = "flags",
.helpText = "Reads and may clear safety flags",
.usageText = "flags [--ack]",
.cmdFct = shell_cmd_read_flags,
.next = &cmd[13],
.next = &cmd[12],
},
{
.cmd = "save-calibration",
@@ -731,7 +879,7 @@ static shellmatta_cmd_t cmd[18] = {
.helpText = "",
.usageText = "",
.cmdFct = shell_cmd_save_cal,
.next = &cmd[14],
.next = &cmd[13],
},
{
.cmd = "hang",
@@ -739,7 +887,7 @@ static shellmatta_cmd_t cmd[18] = {
.helpText = "",
.usageText = "",
.cmdFct = shell_cmd_hang,
.next = &cmd[15],
.next = &cmd[14],
},
{
.cmd = "ui-emulate",
@@ -747,7 +895,7 @@ static shellmatta_cmd_t cmd[18] = {
.helpText = "",
.usageText = "",
.cmdFct = shell_cmd_ui_emulation,
.next = &cmd[16],
.next = &cmd[15],
},
{
.cmd = "panic",
@@ -755,16 +903,49 @@ static shellmatta_cmd_t cmd[18] = {
.helpText = "Panic Mode!",
.usageText = "",
.cmdFct = shell_cmd_panic,
.next = &cmd[17],
.next = &cmd[16],
},
{
.cmd = "safety-mem-dump",
.cmdAlias = NULL,
.helpText = "",
.usageText = "",
.usageText = "safety-mem-dump [output-file]",
.cmdFct = shell_cmd_dump_safety_mem,
.next = NULL,
.next = &cmd[17],
},
{
.cmd = "reset-cal",
.cmdAlias = NULL,
.helpText = "Reset Calibration",
.usageText = "",
.cmdFct = shell_cmd_reset_cal,
.next = &cmd[18],
},
{
.cmd = "update",
.cmdAlias = NULL,
.helpText = "Update Firmware",
.usageText = "",
.cmdFct = shell_cmd_update,
.next = &cmd[19],
},
{
.cmd = "overtemp",
.cmdAlias = NULL,
.helpText = "Overtemperature Config",
.usageText = "",
.cmdFct = shell_cmd_overtemp_cfg,
.next = &cmd[20],
},
{
.cmd = "execute",
.cmdAlias = NULL,
.helpText = "Execute Temp Profile",
.usageText = "execute /path/to/script.tpr",
.cmdFct = shell_cmd_execute,
.next = NULL,
}
};
shellmatta_handle_t shell_init(shellmatta_write_t write_func)

View File

@@ -19,7 +19,7 @@
*/
#include <stm-periph/backup-ram.h>
#include <stm-periph/clock-enable-manager.h>
#include <stm-periph/rcc-manager.h>
#include <stm32/stm32f4xx.h>
#include <helper-macros/helper-macros.h>

View File

@@ -19,7 +19,7 @@
*/
#include <stm-periph/crc-unit.h>
#include <stm-periph/clock-enable-manager.h>
#include <stm-periph/rcc-manager.h>
#include <stm32/stm32f4xx.h>
void crc_unit_init(void)

View File

@@ -29,7 +29,7 @@
*/
#include <stm-periph/dma-ring-buffer.h>
#include <stm-periph/clock-enable-manager.h>
#include <stm-periph/rcc-manager.h>
#include <stdbool.h>
#include <string.h>

View File

@@ -18,9 +18,10 @@
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <stm-periph/clock-enable-manager.h>
#include <stm-periph/rcc-manager.h>
#include <helper-macros/helper-macros.h>
#include <stdlib.h>
#include <stm32/stm32f4xx.h>
struct rcc_enable_count {
volatile uint32_t *rcc_reg_addr;
@@ -153,3 +154,30 @@ int rcc_manager_disable_clock(volatile uint32_t *rcc_enable_register, uint8_t bi
return ret_val;
}
enum rcc_reset_source rcc_manager_get_reset_cause(bool clear_flags)
{
enum rcc_reset_source ret = 0;
uint32_t rcc_csr;
rcc_csr = RCC->CSR;
if (rcc_csr & RCC_CSR_LPWRRSTF)
ret |= RCC_RESET_SOURCE_LOW_POWER;
if (rcc_csr & RCC_CSR_WWDGRSTF)
ret |= RCC_RESET_SOURCE_WWD;
if (rcc_csr & RCC_CSR_WDGRSTF)
ret |= RCC_RESET_SOURCE_IWDG;
if (rcc_csr & RCC_CSR_SFTRSTF)
ret |= RCC_RESET_SOURCE_SOFTWARE;
if (rcc_csr & RCC_CSR_PORRSTF)
ret |= RCC_RESET_SOURCE_POWER_ON;
if (rcc_csr & RCC_CSR_PADRSTF)
ret |= RCC_RESET_SOURCE_PIN;
if (rcc_csr & RCC_CSR_BORRSTF)
ret |= RCC_RESET_BOR_POR;
if (clear_flags)
RCC->CSR |= RCC_CSR_RMVF;
return ret;
}

View File

@@ -19,7 +19,7 @@
*/
#include <stm-periph/rng.h>
#include <stm-periph/clock-enable-manager.h>
#include <stm-periph/rcc-manager.h>
#include <stm32/stm32f4xx.h>
void random_number_gen_init(bool int_enable)

View File

@@ -0,0 +1,167 @@
/* 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 <stm-periph/spi.h>
#include <helper-macros/helper-macros.h>
#include <stm-periph/rcc-manager.h>
#include <string.h>
#define STM_SPI_DEV_MAGIC 0x5A678F5CUL
struct spi_rcc_lookup_pair {
SPI_TypeDef *spi_regs;
volatile uint32_t *rcc_reg;
uint32_t bit_no;
};
const struct spi_rcc_lookup_pair rcc_lookup[3] = {
{SPI1, &RCC->APB2ENR, BITMASK_TO_BITNO(RCC_APB2ENR_SPI1EN)},
{SPI2, &RCC->APB1ENR, BITMASK_TO_BITNO(RCC_APB1ENR_SPI2EN)},
{SPI3, &RCC->APB1ENR, BITMASK_TO_BITNO(RCC_APB1ENR_SPI3EN)},
};
static const struct spi_rcc_lookup_pair *spi_find_rcc_reg_and_bit(SPI_TypeDef *spi_regs)
{
uint32_t i;
const struct spi_rcc_lookup_pair *ret = NULL;
for (i = 0; i < COUNT_OF(rcc_lookup); i++) {
if (rcc_lookup[i].spi_regs == spi_regs) {
ret = &rcc_lookup[i];
break;
}
}
return ret;
}
static struct stm_spi_dev *spi_handle_to_struct(stm_spi_handle handle)
{
struct stm_spi_dev *dev;
dev = (struct stm_spi_dev *)handle;
if (dev == NULL)
return NULL;
if (dev->magic != STM_SPI_DEV_MAGIC)
return NULL;
return dev;
}
stm_spi_handle spi_init(struct stm_spi_dev *spi_dev_struct, SPI_TypeDef *spi_regs, const struct stm_spi_settings *settings)
{
stm_spi_handle ret_handle = NULL;
uint32_t reg_val;
const struct spi_rcc_lookup_pair *rcc_pair;
if (!spi_dev_struct || !spi_regs || !settings)
goto exit;
if (!settings->cs_activate || !settings->cs_deactivate)
goto exit;
rcc_pair = spi_find_rcc_reg_and_bit(spi_regs);
if (!rcc_pair)
goto exit;
memcpy(&spi_dev_struct->settings, settings, sizeof(struct stm_spi_settings));
spi_dev_struct->spi_regs = spi_regs;
rcc_manager_enable_clock(rcc_pair->rcc_reg, rcc_pair->bit_no);
reg_val = 0;
if (settings->cpha)
reg_val |= SPI_CR1_CPHA;
if (settings->cpol)
reg_val |= SPI_CR1_CPOL;
if (settings->master)
reg_val |= SPI_CR1_MSTR;
reg_val |= (settings->prescaler & 0x7) << 3;
reg_val |= SPI_CR1_SPE | SPI_CR1_SSI | SPI_CR1_SSM;
if (!settings->msb_first)
reg_val |= SPI_CR1_LSBFIRST;
spi_regs->CR2 = 0UL;
spi_regs->CR1 = reg_val;
ret_handle = spi_dev_struct;
spi_dev_struct->settings.cs_deactivate();
spi_dev_struct->magic = STM_SPI_DEV_MAGIC;
exit:
return ret_handle;
}
void spi_deinit(stm_spi_handle handle)
{
const struct spi_rcc_lookup_pair *rcc_pair;
struct stm_spi_dev *dev;
dev = spi_handle_to_struct(handle);
if (!dev)
return;
dev->magic = 0UL;
dev->spi_regs->CR1 = 0;
dev->spi_regs->CR2 = 0;
rcc_pair = spi_find_rcc_reg_and_bit(dev->spi_regs);
if (!rcc_pair)
rcc_manager_disable_clock(rcc_pair->rcc_reg, rcc_pair->bit_no);
}
static uint8_t transfer_byte(uint8_t byte, struct stm_spi_dev *dev)
{
while (dev->spi_regs->SR & SPI_SR_BSY);
dev->spi_regs->DR = (uint16_t)byte;
__DSB();
while((dev->spi_regs->SR & SPI_SR_BSY) || !(dev->spi_regs->SR & SPI_SR_TXE));
return (uint8_t)dev->spi_regs->DR;
}
int spi_transfer(stm_spi_handle handle, const uint8_t *tx, uint8_t *rx, uint32_t count, bool handle_cs)
{
struct stm_spi_dev *dev;
uint8_t tx_byte;
uint8_t rx_byte;
uint32_t idx;
dev = spi_handle_to_struct(handle);
if (!dev)
return -1001;
if (handle_cs)
dev->settings.cs_activate();
for (idx = 0; idx < count; idx++) {
tx_byte = (tx ? tx[idx] : 0U);
rx_byte = transfer_byte(tx_byte, dev);
if (rx)
rx[idx] = rx_byte;
}
if (handle_cs)
dev->settings.cs_deactivate();
return 0;
}

View File

@@ -20,7 +20,7 @@
#include <stm-periph/uart.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 <stm-periph/dma-ring-buffer.h>
#include <string.h>

View File

@@ -20,7 +20,7 @@
* RAM: 128K
* CCM RAM: 64L
* FPU: fpv4-sp-d16
*
*/
/* USER PARAMETERS */
__ld_stack_size = 0x3000;
@@ -29,7 +29,7 @@ __stack_corruption_area_size = 128;
/* END OF USER PARAMETERS */
ENTRY(Reset_Handler)
__ld_top_of_stack = 0x20020000; /* One byte above the end of the SRAM. Stack is pre-decrewmenting, so this is okay */
__ld_top_of_stack = 0x20020000; /* One byte above the end of the SRAM. Stack is pre-decrementing, so this is okay */
/* Available memory areas */

View File

@@ -21,6 +21,7 @@
#include <reflow-controller/temp-converter.h>
#include <reflow-controller/temp-converter-data.h>
#include <helper-macros/helper-macros.h>
#include <stdbool.h>
static const float temp_lookup[(TEMP_CONVERSION_MAX_RES-TEMP_CONVERSION_MIN_RES) / TEMP_CONVERSION_RES_STEP+1] = {TEMP_CONVERSION_ARRAY_DATA};
@@ -73,3 +74,49 @@ int temp_converter_convert_resistance_to_temp(float resistance, float *temp_out)
return_ret_val:
return ret_val;
}
int temp_convertet_convert_temp_to_resistance(float temp, float *resistance_out)
{
int retcode = 0;
unsigned int i;
float lower_temp;
float upper_temp;
float lower_resistance;
float upper_resistance;
float temp_ratio;
bool found = false;
if (!resistance_out)
return -1000;
if (temp < temp_lookup[0]) {
/* Requested temperature is smaller than minimum value in lookup table */
*resistance_out = temp_lookup[0];
retcode = -1;
goto exit;
} else if (temp > temp_lookup[COUNT_OF(temp_lookup) - 1]) {
/* Requested temperature is higher than maximum value in lookup table */
*resistance_out = temp_lookup[COUNT_OF(temp_lookup) -1];
retcode = 1;
goto exit;
}
for (i = 0U; i < (COUNT_OF(temp_lookup) - 1); i++) {
if (temp >= temp_lookup[i] && temp <= temp_lookup[i+1]) {
upper_temp = temp_lookup[i+1];
lower_temp = temp_lookup[i];
lower_resistance = TEMP_CONVERSION_MIN_RES + TEMP_CONVERSION_RES_STEP * (i);
upper_resistance = lower_resistance + TEMP_CONVERSION_RES_STEP;
found = true;
}
}
if (!found)
return -100;
temp_ratio = (temp - lower_temp) / (upper_temp - lower_temp);
*resistance_out = lower_resistance + (upper_resistance - lower_resistance) * temp_ratio;
exit:
return retcode;
}

View File

@@ -0,0 +1,256 @@
/* 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/temp-profile-executer.h>
#include <reflow-controller/systick.h>
#include <helper-macros/helper-macros.h>
#include <reflow-controller/oven-driver.h>
#include <reflow-controller/temp-converter.h>
#include <reflow-controller/adc-meas.h>
#include <reflow-controller/digio.h>
static struct tpe_current_state IN_SECTION(.ccm.data) state = {
.status = TPE_OFF,
.start_timestamp = 0,
};
static bool IN_SECTION(.ccm.bss) pid_should_run;
struct pid_controller IN_SECTION(.ccm.bss) pid;
bool IN_SECTION(.ccm.bss) cmd_continue;
static struct pl_command IN_SECTION(.ccm.bss) cmd_list[MAX_PROFILE_LENGTH];
static void tpe_abort(void)
{
temp_profile_executer_stop();
state.status = TPE_ABORT;
}
enum pl_ret_val temp_profile_executer_start(const char *filename)
{
uint32_t parsed_count = 0;
enum pl_ret_val res;
state.setpoint = 0.0f;
state.start_timestamp = 0ULL;
state.setpoint = 0.0f;
state.step = 0;
state.profile_steps = 0;
oven_pid_stop();
pid_should_run = false;
state.status = TPE_OFF;
state.profile_steps = 0;
cmd_continue = false;
res = temp_profile_parse_from_file(filename, cmd_list, MAX_PROFILE_LENGTH, &parsed_count);
if (res == PL_RET_SUCCESS) {
state.profile_steps = parsed_count;
state.status = TPE_RUNNING;
state.start_timestamp = systick_get_global_tick();
}
return res;
}
static bool cmd_wait_temp(struct pl_command *cmd, bool cmd_continue)
{
(void)cmd_continue;
float resistance;
int res;
float temp;
res = adc_pt1000_get_current_resistance(&resistance);
if (res < 0) {
tpe_abort();
return false;
}
(void)temp_converter_convert_resistance_to_temp(resistance, &temp);
if (ABS(cmd->params[0] - temp) < 3.0f)
return true;
return false;
}
static bool cmd_wait_time(struct pl_command *cmd, bool cmd_continue)
{
static uint64_t temp_tick = 0UL;
if (cmd_continue) {
if (systick_ticks_have_passed(temp_tick,
(uint64_t)(cmd->params[0] * 1000.0f)))
return true;
} else {
temp_tick = systick_get_global_tick();
}
return false;
}
static void reactivate_pid_if_suspended(void)
{
if (oven_pid_get_status() == OVEN_PID_DEACTIVATED)
oven_pid_init(&pid);
pid_should_run = true;
}
static bool cmd_set_temp(struct pl_command *cmd, bool cmd_continue)
{
(void)cmd_continue;
reactivate_pid_if_suspended();
oven_pid_set_target_temperature(cmd->params[0]);
state.setpoint = cmd->params[0];
return true;
}
static bool cmd_ramp(struct pl_command *cmd, bool cmd_continue)
{
static uint64_t IN_SECTION(.ccm.bss) start_timestamp;
static float IN_SECTION(.ccm.bss) start_temp;
static float IN_SECTION(.ccm.bss) slope;
float secs_passed;
bool ret = false;
if (!cmd_continue) {
/* Init of command */
start_temp = state.setpoint;
slope = (cmd->params[0] - start_temp) / cmd->params[1];
reactivate_pid_if_suspended();
oven_pid_set_target_temperature(start_temp);
start_timestamp = systick_get_global_tick();
} else {
secs_passed = ((float)(systick_get_global_tick() - start_timestamp)) / 1000.0f;
if ((state.setpoint <= cmd->params[0] && start_temp < cmd->params[0]) ||
(state.setpoint >= cmd->params[0] && start_temp > cmd->params[0])) {
state.setpoint = start_temp + secs_passed * slope;
} else {
state.setpoint = cmd->params[0];
ret = true;
}
oven_pid_set_target_temperature(state.setpoint);
}
return ret;
}
int temp_profile_executer_handle(void)
{
struct pl_command *current_cmd;
static uint64_t last_tick = 0UL;
bool advance;
uint32_t next_step;
/* Return if no profile is currently executed */
if (state.status != TPE_RUNNING)
return -1;
/* Abort profile execution if oven PID is aborted. This is most likely due to some error flags */
if (oven_pid_get_status() == OVEN_PID_ABORTED && pid_should_run) {
tpe_abort();
oven_pid_stop();
return -1;
}
/* Execute Temp Profile every 100 ms. If not yet due, return */
if (!systick_ticks_have_passed(last_tick, 100))
return 0;
current_cmd = &cmd_list[state.step];
next_step = state.step;
switch (current_cmd->cmd) {
case PL_WAIT_FOR_TIME:
advance = cmd_wait_time(current_cmd, cmd_continue);
break;
case PL_WAIT_FOR_TEMP:
advance = cmd_wait_temp(current_cmd, cmd_continue);
break;
case PL_SET_TEMP:
advance = cmd_set_temp(current_cmd, cmd_continue);
break;
case PL_LOUDSPEAKER_SET:
loudspeaker_set((uint16_t)current_cmd->params[0]);
advance = true;
break;
case PL_OFF:
oven_pid_stop();
pid_should_run = false;
advance = true;
break;
case PL_PID_CONF:
pid_init(&pid, current_cmd->params[0], /* Kd */
current_cmd->params[1], /* Ki */
current_cmd->params[2], /* Kp */
0.0f, 0.0f,
current_cmd->params[3], /* Int max */
current_cmd->params[4], /* Kd tau */
current_cmd->params[5]); /* Period */
oven_pid_init(&pid);
advance = true;
pid_should_run = true;
break;
case PL_SET_RAMP:
advance = cmd_ramp(current_cmd, cmd_continue);
break;
default:
tpe_abort();
advance = true;
break;
}
if (advance)
next_step++;
if (next_step != state.step) {
state.step = next_step;
if (next_step >= state.profile_steps) {
(void)temp_profile_executer_stop();
} else {
cmd_continue = false;
}
} else {
cmd_continue = true;
}
last_tick = systick_get_global_tick();
return 0;
}
const struct tpe_current_state *temp_profile_executer_status(void)
{
return &state;
}
int temp_profile_executer_stop(void)
{
if (state.status == TPE_RUNNING) {
state.status = TPE_OFF;
oven_pid_stop();
}
loudspeaker_set(0);
return 0;
}

681
stm-firmware/ui/gui.c Normal file
View File

@@ -0,0 +1,681 @@
/* 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/ui/gui.h>
#include <reflow-controller/ui/gui-config.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/settings/settings.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>
#include <reflow-controller/oven-driver.h>
#include <fatfs/ff.h>
#include <reflow-controller/temp-profile-executer.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 gui_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, GUI_MONITORING_INTERVAL_MS)) {
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 gui_menu_about(struct lcd_menu *menu, enum menu_entry_func_entry entry_type, void *parent)
{
static void *my_parent;
static int page = 0;
static int last_page = -1;
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;
bool button_ready;
if (entry_type == MENU_ENTRY_FIRST_ENTER) {
uptime_secs = 0ULL;
page = 0;
last_page = -1;
my_parent = parent;
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:
if (last_page == 0)
break;
last_page = 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:
if (last_page == 1)
break;
last_page = 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:
if (last_page == 2)
break;
last_page = 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:
if (last_page == 3)
break;
last_page = 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:
last_page = 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;
last_page = -1;
break;
}
push_button = menu_get_button_state(menu);
button_ready = menu_get_button_ready_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 gui_menu_err_flags(struct lcd_menu *menu, enum menu_entry_func_entry entry_type, void *parent)
{
static void *my_parent = NULL;
static uint8_t offset;
static uint64_t timestamp;
static bool end_of_list_reached = true;
bool state;
enum button_state push_button;
int16_t rot;
uint32_t i;
char name[64];
int32_t line_counter;
bool skip_err_flag_prefix;
bool try_ack = false;
enum safety_flag flag;
bool button_ready;
const char *err_flag_prefix = "ERR_FLAG_";
push_button = menu_get_button_state(menu);
rot = menu_get_rotary_delta(menu);
if (entry_type != MENU_ENTRY_CONTINUE) {
if (entry_type == MENU_ENTRY_FIRST_ENTER) {
my_parent = parent;
offset = 0;
end_of_list_reached = true;
}
} else {
if (push_button == BUTTON_IDLE && rot == 0) {
if (!systick_ticks_have_passed(timestamp, 150))
return;
}
}
button_ready = menu_get_button_ready_state(menu);
if (push_button == BUTTON_SHORT_RELEASED && button_ready) {
menu_entry_dropback(menu, my_parent);
} if (push_button == BUTTON_LONG && button_ready) {
try_ack = true;
}
if (rot >= 4 || rot <= -4) {
menu_ack_rotary_delta(menu);
if (rot > 0) {
if (!end_of_list_reached)
offset++;
} else {
if (offset > 0)
offset--;
}
}
menu_display_clear(menu);
line_counter = -offset;
for (i = 0; i < safety_controller_get_flag_count(); i++) {
(void)safety_controller_get_flag_by_index(i, &state, &flag);
if (try_ack)
safety_controller_ack_flag(flag);
if (state) {
if (line_counter >= 0 && line_counter < 4) {
safety_controller_get_flag_name_by_index(i, name, sizeof(name));
if (!strncmp(name, err_flag_prefix, 9)) {
skip_err_flag_prefix = true;
} else {
skip_err_flag_prefix = false;
}
menu_lcd_outputf(menu, line_counter, "%s",
(skip_err_flag_prefix ? &name[9] : name));
}
line_counter++;
}
}
end_of_list_reached = (line_counter > 4 ? false : true);
timestamp = systick_get_global_tick();
}
static void gui_menu_constant_temperature_driver(struct lcd_menu *menu, enum menu_entry_func_entry entry_type, void *parent)
{
static void IN_SECTION(.ccm.bss) *my_parent;
static int16_t IN_SECTION(.ccm.bss) temperature;
static bool IN_SECTION(.ccm.bss) fine;
static uint64_t IN_SECTION(.ccm.bss) last_temp_refresh;
static float IN_SECTION(.ccm.bss) last_temp;
enum button_state button;
float current_temp;
int status;
int16_t rot;
int16_t temp_old;
if (entry_type == MENU_ENTRY_FIRST_ENTER) {
my_parent = parent;
last_temp = -2000.0f;
temperature = 30;
menu_display_clear(menu);
menu_lcd_outputf(menu, 0, "Temp Controller");
temp_old = 0;
} else {
temp_old = temperature;
}
if (menu_get_button_ready_state(menu)) {
button = menu_get_button_state(menu);
rot = menu_get_rotary_delta(menu);
if (rot >= 4 || rot <= -4) {
menu_ack_rotary_delta(menu);
if (rot > 0)
temperature += (fine ? 1 : 10);
else
temperature -= (fine ? 1 : 10);
if (temperature > 300)
temperature = 300;
else if (temperature < 0)
temperature = 0;
}
switch (button) {
case BUTTON_SHORT_RELEASED:
fine = !fine;
break;
case BUTTON_LONG:
oven_pid_stop();
menu_entry_dropback(menu, my_parent);
break;
default:
break;
}
}
if (oven_pid_get_status() != OVEN_PID_RUNNING) {
menu_lcd_output(menu, 1, "PID stopped!");
menu_lcd_output(menu, 2, "Check Flags!");
} else {
if (temperature != temp_old) {
oven_pid_set_target_temperature((float)temperature);
menu_lcd_outputf(menu, 1, "Target: %d " LCD_DEGREE_SYMBOL_STRING "C", (int)temperature);
}
if (entry_type == MENU_ENTRY_FIRST_ENTER || systick_ticks_have_passed(last_temp_refresh, GUI_TEMP_DRIVER_REFRESH_MS)) {
(void)adc_pt1000_get_current_resistance(&current_temp);
status = temp_converter_convert_resistance_to_temp(current_temp, &current_temp);
if (current_temp != last_temp) {
last_temp = current_temp;
menu_lcd_outputf(menu, 2, "Current: %s%.1f", current_temp, (status < 0 ? "<" : status > 0 ? ">" : ""));
}
last_temp_refresh = systick_get_global_tick();
}
}
}
/**
* @brief load_temperature_file_list_from_sdcard
* @return -1 File error, 0 successful
*/
static int load_temperature_file_list_from_sdcard(char (*list)[17], uint32_t len)
{
uint32_t i, j;
DIR directory;
FILINFO finfo;
FRESULT fres;
if (!list)
return -1001;
/* Zero out the list */
for (i = 0; i < len; i++) {
for (j = 0; j < sizeof(*list); j++) {
list[i][j] = 0;
}
}
/* find the frist file */
fres = f_findfirst(&directory, &finfo, "/", "*.tpr");
i = 0;
while (fres == FR_OK && finfo.fname[0]) {
strncpy(list[i], finfo.fname, sizeof(*list));
fres = f_findnext(&directory, &finfo);
i++;
if (i >= len)
break;
}
if (fres != FR_OK) {
return -1;
}
return (int)i;
}
static void gui_menu_temp_profile_execute(struct lcd_menu *menu, enum menu_entry_func_entry entry_type, void* parent)
{
static void *my_parent;
const struct tpe_current_state *state;
static uint64_t last_tick;
float temperature;
float resistance;
int res;
if (entry_type == MENU_ENTRY_FIRST_ENTER) {
my_parent = parent;
menu_display_clear(menu);
last_tick = 0ULL;
}
if (systick_ticks_have_passed(last_tick, 300)) {
state = temp_profile_executer_status();
if (state->status == TPE_RUNNING)
menu_lcd_outputf(menu, 0, "Profile running");
else if (state->status == TPE_OFF) {
menu_lcd_outputf(menu, 0, "Profile finished");
} else {
menu_lcd_outputf(menu, 0, "Profile aborted!");
}
menu_lcd_outputf(menu, 1, "Step %u/%u", state->step, state->profile_steps);
(void)adc_pt1000_get_current_resistance(&resistance);
res = temp_converter_convert_resistance_to_temp(resistance, &temperature);
menu_lcd_outputf(menu, 2, "Temp: %s%.0f", (res < 0 ? "<" : (res > 0 ? ">" : "")), temperature);
if (oven_pid_get_status() == OVEN_PID_RUNNING) {
menu_lcd_outputf(menu, 3, "Target: %.0f", state->setpoint);
} else {
menu_lcd_outputf(menu, 3, "Temp Off");
}
last_tick = systick_get_global_tick();
}
if (menu_get_button_ready_state(menu)) {
if (menu_get_button_state(menu) != BUTTON_IDLE) {
temp_profile_executer_stop();
menu_entry_dropback(menu, my_parent);
}
}
}
static void gui_menu_temp_profile_select(struct lcd_menu *menu, enum menu_entry_func_entry entry_type, void *parent)
{
static void *my_parent;
static char profile_list[10][17];
static bool file_error = false;
static enum pl_ret_val profile_ret_val = PL_RET_SUCCESS;
static uint8_t currently_selected = 0U;
static uint8_t loaded;
int16_t delta;
enum button_state button;
int res;
if (entry_type == MENU_ENTRY_FIRST_ENTER) {
menu_display_clear(menu);
my_parent = parent;
res = load_temperature_file_list_from_sdcard(profile_list, 10);
file_error = false;
loaded = 0;
if (res < 0) {
file_error = true;
}
currently_selected = 0u;
profile_ret_val = PL_RET_SUCCESS;
loaded = (uint32_t)res;
menu_lcd_outputf(menu, 0, "Select:");
} else if (entry_type == MENU_ENTRY_DROPBACK) {
menu_entry_dropback(menu, my_parent);
return;
}
if (menu_get_button_ready_state(menu)) {
delta = menu_get_rotary_delta(menu);
button = menu_get_button_state(menu);
if (button == BUTTON_LONG) {
menu_entry_dropback(menu, my_parent);
}
if (file_error) {
menu_lcd_outputf(menu, 0, "Disk Error");
menu_lcd_outputf(menu, 1, "SD inserted?");
if (button == BUTTON_SHORT_RELEASED)
menu_entry_dropback(menu, my_parent);
return;
} else if (loaded == 0) {
menu_lcd_outputf(menu, 0, "No profiles");
menu_lcd_outputf(menu, 1, "found");
if (button == BUTTON_SHORT_RELEASED)
menu_entry_dropback(menu, my_parent);
return;
} else if (profile_ret_val != PL_RET_SUCCESS) {
menu_lcd_outputf(menu, 0, "ERROR");
switch (profile_ret_val) {
case PL_RET_SCRIPT_ERR:
menu_lcd_outputf(menu, 1, "Syntax Error");
break;
case PL_RET_DISK_ERR:
menu_lcd_outputf(menu, 1, "Disk Error");
break;
case PL_RET_LIST_FULL:
menu_lcd_output(menu, 1, "Too many com-");
menu_lcd_output(menu, 2, "mands in file");
break;
default:
menu_lcd_output(menu, 1, "Unknown error");
break;
}
if (button == BUTTON_SHORT_RELEASED)
menu_entry_dropback(menu, my_parent);
return;
} else if (currently_selected < loaded) {
/* Show currently selected profile */
menu_lcd_outputf(menu, 1, "%s", &profile_list[currently_selected][0]);
if (button == BUTTON_SHORT_RELEASED) {
/* Execute selected profile */
profile_ret_val = temp_profile_executer_start(&profile_list[currently_selected][0]);
if (profile_ret_val == PL_RET_SUCCESS) {
menu_entry_enter(menu, gui_menu_temp_profile_execute, true);
return;
}
}
if (delta >= 4) {
menu_ack_rotary_delta(menu);
if (currently_selected < (loaded - 1))
currently_selected++;
} else if (delta <= -4) {
menu_ack_rotary_delta(menu);
if (currently_selected > 0)
currently_selected--;
}
}
}
}
static void gui_menu_constant_temperature_driver_setup(struct lcd_menu *menu, enum menu_entry_func_entry entry_type, void *parent)
{
static void IN_SECTION(.ccm.bss) *my_parent;
struct oven_pid_settings pid_settings;
enum button_state button;
struct pid_controller pid_controller;
if (entry_type == MENU_ENTRY_FIRST_ENTER) {
my_parent = parent;
/* Try loading PID parameters */
if (settings_load_pid_oven_parameters(&pid_settings)) {
menu_display_clear(menu);
menu_lcd_output(menu, 0, "Could not load");
menu_lcd_output(menu, 1, "PID parameters");
} else {
pid_init(&pid_controller, pid_settings.kd, pid_settings.ki, pid_settings.kp, 0, 100,
pid_settings.max_integral, pid_settings.kd_tau, pid_settings.t_sample);
oven_pid_init(&pid_controller);
menu_entry_enter(menu, gui_menu_constant_temperature_driver, true);
}
} else if (entry_type == MENU_ENTRY_DROPBACK) {
menu_entry_dropback(menu, my_parent);
}
if (menu_get_button_ready_state(menu)) {
button = menu_get_button_state(menu);
if (button == BUTTON_SHORT_RELEASED || button == BUTTON_LONG) {
menu_entry_dropback(menu, my_parent);
}
}
}
static void gui_menu_root_entry(struct lcd_menu *menu, enum menu_entry_func_entry entry_type, void *parent)
{
(void)parent;
static struct menu_list list;
bool menu_changed = false;
static const char * const root_entry_names[] = {
"Constant Temp",
"Temp Profile",
"Monitoring",
"Error Flags",
"About",
NULL
};
static const menu_func_t root_entry_funcs[] = {
gui_menu_constant_temperature_driver_setup,
gui_menu_temp_profile_select,
gui_menu_monitor,
gui_menu_err_flags,
gui_menu_about,
};
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) {
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 (menu_get_button_ready_state(menu) && 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 gui_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 gui_init()
{
rotary_encoder_setup();
button_init();
lcd_init();
menu_init(reflow_menu_ptr, gui_menu_root_entry, update_display_buffer);
}

View File

@@ -26,7 +26,7 @@
#include <stm32/stm32f4xx.h>
#include <reflow-controller/ui/lcd.h>
#include <reflow-controller/systick.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>
#include <stdbool.h>

View File

@@ -27,6 +27,7 @@ void menu_handle(struct lcd_menu *menu, int16_t rotary_encoder_delta, enum butto
{
menu_func_t tmp;
if (!menu)
return;
@@ -38,6 +39,9 @@ void menu_handle(struct lcd_menu *menu, int16_t rotary_encoder_delta, enum butto
tmp = menu->active_entry;
if (menu->active_entry_type == MENU_ENTRY_FIRST_ENTER && push_button != BUTTON_IDLE) {
menu->inputs.button_ready = false;
}
if (menu->active_entry_type == MENU_ENTRY_FIRST_ENTER) {
menu->active_entry(menu, menu->active_entry_type, menu->init_parent);
@@ -48,6 +52,9 @@ void menu_handle(struct lcd_menu *menu, int16_t rotary_encoder_delta, enum butto
if (menu->active_entry_type != MENU_ENTRY_CONTINUE && tmp == menu->active_entry) {
menu->active_entry_type = MENU_ENTRY_CONTINUE;
}
if (push_button == BUTTON_IDLE)
menu->inputs.button_ready = true;
}
void menu_init(struct lcd_menu *menu, menu_func_t root_node, void (*display_update)(uint8_t row, const char *data))
@@ -59,6 +66,7 @@ void menu_init(struct lcd_menu *menu, menu_func_t root_node, void (*display_upda
menu->active_entry = root_node;
menu->init_parent = NULL;
menu->inputs.push_button = BUTTON_IDLE;
menu->inputs.button_ready = false;
menu->inputs.rotary_encoder_delta = 0;
menu->active_entry_type = MENU_ENTRY_FIRST_ENTER;
menu->update_display = display_update;
@@ -73,6 +81,8 @@ void menu_entry_dropback(struct lcd_menu *menu, menu_func_t parent_func)
else
menu->active_entry = menu->root_entry;
menu_ack_rotary_delta(menu);
menu->active_entry_type = MENU_ENTRY_DROPBACK;
}
@@ -243,6 +253,16 @@ enum button_state menu_get_button_state(const struct lcd_menu *menu)
return ret;
}
bool menu_get_button_ready_state(const struct lcd_menu *menu)
{
bool ret = false;
if (menu)
ret = menu->inputs.button_ready;
return ret;
}
void menu_display_clear(struct lcd_menu *menu)
{
uint8_t i;
@@ -253,3 +273,10 @@ void menu_display_clear(struct lcd_menu *menu)
for (i = 0; i < 4; i++)
menu->update_display(i, "");
}
uint32_t menu_list_get_currently_selected(struct menu_list *list)
{
if (!list)
return 0;
return list->currently_selected;
}

View File

@@ -0,0 +1,6 @@
obj/*
*.bin
*.bin.h
*.bin.?
*.elf
*.map

Some files were not shown because too many files have changed in this diff Show More