組込み

【RasPi】4. 1. RaspberryPiでCAN通信

1.記事一覧

記事は複数回に分けて投稿する予定です。AMD/XilinxのARM SoC(ZynqMP Kria K26 SOM)で動作する、AMD/Xilinx公式認定Ubuntuやベアメタル開発を勉強していく覚書きです。

※今回はZynqMPの話は出てきません。

Zynq7000(armv7)版はこちら

2.LinuxからCAN通信を試す

2-1. CANループバック実験の概要

「CANモータを試す」計画の第一段階「RaspberryPiとCANアダプタでCANループバック実験」をやってみます。今回PC-CANブリッジアダプタを用意できないので、デバッグ検証用としてまともに動く環境を1つ用意するのが目的になります。これがないとロジアナしかデバッグに使えません(さすがにしんどい)。

CANにはポート単体で送信と受信をバイパスするループバック機能がありますが、今回は2つのCANポートを物理的に配線するループバックを指しています。

目的1:今後のデバッグ用に確実に動作する環境が欲しいため
目的2:LinuxのI/Fとツールの使用感を確かめるため

今回やること

  • RaspberryPiと絶縁CAN HATのセットアップ
  • can-utilsで疎通確認
  • PythonでCAN通信
  • ロジック・アナライザでバスの波形確認

2-2. 絶縁CAN-HATについて

Waveshare製の2-Channel CAN HAT(CAN2.0B版)を使用します。スイッチサイエンスさんで購入しました。

特徴

  • MCP2515 SPI-CANブリッジICを使用
  • デジタルアイソレータと絶縁DC−DCによるガルバニック絶縁
  • Raspberry Pi 4BでLinuxドライバとDeviceTreeOverlay対応済
  • 3.3V CAN2.0B 2ch搭載(CANFD不可)
  • 終端抵抗の接続の有無を選択可
  • CANトランシーバSN65HVD230(max 1Mbps)

このCAN基板はCAN信号が絶縁されています。電源もGNDも絶縁されているガルバニック絶縁です。産業機器は絶縁がされていることが一般的です。今回のようなちょっとした実験用程度であれば絶縁は無くても問題ないのですが、モータと接続する可能性があるので絶縁タイプを選択します。

モータのような大電力を扱うデバイスはノイズ、サージがCANやGNDのラインに載る場合があり、弱電部(RaspberryPi)に影響を与えます。また、外部機器とケーブルで接続するので、静電気による破壊もありえます。

また、CANFD対応CAN HATもあります。こちらは海外から取り寄せになると思います。

3.環境準備

今回用意するもの一覧です。

Raspberry Pi 4B4GB版
Raspberry Pi用電源5V 3A USB-typeC
LANケーブルSSH接続用. Wifi使用なら不要
2-Channel CAN HATWaveshare製絶縁CAN HAT(MCP2515、 CAN2.0B版)
https://www.switch-science.com/products/7343?_pos=12&_sid=e51d1386f&_ss=r
マイナスドライバCAN HAT端子台がネジ式のため、マイナスのドライバーが必要
2.4mmマイナス精密ドライバを使用
ジャンパ線CAN信号用2本
ホストPCSSH接続用のPC. 今回はUbuntu20.04
OSセットアップツールRaspberry Pi Imager v1.8.5
OSRaspberry Pi OS 64bit. release:2024-3-15
ロジックアナライザZeroplus社 LAP-C 16064 USB接続タイプ

4.RaspberryPi準備

4.1 SDカードセットアップ

OSインストールからやります。SDカードをホストPCに挿してRaspberry Pi Imagerを起動します。OSはRaspberry Pi OS 64bitを選択します。

OSに予め設定を組み込んでから焼くことが出来ます。とても便利です。「設定を編集する」で編集します。

必要な箇所を設定します。今回は有線LAN接続のためWifiは設定していません。

設定を保存し、書込みを開始します。10分くらいで終了します。

4.2 CAN HATの取り付け

CAN HATの終端抵抗を有効にします。ジャンパをON側にします。2ch分あるので2箇所設定します。

