protocol: Introduce OMV protocol V2.

Signed-off-by: iabdalkader <i.abdalkader@gmail.com>
This commit is contained in:
iabdalkader 2025-09-06 17:05:20 +02:00
parent 91a01ee978
commit cc879d15b4
12 changed files with 2851 additions and 2 deletions

View File

@ -33,11 +33,9 @@ COMMON_SRC_C += \
omv_csi.c \
omv_crc.c \
pendsv.c \
tinyusb_debug.c \
trace.c \
umm_malloc.c \
unaligned_memcpy.c \
usbdbg.c \
vospi.c \
queue.c \
omv_profiler.c \

570
docs/protocol.md Normal file
View 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
View 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
View 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__

View 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__

View 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

View 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,
};

View 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

View 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
};

View 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

View 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
View 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))