CAN---通讯实验

一、CAN协议简介

    CAN是控制器局域网络(Controller  Area  Network)的简称,由德国BOSCH公司开发的,并最终成为国际标准(ISO11519),是国际

上应用最广泛的现场总线之一。

    CAN总线协议已经成为汽车计算机控制系统和嵌入式工业控制局域网的标准总线,它具有的高可靠性和良好的错误检测能力受到重视,

被广泛应用于汽车计算机控制系统和环境温度恶劣、电磁辐射强及振动大的工业环境。

    (1)物理层

            是一种异步通讯,只具有CAN_High和CAN_Low两条信号线,共同构成一组差分信号线,以差分信号的形式进行通讯。

            CAN物理层的形式主要分为闭环总线网络和开环总线网络两种,一个适用于高速通讯,一个适用于远距离通讯。

            1》闭环总线网络

                CAN闭环总线网络是一种遵循ISIO11898标准的高速、短距离网络,它的总线最大长度为40m,通讯速度最高1Mbps,总线的

两端各要求有一个“120欧”的电阻。

CAN---通讯实验_第1张图片

            2》开环总线网络

                CAN开环总线网络是遵循ISO11519-2标准的低速、远距离网络,它的最大传输距离为1km,最高通讯速率为125kbps,两根

总线是独立的、不形成闭环,要求每根总线上各串联有一个“2.2千欧”的电阻。

CAN---通讯实验_第2张图片

            通讯节点:

                CAN总线上可以挂载多个通讯节点,节点之间的信号经过总线传输,实现节点间通讯。由于CAN通讯协议不对节点进行地址编

码,而是对数据内容进行编码,所以网络中的节点个数理论上不受限制,只要总线的负载足够即可,可以通过中继器增强负载。     

                CAN通讯节点由一个CAN控制器及CAN收发器组成,控制器与收发器之间通过CAN_Tx及CAN_Rx信号线相连,收发器与CAN

总线之间使用CAN_High及CAN_Low信号线相连。其中CAN_Tx及CAN_Rx使用普通的类似TTL逻辑信号,而CAN_High及CAN_Low是一

对差分信号线,使用比较特别的差分信号。   

                 当CAN节点需要发送数据时,控制器把要发送的二进制编码通过CAN_Tx线发送到收发器,然后由收发器把这个普通的逻辑电

平信号转化成差分信号,通过差分线CAN_High和CAN_Low线输出到CAN总线网络。而通过收发器接收总线上的数据到控制器时,则是

相反的过程,收发器把总线上收到的CAN_High及CAN_Low信号转化成普通的逻辑电平信号,通过CAN_Rx输出到控制器中。

            差分信号:

                差分信号又称差模信号,与传统使用单根信号线电压表示逻辑的方式有区别,使用差分信号传输时,需要两根信号线,这两个

信号线的振幅相等,相位相反,通过两根信号线的电压差值来表示逻辑0和逻辑1。          CAN---通讯实验_第3张图片

            CAN协议中的差分信号:

                CAN协议中对它使用的CAN_High及CAN_Low表示的差分信号做了规定。以高速CAN协议为例,当表示逻辑1时(隐性电平),

CAN_High和CAN_Low线上的电压均为2.5v,即它们的电压差VH-VL=0V;而表示逻辑0时(显性电平),CAN_High的电平为3.5V,

CAN_Low线的电平为1.5V,即它们的电压差为VH-VL=2V。

CAN---通讯实验_第4张图片

CAN---通讯实验_第5张图片

                在CAN总线中,必须使它处于隐性电平(逻辑1)或显性电平(逻辑0)中的其中一个状态。假如有两个CAN通讯节点,在同一时间

,一个输出隐性电平,另一个输出显性电平,类似I2C总线的“线与”特性将使它处于显性电平状态,显性电平的名字就是这样来的,即可

以认为显性具有优先的意味。

                由于CAN总线协议的物理层只有1对差分线,在一个时刻只能表示一个信号,所以对通讯节点来说,CAN通讯是半双工的,收

发数据需要分时进行。在CAN的通讯网络中,因为共用总线,在整个网络中同一时刻只能有一个通讯节点发送信号,其余的节点在该时刻

