Skip to content

Commit 0bafa14

Browse files
authored
Merge pull request #1 from adafruit/feedback
addressing feedback from @jepler
2 parents bafc6db + 2222ece commit 0bafa14

File tree

8 files changed

+187
-131
lines changed

8 files changed

+187
-131
lines changed

adafruit_mcp2515/__init__.py

Lines changed: 49 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
`adafruit_mcp2515`
66
================================================================================
77
8-
A CircuitPython library for working with the MCP2515 CAN bus controller
8+
A CircuitPython library for working with the MCP2515 CAN bus controller using the
9+
CircuitPython `canio` API
910
1011
1112
* Author(s): Bryan Siepert
@@ -20,8 +21,7 @@
2021
* Adafruit CircuitPython firmware for the supported boards:
2122
https://github.com/adafruit/circuitpython/releases
2223
23-
# * Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
24-
# * Adafruit's Register library: https://github.com/adafruit/Adafruit_CircuitPython_Register
24+
* Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
2525
"""
2626

2727
from collections import namedtuple
@@ -30,6 +30,7 @@
3030
from micropython import const
3131
import adafruit_bus_device.spi_device as spi_device
3232
from .canio import *
33+
from .timer import Timer
3334

3435
__version__ = "0.0.0-auto.0"
3536
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_MCP2515.git"
@@ -162,12 +163,11 @@
162163
"TransmitBuffer",
163164
["CTRL_REG", "STD_ID_REG", "INT_FLAG_MASK", "LOAD_CMD", "SEND_CMD"],
164165
)
165-
# perhaps this will be stateful later? #TODO : dedup with above
166-
FilterMask = namedtuple(
167-
"FilterMask", ["CTRL_REG", "STD_ID_REG", "INT_FLAG_MASK", "LOAD_CMD", "SEND_CMD"],
168-
)
169166

167+
# This is magic, don't disturb the dragon
168+
# expects a 16Mhz crystal
170169
_BAUD_RATES = {
170+
# CNF1, CNF2, CNF3
171171
1000000: (0x00, 0xD0, 0x82),
172172
500000: (0x00, 0xF0, 0x86),
173173
250000: (0x41, 0xF1, 0x85),
@@ -209,7 +209,7 @@ def _tx_buffer_status_decode(status_byte):
209209

210210

211211
class MCP2515: # pylint:disable=too-many-instance-attributes
212-
"""Library for the MCP2515 CANbus controller"""
212+
"""A common shared-bus protocol."""
213213

214214
def __init__(
215215
self,
@@ -222,29 +222,35 @@ def __init__(
222222
auto_restart: bool = False,
223223
debug: bool = False
224224
):
225-
"""[summary]
226-
227-
Args:
228-
spi_bus (busio.SPI): The SPI bus used to communicate with the MCP2515
229-
cs_pin (digitalio.DigitalInOut): SPI bus enable pin
230-
baudrate (int, optional): The bit rate of the bus in Hz. All devices on the bus must \
231-
agree on this value. Defaults to 250000.
232-
loopback (bool, optional): When True the rx pin’s value is ignored, and the device \
233-
receives the packets it sends. Defaults to False.
234-
silent (bool, optional): When True the tx pin is always driven to the high logic level.\
235-
This mode can be used to “sniff” a CAN bus without interfering.. Defaults to False.
236-
auto_restart (bool, optional): If True, will restart communications after entering \
237-
bus-off state. Defaults to False.
238-
debug (bool, optional): If True, will enable printing debug information. Defaults to \
239-
False.
225+
"""A common shared-bus protocol.
226+
227+
:param ~busio.SPI spi: The SPI bus used to communicate with the MCP2515
228+
:param ~digitalio.DigitalInOut cs_pin: SPI bus enable pin
229+
:param int baudrate: The bit rate of the bus in Hz, using a 16Mhz crystal. All devices on\
230+
the bus must agree on this value. Defaults to 250000.
231+
:param bool loopback: Receive only packets sent from this device, and send only to this\
232+
device. Requires that `silent` is also set to `True`, but only prevents transmission to\
233+
other devices. Otherwise the send/receive behavior is normal.
234+
:param bool silent: When `True` the controller does not transmit and all messages are\
235+
received, ignoring errors and filters. This mode can be used to “sniff” a CAN bus without\
236+
interfering. Defaults to `False`.
237+
:param bool auto_restart: **Not supported by hardware. An `AttributeError` will be raised\
238+
if `auto_restart` is set to `True`** If `True`, will restart communications after entering\
239+
bus-off state. Defaults to `False`.
240+
241+
:param bool debug: If `True`, will enable printing debug information. Defaults to `False`.
240242
"""
241-
if loopback and silent:
242-
raise AttributeError("only loopback or silent mode can bet set, not both")
243+
244+
if loopback and not silent:
245+
raise AttributeError("Loopback mode requires silent to be set")
246+
if auto_restart:
247+
raise AttributeError("`auto-restart` is not supported by hardware")
248+
243249
self._auto_restart = auto_restart
244250
self._debug = debug
245251
self._bus_device_obj = spi_device.SPIDevice(spi_bus, cs_pin)
246252
self._cs_pin = cs_pin
247-
self._buffer = bytearray(255)
253+
self._buffer = bytearray(20)
248254
self._id_buffer = bytearray(4)
249255
self._unread_message_queue = []
250256
self._timer = Timer()
@@ -324,33 +330,20 @@ def initialize(self):
324330

