Metadata-Version: 2.4
Name: fctboard
Version: 1.0.0
Summary: Control a microcontroller's GPIO, ADC, PWM, DAC, I2C, SPI, UART and CAN from Python over a serial link
Author: Vecmocon Technologies
License-Expression: MIT
Project-URL: Homepage, https://github.com/vecmocon/fctboard
Project-URL: Issues, https://github.com/vecmocon/fctboard/issues
Keywords: embedded,gpio,adc,pwm,i2c,spi,uart,can,test-fixture,functional-test,hardware-in-the-loop
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Manufacturing
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Embedded Systems
Classifier: Topic :: Software Development :: Testing
Classifier: Topic :: System :: Hardware :: Hardware Drivers
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pyserial>=3.4
Dynamic: license-file

# fctboard

Control a microcontroller's GPIO, ADC, PWM, DAC, I²C, SPI, UART and CAN from
Python over a serial link — configured at runtime, with nothing bound to a pin
at compile time.

Written for functional circuit test: stimulate a board under test, measure its
response, assert on the relationship between the two.

```bash
pip install fctboard
```

## Quick start

```python
from fctboard import FctBoard

fct = FctBoard(vid=0x0483, pid=0x5740)
fct.connect()

fct.pinMode("PE0", "OUT")          # any pin, any role, at any time
fct.writePin("PE0", 1)

print(fct.readVoltage("PC4"))      # 1.651
fct.pwmDuty("PB5", 500, freq=1000) # 50% at 1 kHz

fct.close()
```

Don't know your board's USB identifiers? Leave them out and `connect()` probes
each serial port for one that answers.

## What it does

| Peripheral | Methods |
|---|---|
| GPIO | `pinMode` `writePin` `readPin` `togglePin` `freePin` |
| Interrupts | `extiEnable` `extiCount` |
| ADC | `readAdc` `readVoltage` |
| PWM | `pwmDuty` `pwmPercent` `pwmFreq` `pwmStop` `pwmRead` |
| DAC | `dacWrite` `dacVolts` |
| I²C | `i2cInit` `i2cWrite` `i2cRead` `i2cWriteRead` `i2cScan` |
| SPI | `spiInit` `spiXfer` |
| UART | `uartInit` `uartWrite` `uartRead` |
| CAN | `canInit` `canSend` `canRecv` `canFilter` `canStop` |
| Timer | `timInit` `timStart` `timStop` `timCount` `timClear` |
| Config | `canConfig` `adcConfig` `spiConfig` `uartConfig` … `getConfig` |

## Three things worth knowing

**A pin holds its role until you change it.** Set it high and it stays high —
no timeout, nothing to refresh. Giving a pin a new role is legal at any time;
the firmware tears down the old configuration first, so a pin never carries a
stale pull-up into its next job.

**Configure before you initialise.** `canConfig()` and friends edit a copy of
the settings held on the board. Nothing reaches hardware until the matching
`*Init()` runs.

**Failures raise, they do not return zero.** Every method raises `FctError`
with the reason and the originating command, so a broken fixture stops a test
rather than recording a passing one.

```python
try:
    fct.readAdc("PE7")
except FctError as e:
    print(e)      # PIN_HAS_NO_ADC (command: READ_ADC PE7)
```

## Works with any microcontroller

This library holds no value specific to any MCU. Pin names are opaque strings
it never parses — `"PA0"`, `"P1.3"`, `"GPIO24"` and `"DIO7"` are equivalent —
and anything numeric that varies by silicon (ADC resolution, reference
voltage, timer list) is read from the board itself at connect.

```python
print(fct.capabilities())
# {'MCU': 'STM32F072V8', 'ADC_BITS': '12', 'VREF_MV': '3300', ...}

fct.adcFullScale()    # 4095 here, 1023 on a 10-bit part
```

So porting to a different vendor means implementing the same command protocol
in its firmware. Your Python does not change.

Optionally name pins by role, so a rewire — or a change of MCU — is one edit:

```python
fct = FctBoard(vid=0x0483, pid=0x5740,
               pins={"LED": "PE0", "ADC_IN": "PC4"})

fct.pinMode(fct.pin("LED"), "OUT")
```

## Firmware

The board must implement the line-oriented ASCII protocol this library speaks:
one command per line, one reply per line, answering `OK`, a value, or
`ERR <reason>`. A reference implementation for the STM32F072 is documented in
`PROTOCOL.txt`.

```
PIN_MODE PE0 OUT        -> OK
READ_ADC PC4            -> 2048
I2C_WREAD 1 0x68 0F 1   -> 6A
READ_ADC PE7            -> ERR PIN_HAS_NO_ADC
```

## Requirements

Python 3.8+ and `pyserial`. Nothing else.

## Licence

MIT
