电子说
CAN全称是Controller Area Network,控制器局域网络,是ISO国际标准化的串行通信协议。CAN是国际上应用最广泛的现场总线之一。
CAN通信只有两根信号线,分别是CAN_H和CAN_L,CAN 控制器根据这两根线上的电位差来判断总线电平。总线申平分为显性电平和隐性申平,二者必居其一。发送方通过使总线电平发生变化,将消息发送给接收方。
CAN总线遵从“线与”机制, 显性电平可以覆盖隐性电平 。这就导致 只有所有节点都发送隐形电平时总线才处于隐性状态 。
CAN通信示意图
CAN通信有5种帧类型
在上述的几种帧里,数据很和遥控帧有标准帧和扩展帧两种。标准帧有11位ID,扩展帧有29位ID。
CAN通信数据帧的构成如下
CAN通信数据帧结构
RTR是用来表示是否是远程帧(遥控帧)。RTR为0是数据帧,RTR为1是远程帧。扩展帧中的IDE是标识符的选择位,如果为0,使用标准标识符,如果为1,使用扩展标识符。扩展帧的SRR相当于标准帧中的RTR位。 标准帧的ID禁止高七位是隐性电平 。
• 控制段 控制段由6位构成,表示数据段的字节数。
控制段
扩展帧的r0和r1是保留位,保留位必须全部以显性电平发送。DLC是数据的长度码,数据的字节数范围是0~8。IDE是标识符的选择位,如果为0,使用标准标识符,如果为1,使用扩展标识符。
• 数据段 数据段可以包含0~8个字节的数据。从MSB(最高位)开始传输。标准帧和扩展帧的数据段相同。
• CRC段 CRC段用于校验,检查帧传输是否存在错误。CRC段包含15位CRC序列和1位CRC界定符。标准帧和扩展帧的CRC段相同。
CRC段
• ACK段 ACK段用来确认是否正常接收。由ACK槽和ACK界定符2位组成。标准帧和扩展帧的ACK段相同。
ACK段
• 帧结束 由7位隐形电平组成,表示帧的结束。标准帧和扩展帧的帧结束相同。
由发送单元在非同步状态下每秒钟发送的位数称为 位速率 。一个位可以分成4段。
• 同步段 SS
• 传播时间段 PTS
• 相位缓冲段1 PBS1
• 相位缓冲段2 PBS2
上面的这些段由称为Time Quantum( Tq )的最小时间单位组成。1个位分成4个段,一个段又分成若干个Tq,这成为 位时序 。
位构成
采样点是读取总线电平,并将读到的电平作为位值的点。
在总线空闲态,最先开始发送消息的单元获得发送权。当多个单元同时开始发送时,各发送单元从仲裁段的第一位开始进行仲裁。连 续输出显性电平最多的单元可继续发送 。
仲裁过程
STM32F1芯片自带bxCAN 控制器 (Basic Extended CAN),即基本扩展CAN,可与 CAN 网络进行交互,它支持 2.0A 和 B 版本的CAN 协议。STM32F1的bxCAN有以下特点
bxCAN模块可以完全自动地接收和发送CAN报文,且完全支持标准标识符(11位)和扩展标识符(29位)。
bXCAN有3个主要的工作模式: 初始化模式 、正常模式和 睡眠模式 。除此之外,还有测试模式,静默模式,环回模式。
首先看一下CAN主控制寄存器 (CAN_MCR)的INRQ位。
寄存器介绍CAN_MCR
寄存器CAN_MSR介绍
通过介绍可以直到,想要进入初始化模式,软件先将CAN_MCR的INRQ位置1。然后等待硬件将CAN主状态寄存器(CAN_MSR)的INAK位置1。此时进入初始化模式。
当bxCAN处于初始化模式时,禁止报文的接收和发送,并且CANTX引脚输出隐性位(高电平)。
在初始化完成后,软件应该让硬件进入正常模式,以便正常接收和发送报文。继续看上面对于CAN主控制寄存器INRQ位的介绍。软件将INRQ位清0,可以使CAN从初始化模式进入正常模式。此时等待硬件将CAN主状态寄存器的INAK位清0即可。
bxCAN可工作在低功耗的睡眠模式。在该模式下,bxCAN的时钟停止了,但软件仍然可以访问邮箱寄存器。
寄存器CAN_MSR介绍
可以看出,软件将CAN主控制寄存器的SLEEP置1,即可请求进入睡眠模式。清零该位,退出睡眠模式。另外,如果CAN_MCR寄存器的AWUM位为’1’,一旦检测到CAN总线的活动,硬件就自动对SLEEP位清’0’来唤醒bxCAN。
将CAN_BTR寄存器的SILM位置’1’,来选择静默模式。
寄存器CAN_BTR介绍
在静默模式下,bxCAN可以正常地接收数据帧和远程帧,但只能发出隐性位,而不能真正发送报文。如果bxCAN需要发出显性位(确认位、过载标志、主动错误标志),那么这样的显性位在内部被接回来从而可以被CAN内核检测到,同时CAN总线不会受到影响而仍然维持在隐性位状态。因此,静默模式通常用于分析CAN总线的活动,而不会对总线造成影响-显性位(确认位、错误帧)不会真正发送到总线上。
静默模式
将CAN_BTR寄存器的LBKM位置’1’,来选择环回模式。在环回模式下,bxCAN把发送的报文当作接收的报文并保存(如果可以通过接收过滤)在接收邮箱里。
寄存器CAN_BTR寄存器介绍
环回模式可用于自测试。为了避免外部的影响,在环回模式下CAN内核忽略确认错误(在数据/远程帧的确认位时刻,不检测是否有显性位)。在环回模式下,bxCAN在内部把Tx输出回馈到Rx输入上,而完全忽略CANRX引脚的实际状态。发送的报文可以在CANTX引脚上检测到。
环回模式
STM32将每一位分成三段
位时序
其中tpclk是APB1总线的时钟频率,默认为36MHz。
CAN_RX配置为上拉输入模式,CAN_TX配置为复用推挽输出。
uint8_t CAN_Init(CAN_TypeDef* CANx, CAN_InitTypeDef* CAN_InitStruct)
结构体成员如下
波特率 = Fpclk1 / ((CAN_BS1 + CAN_BS2 + 1)* CAN_Prescaler)
void CAN_FilterInit(CAN_FilterInitTypeDef* CAN_FilterInitStruct)
结构体内容如下
void CAN_ITConfig(CAN_TypeDef* CANx, uint32_t CAN_IT, FunctionalState NewState)
CAN的中断类型有很多,这里就不再一一介绍了。
#define IS_CAN_IT(IT) (((IT) == CAN_IT_TME) || ((IT) == CAN_IT_FMP0) ||
((IT) == CAN_IT_FF0) || ((IT) == CAN_IT_FOV0) ||
((IT) == CAN_IT_FMP1) || ((IT) == CAN_IT_FF1) ||
((IT) == CAN_IT_FOV1) || ((IT) == CAN_IT_EWG) ||
((IT) == CAN_IT_EPV) || ((IT) == CAN_IT_BOF) ||
((IT) == CAN_IT_LEC) || ((IT) == CAN_IT_ERR) ||
((IT) == CAN_IT_WKU) || ((IT) == CAN_IT_SLK))
uint8_t CAN_Transmit(CAN_TypeDef* CANx, CanTxMsg* TxMessage)
发送之前需要配置好消息的结构体,消息结构体成员如下
void CAN_ITConfig(CAN_TypeDef* CANx, uint32_t CAN_IT, FunctionalState NewState)
CAN的中断类型有很多,这里就不再一一介绍了。
#define IS_CAN_IT(IT) (((IT) == CAN_IT_TME) || ((IT) == CAN_IT_FMP0) ||
((IT) == CAN_IT_FF0) || ((IT) == CAN_IT_FOV0) ||
((IT) == CAN_IT_FMP1) || ((IT) == CAN_IT_FF1) ||
((IT) == CAN_IT_FOV1) || ((IT) == CAN_IT_EWG) ||
((IT) == CAN_IT_EPV) || ((IT) == CAN_IT_BOF) ||
((IT) == CAN_IT_LEC) || ((IT) == CAN_IT_ERR) ||
((IT) == CAN_IT_WKU) || ((IT) == CAN_IT_SLK))
uint8_t CAN_Transmit(CAN_TypeDef* CANx, CanTxMsg* TxMessage)
发送之前需要配置好消息的结构体,消息结构体成员如下
uint8_t CAN_TransmitStatus(CAN_TypeDef* CANx, uint8_t TransmitMailbox)
FlagStatus CAN_GetFlagStatus(CAN_TypeDef* CANx, uint32_t CAN_FLAG);
/*
*==============================================================================
*函数名称:Drv_Can_Init
*函数功能:初始化CAN
*输入参数:tsjw:重新同步跳跃宽度(Tsjw);tbs1:BS1长度;tbs2:BS2长度;
brp:Tq大小;mode:CAN工作模式
*返回值:无
*备 注:无
*==============================================================================
*/
void Drv_Can_Init (u8 tsjw,u8 tbs1,u8 tbs2,u16 brp,u8 mode)
{
// 结构体定义
GPIO_InitTypeDef GPIO_InitStructure;
CAN_InitTypeDef CAN_InitStructure;
CAN_FilterInitTypeDef CAN_FilterInitStructure;
// 开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE); // 打开CAN1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // PA端口时钟打开
// 初始化GPIO
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; // PA11 CAN_RX
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入模式
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; // PA12 CAN_TX
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // IO口速度为50MHz
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 初始化CAN
CAN_InitStructure.CAN_TTCM=DISABLE; // 非时间触发通信模式
CAN_InitStructure.CAN_ABOM=DISABLE; // 软件自动离线管理
CAN_InitStructure.CAN_AWUM=DISABLE; // 睡眠模式通过软件唤醒(清除CAN- >MCR的SLEEP位)
CAN_InitStructure.CAN_NART=ENABLE; // 使用报文自动传送
CAN_InitStructure.CAN_RFLM=DISABLE; // 报文不锁定,新的覆盖旧的
CAN_InitStructure.CAN_TXFP=DISABLE; // 优先级由报文标识符决定
CAN_InitStructure.CAN_Mode= mode; //CAN工作模式设置
CAN_InitStructure.CAN_SJW=tsjw; // 重新同步跳跃宽度(Tsjw)为tsjw+1个时间单位 CAN_SJW_1tq~CAN_SJW_4tq
CAN_InitStructure.CAN_BS1=tbs1; // Tbs1范围CAN_BS1_1tq ~CAN_BS1_16tq
CAN_InitStructure.CAN_BS2=tbs2; // Tbs2范围CAN_BS2_1tq ~ CAN_BS2_8tq
CAN_InitStructure.CAN_Prescaler=brp; //分频系数(Fdiv)为brp+1
CAN_Init(CAN1, &CAN_InitStructure); // 初始化CAN1
// 初始化过滤器
CAN_FilterInitStructure.CAN_FilterNumber=0; // 过滤器0
CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask; // 掩码模式
CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit; // 32位
CAN_FilterInitStructure.CAN_FilterIdHigh=0x0000; // 32位ID
CAN_FilterInitStructure.CAN_FilterIdLow=0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdHigh=0x0000; // 32位MASK
CAN_FilterInitStructure.CAN_FilterMaskIdLow=0x0000;
CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0; // 过滤器0关联到FIFO0
CAN_FilterInitStructure.CAN_FilterActivation=ENABLE; // 激活过滤器0
CAN_FilterInit(&CAN_FilterInitStructure); // 过滤器初始化
}
/*
*==============================================================================
*函数名称:Med_Can_Send_Msg
*函数功能:发送报文
*输入参数:msg:数据段指针;len:数据长度
*返回值:0:发送成功;1:发送失败
*备 注:固定ID为0x12
*==============================================================================
*/
u8 Med_Can_Send_Msg (u8* msg,u8 len)
{
u8 mbox;
u16 i = 0;
CanTxMsg TxMessage; // 定义发送报文结构体
TxMessage.StdId = 0x12; // 标准标识符
TxMessage.ExtId = 0x12; // 扩展标识符
TxMessage.IDE = CAN_Id_Standard; // 使用标准标识符
TxMessage.RTR = 0; // 消息类型为数据帧,一帧8位
TxMessage.DLC = len;
for(i = 0;i < len;i ++)
{
TxMessage.Data[i] = msg[i]; // 填充帧数据段
}
mbox = CAN_Transmit(CAN1,&TxMessage); // 发送报文
i = 0;
// 等待发送结束
while((CAN_TransmitStatus(CAN1,mbox) == CAN_TxStatus_Failed) && (i < 0XFFF))
{
i++;
}
// 返回发送情况
if(i >= 0XFFF)
{
return 1;
}
return 0;
}
/*
*==============================================================================
*函数名称:Med_Can_Receive_Msg
*函数功能:接收报文
*输入参数:buf:数据缓存区指针
*返回值:0:没有接收到数据;其他:接收数据长度
*备 注:无
*==============================================================================
*/
u8 Med_Can_Receive_Msg (u8 *buf)
{
u32 i;
CanRxMsg RxMessage; // 定义接收报文结构体
// 没有接收到数据,直接退出
if( CAN_MessagePending(CAN1,CAN_FIFO0) == 0)
{
return 0;
}
CAN_Receive(CAN1, CAN_FIFO0, &RxMessage); // 读取数据
for(i = 0;i < RxMessage.DLC;i ++)
{
buf[i] = RxMessage.Data[i];
}
return RxMessage.DLC;
}
利用按键WK UP控制报文的发送,按下一次发送一次报文。配置CAN波特率为500Kbps,环回模式。利用串口打印接收数据。需要注意的是,STM32只有CAN控制器,想要实现报文的收发,需要自己连接CAN收发器。
首先初始化CAN
// 初始化CAN,500Kbps波特率
Drv_Can_Init(CAN_SJW_1tq,CAN_BS1_9tq,CAN_BS2_8tq,4,CAN_Mode_LoopBack);
然后编写主程序
u8 gKeyValue = 0; // 获取按键值
u8 gSendData[8] = {'1','2','3','4','5','6','7','8'}; // 发送内容数组
u8 gReceData[8]; // 接收内容数组
u8 gFlag = 0; // 接收发送标志
int main(void)
{
Med_Mcu_Iint(); // 系统初始化
while(1)
{
gKeyValue = Med_KeyScan();
// WK UP 按下发送消息
if (gKeyValue == 1)
{
gFlag = Med_Can_Send_Msg(gSendData,8);
// 发送失败
if (gFlag)
{
printf ("Send Defeat!rn");
}
else
{
printf ("Send Success!rn");
}
}
// 接收报文
gFlag = Med_Can_Receive_Msg(gReceData);
// 接收成功
if (gFlag)
{
printf ("Receive Data:%srn",gReceData);
}
}
}
测试结果如下
测试结果
上面的CAN收发测试程序,发送的内容是字符串“12345678”。如果发送的是数字12345678。在串口打印接收数据时需要先将接收到的数据转换成字符,然后再打印。转换方法很简单,只需要对接收数组的每一位加48即可。
全部0条评论
快来发表一下你的评论吧 !