This demo shows one of the possible ways you can verify that the intended node has received a message.
Before trying this demo, make sure you have the right version of CircuitPython, that import canio succeeds, and that you've wired the two Feathers together as shown on the Wiring page.
First, we'll set up the listening (receiving) node. Put the text below in that device's code.py and open up the serial terminal. When the program restarts, it will display "No messsage received within timeout" until our second device is up and running.
# SPDX-FileCopyrightText: 2020 Jeff Epler for Adafruit Industries # # SPDX-License-Identifier: MIT import struct import board import canio import digitalio # If the CAN transceiver has a standby pin, bring it out of standby mode if hasattr(board, 'CAN_STANDBY'): standby = digitalio.DigitalInOut(board.CAN_STANDBY) standby.switch_to_output(False) # If the CAN transceiver is powered by a boost converter, turn on its supply if hasattr(board, 'BOOST_ENABLE'): boost_enable = digitalio.DigitalInOut(board.BOOST_ENABLE) boost_enable.switch_to_output(True) # Use this line if your board has dedicated CAN pins. (Feather M4 CAN and Feather STM32F405) can = canio.CAN(rx=board.CAN_RX, tx=board.CAN_TX, baudrate=250_000, auto_restart=True) # On ESP32S2 most pins can be used for CAN. Uncomment the following line to use IO5 and IO6 #can = canio.CAN(rx=board.IO6, tx=board.IO5, baudrate=250_000, auto_restart=True) listener = can.listen(matches=[canio.Match(0x408)], timeout=.9) old_bus_state = None old_count = -1 while True: bus_state = can.state if bus_state != old_bus_state: print(f"Bus state changed to {bus_state}") old_bus_state = bus_state message = listener.receive() if message is None: print("No messsage received within timeout") continue data = message.data if len(data) != 8: print(f"Unusual message length {len(data)}") continue count, now_ms = struct.unpack("<II", data) gap = count - old_count old_count = count print(f"received message: id={message.id:x} count={count} now_ms={now_ms}") if gap != 1: print(f"gap: {gap}") print(f"Sending ACK: {count}") can.send(canio.Message(id=0x409, data=struct.pack("<I", count)))
Next, set up the transmitting (sending) node. Put the text below in that device's code.py and open up a second serial terminal. Once both programs are running, you should see the sender and receiver printing the same information.
# SPDX-FileCopyrightText: 2020 Jeff Epler for Adafruit Industries # # SPDX-License-Identifier: MIT import struct import time import board import canio import digitalio # If the CAN transceiver has a standby pin, bring it out of standby mode if hasattr(board, 'CAN_STANDBY'): standby = digitalio.DigitalInOut(board.CAN_STANDBY) standby.switch_to_output(False) # If the CAN transceiver is powered by a boost converter, turn on its supply if hasattr(board, 'BOOST_ENABLE'): boost_enable = digitalio.DigitalInOut(board.BOOST_ENABLE) boost_enable.switch_to_output(True) # Use this line if your board has dedicated CAN pins. (Feather M4 CAN and Feather STM32F405) can = canio.CAN(rx=board.CAN_RX, tx=board.CAN_TX, baudrate=250_000, auto_restart=True) # On ESP32S2 most pins can be used for CAN. Uncomment the following line to use IO5 and IO6 #can = canio.CAN(rx=board.IO6, tx=board.IO5, baudrate=250_000, auto_restart=True) listener = can.listen(matches=[canio.Match(0x409)], timeout=.1) old_bus_state = None count = 0 while True: bus_state = can.state if bus_state != old_bus_state: print(f"Bus state changed to {bus_state}") old_bus_state = bus_state now_ms = (time.monotonic_ns() // 1_000_000) & 0xffffffff print(f"Sending message: count={count} now_ms={now_ms}") message = canio.Message(id=0x408, data=struct.pack("<II", count, now_ms)) # Keep trying to send the same message until confirmed. while True: received_ack_confirmed = False can.send(message) # Read in all messages. for message_in in listener: data = message_in.data if len(data) != 4: print(f"Unusual message length {len(data)}") continue ack_count = struct.unpack("<I", data)[0] if ack_count == count: print(f"Received ACK: {ack_count}") received_ack_confirmed = True break print(f"Received incorrect ACK: {ack_count} should be {count}") if received_ack_confirmed: break print(f"No ACK received within receive timeout, sending {count} again") time.sleep(.5) count += 1
In this case, the data being transmitted is a packet counter which starts at 0 and counts up 1 for each transmitted packet; and a timestamp which counts up by 1000 each second. These are sent as 4 byte values, for a total of 8 bytes—the maximum for a packet on the CAN bus.
When the receiver receives an acknowledgement packet (an ACK), it sends back one of its own, containing just the 4 byte count value. A different message ID (0x409) is also used.
The sender waits for the ACK. If it is not received shortly, then it re-sends the original message and waits for the ACK again. In this way, the sender knows the receiver has received each message.
Typical output on sending node:
code.py output: Bus state changed to canio.BusState.ERROR_ACTIVE Sending message: count=0 now_ms=123231 Received ACK Sending message: count=1 now_ms=123733 Received ACK Sending message: count=2 now_ms=124235 Received ACK
Typical output on receiving node:
code.py output: Bus state changed to canio.BusState.ERROR_ACTIVE No messsage received within timeout received message: id=408 count=0 now_ms=123231 Sending ACK received message: id=408 count=1 now_ms=123733 Sending ACK received message: id=408 count=2 now_ms=124235 Sending ACK
Text editor powered by tinymce.