rcbench
Servo and ESC test bench: panel, IOmcu and shared C
Loading...
Searching...
No Matches
pdmini.h File Reference

The WeAct PD Power Mini V1 Buck on its UART: the codec and a driver that talks to it one transaction at a time. More...

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

Go to the source code of this file.

Data Structures

struct  pdmini_io_t
 What the driver needs from the hardware. More...
struct  pdmini_status_t
 What the module has said, read back rather than assumed. More...
struct  pdmini_t
 Driver state: what is asked, what is known, and the transaction under way. More...

Macros

#define PDMINI_V_MIN_MV   1000u
 The module's range, from the vendor's page; not measured here.
#define PDMINI_V_MAX_MV   20000u
#define PDMINI_I_MIN_MA   50u
#define PDMINI_I_MAX_MA   3000u
#define PDMINI_HEADROOM_MV   500u
 The margin between the input the module reports and a live output's set point that the buck is taken to need: a buck cannot put out more than it is fed, and a module asked to shows ERR and needs a power cycle (seen on the bench, 2026-10-06, 5.88V asked from a 4.88V input).
#define PDMINI_CAP_MV   700u
 A voltage set point is kept this far under the input the module reports, rounded down to 10mV, and no lower than PDMINI_V_MIN_MV. 200mV above PDMINI_HEADROOM_MV, so a set point at the cap is not on the line its output is cut at: the input is read with the output off, and under load it drops by the current through the source and the cable. 200mV is 3000mA, PDMINI_I_MAX_MA, through 1m of 20 AWG copper out and back, 2 x 33mOhm.
#define PDMINI_WHO   "PD Power Mini"
 What WHO_AM_I's reply holds from this module: "WeAct Studio PD Power Mini V1 BUCK" in the bench station's notes of its 2026-09-11 run.
#define PDMINI_ATTACH_MS   5u
 Timing, from the vendor's working driver; none of it measured.
#define PDMINI_REPLY_MS   400u
#define PDMINI_BYTE_MS   60u
#define PDMINI_TXN_MS   600u
#define PDMINI_CONFIRM_MS   250u
#define PDMINI_WATCH_MS   1000u
#define PDMINI_GAP_MS   10u
#define PDMINI_DISPLAY_MS   100u
#define PDMINI_POLL_LATE_MS   400u
#define PDMINI_STATE_MS   500u
#define PDMINI_IDENTIFY_MS   1000u
#define PDMINI_SLOT_MS   1000u
#define PDMINI_RETRY_MS   2000u
#define PDMINI_FAILS   3u
#define PDMINI_ABSENT_TRIES   10u
#define PDMINI_INPUT_MISSES   3u
#define PDMINI_INPUT_RETRY_MS   5000u
#define PDMINI_SAG_READS   2u

Enumerations

enum  {
  PDMINI_OUTPUT_EN = 0x02 , PDMINI_OUTPUT_DATA = 0x04 , PDMINI_SYSTEM_RESET = 0x40 , PDMINI_WHO_AM_I = 0x81 ,
  PDMINI_READ_STATE = 0x82 , PDMINI_READ_ID = 0x83 , PDMINI_READ_DATA = 0x84 , PDMINI_READ_DISPLAY = 0x85 ,
  PDMINI_READ_INPUT = 0x8A
}
 Commands this driver sends. More...
enum  { PDMINI_MODE_NORMAL = 0 , PDMINI_MODE_CC = 1 , PDMINI_MODE_OC = 2 }
 READ_OUTPUT_STATE's bits 2..1.
enum  pdmini_frame_t { PDMINI_MORE = 0 , PDMINI_DONE , PDMINI_BAD }
 Whether buf (n bytes received) holds a whole reply to cmd. More...
enum  pdmini_check_t {
  PDMINI_CHECK_NONE = 0 , PDMINI_CHECK_ASKED , PDMINI_CHECK_SENT , PDMINI_CHECK_OFF ,
  PDMINI_CHECK_NOT_OFF
}
 What a state read asked for with pdmini_check_off() has shown. More...

Functions

uint8_t pdmini_crc8 (const uint8_t *p, size_t n)
 CRC8, polynomial 0x31, initial 0xFF, over n bytes.
pdmini_frame_t pdmini_reply_frame (uint8_t cmd, const uint8_t *buf, size_t n)
 Checks the bytes received so far against a reply to cmd.
