From f9b28ed77cf3bcf3bf4260e4564d573cb9d053de Mon Sep 17 00:00:00 2001 From: ghfbsd Date: Thu, 9 Jan 2025 16:51:41 +0000 Subject: [PATCH 1/9] BUG FIX: begin() ignored spics= parameter and always uses pin 9 --- canbus/CAN.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/canbus/CAN.py b/canbus/CAN.py index 4ca9b1b..e64105f 100644 --- a/canbus/CAN.py +++ b/canbus/CAN.py @@ -43,7 +43,7 @@ def _get_frame(self): class CAN_1: ERROR = ERROR def __init__(self, board: str = "CANBed_RP2040", spi: int = 0, spics: int = 9): - self.can = CAN(SPI(cs=9)) + self.can = CAN(SPI(cs=spics)) def begin(self, bitrate: int = CAN_SPEED.CAN_500KBPS, canclock: int = CAN_CLOCK.MCP_16MHZ, mode: str = 'normal'): ret = self.can.reset() if ret != ERROR.ERROR_OK: @@ -75,4 +75,4 @@ def recv(self): def send(self, msg): frame = msg._get_frame() error = self.can.sendMessage(frame) - return error \ No newline at end of file + return error From d6ba0dc6672f7ae7041794ecc413a5f57b30b58a Mon Sep 17 00:00:00 2001 From: ghfbsd Date: Thu, 9 Jan 2025 16:53:51 +0000 Subject: [PATCH 2/9] BUG FIX: Causes fault if CAN message payload is absent. --- canbus/CAN.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/canbus/CAN.py b/canbus/CAN.py index e64105f..dde3ae8 100644 --- a/canbus/CAN.py +++ b/canbus/CAN.py @@ -70,7 +70,7 @@ def checkReceive(self): def recv(self): error, frame = self.can.readMessage() msg = CanMsg() - msg._set_frame(frame) + if msg is not None: msg._set_frame(frame) return error, msg def send(self, msg): frame = msg._get_frame() From 14709fbdbed93c542d1465ca99c7e563311335da Mon Sep 17 00:00:00 2001 From: ghfbsd Date: Thu, 9 Jan 2025 16:57:36 +0000 Subject: [PATCH 3/9] Add new CAN_1 methods that basically expose lower-level functionality for testing and so that interrupt programming can be done with the CAN transceiver. setLoopback(): Sets interface into loopback mode. Useful for test programs. getInterrupts(), clearInterrupts(), getInterruptMask(): Interrupt programming functionality. getErrorFlags(), clearErrorFlags(): Error handling during Interrupts. --- canbus/CAN.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/canbus/CAN.py b/canbus/CAN.py index dde3ae8..18c82f9 100644 --- a/canbus/CAN.py +++ b/canbus/CAN.py @@ -53,6 +53,20 @@ def begin(self, bitrate: int = CAN_SPEED.CAN_500KBPS, canclock: int = CAN_CLOCK. return ret ret = self.can.setNormalMode() return ret + def setLoopback(self): + return self.can.setLoopbackMode() + def clearInterrupts(self): + self.can.clearInterrupts() + def getInterrupts(self): + return self.can.getInterrupts() + def getInterruptMask(self): + return self.can.getInterruptMask() + def getErrorFlags(self): + return self.can.getErrorFlags() + def clearErrorFlags(self,RXERR=False): + # Keyword args for clearing more conditions in future if required, e.g. + # TXBO, passive errors, warnings + if RXERR: self.can.clearRXnOVRFlags() def init_mask(self, mask, is_ext_id, mask_id): ret = self.can.setFilterMask(mask + 1, is_ext_id, mask_id) if ret != ERROR.ERROR_OK: From 4451659ef901647d547fdf0342821218373e551f Mon Sep 17 00:00:00 2001 From: ghfbsd Date: Fri, 10 Jan 2025 15:38:53 +0000 Subject: [PATCH 4/9] Added getStatus() to get CAN device status --- canbus/CAN.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/canbus/CAN.py b/canbus/CAN.py index 18c82f9..4a3ec73 100644 --- a/canbus/CAN.py +++ b/canbus/CAN.py @@ -67,6 +67,8 @@ def clearErrorFlags(self,RXERR=False): # Keyword args for clearing more conditions in future if required, e.g. # TXBO, passive errors, warnings if RXERR: self.can.clearRXnOVRFlags() + def getStatus(self): + return self.can.getStatus() def init_mask(self, mask, is_ext_id, mask_id): ret = self.can.setFilterMask(mask + 1, is_ext_id, mask_id) if ret != ERROR.ERROR_OK: From 099e64885d7c833b90b839d1cb4e061a66c55a17 Mon Sep 17 00:00:00 2001 From: ghfbsd Date: Sat, 18 Jan 2025 14:52:44 +0000 Subject: [PATCH 5/9] BUG FIX: Incorrect check for frame; if not present, probably an error --- canbus/CAN.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/canbus/CAN.py b/canbus/CAN.py index 4a3ec73..0b68c1b 100644 --- a/canbus/CAN.py +++ b/canbus/CAN.py @@ -86,7 +86,7 @@ def checkReceive(self): def recv(self): error, frame = self.can.readMessage() msg = CanMsg() - if msg is not None: msg._set_frame(frame) + if frame is not None: msg._set_frame(frame) return error, msg def send(self, msg): frame = msg._get_frame() From e0f889d7837016c99801a96208c8951d8a4f8833 Mon Sep 17 00:00:00 2001 From: ghfbsd Date: Tue, 29 Apr 2025 19:28:55 +0100 Subject: [PATCH 6/9] Add error decoding facility, error clearing facility. --- canbus/CAN.py | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/canbus/CAN.py b/canbus/CAN.py index 0b68c1b..77aa1d3 100644 --- a/canbus/CAN.py +++ b/canbus/CAN.py @@ -15,6 +15,72 @@ class CanError: ERROR_OK = ERROR.ERROR_OK ERROR_FAIL = ERROR.ERROR_FAIL + pfx = '' + + def _cat(txt): + res, CanError.pfx = CanError.pfx + txt, ',' + return res + + # Decodes contents of various MCP2015 status registers + # Returns blank-delimited string, one for each keyword-value, + # e.g. for 3 keywords: + # {status_reg_flags} {interrupt_reg_flags} {error_reg_flags} + # e.g. for 2 keywords (status= and error=): + # {status_reg_flags} {error_reg_flags} + # e.g. for 1 keyword (interrupt=): + # {interrupt_reg_flags} + + @classmethod + def decode(cls,status=None,interrupt=None,error=None): + res, nxt = '', '' + if status is not None: + CanError.pfx = nxt + '{' + bits = status & 0xe0 + if bits == 0x00: res += cls._cat('Normal-mode') + if bits == 0x20: res += cls._cat('Sleep-mode') + if bits == 0x40: res += cls._cat('Loopback-mode') + if bits == 0x60: res += cls._cat('Listen-only-mode') + if bits == 0x80: res += cls._cat('Config-mode') + bits = status & 0x0e + if bits == 0x00: res += cls._cat('No-interrupt') + if bits == 0x02: res += cls._cat('Error-interrupt') + if bits == 0x04: res += cls._cat('Wake-up-interrupt') + if bits == 0x06: res += cls._cat('TXB0-interrupt') + if bits == 0x08: res += cls._cat('TXB1-interrupt') + if bits == 0x0a: res += cls._cat('TXB2-interrupt') + if bits == 0x0c: res += cls._cat('RXB0-interrupt') + if bits == 0x0e: res += cls._cat('RXB1-interrupt') + res += '}' + nxt = ' ' + + if interrupt is not None: + CanError.pfx = nxt + '{' + if interrupt & 0x80: res += cls._cat('Message-error') + if interrupt & 0x40: res += cls._cat('Wake-up') + if interrupt & 0x20: res += cls._cat('Error') + if interrupt & 0x10: res += cls._cat('Xmtbuf2-empty') + if interrupt & 0x08: res += cls._cat('Xmtbuf1-empty') + if interrupt & 0x04: res += cls._cat('Xmtbuf0-empty') + if interrupt & 0x02: res += cls._cat('Rcvbuf1-full') + if interrupt & 0x02: res += cls._cat('Rcvbuf0-full') + res += '}' + nxt = ' ' + + if error is not None: + CanError.pfx = nxt + '{' + if error & 0x80: res += cls._cat('Rcvbuf1-overflow') + if error & 0x40: res += cls._cat('Rcvbuf0-overflow') + if error & 0x20: res += cls._cat('Bus-off') + if error & 0x10: res += cls._cat('Xmterror-pasv') + if error & 0x08: res += cls._cat('Rcverror-pasv') + if error & 0x04: res += cls._cat('Xmterror-warn') + if error & 0x02: res += cls._cat('Rcverror-warn') + if error & 0x01: res += cls._cat('Error-warn') + res += '}' + + return res + + class CanMsgFlag: RTR = CAN_ERR_FLAG EFF = CAN_EFF_FLAG @@ -63,10 +129,11 @@ def getInterruptMask(self): return self.can.getInterruptMask() def getErrorFlags(self): return self.can.getErrorFlags() - def clearErrorFlags(self,RXERR=False): + def clearErrorFlags(self, RXERR=False, MERR=False): # Keyword args for clearing more conditions in future if required, e.g. # TXBO, passive errors, warnings if RXERR: self.can.clearRXnOVRFlags() + if MERR: self.can.clearMERR() def getStatus(self): return self.can.getStatus() def init_mask(self, mask, is_ext_id, mask_id): From a878701f9d19ea42887a90e141b00364c59acd5b Mon Sep 17 00:00:00 2001 From: ghfbsd Date: Tue, 29 Apr 2025 21:28:32 +0100 Subject: [PATCH 7/9] Add routine to decode MCP2515 error registers. --- canbus/CAN.py | 80 ++++++++++++++++++++++++++------------------------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/canbus/CAN.py b/canbus/CAN.py index 77aa1d3..0eb592c 100644 --- a/canbus/CAN.py +++ b/canbus/CAN.py @@ -15,12 +15,6 @@ class CanError: ERROR_OK = ERROR.ERROR_OK ERROR_FAIL = ERROR.ERROR_FAIL - pfx = '' - - def _cat(txt): - res, CanError.pfx = CanError.pfx + txt, ',' - return res - # Decodes contents of various MCP2015 status registers # Returns blank-delimited string, one for each keyword-value, # e.g. for 3 keywords: @@ -32,50 +26,58 @@ def _cat(txt): @classmethod def decode(cls,status=None,interrupt=None,error=None): - res, nxt = '', '' + + def cat(txt): + nonlocal pfx + res, pfx = pfx + txt, ',' + return res + + res, nxt, pfx = '', '', '' if status is not None: - CanError.pfx = nxt + '{' + res += '{' bits = status & 0xe0 - if bits == 0x00: res += cls._cat('Normal-mode') - if bits == 0x20: res += cls._cat('Sleep-mode') - if bits == 0x40: res += cls._cat('Loopback-mode') - if bits == 0x60: res += cls._cat('Listen-only-mode') - if bits == 0x80: res += cls._cat('Config-mode') + if bits == 0x00: res += cat('Normal-mode') + if bits == 0x20: res += cat('Sleep-mode') + if bits == 0x40: res += cat('Loopback-mode') + if bits == 0x60: res += cat('Listen-only-mode') + if bits == 0x80: res += cat('Config-mode') bits = status & 0x0e - if bits == 0x00: res += cls._cat('No-interrupt') - if bits == 0x02: res += cls._cat('Error-interrupt') - if bits == 0x04: res += cls._cat('Wake-up-interrupt') - if bits == 0x06: res += cls._cat('TXB0-interrupt') - if bits == 0x08: res += cls._cat('TXB1-interrupt') - if bits == 0x0a: res += cls._cat('TXB2-interrupt') - if bits == 0x0c: res += cls._cat('RXB0-interrupt') - if bits == 0x0e: res += cls._cat('RXB1-interrupt') + if bits == 0x00: res += cat('No-interrupt') + if bits == 0x02: res += cat('Error-interrupt') + if bits == 0x04: res += cat('Wake-up-interrupt') + if bits == 0x06: res += cat('TXB0-interrupt') + if bits == 0x08: res += cat('TXB1-interrupt') + if bits == 0x0a: res += cat('TXB2-interrupt') + if bits == 0x0c: res += cat('RXB0-interrupt') + if bits == 0x0e: res += cat('RXB1-interrupt') res += '}' nxt = ' ' if interrupt is not None: - CanError.pfx = nxt + '{' - if interrupt & 0x80: res += cls._cat('Message-error') - if interrupt & 0x40: res += cls._cat('Wake-up') - if interrupt & 0x20: res += cls._cat('Error') - if interrupt & 0x10: res += cls._cat('Xmtbuf2-empty') - if interrupt & 0x08: res += cls._cat('Xmtbuf1-empty') - if interrupt & 0x04: res += cls._cat('Xmtbuf0-empty') - if interrupt & 0x02: res += cls._cat('Rcvbuf1-full') - if interrupt & 0x02: res += cls._cat('Rcvbuf0-full') + res += nxt + '{' + pfx = '' + if interrupt & 0x80: res += cat('Message-error') + if interrupt & 0x40: res += cat('Wake-up') + if interrupt & 0x20: res += cat('Error') + if interrupt & 0x10: res += cat('Xmtbuf2-empty') + if interrupt & 0x08: res += cat('Xmtbuf1-empty') + if interrupt & 0x04: res += cat('Xmtbuf0-empty') + if interrupt & 0x02: res += cat('Rcvbuf1-full') + if interrupt & 0x02: res += cat('Rcvbuf0-full') res += '}' nxt = ' ' if error is not None: - CanError.pfx = nxt + '{' - if error & 0x80: res += cls._cat('Rcvbuf1-overflow') - if error & 0x40: res += cls._cat('Rcvbuf0-overflow') - if error & 0x20: res += cls._cat('Bus-off') - if error & 0x10: res += cls._cat('Xmterror-pasv') - if error & 0x08: res += cls._cat('Rcverror-pasv') - if error & 0x04: res += cls._cat('Xmterror-warn') - if error & 0x02: res += cls._cat('Rcverror-warn') - if error & 0x01: res += cls._cat('Error-warn') + res += nxt + '{' + pfx = '' + if error & 0x80: res += cat('Rcvbuf1-overflow') + if error & 0x40: res += cat('Rcvbuf0-overflow') + if error & 0x20: res += cat('Bus-off') + if error & 0x10: res += cat('Xmterror-pasv') + if error & 0x08: res += cat('Rcverror-pasv') + if error & 0x04: res += cat('Xmterror-warn') + if error & 0x02: res += cat('Rcverror-warn') + if error & 0x01: res += cat('Error-warn') res += '}' return res From f23641b403b77189bdcdda821f95901b1525d56f Mon Sep 17 00:00:00 2001 From: ghfbsd Date: Thu, 15 Jan 2026 11:18:07 +0000 Subject: [PATCH 8/9] Speed and memory efficiency improvements to CAN library - Uses static buffers for I/O avoiding changes to heap - Offers recvinto() for use with preallocated buffers - Corrects/eliminates overly-long timing delays --- canbus/CAN.py | 5 ++++ canbus/internal/can/can.py | 20 +++++++------ canbus/internal/can/mcp2515.py | 52 +++++++++++++++++++++++++++------- canbus/internal/spi/spi.py | 24 ++++++++++------ readme.md | 4 ++- 5 files changed, 76 insertions(+), 29 deletions(-) diff --git a/canbus/CAN.py b/canbus/CAN.py index 0eb592c..197ca82 100644 --- a/canbus/CAN.py +++ b/canbus/CAN.py @@ -157,6 +157,11 @@ def recv(self): msg = CanMsg() if frame is not None: msg._set_frame(frame) return error, msg + def recvinto(self,msg): + frame = msg._get_frame() + error = self.can.readMessageInto(frame) + msg._set_frame(frame) + return error def send(self, msg): frame = msg._get_frame() error = self.can.sendMessage(frame) diff --git a/canbus/internal/can/can.py b/canbus/internal/can/can.py index 8312207..af0dda9 100644 --- a/canbus/internal/can/can.py +++ b/canbus/internal/can/can.py @@ -31,8 +31,14 @@ def __init__(self, can_id: int, data: bytes = b"") -> None: # # 32 bit CAN ID + EFF/RTR/ERR flags # - self.can_id = can_id # type: int - self.data = data # type: bytes + self._can_id = can_id # type: int + self._arbitration_id = can_id & CAN_EFF_MASK # type: int + self._data = bytearray(CAN_MAX_DLEN) # type: bytes + if data is not None: + self._dlc = len(data) + self._data[0:len(data)] = data + else: + self._dlc = 0 @property def can_id(self) -> int: @@ -49,17 +55,15 @@ def data(self) -> bytes: @data.setter def data(self, data: bytes) -> None: - self._data = b"" # type: bytes - self._dlc = 0 # frame payload length in byte (0 .. CAN_MAX_DLEN) - if not data: + self._dlc = 0 # frame payload length in byte (0 .. CAN_MAX_DLEN) return if len(data) > CAN_MAX_DLEN: raise Exception("The CAN frame data length exceeds the maximum") - self._data = data self._dlc = len(data) + self._data[0:len(data)] = data[0:len(data)] @property def arbitration_id(self) -> int: @@ -85,6 +89,6 @@ def __str__(self) -> str: data = ( "remote request" if self.is_remote_frame - else " ".join("{:02X}".format(b) for b in self.data) + else " ".join("{:02X}".format(b) for b in self._data[0:self._dlc]) ) - return "{: >8X} [{}] {}".format(self.arbitration_id, self.dlc, data) + return "{:08X} [{}] {}".format(self.arbitration_id, self._dlc, data) diff --git a/canbus/internal/can/mcp2515.py b/canbus/internal/can/mcp2515.py index 3bff7f2..ab2c104 100644 --- a/canbus/internal/can/mcp2515.py +++ b/canbus/internal/can/mcp2515.py @@ -167,17 +167,16 @@ def readRegister(self, reg: int) -> int: return ret - def readRegisters(self, reg: int, n: int) -> List[int]: + def readRegisters(self, reg: int, n: int, buf: bytearray = bytearray(CAN_MAX_DLEN)) -> bytearray: self.SPI.start() self.SPI.transfer(INSTRUCTION.INSTRUCTION_READ) self.SPI.transfer(reg) # MCP2515 has auto-increment of address-pointer - values = [] for i in range(n): - values.append(self.SPI.transfer(read=True)) + buf[i] = self.SPI.transfer(read=True) self.SPI.end() - return values + return buf def setRegister(self, reg: int, value: int) -> None: self.SPI.start() @@ -392,9 +391,40 @@ def sendMessage_(self, frame: Any) -> int: return ERROR.ERROR_ALLTXBUSY + def readMessageInto(self, frame: Any, rxbn: int = None) -> int: + if rxbn is None: + return self.readMessageInto_(frame) + + rxb = RXB[rxbn] + + tbufdata = self.readRegisters(rxb.SIDH, 1 + CAN_IDLEN) + + id_ = (tbufdata[MCP_SIDH] << 3) + (tbufdata[MCP_SIDL] >> 5) + + if (tbufdata[MCP_SIDL] & TXB_EXIDE_MASK) == TXB_EXIDE_MASK: + id_ = (id_ << 2) + (tbufdata[MCP_SIDL] & 0x03) + id_ = (id_ << 8) + tbufdata[MCP_EID8] + id_ = (id_ << 8) + tbufdata[MCP_EID0] + id_ |= CAN_EFF_FLAG + + dlc = tbufdata[MCP_DLC] & DLC_MASK + if dlc > CAN_MAX_DLEN: + return ERROR.ERROR_FAIL + + ctrl = self.readRegister(rxb.CTRL) + if ctrl & RXBnCTRL_RTR: + id_ |= CAN_RTR_FLAG + + frame.can_id = id_ + + frame.data = self.readRegisters(rxb.DATA, dlc)[0:dlc] + + return ERROR.ERROR_OK + def readMessage(self, rxbn: int = None) -> Tuple[int, Any]: if rxbn is None: - return self.readMessage_() + frame = CANFrame(0) + return self.readMessageInto_(frame), frame rxb = RXB[rxbn] @@ -416,23 +446,23 @@ def readMessage(self, rxbn: int = None) -> Tuple[int, Any]: if ctrl & RXBnCTRL_RTR: id_ |= CAN_RTR_FLAG - frame = CANFrame(can_id=id_) + frame = CANFrame(id_) - frame.data = bytearray(self.readRegisters(rxb.DATA, dlc)) + frame.data = copy.copy(self.readRegisters(rxb.DATA, dlc)[0:dlc]) return ERROR.ERROR_OK, frame - def readMessage_(self) -> Tuple[int, Any]: - rc = ERROR.ERROR_NOMSG, None + def readMessageInto_(self, frame: Any) -> int: + rc = ERROR.ERROR_NOMSG stat = self.getStatus() if stat & STAT.STAT_RX0IF and self.mcp2515_rx_index == 0: - rc = self.readMessage(RXBn.RXB0) + rc = self.readMessageInto(frame,RXBn.RXB0) if self.getStatus() & STAT.STAT_RX1IF: self.mcp2515_rx_index = 1 self.modifyRegister(REGISTER.MCP_CANINTF, RXB[RXBn.RXB0].CANINTFRXnIF, 0) elif stat & STAT.STAT_RX1IF: - rc = self.readMessage(RXBn.RXB1) + rc = self.readMessageInto(frame,RXBn.RXB1) self.mcp2515_rx_index = 0 self.modifyRegister(REGISTER.MCP_CANINTF, RXB[RXBn.RXB1].CANINTFRXnIF, 0) diff --git a/canbus/internal/spi/spi.py b/canbus/internal/spi/spi.py index 3950f16..8662eaa 100644 --- a/canbus/internal/spi/spi.py +++ b/canbus/internal/spi/spi.py @@ -16,6 +16,7 @@ class SPI: def __init__(self, cs: int, baudrate: int = SPI_DEFAULT_BAUDRATE) -> None: + # Somehow the self.init() call invokes the machine.SPI .init() method?! self._SPICS = Pin(cs, Pin.OUT) self._SPI = self.init(baudrate=baudrate) # type: Any self.end() @@ -25,23 +26,28 @@ def init(self, baudrate: int) -> Any: def start(self) -> None: self._SPICS.value(0) - time.sleep_us(SPI_HOLD_US) # type: ignore + ### time.sleep_us(SPI_HOLD_US) # type: ignore # MCP2515 spec = 50 ns not us def end(self) -> None: self._SPICS.value(1) - time.sleep_us(SPI_HOLD_US) # type: ignore - - def transfer(self, value: int = SPI_DUMMY_INT, read: bool = False) -> Optional[int]: + ### time.sleep_us(SPI_HOLD_US) # type: ignore # MCP2515 spec = 50 ns not us + + def transfer(self, + value: int = SPI_DUMMY_INT, + read: bool = False, + val: bytes = bytearray(SPI_TRANSFER_LEN), + buf: bytes = bytearray(SPI_TRANSFER_LEN) + ) -> Optional[int]: """Write int value to SPI and read SPI as int value simultaneously. This method supports transfer single byte only, and the system byte order doesn't matter because of that. The input and output int value are unsigned. + val, buf used for static allocation so no changes to heap """ - value_as_byte = value.to_bytes(SPI_TRANSFER_LEN, sys.byteorder) + val = value.to_bytes(SPI_TRANSFER_LEN, sys.byteorder) if read: - output = bytearray(SPI_TRANSFER_LEN) - self._SPI.write_readinto(value_as_byte, output) - return int.from_bytes(output, sys.byteorder) - self._SPI.write(value_as_byte) + self._SPI.write_readinto(val, buf) + return int(buf[0]) + self._SPI.write(val) return None diff --git a/readme.md b/readme.md index 72688b9..0df52e1 100644 --- a/readme.md +++ b/readme.md @@ -1,11 +1,13 @@ # MicroPython CAN Bus Library MicroPython library for MCP2515, it works for most of the MicroPython boards. +This version of the library is extended to optimize for speed and to use +static memory as much as possible for reliable use in IRQs. With this library, you can, - Send a CAN 2.0 frame -- Receive a CAN 2.0 frame +- Receive a CAN 2.0 frame (as a new buffer or into a pre-allocated buffer) - Get data from OBD-II - Set the masks and filters, there're 32 masks and filters. From a1796dbb6429e05296a0d7988ecd889b989a53d9 Mon Sep 17 00:00:00 2001 From: ghfbsd Date: Mon, 19 Jan 2026 16:00:51 +0000 Subject: [PATCH 9/9] Add decode for errors reported in the TXBnCTRL register --- canbus/CAN.py | 16 +++++++++++++++- readme.md | 3 ++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/canbus/CAN.py b/canbus/CAN.py index 197ca82..3772870 100644 --- a/canbus/CAN.py +++ b/canbus/CAN.py @@ -23,9 +23,10 @@ class CanError: # {status_reg_flags} {error_reg_flags} # e.g. for 1 keyword (interrupt=): # {interrupt_reg_flags} + # txctl: TXBnCTRL @classmethod - def decode(cls,status=None,interrupt=None,error=None): + def decode(cls,status=None,interrupt=None,error=None,txctl=None): def cat(txt): nonlocal pfx @@ -53,6 +54,19 @@ def cat(txt): res += '}' nxt = ' ' + if txctl is not None: + res += nxt + '{' + pfx = '' + if txctl & 0x80: res += cat('') + if txctl & 0x40: res += cat('Msg-abort') + if txctl & 0x20: res += cat('Msg-lostarb') + if txctl & 0x10: res += cat('Msg-xmt-error') + if txctl & 0x08: res += cat('Msg-xmt-req') + if txctl & 0x04: res += cat('') + if txctl & 0x02: res += cat('Msg-prio1') + if txctl & 0x01: res += cat('Msg-prio0') + res += '}' + if interrupt is not None: res += nxt + '{' pfx = '' diff --git a/readme.md b/readme.md index 0df52e1..ad684be 100644 --- a/readme.md +++ b/readme.md @@ -9,7 +9,8 @@ With this library, you can, - Send a CAN 2.0 frame - Receive a CAN 2.0 frame (as a new buffer or into a pre-allocated buffer) - Get data from OBD-II -- Set the masks and filters, there're 32 masks and filters. +- Set the masks and filters, there're 32 masks and filters +- Decode various error registers into human-readable form. ## Getting Started