mirror of
https://github.com/Show-maket/IR-protocol.git
synced 2026-06-13 15:00:00 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 59d6e7aa23 | |||
| b2dfee5495 |
11
.gitignore
vendored
11
.gitignore
vendored
@ -1,12 +1,5 @@
|
|||||||
.vscode/*
|
.vscode/*
|
||||||
bin/*
|
bin/*
|
||||||
|
!.vscode/arduino.json
|
||||||
!.vscode/launch.json
|
!.vscode/launch.json
|
||||||
log/*
|
log/*
|
||||||
/.vscode
|
|
||||||
*.zip
|
|
||||||
**/__pycache__
|
|
||||||
Analyzer/raw/dll/*.dll
|
|
||||||
Analyzer/raw/dll/*.so
|
|
||||||
Analyzer/raw/dll/*.dylib
|
|
||||||
/Analyzer/raw/IR_Fox/.github
|
|
||||||
**/.build
|
|
||||||
7
.vscode/arduino.json
vendored
7
.vscode/arduino.json
vendored
@ -1,5 +1,8 @@
|
|||||||
{
|
{
|
||||||
"board": "STMicroelectronics:stm32:GenF4",
|
"configuration": "pnum=BLUEPILL_F103C8,upload_method=swdMethod,xserial=none,usb=CDCgen,xusb=FS,opt=osstd,dbg=none,rtlib=nano",
|
||||||
|
"board": "STMicroelectronics:stm32:GenF1",
|
||||||
"port": "COM17",
|
"port": "COM17",
|
||||||
"prebuild": "if exist bin rd /s /q bin"
|
"output": "bin",
|
||||||
|
"prebuild": "if exist bin rd /s /q bin",
|
||||||
|
"sketch": "IR-protocol.ino"
|
||||||
}
|
}
|
||||||
2
Analyzer/raw/IR_Fox/.gitignore
vendored
2
Analyzer/raw/IR_Fox/.gitignore
vendored
@ -1,2 +0,0 @@
|
|||||||
/build
|
|
||||||
.DS_Store
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.13)
|
|
||||||
|
|
||||||
project(IrFoxAnalyzer)
|
|
||||||
|
|
||||||
add_definitions(-DLOGIC2)
|
|
||||||
|
|
||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
|
||||||
|
|
||||||
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
|
|
||||||
|
|
||||||
include(ExternalAnalyzerSDK)
|
|
||||||
|
|
||||||
set(SOURCES
|
|
||||||
src/IrFoxAnalyzer.cpp
|
|
||||||
src/IrFoxAnalyzer.h
|
|
||||||
src/IrFoxDecoder.cpp
|
|
||||||
src/IrFoxDecoder.h
|
|
||||||
src/IrFoxAnalyzerResults.cpp
|
|
||||||
src/IrFoxAnalyzerResults.h
|
|
||||||
src/IrFoxAnalyzerSettings.cpp
|
|
||||||
src/IrFoxAnalyzerSettings.h
|
|
||||||
src/IrFoxSimulationDataGenerator.cpp
|
|
||||||
src/IrFoxSimulationDataGenerator.h
|
|
||||||
)
|
|
||||||
|
|
||||||
add_analyzer_plugin(${PROJECT_NAME} SOURCES ${SOURCES})
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 3,
|
|
||||||
"cmakeMinimumRequired": {
|
|
||||||
"major": 3,
|
|
||||||
"minor": 19,
|
|
||||||
"patch": 0
|
|
||||||
},
|
|
||||||
"configurePresets": [
|
|
||||||
{
|
|
||||||
"name": "win-vs2022-x64",
|
|
||||||
"displayName": "Visual Studio 2022 (x64)",
|
|
||||||
"generator": "Visual Studio 17 2022",
|
|
||||||
"architecture": "x64",
|
|
||||||
"binaryDir": "${sourceDir}/build"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "win-vs2019-x64",
|
|
||||||
"displayName": "Visual Studio 2019 (x64)",
|
|
||||||
"generator": "Visual Studio 16 2019",
|
|
||||||
"architecture": "x64",
|
|
||||||
"binaryDir": "${sourceDir}/build"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "win-nmake-release",
|
|
||||||
"displayName": "NMake Release (только из «x64 Native Tools Command Prompt for VS»)",
|
|
||||||
"generator": "NMake Makefiles",
|
|
||||||
"binaryDir": "${sourceDir}/build-nmake",
|
|
||||||
"cacheVariables": {
|
|
||||||
"CMAKE_BUILD_TYPE": "Release"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"buildPresets": [
|
|
||||||
{
|
|
||||||
"name": "win-release",
|
|
||||||
"configurePreset": "win-vs2022-x64",
|
|
||||||
"configuration": "Release"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "win-release-vs2019",
|
|
||||||
"configurePreset": "win-vs2019-x64",
|
|
||||||
"configuration": "Release"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "nmake-release",
|
|
||||||
"configurePreset": "win-nmake-release"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
@echo off
|
|
||||||
setlocal EnableDelayedExpansion
|
|
||||||
cd /d "%~dp0"
|
|
||||||
|
|
||||||
if not exist build\CMakeCache.txt (
|
|
||||||
echo Run configure_msvc.bat first.
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
|
|
||||||
set "VSWHERE=%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe"
|
|
||||||
for /f "usebackq tokens=*" %%i in (`"%VSWHERE%" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do set "VSINSTALL=%%i"
|
|
||||||
if not defined VSINSTALL (
|
|
||||||
echo MSVC not found. Add C++ workload in Visual Studio Installer.
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
call "!VSINSTALL!\Common7\Tools\VsDevCmd.bat" -arch=x64 -host_arch=x64
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
|
|
||||||
cmake --build build
|
|
||||||
pause
|
|
||||||
exit /b %ERRORLEVEL%
|
|
||||||
@ -1,66 +0,0 @@
|
|||||||
include(FetchContent)
|
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 11)
|
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED YES)
|
|
||||||
|
|
||||||
if(NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY OR NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY)
|
|
||||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin/)
|
|
||||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin/)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(NOT TARGET Saleae::AnalyzerSDK)
|
|
||||||
FetchContent_Declare(
|
|
||||||
analyzersdk
|
|
||||||
GIT_REPOSITORY https://github.com/saleae/AnalyzerSDK.git
|
|
||||||
GIT_TAG master
|
|
||||||
GIT_SHALLOW True
|
|
||||||
GIT_PROGRESS True
|
|
||||||
)
|
|
||||||
|
|
||||||
FetchContent_GetProperties(analyzersdk)
|
|
||||||
|
|
||||||
if(NOT analyzersdk_POPULATED)
|
|
||||||
FetchContent_Populate(analyzersdk)
|
|
||||||
include(${analyzersdk_SOURCE_DIR}/AnalyzerSDKConfig.cmake)
|
|
||||||
|
|
||||||
if(APPLE OR WIN32)
|
|
||||||
get_target_property(analyzersdk_lib_location Saleae::AnalyzerSDK IMPORTED_LOCATION)
|
|
||||||
if(CMAKE_LIBRARY_OUTPUT_DIRECTORY)
|
|
||||||
file(COPY ${analyzersdk_lib_location} DESTINATION ${CMAKE_LIBRARY_OUTPUT_DIRECTORY})
|
|
||||||
else()
|
|
||||||
message(WARNING "Please define CMAKE_RUNTIME_OUTPUT_DIRECTORY and CMAKE_LIBRARY_OUTPUT_DIRECTORY if you want unit tests to locate ${analyzersdk_lib_location}")
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Shared folder for all Saleae LLA plugins in this repo: Analyzer/raw/dll
|
|
||||||
set(ANALYZER_DLL_OUT_DIR "${CMAKE_SOURCE_DIR}/../dll")
|
|
||||||
get_filename_component(ANALYZER_DLL_OUT_DIR "${ANALYZER_DLL_OUT_DIR}" ABSOLUTE)
|
|
||||||
file(MAKE_DIRECTORY "${ANALYZER_DLL_OUT_DIR}")
|
|
||||||
|
|
||||||
function(add_analyzer_plugin TARGET)
|
|
||||||
set(options)
|
|
||||||
set(single_value_args)
|
|
||||||
set(multi_value_args SOURCES)
|
|
||||||
cmake_parse_arguments(_p "${options}" "${single_value_args}" "${multi_value_args}" ${ARGN})
|
|
||||||
|
|
||||||
add_library(${TARGET} MODULE ${_p_SOURCES})
|
|
||||||
target_link_libraries(${TARGET} PRIVATE Saleae::AnalyzerSDK)
|
|
||||||
|
|
||||||
set(ANALYZER_DESTINATION "Analyzers")
|
|
||||||
install(TARGETS ${TARGET} RUNTIME DESTINATION ${ANALYZER_DESTINATION}
|
|
||||||
LIBRARY DESTINATION ${ANALYZER_DESTINATION})
|
|
||||||
|
|
||||||
set_target_properties(${TARGET} PROPERTIES
|
|
||||||
RUNTIME_OUTPUT_DIRECTORY "${ANALYZER_DLL_OUT_DIR}"
|
|
||||||
LIBRARY_OUTPUT_DIRECTORY "${ANALYZER_DLL_OUT_DIR}")
|
|
||||||
if(CMAKE_CONFIGURATION_TYPES)
|
|
||||||
foreach(CFG ${CMAKE_CONFIGURATION_TYPES})
|
|
||||||
string(TOUPPER ${CFG} CFG_UPPER)
|
|
||||||
set_target_properties(${TARGET} PROPERTIES
|
|
||||||
RUNTIME_OUTPUT_DIRECTORY_${CFG_UPPER} "${ANALYZER_DLL_OUT_DIR}"
|
|
||||||
LIBRARY_OUTPUT_DIRECTORY_${CFG_UPPER} "${ANALYZER_DLL_OUT_DIR}")
|
|
||||||
endforeach()
|
|
||||||
endif()
|
|
||||||
endfunction()
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
@echo off
|
|
||||||
setlocal EnableDelayedExpansion
|
|
||||||
cd /d "%~dp0"
|
|
||||||
|
|
||||||
echo === IrFoxAnalyzer: configure with MSVC ===
|
|
||||||
echo.
|
|
||||||
|
|
||||||
set "VSWHERE=%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe"
|
|
||||||
if not exist "%VSWHERE%" (
|
|
||||||
echo [ERROR] vswhere not found. Install one of:
|
|
||||||
echo - Visual Studio 2022 with workload "Desktop development with C++"
|
|
||||||
echo - Build Tools for Visual Studio 2022: https://visualstudio.microsoft.com/visual-cpp-build-tools/
|
|
||||||
echo ^(select "Desktop development with C++" / MSVC, Windows SDK^)
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
|
|
||||||
for /f "usebackq tokens=*" %%i in (`"%VSWHERE%" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do set "VSINSTALL=%%i"
|
|
||||||
if not defined VSINSTALL (
|
|
||||||
echo [ERROR] MSVC toolset not found. Add "Desktop development with C++" in Visual Studio Installer.
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
|
|
||||||
echo Found: !VSINSTALL!
|
|
||||||
call "!VSINSTALL!\Common7\Tools\VsDevCmd.bat" -arch=x64 -host_arch=x64
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
|
|
||||||
if exist build rmdir /s /q build
|
|
||||||
if exist build-nmake rmdir /s /q build-nmake
|
|
||||||
mkdir build
|
|
||||||
cd build
|
|
||||||
|
|
||||||
cmake .. -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
|
|
||||||
echo.
|
|
||||||
echo Configure OK. Build: build_msvc.bat ^(or from same VS env: cd build ^& cmake --build .^)
|
|
||||||
echo Output DLL: ..\dll\ ^(all analyzers share this folder^)
|
|
||||||
pause
|
|
||||||
exit /b 0
|
|
||||||
@ -1,280 +0,0 @@
|
|||||||
#include "IrFoxAnalyzer.h"
|
|
||||||
#include "IrFoxAnalyzerSettings.h"
|
|
||||||
#include "IrFoxDecoder.h"
|
|
||||||
#include <AnalyzerChannelData.h>
|
|
||||||
#include <AnalyzerResults.h>
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <cstring>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
IrFoxAnalyzer::IrFoxAnalyzer()
|
|
||||||
: Analyzer2(),
|
|
||||||
mSettings(),
|
|
||||||
mSimulationInitilized(false)
|
|
||||||
{
|
|
||||||
SetAnalyzerSettings(&mSettings);
|
|
||||||
UseFrameV2();
|
|
||||||
}
|
|
||||||
|
|
||||||
IrFoxAnalyzer::~IrFoxAnalyzer()
|
|
||||||
{
|
|
||||||
KillThread();
|
|
||||||
}
|
|
||||||
|
|
||||||
void IrFoxAnalyzer::SetupResults()
|
|
||||||
{
|
|
||||||
m_packet_hex_by_frame.clear();
|
|
||||||
mResults.reset(new IrFoxAnalyzerResults(this, &mSettings));
|
|
||||||
SetAnalyzerResults(mResults.get());
|
|
||||||
mResults->AddChannelBubblesWillAppearOn(mSettings.mInputChannel);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void append_hex(std::string& s, const uint8_t* p, size_t n, size_t max_bytes = 64)
|
|
||||||
{
|
|
||||||
static const char* hd = "0123456789abcdef";
|
|
||||||
const size_t m = n < max_bytes ? n : max_bytes;
|
|
||||||
for (size_t i = 0; i < m; i++)
|
|
||||||
{
|
|
||||||
s.push_back(hd[p[i] >> 4]);
|
|
||||||
s.push_back(hd[p[i] & 0xFu]);
|
|
||||||
if (i + 1 < m)
|
|
||||||
s.push_back(' ');
|
|
||||||
}
|
|
||||||
if (n > max_bytes)
|
|
||||||
s += "...";
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* IrFoxAnalyzer::PacketHexForFrame(U64 frame_id)
|
|
||||||
{
|
|
||||||
auto it = m_packet_hex_by_frame.find(frame_id);
|
|
||||||
if (it == m_packet_hex_by_frame.end())
|
|
||||||
return "";
|
|
||||||
m_hex_scratch = it->second;
|
|
||||||
return m_hex_scratch.c_str();
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* IrFoxAnalyzer::BubbleTextForFrame(U64 frame_id) const
|
|
||||||
{
|
|
||||||
auto it = m_bubble_text_by_frame.find(frame_id);
|
|
||||||
if (it == m_bubble_text_by_frame.end())
|
|
||||||
return "";
|
|
||||||
m_bubble_scratch = it->second;
|
|
||||||
return m_bubble_scratch.c_str();
|
|
||||||
}
|
|
||||||
|
|
||||||
void IrFoxAnalyzer::WorkerThread()
|
|
||||||
{
|
|
||||||
mIr = GetAnalyzerChannelData(mSettings.mInputChannel);
|
|
||||||
m_packet_hex_by_frame.clear();
|
|
||||||
m_bubble_text_by_frame.clear();
|
|
||||||
|
|
||||||
const U32 fs = GetSampleRate();
|
|
||||||
IrFoxDecoder decoder;
|
|
||||||
decoder.reset();
|
|
||||||
|
|
||||||
/** Потоковый фильтр: убирает импульсы короче kMinFilteredPulseUs (иголки/дребезг в сэмплах). */
|
|
||||||
const U64 min_seg_samples =
|
|
||||||
std::max<U64>(1ULL, static_cast<U64>((static_cast<double>(irfox::kMinFilteredPulseUs) * 1e-6) * static_cast<double>(fs) + 0.5));
|
|
||||||
struct RawEdge
|
|
||||||
{
|
|
||||||
U64 sample;
|
|
||||||
bool rising;
|
|
||||||
};
|
|
||||||
std::vector<RawEdge> pending;
|
|
||||||
U64 last_dec_edge_sample = 0;
|
|
||||||
bool last_dec_edge_valid = false;
|
|
||||||
|
|
||||||
auto collapse_short_pairs = [&]() {
|
|
||||||
for (size_t i = 0; i + 1 < pending.size();)
|
|
||||||
{
|
|
||||||
if (pending[i + 1].sample - pending[i].sample < min_seg_samples)
|
|
||||||
{
|
|
||||||
pending.erase(pending.begin() + static_cast<std::ptrdiff_t>(i),
|
|
||||||
pending.begin() + static_cast<std::ptrdiff_t>(i + 2));
|
|
||||||
if (i > 0)
|
|
||||||
--i;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
auto strip_vs_last_decoder = [&]() {
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
collapse_short_pairs();
|
|
||||||
if (!last_dec_edge_valid || pending.empty())
|
|
||||||
return;
|
|
||||||
if (pending[0].sample - last_dec_edge_sample >= min_seg_samples)
|
|
||||||
return;
|
|
||||||
pending.erase(pending.begin());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
U32 frames_since_commit = 0;
|
|
||||||
const U32 kCommitBatch = 256;
|
|
||||||
|
|
||||||
IrFoxOnBit on_bit = [&](const IrFoxEmitBit& e) {
|
|
||||||
Frame frame;
|
|
||||||
frame.mStartingSampleInclusive = static_cast<S64>(e.start_sample);
|
|
||||||
frame.mEndingSampleInclusive = static_cast<S64>(e.end_sample);
|
|
||||||
frame.mType = e.frame_type;
|
|
||||||
frame.mData1 = e.bit_value;
|
|
||||||
frame.mData2 = e.bit_index | (U64(e.err_low) << 16) | (U64(e.err_high) << 24) | (U64(e.err_other) << 32);
|
|
||||||
frame.mFlags = e.mflags;
|
|
||||||
// В SDK только ERROR/WARNING меняют цвет бабла; sync выделяем янтарным (как warning), данные — обычные.
|
|
||||||
if (e.frame_type == IRF_FT_SYNC_BIT)
|
|
||||||
frame.mFlags |= DISPLAY_AS_WARNING_FLAG;
|
|
||||||
|
|
||||||
const U64 fid = mResults->AddFrame(frame);
|
|
||||||
if (e.bubble_text[0] != '\0')
|
|
||||||
m_bubble_text_by_frame[fid] = e.bubble_text;
|
|
||||||
if (++frames_since_commit >= kCommitBatch)
|
|
||||||
{
|
|
||||||
mResults->CommitResults();
|
|
||||||
frames_since_commit = 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
IrFoxOnPacket on_pkt = [&](const IrFoxEmitPacket& p) {
|
|
||||||
Frame frame;
|
|
||||||
frame.mStartingSampleInclusive = static_cast<S64>(p.start_sample);
|
|
||||||
frame.mEndingSampleInclusive = static_cast<S64>(p.end_sample);
|
|
||||||
frame.mType = p.crc_ok ? IRF_FT_PACKET_OK : IRF_FT_PACKET_CRC_FAIL;
|
|
||||||
frame.mData1 = p.pack_size;
|
|
||||||
frame.mData2 = (U64(p.err_low) << 0) | (U64(p.err_high) << 8) | (U64(p.err_other) << 16);
|
|
||||||
if (!p.crc_ok)
|
|
||||||
frame.mFlags |= DISPLAY_AS_ERROR_FLAG;
|
|
||||||
|
|
||||||
const U64 fid = mResults->AddFrame(frame);
|
|
||||||
|
|
||||||
std::string hx;
|
|
||||||
append_hex(hx, p.data_bytes, p.pack_size);
|
|
||||||
m_packet_hex_by_frame[fid] = std::move(hx);
|
|
||||||
|
|
||||||
FrameV2 fv2;
|
|
||||||
fv2.AddBoolean("crc_ok", p.crc_ok);
|
|
||||||
fv2.AddInteger("len", static_cast<S64>(p.pack_size));
|
|
||||||
fv2.AddInteger("err_low", static_cast<S64>(p.err_low));
|
|
||||||
fv2.AddInteger("err_high", static_cast<S64>(p.err_high));
|
|
||||||
fv2.AddInteger("err_other", static_cast<S64>(p.err_other));
|
|
||||||
fv2.AddByteArray("data", p.data_bytes, p.pack_size);
|
|
||||||
mResults->AddFrameV2(fv2, p.crc_ok ? "packet_ok" : "packet_bad", static_cast<U64>(p.start_sample),
|
|
||||||
static_cast<U64>(p.end_sample));
|
|
||||||
|
|
||||||
if (++frames_since_commit >= kCommitBatch)
|
|
||||||
{
|
|
||||||
mResults->CommitResults();
|
|
||||||
frames_since_commit = 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
auto emit_confirmed_edges = [&]() {
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
collapse_short_pairs();
|
|
||||||
strip_vs_last_decoder();
|
|
||||||
if (pending.size() < 2)
|
|
||||||
return;
|
|
||||||
if (pending[1].sample - pending[0].sample < min_seg_samples)
|
|
||||||
continue;
|
|
||||||
decoder.processEdge(pending[0].sample, pending[0].rising, fs, on_bit, on_pkt);
|
|
||||||
last_dec_edge_sample = pending[0].sample;
|
|
||||||
last_dec_edge_valid = true;
|
|
||||||
pending.erase(pending.begin());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
auto flush_pending_tail = [&]() {
|
|
||||||
collapse_short_pairs();
|
|
||||||
strip_vs_last_decoder();
|
|
||||||
while (pending.size() >= 2 && pending[1].sample - pending[0].sample >= min_seg_samples)
|
|
||||||
{
|
|
||||||
decoder.processEdge(pending[0].sample, pending[0].rising, fs, on_bit, on_pkt);
|
|
||||||
last_dec_edge_sample = pending[0].sample;
|
|
||||||
last_dec_edge_valid = true;
|
|
||||||
pending.erase(pending.begin());
|
|
||||||
collapse_short_pairs();
|
|
||||||
strip_vs_last_decoder();
|
|
||||||
}
|
|
||||||
if (pending.size() == 1)
|
|
||||||
{
|
|
||||||
decoder.processEdge(pending[0].sample, pending[0].rising, fs, on_bit, on_pkt);
|
|
||||||
last_dec_edge_sample = pending[0].sample;
|
|
||||||
last_dec_edge_valid = true;
|
|
||||||
pending.clear();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
CheckIfThreadShouldExit();
|
|
||||||
|
|
||||||
const U64 segment_start = mIr->GetSampleNumber();
|
|
||||||
const BitState level = mIr->GetBitState();
|
|
||||||
|
|
||||||
mIr->AdvanceToNextEdge();
|
|
||||||
|
|
||||||
const U64 edge_sample = mIr->GetSampleNumber();
|
|
||||||
if (edge_sample == segment_start)
|
|
||||||
break;
|
|
||||||
|
|
||||||
const BitState new_level = mIr->GetBitState();
|
|
||||||
const bool rising = (new_level == BIT_HIGH);
|
|
||||||
|
|
||||||
pending.push_back(RawEdge{edge_sample, rising});
|
|
||||||
emit_confirmed_edges();
|
|
||||||
ReportProgress(edge_sample);
|
|
||||||
}
|
|
||||||
|
|
||||||
flush_pending_tail();
|
|
||||||
decoder.flushEnd(mIr->GetSampleNumber(), fs, on_bit, on_pkt);
|
|
||||||
|
|
||||||
if (frames_since_commit != 0)
|
|
||||||
mResults->CommitResults();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IrFoxAnalyzer::NeedsRerun()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
U32 IrFoxAnalyzer::GenerateSimulationData(U64 minimum_sample_index, U32 device_sample_rate,
|
|
||||||
SimulationChannelDescriptor** simulation_channels)
|
|
||||||
{
|
|
||||||
if (mSimulationInitilized == false)
|
|
||||||
{
|
|
||||||
mSimulationDataGenerator.Initialize(GetSimulationSampleRate(), &mSettings);
|
|
||||||
mSimulationInitilized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return mSimulationDataGenerator.GenerateSimulationData(minimum_sample_index, device_sample_rate,
|
|
||||||
simulation_channels);
|
|
||||||
}
|
|
||||||
|
|
||||||
U32 IrFoxAnalyzer::GetMinimumSampleRateHz()
|
|
||||||
{
|
|
||||||
return 200000;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* IrFoxAnalyzer::GetAnalyzerName() const
|
|
||||||
{
|
|
||||||
return "IR Fox";
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* GetAnalyzerName()
|
|
||||||
{
|
|
||||||
return "IR Fox";
|
|
||||||
}
|
|
||||||
|
|
||||||
Analyzer* CreateAnalyzer()
|
|
||||||
{
|
|
||||||
return new IrFoxAnalyzer();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DestroyAnalyzer(Analyzer* analyzer)
|
|
||||||
{
|
|
||||||
delete analyzer;
|
|
||||||
}
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
#ifndef IRFOX_ANALYZER_H
|
|
||||||
#define IRFOX_ANALYZER_H
|
|
||||||
|
|
||||||
#include <Analyzer.h>
|
|
||||||
#include "IrFoxAnalyzerSettings.h"
|
|
||||||
#include "IrFoxAnalyzerResults.h"
|
|
||||||
#include "IrFoxSimulationDataGenerator.h"
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
class ANALYZER_EXPORT IrFoxAnalyzer : public Analyzer2
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IrFoxAnalyzer();
|
|
||||||
virtual ~IrFoxAnalyzer();
|
|
||||||
|
|
||||||
virtual void SetupResults();
|
|
||||||
virtual void WorkerThread();
|
|
||||||
|
|
||||||
virtual U32 GenerateSimulationData(U64 newest_sample_requested, U32 sample_rate,
|
|
||||||
SimulationChannelDescriptor** simulation_channels);
|
|
||||||
virtual U32 GetMinimumSampleRateHz();
|
|
||||||
|
|
||||||
virtual const char* GetAnalyzerName() const;
|
|
||||||
virtual bool NeedsRerun();
|
|
||||||
|
|
||||||
const char* PacketHexForFrame(U64 frame_id);
|
|
||||||
const char* BubbleTextForFrame(U64 frame_id) const;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
IrFoxAnalyzerSettings mSettings;
|
|
||||||
std::unique_ptr<IrFoxAnalyzerResults> mResults;
|
|
||||||
AnalyzerChannelData* mIr;
|
|
||||||
|
|
||||||
IrFoxSimulationDataGenerator mSimulationDataGenerator;
|
|
||||||
bool mSimulationInitilized;
|
|
||||||
|
|
||||||
std::unordered_map<U64, std::string> m_packet_hex_by_frame;
|
|
||||||
std::unordered_map<U64, std::string> m_bubble_text_by_frame;
|
|
||||||
mutable std::string m_hex_scratch;
|
|
||||||
mutable std::string m_bubble_scratch;
|
|
||||||
};
|
|
||||||
|
|
||||||
extern "C" ANALYZER_EXPORT const char* __cdecl GetAnalyzerName();
|
|
||||||
extern "C" ANALYZER_EXPORT Analyzer* __cdecl CreateAnalyzer();
|
|
||||||
extern "C" ANALYZER_EXPORT void __cdecl DestroyAnalyzer(Analyzer* analyzer);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -1,175 +0,0 @@
|
|||||||
#include "IrFoxAnalyzerResults.h"
|
|
||||||
#include <AnalyzerHelpers.h>
|
|
||||||
#include <AnalyzerResults.h>
|
|
||||||
#include "IrFoxAnalyzer.h"
|
|
||||||
#include "IrFoxAnalyzerSettings.h"
|
|
||||||
#include "IrFoxDecoder.h"
|
|
||||||
#include <cstdio>
|
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
IrFoxAnalyzerResults::IrFoxAnalyzerResults(IrFoxAnalyzer* analyzer, IrFoxAnalyzerSettings* settings)
|
|
||||||
: AnalyzerResults(),
|
|
||||||
mSettings(settings),
|
|
||||||
mAnalyzer(analyzer)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
IrFoxAnalyzerResults::~IrFoxAnalyzerResults()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void IrFoxAnalyzerResults::GenerateBubbleText(U64 frame_index, Channel& channel, DisplayBase display_base)
|
|
||||||
{
|
|
||||||
(void)display_base;
|
|
||||||
(void)channel;
|
|
||||||
ClearResultStrings();
|
|
||||||
Frame frame = GetFrame(frame_index);
|
|
||||||
|
|
||||||
char line[256];
|
|
||||||
|
|
||||||
switch (frame.mType)
|
|
||||||
{
|
|
||||||
case IRF_FT_DATA_BIT:
|
|
||||||
case IRF_FT_SYNC_BIT:
|
|
||||||
case IRF_FT_PREAMBLE:
|
|
||||||
case IRF_FT_OVERFLOW:
|
|
||||||
case IRF_FT_ABORT:
|
|
||||||
{
|
|
||||||
const char* bt = mAnalyzer->BubbleTextForFrame(frame_index);
|
|
||||||
if (bt && bt[0])
|
|
||||||
AddResultString(bt);
|
|
||||||
else if (frame.mType == IRF_FT_DATA_BIT)
|
|
||||||
AddResultString(frame.mData1 ? "1" : "0");
|
|
||||||
else if (frame.mType == IRF_FT_SYNC_BIT)
|
|
||||||
{
|
|
||||||
snprintf(line, sizeof line, "sync: %s", frame.mData1 ? "1" : "0");
|
|
||||||
AddResultString(line);
|
|
||||||
}
|
|
||||||
else if (frame.mType == IRF_FT_OVERFLOW)
|
|
||||||
AddResultString("OVF");
|
|
||||||
else if (frame.mType == IRF_FT_ABORT)
|
|
||||||
AddResultString("SYNC!");
|
|
||||||
else
|
|
||||||
AddResultString("PRE");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case IRF_FT_PACKET_OK:
|
|
||||||
case IRF_FT_PACKET_CRC_FAIL:
|
|
||||||
{
|
|
||||||
snprintf(line, sizeof line, "%s %lluB", frame.mType == IRF_FT_PACKET_OK ? "OK" : "CRC",
|
|
||||||
(unsigned long long)frame.mData1);
|
|
||||||
AddResultString(line);
|
|
||||||
const char* hx = mAnalyzer->PacketHexForFrame(frame_index);
|
|
||||||
if (hx && hx[0])
|
|
||||||
AddResultString(hx);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
snprintf(line, sizeof line, "? type=%u", static_cast<unsigned>(frame.mType));
|
|
||||||
AddResultString(line);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void IrFoxAnalyzerResults::GenerateExportFile(const char* file, DisplayBase display_base, U32 export_type_user_id)
|
|
||||||
{
|
|
||||||
(void)export_type_user_id;
|
|
||||||
(void)display_base;
|
|
||||||
std::ofstream file_stream(file, std::ios::out);
|
|
||||||
|
|
||||||
const U64 trigger_sample = mAnalyzer->GetTriggerSample();
|
|
||||||
const U32 sample_rate = mAnalyzer->GetSampleRate();
|
|
||||||
|
|
||||||
file_stream << "Time[s],Type,Data1,bit_idx,err_low,err_high,err_other,Flags,Hex" << std::endl;
|
|
||||||
|
|
||||||
const U64 num_frames = GetNumFrames();
|
|
||||||
for (U32 i = 0; i < num_frames; i++)
|
|
||||||
{
|
|
||||||
Frame frame = GetFrame(i);
|
|
||||||
|
|
||||||
char time_str[128];
|
|
||||||
AnalyzerHelpers::GetTimeString(frame.mStartingSampleInclusive, trigger_sample, sample_rate, time_str, 128);
|
|
||||||
|
|
||||||
const char* typ = "?";
|
|
||||||
switch (frame.mType)
|
|
||||||
{
|
|
||||||
case IRF_FT_DATA_BIT:
|
|
||||||
typ = "D";
|
|
||||||
break;
|
|
||||||
case IRF_FT_SYNC_BIT:
|
|
||||||
typ = "S";
|
|
||||||
break;
|
|
||||||
case IRF_FT_PACKET_OK:
|
|
||||||
typ = "OK";
|
|
||||||
break;
|
|
||||||
case IRF_FT_PACKET_CRC_FAIL:
|
|
||||||
typ = "CRC";
|
|
||||||
break;
|
|
||||||
case IRF_FT_OVERFLOW:
|
|
||||||
typ = "OVF";
|
|
||||||
break;
|
|
||||||
case IRF_FT_ABORT:
|
|
||||||
typ = "ABORT";
|
|
||||||
break;
|
|
||||||
case IRF_FT_PREAMBLE:
|
|
||||||
typ = "PRE";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* hx = mAnalyzer->PacketHexForFrame(i);
|
|
||||||
if (!hx)
|
|
||||||
hx = "";
|
|
||||||
|
|
||||||
U64 bit_idx = 0;
|
|
||||||
U32 err_l = 0, err_h = 0, err_o = 0;
|
|
||||||
if (frame.mType == IRF_FT_DATA_BIT || frame.mType == IRF_FT_SYNC_BIT ||
|
|
||||||
frame.mType == IRF_FT_OVERFLOW || frame.mType == IRF_FT_ABORT)
|
|
||||||
{
|
|
||||||
bit_idx = frame.mData2 & 0xFFFFull;
|
|
||||||
err_l = static_cast<U32>((frame.mData2 >> 16) & 0xFFull);
|
|
||||||
err_h = static_cast<U32>((frame.mData2 >> 24) & 0xFFull);
|
|
||||||
err_o = static_cast<U32>((frame.mData2 >> 32) & 0xFFull);
|
|
||||||
}
|
|
||||||
|
|
||||||
file_stream << time_str << "," << typ << "," << frame.mData1 << "," << bit_idx << "," << err_l << "," << err_h
|
|
||||||
<< "," << err_o << "," << static_cast<unsigned>(frame.mFlags) << "," << hx << std::endl;
|
|
||||||
|
|
||||||
if (UpdateExportProgressAndCheckForCancel(i, num_frames) == true)
|
|
||||||
{
|
|
||||||
file_stream.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
file_stream.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void IrFoxAnalyzerResults::GenerateFrameTabularText(U64 frame_index, DisplayBase display_base)
|
|
||||||
{
|
|
||||||
#ifdef SUPPORTS_PROTOCOL_SEARCH
|
|
||||||
(void)display_base;
|
|
||||||
Frame frame = GetFrame(frame_index);
|
|
||||||
ClearTabularText();
|
|
||||||
char buf[64];
|
|
||||||
snprintf(buf, sizeof buf, "t%u", static_cast<unsigned>(frame.mType));
|
|
||||||
AddTabularText(buf);
|
|
||||||
snprintf(buf, sizeof buf, "%llu", (unsigned long long)frame.mData1);
|
|
||||||
AddTabularText(buf);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void IrFoxAnalyzerResults::GeneratePacketTabularText(U64 packet_id, DisplayBase display_base)
|
|
||||||
{
|
|
||||||
(void)packet_id;
|
|
||||||
(void)display_base;
|
|
||||||
}
|
|
||||||
|
|
||||||
void IrFoxAnalyzerResults::GenerateTransactionTabularText(U64 transaction_id, DisplayBase display_base)
|
|
||||||
{
|
|
||||||
(void)transaction_id;
|
|
||||||
(void)display_base;
|
|
||||||
}
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
#ifndef IRFOX_ANALYZER_RESULTS
|
|
||||||
#define IRFOX_ANALYZER_RESULTS
|
|
||||||
|
|
||||||
#include <AnalyzerResults.h>
|
|
||||||
|
|
||||||
class IrFoxAnalyzer;
|
|
||||||
class IrFoxAnalyzerSettings;
|
|
||||||
|
|
||||||
class IrFoxAnalyzerResults : public AnalyzerResults
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IrFoxAnalyzerResults(IrFoxAnalyzer* analyzer, IrFoxAnalyzerSettings* settings);
|
|
||||||
virtual ~IrFoxAnalyzerResults();
|
|
||||||
|
|
||||||
virtual void GenerateBubbleText(U64 frame_index, Channel& channel, DisplayBase display_base);
|
|
||||||
virtual void GenerateExportFile(const char* file, DisplayBase display_base, U32 export_type_user_id);
|
|
||||||
|
|
||||||
virtual void GenerateFrameTabularText(U64 frame_index, DisplayBase display_base);
|
|
||||||
virtual void GeneratePacketTabularText(U64 packet_id, DisplayBase display_base);
|
|
||||||
virtual void GenerateTransactionTabularText(U64 transaction_id, DisplayBase display_base);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
IrFoxAnalyzerSettings* mSettings;
|
|
||||||
IrFoxAnalyzer* mAnalyzer;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -1,62 +0,0 @@
|
|||||||
#include "IrFoxAnalyzerSettings.h"
|
|
||||||
#include <AnalyzerHelpers.h>
|
|
||||||
|
|
||||||
IrFoxAnalyzerSettings::IrFoxAnalyzerSettings()
|
|
||||||
: mInputChannel(UNDEFINED_CHANNEL),
|
|
||||||
mInputChannelInterface()
|
|
||||||
{
|
|
||||||
mInputChannelInterface.SetTitleAndTooltip(
|
|
||||||
"IR",
|
|
||||||
"Demodulated IR receiver output (e.g. TSOP: idle HIGH, active LOW)");
|
|
||||||
mInputChannelInterface.SetChannel(mInputChannel);
|
|
||||||
|
|
||||||
AddInterface(&mInputChannelInterface);
|
|
||||||
|
|
||||||
AddExportOption(0, "Export as text/csv file");
|
|
||||||
AddExportExtension(0, "text", "txt");
|
|
||||||
AddExportExtension(0, "csv", "csv");
|
|
||||||
|
|
||||||
ClearChannels();
|
|
||||||
AddChannel(mInputChannel, "IR", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
IrFoxAnalyzerSettings::~IrFoxAnalyzerSettings()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IrFoxAnalyzerSettings::SetSettingsFromInterfaces()
|
|
||||||
{
|
|
||||||
mInputChannel = mInputChannelInterface.GetChannel();
|
|
||||||
|
|
||||||
ClearChannels();
|
|
||||||
AddChannel(mInputChannel, "IR Fox", true);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void IrFoxAnalyzerSettings::UpdateInterfacesFromSettings()
|
|
||||||
{
|
|
||||||
mInputChannelInterface.SetChannel(mInputChannel);
|
|
||||||
}
|
|
||||||
|
|
||||||
void IrFoxAnalyzerSettings::LoadSettings(const char* settings)
|
|
||||||
{
|
|
||||||
SimpleArchive text_archive;
|
|
||||||
text_archive.SetString(settings);
|
|
||||||
|
|
||||||
text_archive >> mInputChannel;
|
|
||||||
|
|
||||||
ClearChannels();
|
|
||||||
AddChannel(mInputChannel, "IR Fox", true);
|
|
||||||
|
|
||||||
UpdateInterfacesFromSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* IrFoxAnalyzerSettings::SaveSettings()
|
|
||||||
{
|
|
||||||
SimpleArchive text_archive;
|
|
||||||
|
|
||||||
text_archive << mInputChannel;
|
|
||||||
|
|
||||||
return SetReturnString(text_archive.GetString());
|
|
||||||
}
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
#ifndef IRFOX_ANALYZER_SETTINGS
|
|
||||||
#define IRFOX_ANALYZER_SETTINGS
|
|
||||||
|
|
||||||
#include <AnalyzerSettings.h>
|
|
||||||
#include <AnalyzerTypes.h>
|
|
||||||
|
|
||||||
class IrFoxAnalyzerSettings : public AnalyzerSettings
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IrFoxAnalyzerSettings();
|
|
||||||
virtual ~IrFoxAnalyzerSettings();
|
|
||||||
|
|
||||||
virtual bool SetSettingsFromInterfaces();
|
|
||||||
void UpdateInterfacesFromSettings();
|
|
||||||
virtual void LoadSettings(const char* settings);
|
|
||||||
virtual const char* SaveSettings();
|
|
||||||
|
|
||||||
Channel mInputChannel;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
AnalyzerSettingInterfaceChannel mInputChannelInterface;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -1,593 +0,0 @@
|
|||||||
#include "IrFoxDecoder.h"
|
|
||||||
#include <AnalyzerResults.h>
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <cmath>
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
void IrFoxDecoder::reset()
|
|
||||||
{
|
|
||||||
*this = IrFoxDecoder{};
|
|
||||||
rise_sync_time_us = irfox::kBitTimeUs;
|
|
||||||
next_control_bit = irfox::kBitPerByte;
|
|
||||||
have_last_processed = false;
|
|
||||||
last_processed_edge_us = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t IrFoxDecoder::ceil_div_u16(uint16_t val, uint16_t divider)
|
|
||||||
{
|
|
||||||
if (divider == 0)
|
|
||||||
return 0;
|
|
||||||
int ret = val / divider;
|
|
||||||
if ((val << 4) / divider - (ret << 4) >= 8)
|
|
||||||
ret++;
|
|
||||||
return static_cast<uint16_t>(ret);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t IrFoxDecoder::crc8(const uint8_t* data, uint8_t start, uint8_t end, uint8_t poly)
|
|
||||||
{
|
|
||||||
uint8_t crc = 0xff;
|
|
||||||
for (size_t i = start; i < end; i++)
|
|
||||||
{
|
|
||||||
crc ^= data[i];
|
|
||||||
for (size_t j = 0; j < 8; j++)
|
|
||||||
{
|
|
||||||
if ((crc & 0x80) != 0)
|
|
||||||
crc = static_cast<uint8_t>((crc << 1) ^ poly);
|
|
||||||
else
|
|
||||||
crc <<= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return crc;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IrFoxDecoder::crc_check(uint8_t len, uint16_t& crc_out)
|
|
||||||
{
|
|
||||||
crc_out = 0;
|
|
||||||
crc_out = static_cast<uint16_t>(static_cast<uint16_t>(crc8(data_buffer, 0, len, irfox::kPoly1) << 8) & 0xFF00u);
|
|
||||||
crc_out = static_cast<uint16_t>(crc_out | (crc8(data_buffer, 0, static_cast<uint8_t>(len + 1), irfox::kPoly2) & 0xFFu));
|
|
||||||
|
|
||||||
const bool ok = (data_buffer[len] == static_cast<uint8_t>((crc_out >> 8) & 0xFF)) &&
|
|
||||||
(data_buffer[len + 1] == static_cast<uint8_t>(crc_out & 0xFF));
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
void IrFoxDecoder::first_rx()
|
|
||||||
{
|
|
||||||
err_low_signal = err_high_signal = err_other = 0;
|
|
||||||
pack_size = 0;
|
|
||||||
is_buffer_overflow = false;
|
|
||||||
is_available = false;
|
|
||||||
buf_bit_pos = 0;
|
|
||||||
is_data = true;
|
|
||||||
i_data_buffer = 0;
|
|
||||||
next_control_bit = irfox::kBitPerByte;
|
|
||||||
i_sync_bit = 0;
|
|
||||||
err_sync_bit = 0;
|
|
||||||
is_wrong_pack = false;
|
|
||||||
is_preamb = true;
|
|
||||||
is_recive = false;
|
|
||||||
is_recive_raw = false;
|
|
||||||
msg_type_receive = 0;
|
|
||||||
rise_sync_time_us = irfox::kBitTimeUs;
|
|
||||||
std::memset(data_buffer, 0, sizeof data_buffer);
|
|
||||||
preamble_bubble_start_valid_ = false;
|
|
||||||
trim_first_data_bit_cell_ = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void IrFoxDecoder::listen_start(double t_us)
|
|
||||||
{
|
|
||||||
const uint32_t irmax = irfox::irTimeoutUs(rise_sync_time_us);
|
|
||||||
// Как IR_DecoderRaw::listenStart: пауза по lastEdgeTime, не по prevRise.
|
|
||||||
if (is_recive_raw && last_edge_time_us > 0.0 && (t_us - last_edge_time_us) > irmax * 2.0)
|
|
||||||
{
|
|
||||||
is_recive_raw = false;
|
|
||||||
first_rx();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void IrFoxDecoder::check_timeout(double t_us)
|
|
||||||
{
|
|
||||||
if (!is_recive)
|
|
||||||
return;
|
|
||||||
const uint32_t irmax = irfox::irTimeoutUs(rise_sync_time_us);
|
|
||||||
if (t_us - last_edge_time_us > irmax * 2.0)
|
|
||||||
{
|
|
||||||
// Как IR_DecoderRaw::checkTimeout после фикса: полный сброс, иначе залипание FSM.
|
|
||||||
is_recive = false;
|
|
||||||
msg_type_receive = 0;
|
|
||||||
is_recive_raw = false;
|
|
||||||
first_rx();
|
|
||||||
// Не last_edge_time_us = t_us: как IR_DecoderRaw — не расходить с «хвостом» фронтов.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void IrFoxDecoder::write_to_buffer(bool bit, bool pack_trace_invert_fix, uint64_t cell_start_s, uint64_t cell_end_s,
|
|
||||||
const IrFoxOnBit& on_bit, const IrFoxOnPacket& on_pkt, IrFoxEmitBitMode emit_mode)
|
|
||||||
{
|
|
||||||
if (i_data_buffer > irfox::kDataByteSizeMax * 8u)
|
|
||||||
{
|
|
||||||
if (!is_buffer_overflow && on_bit)
|
|
||||||
{
|
|
||||||
IrFoxEmitBit e{};
|
|
||||||
e.start_sample = static_cast<int64_t>(cell_start_s);
|
|
||||||
e.end_sample = static_cast<int64_t>(cell_end_s);
|
|
||||||
e.frame_type = IRF_FT_OVERFLOW;
|
|
||||||
e.mflags = DISPLAY_AS_ERROR_FLAG;
|
|
||||||
fill_err_snapshot(e);
|
|
||||||
std::strncpy(e.bubble_text, "OVF", sizeof e.bubble_text);
|
|
||||||
e.bubble_text[sizeof e.bubble_text - 1] = '\0';
|
|
||||||
on_bit(e);
|
|
||||||
}
|
|
||||||
is_buffer_overflow = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_buffer_overflow || is_preamb || is_wrong_pack)
|
|
||||||
{
|
|
||||||
// Как IR_DecoderRaw::writeToBuffer: полный first_rx() вместо только сброса флагов приёма.
|
|
||||||
first_rx();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buf_bit_pos == next_control_bit)
|
|
||||||
{
|
|
||||||
next_control_bit = next_control_bit + (is_data ? irfox::kSyncBits : irfox::kBitPerByte);
|
|
||||||
is_data = !is_data;
|
|
||||||
i_sync_bit = 0;
|
|
||||||
err_sync_bit = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_data)
|
|
||||||
{
|
|
||||||
const bool was_first_data_bit = (i_data_buffer == 0);
|
|
||||||
data_buffer[i_data_buffer / 8] |= static_cast<uint8_t>(bit ? 1 : 0) << (7 - (i_data_buffer % 8));
|
|
||||||
i_data_buffer++;
|
|
||||||
buf_bit_pos++;
|
|
||||||
|
|
||||||
uint8_t fl = 0;
|
|
||||||
if (pack_trace_invert_fix)
|
|
||||||
fl |= DISPLAY_AS_WARNING_FLAG;
|
|
||||||
if (on_bit && emit_mode == IrFoxEmitBitMode::WithBubble)
|
|
||||||
{
|
|
||||||
IrFoxEmitBit e{static_cast<int64_t>(cell_start_s), static_cast<int64_t>(cell_end_s), IRF_FT_DATA_BIT,
|
|
||||||
bit ? 1u : 0u, static_cast<uint64_t>(i_data_buffer - 1), fl, pack_trace_invert_fix, 0, 0, 0};
|
|
||||||
fill_err_snapshot(e);
|
|
||||||
e.bubble_text[0] = static_cast<char>(bit ? '1' : '0');
|
|
||||||
e.bubble_text[1] = '\0';
|
|
||||||
on_bit(e);
|
|
||||||
}
|
|
||||||
if (was_first_data_bit && trim_first_data_bit_cell_)
|
|
||||||
trim_first_data_bit_cell_ = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (i_sync_bit == 0)
|
|
||||||
{
|
|
||||||
const bool last_data_bit =
|
|
||||||
(data_buffer[((i_data_buffer - 1) / 8)] >> (7 - ((i_data_buffer - 1) % 8))) & 1;
|
|
||||||
if (bit != static_cast<bool>(last_data_bit))
|
|
||||||
{
|
|
||||||
buf_bit_pos++;
|
|
||||||
i_sync_bit++;
|
|
||||||
if (on_bit && emit_mode == IrFoxEmitBitMode::WithBubble)
|
|
||||||
{
|
|
||||||
IrFoxEmitBit e{static_cast<int64_t>(cell_start_s), static_cast<int64_t>(cell_end_s), IRF_FT_SYNC_BIT,
|
|
||||||
bit ? 1u : 0u, static_cast<uint64_t>(buf_bit_pos), 0, false, 0, 0, 0};
|
|
||||||
fill_err_snapshot(e);
|
|
||||||
std::snprintf(e.bubble_text, sizeof e.bubble_text, "s: %c", bit ? '1' : '0');
|
|
||||||
on_bit(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
i_sync_bit = 0;
|
|
||||||
err_other++;
|
|
||||||
err_sync_bit++;
|
|
||||||
const bool fatal_sync = (err_sync_bit >= irfox::kSyncBits);
|
|
||||||
if (fatal_sync)
|
|
||||||
is_wrong_pack = true;
|
|
||||||
if (on_bit && fatal_sync)
|
|
||||||
{
|
|
||||||
IrFoxEmitBit e{static_cast<int64_t>(cell_start_s), static_cast<int64_t>(cell_end_s), IRF_FT_ABORT,
|
|
||||||
0, 0, DISPLAY_AS_ERROR_FLAG, false, 0, 0, 0};
|
|
||||||
fill_err_snapshot(e);
|
|
||||||
std::strncpy(e.bubble_text, "SYNC!", sizeof e.bubble_text);
|
|
||||||
e.bubble_text[sizeof e.bubble_text - 1] = '\0';
|
|
||||||
on_bit(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
buf_bit_pos++;
|
|
||||||
i_sync_bit++;
|
|
||||||
if (on_bit && emit_mode == IrFoxEmitBitMode::WithBubble)
|
|
||||||
{
|
|
||||||
IrFoxEmitBit e{static_cast<int64_t>(cell_start_s), static_cast<int64_t>(cell_end_s), IRF_FT_SYNC_BIT,
|
|
||||||
bit ? 1u : 0u, static_cast<uint64_t>(buf_bit_pos), 0, false, 0, 0, 0};
|
|
||||||
fill_err_snapshot(e);
|
|
||||||
std::snprintf(e.bubble_text, sizeof e.bubble_text, "s: %c", bit ? '1' : '0');
|
|
||||||
on_bit(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is_wrong_pack = (err_sync_bit >= irfox::kSyncBits);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!is_available && is_data && !is_wrong_pack)
|
|
||||||
{
|
|
||||||
if (i_data_buffer == 8 * irfox::kMsgBytes)
|
|
||||||
pack_size = static_cast<uint16_t>(data_buffer[0] & 0x1Fu);
|
|
||||||
|
|
||||||
if (pack_size && (i_data_buffer == 8))
|
|
||||||
msg_type_receive = static_cast<uint8_t>((data_buffer[0] >> 5) | 0xF8u);
|
|
||||||
|
|
||||||
if (pack_size && (i_data_buffer == pack_size * irfox::kBitPerByte))
|
|
||||||
{
|
|
||||||
uint16_t crc_computed = 0;
|
|
||||||
const bool crc_ok = crc_check(static_cast<uint8_t>(pack_size - irfox::kCrcBytes), crc_computed);
|
|
||||||
crc_value = crc_computed;
|
|
||||||
is_recive = false;
|
|
||||||
is_recive_raw = false;
|
|
||||||
msg_type_receive = 0;
|
|
||||||
is_available = crc_ok;
|
|
||||||
|
|
||||||
IrFoxEmitPacket pkt{};
|
|
||||||
pkt.start_sample = static_cast<int64_t>(cell_start_s);
|
|
||||||
pkt.end_sample = static_cast<int64_t>(cell_end_s);
|
|
||||||
pkt.crc_ok = crc_ok;
|
|
||||||
pkt.pack_size = static_cast<uint8_t>(pack_size);
|
|
||||||
pkt.err_low = err_low_signal;
|
|
||||||
pkt.err_high = err_high_signal;
|
|
||||||
pkt.err_other = err_other;
|
|
||||||
if (pack_size > 0 && pack_size <= irfox::kDataByteSizeMax)
|
|
||||||
std::memcpy(pkt.data_bytes, data_buffer, pack_size);
|
|
||||||
if (on_pkt)
|
|
||||||
on_pkt(pkt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void IrFoxDecoder::processEdge(uint64_t sample, bool rising, uint32_t fs, const IrFoxOnBit& on_bit,
|
|
||||||
const IrFoxOnPacket& on_pkt)
|
|
||||||
{
|
|
||||||
const double t_us = sample_to_us(sample, fs);
|
|
||||||
const uint32_t irmax = irfox::irTimeoutUs(rise_sync_time_us);
|
|
||||||
uint32_t rise_min_us = rise_sync_time_us > irfox::kToleranceUs ? rise_sync_time_us - irfox::kToleranceUs : 0U;
|
|
||||||
|
|
||||||
listen_start(t_us);
|
|
||||||
|
|
||||||
// Как IR_DecoderRaw: пауза между фронтами по lastEdgeTime при активном приёме кадра.
|
|
||||||
if (last_edge_time_us > 0.0 && (t_us - last_edge_time_us) > irmax * 2.0 && is_recive)
|
|
||||||
check_timeout(t_us);
|
|
||||||
|
|
||||||
last_edge_time_us = t_us;
|
|
||||||
last_edge_sample = sample;
|
|
||||||
|
|
||||||
const uint32_t rise_max_us = rise_sync_time_us + irfox::kToleranceUs;
|
|
||||||
|
|
||||||
/** Визуализация: начало PRE с ближайшего спада в пределах ~3 битовых периодов (ИК-метка). */
|
|
||||||
auto new_bubble_preamble_start = [&](uint64_t edge_s, bool is_rising) -> uint64_t {
|
|
||||||
if (!is_rising)
|
|
||||||
return edge_s;
|
|
||||||
if (edge_s > prev_fall_sample)
|
|
||||||
{
|
|
||||||
const double span_us = double(edge_s - prev_fall_sample) * 1e6 / double(fs);
|
|
||||||
const double max_us = double(rise_max_us) * 3.0;
|
|
||||||
if (span_us <= max_us)
|
|
||||||
return prev_fall_sample;
|
|
||||||
}
|
|
||||||
return edge_s;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (rising)
|
|
||||||
{
|
|
||||||
const double delta_rp = t_us - prev_rise_us;
|
|
||||||
const uint32_t cand_rp = static_cast<uint32_t>(delta_rp);
|
|
||||||
const uint32_t cand_ht = static_cast<uint32_t>(t_us - prev_fall_us);
|
|
||||||
const uint32_t cand_lt = static_cast<uint32_t>(prev_fall_us - prev_rise_us);
|
|
||||||
|
|
||||||
#if IRFOX_SHORT_LOW_GLITCH_REJECT
|
|
||||||
const bool short_low_glitch =
|
|
||||||
is_recive && !is_preamb && cand_ht < (rise_min_us / 8U) && cand_lt >= rise_min_us &&
|
|
||||||
cand_rp >= rise_min_us && cand_rp <= irmax;
|
|
||||||
if (short_low_glitch)
|
|
||||||
{
|
|
||||||
err_other++;
|
|
||||||
irfox::irfoxGlitchPhaseNudgeUs(t_us, rise_sync_time_us, prev_rise_us);
|
|
||||||
last_processed_edge_us = t_us;
|
|
||||||
have_last_processed = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#if IRFOX_MICRO_GAP_RISE_REJECT
|
|
||||||
const bool micro_gap_cand_lt_ok =
|
|
||||||
(cand_lt >= rise_min_us) || (cand_lt >= (rise_min_us / 4U) && cand_lt < rise_min_us);
|
|
||||||
const bool micro_gap_rise = is_recive && !is_preamb && cand_ht < (rise_min_us / 8U) && micro_gap_cand_lt_ok &&
|
|
||||||
cand_rp >= (rise_min_us / 4U) && cand_rp < rise_min_us && cand_rp <= irmax;
|
|
||||||
if (micro_gap_rise)
|
|
||||||
{
|
|
||||||
err_other++;
|
|
||||||
irfox::irfoxGlitchPhaseNudgeUs(t_us, rise_sync_time_us, prev_rise_us);
|
|
||||||
last_processed_edge_us = t_us;
|
|
||||||
have_last_processed = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
if (cand_rp <= rise_max_us / 4U && !high_count && !low_count)
|
|
||||||
{
|
|
||||||
err_other++;
|
|
||||||
last_processed_edge_us = t_us;
|
|
||||||
have_last_processed = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Визуализация PRE: длинная пауза, первый подъём — якорь от спада метки (декод как STM32DMA).
|
|
||||||
if (cand_rp > irmax * 2U && !is_recive_raw)
|
|
||||||
{
|
|
||||||
preamble_bubble_start_sample_ = new_bubble_preamble_start(sample, true);
|
|
||||||
preamble_bubble_start_valid_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bool accept_rise_timing =
|
|
||||||
(delta_rp > static_cast<double>(rise_max_us) / 4.0) || high_count != 0 || low_count != 0;
|
|
||||||
if (accept_rise_timing)
|
|
||||||
{
|
|
||||||
rise_period_anchor_sample_ = prev_rise_sample;
|
|
||||||
rise_period_us = cand_rp;
|
|
||||||
high_time_us = cand_ht;
|
|
||||||
low_time_us = cand_lt;
|
|
||||||
prev_rise_us = t_us;
|
|
||||||
prev_rise_sample = sample;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
err_other++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (t_us - prev_fall_us > rise_min_us / 4.0)
|
|
||||||
{
|
|
||||||
prev_fall_us = t_us;
|
|
||||||
prev_fall_sample = sample;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
err_other++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Как IR_DecoderRaw::tick: после длинной паузы старт сырого приёма (без отдельного firstRX — флаги ниже).
|
|
||||||
if (t_us > prev_rise_us && (t_us - prev_rise_us) > irmax * 2.0 && !is_recive_raw)
|
|
||||||
{
|
|
||||||
preamb_front_counter = static_cast<int8_t>(irfox::kPreambFronts - 1);
|
|
||||||
is_preamb = true;
|
|
||||||
is_recive = true;
|
|
||||||
is_recive_raw = true;
|
|
||||||
is_wrong_pack = false;
|
|
||||||
if (!preamble_bubble_start_valid_)
|
|
||||||
{
|
|
||||||
preamble_bubble_start_sample_ = new_bubble_preamble_start(sample, rising);
|
|
||||||
preamble_bubble_start_valid_ = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (preamb_front_counter)
|
|
||||||
{
|
|
||||||
if (rising && rise_period_us < irmax)
|
|
||||||
{
|
|
||||||
if (rise_period_us < rise_min_us / 2U)
|
|
||||||
{
|
|
||||||
preamb_front_counter += 2;
|
|
||||||
err_other++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
preamb_front_counter--;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (is_preamb)
|
|
||||||
{
|
|
||||||
is_preamb = false;
|
|
||||||
// IR_DecoderRaw: prevRise += risePeriod / 2 — фаза как в прошивке.
|
|
||||||
// Бабл PRE: до текущего фронта (sample−1), чтобы охватить все kPreambPulse периодов (3 импульса),
|
|
||||||
// а не только до предыдущего подъёма (~2 периода).
|
|
||||||
const uint64_t preamble_bubble_end_sample = sample > 0 ? sample - 1 : sample;
|
|
||||||
prev_rise_us += rise_period_us / 2.0;
|
|
||||||
{
|
|
||||||
const double half_us = 0.5 * static_cast<double>(rise_period_us);
|
|
||||||
const uint64_t half_s = static_cast<uint64_t>(std::llround(half_us * double(fs) / 1e6));
|
|
||||||
prev_rise_sample += half_s;
|
|
||||||
}
|
|
||||||
trim_first_data_bit_cell_ = true;
|
|
||||||
if (on_bit && preamble_bubble_start_valid_)
|
|
||||||
{
|
|
||||||
int64_t pe_start = static_cast<int64_t>(preamble_bubble_start_sample_);
|
|
||||||
int64_t pe_end = static_cast<int64_t>(preamble_bubble_end_sample);
|
|
||||||
if (preamble_bubble_end_sample == 0 || pe_end < pe_start)
|
|
||||||
pe_end = static_cast<int64_t>(sample > 0 ? sample - 1 : sample);
|
|
||||||
IrFoxEmitBit pe{};
|
|
||||||
pe.start_sample = pe_start;
|
|
||||||
pe.end_sample = pe_end;
|
|
||||||
pe.frame_type = IRF_FT_PREAMBLE;
|
|
||||||
fill_err_snapshot(pe);
|
|
||||||
std::strncpy(pe.bubble_text, "PRE", sizeof pe.bubble_text);
|
|
||||||
pe.bubble_text[sizeof pe.bubble_text - 1] = '\0';
|
|
||||||
on_bit(pe);
|
|
||||||
}
|
|
||||||
preamble_bubble_start_valid_ = false;
|
|
||||||
last_processed_edge_us = t_us;
|
|
||||||
have_last_processed = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_preamb)
|
|
||||||
{
|
|
||||||
last_processed_edge_us = t_us;
|
|
||||||
have_last_processed = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rise_period_us > irmax || is_buffer_overflow || rise_period_us < rise_min_us || is_wrong_pack)
|
|
||||||
{
|
|
||||||
last_processed_edge_us = t_us;
|
|
||||||
have_last_processed = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rising)
|
|
||||||
{
|
|
||||||
high_count = low_count = all_count = 0;
|
|
||||||
bool invert_err = false;
|
|
||||||
|
|
||||||
uint64_t cell_start_s = rise_period_anchor_sample_;
|
|
||||||
const uint64_t cell_end_s = sample > 0 ? sample - 1 : sample;
|
|
||||||
// После prev_rise += half period якорь может оказаться близко к текущему подъёму;
|
|
||||||
// max(anchor, prev_fall) > cell_end даёт пустой интервал — бабл первого бита пропадает.
|
|
||||||
if (trim_first_data_bit_cell_ && is_data && i_data_buffer == 0)
|
|
||||||
{
|
|
||||||
const uint64_t trimmed = std::max(cell_start_s, prev_fall_sample);
|
|
||||||
if (trimmed <= cell_end_s)
|
|
||||||
cell_start_s = trimmed;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (irfox::aroundRisePeriod(rise_period_us, rise_sync_time_us))
|
|
||||||
{
|
|
||||||
if (high_time_us > low_time_us)
|
|
||||||
write_to_buffer(true, false, cell_start_s, cell_end_s, on_bit, on_pkt, IrFoxEmitBitMode::WithBubble);
|
|
||||||
else
|
|
||||||
write_to_buffer(false, false, cell_start_s, cell_end_s, on_bit, on_pkt, IrFoxEmitBitMode::WithBubble);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
uint16_t hc = ceil_div_u16(static_cast<uint16_t>(high_time_us > 0xFFFF ? 0xFFFF : high_time_us),
|
|
||||||
static_cast<uint16_t>(rise_sync_time_us));
|
|
||||||
uint16_t lc = ceil_div_u16(static_cast<uint16_t>(low_time_us > 0xFFFF ? 0xFFFF : low_time_us),
|
|
||||||
static_cast<uint16_t>(rise_sync_time_us));
|
|
||||||
uint16_t ac = ceil_div_u16(static_cast<uint16_t>(rise_period_us > 0xFFFF ? 0xFFFF : rise_period_us),
|
|
||||||
static_cast<uint16_t>(rise_sync_time_us));
|
|
||||||
high_count = static_cast<int8_t>(hc > 127 ? 127 : hc);
|
|
||||||
low_count = static_cast<int8_t>(lc > 127 ? 127 : lc);
|
|
||||||
all_count = static_cast<int8_t>(ac > 127 ? 127 : ac);
|
|
||||||
|
|
||||||
if (high_count == 0 && high_time_us > rise_sync_time_us / 3U)
|
|
||||||
{
|
|
||||||
high_count++;
|
|
||||||
err_other++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (low_count + high_count > all_count)
|
|
||||||
{
|
|
||||||
if (low_count > high_count)
|
|
||||||
{
|
|
||||||
low_count = static_cast<int8_t>(all_count - high_count);
|
|
||||||
err_low_signal = static_cast<uint8_t>(err_low_signal + static_cast<uint8_t>(low_count));
|
|
||||||
}
|
|
||||||
else if (low_count < high_count)
|
|
||||||
{
|
|
||||||
high_count = static_cast<int8_t>(all_count - low_count);
|
|
||||||
err_high_signal = static_cast<uint8_t>(err_high_signal + static_cast<uint8_t>(high_count));
|
|
||||||
}
|
|
||||||
else if (low_count == high_count)
|
|
||||||
{
|
|
||||||
invert_err = true;
|
|
||||||
err_other = static_cast<uint8_t>(err_other + static_cast<uint8_t>(all_count));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (low_count < high_count)
|
|
||||||
err_high_signal = static_cast<uint8_t>(err_high_signal + static_cast<uint8_t>(high_count));
|
|
||||||
else
|
|
||||||
err_low_signal = static_cast<uint8_t>(err_low_signal + static_cast<uint8_t>(low_count));
|
|
||||||
|
|
||||||
const bool burst_is_data_start = is_data;
|
|
||||||
const uint64_t merge_bit_index =
|
|
||||||
burst_is_data_start ? static_cast<uint64_t>(i_data_buffer) : static_cast<uint64_t>(buf_bit_pos + 1);
|
|
||||||
char s_bits[20]{};
|
|
||||||
char d_bits[20]{};
|
|
||||||
size_t s_n = 0;
|
|
||||||
size_t d_n = 0;
|
|
||||||
int first_merge_bit = -1;
|
|
||||||
bool merge_warn = false;
|
|
||||||
auto append_merge = [&](bool as_data, bool bitv) {
|
|
||||||
if (first_merge_bit < 0)
|
|
||||||
first_merge_bit = bitv ? 1 : 0;
|
|
||||||
char* buf = as_data ? d_bits : s_bits;
|
|
||||||
size_t& n = as_data ? d_n : s_n;
|
|
||||||
if (n + 1 < sizeof s_bits)
|
|
||||||
buf[n++] = static_cast<char>(bitv ? '1' : '0');
|
|
||||||
};
|
|
||||||
auto emit_merge_if_needed = [&]() {
|
|
||||||
if ((s_n == 0 && d_n == 0) || !on_bit)
|
|
||||||
return;
|
|
||||||
IrFoxEmitBit e{};
|
|
||||||
e.start_sample = static_cast<int64_t>(cell_start_s);
|
|
||||||
e.end_sample = static_cast<int64_t>(cell_end_s);
|
|
||||||
e.frame_type = (d_n > 0) ? IRF_FT_DATA_BIT : IRF_FT_SYNC_BIT;
|
|
||||||
e.bit_value = (first_merge_bit > 0) ? 1u : 0u;
|
|
||||||
e.bit_index = merge_bit_index;
|
|
||||||
e.mflags = merge_warn ? DISPLAY_AS_WARNING_FLAG : 0;
|
|
||||||
e.invert_fix = merge_warn;
|
|
||||||
fill_err_snapshot(e);
|
|
||||||
s_bits[s_n] = '\0';
|
|
||||||
d_bits[d_n] = '\0';
|
|
||||||
if (s_n && d_n)
|
|
||||||
std::snprintf(e.bubble_text, sizeof e.bubble_text, "s: %s d: %s", s_bits, d_bits);
|
|
||||||
else if (s_n)
|
|
||||||
std::snprintf(e.bubble_text, sizeof e.bubble_text, "s: %s", s_bits);
|
|
||||||
else
|
|
||||||
std::memcpy(e.bubble_text, d_bits, d_n + 1);
|
|
||||||
on_bit(e);
|
|
||||||
};
|
|
||||||
|
|
||||||
for (int8_t i = 0; i < low_count && 8 - i; i++)
|
|
||||||
{
|
|
||||||
const bool row_is_data = is_data;
|
|
||||||
if (i == low_count - 1 && invert_err)
|
|
||||||
{
|
|
||||||
invert_err = false;
|
|
||||||
write_to_buffer(true, true, cell_start_s, cell_end_s, on_bit, on_pkt, IrFoxEmitBitMode::Quiet);
|
|
||||||
merge_warn = true;
|
|
||||||
append_merge(row_is_data, true);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
write_to_buffer(false, false, cell_start_s, cell_end_s, on_bit, on_pkt, IrFoxEmitBitMode::Quiet);
|
|
||||||
append_merge(row_is_data, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int8_t i = 0; i < high_count && 8 - i; i++)
|
|
||||||
{
|
|
||||||
const bool row_is_data = is_data;
|
|
||||||
if (i == high_count - 1 && invert_err)
|
|
||||||
{
|
|
||||||
invert_err = false;
|
|
||||||
write_to_buffer(false, true, cell_start_s, cell_end_s, on_bit, on_pkt, IrFoxEmitBitMode::Quiet);
|
|
||||||
merge_warn = true;
|
|
||||||
append_merge(row_is_data, false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
write_to_buffer(true, false, cell_start_s, cell_end_s, on_bit, on_pkt, IrFoxEmitBitMode::Quiet);
|
|
||||||
append_merge(row_is_data, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
emit_merge_if_needed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
last_processed_edge_us = t_us;
|
|
||||||
have_last_processed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void IrFoxDecoder::flushEnd(uint64_t last_sample, uint32_t fs, const IrFoxOnBit& on_bit, const IrFoxOnPacket& on_pkt)
|
|
||||||
{
|
|
||||||
const double t_us = sample_to_us(last_sample, fs);
|
|
||||||
listen_start(t_us);
|
|
||||||
check_timeout(t_us);
|
|
||||||
(void)on_bit;
|
|
||||||
(void)on_pkt;
|
|
||||||
}
|
|
||||||
@ -1,135 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "IrFoxProtocolConstants.h"
|
|
||||||
#include <cstdint>
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
enum IrFoxFrameType : uint8_t
|
|
||||||
{
|
|
||||||
IRF_FT_DATA_BIT = 1,
|
|
||||||
IRF_FT_SYNC_BIT = 2,
|
|
||||||
IRF_FT_PACKET_OK = 3,
|
|
||||||
IRF_FT_PACKET_CRC_FAIL = 4,
|
|
||||||
IRF_FT_OVERFLOW = 5,
|
|
||||||
IRF_FT_ABORT = 6,
|
|
||||||
IRF_FT_PREAMBLE = 7,
|
|
||||||
};
|
|
||||||
|
|
||||||
/** WithBubble — вызвать on_bit; Quiet — только обновить состояние (для пакета битов с одного фронта). */
|
|
||||||
enum class IrFoxEmitBitMode : uint8_t
|
|
||||||
{
|
|
||||||
WithBubble,
|
|
||||||
Quiet,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct IrFoxEmitBit
|
|
||||||
{
|
|
||||||
int64_t start_sample;
|
|
||||||
int64_t end_sample;
|
|
||||||
uint8_t frame_type;
|
|
||||||
uint64_t bit_value;
|
|
||||||
uint64_t bit_index;
|
|
||||||
uint8_t mflags;
|
|
||||||
bool invert_fix;
|
|
||||||
uint8_t err_low;
|
|
||||||
uint8_t err_high;
|
|
||||||
uint8_t err_other;
|
|
||||||
/** Подпись бабла: "0", "1" или склейка "001"; пусто — смотреть bit_value. */
|
|
||||||
char bubble_text[32]{};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct IrFoxEmitPacket
|
|
||||||
{
|
|
||||||
int64_t start_sample;
|
|
||||||
int64_t end_sample;
|
|
||||||
bool crc_ok;
|
|
||||||
uint8_t pack_size;
|
|
||||||
uint8_t err_low;
|
|
||||||
uint8_t err_high;
|
|
||||||
uint8_t err_other;
|
|
||||||
uint8_t data_bytes[irfox::kDataByteSizeMax];
|
|
||||||
};
|
|
||||||
|
|
||||||
using IrFoxOnBit = std::function<void(const IrFoxEmitBit&)>;
|
|
||||||
using IrFoxOnPacket = std::function<void(const IrFoxEmitPacket&)>;
|
|
||||||
|
|
||||||
class IrFoxDecoder
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
void reset();
|
|
||||||
void processEdge(uint64_t sample, bool rising, uint32_t sample_rate_hz, const IrFoxOnBit& on_bit,
|
|
||||||
const IrFoxOnPacket& on_pkt);
|
|
||||||
void flushEnd(uint64_t last_sample, uint32_t sample_rate_hz, const IrFoxOnBit& on_bit, const IrFoxOnPacket& on_pkt);
|
|
||||||
|
|
||||||
private:
|
|
||||||
static uint16_t ceil_div_u16(uint16_t val, uint16_t divider);
|
|
||||||
static uint8_t crc8(const uint8_t* data, uint8_t start, uint8_t end, uint8_t poly);
|
|
||||||
bool crc_check(uint8_t len, uint16_t& crc_out);
|
|
||||||
|
|
||||||
void first_rx();
|
|
||||||
void listen_start(double t_us);
|
|
||||||
void check_timeout(double t_us);
|
|
||||||
void write_to_buffer(bool bit, bool pack_trace_invert_fix, uint64_t cell_start_s, uint64_t cell_end_s,
|
|
||||||
const IrFoxOnBit& on_bit, const IrFoxOnPacket& on_pkt,
|
|
||||||
IrFoxEmitBitMode emit_mode = IrFoxEmitBitMode::WithBubble);
|
|
||||||
|
|
||||||
double sample_to_us(uint64_t sample, uint32_t fs) const { return double(sample) * 1e6 / double(fs); }
|
|
||||||
|
|
||||||
// --- state (mirror IR_DecoderRaw) ---
|
|
||||||
uint8_t data_buffer[irfox::kDataByteSizeMax]{};
|
|
||||||
uint8_t err_low_signal = 0;
|
|
||||||
uint8_t err_high_signal = 0;
|
|
||||||
uint8_t err_other = 0;
|
|
||||||
|
|
||||||
bool is_available = false;
|
|
||||||
uint16_t pack_size = 0;
|
|
||||||
uint16_t crc_value = 0;
|
|
||||||
bool is_recive = false;
|
|
||||||
bool is_recive_raw = false;
|
|
||||||
bool is_preamb = false;
|
|
||||||
bool is_buffer_overflow = false;
|
|
||||||
bool is_wrong_pack = false;
|
|
||||||
|
|
||||||
uint32_t rise_sync_time_us = irfox::kBitTimeUs;
|
|
||||||
|
|
||||||
double prev_rise_us = 0;
|
|
||||||
double prev_fall_us = 0;
|
|
||||||
uint64_t prev_rise_sample = 0;
|
|
||||||
uint64_t prev_fall_sample = 0;
|
|
||||||
/** Сэмпл предыдущего нарастающего фронта до обновления на текущем тике (граница ячейки бита, см. IR_DecoderRaw::tick). */
|
|
||||||
uint64_t rise_period_anchor_sample_ = 0;
|
|
||||||
uint64_t preamble_bubble_start_sample_ = 0;
|
|
||||||
bool preamble_bubble_start_valid_ = false;
|
|
||||||
bool trim_first_data_bit_cell_ = false;
|
|
||||||
|
|
||||||
double last_edge_time_us = 0;
|
|
||||||
uint64_t last_edge_sample = 0;
|
|
||||||
double last_processed_edge_us = 0;
|
|
||||||
bool have_last_processed = false;
|
|
||||||
|
|
||||||
void fill_err_snapshot(IrFoxEmitBit& e) const
|
|
||||||
{
|
|
||||||
e.err_low = err_low_signal;
|
|
||||||
e.err_high = err_high_signal;
|
|
||||||
e.err_other = err_other;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t rise_period_us = 0;
|
|
||||||
uint32_t high_time_us = 0;
|
|
||||||
uint32_t low_time_us = 0;
|
|
||||||
|
|
||||||
int8_t high_count = 0;
|
|
||||||
int8_t low_count = 0;
|
|
||||||
int8_t all_count = 0;
|
|
||||||
|
|
||||||
uint16_t wrong_counter = 0;
|
|
||||||
int8_t preamb_front_counter = 0;
|
|
||||||
int16_t buf_bit_pos = 0;
|
|
||||||
bool is_data = true;
|
|
||||||
uint16_t i_data_buffer = 0;
|
|
||||||
uint16_t next_control_bit = irfox::kBitPerByte;
|
|
||||||
uint8_t i_sync_bit = 0;
|
|
||||||
uint8_t err_sync_bit = 0;
|
|
||||||
uint16_t error_counter = 0;
|
|
||||||
uint8_t msg_type_receive = 0;
|
|
||||||
};
|
|
||||||
@ -1,71 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
namespace irfox {
|
|
||||||
|
|
||||||
constexpr uint32_t kCarrierPeriodUs = 1000000U / 38000U;
|
|
||||||
constexpr uint32_t kBitActiveTakts = 25U;
|
|
||||||
constexpr uint32_t kBitPauseTakts = 12U;
|
|
||||||
constexpr uint32_t kBitTakts = kBitActiveTakts + kBitPauseTakts;
|
|
||||||
constexpr uint32_t kBitTimeUs = kBitTakts * kCarrierPeriodUs;
|
|
||||||
constexpr uint32_t kToleranceUs = 300U;
|
|
||||||
|
|
||||||
/** Мин. длительность плато (мкс) для потокового анти-глитча в анализаторе; согласовано с IR_INPUT_MIN_PULSE_US. */
|
|
||||||
constexpr uint32_t kMinFilteredPulseUs = 10U;
|
|
||||||
|
|
||||||
constexpr uint8_t kBitPerByte = 8U;
|
|
||||||
constexpr uint8_t kMsgBytes = 1;
|
|
||||||
constexpr uint8_t kAddrBytes = 2;
|
|
||||||
constexpr uint8_t kCrcBytes = 2;
|
|
||||||
constexpr uint8_t kPoly1 = 0x31;
|
|
||||||
constexpr uint8_t kPoly2 = 0x8C;
|
|
||||||
constexpr uint8_t kSyncBits = 3U;
|
|
||||||
constexpr uint8_t kBytePerPack = 31;
|
|
||||||
constexpr uint8_t kDataByteSizeMax =
|
|
||||||
static_cast<uint8_t>(kMsgBytes + kAddrBytes + kAddrBytes + kBytePerPack + kCrcBytes);
|
|
||||||
|
|
||||||
constexpr uint8_t kPreambPulse = 3;
|
|
||||||
constexpr uint8_t kPreambFronts = kPreambPulse * 2U;
|
|
||||||
|
|
||||||
/** Отброс ложного подъёма после микро-LOW в паузе; зеркало IR_config.h (прошивка). */
|
|
||||||
#ifndef IRFOX_SHORT_LOW_GLITCH_REJECT
|
|
||||||
#define IRFOX_SHORT_LOW_GLITCH_REJECT 1
|
|
||||||
#endif
|
|
||||||
#ifndef IRFOX_GLITCH_REJECT_PHASE_NUDGE
|
|
||||||
#define IRFOX_GLITCH_REJECT_PHASE_NUDGE 1
|
|
||||||
#endif
|
|
||||||
#ifndef IRFOX_MICRO_GAP_RISE_REJECT
|
|
||||||
#define IRFOX_MICRO_GAP_RISE_REJECT 1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
inline uint32_t irTimeoutUs(uint32_t riseSyncTimeUs)
|
|
||||||
{
|
|
||||||
const uint32_t riseMax = riseSyncTimeUs + kToleranceUs;
|
|
||||||
return riseMax * (8U + kSyncBits + 1U);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Как IR_DecoderRaw.h: aroundRise(t) → riseTimeMin < t && t < riseTimeMax (ветка STM32DMA). */
|
|
||||||
inline bool aroundRisePeriod(uint32_t periodUs, uint32_t riseSyncTimeUs)
|
|
||||||
{
|
|
||||||
const uint32_t lo = riseSyncTimeUs > kToleranceUs ? riseSyncTimeUs - kToleranceUs : 0U;
|
|
||||||
const uint32_t hi = riseSyncTimeUs + kToleranceUs;
|
|
||||||
return lo < periodUs && periodUs < hi;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void irfoxGlitchPhaseNudgeUs(double edge_us, uint32_t rise_sync_us, double& prev_rise_us)
|
|
||||||
{
|
|
||||||
#if IRFOX_GLITCH_REJECT_PHASE_NUDGE
|
|
||||||
if (!(edge_us > static_cast<double>(rise_sync_us)))
|
|
||||||
return;
|
|
||||||
const double nudged = edge_us - static_cast<double>(rise_sync_us);
|
|
||||||
if (nudged > prev_rise_us && nudged < edge_us)
|
|
||||||
prev_rise_us = nudged;
|
|
||||||
#else
|
|
||||||
(void)edge_us;
|
|
||||||
(void)rise_sync_us;
|
|
||||||
(void)prev_rise_us;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace irfox
|
|
||||||
@ -1,67 +0,0 @@
|
|||||||
#include "IrFoxSimulationDataGenerator.h"
|
|
||||||
#include "IrFoxAnalyzerSettings.h"
|
|
||||||
#include <AnalyzerHelpers.h>
|
|
||||||
|
|
||||||
IrFoxSimulationDataGenerator::IrFoxSimulationDataGenerator()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
IrFoxSimulationDataGenerator::~IrFoxSimulationDataGenerator()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void IrFoxSimulationDataGenerator::Initialize(U32 simulation_sample_rate, IrFoxAnalyzerSettings* settings)
|
|
||||||
{
|
|
||||||
mSimulationSampleRateHz = simulation_sample_rate;
|
|
||||||
mSettings = settings;
|
|
||||||
|
|
||||||
mIrSimulationData.SetChannel(mSettings->mInputChannel);
|
|
||||||
mIrSimulationData.SetSampleRate(simulation_sample_rate);
|
|
||||||
// Как у «покоя» на выходе TSOP: линия подтянута вверх
|
|
||||||
mIrSimulationData.SetInitialBitState(BIT_HIGH);
|
|
||||||
}
|
|
||||||
|
|
||||||
void IrFoxSimulationDataGenerator::EmitIdle(U32 samples)
|
|
||||||
{
|
|
||||||
mIrSimulationData.Advance(samples);
|
|
||||||
}
|
|
||||||
|
|
||||||
void IrFoxSimulationDataGenerator::EmitLow(U32 samples)
|
|
||||||
{
|
|
||||||
mIrSimulationData.TransitionIfNeeded(BIT_LOW);
|
|
||||||
mIrSimulationData.Advance(samples);
|
|
||||||
}
|
|
||||||
|
|
||||||
void IrFoxSimulationDataGenerator::EmitHigh(U32 samples)
|
|
||||||
{
|
|
||||||
mIrSimulationData.TransitionIfNeeded(BIT_HIGH);
|
|
||||||
mIrSimulationData.Advance(samples);
|
|
||||||
}
|
|
||||||
|
|
||||||
U32 IrFoxSimulationDataGenerator::GenerateSimulationData(U64 largest_sample_requested, U32 sample_rate,
|
|
||||||
SimulationChannelDescriptor** simulation_channel)
|
|
||||||
{
|
|
||||||
const U64 adjusted_largest_sample_requested =
|
|
||||||
AnalyzerHelpers::AdjustSimulationTargetSample(largest_sample_requested, sample_rate, mSimulationSampleRateHz);
|
|
||||||
|
|
||||||
// Упрощённый «пакет»: несколько импульсов вниз (активный уровень приёмника).
|
|
||||||
while (mIrSimulationData.GetCurrentSampleNumber() < adjusted_largest_sample_requested)
|
|
||||||
{
|
|
||||||
const U32 us_to_samples = mSimulationSampleRateHz / 1000000;
|
|
||||||
if (us_to_samples == 0)
|
|
||||||
break;
|
|
||||||
|
|
||||||
EmitIdle(500 * us_to_samples);
|
|
||||||
EmitLow(4500 * us_to_samples);
|
|
||||||
EmitHigh(4500 * us_to_samples);
|
|
||||||
EmitLow(4500 * us_to_samples);
|
|
||||||
EmitHigh(4500 * us_to_samples);
|
|
||||||
EmitLow(560 * us_to_samples);
|
|
||||||
EmitHigh(560 * us_to_samples);
|
|
||||||
EmitLow(560 * us_to_samples);
|
|
||||||
EmitHigh(20000 * us_to_samples);
|
|
||||||
}
|
|
||||||
|
|
||||||
*simulation_channel = &mIrSimulationData;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
#ifndef IRFOX_SIMULATION_DATA_GENERATOR
|
|
||||||
#define IRFOX_SIMULATION_DATA_GENERATOR
|
|
||||||
|
|
||||||
#include <SimulationChannelDescriptor.h>
|
|
||||||
|
|
||||||
class IrFoxAnalyzerSettings;
|
|
||||||
|
|
||||||
class IrFoxSimulationDataGenerator
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IrFoxSimulationDataGenerator();
|
|
||||||
~IrFoxSimulationDataGenerator();
|
|
||||||
|
|
||||||
void Initialize(U32 simulation_sample_rate, IrFoxAnalyzerSettings* settings);
|
|
||||||
U32 GenerateSimulationData(U64 newest_sample_requested, U32 sample_rate, SimulationChannelDescriptor** simulation_channel);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
IrFoxAnalyzerSettings* mSettings;
|
|
||||||
U32 mSimulationSampleRateHz;
|
|
||||||
SimulationChannelDescriptor mIrSimulationData;
|
|
||||||
|
|
||||||
void EmitIdle(U32 samples);
|
|
||||||
void EmitLow(U32 samples);
|
|
||||||
void EmitHigh(U32 samples);
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
name: Build
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [master, main]
|
|
||||||
tags:
|
|
||||||
- "*"
|
|
||||||
pull_request:
|
|
||||||
branches: [master, main]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
windows:
|
|
||||||
runs-on: windows-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Build
|
|
||||||
run: |
|
|
||||||
cmake -B ${{github.workspace}}/Analyzer/raw/PulseLengthStat/build -S ${{github.workspace}}/Analyzer/raw/PulseLengthStat -A x64
|
|
||||||
cmake --build ${{github.workspace}}/Analyzer/raw/PulseLengthStat/build --config Release
|
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: windows
|
|
||||||
path: ${{github.workspace}}/Analyzer/raw/dll/*.dll
|
|
||||||
|
|
||||||
macos:
|
|
||||||
runs-on: macos-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Build
|
|
||||||
run: |
|
|
||||||
cmake -B ${{github.workspace}}/Analyzer/raw/PulseLengthStat/build -S ${{github.workspace}}/Analyzer/raw/PulseLengthStat -DCMAKE_BUILD_TYPE=Release
|
|
||||||
cmake --build ${{github.workspace}}/Analyzer/raw/PulseLengthStat/build
|
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: macos
|
|
||||||
path: ${{github.workspace}}/Analyzer/raw/dll/*.so
|
|
||||||
|
|
||||||
linux:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Build
|
|
||||||
run: |
|
|
||||||
cmake -B ${{github.workspace}}/Analyzer/raw/PulseLengthStat/build -S ${{github.workspace}}/Analyzer/raw/PulseLengthStat -DCMAKE_BUILD_TYPE=Release
|
|
||||||
cmake --build ${{github.workspace}}/Analyzer/raw/PulseLengthStat/build
|
|
||||||
env:
|
|
||||||
CC: gcc-10
|
|
||||||
CXX: g++-10
|
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: linux
|
|
||||||
path: ${{github.workspace}}/Analyzer/raw/dll/*.so
|
|
||||||
3
Analyzer/raw/PulseLengthStat/.gitignore
vendored
3
Analyzer/raw/PulseLengthStat/.gitignore
vendored
@ -1,3 +0,0 @@
|
|||||||
/build
|
|
||||||
/build-nmake
|
|
||||||
.DS_Store
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.13)
|
|
||||||
|
|
||||||
project(PulseLengthStatAnalyzer)
|
|
||||||
|
|
||||||
add_definitions(-DLOGIC2)
|
|
||||||
|
|
||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
|
||||||
|
|
||||||
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
|
|
||||||
|
|
||||||
include(ExternalAnalyzerSDK)
|
|
||||||
|
|
||||||
set(SOURCES
|
|
||||||
src/PulseLengthStatAnalyzer.cpp
|
|
||||||
src/PulseLengthStatAnalyzer.h
|
|
||||||
src/PulseLengthStatAnalyzerResults.cpp
|
|
||||||
src/PulseLengthStatAnalyzerResults.h
|
|
||||||
src/PulseLengthStatAnalyzerSettings.cpp
|
|
||||||
src/PulseLengthStatAnalyzerSettings.h
|
|
||||||
src/PulseLengthStatSimulationDataGenerator.cpp
|
|
||||||
src/PulseLengthStatSimulationDataGenerator.h
|
|
||||||
)
|
|
||||||
|
|
||||||
add_analyzer_plugin(${PROJECT_NAME} SOURCES ${SOURCES})
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 3,
|
|
||||||
"cmakeMinimumRequired": {
|
|
||||||
"major": 3,
|
|
||||||
"minor": 19,
|
|
||||||
"patch": 0
|
|
||||||
},
|
|
||||||
"configurePresets": [
|
|
||||||
{
|
|
||||||
"name": "win-vs2022-x64",
|
|
||||||
"displayName": "Visual Studio 2022 (x64)",
|
|
||||||
"generator": "Visual Studio 17 2022",
|
|
||||||
"architecture": "x64",
|
|
||||||
"binaryDir": "${sourceDir}/build"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "win-vs2019-x64",
|
|
||||||
"displayName": "Visual Studio 2019 (x64)",
|
|
||||||
"generator": "Visual Studio 16 2019",
|
|
||||||
"architecture": "x64",
|
|
||||||
"binaryDir": "${sourceDir}/build"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "win-nmake-release",
|
|
||||||
"displayName": "NMake Release (x64 Native Tools Command Prompt)",
|
|
||||||
"generator": "NMake Makefiles",
|
|
||||||
"binaryDir": "${sourceDir}/build-nmake",
|
|
||||||
"cacheVariables": {
|
|
||||||
"CMAKE_BUILD_TYPE": "Release"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"buildPresets": [
|
|
||||||
{
|
|
||||||
"name": "win-release",
|
|
||||||
"configurePreset": "win-vs2022-x64",
|
|
||||||
"configuration": "Release"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "win-release-vs2019",
|
|
||||||
"configurePreset": "win-vs2019-x64",
|
|
||||||
"configuration": "Release"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "nmake-release",
|
|
||||||
"configurePreset": "win-nmake-release"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
@echo off
|
|
||||||
setlocal EnableDelayedExpansion
|
|
||||||
cd /d "%~dp0"
|
|
||||||
|
|
||||||
if not exist build\CMakeCache.txt (
|
|
||||||
echo Run configure_msvc.bat first.
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
|
|
||||||
set "VSWHERE=%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe"
|
|
||||||
for /f "usebackq tokens=*" %%i in (`"%VSWHERE%" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do set "VSINSTALL=%%i"
|
|
||||||
if not defined VSINSTALL (
|
|
||||||
echo MSVC not found. Add C++ workload in Visual Studio Installer.
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
call "!VSINSTALL!\Common7\Tools\VsDevCmd.bat" -arch=x64 -host_arch=x64
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
|
|
||||||
cmake --build build
|
|
||||||
pause
|
|
||||||
exit /b %ERRORLEVEL%
|
|
||||||
@ -1,66 +0,0 @@
|
|||||||
include(FetchContent)
|
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 11)
|
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED YES)
|
|
||||||
|
|
||||||
if(NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY OR NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY)
|
|
||||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin/)
|
|
||||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin/)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(NOT TARGET Saleae::AnalyzerSDK)
|
|
||||||
FetchContent_Declare(
|
|
||||||
analyzersdk
|
|
||||||
GIT_REPOSITORY https://github.com/saleae/AnalyzerSDK.git
|
|
||||||
GIT_TAG master
|
|
||||||
GIT_SHALLOW True
|
|
||||||
GIT_PROGRESS True
|
|
||||||
)
|
|
||||||
|
|
||||||
FetchContent_GetProperties(analyzersdk)
|
|
||||||
|
|
||||||
if(NOT analyzersdk_POPULATED)
|
|
||||||
FetchContent_Populate(analyzersdk)
|
|
||||||
include(${analyzersdk_SOURCE_DIR}/AnalyzerSDKConfig.cmake)
|
|
||||||
|
|
||||||
if(APPLE OR WIN32)
|
|
||||||
get_target_property(analyzersdk_lib_location Saleae::AnalyzerSDK IMPORTED_LOCATION)
|
|
||||||
if(CMAKE_LIBRARY_OUTPUT_DIRECTORY)
|
|
||||||
file(COPY ${analyzersdk_lib_location} DESTINATION ${CMAKE_LIBRARY_OUTPUT_DIRECTORY})
|
|
||||||
else()
|
|
||||||
message(WARNING "Please define CMAKE_RUNTIME_OUTPUT_DIRECTORY and CMAKE_LIBRARY_OUTPUT_DIRECTORY if you want unit tests to locate ${analyzersdk_lib_location}")
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Shared folder for all Saleae LLA plugins in this repo: Analyzer/raw/dll
|
|
||||||
set(ANALYZER_DLL_OUT_DIR "${CMAKE_SOURCE_DIR}/../dll")
|
|
||||||
get_filename_component(ANALYZER_DLL_OUT_DIR "${ANALYZER_DLL_OUT_DIR}" ABSOLUTE)
|
|
||||||
file(MAKE_DIRECTORY "${ANALYZER_DLL_OUT_DIR}")
|
|
||||||
|
|
||||||
function(add_analyzer_plugin TARGET)
|
|
||||||
set(options)
|
|
||||||
set(single_value_args)
|
|
||||||
set(multi_value_args SOURCES)
|
|
||||||
cmake_parse_arguments(_p "${options}" "${single_value_args}" "${multi_value_args}" ${ARGN})
|
|
||||||
|
|
||||||
add_library(${TARGET} MODULE ${_p_SOURCES})
|
|
||||||
target_link_libraries(${TARGET} PRIVATE Saleae::AnalyzerSDK)
|
|
||||||
|
|
||||||
set(ANALYZER_DESTINATION "Analyzers")
|
|
||||||
install(TARGETS ${TARGET} RUNTIME DESTINATION ${ANALYZER_DESTINATION}
|
|
||||||
LIBRARY DESTINATION ${ANALYZER_DESTINATION})
|
|
||||||
|
|
||||||
set_target_properties(${TARGET} PROPERTIES
|
|
||||||
RUNTIME_OUTPUT_DIRECTORY "${ANALYZER_DLL_OUT_DIR}"
|
|
||||||
LIBRARY_OUTPUT_DIRECTORY "${ANALYZER_DLL_OUT_DIR}")
|
|
||||||
if(CMAKE_CONFIGURATION_TYPES)
|
|
||||||
foreach(CFG ${CMAKE_CONFIGURATION_TYPES})
|
|
||||||
string(TOUPPER ${CFG} CFG_UPPER)
|
|
||||||
set_target_properties(${TARGET} PROPERTIES
|
|
||||||
RUNTIME_OUTPUT_DIRECTORY_${CFG_UPPER} "${ANALYZER_DLL_OUT_DIR}"
|
|
||||||
LIBRARY_OUTPUT_DIRECTORY_${CFG_UPPER} "${ANALYZER_DLL_OUT_DIR}")
|
|
||||||
endforeach()
|
|
||||||
endif()
|
|
||||||
endfunction()
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
@echo off
|
|
||||||
setlocal EnableDelayedExpansion
|
|
||||||
cd /d "%~dp0"
|
|
||||||
|
|
||||||
echo === PulseLengthStatAnalyzer: configure with MSVC ===
|
|
||||||
echo.
|
|
||||||
|
|
||||||
set "VSWHERE=%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe"
|
|
||||||
if not exist "%VSWHERE%" (
|
|
||||||
echo [ERROR] vswhere not found. Install one of:
|
|
||||||
echo - Visual Studio 2022 with workload "Desktop development with C++"
|
|
||||||
echo - Build Tools for Visual Studio 2022: https://visualstudio.microsoft.com/visual-cpp-build-tools/
|
|
||||||
echo ^(select "Desktop development with C++" / MSVC, Windows SDK^)
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
|
|
||||||
for /f "usebackq tokens=*" %%i in (`"%VSWHERE%" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do set "VSINSTALL=%%i"
|
|
||||||
if not defined VSINSTALL (
|
|
||||||
echo [ERROR] MSVC toolset not found. Add "Desktop development with C++" in Visual Studio Installer.
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
|
|
||||||
echo Found: !VSINSTALL!
|
|
||||||
call "!VSINSTALL!\Common7\Tools\VsDevCmd.bat" -arch=x64 -host_arch=x64
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
|
|
||||||
if exist build rmdir /s /q build
|
|
||||||
if exist build-nmake rmdir /s /q build-nmake
|
|
||||||
mkdir build
|
|
||||||
cd build
|
|
||||||
|
|
||||||
cmake .. -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
|
|
||||||
echo.
|
|
||||||
echo Configure OK. Build: build_msvc.bat ^(or from same VS env: cd build ^& cmake --build .^)
|
|
||||||
echo Output DLL: ..\dll\ ^(all analyzers share this folder^)
|
|
||||||
pause
|
|
||||||
exit /b 0
|
|
||||||
@ -1,110 +0,0 @@
|
|||||||
#include "PulseLengthStatAnalyzer.h"
|
|
||||||
#include "PulseLengthStatAnalyzerSettings.h"
|
|
||||||
#include <AnalyzerChannelData.h>
|
|
||||||
|
|
||||||
// One frame per stable level between edges. mData1 = duration in samples; mFlags: 1 = HIGH, 0 = LOW.
|
|
||||||
|
|
||||||
PulseLengthStatAnalyzer::PulseLengthStatAnalyzer()
|
|
||||||
: Analyzer2(),
|
|
||||||
mSettings(),
|
|
||||||
mSimulationInitilized(false)
|
|
||||||
{
|
|
||||||
SetAnalyzerSettings(&mSettings);
|
|
||||||
}
|
|
||||||
|
|
||||||
PulseLengthStatAnalyzer::~PulseLengthStatAnalyzer()
|
|
||||||
{
|
|
||||||
KillThread();
|
|
||||||
}
|
|
||||||
|
|
||||||
void PulseLengthStatAnalyzer::SetupResults()
|
|
||||||
{
|
|
||||||
mResults.reset(new PulseLengthStatAnalyzerResults(this, &mSettings));
|
|
||||||
SetAnalyzerResults(mResults.get());
|
|
||||||
mResults->AddChannelBubblesWillAppearOn(mSettings.mInputChannel);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PulseLengthStatAnalyzer::WorkerThread()
|
|
||||||
{
|
|
||||||
mChannelData = GetAnalyzerChannelData(mSettings.mInputChannel);
|
|
||||||
|
|
||||||
U32 frames_since_commit = 0;
|
|
||||||
const U32 kCommitBatch = 256;
|
|
||||||
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
CheckIfThreadShouldExit();
|
|
||||||
|
|
||||||
const U64 segment_start = mChannelData->GetSampleNumber();
|
|
||||||
const BitState level = mChannelData->GetBitState();
|
|
||||||
|
|
||||||
mChannelData->AdvanceToNextEdge();
|
|
||||||
|
|
||||||
const U64 edge_sample = mChannelData->GetSampleNumber();
|
|
||||||
if (edge_sample == segment_start)
|
|
||||||
break;
|
|
||||||
|
|
||||||
const U64 duration_samples = edge_sample - segment_start;
|
|
||||||
const U64 end_inclusive = edge_sample > segment_start ? edge_sample - 1 : segment_start;
|
|
||||||
|
|
||||||
Frame frame;
|
|
||||||
frame.mData1 = duration_samples;
|
|
||||||
frame.mFlags = (level == BIT_HIGH) ? 1 : 0;
|
|
||||||
frame.mStartingSampleInclusive = static_cast<S64>(segment_start);
|
|
||||||
frame.mEndingSampleInclusive = static_cast<S64>(end_inclusive);
|
|
||||||
|
|
||||||
mResults->AddFrame(frame);
|
|
||||||
if (++frames_since_commit >= kCommitBatch)
|
|
||||||
{
|
|
||||||
mResults->CommitResults();
|
|
||||||
frames_since_commit = 0;
|
|
||||||
}
|
|
||||||
ReportProgress(edge_sample);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (frames_since_commit != 0)
|
|
||||||
mResults->CommitResults();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PulseLengthStatAnalyzer::NeedsRerun()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
U32 PulseLengthStatAnalyzer::GenerateSimulationData(U64 minimum_sample_index, U32 device_sample_rate,
|
|
||||||
SimulationChannelDescriptor** simulation_channels)
|
|
||||||
{
|
|
||||||
if (mSimulationInitilized == false)
|
|
||||||
{
|
|
||||||
mSimulationDataGenerator.Initialize(GetSimulationSampleRate(), &mSettings);
|
|
||||||
mSimulationInitilized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return mSimulationDataGenerator.GenerateSimulationData(minimum_sample_index, device_sample_rate,
|
|
||||||
simulation_channels);
|
|
||||||
}
|
|
||||||
|
|
||||||
U32 PulseLengthStatAnalyzer::GetMinimumSampleRateHz()
|
|
||||||
{
|
|
||||||
return 200000;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* PulseLengthStatAnalyzer::GetAnalyzerName() const
|
|
||||||
{
|
|
||||||
return "Pulse Length Stat";
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* GetAnalyzerName()
|
|
||||||
{
|
|
||||||
return "Pulse Length Stat";
|
|
||||||
}
|
|
||||||
|
|
||||||
Analyzer* CreateAnalyzer()
|
|
||||||
{
|
|
||||||
return new PulseLengthStatAnalyzer();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DestroyAnalyzer(Analyzer* analyzer)
|
|
||||||
{
|
|
||||||
delete analyzer;
|
|
||||||
}
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
#ifndef PULSELENGTHSTAT_ANALYZER_H
|
|
||||||
#define PULSELENGTHSTAT_ANALYZER_H
|
|
||||||
|
|
||||||
#include <Analyzer.h>
|
|
||||||
#include "PulseLengthStatAnalyzerSettings.h"
|
|
||||||
#include "PulseLengthStatAnalyzerResults.h"
|
|
||||||
#include "PulseLengthStatSimulationDataGenerator.h"
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
class ANALYZER_EXPORT PulseLengthStatAnalyzer : public Analyzer2
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
PulseLengthStatAnalyzer();
|
|
||||||
virtual ~PulseLengthStatAnalyzer();
|
|
||||||
|
|
||||||
virtual void SetupResults();
|
|
||||||
virtual void WorkerThread();
|
|
||||||
|
|
||||||
virtual U32 GenerateSimulationData(U64 newest_sample_requested, U32 sample_rate,
|
|
||||||
SimulationChannelDescriptor** simulation_channels);
|
|
||||||
virtual U32 GetMinimumSampleRateHz();
|
|
||||||
|
|
||||||
virtual const char* GetAnalyzerName() const;
|
|
||||||
virtual bool NeedsRerun();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
PulseLengthStatAnalyzerSettings mSettings;
|
|
||||||
std::unique_ptr<PulseLengthStatAnalyzerResults> mResults;
|
|
||||||
AnalyzerChannelData* mChannelData;
|
|
||||||
|
|
||||||
PulseLengthStatSimulationDataGenerator mSimulationDataGenerator;
|
|
||||||
bool mSimulationInitilized;
|
|
||||||
};
|
|
||||||
|
|
||||||
extern "C" ANALYZER_EXPORT const char* __cdecl GetAnalyzerName();
|
|
||||||
extern "C" ANALYZER_EXPORT Analyzer* __cdecl CreateAnalyzer();
|
|
||||||
extern "C" ANALYZER_EXPORT void __cdecl DestroyAnalyzer(Analyzer* analyzer);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -1,107 +0,0 @@
|
|||||||
#include "PulseLengthStatAnalyzerResults.h"
|
|
||||||
#include <AnalyzerHelpers.h>
|
|
||||||
#include "PulseLengthStatAnalyzer.h"
|
|
||||||
#include "PulseLengthStatAnalyzerSettings.h"
|
|
||||||
#include <cstdio>
|
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
PulseLengthStatAnalyzerResults::PulseLengthStatAnalyzerResults(PulseLengthStatAnalyzer* analyzer,
|
|
||||||
PulseLengthStatAnalyzerSettings* settings)
|
|
||||||
: AnalyzerResults(),
|
|
||||||
mSettings(settings),
|
|
||||||
mAnalyzer(analyzer)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
PulseLengthStatAnalyzerResults::~PulseLengthStatAnalyzerResults()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
static void FormatDurationUs(U64 duration_samples, U32 sample_rate_hz, char* out, size_t out_sz)
|
|
||||||
{
|
|
||||||
if (sample_rate_hz == 0)
|
|
||||||
{
|
|
||||||
snprintf(out, out_sz, "? us");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const double us = double(duration_samples) * 1e6 / double(sample_rate_hz);
|
|
||||||
snprintf(out, out_sz, "%.2f us", us);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PulseLengthStatAnalyzerResults::GenerateBubbleText(U64 frame_index, Channel& channel, DisplayBase display_base)
|
|
||||||
{
|
|
||||||
(void)display_base;
|
|
||||||
ClearResultStrings();
|
|
||||||
Frame frame = GetFrame(frame_index);
|
|
||||||
|
|
||||||
const U32 fs = mAnalyzer->GetSampleRate();
|
|
||||||
char dur[64];
|
|
||||||
FormatDurationUs(frame.mData1, fs, dur, sizeof dur);
|
|
||||||
|
|
||||||
const char* lev = (frame.mFlags != 0) ? "HIGH" : "LOW";
|
|
||||||
char line[160];
|
|
||||||
snprintf(line, sizeof line, "%s %s", lev, dur);
|
|
||||||
AddResultString(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PulseLengthStatAnalyzerResults::GenerateExportFile(const char* file, DisplayBase display_base, U32 export_type_user_id)
|
|
||||||
{
|
|
||||||
(void)export_type_user_id;
|
|
||||||
(void)display_base;
|
|
||||||
std::ofstream file_stream(file, std::ios::out);
|
|
||||||
|
|
||||||
const U64 trigger_sample = mAnalyzer->GetTriggerSample();
|
|
||||||
const U32 sample_rate = mAnalyzer->GetSampleRate();
|
|
||||||
|
|
||||||
file_stream << "Time [s],Level,Duration_samples,Duration_us" << std::endl;
|
|
||||||
|
|
||||||
const U64 num_frames = GetNumFrames();
|
|
||||||
for (U32 i = 0; i < num_frames; i++)
|
|
||||||
{
|
|
||||||
Frame frame = GetFrame(i);
|
|
||||||
|
|
||||||
char time_str[128];
|
|
||||||
AnalyzerHelpers::GetTimeString(frame.mStartingSampleInclusive, trigger_sample, sample_rate, time_str, 128);
|
|
||||||
|
|
||||||
char dur_us[64];
|
|
||||||
FormatDurationUs(frame.mData1, sample_rate, dur_us, sizeof dur_us);
|
|
||||||
|
|
||||||
file_stream << time_str << "," << ((frame.mFlags != 0) ? "HIGH" : "LOW") << "," << frame.mData1 << ","
|
|
||||||
<< dur_us << std::endl;
|
|
||||||
|
|
||||||
if (UpdateExportProgressAndCheckForCancel(i, num_frames) == true)
|
|
||||||
{
|
|
||||||
file_stream.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
file_stream.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void PulseLengthStatAnalyzerResults::GenerateFrameTabularText(U64 frame_index, DisplayBase display_base)
|
|
||||||
{
|
|
||||||
#ifdef SUPPORTS_PROTOCOL_SEARCH
|
|
||||||
(void)display_base;
|
|
||||||
Frame frame = GetFrame(frame_index);
|
|
||||||
ClearTabularText();
|
|
||||||
|
|
||||||
const U32 fs = mAnalyzer->GetSampleRate();
|
|
||||||
char dur[64];
|
|
||||||
FormatDurationUs(frame.mData1, fs, dur, sizeof dur);
|
|
||||||
AddTabularText((frame.mFlags != 0) ? "H" : "L");
|
|
||||||
AddTabularText(dur);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void PulseLengthStatAnalyzerResults::GeneratePacketTabularText(U64 packet_id, DisplayBase display_base)
|
|
||||||
{
|
|
||||||
(void)packet_id;
|
|
||||||
(void)display_base;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PulseLengthStatAnalyzerResults::GenerateTransactionTabularText(U64 transaction_id, DisplayBase display_base)
|
|
||||||
{
|
|
||||||
(void)transaction_id;
|
|
||||||
(void)display_base;
|
|
||||||
}
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
#ifndef PULSELENGTHSTAT_ANALYZER_RESULTS
|
|
||||||
#define PULSELENGTHSTAT_ANALYZER_RESULTS
|
|
||||||
|
|
||||||
#include <AnalyzerResults.h>
|
|
||||||
|
|
||||||
class PulseLengthStatAnalyzer;
|
|
||||||
class PulseLengthStatAnalyzerSettings;
|
|
||||||
|
|
||||||
class PulseLengthStatAnalyzerResults : public AnalyzerResults
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
PulseLengthStatAnalyzerResults(PulseLengthStatAnalyzer* analyzer, PulseLengthStatAnalyzerSettings* settings);
|
|
||||||
virtual ~PulseLengthStatAnalyzerResults();
|
|
||||||
|
|
||||||
virtual void GenerateBubbleText(U64 frame_index, Channel& channel, DisplayBase display_base);
|
|
||||||
virtual void GenerateExportFile(const char* file, DisplayBase display_base, U32 export_type_user_id);
|
|
||||||
|
|
||||||
virtual void GenerateFrameTabularText(U64 frame_index, DisplayBase display_base);
|
|
||||||
virtual void GeneratePacketTabularText(U64 packet_id, DisplayBase display_base);
|
|
||||||
virtual void GenerateTransactionTabularText(U64 transaction_id, DisplayBase display_base);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
PulseLengthStatAnalyzerSettings* mSettings;
|
|
||||||
PulseLengthStatAnalyzer* mAnalyzer;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -1,62 +0,0 @@
|
|||||||
#include "PulseLengthStatAnalyzerSettings.h"
|
|
||||||
#include <AnalyzerHelpers.h>
|
|
||||||
|
|
||||||
PulseLengthStatAnalyzerSettings::PulseLengthStatAnalyzerSettings()
|
|
||||||
: mInputChannel(UNDEFINED_CHANNEL),
|
|
||||||
mInputChannelInterface()
|
|
||||||
{
|
|
||||||
mInputChannelInterface.SetTitleAndTooltip(
|
|
||||||
"Input",
|
|
||||||
"Digital channel: one frame per stable level between edges (duration in samples / us).");
|
|
||||||
mInputChannelInterface.SetChannel(mInputChannel);
|
|
||||||
|
|
||||||
AddInterface(&mInputChannelInterface);
|
|
||||||
|
|
||||||
AddExportOption(0, "Export as text/csv file");
|
|
||||||
AddExportExtension(0, "text", "txt");
|
|
||||||
AddExportExtension(0, "csv", "csv");
|
|
||||||
|
|
||||||
ClearChannels();
|
|
||||||
AddChannel(mInputChannel, "Input", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
PulseLengthStatAnalyzerSettings::~PulseLengthStatAnalyzerSettings()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PulseLengthStatAnalyzerSettings::SetSettingsFromInterfaces()
|
|
||||||
{
|
|
||||||
mInputChannel = mInputChannelInterface.GetChannel();
|
|
||||||
|
|
||||||
ClearChannels();
|
|
||||||
AddChannel(mInputChannel, "Pulse Length Stat", true);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PulseLengthStatAnalyzerSettings::UpdateInterfacesFromSettings()
|
|
||||||
{
|
|
||||||
mInputChannelInterface.SetChannel(mInputChannel);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PulseLengthStatAnalyzerSettings::LoadSettings(const char* settings)
|
|
||||||
{
|
|
||||||
SimpleArchive text_archive;
|
|
||||||
text_archive.SetString(settings);
|
|
||||||
|
|
||||||
text_archive >> mInputChannel;
|
|
||||||
|
|
||||||
ClearChannels();
|
|
||||||
AddChannel(mInputChannel, "Pulse Length Stat", true);
|
|
||||||
|
|
||||||
UpdateInterfacesFromSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* PulseLengthStatAnalyzerSettings::SaveSettings()
|
|
||||||
{
|
|
||||||
SimpleArchive text_archive;
|
|
||||||
|
|
||||||
text_archive << mInputChannel;
|
|
||||||
|
|
||||||
return SetReturnString(text_archive.GetString());
|
|
||||||
}
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
#ifndef PULSELENGTHSTAT_ANALYZER_SETTINGS
|
|
||||||
#define PULSELENGTHSTAT_ANALYZER_SETTINGS
|
|
||||||
|
|
||||||
#include <AnalyzerSettings.h>
|
|
||||||
#include <AnalyzerTypes.h>
|
|
||||||
|
|
||||||
class PulseLengthStatAnalyzerSettings : public AnalyzerSettings
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
PulseLengthStatAnalyzerSettings();
|
|
||||||
virtual ~PulseLengthStatAnalyzerSettings();
|
|
||||||
|
|
||||||
virtual bool SetSettingsFromInterfaces();
|
|
||||||
void UpdateInterfacesFromSettings();
|
|
||||||
virtual void LoadSettings(const char* settings);
|
|
||||||
virtual const char* SaveSettings();
|
|
||||||
|
|
||||||
Channel mInputChannel;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
AnalyzerSettingInterfaceChannel mInputChannelInterface;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
#include "PulseLengthStatSimulationDataGenerator.h"
|
|
||||||
#include "PulseLengthStatAnalyzerSettings.h"
|
|
||||||
#include <AnalyzerHelpers.h>
|
|
||||||
|
|
||||||
PulseLengthStatSimulationDataGenerator::PulseLengthStatSimulationDataGenerator()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
PulseLengthStatSimulationDataGenerator::~PulseLengthStatSimulationDataGenerator()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void PulseLengthStatSimulationDataGenerator::Initialize(U32 simulation_sample_rate, PulseLengthStatAnalyzerSettings* settings)
|
|
||||||
{
|
|
||||||
mSimulationSampleRateHz = simulation_sample_rate;
|
|
||||||
mSettings = settings;
|
|
||||||
|
|
||||||
mSimChannel.SetChannel(mSettings->mInputChannel);
|
|
||||||
mSimChannel.SetSampleRate(simulation_sample_rate);
|
|
||||||
mSimChannel.SetInitialBitState(BIT_HIGH);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PulseLengthStatSimulationDataGenerator::EmitIdle(U32 samples)
|
|
||||||
{
|
|
||||||
mSimChannel.Advance(samples);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PulseLengthStatSimulationDataGenerator::EmitLow(U32 samples)
|
|
||||||
{
|
|
||||||
mSimChannel.TransitionIfNeeded(BIT_LOW);
|
|
||||||
mSimChannel.Advance(samples);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PulseLengthStatSimulationDataGenerator::EmitHigh(U32 samples)
|
|
||||||
{
|
|
||||||
mSimChannel.TransitionIfNeeded(BIT_HIGH);
|
|
||||||
mSimChannel.Advance(samples);
|
|
||||||
}
|
|
||||||
|
|
||||||
U32 PulseLengthStatSimulationDataGenerator::GenerateSimulationData(U64 largest_sample_requested, U32 sample_rate,
|
|
||||||
SimulationChannelDescriptor** simulation_channel)
|
|
||||||
{
|
|
||||||
const U64 adjusted_largest_sample_requested =
|
|
||||||
AnalyzerHelpers::AdjustSimulationTargetSample(largest_sample_requested, sample_rate, mSimulationSampleRateHz);
|
|
||||||
|
|
||||||
while (mSimChannel.GetCurrentSampleNumber() < adjusted_largest_sample_requested)
|
|
||||||
{
|
|
||||||
const U32 us_to_samples = mSimulationSampleRateHz / 1000000;
|
|
||||||
if (us_to_samples == 0)
|
|
||||||
break;
|
|
||||||
|
|
||||||
EmitIdle(500 * us_to_samples);
|
|
||||||
EmitLow(4500 * us_to_samples);
|
|
||||||
EmitHigh(4500 * us_to_samples);
|
|
||||||
EmitLow(4500 * us_to_samples);
|
|
||||||
EmitHigh(4500 * us_to_samples);
|
|
||||||
EmitLow(560 * us_to_samples);
|
|
||||||
EmitHigh(560 * us_to_samples);
|
|
||||||
EmitLow(560 * us_to_samples);
|
|
||||||
EmitHigh(20000 * us_to_samples);
|
|
||||||
}
|
|
||||||
|
|
||||||
*simulation_channel = &mSimChannel;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
#ifndef PULSELENGTHSTAT_SIMULATION_DATA_GENERATOR
|
|
||||||
#define PULSELENGTHSTAT_SIMULATION_DATA_GENERATOR
|
|
||||||
|
|
||||||
#include <SimulationChannelDescriptor.h>
|
|
||||||
|
|
||||||
class PulseLengthStatAnalyzerSettings;
|
|
||||||
|
|
||||||
class PulseLengthStatSimulationDataGenerator
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
PulseLengthStatSimulationDataGenerator();
|
|
||||||
~PulseLengthStatSimulationDataGenerator();
|
|
||||||
|
|
||||||
void Initialize(U32 simulation_sample_rate, PulseLengthStatAnalyzerSettings* settings);
|
|
||||||
U32 GenerateSimulationData(U64 newest_sample_requested, U32 sample_rate, SimulationChannelDescriptor** simulation_channel);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
PulseLengthStatAnalyzerSettings* mSettings;
|
|
||||||
U32 mSimulationSampleRateHz;
|
|
||||||
SimulationChannelDescriptor mSimChannel;
|
|
||||||
|
|
||||||
void EmitIdle(U32 samples);
|
|
||||||
void EmitLow(U32 samples);
|
|
||||||
void EmitHigh(U32 samples);
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
379
IR-protocol.ino
379
IR-protocol.ino
@ -4,94 +4,61 @@
|
|||||||
#include "MemoryCheck.h"
|
#include "MemoryCheck.h"
|
||||||
/////////////// Pinout ///////////////
|
/////////////// Pinout ///////////////
|
||||||
|
|
||||||
#define dec0_PIN PIN_KT1_IN
|
#define encForward_PIN PA0
|
||||||
#define dec1_PIN PIN_KT2_IN
|
#define encBackward_PIN PA1
|
||||||
#define dec2_PIN PIN_KT3_IN
|
|
||||||
#define dec3_PIN PIN_KT4_IN
|
#define dec0_PIN PB0
|
||||||
#define dec4_PIN PIN_KT5_IN
|
#define dec1_PIN PB1
|
||||||
#define dec5_PIN PIN_KT6_IN
|
#define dec2_PIN PB2
|
||||||
#define dec6_PIN PIN_KT7_IN
|
#define dec3_PIN PB3
|
||||||
#define dec7_PIN PIN_KT8_IN
|
#define dec4_PIN PB4
|
||||||
// #define dec8_PIN PB8
|
#define dec5_PIN PB5
|
||||||
// #define dec9_PIN PB9
|
#define dec6_PIN PB6
|
||||||
// #define dec10_PIN PB10
|
#define dec7_PIN PB7
|
||||||
// #define dec11_PIN PB11
|
#define dec8_PIN PB8
|
||||||
// #define dec12_PIN PB12
|
#define dec9_PIN PB9
|
||||||
// #define dec13_PIN PB13
|
#define dec10_PIN PB10
|
||||||
// #define dec14_PIN PB14
|
#define dec11_PIN PB11
|
||||||
// #define dec15_PIN PB15
|
#define dec12_PIN PB12
|
||||||
|
#define dec13_PIN PB13
|
||||||
|
#define dec14_PIN PB14
|
||||||
|
#define dec15_PIN PB15
|
||||||
|
|
||||||
#define LoopOut PC13
|
#define LoopOut PC13
|
||||||
|
|
||||||
|
|
||||||
//////////////// Ini /////////////////
|
//////////////// Ini /////////////////
|
||||||
|
|
||||||
#define INFO "IR_FOX TEST"
|
#define INFO "IR_FOX TEST"
|
||||||
#define SERIAL_SPEED 115200
|
#define SERIAL_SPEED 115200
|
||||||
|
|
||||||
//////////////// Var /////////////////
|
//////////////// Var /////////////////
|
||||||
// IR_Encoder encForward(PA5, 42 /* , &decBackward */);
|
|
||||||
|
|
||||||
IR_Encoder enc0(PIN_KT8_OUT, 42 /* , &decBackward */);
|
IR_Decoder dec0(dec1_PIN, 0);
|
||||||
// IR_Encoder enc1(PA1, 127 /* , &decBackward */);
|
IR_Decoder dec1(dec2_PIN, 1);
|
||||||
// IR_Encoder enc2(PA2, 137 /* , &decBackward */);
|
|
||||||
// IR_Encoder enc3(PA3, 777 /* , &decBackward */);
|
|
||||||
// IR_Encoder enc10(PA4, 555 /* , &decBackward */);
|
|
||||||
// IR_Encoder enc11(PC14, 127 /* , &decBackward */);
|
|
||||||
// IR_Encoder enc12(PC13, 137 /* , &decBackward */);
|
|
||||||
// IR_Encoder enc13(PA12, 777 /* , &decBackward */);
|
|
||||||
|
|
||||||
|
IR_Encoder encForward(42 /* , &decBackward */);
|
||||||
|
// IR_Encoder encBackward(321, encBackward_PIN);
|
||||||
// IR_Encoder encTree(325, A2);
|
// IR_Encoder encTree(325, A2);
|
||||||
|
|
||||||
//////////////////////// Функции прерываний ////////////////////////
|
//////////////////////// Функции прерываний ////////////////////////
|
||||||
|
|
||||||
void EncoderISR()
|
void EncoderISR()
|
||||||
{
|
{
|
||||||
IR_Encoder::isr();
|
encForward.isr();
|
||||||
|
// encBackward.isr();
|
||||||
|
// encTree.isr();
|
||||||
|
|
||||||
|
digitalWrite(PB5, encForward.ir_out_virtual);
|
||||||
}
|
}
|
||||||
|
|
||||||
//-------------------------------------------------------------------
|
//------------------------------------------------------------------
|
||||||
|
#define dec_ISR(n) \
|
||||||
|
void dec_##n##_ISR() { dec##n.isr(); }
|
||||||
|
|
||||||
IR_Decoder dec0(dec0_PIN, 0);
|
dec_ISR(0);
|
||||||
void dec_0_ISR() { dec0.isr(); }
|
dec_ISR(1);
|
||||||
|
|
||||||
IR_Decoder dec1(dec1_PIN, 1);
|
|
||||||
void dec_1_ISR() { dec1.isr(); }
|
|
||||||
|
|
||||||
IR_Decoder dec2(dec2_PIN, 2);
|
|
||||||
void dec_2_ISR() { dec2.isr(); }
|
|
||||||
|
|
||||||
IR_Decoder dec3(dec3_PIN, 3);
|
|
||||||
void dec_3_ISR() { dec3.isr(); }
|
|
||||||
|
|
||||||
IR_Decoder dec4(dec4_PIN, 4);
|
|
||||||
void dec_4_ISR() { dec4.isr(); }
|
|
||||||
|
|
||||||
IR_Decoder dec5(dec5_PIN, 5);
|
|
||||||
void dec_5_ISR() { dec5.isr(); }
|
|
||||||
|
|
||||||
IR_Decoder dec6(dec6_PIN, 6);
|
|
||||||
void dec_6_ISR() { dec6.isr(); }
|
|
||||||
|
|
||||||
IR_Decoder dec7(dec7_PIN, 7);
|
|
||||||
void dec_7_ISR() { dec7.isr(); }
|
|
||||||
|
|
||||||
// IR_Decoder dec8(dec8_PIN, 8);
|
|
||||||
// void dec_8_ISR() { dec8.isr(); }
|
|
||||||
|
|
||||||
// IR_Decoder dec9(dec9_PIN, 9);
|
|
||||||
// void dec_9_ISR() { dec9.isr(); }
|
|
||||||
|
|
||||||
// IR_Decoder dec10(dec10_PIN, 10);
|
|
||||||
// void dec_10_ISR() { dec10.isr(); }
|
|
||||||
|
|
||||||
// IR_Decoder dec11(dec11_PIN, 11);
|
|
||||||
// void dec_11_ISR() { dec11.isr(); }
|
|
||||||
|
|
||||||
// IR_Decoder dec12(dec12_PIN, 12);
|
|
||||||
// void dec_12_ISR() { dec12.isr(); }
|
|
||||||
|
|
||||||
// IR_Decoder dec13(dec13_PIN, 13);
|
|
||||||
// void dec_13_ISR() { dec13.isr(); }
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////
|
||||||
uint8_t data0[] = {};
|
uint8_t data0[] = {};
|
||||||
@ -103,172 +70,146 @@ uint8_t data4[] = {42, 127, 137, 255};
|
|||||||
uint32_t loopTimer;
|
uint32_t loopTimer;
|
||||||
uint8_t sig = 0;
|
uint8_t sig = 0;
|
||||||
uint16_t targetAddr = IR_Broadcast;
|
uint16_t targetAddr = IR_Broadcast;
|
||||||
|
|
||||||
Timer t1(500, millis, []()
|
Timer t1(500, millis, []()
|
||||||
{
|
{
|
||||||
// Serial.println( digitalPinToBitMask(enc0.getPin()), BIN);
|
|
||||||
// enc0.sendData(IR_Broadcast, data4, sizeof(data4));
|
|
||||||
// enc1.sendData(IR_Broadcast, data3, sizeof(data3));
|
|
||||||
// enc2.sendData(IR_Broadcast, data2, sizeof(data2));
|
|
||||||
// enc3.sendData(IR_Broadcast, data1, sizeof(data1));
|
|
||||||
// enc10.sendData(IR_Broadcast, data4, sizeof(data4));
|
|
||||||
// enc11.sendData(IR_Broadcast, data3, sizeof(data3));
|
|
||||||
// enc12.sendData(IR_Broadcast, data2, sizeof(data2));
|
|
||||||
// enc13.sendData(IR_Broadcast, data1, sizeof(data1));
|
|
||||||
|
|
||||||
// Serial.println(sig);
|
// Serial.println(sig);
|
||||||
|
|
||||||
// switch (sig)
|
switch (sig)
|
||||||
// {
|
{
|
||||||
// case 0:
|
case 0:
|
||||||
// encForward.sendData(targetAddr);
|
encForward.sendData(targetAddr);
|
||||||
// break;
|
break;
|
||||||
// case 1:
|
case 1:
|
||||||
// encForward.sendData(targetAddr, data1, sizeof(data1));
|
encForward.sendData(targetAddr, data1, sizeof(data1));
|
||||||
// break;
|
break;
|
||||||
// case 2:
|
case 2:
|
||||||
// encForward.sendData(targetAddr, data2, sizeof(data2));
|
encForward.sendData(targetAddr, data2, sizeof(data2));
|
||||||
// break;
|
break;
|
||||||
// case 3:
|
case 3:
|
||||||
// encForward.sendData(targetAddr, data3, sizeof(data3));
|
encForward.sendData(targetAddr, data3, sizeof(data3));
|
||||||
// break;
|
break;
|
||||||
// case 4:
|
case 4:
|
||||||
// encForward.sendData(targetAddr, data4, sizeof(data4));
|
encForward.sendData(targetAddr, data4, sizeof(data4));
|
||||||
// break;
|
break;
|
||||||
|
|
||||||
// case 10:
|
case 10:
|
||||||
// encForward.sendData(targetAddr, data0, sizeof(data0), true);
|
encForward.sendData(targetAddr, data0, sizeof(data0), true);
|
||||||
// break;
|
break;
|
||||||
// case 11:
|
case 11:
|
||||||
// encForward.sendData(targetAddr, data1, sizeof(data1), true);
|
encForward.sendData(targetAddr, data1, sizeof(data1), true);
|
||||||
// break;
|
break;
|
||||||
// case 12:
|
case 12:
|
||||||
// encForward.sendData(targetAddr, data2, sizeof(data2), true);
|
encForward.sendData(targetAddr, data2, sizeof(data2), true);
|
||||||
// break;
|
break;
|
||||||
// case 13:
|
case 13:
|
||||||
// encForward.sendData(targetAddr, data3, sizeof(data3), true);
|
encForward.sendData(targetAddr, data3, sizeof(data3), true);
|
||||||
// break;
|
break;
|
||||||
// case 14:
|
case 14:
|
||||||
// encForward.sendData(targetAddr, data4, sizeof(data4), true);
|
encForward.sendData(targetAddr, data4, sizeof(data4), true);
|
||||||
// break;
|
break;
|
||||||
|
|
||||||
// case 20:
|
case 20:
|
||||||
// encForward.sendBack();
|
encForward.sendBack();
|
||||||
// break;
|
break;
|
||||||
// case 21:
|
case 21:
|
||||||
// encForward.sendBack(data1, sizeof(data1));
|
encForward.sendBack(data1, sizeof(data1));
|
||||||
// break;
|
break;
|
||||||
// case 22:
|
case 22:
|
||||||
// encForward.sendBack(data2, sizeof(data2));
|
encForward.sendBack(data2, sizeof(data2));
|
||||||
// break;
|
break;
|
||||||
// case 23:
|
case 23:
|
||||||
// encForward.sendBack(data3, sizeof(data3));
|
encForward.sendBack(data3, sizeof(data3));
|
||||||
// break;
|
break;
|
||||||
// case 24:
|
case 24:
|
||||||
// encForward.sendBack(data4, sizeof(data4));
|
encForward.sendBack(data4, sizeof(data4));
|
||||||
// break;
|
break;
|
||||||
|
|
||||||
// case 30:
|
case 30:
|
||||||
// encForward.sendBackTo(targetAddr);
|
encForward.sendBackTo(targetAddr);
|
||||||
// break;
|
break;
|
||||||
// case 31:
|
case 31:
|
||||||
// encForward.sendBackTo(targetAddr, data1, sizeof(data1));
|
encForward.sendBackTo(targetAddr, data1, sizeof(data1));
|
||||||
// break;
|
break;
|
||||||
// case 32:
|
case 32:
|
||||||
// encForward.sendBackTo(targetAddr, data2, sizeof(data2));
|
encForward.sendBackTo(targetAddr, data2, sizeof(data2));
|
||||||
// break;
|
break;
|
||||||
// case 33:
|
case 33:
|
||||||
// encForward.sendBackTo(targetAddr, data3, sizeof(data3));
|
encForward.sendBackTo(targetAddr, data3, sizeof(data3));
|
||||||
// break;
|
break;
|
||||||
// case 34:
|
case 34:
|
||||||
// encForward.sendBackTo(targetAddr, data4, sizeof(data4));
|
encForward.sendBackTo(targetAddr, data4, sizeof(data4));
|
||||||
// break;
|
break;
|
||||||
|
|
||||||
// case 41:
|
case 41:
|
||||||
// encForward.sendRequest(targetAddr);
|
encForward.sendRequest(targetAddr);
|
||||||
// break;
|
break;
|
||||||
// case 42:
|
case 42:
|
||||||
// encForward.sendAccept(targetAddr);
|
encForward.sendAccept(targetAddr);
|
||||||
// break;
|
break;
|
||||||
|
|
||||||
// default:
|
default:
|
||||||
// break;
|
break;
|
||||||
// }
|
}
|
||||||
// encBackward.sendData(IR_Broadcast, data2);
|
// encBackward.sendData(IR_Broadcast, data2);
|
||||||
// encTree.sendData(IR_Broadcast, rawData3);
|
// encTree.sendData(IR_Broadcast, rawData3);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Timer t2(50, millis, []()
|
// Timer t2(50, millis, []()
|
||||||
// { digitalToggle(LED_BUILTIN); });
|
// { digitalToggle(LED_BUILTIN); });
|
||||||
|
|
||||||
Timer signalDetectTimer;
|
Timer signalDetectTimer;
|
||||||
/////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////
|
||||||
HardwareTimer IR_Timer(TIM3);
|
HardwareTimer IR_Timer(TIM3);
|
||||||
|
HardwareTimer MicrosTimer(TIM1);
|
||||||
|
|
||||||
|
void MicrosTimerISR(){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void setup()
|
void setup()
|
||||||
{
|
{
|
||||||
IR_Timer.setOverflow(carrierFrec * 2, HERTZ_FORMAT);
|
|
||||||
IR_Timer.attachInterrupt(1, EncoderISR);
|
|
||||||
NVIC_SetPriority(IRQn_Type::TIM3_IRQn, 0);
|
|
||||||
IR_Timer.resume();
|
|
||||||
|
|
||||||
Serial.begin(SERIAL_SPEED);
|
Serial.begin(SERIAL_SPEED);
|
||||||
Serial.println(F(INFO));
|
Serial.println(F(INFO));
|
||||||
|
|
||||||
|
IR_Timer.setOverflow(carrierFrec*2, HERTZ_FORMAT);
|
||||||
|
IR_Timer.attachInterrupt(1, EncoderISR);
|
||||||
|
|
||||||
pinMode(LoopOut, OUTPUT);
|
pinMode(LoopOut, OUTPUT);
|
||||||
|
|
||||||
pinMode(dec0_PIN, INPUT_PULLUP);
|
// IR_DecoderRaw* blindFromForward [] { &decForward, &decBackward };
|
||||||
pinMode(dec1_PIN, INPUT_PULLUP);
|
// encForward.setBlindDecoders(blindFromForward, sizeof(blindFromForward) / sizeof(IR_DecoderRaw*));
|
||||||
pinMode(dec2_PIN, INPUT_PULLUP);
|
|
||||||
pinMode(dec3_PIN, INPUT_PULLUP);
|
|
||||||
pinMode(dec4_PIN, INPUT_PULLUP);
|
pinMode(encForward_PIN, OUTPUT);
|
||||||
pinMode(dec5_PIN, INPUT_PULLUP);
|
pinMode(encBackward_PIN, OUTPUT);
|
||||||
pinMode(dec6_PIN, INPUT_PULLUP);
|
pinMode(LED_BUILTIN, OUTPUT);
|
||||||
pinMode(dec7_PIN, INPUT_PULLUP);
|
|
||||||
// pinMode(dec8_PIN, INPUT_PULLUP);
|
#define decPinMode(n) pinMode(dec##n##_PIN, INPUT_PULLUP);
|
||||||
// pinMode(dec9_PIN, INPUT_PULLUP);
|
#define decAttach(n) attachInterrupt(dec##n##_PIN, dec_##n##_ISR, CHANGE);
|
||||||
// pinMode(dec10_PIN, INPUT_PULLUP);
|
#define decSetup(n) /* decPinMode(n); */ decAttach(n);
|
||||||
// pinMode(dec11_PIN, INPUT_PULLUP);
|
#define decTick(n) dec##n.tick();
|
||||||
// pinMode(dec12_PIN, INPUT_PULLUP);
|
#define decStat(n) rx_flag |= statusSimple(dec##n);
|
||||||
// pinMode(dec13_PIN, INPUT_PULLUP);
|
|
||||||
|
decSetup(0);
|
||||||
|
|
||||||
attachInterrupt(dec0_PIN, dec_0_ISR, CHANGE);
|
|
||||||
attachInterrupt(dec1_PIN, dec_1_ISR, CHANGE);
|
|
||||||
attachInterrupt(dec2_PIN, dec_2_ISR, CHANGE);
|
|
||||||
attachInterrupt(dec3_PIN, dec_3_ISR, CHANGE);
|
|
||||||
attachInterrupt(dec4_PIN, dec_4_ISR, CHANGE);
|
|
||||||
attachInterrupt(dec5_PIN, dec_5_ISR, CHANGE);
|
|
||||||
attachInterrupt(dec6_PIN, dec_6_ISR, CHANGE);
|
|
||||||
attachInterrupt(dec7_PIN, dec_7_ISR, CHANGE);
|
|
||||||
// attachInterrupt(dec8_PIN, dec_8_ISR, CHANGE);
|
|
||||||
// attachInterrupt(dec9_PIN, dec_9_ISR, CHANGE);
|
|
||||||
// attachInterrupt(dec10_PIN, dec_10_ISR, CHANGE);
|
|
||||||
// attachInterrupt(dec11_PIN, dec_11_ISR, CHANGE);
|
|
||||||
// attachInterrupt(dec12_PIN, dec_12_ISR, CHANGE);
|
|
||||||
// attachInterrupt(dec13_PIN, dec_13_ISR, CHANGE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop()
|
void loop()
|
||||||
{
|
{
|
||||||
digitalToggle(LoopOut);
|
digitalToggle(LoopOut);
|
||||||
Timer::tick();
|
Timer::tick();
|
||||||
IR_Decoder::tick();
|
|
||||||
|
|
||||||
bool rx_flag = false;
|
|
||||||
rx_flag |= status(dec0);
|
|
||||||
rx_flag |= status(dec1);
|
decTick(0);
|
||||||
rx_flag |= status(dec2);
|
decTick(1);
|
||||||
rx_flag |= status(dec3);
|
|
||||||
rx_flag |= status(dec4);
|
bool rx_flag;
|
||||||
rx_flag |= status(dec5);
|
decStat(0);
|
||||||
rx_flag |= status(dec6);
|
decStat(1);
|
||||||
rx_flag |= status(dec7);
|
|
||||||
// rx_flag |= status(dec8);
|
if(rx_flag){
|
||||||
// rx_flag |= status(dec9);
|
Serial.print("\n\n\n\n");
|
||||||
// rx_flag |= status(dec10);
|
}
|
||||||
// rx_flag |= status(dec11);
|
|
||||||
// rx_flag |= status(dec12);
|
|
||||||
// rx_flag |= status(dec13);
|
|
||||||
|
|
||||||
if (Serial.available())
|
if (Serial.available())
|
||||||
{
|
{
|
||||||
@ -284,6 +225,7 @@ void loop()
|
|||||||
case 102:
|
case 102:
|
||||||
targetAddr = 777;
|
targetAddr = 777;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
sig = in;
|
sig = in;
|
||||||
break;
|
break;
|
||||||
@ -291,24 +233,6 @@ void loop()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Timer statusSimpleDelay;
|
|
||||||
bool statusSimple(IR_Decoder &dec)
|
|
||||||
{
|
|
||||||
bool ret;
|
|
||||||
if (ret = dec.gotData.available())
|
|
||||||
{
|
|
||||||
Serial.print("DEC: ");
|
|
||||||
Serial.print(dec.getId());
|
|
||||||
Serial.print(" err: ");
|
|
||||||
Serial.print(dec.gotData.getErrorCount());
|
|
||||||
Serial.print("\n");
|
|
||||||
statusSimpleDelay.delay(100, millis, []()
|
|
||||||
{ Serial.print("\n\n\n\n"); });
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
void detectSignal()
|
void detectSignal()
|
||||||
{
|
{
|
||||||
// digitalWrite(SignalDetectLed, HIGH);
|
// digitalWrite(SignalDetectLed, HIGH);
|
||||||
@ -317,17 +241,25 @@ void detectSignal()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// test
|
// test
|
||||||
bool status(IR_Decoder &dec)
|
|
||||||
|
bool statusSimple(IR_Decoder &dec){
|
||||||
|
if (dec.gotData.available())
|
||||||
|
{
|
||||||
|
Serial.print("DEC "); Serial.println(dec.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void status(IR_Decoder &dec)
|
||||||
{
|
{
|
||||||
if (dec.gotData.available())
|
if (dec.gotData.available())
|
||||||
{
|
{
|
||||||
detectSignal();
|
detectSignal();
|
||||||
// Serial.println(micros());
|
Serial.println(micros());
|
||||||
String str;
|
String str;
|
||||||
if (/* dec.gotData.getDataPrt()[1] */ 1)
|
if (/* dec.gotData.getDataPrt()[1] */ 1)
|
||||||
{
|
{
|
||||||
str += ("Data on pin ");
|
str += ("Data on pin ");
|
||||||
str += (dec.getPin());
|
str += (dec.isrPin);
|
||||||
str += "\n";
|
str += "\n";
|
||||||
|
|
||||||
uint8_t msg = dec.gotData.getMsgRAW();
|
uint8_t msg = dec.gotData.getMsgRAW();
|
||||||
@ -401,7 +333,7 @@ bool status(IR_Decoder &dec)
|
|||||||
if (/* dec.gotData.getDataPrt()[1] */ 1)
|
if (/* dec.gotData.getDataPrt()[1] */ 1)
|
||||||
{
|
{
|
||||||
str += ("BackData on pin ");
|
str += ("BackData on pin ");
|
||||||
str += (dec.getPin());
|
str += (dec.isrPin);
|
||||||
str += "\n";
|
str += "\n";
|
||||||
|
|
||||||
uint8_t msg = dec.gotBackData.getMsgRAW();
|
uint8_t msg = dec.gotBackData.getMsgRAW();
|
||||||
@ -473,7 +405,7 @@ bool status(IR_Decoder &dec)
|
|||||||
if (/* dec.gotData.getDataPrt()[1] */ 1)
|
if (/* dec.gotData.getDataPrt()[1] */ 1)
|
||||||
{
|
{
|
||||||
str += ("Accept on pin ");
|
str += ("Accept on pin ");
|
||||||
str += (dec.getPin());
|
str += (dec.isrPin);
|
||||||
str += "\n";
|
str += "\n";
|
||||||
|
|
||||||
uint8_t msg = dec.gotAccept.getMsgRAW();
|
uint8_t msg = dec.gotAccept.getMsgRAW();
|
||||||
@ -525,7 +457,7 @@ bool status(IR_Decoder &dec)
|
|||||||
if (/* dec.gotData.getDataPrt()[1] */ 1)
|
if (/* dec.gotData.getDataPrt()[1] */ 1)
|
||||||
{
|
{
|
||||||
str += ("Request on pin ");
|
str += ("Request on pin ");
|
||||||
str += (dec.getPin());
|
str += (dec.isrPin);
|
||||||
str += "\n";
|
str += "\n";
|
||||||
|
|
||||||
uint8_t msg = dec.gotRequest.getMsgRAW();
|
uint8_t msg = dec.gotRequest.getMsgRAW();
|
||||||
@ -568,5 +500,4 @@ bool status(IR_Decoder &dec)
|
|||||||
// obj->resetAvailable();
|
// obj->resetAvailable();
|
||||||
Serial.write(str.c_str());
|
Serial.write(str.c_str());
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,141 +0,0 @@
|
|||||||
# IR DMA vs ISR: анализ согласованности сигнала и ответа версии
|
|
||||||
|
|
||||||
Связка с остальным пультом (модули, настройки): **[`ARCHITECTURE.md`](ARCHITECTURE.md)**.
|
|
||||||
|
|
||||||
Документ фиксирует наблюдения по переходу машинки (проект Car) на **DMA-передачу** ИК через `IR_Encoder::setExternalTxBackend` и `IrDmaBackend`, сравнение со **старым путём** (таймер + **`_isr()`**), ручную проверку CRC по логу пульта и роль **`buildGateRuns`** в библиотеке **IR-protocol**.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Контекст
|
|
||||||
|
|
||||||
- До введения DMA передача шла через **`IR_Encoder::begin(..., IR_Encoder::isr)`**: на каждый тик таймера (`carrierFrec * 2`) вызывается **`_isr()`**, формируются преамбула, данные, синхробиты.
|
|
||||||
- После коммита с **IR_DMA** (`Car`, `IR.cpp`): **`beginClockOnly`**, **`setExternalTxBackend`**, фактическая модуляция — **`IrDmaBackend::start`** → **`IR_Encoder::buildGateRuns`** + DMA в **BSRR**.
|
|
||||||
- Ответ версии — один из самых **длинных** кадров (до **31 байта** полного кадра по заголовку). Короткие пакеты (эхо `Version_Query`, 8 байт) в логе остаются **FrameOK**; длинный ответ версии даёт **CRC fail** / `Frame reject`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Два пути: ISR и DMA
|
|
||||||
|
|
||||||
| Этап | Старый ISR | DMA |
|
|
||||||
|------|------------|-----|
|
|
||||||
| Байты пакета + CRC | `sendDataFULL` → `sendBuffer` | То же; в `buildGateRuns` — `memcpy` в локальный буфер размером `dataByteSizeMax` |
|
|
||||||
| Развёртка в импульсы | **`_isr()`**: счётчик `toggleCounter`, ветки preamb / data / sync | **`buildGateRuns`**: RLE-сегменты `(gate, lenTicks)` → **`nextWord()`** по тикам таймера |
|
|
||||||
| Останов передачи | `signal == noSignal`, `isSending = false` | `ticksOutput >= totalTicks`, `sum(runs[i].lenTicks)` |
|
|
||||||
|
|
||||||
Идея `buildGateRuns`: **эмулировать** шаги FSM, которые в ISR выполняются при **`toggleCounter == 0`** (см. комментарий в `IR_Encoder.cpp` рядом с внутренним `while`).
|
|
||||||
|
|
||||||
### 2.1. Приоритеты NVIC: приём ИК выше, чем DMA передачи (STM32)
|
|
||||||
|
|
||||||
Пока активна **внешняя** передача по DMA (`IrDmaBackend` и т.п.), таймер крутит поток запросов к DMA — срабатывают **`DMA1_Channelx_IRQn`** (половина/конец буфера и т.д.). Если их приоритет **выше**, чем у **EXTI** линии пина приёмника, обработка фронтов на входе ИК **откладывается** → растёт джиттер `micros()` и страдает заполнение `subBuffer` / журнал `@IRF1v1`, хотя алгоритм `tick`/`writeToBuffer` не менялся.
|
|
||||||
|
|
||||||
**Требование:** числовой приоритет **приёма (EXTI)** должен быть **выше приоритета DMA передачи** (в терминах Cortex-M / STM32 HAL: **меньше** значение preempt priority у EXTI, чем у канала DMA ИК).
|
|
||||||
|
|
||||||
**В репозитории:**
|
|
||||||
|
|
||||||
- **`IR_Decoder`**: библиотека **не** задаёт приоритет EXTI по умолчанию. На Arduino STM32 пользователь вызывает **`setReceiveExtiPreemptPriority(preempt)`** (до или после `enable()`); после `attachInterrupt` применяется поверх приоритета ядра. Семейства с укороченной картой EXTI (C0/F0/G0/L0) — без изменения NVIC из этой функции.
|
|
||||||
- **DMA ИК-TX** (например **`Car/src/IR/IrDmaBackend.cpp`**): preempt задаётся в прошивке носителя (**`CarIrq::kIrTxDmaPreempt`** и т.д.) и должен быть **больше** (ниже срочность), чем у приёма.
|
|
||||||
|
|
||||||
Свой проект: пользователь обязан согласовать приоритеты; **ни один** канал DMA ИК-TX не должен вытеснять EXTI приёма (меньший preempt у DMA = ошибка).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Ключевое наблюдение: `runLenTicks = toggleCounter + 1`
|
|
||||||
|
|
||||||
В **`IR_Encoder::buildGateRuns`** на каждой итерации внешнего цикла:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
const uint16_t runLenTicks = (uint16_t)toggleCounterLocal + 1U;
|
|
||||||
```
|
|
||||||
|
|
||||||
В **`_isr()`** при стартовом **`toggleCounter == N`** выполняется **ровно N** раз ветка `if (toggleCounter) { toggleCounter--; }` подряд, пока счётчик не станет **0**; **следующий** тик попадает в `else` и делает один шаг `switch (signal)`.
|
|
||||||
|
|
||||||
Между двумя такими визитами в `else` проходит **N тиков таймера**, не **N+1**.
|
|
||||||
|
|
||||||
В `buildGateRuns` для того же начального `toggleCounterLocal` в run записывается **`N + 1` тик**. Это даёт **систематическое удлинение каждого сегмента на 1 тик** относительно модели «счётчик убывает N раз до нуля».
|
|
||||||
|
|
||||||
**Следствие:**
|
|
||||||
|
|
||||||
- `totalTicks = Σ lenTicks` в **`IrDmaBackend::startStream`** **больше**, чем число тиков, которое дал бы чистый ISR при том же пакете.
|
|
||||||
- Число внешних итераций `buildGateRuns` (шагов FSM) совпадает с числом таких сегментов; приближённо:
|
|
||||||
`totalTicks ≈ totalTicks_ISR + (число_внешних_шагов)`.
|
|
||||||
|
|
||||||
Короткий кадр: ошибка может «теряться» в допусках приёмника. Длинный (версия) — **накопление** ошибки по времени → сдвиг границ битов → **неверные байты**, в том числе **CRC**.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Ручная проверка CRC по логу (пульт)
|
|
||||||
|
|
||||||
Алгоритм: **`IR_FOX::crc8`** (`IR_config.cpp`), два байта как в **`sendDataFULL`**:
|
|
||||||
|
|
||||||
- первый байт CRC = `crc8(data, 0, packSize - 2, poly1)`;
|
|
||||||
- второй = `crc8(data, 0, packSize - 1, poly2)` (в расчёт второго входит уже первый байт CRC).
|
|
||||||
|
|
||||||
Пример **31-байтного** кадра из лога `Frame reject`:
|
|
||||||
|
|
||||||
- Тело **0…28** (29 байт).
|
|
||||||
- Байты **29…30** — CRC на проводе.
|
|
||||||
|
|
||||||
Для фиксированного дампа байтов **0…28** корректная пара CRC по формуле библиотеки — **`6E 54`**, в логе на проводе — **`96 62`** → **не совпадает**; приёмник обоснованно отклоняет кадр.
|
|
||||||
|
|
||||||
Это **не** объясняется разницей AVR vs STM32: счёт идёт по массиву `uint8_t` побайтно.
|
|
||||||
|
|
||||||
Эхо **8 байт** `C8 FA 2A FD E8 5D AA B4`: пересчёт даёт **`AA B4`** — совпадает с последними байтами кадра → для этого пакета цепочка **байт → CRC** согласована.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Скрипт симуляции
|
|
||||||
|
|
||||||
В репозитории: **`docs/scripts/ir_protocol_gate_runs_sim.py`**.
|
|
||||||
|
|
||||||
Запуск:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python docs/scripts/ir_protocol_gate_runs_sim.py
|
|
||||||
```
|
|
||||||
|
|
||||||
Скрипт:
|
|
||||||
|
|
||||||
1. Считает **CRC** для примеров пакетов (8 байт эха и 31 байт из reject).
|
|
||||||
2. Воспроизводит логику **`buildGateRuns`** (с дополнением буфера до `dataByteSizeMax`, как в C++).
|
|
||||||
3. Печатает **`totalTicks`**, число **внешних шагов** FSM и связь **`totalTicks - outer_steps`** как оценку «тиков в модели ISR без +1 на каждый шаг».
|
|
||||||
|
|
||||||
Пример вывода (значения могут слегка отличаться при смене констант в `IR_config.h`):
|
|
||||||
|
|
||||||
- `preambToggle = 97`
|
|
||||||
- для 8-байт пакета: сотни шагов FSM, `totalTicks` порядка тысяч тиков
|
|
||||||
- для 31-байт: больше шагов и `totalTicks` (~25k+ тиков для текущих констант)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Связь с проектами
|
|
||||||
|
|
||||||
- **Car** (`Executer.cpp`): ответ версии через **`IR_Module::getENC().sendData(...)`** — тот же **`sendDataFULL`**, затем **`rawSend`** → DMA.
|
|
||||||
- **ControlPointUnion** (`CustomCmd.h`, слоты): запрос версии через **`sendResp`** с **`version_query`** — задержка **`IR_ResponseDelay`**, затем **`sendData`** на адрес машинки.
|
|
||||||
- **ControlPointUnion** (`Plan_B.ino`): разбор **`version_response`** из **`gotData` / `gotBackData`** только после **успешного CRC** в декодере.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. Выводы
|
|
||||||
|
|
||||||
1. **Байты в RAM** на передаче формируются корректно библиотекой; проблема «после DMA» укладывается в **расхождение тайминговой развёртки** (`buildGateRuns` + DMA) со **старой** развёрткой ISR, а не в «другой CRC на машинке» при неизменённой библиотеке.
|
|
||||||
2. **Подозрение №1:** `runLenTicks = toggleCounter + 1` в **`buildGateRuns`** не совпадает с числом тиков ISR между шагами FSM (**`N`** vs **`N+1`**). Требуется сверка с эталонной трассой ISR или логическим анализатором.
|
|
||||||
3. **Проверка на будущее:** сравнить побитово выходы ISR и DMA на **одном** буфере (8 и 31 байт); при необходимости поправить формулу длины run в **`IR-protocol`** и пересобрать Car и пульт.
|
|
||||||
4. При **DMA-режиме передачи** на STM32 соблюдать **приоритеты NVIC** (раздел **2.1**): приём EXTI **выше**, чем DMA ИК-TX.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. Ссылки на файлы
|
|
||||||
|
|
||||||
| Файл | Назначение |
|
|
||||||
|------|------------|
|
|
||||||
| `Documents/Arduino/libraries/IR-protocol/IR_Encoder.cpp` | `buildGateRuns`, `_isr`, `rawSend` |
|
|
||||||
| `Documents/Arduino/libraries/IR-protocol/IR_Decoder.cpp` | `setReceiveExtiPreemptPriority` / `enable`: опциональный `NVIC_SetPriority` для EXTI (Arduino STM32) |
|
|
||||||
| `Documents/Arduino/libraries/IR-protocol/IR_config.cpp` | `crc8` |
|
|
||||||
| `Car/src/IR/IR.cpp` | `setExternalTxBackend`, `txStart` |
|
|
||||||
| `Car/src/IR/IrDmaBackend.cpp` | `startStream`, `totalTicks`, `nextWord`, NVIC DMA из `CarIrq` |
|
|
||||||
| `Car/src/IR/IR.cpp` | `setReceiveExtiPreemptPriority` + `enable` декодера |
|
|
||||||
| `ControlPointUnion/Plan_B/TestPoints/CustomCmd.h` | `sendResp` / `version_query` для тестовых слотов |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Документ составлен по обсуждению в чате; при смене версии IR-protocol числа констант и `totalTicks` пересчитывайте скриптом.*
|
|
||||||
110
IR_Decoder.cpp
110
IR_Decoder.cpp
@ -1,110 +0,0 @@
|
|||||||
#include "IR_Decoder.h"
|
|
||||||
|
|
||||||
std::list<IR_Decoder *> &IR_Decoder::get_dec_list() // определение функции
|
|
||||||
{
|
|
||||||
static std::list<IR_Decoder *> dec_list; // статическая локальная переменная
|
|
||||||
return dec_list; // возвращается ссылка на переменную
|
|
||||||
}
|
|
||||||
|
|
||||||
// IR_Decoder::IR_Decoder() {};
|
|
||||||
IR_Decoder::IR_Decoder(const uint8_t pin, uint16_t addr, IR_Encoder *encPair, bool autoHandle)
|
|
||||||
: IR_DecoderRaw(pin, addr, encPair)
|
|
||||||
{
|
|
||||||
get_dec_list().push_back(this);
|
|
||||||
if(autoHandle){
|
|
||||||
enable();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void IR_Decoder::enable()
|
|
||||||
{
|
|
||||||
auto &dec_list = get_dec_list();
|
|
||||||
if (std::find(dec_list.begin(), dec_list.end(), this) == dec_list.end())
|
|
||||||
{
|
|
||||||
dec_list.push_back(this);
|
|
||||||
}
|
|
||||||
pinMode(pin, INPUT_PULLUP);
|
|
||||||
attachInterrupt(pin, (*this)(), CHANGE);
|
|
||||||
}
|
|
||||||
|
|
||||||
void IR_Decoder::disable()
|
|
||||||
{
|
|
||||||
detachInterrupt(pin);
|
|
||||||
pinMode(pin, INPUT);
|
|
||||||
auto &dec_list = get_dec_list();
|
|
||||||
auto it = std::find(dec_list.begin(), dec_list.end(), this);
|
|
||||||
if (it != dec_list.end())
|
|
||||||
{
|
|
||||||
dec_list.erase(it);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
std::function<void()> IR_Decoder::operator()()
|
|
||||||
{
|
|
||||||
return std::bind(&IR_Decoder::isr, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
IR_Decoder::~IR_Decoder()
|
|
||||||
{
|
|
||||||
IR_Decoder::get_dec_list().remove(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void IR_Decoder::tick()
|
|
||||||
{
|
|
||||||
for (const auto &element : IR_Decoder::get_dec_list())
|
|
||||||
{
|
|
||||||
element->_tick();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void IR_Decoder::_tick()
|
|
||||||
{
|
|
||||||
IR_DecoderRaw::tick();
|
|
||||||
|
|
||||||
if (availableRaw())
|
|
||||||
{
|
|
||||||
#ifdef IRDEBUG_INFO
|
|
||||||
Serial.println("PARSING RAW DATA");
|
|
||||||
#endif
|
|
||||||
isWaitingAcceptSend = false;
|
|
||||||
switch (packInfo.buffer[0] >> 5 & IR_MASK_MSG_TYPE)
|
|
||||||
{
|
|
||||||
case IR_MSG_DATA_ACCEPT:
|
|
||||||
case IR_MSG_DATA_NOACCEPT:
|
|
||||||
gotData.set(&packInfo, id);
|
|
||||||
break;
|
|
||||||
case IR_MSG_BACK:
|
|
||||||
case IR_MSG_BACK_TO:
|
|
||||||
gotBackData.set(&packInfo, id);
|
|
||||||
break;
|
|
||||||
case IR_MSG_REQUEST:
|
|
||||||
gotRequest.set(&packInfo, id);
|
|
||||||
break;
|
|
||||||
case IR_MSG_ACCEPT:
|
|
||||||
gotAccept.set(&packInfo, id);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (gotData.isAvailable && (gotData.getMsgType() == IR_MSG_DATA_ACCEPT))
|
|
||||||
{
|
|
||||||
acceptSendTimer = millis();
|
|
||||||
addrAcceptSendTo = gotData.getAddrFrom();
|
|
||||||
acceptCustomByte = crc8(gotData.getDataPrt(), 0, gotData.getDataSize(), poly1);
|
|
||||||
if (addrAcceptSendTo && addrAcceptSendTo < IR_Broadcast)
|
|
||||||
isWaitingAcceptSend = true;
|
|
||||||
}
|
|
||||||
gotRaw.set(&packInfo, id);
|
|
||||||
}
|
|
||||||
if (isWaitingAcceptSend && millis() - acceptSendTimer > acceptDelay)
|
|
||||||
{
|
|
||||||
encoder->sendAccept(addrAcceptSendTo, acceptCustomByte);
|
|
||||||
isWaitingAcceptSend = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IR_Decoder::isReceive(uint8_t type) {
|
|
||||||
return (msgTypeReceive & 0b11111000) && ((msgTypeReceive & IR_MASK_MSG_TYPE) == type);
|
|
||||||
}
|
|
||||||
67
IR_Decoder.h
67
IR_Decoder.h
@ -5,16 +5,11 @@
|
|||||||
|
|
||||||
class IR_Decoder : public IR_DecoderRaw
|
class IR_Decoder : public IR_DecoderRaw
|
||||||
{
|
{
|
||||||
private:
|
|
||||||
// static std::list<IR_Decoder *> dec_list;
|
|
||||||
static std::list<IR_Decoder*>& get_dec_list();
|
|
||||||
void _tick();
|
|
||||||
|
|
||||||
uint32_t acceptSendTimer;
|
uint32_t acceptSendTimer;
|
||||||
bool isWaitingAcceptSend;
|
bool isWaitingAcceptSend;
|
||||||
uint16_t addrAcceptSendTo;
|
uint16_t addrAcceptSendTo;
|
||||||
|
|
||||||
uint16_t acceptDelay = IR_ResponseDelay;
|
uint16_t acceptDelay = 75;
|
||||||
uint8_t acceptCustomByte;
|
uint8_t acceptCustomByte;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -24,25 +19,59 @@ public:
|
|||||||
PacketTypes::Request gotRequest;
|
PacketTypes::Request gotRequest;
|
||||||
PacketTypes::BasePack gotRaw;
|
PacketTypes::BasePack gotRaw;
|
||||||
|
|
||||||
// IR_Decoder();
|
IR_Decoder(const uint8_t isrPin, uint16_t addr, IR_Encoder *encPair = nullptr) : IR_DecoderRaw(isrPin, addr, encPair) {}
|
||||||
IR_Decoder(const uint8_t pin, uint16_t addr = 0, IR_Encoder *encPair = nullptr, bool autoHandle = true);
|
|
||||||
|
|
||||||
std::function<void()> operator()();
|
void tick()
|
||||||
|
{
|
||||||
|
IR_DecoderRaw::tick();
|
||||||
|
if (availableRaw())
|
||||||
|
{
|
||||||
|
#ifdef IRDEBUG_INFO
|
||||||
|
Serial.println("PARSING RAW DATA");
|
||||||
|
#endif
|
||||||
|
isWaitingAcceptSend = false;
|
||||||
|
switch (packInfo.buffer[0] >> 5 & IR_MASK_MSG_TYPE)
|
||||||
|
{
|
||||||
|
case IR_MSG_DATA_ACCEPT:
|
||||||
|
case IR_MSG_DATA_NOACCEPT:
|
||||||
|
gotData.set(&packInfo, id);
|
||||||
|
break;
|
||||||
|
case IR_MSG_BACK:
|
||||||
|
case IR_MSG_BACK_TO:
|
||||||
|
gotBackData.set(&packInfo, id);
|
||||||
|
break;
|
||||||
|
case IR_MSG_REQUEST:
|
||||||
|
gotRequest.set(&packInfo, id);
|
||||||
|
break;
|
||||||
|
case IR_MSG_ACCEPT:
|
||||||
|
gotAccept.set(&packInfo, id);
|
||||||
|
break;
|
||||||
|
|
||||||
void enable();
|
default:
|
||||||
void disable();
|
break;
|
||||||
|
}
|
||||||
bool isReceive(uint8_t type);
|
if (gotData.isAvailable && (gotData.getMsgType() == IR_MSG_DATA_ACCEPT))
|
||||||
|
{
|
||||||
|
acceptSendTimer = millis();
|
||||||
|
addrAcceptSendTo = gotData.getAddrFrom();
|
||||||
|
acceptCustomByte = crc8(gotData.getDataPrt(), 0, gotData.getDataSize(), poly1);
|
||||||
|
if (addrAcceptSendTo && addrAcceptSendTo < IR_Broadcast)
|
||||||
|
isWaitingAcceptSend = true;
|
||||||
|
}
|
||||||
|
gotRaw.set(&packInfo, id);
|
||||||
|
}
|
||||||
|
if (isWaitingAcceptSend && millis() - acceptSendTimer > 75)
|
||||||
|
{
|
||||||
|
encoder->sendAccept(addrAcceptSendTo, acceptCustomByte);
|
||||||
|
isWaitingAcceptSend = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
~IR_Decoder();
|
void setAcceptDelay(uint16_t acceptDelay)
|
||||||
|
|
||||||
static void tick();
|
|
||||||
|
|
||||||
inline void setAcceptDelay(uint16_t acceptDelay)
|
|
||||||
{
|
{
|
||||||
this->acceptDelay = acceptDelay;
|
this->acceptDelay = acceptDelay;
|
||||||
}
|
}
|
||||||
inline uint16_t getAcceptDelay()
|
uint16_t getAcceptDelay()
|
||||||
{
|
{
|
||||||
return this->acceptDelay;
|
return this->acceptDelay;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
#include "IR_DecoderRaw.h"
|
#include "IR_DecoderRaw.h"
|
||||||
#include "IR_Encoder.h"
|
#include "IR_Encoder.h"
|
||||||
|
|
||||||
IR_DecoderRaw::IR_DecoderRaw(const uint8_t pin, uint16_t addr, IR_Encoder *encPair) : encoder(encPair)
|
IR_DecoderRaw::IR_DecoderRaw(const uint8_t isrPin, uint16_t addr, IR_Encoder *encPair) : isrPin(isrPin), encoder(encPair)
|
||||||
{
|
{
|
||||||
setPin(pin);
|
|
||||||
id = addr;
|
id = addr;
|
||||||
prevRise = prevFall = prevPrevFall = prevPrevRise = 0;
|
prevRise = prevFall = prevPrevFall = prevPrevRise = 0;
|
||||||
if (encPair != nullptr)
|
if (encPair != nullptr)
|
||||||
@ -21,45 +20,18 @@ IR_DecoderRaw::IR_DecoderRaw(const uint8_t pin, uint16_t addr, IR_Encoder *encPa
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IR_DecoderRaw::isSubOverflow()
|
|
||||||
{
|
|
||||||
noInterrupts();
|
|
||||||
volatile bool ret = isSubBufferOverflow;
|
|
||||||
interrupts();
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IR_DecoderRaw::availableRaw()
|
|
||||||
{
|
|
||||||
if (isAvailable)
|
|
||||||
{
|
|
||||||
isAvailable = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//////////////////////////////////// isr ///////////////////////////////////////////
|
//////////////////////////////////// isr ///////////////////////////////////////////
|
||||||
volatile uint32_t time_;
|
volatile uint32_t time_;
|
||||||
|
|
||||||
void IR_DecoderRaw::isr()
|
void IR_DecoderRaw::isr()
|
||||||
{
|
{
|
||||||
// Serial.print("ISR\n");
|
|
||||||
if(isPairSending){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
noInterrupts();
|
noInterrupts();
|
||||||
// time_ = HAL_GetTick() * 1000 + ((SysTick->LOAD + 1 - SysTick->VAL) * 1000) / SysTick->LOAD + 1;
|
// time_ = HAL_GetTick() * 1000 + ((SysTick->LOAD + 1 - SysTick->VAL) * 1000) / SysTick->LOAD + 1;
|
||||||
time_ = micros();
|
time_ = micros();
|
||||||
interrupts();
|
interrupts();
|
||||||
if (time_ < oldTime)
|
if (time_ < oldTime)
|
||||||
{
|
{
|
||||||
|
#ifdef IRDEBUG
|
||||||
#ifdef IRDEBUG
|
|
||||||
Serial.print("\n");
|
Serial.print("\n");
|
||||||
Serial.print("count: ");
|
Serial.print("count: ");
|
||||||
Serial.println(wrongCounter++);
|
Serial.println(wrongCounter++);
|
||||||
@ -75,7 +47,7 @@ void IR_DecoderRaw::isr()
|
|||||||
oldTime = time_;
|
oldTime = time_;
|
||||||
|
|
||||||
FrontStorage edge;
|
FrontStorage edge;
|
||||||
edge.dir = port->IDR & mask;
|
edge.dir = digitalRead(isrPin);
|
||||||
edge.time = time_;
|
edge.time = time_;
|
||||||
|
|
||||||
subBuffer.push(edge);
|
subBuffer.push(edge);
|
||||||
@ -83,6 +55,7 @@ void IR_DecoderRaw::isr()
|
|||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
uint32_t wrCounter;
|
||||||
void IR_DecoderRaw::firstRX()
|
void IR_DecoderRaw::firstRX()
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -104,45 +77,23 @@ void IR_DecoderRaw::firstRX()
|
|||||||
|
|
||||||
isPreamb = true;
|
isPreamb = true;
|
||||||
riseSyncTime = bitTime /* 1100 */;
|
riseSyncTime = bitTime /* 1100 */;
|
||||||
#ifdef IRDEBUG
|
|
||||||
wrCounter = 0;
|
wrCounter = 0;
|
||||||
#endif
|
|
||||||
memset(dataBuffer, 0x00, dataByteSizeMax);
|
memset(dataBuffer, 0x00, dataByteSizeMax);
|
||||||
}
|
}
|
||||||
|
|
||||||
void IR_DecoderRaw::listenStart()
|
void IR_DecoderRaw::listenStart()
|
||||||
{
|
{
|
||||||
if (isReciveRaw && ((micros() - prevRise) > IR_timeout * 2))
|
if (isRecive && ((micros() - prevRise) > IR_timeout * 2))
|
||||||
{
|
{
|
||||||
// Serial.print("\nlis>");
|
// Serial.print("\nlis>");
|
||||||
isReciveRaw = false;
|
isRecive = false;
|
||||||
firstRX();
|
firstRX();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ---- быстрая проверка конца пакета ---------------------------------
|
|
||||||
inline void IR_DecoderRaw::checkTimeout()
|
|
||||||
{
|
|
||||||
if (!isRecive) return; // уже не принимаем – нечего проверять
|
|
||||||
|
|
||||||
if (micros() - lastEdgeTime > IR_timeout * 2U)
|
|
||||||
{
|
|
||||||
isRecive = false; // приём завершён
|
|
||||||
msgTypeReceive = 0;
|
|
||||||
// firstRX(); // подготовка к новому пакету
|
|
||||||
lastEdgeTime = micros(); // защита от повторного срабатывания
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// ====================================================================
|
|
||||||
|
|
||||||
void IR_DecoderRaw::tick()
|
void IR_DecoderRaw::tick()
|
||||||
{
|
{
|
||||||
// FrontStorage *currentFrontPtr;
|
|
||||||
// noInterrupts();
|
|
||||||
// currentFrontPtr = subBuffer.pop();
|
|
||||||
// interrupts();
|
|
||||||
|
|
||||||
FrontStorage currentFront;
|
FrontStorage currentFront;
|
||||||
noInterrupts();
|
noInterrupts();
|
||||||
listenStart();
|
listenStart();
|
||||||
@ -151,24 +102,12 @@ void IR_DecoderRaw::tick()
|
|||||||
if (currentFrontPtr == nullptr)
|
if (currentFrontPtr == nullptr)
|
||||||
{
|
{
|
||||||
isSubBufferOverflow = false;
|
isSubBufferOverflow = false;
|
||||||
checkTimeout(); // <--- новое место проверки
|
|
||||||
interrupts();
|
interrupts();
|
||||||
return;
|
return;
|
||||||
} // Если данных нет - ничего не делаем
|
} // Если данных нет - ничего не делаем
|
||||||
currentFront = *currentFrontPtr;
|
currentFront = *currentFrontPtr;
|
||||||
interrupts();
|
interrupts();
|
||||||
|
|
||||||
// ---------- буфер пуст: фронтов нет, проверяем тайм-аут ----------
|
|
||||||
// if (currentFrontPtr == nullptr)
|
|
||||||
// {
|
|
||||||
// isSubBufferOverflow = false;
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // ---------- есть фронт: продолжаем обработку ----------
|
|
||||||
// FrontStorage currentFront = *currentFrontPtr;
|
|
||||||
lastEdgeTime = currentFront.time; // запоминаем любой фронт
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
if (currentFront.dir)
|
if (currentFront.dir)
|
||||||
@ -230,7 +169,7 @@ void IR_DecoderRaw::tick()
|
|||||||
digitalWrite(errOut, currentFront.dir);
|
digitalWrite(errOut, currentFront.dir);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (currentFront.time > prevRise && currentFront.time - prevRise > IR_timeout * 2 && !isReciveRaw)
|
if (currentFront.time > prevRise && currentFront.time - prevRise > IR_timeout * 2 && !isRecive)
|
||||||
{ // первый
|
{ // первый
|
||||||
#ifdef IRDEBUG
|
#ifdef IRDEBUG
|
||||||
errPulse(up, 50);
|
errPulse(up, 50);
|
||||||
@ -242,7 +181,6 @@ void IR_DecoderRaw::tick()
|
|||||||
isPreamb = true;
|
isPreamb = true;
|
||||||
|
|
||||||
isRecive = true;
|
isRecive = true;
|
||||||
isReciveRaw = true;
|
|
||||||
isWrongPack = false;
|
isWrongPack = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -475,8 +413,6 @@ void IR_DecoderRaw::writeToBuffer(bool bit)
|
|||||||
if (isBufferOverflow || isPreamb || isWrongPack)
|
if (isBufferOverflow || isPreamb || isWrongPack)
|
||||||
{
|
{
|
||||||
isRecive = false;
|
isRecive = false;
|
||||||
isReciveRaw = false;
|
|
||||||
msgTypeReceive = 0;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -600,44 +536,36 @@ void IR_DecoderRaw::writeToBuffer(bool bit)
|
|||||||
packInfo.rTime = riseSyncTime;
|
packInfo.rTime = riseSyncTime;
|
||||||
|
|
||||||
isRecive = false;
|
isRecive = false;
|
||||||
isReciveRaw = false;
|
|
||||||
msgTypeReceive = 0;
|
|
||||||
isAvailable = crcCheck(packSize - crcBytes, crcValue);
|
isAvailable = crcCheck(packSize - crcBytes, crcValue);
|
||||||
|
|
||||||
#ifdef BRUTEFORCE_CHECK
|
#ifdef BRUTEFORCE_CHECK
|
||||||
if (!isAvailable) // Исправление первого бита // Очень большая затычка...
|
if (!isAvailable) // Исправление первого бита // Очень большая затычка...
|
||||||
for (size_t i = 0; i < min(uint16_t(packSize - crcBytes * 2U), uint16_t(dataByteSizeMax)); ++i)
|
for (size_t i = 0; i < min(uint16_t(packSize - crcBytes*2U), uint16_t(dataByteSizeMax)); ++i)
|
||||||
{
|
|
||||||
for (int j = 0; j < 8; ++j)
|
|
||||||
{
|
{
|
||||||
// инвертируем бит
|
for (int j = 0; j < 8; ++j)
|
||||||
dataBuffer[i] ^= 1 << j;
|
|
||||||
|
|
||||||
isAvailable = crcCheck(min(uint16_t(packSize - crcBytes), uint16_t(dataByteSizeMax - 1U)), crcValue);
|
|
||||||
// обратно инвертируем бит в исходное состояние
|
|
||||||
|
|
||||||
if (isAvailable)
|
|
||||||
{
|
|
||||||
#ifdef IRDEBUG_INFO
|
|
||||||
Serial.println("!!!INV!!!");
|
|
||||||
#endif
|
|
||||||
goto OUT_BRUTEFORCE;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
|
// инвертируем бит
|
||||||
dataBuffer[i] ^= 1 << j;
|
dataBuffer[i] ^= 1 << j;
|
||||||
|
|
||||||
|
isAvailable = crcCheck(min(uint16_t(packSize - crcBytes), uint16_t(dataByteSizeMax - 1U)), crcValue);
|
||||||
|
// обратно инвертируем бит в исходное состояние
|
||||||
|
|
||||||
|
if (isAvailable)
|
||||||
|
{
|
||||||
|
#ifdef IRDEBUG_INFO
|
||||||
|
Serial.println("!!!INV!!!");
|
||||||
|
#endif
|
||||||
|
goto OUT_BRUTEFORCE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dataBuffer[i] ^= 1 << j;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
OUT_BRUTEFORCE:;
|
||||||
OUT_BRUTEFORCE:;
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packSize && (i_dataBuffer == 8)) {
|
|
||||||
msgTypeReceive = (dataBuffer[0]>>5) | 0b11111000;
|
|
||||||
// SerialUSB.println(msgTypeReceive & IR_MASK_MSG_TYPE);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|||||||
@ -14,6 +14,8 @@
|
|||||||
#define up PA3
|
#define up PA3
|
||||||
#define down PA2
|
#define down PA2
|
||||||
#endif
|
#endif
|
||||||
|
#define up PA3
|
||||||
|
#define down PA2
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@ -23,7 +25,6 @@
|
|||||||
#define riseTimeMin (riseTime - riseTolerance)
|
#define riseTimeMin (riseTime - riseTolerance)
|
||||||
#define aroundRise(t) (riseTimeMin < t && t < riseTimeMax)
|
#define aroundRise(t) (riseTimeMin < t && t < riseTimeMax)
|
||||||
#define IR_timeout (riseTimeMax * (8 + syncBits + 1)) // us // таймаут в 8 data + 3 sync + 1
|
#define IR_timeout (riseTimeMax * (8 + syncBits + 1)) // us // таймаут в 8 data + 3 sync + 1
|
||||||
constexpr uint16_t IR_ResponseDelay = ((uint16_t)(((bitTime+riseTolerance) * (8 + syncBits + 1))*2.7735))/1000;
|
|
||||||
|
|
||||||
class IR_Encoder;
|
class IR_Encoder;
|
||||||
class IR_DecoderRaw : virtual public IR_FOX
|
class IR_DecoderRaw : virtual public IR_FOX
|
||||||
@ -31,25 +32,45 @@ class IR_DecoderRaw : virtual public IR_FOX
|
|||||||
friend IR_Encoder;
|
friend IR_Encoder;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
PackInfo packInfo;
|
PackInfo packInfo;
|
||||||
uint8_t msgTypeReceive = 0;
|
IR_Encoder *encoder; // Указатель на парный передатчик
|
||||||
IR_Encoder *encoder; // Указатель на парный передатчик
|
bool availableRaw()
|
||||||
bool availableRaw();
|
{
|
||||||
|
if (isAvailable)
|
||||||
|
{
|
||||||
|
isAvailable = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
const uint8_t isrPin; // Пин прерывания
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
/// @brief Конструктор
|
/// @brief Конструктор
|
||||||
/// @param pin Номер вывода прерывания/данных от приёмника (2 или 3 для atmega 328p)
|
/// @param isrPin Номер вывода прерывания/данных от приёмника (2 или 3 для atmega 328p)
|
||||||
/// @param addr Адрес приёмника
|
/// @param addr Адрес приёмника
|
||||||
/// @param encPair Указатель на передатчик, работающий в паре
|
/// @param encPair Указатель на передатчик, работающий в паре
|
||||||
IR_DecoderRaw(const uint8_t pin, uint16_t addr, IR_Encoder *encPair = nullptr);
|
IR_DecoderRaw(const uint8_t isrPin, uint16_t addr, IR_Encoder *encPair = nullptr);
|
||||||
|
|
||||||
void isr(); // Функция прерывания
|
void isr(); // Функция прерывания
|
||||||
void tick(); // Обработка приёмника, необходима для работы
|
void tick(); // Обработка приёмника, необходима для работы
|
||||||
|
void tickOld();
|
||||||
|
|
||||||
inline bool isOverflow() { return isBufferOverflow; }; // Буффер переполнился
|
bool isOverflow() { return isBufferOverflow; }; // Буффер переполнился
|
||||||
bool isSubOverflow();
|
bool isSubOverflow()
|
||||||
volatile inline bool isReciving() { return isRecive; }; // Возвращает true, если происходит приём пакета
|
{
|
||||||
|
|
||||||
|
// noInterrupts();
|
||||||
|
volatile bool ret = isSubBufferOverflow;
|
||||||
|
// interrupts();
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
bool isReciving() { return isBufferOverflow; }; // Возвращает true, если происходит приём пакета
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
private:
|
private:
|
||||||
@ -66,8 +87,6 @@ private:
|
|||||||
|
|
||||||
uint16_t riseSyncTime = bitTime; // Подстраиваемое время бита в мкс
|
uint16_t riseSyncTime = bitTime; // Подстраиваемое время бита в мкс
|
||||||
|
|
||||||
volatile uint32_t lastEdgeTime = 0; // время последнего фронта
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////
|
||||||
volatile uint32_t currentSubBufferIndex; // Счетчик текущей позиции во вспомогательном буфере фронтов/спадов
|
volatile uint32_t currentSubBufferIndex; // Счетчик текущей позиции во вспомогательном буфере фронтов/спадов
|
||||||
|
|
||||||
@ -103,9 +122,7 @@ private:
|
|||||||
int16_t bufBitPos = 0; // Позиция для записи бита в буффер
|
int16_t bufBitPos = 0; // Позиция для записи бита в буффер
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool isReciveRaw;
|
void listenStart(); // @brief Слушатель для работы isReciving()
|
||||||
void listenStart();
|
|
||||||
void checkTimeout(); //
|
|
||||||
|
|
||||||
/// @brief Проверка CRC. Проверяет len байт со значением crc, пришедшим в пакете
|
/// @brief Проверка CRC. Проверяет len байт со значением crc, пришедшим в пакете
|
||||||
/// @param len Длина в байтах проверяемых данных
|
/// @param len Длина в байтах проверяемых данных
|
||||||
@ -133,9 +150,8 @@ bool isReciveRaw;
|
|||||||
/// @return Результат
|
/// @return Результат
|
||||||
uint16_t ceil_div(uint16_t val, uint16_t divider);
|
uint16_t ceil_div(uint16_t val, uint16_t divider);
|
||||||
|
|
||||||
#ifdef IRDEBUG
|
#if true //def IRDEBUG
|
||||||
uint32_t wrCounter;
|
|
||||||
inline void errPulse(uint8_t pin, uint8_t count);
|
inline void errPulse(uint8_t pin, uint8_t count);
|
||||||
inline void infoPulse(uint8_t pin, uint8_t count);
|
inline void infoPulse(uint8_t pin, uint8_t count);
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
524
IR_Encoder.cpp
524
IR_Encoder.cpp
@ -1,18 +1,12 @@
|
|||||||
#include "IR_Encoder.h"
|
#include "IR_Encoder.h"
|
||||||
#include "IR_DecoderRaw.h"
|
#include "IR_DecoderRaw.h"
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#define LoopOut 12
|
#define LoopOut 12
|
||||||
#define ISR_Out 10
|
#define ISR_Out 10
|
||||||
#define TestOut 13
|
#define TestOut 13
|
||||||
|
|
||||||
IR_Encoder *IR_Encoder::head = nullptr;
|
IR_Encoder::IR_Encoder(uint16_t addr, IR_DecoderRaw *decPair)
|
||||||
IR_Encoder *IR_Encoder::last = nullptr;
|
|
||||||
volatile bool IR_Encoder::carrierStopPending = false;
|
|
||||||
|
|
||||||
IR_Encoder::IR_Encoder(uint8_t pin, uint16_t addr, IR_DecoderRaw *decPair, bool autoHandle)
|
|
||||||
{
|
{
|
||||||
setPin(pin);
|
|
||||||
id = addr;
|
id = addr;
|
||||||
this->decPair = decPair;
|
this->decPair = decPair;
|
||||||
signal = noSignal;
|
signal = noSignal;
|
||||||
@ -20,7 +14,8 @@ IR_Encoder::IR_Encoder(uint8_t pin, uint16_t addr, IR_DecoderRaw *decPair, bool
|
|||||||
#if disablePairDec
|
#if disablePairDec
|
||||||
if (decPair != nullptr)
|
if (decPair != nullptr)
|
||||||
{
|
{
|
||||||
blindDecoders = new IR_DecoderRaw *[1]{decPair};
|
blindDecoders = new IR_DecoderRaw *[1]
|
||||||
|
{ decPair };
|
||||||
decodersCount = 1;
|
decodersCount = 1;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -28,278 +23,7 @@ IR_Encoder::IR_Encoder(uint8_t pin, uint16_t addr, IR_DecoderRaw *decPair, bool
|
|||||||
{
|
{
|
||||||
decPair->encoder = this;
|
decPair->encoder = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (autoHandle)
|
|
||||||
{
|
|
||||||
if (IR_Encoder::head == nullptr)
|
|
||||||
{
|
|
||||||
IR_Encoder::head = this;
|
|
||||||
}
|
|
||||||
if (last != nullptr)
|
|
||||||
{
|
|
||||||
last->next = this;
|
|
||||||
}
|
|
||||||
last = this;
|
|
||||||
|
|
||||||
pinMode(pin, OUTPUT);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
HardwareTimer* IR_Encoder::IR_Timer = nullptr;
|
|
||||||
IR_Encoder::ExternalTxStartFn IR_Encoder::externalTxStartFn = nullptr;
|
|
||||||
IR_Encoder::ExternalTxBusyFn IR_Encoder::externalTxBusyFn = nullptr;
|
|
||||||
void *IR_Encoder::externalTxCtx = nullptr;
|
|
||||||
|
|
||||||
inline HardwareTimer* IR_Encoder::get_IR_Timer(){return IR_Encoder::IR_Timer;}
|
|
||||||
|
|
||||||
void IR_Encoder::carrierResume() {
|
|
||||||
if (IR_Timer != nullptr)
|
|
||||||
IR_Timer->resume();
|
|
||||||
}
|
|
||||||
|
|
||||||
void IR_Encoder::carrierPauseIfIdle() {
|
|
||||||
for (IR_Encoder *p = head; p != nullptr; p = p->next)
|
|
||||||
if (p->isSending)
|
|
||||||
return;
|
|
||||||
if (IR_Timer != nullptr)
|
|
||||||
IR_Timer->pause();
|
|
||||||
}
|
|
||||||
|
|
||||||
void IR_Encoder::tick() {
|
|
||||||
if (!carrierStopPending)
|
|
||||||
return;
|
|
||||||
carrierStopPending = false;
|
|
||||||
carrierPauseIfIdle();
|
|
||||||
}
|
|
||||||
|
|
||||||
void IR_Encoder::begin(HardwareTimer* timer, uint8_t channel, IRQn_Type IRQn, uint8_t priority, void(*isrCallback)()){
|
|
||||||
IR_Timer = timer;
|
|
||||||
if(IR_Timer == nullptr) return;
|
|
||||||
IR_Timer->pause();
|
|
||||||
IR_Timer->setOverflow(carrierFrec * 2, HERTZ_FORMAT);
|
|
||||||
IR_Timer->attachInterrupt(channel, (isrCallback == nullptr ? IR_Encoder::isr : isrCallback));
|
|
||||||
NVIC_SetPriority(IRQn, priority);
|
|
||||||
IR_Timer->pause();
|
|
||||||
}
|
|
||||||
|
|
||||||
void IR_Encoder::beginClockOnly(HardwareTimer *timer)
|
|
||||||
{
|
|
||||||
IR_Timer = timer;
|
|
||||||
if (IR_Timer == nullptr)
|
|
||||||
return;
|
|
||||||
IR_Timer->pause();
|
|
||||||
IR_Timer->setOverflow(carrierFrec * 2, HERTZ_FORMAT);
|
|
||||||
IR_Timer->pause();
|
|
||||||
}
|
|
||||||
|
|
||||||
void IR_Encoder::setExternalTxBackend(ExternalTxStartFn startFn, ExternalTxBusyFn busyFn, void *ctx)
|
|
||||||
{
|
|
||||||
externalTxStartFn = startFn;
|
|
||||||
externalTxBusyFn = busyFn;
|
|
||||||
externalTxCtx = ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
void IR_Encoder::externalFinishSend()
|
|
||||||
{
|
|
||||||
if (!isSending)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Force output low.
|
|
||||||
if (port != nullptr) {
|
|
||||||
port->BSRR = ((uint32_t)mask) << 16;
|
|
||||||
}
|
|
||||||
|
|
||||||
isSending = false;
|
|
||||||
setDecoder_isSending();
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t IR_Encoder::buildGateRuns(const uint8_t *packet, uint8_t len, IR_TxGateRun *outRuns, size_t maxRuns)
|
|
||||||
{
|
|
||||||
if (packet == nullptr || outRuns == nullptr || maxRuns == 0)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (len == 0 || len > dataByteSizeMax)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy into fixed-size buffer to match original encoder behavior (safe reads past sendLen).
|
|
||||||
uint8_t sendBufferLocal[dataByteSizeMax] = {0};
|
|
||||||
memcpy(sendBufferLocal, packet, len);
|
|
||||||
|
|
||||||
uint8_t sendLenLocal = len;
|
|
||||||
uint8_t toggleCounterLocal = preambToggle;
|
|
||||||
uint8_t dataBitCounterLocal = bitPerByte - 1;
|
|
||||||
uint8_t dataByteCounterLocal = 0;
|
|
||||||
uint8_t preambFrontCounterLocal = preambPulse * 2 - 1;
|
|
||||||
uint8_t dataSequenceCounterLocal = bitPerByte * 2;
|
|
||||||
uint8_t syncSequenceCounterLocal = syncBits * 2;
|
|
||||||
bool syncLastBitLocal = false;
|
|
||||||
SignalPart signalLocal = preamb;
|
|
||||||
bool stateLocal = HIGH;
|
|
||||||
uint8_t *currentBitSequenceLocal = bitHigh;
|
|
||||||
|
|
||||||
size_t runCount = 0;
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
const bool gate = stateLocal;
|
|
||||||
const uint16_t runLenTicks = (uint16_t)toggleCounterLocal + 1U;
|
|
||||||
|
|
||||||
if (runCount > 0 && outRuns[runCount - 1].gate == gate)
|
|
||||||
{
|
|
||||||
outRuns[runCount - 1].lenTicks = (uint16_t)(outRuns[runCount - 1].lenTicks + runLenTicks);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (runCount >= maxRuns)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
outRuns[runCount].gate = gate;
|
|
||||||
outRuns[runCount].lenTicks = runLenTicks;
|
|
||||||
runCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Advance state to the next run boundary (equivalent to ISR iteration when toggleCounter == 0).
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
switch (signalLocal)
|
|
||||||
{
|
|
||||||
case noSignal:
|
|
||||||
return runCount;
|
|
||||||
|
|
||||||
case preamb:
|
|
||||||
if (preambFrontCounterLocal)
|
|
||||||
{
|
|
||||||
preambFrontCounterLocal--;
|
|
||||||
toggleCounterLocal = preambToggle;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// End of preamble.
|
|
||||||
signalLocal = data;
|
|
||||||
stateLocal = !LOW;
|
|
||||||
continue;
|
|
||||||
|
|
||||||
case data:
|
|
||||||
if (dataSequenceCounterLocal)
|
|
||||||
{
|
|
||||||
if (!(dataSequenceCounterLocal & 1U))
|
|
||||||
{
|
|
||||||
currentBitSequenceLocal = ((sendBufferLocal[dataByteCounterLocal] >> dataBitCounterLocal) & 1U) ? bitHigh : bitLow;
|
|
||||||
dataBitCounterLocal--;
|
|
||||||
}
|
|
||||||
toggleCounterLocal = currentBitSequenceLocal[!stateLocal];
|
|
||||||
dataSequenceCounterLocal--;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// End of data byte.
|
|
||||||
syncLastBitLocal = ((sendBufferLocal[dataByteCounterLocal]) & 1U);
|
|
||||||
dataByteCounterLocal++;
|
|
||||||
dataBitCounterLocal = bitPerByte - 1;
|
|
||||||
dataSequenceCounterLocal = bitPerByte * 2;
|
|
||||||
signalLocal = sync;
|
|
||||||
continue;
|
|
||||||
|
|
||||||
case sync:
|
|
||||||
if (syncSequenceCounterLocal)
|
|
||||||
{
|
|
||||||
if (!(syncSequenceCounterLocal & 1U))
|
|
||||||
{
|
|
||||||
if (syncSequenceCounterLocal == 2)
|
|
||||||
{
|
|
||||||
currentBitSequenceLocal = ((sendBufferLocal[dataByteCounterLocal]) & 0b10000000) ? bitLow : bitHigh;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
currentBitSequenceLocal = syncLastBitLocal ? bitLow : bitHigh;
|
|
||||||
syncLastBitLocal = !syncLastBitLocal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
toggleCounterLocal = currentBitSequenceLocal[!stateLocal];
|
|
||||||
syncSequenceCounterLocal--;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// End of sync.
|
|
||||||
signalLocal = data;
|
|
||||||
syncSequenceCounterLocal = syncBits * 2;
|
|
||||||
if (dataByteCounterLocal >= sendLenLocal)
|
|
||||||
{
|
|
||||||
signalLocal = noSignal;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
stateLocal = !stateLocal;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void IR_Encoder::enable()
|
|
||||||
{
|
|
||||||
bool exist = false;
|
|
||||||
IR_Encoder *current = IR_Encoder::head;
|
|
||||||
while (current != nullptr)
|
|
||||||
{
|
|
||||||
exist = (current == this);
|
|
||||||
if (exist) break;
|
|
||||||
current = current->next;
|
|
||||||
}
|
|
||||||
if (!exist)
|
|
||||||
{
|
|
||||||
if (IR_Encoder::head == nullptr)
|
|
||||||
{
|
|
||||||
IR_Encoder::head = this;
|
|
||||||
last = this;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
last->next = this;
|
|
||||||
last = this;
|
|
||||||
}
|
|
||||||
this->next = nullptr; // Указываем, что следующий за этим элементом — nullptr
|
|
||||||
}
|
|
||||||
pinMode(pin, OUTPUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
void IR_Encoder::disable()
|
|
||||||
{
|
|
||||||
IR_Encoder *current = IR_Encoder::head;
|
|
||||||
IR_Encoder *prev = nullptr;
|
|
||||||
|
|
||||||
while (current != nullptr)
|
|
||||||
{
|
|
||||||
if (current == this) break;
|
|
||||||
prev = current;
|
|
||||||
current = current->next;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current != nullptr) // Элемент найден в списке
|
|
||||||
{
|
|
||||||
if (prev != nullptr)
|
|
||||||
{
|
|
||||||
prev->next = current->next; // Убираем текущий элемент из списка
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
IR_Encoder::head = current->next; // Удаляемый элемент был первым
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current == last)
|
|
||||||
{
|
|
||||||
last = prev; // Если удаляется последний элемент, обновляем last
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pinMode(pin, INPUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
void IR_Encoder::setBlindDecoders(IR_DecoderRaw *decoders[], uint8_t count)
|
void IR_Encoder::setBlindDecoders(IR_DecoderRaw *decoders[], uint8_t count)
|
||||||
{
|
{
|
||||||
#if disablePairDec
|
#if disablePairDec
|
||||||
@ -310,23 +34,25 @@ void IR_Encoder::setBlindDecoders(IR_DecoderRaw *decoders[], uint8_t count)
|
|||||||
blindDecoders = decoders;
|
blindDecoders = decoders;
|
||||||
}
|
}
|
||||||
|
|
||||||
IR_Encoder::~IR_Encoder(){};
|
IR_Encoder::~IR_Encoder()
|
||||||
|
|
||||||
IR_SendResult IR_Encoder::sendData(uint16_t addrTo, uint8_t dataByte, bool needAccept)
|
|
||||||
{
|
{
|
||||||
return sendData(addrTo, &dataByte, 1, needAccept);
|
delete[] bitLow;
|
||||||
|
delete[] bitHigh;
|
||||||
|
};
|
||||||
|
|
||||||
|
void IR_Encoder::sendData(uint16_t addrTo, uint8_t dataByte, bool needAccept)
|
||||||
|
{
|
||||||
|
uint8_t *dataPtr = new uint8_t[1];
|
||||||
|
dataPtr[0] = dataByte;
|
||||||
|
sendData(addrTo, dataPtr, 1, needAccept);
|
||||||
|
delete[] dataPtr;
|
||||||
}
|
}
|
||||||
|
|
||||||
IR_SendResult IR_Encoder::sendData(uint16_t addrTo, uint8_t *data, uint8_t len, bool needAccept){
|
void IR_Encoder::sendData(uint16_t addrTo, uint8_t *data, uint8_t len, bool needAccept)
|
||||||
return sendDataFULL(id, addrTo, data, len, needAccept);
|
|
||||||
}
|
|
||||||
|
|
||||||
IR_SendResult IR_Encoder::sendDataFULL(uint16_t addrFrom, uint16_t addrTo, uint8_t *data, uint8_t len, bool needAccept)
|
|
||||||
{
|
{
|
||||||
if (len > bytePerPack)
|
if (len > bytePerPack)
|
||||||
{
|
{
|
||||||
Serial.println("IR Pack to big");
|
return;
|
||||||
return IR_SendResult(false, 0);
|
|
||||||
}
|
}
|
||||||
constexpr uint8_t dataStart = msgBytes + addrBytes + addrBytes;
|
constexpr uint8_t dataStart = msgBytes + addrBytes + addrBytes;
|
||||||
memset(sendBuffer, 0x00, dataByteSizeMax);
|
memset(sendBuffer, 0x00, dataByteSizeMax);
|
||||||
@ -339,8 +65,8 @@ IR_SendResult IR_Encoder::sendDataFULL(uint16_t addrFrom, uint16_t addrTo, uint8
|
|||||||
sendBuffer[0] = msgType;
|
sendBuffer[0] = msgType;
|
||||||
|
|
||||||
// addr_self
|
// addr_self
|
||||||
sendBuffer[1] = addrFrom >> 8 & 0xFF;
|
sendBuffer[1] = id >> 8 & 0xFF;
|
||||||
sendBuffer[2] = addrFrom & 0xFF;
|
sendBuffer[2] = id & 0xFF;
|
||||||
|
|
||||||
// addr_to
|
// addr_to
|
||||||
sendBuffer[3] = addrTo >> 8 & 0xFF;
|
sendBuffer[3] = addrTo >> 8 & 0xFF;
|
||||||
@ -355,19 +81,6 @@ IR_SendResult IR_Encoder::sendDataFULL(uint16_t addrFrom, uint16_t addrTo, uint8
|
|||||||
sendBuffer[packSize - crcBytes] = crc8(sendBuffer, 0, packSize - crcBytes, poly1) & 0xFF;
|
sendBuffer[packSize - crcBytes] = crc8(sendBuffer, 0, packSize - crcBytes, poly1) & 0xFF;
|
||||||
sendBuffer[packSize - crcBytes + 1] = crc8(sendBuffer, 0, packSize - crcBytes + 1, poly2) & 0xFF;
|
sendBuffer[packSize - crcBytes + 1] = crc8(sendBuffer, 0, packSize - crcBytes + 1, poly2) & 0xFF;
|
||||||
|
|
||||||
//* вывод итогового буфера
|
|
||||||
// Serial.print("IR SEND [len=");
|
|
||||||
// Serial.print(packSize);
|
|
||||||
// Serial.print("] : ");
|
|
||||||
// for (uint8_t i = 0; i < packSize; i++)
|
|
||||||
// {
|
|
||||||
// if (sendBuffer[i] < 0x10)
|
|
||||||
// Serial.print('0');
|
|
||||||
// Serial.print(sendBuffer[i], HEX);
|
|
||||||
// Serial.print(' ');
|
|
||||||
// }
|
|
||||||
// Serial.println();
|
|
||||||
|
|
||||||
// if (decPair != nullptr) {
|
// if (decPair != nullptr) {
|
||||||
// decPair->isWaitingAccept = ((msgType >> 5) & IR_MASK_MSG_TYPE == IR_MSG_DATA_ACCEPT);
|
// decPair->isWaitingAccept = ((msgType >> 5) & IR_MASK_MSG_TYPE == IR_MSG_DATA_ACCEPT);
|
||||||
// if (decPair->isWaitingAccept) {
|
// if (decPair->isWaitingAccept) {
|
||||||
@ -377,14 +90,9 @@ IR_SendResult IR_Encoder::sendDataFULL(uint16_t addrFrom, uint16_t addrTo, uint8
|
|||||||
|
|
||||||
// отправка
|
// отправка
|
||||||
rawSend(sendBuffer, packSize);
|
rawSend(sendBuffer, packSize);
|
||||||
|
|
||||||
// Возвращаем результат отправки
|
|
||||||
uint32_t sendTime = calculateSendTime(packSize);
|
|
||||||
return IR_SendResult(true, sendTime);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IR_Encoder::sendAccept(uint16_t addrTo, uint8_t customByte)
|
||||||
IR_SendResult IR_Encoder::sendAccept(uint16_t addrTo, uint8_t customByte)
|
|
||||||
{
|
{
|
||||||
constexpr uint8_t packsize = msgBytes + addrBytes + 1U + crcBytes;
|
constexpr uint8_t packsize = msgBytes + addrBytes + 1U + crcBytes;
|
||||||
memset(sendBuffer, 0x00, dataByteSizeMax);
|
memset(sendBuffer, 0x00, dataByteSizeMax);
|
||||||
@ -405,13 +113,9 @@ IR_SendResult IR_Encoder::sendAccept(uint16_t addrTo, uint8_t customByte)
|
|||||||
sendBuffer[5] = crc8(sendBuffer, 0, 5, poly2) & 0xFF;
|
sendBuffer[5] = crc8(sendBuffer, 0, 5, poly2) & 0xFF;
|
||||||
|
|
||||||
rawSend(sendBuffer, packsize);
|
rawSend(sendBuffer, packsize);
|
||||||
|
|
||||||
// Возвращаем результат отправки
|
|
||||||
uint32_t sendTime = calculateSendTime(packsize);
|
|
||||||
return IR_SendResult(true, sendTime);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IR_SendResult IR_Encoder::sendRequest(uint16_t addrTo)
|
void IR_Encoder::sendRequest(uint16_t addrTo)
|
||||||
{
|
{
|
||||||
constexpr uint8_t packsize = msgBytes + addrBytes + addrBytes + crcBytes;
|
constexpr uint8_t packsize = msgBytes + addrBytes + addrBytes + crcBytes;
|
||||||
memset(sendBuffer, 0x00, dataByteSizeMax);
|
memset(sendBuffer, 0x00, dataByteSizeMax);
|
||||||
@ -431,32 +135,27 @@ IR_SendResult IR_Encoder::sendRequest(uint16_t addrTo)
|
|||||||
sendBuffer[6] = crc8(sendBuffer, 0, 6, poly2) & 0xFF;
|
sendBuffer[6] = crc8(sendBuffer, 0, 6, poly2) & 0xFF;
|
||||||
|
|
||||||
rawSend(sendBuffer, packsize);
|
rawSend(sendBuffer, packsize);
|
||||||
|
|
||||||
// Возвращаем результат отправки
|
|
||||||
uint32_t sendTime = calculateSendTime(packsize);
|
|
||||||
return IR_SendResult(true, sendTime);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IR_SendResult IR_Encoder::sendBack(uint8_t data)
|
void IR_Encoder::sendBack(uint8_t data)
|
||||||
{
|
{
|
||||||
return _sendBack(false, 0, &data, 1);
|
_sendBack(false, 0, &data, 1);
|
||||||
}
|
}
|
||||||
|
void IR_Encoder::sendBack(uint8_t *data , uint8_t len)
|
||||||
IR_SendResult IR_Encoder::sendBack(uint8_t *data, uint8_t len)
|
|
||||||
{
|
{
|
||||||
return _sendBack(false, 0, data, len);
|
_sendBack(false, 0, data, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
IR_SendResult IR_Encoder::sendBackTo(uint16_t addrTo, uint8_t *data, uint8_t len)
|
void IR_Encoder::sendBackTo(uint16_t addrTo, uint8_t *data, uint8_t len)
|
||||||
{
|
{
|
||||||
return _sendBack(true, addrTo, data, len);
|
_sendBack(true, addrTo, data, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
IR_SendResult IR_Encoder::_sendBack(bool isAdressed, uint16_t addrTo, uint8_t *data, uint8_t len)
|
void IR_Encoder::_sendBack(bool isAdressed, uint16_t addrTo, uint8_t *data, uint8_t len)
|
||||||
{
|
{
|
||||||
if (len > bytePerPack)
|
if (len > bytePerPack)
|
||||||
{
|
{
|
||||||
return IR_SendResult(false, 0);
|
return;
|
||||||
}
|
}
|
||||||
memset(sendBuffer, 0x00, dataByteSizeMax);
|
memset(sendBuffer, 0x00, dataByteSizeMax);
|
||||||
uint8_t dataStart = msgBytes + addrBytes + (isAdressed ? addrBytes : 0);
|
uint8_t dataStart = msgBytes + addrBytes + (isAdressed ? addrBytes : 0);
|
||||||
@ -488,10 +187,6 @@ IR_SendResult IR_Encoder::_sendBack(bool isAdressed, uint16_t addrTo, uint8_t *d
|
|||||||
|
|
||||||
// отправка
|
// отправка
|
||||||
rawSend(sendBuffer, packSize);
|
rawSend(sendBuffer, packSize);
|
||||||
|
|
||||||
// Возвращаем результат отправки
|
|
||||||
uint32_t sendTime = calculateSendTime(packSize);
|
|
||||||
return IR_SendResult(true, sendTime);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void IR_Encoder::setDecoder_isSending()
|
void IR_Encoder::setDecoder_isSending()
|
||||||
@ -501,10 +196,6 @@ void IR_Encoder::setDecoder_isSending()
|
|||||||
for (uint8_t i = 0; i < decodersCount; i++)
|
for (uint8_t i = 0; i < decodersCount; i++)
|
||||||
{
|
{
|
||||||
blindDecoders[i]->isPairSending ^= id;
|
blindDecoders[i]->isPairSending ^= id;
|
||||||
// Serial.print("setDecoder_isSending() id = ");
|
|
||||||
// Serial.print(id);
|
|
||||||
// Serial.print(" isPairSending = ");
|
|
||||||
// Serial.println(blindDecoders[i]->isPairSending);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -516,35 +207,7 @@ void IR_Encoder::rawSend(uint8_t *ptr, uint8_t len)
|
|||||||
// TODO: Обработка повторной отправки
|
// TODO: Обработка повторной отправки
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверка на переполнение буфера
|
|
||||||
if (len > dataByteSizeMax)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (externalTxStartFn != nullptr)
|
|
||||||
{
|
|
||||||
if (externalTxBusyFn != nullptr && externalTxBusyFn(externalTxCtx))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark as sending and delegate actual signal output to external backend.
|
|
||||||
setDecoder_isSending();
|
|
||||||
sendLen = len;
|
|
||||||
isSending = true;
|
|
||||||
|
|
||||||
const bool ok = externalTxStartFn(externalTxCtx, this, ptr, len);
|
|
||||||
if (!ok)
|
|
||||||
{
|
|
||||||
isSending = false;
|
|
||||||
setDecoder_isSending();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
IR_Encoder::carrierResume();
|
|
||||||
// Serial.println("START");
|
|
||||||
setDecoder_isSending();
|
setDecoder_isSending();
|
||||||
|
|
||||||
// noInterrupts();
|
// noInterrupts();
|
||||||
@ -563,29 +226,17 @@ void IR_Encoder::rawSend(uint8_t *ptr, uint8_t len)
|
|||||||
state = HIGH;
|
state = HIGH;
|
||||||
|
|
||||||
currentBitSequence = bitHigh;
|
currentBitSequence = bitHigh;
|
||||||
|
isSending = true;
|
||||||
// interrupts();
|
// interrupts();
|
||||||
}
|
}
|
||||||
|
|
||||||
void IR_Encoder::isr()
|
void IR_Encoder::isr()
|
||||||
{
|
|
||||||
IR_Encoder *current = IR_Encoder::head;
|
|
||||||
while (current != nullptr)
|
|
||||||
{
|
|
||||||
current->_isr();
|
|
||||||
current = current->next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void IR_Encoder::_isr()
|
|
||||||
{
|
{
|
||||||
if (!isSending)
|
if (!isSending)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ir_out_virtual = !ir_out_virtual && state;
|
ir_out_virtual = !ir_out_virtual && state;
|
||||||
|
|
||||||
port->ODR &= ~(mask);
|
|
||||||
port->ODR |= mask & (ir_out_virtual ? (uint16_t)0xFFFF : (uint16_t)0x0000);
|
|
||||||
|
|
||||||
if (toggleCounter)
|
if (toggleCounter)
|
||||||
{
|
{
|
||||||
toggleCounter--;
|
toggleCounter--;
|
||||||
@ -600,10 +251,7 @@ void IR_Encoder::_isr()
|
|||||||
// сброс счетчиков
|
// сброс счетчиков
|
||||||
// ...
|
// ...
|
||||||
isSending = false;
|
isSending = false;
|
||||||
// Serial.println("STOP");
|
|
||||||
setDecoder_isSending();
|
setDecoder_isSending();
|
||||||
carrierStopPending = true;
|
|
||||||
// Serial.println();
|
|
||||||
return;
|
return;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -721,101 +369,37 @@ void IR_Encoder::addSync(bool *prev, bool *next)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t IR_Encoder::bitHigh[2] = {
|
void IR_Encoder::send_HIGH(bool prevBite)
|
||||||
(bitPauseTakts) * 2 - 1,
|
|
||||||
(bitActiveTakts) * 2 - 1};
|
|
||||||
uint8_t IR_Encoder::bitLow[2] = {
|
|
||||||
(bitPauseTakts / 2 + bitActiveTakts) * 2 - 1,
|
|
||||||
(bitPauseTakts)-1};
|
|
||||||
|
|
||||||
uint32_t IR_Encoder::calculateSendTime(uint8_t packSize) const
|
|
||||||
{
|
{
|
||||||
// Расчет времени отправки пакета в миллисекундах
|
|
||||||
|
// if (/* prevBite */1) {
|
||||||
// Время преамбулы: preambPulse * 2 фронта * bitTakts тактов
|
// meanderBlock(bitPauseTakts * 2, halfPeriod, LOW);
|
||||||
uint32_t preambTime = preambPulse * 2 * bitTakts;
|
// meanderBlock(bitActiveTakts, halfPeriod, HIGH);
|
||||||
|
// } else { // более короткий HIGH после нуля
|
||||||
// Время данных: количество бит * bitTakts тактов
|
// meanderBlock(bitTakts - (bitActiveTakts - bitPauseTakts), halfPeriod, LOW);
|
||||||
uint32_t dataTime = packSize * 8 * bitTakts;
|
// meanderBlock(bitActiveTakts - bitPauseTakts, halfPeriod, HIGH);
|
||||||
|
// }
|
||||||
// Время синхронизации: syncBits * 2 фронта * bitTakts тактов
|
|
||||||
uint32_t syncTime = syncBits * 2 * bitTakts;
|
|
||||||
|
|
||||||
// Общее время в тактах
|
|
||||||
uint32_t totalTakts = preambTime + dataTime + syncTime;
|
|
||||||
|
|
||||||
// Конвертируем в миллисекунды
|
|
||||||
// carrierPeriod - период несущей в микросекундах
|
|
||||||
// totalTakts * carrierPeriod / 1000 = время в миллисекундах
|
|
||||||
uint32_t sendTimeMs = (totalTakts * carrierPeriod) / 1000;
|
|
||||||
|
|
||||||
return sendTimeMs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Функции для тестирования времени отправки без фактической отправки
|
void IR_Encoder::send_LOW()
|
||||||
|
|
||||||
uint32_t IR_Encoder::testSendTime(uint16_t addrTo, uint8_t dataByte, bool needAccept) const
|
|
||||||
{
|
{
|
||||||
return testSendTime(addrTo, &dataByte, 1, needAccept);
|
// meanderBlock(bitPauseTakts, halfPeriod, LOW);
|
||||||
|
// meanderBlock(bitActiveTakts, halfPeriod, LOW);
|
||||||
|
// meanderBlock(bitPauseTakts, halfPeriod, HIGH);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t IR_Encoder::testSendTime(uint16_t addrTo, uint8_t *data, uint8_t len, bool needAccept) const
|
void IR_Encoder::send_EMPTY(uint8_t count)
|
||||||
{
|
{
|
||||||
return testSendTimeFULL(id, addrTo, data, len, needAccept);
|
// for (size_t i = 0; i < count * 2; i++) {
|
||||||
|
// meanderBlock((bitPauseTakts * 2 + bitActiveTakts), halfPeriod, prevPreambBit);
|
||||||
|
// prevPreambBit = !prevPreambBit;
|
||||||
|
// }
|
||||||
|
// meanderBlock(bitPauseTakts * 2 + bitActiveTakts, halfPeriod, 0); //TODO: Отодвинуть преамбулу
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t IR_Encoder::testSendTimeFULL(uint16_t addrFrom, uint16_t addrTo, uint8_t *data, uint8_t len, bool needAccept) const
|
uint8_t* IR_Encoder::bitHigh = new uint8_t[2]{
|
||||||
{
|
(bitPauseTakts) * 2 - 1,
|
||||||
if (len > bytePerPack)
|
(bitActiveTakts) * 2 - 1};
|
||||||
{
|
uint8_t* IR_Encoder::bitLow = new uint8_t[2]{
|
||||||
return 0; // Возвращаем 0 для недопустимого размера
|
(bitPauseTakts/2 + bitActiveTakts) * 2 - 1,
|
||||||
}
|
(bitPauseTakts) - 1};
|
||||||
|
|
||||||
uint8_t packSize = msgBytes + addrBytes + addrBytes + len + crcBytes;
|
|
||||||
return calculateSendTime(packSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t IR_Encoder::testSendAccept(uint16_t addrTo, uint8_t customByte) const
|
|
||||||
{
|
|
||||||
constexpr uint8_t packsize = msgBytes + addrBytes + 1U + crcBytes;
|
|
||||||
return calculateSendTime(packsize);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t IR_Encoder::testSendRequest(uint16_t addrTo) const
|
|
||||||
{
|
|
||||||
constexpr uint8_t packsize = msgBytes + addrBytes + addrBytes + crcBytes;
|
|
||||||
return calculateSendTime(packsize);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t IR_Encoder::testSendBack(uint8_t data) const
|
|
||||||
{
|
|
||||||
return testSendBack(false, 0, &data, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t IR_Encoder::testSendBack(uint8_t *data, uint8_t len) const
|
|
||||||
{
|
|
||||||
return testSendBack(false, 0, data, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t IR_Encoder::testSendBackTo(uint16_t addrTo, uint8_t *data, uint8_t len) const
|
|
||||||
{
|
|
||||||
return testSendBack(true, addrTo, data, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t IR_Encoder::testSendBack(bool isAdressed, uint16_t addrTo, uint8_t *data, uint8_t len) const
|
|
||||||
{
|
|
||||||
if (len > bytePerPack)
|
|
||||||
{
|
|
||||||
return 0; // Возвращаем 0 для недопустимого размера
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t packSize = msgBytes + addrBytes + (isAdressed ? addrBytes : 0) + min(uint8_t(1), len) + crcBytes;
|
|
||||||
return calculateSendTime(packSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
// uint8_t* IR_Encoder::bitHigh = new uint8_t[2]{
|
|
||||||
// (bitPauseTakts) * 2 - 0,
|
|
||||||
// (bitActiveTakts) * 2 - 0};
|
|
||||||
// uint8_t* IR_Encoder::bitLow = new uint8_t[2]{
|
|
||||||
// (bitPauseTakts/2 + bitActiveTakts) * 2 - 0,
|
|
||||||
// (bitPauseTakts) - 0};
|
|
||||||
113
IR_Encoder.h
113
IR_Encoder.h
@ -3,107 +3,73 @@
|
|||||||
|
|
||||||
// TODO: Отложенная передача после завершения приема
|
// TODO: Отложенная передача после завершения приема
|
||||||
|
|
||||||
// Структура для возврата результата отправки
|
|
||||||
struct IR_SendResult {
|
|
||||||
bool success; // Флаг успешности отправки
|
|
||||||
uint32_t sendTimeMs; // Время отправки пакета в миллисекундах
|
|
||||||
|
|
||||||
IR_SendResult(bool success = false, uint32_t sendTimeMs = 0)
|
|
||||||
: success(success), sendTimeMs(sendTimeMs) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class IR_DecoderRaw;
|
class IR_DecoderRaw;
|
||||||
class IR_Encoder : public IR_FOX
|
class IR_Encoder : IR_FOX
|
||||||
{
|
{
|
||||||
friend IR_DecoderRaw;
|
friend IR_DecoderRaw;
|
||||||
static IR_Encoder *head;
|
|
||||||
static IR_Encoder *last;
|
|
||||||
IR_Encoder *next;
|
|
||||||
public:
|
public:
|
||||||
static HardwareTimer* IR_Timer;
|
|
||||||
|
|
||||||
struct IR_TxGateRun {
|
|
||||||
uint16_t lenTicks; // number of timer ticks at carrierFrec*2
|
|
||||||
bool gate; // true: carrier enabled (output toggles), false: silent (output forced low)
|
|
||||||
};
|
|
||||||
|
|
||||||
using ExternalTxBusyFn = bool (*)(void *ctx);
|
|
||||||
using ExternalTxStartFn = bool (*)(void *ctx, IR_Encoder *enc, const uint8_t *packet, uint8_t len);
|
|
||||||
private:
|
private:
|
||||||
// uint16_t id; /// @brief Адрес передатчика
|
uint16_t id; /// @brief Адрес передатчика
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// @brief Класс передатчика
|
/// @brief Класс передатчика
|
||||||
/// @param addr Адрес передатчика
|
/// @param addr Адрес передатчика
|
||||||
/// @param pin Вывод передатчика
|
/// @param pin Вывод передатчика
|
||||||
|
/// @param tune Подстройка несущей частоты
|
||||||
/// @param decPair Приёмник, для которого отключается приём в момент передачи передатчиком
|
/// @param decPair Приёмник, для которого отключается приём в момент передачи передатчиком
|
||||||
IR_Encoder(uint8_t pin, uint16_t addr = 0, IR_DecoderRaw *decPair = nullptr, bool autoHandle = true);
|
IR_Encoder(uint16_t addr, IR_DecoderRaw *decPair = nullptr);
|
||||||
static void isr();
|
|
||||||
static void begin(HardwareTimer* timer, uint8_t channel, IRQn_Type IRQn, uint8_t priority, void(*isrCallback)() = nullptr);
|
|
||||||
/** Configure timer frequency for TX clock (carrierFrec*2) without attaching ISR. */
|
|
||||||
static void beginClockOnly(HardwareTimer *timer);
|
|
||||||
static HardwareTimer* get_IR_Timer();
|
|
||||||
/** Call from main loop/tick: if ISR requested carrier stop, pause timer here (not in ISR). */
|
|
||||||
static void tick();
|
|
||||||
|
|
||||||
/** Optional: register external TX backend (e.g. DMA driver). */
|
// static void timerSetup()
|
||||||
static void setExternalTxBackend(ExternalTxStartFn startFn, ExternalTxBusyFn busyFn, void *ctx);
|
// {
|
||||||
|
// // // TIMER2 Ini
|
||||||
|
// // uint8_t oldSREG = SREG; // Save global interupts settings
|
||||||
|
// // cli();
|
||||||
|
// // // DDRB |= (1 << PORTB3); //OC2A (17)
|
||||||
|
// // TCCR2A = 0;
|
||||||
|
// // TCCR2B = 0;
|
||||||
|
|
||||||
/** Called by external TX backend on actual end of transmission. */
|
// // // TCCR2A |= (1 << COM2A0); //Переключение состояния
|
||||||
void externalFinishSend();
|
|
||||||
|
|
||||||
/** Build RLE runs of carrier gate for a packet (no HW access). */
|
// // TCCR2A |= (1 << WGM21); // Clear Timer On Compare (Сброс по совпадению)
|
||||||
static size_t buildGateRuns(const uint8_t *packet, uint8_t len, IR_TxGateRun *outRuns, size_t maxRuns);
|
// // TCCR2B |= (1 << CS20); // Предделитель 1
|
||||||
|
// // TIMSK2 |= (1 << OCIE2A); // Прерывание по совпадению
|
||||||
|
|
||||||
void enable();
|
// // OCR2A = /* 465 */ ((F_CPU / (38000 * 2)) - 2); // 38кГц
|
||||||
void disable();
|
|
||||||
|
// // SREG = oldSREG; // Return interrupt settings
|
||||||
|
|
||||||
|
|
||||||
|
// }
|
||||||
|
// static void timerOFFSetup()
|
||||||
|
// {
|
||||||
|
// TIMSK2 &= ~(1 << OCIE2A); // Прерывание по совпадению выкл
|
||||||
|
// }
|
||||||
|
|
||||||
void setBlindDecoders(IR_DecoderRaw *decoders[], uint8_t count);
|
void setBlindDecoders(IR_DecoderRaw *decoders[], uint8_t count);
|
||||||
void rawSend(uint8_t *ptr, uint8_t len);
|
void rawSend(uint8_t *ptr, uint8_t len);
|
||||||
|
|
||||||
IR_SendResult sendData(uint16_t addrTo, uint8_t dataByte, bool needAccept = false);
|
void sendData(uint16_t addrTo, uint8_t dataByte, bool needAccept = false);
|
||||||
IR_SendResult sendData(uint16_t addrTo, uint8_t *data = nullptr, uint8_t len = 0, bool needAccept = false);
|
void sendData(uint16_t addrTo, uint8_t *data = nullptr, uint8_t len = 0, bool needAccept = false);
|
||||||
IR_SendResult sendDataFULL(uint16_t addrFrom, uint16_t addrTo, uint8_t *data = nullptr, uint8_t len = 0, bool needAccept = false);
|
|
||||||
|
|
||||||
|
void sendAccept(uint16_t addrTo, uint8_t customByte = 0);
|
||||||
|
void sendRequest(uint16_t addrTo);
|
||||||
|
|
||||||
IR_SendResult sendAccept(uint16_t addrTo, uint8_t customByte = 0);
|
void sendBack(uint8_t data);
|
||||||
IR_SendResult sendRequest(uint16_t addrTo);
|
void sendBack(uint8_t *data = nullptr, uint8_t len = 0);
|
||||||
|
void sendBackTo(uint16_t addrTo, uint8_t *data = nullptr, uint8_t len = 0);
|
||||||
IR_SendResult sendBack(uint8_t data);
|
|
||||||
IR_SendResult sendBack(uint8_t *data = nullptr, uint8_t len = 0);
|
|
||||||
IR_SendResult sendBackTo(uint16_t addrTo, uint8_t *data = nullptr, uint8_t len = 0);
|
|
||||||
|
|
||||||
// Функция для тестирования времени отправки без фактической отправки
|
|
||||||
uint32_t testSendTime(uint16_t addrTo, uint8_t dataByte, bool needAccept = false) const;
|
|
||||||
uint32_t testSendTime(uint16_t addrTo, uint8_t *data = nullptr, uint8_t len = 0, bool needAccept = false) const;
|
|
||||||
uint32_t testSendTimeFULL(uint16_t addrFrom, uint16_t addrTo, uint8_t *data = nullptr, uint8_t len = 0, bool needAccept = false) const;
|
|
||||||
uint32_t testSendAccept(uint16_t addrTo, uint8_t customByte = 0) const;
|
|
||||||
uint32_t testSendRequest(uint16_t addrTo) const;
|
|
||||||
uint32_t testSendBack(uint8_t data) const;
|
|
||||||
uint32_t testSendBack(uint8_t *data = nullptr, uint8_t len = 0) const;
|
|
||||||
uint32_t testSendBackTo(uint16_t addrTo, uint8_t *data = nullptr, uint8_t len = 0) const;
|
|
||||||
|
|
||||||
inline bool isBusy() const { return isSending;}
|
|
||||||
|
|
||||||
|
void isr();
|
||||||
|
|
||||||
~IR_Encoder();
|
~IR_Encoder();
|
||||||
volatile bool ir_out_virtual;
|
volatile bool ir_out_virtual;
|
||||||
|
|
||||||
void _isr();
|
|
||||||
private:
|
private:
|
||||||
static volatile bool carrierStopPending;
|
void _sendBack(bool isAdressed, uint16_t addrTo, uint8_t *data, uint8_t len);
|
||||||
static void carrierResume();
|
|
||||||
static void carrierPauseIfIdle();
|
|
||||||
|
|
||||||
static ExternalTxStartFn externalTxStartFn;
|
|
||||||
static ExternalTxBusyFn externalTxBusyFn;
|
|
||||||
static void *externalTxCtx;
|
|
||||||
IR_SendResult _sendBack(bool isAdressed, uint16_t addrTo, uint8_t *data, uint8_t len);
|
|
||||||
|
|
||||||
void setDecoder_isSending();
|
void setDecoder_isSending();
|
||||||
void sendByte(uint8_t byte, bool *prev, bool LOW_FIRST);
|
void sendByte(uint8_t byte, bool *prev, bool LOW_FIRST);
|
||||||
void addSync(bool *prev, bool *next);
|
void addSync(bool *prev, bool *next);
|
||||||
uint32_t calculateSendTime(uint8_t packSize) const;
|
|
||||||
uint32_t testSendBack(bool isAdressed, uint16_t addrTo, uint8_t *data, uint8_t len) const;
|
|
||||||
void send_HIGH(bool = 1);
|
void send_HIGH(bool = 1);
|
||||||
void send_LOW();
|
void send_LOW();
|
||||||
void send_EMPTY(uint8_t count);
|
void send_EMPTY(uint8_t count);
|
||||||
@ -141,8 +107,9 @@ private:
|
|||||||
uint8_t low;
|
uint8_t low;
|
||||||
uint8_t high;
|
uint8_t high;
|
||||||
};
|
};
|
||||||
static uint8_t bitHigh[2];
|
static uint8_t *bitHigh;
|
||||||
static uint8_t bitLow[2];
|
static uint8_t *bitLow;
|
||||||
uint8_t *currentBitSequence = bitLow;
|
uint8_t *currentBitSequence = bitLow;
|
||||||
volatile SignalPart signal;
|
volatile SignalPart signal;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,33 +0,0 @@
|
|||||||
#include "IR_config.h"
|
|
||||||
|
|
||||||
void IR_FOX::setPin(uint8_t pin){
|
|
||||||
this->pin = pin;
|
|
||||||
port = digitalPinToPort(pin);
|
|
||||||
mask = digitalPinToBitMask(pin);
|
|
||||||
}
|
|
||||||
|
|
||||||
void IR_FOX::checkAddressRuleApply(uint16_t address, uint16_t id, bool &flag)
|
|
||||||
{
|
|
||||||
flag = false;
|
|
||||||
flag |= id == 0;
|
|
||||||
flag |= address == id;
|
|
||||||
flag |= address >= IR_Broadcast;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t IR_FOX::crc8(uint8_t *data, uint8_t start, uint8_t end, uint8_t poly)
|
|
||||||
{ // TODO: сделать возможность межбайтовой проверки
|
|
||||||
uint8_t crc = 0xff;
|
|
||||||
size_t i, j;
|
|
||||||
for (i = start; i < end; i++)
|
|
||||||
{
|
|
||||||
crc ^= data[i];
|
|
||||||
for (j = 0; j < 8; j++)
|
|
||||||
{
|
|
||||||
if ((crc & 0x80) != 0)
|
|
||||||
crc = (uint8_t)((crc << 1) ^ poly);
|
|
||||||
else
|
|
||||||
crc <<= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return crc;
|
|
||||||
};
|
|
||||||
229
IR_config.h
229
IR_config.h
@ -1,39 +1,25 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <list>
|
|
||||||
// #define IRDEBUG_INFO
|
#define IRDEBUG_INFO
|
||||||
/*//////////////////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
Для работы в паре положить декодер в энкодер
|
Для работы в паре положить декодер в энкодер
|
||||||
|
|
||||||
*/
|
*/// Адресация с 1 до 65 499
|
||||||
// Адресация с 1 до 65 499
|
#define IR_Broadcast 65000 // 65 500 ~ 65 535 - широковещательные пакеты (всем), возможно разделить на 35 типов
|
||||||
#define IR_Broadcast 65000 // 65 500 ~ 65 535 - широковещательные пакеты (всем)
|
|
||||||
/*
|
/*
|
||||||
*Адресное пространство:
|
Адрес 0 запрещен и зарезервирован под NULL, либо тесты
|
||||||
Адрес 0 запрещен и зарезервирован под NULL, либо тесты
|
IR_MSG_ACCEPT с адреса 0 воспринимается всеми устройствами
|
||||||
IR_MSG_ACCEPT с адреса 0 воспринимается всеми устройствами
|
|
||||||
*/
|
|
||||||
//**** Контрольные точки ******
|
|
||||||
#define IR_MAX_ADDR_CPU 63999
|
|
||||||
#define IR_MIN_ADDR_CPU 32000
|
|
||||||
|
|
||||||
// //***** Группы машинок ********
|
|
||||||
// #define IR_MAX_CAR_GROUP 31999
|
|
||||||
// #define IR_MIN_CAR_GROUP 30000
|
|
||||||
|
|
||||||
// //********** FREE *************
|
Адресное пространство:
|
||||||
// #define IR_MAX_FREE 31999
|
|
||||||
// #define IR_MIN_FREE 2000
|
Излучатели контрольных точек: 1000 ~ 1999
|
||||||
|
Излучатели без обратной связиЖ 2000 ~ 2999
|
||||||
|
Излучатели светофоров: 3000 ~ 3999
|
||||||
|
|
||||||
//********* Машинки ***********
|
|
||||||
#define IR_MAX_CAR 31999
|
|
||||||
#define IR_MIN_CAR 1
|
|
||||||
|
|
||||||
//***** Пульты управления *****
|
|
||||||
#define IR_MAX_CONTROLLER 64999
|
|
||||||
#define IR_MIN_CONTROLLER 64000
|
|
||||||
/*
|
|
||||||
|
|
||||||
/```````````````````````````````````````````````` data pack `````````````````````````````````````````````\
|
/```````````````````````````````````````````````` data pack `````````````````````````````````````````````\
|
||||||
|
|
||||||
@ -52,61 +38,61 @@ msg type:
|
|||||||
// | 01234567 |
|
// | 01234567 |
|
||||||
// ----------
|
// ----------
|
||||||
// | xxx..... | = тип сообщения
|
// | xxx..... | = тип сообщения
|
||||||
// | ...xxxxx | = длина (максимум 31 бита) - не больше 24 байт на тело пакета
|
// | ...xxxxx | = длина (максимум 31 бита)
|
||||||
// ---------- */
|
// ---------- */
|
||||||
#define IR_MSG_BACK 0U // | 000...... | = Задний сигнал машинки
|
#define IR_MSG_BACK 0U // | 000...... | = Задний сигнал машинки
|
||||||
#define IR_MSG_ACCEPT 1U // | 001..... | = подтверждение
|
#define IR_MSG_ACCEPT 1U // | 001..... | = подтверждение
|
||||||
#define IR_MSG_REQUEST 2U // | 010..... | = запрос
|
#define IR_MSG_REQUEST 2U // | 010..... | = запрос
|
||||||
// #define IR_MSG_ 3U // | 011..... | = ??
|
#define IR_MSG_ 3U // | 011..... | = ??
|
||||||
#define IR_MSG_BACK_TO 4U // | 100..... | = Задний сигнал машинки c адресацией
|
#define IR_MSG_BACK_TO 4U // | 100..... | = Задний сигнал машинки c адресацией
|
||||||
// #define IR_MSG_ 5U // | 101..... | = ??
|
#define IR_MSG_ 5U // | 101..... | = ??
|
||||||
#define IR_MSG_DATA_NOACCEPT 6U // | 110..... | = данные, не требующие подтверждения
|
#define IR_MSG_DATA_NOACCEPT 6U // | 110..... | = данные, не требующие подтверждения
|
||||||
#define IR_MSG_DATA_ACCEPT 7U // | 111..... | = данные требующие подтверждения
|
#define IR_MSG_DATA_ACCEPT 7U // | 111..... | = данные требующие подтверждения
|
||||||
; /* // ----------
|
;/* // ----------
|
||||||
|
|
||||||
/``````````````````````````````` подтверждение `````````````````````````````\ /``````````````````````````````````````` запрос ``````````````````````````````````\
|
/``````````````````````````````` подтверждение `````````````````````````````\ /``````````````````````````````````````` запрос ``````````````````````````````````\
|
||||||
|
|
||||||
{``````````} [````````````````````````] [``````````````````] [``````````````] {``````````} [````````````````````````] [````````````````````````] [``````````````]
|
{``````````} [````````````````````````] [``````````````````] [``````````````] {``````````} [````````````````````````] [````````````````````````] [``````````````]
|
||||||
{ msg type } [ addr_from uint16_t ] [=== customByte ===] [ CRC Bytes ] { msg type } [ addr_from uint16_t ] [ addr_to uint16_t ] [ CRC Bytes ]
|
{ msg type } [ addr_from uint16_t ] [=== customByte ===] [ CRC Bytes ] { msg type } [ addr_from uint16_t ] [ addr_to uint16_t ] [ CRC Bytes ]
|
||||||
{..........} [........................] [..................] [..............] {..........} [........................] [........................] [..............]
|
{..........} [........................] [..................] [..............] {..........} [........................] [........................] [..............]
|
||||||
|
|
||||||
{ 001..... } [addr_from_H][addr_from_L] [=== customByte ===] [ crc1 ][ crc2 ] { 010..... } [addr_from_H][addr_from_L] [addr_from_H][addr_from_L] [ crc1 ][ crc2 ]
|
{ 001..... } [addr_from_H][addr_from_L] [=== customByte ===] [ crc1 ][ crc2 ] { 010..... } [addr_from_H][addr_from_L] [addr_from_H][addr_from_L] [ crc1 ][ crc2 ]
|
||||||
| 0 1 2 3 4 5 | 0 1 2 3 4 5 6
|
| 0 1 2 3 4 5 | 0 1 2 3 4 5 6
|
||||||
\________________________________________________________________/ | \_____________________________________________________________________/ |
|
\________________________________________________________________/ | \_____________________________________________________________________/ |
|
||||||
| | | |
|
| | | |
|
||||||
\________________________________________________________________________/ \_____________________________________________________________________________/
|
\________________________________________________________________________/ \_____________________________________________________________________________/
|
||||||
|
|
||||||
customByte - контрольная сумма принятых данных по poly1
|
customByte - контрольная сумма принятых данных по poly1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/`````````````````````` Задний сигнал машинки без адресации ``````````````````````\
|
/`````````````````````` Задний сигнал машинки без адресации ``````````````````````\
|
||||||
|
|
||||||
{``````````} [````````````````````````] [````````````````````````] [``````````````]
|
{``````````} [````````````````````````] [````````````````````````] [``````````````]
|
||||||
{ msg type } [ addr_from uint16_t ] [====== data bytes ======] [ CRC Bytes ]
|
{ msg type } [ addr_from uint16_t ] [====== data bytes ======] [ CRC Bytes ]
|
||||||
{..........} [........................] [........................] [..............]
|
{..........} [........................] [........................] [..............]
|
||||||
|
|
||||||
{ 0000xxxx } [addr_from_H][addr_from_L] [data_H][data_n..][data_L] [ crc1 ][ crc2 ]
|
{ 0000xxxx } [addr_from_H][addr_from_L] [data_H][data_n..][data_L] [ crc1 ][ crc2 ]
|
||||||
| 0 1 2 3 | |
|
| 0 1 2 3 | |
|
||||||
\_____________________________________________________________________/ |
|
\_____________________________________________________________________/ |
|
||||||
| |
|
| |
|
||||||
\_____________________________________________________________________________/
|
\_____________________________________________________________________________/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/```````````````````````````````````` Задний сигнал машинки с адресацией ````````````````````````````````````\
|
/```````````````````````````````````` Задний сигнал машинки с адресацией ````````````````````````````````````\
|
||||||
|
|
||||||
{``````````} [````````````````````````] [````````````````````````] [````````````````````````] [``````````````]
|
{``````````} [````````````````````````] [````````````````````````] [````````````````````````] [``````````````]
|
||||||
{ msg type } [ addr_from uint16_t ] [ addr_to uint16_t ] [====== data bytes ======] [ CRC Bytes ]
|
{ msg type } [ addr_from uint16_t ] [ addr_to uint16_t ] [====== data bytes ======] [ CRC Bytes ]
|
||||||
{..........} [........................] [........................] [........................] [..............]
|
{..........} [........................] [........................] [........................] [..............]
|
||||||
|
|
||||||
{ 0001xxxx } [addr_from_H][addr_from_L] [addr_from_H][addr_from_L] [data_H][data_n..][data_L] [ crc1 ][ crc2 ]
|
{ 0001xxxx } [addr_from_H][addr_from_L] [addr_from_H][addr_from_L] [data_H][data_n..][data_L] [ crc1 ][ crc2 ]
|
||||||
| 0 1 2 3 4 5 | |
|
| 0 1 2 3 4 5 | |
|
||||||
\________________________________________________________________________________________________/ |
|
\________________________________________________________________________________________________/ |
|
||||||
| |
|
| |
|
||||||
\________________________________________________________________________________________________________/
|
\________________________________________________________________________________________________________/
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define IR_MASK_MSG_TYPE 0b00000111
|
#define IR_MASK_MSG_TYPE 0b00000111
|
||||||
#define IR_MASK_MSG_INFO 0b00011111
|
#define IR_MASK_MSG_INFO 0b00011111
|
||||||
@ -115,14 +101,14 @@ msg type:
|
|||||||
/////////////////////////////////////////////////////////////////////////////////////*/
|
/////////////////////////////////////////////////////////////////////////////////////*/
|
||||||
typedef uint16_t crc_t;
|
typedef uint16_t crc_t;
|
||||||
|
|
||||||
// #define BRUTEFORCE_CHECK // Перепроверяет пакет на 1 битные ошибки //TODO: зависает
|
#define BRUTEFORCE_CHECK // Перепроверяет пакет на 1 битные ошибки //TODO: зависает
|
||||||
#define bytePerPack (31) // колличество байтов в пакете
|
#define bytePerPack 16 // колличество байтов в пакете
|
||||||
#ifndef freeFrec
|
#ifndef freeFrec
|
||||||
#define freeFrec false
|
#define freeFrec false
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef subBufferSize
|
#ifndef subBufferSize
|
||||||
#define subBufferSize 250 // Буфер для складирования фронтов, пока их не обработают (передатчик)
|
#define subBufferSize 5 //Буфер для складирования фронтов, пока их не обработают (передатчик)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define preambPulse 3
|
#define preambPulse 3
|
||||||
@ -131,40 +117,32 @@ typedef uint16_t crc_t;
|
|||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
#define bitPerByte 8U // Колличество бит в байте
|
#define bitPerByte 8U // Колличество бит в байте
|
||||||
#define addrBytes 2
|
#define addrBytes 2
|
||||||
#define msgBytes 1
|
#define msgBytes 1
|
||||||
#define crcBytes 2
|
#define crcBytes 2
|
||||||
#define poly1 0x31
|
#define poly1 0x31
|
||||||
#define poly2 0x8C
|
#define poly2 0x8C
|
||||||
#define syncBits 3U // количество битов синхронизации
|
#define syncBits 3U // количество битов синхронизации
|
||||||
|
|
||||||
#define dataByteSizeMax (msgBytes + addrBytes + addrBytes + bytePerPack + crcBytes)
|
#define dataByteSizeMax (msgBytes + addrBytes + addrBytes + bytePerPack + crcBytes)
|
||||||
|
|
||||||
#define preambFronts (preambPulse * 2) // количество фронтов преамбулы (Приём)
|
#define preambFronts (preambPulse*2) // количество фронтов преамбулы (Приём)
|
||||||
#define preambToggle ((bitPauseTakts * 2 + bitActiveTakts) * 2 - 1) // колличество переключений преамбулы (Передача)
|
#define preambToggle ((bitPauseTakts * 2 + bitActiveTakts) * 2 - 1) // колличество переключений преамбулы (Передача)
|
||||||
|
|
||||||
#define carrierFrec 38000U // частота несущей (Приём/Передача)
|
#define carrierFrec 38000U // частота несущей (Приём/Передача)
|
||||||
#define carrierPeriod (1000000U / carrierFrec) // период несущей в us (Приём)
|
#define carrierPeriod (1000000U/carrierFrec) // период несущей в us (Приём)
|
||||||
|
|
||||||
// В процессе работы значения будут отклонятся в соответствии с предыдущим битом
|
// В процессе работы значения будут отклонятся в соответствии с предыдущим битом
|
||||||
#define bitActiveTakts 25U // длительность высокого уровня в тактах
|
#define bitActiveTakts 25U // длительность высокого уровня в тактах
|
||||||
#define bitPauseTakts 12U // длительность низкого уровня в тактах
|
#define bitPauseTakts 12U // длительность низкого уровня в тактах
|
||||||
|
|
||||||
#define bitTakts (bitActiveTakts + bitPauseTakts) // Общая длительность бита в тактах
|
#define bitTakts (bitActiveTakts+bitPauseTakts) // Общая длительность бита в тактах
|
||||||
#define bitTime (bitTakts * carrierPeriod) // Общая длительность бита
|
#define bitTime (bitTakts*carrierPeriod) // Общая длительность бита
|
||||||
#define tolerance 300U
|
#define tolerance 300U
|
||||||
|
class IR_FOX {
|
||||||
constexpr uint16_t test_all_Time = bitTime;
|
|
||||||
constexpr uint16_t test_all_Takts = bitTakts * 2;
|
|
||||||
constexpr uint16_t test_hi = ((bitPauseTakts) * 2 - 0) + ((bitActiveTakts) * 2 - 0);
|
|
||||||
constexpr uint16_t test_low = ((bitPauseTakts / 2 + bitActiveTakts) * 2 - 0) + ((bitPauseTakts)-0);
|
|
||||||
|
|
||||||
class IR_FOX
|
|
||||||
{
|
|
||||||
public:
|
public:
|
||||||
struct PackOffsets
|
struct PackOffsets {
|
||||||
{
|
|
||||||
uint8_t msgOffset;
|
uint8_t msgOffset;
|
||||||
uint8_t addrFromOffset;
|
uint8_t addrFromOffset;
|
||||||
uint8_t addrToOffset;
|
uint8_t addrToOffset;
|
||||||
@ -172,43 +150,54 @@ public:
|
|||||||
uint8_t crcOffset;
|
uint8_t crcOffset;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ErrorsStruct
|
struct ErrorsStruct {
|
||||||
{
|
|
||||||
uint8_t lowSignal = 0;
|
uint8_t lowSignal = 0;
|
||||||
uint8_t highSignal = 0;
|
uint8_t highSignal = 0;
|
||||||
uint8_t other = 0;
|
uint8_t other = 0;
|
||||||
|
|
||||||
void reset()
|
void reset() {
|
||||||
{
|
|
||||||
lowSignal = 0;
|
lowSignal = 0;
|
||||||
highSignal = 0;
|
highSignal = 0;
|
||||||
other = 0;
|
other = 0;
|
||||||
}
|
}
|
||||||
uint16_t all() { return lowSignal + highSignal + other; }
|
uint16_t all() { return lowSignal + highSignal + other; }
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PackInfo
|
struct PackInfo {
|
||||||
{
|
uint8_t* buffer = nullptr;
|
||||||
uint8_t *buffer = nullptr;
|
|
||||||
uint8_t packSize = 0;
|
uint8_t packSize = 0;
|
||||||
uint16_t crc = 0;
|
uint16_t crc = 0;
|
||||||
ErrorsStruct err;
|
ErrorsStruct err;
|
||||||
uint16_t rTime = 0;
|
uint16_t rTime = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline uint16_t getId() const { return id; }
|
static void checkAddressRuleApply(uint16_t address, uint16_t id, bool& flag) {
|
||||||
inline void setId(uint16_t id) { this->id = id; }
|
flag = false;
|
||||||
static void checkAddressRuleApply(uint16_t address, uint16_t id, bool &flag);
|
flag |= id == 0;
|
||||||
void setPin(uint8_t pin);
|
flag |= address == id;
|
||||||
inline uint8_t getPin() { return pin; };
|
flag |= address >= IR_Broadcast;
|
||||||
inline GPIO_TypeDef *getPort() const { return port; }
|
}
|
||||||
inline uint16_t getPinMask() const { return mask; }
|
|
||||||
|
uint16_t getId() { return id; }
|
||||||
|
void setId(uint16_t id) { this->id = id; }
|
||||||
|
|
||||||
protected:
|
|
||||||
uint16_t id;
|
uint16_t id;
|
||||||
uint8_t pin;
|
protected:
|
||||||
GPIO_TypeDef *port;
|
|
||||||
uint16_t mask;
|
|
||||||
ErrorsStruct errors;
|
ErrorsStruct errors;
|
||||||
uint8_t crc8(uint8_t *data, uint8_t start, uint8_t end, uint8_t poly);
|
uint8_t crc8(uint8_t* data, uint8_t start, uint8_t end, uint8_t poly) { //TODO: сделать возможность межбайтовой проверки
|
||||||
|
uint8_t crc = 0xff;
|
||||||
|
size_t i, j;
|
||||||
|
for (i = start; i < end; i++) {
|
||||||
|
crc ^= data[i];
|
||||||
|
for (j = 0; j < 8; j++) {
|
||||||
|
if ((crc & 0x80) != 0)
|
||||||
|
crc = (uint8_t)((crc << 1) ^ poly);
|
||||||
|
else
|
||||||
|
crc <<= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
107
PacketTypes.cpp
107
PacketTypes.cpp
@ -1,107 +0,0 @@
|
|||||||
#include "PacketTypes.h"
|
|
||||||
|
|
||||||
namespace PacketTypes
|
|
||||||
{
|
|
||||||
bool BasePack::checkAddress() { return true; };
|
|
||||||
void BasePack::set(IR_FOX::PackInfo *packInfo, uint16_t id)
|
|
||||||
{
|
|
||||||
this->packInfo = packInfo;
|
|
||||||
this->id = id;
|
|
||||||
|
|
||||||
if (checkAddress())
|
|
||||||
{
|
|
||||||
isAvailable = true;
|
|
||||||
isRawAvailable = true;
|
|
||||||
#ifdef IRDEBUG_INFO
|
|
||||||
Serial.print(" OK ");
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
isRawAvailable = true;
|
|
||||||
#ifdef IRDEBUG_INFO
|
|
||||||
Serial.print(" NOT-OK ");
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t BasePack::_getAddrFrom(BasePack *obj)
|
|
||||||
{
|
|
||||||
return (obj->packInfo->buffer[obj->addressFromOffset] << 8) | obj->packInfo->buffer[obj->addressFromOffset + 1];
|
|
||||||
};
|
|
||||||
uint16_t BasePack::_getAddrTo(BasePack *obj)
|
|
||||||
{
|
|
||||||
return (obj->packInfo->buffer[obj->addressToOffset] << 8) | obj->packInfo->buffer[obj->addressToOffset + 1];
|
|
||||||
};
|
|
||||||
|
|
||||||
uint8_t BasePack::_getDataSize(BasePack *obj)
|
|
||||||
{
|
|
||||||
return obj->packInfo->packSize - crcBytes - obj->DataOffset;
|
|
||||||
};
|
|
||||||
uint8_t *BasePack::_getDataPrt(BasePack *obj)
|
|
||||||
{
|
|
||||||
return obj->packInfo->buffer + obj->DataOffset;
|
|
||||||
};
|
|
||||||
uint8_t BasePack::_getDataRawSize(BasePack *obj)
|
|
||||||
{
|
|
||||||
return obj->packInfo->packSize;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool BasePack::available()
|
|
||||||
{
|
|
||||||
if (isAvailable)
|
|
||||||
{
|
|
||||||
isAvailable = false;
|
|
||||||
isRawAvailable = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
bool BasePack::availableRaw()
|
|
||||||
{
|
|
||||||
if (isRawAvailable)
|
|
||||||
{
|
|
||||||
isRawAvailable = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
bool Data::checkAddress()
|
|
||||||
{
|
|
||||||
bool ret;
|
|
||||||
IR_FOX::checkAddressRuleApply(getAddrTo(), this->id, ret);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DataBack::checkAddress()
|
|
||||||
{
|
|
||||||
bool ret;
|
|
||||||
if (getMsgType() == IR_MSG_BACK_TO)
|
|
||||||
{
|
|
||||||
DataOffset = 5;
|
|
||||||
IR_FOX::checkAddressRuleApply((packInfo->buffer[addressToOffset] << 8) | packInfo->buffer[addressToOffset + 1], this->id, ret);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
DataOffset = 3;
|
|
||||||
ret = true;
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Accept::checkAddress() { return true; }
|
|
||||||
|
|
||||||
bool Request::checkAddress()
|
|
||||||
{
|
|
||||||
bool ret;
|
|
||||||
IR_FOX::checkAddressRuleApply(getAddrTo(), this->id, ret);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
308
PacketTypes.h
308
PacketTypes.h
@ -21,28 +21,86 @@ namespace PacketTypes
|
|||||||
IR_FOX::PackInfo *packInfo;
|
IR_FOX::PackInfo *packInfo;
|
||||||
uint16_t id;
|
uint16_t id;
|
||||||
|
|
||||||
virtual bool checkAddress();
|
virtual bool checkAddress() { return true; };
|
||||||
void set(IR_FOX::PackInfo *packInfo, uint16_t id);
|
void set(IR_FOX::PackInfo *packInfo, uint16_t id)
|
||||||
|
{
|
||||||
|
this->packInfo = packInfo;
|
||||||
|
this->id = id;
|
||||||
|
|
||||||
static uint16_t _getAddrFrom(BasePack *obj);
|
if (checkAddress())
|
||||||
static uint16_t _getAddrTo(BasePack *obj);
|
{
|
||||||
static uint8_t _getDataSize(BasePack *obj);
|
isAvailable = true;
|
||||||
static uint8_t *_getDataPrt(BasePack *obj);
|
isRawAvailable = true;
|
||||||
static uint8_t _getDataRawSize(BasePack *obj);
|
#ifdef IRDEBUG_INFO
|
||||||
|
Serial.print(" OK ");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
isRawAvailable = true;
|
||||||
|
#ifdef IRDEBUG_INFO
|
||||||
|
Serial.print(" NOT-OK ");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint16_t _getAddrFrom(BasePack *obj)
|
||||||
|
{
|
||||||
|
return (obj->packInfo->buffer[obj->addressFromOffset] << 8) | obj->packInfo->buffer[obj->addressFromOffset + 1];
|
||||||
|
};
|
||||||
|
static uint16_t _getAddrTo(BasePack *obj)
|
||||||
|
{
|
||||||
|
return (obj->packInfo->buffer[obj->addressToOffset] << 8) | obj->packInfo->buffer[obj->addressToOffset + 1];
|
||||||
|
};
|
||||||
|
|
||||||
|
static uint8_t _getDataSize(BasePack *obj)
|
||||||
|
{
|
||||||
|
return obj->packInfo->packSize - crcBytes - obj->DataOffset;
|
||||||
|
};
|
||||||
|
static uint8_t *_getDataPrt(BasePack *obj)
|
||||||
|
{
|
||||||
|
return obj->packInfo->buffer + obj->DataOffset;
|
||||||
|
};
|
||||||
|
static uint8_t _getDataRawSize(BasePack *obj)
|
||||||
|
{
|
||||||
|
return obj->packInfo->packSize;
|
||||||
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool available();
|
bool available()
|
||||||
bool availableRaw();
|
{
|
||||||
|
if (isAvailable)
|
||||||
inline uint8_t getMsgInfo() { return packInfo->buffer[0] & IR_MASK_MSG_INFO; };
|
{
|
||||||
inline uint8_t getMsgType() { return (packInfo->buffer[0] >> 5) & IR_MASK_MSG_TYPE; };
|
isAvailable = false;
|
||||||
inline uint8_t getMsgRAW() { return packInfo->buffer[0]; };
|
isRawAvailable = false;
|
||||||
inline uint16_t getErrorCount() { return packInfo->err.all(); };
|
return true;
|
||||||
inline uint8_t getErrorLowSignal() { return packInfo->err.lowSignal; };
|
}
|
||||||
inline uint8_t getErrorHighSignal() { return packInfo->err.highSignal; };
|
else
|
||||||
inline uint8_t getErrorOther() { return packInfo->err.other; };
|
{
|
||||||
inline uint16_t getTunerTime() { return packInfo->rTime; };
|
return false;
|
||||||
inline uint8_t *getDataRawPtr() { return packInfo->buffer; };
|
}
|
||||||
|
};
|
||||||
|
bool availableRaw()
|
||||||
|
{
|
||||||
|
if (isRawAvailable)
|
||||||
|
{
|
||||||
|
isRawAvailable = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
uint8_t getMsgInfo() { return packInfo->buffer[0] & IR_MASK_MSG_INFO; };
|
||||||
|
uint8_t getMsgType() { return (packInfo->buffer[0] >> 5) & IR_MASK_MSG_TYPE; };
|
||||||
|
uint8_t getMsgRAW() { return packInfo->buffer[0]; };
|
||||||
|
uint16_t getErrorCount() { return packInfo->err.all(); };
|
||||||
|
uint8_t getErrorLowSignal() { return packInfo->err.lowSignal; };
|
||||||
|
uint8_t getErrorHighSignal() { return packInfo->err.highSignal; };
|
||||||
|
uint8_t getErrorOther() { return packInfo->err.other; };
|
||||||
|
uint16_t getTunerTime() { return packInfo->rTime; };
|
||||||
|
uint8_t *getDataRawPtr() { return packInfo->buffer; };
|
||||||
};
|
};
|
||||||
|
|
||||||
class Data : public BasePack
|
class Data : public BasePack
|
||||||
@ -56,15 +114,20 @@ namespace PacketTypes
|
|||||||
DataOffset = 5;
|
DataOffset = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline uint16_t getAddrFrom() { return _getAddrFrom(this); };
|
uint16_t getAddrFrom() { return _getAddrFrom(this); };
|
||||||
inline uint16_t getAddrTo() { return _getAddrTo(this); };
|
uint16_t getAddrTo() { return _getAddrTo(this); };
|
||||||
|
|
||||||
inline uint8_t getDataSize() { return _getDataSize(this); };
|
uint8_t getDataSize() { return _getDataSize(this); };
|
||||||
inline uint8_t *getDataPrt() { return _getDataPrt(this); };
|
uint8_t *getDataPrt() { return _getDataPrt(this); };
|
||||||
inline uint8_t getDataRawSize() { return _getDataRawSize(this); };
|
uint8_t getDataRawSize() { return _getDataRawSize(this); };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool checkAddress() override;
|
bool checkAddress() override
|
||||||
|
{
|
||||||
|
bool ret;
|
||||||
|
IR_FOX::checkAddressRuleApply(getAddrTo(), this->id, ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class DataBack : public BasePack
|
class DataBack : public BasePack
|
||||||
@ -78,15 +141,29 @@ namespace PacketTypes
|
|||||||
DataOffset = 3;
|
DataOffset = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline uint16_t getAddrFrom() { return _getAddrFrom(this); };
|
uint16_t getAddrFrom() { return _getAddrFrom(this); };
|
||||||
inline uint16_t getAddrTo() { return _getAddrTo(this); };
|
uint16_t getAddrTo() { return _getAddrTo(this); };
|
||||||
|
|
||||||
inline uint8_t getDataSize() { return _getDataSize(this); };
|
uint8_t getDataSize() { return _getDataSize(this); };
|
||||||
inline uint8_t *getDataPrt() { return _getDataPrt(this); };
|
uint8_t *getDataPrt() { return _getDataPrt(this); };
|
||||||
inline uint8_t getDataRawSize() { return _getDataRawSize(this); };
|
uint8_t getDataRawSize() { return _getDataRawSize(this); };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool checkAddress() override;
|
bool checkAddress() override
|
||||||
|
{
|
||||||
|
bool ret;
|
||||||
|
if (getMsgType() == IR_MSG_BACK_TO)
|
||||||
|
{
|
||||||
|
DataOffset = 5;
|
||||||
|
IR_FOX::checkAddressRuleApply((packInfo->buffer[addressToOffset] << 8) | packInfo->buffer[addressToOffset + 1], this->id, ret);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DataOffset = 3;
|
||||||
|
ret = true;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class Accept : public BasePack
|
class Accept : public BasePack
|
||||||
@ -99,11 +176,11 @@ namespace PacketTypes
|
|||||||
DataOffset = 3;
|
DataOffset = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline uint16_t getAddrFrom() { return _getAddrFrom(this); };
|
uint16_t getAddrFrom() { return _getAddrFrom(this); };
|
||||||
inline uint8_t getCustomByte() { return packInfo->buffer[DataOffset]; };
|
uint8_t getCustomByte() { return packInfo->buffer[DataOffset]; };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool checkAddress() override;
|
bool checkAddress() override { return true; }
|
||||||
};
|
};
|
||||||
|
|
||||||
class Request : public BasePack
|
class Request : public BasePack
|
||||||
@ -117,11 +194,168 @@ namespace PacketTypes
|
|||||||
DataOffset = 3;
|
DataOffset = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline uint16_t getAddrFrom() { return _getAddrFrom(this); };
|
uint16_t getAddrFrom() { return _getAddrFrom(this); };
|
||||||
inline uint16_t getAddrTo() { return _getAddrTo(this); };
|
uint16_t getAddrTo() { return _getAddrTo(this); };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool checkAddress() override;
|
bool checkAddress() override
|
||||||
|
{
|
||||||
|
bool ret;
|
||||||
|
IR_FOX::checkAddressRuleApply(getAddrTo(), this->id, ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// class IOffsets {
|
||||||
|
// protected:
|
||||||
|
// uint8_t msgOffset;
|
||||||
|
// uint8_t addressFromOffset;
|
||||||
|
// uint8_t addressToOffset;
|
||||||
|
// uint8_t DataOffset;
|
||||||
|
// };
|
||||||
|
|
||||||
|
// class IPackInfo {
|
||||||
|
// public:
|
||||||
|
// IR_FOX::PackInfo* packInfo;
|
||||||
|
// };
|
||||||
|
|
||||||
|
// class IBaseEmptyPack : virtual public IOffsets, virtual public IPackInfo {
|
||||||
|
// };
|
||||||
|
|
||||||
|
// class IR_Decoder;
|
||||||
|
// class IEmptyPack : virtual protected IBaseEmptyPack, virtual public IR_FOX {
|
||||||
|
// friend IR_Decoder;
|
||||||
|
// bool isAvailable;
|
||||||
|
// bool isRawAvailable;
|
||||||
|
// bool isNeedAccept;
|
||||||
|
|
||||||
|
// protected:
|
||||||
|
// uint16_t id;
|
||||||
|
|
||||||
|
// virtual bool checkAddress() {};
|
||||||
|
|
||||||
|
// virtual void set(IR_FOX::PackInfo* packInfo, uint16_t id, bool isNeedAccept = false) {
|
||||||
|
// IBaseEmptyPack::IPackInfo::packInfo = packInfo;
|
||||||
|
// this->id = id;
|
||||||
|
// this->isNeedAccept = isNeedAccept;
|
||||||
|
|
||||||
|
// if (isAvailable = checkAddress()) {
|
||||||
|
// isAvailable = true;
|
||||||
|
// isRawAvailable = true;
|
||||||
|
// Serial.print(" OK ");
|
||||||
|
// } else {
|
||||||
|
// isRawAvailable = true;
|
||||||
|
// Serial.print(" NOT-OK ");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public:
|
||||||
|
// virtual bool available() { if (isAvailable) { isAvailable = false; isRawAvailable = false; return true; } else { return false; } };
|
||||||
|
// virtual bool availableRaw() { if (isRawAvailable) { isRawAvailable = false; return true; } else { return false; } };
|
||||||
|
// virtual uint8_t getMsgInfo() { return packInfo->buffer[0] & IR_MASK_MSG_INFO; };
|
||||||
|
// virtual uint8_t getMsgType() { return (packInfo->buffer[0] >> 5) & IR_MASK_MSG_TYPE; };
|
||||||
|
// virtual uint8_t getMsgRAW() { return packInfo->buffer[0]; };
|
||||||
|
// virtual uint16_t getErrorCount() { return packInfo->err.all(); };
|
||||||
|
// virtual uint8_t getErrorLowSignal() { return packInfo->err.lowSignal; };
|
||||||
|
// virtual uint8_t getErrorHighSignal() { return packInfo->err.highSignal; };
|
||||||
|
// virtual uint8_t getErrorOther() { return packInfo->err.other; };
|
||||||
|
// virtual uint16_t getTunerTime() { return packInfo->rTime; };
|
||||||
|
// };
|
||||||
|
|
||||||
|
// class IHasAddresFrom : virtual protected IBaseEmptyPack {
|
||||||
|
// public:
|
||||||
|
// virtual uint16_t getAddrFrom() { return (packInfo->buffer[addressFromOffset] << 8) | packInfo->buffer[addressFromOffset + 1]; };
|
||||||
|
// };
|
||||||
|
|
||||||
|
// class IHasAddresTo : virtual protected IBaseEmptyPack {
|
||||||
|
// public:
|
||||||
|
// virtual uint16_t getAddrTo() { return (packInfo->buffer[addressToOffset] << 8) | packInfo->buffer[addressToOffset + 1]; };
|
||||||
|
// };
|
||||||
|
|
||||||
|
// class IHasAddresData : virtual protected IBaseEmptyPack {
|
||||||
|
// public:
|
||||||
|
// virtual uint8_t getDataSize() { return packInfo->packSize - crcBytes - DataOffset; };
|
||||||
|
// virtual uint8_t* getDataPrt() { return packInfo->buffer + DataOffset; };
|
||||||
|
// virtual uint8_t getDataRawSize() { return packInfo->packSize; };
|
||||||
|
// virtual uint8_t* getDataRawPtr() { return packInfo->buffer; };
|
||||||
|
// };
|
||||||
|
|
||||||
|
// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// class Data :
|
||||||
|
// virtual public IEmptyPack,
|
||||||
|
// virtual public IHasAddresFrom,
|
||||||
|
// virtual public IHasAddresTo,
|
||||||
|
// virtual public IHasAddresData {
|
||||||
|
// public:
|
||||||
|
// Data() {
|
||||||
|
// msgOffset = 0;
|
||||||
|
// addressFromOffset = 1;
|
||||||
|
// addressToOffset = 3;
|
||||||
|
// DataOffset = 5;
|
||||||
|
// }
|
||||||
|
// protected:
|
||||||
|
// bool checkAddress() override {
|
||||||
|
// bool ret;
|
||||||
|
// checkAddressRuleApply(getAddrTo(), this->id, ret);
|
||||||
|
// return ret;
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// class DataBack :
|
||||||
|
// virtual public IEmptyPack,
|
||||||
|
// virtual public IHasAddresFrom,
|
||||||
|
// virtual public IHasAddresData {
|
||||||
|
// public:
|
||||||
|
// DataBack() {
|
||||||
|
// msgOffset = 0;
|
||||||
|
// addressFromOffset = 1;
|
||||||
|
// addressToOffset = 3;
|
||||||
|
// DataOffset = 3;
|
||||||
|
// }
|
||||||
|
// protected:
|
||||||
|
// bool checkAddress() override {
|
||||||
|
// bool ret;
|
||||||
|
// if (getMsgType() == IR_MSG_BACK_TO) {
|
||||||
|
// DataOffset = 5;
|
||||||
|
// checkAddressRuleApply((packInfo->buffer[addressToOffset] << 8) | packInfo->buffer[addressToOffset + 1], this->id, ret);
|
||||||
|
// } else {
|
||||||
|
// DataOffset = 3;
|
||||||
|
// ret = true;
|
||||||
|
// }
|
||||||
|
// return ret;
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// class Request :
|
||||||
|
// virtual public IEmptyPack,
|
||||||
|
// virtual public IHasAddresFrom,
|
||||||
|
// virtual public IHasAddresTo {
|
||||||
|
// public:
|
||||||
|
// Request() {
|
||||||
|
// msgOffset = 0;
|
||||||
|
// addressFromOffset = 1;
|
||||||
|
// addressToOffset = 3;
|
||||||
|
// DataOffset = 3;
|
||||||
|
// }
|
||||||
|
// protected:
|
||||||
|
// bool checkAddress() override {
|
||||||
|
// bool ret;
|
||||||
|
// checkAddressRuleApply(getAddrTo(), this->id, ret);
|
||||||
|
// return ret;
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// class Accept :
|
||||||
|
// virtual public IEmptyPack,
|
||||||
|
// virtual public IHasAddresFrom {
|
||||||
|
// public:
|
||||||
|
// Accept() {
|
||||||
|
// msgOffset = 0;
|
||||||
|
// addressFromOffset = 1;
|
||||||
|
// DataOffset = 1;
|
||||||
|
// }
|
||||||
|
// protected:
|
||||||
|
// };
|
||||||
|
|||||||
@ -1,174 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Симуляция логики IR_Encoder::buildGateRuns (IR-protocol) и утилиты CRC8.
|
|
||||||
Запуск из корня репозитория: python docs/scripts/ir_protocol_gate_runs_sim.py
|
|
||||||
Или: python ir_protocol_gate_runs_sim.py из каталога scripts/
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
# --- IR_config.h (фрагмент) ---
|
|
||||||
bitPauseTakts = 12
|
|
||||||
bitActiveTakts = 25
|
|
||||||
preambPulse = 3
|
|
||||||
syncBits = 3
|
|
||||||
bitPerByte = 8
|
|
||||||
preambToggle = ((bitPauseTakts * 2 + bitActiveTakts) * 2 - 1)
|
|
||||||
bitHigh = [(bitPauseTakts) * 2 - 1, (bitActiveTakts) * 2 - 1]
|
|
||||||
bitLow = [(bitPauseTakts // 2 + bitActiveTakts) * 2 - 1, (bitPauseTakts) - 1]
|
|
||||||
|
|
||||||
preamb, data, sync, noSignal = 0, 1, 2, 3
|
|
||||||
HIGH = True
|
|
||||||
|
|
||||||
|
|
||||||
def crc8(data: bytes, start: int, end: int, poly: int) -> int:
|
|
||||||
"""Как IR_FOX::crc8 в IR_config.cpp: [start, end)."""
|
|
||||||
crc = 0xFF
|
|
||||||
for i in range(start, end):
|
|
||||||
crc ^= data[i]
|
|
||||||
for _ in range(8):
|
|
||||||
if (crc & 0x80) != 0:
|
|
||||||
crc = ((crc << 1) ^ poly) & 0xFF
|
|
||||||
else:
|
|
||||||
crc = (crc << 1) & 0xFF
|
|
||||||
return crc
|
|
||||||
|
|
||||||
|
|
||||||
def crc_pair_over_wire(packet: bytes) -> tuple[int, int]:
|
|
||||||
"""Два байта CRC как в IR_Encoder::sendDataFULL (poly1 старший, poly2 младший)."""
|
|
||||||
ps = len(packet)
|
|
||||||
if ps < 2:
|
|
||||||
return 0, 0
|
|
||||||
b1 = crc8(packet, 0, ps - 2, 0x31) & 0xFF
|
|
||||||
b2 = crc8(packet, 0, ps - 1, 0x8C) & 0xFF
|
|
||||||
return b1, b2
|
|
||||||
|
|
||||||
|
|
||||||
# Как dataByteSizeMax в IR_config.h (msg+addr+addr+bytePerPack+crc)
|
|
||||||
DATA_BYTE_SIZE_MAX = 1 + 2 + 2 + 31 + 2
|
|
||||||
|
|
||||||
|
|
||||||
def build_gate_runs(packet: bytes):
|
|
||||||
"""
|
|
||||||
Повторяет IR_Encoder::buildGateRuns: список (gate: bool, lenTicks: int), сумма lenTicks = totalTicks DMA.
|
|
||||||
Буфер дополняется нулями до dataByteSizeMax, как sendBufferLocal[dataByteSizeMax] в C++.
|
|
||||||
"""
|
|
||||||
send_len = len(packet)
|
|
||||||
send_buf = bytearray(packet) + bytes(max(0, DATA_BYTE_SIZE_MAX - len(packet)))
|
|
||||||
|
|
||||||
toggle = preambToggle
|
|
||||||
data_bit = bitPerByte - 1
|
|
||||||
data_byte = 0
|
|
||||||
preamb_front = preambPulse * 2 - 1
|
|
||||||
data_seq = bitPerByte * 2
|
|
||||||
sync_seq = syncBits * 2
|
|
||||||
sync_last = False
|
|
||||||
sig = preamb
|
|
||||||
state = HIGH
|
|
||||||
cur_seq = bitHigh
|
|
||||||
|
|
||||||
runs: list[tuple[bool, int]] = []
|
|
||||||
outer_steps = 0
|
|
||||||
|
|
||||||
while True:
|
|
||||||
outer_steps += 1
|
|
||||||
gate = state
|
|
||||||
run_len = toggle + 1 # как в C++: (uint16_t)toggleCounterLocal + 1U
|
|
||||||
|
|
||||||
if runs and runs[-1][0] == gate:
|
|
||||||
g, ln = runs[-1]
|
|
||||||
runs[-1] = (g, ln + run_len)
|
|
||||||
else:
|
|
||||||
runs.append((gate, run_len))
|
|
||||||
|
|
||||||
while True:
|
|
||||||
if sig == noSignal:
|
|
||||||
return runs, outer_steps
|
|
||||||
|
|
||||||
if sig == preamb:
|
|
||||||
if preamb_front:
|
|
||||||
preamb_front -= 1
|
|
||||||
toggle = preambToggle
|
|
||||||
break
|
|
||||||
sig = data
|
|
||||||
state = not False
|
|
||||||
continue
|
|
||||||
|
|
||||||
if sig == data:
|
|
||||||
if data_seq:
|
|
||||||
if not (data_seq & 1):
|
|
||||||
cur_seq = bitHigh if ((send_buf[data_byte] >> data_bit) & 1) else bitLow
|
|
||||||
data_bit -= 1
|
|
||||||
toggle = cur_seq[not state]
|
|
||||||
data_seq -= 1
|
|
||||||
break
|
|
||||||
sync_last = send_buf[data_byte] & 1
|
|
||||||
data_byte += 1
|
|
||||||
data_bit = bitPerByte - 1
|
|
||||||
data_seq = bitPerByte * 2
|
|
||||||
sig = sync
|
|
||||||
continue
|
|
||||||
|
|
||||||
if sig == sync:
|
|
||||||
if sync_seq:
|
|
||||||
if not (sync_seq & 1):
|
|
||||||
if sync_seq == 2:
|
|
||||||
cur_seq = bitLow if (send_buf[data_byte] & 0x80) else bitHigh
|
|
||||||
else:
|
|
||||||
cur_seq = bitLow if sync_last else bitHigh
|
|
||||||
sync_last = not sync_last
|
|
||||||
toggle = cur_seq[not state]
|
|
||||||
sync_seq -= 1
|
|
||||||
break
|
|
||||||
sig = data
|
|
||||||
sync_seq = syncBits * 2
|
|
||||||
if data_byte >= send_len:
|
|
||||||
sig = noSignal
|
|
||||||
continue
|
|
||||||
|
|
||||||
return [], 0
|
|
||||||
|
|
||||||
state = not state
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
|
||||||
print("IR-protocol: preambToggle =", preambToggle)
|
|
||||||
print()
|
|
||||||
|
|
||||||
# Пример из лога: 8-байтный эхо-пакет Version_Query (CRC OK на приёме)
|
|
||||||
echo = bytes.fromhex("C8 FA 2A FD E8 5D AA B4")
|
|
||||||
c1, c2 = crc_pair_over_wire(echo)
|
|
||||||
print("8 байт (эхо): CRC вычисленный:", f"{c1:02X}", f"{c2:02X}", "| на проводе:", f"{echo[6]:02X}", f"{echo[7]:02X}")
|
|
||||||
|
|
||||||
runs8, steps8 = build_gate_runs(echo)
|
|
||||||
total8 = sum(r[1] for r in runs8)
|
|
||||||
print(" buildGateRuns: внешних шагов FSM =", steps8, ", totalTicks =", total8, ", число run-сегментов =", len(runs8))
|
|
||||||
print()
|
|
||||||
|
|
||||||
# 31 байт из лога Frame reject (пример)
|
|
||||||
reject = bytes.fromhex(
|
|
||||||
"DF 00 00 FA 2A 5E 43 61 72 5F 76 34 2E 33 2E 38 5F 5B 31 32 4D 68 7A 5D 6B ED 1D 9A 53 96 62"
|
|
||||||
)
|
|
||||||
if len(reject) == 31:
|
|
||||||
c1, c2 = crc_pair_over_wire(reject)
|
|
||||||
print("31 байт (reject): CRC по телу 0..28 должен быть:", f"{c1:02X}", f"{c2:02X}", "| байты [29:31]:", f"{reject[29]:02X}", f"{reject[30]:02X}")
|
|
||||||
print(" Совпадение с формулой:", c1 == reject[29] and c2 == reject[30])
|
|
||||||
|
|
||||||
runs31, steps31 = build_gate_runs(reject)
|
|
||||||
total31 = sum(r[1] for r in runs31)
|
|
||||||
print(" buildGateRuns: внешних шагов =", steps31, ", totalTicks =", total31, ", run-сегментов =", len(runs31))
|
|
||||||
print()
|
|
||||||
|
|
||||||
# Связь totalTicks с моделью «N тиков на сегмент до шага FSM»
|
|
||||||
# total_build = sum(toggle_i + 1); если бы было sum(toggle_i), разница = steps
|
|
||||||
theoretical_isr_ticks = total31 - steps31
|
|
||||||
print("Для 31-байт пакета: totalTicks (buildGateRuns) =", total31)
|
|
||||||
print(" Если каждый внешний шаг даёт +1 к длине сегмента относительно ISR (runLen = toggle+1 vs toggle),")
|
|
||||||
print(" оценка «ISR-тиков» как totalTicks - outer_steps =", theoretical_isr_ticks)
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(main())
|
|
||||||
Reference in New Issue
Block a user