32系列串口之CAN通信

本文详细介绍了CAN通信的硬件设计,包括高性能隔离芯片TD(H)341SCAN的选择与使用,以及基于单片机的CAN配置步骤,包括时钟使能、GPIO配置、波特率设置和过滤器配置。并展示了使用HAL库进行CAN数据发送和接收的代码实例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近在搞串口通信,把一些串口的调试及心得与大家分享分享,如有错误,欢迎大家指正。

在前面的博文当中已经讲解了RS485通信的原理及代码的逐一分析以及CAN数据的数据帧处理方式,今天和大家讲解一下有管CAN通信的硬件设计及代码分析;

本次采用的通讯芯片是拥有高性能的隔离芯片TD(H)341SCAN,TD(H)341SCAN是一款带隔离电源的CAN转发器,此转发器符合或者优于ISO11898-2标准的技术规范。它集成3.3V隔离电源,逻辑侧支持3.3V 和5V逻辑电平的转换。作为CAN收发器,该器件可为总线和CAN控制器分别提供差分发射能力和差分接收能力,信号传输速率高达1兆位每秒(Mbps)。 该器件尤其适合工作在恶劣环境下,其具有串线、过压(–40V至40V)和接地损耗保护以及过热关断功能。改款芯片常用于工业自动化、控制、传感器和驱动系统电路当中。

改款芯片的硬件电路设计也是非常的简单,网上都可以查到相关电路的资料,下图是博主电路中有关CAN部分的原理图设计:

原理图中,120R电阻及SM712元器件的作用都是不可或缺的,其中最120R电阻主要作用可分为3点:

1.提高抗干扰能力(快速通过高频低两信号);

2.降低反射信号,提高信号质量。

3.确保总线快速进入隐性状态,让电容中的电信号快速消除(示波器可观测);

SM712是一款瞬态电压抑制器 (TVS)二极管, 设计用于多点数据传输标准应用的不对称保护, SM712可用于保护设备免受静电放电 (ESD), 电快速瞬变 (EFT)与闪电造成的瞬态电压 SM712具有400 W (tp=8/20µs)功率的处理能力, 以适用扩展共模应用中可能会出现的较高瞬态电压。

硬件电路就是比较简单的,接下来该到讲解程序方面了。

单片机串口的配置过程我相信各位是非常清楚不过的了,但是为了混几行字,我就再啰嗦一遍

单片机串口配置大致分为3个步骤,分别是串口时钟的使能、GPIO端口的配置、初始化串口数据,这些如果不懂的可以去看看博主之前发的博文,里面有细讲。

对于CAN通信的配置步骤可就比串口的配置复杂些了,不过具体步骤也还是一样的,具体如下:

1.使能CAN时钟,将MCU对应的引脚复用映射为CAN功能

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, EANBLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, EANBLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;			//上拉输入模式
GPIO_Init(GPIOA, & GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;		//复用推挽输出
GPIO_InitStructure.GPIO_Speede = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, & GPIO_InitStructure);

2.设置CAN的工作方式、波特率等等

//CAN配置 可以通过调用底层CAN_InitStruct结构体进行配置选择
uint16_t CAN_prescaler;		//设置CAN外设时钟分频
uint8_t CAN_Mode;			//CAN模式 正常模式、回环模式、静默模式、回环静默模式
uint8_t CAN_SJW;				//设置CAN重新同步时单次可增加或缩短的最大长度
uint8_t CAN_BS1;				//设置位时序BS1长度,CAN_BS1_1/2/3/…/16tp
uint8_t CAN_BS2;				//设置位时序BS2长度,CAN_BS1_1/2/3/…/8tp
FunctionalState CAN_TTCM;	//是否使用时间触发功能
FunctionalState CAN_ABOM;	//是否使用自动离线管理
FunctionalState CAN_AWUM;	//是否使用自动唤醒功能
FunctionalState CAN_NART;	//是否使用自动重传功能
FunctionalState CAN_RFLM;	//是否使用锁定接收FIFO
FunctionalState CAN_TXFP;		//设置发送报文的优先级判断方法

3.设置CAN通讯的过滤器

uint16_t CAN_FilterIdHigh;			//存储需要筛选的ID高16位
uint16_t CAN_FilterIdLow;			//存储需要筛选的ID低16位
uint16_t CAN_FilterMaskIdHigh;		//存储需要筛选ID或掩码
uint16_t CAN_FilterMaskIdLow;		//存储需要筛选ID或掩码
uint16_t CAN_FilterFIFOAssignment;	//设置报文被存储到那一个接收FIFO
uint8_t CAN_FilterNumber;			//设置筛选器编号,0~27
uint8_t CAN_FilterMode;			//设置筛选器工作模式
uint8_t CAN_FilterScale;			//设置筛选器位宽

