简述STM32 CAN总线的设置

简述CAN总线

最近公司开发CAN总线项目,以前也学习了,没有实际的用于项目制作,现在具体的总结一下,也是借鉴了很多大神的资料,站在巨人的肩膀之上写下来这篇文章

CAN 是 Controller Area Network 的缩写(以下称为 CAN),是 ISO 国际标准化的串行通信

协议。在当前的汽车产业中,出于对安全性、舒适性、方便性、低公害、低成本的要求,各种

各样的电子控制系统被开发了出来。由于这些系统之间通信所用的数据类型及对可靠性的要求

不尽相同,由多条总线构成的情况很多,线束的数量也随之增加。为适应“减少线束的数量”、

“通过多个 LAN,进行大量数据的高速通信”的需要,1986 年德国电气商博世公司开发出面

向汽车的 CAN 通信协议。此后,CAN 通过 ISO11898 ISO11519 进行了标准化,现在在欧

洲已是汽车网络的标准协议。

现在,CAN 的高性能和可靠性已被认同,并被广泛地应用于工业自动化、船舶、医疗设

备、工业设备等方面。现场总线是当今自动化领域技术发展的热点之一,被誉为自动化领域的

计算机局域网。它的出现为分布式控制系统实现各节点之间实时、可靠的数据通信提供了强有

力的技术支持。

CAN 控制器根据两根线上的电位差来判断总线电平。总线电平分为显性电平和隐性电平,

二者必居其一。发送方通过使总线电平发生变化,将消息发送给接收方。

CAN 协议具有一下特点:

1多主控制。在总线空闲时,所有单元都可以发送消息(多主控制),而两个以上的单元

同时开始发送消息时,根据标识符(Identifier 以下称为 ID)决定优先级。ID 并不是

表示发送的目的地址,而是表示访问总线的消息的优先级。两个以上的单元同时开始

发送消息时,对各消息 ID 的每个位进行逐个仲裁比较。仲裁获胜(被判定为优先级

最高)的单元可继续发送消息,仲裁失利的单元则立刻停止发送而进行接收工作。

2系统的柔软性。与总线相连的单元没有类似于“地址”的信息。因此在总线上增加单

元时,连接在总线上的其它单元的软硬件及应用层都不需要改变。

3通信速度较快,通信距离远。最高 1Mbps(距离小于 40M),最远可达 10KM(速率低

5Kbps)。

4具有错误检测、错误通知和错误恢复功能。所有单元都可以检测错误(错误检测功能),

检测出错误的单元会立即同时通知其他所有单元(错误通知功能),正在发送消息的单

元一旦检测出错误,会强制结束当前的发送。强制结束发送的单元会不断反复地重新

发送此消息直到成功发送为止(错误恢复功能)。

5故障封闭功能。CAN 可以判断出错误的类型是总线上暂时的数据错误(如外部噪声等)

还是持续的数据错误(如单元内部故障、驱动器故障、断线等)。由此功能,当总线上

发生持续数据错误时,可将引起此故障的单元从总线上隔离出去。

6连接节点多。CAN 总线是可同时连接多个单元的总线。可连接的单元总数理论上是没

有限制的。但实际上可连接的单元数受总线上的时间延迟及电气负载的限制。降低通

信速度,可连接的单元数增加;提高通信速度,则可连接的单元数减少。

正是因为 CAN 协议的这些特点,使得 CAN 特别适合工业过程监控设备的互连,因此,越

来越受到工业界的重视,并已公认为最有前途的现场总线之一。

CAN 协议经过 ISO 标准化后有两个标准:ISO11898标准和 ISO11519-2 标准。其中 ISO11898

是针对通信速率为 125Kbps~1Mbps 的高速通信标准,而 ISO11519-2 是针对通信速率为 125Kbps

以下的低速通信标准。

本章,我们使用的是 450Kbps 的通信速率,使用的是 ISO11898 标准,该标准的物理层特

征如图 30.1.1 所示:

们所要设置的寄存器CAN1->sFilterRegister[ x ].FR1便是如上图所说的标识符屏蔽模式中的 ID,

而CAN1->sFilterRegister[ x ].FR2, 就是屏蔽寄存器; 在标识符列表模式中,两者都是ID。

我们按上面的格式去设置寄存器:

标准ID / 拓展ID + IDE + RTR + x 。

已知 IDE = 0 代表标准ID,  IDE =1 代表拓展ID;  RTR = 0代表数据帧,RTR = 1代表远程帧; 

那么,我们给个例子:

  标识符列表 ID = 0x09  拓展帧 :

 CAN1->sFilterRegister[0].FR1=0x09<<3|0x04;     //id=0x01,拓展帧数据
 CAN1->sFilterRegister[0].FR2=0x09<<3|0x04;      //标识符列表,相同
过滤寄存器如此设置就能过滤拓展帧的id为0x09的数据了
         标识符屏蔽  ID = 0x11  标准帧 :

CAN1->sFilterRegister[1].FR1=0x11<<21|0x04;  //标准id, 0x04为屏蔽模式,
CAN1->sFilterRegister[1].FR2=0xffc00004;    //id全部屏蔽,IDE屏蔽,RTR屏蔽    
过滤器如此设置就能过滤标准帧id为0x11的数据。

 

为了使工程更加有条理,我们把CAN控制器相关的代码独立分开存储,方便以后移植。在"串口实验"之上新建"bsp_can.c"及"bsp_can.h"文件,这些文件也可根据您的喜好命名,它们不属于STM32标准库的内容,是由我们自己根据应用需要编写的。

1.    编程要点
(1)    初始化CAN通讯使用的目标引脚及端口时钟;

(2)    使能CAN外设的时钟;

(3)    配置CAN外设的工作模式、位时序以及波特率;

(4)    配置筛选器的工作方式;

(5)    编写测试程序,收发报文并校验。

2.    代码分析
CAN硬件相关宏定义
我们把CAN硬件相关的配置都以宏的形式定义到"bsp_can.h"文件中,见代码清单 242。

代码清单 404 CAN硬件配置相关的宏(bsp_can.h文件)

1

2 /*CAN硬件相关的定义*/

3 #define CANx CAN1

4 #define CAN_CLK RCC_APB1Periph_CAN1

5 /*接收中断号*/

6 #define CAN_RX_IRQ CAN1_RX0_IRQn

7 /*接收中断服务函数*/

8 #define CAN_RX_IRQHandler CAN1_RX0_IRQHandler

9

10 /*引脚*/

11 #define CAN_RX_PIN GPIO_Pin_8

12 #define CAN_TX_PIN GPIO_Pin_9

13 #define CAN_TX_GPIO_PORT GPIOB

14 #define CAN_RX_GPIO_PORT GPIOB

15 #define CAN_TX_GPIO_CLK RCC_AHB1Periph_GPIOB

16 #define CAN_RX_GPIO_CLK RCC_AHB1Periph_GPIOB

17 #define CAN_AF_PORT GPIO_AF_CAN1

18 #define CAN_RX_SOURCE GPIO_PinSource8

19 #define CAN_TX_SOURCE GPIO_PinSource9

