Skip to content

am243x : add configuration of pulse per revolution - #162

Open
a1248924 wants to merge 1 commit into
mainfrom
ADD_PRU_EQEP_PPR_CONFIG
Open

am243x : add configuration of pulse per revolution#162
a1248924 wants to merge 1 commit into
mainfrom
ADD_PRU_EQEP_PPR_CONFIG

Conversation

@a1248924

Copy link
Copy Markdown
Collaborator

signed-off by Ayushman a-ayushman@ti.com

@qodo-code-review

qodo-code-review Bot commented Aug 19, 2026

Copy link
Copy Markdown

PR Summary by Qodo

am243x pru_eqep: publish/configure QPOSMAX (PPR×4−1) and use modulus-aware direction

✨ Enhancement 🐞 Bug fix 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Add a build-time QPOSMAX (PPR×4−1) and enforce bounded QPOS wrap/underflow in PRU firmware.
• Publish QPOSMAX to DMEM so R5F reads the true modulus and computes wrap-safe direction.
• Document how to set PPR/QPOSMAX and adjust example syscfg debug-log settings.
Diagram

graph TD
  A["macros.inc\n(QPOSMAX const)"] --> B["PRU eQEP firmware\n(main.asm)"] --> C[("DMEM1\nshared memory")]
  C --> D["R5F init\n(pru_eqep_example.c)"] --> E["Direction calc\n(modulus rewrap)"]
  F["readme.md\n(PPR/QPOSMAX docs)"] --> A

  subgraph Legend
    direction LR
    _cfg["Config/Constant"] ~~~ _fw["Firmware"] ~~~ _mem[("Shared Memory")] ~~~ _app["Host App"] ~~~ _doc["Docs"]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Runtime-configurable QPOSMAX via DMEM (R5F writes, PRU reads)
  • ➕ No firmware rebuild needed to change encoder PPR
  • ➕ Allows per-channel PPR (different encoders on different channels)
  • ➖ Requires a defined boot-time handshake/ordering (PRU must block until config is written)
  • ➖ Adds validation/error handling for invalid modulus values and increases boot complexity
2. Single source of truth generated from syscfg (auto-emit both offsets and modulus)
  • ➕ Prevents duplicated offsets/constants between firmware and R5F
  • ➕ Makes configuration discoverable in one place (syscfg) and reduces drift risk
  • ➖ Requires syscfg template changes and a generation flow for PRU assembly constants
  • ➖ May be heavier-weight than needed if only QPOSMAX is configurable

Recommendation: The PR’s approach (build-time QPOSMAX in firmware, published once to R5F) is a good near-term tradeoff: it guarantees host/firmware agreement on modulus without adding a runtime handshake, and it keeps the PRU hot path fast by caching QPOSMAX in a register. If frequent PPR changes or per-channel variability are expected, consider the runtime DMEM configuration handshake as a follow-up.

Files changed (7) +100 / -17

Enhancement (4) +37 / -0
macros.incDefine QPOSMAX build-time constant for bounded position counter +3/-0

Define QPOSMAX build-time constant for bounded position counter

• Adds a QPOSMAX assembler constant representing the position counter maximum (PPR×4−1). This becomes the single firmware-side source for the counter modulus.

examples/pru_eqep/firmware/include/macros.inc

memory.incAdd per-channel DMEM offsets and register for publishing/caching QPOSMAX +7/-0

Add per-channel DMEM offsets and register for publishing/caching QPOSMAX

• Reserves r2 as a dedicated cached QPOSMAX register and introduces QPOSMAX_OFFSET values per channel in DMEM1. This defines where firmware publishes QPOSMAX for the R5F to read.

examples/pru_eqep/firmware/include/memory.inc

main.asmPublish and cache QPOSMAX at boot; wrap QPOS on overflow/underflow +23/-0

Publish and cache QPOSMAX at boot; wrap QPOS on overflow/underflow