具体比较重要的CAN配置就介绍完毕了,接下来给大家展示一下博主这边的通讯代码(HAL库)

#include "can.h"

/* USER CODE BEGIN 0 */
#define CAN1_ID_H 0x0000   // 32位基础ID设置(高16位)
#define CAN1_ID_L 0x0000   // 32位基础ID设置(低16位)
#define CAN1_MASK_H 0x0000 // 32位屏蔽MASK设置(高16位)
#define CAN1_MASK_L 0x0000 // 32位屏蔽MASK设置(低16位)

CAN_TxHeaderTypeDef TxMeg;         // CAN发送设置相关结构体
CAN_RxHeaderTypeDef RxMeg;         // CAN接收设置相关结构体
COMM_HandleTypeDef can1data;
/* USER CODE END 0 */

CAN_HandleTypeDef hcan1;

/* CAN1 init function */
void MX_CAN1_Init(void)
{
  CAN_FilterTypeDef CAN1_sFilterConfig;
  HAL_StatusTypeDef HAL_Status;

  hcan1.Instance = CAN1;
  hcan1.Init.Prescaler = 6;//配置 CAN 外设的时钟分频   Tq = CAN_Prescaler x TPCLK
  hcan1.Init.Mode = CAN_MODE_NORMAL;//CAN正常模式
  hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ;//配置 SJW 极限值
  hcan1.Init.TimeSeg1 = CAN_BS1_5TQ;//配置 BS1 段长度
  hcan1.Init.TimeSeg2 = CAN_BS2_1TQ;//配置 BS2 段长度
  hcan1.Init.TimeTriggeredMode = DISABLE;//不使能 TTCM 时间触发功能
  hcan1.Init.AutoBusOff = DISABLE;//不使能 ABOM 自动离线管理功能
  hcan1.Init.AutoWakeUp = DISABLE;//不使能 AWUM 自动唤醒功能 
  hcan1.Init.AutoRetransmission = DISABLE;//不使能 NART 自动重传功能
  hcan1.Init.ReceiveFifoLocked = DISABLE;//不使能 RFLM 锁定 FIFO 功能
  hcan1.Init.TransmitFifoPriority = DISABLE;//配置 TXFP 报文优先级的判定方法 
  if (HAL_CAN_Init(&hcan1) != HAL_OK) // 判断初始化是否成功
  {
    Error_Handler();// 开启CAN总线失败的处理程序 
  }

  CAN1_sFilterConfig.FilterIdHigh = CAN1_ID_H;            // 32位基础ID设置(高16位)
  CAN1_sFilterConfig.FilterIdLow = CAN1_ID_L;             // 32位基础ID设置(低16位)
  CAN1_sFilterConfig.FilterMaskIdHigh = CAN1_MASK_H;      // 32位屏蔽MASK设置(高16位)
  CAN1_sFilterConfig.FilterMaskIdLow = CAN1_MASK_L;       // 32位屏蔽MASK设置(低16位)
  CAN1_sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0; // 接收到的报文放入FIFO0位置
  CAN1_sFilterConfig.FilterBank = 0;                      // 过滤器0
  CAN1_sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;  // 设为IDLIST列表模式/IDMASK屏蔽模式
  CAN1_sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; // 过滤器位宽度
  CAN1_sFilterConfig.FilterActivation = ENABLE;           // ENABLE激活过滤器,DISABLE禁止过滤器
  CAN1_sFilterConfig.SlaveStartFilterBank = 0;            // 过滤器组设置(单个CAN总线时无用)
  HAL_Status = HAL_CAN_ConfigFilter(&hcan1, &CAN1_sFilterConfig);

  if (HAL_Status != HAL_OK) // 判断开启是否成功
  {
    Error_Handler(); // 开启CAN总线失败的处理程序
  }

  /* Start the CAN peripheral */
  HAL_Status = HAL_CAN_Start(&hcan1);

}