以上代码根据硬件连接,把与CAN通讯使用的CAN号、引脚号、引脚源以及复用功能映射都以宏封装起来,并且定义了接收中断的中断向量和中断服务函数,我们通过中断来获知接收FIFO的信息。

初始化CAN的 GPIO
利用上面的宏,编写CAN的初始化函数,见代码清单 243。

代码清单 405 CAN的GPIO初始化函数(bsp_can.c文件)

  1 /*
				
2 * 函数名:CAN_GPIO_Config

3 * 描述:CAN的GPIO 配置

4 * 输入:无

5 * 输出 : 无

6 * 调用:内部调用

7 */

8 static void CAN_GPIO_Config(void)

9 {

10 GPIO_InitTypeDef GPIO_InitStructure;

11

12 /* 使能GPIO时钟*/

13 RCC_AHB1PeriphClockCmd(CAN_TX_GPIO_CLK|CAN_RX_GPIO_CLK, ENABLE);

14

15 /* 引脚源*/

16 GPIO_PinAFConfig(CAN_TX_GPIO_PORT, CAN_RX_SOURCE, CAN_AF_PORT);

17 GPIO_PinAFConfig(CAN_RX_GPIO_PORT, CAN_TX_SOURCE, CAN_AF_PORT);

18

19 /* 配置 CAN TX 引脚 */

20 GPIO_InitStructure.GPIO_Pin = CAN_TX_PIN;

21 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;

22 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

23 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;

24 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;

25 GPIO_Init(CAN_TX_GPIO_PORT, &GPIO_InitStructure);

26

27 /* 配置 CAN RX 引脚 */

28 GPIO_InitStructure.GPIO_Pin = CAN_RX_PIN ;

29 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;

30 GPIO_Init(CAN_RX_GPIO_PORT, &GPIO_InitStructure);

31 }

与所有使用到GPIO的外设一样,都要先把使用到的GPIO引脚模式初始化,配置好复用功能。CAN的两个引脚都配置成通用推挽输出模式即可。

配置CAN的工作模式
接下来我们配置CAN的工作模式,由于我们是自己用的两个板子之间进行通讯,波特率之类的配置只要两个板子一致即可。如果您要使实验板与某个CAN总线网络的通讯的节点通讯,那么实验板的CAN配置必须要与该总线一致。我们实验中使用的配置见代码清单 244。

代码清单 406 配置CAN的工作模式(bsp_can.c文件)

1 /*

2 * 函数名:CAN_Mode_Config

3 * 描述:CAN的模式配置

4 * 输入:无

5 * 输出 : 无

6 * 调用:内部调用

7 */

8 static void CAN_Mode_Config(void)

9 {

10 CAN_InitTypeDef CAN_InitStructure;

11 /************************CAN通信参数设置************************/

12 /* Enable CAN clock */

13 RCC_APB1PeriphClockCmd(CAN_CLK, ENABLE);

14

15 /*CAN寄存器初始化*/

16 CAN_DeInit(CAN1);

17 CAN_StructInit(&CAN_InitStructure);

18

19 /*CAN单元初始化*/

20 CAN_InitStructure.CAN_TTCM=DISABLE; //MCR-TTCM 关闭时间触发通信模式使能

21 CAN_InitStructure.CAN_ABOM=ENABLE; //MCR-ABOM 使能自动离线管理

22 CAN_InitStructure.CAN_AWUM=ENABLE; //MCR-AWUM 使用自动唤醒模式

23 CAN_InitStructure.CAN_NART=DISABLE; //MCR-NART 禁止报文自动重传

24 CAN_InitStructure.CAN_RFLM=DISABLE; //MCR-RFLM 接收FIFO 不锁定

25                         // 溢出时新报文会覆盖原有报文

26 CAN_InitStructure.CAN_TXFP=DISABLE; //MCR-TXFP 发送FIFO优先级取决于报文标示符

27 CAN_InitStructure.CAN_Mode = CAN_Mode_Normal; //正常工作模式

28 CAN_InitStructure.CAN_SJW=CAN_SJW_2tq; //BTR-SJW 重新同步跳跃宽度 2个时间单元

29

30 /* ss=1 bs1=5 bs2=3 位时间宽度为(1+5+3) 波特率即为时钟周期tq*(1+3+5) */

31 CAN_InitStructure.CAN_BS1=CAN_BS1_5tq; //BTR-TS1 时间段1 占用了5个时间单元

32 CAN_InitStructure.CAN_BS2=CAN_BS2_3tq; //BTR-TS1 时间段2 占用了3个时间单元

33

34 /* CAN Baudrate = 1 MBps (1MBps已为stm32的CAN最高速率) (CAN 时钟频率为 APB 1 = 45 MHz) */

35 ////BTR-BRP 波特率分频器定义了时间单元的时间长度 45/(1+5+3)/5=1 Mbps

36 CAN_InitStructure.CAN_Prescaler =5;

37 CAN_Init(CANx, &CAN_InitStructure);

38 }

这段代码主要是把CAN的模式设置成了正常工作模式,如果您阅读的是"CAN—回环测试"的工程,这里是被配置成回环模式的,除此之外,两个工程就没有其它差别了。

代码中还把位时序中的BS1和BS2段分别设置成了5Tq和3Tq,再加上SYNC_SEG段,一个CAN数据位就是9Tq了,加上CAN外设的分频配置为5分频,CAN所使用的总线时钟fAPB1 = 45MHz,于是我们可计算出它的波特率:

1Tq = 1/(45M/5)=1/9 us

T1bit=(5+3+1) x Tq =1us

波特率=1/T1bit =1Mbps

配置筛选器
以上是配置CAN的工作模式,为了方便管理接收报文,我们还要把筛选器用起来,见代码清单 245。

代码清单 407 配置CAN的筛选器(bsp_can.c文件)

1

2 /*IDE位的标志*/

3 #define CAN_ID_STD ((uint32_t)0x00000000) /*标准ID */

4 #define CAN_ID_EXT ((uint32_t)0x00000004) /*扩展ID */

5

6 /*RTR位的标志*/

7 #define CAN_RTR_Data ((uint32_t)0x00000000) /*数据帧 */

8 #define CAN_RTR_Remote ((uint32_t)0x00000002) /*远程帧*/

9

10 /************************************************************************/

11 /*

12 * 函数名:CAN_Filter_Config

13 * 描述:CAN的筛选器配置

14 * 输入:无

15 * 输出 : 无

16 * 调用:内部调用

17 */

18 static void CAN_Filter_Config(void)