付属している40Pロングピンソケットを取り付けます。はんだづけ不要です。

CANHとCANLの信号をジャンパ線でループ接続します。2.4mmかそれより小さいマイナス精密ドライバが必要です。

接続はCANH-CANH、CANL-CANLをそれぞれ接続します。クロス接続はNGです。

CAN HATをRaspberry Piに取り付けます。

4.3 OSのセットアップ

SDカードとLANセーブルをセットし、電源をいれます。SSHで接続するために、RasPiのIPアドレスをpingで調べます。DNSが立ち上がっているようです。

Bash
$ ping raspberrypi.local
PING raspberrypi.local (10.42.0.68) 56(84) バイトのデータ
64 バイト応答 送信元 raspberrypi (10.42.0.68): icmp_seq=1 ttl=64 時間=0.263ミリ秒
64 バイト応答 送信元 raspberrypi (10.42.0.68): icmp_seq=2 ttl=64 時間=0.196ミリ秒
64 バイト応答 送信元 raspberrypi (10.42.0.68): icmp_seq=3 ttl=64 時間=0.211ミリ秒
^C
--- raspberrypi.local ping 統計 ---
送信パケット数 7, 受信パケット数 7, パケット損失 0%, 時間 6105ミリ秒
rtt 最小/平均/最大/mdev = 0.136/0.186/0.263/0.040ミリ秒
$

ホストPCからSSHで接続します。

Bash
$ ssh sh-goto@10.42.0.68
sh-goto@raspberrypi:~ $

apt更新します。7分ほどで終わりました。その後再起動します。

Bash
$ sudo apt update && sudo apt upgrade
$ sudo reboot

カーネル情報を見てみます。

Bash
$ uname -a
Linux raspberrypi 6.6.28+rpt-rpi-v8 #1 SMP PREEMPT Debian 1:6.6.28-1+rpt1 (2024-04-22) aarch64 GNU/Linux

kernel 6.6でした。結構新しいですね。

4.4 CAN HATのセットアップ

WaveshareのWikiを参考に進めていきます。若干古い部分もあるので今に合わせます。

SPIを有効化

MCP2515はSPI-CANブリッジICなので、まずSPI通信が出来るようにします。

Bash
$ sudo raspi-config

Interface Optionsを選択

SPIを選択してYes

finishで抜けます。

再起動します。これでSPI通信が使えるようになります。

Bash
$ sudo reboot

MCP2515のドライバの有効化

Device Tree Overlayによる、デバイスドライバの有効化を行います。RasPiでは簡単に有効化出来る仕組みが備わっています。/boot/firmware/config.txtファイルを編集して再起動するだけです。

/boot/firmware/config.txtを編集します。末尾に以下の2行を追記します。

Plaintext
dtoverlay=mcp2515-can1,oscillator=16000000,interrupt=25
dtoverlay=mcp2515-can0,oscillator=16000000,interrupt=23

今回はnanoエディタで編集します。

Bash
$ sudo nano /boot/firmware/config.txt

全体は以下のようになります。

Bash
# For more options and information see
# http://rptl.io/configtxt
# Some settings may impact device functionality. See link above for details

# Uncomment some or all of these to enable the optional hardware interfaces
#dtparam=i2c_arm=on
#dtparam=i2s=on
dtparam=spi=on

# Enable audio (loads snd_bcm2835)
dtparam=audio=on

# Additional overlays and parameters are documented
# /boot/firmware/overlays/README

# Automatically load overlays for detected cameras
camera_auto_detect=1

# Automatically load overlays for detected DSI displays
display_auto_detect=1

# Automatically load initramfs files, if found
auto_initramfs=1

# Enable DRM VC4 V3D driver
dtoverlay=vc4-kms-v3d
max_framebuffers=2

# Don't have the firmware create an initial video= setting in cmdline.txt.
# Use the kernel's default instead.
disable_fw_kms_setup=1

# Run in 64-bit mode
arm_64bit=1

# Disable compensation for displays with overscan
disable_overscan=1

# Run as fast as firmware / board allows
arm_boost=1

