CAN通信するための具体的な設定、実装について解説していきます
ここから有料記事になりますので、ご興味ある方はご購入をお願いします
CANトランシーバ
STM32F446は3.3V駆動なので3.3V用のCANトランシーバを用意する必要があります
5V用のTJA1051、MCP2551などが入手しやすく、レベル変換して使用する方法もありますが
今回はCJMCU-1051の基板を使って、搭載されているTJA1051を一旦取り外し、3.3Vで動作可能なTJA1051T/3に付け換えて使うことにします
TJA1051T/3は検索すると半導体系サイトがいくつかヒットしますのでお安いところから購入してください
CJMCU-1051はAmazon等で購入可能です
TJA1051T/3に付け換えたCJMCU-1051基板のヘッダピンの接続は下記の通りです
VCC:5V(CN6の上から5番目)
GND:GND(どこでも可)
CTX:PB9 CAN1_TX(CN5の上から2番目、シルクプリントはSDA/D14)
CRX:PB8 CAN1_RX(CN5の一番上、シルクプリントはSCL/D15)
CANH:CANH
CANL:CANL
S:未使用
NC:3.3V(CN6の上から4番目)※シルクプリントはNCとなっていますが、実際はVIOに接続されていますので、NCに3.3Vを接続することで3.3Vマイコンで使用可能となります
CANのポート割り当て
左のメニュー、ConnectivityからCAN1を選択、Master Modeにチェック

これで自動的にポートが割り当てられます
CAN1_TX:PA12
CAN1_RX:PA11
ポート割りのバリエーションはもう一種類
CAN1_TX:PB9
CAN1_RX:PB8
とすることも可能です
ヘッダーピンはPA系がオス、PB系がメスでArduino互換という違いがあります
またPB系の方が割り当てられる機能が多いので今後の拡張次第ではPA系を使っておいた方が有利かと思います
今回はCANだけの解説なのでPB系に再設定して進めていきます
画面右側に表示されているマイコンのピン番号をクリックすると割り当て機能のメニューが出るので所望の機能を選択すればOKです

車載ECU用のCANパラメータ設定
ここでは500kbpsのCAN通信をターゲットとして設定していきます
まずはデフォルトのクロック設定を確認します
Clock Configurationをクリックすると下記の通りとなっています

CANはAPB1 peripheral clocks(PCLK1)を使用しますのでそこを確認すると42MHzとなっています
今回はデフォルト値を使用して設定していきます
Pinout & Configurationをクリックして、CAN1内のParameter Settingsをクリックし、下記の通り変更します
Time Quanta in Bit Segment 1:16
Time Quanta in Bit Segment 2:4

Time Quanta(TQ)の設定ですが、規格に基づいてSample Pointを計算する必要があります
詳細は割愛しますが規格に応じて範囲が決まっています
今回はSample Point = 80%程度を目標として下記の通りとしました
▲Sample Point設定値の計算
CAN_Clock = APB1_Peripheral_clock / Prescaler = 42MHz / 4 = 10.5MHz
CAN_Bitrate = 500kbps = 0.5Mbps
TQ = CAN_Clock / CAN_Bitrate = 10.5MHz / 0.5Mbps = 21
Sample_Point = ( TQ1 + SJW ) / TQ = ( 16 + 1 ) / 21 = 80.9524%
TQ2 = TQ -TQ1 – SJW = 4
初期コード出力
ここまで設定した内容で初期コードを出力します
Project ManagerをクリックしてProject NameとProject Locationを任意で入力
Toolchain / IDE は SW4STM32 を選択します
(今回はProject NameをSTM32_CAN_Testとしました)
右上のGENERATE CODEをクリックすると初期コードが出力できます

表示されたダイアログでOpen ProjectをクリックするとSW4STM32が起動してプロジェクトが開けます
(2回目以降の出力で、既にIDEにプロジェクトを読み込んだ状態ではCloseでも構いません)

起動してプロジェクトツリーを開くとこの状態になります

これで初期コード出力は完了です
ここからコーディング作業に入っていきます
Lチカで動作確認
まずはマイコンが動作しているかの確認のためLチカからやってみます
Project ExplorerからSrc→main.cをダブルクリックし、Infinite loopのところまでスクロールします
そしてUSER CODE BEGIN 3とUSER CODE END 3の間に下記の通り記述します
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_Delay(1000);
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
}
/* USER CODE END 3 */

プロジェクトをビルドします
Project Explorerからフォルダを右クリックしてBuild Projectを選択

マイコンにFlashします
Project Explorerからフォルダを右クリックしてTarget→Program chipを選択
Reset after programにチェックを入れてOKをクリック


これで1000ms間隔でLEDが点滅します
ここでコーディングについて補足しておきます
▲コーディングの補足
ユーザーコードは初期のコメントにある
”USER CODE BEGIN ***”と”USER CODE END ***”の間に記述します
そうすることで、STM32CubeMXで再度設定変更してGENERATE CODEを実施しても、ユーザーコード領域は上書きされずに残すことができます
簡易デバッグ環境の構築
早くCAN通信してみたいところですが焦らずにもう少し準備です
Arduinoでも標準的に使用されているお手軽なデバッグ環境と同様に
デバッガを使用しないシリアルのprintベースの環境を構築しておきます
ここで導入するのはChaNさんが公開されているxprintfライブラリです
こちらを導入する必要性についてですが
標準のprintfやsprintfを使っていると今後FreeRTOSを使用する場合などに
うまく動作しなくて悩むことが出てくると思いますので
初めから対策されているxprintfを入れておくと悩まずに幸せになれます
DLして解凍後、xprintf.cをSrcフォルダに、xprintf.hをIncフォルダに入れます