19 {

20 CAN_FilterInitTypeDef CAN_FilterInitStructure;

21

22 /*CAN筛选器初始化*/

23 CAN_FilterInitStructure.CAN_FilterNumber=0; //筛选器组0

24 //工作在掩码模式

25 CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask;

26 //筛选器位宽为单个32位。

27 CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit;

28

29 /* 使能筛选器,按照标志符的内容进行比对筛选,

30 扩展ID不是如下的就抛弃掉,是的话,会存入FIFO0。 */

31//要筛选的ID高位,第0位保留,第1位为RTR标志,第2位为IDE标志,从第3位开始是EXID

32CAN_FilterInitStructure.CAN_FilterIdHigh= ((((u32)0x1314<<3)|CAN_ID_EXT|CAN_RTR_DATA)&0xFFFF0000)>>16;

33 //要筛选的ID低位

34 CAN_FilterInitStructure.CAN_FilterIdLow= (((u32)0x1314<<3)|CAN_ID_EXT|CAN_RTR_DATA)&0xFFFF;

35 //筛选器高16位每位必须匹配

36 CAN_FilterInitStructure.CAN_FilterMaskIdHigh= 0xFFFF;

37 //筛选器低16位每位必须匹配

38 CAN_FilterInitStructure.CAN_FilterMaskIdLow= 0xFFFF;

39 //筛选器被关联到FIFO0

40 CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0 ;

41 //使能筛选器

42 CAN_FilterInitStructure.CAN_FilterActivation=ENABLE;

43

44 CAN_FilterInit(&CAN_FilterInitStructure);

45 /*CAN通信中断使能*/

46 CAN_ITConfig(CANx, CAN_IT_FMP0, ENABLE);

47 }

这段代码把筛选器第0组配置成了32位的掩码模式,并且把它的输出连接到接收FIFO0,若通过了筛选器的匹配,报文会被存储到接收FIFO0。

筛选器配置的重点是配置ID和掩码,根据我们的配置,这个筛选器工作在图 4017中的模式。



图 4017 一个32位的掩码模式筛选器

在该配置中,结构体成员CAN_FilterIdHigh和CAN_FilterIdLow存储的是要筛选的ID,而CAN_FilterMaskIdHigh和CAN_FilterMaskIdLow存储的是相应的掩码。在赋值时,要注意寄存器位的映射,在32位的ID中,第0位是保留位,第1位是RTR标志,第2位是IDE标志,从第3位起才是报文的ID(扩展ID)。

因此在上述代码中我们先把扩展ID"0x1314"、IDE位标志"宏CAN_ID_EXT"以及RTR位标志"宏CAN_RTR_DATA"根据寄存器位映射组成一个32位的数据,然后再把它的高16位和低16位分别赋值给结构体成员CAN_FilterIdHigh和CAN_FilterIdLow。

而在掩码部分,为简单起见我们直接对所有位赋值为1,表示上述所有标志都完全一样的报文才能经过筛选,所以我们这个配置相当于单个ID列表的模式,只筛选了一个ID号,而不是筛选一组ID号。这里只是为了演示方便,实际使用中一般会对不要求相等的数据位赋值为0,从而过滤一组ID,如果有需要,还可以继续配置多个筛选器组,最多可以配置28个,代码中只是配置了筛选器组0。

对结构体赋值完毕后调用库函数CAN_FilterInit把个筛选器组的参数写入到寄存器中。

配置接收中断
在配置筛选器代码的最后部分我们还调用库函数CAN_ITConfig使能了CAN的中断,该函数使用的输入参数宏CAN_IT_FMP0表示当FIFO0接收到数据时会引起中断,该接收中断的优先级配置如下,见代码清单 246。

代码清单 408 配置CAN接收中断的优先级(bsp_can.c文件)

1 /*接收中断号*/

2 #define CAN_RX_IRQ CAN1_RX0_IRQn

3 /*

4 * 函数名:CAN_NVIC_Config

5 * 描述:CAN的NVIC 配置,第1优先级组,0,0优先级

6 * 输入:无

7 * 输出 : 无

8 * 调用:内部调用

9 */

10 static void CAN_NVIC_Config(void)

11 {

12 NVIC_InitTypeDef NVIC_InitStructure;

13 /* Configure one bit for preemption priority */

14 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);

15 /*中断设置*/

16 NVIC_InitStructure.NVIC_IRQChannel = CAN_RX_IRQ; //CAN RX中断

17 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;

18 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;

19 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

20 NVIC_Init(&NVIC_InitStructure);

21 }

这部分与我们配置其它中断的优先级无异,都是配置NVIC结构体,优先级可根据自己的需要配置,最主要的是中断向量,上述代码中把中断向量配置成了CAN的接收中断。

设置发送报文
要使用CAN发送报文时,我们需要先定义一个发送报文结构体并向它赋值,见代码清单 247。

代码清单 409 设置要发送的报文(bsp_can.c文件)

1 /*IDE位的标志*/

2 #define CAN_ID_STD ((uint32_t)0x00000000) /*标准ID */

3 #define CAN_ID_EXT ((uint32_t)0x00000004) /*扩展ID */

4

5 /*RTR位的标志*/

6 #define CAN_RTR_Data ((uint32_t)0x00000000) /*数据帧 */

7 #define CAN_RTR_Remote ((uint32_t)0x00000002) /*远程帧*/

8

9 /*

10 * 函数名:CAN_SetMsg

11 * 描述:CAN通信报文内容设置,设置一个数据内容为0-7的数据包

12 * 输入:无

13 * 输出 : 无

14 * 调用:外部调用

15 */

16 void CAN_SetMsg(CanTxMsg *TxMessage)

17 {

18 uint8_t ubCounter = 0;

19

20 //TxMessage.StdId=0x00;

21 TxMessage->ExtId=0x1314; //使用的扩展ID

22 TxMessage->IDE=CAN_ID_EXT; //扩展模式

23 TxMessage->RTR=CAN_RTR_DATA; //发送的是数据

24 TxMessage->DLC=8; //数据长度为8字节

25

26 /*设置要发送的数据0-7*/

27 for (ubCounter = 0; ubCounter < 8; ubCounter++)

28 {

29 TxMessage->Data[ubCounter] = ubCounter;

30 }

31 }

这段代码是我们为了方便演示而自己定义的设置报文内容的函数,它把报文设置成了扩展模式的数据帧,扩展ID为0x1314,数据段的长度为8,且数据内容分别为0-7,实际应用中您可根据自己的需求发设置报文内容。当我们设置好报文内容后,调用库函数CAN_Transmit即可把该报文存储到发送邮箱,然后CAN外设会把它发送出去:

CAN_Transmit(CANx, &TxMessage);

接收报文
由于我们设置了接收中断,所以接收报文的操作是在中断的服务函数中完成的,见代码清单 248。

代码清单 4010 接收报文(stm32f4xx_it.c)

1

2 /*接收中断服务函数*/

3 #define CAN_RX_IRQHandler CAN1_RX0_IRQHandler

4

5 extern __IO uint32_t flag ; //用于标志是否接收到数据,在中断函数中赋值

6 extern CanRxMsg RxMessage; //接收缓冲区

7 /********************************************************************/

8 void CAN_RX_IRQHandler(void)

9 {

10 /*从邮箱中读出报文*/

11 CAN_Receive(CANx, CAN_FIFO0, &RxMessage);

12

13 /* 比较ID是否为0x1314 */

14 if ((RxMessage.ExtId==0x1314) && (RxMessage.IDE==CAN_ID_EXT) && (RxMessage.DLC==8) )

15 {

16 flag = 1; //接收成功

17 }

18 else

19 {

20 flag = 0; //接收失败

21 }

22 }