都只能接收。

    (2)协议层

            CAN的协议层规定了通讯逻辑。

            1》CAN的波特率及位同步

                由于CAN属于异步通讯,没有时钟信号线,连接在同一个总线网络中的各个节点会像串口异步通讯那样,节点间使用约定好的

波特率进行通讯,特别地,CAN还会使用“位同步”的方式来抗干扰、吸收误差,实现对总线电平信号进行正确的采样,确保通讯正常。

                为了实现位同步,CAN协议把每一个数据位的时序分解成SS段、PTS段、PBS1段和PBS2段,这四段的长度加起来即为一个

CAN数据位的长度。分解后的最小的时间单位是Tq,而一个完整的位由8~25个Tq组成。

CAN---通讯实验_第6张图片

                图中表示的CAN通讯信号每一个数据位的长度为19Tq,其中SS段占1Tq,PTS段占6Tq,PBS1段占5Tq,PBS2段占7Tq。信号

的采样点位于PBS1段与PBS2段之间,通过控制各段的长度,可以对采样点的位置进行偏移,以便准确地采样。

                SS段:SS译为同步段,若通讯节点检测到总线上信号的跳变沿被包含在SS段的范围之内,则表示节点与总线的时序是同步的,

当节点与总线同步时,采样点采集到的总线电平即可被确定为该位的电平。SS段的大小固定为1Tq。

                PTS段:PTS译为传播时间段,这个时间段是用于补偿网络的物理延时时间。是总线上输入比较器延时和输出驱动器延时总和的

两倍。PTS段的大小可以为1~8Tq。

                PBS1段:PBS1译为相位缓冲段,主要用来补偿边沿阶段的误差,它的时间长度在重新同步的时候可以加长。PBS1段的初始大

小可以为1~8Tq。

                PBS2段:PBS2这是另一个相位缓冲段,也是用来补偿边沿阶段误差的,它的时间长度在重新同步时可以缩短。PBS2段的初始

大小可以为2~8Tq。

                总线上的各个通讯节点只要约定好1个Tq的时间长度以及每一个数据位占据多少个Tq,就可以确定CAN通讯的波特率。   

                例如,假设上图中的1Tq=1us,而每个数据位由19个Tq组成,则传输一位数据需要时间T1bit =19us,从而每秒可以传输的数

据位个数为:1x10^6­/19 = 52631.6 (bps)  ,这个每秒可传输的数据位的个数即为通讯中的波特率。

            2》CAN的报文种类及结构

                当使用CAN协议进行通讯时,需要对数据、操作命令(如读/写)以及同步信号进行打包,打包后的这些内容成为报文。

                报文的种类:

                    在原始数据段的前面加上传输起始标签、片选(识别)标签和控制标签,在数据的尾段加上CRC校验标签、应答标签和传输结束

标签,把这些内容按特定的格式打包好,就可以用一个通道表达各种信号,各种各样的标签就如同SPI中各种通道上的信号,起到了协同传

输的作用。当整个数据包被传输到其它设备时,只要这些设备按格式去解读,就能还原出原始数据,这样的报文就被称为CAN的“数据

帧”。

CAN---通讯实验_第7张图片

                数据帧的结构:

                   数据帧以一个显性位(逻辑0)开始,以7个连续的隐性位(逻辑1)结束,在它们之间,分别有仲裁段、控制段、数据段、CRC段和ACK段。

CAN---通讯实验_第8张图片

                帧起始:

                    SOF段(Start Of Frame),译为帧起始,帧起始信号只有一个数据位,是一个显性电平,它用于通知各个节点将有数据

传输,其它节点通过帧起始信号的电平跳变沿来进行硬同步。

                仲裁段 :

                    当同时有两个报文被发送时,总线会根据仲裁段的内容决定哪个数据包能被传输,这也是它名称的由来。

                    仲裁段的内容主要为本数据帧的ID信息(标识符),数据帧具有标准格式和扩展格式两种,区别就在于ID信息的长度,

标准格式的ID为11位,扩展格式的ID为29位,它在标准ID的基础上多出18位。   

                     在CAN协议中,ID起着重要的作用,它决定着数据帧发送的优先级,也决定着其它节点是否会接收这个数据帧。

