mirror of
https://github.com/openmv/openmv.git
synced 2025-09-26 23:09:13 +08:00
protocol: Introduce OMV protocol V2.
Signed-off-by: iabdalkader <i.abdalkader@gmail.com>
This commit is contained in:
parent
91a01ee978
commit
cc879d15b4
@ -33,11 +33,9 @@ COMMON_SRC_C += \
|
|||||||
omv_csi.c \
|
omv_csi.c \
|
||||||
omv_crc.c \
|
omv_crc.c \
|
||||||
pendsv.c \
|
pendsv.c \
|
||||||
tinyusb_debug.c \
|
|
||||||
trace.c \
|
trace.c \
|
||||||
umm_malloc.c \
|
umm_malloc.c \
|
||||||
unaligned_memcpy.c \
|
unaligned_memcpy.c \
|
||||||
usbdbg.c \
|
|
||||||
vospi.c \
|
vospi.c \
|
||||||
queue.c \
|
queue.c \
|
||||||
omv_profiler.c \
|
omv_profiler.c \
|
||||||
|
570
docs/protocol.md
Normal file
570
docs/protocol.md
Normal file
@ -0,0 +1,570 @@
|
|||||||
|
# OpenMV Protocol Specification
|
||||||
|
|
||||||
|
## 1. Overview
|
||||||
|
|
||||||
|
The OpenMV Protocol is a channel-based communication protocol for OpenMV camera devices. It provides reliable command/response communication with error detection, packet sequencing, and optimized data channel operations.
|
||||||
|
|
||||||
|
### 1.1 Key Features
|
||||||
|
- Channel-based architecture with 8-bit channel IDs (up to 32 channels)
|
||||||
|
- Transport-agnostic design (USB, UART, TCP/IP)
|
||||||
|
- Dual CRC-16-CCITT error detection (separate header and payload CRCs)
|
||||||
|
- Packet fragmentation for large transfers
|
||||||
|
- Zero-copy data transmission with readp() interface
|
||||||
|
- Channel-specific operations (read, write, ioctl, lock/unlock, shape)
|
||||||
|
- Configurable CRC and sequence number validation
|
||||||
|
- Configurable async event notifications
|
||||||
|
- Real-time stream processing with ring buffer
|
||||||
|
- Default configuration optimized for USB transport with CRC disabled
|
||||||
|
|
||||||
|
### 1.2 Channel Architecture
|
||||||
|
|
||||||
|
The protocol uses a channel-based model where every readable/writable resource is abstracted as a data channel (such as stdout, stdin, the frame buffer, etc.). Channels have flags that indicate if they're read-only, write-only, or read/write. The protocol provides these predefined channels, and it's possible to extend it with user-defined channels from Python. Each channel implements a standard interface with init, read, write, flush, ioctl, available, and locking operations as needed.
|
||||||
|
|
||||||
|
## 2. Packet Structure
|
||||||
|
|
||||||
|
### 2.1 Packet Format
|
||||||
|
|
||||||
|
All packets follow this structure:
|
||||||
|
|
||||||
|
| Offset | Field | Size | Type | Description |
|
||||||
|
|--------|----------|----------|-------------------------|-----------------------------------|
|
||||||
|
| 0 | SYNC | 2 bytes | Pattern | Synchronization pattern (0xAA55) |
|
||||||
|
| 2 | SEQ | 1 byte | Number | Sequence number (0-255), wraps |
|
||||||
|
| 3 | CHAN | 1 byte | ID | Channel ID for routing data ops |
|
||||||
|
| 4 | FLAGS | 1 byte | Flags | Packet flags (see section 2.2) |
|
||||||
|
| 5 | OPCODE | 1 byte | Code | Command/Response operation code |
|
||||||
|
| 6 | LENGTH | 2 bytes | Length | Length of data field only |
|
||||||
|
| 8 | CRC | 2 bytes | Checksum | CRC-16-CCITT over header fields |
|
||||||
|
| 10 | DATA | Variable | Payload | Optional payload data |
|
||||||
|
| 10+N | D_CRC | 2 bytes | Checksum | CRC-16-CCITT over data (if N > 0) |
|
||||||
|
|
||||||
|
Total header size: 10 bytes (including header CRC)
|
||||||
|
Data CRC: 2 bytes (only present if LENGTH > 0)
|
||||||
|
|
||||||
|
### 2.2 Flags Field
|
||||||
|
|
||||||
|
```
|
||||||
|
Bit 7 6 5 4 3 2 1 0
|
||||||
|
│ │ │ │ │ │ │ └─ ACK: Acknowledgment packet
|
||||||
|
│ │ │ │ │ │ └───── NAK: Negative acknowledgment packet
|
||||||
|
│ │ │ │ │ └───────── FRAGMENT: More fragments follow
|
||||||
|
│ │ │ │ └───────────── EVENT: Event packet
|
||||||
|
│ │ │ └───────────────── Reserved
|
||||||
|
│ │ └───────────────────── Reserved
|
||||||
|
│ └───────────────────────── Reserved
|
||||||
|
└───────────────────────────── Reserved
|
||||||
|
```
|
||||||
|
|
||||||
|
- **ACK (bit 0)**: Set for acknowledgment packets
|
||||||
|
- **NAK (bit 1)**: Set for negative acknowledgment packets
|
||||||
|
- **FRAGMENT (bit 2)**: Set when more fragments follow, clear for last fragment
|
||||||
|
- **EVENT (bit 3)**: Set for event packets
|
||||||
|
|
||||||
|
## 3. Protocol Operations
|
||||||
|
|
||||||
|
### 3.1 Sequence Numbers
|
||||||
|
|
||||||
|
- Each endpoint maintains separate sequence counters for TX and RX
|
||||||
|
- Sequence numbers increment for each new packet (not retransmissions)
|
||||||
|
- Used to detect duplicates and missing packets
|
||||||
|
- Wraps from 255 to 0 (8-bit counter)
|
||||||
|
|
||||||
|
### 3.2 Acknowledgment Mechanism
|
||||||
|
|
||||||
|
All response packets automatically include ACK flag unless NAK is explicitly set:
|
||||||
|
1. ACK packets indicate successful processing
|
||||||
|
2. NAK packets include status code indicating the error
|
||||||
|
3. Sequence numbers increment with each packet sent
|
||||||
|
|
||||||
|
### 3.3 Fragmentation
|
||||||
|
|
||||||
|
The protocol supports fragmentation of large data packets. For data larger than maximum payload size, the fragmentation flag will be set to indicate more fragments to follow, and cleared on the last fragment.
|
||||||
|
|
||||||
|
## 4. Command Set
|
||||||
|
|
||||||
|
### 4.1 Command Categories
|
||||||
|
|
||||||
|
Commands are organized by functional area:
|
||||||
|
|
||||||
|
| Range | Category | Description |
|
||||||
|
|-------|----------|-------------|
|
||||||
|
| 0x00-0x0F | Protocol Control | Sync, capabilities |
|
||||||
|
| 0x10-0x1F | System Control | Reset, boot, system info, events |
|
||||||
|
| 0x20-0x2F | Channel Operations | Channel list, poll, lock, size, read, write, ioctl |
|
||||||
|
| 0x30-0xFF | Reserved/Extensions | Future use |
|
||||||
|
|
||||||
|
### 4.2 Command Summary
|
||||||
|
|
||||||
|
#### Protocol Control (0x00-0x0F)
|
||||||
|
| Opcode | Command | Payload Size | Response Size | Description |
|
||||||
|
|--------|---------|-------------|---------------|-------------|
|
||||||
|
| 0x00 | PROTO_SYNC | 0 bytes | 2 bytes (status) | Synchronization request |
|
||||||
|
| 0x01 | PROTO_GET_CAPS | 0 bytes | 32 bytes | Get protocol capabilities |
|
||||||
|
| 0x02 | PROTO_SET_CAPS | 32 bytes | 32 bytes | Set protocol capabilities |
|
||||||
|
| 0x03 | PROTO_STATS | 0 bytes | 32 bytes | Get protocol statistics |
|
||||||
|
|
||||||
|
#### System Control (0x10-0x1F)
|
||||||
|
| Opcode | Command | Payload Size | Response Size | Description |
|
||||||
|
|--------|---------|-------------|---------------|-------------|
|
||||||
|
| 0x10 | SYS_RESET | 0 bytes | No response | System reset |
|
||||||
|
| 0x11 | SYS_BOOT | 0 bytes | No response | Jump to bootloader |
|
||||||
|
| 0x12 | SYS_INFO | 0 bytes | 64 bytes | Get system information |
|
||||||
|
| 0x13 | SYS_EVENT | 4 bytes | No response | System event notification |
|
||||||
|
|
||||||
|
#### Channel Operations (0x20-0x2F)
|
||||||
|
| Opcode | Command | Payload Size | Response Size | Description |
|
||||||
|
|--------|---------|-------------|---------------|-------------|
|
||||||
|
| 0x20 | CHANNEL_LIST | 0 bytes | Variable (16 bytes × channel count) | List registered channels |
|
||||||
|
| 0x21 | CHANNEL_POLL | 0 bytes | 4 bytes (flags) | Poll channels status |
|
||||||
|
| 0x22 | CHANNEL_LOCK | 0 bytes | 2 bytes (status) | Lock channel for exclusive access |
|
||||||
|
| 0x23 | CHANNEL_UNLOCK | 0 bytes | 2 bytes (status) | Unlock channel |
|
||||||
|
| 0x24 | CHANNEL_SHAPE | 0 bytes | Variable | Get data shape/dimensions |
|
||||||
|
| 0x25 | CHANNEL_SIZE | 0 bytes | 4 bytes | Get available data size |
|
||||||
|
| 0x26 | CHANNEL_READ | 8 bytes (offset + length) | Variable (requested data) | Read data from channel |
|
||||||
|
| 0x27 | CHANNEL_WRITE | 8+ bytes (offset + length + data) | 2 bytes (status) | Write data to channel |
|
||||||
|
| 0x28 | CHANNEL_IOCTL | 4+ bytes (request + data) | 2 bytes (status) | Channel-specific control operation |
|
||||||
|
| 0x29 | CHANNEL_EVENT | 4+ bytes (event + data) | No response | Channel-specific event notification |
|
||||||
|
|
||||||
|
### 4.3 Detailed Command Formats
|
||||||
|
|
||||||
|
#### Protocol Control Commands
|
||||||
|
|
||||||
|
**PROTO_SYNC (0x00)**
|
||||||
|
|
||||||
|
Request Format: (No payload)
|
||||||
|
|
||||||
|
Response Format:
|
||||||
|
| Offset | Field | Size | Type | Description |
|
||||||
|
|--------|----------|----------|-------------------------|-----------------------------------|
|
||||||
|
| 0-1 | status | 2 bytes | Code | Status code (0x00 = success) |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**PROTO_GET_CAPS (0x01)**
|
||||||
|
|
||||||
|
Request Format: (No payload)
|
||||||
|
|
||||||
|
Response Format: (16 bytes)
|
||||||
|
| Offset | Field | Size | Type | Description |
|
||||||
|
|--------|---------------|----------|----------------------|-----------------------------------|
|
||||||
|
| 0-3 | flags | 4 bytes | Bitfield | Capability flags (bits 0-3) |
|
||||||
|
| | | | | bit 0: CRC enabled |
|
||||||
|
| | | | | bit 1: Sequence enabled |
|
||||||
|
| | | | | bit 2: ACK enabled |
|
||||||
|
| | | | | bit 3: Events enabled |
|
||||||
|
| 4-5 | max_payload | 2 bytes | Size | Maximum payload size |
|
||||||
|
| 6-15 | reserved | 10 bytes | Padding | Reserved for future use |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**PROTO_SET_CAPS (0x02)**
|
||||||
|
|
||||||
|
Request Format: (16 bytes)
|
||||||
|
| Offset | Field | Size | Type | Description |
|
||||||
|
|--------|---------------|----------|----------------------|-----------------------------------|
|
||||||
|
| 0-3 | flags | 4 bytes | Bitfield | Capability flags to set |
|
||||||
|
| 4-5 | max_payload | 2 bytes | Size | Maximum payload size |
|
||||||
|
| 6-15 | reserved | 10 bytes | Padding | Reserved for future use |
|
||||||
|
|
||||||
|
Response Format: (Same as request - echoes back the set values)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**PROTO_STATS (0x03)**
|
||||||
|
|
||||||
|
Request Format: (No payload)
|
||||||
|
|
||||||
|
Response Format: (32 bytes)
|
||||||
|
| Offset | Field | Size | Type | Description |
|
||||||
|
|--------|----------------------|----------|----------------------|-----------------------------------|
|
||||||
|
| 0-3 | sent_packets | 4 bytes | Count | Number of packets sent |
|
||||||
|
| 4-7 | recv_packets | 4 bytes | Count | Number of packets received |
|
||||||
|
| 8-11 | checksum_errors | 4 bytes | Count | Number of CRC errors detected |
|
||||||
|
| 12-15 | sequence_errors | 4 bytes | Count | Number of sequence errors |
|
||||||
|
| 16-19 | retransmit | 4 bytes | Count | Number of packet retransmissions |
|
||||||
|
| 20-23 | transport_errors | 4 bytes | Count | Number of transport layer errors |
|
||||||
|
| 24-27 | sent_events | 4 bytes | Count | Number of events sent |
|
||||||
|
| 28-31 | max_ack_queue_depth | 4 bytes | Count | Maximum ACK queue depth reached |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### System Control Commands
|
||||||
|
|
||||||
|
**SYS_RESET (0x10)**
|
||||||
|
|
||||||
|
Request Format: (No payload)
|
||||||
|
Response: (No response - system resets immediately)
|
||||||
|
|
||||||
|
**SYS_BOOT (0x11)**
|
||||||
|
|
||||||
|
Request Format: (No payload)
|
||||||
|
Response: (No response - system enters bootloader)
|
||||||
|
|
||||||
|
**SYS_EVENT (0x13)**
|
||||||
|
|
||||||
|
Request Format:
|
||||||
|
| Offset | Field | Size | Type | Description |
|
||||||
|
|--------|----------|----------|-------------------------|-----------------------------------|
|
||||||
|
| 0-3 | event | 4 bytes | Event code | Event type and data |
|
||||||
|
|
||||||
|
Response: (No response - events are notifications)
|
||||||
|
|
||||||
|
Event types:
|
||||||
|
- 0x00: CHANNEL_REGISTERED - Channel dynamically registered
|
||||||
|
- 0x01: CHANNEL_UNREGISTERED - Channel unregistered
|
||||||
|
- 0x02: SOFT_REBOOT - System is going for a soft-reboot
|
||||||
|
|
||||||
|
Note: Events are only sent when the events_enabled capability is set to true.
|
||||||
|
|
||||||
|
**SYS_INFO (0x12)**
|
||||||
|
|
||||||
|
Request Format: (No payload)
|
||||||
|
|
||||||
|
Response Format: (80 bytes)
|
||||||
|
| Offset | Field | Size | Type | Description |
|
||||||
|
|--------|---------------------|----------|--------------|-----------------------------------|
|
||||||
|
| 0-3 | cpu_id | 4 bytes | Register | ARM CPUID register value |
|
||||||
|
| 4-15 | dev_id | 12 bytes | ID Array | Unique device ID (3×uint32) |
|
||||||
|
| 16-27 | chip_id | 12 bytes | ID Array | Camera sensor chip ID (3×uint32, supports multiple sensors) |
|
||||||
|
| 28-35 | id_reserved | 8 bytes | Padding | Reserved for future expansion |
|
||||||
|
| 36-43 | hw_caps | 8 bytes | Bitfield | Hardware capability flags (see omv_protocol_hw_caps.h): |
|
||||||
|
| | | | | bit 0: GPU, |
|
||||||
|
| | | | | bit 1: NPU, |
|
||||||
|
| | | | | bit 2: ISP, |
|
||||||
|
| | | | | bit 3: Video encoder, |
|
||||||
|
| | | | | bit 4: JPEG encoder, |
|
||||||
|
| | | | | bit 5: DRAM, |
|
||||||
|
| | | | | bit 6: Hardware CRC, |
|
||||||
|
| | | | | bit 7: PMU, |
|
||||||
|
| | | | | bits 8-15: PMU event count, |
|
||||||
|
| | | | | bit 16: WiFi, |
|
||||||
|
| | | | | bit 17: Bluetooth, |
|
||||||
|
| | | | | bit 18: SD card, |
|
||||||
|
| | | | | bit 19: Ethernet, |
|
||||||
|
| | | | | bit 20: USB High-Speed, |
|
||||||
|
| | | | | bit 21: Multi-core |
|
||||||
|
| 44-47 | flash_size_kb | 4 bytes | Size | Flash memory size (KB) - currently 0 |
|
||||||
|
| 48-51 | ram_size_kb | 4 bytes | Size | RAM size (KB) - currently 0 |
|
||||||
|
| 52-55 | framebuffer_size_kb | 4 bytes | Size | Main framebuffer size (KB) |
|
||||||
|
| 56-59 | stream_buffer_size_kb| 4 bytes | Size | Stream framebuffer size (KB) |
|
||||||
|
| 60-67 | memory_reserved | 8 bytes | Padding | Reserved for future expansion |
|
||||||
|
| 68-70 | firmware_version | 3 bytes | Version | Firmware version (4.7.0) |
|
||||||
|
| 71-73 | protocol_version | 3 bytes | Version | Protocol version (1.0.0) |
|
||||||
|
| 74-76 | bootloader_version | 3 bytes | Version | Bootloader version |
|
||||||
|
| 77-79 | reserved | 3 bytes | Padding | Padding to 80 bytes |
|
||||||
|
|
||||||
|
#### Channel Operations Commands
|
||||||
|
|
||||||
|
**CHANNEL_LIST (0x20)**
|
||||||
|
|
||||||
|
Request Format: (No payload)
|
||||||
|
|
||||||
|
Response Format: (Variable length - 16 bytes per channel)
|
||||||
|
| Offset | Field | Size | Type | Description |
|
||||||
|
|--------|----------|----------|-------------------------|-----------------------------------|
|
||||||
|
| N×0 | id | 1 byte | ID | Channel ID |
|
||||||
|
| N×1 | flags | 1 byte | Bitfield | Channel capability flags |
|
||||||
|
| N×2-15 | name | 14 bytes | String | Channel name (null-terminated) |
|
||||||
|
|
||||||
|
(Repeat for each registered channel)
|
||||||
|
|
||||||
|
**CHANNEL_POLL (0x21)**
|
||||||
|
|
||||||
|
Request Format: (No payload)
|
||||||
|
|
||||||
|
Response Format:
|
||||||
|
| Offset | Field | Size | Type | Description |
|
||||||
|
|--------|----------|----------|-------------------------|-----------------------------------|
|
||||||
|
| 0-3 | flags | 4 bytes | Bitfield | Channel status flags |
|
||||||
|
|
||||||
|
**CHANNEL_LOCK (0x22) / CHANNEL_UNLOCK (0x23)**
|
||||||
|
|
||||||
|
Request Format: (No payload)
|
||||||
|
|
||||||
|
Response Format:
|
||||||
|
| Offset | Field | Size | Type | Description |
|
||||||
|
|--------|----------|----------|-------------------------|-----------------------------------|
|
||||||
|
| 0-1 | status | 2 bytes | Code | Status code |
|
||||||
|
|
||||||
|
**CHANNEL_SIZE (0x25)**
|
||||||
|
|
||||||
|
Request Format: (No payload)
|
||||||
|
|
||||||
|
Response Format:
|
||||||
|
| Offset | Field | Size | Type | Description |
|
||||||
|
|--------|----------|----------|-------------------------|-----------------------------------|
|
||||||
|
| 0-3 | size | 4 bytes | Count | Available bytes in channel |
|
||||||
|
|
||||||
|
**CHANNEL_READ (0x26)**
|
||||||
|
|
||||||
|
Request Format:
|
||||||
|
| Offset | Field | Size | Type | Description |
|
||||||
|
|--------|----------|----------|-------------------------|-----------------------------------|
|
||||||
|
| 0-3 | offset | 4 bytes | Position | Starting position to read |
|
||||||
|
| 4-7 | length | 4 bytes | Count | Number of bytes to read |
|
||||||
|
|
||||||
|
Response Format:
|
||||||
|
| Offset | Field | Size | Type | Description |
|
||||||
|
|--------|----------|----------|-------------------------|-----------------------------------|
|
||||||
|
| 0-N | data | N bytes | Payload | Requested channel data |
|
||||||
|
|
||||||
|
**CHANNEL_WRITE (0x27)**
|
||||||
|
|
||||||
|
Request Format:
|
||||||
|
| Offset | Field | Size | Type | Description |
|
||||||
|
|--------|----------|----------|-------------------------|-----------------------------------|
|
||||||
|
| 0-3 | offset | 4 bytes | Position | Starting position to write |
|
||||||
|
| 4-7 | length | 4 bytes | Count | Number of bytes to write |
|
||||||
|
| 8-N | data | N bytes | Payload | Data to write to channel |
|
||||||
|
|
||||||
|
Response Format:
|
||||||
|
| Offset | Field | Size | Type | Description |
|
||||||
|
|--------|----------|----------|-------------------------|-----------------------------------|
|
||||||
|
| 0-1 | status | 2 bytes | Code | Status code |
|
||||||
|
|
||||||
|
**CHANNEL_IOCTL (0x28)**
|
||||||
|
|
||||||
|
Request Format:
|
||||||
|
| Offset | Field | Size | Type | Description |
|
||||||
|
|--------|----------|----------|-------------------------|-----------------------------------|
|
||||||
|
| 0-3 | request | 4 bytes | Code | IOCTL request code |
|
||||||
|
| 4-N | data | N bytes | Payload | Request-specific data |
|
||||||
|
|
||||||
|
Response Format:
|
||||||
|
| Offset | Field | Size | Type | Description |
|
||||||
|
|--------|----------|----------|-------------------------|-----------------------------------|
|
||||||
|
| 0-1 | status | 2 bytes | Code | Status code |
|
||||||
|
|
||||||
|
**CHANNEL_SHAPE (0x24)**
|
||||||
|
|
||||||
|
Request Format: (No payload)
|
||||||
|
|
||||||
|
Response Format: (Variable length - array of size_t values)
|
||||||
|
| Offset | Field | Size | Type | Description |
|
||||||
|
|--------|------------|----------|-----------------------|-----------------------------------|
|
||||||
|
| 0-3 | dimension0 | 4 bytes | size_t | First dimension (e.g., count) |
|
||||||
|
| 4-7 | dimension1 | 4 bytes | size_t | Second dimension (e.g., size) |
|
||||||
|
| ... | ... | 4 bytes | size_t | Additional dimensions (if any) |
|
||||||
|
|
||||||
|
The response contains 1-4 size_t values depending on the channel type:
|
||||||
|
- Profile channel: 2 values (record_count, record_size)
|
||||||
|
- Stream channel: 1 value (total_size) for compressed, 3 values (width, height, bpp) for uncompressed
|
||||||
|
- Other channels: Channel-specific format
|
||||||
|
|
||||||
|
|
||||||
|
### 4.4 Async Events
|
||||||
|
|
||||||
|
The protocol supports asynchronous event notifications that can be sent from device to host at any time when the `events_enabled` capability is set to true. There are two types of events:
|
||||||
|
|
||||||
|
1. **System Events**: Use the SYS_EVENT opcode (0x13) on channel 0 and are marked with the EVENT flag (bit 3)
|
||||||
|
2. **Channel Events**: Use the CHANNEL_EVENT opcode (0x29) on the specific channel that generates the event
|
||||||
|
|
||||||
|
Events do not require acknowledgment and do not affect sequence numbering. The device will only send events if:
|
||||||
|
1. The protocol transport is active
|
||||||
|
2. The ACK queue is not full
|
||||||
|
3. The events_enabled capability is set to true
|
||||||
|
|
||||||
|
**System Events (SYS_EVENT - 0x13):**
|
||||||
|
- **CHANNEL_REGISTERED (0x00)**: Sent when a dynamic channel is registered
|
||||||
|
- **CHANNEL_UNREGISTERED (0x01)**: Sent when a dynamic channel is removed
|
||||||
|
- **SOFT_REBOOT (0x02)**: Sent when system is going for a soft-reboot
|
||||||
|
|
||||||
|
**Channel Events (CHANNEL_EVENT - 0x29):**
|
||||||
|
Channel events are sent on the specific channel that generates the event and can include channel-specific event data in the payload.
|
||||||
|
|
||||||
|
### 4.5 Status Codes
|
||||||
|
|
||||||
|
Protocol status responses use these codes:
|
||||||
|
|
||||||
|
| Code | Name | Value | Type | Description |
|
||||||
|
|------|----------|----------|---------------------------|-----------------------------------|
|
||||||
|
| 0x00 | SUCCESS | 0 | Status | Operation completed successfully |
|
||||||
|
| 0x01 | FAILED | 1 | Error | Command failed |
|
||||||
|
| 0x02 | INVALID | 2 | Error | Invalid command/argument |
|
||||||
|
| 0x03 | TIMEOUT | 3 | Error | Operation timeout |
|
||||||
|
| 0x04 | BUSY | 4 | Status | Device busy |
|
||||||
|
| 0x05 | CHECKSUM | 5 | Error | CRC error |
|
||||||
|
| 0x06 | SEQUENCE | 6 | Error | Sequence error |
|
||||||
|
| 0x07 | OVERFLOW | 7 | Error | Buffer overflow |
|
||||||
|
| 0x08 | FRAGMENT | 8 | Error | Fragmentation error |
|
||||||
|
| 0x09 | UNKNOWN | 9 | Error | Unknown error |
|
||||||
|
|
||||||
|
## 5. Channel System
|
||||||
|
|
||||||
|
The OpenMV Protocol uses a channel-based architecture where each channel represents a specialized data pipeline. Channels are identified by numeric IDs and provide a uniform interface for accessing different types of data and functionality.
|
||||||
|
|
||||||
|
### 5.1 Channel Types
|
||||||
|
|
||||||
|
**Channel 0 (transport)**: The physical transport layer (USB, UART, TCP/IP) that handles low-level data transmission. Not directly accessible via protocol commands.
|
||||||
|
|
||||||
|
**Channel 1 (stdin)**: Script input channel (write-only) for sending Python code to the device for execution.
|
||||||
|
|
||||||
|
**Channel 2 (stdout)**: Text output channel (read-only) for receiving console output and script results from the device.
|
||||||
|
|
||||||
|
**Channel 3 (stream)**: Read-only channel for high-bandwidth data like image frames. Supports exclusive locking to prevent concurrent access conflicts.
|
||||||
|
|
||||||
|
**Channel 4 (profile)**: Optional read-only channel providing performance metrics and diagnostic information. Profiling availability is determined by the presence of this channel rather than a capability flag. The channel is only registered when OMV_PROFILER_ENABLE is defined at compile time. The number of PMU event counters is embedded in the hardware capabilities bitfield (bits 7-12) in the system information.
|
||||||
|
|
||||||
|
### 5.2 Channel Capabilities
|
||||||
|
|
||||||
|
Each channel declares its supported operations through capability flags:
|
||||||
|
- **READ**: Channel supports read operations
|
||||||
|
- **WRITE**: Channel supports write operations
|
||||||
|
- **LOCK**: Channel supports exclusive locking
|
||||||
|
- **DYNAMIC**: Channel was dynamically created
|
||||||
|
- **PHYSICAL**: Transport-only channel (not accessible via protocol)
|
||||||
|
|
||||||
|
### 5.3 Channel Operations
|
||||||
|
|
||||||
|
**Discovery**: The CHANNEL_LIST command returns available channels with their IDs, capabilities, and names.
|
||||||
|
|
||||||
|
**Data Access**: Read operations specify offset and length parameters. The offset meaning depends on the channel - it might be a byte position, frame number, or other logical addressing scheme.
|
||||||
|
|
||||||
|
**Zero-Copy Reads**: The readp operation returns direct pointers to channel data, eliminating copy overhead for large transfers.
|
||||||
|
|
||||||
|
**Control**: The ioctl operation handles channel-specific commands like configuration changes or status queries.
|
||||||
|
|
||||||
|
**Flow Control**: Channels return appropriate status codes when data isn't ready, and use locking mechanisms to coordinate access between host and device.
|
||||||
|
|
||||||
|
## 6. Error Handling
|
||||||
|
|
||||||
|
### 6.1 CRC Verification
|
||||||
|
- CRC-16-CCITT polynomial (0x1021) with initial value 0xFFFF
|
||||||
|
- Header CRC: calculated over header fields (excluding CRC field itself)
|
||||||
|
- Data CRC: calculated over payload data (only if payload length > 0)
|
||||||
|
- Hardware acceleration available on STM32 and Alif platforms
|
||||||
|
|
||||||
|
### 6.2 Stream Recovery
|
||||||
|
- On sync loss, scan for SYNC pattern (0xAA55)
|
||||||
|
- Validate packet header after SYNC
|
||||||
|
- Verify CRC before processing
|
||||||
|
|
||||||
|
### 6.3 Sequence Numbers
|
||||||
|
- Increment after successful packet transmission
|
||||||
|
- Reset to 0 on PROTO_SYNC command
|
||||||
|
- Used for duplicate detection
|
||||||
|
|
||||||
|
## 7. Protocol State Machine
|
||||||
|
|
||||||
|
The state machine is driven by the transport layer when data is available:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────┐
|
||||||
|
│ IDLE │◄──────────────────────┐
|
||||||
|
└──┬───┘ │
|
||||||
|
│ Data available │
|
||||||
|
▼ │
|
||||||
|
┌──────┐ │
|
||||||
|
│ SYNC │───── Timeout ─────────┤
|
||||||
|
└──┬───┘ │
|
||||||
|
│ SYNC found │
|
||||||
|
▼ │
|
||||||
|
┌────────┐ │
|
||||||
|
│ HEADER │──── Invalid ────────┤
|
||||||
|
└──┬─────┘ │
|
||||||
|
│ Valid header │
|
||||||
|
▼ │
|
||||||
|
┌──────┐ │
|
||||||
|
│ DATA │───── Timeout ─────────┤
|
||||||
|
└──┬───┘ │
|
||||||
|
│ Data complete │
|
||||||
|
▼ │
|
||||||
|
┌─────┐ │
|
||||||
|
│ CRC │────── Invalid ─────────┤
|
||||||
|
└──┬──┘ │
|
||||||
|
│ Valid CRC │
|
||||||
|
▼ │
|
||||||
|
┌─────────┐ │
|
||||||
|
│ PROCESS │────────────────────┘
|
||||||
|
└─────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. Implementation Notes
|
||||||
|
|
||||||
|
### 8.1 Zero-Copy Optimization
|
||||||
|
- readp() interface allows direct pointer access to data without copying
|
||||||
|
- Header, payload, and data CRC sent as separate transport writes
|
||||||
|
- Incremental CRC calculation supports non-contiguous data
|
||||||
|
|
||||||
|
### 8.2 Buffer Requirements
|
||||||
|
- Maximum buffer size: 4096 bytes (configurable via OMV_PROTOCOL_MAX_BUFFER_SIZE)
|
||||||
|
- Maximum payload: 4086 bytes (4096 - 10 header - 2 data CRC)
|
||||||
|
- Minimum payload: 52 bytes (64 - 10 header - 2 data CRC)
|
||||||
|
- Ring buffer implementation for received data with double buffering (8192 bytes total)
|
||||||
|
|
||||||
|
### 8.3 Channel Registration
|
||||||
|
- Maximum 32 channels supported (indices 0-31)
|
||||||
|
- Channels registered at protocol initialization
|
||||||
|
- Channel 0 is transport layer (not accessible via protocol)
|
||||||
|
- Protocol responses use channel 0 in packet header but are routed through transport
|
||||||
|
|
||||||
|
## 9. Example Communication Flow
|
||||||
|
|
||||||
|
### 9.1 Channel Read Operation
|
||||||
|
```
|
||||||
|
Host Device
|
||||||
|
│ │
|
||||||
|
├─── CHANNEL_READ(CHAN=2) ────►│ // Read frame data
|
||||||
|
│ offset=0, length=1024 │
|
||||||
|
│ │
|
||||||
|
│◄─ Response(FRAGMENTED) ──────┤ // More fragments follow
|
||||||
|
│ │
|
||||||
|
│◄─ Response(no flags) ────────┤ // Final fragment
|
||||||
|
│ │
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.2 Channel Discovery
|
||||||
|
```
|
||||||
|
Host Device
|
||||||
|
│ │
|
||||||
|
├─── CHANNEL_LIST ────────────►│
|
||||||
|
│ │
|
||||||
|
│◄─ Channel entries ───────────┤ // Array of channel info
|
||||||
|
│ │
|
||||||
|
├─── CHANNEL_SIZE(CHAN=2) ────►│
|
||||||
|
│ │
|
||||||
|
│◄─ Size response ─────────────┤ // Available bytes
|
||||||
|
│ │
|
||||||
|
```
|
||||||
|
|
||||||
|
## 10. Protocol Configuration
|
||||||
|
|
||||||
|
### 10.1 Protocol Capabilities
|
||||||
|
|
||||||
|
The protocol supports negotiation of these capabilities between host and device:
|
||||||
|
|
||||||
|
- **crc_enabled**: Enable/disable CRC error checking
|
||||||
|
- **seq_enabled**: Enable/disable sequence number validation
|
||||||
|
- **ack_enabled**: Enable/disable ACK packet requirement
|
||||||
|
- **events_enabled**: Enable/disable async event notifications
|
||||||
|
- **max_payload**: Maximum payload size supported
|
||||||
|
|
||||||
|
### 10.2 Default Capabilities
|
||||||
|
|
||||||
|
The protocol uses optimized defaults for USB transport:
|
||||||
|
|
||||||
|
- **CRC enabled**: false (optimized for USB reliability)
|
||||||
|
- **Sequence validation**: false (optimized for USB in-order delivery)
|
||||||
|
- **ACK enabled**: false
|
||||||
|
- **Events enabled**: true
|
||||||
|
- **Max payload**: 4086 bytes (4096 - 10 header - 2 data CRC)
|
||||||
|
- **Soft reboot**: true
|
||||||
|
- **RTX retries**: 3
|
||||||
|
- **RTX timeout**: 3000ms
|
||||||
|
- **Lock interval**: 10ms
|
||||||
|
|
||||||
|
Fragmentation is always supported and handled automatically by the protocol when data exceeds the maximum payload size.
|
||||||
|
|
||||||
|
### 10.3 Capability Negotiation
|
||||||
|
|
||||||
|
The `PROTO_GET_CAPS` and `PROTO_SET_CAPS` commands allow dynamic configuration. See the detailed command formats above for the exact 32-byte payload structure.
|
||||||
|
|
||||||
|
## 11. System Information
|
||||||
|
|
||||||
|
The `SYS_INFO` command returns comprehensive system information in a 64-byte response. See the detailed command format above for the complete field layout including hardware identification, capabilities, memory information, and version data.
|
||||||
|
|
||||||
|
## 12. Version History
|
||||||
|
|
||||||
|
| Version | Date | Status | Type | Description |
|
||||||
|
|---------|----------|----------|-------------------------|----------------------------------------|
|
||||||
|
| 1.0.0 | 2025 | Current | Specification | OpenMV Protocol specification |
|
854
protocol/omv_protocol.c
Normal file
854
protocol/omv_protocol.c
Normal file
@ -0,0 +1,854 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025 OpenMV, LLC.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* OpenMV Protocol - Transport-agnostic communication protocol
|
||||||
|
* This protocol provides reliable communication between host and
|
||||||
|
* device with support for multiple transport layers (USB, UART, TCP/IP).
|
||||||
|
*/
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#include "py/mphal.h"
|
||||||
|
#include "omv_common.h"
|
||||||
|
#include "omv_csi.h"
|
||||||
|
#include "omv_crc.h"
|
||||||
|
#include "omv_protocol.h"
|
||||||
|
#include "omv_protocol_hw_caps.h"
|
||||||
|
#include "boot/include/version.h"
|
||||||
|
|
||||||
|
#ifndef OMV_PROTOCOL_HW_CAPS
|
||||||
|
#define OMV_PROTOCOL_HW_CAPS (0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Static global protocol context
|
||||||
|
static omv_protocol_context_t ctx;
|
||||||
|
static const omv_protocol_config_t default_config = {
|
||||||
|
.crc_enabled = true,
|
||||||
|
.seq_enabled = true,
|
||||||
|
.ack_enabled = true,
|
||||||
|
.event_enabled = true,
|
||||||
|
.max_payload = OMV_PROTOCOL_MAX_PAYLOAD_SIZE,
|
||||||
|
.caps_negotiated = false,
|
||||||
|
.soft_reboot = true,
|
||||||
|
.rtx_retries = OMV_PROTOCOL_DEF_RTX_RETRIES,
|
||||||
|
.rtx_timeout_ms = OMV_PROTOCOL_DEF_RTX_TIMEOUT_MS,
|
||||||
|
.lock_intval_ms = OMV_PROTOCOL_MIN_LOCK_INTERVAL_MS,
|
||||||
|
};
|
||||||
|
|
||||||
|
int omv_protocol_init(const omv_protocol_config_t *config) {
|
||||||
|
if (!config) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate config
|
||||||
|
if (config->max_payload < OMV_PROTOCOL_MIN_PAYLOAD_SIZE ||
|
||||||
|
config->max_payload > OMV_PROTOCOL_MAX_PAYLOAD_SIZE ||
|
||||||
|
config->lock_intval_ms < OMV_PROTOCOL_MIN_LOCK_INTERVAL_MS) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize state
|
||||||
|
ctx.sequence = 0;
|
||||||
|
ctx.last_lock_ms = 0;
|
||||||
|
ctx.scan_offset = 0;
|
||||||
|
ctx.state = OMV_PROTOCOL_STATE_SYNC;
|
||||||
|
ctx.channels_count = 0;
|
||||||
|
memset(ctx.channels, 0, sizeof(ctx.channels));
|
||||||
|
|
||||||
|
// Use the config provided by this transport
|
||||||
|
// For USB, see defaults below. Uart could enable CRC, ACKs etc..
|
||||||
|
ctx.config = *config;
|
||||||
|
|
||||||
|
|
||||||
|
// Initialize buffer
|
||||||
|
omv_buffer_init(&ctx.buffer, ctx.rawbuf, sizeof(ctx.rawbuf));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int omv_protocol_init_default() {
|
||||||
|
if (omv_protocol_init(&default_config) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if OMV_PROTOCOL_DEFAULT_CHANNELS
|
||||||
|
// Register the physical transport as channel 0
|
||||||
|
omv_protocol_register_channel(&omv_usb_channel);
|
||||||
|
|
||||||
|
// Register the default logical data channels next
|
||||||
|
omv_protocol_register_channel(&omv_stdin_channel);
|
||||||
|
omv_protocol_register_channel(&omv_stdout_channel);
|
||||||
|
omv_protocol_register_channel(&omv_stream_channel);
|
||||||
|
|
||||||
|
// Register the profiler channel (if enabled)
|
||||||
|
#if OMV_PROFILER_ENABLE
|
||||||
|
omv_protocol_register_channel(&omv_profile_channel);
|
||||||
|
#endif // OMV_PROFILER_ENABLE
|
||||||
|
|
||||||
|
#endif // OMV_PROTOCOL_DEFAULT_CHANNELS
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void omv_protocol_deinit(void) {
|
||||||
|
// Deinitialize all channels (including transport at index 0)
|
||||||
|
for (int i = 0; i < OMV_PROTOCOL_MAX_CHANNELS; i++) {
|
||||||
|
if (ctx.channels[i] && ctx.channels[i]->deinit) {
|
||||||
|
ctx.channels[i]->deinit(ctx.channels[i]);
|
||||||
|
ctx.channels[i] = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void omv_protocol_reset(void) {
|
||||||
|
// Reset state
|
||||||
|
ctx.sequence = 0;
|
||||||
|
ctx.scan_offset = 0;
|
||||||
|
ctx.state = OMV_PROTOCOL_STATE_SYNC;
|
||||||
|
omv_buffer_clear(&ctx.buffer);
|
||||||
|
|
||||||
|
// Restore default config
|
||||||
|
ctx.config = default_config;
|
||||||
|
|
||||||
|
// Unlock channels
|
||||||
|
for (int i = 0; i < ctx.channels_count; i++) {
|
||||||
|
const omv_protocol_channel_t *channel = ctx.channels[i];
|
||||||
|
if (channel && channel->unlock) {
|
||||||
|
channel->unlock(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool omv_protocol_is_active(void) {
|
||||||
|
const omv_protocol_channel_t *transport = omv_protocol_find_transport();
|
||||||
|
return transport && transport->is_active(transport);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool omv_protocol_exec_script(void) {
|
||||||
|
const omv_protocol_channel_t *channel = omv_protocol_find_channel(OMV_PROTOCOL_CHANNEL_ID_STDIN);
|
||||||
|
if (!channel || !channel->exec) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool result = channel->exec(channel);
|
||||||
|
if (result) {
|
||||||
|
omv_protocol_send_event(0, OMV_PROTOCOL_EVENT_SOFT_REBOOT, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
// A script was executed - return true if the transport allows soft-reboot
|
||||||
|
return ctx.config.soft_reboot;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int omv_protocol_register_channel(const omv_protocol_channel_t *channel) {
|
||||||
|
int channel_id = -1;
|
||||||
|
|
||||||
|
if (OMV_PROTOCOL_CHANNEL_IS_TRANSPORT(channel)) {
|
||||||
|
channel_id = 0;
|
||||||
|
} else if (!OMV_PROTOCOL_CHANNEL_FLAG_GET(channel, DYNAMIC)) {
|
||||||
|
// Use statically defined channel ID
|
||||||
|
channel_id = channel->id;
|
||||||
|
} else {
|
||||||
|
// Find the first free channel
|
||||||
|
for (size_t i = 1; i < OMV_PROTOCOL_MAX_CHANNELS; i++) {
|
||||||
|
if (ctx.channels[i] == NULL) {
|
||||||
|
channel_id = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the channel
|
||||||
|
if (channel->init && channel->init(channel)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register channel at next available index
|
||||||
|
ctx.channels[channel_id] = channel;
|
||||||
|
|
||||||
|
// Send channel registered event to host
|
||||||
|
if (OMV_PROTOCOL_CHANNEL_FLAG_GET(channel, DYNAMIC)) {
|
||||||
|
((omv_protocol_channel_t *) channel)->id = channel_id;
|
||||||
|
omv_protocol_send_event(0, OMV_PROTOCOL_EVENT_CHANNEL_REGISTERED, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no physical transport is ever registered, the count will be one
|
||||||
|
// less than the number of channels as they're offset by 1. However,
|
||||||
|
// channels_count is only used when the physical transport is active.
|
||||||
|
ctx.channels_count++;
|
||||||
|
|
||||||
|
return channel_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find and verify transport channel
|
||||||
|
const omv_protocol_channel_t *omv_protocol_find_transport(void) {
|
||||||
|
const omv_protocol_channel_t *transport = ctx.channels[OMV_PROTOCOL_CHANNEL_ID_TRANSPORT];
|
||||||
|
return (transport && OMV_PROTOCOL_CHANNEL_IS_TRANSPORT(transport)) ? transport : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const omv_protocol_channel_t *omv_protocol_find_channel(uint8_t channel_id) {
|
||||||
|
if (channel_id >= ctx.channels_count) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.channels[channel_id];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate and check if CRC matches the one stored in buffer
|
||||||
|
static inline bool omv_protocol_crc_check(void *buf, size_t size) {
|
||||||
|
return !size || !ctx.config.crc_enabled || omv_crc_check(buf, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if packet is a valid SYNC command
|
||||||
|
static inline bool omv_protocol_is_sync(const omv_protocol_packet_t *packet) {
|
||||||
|
return packet->sync == OMV_PROTOCOL_SYNC_WORD &&
|
||||||
|
packet->channel == 0 && packet->length == 0 &&
|
||||||
|
packet->opcode == OMV_PROTOCOL_OPCODE_PROTO_SYNC &&
|
||||||
|
omv_protocol_crc_check((void *) packet, OMV_PROTOCOL_HEADER_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if packet is a valid ACK for expected opcode/sequence
|
||||||
|
static inline bool omv_protocol_is_ack(const omv_protocol_packet_t *packet, uint8_t opcode, uint8_t sequence) {
|
||||||
|
return packet->sync == OMV_PROTOCOL_SYNC_WORD &&
|
||||||
|
(packet->flags & OMV_PROTOCOL_FLAG_ACK) &&
|
||||||
|
packet->opcode == opcode && packet->sequence == sequence &&
|
||||||
|
omv_protocol_crc_check((void *) packet, OMV_PROTOCOL_HEADER_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Protocol and system commands and events must arrive on channel 0
|
||||||
|
static bool omv_protocol_channel_check(const omv_protocol_packet_t *packet) {
|
||||||
|
return packet->channel == 0 || packet->opcode > OMV_PROTOCOL_OPCODE_SYS_LAST;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool omv_protocol_seq_check(omv_protocol_packet_t *packet) {
|
||||||
|
return !ctx.config.seq_enabled ||
|
||||||
|
ctx.sequence == packet->sequence ||
|
||||||
|
(packet->flags & OMV_PROTOCOL_FLAG_ACK) ||
|
||||||
|
packet->opcode == OMV_PROTOCOL_OPCODE_SYS_EVENT ||
|
||||||
|
packet->opcode == OMV_PROTOCOL_OPCODE_CHANNEL_EVENT ||
|
||||||
|
packet->opcode == OMV_PROTOCOL_OPCODE_PROTO_SYNC;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool omv_protocol_ioctl_check(uint8_t channel_id, uint32_t cmd, size_t len) {
|
||||||
|
static const struct {
|
||||||
|
uint8_t ch; uint32_t cmd; size_t size;
|
||||||
|
} ioctl_table[] = {
|
||||||
|
OMV_PROTOCOL_CHANNEL_IOCTL_TABLE
|
||||||
|
};
|
||||||
|
for (int i = 0; i < sizeof(ioctl_table) / sizeof(ioctl_table[0]); i++) {
|
||||||
|
if (ioctl_table[i].ch == channel_id && ioctl_table[i].cmd == cmd) {
|
||||||
|
return len == ioctl_table[i].size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false; // Unknown ioctl on static channel
|
||||||
|
}
|
||||||
|
|
||||||
|
static void omv_protocol_send_status(const omv_protocol_packet_t *packet, omv_protocol_status_t status) {
|
||||||
|
if (status == OMV_PROTOCOL_STATUS_SEQUENCE) {
|
||||||
|
ctx.stats.sequence_errors++;
|
||||||
|
} else if (status == OMV_PROTOCOL_STATUS_CHECKSUM) {
|
||||||
|
ctx.stats.checksum_errors++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status == OMV_PROTOCOL_STATUS_SUCCESS) {
|
||||||
|
omv_protocol_send_packet(packet->opcode, packet->channel, 0, NULL, OMV_PROTOCOL_FLAG_ACK);
|
||||||
|
} else {
|
||||||
|
omv_protocol_response_t resp = {
|
||||||
|
.status = status,
|
||||||
|
};
|
||||||
|
omv_protocol_send_packet(packet->opcode, packet->channel, sizeof(resp), &resp, OMV_PROTOCOL_FLAG_NAK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int omv_protocol_send_event(uint8_t channel_id, uint16_t event, bool wait_ack) {
|
||||||
|
if (!ctx.config.event_enabled || !omv_protocol_is_active()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t flags = OMV_PROTOCOL_FLAG_EVENT | (wait_ack ? OMV_PROTOCOL_FLAG_ACK_REQ : 0);
|
||||||
|
uint8_t opcode = (channel_id == 0) ? OMV_PROTOCOL_OPCODE_SYS_EVENT: OMV_PROTOCOL_OPCODE_CHANNEL_EVENT;
|
||||||
|
|
||||||
|
ctx.stats.sent_events++;
|
||||||
|
if (event == OMV_PROTOCOL_EVENT_NOTIFY) {
|
||||||
|
return omv_protocol_send_packet(opcode, channel_id, 0, NULL, flags);
|
||||||
|
} else {
|
||||||
|
return omv_protocol_send_packet(opcode, channel_id, sizeof(event), &event, flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int omv_protocol_send_packet(uint8_t opcode, uint8_t channel_id, size_t size, const void *data, uint8_t flags) {
|
||||||
|
const omv_protocol_channel_t *transport = omv_protocol_find_transport();
|
||||||
|
if (!transport || !transport->is_active(transport)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ctx.config.ack_enabled) {
|
||||||
|
// ACK is disabled globall
|
||||||
|
flags &= ~OMV_PROTOCOL_FLAG_ACK_REQ;
|
||||||
|
} else if (!(flags & OMV_PROTOCOL_FLAG_NO_ACK)) {
|
||||||
|
flags |= OMV_PROTOCOL_FLAG_ACK_REQ;
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
int rtx_retries = ctx.config.rtx_retries;
|
||||||
|
uint32_t rtx_timeout = ctx.config.rtx_timeout_ms;
|
||||||
|
bool wait_for_ack = flags & OMV_PROTOCOL_FLAG_ACK_REQ;
|
||||||
|
|
||||||
|
uint8_t crc16_bytes[2];
|
||||||
|
size_t frag_len = (size <= ctx.config.max_payload) ? size : ctx.config.max_payload;
|
||||||
|
uint8_t frag_flags = (size <= ctx.config.max_payload) ? flags : (flags | OMV_PROTOCOL_FLAG_FRAGMENT);
|
||||||
|
|
||||||
|
// Build packet header
|
||||||
|
omv_protocol_packet_t packet = {
|
||||||
|
.sync = OMV_PROTOCOL_SYNC_WORD,
|
||||||
|
.sequence = ctx.sequence,
|
||||||
|
.channel = channel_id,
|
||||||
|
.flags = frag_flags,
|
||||||
|
.opcode = opcode,
|
||||||
|
.length = frag_len,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Calculate header CRC (excluding the CRC field itself)
|
||||||
|
if (ctx.config.crc_enabled) {
|
||||||
|
packet.crc = omv_crc_start(&packet, OMV_PROTOCOL_HEADER_SIZE - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate payload CRC (excluding the CRC field itself)
|
||||||
|
if (ctx.config.crc_enabled && size && data) {
|
||||||
|
omv_crc_t crc = omv_crc_start(data, frag_len);
|
||||||
|
crc16_bytes[0] = crc & 0xFF;
|
||||||
|
crc16_bytes[1] = (crc >> 8) & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up ACK waiting context
|
||||||
|
if (flags & OMV_PROTOCOL_FLAG_ACK_REQ) {
|
||||||
|
ctx.scan_offset = 0;
|
||||||
|
ctx.wait_ack_opcode = packet.opcode;
|
||||||
|
ctx.wait_ack_sequence = packet.sequence;
|
||||||
|
ctx.state = OMV_PROTOCOL_STATE_WAIT_ACK;
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
// Send packet header, payload and CRC
|
||||||
|
int sent = transport->write(transport, 0, OMV_PROTOCOL_HEADER_SIZE, &packet);
|
||||||
|
if (size && data) {
|
||||||
|
sent += transport->write(transport, 0, frag_len, data);
|
||||||
|
sent += transport->write(transport, 0, 2, crc16_bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transport->flush) {
|
||||||
|
transport->flush(transport);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sent != OMV_PROTOCOL_HEADER_SIZE + frag_len + (frag_len > 0 ? 2 : 0)) {
|
||||||
|
ctx.stats.transport_errors++;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t start = mp_hal_ticks_ms(); wait_for_ack; mp_event_handle_nowait()) {
|
||||||
|
if (omv_protocol_task() == -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if state changed back to SYNC (ACK received)
|
||||||
|
wait_for_ack = (ctx.state == OMV_PROTOCOL_STATE_WAIT_ACK);
|
||||||
|
|
||||||
|
if (wait_for_ack && check_timeout_ms(start, rtx_timeout)) {
|
||||||
|
rtx_timeout *= 2;
|
||||||
|
ctx.stats.retransmit++;
|
||||||
|
|
||||||
|
// Set RTX and recalculate the CRC.
|
||||||
|
if (ctx.config.crc_enabled && !(packet.flags & OMV_PROTOCOL_FLAG_RTX)) {
|
||||||
|
packet.flags |= OMV_PROTOCOL_FLAG_RTX;
|
||||||
|
packet.crc = omv_crc_start(&packet, offsetof(omv_protocol_packet_t, crc));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (wait_for_ack && rtx_retries--);
|
||||||
|
|
||||||
|
// Never received the ACK
|
||||||
|
if (wait_for_ack) {
|
||||||
|
omv_protocol_reset();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size && data) {
|
||||||
|
size -= frag_len;
|
||||||
|
data = (uint8_t *) data + frag_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(flags & OMV_PROTOCOL_FLAG_EVENT)) {
|
||||||
|
ctx.sequence++;
|
||||||
|
ctx.stats.sent_packets++;
|
||||||
|
}
|
||||||
|
} while (size > 0);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int omv_protocol_task(void) {
|
||||||
|
size_t available = 0;
|
||||||
|
const omv_protocol_channel_t *transport = omv_protocol_find_transport();
|
||||||
|
|
||||||
|
if (!transport || !transport->is_active(transport)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Siphon off all available data from CDC buffer.
|
||||||
|
while ((available = transport->size(transport))) {
|
||||||
|
// Calculate read size: MIN(available, free_size)
|
||||||
|
size_t free_size = omv_buffer_free(&ctx.buffer);
|
||||||
|
size_t read_size = OMV_MIN(available, free_size);
|
||||||
|
uint8_t *write_ptr = omv_buffer_claim(&ctx.buffer, read_size);
|
||||||
|
|
||||||
|
if (!write_ptr) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write data directly into buffer
|
||||||
|
int bytes_read = transport->read(transport, 0, read_size, write_ptr);
|
||||||
|
if (bytes_read <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit the received data
|
||||||
|
omv_buffer_commit(&ctx.buffer, bytes_read);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (omv_buffer_avail(&ctx.buffer) >= OMV_PROTOCOL_SYNC_SIZE) {
|
||||||
|
uint8_t *buffer = omv_buffer_data(&ctx.buffer);
|
||||||
|
int32_t buffer_size = omv_buffer_avail(&ctx.buffer);
|
||||||
|
omv_protocol_packet_t *packet = omv_buffer_data(&ctx.buffer);
|
||||||
|
|
||||||
|
switch (ctx.state) {
|
||||||
|
case OMV_PROTOCOL_STATE_SYNC:
|
||||||
|
// Look for sync pattern in the buffer
|
||||||
|
while (omv_buffer_avail(&ctx.buffer) >= OMV_PROTOCOL_SYNC_SIZE) {
|
||||||
|
if (omv_buffer_peek16(&ctx.buffer) == OMV_PROTOCOL_SYNC_WORD) {
|
||||||
|
ctx.state = OMV_PROTOCOL_STATE_HEADER;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Consume one byte and check the next word
|
||||||
|
omv_buffer_consume(&ctx.buffer, 1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OMV_PROTOCOL_STATE_HEADER:
|
||||||
|
// Check if we have a complete header
|
||||||
|
if (buffer_size < OMV_PROTOCOL_HEADER_SIZE) {
|
||||||
|
return 0; // Need more data
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate packet header.
|
||||||
|
ctx.state = OMV_PROTOCOL_STATE_SYNC;
|
||||||
|
if (!omv_protocol_crc_check(packet, OMV_PROTOCOL_HEADER_SIZE)) {
|
||||||
|
// No further validation needed
|
||||||
|
} else if (packet->length > ctx.config.max_payload) {
|
||||||
|
omv_protocol_send_status(packet, OMV_PROTOCOL_STATUS_OVERFLOW);
|
||||||
|
} else if (!omv_protocol_seq_check(packet)) {
|
||||||
|
omv_protocol_send_status(packet, OMV_PROTOCOL_STATUS_SEQUENCE);
|
||||||
|
} else {
|
||||||
|
ctx.state = OMV_PROTOCOL_STATE_PAYLOAD;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume a byte if the header has been rejected.
|
||||||
|
if (ctx.state != OMV_PROTOCOL_STATE_PAYLOAD) {
|
||||||
|
omv_buffer_consume(&ctx.buffer, 1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OMV_PROTOCOL_STATE_PAYLOAD:
|
||||||
|
// HEADER + CRC + PAYLOAD + CRC
|
||||||
|
size_t packet_size = OMV_PROTOCOL_PACKET_GET_SIZE(packet);
|
||||||
|
size_t payload_size = packet_size - OMV_PROTOCOL_HEADER_SIZE;
|
||||||
|
|
||||||
|
// Check if we have the complete packet
|
||||||
|
if (buffer_size < packet_size) {
|
||||||
|
// Transition to SYNC_RECOVERY to scan for SYNC commands
|
||||||
|
ctx.scan_offset = 0;
|
||||||
|
ctx.state = OMV_PROTOCOL_STATE_SYNC_RECOVERY;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.state = OMV_PROTOCOL_STATE_SYNC;
|
||||||
|
// protocol_process may send a packet that expects an ACK.
|
||||||
|
// Clear the buffer first before calling protocol_process.
|
||||||
|
omv_buffer_consume(&ctx.buffer, packet_size);
|
||||||
|
|
||||||
|
// Check data CRC if we have payload data
|
||||||
|
if (!omv_protocol_crc_check(packet->payload, payload_size)) {
|
||||||
|
omv_protocol_send_status(packet, OMV_PROTOCOL_STATUS_CHECKSUM);
|
||||||
|
} else if (!omv_protocol_channel_check(packet)) {
|
||||||
|
omv_protocol_send_status(packet, OMV_PROTOCOL_STATUS_INVALID);
|
||||||
|
} else if (packet->flags & OMV_PROTOCOL_FLAG_ACK) {
|
||||||
|
// ACK packets are handled by WAIT_ACK state - ignore here
|
||||||
|
} else {
|
||||||
|
omv_protocol_process(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Scan through the buffer looking for a complete SYNC command
|
||||||
|
// Prevents a partial packet from deadlocking the state machine
|
||||||
|
case OMV_PROTOCOL_STATE_SYNC_RECOVERY:
|
||||||
|
if (buffer_size >= OMV_PROTOCOL_PACKET_GET_SIZE(packet)) {
|
||||||
|
ctx.state = OMV_PROTOCOL_STATE_SYNC;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; ctx.scan_offset <= buffer_size - OMV_PROTOCOL_HEADER_SIZE; ctx.scan_offset++) {
|
||||||
|
omv_protocol_packet_t *packet = (omv_protocol_packet_t *) (buffer + ctx.scan_offset);
|
||||||
|
|
||||||
|
if (omv_protocol_is_sync(packet)) {
|
||||||
|
// SYNC command is found - process SYNC and reset state
|
||||||
|
omv_protocol_process(packet);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No SYNC command found - go back to PAYLOAD state
|
||||||
|
ctx.state = OMV_PROTOCOL_STATE_PAYLOAD;
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
// Scan through buffer looking for expected ACK packet
|
||||||
|
case OMV_PROTOCOL_STATE_WAIT_ACK:
|
||||||
|
for (; ctx.scan_offset <= buffer_size - OMV_PROTOCOL_HEADER_SIZE; ctx.scan_offset++) {
|
||||||
|
omv_protocol_packet_t *packet = (omv_protocol_packet_t *) (buffer + ctx.scan_offset);
|
||||||
|
|
||||||
|
// SYNC found while waiting for ACK - process SYNC and reset state
|
||||||
|
if (omv_protocol_is_sync(packet)) {
|
||||||
|
omv_protocol_process(packet);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Found the matching ACK - consume it and return to SYNC
|
||||||
|
if (omv_protocol_is_ack(packet, ctx.wait_ack_opcode, ctx.wait_ack_sequence)) {
|
||||||
|
if ((void *) packet == buffer) {
|
||||||
|
omv_buffer_consume(&ctx.buffer, OMV_PROTOCOL_HEADER_SIZE);
|
||||||
|
}
|
||||||
|
ctx.state = OMV_PROTOCOL_STATE_SYNC;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// No matching ACK found - stay in WAIT_ACK state
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void omv_protocol_process(const omv_protocol_packet_t *packet) {
|
||||||
|
ctx.stats.recv_packets++;
|
||||||
|
|
||||||
|
switch (packet->opcode) {
|
||||||
|
case OMV_PROTOCOL_OPCODE_SYS_RESET: {
|
||||||
|
#if defined(OMV_BOARD_RESET)
|
||||||
|
OMV_BOARD_RESET();
|
||||||
|
#else
|
||||||
|
NVIC_SystemReset();
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OMV_PROTOCOL_OPCODE_SYS_BOOT: {
|
||||||
|
#if defined(MICROPY_BOARD_ENTER_BOOTLOADER)
|
||||||
|
MICROPY_BOARD_ENTER_BOOTLOADER(0, 0);
|
||||||
|
#else
|
||||||
|
NVIC_SystemReset();
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OMV_PROTOCOL_OPCODE_PROTO_SYNC: {
|
||||||
|
ctx.sequence = 0;
|
||||||
|
omv_protocol_send_status(packet, OMV_PROTOCOL_STATUS_SUCCESS);
|
||||||
|
omv_protocol_reset();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OMV_PROTOCOL_OPCODE_PROTO_GET_CAPS: {
|
||||||
|
// Convert internal config to wire caps format
|
||||||
|
omv_protocol_caps_t caps = {0};
|
||||||
|
caps.crc_enabled = ctx.config.crc_enabled;
|
||||||
|
caps.seq_enabled = ctx.config.seq_enabled;
|
||||||
|
caps.ack_enabled = ctx.config.ack_enabled;
|
||||||
|
caps.event_enabled = ctx.config.event_enabled;
|
||||||
|
caps.max_payload = ctx.config.max_payload;
|
||||||
|
// Transport fields are not sent over wire
|
||||||
|
omv_protocol_send_packet(packet->opcode, packet->channel, sizeof(caps), &caps, 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OMV_PROTOCOL_OPCODE_PROTO_SET_CAPS: {
|
||||||
|
omv_protocol_caps_t *caps = (void *) packet->payload;
|
||||||
|
|
||||||
|
// Validate only the protocol capability fields
|
||||||
|
if (packet->length != sizeof(omv_protocol_caps_t) ||
|
||||||
|
caps->max_payload < OMV_PROTOCOL_MIN_PAYLOAD_SIZE ||
|
||||||
|
caps->max_payload > OMV_PROTOCOL_MAX_PAYLOAD_SIZE) {
|
||||||
|
omv_protocol_send_status(packet, OMV_PROTOCOL_STATUS_INVALID);
|
||||||
|
} else {
|
||||||
|
// ACK the updated caps first before changing them.
|
||||||
|
omv_protocol_send_status(packet, OMV_PROTOCOL_STATUS_SUCCESS);
|
||||||
|
|
||||||
|
// Mark capabilities as negotiated for soft reset preservation
|
||||||
|
ctx.config.caps_negotiated = true;
|
||||||
|
|
||||||
|
// Update only protocol capability fields, preserve transport config
|
||||||
|
ctx.config.crc_enabled = caps->crc_enabled;
|
||||||
|
ctx.config.seq_enabled = caps->seq_enabled;
|
||||||
|
ctx.config.ack_enabled = caps->ack_enabled;
|
||||||
|
ctx.config.event_enabled = caps->event_enabled;
|
||||||
|
ctx.config.max_payload = caps->max_payload;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OMV_PROTOCOL_OPCODE_PROTO_STATS: {
|
||||||
|
omv_protocol_send_packet(packet->opcode, packet->channel, sizeof(ctx.stats), &ctx.stats, 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OMV_PROTOCOL_OPCODE_SYS_INFO: {
|
||||||
|
omv_protocol_sys_info_t sysinfo = { 0 };
|
||||||
|
|
||||||
|
// Hardware identification
|
||||||
|
sysinfo.cpu_id = SCB->CPUID;
|
||||||
|
|
||||||
|
// Device ID from board UID
|
||||||
|
#if (OMV_BOARD_UID_SIZE > 2)
|
||||||
|
sysinfo.dev_id[0] = *((uint32_t *) (OMV_BOARD_UID_ADDR + OMV_BOARD_UID_OFFSET * 2));
|
||||||
|
#endif
|
||||||
|
sysinfo.dev_id[1] = *((uint32_t *) (OMV_BOARD_UID_ADDR + OMV_BOARD_UID_OFFSET * 1));
|
||||||
|
sysinfo.dev_id[2] = *((uint32_t *) (OMV_BOARD_UID_ADDR + OMV_BOARD_UID_OFFSET * 0));
|
||||||
|
|
||||||
|
// Camera sensor chip ID
|
||||||
|
#if MICROPY_PY_CSI
|
||||||
|
size_t chip_count = 0;
|
||||||
|
size_t max_chip_ids = OMV_ARRAY_SIZE(sysinfo.chip_id);
|
||||||
|
for (size_t i = 0; chip_count < max_chip_ids && i < OMV_CSI_MAX_DEVICES; i++) {
|
||||||
|
omv_csi_t *csi = &csi_all[i];
|
||||||
|
if (csi->detected) {
|
||||||
|
sysinfo.chip_id[chip_count++] = omv_csi_get_id(csi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Hardware capabilities
|
||||||
|
#ifdef OMV_PROTOCOL_HW_CAPS
|
||||||
|
sysinfo.hw_caps[0] = OMV_PROTOCOL_HW_CAPS;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Memory information
|
||||||
|
sysinfo.flash_size_kb = 0;
|
||||||
|
sysinfo.ram_size_kb = 0;
|
||||||
|
sysinfo.framebuffer_size_kb = framebuffer_get(FB_MAINFB_ID)->raw_size / 1024;
|
||||||
|
sysinfo.stream_buffer_size_kb = framebuffer_get(FB_STREAM_ID)->raw_size / 1024;
|
||||||
|
|
||||||
|
// Firmware version
|
||||||
|
sysinfo.firmware_version[0] = OMV_FIRMWARE_VERSION_MAJOR;
|
||||||
|
sysinfo.firmware_version[1] = OMV_FIRMWARE_VERSION_MINOR;
|
||||||
|
sysinfo.firmware_version[2] = OMV_FIRMWARE_VERSION_PATCH;
|
||||||
|
|
||||||
|
// Protocol version
|
||||||
|
sysinfo.protocol_version[0] = OMV_PROTOCOL_VERSION_MAJOR;
|
||||||
|
sysinfo.protocol_version[1] = OMV_PROTOCOL_VERSION_MINOR;
|
||||||
|
sysinfo.protocol_version[2] = OMV_PROTOCOL_VERSION_PATCH;
|
||||||
|
|
||||||
|
// Bootloader version
|
||||||
|
sysinfo.bootloader_version[0] = OMV_BOOTLOADER_VERSION_MAJOR;
|
||||||
|
sysinfo.bootloader_version[1] = OMV_BOOTLOADER_VERSION_MINOR;
|
||||||
|
sysinfo.bootloader_version[2] = OMV_BOOTLOADER_VERSION_PATCH;
|
||||||
|
|
||||||
|
omv_protocol_send_packet(OMV_PROTOCOL_OPCODE_SYS_INFO, packet->channel, sizeof(sysinfo), &sysinfo, 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OMV_PROTOCOL_OPCODE_CHANNEL_LIST: {
|
||||||
|
// Build list of registered channels
|
||||||
|
int ch_count = 0;
|
||||||
|
omv_protocol_channel_entry_t ch_list[OMV_PROTOCOL_MAX_CHANNELS];
|
||||||
|
|
||||||
|
for (int i = 0; i < ctx.channels_count; i++) {
|
||||||
|
if (ctx.channels[i] != NULL) {
|
||||||
|
ch_list[ch_count].id = ctx.channels[i]->id;
|
||||||
|
ch_list[ch_count].flags = ctx.channels[i]->flags;
|
||||||
|
strncpy(ch_list[ch_count].name, ctx.channels[i]->name, OMV_PROTOCOL_CHANNEL_NAME_SIZE);
|
||||||
|
ch_list[ch_count].name[OMV_PROTOCOL_CHANNEL_NAME_SIZE - 1] = '\0';
|
||||||
|
ch_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t ch_list_size = ch_count * sizeof(omv_protocol_channel_entry_t);
|
||||||
|
omv_protocol_send_packet(packet->opcode, packet->channel, ch_list_size, ch_list, 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OMV_PROTOCOL_OPCODE_CHANNEL_POLL: {
|
||||||
|
omv_protocol_channel_poll_t response = { 0 };
|
||||||
|
|
||||||
|
for (int i = 0; i < ctx.channels_count; i++) {
|
||||||
|
const omv_protocol_channel_t *channel = ctx.channels[i];
|
||||||
|
if (channel && channel->poll) {
|
||||||
|
response.flags |= channel->poll(channel) << i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
omv_protocol_send_packet(packet->opcode, packet->channel, sizeof(response), &response, 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OMV_PROTOCOL_OPCODE_CHANNEL_LOCK: {
|
||||||
|
const omv_protocol_channel_t *channel = omv_protocol_find_channel(packet->channel);
|
||||||
|
|
||||||
|
if (!check_timeout_ms(ctx.last_lock_ms, ctx.config.lock_intval_ms)) {
|
||||||
|
omv_protocol_send_status(packet, OMV_PROTOCOL_STATUS_BUSY);
|
||||||
|
} else if (channel && channel->lock && channel->lock(channel) == 0) {
|
||||||
|
ctx.last_lock_ms = mp_hal_ticks_ms();
|
||||||
|
omv_protocol_send_status(packet, OMV_PROTOCOL_STATUS_SUCCESS);
|
||||||
|
} else {
|
||||||
|
omv_protocol_send_status(packet, OMV_PROTOCOL_STATUS_BUSY);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OMV_PROTOCOL_OPCODE_CHANNEL_UNLOCK: {
|
||||||
|
const omv_protocol_channel_t *channel = omv_protocol_find_channel(packet->channel);
|
||||||
|
|
||||||
|
if (channel && channel->unlock && channel->unlock(channel) == 0) {
|
||||||
|
omv_protocol_send_status(packet, OMV_PROTOCOL_STATUS_SUCCESS);
|
||||||
|
} else {
|
||||||
|
omv_protocol_send_status(packet, OMV_PROTOCOL_STATUS_BUSY);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OMV_PROTOCOL_OPCODE_CHANNEL_SIZE: {
|
||||||
|
const omv_protocol_channel_t *channel = omv_protocol_find_channel(packet->channel);
|
||||||
|
|
||||||
|
if (channel && channel->size) {
|
||||||
|
omv_protocol_channel_size_t response;
|
||||||
|
response.size = channel->size(channel);
|
||||||
|
omv_protocol_send_packet(packet->opcode, packet->channel, sizeof(response), &response, 0);
|
||||||
|
} else {
|
||||||
|
omv_protocol_send_status(packet, OMV_PROTOCOL_STATUS_INVALID);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OMV_PROTOCOL_OPCODE_CHANNEL_SHAPE: {
|
||||||
|
const omv_protocol_channel_t *channel = omv_protocol_find_channel(packet->channel);
|
||||||
|
|
||||||
|
if (channel && channel->shape) {
|
||||||
|
size_t shape_array[4];
|
||||||
|
size_t shape_size = channel->shape(channel, shape_array) * sizeof(size_t);
|
||||||
|
omv_protocol_send_packet(packet->opcode, packet->channel, shape_size, shape_array, 0);
|
||||||
|
} else {
|
||||||
|
omv_protocol_send_status(packet, OMV_PROTOCOL_STATUS_INVALID);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OMV_PROTOCOL_OPCODE_CHANNEL_READ: {
|
||||||
|
omv_protocol_channel_io_t *request = (void *) packet->payload;
|
||||||
|
const omv_protocol_channel_t *channel = omv_protocol_find_channel(packet->channel);
|
||||||
|
|
||||||
|
if (!request->length || !channel || !(channel->read || channel->readp)) {
|
||||||
|
omv_protocol_send_status(packet, OMV_PROTOCOL_STATUS_INVALID);
|
||||||
|
} else if (channel->readp) {
|
||||||
|
const void *data = channel->readp(channel, request->offset, request->length);
|
||||||
|
if (!data) {
|
||||||
|
omv_protocol_send_status(packet, OMV_PROTOCOL_STATUS_FAILED);
|
||||||
|
} else {
|
||||||
|
omv_protocol_send_packet(packet->opcode, packet->channel, request->length, data, 0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
uint8_t buffer[OMV_MIN(512, ctx.config.max_payload)];
|
||||||
|
|
||||||
|
while (request->length > 0) {
|
||||||
|
size_t size_rq = OMV_MIN(request->length, sizeof(buffer));
|
||||||
|
int32_t size_rd = channel->read(channel, request->offset, size_rq, buffer);
|
||||||
|
|
||||||
|
if (size_rd <= 0) {
|
||||||
|
omv_protocol_send_status(packet, OMV_PROTOCOL_STATUS_FAILED);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
request->length -= size_rd;
|
||||||
|
request->offset += size_rd;
|
||||||
|
uint8_t flags = (request->length == 0) ? 0 : OMV_PROTOCOL_FLAG_FRAGMENT;
|
||||||
|
omv_protocol_send_packet(packet->opcode, packet->channel, size_rd, buffer, flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OMV_PROTOCOL_OPCODE_CHANNEL_WRITE: {
|
||||||
|
omv_protocol_channel_io_t *request = (void *) packet->payload;
|
||||||
|
const omv_protocol_channel_t *channel = omv_protocol_find_channel(packet->channel);
|
||||||
|
|
||||||
|
if (channel && channel->write && request->length) {
|
||||||
|
if (channel->write(channel, request->offset, request->length, request->payload)) {
|
||||||
|
omv_protocol_send_status(packet, OMV_PROTOCOL_STATUS_SUCCESS);
|
||||||
|
} else {
|
||||||
|
omv_protocol_send_status(packet, OMV_PROTOCOL_STATUS_FAILED);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
omv_protocol_send_status(packet, OMV_PROTOCOL_STATUS_INVALID);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OMV_PROTOCOL_OPCODE_CHANNEL_IOCTL: {
|
||||||
|
omv_protocol_channel_ioctl_t *ioctl = (void *) packet->payload;
|
||||||
|
size_t ioctl_len = packet->length - offsetof(omv_protocol_channel_ioctl_t, payload);
|
||||||
|
uint8_t *ioctl_arg = (ioctl_len) ? ioctl->payload : NULL;
|
||||||
|
const omv_protocol_channel_t *channel = omv_protocol_find_channel(packet->channel);
|
||||||
|
|
||||||
|
if (channel && channel->ioctl) {
|
||||||
|
// Validate argument size for static channels only
|
||||||
|
if (!OMV_PROTOCOL_CHANNEL_FLAG_GET(channel, DYNAMIC) && // TODO
|
||||||
|
!omv_protocol_ioctl_check(packet->channel, ioctl->request, ioctl_len)) {
|
||||||
|
omv_protocol_send_status(packet, OMV_PROTOCOL_STATUS_INVALID);
|
||||||
|
} else if (channel->ioctl(channel, ioctl->request, ioctl_len, ioctl_arg)) {
|
||||||
|
omv_protocol_send_status(packet, OMV_PROTOCOL_STATUS_FAILED);
|
||||||
|
} else {
|
||||||
|
omv_protocol_send_status(packet, OMV_PROTOCOL_STATUS_SUCCESS);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
omv_protocol_send_status(packet, OMV_PROTOCOL_STATUS_INVALID);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
omv_protocol_send_status(packet, OMV_PROTOCOL_STATUS_INVALID);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
370
protocol/omv_protocol.h
Normal file
370
protocol/omv_protocol.h
Normal file
@ -0,0 +1,370 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025 OpenMV, LLC.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* OpenMV Protocol - Transport-agnostic communication protocol
|
||||||
|
* This protocol provides reliable communication between host and
|
||||||
|
* device with support for multiple transport layers (USB, UART, TCP/IP).
|
||||||
|
*/
|
||||||
|
#ifndef __OMV_PROTOCOL_H__
|
||||||
|
#define __OMV_PROTOCOL_H__
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include "omv_buffer.h"
|
||||||
|
#include "omv_protocol_channel.h"
|
||||||
|
#include "omv_boardconfig.h"
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
* Protocol Constants
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
#define OMV_FIRMWARE_VERSION_MAJOR (4)
|
||||||
|
#define OMV_FIRMWARE_VERSION_MINOR (7)
|
||||||
|
#define OMV_FIRMWARE_VERSION_PATCH (0)
|
||||||
|
|
||||||
|
#define OMV_PROTOCOL_VERSION_MAJOR (1)
|
||||||
|
#define OMV_PROTOCOL_VERSION_MINOR (0)
|
||||||
|
#define OMV_PROTOCOL_VERSION_PATCH (0)
|
||||||
|
|
||||||
|
#define OMV_PROTOCOL_SYNC_SIZE (2)
|
||||||
|
#define OMV_PROTOCOL_SYNC_WORD (0xD5AA)
|
||||||
|
#define OMV_PROTOCOL_CRC_SIZE (2)
|
||||||
|
#define OMV_PROTOCOL_HEADER_SIZE (10) // SYNC[2] SEQ[1] CHAN[1] FLAGS[1] OPCODE[1] LEN[2] CRC[2]
|
||||||
|
|
||||||
|
#define OMV_PROTOCOL_MAX_CHANNELS (32)
|
||||||
|
#define OMV_PROTOCOL_DEF_RTX_RETRIES (3)
|
||||||
|
#define OMV_PROTOCOL_DEF_RTX_TIMEOUT_MS (500) // Doubled after each timeout
|
||||||
|
#define OMV_PROTOCOL_MIN_LOCK_INTERVAL_MS (10)
|
||||||
|
#define OMV_PROTOCOL_MAGIC_BAUDRATE (921600)
|
||||||
|
|
||||||
|
#ifndef OMV_PROTOCOL_DEFAULT_CHANNELS
|
||||||
|
#define OMV_PROTOCOL_DEFAULT_CHANNELS (1)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef OMV_PROTOCOL_MAX_BUFFER_SIZE
|
||||||
|
#define OMV_PROTOCOL_MAX_BUFFER_SIZE (4096)
|
||||||
|
#endif
|
||||||
|
#define OMV_PROTOCOL_MIN_PAYLOAD_SIZE (64 - OMV_PROTOCOL_HEADER_SIZE - 2)
|
||||||
|
#define OMV_PROTOCOL_MAX_PAYLOAD_SIZE (OMV_PROTOCOL_MAX_BUFFER_SIZE - OMV_PROTOCOL_HEADER_SIZE - 2)
|
||||||
|
#define OMV_PROTOCOL_MAX_PACKET_SIZE (OMV_PROTOCOL_HEADER_SIZE + OMV_PROTOCOL_MAX_PAYLOAD_SIZE + 2)
|
||||||
|
|
||||||
|
#define OMV_PROTOCOL_PACKET_GET_SIZE(pkt) \
|
||||||
|
(OMV_PROTOCOL_HEADER_SIZE + (pkt->length + (!!pkt->length * 2)))
|
||||||
|
|
||||||
|
// Macro for protocol struct size validation
|
||||||
|
#define OMV_PROTOCOL_ASSERT_SIZE(type, size) \
|
||||||
|
_Static_assert(sizeof(type) == (size), #type " must be exactly " #size " bytes")
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
* Packet Flags
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
OMV_PROTOCOL_FLAG_ACK = (1 << 0), // ACK packet
|
||||||
|
OMV_PROTOCOL_FLAG_NAK = (1 << 1), // NAK packet
|
||||||
|
OMV_PROTOCOL_FLAG_RTX = (1 << 2), // RTX packet
|
||||||
|
OMV_PROTOCOL_FLAG_ACK_REQ = (1 << 3), // Packet requires an ACK
|
||||||
|
OMV_PROTOCOL_FLAG_FRAGMENT = (1 << 4), // More fragments to follow
|
||||||
|
OMV_PROTOCOL_FLAG_EVENT = (1 << 5), // Event packet
|
||||||
|
// Bits 6-7 reserved for future use
|
||||||
|
} omv_protocol_flags_t;
|
||||||
|
|
||||||
|
#define OMV_PROTOCOL_FLAG_NO_ACK (OMV_PROTOCOL_FLAG_ACK | OMV_PROTOCOL_FLAG_NAK | OMV_PROTOCOL_FLAG_EVENT)
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
* System Events (Channel 0)
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
OMV_PROTOCOL_EVENT_CHANNEL_REGISTERED = 0x00, // Channel dynamically registered
|
||||||
|
OMV_PROTOCOL_EVENT_CHANNEL_UNREGISTERED = 0x01, // Channel unregistered
|
||||||
|
OMV_PROTOCOL_EVENT_SOFT_REBOOT = 0x02, // System is going for a soft-reboot
|
||||||
|
OMV_PROTOCOL_EVENT_NOTIFY = 0xFFFF, // Notification event (no payload)
|
||||||
|
} omv_protocol_event_type_t;
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
* Status Codes
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
OMV_PROTOCOL_STATUS_SUCCESS = 0x00, // Success
|
||||||
|
OMV_PROTOCOL_STATUS_FAILED = 0x01, // Command failed
|
||||||
|
OMV_PROTOCOL_STATUS_INVALID = 0x02, // Invalid command/arg
|
||||||
|
OMV_PROTOCOL_STATUS_TIMEOUT = 0x03, // Operation timeout
|
||||||
|
OMV_PROTOCOL_STATUS_BUSY = 0x04, // Device busy
|
||||||
|
OMV_PROTOCOL_STATUS_CHECKSUM = 0x05, // CRC error
|
||||||
|
OMV_PROTOCOL_STATUS_SEQUENCE = 0x06, // Sequence error
|
||||||
|
OMV_PROTOCOL_STATUS_OVERFLOW = 0x07, // Buffer overflow
|
||||||
|
OMV_PROTOCOL_STATUS_FRAGMENT = 0x08, // Fragmentation error
|
||||||
|
OMV_PROTOCOL_STATUS_UNKNOWN = 0x09, // Unknown error
|
||||||
|
} omv_protocol_status_t;
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
* Protocol Opcodes
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
// Protocol Control Commands (0x00-0x0F)
|
||||||
|
OMV_PROTOCOL_OPCODE_PROTO_SYNC = 0x00, // Synchronization request
|
||||||
|
OMV_PROTOCOL_OPCODE_PROTO_GET_CAPS = 0x01, // Get capabilities
|
||||||
|
OMV_PROTOCOL_OPCODE_PROTO_SET_CAPS = 0x02, // Set capabilities
|
||||||
|
OMV_PROTOCOL_OPCODE_PROTO_STATS = 0x03, // Get protocol statistics
|
||||||
|
|
||||||
|
// System Control Commands (0x10-0x1F)
|
||||||
|
OMV_PROTOCOL_OPCODE_SYS_RESET = 0x10, // System reset
|
||||||
|
OMV_PROTOCOL_OPCODE_SYS_BOOT = 0x11, // Jump to bootloader
|
||||||
|
OMV_PROTOCOL_OPCODE_SYS_INFO = 0x12, // Get system info
|
||||||
|
OMV_PROTOCOL_OPCODE_SYS_EVENT = 0x13, // System event
|
||||||
|
OMV_PROTOCOL_OPCODE_SYS_LAST = 0x13, // Last system command
|
||||||
|
|
||||||
|
// Data Channels Commands (0x20-0x2F)
|
||||||
|
OMV_PROTOCOL_OPCODE_CHANNEL_LIST = 0x20, // List registered channels
|
||||||
|
OMV_PROTOCOL_OPCODE_CHANNEL_POLL = 0x21, // Poll channels status
|
||||||
|
OMV_PROTOCOL_OPCODE_CHANNEL_LOCK = 0x22, // Lock the data channel
|
||||||
|
OMV_PROTOCOL_OPCODE_CHANNEL_UNLOCK = 0x23, // Lock the data channel
|
||||||
|
OMV_PROTOCOL_OPCODE_CHANNEL_SHAPE = 0x24, // Available data size.
|
||||||
|
OMV_PROTOCOL_OPCODE_CHANNEL_SIZE = 0x25, // Available data size.
|
||||||
|
OMV_PROTOCOL_OPCODE_CHANNEL_READ = 0x26, // Dump the data channel
|
||||||
|
OMV_PROTOCOL_OPCODE_CHANNEL_WRITE = 0x27, // Write the data channel
|
||||||
|
OMV_PROTOCOL_OPCODE_CHANNEL_IOCTL = 0x28, // Perform ioctl on channel
|
||||||
|
OMV_PROTOCOL_OPCODE_CHANNEL_EVENT = 0x29, // System event
|
||||||
|
} omv_protocol_opcode_t;
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
* Packet structure
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
typedef struct __attribute__((packed)) {
|
||||||
|
uint16_t sync; // Synchronization word (0xAA55)
|
||||||
|
uint8_t sequence; // Sequence number
|
||||||
|
uint8_t channel; // Channel ID
|
||||||
|
uint8_t flags; // Packet flags
|
||||||
|
uint8_t opcode; // Command/response opcode
|
||||||
|
uint16_t length; // Length of data field only
|
||||||
|
uint16_t crc; // CRC of header fields
|
||||||
|
uint8_t payload[]; // Flexible array member
|
||||||
|
}
|
||||||
|
omv_protocol_packet_t;
|
||||||
|
OMV_PROTOCOL_ASSERT_SIZE(omv_protocol_packet_t, OMV_PROTOCOL_HEADER_SIZE);
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
* Packet Payloads
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
// NAK respone structure
|
||||||
|
typedef struct __attribute__((packed)) {
|
||||||
|
uint16_t status; // Error status
|
||||||
|
}
|
||||||
|
omv_protocol_response_t;
|
||||||
|
OMV_PROTOCOL_ASSERT_SIZE(omv_protocol_response_t, 2);
|
||||||
|
|
||||||
|
// Channel size structure
|
||||||
|
typedef struct __attribute__((packed)) {
|
||||||
|
uint32_t size;
|
||||||
|
}
|
||||||
|
omv_protocol_channel_size_t;
|
||||||
|
OMV_PROTOCOL_ASSERT_SIZE(omv_protocol_channel_size_t, 4);
|
||||||
|
|
||||||
|
// Channel poll response structure
|
||||||
|
typedef struct __attribute__((packed)) {
|
||||||
|
uint32_t flags;
|
||||||
|
}
|
||||||
|
omv_protocol_channel_poll_t;
|
||||||
|
OMV_PROTOCOL_ASSERT_SIZE(omv_protocol_channel_poll_t, 4);
|
||||||
|
|
||||||
|
// Channel io structure
|
||||||
|
typedef struct __attribute__((packed)) {
|
||||||
|
uint32_t offset;
|
||||||
|
uint32_t length;
|
||||||
|
uint8_t payload[];
|
||||||
|
}
|
||||||
|
omv_protocol_channel_io_t;
|
||||||
|
OMV_PROTOCOL_ASSERT_SIZE(omv_protocol_channel_io_t, 8);
|
||||||
|
|
||||||
|
// Channel ioctl structure
|
||||||
|
typedef struct __attribute__((packed)) {
|
||||||
|
uint32_t request;
|
||||||
|
uint8_t payload[];
|
||||||
|
}
|
||||||
|
omv_protocol_channel_ioctl_t;
|
||||||
|
OMV_PROTOCOL_ASSERT_SIZE(omv_protocol_channel_ioctl_t, 4);
|
||||||
|
|
||||||
|
// Channel list entry structure
|
||||||
|
typedef struct __attribute__((packed)) {
|
||||||
|
uint8_t id;
|
||||||
|
uint8_t flags;
|
||||||
|
char name[OMV_PROTOCOL_CHANNEL_NAME_SIZE];
|
||||||
|
}
|
||||||
|
omv_protocol_channel_entry_t;
|
||||||
|
OMV_PROTOCOL_ASSERT_SIZE(omv_protocol_channel_entry_t, 16);
|
||||||
|
|
||||||
|
// Protocol capabilities structure
|
||||||
|
typedef struct __attribute__((packed)) {
|
||||||
|
uint32_t crc_enabled : 1;
|
||||||
|
uint32_t seq_enabled : 1;
|
||||||
|
uint32_t ack_enabled : 1;
|
||||||
|
uint32_t event_enabled : 1;
|
||||||
|
uint32_t reserved1 : 28;
|
||||||
|
uint16_t max_payload;
|
||||||
|
uint8_t reserved2[10];
|
||||||
|
}
|
||||||
|
omv_protocol_caps_t;
|
||||||
|
OMV_PROTOCOL_ASSERT_SIZE(omv_protocol_caps_t, 16);
|
||||||
|
|
||||||
|
// Protocol statistics structure
|
||||||
|
typedef struct __attribute__((packed)) {
|
||||||
|
uint32_t sent_packets;
|
||||||
|
uint32_t recv_packets;
|
||||||
|
uint32_t checksum_errors;
|
||||||
|
uint32_t sequence_errors;
|
||||||
|
uint32_t retransmit;
|
||||||
|
uint32_t transport_errors;
|
||||||
|
uint32_t sent_events;
|
||||||
|
uint32_t reserved;
|
||||||
|
}
|
||||||
|
omv_protocol_stats_t;
|
||||||
|
OMV_PROTOCOL_ASSERT_SIZE(omv_protocol_stats_t, 32);
|
||||||
|
|
||||||
|
// System information structure
|
||||||
|
typedef struct __attribute__((packed)) {
|
||||||
|
// Hardware identification
|
||||||
|
uint32_t cpu_id; // CPUID register value
|
||||||
|
uint32_t dev_id[3]; // Unique device ID register
|
||||||
|
uint32_t chip_id[3]; // Camera sensor chip ID
|
||||||
|
uint32_t id_reserved[2]; // Reserved for future expansion
|
||||||
|
|
||||||
|
// Hardware capabilities
|
||||||
|
uint32_t hw_caps[2]; // Defined in hw_caps.h
|
||||||
|
|
||||||
|
// Memory information
|
||||||
|
uint32_t flash_size_kb; // Flash memory size in KB
|
||||||
|
uint32_t ram_size_kb; // RAM size in KB
|
||||||
|
uint32_t framebuffer_size_kb; // Framebuffer size in KB
|
||||||
|
uint32_t stream_buffer_size_kb; // Streaming buffer size in KB
|
||||||
|
uint32_t memory_reserved[2]; // Reserved for future expansion
|
||||||
|
|
||||||
|
// Version information
|
||||||
|
uint8_t firmware_version[3]; // Major, Minor, Patch
|
||||||
|
uint8_t protocol_version[3]; // Major, Minor, Patch
|
||||||
|
uint8_t bootloader_version[3]; // Bootloader version
|
||||||
|
|
||||||
|
// Pad struct to 80 bytes
|
||||||
|
uint8_t reserved[3];
|
||||||
|
}
|
||||||
|
omv_protocol_sys_info_t;
|
||||||
|
OMV_PROTOCOL_ASSERT_SIZE(omv_protocol_sys_info_t, 80);
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
* Protocol Context
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
// Protocol State Machine
|
||||||
|
typedef enum {
|
||||||
|
OMV_PROTOCOL_STATE_SYNC = 0x00, // Wait for sync bytes
|
||||||
|
OMV_PROTOCOL_STATE_HEADER = 0x01, // Reading header
|
||||||
|
OMV_PROTOCOL_STATE_PAYLOAD = 0x02, // Reading data
|
||||||
|
OMV_PROTOCOL_STATE_SYNC_RECOVERY = 0x03, // Scan for SYNC commands when stuck
|
||||||
|
OMV_PROTOCOL_STATE_WAIT_ACK = 0x04, // Wait for ACK response
|
||||||
|
} omv_protocol_state_t;
|
||||||
|
|
||||||
|
// Protocol configuration structure (internal use)
|
||||||
|
typedef struct {
|
||||||
|
// Protocol capabilities (negotiated)
|
||||||
|
bool crc_enabled;
|
||||||
|
bool seq_enabled;
|
||||||
|
bool ack_enabled;
|
||||||
|
bool event_enabled;
|
||||||
|
uint16_t max_payload;
|
||||||
|
bool caps_negotiated;
|
||||||
|
// Transport configuration (local only)
|
||||||
|
bool soft_reboot;
|
||||||
|
uint16_t rtx_retries;
|
||||||
|
uint16_t rtx_timeout_ms;
|
||||||
|
uint16_t lock_intval_ms;
|
||||||
|
} omv_protocol_config_t;
|
||||||
|
|
||||||
|
// Protocol context
|
||||||
|
typedef struct {
|
||||||
|
// State machine
|
||||||
|
uint8_t sequence; // Next sequence number
|
||||||
|
uint32_t last_lock_ms; // Timestamp of the last successful lock
|
||||||
|
int32_t scan_offset;
|
||||||
|
omv_protocol_state_t state;
|
||||||
|
|
||||||
|
// ACK waiting context
|
||||||
|
uint8_t wait_ack_opcode; // Opcode we're waiting ACK for
|
||||||
|
uint8_t wait_ack_sequence; // Sequence we're waiting ACK for
|
||||||
|
|
||||||
|
// Protocol configuration (capabilities + transport config)
|
||||||
|
omv_protocol_config_t config;
|
||||||
|
|
||||||
|
// Buffer for received data
|
||||||
|
omv_buffer_t buffer;
|
||||||
|
uint8_t rawbuf[OMV_PROTOCOL_MAX_BUFFER_SIZE];
|
||||||
|
|
||||||
|
// Protocol physical/logical channels
|
||||||
|
uint8_t channels_count;
|
||||||
|
const omv_protocol_channel_t *channels[OMV_PROTOCOL_MAX_CHANNELS];
|
||||||
|
|
||||||
|
// Protocol Statistics
|
||||||
|
omv_protocol_stats_t stats;
|
||||||
|
} omv_protocol_context_t;
|
||||||
|
|
||||||
|
// Initialize the protocol context
|
||||||
|
int omv_protocol_init(const omv_protocol_config_t *config);
|
||||||
|
|
||||||
|
// Initialize the protocol using defaults (default config + default channels)
|
||||||
|
int omv_protocol_init_default(void);
|
||||||
|
|
||||||
|
// Deinitialize protocol context
|
||||||
|
void omv_protocol_deinit(void);
|
||||||
|
|
||||||
|
// Helper function to check if the transport is active.
|
||||||
|
bool omv_protocol_is_active(void);
|
||||||
|
|
||||||
|
// Helper function to exec scripts in stdio buffer.
|
||||||
|
// Returns false, if no script is ready, true on executing the script.
|
||||||
|
bool omv_protocol_exec_script(void);
|
||||||
|
|
||||||
|
// Register a channel
|
||||||
|
int omv_protocol_register_channel(const omv_protocol_channel_t *channel);
|
||||||
|
|
||||||
|
// Find channel by ID.
|
||||||
|
const omv_protocol_channel_t *omv_protocol_find_channel(uint8_t channel_id);
|
||||||
|
|
||||||
|
// Find transport channel.
|
||||||
|
const omv_protocol_channel_t *omv_protocol_find_transport(void);
|
||||||
|
|
||||||
|
// Send event packet
|
||||||
|
int omv_protocol_send_event(uint8_t channel_id, uint16_t event, bool wait_ack);
|
||||||
|
|
||||||
|
// Send response packet
|
||||||
|
int omv_protocol_send_packet(uint8_t opcode, uint8_t channel_id, size_t size, const void *data, uint8_t flags);
|
||||||
|
|
||||||
|
// Call on events or periodically to process events
|
||||||
|
int omv_protocol_task(void);
|
||||||
|
|
||||||
|
// Process assembled packet
|
||||||
|
void omv_protocol_process(const omv_protocol_packet_t *packet);
|
||||||
|
#endif // __OMV_PROTOCOL_H__
|
150
protocol/omv_protocol_channel.h
Normal file
150
protocol/omv_protocol_channel.h
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025 OpenMV, LLC.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* OpenMV Protocol - Channel Interface
|
||||||
|
* This header defines the channel abstraction layer for the OpenMV Protocol.
|
||||||
|
* Channels provide a unified interface for accessing different types of data
|
||||||
|
* and functionality through the protocol.
|
||||||
|
*/
|
||||||
|
#ifndef __OMV_PROTOCOL_CHANNEL_H__
|
||||||
|
#define __OMV_PROTOCOL_CHANNEL_H__
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
* Channel Constants
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
#define OMV_PROTOCOL_CHANNEL_NAME_SIZE (14)
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
* Channel IOCTL Commands
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
// Standard IOCTL commands for stdin channel
|
||||||
|
typedef enum {
|
||||||
|
OMV_CHANNEL_IOCTL_STDIN_STOP = 0x01, // Stop running script
|
||||||
|
OMV_CHANNEL_IOCTL_STDIN_EXEC = 0x02, // Execute script
|
||||||
|
OMV_CHANNEL_IOCTL_STDIN_RESET = 0x03, // Reset script buffer
|
||||||
|
} omv_channel_ioctl_stdin_t;
|
||||||
|
|
||||||
|
// Standard IOCTL commands for stream channel
|
||||||
|
typedef enum {
|
||||||
|
OMV_CHANNEL_IOCTL_STREAM_CTRL = 0x00, // Enable/disable streaming
|
||||||
|
OMV_CHANNEL_IOCTL_STREAM_RAW_CTRL = 0x01, // Enable/disable raw streaming
|
||||||
|
OMV_CHANNEL_IOCTL_STREAM_RAW_CFG = 0x02, // Set raw stream resolution
|
||||||
|
} omv_channel_ioctl_stream_t;
|
||||||
|
|
||||||
|
// Standard IOCTL commands for profile channel
|
||||||
|
typedef enum {
|
||||||
|
OMV_CHANNEL_IOCTL_PROFILE_MODE = 0x00, // Set profiling mode
|
||||||
|
OMV_CHANNEL_IOCTL_PROFILE_SET_EVENT = 0x01, // Set event type to profile
|
||||||
|
OMV_CHANNEL_IOCTL_PROFILE_RESET = 0x02, // Reset profiler data
|
||||||
|
OMV_CHANNEL_IOCTL_PROFILE_STATS = 0x03, // Get profiler statistics
|
||||||
|
} omv_channel_ioctl_profile_t;
|
||||||
|
|
||||||
|
// IOCTL sizes lookup table entries
|
||||||
|
#define OMV_PROTOCOL_CHANNEL_IOCTL_TABLE \
|
||||||
|
{OMV_PROTOCOL_CHANNEL_ID_STDIN, OMV_CHANNEL_IOCTL_STDIN_STOP, 0}, \
|
||||||
|
{OMV_PROTOCOL_CHANNEL_ID_STDIN, OMV_CHANNEL_IOCTL_STDIN_EXEC, 0}, \
|
||||||
|
{OMV_PROTOCOL_CHANNEL_ID_STDIN, OMV_CHANNEL_IOCTL_STDIN_RESET, 0}, \
|
||||||
|
\
|
||||||
|
{OMV_PROTOCOL_CHANNEL_ID_STREAM, OMV_CHANNEL_IOCTL_STREAM_CTRL, 4}, \
|
||||||
|
{OMV_PROTOCOL_CHANNEL_ID_STREAM, OMV_CHANNEL_IOCTL_STREAM_RAW_CTRL, 4}, \
|
||||||
|
{OMV_PROTOCOL_CHANNEL_ID_STREAM, OMV_CHANNEL_IOCTL_STREAM_RAW_CFG, 8}, \
|
||||||
|
\
|
||||||
|
{OMV_PROTOCOL_CHANNEL_ID_PROFILE, OMV_CHANNEL_IOCTL_PROFILE_MODE, 4}, \
|
||||||
|
{OMV_PROTOCOL_CHANNEL_ID_PROFILE, OMV_CHANNEL_IOCTL_PROFILE_SET_EVENT, 8}, \
|
||||||
|
{OMV_PROTOCOL_CHANNEL_ID_PROFILE, OMV_CHANNEL_IOCTL_PROFILE_RESET, 0}, \
|
||||||
|
{OMV_PROTOCOL_CHANNEL_ID_PROFILE, OMV_CHANNEL_IOCTL_PROFILE_STATS, 0}
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
* Channel Interface
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
// Reserved/predefined channel IDs (usable as array indices)
|
||||||
|
typedef enum {
|
||||||
|
OMV_PROTOCOL_CHANNEL_ID_TRANSPORT = 0, // Transport layer (not accessible via protocol)
|
||||||
|
OMV_PROTOCOL_CHANNEL_ID_STDIN = 1, // Script input (write-only)
|
||||||
|
OMV_PROTOCOL_CHANNEL_ID_STDOUT = 2, // Text output (read-only)
|
||||||
|
OMV_PROTOCOL_CHANNEL_ID_STREAM = 3, // Stream data (read-only)
|
||||||
|
OMV_PROTOCOL_CHANNEL_ID_PROFILE = 4, // Profiling data (when enabled, read-only)
|
||||||
|
} omv_protocol_channel_id_t;
|
||||||
|
|
||||||
|
// Channel flags
|
||||||
|
typedef enum {
|
||||||
|
OMV_PROTOCOL_CHANNEL_FLAG_READ = (1 << 0), // Channel supports read operations
|
||||||
|
OMV_PROTOCOL_CHANNEL_FLAG_WRITE = (1 << 1), // Channel supports write operations
|
||||||
|
OMV_PROTOCOL_CHANNEL_FLAG_EXEC = (1 << 2), // Executable channel
|
||||||
|
OMV_PROTOCOL_CHANNEL_FLAG_LOCK = (1 << 3), // Channel requires locking before read/write
|
||||||
|
OMV_PROTOCOL_CHANNEL_FLAG_STREAM = (1 << 4), // Streaming channel
|
||||||
|
OMV_PROTOCOL_CHANNEL_FLAG_DYNAMIC = (1 << 5), // Channel was dynamically created
|
||||||
|
OMV_PROTOCOL_CHANNEL_FLAG_PHYSICAL = (1 << 6), // Physical transport channel (not accessible via protocol)
|
||||||
|
} omv_protocol_channel_flags_t;
|
||||||
|
|
||||||
|
// Helper macros
|
||||||
|
#define OMV_PROTOCOL_CHANNEL_FLAG_GET(channel, flag) \
|
||||||
|
(((channel)->flags & OMV_PROTOCOL_CHANNEL_FLAG_##flag) != 0)
|
||||||
|
|
||||||
|
#define OMV_PROTOCOL_CHANNEL_IS_TRANSPORT(channel) \
|
||||||
|
(OMV_PROTOCOL_CHANNEL_FLAG_GET(channel, PHYSICAL) && \
|
||||||
|
(channel)->read && (channel)->write && (channel)->size && \
|
||||||
|
(channel)->is_active)
|
||||||
|
|
||||||
|
// Forward declaration
|
||||||
|
typedef struct omv_protocol_channel omv_protocol_channel_t;
|
||||||
|
|
||||||
|
// Channel interface (used for both transport and logical channels)
|
||||||
|
struct omv_protocol_channel {
|
||||||
|
void *priv;
|
||||||
|
uint8_t id;
|
||||||
|
uint32_t flags;
|
||||||
|
char name[OMV_PROTOCOL_CHANNEL_NAME_SIZE];
|
||||||
|
int (*init) (const omv_protocol_channel_t *channel);
|
||||||
|
int (*deinit) (const omv_protocol_channel_t *channel);
|
||||||
|
bool (*poll) (const omv_protocol_channel_t *channel);
|
||||||
|
int (*lock) (const omv_protocol_channel_t *channel);
|
||||||
|
int (*unlock) (const omv_protocol_channel_t *channel);
|
||||||
|
size_t (*size) (const omv_protocol_channel_t *channel);
|
||||||
|
size_t (*shape) (const omv_protocol_channel_t *channel, size_t shape[4]);
|
||||||
|
int (*read) (const omv_protocol_channel_t *channel, uint32_t offset, size_t size, void *data);
|
||||||
|
int (*write) (const omv_protocol_channel_t *channel, uint32_t offset, size_t size, const void *data);
|
||||||
|
const void *(*readp) (const omv_protocol_channel_t *channel, uint32_t offset, size_t size);
|
||||||
|
int (*flush) (const omv_protocol_channel_t *channel);
|
||||||
|
int (*ioctl) (const omv_protocol_channel_t *channel, uint32_t cmd, size_t len, void *arg);
|
||||||
|
// Stdin-specific function to execute scripts internall (not exposed via ioctl).
|
||||||
|
bool (*exec) (const omv_protocol_channel_t *channel);
|
||||||
|
// Transport-specific functions (for channel ID 0 only)
|
||||||
|
bool (*is_active) (const omv_protocol_channel_t *channel);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Default channels.
|
||||||
|
extern const omv_protocol_channel_t omv_usb_channel;
|
||||||
|
extern const omv_protocol_channel_t omv_stdin_channel;
|
||||||
|
extern const omv_protocol_channel_t omv_stdout_channel;
|
||||||
|
extern const omv_protocol_channel_t omv_stream_channel;
|
||||||
|
extern const omv_protocol_channel_t omv_profile_channel;
|
||||||
|
|
||||||
|
#endif // __OMV_PROTOCOL_CHANNEL_H__
|
97
protocol/omv_protocol_channel_profile.c
Normal file
97
protocol/omv_protocol_channel_profile.c
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2022-2024 OpenMV, LLC.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* OpenMV Protocol Channels.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "omv_common.h"
|
||||||
|
#include "omv_profiler.h"
|
||||||
|
#include "omv_protocol.h"
|
||||||
|
#include "omv_boardconfig.h"
|
||||||
|
|
||||||
|
#if OMV_PROFILER_ENABLE
|
||||||
|
static size_t profile_channel_size(const omv_protocol_channel_t *channel) {
|
||||||
|
return omv_profiler_get_size();
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t profile_channel_shape(const omv_protocol_channel_t *channel, size_t shape[4]) {
|
||||||
|
shape[0] = omv_profiler_get_size() / sizeof(omv_profiler_data_t);
|
||||||
|
shape[1] = sizeof(omv_profiler_data_t);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int profile_channel_lock(const omv_protocol_channel_t *channel) {
|
||||||
|
size_t size = channel->size(channel);
|
||||||
|
return size && mutex_try_lock(omv_profiler_lock(), MUTEX_TID_IDE) ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int profile_channel_unlock(const omv_protocol_channel_t *channel) {
|
||||||
|
mutex_unlock(omv_profiler_lock(), MUTEX_TID_IDE);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const void *profile_channel_readp(const omv_protocol_channel_t *channel, uint32_t offset, size_t size) {
|
||||||
|
const uint8_t *data = omv_profiler_get_data();
|
||||||
|
if (offset + size > channel->size(channel)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return data + offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int profile_channel_ioctl(const omv_protocol_channel_t *channel, uint32_t cmd, size_t len, void *arg) {
|
||||||
|
union {
|
||||||
|
uint8_t bytes[16];
|
||||||
|
uint32_t args[4];
|
||||||
|
} u;
|
||||||
|
|
||||||
|
memcpy(u.bytes, arg, len);
|
||||||
|
|
||||||
|
switch (cmd) {
|
||||||
|
case OMV_CHANNEL_IOCTL_PROFILE_MODE:
|
||||||
|
omv_profiler_set_mode(u.args[0]);
|
||||||
|
return 0;
|
||||||
|
case OMV_CHANNEL_IOCTL_PROFILE_SET_EVENT:
|
||||||
|
omv_profiler_set_event(u.args[0], u.args[1]);
|
||||||
|
return 0;
|
||||||
|
case OMV_CHANNEL_IOCTL_PROFILE_RESET:
|
||||||
|
omv_profiler_reset();
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const omv_protocol_channel_t omv_profile_channel = {
|
||||||
|
.priv = NULL,
|
||||||
|
.id = OMV_PROTOCOL_CHANNEL_ID_PROFILE,
|
||||||
|
.name = "profile",
|
||||||
|
.flags = OMV_PROTOCOL_CHANNEL_FLAG_READ |
|
||||||
|
OMV_PROTOCOL_CHANNEL_FLAG_LOCK,
|
||||||
|
.lock = profile_channel_lock,
|
||||||
|
.unlock = profile_channel_unlock,
|
||||||
|
.size = profile_channel_size,
|
||||||
|
.shape = profile_channel_shape,
|
||||||
|
.readp = profile_channel_readp,
|
||||||
|
.ioctl = profile_channel_ioctl,
|
||||||
|
};
|
||||||
|
#endif // OMV_PROFILER_ENABLE
|
219
protocol/omv_protocol_channel_stdio.c
Normal file
219
protocol/omv_protocol_channel_stdio.c
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2022-2024 OpenMV, LLC.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* OpenMV Protocol Channels.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "py/mphal.h"
|
||||||
|
#include "py/nlr.h"
|
||||||
|
#include "py/gc.h"
|
||||||
|
#include "py/ringbuf.h"
|
||||||
|
#include "py/runtime.h"
|
||||||
|
#include "shared/runtime/interrupt_char.h"
|
||||||
|
#include "shared/runtime/pyexec.h"
|
||||||
|
|
||||||
|
#include "omv_common.h"
|
||||||
|
#include "omv_protocol.h"
|
||||||
|
#include "omv_boardconfig.h"
|
||||||
|
|
||||||
|
#ifndef OMV_PROTOCOL_STDIO_BUFFER_SIZE
|
||||||
|
#define OMV_PROTOCOL_STDIO_BUFFER_SIZE (512)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
vstr_t vstrbuf;
|
||||||
|
bool script_running;
|
||||||
|
ringbuf_t ringbuf;
|
||||||
|
uint8_t rawbuf[OMV_PROTOCOL_STDIO_BUFFER_SIZE];
|
||||||
|
} stdio_channel_context_t;
|
||||||
|
|
||||||
|
static stdio_channel_context_t stdio_channel_ctx;
|
||||||
|
|
||||||
|
static int stdin_channel_init(const omv_protocol_channel_t *channel) {
|
||||||
|
stdio_channel_context_t *ctx = channel->priv;
|
||||||
|
ctx->script_running = false;
|
||||||
|
vstr_init(&ctx->vstrbuf, 2048);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int stdout_channel_init(const omv_protocol_channel_t *channel) {
|
||||||
|
stdio_channel_context_t *ctx = channel->priv;
|
||||||
|
|
||||||
|
// Initialize ring buffer once to keep output from previous runs.
|
||||||
|
if (ctx->ringbuf.buf == NULL) {
|
||||||
|
ctx->ringbuf = (ringbuf_t) {
|
||||||
|
ctx->rawbuf, sizeof(ctx->rawbuf), 0, 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void stdio_channel_pyexec_hook(bool running) {
|
||||||
|
stdio_channel_ctx.script_running = running;
|
||||||
|
omv_protocol_send_event(OMV_PROTOCOL_CHANNEL_ID_STDIN, running, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool stdin_channel_poll(const omv_protocol_channel_t *channel) {
|
||||||
|
stdio_channel_context_t *ctx = channel->priv;
|
||||||
|
return ctx->script_running;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool stdout_channel_poll(const omv_protocol_channel_t *channel) {
|
||||||
|
stdio_channel_context_t *ctx = channel->priv;
|
||||||
|
return ringbuf_avail(&ctx->ringbuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t stdio_channel_size(const omv_protocol_channel_t *channel) {
|
||||||
|
stdio_channel_context_t *ctx = channel->priv;
|
||||||
|
return ringbuf_avail(&ctx->ringbuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int stdio_channel_read(const omv_protocol_channel_t *channel,
|
||||||
|
uint32_t offset, size_t size, void *data) {
|
||||||
|
stdio_channel_context_t *ctx = channel->priv;
|
||||||
|
|
||||||
|
size = OMV_MIN(size, ringbuf_avail(&ctx->ringbuf));
|
||||||
|
return !ringbuf_get_bytes(&ctx->ringbuf, data, size) ? size : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int stdio_channel_write(const omv_protocol_channel_t *channel,
|
||||||
|
uint32_t offset, size_t size, const void *data) {
|
||||||
|
stdio_channel_context_t *ctx = channel->priv;
|
||||||
|
|
||||||
|
if (offset == 0) {
|
||||||
|
vstr_reset(&ctx->vstrbuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
nlr_buf_t nlr;
|
||||||
|
if (!gc_is_locked() && nlr_push(&nlr) == 0) {
|
||||||
|
char *buf = vstr_add_len(&ctx->vstrbuf, size);
|
||||||
|
memcpy(buf, data, size);
|
||||||
|
nlr_pop();
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool stdin_channel_exec(const omv_protocol_channel_t *channel) {
|
||||||
|
stdio_channel_context_t *ctx = channel->priv;
|
||||||
|
|
||||||
|
if (!vstr_len(&ctx->vstrbuf)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
nlr_buf_t nlr;
|
||||||
|
if (nlr_push(&nlr) == 0) {
|
||||||
|
// Execute the script.
|
||||||
|
pyexec_vstr(&ctx->vstrbuf, true);
|
||||||
|
nlr_pop();
|
||||||
|
} else {
|
||||||
|
mp_obj_print_exception(&mp_plat_print, (mp_obj_t) nlr.ret_val);
|
||||||
|
}
|
||||||
|
|
||||||
|
vstr_reset(&ctx->vstrbuf);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int stdio_channel_ioctl(const omv_protocol_channel_t *channel, uint32_t cmd, size_t len, void *arg) {
|
||||||
|
stdio_channel_context_t *ctx = channel->priv;
|
||||||
|
|
||||||
|
switch (cmd) {
|
||||||
|
case OMV_CHANNEL_IOCTL_STDIN_STOP:
|
||||||
|
if (mp_interrupt_char != -1) {
|
||||||
|
mp_sched_vm_abort();
|
||||||
|
mp_sched_keyboard_interrupt();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case OMV_CHANNEL_IOCTL_STDIN_EXEC:
|
||||||
|
if (!vstr_len(&ctx->vstrbuf)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (mp_interrupt_char != -1) {
|
||||||
|
mp_sched_vm_abort();
|
||||||
|
mp_sched_keyboard_interrupt();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case OMV_CHANNEL_IOCTL_STDIN_RESET:
|
||||||
|
vstr_reset(&ctx->vstrbuf);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap MicroPython stdio functions to intercept REPL.
|
||||||
|
extern uintptr_t __real_mp_hal_stdio_poll(uintptr_t poll_flags);
|
||||||
|
uintptr_t __wrap_mp_hal_stdio_poll(uintptr_t poll_flags) {
|
||||||
|
if (!omv_protocol_is_active()) {
|
||||||
|
return __real_mp_hal_stdio_poll(poll_flags);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern mp_uint_t __real_mp_hal_stdout_tx_strn(const char *str, mp_uint_t len);
|
||||||
|
mp_uint_t __wrap_mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) {
|
||||||
|
if (!omv_protocol_is_active()) {
|
||||||
|
return __real_mp_hal_stdout_tx_strn(str, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
const omv_protocol_channel_t *channel = omv_protocol_find_channel(OMV_PROTOCOL_CHANNEL_ID_STDOUT);
|
||||||
|
stdio_channel_context_t *ctx = channel->priv;
|
||||||
|
|
||||||
|
// On overflow, reset the ring buffer, if this string fits
|
||||||
|
// entirely in the buffer, to recover from broken strings.
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
if (ringbuf_put(&ctx->ringbuf, str[i]) == -1 && len <= ctx->ringbuf.size) {
|
||||||
|
ctx->ringbuf.iget = 0;
|
||||||
|
ctx->ringbuf.iput = 0;
|
||||||
|
ringbuf_put(&ctx->ringbuf, str[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
const omv_protocol_channel_t omv_stdin_channel = {
|
||||||
|
.priv = &stdio_channel_ctx,
|
||||||
|
.id = OMV_PROTOCOL_CHANNEL_ID_STDIN,
|
||||||
|
.flags = OMV_PROTOCOL_CHANNEL_FLAG_WRITE | OMV_PROTOCOL_CHANNEL_FLAG_EXEC,
|
||||||
|
.name = "stdin",
|
||||||
|
.init = stdin_channel_init,
|
||||||
|
.poll = stdin_channel_poll,
|
||||||
|
.write = stdio_channel_write,
|
||||||
|
.ioctl = stdio_channel_ioctl,
|
||||||
|
.exec = stdin_channel_exec
|
||||||
|
};
|
||||||
|
|
||||||
|
const omv_protocol_channel_t omv_stdout_channel = {
|
||||||
|
.priv = &stdio_channel_ctx,
|
||||||
|
.id = OMV_PROTOCOL_CHANNEL_ID_STDOUT,
|
||||||
|
.flags = OMV_PROTOCOL_CHANNEL_FLAG_READ,
|
||||||
|
.name = "stdout",
|
||||||
|
.init = stdout_channel_init,
|
||||||
|
.poll = stdout_channel_poll,
|
||||||
|
.size = stdio_channel_size,
|
||||||
|
.read = stdio_channel_read,
|
||||||
|
};
|
135
protocol/omv_protocol_channel_stmusb.c
Normal file
135
protocol/omv_protocol_channel_stmusb.c
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2022-2024 OpenMV, LLC.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* OpenMV Protocol USB Channel.
|
||||||
|
*/
|
||||||
|
#if OMV_USB_STACK_STMUSB
|
||||||
|
#include "py/stream.h"
|
||||||
|
#include "py/mphal.h"
|
||||||
|
#include "py/ringbuf.h"
|
||||||
|
#include "py/runtime.h"
|
||||||
|
|
||||||
|
#include "omv_common.h"
|
||||||
|
#include "cmsis_gcc.h"
|
||||||
|
#include "omv_protocol.h"
|
||||||
|
#include "omv_boardconfig.h"
|
||||||
|
#undef MIN
|
||||||
|
#undef MAX
|
||||||
|
#include "usbd_cdc_msc_hid.h"
|
||||||
|
#include "usbd_cdc_interface.h"
|
||||||
|
|
||||||
|
#ifndef OMV_PROTOCOL_USB_CHANNEL_TIMEOUT_MS
|
||||||
|
#define OMV_PROTOCOL_USB_CHANNEL_TIMEOUT_MS (1500)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static bool usb_channel_active = false;
|
||||||
|
|
||||||
|
static size_t usb_channel_size(const omv_protocol_channel_t *channel) {
|
||||||
|
usbd_cdc_itf_t *cdc = usb_vcp_get(0);
|
||||||
|
return usbd_cdc_rx_num(cdc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool usb_channel_is_active(const omv_protocol_channel_t *channel) {
|
||||||
|
return usb_channel_active;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb_channel_read(const omv_protocol_channel_t *channel, uint32_t offset, size_t size, void *data) {
|
||||||
|
usbd_cdc_itf_t *cdc = usb_vcp_get(0);
|
||||||
|
return usbd_cdc_rx(cdc, data, size, OMV_PROTOCOL_USB_CHANNEL_TIMEOUT_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb_channel_write(const omv_protocol_channel_t *channel, uint32_t offset, size_t size, const void *data) {
|
||||||
|
usbd_cdc_itf_t *cdc = usb_vcp_get(0);
|
||||||
|
return usbd_cdc_tx(cdc, data, size, OMV_PROTOCOL_USB_CHANNEL_TIMEOUT_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usb_channel_task(mp_sched_node_t *node) {
|
||||||
|
if (usb_channel_active) {
|
||||||
|
omv_protocol_task();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int __real_mp_os_dupterm_rx_chr(void);
|
||||||
|
int __wrap_mp_os_dupterm_rx_chr(void) {
|
||||||
|
if (usb_channel_active) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return __real_mp_os_dupterm_rx_chr();
|
||||||
|
}
|
||||||
|
|
||||||
|
int8_t __real_usbd_cdc_receive(usbd_cdc_state_t *cdc_in, size_t len);
|
||||||
|
int8_t __wrap_usbd_cdc_receive(usbd_cdc_state_t *cdc_in, size_t len) {
|
||||||
|
static mp_sched_node_t usb_channel_node;
|
||||||
|
|
||||||
|
// Ensure the CDC is detached from REPL, if the protocol is active,
|
||||||
|
// before calling receive as it raises exceptions on interrupt chars.
|
||||||
|
usbd_cdc_itf_t *cdc = usb_vcp_get(0);
|
||||||
|
if (usb_channel_active && cdc->attached_to_repl) {
|
||||||
|
cdc->attached_to_repl = false;
|
||||||
|
cdc->flow |= USBD_CDC_FLOWCONTROL_RTS | USBD_CDC_FLOWCONTROL_CTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
int8_t ret = __real_usbd_cdc_receive(cdc_in, len);
|
||||||
|
if (usb_channel_active) {
|
||||||
|
mp_sched_schedule_node(&usb_channel_node, usb_channel_task);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int8_t __real_usbd_cdc_control(usbd_cdc_state_t *cdc_in, uint8_t cmd, uint8_t *pbuf, uint16_t length);
|
||||||
|
int8_t __wrap_usbd_cdc_control(usbd_cdc_state_t *cdc_in, uint8_t cmd, uint8_t *pbuf, uint16_t length) {
|
||||||
|
usbd_cdc_itf_t *cdc = usb_vcp_get(0);
|
||||||
|
int8_t ret = __real_usbd_cdc_control(cdc_in, cmd, pbuf, length);
|
||||||
|
|
||||||
|
if (cdc->bitrate != OMV_PROTOCOL_MAGIC_BAUDRATE ||
|
||||||
|
cdc->connect_state == USBD_CDC_CONNECT_STATE_DISCONNECTED) {
|
||||||
|
// Reattach to REPL
|
||||||
|
cdc->rx_buf_put = 0;
|
||||||
|
cdc->rx_buf_get = 0;
|
||||||
|
cdc->rx_buf_full = false;
|
||||||
|
cdc->tx_need_empty_packet = 0;
|
||||||
|
cdc->attached_to_repl = true;
|
||||||
|
cdc->flow &= ~USBD_CDC_FLOWCONTROL_CTS;
|
||||||
|
} else if (cdc->bitrate == OMV_PROTOCOL_MAGIC_BAUDRATE) {
|
||||||
|
// Detach from REPL
|
||||||
|
cdc->attached_to_repl = false;
|
||||||
|
cdc->flow |= USBD_CDC_FLOWCONTROL_RTS | USBD_CDC_FLOWCONTROL_CTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
usb_channel_active = !cdc->attached_to_repl;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// USB Channel
|
||||||
|
const omv_protocol_channel_t omv_usb_channel = {
|
||||||
|
.priv = NULL,
|
||||||
|
.id = OMV_PROTOCOL_CHANNEL_ID_TRANSPORT,
|
||||||
|
.name = "usb",
|
||||||
|
.flags = OMV_PROTOCOL_CHANNEL_FLAG_PHYSICAL,
|
||||||
|
.size = usb_channel_size,
|
||||||
|
.read = usb_channel_read,
|
||||||
|
.write = usb_channel_write,
|
||||||
|
.is_active = usb_channel_is_active
|
||||||
|
};
|
||||||
|
#endif // OMV_USB_STACK_STM
|
134
protocol/omv_protocol_channel_stream.c
Normal file
134
protocol/omv_protocol_channel_stream.c
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2022-2024 OpenMV, LLC.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* OpenMV Protocol Channels.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "omv_common.h"
|
||||||
|
#include "omv_protocol.h"
|
||||||
|
#include "omv_boardconfig.h"
|
||||||
|
#include "framebuffer.h"
|
||||||
|
|
||||||
|
|
||||||
|
static int stream_channel_init(const omv_protocol_channel_t *channel) {
|
||||||
|
return channel->unlock(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool stream_channel_poll(const omv_protocol_channel_t *channel) {
|
||||||
|
return channel->size(channel) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int stream_channel_lock(const omv_protocol_channel_t *channel) {
|
||||||
|
framebuffer_t *fb = framebuffer_get(FB_STREAM_ID);
|
||||||
|
size_t size = channel->size(channel);
|
||||||
|
// Attempt locking only if the stream is ready.
|
||||||
|
return size && mutex_try_lock(&fb->lock, MUTEX_TID_IDE) ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int stream_channel_unlock(const omv_protocol_channel_t *channel) {
|
||||||
|
framebuffer_t *fb = framebuffer_get(FB_STREAM_ID);
|
||||||
|
if (mutex_unlock(&fb->lock, MUTEX_TID_IDE)) {
|
||||||
|
// Reset header even if we don't hold the lock
|
||||||
|
memset(fb->raw_base, 0, sizeof(framebuffer_header_t));
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t stream_channel_size(const omv_protocol_channel_t *channel) {
|
||||||
|
framebuffer_t *fb = framebuffer_get(FB_STREAM_ID);
|
||||||
|
framebuffer_header_t *hdr = (framebuffer_header_t *) fb->raw_base;
|
||||||
|
size_t size = hdr->is_compressed ? hdr->size : (hdr->width * hdr->height * hdr->bpp);
|
||||||
|
// Return header size + stream data size
|
||||||
|
return !size ? 0 : (size + sizeof(framebuffer_header_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t stream_channel_shape(const omv_protocol_channel_t *channel, size_t shape[4]) {
|
||||||
|
framebuffer_t *fb = framebuffer_get(FB_STREAM_ID);
|
||||||
|
|
||||||
|
if (fb->is_compressed > 0) {
|
||||||
|
// Compressed: shape is (size,)
|
||||||
|
shape[0] = fb->size;
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
// Uncompressed: shape is (w, h, bpp)
|
||||||
|
shape[0] = fb->w;
|
||||||
|
shape[1] = fb->h;
|
||||||
|
shape[2] = fb->bpp;
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int stream_channel_ioctl(const omv_protocol_channel_t *channel, uint32_t cmd, size_t len, void *arg) {
|
||||||
|
union {
|
||||||
|
uint8_t bytes[16];
|
||||||
|
uint32_t args[4];
|
||||||
|
} u;
|
||||||
|
|
||||||
|
memcpy(u.bytes, arg, len);
|
||||||
|
framebuffer_t *fb = framebuffer_get(FB_STREAM_ID);
|
||||||
|
|
||||||
|
switch (cmd) {
|
||||||
|
case OMV_CHANNEL_IOCTL_STREAM_CTRL:
|
||||||
|
fb->enabled = u.args[0];
|
||||||
|
// Reset stream buffer state
|
||||||
|
mutex_init0(&fb->lock);
|
||||||
|
memset(fb->raw_base, 0, sizeof(framebuffer_header_t));
|
||||||
|
return 0;
|
||||||
|
case OMV_CHANNEL_IOCTL_STREAM_RAW_CFG:
|
||||||
|
fb->raw_w = u.args[0];
|
||||||
|
fb->raw_h = u.args[1];
|
||||||
|
return 0;
|
||||||
|
case OMV_CHANNEL_IOCTL_STREAM_RAW_CTRL:
|
||||||
|
fb->raw_enabled = u.args[0];
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const void *stream_channel_readp(const omv_protocol_channel_t *channel, uint32_t offset, size_t size) {
|
||||||
|
framebuffer_t *fb = framebuffer_get(FB_STREAM_ID);
|
||||||
|
size_t available = channel->size(channel);
|
||||||
|
if (offset + size > available) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
// Return pointer to framebuffer data starting from header
|
||||||
|
return (uint8_t *) fb->raw_base + offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
const omv_protocol_channel_t omv_stream_channel = {
|
||||||
|
.priv = NULL,
|
||||||
|
.id = OMV_PROTOCOL_CHANNEL_ID_STREAM,
|
||||||
|
.name = "stream",
|
||||||
|
.flags = OMV_PROTOCOL_CHANNEL_FLAG_READ |
|
||||||
|
OMV_PROTOCOL_CHANNEL_FLAG_LOCK |
|
||||||
|
OMV_PROTOCOL_CHANNEL_FLAG_STREAM,
|
||||||
|
.init = stream_channel_init,
|
||||||
|
.poll = stream_channel_poll,
|
||||||
|
.lock = stream_channel_lock,
|
||||||
|
.unlock = stream_channel_unlock,
|
||||||
|
.size = stream_channel_size,
|
||||||
|
.shape = stream_channel_shape,
|
||||||
|
.readp = stream_channel_readp,
|
||||||
|
.ioctl = stream_channel_ioctl
|
||||||
|
};
|
164
protocol/omv_protocol_channel_tinyusb.c
Normal file
164
protocol/omv_protocol_channel_tinyusb.c
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2022-2024 OpenMV, LLC.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* OpenMV Protocol USB Channel.
|
||||||
|
*/
|
||||||
|
#if OMV_USB_STACK_TINYUSB
|
||||||
|
#include "py/stream.h"
|
||||||
|
#include "py/mphal.h"
|
||||||
|
#include "py/ringbuf.h"
|
||||||
|
#include "py/runtime.h"
|
||||||
|
|
||||||
|
#include "omv_common.h"
|
||||||
|
#include "cmsis_gcc.h"
|
||||||
|
#include "omv_protocol.h"
|
||||||
|
#include "omv_boardconfig.h"
|
||||||
|
#include "tusb.h"
|
||||||
|
|
||||||
|
#ifndef OMV_PROTOCOL_USB_CHANNEL_TIMEOUT_MS
|
||||||
|
#define OMV_PROTOCOL_USB_CHANNEL_TIMEOUT_MS (1500)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static bool usb_channel_active;
|
||||||
|
|
||||||
|
static size_t usb_channel_size(const omv_protocol_channel_t *channel) {
|
||||||
|
if (tud_task_event_ready()) {
|
||||||
|
tud_task_ext(0, false);
|
||||||
|
}
|
||||||
|
return tud_cdc_available();
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb_channel_flush(const omv_protocol_channel_t *channel) {
|
||||||
|
if (tud_task_event_ready()) {
|
||||||
|
tud_task_ext(0, false);
|
||||||
|
}
|
||||||
|
return tud_cdc_write_flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool usb_channel_is_active(const omv_protocol_channel_t *channel) {
|
||||||
|
return usb_channel_active;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb_channel_read(const omv_protocol_channel_t *channel, uint32_t offset, size_t size, void *data) {
|
||||||
|
size_t bytes = 0;
|
||||||
|
uint32_t start_ms = mp_hal_ticks_ms();
|
||||||
|
while (bytes < size && !check_timeout_ms(start_ms, OMV_PROTOCOL_USB_CHANNEL_TIMEOUT_MS)) {
|
||||||
|
bytes += tud_cdc_read((uint8_t *) data + bytes, size - bytes);
|
||||||
|
if (tud_task_event_ready()) {
|
||||||
|
tud_task_ext(0, false);
|
||||||
|
}
|
||||||
|
if (bytes < size) {
|
||||||
|
mp_event_handle_nowait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb_channel_write(const omv_protocol_channel_t *channel, uint32_t offset, size_t size, const void *data) {
|
||||||
|
size_t bytes = 0;
|
||||||
|
uint32_t start_ms = mp_hal_ticks_ms();
|
||||||
|
while (bytes < size && !check_timeout_ms(start_ms, OMV_PROTOCOL_USB_CHANNEL_TIMEOUT_MS)) {
|
||||||
|
bytes += tud_cdc_write((uint8_t *) data + bytes, size - bytes);
|
||||||
|
if (tud_task_event_ready()) {
|
||||||
|
tud_task_ext(0, false);
|
||||||
|
}
|
||||||
|
if (bytes < size) {
|
||||||
|
mp_event_handle_nowait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usb_channel_task(mp_sched_node_t *node) {
|
||||||
|
tud_task_ext(0, false);
|
||||||
|
if (omv_protocol_is_active()) {
|
||||||
|
omv_protocol_task();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap MicroPython TinyUSB functions to run our own task.
|
||||||
|
void __real_tud_cdc_rx_cb(uint8_t itf);
|
||||||
|
void __wrap_tud_cdc_rx_cb(uint8_t itf) {
|
||||||
|
if (!omv_protocol_is_active()) {
|
||||||
|
__real_tud_cdc_rx_cb(itf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const *coding) {
|
||||||
|
usb_channel_active = (coding->bit_rate == OMV_PROTOCOL_MAGIC_BAUDRATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void __real_tud_cdc_line_state_cb(uint8_t instance, bool dtr, bool rts);
|
||||||
|
void __wrap_tud_cdc_line_state_cb(uint8_t instance, bool dtr, bool rts) {
|
||||||
|
cdc_line_coding_t coding;
|
||||||
|
tud_cdc_get_line_coding(&coding);
|
||||||
|
|
||||||
|
if (!dtr) {
|
||||||
|
usb_channel_active = false;
|
||||||
|
} else {
|
||||||
|
usb_channel_active = (coding.bit_rate == OMV_PROTOCOL_MAGIC_BAUDRATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
__real_tud_cdc_line_state_cb(instance, dtr, rts);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For the mimxrt, and nrf ports this replaces the weak USB IRQ handlers.
|
||||||
|
// For the RP2 port, this handler is installed in main.c
|
||||||
|
void OMV_USB1_IRQ_HANDLER(void) {
|
||||||
|
static mp_sched_node_t usb_channel_node;
|
||||||
|
|
||||||
|
dcd_int_handler(0);
|
||||||
|
// If there are any event to process, schedule a call to channel task.
|
||||||
|
if (tud_task_event_ready()) {
|
||||||
|
mp_sched_schedule_node(&usb_channel_node, usb_channel_task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef OMV_USB2_IRQ_HANDLER
|
||||||
|
void OMV_USB2_IRQ_HANDLER(void) {
|
||||||
|
static mp_sched_node_t usb_channel_node;
|
||||||
|
|
||||||
|
dcd_int_handler(1);
|
||||||
|
// If there are any event to process, schedule a call to channel task.
|
||||||
|
if (tud_task_event_ready()) {
|
||||||
|
mp_sched_schedule_node(&usb_channel_node, usb_channel_task);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif // OMV_USB2_IRQ_HANDLER
|
||||||
|
|
||||||
|
// USB Channel
|
||||||
|
const omv_protocol_channel_t omv_usb_channel = {
|
||||||
|
.priv = NULL,
|
||||||
|
.id = OMV_PROTOCOL_CHANNEL_ID_TRANSPORT,
|
||||||
|
.name = "usb",
|
||||||
|
.flags = OMV_PROTOCOL_CHANNEL_FLAG_PHYSICAL,
|
||||||
|
.size = usb_channel_size,
|
||||||
|
.read = usb_channel_read,
|
||||||
|
.write = usb_channel_write,
|
||||||
|
.flush = usb_channel_flush,
|
||||||
|
.is_active = usb_channel_is_active
|
||||||
|
};
|
||||||
|
#endif // OMV_USB_STACK_TINYUSB
|
115
protocol/omv_protocol_hw_caps.h
Normal file
115
protocol/omv_protocol_hw_caps.h
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025 OpenMV, LLC.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* OpenMV Protocol Hardware Capabilities Definitions
|
||||||
|
* This header provides macros for boards to easily define their hardware
|
||||||
|
* capabilities without manual bit manipulation.
|
||||||
|
*/
|
||||||
|
#ifndef __OMV_PROTOCOL_HW_CAPS_H__
|
||||||
|
#define __OMV_PROTOCOL_HW_CAPS_H__
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "cmsis_gcc.h"
|
||||||
|
|
||||||
|
#ifndef __PMU_NUM_EVENTCNT
|
||||||
|
#define __PMU_NUM_EVENTCNT 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
* Hardware Capability Bit Definitions
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
#define OMV_PROTOCOL_HW_CAPS_HAS_GPU (1U << 0) // Graphics Processing Unit
|
||||||
|
#define OMV_PROTOCOL_HW_CAPS_HAS_NPU (1U << 1) // Neural Processing Unit
|
||||||
|
#define OMV_PROTOCOL_HW_CAPS_HAS_ISP (1U << 2) // Image Signal Processor
|
||||||
|
#define OMV_PROTOCOL_HW_CAPS_HAS_VENC (1U << 3) // Video encoder present
|
||||||
|
#define OMV_PROTOCOL_HW_CAPS_HAS_JPEG (1U << 4) // JPEG encoder present
|
||||||
|
#define OMV_PROTOCOL_HW_CAPS_HAS_DRAM (1U << 5) // DRAM present
|
||||||
|
#define OMV_PROTOCOL_HW_CAPS_HAS_CRC (1U << 6) // Hardware-accelerated CRC
|
||||||
|
#define OMV_PROTOCOL_HW_CAPS_HAS_PMU (1U << 7) // Performance Monitoring Unit
|
||||||
|
#define OMV_PROTOCOL_HW_CAPS_PMU_EVENTCNT ((__PMU_NUM_EVENTCNT & 0xFF) << 8) // PMU number of event counters
|
||||||
|
#define OMV_PROTOCOL_HW_CAPS_HAS_WIFI (1U << 16) // WiFi module present
|
||||||
|
#define OMV_PROTOCOL_HW_CAPS_HAS_BT (1U << 17) // Bluetooth available
|
||||||
|
#define OMV_PROTOCOL_HW_CAPS_HAS_SD (1U << 18) // SD card slot available
|
||||||
|
#define OMV_PROTOCOL_HW_CAPS_HAS_ETH (1U << 19) // Ethernet interface
|
||||||
|
#define OMV_PROTOCOL_HW_CAPS_HAS_USB_HS (1U << 20) // USB High-Speed capable
|
||||||
|
#define OMV_PROTOCOL_HW_CAPS_HAS_MULTICORE (1U << 21) // Multi-core processor
|
||||||
|
|
||||||
|
|
||||||
|
// Prefix paste
|
||||||
|
#define OMV_PROTOCOL_HW_CAPS_(x) OMV_PROTOCOL_HW_CAPS_##x
|
||||||
|
|
||||||
|
// Expander helpers
|
||||||
|
#define EXPAND(x) x
|
||||||
|
|
||||||
|
// Recursive apply with OR
|
||||||
|
#define FE_1(f, x) f(x)
|
||||||
|
#define FE_2(f, x, ...) f(x) | FE_1(f, __VA_ARGS__)
|
||||||
|
#define FE_3(f, x, ...) f(x) | FE_2(f, __VA_ARGS__)
|
||||||
|
#define FE_4(f, x, ...) f(x) | FE_3(f, __VA_ARGS__)
|
||||||
|
#define FE_5(f, x, ...) f(x) | FE_4(f, __VA_ARGS__)
|
||||||
|
#define FE_6(f, x, ...) f(x) | FE_5(f, __VA_ARGS__)
|
||||||
|
#define FE_7(f, x, ...) f(x) | FE_6(f, __VA_ARGS__)
|
||||||
|
#define FE_8(f, x, ...) f(x) | FE_7(f, __VA_ARGS__)
|
||||||
|
#define FE_9(f, x, ...) f(x) | FE_8(f, __VA_ARGS__)
|
||||||
|
#define FE_10(f, x, ...) f(x) | FE_9(f, __VA_ARGS__)
|
||||||
|
#define FE_11(f, x, ...) f(x) | FE_10(f, __VA_ARGS__)
|
||||||
|
#define FE_12(f, x, ...) f(x) | FE_11(f, __VA_ARGS__)
|
||||||
|
#define FE_13(f, x, ...) f(x) | FE_12(f, __VA_ARGS__)
|
||||||
|
#define FE_14(f, x, ...) f(x) | FE_13(f, __VA_ARGS__)
|
||||||
|
#define FE_15(f, x, ...) f(x) | FE_14(f, __VA_ARGS__)
|
||||||
|
#define FE_16(f, x, ...) f(x) | FE_15(f, __VA_ARGS__)
|
||||||
|
#define FE_17(f, x, ...) f(x) | FE_16(f, __VA_ARGS__)
|
||||||
|
#define FE_18(f, x, ...) f(x) | FE_17(f, __VA_ARGS__)
|
||||||
|
#define FE_19(f, x, ...) f(x) | FE_18(f, __VA_ARGS__)
|
||||||
|
#define FE_20(f, x, ...) f(x) | FE_19(f, __VA_ARGS__)
|
||||||
|
#define FE_21(f, x, ...) f(x) | FE_20(f, __VA_ARGS__)
|
||||||
|
#define FE_22(f, x, ...) f(x) | FE_21(f, __VA_ARGS__)
|
||||||
|
#define FE_23(f, x, ...) f(x) | FE_22(f, __VA_ARGS__)
|
||||||
|
#define FE_24(f, x, ...) f(x) | FE_23(f, __VA_ARGS__)
|
||||||
|
#define FE_25(f, x, ...) f(x) | FE_24(f, __VA_ARGS__)
|
||||||
|
#define FE_26(f, x, ...) f(x) | FE_25(f, __VA_ARGS__)
|
||||||
|
#define FE_27(f, x, ...) f(x) | FE_26(f, __VA_ARGS__)
|
||||||
|
#define FE_28(f, x, ...) f(x) | FE_27(f, __VA_ARGS__)
|
||||||
|
#define FE_29(f, x, ...) f(x) | FE_28(f, __VA_ARGS__)
|
||||||
|
#define FE_30(f, x, ...) f(x) | FE_29(f, __VA_ARGS__)
|
||||||
|
#define FE_31(f, x, ...) f(x) | FE_30(f, __VA_ARGS__)
|
||||||
|
#define FE_32(f, x, ...) f(x) | FE_31(f, __VA_ARGS__)
|
||||||
|
|
||||||
|
// Dispatcher (up to 32 args)
|
||||||
|
#define GET_FE_MACRO(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \
|
||||||
|
_11, _12, _13, _14, _15, _16, _17, _18, _19, _20, \
|
||||||
|
_21, _22, _23, _24, _25, _26, _27, _28, _29, _30, \
|
||||||
|
_31, _32, NAME, ...) NAME
|
||||||
|
|
||||||
|
#define FOR_EACH(f, ...) \
|
||||||
|
EXPAND(GET_FE_MACRO(__VA_ARGS__, \
|
||||||
|
FE_32, FE_31, FE_30, FE_29, FE_28, FE_27, FE_26, FE_25, FE_24, FE_23, \
|
||||||
|
FE_22, FE_21, FE_20, FE_19, FE_18, FE_17, FE_16, FE_15, FE_14, FE_13, \
|
||||||
|
FE_12, FE_11, FE_10, FE_9, FE_8, FE_7, FE_6, FE_5, FE_4, FE_3, FE_2, FE_1 \
|
||||||
|
) (f, __VA_ARGS__))
|
||||||
|
|
||||||
|
// Main macro
|
||||||
|
#define OMV_PROTOCOL_HW_CAPS_MAKE(...) \
|
||||||
|
FOR_EACH(OMV_PROTOCOL_HW_CAPS_, __VA_ARGS__) | OMV_PROTOCOL_HW_CAPS_PMU_EVENTCNT
|
||||||
|
#endif // __OMV_PROTOCOL_HW_CAPS_H__
|
43
protocol/protocol.mk
Normal file
43
protocol/protocol.mk
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
#
|
||||||
|
# Copyright (C) 2025 OpenMV, LLC.
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
#
|
||||||
|
# OpenMV Protocol Makefile
|
||||||
|
|
||||||
|
PROTOCOL_SRC_C += \
|
||||||
|
omv_protocol.c \
|
||||||
|
omv_protocol_channel_stdio.c \
|
||||||
|
omv_protocol_channel_stream.c \
|
||||||
|
omv_protocol_channel_profile.c \
|
||||||
|
|
||||||
|
ifeq ($(OMV_USB_STACK_TINYUSB), 1)
|
||||||
|
CFLAGS += -DOMV_USB_STACK_TINYUSB=1
|
||||||
|
PROTOCOL_SRC_C += omv_protocol_channel_tinyusb.c
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(OMV_USB_STACK_STMUSB), 1)
|
||||||
|
CFLAGS += -DOMV_USB_STACK_STMUSB=1
|
||||||
|
PROTOCOL_SRC_C += omv_protocol_channel_stmusb.c
|
||||||
|
endif
|
||||||
|
|
||||||
|
CFLAGS += -I$(TOP_DIR)/protocol
|
||||||
|
OMV_FIRM_OBJ += $(addprefix $(BUILD)/protocol/, $(PROTOCOL_SRC_C:.c=.o))
|
||||||
|
|
Loading…
Reference in New Issue
Block a user