AnasSarkiz/usb-c-pd-brushed-motor
USB-C PD-powered motor controller with protected input, selectable 5/9/12 V buck supply, MCU qualification, PWM H-bridge drive, overvoltage sensing, regenerative energy-dump resistors, and safety interlocks.
- Version
- 0.0.1-a48-cloud-pipeline9-unrouted-c41d79a-305b1e7a
- License
- unset
- Stars
- 0
firmware/stusb4500_rx.c
#include "stusb4500_rx.h"
/* ST's reference warns of about 3ms before Accept overwrites RX_DATA_OBJ.
* 2.5ms is a proposed conservative software deadline, not a measured guarantee. */
#define CAPTURE_MAX_AGE_US 2500u
#define PRT_STATUS_REG 0x16u
#define RX_BYTE_COUNT_REG 0x30u
#define PRT_RECEIVED_MASK 0x04u
/* Bits 1/7 are named in ST's reference header but reserved in UM2650.
* Conservatively reject them; do not treat their semantics as qualified. */
#define PRT_RESET_TX_ERROR_MASK 0x83u
static uint16_t little_endian_header(const uint8_t *bytes) {
return (uint16_t)((uint16_t)bytes[0] | (uint16_t)bytes[1] << 8);
}
static uint32_t little_endian_pdo(const uint8_t *bytes) {
return (uint32_t)bytes[0] | (uint32_t)bytes[1] << 8 |
(uint32_t)bytes[2] << 16 | (uint32_t)bytes[3] << 24;
}
static enum stusb_message_kind message_kind(uint16_t header) {
const uint8_t type = header & 31u;
const uint8_t count = (header >> 12) & 7u;
if (count) return type == 1 ? STUSB_MESSAGE_SOURCE_CAPABILITIES : STUSB_MESSAGE_OTHER;
switch (type) {
case 3: return STUSB_MESSAGE_ACCEPT;
case 4: return STUSB_MESSAGE_REJECT;
case 6: return STUSB_MESSAGE_PS_RDY;
case 12: return STUSB_MESSAGE_WAIT;
case 13: return STUSB_MESSAGE_SOFT_RESET;
default: return STUSB_MESSAGE_OTHER;
}
}
enum stusb_rx_result stusb_capture_message(const struct stusb_bus *bus,
const struct stusb_capture *capture) {
if (!capture || !capture->message) return STUSB_RX_INVALID_ARGUMENT;
*capture->message = (struct stusb_message){0};
if (!bus || !bus->read || !bus->now_us) return STUSB_RX_INVALID_ARGUMENT;
if (bus->now_us(bus->context) - capture->alert_us > CAPTURE_MAX_AGE_US)
return STUSB_RX_LATE;
const uint32_t deadline_us = capture->alert_us + CAPTURE_MAX_AGE_US;
uint8_t protocol_status = 0;
const struct stusb_read protocol_read = {
.register_address=PRT_STATUS_REG, .byte_count=1,
.bytes=&protocol_status, .deadline_us=deadline_us
};
if (!bus->read(bus->context, &protocol_read)) return STUSB_RX_IO_ERROR;
if (bus->now_us(bus->context) - capture->alert_us > CAPTURE_MAX_AGE_US)
return STUSB_RX_LATE;
if (protocol_status & PRT_RESET_TX_ERROR_MASK) return STUSB_RX_RESET_OR_TX_ERROR;
if (!(protocol_status & PRT_RECEIVED_MASK)) return STUSB_RX_NO_MESSAGE;
/* One contiguous burst: byte count, 16-bit header, and all seven objects.
* 0x30..0x4e avoids the incorrect OBJ5/6 aliases in ST's older header. */
uint8_t received_bytes[3 + 4 * PD_MAX_OBJECTS] = {0};
const struct stusb_read frame_read = {
.register_address=RX_BYTE_COUNT_REG, .byte_count=sizeof received_bytes,
.bytes=received_bytes, .deadline_us=deadline_us
};
if (!bus->read(bus->context, &frame_read)) return STUSB_RX_IO_ERROR;
if (bus->now_us(bus->context) - capture->alert_us > CAPTURE_MAX_AGE_US)
return STUSB_RX_LATE;
uint8_t checked_prefix[3] = {0};
const struct stusb_read prefix_read = {
.register_address=RX_BYTE_COUNT_REG, .byte_count=sizeof checked_prefix,
.bytes=checked_prefix, .deadline_us=deadline_us
};
if (!bus->read(bus->context, &prefix_read)) return STUSB_RX_IO_ERROR;
const uint32_t captured_us = bus->now_us(bus->context);
if (captured_us - capture->alert_us > CAPTURE_MAX_AGE_US) return STUSB_RX_LATE;
for (unsigned i=0; i<sizeof checked_prefix; ++i)
if (checked_prefix[i] != received_bytes[i]) return STUSB_RX_CHANGED;
/* PRT_STATUS is read-to-clear (UM2650 rev2, 3.17). A new receive
* indication since the first read invalidates even an identical header.
* This guards same-header payload replacement; hardware timing still
* requires validation. Do not infer atomic RX-buffer reads from it. */
if (!bus->read(bus->context, &protocol_read)) return STUSB_RX_IO_ERROR;
const uint32_t validated_us = bus->now_us(bus->context);
if (validated_us - capture->alert_us > CAPTURE_MAX_AGE_US) return STUSB_RX_LATE;
if (protocol_status & PRT_RESET_TX_ERROR_MASK) return STUSB_RX_RESET_OR_TX_ERROR;
if (protocol_status & PRT_RECEIVED_MASK) return STUSB_RX_CHANGED;
const uint16_t header = little_endian_header(&received_bytes[1]);
const uint8_t object_count = (header >> 12) & 7u;
const uint8_t revision = (header >> 6) & 3u;
/* ST's reference checks RX_BYTE_CNT only for data messages. Its reset
* behavior for a zero-object control frame is not documented in UM2650;
* stale count/object bytes must never become control-message payload. */
if (object_count && received_bytes[0] != object_count * 4u)
return STUSB_RX_MALFORMED;
/* Accept source-role non-extended PD2/PD3 headers only. This does not
* establish SOP routing: UM2650 reserves PHY_STATUS at 0x17. Packet
* routing, ALERT clearing and attach/reset provenance remain adapter work. */
if ((header & 0x8000u) || (revision != 1 && revision != 2))
return STUSB_RX_UNSUPPORTED;
if (!(header & 0x0100u)) return STUSB_RX_MALFORMED;
struct stusb_message message = {
.kind=message_kind(header), .header=header,
.message_id=(header >> 9) & 7u, .object_count=object_count,
.alert_us=capture->alert_us, .captured_us=validated_us
};
if (message.kind == STUSB_MESSAGE_SOURCE_CAPABILITIES)
for (unsigned i=0; i<object_count; ++i)
message.source_pdos[i] = little_endian_pdo(&received_bytes[3 + i*4]);
*capture->message = message;
return STUSB_RX_OK;
}