xprintfを使用できるように初期設定します
・Include設定
/* USER CODE BEGIN Includes */
#include "xprintf.h"
/* USER CODE END Includes */
/* USER CODE BEGIN 0 */
void usb_putc(char c)
{
while(HAL_BUSY == HAL_UART_Transmit(&huart2, (uint8_t *)&c, (uint16_t)1, 0xFFFF))
{
;
}
}
/* USER CODE END 0 */
/* USER CODE BEGIN 1 */
xdev_out(usb_putc);
/* USER CODE END 1 */
それではテストプリントしてみます
/* USER CODE BEGIN 2 */
xprintf("Loop Start\r\n");
/* USER CODE END 2 */
USB経由のVCPはSTM32CubeMXのデフォルト設定では下記の通りになっています
▲VCPデフォルト設定値
・USART2
・115200bps
・8N1
TeraTermで通信するとこの様に表示されます

CANの初期設定
下記の通りコードを追加していきます
初期化関数定義
void CAN_User_Init(void)
{
HAL_CAN_Start(&hcan1);
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);
}
初期化関数コールを追加
/* USER CODE BEGIN 2 */
CAN_User_Init();
xprintf("Loop Start\r\n");
/* USER CODE END 2 */
CAN送信設定
・変数定義
USER CODE PV内に定義します
/* USER CODE BEGIN PV */
CAN_TxHeaderTypeDef pTxHeader;
uint32_t TxMailBox;
/* USER CODE END PV */
・送信関数定義
CANを送信する適当なコードを記述してUSER CODE 0の中に入れます
void CAN_User_Tx(void)
{
uint8_t tx_data[8];
static uint8_t tx_count = 0;
pTxHeader.StdId = 0x654;
pTxHeader.IDE = CAN_ID_STD;
pTxHeader.RTR = CAN_RTR_DATA;
pTxHeader.DLC = 8;
tx_data[0] = 0x01;
tx_data[1] = 0x23;
tx_data[2] = 0x45;
tx_data[3] = 0x67;
tx_data[4] = 0x89;
tx_data[5] = 0xAB;
tx_data[6] = 0xCD;
tx_data[7] = tx_count++;
if(HAL_CAN_AddTxMessage(&hcan1, &pTxHeader, tx_data, &TxMailBox) != HAL_OK)
{
Error_Handler();
}
else
{
xprintf("Tx OK\r\n");
}
}
Infinite loopの中にCAN送信関数コールを追加します
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_Delay(1000);
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
CAN_User_Tx();
}
/* USER CODE END 3 */
これでCAN送信が1000ms毎に実行されます
CAN受信設定
・変数定義
USER CODE PV内に定義します
CAN_RxHeaderTypeDef pRxHeader;
CAN_FilterTypeDef pFilter; //declare CAN filter structure
uint8_t rx_data[8] = {0};
・初期化処理追加
先に定義していたCAN_User_Init内にフィルタの初期化処理を追加します
今回は標準IDのデータフレームのみを受信する設定としています
void CAN_User_Init(void)
{
uint16_t filter_id;
uint16_t mask_id;
filter_id = 0x000;
mask_id = 0x000;
pFilter.FilterIdHigh = filter_id << 5;
pFilter.FilterIdLow = CAN_ID_STD | CAN_RTR_DATA;
pFilter.FilterMaskIdHigh = mask_id << 5;
pFilter.FilterMaskIdLow = CAN_ID_EXT | CAN_RTR_REMOTE;
pFilter.FilterFIFOAssignment = CAN_FILTER_FIFO0;
pFilter.FilterBank = 0;
pFilter.FilterMode = CAN_FILTERMODE_IDMASK;
pFilter.FilterScale = CAN_FILTERSCALE_32BIT;
pFilter.FilterActivation = ENABLE;
pFilter.SlaveStartFilterBank = 0;
HAL_CAN_ConfigFilter(&hcan1, &pFilter);
HAL_CAN_Start(&hcan1);
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);
}
filter_idとmask_idは0に設定するとCAN IDによるフィルタが無効となります
拡張IDをマスクして標準IDのみを受信するために、IdLowにCAN_ID_STDを、MaskIdLowにCAN_ID_EXTを設定します
同様に、リモートフレームをマスクしてデータフレームのみを受信するために、IdLowにCAN_RTR_DATAを、MaskIdLowにCAN_RTR_REMOTEを設定します
▲フィルタの注意点
フィルタを使用しない場合でもCAN受信させるために下記は必須です
・pFilter.FilterActivation = ENABLE;
・HAL_CAN_ConfigFilter(&hcan1, &pFilter);
・受信関数定義
CANを受信する適当なコードを記述してUSER CODE 0の中に入れます
void CAN_User_Rx(void)
{
uint8_t i;
while(HAL_CAN_GetRxFifoFillLevel(&hcan1, CAN_RX_FIFO0) != 0)
{
HAL_CAN_GetRxMessage(&hcan1, CAN_RX_FIFO0, &pRxHeader, rx_data);
xprintf("ID:0x%03X ", pRxHeader.StdId);
xprintf("DLC:%d ", pRxHeader.DLC);
xprintf("Data:");
for(i = 0; i < pRxHeader.DLC; i++)
{
xprintf("%02X ", rx_data[i]);
}
xprintf("\r\n");
}
}
終わりに
こんな感じでシリアルやSPI経由ではなく、
CANレシーバーを直接接続したCAN通信ができるようになりました
この後は、やりたいCAN通信の解析に移行していきたいと思います。。。
コメント