[cm4] 
# Enable host mode on the 2711 built-in XHCI USB controller.
# This line should be removed if the legacy DWC2 controller is required
# (e.g. for USB device mode) or if USB support is not required.
otg_mode=1

[all] 
dtoverlay=mcp2515-can1,oscillator=16000000,interrupt=25
dtoverlay=mcp2515-can0,oscillator=16000000,interrupt=23

Ctrl+xで保存するか聞かれ、y(=yes)を押すと保存するファイル名を聞かれるので、そのままEnterします。

保存できたら再起動します。

Bash
$ sudo reboot

再度SSH接続したら、起動ログを確認します。

Bash
$ dmesg | grep spi
[    7.944169] mcp251x spi0.0 can0: MCP2515 successfully initialized.
[    7.994961] mcp251x spi0.1 can1: MCP2515 successfully initialized.
$

上のようになっていればドライバが有効になります。

CANはSocketCANとして認識するので、ネットワークI/F一覧を確認します。

Bash
$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host noprefixroute 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether e4:5f:01:b2:07:21 brd ff:ff:ff:ff:ff:ff
    inet 10.42.0.68/24 brd 10.42.0.255 scope global dynamic noprefixroute eth0
       valid_lft 3445sec preferred_lft 3445sec
    inet6 fe80::26a3:a70b:61cd:3c5b/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever
3: can0: <NOARP,ECHO> mtu 16 qdisc noop state DOWN group default qlen 10
    link/can 
4: can1: <NOARP,ECHO> mtu 16 qdisc noop state DOWN group default qlen 10
    link/can 
5: wlan0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN group default qlen 1000
    link/ether e4:5f:01:b2:07:22 brd ff:ff:ff:ff:ff:ff

CAN0とCAN1が出現しているのでうまく行っているようです。楽でとても良いです。

【補足】config.txtについて(分かる範囲で)

このconfig.txtは起動時に何らかのスクリプトが読み取り、対応するDevice tree overlayファイルをロードしているものと思われます。

スクリプト詳細

Bash
dtoverlay=mcp2515-can0,oscillator=16000000,interrupt=25

“mcp2515-can0″は以下のDevice tree overlayファイル(dtbo)に対応しているようです。

Bash
$ ls /boot/firmware/overlays/ |grep mcp2515-can
mcp2515-can0.dtbo
mcp2515-can1.dtbo

“oscillator=16000000″はMCP2515の水晶発振器の周波数です。基板上に実装されているのは16MHzなので一致しています。

“interrupt=25″はMCP2515の割込み信号線が接続されているRasPiピンヘッダのピン番号です。

5.LinuxからCANにアクセス

5-1. CANの初期設定と有効化

ここまでで、SocketCANとしてCANが認識されましたが、通信速度を設定しないと通信できません。ipコマンドで状態を確認します。ifconfigコマンドは現在利用非推奨のため、ipコマンドを使います。

Bash
$ ip link show can0
3: can0: <NOARP,ECHO> mtu 16 qdisc pfifo_fast state DOWN mode DEFAULT group default qlen 10 
    link/can

$ ip link show can1
4: can1: <NOARP,ECHO> mtu 16 qdisc pfifo_fast state DOWN mode DEFAULT group default qlen 10 
    link/can

CAN0 CAN1共に”state DOWN”=無効になっています。

ipコマンドで通信速度を1Mbpsに設定して有効にします。

Bash
# CAN0
$ sudo ip link set can0 type can bitrate 1000000
$ sudo ip link set can0 up

# CAN1
$ sudo ip link set can1 type can bitrate 1000000
$ sudo ip link set can1 up

再度確認。CANが”state UP”=有効になりました。

Bash
$ ip link show can0
3: can0: <NOARP,UP,LOWER_UP,ECHO> mtu 16 qdisc pfifo_fast state UP mode DEFAULT group default qlen 10
    link/can

$ ip link show can1
4: can1: <NOARP,UP,LOWER_UP,ECHO> mtu 16 qdisc pfifo_fast state UP mode DEFAULT group default qlen 10
    link/can