CAN协议不对挂载在它之上的节点分配优先级和地址,对总线的占有权是由信息的重要性决定的,即对于重要的信息,可给它打包上一个

优先级高的ID,使它能够及时地发送出去。     

                    也正因为它这样的优先级分配原则,使得CAN的扩展性大大加强,在总线上增加或减少节点并不影响其它设备。

CAN---通讯实验_第9张图片

                    报文的优先级,是通过对ID的仲裁来确定的。根据前面对物理层的分析我们知道如果总线上同时出现显性电平和隐性电平,

总线的状态会被置为显性电平,CAN正是利用这个特性进行仲裁。

                    若两个节点同时竞争CAN总线的占有权,当它们发送报文时,若首先出现隐性电平,则会失去对总线的占有权,进入接收状

态。在开始阶段,两个设备发送的电平一样,所以它们一直继续发送数据。到了图中箭头所指的时序处,节点单元1发送的为隐性电平,而

此时节点单元2发送的为显性电平,由于总线的“线与”特性使它表达出显示电平,因此单元2竞争总线成功,这个报文得以被继续发送出

去。

                    仲裁段ID的优先级也影响着接收设备对报文的反应。因为在CAN总线上数据是以广播的形式发送的,所有连接在CAN总线的

节点都会收到所有其它节点发出的有效数据,因而CAN控制器大多具有根据ID过滤报文的功能,它可以控制自己只接收某些ID的报文。

                RTR位:

                    RTR位(Remote Transmission Request Bit),译作远程传输请求位,它是用于区分数据帧和遥控帧的,当它为显性电平时表

示数据帧,隐性电平时表示遥控帧。

                IDE位:

                    IDE位(Identifier Extension Bit),译作标识符扩展位,它是用于区分标准格式与扩展格式,当它为显性电平时表示标准格

式,隐性电平时表示扩展格式。

                SRR位:

                    SRR位(Substitute Remote Request Bit),只存在于扩展格式,它用于替代标准格式中的RTR位。由于扩展帧中的SRR位为

隐性位,RTR在数据帧为显性位,所以在两个ID相同的标准格式报文与扩展格式报文中,标准格式的优先级较高。

                控制段:

                    在控制段中的r1和r0为保留位,默认设置为显性位。它最主要的是DLC段(Data Length Code),译为数据长度码,它由4个

数据位组成,用于表示本报文中的数据段含有多少个字节,DLC段表示的数字为0~8。

                数据段:

                    数据段为数据帧的核心内容,它是节点要发送的原始信息,由0~8个字节组成,MSB先行。

                CRC段:

                    为了保证报文的正确传输,CAN的报文包含了一段15位的CRC校验码,一旦接收节点算出的CRC码跟接收到的CRC码不同,

则它会向发送节点反馈出错信息,利用错误帧请求它重新发送。CRC部分的计算一般由CAN控制器硬件完成,出错时的处理则由软件控制

最大重发数。   

                     在CRC校验码之后,有一个CRC界定符,它为隐性位,主要作用是把CRC校验码与后面的ACK段间隔起来。

                ACK段:

                    ACK段包括一个ACK槽位,和ACK界定符位。类似I2C总线,在ACK槽位中,发送节点发送的是隐性位,而接收节点则在这一

位中发送显性位以示应答。在ACK槽和帧结束之间由ACK界定符间隔开。

                帧结束:

                    EOF段(End Of Frame),译为帧结束,帧结束段由发送节点发送的7个隐性位表示结束。

            3》其它报文

CAN---通讯实验_第10张图片

二、CAN外设

    STM32的芯片中具有bxCAN控制器,它支持CAN协议2.0A和2.0B标准。

    CAN控制器支持的最高的通讯速率为1Mbit/s;可以自动地接收和发送CAN报文,支持使用标准ID和扩展ID的报文;外设中具有3个发送邮箱,发送报文的优先级可以使用软件控制,还可以记录发送的时间;具有2个3级深度的接收FIFO,可使用过滤功能只接收或不接收某些ID号的报文;可配置成自动重发;不支持使用DMA进行数据收发。

    功能框图:

CAN---通讯实验_第11张图片

    (1)CAN控制内核

            自动离线管理:用于设置是否使用自动离线管理功能。当节点检测到它发送错误或接收错误超过一定值时,会自动进入离线状态,在离线状态中,CAN不能接收或发送报文。处于离线状态的时候,可以软件控制恢复或直接使用这个自动离线管理功能,它会在适当的时候自动恢复。

            自动唤醒:CAN外设可以使用软件进入低功耗的睡眠模式,如果使能了这个自动唤醒功能,当CAN检测到总线活动的时候,会自动唤醒。

            自动重传:报文自动重传功能,设置这个功能后,当报文发送失败时会自动重传至成功为止。若不使用这个功能,无论发送结果如何,消息只发送一次。

            锁定模式:FIFO锁定模式,该功能用于锁定接收FIFO。锁定后,当接收FIFO溢出时,会丢弃下一个接收的报文。若不锁定,则下一个接收到的报文会覆盖原报文。

            报文发送优先级的判定方法:当CAN外设的发送邮箱中有多个待发送报文时,本功能可以控制它是根据报文的ID优先级还是报文存进邮箱的顺序来发送。

            工作模式:

CAN---通讯实验_第12张图片

                  1》正常模式

                      正常模式下就是一个正常的CAN节点,可以向总线发送数据和接收数据。

                  2》静默模式

                      静默模式下,它自己的输出端的逻辑0数据会直接传输到它自己的输入端,逻辑1可以被发送到总线,所以它不能向总线发送显性位(逻辑0),只能发送隐性位(逻辑1)。输入端可以从总线接收内容。由于它只可发送的隐性位不会强制影响总线的状态,所以把它称为静默模式。这种模式一般用于检测,它可以用于分析总线上的流量,但又不会因为发送显性位而影响总线。

                  3》回环模式

                      回环模式下,它自己的输出端的所有内容都直接传输到自己的输入端,输出端的内容同时也会被传输到总线上,即也可使用总线检测它的发送内容。输入端只能接收自己发送端的内容,不接收来自总线上的内容。使用回环模式可以进行自检。

                  4》回环静默模式

                      回环静默模式是回环模式和静默模式的结合,自己的输出端的所有内容都直接传输到自己的输入端,并且不会向总线发送显性位影响总线,不能通过总线监测它的发送内容。输入端只接收自己发送端的内容,不接收来自总线上的内容。这种方式可以在“热自检”时使用,即自我检查的时候,不会干扰总线。

            位时序及波特率:

CAN---通讯实验_第13张图片

                STM32的CAN外设位时序中只包含3段,分别是SYNC_SEG、位段BS1及位段BS2,采样点位于BS1及BS2段的交界处。其中SYNC_SEG段固定长度为1Tq,而BS1及BS2段可以在位时序寄存器CNA_BTR设置它们的时间长度,它们可以在重新同步期间增长或减短,该长度SJW也可在位时序寄存器中配置。

                理解STM32的CAN外设的位时序时,可以把它的BS1段理解为是由CAN标准协议中PTS段与PBS1段合在一起的,而BS2段就相当于PBS2段。                CAN---通讯实验_第14张图片

   CAN---通讯实验_第15张图片

    (2)CAN发送邮箱

            CAN外设一共有3个发送邮箱,即最多可以缓存3个待发送的报文。

            每个发送邮箱中包含有标识符寄存器CAN_TIxR、数据长度控制寄存器CAN_TDTxR及2个数据寄存器CAN_TDLxR、CAN_TDHxR,它们的功能如下:

CAN---通讯实验_第16张图片

    (3)CAN接收FIFO

            CAN外设一共有2个接收FIFO,每个FIFO有3个邮箱,即最多可以缓存6个接收到的报文。当接收到报文时,FIFO的报文计数器会自增,而STM32内部读取FIFO数据之后,报文计数器会自减,通过状态寄存器可获知报文计数器的值,而通过前面主控制寄存器的RFLM位,可设置锁定模式,锁定模式下FIFO溢出时会丢弃新报文,非锁定模式下FIFO溢出时新报文会覆盖旧报文。

            跟发送邮箱类似,每个接收FIFO中包含有标识符寄存器CAN_RIxR、数据长度控制寄存器CAN_RDTxT及2个数据寄存器CAN_RDLxR、CAN_RCHxR,其功能如下:

