声明:此为本人学习(尚硅谷)过程中的笔记

1 介绍

CAN(Control Area Network,控制器局域网  简称CAN或者CAN Bus)。是功能丰富的车用总线标准。

有一根CAN-Bus总线,所有的装置可以挂在这条总线上。

1.1 物理层

  • 一种功能丰富的车用总线标准。
  • 设计在于不需要主机的情况下,允许网络上的单片机和设备相互通信。
  • 基于消息传递协议。
  • CAN拥有良性的弹性调整功能,可以在现有网络中增加节点,而不用在软、硬键上调整。        
  • 消息的传递不基于任何的特殊结点。

CAN的网络节点:

一个CAN控制器:

一般由MCU提供,STM32内部提供了一个CAN控制器。

一个CAN收发器:

由 专门的芯片提供,如PD1050收发器芯片。

 CAN网络节点数据:

当设备需要发数据时,CAN控制器会通过CAN_TX将要发送的二进制编码发送到CAN收发器

CAN收发器会把收到的逻辑信号转换为差分信号,通过CAN_High和CAN_Low将信号传输到CAN总线网络。

CAN网络节点数据

过程与发数据相反,通过CAN收发器接收总线上的信号,然后将信号转换成逻辑电平向CAN控制器发送。

CAN总线网络

当CAN总线上挂载多个设备时,就形成了CAN总线网络。

根据接法不同,总线网络分成两种:

闭环总线网络:传输距离40m,速度最高1Mbps;

开环总线网络:

最长1km,速度125kbps;

差分信号:

  • CAN_High及CAN_Low 中走的是一对差分信号

  • 差分传输是一种信号传输的技术,差分传输在这两根线上都传输信号,这两个信号的振幅相同,相位相反。

  • 信号接收端比较这两个电压的差值来判断发送端发送的逻辑状态。

  • 在电路板上,差分走线必须是等长、等宽、紧密靠近且在同一层面的两根线

差分信号相比传统的单端信号传输具有以下优势:  

麻烦的点在于差分信号需严格匹配走线(等长、等距,等宽),布局复杂度较高。

2.协议层

2.1 数据帧介绍

2.1.1 CAN的帧(报文)种类

CAN总线是广播类型的总线。这意味着所有节点都可以侦听到所有传输的报文。

数据帧:发送方接收方发送数据的格式

遥控帧:接收方向具有相同ID发送单元发送请求接收数据的请求。

错误帧:检测出错误时向其他单元通知错误的帧。

过载帧:不常见。

帧间隔:用于将数据帧及遥控帧与前面的帧分离开来的帧。

 2.1.2 标准帧

帧起始:与I2C类似,表明我要开始发送数据了。

仲裁段:11位ID+1位RTR:ID是高位先行的;RTR是判断数据帧还是遥控帧的依据,RTR=0时是数据帧,1时是遥控帧。

控制段:1位IDE+1位R0+4位DLC:1位IDE是说明这个是标准帧还是扩展帧;1位R0是保留位;4位DLC是说明数据位的长度

数据段:高位先行

CRC段:CRC校验有关,SPI也有CRC校验,不过SPI可以选择不开启,而CAN必须开启。

2.1.2 扩展帧

与标准帧不一样的就是11位ID后面的 SRR位,这个只是一个占位符,当IDE说明这是个扩展帧时,后面跟18位扩展的ID,然后与标准帧一样,只不过后面说明扩展的IDE位变成了R1保留位。

2.1.3 总线仲裁

开始发送数据时,如果有两个设备同时发送数据,先发一个帧起始0。

CAN总线同一时间只能允许一个信号,现在就会产生谁先发的问题。

此时仲裁段开始仲裁谁先发。

000和001是000占据总线,11位ID通过这个比较来判断,如果设备1(0001)和设备2(001)同时发送,总线数据呈现出0001的数据,此时设备2停止发送,进入接收状态,此时设备2 的数据也没有遭到破环(优点)。所以id小的优先级越高。

如果遥控帧和数据帧的ID相同,那是数据帧先发送,因为数据帧的RTR=0,遥控帧的RTR=1;所以可以解释为什么RTR也在仲裁段。

2.1.4 CAN的位时序


串行,半双工,异步

CAN中提出了位同步的方式来确保通讯时序。

一帧中包含了很多个位

CAN把每1位分成4段时间角度划分):

  • 同步段(SS):一般是上升沿或者下降沿所在的位置,时间为1Tq.
  • 传播时间段(PTS):传输可能会有各种延时,相当于延时缓冲段,1—8Tq.
  • 相位缓冲段1(PBS1)
  • 相位缓冲段2(PBS2)