• Writes QPOSMAX into DMEM1 once during firmware init and caches it in a register to avoid reloads in the edge hot-path. Updates the QPOS increment/decrement logic to wrap at QPOSMAX/0, mirroring hardware eQEP bounded counting behavior.

examples/pru_eqep/firmware/main.asm

eqep_diagnostic.hExtend ABZ_Config with QPOSMAX pointer and cached modulus value +4/-0

Extend ABZ_Config with QPOSMAX pointer and cached modulus value

• Adds fields to hold the DMEM1 address where firmware publishes QPOSMAX and a cached qposmax value read once at init. This supports modulus-aware calculations on the R5F side.

examples/pru_eqep/mcuplus/eqep_diagnostic.h

Bug fix (1) +38 / -9
pru_eqep_example.cRead firmware-published QPOSMAX and compute wrap-safe direction using modulus +38/-9

Read firmware-published QPOSMAX and compute wrap-safe direction using modulus

• Defines per-channel QPOSMAX DMEM offsets, initializes qposmax_base pointers, and reads QPOSMAX once after firmware start. Replaces 32-bit wrap assumptions in direction detection with modulus-aware re-wrap based on (QPOSMAX+1) in both the main polling loop and EQEP_Get_position_ABZ.

examples/pru_eqep/mcuplus/pru_eqep_example.c

Documentation (1) +19 / -1
readme.mdDocument QPOSMAX/PPR configuration and modulus-aware direction behavior +19/-1

Document QPOSMAX/PPR configuration and modulus-aware direction behavior

• Updates troubleshooting guidance to reflect bounded QPOS behavior and modulus-aware direction computation. Adds a dedicated section explaining how to set QPOSMAX for a given encoder PPR and clarifies that the value is build-time (not runtime) configurable.

examples/pru_eqep/readme.md

Other (1) +6 / -7
example.syscfgUpdate debug_log syscfg settings (UART enabled, CCS log disabled, baud set) +6/-7

Update debug_log syscfg settings (UART enabled, CCS log disabled, baud set)

• Reworks debug_log configuration entries, enabling UART logging with explicit baud rate and disabling CCS log. This is a configuration-only change for the example build/runtime logging behavior.

examples/pru_eqep/mcuplus/am243x-lp/r5fss0-0_freertos/example.syscfg

@qodo-code-review

qodo-code-review Bot commented Aug 19, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (5) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Unsynced QPOSMAX read 🐞 Bug ☼ Reliability
Description
R5F reads qposmax immediately after starting the PRU cores, but there is no handshake/ready flag
ensuring the PRU has executed its boot-time sbco publish yet. If the read happens first (DMEM is
zeroed at init), modulus becomes 1 and the new direction wrap logic produces incorrect direction
results.
Code

examples/pru_eqep/mcuplus/pru_eqep_example.c[R318-325]

+    /* Read QPOSMAX once, after firmware start: PRU publishes it to DMEM
+     * during its own init block (before the capture loop begins), so it
+     * is guaranteed valid here. Read once, not per-poll, since firmware
+     * never rewrites it again after boot. */
+    for (int i = 0; i < 6; i++)
+    {
+        ABZHandle[i]->qposmax = HW_RD_REG32((uint32_t)ABZHandle[i]->qposmax_base);
+    }
Evidence
PRU publishes QPOSMAX at boot, but R5F reads it immediately after core enable without any
synchronization; since ABZ_PRU_ICSS_Init clears PRU data RAM to 0, an early read can return 0 and
invalidate the modulus-based direction calculation.

examples/pru_eqep/firmware/main.asm[64-68]
examples/pru_eqep/mcuplus/pru_eqep_example.c[141-208]
examples/pru_eqep/mcuplus/pru_eqep_example.c[318-325]
examples/pru_eqep/mcuplus/pru_eqep_example.c[210-218]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
R5F reads `ABZHandle[i]->qposmax` right after `EQEP_pruss_load_run_fw()`, assuming PRU has already published `QPOSMAX` into DMEM1. However, core enable is asynchronous and DMEM is explicitly zeroed during init, so this read can return `0` transiently. That makes `modulus = qposmax + 1` equal `1` and breaks the new modulus-based direction computation.