void pdmini_init (pdmini_t *d, const pdmini_io_t *io, uint32_t now_ms)
 Set up a driver with nothing asked and nothing known.
void pdmini_check_off (pdmini_t *d)
 A state read sent from now on, ahead of the other readings: the answer is in pdmini_checked().
pdmini_check_t pdmini_checked (const pdmini_t *d)
 The answer to the last pdmini_check_off().
void pdmini_want (pdmini_t *d, bool output, uint16_t set_mv, uint16_t set_ma)
 What the output is to be.
void pdmini_restored (pdmini_t *d)
 Attached to a module whose state is not known – wiring restored after a restart, or newly given: it may be on. pdmini_may_be_on() says so until a state read shows it off, or until PDMINI_ABSENT_TRIES WHO_AM_I in a row go unanswered by a byte.
void pdmini_reset (pdmini_t *d)
 Restart the module with SYSTEM_RESET: sent once it has answered, its output reads off and nothing else waits; it is then asked who it is again, PDMINI_IDENTIFY_MS on.
void pdmini_want_off (pdmini_t *d)
 The output off, and no set points asked: the module's are left as they are until pdmini_want() names some.
void pdmini_rx (pdmini_t *d, uint8_t byte, uint32_t now_ms)
 A byte from the module.
void pdmini_step (pdmini_t *d, uint32_t now_ms)
 Run one pass of the driver.
const pdmini_status_t * pdmini_status (const pdmini_t *d)
 The driver's status, read back from the module.
bool pdmini_may_be_on (const pdmini_t *d)
 Whether the output is on or may be: it reads on, an ON waits to be sent or confirmed or went out unsettled, an OFF is owed to a module that went quiet with it on, or a module that has answered is not read off now.
uint16_t pdmini_cap_mv (uint32_t vin_mv)
 The highest voltage set point written for an input of vin_mv, as read.

Detailed Description

The WeAct PD Power Mini V1 Buck on its UART: the codec and a driver that talks to it one transaction at a time.

Implements the UART protocol of the WeAct PD Power Mini V1 Buck module. WeAct is a name or trademark of its owner; it is used here only to say which module this driver works with (NOTICE, "Trademarks").

The module's protocol, from the vendor's sheet uart_protocol_v1.1: a request is a command byte, its arguments and a CRC8 (polynomial 0x31, initial 0xFF); a read is answered with the command byte, its data and a CRC8. WHO_AM_I's reply ends in 0x0A by the sheet, and the driver takes either that or a CRC. A write is answered with nothing at all, so the driver confirms each one with the matching read: the output with READ_OUTPUT_STATE 250ms later – and reads no state in between, so an early read of a module still settling is not taken for the answer – a set point with READ_OUTPUT_DATA.

An OFF goes first, before anything else waiting: an ON or a set point not yet sent is dropped – an ON also when its set points change – a read under way other than the state's is left, and an OFF that does not take is written again without pause. An OFF asked for while an ON is being confirmed is written at once when a read-back has shown which argument means off; before that, the state is read back to back for 1000ms from the ON and the output switched off the moment it reads on. An ON that comes later than that is an output on while OFF is asked, switched off at the next state read: about 500ms on with a module answering at once, up to 1.8s with one answering up to 390ms late, and up to 2.1s while set points change faster than they are written, see below.

The readings come round on their periods: the display every 100ms, the state and the input every 500ms, the slot every 1000ms, with a module answering within 12ms. A module answering later stretches them, each transaction taking its reply time: at rest the input is read 850ms apart at 100ms late, 1.3s at 200ms and 2.5s at 390ms, the state up to 1.5s apart (host model). A reading PDMINI_POLL_LATE_MS past its due goes before the next set point, one at a time with a set-point job or a read for an ON between two, so set points changed faster than a write and its read-back take – a slider dragged – hold no reading back for long. The state read, and the input read before a write, may follow another reading straight away. The input is read before a set point write or an ON once it is PDMINI_STATE_MS old, and not between the reads for an ON. Set points and an ON reach a module answering up to 390ms late (host model, 40 start phases): one set point within 2.5s; an ON within 3.6s at set points already written, up to 7.5s right after a set point change. With replies 279ms or more late and a set point changed every 50ms, the display read stands 3.7s to 5.0s, past the 3s the panel allows (see the README); from 263ms it stands over 2.9s, which the panel's page read every 100ms can take past 3s. At rest it stands up to 2.5s at 390ms. The real module's reply time is not measured.

