Commit 8067d32f authored by Juergen Hasch's avatar Juergen Hasch

Add tests, make API more Pythonic

parent 7948142d
......@@ -2,18 +2,15 @@
# -*- coding: utf-8 -*-
# Example of SPI data transfer
from pyBusPirateLite.SPI import SPI, CFG_PUSH_PULL, CFG_IDLE
from pyBusPirateLite.BBIO_base import PIN_POWER, PIN_CS
from pyBusPirateLite.SPI import *
spi = SPI()
spi.outputs = PIN_POWER | PIN_CS
spi.state = PIN_POWER | PIN_CS
spi.config = CFG_PUSH_PULL | CFG_IDLE
spi.pins = PIN_POWER | PIN_CS
spi.config = CFG_PUSH_PULL
spi.speed = '1MHz'
# send two bytes and receive answer
spi.cs = True
data = spi.transfer( [0x82, 0x00])
data = spi.transfer([0x82, 0x55])
spi.cs = False
print(ord(data[2]))
spi.pins = PIN_CS # turn off power
......@@ -86,6 +86,11 @@ class BBIO_base:
self.port.flushInput()
for i in range(20):
self.write(0x00)
r = self.response(1, True)
if r:
break
self.port.flushInput()
self.write(0x00)
if self.response(5) == "BBIO1":
self.mode = 'bb'
self.bp_config = 0x00 # configuration bits determine action of power sources and pullups
......@@ -97,34 +102,6 @@ class BBIO_base:
self.recurse_flush(self.enter)
raise BPError('Could not enter bitbang mode')
def reset(self):
""" Reset to raw bitbang mode
Notes
-----
This command resets the Bus Pirate into raw bitbang mode from the user terminal. It also resets to raw bitbang
mode from raw SPI mode, or any other protocol mode. This command always returns a five byte bitbang version
string "BBIOx", where x is the current protocol version (currently 1).
Some terminals send a NULL character (0x00) on start-up, causing the Bus Pirate to enter binary mode when it
wasn't wanted. To get around this, you must now enter 0x00 at least 20 times to enter raw bitbang mode.
Note: The Bus Pirate user terminal could be stuck in a configuration menu when your program attempts to enter
binary mode. One way to ensure that you're at the command line is to send <enter> at least 10 times, and then
send '#' to reset. Next, send 0x00 to the command line 20+ times until you get the BBIOx version string.
After entering bitbang mode, you can enter other binary protocol modes.
Raises
-------
BPError
If bus pirate does not respond correctly
"""
self.port.flushInput()
for i in range(20):
self.write(0x00)
self.timeout(self.minDelay * 10)
if self.response(5) == "BBIO1":
self.port.flushInput()
return
raise ProtocolError('Could not return to bitbang mode')
def hw_reset(self):
"""Reset Bus Pirate
......@@ -187,6 +164,7 @@ class BBIO_base:
sleep(timeout)
def write(self, value):
# print('val %s' % value.to_bytes(1, 'big'))
self.port.write(value.to_bytes(1, 'big'))
def response(self, byte_count=1, binary=False):
......@@ -201,95 +179,10 @@ class BBIO_base:
"""
data = self.port.read(byte_count)
if binary is True:
if byte_count ==1 :
print('Response: %s' % ord(data))
return data
else:
print('Decoded response: %s' % data)
return data.decode()
@property
def outputs(self):
"""
Returns
-------
byte
Current state of the pins
PIN_AUX, PIN_MOSI, PIN_CLK, PIN_MISO, PIN_CS
"""
self.write(0x40 | ~ self.pins_direction & 0x1f) # map input->1, output->0
self.timeout(self.minDelay * 10)
return ord(self.response(1, True)) & 0x1f
@outputs.setter
def outputs(self, pinlist=0):
""" Configure pins as input our output
Notes
-----
The Bus pirate responds to each direction update with a byte showing the current state of the pins, regardless
of direction. This is useful for open collector I/O modes. Used in every mode to configure pins.
In bb it configures as either input or output, in the other modes it normally configures peripherals such as
power supply and the aux pin
Parameters
----------
pinlist : byte
List of pins to be set as outputs (default: all inputs)
PIN_AUX, PIN_MOSI, PIN_CLK, PIN_MISO, PIN_CS
Returns
-------
byte
Current state of the pins
PIN_AUX, PIN_MOSI, PIN_CLK, PIN_MISO, PIN_CS
"""
self.pins_direction = pinlist & 0x1f
self.write(0x40 | ~ self.pins_direction & 0x1f) # map input->1, output->0
self.timeout(self.minDelay * 10)
return ord(self.response(1, True)) & 0x1f
@property
def pins(self):
""" Get pins status
Returns
-------
byte
Current state of the pins
PIN_POWER, PIN_PULLUP, PIN_AUX, PIN_MOSI, PIN_CLK, PIN_MISO, PIN_CS
"""
self.write(0x80 | self.pins_state & 0x7f)
self.timeout(self.minDelay * 10)
self.pins_state = ord(self.response(1, True)) & 0x7f
return self.pins_state
@pins.setter
def pins(self, pinlist=0):
""" Set pins to high or low
Notes
-----
The lower 7bits of the command byte control the Bus Pirate pins and peripherals.
Bitbang works like a player piano or bitmap. The Bus Pirate pins map to the bits in the command byte as follows:
1|POWER|PULLUP|AUX|MOSI|CLK|MISO|CS
The Bus pirate responds to each update with a byte in the same format that shows the current state of the pins.
Parameters
----------
pinlist : byte
List of pins to be set high
PIN_POWER, PIN_PULLUP, PIN_AUX, PIN_MOSI, PIN_CLK, PIN_MISO, PIN_CS
"""
self.pins_state = pinlist & 0x7f
self.write(0x80 | self.pins_state)
self.timeout(self.minDelay * 10)
self.pins_state = ord(self.response(1, True)) & 0x7f
_attempts_ = 0
def recurse_end(self):
self._attempts_ = 0
......@@ -337,7 +230,6 @@ def send_stop_bit(self):
def read_byte(self):
"""Reads a byte from the bus, returns the byte. You must ACK or NACK each
byte manually. NO ERROR CHECKING (obviously)"""
self.check_mode(not_bb)
if self.mode == 'raw':
self.write(0x06)
return self.response(1, True) # this was changed, before it didn't have the 'True' which means it
......@@ -357,7 +249,7 @@ def bulk_trans(self, byte_count=1, byte_string=None):
In modes other than I2C I think it returns whatever data it gets while
sending, but this feature is untested. PLEASE REPORT so that I can
document it."""
self.check_mode(not_bb)
# self.check_mode(not_bb)
if byte_string is None:
pass
self.write(0x10 | (byte_count - 1))
......
......@@ -26,7 +26,7 @@ import serial
from .BBIO_base import BBIO_base, BPError, ProtocolError
class BitBang(BBIO_base):
def __init__(self, portname='', speed=115200, timeout=1):
def __init__(self, portname='', speed=115200, timeout=1, connect=True):
""" Provide access to the Bus Pirate bitbang mode
Parameters
......@@ -43,8 +43,89 @@ class BitBang(BBIO_base):
>>> bb = BitBang()
"""
super().__init__()
self.connect(portname, speed, timeout)
self.enter()
if connect is True:
self.connect(portname, speed, timeout)
self.enter()
@property
def outputs(self):
"""
Returns
-------
byte
Current state of the pins
PIN_AUX, PIN_MOSI, PIN_CLK, PIN_MISO, PIN_CS
"""
self.write(0x40 | ~ self.pins_direction & 0x1f) # map input->1, output->0
self.timeout(self.minDelay * 10)
return ord(self.response(1, True)) & 0x1f
@outputs.setter
def outputs(self, pinlist=0):
""" Configure pins as input our output
Notes
-----
The Bus pirate responds to each direction update with a byte showing the current state of the pins, regardless
of direction. This is useful for open collector I/O modes. Used in every mode to configure pins.
In bb it configures as either input or output, in the other modes it normally configures peripherals such as
power supply and the aux pin
Parameters
----------
pinlist : byte
List of pins to be set as outputs (default: all inputs)
PIN_AUX, PIN_MOSI, PIN_CLK, PIN_MISO, PIN_CS
Returns
-------
byte
Current state of the pins
PIN_AUX, PIN_MOSI, PIN_CLK, PIN_MISO, PIN_CS
"""
self.pins_direction = pinlist & 0x1f
self.write(0x40 | ~ self.pins_direction & 0x1f) # map input->1, output->0
self.timeout(self.minDelay * 10)
self.response(1, True)
@property
def pins(self):
""" Get pins status
Returns
-------
byte
Current state of the pins
PIN_POWER, PIN_PULLUP, PIN_AUX, PIN_MOSI, PIN_CLK, PIN_MISO, PIN_CS
"""
self.write(0x80 | (self.pins_state & 0x7f))
self.timeout(self.minDelay * 10)
self.pins_state = ord(self.response(1, True)) & 0x7f
return self.pins_state
@pins.setter
def pins(self, pinlist=0):
""" Set pins to high or low
Notes
-----
The lower 7bits of the command byte control the Bus Pirate pins and peripherals.
Bitbang works like a player piano or bitmap. The Bus Pirate pins map to the bits in the command byte as follows:
1|POWER|PULLUP|AUX|MOSI|CLK|MISO|CS
The Bus pirate responds to each update with a byte in the same format that shows the current state of the pins.
Parameters
----------
pinlist : byte
List of pins to be set high
PIN_POWER, PIN_PULLUP, PIN_AUX, PIN_MOSI, PIN_CLK, PIN_MISO, PIN_CS
"""
self.pins_state = pinlist & 0x7f
self.write(0x80 | self.pins_state)
self.timeout(self.minDelay * 10)
self.pins_state = ord(self.response(1, True)) & 0x7f
@property
def adc(self):
......@@ -58,6 +139,7 @@ class BitBang(BBIO_base):
self.write(0x14)
self.timeout(self.minDelay)
ret = self.response(2, True)
print('0: %d, 1: %d' % (ret[0], ret[1]))
voltage = (ret[0] << 8) + ret[1]
voltage = (voltage * 6.6) / 1024
return voltage
......@@ -137,8 +219,8 @@ class BitBang(BBIO_base):
raise ProtocolError('Self test did not return to bitbang mode')
return ord(errors)
def set_pwm_frequency(self, frequency, dutycycle=.5):
"""set PWM frequency and duty cycle.
def enable_PWM(self, frequency, dutycycle=.5):
""" Enable PWM output
Parameters
----------
......@@ -149,10 +231,16 @@ class BitBang(BBIO_base):
Notes
-----
Stolen from http://codepad.org/qtYpZmIF
Configure and enable pulse-width modulation output in the AUX pin. Requires a 5 byte configuration sequence.
Responds 0x01 after a complete sequence is received. The PWM remains active after leaving binary bitbang mode!
Equations to calculate the PWM frequency and period are in the PIC24F output compare manual.
Bit 0 and 1 of the first configuration byte set the prescaler value. The Next two bytes set the duty cycle
register, high 8bits first. The final two bytes set the period register, high 8bits first.
Parameter calculation stolen from http://codepad.org/qtYpZmIF
"""
if DutyCycle > 1:
if dutycycle > 1:
raise ValueError('Duty cycle should be between 0 and 1')
Fosc = 24e6
Tcy = 2.0 / Fosc
......@@ -165,30 +253,17 @@ class BitBang(BBIO_base):
Prescaler = PrescalerList[n]
PRy = PwmPeriod * 1.0 / (Tcy * Prescaler)
PRy = int(PRy - 1)
OCR = int(PRy * DutyCycle)
OCR = int(PRy * dutycycle)
if PRy < (2 ** 16 - 1):
break # valid value for PRy, keep values
else:
raise ValueError('frequency requested is invalid')
if self.setup_PWM(prescaler=Prescaler, dutycycle=OCR, period=PRy):
self.recurse_end()
return 1
return self.recurse(self.set_pwm_frequency, frequency, DutyCycle)
def setup_PWM(self, prescaler, dutycycle, period):
""" Setup pulse-width modulation
prescaler=Prescaler
dutycycle=OCR
period=PRy
Notes
-----
Configure and enable pulse-width modulation output in the AUX pin. Requires a 5 byte configuration sequence.
Responds 0x01 after a complete sequence is received. The PWM remains active after leaving binary bitbang mode!
Equations to calculate the PWM frequency and period are in the PIC24F output compare manual.
Bit 0 and 1 of the first configuration byte set the prescaler value. The Next two bytes set the duty cycle
register, high 8bits first. The final two bytes set the period register, high 8bits first.
"""
self.write(0x12)
self.write(prescaler)
self.write((dutycycle >> 8) & 0xFF)
......
......@@ -35,7 +35,7 @@ pin_mapping = {'AUX': 0b10,
class I2C(BBIO_base):
def __init__(self, portname='', speed=115200, timeout=1):
def __init__(self, portname='', speed=115200, timeout=1, connect=True):
""" Provide access to the Bus Pirate I2C interface
Parameters
......@@ -53,8 +53,9 @@ class I2C(BBIO_base):
>>> i2c.speed = '400kHz'
"""
super().__init__()
self.connect(portname, speed, timeout)
self.enter()
if connect is True:
self.connect(portname, speed, timeout)
self.enter()
def enter(self):
""" Enter I2C mode
......@@ -185,6 +186,13 @@ class I2C(BBIO_base):
@property
def speed(self):
""" Return current I2C clock speed
Returns
-------
str
I2C clock speed (5kHz, 50kHz, 100kHz, 400kHz)
"""
return self.i2c_speed
@speed.setter
......
......@@ -42,9 +42,14 @@ CFG_CLK_EDGE = 0x02
CFG_IDLE = 0x04
CFG_PUSH_PULL = 0x08
PIN_CS = 1
PIN_AUX = 2
PIN_PULLUP = 4
PIN_POWER = 8
class SPI(BBIO_base):
def __init__(self, portname='', speed=115200, timeout=1):
def __init__(self, portname='', speed=115200, timeout=1, connect=True):
""" Provide high-speed access to the Bus Pirate SPI hardware
Parameters
......@@ -59,7 +64,6 @@ class SPI(BBIO_base):
Example
-------
>>> spi = SPI()
>>> spi.state = PIN_POWER | PIN_CS
>>> spi.config(CFG_PUSH_PULL | CFG_IDLE)
>>> spi.speed = '1MHz'
>>> spi.cs = True
......@@ -67,12 +71,16 @@ class SPI(BBIO_base):
>>> spi.cs = False
"""
super().__init__()
self.connect(portname, speed, timeout)
self.enter()
self.spi_config = None
if connect is True:
self.connect(portname, speed, timeout)
self.enter()
self._config = None
self._speed = None
self._cs = None
self._pins = None
def enter(self):
"""Enter raw SPI mode
""" Enter raw SPI mode
Once in raw bitbang mode, send 0x01 to enter raw SPI mode. The Bus Pirate responds 'SPIx',
where x is the raw SPI protocol version (currently 1). Get the version string at any time by sending 0x01 again.
......@@ -89,8 +97,6 @@ class SPI(BBIO_base):
self.timeout(self.minDelay * 10)
if self.response(4) == "SPI1":
self.mode = 'spi'
self.bp_port = 0b00 # two bit port
self.bp_config = 0b0000
self.recurse_end()
return
self.recurse_flush(self.enter)
......@@ -103,9 +109,37 @@ class SPI(BBIO_base):
self.timeout(self.minDelay * 10)
return self.response(4)
@property
def pins(self):
return self._pins
@pins.setter
def pins(self, cfg):
""" Configure peripherals
Parameters
----------
cfg: int
Pin configuration 0000wxyz
w=power, x=pull-ups, y=AUX, z=CS
Notes
-----
Enable (1) and disable (0) Bus Pirate peripherals and pins. Bit w enables the power supplies, bit x toggles
the on-board pull-up resistors, y sets the state of the auxiliary pin, and z sets the chip select pin.
Features not present in a specific hardware version are ignored. Bus Pirate responds 0x01 on success.
* CS pin always follows the current HiZ pin configuration.
* AUX is always a normal pin output (0=GND, 1=3.3volts).
"""
self.write(0x40 | (cfg & 0x0f))
if self.response(1, True) != b'\x01':
raise ValueError("Could not set SPI pins")
self._pins = cfg
@property
def config(self):
return self.spi_config
return self._config
@config.setter
def config(self, cfg):
......@@ -123,7 +157,7 @@ class SPI(BBIO_base):
----------
cfg : byte
CFG_SAMPLE: sample time (0 = middle)
CFG_CLK_EDGE: clock edge (1 = active to idle
CFG_CLK_EDGE: clock edge (1 = active to idle)
CFG_IDLE: clock idle phase (0 = low)
CFG_PUSH_PULL: pin output (0 = HiZ, 1 = push-pull)
......@@ -137,10 +171,10 @@ class SPI(BBIO_base):
If configuration could not be set
"""
self.write(0x80 | cfg)
self.timeout(0.1)
if self.response(1, True) != '\x01':
self.timeout(self.minDelay)
if self.response(1, True) != b'\x01':
raise ValueError("Could not set SPI configuration")
self.spi_config = cfg
self._config = cfg
def transfer(self, txdata):
""" Bulk SPI transfer, send/read 1-16 bytes
......@@ -179,8 +213,10 @@ class SPI(BBIO_base):
self.write(0x10 + length-1)
for data in txdata:
self.write(data)
if self.response(1, True) != '\x01':
if self.response(1, True) != b'\x01':
raise ValueError("Could not transfer SPI data")
rxdata = self.response(length, True)
return rxdata
def write_then_read(self, numtx, numrx, txdata, cs = True):
""" Write then read
......@@ -243,16 +279,17 @@ class SPI(BBIO_base):
self.write(numrx & 0xff)
for data in txdata:
self.write(data)
if self.response(1, True) != '\x01':
if self.response(1, True) != b'\x01':
raise ProtocolError("Error transmitting data")
@property
def cs(self):
""" Return chip select pin status """
return self._cs
@cs.setter
def cs(self, value):
"""
""" Set chip select pin
Parameters
----------
value: bool
......@@ -267,37 +304,73 @@ class SPI(BBIO_base):
self.write(0x02)
else:
self.write(0x03)
if self.response(1, True) != '\x01':
if self.response(1, True) != b'\x01':
raise ProtocolError("CS could not be set")
self._cs = value
@property
def speed(self):
return self._speed
@speed.setter
def speed(self, frequency):
""" Set SPI bus speed
Parameters
----------
frequency : str
SPI clock speed (30kHz, 125kHz, 250kHz, 1MHz, 2MHz, 2.6MHz, 4MHz, 8MHz)
Raises
------
ProtocolError
If I2C speed could not be set
"""
try:
clock = SPI_speed[frequency]
except KeyError:
raise ValueError('Clock speed not supported')
self.write(0x60 | clock)
@property
def speed(self):
return self.spi_speed
if self.response(1, True) != b'\x01':
raise ProtocolError('Could not set SPI speed')
self._speed = frequency
def sniffer(self, cs):
""" Sniff SPI traffic when CS low(10)/all(01) TODO
@speed.setter
def speed(self, frequency):
""" Set SPI speed
Parameters
----------
cs : Bool
True: Capture when CS is low
False: Capture all
Notes
-----
0000 11XX
The SPI sniffer is implemented in hardware and should work up to 10MHz. It follows the configuration settings
you entered for SPI mode. The sniffer can read all traffic, or filter by the state of the CS pin.
[/] - CS enable/disable
xy - escape character (\\) precedes two byte values X (MOSI pin) and Y (MISO pin) (updated in v5.1)
Parameters
----------
frequency : str
SPI clock speed (30kHz, 125kHz, 250kHz, 1MHz, 2MHz, 2.6MHz, 4MHz, 8MHz)
Sniffed traffic is encoded according to the table above. The two data bytes are escaped with the '\' character
to help locate data in the stream.
Raises
------
ProtocolError
If I2C speed could not be set
"""
try:
clock = SPI_speed[frequency]
except KeyError:
raise ValueError('Clock speed not supported')
self.write(0x60 | clock)
Send the SPI sniffer command to start the sniffer, the Bus Pirate responds 0x01 then sniffed data starts to
flow. Send any byte to exit. Bus Pirate responds 0x01 on exit. (0x01 reply location was changed in v5.8)
if self.response(1, True) != 0x01:
raise ProtocolError('Could not set SPI speed')
self.spi_speed = frequency
If the sniffer can't keep with the SPI data, the MODE LED turns off and the sniff is aborted. (new in v5.1)
The sniffer follows the output clock edge and output polarity settings of the SPI mode, but not the input
sample phase.
More detailed notes on the SPI sniffer in the SPI user terminal documentation.
"""
if cs is True:
cmd = 0x0e
else:
cmd = 0x0d
self.write(cmd)
if self.response(1, True) != b'\x01':
raise ProtocolError('Could not set SPI sniff mode')
......@@ -23,7 +23,7 @@ You should have received a copy of the GNU General Public License
along with pyBusPirate. If not, see <http://www.gnu.org/licenses/>.
"""
from .BBIO_base import BBIO_base, BPError, ProtocolError
from .BBIO_base import BBIO_base, BPError
FOSC = (32000000 / 2)
......@@ -68,10 +68,11 @@ class UART(BBIO_base):
super().__init__()
self.connect(portname, speed, timeout)
self.enter()
self.uart_config = None
self._config = None
self._echo = False
def enter(self):
"""
""" Enter UART mode
Raises
------
......@@ -80,9 +81,6 @@ class UART(BBIO_base):
"""
if self.mode == 'uart':
return
if self.mode != 'bb':
super().enter()
self.write(0x03)
self.timeout(self.minDelay * 10)
if self.response(4) == "ART1":
......@@ -90,10 +88,30 @@ class UART(BBIO_base):
self.bp_port = 0b00 # two bit port
self.bp_config = 0b0000
self.recurse_end()
return 1
return
self.recurse_flush(self.enter)
raise BPError('Could not enter UART mode')
@property
def modestring(self):
""" Return mode version string """
self.write(0x01)
self.timeout(self.minDelay * 10)
return self.response(4)
@property
def echo(self):
return self._echo
@echo.setter
def echo(self, mode):
if mode is True:
self.write(0x03)
else:
self.write(0x02)
if self.response(1, True) != '\x01':
raise ValueError("Could not set echo mode")
self._echo = mode
def manual_speed_cfg(self, baud):
""" Manual baud rate configuration, send 2 bytes
......@@ -104,38 +122,36 @@ class UART(BBIO_base):
Use the UART manual [PDF] or an online calculator to find the correct value (key values: fosc 32mHz,
clock divider = 2, BRGH=1) . Bus Pirate responds 0x01 to each byte. Settings take effect immediately.
"""
if self.mode == 'uart':
raise BPError('Not in UART mode')
BRG = ((FOSC) / (4 * baud)) - 1
BRG = (FOSC / (4 * baud)) - 1
BRGH = ((BRG >> 8) & 0xFF)
BRGL = (BRG & 0xFF)
self.port.write(chr(0x02))
self.port.write(chr(BRGH))
self.port.write(chr(BRGL))
self.write(0x03)
self.write(BRGH)
self.write(BRGL)
self.timeout(0.1)
return self.response()
def begin_input(self):
self.port.write(chr(0x04))
self.write(0x04)
def end_input(self):
self.port.write(chr(0x05))
self.write(0x05)
def enter_bridge_mode(self):
""" UART bridge mode (reset to exit)
Starts a transparent UART bridge using the current configuration. Unplug the Bus Pirate to exit.
"""
self.port.write(chr(0x0f))
self.write(0x0f)
self.timeout(0.1)
self.response(1, True)