Skip to content

Commit f6923eb

Browse files
Fix #34, fix #24 (#35)
* Fix #34 * Fix #24 by removing the offending test * Acceptance filters are initialized to discard everything except EXTENDED DATA frames (29-bit); RTR/11-bit are dropped * Describe an important edge case in the docs for bxCANPop() Co-authored-by: Kalyan Sriram <[email protected]>
1 parent bb8c613 commit f6923eb

File tree

4 files changed

+91
-99
lines changed

4 files changed

+91
-99
lines changed

.github/workflows/main.yml

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ jobs:
2323
- name: Install Dependencies
2424
run: |
2525
sudo dpkg --add-architecture i386
26+
sudo apt update
2627
sudo apt install gcc-10 g++-10 linux-libc-dev:i386 linux-*-extra-$(uname -r)
2728
2829
- name: Configure CMake
@@ -41,29 +42,25 @@ jobs:
4142

4243
# stm32/libcanard/bxcan
4344
stm32:
44-
env:
45-
CC: gcc-10
46-
CXX: g++-10
47-
4845
runs-on: ubuntu-latest
49-
5046
steps:
5147
- uses: actions/checkout@v2
5248
with:
5349
submodules: 'recursive'
5450

5551
- name: Install Dependencies
5652
run: |
57-
sudo apt install gcc-10 g++-10 gcc-10-multilib g++-10-multilib clang-tidy-10 clang-format-10
58-
wget https://apt.llvm.org/llvm.sh && chmod +x llvm.sh && sudo ./llvm.sh 10
53+
sudo apt update
54+
wget https://apt.llvm.org/llvm.sh && chmod +x llvm.sh && sudo ./llvm.sh 12
55+
sudo apt install clang-tidy-12 clang-format-12
5956
6057
- name: Static analysis
6158
working-directory: ${{github.workspace}}/stm32/libcanard/bxcan/
62-
run: clang-tidy-10 src/*.c --extra-arg='-DBXCAN_MAX_IFACE_INDEX=1' --extra-arg='-DBXCAN_BUSYWAIT_DELAY_SYSTEM_CORE_CLOCK=72000000'
59+
run: clang-tidy-12 src/*.c --extra-arg='-DBXCAN_MAX_IFACE_INDEX=1' --extra-arg='-DBXCAN_BUSYWAIT_DELAY_SYSTEM_CORE_CLOCK=72000000'
6360

6461
- name: Format check
6562
working-directory: ${{github.workspace}}/stm32/libcanard/bxcan/
6663
run: |
67-
clang-format-10 -i -fallback-style=none -style=file --verbose src/*.[ch]
64+
clang-format-12 -i -fallback-style=none -style=file --verbose src/*.[ch]
6865
modified="$(git status --porcelain --untracked-files=no)"
6966
if [ -n "$modified" ]; then echo "Please format code properly."; exit 1; fi

stm32/libcanard/.clang-tidy

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ Checks: >-
1818
-llvm-header-guard,
1919
-cppcoreguidelines-avoid-magic-numbers,
2020
-readability-magic-numbers,
21+
CheckOptions:
22+
- key: readability-function-cognitive-complexity.Threshold
23+
value: '99'
2124
WarningsAsErrors: '*'
2225
HeaderFilterRegex: '.*'
2326
AnalyzeTemporaryDtors: false

stm32/libcanard/bxcan/src/bxcan.c

Lines changed: 75 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,14 @@ extern uint32_t SystemCoreClock;
4343

4444
/// Error trackers, one for each possible CAN interface.
4545
/// As the bxCANReapError() function only returns a bool, we can keep this as a simple flag.
46-
static bool g_error[2] = {false};
46+
static bool g_error[2] = {false}; // NOLINT mutable global
4747

4848
/// Time stamps for TX time-out management. There are three TX mailboxes for each CAN interface.
4949
/// An UINT64_MAX value means the time stamp is not set (mailbox is not in use). Zero initialization
5050
/// will work, as the value will be re-set to UINT64_MAX in abortExpiredTxMailboxes() and set to the
5151
/// required value in bxCANPush(). This way, any inadvertently busy mailboxes will still be aborted
5252
/// automatically, making this more robust than initializing to UINT64_MAX from the onset.
53-
static uint64_t g_tx_deadline[(1 + BXCAN_MAX_IFACE_INDEX) * 3] = {0};
53+
static uint64_t g_tx_deadline[(1 + BXCAN_MAX_IFACE_INDEX) * 3] = {0}; // NOLINT mutable global
5454

5555
/// Converts an extended-ID frame format into the bxCAN TX ID register format.
5656
static uint32_t convertFrameIDToRegister(const uint32_t id)
@@ -103,8 +103,7 @@ static bool waitMSRINAKBitStateChange(volatile const BxCANType* const bxcan_base
103103
// The counter variable is declared volatile to prevent the compiler from optimizing it away.
104104
volatile size_t nticks = BXCAN_BUSYWAIT_DELAY_SYSTEM_CORE_CLOCK / 7000U;
105105
while (--nticks)
106-
{
107-
}
106+
{}
108107
}
109108

110109
return out_status;
@@ -171,7 +170,7 @@ static void processErrorStatus(volatile BxCANType* const bxcan_base, //
171170
BXCAN_ASSERT((error_iface == &g_error[0]) || (error_iface == &g_error[1])); // Valid g_error address.
172171

173172
// Updating error flag.
174-
const uint8_t lec = (uint8_t)((bxcan_base->ESR & BXCAN_ESR_LEC_MASK) >> BXCAN_ESR_LEC_SHIFT);
173+
const uint8_t lec = (uint8_t) ((bxcan_base->ESR & BXCAN_ESR_LEC_MASK) >> BXCAN_ESR_LEC_SHIFT);
175174

176175
if (lec != 0U)
177176
{
@@ -204,19 +203,16 @@ bool bxCANConfigure(const uint8_t iface_index, //
204203

205204
// Select the appropriate CAN interface base address and zero the error flags for it.
206205
// If the interface number is invalid, return with an error.
207-
volatile BxCANType* bxcan_base = NULL; // Selected CAN interface base address.
208-
uint8_t filter_index = 0U;
206+
volatile BxCANType* bxcan_base = NULL; // Selected CAN interface base address.
209207
if (iface_index == 0U)
210208
{
211-
bxcan_base = BXCAN1;
212-
g_error[0] = false;
213-
filter_index = 0U; // First filter slot for CAN1.
209+
bxcan_base = BXCAN1;
210+
g_error[0] = false;
214211
}
215212
else if ((iface_index == 1U) && (BXCAN_MAX_IFACE_INDEX == 1U))
216213
{
217-
bxcan_base = BXCAN2;
218-
g_error[1] = false;
219-
filter_index = (uint8_t) BXCAN_NUM_ACCEPTANCE_FILTERS; // First filter slot for CAN2.
214+
bxcan_base = BXCAN2;
215+
g_error[1] = false;
220216
}
221217
else
222218
{
@@ -279,8 +275,6 @@ bool bxCANConfigure(const uint8_t iface_index, //
279275
((timings.bit_rate_prescaler - 1U) & 1023U) | //
280276
(silent ? BXCAN_BTR_SILM : 0U);
281277

282-
BXCAN_ASSERT(bxcan_base->IER == 0U); // Make sure the interrupts are indeed disabled.
283-
284278
bxcan_base->MCR &= ~BXCAN_MCR_INRQ; // Leave init mode.
285279

286280
if (!waitMSRINAKBitStateChange(bxcan_base, false))
@@ -302,44 +296,51 @@ bool bxCANConfigure(const uint8_t iface_index, //
302296
// This will cause occasional priority inversion and frame reordering on reception,
303297
// but that is acceptable for UAVCAN, and a majority of other protocols will tolerate
304298
// this too, since there will be no reordering within the same CAN ID.
305-
if (BXCAN_MAX_IFACE_INDEX > 0)
299+
if (iface_index == 0U)
306300
{
307-
// MCU with two bxCAN interfaces: configure the filter start banks for each interface.
308-
// Example MCU: STM32F446.
309-
// Note: block statement is introduced to contain the scope of fmr (defensive programming).
301+
if (BXCAN_MAX_IFACE_INDEX > 0)
310302
{
311-
uint32_t fmr = BXCAN1->FMR & 0xFFFFC0FEU;
312-
fmr |= BXCAN_NUM_ACCEPTANCE_FILTERS << 8U; // CAN2 start bank = 14 (if CAN2 is present)
313-
BXCAN1->FMR = fmr | BXCAN_FMR_FINIT; // Set the configuration, enter filter initialization mode.
314-
}
303+
// MCU with two bxCAN interfaces: configure the filter start banks for each interface.
304+
// Example MCU: STM32F446.
305+
{
306+
uint32_t fmr = BXCAN1->FMR & 0xFFFFC0FEU;
307+
fmr |= BXCAN_NUM_ACCEPTANCE_FILTERS << 8U; // CAN2 start bank = 14 (if CAN2 is present)
308+
BXCAN1->FMR = fmr | BXCAN_FMR_FINIT; // Set the configuration, enter filter initialization mode.
309+
}
315310

316-
BXCAN_ASSERT(((BXCAN1->FMR >> 8U) & 0x3FU) == BXCAN_NUM_ACCEPTANCE_FILTERS);
311+
BXCAN_ASSERT(((BXCAN1->FMR >> 8U) & 0x3FU) == BXCAN_NUM_ACCEPTANCE_FILTERS);
317312

318-
BXCAN1->FM1R &= 0xF0000000U; // Identifier Mask mode. (4 MSB are reserved.)
319-
BXCAN1->FS1R |= 0x0FFFFFFFU; // All 32-bit filters. (4 MSB are reserved.)
313+
BXCAN1->FM1R &= 0xF0000000U; // Identifier Mask mode. (4 MSB are reserved.)
314+
BXCAN1->FS1R |= 0x0FFFFFFFU; // All 32-bit filters. (4 MSB are reserved.)
320315

321-
BXCAN1->FFA1R =
322-
(BXCAN1->FFA1R & 0xF0000000U) | 0x0AAAAAAAU; // Alternate the filters between FIFO0 & FIFO1.
323-
}
324-
else
325-
{
326-
// MCU with single bxCAN interface has no filter start bank configuration in FMR.
327-
// Example MCU: STM32L431.
328-
BXCAN1->FMR |= BXCAN_FMR_FINIT; // Enter filter initialization mode.
316+
BXCAN1->FFA1R =
317+
(BXCAN1->FFA1R & 0xF0000000U) | 0x0AAAAAAAU; // Alternate the filters between FIFO0 & FIFO1.
329318

330-
BXCAN1->FM1R &= 0xFFFFC000U; // Identifier Mask mode. (18 MSB are reserved.)
331-
BXCAN1->FS1R |= 0x00003FFFU; // All 32-bit filters. (18 MSB are reserved.)
319+
// Configure one "accept all" filter and enable it.
320+
BXCAN1->FilterRegister[BXCAN_NUM_ACCEPTANCE_FILTERS].FR1 = 0U;
321+
BXCAN1->FilterRegister[BXCAN_NUM_ACCEPTANCE_FILTERS].FR2 = 0U;
322+
BXCAN1->FA1R |= (1U << BXCAN_NUM_ACCEPTANCE_FILTERS);
323+
}
324+
else
325+
{
326+
// MCU with single bxCAN interface has no filter start bank configuration in FMR.
327+
// Example MCU: STM32L431.
328+
BXCAN1->FMR |= BXCAN_FMR_FINIT; // Enter filter initialization mode.
332329

333-
BXCAN1->FFA1R =
334-
(BXCAN1->FFA1R & 0xFFFFC000U) | 0x00002AAAU; // Alternate the filters between FIFO0 & FIFO1.
335-
}
330+
BXCAN1->FM1R &= 0xFFFFC000U; // Identifier Mask mode. (18 MSB are reserved.)
331+
BXCAN1->FS1R |= 0x00003FFFU; // All 32-bit filters. (18 MSB are reserved.)
332+
333+
BXCAN1->FFA1R =
334+
(BXCAN1->FFA1R & 0xFFFFC000U) | 0x00002AAAU; // Alternate the filters between FIFO0 & FIFO1.
335+
}
336336

337-
// Configure one "accept all" filter and enable it.
338-
BXCAN1->FilterRegister[filter_index].FR1 = 0U; // Accept all.
339-
BXCAN1->FilterRegister[filter_index].FR2 = 0U; // Accept all.
340-
BXCAN1->FA1R |= (1U << filter_index); // One filter enabled
337+
// Configure one "accept all" filter and enable it.
338+
BXCAN1->FilterRegister[0].FR1 = 0U;
339+
BXCAN1->FilterRegister[0].FR2 = 0U;
340+
BXCAN1->FA1R |= (1U << 0U);
341341

342-
bxcan_base->FMR &= ~BXCAN_FMR_FINIT; // Leave initialization mode.
342+
BXCAN1->FMR &= ~BXCAN_FMR_FINIT; // Leave initialization mode.
343+
}
343344
out_status = true;
344345
}
345346

@@ -360,7 +361,7 @@ void bxCANConfigureFilters(const uint8_t iface_index, //
360361
}
361362
else if ((iface_index == 1U) && (BXCAN_MAX_IFACE_INDEX == 1U))
362363
{
363-
filter_index_offset = (uint8_t)(BXCAN_NUM_ACCEPTANCE_FILTERS);
364+
filter_index_offset = (uint8_t) (BXCAN_NUM_ACCEPTANCE_FILTERS);
364365
}
365366
else
366367
{
@@ -370,23 +371,17 @@ void bxCANConfigureFilters(const uint8_t iface_index, //
370371
// Only modify the registers if the filter register index offset is valid.
371372
if (filter_index_offset != 0xFF)
372373
{
373-
// First we disable all filters. This may cause momentary RX frame losses, but the application
374-
// should be able to tolerate that. The number of reserved bits is different for devices with
375-
// one or two bxCAN interfaces.
376-
if (BXCAN_MAX_IFACE_INDEX > 0)
377-
{
378-
BXCAN1->FA1R &= 0xF0000000U; // Dual CAN: 28 filter banks, 4 MSB reserved.
379-
}
380-
else
381-
{
382-
BXCAN1->FA1R &= 0xFFFFC000U; // Single CAN: 14 filter banks, 18 MSB reserved.
383-
}
384-
374+
BXCAN1->FMR |= BXCAN_FMR_FINIT; // This is required for disabling filters.
385375
// Having filters disabled we can update the configuration.
386376
// Register mapping: FR1 - ID, FR2 - Mask
387377
for (uint8_t i = 0U; i < (uint8_t) BXCAN_NUM_ACCEPTANCE_FILTERS; i++)
388378
{
389-
// Converting the ID and the Mask into the representation that can be chewed by the hardware.
379+
const BxCANFilterParams* const cfg = params + i;
380+
const uint8_t filter_index = i + filter_index_offset;
381+
382+
BXCAN1->FA1R &= ~(1U << filter_index); // Disable the filter first. Only re-enable later if needed.
383+
384+
// Convert the filter to the register format.
390385
//
391386
// The logic of the hardware acceptance filters can be described as follows:
392387
//
@@ -420,33 +415,25 @@ void bxCANConfigureFilters(const uint8_t iface_index, //
420415
// 1 1 0 0
421416
// 1 0 1 0
422417
// 1 1 1 1
423-
uint32_t id = 0;
424-
uint32_t mask = 0;
425-
426-
const BxCANFilterParams* const cfg = params + i;
427-
428-
// Convert the filter to the register format.
418+
//
429419
// The special case of a filter entry set to {0, 0} (all bits zero) signifies that the filter should block
430420
// all traffic. This is done to improve the API, avoiding a magic number. Detect this, and leave that filter
431421
// disabled to block all traffic as there is no set of filter values that achieves this. Eg: {0, 0x1FFFFFFF}
432422
// will still allow data with ID = 0 to pass. (Setting a {0, 0} filter in the registers as-such would
433423
// actually pass all traffic.)
434-
if ((cfg->extended_id != 0U) ||
435-
(cfg->extended_mask != 0U)) // Only configure and enable non-{0, 0} filters.
424+
if ((cfg->extended_id != 0U) || (cfg->extended_mask != 0U))
436425
{
437-
id = (cfg->extended_id & BXCAN_FRAME_EXT_ID_MASK) << 3U;
438-
mask = (cfg->extended_mask & BXCAN_FRAME_EXT_ID_MASK) << 3U;
439-
id |=
440-
BXCAN_RIR_IDE; // Must be set to accept extended-ID frames. (The mask bit for IDE is already zero.)
426+
const uint32_t id = ((cfg->extended_id & BXCAN_FRAME_EXT_ID_MASK) << 3U) | BXCAN_RIR_IDE;
427+
const uint32_t mask =
428+
((cfg->extended_mask & BXCAN_FRAME_EXT_ID_MASK) << 3U) | BXCAN_RIR_IDE | BXCAN_RIR_RTR;
441429

442-
// Applying the converted representation to the registers.
443-
const uint8_t filter_index = i + filter_index_offset;
444430
BXCAN1->FilterRegister[filter_index].FR1 = id;
445431
BXCAN1->FilterRegister[filter_index].FR2 = mask;
446432

447433
BXCAN1->FA1R |= (1U << filter_index); // Enable the filter.
448434
}
449435
}
436+
BXCAN1->FMR &= ~BXCAN_FMR_FINIT;
450437
}
451438
}
452439

@@ -698,21 +685,21 @@ bool bxCANPop(const uint8_t iface_index, //
698685

699686
*out_extended_can_id = convertRegisterToFrameID(mb->RIR);
700687

701-
*out_payload_size = (uint8_t)(mb->RDTR & BXCAN_RDTR_DLC_MASK);
688+
*out_payload_size = (uint8_t) (mb->RDTR & BXCAN_RDTR_DLC_MASK);
702689

703690
// Caching to regular (non volatile) memory for faster reads
704691
const uint32_t rdlr = mb->RDLR;
705692
const uint32_t rdhr = mb->RDHR;
706693

707694
uint8_t* out_ptr = (uint8_t*) out_payload;
708-
*out_ptr++ = (uint8_t)(0xFFU & (rdlr >> 0U));
709-
*out_ptr++ = (uint8_t)(0xFFU & (rdlr >> 8U));
710-
*out_ptr++ = (uint8_t)(0xFFU & (rdlr >> 16U));
711-
*out_ptr++ = (uint8_t)(0xFFU & (rdlr >> 24U));
712-
*out_ptr++ = (uint8_t)(0xFFU & (rdhr >> 0U));
713-
*out_ptr++ = (uint8_t)(0xFFU & (rdhr >> 8U));
714-
*out_ptr++ = (uint8_t)(0xFFU & (rdhr >> 16U));
715-
*out_ptr++ = (uint8_t)(0xFFU & (rdhr >> 24U));
695+
*out_ptr++ = (uint8_t) (0xFFU & (rdlr >> 0U));
696+
*out_ptr++ = (uint8_t) (0xFFU & (rdlr >> 8U));
697+
*out_ptr++ = (uint8_t) (0xFFU & (rdlr >> 16U));
698+
*out_ptr++ = (uint8_t) (0xFFU & (rdlr >> 24U));
699+
*out_ptr++ = (uint8_t) (0xFFU & (rdhr >> 0U));
700+
*out_ptr++ = (uint8_t) (0xFFU & (rdhr >> 8U));
701+
*out_ptr++ = (uint8_t) (0xFFU & (rdhr >> 16U));
702+
*out_ptr++ = (uint8_t) (0xFFU & (rdhr >> 24U));
716703

717704
// Release FIFO entry we just read
718705
*RFxR[i] |= BXCAN_RFR_RFOM | BXCAN_RFR_FOVR | BXCAN_RFR_FULL;
@@ -762,7 +749,7 @@ bool bxCANComputeTimings(const uint32_t peripheral_clock_rate, //
762749
// 500 kbps 16 17
763750
// 250 kbps 16 17
764751
// 125 kbps 16 17
765-
const uint8_t max_quanta_per_bit = (uint8_t)((target_bitrate >= 1000000) ? 10U : 17U);
752+
const uint8_t max_quanta_per_bit = (uint8_t) ((target_bitrate >= 1000000) ? 10U : 17U);
766753
BXCAN_ASSERT(max_quanta_per_bit <= (MaxBS1 + MaxBS2));
767754

768755
static const uint16_t MaxSamplePointLocationPermill = 900U;
@@ -778,7 +765,7 @@ bool bxCANComputeTimings(const uint32_t peripheral_clock_rate, //
778765
const uint32_t prescaler_bs = peripheral_clock_rate / target_bitrate;
779766

780767
// Searching for such prescaler value so that the number of quanta per bit is highest.
781-
uint8_t bs1_bs2_sum = (uint8_t)(max_quanta_per_bit - 1U);
768+
uint8_t bs1_bs2_sum = (uint8_t) (max_quanta_per_bit - 1U);
782769

783770
while ((prescaler_bs % (1U + bs1_bs2_sum)) != 0U)
784771
{
@@ -812,17 +799,17 @@ bool bxCANComputeTimings(const uint32_t peripheral_clock_rate, //
812799
// Since the optimal solution is so close to the maximum, we prepare two solutions, and then pick the best one:
813800
// - With rounding to nearest
814801
// - With rounding to zero
815-
uint8_t bs1 = (uint8_t)(((7U * bs1_bs2_sum - 1U) + 4U) / 8U); // Trying rounding to nearest first
816-
uint8_t bs2 = (uint8_t)(bs1_bs2_sum - bs1);
802+
uint8_t bs1 = (uint8_t) (((7U * bs1_bs2_sum - 1U) + 4U) / 8U); // Trying rounding to nearest first
803+
uint8_t bs2 = (uint8_t) (bs1_bs2_sum - bs1);
817804
BXCAN_ASSERT(bs1_bs2_sum > bs1);
818805

819806
{
820-
const uint16_t sample_point_permill = (uint16_t)(1000U * (1U + bs1) / (1U + bs1 + bs2));
807+
const uint16_t sample_point_permill = (uint16_t) (1000U * (1U + bs1) / (1U + bs1 + bs2));
821808

822809
if (sample_point_permill > MaxSamplePointLocationPermill) // Strictly more!
823810
{
824-
bs1 = (uint8_t)((7U * bs1_bs2_sum - 1U) / 8U); // Nope, too far; now rounding to zero
825-
bs2 = (uint8_t)(bs1_bs2_sum - bs1);
811+
bs1 = (uint8_t) ((7U * bs1_bs2_sum - 1U) / 8U); // Nope, too far; now rounding to zero
812+
bs2 = (uint8_t) (bs1_bs2_sum - bs1);
826813
}
827814
}
828815

stm32/libcanard/bxcan/src/bxcan.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ typedef struct
8787
/// Use bxCANConfigureFilters() to override this after the interface is configured.
8888
///
8989
/// WARNING: The clock of the CAN module must be enabled before this function is invoked!
90-
/// If CAN2 is used, CAN1 must be also enabled!
90+
/// If CAN2 is used, CAN1 must be initialized first (at least in silent mode)!
9191
///
9292
/// WARNING: The driver is not thread-safe!
9393
/// It does not use IRQ or critical sections though, so it is safe to invoke its API functions from the
@@ -96,7 +96,7 @@ bool bxCANConfigure(const uint8_t iface_index, const BxCANTimings timings, const
9696

9797
/// Acceptance filter configuration. Unused filters shall be set to {0, 0} (all bits zero); they will reject all frames.
9898
/// When the interface is reinitialized, hardware acceptance filters are reset, so this function shall be re-invoked.
99-
/// While reconfiguration is in progress, some received frames may be lost.
99+
/// While reconfiguration is in progress, some received frames may be lost, and/or undesired frames may be received.
100100
/// Filters alternate between FIFO0/1 in order to equalize the load: even filters take FIFO0, odd filters take FIFO1.
101101
/// This will cause occasional priority inversion and frame reordering on reception, but that is acceptable for UAVCAN,
102102
/// and most other CAN-based protocols will tolerate this too since there will be no reordering within the same CAN ID.
@@ -158,6 +158,11 @@ bool bxCANPush(const uint8_t iface_index,
158158

159159
/// Extract one frame from the RX FIFOs. FIFO0 checked first.
160160
/// The out_payload memory shall be large enough to accommodate the largest CAN frame payload.
161+
///
162+
/// Special case: if the top entry of a FIFO contains a non-extended-data frame (e.g., RTR/11-bit), said entry
163+
/// will be silently discarded and the function may return false even if there are relevant items
164+
/// further down the queue. To avoid such complications, be sure to always use acceptance filters.
165+
///
161166
/// Returns true if received; false if both RX FIFOs are empty.
162167
bool bxCANPop(const uint8_t iface_index,
163168
uint32_t* const out_extended_can_id,

0 commit comments

Comments
 (0)