An ON waits for the set points: it is written only once the active slot reads back what is asked, read again straight before the ON together with which slot is active, so the output never comes on at the voltage it held before. A slot chosen or a set point turned on the module's own buttons in the 15ms between those reads and the ON is seen by the next slot read, at most 1000ms on, and put back.

Three state reads in a row that fail take the module for gone, however the other readings are answered: its output state is what matters.

A set point is kept PDMINI_CAP_MV under the input the module reports. A live output whose input reads under its set point and PDMINI_HEADROOM_MV on PDMINI_SAG_READS input reads in a row is switched off and held off (st.sagged) until an OFF is asked: a buck fed less than it is set to put out may show ERR and need a power cycle. The 200mV between the two lets the input drop under load without a cut. An ON asked while the input reads under 1500mV, with no room for the lowest set point, is not written and is held off the same way. A rule chosen without a bench measurement – whether a live module goes to ERR on a sagging input is not measured – so it may cut a run that would have survived. An input read that fails neither counts nor clears the run of low readings. While the output is on, the set point is not lowered to follow a falling input; the rule switches it off instead. From a sag to the cut takes about 1.0s with a module answering at once, 1.7s at 100ms late, 1.9s at 200ms and up to 4.7s up to 390ms, the input reads stretching as above (host model).

A state read can be asked for (pdmini_check_off()): one sent after the asking, whose answer says whether the output is off now. The SUPPLY page takes a wiring change from a module that has answered only on such a read: the module can switch itself on between two state reads.

A module that stops answering while its output is on may still be listening: while an OFF is asked for, it is sent blind straight after each WHO_AM_I that is answered by not one byte, once a second, until a state read shows the output off – only with an argument a read-back has shown to mean on, so the blind OFF cannot switch on an output that was off. A module that answers WHO_AM_I is read, never written blind, as it may be another one. Not closed: a module put in place of the quiet one while a WHO_AM_I is on its way, up to 410ms before the blind OFF, is sent that OFF, and one that reads OUTPUT_EN the other way round comes on until it has answered, been read and been switched off.

Four writes of OUTPUT_EN that do not take put the argument in doubt again, and both are tried, in case the module was swapped between two reads.

Which OUTPUT_EN argument means on is learnt from the module rather than taken from the sheet, which has it backwards: 1 first, the bench's and the vendor's Python's, then 0. It is kept across a module going quiet and answering again. It is learnt only from an output seen to come on after a write towards on – relied on once two ONs with it have taken, as the module's button or AUTO OUT can switch it on in the same moment – never from one seen to go off, which the module's overcurrent protection does by itself. The output is written only when it reads otherwise than asked, so an OFF goes only to an output that is on and the learning cannot switch on one that was off.

The UART is attached to the pins for one transaction and the pins are pulled-down inputs between them, because a TX idling high back-feeds an unpowered module through its RX and stops it starting. The io callbacks do that; the driver says when.

Non-blocking: the caller feeds received bytes to pdmini_rx() and calls pdmini_step() every pass. Pure C, host-tested against a modelled module; rp2350/pd_uart.c supplies the PIO UART.

SPDX-License-Identifier: MIT

Macro Definition Documentation

◆ PDMINI_ABSENT_TRIES

#define PDMINI_ABSENT_TRIES   10u

WHO_AM_I unanswered by a byte this many times, about 10s: a restored module is taken not to be there

◆ PDMINI_ATTACH_MS

#define PDMINI_ATTACH_MS   5u

Timing, from the vendor's working driver; none of it measured.

pins handed over before a request

◆ PDMINI_BYTE_MS

#define PDMINI_BYTE_MS   60u

and longer by this on every byte

◆ PDMINI_CAP_MV

#define PDMINI_CAP_MV   700u

A voltage set point is kept this far under the input the module reports, rounded down to 10mV, and no lower than PDMINI_V_MIN_MV. 200mV above PDMINI_HEADROOM_MV, so a set point at the cap is not on the line its output is cut at: the input is read with the output off, and under load it drops by the current through the source and the cable. 200mV is 3000mA, PDMINI_I_MAX_MA, through 1m of 20 AWG copper out and back, 2 x 33mOhm.

The drop on a real source and cable is not measured.

◆ PDMINI_CONFIRM_MS

#define PDMINI_CONFIRM_MS   250u

OUTPUT_EN to the read that confirms

