stm32—CAN

1. CAN BUS 是什么

由德国电器商 BOSCH(博世) 设计,其为世界第一汽车技术供应商

CAN BUS:Controller Area Network Bus 控制器局域网总线,也是像 UART / IIC / SPI 等协议一样用于通讯,当时主要用于汽车电子   ===> 半双工,异步,总线


为什么要设计 CAN 总线呢?

        因为汽车中有大量的电子器件(比如传感器等),有大量的数据需要与汽车的主控进行通信,而 UART / IIC / SPI 总线并不适合汽车电子:

                1. UART 是一对一通信,所以一个电子器件就需要一个串口,设备开销大

                2. IIC 虽然可以一对多,但是 IIC 速率较低(100K bps—400K bps,400K bps相当于 48.8KB/s),而且 IIC 另外的一个缺点是,当主控与总线上的一个设备 A 进行通信时,假设有另外一个设备 B 需要报告一个非常紧急的事件给主控,因 A 的原因,B 会导致紧急事件反应不及时

                3. SPI 虽然速率较 IIC 快(可以达到几M甚至几十M),但是多台设备就需要多个片选

引脚(CS),同样设备开销较大

                4. 以上协议都有同样的问题就是信号传输距离较短(几米甚至几厘米)
        相对而言采用 SPI 更加合理,但 SPI 总线线束数量太多,所以为了解决汽车中大量线束的问题,设计出了 CAN 总线。可以通过多个 LAN(局域网),进行大量数据的高速传输


CAN 怎么实现的?

        CAN 总线只有两根差分信号的数据线,没有其他的线束了

        差分信号: 单端信号用一根线传递信号,差分信号是指两根线(双绞线)上传输同样的信号,振幅相同,相位相同,极性相反,抗干扰能力强,传输距离远

        因为只有数据线,没有时钟线,所以是异步的

        没有时钟线进行同步,所以只能像 UART 一样收发双方约定一个既定的速率(波特率)去收发

        处于同一个 LAN 下设备按照相同的速率进行收发,如果有多个 LAN,LAN 与 LAN 之间速率可能不一样,但同样可以通信,利用 网关 / 转发器 即可


其中 CAN 总线的两根数据线分别为 CAN_H / CAN_L

        CAN_H ---> 高电位驱动(正)

        CAN_L ---> 低电位驱动(负)

                在不同的 LAN 中 CAN_H 有不同的颜色

                                LAN                COLOR

                CAN_L    ALL                   棕色

                CAN_H   驱动系统            黑色

                                舒适系统            绿色

                                信息系统            紫色

                                ......

那么这两根数据线是怎么传输数据的呢?

        CAN_H  ≈ CAN_L               对应于 Logic   1      隐性:Recessive (相差不明显)

        CAN_H - CAN_L ≈ 2.0V      对应于 Logic   0     显性:Dominant (相差很明显)

2. CAN BUS 协议

CAN总线上的数据传输是以 Frame(帧) 为单位进行传输

帧(或者报文)分为四种:

  1. 数据帧:最常见的报文类型,用来发送数据用的,传输某某数据
  2. 远程帧(遥控帧):用于接收单元向具体的发送单元请求数据
  3. 错误帧
  4. 过载帧:overload,你们发的太频繁了,我实在是忙不过来接收不了啦
  5. 间隔帧:用于将数据帧及远程帧与前面的帧分离开来

帧格式:详情见<STM32F4xx中文参考手册.pdf>623页(图236.CAN帧)

 Field Name              Length(Bits)           Purpose(用途)
   字段名               
-------------------------------------------------------------------------------
    SOF                    1bit                 标志一个消息帧的开始
Start Of Frame          (Dominant,0)
-------------------------------------------------------------------------------
                         11bits             CAN 总线是广播类型的总线,位于
                         标准 ID            总线上的所有设备都可以收到总线上
                                            传输的报文,并且 CAN 总线上的设备
Identifier               29bits             没有地址的概念,所以无法将报文单独
(for the data)           扩展 ID            发给指定节点,所有节点都将始终捕获
                                            所有报文。那怎么实现数据的收发?
                                            我们在CAN总线上用不同的 Identifier
                                            标识    A unique identifier
                                            CAN BUS 没有“地址的概念”
                                            也没有某某设备的概念,它只有某某数据内容
                                            CAN BUS 只标识某某数据
                                            id = 1 表示xx数据
                                            id = 2 表示xx数据
                                            id = 3 表示xx数据
                                            ...