CAN---通讯实验_第17张图片

    (4)验收筛选器

            

    (5)整体控制逻辑

            CAN2外设的结构与CAN1外设是一样的,它们共用筛选器,且由于存储访问控制器由CAN1控制,所以要使用CAN2的时候必须要使能CAN1的时钟。

三、初始化结构体

    (1)初始化结构体

         typedef struct

         {

             uint16_t   CAN_Prescaler;              //设置时钟分频,可设置为1~1024。它可控制时间片Tq的时间长度,这里设置的值                                                                             最终会减1再写入BRP寄存器位。Tq计算公式:Tq = (BRP[9:0] + 1) * Tpclk                                                                                     等效于:Tq = CAN_Prescaler * Tpclk。

             uint8_t   CAN_Mode;                      //设置工作模式。

                                                                      CAN_Mode_Normal (正常模式)           
                                                                      CAN_Mode_LoopBack (回环模式)         
                                                                      CAN_Mode_Silent  (静默模式)          
                                                                      CAN_Mode_Silent_LoopBack (回环静默模式) 

             uint8_t   CAN_SJW;                       //设置SJW的极限长度,即CAN重新同步时单次可增加或缩短的最大长度。

                                                                      CAN_SJW_1tq(1Tq)
                                                                      CAN_SJW_2tq(2Tq)
                                                                      CAN_SJW_3tq(3Tq)
                                                                      CAN_SJW_4tq(4Tq)

             uint8_t   CAN_BS1;                        //设置CAN位时序中的BS1段的长度,可被设置为1~16个Tq长度。

                                                                      #define CAN_BS1_1tq  
                                                                      #define CAN_BS1_2tq  
                                                                      #define CAN_BS1_3tq  
                                                                      #define CAN_BS1_4tq  
                                                                      #define CAN_BS1_5tq  
                                                                      #define CAN_BS1_6tq  
                                                                      #define CAN_BS1_7tq  
                                                                      #define CAN_BS1_8tq  
                                                                      #define CAN_BS1_9tq  
                                                                      #define CAN_BS1_10tq 
                                                                      #define CAN_BS1_11tq 
                                                                      #define CAN_BS1_12tq 
                                                                      #define CAN_BS1_13tq 
                                                                      #define CAN_BS1_14tq 
                                                                      #define CAN_BS1_15tq 
                                                                      #define CAN_BS1_16tq 

             uint8_t   CAN_BS2;                        //设置CAN位时序中的BS2段的长度,可被设置为1~8个Tq长度。

                                                                      #define CAN_BS2_1tq
                                                                      #define CAN_BS2_2tq
                                                                      #define CAN_BS2_3tq
                                                                      #define CAN_BS2_4tq
                                                                      #define CAN_BS2_5tq
                                                                      #define CAN_BS2_6tq
                                                                      #define CAN_BS2_7tq
                                                                      #define CAN_BS2_8tq

             FunctionalState   CAN_TTCM;       //设置是否使用时间触发功能,使用(ENABLE);不使用(DISABLE)。

             FunctionalState   CAN_ABOM;      //设置是否使用自动离线管理功能,使用(ENABLE);不使用(DISABLE)。

             FunctionalState   CAN_AWUM;     //设置是否使用自动唤醒功能,使用(ENABLE);不使用(DISABLE)。

             FunctionalState   CAN_NART;       //设置是否使用自动重传功能,使用(ENABLE);不使用(DISABLE)。

             FunctionalState   CAN_RFLM;      //设置是否使用锁定FIFO功能,使用(ENABLE);不使用(DISABLE)。

             FunctionalState   CAN_TXFP;       //设置报文优先级的判定方法,以报文存入发送邮箱的先后顺序来发送(ENABLE),按照报文ID的优先级来发送(DISABLE)。

         } CAN_InitTypeDef;

    (2)发送结构体

         typedef struct
         {
               uint32_t   StdId;             //存储报文的标准标识符11位,0~0x7FF。

               uint32_t   ExtId;             //存储报文的扩展标识符29位,0~0x1FFFFFFF。

               uint8_t   IDE;                 //存储IDE扩展标志。

                                                       CAN_ID_STD(表示本报文时标准帧,使用StdId成员存储报文ID)

                                                       CAN_ID_EXT(表示本报文时扩展帧,使用ExtId成员存储报文ID)

               uint8_t   RTR;                //存储RTR远程帧标志。由于遥控帧没有数据段,所以当报文是遥控帧时,Data[8]成员无效。

                                                       CAN_RTR_Data(表示本报文是数据帧)

                                                       CAN_RTR_Remote(表示本报文是遥控帧)

               uint8_t   DLC;                //存储报文数据段的长度,0~8。当报文是遥控帧时DLC的值为0。

               uint8_t   Data[8];            //存储报文数据段的内容。
                   
         } CanTxMsg;

    (3)接收结构体

         typedef struct

         {

             uint32_t   StdId;                    //存储报文的标准标识符11位,0~0x7FF。

             uint32_t   ExtId;                    //存储报文的扩展标识符29位,0~0x1FFFFFFF。

             uint8_t   IDE;                        //存储IDE扩展标志。

                                                            CAN_ID_STD(表示本报文时标准帧,使用StdId成员存储报文ID)

                                                            CAN_ID_EXT(表示本报文时扩展帧,使用ExtId成员存储报文ID)

             uint8_t   RTR;                     //存储RTR远程帧标志。由于遥控帧没有数据段,所以当报文是遥控帧时,Data[8]成员无效。

                                                           CAN_RTR_Data(表示本报文是数据帧)

                                                           CAN_RTR_Remote(表示本报文是遥控帧)

             uint8_t   DLC;                       //存储报文数据段的长度,0~8。当报文是遥控帧时DLC的值为0。

             uint8_t   Data[8];                  //存储报文数据段的内容。

             uint8_t   FMI;                        //存储了筛选器的标号,表示本报文是经过哪个筛选器存储进FIFO的,0~0xFF。

         } CanRxMsg;

    (4)筛选器结构体

         typedef struct

         {

             uint16_t   CAN_FilterIdHigh;                     //用于存储要筛选的ID,若筛选器工作在32位模式,它存储的是所筛选ID的高16位;若筛选器工作在16位模式,它存储的就是一个完整的要筛选的ID。

             uint16_t   CAN_FilterIdLow;                      //用于存储要筛选的ID,若筛选器工作在32位模式,它存储的是所筛选ID的低16位;若筛选器工作在16位模式,它存储的就是一个完整的要筛选的ID。

             uint16_t   CAN_FilterMaskIdHigh;             //存储的内容分两种情况,当筛选器工作在标识符列表模式时,它的功能与CAN_FilterIdHigh相同,都是存储要筛选的ID;而当筛选器工作在掩码模式时,它存储的是CAN_FilterIdHigh成员对应的掩码,与CAN_FilterIdLow组成一组筛选器。

             uint16_t   CAN_FilterMaskIdLow;              //存储的内容也分两种情况,当筛选器工作在标识符列表模式时,它的功能与CAN_FilterIdLow相同,都是存储要筛选的ID;而当筛选器工作在掩码模式时,它存储的是CAN_FilterIdLow成员对应的掩码,与CAN_FilterIdLow组成一组筛选器。

             uint16_t   CAN_FilterFIFOAssignment;     //设置当报文通过筛选器的匹配后,该报文会被存储到哪一个接收FIFO。

                                                                                 CAN_Filter_FIFO0(FIFO0)

                                                                                 CAN_Filter_FIFO1(FIFO1)

             uint8_t   CAN_FilterNumber;                     //筛选器编号,0~27

             uint8_t   CAN_FilterMode;                         //设置筛选器的工作模式。

                                                                                  CAN_FilterMode_IdList(列表模式)

                                                                                  CAN_FilterMode_IdMask(掩码模式)

             uint8_t   CAN_FilterScale;                         //设置筛选器的尺度 。

                                                                                 CAN_FilterScale_32bit(32位长)  

                                                                                 CAN_FilterScale_16bit(16位长)      

             FunctionalState CAN_FilterActivation;      //设置是否激活这个筛选器,ENABLE(激活);DISABLE(不激活)。

         } CAN_FilterInitTypeDef;

