20 CAN 通信实验

开发板官网(https://www.edevkit.com/)
本系列教程目录:
- 瑞萨单片机开发教程[一] e2-studio + DAP LINK开发环境搭建
- 瑞萨单片机开发教程[二] Keil/RASC开发环境搭建
- 瑞萨单片机开发教程[三] 新建RASC工程
- 瑞萨单片机开发教程[四] GPIO输出——led实验
- 瑞萨单片机开发教程[五] GPIO输入——按键key实验
- 瑞萨单片机开发教程[六] ICU外部中断实验
- 瑞萨单片机开发教程[七] SCI UART串口通信实验
- 瑞萨单片机开发教程[八] DMAC 串口数据传输实验
- 瑞萨单片机开发教程[九] ADC 电压采集实验
CAN 通信实验
本章目标:
- 了解 RA6M5 处理器的 CAN 通信及其工作原理;
- 学会使用 RASC 配置 CAN 模块、使用 API 函数;
硬件设计
(略,参见配套硬件原理图)
软件设计
新建工程
复制一份“07 SCI_UART_Recv_send”修改文件夹名“20 CAN_Recv_Send”,双击打开 EBF_RA6M5.uvprojx 工程,通过 Keil 的新建文件新建 can.c 和 can.h,保存到目录下的 src 文件夹下。
FSP 配置
Keil 界面选择“Tools”→“RA Smart Configurator”打开 RASC 界面。
首先配置 CAN 的时钟源,打开 Clocks 界面。

在“Pins”→“Peripherals”→“Connectivity:CAN”选择“CAN0”,选择配置可用的引脚。

下一步添加 CAN Stack 模块。

下一步,配置 CAN “Common”属性参数。

- Global Error Interrupt > Sources:选择全局错误中断源。
- DLC Check:DLC 数据长度检查
- Message Lost:数据丢失
- FD Payload Overflow:FD 有效负载溢出
- Reception > FIFOs > FIFO x > Enable:使能或禁用 RX FIFO x。
- Reception > FIFOs > FIFO x > Interrupt Mode:设置 RX FIFO x 的中断模式。阈值模式下在每次传入消息超过下面设置的阈值时,仅会触发一次中断。
- Reception > FIFOs > FIFO x > Interrupt Threshold:设置 RX FIFO x 的中断阈值。
- Reception > FIFOs > FIFO x > Payload Size:选择 RX FIFO x 的消息有效负载的大小。
- Reception > FIFOs > FIFO x > Depth:深度。即选择 RX FIFO x 的阶数(stages)
- Reception > Message Buffers:配置 RX 消息缓冲区。可配置用于接收的消息缓冲区数量以及所有 RX 消息缓冲区的有效负载大小。
- Number of Buffers:缓冲区个数
- Payload Size:有效负载大小
- Reception > Acceptance Filtering:专用于通道 x 的验收筛选器列表(AFL)规则数。
- Channel 0 Rule Count:通道 0 规则计数
- Channel 1 Rule Count:通道 1 规则计数
下一步配置 CANFD0 通道的属性参数。

- Bitrate > Automatic > Nominal Rate (bps):自动设置:指定标称比特率(位/秒)。
- Bitrate > Automatic > FD Data Rate (bps):自动设置:指定数据比特率(位/秒)。
- Bitrate > Automatic > Sample Point (%):自动设置:指定所需的采样点。
- Bitrate > Manual > Nominal 标称比特率:手动设置:标称参数:预分频、TS1、TS2、SJW
- Nominal→Prescaler (divisor):预分频(1 ~ 1024)
- Nominal→Time Segment 1 (Tq):2 ~ 256 Tq
- Nominal→Time Segment 2 (Tq):2 ~ 128 Tq
- Nominal→Sync Jump Width (Tq):1 ~ Time Segment 2
- Bitrate > Manual > Data 数据比特率:手动设置:数据参数:预分频、TS1、TS2、SJW
- Data→Prescaler (divisor):预分频(1 ~ 256)
- Data→Time Segment 1 (Tq):2 ~ 32 Tq
- Data→Time Segment 2 (Tq):2 ~ 16 Tq
- Data→Sync Jump Width (Tq):1 ~ Time Segment 2
- Interrupts > Callback:用户的中断回调函数,如果提供了此回调函数,则每次发生任何中断时,都会从中断服务函数(ISR)调用该函数。
- Transmit Interrupts:选择在传输完成时会触发中断的 TX 消息缓冲区(TXMB 0 ~ TXMB 7 和 TXMB 32 ~ TXMB 39)。
- Channel Error Interrupts:使能通道错误中断源。
- Error Warning:错误警告
- Error Passive:被动错误
- Bus-Off Entry:错误总线关闭
- Bus-Off Recovery:总线恢复错误
- Overload:超载
配置完成之后可以按下快捷键“Ctrl + S”保存,最后点击“Generate Project Content”按钮,让软件自动生成配置代码即可。
程序设计
设计程序,初始化 CAN 设置为内部回环模式,封装 CAN 发送消息函数,通过按键发送帧消息,第一次发送普通 8 字节 CAN 消息,第二次发送 CAN FD 64 字节消息,在中断中接收数据,接收到数据打印数据到串口。
- 打开 CAN 模块
R_CANFD_Open(can_ctrl_t * const p_ctrl, can_cfg_t const * const p_cfg);
- 设置通信模式
R_CANFD_ModeTransition(can_ctrl_t * const p_api_ctrl,
can_operation_mode_t operation_mode,
can_test_mode_t test_mode);
第二个参数定义了 CAN 支持的所有操作模式,第三个参数设置测试模式,比如使用内部回环模式 CAN_TEST_MODE_LOOPBACK_INTERNAL。
- 发送帧消息
R_CANFD_Write(can_ctrl_t * const p_ctrl,
uint32_t buffer_number,
can_frame_t * const p_frame);
第二个参数指的是发送的 buffer 序号,而不是数据个数,取值范围在 CANFD_TX_MB_0CANFD_TX_MB_7、CANFD_TX_MB_32CANFD_TX_MB_39 之间;
第三个参数表示数据帧的结构,包括 ID、ID 类型、数据等。
typedef struct st_can_frame
{
uint32_t id; ///< CAN ID.
can_id_mode_t id_mode; ///< Standard or Extended ID (IDE).
can_frame_type_t type; ///< Frame type (RTR).
uint8_t data_length_code; ///< CAN Data Length Code (DLC).
uint32_t options; ///< Implementation-specific options.
uint8_t data[CAN_DATA_BUFFER_LENGTH]; ///< CAN data.
} can_frame_t;
“options”参数设定是否发送 CAN FD 类型数据,设置为 0 时发送普通 8 字节消息,设置为 CANFD_FRAME_OPTION_FD,则发送 CAN FD 64 字节消息。
程序编写
在 can.h 头文件里定义初始化函数、发送帧消息函数。
#ifndef BSP_CAN_H_
#define BSP_CAN_H_
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "hal_data.h"
#include "r_canfd.h"
#include "uart.h"
void canfd0_Init(void);
uint8_t canfd0_send_msg(can_frame_t *canfd0_tx_frame);
void can0_send_test(void);
void printf_can_msg(void);
#endif
在 can.c 里实现初始化函数、发送帧消息函数、中断函数。
#include "can.h"
void canfd0_Init(void)
{
fsp_err_t err;
err = R_CANFD_Open(&g_canfd0_ctrl, &g_canfd0_cfg);
assert(FSP_SUCCESS == err);
//内回环测试模式
R_CANFD_ModeTransition(&g_canfd0_ctrl, CAN_OPERATION_MODE_NORMAL, CAN_TEST_MODE_LOOPBACK_INTERNAL);
}
//AFL
canfd_afl_entry_t p_canfd0_afl[CANFD_CFG_AFL_CH0_RULE_NUM] =
{
/* Store all data frames with at least 4 bytes from Standard IDs 0x40-0x4F in RX FIFO 0 and RX FIFO 1 */
{
.id =
{
/* 指定要接受的ID、ID类型和帧类型。 */
.id = 0,
.frame_type = CAN_FRAME_TYPE_DATA,
.id_mode = CAN_ID_MODE_STANDARD
},
.mask =
{
/* 这些值屏蔽了过滤邮件时要比较的ID/模式位。 */
.mask_id = 0,
.mask_frame_type = 1,
.mask_id_mode = 1
},
.destination =
{
/* 如果启用了DLC检查,则任何短于以下设置的消息都将被拒绝。 */
.minimum_dlc = CANFD_MINIMUM_DLC_4,
/* 也可以指定接收消息缓冲区(RX MB)来存储接受的帧。
* RX MB没有中断或重写保护,必须使用R_CANFD_INFO Get和R_CANFD_READ进行检查。 */
.rx_buffer = CANFD_RX_MB_NONE,
/* 指定要将筛选的消息发送到的FIFO。多个FIFO可以一起进行或运算。 */
.fifo_select_flags = (canfd_rx_fifo_t) (CANFD_RX_FIFO_0)
}
}
};
bool canfd0_tx_complete_flag = false;
bool canfd0_rx_complete_flag = false;
bool canfd0_err_status_flag = false;
can_frame_t g_can_tx_frame;
can_frame_t g_can_rx_frame;
bool can_rx_flag = false;
canfd_error_t g_err_status = (canfd_error_t) 0;
//can回调函数
void canfd0_callback (can_callback_args_t * p_args)
{
switch (p_args->event)
{
case CAN_EVENT_RX_COMPLETE: //接收完成中断
{
memcpy(&g_can_rx_frame, &p_args->frame, sizeof(can_frame_t));
can_rx_flag = true;
break;
}
case CAN_EVENT_TX_COMPLETE: //传输完成中断
{
//canfd0数据发送完成
canfd0_tx_complete_flag = true;
/* Handle event */
break;
}
case CAN_EVENT_ERR_GLOBAL: //Global error
case CAN_EVENT_ERR_WARNING: //error warning event
case CAN_EVENT_ERR_PASSIVE: //error passive event
case CAN_EVENT_ERR_BUS_OFF: //error Bus Off event
case CAN_EVENT_BUS_RECOVERY: //Bus recovery error event
case CAN_EVENT_MAILBOX_MESSAGE_LOST: //overwrite/overrun error event
case CAN_EVENT_ERR_BUS_LOCK: //Bus lock detected (32 consecutive dominant bits).
case CAN_EVENT_TX_ABORTED: //Transmit abort event.
case CAN_EVENT_ERR_CHANNEL: //Channel error
case CAN_EVENT_TX_FIFO_EMPTY:
{
//Get error status
g_err_status = (canfd_error_t) p_args->error; /* Check error code with canfd_error_t. */
//Handle event
break;
}
default:
{
break;
}
}
}
//发送CAN数据
uint8_t canfd0_send_msg(can_frame_t *canfd0_tx_frame)
{
uint32_t time_out = 0xFFFF;
fsp_err_t err = FSP_SUCCESS;
/* 通过 mail box #0 传输数据 */
err = R_CANFD_Write(&g_canfd0_ctrl, CANFD_TX_MB_0, canfd0_tx_frame);
assert(FSP_SUCCESS == err);
/* 等待传输完成 */
while ((true != canfd0_tx_complete_flag) && (--time_out));
canfd0_tx_complete_flag = false;
if (0 == time_out)
{
return 0;
}
return 1;
}
//测试函数,发送传统CAN帧和 CAN FD帧
bool send_flag = 0;
can_frame_t canfd0_tx_frame;
void can0_send_test(void)
{
if(!send_flag) //发送传统CAN帧数据
{
canfd0_tx_frame.id = 0x111;
canfd0_tx_frame.id_mode = CAN_ID_MODE_STANDARD;
canfd0_tx_frame.type = CAN_FRAME_TYPE_DATA;
canfd0_tx_frame.data_length_code = 8;
canfd0_tx_frame.options = 0; //传统CAN帧
for( uint16_t j = 0; j < 64; j++)
{
canfd0_tx_frame.data[j] = (uint8_t) (j);
}
}
else //发送 CAN FD 帧数据
{
canfd0_tx_frame.id = 0x222;
canfd0_tx_frame.id_mode = CAN_ID_MODE_STANDARD;
canfd0_tx_frame.type = CAN_FRAME_TYPE_DATA;
canfd0_tx_frame.data_length_code = 64;
canfd0_tx_frame.options = CANFD_FRAME_OPTION_FD | CANFD_FRAME_OPTION_BRS;//CAN FD 帧
for( uint16_t j = 0; j < 64; j++)
{
canfd0_tx_frame.data[j] = (uint8_t) (j);
}
}
canfd0_send_msg( &canfd0_tx_frame );
send_flag =! send_flag;
}
//打印接收的数据
void printf_can_msg(void)
{
if(can_rx_flag)
{
can_rx_flag = 0;
printf("recv can0 msg:\r\n");
printf("id:0x%x,len:0x%x \r\n", g_can_rx_frame.id, g_can_rx_frame.data_length_code);
for(uint8_t i=0; i<g_can_rx_frame.data_length_code; i++)
{
printf("%x ", g_can_rx_frame.data[i]);
}
printf("\r\n");
}
}
在 hal_entry.c 文件中添加头文件 can.h,在 hal_entry 入口函数初始化,按键发送 CAN 消息并打印接收到的数据。
void hal_entry(void)
{
/* TODO: add your own code here */
led_Init();
uart0_Init();
key_Init();
canfd0_Init();
while(1)
{
if( key_Scan(KEY1_PIN) )
{
can0_send_test();
LED1_TOGGLE();
}
printf_can_msg();
}
#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
}
下载验证
连接下载器,将程序编译并下载到开发板之后,按下复位按键来复位开发板,程序启动初始化完成后,观察实验结果。
第一次按键发送传统 CAN 数据帧消息,第二次按键发送 CAN FD 数据,可以看到串口打印接收到的数据。