--------------------------------------------------------------------------------
  RTR                    
(Romote                                     0   数据帧 ---> 主动发数据
Transmission             1bit               1   远程帧(请求帧) ---> 请求别人发数据
Request)
--------------------------------------------------------------------------------
  IDE                                       0   表示本帧使用的是标准ID 11bits
(Identifier              1bit               1   表示本帧使用的是扩展ID 29bits
Extension)                                   
-------------------------------------------------------------------------------
      R0                                           
(Reserved Bit 0)         1bit               保留位,无意义,但必须为0
-------------------------------------------------------------------------------
      DLC                                   用来确定该消息中,正文数据内容的长度
(Data Length Code)       4bits                   可取值:0 ~ 8bytes
-------------------------------------------------------------------------------
Data Field              0~8bytes            消息正文,也就是你要发送的数据
-------------------------------------------------------------------------------
     CRC                15bits              循环冗余校验多项式(大于0,并且收发方一
                                                                     致就可以了)
-------------------------------------------------------------------------------
CRC Delimter            1bit                CRC分隔符,用来将CRC和后面的内容分隔开
-------------------------------------------------------------------------------
ACK Slot                1bit                应答位:发送时为 Recessive ==> 1
                                                   接收时为 Dominant  ==> 0
-------------------------------------------------------------------------------
ACK Delimter            1bit                ACK 分隔符,必须为1(Recessive)
-------------------------------------------------------------------------------
    EDF
(End Of Frame)          7bits                Must Be Recessive(1)
-------------------------------------------------------------------------------

思考:CAN总线是以广播的形式进行通信,大家都可以往总线上发送数据和接收数据,如果两个或者两个以上的节点同时往CAN总线上发送数据呢?

        此时我们就需要进行总线仲裁:

                总线仲裁是依据于仲裁字段进行的,根据<图236.CAN帧>可知,标准ID数据帧中仲裁字段一共12bit(中文参考手册标的是32bit),其中:

                        RTR     占最后1bit

                        ID        11bit 标准ID

                                    29bit 扩展ID

                怎么依据仲裁字段进行总线仲裁呢?

                        我们约定,CAN总线上每一个节点,都需要去总线上采样(包括发送者,这种行为称之为回读:每个节点往总线上发送1bit 数据的同时会读取1bit总线上的数据,并与自己发送的数据作对比)。采样后就可以进行仲裁啦

                        例子:

                        R:Recessive 隐性       D:Dominant 显性

发送者1

1(R)

0(D)

0(D)

0(D)

...

EOF

发送者2

1(R)

0(D)

1(R)^2  Loser(仲裁失败) --> 转变为接收者

发送者3

1(R)

0(D)

0(D)

1(R) 仲裁失败

总线值

1(R)

0(D)

0(D)^1

0(D)

...

EOF

                        注: 1. 只有总线上所有节点发出的数据都为1时,总线状态才为1, 只要有一个节点发送0,则总线上的状态为0,称之为线与

                                2. 当节点回读的时候发现总线上的电平与自己发送的不一致的时候,则节点会采取相应的措施,比如退出仲裁或者报错

        总结:在总线空闲时,最先开始发送消息的节点获得发送权。多个节点同时开始发送的时候,各发送节点从仲裁字段的第一位开始进行仲裁,连续输出显性电平(0)最多的节点可继续发送。说白了,仲裁其实就是通过比较数据标识符(Identifier)的大小来进行的,标识符越小优先级越高,也就会获得仲裁,标识符越大则退出仲裁
 

CAN总线的特点:

  1. 串行通信,半双工,但是有两根数据线(构成差分信号)
  2. 多主机的协议
  3. 异步的,没有时钟线
  4. 以消息内容中的 ID 进行仲裁,每个发送节点,也必须去采集总线上的数据,如果发现采集到的数据与自己发送的不同,你就是Loser,你就变为接收者
  5. 传输速率高达1M bps,同一个LAN,必须按照相同的速率传输,不同的LAN,也可以通过网关连接

由于每一个发送者发送一个 bit 又会从总线上采集一个 bit,为了确保数据的发送和采集没有误差,每个 bit 位的传输CAN总线都做了规定:

        每个 bit 位的传输,都需要经历如下4个阶段的时间:

                SYNC_SEG        PROP_SEG        PHASE_SEG1        PHASE_SEG2

                   同步段               传播段                 相位缓冲段1             相位缓冲段2

        把这四个阶段合称为NBT:Norminal Bit Time    一个 bit 位的时序 ---> 位时序

        位时序决定CAN总线的波特率的大小

