-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #31 from kbsriram/fix-phase
Move SPI bit writes to the right clock phase.
- Loading branch information
Showing
5 changed files
with
648 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
.. | ||
SPDX-FileCopyrightText: KB Sriram | ||
SPDX-License-Identifier: MIT | ||
.. | ||
Bitbangio Tests | ||
=============== | ||
|
||
These tests run under CPython, and are intended to verify that the | ||
library passes some sanity checks, using a lightweight simulator as | ||
the target device. | ||
|
||
These tests run automatically from the standard `circuitpython github | ||
workflow <wf_>`_. To run them manually, first install these packages | ||
if necessary:: | ||
|
||
$ pip3 install pytest | ||
|
||
Then ensure you're in the *root* directory of the repository and run | ||
the following command:: | ||
|
||
$ python -m pytest | ||
|
||
Notes on the simulator | ||
====================== | ||
|
||
`simulator.py` implements a small logic level simulator and a few test | ||
doubles so the library can run under CPython. | ||
|
||
The `Engine` class is used as a singleton in the module to co-ordinate | ||
the simulation. | ||
|
||
A `Net` holds a list of `FakePins` that are connected together. It | ||
also resolves the overall logic level of the net when a `FakePin` is | ||
updated. It can optionally hold a history of logic level changes, | ||
which may be useful for testing some timing expectations, or export | ||
them as a VCD file for `Pulseview <pv_>`_. Test code can also register | ||
listeners on a `Net` when the net's level changes, so it can simulate | ||
device behavior. | ||
|
||
A `FakePin` is a test double for the CircuitPython `Pin` class, and | ||
implements all the functionality so it behaves appropriately in | ||
CPython. | ||
|
||
A simulated device can create a `FakePin` for each of its terminals, | ||
and connect them to one or more `Net` instances. It can listen for | ||
level changes on the `Net`, and bitbang the `FakePin` to simulate | ||
behavior. `simulated_spi_device.py` implements a peripheral device | ||
that writes a constant value onto an SPI bus. | ||
|
||
|
||
.. _wf: https://github.com/adafruit/workflows-circuitpython-libs/blob/6e1562eaabced4db1bd91173b698b1cc1dfd35ab/build/action.yml#L78-L84 | ||
.. _pv: https://sigrok.org/wiki/PulseView |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
# SPDX-FileCopyrightText: KB Sriram | ||
# SPDX-License-Identifier: MIT | ||
"""Implementation of testable SPI devices.""" | ||
|
||
import dataclasses | ||
import simulator as sim | ||
|
||
|
||
@dataclasses.dataclass(frozen=True) | ||
class SpiBus: | ||
enable: sim.Net | ||
clock: sim.Net | ||
copi: sim.Net | ||
cipo: sim.Net | ||
|
||
|
||
class Constant: | ||
"""Device that always writes a constant.""" | ||
|
||
def __init__(self, data: bytearray, bus: SpiBus, polarity: int, phase: int) -> None: | ||
# convert to binary string array of bits for convenience | ||
datalen = 8 * len(data) | ||
self._data = f"{int.from_bytes(data, 'big'):0{datalen}b}" | ||
self._bit_position = 0 | ||
self._clock = sim.FakePin("const_clock_pin", bus.clock) | ||
self._last_clock_level = bus.clock.level | ||
self._cipo = sim.FakePin("const_cipo_pin", bus.cipo) | ||
self._enable = sim.FakePin("const_enable_pin", bus.enable) | ||
self._cipo.init(sim.Mode.OUT) | ||
self._phase = phase | ||
self._polarity = sim.Level.HIGH if polarity else sim.Level.LOW | ||
self._enabled = False | ||
bus.clock.on_level_change(self._on_level_change) | ||
bus.enable.on_level_change(self._on_level_change) | ||
|
||
def write_bit(self) -> None: | ||
"""Writes the next bit to the cipo net.""" | ||
if self._bit_position >= len(self._data): | ||
# Just write a zero | ||
self._cipo.value(0) # pylint: disable=not-callable | ||
return | ||
self._cipo.value( | ||
int(self._data[self._bit_position]) # pylint: disable=not-callable | ||
) | ||
self._bit_position += 1 | ||
|
||
def _on_level_change(self, net: sim.Net) -> None: | ||
if net == self._enable.net: | ||
# Assumes enable is active high. | ||
self._enabled = net.level == sim.Level.HIGH | ||
if self._enabled: | ||
self._bit_position = 0 | ||
if self._phase == 0: | ||
# Write on enable or idle->active | ||
self.write_bit() | ||
return | ||
if not self._enabled: | ||
return | ||
if net != self._clock.net: | ||
return | ||
cur_clock_level = net.level | ||
if cur_clock_level == self._last_clock_level: | ||
return | ||
active = 0 if cur_clock_level == self._polarity else 1 | ||
if self._phase == active: | ||
self.write_bit() | ||
self._last_clock_level = cur_clock_level |
Oops, something went wrong.