54 Commits

Author SHA1 Message Date
Emanuele Trabattoni
28a2bf75e3 start refactoring 2026-04-22 22:23:55 +02:00
c9887a563e more variables name refactoring 2026-04-22 15:14:25 +02:00
15ca82b6df task variable name refactoring 2026-04-22 14:17:35 +02:00
d700578256 disable interrupts in adc reading critical section 2026-04-22 13:43:41 +02:00
10f8026c6d enable disable interrupts on adc drdy only when needed (only for cycle read now) fixed useless delays 2026-04-22 12:07:39 +02:00
dc56990f1e fixed ws ping timer 2026-04-21 23:30:08 +02:00
a9d5bcfd66 fixed pinmap 2026-04-21 23:29:48 +02:00
9bb66a9459 re enable interrupt logic for ADC drdy 2026-04-21 22:32:01 +02:00
aa9935ef22 reorder upload and monitor ports 2026-04-21 22:25:56 +02:00
79dbd5db5d added some fake commands 2026-04-21 22:22:59 +02:00
94c5c7491a file cleanup 2026-04-21 22:22:47 +02:00
5ca3d3a46b Added module datasheet 2026-04-21 21:53:22 +02:00
6f372fcb49 Vhanged pin assignment to avoid 35,36,37 used in QSPI PSRAM 2026-04-21 21:51:58 +02:00
fec59815a6 Merge branch 'ioexpander' into debug 2026-04-21 16:16:16 +02:00
7e7d0a1c59 Second ADC debugging in process 2026-04-21 16:11:07 +02:00
59e4e955ff Merged for debug 2026-04-21 16:08:34 +02:00
Emanuele Trabattoni
dce6b0fd4f working on second adc 2026-04-17 13:24:43 +02:00
Emanuele Trabattoni
bea29dc8f5 ADC ok with interrupt or drdy 2026-04-17 12:21:35 +02:00
Emanuele Trabattoni
1b8ba88b05 ADC working ok in sync with system 2026-04-17 11:01:41 +02:00
5aa5aaa07a ADC Testing 2026-04-17 09:13:05 +02:00
1b7a531d54 Updated test instrument with cli commands 2026-04-17 09:11:41 +02:00
8171cab9cb adc ok 2026-04-14 14:16:11 +02:00
Emanuele Trabattoni
899c8cffbc io expander class ok , adc not working 2026-04-14 11:02:33 +02:00
Emanuele Trabattoni
782aa95ee6 Merge branch 'task-refactor' 2026-04-13 10:28:24 +02:00
Emanuele Trabattoni
212b37c95f updated and fixed charts 2026-04-13 10:26:55 +02:00
Emanuele Trabattoni
f8c3c69e80 fix graph 2026-04-12 14:42:40 +02:00
Emanuele Trabattoni
7da58c8a49 Set time from browser 2026-04-12 14:40:58 +02:00
a153402d28 webpage chats 2026-04-12 02:38:27 +02:00
095aa59f36 task refactoring working, sometimes misses events, check priorities 2026-04-12 01:45:32 +02:00
Emanuele Trabattoni
fdba6d5ad5 refactor continued, at least it compiles 2026-04-11 16:39:59 +02:00
Emanuele Trabattoni
d1b96e932c task refactoring work in progress 2026-04-11 15:49:40 +02:00
684c34e209 adding pins and task class 2026-04-11 12:27:19 +02:00
37fa6a686f Merge branch 'datasave' 2026-04-11 11:40:20 +02:00
9c012efef1 refactor led class 2026-04-11 11:37:40 +02:00
d41a99ee88 rgb led 2026-04-11 00:40:33 +02:00
eaeb515074 Modified Task display to order based on task number or arbitraruy function 2026-04-10 23:33:22 +02:00
246ba7eeb2 Task A+B concurrency <check ok without ADC 2026-04-10 22:03:09 +02:00
Emanuele Trabattoni
736a8d8bd5 modified to also read channel B 2026-04-10 12:12:28 +02:00
Emanuele Trabattoni
2083119d79 Updated interface to show Box A+B 2026-04-10 09:27:41 +02:00
Emanuele Trabattoni
575730a340 Finalized PINMAP 2026-04-09 15:54:59 +02:00
Emanuele Trabattoni
155f58a347 refactored webserver code 2026-04-09 14:42:13 +02:00
Emanuele Trabattoni
1e068476af LittleFS mount OK, updated interface, upload to littlefs from browser 2026-04-09 13:41:50 +02:00
Emanuele Trabattoni
de9ffe40e5 refactor variables and LittleFS mount 2026-04-09 10:23:57 +02:00
Emanuele Trabattoni
97bce90ba6 changed partition to littlefs, not working yet 2026-04-08 17:10:30 +02:00
Emanuele Trabattoni
12e1e8e7a4 Fixed Filters, split file in html, script and css 2026-04-08 16:55:03 +02:00
Emanuele Trabattoni
4dc45954e9 Webpage is OK, html in memory since SPIFFS is to slow.
Moving average to be fixed
2026-04-08 15:23:21 +02:00
Emanuele Trabattoni
07eb06f67b Save History Sync on flash, still some issues in deleting previous file 2026-04-08 10:27:18 +02:00
Emanuele Trabattoni
481f12f526 Enable/Disable pickup simulation 2026-04-08 10:26:44 +02:00
Emanuele Trabattoni
7c96101cdd SPIFFS mount sometimes fail 2026-04-07 17:33:08 +02:00
Emanuele Trabattoni
877236ee4e Save files appending on same session and new file on new session 2026-04-07 15:53:52 +02:00
Emanuele Trabattoni
668b590d7c CSV file save on SPIFF filesystem, 10MB on internal flash 2026-04-07 13:21:27 +02:00
Emanuele Trabattoni
f36cb96f21 Improved task code 2026-04-07 10:51:53 +02:00
Emanuele Trabattoni
dc44decd64 Moved files in libraries 2026-04-07 10:19:28 +02:00
Emanuele Trabattoni
0d0db29bba Merge branch 'adc' 2026-04-07 10:12:50 +02:00
51 changed files with 23129 additions and 1765 deletions

Binary file not shown.

View File

@@ -3,3 +3,4 @@
.vscode/c_cpp_properties.json .vscode/c_cpp_properties.json
.vscode/launch.json .vscode/launch.json
.vscode/ipch .vscode/ipch
unpacked_fs

View File