根据我们前面的配置,若CAN接收的报文经过筛选器匹配后会被存储到FIFO0中,并引起中断进入到这个中断服务函数中,在这个函数里我们调用了库函数CAN_Receive把报文从FIFO复制到自定义的接收报文结构体RxMessage中,并且比较了接收到的报文ID是否与我们希望接收的一致,若一致就设置标志flag=1,否则为0,通过flag标志通知主程序流程获知是否接收到数据。

要注意如果设置了接收报文中断,必须要在中断内调用CAN_Receive函数读取接收FIFO的内容,因为只有这样才能清除该FIFO的接收中断标志,如果不在中断内调用它清除标志的话,一旦接收到报文,STM32会不断进入中断服务函数,导致程序卡死。

3.    main函数
最后我们来阅读main函数,了解整个通讯流程,见代码清单 2414。

代码清单 4011 main函数

1

2 _IO uint32_t flag = 0; //用于标志是否接收到数据,在中断函数中赋值

3 CanTxMsg TxMessage; //发送缓冲区

4 CanRxMsg RxMessage; //接收缓冲区

5

6 /**

7 * @brief 主函数

8 * @param 无

9 * @retval 无

10 */

11 int main(void)

12 {

13 LED_GPIO_Config();

14

15 /*初始化USART1*/

16 Debug_USART_Config();

17

18 /*初始化按键*/

19 Key_GPIO_Config();

20

21 /*初始化can,在中断接收CAN数据包*/

22 CAN_Config();

23

24 printf("\r\n欢迎使用秉火 STM32 F429 开发板。\r\n");

25 printf("\r\n秉火F429 CAN通讯实验例程\r\n");

26

27 printf("\r\n实验步骤:\r\n");

28

29 printf("\r\n 1.使用导线连接好两个CAN讯设备\r\n");

30 printf("\r\n 2.使用跳线帽连接好:5v --- C/4-5V \r\n");

31 printf("\r\n 3.按下开发板的KEY1键,会使用CAN向外发送0-7的数据包,包的扩展ID为0x1314 \r\n");

32 printf("\r\n 4.若开发板的CAN接收到扩展ID为0x1314的数据包,会把数据以打印到串口。 \r\n");

33 printf("\r\n 5.本例中的can波特率为1MBps,为stm32的can最高速率。 \r\n");

34

35 while (1)

36 {

37 /*按一次按键发送一次数据*/

38 if ( Key_Scan(KEY1_GPIO_PORT,KEY1_PIN) == KEY_ON)

39 {

40 LED_BLUE;

41 /*设置要发送的报文*/

42 CAN_SetMsg(&TxMessage);

43 /*把报文存储到发送邮箱,发送*/

44 CAN_Transmit(CANx, &TxMessage);

45

46 can_delay(10000);//等待发送完毕,可使用CAN_TransmitStatus查看状态

47

48 LED_GREEN;

49

50 printf("\r\n已使用CAN发送数据包!\r\n");

51 printf("\r\n发送的报文内容为:\r\n");

52 printf("\r\n扩展ID号ExtId:0x%x \r\n",TxMessage.ExtId);

53 CAN_DEBUG_ARRAY(TxMessage.Data,8);

54 }

55 if (flag==1)

56 {

57 LED_GREEN;

58 printf("\r\nCAN接收到数据:\r\n");

59

60 CAN_DEBUG_ARRAY(RxMessage.Data,8);

61

62 flag=0;

63 }

64 }

65 }

在main函数里,我们调用了CAN_Config函数初始化CAN外设,它包含我们前面解说的GPIO初始化函数CAN_GPIO_Config、中断优先级设置函数CAN_NVIC_Config、工作模式设置函数CAN_Mode_Config以及筛选器配置函数CAN_Filter_Config。

初始化完成后,我们在while循环里检测按键,当按下实验板的按键1时,它就调用CAN_SetMsg函数设置要发送的报文,然后调用CAN_Transmit函数把该报文存储到发送邮箱,等待CAN外设把它发送出去。代码中并没有检测发送状态,如果需要,您可以调用库函数CAN_TransmitStatus检查发送状态。

while循环中在其它时间一直检查flag标志,当接收到报文时,我们的中断服务函数会把它置1,所以我们可以通过它获知接收状态,当接收到报文时,我们把它使用宏CAN_DEBUG_ARRAY输出到串口。

看完程序简述一下发送和接收的流程

CAN 发送流程

CAN 发送流程为:程序选择 1 个空置的邮箱(TME=1设置标识符(ID),数据长度和

发送数据设置 CAN_TIxR TXRQ 位为 1,请求发送邮箱挂号(等待成为最高优先级)

预定发送(等待总线空闲)发送邮箱空置。整个流程如图 30.1.12 所示:

上图中,还包含了很多其他处理,不强制退出发送(ABRQ=1)和发送失败处理等。通过

这个流程图,我们大致了解了 CAN 的发送流程,后面的数据发送,我们基本就是按照此流程

来走。接下来再看看 CAN 的接收流程。

CAN 接收流程

CAN 接收到的有效报文,被存储在 3 级邮箱深度的 FIFO 中。FIFO 完全由硬件来管理,从

而节省了 CPU 的处理负荷,简化了软件并保证了数据的一致性。应用程序只能通过读取 FIFO

输出邮箱,来读取 FIFO 中最先收到的报文。这里的有效报文是指那些正确被接收的(直到 EOF

都没有错误)且通过了标识符过滤的报文。前面我们知道 CAN 的接收有 2 FIFO,我们每个

滤波器组都可以设置其关联的 FIFO,通过 CAN_FFA1R 的设置,可以将滤波器组关联到

FIFO0/FIFO1

CAN 接收流程为:FIFO 收到有效报文挂号_1(存入 FIFO 的一个邮箱,这个由硬件

控制,我们不需要理会)收到有效报文挂号_2收到有效报文挂号_3收到有效报文

溢出。

这个流程里面,我们没有考虑从 FIFO 读出报文的情况,实际情况是:我们必须在 FIFO 399

出之前,读出至少 1 个报文,否则下个报文到来,将导致 FIFO 溢出,从而出现报文丢失。每

读出 1 个报文,相应的挂号就减 1,直到 FIFO 空。CAN 接收流程如图 30.1.13 所示:

FIFO 接收到的报文数,我们可以通过查询 CAN_RFxR FMP 寄存器来得到,只要 FMP

不为 0,我们就可以从 FIFO 读出收到的报文。

接下来,我们简单看看 STM32 CAN 位时间特性,STM32 CAN 位时间特性和之前我

们介绍的,稍有点区别。STM32 把传播时间段和相位缓冲段 1STM32 称之为时间段 1)合并

了,所以 STM32 CAN 一个位只有 3 段:同步段(SYNC_SEG)、时间段 1BS1)和时间段

2BS2)。STM32 BS1 段可以设置为 1~16 个时间单元,刚好等于我们上面介绍的传播时间

段和相位缓冲段 1 之和。STM32 CAN 位时序如图 30.1.14 所示:

 STM32使用CAN进行主从通讯

