使用小华(华大)的MCU HC32F07X实现 CAN 通讯配置和使用
【1】CAN原理说明(参考文章《CAN通信详解》):
CAN是控制器局域网络(Controller Area Network, CAN)的简称,是一种能够实现分布式实时控制的串行通信网络。
(i)CAN通信形式:CAN 使用称为 CANH / CANL 的通信线路执行传输和接收。电位差较小的电信号称为隐性信号,其逻辑值为1。电位差较大的电信号称为显性信号,其逻辑值0。如果通信总线上发生显性和隐性冲突,则显性优先。总线空闲时保持隐性。
(ii)CAN数据格式:CAN的数据定义了有5种帧类型:
(ii)CAN数据帧:数据帧一般由 7 个段构成,即:
(1) 帧起始。表示数据帧开始的段。
(2) 仲裁段。表示该帧优先级的段。
(3) 控制段。表示数据的字节数及保留位的段。
(4) 数据段。数据的内容,一帧可发送 0~8 个字节的数据。
(5) CRC 段。检查帧的传输错误的段。
(6) ACK 段。表示确认正常接收的段。
(7) 帧结束。表示数据帧结束的段
图中 D 表示显性电平, R 表示隐形电平。
更多具体内容不再赘述,可以参考上面的文章链接或者自行搜索。
【2】HC32F07X的CAN外设:
芯片CAN外设的主要特性:
■ 完全支持 CAN2.0A/CAN2.0B 协议。
■ 向上兼容 CAN-FD 协议。
■ 支持最高通信波特率 1Mbit/s
■ 支持 1~1/256 的波特率预分频, 灵活配置波特率。
■ 10 个接收缓冲器
- FIFO 方式
- 错误或者不被接收的数据不会覆盖存储的消息
■ 1 个高优先主发送缓冲器 PTB
■ 4 个副发送缓冲器 STB
- FIFO 方式
- 优先级仲裁方式
■ 8 组独立的筛选器
- 支持 11 位标准 ID 和 29 位扩展 ID
- 可编程 ID CODE 位以及 MASK 位
■ PTB/STB 均支持支持单次发送模式
■ 支持静默模式
■ 支持回环模式
■ 支持捕捉传输的错误种类以及定位仲裁失败位置
■ 可编程的错误警告值
■ 支持 ISO11898-4 规定时间触发 CAN 以及接收时间戳
系统框图如下:
更多详细的内容可以参考HC32F07X芯片的DATASHEET。
以下推荐了两个CAN收发的硬件电路,可以将外部的 CANH/CANL 差分信号,转换为 CAN_TX/CAN_RX 信号用以内部MCU处理。一个是官方提供的,一个是开发板提供的,都可以正常使用。
注意CAN通信需要5V,用以给收发器供电。
【1】推荐电路1:
参考官方的硬件设计指南《AN_HC32L072_HC32L073_HC32F072系列硬件开发指南_Rev1.1》,里面对于CAN通信收发的硬件电路有如下推荐电路,其中使用了NXP的 TJA1042 作为收发器:
【2】推荐电路2:
使用的周立功开发板中也引出了CAN功能,其中使用了NXP的TJA1051T作为收发器:
选用引脚 PD00(CAN_RX)、PD01(CAN_TX)、PC12(CAN_STB)实现CAN通信功能,使用时钟频率为48MHz(官方例程使用的是外部8M晶振),CAN通讯波特率为1M,标准帧ID号为1。
【1】系统时钟配置:
static void App_SysClkInit(void)
{
stc_sysctrl_clk_cfg_t stcCfg;
stc_sysctrl_pll_cfg_t stcPLLCfg;
Sysctrl_SetPeripheralGate(SysctrlPeripheralFlash, TRUE); ///< 使能FLASH模块的外设时钟
Flash_WaitCycle(FlashWaitCycle1);
Sysctrl_SetRCHTrim(SysctrlRchFreq4MHz); ///< PLL使用RCH作为时钟源,因此需要先设置RCH
stcPLLCfg.enInFreq = SysctrlPllInFreq4_6MHz; ///< RCH 4MHz
stcPLLCfg.enOutFreq = SysctrlPllOutFreq36_48MHz; ///< PLL 输出48MHz
stcPLLCfg.enPllClkSrc = SysctrlPllRch; ///< 输入时钟源选择RCH
stcPLLCfg.enPllMul = SysctrlPllMul12; ///< 4MHz x 12 = 48MHz
Sysctrl_SetPLLFreq(&stcPLLCfg);
///< 选择PLL作为HCLK时钟源;
stcCfg.enClkSrc = SysctrlClkPLL;
///< HCLK SYSCLK/2
stcCfg.enHClkDiv = SysctrlHclkDiv1;
///< PCLK 为HCLK/8
stcCfg.enPClkDiv = SysctrlPclkDiv1;
///< 系统时钟初始化
Sysctrl_ClkInit(&stcCfg);
}
【2】CAN初始化GPIO:
//CAN通信引脚定义
#define CAN_RX_PORT (GpioPortD)
#define CAN_RX_PIN (GpioPin0)
#define CAN_TX_PORT (GpioPortD)
#define CAN_TX_PIN (GpioPin1)
#define CAN_STB_PORT (GpioPortC)
#define CAN_STB_PIN (GpioPin12)
#define APB1_CLK 48000000 //CAN 的输入时钟
#define CAN_BAUD 1000000 //CAN 的波特率
/**************************************************************************
* 函数名称: COM_Init
* 功能描述: CAN通信初始化GPIO
**************************************************************************/
void COM_Init(void)
{
stc_gpio_cfg_t stcGpioCfg;
Sysctrl_SetPeripheralGate(SysctrlPeripheralGpio, TRUE);
stcGpioCfg.enDir = GpioDirIn; ///< 端口方向配置->输入
stcGpioCfg.enDrv = GpioDrvL; ///< 端口驱动能力配置->高驱动能力
stcGpioCfg.enPu = GpioPuDisable; ///< 端口上下拉配置->无
stcGpioCfg.enPd = GpioPdDisable;
stcGpioCfg.enOD = GpioOdDisable; ///< 端口开漏输出配置->开漏输出关闭
stcGpioCfg.enCtrlMode = GpioAHB; ///< 端口输入/输出值寄存器总线控制模式配置->AHB
Gpio_Init(CAN_RX_PORT, CAN_RX_PIN, &stcGpioCfg);
stcGpioCfg.enDir = GpioDirOut;
Gpio_Init(CAN_TX_PORT, CAN_TX_PIN, &stcGpioCfg);
Gpio_Init(CAN_STB_PORT, CAN_STB_PIN, &stcGpioCfg);
///
【3】CAN波特率自适应配置接口:
/**************************************************************************
* 函数名称: COM_BaudCfg
* 功能描述: CAN通信波特率自动配置
* 其他说明: 48MHz主频,1M波特率计算得到:SJW=2, PRESC=1-1, SEG_2=15, SEG_1=30
若输入500K波特率,PRESC为2-1; 输入250K波特率,PRESC为4-1
**************************************************************************/
void COM_BaudCfg(stc_can_init_config_t *p_stcCanInitCfg, uint32_t src_clk, uint32_t baud)
{
uint32_t i,value = baud,record = 1;
uint32_t remain = 0,sum_prescaler = 0;
while(( baud == 0 )||( src_clk == 0 ));
sum_prescaler = src_clk / baud;
for ( i = 73; i > 3; i-- ) {
remain = sum_prescaler - ((sum_prescaler / i)*i);
if( remain == 0 ) {
record = i;
break;
} else {
if (remain < value) {
value = remain;
record = i;
}
}
}
/* 设置重新同步跳跃宽度为2个时间单位 */
p_stcCanInitCfg->stcCanBt.SJW = 2;
p_stcCanInitCfg->stcCanBt.PRESC = (sum_prescaler/record) - 1;
p_stcCanInitCfg->stcCanBt.SEG_2 = (record - 3) / 3;
p_stcCanInitCfg->stcCanBt.SEG_1 = (record - 3) - p_stcCanInitCfg->stcCanBt.SEG_2;
}
【注】:CAN波特率的计算和配置,也可以使用CAN波特率计算器工具来计算得到(下载地址:CAN波特率计算器下载):
【4】CAN初始化配置(其中,波特率配置为1M,滤波器配置标准帧ID为1):
/**************************************************************************
* 函数名称: COM_Cfg
* 功能描述: CAN通信初始化配置
**************************************************************************/
void COM_Cfg(void)
{
stc_can_init_config_t stcCanInitCfg;
stc_can_filter_t stcFilter;
Sysctrl_SetPeripheralGate(SysctrlPeripheralCan, TRUE);
///
【5】CAN中断服务子程序(用于CAN接收):
stc_can_rxframe_t stcRxFrame;
stc_can_txframe_t stcTxFrame;
uint8_t u8RxFlag = FALSE;
/**************************************************************************
* 函数名称: Can_IRQHandler
* 功能描述: CAN中断服务函数
**************************************************************************/
void Can_IRQHandler(void)
{
if(TRUE == CAN_IrqFlgGet(CanRxIrqFlg))
{
CAN_IrqFlgClr(CanRxIrqFlg);
CAN_IrqCmd(CanRxIrqEn, FALSE);
CAN_Receive(&stcRxFrame);
u8RxFlag = TRUE;
}
}
【6】CAN发送接口(用于CAN发送):
/**************************************************************************
* 函数名称: COM_Tx
* 功能描述: CAN通信发送测试
**************************************************************************/
void COM_Tx(void)
{
//中断接收,循环发送
uint8_t u8Idx = 0;
if(TRUE == u8RxFlag)
{
u8RxFlag = FALSE;
if(1 == stcRxFrame.Cst.Control_f.RTR)
{
return;
}
//<
【7】主函数调用:
int32_t main(void)
{
//系统时钟
App_SysClkInit();
//通信模块
COM_Init();
COM_Cfg();
while(1)
{
//通信
COM_Tx();
delay1ms(10);
}
}
进行CAN测试的方法很多,如果没有USB-CAN通信转换工具/CAN协议分析工具,可以增加CAN外部回环或者内部回环的配置,配合Log串口输出或者仿真器Debug进行测试。我这边使用了USB-CAN通信转换工具(CAN-II)/CAN协议分析工具(CANTest)直接进行收发测试:
【1】选择 CANTest 中的对应设备 USBCAN2:
【2】匹配软件中的波特率为1M,确定并启动CAN:
【3】修改帧ID为1,点击发送,可以看到上位机发送数据到测试板,经由测试板返回相同长度及内容的数据:
以上配置及测试成功。
【1】发送多帧背景说明:
实际应用中,往往需要收到一组请求之后,发送多组数据。尝试直接在 COM_Tx 函数中紧跟着第一帧数据发送之后再发一帧,有了如下操作:
实测上述连续发送的方法是错误的,因为时间间隔太短,前一组通过主缓冲器(PTB)发送的数据还没有完成就又收到新的发送命令,导致后发送的命令失败。
【2】同一个缓冲器连续发送多帧,中间加延时的方法:
使用同一个主缓冲器(PTB)发送,中间增加1ms延时等待上一帧发送结束(在我的系统上实测这个延迟的时间要大于500us,其他环境不绝对):
/**************************************************************************
* 函数名称: COM_Tx
* 功能描述: CAN通信发送测试
* 其他说明:
**************************************************************************/
void COM_Tx(void)
{
uint8_t u8Idx = 0;
if(TRUE == u8RxFlag)
{
u8RxFlag = FALSE;
if(1 == stcRxFrame.Cst.Control_f.RTR)
{
return;
}
//<<主缓冲器Can Tx第一帧
stcTxFrame.StdID = 0;
stcTxFrame.Control_f.DLC = 8;
stcTxFrame.Control_f.IDE = 0;
stcTxFrame.Control_f.RTR = 0;
stcTxFrame.TBUF32_2[0] = 0;
stcTxFrame.TBUF32_2[1] = 1;
CAN_SetFrame(&stcTxFrame);
CAN_TransmitCmd(CanPTBTxCmd); //PTB发送命令
delay1ms(1); //若使用单一缓冲器,连续发送失败,需要中间加延时等待上一帧发送结束(实测大于500us)
stcTxFrame.StdID = 1;
stcTxFrame.Control_f.DLC = 8;
stcTxFrame.Control_f.IDE = 0;
stcTxFrame.Control_f.RTR = 0;
stcTxFrame.TBUF32_2[0] = 2;
stcTxFrame.TBUF32_2[1] = 3;
CAN_SetFrame(&stcTxFrame);
CAN_TransmitCmd(CanPTBTxCmd); //PTB发送命令
CAN_IrqCmd(CanRxIrqEn, TRUE);
}
}
测试结果如下:
【3】使用不同缓冲器发送多帧的方法:
除了上述同一个缓冲器加延时以实现连续发送多帧数据之外,还可以依赖MCU CAN外设提供的另外几组缓冲器实现连续发送多帧数据的目的(更可靠)。
由芯片说明书可以看到,主缓冲器(PTB)只能缓冲一帧数据。而另外的副缓冲器(STB)可以缓冲4帧数据。实现代码如下(收到信号之后,一次发送5帧数据,其中第一帧挂载主缓冲器PTB上,其他四帧挂载副缓冲器STB上):
/**************************************************************************
* 函数名称: COM_Tx
* 功能描述: CAN通信发送测试
* 其他说明:
**************************************************************************/
void COM_Tx(void)
{
uint8_t u8Idx = 0;
if(TRUE == u8RxFlag)
{
u8RxFlag = FALSE;
if(1 == stcRxFrame.Cst.Control_f.RTR)
{
return;
}
//<<主缓冲器Can Tx第一帧
stcTxFrame.StdID = 0;
stcTxFrame.Control_f.DLC = 8;
stcTxFrame.Control_f.IDE = 0;
stcTxFrame.Control_f.RTR = 0;
stcTxFrame.TBUF32_2[0] = 0;
stcTxFrame.TBUF32_2[1] = 1;
CAN_SetFrame(&stcTxFrame);
CAN_TransmitCmd(CanPTBTxCmd); //PTB发送命令
// delay1ms(1); //若使用单一缓冲器,连续发送失败,需要中间加延时等待上一帧发送结束(实测大于500us)
//<<副缓冲器Can Tx第二帧
stcTxFrame.StdID = 1;
stcTxFrame.Control_f.DLC = 8;
stcTxFrame.Control_f.IDE = 0;
stcTxFrame.Control_f.RTR = 0;
stcTxFrame.enBufferSel = CanSTBSel;
stcTxFrame.TBUF32_2[0] = 2;
stcTxFrame.TBUF32_2[1] = 3;
CAN_SetFrame(&stcTxFrame);
//<<副缓冲器Can Tx第三帧
stcTxFrame.StdID = 2;
stcTxFrame.Control_f.DLC = 8;
stcTxFrame.Control_f.IDE = 0;
stcTxFrame.Control_f.RTR = 0;
stcTxFrame.enBufferSel = CanSTBSel;
stcTxFrame.TBUF32_2[0] = 4;
stcTxFrame.TBUF32_2[1] = 5;
CAN_SetFrame(&stcTxFrame);
//<<副缓冲器Can Tx第四帧
stcTxFrame.StdID = 3;
stcTxFrame.Control_f.DLC = 8;
stcTxFrame.Control_f.IDE = 0;
stcTxFrame.Control_f.RTR = 0;
stcTxFrame.enBufferSel = CanSTBSel;
stcTxFrame.TBUF32_2[0] = 6;
stcTxFrame.TBUF32_2[1] = 7;
CAN_SetFrame(&stcTxFrame);
//<<副缓冲器Can Tx第五帧
stcTxFrame.StdID = 4;
stcTxFrame.Control_f.DLC = 8;
stcTxFrame.Control_f.IDE = 0;
stcTxFrame.Control_f.RTR = 0;
stcTxFrame.enBufferSel = CanSTBSel;
stcTxFrame.TBUF32_2[0] = 8;
stcTxFrame.TBUF32_2[1] = 9;
CAN_SetFrame(&stcTxFrame);
CAN_TransmitCmd(CanSTBTxAllCmd); //STB所有帧发送命令
CAN_IrqCmd(CanRxIrqEn, TRUE);
}
}
测试结果如下:
综上,CAN通信收发测试完成。