采样时间在相位缓冲段1和相位缓冲段2的交界处。

4段的总时间构成了位时间(Bit Time),就是传输一个位所需的总时间

位时间通常被分为若干段等长的时间单元,称为时间量化器(Tq)。

一个Tq的长度可以根据传输速率的需要设置。

在STM32的CAN外设中,通过设置波特率分频器的值来确定Tq的大小。

根据数据同步的差异,将数据同步分成硬同步和再同步。

硬同步:当一个结点检测到起始位时,他会执行硬同步,以便将其内部的时间基准与数据帧的时间基准对齐。

再同步:在检测到总线上的时序与节点使用的时序有相位差时(即总线的跳变沿不在节点时序的SS段范围),通过延长PBS1段或者缩短PBS2段,来获得同步

上面的同步都是由CAN控制器硬件自动完成的!!!

问题:就是当传输的数据是000000或者111111时,此时判断不了同步情况,会不会出现数据传输错误?

解决方法:位填充, 当连续出现6个0或1时,每5个0或1后加一个相反的位。即

0000010    1111101

3.CAN外设(CAN控制器)介绍

3.1CAN控制器的3种工作模式

初始化,睡眠,正常模式

上电复位后CAN控制器默认会进入睡眠模式,作用是降低功耗。当需要将进行初始的时候(配置寄存器),会进入初始化模式。当需要通讯的时候,就进入正常模式(退出初始化模式)

三种工作模式的转化:复位进入睡眠模式,此时SLAK=1(SL=sleep),INAK=0(IN=init)。

3.2 三种测试模式

静默模式,环回模式,环回静默模式。

1.静默模式:

把CANTX拉高,CANRX正常接收。而且里面的发送接到接收,因为如果发生错误帧等所有状态的报告需要发送到接收段。

    

静默模式可以用于检测总线的数据流量。

环回模式可以用于自检(影响总线)。

环回静默模式也是用于自检,不会影响到总线。

既不是静默模式也不是环回模式时就是正常模式。

3.4 接收滤波器(过滤器)

作用:对接到的报文进行过滤,最后放入FIFO0或FIFO1。

如果总线上有很多设备都发送信号,单纯几个邮箱根本就不足以容纳那么多信息,我们可以检验这个信息是否是我们需要的,需要的信息就拿过来。这样就大大减少CPU的工作。

这个可以由硬件来控制。

有两种过滤模式来检验:

1.标识符列表模式:就是我们把需要接收信息的ID列一个列表,只要符合这个列表的信息就可以拿过来。相当于白名单

2.掩码模式(屏蔽位模式):可以选择ID的前几位,比如要筛选电话开头为158566的号码,就可以用这个,此时ID位可以设置158566*****,(*代表随意数)。屏蔽位设置为11111100000,屏蔽位是1表示来的ID的这位必须和其中对应的位一致,屏蔽位位是0,表示ID的这位不关心。

3.5 STM32中 CAN的位时序

STM32的位时序:把传播时间段和相位缓冲段1做了合并

变成了:

如果是36M的总线,BRP[9:0]设置为35,一个tq就是1us。

如果我们设置的位时序一共为10个Tq,一位就需要10us。

4.代码实现(静默环回的测试模式)

can.c

#include"can.h"
#include"usart.h"