2.0 代码部分

can.c (从机部分)

CAN模式

一.工作模式 通过CAN_MCR寄存器控制INRQ和SLEEP 
1.初始化INRQ=1 SLEEP=0 
软件初始化应该在硬件 
2.正常INRQ=0 SLEEP=0 
在初始化完成后,软件应该让硬件进入正常模式,以便正常接收和发送报文 
3.睡眠SLEEP=1 bxCAN可工作在低功耗的睡眠模式 
二.测试模式 通过CAN_BTR寄存器控制LBKM和SILM 
1. 静默 可以接受不能发送 
这里写图片描述 
2. 循回 可以发送不能接受 
3.环回静默 只能自发自收 
这里写图片描述 
三.调试模式

STM32标识符筛选器

在CAN协议里,报文的标识符不代表节点的地址,而是跟报文的优先级相关的。因此,节点在接收报文时-根据标识符的值-决定软件是否需要该报文;如果需要,就拷贝到SRAM里;如果不需要,报文就被丢弃且无需软件的干预。为满足这一需求,bxCAN为应用程序提供了14个位宽可变的、可配置的过滤器组(13~0),以便只接收那些软件需要的报文。硬件过滤的做法节省了CPU开销,否则就必须由软件过滤从而占用一定的CPU开销。 
STM32普通型芯片的 CAN 有14组过滤器组(互联型有28组过滤器组) ,用以对接收到的帧进行过滤。每组过滤器包括了2个可配置的32位寄存器:CAN_FxR1和 CAN_FxR2。对于过滤器组,通过设置CAN_FM0R的FBMx位, 
1.屏蔽位模式 
这样 CAN_FxR0中保存的就是标识符匹配值,CAN_FxR2中保存的是屏蔽码,即 CAN_FxR2中如果某一位为1,则 CAN_FxR1中相应的位必须与收到的帧的标志符中的相应位吻合才能通过过滤器。CAN_FxR2中为0的位表示 CAN_FxR1中的相应位可不必与收到的帧进行匹配。 
2.标识符列表模式 
此时 CAN_FxR1和CAN_FxR2中的都是要匹配的标识符,收到的帧的标识符必须与其中的一个吻合才能通过过滤。 
理解:标识符列表模式是为了过滤出一个标识符,而屏蔽位模式因为屏蔽了某些位所以可以过滤出一组标识符,对于不需要用筛选器组的应处以禁用状态 
一般我们用的都是普通型的,所以在本文中可以说 STM32有14组过滤器组。根据配置,每1组过滤器组可以有1个,2个或4个过滤器。这些过滤器相当于关卡,每当收到一条报文时,CAN 要先将收到的报文从这些过滤器上”过”一下,能通过的报文是有效报文,收进 FIFO,不能通过的是无效报文(不是发给”我”的报文),直接丢弃。通过对两个可配置寄存器值得改变可以选择过滤器的数量。在一组过滤器中,整组的过滤器都使用同一种工作模式。 
另外,每组过滤器中的过滤器宽度是可变的,可以是32位或16位。按工作模式和宽度,一个过滤器组可以变成以下几中形式之一: 
(1) 1个32位的屏蔽位模式的过滤器。 
(2) 2个32位的列表模式的过滤器。 
(3) 2个16位的屏蔽位模式的过滤器。 
(4) 4个16位的列表模式的过滤器。 
所有的过滤器是并联的,即一个报文只要通过了一个过滤器,就是算是有效的。每组过滤器组有两个32位的寄存器用于存储过滤用的”标准值”,分别是 FxR1,FxR2。 
这里写图片描述 
解读: 
1.在32位的屏蔽位模式下: 
有1个过滤器。 
FxR2用于指定需要关心哪些位,FxR1用于指定这些位的标准值。 
2.在32位的列表模式下: 
有两个过滤器。 
FxR1指定过滤器0的标准值,收到报文的标识符只有跟 FxR1完全相同时,才算通过。 
FxR2指定过滤器1的标准值。 
3.在16位的屏蔽位模式下: 
有2个过滤器。 
FxR1配置过滤器0,其中,[31-16]位指定要关心的位,[15-0]位指定这些位的标准值。 
FxR2配置过滤器1,其中,[31-16]位指定要关心的位,[15-0]位指定这些位的标准值。 
4.在16位的列表模式下: 
有4个过滤器。 
FxR1的[15-0]位配置过滤器0,FxR1的[31-16]位配置过滤器1。 
FxR2的[15-0]位配置过滤器2,FxR2的[31-16]位配置过滤器3。

FIFO

STM32的 CAN 有两个 FIFO,分别是 FIFO0和 FIFO1。为了便于区分,下面 FIFO0写作FIFO_0,FIFO1写作 FIFO_1。 
每组过滤器组必须关联且只能关联一个 FIFO。复位默认都关联到 FIFO_0。所谓“关联”是指假如收到的报文从某个过滤器通过了,那么该报文会被存到该过滤器相连的 FIFO。从另一方面来说,每个 FIFO 都关联了一串的过滤器组,两个 FIFO 刚好瓜分了所有的过滤器组。每当收到一个报文,CAN 就将这个报文先与 FIFO_0关联的过滤器比较,如果被匹配,就将此报文放入 FIFO_0中。如果不匹配, 再将报文与 FIFO_1关联的过滤器比较, 如果被匹配, 该报文就放入 FIFO_1中。如果还是不匹配,此报文就被丢弃。 
每个 FIFO 的所有过滤器都是并联的,只要通过了其中任何一个过滤器,该报文就有效。如果一个报文既符合 FIFO_0的规定,又符合 FIFO_1的规定,显然,根据操作顺序,它只会放到 FIFO_0中。 
每个 FIFO 中只有激活了的过滤器才起作用,换句话说,如果一个 FIFO 有20个过滤器,但是只激话了5个,那么比较报文时,只拿这5个过滤器作比较。一般要用到某个过滤器时,在初始化阶段就直接将它激活。需要注意的是,每个 FIFO 必须至少激活一个过滤器,它才有可能收到报文。如果一个过滤器都没有激活,那么是所有报文都报废的。一般的,如果不想用复杂的过滤功能, FIFO 可以只激活一组过滤器组,且将它设置成 32位的屏蔽位模式,两个标准值寄存器(FxR1,FxR2)都设置成0。这样所有报文均能通过。(STM32提供的例程里就是这么做的! )

过滤器匹配序号