※無効にするとき

Bash
$ sudo ip link set can0 down
$ sudo ip link set can1 down

5-2. can-utilsによるCAN送受信

can-utilsはコマンドで送受信とダンプ表示が可能なshellツールです。

can-utilsをインストールします。

Bash
$ sudo apt install can-utils

CAN0で受信データをダンプして待ちます。

Bash
# SSH端末1つ目
$ candump can0
#待ちに入る

もう一つ端末を立ち上げ、2つ目のSSH接続をして、CAN1からデータを送信します。

“001”はCANID、”11.22.33.44.55.66.77.88″は8byteのデータです。標準CANの最大データ長は8byteです。

Bash
#SSH端末2つ目
$ cansend can1 001#11.22.33.44.55.66.77.88

CAN0のダンプ待ちの方に受信データが現れれば成功です。

Bash
# SSH端末1つ目
$ candump can0
  can0  001   [8]  11 22 33 44 55 66 77 88

6.PythonでCAN送受信

6-1. python-canのインストールとエラー回避

今回のサンプルを実行するにはpython-canライブラリが必要です。

RasPi上でインストールするには以下の方法でできますが、エラーが出ます。

Bash
#確認
$ python -V
Python 3.11.2

#インストールエラー
$ python -m pip install python-can
error: externally-managed-environment

× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
    python3-xyz, where xyz is the package you are trying to
    install.
    
    If you wish to install a non-Debian-packaged Python package,
    create a virtual environment using python3 -m venv path/to/venv.
    Then use path/to/venv/bin/python and path/to/venv/bin/pip. Make
    sure you have python3-full installed.
    
    For more information visit http://rptl.io/venv

note: If you believe this is a mistake, please contact your Python installation or OS distribution provider. You can override this, at the risk of breaking your Python installation or OS, by passing --break-system-packages.
hint: See PEP 668 for the detailed specification.

PEP668関連によりノーマル環境にpipで直接パッケージをインストールできなくなったようです。PEPは Python Enhancement Proposalの略でPythonの仕様策定における議論のまとめのようなものです。

背景としてpythonの環境破壊トラブルがあります。特に2つが問題です。

  • 1.システム側で使われているPythonに対して改変を行い不具合が出る
  • 2.apt、pip、anacondaなどのパッケージ管理機能の混在

1点目はOSなどの機能の一部にPythonが使われている場合、既にシステム用としてPythonがインストールされています。それをユーザがPython環境を改変し、OSや既存のソフトウェアに不具合を与えることがあります。

2点目はapt、pip、anacondaなどの様々なパッケージ管理システムが存在しますが、それぞれインストールしたパッケージは保存Pathも管理もバラバラになります。優先度がapt>pipで混載利用できる場合もありますが、pipとanacondaの混在利用はトラブルのもとになります。そもそも開発元が違うので互換性を期待するのはお門違いです。

環境トラブルの回避方法は主に2つです。

  • ・Python標準仮想環境venvを使用し、仮想環境ごとにパッケージや管理機能を使い分ける
  • ・Dockerなどの仮想環境を利用する←PEP668の解決にはならない

PEP668はaptによるパッケージ管理または仮想環境venv上でpipを使うことを推奨しています。私は以前はDockerによる環境トラブルの回避を行っていたのですが、最近のPythonではPEP668警告がでるので、Docker上で更にvenv仮想環境を使うことになり2度手間です。この状況なので、今回はそのままvenvを使うことにします。

venvは仮想環境と呼ばれていますが、OS仮想化やコンテナ仮想化のようなものでは無く、どちらかと言うとプロジェクト管理機能のようなものです。仮想環境を生成すると直下に管理フォルダが作られ、インストールしたパッケージがその中に保存されます。パッケージのアクセススコープが仮想環境内に閉じられるので、システム側と独立したパッケージ管理が出来ます。

ちなみにaptでインストールできるPythonパッケージは一部のみです。python-canはインストールできません。venvとpipであればインストールできます。

PEP668エラー対策方法