### Issue Context
- PRU publishes QPOSMAX once at boot via `sbco`.
- R5F enables PRU cores and immediately reads QPOSMAX.
- PRU data RAM is cleared to 0 before firmware start.

### Fix Focus Areas
- examples/pru_eqep/mcuplus/pru_eqep_example.c[141-208]
- examples/pru_eqep/mcuplus/pru_eqep_example.c[318-325]
- examples/pru_eqep/firmware/main.asm[64-68]
- examples/pru_eqep/mcuplus/pru_eqep_example.c[210-218]

### Suggested fix
Implement a simple readiness handshake, e.g.:
1) Reserve a DMEM1 `READY` word per channel (or reuse an existing safe location).
2) PRU writes `QPOSMAX` and then writes `READY=0xA5A5A5A5`.
3) R5F loops with a timeout waiting for `READY` before reading `QPOSMAX` (or loops until `qposmax != 0`).
4) If timeout, log an error and fall back to a safe default (or abort).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Rollover reverses reported direction 🐞 Bug ≡ Correctness
Description
The firmware now wraps QPOS from 3999 to 0 and from 0 to 3999, while both R5F readers determine
direction using subtraction modulo 2^32. A forward boundary crossing therefore produces a negative
difference and reports reverse, while a reverse crossing reports forward.
Code

examples/pru_eqep/firmware/main.asm[R135-136]

+    qbge    no_qpos_overflow0, QPOS, scratch2   ; jump if scratch2 >= QPOS (i.e. QPOS <= QPOSMAX)
+    ldi     QPOS, 0
Evidence
The added firmware code reloads QPOS to zero after its configured maximum and reloads QPOSMAX after
decrementing below zero. However, the polling reader explicitly documents and implements only 32-bit
rollover handling at pru_eqep_example.c lines 359-375, and EQEP_Get_position_ABZ repeats that
subtraction at lines 538-544; with QPOSMAX=3999, 3999→0 yields -3999 and 0→3999 yields +3999.

examples/pru_eqep/firmware/main.asm[131-146]
examples/pru_eqep/firmware/main.asm[173-188]
examples/pru_eqep/firmware/include/macros.inc[56-57]
examples/pru_eqep/mcuplus/pru_eqep_example.c[348-375]
examples/pru_eqep/mcuplus/pru_eqep_example.c[526-545]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Firmware now bounds QPOS using `QPOSMAX`, but R5F direction calculations still assume modulo-2^32 rollover. Update direction detection to use the configured counter modulus so crossings between zero and QPOSMAX preserve the actual direction.

## Issue Context
Both polling and `EQEP_Get_position_ABZ()` use signed subtraction intended for `0xFFFFFFFF` rollover. Ensure the host and firmware obtain QPOSMAX from a synchronized definition or shared configuration rather than duplicating an independently editable value.

## Fix Focus Areas
- examples/pru_eqep/firmware/main.asm[131-146]
- examples/pru_eqep/firmware/main.asm[173-188]
- examples/pru_eqep/mcuplus/pru_eqep_example.c[348-375]
- examples/pru_eqep/mcuplus/pru_eqep_example.c[526-545]
- examples/pru_eqep/firmware/include/macros.inc[56-57]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Direction flips on long stalls 🐞 Bug ≡ Correctness ⭐ New
Description
The new modulus-based rewrap forces qpos_diff into [-modulus/2, modulus/2), which will report the
opposite direction if the polling loop is delayed long enough for QPOS to advance by more than half
the modulus between reads. This can happen during UART logging or task preemption, producing
deterministic wrong direction even without any counter wraparound.
Code

examples/pru_eqep/mcuplus/pru_eqep_example.c[R396-399]