void HAL_CAN_MspInit(CAN_HandleTypeDef* canHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(canHandle->Instance==CAN1)
  {
    __HAL_RCC_CAN1_CLK_ENABLE(); //使能CAN时钟
    __HAL_RCC_GPIOA_CLK_ENABLE();//使能GPIO时钟
    /**CAN1 GPIO Configuration
    PA11     ------> CAN1_RX
    PA12     ------> CAN1_TX
    */
    GPIO_InitStruct.Pin = GPIO_PIN_11|GPIO_PIN_12;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF9_CAN1;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    HAL_NVIC_SetPriority(CAN1_RX0_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(CAN1_RX0_IRQn);

  }
}


void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) // 接收回调函数
{
  uint8_t Data[8];                                                     // 接收缓存数组
  HAL_StatusTypeDef HAL_RetVal;                                        // 判断状态的枚举
  HAL_RetVal = HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &RxMeg, Data); // 接收邮箱中的数据

  if (HAL_OK == HAL_RetVal)
  { // 判断接收是否成功
    // 接收成功后的数据处理程序,写在此处。(数据在Data数组中)
    memcpy(&can1data.RxBuffer[can1data.RxCnt],&Data[0],8);
    can1data.RxCnt+=8;            // 数据接收每次8个
    can1data.RxTimeOut = 5;// 超时2ms
  }
}
// CAN发送数据函数(参数:总线名,ID,数据数组,数量。返回值:0成功HAL_OK,1参数错误HAL_ERROR,2发送失败HAL_BUSY)
// 示例:CAN1_SendNormalData(&hcan1,0,CAN_buffer,8);//CAN发送数据函数
uint8_t CAN1_SendNormalData(CAN_HandleTypeDef *hcan, uint16_t ID, uint8_t *pData, uint16_t Len)
{
  HAL_StatusTypeDef HAL_RetVal; // 判断状态的枚举
  uint16_t SendTimes, SendCNT = 0;
  uint8_t FreeTxNum = 0;
  uint8_t FreeLevelCount = 0;
  uint32_t CAN_TX_BOX0;
  TxMeg.StdId = ID;
  TxMeg.IDE = CAN_ID_STD;   // 扩展帧标识(STD标准帧/EXT扩展帧)
  TxMeg.RTR = CAN_RTR_DATA; // 远程帧标识(DATA数据帧/REMOTE远程帧)

  if (!hcan || !pData || !Len)
  {
    // printf("\n\rCAN发送失败!\n\r"); // 串口发送
    return HAL_ERROR;                // 如果总线名、数据、数量任何一个为0则返回值为1
  }

  SendTimes = Len / 8 + (Len % 8 ? 1 : 0);
  FreeTxNum = HAL_CAN_GetTxMailboxesFreeLevel(hcan); // 得出空闲邮箱的数量
  TxMeg.DLC = 8;

  while (SendTimes--)
  { // 循环判断分批发送是否结束
    if (0 == SendTimes)
    { // 如果分批发送结束
      if (Len % 8)
        TxMeg.DLC = Len % 8; // 则加入最后不足8个的数据内容
    }

    FreeLevelCount = 0; // 防止死循环

    while (0 == FreeTxNum && FreeLevelCount < 10)
    {
      FreeLevelCount++;
      HAL_Delay(1);
      FreeTxNum = HAL_CAN_GetTxMailboxesFreeLevel(hcan);
    }
    HAL_Delay(1); // 延时防止速度过快导致的发送失败
    // 开始发送数据(参数:总线名,设置参数,数据,邮箱号)
    HAL_RetVal = HAL_CAN_AddTxMessage(hcan, &TxMeg, pData + SendCNT, &CAN_TX_BOX0);

    if (HAL_RetVal != HAL_OK)
    {
      // printf("\n\rCAN总线忙碌!\n\r"); // 串口发送
      return HAL_BUSY;                 // 如果发送失败,则返回值为2
    }

    SendCNT += 8;
  }
  return HAL_OK; // 如果发送成功结束,返回值为0
}

can.h文件

#ifndef __CAN_H__
#define __CAN_H__

#ifdef __cplusplus
extern "C"
{
#endif

#include "main.h"

extern CAN_HandleTypeDef hcan1;
extern COMM_HandleTypeDef can1data;
//Can通讯收集的数据存入结构体里面extern为全局变量 后面直接调用结构体取出数据出来调用即可

void MX_CAN1_Init(void);
uint8_t CAN1_SendNormalData(CAN_HandleTypeDef *hcan, uint16_t ID, uint8_t *pData, uint16_t Len); // CAN发送函数

#ifdef __cplusplus
}
#endif

#endif /* __CAN_H__ */

以上便是有关CAN通信的数据采集了,希望博文可以帮助到各位小伙伴,如有错误,欢迎矫正,对于CAN数据帧的处理在之前的博文里面已经提到过了,感兴趣的小伙伴可以去看看,下期博文就讲解一下网口转串口的硬件设计及代码分析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值