venvで仮想環境を作成します。開発中と実行時は環境に入る必要があります。

Bash
$ mkdir pycan_work
$ cd pycan_work/

# 仮想環境作成(環境名:pycan-env)
$ python -m venv pycan-env

# 環境管理フォルダが作られる
$ cat ./
pycan-env

# 仮想環境に入る
$ source pycan-env/bin/activate
(pycan-env) $

# ※仮想環境から出る
(pycan-env) $ deactivate
$

# ※仮想環境を消す場合はフォルダを消すだけ
$ rm -rf ./pycan-env

pipでパッケージをインストールします。

Bash
(pycan-env) $ python -m pip install python-can
Looking in indexes: https://pypi.org/simple, https://www.piwheels.org/simple
Collecting python-can
  Downloading https://www.piwheels.org/simple/python-can/python_can-4.3.1-py3-none-any.whl (262 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 262.1/262.1 kB 327.3 kB/s eta 0:00:00
Collecting wrapt~=1.10
  Downloading wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (80 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 80.9/80.9 kB 1.7 MB/s eta 0:00:00
Collecting packaging>=23.1
  Downloading https://www.piwheels.org/simple/packaging/packaging-24.0-py3-none-any.whl (53 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 53.5/53.5 kB 3.8 MB/s eta 0:00:00
Collecting typing-extensions>=3.10.0.0
  Downloading https://www.piwheels.org/simple/typing-extensions/typing_extensions-4.12.0-py3-none-any.whl (37 kB)
Collecting msgpack~=1.0.0
  Downloading msgpack-1.0.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (400 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 400.8/400.8 kB 7.3 MB/s eta 0:00:00
Installing collected packages: wrapt, typing-extensions, packaging, msgpack, python-can
Successfully installed msgpack-1.0.8 packaging-24.0 python-can-4.3.1 typing-extensions-4.12.0 wrapt-1.16.0
(pycan-env) $

6-2. 送受信サンプル

今回作成したサンプルの概要です。

can_master側から計10回データを送信し、can_slave側が受信して1byte目のデータをインクリメント(+1)してCANID=0x00Fで返送します。can_master側は送り返されたデータをコンソールに表示すると共にログファイルに記録します。10回繰り返すと終了します。

can_slaveとcan_masterはCANIDのフィルタリングをしていません。

実行方法

まず、can0、can1が有効になっていることが前提条件です。SSH端末を2つ立ち上げ、先にcan_slave.pyを起動します。can1で受信待ちになります。

Bash
#仮想環境に先に入っておく
(pycan-env) $ python can_slave.py 
#待機

2つ目の端末でcan_master.pyを起動します。can0に送り返されたデータが来たら表示されます。CANID=0xFでデータの1byte目が0x01に変わっていたら成功です。

Bash
(pycan-env)$ python can_master.py 
Timestamp: 1716611497.345255    ID: 0000000f    X Rx  DL:  8    01 00 00 00 00 00 00 00     Channel: can0
Timestamp: 1716611497.846874    ID: 0000000f    X Rx  DL:  8    01 00 00 00 00 00 00 01     Channel: can0
Timestamp: 1716611498.347962    ID: 0000000f    X Rx  DL:  8    01 00 00 00 00 00 00 02     Channel: can0
Timestamp: 1716611498.849077    ID: 0000000f    X Rx  DL:  8    01 00 00 00 00 00 00 03     Channel: can0
Timestamp: 1716611499.350185    ID: 0000000f    X Rx  DL:  8    01 00 00 00 00 00 00 04     Channel: can0
Timestamp: 1716611499.851078    ID: 0000000f    X Rx  DL:  8    01 00 00 00 00 00 00 05     Channel: can0
Timestamp: 1716611500.352096    ID: 0000000f    X Rx  DL:  8    01 00 00 00 00 00 00 06     Channel: can0
Timestamp: 1716611500.853139    ID: 0000000f    X Rx  DL:  8    01 00 00 00 00 00 00 07     Channel: can0
Timestamp: 1716611501.354108    ID: 0000000f    X Rx  DL:  8    01 00 00 00 00 00 00 08     Channel: can0
Timestamp: 1716611501.855084    ID: 0000000f    X Rx  DL:  8    01 00 00 00 00 00 00 09     Channel: can0
Done!
(pycan-env)$

ログファイルの内容です。python-canのログ機能(Loggerクラス)を使用しています。

Bash
$ cat logfile.asc 
date Sat May 25 01:51:32.979 PM 2024
base hex  timestamps absolute
internal events logged
Begin Triggerblock Sat May 25 01:51:32.980 PM 2024
 0.000000 Start of measurement
 0.000000 1  Fx              Rx   d 8 01 00 00 00 00 00 00 00
 0.501672 1  Fx              Rx   d 8 01 00 00 00 00 00 00 01
 1.002806 1  Fx              Rx   d 8 01 00 00 00 00 00 00 02
 1.504947 1  Fx              Rx   d 8 01 00 00 00 00 00 00 03
 2.006420 1  Fx              Rx   d 8 01 00 00 00 00 00 00 04
 2.507915 1  Fx              Rx   d 8 01 00 00 00 00 00 00 05
 3.009299 1  Fx              Rx   d 8 01 00 00 00 00 00 00 06
 3.510930 1  Fx              Rx   d 8 01 00 00 00 00 00 00 07
 4.012253 1  Fx              Rx   d 8 01 00 00 00 00 00 00 08
 4.517649 1  Fx              Rx   d 8 01 00 00 00 00 00 00 09
End TriggerBlock
$

ちなみに3つ目の端末を立ち上げてcandumpすると、送受信の様子が見えます。

Bash
$ candump can0
  can0  00000001   [8]  00 00 00 00 00 00 00 00
  can0  0000000F   [8]  01 00 00 00 00 00 00 00
  can0  00000001   [8]  00 00 00 00 00 00 00 01
  can0  0000000F   [8]  01 00 00 00 00 00 00 01
  can0  00000001   [8]  00 00 00 00 00 00 00 02
  can0  0000000F   [8]  01 00 00 00 00 00 00 02
  can0  00000001   [8]  00 00 00 00 00 00 00 03
  can0  0000000F   [8]  01 00 00 00 00 00 00 03
  can0  00000001   [8]  00 00 00 00 00 00 00 04
  can0  0000000F   [8]  01 00 00 00 00 00 00 04
  can0  00000001   [8]  00 00 00 00 00 00 00 05
  can0  0000000F   [8]  01 00 00 00 00 00 00 05
  can0  00000001   [8]  00 00 00 00 00 00 00 06
  can0  0000000F   [8]  01 00 00 00 00 00 00 06
  can0  00000001   [8]  00 00 00 00 00 00 00 07
  can0  0000000F   [8]  01 00 00 00 00 00 00 07
  can0  00000001   [8]  00 00 00 00 00 00 00 08
  can0  0000000F   [8]  01 00 00 00 00 00 00 08
  can0  00000001   [8]  00 00 00 00 00 00 00 09
  can0  0000000F   [8]  01 00 00 00 00 00 00 09
  $

6-3. 送受信サンプルコード

python-canの公式Githubにサンプルコードがあるのでそれを改変して作成しました。公式リファレンスページもあるので、細かい仕様はそちらを確認します。

CAN受信通知を行うcan.Notifierクラスがasyncioに対応していますので、コールバック形式でイベント駆動処理が出来ます。

can_master.py

#!/usr/bin/env python

import asyncio
import can
from can.notifier import MessageRecipient

def print_message(msg: can.Message) -> None:
    #CAN受信したらデータをコンソールに表示
    print(msg)

async def main() -> None:
    with can.Bus(interface="socketcan", channel="can0") as bus:
        #ログファイル生成
        logger = can.Logger("logfile.asc")

        listeners: List[MessageRecipient] = [
            print_message,  # コンソール表示コールバック
            logger,  # ログ記録コールバック
        ]
        #asyncイベントループ作成
        loop = asyncio.get_running_loop()
        #asyncコールバック登録
        notifier = can.Notifier(bus, listeners, loop=loop)

        try:
            for i in range(10):
                #CANフレーム生成
                msg = can.Message(arbitration_id=0, data=[0,0,0,0,0,0,0,0])
                msg.is_extended_id = True #True=extend,False=standard
                msg.arbitration_id = 1
                msg.data[7] = i
                #CAN送信
                bus.send(msg)
                await asyncio.sleep(0.5)
            
            print("Done!")
        
        except can.CanError:
            print("Can Err.")
        finally:
            notifier.stop()
            bus.shutdown()

if __name__ == "__main__":
    asyncio.run(main())

can_slave.py

#!/usr/bin/env python

import asyncio
import can

async def main() -> None:
    with can.Bus(interface="socketcan", channel="can1") as bus:
        #受信バッファ作成
        reader = can.AsyncBufferedReader()
        #asyncイベントループ作成
        loop = asyncio.get_running_loop()
        #asyncコールバック登録
        notifier = can.Notifier(bus=bus, listeners=[reader], loop=loop)

        try:
            for _ in range(10):
                #受信データが来るまで待ち
                msg = await reader.get_message()
                #await asyncio.sleep(0.1)
                #CANIDとデータ書き換えて返送
                msg.arbitration_id = 0xf
                msg.data[0] += 1
                bus.send(msg)
            
            print("Done!")
        
        except can.CanError:
            print("Can Err.")
        finally:
            notifier.stop()
            bus.shutdown()

if __name__ == "__main__":
    asyncio.run(main())

7.ロジック・アナライザで波形を見る

CANバスは2線式差動バスの通信規格です。差動ではありますが、各2線の信号電圧は異なります。

参照<Overview of 3.3V CAN (Controller Area Network) Transceivers TI社>

CANHとCANLが同電位の時をリセッシブ(論理1)、CANH=High、CANL=Lowの電位の時をドミナント(論理0)と呼びます。クロック線が無い非同期方式になるため、5bit以上同じ値が続くと6bit目にスタッフビット(反転bit)が挿入され、タイミング調整が行われます。スタッフビットは物理層の話なので、データリンク層では論理値は無視されます。

5V系CANではリセッシブの電位が2.5Vですが、3.3V系CANでは約2.3V程度になるようです。最終的に使うRollerCANは5V系のようです。少なくともRasPi CAN HATは3.3V系なので、直結は注意が必要そうです。基本的に5V系と3.3V系は混在させないのが一番なのですが…

TI社のCANトランシーバは、5V系CANと3.3V系CANの混在は可能とあります(すごい)。2ch以上のまともなオシロスコープを持っていないので、このあたりでバグるとさすがに詰みが見えてきます。

さて、オシロスコープが無いので、手持ちのロジックアナライザで波形を見てみます。CANL、CANH共に電位が常にHigh側のため、電位しきい値どう判断するんだ?と思ったのですが、CANL1本をプローブすれば読めるようです(少なくともZeroplus LAP-Cは出来る)。

CANバス速度1Mbpsでサンプリング10MHzでキャプチャしてみました。テストデータは上記のPythonのサンプルです。

サンプルでは拡張フレームを指定しているので、CANIDが29bitの拡張フレームになっています。CAN2.0Bは標準フレーム(CANID11bit)と拡張フレームの2種類の仕様があります。とりあえずしっかり計測出来ているようで安心です。

拡張フレームのバス専有時間は約146usでした。

続いて標準フレームです。

標準フレームのバス専有時間は約125usでした。

RollerCANは29bit拡張フレームかつ1MHzの通信速度を使用するため、1つのバスあたりに接続できるモータの数をバス占有率から見積もれそうです。RollerCANのCAN仕様ドキュメントを見る限り、CANIDを0~255の間で設定できるのでかなりの数をバスにぶら下げられます。

しかし、制御のために高頻度でモータのフィードバック値の取得のようなことをすると、すぐにバス帯域を使い切ってしまいますので、実際は数個が限界だと思います。

ABOUT ME
sh-goto
低レイヤで遊んでいます
関連記事