+            int32_t modulus = (int32_t)ABZHandle[ch]->qposmax + 1;
            int32_t qpos_diff = (int32_t)(ABZHandle[ch]->QPOSCOUNT - ABZHandle[ch]->prev_QPOS);
+            if      (qpos_diff >  modulus / 2) qpos_diff -= modulus;
+            else if (qpos_diff < -modulus / 2) qpos_diff += modulus;
Evidence
The newly added modulus-rewrap code explicitly flips large deltas into the opposite sign, and the
same task contains potentially blocking UART logging—making it realistic for deltas to exceed
modulus/2 between reads and thus invert direction.

examples/pru_eqep/mcuplus/pru_eqep_example.c[390-402]
examples/pru_eqep/mcuplus/pru_eqep_example.c[410-427]
examples/pru_eqep/mcuplus/pru_eqep_example.c[561-573]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Direction is derived from a modulus-wrapped delta:
- Compute `qpos_diff = curr - prev`
- If `qpos_diff > modulus/2`, subtract modulus; if `< -modulus/2`, add modulus

If the task stalls (UART prints, preemption, interrupts), `curr-prev` can legitimately exceed `modulus/2` without any wrap, and the current logic will *flip the sign* and report the wrong direction.

### Issue Context
The main loop includes periodic `DebugP_log(...)` calls which can block long enough for many encoder edges to accumulate.

### Fix Focus Areas
- examples/pru_eqep/mcuplus/pru_eqep_example.c[390-402]
- examples/pru_eqep/mcuplus/pru_eqep_example.c[410-427]
- examples/pru_eqep/mcuplus/pru_eqep_example.c[564-573]

### What to change
- Add an explicit ambiguity guard:
 - If `abs(qpos_diff) > modulus/2` (or `>=` depending on your convention), do **not** infer direction from the wrapped delta.
 - Instead set direction to 0/unknown, or keep the previous direction, or (best) fetch direction from a PRU-provided direction signal/metadata.
- Apply the same fix in both places where the modulus rewrap logic exists (main polling loop and `EQEP_Get_position_ABZ`).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Unknown SysCfg property ✗ Dismissed 🐞 Bug ≡ Correctness ⭐ New
Description
The SysCfg file sets debug_log.enableCssLog, but this property is not used anywhere else in the
repo’s SysCfg examples, so SysCfg generation may fail or silently ignore the setting. If
ignored/failing, debug logging configuration will not match what the example expects.
Code

examples/pru_eqep/mcuplus/am243x-lp/r5fss0-0_freertos/example.syscfg[R100-101]

+debug_log.enableUartLog        = true;
+debug_log.enableCssLog         = false;
Evidence
The PR introduces debug_log.enableCssLog, but this key is unique in the repo, while other examples
configure debug_log without it—suggesting it may be unsupported and could break SysCfg processing.

examples/pru_eqep/mcuplus/am243x-lp/r5fss0-0_freertos/example.syscfg[100-110]
examples/spi_loopback/spi_loopback_app/am243x-lp/r5fss0-0_freertos/example.syscfg[62-69]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`example.syscfg` sets `debug_log.enableCssLog`, which appears to be an invalid/unknown configuration knob in this repo context (no other SysCfg uses it). This can break SysCfg generation or lead to the setting being ignored.

### Issue Context
Other example `.syscfg` files configure `debug_log` without any `enableCssLog` field.

### Fix Focus Areas
- examples/pru_eqep/mcuplus/am243x-lp/r5fss0-0_freertos/example.syscfg[100-105]

### What to change
- Confirm the correct SysCfg property name(s) for disabling CCS/shared-mem logging in the `debug_log` module used by this SDK version.
- Replace/remove `debug_log.enableCssLog` accordingly.
- If the intention is “UART-only logging”, align this file with patterns used in other examples (e.g., only `enableUartLog` + uart instance config), or explicitly set the actually-supported shared-mem log toggles.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. QPOSMAX reloaded per edge 🐞 Bug ➹ Performance
Description
The PRU hot path reloads the constant QPOSMAX via ldi32 on every increment path in both duplicated
LUT blocks, adding extra instructions to the edge-processing critical path. This reduces the maximum
sustainable encoder edge rate compared to loading QPOSMAX once at boot into a dedicated register.
Code