325331
self._set_mode(new_mode)
326332

327-
def send(self, message_obj, wait_sent=True):
333+
def send(self, message_obj):
328334
"""Send a message on the bus with the given data and id. If the message could not be sent
329335
due to a full fifo or a bus error condition, RuntimeError is raised.
330336
331337
Args:
332338
message (canio.Message): The message to send. Must be a valid `canio.Message`
333339
"""
340+
334341
# TODO: Timeout
335342
tx_buff = self._get_tx_buffer() # info = addr.
343+
if tx_buff is None:
344+
raise RuntimeError("No transmit buffer available to send")
336345

337-
# TODO: set buffer priority
338-
self._write_message(tx_buff, message_obj)
339-
if not wait_sent:
340-
return True
341-
sleep(0.010)
342-
343-
send_confirmed = False
344-
self._timer.rewind_to(0.005)
345-
while not self._timer.expired:
346-
# the status register address is whatever tx_buff_n is, minus one?
347-
tx_buff_status = self._read_register(tx_buff.CTRL_REG)
348-
self._dbg(_tx_buffer_status_decode(tx_buff_status))
349-
send_confirmed = (tx_buff_status & _TXB_TXREQ_M) == 0
350-
if send_confirmed:
351-
return True
352-
353-
raise RuntimeError("Timeout occoured waiting for transmit confirmation")
346+
return self._write_message(tx_buff, message_obj)
354347

355348
@property
356349
def unread_message_count(self):
@@ -375,6 +368,9 @@ def read_message(self):
375368
return self._unread_message_queue.pop(0)
376369

377370
def _read_rx_buffer(self, read_command):
371+
for i in len(self._buffer):
372+
self._buffer[i] = 0
373+
378374
# read from buffer
379375
with self._bus_device_obj as spi:
380376
self._buffer[0] = read_command
@@ -407,7 +403,6 @@ def _read_rx_buffer(self, read_command):
407403
data=bytes(self._buffer[5 : 5 + message_length]),
408404
extended=extended,
409405
)
410-
411406
self._unread_message_queue.append(frame_obj)
412407

413408
def _read_from_rx_buffers(self):
@@ -427,6 +422,8 @@ def _read_from_rx_buffers(self):
427422

428423
def _write_message(self, tx_buffer, message_obj):
429424

425+
if tx_buffer is None:
426+
raise RuntimeError("No transmit buffer available to send")
430427
if isinstance(message_obj, RemoteTransmissionRequest):
431428
dlc = message_obj.length
432429
else:
@@ -471,6 +468,7 @@ def _write_message(self, tx_buffer, message_obj):
471468

472469
# send the frame based on the current buffers
473470
self._start_transmit(tx_buffer)
471+
return True
474472