为什么会有四个阶段呢?

        分阶段的原因有多个:

                (1) 网络(LAN)上有延时

                (2) 发送者发送需要时间,自己本身采样也需要时间

                (3) 同一个LAN上的所有节点都要求按照相同的速率进行收发

                        比如:总线上的设备都按500K Hz进行通信

                        那么T = (1/500K) s

                        CAN总线上的时钟周期为(1/500K)s,记为 tq

                (4) 因为每一个节点以各自独立的时钟工作,为了补偿节点间的晶振误差,所以会有 PHASE_SEG 相位缓冲段,又分为相位缓冲段1(PS1)和相位缓冲段2(PS2)

                      PHASE_SEG1 用来对每个节点进行同步,此段花费的时间为1~8tq,记为同步时间长度SJW

                      PHASE_SEG2用来对信号进行处理,此段花费的时间为1~8tq,记为信号处理时间 IPT (Information Proccessing Time)

四个阶段各自的作用为:

        (1) YNC_SEG同步段,占1个CAN总线时钟周期 tq

                主要用于电平从隐性--->显性或从显性--->隐性的变换所需要的时间

        (2) PROP_SEG传播段(网络延时段),占1~8个tq

                tPROP_SEG = 2(tTX + tBUS + tRX)

                tTX:发送延时,从发送方发出指令,到总线上电平变化所需要的时间

                        取总线上发送数据最慢的那个节点所需要的 tTX

                tBUS:总线传输延时

                        取总线上数据传输到最远节点所需要的时间

                tRX:接收延时,从总线上电平的变化到接收者保存数据所需要的时间

                        取总线上接收数据最慢的那个节点所需要的 tRX

        (3) PHASE_SEG(PHASE_SEG1 / PHASE_SEG2)相位缓冲段

                为了消除不同设备节点之间的晶振差异,以及同步时间和信息处理时间

                PHASE_SEG1:1~8 tq  主要负责同步时间长度(SJW)

                PHASE_SEG2:1~8 tq  主要负责信息处理时间(IPT)

                PHASE_SEG2 = MAX(PHASE_SEG1, tIPT)

        

        采样是发生在PHASE_SEG1的末尾,一般来说,采样点要处于整个NBT的75%-80%处

        同时 tNBT = tSYNC_SEG + tPROP_SEG + tPHASE_SEG1 + tPHASE_SEG2

3. STM32F4xx的CAN总线控制器

STM32自带了基本扩展的CAN外设,又称为bxCAN,其传输频率最高可达1M bps

CAN控制器的特性:

        1. 筛选器

                用来筛选特定的消息(比如:按消息的ID来筛选),其它的消息会被丢弃掉。共有28个筛选器(0,1,2,..,27),每个筛选器都有两个寄存器,用来配置筛选内容(比如:配置要筛选的ID)

        2. 三个发送邮箱(邮箱相当于缓冲区)

                可以同时缓存3个CAN消息等待发送

        3. 两个接收FIFO

                每个FIFO有3个接收邮箱,一共可以临时存储6个CAN消息

STM32F4xxCAN控制器模式:

        1)工作模式

                a. 初始化模式:主要初始化 CAN 寄存器 / 筛选器等等

                b. 正常模式:可以收发 CAN 总线消息

                c. 睡眠模式(低功耗模式):可以收 CAN 总线消息

        2)测试模式

                a. 静默模式 SILM

                b. 回环模式 LBKM:LoopBack Mode

                        自己的 Tx 线接自己的 Rx ----> 自己发的数据自己接收

                        回环模式主要是测试软件及上层业务逻辑是否正确

                c. 环回静默模式

        3)调试模式


bxCAN 功能说明:

        1)发送处理

                发送的数据必须要按帧格式处理

        2)接收处理

        3)时间触发通信模式

                用来为发送和接收消息生成时间戳(可以实现定时发送等功能)

        4)标识筛选器

4. STM32F4xx 固件库函数

(1) 配置CAN对应的GPIO(CANH和CANL是由GPIO复用而来)
        RCC_AHB1PeriphClockCmd
        GPIO_Init
        GPIO_PinAFConfig


(2) 初始化CAN控制器

        a. 使能时钟   RCC_APB1PeriphClockCmd // CAN1/CAN2都在APB1上

        b. CAN_Init

