mirror of
https://github.com/eddyem/stm32samples.git
synced 2026-10-01 22:30:32 +03:00
559 lines
20 KiB
Markdown
559 lines
20 KiB
Markdown
# A useful thing made of a Chinese FX3U clone
|
||
|
||
Device works over RS-232 (default 115200 8N1), CAN bus (default 250 kbit/s), or MODBUS-RTU (default
|
||
9600 8N1).
|
||
|
||
Full pinout table is available in `hardware.c`.
|
||
|
||
The firmware help output also prints the build number and build date from `version.inc`.
|
||
|
||
## Startup diagnostics
|
||
|
||
On power-up or reset the firmware prints a startup banner over USART1:
|
||
|
||
```
|
||
START
|
||
IWDGRSTF=1 # reset occurred due to independent watchdog
|
||
SFTRSTF=1 # software reset (NVIC_SystemReset)
|
||
PORRSTF=1 # power-on / power-down reset
|
||
PINRSTF=1 # reset via NRST pin
|
||
```
|
||
|
||
Only the flags that were actually set are printed. After printing, all reset flags are cleared.
|
||
|
||
## Hardware notes
|
||
|
||
### Inputs (X)
|
||
|
||
X8 is not a screw terminal — it is the on-board "Prog" pushbutton (PB2).
|
||
X9 is absent. Bits of the `inchannels` mask correspond to these positions.
|
||
|
||
| Ch | Pin | Notes |
|
||
|-----|------|-------|
|
||
| X0 | PB13 | |
|
||
| X1 | PB14 | |
|
||
| X2 | PB11 | |
|
||
| X3 | PB12 | |
|
||
| X4 | PE15 | |
|
||
| X5 | PB10 | |
|
||
| X6 | PE13 | |
|
||
| X7 | PE14 | |
|
||
| X8 | PB2 | on-board "Prog" button |
|
||
| X9 | — | absent |
|
||
| X10 | PE11 | |
|
||
| X11 | PE12 | |
|
||
| X12 | PE9 | |
|
||
| X13 | PE10 | |
|
||
| X14 | PE7 | |
|
||
| X15 | PE8 | |
|
||
|
||
### Outputs (Y)
|
||
|
||
Y8 and Y9 are absent.
|
||
|
||
| Ch | Pin |
|
||
|-----|------|
|
||
| Y0 | PC9 |
|
||
| Y1 | PC8 |
|
||
| Y2 | PA8 |
|
||
| Y3 | PA0 |
|
||
| Y4 | PB3 |
|
||
| Y5 | PD12 |
|
||
| Y6 | PB15 |
|
||
| Y7 | PA7 |
|
||
| Y8 | — |
|
||
| Y9 | — |
|
||
| Y10 | PA6 |
|
||
| Y11 | PA2 |
|
||
|
||
### On-board LED
|
||
|
||
"RUN" LED is on PD10. **Active low**: `led` returns the logical state (`1` when the LED is on,
|
||
`0` when off).
|
||
|
||
### ADC channels
|
||
|
||
| № | Enum | Pin / source | Meaning |
|
||
|---|--------------|--------------|---------|
|
||
| 0 | `ADC_CH_0` | PA1 / adc1 | voltage input, up to 11 V |
|
||
| 1 | `ADC_CH_1` | PA3 / adc3 | voltage input, up to 11 V |
|
||
| 2 | `ADC_CH_2` | PC4 / adc14 | voltage input |
|
||
| 3 | `ADC_CH_3` | PC5 / adc15 | current input |
|
||
| 4 | `ADC_CH_4` | PC0 / adc10 | current input, 0..20 mA |
|
||
| 5 | `ADC_CH_5` | PC1 / adc11 | current input |
|
||
| 6 | `ADC_POT0` | PC2 / adc12 | right on-board potentiometer |
|
||
| 7 | `ADC_POT1` | PC3 / adc13 | left on-board potentiometer |
|
||
| 8 | `ADC_CH_TSEN`| internal | MCU temperature sensor |
|
||
| 9 | `ADC_CH_VDD` | internal | Vdd reference |
|
||
|
||
Each channel is sampled continuously in scan mode via DMA into a circular buffer of `9 ×
|
||
ADC_CHANNELS` values. The getter returns the median of the last 9 samples per channel. Reported raw
|
||
values are 12-bit (0..4095).
|
||
|
||
The `mcutemp` command returns MCU temperature in `°C × 10` (as `int32_t`).
|
||
|
||
## Runtime parameters
|
||
|
||
### Watchdog
|
||
|
||
IWDG prescaler `/4` (LSI ≈ 40 kHz) with reload 1250 → about **125 ms** watchdog timeout. Refreshed
|
||
in every main-loop iteration, in DMA/send wait loops and during flash writes. Any hang longer than
|
||
that triggers a reset.
|
||
|
||
### CAN timeouts
|
||
|
||
- Mailbox wait inside `CAN_send()`: `SEND_TIMEOUT_MS / 10` = **10 ms**.
|
||
- High-level send loops (command reply, ESW notifications): up to `SEND_TIMEOUT_MS` = **100 ms**.
|
||
If a message cannot be queued, `error=canbusy` is printed to USART.
|
||
|
||
### Buffer sizes
|
||
|
||
| Subsystem | Macro | Value | Notes |
|
||
|--------------|----------------------|-------|-------|
|
||
| USART input | `UARTBUFSZI` | 196 | longer lines are dropped; firmware prints `USART IN buffer overflow!` |
|
||
| USART output | `UARTBUFSZO` | 256 | |
|
||
| CAN RX queue | `CAN_INMESSAGE_SIZE` | 8 | extra messages are dropped silently |
|
||
| Modbus RX | `MODBUSBUFSZI` | 68 | |
|
||
| Modbus TX | `MODBUSBUFSZO` | 64 | |
|
||
|
||
## Serial protocol
|
||
|
||
Every command is a string terminated by `\n`. General syntax:
|
||
|
||
```
|
||
command[number][=value]
|
||
```
|
||
|
||
- `command` — command name (letters/digits);
|
||
- `number` — optional parameter number, 0..127;
|
||
- `=value` — optional setter value.
|
||
|
||
Numbers are parsed by `getnum()` and accept decimal (`123`), hexadecimal (`0x7B`), octal (`0173`)
|
||
and binary (`0b1111011`). Signed values (`getint()`) allow a leading `-`.
|
||
|
||
Values in parentheses after a flag command is its bit number in the whole `uint32_t`. E.g. to reset
|
||
flag `f_relay_inverted` you can call `f_relay_inverted=0` or `flags2=0`.
|
||
|
||
```
|
||
commands format: parameter[number][=setter]
|
||
parameter [CAN idx] - help
|
||
--------------------------
|
||
|
||
CAN bus commands:
|
||
canbuserr - print all CAN bus errors (a lot of if not connected)
|
||
cansniff - switch CAN sniffer mode
|
||
s - send CAN message: ID 0..8 data bytes
|
||
|
||
Configuration:
|
||
bounce [14] - set/get anti-bounce timeout (ms, max: 1000)
|
||
canid [6] - set both (in/out) CAN ID / get in CAN ID
|
||
canidin [7] - get/set input CAN ID
|
||
canidout [8] - get/set output CAN ID
|
||
canspeed [5] - get/set CAN speed (bps)
|
||
dumpconf - dump current configuration
|
||
eraseflash [10] - erase all flash storage
|
||
f_relay_inverted (2) - inverted state between relay and inputs
|
||
f_send_esw_can (0) - change of IN will send status over CAN with `canidin`
|
||
f_send_relay_can (1) - change of IN will send also CAN command to change OUT with `canidout`
|
||
f_send_relay_modbus (3) - change of IN will send also MODBUS command to change OUT with `modbusidout` (only for master!)
|
||
flags [17] - set/get configuration flags (as one U32 without parameter or Nth bit with)
|
||
modbusid [20] - set/get modbus slave ID (1..247) or set it master (0)
|
||
modbusidout [21] - set/get modbus slave ID (0..247) to send relay commands
|
||
modbusspeed [22] - set/get modbus speed (1200..115200)
|
||
saveconf [9] - save configuration
|
||
usartspeed [15] - get/set USART1 speed
|
||
|
||
IN/OUT:
|
||
adc [4] - get raw ADC value for the given channel (0..9)
|
||
esw [12] - anti-bounce read inputs
|
||
eswnow [13] - read current inputs' state
|
||
led [16] - work with onboard LED
|
||
relay [11] - get/set relay state (0 - off, 1 - on)
|
||
|
||
Other commands:
|
||
inchannels [18] - get u32 with bits set on supported IN channels
|
||
mcutemp [3] - get MCU temperature (*10degrC)
|
||
modbus - send modbus request with format "slaveID fcode regaddr nregs [N data]", to send zeros you can omit rest of 'data'
|
||
modbusraw - send RAW modbus request (will send up to 62 bytes + calculated CRC)
|
||
outchannels [19] - get u32 with bits set on supported OUT channels
|
||
reset [1] - reset MCU
|
||
time [2] - get/set time (1ms, 32bit)
|
||
wdtest - test watchdog
|
||
```
|
||
|
||
Value in square brackets is the CAN bus command code (see below).
|
||
|
||
### Notes on specific commands
|
||
|
||
#### `bounce`
|
||
|
||
`bouncetime` (default 50 ms) is not a classical debounce delay — it is the **per-input sampling
|
||
interval**. Once an input has been sampled, it will not be re-read until `bouncetime` milliseconds
|
||
have elapsed. Worst-case reaction time for an input change is therefore up to `bouncetime` ms;
|
||
changes within that window are ignored (which is the debounce behaviour itself).
|
||
|
||
#### `s`
|
||
|
||
Send a CAN message. All numbers are space-separated; the first is the CAN ID (0..0x7FF), the
|
||
remaining 0..8 numbers are data bytes. Numbers may be given in any format accepted by `getnum()`.
|
||
|
||
```
|
||
s 0x123 0x11 0x22 0x33
|
||
s 291 1 2 3 4
|
||
```
|
||
|
||
On invalid arguments the firmware prints `error=badpar` / `error=badval` / `error=wronglen` and
|
||
sends nothing.
|
||
|
||
#### `cansniff`
|
||
|
||
When enabled, every received CAN frame is printed to USART in the format:
|
||
|
||
```
|
||
<time_ms> #<ID> <b0> <b1> ...
|
||
```
|
||
|
||
All fields are hexadecimal except `<time_ms>`. While messages are being received, the regular
|
||
periodic USART keep-alive is suppressed.
|
||
|
||
#### `adc`
|
||
|
||
Accepts a parameter number 0..9 (`ADC_CHANNELS - 1`). Numbers outside this range return
|
||
`error=badpar`.
|
||
|
||
#### `flags`
|
||
|
||
With a parameter number (0..`MAX_FLAG_BITNO` = 0..3): sets/reads the Nth bit only. Without a
|
||
parameter ("no par", 0x7F): operates on the whole `uint32_t`. Bits above `MAX_FLAG_BITNO` return
|
||
`error=badpar`.
|
||
|
||
### Default configuration
|
||
|
||
Flash storage is empty after flashing; on first boot `flashstorage_init()` returns `currentconfidx
|
||
= -1` and the firmware uses `USERCONF_INITIALIZER` from `flash.c`:
|
||
|
||
```
|
||
CANspeed = 250000
|
||
CANIDin = 1
|
||
CANIDout = 2
|
||
usartspeed = 115200
|
||
bouncetime = 50
|
||
modbusID = 1
|
||
modbusIDout = 2
|
||
modbusspeed = 9600
|
||
flags = { sw_send_relay_inv = 1 }
|
||
```
|
||
|
||
After the first `saveconf`, these defaults are replaced by the stored record.
|
||
|
||
## CAN bus protocol
|
||
|
||
Default speed is 250 kbit/s. Default CAN IDs are 1 (input) and 2 (output) for a slave. **All
|
||
multi-byte data is little-endian.**
|
||
|
||
| Byte(s) | Meaning |
|
||
|---------|---------|
|
||
| 0, 1 | `uint16_t` command code (see table below) |
|
||
| 2 | `uint8_t` parameter number: 0..126, ORed with 0x80 for setter, 127 = "no parameter" |
|
||
| 3 | `uint8_t` error code (only in device answers) |
|
||
| 4..7 | `int32_t` data |
|
||
|
||
When the device receives a CAN packet addressed to its own ID or to ID = 0 ("broadcast"), it
|
||
performs the requested action and sends an answer (usually a getter reply). If the command cannot
|
||
be executed or carries bad data, the device returns the same packet with the error code inserted
|
||
into byte 3.
|
||
|
||
Getters may be requested by a 3-byte packet (command code + parameter). "No parameter" (0x7F) in
|
||
some commands means "all data" — e.g. get/set all relays or get all inputs.
|
||
|
||
### CAN bus error codes (byte 3 of the answer)
|
||
|
||
| Code | Name | Meaning |
|
||
|------|------------------|---------|
|
||
| 0 | `ERR_OK` | all OK |
|
||
| 1 | `ERR_BADPAR` | wrong parameter |
|
||
| 2 | `ERR_BADVAL` | value out of range |
|
||
| 3 | `ERR_WRONGLEN` | wrong message length (for setter or where a parameter is required) |
|
||
| 4 | `ERR_BADCMD` | unknown command code |
|
||
| 5 | `ERR_CANTRUN` | cannot run the command (bad parameters or other reason) |
|
||
|
||
Bus-level errors (stuff/form/ack/bit/CRC, bus-off, error-passive, error-warning) are not reported
|
||
in byte 3. They are printed over USART by `CAN_printerr()` when the `canbuserr` printer is enabled:
|
||
|
||
```
|
||
Receive error counter: <n>
|
||
Transmit error counter: <n>
|
||
Last error code: <name>
|
||
[Bus off] [Passive error limit] [Error counter limit]
|
||
```
|
||
|
||
### CAN command codes
|
||
|
||
| Code | Enum | Text command |
|
||
|------|------|--------------|
|
||
| 0 | `CMD_PING` | (ping) |
|
||
| 1 | `CMD_RESET` | `reset` |
|
||
| 2 | `CMD_TIME` | `time` |
|
||
| 3 | `CMD_MCUTEMP` | `mcutemp` |
|
||
| 4 | `CMD_ADCRAW` | `adc` |
|
||
| 5 | `CMD_CANSPEED` | `canspeed` |
|
||
| 6 | `CMD_CANID` | `canid` |
|
||
| 7 | `CMD_CANIDin` | `canidin` |
|
||
| 8 | `CMD_CANIDout` | `canidout` |
|
||
| 9 | `CMD_SAVECONF` | `saveconf` |
|
||
| 10 | `CMD_ERASESTOR` | `eraseflash` |
|
||
| 11 | `CMD_RELAY` | `relay` |
|
||
| 12 | `CMD_GETESW` | `esw` |
|
||
| 13 | `CMD_GETESWNOW` | `eswnow` |
|
||
| 14 | `CMD_BOUNCE` | `bounce` |
|
||
| 15 | `CMD_USARTSPEED` | `usartspeed` |
|
||
| 16 | `CMD_LED` | `led` |
|
||
| 17 | `CMD_FLAGS` | `flags` |
|
||
| 18 | `CMD_INCHNLS` | `inchannels` |
|
||
| 19 | `CMD_OUTCHNLS` | `outchannels` |
|
||
| 20 | `CMD_MODBUSID` | `modbusid` |
|
||
| 21 | `CMD_MODBUSIDOUT` | `modbusidout` |
|
||
| 22 | `CMD_MODBUSSPEED` | `modbusspeed` |
|
||
|
||
### Examples
|
||
|
||
All data in hex. Slave ID is omitted.
|
||
|
||
Get current time:
|
||
- request: `02 00 00`
|
||
- answer: `02 00 00 00 de ad be ef` — last four bytes are time in ms since power-up.
|
||
|
||
Set relay number 5:
|
||
- request: `0b 00 85 00 01 00 00 00`
|
||
- answer: `0b 00 05 00 01 00 00 00`
|
||
|
||
Set relays 0..3, reset the rest:
|
||
- request: `0b 00 ff 00 07 00 00 00`
|
||
- answer: `0b 00 7f 00 07 00 00 00`
|
||
|
||
Changing flags works like the text command: with a parameter number the Nth bit is changed, without
|
||
a parameter the whole `uint32_t` is replaced.
|
||
|
||
## MODBUS-RTU protocol
|
||
|
||
The device can operate as master or slave. Default format is 9600-8N1. **Big-endian**, as the
|
||
standard requires. Default slave ID is 1, and the "relay command" target ID is 2.
|
||
|
||
Set `modbusid=0` to enter master mode. In master mode the device no longer answers incoming modbus
|
||
requests, but instead parses incoming **responses** and prints them to USART.
|
||
|
||
The `modbus` command sends a formal modbus request in the format:
|
||
|
||
```
|
||
modbus = slaveID fcode regaddr nregs [N data]
|
||
```
|
||
|
||
All numbers are space-separated and parsed by `getnum()` (decimal / hex / octal / binary).
|
||
`slaveID` and `fcode` are one byte each; `regaddr` and `nregs` are two bytes little-endian; `N` is
|
||
one byte; `data` is N bytes. Optional data bytes are allowed only for "multiple" functions (0x0F,
|
||
0x10). For simple setters (0x05, 0x06) `nregs` is the two-byte value written to the slave.
|
||
|
||
```
|
||
modbus = 1 6 2 1 # slave 1, write register, register 2 (MR_LED), value 1
|
||
modbus = 1 0x0f 0 8 1 0xff # slave 1, write coils, 8 coils, 1 byte of data
|
||
```
|
||
|
||
`modbusraw` does not validate the fields; it just sends the data (user should add CRC by himself).
|
||
Useful for testing unusual requests.
|
||
|
||
In master mode, flag `f_send_relay_modbus` makes the device send an "write coils" command with ID =
|
||
`modbusidout` every time the IN state changes. This lets you bind several devices: inputs of one
|
||
drive the outputs of another. If `modbusidout` is zero, a broadcast is sent (slaves do not reply to
|
||
broadcasts, they just perform the action).
|
||
|
||
### Implementation notes
|
||
|
||
- Modbus uses UART4 with DMA for both RX and TX. End of frame is detected by the IDLE interrupt.
|
||
- There is no 3.5-character silent-interval handling — any IDLE marks the end of a packet.
|
||
- Input buffer is 68 bytes (up to 67 data bytes), output buffer is 64 bytes (up to 64 bytes).
|
||
Enlarge `MODBUSBUFSZI` / `MODBUSBUFSZO` in `modbusrtu.h` if needed.
|
||
- Maximal modbus slave ID is 247.
|
||
- The device does **not reply** to broadcast requests (ID = 0).
|
||
|
||
### Slave registers
|
||
|
||
Holding registers: `[R]` = read-only, `[W]` = write-only, `[RW]` = read/write.
|
||
|
||
| № | Symbol | Access | Meaning |
|
||
|---|-------------------|--------|---------|
|
||
| 0 | `MR_RESET` | W | reset MCU |
|
||
| 1 | `MR_TIME` | RW | MCU time in ms (`uint32_t`) |
|
||
| 2 | `MR_LED` | RW | on-board LED state |
|
||
| 3 | `MR_INCHANNELS` | R | `uint32_t` of available IN channels |
|
||
| 4 | `MR_OUTCHANNELS` | R | `uint32_t` of available OUT channels |
|
||
|
||
### Supported function codes
|
||
|
||
#### 01 — read coils
|
||
Read state of all relays. `regaddr` must be 0, `nregs` must be a multiple of 8 (in this hardware: 8 or 16). Answer contains `nregs / 8` bytes; bit 0 of the first data byte is relay 0.
|
||
|
||
Example — read all relays; only relay 10 active:
|
||
- request: `01 01 00 00 00 10`
|
||
- answer: `01 01 02 00 04`
|
||
|
||
Errors: `02` — non-zero `regaddr`; `03` — `nregs` not a multiple of 8 or too large.
|
||
|
||
#### 02 — read discrete inputs
|
||
Same semantics as "read coils", but for the IN channels.
|
||
|
||
Example — read first 8 INs; all 4 low-order inputs active:
|
||
- request: `01 02 00 00 00 08`
|
||
- answer: `01 02 01 0f`
|
||
|
||
#### 03 — read holding register
|
||
Reads one register at a time.
|
||
|
||
Example — read time:
|
||
- request: `01 03 00 01 00 01`
|
||
- answer: `01 03 04 01 53 15 00` — value `0x00155301` = 1397505 ms ≈ 1397.5 s.
|
||
|
||
Errors: `02` — bad `regaddr`; `03` — `regno != 1`.
|
||
|
||
#### 04 — read input register
|
||
Read `nregs` ADC channels starting at `regaddr`.
|
||
|
||
Example — read channels 5..8:
|
||
- request: `01 04 00 05 00 04`
|
||
- answer: `01 04 08 6c 08 21 00 33 00 41 00` — `0x086c` (2156) for channel 5, etc.
|
||
|
||
Errors: `02` — bad start channel; `03` — bad amount (zero or beyond last channel).
|
||
|
||
#### 05 — write coil
|
||
Changes a single relay state. `regaddr` — relay number, `nregs` — value (0 = off, non-zero = on).
|
||
|
||
Example:
|
||
- request: `01 05 00 03 00 01`
|
||
- answer: `01 05 00 03 00 01`
|
||
|
||
Errors: `02` — bad relay number.
|
||
|
||
#### 06 — write holding register
|
||
Writes to one register (`MR_RESET`, `MR_TIME` or `MR_LED`).
|
||
|
||
Example — turn LED on:
|
||
- request: `01 06 00 02 00 01`
|
||
- answer: `01 06 00 02 00 01`
|
||
|
||
Errors: `02` — bad register.
|
||
|
||
#### 0F — write multiple coils
|
||
Changes all relays at once. `regaddr` must be 0, `nregs` a multiple of 8, `N` = `(nregs + 7) / 8`.
|
||
Each data bit is a relay state.
|
||
|
||
Example — turn on relays 0..7:
|
||
- request: `01 0f 00 00 00 08 ff 01`
|
||
- answer: `01 0f 00 00 00 08`
|
||
|
||
Errors: `02` — non-zero `regaddr`; `03` — wrong amount; `07` — cannot change relays.
|
||
|
||
#### 10 — write multiple registers
|
||
Only `MR_TIME` can be written this way; `nregs` must be 1 and the data length 4 bytes.
|
||
|
||
Example — clear `Tms`:
|
||
- request: `01 10 00 01 00 01 04 00 00 00 00`
|
||
- answer: `01 10 00 01 00 01`
|
||
|
||
Errors: `02` — wrong register.
|
||
|
||
### Modbus exception codes
|
||
|
||
| Code | Name | Meaning |
|
||
|------|------|---------|
|
||
| 01 | `ME_ILLEGAL_FUNCION` | function code is not authorized for the slave |
|
||
| 02 | `ME_ILLEGAL_ADDRESS` | data address is not authorized |
|
||
| 03 | `ME_ILLEGAL_VALUE` | data field value is not authorized |
|
||
| 04 | `ME_SLAVE_FAILURE` | unrecoverable error |
|
||
| 05 | `ME_ACK` | accepted, but processing takes a long time |
|
||
| 06 | `ME_SLAVE_BUSY` | slave is busy |
|
||
| 07 | `ME_NACK` | programming request cannot be performed |
|
||
| 08 | `ME_PARITY_ERROR` | memory parity error |
|
||
|
||
## Limitations
|
||
|
||
- CAN RX queue holds 8 messages; extras are dropped silently.
|
||
- USART RX buffer holds 196 bytes; longer lines are discarded with an error message.
|
||
- Modbus does not implement the 3.5-character silent interval. Buffers are 67/64 bytes.
|
||
- The Modbus master does not implement retries, timeouts or a transaction queue — it just prints
|
||
incoming responses. Reliable exchange should be arranged by the host.
|
||
- CAN filters accept only the configured `CANIDin` plus ID 0 (broadcast); the "monitor" mode adds a
|
||
second, match-all filter.
|
||
- The IWDG is enabled in release builds. Any hang longer than ~125 ms triggers a reset.
|
||
- The `EBUG` build disables the IWDG and enables verbose `DBG(...)` messages.
|
||
|
||
## Short programming guide
|
||
|
||
### Adding a new value to flash storage
|
||
|
||
All stored values are described in `struct user_conf` (`flash.h`). You can add new fields, but keep
|
||
32-bit alignment in mind. Bit flags live in `union confflags_t`, which combines 32-bit and per-bit
|
||
access.
|
||
|
||
After adding a field:
|
||
1. Add a setter/getter (usually via `u32setget` or `flagsetget`).
|
||
2. Add a line in `dumpconf()` (`proto.c`).
|
||
|
||
The text protocol allows working with flags by their semantic name. To add a flag, edit `proto.c`:
|
||
- add a `static const char* S_f_...` constant with the flag name;
|
||
- add its address to the `bitfields[]` array **in the same order as the bits are defined in
|
||
`confflags_t`** (critical: `dumpconf` and `confflags` index this array by bit number);
|
||
- add an entry to the `text_cmd` enum;
|
||
- add a `funcdescr` entry to `funclist`;
|
||
- modify `confflags()` for setter/getter handling.
|
||
|
||
### Adding a new command
|
||
|
||
Base commands are processed in `canproto.c` and `proto.c`. `modbusproto.c` handles modbus-specific
|
||
commands.
|
||
|
||
To add a CAN/serial command:
|
||
|
||
1. Add an enum member in `canproto.h` (`CMD_...`). **This value is the numeric command code** on
|
||
the wire.
|
||
2. Add a string constant with the text command name in `proto.c`.
|
||
3. Add a `funcdescr` entry to `funclist`.
|
||
4. Implement the handler in `canproto.c` (returns one of `errcodes`, receives a `CAN_message *`).
|
||
|
||
**Important:** the `funclist[]` array in `canproto.c` is indexed by enum value — the entry for
|
||
`CMD_X` must be at array position `CMD_X`. Use designated initializers (`[CMD_X] = {...}`) as the
|
||
existing code does.
|
||
|
||
The `commonfunction` struct has fields `{fn, minval, maxval, datalen}`:
|
||
- `minval == maxval` disables range checking of the value (bytes 4..7) for setter commands;
|
||
- `datalen` is the minimal packet length in bytes that the handler requires.
|
||
|
||
The handler only sees a `CAN_message *`. The serial parser builds an equivalent packet from user
|
||
input: `[C C P 0 V0 V1 V2 V3]`, where `C` = command code (little-endian); `P` = parameter number
|
||
(or 0x7F if not specified), ORed with 0x80 in case of a setter; `Vx` = bytes of the user value
|
||
(little-endian).
|
||
|
||
For `uint32_t` configuration values use `u32setget`; for bit flags — `flagsetget`.
|
||
|
||
### Adding a serial-only command
|
||
|
||
If the command has no CAN equivalent, work purely in `proto.c`:
|
||
- add an entry to the `text_cmd` enum (negative indices are used in `funclist`);
|
||
- add a string constant and a `funcdescr` entry;
|
||
- implement the handler with signature `errcodes fn(const char *str, text_cmd cmd)`;
|
||
- register it in the `textfunctions[]` array.
|
||
|
||
### Working with modbus
|
||
|
||
Modbus-specific enums (`modbus_fcode`, `modbus_exceptions`) and structs (`modbus_request`,
|
||
`modbus_response`) are declared in `modbusrtu.h`. `data` fields hold bytes in wire order. For
|
||
requests without data (Fcode ≤ 6), `data` may be `NULL`.
|
||
|
||
High-level modbus slave handlers live in `modbusproto.c`. To add a new register, extend the
|
||
`modbus_registers` enum in `modbusproto.h` and handle the new value in `readreg()`, `writereg()` or
|
||
`writeregs()`. The main dispatch point is `parse_modbus_request()`.
|
||
|
||
---
|
||
|
||
## License
|
||
|
||
All source files are licensed under **GNU General Public License v3.0** unless stated otherwise.
|