BBIO_base.py 9.31 KB
Newer Older
juhasch's avatar
juhasch committed
1 2 3 4
#! /usr/bin/env python
# -*- coding: utf-8 -*-

from time import sleep
5 6 7 8 9 10 11 12 13 14 15 16 17
import serial


class BPError(IOError):
    pass


class ProtocolError(IOError):
    pass

"""
PICSPEED = 24MHZ / 16MIPS
"""
Jürgen Hasch's avatar
Jürgen Hasch committed
18 19 20 21 22 23 24 25 26 27
# 0x01 CS
# 0x08 - +3.3V

PIN_CS = 0x01
PIN_MISO = 0x02
PIN_CLK = 0x04
PIN_MOSI = 0x08
PIN_AUX = 0x10
PIN_PULLUP = 0x20
PIN_POWER = 0x40
28

29 30

class BBIO_base:
juhasch's avatar
juhasch committed
31
    """Functions used in every mode, the base of class.  Most of these you can
32 33 34 35 36 37
    probably ignore, as they are just used in the other member classes
    Note: Also contains some of the older functions that are now probably outdated
    """
    def __init__(self):
        self.minDelay = 1 / 115200
        self.mode = None
38 39 40 41 42 43 44
        self.port = None
        self.connected = False
        self.t = True
        self.bp_config = None
        self.bp_port = None
        self.bp_dir = None
        self.portname = ''
Jürgen Hasch's avatar
Jürgen Hasch committed
45 46
        self.pins_state = None
        self.pins_direction = None
47 48 49 50


    _attempts_ = 0  # global stored for use in enter

juhasch's avatar
juhasch committed
51
    def enter_bb(self):
52 53 54 55 56 57 58 59 60 61
        """Enter bitbang mode

        This is the be-all-end-all restart function.  It will keep trying
        to get the bus pirate into bit bang mode even if it is stuck.  Call this
        to get the bus pirate into a known state (bb mode)

        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", w
        here x is the current protocol version (currently 1).
juhasch's avatar
juhasch committed
62

63 64
        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.
juhasch's avatar
juhasch committed
65 66 67 68

        Notes
        -----
        The Bus Pirate user terminal could be stuck in a configuration menu when your program attempts to enter
69 70 71 72 73 74 75 76 77 78 79
        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
        ------
        IOError
            If device is not connected
        """
        if self.connected is not True:
            raise IOError('Device not connected')
Jürgen Hasch's avatar
Jürgen Hasch committed
80
        self.timeout(self.minDelay * 10)
Juergen Hasch's avatar
Juergen Hasch committed
81
        self.port.flushInput()
Jürgen Hasch's avatar
Jürgen Hasch committed
82
        for i in range(10):
Juergen Hasch's avatar
Juergen Hasch committed
83
            self.write(0x00)
84 85 86
            r = self.response(1, True)
            if r:
                break
juhasch's avatar
juhasch committed
87
            for m in range(2):
Jürgen Hasch's avatar
Jürgen Hasch committed
88 89 90
                 self.write(0x00)

        self.timeout(self.minDelay * 10)
91
        self.port.flushInput()
Jürgen Hasch's avatar
Jürgen Hasch committed
92 93
        self.timeout(self.minDelay * 10)
        resp = self.response(200)
94
        self.write(0x00)
Jürgen Hasch's avatar
Jürgen Hasch committed
95 96
        resp =  self.response(5)
        if resp == "BBIO1":
97 98 99 100
            self.mode = 'bb'
            self.bp_config = 0x00  # configuration bits determine action of power sources and pullups
            self.bp_port = 0x00  # out_port similar to ports in microcontrollers
            self.bp_dir = 0x1F  # direction port similar to microchip microcontrollers.  (1) is input, (0) is output
Juergen Hasch's avatar
Juergen Hasch committed
101
            self.port.flushInput()
Jürgen Hasch's avatar
Jürgen Hasch committed
102
            return True
103
        raise BPError('Could not enter bitbang mode')
104

juhasch's avatar
juhasch committed
105
    def enter(self):
juhasch's avatar
juhasch committed
106
        """Enter bitbang mode.
juhasch's avatar
juhasch committed
107 108 109 110 111
           Will be overriden by other classes 
        """
        if self.mode == 'bb':
            return
        return self.enter_bb()
Jürgen Hasch's avatar
Jürgen Hasch committed
112 113

    def hw_reset(self):
114 115 116 117 118 119
        """Reset Bus Pirate

        The Bus Pirate responds 0x01 and then performs a complete hardware reset.
        The hardware and firmware version is printed (same as the 'i' command in the terminal),
        and the Bus Pirate returns to the user terminal interface. Send 0x00 20 times to enter binary mode again.
        """
juhasch's avatar
juhasch committed
120 121
        if self.mode != 'bb':
            self.enter_bb()
122
        self.write(0x0f)
Jürgen Hasch's avatar
Jürgen Hasch committed
123
        self.port.flushInput()
124 125 126
        self.timeout(.1)
        self.mode = None

juhasch's avatar
juhasch committed
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
    def get_port(self):
        """Detect Buspirate and return first detected port
        
        Returns
        -------
        str
            First valid portname
        """
        try:
            import serial.tools.list_ports as list_ports
        except ImportError:
            raise ImportError('Pyserial version with serial.tools.list_port required')

        import serial

        # the API in version 2 and 3 is different
        if serial.VERSION[0] == '2':
            ports = list_ports.comports()
            for port in ports:
                if len(port) == 3 and '0403:6001' in port[2]:
                    return port[0]