uint8_t CAN_Init(CAN_TypeDef *CANx, CAN_InitTypeDef *CAN_InitStruct);
    @CANx:指定CAN BUS编号

    @CAN_InitStruct:指向初始化信息结构体

typedef struct {

  	uint16_t CAN_Prescaler;
        指定CAN总线时钟频率的预分频值,范围为1~1024
        Fcan = FAPB1 / CAN_Prescaler(FAPB1 = 42M Hz)
  	    只需注意CAN总线波特率最高1Mbps即可
  	
    uint8_t CAN_Mode;
        指定CAN总线模式
            CAN_Mode_Normal			正常模式
            CAN_Mode_LoopBack		回环模式
            CAN_Mode_Silent			静默模式
            CAN_Mode_Silent_LoopBack	静默和回环模式
  	
    uint8_t CAN_SJW;
        指定SJW(同步时间长度)的值,1~4 tq
        CAN_SJW_1tq			       
        CAN_SJW_2tq								
        CAN_SJW_3tq											    
        CAN_SJW_4tq
  	
    uint8_t CAN_BS1;
        指定收到同步信号到采样点之间经历的时钟周期数
        BS1 = tPROP_SEG + tPHASE_SEG1
        CAN_BS1_1tq
        CAN_BS1_2tq
        ...
        CAN_BS1_16tq
  	
    uint8_t CAN_BS2;    
  		BS2 = tPHASE_SEG2
        指定采样点到这个bit处理结束经历的时钟周期数
        tPHASE_SEG2 = MAX(tPHASE_SEG1, tIPT)
  	
    FunctionalState CAN_TTCM;
   		使能或禁止收发消息的时间戳     
  			ENABLE
            DIASBLE
  	FunctionalState CAN_ABOM;

        ABOM:Automatic Bus-Off Manangement
        自动离线管理

  	FunctionalState CAN_AWUM;
        AWUM:Automatic Wake-Up Manangement
        自动唤醒管理

  	FunctionalState CAN_NART;
        Non-Automatic ReTransmittion  
        不自动重发,默认是重发

  	FunctionalState CAN_RFLM;
        RFLM:Receive FIFO Lock Manangement
        接收缓冲区锁

  	FunctionalState CAN_TXFP;
        Tx FIFO Prioty 发送缓存区优先级
} CAN_InitTypeDef;

(3) 筛选器配置(可以配置也可以不配置,需要筛选则配置不需要则不配置)

        可以选择性接收CAN消息

        两个CAN控制器(CAN1, CAN2)共有28个筛选器

                CAN1 0 ~13

                CAN2 14~27

        每个筛选器都有两个32bits的寄存器(Fx_R1, Fx_R2  x=0,,1,2...27),主要功能是用来筛选收到的消息的ID,RTR...

        筛选器可以配置为32bits筛选,也可以配置为 16bits 筛选

        为STDID[10:0]、EXTID[17:0]、IDE和RTR位提供一个 32 位筛选器(扩展ID时,32位筛选)

        为STDID[10:0]、RTR、IDE 和 EXTID[17:15] 位提供两个 16 位筛选器。

        筛选功能:

                a. 掩码模式

                        它只检查ID中MASK为1的那个bit位的值是否匹配

                        Fx_R1  ---> ID

                        Fx_R2  ---> MASK掩码

                        举例子:

b10

b9

b8

b7

b6

b5

b4

b3

b2

b1

b0

ID

0

1

0

1

x

x

x

x

x

x

x

MASK

1

1

1

1

0

0

0

0

0

0

0

                        如按上述设置筛选器的话,只要ID是以0101开头的消息都会接收,不以0101开头的则直接丢弃

                        所以(R_ID & MASK)  ==  (ID & MASK)//相等就接收

                b. ID列表模式

                        Fx_R1与Fx_R2都作为一个ID

                        只有Fx_R1、Fx_R2指定的段(ID,RTR,IDE)要完全与收到的消息的ID匹配,才会被接收

                        可以指定2个32bits的ID或4个16bits的ID

void CAN_FilterInit(CAN_TypeDef *CANx, 
                            CAN_FilterInitTypeDef *CAN_FilterInitStruct);