examples/pru_eqep/firmware/main.asm[R139-142]

+    ldi32   scratch2, QPOSMAX
+    qbge    no_qpos_overflow0, QPOS, scratch2   ; jump if scratch2 >= QPOS (i.e. QPOS <= QPOSMAX)
+    ldi     QPOS, 0
+no_qpos_overflow0:
Evidence
The increment paths in both duplicated update blocks perform ldi32 scratch2, QPOSMAX just to do
the wrap compare, despite QPOSMAX being constant and already available to load once in the init
block.

examples/pru_eqep/firmware/main.asm[138-142]
examples/pru_eqep/firmware/main.asm[179-184]
examples/pru_eqep/firmware/main.asm[64-68]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`ldi32 scratch2, QPOSMAX` is executed on every qualifying edge before the wrap compare, even though QPOSMAX is a compile-time constant.

### Issue Context
This firmware claims high-speed capture; adding avoidable instructions inside the per-edge loop reduces the headroom.

### Fix Focus Areas
- examples/pru_eqep/firmware/main.asm[64-68]
- examples/pru_eqep/firmware/main.asm[138-142]
- examples/pru_eqep/firmware/main.asm[179-184]

### Suggested fix
- Load QPOSMAX once during init into an unused register (e.g., `r29`/`r30` depending on conventions), and use that register for both wrap compares.
- Ensure both duplicated LUT-handling blocks use the same cached register so the hot path does not perform constant reloads.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

6. Unused QPOSMAX offset defines 🐞 Bug ⚙ Maintainability ⭐ New
Description
The PR adds per-channel CH1_QPOSMAX_OFFSET..CH5_QPOSMAX_OFFSET defines, but the code initializes
all qposmax_base pointers using only CH0_QPOSMAX_OFFSET, leaving the per-channel constants
unused and likely to drift out of sync with firmware offsets. This increases maintenance risk for
future layout changes.
Code

examples/pru_eqep/mcuplus/pru_eqep_example.c[R78-82]

+// Define QPOSMAX offsets for each channel (PRU-write-once at boot, R5F-read-once at init)
+#define CH0_QPOSMAX_OFFSET 0x4C
+#define CH1_QPOSMAX_OFFSET 0x50
+#define CH2_QPOSMAX_OFFSET 0x54
+#define CH3_QPOSMAX_OFFSET 0x58
Evidence
The new per-channel QPOSMAX offsets are defined, but the subsequent initialization uses only
CH0_QPOSMAX_OFFSET for every channel’s qposmax_base, making the other new defines dead and
potentially misleading.

examples/pru_eqep/mcuplus/pru_eqep_example.c[78-85]
examples/pru_eqep/mcuplus/pru_eqep_example.c[282-287]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Per-channel QPOSMAX offset macros were added, but the initialization uses only `CH0_QPOSMAX_OFFSET` for all channels. This leaves the new macros unused and creates duplicated constants that can silently drift.

### Issue Context
Today this likely “works by construction” because `baseMemAddr1` is offset by `i*4`, but that coupling is non-obvious and fragile.

### Fix Focus Areas
- examples/pru_eqep/mcuplus/pru_eqep_example.c[78-85]
- examples/pru_eqep/mcuplus/pru_eqep_example.c[282-287]

### What to change
Pick one:
1) Remove `CH1_QPOSMAX_OFFSET..CH5_QPOSMAX_OFFSET` and document that per-channel spacing is provided by the `baseMemAddr1` stride.
2) Use the per-channel defines when building `qposmax_base` (e.g., an offset array indexed by channel), so the code matches the comments and the constants serve a purpose.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can tweak Display preferences with a live preview to see your comment before it ships

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Previous reviews