148 149
                if len(port) == 3 and 'PID=0403,VID=6001' in port[2]:
                    return port[0]
juhasch's avatar
juhasch committed
150 151 152 153 154 155 156
        else:
            ports = list_ports.comports()
            for port in ports:
                if hasattr(port, 'pid') and hasattr(port, 'vid'):
                    if port.vid == 1027 and port.pid == 24577:
                        return port.name

Jürgen Hasch's avatar
Jürgen Hasch committed
157
    def connect(self, portname='', speed=115200, timeout=0.1):
juhasch's avatar
juhasch committed
158
        """Will try to automatically find a port regardless of os
159 160 161 162

        Parameters
        ----------
        portname : str
juhasch's avatar
juhasch committed
163
            Name of comport (e.g. /dev/ttyUSB0 or COM3)
164 165 166 167 168 169 170 171 172 173 174 175 176 177
        speed : int
            Communication speed, use default of 115200
        timeout : int
            Timeout in s to wait for reply

        Raises
        ------
        ImportError
            If helper function to find serial port is not available
        IOError
            If device could not be opened
        """

        if portname == '':
juhasch's avatar
juhasch committed
178
            portname = self.get_port()
179 180
        if portname == '':
            raise IOError('Could not autodetect a BusPirate device.')
juhasch's avatar
juhasch committed
181

182 183 184 185 186 187 188 189 190 191 192 193
        self.portname = portname
        try:
            self.port = serial.Serial(portname, speed, timeout=timeout)
        except serial.serialutil.SerialException:
            raise IOError('Could not open port %s' % portname)
        self.connected = True
        self.minDelay = 1 / speed

    def disconnect(self):
        """ Disconnect bus pirate, close com port """
        if self.port:
            self.port.close()
194

juhasch's avatar
juhasch committed
195 196 197 198
    def __exit__(self):
        """ Disconnect bus pirate when exiting"""
        self.disconnect()

199 200 201
    def timeout(self, timeout = 0.1):
        sleep(timeout)

Jürgen Hasch's avatar
Jürgen Hasch committed
202 203
    def write(self, value):
        self.port.write(value.to_bytes(1, 'big'))
204
        
Jürgen Hasch's avatar
Jürgen Hasch committed
205 206 207 208 209 210 211 212 213 214
    def response(self, byte_count=1, binary=False):
        """Request a number of bytes

        Parameters
        ----------
        byte_count : int
            Number of bytes to read
        binary : bool
            Return binary (True) or unicode values (False)
        """
215
        data = self.port.read(byte_count)
Jürgen Hasch's avatar
Jürgen Hasch committed
216 217
        if binary is True:
            return data
218 219 220 221 222 223
        else:
            return data.decode()

    def recurse_end(self):
        self._attempts_ = 0

juhasch's avatar
juhasch committed
224
    def recurse(self, func, *args):
225 226
        if self._attempts_ < 15:
            self._attempts_ += 1
juhasch's avatar
juhasch committed
227
            return func(*args)
228 229
        raise IOError('bus pirate malfunctioning')

juhasch's avatar
juhasch committed
230
    def recurse_flush(self, func, *args):
231 232 233 234
        if self._attempts_ < 15:
            self._attempts_ += 1
            for n in range(5):
                self.write(0x00)
Jürgen Hasch's avatar
Jürgen Hasch committed
235
                self.port.flushInput()
juhasch's avatar
juhasch committed
236
            return func(*args)
237 238
        raise IOError('bus pirate malfunctioning')

239 240 241 242 243 244 245

""" General Commands for Higher-Level Modes.
Note: Some of these do not have error checking implemented
(they return a 0 or 1.  You have to do your own error
checking.  This is as planned, since all of these
depend on the device you are interfacing with)"""

juhasch's avatar
juhasch committed
246

247 248
def send_start_bit(self):
    self.write(0x02)
juhasch's avatar
juhasch committed
249
    self.response(1, True)
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
    if self.response(1, True) == '\x01':
        self.recurse_end()
        return 1
    return self.recurse(self.send_start_bit)


def send_stop_bit(self):
    self.write(0x03)
    if self.response(1, True) == 'x01':
        self.recurse_end()
        return 1
    return self.recurse(self.send_stop_bit)


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)"""
    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
        # would have never returned any real data!
    else:
        self.write(0x04)
        return self.response(1, True)


def bulk_trans(self, byte_count=1, byte_string=None):
    """this is how you send data in most of the communication modes.
    See the i2c example function in common_functions.
    Send the data, and read the returned array.
    In I2C:  A '1' means that it was NOT ACKNOWLEDGED, and a '0' means that
    it WAS ACKNOWLEDGED (the reason for this is because this is what the
    bus pirate itself does...)
    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."""
    if byte_string is None:
        pass
    self.write(0x10 | (byte_count - 1))
    for i in range(byte_count):
        self.write(byte_string[i])
    data = self.response(byte_count + 1, True)
    if ord(data[0]) == 1:  # bus pirate sent an acknolwedge properly
        self.recurse_end()
        return data[1:]
    self.recurse(self.bulk_trans, byte_count, byte_string)