过滤器编号用于加速 CPU 对收到报文的处理。收到一个有效报文时, CAN 会将收到的报文 以及它所通过的过滤器编号, 一起存入接 
收邮箱中。CPU 在处理时,可以根据过滤器编号,快速的知道该报文的用途,从而作出相应处理。 
不用过滤器编号其实也是可以的, 这时候 CPU 就要分析所收报文的标识符, 从而知道报文的用途。由于标识符所含的信息较多,处理起来就慢一点了。 
这里写图片描述 
STM32使用以下规则对过滤器编号: 
(1) FIFO_0和 FIFO_1的过滤器分别独立编号,均从0开始按顺序编号。 
(2) 所有关联同一个 FIFO 的过滤器,不管有没有被激活,均统一进行编号。 
(3) 编号从0开始,按过滤器组的编号从小到大,按顺序排列。 
(4) 在同一过滤器组内,按寄存器从小到大编号。FxR1配置的过滤器编号小,FxR2配置 
的过滤器编号大。 
(5) 同一个寄存器内,按位序从小到大编号。[15-0]位配置的过滤器编号小,[31-16]位 
配置的过滤器编号大。 
(6) 过滤器编号是弹性的。 当更改了设置时,每个过滤器的编号都会改变。 
但是在设置不变的情况下,各个过滤器的编号是相对稳定的。 
这样,每个过滤器在自己在 FIFO 中都有编号。 
在 FIFO_0中,编号从0 – (M-1), 其中 M 为它的过滤器总数。 
在 FIFO_1中,编号从0 – (N-1),,其中 N 为它的过滤器总数。 
这里写图片描述 
一个 FIFO 如果有很多的过滤器,,可能会有一条报文, 在几个过滤器上均能通过,这时 
候,,这条报文算是从哪儿过来的呢? 
STM32在使用过滤器时,按以下顺序进行过滤: 
(1) 位宽为32位的过滤器,优先级高于位宽为16位的过滤器。 
(2) 对于位宽相同的过滤器,标识符列表模式的优先级高于屏蔽位模式。 
(3) 位宽和模式都相同的过滤器,优先级由过滤器号决定,过滤器号小的优先级高。 
按这样的顺序,报文能通过的第一个过滤器,就是该报文的过滤器编号,被存入接收邮箱中。 
上面的例子说明了bxCAN的过滤器规则:在接收一个报文时,其标识符首先与配置在标识符列表模式下的过滤器相比较;如果匹配上,报文就被存放到相关联的FIFO中,并且所匹配的过滤器的序号被存入过滤器匹配序号中。如同例子中所显示,报文标识符跟#4标识符匹配,因此报文内容和FMI4被存入FIFO。如果没有匹配,报文标识符接着与配置在屏蔽位模式下的过滤器进行比较。如果报文标识符没有跟过滤器中的任何标识符相匹配,那么硬件就丢弃该报文,且不会对软件有任何打扰。


一.标识符决定 
当有超过1个发送邮箱在挂号时,发送顺序由邮箱中报文的标识符决定。根据CAN协议,标识符数值最低的报文具有最高的优先级。如果标识符的值相等,那么邮箱号小的报文先被发送。由发送请求次序决定。 
二.由发送请求次序决定 
通过对CAN_MCR寄存器的TXFP位置’1’,可以把发送邮箱配置为发送FIFO。在该模式下,发送的优先级由发送请求次序决定。该模式对分段发送很有用。 
中止 
通过对CAN_TSR寄存器的ABRQ位置’1’,可以中止发送请求。邮箱如果处于挂号或预定状态,发送请求马上就被中止了。如果邮箱处于发送状态,那么中止请求可能导致2种结果。如果邮箱中的报文被成功发送,那么邮箱变为空置邮箱,并且CAN_TSR寄存器的TXOK位被硬件置’1’。如果邮箱中的报文发送失败了,那么邮箱变为预定状态,然后发送请求被中止,邮箱变为空置邮箱且TXOK位被硬件清’0’。因此如果邮箱处于发送状态,那么在发送操作结束后,邮箱都会变为空置邮箱。

接受

 
接受流程 
FIFO从空状态开始,在接收到第一个有效的报文后, FIFO状态变为挂号_1(pending_1),硬件相应地把CAN_RFR寄存器的FMP[1:0]设置为’01’(二进制01b)。软件可以读取FIFO输出邮箱来读出邮箱中的报文,然后通过对CAN_RFR寄存器的RFOM位设置’1’来释放邮箱,这样FIFO又变为空状态了。如果在释放邮箱的同时,又收到了一个有效的报文,那么FIFO仍然保留在挂号_1状态,软件可以读取FIFO输出邮箱来读出新收到的报文。如果应用程序不释放邮箱,在接收到下一个有效的报文后, FIFO状态变为挂号_2(pending_2),硬件相应地把FMP[1:0]设置为’10’(二进制10b)。重复上面的过程,第三个有效的报文把FIFO变为挂号_3状态(FMP[1:0]=11b)。此时,软件必须对RFOM位设置1来释放邮箱,以便FIFO可以有空间来存放下一个有效的报文;否则,下一个有效的报文到来时就会导致一个报文的丢失。

溢出

当FIFO处于挂号_3状态(即FIFO的3个邮箱都是满的),下一个有效的报文就会导致溢出,并且一个报文会丢失。此时,硬件对CAN_RFR寄存器的FOVR位进行置’1’来表明溢出情况。至于哪个报文会被丢弃,取决于对FIFO的设置: 
● 如果禁用了FIFO锁定功能(CAN_MCR寄存器的RFLM位被清’0’),那么FIFO中最后收到的报文就被新报文所覆盖。这样,最新收到的报文不会被丢弃掉。 
● 如果启用了FIFO锁定功能(CAN_MCR寄存器的RFLM位被置’1’),那么新收到的报文就被丢弃,软件可以读到FIFO中最早收到的3个报文。

接收相关的中断

一旦往FIFO存入一个报文,硬件就会更新FMP[1:0]位,并且如果CAN_IER寄存器的FMPIE位为’1’,那么就会产生一个中断请求。 
当FIFO 变 满 时( 即 第3 个 报 文 被 存 入) , CAN_RFR 寄 存 器 的FULL 位 就 被 置’1’ , 并 且 如 果CAN_IER寄存器的FFIE位为’1’,那么就会产生一个满中断请求。 
在溢出的情况下, FOVR位被置’1’,并且如果CAN_IER寄存器的FOVIE位为’1’,那么就会产生一个溢出中断请求。

位时序

这里写图片描述 
位时间特性逻辑通过采样来监视串行的CAN总线,并且通过与帧起始位的边沿进行同步,及通过与后面的边沿进行重新同步,来调整其采样点。 
它的操作可以简单解释为,如下所述把名义上的每位时间分为3段: 
● 同步段(SYNC_SEG):通常期望位的变化发生在该时间段内。其值固定为1个时间单元(1 xtCAN)。 
● 时间段1(BS1):定义采样点的位置。它包含CAN标准里的PROP_SEG和PHASE_SEG1。 
其值可以编程为1到16个时间单元,但也可以被自动延长,以补偿因为网络中不同节点的频率差异所造成的相位的正向漂移。 
● 时间段2(BS2):定义发送点的位置。它代表CAN标准里的PHASE_SEG2。其值可以编程为1到8个时间单元,但也可以被自动缩短以补偿相位的负向漂移。 
重新同步跳跃宽度(SJW)定义了,在每位中可以延长或缩短多少个时间单元的上限。其值可以编程为1到4个时间单元。 
有效跳变被定义为,当bxCAN自己没有发送隐性位时,从显性位到隐性位的第1次转变。如果在时间段1(BS1)而不是在同步段(SYNC_SEG)检测到有效跳变,那么BS1的时间就被延长最多SJW那么长,从而采样点被延迟了。相反如果在时间段2(BS2)而不是在SYNC_SEG检测到有效跳变,那么BS2的时间就被缩短最多SJW那么长,从而采样点被提前了。为了避免软件的编程错误,对位时间特性寄存器(CAN_BTR)的设置,只能bxCAN处于初始化状态下进行。

