50 Commits

Author SHA1 Message Date
227562cf3c Load calibration when SD card is inserted.
* Load calibration when SD card is inserted and controller is not yet calibrated
* Fix #7: LED0 blinks when SD is inserted / removed.
2020-08-16 20:33:25 +02:00
58937b46f6 Improve code 2020-08-16 20:33:17 +02:00
e06c9f7ddc Remove toml. Write calibration to dat files. Implement first draft for reading function 2020-08-16 19:37:41 +02:00
a5402d3f04 Add gitignore to build directory of documentation 2020-08-16 13:18:04 +02:00
d04d8ebf9d Change save fuinction for SD card calibration data 2020-08-16 13:15:35 +02:00
42ca1a01b5 Merge branch 'issue/1-shell-function-save-calibration-data' into dev 2020-08-16 12:53:47 +02:00
68883735ec Remove global error state from main file 2020-08-16 12:53:05 +02:00
15d255778c Fix #1: Implement save function for calibration 2020-08-16 12:52:37 +02:00
2f39b5eb69 Merge branch 'dev' of git.shimatta.de:mhu/reflow-oven-control-sw into dev 2020-08-16 12:35:45 +02:00
d1d2d514bd Add timing monitor for main loop and add monitors to safety flag command 2020-08-16 12:34:41 +02:00
4a441a9c44 Documentation: Create build folder in case it does not exist. However, add it to the git repo by placing a .gitignore inside 2020-08-16 11:50:20 +02:00
fa3c980207 shell: Add dummy function save-calibration. Not yet correcly implemented! 2020-08-16 01:24:59 +02:00
3c6200e08c Settings: Add preliminary functions to store Claibration data on SD Card. Not yet implemented correctly 2020-08-16 01:24:20 +02:00
e7d150e8f5 FatFs: Enable relative paths 2020-08-16 01:23:50 +02:00
c5667c6895 Toml: Add TOML interpreter for config files 2020-08-16 01:22:57 +02:00
b7ccd8542e Increase safety ADC timing monitor to over a second. It might take a while for the ADC to be called if a demanding operation is taking place 2020-08-16 01:22:26 +02:00
1ad68a2c43 SDIO Driver: Fix bug created in commit 3705cc09d1 that makes writing to disk impossible 2020-08-16 01:19:56 +02:00
4ab91ace5f Remove redundant define macro 2020-08-11 23:37:26 +02:00
97c32b0443 Write documentation 2020-08-11 23:21:24 +02:00
a68b9176cb Move ADC_TO_RES macro to header file 2020-08-11 23:21:14 +02:00
cb3c989683 Docu 2020-08-04 00:55:16 +02:00
f6f01b0510 Improve sphinx 2020-08-03 21:13:04 +02:00
d6815f8285 Documentation: Breathe: Detect c/h files correctly as C Files and not C++ 2020-08-02 22:29:18 +02:00
62ea995c2d fix typo in sphinx doc 2020-08-02 22:20:41 +02:00
e8e3d71bbe doxygen: Remove latex output and add optional flags to buildscript 2020-08-02 22:15:33 +02:00
75d4af84c4 Basic template for documentation 2020-08-02 22:14:49 +02:00
6cde956c31 Merge branch 'safety-controller' into dev 2020-08-02 19:00:35 +02:00
bedf231550 Use the RTD theme for sphinx 2020-07-30 23:08:40 +02:00
a112cd80bf Add sphinx docu 2020-07-30 22:58:42 +02:00
fdf3f2c7d6 Merge branch 'dev' into safety-controller 2020-07-30 22:51:08 +02:00
21ad2ace4a Remove temp profile submodule 2020-07-30 22:50:34 +02:00
464c247e32 Fix a few bugs and implement flags command further 2020-07-30 20:29:48 +02:00
6c4b698fd7 Add safety flag for debugbuild 2020-07-28 23:29:35 +02:00
6498aaf8b8 Add color to flag monitor command and set oven output to 0 2020-07-28 23:26:28 +02:00
b65d94b0e8 Fix ADC measurement to run at 1000 Hz and fix wrong error handling for PT1000 Watchdog. Add function for flags to shell 2020-07-28 22:55:02 +02:00
97fc04399e Fix recursion loop in safety controller 2020-07-28 21:00:37 +02:00
da96daa767 Reworked measurement ADC to use safety controller 2020-07-27 22:15:01 +02:00
a9e300bf5b Make error structures volatile 2020-07-27 21:32:25 +02:00
4f3016649d First draft of safety controller 2020-07-27 21:29:15 +02:00
a04e894518 Further work on safety controller 2020-07-26 21:40:09 +02:00
9136dc196c Further rewrite safety handling 2020-07-09 22:31:42 +02:00
5eb51f08b6 Start safety implementation. Completely dumped old stuff 2020-07-07 20:47:22 +02:00
06a75559f0 Add structure for safety controller config. 2020-07-07 19:26:00 +02:00
248707055e Fix watchdog init code 2020-07-06 21:37:36 +02:00
67a32cdc20 Safety Controller:
* Add watchdog code
* Add file structure for safety controller
* Lay groundstones to move all error flags to the safety controller
* Improve doxygen
2020-07-06 21:12:18 +02:00
8a365ab5e0 Move safety ADC to safety subfolder 2020-07-06 20:13:01 +02:00
7cd05e1582 Update shellmatta 2020-06-25 23:54:36 +02:00
0e97d57883 Improve menu function 2020-06-25 23:52:58 +02:00
cced874460 Add Pullup to uart RX pin 2020-06-21 01:29:50 +02:00
49927a25cf Add Omega as unit for Ohm in LCD menu 2020-06-16 20:05:32 +02:00
60 changed files with 24672 additions and 534 deletions

5
.gitmodules vendored
View File

@@ -5,7 +5,4 @@
path = c-style-checker
url = https://git.shimatta.de/mhu/c-style-checker.git
branch = master
[submodule "reflow-controller-temp-profile-lang"]
path = reflow-controller-temp-profile-lang
url = https://git.shimatta.de/mhu/reflow-controller-temp-profile-lang.git
branch = master

0
doc/.gitignore vendored Normal file
View File

22
doc/Makefile Normal file
View File

@@ -0,0 +1,22 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = source
BUILDDIR = build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
livehtml:
sphinx-autobuild -b html $(SPHINXOPTS) $(SOURCEDIR) $(BUILDDIR)/html

3
doc/build/.gitignore vendored Normal file
View File

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

35
doc/make.bat Normal file
View File

@@ -0,0 +1,35 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=source
set BUILDDIR=build
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
:end
popd

2556
doc/source/Doxyfile.in Normal file

File diff suppressed because it is too large Load Diff

3432
doc/source/_static/ibom.html Normal file

File diff suppressed because one or more lines are too long

84
doc/source/conf.py Normal file
View File

@@ -0,0 +1,84 @@
# Configuration file for the Sphinx documentation builder.
#
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
import os, subprocess
import sys
# sys.path.insert(0, os.path.abspath('.'))
# -- Project information -----------------------------------------------------
import re
project = 'Shimatta Reflow Controller'
copyright = '2020, Mario Hüttel'
author = 'Mario Hüttel'
# The full version, including alpha/beta/rc tags.
release = re.sub('^v', '', os.popen('git describe --always --tags --dirty').read().strip())
# The short X.Y version.
version = release
try:
os.mkdir('../build/_doxygen')
except FileExistsError:
pass
subprocess.call('doxygen Doxyfile.in', shell=True)
# -- General configuration ---------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx_rtd_theme',
'sphinx.ext.autodoc',
'sphinx.ext.todo',
'sphinxcontrib.blockdiag',
'breathe'
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = []
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'sphinx_rtd_theme'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
breathe_projects = {
"Reflow Controller Firmware": "../build/_doxygen/xml/"
}
breathe_domain_by_extension = { "h" : "c",
"c" : "c" }
breathe_default_project = "Reflow Controller Firmware"
breathe_default_members = ('members', 'undoc-members')
blockdiag_html_image_format = 'SVG'
breathe_show_define_initializer = True
blockdiag_latex_image_format = 'PDF'
todo_include_todos = True

View File

@@ -0,0 +1,8 @@
.. _api_dmas:
Peripheral DMA Library Code
====================================
.. doxygengroup:: dma-ring-buffer
:project: Reflow Controller Firmware

View File

@@ -0,0 +1,11 @@
.. _api:
Important Code APIs
===================
.. toctree::
:maxdepth: 2
:glob:
*
safety/safety-controller

View File

@@ -0,0 +1,8 @@
.. _api_main:
Reflow Controller Firmware Main File
====================================
.. doxygenfile:: main.c
:project: Reflow Controller Firmware

View File

@@ -0,0 +1,8 @@
.. _dox_safety_adc:
Safety ADC
====================================
.. doxygengroup:: safety-adc
:project: Reflow Controller Firmware

View File

@@ -0,0 +1,14 @@
.. _dox_safety_controller:
Safety Controller
====================================
.. toctree::
:maxdepth: 1
safety-adc
.. doxygengroup:: safety-controller
:project: Reflow Controller Firmware

View File

@@ -0,0 +1,29 @@
.. _safety_flags:
Safety Flags
============
The safety flags are represented in software by the following enums
.. doxygenenum:: safety_flag
.. _safety_flags_adc_overflow:
ERR_FLAG_MEAS_ADC_OVERFLOW
--------------------------
.. _safety_flags_adc_off:
ERR_FLAG_MEAS_ADC_OFF
---------------------
.. _safety_flags_adc_watchdog:
ERR_FLAG_MEAS_ADC_WATCHDOG
--------------------------
.. _safety_flags_adc_unstable:
ERR_FLAG_MEAS_ADC_UNSTABLE
--------------------------

View File

@@ -0,0 +1,16 @@
.. _firmware:
Reflow Controller Firmware
==========================
This chapter describes the reflow controller's firmware.
This is in most cases not intended to be a code documentation but an overview over the functional
mechanisms and the behavior. For a detailed code documentation see the doxygen output.
.. toctree::
:maxdepth: 2
pt1000-processing
safety
code/index

View File

@@ -0,0 +1,192 @@
.. _pt1000_processing:
PT1000 Temperature Value Processing
===================================
The PT1000 temperature sensor is the sensing element used for determining the Reflow Oven Temperature.
The PT1000 value processing is enabled by default and not intended to be turned off.
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"];
}
ADC
~~~
The internal ADC of the STM32F407 controller is used to sample the analog signal from the :ref:`hw_analog_fe`. The ADC is triggered by the hardware Timer *TIM2* each millisecond, which results in a sampling frequency of
1 kHz. The ADC module provides an analog value `watchdog <Watchdog_>`_, which is used to detect wirebreaks and other hardware errors that result in a wrong resistance measurement.
The sample frequency is controlled by
.. doxygendefine:: ADC_PT1000_SAMPLE_CNT_DELAY
whereas the ADC Peripheral module is defined by
.. doxygendefine:: ADC_PT1000_PERIPH
Prefilter
~~~~~~~~~
The analog value prefilter is used to filter outliers. It is triggered after a certain amount ``n`` of values have been sampled by the `ADC`_.
The filter then removes the two most extreme values and computes the average of the remaining ``n - 2`` values. By default ``n`` is configured to:
.. doxygendefine:: ADC_PT1000_DMA_AVG_SAMPLES
Therefore, by default, the resulting datastream has a sampling rate of 1/6 kHz. This depends on the :c:macro:`ADC_PT1000_SAMPLE_CNT_DELAY` and ``n``
Watchdog
~~~~~~~~
The analog watchdog supervises the measured value of the `ADC`_. It is configured by the following defines:
.. doxygendefine:: ADC_PT1000_LOWER_WATCHDOG
.. doxygendefine:: ADC_PT1000_UPPER_WATCHDOG
.. doxygendefine:: ADC_PT1000_WATCHDOG_SAMPLE_COUNT
The watchdog will set the :ref:`safety_flags_adc_watchdog` error flag.
ADC Value to Ohm
~~~~~~~~~~~~~~~~
This block converts the analog value to an Ohm resistance value.
The formula is:
.. math::
R(V) = \frac{V}{4096} \cdot 2500~\Omega
The equation is implemented in
.. doxygendefine:: ADC_TO_RES
and applied during the `Exponential Moving Average Filter`_.
Exponential Moving Average Filter
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The external moving average filter filters the measured resistance value. It's equation is:
.. math::
y[n] = (1-\alpha) y[n-1] + \alpha x[n]
The filter constant *alpha* defaults to the define
.. doxygendefine:: ADC_PT1000_FILTER_WEIGHT
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:
.. doxygendefine:: ADC_FILTER_STARTUP_CYCLES
The moving average filter's output signal is the Low Frequency (LF) PT1000 resistance signal used for internal PT1000 measurements.
Reading and Converting the PT1000 Value
---------------------------------------
Calibration
~~~~~~~~~~~
The functions
.. doxygenfunction:: adc_pt1000_set_resistance_calibration
:outline:
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.
The calibration is calculated the following way:
.. blockdiag::
:desctable:
blockdiag {
orientation = portrait;
LF [label="LF", shape=beginpoint, description="Low Frequency PT1000 Value"];
SENS [label="Sens", description="Sensitivity Correction :math:`\sigma`"];
OFFSET [label="Offset", description="Offset Correction :math:`O`"];
OUT [shape=endpoint, description="Corrected Value"];
LF -> SENS -> OFFSET -> OUT
}
The final calibrated PT1000 resistance is calculated as:
.. math::
R_{PT1000_{corr}} = R_{PT1000_{LF}} \cdot (1 + \sigma) + O
The default values, if no calibration is loaded / executed, are:
============== =========
:math:`\sigma` :math:`O`
============== =========
0 1
============== =========
Get Calibration Corrected Value
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The PT1000 value is available through the following function.
If a calibration is set, it is applied.
.. doxygenfunction:: adc_pt1000_get_current_resistance
Converting the Value
~~~~~~~~~~~~~~~~~~~~
The valid range for conversion is between
.. doxygendefine:: TEMP_CONVERSION_MIN_RES
:outline:
and
.. doxygendefine:: TEMP_CONVERSION_MAX_RES
:outline:
By default, the valid range is:
.. math::
1000~\Omega \le R_{PT1000} \le 2200~\Omega
.. doxygenfunction:: temp_converter_convert_resistance_to_temp
The cvonversion function is based on a lookup table with linear interpolation between the data points.
The lookuptable is stored as a header file and can, if necessary, be recreated using the ``create-temp-lookup-table.py`` script.