四、常用固件库函数

    (1)初始化函数

            uint8_t CAN_Init(CAN_TypeDef* CANx, CAN_InitTypeDef* CAN_InitStruct);

            CANx:CAN1或CAN2

    (2)发送数据(将数据发送到发送邮箱中)

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

            CANx:CAN1或CAN2

    (3)接收数据(从接收FIFO中接收数据)

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

            CANx:CAN1或CAN2

            FIFONumber:CAN_FIFO0或CAN_FIFO1

    (4)筛选器

            void CAN_FilterInit(CAN_FilterInitTypeDef* CAN_FilterInitStruct);

五、原理图

CAN---通讯实验_第18张图片

六、实验

bsp_can.h文件

#ifndef __BSP_CAN_H__
#define __BSP_CAN_H__

#include "stm32f4xx_conf.h"

extern void CAN_Config(void);
extern void CAN_Filter_Config(void);


#endif

bsp_can.c文件

#include "./can/bsp_can.h"


/*
    CAN_Tx---PB13 ; CAN_Rx---PB12
*/


/*******************
    功能:CAN初始化
    参数:无
    返回值:无
********************/
void CAN_Config(void)
{
    GPIO_InitTypeDef initValue;
    CAN_InitTypeDef  canInitValue;
    NVIC_InitTypeDef nvicInitValue;
    
    /*1、打开时钟*/
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1,ENABLE);//使用CAN2时必须同时使能CAN1时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN2,ENABLE);
    
    /*2、配置GPIO的复用功能*/
    initValue.GPIO_Mode = GPIO_Mode_AF;
    initValue.GPIO_OType = GPIO_OType_PP;
    initValue.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13;
    initValue.GPIO_PuPd = GPIO_PuPd_UP;
    initValue.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB,&initValue);
    
    /*3、选择复用引脚*/
    GPIO_PinAFConfig(GPIOB,GPIO_PinSource12,GPIO_AF_CAN2);
    GPIO_PinAFConfig(GPIOB,GPIO_PinSource13,GPIO_AF_CAN2);
    
    /*4、初始化CAN结构体*/
    canInitValue.CAN_ABOM = ENABLE;
    canInitValue.CAN_AWUM = ENABLE;
    canInitValue.CAN_BS1 = CAN_BS1_4tq;
    canInitValue.CAN_BS2 = CAN_BS2_2tq;
    canInitValue.CAN_Mode = CAN_Mode_LoopBack;
    canInitValue.CAN_NART = ENABLE;
    canInitValue.CAN_Prescaler = 6;
    canInitValue.CAN_RFLM = DISABLE;
    canInitValue.CAN_SJW = CAN_SJW_2tq;
    canInitValue.CAN_TTCM = DISABLE;
    canInitValue.CAN_TXFP = DISABLE;
    CAN_Init(CAN2,&canInitValue);
    
    /*5、配置NVIC*/
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    nvicInitValue.NVIC_IRQChannel = CAN2_RX0_IRQn;
    nvicInitValue.NVIC_IRQChannelCmd = ENABLE;
    nvicInitValue.NVIC_IRQChannelPreemptionPriority = 1;
    nvicInitValue.NVIC_IRQChannelSubPriority = 1;
    NVIC_Init(&nvicInitValue);
    
    /*6、打开FIFO 0接收中断*/
    CAN_ITConfig(CAN2,CAN_IT_FMP0,ENABLE);
    
}