Review updated until commit 90136a2

Results up to commit f8618ff ⚖️ Balanced


🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)


Action required
1. Rollover reverses reported direction 🐞 Bug ≡ Correctness
Description
The firmware now wraps QPOS from 3999 to 0 and from 0 to 3999, while both R5F readers determine
direction using subtraction modulo 2^32. A forward boundary crossing therefore produces a negative
difference and reports reverse, while a reverse crossing reports forward.
Code

examples/pru_eqep/firmware/main.asm[R135-136]

+    qbge    no_qpos_overflow0, QPOS, scratch2   ; jump if scratch2 >= QPOS (i.e. QPOS <= QPOSMAX)
+    ldi     QPOS, 0
Evidence
The added firmware code reloads QPOS to zero after its configured maximum and reloads QPOSMAX after
decrementing below zero. However, the polling reader explicitly documents and implements only 32-bit
rollover handling at pru_eqep_example.c lines 359-375, and EQEP_Get_position_ABZ repeats that
subtraction at lines 538-544; with QPOSMAX=3999, 3999→0 yields -3999 and 0→3999 yields +3999.

examples/pru_eqep/firmware/main.asm[131-146]
examples/pru_eqep/firmware/main.asm[173-188]
examples/pru_eqep/firmware/include/macros.inc[56-57]
examples/pru_eqep/mcuplus/pru_eqep_example.c[348-375]
examples/pru_eqep/mcuplus/pru_eqep_example.c[526-545]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Firmware now bounds QPOS using `QPOSMAX`, but R5F direction calculations still assume modulo-2^32 rollover. Update direction detection to use the configured counter modulus so crossings between zero and QPOSMAX preserve the actual direction.

## Issue Context
Both polling and `EQEP_Get_position_ABZ()` use signed subtraction intended for `0xFFFFFFFF` rollover. Ensure the host and firmware obtain QPOSMAX from a synchronized definition or shared configuration rather than duplicating an independently editable value.

## Fix Focus Areas
- examples/pru_eqep/firmware/main.asm[131-146]
- examples/pru_eqep/firmware/main.asm[173-188]
- examples/pru_eqep/mcuplus/pru_eqep_example.c[348-375]
- examples/pru_eqep/mcuplus/pru_eqep_example.c[526-545]
- examples/pru_eqep/firmware/include/macros.inc[56-57]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Results up to commit 2ba57e6 ⚖️ Balanced


🐞 Bugs (2) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)


Action required
1. Unsynced QPOSMAX read 🐞 Bug ☼ Reliability
Description
R5F reads qposmax immediately after starting the PRU cores, but there is no handshake/ready flag
ensuring the PRU has executed its boot-time sbco publish yet. If the read happens first (DMEM is
zeroed at init), modulus becomes 1 and the new direction wrap logic produces incorrect direction
results.
Code

examples/pru_eqep/mcuplus/pru_eqep_example.c[R318-325]

+    /* Read QPOSMAX once, after firmware start: PRU publishes it to DMEM
+     * during its own init block (before the capture loop begins), so it
+     * is guaranteed valid here. Read once, not per-poll, since firmware
+     * never rewrites it again after boot. */
+    for (int i = 0; i < 6; i++)
+    {
+        ABZHandle[i]->qposmax = HW_RD_REG32((uint32_t)ABZHandle[i]->qposmax_base);
+    }
Evidence
PRU publishes QPOSMAX at boot, but R5F reads it immediately after core enable without any
synchronization; since ABZ_PRU_ICSS_Init clears PRU data RAM to 0, an early read can return 0 and
invalidate the modulus-based direction calculation.

