前面我们用了很大篇幅介绍 CAN 总线和 STM32F1 的 CAN 控制器, 其实还是比较复杂的, 如果不明白没有关系, 我们可以先从软件层面上来学习怎么使用CAN,然后再回过头看这些理论知识,这样学习 CAN 应该会比较轻松。接下来我们就介绍下如何使用库函数对 CAN进行配置。这个也是在编写程序中必须要了解的。具体步骤如下:(CAN 相关库函数在 stm32f10x_can.c 和 stm32f10x_can.h文件中) (1)使能 CAN 时钟,将对应引脚复用映射为 CAN 功能 要使用 CAN,首先就是使能它的时钟,我们知道 CAN1 和 CAN2 是挂接在APB1总线上的,其发送和接收引脚对应不同的 STM32F1 IO(具体 IO 可以通过数据手册查找,也可以在我们原理图上查找),因此使能 CAN 时钟后,还需要使能对应端口的时钟,并且将其引脚配置为复用功能。因为我们使用的 STM32F103ZET6芯片只有一个 CAN, 即 CAN1, 其对应的 IO 是 PA11 (CAN1_RX) 和 PA12 (CAN1_TX) 。 所以配置代码如下: - //使能相关时钟
) t! S* _" X, U! E# n - RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//使能 PORTA时钟$ M/ a9 |) l" m% S7 N
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);//使能 CAN1 时钟4 I. `. l [9 [3 ? b% p
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; //PA11$ n; T* v; j) m1 t
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入模式
/ {) g' M' q( B/ p& K( K, k: N# m - GPIO_Init(GPIOA, &GPIO_InitStructure);
2 U. S/ z; U' S( ] - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; //PA12
3 Z0 P1 F0 `# j7 }: Q9 l" a8 J - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
' v) Q- b3 e5 z. e - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO 口速度为 50MHz
- u- j+ N9 T. [% N - GPIO_Init(GPIOA, &GPIO_InitStructure);
复制代码
: ?# F- Z1 |5 h5 \8 I
(2)设置 CAN 工作模式、波特率等 使能了 CAN 时钟后,接下来就可以通过 CAN_MCR 寄存器配置其工作模式、波特率大小等参数。库函数中提供了 CAN_Init()函数用来完成这一步骤,函数原型是: - uint8_t CAN_Init(CAN_TypeDef* CANx, CAN_InitTypeDef*CAN_InitStruct);
复制代码
0 M1 v9 ~$ F a. J' d 函数中第一个参数是用来设置哪个 CAN,例如 CAN1;第二个参数是一个结构体指针变量,结构体类型是 CAN_InitTypeDef,其内包含了 CAN 工作模式及波特率初始化的成员变量。下面我们简单介绍下它的成员: - typedef struct
% P+ @# \0 R3 B) k& b - {
7 K. n, g7 J) }- C - uint16_t CAN_Prescaler; //
. t' O f5 o, p - uint8_t CAN_Mode; //
, p# f5 a. r- U - uint8_t CAN_SJW; //
, w" a2 U5 w# V - uint8_t CAN_BS1; //& y% u u3 ^# q: d$ A
- uint8_t CAN_BS2; // i) P4 n8 B, t. N& V# c
- FunctionalState CAN_TTCM; //
9 a! p: x% x- x - FunctionalState CAN_ABOM; //" P* k7 W+ t1 L0 e; e! u
- FunctionalState CAN_AWUM; //
6 M; L- r1 V; E, I9 p, H, U& J - FunctionalState CAN_NART; //
& J5 |; B6 G3 I. h1 b* l- V4 H - FunctionalState CAN_RFLM; //5 n& r$ d* a7 q4 G0 [/ S
- FunctionalState CAN_TXFP; //
* k9 ]4 T+ o' c, g. L: i - } CAN_InitTypeDef;
- \% H: y, U! F, @( j* i* Y( h6 N
复制代码
3 k. T1 p( E- p" `& X0 L; @( |
CAN_Prescaler:用于设置 CAN 外设的时钟分频,它可控制时间片 tq 的时间长度,这里设置的值最终会加 1 后再写入 BRP 寄存器位。 CAN_Mode :用 于 设 置 CAN 的 工 作 模 式 , 可 设 置 为 正 常 模 式 (CAN_Mode_Normal) 、 回 环 模 式 (CAN_Mode_LoopBack) 、 静 默 模 式(CAN_Mode_Silent)以及回环静默模式(CAN_Mode_Silent_LoopBack)。 本实验使用到的只有正常模式和回环模式。 CAN_SJW:用于置 SJW 的极限长度,即 CAN 重新同步时单次可增加或缩短的最大长度,它可以被配置为 1-4tq(CAN_SJW_1/2/3/4tq)。 CAN_BS1:用于设置 CAN 位时序中的 BS1 段的长度,它可以被配置为 1-16个 tq 长度(CAN_BS1_1/2/3…16tq)。 CAN_BS2:用于设置 CAN 位时序中的 BS2 段的长度,它可以被配置为 1-8个 tq 长度(CAN_BS2_1/2/3…8tq)。 CAN_TTCM:用于设置是否使用时间触发功能,ENABLE 为使能,DISABLE为失能。时间触发功能在某些CAN 标准中会使用到。 CAN_ABOM:用于设置是否使用自动离线管理(ENABLE/DISABLE),使用自动离线管理可以在节点出错离线后适时自动恢复,不需要软件干预。 CAN_AWUM:用于设置是否使用自动唤醒功能(ENABLE/DISABLE),使能自动唤醒功能后它会在监测到总线活动后自动唤醒。 CAN_NART:用于设置是否使用自动重传功能(ENABLE/DISABLE),使用自动重传功能时,会一直发送报文直到成功为止。 CAN_RFLM:用于设置是否使用锁定接收 FIFO(ENABLE/DISABLE),锁定接收FIFO 后,若 FIFO 溢出时会丢弃新数据,否则在 FIFO 溢出时以新数据覆盖旧数据。 CAN_TXFP:用于设置发送报文的优先级判定方法(ENABLE/DISABLE), 使能时,以报文存入发送邮箱的先后顺序来发送,否则按照报文 ID 的优先级来发送。 了解结构体成员功能后,就可以进行配置,设置好 CAN_Prescaler、CAN_BS1和 CAN_BS2 的值,带入到CAN波特率计算公式: CAN 波特率=Fpclk1/((CAN_BS1+CAN_BS2+1)*CAN_Prescaler)就可以算出波特率。本章实验我们初始化配置 CAN 为正常工作模式,波特率为 500Kbps,配置代码如下: - CAN_InitTypeDef CAN_InitStructure;
: Q* i" m8 H- [ - //CAN 单元设置
5 J; J5 Y% V) b) [: }/ ?) x1 _ - CAN_InitStructure.CAN_TTCM=DISABLE; //非时间触发通信模式
6 f/ A& j3 x% n; g8 w' U - CAN_InitStructure.CAN_ABOM=DISABLE; //软件自动离线管理. u% d2 R) c* p( b, v2 m7 l& }5 x
- CAN_InitStructure.CAN_AWUM=DISABLE;//睡眠模式通过软件唤醒(清除CAN->MCR 的 SLEEP 位)
" C) j: [( V/ T" L+ s7 M - CAN_InitStructure.CAN_NART=ENABLE; //使用报文自动传送
; w- w( R* E2 N" n$ x1 M - CAN_InitStructure.CAN_RFLM=DISABLE; //报文不锁定,新的覆盖旧的
. r" Y, J( l3 W% f' Z/ \& g, q$ \ - CAN_InitStructure.CAN_TXFP=DISABLE; //优先级由报文标识符决定
* i5 t$ c! {2 K - CAN_InitStructure.CAN_Mode= CAN_Mode_Normal; //模式设置1 \* z6 q! v$ v2 n
- CAN_InitStructure.CAN_SJW=CAN_SJW_1tq; //重新同步跳跃宽度(Tsjw)为 tsjw+1 个时间单位 CAN_SJW_1tq~CAN_SJW_4tq
3 J) D# E- R8 ^+ n; f' d& H- Q+ x - CAN_InitStructure.CAN_BS1=CAN_BS1_7tq; //Tbs1 范 围 CAN_BS1_1tq~CAN_BS1_16tq2 ]& e+ @9 _# v; D0 H# @8 _
- CAN_InitStructure.CAN_BS2=CAN_BS2_6tq;//Tbs2 范 围 CAN_BS2_1tq ~CAN_BS2_8tq
3 C, b$ P3 ]: ^) C/ L - CAN_InitStructure.CAN_Prescaler=6; //分频系数(Fdiv)为brp+1
3 a4 o$ I, W* }- B# A - CAN_Init(CAN1, &CAN_InitStructure); // 初始化 CAN1
复制代码$ H6 u1 e9 H; l. K+ t- ]! E
(3)设置 CAN 筛选器
- u4 C& m) c ?+ R$ `5 `5 [ 设置好 CAN 的工作模式及波特率后,我们还需要通过 CAN_FMR 寄存器设置CAN 筛选器,库函数中提供了 CAN_FilterInit()函数来完成这一步骤,函数原型是: - void CAN_FilterInit(CAN_FilterInitTypeDef* CAN_FilterInitStruct);
复制代码 , b8 Z( w1 _+ N$ F7 {8 }
与前面一样采用结构体方式对 CAN 筛选器进行控制管理,结构体类型是! e0 A$ I" ]6 m# Y
CAN_FilterInitTypeDef。下面我们简单介绍下它的成员: - typedef struct
3 S# D3 b2 |; @" F - {8 ~* @2 E) E' ]8 R& r7 a
- uint16_t CAN_FilterIdHigh; //) M6 m4 @9 m/ \% p1 r
- uint16_t CAN_FilterIdLow; //
6 F5 L6 \ v0 i9 n( Y+ d+ P - uint16_t CAN_FilterMaskIdHigh; //- f1 Z2 r' N5 A$ ^
- uint16_t CAN_FilterMaskIdLow; //
; V) w v0 L1 [# t( g - uint16_t CAN_FilterFIFOAssignment; //
. `! T6 S( X' \5 K - uint8_t CAN_FilterNumber; //$ g1 [, X# S) J/ E& F8 g
- uint8_t CAN_FilterMode; //7 H4 h1 R) r' O& I9 h0 W) j0 x- u
- uint8_t CAN_FilterScale; //+ ?' b1 R4 ~4 J: f( i
- FunctionalState CAN_FilterActivation; //8 _' q& v* F! }) O2 \2 T
- } CAN_FilterInitTypeDef;
复制代码
; q6 @ U" X9 K2 e
CAN_FilterIdHigh:用于存储要筛选的 ID,若筛选器工作在 32 位模式,
- f9 l4 V9 s. f& W# v7 N3 ~6 S 它存储的是所筛选 ID 的高 16 位;若筛选器工作在 16 位模式,它存储的就是一个完整的要筛选的 ID。 CAN_FilterIdLow:同上一个成员一样,它也是用于存储要筛选的 ID,若筛选器工作在 32 位模式, 它存储的是所筛选 ID 的低 16 位;若筛选器工作在 16位模式,它存储的就是一个完整的要筛选的 ID。 CAN_FilterMaskIdHigh :用 于 存 储 要 筛 选 的 ID 或 掩 码 。 CAN_FilterMaskIdHigh 存储的内容分两种情况,当筛选器工作在标识符列表模式时,它的功能与 CAN_FilterIdHigh 相同,都是存储要筛选的 ID;而当筛选器工作在掩码模式时,它存储的是 CAN_FilterIdHigh 成员对应的掩码,与CAN_FilterIdLow 组成一组筛选器。 CAN_FilterMaskIdLow:同上一个成员一样,它也是用于存储要筛选的 ID 或掩码,只不过这里对应存储 CAN_FilterIdLow 的成员。 CAN_FilterFIFOAssignment:用于设置当报文通过筛选器的匹配后,该报文会 被 存 储 到 哪 一 个 接 收 FIFO , 它 的 可 选 值 为 FIFO0 或 FIFO1(CAN_Filter_FIFO0/1)。 CAN_FilterNumber:用于设置筛选器的编号, 即使用的是哪个筛选器。CAN 一共有 28 个筛选器,所以它的可输入参数范围为 0-27。 CAN_FilterMode:用于设置筛选器的工作模式,可以设置为列表模式 (CAN_FilterMode_IdList)及掩码模式(CAN_FilterMode_IdMask)。 CAN_FilterScale:用于设置筛选器的位宽,可以设置为 32 位长 (CAN_FilterScale_32bit)及 16 位长(CAN_FilterScale_16bit)。 CAN_FilterActivation:用于设置是否激活这个筛选器(ENABLE/DISABLE)。 了解结构体成员功能后,就可以进行配置,本章实验使用滤波器组 0,并工 作在 32 位标识符屏蔽位模式,配置代码如下: - CAN_FilterInitTypeDef CAN_FilterInitStructure;
: r" d" l/ O, O$ ^+ I - //配置过滤器
& a5 B+ r' `2 d - CAN_FilterInitStructure.CAN_FilterNumber=0; //过滤器0
; [, r' a7 |6 Z; [" Y - CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask;! Z. U1 Y5 p; x- N) e5 P3 o9 L5 [
- CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit;
2 r, R. l' y) C7 E4 a6 I - //32位
; F* a( u; f ^ z" o3 K7 f - CAN_FilterInitStructure.CAN_FilterIdHigh=0x0000;////32 位ID X9 m& A3 a* J6 P
- CAN_FilterInitStructure.CAN_FilterIdLow=0x0000;" O# k& d! Q. {+ C8 p
- CAN_FilterInitStructure.CAN_FilterMaskIdHigh=0x0000;//32 位MASK% i4 ~0 j/ l, h6 p- P- d+ Z
- CAN_FilterInitStructure.CAN_FilterMaskIdLow=0x0000;
- e+ i# L2 Y! h1 ^0 r/ e2 ?8 k' R# V - CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0;
, M* n4 D- W" X1 l3 S8 ~: V - //过滤器 0 关联到 FIFO0
$ r/ w5 T! Z0 z# S/ X - CAN_FilterInitStructure.CAN_FilterActivation=ENABLE; //激活过滤器08 \" W" ^$ U" D5 K* }/ c
- CAN_FilterInit(&CAN_FilterInitStructure);//滤波器初始化
复制代码9 y! l* j6 @0 C+ T w
(4)选择 CAN 中断类型,开启中断
' u {9 L% o3 y+ p0 Y2 P7 I D 配置好上述 3 个步骤后,CAN 就可以开始工作了,如果要开启 CAN 的接收中断, 我们还需要选择它的中断类型并使能。配置 CAN 中断类型及使能的库函数是: - void CAN_ITConfig(CAN_TypeDef* CANx, uint32_t CAN_IT,FunctionalState NewState);
复制代码
, J8 X, f. J2 ~" ~- E9 I 第一个参数用来选择CAN,第二个参数用来选择 CAN 中断类型,最后一个参数用来使能或者失能 CAN中断。 CAN 的中断类型很多,可以在 stm32f10x_can.h 文件查找,如下:
& y, |: [% T. p4 G/ S+ V# {8 w
% D% `8 W) [! X+ t- \- _$ z" V5 i 比如我们使用 FIFO0 消息挂号允许中断,那么此参数可以设置为CAN_IT_FMP0。使能中断后还需要设置它的中断优先级,即初始化 NVIC。当产生中断后就会进去CAN接收中断内进行处理, 所以需要编写一个CAN接收中断函数,如 CAN1_RX0_IRQHandler。本实验默认不开启 CAN 的接收中断,所以此步骤可以不用配置。 (5)CAN 发送和接收消息 初始化 CAN 相关参数以及筛选器之后,接下来就是发送和接收消息了。库 函数中提供了发送和接受消息的函数。 发送消息的函数是: - uint8_t CAN_Transmit(CAN_TypeDef* CANx, CanTxMsg* TxMessage);
复制代码
5 I) G) I4 O7 k% P, j: } 这个函数非常好理解,第一个参数用来选择 CAN,比如 CAN1,第二个参数是一个结构体指针变量,结构体类型是 CanTxMsg,其内包含了往邮箱发送的报文信息。下面我们简单介绍下它的成员: - typedef struct- p: e$ Q) z3 O6 h, f; U
- {9 p! P$ R, D. {4 k! k# [/ d
- uint32_t StdId;" ?1 F3 z1 ^$ p% R0 E) `+ T
- uint32_t ExtId;4 U* ~/ A b$ k* x- h3 ]+ x; [% ]
- uint8_t IDE;& N0 }% c6 f) X7 w$ g% y8 J
- uint8_t RTR;
" I8 l$ m7 F! ~# F, z0 ^ h: x% S - uint8_t DLC;
( H9 Z6 z, t+ ?+ h1 ~# c7 S - uint8_t Data[8];5 v5 Y3 p& s ` R1 ~; T+ Q1 y
- } CanTxMsg;
复制代码
% Z! {9 W6 r$ O6 N5 Q" ~* K
StdId:用于存储报文的 11 位标准标识符,范围是 0-0x7FF。
' _- N" N6 {: w$ G3 H ExtId:用于存储报文的 29 位扩展标识符, 范围是 0-0x1FFFFFFF。ExtId 与StdId 这两个成员哪一个有效要根据下面的 IDE 位配置。 IDE:用于存储扩展标志 IDE 位的值,其值可配置为 CAN_ID_STD 和 CAN_ID_EXT。如果为 CAN_ID_STD 时表示本报文是标准帧,使用 StdId 成员存储报文 ID。如果为 CAN_ID_EXT 时表示本报文是扩展帧,使用 ExtId 成员存储报文 ID。 RTR:用于存储报文类型标志 RTR 位的值,当它的值为宏 CAN_RTR_Data 时表示本报文是数据帧;当它的值为宏 CAN_RTR_Remote 时表示本报文是遥控帧,由于遥控帧没有数据段,所以当报文是遥控帧时,下面的 Data[8]成员的内容是无效的。 DLC:用于存储数据帧数据段的长度,其值范围是 0-8,当报文是遥控帧时DLC 值为 0。 Data[8]:用于存储数据帧中数据段的数据。 当我们需要发送报文时,就需要对此结构体进行初始化,然后调用该函数发送出去,例如: - CanTxMsg TxMessage;! _8 v2 X' p( ?4 t4 u) b
- TxMessage.StdId=0x12; // 标准标识符为 0
# y) S- m! l0 c4 X5 h0 |; }# l# d9 V - TxMessage.ExtId=0x12; // 设置扩展标示符(29 位)$ i% m" c% v5 X% y. n/ z/ a
- TxMessage.IDE=0; // 使用扩展标识符) D a8 P/ f( ?
- TxMessage.RTR=0; // 消息类型为数据帧,一帧 8 位0 }% v' j8 r8 O( O5 [9 \+ ~( F
- TxMessage.DLC=8; // 发送两帧信息- ~0 |- l3 V6 S0 v; U4 e
- for(i=0;i<8;i++)
; e0 A4 G8 n2 X- l1 H - TxMessage.Data[8]=msg[i]; // 第一帧信息* n* F( z( G* Y' d4 C0 ~1 d" ^: ]
- CAN_Transmit(CAN1, &TxMessage);
复制代码
6 x& `+ w) O& J& [ 接收消息的函数是: - void CAN_Receive(CAN_TypeDef* CANx, uint8_t FIFONumber, CanRxMsg*RxMessage);
复制代码 3 p; L7 H" @/ l# r8 X7 p3 ~7 j# Z
前两个参数很好理解,选择 CAN 和 FIFO。第三个参数是一个结构体指针变量,结构体类型是 CanRxMsg,与 CanTxMsg 比较类似,其内包含了从邮箱接收的报文信息。下面我们简单介绍下它的成员: - typedef struct
9 S1 v: o. Q2 q1 q - {
' M% e0 C5 O1 `9 e v - uint32_t StdId;) Q! Q8 M6 O' f% h: B4 Z
- uint32_t ExtId;/ U1 z# S: H$ C& f" T9 \! _
- uint8_t IDE;3 \# G1 @) P1 v( A7 C
- uint8_t RTR;
6 h: W5 n$ ?8 _4 X4 f - uint8_t DLC;
X5 A0 \7 Y; g1 ^; h& n - uint8_t Data[8];
% A0 x/ y; R9 Y5 [4 B5 ~ - uint8_t FMI;
8 W+ Z) l/ [) O9 b6 y5 X/ _ - } CanRxMsg;
复制代码& C& X" g# W6 A6 ]$ k3 @7 }5 v
前面几个成员和 CanTxMsg结构体内是一样的, 在 CanRxMsg 中多了一个成员FMI,它用于存储筛选器的编号,表示本报文是经过哪个筛选器存储进接收 FIFO的,可以用它简化软件处理。 (6)CAN 状态获取 当使用 CAN 进行数据传输时, 我们会通过获取 CAN 状态标志来确认是否传输完成,比如说在接收报文时,通过检测标志位获知接收 FIFO 的状态,若收到报文,可调用库函数 CAN_Receive 把接收 FIFO 中的内容读取到预先定义的接收类型结构体中,然后再访问该结构体即可利用报文了。 库函数中提供了很多获取 CAN 状态标志的函数,如 CAN_TransmitStatus()函数, CAN_MessagePending()函数, CAN_GetFlagStatus()函数等等,大家可以根据需要来调用。 将以上几步全部配置好后,我们就可以使用 STM32F1 的 CAN收发数据了。 " y) W$ p! C# P: R+ r+ ]6 P
|