/*********************
    功能:初始化筛选器
  参数:无
    返回值:无
**********************/
void CAN_Filter_Config(void)
{
    CAN_FilterInitTypeDef filterInitValue;
    
    filterInitValue.CAN_FilterActivation = ENABLE;
    filterInitValue.CAN_FilterFIFOAssignment = CAN_FilterFIFO0;
    filterInitValue.CAN_FilterIdHigh = (((uint32_t)0x1314<<3|CAN_ID_EXT|CAN_RTR_DATA)&0xFFFF0000) >>16;
    filterInitValue.CAN_FilterIdLow = ((uint32_t)0x1314<<3|CAN_ID_EXT|CAN_RTR_DATA)&0x0000FFFF;
    filterInitValue.CAN_FilterMaskIdHigh = 0xFFFF;
    filterInitValue.CAN_FilterMaskIdLow = 0xFFFF;
    filterInitValue.CAN_FilterMode = CAN_FilterMode_IdMask;
    filterInitValue.CAN_FilterNumber = 14;  //筛选器编号,CAN1:0~13 ; CAN2:14~28
    filterInitValue.CAN_FilterScale = CAN_FilterScale_32bit;
    
    CAN_FilterInit(&filterInitValue);
}

stm32f4xx_it.c文件

/*********************************
    功能:CAN接收到数据的中断处理函数
    参数:无
    返回值:无
**********************************/
extern uint8_t rec_flag;
extern CanRxMsg test_RxMsg;
void CAN2_RX0_IRQHandler(void)
{
    //判断是否是由于FIFO 0接收到数据而产生的中断
    if(CAN_GetITStatus(CAN2,CAN_IT_FMP0) == SET)
    {
        //处理接收数据,读取数据后,系统会自动清除中断标志
        CAN_Receive(CAN2,CAN_FIFO0,&test_RxMsg);
        rec_flag = 1;
    }
}