examples/pru_eqep/firmware/main.asm[64-68]
examples/pru_eqep/mcuplus/pru_eqep_example.c[141-208]
examples/pru_eqep/mcuplus/pru_eqep_example.c[318-325]
examples/pru_eqep/mcuplus/pru_eqep_example.c[210-218]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
R5F reads `ABZHandle[i]->qposmax` right after `EQEP_pruss_load_run_fw()`, assuming PRU has already published `QPOSMAX` into DMEM1. However, core enable is asynchronous and DMEM is explicitly zeroed during init, so this read can return `0` transiently. That makes `modulus = qposmax + 1` equal `1` and breaks the new modulus-based direction computation.

### Issue Context
- PRU publishes QPOSMAX once at boot via `sbco`.
- R5F enables PRU cores and immediately reads QPOSMAX.
- PRU data RAM is cleared to 0 before firmware start.

### Fix Focus Areas
- examples/pru_eqep/mcuplus/pru_eqep_example.c[141-208]
- examples/pru_eqep/mcuplus/pru_eqep_example.c[318-325]
- examples/pru_eqep/firmware/main.asm[64-68]
- examples/pru_eqep/mcuplus/pru_eqep_example.c[210-218]

### Suggested fix
Implement a simple readiness handshake, e.g.:
1) Reserve a DMEM1 `READY` word per channel (or reuse an existing safe location).
2) PRU writes `QPOSMAX` and then writes `READY=0xA5A5A5A5`.
3) R5F loops with a timeout waiting for `READY` before reading `QPOSMAX` (or loops until `qposmax != 0`).
4) If timeout, log an error and fall back to a safe default (or abort).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended
2. QPOSMAX reloaded per edge 🐞 Bug ➹ Performance
Description
The PRU hot path reloads the constant QPOSMAX via ldi32 on every increment path in both duplicated
LUT blocks, adding extra instructions to the edge-processing critical path. This reduces the maximum
sustainable encoder edge rate compared to loading QPOSMAX once at boot into a dedicated register.
Code

examples/pru_eqep/firmware/main.asm[R139-142]

+    ldi32   scratch2, QPOSMAX
+    qbge    no_qpos_overflow0, QPOS, scratch2   ; jump if scratch2 >= QPOS (i.e. QPOS <= QPOSMAX)
+    ldi     QPOS, 0
+no_qpos_overflow0:
Evidence
The increment paths in both duplicated update blocks perform ldi32 scratch2, QPOSMAX just to do
the wrap compare, despite QPOSMAX being constant and already available to load once in the init
block.

examples/pru_eqep/firmware/main.asm[138-142]
examples/pru_eqep/firmware/main.asm[179-184]
examples/pru_eqep/firmware/main.asm[64-68]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`ldi32 scratch2, QPOSMAX` is executed on every qualifying edge before the wrap compare, even though QPOSMAX is a compile-time constant.

### Issue Context
This firmware claims high-speed capture; adding avoidable instructions inside the per-edge loop reduces the headroom.

### Fix Focus Areas
- examples/pru_eqep/firmware/main.asm[64-68]
- examples/pru_eqep/firmware/main.asm[138-142]
- examples/pru_eqep/firmware/main.asm[179-184]

### Suggested fix
- Load QPOSMAX once during init into an unused register (e.g., `r29`/`r30` depending on conventions), and use that register for both wrap compares.
- Ensure both duplicated LUT-handling blocks use the same cached register so the hot path does not perform constant reloads.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread examples/pru_eqep/firmware/main.asm Outdated
@a1248924

Copy link
Copy Markdown
Collaborator Author

/agentic_review

@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit f8618ff

@a1248924
a1248924 force-pushed the ADD_PRU_EQEP_PPR_CONFIG branch from f8618ff to 2ba57e6 Compare August 21, 2026 06:06
@a1248924

Copy link
Copy Markdown
Collaborator Author

/agentic_review

Comment thread examples/pru_eqep/mcuplus/pru_eqep_example.c
Comment thread examples/pru_eqep/firmware/main.asm Outdated
@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 2ba57e6