中断

这里写图片描述 
● 发送中断可由下列事件产生: 
─ 发送邮箱0变为空, CAN_TSR寄存器的RQCP0位被置’1’。 
─ 发送邮箱1变为空, CAN_TSR寄存器的RQCP1位被置’1’。 
─ 发送邮箱2变为空, CAN_TSR寄存器的RQCP2位被置’1’。 
● FIFO0中断可由下列事件产生: 
─ FIFO0接收到一个新报文, CAN_RF0R寄存器的FMP0位不再是’00’。 
─ FIFO0变为满的情况, CAN_RF0R寄存器的FULL0位被置’1’。 
─ FIFO0发生溢出的情况, CAN_RF0R寄存器的FOVR0位被置’1’。 
● FIFO1中断可由下列事件产生: 
─ FIFO1接收到一个新报文, CAN_RF1R寄存器的FMP1位不再是’00’。 
─ FIFO1变为满的情况, CAN_RF1R寄存器的FULL1位被置’1’。 
─ FIFO1发生溢出的情况, CAN_RF1R寄存器的FOVR1位被置’1’。 
● 错误和状态变化中断可由下列事件产生: 
─ 出错情况,关于出错情况的详细信息请参考CAN错误状态寄存器(CAN_ESR)。 
─ 唤醒情况,在CAN接收引脚上监视到帧起始位(SOF)。 
─ CAN进入睡眠模式。

最后为普通模式进行调试

CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,CAN_Mode_Normal); 都设置普通模式

两个板子进行相互通信 并将接收到的数据打印出来

can.c (主机部分)

#include "can.h"

u8 can_buf[8] = {0};

/**
 *@function CAN初始化
 *@param	
 *				tsjw:	重新同步跳跃时间单元,范围:CAN_SJW_1tq~ CAN_SJW_4tq
 *				tbs2:	时间段2的时间单元,范围:CAN_BS2_1tq~CAN_BS2_8tq;
 *				tbs1:	时间段1的时间单元,范围:CAN_BS1_1tq ~CAN_BS1_16tq
 *				brp :	波特率分频器,范围:1~1024;  tq=(brp)*tpclk1
 *							波特率=Fpclk1/((tbs1+1+tbs2+1+1)*brp);
 *							mode:CAN_Mode_Normal,普通模式;CAN_Mode_LoopBack,回环模式;
 *							Fpclk1的时钟在初始化时设置为36M,如果设置CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,CAN_Mode_LoopBack);
 *							则波特率:36M/((8+9+1)*4)=500Kbps
 *@return	0,初始化成功;
 *   		 其他,初始化失败; 
 */
u8 CAN_Mode_Init(u8 tsjw,u8 tbs2,u8 tbs1,u16 brp,u8 mode)
{ 
	GPIO_InitTypeDef 		GPIO_InitStructure; 
	CAN_InitTypeDef        	CAN_InitStructure;
	CAN_FilterInitTypeDef  	CAN_FilterInitStructure;
#if CAN_RX0_INT_ENABLE 
	NVIC_InitTypeDef  		NVIC_InitStructure;
#endif

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);/* 使能PORTA时钟	*/                   											 

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);/* 使能CAN1时钟 */
	
	/* CAN IO口设置 */
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	/* 复用推挽 */
	GPIO_Init(GPIOA, &GPIO_InitStructure);	/* PA12----CAN_TX */

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;	/* 上拉输入 */
	GPIO_Init(GPIOA, &GPIO_InitStructure);			/* PA11----CAN_RX */

	/* CAN单元设置 */
	CAN_InitStructure.CAN_TTCM=DISABLE;
	CAN_InitStructure.CAN_ABOM=DISABLE;
	CAN_InitStructure.CAN_AWUM=DISABLE;
	CAN_InitStructure.CAN_NART=ENABLE;
	CAN_InitStructure.CAN_RFLM=DISABLE;
	CAN_InitStructure.CAN_TXFP=DISABLE;
	CAN_InitStructure.CAN_Mode= mode;
	
	/* 波特率设置 */
	CAN_InitStructure.CAN_SJW=tsjw;
	CAN_InitStructure.CAN_BS1=tbs1;
	CAN_InitStructure.CAN_BS2=tbs2;
	CAN_InitStructure.CAN_Prescaler=brp; 
	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=0x2000;	/* 主机的32位ID*/
	CAN_FilterInitStructure.CAN_FilterIdLow=0x0000;
	CAN_FilterInitStructure.CAN_FilterMaskIdHigh=0x2000;	/* 32位MASK */
	CAN_FilterInitStructure.CAN_FilterMaskIdLow=0x0000;
	CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0;	/* 过滤器关联到FIFO0 */
	CAN_FilterInitStructure.CAN_FilterActivation=ENABLE;	/* 激活过滤器 */

	CAN_FilterInit(&CAN_FilterInitStructure);			/* 过滤器初始化 */
	
#if CAN_RX0_INT_ENABLE 	/* 如果使能RX0中断 */
	CAN_ITConfig(CAN1,CAN_IT_FMP0,ENABLE);	/* FIFO消息挂号中断允许 */

	NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; 	/* 主优先级为1 */
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;   /* 次优先级为0 */        
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
#endif
	return 0;
}   
 
#if CAN_RX0_INT_ENABLE	/* 如果使能RX0中断 */		    
void USB_LP_CAN1_RX0_IRQHandler(void)
{
  	CanRxMsg RxMessage;
	int i=0;
    CAN_Receive(CAN1, 0, &RxMessage);
	for(i=0;i<8;i++)
	printf("rxbuf[%d]:%d\r\n",i,RxMessage.Data[i]);
}
#endif

/**
 *@function can发送一组数据
 *固定格式:ID,标准帧,数据帧
 *@param 
 *				len:数据长度(max 8)		     
 *				msg:数据指针,最大8个字节
 *@return 
 *				0----成功
 *				其他----失败
 */
u8 Can_Send_Msg(u8* msg,u8 len,u32 ID)//标识符也修改为入口参数
{	
	u8 mbox;
	u16 i=0;
	CanTxMsg TxMessage;
	TxMessage.StdId=(ID >> 21);			/* 标准标识符 */
	TxMessage.ExtId=((ID >> 3)&0x3ffff);			/* 设置扩展标识符 */
	TxMessage.IDE=CAN_Id_Standard; 	/* 标准帧 */
	TxMessage.RTR=CAN_RTR_Data;		/* 数据帧 */
	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;	 
}

/**
 *@function	CAN接收数据查询
 *@param
 *				buf:数据缓存区
 *@return
 *				0----无数据
 *				其他----接收到的数据长度
 */