View File

@@ -0,0 +1,12 @@
.. _firmware_safety:
Safety Controller
=================
The safety controller is the software component that monitors the overall condition of the reflow controller,
and stops the output driver in case of an error.
.. toctree::
:maxdepth: 2
flags

View File

@@ -0,0 +1,8 @@
.. _hw_bom:
Interactive BoM of Controller Board
===================================
.. raw:: html
<iframe src="../_static/ibom.html" width="100%" height="1500"></iframe>

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 284 KiB

View File

@@ -0,0 +1,12 @@
.. _hw_analog_fe:
Analog Frontend
===============
Schematic
---------
.. image:: frontend-schematic_v1.2.svg
:target: /_images/frontend-schematic_v1.2.svg

View File

@@ -0,0 +1,12 @@
.. _hw:
Hardware
========
This guide on the reflow controller's hardware is based on the ``reflow-oven-control-pcb`` -- Version ``v1.2``
.. toctree::
:maxdepth: 2
:glob:
*

21
doc/source/index.rst Normal file
View File

@@ -0,0 +1,21 @@
.. Shimatta Reflow Controller documentation master file, created by
sphinx-quickstart on Thu Jul 30 22:56:09 2020.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to Shimatta Reflow Controller's documentation!
======================================================
Quick Links
===========
* :ref:`genindex`
.. toctree::
:maxdepth: 2
:caption: Contents
self
usage/index
hardware/index
firmware/index

View File

@@ -0,0 +1,17 @@
.. _usage_calibration:
Calibration
===========
In order to provide higher measurement accuracy, the PT1000 measurement can be calibrated. The calibration only calibrates the internal :ref:`hw_analog_fe` and not the PT1000 Sensor element itself.
The Sensor element must be conform with the standard PT1000 norms.
Tests have shown, that a calibration is most likely not necessary, because the resolution of the 12 bit analog measurement is far worse than the reistance reading error produced by the :ref:`hw_analog_fe`.
Calibration might only be necessary if no precission reistors in the frontend hardware are used.
Calibration can be performed the following ways:
Command Line Calibration
------------------------
Use the :ref:`command_line` to invoke the :ref:`shell_command_calibrate` command.

View File

@@ -0,0 +1,60 @@
.. _command_line:
Command Line Interface
======================
This section describes the command line interface located on the UART interface.
The command line interface is implemented using a "shellmatta" shell module <https://git.shimatta.net/shimatta/shellmatta>
Hardware Settings
-----------------
General Settings
~~~~~~~~~~~~~~~~
The UART is configured for the following settings:
- 115200 Baud
- 1 Stopbit
- No parity
- 8 data bits
Setup in Debug Build
~~~~~~~~~~~~~~~~~~~~
If the Reflow controller is build in **debug** mode, the UART is located on the internal spring contact connector, which is also used for the SWD interface.
Setup in Release Build
~~~~~~~~~~~~~~~~~~~~~~
In case of a **release** build, the UART is externally accessible on the DIGIO Header. The voltage level is 3.3 Volt LVCMOS. The inputs are ESD protected. Overvoltage is interally clamped and may dammage the clamping diodes!
- DIGIO2: Reflow Controller's TX
- DIGIO3: Reflow Controller's RX
Shell Commands
--------------
The following shell commands are available.
- `safety-flags <safety-flags_>`_ (alias: flags)
- `calibrate`_ (alias: cal)
safety-flags
~~~~~~~~~~~~
The ``safety-flags`` (``flags``) command displays the status of all safety flags and analog monitors. See: :ref:`safety_flags`
.. _shell_command_calibrate:
calibrate
~~~~~~~~~
The ``calibrate`` (``cal``) command is used to calibrate the :ref:`hw_analog_fe`, in order to ensure correct resistance measurement.
Calibration is most likely not necessary! See the :ref:`usage_calibration` page.
The command will guide you through the calibration process and will ask for two reference resistors with ``1000 Ohm`` and ``2000 Ohm`` values.
Calibration can be aborted using ``CTRL + C``.

View File

@@ -0,0 +1,12 @@
.. _usage:
Reflow Controller Usage Guide
=============================
.. toctree::
:maxdepth: 2
command-line
calibration

View File

@@ -46,10 +46,11 @@ CFILES += ui/lcd.c ui/menu.c reflow-menu.c
CFILES += fatfs/diskio.c fatfs/ff.c fatfs/ffsystem.c fatfs/ffunicode.c fatfs/shimatta_sdio_driver/shimatta_sdio.c
CFILES += pid-controller.c oven-driver.c
CFILES += settings/settings.c settings/settings-sd-card.c
CFILES += safety-adc.c
CFILES += safety/safety-adc.c safety/safety-controller.c safety/watchdog.c
DEBUG_DEFINES = -DDEBUGBUILD
RELEASE_DEFINES =
RELEASE_DEFINES =
###################################################################################
ifeq ($(CROSS_COMPILE),)

View File

@@ -1,4 +1,4 @@
/* Reflow Oven Controller
/* Reflow Oven Controller
*
* Copyright (C) 2020 Mario Hüttel <mario.huettel@gmx.net>
*
@@ -18,20 +18,30 @@
* If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @file adc-meas.c
* @brief Implementation of the PT1000 measurement ADC and filtering functions
*/
#include <reflow-controller/adc-meas.h>
#include <stm32/stm32f4xx.h>
#include <cmsis/core_cm4.h>
#include <stm-periph/stm32-gpio-macros.h>
#include <stdlib.h>
#include <stm-periph/clock-enable-manager.h>
#include <reflow-controller/safety/safety-controller.h>
static float pt1000_offset;
static float pt1000_sens_dev;
static bool calibration_active;
static float filter_alpha;
/**
* @brief Filtered PT1000 resistance value.
* @note This value is not yet calibrated. Use @ref adc_pt1000_get_current_resistance to get this value with calibration.
*/
static volatile float pt1000_res_raw_lf;
static volatile bool filter_ready;
static volatile enum adc_pt1000_error pt1000_error = ADC_PT1000_INACTIVE;
static volatile int * volatile streaming_flag_ptr = NULL;
static uint32_t filter_startup_cnt;
static volatile float adc_pt1000_raw_reading_hf;
@@ -42,8 +52,6 @@ volatile float * volatile stream_buffer = NULL;
volatile uint32_t stream_count;
volatile uint32_t stream_pos;
#define ADC_TO_RES(adc) ((float)(adc) / 4096.0f * 2500.0f)
static inline void adc_pt1000_stop_sample_frequency_timer()
{
TIM2->CR1 &= ~TIM_CR1_CEN;
@@ -54,8 +62,8 @@ static inline void adc_pt1000_setup_sample_frequency_timer()
{
rcc_manager_enable_clock(&RCC->APB1ENR, BITMASK_TO_BITNO(RCC_APB1ENR_TIM2EN));
/* Divide 42 MHz peripheral clock by 42 */
TIM2->PSC = (42UL-1UL);
/* Divide 2*42 MHz peripheral clock by 42 */
TIM2->PSC = (84UL-1UL);
/* Reload value */
TIM2->ARR = ADC_PT1000_SAMPLE_CNT_DELAY;
@@ -73,8 +81,8 @@ static inline void adc_pt1000_disable_adc()
ADC_PT1000_PERIPH->CR2 &= ~ADC_CR2_ADON;
DMA2_Stream0->CR = 0;
pt1000_error |= ADC_PT1000_INACTIVE;
safety_controller_report_error_with_key(ERR_FLAG_MEAS_ADC_OFF, MEAS_ADC_SAFETY_FLAG_KEY);
safety_controller_enable_timing_mon(ERR_TIMING_MEAS_ADC, false);
rcc_manager_disable_clock(&RCC->APB2ENR, BITMASK_TO_BITNO(RCC_APB2ENR_ADC3EN));
rcc_manager_disable_clock(&RCC->AHB1ENR, BITMASK_TO_BITNO(ADC_PT1000_PORT_RCC_MASK));
}
@@ -166,13 +174,13 @@ void adc_pt1000_setup_meas()
adc_pt1000_setup_sample_frequency_timer();
pt1000_error &= ~ADC_PT1000_INACTIVE;
safety_controller_ack_flag_with_key(ERR_FLAG_MEAS_ADC_OFF, MEAS_ADC_SAFETY_FLAG_KEY);
}
void adc_pt1000_set_moving_average_filter_param(float alpha)
{
filter_alpha = alpha;
filter_ready = false;
safety_controller_report_error_with_key(ERR_FLAG_MEAS_ADC_UNSTABLE, MEAS_ADC_SAFETY_FLAG_KEY);
filter_startup_cnt = ADC_FILTER_STARTUP_CYCLES;
}
@@ -181,16 +189,22 @@ void adc_pt1000_set_resistance_calibration(float offset, float sensitivity_devia
pt1000_offset = offset;
pt1000_sens_dev = sensitivity_deviation;
calibration_active = active;
if (!calibration_active) {
safety_controller_report_error_with_key(ERR_FLAG_UNCAL, MEAS_ADC_SAFETY_FLAG_KEY);
} else {
safety_controller_ack_flag_with_key(ERR_FLAG_UNCAL, MEAS_ADC_SAFETY_FLAG_KEY);
}
}
void adc_pt1000_get_resistance_calibration(float *offset, float *sensitivity_deviation, bool *active)
{
if (!offset || !sensitivity_deviation || !active)
return;
*offset = pt1000_offset;
*sensitivity_deviation = pt1000_sens_dev;
*active = calibration_active;
if (offset)
*offset = pt1000_offset;
if (sensitivity_deviation)
*sensitivity_deviation = pt1000_sens_dev;
if (active)
*active = calibration_active;
}
static inline float adc_pt1000_apply_calibration(float raw_resistance)
@@ -205,18 +219,23 @@ static inline float adc_pt1000_apply_calibration(float raw_resistance)
int adc_pt1000_get_current_resistance(float *resistance)
{
int ret_val = 0;
bool flag = true;
if (!resistance)
return -1001;
*resistance = adc_pt1000_apply_calibration(pt1000_res_raw_lf);
if (adc_pt1000_check_error()) {
if (safety_controller_get_flags_by_mask(ERR_FLAG_MEAS_ADC_OFF | ERR_FLAG_MEAS_ADC_OVERFLOW |
ERR_FLAG_MEAS_ADC_WATCHDOG)) {
ret_val = -100;
goto return_value;
}
if (!filter_ready) {
(void)safety_controller_get_flag(ERR_FLAG_MEAS_ADC_UNSTABLE, &flag, false);
if (flag) {
ret_val = 2;
goto return_value;
}
@@ -260,25 +279,15 @@ void adc_pt1000_convert_raw_value_array_to_resistance(float *resistance_dest, fl
resistance_dest[i] = ADC_TO_RES(raw_source[i]);
}
enum adc_pt1000_error adc_pt1000_check_error()
{
return pt1000_error;
}
void adc_pt1000_clear_error()
{
pt1000_error &= ~ADC_PT1000_OVERFLOW & ~ADC_PT1000_WATCHDOG_ERROR;
}
void adc_pt1000_disable()
{
adc_pt1000_disable_adc();
adc_pt1000_stop_sample_frequency_timer();
adc_pt1000_disable_dma_stream();
filter_ready = false;
pt1000_res_raw_lf = 0.0f;
pt1000_error |= ADC_PT1000_INACTIVE;
safety_controller_report_error_with_key(ERR_FLAG_MEAS_ADC_OFF, MEAS_ADC_SAFETY_FLAG_KEY);
safety_controller_report_error_with_key(ERR_FLAG_MEAS_ADC_UNSTABLE, MEAS_ADC_SAFETY_FLAG_KEY);
if (streaming_flag_ptr) {
*streaming_flag_ptr = -3;
@@ -288,10 +297,15 @@ void adc_pt1000_disable()
static inline __attribute__((optimize("O3"))) void adc_pt1000_filter(float adc_prefiltered_value)
{
if (!filter_ready && --filter_startup_cnt <= 0)
filter_ready = true;
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);
}
}
pt1000_res_raw_lf = (1.0f-filter_alpha) * pt1000_res_raw_lf + filter_alpha * ADC_TO_RES(adc_prefiltered_value);
safety_controller_report_timing(ERR_TIMING_MEAS_ADC);
}
static inline __attribute__((optimize("O3"))) float adc_pt1000_dma_avg_pre_filter()
@@ -328,7 +342,7 @@ void ADC_IRQHandler(void)
if (adc1_sr & ADC_SR_OVR) {
ADC_PT1000_PERIPH->SR &= ~ADC_SR_OVR;
pt1000_error |= ADC_PT1000_OVERFLOW;
safety_controller_report_error(ERR_FLAG_MEAS_ADC_OVERFLOW);
/* Disable ADC in case of overrrun*/
adc_pt1000_disable();
}
@@ -337,7 +351,7 @@ void ADC_IRQHandler(void)
ADC_PT1000_PERIPH->SR &= ~ADC_SR_AWD;
adc_watchdog_counter++;
if (adc_watchdog_counter >= ADC_PT1000_WATCHDOG_SAMPLE_COUNT)
pt1000_error |= ADC_PT1000_WATCHDOG_ERROR;
safety_controller_report_error(ERR_FLAG_MEAS_ADC_WATCHDOG);
}
}

