最近在搞串口通信,把一些串口的调试及心得与大家分享分享,如有错误,欢迎大家指正。
在前面的博文当中已经讲解了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数据帧的处理在之前的博文里面已经提到过了,感兴趣的小伙伴可以去看看,下期博文就讲解一下网口转串口的硬件设计及代码分析。