@@ -1,8 +1,7 @@
{ {
"recommendations": [ "recommendations": [
"Jason2866.esp-decoder", "Jason2866.esp-decoder",
"pioarduino.pioarduino-ide", "pioarduino.pioarduino-ide"
"platformio.platformio-ide"
], ],
"unwantedRecommendations": [ "unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack" "ms-vscode.cpptools-extension-pack"

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,3 @@
{
}

View File

@@ -0,0 +1,210 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Astro Rotax Monitor</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header class="page-header">
<div class="header-content">
<img src="logo_astro_dev.svg" alt="Astro Tecnologie" class="logo">
</div>
<div>
<h1>Rotax Ignition Box Monitor</h1>
</div>
</header>
<!-- TAB BUTTONS -->
<div class="tabs">
<button class="tab-button active" onclick="openTab('tab1')">Monitor</button>
<button class="tab-button" onclick="openTab('tab2')">Grafico</button>
</div>
<!-- TAB 1 (contenuto attuale) -->
<div id="tab1" class="tab-content active">
<div id="loadingIndicator" class="loading-indicator">
<span class="spinner"></span> Waiting for data...
</div>
<div class="tables-container">
<div class="box">
<h2>Box_A</h2>
<div class="box-data">
<p><strong>Timestamp:</strong> <span id="a_timestamp">-</span></p>
<p><strong>Data Valid:</strong> <span id="a_datavalid">-</span></p>
<p><strong>ADC read time:</strong> <span id="a_adc_read_time">-</span></p>
<p><strong>Queue errors:</strong> <span id="a_n_queue_errors">-</span></p>
</div>
<div class="rpm-highlight">
<strong>Engine RPM:</strong> <span id="a_eng_rpm">-</span>
</div>
<table>
<thead>
<tr>
<th>Property</th>
<th>Pickup 12</th>
<th>Pickup 34</th>
</tr>
</thead>
<tbody>
<tr>
<td>Spark delay</td>
<td id="a_coils12_spark_delay">-</td>
<td id="a_coils34_spark_delay">-</td>
</tr>
<tr>
<td>Spark status</td>
<td id="a_coils12_spark_status">-</td>
<td id="a_coils34_spark_status">-</td>
</tr>
<tr>
<td>Soft start status</td>
<td id="a_coils12_sstart_status">-</td>
<td id="a_coils34_sstart_status">-</td>
</tr>
<tr>
<td>Peak P in</td>
<td id="a_coils12_peak_p_in">-</td>
<td id="a_coils34_peak_p_in">-</td>
</tr>
<tr>
<td>Peak N in</td>
<td id="a_coils12_peak_n_in">-</td>
<td id="a_coils34_peak_n_in">-</td>
</tr>
<tr>
<td>Peak P out</td>
<td id="a_coils12_peak_p_out">-</td>
<td id="a_coils34_peak_p_out">-</td>
</tr>
<tr>
<td>Peak N out</td>
<td id="a_coils12_peak_n_out">-</td>
<td id="a_coils34_peak_n_out">-</td>
</tr>
<tr>
<td>Level spark</td>
<td id="a_coils12_level_spark">-</td>
<td id="a_coils34_level_spark">-</td>
</tr>
<tr>
<td>Spark Events</td>
<td id="a_coils12_n_events">-</td>
<td id="a_coils34_n_events">-</td>
</tr>
<tr>
<td>Missed Events</td>
<td id="a_coils12_n_missed_firing">-</td>
<td id="a_coils34_n_missed_firing">-</td>
</tr>
</tbody>
</table>
</div>
<div class="box">
<h2>Box_B</h2>
<div class="box-data">
<p><strong>Timestamp:</strong> <span id="b_timestamp">-</span></p>
<p><strong>Data Valid:</strong> <span id="b_datavalid">-</span></p>
<p><strong>ADC read time:</strong> <span id="b_adc_read_time">-</span></p>
<p><strong>Queue errors:</strong> <span id="b_n_queue_errors">-</span></p>
</div>
<div class="rpm-highlight">
<strong>Engine RPM:</strong> <span id="b_eng_rpm">-</span>
</div>
<table>
<thead>
<tr>
<th>Property</th>
<th>Pickup 12</th>
<th>Pickup 34</th>
</tr>
</thead>
<tbody>
<tr>
<td>Spark delay</td>
<td id="b_coils12_spark_delay">-</td>
<td id="b_coils34_spark_delay">-</td>
</tr>
<tr>
<td>Spark status</td>
<td id="b_coils12_spark_status">-</td>
<td id="b_coils34_spark_status">-</td>
</tr>
<tr>
<td>Soft start status</td>
<td id="b_coils12_sstart_status">-</td>
<td id="b_coils34_sstart_status">-</td>
</tr>
<tr>
<td>Peak P in</td>
<td id="b_coils12_peak_p_in">-</td>
<td id="b_coils34_peak_p_in">-</td>
</tr>
<tr>
<td>Peak N in</td>
<td id="b_coils12_peak_n_in">-</td>
<td id="b_coils34_peak_n_in">-</td>
</tr>
<tr>
<td>Peak P out</td>
<td id="b_coils12_peak_p_out">-</td>
<td id="b_coils34_peak_p_out">-</td>
</tr>
<tr>
<td>Peak N out</td>
<td id="b_coils12_peak_n_out">-</td>
<td id="b_coils34_peak_n_out">-</td>
</tr>
<tr>
<td>Level spark</td>
<td id="b_coils12_level_spark">-</td>
<td id="b_coils34_level_spark">-</td>
</tr>
<tr>
<td>Spark Events</td>
<td id="b_coils12_n_events">-</td>
<td id="b_coils34_n_events">-</td>
</tr>
<tr>
<td>Missed Events</td>
<td id="b_coils12_n_missed_firing">-</td>
<td id="b_coils34_n_missed_firing">-</td>
</tr>
</tbody>
</table>
</div>
</div>
</div> <!-- END TAB1 -->
<!-- TAB 2 (grafico) -->
<div id="tab2" class="tab-content">
<div class="chart-container">
<h3>Box A</h3>
<canvas id="chartA" height="100"></canvas>
</div>
<div class="chart-container">
<h3>Box B</h3>
<canvas id="chartB" height="100"></canvas>
</div>
</div>
</body>
<div class="upload-section">
<h3>Upload file to Flash</h3>
<p>Select a file and upload it to Flash.</p>
<input type="file" id="littlefsFile">
<button onclick="uploadLittleFS()">Upload</button>
<div id="uploadStatus" class="upload-status">No file uploaded yet.</div>
</div>
<script src="chart.js"></script>
<script src="script.js"></script>
</html>

View File

@@ -0,0 +1,306 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="e_astro_logo_negativo" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px" y="0px" viewBox="0 0 374 56" style="enable-background:new 0 0 374 56;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<g>
<polygon class="st0" points="53.9,46 59.2,46 59.2,44.8 55.3,44.8 55.3,42.4 59,42.4 59,41.2 55.3,41.2 55.3,38.8 59.2,38.8
59.2,37.6 53.9,37.6 "/>
<rect x="61.5" y="37.1" class="st0" width="1.4" height="8.9"/>
<path class="st0" d="M70.3,41.8c-0.1-0.4-0.2-0.8-0.4-1.2c-0.2-0.3-0.5-0.6-0.9-0.8c-0.4-0.2-0.8-0.3-1.3-0.3c-0.5,0-1,0.1-1.5,0.4
c-0.4,0.3-0.8,0.6-1.1,1.1c-0.3,0.5-0.4,1.1-0.4,1.8c0,0.7,0.1,1.2,0.4,1.7c0.3,0.5,0.6,0.9,1.1,1.2c0.4,0.3,0.9,0.4,1.5,0.4
c0.5,0,0.9-0.1,1.3-0.3c0.4-0.2,0.7-0.4,0.9-0.7c0.2-0.3,0.4-0.7,0.5-1.1h-1.4c-0.1,0.3-0.2,0.5-0.4,0.7c-0.2,0.2-0.5,0.2-0.8,0.2
c-0.3,0-0.6-0.1-0.9-0.3c-0.2-0.2-0.4-0.4-0.5-0.7c-0.1-0.3-0.2-0.6-0.2-1h4.2C70.4,42.6,70.4,42.2,70.3,41.8z M66.2,42.3
c0-0.3,0.1-0.5,0.2-0.8c0.1-0.3,0.3-0.5,0.5-0.7c0.2-0.2,0.5-0.3,0.9-0.3c0.3,0,0.5,0.1,0.7,0.2c0.2,0.1,0.3,0.3,0.4,0.4
c0.1,0.2,0.2,0.4,0.2,0.6c0,0.2,0,0.3,0,0.5H66.2z"/>
<path class="st0" d="M73.8,41c0.2-0.1,0.5-0.2,0.7-0.2c0.3,0,0.6,0.1,0.8,0.3c0.2,0.2,0.4,0.4,0.4,0.7l1.4-0.1
c0-0.3-0.1-0.5-0.3-0.8c-0.1-0.3-0.3-0.5-0.5-0.7c-0.2-0.2-0.5-0.4-0.8-0.5c-0.3-0.1-0.7-0.2-1.1-0.2c-0.5,0-1,0.1-1.5,0.4
c-0.4,0.3-0.8,0.6-1.1,1.1c-0.3,0.5-0.4,1.1-0.4,1.8c0,0.7,0.1,1.3,0.4,1.8c0.3,0.5,0.6,0.9,1.1,1.2c0.5,0.3,0.9,0.4,1.5,0.4
c0.6,0,1-0.1,1.4-0.3c0.4-0.2,0.7-0.5,0.9-0.9c0.2-0.4,0.3-0.8,0.3-1.2h-1.4c0,0.3-0.1,0.6-0.3,0.8c-0.2,0.2-0.5,0.3-0.9,0.3
c-0.3,0-0.5-0.1-0.8-0.2c-0.2-0.2-0.4-0.4-0.6-0.7c-0.1-0.3-0.2-0.7-0.2-1.1c0-0.5,0.1-0.9,0.2-1.2C73.4,41.4,73.6,41.1,73.8,41z"
/>
<path class="st0" d="M81.8,45c-0.2,0-0.3,0.1-0.5,0.1c-0.5,0-0.8-0.3-0.8-0.8v-3.6h1.8v-0.9h-1.8V38h-1.4v1.7h-0.9v0.9h0.9v3.7
c0,0.4,0.1,0.8,0.3,1.1c0.2,0.3,0.4,0.5,0.7,0.6c0.3,0.1,0.6,0.2,1,0.2c0.2,0,0.5,0,0.7-0.1c0.2-0.1,0.5-0.1,0.7-0.2l-0.2-1
C82.2,44.9,82,45,81.8,45z"/>
<path class="st0" d="M87.3,39.6c-0.4,0-0.7,0.1-1.1,0.4c-0.3,0.2-0.5,0.6-0.6,1v-1.3h-1.4V46h1.4v-2.8h0c0-0.5,0.1-0.9,0.2-1.2
c0.1-0.3,0.3-0.6,0.5-0.8c0.2-0.2,0.5-0.3,0.9-0.3c0.1,0,0.2,0,0.4,0c0.1,0,0.3,0,0.4,0.1l0-1.4c-0.1,0-0.2-0.1-0.3-0.1
C87.5,39.6,87.4,39.6,87.3,39.6z"/>
<path class="st0" d="M93.1,39.9c-0.5-0.2-1-0.4-1.5-0.4c-0.6,0-1.1,0.1-1.5,0.4c-0.5,0.2-0.8,0.6-1.1,1.1c-0.3,0.5-0.4,1.1-0.4,1.9
c0,0.7,0.1,1.3,0.4,1.8c0.3,0.5,0.6,0.9,1.1,1.1c0.5,0.2,1,0.4,1.5,0.4c0.6,0,1.1-0.1,1.5-0.4c0.5-0.2,0.8-0.6,1.1-1.1
c0.3-0.5,0.4-1.1,0.4-1.8c0-0.7-0.1-1.4-0.4-1.9C94,40.5,93.6,40.1,93.1,39.9z M93,44c-0.1,0.3-0.3,0.6-0.6,0.7
c-0.3,0.2-0.5,0.2-0.9,0.2c-0.5,0-0.9-0.2-1.2-0.5C90.1,44,90,43.5,90,42.9c0-0.5,0.1-0.9,0.2-1.2c0.1-0.3,0.3-0.6,0.6-0.7
c0.3-0.2,0.5-0.2,0.9-0.2c0.5,0,0.9,0.2,1.2,0.5c0.3,0.4,0.4,0.9,0.4,1.6C93.2,43.3,93.2,43.7,93,44z"/>
<path class="st0" d="M101.2,39.9c-0.4-0.2-0.8-0.3-1.3-0.3c-0.5,0-1,0.1-1.3,0.4c-0.3,0.2-0.5,0.5-0.7,0.9l-0.2-1.1h-1.2V46h1.4
v-2.9c0-0.5,0.1-0.9,0.2-1.3c0.1-0.4,0.3-0.6,0.6-0.8c0.2-0.2,0.5-0.3,0.9-0.3c0.4,0,0.7,0.1,1,0.4c0.2,0.3,0.4,0.6,0.4,1.2V46h1.4
v-3.7c0-0.6-0.1-1.1-0.3-1.5C101.8,40.4,101.5,40.1,101.2,39.9z"/>
<rect x="104.7" y="39.7" class="st0" width="1.4" height="6.3"/>
<path class="st0" d="M105.4,36.8c-0.3,0-0.5,0.1-0.6,0.2c-0.2,0.1-0.2,0.3-0.2,0.6c0,0.3,0.1,0.5,0.2,0.6c0.2,0.1,0.4,0.2,0.6,0.2
c0.3,0,0.5-0.1,0.6-0.2c0.2-0.1,0.2-0.4,0.2-0.6c0-0.2-0.1-0.4-0.2-0.6C105.9,36.9,105.6,36.8,105.4,36.8z"/>
<path class="st0" d="M110.3,41c0.2-0.1,0.5-0.2,0.7-0.2c0.3,0,0.6,0.1,0.8,0.3c0.2,0.2,0.4,0.4,0.4,0.7l1.4-0.1
c0-0.3-0.1-0.5-0.3-0.8c-0.1-0.3-0.3-0.5-0.5-0.7c-0.2-0.2-0.5-0.4-0.8-0.5c-0.3-0.1-0.7-0.2-1.1-0.2c-0.5,0-1,0.1-1.5,0.4
c-0.5,0.3-0.8,0.6-1.1,1.1c-0.3,0.5-0.4,1.1-0.4,1.8c0,0.7,0.1,1.3,0.4,1.8c0.3,0.5,0.6,0.9,1.1,1.2c0.4,0.3,0.9,0.4,1.5,0.4
c0.6,0,1-0.1,1.4-0.3c0.4-0.2,0.7-0.5,0.9-0.9c0.2-0.4,0.3-0.8,0.3-1.2h-1.4c0,0.3-0.1,0.6-0.3,0.8c-0.2,0.2-0.5,0.3-0.9,0.3
c-0.3,0-0.5-0.1-0.8-0.2c-0.2-0.2-0.4-0.4-0.6-0.7c-0.1-0.3-0.2-0.7-0.2-1.1c0-0.5,0.1-0.9,0.2-1.2C109.9,41.4,110.1,41.1,110.3,41
z"/>
<path class="st0" d="M123.7,38.1c-0.6-0.3-1.4-0.5-2.2-0.5h-2.6V46h2.6c0.9,0,1.6-0.2,2.2-0.5c0.6-0.3,1.1-0.8,1.5-1.5
c0.4-0.6,0.5-1.4,0.5-2.2c0-0.9-0.2-1.6-0.5-2.2C124.9,38.9,124.4,38.5,123.7,38.1z M123.9,43.3c-0.2,0.4-0.5,0.8-1,1.1
c-0.4,0.3-0.9,0.4-1.5,0.4h-1.3v-5.9h1.3c0.6,0,1.1,0.1,1.5,0.4c0.4,0.3,0.7,0.6,1,1c0.2,0.4,0.3,1,0.3,1.5
C124.2,42.4,124.1,42.9,123.9,43.3z"/>
<path class="st0" d="M132.6,41.8c-0.1-0.4-0.2-0.8-0.4-1.2c-0.2-0.3-0.5-0.6-0.9-0.8c-0.4-0.2-0.8-0.3-1.3-0.3
c-0.5,0-1,0.1-1.5,0.4c-0.4,0.3-0.8,0.6-1.1,1.1c-0.3,0.5-0.4,1.1-0.4,1.8c0,0.7,0.1,1.2,0.4,1.7c0.3,0.5,0.6,0.9,1.1,1.2
c0.4,0.3,0.9,0.4,1.5,0.4c0.5,0,0.9-0.1,1.3-0.3c0.4-0.2,0.7-0.4,0.9-0.7c0.2-0.3,0.4-0.7,0.5-1.1h-1.4c-0.1,0.3-0.2,0.5-0.4,0.7
c-0.2,0.2-0.5,0.2-0.8,0.2c-0.3,0-0.6-0.1-0.9-0.3c-0.2-0.2-0.4-0.4-0.5-0.7c-0.1-0.3-0.2-0.6-0.2-1h4.2
C132.7,42.6,132.7,42.2,132.6,41.8z M128.5,42.3c0-0.3,0.1-0.5,0.2-0.8c0.1-0.3,0.3-0.5,0.5-0.7c0.2-0.2,0.5-0.3,0.9-0.3
c0.3,0,0.5,0.1,0.7,0.2c0.2,0.1,0.3,0.3,0.4,0.4c0.1,0.2,0.2,0.4,0.2,0.6c0,0.2,0,0.3,0,0.5H128.5z"/>
<path class="st0" d="M137.9,42.6l-1.4-0.5c-0.6-0.2-0.9-0.4-0.9-0.8c0-0.2,0.1-0.4,0.3-0.5c0.2-0.1,0.5-0.2,0.8-0.2
c0.4,0,0.7,0.1,0.9,0.2c0.2,0.1,0.3,0.3,0.3,0.5h1.3c0-0.5-0.2-1-0.7-1.3c-0.4-0.3-1.1-0.5-1.9-0.5c-0.8,0-1.4,0.2-1.9,0.5
c-0.5,0.3-0.7,0.7-0.7,1.3c0,0.4,0.1,0.8,0.4,1c0.3,0.3,0.7,0.5,1.2,0.7l1.3,0.5c0.3,0.1,0.5,0.2,0.7,0.3c0.1,0.1,0.2,0.3,0.2,0.5
c0,0.2-0.1,0.3-0.2,0.4c-0.1,0.1-0.3,0.2-0.4,0.3c-0.2,0.1-0.4,0.1-0.6,0.1c-0.4,0-0.8-0.1-1-0.2c-0.3-0.2-0.4-0.4-0.4-0.7H134
c0,0.4,0.1,0.8,0.4,1.1c0.2,0.3,0.6,0.6,1,0.7c0.4,0.2,0.9,0.3,1.6,0.3c0.5,0,1-0.1,1.4-0.3c0.4-0.2,0.7-0.4,0.9-0.7
c0.2-0.3,0.3-0.6,0.3-0.9c0-0.4-0.1-0.7-0.4-1C138.8,43,138.4,42.8,137.9,42.6z"/>
<rect x="141.6" y="39.7" class="st0" width="1.4" height="6.3"/>
<path class="st0" d="M142.3,36.8c-0.3,0-0.5,0.1-0.6,0.2c-0.2,0.1-0.2,0.3-0.2,0.6c0,0.3,0.1,0.5,0.2,0.6c0.2,0.1,0.4,0.2,0.6,0.2
c0.3,0,0.5-0.1,0.6-0.2c0.2-0.1,0.2-0.4,0.2-0.6c0-0.2-0.1-0.4-0.2-0.6C142.7,36.9,142.5,36.8,142.3,36.8z"/>
<path class="st0" d="M151.1,45.5c-0.4-0.2-0.9-0.4-1.5-0.4h-2.2c-0.4,0-0.7-0.1-0.8-0.2c-0.2-0.1-0.2-0.3-0.2-0.4
c0-0.2,0-0.3,0.1-0.4c0.1-0.1,0.2-0.2,0.3-0.2c0.1,0,0.1,0,0.2,0c0.4,0.1,0.7,0.2,1.2,0.2c0.5,0,0.9-0.1,1.3-0.3
c0.4-0.2,0.7-0.5,1-0.8c0.2-0.3,0.4-0.7,0.4-1.2c0-0.5-0.1-0.9-0.4-1.2c0,0-0.1-0.1-0.1-0.1c0-0.3,0.1-0.5,0.3-0.6
c0.2-0.1,0.5-0.2,0.9-0.2l0.1-1.3c-0.4,0-0.7,0.1-1,0.2c-0.3,0.2-0.5,0.4-0.7,0.7c-0.1,0.2-0.2,0.5-0.2,0.8
c-0.1-0.1-0.2-0.1-0.2-0.2c-0.4-0.2-0.8-0.3-1.3-0.3c-0.5,0-0.9,0.1-1.4,0.3c-0.4,0.2-0.7,0.5-1,0.8c-0.2,0.3-0.4,0.7-0.4,1.2
c0,0.5,0.1,0.9,0.4,1.2c0.1,0.2,0.3,0.3,0.4,0.4c-0.1,0-0.2,0-0.2,0.1c-0.3,0.1-0.4,0.2-0.6,0.4c-0.1,0.2-0.2,0.4-0.2,0.7
c0,0.3,0.1,0.5,0.3,0.7c0.1,0.2,0.3,0.3,0.5,0.4c-0.3,0.1-0.6,0.1-0.8,0.3c-0.3,0.2-0.5,0.6-0.5,1.1c0,0.4,0.1,0.7,0.4,1
c0.3,0.3,0.7,0.6,1.2,0.8c0.5,0.2,1.1,0.3,1.8,0.3c0.8,0,1.4-0.1,2-0.4c0.5-0.3,1-0.6,1.3-1c0.3-0.4,0.4-0.8,0.4-1.3
C151.7,46.1,151.5,45.8,151.1,45.5z M147.1,40.9c0.2-0.2,0.5-0.4,1-0.4c0.4,0,0.7,0.1,1,0.4c0.2,0.2,0.3,0.6,0.3,0.9
c0,0.4-0.1,0.7-0.3,0.9c-0.2,0.3-0.5,0.4-1,0.4c-0.4,0-0.7-0.1-1-0.4c-0.2-0.3-0.3-0.6-0.3-0.9C146.8,41.4,146.9,41.1,147.1,40.9z
M150,47.4c-0.2,0.2-0.5,0.4-0.8,0.5c-0.3,0.1-0.7,0.2-1.2,0.2c-0.6,0-1.1-0.1-1.5-0.3c-0.4-0.2-0.5-0.4-0.5-0.8
c0-0.3,0.1-0.5,0.4-0.7c0.2-0.2,0.6-0.3,1-0.3h2.2c0.2,0,0.4,0.1,0.5,0.2c0.1,0.1,0.2,0.3,0.2,0.4C150.3,47,150.2,47.2,150,47.4z"
/>
<path class="st0" d="M157.9,39.9c-0.3-0.2-0.8-0.3-1.3-0.3c-0.5,0-1,0.1-1.3,0.4c-0.3,0.2-0.5,0.5-0.7,0.9l-0.2-1.1h-1.2V46h1.4
v-2.9c0-0.5,0.1-0.9,0.2-1.3c0.1-0.4,0.3-0.6,0.6-0.8c0.2-0.2,0.5-0.3,0.9-0.3c0.4,0,0.7,0.1,1,0.4c0.2,0.3,0.4,0.6,0.4,1.2V46h1.4
v-3.7c0-0.6-0.1-1.1-0.3-1.5C158.5,40.4,158.2,40.1,157.9,39.9z"/>
<path class="st0" d="M166.7,37c-0.3,0-0.7,0.1-0.9,0.2c-0.3,0.1-0.5,0.3-0.7,0.6c-0.2,0.3-0.3,0.6-0.3,1.1v0.8h-1v0.9h1V46h1.4
v-5.4h1.2v-0.9h-1.2v-0.8c0-0.2,0-0.4,0.1-0.5c0.1-0.1,0.2-0.2,0.3-0.3c0.1,0,0.2-0.1,0.4-0.1c0.1,0,0.2,0,0.3,0
c0.1,0,0.2,0.1,0.3,0.1l0.3-1.1c-0.2,0-0.4-0.1-0.6-0.1C167.1,37,166.9,37,166.7,37z"/>
<path class="st0" d="M173.1,39.9c-0.5-0.2-1-0.4-1.5-0.4c-0.6,0-1.1,0.1-1.5,0.4c-0.5,0.2-0.8,0.6-1.1,1.1
c-0.3,0.5-0.4,1.1-0.4,1.9c0,0.7,0.1,1.3,0.4,1.8c0.3,0.5,0.6,0.9,1.1,1.1c0.5,0.2,1,0.4,1.5,0.4c0.6,0,1.1-0.1,1.5-0.4
c0.5-0.2,0.8-0.6,1.1-1.1c0.3-0.5,0.4-1.1,0.4-1.8c0-0.7-0.1-1.4-0.4-1.9C173.9,40.5,173.6,40.1,173.1,39.9z M173,44
c-0.1,0.3-0.3,0.6-0.6,0.7c-0.3,0.2-0.5,0.2-0.9,0.2c-0.5,0-0.9-0.2-1.2-0.5c-0.3-0.4-0.4-0.9-0.4-1.6c0-0.5,0.1-0.9,0.2-1.2
c0.1-0.3,0.3-0.6,0.6-0.7c0.3-0.2,0.5-0.2,0.9-0.2c0.5,0,0.9,0.2,1.2,0.5c0.3,0.4,0.4,0.9,0.4,1.6C173.2,43.3,173.2,43.7,173,44z"
/>
<path class="st0" d="M179.6,39.6c-0.4,0-0.7,0.1-1.1,0.4c-0.3,0.2-0.5,0.6-0.6,1v-1.3h-1.4V46h1.4v-2.8h0c0-0.5,0.1-0.9,0.2-1.2
c0.1-0.3,0.3-0.6,0.5-0.8c0.2-0.2,0.5-0.3,0.9-0.3c0.1,0,0.2,0,0.4,0c0.1,0,0.3,0,0.4,0.1l0-1.4c-0.1,0-0.2-0.1-0.3-0.1
C179.8,39.6,179.7,39.6,179.6,39.6z"/>
<path class="st0" d="M188.9,41.4l-1.8-0.7c-0.4-0.1-0.6-0.3-0.8-0.4c-0.2-0.1-0.3-0.4-0.3-0.6c0-0.3,0.1-0.5,0.4-0.7
c0.3-0.2,0.6-0.3,1.1-0.3c0.5,0,0.9,0.1,1.1,0.3c0.3,0.2,0.4,0.5,0.5,0.8h1.4c-0.1-0.7-0.4-1.3-0.9-1.8c-0.5-0.4-1.2-0.6-2.1-0.6
c-1,0-1.7,0.2-2.2,0.6c-0.5,0.4-0.8,1-0.8,1.6c0,0.6,0.2,1.1,0.5,1.4c0.3,0.3,0.9,0.6,1.5,0.9l1.6,0.6c0.4,0.1,0.7,0.3,0.9,0.5
c0.2,0.2,0.3,0.4,0.3,0.7c0,0.2-0.1,0.4-0.2,0.6c-0.1,0.2-0.3,0.3-0.6,0.4c-0.3,0.1-0.6,0.1-0.9,0.1c-0.3,0-0.6-0.1-0.9-0.2
c-0.3-0.1-0.5-0.3-0.7-0.5c-0.2-0.2-0.3-0.5-0.3-0.8h-1.4c0,0.6,0.2,1.1,0.5,1.6c0.3,0.4,0.7,0.7,1.2,0.9c0.5,0.2,1,0.3,1.6,0.3
c0.7,0,1.3-0.1,1.8-0.3c0.5-0.2,0.8-0.5,1.1-0.9c0.3-0.4,0.4-0.8,0.4-1.3c0-0.6-0.2-1-0.5-1.4C190,42,189.5,41.7,188.9,41.4z"/>
<path class="st0" d="M197.5,39.9c-0.4-0.3-0.9-0.4-1.4-0.4c-0.4,0-0.8,0.1-1.1,0.3c-0.3,0.2-0.5,0.4-0.7,0.8l-0.1-0.9h-1.1v9.4h1.4
v-3.7c0.1,0.2,0.3,0.3,0.5,0.5c0.3,0.2,0.7,0.3,1.2,0.3c0.5,0,1-0.1,1.4-0.4c0.4-0.3,0.8-0.6,1.1-1.1c0.3-0.5,0.4-1.1,0.4-1.8
c0-0.7-0.1-1.3-0.4-1.8C198.3,40.6,197.9,40.2,197.5,39.9z M197.3,44c-0.2,0.3-0.4,0.5-0.6,0.7c-0.3,0.2-0.5,0.2-0.8,0.2
c-0.3,0-0.5-0.1-0.7-0.2c-0.2-0.1-0.4-0.3-0.6-0.5c-0.1-0.2-0.2-0.5-0.2-0.8v-1.1c0-0.3,0.1-0.6,0.2-0.9c0.1-0.2,0.3-0.4,0.6-0.5
c0.2-0.1,0.5-0.2,0.8-0.2c0.3,0,0.6,0.1,0.9,0.2c0.3,0.2,0.5,0.4,0.6,0.7c0.1,0.3,0.2,0.7,0.2,1.1C197.5,43.3,197.4,43.7,197.3,44z
"/>
<path class="st0" d="M205.7,41.8c-0.1-0.4-0.2-0.8-0.4-1.2c-0.2-0.3-0.5-0.6-0.9-0.8c-0.4-0.2-0.8-0.3-1.3-0.3
c-0.5,0-1,0.1-1.5,0.4c-0.4,0.3-0.8,0.6-1.1,1.1c-0.3,0.5-0.4,1.1-0.4,1.8c0,0.7,0.1,1.2,0.4,1.7c0.3,0.5,0.6,0.9,1.1,1.2
c0.4,0.3,0.9,0.4,1.5,0.4c0.5,0,0.9-0.1,1.3-0.3c0.4-0.2,0.7-0.4,0.9-0.7c0.2-0.3,0.4-0.7,0.5-1.1h-1.4c-0.1,0.3-0.2,0.5-0.4,0.7
c-0.2,0.2-0.5,0.2-0.8,0.2c-0.3,0-0.6-0.1-0.9-0.3c-0.2-0.2-0.4-0.4-0.5-0.7c-0.1-0.3-0.2-0.6-0.2-1h4.2
C205.8,42.6,205.8,42.2,205.7,41.8z M201.6,42.3c0-0.3,0.1-0.5,0.2-0.8c0.1-0.3,0.3-0.5,0.5-0.7c0.2-0.2,0.5-0.3,0.9-0.3
c0.3,0,0.5,0.1,0.7,0.2c0.2,0.1,0.3,0.3,0.4,0.4c0.1,0.2,0.2,0.4,0.2,0.6c0,0.2,0,0.3,0,0.5H201.6z"/>
<path class="st0" d="M209.3,41c0.2-0.1,0.5-0.2,0.7-0.2c0.3,0,0.6,0.1,0.8,0.3c0.2,0.2,0.4,0.4,0.4,0.7l1.4-0.1
c0-0.3-0.1-0.5-0.3-0.8c-0.1-0.3-0.3-0.5-0.5-0.7c-0.2-0.2-0.5-0.4-0.8-0.5c-0.3-0.1-0.7-0.2-1.1-0.2c-0.5,0-1,0.1-1.5,0.4
c-0.5,0.3-0.8,0.6-1.1,1.1c-0.3,0.5-0.4,1.1-0.4,1.8c0,0.7,0.1,1.3,0.4,1.8c0.3,0.5,0.6,0.9,1.1,1.2c0.4,0.3,0.9,0.4,1.5,0.4
c0.6,0,1-0.1,1.4-0.3c0.4-0.2,0.7-0.5,0.9-0.9c0.2-0.4,0.3-0.8,0.3-1.2h-1.4c0,0.3-0.1,0.6-0.3,0.8c-0.2,0.2-0.5,0.3-0.9,0.3
c-0.3,0-0.5-0.1-0.8-0.2c-0.2-0.2-0.4-0.4-0.6-0.7c-0.1-0.3-0.2-0.7-0.2-1.1c0-0.5,0.1-0.9,0.2-1.2C208.8,41.4,209,41.1,209.3,41z"
/>
<path class="st0" d="M215.3,36.8c-0.3,0-0.5,0.1-0.6,0.2c-0.2,0.1-0.2,0.3-0.2,0.6c0,0.3,0.1,0.5,0.2,0.6c0.2,0.1,0.4,0.2,0.6,0.2
c0.3,0,0.5-0.1,0.6-0.2c0.2-0.1,0.2-0.4,0.2-0.6c0-0.2-0.1-0.4-0.2-0.6C215.8,36.9,215.6,36.8,215.3,36.8z"/>
<rect x="214.6" y="39.7" class="st0" width="1.4" height="6.3"/>
<path class="st0" d="M222.6,39.8c-0.4-0.2-0.9-0.3-1.4-0.3c-0.6,0-1.1,0.1-1.5,0.2c-0.4,0.1-0.7,0.4-1,0.6
c-0.2,0.3-0.3,0.7-0.3,1.2h1.5c0-0.2,0.1-0.4,0.2-0.5c0.1-0.1,0.3-0.3,0.5-0.3c0.2-0.1,0.4-0.1,0.7-0.1c0.4,0,0.8,0.1,1,0.3
c0.2,0.2,0.3,0.6,0.3,1.1v0.7c-0.3-0.1-0.5-0.1-0.8-0.2c-0.3-0.1-0.6-0.1-1-0.1c-0.5,0-0.9,0.1-1.3,0.2c-0.4,0.1-0.7,0.3-0.9,0.6
c-0.2,0.3-0.3,0.6-0.3,1c0,0.3,0.1,0.7,0.3,1c0.2,0.3,0.4,0.6,0.8,0.7c0.4,0.2,0.8,0.3,1.3,0.3c0.5,0,0.9-0.1,1.2-0.3
c0.3-0.2,0.6-0.5,0.7-0.9l0.1,1h1.2v-4c0-0.6-0.1-1-0.3-1.4C223.3,40.2,223,39.9,222.6,39.8z M222.5,43.7c0,0.2-0.1,0.4-0.2,0.7
c-0.1,0.2-0.3,0.4-0.5,0.5c-0.2,0.1-0.5,0.2-0.8,0.2c-0.4,0-0.8-0.1-1-0.3c-0.2-0.2-0.3-0.4-0.3-0.7c0-0.3,0.1-0.6,0.4-0.7
c0.2-0.1,0.6-0.2,0.9-0.2c0.3,0,0.5,0,0.8,0.1c0.2,0,0.5,0.1,0.7,0.2V43.7z"/>
<rect x="226.2" y="37.1" class="st0" width="1.4" height="8.9"/>
<polygon class="st0" points="233.3,46 238.7,46 238.7,44.8 234.7,44.8 234.7,42.4 238.4,42.4 238.4,41.2 234.7,41.2 234.7,38.8
238.7,38.8 238.7,37.6 233.3,37.6 "/>
<path class="st0" d="M246.4,47.6c-0.1-0.2-0.1-0.4-0.1-0.6v-7.3h-0.9l-0.3,1c-0.2-0.4-0.5-0.7-0.8-0.9c-0.3-0.2-0.7-0.3-1.1-0.3
c-0.5,0-1,0.1-1.4,0.4c-0.4,0.3-0.8,0.6-1.1,1.1c-0.3,0.5-0.4,1.1-0.4,1.8c0,0.7,0.1,1.3,0.4,1.8c0.3,0.5,0.6,0.9,1.1,1.1
c0.4,0.3,0.9,0.4,1.4,0.4c0.5,0,0.8-0.1,1.1-0.3c0.2-0.2,0.4-0.4,0.5-0.8v1.8c0,0.7,0.1,1.2,0.4,1.5c0.3,0.4,0.8,0.6,1.4,0.8l0.4-1
c-0.2-0.1-0.4-0.2-0.5-0.2C246.5,47.9,246.4,47.8,246.4,47.6z M244.9,43.4c0,0.3-0.1,0.6-0.2,0.8c-0.1,0.2-0.3,0.4-0.6,0.5
c-0.2,0.1-0.5,0.2-0.7,0.2c-0.3,0-0.5-0.1-0.8-0.2c-0.3-0.2-0.5-0.4-0.6-0.7c-0.2-0.3-0.3-0.7-0.3-1.2c0-0.4,0.1-0.8,0.2-1.1
c0.1-0.3,0.3-0.6,0.6-0.7c0.3-0.2,0.5-0.2,0.9-0.2c0.3,0,0.5,0.1,0.7,0.2c0.2,0.1,0.4,0.3,0.5,0.5c0.1,0.2,0.2,0.5,0.2,0.9V43.4z"
/>
<path class="st0" d="M253,42.7c0,0.5-0.1,0.9-0.2,1.2c-0.2,0.3-0.4,0.6-0.6,0.7c-0.3,0.2-0.6,0.3-0.9,0.3c-0.4,0-0.7-0.1-0.9-0.3
c-0.2-0.2-0.3-0.6-0.3-1v-3.9h-1.4v4c0,0.6,0.1,1,0.3,1.4c0.2,0.4,0.5,0.6,0.9,0.8c0.4,0.2,0.8,0.2,1.2,0.2c0.5,0,0.9-0.1,1.2-0.4
c0.3-0.2,0.5-0.5,0.7-0.9V46h1.4v-6.3H253V42.7z"/>
<path class="st0" d="M257.7,36.8c-0.3,0-0.5,0.1-0.6,0.2c-0.2,0.1-0.2,0.3-0.2,0.6c0,0.3,0.1,0.5,0.2,0.6c0.2,0.1,0.4,0.2,0.6,0.2
c0.3,0,0.5-0.1,0.6-0.2c0.2-0.1,0.2-0.4,0.2-0.6c0-0.2-0.1-0.4-0.2-0.6C258.2,36.9,258,36.8,257.7,36.8z"/>
<rect x="257" y="39.7" class="st0" width="1.4" height="6.3"/>
<path class="st0" d="M265.6,39.9c-0.4-0.3-0.9-0.4-1.4-0.4c-0.4,0-0.8,0.1-1.1,0.3c-0.3,0.2-0.5,0.4-0.7,0.8l-0.1-0.9h-1.1v9.4h1.4
v-3.7c0.1,0.2,0.3,0.3,0.5,0.5c0.3,0.2,0.7,0.3,1.2,0.3c0.5,0,1-0.1,1.4-0.4c0.4-0.3,0.8-0.6,1.1-1.1c0.3-0.5,0.4-1.1,0.4-1.8
c0-0.7-0.1-1.3-0.4-1.8C266.4,40.6,266,40.2,265.6,39.9z M265.4,44c-0.2,0.3-0.4,0.5-0.6,0.7c-0.3,0.2-0.5,0.2-0.8,0.2
c-0.3,0-0.5-0.1-0.7-0.2c-0.2-0.1-0.4-0.3-0.6-0.5c-0.1-0.2-0.2-0.5-0.2-0.8v-1.1c0-0.3,0.1-0.6,0.2-0.9c0.1-0.2,0.3-0.4,0.6-0.5
c0.2-0.1,0.5-0.2,0.8-0.2c0.3,0,0.6,0.1,0.9,0.2c0.3,0.2,0.5,0.4,0.6,0.7c0.1,0.3,0.2,0.7,0.2,1.1C265.6,43.3,265.6,43.7,265.4,44z
"/>
<path class="st0" d="M277.9,39.9c-0.3-0.2-0.8-0.3-1.3-0.3c-0.4,0-0.7,0.1-1,0.2c-0.3,0.1-0.6,0.3-0.8,0.6
c-0.1,0.2-0.2,0.4-0.3,0.6c0,0,0-0.1,0-0.1c-0.2-0.4-0.4-0.7-0.8-1c-0.4-0.2-0.8-0.3-1.3-0.3c-0.5,0-0.9,0.1-1.2,0.3
c-0.3,0.2-0.6,0.5-0.8,1l-0.2-1.1h-1.2V46h1.4v-2.9c0-0.5,0.1-0.9,0.2-1.3c0.1-0.4,0.3-0.6,0.6-0.8c0.2-0.2,0.5-0.3,0.9-0.3
c0.4,0,0.7,0.1,1,0.4c0.2,0.3,0.4,0.6,0.4,1.2V46h1.4v-2.9c0-0.8,0.1-1.3,0.4-1.7c0.3-0.4,0.7-0.6,1.2-0.6c0.4,0,0.7,0.1,1,0.4
c0.2,0.3,0.4,0.6,0.4,1.2V46h1.4v-3.7c0-0.6-0.1-1.1-0.3-1.5C278.5,40.4,278.3,40.1,277.9,39.9z"/>
<path class="st0" d="M286.2,41.8c-0.1-0.4-0.2-0.8-0.4-1.2c-0.2-0.3-0.5-0.6-0.9-0.8c-0.4-0.2-0.8-0.3-1.3-0.3
c-0.5,0-1,0.1-1.5,0.4c-0.4,0.3-0.8,0.6-1.1,1.1c-0.3,0.5-0.4,1.1-0.4,1.8c0,0.7,0.1,1.2,0.4,1.7c0.3,0.5,0.6,0.9,1.1,1.2
c0.4,0.3,0.9,0.4,1.5,0.4c0.5,0,0.9-0.1,1.3-0.3c0.4-0.2,0.7-0.4,0.9-0.7c0.2-0.3,0.4-0.7,0.5-1.1h-1.4c-0.1,0.3-0.2,0.5-0.4,0.7
c-0.2,0.2-0.5,0.2-0.8,0.2c-0.3,0-0.6-0.1-0.9-0.3c-0.2-0.2-0.4-0.4-0.5-0.7c-0.1-0.3-0.2-0.6-0.2-1h4.2
C286.3,42.6,286.2,42.2,286.2,41.8z M282,42.3c0-0.3,0.1-0.5,0.2-0.8c0.1-0.3,0.3-0.5,0.5-0.7c0.2-0.2,0.5-0.3,0.9-0.3
c0.3,0,0.5,0.1,0.7,0.2c0.2,0.1,0.3,0.3,0.4,0.4c0.1,0.2,0.2,0.4,0.2,0.6c0,0.2,0,0.3,0,0.5H282z"/>
<path class="st0" d="M292.8,39.9c-0.3-0.2-0.8-0.3-1.3-0.3c-0.5,0-1,0.1-1.3,0.4c-0.3,0.2-0.5,0.5-0.7,0.9l-0.2-1.1h-1.2V46h1.4
v-2.9c0-0.5,0.1-0.9,0.2-1.3c0.1-0.4,0.3-0.6,0.6-0.8c0.2-0.2,0.5-0.3,0.9-0.3c0.4,0,0.7,0.1,1,0.4c0.2,0.3,0.4,0.6,0.4,1.2V46h1.4
v-3.7c0-0.6-0.1-1.1-0.3-1.5C293.4,40.4,293.2,40.1,292.8,39.9z"/>
<path class="st0" d="M298.9,45c-0.2,0-0.3,0.1-0.5,0.1c-0.5,0-0.8-0.3-0.8-0.8v-3.6h1.8v-0.9h-1.8V38h-1.4v1.7h-0.9v0.9h0.9v3.7
c0,0.4,0.1,0.8,0.3,1.1c0.2,0.3,0.4,0.5,0.7,0.6c0.3,0.1,0.6,0.2,1,0.2c0.2,0,0.5,0,0.7-0.1c0.2-0.1,0.5-0.1,0.7-0.2l-0.2-1
C299.3,44.9,299.1,45,298.9,45z"/>
<path class="st0" d="M58.3,21.7H65l1.3,4h3.1L63.5,8.1h-3.6l-6,17.5H57L58.3,21.7z M61.7,11.6l2.6,7.9h-5.2L61.7,11.6z"/>
<path class="st0" d="M80.4,23.1c-0.5,0.2-1.2,0.3-1.9,0.3c-0.7,0-1.3-0.1-1.9-0.3c-0.6-0.2-1.1-0.6-1.4-1c-0.4-0.5-0.5-1.1-0.5-1.8
h-2.8c0,1.3,0.4,2.4,1,3.2c0.6,0.9,1.4,1.5,2.4,1.9c1,0.4,2.1,0.6,3.3,0.6c1.4,0,2.7-0.2,3.7-0.6c1-0.4,1.8-1,2.3-1.8
c0.5-0.8,0.8-1.7,0.8-2.7c0-1.2-0.4-2.1-1.1-2.9c-0.7-0.8-1.7-1.4-3-1.9l-3.8-1.4c-0.7-0.3-1.3-0.6-1.6-0.9
c-0.3-0.3-0.5-0.7-0.5-1.3c0-0.6,0.3-1.1,0.8-1.6c0.6-0.4,1.3-0.6,2.3-0.6c1.1,0,1.9,0.2,2.4,0.6c0.5,0.4,0.9,1,1,1.7h2.8
c-0.1-1.6-0.7-2.8-1.8-3.7c-1.1-0.9-2.5-1.3-4.4-1.3c-2,0-3.5,0.4-4.6,1.3c-1.1,0.9-1.7,2-1.7,3.4c0,1.2,0.4,2.2,1.1,2.9
c0.7,0.7,1.8,1.3,3.2,1.8l3.4,1.3c0.8,0.3,1.4,0.6,1.8,1c0.4,0.4,0.6,0.9,0.6,1.5c0,0.5-0.2,0.9-0.5,1.2
C81.3,22.6,80.9,22.9,80.4,23.1z"/>
<polygon class="st0" points="92.7,25.6 95.7,25.6 95.7,10.6 101.2,10.6 101.2,8.1 87.2,8.1 87.2,10.6 92.7,10.6 "/>
<path class="st0" d="M106.8,18.7h2.6l4,6.9h3.8l-4.8-7.2c1-0.2,1.9-0.6,2.5-1.2c1.1-1,1.6-2.2,1.6-3.8c0-1.6-0.5-2.9-1.6-3.9
c-1.1-1-2.7-1.5-4.7-1.5h-6.4v17.5h2.9V18.7z M106.8,10.6h3.5c1.1,0,2,0.3,2.6,0.8c0.6,0.6,0.9,1.3,0.9,2.2c0,0.9-0.3,1.6-0.9,2.2
c-0.6,0.6-1.5,0.9-2.8,0.9h-3.2V10.6z"/>
<path class="st0" d="M123.7,25c1.2,0.7,2.5,1,4.1,1c1.5,0,2.9-0.3,4.1-1c1.2-0.7,2.1-1.7,2.8-3c0.7-1.4,1-3.1,1-5.1
c0-2-0.3-3.7-1-5c-0.7-1.3-1.6-2.4-2.8-3c-1.2-0.7-2.5-1-4.1-1c-1.5,0-2.9,0.3-4.1,1c-1.2,0.7-2.1,1.7-2.8,3s-1,3-1,5.1
c0,2,0.3,3.7,1,5.1C121.6,23.3,122.5,24.3,123.7,25z M124.2,12.1c0.9-1.1,2-1.7,3.5-1.7c1.5,0,2.7,0.6,3.5,1.7s1.3,2.7,1.3,4.8
c0,2.1-0.4,3.7-1.3,4.8c-0.9,1.1-2.1,1.7-3.5,1.7c-1.5,0-2.6-0.6-3.5-1.7c-0.9-1.1-1.3-2.7-1.3-4.8
C122.9,14.8,123.4,13.2,124.2,12.1z"/>
<polygon class="st0" points="149.4,25.6 152.3,25.6 152.3,10.6 157.8,10.6 157.8,8.1 143.9,8.1 143.9,10.6 149.4,10.6 "/>
<polygon class="st0" points="171.6,23.1 163.4,23.1 163.4,18.1 171.1,18.1 171.1,15.6 163.4,15.6 163.4,10.6 171.6,10.6 171.6,8.1
160.5,8.1 160.5,25.6 171.6,25.6 "/>
<path class="st0" d="M178.6,24.9c1.1,0.7,2.5,1.1,4,1.1c1.4,0,2.7-0.2,3.7-0.7c1-0.5,1.8-1.2,2.4-2.1c0.6-0.9,0.8-2,0.8-3.3h-3.1
c0,1-0.3,1.8-1,2.5c-0.7,0.6-1.6,1-2.8,1c-1,0-1.8-0.3-2.4-0.8c-0.7-0.5-1.2-1.3-1.5-2.3c-0.3-1-0.5-2.1-0.5-3.4
c0-1.4,0.2-2.6,0.6-3.6c0.4-1,0.9-1.7,1.6-2.1c0.7-0.5,1.4-0.7,2.2-0.7c0.9,0,1.7,0.3,2.4,0.9c0.7,0.6,1.1,1.4,1.5,2.3l3.1-0.6
c-0.4-1.6-1.1-2.8-2.2-3.8c-1.1-1-2.7-1.5-4.7-1.5c-1.5,0-2.8,0.3-3.9,1c-1.1,0.7-2,1.7-2.7,3c-0.7,1.3-1,3-1,5
c0,1.9,0.3,3.5,0.9,4.9C176.6,23.1,177.5,24.1,178.6,24.9z"/>
<polygon class="st0" points="196.3,12.7 204.5,25.6 207.5,25.6 207.5,8.1 204.6,8.1 204.6,20.8 196.6,8.1 193.4,8.1 193.4,25.6
196.3,25.6 "/>
<path class="st0" d="M215.2,25c1.2,0.7,2.5,1,4.1,1c1.5,0,2.9-0.3,4.1-1c1.2-0.7,2.1-1.7,2.8-3c0.7-1.4,1-3.1,1-5.1
c0-2-0.3-3.7-1-5c-0.7-1.3-1.6-2.4-2.8-3c-1.2-0.7-2.5-1-4.1-1c-1.5,0-2.9,0.3-4.1,1c-1.2,0.7-2.1,1.7-2.8,3c-0.7,1.3-1,3-1,5.1
c0,2,0.3,3.7,1,5.1C213.1,23.3,214,24.3,215.2,25z M215.7,12.1c0.9-1.1,2-1.7,3.5-1.7c1.5,0,2.7,0.6,3.5,1.7
c0.9,1.1,1.3,2.7,1.3,4.8c0,2.1-0.4,3.7-1.3,4.8c-0.9,1.1-2.1,1.7-3.5,1.7c-1.5,0-2.6-0.6-3.5-1.7c-0.9-1.1-1.3-2.7-1.3-4.8
C214.4,14.8,214.9,13.2,215.7,12.1z"/>
<polygon class="st0" points="241.3,23.1 233.9,23.1 233.9,8.1 231,8.1 231,25.6 241.3,25.6 "/>
<path class="st0" d="M246.8,25c1.2,0.7,2.5,1,4.1,1c1.5,0,2.9-0.3,4.1-1c1.2-0.7,2.1-1.7,2.8-3c0.7-1.4,1-3.1,1-5.1
c0-2-0.3-3.7-1-5c-0.7-1.3-1.6-2.4-2.8-3c-1.2-0.7-2.5-1-4.1-1c-1.5,0-2.9,0.3-4.1,1c-1.2,0.7-2.1,1.7-2.8,3c-0.7,1.3-1,3-1,5.1
c0,2,0.3,3.7,1,5.1C244.7,23.3,245.6,24.3,246.8,25z M247.3,12.1c0.9-1.1,2-1.7,3.5-1.7c1.5,0,2.7,0.6,3.5,1.7
c0.9,1.1,1.3,2.7,1.3,4.8c0,2.1-0.4,3.7-1.3,4.8c-0.9,1.1-2.1,1.7-3.5,1.7c-1.5,0-2.6-0.6-3.5-1.7c-0.9-1.1-1.3-2.7-1.3-4.8
C246,14.8,246.4,13.2,247.3,12.1z"/>
<path class="st0" d="M265.1,24.9c1.2,0.7,2.4,1.1,3.8,1.1c1.2,0,2.1-0.3,2.9-0.9c0.7-0.6,1.2-1.4,1.6-2.4l0.3,2.9h2.4v-8.9h-6.9
v1.9l4.1,0.1v0.1c0,1-0.2,1.8-0.5,2.5c-0.4,0.7-0.8,1.2-1.5,1.5c-0.6,0.4-1.3,0.5-2.1,0.5c-0.9,0-1.8-0.3-2.5-0.8
c-0.7-0.5-1.3-1.3-1.7-2.2c-0.4-1-0.6-2.1-0.6-3.3c0-1.4,0.2-2.5,0.7-3.5c0.5-1,1.1-1.7,1.8-2.3c0.8-0.5,1.6-0.8,2.5-0.8
c0.8,0,1.5,0.2,2.1,0.6c0.6,0.4,1.2,1,1.7,1.7l2.9-0.7c-0.7-1.4-1.5-2.5-2.6-3.2c-1.1-0.7-2.4-1-4-1c-1.2,0-2.2,0.2-3.2,0.6
c-1,0.4-1.9,1-2.6,1.8c-0.8,0.8-1.3,1.8-1.8,2.9c-0.4,1.1-0.6,2.4-0.6,3.9c0,1.8,0.3,3.4,1,4.7C263,23.1,263.9,24.1,265.1,24.9z"/>
<rect x="280.8" y="8.1" class="st0" width="2.9" height="17.5"/>
<polygon class="st0" points="300,23.1 291.8,23.1 291.8,18.1 299.5,18.1 299.5,15.6 291.8,15.6 291.8,10.6 300,10.6 300,8.1
288.9,8.1 288.9,25.6 300,25.6 "/>
<path class="st0" d="M40.6,6.7H0v40.6h40.6V32.2H300v-1.2H40.6V6.7z M7.9,36.7H3.3c-0.4,0-0.7-0.3-0.7-0.7c0-0.4,0.3-0.7,0.7-0.7
h4.7c0.4,0,0.7,0.3,0.7,0.7C8.7,36.4,8.3,36.7,7.9,36.7z M7.9,32.2H3.3c-0.4,0-0.7-0.3-0.7-0.7c0-0.4,0.3-0.7,0.7-0.7h4.7
c0.4,0,0.7,0.3,0.7,0.7C8.7,31.9,8.3,32.2,7.9,32.2z M7.9,27.7H3.3c-0.4,0-0.7-0.3-0.7-0.7c0-0.4,0.3-0.7,0.7-0.7h4.7
c0.4,0,0.7,0.3,0.7,0.7C8.7,27.4,8.3,27.7,7.9,27.7z M7.9,23.3H3.3c-0.4,0-0.7-0.3-0.7-0.7c0-0.4,0.3-0.7,0.7-0.7h4.7
c0.4,0,0.7,0.3,0.7,0.7C8.7,22.9,8.3,23.3,7.9,23.3z M7.9,18.8H3.3c-0.4,0-0.7-0.3-0.7-0.7c0-0.4,0.3-0.7,0.7-0.7h4.7
c0.4,0,0.7,0.3,0.7,0.7C8.7,18.4,8.3,18.8,7.9,18.8z M12.1,43.5c0,0.4-0.3,0.7-0.7,0.7c-0.4,0-0.7-0.3-0.7-0.7v-4.7
c0-0.4,0.3-0.7,0.7-0.7c0.4,0,0.7,0.3,0.7,0.7V43.5z M12.1,15.2c0,0.4-0.3,0.7-0.7,0.7c-0.4,0-0.7-0.3-0.7-0.7v-4.7
c0-0.4,0.3-0.7,0.7-0.7c0.4,0,0.7,0.3,0.7,0.7V15.2z M16.6,43.5c0,0.4-0.3,0.7-0.7,0.7c-0.4,0-0.7-0.3-0.7-0.7v-4.7
c0-0.4,0.3-0.7,0.7-0.7c0.4,0,0.7,0.3,0.7,0.7V43.5z M16.6,15.2c0,0.4-0.3,0.7-0.7,0.7c-0.4,0-0.7-0.3-0.7-0.7v-4.7
c0-0.4,0.3-0.7,0.7-0.7c0.4,0,0.7,0.3,0.7,0.7V15.2z M21.1,43.5c0,0.4-0.3,0.7-0.7,0.7c-0.4,0-0.7-0.3-0.7-0.7v-4.7
c0-0.4,0.3-0.7,0.7-0.7c0.4,0,0.7,0.3,0.7,0.7V43.5z M21.1,15.2c0,0.4-0.3,0.7-0.7,0.7c-0.4,0-0.7-0.3-0.7-0.7v-4.7
c0-0.4,0.3-0.7,0.7-0.7c0.4,0,0.7,0.3,0.7,0.7V15.2z M25.5,43.5c0,0.4-0.3,0.7-0.7,0.7c-0.4,0-0.7-0.3-0.7-0.7v-4.7
c0-0.4,0.3-0.7,0.7-0.7c0.4,0,0.7,0.3,0.7,0.7V43.5z M25.5,15.2c0,0.4-0.3,0.7-0.7,0.7c-0.4,0-0.7-0.3-0.7-0.7v-4.7
c0-0.4,0.3-0.7,0.7-0.7c0.4,0,0.7,0.3,0.7,0.7V15.2z M30,43.5c0,0.4-0.3,0.7-0.7,0.7s-0.7-0.3-0.7-0.7v-4.7c0-0.4,0.3-0.7,0.7-0.7
s0.7,0.3,0.7,0.7V43.5z M30,15.2c0,0.4-0.3,0.7-0.7,0.7s-0.7-0.3-0.7-0.7v-4.7c0-0.4,0.3-0.7,0.7-0.7s0.7,0.3,0.7,0.7V15.2z
M37.3,36.7h-4.7c-0.4,0-0.7-0.3-0.7-0.7c0-0.4,0.3-0.7,0.7-0.7h4.7c0.4,0,0.7,0.3,0.7,0.7C38.1,36.4,37.7,36.7,37.3,36.7z
M37.3,32.2h-4.7c-0.4,0-0.7-0.3-0.7-0.7c0-0.4,0.3-0.7,0.7-0.7h4.7c0.4,0,0.7,0.3,0.7,0.7C38.1,31.9,37.7,32.2,37.3,32.2z
M37.3,27.7h-4.7c-0.4,0-0.7-0.3-0.7-0.7c0-0.4,0.3-0.7,0.7-0.7h4.7c0.4,0,0.7,0.3,0.7,0.7C38.1,27.4,37.7,27.7,37.3,27.7z
M37.3,23.3h-4.7c-0.4,0-0.7-0.3-0.7-0.7c0-0.4,0.3-0.7,0.7-0.7h4.7c0.4,0,0.7,0.3,0.7,0.7C38.1,22.9,37.7,23.3,37.3,23.3z
M37.3,18.8h-4.7c-0.4,0-0.7-0.3-0.7-0.7c0-0.4,0.3-0.7,0.7-0.7h4.7c0.4,0,0.7,0.3,0.7,0.7C38.1,18.4,37.7,18.8,37.3,18.8z"/>
<path class="st0" d="M360.9,50.7v0.7c0.3,0,0.6-0.1,0.9-0.3v4.6h0.7v-5.3h-0.5C361.6,50.5,361.3,50.7,360.9,50.7z"/>
<rect x="341" y="52.9" class="st0" width="2.3" height="0.6"/>
<path class="st0" d="M332.1,50.4c-0.3-0.1-0.5-0.2-0.8-0.2c-0.6,0-1,0.1-1.3,0.4c-0.3,0.3-0.5,0.7-0.5,1.2c0,0.5,0.2,0.9,0.5,1.2
c0.3,0.3,0.7,0.5,1.1,0.5c0.4,0,0.8-0.1,1.1-0.3c0.3-0.2,0.4-0.6,0.5-1c0,0.2,0,0.4,0,0.6c0,1.1-0.3,1.9-0.8,2.2
c-0.2,0.1-0.4,0.2-0.7,0.2c-0.3,0-0.5-0.1-0.7-0.3c-0.2-0.2-0.3-0.4-0.3-0.8h-0.7c0,0.5,0.2,0.9,0.5,1.2c0.3,0.3,0.8,0.5,1.4,0.5
c0.3,0,0.6-0.1,0.9-0.2c0.8-0.4,1.2-1.3,1.2-2.7c0-0.8-0.2-1.4-0.6-1.9C332.6,50.7,332.3,50.5,332.1,50.4z M332,52.6
c-0.2,0.2-0.5,0.3-0.8,0.3s-0.6-0.1-0.8-0.3c-0.2-0.2-0.3-0.4-0.3-0.8c0-0.3,0.1-0.6,0.3-0.8c0.2-0.2,0.5-0.3,0.8-0.3
c0.3,0,0.6,0.1,0.8,0.3c0.2,0.2,0.3,0.4,0.3,0.7C332.3,52.2,332.2,52.4,332,52.6z"/>
<path class="st0" d="M347,54.2c0.1-0.1,0.3-0.3,0.6-0.4l1.1-0.4c0.4-0.2,0.7-0.4,0.9-0.6c0.2-0.3,0.3-0.6,0.3-1
c0-0.4-0.2-0.8-0.5-1.1c-0.3-0.3-0.8-0.4-1.3-0.4c-0.5,0-1,0.1-1.3,0.4c-0.3,0.2-0.5,0.6-0.6,1.1h0.7c0-0.3,0.2-0.5,0.4-0.6
c0.2-0.1,0.5-0.2,0.8-0.2c0.3,0,0.6,0.1,0.8,0.3c0.2,0.2,0.3,0.4,0.3,0.7c0,0.5-0.3,0.8-0.8,1.1l-1.1,0.4c-0.2,0.1-0.4,0.2-0.5,0.3
c-0.5,0.3-0.7,0.8-0.7,1.4v0.8h3.8V55h-3.1v-0.3C346.8,54.5,346.9,54.4,347,54.2z"/>
<path class="st0" d="M336.5,52.4c-0.4,0-0.8,0.1-1.1,0.4c-0.3,0.2-0.4,0.6-0.5,1.1c0-0.2,0-0.4,0-0.7c0-0.7,0.1-1.3,0.4-1.7
c0.3-0.4,0.7-0.6,1.2-0.6c0.5,0,0.8,0.3,0.9,0.8h0.7c0-0.4-0.2-0.8-0.5-1c-0.3-0.3-0.7-0.4-1.2-0.4c-0.7,0-1.2,0.3-1.6,0.8
c-0.4,0.5-0.6,1.2-0.6,2s0.2,1.5,0.6,2c0.4,0.5,0.9,0.8,1.5,0.8c0.5,0,1-0.2,1.3-0.5c0.3-0.3,0.5-0.7,0.5-1.2
c0-0.5-0.2-0.9-0.5-1.2C337.3,52.6,337,52.4,336.5,52.4z M337.1,54.9c-0.2,0.2-0.5,0.3-0.8,0.3s-0.6-0.1-0.8-0.3
c-0.2-0.2-0.3-0.4-0.3-0.7c0-0.3,0.1-0.6,0.3-0.8c0.2-0.2,0.5-0.3,0.8-0.3c0.3,0,0.6,0.1,0.8,0.3c0.2,0.2,0.3,0.4,0.3,0.8
C337.5,54.4,337.3,54.7,337.1,54.9z"/>
<path class="st0" d="M353.1,50.2c-0.6,0-1.2,0.2-1.5,0.7c-0.4,0.5-0.6,1.2-0.6,2.1c0,0.9,0.2,1.6,0.6,2.1c0.4,0.5,0.9,0.7,1.5,0.7
c0.6,0,1.2-0.2,1.5-0.7c0.4-0.5,0.6-1.2,0.6-2c0-0.9-0.2-1.6-0.6-2C354.2,50.5,353.7,50.2,353.1,50.2z M354.1,54.6
c-0.2,0.4-0.6,0.6-1,0.6c-0.5,0-0.8-0.2-1-0.6c-0.2-0.4-0.4-0.9-0.4-1.6c0-0.7,0.1-1.2,0.4-1.6c0.2-0.4,0.6-0.6,1-0.6
c0.5,0,0.8,0.2,1,0.6c0.2,0.4,0.4,0.9,0.4,1.6C354.5,53.7,354.3,54.2,354.1,54.6z"/>
<path class="st0" d="M327.1,50.4c-0.3-0.1-0.5-0.2-0.8-0.2c-0.6,0-1,0.1-1.3,0.4c-0.3,0.3-0.5,0.7-0.5,1.2c0,0.5,0.2,0.9,0.5,1.2
c0.3,0.3,0.7,0.5,1.1,0.5c0.4,0,0.8-0.1,1.1-0.3c0.3-0.2,0.4-0.6,0.5-1c0,0.2,0,0.4,0,0.6c0,1.1-0.3,1.9-0.8,2.2
c-0.2,0.1-0.4,0.2-0.7,0.2c-0.3,0-0.5-0.1-0.7-0.3c-0.2-0.2-0.3-0.4-0.3-0.8h-0.7c0,0.5,0.2,0.9,0.5,1.2c0.3,0.3,0.8,0.5,1.4,0.5
c0.3,0,0.6-0.1,0.9-0.2c0.8-0.4,1.2-1.3,1.2-2.7c0-0.8-0.2-1.4-0.6-1.9C327.6,50.7,327.4,50.5,327.1,50.4z M327.1,52.6
c-0.2,0.2-0.5,0.3-0.8,0.3c-0.3,0-0.6-0.1-0.8-0.3c-0.2-0.2-0.3-0.4-0.3-0.8c0-0.3,0.1-0.6,0.3-0.8c0.2-0.2,0.5-0.3,0.8-0.3
c0.3,0,0.6,0.1,0.8,0.3c0.2,0.2,0.3,0.4,0.3,0.7C327.4,52.2,327.3,52.4,327.1,52.6z"/>
<path class="st0" d="M321.5,50.7v0.7c0.3,0,0.6-0.1,0.9-0.3v4.6h0.7v-5.3h-0.5C322.3,50.5,321.9,50.7,321.5,50.7z"/>
<path class="st0" d="M357,54.2c0.1-0.1,0.3-0.3,0.6-0.4l1.1-0.4c0.4-0.2,0.7-0.4,0.9-0.6c0.2-0.3,0.3-0.6,0.3-1
c0-0.4-0.2-0.8-0.5-1.1c-0.3-0.3-0.8-0.4-1.3-0.4c-0.5,0-1,0.1-1.3,0.4c-0.3,0.2-0.5,0.6-0.6,1.1h0.7c0-0.3,0.2-0.5,0.4-0.6
c0.2-0.1,0.5-0.2,0.8-0.2c0.3,0,0.6,0.1,0.8,0.3c0.2,0.2,0.3,0.4,0.3,0.7c0,0.5-0.3,0.8-0.8,1.1l-1.1,0.4c-0.2,0.1-0.4,0.2-0.5,0.3
c-0.5,0.3-0.7,0.8-0.7,1.4v0.8h3.8V55h-3.1v-0.3C356.8,54.5,356.9,54.4,357,54.2z"/>
<rect x="350.4" y="41" class="st0" width="1" height="5.5"/>
<polygon class="st0" points="342.8,46.5 342.8,41 341.8,41 341.8,44.9 339.4,41 338.3,41 338.3,46.5 339.3,46.5 339.3,42.5
341.8,46.5 "/>
<polygon class="st0" points="348.9,46.5 348.9,41 347.9,41 347.9,44.9 345.4,41 344.4,41 344.4,46.5 345.4,46.5 345.4,42.5
347.9,46.5 "/>
<path class="st0" d="M336.3,46.5h1.1l-1.8-5.5h-1.3l-1.9,5.5h1.1l0.4-1.2h2L336.3,46.5z M334.1,44.6l0.8-2.4l0.8,2.4H334.1z"/>
<path class="st0" d="M373.5,33.6c-0.1-0.1-0.2-0.2-0.3-0.3c-0.1-0.1-0.2-0.2-0.3-0.4c-0.1-0.1-0.3-0.3-0.4-0.4c0,0-0.1-0.1-0.1-0.1
c0,0-0.1-0.1-0.1-0.1c-0.1-0.1-0.2-0.2-0.2-0.2c-0.1-0.1-0.2-0.2-0.3-0.2c0,0-0.1-0.1-0.1-0.1c0,0-0.1-0.1-0.1-0.1
c-0.2-0.2-0.4-0.3-0.6-0.5c-0.2-0.2-0.5-0.4-0.7-0.6c-0.1-0.1-0.2-0.2-0.4-0.3c-0.1,0-0.1-0.1-0.2-0.1c-0.1,0-0.1-0.1-0.2-0.1
c-1.1-0.8-2.4-1.5-3.9-2.2c-0.2-0.1-0.4-0.2-0.6-0.3c-0.1,0-0.2-0.1-0.3-0.1c-0.1,0-0.2-0.1-0.3-0.1c-0.2-0.1-0.4-0.2-0.6-0.2
c-0.2-0.1-0.4-0.1-0.6-0.2c-0.1,0-0.2-0.1-0.3-0.1c-0.1,0-0.2-0.1-0.3-0.1c-0.2-0.1-0.4-0.1-0.7-0.2l-0.1,0l-0.1,0
c-0.1,0-0.1,0-0.2,0c-0.1,0-0.2-0.1-0.3-0.1c-0.2-0.1-0.5-0.1-0.7-0.2c-0.2,0-0.5-0.1-0.7-0.1c-0.1,0-0.2,0-0.4-0.1
c-0.1,0-0.2,0-0.4-0.1c-0.5-0.1-1-0.1-1.5-0.2c-0.3,0-0.5,0-0.8-0.1c-0.1,0-0.3,0-0.4,0c0.1,0,0.1,0,0.2,0c1.1-1.6,1.8-3.5,1.8-5.7
c0-2.8-0.9-5.1-2.6-6.8c-1.7-1.8-3.9-2.7-6.5-2.7c-0.3,0-0.6,0-0.9,0.1c-0.3,0-0.6,0.1-1,0.2l1.2-5.2h9.4V0.2h-13.5l-2.1,9.7
c-0.2-0.7-0.5-1.4-0.9-2c-0.8-1.4-1.9-2.5-3.2-3.2c-1.3-0.7-2.9-1.1-4.7-1.1c-2.9,0-5.2,0.9-7,2.7c-1.8,1.8-2.8,4.3-2.9,7.4h5.4
c0.1-1.5,0.5-2.7,1.3-3.6c0.8-0.9,1.8-1.3,3-1.3c1.2,0,2.1,0.4,2.9,1.1c0.7,0.7,1.1,1.7,1.1,2.8c0,1.1-0.4,2.3-1.1,3.6
c-0.7,1.3-2.2,3.1-4.3,5.3l-9,9.1v2.6c-0.1,0-0.2,0-0.3,0c-0.8,0-1.6,0-2.4-0.1c-1.5-0.1-2.9-0.4-4.1-0.8c-0.6-0.2-1.2-0.4-1.7-0.5
c-0.1,0-0.3-0.1-0.4-0.1c-0.1,0-0.1,0-0.2-0.1c-0.1,0-0.1,0-0.2-0.1c-0.1,0-0.2-0.1-0.3-0.1c-0.1,0-0.2-0.1-0.3-0.1
c-0.1,0-0.2-0.1-0.3-0.1c-0.1,0-0.2-0.1-0.3-0.1c-0.2-0.1-0.4-0.2-0.5-0.3c-0.2-0.1-0.3-0.2-0.4-0.2c-0.1-0.1-0.3-0.1-0.4-0.2
c-0.2-0.1-0.4-0.2-0.5-0.3c-0.1-0.1-0.2-0.1-0.2-0.1c0,0,0.1,0.1,0.1,0.1c0,0,0.1,0.1,0.2,0.2c0,0,0.1,0.1,0.1,0.1
c0,0,0.1,0.1,0.1,0.1c0.1,0.1,0.2,0.2,0.3,0.3c0.1,0.1,0.2,0.2,0.4,0.3c0.1,0.1,0.3,0.2,0.4,0.4c0.1,0.1,0.2,0.1,0.3,0.2
c0.1,0.1,0.2,0.1,0.3,0.2c0.1,0.1,0.2,0.1,0.3,0.2c0.1,0.1,0.2,0.1,0.3,0.2c0.1,0,0.1,0.1,0.2,0.1c0.1,0,0.1,0.1,0.2,0.1
c0.1,0.1,0.2,0.2,0.4,0.2c0.1,0.1,0.2,0.2,0.4,0.2c0.1,0.1,0.3,0.2,0.4,0.2c0.1,0.1,0.3,0.2,0.4,0.2c0.1,0,0.1,0.1,0.2,0.1
c0.1,0,0.1,0.1,0.2,0.1c1.2,0.6,2.6,1.2,4.2,1.6c0.8,0.2,1.7,0.4,2.5,0.5c0.9,0.1,1.8,0.2,2.8,0.2c1.9,0.1,3.9-0.1,6-0.4
c2-0.3,4.1-0.8,6.2-1.5c2.1-0.6,4.1-1.4,6.1-2.3c0.1-0.1,0.2-0.1,0.4-0.2l0.3-0.2c0.2-0.1,0.5-0.2,0.7-0.3l0.1,0l0.1,0l0.2-0.1
l0.3-0.1l0.2-0.1c0.1,0,0.1,0,0.2-0.1l0.3-0.1c0.9-0.3,1.9-0.6,2.8-0.9c0.1,0,0.1,0,0.2,0l0.2,0c0.1,0,0.2-0.1,0.4-0.1
c0.2-0.1,0.5-0.1,0.7-0.2c0.1,0,0.2-0.1,0.4-0.1c0.1,0,0.1,0,0.2,0l0.2,0c0.1,0,0.2,0,0.4-0.1l0.2,0c0.1,0,0.1,0,0.2,0
c0.2,0,0.5-0.1,0.7-0.1c0.1,0,0.2,0,0.4,0c0.1,0,0.2,0,0.4,0c0.1,0,0.2,0,0.4,0c0.1,0,0.2,0,0.4,0c0.1,0,0.2,0,0.3,0
c0.1,0,0.2,0,0.3,0c0.9-0.1,1.8-0.1,2.7-0.1c0.2,0,0.4,0,0.7,0c0.1,0,0.1,0,0.2,0l0.1,0l0.1,0c0.1,0,0.2,0,0.3,0c0.1,0,0.2,0,0.3,0
c0.1,0,0.2,0,0.3,0c0.2,0,0.4,0,0.6,0.1c0.2,0,0.4,0,0.6,0.1c0.1,0,0.2,0,0.3,0c0.1,0,0.1,0,0.2,0l0.1,0l0.1,0
c0.2,0,0.4,0.1,0.6,0.1c0.1,0,0.2,0,0.3,0c0.1,0,0.2,0,0.3,0.1c0.2,0,0.4,0.1,0.6,0.1c0,0,0.1,0,0.1,0c0,0,0.1,0,0.1,0
c0.1,0,0.2,0,0.3,0.1c0.1,0,0.2,0,0.3,0.1c0.1,0,0.2,0,0.3,0.1c0.2,0.1,0.4,0.1,0.6,0.2c1.5,0.4,2.8,0.9,3.9,1.4
c0.1,0,0.1,0.1,0.2,0.1c0.1,0,0.1,0.1,0.2,0.1c0.1,0.1,0.3,0.1,0.4,0.2c0.1,0.1,0.3,0.1,0.4,0.2c0.1,0.1,0.3,0.1,0.4,0.2
c0.2,0.1,0.5,0.3,0.7,0.4c0.1,0,0.1,0.1,0.2,0.1c0.1,0,0.1,0.1,0.2,0.1c0.1,0.1,0.2,0.1,0.3,0.2c0.1,0.1,0.2,0.1,0.3,0.2
c0,0,0.1,0.1,0.1,0.1c0,0,0.1,0.1,0.1,0.1c0.2,0.1,0.3,0.2,0.5,0.3c0.2,0.1,0.3,0.2,0.4,0.3c0.1,0.1,0.2,0.2,0.3,0.2
c0.4,0.3,0.6,0.4,0.6,0.4C374,34.2,373.8,34,373.5,33.6z M335.8,24.8c2.6-2.7,4.4-5,5.4-7c0.2-0.4,0.4-0.9,0.6-1.3l3.6,0.8
c0.6-0.6,1.1-1,1.7-1.3c0.6-0.3,1.2-0.4,1.8-0.4c1.1,0,2.1,0.4,2.9,1.3c0.8,0.9,1.2,1.9,1.2,3.3c0,1.4-0.4,2.5-1.3,3.4
c-0.8,0.9-1.9,1.3-3.1,1.3c-0.9,0-1.7-0.2-2.4-0.7c-0.7-0.5-1.3-1.1-1.7-2h-5.7c0.5,2.4,1.7,4.3,3.5,5.7c0.2,0.1,0.3,0.2,0.5,0.4
c0,0,0,0,0,0c-0.2,0.1-0.3,0.1-0.5,0.2c0,0,0,0,0,0h-9.6L335.8,24.8z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 31 KiB

286
RotaxMonitor/data/script.js Normal file
View File

@@ -0,0 +1,286 @@
let ws;
let lastMessageTimestamp = 0;
const IDLE_THRESHOLD_MS = 1000;
const loadingIndicator = document.getElementById("loadingIndicator");
let chartA, chartB;
let dataA = {
labels: [],
datasets: [
{ label: "RPM", data: [] },
{ label: "Coils12 Delay", data: [] },
{ label: "Coils34 Delay", data: [] }
]
};
let dataB = {
labels: [],
datasets: [
{ label: "RPM", data: [] },
{ label: "Coils12 Delay", data: [] },
{ label: "Coils34 Delay", data: [] }
]
};
function setLoadingIndicator(visible) {
if (!loadingIndicator) {
return;
}
loadingIndicator.classList.toggle("hidden", !visible);
}
function updateLoadingState() {
const isConnected = ws && ws.readyState === WebSocket.OPEN;
const idle = Date.now() - lastMessageTimestamp >= IDLE_THRESHOLD_MS;
setLoadingIndicator(isConnected && idle);
}
function connectWS() {
ws = new WebSocket("ws://" + location.host + "/ws");
ws.onopen = () => {
console.log("WebSocket connesso");
lastMessageTimestamp = Date.now();
setLoadingIndicator(false);
ws.send(JSON.stringify({
cmd: "setTime",
time: Math.floor(Date.now() / 1000)
}));
};
ws.onclose = () => {
console.log("WebSocket disconnesso, retry...");
setLoadingIndicator(false);
setTimeout(connectWS, 5000);
};
ws.onmessage = (event) => {
let data;
try {
data = JSON.parse(event.data);
} catch (e) {
console.error("Invalid JSON received", e);
return;
}
lastMessageTimestamp = Date.now();
setLoadingIndicator(false);
updateCharts(data)
// Update Box_A
if (data.box_a) {
const boxA = data.box_a;
document.getElementById("a_datavalid").textContent = boxA.datavalid ?? "-";
document.getElementById("a_timestamp").textContent = boxA.timestamp ?? "-";
document.getElementById("a_eng_rpm").textContent = boxA.engRpm ?? "-";
document.getElementById("a_adc_read_time").textContent = boxA.adcReadTime ?? "-";
document.getElementById("a_n_queue_errors").textContent = boxA.nQueueErrors ?? "-";
const coils12A = boxA.coils12 || {};
const coils34A = boxA.coils34 || {};
document.getElementById("a_coils12_spark_delay").textContent = coils12A.sparkDelay ?? "-";
document.getElementById("a_coils34_spark_delay").textContent = coils34A.sparkDelay ?? "-";
document.getElementById("a_coils12_spark_status").textContent = coils12A.sparkStatus ?? "-";
document.getElementById("a_coils34_spark_status").textContent = coils34A.sparkStatus ?? "-";
document.getElementById("a_coils12_sstart_status").textContent = coils12A.softStartStatus ?? "-";
document.getElementById("a_coils34_sstart_status").textContent = coils34A.softStartStatus ?? "-";
document.getElementById("a_coils12_peak_p_in").textContent = coils12A.peakPos ?? "-";
document.getElementById("a_coils34_peak_p_in").textContent = coils34A.peakPos ?? "-";
document.getElementById("a_coils12_peak_n_in").textContent = coils12A.peakNeg ?? "-";
document.getElementById("a_coils34_peak_n_in").textContent = coils34A.peakNeg ?? "-";
document.getElementById("a_coils12_peak_p_out").textContent = coils12A.trigLevelPos ?? "-";
document.getElementById("a_coils34_peak_p_out").textContent = coils34A.trigLevelPos ?? "-";
document.getElementById("a_coils12_peak_n_out").textContent = coils12A.trigLevelNeg ?? "-";
document.getElementById("a_coils34_peak_n_out").textContent = coils34A.trigLevelNeg ?? "-";
document.getElementById("a_coils12_n_events").textContent = coils12A.nEvents ?? "-";
document.getElementById("a_coils34_n_events").textContent = coils34A.nEvents ?? "-";
document.getElementById("a_coils12_n_missed_firing").textContent = coils12A.nMissedFiring ?? "-";
document.getElementById("a_coils34_n_missed_firing").textContent = coils34A.nMissedFiring ?? "-";
}
// Update Box_B
if (data.box_b) {
const boxB = data.box_b;
document.getElementById("b_datavalid").textContent = boxB.datavalid ?? "-";
document.getElementById("b_timestamp").textContent = boxB.timestamp ?? "-";
document.getElementById("b_eng_rpm").textContent = boxB.engRpm ?? "-";
document.getElementById("b_adc_read_time").textContent = boxB.adcReadTime ?? "-";
document.getElementById("b_n_queue_errors").textContent = boxB.nQueueErrors ?? "-";
const coils12B = boxB.coils12 || {};
const coils34B = boxB.coils34 || {};
document.getElementById("a_coils12_spark_delay").textContent = coils12B.sparkDelay ?? "-";
document.getElementById("a_coils34_spark_delay").textContent = coils34B.sparkDelay ?? "-";
document.getElementById("a_coils12_spark_status").textContent = coils12B.sparkStatus ?? "-";
document.getElementById("a_coils34_spark_status").textContent = coils34B.sparkStatus ?? "-";
document.getElementById("a_coils12_sstart_status").textContent = coils12B.softStartStatus ?? "-";
document.getElementById("a_coils34_sstart_status").textContent = coils34B.softStartStatus ?? "-";
document.getElementById("a_coils12_peak_p_in").textContent = coils12B.peakPos ?? "-";
document.getElementById("a_coils34_peak_p_in").textContent = coils34B.peakPos ?? "-";
document.getElementById("a_coils12_peak_n_in").textContent = coils12B.peakNeg ?? "-";
document.getElementById("a_coils34_peak_n_in").textContent = coils34B.peakNeg ?? "-";
document.getElementById("a_coils12_peak_p_out").textContent = coils12B.trigLevelPos ?? "-";
document.getElementById("a_coils34_peak_p_out").textContent = coils34B.trigLevelPos ?? "-";
document.getElementById("a_coils12_peak_n_out").textContent = coils12B.trigLevelNeg ?? "-";
document.getElementById("a_coils34_peak_n_out").textContent = coils34B.trigLevelNeg ?? "-";
document.getElementById("a_coils12_n_events").textContent = coils12B.nEvents ?? "-";
document.getElementById("a_coils34_n_events").textContent = coils34B.nEvents ?? "-";
document.getElementById("a_coils12_n_missed_firing").textContent = coils12B.nMissedFiring ?? "-";
document.getElementById("a_coils34_n_missed_firing").textContent = coils34B.nMissedFiring ?? "-";
}
};
}
function updateCharts(data) {
const t = new Date().toLocaleTimeString();
// ===== BOX A =====
dataA.labels.push(t);
if (data.box_a) {
dataA.datasets[0].data.push(data.box_a.eng_rpm / 10);
dataA.datasets[1].data.push(data.box_a.coils12.spark_delay);
dataA.datasets[2].data.push(data.box_a.coils34.spark_delay);
} else {
dataA.datasets[0].data.push(undefined);
dataA.datasets[1].data.push(undefined);
dataA.datasets[2].data.push(undefined);
}
// ===== BOX B =====
dataB.labels.push(t);
if (data.box_b) {
dataB.datasets[0].data.push(data.box_b.eng_rpm / 10);
dataB.datasets[1].data.push(data.box_b.coils12.spark_delay);
dataB.datasets[2].data.push(data.box_b.coils34.spark_delay);
} else {
dataB.datasets[0].data.push(undefined);
dataB.datasets[1].data.push(undefined);
dataB.datasets[2].data.push(undefined);
}
// limite buffer
const maxPoints = 100;
if (dataA.labels.length > maxPoints) {
dataA.labels.shift();
dataA.datasets.forEach(d => d.data.shift());
}
if (dataB.labels.length > maxPoints) {
dataB.labels.shift();
dataB.datasets.forEach(d => d.data.shift());
}
chartA.update();
chartB.update();
}
function start() {
fetch("/start");
}
function stop() {
fetch("/stop");
}
function uploadLittleFS() {
const fileInput = document.getElementById("littlefsFile");
const status = document.getElementById("uploadStatus");
if (!fileInput || fileInput.files.length === 0) {
if (status) status.textContent = "Select a file first.";
return;
}
const file = fileInput.files[0];
const formData = new FormData();
formData.append("file", file, file.name);
if (status) status.textContent = "Uploading...";
fetch("/upload", {
method: "POST",
body: formData,
})
.then((resp) => {
if (!resp.ok) {
throw new Error("Upload failed: " + resp.status + " " + resp.statusText);
}
return resp.text();
})
.then(() => {
if (status) status.textContent = "Uploaded: " + file.name;
fileInput.value = "";
})
.catch((err) => {
if (status) status.textContent = err.message;
});
}
function openTab(tabId) {
document.querySelectorAll('.tab-content').forEach(tab => {
tab.classList.remove('active');
});
document.querySelectorAll('.tab-button').forEach(btn => {
btn.classList.remove('active');
});
document.getElementById(tabId).classList.add('active');
event.target.classList.add('active');
}
function initCharts() {
const ctxA = document.getElementById('chartA').getContext('2d');
const ctxB = document.getElementById('chartB').getContext('2d');
chartA = new Chart(ctxA, {
type: 'line',
data: dataA,
options: {
animation: false,
responsive: true,
scales: {
x: {
display: true
},
y: {
beginAtZero: true
}
}
}
});
chartB = new Chart(ctxB, {
type: 'line',
data: dataB,
options: {
animation: false,
responsive: true,
scales: {
x: {
display: true
},
y: {
beginAtZero: true
}
}
}
});
}
window.onload = () => {
initCharts();
};
setInterval(updateLoadingState, 200);
connectWS();

259
RotaxMonitor/data/style.css Normal file
View File

@@ -0,0 +1,259 @@
:root {
--primary-dark: #0a1929;
--primary-blue: #003585;
--accent-blue: #1e88e5;
--light-bg: #f5f7fa;
--border-color: #d0d6dd;
--text-dark: #1a1a1a;
--text-muted: #666666;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
margin: 0;
padding: 0;
background-color: var(--light-bg);
color: var(--text-dark);
}
.page-header {
background: linear-gradient(135deg, var(--primary-dark) 0%, #1a3a52 100%);
color: white;
padding: 30px 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
margin-bottom: 30px;
}
.header-content {
max-width: 900px;
margin: 0 auto;
display: flex;
align-items: center;
gap: 20px;
}
.logo {
height: 50px;
width: auto;
margin: auto;
}
.page-header h1 {
margin: auto;
margin-top: 20px;
text-align: center;
font-size: 28px;
font-weight: 600;
}
table {
margin: auto;
border-collapse: collapse;
width: 100%;
max-width: 900px;
background: white;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
border-radius: 6px;
overflow: hidden;
}
th, td {
border: 1px solid var(--border-color);
padding: 12px;
font-size: 14px;
text-align: center;
}
th {
background-color: var(--primary-blue);
color: white;
font-weight: 600;
}
tr:hover {
background-color: #f9fbfc;
}
button {
margin: 10px;
padding: 10px 20px;
font-size: 16px;
background-color: var(--primary-blue);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s;
}
button:hover {
background-color: var(--accent-blue);
}
.upload-section {
margin: 30px auto 20px;
max-width: 900px;
text-align: left;
padding: 20px;
border: 1px solid var(--border-color);
border-radius: 6px;
background: white;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}
.upload-section h3 {
margin-top: 0;
margin-bottom: 8px;
color: var(--primary-blue);
font-size: 16px;
}
.upload-section p {
margin: 8px 0;
color: var(--text-muted);
font-size: 14px;
}
.upload-section input[type="file"] {
margin-top: 8px;
margin-bottom: 12px;
}
.upload-status {
margin-top: 10px;
font-size: 14px;
color: var(--text-muted);
}
.loading-indicator {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
margin: 0;
padding: 16px 20px;
font-size: 20px;
color: var(--primary-blue);
border-bottom: 1px solid var(--border-color);
background: white;
width: 100%;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}
.loading-indicator.hidden {
display: none;
}
.spinner {
width: 16px;
height: 16px;
border: 2px solid transparent;
border-top-color: var(--primary-blue);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.tables-container {
display: flex;
gap: 20px;
max-width: 1800px;
margin: 0 auto;
padding: 0 20px;
}
.box {
flex: 1;
background: white;
padding: 20px;
border-radius: 6px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}
.box h2 {
margin-top: 0;
margin-bottom: 16px;
color: var(--primary-blue);
font-size: 18px;
font-weight: 700;
text-align: center;
}
.box-data {
margin-bottom: 20px;
}
.box-data p {
margin: 8px 0;
font-size: 14px;
}
.box-data strong {
color: var(--primary-blue);
}
.rpm-highlight {
background: #c6e4fa;
border: 3px double var(--primary-blue);
border-radius: 8px;
padding: 12px 16px;
margin-bottom: 20px;
text-align: center;
font-size: 18px;
font-weight: bold;
color: var(--text-dark);
}
.rpm-highlight strong {
color: var(--primary-blue);
}
span {
color: var(--text-dark);
}
/* TABS */
.tabs {
display: flex;
justify-content: center;
margin: 20px;
}
.tab-button {
padding: 10px 20px;
margin: 0 5px;
border: none;
cursor: pointer;
background: var(--border-color);
border-radius: 4px;
}
.tab-button.active {
background: var(--primary-blue);
color: white;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.chart-container {
max-width: 1000px;
margin: 20px auto;
background: white;
padding: 20px;
border-radius: 6px;
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,815 @@
// ADS1256 cpp file
/*
Name: ADS1256.cpp
Created: 2022/07/14
Author: Curious Scientist
Editor: Notepad++
Comment: Visit https://curiousscientist.tech/blog/ADS1256-custom-library
Special thanks to:
Abraão Queiroz for spending time on the code and suggesting corrections for ESP32 microcontrollers
Benjamin Pelletier for pointing out and fixing an issue around the handling of the DRDY signal
RadoMmm for suggesting an improvement on the ADC-to-Volts conversion
*/
#define DEBUGLOG_DEFAULT_LOG_LEVEL_DEBUG
#include "Arduino.h"
#include "ADS1256.h"
#include "SPI.h"
#include <DebugLog.h>
#define convertSigned24BitToLong(value) ((value) & (1l << 23) ? (value) - 0x1000000 : value)
void IRAM_ATTR drdyCallback(void *arg)
{
auto cls = (ADS1256 *)arg;
if (!arg)
return;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if (digitalRead(cls->getDRDYpin())) // impose wait on low
{
xSemaphoreTakeFromISR(cls->getDRDYsemaphoreLow(), &xHigherPriorityTaskWoken);
xSemaphoreGiveFromISR(cls->getDRDYsemaphoreHigh(), &xHigherPriorityTaskWoken);
}
else // impose wait on high
{
xSemaphoreTakeFromISR(cls->getDRDYsemaphoreHigh(), &xHigherPriorityTaskWoken);
xSemaphoreGiveFromISR(cls->getDRDYsemaphoreLow(), &xHigherPriorityTaskWoken);
}
if (xHigherPriorityTaskWoken)
portYIELD_FROM_ISR();
}
// Constructor
ADS1256::ADS1256(const int8_t DRDY_pin, const int8_t RESET_pin, const int8_t SYNC_pin, const int8_t CS_pin, float VREF, SPIClass *spi) : _spi(spi),
m_DRDY_pin(DRDY_pin), m_RESET_pin(RESET_pin), m_SYNC_pin(SYNC_pin), m_CS_pin(CS_pin), m_VREF(VREF), m_PGA(0)
{
pinMode(m_DRDY_pin, INPUT);
if (RESET_pin != PIN_UNUSED)
{
pinMode(m_RESET_pin, OUTPUT);
}
if (SYNC_pin != PIN_UNUSED)
{
pinMode(m_SYNC_pin, OUTPUT);
}
if (CS_pin != PIN_UNUSED)
{
pinMode(m_CS_pin, OUTPUT);
}
updateConversionParameter();
m_drdyHigh = xSemaphoreCreateBinary();
m_drdyLow = xSemaphoreCreateBinary();
if (!m_drdyHigh || !m_drdyLow)
{
LOG_ERROR("ADC Unable to create interrupt semaphores");
return;
}
xSemaphoreGive(m_drdyHigh);
xSemaphoreGive(m_drdyLow);
attachInterruptArg(DRDY_pin, drdyCallback, (void *)this, CHANGE);
disableInterrupt(DRDY_pin);
}
// Initialization
void ADS1256::InitializeADC()
{
// Chip select LOW
CS_LOW();
// We do a manual chip reset on the ADS1256 - Datasheet Page 27/ RESET
if (m_RESET_pin != PIN_UNUSED)
{
digitalWrite(m_RESET_pin, LOW);
delayMicroseconds(500);
digitalWrite(m_RESET_pin, HIGH); // RESET is set to high
delay(1000);
}
// Sync pin is also treated if it is defined
if (m_SYNC_pin != PIN_UNUSED)
{
digitalWrite(m_SYNC_pin, HIGH); // RESET is set to high
}
// Applying arbitrary default values to speed up the starting procedure if the user just want to get quick readouts
// We both pass values to the variables and then send those values to the corresponding registers
delayMicroseconds(500);
m_STATUS = 0b00110110; // BUFEN and ACAL enabled, Order is MSB, rest is read only
writeRegister(STATUS_REG, m_STATUS);
delayMicroseconds(500);
m_MUX = DIFF_0_1; // MUX AIN0+AIN1
writeRegister(MUX_REG, m_MUX);
delayMicroseconds(500);
m_ADCON = WAKEUP; // ADCON - CLK: OFF, SDCS: OFF, PGA = 0 (+/- 5 V)
writeRegister(ADCON_REG, m_ADCON);
delayMicroseconds(500);
updateConversionParameter();
m_DRATE = DRATE_100SPS; // 100SPS
writeRegister(DRATE_REG, m_DRATE);
delayMicroseconds(500);
sendDirectCommand(SELFCAL); // Offset and self-gain calibration
delayMicroseconds(500);
m_isAcquisitionRunning = false; // MCU will be waiting to start a continuous acquisition
}
void ADS1256::waitForLowDRDY()
{
if (!m_isAcquisitionRunning)
while (digitalRead(m_DRDY_pin) == HIGH)
; // wait in loop only for single shot modes
xSemaphoreTake(m_drdyLow, pdMS_TO_TICKS(10));
xSemaphoreGive(m_drdyLow);
}
void ADS1256::waitForHighDRDY()
{
if (!m_isAcquisitionRunning)
while (digitalRead(m_DRDY_pin) == LOW)
; // wait in loop only for single shot modes
xSemaphoreTake(m_drdyHigh, pdMS_TO_TICKS(10));
xSemaphoreGive(m_drdyHigh);
}
void ADS1256::stopConversion() // Sending SDATAC to stop the continuous conversion
{
waitForLowDRDY(); // SDATAC should be called after DRDY goes LOW (p35. Figure 33)
_spi->transfer(SDATAC); // Send SDATAC to the ADC
CS_HIGH(); // We finished the command sequence, so we switch it back to HIGH
_spi->endTransaction();
m_isAcquisitionRunning = false; // Reset to false, so the MCU will be able to start a new conversion
disableDRDYinterrupt();
}
void ADS1256::setDRATE(uint8_t drate) // Setting DRATE (sampling frequency)
{
writeRegister(DRATE_REG, drate);
m_DRATE = drate;
delayMicroseconds(500);
}
void ADS1256::setMUX(uint8_t mux) // Setting MUX (input channel)
{
writeRegister(MUX_REG, mux);
m_MUX = mux;
delayMicroseconds(500);
}
void ADS1256::setPGA(uint8_t pga) // Setting PGA (input voltage range)
{
m_PGA = pga;
m_ADCON = readRegister(ADCON_REG); // Read the most recent value of the register
m_ADCON = (m_ADCON & 0b11111000) | (m_PGA & 0b00000111); // Clearing and then setting bits 2-0 based on pga
writeRegister(ADCON_REG, m_ADCON);
delayMicroseconds(500);
updateConversionParameter(); // Update the multiplier according top the new PGA value
}
uint8_t ADS1256::getPGA() // Reading PGA from the ADCON register
{
uint8_t pgaValue = readRegister(ADCON_REG) & 0b00000111;
// Reading the ADCON_REG and keeping the first three bits.
return (pgaValue);
}
void ADS1256::setCLKOUT(uint8_t clkout) // Setting CLKOUT
{
m_ADCON = readRegister(ADCON_REG); // Read the most recent value of the register
// Values: 0, 1, 2, 3
if (clkout == 0)
{
// 00
bitWrite(m_ADCON, 6, 0);
bitWrite(m_ADCON, 5, 0);
}
else if (clkout == 1)
{
// 01 (default)
bitWrite(m_ADCON, 6, 0);
bitWrite(m_ADCON, 5, 1);
}
else if (clkout == 2)
{
// 10
bitWrite(m_ADCON, 6, 1);
bitWrite(m_ADCON, 5, 0);
}
else if (clkout == 3)
{
// 11
bitWrite(m_ADCON, 6, 1);
bitWrite(m_ADCON, 5, 1);
}
else
{
}
writeRegister(ADCON_REG, m_ADCON);
}
void ADS1256::setSDCS(uint8_t sdcs) // Setting SDCS
{
m_ADCON = readRegister(ADCON_REG); // Read the most recent value of the register
// Values: 0, 1, 2, 3
if (sdcs == 0)
{
// 00 (default)
bitWrite(m_ADCON, 4, 0);
bitWrite(m_ADCON, 3, 0);
}
else if (sdcs == 1)
{
// 01
bitWrite(m_ADCON, 4, 0);
bitWrite(m_ADCON, 3, 1);
}
else if (sdcs == 2)
{
// 10
bitWrite(m_ADCON, 4, 1);
bitWrite(m_ADCON, 3, 0);
}
else if (sdcs == 3)
{
// 11
bitWrite(m_ADCON, 4, 1);
bitWrite(m_ADCON, 3, 1);
}
else
{
}
writeRegister(ADCON_REG, m_ADCON);
}
void ADS1256::setByteOrder(uint8_t byteOrder) // Setting byte order (MSB/LSB)
{
m_STATUS = readRegister(STATUS_REG); // Read the most recent value of the register
if (byteOrder == 0)
{
// Byte order is MSB (default)
bitWrite(m_STATUS, 3, 0);
// Set value of _STATUS at the third bit to 0
}
else if (byteOrder == 1)
{
// Byte order is LSB
bitWrite(m_STATUS, 3, 1);
// Set value of _STATUS at the third bit to 1
}
else
{
}
writeRegister(STATUS_REG, m_STATUS);
}
uint8_t ADS1256::getByteOrder() // Getting byte order (MSB/LSB)
{
uint8_t statusValue = readRegister(STATUS_REG); // Read the whole STATUS register
return bitRead(statusValue, 3);
}
void ADS1256::setAutoCal(uint8_t acal) // Setting ACAL (Automatic SYSCAL)
{
m_STATUS = readRegister(STATUS_REG); // Read the most recent value of the register
if (acal == 0)
{
// Auto-calibration is disabled (default)
bitWrite(m_STATUS, 2, 0);
//_STATUS |= B00000000;
}
else if (acal == 1)
{
// Auto-calibration is enabled
bitWrite(m_STATUS, 2, 1);
//_STATUS |= B00000100;
}
else
{
}
writeRegister(STATUS_REG, m_STATUS);
}
uint8_t ADS1256::getAutoCal() // Getting ACAL (Automatic SYSCAL)
{
uint8_t statusValue = readRegister(STATUS_REG); // Read the whole STATUS register
return bitRead(statusValue, 2);
}
void ADS1256::setBuffer(uint8_t bufen) // Setting input buffer (Input impedance)
{
m_STATUS = readRegister(STATUS_REG); // Read the most recent value of the register
if (bufen == 0)
{
// Analog input buffer is disabled (default)
//_STATUS |= B00000000;
bitWrite(m_STATUS, 1, 0);
}
else if (bufen == 1)
{
// Analog input buffer is enabled (recommended)
//_STATUS |= B00000010;
bitWrite(m_STATUS, 1, 1);
}
else
{
}
writeRegister(STATUS_REG, m_STATUS);
}
uint8_t ADS1256::getBuffer() // Getting input buffer (Input impedance)
{
uint8_t statusValue = readRegister(STATUS_REG); // Read the whole STATUS register
return bitRead(statusValue, 1);
}
void ADS1256::setGPIO(uint8_t dir0, uint8_t dir1, uint8_t dir2, uint8_t dir3) // Setting GPIO
{
m_GPIO = readRegister(IO_REG); // Read the most recent value of the register
// Default: 11100000 - DEC: 224 - Ref: p32 I/O section
// Sets D3-D0 as input or output
uint8_t GPIO_bit7, GPIO_bit6, GPIO_bit5, GPIO_bit4;
// Bit7: DIR3
if (dir3 == 1)
{
GPIO_bit7 = 1; // D3 is input (default)
}
else
{
GPIO_bit7 = 0; // D3 is output
}
bitWrite(m_GPIO, 7, GPIO_bit7);
//-----------------------------------------------------
// Bit6: DIR2
if (dir2 == 1)
{
GPIO_bit6 = 1; // D2 is input (default)
}
else
{
GPIO_bit6 = 0; // D2 is output
}
bitWrite(m_GPIO, 6, GPIO_bit6);
//-----------------------------------------------------
// Bit5: DIR1
if (dir1 == 1)
{
GPIO_bit5 = 1; // D1 is input (default)
}
else
{
GPIO_bit5 = 0; // D1 is output
}
bitWrite(m_GPIO, 5, GPIO_bit5);
//-----------------------------------------------------
// Bit4: DIR0
if (dir0 == 1)
{
GPIO_bit4 = 1; // D0 is input
}
else
{
GPIO_bit4 = 0; // D0 is output (default)
}
bitWrite(m_GPIO, 4, GPIO_bit4);
//-----------------------------------------------------
writeRegister(IO_REG, m_GPIO);
}
void ADS1256::writeGPIO(uint8_t dir0value, uint8_t dir1value, uint8_t dir2value, uint8_t dir3value) // Writing GPIO
{
m_GPIO = readRegister(IO_REG);
// Sets D3-D0 output values
// It is important that first one must use setGPIO, then writeGPIO
uint8_t GPIO_bit3, GPIO_bit2, GPIO_bit1, GPIO_bit0;
// Bit3: DIR3
if (dir3value == 1)
{
GPIO_bit3 = 1;
}
else
{
GPIO_bit3 = 0;
}
bitWrite(m_GPIO, 3, GPIO_bit3);
//-----------------------------------------------------
// Bit2: DIR2
if (dir2value == 1)
{
GPIO_bit2 = 1;
}
else
{
GPIO_bit2 = 0;
}
bitWrite(m_GPIO, 2, GPIO_bit2);
//-----------------------------------------------------
// Bit1: DIR1
if (dir1value == 1)
{
GPIO_bit1 = 1;
}
else
{
GPIO_bit1 = 0;
}
bitWrite(m_GPIO, 1, GPIO_bit1);
//-----------------------------------------------------
// Bit0: DIR0
if (dir0value == 1)
{
GPIO_bit0 = 1;
}
else
{
GPIO_bit0 = 0;
}
bitWrite(m_GPIO, 0, GPIO_bit0);
//-----------------------------------------------------
writeRegister(IO_REG, m_GPIO);
}
uint8_t ADS1256::readGPIO(uint8_t gpioPin) // Reading GPIO
{
uint8_t GPIO_bit3, GPIO_bit2, GPIO_bit1, GPIO_bit0, GPIO_return;
m_GPIO = readRegister(IO_REG); // Read the GPIO register
// Save each bit values in a variable
GPIO_bit3 = bitRead(m_GPIO, 3);
GPIO_bit2 = bitRead(m_GPIO, 2);
GPIO_bit1 = bitRead(m_GPIO, 1);
GPIO_bit0 = bitRead(m_GPIO, 0);
switch (gpioPin) // Selecting which value should be returned
{
case 0:
GPIO_return = GPIO_bit0;
break;
case 1:
GPIO_return = GPIO_bit1;
break;
case 2:
GPIO_return = GPIO_bit2;
break;
case 3:
GPIO_return = GPIO_bit3;
break;
}
return GPIO_return;
}
void ADS1256::sendDirectCommand(uint8_t directCommand)
{
// Direct commands can be found in the datasheet Page 34, Table 24.
_spi->beginTransaction(SPISettings(SPI_FREQ, MSBFIRST, SPI_MODE1));
CS_LOW(); // REF: P34: "CS must stay low during the entire command sequence"
delayMicroseconds(5);
_spi->transfer(directCommand); // Send Command
delayMicroseconds(5);
CS_HIGH(); // REF: P34: "CS must stay low during the entire command sequence"
_spi->endTransaction();
}
float ADS1256::convertToVoltage(int32_t rawData) // Converting the 24-bit data into a voltage value
{
return (m_conversionParameter * rawData);
}
void ADS1256::writeRegister(uint8_t registerAddress, uint8_t registerValueToWrite)
{
waitForLowDRDY();
_spi->beginTransaction(SPISettings(SPI_FREQ, MSBFIRST, SPI_MODE1)); // SPI_MODE1 = output edge: rising, data capture: falling; clock polarity: 0, clock phase: 1.
CS_LOW(); // CS must stay LOW during the entire sequence [Ref: P34, T24]
delayMicroseconds(5); // see t6 in the datasheet
_spi->transfer(WREG | registerAddress); // 0x50 = 01010000 = WREG
_spi->transfer(0x00); // 2nd (empty) command byte
_spi->transfer(registerValueToWrite); // pass the value to the register
CS_HIGH();
_spi->endTransaction();
}
long ADS1256::readRegister(uint8_t registerAddress) // Reading a register
{
waitForLowDRDY();
_spi->beginTransaction(SPISettings(SPI_FREQ, MSBFIRST, SPI_MODE1)); // SPI_MODE1 = output edge: rising, data capture: falling; clock polarity: 0, clock phase: 1.
CS_LOW(); // CS must stay LOW during the entire sequence [Ref: P34, T24]
_spi->transfer(RREG | registerAddress); // 0x10 = 0001000 = RREG - OR together the two numbers (command + address)
_spi->transfer(0x00); // 2nd (empty) command byte
delayMicroseconds(5); // see t6 in the datasheet
uint8_t regValue = _spi->transfer(0x00); // read out the register value
CS_HIGH();
_spi->endTransaction();
return regValue;
}
long ADS1256::readSingle() // Reading a single value ONCE using the RDATA command
{
_spi->beginTransaction(SPISettings(SPI_FREQ, MSBFIRST, SPI_MODE1));
CS_LOW(); // REF: P34: "CS must stay low during the entire command sequence"
waitForLowDRDY();
_spi->transfer(RDATA); // Issue RDATA (0000 0001) command
delayMicroseconds(7); // Wait t6 time (~6.51 us) REF: P34, FIG:30.
m_outputBuffer[0] = _spi->transfer(0); // MSB
m_outputBuffer[1] = _spi->transfer(0); // Mid-byte
m_outputBuffer[2] = _spi->transfer(0); // LSB
// Shifting and combining the above three items into a single, 24-bit number
m_outputValue = ((long)m_outputBuffer[0] << 16) | ((long)m_outputBuffer[1] << 8) | (m_outputBuffer[2]);
m_outputValue = convertSigned24BitToLong(m_outputValue);
CS_HIGH(); // We finished the command sequence, so we set CS to HIGH
_spi->endTransaction();
return (m_outputValue);
}
long ADS1256::readSingleContinuous() // Reads the recently selected input channel using RDATAC
{
if (m_isAcquisitionRunning == false)
{
enableDRDYinterrupt();
m_isAcquisitionRunning = true;
_spi->beginTransaction(SPISettings(SPI_FREQ, MSBFIRST, SPI_MODE1));
CS_LOW(); // REF: P34: "CS must stay low during the entire command sequence"
waitForLowDRDY();
_spi->transfer(RDATAC); // Issue RDATAC (0000 0011)
delayMicroseconds(7); // Wait t6 time (~6.51 us) REF: P34, FIG:30.
}
else
{
waitForLowDRDY();
}
m_outputBuffer[0] = _spi->transfer(0); // MSB
m_outputBuffer[1] = _spi->transfer(0); // Mid-byte
m_outputBuffer[2] = _spi->transfer(0); // LSB
m_outputValue = ((long)m_outputBuffer[0] << 16) | ((long)m_outputBuffer[1] << 8) | (m_outputBuffer[2]);
m_outputValue = convertSigned24BitToLong(m_outputValue);
waitForHighDRDY();
return m_outputValue;
}
long ADS1256::cycleSingle()
{
if (m_isAcquisitionRunning == false)
{
enableDRDYinterrupt();
m_isAcquisitionRunning = true;
m_cycle = 0;
_spi->beginTransaction(SPISettings(SPI_FREQ, MSBFIRST, SPI_MODE1));
CS_LOW(); // CS must stay LOW during the entire sequence [Ref: P34, T24]
_spi->transfer(WREG | MUX_REG); // 0x50 = WREG //1 = MUX
_spi->transfer(0x00);
_spi->transfer(SING_0); // AIN0+AINCOM
delayMicroseconds(250);
}
if (m_cycle < 8)
{
m_outputValue = 0;
waitForLowDRDY();
// Step 1. - Updating MUX
switch (m_cycle)
{
// Channels are written manually
case 0: // Channel 2
updateMUX(SING_1); // AIN1+AINCOM
break;
case 1: // Channel 3
updateMUX(SING_2); // AIN2+AINCOM
break;
case 2: // Channel 4
updateMUX(SING_3); // AIN3+AINCOM
break;
case 3: // Channel 5
updateMUX(SING_4); // AIN4+AINCOM
break;
case 4: // Channel 6
updateMUX(SING_5); // AIN5+AINCOM
break;
case 5: // Channel 7
updateMUX(SING_6); // AIN6+AINCOM
break;
case 6: // Channel 8
updateMUX(SING_7); // AIN7+AINCOM
break;
case 7: // Channel 1
updateMUX(SING_0); // AIN0+AINCOM
break;
}
// Step 2.
_spi->transfer(SYNC); // SYNC
delayMicroseconds(4); // t11 delay 24*tau = 3.125 us //delay should be larger, so we delay by 4 us
_spi->transfer(WAKEUP); // WAKEUP
// Step 3.
// Issue RDATA (0000 0001) command
_spi->transfer(RDATA);
delayMicroseconds(7); // Wait t6 time (~6.51 us) REF: P34, FIG:30.
m_outputBuffer[0] = _spi->transfer(0); // MSB
m_outputBuffer[1] = _spi->transfer(0); // Mid-byte
m_outputBuffer[2] = _spi->transfer(0); // LSB
m_outputValue = ((long)m_outputBuffer[0] << 16) | ((long)m_outputBuffer[1] << 8) | (m_outputBuffer[2]);
m_outputValue = convertSigned24BitToLong(m_outputValue);
m_cycle++; // Increase cycle - This will move to the next MUX input channel
if (m_cycle == 8)
{
m_cycle = 0; // Reset to 0 - Restart conversion from the 1st input channel
}
}
return m_outputValue;
}
long ADS1256::cycleDifferential()
{
if (m_isAcquisitionRunning == false)
{
enableDRDYinterrupt();
m_cycle = 0;
m_isAcquisitionRunning = true;
_spi->beginTransaction(SPISettings(SPI_FREQ, MSBFIRST, SPI_MODE1));
CS_LOW(); // CS must stay LOW during the entire sequence [Ref: P34, T24]
_spi->transfer(WREG | MUX_REG); // 0x50 = WREG //1 = MUX
_spi->transfer(0x00);
_spi->transfer(DIFF_0_1); // Set the AIN0+AIN1 as inputs manually
delayMicroseconds(250);
}
if (m_cycle < 4)
{
m_outputValue = 0;
// DRDY has to go low
waitForLowDRDY();
// Step 1. - Updating MUX
switch (m_cycle)
{
case 0: // Channel 2
updateMUX(DIFF_2_3); // AIN2+AIN3
break;
case 1: // Channel 3
updateMUX(DIFF_4_5); // AIN4+AIN5
break;
case 2: // Channel 4
updateMUX(DIFF_6_7); // AIN6+AIN7
break;
case 3: // Channel 1
updateMUX(DIFF_0_1); // AIN0+AIN1
break;
}
_spi->transfer(SYNC); // SYNC
delayMicroseconds(4); // t11 delay 24*tau = 3.125 us //delay should be larger, so we delay by 4 us
_spi->transfer(WAKEUP); // WAKEUP
// Step 3.
_spi->transfer(RDATA); // Issue RDATA (0000 0001) command
delayMicroseconds(7); // Wait t6 time (~6.51 us) REF: P34, FIG:30.
m_outputBuffer[0] = _spi->transfer(0); // MSB
m_outputBuffer[1] = _spi->transfer(0); // Mid-byte
m_outputBuffer[2] = _spi->transfer(0); // LSB
m_outputValue = ((long)m_outputBuffer[0] << 16) | ((long)m_outputBuffer[1] << 8) | (m_outputBuffer[2]);
m_outputValue = convertSigned24BitToLong(m_outputValue);
m_cycle++;
if (m_cycle == 4)
{
m_cycle = 0;
// After the 4th cycle, we reset to zero so the next iteration reads the 1st MUX again
}
}
return m_outputValue;
}
void ADS1256::updateConversionParameter()
{
m_conversionParameter = ((2.0 * m_VREF) / 8388608.0) / (pow(2, m_PGA)); // Calculate the "bit to Volts" multiplier
// 8388608 = 2^{23} - 1, REF: p23, Table 16.
}
void ADS1256::updateMUX(uint8_t muxValue)
{
_spi->transfer(WREG | MUX_REG); // Write to the MUX register (0x50 is the WREG command)
_spi->transfer(0x00);
_spi->transfer(muxValue); // Write the new MUX value
}
inline void ADS1256::CS_LOW()
{
if (m_CS_pin != PIN_UNUSED) // Sets CS LOW if it is not an unused pin
{
digitalWrite(m_CS_pin, LOW);
}
}
inline void ADS1256::CS_HIGH()
{
if (m_CS_pin != PIN_UNUSED) // Sets CS HIGH if it is not an unused pin
{
digitalWrite(m_CS_pin, HIGH);
}
}
// functions for callback
inline uint8_t ADS1256::getDRDYpin()
{
return m_DRDY_pin;
}
inline SemaphoreHandle_t ADS1256::getDRDYsemaphoreHigh()
{
return m_drdyHigh;
}
inline SemaphoreHandle_t ADS1256::getDRDYsemaphoreLow()
{
return m_drdyLow;
}
inline void ADS1256::enableDRDYinterrupt()
{
// release semaphores to avoid deadlock
xSemaphoreGive(m_drdyHigh);
xSemaphoreGive(m_drdyLow);
enableInterrupt(m_DRDY_pin);
}
inline void ADS1256::disableDRDYinterrupt()
{
// release semaphores to avoid deadlock
disableInterrupt(m_DRDY_pin);
xSemaphoreGive(m_drdyHigh);
xSemaphoreGive(m_drdyLow);
}

View File

@@ -0,0 +1,204 @@
// ADS1256 header file
/*
Name: ADS1256.h
Created: 2022/07/14
Author: Curious Scientist
Editor: Notepad++
Comment: Visit https://curiousscientist.tech/blog/ADS1256-custom-library
Special thanks to
Abraão Queiroz for spending time on the code and suggesting corrections for ESP32 microcontrollers
Benjamin Pelletier for pointing out and fixing an issue around the handling of the DRDY signal
*/
#pragma once
#include <SPI.h>
#include <Arduino.h>
// SPI Frequency
#define SPI_FREQ 1920000
// Differential inputs
#define DIFF_0_1 0b00000001 // A0 + A1 as differential input
#define DIFF_2_3 0b00100011 // A2 + A3 as differential input
#define DIFF_4_5 0b01000101 // A4 + A5 as differential input
#define DIFF_6_7 0b01100111 // A6 + A7 as differential input
// Single-ended inputs
#define SING_0 0b00001111 // A0 + GND (common) as single-ended input
#define SING_1 0b00011111 // A1 + GND (common) as single-ended input
#define SING_2 0b00101111 // A2 + GND (common) as single-ended input
#define SING_3 0b00111111 // A3 + GND (common) as single-ended input
#define SING_4 0b01001111 // A4 + GND (common) as single-ended input
#define SING_5 0b01011111 // A5 + GND (common) as single-ended input
#define SING_6 0b01101111 // A6 + GND (common) as single-ended input
#define SING_7 0b01111111 // A7 + GND (common) as single-ended input
// PGA settings //Input voltage range
#define PGA_1 0b00000000 // ± 5 V
#define PGA_2 0b00000001 // ± 2.5 V
#define PGA_4 0b00000010 // ± 1.25 V
#define PGA_8 0b00000011 // ± 625 mV
#define PGA_16 0b00000100 // ± 312.5 mV
#define PGA_32 0b00000101 //+ 156.25 mV
#define PGA_64 0b00000110 // ± 78.125 mV
// Datarate //DEC
#define DRATE_30000SPS 0b11110000 // 240
#define DRATE_15000SPS 0b11100000 // 224
#define DRATE_7500SPS 0b11010000 // 208
#define DRATE_3750SPS 0b11000000 // 192
#define DRATE_2000SPS 0b10110000 // 176
#define DRATE_1000SPS 0b10100001 // 161
#define DRATE_500SPS 0b10010010 // 146
#define DRATE_100SPS 0b10000010 // 130
#define DRATE_60SPS 0b01110010 // 114
#define DRATE_50SPS 0b01100011 // 99
#define DRATE_30SPS 0b01010011 // 83
#define DRATE_25SPS 0b01000011 // 67
#define DRATE_15SPS 0b00110011 // 51
#define DRATE_10SPS 0b00100011 // 35
#define DRATE_5SPS 0b00010011 // 19
#define DRATE_2SPS 0b00000011 // 3
// Status register
#define BITORDER_MSB 0
#define BITORDER_LSB 1
#define ACAL_DISABLED 0
#define ACAL_ENABLED 1
#define BUFFER_DISABLED 0
#define BUFFER_ENABLED 1
// Register addresses
#define STATUS_REG 0x00
#define MUX_REG 0x01
#define ADCON_REG 0x02
#define DRATE_REG 0x03
#define IO_REG 0x04
#define OFC0_REG 0x05
#define OFC1_REG 0x06
#define OFC2_REG 0x07
#define FSC0_REG 0x08
#define FSC1_REG 0x09
#define FSC2_REG 0x0A
// Command definitions
#define WAKEUP 0b00000000
#define RDATA 0b00000001
#define RDATAC 0b00000011
#define SDATAC 0b00001111
#define RREG 0b00010000
#define WREG 0b01010000
#define SELFCAL 0b11110000
#define SELFOCAL 0b11110001
#define SELFGCAL 0b11110010
#define SYSOCAL 0b11110011
#define SYSGCAL 0b11110100
#define SYNC 0b11111100
#define STANDBY 0b11111101
#define RESET 0b11111110
//----------------------------------------------------------------
class ADS1256
{
public:
static constexpr int8_t PIN_UNUSED = -1;
// Constructor
ADS1256(const int8_t DRDY_pin, const int8_t RESET_pin, const int8_t SYNC_pin, const int8_t CS_pin, float VREF, SPIClass *spi = &SPI);
~ADS1256()
{
vSemaphoreDelete(m_drdyHigh);
vSemaphoreDelete(m_drdyLow);
}
// Initializing function
void InitializeADC();
// ADS1256(int drate, int pga, int byteOrder, bool bufen);
// Read a register
long readRegister(uint8_t registerAddress);
// Write a register
void writeRegister(uint8_t registerAddress, uint8_t registerValueToWrite);
// Individual methods
void setDRATE(uint8_t drate);
void setPGA(uint8_t pga);
uint8_t getPGA();
void setMUX(uint8_t mux);
void setByteOrder(uint8_t byteOrder);
uint8_t getByteOrder();
void setBuffer(uint8_t bufen);
uint8_t getBuffer();
void setAutoCal(uint8_t acal);
uint8_t getAutoCal();
void setGPIO(uint8_t dir0, uint8_t dir1, uint8_t dir2, uint8_t dir3);
void writeGPIO(uint8_t dir0value, uint8_t dir1value, uint8_t dir2value, uint8_t dir3value);
uint8_t readGPIO(uint8_t gpioPin);
void setCLKOUT(uint8_t clkout);
void setSDCS(uint8_t sdcs);
void sendDirectCommand(uint8_t directCommand);
// Get a single conversion
long readSingle();
// Single input continuous reading
long readSingleContinuous();
// Cycling through the single-ended inputs
long cycleSingle(); // Ax + COM
// Cycling through the differential inputs
long cycleDifferential(); // Ax + Ay
// Converts the reading into a voltage value
float convertToVoltage(int32_t rawData);
// Stop AD
void stopConversion();
// functions for callback, public to be accessed by static callback
inline uint8_t getDRDYpin();
inline SemaphoreHandle_t getDRDYsemaphoreHigh();
inline SemaphoreHandle_t getDRDYsemaphoreLow();
private:
SPIClass *_spi; // Pointer to an SPIClass object
void waitForLowDRDY(); // Block until DRDY is low
void waitForHighDRDY(); // Block until DRDY is high
void updateMUX(uint8_t muxValue);
inline void CS_LOW();
inline void CS_HIGH();
inline void enableDRDYinterrupt();
inline void disableDRDYinterrupt();
void updateConversionParameter(); // Refresh the conversion parameter based on the PGA
float m_VREF = 0; // Value of the reference voltage
float m_conversionParameter = 0; // PGA-dependent multiplier
// Pins
int8_t m_DRDY_pin; // Pin assigned for DRDY
int8_t m_RESET_pin; // Pin assigned for RESET
int8_t m_SYNC_pin; // Pin assigned for SYNC
int8_t m_CS_pin; // Pin assigned for CS
// Register values
uint8_t m_DRATE; // Value of the DRATE register
uint8_t m_ADCON; // Value of the ADCON register
uint8_t m_MUX; // Value of the MUX register
uint8_t m_PGA; // Value of the PGA (within ADCON)
uint8_t m_GPIO; // Value of the GPIO register
uint8_t m_STATUS; // Value of the status register
uint8_t m_GPIOvalue; // GPIO value
uint8_t m_ByteOrder; // Byte order
uint8_t m_outputBuffer[3]; // 3-byte (24-bit) buffer for the fast acquisition - Single-channel, continuous
int32_t m_outputValue; // Combined value of the m_outputBuffer[3]
bool m_isAcquisitionRunning; // bool that keeps track of the acquisition (running or not)
uint8_t m_cycle; // Tracks the cycles as the MUX is cycling through the input channels
SemaphoreHandle_t m_drdyHigh;
SemaphoreHandle_t m_drdyLow;
};

View File

@@ -0,0 +1,38 @@
#include <led.h>
RGBled::RGBled(const uint8_t pin) : m_led(pin)
{
pinMode(m_led, OUTPUT);
writeStatus(RGBled::ERROR);
m_brightness = 1.0f;
}
RGBled::~RGBled()
{
pinMode(m_led, INPUT);
}
void RGBled::setBrightness(const float b)
{
m_brightness = b;
}
void RGBled::setStatus(const LedStatus s)
{
if (m_status == s)
return;
std::lock_guard<std::mutex> lock(m_mutex);
m_status = s;
writeStatus(m_status);
}
const RGBled::LedStatus RGBled::getSatus(void)
{
return m_status;
}
void RGBled::writeStatus(const RGBled::LedStatus s)
{
const RGBled::color_u u{.status = s};
rgbLedWrite(m_led, (uint8_t)(m_brightness*u.color.r), (uint8_t)(m_brightness*u.color.g), (uint8_t)(m_brightness*u.color.b));
}

View File

@@ -0,0 +1,65 @@
#pragma once
// System Inlcudes
#include <Arduino.h>
#include <mutex>
#define RED 0x00FF00
#define GREEN 0xFF0000
#define BLUE 0x0000FF
#define WHITE 0xFFFFFF
#define YELLOW 0xFFFF00
#define CYAN 0xFF00FF
#define MAGENTA 0x00FFFF
#define ORANGE 0xA5FF00
#define PURPLE 0x008080
#define PINK 0x69FFB4
#define LIME 0xCD3232
#define SKY_BLUE 0xCE87EB
#define GOLD 0xD7FF00
#define TURQUOISE 0xE040D0
#define INDIGO 0x004B82
#define GRAY 0x808080
class RGBled
{
public:
enum LedStatus
{
OK = GREEN,
ERROR = RED,
INIT = YELLOW,
DATA_A = CYAN,
DATA_B = MAGENTA,
DATA_ALL = ORANGE,
IDLE = GRAY
};
struct color_t
{
uint8_t a, r, g, b;
};
union color_u
{
uint32_t status;
color_t color;
};
public:
RGBled(const uint8_t pin = 48);
~RGBled();
void setBrightness(const float b);
void setStatus(const LedStatus s);
const LedStatus getSatus(void);
private:
void writeStatus(const LedStatus s);
private:
LedStatus m_status = LedStatus::IDLE;
std::mutex m_mutex;
float m_brightness;
const uint8_t m_led;
};

View File

@@ -1,6 +0,0 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x700000,
app1, app, ota_1, 0x710000,0x700000,
spiffs, data, spiffs, 0xE10000,0x1F0000,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x5000
3 otadata data ota 0xe000 0x2000
4 app0 app ota_0 0x10000 0x700000
5 app1 app ota_1 0x710000 0x700000
6 spiffs data spiffs 0xE10000 0x1F0000

View File

@@ -0,0 +1,6 @@
# ESP32 Partition Table
# Name, Type, SubType, Offset, Size
nvs, data, nvs, 0x9000, 0x4000
phy_init, data, phy, 0xd000, 0x1000
factory, app, factory, 0x10000, 0x300000
littlefs, data, littlefs, 0x310000, 0xCF0000
1 # ESP32 Partition Table
2 # Name, Type, SubType, Offset, Size
3 nvs, data, nvs, 0x9000, 0x4000
4 phy_init, data, phy, 0xd000, 0x1000
5 factory, app, factory, 0x10000, 0x300000
6 littlefs, data, littlefs, 0x310000, 0xCF0000

View File

@@ -0,0 +1,6 @@
# ESP32 Partition Table
# Name, Type, SubType, Offset, Size
nvs, data, nvs, 0x9000, 0x4000
phy_init, data, phy, 0xd000, 0x1000
factory, app, factory, 0x10000, 0x300000
spiffs, data, spiffs, 0x310000, 0xCF0000
1 # ESP32 Partition Table
2 # Name, Type, SubType, Offset, Size
3 nvs, data, nvs, 0x9000, 0x4000
4 phy_init, data, phy, 0xd000, 0x1000
5 factory, app, factory, 0x10000, 0x300000
6 spiffs, data, spiffs, 0x310000, 0xCF0000

View File

@@ -10,59 +10,58 @@
[env:esp32-s3-devkitc1-n16r8] [env:esp32-s3-devkitc1-n16r8]
board = esp32-s3-devkitc1-n16r8 board = esp32-s3-devkitc1-n16r8
board_build.partitions = partitions/no_ota_10mb_littlefs.csv
board_build.filesystem = littlefs
platform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip platform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip
framework = arduino framework = arduino
lib_deps = lib_deps =
hideakitai/DebugLog@^0.8.4 hideakitai/DebugLog@^0.8.4
bblanchon/ArduinoJson@^7.4.2 bblanchon/ArduinoJson@^7.4.2
hideakitai/PCA95x5@^0.1.3 hideakitai/PCA95x5@^0.1.3
adafruit/Adafruit SSD1306@^2.5.16 me-no-dev/AsyncTCP@^3.3.2
garfius/Menu-UI@^1.2.0 me-no-dev/ESPAsyncWebServer@^3.6.0
;Upload protocol configuration
upload_protocol = esptool upload_protocol = esptool
upload_port = /dev/ttyACM2 upload_port = /dev/ttyACM0
upload_speed = 921600 upload_speed = 921600
monitor_port = /dev/ttyACM1
;Monitor configuration
monitor_port = /dev/ttyACM2
monitor_speed = 921600 monitor_speed = 921600
; Build configuration
build_type = release build_type = release
build_flags = build_flags =
-DARDUINO_USB_CDC_ON_BOOT=0 -DCORE_DEBUG_LEVEL=1
-DARDUINO_USB_MODE=0 -DARDUINO_USB_CDC_ON_BOOT=0
-fstack-protector-all -DARDUINO_USB_MODE=0
-DCONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=1 -DCONFIG_ASYNC_TCP_MAX_ACK_TIME=5000
-DCONFIG_FREERTOS_USE_TRACE_FACILITY=1 -DCONFIG_ASYNC_TCP_PRIORITY=21
-DCONFIG_ASYNC_TCP_QUEUE_SIZE=64
-DCONFIG_ASYNC_TCP_RUNNING_CORE=1
-DCONFIG_ASYNC_TCP_STACK_SIZE=4096
-fstack-protector-strong
[env:esp32-s3-devkitc1-n16r8-debug] [env:esp32-s3-devkitc1-n16r8-debug]
board = ${env:esp32-s3-devkitc1-n16r8.board} board = ${env:esp32-s3-devkitc1-n16r8.board}
board_build.partitions = ${env:esp32-s3-devkitc1-n16r8.board_build.partitions}
board_build.filesystem = ${env:esp32-s3-devkitc1-n16r8.board_build.filesystem}
platform = ${env:esp32-s3-devkitc1-n16r8.platform} platform = ${env:esp32-s3-devkitc1-n16r8.platform}
framework = ${env:esp32-s3-devkitc1-n16r8.framework} framework = ${env:esp32-s3-devkitc1-n16r8.framework}
lib_deps = ${env:esp32-s3-devkitc1-n16r8.lib_deps} lib_deps =
${env:esp32-s3-devkitc1-n16r8.lib_deps}
;Upload protocol configuration
upload_protocol = esptool upload_protocol = esptool
upload_port = /dev/ttyACM2 upload_port = /dev/ttyACM0
upload_speed = 921600 upload_speed = 921600
monitor_port = /dev/ttyACM1
;Monitor configuration
monitor_port = /dev/ttyACM2
monitor_speed = 921600 monitor_speed = 921600
; Debug configuration
debug_tool = esp-builtin debug_tool = esp-builtin
debug_speed = 15000 debug_speed = 15000
; Build configuration
build_type = debug build_type = debug
build_flags = build_flags =
-O0 -O0
-g3 -g3
-ggdb3 -ggdb3
-DCORE_DEBUG_LEVEL=5 -DCORE_DEBUG_LEVEL=3
-DARDUINO_USB_CDC_ON_BOOT=0 -DARDUINO_USB_CDC_ON_BOOT=0
-DARDUINO_USB_MODE=0 -DARDUINO_USB_MODE=0
-fstack-protector-all -DCONFIG_ASYNC_TCP_MAX_ACK_TIME=5000
-DCONFIG_ASYNC_TCP_PRIORITY=21
-DCONFIG_ASYNC_TCP_QUEUE_SIZE=64
-DCONFIG_ASYNC_TCP_RUNNING_CORE=1
-DCONFIG_ASYNC_TCP_STACK_SIZE=4096

View File

@@ -1,764 +0,0 @@
//ADS1256 cpp file
/*
Name: ADS1256.cpp
Created: 2022/07/14
Author: Curious Scientist
Editor: Notepad++
Comment: Visit https://curiousscientist.tech/blog/ADS1256-custom-library
Special thanks to:
Abraão Queiroz for spending time on the code and suggesting corrections for ESP32 microcontrollers
Benjamin Pelletier for pointing out and fixing an issue around the handling of the DRDY signal
RadoMmm for suggesting an improvement on the ADC-to-Volts conversion
*/
#include "Arduino.h"
#include "ADS1256.h"
#include "SPI.h"
#define convertSigned24BitToLong(value) ((value) & (1l << 23) ? (value) - 0x1000000 : value)
//Constructor
ADS1256::ADS1256(const int8_t DRDY_pin, const int8_t RESET_pin, const int8_t SYNC_pin, const int8_t CS_pin,float VREF, SPIClass* spi): _spi(spi),
_DRDY_pin(DRDY_pin), _RESET_pin(RESET_pin), _SYNC_pin(SYNC_pin), _CS_pin(CS_pin), _VREF(VREF), _PGA(0)
{
pinMode(_DRDY_pin, INPUT);
if(RESET_pin != PIN_UNUSED)
{
pinMode(_RESET_pin, OUTPUT);
}
if(SYNC_pin != PIN_UNUSED)
{
pinMode(_SYNC_pin, OUTPUT);
}
if(CS_pin != PIN_UNUSED)
{
pinMode(_CS_pin, OUTPUT);
}
updateConversionParameter();
}
//Initialization
void ADS1256::InitializeADC()
{
//Chip select LOW
CS_LOW();
//We do a manual chip reset on the ADS1256 - Datasheet Page 27/ RESET
if(_RESET_pin != PIN_UNUSED)
{
digitalWrite(_RESET_pin, LOW);
delay(200);
digitalWrite(_RESET_pin, HIGH); //RESET is set to high
delay(1000);
}
//Sync pin is also treated if it is defined
if(_SYNC_pin != PIN_UNUSED)
{
digitalWrite(_SYNC_pin, HIGH); //RESET is set to high
}
#ifndef ADS1256_SPI_ALREADY_STARTED //Guard macro to allow external initialization of the SPI
_spi->begin();
#endif
//Applying arbitrary default values to speed up the starting procedure if the user just want to get quick readouts
//We both pass values to the variables and then send those values to the corresponding registers
delay(200);
_STATUS = 0b00110110; //BUFEN and ACAL enabled, Order is MSB, rest is read only
writeRegister(STATUS_REG, _STATUS);
delay(200);
_MUX = 0b00000001; //MUX AIN0+AIN1
writeRegister(MUX_REG, _MUX);
delay(200);
_ADCON = 0b00000000; //ADCON - CLK: OFF, SDCS: OFF, PGA = 0 (+/- 5 V)
writeRegister(ADCON_REG, _ADCON);
delay(200);
updateConversionParameter();
_DRATE = 0b10000010; //100SPS
writeRegister(DRATE_REG, _DRATE);
delay(200);
sendDirectCommand(0b11110000); //Offset and self-gain calibration
delay(200);
_isAcquisitionRunning = false; //MCU will be waiting to start a continuous acquisition
}
void ADS1256::waitForLowDRDY()
{
while (digitalRead(_DRDY_pin) == HIGH) {}
}
void ADS1256::waitForHighDRDY()
{
#if F_CPU >= 48000000 //Fast MCUs need this protection to wait until DRDY goes high after a conversion
while (digitalRead(_DRDY_pin) == LOW) {}
#endif
}
void ADS1256::stopConversion() //Sending SDATAC to stop the continuous conversion
{
waitForLowDRDY(); //SDATAC should be called after DRDY goes LOW (p35. Figure 33)
_spi->transfer(0b00001111); //Send SDATAC to the ADC
CS_HIGH(); //We finished the command sequence, so we switch it back to HIGH
_spi->endTransaction();
_isAcquisitionRunning = false; //Reset to false, so the MCU will be able to start a new conversion
}
void ADS1256::setDRATE(uint8_t drate) //Setting DRATE (sampling frequency)
{
writeRegister(DRATE_REG, drate);
_DRATE = drate;
delayMicroseconds(500);
}
void ADS1256::setMUX(uint8_t mux) //Setting MUX (input channel)
{
writeRegister(MUX_REG, mux);
_MUX = mux;
//delayMicroseconds(500);
}
void ADS1256::setPGA(uint8_t pga) //Setting PGA (input voltage range)
{
_PGA = pga;
_ADCON = readRegister(ADCON_REG); //Read the most recent value of the register
_ADCON = (_ADCON & 0b11111000) | (_PGA & 0b00000111); // Clearing and then setting bits 2-0 based on pga
writeRegister(ADCON_REG, _ADCON);
delayMicroseconds(1000); //Delay to allow the PGA to settle after changing its value
updateConversionParameter(); //Update the multiplier according top the new PGA value
}
uint8_t ADS1256::getPGA() //Reading PGA from the ADCON register
{
uint8_t pgaValue = readRegister(ADCON_REG) & 0b00000111;
//Reading the ADCON_REG and keeping the first three bits.
return(pgaValue);
}
void ADS1256::setCLKOUT(uint8_t clkout) //Setting CLKOUT
{
_ADCON = readRegister(ADCON_REG); //Read the most recent value of the register
//Values: 0, 1, 2, 3
if(clkout == 0)
{
//00
bitWrite(_ADCON, 6, 0);
bitWrite(_ADCON, 5, 0);
}
else if(clkout == 1)
{
//01 (default)
bitWrite(_ADCON, 6, 0);
bitWrite(_ADCON, 5, 1);
}
else if(clkout == 2)
{
//10
bitWrite(_ADCON, 6, 1);
bitWrite(_ADCON, 5, 0);
}
else if(clkout == 3)
{
//11
bitWrite(_ADCON, 6, 1);
bitWrite(_ADCON, 5, 1);
}
else{}
writeRegister(ADCON_REG, _ADCON);
delay(100);
}
void ADS1256::setSDCS(uint8_t sdcs) //Setting SDCS
{
_ADCON = readRegister(ADCON_REG); //Read the most recent value of the register
//Values: 0, 1, 2, 3
if(sdcs == 0)
{
//00 (default)
bitWrite(_ADCON, 4, 0);
bitWrite(_ADCON, 3, 0);
}
else if(sdcs == 1)
{
//01
bitWrite(_ADCON, 4, 0);
bitWrite(_ADCON, 3, 1);
}
else if(sdcs == 2)
{
//10
bitWrite(_ADCON, 4, 1);
bitWrite(_ADCON, 3, 0);
}
else if(sdcs == 3)
{
//11
bitWrite(_ADCON, 4, 1);
bitWrite(_ADCON, 3, 1);
}
else{}
writeRegister(ADCON_REG, _ADCON);
delay(100);
}
void ADS1256::setByteOrder(uint8_t byteOrder) //Setting byte order (MSB/LSB)
{
_STATUS = readRegister(STATUS_REG); //Read the most recent value of the register
if(byteOrder == 0)
{
//Byte order is MSB (default)
bitWrite(_STATUS, 3, 0);
//Set value of _STATUS at the third bit to 0
}
else if(byteOrder == 1)
{
//Byte order is LSB
bitWrite(_STATUS, 3, 1);
//Set value of _STATUS at the third bit to 1
}
else{}
writeRegister(STATUS_REG, _STATUS);
delay(100);
}
uint8_t ADS1256::getByteOrder() //Getting byte order (MSB/LSB)
{
uint8_t statusValue = readRegister(STATUS_REG); //Read the whole STATUS register
return bitRead(statusValue, 3);
}
void ADS1256::setAutoCal(uint8_t acal) //Setting ACAL (Automatic SYSCAL)
{
_STATUS = readRegister(STATUS_REG); //Read the most recent value of the register
if(acal == 0)
{
//Auto-calibration is disabled (default)
bitWrite(_STATUS, 2, 0);
//_STATUS |= B00000000;
}
else if(acal == 1)
{
//Auto-calibration is enabled
bitWrite(_STATUS, 2, 1);
//_STATUS |= B00000100;
}
else{}
writeRegister(STATUS_REG, _STATUS);
delay(100);
}
uint8_t ADS1256::getAutoCal() //Getting ACAL (Automatic SYSCAL)
{
uint8_t statusValue = readRegister(STATUS_REG); //Read the whole STATUS register
return bitRead(statusValue, 2);
}
void ADS1256::setBuffer(uint8_t bufen) //Setting input buffer (Input impedance)
{
_STATUS = readRegister(STATUS_REG); //Read the most recent value of the register
if(bufen == 0)
{
//Analog input buffer is disabled (default)
//_STATUS |= B00000000;
bitWrite(_STATUS, 1, 0);
}
else if(bufen == 1)
{
//Analog input buffer is enabled (recommended)
//_STATUS |= B00000010;
bitWrite(_STATUS, 1, 1);
}
else{}
writeRegister(STATUS_REG, _STATUS);
delay(100);
}
uint8_t ADS1256::getBuffer() //Getting input buffer (Input impedance)
{
uint8_t statusValue = readRegister(STATUS_REG); //Read the whole STATUS register
return bitRead(statusValue, 1);
}
void ADS1256::setGPIO(uint8_t dir0, uint8_t dir1, uint8_t dir2, uint8_t dir3) //Setting GPIO
{
_GPIO = readRegister(IO_REG); //Read the most recent value of the register
//Default: 11100000 - DEC: 224 - Ref: p32 I/O section
//Sets D3-D0 as input or output
uint8_t GPIO_bit7, GPIO_bit6, GPIO_bit5, GPIO_bit4;
//Bit7: DIR3
if(dir3 == 1)
{
GPIO_bit7 = 1; //D3 is input (default)
}
else
{
GPIO_bit7 = 0; //D3 is output
}
bitWrite(_GPIO, 7, GPIO_bit7);
//-----------------------------------------------------
//Bit6: DIR2
if(dir2 == 1)
{
GPIO_bit6 = 1; //D2 is input (default)
}
else
{
GPIO_bit6 = 0; //D2 is output
}
bitWrite(_GPIO, 6, GPIO_bit6);
//-----------------------------------------------------
//Bit5: DIR1
if(dir1 == 1)
{
GPIO_bit5 = 1; //D1 is input (default)
}
else
{
GPIO_bit5 = 0; //D1 is output
}
bitWrite(_GPIO, 5, GPIO_bit5);
//-----------------------------------------------------
//Bit4: DIR0
if(dir0 == 1)
{
GPIO_bit4 = 1; //D0 is input
}
else
{
GPIO_bit4 = 0; //D0 is output (default)
}
bitWrite(_GPIO, 4, GPIO_bit4);
//-----------------------------------------------------
writeRegister(IO_REG, _GPIO);
delay(100);
}
void ADS1256::writeGPIO(uint8_t dir0value, uint8_t dir1value, uint8_t dir2value, uint8_t dir3value) //Writing GPIO
{
_GPIO = readRegister(IO_REG);
//Sets D3-D0 output values
//It is important that first one must use setGPIO, then writeGPIO
uint8_t GPIO_bit3, GPIO_bit2, GPIO_bit1, GPIO_bit0;
//Bit3: DIR3
if(dir3value == 1)
{
GPIO_bit3 = 1;
}
else
{
GPIO_bit3 = 0;
}
bitWrite(_GPIO, 3, GPIO_bit3);
//-----------------------------------------------------
//Bit2: DIR2
if(dir2value == 1)
{
GPIO_bit2 = 1;
}
else
{
GPIO_bit2 = 0;
}
bitWrite(_GPIO, 2, GPIO_bit2);
//-----------------------------------------------------
//Bit1: DIR1
if(dir1value == 1)
{
GPIO_bit1 = 1;
}
else
{
GPIO_bit1 = 0;
}
bitWrite(_GPIO, 1, GPIO_bit1);
//-----------------------------------------------------
//Bit0: DIR0
if(dir0value == 1)
{
GPIO_bit0 = 1;
}
else
{
GPIO_bit0 = 0;
}
bitWrite(_GPIO, 0, GPIO_bit0);
//-----------------------------------------------------
writeRegister(IO_REG, _GPIO);
delay(100);
}
uint8_t ADS1256::readGPIO(uint8_t gpioPin) //Reading GPIO
{
uint8_t GPIO_bit3, GPIO_bit2, GPIO_bit1, GPIO_bit0, GPIO_return;
_GPIO = readRegister(IO_REG); //Read the GPIO register
//Save each bit values in a variable
GPIO_bit3 = bitRead(_GPIO, 3);
GPIO_bit2 = bitRead(_GPIO, 2);
GPIO_bit1 = bitRead(_GPIO, 1);
GPIO_bit0 = bitRead(_GPIO, 0);
delay(100);
switch(gpioPin) //Selecting which value should be returned
{
case 0:
GPIO_return = GPIO_bit0;
break;
case 1:
GPIO_return = GPIO_bit1;
break;
case 2:
GPIO_return = GPIO_bit2;
break;
case 3:
GPIO_return = GPIO_bit3;
break;
}
return GPIO_return;
}
void ADS1256::sendDirectCommand(uint8_t directCommand)
{
//Direct commands can be found in the datasheet Page 34, Table 24.
_spi->beginTransaction(SPISettings(1920000, MSBFIRST, SPI_MODE1));
CS_LOW(); //REF: P34: "CS must stay low during the entire command sequence"
delayMicroseconds(5);
_spi->transfer(directCommand); //Send Command
delayMicroseconds(5);
CS_HIGH(); //REF: P34: "CS must stay low during the entire command sequence"
_spi->endTransaction();
}
float ADS1256::convertToVoltage(int32_t rawData) //Converting the 24-bit data into a voltage value
{
return(conversionParameter * rawData);
}
void ADS1256::writeRegister(uint8_t registerAddress, uint8_t registerValueToWrite)
{
waitForLowDRDY();
_spi->beginTransaction(SPISettings(1920000, MSBFIRST, SPI_MODE1));
//SPI_MODE1 = output edge: rising, data capture: falling; clock polarity: 0, clock phase: 1.
CS_LOW(); //CS must stay LOW during the entire sequence [Ref: P34, T24]
delayMicroseconds(5); //see t6 in the datasheet
_spi->transfer(0x50 | registerAddress); // 0x50 = 01010000 = WREG
_spi->transfer(0x00); //2nd (empty) command byte
_spi->transfer(registerValueToWrite); //pass the value to the register
CS_HIGH();
_spi->endTransaction();
}
long ADS1256::readRegister(uint8_t registerAddress) //Reading a register
{
waitForLowDRDY();
_spi->beginTransaction(SPISettings(1920000, MSBFIRST, SPI_MODE1));
//SPI_MODE1 = output edge: rising, data capture: falling; clock polarity: 0, clock phase: 1.
CS_LOW(); //CS must stay LOW during the entire sequence [Ref: P34, T24]
_spi->transfer(0x10 | registerAddress); //0x10 = 0001000 = RREG - OR together the two numbers (command + address)
_spi->transfer(0x00); //2nd (empty) command byte
delayMicroseconds(5); //see t6 in the datasheet
uint8_t regValue = _spi->transfer(0xFF); //read out the register value
CS_HIGH();
_spi->endTransaction();
return regValue;
}
long ADS1256::readSingle() //Reading a single value ONCE using the RDATA command
{
_spi->beginTransaction(SPISettings(1920000, MSBFIRST, SPI_MODE1));
CS_LOW(); //REF: P34: "CS must stay low during the entire command sequence"
waitForLowDRDY();
_spi->transfer(0b00000001); //Issue RDATA (0000 0001) command
delayMicroseconds(7); //Wait t6 time (~6.51 us) REF: P34, FIG:30.
_outputBuffer[0] = _spi->transfer(0); // MSB
_outputBuffer[1] = _spi->transfer(0); // Mid-byte
_outputBuffer[2] = _spi->transfer(0); // LSB
//Shifting and combining the above three items into a single, 24-bit number
_outputValue = ((long)_outputBuffer[0]<<16) | ((long)_outputBuffer[1]<<8) | (_outputBuffer[2]);
_outputValue = convertSigned24BitToLong(_outputValue);
CS_HIGH(); //We finished the command sequence, so we set CS to HIGH
_spi->endTransaction();
return(_outputValue);
}
long ADS1256::readSingleContinuous() //Reads the recently selected input channel using RDATAC
{
if(_isAcquisitionRunning == false)
{
_isAcquisitionRunning = true;
_spi->beginTransaction(SPISettings(1920000, MSBFIRST, SPI_MODE1));
CS_LOW(); //REF: P34: "CS must stay low during the entire command sequence"
waitForLowDRDY();
_spi->transfer(0b00000011); //Issue RDATAC (0000 0011)
delayMicroseconds(7); //Wait t6 time (~6.51 us) REF: P34, FIG:30.
}
else
{
waitForLowDRDY();
}
_outputBuffer[0] = _spi->transfer(0); // MSB
_outputBuffer[1] = _spi->transfer(0); // Mid-byte
_outputBuffer[2] = _spi->transfer(0); // LSB
_outputValue = ((long)_outputBuffer[0]<<16) | ((long)_outputBuffer[1]<<8) | (_outputBuffer[2]);
_outputValue = convertSigned24BitToLong(_outputValue);
waitForHighDRDY();
return _outputValue;
}
long ADS1256::cycleSingle()
{
if(_isAcquisitionRunning == false)
{
_isAcquisitionRunning = true;
_cycle = 0;
_spi->beginTransaction(SPISettings(1920000, MSBFIRST, SPI_MODE1));
CS_LOW(); //CS must stay LOW during the entire sequence [Ref: P34, T24]
_spi->transfer(0x50 | 1); // 0x50 = WREG //1 = MUX
_spi->transfer(0x00);
_spi->transfer(SING_0); //AIN0+AINCOM
CS_HIGH();
delay(50);
CS_LOW(); //CS must stay LOW during the entire sequence [Ref: P34, T24]
}
else
{}
if(_cycle < 8)
{
_outputValue = 0;
waitForLowDRDY();
//Step 1. - Updating MUX
switch (_cycle)
{
//Channels are written manually
case 0: //Channel 2
updateMUX(SING_1); //AIN1+AINCOM
break;
case 1: //Channel 3
updateMUX(SING_2); //AIN2+AINCOM
break;
case 2: //Channel 4
updateMUX(SING_3); //AIN3+AINCOM
break;
case 3: //Channel 5
updateMUX(SING_4); //AIN4+AINCOM
break;
case 4: //Channel 6
updateMUX(SING_5); //AIN5+AINCOM
break;
case 5: //Channel 7
updateMUX(SING_6); //AIN6+AINCOM
break;
case 6: //Channel 8
updateMUX(SING_7); //AIN7+AINCOM
break;
case 7: //Channel 1
updateMUX(SING_0); //AIN0+AINCOM
break;
}
//Step 2.
_spi->transfer(0b11111100); //SYNC
delayMicroseconds(4); //t11 delay 24*tau = 3.125 us //delay should be larger, so we delay by 4 us
_spi->transfer(0b11111111); //WAKEUP
//Step 3.
//Issue RDATA (0000 0001) command
_spi->transfer(0b00000001);
delayMicroseconds(7); //Wait t6 time (~6.51 us) REF: P34, FIG:30.
_outputBuffer[0] = _spi->transfer(0x0F); // MSB
_outputBuffer[1] = _spi->transfer(0x0F); // Mid-byte
_outputBuffer[2] = _spi->transfer(0x0F); // LSB
_outputValue = ((long)_outputBuffer[0]<<16) | ((long)_outputBuffer[1]<<8) | (_outputBuffer[2]);
_outputValue = convertSigned24BitToLong(_outputValue);
_cycle++; //Increase cycle - This will move to the next MUX input channel
if(_cycle == 8)
{
_cycle = 0; //Reset to 0 - Restart conversion from the 1st input channel
}
}
return _outputValue;
}
long ADS1256::cycleDifferential()
{
if(_isAcquisitionRunning == false)
{
_cycle = 0;
_isAcquisitionRunning = true;
_spi->beginTransaction(SPISettings(1920000, MSBFIRST, SPI_MODE1));
//Set the AIN0+AIN1 as inputs manually
CS_LOW(); //CS must stay LOW during the entire sequence [Ref: P34, T24]
_spi->transfer(0x50 | 1); // 0x50 = WREG //1 = MUX
_spi->transfer(0x00);
_spi->transfer(DIFF_0_1); //AIN0+AIN1
CS_HIGH();
delay(50);
CS_LOW(); //CS must stay LOW during the entire sequence [Ref: P34, T24]
}
else
{}
if(_cycle < 4)
{
_outputValue = 0;
//DRDY has to go low
waitForLowDRDY();
//Step 1. - Updating MUX
switch (_cycle)
{
case 0: //Channel 2
updateMUX(DIFF_2_3); //AIN2+AIN3
break;
case 1: //Channel 3
updateMUX(DIFF_4_5); //AIN4+AIN5
break;
case 2: //Channel 4
updateMUX(DIFF_6_7); //AIN6+AIN7
break;
case 3: //Channel 1
updateMUX(DIFF_0_1); //AIN0+AIN1
break;
}
_spi->transfer(0b11111100); //SYNC
delayMicroseconds(4); //t11 delay 24*tau = 3.125 us //delay should be larger, so we delay by 4 us
_spi->transfer(0b11111111); //WAKEUP
//Step 3.
_spi->transfer(0b00000001); //Issue RDATA (0000 0001) command
delayMicroseconds(7); //Wait t6 time (~6.51 us) REF: P34, FIG:30.
_outputBuffer[0] = _spi->transfer(0); // MSB
_outputBuffer[1] = _spi->transfer(0); // Mid-byte
_outputBuffer[2] = _spi->transfer(0); // LSB
_outputValue = ((long)_outputBuffer[0]<<16) | ((long)_outputBuffer[1]<<8) | (_outputBuffer[2]);
_outputValue = convertSigned24BitToLong(_outputValue);
_cycle++;
if(_cycle == 4)
{
_cycle = 0;
//After the 4th cycle, we reset to zero so the next iteration reads the 1st MUX again
}
}
return _outputValue;
}
void ADS1256::updateConversionParameter()
{
conversionParameter = ((2.0 * _VREF) / 8388608.0) / (pow(2, _PGA)); //Calculate the "bit to Volts" multiplier
//8388608 = 2^{23} - 1, REF: p23, Table 16.
}
void ADS1256::updateMUX(uint8_t muxValue)
{
_spi->transfer(0x50 | MUX_REG); //Write to the MUX register (0x50 is the WREG command)
_spi->transfer(0x00);
_spi->transfer(muxValue); //Write the new MUX value
}
inline void ADS1256::CS_LOW()
{
if (_CS_pin != PIN_UNUSED) //Sets CS LOW if it is not an unused pin
{
digitalWrite(_CS_pin, LOW);
}
}
inline void ADS1256::CS_HIGH()
{
if (_CS_pin != PIN_UNUSED) //Sets CS HIGH if it is not an unused pin
{
digitalWrite(_CS_pin, HIGH);
}
}

View File

@@ -1,189 +0,0 @@
//ADS1256 header file
/*
Name: ADS1256.h
Created: 2022/07/14
Author: Curious Scientist
Editor: Notepad++
Comment: Visit https://curiousscientist.tech/blog/ADS1256-custom-library
Special thanks to
Abraão Queiroz for spending time on the code and suggesting corrections for ESP32 microcontrollers
Benjamin Pelletier for pointing out and fixing an issue around the handling of the DRDY signal
*/
#ifndef _ADS1256_h
#define _ADS1256_h
#include <SPI.h>
//Differential inputs
#define DIFF_0_1 0b00000001 //A0 + A1 as differential input
#define DIFF_2_3 0b00100011 //A2 + A3 as differential input
#define DIFF_4_5 0b01000101 //A4 + A5 as differential input
#define DIFF_6_7 0b01100111 //A6 + A7 as differential input
//Single-ended inputs
#define SING_0 0b00001111 //A0 + GND (common) as single-ended input
#define SING_1 0b00011111 //A1 + GND (common) as single-ended input
#define SING_2 0b00101111 //A2 + GND (common) as single-ended input
#define SING_3 0b00111111 //A3 + GND (common) as single-ended input
#define SING_4 0b01001111 //A4 + GND (common) as single-ended input
#define SING_5 0b01011111 //A5 + GND (common) as single-ended input
#define SING_6 0b01101111 //A6 + GND (common) as single-ended input
#define SING_7 0b01111111 //A7 + GND (common) as single-ended input
//PGA settings //Input voltage range
#define PGA_1 0b00000000 //± 5 V
#define PGA_2 0b00000001 //± 2.5 V
#define PGA_4 0b00000010 //± 1.25 V
#define PGA_8 0b00000011 //± 625 mV
#define PGA_16 0b00000100 //± 312.5 mV
#define PGA_32 0b00000101 //+ 156.25 mV
#define PGA_64 0b00000110 //± 78.125 mV
//Datarate //DEC
#define DRATE_30000SPS 0b11110000 //240
#define DRATE_15000SPS 0b11100000 //224
#define DRATE_7500SPS 0b11010000 //208
#define DRATE_3750SPS 0b11000000 //192
#define DRATE_2000SPS 0b10110000 //176
#define DRATE_1000SPS 0b10100001 //161
#define DRATE_500SPS 0b10010010 //146
#define DRATE_100SPS 0b10000010 //130
#define DRATE_60SPS 0b01110010 //114
#define DRATE_50SPS 0b01100011 //99
#define DRATE_30SPS 0b01010011 //83
#define DRATE_25SPS 0b01000011 //67
#define DRATE_15SPS 0b00110011 //51
#define DRATE_10SPS 0b00100011 //35
#define DRATE_5SPS 0b00010011 //19
#define DRATE_2SPS 0b00000011 //3
//Status register
#define BITORDER_MSB 0
#define BITORDER_LSB 1
#define ACAL_DISABLED 0
#define ACAL_ENABLED 1
#define BUFFER_DISABLED 0
#define BUFFER_ENABLED 1
//Register addresses
#define STATUS_REG 0x00
#define MUX_REG 0x01
#define ADCON_REG 0x02
#define DRATE_REG 0x03
#define IO_REG 0x04
#define OFC0_REG 0x05
#define OFC1_REG 0x06
#define OFC2_REG 0x07
#define FSC0_REG 0x08
#define FSC1_REG 0x09
#define FSC2_REG 0x0A
//Command definitions
#define WAKEUP 0b00000000
#define RDATA 0b00000001
#define RDATAC 0b00000011
#define SDATAC 0b00001111
#define RREG 0b00010000
#define WREG 0b01010000
#define SELFCAL 0b11110000
#define SELFOCAL 0b11110001
#define SELFGCAL 0b11110010
#define SYSOCAL 0b11110011
#define SYSGCAL 0b11110100
#define SYNC 0b11111100
#define STANDBY 0b11111101
#define RESET 0b11111110
//----------------------------------------------------------------
class ADS1256
{
public:
static constexpr int8_t PIN_UNUSED = -1;
//Constructor
ADS1256(const int8_t DRDY_pin, const int8_t RESET_pin, const int8_t SYNC_pin, const int8_t CS_pin, float VREF, SPIClass* spi = &SPI);
//Initializing function
void InitializeADC();
//ADS1256(int drate, int pga, int byteOrder, bool bufen);
//Read a register
long readRegister(uint8_t registerAddress);
//Write a register
void writeRegister(uint8_t registerAddress, uint8_t registerValueToWrite);
//Individual methods
void setDRATE(uint8_t drate);
void setPGA(uint8_t pga);
uint8_t getPGA();
void setMUX(uint8_t mux);
void setByteOrder(uint8_t byteOrder);
uint8_t getByteOrder();
void setBuffer(uint8_t bufen);
uint8_t getBuffer();
void setAutoCal(uint8_t acal);
uint8_t getAutoCal();
void setGPIO(uint8_t dir0, uint8_t dir1, uint8_t dir2, uint8_t dir3);
void writeGPIO(uint8_t dir0value, uint8_t dir1value, uint8_t dir2value, uint8_t dir3value);
uint8_t readGPIO(uint8_t gpioPin);
void setCLKOUT(uint8_t clkout);
void setSDCS(uint8_t sdcs);
void sendDirectCommand(uint8_t directCommand);
//Get a single conversion
long readSingle();
//Single input continuous reading
long readSingleContinuous();
//Cycling through the single-ended inputs
long cycleSingle(); //Ax + COM
//Cycling through the differential inputs
long cycleDifferential(); //Ax + Ay
//Converts the reading into a voltage value
float convertToVoltage(int32_t rawData);
//Stop AD
void stopConversion();
private:
SPIClass* _spi; //Pointer to an SPIClass object
void waitForLowDRDY(); // Block until DRDY is low
void waitForHighDRDY(); // Block until DRDY is high
void updateMUX(uint8_t muxValue);
inline void CS_LOW();
inline void CS_HIGH();
void updateConversionParameter(); //Refresh the conversion parameter based on the PGA
float _VREF = 0; //Value of the reference voltage
float conversionParameter = 0; //PGA-dependent multiplier
//Pins
int8_t _DRDY_pin; //Pin assigned for DRDY
int8_t _RESET_pin; //Pin assigned for RESET
int8_t _SYNC_pin; //Pin assigned for SYNC
int8_t _CS_pin; //Pin assigned for CS
//Register values
byte _DRATE; //Value of the DRATE register
byte _ADCON; //Value of the ADCON register
byte _MUX; //Value of the MUX register
byte _PGA; //Value of the PGA (within ADCON)
byte _GPIO; //Value of the GPIO register
byte _STATUS; //Value of the status register
byte _GPIOvalue; //GPIO value
byte _ByteOrder; //Byte order
byte _outputBuffer[3]; //3-byte (24-bit) buffer for the fast acquisition - Single-channel, continuous
long _outputValue; //Combined value of the _outputBuffer[3]
bool _isAcquisitionRunning; //bool that keeps track of the acquisition (running or not)
uint8_t _cycle; //Tracks the cycles as the MUX is cycling through the input channels
};
#endif

View File

@@ -1,11 +0,0 @@
// ADC Channels
#define A1_RAW 0
#define A2_RAW 1
#define B1_RAW 2
#define B2_RAW 3
#define A1_COND 4
#define A2_COND 5
#define B1_COND 6
#define B2_COND 7

View File

@@ -0,0 +1,123 @@
#include "datasave.h"
#include <math.h>
LITTLEFSGuard::LITTLEFSGuard()
{
if (!LittleFS.begin(true, "/littlefs", 10, "littlefs"))
{
LOG_ERROR("Failed to mount LittleFS");
}
else
{
LOG_INFO("LittleFS mounted successfully");
LOG_INFO("LittleFS Free KBytes:", (LittleFS.totalBytes() - LittleFS.usedBytes()) / 1024);
}
}
LITTLEFSGuard::~LITTLEFSGuard()
{
LittleFS.end();
LOG_INFO("LittleFS unmounted successfully");
}
void ignitionBoxStatusFiltered::filter(int32_t &old, const int32_t value, const uint32_t k)
{
float alpha = 1.0f / (float)k;
old = old + (int32_t)(alpha * (float)(value - old));
}
void ignitionBoxStatusFiltered::filter(float &old, const float value, const uint32_t k)
{
float alpha = 1.0f / (float)k;
old = old + (float)(alpha * (float)(value - old));
}
void ignitionBoxStatusFiltered::reset()
{
m_last = ignitionBoxStatus();
m_count = 0;
m_data_valid = false;
}
void ignitionBoxStatusFiltered::update(const ignitionBoxStatus &new_status)
{
if (m_count == 0 && !m_data_valid)
{
m_last = new_status;
}
m_count++;
// simple moving average calculation
m_last.timestamp = new_status.timestamp; // keep timestamp of latest status
m_last.coils12.nEvents = new_status.coils12.nEvents; // sum events instead of averaging
m_last.coils12.nMissedFiring = new_status.coils12.nMissedFiring; // sum missed firings instead of averaging
m_last.coils12.sparkStatus = new_status.coils12.sparkStatus; // take latest spark status
m_last.coils12.softStartStatus = new_status.coils12.softStartStatus; // take latest soft start status
filter(m_last.coils12.sparkDelay, new_status.coils12.sparkDelay, m_max_count); // incremental average calculation
filter(m_last.coils12.peakPos, new_status.coils12.peakPos, m_max_count); // incremental average calculation
filter(m_last.coils12.peakNeg, new_status.coils12.peakNeg, m_max_count); // incremental average calculation
filter(m_last.coils12.trigLevelPos, new_status.coils12.trigLevelPos, m_max_count); // incremental average calculation
filter(m_last.coils12.trigLevelNeg, new_status.coils12.trigLevelNeg, m_max_count); // incremental average calculation
m_last.coils34.nEvents = new_status.coils34.nEvents; // sum events instead of averaging
m_last.coils34.nMissedFiring = new_status.coils34.nMissedFiring; // sum missed firings instead of averaging
m_last.coils34.sparkStatus = new_status.coils34.sparkStatus; // take latest spark status
m_last.coils34.softStartStatus = new_status.coils34.softStartStatus; // take latest soft start status
filter(m_last.coils34.sparkDelay, new_status.coils34.sparkDelay, m_max_count); // incremental average calculation
filter(m_last.coils34.peakPos, new_status.coils34.peakPos, m_max_count); // incremental average calculation
filter(m_last.coils34.peakNeg, new_status.coils34.peakNeg, m_max_count); // incremental average calculation
filter(m_last.coils34.trigLevelPos, new_status.coils34.trigLevelPos, m_max_count); // incremental average calculation
filter(m_last.coils34.trigLevelNeg, new_status.coils34.trigLevelNeg, m_max_count); // incremental average calculation
filter(m_last.engRpm, new_status.engRpm, m_max_count); // incremental average calculation // incremental average calculation
filter(m_last.adcReadTime, m_last.adcReadTime, m_max_count); // incremental average calculation
m_last.nQueueErrors = new_status.nQueueErrors;
if (m_count >= m_max_count)
{
m_count = 0; // reset count after reaching max samples to average
m_data_valid = true; // set data valid flag after first average is calculated
}
}
const bool ignitionBoxStatusFiltered::get(ignitionBoxStatus &status) const
{
if (m_data_valid)
{
status = m_last;
}
return m_data_valid;
}
const ArduinoJson::JsonDocument ignitionBoxStatusFiltered::toJson() const
{
ArduinoJson::JsonDocument doc;
if (m_data_valid)
{
doc["timestamp"] = m_last.timestamp;
doc["datavalid"] = m_data_valid ? "TRUE" : "FALSE";
doc["coils12"]["nEvents"] = m_last.coils12.nEvents;
doc["coils12"]["nMissedFiring"] = m_last.coils12.nMissedFiring;
doc["coils12"]["sparkDelay"] = m_last.coils12.sparkDelay;
doc["coils12"]["sparkStatus"] = sparkStatusNames.at(m_last.coils12.sparkStatus);
doc["coils12"]["peakPos"] = m_last.coils12.peakPos;
doc["coils12"]["peakNeg"] = m_last.coils12.peakNeg;
doc["coils12"]["trigLevelPos"] = m_last.coils12.trigLevelPos;
doc["coils12"]["trigLevelNeg"] = m_last.coils12.trigLevelNeg;
doc["coils12"]["softStartStatus"] = softStartStatusNames.at(m_last.coils12.softStartStatus);
doc["coils34"]["nEvents"] = m_last.coils34.nEvents;
doc["coils34"]["nMissedFiring"] = m_last.coils34.nMissedFiring;
doc["coils34"]["sparkDelay"] = m_last.coils34.sparkDelay;
doc["coils34"]["sparkStatus"] = sparkStatusNames.at(m_last.coils34.sparkStatus);
doc["coils34"]["peakPos"] = m_last.coils34.peakPos;
doc["coils34"]["peakNeg"] = m_last.coils34.peakNeg;
doc["coils34"]["trigLevelPos"] = m_last.coils34.trigLevelPos;
doc["coils34"]["trigLevelNeg"] = m_last.coils34.trigLevelNeg;
doc["coils34"]["softStartStatus"] = softStartStatusNames.at(m_last.coils34.softStartStatus);
doc["engRpm"] = m_last.engRpm;
doc["adcReadTime"] = m_last.adcReadTime;
doc["nQueueErrors"] = m_last.nQueueErrors;
}
return doc;
}

View File

@@ -0,0 +1,48 @@
#pragma once
#define DEBUGLOG_DEFAULT_LOG_LEVEL_INFO
// System Includes
#include <Arduino.h>
#include <DebugLog.h>
#include <string>
#include <fstream>
#include <ArduinoJson.h>
#include <filesystem>
#include <LittleFS.h>
// Project Includes
#include "isr.h"
#include "psvector.h"
class LITTLEFSGuard
{
public:
LITTLEFSGuard();
~LITTLEFSGuard();
};
class ignitionBoxStatusFiltered
{
private:
ignitionBoxStatus m_last;
uint32_t m_count = 0;
uint32_t m_max_count = 100; // number of samples to average before resetting
bool m_data_valid = false; // flag to indicate if the average data is valid (i.e. at least one sample has been added)
public:
ignitionBoxStatusFiltered() = default;
ignitionBoxStatusFiltered(const uint32_t max_count) : m_max_count(max_count)
{
m_data_valid = false;
m_count = 0;
}
void reset();
void update(const ignitionBoxStatus &new_status);
const bool get(ignitionBoxStatus &status) const;
const ArduinoJson::JsonDocument toJson() const;
private:
void filter(int32_t &old, const int32_t value, const uint32_t k);
void filter(float &old, const float value, const uint32_t k);
};

View File

@@ -0,0 +1,93 @@
#pragma once
#include <Arduino.h>
#include <map>
#include <psvector.h>
// =====================
// Event Flags (bitmask)
// =====================
static const uint32_t TRIG_FLAG_12P = (1 << 0);
static const uint32_t TRIG_FLAG_12N = (1 << 1);
static const uint32_t TRIG_FLAG_34P = (1 << 2);
static const uint32_t TRIG_FLAG_34N = (1 << 3);
static const uint32_t SPARK_FLAG_TIMEOUT = (1 << 8);
static const uint32_t SPARK_FLAG_12 = (1 << 9);
static const uint32_t SPARK_FLAG_34 = (1 << 10);
// Spark Status
enum sparkStatusEnum
{
SPARK_POS_OK,
SPARK_NEG_OK,
SPARK_POS_SKIP,
SPARK_NEG_SKIP,
SPARK_POS_WAIT,
SPARK_NEG_WAIT,
SPARK_POS_FAIL,
SPARK_NEG_FAIL,
SPARK_POS_UNEXPECTED,
SPARK_NEG_UNEXPECTED,
SPARK_SYNC_FAIL,
};
static const std::map<const sparkStatusEnum, const char *> sparkStatusNames = {
{SPARK_POS_OK, "SPARK_POS_OK"},
{SPARK_NEG_OK, "SPARK_NEG_OK"},
{SPARK_POS_SKIP, "SPARK_POS_SKIP"},
{SPARK_NEG_SKIP, "SPARK_NEG_SKIP"},
{SPARK_POS_WAIT, "SPARK_POS_WAIT"},
{SPARK_NEG_WAIT, "SPARK_NEG_WAIT"},
{SPARK_POS_FAIL, "SPARK_POS_FAIL"},
{SPARK_NEG_FAIL, "SPARK_NEG_FAIL"},
{SPARK_POS_UNEXPECTED, "SPARK_POS_UNEXPECTED"},
{SPARK_NEG_UNEXPECTED, "SPARK_NEG_UNEXPECTED"},
{SPARK_SYNC_FAIL, "SPARK_SYNC_FAIL"},
};
enum softStartStatusEnum
{
NORMAL,
SOFT_START,
ERROR,
};
const std::map<const softStartStatusEnum, const char *> softStartStatusNames = {
{NORMAL, "NORMAL"},
{SOFT_START, "SOFT_START"},
{ERROR, "ERROR"},
};
struct coilsStatus
{
int64_t coilTime = 0;
int64_t sparkTime = 0;
int32_t sparkDelay = 0; // in microseconds
sparkStatusEnum sparkStatus = sparkStatusEnum::SPARK_POS_OK;
softStartStatusEnum softStartStatus = softStartStatusEnum::NORMAL;
float peakPos = 0.0;
float peakNeg = 0.0;
float trigLevelPos = 0.0;
float trigLevelNeg = 0.0;
uint32_t nEvents = 0;
uint32_t nMissedFiring = 0;
};
// Task internal Status
struct ignitionBoxStatus
{
int64_t timestamp = 0;
// coils pairs for each ignition
coilsStatus coils12;
coilsStatus coils34;
// enine rpm
int32_t engRpm = 0;
// debug values
uint32_t nQueueErrors = 0;
int32_t adcReadTime = 0;
int32_t ioReadWriteTime = 0;
};
template <typename T>
using PSRAMVector = std::vector<T, PSRAMAllocator<T>>;

View File

@@ -3,37 +3,53 @@
// Library defines // Library defines
#define ADS1256_SPI_ALREADY_STARTED #define ADS1256_SPI_ALREADY_STARTED
// System Includes
#include <memory>
// Device Libraries // Device Libraries
#include <ADS1256.h> #include <ADS1256.h>
#include <AD5292.h> #include <AD5292.h>
#include <Adafruit_SSD1306.h> #include <extio.h>
#include <PCA95x5.h> #include <Wire.h>
// ADC Channel mapping // ADC Channel mapping
#define ADC_CH_PEAK_12P_IN SING_0 #define ADC_CH_PEAK_12P_IN SING_0
#define ADC_CH_PEAK_12N_IN SING_1 #define ADC_CH_PEAK_12N_IN SING_1
#define ADC_CH_PEAK_34P_IN SING_2 #define ADC_CH_PEAK_34P_IN SING_2
#define ADC_CH_PEAK_34N_IN SING_3 #define ADC_CH_PEAK_34N_IN SING_3
#define ADC_CH_PEAK_12P_OUT SING_4 #define ADC_CH_PEAK_12P_OUT SING_4
#define ADC_CH_PEAK_12N_OUT SING_5 #define ADC_CH_PEAK_12N_OUT SING_5
#define ADC_CH_PEAK_34P_OUT SING_6 #define ADC_CH_PEAK_34P_OUT SING_6
#define ADC_CH_PEAK_34N_OUT SING_7 #define ADC_CH_PEAK_34N_OUT SING_7
// Device Pointer structs for tasks // Device Pointer structs for tasks
struct Devices { struct Devices
AD5292 *pot_a = NULL, *pot_b = NULL; {
ADS1256 *adc_a = NULL, *adc_b = NULL; // Busses
Adafruit_SSD1306* lcd = NULL; TwoWire *m_i2c = NULL;
PCA9555* io = NULL; SPIClass *m_spi_a = NULL;
SPIClass *m_spi_b = NULL;
// Bus Mutextes
std::mutex m_spi_a_mutex;
std::mutex m_spi_b_mutex;
std::mutex m_i2c_mutex;
// Device Pointers
AD5292 *m_pot_a = NULL;
AD5292 *m_pot_b = NULL;
ADS1256 *m_adc_a = NULL;
ADS1256 *m_adc_b = NULL;
ExternalIO *m_ext_io = NULL;
}; };
// Adc read channel wrapper to selet mux before reading // Adc read channel wrapper to selet mux before reading
inline float adcReadChannel(ADS1256* adc, const uint8_t ch){ inline float adcReadChannel(ADS1256 *adc, const uint8_t ch)
{
adc->setMUX(ch); adc->setMUX(ch);
// scarta 3 conversioni adc->readSingle();
for (int i = 0; i < 3; i++) {
adc->readSingle();
}
// ora lettura valida a 30kSPS → ~100 µs di settling // ora lettura valida a 30kSPS → ~100 µs di settling
return adc->convertToVoltage(adc->readSingle()); return adc->convertToVoltage(adc->readSingle());
} }

129
RotaxMonitor/src/extio.cpp Normal file
View File

@@ -0,0 +1,129 @@
#include <extio.h>
// Static interrupt callback
static void onExpanderInterrupt(void *arg)
{
auto cls = (ExternalIO *)(arg);
if (!cls) // invalid args
return;
cls->extReadInterrupt();
}
ExternalIO::ExternalIO(TwoWire &i2c, std::mutex &i2c_mutex, const uint8_t int_pin) : m_i2cMutex(i2c_mutex), m_i2c(i2c), m_intPin(int_pin)
{
std::lock_guard<std::mutex> lock(m_i2cMutex);
// Attach OUT expanders on BUS
m_outMap[EXPANDER_A_OUT_ADDR] = std::make_unique<PCA9555>();
m_outMap[EXPANDER_A_OUT_ADDR]->attach(m_i2c, EXPANDER_A_OUT_ADDR);
m_outMap[EXPANDER_B_OUT_ADDR] = std::make_unique<PCA9555>();
m_outMap[EXPANDER_B_OUT_ADDR]->attach(m_i2c, EXPANDER_B_OUT_ADDR);
for (auto &[a, e] : m_outMap)
{
e->direction(PCA95x5::Direction::OUT_ALL);
e->polarity(PCA95x5::Polarity::ORIGINAL_ALL);
};
// Attach IN Expanders on Bus
m_inMap[EXPANDER_A_IN_ADDR] = std::make_unique<PCA9555>();
m_inMap[EXPANDER_A_IN_ADDR]->attach(m_i2c, EXPANDER_A_IN_ADDR);
m_inMap[EXPANDER_B_IN_ADDR] = std::make_unique<PCA9555>();
m_inMap[EXPANDER_B_IN_ADDR]->attach(m_i2c, EXPANDER_B_IN_ADDR);
for (auto &[a, e] : m_inMap)
{
e->direction(PCA95x5::Direction::IN_ALL);
e->polarity(PCA95x5::Polarity::ORIGINAL_ALL);
m_lastInputState[a] = e->read(); /// initialize input state to collect interrupts
};
}
ExternalIO::~ExternalIO() {
}
void ExternalIO::extDigitalWrite(const uint32_t mappedPin, const bool val)
{
std::lock_guard<std::mutex> lock(m_i2cMutex);
const io_t pa = map2pin(mappedPin);
if (!m_outMap.contains(pa.addr))
{
LOG_ERROR("Undefined IO Expander addr: [", pa.addr, "]");
return;
}
auto &io = m_outMap.at(pa.addr);
if (!io->write(static_cast<PCA95x5::Port::Port>(pa.pin), val ? PCA95x5::Level::H : PCA95x5::Level::L))
{
LOG_ERROR("IO Expander [", pa.addr, "] Unable to WRITE Port [", pa.pin, "] to [", val ? "HIGH" : "LOW");
LOG_ERROR("IO Expander Error [", io->i2c_error(), "]");
}
}
const bool ExternalIO::extDigitalRead(const uint32_t mappedPin)
{
std::lock_guard<std::mutex> lock(m_i2cMutex);
const io_t pa = map2pin(mappedPin);
if (!m_inMap.contains(pa.addr))
{
LOG_ERROR("Undefined IO Expander addr: [", pa.addr, "]");
return false;
}
auto &io = m_inMap.at(pa.addr);
const bool rv = io->read(static_cast<PCA95x5::Port::Port>(pa.pin)) == PCA95x5::Level::H ? true : false; // read value
const uint8_t err = io->i2c_error();
if (err)
{
LOG_ERROR("IO Expander [", pa.addr, "] Unable to READ Port [", pa.pin, "]");
LOG_ERROR("IO Expander Error [", err, "]");
}
return rv;
}
void ExternalIO::extAttachInterrupt(ExtInterruptCb cb)
{
attachInterruptArg(EXPANDER_ALL_INTERRUPT, onExpanderInterrupt, (void *)(this), FALLING);
m_extInterruptCb = cb;
}
void ExternalIO::extDetachInterrupt()
{
detachInterrupt(EXPANDER_ALL_INTERRUPT);
}
void ExternalIO::extReadInterrupt()
{
std::lock_guard<std::mutex> lock(m_i2cMutex);
disableInterrupt(EXPANDER_ALL_INTERRUPT);
// read all registers and collect
IOstate interruptState;
for (auto &[a, e] : m_inMap)
{
interruptState[a] = e->read();
}
m_lastInputState = interruptState; // restore to current values
// compare to last state to see the difference
if (m_extInterruptCb)
{
for (auto &[a, v] : interruptState)
{
if (v)
m_extInterruptCb(stat2map(a, v));
}
}
enableInterrupt(EXPANDER_ALL_INTERRUPT);
}
const ExternalIO::io_t ExternalIO::map2pin(const uint32_t mappedIO)
{
return io_t{
.addr = (uint8_t)((mappedIO >> 16) & (uint8_t)0xFF),
.pin = (uint8_t)(mappedIO && (uint32_t)0xFF),
};
}
const uint32_t ExternalIO::stat2map(const uint8_t addr, const uint16_t stat)
{
if (!stat)
return 0;
return (uint32_t)(addr << 16) | (1UL << __builtin_ctz(stat));
}

49
RotaxMonitor/src/extio.h Normal file
View File

@@ -0,0 +1,49 @@
#pragma once
#define DEBUGLOG_DEFAULT_LOG_LEVEL_DEBUG
#include <Arduino.h>
#include <DebugLog.h>
#include <PCA95x5.h>
#include <pins.h>
#include <memory>
#include <map>
class ExternalIO
{
using IOptr = std::unique_ptr<PCA9555>;
using IOmap = std::map<const uint8_t, IOptr>;
using IOstate = std::map<const uint8_t, uint16_t>;
using ExtInterruptCb = std::function<void(const uint32_t)>;
struct io_t
{
uint8_t addr;
uint8_t pin;
};
public:
ExternalIO(TwoWire &i2c, std::mutex &i2c_mutex, const uint8_t int_pin);
~ExternalIO();
void extDigitalWrite(const uint32_t mappedPin, const bool val);
const bool extDigitalRead(const uint32_t mappedPin);
void extAttachInterrupt(ExtInterruptCb cb = nullptr);
void extDetachInterrupt();
void extReadInterrupt();
private:
const io_t map2pin(const uint32_t mappedIO);
const uint32_t stat2map(const uint8_t addr, const uint16_t stat);
private:
const uint8_t m_intPin;
IOmap m_inMap;
IOmap m_outMap;
uint8_t m_intPinChanged;
IOstate m_lastInputState;
ExtInterruptCb m_extInterruptCb = nullptr;
std::mutex &m_i2cMutex;
TwoWire &m_i2c;
};

View File

@@ -4,7 +4,7 @@
// ISR (Pass return bitmask to ISR management function) // ISR (Pass return bitmask to ISR management function)
// one function for each wake up pin conncted to a trigger // one function for each wake up pin conncted to a trigger
// ===================== // =====================
void trig_isr(void *arg) void trig_isr_A(void *arg)
{ {
const int64_t time_us = esp_timer_get_time(); const int64_t time_us = esp_timer_get_time();
@@ -26,28 +26,23 @@ void trig_isr(void *arg)
case TRIG_FLAG_12P: case TRIG_FLAG_12P:
case TRIG_FLAG_12N: case TRIG_FLAG_12N:
// only on first trigger to avoid multiple firing due to noise, to be fixed with hardware debounce // only on first trigger to avoid multiple firing due to noise, to be fixed with hardware debounce
box->coils12.trig_time = time_us; box->coils12.coilTime = time_us;
xTaskNotifyFromISR(task_handle, params->flag, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); xTaskNotifyFromISR(task_handle, params->flag, eSetValueWithOverwrite, &xHigherPriorityTaskWoken);
break; break;
case TRIG_FLAG_34P: case TRIG_FLAG_34P:
case TRIG_FLAG_34N: case TRIG_FLAG_34N:
// only on first trigger to avoid multiple firing due to noise, to be fixed with hardware debounce // only on first trigger to avoid multiple firing due to noise, to be fixed with hardware debounce
box->coils34.trig_time = time_us; box->coils34.coilTime = time_us;
xTaskNotifyFromISR(task_handle, params->flag, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); xTaskNotifyFromISR(task_handle, params->flag, eSetValueWithOverwrite, &xHigherPriorityTaskWoken);
break; break;
case SPARK_FLAG_12: case SPARK_FLAG_12:
box->coils34.spark_ok = false;
box->coils12.spark_ok = true; box->coils12.sparkTime = time_us;
box->coils12.spark_time = time_us;
xTaskNotifyFromISR(task_handle, params->flag, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); xTaskNotifyFromISR(task_handle, params->flag, eSetValueWithOverwrite, &xHigherPriorityTaskWoken);
// vTaskNotifyGiveFromISR(task_handle, &xHigherPriorityTaskWoken);
break; break;
case SPARK_FLAG_34: case SPARK_FLAG_34:
box->coils12.spark_ok = false; box->coils34.sparkTime = time_us;
box->coils34.spark_ok = true;
box->coils34.spark_time = time_us;
xTaskNotifyFromISR(task_handle, params->flag, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); xTaskNotifyFromISR(task_handle, params->flag, eSetValueWithOverwrite, &xHigherPriorityTaskWoken);
// vTaskNotifyGiveFromISR(task_handle, &xHigherPriorityTaskWoken);
break; break;
default: default:
break; break;
@@ -55,4 +50,53 @@ void trig_isr(void *arg)
if (xHigherPriorityTaskWoken) if (xHigherPriorityTaskWoken)
portYIELD_FROM_ISR(); portYIELD_FROM_ISR();
} }
void trig_isr_B(void *arg)
{
const int64_t time_us = esp_timer_get_time();
// exit if invalid args
if (!arg)
return;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
isrParams *params = (isrParams *)arg;
ignitionBoxStatus *box = params->ign_stat;
TaskHandle_t task_handle = params->rt_handle_ptr;
// exit if task not running
if (!task_handle)
return;
switch (params->flag)
{
case TRIG_FLAG_12P:
case TRIG_FLAG_12N:
// only on first trigger to avoid multiple firing due to noise, to be fixed with hardware debounce
box->coils12.coilTime = time_us;
xTaskNotifyFromISR(task_handle, params->flag, eSetValueWithOverwrite, &xHigherPriorityTaskWoken);
break;
case TRIG_FLAG_34P:
case TRIG_FLAG_34N:
// only on first trigger to avoid multiple firing due to noise, to be fixed with hardware debounce
box->coils34.coilTime = time_us;
xTaskNotifyFromISR(task_handle, params->flag, eSetValueWithOverwrite, &xHigherPriorityTaskWoken);
break;
case SPARK_FLAG_12:
box->coils12.sparkTime = time_us;
xTaskNotifyFromISR(task_handle, params->flag, eSetValueWithOverwrite, &xHigherPriorityTaskWoken);
break;
case SPARK_FLAG_34:
box->coils34.sparkTime = time_us;
xTaskNotifyFromISR(task_handle, params->flag, eSetValueWithOverwrite, &xHigherPriorityTaskWoken);
break;
default:
break;
}
if (xHigherPriorityTaskWoken)
portYIELD_FROM_ISR();
}

View File

@@ -1,106 +1,17 @@
#pragma once #pragma once
// Test device Flag
// #define TEST
// Arduino Libraries // Arduino Libraries
#include <Arduino.h> #include <Arduino.h>
#include "soc/gpio_struct.h" #include "soc/gpio_struct.h"
#include <map> #include <map>
#ifndef TEST
#include "pins.h" #include "pins.h"
#else #include "datastruct.h"
#include "pins_test.h"
#endif
#define CORE_0 0 #define CORE_0 0
#define CORE_1 1 #define CORE_1 1
#define TASK_STACK 4096 // in words #define RT_TASK_STACK 4096 // in words
#define TASK_PRIORITY (configMAX_PRIORITIES - 4) // highest priority after wifi tasks #define RT_TASK_PRIORITY (configMAX_PRIORITIES - 5) // highest priority after wifi tasks
// =====================
// Event Flags (bitmask)
// =====================
static const uint32_t TRIG_FLAG_12P = (1 << 0);
static const uint32_t TRIG_FLAG_12N = (1 << 1);
static const uint32_t TRIG_FLAG_34P = (1 << 2);
static const uint32_t TRIG_FLAG_34N = (1 << 3);
static const uint32_t SPARK_FLAG_NIL = (1 << 8);
static const uint32_t SPARK_FLAG_12 = (1 << 9);
static const uint32_t SPARK_FLAG_34 = (1 << 10);
static const uint32_t SPARK_FLAG_TIMEOUT = (1 << 11);
// Spark Status
enum sparkStatus
{
SPARK_POS_OK,
SPARK_NEG_OK,
SPARK_POS_SKIP,
SPARK_NEG_SKIP,
SPARK_POS_WAIT,
SPARK_NEG_WAIT,
SPARK_POS_FAIL,
SPARK_NEG_FAIL,
SPARK_POS_UNEXPECTED,
SPARK_NEG_UNEXPECTED,
SPARK_SYNC_FAIL,
};
static const std::map<const sparkStatus, const char *> sparkStatusNames = {
{SPARK_POS_OK, "SPARK_POS_OK"},
{SPARK_NEG_OK, "SPARK_NEG_OK"},
{SPARK_POS_SKIP, "SPARK_POS_SKIP"},
{SPARK_NEG_SKIP, "SPARK_NEG_SKIP"},
{SPARK_POS_WAIT, "SPARK_POS_WAIT"},
{SPARK_NEG_WAIT, "SPARK_NEG_WAIT"},
{SPARK_POS_FAIL, "SPARK_POS_FAIL"},
{SPARK_NEG_FAIL, "SPARK_NEG_FAIL"},
{SPARK_POS_UNEXPECTED, "SPARK_POS_UNEXPECTED"},
{SPARK_NEG_UNEXPECTED, "SPARK_NEG_UNEXPECTED"},
{SPARK_SYNC_FAIL, "SPARK_SYNC_FAIL"},
};
enum softStartStatus
{
NORMAL,
SOFT_START,
ERROR,
};
const std::map<const softStartStatus, const char *> softStartStatusNames = {
{NORMAL, "NORMAL"},
{SOFT_START, "SOFT_START"},
{ERROR, "ERROR"},
};
struct coilsStatus
{
int64_t trig_time = 0;
int64_t spark_time = 0;
int64_t spark_delay = 0; // in microseconds
sparkStatus spark_status = sparkStatus::SPARK_POS_OK;
softStartStatus sstart_status = softStartStatus::NORMAL;
float peak_p_in = 0.0, peak_n_in = 0.0;
float peak_p_out = 0.0, peak_n_out = 0.0;
float trigger_spark = 0.0;
bool spark_ok = false;
uint32_t n_events = 0;
};
// Task internal Status
struct ignitionBoxStatus
{
int64_t timestamp = 0;
// coils pairs for each ignition
coilsStatus coils12;
coilsStatus coils34;
// voltage from generator
float volts_gen = 0.0;
uint32_t n_queue_errors = 0;
uint32_t adc_read_time = 0;
};
struct isrParams struct isrParams
{ {
@@ -109,4 +20,5 @@ struct isrParams
TaskHandle_t rt_handle_ptr; TaskHandle_t rt_handle_ptr;
}; };
void IRAM_ATTR trig_isr(void *arg); void IRAM_ATTR trig_isr_A(void *arg);
void IRAM_ATTR trig_isr_B(void *arg);

View File

@@ -1,35 +1,34 @@
#define DEBUGLOG_DEFAULT_LOG_LEVEL_INFO #define DEBUGLOG_DEFAULT_LOG_LEVEL_DEBUG
// Arduino Libraries // Arduino Libraries
#include <Arduino.h> #include <Arduino.h>
#include <DebugLog.h> #include <DebugLog.h>
#include <DebugLogEnable.h> #include <DebugLogEnable.h>
#include <SPI.h> #include <SPI.h>
#include <WiFi.h>
#include <ArduinoJson.h>
// Definitions // Definitions
#include <tasks.h> #include <tasks.h>
#include <channels.h>
#include <devices.h> #include <devices.h>
#include <datasave.h>
#include <webserver.h>
#include <ui.h> #include <ui.h>
#include <led.h>
// FreeRTOS directives // #define CH_A_ENABLE
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
// #define CH_B_ENABLE // #define CH_B_ENABLE
#define TEST #define CH_A_RT_ENABLE
#define CH_B_RT_ENABLE
// #define I2C_ENABLE
#define WEB_ENABLE
float freqToRPM(float freq) // Debug Defines
{ #define WIFI_SSID "AstroRotaxMonitor"
return freq * 60.0f; // 1 pulse per revolution #define WIFI_PASSWORD "maledettirotax"
} #define PSRAM_MAX 4096
#define QUEUE_MAX 128
void printTaskStats() #define HTOP_DELAY 2000
{
char buffer[1024];
vTaskGetRunTimeStats(buffer);
Serial.println(buffer);
}
void setup() void setup()
{ {
@@ -38,19 +37,44 @@ void setup()
// Setup Logger // Setup Logger
LOG_ATTACH_SERIAL(Serial); LOG_ATTACH_SERIAL(Serial);
LOG_SET_LEVEL(DebugLogLevel::LVL_INFO); LOG_SET_LEVEL(DebugLogLevel::LVL_DEBUG);
// Print Processor Info // Print Processor Info
LOG_INFO("ESP32 Chip:", ESP.getChipModel()); LOG_DEBUG("ESP32 Chip:", ESP.getChipModel());
if (psramFound()) if (psramFound())
{ {
LOG_INFO("ESP32 PSram Found"); LOG_DEBUG("ESP32 PSram Found");
LOG_INFO("ESP32 PSram:", ESP.getPsramSize()); LOG_DEBUG("ESP32 PSram:", ESP.getPsramSize());
psramInit(); psramInit();
} }
LOG_INFO("ESP32 Flash:", ESP.getFlashChipSize()); LOG_DEBUG("ESP32 Flash:", ESP.getFlashChipSize());
LOG_INFO("ESP32 Heap:", ESP.getHeapSize()); LOG_DEBUG("ESP32 Heap:", ESP.getHeapSize());
LOG_INFO("ESP32 Sketch:", ESP.getFreeSketchSpace()); LOG_DEBUG("ESP32 Sketch:", ESP.getFreeSketchSpace());
// Init Wifi station
#ifdef WEB_ENABLE
LOG_INFO("Initializing WiFi...");
WiFi.mode(WIFI_AP);
IPAddress local_IP(10, 11, 12, 1);
IPAddress gateway(10, 11, 12, 1);
IPAddress subnet(255, 255, 255, 0);
WiFi.softAPConfig(local_IP, gateway, subnet);
WiFi.setTxPower(WIFI_POWER_5dBm); // reduce wifi power
if (WiFi.softAP(WIFI_SSID, WIFI_PASSWORD))
{
LOG_INFO("WiFi AP Mode Started");
LOG_INFO("Wifi SSID:", WIFI_SSID);
LOG_INFO("Wifi Password:", WIFI_PASSWORD);
LOG_INFO("WiFi IP:" + WiFi.softAPIP().toString());
}
else
{
LOG_ERROR("Failed to start WiFi AP Mode");
LOG_ERROR("5 seconds to restart...");
vTaskDelay(pdMS_TO_TICKS(5000));
esp_restart();
}
#endif
// Initialize Interrupt pins on PICKUP detectors // Initialize Interrupt pins on PICKUP detectors
initTriggerPinsInputs(); initTriggerPinsInputs();
@@ -58,65 +82,60 @@ void setup()
initSparkPinInputs(); initSparkPinInputs();
} }
////////////////////// MAIN LOOP //////////////////////
void loop() void loop()
{ {
// global variables // global variables
RGBled led;
led.setBrightness(0.025f);
led.setStatus(RGBled::LedStatus::INIT);
Devices dev;
bool running = true; bool running = true;
static Devices dev; std::mutex fs_mutex;
LITTLEFSGuard fsGuard;
// Task handle
static TaskHandle_t trigA_TaskHandle = NULL;
static TaskHandle_t trigB_TaskHandle = NULL;
static QueueHandle_t rt_taskA_queue = xQueueCreate(10, sizeof(ignitionBoxStatus));
static QueueHandle_t rt_taskB_queue = xQueueCreate(10, sizeof(ignitionBoxStatus));
static rtTaskParams taskA_params{
.rt_running = true,
.dev = &dev,
.rt_handle_ptr = &trigA_TaskHandle,
.rt_queue = rt_taskA_queue,
.rt_int = rtTaskInterrupts{
.isr_ptr = trig_isr,
.trig_pin_12p = TRIG_PIN_A12P,
.trig_pin_12n = TRIG_PIN_A12N,
.trig_pin_34p = TRIG_PIN_A34P,
.trig_pin_34n = TRIG_PIN_A34N,
.spark_pin_12 = SPARK_PIN_A12,
.spark_pin_34 = SPARK_PIN_A34},
.rt_resets = rtTaskResets{.rst_io_12p = RST_EXT_A12P, .rst_io_12n = RST_EXT_A12N, .rst_io_34p = RST_EXT_A34P, .rst_io_34n = RST_EXT_A34N}};
LOG_INFO("Task Variables OK");
#ifdef CH_B_ENABLE
QueueHandle_t rt_taskB_queue = xQueueCreate(10, sizeof(ignitionBoxStatus));
rtTaskParams taskB_params{
.rt_running = true,
.dev = &dev,
.rt_handle_ptr = &trigB_TaskHandle,
.rt_queue = rt_taskB_queue,
.rt_int = rtTaskInterrupts{
.isr_ptr = trig_isr,
.trig_pin_12p = TRIG_PIN_B12P,
.trig_pin_12n = TRIG_PIN_B12N,
.trig_pin_34p = TRIG_PIN_B34P,
.trig_pin_34n = TRIG_PIN_B34N,
.spark_pin_12 = SPARK_PIN_B12,
.spark_pin_34 = SPARK_PIN_B34},
.rt_resets = rtTaskResets{.rst_io_12p = RST_EXT_B12P, .rst_io_12n = RST_EXT_B12N, .rst_io_34p = RST_EXT_B34P, .rst_io_34n = RST_EXT_B34N}};
#endif
//////// INIT SPI INTERFACES ////////
bool spiA_ok = true; bool spiA_ok = true;
bool spiB_ok = true; bool spiB_ok = true;
//////// INIT SPI INTERFACES ////////
// Init 2 SPI interfaces LOG_DEBUG("Init SPI Interfaces");
SPIClass SPI_A(FSPI); #ifdef CH_A_ENABLE
LOG_DEBUG("Begin Init SPI_A");
SPIClass SPI_A(HSPI);
spiA_ok = SPI_A.begin(SPI_A_SCK, SPI_A_MISO, SPI_A_MOSI); spiA_ok = SPI_A.begin(SPI_A_SCK, SPI_A_MISO, SPI_A_MOSI);
SPI_A.setDataMode(SPI_MODE1); // ADS1256 requires SPI mode 1 SPI_A.setDataMode(SPI_MODE1); // ADS1256 requires SPI mode 1
#ifndef TEST LOG_DEBUG("Init SPI_A -> OK");
SPIClass SPI_B(HSPI); delay(100);
LOG_DEBUG("Begin Init ADC_A");
ADS1256 ADC_A(ADC_A_DRDY, ADS1256::PIN_UNUSED, ADS1256::PIN_UNUSED, ADC_A_CS, 2.5, &SPI_A);
ADC_A.InitializeADC();
ADC_A.setPGA(PGA_1);
ADC_A.setDRATE(DRATE_7500SPS);
dev.m_adc_a = &ADC_A;
dev.m_spi_a = &SPI_A;
LOG_DEBUG("Init ADC_A -> OK");
delay(100);
#endif
#ifdef CH_B_ENABLE
LOG_DEBUG("Begin Init SPI_B");
SPIClass SPI_B(FSPI);
spiB_ok = SPI_B.begin(SPI_B_SCK, SPI_B_MISO, SPI_B_MOSI); spiB_ok = SPI_B.begin(SPI_B_SCK, SPI_B_MISO, SPI_B_MOSI);
SPI_B.setDataMode(SPI_MODE1); // ADS1256 requires SPI mode 1 SPI_B.setDataMode(SPI_MODE1); // ADS1256 requires SPI mode 1
#endif LOG_DEBUG("Init SPI_B -> OK");
delay(100);
LOG_DEBUG("Begin Init ADC_B");
ADS1256 ADC_B(ADC_B_DRDY, ADS1256::PIN_UNUSED, ADS1256::PIN_UNUSED, ADC_B_CS, 2.5, &SPI_B);
ADC_B.InitializeADC();
ADC_B.setPGA(PGA_1);
ADC_B.setDRATE(DRATE_7500SPS);
dev.m_adc_b = &ADC_B;
dev.m_spi_b = &SPI_B;
LOG_DEBUG("Init ADC_B -> OK");
delay(100);
#endif
if (!spiA_ok || !spiB_ok) if (!spiA_ok || !spiB_ok)
{ {
LOG_ERROR("Unable to Initialize SPI Busses"); LOG_ERROR("Unable to Initialize SPI Busses");
@@ -124,121 +143,209 @@ void loop()
vTaskDelay(pdMS_TO_TICKS(5000)); vTaskDelay(pdMS_TO_TICKS(5000));
esp_restart(); esp_restart();
} }
LOG_INFO("Init SPI OK");
// Init ADC_A LOG_DEBUG("Init SPI -> OK");
dev.adc_a = new ADS1256(ADC_A_DRDY, ADS1256::PIN_UNUSED, ADC_A_SYNC, ADC_A_CS, 2.5, &SPI_A);
dev.adc_a->InitializeADC();
dev.adc_a->setPGA(PGA_1);
dev.adc_a->setDRATE(DRATE_7500SPS);
#ifndef TEST //////// INIT I2C INTERFACES ////////
// Init ADC_B #ifdef I2C_ENABLE
dev.adc_a = new ADS1256(ADC_B_DRDY, ADC_B_RST, ADC_B_SYNC, ADC_B_CS, 2.5, &SPI_B); LOG_DEBUG("Init I2C Interfaces");
dev.adc_a->InitializeADC(); bool i2c_ok = true;
dev.adc_a->setPGA(PGA_1); i2c_ok = Wire.begin(SDA, SCL, 100000);
dev.adc_a->setDRATE(DRATE_1000SPS); if (!i2c_ok)
#endif
LOG_INFO("Init ADC OK");
// Ignition A on Core 0
auto ignA_task_success = pdPASS;
ignA_task_success = xTaskCreatePinnedToCore(
rtIgnitionTask,
"rtIgnitionTask_boxA",
TASK_STACK,
(void *)&taskA_params,
TASK_PRIORITY,
&trigA_TaskHandle,
CORE_0);
// Ignition B on Core 1
auto ignB_task_success = pdPASS;
#ifdef CH_B_ENABLE
ignB_task_success = xTaskCreatePinnedToCore(
rtIgnitionTask,
"rtIgnitionTask_boxB",
TASK_STACK,
(void *)&taskB_params,
TASK_PRIORITY, // priorità leggermente più alta
&trigB_TaskHandle,
CORE_1);
#endif
if ((ignA_task_success && ignB_task_success) != pdPASS)
{ {
LOG_ERROR("Unble to initialize ISR task"); LOG_ERROR("Unable to Initialize I2C Bus");
LOG_ERROR("5 seconds to restart..."); LOG_ERROR("5 seconds to restart...");
vTaskDelay(pdMS_TO_TICKS(5000)); vTaskDelay(pdMS_TO_TICKS(5000));
esp_restart(); esp_restart();
} }
LOG_DEBUG("Init I2c ok");
LOG_INFO("Real Time Tasks A & B initialized"); // Init IO Expanders
ExternalIO extIo(Wire, dev.m_i2c_mutex, EXPANDER_ALL_INTERRUPT);
dev.m_ext_io = &extIo;
#endif
////////////////////// MAIN LOOP ////////////////////// //////// INIT REALTIME TASKS PARAMETERS ////////
clearScreen(); #ifdef CH_A_RT_ENABLE
setCursor(0, 0); const rtIgnitionTask::rtTaskParams taskA_params{
ignitionBoxStatus ignA; .rt_running = true,
int64_t last = esp_timer_get_time(); .name = "rtIgnTask_A",
uint32_t missed_firings12 = 0; .rt_stack_size = RT_TASK_STACK,
uint32_t missed_firings34 = 0; .rt_priority = RT_TASK_PRIORITY,
uint32_t counter = 0; .rt_int = rtIgnitionTask::rtTaskInterruptParams{
.isrPtr = &trig_isr_A,
.trigPin_12p = TRIG_PIN_A12P,
.trigPin_12n = TRIG_PIN_A12N,
.trigPin_34p = TRIG_PIN_A34P,
.trigPin_34n = TRIG_PIN_A34N,
.sparkPin_12 = SPARK_PIN_A12,
.sparkPin_34 = SPARK_PIN_A34},
.rt_io = rtIgnitionTask::rtTaskIOParams{
.pot_cs_12 = POT_CS_A12,
.pot_cs_34 = POT_CS_A34,
.ss_force = SS_FORCE_A,
.ss_inhibit_12 = SS_INIBHIT_A12,
.ss_inhibit_34 = SS_INHIBIT_A34,
.sh_disch_12 = SH_DISCH_A12,
.sh_disch_34 = SH_DISCH_A34,
.sh_arm_12 = SH_ARM_A12,
.sh_arm_34 = SH_ARM_A34,
.relay_in_12 = RELAY_IN_A12,
.relay_in_34 = RELAY_OUT_A12,
.relay_out_12 = RELAY_IN_A34,
.relay_out_34 = RELAY_OUT_A34,
},
.rt_queue = nullptr,
.dev = &dev};
#endif
#ifdef CH_B_RT_ENABLE
const rtIgnitionTask::rtTaskParams taskB_params{
.rt_running = true,
.name = "rtIgnTask_B",
.rt_stack_size = RT_TASK_STACK,
.rt_priority = RT_TASK_PRIORITY,
.rt_int = rtIgnitionTask::rtTaskInterruptParams{
.isrPtr = &trig_isr_B,
.trigPin_12p = TRIG_PIN_B12P,
.trigPin_12n = TRIG_PIN_B12N,
.trigPin_34p = TRIG_PIN_B34P,
.trigPin_34n = TRIG_PIN_B34N,
.sparkPin_12 = SPARK_PIN_B12,
.sparkPin_34 = SPARK_PIN_B34},
.rt_io = rtIgnitionTask::rtTaskIOParams{
.pot_cs_12 = POT_CS_B12,
.pot_cs_34 = POT_CS_B34,
.ss_force = SS_FORCE_B,
.ss_inhibit_12 = SS_INIBHIT_B12,
.ss_inhibit_34 = SS_INHIBIT_B34,
.sh_disch_12 = SH_DISCH_B12,
.sh_disch_34 = SH_DISCH_B34,
.sh_arm_12 = SH_ARM_B12,
.sh_arm_34 = SH_ARM_B34,
.relay_in_12 = RELAY_IN_B12,
.relay_in_34 = RELAY_OUT_B12,
.relay_out_12 = RELAY_IN_B34,
.relay_out_34 = RELAY_OUT_B34,
},
.rt_queue = nullptr,
.dev = &dev};
#endif
while (running) //////// SPAWN REALTIME TASKS ////////
bool tasK_A_rt = true;
bool task_B_rt = true;
BaseType_t ignA_task_success = pdPASS;
BaseType_t ignB_task_success = pdPASS;
#ifdef CH_A_RT_ENABLE
auto task_A = rtIgnitionTask(taskA_params, PSRAM_MAX, QUEUE_MAX, CORE_0, fs_mutex);
ignA_task_success = task_A.getStatus() == rtIgnitionTask::OK ? pdPASS : pdFAIL;
tasK_A_rt = task_A.start();
delay(100);
#endif
#ifdef CH_B_RT_ENABLE
auto task_B = rtIgnitionTask(taskB_params, PSRAM_MAX, QUEUE_MAX, CORE_1, fs_mutex);
ignB_task_success = task_B.getStatus() == rtIgnitionTask::OK ? pdPASS : pdFAIL;
task_B_rt = task_B.start();
delay(100);
#endif
// Ignition A on Core 0
if (ignA_task_success != pdPASS || ignB_task_success != pdPASS)
{ {
if (xQueueReceive(rt_taskA_queue, &ignA, pdMS_TO_TICKS(1000)) == pdTRUE) LOG_ERROR("Unable to initialize ISR task");
{ LOG_ERROR("5 seconds to restart...");
float freq = (esp_timer_get_time() - last) / 1000000.0f; // in seconds vTaskDelay(pdMS_TO_TICKS(5000));
freq = freq > 0 ? 1.0f / freq : 0; // Calculate frequency (Hz) esp_restart();
last = esp_timer_get_time(); }
if (tasK_A_rt != true || task_B_rt != true)
if (ignA.coils12.spark_status == sparkStatus::SPARK_POS_FAIL || ignA.coils12.spark_status == sparkStatus::SPARK_NEG_FAIL) {
missed_firings12++; led.setStatus(RGBled::LedStatus::ERROR);
if (ignA.coils34.spark_status == sparkStatus::SPARK_POS_FAIL || ignA.coils34.spark_status == sparkStatus::SPARK_NEG_FAIL) LOG_ERROR("Unable to start realtime tasks");
missed_firings34++; }
else
clearScreen(); {
setCursor(0, 0); LOG_DEBUG("Real Time Tasks A & B initialized");
printField("++ Timestamp", (uint32_t)ignA.timestamp); led.setStatus(RGBled::LedStatus::OK);
Serial.println("========== Coils 12 =============");
printField("Events", (uint32_t)ignA.coils12.n_events);
printField("Missed Firing", missed_firings12);
printField("Spark Dly", (uint32_t)ignA.coils12.spark_delay);
printField("Spark Sts", sparkStatusNames.at(ignA.coils12.spark_status));
printField("Peak P_IN", ignA.coils12.peak_p_in);
printField("Peak N_IN", ignA.coils12.peak_n_in);
printField("Peak P_OUT", ignA.coils12.peak_p_out);
printField("Peak N_OUT", ignA.coils12.peak_n_out);
printField("Soft Start ", softStartStatusNames.at(ignA.coils12.sstart_status));
Serial.println("========== Coils 34 =============");
printField("Events", (uint32_t)ignA.coils34.n_events);
printField("Missed Firing", missed_firings34);
printField("Spark Dly", (uint32_t)ignA.coils34.spark_delay);
printField("Spark Sts", sparkStatusNames.at(ignA.coils34.spark_status));
printField("Peak P_IN", ignA.coils34.peak_p_in);
printField("Peak N_IN", ignA.coils34.peak_n_in);
printField("Peak P_OUT", ignA.coils34.peak_p_out);
printField("Peak N_OUT", ignA.coils34.peak_n_out);
printField("Soft Start ", softStartStatusNames.at(ignA.coils34.sstart_status));
Serial.println("========== END =============");
Serial.println();
printField("Engine RPM", freqToRPM(freq));
printField("ADC Read Time", (uint32_t)ignA.adc_read_time);
printField("Queue Errors", (uint32_t)ignA.n_queue_errors);
}
else
{
Serial.println("Waiting for data... ");
delay(500);
}
} }
if (trigA_TaskHandle) //////// SPAWN WEBSERVER and WEBSOCKET ////////
vTaskDelete(trigA_TaskHandle); ArduinoJson::JsonDocument json_data;
if (trigB_TaskHandle) bool data_a = false, data_b = false;
vTaskDelete(trigB_TaskHandle); #ifdef WEB_ENABLE
////////////////////// MAIN LOOP ////////////////////// AstroWebServer webPage(80, LittleFS);
} delay(100);
#ifdef CH_A_RT_ENABLE
task_A.onMessage([&webPage, &json_data, &data_a](ignitionBoxStatusFiltered sts)
{
json_data["box_a"] = sts.toJson();
data_a = true; });
#endif
#ifdef CH_B_RT_ENABLE
task_B.onMessage([&webPage, &json_data, &data_b](ignitionBoxStatusFiltered sts)
{
json_data["box_b"] = sts.toJson();
data_b = true; });
#endif
webPage.registerWsCommand("saveEnable", [&task_A, &task_B](const ArduinoJson::JsonDocument &doc) {
if(!doc["params"].is<ArduinoJson::JsonObject>()) return;
if(!doc["filename_a"].is<std::string>() ||!doc["filename_b"].is<std::string>()){
LOG_ERROR("saveEnable invalid or missing filenames");
return;
}
task_A.enableSave(true, doc["filename_a"].as<std::string>());
task_B.enableSave(true, doc["filename_a"].as<std::string>());
return; });
webPage.registerWsCommand("saveDisable", [&task_A, &task_B](const ArduinoJson::JsonDocument &doc) {
task_A.enableSave(false, "");
task_B.enableSave(false, ""); });
webPage.registerWsCommand("downloadHistory", [](const ArduinoJson::JsonDocument &doc) {
LOG_WARN("Command downloadHistory not Implemented");
});
webPage.registerWsCommand("clearHistory", [](const ArduinoJson::JsonDocument &doc) {
LOG_WARN("Command clearHistory not Implemented");
});
webPage.registerWsCommand("startTest", [](const ArduinoJson::JsonDocument &doc) {
LOG_WARN("Command startTest not Implemented");
});
webPage.registerWsCommand("stopTest", [](const ArduinoJson::JsonDocument &doc) {
LOG_WARN("Command stopTest not Implemented");
});
#endif
uint32_t monitor_loop = millis();
uint32_t data_loop = monitor_loop;
//////////////// INNER LOOP /////////////////////
while (running)
{
uint32_t this_loop = millis();
if (this_loop - monitor_loop > HTOP_DELAY)
{
clearScreen();
printRunningTasksMod(Serial);
monitor_loop = millis();
}
#ifdef WEB_ENABLE
if ((data_a && data_b) || ((this_loop - data_loop > 500) && (data_b || data_b)))
{
webPage.sendWsData(json_data.as<String>());
json_data.clear();
data_a = data_b = false;
data_loop = millis();
}
vTaskDelay(pdMS_TO_TICKS(10));
#endif
} //////////////// INNER LOOP /////////////////////
} ////////////////////// MAIN LOOP //////////////////////

View File

@@ -4,19 +4,19 @@
// ===================== // =====================
// USB (RISERVATA) // USB (RISERVATA)
// ===================== // =====================
#define USB_DM 19 #define USB_DM 19
#define USB_DP 20 #define USB_DP 20
// ===================== // =====================
// UART DEBUG (RISERVATA) // UART DEBUG (RISERVATA)
// ===================== // =====================
#define UART_TX 43 #define UART_TX 43
#define UART_RX 44 #define UART_RX 44
// ===================== // =====================
// RGB Led // RGB Led
// ===================== // =====================
#define LED 48 #define LED 48
// ===================== // =====================
// STRAPPING CRITICI (NON USARE) // STRAPPING CRITICI (NON USARE)
@@ -26,85 +26,133 @@
// ===================== // =====================
// SPI BUS ADC1 (VSPI) // SPI BUS ADC1 (VSPI)
// ===================== // =====================
#define SPI_A_MOSI 11 #define SPI_A_MOSI 10
#define SPI_A_MISO 13 #define SPI_A_SCK 11
#define SPI_A_SCK 12 #define SPI_A_MISO 12
// ===================== // =====================
// SPI BUS ADC2 (HSPI) // SPI BUS ADC2 (HSPI)
// ===================== // =====================
#define SPI_B_MOSI 35 #define SPI_B_MOSI 17
#define SPI_B_MISO 37 #define SPI_B_SCK 18
#define SPI_B_SCK 36 #define SPI_B_MISO 8
// ===================== // =====================
// I2C BUS (PCA9555) // I2C BUS (PCA9555)
// ===================== // =====================
#define SDA 8 #define SDA 21
#define SCL 9 #define SCL 47
// ===================== // =====================
// ADC CONTROL // ADC CONTROL
// ===================== // =====================
#define ADC_A_CS 4 #define ADC_A_CS 14
#define ADC_A_DRDY 5 #define ADC_A_DRDY 13
#define ADC_A_SYNC 6
#define ADC_B_CS 14 #define ADC_B_CS 3
#define ADC_B_DRDY 15 #define ADC_B_DRDY 46
#define ADC_B_SYNC 16
// =====================
// DIGITAL POT
// =====================
#define POT_A_CS 7
#define POT_B_CS 17
// ===================== // =====================
// TRIGGER INPUT INTERRUPTS // TRIGGER INPUT INTERRUPTS
// ===================== // =====================
#define TRIG_PIN_A12P 18 #define TRIG_PIN_A12P 6
#define TRIG_PIN_A12N 21 #define TRIG_PIN_A12N 7
#define TRIG_PIN_A34P 1 #define TRIG_PIN_A34P 15
#define TRIG_PIN_A34N 2 #define TRIG_PIN_A34N 16
#define TRIG_PIN_B12P 38 #define TRIG_PIN_B12P 42
#define TRIG_PIN_B12N 39 #define TRIG_PIN_B12N 41
#define TRIG_PIN_B34P 40 #define TRIG_PIN_B34P 40
#define TRIG_PIN_B34N 41 #define TRIG_PIN_B34N 39
// ===================== // =====================
// SPARK DETECT INPUTS // SPARK DETECT INPUTS
// ===================== // =====================
#define SPARK_PIN_A12 42 #define SPARK_PIN_A12 4
#define SPARK_PIN_A34 45 // OK (strapping ma consentito) 45 #define SPARK_PIN_A34 5
#define SPARK_PIN_B12 46 // OK (strapping ma consentito) 46 #define SPARK_PIN_B12 1
#define SPARK_PIN_B34 47 #define SPARK_PIN_B34 2
// +++++++++++++++++++++
// MACRO TO COMBINE PIN NUMBER AND ADDRESS
#define PIN2ADDR(p, a) ((1UL << p) | ((uint32_t)(a) << 16))
// +++++++++++++++++++++
// ===================== // =====================
// PCA9555 (I2C EXPANDER) // PCA9555 I/O EXPANDER INTERRUPT (Common)
// ===================== // =====================
#define EXPANDER_ALL_INTERRUPT 45
// --- RESET LINES --- // =====================
#define RST_EXT_A12P 0 // PCA9555 I/O EXPANDER BOX_A (OUT)
#define RST_EXT_A12N 1 // =====================
#define RST_EXT_A34P 2 #define EXPANDER_A_OUT_ADDR 0x7F
#define RST_EXT_A34N 3
#define RST_EXT_B12P 4 // --- DIGITAL POT CHIP SELECT LINES ---
#define RST_EXT_B12N 5 #define POT_CS_A12 PIN2ADDR(0, EXPANDER_A_OUT_ADDR)
#define RST_EXT_B34P 6 #define POT_CS_A34 PIN2ADDR(1, EXPANDER_A_OUT_ADDR)
#define RST_EXT_B34N 7
// --- SOFT START FORCE LINES ---
#define SS_FORCE_A PIN2ADDR(2, EXPANDER_A_OUT_ADDR)
#define SS_INIBHIT_A12 PIN2ADDR(3, EXPANDER_A_OUT_ADDR)
#define SS_INHIBIT_A34 PIN2ADDR(4, EXPANDER_A_OUT_ADDR)
// --- SAMPLE AND HOLD ARM AND DISCHARGE ---
#define SH_DISCH_A12 PIN2ADDR(5, EXPANDER_A_OUT_ADDR)
#define SH_DISCH_A34 PIN2ADDR(6, EXPANDER_A_OUT_ADDR)
#define SH_ARM_A12 PIN2ADDR(7, EXPANDER_A_OUT_ADDR)
#define SH_ARM_A34 PIN2ADDR(8, EXPANDER_A_OUT_ADDR)
// --- RELAY --- // --- RELAY ---
#define A_EXT_RELAY 8 #define RELAY_IN_A12 PIN2ADDR(9, EXPANDER_A_OUT_ADDR)
#define B_EXT_RELAY 9 #define RELAY_OUT_A12 PIN2ADDR(10, EXPANDER_A_OUT_ADDR)
#define RELAY_IN_A34 PIN2ADDR(11, EXPANDER_A_OUT_ADDR)
#define RELAY_OUT_A34 PIN2ADDR(12, EXPANDER_A_OUT_ADDR)
// --- STATUS / BUTTON --- // =====================
#define BTN_3 10 // PCA9555 I/O EXPANDER BOX_A (IN)
#define BTN_4 11 // =====================
#define STA_1 12 #define EXPANDER_A_IN_ADDR 0x7F
#define STA_2 13
#define STA_3 14 #define SS_A12_ON PIN2ADDR(0, EXPANDER_A_IN_ADDR)
#define STA_4 15 #define SS_A12_OFF PIN2ADDR(1, EXPANDER_A_IN_ADDR)
#define SS_A34_ON PIN2ADDR(2, EXPANDER_A_IN_ADDR)
#define SS_A34_OFF PIN2ADDR(3, EXPANDER_A_IN_ADDR)
// =====================
// PCA9555 I/O EXPANDER BOX_B (OUT)
// =====================
#define EXPANDER_B_OUT_ADDR 0x7F
// --- DIGITAL POT CHIP SELECT LINES ---
#define POT_CS_B12 PIN2ADDR(0, EXPANDER_B_OUT_ADDR)
#define POT_CS_B34 PIN2ADDR(1, EXPANDER_B_OUT_ADDR)
// --- SOFT START FORCE LINES ---
#define SS_FORCE_B PIN2ADDR(2, EXPANDER_B_OUT_ADDR)
#define SS_INIBHIT_B12 PIN2ADDR(3, EXPANDER_B_OUT_ADDR)
#define SS_INHIBIT_B34 PIN2ADDR(4, EXPANDER_B_OUT_ADDR)
// --- SAMPLE AND HOLD ARM AND DISCHARGE ---
#define SH_DISCH_B12 PIN2ADDR(5, EXPANDER_B_OUT_ADDR)
#define SH_DISCH_B34 PIN2ADDR(6, EXPANDER_B_OUT_ADDR)
#define SH_ARM_B12 PIN2ADDR(7, EXPANDER_B_OUT_ADDR)
#define SH_ARM_B34 PIN2ADDR(8, EXPANDER_B_OUT_ADDR)
// --- RELAY ---
#define RELAY_IN_B12 PIN2ADDR(9, EXPANDER_B_OUT_ADDR)
#define RELAY_OUT_B12 PIN2ADDR(10, EXPANDER_B_OUT_ADDR)
#define RELAY_IN_B34 PIN2ADDR(11, EXPANDER_B_OUT_ADDR)
#define RELAY_OUT_B34 PIN2ADDR(12, EXPANDER_B_OUT_ADDR)
// =====================
// PCA9555 I/O EXPANDER BOX_B (IN)
// =====================
#define EXPANDER_B_IN_ADDR 0x7F
#define SS_B12_ON PIN2ADDR(0, EXPANDER_B_IN_ADDR)
#define SS_B12_OFF PIN2ADDR(1, EXPANDER_B_IN_ADDR)
#define SS_B34_ON PIN2ADDR(2, EXPANDER_B_IN_ADDR)
#define SS_B34_OFF PIN2ADDR(3, EXPANDER_B_IN_ADDR)
// Init Pin Functions // Init Pin Functions
inline void initTriggerPinsInputs() inline void initTriggerPinsInputs()

View File

@@ -1,84 +0,0 @@
#pragma once
#include <Arduino.h>
// =====================
// UART DEBUG
// =====================
#define UART_TX 1 // TX0 (USB seriale)
#define UART_RX 3 // RX0
// =====================
// SPI BUS
// =====================
#define SPI_A_MOSI 23
#define SPI_A_MISO 19
#define SPI_A_SCK 18
// =====================
// I2C BUS
// =====================
#define SDA 21
#define SCL 22
// =====================
// ADC CONTROL (SPI + interrupt safe)
// =====================
#define ADC_A_CS 5 // chip select
#define ADC_A_DRDY 34 // input only + interrupt perfetto
#define ADC_A_RST 27 // output
#define ADC_A_SYNC 26 // output
// =====================
// DIGITAL OUT
// =====================
#define POT_A_CS 25
#define POT_B_CS 33
// =====================
// TRIGGER INPUT INTERRUPTS
// =====================
#define TRIG_PIN_A12P 35
#define TRIG_PIN_A12N 32
#define TRIG_PIN_A34P 39
#define TRIG_PIN_A34N 36
// =====================
// SPARK DETECT INTERRUPTS
// =====================
#define SPARK_PIN_A12 4
#define SPARK_PIN_A34 2
// =====================
// PCA9555 (I2C EXPANDER)
// =====================
// --- RESET LINES ---
#define RST_EXT_A12P 0
#define RST_EXT_A12N 1
#define RST_EXT_A34P 2
#define RST_EXT_A34N 3
// --- RELAY ---
#define A_EXT_RELAY 8
// Init Pin Functions
inline void initTriggerPinsInputs()
{
pinMode(TRIG_PIN_A12P, INPUT_PULLDOWN);
pinMode(TRIG_PIN_A12N, INPUT_PULLDOWN);
pinMode(TRIG_PIN_A34P, INPUT_PULLDOWN);
pinMode(TRIG_PIN_A34N, INPUT_PULLDOWN);
}
inline void initSparkPinInputs()
{
pinMode(SPARK_PIN_A12, INPUT_PULLDOWN);
pinMode(SPARK_PIN_A34, INPUT_PULLDOWN);
}

View File

@@ -0,0 +1,27 @@
#pragma once
#include <vector>
#include "esp_heap_caps.h"
// Allocator custom per PSRAM
template <typename T>
struct PSRAMAllocator {
using value_type = T;
PSRAMAllocator() noexcept {}
template <typename U>
PSRAMAllocator(const PSRAMAllocator<U>&) noexcept {}
T* allocate(std::size_t n) {
void* ptr = heap_caps_malloc(n * sizeof(T), MALLOC_CAP_SPIRAM);
if (!ptr) {
throw std::bad_alloc();
}
return static_cast<T*>(ptr);
}
void deallocate(T* p, std::size_t) noexcept {
heap_caps_free(p);
}
};

View File

@@ -1,13 +1,32 @@
#include "tasks.h" #include "tasks.h"
#include <esp_timer.h> #include <esp_timer.h>
#include <datasave.h>
#include <mutex>
//// GLOBAL STATIC FUNCTIONS
// Timeout callback for microsecond precision // Timeout callback for microsecond precision
void spark_timeout_callback(void* arg) { void IRAM_ATTR spark_timeout_callback(void *arg)
{
TaskHandle_t handle = (TaskHandle_t)arg; TaskHandle_t handle = (TaskHandle_t)arg;
xTaskNotify(handle, SPARK_FLAG_TIMEOUT, eSetValueWithOverwrite); xTaskNotify(handle, SPARK_FLAG_TIMEOUT, eSetValueWithOverwrite);
} }
void rtIgnitionTask(void *pvParameters) // Manages queue receive, save data and callback to external tasks for communication
void rtIgnitionTask::rtIgnitionTask_manager(void *pvParameters)
{
rtIgnitionTask *cls = (rtIgnitionTask *)pvParameters;
auto last_loop = millis();
uint32_t count(0);
while (cls->m_running)
{
cls->run();
vTaskDelay(pdMS_TO_TICKS(1));
}
}
// Static task function
void rtIgnitionTask::rtIgnitionTask_realtime(void *pvParameters)
{ {
// Invalid real time rt_task_ptr parameters, exit immediate // Invalid real time rt_task_ptr parameters, exit immediate
@@ -16,189 +35,158 @@ void rtIgnitionTask(void *pvParameters)
LOG_ERROR("Null rt_task_ptr parameters"); LOG_ERROR("Null rt_task_ptr parameters");
vTaskDelete(NULL); vTaskDelete(NULL);
} }
LOG_INFO("rtTask Params OK");
// Task Parameters and Devices // Task Parameters and Devices
rtTaskParams *params = (rtTaskParams *)pvParameters; const rtTaskParams *params = (const rtTaskParams *)pvParameters;
const rtTaskInterrupts rt_int = params->rt_int; // copy to avoid external override const rtTaskInterruptParams rtInterrupts = params->rt_int; // copy to avoid external override
const rtTaskResets rt_rst = params->rt_resets; // copy to avoid external override const rtTaskIOParams rtResets = params->rt_io; // copy to avoid external override
QueueHandle_t rt_queue = params->rt_queue; QueueHandle_t rtQueue = params->rt_queue;
TaskHandle_t rt_handle_ptr = *params->rt_handle_ptr;
Devices *dev = params->dev; Devices *dev = params->dev;
ADS1256 *adc = dev->adc_a; ExternalIO *io = dev->m_ext_io;
PCA9555 *io = dev->io; ADS1256 *adc = params->name == "rtIgnTask_A" ? dev->m_adc_a : dev->m_adc_b;
std::mutex &spi_mutex = params->name == "rtIgnTask_A" ? dev->m_spi_a_mutex : dev->m_spi_b_mutex;
ignitionBoxStatus ign_box_sts; // Geta task name and additiona info for debug messages
TaskStatus_t rtTaskInfo;
vTaskGetInfo(NULL, &rtTaskInfo, pdFALSE, eInvalid);
LOG_INFO("rtTask Params OK [", params->name.c_str(), "]");
// Status of ignition box for this task, to be used locally and passed to isr to get timing
ignitionBoxStatus ignBoxStatus;
// Variables for ISR, static to be fixed in memory locations // Variables for ISR, static to be fixed in memory locations
static isrParams isr_params_t12p{ isrParams isrParams_t12p{
.flag = TRIG_FLAG_12P, .flag = TRIG_FLAG_12P,
.ign_stat = &ign_box_sts, .ign_stat = &ignBoxStatus,
.rt_handle_ptr = rt_handle_ptr}; .rt_handle_ptr = rtTaskInfo.xHandle};
static isrParams isr_params_t12n{ isrParams isrParams_t12n{
.flag = TRIG_FLAG_12N, .flag = TRIG_FLAG_12N,
.ign_stat = &ign_box_sts, .ign_stat = &ignBoxStatus,
.rt_handle_ptr = rt_handle_ptr}; .rt_handle_ptr = rtTaskInfo.xHandle};
static isrParams isr_params_t34p{ isrParams isrParams_t34p{
.flag = TRIG_FLAG_34P, .flag = TRIG_FLAG_34P,
.ign_stat = &ign_box_sts, .ign_stat = &ignBoxStatus,
.rt_handle_ptr = rt_handle_ptr}; .rt_handle_ptr = rtTaskInfo.xHandle};
static isrParams isr_params_t34n{ isrParams isrParams_t34n{
.flag = TRIG_FLAG_34N, .flag = TRIG_FLAG_34N,
.ign_stat = &ign_box_sts, .ign_stat = &ignBoxStatus,
.rt_handle_ptr = rt_handle_ptr}; .rt_handle_ptr = rtTaskInfo.xHandle};
static isrParams isr_params_sp12{ isrParams isrParams_sp12{
.flag = SPARK_FLAG_12, .flag = SPARK_FLAG_12,
.ign_stat = &ign_box_sts, .ign_stat = &ignBoxStatus,
.rt_handle_ptr = rt_handle_ptr}; .rt_handle_ptr = rtTaskInfo.xHandle};
static isrParams isr_params_sp34{ isrParams isrParams_sp34{
.flag = SPARK_FLAG_34, .flag = SPARK_FLAG_34,
.ign_stat = &ign_box_sts, .ign_stat = &ignBoxStatus,
.rt_handle_ptr = rt_handle_ptr}; .rt_handle_ptr = rtTaskInfo.xHandle};
LOG_INFO("rtTask ISR Params OK");
// Create esp_timer for microsecond precision timeout // Create esp_timer for microsecond precision timeout
esp_timer_handle_t timeout_timer; esp_timer_handle_t timeoutTimer;
esp_timer_create_args_t timer_args = { esp_timer_create_args_t timeoutTimerArgs = {
.callback = spark_timeout_callback, .callback = spark_timeout_callback,
.arg = (void*)rt_handle_ptr, .arg = (void *)rtTaskInfo.xHandle,
.dispatch_method = ESP_TIMER_TASK, .dispatch_method = ESP_TIMER_TASK,
.name = "spark_timeout" .name = "spark_timeout"};
}; if (esp_timer_create(&timeoutTimerArgs, &timeoutTimer) != ESP_OK)
esp_timer_create(&timer_args, &timeout_timer); {
LOG_INFO("rtTask [", params->name.c_str(), "] Fail to allocate timeoutTimer");
vTaskDelete(NULL);
}
// Attach Pin Interrupts // Attach Pin Interrupts
attachInterruptArg(digitalPinToInterrupt(rt_int.trig_pin_12p), rt_int.isr_ptr, (void *)&isr_params_t12p, RISING); attachInterruptArg(digitalPinToInterrupt(rtInterrupts.trigPin_12p), rtInterrupts.isrPtr, (void *)&isrParams_t12p, RISING);
attachInterruptArg(digitalPinToInterrupt(rt_int.trig_pin_12n), rt_int.isr_ptr, (void *)&isr_params_t12n, RISING); attachInterruptArg(digitalPinToInterrupt(rtInterrupts.trigPin_12n), rtInterrupts.isrPtr, (void *)&isrParams_t12n, RISING);
attachInterruptArg(digitalPinToInterrupt(rt_int.trig_pin_34p), rt_int.isr_ptr, (void *)&isr_params_t34p, RISING); attachInterruptArg(digitalPinToInterrupt(rtInterrupts.trigPin_34p), rtInterrupts.isrPtr, (void *)&isrParams_t34p, RISING);
attachInterruptArg(digitalPinToInterrupt(rt_int.trig_pin_34n), rt_int.isr_ptr, (void *)&isr_params_t34n, RISING); attachInterruptArg(digitalPinToInterrupt(rtInterrupts.trigPin_34n), rtInterrupts.isrPtr, (void *)&isrParams_t34n, RISING);
attachInterruptArg(digitalPinToInterrupt(rt_int.spark_pin_12), rt_int.isr_ptr, (void *)&isr_params_sp12, RISING); attachInterruptArg(digitalPinToInterrupt(rtInterrupts.sparkPin_12), rtInterrupts.isrPtr, (void *)&isrParams_sp12, RISING);
attachInterruptArg(digitalPinToInterrupt(rt_int.spark_pin_34), rt_int.isr_ptr, (void *)&isr_params_sp34, RISING); attachInterruptArg(digitalPinToInterrupt(rtInterrupts.sparkPin_34), rtInterrupts.isrPtr, (void *)&isrParams_sp34, RISING);
LOG_INFO("rtTask ISR Attach OK"); LOG_INFO("rtTask ISR Attach OK [", params->name.c_str(), "]");
// Compute Reset Pin Bitmask
const uint16_t rst_bitmask = (1 << rt_rst.rst_io_12p) |
(1 << rt_rst.rst_io_12n) |
(1 << rt_rst.rst_io_34p) |
(1 << rt_rst.rst_io_34n);
LOG_WARN("rtTask Init Correct");
// Global rt_task_ptr variables // Global rt_task_ptr variables
bool first_cycle = true; bool firstCycle = true;
bool cycle12 = false; bool cycle12 = false;
bool cycle34 = false; bool cycle34 = false;
int64_t lastCycleTime = 0;
uint32_t nErrors = 0;
while (params->rt_running) while (params->rt_running)
{ {
uint32_t pickup_flag = 0; uint32_t pickupFlag = 0;
uint32_t spark_flag = 0; uint32_t sparkFlag = 0;
// WAIT FOR PICKUP SIGNAL // WAIT FOR PICKUP SIGNAL
xTaskNotifyWait( xTaskNotifyWait(
0x00, // non pulire all'ingresso 0x00, // non pulire all'ingresso
ULONG_MAX, // pulisci i primi 8 bit ULONG_MAX, // pulisci i primi 8 bit
&pickup_flag, // valore ricevuto &pickupFlag, // valore ricevuto
portMAX_DELAY); portMAX_DELAY);
if (first_cycle && pickup_flag != TRIG_FLAG_12P) // skip first cycle because of possible initial noise on pickup signals at startu
{
continue;
}
#ifdef DEBUG if (firstCycle && pickupFlag != TRIG_FLAG_12P) // skip first cycle because of possible initial noise on pickup signals at startu
Serial.print("\033[2J"); // clear screen
Serial.print("\033[H"); // cursor home
LOG_INFO("Iteration [", it++, "]");
if (!names.contains(pickup_flag))
{
LOG_ERROR("Wrong Pickup Flag");
LOG_ERROR("Pickup Flags: ", printBits(pickup_flag).c_str());
continue; continue;
}
else
{
LOG_INFO("Pickup Trigger: ", names.at(pickup_flag));
}
#endif
// Start microsecond precision timeout timer // Start microsecond precision timeout timer
esp_timer_stop(timeout_timer); // stop timer in case it was running from previous cycle esp_timer_stop(timeoutTimer); // stop timer in case it was running from previous cycle
esp_timer_start_once(timeout_timer, spark_timeout_max); esp_timer_start_once(timeoutTimer, c_sparkTimeoutMax);
spark_flag = SPARK_FLAG_NIL; // default value in case of timeout, to be set by ISR if spark event occours
// WAIT FOR SPARK TO HAPPEN OR TIMEOUT // WAIT FOR SPARK TO HAPPEN OR TIMEOUT
BaseType_t sp = pdFALSE; xTaskNotifyWait(
sp = xTaskNotifyWait( 0x00, // non pulire all'ingresso
0x00, // non pulire all'ingresso ULONG_MAX, // pulisci i primi 8 bit
ULONG_MAX, // pulisci i primi 8 bit &sparkFlag, // valore ricevuto
&spark_flag, // valore ricevuto
portMAX_DELAY); // wait indefinitely, timeout handled by esp_timer portMAX_DELAY); // wait indefinitely, timeout handled by esp_timer
// Handle timeout or spark event // Handle timeout or spark event
if (spark_flag == SPARK_FLAG_TIMEOUT) { if (sparkFlag != SPARK_FLAG_TIMEOUT)
spark_flag = SPARK_FLAG_NIL; esp_timer_stop(timeoutTimer);
} else {
// Spark occurred, stop the timer
esp_timer_stop(timeout_timer);
}
#ifdef DEBUG
// LOG_INFO("Spark Flags: ", printBits(spark_flag).c_str());
LOG_INFO("Spark12:", ign_box_sts.coils12.spark_ok ? "TRUE" : "FALSE");
LOG_INFO("Spark34:", ign_box_sts.coils34.spark_ok ? "TRUE" : "FALSE");
if (names.contains(spark_flag))
LOG_INFO("Spark Trigger:", names.at(spark_flag));
#endif
xTaskNotifyStateClear(NULL);
ulTaskNotifyValueClear(NULL, 0xFFFFFFFF);
// A trigger from pickup 12 is followed by a spark event on 34 or vice versa pickup 34 triggers spark on 12 // A trigger from pickup 12 is followed by a spark event on 34 or vice versa pickup 34 triggers spark on 12
if ((pickup_flag == TRIG_FLAG_12P || pickup_flag == TRIG_FLAG_12N) && (spark_flag != SPARK_FLAG_12 && spark_flag != SPARK_FLAG_NIL)) if ((pickupFlag == TRIG_FLAG_12P || pickupFlag == TRIG_FLAG_12N) && (sparkFlag != SPARK_FLAG_12 && sparkFlag != SPARK_FLAG_TIMEOUT))
{ {
ign_box_sts.coils12.spark_status = ign_box_sts.coils34.spark_status = sparkStatus::SPARK_SYNC_FAIL; ignBoxStatus.coils12.sparkStatus = ignBoxStatus.coils34.sparkStatus = sparkStatusEnum::SPARK_SYNC_FAIL;
continue; continue;
} }
// Select coil status reference based on pickup_flag // Select coil status reference based on pickupFlag
coilsStatus *coils; coilsStatus *coils;
switch (pickup_flag) switch (pickupFlag)
{ {
case TRIG_FLAG_12P: case TRIG_FLAG_12P:
first_cycle = false; {
firstCycle = false;
// compute engine rpm from cycle time
auto currentTime = esp_timer_get_time();
auto cycleTime = currentTime - lastCycleTime;
lastCycleTime = currentTime;
ignBoxStatus.engRpm = (int32_t)(60.0f / (cycleTime / 1000000.0f));
}
case TRIG_FLAG_12N: case TRIG_FLAG_12N:
coils = &ign_box_sts.coils12; coils = &ignBoxStatus.coils12;
break; break;
case TRIG_FLAG_34P: case TRIG_FLAG_34P:
case TRIG_FLAG_34N: case TRIG_FLAG_34N:
coils = &ign_box_sts.coils34; coils = &ignBoxStatus.coils34;
break; break;
} }
// Select logic based on pickup and spark flags // Select logic based on pickup and spark flags
switch (pickup_flag) switch (pickupFlag)
{ {
case TRIG_FLAG_12P: case TRIG_FLAG_12P:
case TRIG_FLAG_34P: case TRIG_FLAG_34P:
{ {
// Timeout not occourred, expected POSITIVE edge spark OCCOURRED // Timeout not occourred, expected POSITIVE edge spark OCCOURRED
if (spark_flag != SPARK_FLAG_NIL) if (sparkFlag != SPARK_FLAG_TIMEOUT)
{ {
coils->spark_delay = coils->spark_time - coils->trig_time; coils->sparkDelay = (int32_t)(coils->sparkTime - coils->coilTime);
coils->sstart_status = softStartStatus::NORMAL; // because spark on positive edge coils->softStartStatus = softStartStatusEnum::NORMAL; // because spark on positive edge
coils->spark_status = sparkStatus::SPARK_POS_OK; // do not wait for spark on negative edge coils->sparkStatus = sparkStatusEnum::SPARK_POS_OK; // do not wait for spark on negative edge
#ifdef DEBUG
LOG_INFO("Spark on POSITIVE pulse");
LOG_INFO("Spark Delay Time: ", (int32_t)coils->spark_delay);
#endif
} }
// Timeout occourred, expected POSITIVE edge spark NOT OCCOURRED // Timeout occourred, expected POSITIVE edge spark NOT OCCOURRED
else if (spark_flag == SPARK_FLAG_NIL) else if (sparkFlag == SPARK_FLAG_TIMEOUT)
{ {
coils->spark_status = sparkStatus::SPARK_NEG_WAIT; coils->sparkStatus = sparkStatusEnum::SPARK_NEG_WAIT;
coils->sstart_status = softStartStatus::NORMAL; coils->softStartStatus = softStartStatusEnum::NORMAL;
} }
continue; // Do nothing more on positive pulse continue; // Do nothing more on positive pulse
} }
@@ -206,103 +194,388 @@ void rtIgnitionTask(void *pvParameters)
case TRIG_FLAG_12N: case TRIG_FLAG_12N:
case TRIG_FLAG_34N: case TRIG_FLAG_34N:
{ {
const bool expected_negative = coils->spark_status == sparkStatus::SPARK_NEG_WAIT; const bool negativeSparkExpected = coils->sparkStatus == sparkStatusEnum::SPARK_NEG_WAIT;
// Timeout not occourred, expected NEGATIVE edge spark OCCOURRED // Timeout not occourred, expected NEGATIVE edge spark OCCOURRED
if (spark_flag != SPARK_FLAG_NIL && expected_negative) if (sparkFlag != SPARK_FLAG_TIMEOUT && negativeSparkExpected)
{ {
coils->spark_delay = coils->spark_time - coils->trig_time; coils->sparkDelay = (int32_t)(coils->sparkTime - coils->coilTime);
coils->sstart_status = softStartStatus::SOFT_START; coils->softStartStatus = softStartStatusEnum::SOFT_START;
coils->spark_status = sparkStatus::SPARK_NEG_OK; coils->sparkStatus = sparkStatusEnum::SPARK_NEG_OK;
#ifdef DEBUG
LOG_INFO("Spark on NEGATIVE pulse");
LOG_INFO("Spark Delay Time: ", (int32_t)coils->spark_delay);
#endif
} }
// Timeout occourred, expected POSITIVE edge spark NOT OCCOURRED // Timeout occourred, expected POSITIVE edge spark NOT OCCOURRED
else if (spark_flag == SPARK_FLAG_NIL && expected_negative) else if (sparkFlag == SPARK_FLAG_TIMEOUT && negativeSparkExpected)
{ {
coils->sstart_status = softStartStatus::ERROR; coils->softStartStatus = softStartStatusEnum::ERROR;
coils->spark_status = sparkStatus::SPARK_NEG_FAIL; coils->sparkStatus = sparkStatusEnum::SPARK_NEG_FAIL;
} }
// Timeout not occouured, unexpected negative edge spark // Timeout not occouured, unexpected negative edge spark
else if (spark_flag != SPARK_FLAG_NIL && !expected_negative) else if (sparkFlag != SPARK_FLAG_TIMEOUT && !negativeSparkExpected)
{ {
coils->sstart_status = softStartStatus::SOFT_START; coils->softStartStatus = softStartStatusEnum::SOFT_START;
coils->spark_status = sparkStatus::SPARK_NEG_UNEXPECTED; coils->sparkStatus = sparkStatusEnum::SPARK_NEG_UNEXPECTED;
} }
// Wait for finish of negative pulse to save data to buffer // Wait for finish of negative pulse to save data to buffer
coils->n_events++; coils->nEvents++;
if (pickup_flag == TRIG_FLAG_12N) if (pickupFlag == TRIG_FLAG_12N)
cycle12 = true; cycle12 = true;
else else
cycle34 = true; cycle34 = true;
break; break;
} }
default: default:
#ifdef DEUG
LOG_ERROR("Invalid Interrupt");
LOG_ERROR("Pickup Flags: ", printBits(pickup_flag).c_str());
LOG_ERROR("Spark Flags: ", printBits(spark_flag).c_str());
#endif
break; break;
} }
if (cycle12 && cycle34) // wait for both 12 and 34 cycles to complete before sending data to main loop and resetting peak detectors if (cycle12 && cycle34) // wait for both 12 and 34 cycles to complete before sending data to main loop and resetting peak detectors
{ {
// disable interrupts during adc samples
disableInterrupt(digitalPinToInterrupt(rtInterrupts.trigPin_12p));
disableInterrupt(digitalPinToInterrupt(rtInterrupts.trigPin_12n));
disableInterrupt(digitalPinToInterrupt(rtInterrupts.trigPin_34p));
disableInterrupt(digitalPinToInterrupt(rtInterrupts.trigPin_34n));
disableInterrupt(digitalPinToInterrupt(rtInterrupts.sparkPin_12));
disableInterrupt(digitalPinToInterrupt(rtInterrupts.sparkPin_34));
// reset coils 12 and 34 cycles
cycle12 = false; cycle12 = false;
cycle34 = false; cycle34 = false;
if (ignBoxStatus.coils12.sparkStatus == sparkStatusEnum::SPARK_POS_FAIL || ignBoxStatus.coils12.sparkStatus == sparkStatusEnum::SPARK_NEG_FAIL)
ignBoxStatus.coils12.nMissedFiring++;
if (ignBoxStatus.coils34.sparkStatus == sparkStatusEnum::SPARK_POS_FAIL || ignBoxStatus.coils34.sparkStatus == sparkStatusEnum::SPARK_NEG_FAIL)
ignBoxStatus.coils34.nMissedFiring++;
// read adc channels: pickup12, out12 [ pos + neg ] // read adc channels: pickup12, out12 [ pos + neg ]
if (adc) // read only if adc initialized if (adc) // read only if adc initialized
{ {
uint32_t start_adc_read = esp_timer_get_time(); std::lock_guard<std::mutex> lock(spi_mutex);
uint32_t startAdcReadTime = esp_timer_get_time();
// from peak detector circuits // from peak detector circuits
ign_box_sts.coils12.peak_p_in = adcReadChannel(adc, ADC_CH_PEAK_12P_IN); ignBoxStatus.coils12.peakPos = adc->convertToVoltage(adc->cycleSingle());
ign_box_sts.coils12.peak_n_in = adcReadChannel(adc, ADC_CH_PEAK_12N_IN); ignBoxStatus.coils12.peakNeg = adc->convertToVoltage(adc->cycleSingle());
ign_box_sts.coils34.peak_p_in = adcReadChannel(adc, ADC_CH_PEAK_34P_IN); ignBoxStatus.coils34.peakPos = adc->convertToVoltage(adc->cycleSingle());
ign_box_sts.coils34.peak_n_in = adcReadChannel(adc, ADC_CH_PEAK_34N_IN); ignBoxStatus.coils34.peakNeg = adc->convertToVoltage(adc->cycleSingle());
ign_box_sts.coils12.peak_p_out = adcReadChannel(adc, ADC_CH_PEAK_12P_OUT); ignBoxStatus.coils12.trigLevelPos = adc->convertToVoltage(adc->cycleSingle());
ign_box_sts.coils12.peak_n_out = adcReadChannel(adc, ADC_CH_PEAK_12N_OUT); ignBoxStatus.coils12.trigLevelNeg = adc->convertToVoltage(adc->cycleSingle());
ign_box_sts.coils34.peak_p_out = adcReadChannel(adc, ADC_CH_PEAK_34P_OUT); ignBoxStatus.coils34.trigLevelPos = adc->convertToVoltage(adc->cycleSingle());
ign_box_sts.coils34.peak_n_out = adcReadChannel(adc, ADC_CH_PEAK_34N_OUT); ignBoxStatus.coils34.trigLevelNeg = adc->convertToVoltage(adc->cycleSingle());
ign_box_sts.adc_read_time = (uint32_t)(esp_timer_get_time() - start_adc_read); adc->stopConversion();
ignBoxStatus.adcReadTime = (int32_t)(esp_timer_get_time() - startAdcReadTime);
} }
else // simulate adc read timig else // simulate adc read timig
vTaskDelay(pdMS_TO_TICKS(1)); vTaskDelay(pdMS_TO_TICKS(c_adcTime));
// reset peak detectors + sample and hold // reset peak detectors + sample and hold
// outputs on io expander // outputs on io expander
if (io) if (io)
{ {
const uint16_t iostat = io->read(); uint32_t startIoReadWriteTime = esp_timer_get_time();
io->write(iostat | rst_bitmask); // Discharge Pulse
vTaskDelay(pdMS_TO_TICKS(1)); io->extDigitalWrite(rtResets.sh_disch_12, true);
io->write(iostat & ~rst_bitmask); io->extDigitalWrite(rtResets.sh_disch_34, true);
delayMicroseconds(250);
io->extDigitalWrite(rtResets.sh_disch_12, false);
io->extDigitalWrite(rtResets.sh_disch_34, false);
// Safety delay
delayMicroseconds(500);
// Re-Arm Pulse
io->extDigitalWrite(rtResets.sh_arm_12, true);
io->extDigitalWrite(rtResets.sh_arm_34, true);
delayMicroseconds(250);
io->extDigitalWrite(rtResets.sh_arm_12, false);
io->extDigitalWrite(rtResets.sh_arm_34, false);
ignBoxStatus.ioReadWriteTime = (int32_t)(esp_timer_get_time() - startIoReadWriteTime);
} }
else else
vTaskDelay(pdMS_TO_TICKS(1)); vTaskDelay(pdMS_TO_TICKS(c_ioTime));
// send essage to main loop with ignition info, by copy so local static variable is ok // send essage to main loop with ignition info, by copy so local static variable is ok
if (rt_queue) if (rtQueue)
ign_box_sts.timestamp = esp_timer_get_time(); // update data timestamp
if (xQueueSendToBack(rt_queue, (void *)&ign_box_sts, 0) != pdPASS)
{ {
static uint32_t n_errors = 0; ignBoxStatus.timestamp = esp_timer_get_time(); // update data timestamp
n_errors++; if (xQueueSendToBack(rtQueue, (void *)&ignBoxStatus, 0) != pdPASS)
ign_box_sts.n_queue_errors = n_errors; ignBoxStatus.nQueueErrors = ++nErrors;
LOG_ERROR("Failed to send to rt_queue");
} }
// enable interrupts ready for a new cycle
enableInterrupt(digitalPinToInterrupt(rtInterrupts.trigPin_12p));
enableInterrupt(digitalPinToInterrupt(rtInterrupts.trigPin_12n));
enableInterrupt(digitalPinToInterrupt(rtInterrupts.trigPin_34p));
enableInterrupt(digitalPinToInterrupt(rtInterrupts.trigPin_34n));
enableInterrupt(digitalPinToInterrupt(rtInterrupts.sparkPin_12));
enableInterrupt(digitalPinToInterrupt(rtInterrupts.sparkPin_34));
} }
} }
// Delete the timeout timer // Delete the timeout timer
esp_timer_delete(timeout_timer); esp_timer_stop(timeoutTimer);
LOG_WARN("Ending realTime Task"); esp_timer_delete(timeoutTimer);
LOG_WARN("rtTask Ending [", params->name.c_str(), "]");
// Ignition A Interrupts DETACH // Ignition A Interrupts DETACH
detachInterrupt(rt_int.trig_pin_12p); detachInterrupt(rtInterrupts.trigPin_12p);
detachInterrupt(rt_int.trig_pin_12n); detachInterrupt(rtInterrupts.trigPin_12n);
detachInterrupt(rt_int.trig_pin_34p); detachInterrupt(rtInterrupts.trigPin_34p);
detachInterrupt(rt_int.trig_pin_34n); detachInterrupt(rtInterrupts.trigPin_34n);
detachInterrupt(rt_int.spark_pin_12); detachInterrupt(rtInterrupts.sparkPin_12);
detachInterrupt(rt_int.spark_pin_34); detachInterrupt(rtInterrupts.sparkPin_34);
// delete present task // delete present task
vTaskDelete(NULL); vTaskDelete(NULL);
} }
///////////// CLASS MEMBER DEFINITIONS /////////////
rtIgnitionTask::rtIgnitionTask(const rtTaskParams params, const uint32_t history_size, const uint32_t queue_size, const uint8_t core, std::mutex &fs_mutex, fs::FS &filesystem) : m_params(params), m_filesystem(filesystem), m_filesystemMutex(fs_mutex), m_core(core), m_historyMax(history_size)
{
LOG_WARN("Starting Manager for [", m_params.name.c_str(), "]");
// create queue buffers
m_rtQueueHandle = xQueueCreate(queue_size, sizeof(ignitionBoxStatus));
if (!m_rtQueueHandle)
{
LOG_ERROR("Unable To Create Task [", params.name.c_str(), "] queues");
m_managerStatus = rtTaskStatus::ERROR;
return;
}
else
m_params.rt_queue = m_rtQueueHandle;
try
{
// create PSram history vectors
m_historyBuf0 = PSHistory(history_size);
m_historyBuf1 = PSHistory(history_size);
// assing active and writable history
m_historyActive = std::unique_ptr<PSHistory>(&m_historyBuf0);
m_historyInactive = std::unique_ptr<PSHistory>(&m_historyBuf1);
}
catch (std::bad_alloc &e)
{
LOG_ERROR("Task [", params.name.c_str(), "] Unable to allocate history PSRAM: ", e.what());
return;
}
m_managerTaskName = (std::string("man_") + m_params.name).c_str();
auto task_success = xTaskCreatePinnedToCore(
rtIgnitionTask_manager,
m_managerTaskName.c_str(),
RT_TASK_STACK,
(void *)this,
m_params.rt_priority >> 2,
&m_managerHandle,
m_core);
if (task_success != pdPASS)
{
LOG_ERROR("Unable To Create Manager for [", params.name.c_str(), "]");
m_managerStatus = rtTaskStatus::ERROR;
return;
}
// average every 10 samples
m_statusFiltered = ignitionBoxStatusFiltered(m_filterSize);
m_dataLast = millis();
m_managerStatus = rtTaskStatus::OK;
}
rtIgnitionTask::~rtIgnitionTask()
{
if (m_rtHandle)
vTaskDelete(m_rtHandle);
if (m_managerHandle)
vTaskDelete(m_managerHandle);
if (m_rtQueueHandle)
vQueueDelete(m_rtQueueHandle);
}
void rtIgnitionTask::run()
{
// receive new data from the queue
auto new_data = xQueueReceive(m_rtQueueHandle, &m_statusLast, 0); // non blocking receive
if (new_data == pdPASS)
{
m_dataLast = millis();
m_managerStatus = rtTaskStatus::RUNNING;
// if history buffer is full swap buffers and if enabled save history buffer
if (m_statusCounter >= m_historyMax)
{
LOG_DEBUG("Save for Buffer Full: ", m_statusCounter);
m_statusCounter = 0;
m_savePartial = false; // reset partial save flag on new data cycle
std::swap(m_historyActive, m_historyInactive);
if (m_historySaveEnable)
saveHistory(*m_historyInactive, m_historyPath); // directly call the save task function to save without delay
LOG_INFO("Save History");
}
// update filtered data
m_statusFiltered.update(m_statusLast);
(*m_historyActive)[m_statusCounter] = m_statusLast;
// callback
if (m_onFilteredStatusUpdate && m_statusCounter % m_filterSize == 0)
{
m_onFilteredStatusUpdate(m_statusFiltered);
}
// update data counter
m_statusCounter++;
}
else
{
if (millis() - m_dataLast > c_idleTime)
{
if (m_statusCounter > 0 && !m_savePartial)
{
LOG_DEBUG("Save Partial: ", m_statusCounter);
m_historyActive->resize(m_statusCounter);
saveHistory(*m_historyActive, m_historyPath);
m_historyActive->resize(m_historyMax);
m_statusCounter = 0;
m_savePartial = true;
}
m_managerStatus = rtTaskStatus::IDLE;
}
}
}
const bool rtIgnitionTask::start()
{
LOG_WARN("Starting rtTask [", m_params.name.c_str(), "]");
auto task_success = xTaskCreatePinnedToCore(
rtIgnitionTask_realtime,
m_params.name.c_str(),
m_params.rt_stack_size,
(void *)&m_params,
m_params.rt_priority,
&m_rtHandle,
m_core);
const bool success = task_success == pdPASS && m_rtHandle != nullptr;
if (success)
m_managerStatus = rtTaskStatus::IDLE;
return success;
}
const bool rtIgnitionTask::stop()
{
LOG_WARN("Ending Task [", m_params.name.c_str(), "]");
if (m_rtHandle)
{
m_params.rt_running = false;
m_rtHandle = nullptr;
m_managerStatus = rtTaskStatus::STOPPED;
return true;
}
return false;
}
const ignitionBoxStatus rtIgnitionTask::getLast() const
{
return m_statusLast;
}
const ignitionBoxStatusFiltered rtIgnitionTask::getFiltered() const
{
return m_statusFiltered;
}
const rtIgnitionTask::rtTaskStatus rtIgnitionTask::getStatus() const
{
return m_managerStatus;
}
void rtIgnitionTask::enableSave(const bool enable, const std::filesystem::path filename)
{
m_historySaveEnable = enable;
if (enable && !filename.empty())
{
LOG_WARN("Save History Enabled Task [", m_params.name.c_str(), "]");
m_historyPath = m_filesystem.mountpoint() / filename;
}
else
{
LOG_WARN("Save History Disabled Task [", m_params.name.c_str(), "]");
}
}
void rtIgnitionTask::onMessage(std::function<void(ignitionBoxStatusFiltered)> callaback)
{
m_onFilteredStatusUpdate = callaback;
}
void rtIgnitionTask::saveHistory(const rtIgnitionTask::PSHistory &history, const std::filesystem::path &fileName)
{
// Lock filesystem mutex to avoid concurrent access
std::lock_guard<std::mutex> fs_lock(m_filesystemMutex);
// Check for free space
if (LittleFS.totalBytes() - LittleFS.usedBytes() < history.size() * sizeof(ignitionBoxStatus)) // check if at least 1MB is free for saving history
{
LOG_ERROR("Not enough space in SPIFFS to save history");
return;
}
// create complete file path
const std::filesystem::path mountPoint = std::filesystem::path(m_filesystem.mountpoint());
std::filesystem::path filePath = fileName;
if (fileName.root_path() != mountPoint)
filePath = mountPoint / fileName;
// if firt save remove old file and create new
auto saveFlags = std::ios::out;
if (m_saveFirst)
{
saveFlags |= std::ios::trunc; // overwrite existing file
m_filesystem.remove(filePath.c_str()); // ensure file is removed before saving to avoid issues with appending to existing file in SPIFFS
LOG_INFO("Saving history to Flash, new file:", filePath.c_str());
}
else // else append to existing file
{
saveFlags |= std::ios::app; // append to new file
LOG_INFO("Saving history to Flash, appending to existing file:", filePath.c_str());
}
std::ofstream ofs(filePath, saveFlags);
if (ofs.fail())
{
LOG_ERROR("Failed to open file for writing");
return;
}
// write csv header
if (m_saveFirst)
{
ofs << "TS,EVENTS_12,DLY_12,STAT_12,V_12_1,V_12_2,V_12_3,V_12_4,IGNITION_MODE_12,"
<< "EVENTS_34,DLY_34,STAT_34,V_34_1,V_34_2,V_34_3,V_34_4,IGNITION_MODE_34,"
<< "ENGINE_RPM,ADC_READTIME,N_QUEUE_ERRORS"
<< std::endl;
ofs.flush();
m_saveFirst = false;
}
for (const auto &entry : history)
{
ofs << std::to_string(entry.timestamp) << ","
<< std::to_string(entry.coils12.nEvents) << ","
<< std::to_string(entry.coils12.sparkDelay) << ","
<< std::string(sparkStatusNames.at(entry.coils12.sparkStatus)) << ","
<< std::to_string(entry.coils12.peakPos) << ","
<< std::to_string(entry.coils12.peakNeg) << ","
<< std::to_string(entry.coils12.trigLevelPos) << ","
<< std::to_string(entry.coils12.trigLevelNeg) << ","
<< std::string(softStartStatusNames.at(entry.coils12.softStartStatus)) << ","
<< std::to_string(entry.coils34.nEvents) << ","
<< std::to_string(entry.coils34.sparkDelay) << ","
<< std::string(sparkStatusNames.at(entry.coils34.sparkStatus)) << ","
<< std::to_string(entry.coils34.peakPos) << ","
<< std::to_string(entry.coils34.peakNeg) << ","
<< std::to_string(entry.coils34.trigLevelPos) << ","
<< std::to_string(entry.coils34.trigLevelNeg) << ","
<< std::string(softStartStatusNames.at(entry.coils34.softStartStatus)) << ","
<< std::to_string(entry.engRpm) << ","
<< std::to_string(entry.adcReadTime) << ","
<< std::to_string(entry.nQueueErrors);
ofs << std::endl;
ofs.flush();
}
ofs.close();
LOG_INFO("Ignition Box history saved to Flash, records written: ", history.size());
}

View File

@@ -2,12 +2,19 @@
#define DEBUGLOG_DEFAULT_LOG_LEVEL_DEBUG #define DEBUGLOG_DEFAULT_LOG_LEVEL_DEBUG
// Serial debug flag // Serial debug flag
//#define DEBUG // #define DEBUG
// Arduino Libraries // Arduino Libraries
#include <Arduino.h> #include <Arduino.h>
#include <DebugLog.h> #include <DebugLog.h>
#include "utils.h" #include "utils.h"
#include <memory>
#include <mutex>
#include <filesystem>
#include <FS.h>
#include <LittleFS.h>
#include <datasave.h>
#include <functional>
// ISR // ISR
#include "isr.h" #include "isr.h"
@@ -15,9 +22,6 @@
// DEVICES // DEVICES
#include "devices.h" #include "devices.h"
// Global Variables and Flags
const uint32_t spark_timeout_max = 500; // in microseconds
// Debug Variables // Debug Variables
#ifdef DEBUG #ifdef DEBUG
static const std::map<const uint32_t, const char *> names = { static const std::map<const uint32_t, const char *> names = {
@@ -31,36 +35,124 @@ static const std::map<const uint32_t, const char *> names = {
}; };
#endif #endif
// RT task Interrupt parameters class rtIgnitionTask
struct rtTaskInterrupts
{ {
void (*isr_ptr)(void *); using PSHistory = PSRAMVector<ignitionBoxStatus>;
const uint8_t trig_pin_12p;
const uint8_t trig_pin_12n;
const uint8_t trig_pin_34p;
const uint8_t trig_pin_34n;
const uint8_t spark_pin_12;
const uint8_t spark_pin_34;
};
// RT Task Peak Detector Reset pins public:
struct rtTaskResets // RT task Interrupt parameters
{ struct rtTaskInterruptParams
const uint8_t rst_io_12p; {
const uint8_t rst_io_12n; void (*isrPtr)(void *);
const uint8_t rst_io_34p; const uint8_t trigPin_12p;
const uint8_t rst_io_34n; const uint8_t trigPin_12n;
}; const uint8_t trigPin_34p;
const uint8_t trigPin_34n;
const uint8_t sparkPin_12;
const uint8_t sparkPin_34;
};
// RT task parameters // RT Task Peak Detector Reset pins
struct rtTaskParams struct rtTaskIOParams
{ {
bool rt_running; // run flag, false to terminate const uint32_t expander_addr;
Devices *dev; const uint32_t pot_cs_12;
TaskHandle_t* rt_handle_ptr; const uint32_t pot_cs_34;
const QueueHandle_t rt_queue; const uint32_t ss_force;
const rtTaskInterrupts rt_int; // interrupt pins to attach const uint32_t ss_inhibit_12;
const rtTaskResets rt_resets; // reset ping for peak detectors const uint32_t ss_inhibit_34;
}; const uint32_t sh_disch_12;
const uint32_t sh_disch_34;
const uint32_t sh_arm_12;
const uint32_t sh_arm_34;
const uint32_t relay_in_12;
const uint32_t relay_in_34;
const uint32_t relay_out_12;
const uint32_t relay_out_34;
};
void rtIgnitionTask(void *pvParameters); // RT task parameters
struct rtTaskParams
{
bool rt_running; // run flag, false to terminate
const std::string name;
const uint32_t rt_stack_size;
const uint32_t rt_priority;
const rtTaskInterruptParams rt_int; // interrupt pins to attach
const rtTaskIOParams rt_io; // reset ping for peak detectors
QueueHandle_t rt_queue; // queue for task io
Devices *dev;
};
enum rtTaskStatus
{
INIT,
OK,
ERROR,
RUNNING,
IDLE,
STOPPED
};
public:
rtIgnitionTask(const rtTaskParams params, const uint32_t history_size, const uint32_t queue_size, const uint8_t core, std::mutex &fs_mutex, fs::FS &filesystem = LittleFS);
~rtIgnitionTask();
void run();
const bool start();
const bool stop();
const ignitionBoxStatus getLast() const;
const ignitionBoxStatusFiltered getFiltered() const;
const rtTaskStatus getStatus() const;
void enableSave(const bool enable, const std::filesystem::path filename);
void onMessage(std::function<void(ignitionBoxStatusFiltered)> callaback);
private:
void saveHistory(const rtIgnitionTask::PSHistory &history, const std::filesystem::path &file_name);
private: // static functions for FreeRTOS
static void rtIgnitionTask_manager(void *pvParameters);
static void rtIgnitionTask_realtime(void *pvParameters);
private:
bool m_running = true;
rtTaskStatus m_managerStatus = INIT;
rtTaskParams m_params;
const uint8_t m_core;
std::string m_managerTaskName;
TaskHandle_t m_rtHandle = nullptr;
TaskHandle_t m_managerHandle = nullptr;
QueueHandle_t m_rtQueueHandle = nullptr;
const uint32_t m_historyMax;
bool m_historySaveEnable = false;
std::filesystem::path m_historyPath;
PSHistory m_historyBuf0;
PSHistory m_historyBuf1;
std::unique_ptr<PSHistory> m_historyActive;
std::unique_ptr<PSHistory> m_historyInactive;
bool m_savePartial = false;
bool m_saveFirst = true;
fs::FS &m_filesystem;
std::mutex &m_filesystemMutex;
uint8_t m_filterSize = 10;
uint32_t m_statusCounter = 0;
uint32_t m_dataLast = 0;
ignitionBoxStatus m_statusLast;
ignitionBoxStatusFiltered m_statusFiltered;
std::function<void(ignitionBoxStatusFiltered)> m_onFilteredStatusUpdate = nullptr;
// Global Variables and Flags
static const uint32_t c_sparkTimeoutMax = 500; // in microseconds
static const uint32_t c_idleTime = 10000; // in mS
static const uint8_t c_adcTime = 4; // in mS
static const uint8_t c_ioTime = 2; // in mS
};

73
RotaxMonitor/src/ui.cpp Normal file
View File

@@ -0,0 +1,73 @@
#include <ui.h>
void clearScreen()
{
Serial.print("\033[2J"); // clear screen
Serial.print("\033[H"); // cursor home
Serial.flush();
}
void setCursor(const uint8_t x, const uint8_t y)
{
Serial.printf("\033[%d;%d", y, x + 1);
Serial.flush();
}
void printField(const char name[], const uint32_t val)
{
Serial.printf("%15s: %06u\n", name, val);
}
void printField(const char name[], const int32_t val)
{
Serial.printf("%15s: %06d\n", name, val);
}
void printField(const char name[], const int64_t val)
{
Serial.printf("%15s: %06u\n", name, (uint64_t)val);
}
void printField(const char name[], const float val)
{
Serial.printf("%15s: %4.2f\n", name, val);
}
void printField(const char name[], const char *val)
{
Serial.printf("%15s: %s\n", name, val);
}
void printInfo(const ignitionBoxStatus &info)
{
clearScreen();
setCursor(0, 0);
printField("++ Timestamp ++", (uint32_t)info.timestamp);
Serial.println("========== Coils 12 =============");
printField("Events", info.coils12.nEvents);
printField("Events Missed", info.coils12.nMissedFiring);
printField("Spark Dly", (uint32_t)info.coils12.sparkDelay);
printField("Spark Sts", sparkStatusNames.at(info.coils12.sparkStatus));
printField("Peak P_IN", info.coils12.peakPos);
printField("Peak N_IN", info.coils12.peakNeg);
printField("Peak P_OUT", info.coils12.trigLevelPos);
printField("Peak N_OUT", info.coils12.trigLevelNeg);
printField("Soft Start ", softStartStatusNames.at(info.coils12.softStartStatus));
Serial.println("========== Coils 34 =============");
printField("Events", info.coils34.nEvents);
printField("Events Missed", info.coils34.nMissedFiring);
printField("Spark Dly", (uint32_t)info.coils34.sparkDelay);
printField("Spark Sts", sparkStatusNames.at(info.coils34.sparkStatus));
printField("Peak P_IN", info.coils34.peakPos);
printField("Peak N_IN", info.coils34.peakNeg);
printField("Peak P_OUT", info.coils34.trigLevelPos);
printField("Peak N_OUT", info.coils34.trigLevelNeg);
printField("Soft Start ", softStartStatusNames.at(info.coils34.softStartStatus));
Serial.println("============ END ===============");
Serial.println();
printField("Engine RPM", info.engRpm);
printField("ADC Read Time", info.adcReadTime);
printField("Queue Errors", info.nQueueErrors);
}

View File

@@ -1,36 +1,16 @@
#pragma once #pragma once
#include <Arduino.h> #include <Arduino.h>
#include <datastruct.h>
#include <string>
void clearScreen() void clearScreen();
{ void setCursor(const uint8_t x, const uint8_t y);
Serial.print("\033[2J"); // clear screen void printField(const char name[], const uint32_t val);
Serial.print("\033[H"); // cursor home void printField(const char name[], const int32_t val);
Serial.flush(); void printField(const char name[], const int64_t val);
} void printField(const char name[], const float val);
void printField(const char name[], const char *val);
void setCursor(const uint8_t x, const uint8_t y) void printInfo(const ignitionBoxStatus &info);
{
Serial.printf("\033[%d;%d", y, x + 1);
Serial.flush();
}
void printField(const char name[], const uint32_t val)
{
Serial.printf("%15s: %06d\n", name, val);
}
void printField(const char name[], const int64_t val)
{
Serial.printf("%15s: %06u\n", name, (uint64_t)val);
}
void printField(const char name[], const float val)
{
Serial.printf("%15s: %4.2f\n", name, val);
}
void printField(const char name[], const char *val)
{
Serial.printf("%15s: %s\n", name, val);
}

View File

@@ -1,14 +1,190 @@
#include "utils.h" #include "utils.h"
#include "freertos_stats.h"
#include "sdkconfig.h"
std::string printBits(uint32_t value) { #include "freertos/FreeRTOS.h"
#include "freertos/portable.h"
#include "esp_heap_caps.h"
#include "esp_system.h"
#include "spi_flash_mmap.h"
#include "esp_partition.h"
#include "LittleFS.h"
#include <vector>
#include <algorithm>
#include <functional>
#define FREERTOS_TASK_NUMBER_MAX_NUM 256 // RunTime stats for how many Tasks to be stored
std::string printBits(uint32_t value)
{
std::string result; std::string result;
for (int i = 31; i >= 0; i--) { for (int i = 31; i >= 0; i--)
{
// ottieni il singolo bit // ottieni il singolo bit
result += ((value >> i) & 1) ? '1' : '0'; result += ((value >> i) & 1) ? '1' : '0';
// aggiungi uno spazio ogni 8 bit, tranne dopo l'ultimo // aggiungi uno spazio ogni 8 bit, tranne dopo l'ultimo
if (i % 8 == 0 && i != 0) { if (i % 8 == 0 && i != 0)
{
result += ' '; result += ' ';
} }
} }
return result; return result;
} }
// ANSI colors
#define BAR_WIDTH 30
#define COLOR_RESET "\033[0m"
#define COLOR_RED "\033[31m"
#define COLOR_GREEN "\033[32m"
#define COLOR_BLUE "\033[34m"
#define COLOR_MAGENTA "\033[35m"
#define COLOR_CYAN "\033[36m"
#define COLOR_YELLOW "\033[33m"
#define COLOR_WHITE "\033[37m"
#define COLOR_LBLUE "\033[94m"
void printBar(Print &printer, const char *label, size_t used, size_t total, const char *color)
{
float perc = total > 0 ? ((float)used / total) : 0;
int filled = perc * BAR_WIDTH;
char str[256] = {0};
uint16_t k(0);
k += sprintf(str, "%s%-12s [" COLOR_RESET, color, label);
for (int i = 0; i < BAR_WIDTH; i++)
{
if (i < filled)
k += sprintf(&str[k], "%s#%s", color, COLOR_RESET);
else
k += sprintf(&str[k], "-");
}
sprintf(&str[k], "] %s%6.2f%%%s (%5.3f/%5.3f)MB",
color,
perc * 100.0,
COLOR_RESET,
(used / 1024.0f / 1024.0f),
(total / 1024.0f / 1024.0f));
printer.println(str);
}
void printRunningTasksMod(Print &printer, std::function<bool(const TaskStatus_t &a, const TaskStatus_t &b)> orderBy)
{
static const char *taskStates[] = {"Running", "Ready", "Blocked", "Suspended", "Deleted", "Invalid"};
static uint32_t ulRunTimeCounters[FREERTOS_TASK_NUMBER_MAX_NUM];
static uint32_t ulLastRunTime = 0;
uint32_t ulCurrentRunTime = 0, ulTaskRunTime = 0;
uint32_t ulTotalRunTime = 0;
std::vector<TaskStatus_t> pxTaskStatusArray;
UBaseType_t uxArraySize = 0;
// Take a snapshot of the number of tasks in case it changes while this function is executing.
uxArraySize = uxTaskGetNumberOfTasks();
pxTaskStatusArray.resize(uxArraySize);
// Generate raw status information about each task.
uxArraySize = uxTaskGetSystemState(pxTaskStatusArray.data(), uxArraySize, &ulTotalRunTime);
if (orderBy == nullptr)
std::sort(pxTaskStatusArray.begin(), pxTaskStatusArray.end(), [](const TaskStatus_t &a, const TaskStatus_t &b)
{ return a.xTaskNumber < b.xTaskNumber; });
else
std::sort(pxTaskStatusArray.begin(), pxTaskStatusArray.end(), orderBy);
// Compute system total runtime
ulCurrentRunTime = ulTotalRunTime - ulLastRunTime;
ulCurrentRunTime = ulCurrentRunTime > 0 ? ulCurrentRunTime : 1;
ulLastRunTime = ulTotalRunTime;
// PRINT MEMORY INFO
printer.printf("\033[H");
printer.printf(COLOR_WHITE "====================== ESP32 SYSTEM MONITOR ======================\n" COLOR_RESET);
std::string buffer;
time_t now = time(nullptr);
struct tm *t = localtime(&now);
buffer.resize(64);
strftime(buffer.data(), sizeof(buffer), "%Y-%m-%d %H:%M:%S", t);
printer.printf(COLOR_WHITE "=================== Datetime: %s ==================\n\n" COLOR_RESET, buffer.c_str());
// ===== HEAP =====
size_t freeHeap = esp_get_free_heap_size();
size_t totalHeap = heap_caps_get_total_size(MALLOC_CAP_DEFAULT);
printBar(printer, "HEAP", totalHeap - freeHeap, totalHeap, COLOR_GREEN);
// ===== RAM INTERNA =====
size_t freeInternal = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
size_t totalInternal = heap_caps_get_total_size(MALLOC_CAP_INTERNAL);
printBar(printer, "INTERNAL", totalInternal - freeInternal, totalInternal, COLOR_CYAN);
// ===== PSRAM =====
size_t totalPsram = heap_caps_get_total_size(MALLOC_CAP_SPIRAM);
if (totalPsram > 0)
{
size_t freePsram = heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
printBar(printer, "PSRAM", totalPsram - freePsram, totalPsram, COLOR_MAGENTA);
}
printer.printf("\n");
// ===== FLASH APP (approssimato) =====
const esp_partition_t *app_partition =
esp_partition_find_first(ESP_PARTITION_TYPE_APP,
ESP_PARTITION_SUBTYPE_APP_FACTORY,
NULL);
// ===== LITTLEFS (corretto con partition table) =====
const esp_partition_t *fs_partition =
esp_partition_find_first(ESP_PARTITION_TYPE_DATA,
ESP_PARTITION_SUBTYPE_DATA_LITTLEFS,
"littlefs");
if (fs_partition)
{
size_t totalFS = fs_partition->size; // dimensione reale partizione
size_t usedFS = LittleFS.usedBytes(); // spazio usato reale
printBar(printer, "LITTLEFS", usedFS, totalFS, COLOR_YELLOW);
}
else
{
printer.printf(COLOR_YELLOW "%-12s [NOT FOUND]\n" COLOR_RESET, "LITTLEFS");
}
// ===== MIN HEAP =====
size_t minHeap = esp_get_minimum_free_heap_size();
printer.printf("%s\nMin Heap Ever:%s %u KB\n", COLOR_RED, COLOR_RESET, minHeap / 1024);
size_t max_block = heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM);
printer.printf("%sMax PSRAM Block:%s %u KB\n\n", COLOR_RED, COLOR_RESET, max_block / 1024);
// Print Runtime Information
printer.printf("Tasks: %u, Runtime: %lus, Period: %luus\n", uxArraySize, ulTotalRunTime / 1000000, ulCurrentRunTime);
// Print Task Headers
printer.printf("Num\t Name\tLoad\tPrio\t Free\tCore\tState\n");
for (const auto &task : pxTaskStatusArray)
{
ulTaskRunTime = (task.ulRunTimeCounter - ulRunTimeCounters[task.xTaskNumber]);
ulRunTimeCounters[task.xTaskNumber] = task.ulRunTimeCounter;
ulTaskRunTime = (ulTaskRunTime * 100) / ulCurrentRunTime; // in percentage
printer.printf(
"%3u\t%16s"
"\t%3lu%%"
"\t%4u\t%5lu"
"\t%4c"
"\t%s\n",
task.xTaskNumber, task.pcTaskName,
ulTaskRunTime,
task.uxCurrentPriority, task.usStackHighWaterMark,
(task.xCoreID == tskNO_AFFINITY) ? '*' : ('0' + task.xCoreID),
taskStates[task.eCurrentState]);
}
printer.println();
}

View File

@@ -2,5 +2,14 @@
#include <Arduino.h> #include <Arduino.h>
#include <string> #include <string>
#include <datastruct.h>
std::string printBits(uint32_t value); std::string printBits(uint32_t value);
void printRunningTasksMod(Print &printer, std::function<bool(const TaskStatus_t &a, const TaskStatus_t &b)> orderBy = nullptr);
inline void swapHistory(PSRAMVector<ignitionBoxStatus>* active, PSRAMVector<ignitionBoxStatus>* writable) {
auto *temp = active;
active = writable; // switch active and writable buffers
writable = temp; // ensure writable_history points to the buffer we just filled
}

View File

@@ -0,0 +1,177 @@
#include <webserver.h>
void on_ping(TimerHandle_t xTimer)
{
if (!xTimer)
return;
auto ws = (AsyncWebSocket *)pvTimerGetTimerID(xTimer);
ws->pingAll();
ws->cleanupClients();
}
AstroWebServer::AstroWebServer(const uint8_t port, fs::FS &filesystem) : c_port(port), m_webserver(AsyncWebServer(port)), m_websocket(AsyncWebSocket("/ws")), m_filesystem(filesystem)
{
LOG_DEBUG("Initializing Web Server");
m_websocket.onEvent([this](AsyncWebSocket *server, AsyncWebSocketClient *client,
AwsEventType type, void *arg, uint8_t *data, size_t len)
{ onWsEvent(server, client, type, arg, data, len); });
m_webserver.addHandler(&m_websocket);
m_webserver.serveStatic("/", m_filesystem, "/").setDefaultFile("index.html");
m_webserver.on("/upload", HTTP_POST, [this](AsyncWebServerRequest *request)
{ onUploadRequest(request); }, [this](AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final)
{ onUploadHandler(request, filename, index, data, len, final); });
m_webserver.begin();
m_websocket.enable(true);
m_pingTimer = xTimerCreate("wsPingTimer", pdMS_TO_TICKS(c_pingTime), pdTRUE, (void *)&m_websocket, on_ping);
xTimerStart(m_pingTimer, pdMS_TO_TICKS(10));
registerWsCommand("setTime", [this](const ArduinoJson::JsonDocument &doc)
{ onSetTme(doc); });
LOG_DEBUG("Webserver Init OK");
}
AstroWebServer::~AstroWebServer()
{
xTimerStop(m_pingTimer, 0);
xTimerDelete(m_pingTimer, pdMS_TO_TICKS(10));
m_webserver.removeHandler(&m_websocket);
m_webserver.end();
}
void AstroWebServer::sendWsData(const String &data)
{
if (m_websocket.count())
{
m_websocket.textAll(data);
}
}
void AstroWebServer::registerWsCommand(const std::string &cmd, const WScommand func)
{
if (cmd.empty() || m_webserverCommands.contains(cmd))
return;
if (!func)
return;
m_webserverCommands[cmd] = func;
}
void AstroWebServer::unRegisterWsCommand(const std::string &cmd)
{
if (m_webserverCommands.contains(cmd))
m_webserverCommands.erase(cmd);
}
void AstroWebServer::onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len)
{
switch (type)
{
case WS_EVT_CONNECT:
LOG_DEBUG("WS client IP [", client->remoteIP().toString().c_str(), "]-ID [", client->id(), "] CONNECTED");
break;
case WS_EVT_DISCONNECT:
LOG_DEBUG("WS client IP [", client->remoteIP().toString().c_str(), "]-ID [", client->id(), "] DISCONNECTED");
break;
case WS_EVT_PONG:
LOG_DEBUG("WS client IP [", client->remoteIP().toString().c_str(), "]-ID [", client->id(), "] PONG");
break;
case WS_EVT_DATA:
{
AwsFrameInfo *info = (AwsFrameInfo *)arg;
if (info->final && info->index == 0 && info->len == len)
{
std::string data_str((char *)data, len);
ArduinoJson::JsonDocument doc;
if (auto rv = ArduinoJson::deserializeJson(doc, data_str) != ArduinoJson::DeserializationError::Ok)
{
LOG_ERROR("WS Client unable to deserialize Json");
return;
}
if (!doc["cmd"].is<std::string>() || !m_webserverCommands.contains(doc["cmd"]))
{
LOG_WARN("WS Client Invalid Json command [", doc["cmd"].as<std::string>().c_str(), "]");
return;
}
// execute callback function
m_webserverCommands[doc["cmd"]](doc);
}
}
}
}
void AstroWebServer::onUploadRequest(AsyncWebServerRequest *request)
{
if (m_uploadFailed)
request->send(500, "text/plain", "Upload failed");
else
request->send(200, "text/plain", "Upload successful");
}
void AstroWebServer::onUploadHandler(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final)
{
if (index == 0) // only on first iteration to open file
{
m_uploadFailed = false;
String safeName = filename;
int slashIndex = safeName.lastIndexOf('/');
if (slashIndex >= 0)
safeName = safeName.substring(slashIndex + 1);
if (safeName.length() == 0)
{
m_uploadFailed = true;
LOG_ERROR("Invalid file name");
return;
}
const std::filesystem::path filePath = std::filesystem::path(m_filesystem.mountpoint()) / safeName.c_str();
if (m_filesystem.exists(filePath.c_str()))
m_filesystem.remove(filePath.c_str());
m_uploadFile = m_filesystem.open(filePath.c_str(), FILE_WRITE);
if (!m_uploadFile)
{
m_uploadFailed = true;
LOG_ERROR("Failed to open upload file:", filePath.c_str());
return;
}
}
// Actual write of file data
if (!m_uploadFailed && m_uploadFile)
{
if (m_uploadFile.write(data, len) != len)
m_uploadFailed = true;
}
// close the file and save on final call
if (final && m_uploadFile)
{
m_uploadFile.close();
if (!m_uploadFailed)
LOG_INFO("Uploaded file to LittleFS:", filename.c_str());
}
}
void AstroWebServer::onSetTme(const ArduinoJson::JsonDocument &doc)
{
std::string buffer;
auto epoch = doc["time"].as<time_t>();
timeval te{
.tv_sec = epoch,
.tv_usec = 0,
};
timezone tz{
.tz_minuteswest = 0,
.tz_dsttime = DST_MET,
};
settimeofday(&te, &tz);
time_t now = time(nullptr);
struct tm *t = localtime(&now);
buffer.resize(64);
strftime(buffer.data(), sizeof(buffer), "%Y-%m-%d %H:%M:%S", t);
LOG_DEBUG("WS Client set Datetime to: ", buffer.c_str());
}

View File

@@ -0,0 +1,47 @@
#pragma once
#define DEBUGLOG_DEFAULT_LOG_LEVEL_DEBUG
// System includes
#include <Arduino.h>
#include <DebugLog.h>
#include <ESPAsyncWebServer.h>
#include <AsyncTCP.h>
#include <filesystem>
#include <map>
#include <FS.h>
#include <ArduinoJson.h>
class AstroWebServer
{
public:
using WScommand = std::function<void(const ArduinoJson::JsonDocument &)>;
public:
AstroWebServer(const uint8_t port, fs::FS &filesystem);
~AstroWebServer();
void sendWsData(const String &data);
void registerWsCommand(const std::string &cmd, const WScommand func);
void unRegisterWsCommand(const std::string &cmd);
private:
void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client,
AwsEventType type, void *arg, uint8_t *data, size_t len);
void onUploadRequest(AsyncWebServerRequest *request);
void onUploadHandler(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final);
void onSetTme(const ArduinoJson::JsonDocument &doc);
private:
const uint8_t c_port = 80;
const uint32_t c_pingTime = 5000;
fs::FS &m_filesystem;
AsyncWebServer m_webserver;
AsyncWebSocket m_websocket;
bool m_uploadFailed = false;
fs::File m_uploadFile;
TimerHandle_t m_pingTimer = NULL;
std::map<const std::string, AstroWebServer::WScommand> m_webserverCommands;
};

View File

@@ -1,8 +1,7 @@
{ {
"recommendations": [ "recommendations": [
"Jason2866.esp-decoder", "Jason2866.esp-decoder",
"pioarduino.pioarduino-ide", "pioarduino.pioarduino-ide"
"platformio.platformio-ide"
], ],
"unwantedRecommendations": [ "unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack" "ms-vscode.cpptools-extension-pack"

View File

@@ -22,7 +22,7 @@ build_type = release
[env:esp32-devtest-debug] [env:esp32-devtest-debug]
board = esp32dev board = esp32dev
platform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip platform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip
framework = arduino
lib_deps = lib_deps =
hideakitai/DebugLog@^0.8.4 hideakitai/DebugLog@^0.8.4
board_build.flash_size = 4MB board_build.flash_size = 4MB

View File

@@ -0,0 +1,12 @@
#pragma once
// ANSI colors
#define COLOR_RESET "\033[0m"
#define COLOR_RED "\033[31m"
#define COLOR_GREEN "\033[32m"
#define COLOR_BLUE "\033[34m"
#define COLOR_MAGENTA "\033[35m"
#define COLOR_CYAN "\033[36m"
#define COLOR_YELLOW "\033[33m"
#define COLOR_WHITE "\033[37m"
#define COLOR_LBLUE "\033[94m"

View File

@@ -1,7 +1,11 @@
#define DEBUGLOG_DEFAULT_LOG_LEVEL_DEBUG
#include <Arduino.h> #include <Arduino.h>
#include <DebugLog.h> #include <DebugLog.h>
#include "timer.h" #include "timer.h"
#include "colors.h"
#include <map> #include <map>
static hw_timer_t *timerA = NULL; static hw_timer_t *timerA = NULL;
@@ -15,19 +19,27 @@ static uint32_t count = 0;
#define SPARK_DLY_MIN 10 #define SPARK_DLY_MIN 10
#define SPARK_DLY_MAX 490 #define SPARK_DLY_MAX 490
#define PAUSE_LONG_MIN 5000 #define COIL_PULSE_MIN 100
#define PAUSE_LONG_MAX PAUSE_LONG_MIN*100 #define COIL_PULSE_MAX 1000
#define RPM_MIN 800 #define SPARK_PULSE_MIN 10
#define SPARK_PULSE_MAX 500
#define PAUSE_LONG_MIN 5000
#define PAUSE_LONG_MAX PAUSE_LONG_MIN * 100
#define RPM_MIN 250
#define RPM_MAX 5500 #define RPM_MAX 5500
void clearScreen(){ void clearScreen()
Serial.print("\033[2J"); // clear screen {
Serial.print("\033[H"); // cursor home Serial.print("\033[2J"); // clear screen
Serial.flush(); Serial.print("\033[H"); // cursor home
Serial.flush();
} }
static double filtered_rpm = 0; static uint32_t set_rpm = 500;
static uint32_t set_delay = 100;
static const std::map<const uint32_t, const char *> pin2Name = { static const std::map<const uint32_t, const char *> pin2Name = {
{PIN_TRIG_A12P, "HIGH_PIN_TRIG_A12P"}, {PIN_TRIG_A12P, "HIGH_PIN_TRIG_A12P"},
@@ -46,19 +58,48 @@ static const std::map<const uint32_t, const char *> pin2Name = {
{State::S_WAIT_10MS, "S_WAIT_10MS"}}; {State::S_WAIT_10MS, "S_WAIT_10MS"}};
static timerStatus stsA = { static timerStatus stsA = {
.clock_period_us = (uint32_t)PERIOD_US, .clock_period_us = (uint32_t)PERIOD_US,
.pause_long_us = 10000, .pause_long_us = 10000,
.pause_short_us = 1000, .pause_short_us = 1000,
.coil_pulse_us = 1000, .coil_pulse_us = 1000,
.spark_pulse_us = 100, .spark_pulse_us = 100,
.spark_delay_us = 50, .spark_delay_us = 50,
.main_task = NULL}; .pins = {
.pin_trig_12p = PIN_TRIG_A12P,
.pin_trig_12n = PIN_TRIG_A12N,
.pin_trig_34p = PIN_TRIG_A34P,
.pin_trig_34n = PIN_TRIG_A34N,
.pin_spark_12 = SPARK_A12,
.pin_spark_34 = SPARK_A34},
.main_task = NULL};
static timerStatus stsB = {
.clock_period_us = (uint32_t)PERIOD_US,
.pause_long_us = 10000,
.pause_short_us = 1000,
.coil_pulse_us = 500,
.spark_pulse_us = 100,
.spark_delay_us = 50,
.pins = {
.pin_trig_12p = PIN_TRIG_B12P,
.pin_trig_12n = PIN_TRIG_B12N,
.pin_trig_34p = PIN_TRIG_B34P,
.pin_trig_34n = PIN_TRIG_B34N,
.pin_spark_12 = SPARK_B12,
.pin_spark_34 = SPARK_B34},
.main_task = NULL};
static bool isEnabled_A = false;
static bool isEnabled_B = false;
static String last_command;
void setup() void setup()
{ {
Serial.begin(921600); Serial.begin(115200);
delay(1000); delay(1000);
Serial.setTimeout(100);
LOG_ATTACH_SERIAL(Serial); LOG_ATTACH_SERIAL(Serial);
pinMode(PIN_TRIG_A12P, OUTPUT); pinMode(PIN_TRIG_A12P, OUTPUT);
@@ -76,36 +117,152 @@ void setup()
pinMode(SPARK_B34, OUTPUT); pinMode(SPARK_B34, OUTPUT);
pinMode(SPARK_DELAY_POT, ANALOG); pinMode(SPARK_DELAY_POT, ANALOG);
stsA.main_task = xTaskGetCurrentTaskHandleForCore(1); pinMode(FREQ_POT, ANALOG);
pinMode(ENABLE_PIN_A, INPUT_PULLUP);
pinMode(ENABLE_PIN_B, INPUT_PULLUP);
// get the task handle for the main loop
stsA.main_task = xTaskGetCurrentTaskHandleForCore(1);
stsB.main_task = xTaskGetCurrentTaskHandleForCore(1);
// Begin timer with preset fixed frequency
timerA = timerBegin(FREQUENCY); timerA = timerBegin(FREQUENCY);
timerB = timerBegin(FREQUENCY);
// Stop timers because of autostart
timerStop(timerA);
timerStop(timerB);
// Attach interrupts and call callback every timer expiry
timerAttachInterruptArg(timerA, &onTimer, (void *)&stsA); timerAttachInterruptArg(timerA, &onTimer, (void *)&stsA);
timerAlarm(timerA, 1, true, 0); timerAttachInterruptArg(timerB, &onTimer, (void *)&stsB);
timerAlarm(timerA, 1, true, 0); // infinite number of reloads
timerAlarm(timerB, 1, true, 0);
LOG_INFO("Setup Complete"); LOG_INFO("Setup Complete");
} }
void loop() void loop()
{ {
LOG_INFO("Loop: ", count++); clearScreen();
uint32_t spark_delay = (uint32_t)(map(analogRead(SPARK_DELAY_POT), 0, 4096, SPARK_DLY_MIN, SPARK_DLY_MAX) / PERIOD_US);
stsA.spark_delay_us = spark_delay * PERIOD_US; Serial.printf("\t++++ Loop: %u ++++\n", count++);
if (stsA.spark_delay_us > (SPARK_DLY_MIN + SPARK_DLY_MAX) / 2) {
stsA.soft_start = true; if (isEnabled_A)
stsA.spark_delay_us -= (SPARK_DLY_MIN + SPARK_DLY_MAX) / 2; Serial.println("==== System A is" COLOR_GREEN " ENABLED" COLOR_RESET " ====");
} else { else
stsA.soft_start = false; Serial.println("==== System A is" COLOR_RED " DISABLED" COLOR_RESET " ====");
if (isEnabled_B)
Serial.println("==== System B is" COLOR_GREEN " ENABLED" COLOR_RESET " ====");
else
Serial.println("==== System B is" COLOR_RED " DISABLED" COLOR_RESET " ====");
Serial.printf("Spark Delay uS: %u\n", stsA.spark_delay_us);
Serial.printf("Soft Start: %s\n", stsA.soft_start ? "ENABLED" : "DISABLED");
Serial.printf("Engine Rpm: %u\n", (uint32_t)(set_rpm));
Serial.printf("Coil Pulse: %u uS\n", stsA.coil_pulse_us);
Serial.printf("Spark Pulse: %u uS\n", stsA.spark_pulse_us);
Serial.println(COLOR_CYAN "-------------------------------------");
Serial.println("E[a/b] > Enable Box a/b | D[a/b] > Disable a/b");
Serial.println("S[ddd] > Spark Delay | R[dddd] > Engine RPM");
Serial.println("C[ddd] > Spark Pulse | P[ddd] > Coil Pulse");
Serial.println("-------------------------------------" COLOR_RESET);
Serial.printf("Last Command: %s\n", last_command.c_str());
auto str = Serial.readStringUntil('\n');
if (!str.isEmpty())
{
last_command = str;
const auto cmd = str.charAt(0);
char c;
switch (cmd)
{
case 'E':
{
char box;
sscanf(str.c_str(), "%c%c\n", &c, &box);
if (box == 'a' && !isEnabled_A)
{
timerStart(timerA);
isEnabled_A = true;
}
else if (box == 'b' && !isEnabled_B)
{
timerStart(timerB);
isEnabled_B = true;
}
break;
}
case 'D':
{
char c;
char box;
sscanf(str.c_str(), "%c%c\n", &c, &box);
if (box == 'a' && isEnabled_A)
{
timerStop(timerA);
isEnabled_A = false;
}
else if (box == 'b' && isEnabled_B)
{
timerStop(timerB);
isEnabled_B = false;
}
break;
}
case 'R':
{
int new_rpm;
sscanf(str.c_str(), "%c%d\n", &c, &new_rpm);
new_rpm = min(RPM_MAX, max(RPM_MIN, new_rpm));
stsA.pause_long_us = (uint32_t)(60000000.0f / (float)new_rpm / 2.0f);
stsB.pause_long_us = stsA.pause_long_us;
set_rpm = (uint32_t)new_rpm;
break;
}
case 'S':
{
int new_delay;
sscanf(str.c_str(), "%c%d\n", &c, &new_delay);
new_delay = min(SPARK_DLY_MAX, max(SPARK_DLY_MIN, new_delay));
stsA.spark_delay_us = (uint32_t)(new_delay);
if (stsA.spark_delay_us > (SPARK_DLY_MIN + SPARK_DLY_MAX) / 2)
{
stsA.soft_start = true;
stsA.spark_delay_us -= (SPARK_DLY_MIN + SPARK_DLY_MAX) / 2;
}
else
{
stsA.soft_start = false;
}
stsB.soft_start = stsA.soft_start;
stsB.spark_delay_us = stsA.spark_delay_us;
break;
}
case 'P':
{
int new_pulse;
sscanf(str.c_str(), "%c%d\n", &c, &new_pulse);
new_pulse = min(COIL_PULSE_MAX, max(COIL_PULSE_MIN, new_pulse));
stsA.coil_pulse_us = stsB.coil_pulse_us = (uint32_t)new_pulse;
break;
}
case 'C':
{
int new_pulse;
sscanf(str.c_str(), "%c%d\n", &c, &new_pulse);
new_pulse = min(SPARK_PULSE_MAX, max(SPARK_PULSE_MIN, new_pulse));
stsA.spark_pulse_us = stsB.spark_pulse_us = (uint32_t)new_pulse;
break;
}
default:
break;
}
Serial.read();
} }
double new_rpm = (double)(map(analogRead(FREQ_POT), 0, 4096, RPM_MIN, RPM_MAX)); str.clear();
filtered_rpm = filtered_rpm + 0.1 * (new_rpm - filtered_rpm); delay(1000);
stsA.pause_long_us = (uint32_t)(60000000.0f / filtered_rpm / 2.0f);
LOG_INFO("Spark Delay uS: ", stsA.spark_delay_us, "\tSoft Start: ", stsA.soft_start ? "TRUE" : "FALSE");
LOG_INFO("Engine Rpm: ", (uint32_t)(filtered_rpm));
LOG_INFO("Coil Pulse: ", stsA.coil_pulse_us, "us");
LOG_INFO("Spark Pulse: ", stsA.spark_pulse_us, "us");
delay(100);
clearScreen();
} }

View File

@@ -1,5 +1,9 @@
#pragma once #pragma once
// Enable Pin
#define ENABLE_PIN_A 16
#define ENABLE_PIN_B 15
///// Ignition Box A ///// ///// Ignition Box A /////
#define PIN_TRIG_A12P 18 #define PIN_TRIG_A12P 18
#define PIN_TRIG_A12N 19 #define PIN_TRIG_A12N 19

View File

@@ -7,20 +7,18 @@ void onTimer(void *arg)
BaseType_t xHigherPriorityTaskWoken = pdFALSE; BaseType_t xHigherPriorityTaskWoken = pdFALSE;
timerStatus *params = (timerStatus *)(arg); timerStatus *params = (timerStatus *)(arg);
TaskHandle_t task = params->main_task; TaskHandle_t task = params->main_task;
const timerPins pins = params->pins;
// increment state time // increment state time
params->state_time += params->clock_period_us; params->state_time += params->clock_period_us;
digitalWrite(PIN_TRIG_B12P, HIGH);
switch (params->state) switch (params->state)
{ {
case S_12P: case S_12P:
if (params->state_time == params->clock_period_us && !params->coil12p_high) if (params->state_time == params->clock_period_us && !params->coil12p_high)
{ {
// xTaskNotifyFromISR(task, PIN_TRIG_A12P, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); digitalWrite(pins.pin_trig_12p, HIGH);
digitalWrite(PIN_TRIG_A12P, HIGH);
params->coil12p_high = true; params->coil12p_high = true;
wait_sent = false; wait_sent = false;
} }
@@ -29,21 +27,18 @@ void onTimer(void *arg)
{ {
if (params->state_time == params->spark_delay_us) if (params->state_time == params->spark_delay_us)
{ {
// xTaskNotifyFromISR(task, SPARK_A12, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); digitalWrite(pins.pin_spark_12, HIGH);
digitalWrite(SPARK_A12, HIGH);
} }
if (params->state_time == (params->spark_delay_us + params->spark_pulse_us)) if (params->state_time == (params->spark_delay_us + params->spark_pulse_us))
{ {
// xTaskNotifyFromISR(task, ~SPARK_A12, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); digitalWrite(pins.pin_spark_12, LOW);
digitalWrite(SPARK_A12, LOW);
} }
} }
if (params->state_time >= params->coil_pulse_us && params->coil12p_high) if (params->state_time >= params->coil_pulse_us && params->coil12p_high)
{ {
// xTaskNotifyFromISR(task, ~PIN_TRIG_A12P, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); digitalWrite(pins.pin_trig_12p, LOW);
digitalWrite(PIN_TRIG_A12P, LOW);
params->coil12p_high = false; params->coil12p_high = false;
} }
@@ -57,8 +52,7 @@ void onTimer(void *arg)
case S_12N: case S_12N:
if (params->state_time == params->clock_period_us && !params->coil12n_high) if (params->state_time == params->clock_period_us && !params->coil12n_high)
{ {
// xTaskNotifyFromISR(task, PIN_TRIG_A12N, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); digitalWrite(pins.pin_trig_12n, HIGH);
digitalWrite(PIN_TRIG_A12N, HIGH);
params->coil12n_high = true; params->coil12n_high = true;
} }
@@ -66,21 +60,18 @@ void onTimer(void *arg)
{ {
if (params->state_time == params->spark_delay_us) if (params->state_time == params->spark_delay_us)
{ {
// xTaskNotifyFromISR(task, SPARK_A12, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); digitalWrite(pins.pin_spark_12, HIGH);
digitalWrite(SPARK_A12, HIGH);
} }
if (params->state_time == (params->spark_delay_us + params->spark_pulse_us)) if (params->state_time == (params->spark_delay_us + params->spark_pulse_us))
{ {
// xTaskNotifyFromISR(task, ~SPARK_A12, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); digitalWrite(pins.pin_spark_12, LOW);
digitalWrite(SPARK_A12, LOW);
} }
} }
if (params->state_time >= params->coil_pulse_us && params->coil12n_high) if (params->state_time >= params->coil_pulse_us && params->coil12n_high)
{ {
// xTaskNotifyFromISR(task, ~PIN_TRIG_A12N, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); digitalWrite(pins.pin_trig_12n, LOW);
digitalWrite(PIN_TRIG_A12N, LOW);
params->coil12n_high = false; params->coil12n_high = false;
params->state = S_WAIT_10MS; params->state = S_WAIT_10MS;
params->state_time = 0; params->state_time = 0;
@@ -90,7 +81,6 @@ void onTimer(void *arg)
case S_WAIT_10MS: case S_WAIT_10MS:
if (!wait_sent) if (!wait_sent)
{ {
// xTaskNotifyFromISR(task, S_WAIT_10MS, eSetValueWithOverwrite, &xHigherPriorityTaskWoken);
wait_sent = true; wait_sent = true;
} }
if (params->state_time >= params->pause_long_us) if (params->state_time >= params->pause_long_us)
@@ -103,8 +93,7 @@ void onTimer(void *arg)
case S_34P: case S_34P:
if (params->state_time == params->clock_period_us && !params->coil34p_high) if (params->state_time == params->clock_period_us && !params->coil34p_high)
{ {
// xTaskNotifyFromISR(task, PIN_TRIG_A34P, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); digitalWrite(pins.pin_trig_34p, HIGH);
digitalWrite(PIN_TRIG_A34P, HIGH);
params->coil34p_high = true;; params->coil34p_high = true;;
wait_sent = false; wait_sent = false;
} }
@@ -113,21 +102,18 @@ void onTimer(void *arg)
{ {
if (params->state_time == params->spark_delay_us) if (params->state_time == params->spark_delay_us)
{ {
// xTaskNotifyFromISR(task, SPARK_A34, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); digitalWrite(pins.pin_spark_34, HIGH);
digitalWrite(SPARK_A34, HIGH);
} }
if (params->state_time == params->spark_delay_us + params->spark_pulse_us) if (params->state_time == params->spark_delay_us + params->spark_pulse_us)
{ {
// xTaskNotifyFromISR(task, ~SPARK_A34, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); digitalWrite(pins.pin_spark_34, LOW);
digitalWrite(SPARK_A34, LOW);
} }
} }
if (params->state_time >= params->coil_pulse_us && params->coil34p_high) if (params->state_time >= params->coil_pulse_us && params->coil34p_high)
{ {
// xTaskNotifyFromISR(task, ~PIN_TRIG_A34P, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); digitalWrite(pins.pin_trig_34p, LOW);
digitalWrite(PIN_TRIG_A34P, LOW);
params->coil34p_high = false; params->coil34p_high = false;
} }
@@ -141,8 +127,7 @@ void onTimer(void *arg)
case S_34N: case S_34N:
if (params->state_time == params->clock_period_us && !params->coil34n_high) if (params->state_time == params->clock_period_us && !params->coil34n_high)
{ {
// xTaskNotifyFromISR(task, PIN_TRIG_A34N, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); digitalWrite(pins.pin_trig_34n, HIGH);
digitalWrite(PIN_TRIG_A34N, HIGH);
params->coil34n_high = true; params->coil34n_high = true;
} }
@@ -150,21 +135,18 @@ void onTimer(void *arg)
{ {
if (params->state_time == params->spark_delay_us) if (params->state_time == params->spark_delay_us)
{ {
// xTaskNotifyFromISR(task, SPARK_A34, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); digitalWrite(pins.pin_spark_34, HIGH);
digitalWrite(SPARK_A34, HIGH);
} }
if (params->state_time == params->spark_delay_us + params->spark_pulse_us) if (params->state_time == params->spark_delay_us + params->spark_pulse_us)
{ {
// xTaskNotifyFromISR(task, ~SPARK_A34, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); digitalWrite(pins.pin_spark_34, LOW);
digitalWrite(SPARK_A34, LOW);
} }
} }
if (params->state_time >= params->coil_pulse_us && params->coil34n_high) if (params->state_time >= params->coil_pulse_us && params->coil34n_high)
{ {
// xTaskNotifyFromISR(task, ~PIN_TRIG_A34N, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); digitalWrite(pins.pin_trig_34n, LOW);
digitalWrite(PIN_TRIG_A34N, LOW);
params->coil34n_high = false; params->coil34n_high = false;
params->state = S_WAIT_10MS_END; params->state = S_WAIT_10MS_END;
params->state_time = 0; params->state_time = 0;
@@ -174,7 +156,6 @@ void onTimer(void *arg)
case S_WAIT_10MS_END: case S_WAIT_10MS_END:
if (!wait_sent) if (!wait_sent)
{ {
// xTaskNotifyFromISR(task, S_WAIT_10MS_END, eSetValueWithOverwrite, &xHigherPriorityTaskWoken);
wait_sent = true; wait_sent = true;
} }
if (params->state_time >= params->pause_long_us) if (params->state_time >= params->pause_long_us)
@@ -184,9 +165,7 @@ void onTimer(void *arg)
} }
break; break;
} }
digitalWrite(PIN_TRIG_B12P, LOW);
if (xHigherPriorityTaskWoken) if (xHigherPriorityTaskWoken)
portYIELD_FROM_ISR(); portYIELD_FROM_ISR();
} }

View File

@@ -1,5 +1,7 @@
#pragma once #pragma once
#define DEBUGLOG_DEFAULT_LOG_LEVEL_DEBUG
#include <Arduino.h> #include <Arduino.h>
#include <DebugLog.h> #include <DebugLog.h>
#include "pins.h" #include "pins.h"
@@ -19,6 +21,15 @@ enum State
S_WAIT_10MS_END S_WAIT_10MS_END
}; };
struct timerPins {
const uint8_t pin_trig_12p;
const uint8_t pin_trig_12n;
const uint8_t pin_trig_34p;
const uint8_t pin_trig_34n;
const uint8_t pin_spark_12;
const uint8_t pin_spark_34;
};
struct timerStatus struct timerStatus
{ {
State state = State::S_12P; State state = State::S_12P;
@@ -34,6 +45,7 @@ struct timerStatus
bool coil34p_high = false; bool coil34p_high = false;
bool coil12n_high = false; bool coil12n_high = false;
bool coil34n_high = false; bool coil34n_high = false;
timerPins pins;
TaskHandle_t main_task; TaskHandle_t main_task;
}; };