These best practices will be used as by Qodo Merge as context for evaluation during the Improve stage
This document outlines best practices for developing PRU (Programmable Real-time Unit) firmware for Texas Instruments Sitara devices. The PRU is a deterministic, real-time processor subsystem that provides precise timing control and low-latency I/O operations.
Supported Devices:
- AM243x, AM261x, AM263x, AM263Px, AM62x, AM64x series
- Predictable Timing: Every instruction should have predictable execution time except load/store from outside ICSS IP
- Avoid Conditional Branches: When possible, use unconditional code paths
- Cycle Counting: Always document cycle counts for critical code sections
- Interrupt Handling: Minimize interrupt latency and processing time
- Register Usage: Efficiently utilize the 32 available registers (R0-R31). R30 and R31 are special purpose registers connected to IO and may not be available for general purpose use
- Memory Conservation: PRU has limited memory (8KB to 16KB instruction which is device dependent, 8KB data and 12KB/32KB/64KB shared data mem which is device dependent)
- Power Efficiency: Implement power-saving techniques when appropriate via sleep instruction
- Modular Design: Use macros and includes for reusable code
- Clear Documentation: Document timing requirements and hardware dependencies
- Version Control: Follow consistent naming and versioning conventions
Before any memory access, ensure the address is within allocated bounds.
Example:
; Safe memory write with bounds checking
safe_write:
; Check if offset is within bounds
ldi32 TEMP_REG_2, WRITE_OFFSET
ldi32 TEMP_REG_3, MAX_BUFFER_SIZE
qbgt handle_error, TEMP_REG_3, TEMP_REG_2 ; Jump to error if offset > max
; Safe to write
add TEMP_REG_2, TEMP_REG_2, DATA_BUFFER_OFFSET ; DATA_BUFFER_OFFSET < 255
sbco &r_dataReg, c28, TEMP_REG_2, 4
qba continue_execution
handle_error:
; Set error flag and handle gracefully
ldi ERROR_REG, ERR_BOUNDS_VIOLATION
qba error_handlerDefine and use constants for all memory boundaries.
Example:
; Define memory constants
.define DATA_BUFFER_START 0x1000
.define DATA_BUFFER_SIZE 0x400 ; 1KB buffer
.define DATA_BUFFER_END 0x1400 ; START + SIZE
; Validate access is within [START, END)
validate_address:
ldi TEMP_REG_2.w0, DATA_BUFFER_START
qblt addr_too_low, TEMP_REG_2.w0, TEMP_REG_1
ldi TEMP_REG_2.w0, DATA_BUFFER_END
qbge addr_too_high, TEMP_REG_2.w0, TEMP_REG_1
; Address is valid, continue...Always include termination conditions and bounds checking in loops.
Example:
; Safe loop with bounds checking
safe_loop_start:
ldi LOOP_COUNTER, 0
ldi MAX_ITERATIONS, 100
ldi MAX_BUFFER_SIZE, 1024
loop_body:
; Check loop bounds
qbeq loop_done, LOOP_COUNTER, MAX_ITERATIONS
; Calculate safe offset
lsl TEMP_OFFSET, LOOP_COUNTER, 2 ; Multiply by 4 for word access
qbgt loop_done, MAX_BUFFER_SIZE, TEMP_OFFSET
; Safe memory access
add TEMP_ADDR, DATA_BUFFER_OFFSET, TEMP_OFFSET
lbco &dataReg, c28, TEMP_ADDR, 4
; Process data...
; Increment and continue
add LOOP_COUNTER, LOOP_COUNTER, 1
qba loop_body
loop_done:
; Clean exit4. Use Defensive Programming Techniques in use cases when performance considerations allow this or requirements mandate it
Add sanity checks and fail-safe mechanisms.
Example:
; Defensive buffer access with multiple checks
secure_buffer_access:
; Validate pointer is not null
qbeq null_ptr_error, BUFFER_PTR, 0
; Validate size is reasonable
ldi32 TEMP_REG_1, MAX_ALLOWED_SIZE
qbgt size_error, TEMP_REG_1, ACCESS_SIZE
; Calculate end address and check for overflow
add END_ADDR, BUFFER_PTR, ACCESS_SIZE
qblt overflow_error, BUFFER_PTR, END_ADDR ; Overflow if end < start
; Verify end address is within bounds
ldi32 TEMP_REG_2, MEMORY_LIMIT
qbgt bounds_error, END_ADDR, TEMP_REG_2
; All checks passed - safe to access
lbco &dataReg, c28, BUFFER_PTR, ACCESS_SIZEClear documentation prevents mistakes.
Example:
;==============================================================================
; Memory Layout:
; 0x0000 - 0x0FFF : Reserved system memory (DO NOT ACCESS)
; 0x1000 - 0x13FF : Data buffer (1KB)
; 0x1400 - 0x17FF : Stack space (1KB)
; 0x1800 - 0x1FFF : Shared memory region (2KB)
;==============================================================================
; Safe accessor macro for data buffer
.macro m_SAFE_DATA_ACCESS offset, size, dest_reg
; Validate offset + size <= buffer size
ldi32 TEMP_CHECK, \offset + \size
ldi32 TEMP_LIMIT, DATA_BUFFER_SIZE
qbgt memory_fault, TEMP_LIMIT, TEMP_CHECK
; Perform safe access
ldi32 TEMP_ADDR, DATA_BUFFER_START + \offset
lbco &\dest_reg, c28, TEMP_ADDR, \size
.endmHandle errors gracefully rather than crashing.
Example:
; Centralized error handler
error_handler:
; Log error type
sbco &ERROR_REG, c28, ERROR_LOG_ADDR, 4
; Reset to safe state
ldi BUFFER_PTR, DATA_BUFFER_START
ldi ACCESS_SIZE, 0
; Signal error to host
ldi r31.b0, PRU_ERROR_SIGNAL
; Return to safe execution point
qba safe_restart_pointLeverage assembler features to catch errors at build time.
Example:
; Use assembler assertions
.if (BUFFER_OFFSET + MAX_ACCESS_SIZE) > TOTAL_MEMORY_SIZE
.emsg "Buffer configuration exceeds memory limits"
.endif
; Create safe access patterns
.macro BOUNDS_CHECKED_WRITE reg, offset
.if (\offset >= MAX_BUFFER_SIZE)
.emsg "Static bounds check failed: offset too large"
.endif
sbco &\reg, c28, DATA_BUFFER_OFFSET + \offset, 4
.endm; Copyright header (required for TI projects)
; File description and purpose
; Build instructions
; Hardware requirements
; CCS/makefile specific settings
.retain ; Required for building .out with assembly file
.retainrefs ; Required for building .out with assembly file
.global main
.sect ".text"
; Include files
.include "icss_constant_defines.inc"
.include "custom_macros.inc"
; Register aliases for clarity
.asg R2, TEMP_REG
.asg R3.w0, OFFSET_ADDR
.asg 100000, DELAY_COUNT
; Main program
main:
; Implementation; Use consistent register aliases
.asg R0, DATA_REG
.asg R1, COUNTER_REG
.asg R2, TEMP_REG
.asg R3, ADDRESS_REG
; R30 - GPO (General Purpose Output)
; R31 - GPI (General Purpose Input)init:
; Always clear register space at startup
zero &r0, 120 ; Clear R0-R29 (30 registers × 4 bytes)
; Initialize specific registers
ldi DATA_REG, 0
ldi COUNTER_REG, 0;************************************************************************************
; Macro: m_delay_cycles
;
; PEAK cycles: 3 + DELAY_COUNT cycles
;
; Parameters:
; DELAY_COUNT - Number of cycles to delay
;************************************************************************************
m_delay_cycles .macro DELAY_COUNT
ldi32 r5, DELAY_COUNT ; 2 cycles
delay_loop?:
sub r5, r5, 1 ; 1 cycle
qbne delay_loop?, r5, 0 ; 1 cycle (when branching)
.endm; Preferred: LOOP instruction when feasible (more efficient)
loop loop_end, LOOP_COUNT
loop_start:
; Loop body
loop_end:
; Preferred second option: Down-counting loops
ldi COUNTER_REG, LOOP_COUNT
loop_start:
; Loop body
sub COUNTER_REG, COUNTER_REG, 1
qbne loop_start, COUNTER_REG, 0
; Avoid: Up-counting loops (less efficient)
; ldi COUNTER_REG, 0
; qbge loop_end, LOOP_COUNT, COUNTER_REG;************************************************************************************
; Macro: m_spi_transfer
;
; Description: Transfer data via SPI interface
;
; PEAK cycles: (7 + DELAY1 + DELAY2) × PACKET_SIZE
;
; Parameters:
; dataReg - Register containing data to send
; PACKET_SIZE - Size of data packet in bits (1-32)
; SCLK_PIN - PRU pin number for SCLK
; DELAY1 - High phase delay cycles
; DELAY2 - Low phase delay cycles
;
; Assumptions:
; - CS must be asserted before calling
; - SCLK_PIN configured as output
; - PACKET_SIZE validated (1-32 bits)
;************************************************************************************
m_spi_transfer .macro dataReg, PACKET_SIZE, SCLK_PIN, DELAY1, DELAY2
; Parameter validation
.if (PACKET_SIZE > 32) | (PACKET_SIZE < 1)
.emsg "Error: PACKET_SIZE must be 1-32 bits"
.endif
; Implementation
ldi r10, PACKET_SIZE - 1
transfer_loop?:
; Transfer logic here
.endm- Prefix with
m_for macros - Use descriptive names:
m_spi_read_packet,m_gpio_toggle - Include parameter validation
- Use unique local labels with
?suffix
; Use macros for pin operations
m_pru_set_pin .macro PIN_NUM
set r30, r30, PIN_NUM
.endm
m_pru_clr_pin .macro PIN_NUM
clr r30, r30, PIN_NUM
.endm
; Usage
m_pru_set_pin 4 ; Set GPIO pin 4
m_pru_clr_pin 4 ; Clear GPIO pin 4; Read input pins from R31
qbbc pin_low, r31, PIN_NUM ; Branch if bit clear
; Pin is high
qba continue
pin_low:
; Pin is low
continue:; Use appropriate addressing modes
lbbo &r2, r1, 0, 4 ; Load 4 bytes from address in r1
sbbo &r2, r1, 0, 4 ; Store 4 bytes to address in r1
; Burst operations for multiple bytes
lbbo &r2, r1, 0, 16 ; Load 16 bytes (4 registers)This section covers C code compiled for PRU cores using the TI PRU C/C++ compiler (clpru). For C code running on MCU+ host cores (R5F or A53), see C Code Best Practices (MCU+ Host Cores).
/*
* SPDX-License-Identifier: <license, usually BSD-3-Clause>
* Copyright (C) <Year> Texas Instruments Incorporated
*
* Brief file description
*/
#include <stdint.h>
int main(void)
{
/* Initialization */
/* Main loop or one-shot logic */
__halt();
}Key conventions:
- Copyright header is required on all source files (see General Remarks).
- Use FIXME (not TODO) for pending work markers.
- Column limits: no text beyond column 79 (soft), column 129 (hard).
These practices apply to C code running on MCU+ host cores (R5F or A53) using the TI ARM Clang compiler. For C firmware running on PRU cores, see PRU C Firmware Best Practices above.
#include <stdio.h>
#include <kernel/dpl/DebugP.h>
#include "ti_drivers_config.h"
#include "ti_drivers_open_close.h"
#include "ti_board_open_close.h"
#include <drivers/pruicss.h>
/* Include PRU firmware headers */
#include <pru0_load_bin.h>
#include <pru1_load_bin.h>void pru_application_main(void *args)
{
int32_t status;
/* Initialize drivers with error checking */
Drivers_open();
status = Board_driversOpen();
DebugP_assert(SystemP_SUCCESS == status);
/* PRU initialization */
gPruIcssHandle = PRUICSS_open(CONFIG_PRU_ICSS0);
DebugP_assert(gPruIcssHandle != NULL);
/* Memory initialization */
status = PRUICSS_initMemory(gPruIcssHandle, PRUICSS_DATARAM(PRUICSS_PRU0));
DebugP_assert(status != 0);
/* Firmware loading with size validation */
status = PRUICSS_loadFirmware(gPruIcssHandle, PRUICSS_PRU0,
PRU0Firmware_0, sizeof(PRU0Firmware_0));
DebugP_assert(SystemP_SUCCESS == status);
}/* Always clean up resources */
void cleanup_resources(void)
{
if (gPruIcssHandle != NULL) {
PRUICSS_close(gPruIcssHandle);
}
Board_driversClose();
Drivers_close();
}/* Define shared memory structures */
typedef struct {
uint32_t command;
uint32_t data[16];
uint32_t status;
} pru_shared_mem_t;
/* Access shared memory safely */
void write_pru_command(uint32_t command, uint32_t *data, uint32_t length)
{
volatile pru_shared_mem_t *shared_mem =
(volatile pru_shared_mem_t *)PRUICSS_getSharedMemAddr(gPruIcssHandle);
/* Write data first */
for (uint32_t i = 0; i < length && i < 16; i++) {
shared_mem->data[i] = data[i];
}
/* Write command last to trigger PRU processing */
shared_mem->command = command;
}See docs/open_pru_organization.md for the authoritative project and repository layout.
- Assembly files:
main.asm,spi_driver.asm - Include files:
custom_macros.inc,register_defines.inc - C files:
pru_application.c,spi_interface.c - Use lowercase with underscores
; Preferred: Branchless code
qbbc skip_set, r31, INPUT_PIN
set r30, r30, OUTPUT_PIN
skip_set:
clr r30, r30, OUTPUT_PIN
; Instead of multiple conditional branches; For small, fixed iteration counts
; Unrolled loop (4 iterations)
operation_macro PARAM1
operation_macro PARAM2
operation_macro PARAM3
operation_macro PARAM4
; Instead of:
; ldi r1, 4
; loop:
; operation_macro
; sub r1, r1, 1
; qbne loop, r1, 0; Load multiple registers in one operation (except in scenarios which requires predicatble task manager switching latency as ongoing burst instruction from lowee priority back ground task can stall task switch)
lbbo &r2, r1, 0, 16 ; Load r2, r3, r4, r5 (16 bytes)
; Instead of individual loads
; lbbo &r2, r1, 0, 4
; lbbo &r3, r1, 4, 4
; lbbo &r4, r1, 8, 4
; lbbo &r5, r1, 12, 4; Add debug outputs for timing analysis
debug_point_1:
set r30, r30, DEBUG_PIN_1 ; Mark start of critical section
; Critical code here
clr r30, r30, DEBUG_PIN_1 ; Mark end of critical section
; Add debug outputs for timing analysis - alternatively use edio when available (slower but predictable latency < 4 PRU cycles)
; Add debug outputs for timing analysis - if use case allows and 100+ ns jitter is ok, can also use SoC GPIO// In host application
void log_pru_registers(void)
{
uint32_t *pru_regs = (uint32_t *)PRUICSS_getDataMemAddr(gPruIcssHandle, PRUICSS_PRU0);
DebugP_log("PRU Register Dump:\n");
for (int i = 0; i < 30; i++) {
DebugP_log("R%d: 0x%08x\n", i, pru_regs[i]);
}
}; Test macro for validation
m_test_assert .macro condition, error_code
.if condition
; Test passed
.else
ldi r30.b0, error_code
halt
.endif
.endm- The basic indentation for assembly & C code must be 4 spaces.
- Single spaces and newlines are the only allowed white space characters.
- double spaces may be used in markdown docs to force a carriage return at the end of a line.
- Unix end-of-line character ("\n" or ASCII LF (0x0A)) will be used on all files.
- All source files must contain a header denoting the file name.
- Any reference to a file name shall match the capitalization of the actual file name.
- All source and header files must contain a copyright statement attributing the files to Texas Instruments.
- No random blank line pattern must exist in the code.
- Use "for example" instead of "e.g.".
- Use "that is" instead of "i.e.".
- Use "and so on" instead of "etc,".
- No text beyond column 79 in source files (does not apply to markdown docs or .txt docs).
- Column 79 is the "soft" limit.
- Long lines of code are more difficult to follow and interrupt code flow.
- There shall be no text beyond column 129.
- This is the "hard" limit.
- Lines longer than column 79 should be the exceptional case in any source file.
- The extension for clpru (CGT PRU) source files shall be ".asm".
- The extension for pasm source files shall be ".p".
- The extension for clpru header files shall be ".h".
- The extension for pasm header files shall be ".hp" or ".h".
- It is recommended to create separate sources for functions/macros which can be grouped in terms of feature, function etc to enable reuse
- It is recommended to create separate headers for macros which can be grouped together wherever possible
Registers are most critical resource in PRU assembly programs. Since they are scarce - often error prone as multiple portions of program may reuse same register or part of register and this unintended corruption leads to really hard to debug issues in practice. Documenting function and macros helps to avoid this issues but since multiple developers may be using the same code base and reuse of functions and macros across different firmware programs - this requires stricter discipline for compliance.
Naming registers using macros like define, .set, .asg is common practice and considered to be helping with readability. This guideline strongly discourages this practice. The recommended approach is below (more in line of object oriented programming principles).
This guideline recommends to avoid direct references to PRU registers (even via define, .set, .asg directives) and always access them as structure datatype. clpru requires to use .struct and .sassign directives for this purpose. This forces alignment checks, same structure can be re-used in different context and can be mapped to different set of registers. Another important advantage is that this makes it easy to check for register overlaps via monitoring .sassign declarations and could be automated or built into assembler warnings of some sort.
Reg_params_s .struct
x_u .union
x .uint
.struct
w0 .ushort
w2 .ushort
.endstruct
.endunion
y .ubyte
z .ubyte
.endstruct
Reg .sassign R20, Reg_params_s ; Assign R20 to Reg_params_s
M_MOV32 .macro ARG1, ARG2
LDI ARG1.w0 , :ARG2: & 0xFFFF
LDI ARG1.w2 , :ARG2: >> 16
.endm
main:
M_MOV32 Reg.x_u, 0 Note that due to a clpru issue - one can't access register modifier suffix (.b0, .w0 etc) from structure elements. In a way this lack of support can be considered like protection against unintended/unwanted access from a different context. Programmer can always expose desired byte, halfword access via union.
NaturalDocs (Perl based cross platform and easy to use HTML documentation generator) is recommended for PRU assembly documentation. For detailed documentation tips - refer to https://www.naturaldocs.org/reference/
Following information is mandatory to be documented for functions and macros:
- Pseudo code: To enable quality code reviews and improved readability of assembly source
- Peak Cycle Budget: Worst case cycle budget - profiled using timer/cycle counter or hand counted. Most common hard to debug issues in PRU firmware are due to cycle budget overflow during execution. This is the only reliable way to monitor such issues systematically and in an organized manner.
- Registers modified: Including macros invoked within shall be documented clearly as register corruption is another common cause behind hard to debug issues
Quick Tips:
- Use <function_name> or <macro_name> to create HTML reference from description
- Use (start code), (end code) to highlight the code block. For small blocks - start the line with >, I or :
- text_to_display to underline and text_to_display for bold
Each developer updating an existing function or macro needs to update above fields before committing the changes to repository for code review.
This guideline does not intend to make an exhaustive requirement for naming of variables in this document. The predominant methods are styles like "CamelCase" as well as styles that use underscore between lowercase words. Either is acceptable. The requirements here are intentionally simplified as naming conventions tend to be fairly set for existing code, and changing them will not generally improve the quality. Some key guidelines for all new code are documented below:
-
All functions are recommended to start with prefix FN_<function_name>
- Example: FN_RCV_FB
- Must be all uppercase and in underscore_case format.
- This recommended to use
.asmfuncand.endasmfuncdirectives to mark start and end of function.
-
All tasks are recommended to be in the given format with fixed prefix and suffix - FN_<task_name>_TASK
- Example: FN_RX_EOF_TASK
- Must be all uppercase and in underscore_case format.
- This recommended to use
.asmfuncand.endasmfuncdirectives to mark start and end of task.
-
All macros are recommended to start with prefix m_<macro_name>.
- Above guideline is for standard macros (or macros for ethernet_switch).
- It is recommended to have a secondary prefix for protocol specific macros. Example - m_pn_<macro_name> for PROFINET.
- Macro name must be all lowercase and in underscore_case format.
-
All labels must be uppercase and in underscore_case format.
- It is recommended to use task/function/macro short name as prefix for label.
- Example: FN_RX_EOF_TASK → Labels start with RX_EOF_<>.
- Prefer line break before label for better readability.
-
All instruction operation code must be in lowercase - example xin instead of XIN, or in consistency with the existing code.
- White space (5 spaces for 3-letter operations and 4 spaces for 4-letter operations) shall provide better readability.
- Example:
instead of
xin PRU_SPAD_B0_XID, &MII_RCV, $sizeof(MII_RCV) qbbs RX_EOF_YIELD, MII_RCV.rx_status, rx_frame_errorxin PRU_SPAD_B0_XID, &MII_RCV, $sizeof(MII_RCV) qbbs RX_EOF_YIELD, MII_RCV.rx_status, rx_frame_error
-
Structure names must of the form Struct_<structure_name>.
- structure_name must be all uppercase and in underscore_case format.
- Example: Struct_MII_RCV where structure_name = MII_RCV
- structure_name can then be used for register assignment.
- Example: MII_RCV .sassign R21, Struct_MII_RCV
-
Only FIXME markers should be used for pending work/bugs. No TODO markers to be used.
- This will help for easy identification in firmware using single marker.
Compiler does not support forward declarations of symbols via the .set directive in an assembly file. Hence programmer must ensure that all symbols used as sources for .set directives are defined before they are used.
- It is recommended to include only header files required in .asm sources
- Four underscores recommended in header guard, example below:
.if !$isdefed("____icss_header_h") ____icss_header_h .set 1 .endif ; ____icss_header_h
;************************************************************************************
; File: filename.asm
;
; Brief: Brief description of file purpose
;
; Author: Developer Name
; Date: YYYY-MM-DD
; Version: X.Y.Z
;
; Hardware: Supported devices (AM243x, AM64x, etc.)
;
; Dependencies:
; - icss_constant_defines.inc
; - custom_macros.inc
;
; Build Instructions:
; CCS: Import project and build
; Makefile: make -C firmware all
;
; Performance:
; Critical path: X cycles
; Memory usage: Y bytes
;
; Notes:
; - Special considerations
; - Known limitations
;************************************************************************************;************************************************************************************
; Macro: m_function_name
;
; Description:
; Detailed description of what the macro does
;
; Pseudo code:
;
; PEAK cycles: X cycles (breakdown if complex)
; Memory usage: Y bytes
;
; Parameters:
; param1 - Description and valid range
; param2 - Description and constraints
;
; Returns:
; Description of return values or side effects
;
; Assumptions:
; - List all assumptions about system state
; - Hardware configuration requirements
; - Register usage assumptions
;
; Side Effects:
; - Modified registers
; - Changed pin states
; - Memory modifications
;
; Example:
; m_function_name r1, 8, PIN_4
;************************************************************************************
;************************************************************************************
;
; Macro: m_store_bytes_n_advance
;
; Store num_bytes to (*c28+write_ptr) offset from register file (reg_src)
;
; PEAK cycles:
; 2+(num_bytes/4)
;
; Invokes:
; None
;
; Pseudo code:
; (start code)
; for (i=0; i < num_bytes; i++) {
; *(uint8_t*)(c28+write_ptr+i) = *(uint8_t*)(®_src);
; }
; write_ptr += num_bytes;
; (end code)
;
; Parameters:
; reg_src : First register in the register file
; write_ptr : Write pointer register (Offset in shared memory)
; num_bytes : Number of bytes to write (32 or b0)
;
; Returns:
; None
;
; See Also:
;
;************************************************************************************
m_store_bytes_n_advance .macro reg_src, write_ptr, num_bytes
.var tmp
.asg :num_bytes(1):, tmp ; extract first char to tmp
.if $symcmp(tmp,"b") = 0 ;variable length : .b0, .b1, .b2, .b3
sbco ®_src, c28, write_ptr, num_bytes; r20.w0 is read pointer
add write_ptr, write_ptr, r0.:num_bytes: ; Increment bytes read
.else ; fixed length : 1..255
sbco ®_src, c28, write_ptr, num_bytes; r20.w0 is read pointer
add write_ptr, write_ptr, num_bytes; Increment bytes read
.endif
.endm
; Problem: Not preserving registers
function_call:
; Using r1 without saving
ldi r1, 0x1234
; ... function body
jmp r3.w2
; Solution: Save and restore registers
function_call:
; Save registers on stack or in unused registers
mov r29, r1 ; Save r1
ldi r1, 0x1234
; ... function body
mov r1, r29 ; Restore r1
jmp r3.w2; Problem: Assuming fixed timing without measurement
delay_loop:
sub r1, r1, 1
qbne delay_loop, r1, 0
; Assuming this takes exactly X microseconds
; Solution: Measure and document actual timing
; Measured timing: 3 cycles per iteration at 200MHz = 15ns per iteration
delay_loop:
sub r1, r1, 1 ; 1 cycle
qbne delay_loop, r1, 0 ; 2 cycles when branching; Problem: No parameter validation
m_bad_macro .macro PARAM
ldi r1, PARAM
.endm
; Solution: Always validate parameters
m_good_macro .macro PARAM
.if (PARAM > MAX_VALUE) | (PARAM < MIN_VALUE)
.emsg "Error: PARAM out of range"
.endif
ldi r1, PARAM
.endm// Problem: Race condition in shared memory access
shared_mem->data = new_value;
shared_mem->flag = 1; // PRU might read old data
// Solution: Use proper ordering and barriers
shared_mem->data = new_value;
__sync_synchronize(); // Memory barrier
shared_mem->flag = 1;// Problem: Not cleaning up on error paths
int init_pru(void) {
handle = PRUICSS_open(CONFIG_PRU_ICSS0);
if (some_error_condition) {
return -1; // Leaked handle!
}
return 0;
}
// Solution: Proper cleanup
int init_pru(void) {
handle = PRUICSS_open(CONFIG_PRU_ICSS0);
if (some_error_condition) {
PRUICSS_close(handle);
return -1;
}
return 0;
}For the complete and up-to-date PRU Assembly Instruction Cheat Sheet, see: docs/PRU Assembly Instruction Cheat Sheet.md
- Problem: Testing only happy path scenarios
- Solution: Test edge cases, error conditions, and boundary values
- Problem: No clear versioning or change tracking
- Solution: Use semantic versioning and detailed commit messages
- Problem: Code without timing specifications or hardware requirements
- Solution: Document all timing-critical sections and hardware dependencies