View File

@@ -26,6 +26,7 @@
#include <arm_math.h>
#include <stdlib.h>
#include <float.h>
#include <reflow-controller/safety/safety-controller.h>
enum calibration_shell_state {CAL_START = 0, CAL_WAIT_RES1, CAL_MEAS_RES1, CAL_WAIT_RES2, CAL_MEAS_RES2};
@@ -121,6 +122,8 @@ shellmatta_retCode_t calibration_sequence_shell_cmd(shellmatta_handle_t shell, c
{
(void)arg;
(void)len;
bool error_occured;
const enum safety_flag meas_adc_err_mask = ERR_FLAG_MEAS_ADC_OFF | ERR_FLAG_MEAS_ADC_WATCHDOG;
/* This stores the current state of the calibration process */
static enum calibration_shell_state cal_state = CAL_START;
@@ -139,7 +142,7 @@ shellmatta_retCode_t calibration_sequence_shell_cmd(shellmatta_handle_t shell, c
switch (cal_state) {
case CAL_START:
/* Clear errors of PT1000 reading */
adc_pt1000_clear_error();
safety_controller_ack_flag(ERR_FLAG_MEAS_ADC_WATCHDOG);
shellmatta_printf(shell, "Starting calibration: Insert 1000 Ohm calibration resistor and press ENTER\r\n");
cal_state = CAL_WAIT_RES1;
ret_val = SHELLMATTA_CONTINUE;
@@ -154,7 +157,7 @@ shellmatta_retCode_t calibration_sequence_shell_cmd(shellmatta_handle_t shell, c
cal_state = CAL_MEAS_RES1;
ret_val = SHELLMATTA_BUSY;
shellmatta_printf(shell, "Measurement...\r\n");
adc_pt1000_clear_error();
safety_controller_ack_flag(ERR_FLAG_MEAS_ADC_WATCHDOG);
data_buffer = calibration_acquire_data_start(512UL, &flag);
break;
} else if (stdin_data[i] == '\x03') {
@@ -179,8 +182,9 @@ shellmatta_retCode_t calibration_sequence_shell_cmd(shellmatta_handle_t shell, c
cal_state = CAL_MEAS_RES1;
} else if (res == 0) {
shellmatta_printf(shell, "R=%.2f, Noise peak-peak: %.2f\r\n", mu, dev);
if (adc_pt1000_check_error() != ADC_PT1000_NO_ERR) {
shellmatta_printf(shell, "Error in resistance measurement: %d", adc_pt1000_check_error());
error_occured = safety_controller_get_flags_by_mask(meas_adc_err_mask);
if (error_occured) {
shellmatta_printf(shell, "Error in resistance measurement");
ret_val = SHELLMATTA_OK;
cal_state = CAL_START;
} else {
@@ -189,7 +193,7 @@ shellmatta_retCode_t calibration_sequence_shell_cmd(shellmatta_handle_t shell, c
cal_state = CAL_WAIT_RES2;
}
} else {
shellmatta_printf(shell, "Error in resistance measurement: %d", adc_pt1000_check_error());
shellmatta_printf(shell, "Error in resistance measurement");
ret_val = SHELLMATTA_OK;
cal_state = CAL_START;
}
@@ -204,7 +208,7 @@ shellmatta_retCode_t calibration_sequence_shell_cmd(shellmatta_handle_t shell, c
cal_state = CAL_MEAS_RES2;
ret_val = SHELLMATTA_BUSY;
shellmatta_printf(shell, "Measurement...\r\n");
adc_pt1000_clear_error();
safety_controller_ack_flag(ERR_FLAG_MEAS_ADC_WATCHDOG);
data_buffer = calibration_acquire_data_start(512UL, &flag);
break;
} else if (stdin_data[i] == '\x03') {
@@ -229,8 +233,9 @@ shellmatta_retCode_t calibration_sequence_shell_cmd(shellmatta_handle_t shell, c
cal_state = CAL_MEAS_RES2;
} else if (res == 0) {
shellmatta_printf(shell, "R=%.2f, Noise peak-peak: %.2f\r\n", mu2, dev2);
if (adc_pt1000_check_error() != ADC_PT1000_NO_ERR) {
shellmatta_printf(shell, "Error in resistance measurement: %d", adc_pt1000_check_error());
error_occured = safety_controller_get_flags_by_mask(meas_adc_err_mask);
if (error_occured) {
shellmatta_printf(shell, "Error in resistance measurement");
ret_val = SHELLMATTA_OK;
cal_state = CAL_START;
} else {
@@ -250,7 +255,7 @@ shellmatta_retCode_t calibration_sequence_shell_cmd(shellmatta_handle_t shell, c
adc_pt1000_set_resistance_calibration(offset, sens_dev, true);
}
} else {
shellmatta_printf(shell, "Error in resistance measurement: %d", adc_pt1000_check_error());
shellmatta_printf(shell, "Error in resistance measurement");
ret_val = SHELLMATTA_OK;
cal_state = CAL_START;
}

View File

@@ -1,4 +1,4 @@
# Doxyfile 1.8.17
# Doxyfile 1.8.18
# This file describes the settings to be used by the documentation system
# doxygen (www.doxygen.org) for a project.
@@ -263,12 +263,6 @@ TAB_SIZE = 8
ALIASES =
# This tag can be used to specify a number of word-keyword mappings (TCL only).
# A mapping has the form "name=value". For example adding "class=itcl::class"
# will allow you to use the command class in the itcl::class meaning.
TCL_SUBST =
# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
# only. Doxygen will then generate output that is more tailored for C. For
# instance, some of the names that are used will be different. The list of all
@@ -310,13 +304,13 @@ OPTIMIZE_OUTPUT_SLICE = NO
# extension. Doxygen has a built-in mapping, but you can override or extend it
# using this tag. The format is ext=language, where ext is a file extension, and
# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,
# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice,
# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL,
# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
# tries to guess whether the code is fixed or free formatted code, this is the
# default for Fortran type files), VHDL, tcl. For instance to make doxygen treat
# .inc files as Fortran files (default is PHP), and .f files as C (default is
# Fortran), use: inc=Fortran f=C.
# default for Fortran type files). For instance to make doxygen treat .inc files
# as Fortran files (default is PHP), and .f files as C (default is Fortran),
# use: inc=Fortran f=C.
#
# Note: For files without extension you can use no_extension as a placeholder.
#
@@ -853,7 +847,7 @@ INPUT_ENCODING = UTF-8
# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment),
# *.doc (to be provided as doxygen C comment), *.txt (to be provided as doxygen
# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f, *.for, *.tcl, *.vhd,
# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd,
# *.vhdl, *.ucf, *.qsf and *.ice.
FILE_PATTERNS = *.c \
@@ -1543,6 +1537,17 @@ TREEVIEW_WIDTH = 250
EXT_LINKS_IN_WINDOW = NO
# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg
# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see
# https://inkscape.org) to generate formulas as SVG images instead of PNGs for
# the HTML output. These images will generally look nicer at scaled resolutions.
# Possible values are: png The default and svg Looks nicer but requires the
# pdf2svg tool.
# The default value is: png.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_FORMULA_FORMAT = png
# Use this tag to change the font size of LaTeX formulas included as images in
# the HTML documentation. When you change the font size after a successful
# doxygen run you need to manually remove any form_*.png images from the HTML
@@ -1598,7 +1603,7 @@ MATHJAX_FORMAT = HTML-CSS
# Content Delivery Network so you can quickly see the result without installing
# MathJax. However, it is strongly recommended to install a local copy of
# MathJax from https://www.mathjax.org before deployment.
# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/.
# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2.
# This tag requires that the tag USE_MATHJAX is set to YES.
MATHJAX_RELPATH = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/
@@ -1711,7 +1716,7 @@ EXTRA_SEARCH_MAPPINGS =
# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
# The default value is: YES.
GENERATE_LATEX = YES
GENERATE_LATEX = NO
# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
@@ -2013,7 +2018,7 @@ MAN_LINKS = NO
# captures the structure of the code including all documentation.
# The default value is: NO.
GENERATE_XML = NO
GENERATE_XML = YES
# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of

View File

@@ -11,10 +11,17 @@ cd "$DIR"
export PROJECT_NUMBER=`git describe --always --tags --dirty`
if [ $# != 1 ]; then
configfile="Doxyconfig"
if [[ -z "$1" ]]; then
export OUTPUT_DIRECTORY="./output"
else
export OUTPUT_DIRECTORY="$1"
fi
doxygen Doxyconfig
if [[ -n "$2" ]]; then
configfile="$2"
fi
doxygen "$configfile"

View File

@@ -710,21 +710,16 @@ DRESULT sdio_disk_write(const BYTE *buff, DWORD sector, UINT count)
addr = (card_info.type == SD_V2_HC ? (sector) : (sector * 512));
while (count) {
do {
ret = sdio_check_status_register_cmd13(card_info.rca, &status.value);
} while (status.statusstruct.CURRENT_STATE == CURRENT_STATE_PRG ||
status.statusstruct.CURRENT_STATE == CURRENT_STATE_RCV ||
!ret);
ret = sdio_check_status_register_cmd13(card_info.rca, &status.value);
if (ret)
return RES_ERROR;
if (status.statusstruct.CURRENT_STATE == CURRENT_STATE_STBY) {
if (sdio_send_select_card_cmd7(card_info.rca))
return RES_ERROR;
}
do {
sdio_check_status_register_cmd13(card_info.rca, &status.value);
} while (status.statusstruct.READY_FOR_DATA != 1);
ret = sdio_send_write_block_cmd24(addr);
if (ret) {
return RES_ERROR;

View File

@@ -12,9 +12,9 @@
//4 bit: 4
#define BUSWIDTH 4 //4
//Initial Transfer CLK (ca. 400kHz)
#define INITCLK 130 //120
#define INITCLK 140 //120
//Working CLK (Maximum)
#define WORKCLK 50 //0
#define WORKCLK 45 //0
//Data Timeout in CLK Cycles
#define DTIMEOUT 0x3000 //150
//DMA Stream used for TX and RX DMA2 Stream 3 or 6 possible

View File

@@ -150,7 +150,7 @@
*/
#define FF_FS_RPATH 0
#define FF_FS_RPATH 2
/* This option configures support for relative path.
/
/ 0: Disable relative path and remove related functions.

View File

@@ -36,4 +36,6 @@
#define ABS(a) ((a) < 0 ? (-1*(a)) : (a))
#define is_power_of_two(num) ((num) && !((num) & ((num) - 1)))
#endif /* __HELPER_MACROS_H__ */

View File

@@ -18,6 +18,10 @@
* If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @file adc-meas.h
*/
#ifndef __ADCMEAS_H__
#define __ADCMEAS_H__
@@ -90,7 +94,10 @@
*/
#define ADC_PT1000_WATCHDOG_SAMPLE_COUNT 25U
enum adc_pt1000_error {ADC_PT1000_NO_ERR= 0, ADC_PT1000_WATCHDOG_ERROR=(1UL<<0), ADC_PT1000_OVERFLOW=(1UL<<1), ADC_PT1000_INACTIVE = (1UL<<2)};
/**
* @brief Conversion macro: ADC value to resistance
*/
#define ADC_TO_RES(adc) ((float)(adc) / 4096.0f * 2500.0f)
/**
* @brief This function sets up the ADC measurement fo the external PT1000 temperature sensor
@@ -136,15 +143,13 @@ void adc_pt1000_set_resistance_calibration(float offset, float sensitivity_devia
void adc_pt1000_get_resistance_calibration(float *offset, float *sensitivity_deviation, bool *active);
/**
* @brief Get the current reistance value
* @brief Get the current resistance value
*
* If the reistance calibration is enabled, this function applies the calculations of the raw resistance reading and
* If the resistance calibration is enabled, this function applies the calculations of the raw resistance reading and
* returns the corrected value.
*
* If an ADC error is set, the status is negative. The status is 2 during the first measurements with a given filter setting. Technically, the resistance value is
* correct but the filter is not stable yet.
* Use adc_pt1000_check_error to check the error and reinitialize the ADC.
*
*
* @param[out] resistance Resistance output in Ohms
* @return Status
@@ -160,19 +165,17 @@ int adc_pt1000_get_current_resistance(float *resistance);
*/
int adc_pt1000_stream_raw_value_to_memory(volatile float *adc_array, uint32_t length, volatile int *flag_to_set);
void adc_pt1000_convert_raw_value_array_to_resistance(float *resistance_dest, float *raw_source, uint32_t count);
/**
* @brief Check if the ADC measurement experienced any kind of error (DMA, Analog Watchdog, etc...)
* @brief Convert an array of raw adc values to resistance values
*
* In case of an error, it may be necessary to call adc_pt1000_setup_meas() again in order to recover from the error
* In case \p resistance_dest is NULL, the conversion is done inplace in the
* \p raw_source array.
*
* @param resistance_dest Destination. Maybe NULL.
* @param raw_source Source array
* @param count Number of values to convert
*/
enum adc_pt1000_error adc_pt1000_check_error();
/**
* @brief Clear the error status of the PT1000 measurement
*/
void adc_pt1000_clear_error();
void adc_pt1000_convert_raw_value_array_to_resistance(float *resistance_dest, float *raw_source, uint32_t count);
/**
* @brief Disable the PT1000 measurement

View File

@@ -25,32 +25,9 @@
#include <stdbool.h>
#include <reflow-controller/pid-controller.h>
enum oven_pid_error_report {
OVEN_PID_NO_ERROR = 0,
OVEN_PID_ERR_PT1000_ADC_WATCHDOG = (1<<0),
OVEN_PID_ERR_PT1000_ADC_OFF = (1<<1),
OVEN_PID_ERR_PT1000_OTHER = (1<<2),
OVEN_PID_ERR_VREF_TOL = (1<<3),
OVEN_PID_ERR_OVERTEMP = (1<<4),
};
struct oven_pid_errors {
bool generic_error;
bool pt1000_adc_watchdog;
bool pt1000_adc_off;
bool pt1000_other;
bool vref_tol;
bool controller_overtemp;
};
struct oven_pid_status {
bool active;
bool error_set;
struct oven_pid_errors error_flags;
float target_temp;
float current_temp;
uint64_t timestamp_last_run;
};
enum oven_pid_status {OVEN_PID_DEACTIVATED,
OVEN_PID_RUNNING,
OVEN_PID_ABORTED};
void oven_driver_init(void);
@@ -66,8 +43,8 @@ void oven_pid_handle(float target_temp);
void oven_pid_stop();
void oven_pid_report_error(enum oven_pid_error_report report);
void oven_driver_apply_power_level(void);
const struct oven_pid_status *oven_pid_get_status(void);
enum oven_pid_status oven_pid_get_status(void);
#endif /* __OVEN_DRIVER_H__ */

View File

@@ -18,32 +18,18 @@
* If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @addtogroup safety-adc
* @{
*/
#ifndef __SAFETY_ADC_H__
#define __SAFETY_ADC_H__
#include <stdint.h>
#include <stdbool.h>
#define SAFETY_ADC_VREF_MVOLT (2500.0f)
#define SAFETY_ADC_VREF_TOL_MVOLT (100.0f)
#define SAFETY_ADC_TEMP_LOW_LIM (0.0f)
#define SAFETY_ADC_TEMP_HIGH_LIM (65.0f)
enum safety_adc_meas_channel {SAFETY_ADC_MEAS_VREF, SAFETY_ADC_MEAS_TEMP};
enum safety_adc_check_result {
SAFETY_ADC_CHECK_OK = 0UL,
SAFETY_ADC_CHECK_VREF_LOW = (1U<<0),
SAFETY_ADC_CHECK_VREF_HIGH = (1U<<1),
SAFETY_ADC_CHECK_TEMP_LOW = (1U<<2),
SAFETY_ADC_CHECK_TEMP_HIGH = (1U<<3),
SAFETY_ADC_INTERNAL_ERROR = (1U<<4),
};
extern enum safety_adc_check_result global_safety_adc_status;
enum safety_adc_check_result safety_adc_get_errors();
void safety_adc_clear_errors(void);
void safety_adc_init();
@@ -58,13 +44,10 @@ void safety_adc_trigger_meas(enum safety_adc_meas_channel measurement);
*/
int safety_adc_poll_result(uint16_t *adc_result);
enum safety_adc_check_result safety_adc_check_results(uint16_t vref_result, uint16_t temp_result,
float *vref_calculated, float *temp_calculated);
enum safety_adc_check_result handle_safety_adc();
float safety_adc_get_temp();
float safety_adc_get_vref();
float safety_adc_convert_channel(enum safety_adc_meas_channel channel, uint16_t analog_value);
#endif /* __SAFETY_ADC_H__ */
/** @} */

View File

@@ -0,0 +1,95 @@
/* Reflow Oven Controller
*
* Copyright (C) 2020 Mario Hüttel <mario.huettel@gmx.net>
*
* This file is part of the Reflow Oven Controller Project.
*
* The reflow oven controller is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* The Reflow Oven Control Firmware is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with the reflow oven controller project.
* If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __SAFETY_CONFIG_H__
#define __SAFETY_CONFIG_H__
enum safety_flag {
ERR_FLAG_MEAS_ADC_OFF = (1<<0),
ERR_FLAG_MEAS_ADC_OVERFLOW = (1<<1),
ERR_FLAG_MEAS_ADC_WATCHDOG = (1<<2),
ERR_FLAG_MEAS_ADC_UNSTABLE = (1<<3),
ERR_FLAG_TIMING_PID = (1<<4),
ERR_FLAG_TIMING_MEAS_ADC = (1<<5),
ERR_FLAG_AMON_VREF = (1<<6),
ERR_FLAG_AMON_UC_TEMP = (1<<7),
ERR_FLAG_STACK = (1<<8),
ERR_FLAG_SAFETY_ADC = (1<<9),
ERR_FLAG_SYSTICK = (1<<10),
ERR_FLAG_WTCHDG_FIRED = (1<<11),
ERR_FLAG_UNCAL = (1<<12),
ERR_FLAG_DEBUG = (1<<13),
ERR_FLAG_TIMING_MAIN_LOOP = (1<<14),
};
enum timing_monitor {
ERR_TIMING_PID = (1<<0),
ERR_TIMING_MEAS_ADC = (1<<1),
ERR_TIMING_SAFETY_ADC = (1<<2),
ERR_TIMING_MAIN_LOOP = (1<<3),
};
enum analog_value_monitor {
ERR_AMON_VREF = (1<<0),
ERR_AMON_UC_TEMP = (1<<1),
};
/**
* @brief Magic key used to reset the watchdog using the @ref watchdog_ack function
*/
#define WATCHDOG_MAGIC_KEY 0x1a2c56F4
#ifdef DEBUGBUILD
/**
* @brief If one, the watchdog is halted whenever the core is halted by the debugger.
*
* This is only applicable in a debug build. In release mode, the watchdog stays always enabled
*/
#define WATCHDOG_HALT_DEBUG (1)
#else
#define WATCHDOG_HALT_DEBUG (0)
#endif
#define WATCHDOG_PRESCALER 4
#define SAFETY_MIN_STACK_FREE 0x100
#define PID_CONTROLLER_ERR_CAREMASK (ERR_FLAG_STACK | ERR_FLAG_AMON_UC_TEMP | ERR_FLAG_AMON_VREF | \
ERR_FLAG_TIMING_PID | ERR_FLAG_TIMING_MEAS_ADC | ERR_FLAG_MEAS_ADC_OFF | \
ERR_FLAG_MEAS_ADC_OVERFLOW)
#define HALTING_CAREMASK (ERR_FLAG_STACK | ERR_FLAG_AMON_UC_TEMP)
#define SAFETY_ADC_VREF_MVOLT (2500.0f)
#define SAFETY_ADC_VREF_TOL_MVOLT (100.0f)
#define SAFETY_ADC_TEMP_LOW_LIM (0.0f)
#define SAFETY_ADC_TEMP_HIGH_LIM (65.0f)
/**
* @brief Key used to lock the safefety flags from external ack'ing
*/
#define MEAS_ADC_SAFETY_FLAG_KEY 0xe554dac3UL
#define SAFETY_CONTROLLER_ADC_DELAY_MS 120
#endif /* __SAFETY_CONFIG_H__ */

View File

@@ -0,0 +1,113 @@
/* Reflow Oven Controller
*
* Copyright (C) 2020 Mario Hüttel <mario.huettel@gmx.net>
*
* This file is part of the Reflow Oven Controller Project.
*
* The reflow oven controller is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* The Reflow Oven Control Firmware is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with the reflow oven controller project.
* If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @addtogroup safety-controller
* @{
*/
#ifndef __SAFETY_CONTROLLER_H__
#define __SAFETY_CONTROLLER_H__
#include <reflow-controller/safety/safety-config.h>
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
enum analog_monitor_status {ANALOG_MONITOR_OK = 0,
ANALOG_MONITOR_ERROR,
ANALOG_MONITOR_INACTIVE,
ANALOG_MONITOR_OVER,
ANALOG_MONITOR_UNDER};
struct analog_monitor_info {
float value;
float min;
float max;
enum analog_monitor_status status;
uint64_t timestamp;
};
struct timing_monitor_info {
uint64_t last_run;
uint64_t min;
uint64_t max;
bool enabled;
uint64_t delta;
};
/**
* @brief Initialize the safety controller
*
* After a call to this function the controller is iniotlaized and the watchdog is set up.
* You have to call safety_controller_handle
* If this function fails, it will hang, because errors in the safety controller are not recoverable
*/
void safety_controller_init();
/**
* @brief Handle the safety controller.
* @note This function must be executed periodically in order to prevent the watchdog from resetting the firmware
* @return 0 if successful
*/
int safety_controller_handle();
int safety_controller_report_error(enum safety_flag flag);
int safety_controller_report_error_with_key(enum safety_flag flag, uint32_t key);
void safety_controller_report_timing(enum timing_monitor monitor);
void safety_controller_report_analog_value(enum analog_value_monitor monitor, float value);
int safety_controller_enable_timing_mon(enum timing_monitor monitor, bool enable);
enum analog_monitor_status safety_controller_get_analog_mon_value(enum analog_value_monitor monitor, float *value);
int safety_controller_get_flag(enum safety_flag flag, bool *status, bool try_ack);
int safety_controller_ack_flag(enum safety_flag flag);
int safety_controller_ack_flag_with_key(enum safety_flag flag, uint32_t key);
bool safety_controller_get_flags_by_mask(enum safety_flag mask);
uint32_t safety_controller_get_flag_count();
uint32_t safety_controller_get_analog_monitor_count();
int safety_controller_get_flag_name_by_index(uint32_t index, char *buffer, size_t buffsize);
int safety_controller_get_flag_by_index(uint32_t index, bool *status, enum safety_flag *flag_enum);
int safety_controller_get_analog_mon_by_index(uint32_t index, struct analog_monitor_info *info);
int safety_controller_get_analog_mon_name_by_index(uint32_t index, char *buffer, size_t buffsize);
int safety_controller_get_timing_mon_by_index(uint32_t index, struct timing_monitor_info *info);
int safety_controller_get_timing_mon_name_by_index(uint32_t index, char *buffer, size_t buffsize);
uint32_t safety_controller_get_timing_monitor_count();
#endif /* __SAFETY_CONTROLLER_H__ */
/** @} */

View File

@@ -0,0 +1,50 @@
/* 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 __WATCHDOG_H__
#define __WATCHDOG_H__
#include <reflow-controller/safety/safety-config.h>
#include <stdint.h>
#include <stdbool.h>
/**
* @brief Setup the watchdog for the safety controller
* @param Prescaler to use for the 32 KHz LSI clock
* @return 0 if successful
* @note Once the watchdog is enabled, it cannot be turned off!
*/
int watchdog_setup(uint8_t prescaler);
/**
* @brief Reset watchdog counter
* @param magic Magic value to prevent this fuinction from being called randomly
* @return 0 if successful
*/
int watchdog_ack(uint32_t magic);
/**
* @brief Check if reset was generated by the watchdog.
* @note This also clears the relevant flag, so the function will reutrn false when called a second time
* @return
*/
bool watchdog_check_reset_source(void);
#endif /* __WATCHDOG_H__ */

View File

@@ -21,4 +21,18 @@
#ifndef __SETTINGS_SETTINGS_SD_CARD_H__
#define __SETTINGS_SETTINGS_SD_CARD_H__
#include <stdbool.h>
#define CALIBRATION_FILE_NAME "settings.ini"
int sd_card_settings_save_calibration(float sens_deviation, float offset, bool active);
/**
* @brief Try and load calibration from SD card
* @param sens_deviation
* @param offset
* @return 0 if files found -1 if files errorneous, -2 if no files found
*/
int sd_card_settings_try_load_calibration(float *sens_deviation, float *offset);
#endif /* __SETTINGS_SETTINGS_SD_CARD_H__ */

View File

@@ -22,6 +22,16 @@
#ifndef __SETTINGS_SETTINGS_H__
#define __SETTINGS_SETTINGS_H__
int settings_save_calibration();
#include <stdbool.h>
/**
* @brief Save the calibration
* @param sens_deviation
* @param offset
* @return 0 if successful, -1 if generic error, -2 if medium unavailable
*/
int settings_save_calibration(float sens_deviation, float offset, bool active);
int settings_load_calibration(float *sens_dev, float *offset);
#endif /* __SETTINGS_SETTINGS_H__ */

View File

@@ -39,6 +39,8 @@
#define LCD_SHIMATTA_STRING "\xBC\xCF\xAF\xC0"
#define LCD_DEGREE_SYMBOL_STRING "\xDF"
#define LCD_DEGREE_SYMBOL_CHAR '\xDF'
#define LCD_OHM_SYMBOL_CHAR '\xF4'
#define LCD_OHM_SYMBOL_STRING "\xF4"
enum lcd_fsm_ret {LCD_FSM_NOP, LCD_FSM_CALL_AGAIN, LCD_FSM_WAIT_CALL};

View File

@@ -64,5 +64,4 @@ 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__ */

View File

@@ -41,11 +41,10 @@
#include <stm-periph/uart.h>
#include <reflow-controller/shell-uart-config.h>
#include <reflow-controller/oven-driver.h>
#include <reflow-controller/safety-adc.h>
#include <fatfs/ff.h>
#include <reflow-controller/reflow-menu.h>
bool global_error_state;
#include <reflow-controller/safety/safety-controller.h>
#include <reflow-controller/settings/settings.h>
static void setup_nvic_priorities(void)
{
@@ -74,6 +73,9 @@ static inline void uart_gpio_config(void)
SHELL_UART_PORT->MODER |= ALTFUNC(SHELL_UART_RX_PIN) | ALTFUNC(SHELL_UART_TX_PIN);
SETAF(SHELL_UART_PORT, SHELL_UART_RX_PIN, SHELL_UART_RX_PIN_ALTFUNC);
SETAF(SHELL_UART_PORT, SHELL_UART_TX_PIN, SHELL_UART_TX_PIN_ALTFUNC);
/* Setup Pullup resistor at UART RX */
SHELL_UART_PORT->PUPDR |= PULLUP(SHELL_UART_RX_PIN);
#endif
}
@@ -145,18 +147,21 @@ static inline void setup_system(void)
setup_nvic_priorities();
systick_setup();
adc_pt1000_setup_meas();
oven_driver_init();
digio_setup_default_all();
led_setup();
loudspeaker_setup();
reflow_menu_init();
safety_adc_init();
uart_gpio_config();
setup_shell_uart(&shell_uart);
setup_unused_pins();
safety_controller_init();
adc_pt1000_setup_meas();
}
static void handle_shell_uart_input(shellmatta_handle_t shell_handle)
@@ -185,77 +190,61 @@ static void zero_ccm_ram(void)
ptr[i] = 0UL;
}
/**
* @brief This function sets the appropriate error flags in the oven PID controller
* depending on the Safety ADC measurements.
* The PID controller's error flags have to be cleared via the GUI by either starting a new RUN or explicitly
* ack'ing these errors.
*/
static void propagate_safety_adc_error_to_oven_pid(void)
{
enum safety_adc_check_result safety_adc_result;
safety_adc_result = safety_adc_get_errors();
if (safety_adc_result & SAFETY_ADC_CHECK_TEMP_LOW ||
safety_adc_result & SAFETY_ADC_CHECK_TEMP_HIGH)
oven_pid_report_error(OVEN_PID_ERR_OVERTEMP);
if (safety_adc_result & SAFETY_ADC_CHECK_VREF_LOW ||
safety_adc_result & SAFETY_ADC_CHECK_VREF_HIGH)
oven_pid_report_error(OVEN_PID_ERR_VREF_TOL);
if (safety_adc_result & SAFETY_ADC_INTERNAL_ERROR)
oven_pid_report_error(0);
}
int main(void)
{
bool cal_active;
float offset;
float sens;
int status;
bool sd_card_mounted = false;
bool sd_old;
shellmatta_handle_t shell_handle;
int menu_wait_request;
uint64_t quarter_sec_timestamp = 0ULL;
const struct oven_pid_status *pid_status;
enum adc_pt1000_error pt1000_status;
zero_ccm_ram();
setup_system();
global_error_state = false;
shell_handle = shell_init(write_shell_callback);
shell_print_motd(shell_handle);
while (1) {
sd_card_mounted = mount_sd_card_if_avail(sd_card_mounted);
pid_status = oven_pid_get_status();
if (systick_ticks_have_passed(quarter_sec_timestamp, 250)) {
quarter_sec_timestamp = systick_get_global_tick();
sd_old = sd_card_mounted;
sd_card_mounted = mount_sd_card_if_avail(sd_card_mounted);
(void)handle_safety_adc();
propagate_safety_adc_error_to_oven_pid();
if (global_error_state)
led_set(0, !led_get(0));
else
if (sd_old != sd_card_mounted) {
led_set(0, 1);
} else {
led_set(0, 0);
}
if (sd_card_mounted && !sd_old) {
adc_pt1000_get_resistance_calibration(NULL, NULL, &cal_active);
if (!cal_active) {
status = settings_load_calibration(&sens, &offset);
if (!status) {
adc_pt1000_set_resistance_calibration(offset, sens, true);
}
}
}
quarter_sec_timestamp = systick_get_global_tick();
}
pt1000_status = adc_pt1000_check_error();
global_error_state = pid_status->error_set || !!safety_adc_get_errors() || !!pt1000_status;
menu_wait_request = reflow_menu_handle();
/* Deactivate oven output in case of error! */
if (!pid_status->active || global_error_state)
oven_driver_set_power(0U);
handle_shell_uart_input(shell_handle);
safety_controller_handle();
oven_driver_set_power(0);
oven_driver_apply_power_level();
safety_controller_report_timing(ERR_TIMING_MAIN_LOOP);
if (menu_wait_request)
__WFI();
else
__NOP();
}

View File

@@ -24,16 +24,12 @@
#include <reflow-controller/systick.h>
#include <reflow-controller/adc-meas.h>
#include <reflow-controller/temp-converter.h>
#include <reflow-controller/safety/safety-controller.h>
static struct pid_controller oven_pid;
static struct oven_pid_status oven_pid_current_status = {
.active = false,
.error_set = false,
.target_temp = 0.0f,
.current_temp = 0.0f,
.timestamp_last_run = 0ULL
};
static bool oven_pid_running = false;
static bool oven_pid_aborted = false;
static uint8_t oven_driver_power_level = 0U;
void oven_driver_init()
{
@@ -60,7 +56,12 @@ void oven_driver_set_power(uint8_t power)
if (power > 100U)
power = 100U;
OVEN_CONTROLLER_PWM_TIMER->CCR3 = power * 10;
oven_driver_power_level = power;
}
void oven_driver_apply_power_level(void)
{
OVEN_CONTROLLER_PWM_TIMER->CCR3 = oven_driver_power_level * 10;
}
void oven_driver_disable()
@@ -71,93 +72,52 @@ void oven_driver_disable()
rcc_manager_disable_clock(&RCC->APB1ENR, BITMASK_TO_BITNO(OVEN_CONTROLLER_TIM_RCC_MASK));
}
void oven_pid_ack_errors(void)
{
oven_pid_current_status.error_set = false;
oven_pid_current_status.error_flags.vref_tol = false;
oven_pid_current_status.error_flags.pt1000_other = false;
oven_pid_current_status.error_flags.generic_error = false;
oven_pid_current_status.error_flags.pt1000_adc_off = false;
oven_pid_current_status.error_flags.controller_overtemp = false;
oven_pid_current_status.error_flags.pt1000_adc_watchdog = false;
}
void oven_pid_init(struct pid_controller *controller_to_copy)
{
pid_copy(&oven_pid, controller_to_copy);
oven_pid.output_sat_min = 0.0f;
oven_pid.output_sat_max = 100.0f;
oven_pid_current_status.timestamp_last_run = 0ULL;
oven_pid_current_status.active = true;
oven_pid_ack_errors();
oven_pid_running = true;
oven_pid_aborted = false;
safety_controller_report_timing(ERR_TIMING_PID);
}
void oven_pid_handle(float target_temp)
{
float pid_out;
float current_temp;
int resistance_status;
enum adc_pt1000_error pt1000_error;
if (oven_pid_current_status.active && !oven_pid_current_status.error_set) {
if (systick_ticks_have_passed(oven_pid_current_status.timestamp_last_run,
(uint64_t)(oven_pid.sample_period * 1000))) {
resistance_status = adc_pt1000_get_current_resistance(&current_temp);
if (resistance_status < 0) {
oven_driver_set_power(0);
pt1000_error = adc_pt1000_check_error();
if (pt1000_error & ADC_PT1000_WATCHDOG_ERROR)
oven_pid_report_error(OVEN_PID_ERR_PT1000_ADC_WATCHDOG);
if (pt1000_error & ADC_PT1000_INACTIVE)
oven_pid_report_error(OVEN_PID_ERR_PT1000_ADC_OFF);
if (pt1000_error & ADC_PT1000_OVERFLOW)
oven_pid_report_error(OVEN_PID_ERR_PT1000_OTHER);
return;
}
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))) {
/* No need to check. Safety controller will monitor this */
(void)adc_pt1000_get_current_resistance(&current_temp);
(void)temp_converter_convert_resistance_to_temp(current_temp, &current_temp);
pid_out = pid_sample(&oven_pid, target_temp - current_temp);
oven_driver_set_power((uint8_t)pid_out);
oven_pid_current_status.timestamp_last_run = systick_get_global_tick();
oven_pid_current_status.target_temp = target_temp;
oven_pid_current_status.current_temp = current_temp;
timestamp_last_run = systick_get_global_tick();
safety_controller_report_timing(ERR_TIMING_PID);
}
}
}
void oven_pid_report_error(enum oven_pid_error_report report)
{
struct oven_pid_errors *e = &oven_pid_current_status.error_flags;
oven_pid_current_status.active = false;
oven_pid_current_status.error_set = true;
if (report == 0) {
e->generic_error = true;
}
if (report & OVEN_PID_ERR_OVERTEMP)
e->controller_overtemp = true;
if (report & OVEN_PID_ERR_VREF_TOL)
e->controller_overtemp = true;
if (report & OVEN_PID_ERR_PT1000_OTHER)
e->pt1000_other = true;
if (report & OVEN_PID_ERR_PT1000_ADC_OFF)
e->pt1000_adc_off = true;
if (report & OVEN_PID_ERR_PT1000_ADC_WATCHDOG)
e->pt1000_adc_watchdog = true;
}
const struct oven_pid_status *oven_pid_get_status()
{
return &oven_pid_current_status;
}
void oven_pid_stop()
{
oven_pid_current_status.active = false;
oven_pid_current_status.target_temp = 0.0f;
oven_pid_current_status.current_temp = 0.0f;
oven_pid_running = false;
safety_controller_enable_timing_mon(ERR_TIMING_PID, false);
}
enum oven_pid_status oven_pid_get_status(void)
{
enum oven_pid_status ret = OVEN_PID_ABORTED;
if (oven_pid_running && !oven_pid_aborted)
ret = OVEN_PID_RUNNING;
else if (!oven_pid_running && !oven_pid_aborted)
ret = OVEN_PID_DEACTIVATED;
return ret;
}

View File

@@ -24,7 +24,7 @@
#include <reflow-controller/rotary-encoder.h>
#include <reflow-controller/systick.h>
#include <reflow-controller/adc-meas.h>
#include <reflow-controller/safety-adc.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>
@@ -71,7 +71,7 @@ static void reflow_menu_monitor(struct lcd_menu *menu, enum menu_entry_func_entr
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", 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);
@@ -90,11 +90,11 @@ static void reflow_menu_monitor(struct lcd_menu *menu, enum menu_entry_func_entr
snprintf(line, sizeof(line), "Temp: %s%.1f " LCD_DEGREE_SYMBOL_STRING "C", prefix, tmp);
menu->update_display(1, line);
tmp = safety_adc_get_temp();
(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);
tmp = safety_adc_get_vref();
(void)safety_controller_get_analog_mon_value(ERR_AMON_VREF, &tmp);
snprintf(line, sizeof(line), "Vref: %.1f mV", tmp);
menu->update_display(3, line);
}
@@ -206,6 +206,7 @@ static void reflow_menu_root_entry(struct lcd_menu *menu, enum menu_entry_func_e
(void)parent;
static struct menu_list list;
static bool button_valid;
static bool menu_changed = true;
static const char * const root_entry_names[] = {
"About",
"Monitoring",
@@ -219,6 +220,7 @@ static void reflow_menu_root_entry(struct lcd_menu *menu, enum menu_entry_func_e
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);
@@ -245,12 +247,15 @@ static void reflow_menu_root_entry(struct lcd_menu *menu, enum menu_entry_func_e
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;
}
menu_list_display(&list, 1, 3);
if (menu_changed)
menu_list_display(&list, 1, 3);
}
int reflow_menu_handle()

View File

@@ -1,188 +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/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>
enum safety_adc_check_result global_safety_adc_status;
enum safety_adc_check_result safety_adc_get_errors()
{
return global_safety_adc_status;
}
void safety_adc_clear_errors(void)
{
global_safety_adc_status = SAFETY_ADC_CHECK_OK;
}
void safety_adc_init()
{
rcc_manager_enable_clock(&RCC->APB2ENR, BITMASK_TO_BITNO(SAFETY_ADC_ADC_RCC_MASK));
safety_adc_clear_errors();
/* 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;
/* Standard sequence. One measurement */
SAFETY_ADC_ADC_PERIPHERAL->SQR1 = 0UL;
}
void safety_adc_deinit()
{
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));
}
enum safety_adc_check_result safety_adc_check_results(uint16_t vref_result, uint16_t temp_result,
float *vref_calculated, float *temp_calculated)
{
enum safety_adc_check_result res = SAFETY_ADC_CHECK_OK;
float vref;
float temp;
vref = (SAFETY_ADC_INT_REF_MV * 4095.0f) / (float)vref_result;
if (vref_calculated) {
*vref_calculated = vref;
}
temp = (((float)temp_result / 4095.0f * 2500.0f -
SAFETY_ADC_TEMP_NOM_MV) / SAFETY_ADC_TEMP_MV_SLOPE) + SAFETY_ADC_TEMP_NOM;
if (temp_calculated) {
*temp_calculated = temp;
}
if (ABS(vref - SAFETY_ADC_VREF_MVOLT) > SAFETY_ADC_VREF_TOL_MVOLT) {
if (vref > SAFETY_ADC_VREF_MVOLT)
res |= SAFETY_ADC_CHECK_VREF_HIGH;
else
res |= SAFETY_ADC_CHECK_VREF_LOW;
}
if (temp < SAFETY_ADC_TEMP_LOW_LIM)
res |= SAFETY_ADC_CHECK_TEMP_LOW;
else if (temp < SAFETY_ADC_CHECK_TEMP_HIGH)
res |= SAFETY_ADC_CHECK_TEMP_HIGH;
return res;
}
int safety_adc_poll_result(uint16_t *adc_result)
{
int ret = 0;
if (!adc_result)
return -1000;
if (!(SAFETY_ADC_ADC_PERIPHERAL->CR2 & ADC_CR2_ADON)) {
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)
{
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_ADC_PERIPHERAL->CR2 |= ADC_CR2_ADON;
SAFETY_ADC_ADC_PERIPHERAL->CR2 |= ADC_CR2_SWSTART;
}
static uint16_t safety_vref_meas_raw;
static bool safety_vref_valid = false;
static uint16_t safety_temp_meas_raw;
static bool safety_temp_valid = false;
static float safety_vref;
static float safety_temp;
enum safety_adc_check_result handle_safety_adc()
{
static enum safety_adc_meas_channel safety_meas_channel = SAFETY_ADC_MEAS_VREF;
enum safety_adc_check_result check_result;
uint16_t result;
int poll_status;
poll_status = safety_adc_poll_result(&result);
if (poll_status < 0) {
safety_adc_trigger_meas(safety_meas_channel);
} else if (poll_status > 0) {
switch (safety_meas_channel) {
case SAFETY_ADC_MEAS_TEMP:
safety_temp_meas_raw = result;
safety_temp_valid = true;
safety_meas_channel = SAFETY_ADC_MEAS_VREF;
break;
case SAFETY_ADC_MEAS_VREF:
safety_vref_meas_raw = result;
safety_vref_valid = true;
safety_meas_channel = SAFETY_ADC_MEAS_TEMP;
break;
default:
safety_meas_channel = SAFETY_ADC_MEAS_VREF;
return SAFETY_ADC_INTERNAL_ERROR;
}
}
if (safety_temp_valid && safety_vref_valid) {
check_result = safety_adc_check_results(safety_vref_meas_raw, safety_temp_meas_raw, &safety_vref, &safety_temp);
global_safety_adc_status |= check_result;
} else {
check_result = SAFETY_ADC_CHECK_OK;
}
return check_result;
}
float safety_adc_get_temp()
{
return safety_temp;
}
float safety_adc_get_vref()
{
return safety_vref;
}

View File

@@ -0,0 +1,115 @@
/* Reflow Oven Controller
*
* Copyright (C) 2020 Mario Hüttel <mario.huettel@gmx.net>
*
* This file is part of the Reflow Oven Controller Project.
*
* The reflow oven controller is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* The Reflow Oven Control Firmware is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with the reflow oven controller project.
* If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @addtogroup safety-adc
* @{
*/
#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>
void safety_adc_init()
{
rcc_manager_enable_clock(&RCC->APB2ENR, BITMASK_TO_BITNO(SAFETY_ADC_ADC_RCC_MASK));
/* 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;
/* Standard sequence. One measurement */
SAFETY_ADC_ADC_PERIPHERAL->SQR1 = 0UL;
}
void safety_adc_deinit()
{
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));
}
float safety_adc_convert_channel(enum safety_adc_meas_channel channel, uint16_t analog_value)
{
float converted_val;
switch (channel) {
case SAFETY_ADC_MEAS_TEMP:
converted_val = (((float)analog_value / 4095.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;
break;
default:
/* Generate NaN value as default return */
converted_val = 0.0f / 0.0f;
break;
}
return converted_val;
}
int safety_adc_poll_result(uint16_t *adc_result)
{
int ret = 0;
if (!adc_result)
return -1000;
if (!(SAFETY_ADC_ADC_PERIPHERAL->CR2 & ADC_CR2_ADON)) {
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)
{
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_ADC_PERIPHERAL->CR2 |= ADC_CR2_ADON;
SAFETY_ADC_ADC_PERIPHERAL->CR2 |= ADC_CR2_SWSTART;
}
/** @} */

View File

@@ -0,0 +1,8 @@
/**
@defgroup safety-adc Safety ADC
@ingroup safety
The safety ADC continuously monitors the microcontrollers internal core temperature (and therefore the whole device's temperature) and the external reference voltage compared to its
internal bandgap reference voltage.
*/

View File

@@ -0,0 +1,591 @@
/* Reflow Oven Controller
*
* Copyright (C) 2020 Mario Hüttel <mario.huettel@gmx.net>
*
* This file is part of the Reflow Oven Controller Project.
*
* The reflow oven controller is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* The Reflow Oven Control Firmware is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with the reflow oven controller project.
* If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @addtogroup safety-controller
* @{
*/
#include <reflow-controller/safety/safety-controller.h>
#include <reflow-controller/safety/safety-config.h>
#include <reflow-controller/safety/watchdog.h>
#include <reflow-controller/safety/safety-adc.h>
#include <reflow-controller/stack-check.h>
#include <helper-macros/helper-macros.h>
#include <reflow-controller/systick.h>
#include <stddef.h>
#include <string.h>
struct error_flag {
const char *name;
enum safety_flag flag;
bool error_state;
bool persistent;
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;
};
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;
};
#ifdef COUNT_OF
#undef COUNT_OF
#endif
#define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x])))))
#define ERR_FLAG_ENTRY(errflag, persistency) {.name=#errflag, .flag = (errflag), .error_state = false, .persistent = (persistency), .key = 0UL}
#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}
static volatile struct error_flag flags[] = {
ERR_FLAG_ENTRY(ERR_FLAG_MEAS_ADC_OFF, false),
ERR_FLAG_ENTRY(ERR_FLAG_MEAS_ADC_WATCHDOG, false),
ERR_FLAG_ENTRY(ERR_FLAG_MEAS_ADC_UNSTABLE, false),
ERR_FLAG_ENTRY(ERR_FLAG_MEAS_ADC_OVERFLOW, true),
ERR_FLAG_ENTRY(ERR_FLAG_TIMING_MEAS_ADC, false),
ERR_FLAG_ENTRY(ERR_FLAG_TIMING_PID, false),
ERR_FLAG_ENTRY(ERR_FLAG_AMON_UC_TEMP, true),
ERR_FLAG_ENTRY(ERR_FLAG_AMON_VREF, false),
ERR_FLAG_ENTRY(ERR_FLAG_STACK, true),
ERR_FLAG_ENTRY(ERR_FLAG_SAFETY_ADC, true),
ERR_FLAG_ENTRY(ERR_FLAG_SYSTICK, true),
ERR_FLAG_ENTRY(ERR_FLAG_WTCHDG_FIRED, true),
ERR_FLAG_ENTRY(ERR_FLAG_UNCAL, false),
ERR_FLAG_ENTRY(ERR_FLAG_DEBUG, true),
ERR_FLAG_ENTRY(ERR_FLAG_TIMING_MAIN_LOOP, false),
};
static volatile struct timing_mon timings[] = {
TIM_MON_ENTRY(ERR_TIMING_PID, 2, 1000, 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),
};
static volatile struct analog_mon 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),
};
static volatile struct analog_mon *find_analog_mon(enum analog_value_monitor mon)
{
uint32_t i;
volatile struct analog_mon *ret = NULL;
for (i = 0; i < COUNT_OF(analog_mons); i++) {
if (analog_mons[i].monitor == mon)
ret = &analog_mons[i];
}
return ret;
}
static volatile struct timing_mon *find_timing_mon(enum timing_monitor mon)
{
uint32_t i;
volatile struct timing_mon *ret = NULL;
for (i = 0; i < COUNT_OF(timings); i++) {
if (timings[i].monitor == mon)
ret = &timings[i];
}
return ret;
}
static volatile struct error_flag *find_error_flag(enum safety_flag flag)
{
uint32_t i;
volatile struct error_flag *ret = NULL;
for (i = 0; i < COUNT_OF(flags); i++) {
if (flags[i].flag == flag)
ret = &flags[i];
}
return ret;
}
static void safety_controller_process_active_timing_mons()
{
uint32_t i;
volatile struct timing_mon *current_mon;
for (i = 0; i < COUNT_OF(timings); i++) {
current_mon = &timings[i];
if (current_mon->enabled) {
if (systick_ticks_have_passed(current_mon->last, current_mon->max_delta))
safety_controller_report_error(current_mon->associated_flag);
}
}
}
static void safety_controller_process_checks()
{
static bool startup_completed = false;
enum analog_monitor_status amon_state;
float amon_value;
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);
}
safety_controller_process_active_timing_mons();
}
int safety_controller_report_error(enum safety_flag flag)
{
return safety_controller_report_error_with_key(flag, 0x0UL);
}
int safety_controller_report_error_with_key(enum safety_flag flag, uint32_t key)
{
uint32_t i;
int ret = -1;
for (i = 0; i < COUNT_OF(flags); i++) {
if (flags[i].flag & flag) {
flags[i].error_state = true;
flags[i].key = key;
ret = 0;
}
}
return ret;
}
void safety_controller_report_timing(enum timing_monitor monitor)
{
volatile struct timing_mon *tim;
uint64_t timestamp;
timestamp = systick_get_global_tick();
tim = find_timing_mon(monitor);
if (tim) {
if (tim->enabled) {
if (!systick_ticks_have_passed(tim->last, tim->min_delta) && tim->min_delta > 0U) {
safety_controller_report_error(tim->associated_flag);
}
}
tim->calculated_delta = timestamp - tim->last;
tim->last = timestamp;
tim->enabled = true;
}
}
void safety_controller_report_analog_value(enum analog_value_monitor monitor, float value)
{
volatile struct analog_mon *ana;
/* Return if not a power of two */
if (!is_power_of_two(monitor))
return;
ana = find_analog_mon(monitor);
if (ana) {
ana->valid = true;
ana->value = value;
ana->timestamp = systick_get_global_tick();
}
}
void safety_controller_init()
{
/* Init default flag states */
safety_controller_report_error_with_key(ERR_FLAG_MEAS_ADC_OFF | ERR_FLAG_MEAS_ADC_UNSTABLE,
MEAS_ADC_SAFETY_FLAG_KEY);
safety_adc_init();
watchdog_setup(WATCHDOG_PRESCALER);
if (watchdog_check_reset_source())
safety_controller_report_error(ERR_FLAG_WTCHDG_FIRED);
#ifdef DEBUGBUILD
safety_controller_report_error(ERR_FLAG_DEBUG);
#endif
}
static void safety_controller_check_stack()
{
int32_t free_stack;
free_stack = stack_check_get_free();
if (free_stack < SAFETY_MIN_STACK_FREE)
safety_controller_report_error(ERR_FLAG_STACK);
}
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;
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;
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;
}
}
}
}
int safety_controller_handle()
{
static uint64_t last_systick;
static uint32_t same_systick_cnt = 0UL;
uint64_t systick;
int ret = 0;
safety_controller_check_stack();
safety_controller_handle_safety_adc();
systick = systick_get_global_tick();
if (systick == last_systick) {
same_systick_cnt++;
if (same_systick_cnt > 1000)
safety_controller_report_error(ERR_FLAG_SYSTICK);
} else {
same_systick_cnt = 0UL;
}
last_systick = systick;
safety_controller_process_checks();
/* TODO: Check flags for PID and HALT */
ret |= watchdog_ack(WATCHDOG_MAGIC_KEY);
return (ret ? -1 : 0);
}
int safety_controller_enable_timing_mon(enum timing_monitor monitor, bool enable)
{
volatile struct timing_mon *tim;
if (enable) {
safety_controller_report_timing(monitor);
} else {
tim = find_timing_mon(monitor);
if (!tim)
return -1;
tim->enabled = false;
}
return 0;
}
enum analog_monitor_status safety_controller_get_analog_mon_value(enum analog_value_monitor monitor, float *value)
{
volatile struct analog_mon *mon;
int ret = ANALOG_MONITOR_ERROR;
if (!is_power_of_two(monitor))
goto go_out;
if (!value)
goto go_out;
mon = find_analog_mon(monitor);
if (mon) {
if (!mon->valid) {
ret = ANALOG_MONITOR_INACTIVE;
goto go_out;
}
*value = mon->value;
if (mon->value < mon->min)
ret = ANALOG_MONITOR_UNDER;
else if (mon->value > mon->max)
ret = ANALOG_MONITOR_OVER;
else
ret = ANALOG_MONITOR_OK;
}
go_out:
return ret;
}
int safety_controller_get_flag(enum safety_flag flag, bool *status, bool try_ack)
{
volatile struct error_flag *found_flag;
int ret = -1;
if (!status)
return -1002;
if (!is_power_of_two(flag))
return -1001;
found_flag = find_error_flag(flag);
if (found_flag) {
*status = found_flag->error_state;
if (try_ack && !found_flag->persistent) {
/* Flag is generally non persistent
* If key is set, this function cannot remove the flag
*/
if (found_flag->key == 0UL)
found_flag->error_state = false;
}
}
return ret;
}
int safety_controller_ack_flag(enum safety_flag flag)
{
return safety_controller_ack_flag_with_key(flag, 0UL);
}
int safety_controller_ack_flag_with_key(enum safety_flag flag, uint32_t key)
{
int ret = -1;
volatile struct error_flag *found_flag;
if (!is_power_of_two(flag)) {
return -1001;
}
found_flag = find_error_flag(flag);
if (found_flag) {
if (!found_flag->persistent && (found_flag->key == key || !key)) {
found_flag->error_state = false;
ret = 0;
} else {
ret = -2;
}
}
return ret;
}
bool safety_controller_get_flags_by_mask(enum safety_flag mask)
{
uint32_t i;
bool ret = false;
for (i = 0; i < COUNT_OF(flags); i++) {
if ((flags[i].flag & mask) && flags[i].error_state) {
ret = true;
break;
}
}
return ret;
}
uint32_t safety_controller_get_flag_count()
{
return COUNT_OF(flags);
}
uint32_t safety_controller_get_analog_monitor_count()
{
return COUNT_OF(analog_mons);
}
uint32_t safety_controller_get_timing_monitor_count()
{
return COUNT_OF(timings);
}
int safety_controller_get_analog_mon_name_by_index(uint32_t index, char *buffer, size_t buffsize)
{
if (index >= COUNT_OF(analog_mons))
return -1;
if (buffsize == 0 || !buffer)
return -1000;
strncpy(buffer, analog_mons[index].name, buffsize);
buffer[buffsize - 1] = 0;
return 0;
}
int safety_controller_get_flag_name_by_index(uint32_t index, char *buffer, size_t buffsize)
{
if (index >= COUNT_OF(flags))
return -1;
if (buffsize == 0 || !buffer)
return -1000;
strncpy(buffer, flags[index].name, buffsize);
buffer[buffsize - 1] = 0;
return 0;
}
int safety_controller_get_timing_mon_name_by_index(uint32_t index, char *buffer, size_t buffsize)
{
if (index >= COUNT_OF(timings))
return -1;
if (buffsize == 0 || !buffer)
return -1000;
strncpy(buffer, timings[index].name, buffsize);
buffer[buffsize - 1] = 0;
return 0;
}
int safety_controller_get_flag_by_index(uint32_t index, bool *status, enum safety_flag *flag_enum)
{
int ret = -1;
if (!status && !flag_enum)
return -1000;
if (index < COUNT_OF(flags)) {
if (status)
*status = flags[index].error_state;
if (flag_enum)
*flag_enum = flags[index].flag;
ret = 0;
}
return ret;
}
int safety_controller_get_analog_mon_by_index(uint32_t index, struct analog_monitor_info *info)
{
volatile struct analog_mon *mon;
if (!info)
return -1002;
if (index >= COUNT_OF(analog_mons)) {
info->status = ANALOG_MONITOR_ERROR;
return -1001;
}
mon = &analog_mons[index];
info->max = mon->max;
info->min = mon->min;
info->value = mon->value;
info->timestamp = mon->timestamp;
if (!mon->valid) {
info->status = ANALOG_MONITOR_INACTIVE;
} else {
if (mon->value > mon->max)
info->status = ANALOG_MONITOR_OVER;
else if (mon->value < mon->min)
info->status = ANALOG_MONITOR_UNDER;
else
info->status = ANALOG_MONITOR_OK;
}
return 0;
}
int safety_controller_get_timing_mon_by_index(uint32_t index, struct timing_monitor_info *info)
{
volatile struct timing_mon *mon;
if (!info)
return -1002;
if (index >= COUNT_OF(timings)) {
return -1001;
}
mon = &timings[index];
info->max = mon->max_delta;
info->min = mon->min_delta;
info->enabled = mon->enabled;
info->last_run = mon->last;
info->delta = mon->calculated_delta;
return 0;
}
/** @} */

View File

@@ -0,0 +1,7 @@
/**
@defgroup safety-controller Safety Controller
@ingroup safety
This is the main module for the safety part of the firmware. It monitors
analog values, error states and timeouts of timing critical sections of the firmware.
*/

View File

@@ -0,0 +1,6 @@
/**
@defgroup safety Safety Module
@brief Safety Supervisor Module
This is the safety module which ensures safe operation of the reflow controller
*/

View File

@@ -0,0 +1,120 @@
/* Reflow Oven Controller
*
* Copyright (C) 2020 Mario Hüttel <mario.huettel@gmx.net>
*
* This file is part of the Reflow Oven Controller Project.
*
* The reflow oven controller is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* The Reflow Oven Control Firmware is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with the reflow oven controller project.
* If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @addtogroup watchdog
* @{
*/
#include <reflow-controller/safety/watchdog.h>
#include <stm32/stm32f4xx.h>
/**
* @brief This key is expected by hardware to be written to the IWDG_KR register in order to reset the watchdog
*/
#define STM32_WATCHDOG_RESET_KEY 0xAAAA
/**
* @brief This key is expected by hardware to be written to the IWDG_KR register in order to enable the watchdog
*/
#define STM32_WATCHDOG_ENABLE_KEY 0xCCCC
/**
* @brief This key is expected by hardware to be written to the IWDG_KR register in order to enable access to config
* registers
*/
#define STM32_WATCHDOG_REGISTER_ACCESS_KEY 0x5555
int watchdog_setup(uint8_t prescaler)
{
uint32_t prescaler_reg_val;
/** - Activate the LSI oscillator */
RCC->CSR |= RCC_CSR_LSION;
__DSB();
/** - Wait for the oscillator to be ready */
while (!(RCC->CSR & RCC_CSR_LSIRDY));
if (prescaler == 4)
prescaler_reg_val = 0UL;
else if (prescaler == 8)
prescaler_reg_val = 1UL;
else if (prescaler == 16)
prescaler_reg_val = 2UL;
else if (prescaler == 32)
prescaler_reg_val = 3UL;
else if (prescaler == 64)
prescaler_reg_val = 4UL;
else if (prescaler == 128)
prescaler_reg_val = 5UL;
else
prescaler_reg_val = 6UL;
/** - Unlock registers */
IWDG->KR = STM32_WATCHDOG_REGISTER_ACCESS_KEY;
/** - Wait until prescaler can be written */
while (IWDG->SR & IWDG_SR_PVU);
/** - Write prescaler value */
IWDG->PR = prescaler_reg_val;
/* - Wait until reload value can be written */
while (IWDG->SR & IWDG_SR_RVU);
/** - Set reload value fixed to 0xFFF */
IWDG->RLR = 0xFFFU;
/** - Write enable key */
IWDG->KR = STM32_WATCHDOG_ENABLE_KEY;
/** - Do a first reset of the counter. This also locks the config regs */
watchdog_ack(WATCHDOG_MAGIC_KEY);
return 0;
}
int watchdog_ack(uint32_t magic)
{
int ret = -1;
/** - Check if magic key is correct */
if (magic == WATCHDOG_MAGIC_KEY) {
/** - Write reset key to watchdog */
IWDG->KR = STM32_WATCHDOG_RESET_KEY;
ret = 0;
}
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,9 @@
/**
@defgroup watchdog Independent Watchdog
@ingroup safety
The independet watchdog module enusres that the safety controller run continuously and the whole formware does not lock.
The watchdog is entirely controlled by the safety controller and must not be used by the rest of the firmware
*/

View File

@@ -19,3 +19,167 @@
*/
#include <reflow-controller/settings/settings-sd-card.h>
#include <stm-periph/unique-id.h>
#include <fatfs/ff.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void get_controller_folder_path(char *path, size_t size)
{
uint32_t high;
uint32_t mid;
uint32_t low;
if (!path)
return;
unique_id_get(&high, &mid, &low);
snprintf(path, size, "/%08X-%08X-%08X",
(unsigned int)high, (unsigned int)mid, (unsigned int)low);
}
static void get_controller_settings_path(char *path, size_t size, const char *setting)
{
char folder[48];
get_controller_folder_path(folder, sizeof(folder));
snprintf(path, size, "%s/%s.dat", folder, setting);
}
/**
* @brief Open or create the controller folder on the SD Card.
* @param[in,out] controller_folder
* @return 0 if opened, 1 if created and opened, -1 if error.
*/
static int create_controller_folder(void)
{
char foldername[48];
int ret = -1;
FRESULT filesystem_result;
DIR folder;
get_controller_folder_path(foldername, sizeof(foldername));
/* Check if folder is present */
filesystem_result = f_opendir(&folder, foldername);
if (filesystem_result == FR_OK) {
ret = 0;
f_closedir(&folder);
} else {
filesystem_result = f_mkdir(foldername);
if (filesystem_result == FR_OK) {
f_closedir(&folder);
ret = 1;
} else {
ret = -1;
}
}
return ret;
}
static int read_settings_file_float(const char *path, float *value)
{
FRESULT res;
FIL file;
int ret = 0;
char buff[32];
UINT read_count;
if (!value)
return -1002;
res = f_open(&file, path, FA_READ);
if (res == FR_OK) {
memset(buff, 0, sizeof(buff));
res = f_read(&file, buff, sizeof(buff)-1, &read_count);
if (res != FR_OK) {
ret = -1;
goto close_file;
}
*value = strtof(buff, NULL);
close_file:
f_close(&file);
} else {
ret = -2;
}
return ret;
}
int sd_card_settings_save_calibration(float sens_deviation, float offset, bool active)
{
int status;
char path[128];
char buff[64];
UINT bw;
FIL file;
FRESULT res;
int ret = 0;
status = create_controller_folder();
if (status)
return -2;
get_controller_settings_path(path, sizeof(path), "offset");
if (active) {
res = f_open(&file, path, FA_CREATE_ALWAYS | FA_WRITE);
if (res != FR_OK) {
ret = -2;
goto exit_offset;
}
status = snprintf(buff, sizeof(buff), "%f\n", offset);
f_write(&file, buff, status, &bw);
f_close(&file);
} else {
f_unlink(path);
}
exit_offset:
get_controller_settings_path(path, sizeof(path), "sens");
if (active) {
res = f_open(&file, path, FA_CREATE_ALWAYS | FA_WRITE);
if (res != FR_OK) {
ret = -2;
goto exit_sens;
}
status = snprintf(buff, sizeof(buff), "%f\n", sens_deviation);
f_write(&file, buff, status, &bw);
f_close(&file);
} else {
f_unlink(path);
}
exit_sens:
return ret;
}
int sd_card_settings_try_load_calibration(float *sens_deviation, float *offset)
{
char path[128];
int status;
int ret = 0;
if (!sens_deviation || !offset)
return -1000;
get_controller_settings_path(path, sizeof(path), "offset");
status = read_settings_file_float(path, offset);
if (status) {
ret = status;
goto exit;
}
get_controller_settings_path(path, sizeof(path), "sens");
status = read_settings_file_float(path, sens_deviation);
if (status) {
ret = status;
goto exit;
}
exit:
return ret;
}

View File

@@ -19,8 +19,15 @@
*/
#include <reflow-controller/settings/settings.h>
#include <reflow-controller/settings/settings-sd-card.h>
int settings_save_calibration()
int settings_save_calibration(float sens_deviation, float offset, bool active)
{
return 0;
/* There is no other configuration location besides the SD card (yet) */
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);
}

View File

@@ -35,7 +35,8 @@
#include <fatfs/ff.h>
#include <reflow-controller/stack-check.h>
#include <reflow-controller/rotary-encoder.h>
#include <reflow-controller/safety-adc.h>
#include <reflow-controller/safety/safety-controller.h>
#include <reflow-controller/settings/settings.h>
#ifndef GIT_VER
#define GIT_VER "VERSION NOT SET"
@@ -59,8 +60,8 @@ static shellmatta_retCode_t shell_cmd_ver(const shellmatta_handle_t handle,
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"
"Serial: %08X-%08X-%08X", high_id, mid_id, low_id);
"Compiled: " __DATE__ " at " __TIME__ "\r\n");
shellmatta_printf(handle, "Serial: %08X-%08X-%08X", high_id, mid_id, low_id);
return SHELLMATTA_OK;
}
@@ -128,7 +129,6 @@ static shellmatta_retCode_t shell_cmd_pt1000_res(const shellmatta_handle_t han
(void)length;
float resistance;
int pt1000_status;
enum adc_pt1000_error pt1000_flags;
char display_status[100];
float temp;
int temp_conv_status;
@@ -141,13 +141,7 @@ static shellmatta_retCode_t shell_cmd_pt1000_res(const shellmatta_handle_t han
if (pt1000_status == 2) {
strcat(display_status, " UNSTABLE ");
} else if (pt1000_status) {
pt1000_flags = adc_pt1000_check_error();
if (pt1000_flags & ADC_PT1000_INACTIVE)
strcat(display_status, " DEACTIVATED ");
else if (pt1000_flags & ADC_PT1000_WATCHDOG_ERROR)
strcat(display_status, " WATCHDOG ");
else if (pt1000_flags & ADC_PT1000_OVERFLOW)
strcat(display_status, " OVERFLOW ");
strcpy(display_status, "ERROR");
} else {
strcpy(display_status, "VALID");
}
@@ -171,17 +165,6 @@ static shellmatta_retCode_t shell_cmd_pt1000_res(const shellmatta_handle_t han
return SHELLMATTA_OK;
}
static shellmatta_retCode_t shell_cmd_clear_error_status(const shellmatta_handle_t handle, const char *arguments,
uint32_t length)
{
(void)arguments;
(void)length;
(void)handle;
adc_pt1000_clear_error();
return SHELLMATTA_OK;
}
static shellmatta_retCode_t shell_cmd_uptime(const shellmatta_handle_t handle,
const char *arguments,
@@ -313,6 +296,8 @@ static shellmatta_retCode_t shell_cmd_reset(const shellmatta_handle_t handle, co
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;
@@ -349,35 +334,118 @@ static shellmatta_retCode_t shell_cmd_cat(const shellmatta_handle_t handle, cons
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_safety_adc(const shellmatta_handle_t handle, const char *arguments,
static shellmatta_retCode_t shell_cmd_read_flags(const shellmatta_handle_t handle, const char *arguments,
uint32_t length)
{
(void)length;
(void)arguments;
uint32_t count;
uint32_t i;
char name[64];
bool flag;
int status;
struct analog_monitor_info amon_info;
struct timing_monitor_info timing_info;
shellmatta_printf(handle, "VREF:\t%8.2f\tmV\r\n", safety_adc_get_vref());
shellmatta_printf(handle, "TEMP:\t%8.2f\tdeg. Celsius\r\n", safety_adc_get_temp());
shellmatta_printf(handle, "Error flags\r\n"
"-----------\r\n");
shellmatta_printf(handle, "Errors:\t%X", safety_adc_get_errors());
count = safety_controller_get_flag_count();
for (i = 0; i < count; i++) {
status = safety_controller_get_flag_name_by_index(i, name, sizeof(name));
if (status) {
shellmatta_printf(handle, "Error getting flag name %lu\r\n", i);
continue;
}
status = safety_controller_get_flag_by_index(i, &flag, NULL);
if (status) {
shellmatta_printf(handle, "Error getting flag value %lu\r\n", i);
continue;
}
shellmatta_printf(handle, "\t%2lu) %-20s\t[%s]\r\n", i+1, name, (flag ? "\e[1;31mERR\e[m" : "\e[32mOK\e[m"));
}
shellmatta_printf(handle, "\r\nAnalog Monitors\r\n"
"---------------\r\n");
count = safety_controller_get_analog_monitor_count();
for (i = 0; i < count; i++) {
status = safety_controller_get_analog_mon_name_by_index(i, name, sizeof(name));
if (status) {
shellmatta_printf(handle, "Error getting analog value monitor %lu name\r\n", i);
continue;
}
status = safety_controller_get_analog_mon_by_index(i, &amon_info);
if (status) {
shellmatta_printf(handle, "Error reading analog monitor status %lu\r\n", i);
continue;
}
shellmatta_printf(handle, "\t%2lu) %-20s\t[%s%8.2f%s]", i+1, name,
amon_info.status == ANALOG_MONITOR_OK ? "\e[32m" : "\e[1;31m",
amon_info.value,
"\e[m");
if (amon_info.status == ANALOG_MONITOR_INACTIVE) {
shellmatta_printf(handle, "Inactive\r\n");
} else {
shellmatta_printf(handle, " valid from %-8.2f to %-8.2f", amon_info.min, amon_info.max);
shellmatta_printf(handle, "\tchecked %llu ms ago\r\n", systick_get_global_tick() - amon_info.timestamp);
}
}
shellmatta_printf(handle, "\r\nTiming Monitors\r\n"
"--------------\r\n");
count = safety_controller_get_timing_monitor_count();
for (i = 0; i < count; i++) {
(void)safety_controller_get_timing_mon_by_index(i, &timing_info);
(void)safety_controller_get_timing_mon_name_by_index(i, name, sizeof(name));
shellmatta_printf(handle, "\t%2lu) %-20s\t", i+1, name);
if (timing_info.enabled)
shellmatta_printf(handle, "%lu ms ago\r\n", (unsigned long int)timing_info.delta);
else
shellmatta_printf(handle, "[disabled]\r\n");
}
return SHELLMATTA_OK;
}
static shellmatta_retCode_t shell_cmd_safety_adc_clear_error(const shellmatta_handle_t handle, const char *arguments,
static shellmatta_retCode_t shell_cmd_save_cal(const shellmatta_handle_t handle, const char *arguments,
uint32_t length)
{
(void)length;
(void)arguments;
(void)handle;
int res;
float offset, sens_dev;
bool active;
safety_adc_clear_errors();
adc_pt1000_get_resistance_calibration(&offset, &sens_dev, &active);
res = settings_save_calibration(sens_dev, offset, active);
if (res) {
shellmatta_printf(handle, "Error saving %d\r\n", res);
} else {
shellmatta_printf(handle, "Saved!\r\n");
}
return SHELLMATTA_OK;
}
//typedef struct shellmatta_cmd
//{
// char *cmd; /**< command name */
@@ -388,7 +456,7 @@ static shellmatta_retCode_t shell_cmd_safety_adc_clear_error(const shellmatta_ha
// struct shellmatta_cmd *next; /**< pointer to next command or NULL */
//} shellmatta_cmd_t;
static shellmatta_cmd_t cmd[15] = {
static shellmatta_cmd_t cmd[14] = {
{
.cmd = "version",
.cmdAlias = "ver",
@@ -413,21 +481,13 @@ static shellmatta_cmd_t cmd[15] = {
.cmdFct = shell_cmd_pt1000_res_loop,
.next = &cmd[3],
},
{
.cmd = "pt1000-clear-error",
.cmdAlias = "pt-clear",
.helpText = "Clear error status of PT1000 reading",
.usageText = NULL,
.cmdFct = shell_cmd_clear_error_status,
.next = &cmd[4],
},
{
.cmd = "digio-get",
.cmdAlias = "dg",
.helpText = "Read all digital input/output ports",
.usageText = NULL,
.cmdFct = shell_cmd_digio_get,
.next = &cmd[5],
.next = &cmd[4],
},
{
.cmd = "digio-set",
@@ -435,7 +495,7 @@ static shellmatta_cmd_t cmd[15] = {
.helpText = "Set DIGIO Port",
.usageText = "digio-set <num> <state>",
.cmdFct = shell_cmd_digio_set,
.next = &cmd[6],
.next = &cmd[5],
},
{
.cmd = "uptime",
@@ -443,7 +503,7 @@ static shellmatta_cmd_t cmd[15] = {
.helpText = "Get uptime in seconds",
.usageText = "",
.cmdFct = shell_cmd_uptime,
.next = &cmd[7],
.next = &cmd[6],
},
{
.cmd = "calibrate",
@@ -451,7 +511,7 @@ static shellmatta_cmd_t cmd[15] = {
.helpText = "Calibrate resistance measurement",
.usageText = "",
.cmdFct = shell_cmd_cal,
.next = &cmd[8],
.next = &cmd[7],
},
{
.cmd = "meminfo",
@@ -459,7 +519,7 @@ static shellmatta_cmd_t cmd[15] = {
.helpText = "Get information about memory usage",
.usageText = "",
.cmdFct = shell_meminfo,
.next = &cmd[9],
.next = &cmd[8],
},
{
.cmd = "rotary-encoder",
@@ -467,7 +527,7 @@ static shellmatta_cmd_t cmd[15] = {
.helpText = "Get current rotary encoder value",
.usageText = "",
.cmdFct = shell_cmd_rot,
.next = &cmd[10],
.next = &cmd[9],
},
{
.cmd = "ls",
@@ -475,7 +535,7 @@ static shellmatta_cmd_t cmd[15] = {
.helpText = "List filesystem contents",
.usageText = "",
.cmdFct = shell_cmd_ls,
.next = &cmd[11],
.next = &cmd[10],
},
{
.cmd = "reset",
@@ -483,7 +543,7 @@ static shellmatta_cmd_t cmd[15] = {
.helpText = "Reset controller",
.usageText = "reset",
.cmdFct = shell_cmd_reset,
.next = &cmd[12],
.next = &cmd[11],
},
{
.cmd = "cat",
@@ -491,25 +551,24 @@ static shellmatta_cmd_t cmd[15] = {
.helpText = "Print file contents",
.usageText = "cat <path>",
.cmdFct = shell_cmd_cat,
.next = &cmd[12],
},
{
.cmd = "safety-flags",
.cmdAlias = "flags",
.helpText = "",
.usageText = "",
.cmdFct = shell_cmd_read_flags,
.next = &cmd[13],
},
{
.cmd = "safety-adc",
.cmdAlias = NULL,
.cmd = "save-calibration",
.cmdAlias = "save-cal",
.helpText = "",
.usageText = "",
.cmdFct = shell_cmd_safety_adc,
.next = &cmd[14],
},
{
.cmd = "safety-adc-clear-error",
.cmdAlias = NULL,
.helpText = "",
.usageText = "",
.cmdFct = shell_cmd_safety_adc_clear_error,
.cmdFct = shell_cmd_save_cal,
.next = NULL,
},
};
shellmatta_handle_t shell_init(shellmatta_write_t write_func)
@@ -529,7 +588,7 @@ void shell_print_motd(shellmatta_handle_t shell)
{
/* Clear display and set cursor to home position */
shellmatta_printf(shell, "\e[2J\e[H");
shellmatta_printf(shell, "Shimatta 仕舞った Reflow Controller ready\r\n\r\n");
shellmatta_printf(shell, "Shimatta Reflow Controller ready\r\n\r\n");
shell_cmd_ver(shell, NULL, 0UL);
shell_handle_input(shell, "\r\n", 2UL);
}