signed-off by Ayushman <a-ayushman@ti.com>
@a1248924
a1248924 force-pushed the ADD_PRU_EQEP_PPR_CONFIG branch from 2ba57e6 to 90136a2 Compare August 21, 2026 06:27
Comment on lines +396 to +399
int32_t modulus = (int32_t)ABZHandle[ch]->qposmax + 1;
int32_t qpos_diff = (int32_t)(ABZHandle[ch]->QPOSCOUNT - ABZHandle[ch]->prev_QPOS);
if (qpos_diff > modulus / 2) qpos_diff -= modulus;
else if (qpos_diff < -modulus / 2) qpos_diff += modulus;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

2. Direction flips on long stalls 🐞 Bug ≡ Correctness

The new modulus-based rewrap forces qpos_diff into [-modulus/2, modulus/2), which will report the
opposite direction if the polling loop is delayed long enough for QPOS to advance by more than half
the modulus between reads. This can happen during UART logging or task preemption, producing
deterministic wrong direction even without any counter wraparound.
Agent Prompt
### Issue description
Direction is derived from a modulus-wrapped delta:
- Compute `qpos_diff = curr - prev`
- If `qpos_diff > modulus/2`, subtract modulus; if `< -modulus/2`, add modulus

If the task stalls (UART prints, preemption, interrupts), `curr-prev` can legitimately exceed `modulus/2` without any wrap, and the current logic will *flip the sign* and report the wrong direction.

### Issue Context
The main loop includes periodic `DebugP_log(...)` calls which can block long enough for many encoder edges to accumulate.

### Fix Focus Areas
- examples/pru_eqep/mcuplus/pru_eqep_example.c[390-402]
- examples/pru_eqep/mcuplus/pru_eqep_example.c[410-427]
- examples/pru_eqep/mcuplus/pru_eqep_example.c[564-573]

### What to change
- Add an explicit ambiguity guard:
  - If `abs(qpos_diff) > modulus/2` (or `>=` depending on your convention), do **not** infer direction from the wrapped delta.
  - Instead set direction to 0/unknown, or keep the previous direction, or (best) fetch direction from a PRU-provided direction signal/metadata.
- Apply the same fix in both places where the modulus rewrap logic exists (main polling loop and `EQEP_Get_position_ABZ`).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +78 to +82
// Define QPOSMAX offsets for each channel (PRU-write-once at boot, R5F-read-once at init)
#define CH0_QPOSMAX_OFFSET 0x4C
#define CH1_QPOSMAX_OFFSET 0x50
#define CH2_QPOSMAX_OFFSET 0x54
#define CH3_QPOSMAX_OFFSET 0x58

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Informational

3. Unused qposmax offset defines 🐞 Bug ⚙ Maintainability

The PR adds per-channel CH1_QPOSMAX_OFFSET..CH5_QPOSMAX_OFFSET defines, but the code initializes
all qposmax_base pointers using only CH0_QPOSMAX_OFFSET, leaving the per-channel constants
unused and likely to drift out of sync with firmware offsets. This increases maintenance risk for
future layout changes.
Agent Prompt
### Issue description
Per-channel QPOSMAX offset macros were added, but the initialization uses only `CH0_QPOSMAX_OFFSET` for all channels. This leaves the new macros unused and creates duplicated constants that can silently drift.

### Issue Context
Today this likely “works by construction” because `baseMemAddr1` is offset by `i*4`, but that coupling is non-obvious and fragile.

### Fix Focus Areas
- examples/pru_eqep/mcuplus/pru_eqep_example.c[78-85]
- examples/pru_eqep/mcuplus/pru_eqep_example.c[282-287]

### What to change
Pick one:
1) Remove `CH1_QPOSMAX_OFFSET..CH5_QPOSMAX_OFFSET` and document that per-channel spacing is provided by the `baseMemAddr1` stride.
2) Use the per-channel defines when building `qposmax_base` (e.g., an offset array indexed by channel), so the code matches the comments and the constants serve a purpose.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 90136a2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant