26 Commits

Author SHA1 Message Date
Obbart 9bb66a9459 re enable interrupt logic for ADC drdy 2026-04-21 22:32:01 +02:00
Obbart aa9935ef22 reorder upload and monitor ports 2026-04-21 22:25:56 +02:00
Obbart 79dbd5db5d added some fake commands 2026-04-21 22:22:59 +02:00
Obbart 94c5c7491a file cleanup 2026-04-21 22:22:47 +02:00
Obbart 5ca3d3a46b Added module datasheet 2026-04-21 21:53:22 +02:00
Obbart 6f372fcb49 Vhanged pin assignment to avoid 35,36,37 used in QSPI PSRAM 2026-04-21 21:51:58 +02:00
Obbart fec59815a6 Merge branch 'ioexpander' into debug 2026-04-21 16:16:16 +02:00
Obbart 7e7d0a1c59 Second ADC debugging in process 2026-04-21 16:11:07 +02:00
Obbart 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
Obbart 5aa5aaa07a ADC Testing 2026-04-17 09:13:05 +02:00
Obbart 1b7a531d54 Updated test instrument with cli commands 2026-04-17 09:11:41 +02:00
Obbart 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
Obbart a153402d28 webpage chats 2026-04-12 02:38:27 +02:00
Obbart 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
Obbart 684c34e209 adding pins and task class 2026-04-11 12:27:19 +02:00
Obbart 37fa6a686f Merge branch 'datasave' 2026-04-11 11:40:20 +02:00
27 changed files with 2415 additions and 1342 deletions
Binary file not shown.
File diff suppressed because one or more lines are too long
+181 -157
View File
@@ -13,176 +13,200 @@
<img src="logo_astro_dev.svg" alt="Astro Tecnologie" class="logo">
</div>
<div>
<h1>Rotax Ignition Box Monitor</h1>
</div>
</header>
<div id="loadingIndicator" class="loading-indicator">
<span class="spinner"></span> Waiting for data...
<!-- 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>
<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>Generator voltage:</strong> <span id="a_volts_gen">-</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>
<!-- 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="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>Generator voltage:</strong> <span id="b_volts_gen">-</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 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>Generator voltage:</strong> <span id="a_volts_gen">-</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="rpm-highlight">
<strong>Engine RPM:</strong> <span id="b_eng_rpm">-</span>
<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>Generator voltage:</strong> <span id="b_volts_gen">-</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>
<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> <!-- 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>
<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="script.js"></script>
</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>
+128
View File
@@ -3,6 +3,26 @@ 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;
@@ -25,6 +45,11 @@ function connectWS() {
console.log("WebSocket connesso");
lastMessageTimestamp = Date.now();
setLoadingIndicator(false);
ws.send(JSON.stringify({
cmd: "setTime",
time: Math.floor(Date.now() / 1000)
}));
};
ws.onclose = () => {
@@ -46,6 +71,8 @@ function connectWS() {
lastMessageTimestamp = Date.now();
setLoadingIndicator(false);
updateCharts(data)
// Update Box_A
if (data.box_a) {
const boxA = data.box_a;
@@ -118,6 +145,51 @@ function connectWS() {
};
}
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");
}
@@ -160,5 +232,61 @@ function uploadLittleFS() {
});
}
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();
+38
View File
@@ -219,3 +219,41 @@ button:hover {
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
+129 -103
View File
@@ -1,11 +1,11 @@
//ADS1256 header file
// 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
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
*/
@@ -14,51 +14,55 @@
#define _ADS1256_h
#include <SPI.h>
#include <Arduino.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
// SPI Frequency
#define SPI_FREQ 1920000
//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
// 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
//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
// 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
#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
// 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
// Status register
#define BITORDER_MSB 0
#define BITORDER_LSB 1
#define ACAL_DISABLED 0
@@ -66,7 +70,7 @@
#define BUFFER_DISABLED 0
#define BUFFER_ENABLED 1
//Register addresses
// Register addresses
#define STATUS_REG 0x00
#define MUX_REG 0x01
#define ADCON_REG 0x02
@@ -79,7 +83,7 @@
#define FSC1_REG 0x09
#define FSC2_REG 0x0A
//Command definitions
// Command definitions
#define WAKEUP 0b00000000
#define RDATA 0b00000001
#define RDATAC 0b00000011
@@ -96,26 +100,30 @@
#define RESET 0b11111110
//----------------------------------------------------------------
class ADS1256
{
{
public:
static constexpr int8_t PIN_UNUSED = -1;
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
// 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
// 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();
@@ -128,62 +136,80 @@ static constexpr int8_t PIN_UNUSED = -1;
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);
uint8_t readGPIO(uint8_t gpioPin);
void setCLKOUT(uint8_t clkout);
void setSDCS(uint8_t sdcs);
void sendDirectCommand(uint8_t directCommand);
void setSDCS(uint8_t sdcs);
void sendDirectCommand(uint8_t directCommand);
//Get a single conversion
// Get a single conversion
long readSingle();
//Single input continuous reading
// 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
// 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
// Stop AD
void stopConversion();
// functions for callback
inline uint8_t getDRDYpin()
{
return m_DRDY_pin;
}
SemaphoreHandle_t getDRDYsemaphoreHigh()
{
return m_drdyHigh;
}
SemaphoreHandle_t getDRDYsemaphoreLow()
{
return m_drdyLow;
}
private:
SPIClass* _spi; //Pointer to an SPIClass object
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 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
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
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
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
// 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
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
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;
};
#endif
+8 -2
View File
@@ -4,6 +4,7 @@ RGBled::RGBled(const uint8_t pin) : m_led(pin)
{
pinMode(m_led, OUTPUT);
writeStatus(RGBled::ERROR);
m_brightness = 1.0f;
}
RGBled::~RGBled()
@@ -11,6 +12,11 @@ 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)
@@ -27,6 +33,6 @@ const RGBled::LedStatus RGBled::getSatus(void)
void RGBled::writeStatus(const RGBled::LedStatus s)
{
RGBled::color_u u{.status = s};
rgbLedWrite(m_led, u.color.r, u.color.g, u.color.b);
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));
}
+3 -1
View File
@@ -37,7 +37,7 @@ public:
struct color_t
{
uint8_t a, g, r, b;
uint8_t a, r, g, b;
};
union color_u
@@ -50,6 +50,7 @@ public:
RGBled(const uint8_t pin = 48);
~RGBled();
void setBrightness(const float b);
void setStatus(const LedStatus s);
const LedStatus getSatus(void);
@@ -59,5 +60,6 @@ private:
private:
LedStatus m_status = LedStatus::IDLE;
std::mutex m_mutex;
float m_brightness;
const uint8_t m_led;
};
+4 -8
View File
@@ -20,11 +20,10 @@ lib_deps =
hideakitai/PCA95x5@^0.1.3
me-no-dev/AsyncTCP@^3.3.2
me-no-dev/ESPAsyncWebServer@^3.6.0
adafruit/Adafruit NeoPixel@^1.15.4
upload_protocol = esptool
upload_port = /dev/ttyACM1
upload_port = /dev/ttyACM0
upload_speed = 921600
monitor_port = /dev/ttyACM0
monitor_port = /dev/ttyACM1
monitor_speed = 921600
build_type = release
build_flags =
@@ -36,7 +35,6 @@ build_flags =
-DCONFIG_ASYNC_TCP_QUEUE_SIZE=64
-DCONFIG_ASYNC_TCP_RUNNING_CORE=1
-DCONFIG_ASYNC_TCP_STACK_SIZE=4096
-fstack-protector-all
[env:esp32-s3-devkitc1-n16r8-debug]
board = ${env:esp32-s3-devkitc1-n16r8.board}
@@ -46,11 +44,10 @@ platform = ${env:esp32-s3-devkitc1-n16r8.platform}
framework = ${env:esp32-s3-devkitc1-n16r8.framework}
lib_deps =
${env:esp32-s3-devkitc1-n16r8.lib_deps}
adafruit/Adafruit NeoPixel@^1.15.4
upload_protocol = esptool
upload_port = /dev/ttyACM1
upload_port = /dev/ttyACM0
upload_speed = 921600
monitor_port = /dev/ttyACM0
monitor_port = /dev/ttyACM1
monitor_speed = 921600
debug_tool = esp-builtin
debug_speed = 15000
@@ -67,4 +64,3 @@ build_flags =
-DCONFIG_ASYNC_TCP_QUEUE_SIZE=64
-DCONFIG_ASYNC_TCP_RUNNING_CORE=1
-DCONFIG_ASYNC_TCP_STACK_SIZE=4096
-fstack-protector-all
+9 -107
View File
@@ -1,8 +1,6 @@
#include "datasave.h"
#include <math.h>
static const size_t min_free = 1024 * 1024; // minimum free space in LittleFS to allow saving history (1MB)
LITTLEFSGuard::LITTLEFSGuard()
{
if (!LittleFS.begin(true, "/littlefs", 10, "littlefs"))
@@ -12,7 +10,7 @@ LITTLEFSGuard::LITTLEFSGuard()
else
{
LOG_INFO("LittleFS mounted successfully");
LOG_INFO("LittleFS Free KBytes:", (LittleFS.totalBytes() - LittleFS.usedBytes()) /1024);
LOG_INFO("LittleFS Free KBytes:", (LittleFS.totalBytes() - LittleFS.usedBytes()) / 1024);
}
}
@@ -22,26 +20,26 @@ LITTLEFSGuard::~LITTLEFSGuard()
LOG_INFO("LittleFS unmounted successfully");
}
void ignitionBoxStatusAverage::filter(int32_t &old, const int32_t value, const uint32_t k)
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 ignitionBoxStatusAverage::filter(float &old, const float value, const uint32_t k)
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 ignitionBoxStatusAverage::reset()
void ignitionBoxStatusFiltered::reset()
{
m_last = ignitionBoxStatus();
m_count = 0;
m_data_valid = false;
}
void ignitionBoxStatusAverage::update(const ignitionBoxStatus &new_status)
void ignitionBoxStatusFiltered::update(const ignitionBoxStatus &new_status)
{
if (m_count == 0 && !m_data_valid)
{
@@ -49,8 +47,7 @@ void ignitionBoxStatusAverage::update(const ignitionBoxStatus &new_status)
}
m_count++;
// simple moving average calculation
m_last.timestamp = new_status.timestamp; // keep timestamp of latest status
m_last.timestamp = new_status.timestamp; // keep timestamp of latest status
m_last.coils12.n_events = new_status.coils12.n_events; // sum events instead of averaging
m_last.coils12.n_missed_firing = new_status.coils12.n_missed_firing; // sum missed firings instead of averaging
m_last.coils12.spark_status = new_status.coils12.spark_status; // take latest spark status
@@ -72,7 +69,7 @@ void ignitionBoxStatusAverage::update(const ignitionBoxStatus &new_status)
filter(m_last.coils34.peak_n_out, new_status.coils34.peak_n_out, m_max_count); // incremental average calculation
filter(m_last.eng_rpm, new_status.eng_rpm, m_max_count); // incremental average calculation // incremental average calculation
filter(m_last.adc_read_time, m_last.adc_read_time, m_max_count); // incremental average calculation
m_last.n_queue_errors = new_status.n_queue_errors; // take last of queue errors since it's a cumulative count of errors in the queue, not an average value
m_last.n_queue_errors = new_status.n_queue_errors;
if (m_count >= m_max_count)
{
@@ -81,7 +78,7 @@ void ignitionBoxStatusAverage::update(const ignitionBoxStatus &new_status)
}
}
const bool ignitionBoxStatusAverage::get(ignitionBoxStatus &status) const
const bool ignitionBoxStatusFiltered::get(ignitionBoxStatus &status) const
{
if (m_data_valid)
{
@@ -90,7 +87,7 @@ const bool ignitionBoxStatusAverage::get(ignitionBoxStatus &status) const
return m_data_valid;
}
const ArduinoJson::JsonDocument ignitionBoxStatusAverage::toJson() const
const ArduinoJson::JsonDocument ignitionBoxStatusFiltered::toJson() const
{
ArduinoJson::JsonDocument doc;
if (m_data_valid)
@@ -124,98 +121,3 @@ const ArduinoJson::JsonDocument ignitionBoxStatusAverage::toJson() const
}
return doc;
}
void saveHistoryTask(void *pvParameters)
{
const auto *params = static_cast<dataSaveParams *>(pvParameters);
const auto &history = *params->history;
const auto &file_path = params->file_path;
if (!params)
{
LOG_ERROR("Invalid parameters for saveHistoryTask");
return;
}
LOG_DEBUG("Starting saving: ", file_path.c_str());
save_history(history, file_path);
vTaskDelete(NULL);
}
void save_history(const PSRAMVector<ignitionBoxStatus> &history, const std::filesystem::path &file_name)
{
// Initialize SPIFFS
if (!SAVE_HISTORY_TO_LITTLEFS)
return;
auto littlefs_guard = LITTLEFSGuard(); // use RAII guard to ensure LittleFS is properly mounted and unmounted
if (LittleFS.totalBytes() - LittleFS.usedBytes() < min_free) // check if at least 1MB is free for saving history
{
LOG_ERROR("Not enough space in SPIFFS to save history");
return;
}
std::filesystem::path file_path = file_name;
if (file_name.root_path() != "/littlefs")
file_path = std::filesystem::path("/littlefs") / file_name;
auto save_flags = std::ios::out;
if (first_save && LittleFS.exists(file_path.c_str()))
{
first_save = false;
save_flags |= std::ios::trunc; // overwrite existing file
LittleFS.remove(file_path.c_str()); // ensure file is removed before saving to avoid issues with appending to existing file in SPIFFS
LOG_INFO("Saving history to LittleFS, new file:", file_path.c_str());
}
else
{
save_flags |= std::ios::app; // append to new file
LOG_INFO("Saving history to LittleFS, appending to existing file:", file_path.c_str());
}
std::ofstream ofs(file_path, save_flags);
if (ofs.fail())
{
LOG_ERROR("Failed to open file for writing");
return;
}
// write csv header
if (first_save)
{
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();
}
for (const auto &entry : history)
{
ofs << std::to_string(entry.timestamp) << ","
<< std::to_string(entry.coils12.n_events) << ","
<< std::to_string(entry.coils12.spark_delay) << ","
<< std::string(sparkStatusNames.at(entry.coils12.spark_status)) << ","
<< std::to_string(entry.coils12.peak_p_in) << ","
<< std::to_string(entry.coils12.peak_n_in) << ","
<< std::to_string(entry.coils12.peak_p_out) << ","
<< std::to_string(entry.coils12.peak_n_out) << ","
<< std::string(softStartStatusNames.at(entry.coils12.sstart_status)) << ","
<< std::to_string(entry.coils34.n_events) << ","
<< std::to_string(entry.coils34.spark_delay) << ","
<< std::string(sparkStatusNames.at(entry.coils34.spark_status)) << ","
<< std::to_string(entry.coils34.peak_p_in) << ","
<< std::to_string(entry.coils34.peak_n_in) << ","
<< std::to_string(entry.coils34.peak_p_out) << ","
<< std::to_string(entry.coils34.peak_n_out) << ","
<< std::string(softStartStatusNames.at(entry.coils34.sstart_status)) << ","
<< std::to_string(entry.eng_rpm) << ","
<< std::to_string(entry.adc_read_time) << ","
<< std::to_string(entry.n_queue_errors);
ofs << std::endl;
ofs.flush();
}
ofs.close();
LOG_INFO("Ignition A history saved to LittleFS, records written: ", history.size());
}
+3 -17
View File
@@ -14,16 +14,6 @@
#include "isr.h"
#include "psvector.h"
const uint32_t max_history = 256;
const bool SAVE_HISTORY_TO_LITTLEFS = false; // Set to true to enable saving history to LittleFS, false to disable
static bool first_save = true; // flag to indicate if this is the first save (to write header)
struct dataSaveParams
{
const PSRAMVector<ignitionBoxStatus> *history;
const std::filesystem::path file_path;
};
class LITTLEFSGuard
{
public:
@@ -31,7 +21,7 @@ public:
~LITTLEFSGuard();
};
class ignitionBoxStatusAverage
class ignitionBoxStatusFiltered
{
private:
ignitionBoxStatus m_last;
@@ -40,8 +30,8 @@ private:
bool m_data_valid = false; // flag to indicate if the average data is valid (i.e. at least one sample has been added)
public:
ignitionBoxStatusAverage() = default;
ignitionBoxStatusAverage(const uint32_t max_count) : m_max_count(max_count)
ignitionBoxStatusFiltered() = default;
ignitionBoxStatusFiltered(const uint32_t max_count) : m_max_count(max_count)
{
m_data_valid = false;
m_count = 0;
@@ -56,7 +46,3 @@ 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);
};
// Task and function declarations
void saveHistoryTask(void *pvParameters);
void save_history(const PSRAMVector<ignitionBoxStatus> &history, const std::filesystem::path &file_path);
+36 -18
View File
@@ -3,35 +3,53 @@
// Library defines
#define ADS1256_SPI_ALREADY_STARTED
// System Includes
#include <memory>
// Device Libraries
#include <ADS1256.h>
#include <AD5292.h>
#include <PCA95x5.h>
#include <extio.h>
#include <Wire.h>
// ADC Channel mapping
#define ADC_CH_PEAK_12P_IN SING_0
#define ADC_CH_PEAK_12N_IN SING_1
#define ADC_CH_PEAK_34P_IN SING_2
#define ADC_CH_PEAK_34N_IN SING_3
#define ADC_CH_PEAK_12P_OUT SING_4
#define ADC_CH_PEAK_12N_OUT SING_5
#define ADC_CH_PEAK_34P_OUT SING_6
#define ADC_CH_PEAK_34N_OUT SING_7
#define ADC_CH_PEAK_12P_IN SING_0
#define ADC_CH_PEAK_12N_IN SING_1
#define ADC_CH_PEAK_34P_IN SING_2
#define ADC_CH_PEAK_34N_IN SING_3
#define ADC_CH_PEAK_12P_OUT SING_4
#define ADC_CH_PEAK_12N_OUT SING_5
#define ADC_CH_PEAK_34P_OUT SING_6
#define ADC_CH_PEAK_34N_OUT SING_7
// Device Pointer structs for tasks
struct Devices {
AD5292 *pot_a = NULL, *pot_b = NULL;
ADS1256 *adc_a = NULL, *adc_b = NULL;
PCA9555* io = NULL;
struct Devices
{
// Busses
TwoWire *m_i2c = 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
inline float adcReadChannel(ADS1256* adc, const uint8_t ch){
inline float adcReadChannel(ADS1256 *adc, const uint8_t ch)
{
adc->setMUX(ch);
// scarta 3 conversioni
for (int i = 0; i < 3; i++) {
adc->readSingle();
}
adc->readSingle();
// ora lettura valida a 30kSPS → ~100 µs di settling
return adc->convertToVoltage(adc->readSingle());
}
+129
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
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;
};
+2 -2
View File
@@ -16,8 +16,8 @@
#define CORE_0 0
#define CORE_1 1
#define RT_TASK_STACK 2048 // in words
#define RT_TASK_PRIORITY (configMAX_PRIORITIES - 6) // highest priority after wifi tasks
#define RT_TASK_STACK 4096 // in words
#define RT_TASK_PRIORITY (configMAX_PRIORITIES - 5) // highest priority after wifi tasks
struct isrParams
{
+228 -223
View File
@@ -16,13 +16,19 @@
#include <ui.h>
#include <led.h>
// Defines to enable channel B
#define CH_B_ENABLE
#define TEST
// #define CH_A_ENABLE
// #define CH_B_ENABLE
#define CH_A_RT_ENABLE
#define CH_B_RT_ENABLE
// #define I2C_ENABLE
#define WEB_ENABLE
// Debug Defines
#define WIFI_SSID "AstroRotaxMonitor"
#define WIFI_PASSWORD "maledettirotax"
#define PSRAM_MAX 4096
#define QUEUE_MAX 128
#define HTOP_DELAY 2000
void setup()
{
@@ -31,7 +37,7 @@ void setup()
// Setup Logger
LOG_ATTACH_SERIAL(Serial);
LOG_SET_LEVEL(DebugLogLevel::LVL_INFO);
LOG_SET_LEVEL(DebugLogLevel::LVL_DEBUG);
// Print Processor Info
LOG_DEBUG("ESP32 Chip:", ESP.getChipModel());
@@ -45,13 +51,15 @@ void setup()
LOG_DEBUG("ESP32 Heap:", ESP.getHeapSize());
LOG_DEBUG("ESP32 Sketch:", ESP.getFreeSketchSpace());
// Init Wifi station
// 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");
@@ -66,6 +74,7 @@ void setup()
vTaskDelay(pdMS_TO_TICKS(5000));
esp_restart();
}
#endif
// Initialize Interrupt pins on PICKUP detectors
initTriggerPinsInputs();
@@ -78,86 +87,55 @@ void loop()
{
// global variables
RGBled led;
led.setBrightness(0.025f);
led.setStatus(RGBled::LedStatus::INIT);
bool running = true;
const uint32_t max_queue = 128;
const uint32_t filter_k = 10;
PSRAMVector<ignitionBoxStatus> ignA_history_0(max_history);
PSRAMVector<ignitionBoxStatus> ignA_history_1(max_history);
auto *active_history_A = &ignA_history_0;
auto *writable_history_A = &ignA_history_1;
#ifdef CH_B_ENABLE
PSRAMVector<ignitionBoxStatus> ignB_history_0(max_history);
PSRAMVector<ignitionBoxStatus> ignB_history_1(max_history);
auto *active_history_B = &ignB_history_0;
auto *writable_history_B = &ignB_history_1;
#endif
// Resources Initialization
Devices dev;
// Task handle
TaskHandle_t trigA_TaskHandle = NULL;
TaskHandle_t trigB_TaskHandle = NULL;
// Data Queue for real time task to main loop communication
QueueHandle_t rt_taskA_queue = xQueueCreate(max_queue, sizeof(ignitionBoxStatus));
QueueHandle_t rt_taskB_queue = xQueueCreate(max_queue, sizeof(ignitionBoxStatus));
bool running = true;
std::mutex fs_mutex;
LITTLEFSGuard fsGuard;
rtTaskParams taskA_params{
.rt_running = true,
.dev = &dev,
.rt_queue = rt_taskA_queue,
.rt_int = rtTaskInterrupts{
.isr_ptr = &trig_isr_A,
.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_peak = RST_EXT_PEAK_DETECT_A, .rst_io_sh = RST_EXT_SAMPLE_HOLD_A}};
#ifdef CH_B_ENABLE
rtTaskParams taskB_params{
.rt_running = true,
.dev = &dev,
.rt_queue = rt_taskB_queue,
.rt_int = rtTaskInterrupts{
.isr_ptr = &trig_isr_B,
.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_peak = RST_EXT_PEAK_DETECT_B, .rst_io_sh = RST_EXT_SAMPLE_HOLD_B}};
#endif
if (!rt_taskA_queue || !rt_taskB_queue)
{
LOG_ERROR("Unable To Create task queues");
LOG_ERROR("5 seconds to restart...");
vTaskDelay(pdMS_TO_TICKS(5000));
esp_restart();
}
else
LOG_DEBUG("Task Variables OK");
// Spi ok flags
//////// INIT SPI INTERFACES ////////
bool spiA_ok = true;
bool spiB_ok = true;
// Init 2 SPI interfaces
SPIClass SPI_A(FSPI);
//////// INIT SPI INTERFACES ////////
LOG_DEBUG("Init SPI Interfaces");
#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);
SPI_A.setDataMode(SPI_MODE1); // ADS1256 requires SPI mode 1
LOG_DEBUG("Init SPI_A -> OK");
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
#ifndef TEST
SPIClass SPI_B(HSPI);
LOG_DEBUG("Begin Init SPI_B");
SPIClass SPI_B(FSPI);
spiB_ok = SPI_B.begin(SPI_B_SCK, SPI_B_MISO, SPI_B_MOSI);
SPI_B.setDataMode(SPI_MODE1); // ADS1256 requires SPI mode 1
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
#endif
if (!spiA_ok || !spiB_ok)
{
LOG_ERROR("Unable to Initialize SPI Busses");
@@ -165,54 +143,115 @@ void loop()
vTaskDelay(pdMS_TO_TICKS(5000));
esp_restart();
}
LOG_DEBUG("Init SPI OK");
#ifndef TEST
// Init ADC_A
dev.adc_a = new ADS1256(ADC_A_DRDY, ADS1256::PIN_UNUSED, ADS1256::PIN_UNUSED, ADC_A_CS, 2.5, &SPI_A);
dev.adc_a->InitializeADC();
dev.adc_a->setPGA(PGA_1);
dev.adc_a->setDRATE(DRATE_7500SPS);
#endif
#ifdef CH_B_ENABLE
#ifndef TEST
// Init ADC_B
dev.adc_a = new ADS1256(ADC_B_DRDY, ADS1256::PIN_UNUSED, ADS1256::PIN_UNUSED, ADC_B_CS, 2.5, &SPI_B);
dev.adc_a->InitializeADC();
dev.adc_a->setPGA(PGA_1);
dev.adc_a->setDRATE(DRATE_1000SPS);
#endif
LOG_DEBUG("Init SPI -> OK");
//////// INIT I2C INTERFACES ////////
#ifdef I2C_ENABLE
LOG_DEBUG("Init I2C Interfaces");
bool i2c_ok = true;
i2c_ok = Wire.begin(SDA, SCL, 100000);
if (!i2c_ok)
{
LOG_ERROR("Unable to Initialize I2C Bus");
LOG_ERROR("5 seconds to restart...");
vTaskDelay(pdMS_TO_TICKS(5000));
esp_restart();
}
LOG_DEBUG("Init I2c ok");
// Init IO Expanders
ExternalIO extIo(Wire, dev.m_i2c_mutex, EXPANDER_ALL_INTERRUPT);
dev.m_ext_io = &extIo;
#endif
LOG_DEBUG("Init ADC OK");
//////// INIT REALTIME TASKS PARAMETERS ////////
#ifdef CH_A_RT_ENABLE
const rtIgnitionTask::rtTaskParams taskA_params{
.rt_running = true,
.name = "rtIgnTask_A",
.rt_stack_size = RT_TASK_STACK,
.rt_priority = RT_TASK_PRIORITY,
.rt_int = rtIgnitionTask::rtTaskInterruptParams{
.isr_ptr = &trig_isr_A,
.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_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{
.isr_ptr = &trig_isr_B,
.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_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
//////// 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
auto ignA_task_success = pdPASS;
ignA_task_success = xTaskCreatePinnedToCore(
rtIgnitionTask,
"rtTask_A",
RT_TASK_STACK,
(void *)&taskA_params,
RT_TASK_PRIORITY,
&trigA_TaskHandle,
CORE_0);
delay(100); // give some time to the thread to start
// Ignition B on Core 1
auto ignB_task_success = pdPASS;
#ifdef CH_B_ENABLE
ignB_task_success = xTaskCreatePinnedToCore(
rtIgnitionTask,
"rtTask_B",
RT_TASK_STACK,
(void *)&taskB_params,
RT_TASK_PRIORITY, // priorità leggermente più alta
&trigB_TaskHandle,
CORE_1);
delay(100); // give some time to the thread to start
#endif
if (ignA_task_success != pdPASS || ignB_task_success != pdPASS)
{
LOG_ERROR("Unable to initialize ISR task");
@@ -220,127 +259,93 @@ void loop()
vTaskDelay(pdMS_TO_TICKS(5000));
esp_restart();
}
if (tasK_A_rt != true || task_B_rt != true)
{
led.setStatus(RGBled::LedStatus::ERROR);
LOG_ERROR("Unable to start realtime tasks");
}
else
{
LOG_DEBUG("Real Time Tasks A & B initialized");
led.setStatus(RGBled::LedStatus::OK);
}
LOG_DEBUG("Real Time Tasks A & B initialized");
led.setStatus(RGBled::LedStatus::OK);
//////// SPAWN WEBSERVER and WEBSOCKET ////////
ArduinoJson::JsonDocument json_data;
bool data_a = false, data_b = false;
#ifdef WEB_ENABLE
AstroWebServer webPage(80, LittleFS);
delay(100);
bool partial_save = false; // flag to indicate if a partial save has been done after a timeout
auto last_data = millis();
auto last_info = millis();
#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
uint32_t counter_a = 0;
uint32_t counter_b = 0;
uint32_t wait_count = 0;
#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
ignitionBoxStatus ign_info_A;
ignitionBoxStatus ign_info_B;
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; });
ignitionBoxStatusAverage ign_info_avg_A(filter_k);
ignitionBoxStatusAverage ign_info_avg_B(filter_k);
webPage.registerWsCommand("saveDisable", [&task_A, &task_B](const ArduinoJson::JsonDocument &doc) {
task_A.enableSave(false, "");
task_B.enableSave(false, ""); });
LITTLEFSGuard fsGuard;
WebPage webPage(80, LittleFS); // Initialize webserver and Websocket
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)
{
auto dataA = pdFALSE;
auto dataB = pdFALSE;
dataA = xQueueReceive(rt_taskA_queue, &ign_info_A, 0);
if (counter_a >= active_history_A->size()) // not concurrent with write task
{
counter_a = 0;
partial_save = false; // reset partial save flag on new data cycle
swapHistory(active_history_A, writable_history_A);
save_history(*writable_history_A, "ignition_historyA.csv"); // directly call the save task function to save without delay
}
#ifdef CH_B_ENABLE
dataB = xQueueReceive(rt_taskB_queue, &ign_info_B, 0);
if (counter_b >= active_history_B->size()) // not concurrent with write task
{
counter_b = 0;
partial_save = false; // reset partial save flag on new data cycle
swapHistory(active_history_B, writable_history_B);
save_history(*writable_history_B, "ignition_historyB.csv"); // directly call the save task function to save without delay
}
#endif
// Update last data
if (dataA == pdTRUE || dataB == pdTRUE)
last_data = millis();
// Update Led color
if (dataA == pdTRUE && dataB == pdFALSE)
led.setStatus(RGBled::DATA_A);
else if (dataB == pdTRUE && dataA == pdFALSE)
led.setStatus(RGBled::DATA_B);
else
led.setStatus(RGBled::DATA_ALL);
if (dataA == pdTRUE)
{
(*active_history_A)[counter_a++ % active_history_A->size()] = ign_info_A;
ign_info_avg_A.update(ign_info_A); // update moving average with latest ignition status
// Serial.printf("Data Received A: %d/%d\n\r", counter_a, (*active_history_A).size());
if (counter_a % filter_k == 0) // send data every 10 samples
{
ArduinoJson::JsonDocument wsData;
wsData["box_a"] = ign_info_avg_A.toJson();
wsData["box_b"] = JsonObject();
webPage.sendWsData(wsData.as<String>());
}
}
#ifdef CH_B_ENABLE
if (dataB == pdTRUE)
{
(*active_history_B)[counter_b++ % active_history_B->size()] = ign_info_B;
ign_info_avg_B.update(ign_info_B); // update moving average with latest ignition status
// Serial.printf("Data Received B: %d/%d\n\r", counter_b, (*active_history_B).size());
if (counter_b % filter_k == 0) // send data every 10 samples
{
ArduinoJson::JsonDocument wsData;
wsData["box_a"] = JsonObject();
wsData["box_b"] = ign_info_avg_B.toJson();
webPage.sendWsData(wsData.as<String>());
}
}
#endif
if (dataA == pdFALSE && dataB == pdFALSE && (millis() - last_data) > 2000)
{
if (!partial_save && counter_a > 0) // if timeout occurs but we have unsaved data, save it before next timeout
{
active_history_A->resize(counter_a); // resize active history to actual number of records received to avoid saving empty records
save_history(*active_history_A, "ignition_history_A.csv");
active_history_A->resize(max_history); // resize back to max history size for next data cycle
#ifdef CH_B_ENABLE
active_history_B->resize(counter_a); // resize active history to actual number of records received to avoid saving empty records
save_history(*active_history_B, "ignition_history_B.csv");
active_history_B->resize(max_history); // resize back to max history size for next data cycle
#endif
counter_a = 0; // reset counter after saving
counter_b = 0; // reset counter after saving
partial_save = true;
first_save = true;
}
// Serial.printf("[%d] Waiting for data...\r", wait_count++);
led.setStatus(RGBled::LedStatus::IDLE);
delay(100);
}
if ((millis() - last_info) > 1000)
uint32_t this_loop = millis();
if (this_loop - monitor_loop > HTOP_DELAY)
{
clearScreen();
Serial.println();
printRunningTasksMod(Serial);
last_info = millis();
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 /////////////////////
if (trigA_TaskHandle)
vTaskDelete(trigA_TaskHandle);
if (trigB_TaskHandle)
vTaskDelete(trigB_TaskHandle);
} ////////////////////// MAIN LOOP //////////////////////
+84 -35
View File
@@ -33,16 +33,15 @@
// =====================
// SPI BUS ADC2 (HSPI)
// =====================
#define SPI_B_MOSI 36
#define SPI_B_SCK 37
#define SPI_B_MISO 38
#define SPI_B_MOSI 17
#define SPI_B_SCK 18
#define SPI_B_MISO 8
// =====================
// I2C BUS (PCA9555)
// =====================
#define SDA 8
#define SCL 9
#define I2C_INT 17
#define SDA 21
#define SCL 38
// =====================
// ADC CONTROL
@@ -50,14 +49,8 @@
#define ADC_A_CS 14
#define ADC_A_DRDY 13
#define ADC_B_CS 21
#define ADC_B_DRDY 47
// =====================
// DIGITAL POT
// =====================
#define POT_A_CS 18
#define POT_B_CS 35
#define ADC_B_CS 3
#define ADC_B_DRDY 9
// =====================
// TRIGGER INPUT INTERRUPTS
@@ -79,31 +72,87 @@
#define SPARK_PIN_B12 1
#define SPARK_PIN_B34 2
// =====================
// PCA9555 (I2C EXPANDER)
// =====================
// +++++++++++++++++++++
// MACRO TO COMBINE PIN NUMBER AND ADDRESS
#define PIN2ADDR(p, a) ((1UL << p) | ((uint32_t)(a) << 16))
// +++++++++++++++++++++
// --- RESET LINES ---
#define RST_EXT_PEAK_DETECT_A 0
#define RST_EXT_SAMPLE_HOLD_A 1
#define RST_EXT_PEAK_DETECT_B 2
#define RST_EXT_SAMPLE_HOLD_B 3
#define BTN_3 4
#define BTN_4 5
#define BTN_5 6
#define BTN_6 7
// =====================
// PCA9555 I/O EXPANDER INTERRUPT (Common)
// =====================
#define EXPANDER_ALL_INTERRUPT 45
// =====================
// PCA9555 I/O EXPANDER BOX_A (OUT)
// =====================
#define EXPANDER_A_OUT_ADDR 0xFF
// --- DIGITAL POT CHIP SELECT LINES ---
#define POT_CS_A12 PIN2ADDR(0, EXPANDER_A_OUT_ADDR)
#define POT_CS_A34 PIN2ADDR(1, EXPANDER_A_OUT_ADDR)
// --- 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 ---
#define EXT_RELAY_A 8
#define EXT_RELAY_B 9
#define RELAY_IN_A12 PIN2ADDR(9, EXPANDER_A_OUT_ADDR)
#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_7 10
#define BTN_8 11
#define STA_1 12
#define STA_2 13
#define STA_3 14
#define STA_4 15
// =====================
// PCA9555 I/O EXPANDER BOX_A (IN)
// =====================
#define EXPANDER_A_IN_ADDR 0xFF
#define SS_A12_ON PIN2ADDR(0, EXPANDER_A_IN_ADDR)
#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 0xFF
// --- 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 0xFF
#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
inline void initTriggerPinsInputs()
+1 -1
View File
@@ -65,7 +65,7 @@
#define RST_EXT_A34N 3
// --- RELAY ---
#define EXT_RELAY_A 8
#define SH_ARM_A34 8
// Init Pin Functions
+318 -33
View File
@@ -1,14 +1,32 @@
#include "tasks.h"
#include <esp_timer.h>
#include <datasave.h>
#include <mutex>
//// GLOBAL STATIC FUNCTIONS
// 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;
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
@@ -18,21 +36,20 @@ void rtIgnitionTask(void *pvParameters)
vTaskDelete(NULL);
}
// Task Parameters and Devices
rtTaskParams *params = (rtTaskParams *)pvParameters;
const rtTaskInterrupts rt_int = params->rt_int; // copy to avoid external override
const rtTaskResets rt_rst = params->rt_resets; // copy to avoid external override
const rtTaskInterruptParams rt_int = params->rt_int; // copy to avoid external override
const rtTaskIOParams rt_rst = params->rt_io; // copy to avoid external override
QueueHandle_t rt_queue = params->rt_queue;
Devices *dev = params->dev;
ADS1256 *adc = dev->adc_a;
PCA9555 *io = dev->io;
ExternalIO *io = dev->m_ext_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;
TaskStatus_t rt_task_info;
vTaskGetInfo(NULL, &rt_task_info, pdFALSE, eInvalid);
const auto rt_task_name = pcTaskGetName(rt_task_info.xHandle);
LOG_INFO("rtTask Params OK [", rt_task_name, "]");
LOG_INFO("rtTask Params OK [", params->name.c_str(), "]");
ignitionBoxStatus ign_box_sts;
@@ -62,10 +79,6 @@ void rtIgnitionTask(void *pvParameters)
.ign_stat = &ign_box_sts,
.rt_handle_ptr = rt_task_info.xHandle};
LOG_DEBUG("rtTask HDL Params OK, HDL* [", (uint32_t)rt_task_info.xHandle, "]");
LOG_DEBUG("rtTask ISR Params OK, ISR* [", (uint32_t)rt_int.isr_ptr, "]");
LOG_DEBUG("rtTask QUE Params OK, QUE* [", (uint32_t)rt_queue, "]");
// Create esp_timer for microsecond precision timeout
esp_timer_handle_t timeout_timer;
esp_timer_create_args_t timer_args = {
@@ -73,8 +86,11 @@ void rtIgnitionTask(void *pvParameters)
.arg = (void *)rt_task_info.xHandle,
.dispatch_method = ESP_TIMER_TASK,
.name = "spark_timeout"};
esp_timer_create(&timer_args, &timeout_timer);
if (esp_timer_create(&timer_args, &timeout_timer) != ESP_OK)
{
LOG_INFO("rtTask [", params->name.c_str(), "] Fail to allocate timeoutTimer");
vTaskDelete(NULL);
}
// Attach Pin Interrupts
attachInterruptArg(digitalPinToInterrupt(rt_int.trig_pin_12p), rt_int.isr_ptr, (void *)&isr_params_t12p, RISING);
@@ -84,7 +100,7 @@ void rtIgnitionTask(void *pvParameters)
attachInterruptArg(digitalPinToInterrupt(rt_int.spark_pin_12), rt_int.isr_ptr, (void *)&isr_params_sp12, RISING);
attachInterruptArg(digitalPinToInterrupt(rt_int.spark_pin_34), rt_int.isr_ptr, (void *)&isr_params_sp34, RISING);
LOG_INFO("rtTask ISR Attach OK [", rt_task_name, "]");
LOG_INFO("rtTask ISR Attach OK [", params->name.c_str(), "]");
// Global rt_task_ptr variables
bool first_cycle = true;
@@ -222,33 +238,44 @@ void rtIgnitionTask(void *pvParameters)
// read adc channels: pickup12, out12 [ pos + neg ]
if (adc) // read only if adc initialized
{
std::lock_guard<std::mutex> lock(spi_mutex);
uint32_t start_adc_read = esp_timer_get_time();
// from peak detector circuits
ign_box_sts.coils12.peak_p_in = adcReadChannel(adc, ADC_CH_PEAK_12P_IN);
ign_box_sts.coils12.peak_n_in = adcReadChannel(adc, ADC_CH_PEAK_12N_IN);
ign_box_sts.coils34.peak_p_in = adcReadChannel(adc, ADC_CH_PEAK_34P_IN);
ign_box_sts.coils34.peak_n_in = adcReadChannel(adc, ADC_CH_PEAK_34N_IN);
ign_box_sts.coils12.peak_p_out = adcReadChannel(adc, ADC_CH_PEAK_12P_OUT);
ign_box_sts.coils12.peak_n_out = adcReadChannel(adc, ADC_CH_PEAK_12N_OUT);
ign_box_sts.coils34.peak_p_out = adcReadChannel(adc, ADC_CH_PEAK_34P_OUT);
ign_box_sts.coils34.peak_n_out = adcReadChannel(adc, ADC_CH_PEAK_34N_OUT);
ign_box_sts.coils12.peak_p_in = adc->convertToVoltage(adc->cycleSingle());
ign_box_sts.coils12.peak_n_in = adc->convertToVoltage(adc->cycleSingle());
ign_box_sts.coils34.peak_p_in = adc->convertToVoltage(adc->cycleSingle());
ign_box_sts.coils34.peak_n_in = adc->convertToVoltage(adc->cycleSingle());
ign_box_sts.coils12.peak_p_out = adc->convertToVoltage(adc->cycleSingle());
ign_box_sts.coils12.peak_n_out = adc->convertToVoltage(adc->cycleSingle());
ign_box_sts.coils34.peak_p_out = adc->convertToVoltage(adc->cycleSingle());
ign_box_sts.coils34.peak_n_out = adc->convertToVoltage(adc->cycleSingle());
ign_box_sts.adc_read_time = (int32_t)(esp_timer_get_time() - start_adc_read);
adc->stopConversion();
}
else // simulate adc read timig
vTaskDelay(pdMS_TO_TICKS(1));
vTaskDelay(pdMS_TO_TICKS(c_adc_time));
// reset peak detectors + sample and hold
// outputs on io expander
if (io)
{
const uint16_t iostat = io->read();
const uint16_t rst_bitmask = (0x0001 << rt_rst.rst_io_peak);
io->write(iostat | rst_bitmask);
vTaskDelay(pdMS_TO_TICKS(1));
io->write(iostat & ~rst_bitmask);
// Discharge Pulse
io->extDigitalWrite(rt_rst.sh_disch_12, true);
io->extDigitalWrite(rt_rst.sh_disch_34, true);
delayMicroseconds(250);
io->extDigitalWrite(rt_rst.sh_disch_12, false);
io->extDigitalWrite(rt_rst.sh_disch_34, false);
// Safety delay
delayMicroseconds(500);
// Re-Arm Pulse
io->extDigitalWrite(rt_rst.sh_arm_12, true);
io->extDigitalWrite(rt_rst.sh_arm_34, true);
delayMicroseconds(250);
io->extDigitalWrite(rt_rst.sh_arm_12, false);
io->extDigitalWrite(rt_rst.sh_arm_34, false);
}
else
vTaskDelay(pdMS_TO_TICKS(1));
vTaskDelay(pdMS_TO_TICKS(c_io_time));
// send essage to main loop with ignition info, by copy so local static variable is ok
if (rt_queue)
@@ -261,7 +288,7 @@ void rtIgnitionTask(void *pvParameters)
}
// Delete the timeout timer
esp_timer_delete(timeout_timer);
LOG_WARN("Ending realTime Task");
LOG_WARN("rtTask Ending [", params->name.c_str(), "]");
// Ignition A Interrupts DETACH
detachInterrupt(rt_int.trig_pin_12p);
detachInterrupt(rt_int.trig_pin_12n);
@@ -272,3 +299,261 @@ void rtIgnitionTask(void *pvParameters)
// delete present task
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_fs_mutex(fs_mutex), m_core(core), m_max_history(history_size)
{
LOG_WARN("Starting Manager for [", m_params.name.c_str(), "]");
// create queue buffers
m_queue = xQueueCreate(queue_size, sizeof(ignitionBoxStatus));
if (!m_queue)
{
LOG_ERROR("Unable To Create Task [", params.name.c_str(), "] queues");
m_manager_status = rtTaskStatus::ERROR;
return;
}
else
m_params.rt_queue = m_queue;
try
{
// create PSram history vectors
m_history_0 = PSHistory(history_size);
m_history_1 = PSHistory(history_size);
// assing active and writable history
m_active_history = std::unique_ptr<PSHistory>(&m_history_0);
m_save_history = std::unique_ptr<PSHistory>(&m_history_1);
}
catch (std::bad_alloc &e)
{
LOG_ERROR("Task [", params.name.c_str(), "] Unable to allocate history PSRAM: ", e.what());
return;
}
m_name = (std::string("man_") + m_params.name).c_str();
auto task_success = xTaskCreatePinnedToCore(
rtIgnitionTask_manager,
m_name.c_str(),
RT_TASK_STACK,
(void *)this,
m_params.rt_priority >> 2,
&m_manager_handle,
m_core);
if (task_success != pdPASS)
{
LOG_ERROR("Unable To Create Manager for [", params.name.c_str(), "]");
m_manager_status = rtTaskStatus::ERROR;
return;
}
// average every 10 samples
m_info_filtered = ignitionBoxStatusFiltered(10);
m_last_data = millis();
m_manager_status = rtTaskStatus::OK;
}
rtIgnitionTask::~rtIgnitionTask()
{
if (m_rt_handle)
vTaskDelete(m_rt_handle);
if (m_manager_handle)
vTaskDelete(m_manager_handle);
if (m_queue)
vQueueDelete(m_queue);
}
void rtIgnitionTask::run()
{
// receive new data from the queue
auto new_data = xQueueReceive(m_queue, &m_last_status, 0); // non blocking receive
if (new_data == pdPASS)
{
m_last_data = millis();
m_manager_status = rtTaskStatus::RUNNING;
// if history buffer is full swap buffers and if enabled save history buffer
if (m_counter_status >= m_max_history)
{
LOG_DEBUG("Save for Buffer Full: ", m_counter_status);
m_counter_status = 0;
m_partial_save = false; // reset partial save flag on new data cycle
std::swap(m_active_history, m_save_history);
if (m_enable_save)
saveHistory(*m_save_history, m_history_path); // directly call the save task function to save without delay
LOG_INFO("Save History");
}
// update filtered data
m_info_filtered.update(m_last_status);
(*m_active_history)[m_counter_status] = m_last_status;
if (m_on_message_cb && m_counter_status % 10 == 0)
{
m_on_message_cb(m_info_filtered);
}
// update data counter
m_counter_status++;
}
else
{
if (millis() - m_last_data > c_idle_time)
{
if (m_counter_status > 0 && !m_partial_save)
{
LOG_DEBUG("Save Partial: ", m_counter_status);
m_active_history->resize(m_counter_status);
saveHistory(*m_active_history, m_history_path);
m_active_history->resize(m_max_history);
m_counter_status = 0;
m_partial_save = true;
}
m_manager_status = 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_rt_handle,
m_core);
const bool success = task_success == pdPASS && m_rt_handle != nullptr;
if (success)
m_manager_status = rtTaskStatus::IDLE;
return success;
}
const bool rtIgnitionTask::stop()
{
LOG_WARN("Ending Task [", m_params.name.c_str(), "]");
if (m_rt_handle)
{
m_params.rt_running = false;
m_rt_handle = nullptr;
m_manager_status = rtTaskStatus::STOPPED;
return true;
}
return false;
}
const ignitionBoxStatus rtIgnitionTask::getLast() const
{
return m_last_status;
}
const ignitionBoxStatusFiltered rtIgnitionTask::getFiltered() const
{
return m_info_filtered;
}
const rtIgnitionTask::rtTaskStatus rtIgnitionTask::getStatus() const
{
return m_manager_status;
}
void rtIgnitionTask::enableSave(const bool enable, const std::filesystem::path filename)
{
m_enable_save = enable;
if (enable && !filename.empty())
{
LOG_WARN("Save History Enabled Task [", m_params.name.c_str(), "]");
m_history_path = 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_on_message_cb = callaback;
}
void rtIgnitionTask::saveHistory(const rtIgnitionTask::PSHistory &history, const std::filesystem::path &file_name)
{
// Lock filesystem mutex to avoid concurrent access
std::lock_guard<std::mutex> fs_lock(m_fs_mutex);
// 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 mount_point = std::filesystem::path(m_filesystem.mountpoint());
std::filesystem::path file_path = file_name;
if (file_name.root_path() != mount_point)
file_path = mount_point / file_name;
// if firt save remove old file and create new
auto save_flags = std::ios::out;
if (m_first_save)
{
save_flags |= std::ios::trunc; // overwrite existing file
m_filesystem.remove(file_path.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:", file_path.c_str());
}
else // else append to existing file
{
save_flags |= std::ios::app; // append to new file
LOG_INFO("Saving history to Flash, appending to existing file:", file_path.c_str());
}
std::ofstream ofs(file_path, save_flags);
if (ofs.fail())
{
LOG_ERROR("Failed to open file for writing");
return;
}
// write csv header
if (m_first_save)
{
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_first_save = false;
}
for (const auto &entry : history)
{
ofs << std::to_string(entry.timestamp) << ","
<< std::to_string(entry.coils12.n_events) << ","
<< std::to_string(entry.coils12.spark_delay) << ","
<< std::string(sparkStatusNames.at(entry.coils12.spark_status)) << ","
<< std::to_string(entry.coils12.peak_p_in) << ","
<< std::to_string(entry.coils12.peak_n_in) << ","
<< std::to_string(entry.coils12.peak_p_out) << ","
<< std::to_string(entry.coils12.peak_n_out) << ","
<< std::string(softStartStatusNames.at(entry.coils12.sstart_status)) << ","
<< std::to_string(entry.coils34.n_events) << ","
<< std::to_string(entry.coils34.spark_delay) << ","
<< std::string(sparkStatusNames.at(entry.coils34.spark_status)) << ","
<< std::to_string(entry.coils34.peak_p_in) << ","
<< std::to_string(entry.coils34.peak_n_in) << ","
<< std::to_string(entry.coils34.peak_p_out) << ","
<< std::to_string(entry.coils34.peak_n_out) << ","
<< std::string(softStartStatusNames.at(entry.coils34.sstart_status)) << ","
<< std::to_string(entry.eng_rpm) << ","
<< std::to_string(entry.adc_read_time) << ","
<< std::to_string(entry.n_queue_errors);
ofs << std::endl;
ofs.flush();
}
ofs.close();
LOG_INFO("Ignition Box history saved to Flash, records written: ", history.size());
}
+124 -27
View File
@@ -2,12 +2,19 @@
#define DEBUGLOG_DEFAULT_LOG_LEVEL_DEBUG
// Serial debug flag
//#define DEBUG
// #define DEBUG
// Arduino Libraries
#include <Arduino.h>
#include <DebugLog.h>
#include "utils.h"
#include <memory>
#include <mutex>
#include <filesystem>
#include <FS.h>
#include <LittleFS.h>
#include <datasave.h>
#include <functional>
// ISR
#include "isr.h"
@@ -31,33 +38,123 @@ static const std::map<const uint32_t, const char *> names = {
};
#endif
// RT task Interrupt parameters
struct rtTaskInterrupts
class rtIgnitionTask
{
void (*isr_ptr)(void *);
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;
};
using PSHistory = PSRAMVector<ignitionBoxStatus>;
// RT Task Peak Detector Reset pins
struct rtTaskResets
{
const uint8_t rst_io_peak;
const uint8_t rst_io_sh;
};
public:
// RT task Interrupt parameters
struct rtTaskInterruptParams
{
void (*isr_ptr)(void *);
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 parameters
struct rtTaskParams
{
bool rt_running; // run flag, false to terminate
Devices *dev;
const QueueHandle_t rt_queue;
const rtTaskInterrupts rt_int; // interrupt pins to attach
const rtTaskResets rt_resets; // reset ping for peak detectors
};
// RT Task Peak Detector Reset pins
struct rtTaskIOParams
{
const uint32_t expander_addr;
const uint32_t pot_cs_12;
const uint32_t pot_cs_34;
const uint32_t ss_force;
const uint32_t ss_inhibit_12;
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_manager_status = INIT;
std::string m_name;
rtTaskParams m_params;
const uint8_t m_core;
TaskHandle_t m_rt_handle = nullptr;
TaskHandle_t m_manager_handle = nullptr;
QueueHandle_t m_queue = nullptr;
bool m_enable_save = false;
std::filesystem::path m_history_path;
const uint32_t m_max_history;
PSHistory m_history_0;
PSHistory m_history_1;
std::unique_ptr<PSHistory> m_active_history;
std::unique_ptr<PSHistory> m_save_history;
fs::FS &m_filesystem;
std::mutex &m_fs_mutex;
bool m_partial_save = false;
bool m_first_save = true;
uint32_t m_counter_status = 0;
uint32_t m_last_data = 0;
ignitionBoxStatus m_last_status;
ignitionBoxStatusFiltered m_info_filtered;
std::function<void(ignitionBoxStatusFiltered)> m_on_message_cb = nullptr;
static const uint32_t c_idle_time = 10000; // in mS
static const uint32_t c_spark_timeout_max = 500; // uS
static const uint8_t c_adc_time = 4; // in mS
static const uint8_t c_io_time = 2; // in mS
};
+115 -7
View File
@@ -5,25 +5,73 @@
#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 printBits(uint32_t value)
{
std::string result;
for (int i = 31; i >= 0; i--) {
for (int i = 31; i >= 0; i--)
{
// ottieni il singolo bit
result += ((value >> i) & 1) ? '1' : '0';
// aggiungi uno spazio ogni 8 bit, tranne dopo l'ultimo
if (i % 8 == 0 && i != 0) {
if (i % 8 == 0 && i != 0)
{
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"};
@@ -51,13 +99,74 @@ void printRunningTasksMod(Print &printer, std::function<bool(const TaskStatus_t
// 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\r\n", uxArraySize, ulTotalRunTime / 1000000, ulCurrentRunTime);
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\r\n");
printer.printf("Num\t Name\tLoad\tPrio\t Free\tCore\tState\n");
for (const auto &task : pxTaskStatusArray)
{
@@ -70,7 +179,7 @@ void printRunningTasksMod(Print &printer, std::function<bool(const TaskStatus_t
"\t%3lu%%"
"\t%4u\t%5lu"
"\t%4c"
"\t%s\r\n",
"\t%s\n",
task.xTaskNumber, task.pcTaskName,
ulTaskRunTime,
task.uxCurrentPriority, task.usStackHighWaterMark,
@@ -79,4 +188,3 @@ void printRunningTasksMod(Print &printer, std::function<bool(const TaskStatus_t
}
printer.println();
}
+104 -27
View File
@@ -1,7 +1,17 @@
#include <webserver.h>
WebPage::WebPage(const uint8_t port, fs::FS &filesystem) : m_port(port), m_webserver(AsyncWebServer(port)), m_websocket(AsyncWebSocket("/ws")), m_filesystem(filesystem)
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) : m_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); });
@@ -9,61 +19,108 @@ WebPage::WebPage(const uint8_t port, fs::FS &filesystem) : m_port(port), m_webse
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.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(2000), pdTRUE, (void *)&m_websocket, on_ping);
registerWsCommand("setTime", [this](const ArduinoJson::JsonDocument &doc)
{ onSetTme(doc); });
LOG_DEBUG("Webserver Init OK");
}
WebPage::~WebPage()
AstroWebServer::~AstroWebServer()
{
xTimerDelete(m_pingTimer, pdMS_TO_TICKS(10));
m_webserver.removeHandler(&m_websocket);
m_webserver.end();
}
void WebPage::sendWsData(const String &data){
if (m_websocket.count()){
void AstroWebServer::sendWsData(const String &data)
{
if (m_websocket.count())
{
m_websocket.textAll(data);
}
}
void WebPage::onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len)
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:
Serial.printf("WS client IP[%s]-ID[%u] CONNECTED\r\n", client->remoteIP().toString().c_str(), client->id());
LOG_DEBUG("WS client IP[", client->remoteIP().toString().c_str(), "]-ID[", client->id(), "] CONNECTED");
break;
case WS_EVT_DISCONNECT:
Serial.printf("WS client ID[%u] DISCONNECTED\r\n", client->remoteIP().toString().c_str(), client->id());
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 WebPage::onUploadRequest(AsyncWebServerRequest *request)
void AstroWebServer::onUploadRequest(AsyncWebServerRequest *request)
{
if (m_upload_failed)
if (m_uploadFailed)
request->send(500, "text/plain", "Upload failed");
else
request->send(200, "text/plain", "Upload successful");
}
void WebPage::onUploadHandler(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final)
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_upload_failed = false;
m_uploadFailed = false;
String safeName = filename;
int slashIndex = safeName.lastIndexOf('/');
if (slashIndex >= 0)
safeName = safeName.substring(slashIndex + 1);
if (safeName.length() == 0)
{
m_upload_failed = true;
m_uploadFailed = true;
LOG_ERROR("Invalid file name");
return;
}
@@ -72,27 +129,47 @@ void WebPage::onUploadHandler(AsyncWebServerRequest *request, const String &file
if (m_filesystem.exists(filePath.c_str()))
m_filesystem.remove(filePath.c_str());
m_upload_file = m_filesystem.open(filePath.c_str(), FILE_WRITE);
if (!m_upload_file)
m_uploadFile = m_filesystem.open(filePath.c_str(), FILE_WRITE);
if (!m_uploadFile)
{
m_upload_failed = true;
m_uploadFailed = true;
LOG_ERROR("Failed to open upload file:", filePath.c_str());
return;
}
}
// Actual write of file data
if (!m_upload_failed && m_upload_file)
if (!m_uploadFailed && m_uploadFile)
{
if (m_upload_file.write(data, len) != len)
m_upload_failed = true;
if (m_uploadFile.write(data, len) != len)
m_uploadFailed = true;
}
// close the file and save on final call
if (final && m_upload_file)
if (final && m_uploadFile)
{
m_upload_file.close();
if (!m_upload_failed)
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());
}
+22 -14
View File
@@ -1,5 +1,5 @@
#pragma once
#define DEBUGLOG_DEFAULT_LOG_LEVEL_INFO
#define DEBUGLOG_DEFAULT_LOG_LEVEL_DEBUG
// System includes
#include <Arduino.h>
@@ -7,32 +7,40 @@
#include <ESPAsyncWebServer.h>
#include <AsyncTCP.h>
#include <filesystem>
#include <map>
#include <FS.h>
#include <ArduinoJson.h>
class WebPage
class AstroWebServer
{
const uint8_t m_port = 80;
fs::FS &m_filesystem;
AsyncWebServer m_webserver;
AsyncWebSocket m_websocket;
bool m_upload_failed = false;
fs::File m_upload_file;
public:
using WScommand = std::function<void(const ArduinoJson::JsonDocument &)>;
public:
WebPage(const uint8_t port, fs::FS &filesystem);
~WebPage();
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);
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 onStart(AsyncWebServerRequest *request);
void onStop(AsyncWebServerRequest *request);
void onDownload(AsyncWebServerRequest *request);
void onSetTme(const ArduinoJson::JsonDocument &doc);
private:
const uint8_t m_port = 80;
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;
};
+1 -1
View File
@@ -22,7 +22,7 @@ build_type = release
[env:esp32-devtest-debug]
board = esp32dev
platform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip
framework = arduino
lib_deps =
hideakitai/DebugLog@^0.8.4
board_build.flash_size = 4MB
+12
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"
+122 -49
View File
@@ -4,6 +4,8 @@
#include <DebugLog.h>
#include "timer.h"
#include "colors.h"
#include <map>
static hw_timer_t *timerA = NULL;
@@ -17,6 +19,12 @@ static uint32_t count = 0;
#define SPARK_DLY_MIN 10
#define SPARK_DLY_MAX 490
#define COIL_PULSE_MIN 100
#define COIL_PULSE_MAX 1000
#define SPARK_PULSE_MIN 10
#define SPARK_PULSE_MAX 500
#define PAUSE_LONG_MIN 5000
#define PAUSE_LONG_MAX PAUSE_LONG_MIN * 100
@@ -30,7 +38,8 @@ void clearScreen()
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 = {
{PIN_TRIG_A12P, "HIGH_PIN_TRIG_A12P"},
@@ -68,7 +77,7 @@ static timerStatus stsB = {
.clock_period_us = (uint32_t)PERIOD_US,
.pause_long_us = 10000,
.pause_short_us = 1000,
.coil_pulse_us = 1000,
.coil_pulse_us = 500,
.spark_pulse_us = 100,
.spark_delay_us = 50,
.pins = {
@@ -83,11 +92,14 @@ static timerStatus stsB = {
static bool isEnabled_A = false;
static bool isEnabled_B = false;
static String last_command;
void setup()
{
Serial.begin(115200);
delay(1000);
Serial.setTimeout(100);
LOG_ATTACH_SERIAL(Serial);
pinMode(PIN_TRIG_A12P, OUTPUT);
@@ -133,63 +145,124 @@ void setup()
void loop()
{
LOG_INFO("Loop: ", count++);
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;
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;
clearScreen();
double new_rpm = (double)(map(analogRead(FREQ_POT), 0, 4096, RPM_MIN, RPM_MAX));
filtered_rpm = filtered_rpm + 0.1 * (new_rpm - filtered_rpm);
stsA.pause_long_us = (uint32_t)(60000000.0f / filtered_rpm / 2.0f);
stsB.pause_long_us = stsA.pause_long_us;
Serial.printf("\t++++ Loop: %u ++++\n", count++);
if (isEnabled_A)
LOG_INFO("==== System A is ENABLED ====");
Serial.println("==== System A is" COLOR_GREEN " ENABLED" COLOR_RESET " ====");
else
LOG_INFO("==== System A is DISABLED ====");
Serial.println("==== System A is" COLOR_RED " DISABLED" COLOR_RESET " ====");
if (isEnabled_B)
LOG_INFO("==== System B is ENABLED ====");
Serial.println("==== System B is" COLOR_GREEN " ENABLED" COLOR_RESET " ====");
else
LOG_INFO("==== System B is DISABLED ====");
Serial.println("==== System B is" COLOR_RED " DISABLED" COLOR_RESET " ====");
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");
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());
if (digitalRead(ENABLE_PIN_A) == LOW && !isEnabled_A)
auto str = Serial.readStringUntil('\n');
if (!str.isEmpty())
{
timerStart(timerA);
isEnabled_A = true;
}
else if (digitalRead(ENABLE_PIN_A) == HIGH && isEnabled_A)
{
timerStop(timerA);
isEnabled_A = false;
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();
}
if (digitalRead(ENABLE_PIN_B) == LOW && !isEnabled_B)
{
timerStart(timerB);
isEnabled_B = true;
}
else if (digitalRead(ENABLE_PIN_B) == HIGH && isEnabled_B)
{
timerStop(timerB);
isEnabled_B = false;
}
delay(100);
clearScreen();
str.clear();
delay(1000);
}