475473
# TODO: Priority
476474
def _start_transmit(self, tx_buffer):
@@ -830,7 +828,7 @@ def listen(self, matches=None, *, timeout: float = 10):
830828
831829
An empty filter list causes all messages to be accepted.
832830
833-
Timeout dictates how long ``receive()`` and ``next()`` will block.
831+
Timeout dictates how long ``receive()`` will block.
834832
835833
Args:
836834
match (Optional[Sequence[Match]], optional): [description]. Defaults to None.
@@ -841,6 +839,11 @@ def listen(self, matches=None, *, timeout: float = 10):
841839
"""
842840
if matches is None:
843841
matches = []
842+
elif self.silent and not self.loopback:
843+
raise AttributeError(
844+
"Hardware does not support setting `matches` in when\
845+
`silent`==`True` and `loopback` == `False`"
846+
)
844847

845848
for match in matches:
846849
self._dbg("match:", match)

adafruit_mcp2515/canio/__init__.py

Lines changed: 4 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44
"""Python implementation of the CircuitPython core `canio` API"""
55
# pylint:disable=too-few-public-methods, invalid-name, redefined-builtin
66
import time
7+
from ..timer import Timer
78

89

910
class Message:
10-
"""A class representing a message to send on a `canio` bus
11-
"""
11+
"""A class representing a CANbus data frame"""
1212

1313
# pylint:disable=too-many-arguments,invalid-name,redefined-builtin
1414
def __init__(self, id, data, extended=False):
@@ -50,10 +50,10 @@ def data(self, new_data):
5050

5151

5252
class RemoteTransmissionRequest:
53-
"""RRTTTTRRRR """
53+
"""A class representing a CANbus remote frame"""
5454

5555
def __init__(self, id: int, length: int, *, extended: bool = False):
56-
"""Construct a Message to send on a CAN bus
56+
"""Construct a RemoteTransmissionRequest to send on a CAN bus
5757
5858
Args:
5959
id (int): The numeric ID of the requested message
@@ -205,29 +205,3 @@ def __init__(self, address: int, *, mask: int = 0, extended: bool = False):
205205
self.address = address
206206
self.mask = mask
207207
self.extended = extended
208-
209-
210-
############# non-api classes and methods
211-
class Timer:
212-
"""A class to track timeouts, like an egg timer
213-
"""
214-
215-
def __init__(self, timeout=0.0):
216-
self._timeout = None
217-
self._start_time = None
218-
if timeout:
219-
self.rewind_to(timeout)
220-
221-
@property
222-
def expired(self):
223-
"""Returns the expiration status of the timer
224-
225-
Returns:
226-
bool: True if more than `timeout` seconds has past since it was set
227-
"""
228-
return (time.monotonic() - self._start_time) > self._timeout
229-
230-
def rewind_to(self, new_timeout):
231-
"""Re-wind the timer to a new timeout and start ticking"""
232-
self._timeout = float(new_timeout)
233-
self._start_time = time.monotonic()

adafruit_mcp2515/timer.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2020 Bryan Siepert for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: MIT
4+
"""Provides a simple timer class; see `Timer`"""
5+
from time import monotonic
6+
7+
8+
class Timer:
9+
"""A reusable class to track timeouts, like an egg timer"""
10+
11+
def __init__(self, timeout=0.0):
12+
self._timeout = None
13+
self._start_time = None
14+
if timeout:
15+
self.rewind_to(timeout)
16+
17+
@property
18+
def expired(self):
19+
"""Returns the expiration status of the timer
20+
21+
Returns:
22+
bool: True if more than `timeout` seconds has past since it was set
23+
"""
24+
return (monotonic() - self._start_time) > self._timeout
25+
26+
def rewind_to(self, new_timeout):
27+
"""Re-wind the timer to a new timeout and start ticking"""
28+
self._timeout = float(new_timeout)
29+
self._start_time = monotonic()

examples/canio_test.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
# SPDX-FileCopyrightText: Copyright (c) 2020 Jeff Epler for Adafruit Industries
22
#
33
# SPDX-License-Identifier: MIT
4+
5+
###################### Board/ Config selection #################
6+
# This is is only on way to make this test work for other boards.
7+
# As an alternative, CAN_TYPE could be set manually and
8+
# board-specific expectations can be if/else'd, and support for a
9+
# new CAN controller or peripheral would start with defining the
10+
# controller-specifc bits
11+
412
CAN_TYPE = None
513
try:
614
from canio import (
@@ -37,8 +45,10 @@ def builtin_bus_factory():
3745
def builtin_bus_factory():
3846
cs = DigitalInOut(board.D5)
3947
cs.switch_to_output()
40-
return CAN(board.SPI(), cs, baudrate=1000000, loopback=True)
48+
return CAN(board.SPI(), cs, baudrate=1000000, loopback=True, silent=True)
49+
4150

51+
################################################################
4252

4353
max_standard_id = 0x7FF
4454
max_extended_id = 0x1FFFFFFF

examples/mcp2515_loopback_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
def bus():
2121
cs = DigitalInOut(CS_PIN)
2222
cs.switch_to_output()
23-
return CAN(SPI(), cs, loopback=True)
23+
return CAN(SPI(), cs, loopback=True, silent=True)
2424

2525

2626
mb1 = [0xDE, 0xAD, 0xBE, 0xEF]

0 commit comments

Comments
 (0)