mirror of
https://github.com/Show-maket/IR-protocol.git
synced 2026-04-28 03:08:08 +00:00
Compare commits
75 Commits
AVR
...
e7d7c0e1c1
| Author | SHA1 | Date | |
|---|---|---|---|
| e7d7c0e1c1 | |||
| af3e012aac | |||
| fc1a3bacef | |||
| 8a0d7f8dba | |||
| d1c84ba18a | |||
| e9c568aed2 | |||
| 7bf71d1d52 | |||
| 38f3ecac3a | |||
| dec8467280 | |||
| bc9563fbb5 | |||
| 021e1e290d | |||
| 89d14919c9 | |||
| 403b8e6850 | |||
| d0c3138c52 | |||
| 2d839d3ff8 | |||
| 6ba8fdffe4 | |||
| aa0b478229 | |||
| 277985f79a | |||
| 444b84c313 | |||
| 2db1ef7805 | |||
| 1353ab6f75 | |||
| d1cb167aaf | |||
| 30ad816c2a | |||
| ecfb3b5f98 | |||
| 70a22463ef | |||
| 71f58a4992 | |||
| b6b9d2c820 | |||
| 98a21f5e56 | |||
| 591727546e | |||
| 79bb804bb4 | |||
| 0471b8cc89 | |||
| 90c41cfe2b | |||
| 1ecc33e9c4 | |||
| 7ef8158a00 | |||
| 341ff3a470 | |||
| e6dbdcee74 | |||
| cf5a6641f4 | |||
| da152c65ee | |||
| 8f77c60cba | |||
| fd51a4935c | |||
| aa862d8f2c | |||
| 7c9529d42f | |||
| d4dd0e95fd | |||
| 04af094f4b | |||
| 2f4ac3ddf8 | |||
| 784365181e | |||
| c66d47e464 | |||
| 3057e78aeb | |||
| a958c1d3b2 | |||
| 2147bf0788 | |||
| e37d4d79f1 | |||
| 373cd43b73 | |||
| 96f0ac623e | |||
| d46640b145 | |||
| e752d0fb50 | |||
| 5a5142e0aa | |||
| e951111c53 | |||
| 06d27f2590 | |||
| 3965616cd1 | |||
| 16da39a293 | |||
| 16a626db22 | |||
| 5cc4555bac | |||
| e334625864 | |||
| 03d74e30cd | |||
| 2972560b13 | |||
| 4d12f77a8b | |||
| 3b5045669b | |||
| 99e523129d | |||
| 0b888a5840 | |||
| 9643670942 | |||
| fd62972717 | |||
| e5983c8367 | |||
| d774f87f7a | |||
| 27a5b28a17 | |||
| ff306e7fae |
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,3 +1,7 @@
|
||||
.vscode/*
|
||||
bin/*
|
||||
log/*
|
||||
!.vscode/launch.json
|
||||
log/*
|
||||
/.vscode
|
||||
*.zip
|
||||
**/__pycache__
|
||||
|
||||
5
.vscode/arduino.json
vendored
Normal file
5
.vscode/arduino.json
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"board": "STMicroelectronics:stm32:GenF4",
|
||||
"port": "COM17",
|
||||
"prebuild": "if exist bin rd /s /q bin"
|
||||
}
|
||||
20
.vscode/launch.json
vendored
Normal file
20
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
|
||||
{
|
||||
"cwd": "${workspaceFolder}",
|
||||
"executable": "${workspaceFolder}/bin/${workspaceFolderBasename}.ino.elf",
|
||||
"name": "Debug with ST-Link",
|
||||
"request": "launch",
|
||||
"type": "cortex-debug",
|
||||
"runToEntryPoint": "main",
|
||||
"showDevDebugOutput": "raw",
|
||||
"servertype": "stlink",
|
||||
"armToolchainPath": "C://Program Files (x86)//Arm GNU Toolchain arm-none-eabi//13.2 Rel1//bin"
|
||||
}
|
||||
]
|
||||
}
|
||||
663
IR-protocol.ino
663
IR-protocol.ino
@ -2,16 +2,26 @@
|
||||
#include "IR_Encoder.h"
|
||||
#include "TimerStatic.h"
|
||||
#include "MemoryCheck.h"
|
||||
#include "CarCmdList.h"
|
||||
/////////////// Pinout ///////////////
|
||||
|
||||
#define encForward_PIN 0
|
||||
#define encBackward_PIN 5
|
||||
#define dec0_PIN PIN_KT1_IN
|
||||
#define dec1_PIN PIN_KT2_IN
|
||||
#define dec2_PIN PIN_KT3_IN
|
||||
#define dec3_PIN PIN_KT4_IN
|
||||
#define dec4_PIN PIN_KT5_IN
|
||||
#define dec5_PIN PIN_KT6_IN
|
||||
#define dec6_PIN PIN_KT7_IN
|
||||
#define dec7_PIN PIN_KT8_IN
|
||||
// #define dec8_PIN PB8
|
||||
// #define dec9_PIN PB9
|
||||
// #define dec10_PIN PB10
|
||||
// #define dec11_PIN PB11
|
||||
// #define dec12_PIN PB12
|
||||
// #define dec13_PIN PB13
|
||||
// #define dec14_PIN PB14
|
||||
// #define dec15_PIN PB15
|
||||
|
||||
#define LoopOut 12
|
||||
#define ISR_Out 10
|
||||
|
||||
#define TestOut 13
|
||||
#define LoopOut PC13
|
||||
|
||||
//////////////// Ini /////////////////
|
||||
|
||||
@ -19,258 +29,335 @@
|
||||
#define SERIAL_SPEED 115200
|
||||
|
||||
//////////////// Var /////////////////
|
||||
// IR_Encoder encForward(PA5, 42 /* , &decBackward */);
|
||||
|
||||
IR_Decoder decForward(2, 555);
|
||||
IR_Decoder decBackward(3, 777);
|
||||
IR_Encoder enc0(PIN_KT8_OUT, 42 /* , &decBackward */);
|
||||
// IR_Encoder enc1(PA1, 127 /* , &decBackward */);
|
||||
// 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);
|
||||
|
||||
//////////////////////// Функции прерываний ////////////////////////
|
||||
|
||||
void decForwardISR() {
|
||||
decForward.isr();
|
||||
void EncoderISR()
|
||||
{
|
||||
IR_Encoder::isr();
|
||||
}
|
||||
|
||||
void decBackwardISR() {
|
||||
decBackward.isr();
|
||||
}
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
IR_Decoder dec0(dec0_PIN, 0);
|
||||
void dec_0_ISR() { dec0.isr(); }
|
||||
|
||||
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(); }
|
||||
|
||||
static uint8_t* portOut;
|
||||
ISR(TIMER2_COMPA_vect) {
|
||||
encForward.isr();
|
||||
// encBackward.isr();
|
||||
// encTree.isr();
|
||||
//TODO: Сделать выбор порта
|
||||
*portOut = (*portOut & 0b11111110) |
|
||||
(
|
||||
encForward.ir_out_virtual << 0U
|
||||
// | encBackward.ir_out_virtual << 6U
|
||||
// | encTree.ir_out_virtual << 2U
|
||||
);
|
||||
}
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
uint8_t data0 [] = { };
|
||||
uint8_t data1 [] = { 42 };
|
||||
uint8_t data2 [] = { 42 , 127 };
|
||||
uint8_t data3 [] = { 42 , 127, 137 };
|
||||
uint8_t data4 [] = { 42 , 127, 137, 255 };
|
||||
uint8_t data0[] = {};
|
||||
uint8_t data1[] = {42};
|
||||
uint8_t data2[] = {42, 127};
|
||||
uint8_t data3[] = {42, 127, 137};
|
||||
uint8_t data4[] = {42, 127, 137, 255};
|
||||
|
||||
uint32_t loopTimer;
|
||||
uint8_t sig = 255;
|
||||
|
||||
uint8_t sig = 0;
|
||||
uint16_t targetAddr = IR_Broadcast;
|
||||
Timer t1(750, millis, []() {
|
||||
|
||||
// Serial.println(sig);
|
||||
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));
|
||||
|
||||
switch (sig) {
|
||||
case 0:
|
||||
encForward.sendData(targetAddr);
|
||||
break;
|
||||
case 1:
|
||||
encForward.sendData(targetAddr, data1, sizeof(data1));
|
||||
break;
|
||||
case 2:
|
||||
encForward.sendData(targetAddr, data2, sizeof(data2));
|
||||
break;
|
||||
case 3:
|
||||
encForward.sendData(targetAddr, data3, sizeof(data3));
|
||||
break;
|
||||
case 4:
|
||||
encForward.sendData(targetAddr, data4, sizeof(data4));
|
||||
break;
|
||||
// Serial.println(sig);
|
||||
|
||||
case 10:
|
||||
encForward.sendData(targetAddr, data0, sizeof(data0), true);
|
||||
break;
|
||||
case 11:
|
||||
encForward.sendData(targetAddr, data1, sizeof(data1), true);
|
||||
break;
|
||||
case 12:
|
||||
encForward.sendData(targetAddr, data2, sizeof(data2), true);
|
||||
break;
|
||||
case 13:
|
||||
encForward.sendData(targetAddr, data3, sizeof(data3), true);
|
||||
break;
|
||||
case 14:
|
||||
encForward.sendData(targetAddr, data4, sizeof(data4), true);
|
||||
break;
|
||||
// switch (sig)
|
||||
// {
|
||||
// case 0:
|
||||
// encForward.sendData(targetAddr);
|
||||
// break;
|
||||
// case 1:
|
||||
// encForward.sendData(targetAddr, data1, sizeof(data1));
|
||||
// break;
|
||||
// case 2:
|
||||
// encForward.sendData(targetAddr, data2, sizeof(data2));
|
||||
// break;
|
||||
// case 3:
|
||||
// encForward.sendData(targetAddr, data3, sizeof(data3));
|
||||
// break;
|
||||
// case 4:
|
||||
// encForward.sendData(targetAddr, data4, sizeof(data4));
|
||||
// break;
|
||||
|
||||
// case 10:
|
||||
// encForward.sendData(targetAddr, data0, sizeof(data0), true);
|
||||
// break;
|
||||
// case 11:
|
||||
// encForward.sendData(targetAddr, data1, sizeof(data1), true);
|
||||
// break;
|
||||
// case 12:
|
||||
// encForward.sendData(targetAddr, data2, sizeof(data2), true);
|
||||
// break;
|
||||
// case 13:
|
||||
// encForward.sendData(targetAddr, data3, sizeof(data3), true);
|
||||
// break;
|
||||
// case 14:
|
||||
// encForward.sendData(targetAddr, data4, sizeof(data4), true);
|
||||
// break;
|
||||
|
||||
// case 20:
|
||||
// encForward.sendBack();
|
||||
// break;
|
||||
// case 21:
|
||||
// encForward.sendBack(data1, sizeof(data1));
|
||||
// break;
|
||||
// case 22:
|
||||
// encForward.sendBack(data2, sizeof(data2));
|
||||
// break;
|
||||
// case 23:
|
||||
// encForward.sendBack(data3, sizeof(data3));
|
||||
// break;
|
||||
// case 24:
|
||||
// encForward.sendBack(data4, sizeof(data4));
|
||||
// break;
|
||||
|
||||
case 20:
|
||||
encForward.sendBack();
|
||||
break;
|
||||
case 21:
|
||||
encForward.sendBack(data1, sizeof(data1));
|
||||
break;
|
||||
case 22:
|
||||
encForward.sendBack(data2, sizeof(data2));
|
||||
break;
|
||||
case 23:
|
||||
encForward.sendBack(data3, sizeof(data3));
|
||||
break;
|
||||
case 24:
|
||||
encForward.sendBack(data4, sizeof(data4));
|
||||
break;
|
||||
// case 30:
|
||||
// encForward.sendBackTo(targetAddr);
|
||||
// break;
|
||||
// case 31:
|
||||
// encForward.sendBackTo(targetAddr, data1, sizeof(data1));
|
||||
// break;
|
||||
// case 32:
|
||||
// encForward.sendBackTo(targetAddr, data2, sizeof(data2));
|
||||
// break;
|
||||
// case 33:
|
||||
// encForward.sendBackTo(targetAddr, data3, sizeof(data3));
|
||||
// break;
|
||||
// case 34:
|
||||
// encForward.sendBackTo(targetAddr, data4, sizeof(data4));
|
||||
// break;
|
||||
|
||||
case 30:
|
||||
encForward.sendBackTo(targetAddr);
|
||||
break;
|
||||
case 31:
|
||||
encForward.sendBackTo(targetAddr, data1, sizeof(data1));
|
||||
break;
|
||||
case 32:
|
||||
encForward.sendBackTo(targetAddr, data2, sizeof(data2));
|
||||
break;
|
||||
case 33:
|
||||
encForward.sendBackTo(targetAddr, data3, sizeof(data3));
|
||||
break;
|
||||
case 34:
|
||||
encForward.sendBackTo(targetAddr, data4, sizeof(data4));
|
||||
break;
|
||||
// case 41:
|
||||
// encForward.sendRequest(targetAddr);
|
||||
// break;
|
||||
// case 42:
|
||||
// encForward.sendAccept(targetAddr);
|
||||
// break;
|
||||
|
||||
case 41:
|
||||
encForward.sendRequest(targetAddr);
|
||||
break;
|
||||
case 42:
|
||||
encForward.sendAccept(targetAddr);
|
||||
break;
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
// encBackward.sendData(IR_Broadcast, data2);
|
||||
// encTree.sendData(IR_Broadcast, rawData3);
|
||||
});
|
||||
|
||||
// Timer t2(50, millis, []()
|
||||
// { digitalToggle(LED_BUILTIN); });
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// encBackward.sendData(IR_Broadcast, data2);
|
||||
// encTree.sendData(IR_Broadcast, rawData3);
|
||||
});
|
||||
Timer t2(500, millis, []() {
|
||||
digitalToggle(13);
|
||||
});
|
||||
Timer signalDetectTimer;
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
void setup() {
|
||||
IR_Encoder::timerSetup();
|
||||
portOut = &PORTB;
|
||||
HardwareTimer IR_Timer(TIM3);
|
||||
|
||||
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.println(F(INFO));
|
||||
|
||||
pinMode(A0, INPUT_PULLUP);
|
||||
pinMode(A1, INPUT_PULLUP);
|
||||
pinMode(A2, INPUT_PULLUP);
|
||||
pinMode(A3, INPUT_PULLUP);
|
||||
|
||||
pinMode(LoopOut, OUTPUT);
|
||||
pinMode(ISR_Out, OUTPUT);
|
||||
|
||||
pinMode(2, INPUT_PULLUP);
|
||||
pinMode(3, INPUT_PULLUP);
|
||||
pinMode(dec0_PIN, INPUT_PULLUP);
|
||||
pinMode(dec1_PIN, INPUT_PULLUP);
|
||||
pinMode(dec2_PIN, INPUT_PULLUP);
|
||||
pinMode(dec3_PIN, INPUT_PULLUP);
|
||||
pinMode(dec4_PIN, INPUT_PULLUP);
|
||||
pinMode(dec5_PIN, INPUT_PULLUP);
|
||||
pinMode(dec6_PIN, INPUT_PULLUP);
|
||||
pinMode(dec7_PIN, INPUT_PULLUP);
|
||||
// pinMode(dec8_PIN, INPUT_PULLUP);
|
||||
// pinMode(dec9_PIN, INPUT_PULLUP);
|
||||
// pinMode(dec10_PIN, INPUT_PULLUP);
|
||||
// pinMode(dec11_PIN, INPUT_PULLUP);
|
||||
// pinMode(dec12_PIN, INPUT_PULLUP);
|
||||
// pinMode(dec13_PIN, INPUT_PULLUP);
|
||||
|
||||
pinMode(8, OUTPUT);
|
||||
pinMode(9, OUTPUT);
|
||||
pinMode(11, OUTPUT);
|
||||
pinMode(13, OUTPUT);
|
||||
pinMode(encForward_PIN, OUTPUT);
|
||||
pinMode(encBackward_PIN, OUTPUT);
|
||||
pinMode(13, OUTPUT);
|
||||
pinMode(12, OUTPUT);
|
||||
|
||||
|
||||
|
||||
// IR_DecoderRaw* blindFromForward [] { &decForward, &decBackward };
|
||||
// encForward.setBlindDecoders(blindFromForward, sizeof(blindFromForward) / sizeof(IR_DecoderRaw*));
|
||||
|
||||
|
||||
attachInterrupt(0, decForwardISR, CHANGE); // D2
|
||||
attachInterrupt(1, decBackwardISR, CHANGE); // D3
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
bool testLed = false;
|
||||
uint32_t testLed_timer;
|
||||
|
||||
void loop() {
|
||||
// digitalToggle(LoopOut);
|
||||
void loop()
|
||||
{
|
||||
digitalToggle(LoopOut);
|
||||
Timer::tick();
|
||||
IR_Decoder::tick();
|
||||
|
||||
decForward.tick();
|
||||
decBackward.tick();
|
||||
bool rx_flag = false;
|
||||
rx_flag |= status(dec0);
|
||||
rx_flag |= status(dec1);
|
||||
rx_flag |= status(dec2);
|
||||
rx_flag |= status(dec3);
|
||||
rx_flag |= status(dec4);
|
||||
rx_flag |= status(dec5);
|
||||
rx_flag |= status(dec6);
|
||||
rx_flag |= status(dec7);
|
||||
// rx_flag |= status(dec8);
|
||||
// rx_flag |= status(dec9);
|
||||
// rx_flag |= status(dec10);
|
||||
// rx_flag |= status(dec11);
|
||||
// rx_flag |= status(dec12);
|
||||
// rx_flag |= status(dec13);
|
||||
|
||||
status(decForward);
|
||||
status(decBackward);
|
||||
|
||||
|
||||
// Serial.println(micros() - loopTimer);
|
||||
// loopTimer = micros();
|
||||
// delayMicroseconds(120*5);
|
||||
|
||||
if (Serial.available()) {
|
||||
if (Serial.available())
|
||||
{
|
||||
uint8_t in = Serial.parseInt();
|
||||
switch (in) {
|
||||
case 100:
|
||||
targetAddr = IR_Broadcast;
|
||||
break;
|
||||
case 101:
|
||||
targetAddr = 555;
|
||||
break;
|
||||
case 102:
|
||||
targetAddr = 777;
|
||||
break;
|
||||
|
||||
default:
|
||||
sig = in;
|
||||
break;
|
||||
switch (in)
|
||||
{
|
||||
case 100:
|
||||
targetAddr = IR_Broadcast;
|
||||
break;
|
||||
case 101:
|
||||
targetAddr = 555;
|
||||
break;
|
||||
case 102:
|
||||
targetAddr = 777;
|
||||
break;
|
||||
default:
|
||||
sig = in;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(testLed && millis() - testLed_timer > 100){
|
||||
testLed=false;
|
||||
digitalWrite(12, LOW);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
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()
|
||||
{
|
||||
// digitalWrite(SignalDetectLed, HIGH);
|
||||
// signalDetectTimer.delay(50, millis, []()
|
||||
// { digitalWrite(SignalDetectLed, LOW); });
|
||||
}
|
||||
|
||||
|
||||
|
||||
//test
|
||||
void status(IR_Decoder& dec) {
|
||||
if (dec.gotData.available() && dec.gotData.getAddrFrom() != 42) {
|
||||
|
||||
digitalWrite(12, HIGH);
|
||||
testLed = true;
|
||||
testLed_timer = millis();
|
||||
|
||||
encForward.sendData(IR_Broadcast, CarCmd::stop); //<<<<<<<<<<<<<<<<<<<<<<<<<<< CMD IS HERE
|
||||
|
||||
|
||||
// test
|
||||
bool status(IR_Decoder &dec)
|
||||
{
|
||||
if (dec.gotData.available())
|
||||
{
|
||||
detectSignal();
|
||||
// Serial.println(micros());
|
||||
String str;
|
||||
if (/* dec.gotData.getDataPrt()[1] */1) {
|
||||
str += ("Data on pin "); str += (dec.isrPin); str += "\n";
|
||||
if (/* dec.gotData.getDataPrt()[1] */ 1)
|
||||
{
|
||||
str += ("Data on pin ");
|
||||
str += (dec.getPin());
|
||||
str += "\n";
|
||||
|
||||
uint8_t msg = dec.gotData.getMsgRAW();
|
||||
str += (" MSG: ");
|
||||
for (size_t i = 0; i < 8; i++) {
|
||||
if (i == 3) str += " ";
|
||||
for (size_t i = 0; i < 8; i++)
|
||||
{
|
||||
if (i == 3)
|
||||
str += " ";
|
||||
str += (msg >> (7 - i)) & 1U;
|
||||
}
|
||||
|
||||
str += "\n";
|
||||
|
||||
str += (" DATA SIZE: "); str += (dec.gotData.getDataSize()); str += "\n";
|
||||
str += (" ADDRESS FROM: "); str += (dec.gotData.getAddrFrom()); str += "\n";
|
||||
str += (" ADDRESS TO: "); str += (dec.gotData.getAddrTo()); str += "\n";
|
||||
str += (" DATA SIZE: ");
|
||||
str += (dec.gotData.getDataSize());
|
||||
str += "\n";
|
||||
str += (" ADDRESS FROM: ");
|
||||
str += (dec.gotData.getAddrFrom());
|
||||
str += "\n";
|
||||
str += (" ADDRESS TO: ");
|
||||
str += (dec.gotData.getAddrTo());
|
||||
str += "\n";
|
||||
// str += (" CRC PACK: "); str += (dec.gotData.getCrcIN()); str += "\n";
|
||||
// str += (" CRC CALC: "); str += (dec.gotData.getCrcCALC()); str += "\n";
|
||||
str += "\n";
|
||||
|
||||
for (size_t i = 0; i < min(10, dec.gotData.getDataSize()); i++) {
|
||||
switch (i) {
|
||||
for (size_t i = 0; i < min(uint8_t(10), dec.gotData.getDataSize()); i++)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
// case 0:
|
||||
// str += (" ADDR: ");
|
||||
// break;
|
||||
@ -278,49 +365,71 @@ void status(IR_Decoder& dec) {
|
||||
// str += (" CMD: ");
|
||||
// break;
|
||||
|
||||
default:
|
||||
str += (" Data["); str += (i); str += ("]: ");
|
||||
break;
|
||||
default:
|
||||
str += (" Data[");
|
||||
str += (i);
|
||||
str += ("]: ");
|
||||
break;
|
||||
}
|
||||
str += (dec.gotData.getDataPrt()[i]); str += "\n";
|
||||
str += (dec.gotData.getDataPrt()[i]);
|
||||
str += "\n";
|
||||
}
|
||||
|
||||
|
||||
str += ("\n*******ErrAll: "); str += (dec.gotData.getErrorCount()); str += "\n";
|
||||
str += ("**ErrDistance: "); str += ((int)(dec.gotData.getErrorHighSignal() - dec.gotData.getErrorLowSignal())); str += "\n";
|
||||
str += ("\n*******ErrAll: ");
|
||||
str += (dec.gotData.getErrorCount());
|
||||
str += "\n";
|
||||
str += ("**ErrDistance: ");
|
||||
str += ((int)(dec.gotData.getErrorHighSignal() - dec.gotData.getErrorLowSignal()));
|
||||
str += "\n";
|
||||
|
||||
str += "\n";
|
||||
} else {
|
||||
str += ("SELF"); str += "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
str += ("SELF");
|
||||
str += "\n";
|
||||
str += "\n";
|
||||
}
|
||||
// obj->resetAvailable();
|
||||
Serial.write(str.c_str());
|
||||
}
|
||||
|
||||
if (dec.gotBackData.available()) {
|
||||
if (dec.gotBackData.available())
|
||||
{
|
||||
detectSignal();
|
||||
String str;
|
||||
if (/* dec.gotData.getDataPrt()[1] */1) {
|
||||
str += ("BackData on pin "); str += (dec.isrPin); str += "\n";
|
||||
if (/* dec.gotData.getDataPrt()[1] */ 1)
|
||||
{
|
||||
str += ("BackData on pin ");
|
||||
str += (dec.getPin());
|
||||
str += "\n";
|
||||
|
||||
uint8_t msg = dec.gotBackData.getMsgRAW();
|
||||
str += (" MSG: ");
|
||||
for (size_t i = 0; i < 8; i++) {
|
||||
if (i == 3) str += " ";
|
||||
for (size_t i = 0; i < 8; i++)
|
||||
{
|
||||
if (i == 3)
|
||||
str += " ";
|
||||
str += (msg >> (7 - i)) & 1U;
|
||||
}
|
||||
|
||||
str += "\n";
|
||||
|
||||
str += (" DATA SIZE: "); str += (dec.gotBackData.getDataSize()); str += "\n";
|
||||
str += (" ADDRESS FROM: "); str += (dec.gotBackData.getAddrFrom()); str += "\n";
|
||||
str += (" DATA SIZE: ");
|
||||
str += (dec.gotBackData.getDataSize());
|
||||
str += "\n";
|
||||
str += (" ADDRESS FROM: ");
|
||||
str += (dec.gotBackData.getAddrFrom());
|
||||
str += "\n";
|
||||
// str += (" ADDRESS TO: "); str += (dec.gotBackData.getAddrTo()); str += "\n";
|
||||
// str += (" CRC PACK: "); str += (dec.gotBackData.getCrcIN()); str += "\n";
|
||||
// str += (" CRC CALC: "); str += (dec.gotBackData.getCrcCALC()); str += "\n";
|
||||
str += "\n";
|
||||
|
||||
for (size_t i = 0; i < min(10, dec.gotBackData.getDataSize()); i++) {
|
||||
switch (i) {
|
||||
for (size_t i = 0; i < min(uint8_t(10), dec.gotBackData.getDataSize()); i++)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
// case 0:
|
||||
// str += (" ADDR: ");
|
||||
// break;
|
||||
@ -328,98 +437,136 @@ void status(IR_Decoder& dec) {
|
||||
// str += (" CMD: ");
|
||||
// break;
|
||||
|
||||
default:
|
||||
str += (" Data["); str += (i); str += ("]: ");
|
||||
break;
|
||||
default:
|
||||
str += (" Data[");
|
||||
str += (i);
|
||||
str += ("]: ");
|
||||
break;
|
||||
}
|
||||
str += (dec.gotBackData.getDataPrt()[i]); str += "\n";
|
||||
str += (dec.gotBackData.getDataPrt()[i]);
|
||||
str += "\n";
|
||||
}
|
||||
|
||||
|
||||
str += ("\n*******ErrAll: "); str += (dec.gotBackData.getErrorCount()); str += "\n";
|
||||
str += ("**ErrDistance: "); str += ((int)(dec.gotBackData.getErrorHighSignal() - dec.gotBackData.getErrorLowSignal())); str += "\n";
|
||||
str += ("\n*******ErrAll: ");
|
||||
str += (dec.gotBackData.getErrorCount());
|
||||
str += "\n";
|
||||
str += ("**ErrDistance: ");
|
||||
str += ((int)(dec.gotBackData.getErrorHighSignal() - dec.gotBackData.getErrorLowSignal()));
|
||||
str += "\n";
|
||||
|
||||
str += "\n";
|
||||
} else {
|
||||
str += ("SELF"); str += "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
str += ("SELF");
|
||||
str += "\n";
|
||||
str += "\n";
|
||||
}
|
||||
// obj->resetAvailable();
|
||||
Serial.write(str.c_str());
|
||||
}
|
||||
|
||||
if (dec.gotAccept.available()) {
|
||||
if (dec.gotAccept.available())
|
||||
{
|
||||
detectSignal();
|
||||
String str;
|
||||
if (/* dec.gotData.getDataPrt()[1] */1) {
|
||||
str += ("Accept on pin "); str += (dec.isrPin); str += "\n";
|
||||
if (/* dec.gotData.getDataPrt()[1] */ 1)
|
||||
{
|
||||
str += ("Accept on pin ");
|
||||
str += (dec.getPin());
|
||||
str += "\n";
|
||||
|
||||
uint8_t msg = dec.gotAccept.getMsgRAW();
|
||||
str += (" MSG: ");
|
||||
for (size_t i = 0; i < 8; i++) {
|
||||
if (i == 3) str += " ";
|
||||
for (size_t i = 0; i < 8; i++)
|
||||
{
|
||||
if (i == 3)
|
||||
str += " ";
|
||||
str += (msg >> (7 - i)) & 1U;
|
||||
}
|
||||
|
||||
str += "\n";
|
||||
|
||||
// str += (" DATA SIZE: "); str += (dec.gotAccept.getDataSize()); str += "\n";
|
||||
str += (" ADDRESS FROM: "); str += (dec.gotAccept.getAddrFrom()); str += "\n";
|
||||
str += (" ADDRESS FROM: ");
|
||||
str += (dec.gotAccept.getAddrFrom());
|
||||
str += "\n";
|
||||
// str += (" ADDRESS TO: "); str += (dec.gotAccept.getAddrTo()); str += "\n";
|
||||
// str += (" CRC PACK: "); str += (dec.gotAccept.getCrcIN()); str += "\n";
|
||||
// str += (" CRC CALC: "); str += (dec.gotAccept.getCrcCALC()); str += "\n";
|
||||
str += "\n";
|
||||
|
||||
str += (" Data: "); str += (dec.gotAccept.getCustomByte());
|
||||
str += (" Data: ");
|
||||
str += (dec.gotAccept.getCustomByte());
|
||||
|
||||
|
||||
|
||||
str += ("\n\n*******ErrAll: "); str += (dec.gotAccept.getErrorCount()); str += "\n";
|
||||
str += ("**ErrDistance: "); str += ((int)(dec.gotAccept.getErrorHighSignal() - dec.gotAccept.getErrorLowSignal())); str += "\n";
|
||||
str += ("\n\n*******ErrAll: ");
|
||||
str += (dec.gotAccept.getErrorCount());
|
||||
str += "\n";
|
||||
str += ("**ErrDistance: ");
|
||||
str += ((int)(dec.gotAccept.getErrorHighSignal() - dec.gotAccept.getErrorLowSignal()));
|
||||
str += "\n";
|
||||
|
||||
str += "\n";
|
||||
} else {
|
||||
str += ("SELF"); str += "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
str += ("SELF");
|
||||
str += "\n";
|
||||
str += "\n";
|
||||
}
|
||||
// obj->resetAvailable();
|
||||
Serial.write(str.c_str());
|
||||
}
|
||||
|
||||
if (dec.gotRequest.available()) {
|
||||
if (dec.gotRequest.available())
|
||||
{
|
||||
detectSignal();
|
||||
String str;
|
||||
if (/* dec.gotData.getDataPrt()[1] */1) {
|
||||
str += ("Request on pin "); str += (dec.isrPin); str += "\n";
|
||||
if (/* dec.gotData.getDataPrt()[1] */ 1)
|
||||
{
|
||||
str += ("Request on pin ");
|
||||
str += (dec.getPin());
|
||||
str += "\n";
|
||||
|
||||
uint8_t msg = dec.gotRequest.getMsgRAW();
|
||||
str += (" MSG: ");
|
||||
for (size_t i = 0; i < 8; i++) {
|
||||
if (i == 3) str += " ";
|
||||
for (size_t i = 0; i < 8; i++)
|
||||
{
|
||||
if (i == 3)
|
||||
str += " ";
|
||||
str += (msg >> (7 - i)) & 1U;
|
||||
}
|
||||
|
||||
str += "\n";
|
||||
|
||||
// str += (" DATA SIZE: "); str += (dec.gotRequest.getDataSize()); str += "\n";
|
||||
str += (" ADDRESS FROM: "); str += (dec.gotRequest.getAddrFrom()); str += "\n";
|
||||
str += (" ADDRESS TO: "); str += (dec.gotRequest.getAddrTo()); str += "\n";
|
||||
str += (" ADDRESS FROM: ");
|
||||
str += (dec.gotRequest.getAddrFrom());
|
||||
str += "\n";
|
||||
str += (" ADDRESS TO: ");
|
||||
str += (dec.gotRequest.getAddrTo());
|
||||
str += "\n";
|
||||
// str += (" CRC PACK: "); str += (dec.gotRequest.getCrcIN()); str += "\n";
|
||||
// str += (" CRC CALC: "); str += (dec.gotRequest.getCrcCALC()); str += "\n";
|
||||
str += "\n";
|
||||
|
||||
|
||||
str += ("\n*******ErrAll: "); str += (dec.gotRequest.getErrorCount()); str += "\n";
|
||||
str += ("**ErrDistance: "); str += ((int)(dec.gotRequest.getErrorHighSignal() - dec.gotRequest.getErrorLowSignal())); str += "\n";
|
||||
str += ("\n*******ErrAll: ");
|
||||
str += (dec.gotRequest.getErrorCount());
|
||||
str += "\n";
|
||||
str += ("**ErrDistance: ");
|
||||
str += ((int)(dec.gotRequest.getErrorHighSignal() - dec.gotRequest.getErrorLowSignal()));
|
||||
str += "\n";
|
||||
|
||||
str += "\n";
|
||||
} else {
|
||||
str += ("SELF"); str += "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
str += ("SELF");
|
||||
str += "\n";
|
||||
str += "\n";
|
||||
}
|
||||
// obj->resetAvailable();
|
||||
Serial.write(str.c_str());
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
141
IR_DMA_ISR_signal_analysis.md
Normal file
141
IR_DMA_ISR_signal_analysis.md
Normal file
@ -0,0 +1,141 @@
|
||||
# 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` пересчитывайте скриптом.*
|
||||
176
IR_Decoder.cpp
Normal file
176
IR_Decoder.cpp
Normal file
@ -0,0 +1,176 @@
|
||||
#include "IR_Decoder.h"
|
||||
|
||||
#if defined(ARDUINO_ARCH_STM32) && !defined(HAL_EXTI_MODULE_DISABLED)
|
||||
#include "Arduino.h"
|
||||
/* NVIC_SetPriority — CMSIS, как в IR_Encoder::begin и Car.ino (без HAL-заголовка yyxx). */
|
||||
|
||||
/** NVIC для линии EXTI пина (как в Arduino STM32 SrcWrapper interrupt.cpp). */
|
||||
static IRQn_Type ir_decoder_exti_irqn_for_pin(uint8_t arduino_pin)
|
||||
{
|
||||
#if defined(STM32C0xx) || defined(STM32F0xx) || defined(STM32G0xx) || defined(STM32L0xx)
|
||||
(void)arduino_pin;
|
||||
return (IRQn_Type)(-1);
|
||||
#else
|
||||
const PinName p = digitalPinToPinName(arduino_pin);
|
||||
if (p == NC) {
|
||||
return (IRQn_Type)(-1);
|
||||
}
|
||||
const uint16_t pinmask = STM_GPIO_PIN(p);
|
||||
uint8_t id = 0U;
|
||||
uint16_t pm = pinmask;
|
||||
while (pm != 0x0001U) {
|
||||
pm = (uint16_t)(pm >> 1U);
|
||||
id++;
|
||||
}
|
||||
#if defined(STM32H5xx) || defined(STM32MP1xx) || defined(STM32L5xx) || defined(STM32U5xx) || defined(STM32WBAxx)
|
||||
static const IRQn_Type exti_irqnb[16] = {
|
||||
EXTI0_IRQn, EXTI1_IRQn, EXTI2_IRQn, EXTI3_IRQn, EXTI4_IRQn, EXTI5_IRQn, EXTI6_IRQn,
|
||||
EXTI7_IRQn, EXTI8_IRQn, EXTI9_IRQn, EXTI10_IRQn, EXTI11_IRQn,
|
||||
EXTI12_IRQn, EXTI13_IRQn, EXTI14_IRQn, EXTI15_IRQn};
|
||||
#else
|
||||
static const IRQn_Type exti_irqnb[16] = {
|
||||
EXTI0_IRQn, EXTI1_IRQn, EXTI2_IRQn, EXTI3_IRQn, EXTI4_IRQn,
|
||||
EXTI9_5_IRQn, EXTI9_5_IRQn, EXTI9_5_IRQn, EXTI9_5_IRQn, EXTI9_5_IRQn,
|
||||
EXTI15_10_IRQn, EXTI15_10_IRQn, EXTI15_10_IRQn, EXTI15_10_IRQn,
|
||||
EXTI15_10_IRQn, EXTI15_10_IRQn};
|
||||
#endif
|
||||
if (id < 16U) {
|
||||
return exti_irqnb[id];
|
||||
}
|
||||
return (IRQn_Type)(-1);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void ir_decoder_apply_rx_exti_nvic(uint8_t arduino_pin, uint32_t preempt)
|
||||
{
|
||||
const IRQn_Type irqn = ir_decoder_exti_irqn_for_pin(arduino_pin);
|
||||
if ((int)irqn < 0) {
|
||||
return;
|
||||
}
|
||||
#if !defined(STM32C0xx) && !defined(STM32F0xx) && !defined(STM32G0xx) && !defined(STM32L0xx)
|
||||
NVIC_SetPriority(irqn, preempt);
|
||||
#endif
|
||||
}
|
||||
|
||||
void IR_Decoder::setReceiveExtiPreemptPriority(uint32_t preempt)
|
||||
{
|
||||
rxExtiPreemptConfigured_ = true;
|
||||
rxExtiPreemptValue_ = preempt;
|
||||
if (extiEnabled_) {
|
||||
ir_decoder_apply_rx_exti_nvic(pin, preempt);
|
||||
}
|
||||
}
|
||||
#endif /* ARDUINO_ARCH_STM32 && !HAL_EXTI_MODULE_DISABLED */
|
||||
|
||||
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 enableOnConstruct)
|
||||
: IR_DecoderRaw(pin, addr, encPair)
|
||||
{
|
||||
get_dec_list().push_back(this);
|
||||
if (enableOnConstruct) {
|
||||
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);
|
||||
extiEnabled_ = true;
|
||||
#if defined(ARDUINO_ARCH_STM32) && !defined(HAL_EXTI_MODULE_DISABLED)
|
||||
if (rxExtiPreemptConfigured_) {
|
||||
ir_decoder_apply_rx_exti_nvic(pin, rxExtiPreemptValue_);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void IR_Decoder::disable()
|
||||
{
|
||||
extiEnabled_ = false;
|
||||
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())
|
||||
{
|
||||
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);
|
||||
}
|
||||
88
IR_Decoder.h
88
IR_Decoder.h
@ -5,13 +5,22 @@
|
||||
|
||||
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;
|
||||
bool isWaitingAcceptSend;
|
||||
uint16_t addrAcceptSendTo;
|
||||
|
||||
uint16_t acceptDelay = 75;
|
||||
uint16_t acceptDelay = IR_ResponseDelay;
|
||||
uint8_t acceptCustomByte;
|
||||
|
||||
bool extiEnabled_ = false;
|
||||
bool rxExtiPreemptConfigured_ = false;
|
||||
uint32_t rxExtiPreemptValue_ = 0;
|
||||
|
||||
public:
|
||||
PacketTypes::Data gotData;
|
||||
PacketTypes::DataBack gotBackData;
|
||||
@ -19,59 +28,44 @@ public:
|
||||
PacketTypes::Request gotRequest;
|
||||
PacketTypes::BasePack gotRaw;
|
||||
|
||||
IR_Decoder(const uint8_t isrPin, uint16_t addr, IR_Encoder *encPair = nullptr) : IR_DecoderRaw(isrPin, addr, encPair) {}
|
||||
// IR_Decoder();
|
||||
/** @param enableOnConstruct true — вызвать enable() из конструктора; false — отложенный enable() (NVIC и т.д.), tick — tickThis() / tick(). */
|
||||
IR_Decoder(const uint8_t pin, uint16_t addr = 0, IR_Encoder *encPair = nullptr, bool enableOnConstruct = true);
|
||||
|
||||
void tick()
|
||||
{
|
||||
IR_DecoderRaw::tick();
|
||||
if (availableRaw())
|
||||
{
|
||||
#ifdef IRDEBUG_INFO
|
||||
Serial.println("PARSING RAW DATA");
|
||||
std::function<void()> operator()();
|
||||
|
||||
/**
|
||||
* Arduino STM32: после attachInterrupt ядро выставляет свой приоритет EXTI.
|
||||
* Если вызывали setReceiveExtiPreemptPriority(), здесь он применяется поверх (обычно нужен выше срочности, чем DMA ИК-TX).
|
||||
* На других платформах поведение без изменений.
|
||||
*/
|
||||
void enable();
|
||||
void disable();
|
||||
|
||||
#if defined(ARDUINO_ARCH_STM32) && !defined(HAL_EXTI_MODULE_DISABLED)
|
||||
/**
|
||||
* Задать preempt-приоритет NVIC для EXTI линии этого пина (тот же смысл, что второй аргумент CMSIS NVIC_SetPriority).
|
||||
* Вызывайте до или после enable(); при активном приёме применяется сразу.
|
||||
* При использовании DMA на передачу ИК preempt приёма должен быть меньше, чем у DMA TX (выше срочность прерывания).
|
||||
*/
|
||||
void setReceiveExtiPreemptPriority(uint32_t preempt);
|
||||
#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;
|
||||
|
||||
bool isReceive(uint8_t type);
|
||||
|
||||
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 > 75)
|
||||
{
|
||||
encoder->sendAccept(addrAcceptSendTo, acceptCustomByte);
|
||||
isWaitingAcceptSend = false;
|
||||
}
|
||||
}
|
||||
~IR_Decoder();
|
||||
|
||||
void setAcceptDelay(uint16_t acceptDelay)
|
||||
/** Обойти все экземпляры из внутреннего списка и вызвать tick у каждого. */
|
||||
static void tick();
|
||||
|
||||
/** Tick только этого декодера (без обхода списка). Не комбинируйте с static tick() для того же экземпляра. */
|
||||
void tickThis() { _tick(); }
|
||||
|
||||
inline void setAcceptDelay(uint16_t acceptDelay)
|
||||
{
|
||||
this->acceptDelay = acceptDelay;
|
||||
}
|
||||
uint16_t getAcceptDelay()
|
||||
inline uint16_t getAcceptDelay()
|
||||
{
|
||||
return this->acceptDelay;
|
||||
}
|
||||
|
||||
1220
IR_DecoderRaw.cpp
1220
IR_DecoderRaw.cpp
File diff suppressed because it is too large
Load Diff
160
IR_DecoderRaw.h
160
IR_DecoderRaw.h
@ -1,15 +1,20 @@
|
||||
#pragma once
|
||||
#include "IR_config.h"
|
||||
#include "RingBuffer.h"
|
||||
|
||||
// #define IRDEBUG
|
||||
class Print;
|
||||
|
||||
#define IRDEBUG
|
||||
|
||||
#ifdef IRDEBUG
|
||||
#define wrHigh A3 // Запись HIGH инициирована // green
|
||||
#define wrLow A3 // Запись LOW инициирована // blue
|
||||
#define writeOp 13 // Операция записи, 1 пульс для 0 и 2 для 1 // orange
|
||||
#define wrHigh 255 // Запись HIGH инициирована // green
|
||||
#define wrLow 255 // Запись LOW инициирована // blue
|
||||
#define writeOp 255 // Операция записи, 1 пульс для 0 и 2 для 1 // orange
|
||||
// Исправленные ошибки // purle
|
||||
// 1 пульс: fix
|
||||
#define errOut A3
|
||||
#define errOut 255
|
||||
#define up 255
|
||||
#define down 255
|
||||
#endif
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -20,6 +25,7 @@
|
||||
#define riseTimeMin (riseTime - riseTolerance)
|
||||
#define aroundRise(t) (riseTimeMin < t && t < riseTimeMax)
|
||||
#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_DecoderRaw : virtual public IR_FOX
|
||||
@ -27,47 +33,45 @@ class IR_DecoderRaw : virtual public IR_FOX
|
||||
friend IR_Encoder;
|
||||
|
||||
protected:
|
||||
PackInfo packInfo;
|
||||
IR_Encoder *encoder; // Указатель на парный передатчик
|
||||
bool availableRaw()
|
||||
{
|
||||
if (isAvailable)
|
||||
{
|
||||
isAvailable = false;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
PackInfo packInfo;
|
||||
uint8_t msgTypeReceive = 0;
|
||||
IR_Encoder *encoder; // Указатель на парный передатчик
|
||||
bool availableRaw();
|
||||
|
||||
public:
|
||||
const uint8_t isrPin; // Пин прерывания
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
/// @brief Конструктор
|
||||
/// @param isrPin Номер вывода прерывания/данных от приёмника (2 или 3 для atmega 328p)
|
||||
/// @param pin Номер вывода прерывания/данных от приёмника (2 или 3 для atmega 328p)
|
||||
/// @param addr Адрес приёмника
|
||||
/// @param encPair Указатель на передатчик, работающий в паре
|
||||
IR_DecoderRaw(const uint8_t isrPin, uint16_t addr, IR_Encoder *encPair = nullptr);
|
||||
IR_DecoderRaw(const uint8_t pin, uint16_t addr, IR_Encoder *encPair = nullptr);
|
||||
|
||||
void isr(); // Функция прерывания
|
||||
void tick(); // Обработка приёмника, необходима для работы
|
||||
|
||||
bool isOverflow() { return isBufferOverflow; }; // Буффер переполнился
|
||||
bool isSubOverflow()
|
||||
{
|
||||
uint8_t oldSREG = SREG;
|
||||
cli();
|
||||
volatile bool ret = isSubBufferOverflow;
|
||||
SREG = oldSREG;
|
||||
return ret;
|
||||
};
|
||||
bool isReciving() { return isBufferOverflow; }; // Возвращает true, если происходит приём пакета
|
||||
inline bool isOverflow() { return isBufferOverflow; }; // Буффер переполнился
|
||||
bool isSubOverflow();
|
||||
volatile inline bool isReciving() { return isRecive; }; // Возвращает true, если происходит приём пакета
|
||||
|
||||
#if defined(IR_EDGE_TRACE)
|
||||
void edgeTraceClear();
|
||||
bool edgeTraceOverflow() const { return edgeTrace_overflow; }
|
||||
uint16_t edgeTracePendingCount() const;
|
||||
/** При непустом кольце: перевод строки + @IRF1v1: + hex; в tick() сброс на Serial автоматически. См. ref/IR_EDGE_TRACE_FORMAT.md */
|
||||
uint16_t edgeTraceFlushChunk(Print &out, uint16_t maxRec = 48);
|
||||
#endif
|
||||
|
||||
/// Кадр собран по длине из заголовка, но CRC не сошёлся — один раз можно прочитать копию сырых байтов.
|
||||
bool availableReject();
|
||||
uint8_t getRejectSize() const { return rejectPackSize; }
|
||||
const uint8_t* getRejectBuffer() const { return rejectBuffer; }
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
private:
|
||||
bool isRejectAvailable = false;
|
||||
uint8_t rejectPackSize = 0;
|
||||
uint8_t rejectBuffer[dataByteSizeMax]{};
|
||||
|
||||
ErrorsStruct errors;
|
||||
bool isAvailable = false;
|
||||
uint16_t packSize;
|
||||
@ -81,27 +85,60 @@ private:
|
||||
|
||||
uint16_t riseSyncTime = bitTime; // Подстраиваемое время бита в мкс
|
||||
|
||||
volatile uint32_t lastEdgeTime = 0; // время последнего фронта
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
volatile uint8_t currentSubBufferIndex; // Счетчик текущей позиции во вспомогательном буфере фронтов/спадов
|
||||
volatile uint32_t currentSubBufferIndex; // Счетчик текущей позиции во вспомогательном буфере фронтов/спадов
|
||||
|
||||
struct FrontStorage
|
||||
{ // Структура для хранения времени и направления фронта/спада
|
||||
volatile uint32_t time = 0; // Время
|
||||
volatile bool dir = false; // Направление (true = ↑; false = ↓)
|
||||
volatile FrontStorage *next = nullptr; // Указатель на следующий связанный фронт/спад, или nullptr если конец
|
||||
{ // Структура для хранения времени и направления фронта/спада
|
||||
volatile uint32_t time = 0; // Время
|
||||
volatile bool dir = false; // Направление (true = ↑; false = ↓)
|
||||
// volatile FrontStorage *next = nullptr; // Указатель на следующий связанный фронт/спад, или nullptr если конец
|
||||
};
|
||||
volatile FrontStorage *lastFront = nullptr; // Указатель последнего фронта/спада
|
||||
volatile FrontStorage *firstUnHandledFront = nullptr; // Указатель первого необработанного фронта/спада
|
||||
volatile FrontStorage subBuffer[subBufferSize]; // вспомогательный буфер для хранения необработанных фронтов/спадов
|
||||
// volatile FrontStorage subBuffer[subBufferSize]; // вспомогательный буфер для хранения необработанных фронтов/спадов
|
||||
|
||||
RingBuffer<FrontStorage, subBufferSize> subBuffer;
|
||||
|
||||
#if defined(IR_EDGE_TRACE)
|
||||
struct IrEdgeTraceRec
|
||||
{
|
||||
uint32_t t_us;
|
||||
uint8_t level;
|
||||
uint8_t flags;
|
||||
};
|
||||
void edgeTracePush(uint32_t t_us, uint8_t level, uint8_t flags);
|
||||
IrEdgeTraceRec edgeTrace_buf[IR_EDGE_TRACE_CAPACITY]{};
|
||||
volatile uint16_t edgeTrace_w = 0;
|
||||
volatile uint16_t edgeTrace_r = 0;
|
||||
volatile bool edgeTrace_overflow = false;
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
uint8_t dataBuffer[dataByteSizeMax]{0}; // Буффер данных
|
||||
uint32_t prevRise, prevPrevRise, prevFall, prevPrevFall; // Время предыдущих фронтов/спадов
|
||||
uint16_t errorCounter = 0; // Счётчик ошибок
|
||||
int8_t preambFrontCounter = 0; // Счётчик __/``` ↑ преамбулы
|
||||
int16_t bufBitPos = 0; // Позиция для записи бита в буффер
|
||||
uint8_t dataBuffer[dataByteSizeMax]{0}; // Буффер данных
|
||||
volatile uint32_t prevRise, prevPrevRise, prevFall, prevPrevFall; // Время предыдущих фронтов/спадов
|
||||
|
||||
volatile uint32_t risePeriod;
|
||||
volatile uint32_t highTime;
|
||||
volatile uint32_t lowTime;
|
||||
|
||||
uint32_t oldTime;
|
||||
uint16_t wrongCounter;
|
||||
|
||||
int8_t highCount;
|
||||
int8_t lowCount;
|
||||
int8_t allCount;
|
||||
|
||||
uint16_t errorCounter = 0; // Счётчик ошибок
|
||||
int8_t preambFrontCounter = 0; // Счётчик __/``` ↑ преамбулы
|
||||
int16_t bufBitPos = 0; // Позиция для записи бита в буффер
|
||||
|
||||
private:
|
||||
void listenStart(); // @brief Слушатель для работы isReciving()
|
||||
bool isReciveRaw;
|
||||
void listenStart();
|
||||
void checkTimeout(); //
|
||||
|
||||
/// @brief Проверка CRC. Проверяет len байт со значением crc, пришедшим в пакете
|
||||
/// @param len Длина в байтах проверяемых данных
|
||||
@ -117,8 +154,8 @@ private:
|
||||
uint8_t err_syncBit; // Счётчик ошибок синхронизации
|
||||
|
||||
/// @brief Запиь бита в буффер, а так же проверка битов синхранизации и их фильтрация
|
||||
/// @param Бит данных
|
||||
void writeToBuffer(bool);
|
||||
/// @param packTraceInvertFix если true — в IRDEBUG_SERIAL_PACK бит в трассе пишется как `0`/`1` (исправление по фронтам)
|
||||
void writeToBuffer(bool bit, bool packTraceInvertFix = false);
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void firstRX(); /// @brief Установка и сброс начальных значений и флагов в готовность к приёму данных
|
||||
@ -130,7 +167,34 @@ private:
|
||||
uint16_t ceil_div(uint16_t val, uint16_t divider);
|
||||
|
||||
#ifdef IRDEBUG
|
||||
uint32_t wrCounter;
|
||||
inline void errPulse(uint8_t pin, uint8_t count);
|
||||
inline void infoPulse(uint8_t pin, uint8_t count);
|
||||
#endif
|
||||
};
|
||||
|
||||
#if defined(IRDEBUG_SERIAL_PACK)
|
||||
static constexpr uint16_t kPackTraceBufCap =
|
||||
uint16_t(dataByteSizeMax) * (uint16_t(bitPerByte) + uint16_t(syncBits)) + 48u;
|
||||
|
||||
void packTraceResetFrame();
|
||||
void packTracePushBit(bool bit);
|
||||
void packTracePushChar(char c);
|
||||
/** Помечает в packTraceBitBuf бит (после BRUTEFORCE_CHECK) обёрткой `0`/`1` по финальному значению в dataBuffer. */
|
||||
void packTraceWrapDataBitInBackticks(uint16_t byteIndex, uint8_t bitInByte);
|
||||
/** IR hex: все байты dataBuffer[0 .. byteCount-1] в hex. */
|
||||
void packTraceEmitHex(uint8_t byteCount) const;
|
||||
/** IR raw: биты и синхра; тройной пробел между блоками msg/from/to/data/CRC; первый байт 3+пробел+5. endWithNewline — перевод строки после сырой строки. */
|
||||
void packTraceEmitRawBitsLine(bool endWithNewline = true) const;
|
||||
void packTraceEmitErrorFlash(const __FlashStringHelper *msg);
|
||||
void packTraceEmitEndOk(uint8_t packSize);
|
||||
void packTraceEmitEndBadCrc(uint8_t packSize);
|
||||
void packTraceOnTimeoutOrAbort(bool fromListenStart);
|
||||
void packTraceForceEndSyncPhase();
|
||||
bool packTraceSoftReject() const;
|
||||
|
||||
bool packTraceOpen = false;
|
||||
bool packTraceHadWrongSync = false;
|
||||
char packTraceBitBuf[kPackTraceBufCap]{};
|
||||
uint16_t packTraceLen = 0;
|
||||
#endif
|
||||
};
|
||||
|
||||
552
IR_Encoder.cpp
552
IR_Encoder.cpp
@ -1,12 +1,18 @@
|
||||
#include "IR_Encoder.h"
|
||||
#include "IR_DecoderRaw.h"
|
||||
#include <string.h>
|
||||
|
||||
#define LoopOut 12
|
||||
#define ISR_Out 10
|
||||
#define TestOut 13
|
||||
|
||||
IR_Encoder::IR_Encoder(uint16_t addr, IR_DecoderRaw *decPair = nullptr)
|
||||
IR_Encoder *IR_Encoder::head = nullptr;
|
||||
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;
|
||||
this->decPair = decPair;
|
||||
signal = noSignal;
|
||||
@ -14,8 +20,7 @@ IR_Encoder::IR_Encoder(uint16_t addr, IR_DecoderRaw *decPair = nullptr)
|
||||
#if disablePairDec
|
||||
if (decPair != nullptr)
|
||||
{
|
||||
blindDecoders = new IR_DecoderRaw *[1]
|
||||
{ decPair };
|
||||
blindDecoders = new IR_DecoderRaw *[1]{decPair};
|
||||
decodersCount = 1;
|
||||
}
|
||||
#endif
|
||||
@ -23,7 +28,278 @@ IR_Encoder::IR_Encoder(uint16_t addr, IR_DecoderRaw *decPair = nullptr)
|
||||
{
|
||||
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)
|
||||
{
|
||||
#if disablePairDec
|
||||
@ -34,28 +310,23 @@ void IR_Encoder::setBlindDecoders(IR_DecoderRaw *decoders[], uint8_t count)
|
||||
blindDecoders = decoders;
|
||||
}
|
||||
|
||||
IR_Encoder::~IR_Encoder()
|
||||
{
|
||||
delete[] bitLow;
|
||||
delete[] bitHigh;
|
||||
};
|
||||
IR_Encoder::~IR_Encoder(){};
|
||||
|
||||
void IR_Encoder::sendData(uint16_t addrTo, uint8_t dataByte, bool needAccept = false)
|
||||
IR_SendResult 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;
|
||||
return sendData(addrTo, &dataByte, 1, needAccept);
|
||||
}
|
||||
|
||||
void IR_Encoder::sendData(uint16_t addrTo, uint8_t *data = nullptr, uint8_t len = 0, bool needAccept = false){
|
||||
sendData(id, addrTo, data, len, needAccept);
|
||||
IR_SendResult IR_Encoder::sendData(uint16_t addrTo, uint8_t *data, uint8_t len, bool needAccept){
|
||||
return sendDataFULL(id, addrTo, data, len, needAccept);
|
||||
}
|
||||
void IR_Encoder::sendData(uint16_t addrFrom, uint16_t addrTo, uint8_t *data = nullptr, uint8_t len = 0, bool needAccept = false)
|
||||
|
||||
IR_SendResult IR_Encoder::sendDataFULL(uint16_t addrFrom, uint16_t addrTo, uint8_t *data, uint8_t len, bool needAccept)
|
||||
{
|
||||
if (len > bytePerPack)
|
||||
{
|
||||
return;
|
||||
Serial.println("IR Pack to big");
|
||||
return IR_SendResult(false, 0);
|
||||
}
|
||||
constexpr uint8_t dataStart = msgBytes + addrBytes + addrBytes;
|
||||
memset(sendBuffer, 0x00, dataByteSizeMax);
|
||||
@ -84,6 +355,19 @@ void IR_Encoder::sendData(uint16_t addrFrom, uint16_t addrTo, uint8_t *data = nu
|
||||
sendBuffer[packSize - crcBytes] = crc8(sendBuffer, 0, packSize - crcBytes, poly1) & 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) {
|
||||
// decPair->isWaitingAccept = ((msgType >> 5) & IR_MASK_MSG_TYPE == IR_MSG_DATA_ACCEPT);
|
||||
// if (decPair->isWaitingAccept) {
|
||||
@ -93,9 +377,14 @@ void IR_Encoder::sendData(uint16_t addrFrom, uint16_t addrTo, uint8_t *data = nu
|
||||
|
||||
// отправка
|
||||
rawSend(sendBuffer, packSize);
|
||||
|
||||
// Возвращаем результат отправки
|
||||
uint32_t sendTime = calculateSendTime(packSize);
|
||||
return IR_SendResult(true, sendTime);
|
||||
}
|
||||
|
||||
void IR_Encoder::sendAccept(uint16_t addrTo, uint8_t customByte = 0)
|
||||
|
||||
IR_SendResult IR_Encoder::sendAccept(uint16_t addrTo, uint8_t customByte)
|
||||
{
|
||||
constexpr uint8_t packsize = msgBytes + addrBytes + 1U + crcBytes;
|
||||
memset(sendBuffer, 0x00, dataByteSizeMax);
|
||||
@ -116,9 +405,13 @@ void IR_Encoder::sendAccept(uint16_t addrTo, uint8_t customByte = 0)
|
||||
sendBuffer[5] = crc8(sendBuffer, 0, 5, poly2) & 0xFF;
|
||||
|
||||
rawSend(sendBuffer, packsize);
|
||||
|
||||
// Возвращаем результат отправки
|
||||
uint32_t sendTime = calculateSendTime(packsize);
|
||||
return IR_SendResult(true, sendTime);
|
||||
}
|
||||
|
||||
void IR_Encoder::sendRequest(uint16_t addrTo)
|
||||
IR_SendResult IR_Encoder::sendRequest(uint16_t addrTo)
|
||||
{
|
||||
constexpr uint8_t packsize = msgBytes + addrBytes + addrBytes + crcBytes;
|
||||
memset(sendBuffer, 0x00, dataByteSizeMax);
|
||||
@ -138,34 +431,39 @@ void IR_Encoder::sendRequest(uint16_t addrTo)
|
||||
sendBuffer[6] = crc8(sendBuffer, 0, 6, poly2) & 0xFF;
|
||||
|
||||
rawSend(sendBuffer, packsize);
|
||||
|
||||
// Возвращаем результат отправки
|
||||
uint32_t sendTime = calculateSendTime(packsize);
|
||||
return IR_SendResult(true, sendTime);
|
||||
}
|
||||
|
||||
void IR_Encoder::sendBack(uint8_t data)
|
||||
IR_SendResult IR_Encoder::sendBack(uint8_t data)
|
||||
{
|
||||
_sendBack(false, 0, &data, 1);
|
||||
}
|
||||
void IR_Encoder::sendBack(uint8_t *data = nullptr, uint8_t len = 0)
|
||||
{
|
||||
_sendBack(false, 0, data, len);
|
||||
return _sendBack(false, 0, &data, 1);
|
||||
}
|
||||
|
||||
void IR_Encoder::sendBackTo(uint16_t addrTo, uint8_t *data = nullptr, uint8_t len = 0)
|
||||
IR_SendResult IR_Encoder::sendBack(uint8_t *data, uint8_t len)
|
||||
{
|
||||
_sendBack(true, addrTo, data, len);
|
||||
return _sendBack(false, 0, data, len);
|
||||
}
|
||||
|
||||
void IR_Encoder::_sendBack(bool isAdressed, uint16_t addrTo, uint8_t *data, uint8_t len)
|
||||
IR_SendResult IR_Encoder::sendBackTo(uint16_t addrTo, uint8_t *data, uint8_t len)
|
||||
{
|
||||
return _sendBack(true, addrTo, data, len);
|
||||
}
|
||||
|
||||
IR_SendResult IR_Encoder::_sendBack(bool isAdressed, uint16_t addrTo, uint8_t *data, uint8_t len)
|
||||
{
|
||||
if (len > bytePerPack)
|
||||
{
|
||||
return;
|
||||
return IR_SendResult(false, 0);
|
||||
}
|
||||
memset(sendBuffer, 0x00, dataByteSizeMax);
|
||||
uint8_t dataStart = msgBytes + addrBytes + (isAdressed ? addrBytes : 0);
|
||||
|
||||
uint8_t packSize = msgBytes + addrBytes + (isAdressed ? addrBytes : 0) + min(1, len) + crcBytes;
|
||||
uint8_t packSize = msgBytes + addrBytes + (isAdressed ? addrBytes : 0) + min(uint8_t(1), len) + crcBytes;
|
||||
uint8_t msgType =
|
||||
((isAdressed ? IR_MSG_BACK_TO : IR_MSG_BACK) << 5) | ((packSize) & (IR_MASK_MSG_INFO >> 1));
|
||||
((isAdressed ? IR_MSG_BACK_TO : IR_MSG_BACK) << 5) | ((packSize) & IR_MASK_MSG_INFO);
|
||||
|
||||
// формирование массива
|
||||
// msg_type
|
||||
@ -190,6 +488,10 @@ void IR_Encoder::_sendBack(bool isAdressed, uint16_t addrTo, uint8_t *data, uint
|
||||
|
||||
// отправка
|
||||
rawSend(sendBuffer, packSize);
|
||||
|
||||
// Возвращаем результат отправки
|
||||
uint32_t sendTime = calculateSendTime(packSize);
|
||||
return IR_SendResult(true, sendTime);
|
||||
}
|
||||
|
||||
void IR_Encoder::setDecoder_isSending()
|
||||
@ -199,6 +501,10 @@ void IR_Encoder::setDecoder_isSending()
|
||||
for (uint8_t i = 0; i < decodersCount; i++)
|
||||
{
|
||||
blindDecoders[i]->isPairSending ^= id;
|
||||
// Serial.print("setDecoder_isSending() id = ");
|
||||
// Serial.print(id);
|
||||
// Serial.print(" isPairSending = ");
|
||||
// Serial.println(blindDecoders[i]->isPairSending);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -210,10 +516,38 @@ void IR_Encoder::rawSend(uint8_t *ptr, uint8_t len)
|
||||
// TODO: Обработка повторной отправки
|
||||
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();
|
||||
|
||||
cli();
|
||||
// noInterrupts();
|
||||
sendLen = len;
|
||||
toggleCounter = preambToggle; // Первая генерация для первого signal
|
||||
|
||||
@ -229,17 +563,29 @@ void IR_Encoder::rawSend(uint8_t *ptr, uint8_t len)
|
||||
state = HIGH;
|
||||
|
||||
currentBitSequence = bitHigh;
|
||||
isSending = true;
|
||||
sei();
|
||||
// interrupts();
|
||||
}
|
||||
|
||||
void IR_Encoder::isr()
|
||||
{
|
||||
IR_Encoder *current = IR_Encoder::head;
|
||||
while (current != nullptr)
|
||||
{
|
||||
current->_isr();
|
||||
current = current->next;
|
||||
}
|
||||
}
|
||||
|
||||
void IR_Encoder::_isr()
|
||||
{
|
||||
if (!isSending)
|
||||
return;
|
||||
|
||||
ir_out_virtual = !ir_out_virtual && state;
|
||||
|
||||
port->ODR &= ~(mask);
|
||||
port->ODR |= mask & (ir_out_virtual ? (uint16_t)0xFFFF : (uint16_t)0x0000);
|
||||
|
||||
if (toggleCounter)
|
||||
{
|
||||
toggleCounter--;
|
||||
@ -254,7 +600,10 @@ void IR_Encoder::isr()
|
||||
// сброс счетчиков
|
||||
// ...
|
||||
isSending = false;
|
||||
// Serial.println("STOP");
|
||||
setDecoder_isSending();
|
||||
carrierStopPending = true;
|
||||
// Serial.println();
|
||||
return;
|
||||
break;
|
||||
|
||||
@ -335,32 +684,6 @@ void IR_Encoder::isr()
|
||||
}
|
||||
}
|
||||
|
||||
void old()
|
||||
{ ///////////////////////////////////////////////////////
|
||||
// void IR_Encoder::rawSend(uint8_t* ptr, uint8_t len) {
|
||||
// /*tmp*/bool LOW_FIRST = false;/*tmp*/
|
||||
|
||||
// if (decoders != nullptr) { decoders->isPairSending = true; }
|
||||
|
||||
// bool prev = 1;
|
||||
// bool next;
|
||||
|
||||
// send_EMPTY(preambPulse); // преамбула
|
||||
// for (uint16_t byteNum = 0; byteNum < len; byteNum++) {
|
||||
// sendByte(ptr[byteNum], &prev, LOW_FIRST);
|
||||
// if (byteNum < len - 1) {
|
||||
// next = ptr[byteNum + 1] & (LOW_FIRST ? 0b00000001 : 0b10000000);
|
||||
// } else {
|
||||
// next = 0;
|
||||
// }
|
||||
// addSync(&prev, &next);
|
||||
// }
|
||||
|
||||
// if (decoders != nullptr) { decoders->isPairSending = false; }
|
||||
|
||||
// }
|
||||
}
|
||||
|
||||
void IR_Encoder::sendByte(uint8_t byte, bool *prev, bool LOW_FIRST)
|
||||
{
|
||||
uint8_t mask = LOW_FIRST ? 0b00000001 : 0b10000000;
|
||||
@ -398,30 +721,101 @@ void IR_Encoder::addSync(bool *prev, bool *next)
|
||||
}
|
||||
}
|
||||
|
||||
void IR_Encoder::send_HIGH(bool prevBite = 1)
|
||||
{
|
||||
uint8_t IR_Encoder::bitHigh[2] = {
|
||||
(bitPauseTakts) * 2 - 1,
|
||||
(bitActiveTakts) * 2 - 1};
|
||||
uint8_t IR_Encoder::bitLow[2] = {
|
||||
(bitPauseTakts / 2 + bitActiveTakts) * 2 - 1,
|
||||
(bitPauseTakts)-1};
|
||||
|
||||
// if (/* prevBite */1) {
|
||||
// meanderBlock(bitPauseTakts * 2, halfPeriod, LOW);
|
||||
// meanderBlock(bitActiveTakts, halfPeriod, HIGH);
|
||||
// } else { // более короткий HIGH после нуля
|
||||
// meanderBlock(bitTakts - (bitActiveTakts - bitPauseTakts), halfPeriod, LOW);
|
||||
// meanderBlock(bitActiveTakts - bitPauseTakts, halfPeriod, HIGH);
|
||||
// }
|
||||
uint32_t IR_Encoder::calculateSendTime(uint8_t packSize) const
|
||||
{
|
||||
// Расчет времени отправки пакета в миллисекундах
|
||||
|
||||
// Время преамбулы: preambPulse * 2 фронта * bitTakts тактов
|
||||
uint32_t preambTime = preambPulse * 2 * bitTakts;
|
||||
|
||||
// Время данных: количество бит * bitTakts тактов
|
||||
uint32_t dataTime = packSize * 8 * bitTakts;
|
||||
|
||||
// Время синхронизации: 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
|
||||
{
|
||||
// meanderBlock(bitPauseTakts, halfPeriod, LOW);
|
||||
// meanderBlock(bitActiveTakts, halfPeriod, LOW);
|
||||
// meanderBlock(bitPauseTakts, halfPeriod, HIGH);
|
||||
return testSendTime(addrTo, &dataByte, 1, needAccept);
|
||||
}
|
||||
|
||||
void IR_Encoder::send_EMPTY(uint8_t count)
|
||||
uint32_t IR_Encoder::testSendTime(uint16_t addrTo, uint8_t *data, uint8_t len, bool needAccept) const
|
||||
{
|
||||
// for (size_t i = 0; i < count * 2; i++) {
|
||||
// meanderBlock((bitPauseTakts * 2 + bitActiveTakts), halfPeriod, prevPreambBit);
|
||||
// prevPreambBit = !prevPreambBit;
|
||||
// }
|
||||
// meanderBlock(bitPauseTakts * 2 + bitActiveTakts, halfPeriod, 0); //TODO: Отодвинуть преамбулу
|
||||
return testSendTimeFULL(id, addrTo, data, len, needAccept);
|
||||
}
|
||||
|
||||
uint32_t IR_Encoder::testSendTimeFULL(uint16_t addrFrom, uint16_t addrTo, uint8_t *data, uint8_t len, bool needAccept) const
|
||||
{
|
||||
if (len > bytePerPack)
|
||||
{
|
||||
return 0; // Возвращаем 0 для недопустимого размера
|
||||
}
|
||||
|
||||
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};
|
||||
|
||||
115
IR_Encoder.h
115
IR_Encoder.h
@ -3,72 +3,107 @@
|
||||
|
||||
// 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_Encoder : public IR_FOX
|
||||
{
|
||||
friend IR_DecoderRaw;
|
||||
|
||||
static IR_Encoder *head;
|
||||
static IR_Encoder *last;
|
||||
IR_Encoder *next;
|
||||
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:
|
||||
// uint16_t id; /// @brief Адрес передатчика
|
||||
|
||||
public:
|
||||
/// @brief Класс передатчика
|
||||
/// @param addr Адрес передатчика
|
||||
/// @param pin Вывод передатчика
|
||||
/// @param tune Подстройка несущей частоты
|
||||
/// @param decPair Приёмник, для которого отключается приём в момент передачи передатчиком
|
||||
IR_Encoder(uint16_t addr, IR_DecoderRaw *decPair = nullptr);
|
||||
IR_Encoder(uint8_t pin, uint16_t addr = 0, IR_DecoderRaw *decPair = nullptr, bool autoHandle = true);
|
||||
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();
|
||||
|
||||
static void timerSetup()
|
||||
{
|
||||
// TIMER2 Ini
|
||||
uint8_t oldSREG = SREG; // Save global interupts settings
|
||||
cli();
|
||||
// DDRB |= (1 << PORTB3); //OC2A (17)
|
||||
TCCR2A = 0;
|
||||
TCCR2B = 0;
|
||||
/** Optional: register external TX backend (e.g. DMA driver). */
|
||||
static void setExternalTxBackend(ExternalTxStartFn startFn, ExternalTxBusyFn busyFn, void *ctx);
|
||||
|
||||
// TCCR2A |= (1 << COM2A0); //Переключение состояния
|
||||
/** Called by external TX backend on actual end of transmission. */
|
||||
void externalFinishSend();
|
||||
|
||||
TCCR2A |= (1 << WGM21); // Clear Timer On Compare (Сброс по совпадению)
|
||||
TCCR2B |= (1 << CS20); // Предделитель 1
|
||||
TIMSK2 |= (1 << OCIE2A); // Прерывание по совпадению
|
||||
/** Build RLE runs of carrier gate for a packet (no HW access). */
|
||||
static size_t buildGateRuns(const uint8_t *packet, uint8_t len, IR_TxGateRun *outRuns, size_t maxRuns);
|
||||
|
||||
OCR2A = /* 465 */ ((F_CPU / (38000 * 2)) - 2); // 38кГц
|
||||
void enable();
|
||||
void disable();
|
||||
|
||||
SREG = oldSREG; // Return interrupt settings
|
||||
}
|
||||
static void timerOFFSetup()
|
||||
{
|
||||
TIMSK2 &= ~(1 << OCIE2A); // Прерывание по совпадению выкл
|
||||
}
|
||||
|
||||
void IR_Encoder::setBlindDecoders(IR_DecoderRaw *decoders[], uint8_t count);
|
||||
void setBlindDecoders(IR_DecoderRaw *decoders[], uint8_t count);
|
||||
void rawSend(uint8_t *ptr, uint8_t len);
|
||||
|
||||
void sendData(uint16_t addrTo, uint8_t dataByte, bool needAccept = false);
|
||||
void sendData(uint16_t addrTo, uint8_t *data = nullptr, uint8_t len = 0, bool needAccept = false);
|
||||
void sendData(uint16_t addrFrom, uint16_t addrTo, uint8_t *data = nullptr, uint8_t len = 0, bool needAccept = false);
|
||||
IR_SendResult 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);
|
||||
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);
|
||||
|
||||
void sendBack(uint8_t data);
|
||||
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 sendAccept(uint16_t addrTo, uint8_t customByte = 0);
|
||||
IR_SendResult sendRequest(uint16_t addrTo);
|
||||
|
||||
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();
|
||||
volatile bool ir_out_virtual;
|
||||
|
||||
void _isr();
|
||||
private:
|
||||
void IR_Encoder::_sendBack(bool isAdressed, uint16_t addrTo, uint8_t *data, uint8_t len);
|
||||
static volatile bool carrierStopPending;
|
||||
static void carrierResume();
|
||||
static void carrierPauseIfIdle();
|
||||
|
||||
void IR_Encoder::setDecoder_isSending();
|
||||
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 sendByte(uint8_t byte, bool *prev, bool LOW_FIRST);
|
||||
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_LOW();
|
||||
void send_EMPTY(uint8_t count);
|
||||
@ -106,12 +141,8 @@ private:
|
||||
uint8_t low;
|
||||
uint8_t high;
|
||||
};
|
||||
static inline uint8_t *bitHigh = new uint8_t[2]{
|
||||
(bitPauseTakts * 2) * 2 - 1,
|
||||
(bitActiveTakts) * 2 - 1};
|
||||
static inline uint8_t *bitLow = new uint8_t[2]{
|
||||
(bitPauseTakts + bitActiveTakts) * 2 - 1,
|
||||
(bitPauseTakts) * 2 - 1};
|
||||
static uint8_t bitHigh[2];
|
||||
static uint8_t bitLow[2];
|
||||
uint8_t *currentBitSequence = bitLow;
|
||||
volatile SignalPart signal;
|
||||
};
|
||||
|
||||
33
IR_config.cpp
Normal file
33
IR_config.cpp
Normal file
@ -0,0 +1,33 @@
|
||||
#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;
|
||||
};
|
||||
266
IR_config.h
266
IR_config.h
@ -1,25 +1,65 @@
|
||||
#pragma once
|
||||
#include <Arduino.h>
|
||||
|
||||
#include <list>
|
||||
// #define IRDEBUG_INFO
|
||||
|
||||
/** Число потоков DMA-TX задаётся шаблоном: IrDmaTxStm32<2>, см. IrDmaTxStm32.h и irproto::kDefaultDmaTxMaxStreams. */
|
||||
namespace irproto {
|
||||
constexpr size_t kDefaultDmaTxMaxStreams = 4U;
|
||||
}
|
||||
|
||||
// Пошаговый разбор кадра на Serial (по умолчанию выключено). Пульсы IRDEBUG на пинах не меняют.
|
||||
// #define IRDEBUG_SERIAL_PACK
|
||||
// Не обрывать приём сразу при накопленной sync-ошибке — «дописывать» до таймаута (только вместе с IRDEBUG_SERIAL_PACK).
|
||||
// #define IRDEBUG_SERIAL_SOFT_REJECT
|
||||
|
||||
// Журнал фронтов ИК в ISR; сброс строк @IRF1v1: в IR_DecoderRaw::tick(). См. ref/IR_EDGE_TRACE_FORMAT.md
|
||||
// Расход RAM ≈ IR_EDGE_TRACE_CAPACITY * 6 байт на декодер. Выключить — закомментировать:
|
||||
// #define IR_EDGE_TRACE
|
||||
#if defined(IR_EDGE_TRACE)
|
||||
#ifndef IR_EDGE_TRACE_CAPACITY
|
||||
#define IR_EDGE_TRACE_CAPACITY 512u
|
||||
#endif
|
||||
/** Запись в edgeTrace: фронт не передан в decode (isPairSending). */
|
||||
#define IR_EDGE_TRACE_F_SKIP_DECODE 0x01u
|
||||
#endif
|
||||
// Пример в скетче: void irPackTracePrintOkCommand(const uint8_t* b, uint8_t n) { Serial.print(F("CarCmd::...")); }
|
||||
#if defined(IRDEBUG_SERIAL_PACK)
|
||||
/** Слабая реализация в IR_DecoderRaw.cpp; в скетче определите свою для вывода вроде CarCmd::... */
|
||||
void irPackTracePrintOkCommand(const uint8_t *buf, uint8_t packSize);
|
||||
#endif
|
||||
/*//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Для работы в паре положить декодер в энкодер
|
||||
|
||||
*/// Адресация с 1 до 65 499
|
||||
#define IR_Broadcast 65000 // 65 500 ~ 65 535 - широковещательные пакеты (всем), возможно разделить на 35 типов
|
||||
*/
|
||||
// Адресация с 1 до 65 499
|
||||
#define IR_Broadcast 65000 // 65 500 ~ 65 535 - широковещательные пакеты (всем)
|
||||
/*
|
||||
Адрес 0 запрещен и зарезервирован под NULL, либо тесты
|
||||
IR_MSG_ACCEPT с адреса 0 воспринимается всеми устройствами
|
||||
*Адресное пространство:
|
||||
Адрес 0 запрещен и зарезервирован под NULL, либо тесты
|
||||
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
|
||||
|
||||
Адресное пространство:
|
||||
|
||||
Излучатели контрольных точек: 1000 ~ 1999
|
||||
Излучатели без обратной связиЖ 2000 ~ 2999
|
||||
Излучатели светофоров: 3000 ~ 3999
|
||||
// //********** FREE *************
|
||||
// #define IR_MAX_FREE 31999
|
||||
// #define IR_MIN_FREE 2000
|
||||
|
||||
//********* Машинки ***********
|
||||
#define IR_MAX_CAR 31999
|
||||
#define IR_MIN_CAR 1
|
||||
|
||||
//***** Пульты управления *****
|
||||
#define IR_MAX_CONTROLLER 64999
|
||||
#define IR_MIN_CONTROLLER 64000
|
||||
/*
|
||||
|
||||
/```````````````````````````````````````````````` data pack `````````````````````````````````````````````\
|
||||
|
||||
@ -34,65 +74,68 @@ IR_MSG_ACCEPT с адреса 0 воспринимается всеми устр
|
||||
\____________________________________________________________________________________________________/
|
||||
|
||||
msg type:
|
||||
// __________
|
||||
// | 01234567 |
|
||||
// ----------
|
||||
// | xxx..... | = тип сообщения
|
||||
// | ...xxxxx | = длина (максимум 31 бита)
|
||||
// ---------- */
|
||||
#define IR_MSG_BACK 0U // | 000...... | = Задний сигнал машинки
|
||||
#define IR_MSG_ACCEPT 1U // | 001..... | = подтверждение
|
||||
#define IR_MSG_REQUEST 2U // | 010..... | = запрос
|
||||
#define IR_MSG_ 3U // | 011..... | = ??
|
||||
#define IR_MSG_BACK_TO 4U // | 100..... | = Задний сигнал машинки c адресацией
|
||||
#define IR_MSG_ 5U // | 101..... | = ??
|
||||
#define IR_MSG_DATA_NOACCEPT 6U // | 110..... | = данные, не требующие подтверждения
|
||||
#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 ]
|
||||
{..........} [........................] [..................] [..............] {..........} [........................] [........................] [..............]
|
||||
|
||||
{ 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
|
||||
\________________________________________________________________/ | \_____________________________________________________________________/ |
|
||||
| | | |
|
||||
\________________________________________________________________________/ \_____________________________________________________________________________/
|
||||
|
||||
customByte - контрольная сумма принятых данных по poly1
|
||||
|
||||
|
||||
|
||||
/`````````````````````` Задний сигнал машинки без адресации ``````````````````````\
|
||||
|
||||
{``````````} [````````````````````````] [````````````````````````] [``````````````]
|
||||
{ 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 ]
|
||||
| 0 1 2 3 | |
|
||||
\_____________________________________________________________________/ |
|
||||
| |
|
||||
\_____________________________________________________________________________/
|
||||
|
||||
|
||||
|
||||
/```````````````````````````````````` Задний сигнал машинки с адресацией ````````````````````````````````````\
|
||||
|
||||
{``````````} [````````````````````````] [````````````````````````] [````````````````````````] [``````````````]
|
||||
{ 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 ]
|
||||
| 0 1 2 3 4 5 | |
|
||||
\________________________________________________________________________________________________/ |
|
||||
| |
|
||||
\________________________________________________________________________________________________________/
|
||||
|
||||
*/
|
||||
// __________
|
||||
// | 01234567 |
|
||||
// ----------
|
||||
// | xxx..... | = тип сообщения (биты 7..5)
|
||||
// | ...xxxxx | = полная длина кадра в байтах (5 бит, 0..31, IR_MASK_MSG_INFO), не «31 бит» и не отдельный лимит «24 байта»
|
||||
// Полезная нагрузка в data pack: до bytePerPack байт (см. #define bytePerPack).
|
||||
// ---------- */
|
||||
#define IR_MSG_BACK 0U // | 000...... | = Задний сигнал машинки
|
||||
#define IR_MSG_ACCEPT 1U // | 001..... | = подтверждение
|
||||
#define IR_MSG_REQUEST 2U // | 010..... | = запрос
|
||||
// #define IR_MSG_ 3U // | 011..... | = ??
|
||||
#define IR_MSG_BACK_TO 4U // | 100..... | = Задний сигнал машинки c адресацией
|
||||
// #define IR_MSG_ 5U // | 101..... | = ??
|
||||
#define IR_MSG_DATA_NOACCEPT 6U // | 110..... | = данные, не требующие подтверждения
|
||||
#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 ]
|
||||
{..........} [........................] [..................] [..............] {..........} [........................] [........................] [..............]
|
||||
|
||||
{ 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
|
||||
\________________________________________________________________/ | \_____________________________________________________________________/ |
|
||||
| | | |
|
||||
\________________________________________________________________________/ \_____________________________________________________________________________/
|
||||
|
||||
customByte - контрольная сумма принятых данных по poly1
|
||||
|
||||
|
||||
|
||||
/`````````````````````` Задний сигнал машинки без адресации ``````````````````````\
|
||||
// Первый байт: (IR_MSG_BACK<<5) | (packSize & IR_MASK_MSG_INFO) — как у data pack (тип + длина 0..31).
|
||||
|
||||
{``````````} [````````````````````````] [````````````````````````] [``````````````]
|
||||
{ msg type } [ addr_from uint16_t ] [====== data bytes ======] [ CRC Bytes ]
|
||||
{..........} [........................] [........................] [..............]
|
||||
|
||||
{ xxx..|..xxxxx } [addr_from_H][addr_from_L] [data_H][data_n..][data_L] [ crc1 ][ crc2 ]
|
||||
| 0 1 2 3 | |
|
||||
\_____________________________________________________________________/ |
|
||||
| |
|
||||
\_____________________________________________________________________________/
|
||||
|
||||
|
||||
|
||||
/```````````````````````````````````` Задний сигнал машинки с адресацией ````````````````````````````````````\
|
||||
// Первый байт: (IR_MSG_BACK_TO<<5) | (packSize & IR_MASK_MSG_INFO) — IR_MSG_BACK_TO в битах 7..5, длина 0..31.
|
||||
|
||||
{``````````} [````````````````````````] [````````````````````````] [````````````````````````] [``````````````]
|
||||
{ msg type } [ addr_from uint16_t ] [ addr_to uint16_t ] [====== data bytes ======] [ CRC Bytes ]
|
||||
{..........} [........................] [........................] [........................] [..............]
|
||||
|
||||
{ xxx..|..xxxxx } [addr_from_H][addr_from_L] [addr_to_H][addr_to_L] [data_H][data_n..][data_L] [ crc1 ][ crc2 ]
|
||||
| 0 1 2 3 4 5 | |
|
||||
\________________________________________________________________________________________________/ |
|
||||
| |
|
||||
\________________________________________________________________________________________________________/
|
||||
|
||||
*/
|
||||
|
||||
#define IR_MASK_MSG_TYPE 0b00000111
|
||||
#define IR_MASK_MSG_INFO 0b00011111
|
||||
@ -101,13 +144,14 @@ customByte - контрольная сумма принятых данных п
|
||||
/////////////////////////////////////////////////////////////////////////////////////*/
|
||||
typedef uint16_t crc_t;
|
||||
|
||||
#define bytePerPack 16 // колличество байтов в пакете
|
||||
// #define BRUTEFORCE_CHECK // Перепроверяет пакет на 1 битные ошибки //TODO: зависает
|
||||
#define bytePerPack (31) // колличество байтов в пакете
|
||||
#ifndef freeFrec
|
||||
#define freeFrec true
|
||||
#define freeFrec false
|
||||
#endif
|
||||
|
||||
#ifndef subBufferSize
|
||||
#define subBufferSize 35 //Буфер для складирования фронтов, пока их не обработают (передатчик)
|
||||
#define subBufferSize 250 // Буфер для складирования фронтов, пока их не обработают (передатчик)
|
||||
#endif
|
||||
|
||||
#define preambPulse 3
|
||||
@ -116,33 +160,40 @@ typedef uint16_t crc_t;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#define bitPerByte 8U // Колличество бит в байте
|
||||
#define bitPerByte 8U // Колличество бит в байте
|
||||
#define addrBytes 2
|
||||
#define msgBytes 1
|
||||
#define crcBytes 2
|
||||
#define poly1 0x31
|
||||
#define poly2 0x8C
|
||||
#define syncBits 3U // количество битов синхронизации
|
||||
#define syncBits 3U // количество битов синхронизации
|
||||
|
||||
#define dataByteSizeMax (msgBytes + addrBytes + addrBytes + bytePerPack + crcBytes)
|
||||
|
||||
#define preambFronts (preambPulse*2) // количество фронтов преамбулы (Приём)
|
||||
#define preambFronts (preambPulse * 2) // количество фронтов преамбулы (Приём)
|
||||
#define preambToggle ((bitPauseTakts * 2 + bitActiveTakts) * 2 - 1) // колличество переключений преамбулы (Передача)
|
||||
|
||||
#define carrierFrec 38000U // частота несущей (Приём/Передача)
|
||||
#define carrierPeriod (1000000U/carrierFrec) // период несущей в us (Приём)
|
||||
#define carrierFrec 38000U // частота несущей (Приём/Передача)
|
||||
#define carrierPeriod (1000000U / carrierFrec) // период несущей в us (Приём)
|
||||
|
||||
// В процессе работы значения будут отклонятся в соответствии с предыдущим битом
|
||||
#define bitActiveTakts 25U // длительность высокого уровня в тактах
|
||||
#define bitPauseTakts 6U // длительность низкого уровня в тактах
|
||||
#define bitActiveTakts 25U // длительность высокого уровня в тактах
|
||||
#define bitPauseTakts 12U // длительность низкого уровня в тактах
|
||||
|
||||
#define bitTakts (bitActiveTakts+(bitPauseTakts*2U)) // Общая длительность бита в тактах
|
||||
#define bitTime (bitTakts*carrierPeriod) // Общая длительность бита
|
||||
#define bitTakts (bitActiveTakts + bitPauseTakts) // Общая длительность бита в тактах
|
||||
#define bitTime (bitTakts * carrierPeriod) // Общая длительность бита
|
||||
#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:
|
||||
struct PackOffsets {
|
||||
struct PackOffsets
|
||||
{
|
||||
uint8_t msgOffset;
|
||||
uint8_t addrFromOffset;
|
||||
uint8_t addrToOffset;
|
||||
@ -150,54 +201,43 @@ public:
|
||||
uint8_t crcOffset;
|
||||
};
|
||||
|
||||
struct ErrorsStruct {
|
||||
struct ErrorsStruct
|
||||
{
|
||||
uint8_t lowSignal = 0;
|
||||
uint8_t highSignal = 0;
|
||||
uint8_t other = 0;
|
||||
|
||||
void reset() {
|
||||
void reset()
|
||||
{
|
||||
lowSignal = 0;
|
||||
highSignal = 0;
|
||||
other = 0;
|
||||
}
|
||||
uint16_t all() { return lowSignal + highSignal + other; }
|
||||
|
||||
};
|
||||
|
||||
struct PackInfo {
|
||||
uint8_t* buffer = nullptr;
|
||||
struct PackInfo
|
||||
{
|
||||
uint8_t *buffer = nullptr;
|
||||
uint8_t packSize = 0;
|
||||
uint16_t crc = 0;
|
||||
ErrorsStruct err;
|
||||
uint16_t rTime = 0;
|
||||
};
|
||||
|
||||
static void checkAddressRuleApply(uint16_t address, uint16_t id, bool& flag) {
|
||||
flag = false;
|
||||
flag |= id == 0;
|
||||
flag |= address == id;
|
||||
flag |= address >= IR_Broadcast;
|
||||
}
|
||||
|
||||
uint16_t getId() { return id; }
|
||||
void setId(uint16_t id) { this->id = id; }
|
||||
inline uint16_t getId() const { return id; }
|
||||
inline void setId(uint16_t id) { this->id = id; }
|
||||
static void checkAddressRuleApply(uint16_t address, uint16_t id, bool &flag);
|
||||
void setPin(uint8_t pin);
|
||||
inline uint8_t getPin() { return pin; };
|
||||
inline GPIO_TypeDef *getPort() const { return port; }
|
||||
inline uint16_t getPinMask() const { return mask; }
|
||||
|
||||
protected:
|
||||
ErrorsStruct errors;
|
||||
uint16_t id;
|
||||
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;
|
||||
}
|
||||
|
||||
uint8_t pin;
|
||||
GPIO_TypeDef *port;
|
||||
uint16_t mask;
|
||||
ErrorsStruct errors;
|
||||
uint8_t crc8(uint8_t *data, uint8_t start, uint8_t end, uint8_t poly);
|
||||
};
|
||||
|
||||
386
IrDmaTxStm32.h
Normal file
386
IrDmaTxStm32.h
Normal file
@ -0,0 +1,386 @@
|
||||
#pragma once
|
||||
|
||||
#include "IR_Encoder.h"
|
||||
|
||||
#if defined(ARDUINO_ARCH_STM32) && defined(STM32G4xx)
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <HardwareTimer.h>
|
||||
|
||||
#if defined(__GNUC__)
|
||||
#define IR_DMA_TX_HOT __attribute__((always_inline)) inline
|
||||
#else
|
||||
#define IR_DMA_TX_HOT inline
|
||||
#endif
|
||||
|
||||
#include "stm32g4xx_hal.h"
|
||||
|
||||
/**
|
||||
* STM32G4: ИК TX через DMA в GPIO BSRR, такт от TIM UPDATE (carrierFrec×2).
|
||||
*
|
||||
* Число слотов потоков — параметр шаблона (без макросов), например IrDmaTxStm32<2>.
|
||||
* По умолчанию: IrDmaTxStm32<> ≡ irproto::kDefaultDmaTxMaxStreams (см. IR_config.h).
|
||||
*
|
||||
* Контракт: ref/IR_DMA_TX_backend.md
|
||||
*/
|
||||
template<size_t MaxStreams = irproto::kDefaultDmaTxMaxStreams>
|
||||
class IrDmaTxStm32 {
|
||||
static_assert(MaxStreams >= 1U, "IrDmaTxStm32: MaxStreams >= 1");
|
||||
|
||||
public:
|
||||
struct StreamCfg {
|
||||
DMA_Channel_TypeDef* instance = nullptr;
|
||||
IRQn_Type irq = IRQn_Type(0);
|
||||
uint32_t dmamuxRequest = 0;
|
||||
IR_Encoder* enc = nullptr;
|
||||
|
||||
uint32_t* dmaWords = nullptr;
|
||||
uint16_t dmaWordCount = 0;
|
||||
|
||||
IR_Encoder::IR_TxGateRun* gateRuns = nullptr;
|
||||
size_t maxGateRuns = 0;
|
||||
};
|
||||
|
||||
struct Config {
|
||||
HardwareTimer* timer = nullptr;
|
||||
uint8_t streamCount = 0;
|
||||
StreamCfg streams[MaxStreams];
|
||||
};
|
||||
|
||||
bool begin(const Config& cfg) {
|
||||
cfg_ = cfg;
|
||||
streamCount_ = cfg.streamCount;
|
||||
|
||||
if (cfg_.timer == nullptr || streamCount_ == 0) return false;
|
||||
if (streamCount_ > MaxStreams) return false;
|
||||
|
||||
htim_ = cfg_.timer->getHandle();
|
||||
if (htim_ == nullptr) return false;
|
||||
|
||||
for (uint8_t i = 0; i < streamCount_; i++) {
|
||||
const StreamCfg& sc = cfg_.streams[i];
|
||||
if (sc.enc == nullptr || sc.instance == nullptr) return false;
|
||||
if (sc.dmaWords == nullptr || sc.dmaWordCount < 2U) return false;
|
||||
if ((sc.dmaWordCount & 1U) != 0U) return false;
|
||||
if (sc.gateRuns == nullptr || sc.maxGateRuns == 0U) return false;
|
||||
}
|
||||
|
||||
__HAL_RCC_DMA1_CLK_ENABLE();
|
||||
__HAL_RCC_DMAMUX1_CLK_ENABLE();
|
||||
|
||||
for (uint8_t i = 0; i < streamCount_; i++) {
|
||||
const StreamCfg& sc = cfg_.streams[i];
|
||||
if (!initStream(streams_[i], sc)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
s_instance = this;
|
||||
activeCount_ = 0;
|
||||
|
||||
for (uint8_t i = 0; i < streamCount_; i++) {
|
||||
HAL_NVIC_EnableIRQ(streams_[i].dmaIrq);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static IrDmaTxStm32<MaxStreams>* instance() {
|
||||
return s_instance;
|
||||
}
|
||||
|
||||
bool busy() const {
|
||||
if (streamCount_ == 0) return false;
|
||||
for (uint8_t i = 0; i < streamCount_; i++) {
|
||||
if (!streams_[i].active) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool start(IR_Encoder* enc, const uint8_t* packet, uint8_t len) {
|
||||
if (enc == nullptr) return false;
|
||||
for (uint8_t i = 0; i < streamCount_; i++) {
|
||||
if (streams_[i].enc == enc) {
|
||||
return startStream(streams_[i], packet, len);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void irqForStream(size_t streamIndex) {
|
||||
if (streamIndex >= streamCount_) return;
|
||||
HAL_DMA_IRQHandler(&streams_[streamIndex].hdma);
|
||||
}
|
||||
|
||||
DMA_HandleTypeDef* dmaHandle(size_t streamIndex) {
|
||||
if (streamIndex >= streamCount_) return nullptr;
|
||||
return &streams_[streamIndex].hdma;
|
||||
}
|
||||
|
||||
private:
|
||||
struct TxStream {
|
||||
DMA_HandleTypeDef hdma{};
|
||||
DMA_Channel_TypeDef* dmaInstance = nullptr;
|
||||
IRQn_Type dmaIrq = IRQn_Type(0);
|
||||
uint32_t dmamuxRequest = 0;
|
||||
|
||||
IR_Encoder* enc = nullptr;
|
||||
GPIO_TypeDef* port = nullptr;
|
||||
uint16_t mask = 0;
|
||||
uint32_t setWord = 0;
|
||||
uint32_t resetWord = 0;
|
||||
|
||||
uint32_t* dmaBuf = nullptr;
|
||||
uint16_t bufLen = 0;
|
||||
uint16_t halfLen = 0;
|
||||
|
||||
IR_Encoder::IR_TxGateRun* runs = nullptr;
|
||||
size_t maxRuns = 0;
|
||||
|
||||
size_t runCount = 0;
|
||||
size_t runIndex = 0;
|
||||
uint16_t ticksLeftInRun = 0;
|
||||
bool carrierPhase = false;
|
||||
|
||||
uint32_t totalTicks = 0;
|
||||
volatile uint32_t ticksOutput = 0;
|
||||
|
||||
bool active = false;
|
||||
|
||||
void resetWave() {
|
||||
runIndex = 0;
|
||||
carrierPhase = false;
|
||||
ticksOutput = 0;
|
||||
totalTicks = 0;
|
||||
runCount = 0;
|
||||
ticksLeftInRun = 0;
|
||||
}
|
||||
|
||||
IR_DMA_TX_HOT uint32_t nextWord() {
|
||||
if (runIndex >= runCount) {
|
||||
return resetWord;
|
||||
}
|
||||
const bool gate = runs[runIndex].gate;
|
||||
if (!gate) {
|
||||
carrierPhase = false;
|
||||
} else {
|
||||
carrierPhase = !carrierPhase;
|
||||
}
|
||||
const uint32_t out = (gate && carrierPhase) ? setWord : resetWord;
|
||||
|
||||
if (ticksLeftInRun > 0) {
|
||||
ticksLeftInRun--;
|
||||
}
|
||||
if (ticksLeftInRun == 0) {
|
||||
runIndex++;
|
||||
if (runIndex < runCount) {
|
||||
ticksLeftInRun = runs[runIndex].lenTicks;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
IR_DMA_TX_HOT void fill(uint32_t* dst, uint16_t count) {
|
||||
if (dst == nullptr || count == 0) {
|
||||
return;
|
||||
}
|
||||
do {
|
||||
*dst++ = nextWord();
|
||||
} while (--count != 0);
|
||||
}
|
||||
|
||||
void onHalf() {
|
||||
ticksOutput += halfLen;
|
||||
fill(&dmaBuf[0], halfLen);
|
||||
}
|
||||
|
||||
void onComplete() {
|
||||
ticksOutput += halfLen;
|
||||
fill(&dmaBuf[halfLen], halfLen);
|
||||
}
|
||||
|
||||
void onError() {}
|
||||
};
|
||||
|
||||
static IrDmaTxStm32<MaxStreams>* s_instance;
|
||||
|
||||
Config cfg_{};
|
||||
TIM_HandleTypeDef* htim_ = nullptr;
|
||||
|
||||
TxStream streams_[MaxStreams]{};
|
||||
uint8_t streamCount_ = 0;
|
||||
|
||||
volatile uint8_t activeCount_ = 0;
|
||||
|
||||
static uint32_t u32ptr(const volatile void* p) {
|
||||
return (uint32_t)(uintptr_t)p;
|
||||
}
|
||||
|
||||
void startTimerIfNeeded() {
|
||||
if (htim_ == nullptr) return;
|
||||
if (activeCount_ != 1) return;
|
||||
|
||||
__HAL_TIM_DISABLE_DMA(htim_, TIM_DMA_UPDATE);
|
||||
__HAL_TIM_CLEAR_FLAG(htim_, TIM_FLAG_UPDATE);
|
||||
__HAL_TIM_SET_COUNTER(htim_, 0);
|
||||
__HAL_TIM_ENABLE_DMA(htim_, TIM_DMA_UPDATE);
|
||||
HAL_TIM_Base_Start(htim_);
|
||||
}
|
||||
|
||||
void stopTimerIfIdle() {
|
||||
if (htim_ == nullptr) return;
|
||||
if (activeCount_ != 0) return;
|
||||
|
||||
__HAL_TIM_DISABLE_DMA(htim_, TIM_DMA_UPDATE);
|
||||
HAL_TIM_Base_Stop(htim_);
|
||||
}
|
||||
|
||||
static TxStream* streamFromDma(DMA_HandleTypeDef* hdma) {
|
||||
if (s_instance == nullptr || hdma == nullptr) return nullptr;
|
||||
for (uint8_t i = 0; i < s_instance->streamCount_; i++) {
|
||||
if (hdma == &s_instance->streams_[i].hdma) {
|
||||
return &s_instance->streams_[i];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void dmaHalfCpltCb(DMA_HandleTypeDef* hdma) {
|
||||
auto* s = streamFromDma(hdma);
|
||||
if (s == nullptr || !s->active) return;
|
||||
s->onHalf();
|
||||
if (s_instance != nullptr && s->ticksOutput >= s->totalTicks) {
|
||||
s_instance->stopStream(*s);
|
||||
}
|
||||
}
|
||||
|
||||
static void dmaCpltCb(DMA_HandleTypeDef* hdma) {
|
||||
auto* s = streamFromDma(hdma);
|
||||
if (s == nullptr || !s->active) return;
|
||||
s->onComplete();
|
||||
if (s_instance != nullptr && s->ticksOutput >= s->totalTicks) {
|
||||
s_instance->stopStream(*s);
|
||||
}
|
||||
}
|
||||
|
||||
static void dmaErrorCb(DMA_HandleTypeDef* hdma) {
|
||||
auto* s = streamFromDma(hdma);
|
||||
if (s == nullptr) return;
|
||||
s->onError();
|
||||
if (s_instance != nullptr) {
|
||||
s_instance->stopStream(*s);
|
||||
}
|
||||
}
|
||||
|
||||
bool initStream(TxStream& s, const StreamCfg& chCfg) {
|
||||
s.enc = chCfg.enc;
|
||||
s.dmaInstance = chCfg.instance;
|
||||
s.dmaIrq = chCfg.irq;
|
||||
s.dmamuxRequest = chCfg.dmamuxRequest;
|
||||
|
||||
s.dmaBuf = chCfg.dmaWords;
|
||||
s.bufLen = chCfg.dmaWordCount;
|
||||
s.halfLen = (uint16_t)(chCfg.dmaWordCount / 2U);
|
||||
s.runs = chCfg.gateRuns;
|
||||
s.maxRuns = chCfg.maxGateRuns;
|
||||
|
||||
s.port = (s.enc != nullptr) ? s.enc->getPort() : nullptr;
|
||||
s.mask = (s.enc != nullptr) ? s.enc->getPinMask() : 0;
|
||||
s.setWord = (uint32_t)s.mask;
|
||||
s.resetWord = ((uint32_t)s.mask) << 16;
|
||||
|
||||
s.resetWave();
|
||||
|
||||
s.hdma.Instance = s.dmaInstance;
|
||||
s.hdma.Init.Request = s.dmamuxRequest;
|
||||
s.hdma.Init.Direction = DMA_MEMORY_TO_PERIPH;
|
||||
s.hdma.Init.PeriphInc = DMA_PINC_DISABLE;
|
||||
s.hdma.Init.MemInc = DMA_MINC_ENABLE;
|
||||
s.hdma.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
|
||||
s.hdma.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
|
||||
s.hdma.Init.Mode = DMA_CIRCULAR;
|
||||
s.hdma.Init.Priority = DMA_PRIORITY_HIGH;
|
||||
|
||||
HAL_DMA_DeInit(&s.hdma);
|
||||
if (HAL_DMA_Init(&s.hdma) != HAL_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
s.hdma.XferHalfCpltCallback = dmaHalfCpltCb;
|
||||
s.hdma.XferCpltCallback = dmaCpltCb;
|
||||
s.hdma.XferErrorCallback = dmaErrorCb;
|
||||
s.hdma.XferAbortCallback = nullptr;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool startStream(TxStream& s, const uint8_t* packet, uint8_t len) {
|
||||
if (s.enc == nullptr || s.port == nullptr || s.mask == 0) return false;
|
||||
if (s.active) return false;
|
||||
if (s.dmaBuf == nullptr || s.bufLen < 2 || s.halfLen == 0) return false;
|
||||
if (s.runs == nullptr || s.maxRuns == 0) return false;
|
||||
|
||||
s.resetWave();
|
||||
|
||||
s.runCount = IR_Encoder::buildGateRuns(packet, len, s.runs, s.maxRuns);
|
||||
if (s.runCount == 0) return false;
|
||||
|
||||
uint32_t total = 0;
|
||||
for (size_t i = 0; i < s.runCount; i++) total += s.runs[i].lenTicks;
|
||||
s.totalTicks = total;
|
||||
|
||||
s.runIndex = 0;
|
||||
s.ticksLeftInRun = s.runs[0].lenTicks;
|
||||
s.carrierPhase = false;
|
||||
|
||||
s.fill(&s.dmaBuf[0], s.bufLen);
|
||||
|
||||
s.port->BSRR = s.resetWord;
|
||||
|
||||
const uint32_t dst = u32ptr(&s.port->BSRR);
|
||||
if (HAL_DMA_Start_IT(&s.hdma, (uint32_t)(uintptr_t)s.dmaBuf, dst, s.bufLen) != HAL_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
s.active = true;
|
||||
activeCount_++;
|
||||
startTimerIfNeeded();
|
||||
return true;
|
||||
}
|
||||
|
||||
void stopStream(TxStream& s) {
|
||||
if (!s.active) return;
|
||||
|
||||
s.active = false;
|
||||
HAL_DMA_Abort_IT(&s.hdma);
|
||||
|
||||
if (s.port != nullptr) {
|
||||
s.port->BSRR = s.resetWord;
|
||||
}
|
||||
|
||||
if (s.enc != nullptr) {
|
||||
s.enc->externalFinishSend();
|
||||
}
|
||||
|
||||
if (activeCount_ > 0) activeCount_--;
|
||||
stopTimerIfIdle();
|
||||
}
|
||||
};
|
||||
|
||||
template<size_t MaxStreams>
|
||||
IrDmaTxStm32<MaxStreams>* IrDmaTxStm32<MaxStreams>::s_instance = nullptr;
|
||||
|
||||
inline void IrDmaTxStm32_onDmaHandle(DMA_HandleTypeDef* hdma) {
|
||||
HAL_DMA_IRQHandler(hdma);
|
||||
}
|
||||
|
||||
#elif defined(ARDUINO_ARCH_STM32)
|
||||
|
||||
#error "IrDmaTxStm32: добавьте ветку HAL для вашей серии STM32 (сейчас только STM32G4xx)."
|
||||
|
||||
#else
|
||||
|
||||
template<size_t MaxStreams = irproto::kDefaultDmaTxMaxStreams>
|
||||
class IrDmaTxStm32 {};
|
||||
|
||||
#endif
|
||||
107
PacketTypes.cpp
Normal file
107
PacketTypes.cpp
Normal file
@ -0,0 +1,107 @@
|
||||
#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,86 +21,30 @@ namespace PacketTypes
|
||||
IR_FOX::PackInfo *packInfo;
|
||||
uint16_t id;
|
||||
|
||||
virtual bool checkAddress() { return true; };
|
||||
void set(IR_FOX::PackInfo *packInfo, uint16_t id)
|
||||
{
|
||||
this->packInfo = packInfo;
|
||||
this->id = id;
|
||||
virtual bool checkAddress();
|
||||
void set(IR_FOX::PackInfo *packInfo, uint16_t 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
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
static uint16_t _getAddrFrom(BasePack *obj);
|
||||
static uint16_t _getAddrTo(BasePack *obj);
|
||||
static uint8_t _getDataSize(BasePack *obj);
|
||||
static uint8_t *_getDataPrt(BasePack *obj);
|
||||
static uint8_t _getDataRawSize(BasePack *obj);
|
||||
|
||||
public:
|
||||
bool available()
|
||||
{
|
||||
if (isAvailable)
|
||||
{
|
||||
isAvailable = false;
|
||||
isRawAvailable = false;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
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; };
|
||||
bool available();
|
||||
bool availableRaw();
|
||||
|
||||
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; };
|
||||
inline uint8_t getMsgRAW() { return packInfo->buffer[0]; };
|
||||
inline uint16_t getErrorCount() { return packInfo->err.all(); };
|
||||
inline uint8_t getErrorLowSignal() { return packInfo->err.lowSignal; };
|
||||
inline uint8_t getErrorHighSignal() { return packInfo->err.highSignal; };
|
||||
inline uint8_t getErrorOther() { return packInfo->err.other; };
|
||||
inline uint16_t getTunerTime() { return packInfo->rTime; };
|
||||
inline uint8_t *getDataRawPtr() { return packInfo->buffer; };
|
||||
/** Полный размер кадра в байтах (как packInfo.packSize); доступен для gotRaw (BasePack). */
|
||||
inline uint8_t getDataRawSize() { return _getDataRawSize(this); };
|
||||
};
|
||||
|
||||
class Data : public BasePack
|
||||
@ -114,20 +58,14 @@ namespace PacketTypes
|
||||
DataOffset = 5;
|
||||
}
|
||||
|
||||
uint16_t getAddrFrom() { return _getAddrFrom(this); };
|
||||
uint16_t getAddrTo() { return _getAddrTo(this); };
|
||||
inline uint16_t getAddrFrom() { return _getAddrFrom(this); };
|
||||
inline uint16_t getAddrTo() { return _getAddrTo(this); };
|
||||
|
||||
uint8_t getDataSize() { return _getDataSize(this); };
|
||||
uint8_t *getDataPrt() { return _getDataPrt(this); };
|
||||
uint8_t getDataRawSize() { return _getDataRawSize(this); };
|
||||
inline uint8_t getDataSize() { return _getDataSize(this); };
|
||||
inline uint8_t *getDataPrt() { return _getDataPrt(this); };
|
||||
|
||||
private:
|
||||
bool checkAddress() override
|
||||
{
|
||||
bool ret;
|
||||
IR_FOX::checkAddressRuleApply(getAddrTo(), this->id, ret);
|
||||
return ret;
|
||||
}
|
||||
bool checkAddress() override;
|
||||
};
|
||||
|
||||
class DataBack : public BasePack
|
||||
@ -141,29 +79,14 @@ namespace PacketTypes
|
||||
DataOffset = 3;
|
||||
}
|
||||
|
||||
uint16_t getAddrFrom() { return _getAddrFrom(this); };
|
||||
uint16_t getAddrTo() { return _getAddrTo(this); };
|
||||
inline uint16_t getAddrFrom() { return _getAddrFrom(this); };
|
||||
inline uint16_t getAddrTo() { return _getAddrTo(this); };
|
||||
|
||||
uint8_t getDataSize() { return _getDataSize(this); };
|
||||
uint8_t *getDataPrt() { return _getDataPrt(this); };
|
||||
uint8_t getDataRawSize() { return _getDataRawSize(this); };
|
||||
inline uint8_t getDataSize() { return _getDataSize(this); };
|
||||
inline uint8_t *getDataPrt() { return _getDataPrt(this); };
|
||||
|
||||
private:
|
||||
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;
|
||||
}
|
||||
bool checkAddress() override;
|
||||
};
|
||||
|
||||
class Accept : public BasePack
|
||||
@ -176,11 +99,11 @@ namespace PacketTypes
|
||||
DataOffset = 3;
|
||||
}
|
||||
|
||||
uint16_t getAddrFrom() { return _getAddrFrom(this); };
|
||||
uint8_t getCustomByte() { return packInfo->buffer[DataOffset]; };
|
||||
inline uint16_t getAddrFrom() { return _getAddrFrom(this); };
|
||||
inline uint8_t getCustomByte() { return packInfo->buffer[DataOffset]; };
|
||||
|
||||
private:
|
||||
bool checkAddress() override { return true; }
|
||||
bool checkAddress() override;
|
||||
};
|
||||
|
||||
class Request : public BasePack
|
||||
@ -194,168 +117,11 @@ namespace PacketTypes
|
||||
DataOffset = 3;
|
||||
}
|
||||
|
||||
uint16_t getAddrFrom() { return _getAddrFrom(this); };
|
||||
uint16_t getAddrTo() { return _getAddrTo(this); };
|
||||
inline uint16_t getAddrFrom() { return _getAddrFrom(this); };
|
||||
inline uint16_t getAddrTo() { return _getAddrTo(this); };
|
||||
|
||||
private:
|
||||
bool checkAddress() override
|
||||
{
|
||||
bool ret;
|
||||
IR_FOX::checkAddressRuleApply(getAddrTo(), this->id, ret);
|
||||
return ret;
|
||||
}
|
||||
bool checkAddress() override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// 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:
|
||||
// };
|
||||
|
||||
39
RingBuffer.h
Normal file
39
RingBuffer.h
Normal file
@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
#include "Arduino.h"
|
||||
template <typename T, unsigned int BufferSize>
|
||||
class RingBuffer {
|
||||
public:
|
||||
RingBuffer() : start(0), end(0) {}
|
||||
|
||||
bool isFull() const {
|
||||
return ((end + 1) % BufferSize) == start;
|
||||
}
|
||||
|
||||
bool isEmpty() const {
|
||||
return start == end;
|
||||
}
|
||||
|
||||
void push(T element) {
|
||||
noInterrupts();
|
||||
if (!isFull()) {
|
||||
data[end] = element;
|
||||
end = (end + 1) % BufferSize;
|
||||
}
|
||||
interrupts();
|
||||
}
|
||||
|
||||
T* pop() {
|
||||
noInterrupts();
|
||||
T* value = nullptr;
|
||||
if (!isEmpty()) {
|
||||
value = &data[start];
|
||||
start = (start + 1) % BufferSize;
|
||||
}
|
||||
interrupts();
|
||||
return value;
|
||||
}
|
||||
|
||||
private:
|
||||
T data[BufferSize];
|
||||
unsigned int start, end;
|
||||
};
|
||||
174
ir_protocol_gate_runs_sim.py
Normal file
174
ir_protocol_gate_runs_sim.py
Normal file
@ -0,0 +1,174 @@
|
||||
#!/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())
|
||||
41082
ref/DMA_no_send__extRX.txt
Normal file
41082
ref/DMA_no_send__extRX.txt
Normal file
File diff suppressed because it is too large
Load Diff
14922
ref/DMA_self_frontlog.txt
Normal file
14922
ref/DMA_self_frontlog.txt
Normal file
File diff suppressed because it is too large
Load Diff
64
ref/IR_DMA_TX_backend.md
Normal file
64
ref/IR_DMA_TX_backend.md
Normal file
@ -0,0 +1,64 @@
|
||||
# Контракт бэкенда DMA-TX ИК (`IrDmaTxStm32`)
|
||||
|
||||
Платформа: **STM32G4**, Arduino STM32. Передача: **DMA memory → GPIO BSRR**, запрос от **TIM UPDATE** (частота `carrierFrec×2` из `IR_Encoder::beginClockOnly`).
|
||||
|
||||
### Число потоков (шаблон)
|
||||
|
||||
Класс: **`IrDmaTxStm32<MaxStreams>`**. Число слотов в `Config::streams[]` и внутреннем массиве задаётся **в коде**, без `-D` и без макроса до инклюда:
|
||||
|
||||
```cpp
|
||||
constexpr size_t kStreams = 2;
|
||||
static IrDmaTxStm32<kStreams> dma;
|
||||
IrDmaTxStm32<kStreams>::Config cfg;
|
||||
// IRQ: IrDmaTxStm32<kStreams>::instance()
|
||||
```
|
||||
|
||||
По умолчанию: **`IrDmaTxStm32<>`** эквивалентно **`IrDmaTxStm32<irproto::kDefaultDmaTxMaxStreams>`** (`IR_config.h`, обычно 4). Реализация в заголовке (отдельного `.cpp` нет).
|
||||
|
||||
## Роль библиотеки
|
||||
|
||||
- Разбор пакета в RLE-пробеги: `IR_Encoder::buildGateRuns`.
|
||||
- Генерация слов для BSRR (несущая/тишина по тикам), предзаполнение буфера и дозаполнение по прерываниям half/complete.
|
||||
- Настройка канала DMA, DMAMUX, кольцевой режим, старт/стоп DMA и таймера, колбэки HAL.
|
||||
- В `begin()` только `HAL_NVIC_EnableIRQ` для каналов DMA (без `SetPriority`).
|
||||
|
||||
## Роль прошивки (клиента)
|
||||
|
||||
### Буферы и размеры
|
||||
|
||||
На **каждый** поток в `StreamCfg` клиент передаёт:
|
||||
|
||||
| Поле | Смысл |
|
||||
|------|--------|
|
||||
| `dmaWords` | Указатель на массив `uint32_t` — слова для записи в BSRR. |
|
||||
| `dmaWordCount` | Число **слов** (32-bit), **чётное**, ≥ 2. Половина — один «полубуфер» для HT/TC IRQ. |
|
||||
| `gateRuns` | Массив `IR_Encoder::IR_TxGateRun` для выхода `buildGateRuns`. |
|
||||
| `maxGateRuns` | Длина этого массива. Должен быть достаточен для самого длинного кадра. |
|
||||
|
||||
Память и выравнивание — ответственность клиента; типичные порядки: 4096 слов DMA, 1024 ранов (как в машинке).
|
||||
|
||||
### Таймер и DMA
|
||||
|
||||
- `HardwareTimer` / тот же TIM, что и `beginClockOnly`, без конкурирующего `attachInterrupt` на UPDATE.
|
||||
- `instance`, `irq`, `dmamuxRequest` (например `DMA_REQUEST_TIM17_UP`) — из схемы платы; оба потока на одном TIM обычно используют **один** `TIMx_UP` в DMAMUX.
|
||||
|
||||
### Приоритеты NVIC
|
||||
|
||||
Не задаются в библиотеке. После `begin()` клиент выставляет preempt/sub для `DMA1_ChannelN` (и согласует с приёмом EXTI и др.), например общей функцией вроде `Car_applyInterruptPriorities()`.
|
||||
|
||||
### Прерывания DMA
|
||||
|
||||
Библиотека **не** объявляет `DMA1_ChannelN_IRQHandler`. В одном `.cpp` прошивки — единственное определение на канал, внутри:
|
||||
|
||||
`IrDmaTxStm32<N>::instance()->irqForStream(i)` (тот же **N**, что у объекта бэкенда) или `IrDmaTxStm32_onDmaHandle(hdma)`.
|
||||
|
||||
## Контракт `IR_Encoder::setExternalTxBackend`
|
||||
|
||||
Подключение: `setExternalTxBackend(startFn, busyFn, ctx)`.
|
||||
|
||||
- **`startFn(ctx, enc, packet, len)`** — должен вызвать `IrDmaTxStm32<N>::start(enc, packet, len)` (или обёртку). Возвращает успех старта DMA.
|
||||
- **`busyFn(ctx)`** — пока возвращает «занято», новая отправка не стартует. У `IrDmaTxStm32<N>::busy()`: **true**, если **все** настроенные потоки в передаче (для двух передатчиков — оба активны); иначе можно запустить второй канал.
|
||||
|
||||
## Сбой `begin()`
|
||||
|
||||
При ошибке `HAL_DMA_Init` и т.п. `begin()` возвращает `false`, `instance()` не используется для IRQ до успешного `begin()`.
|
||||
87
ref/IR_EDGE_TRACE_FORMAT.md
Normal file
87
ref/IR_EDGE_TRACE_FORMAT.md
Normal file
@ -0,0 +1,87 @@
|
||||
# Формат журнала фронтов ИК (`IR_EDGE_TRACE`)
|
||||
|
||||
Включается в `IR_config.h`: раскомментируйте `#define IR_EDGE_TRACE`.
|
||||
Размер кольца задаётся `IR_EDGE_TRACE_CAPACITY` (по умолчанию 512 записей).
|
||||
Память: примерно `IR_EDGE_TRACE_CAPACITY × 6` байт на экземпляр `IR_DecoderRaw`.
|
||||
|
||||
## Назначение
|
||||
|
||||
В ISR на каждый аппаратный фронт на линии приёмника пишется запись: абсолютное время `micros()`, уровень линии после фронта, флаги. Это **отдельное** кольцо от `subBuffer`: при переполнении `subBuffer` фронты в журнале всё равно сохраняются, пока не заполнится это кольцо.
|
||||
|
||||
При **передаче ИК по DMA** на STM32 важно, чтобы **прерывание приёма (EXTI)** имело **более высокий приоритет NVIC**, чем DMA канала передачи — иначе метки времени и сам поток фронтов в логе искажаются. См. **[`IR_DMA_ISR_signal_analysis.md`](../IR_DMA_ISR_signal_analysis.md)** (раздел 2.1).
|
||||
|
||||
## Вывод в Serial
|
||||
|
||||
При включённом `IR_EDGE_TRACE` протокол **сам** сбрасывает накопленные фронты в конце каждого `IR_DecoderRaw::tick()` (и на ветке «subBuffer пуст»): в цикле вызывается `edgeTraceFlushChunk(Serial, 48)`, пока в кольце есть записи.
|
||||
|
||||
Вручную тот же смысл: `edgeTraceFlushChunk(Print &out, maxRec)` печатает **ровно одну строку** (если есть что выгрузить; при пустом кольце выход без печати):
|
||||
|
||||
1. Символ перевода строки `\n` (отделяет блок от предыдущего вывода).
|
||||
2. Литеральный префикс **`@IRF1v1:`** (удобно grep-ать; не используйте этот текст в других `Serial.print`, чтобы строки не сливались).
|
||||
3. **Нижний регистр hex** без пробелов: полезная нагрузка бинарного блока ниже.
|
||||
4. `\n` в конце.
|
||||
|
||||
Другой текст (например `IR raw:` из `IRDEBUG_SERIAL_PACK`) не содержит `@IRF1v1:`, поэтому визуально и по парсеру блоки разделимы.
|
||||
|
||||
Если авто-сброс в `tick()` отключён или нужен другой `Print`, вызывайте `edgeTraceFlushChunk` в цикле, пока возвращаемое значение > 0.
|
||||
|
||||
## Бинарная полезная нагрузка (до кодирования в hex)
|
||||
|
||||
Все многобайтовые целые — **little-endian**, порядок байт от младшего к старшему.
|
||||
|
||||
| Смещение | Размер | Поле |
|
||||
|----------|--------|------|
|
||||
| 0 | 1 | **meta** |
|
||||
| 1 | 2 | **count** — число записей в этой строке (`uint16_t`) |
|
||||
| 3 | `count × 6` | Массив записей |
|
||||
|
||||
### meta (байт)
|
||||
|
||||
| Бит | Значение |
|
||||
|-----|----------|
|
||||
| 0 | **overflow**: кольцо хотя бы раз переполнилось с момента последнего `edgeTraceClear()`; новые фронты терялись, пока не освободилось место. Сбрасывается только `edgeTraceClear()`. |
|
||||
| 1 | **truncated**: после этой выгрузки в буфере ещё есть записи (chunk урезан лимитом `maxRec` или внутренним максимумом 64). |
|
||||
|
||||
Биты 2–7 зарезервированы (0).
|
||||
|
||||
### Одна запись (6 байт)
|
||||
|
||||
| Смещение в записи | Размер | Поле |
|
||||
|-------------------|--------|------|
|
||||
| 0 | 4 | **t_us** — значение `micros()` на момент фронта (`uint32_t`). При переполнении `micros()` (~70 мин) разницы между соседними записями всё ещё корректны, если обрабатывать как unsigned. |
|
||||
| 4 | 1 | **level** — уровень входа приёмника после фронта: `0` = LOW, `1` = HIGH (как `port->IDR & mask` в ISR). |
|
||||
| 5 | 1 | **flags** |
|
||||
| | | бит 0 **SKIP_DECODE** (`IR_EDGE_TRACE_F_SKIP_DECODE`): фронт записан, но в `subBuffer` **не** попал, потому что был активен `isPairSending` (пара передаёт). Алгоритм декодирования этот фронт не видит. |
|
||||
|
||||
## Минимальный разбор (Python 3)
|
||||
|
||||
```python
|
||||
import binascii, re
|
||||
|
||||
def parse_irf1_line(line: str):
|
||||
m = re.search(r"@IRF1v1:([0-9a-f]+)\s*$", line.strip())
|
||||
if not m:
|
||||
return None
|
||||
raw = binascii.unhexlify(m.group(1))
|
||||
meta, cnt_lo, cnt_hi = raw[0], raw[1], raw[2]
|
||||
count = cnt_lo | (cnt_hi << 8)
|
||||
recs = []
|
||||
p = 3
|
||||
for _ in range(count):
|
||||
t = raw[p] | (raw[p+1]<<8) | (raw[p+2]<<16) | (raw[p+3]<<24)
|
||||
level, flags = raw[p+4], raw[p+5]
|
||||
recs.append((t, level, flags))
|
||||
p += 6
|
||||
return {
|
||||
"overflow": bool(meta & 1),
|
||||
"truncated": bool(meta & 2),
|
||||
"count": count,
|
||||
"records": recs,
|
||||
}
|
||||
```
|
||||
|
||||
## Рекомендации по съёму
|
||||
|
||||
- При очень плотном потоке фронтов кольцо всё же может переполниться до следующего `tick()` — увеличьте `IR_EDGE_TRACE_CAPACITY` или уменьшите нагрузку на ISR.
|
||||
- Для «чистого» лога отключите или сильно урежьте `IRDEBUG_SERIAL_PACK`, иначе объём Serial будет очень большим.
|
||||
- Для полного сброса состояния перед тестом: `edgeTraceClear()`.
|
||||
289
ref/ISR_self.txt
Normal file
289
ref/ISR_self.txt
Normal file
@ -0,0 +1,289 @@
|
||||
|
||||
IR raw: 110 01010 101 00000000 101 00000000 100 11111101 010 11101000 101 01011000 101 00000000 101 00000000 100 11011101 010 11011000 => OK: SendInfo_v1_1, Empty_Command, Empty_Command
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
IR sendTimer()
|
||||
IR Laser();
|
||||
IR SKIP LASER
|
||||
|
||||
IR sendTimer()
|
||||
IR irSend()
|
||||
|
||||
|
||||
IR raw: => ERROR: TIMEOUT, rx_data_size = 10, but only 10 bytes received
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
|
||||
IR raw: 110 01010 101 00000000 101 00000000 100 11111101 010 11101000 101 01011000 101 00000000 101 00000000 100 11011101 010 11011000 => OK: SendInfo_v1_1, Empty_Command, Empty_Command
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
IR sendTimer()
|
||||
IR Laser();
|
||||
IR SKIP LASER
|
||||
|
||||
IR sendTimer()
|
||||
IR irSend()
|
||||
|
||||
|
||||
IR raw: => ERROR: TIMEOUT, rx_data_size = 10, but only 10 bytes received
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
|
||||
IR raw: 110 01010 101 00000000 101 00000000 100 11111101 010 11101000 101 01011000 101 00000000 101 00000000 100 11011101 010 11011000 => OK: SendInfo_v1_1, Empty_Command, Empty_Command
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
IR sendTimer()
|
||||
IR Laser();
|
||||
IR SKIP LASER
|
||||
|
||||
IR sendTimer()
|
||||
IR irSend()
|
||||
|
||||
|
||||
IR raw: => ERROR: TIMEOUT, rx_data_size = 10, but only 10 bytes received
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
|
||||
IR raw: 110 01010 101 00000000 101 00000000 100 11111101 010 11101000 101 01011000 101 00000000 101 00000000 100 11011101 010 11011000 => OK: SendInfo_v1_1, Empty_Command, Empty_Command
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
IR sendTimer()
|
||||
IR Laser();
|
||||
IR SKIP LASER
|
||||
|
||||
IR sendTimer()
|
||||
IR irSend()
|
||||
|
||||
|
||||
IR raw: => ERROR: TIMEOUT, rx_data_size = 10, but only 10 bytes received
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
|
||||
IR raw: 110 01010 101 00000000 101 00000000 100 11111101 010 11101000 101 01011000 101 00000000 101 00000000 100 11011101 010 11011000 => OK: SendInfo_v1_1, Empty_Command, Empty_Command
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
IR sendTimer()
|
||||
IR Laser();
|
||||
IR SKIP LASER
|
||||
|
||||
IR sendTimer()
|
||||
IR irSend()
|
||||
|
||||
|
||||
IR raw: => ERROR: TIMEOUT, rx_data_size = 10, but only 10 bytes received
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
|
||||
IR raw: 110 01010 101 00000000 101 00000000 100 11111101 010 11101000 101 01011000 101 00000000 101 00000000 100 11011101 010 11011000 => OK: SendInfo_v1_1, Empty_Command, Empty_Command
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
IR sendTimer()
|
||||
IR Laser();
|
||||
IR SKIP LASER
|
||||
|
||||
IR sendTimer()
|
||||
IR irSend()
|
||||
|
||||
|
||||
IR raw: => ERROR: TIMEOUT, rx_data_size = 10, but only 10 bytes received
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
|
||||
IR raw: 110 01010 101 00000000 101 00000000 100 11111101 010 11101000 101 01011000 101 00000000 101 00000000 100 11011101 010 11011000 => OK: SendInfo_v1_1, Empty_Command, Empty_Command
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
IR sendTimer()
|
||||
IR Laser();
|
||||
IR SKIP LASER
|
||||
|
||||
IR sendTimer()
|
||||
IR irSend()
|
||||
|
||||
|
||||
IR raw: => ERROR: TIMEOUT, rx_data_size = 10, but only 10 bytes received
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
|
||||
IR raw: 110 01010 101 00000000 101 00000000 100 11111101 010 11101000 101 01011000 101 00000000 101 00000000 100 11011101 010 11011000 => OK: SendInfo_v1_1, Empty_Command, Empty_Command
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
IR sendTimer()
|
||||
IR Laser();
|
||||
IR SKIP LASER
|
||||
|
||||
IR sendTimer()
|
||||
IR irSend()
|
||||
|
||||
|
||||
IR raw: => ERROR: TIMEOUT, rx_data_size = 10, but only 10 bytes received
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
|
||||
IR raw: 110 01010 101 00000000 101 00000000 100 11111101 010 11101000 101 01011000 101 00000000 101 00000000 100 11011101 010 11011000 => OK: SendInfo_v1_1, Empty_Command, Empty_Command
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
IR sendTimer()
|
||||
IR Laser();
|
||||
IR SKIP LASER
|
||||
|
||||
IR sendTimer()
|
||||
IR irSend()
|
||||
|
||||
|
||||
IR raw: => ERROR: TIMEOUT, rx_data_size = 10, but only 10 bytes received
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
|
||||
IR raw: 110 01010 101 00000000 101 00000000 100 11111101 010 11101000 101 01011000 101 00000000 101 00000000 100 11011101 010 11011000 => OK: SendInfo_v1_1, Empty_Command, Empty_Command
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
IR sendTimer()
|
||||
IR Laser();
|
||||
IR SKIP LASER
|
||||
|
||||
IR sendTimer()
|
||||
IR irSend()
|
||||
|
||||
|
||||
IR raw: => ERROR: TIMEOUT, rx_data_size = 10, but only 10 bytes received
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
|
||||
IR raw: 110 01010 101 00000000 101 00000000 100 11111101 010 11101000 101 01011000 101 00000000 101 00000000 100 11011101 010 11011000 => OK: SendInfo_v1_1, Empty_Command, Empty_Command
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
IR sendTimer()
|
||||
IR Laser();
|
||||
IR SKIP LASER
|
||||
|
||||
IR sendTimer()
|
||||
IR irSend()
|
||||
|
||||
|
||||
IR raw: => ERROR: TIMEOUT, rx_data_size = 10, but only 10 bytes received
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
|
||||
IR raw: 110 01010 101 00000000 101 00000000 100 11111101 010 11101000 101 01011000 101 00000000 101 00000000 100 11011101 010 11011000 => OK: SendInfo_v1_1, Empty_Command, Empty_Command
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
IR sendTimer()
|
||||
IR Laser();
|
||||
IR SKIP LASER
|
||||
|
||||
IR sendTimer()
|
||||
IR irSend()
|
||||
|
||||
|
||||
IR raw: => ERROR: TIMEOUT, rx_data_size = 10, but only 10 bytes received
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
|
||||
IR raw: 110 01010 101 00000000 101 00000000 100 11111101 010 11101000 101 01011000 101 00000000 101 00000000 100 11011101 010 11011000 => OK: SendInfo_v1_1, Empty_Command, Empty_Command
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
IR sendTimer()
|
||||
IR Laser();
|
||||
IR SKIP LASER
|
||||
|
||||
IR sendTimer()
|
||||
IR irSend()
|
||||
|
||||
|
||||
IR raw: => ERROR: TIMEOUT, rx_data_size = 10, but only 10 bytes received
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
|
||||
IR raw: 110 01010 101 00000000 101 00000000 100 11111101 010 11101000 101 01011000 101 00000000 101 00000000 100 11011101 010 11011000 => OK: SendInfo_v1_1, Empty_Command, Empty_Command
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
IR sendTimer()
|
||||
IR Laser();
|
||||
IR SKIP LASER
|
||||
|
||||
IR sendTimer()
|
||||
IR irSend()
|
||||
|
||||
|
||||
IR raw: => ERROR: TIMEOUT, rx_data_size = 10, but only 10 bytes received
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
|
||||
IR raw: 110 01010 101 00000000 101 00000000 100 11111101 010 11101000 101 01011000 101 00000000 101 00000000 100 11011101 010 11011000 => OK: SendInfo_v1_1, Empty_Command, Empty_Command
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
IR sendTimer()
|
||||
IR Laser();
|
||||
IR SKIP LASER
|
||||
|
||||
IR sendTimer()
|
||||
IR irSend()
|
||||
|
||||
|
||||
IR raw: => ERROR: TIMEOUT, rx_data_size = 10, but only 10 bytes received
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
|
||||
IR raw: 110 01010 101 00000000 101 00000000 100 11111101 010 11101000 101 01011000 101 00000000 101 00000000 100 11011101 010 11011000 => OK: SendInfo_v1_1, Empty_Command, Empty_Command
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
IR sendTimer()
|
||||
IR Laser();
|
||||
IR SKIP LASER
|
||||
|
||||
IR sendTimer()
|
||||
IR irSend()
|
||||
|
||||
|
||||
IR raw: => ERROR: TIMEOUT, rx_data_size = 10, but only 10 bytes received
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
|
||||
IR raw: 110 01010 101 00000000 101 00000000 100 11111101 010 11101000 101 01011000 101 00000000 101 00000000 100 11011101 010 11011000 => OK: SendInfo_v1_1, Empty_Command, Empty_Command
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
IR sendTimer()
|
||||
IR Laser();
|
||||
IR SKIP LASER
|
||||
|
||||
IR sendTimer()
|
||||
IR irSend()
|
||||
|
||||
|
||||
IR raw: => ERROR: TIMEOUT, rx_data_size = 10, but only 10 bytes received
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
|
||||
IR raw: 110 01010 101 00000000 101 00000000 100 11111101 010 11101000 101 01011000 101 00000000 101 00000000 100 11011101 010 11011000 => OK: SendInfo_v1_1, Empty_Command, Empty_Command
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
IR sendTimer()
|
||||
IR Laser();
|
||||
IR SKIP LASER
|
||||
|
||||
IR sendTimer()
|
||||
IR irSend()
|
||||
|
||||
|
||||
IR raw: => ERROR: TIMEOUT, rx_data_size = 10, but only 10 bytes received
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
|
||||
IR raw: 110 01010 101 00000000 101 00000000 100 11111101 010 11101000 101 01011000 101 00000000 101 00000000 100 11011101 010 11011000 => OK: SendInfo_v1_1, Empty_Command, Empty_Command
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
IR sendTimer()
|
||||
IR Laser();
|
||||
IR SKIP LASER
|
||||
|
||||
IR sendTimer()
|
||||
IR irSend()
|
||||
|
||||
|
||||
IR raw: => ERROR: TIMEOUT, rx_data_size = 10, but only 10 bytes received
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
|
||||
IR raw: 110 01010 101 00000000 101 00000000 100 11111101 010 11101000 101 01011000 101 00000000 101 00000000 100 11011101 010 11011000 => OK: SendInfo_v1_1, Empty_Command, Empty_Command
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
IR sendTimer()
|
||||
IR Laser();
|
||||
IR SKIP LASER
|
||||
|
||||
IR sendTimer()
|
||||
IR irSend()
|
||||
|
||||
|
||||
IR raw: => ERROR: TIMEOUT, rx_data_size = 10, but only 10 bytes received
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
|
||||
IR raw: 110 01010 101 00000000 101 00000000 100 11111101 010 11101000 101 01011000 101 00000000 101 00000000 100 11011101 010 11011000 => OK: SendInfo_v1_1, Empty_Command, Empty_Command
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
IR sendTimer()
|
||||
IR Laser();
|
||||
IR SKIP LASER
|
||||
|
||||
IR sendTimer()
|
||||
IR irSend()
|
||||
|
||||
|
||||
IR raw: => ERROR: TIMEOUT, rx_data_size = 10, but only 10 bytes received
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
|
||||
IR raw: 110 01010 101 00000000 101 00000000 100 11111101 010 11101000 101 01011000 101 00000000 101 00000000 100 11011101 010 11011000 => OK: SendInfo_v1_1, Empty_Command, Empty_Command
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
IR sendTimer()
|
||||
IR Laser();
|
||||
IR SKIP LASER
|
||||
|
||||
IR sendTimer()
|
||||
IR irSend()
|
||||
|
||||
|
||||
IR raw: => ERROR: TIMEOUT, rx_data_size = 10, but only 10 bytes received
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
|
||||
IR raw: 110 01010 101 00000000 101 00000000 100 11111101 010 11101000 101 01011000 101 00000000 101 00000000 100 11011101 010 11011000 => OK: SendInfo_v1_1, Empty_Command, Empty_Command
|
||||
IR hex: CA 00 00 FD E8 58 00 00 DD D8
|
||||
17170
ref/ISR_self_frontlog.txt
Normal file
17170
ref/ISR_self_frontlog.txt
Normal file
File diff suppressed because it is too large
Load Diff
13744
ref/OtherCar_DMA_send__NoSelfTx.txt
Normal file
13744
ref/OtherCar_DMA_send__NoSelfTx.txt
Normal file
File diff suppressed because it is too large
Load Diff
13
ref/point response.txt
Normal file
13
ref/point response.txt
Normal file
@ -0,0 +1,13 @@
|
||||
IR raw: 110 01001 011 01111101 011 00100110 101 00000000 100 00000000 101 01110110 101 00100111 010 10011100 101 01101100 => OK: Laser_ON, LED_Left_BlinkTest
|
||||
IR hex: C9 7D 26 00 00 76 27 9C 6C
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
IR raw: 110 01001 011 01111101 011 00100110 101 00000000 100 00000000 101 01110110 101 00100111 010 10011100 101 01101100 => OK: Laser_ON, LED_Left_BlinkTest
|
||||
|
||||
|
||||
721
tools/ir_decoder_sim.py
Normal file
721
tools/ir_decoder_sim.py
Normal file
@ -0,0 +1,721 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Офлайн-симуляция IR_DecoderRaw::tick + writeToBuffer по журналу @IRF1v1:
|
||||
См. ref/IR_EDGE_TRACE_FORMAT.md
|
||||
|
||||
При FRAME_END: сводка (чистые биты / burst / отброшенные фронты), список точечных
|
||||
исправлений (преамбула, пропуск такта, поджатие low/high, sync), при crc_ok=False —
|
||||
разбор несовпадения CRC; в первой строке пакет как hex= и bin= (8 бит на байт MSB-first,
|
||||
байты через пробел); отдельная строка «синхро» — счётчик ошибок 1-го бита тройки (как
|
||||
err_syncBit в прошивке) и принятые тройки sync-битов между байтами (2-й и 3-й в прошивке
|
||||
не сверяются с шаблоном, только пишутся в поток — их значения для наглядности сдвига).
|
||||
WRONG_PACK_SYNC — отдельное событие с причиной и тем же блоком «синхро».
|
||||
|
||||
Флаг -v/--verbose: дополнительно каждая строка для «чистого» бита (aroundRise) и
|
||||
отброшенных фронтов; без флага эти события только в счётчиках сводки.
|
||||
|
||||
Не моделирует IRDEBUG_SERIAL_SOFT_REJECT (жёсткий Wrong sync).
|
||||
Таймауты: между фронтами, если gap > IR_timeout*2 и isRecive — checkTimeout.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import binascii
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Tuple
|
||||
|
||||
# --- IR_config.h (как в прошивке, целочисленно) ---
|
||||
CARRIER_FREC = 38000
|
||||
CARRIER_PERIOD = 1000000 // CARRIER_FREC
|
||||
BIT_ACTIVE_TAKTS = 25
|
||||
BIT_PAUSE_TAKTS = 12
|
||||
BIT_TAKTS = BIT_ACTIVE_TAKTS + BIT_PAUSE_TAKTS
|
||||
BIT_TIME = BIT_TAKTS * CARRIER_PERIOD
|
||||
TOLERANCE = 300
|
||||
SYNCBITS = 3
|
||||
BIT_PER_BYTE = 8
|
||||
MSGBYTES = 1
|
||||
CRC_BYTES = 2
|
||||
POLY1 = 0x31
|
||||
POLY2 = 0x8C
|
||||
IR_MASK_MSG_INFO = 0x1F
|
||||
PREAMB_PULSE = 3
|
||||
PREAMB_FRONTS = PREAMB_PULSE * 2
|
||||
BYTE_PER_PACK = 31
|
||||
DATA_BYTE_SIZE_MAX = MSGBYTES + 2 + 2 + BYTE_PER_PACK + CRC_BYTES
|
||||
FREE_FREC = False
|
||||
SKIP_DECODE_FLAG = 0x01
|
||||
|
||||
# Myagkie tsveta dlya terminala (256-color), ne iarkie default 31/32
|
||||
_ANSI_GREEN = "\033[38;5;107m" # priglushennyj zelenyj (ne iarkij 32)
|
||||
_ANSI_RED = "\033[38;5;174m" # pylno-rozovyj / myagkij krasnyj (ne iarkij 31)
|
||||
_ANSI_RESET = "\033[0m"
|
||||
# Zolotistyj / birjuzovyj + zhirnyj dlya hex i bin paketa v stroke FRAME_END
|
||||
_HEX_PACKET_FG = "\033[1;38;5;222m"
|
||||
_BIN_PACKET_FG = "\033[1;38;5;109m"
|
||||
|
||||
|
||||
def _bytes_bin_msb(data: bytes) -> str:
|
||||
"""8 bit na bajt (MSB pervyj, kak v writeToBuffer), bajty cherez probel."""
|
||||
return " ".join(f"{b:08b}" for b in data)
|
||||
|
||||
|
||||
def _use_terminal_color() -> bool:
|
||||
if os.environ.get("NO_COLOR"):
|
||||
return False
|
||||
try:
|
||||
return sys.stdout.isatty()
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _colorize_block(text: str, ok: bool, enabled: bool) -> str:
|
||||
if not enabled:
|
||||
return text
|
||||
code = _ANSI_GREEN if ok else _ANSI_RED
|
||||
return f"{code}{text}{_ANSI_RESET}"
|
||||
|
||||
|
||||
def _highlight_hex_bin_in_frame_line(line: str, ok: bool, enabled: bool) -> str:
|
||||
"""Odna stroka s FRAME_END: vydeljaet hex= i bin=."""
|
||||
if not enabled or "FRAME_END" not in line:
|
||||
return line
|
||||
parent = _ANSI_GREEN if ok else _ANSI_RED
|
||||
after = "\033[22m" + parent
|
||||
spans: list[tuple[int, int, str]] = []
|
||||
mhx = re.search(r"hex=([0-9a-fA-F]+)", line)
|
||||
if mhx:
|
||||
spans.append((mhx.start(1), mhx.end(1), _HEX_PACKET_FG))
|
||||
mbn = re.search(r"bin=([01 ]+)$", line)
|
||||
if mbn:
|
||||
spans.append((mbn.start(1), mbn.end(1), _BIN_PACKET_FG))
|
||||
if not spans:
|
||||
return line
|
||||
spans.sort(key=lambda t: t[0])
|
||||
out: list[str] = []
|
||||
last = 0
|
||||
for s, e, col in spans:
|
||||
out.append(line[last:s])
|
||||
out.append(col + line[s:e] + after)
|
||||
last = e
|
||||
out.append(line[last:])
|
||||
return "".join(out)
|
||||
|
||||
|
||||
def _highlight_frame_end_payloads(ev: str, ok: bool, enabled: bool) -> str:
|
||||
"""Vo vseh strokah FRAME_END vydeljaet hex= i bin= (v tom chisle povtor svodki v konce)."""
|
||||
if not enabled:
|
||||
return ev
|
||||
return "\n".join(_highlight_hex_bin_in_frame_line(L, ok, enabled) for L in ev.split("\n"))
|
||||
|
||||
|
||||
def _packet_event_tone(ev: str) -> str | None:
|
||||
"""'ok' | 'bad' | None — dlya pokrashki celogo bloka sobytija."""
|
||||
head = ev.split("\n", 1)[0]
|
||||
if "FRAME_END" in head:
|
||||
if "crc_ok=True" in head:
|
||||
return "ok"
|
||||
if "crc_ok=False" in head:
|
||||
return "bad"
|
||||
if "WRONG_PACK_SYNC" in head:
|
||||
return "bad"
|
||||
return None
|
||||
|
||||
|
||||
def rise_time_max(rise_sync: int) -> int:
|
||||
return rise_sync + TOLERANCE
|
||||
|
||||
|
||||
def rise_time_min(rise_sync: int) -> int:
|
||||
return rise_sync - TOLERANCE
|
||||
|
||||
|
||||
def ir_timeout_us(rise_sync: int) -> int:
|
||||
return rise_time_max(rise_sync) * (8 + SYNCBITS + 1)
|
||||
|
||||
|
||||
def around_rise(t: int, rise_sync: int) -> bool:
|
||||
return rise_time_min(rise_sync) < t < rise_time_max(rise_sync)
|
||||
|
||||
|
||||
def ceil_div(val: int, divider: int) -> int:
|
||||
ret = val // divider
|
||||
if (val << 4) // divider - (ret << 4) >= 8:
|
||||
ret += 1
|
||||
return ret
|
||||
|
||||
|
||||
def crc8(data: bytes, start: int, end: int, poly: int) -> int:
|
||||
crc = 0xFF
|
||||
for i in range(start, end):
|
||||
crc ^= data[i]
|
||||
for _ in range(8):
|
||||
if crc & 0x80:
|
||||
crc = ((crc << 1) ^ poly) & 0xFF
|
||||
else:
|
||||
crc = (crc << 1) & 0xFF
|
||||
return crc
|
||||
|
||||
|
||||
def crc_compute_bytes(data: bytearray, pack_size: int) -> Tuple[int, int, int]:
|
||||
"""Ожидаемые байты CRC: (crc1_hi, crc2_lo, len_payload) или (-1,-1,-1) при неверном pack_size."""
|
||||
if pack_size < 1 + CRC_BYTES or pack_size > DATA_BYTE_SIZE_MAX:
|
||||
return (-1, -1, -1)
|
||||
ln = pack_size - CRC_BYTES
|
||||
c1 = crc8(bytes(data), 0, ln, POLY1)
|
||||
c2 = crc8(bytes(data), 0, ln + 1, POLY2)
|
||||
return (c1 & 0xFF, c2 & 0xFF, ln)
|
||||
|
||||
|
||||
def crc_check(data: bytearray, pack_size: int) -> bool:
|
||||
ln = pack_size - CRC_BYTES
|
||||
c1 = crc8(bytes(data), 0, ln, POLY1)
|
||||
c2 = crc8(bytes(data), 0, ln + 1, POLY2)
|
||||
crc = ((c1 << 8) & ~0xFF) | (c2 & 0xFF)
|
||||
return data[ln] == ((crc >> 8) & 0xFF) and data[ln + 1] == (crc & 0xFF)
|
||||
|
||||
|
||||
def crc_failure_lines(data: bytearray, pack_size: int) -> List[str]:
|
||||
"""Подробности при несовпадении CRC."""
|
||||
out: List[str] = []
|
||||
exp_hi, exp_lo, ln = crc_compute_bytes(data, pack_size)
|
||||
if ln < 0:
|
||||
out.append(f" некорректный pack_size={pack_size} (ожидается 1..{DATA_BYTE_SIZE_MAX})")
|
||||
return out
|
||||
got_hi = data[pack_size - 2]
|
||||
got_lo = data[pack_size - 1]
|
||||
hdr = data[0]
|
||||
msg_t = (hdr >> 5) & 0x07
|
||||
out.append(
|
||||
f" байт[0]=0x{hdr:02x} тип_сообщения={msg_t} заявл._длина_кадра={hdr & IR_MASK_MSG_INFO} байт"
|
||||
)
|
||||
out.append(
|
||||
f" правило CRC: crc8(data[0..{ln - 1}], poly1=0x{POLY1:02x}) -> байт[{ln}]; "
|
||||
f"crc8(data[0..{ln}], poly2=0x{POLY2:02x}) -> байт[{ln + 1}]"
|
||||
)
|
||||
out.append(f" ожидалось: {exp_hi:02x} {exp_lo:02x} принято в кадре: {got_hi:02x} {got_lo:02x}")
|
||||
if got_hi != exp_hi:
|
||||
out.append(
|
||||
f" первый байт CRC не совпал - искажены данные [0..{ln - 1}] и/или этот байт CRC"
|
||||
)
|
||||
if got_lo != exp_lo:
|
||||
out.append(
|
||||
" второй байт CRC не совпал - в poly2 входит уже первый байт CRC; типично сдвиг битовой границы"
|
||||
)
|
||||
pl = data[:ln].hex()
|
||||
out.append(f" полезная нагрузка без CRC ({ln} байт): {pl}")
|
||||
return out
|
||||
|
||||
|
||||
@dataclass
|
||||
class EdgeRec:
|
||||
t_us: int
|
||||
level: int # 0/1 после фронта
|
||||
flags: int
|
||||
|
||||
|
||||
def parse_irf1_lines(text: str) -> List[EdgeRec]:
|
||||
out: List[EdgeRec] = []
|
||||
pat = re.compile(r"@IRF1v1:([0-9a-fA-F]+)\s*")
|
||||
for m in pat.finditer(text):
|
||||
hx = m.group(1)
|
||||
if len(hx) % 2:
|
||||
continue
|
||||
try:
|
||||
raw = binascii.unhexlify(hx)
|
||||
except binascii.Error:
|
||||
continue
|
||||
if len(raw) < 3:
|
||||
continue
|
||||
meta = raw[0]
|
||||
count = raw[1] | (raw[2] << 8)
|
||||
need = 3 + count * 6
|
||||
if len(raw) < need or count > 2000:
|
||||
continue
|
||||
p = 3
|
||||
for _ in range(count):
|
||||
t = raw[p] | (raw[p + 1] << 8) | (raw[p + 2] << 16) | (raw[p + 3] << 24)
|
||||
lvl = raw[p + 4]
|
||||
flg = raw[p + 5]
|
||||
out.append(EdgeRec(t, lvl & 1, flg))
|
||||
p += 6
|
||||
if meta & 1:
|
||||
pass # overflow — запись могла обрезаться
|
||||
return out
|
||||
|
||||
|
||||
@dataclass
|
||||
class DecoderSim:
|
||||
prev_rise: int = 0
|
||||
prev_fall: int = 0
|
||||
rise_period: int = 0
|
||||
high_time: int = 0
|
||||
low_time: int = 0
|
||||
last_edge_time: int = 0
|
||||
preamb_front_counter: int = 0
|
||||
is_preamb: bool = False
|
||||
is_recive: bool = False
|
||||
is_recive_raw: bool = False
|
||||
is_wrong_pack: bool = False
|
||||
is_buffer_overflow: bool = False
|
||||
rise_sync_time: int = BIT_TIME
|
||||
high_count: int = 0
|
||||
low_count: int = 0
|
||||
all_count: int = 0
|
||||
i_data_buffer: int = 0
|
||||
buf_bit_pos: int = 0
|
||||
next_control_bit: int = BIT_PER_BYTE
|
||||
is_data: bool = True
|
||||
i_sync_bit: int = 0
|
||||
err_sync_bit: int = 0
|
||||
data_buffer: bytearray = field(default_factory=lambda: bytearray(DATA_BYTE_SIZE_MAX))
|
||||
pack_size: int = 0
|
||||
errors_other: int = 0
|
||||
events: List[str] = field(default_factory=list)
|
||||
verbose: bool = False
|
||||
"""Подстроеки/исправления как в IR_DecoderRaw::tick (за текущий пакет)."""
|
||||
packet_fixes: List[str] = field(default_factory=list)
|
||||
_fatal_sync_event_sent: bool = False
|
||||
stat_clean_bits: int = 0
|
||||
stat_burst_edges: int = 0
|
||||
stat_debounce_rise: int = 0
|
||||
stat_debounce_fall: int = 0
|
||||
# Mezhdu bajtami: trojki sync-bitov (kak v writeToBuffer); oshibka schetaetsja tolko za 1-j bit
|
||||
stat_sync_first_error: int = 0
|
||||
sync_groups: List[str] = field(default_factory=list)
|
||||
sync_bits_current_group: List[int] = field(default_factory=list)
|
||||
|
||||
def _fix(self, msg: str) -> None:
|
||||
self.packet_fixes.append(msg)
|
||||
|
||||
def _clear_packet_state(self) -> None:
|
||||
self.packet_fixes.clear()
|
||||
self._fatal_sync_event_sent = False
|
||||
self.stat_clean_bits = 0
|
||||
self.stat_burst_edges = 0
|
||||
self.stat_debounce_rise = 0
|
||||
self.stat_debounce_fall = 0
|
||||
self.stat_sync_first_error = 0
|
||||
self.sync_groups.clear()
|
||||
self.sync_bits_current_group.clear()
|
||||
|
||||
def _sync_bit_consumed(self, bit_val: int) -> None:
|
||||
"""Odin prinjatyj sync-bit (bufBitPos++ v vetke sync v proshivke)."""
|
||||
self.sync_bits_current_group.append(bit_val & 1)
|
||||
if len(self.sync_bits_current_group) == SYNCBITS:
|
||||
self.sync_groups.append("".join(str(b) for b in self.sync_bits_current_group))
|
||||
self.sync_bits_current_group.clear()
|
||||
|
||||
def _sync_summary_lines(self, *, with_firmware_note: bool = False) -> List[str]:
|
||||
"""Stroki svodki po sinhrobitam dlya FRAME_END i WRONG_PACK_SYNC."""
|
||||
sg = "/".join(self.sync_groups) if self.sync_groups else "—"
|
||||
lines = [
|
||||
f" синхро: ошибок_1-го_бита(как_в_IR_DecoderRaw)={self.stat_sync_first_error}; "
|
||||
f"полных_троек={len(self.sync_groups)}; биты_троек={sg}"
|
||||
]
|
||||
if self.sync_bits_current_group:
|
||||
tail = "".join(str(b) for b in self.sync_bits_current_group)
|
||||
lines.append(f" синхро: незавершённая_тройка (уже приняты биты): {tail}")
|
||||
if with_firmware_note:
|
||||
lines.append(
|
||||
" синхро: в прошивке при ошибке считается только случай «1-й бит тройки совпал с последним "
|
||||
"data-битом» (errors.other++, err_syncBit); 2-й и 3-й sync-биты не сравниваются с эталоном."
|
||||
)
|
||||
return lines
|
||||
|
||||
def _emit_wrong_sync_fatal(self, t: int) -> None:
|
||||
lines = [
|
||||
f"t={t} WRONG_PACK_SYNC (аналог ERROR: Wrong sync bit в прошивке, err_sync_bit>={SYNCBITS})"
|
||||
]
|
||||
if self.packet_fixes:
|
||||
lines.append(" подстройки и исправления до ошибки:")
|
||||
for fx in self.packet_fixes:
|
||||
lines.append(f" - {fx}")
|
||||
lines.extend(self._sync_summary_lines(with_firmware_note=True))
|
||||
lines.append(
|
||||
" причина фатала: повторы неверного 1-го sync-бита накапливают err_syncBit до порога syncBits."
|
||||
)
|
||||
self.events.append("\n".join(lines))
|
||||
|
||||
def first_rx(self) -> None:
|
||||
self.is_buffer_overflow = False
|
||||
self.pack_size = 0
|
||||
self.buf_bit_pos = 0
|
||||
self.is_data = True
|
||||
self.i_data_buffer = 0
|
||||
self.next_control_bit = BIT_PER_BYTE
|
||||
self.i_sync_bit = 0
|
||||
self.err_sync_bit = 0
|
||||
self.is_wrong_pack = False
|
||||
self.data_buffer[:] = bytes(DATA_BYTE_SIZE_MAX)
|
||||
self.rise_sync_time = BIT_TIME
|
||||
self.stat_sync_first_error = 0
|
||||
self.sync_groups.clear()
|
||||
self.sync_bits_current_group.clear()
|
||||
|
||||
def listen_start(self, now: int) -> None:
|
||||
to = ir_timeout_us(self.rise_sync_time)
|
||||
if self.is_recive_raw and (now - self.prev_rise) > to * 2:
|
||||
self.events.append(f"t={now} listenStart abort raw (gap from prev_rise)")
|
||||
self.is_recive_raw = False
|
||||
self._clear_packet_state()
|
||||
self.first_rx()
|
||||
|
||||
def check_timeout(self, now: int) -> None:
|
||||
if not self.is_recive:
|
||||
return
|
||||
to = ir_timeout_us(self.rise_sync_time)
|
||||
if now - self.last_edge_time > to * 2:
|
||||
self.events.append(f"t={now} checkTimeout (gap since last edge)")
|
||||
self.is_recive = False
|
||||
self.last_edge_time = now
|
||||
|
||||
def write_to_buffer(self, bit_val: int) -> None:
|
||||
if self.i_data_buffer > DATA_BYTE_SIZE_MAX * 8:
|
||||
self.is_buffer_overflow = True
|
||||
self._fix("переполнение буфера битов (writeToBuffer: i_dataBuffer > dataByteSizeMax*8)")
|
||||
if self.is_buffer_overflow or self.is_preamb or self.is_wrong_pack:
|
||||
self.is_recive = False
|
||||
self.is_recive_raw = False
|
||||
return
|
||||
|
||||
if self.buf_bit_pos == self.next_control_bit:
|
||||
self.next_control_bit += SYNCBITS if self.is_data else BIT_PER_BYTE
|
||||
self.is_data = not self.is_data
|
||||
self.i_sync_bit = 0
|
||||
self.err_sync_bit = 0
|
||||
|
||||
if self.is_data:
|
||||
bi = self.i_data_buffer // 8
|
||||
self.data_buffer[bi] |= (bit_val & 1) << (7 - (self.i_data_buffer % 8))
|
||||
self.i_data_buffer += 1
|
||||
self.buf_bit_pos += 1
|
||||
else:
|
||||
if self.i_sync_bit == 0:
|
||||
prev_bit = (
|
||||
self.data_buffer[(self.i_data_buffer - 1) // 8]
|
||||
>> (7 - (self.i_data_buffer - 1) % 8)
|
||||
) & 1
|
||||
if bit_val != prev_bit:
|
||||
self.buf_bit_pos += 1
|
||||
self.i_sync_bit += 1
|
||||
self._sync_bit_consumed(bit_val)
|
||||
else:
|
||||
self.i_sync_bit = 0
|
||||
self.errors_other += 1
|
||||
self.err_sync_bit += 1
|
||||
self.stat_sync_first_error += 1
|
||||
self._fix(
|
||||
f"sync: 1-й sync-бит совпал с последним data-битом (data={prev_bit}); "
|
||||
f"err_sync_bit={self.err_sync_bit}/{SYNCBITS} (как в прошивке)"
|
||||
)
|
||||
if self.err_sync_bit >= SYNCBITS:
|
||||
self.is_wrong_pack = True
|
||||
if not self._fatal_sync_event_sent:
|
||||
self._fatal_sync_event_sent = True
|
||||
self._emit_wrong_sync_fatal(self.last_edge_time)
|
||||
else:
|
||||
self.buf_bit_pos += 1
|
||||
self.i_sync_bit += 1
|
||||
self._sync_bit_consumed(bit_val)
|
||||
self.is_wrong_pack = self.err_sync_bit >= SYNCBITS
|
||||
|
||||
if self.is_data and not self.is_wrong_pack:
|
||||
if self.i_data_buffer == 8 * MSGBYTES:
|
||||
self.pack_size = self.data_buffer[0] & IR_MASK_MSG_INFO
|
||||
if self.pack_size and self.i_data_buffer == self.pack_size * BIT_PER_BYTE:
|
||||
ok = crc_check(self.data_buffer, self.pack_size)
|
||||
raw = self.data_buffer[: self.pack_size]
|
||||
hx = raw.hex()
|
||||
bstr = _bytes_bin_msb(raw)
|
||||
frame_line = (
|
||||
f"t={self.last_edge_time} FRAME_END pack={self.pack_size} crc_ok={ok} hex={hx} bin={bstr}"
|
||||
)
|
||||
sync_lines = self._sync_summary_lines()
|
||||
tick_summary = (
|
||||
f" сводка тактов: чистых_битов_aroundRise={self.stat_clean_bits}, "
|
||||
f"фронтов_с_burst-коррекцией={self.stat_burst_edges}, "
|
||||
f"отброшенных_фронтов_up={self.stat_debounce_rise}, down={self.stat_debounce_fall}"
|
||||
)
|
||||
|
||||
def _frame_summary_block() -> List[str]:
|
||||
return [frame_line, *sync_lines, tick_summary]
|
||||
|
||||
lines: List[str] = []
|
||||
lines.extend(_frame_summary_block())
|
||||
if self.packet_fixes:
|
||||
if self.verbose:
|
||||
lines.append(
|
||||
" подстройки и исправления за пакет, подробный режим (-v) (аналог IR_DecoderRaw::tick):"
|
||||
)
|
||||
else:
|
||||
lines.append(
|
||||
" подстройки и исправления за пакет (преамбула, пропуск такта, burst, sync; "
|
||||
"без строк по каждому «чистому» биту — включите -v):"
|
||||
)
|
||||
for fx in self.packet_fixes:
|
||||
lines.append(f" - {fx}")
|
||||
else:
|
||||
lines.append(
|
||||
" дополнительных исправлений нет (см. сводку; для строк по каждому биту: -v/--verbose)"
|
||||
)
|
||||
if not ok:
|
||||
lines.append(" неуспешный пакет — причина:")
|
||||
lines.extend(crc_failure_lines(self.data_buffer, self.pack_size))
|
||||
lines.append(" --- сводка пакета (конец записи) ---")
|
||||
lines.extend(_frame_summary_block())
|
||||
self.events.append("\n".join(lines))
|
||||
self.is_recive = False
|
||||
self.is_recive_raw = False
|
||||
self._clear_packet_state()
|
||||
self.first_rx()
|
||||
|
||||
def tick_edge(self, t: int, level: int) -> None:
|
||||
"""Один фронт: level = состояние линии ПОСЛЕ фронта (как dir в C++)."""
|
||||
self.listen_start(t)
|
||||
self.last_edge_time = t
|
||||
rising = level == 1
|
||||
|
||||
if rising:
|
||||
cond = (t - self.prev_rise > rise_time_max(self.rise_sync_time) // 4) or self.high_count or self.low_count
|
||||
if cond:
|
||||
self.rise_period = t - self.prev_rise
|
||||
self.high_time = t - self.prev_fall
|
||||
self.low_time = self.prev_fall - self.prev_rise
|
||||
self.prev_rise = t
|
||||
else:
|
||||
self.errors_other += 1
|
||||
self.stat_debounce_rise += 1
|
||||
if self.verbose:
|
||||
self._fix(
|
||||
f"t={t} отброшен фронт ↑: слишком короткий интервал до предыдущего ↑ "
|
||||
f"(<= riseTimeMax/4 при hc=lc=0), errors.other++"
|
||||
)
|
||||
else:
|
||||
if t - self.prev_fall > rise_time_min(self.rise_sync_time) // 4:
|
||||
self.prev_fall = t
|
||||
else:
|
||||
self.errors_other += 1
|
||||
self.stat_debounce_fall += 1
|
||||
if self.verbose:
|
||||
self._fix(
|
||||
f"t={t} отброшен фронт ↓: слишком короткий интервал до предыдущего ↓ (<= riseTimeMin/4), errors.other++"
|
||||
)
|
||||
|
||||
rt = self.rise_sync_time
|
||||
to = ir_timeout_us(rt)
|
||||
if t > self.prev_rise and (t - self.prev_rise) > to * 2 and not self.is_recive_raw:
|
||||
self.preamb_front_counter = PREAMB_FRONTS - 1
|
||||
self.is_preamb = True
|
||||
self.is_recive = True
|
||||
self.is_recive_raw = True
|
||||
self.is_wrong_pack = False
|
||||
self._clear_packet_state()
|
||||
self.events.append(f"t={t} PACKET_START (long idle)")
|
||||
|
||||
if self.preamb_front_counter:
|
||||
if rising and self.rise_period < to:
|
||||
if self.rise_period < rise_time_min(rt) // 2:
|
||||
self.preamb_front_counter += 2
|
||||
self.errors_other += 1
|
||||
self._fix(
|
||||
f"преамбула: «рваная единица» risePeriod={self.rise_period} us < riseTimeMin/2 "
|
||||
f"({rise_time_min(rt) // 2} us) -> preambFrontCounter += 2, errors.other++"
|
||||
)
|
||||
elif FREE_FREC:
|
||||
old = self.rise_sync_time
|
||||
self.rise_sync_time = (self.rise_sync_time + self.rise_period // 2) // 2
|
||||
self._fix(
|
||||
f"преамбула: подстройка riseSyncTime {old}->{self.rise_sync_time} us (freeFrec)"
|
||||
)
|
||||
self.preamb_front_counter -= 1
|
||||
else:
|
||||
if self.is_preamb:
|
||||
self.is_preamb = False
|
||||
half = self.rise_period // 2
|
||||
self.prev_rise += half
|
||||
self._fix(
|
||||
f"после преамбулы: prev_rise += risePeriod/2 (+{half} us) - фазовая привязка к центру бита"
|
||||
)
|
||||
return
|
||||
|
||||
if self.is_preamb:
|
||||
return
|
||||
|
||||
if self.rise_period > to or self.is_buffer_overflow or self.rise_period < rise_time_min(rt) or self.is_wrong_pack:
|
||||
if self.is_recive and rising and (self.rise_period > to or self.rise_period < rise_time_min(rt)):
|
||||
reason = (
|
||||
f"risePeriod={self.rise_period} us: "
|
||||
+ (f"> IR_timeout={to} us " if self.rise_period > to else "")
|
||||
+ (f"< riseTimeMin={rise_time_min(rt)} us " if self.rise_period < rise_time_min(rt) else "")
|
||||
)
|
||||
self._fix(f"t={t} пропуск такта (goto END): {reason.strip()}")
|
||||
return
|
||||
|
||||
if not rising:
|
||||
return
|
||||
|
||||
self.high_count = 0
|
||||
self.low_count = 0
|
||||
self.all_count = 0
|
||||
invert_err = False
|
||||
rt = self.rise_sync_time
|
||||
|
||||
if around_rise(self.rise_period, rt):
|
||||
self.stat_clean_bits += 1
|
||||
bit = 1 if self.high_time > self.low_time else 0
|
||||
if self.verbose:
|
||||
self._fix(
|
||||
f"t={t} «чистый» бит: aroundRise (risePeriod={self.rise_period} us в [{rise_time_min(rt)}..{rise_time_max(rt)}]), "
|
||||
f"highTime={self.high_time} lowTime={self.low_time} us -> bit {bit}"
|
||||
)
|
||||
self.write_to_buffer(bit)
|
||||
else:
|
||||
self.stat_burst_edges += 1
|
||||
self.high_count = ceil_div(self.high_time, rt)
|
||||
self.low_count = ceil_div(self.low_time, rt)
|
||||
self.all_count = ceil_div(self.rise_period, rt)
|
||||
self._fix(
|
||||
f"t={t} пропуск такта / растяжение: risePeriod={self.rise_period} us вне aroundRise "
|
||||
f"[{rise_time_min(rt)}..{rise_time_max(rt)}]; "
|
||||
f"ceil_div: highTime/{rt}->{self.high_count}, lowTime/{rt}->{self.low_count}, risePeriod/{rt}->{self.all_count}"
|
||||
)
|
||||
if self.high_count == 0 and self.high_time > rt // 3:
|
||||
self.high_count += 1
|
||||
self.errors_other += 1
|
||||
self._fix(
|
||||
f"доп. коррекция: highCount был 0 при highTime={self.high_time} > riseTime/3 ({rt // 3}) -> highCount++"
|
||||
)
|
||||
if self.low_count + self.high_count > self.all_count:
|
||||
lo, hi, ac = self.low_count, self.high_count, self.all_count
|
||||
if self.low_count > self.high_count:
|
||||
self.low_count = self.all_count - self.high_count
|
||||
self._fix(
|
||||
f"поджатие: low+high>{ac} и low>high -> lowCount {lo}->{self.low_count} (лишние нули)"
|
||||
)
|
||||
elif self.low_count < self.high_count:
|
||||
self.high_count = self.all_count - self.low_count
|
||||
self._fix(
|
||||
f"поджатие: low+high>{ac} и low<high -> highCount {hi}->{self.high_count} (лишние единицы)"
|
||||
)
|
||||
elif self.low_count == self.high_count:
|
||||
invert_err = True
|
||||
self.errors_other += self.all_count
|
||||
self._fix(
|
||||
f"поджатие: low==high при low+high>{ac} -> invertErr (последний из low-цикла пишется как 1)"
|
||||
)
|
||||
i = 0
|
||||
while i < self.low_count and (8 - i):
|
||||
if i == self.low_count - 1 and invert_err:
|
||||
invert_err = False
|
||||
self.write_to_buffer(1)
|
||||
else:
|
||||
self.write_to_buffer(0)
|
||||
i += 1
|
||||
i = 0
|
||||
while i < self.high_count and (8 - i):
|
||||
if i == self.high_count - 1 and invert_err:
|
||||
invert_err = False
|
||||
self.write_to_buffer(0)
|
||||
else:
|
||||
self.write_to_buffer(1)
|
||||
i += 1
|
||||
|
||||
|
||||
def timing_stats(edges: List[EdgeRec]) -> None:
|
||||
dts: List[int] = []
|
||||
for i in range(1, len(edges)):
|
||||
d = edges[i].t_us - edges[i - 1].t_us
|
||||
if 0 <= d < 1_000_000:
|
||||
dts.append(d)
|
||||
if not dts:
|
||||
print("Нет интервалов для статистики.")
|
||||
return
|
||||
dts.sort()
|
||||
def pct(p: float) -> int:
|
||||
return dts[int(len(dts) * p)]
|
||||
print("--- Inter-edge deltas in log (us) ---")
|
||||
print(f" N={len(dts)} min={dts[0]} p50={pct(0.5)} p90={pct(0.9)} max={dts[-1]}")
|
||||
print(
|
||||
f" bitTime(ref)~{BIT_TIME} us aroundRise window ({rise_time_min(BIT_TIME)}..{rise_time_max(BIT_TIME)}) us"
|
||||
)
|
||||
# грубые корзины
|
||||
buckets = [0, 0, 0, 0, 0]
|
||||
for d in dts:
|
||||
if d < 200:
|
||||
buckets[0] += 1
|
||||
elif d < 600:
|
||||
buckets[1] += 1
|
||||
elif d < 1200:
|
||||
buckets[2] += 1
|
||||
elif d < 3000:
|
||||
buckets[3] += 1
|
||||
else:
|
||||
buckets[4] += 1
|
||||
print(f" корзины [0-200) [200-600) [600-1200) [1200-3000) [3000+): {buckets}")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser(description="Симуляция IR decode по @IRF1v1 логу")
|
||||
ap.add_argument("logfile", nargs="?", default=None, help="Текстовый лог с @IRF1v1:")
|
||||
ap.add_argument("--include-skipped", action="store_true", help="Подмешивать фронты с SKIP_DECODE (обычно нет)")
|
||||
ap.add_argument("--max-events", type=int, default=80, help="Макс. событий FRAME_START/END в отчёте")
|
||||
ap.add_argument(
|
||||
"--no-color",
|
||||
action="store_true",
|
||||
help="Bez ANSI-tsvetov (ili zadajte NO_COLOR v okruzhenii)",
|
||||
)
|
||||
ap.add_argument(
|
||||
"-v",
|
||||
"--verbose",
|
||||
action="store_true",
|
||||
help="Podrobnyj vyvod: kazhdyj chistyj bit (aroundRise), otbrosy frontov; inache tolko svodka",
|
||||
)
|
||||
args = ap.parse_args()
|
||||
if not args.logfile:
|
||||
print("Укажите путь к логу, например: python tools/ir_decoder_sim.py ref/ISR_self_frontlog.txt")
|
||||
return 1
|
||||
text = open(args.logfile, "r", encoding="utf-8", errors="replace").read()
|
||||
raw_edges = parse_irf1_lines(text)
|
||||
edges = [e for e in raw_edges if args.include_skipped or not (e.flags & SKIP_DECODE_FLAG)]
|
||||
edges.sort(key=lambda e: (e.t_us, id(e)))
|
||||
print(f"Записей фронтов (после фильтра SKIP): {len(edges)} (всего распарсено: {len(raw_edges)})")
|
||||
timing_stats(edges)
|
||||
|
||||
dec = DecoderSim(verbose=args.verbose)
|
||||
for e in edges:
|
||||
to_us = ir_timeout_us(dec.rise_sync_time)
|
||||
if dec.is_recive and dec.last_edge_time > 0 and (e.t_us - dec.last_edge_time) > to_us * 2:
|
||||
dec.check_timeout(e.t_us)
|
||||
dec.tick_edge(e.t_us, e.level)
|
||||
|
||||
print("--- События декодера (первые N), пакеты разделены пустой строкой ---")
|
||||
slice_ev = dec.events[: args.max_events]
|
||||
first_packet = True
|
||||
use_color = _use_terminal_color() and not args.no_color
|
||||
for ev in slice_ev:
|
||||
head = ev.split("\n", 1)[0]
|
||||
if "PACKET_START" in head:
|
||||
if not first_packet:
|
||||
print()
|
||||
first_packet = False
|
||||
tone = _packet_event_tone(ev)
|
||||
if tone is not None:
|
||||
ok = tone == "ok"
|
||||
ev_out = _highlight_frame_end_payloads(ev, ok=ok, enabled=use_color)
|
||||
print(_colorize_block(ev_out, ok=ok, enabled=use_color))
|
||||
else:
|
||||
print(ev)
|
||||
if len(dec.events) > args.max_events:
|
||||
print(f"... всего событий: {len(dec.events)}")
|
||||
print(f"errors_other={dec.errors_other} wrong_pack_end={dec.is_wrong_pack} recive={dec.is_recive}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user