u8 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<8;i++)
	{
    buf[i]=RxMessage.Data[i];
	}
	return RxMessage.DLC;	
}
can.h (主机部分)

#ifndef __CAN_H
#define __CAN_H	 
#include "sys.h"	   

#define CAN_RX0_INT_ENABLE	0					    
										 							 				    
u8 CAN_Mode_Init(u8 tsjw,u8 tbs2,u8 tbs1,u16 brp,u8 mode);
u8 Can_Send_Msg(u8* msg,u8 len,u32 ID);
u8 Can_Receive_Msg(u8 *buf);
#endif
main.c(主机部分)

#include "can.h"
#include "sys.h"

extern u8 can_buf[];

int main(void)
{
	delay_init();
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,CAN_Mode_Normal);
	while(1)
		{
			Can_Send_Msg(can_buf,8,0x00200000);
		}
}
can.c (从机部分)
#include "can.h"

/**
 *@function CAN初始化
 *@param	
 *				tsjw:	重新同步跳跃时间单元,范围:CAN_SJW_1tq~ CAN_SJW_4tq
 *				tbs2:	时间段2的时间单元,范围:CAN_BS2_1tq~CAN_BS2_8tq;
 *				tbs1:	时间段1的时间单元,范围:CAN_BS1_1tq ~CAN_BS1_16tq
 *				brp :	波特率分频器,范围:1~1024;  tq=(brp)*tpclk1
 *							波特率=Fpclk1/((tbs1+1+tbs2+1+1)*brp);
 *							mode:CAN_Mode_Normal,普通模式;CAN_Mode_LoopBack,回环模式;
 *							Fpclk1的时钟在初始化时设置为36M,如果设置CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,CAN_Mode_LoopBack);
 *							则波特率:36M/((8+9+1)*4)=500Kbps
 *@return	0,初始化成功;
 *   		 其他,初始化失败; 
 */
u8 CAN_Mode_Init(u8 tsjw,u8 tbs2,u8 tbs1,u16 brp,u8 mode)
{ 
	GPIO_InitTypeDef 		GPIO_InitStructure; 
	CAN_InitTypeDef        	CAN_InitStructure;
	CAN_FilterInitTypeDef  	CAN_FilterInitStructure;
#if CAN_RX0_INT_ENABLE 
	NVIC_InitTypeDef  		NVIC_InitStructure;
#endif

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);/* 使能PORTA时钟	*/                   											 

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);/* 使能CAN1时钟 */
	
	/* CAN IO口设置 */
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	/* 复用推挽 */
	GPIO_Init(GPIOA, &GPIO_InitStructure);	/* PA12----CAN_TX */

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;	/* 上拉输入 */
	GPIO_Init(GPIOA, &GPIO_InitStructure);			/* PA11----CAN_RX */

	/* CAN单元设置 */
	CAN_InitStructure.CAN_TTCM=DISABLE;
	CAN_InitStructure.CAN_ABOM=DISABLE;
	CAN_InitStructure.CAN_AWUM=DISABLE;
	CAN_InitStructure.CAN_NART=ENABLE;
	CAN_InitStructure.CAN_RFLM=DISABLE;
	CAN_InitStructure.CAN_TXFP=DISABLE;
	CAN_InitStructure.CAN_Mode= mode;
	
	/* 波特率设置 */
	CAN_InitStructure.CAN_SJW=tsjw;
	CAN_InitStructure.CAN_BS1=tbs1;
	CAN_InitStructure.CAN_BS2=tbs2;
	CAN_InitStructure.CAN_Prescaler=brp; 
	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=0x0020;	/* 从机的32位ID */
	CAN_FilterInitStructure.CAN_FilterIdLow=0x0000;
	CAN_FilterInitStructure.CAN_FilterMaskIdHigh=0x0020;	/* 32位MASK */
	CAN_FilterInitStructure.CAN_FilterMaskIdLow=0x0000;
	CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0;	/* 过滤器关联到FIFO0 */
	CAN_FilterInitStructure.CAN_FilterActivation=ENABLE;	/* 激活过滤器 */

	CAN_FilterInit(&CAN_FilterInitStructure);			/* 过滤器初始化 */
	
#if CAN_RX0_INT_ENABLE 	/* 如果使能RX0中断 */
	CAN_ITConfig(CAN1,CAN_IT_FMP0,ENABLE);	/* FIFO消息挂号中断允许 */

	NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; 	/* 主优先级为1 */
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;   /* 次优先级为0 */        
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
#endif
	return 0;
}   
 
#if CAN_RX0_INT_ENABLE	/* 如果使能RX0中断 */		    
void USB_LP_CAN1_RX0_IRQHandler(void)
{
  	CanRxMsg RxMessage;
	int i=0;
    CAN_Receive(CAN1, 0, &RxMessage);
	for(i=0;i<8;i++)
	printf("rxbuf[%d]:%d\r\n",i,RxMessage.Data[i]);
}
#endif

/**
 *@function can发送一组数据
 *固定格式:ID,标准帧,数据帧  直接将标识符作为入口参数
 *@param 
 *				len:数据长度(max 8)		     
 *				msg:数据指针,最大8个字节
 *@return 
 *				0----成功
 *				其他----失败
 */
u8 Can_Send_Msg(u8* msg,u8 len,u32 ID)
{	
	u8 mbox;
	u16 i=0;
	CanTxMsg TxMessage;
	TxMessage.StdId=(ID >> 21);			/* 标准标识符 */
	TxMessage.ExtId=((ID >> 3)&0x3ffff);			/* 设置扩展标识符 */
	TxMessage.IDE=CAN_Id_Standard; 	/* 标准帧 */
	TxMessage.RTR=CAN_RTR_Data;		/* 数据帧 */
	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;	 
}

/**
 *@function	CAN接收数据查询
 *@param
 *				buf:数据缓存区
 *@return
 *				0----无数据
 *				其他----接收到的数据长度
 */
u8 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<8;i++)
	{
    buf[i]=RxMessage.Data[i];
	}
	return RxMessage.DLC;	
}
can.h(从机部分)

#ifndef __CAN_H
#define __CAN_H	 
#include "sys.h"	 

#define CAN_RX0_INT_ENABLE	0					    
										 							 				    
u8 CAN_Mode_Init(u8 tsjw,u8 tbs2,u8 tbs1,u16 brp,u8 mode);
u8 Can_Send_Msg(u8* msg,u8 len,u32 ID);
u8 Can_Receive_Msg(u8 *buf);
#endif


丛机只接收数据,将接收到的数据打印出来

main.c(从机部分)

#include "can.h"
#include "sys.h"

u8 rec_buf[8] = {0};

int main(void)
{
	int key = 0;
	delay_init();
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,CAN_Mode_Normal);
	while(1)
		{
			key = Can_Receive_Msg(rec_buf);
			if(key!=0)
			{
				for(int i = 0;i<8;++i)
				{
					println(rec_buf[i]);
				}
			}
		}
}
发布了112 篇原创文章 · 获赞 120 · 访问量 62万+

猜你喜欢

转载自blog.csdn.net/qq_36958104/article/details/97391886