main.c文件

#include "./usart/bsp_usart.h"
#include "./led/bsp_led.h"
#include "./can/bsp_can.h"
#include "./key/button.h"
#include "stdio.h"

CanTxMsg test_TxMsg;
CanRxMsg test_RxMsg;

uint8_t rec_flag = 0;

/// 不精确的延时
static void can_delay(__IO u32 nCount)
{
    for(; nCount != 0; nCount--);

int main(void)
{
    uint8_t i = 0;
    uint8_t mail_box;
    
    LED_Config();
    USART_Config();
    CAN_Config();
    CAN_Filter_Config();
    button_init();
    
    /* 发送一个字符串 */
    printf("这是一个CAN实验\n");
    
    printf("\r\n 实验步骤:\r\n");

    printf("\r\n 1.使用回环模式,不需要连接外部芯片\r\n");
    printf("\r\n 2.按下开发板的KEY1键,会使用CAN向外发送0-7的数据包,包的扩展ID为0x1314 (由于回环模式,是自己发送给自己)\r\n");
    printf("\r\n 3.若开发板的CAN接收到扩展ID为0x1314的数据包,会把数据以打印到串口。 \r\n");
    printf("\r\n 4.本例中的can波特率为1MBps,为stm32的can最高速率。 \r\n");
    
    while(1)
    {
        //按下按键,发送数据
        if(button_state(1) == 0)//key按下
        {        
            //设置要发送的数据
            test_TxMsg.DLC = 8;
            test_TxMsg.ExtId = 0x1314;
            test_TxMsg.IDE = CAN_ID_EXT;
            test_TxMsg.RTR = CAN_RTR_DATA;
            test_TxMsg.StdId = 0;
            for(i = 0;i<8;i++)
            {
                test_TxMsg.Data[i] = i;
            }
            
            printf("\r\n 准备发送数据");
            //发送数据
            mail_box = CAN_Transmit(CAN2,&test_TxMsg);
            if(mail_box != CAN_TxStatus_NoMailBox)
            {
                //等待直至数据发送到CAN收发器完成(监测是否把数据发送给CAN收发器)
                while(CAN_TransmitStatus(CAN2,mail_box) != CAN_TxStatus_Ok);
                
                //给一定的时间等待CAN收发器把数据发送到总线
                can_delay(0x1000);
                printf("\r\n 发送数据完成");
            }
            else
            {
                printf("\r\n no mail box");
            }
        }
        
        //接收数据
        if(rec_flag == 1)//表示接收到数据
        {            
            printf("\r\n CAN Rx.DLC = %x",test_RxMsg.DLC);
            printf("\r\n CAN Rx.ExtId = %x",test_RxMsg.ExtId);
            printf("\r\n CAN Rx.IDE = %x",test_RxMsg.IDE);
            printf("\r\n CAN Rx.RTR = %x",test_RxMsg.RTR);
            printf("\r\n CAN Rx.StdId = %x",test_RxMsg.StdId);
            printf("\r\n CAN Rx.FMI = %x",test_RxMsg.FMI);
            for(i = 0;i             {
                printf("\r\n CAN Rx.Data[%d] = %x",i,test_RxMsg.Data[i]);
            }
            
            rec_flag = 0;
        }
    }
}

            

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(STM32)