◆ PDMINI_DISPLAY_MS

#define PDMINI_DISPLAY_MS   100u

how often the output is read

◆ PDMINI_FAILS

#define PDMINI_FAILS   3u

failed transactions in a row: gone

◆ PDMINI_GAP_MS

#define PDMINI_GAP_MS   10u

pins at rest between transactions

◆ PDMINI_HEADROOM_MV

#define PDMINI_HEADROOM_MV   500u

The margin between the input the module reports and a live output's set point that the buck is taken to need: a buck cannot put out more than it is fed, and a module asked to shows ERR and needs a power cycle (seen on the bench, 2026-10-06, 5.88V asked from a 4.88V input).

A live output whose input reads under its set point and this is switched off (PDMINI_SAG_READS). The margin the module needs is not measured; 4.00V from 4.85V worked.

◆ PDMINI_IDENTIFY_MS

#define PDMINI_IDENTIFY_MS   1000u

WHO_AM_I again while nothing answers

◆ PDMINI_INPUT_MISSES

#define PDMINI_INPUT_MISSES   3u

READ_INPUT_STATE unanswered this many times is not asked again until the module is identified again: the vendor's client has it from firmware v1.0.2.0 on

◆ PDMINI_INPUT_RETRY_MS

#define PDMINI_INPUT_RETRY_MS   5000u

and asked again this often after

◆ PDMINI_POLL_LATE_MS

#define PDMINI_POLL_LATE_MS   400u

a reading this far past its due goes before the next set-point write: set points changed faster than a write and its read-back take would otherwise hold the readings back

◆ PDMINI_REPLY_MS

#define PDMINI_REPLY_MS   400u

the window for a reply

◆ PDMINI_RETRY_MS

#define PDMINI_RETRY_MS   2000u

a write that did not take, again

◆ PDMINI_SAG_READS

#define PDMINI_SAG_READS   2u

input reads in a row under a live output's set point and PDMINI_HEADROOM_MV that switch it off; chosen, not measured

◆ PDMINI_SLOT_MS

#define PDMINI_SLOT_MS   1000u

the active slot read again

◆ PDMINI_STATE_MS

#define PDMINI_STATE_MS   500u

the output state and the input

◆ PDMINI_TXN_MS

#define PDMINI_TXN_MS   600u

but never past this from the request; a 37-byte reply is 20ms at 19200

◆ PDMINI_WATCH_MS

#define PDMINI_WATCH_MS   1000u

an ON cancelled before its argument is known, watched this long

◆ PDMINI_WHO

#define PDMINI_WHO   "PD Power Mini"

What WHO_AM_I's reply holds from this module: "WeAct Studio PD Power Mini V1 BUCK" in the bench station's notes of its 2026-09-11 run.

A reply without it is another device, and nothing is written to it.

Enumeration Type Documentation

◆ anonymous enum

anonymous enum

Commands this driver sends.

Enumerator
PDMINI_OUTPUT_EN 

write the output on or off

PDMINI_OUTPUT_DATA 

write the set points

PDMINI_SYSTEM_RESET 

restart the module

PDMINI_WHO_AM_I 

identify the module

PDMINI_READ_STATE 

read the output state

PDMINI_READ_ID 

read the module ID

PDMINI_READ_DATA 

read back the set points

PDMINI_READ_DISPLAY 

read the terminal voltage and current

PDMINI_READ_INPUT 

read the input state

◆ pdmini_check_t

What a state read asked for with pdmini_check_off() has shown.

Enumerator
PDMINI_CHECK_NONE 

none asked

PDMINI_CHECK_ASKED 

asked, no state read sent since

PDMINI_CHECK_SENT 

a state read sent since, not yet answered

PDMINI_CHECK_OFF 

it showed the output off, and nothing that may be on (pdmini_may_be_on())

PDMINI_CHECK_NOT_OFF 

it showed the output on, or something that may be on, or it failed, or the module was not identified to be read

◆ pdmini_frame_t

Whether buf (n bytes received) holds a whole reply to cmd.

Enumerator
PDMINI_MORE 

not yet

PDMINI_DONE 

a whole reply, its check passed

PDMINI_BAD 

a whole reply that fails its check, or junk

Function Documentation

◆ pdmini_cap_mv()

uint16_t pdmini_cap_mv ( uint32_t vin_mv)

The highest voltage set point written for an input of vin_mv, as read.