void can_init(void)
{
    //1.时钟的开启
    RCC->APB1ENR|=RCC_APB1ENR_CAN1EN;
    RCC->APB2ENR|=RCC_APB2ENR_IOPBEN;
    RCC->APB2ENR|=RCC_APB2ENR_AFIOEN;
    
    //引脚重映射
    AFIO->MAPR|=AFIO_MAPR_CAN_REMAP_1;
    AFIO->MAPR&=~AFIO_MAPR_CAN_REMAP_0;

    //2.GPIO的配置  PB8浮空输入 mode 00  cnf 01       PB9复用推挽输出  MODE11 CNF10 
    GPIOB->CRH&=~GPIO_CRH_MODE8;
    GPIOB->CRH&=~GPIO_CRH_CNF8_1;
    GPIOB->CRH|=GPIO_CRH_CNF8_0;

    GPIOB->CRH|=GPIO_CRH_MODE9;
    GPIOB->CRH&=~GPIO_CRH_CNF9_0;
    GPIOB->CRH|=GPIO_CRH_CNF9_1;


    //3.配置CAN
    //3.1进入初始化模式
    CAN1->MCR|=CAN_MCR_INRQ;

    while ((CAN1->MSR & CAN_MSR_INAK)==0)   //等待
    {
       
    }
   //退出睡眠模式
    CAN1->MCR&=~CAN_MCR_SLEEP;
    while ((CAN1->MSR & CAN_MSR_SLAK)!=0)
    {
       
    }
    
    //自动唤醒还有自动离线管理打开
    CAN1->MCR|=CAN_MCR_AWUM;
    CAN1->MCR|=CAN_MCR_ABOM;

    //设置静默环回模式
    CAN1->BTR|=CAN_BTR_LBKM;
    CAN1->BTR|=CAN_BTR_SILM;

    //设置波特率和位时序
    CAN1->BTR&=~CAN_BTR_BRP;
    CAN1->BTR|=(35<<0);
    CAN1->BTR&=~CAN_BTR_TS1;
    CAN1->BTR|=(2<<16);
    CAN1->BTR&=~CAN_BTR_TS2;
    CAN1->BTR|=(5<<20);
    CAN1->BTR&=~CAN_BTR_SJW;
    CAN1->BTR|=(1<<24);

    //退出初始化模式相当于进入正常模式
    CAN1->MCR&=~CAN_MCR_INRQ;

    while ((CAN1->MSR & CAN_MSR_INAK)!=0)
    {
        /* code */
    }

    //4.对过滤器进行配置
    //进入过滤器初始化模式
    CAN1->FMR|=CAN_FMR_FINIT;
    //屏蔽位模式
    CAN1->FM1R&=~CAN_FM1R_FBM0;  
    //32位
    CAN1->FS1R|=CAN_FS1R_FSC0;  
    //过滤器分配给FIFO0
    CAN1->FFA1R&=~CAN_FFA1R_FFA0;
    //过滤的id设置
    CAN1->sFilterRegister[0].FR1=0x0000;
    CAN1->sFilterRegister[0].FR2=0x0000;

    
    //激活过滤器
    CAN1->FA1R|=CAN_FA1R_FACT0;
    //退出过滤器初始化模式
    CAN1->FMR &= ~CAN_FMR_FINIT;
}


void can_tx(uint16_t id,uint8_t *data,uint8_t len)
{
    while ((CAN1->TSR&CAN_TSR_TME0)==0)
    {
        /* code */
    }
    
    CAN1->sTxMailBox[0].TIR&=~CAN_TI0R_IDE;
    CAN1->sTxMailBox[0].TIR&=~CAN_TI0R_RTR;
    CAN1->sTxMailBox[0].TIR&=~CAN_TI0R_STID;
    CAN1->sTxMailBox[0].TIR|=(id<<21);
    CAN1->sTxMailBox[0].TDTR&=~CAN_TDT0R_DLC;
    CAN1->sTxMailBox[0].TDTR|=len<<0;
    uint8_t i;
    CAN1->sTxMailBox[0].TDHR=0;
    CAN1->sTxMailBox[0].TDLR=0;

    for ( i = 0; i < len; i++)
    {
        if(i<4)
        {
            CAN1->sTxMailBox[0].TDLR|=(data[i]<<(i*8));
        }
        else
        {
            CAN1->sTxMailBox[0].TDHR|=(data[i]<<((i-4)*8));
        }
    }

    CAN1->sTxMailBox[0].TIR|=CAN_TI0R_TXRQ;

    while((CAN1->TSR&CAN_TSR_TXOK0)==0)
    {

    }
}

void can_rx(rx_msg m_msg[],uint8_t * length)
{
    *length = (CAN1->RF0R & CAN_RF0R_FMP0)>>0;
    printf("%d***********\n",*length);
    
  for (uint8_t i = 0; i < *length; i++)
  {
    rx_msg *msg=&m_msg[i];
    msg->stdid=((CAN1->sFIFOMailBox[0].RIR>>21)&0x7ff);
    msg->len=((CAN1->sFIFOMailBox[0].RDTR>>0) & 0x0f);
    uint32_t low=CAN1->sFIFOMailBox[0].RDLR;
    uint32_t high=CAN1->sFIFOMailBox[0].RDHR;
    for (uint8_t j = 0; j < msg->len; j++)
    {
        if(j<4)
        {
            msg->data[j]=((low>>(j*8)) & 0xff);
        }       
        else
        {
            msg->data[j]=((high>>((j-4)*8))&0xff);
        }
    }
    CAN1->RF0R|=CAN_RF0R_RFOM0;
  }
  
}


对于接收和发送,只要把静默环回去掉就行,在HAL库中设置为NORMAL; 

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