typedef struct {

	uint16_t CAN_FilterIdHigh;  		Fx_R1 高16位
	uint16_t CAN_FilterIdLow;   		Fx_R1 低16位
	uint16_t CAN_FilterMaskIdHigh; 		Fx_R2 高16位
	uint16_t CAN_FilterMaskIdLow; 		Fx_R2 低16位	
	uint16_t CAN_FilterFIFOAssignment; 	指定接收的消息分配到哪个FIFO
				CAN_Filter_FIFO0			接收消息存入FIFO0
				CAN_Filter_FIFO1			接收消息存入FIFO1
	uint8_t CAN_FilterNumber; 			筛选器编号0~13
	uint8_t CAN_FilterMode; 			筛选模式
				CAN_FilterMode_IdMask		掩码模式
				CAN_FilterMode_IdList		ID列表模式
	uint8_t CAN_FilterScale;				指定筛选器宽度
				CAN_FilterScale_16bit			16bits
				CAN_FilterScale_32bit			32bits
	FunctionalState CAN_FilterActivation; 使能或禁止筛选		

} CAN_FilterInitTypeDef;	

(4) 收发消息

        a. 发送消息

STM32F4xx有3个发送邮箱(至多同时发送3个消息)

uint8_t CAN_Transmit(CAN_TypeDef *CANx, CanTxMsg *TxMessage);

    @CANx:指定CAN编号

    @TxMessage:指向发送消息结构体,CAN中的数据是以消息帧进行发送
    
typedef struct {

    uint32_t StdId;	指定消息标准ID,自己赋值就可以了 11bits
	
    uint32_t ExtId;  	扩展ID,29bits
	
    uint8_t IDE;    	指定ID类型
					CAN_Id_Standard		使用StdId
					CAN_Id_Extended		使用ExtId
	
    uint8_t RTR;    指定是数据帧还是远程帧
				CAN_RTR_Data	数据帧,表示发送数据出去
				CAN_RTR_Remote远程帧,表示请求对方发数据给"我"
	
    uint8_t DLC;    指定发送数据正文长度  0~8

    uint8_t Data[8]; 消息正文,最多8个字节,具体长度与DLC要相同

} CanTxMsg;

    @返回值:
	    0		把消息发送到了 0号邮箱
	    1		把消息发送到了 1号邮箱
	    2		把消息发送到了 2号邮箱
	CAN_TxStatus_NoMailBox  表示所有邮箱都满了,不能发送,请稍后再尝试发送

CAN_Transmit这个只是把Message发送到控制器的发件箱,然后在控制器的作用下
会自动将消息发送出去,我们可以通过另一个函数可以得知有没有发送成功或者失败:

uint8_t CAN_TransmitStatus(CAN_TypeDef* CANx, uint8_t TransmitMailbox);

    @CANx:指定CAN编号
    
    @TransmitMailbox:指定要检测的邮箱号0,1,2

    @返回值:	
        CAN_TxStatus_Ok		    发送完成
        CAN_TxStatus_Failed		发送失败

        b. 接收消息(轮询方式/中断发送)

轮询的方式:
    
CAN_MessagePending用来检测指定接收FIFO中的邮箱状态

uint8_t CAN_MessagePending(CAN_TypeDef *CANx, uint8_t FIFONumber);

    @FIFONumber:用来指定要检测的是哪个FIFO
    @返回值:返回指定FIFO中有多少个message
        0    该FIFO没有消息
        1    该FIFO有1个消息
        2    该FIFO有2个消息
        3    该FIFO有3个消息
------------------------------------------------------------------------------- 
CAN_Receive用来从指定缓存区FIFO接收消息

void CAN_Receive(CAN_TypeDef *CANx, uint8_t FIFONumber, CanRxMsg *RxMessage);

    @FIFONumber:指定FIFO
    
    @RxMessage:指向一个接收消息结构体,用来存储接收到的消息
-------------------------------------------------------------------------------
中断的方式:

先在配置CAN的时候使能中断:
    CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE); // 当FIFO0有消息时,触发中断
    CAN_ITConfig(CAN1, CAN_IT_FMP1, ENABLE); // 当FIFO1有消息时,触发中断

    NVIC_Init();

编写中断处理函数:
void CAN1_RX0_IRQHandler(void)
{
    CanRxMsg msg; // 保存接收到的消息
    CAN_Receive(CAN1, 0, &msg);
}

void CAN1_RX1_IRQHandler(void)
{
	CanRxMsg msg; // 保存接收到的消息
	CAN_Receive(CAN1, 1, &msg);
}

5. 练习

1. 在回环模式下,测试能否收到自己发送的 CAN 消息

2. 两两对接,测试对方能否收到自己发送的 CAN 消息(TJA105CAN 收发器)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值