vin_mv - PDMINI_CAP_MV rounded down to 10mV, at least PDMINI_V_MIN_MV (an input under PDMINI_V_MIN_MV + PDMINI_CAP_MV, 1700mV, gets the 1000mV floor, and one under 1500mV is held off), at most PDMINI_V_MAX_MV. The caller treats an input not known as PDMINI_V_MAX_MV.

Parameters
vin_mvInput voltage as read, in mV.
Returns
The highest voltage set point for that input, in mV.

◆ pdmini_check_off()

void pdmini_check_off ( pdmini_t * d)

A state read sent from now on, ahead of the other readings: the answer is in pdmini_checked().

It waits for the transaction under way and for an OFF owed, so at most about two transactions, 1210ms.

Parameters
dDriver state.

◆ pdmini_checked()

pdmini_check_t pdmini_checked ( const pdmini_t * d)

The answer to the last pdmini_check_off().

Parameters
dDriver state.
Returns
The state of the check.

◆ pdmini_crc8()

uint8_t pdmini_crc8 ( const uint8_t * p,
size_t n )

CRC8, polynomial 0x31, initial 0xFF, over n bytes.

Parameters
pBytes to check.
nNumber of bytes in p.
Returns
The CRC8 of the bytes.

◆ pdmini_init()

void pdmini_init ( pdmini_t * d,
const pdmini_io_t * io,
uint32_t now_ms )

Set up a driver with nothing asked and nothing known.

Parameters
dDriver state to initialise.
ioHardware callbacks; copied.
now_msCurrent time, in milliseconds.

◆ pdmini_may_be_on()

bool pdmini_may_be_on ( const pdmini_t * d)

Whether the output is on or may be: it reads on, an ON waits to be sent or confirmed or went out unsettled, an OFF is owed to a module that went quiet with it on, or a module that has answered is not read off now.

Its wiring is not to be taken from under it while this holds.

Parameters
dDriver state.
Returns
true while the output is, or may be, on.

◆ pdmini_reply_frame()

pdmini_frame_t pdmini_reply_frame ( uint8_t cmd,
const uint8_t * buf,
size_t n )

Checks the bytes received so far against a reply to cmd.

Parameters
cmdCommand byte the reply answers.
bufBytes received so far.
nNumber of bytes in buf.
Returns
PDMINI_MORE, PDMINI_DONE or PDMINI_BAD.

◆ pdmini_reset()

void pdmini_reset ( pdmini_t * d)

Restart the module with SYSTEM_RESET: sent once it has answered, its output reads off and nothing else waits; it is then asked who it is again, PDMINI_IDENTIFY_MS on.

Parameters
dDriver state.

◆ pdmini_restored()

void pdmini_restored ( pdmini_t * d)

Attached to a module whose state is not known – wiring restored after a restart, or newly given: it may be on. pdmini_may_be_on() says so until a state read shows it off, or until PDMINI_ABSENT_TRIES WHO_AM_I in a row go unanswered by a byte.

Parameters
dDriver state.

◆ pdmini_rx()

void pdmini_rx ( pdmini_t * d,
uint8_t byte,
uint32_t now_ms )

A byte from the module.

Parameters
dDriver state.
byteReceived byte.
now_msCurrent time, in milliseconds.

◆ pdmini_status()

const pdmini_status_t * pdmini_status ( const pdmini_t * d)

The driver's status, read back from the module.

Parameters
dDriver state.
Returns
Pointer to the status inside d.

◆ pdmini_step()

void pdmini_step ( pdmini_t * d,
uint32_t now_ms )

Run one pass of the driver.

Call every pass.

Parameters
dDriver state.
now_msCurrent time, in milliseconds.

◆ pdmini_want()

void pdmini_want ( pdmini_t * d,
bool output,
uint16_t set_mv,
uint16_t set_ma )

What the output is to be.

Set points are clamped to the module's range; switching off is the first thing done whatever else is waiting. An output switched off because its set points would not take stays off, whatever is asked, until an OFF is asked – and so does one the module switched off by itself while ON was asked (st.tripped), and one switched off for a sagging input (st.sagged).

Parameters
dDriver state.
outputtrue to ask for the output on.
set_mvVoltage set point, in mV.
set_maCurrent set point, in mA.

◆ pdmini_want_off()

void pdmini_want_off ( pdmini_t * d)

The output off, and no set points asked: the module's are left as they are until pdmini_want() names some.

Parameters
dDriver state.