你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32实例-CAN通信③-CAN 配置步骤

[复制链接]
STMCU小助手 发布时间:2022-6-26 17:23
    前面我们用了很大篇幅介绍 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) 。
    所以配置代码如下:
  1. //使能相关时钟  J3 \, Y( {- M
  2. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//使能 PORTA时钟3 X5 ?. J3 c( x& `) i) H+ B
  3. RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);//使能 CAN1 时钟
    ( T9 {' R6 f! R
  4. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; //PA11
    $ n; s4 s3 D% B# @% y
  5. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入模式! F8 O& ?1 p% F5 ]9 A1 g1 b) L
  6. GPIO_Init(GPIOA, &GPIO_InitStructure);
      R) B# h7 I6 i! F/ u+ H/ }
  7. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; //PA121 X7 g, Z) g" n. h3 h' E
  8. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出' N/ O8 z4 e) t
  9. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO 口速度为 50MHz
    + ?4 F" E# r5 W2 u) l5 _5 n
  10. GPIO_Init(GPIOA, &GPIO_InitStructure);
复制代码
1 {2 r# a2 H1 r: M
(2)设置 CAN 工作模式、波特率等
    使能了 CAN 时钟后,接下来就可以通过 CAN_MCR 寄存器配置其工作模式、波特率大小等参数。库函数中提供了 CAN_Init()函数用来完成这一步骤,函数原型是:
  1. uint8_t CAN_Init(CAN_TypeDef* CANx, CAN_InitTypeDef*CAN_InitStruct);
复制代码
2 o1 E* v+ o0 U2 a( H" [
    函数中第一个参数是用来设置哪个 CAN,例如 CAN1;第二个参数是一个结构体指针变量,结构体类型是 CAN_InitTypeDef,其内包含了 CAN 工作模式及波特率初始化的成员变量。下面我们简单介绍下它的成员:
  1. typedef struct9 a0 [* e' }! ^! I  ]$ ~3 m8 @
  2. {
    9 L* P0 P4 N$ o, Y
  3. uint16_t CAN_Prescaler; //
    9 A3 q9 i' l' q  M: z9 x
  4. uint8_t CAN_Mode; //
    - R& b& X6 c" _% T- j6 {4 C5 M
  5. uint8_t CAN_SJW; //
    ) ]  d5 L% d% D) ^  {( u# `
  6. uint8_t CAN_BS1; //
    2 ^& ~3 M/ K: c
  7. uint8_t CAN_BS2; //
    3 I' u% k9 t$ K3 k( Z& t) o7 U
  8. FunctionalState CAN_TTCM; //
    4 s: {' V  q; F) ^
  9. FunctionalState CAN_ABOM; //
    5 t6 Q! I+ T: A) U& X
  10. FunctionalState CAN_AWUM; //1 {9 R, u5 g. H- Z
  11. FunctionalState CAN_NART; //
      d* B8 x0 c1 n
  12. FunctionalState CAN_RFLM; //
    9 f$ i5 v, t3 o$ M1 X4 g# A- U
  13. FunctionalState CAN_TXFP; //7 t5 S5 J1 O% n8 T" V# w* M/ n' e7 c
  14. } CAN_InitTypeDef;
    3 C1 y: f0 D+ w' V  \
复制代码

3 Z, _1 K( k. W$ P  c5 e* {
    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,配置代码如下:
  1. CAN_InitTypeDef CAN_InitStructure;
    , Q2 }7 G8 k" B& t/ Z
  2. //CAN 单元设置
    ) _+ |- S8 A9 d! i. g
  3. CAN_InitStructure.CAN_TTCM=DISABLE; //非时间触发通信模式  d+ G8 m9 ~# h# P" w- w
  4. CAN_InitStructure.CAN_ABOM=DISABLE; //软件自动离线管理4 K6 x4 i+ ~: e3 Q) }
  5. CAN_InitStructure.CAN_AWUM=DISABLE;//睡眠模式通过软件唤醒(清除CAN->MCR 的 SLEEP 位)# E5 J9 N" Q. S4 _) n0 P
  6. CAN_InitStructure.CAN_NART=ENABLE; //使用报文自动传送7 g  ]) A  z0 d- c9 T$ e+ X
  7. CAN_InitStructure.CAN_RFLM=DISABLE; //报文不锁定,新的覆盖旧的) f) X" w  [" V, R4 {
  8. CAN_InitStructure.CAN_TXFP=DISABLE; //优先级由报文标识符决定4 S6 e5 b3 J+ w" i* r5 P1 x
  9. CAN_InitStructure.CAN_Mode= CAN_Mode_Normal; //模式设置! G2 I# x" A& {6 G% N. V
  10. CAN_InitStructure.CAN_SJW=CAN_SJW_1tq; //重新同步跳跃宽度(Tsjw)为 tsjw+1 个时间单位 CAN_SJW_1tq~CAN_SJW_4tq, d5 A  `+ F" \
  11. CAN_InitStructure.CAN_BS1=CAN_BS1_7tq; //Tbs1 范 围 CAN_BS1_1tq~CAN_BS1_16tq( X1 D" X( v. \/ e2 |
  12. CAN_InitStructure.CAN_BS2=CAN_BS2_6tq;//Tbs2 范 围 CAN_BS2_1tq ~CAN_BS2_8tq
    5 |# v- ^* k/ H. }8 U9 Z; r$ Y
  13. CAN_InitStructure.CAN_Prescaler=6; //分频系数(Fdiv)为brp+1
    # J0 o- g3 h" X3 w
  14. CAN_Init(CAN1, &CAN_InitStructure); // 初始化 CAN1
复制代码
4 Y) f& Z- ~4 I7 Z3 g7 F+ x! Z
(3)设置 CAN 筛选器5 l) F, d& _! Y. j
    设置好 CAN 的工作模式及波特率后,我们还需要通过 CAN_FMR 寄存器设置CAN 筛选器,库函数中提供了 CAN_FilterInit()函数来完成这一步骤,函数原型是:
  1. void CAN_FilterInit(CAN_FilterInitTypeDef* CAN_FilterInitStruct);
复制代码
+ T4 |. R1 V/ k8 ~/ e: I, z* ]( Z& P- \
    与前面一样采用结构体方式对 CAN 筛选器进行控制管理,结构体类型是
2 k4 p4 o1 g; }3 m2 j/ f" I' [
CAN_FilterInitTypeDef。下面我们简单介绍下它的成员:
  1. typedef struct; _4 ?% p3 L( L- N. q
  2. {
    4 r% s" n0 i, ]/ o; r
  3. uint16_t CAN_FilterIdHigh; //8 m0 }- C: @, a
  4. uint16_t CAN_FilterIdLow; //* n+ q+ h0 ^$ b9 f
  5. uint16_t CAN_FilterMaskIdHigh; //& }# c) f4 k& y* i
  6. uint16_t CAN_FilterMaskIdLow; //
    / [! \- ^" ^; m
  7. uint16_t CAN_FilterFIFOAssignment; //
    + b) }, c$ p' `
  8. uint8_t CAN_FilterNumber; //' I+ c8 o" B8 V# P2 d9 G- i0 J
  9. uint8_t CAN_FilterMode; //$ C/ l7 u* ^; L8 x6 Y
  10. uint8_t CAN_FilterScale; //
    2 d2 c* o- U3 `
  11. FunctionalState CAN_FilterActivation; //+ O2 f) [' S$ E/ l
  12. } CAN_FilterInitTypeDef;
复制代码
& L9 }3 l7 j9 Z/ u: D
    CAN_FilterIdHigh:用于存储要筛选的 ID,若筛选器工作在 32 位模式,
1 q% k4 w+ W: ~# b/ m( e, w8 I+ y5 F
它存储的是所筛选 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 位标识符屏蔽位模式,配置代码如下:
  1. CAN_FilterInitTypeDef CAN_FilterInitStructure;5 y/ j, Y' {! \
  2. //配置过滤器' w( ^; K  I( e6 ^: ~% o! ~
  3. CAN_FilterInitStructure.CAN_FilterNumber=0; //过滤器0
    4 V  p/ s. S7 p7 O
  4. CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask;6 g+ }- |' L/ W7 e
  5. CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit;% @$ S! ~( B6 N* W" `5 P
  6. //32位
    8 ?( i; M- ?1 u' h- k9 {8 m5 `
  7. CAN_FilterInitStructure.CAN_FilterIdHigh=0x0000;////32 位ID& Z2 H2 L' M# Z1 _- Y( {4 v* F
  8. CAN_FilterInitStructure.CAN_FilterIdLow=0x0000;
    ! C9 I! C$ ]& d6 e6 J' Q( s
  9. CAN_FilterInitStructure.CAN_FilterMaskIdHigh=0x0000;//32 位MASK
    4 V9 d+ Q  @1 {- }' K
  10. CAN_FilterInitStructure.CAN_FilterMaskIdLow=0x0000;
    ; a8 `' x4 h8 v1 h3 J0 \  B& |2 h. ^& ]/ L
  11. CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0;
    ; `" |3 g: C; J) H4 [
  12. //过滤器 0 关联到 FIFO02 |. ^' a  p7 [4 L; E3 b& u% i9 x% L
  13. CAN_FilterInitStructure.CAN_FilterActivation=ENABLE; //激活过滤器0
    * Q8 q* f# t4 G- U( K8 j/ u
  14. CAN_FilterInit(&CAN_FilterInitStructure);//滤波器初始化
复制代码
; ~' a; x# O- W* q5 P4 k
(4)选择 CAN 中断类型,开启中断
9 A7 z3 |# I5 i; r
    配置好上述 3 个步骤后,CAN 就可以开始工作了,如果要开启 CAN 的接收中断, 我们还需要选择它的中断类型并使能。配置 CAN 中断类型及使能的库函数是:
  1. void CAN_ITConfig(CAN_TypeDef* CANx, uint32_t CAN_IT,FunctionalState NewState);
复制代码
9 j: \6 b; e# w% c
    第一个参数用来选择CAN,第二个参数用来选择 CAN 中断类型,最后一个参数用来使能或者失能 CAN中断。
    CAN 的中断类型很多,可以在 stm32f10x_can.h 文件查找,如下:
* }9 e8 C% O% n! p, c
微信图片_20220626172326.png
  p. W0 Z1 m5 m+ ?$ J
    比如我们使用 FIFO0 消息挂号允许中断,那么此参数可以设置为CAN_IT_FMP0。使能中断后还需要设置它的中断优先级,即初始化 NVIC。当产生中断后就会进去CAN接收中断内进行处理, 所以需要编写一个CAN接收中断函数,如 CAN1_RX0_IRQHandler。本实验默认不开启 CAN 的接收中断,所以此步骤可以不用配置。
(5)CAN 发送和接收消息
    初始化 CAN 相关参数以及筛选器之后,接下来就是发送和接收消息了。库
函数中提供了发送和接受消息的函数。
    发送消息的函数是:
  1. uint8_t CAN_Transmit(CAN_TypeDef* CANx, CanTxMsg* TxMessage);
复制代码

+ x& U9 d& m4 r
    这个函数非常好理解,第一个参数用来选择 CAN,比如 CAN1,第二个参数是一个结构体指针变量,结构体类型是 CanTxMsg,其内包含了往邮箱发送的报文信息。下面我们简单介绍下它的成员:
  1. typedef struct
    5 R( k2 _& i( ?. ]9 m
  2. {
    : C. h2 u) {& e- \1 Z
  3. uint32_t StdId;  u  ^! Z2 G1 I2 r1 m
  4. uint32_t ExtId;
    6 f9 ?) v# ]+ [
  5. uint8_t IDE;0 R( K  |. l8 ^1 t
  6. uint8_t RTR;1 I2 M4 ^+ A9 \
  7. uint8_t DLC;
    " O! F  _- T) I$ t/ }' {
  8. uint8_t Data[8];
    0 z. Z) H5 k( u/ [
  9. } CanTxMsg;
复制代码

$ \& n9 C0 Z7 \7 ^9 E. P
    StdId:用于存储报文的 11 位标准标识符,范围是 0-0x7FF。: g+ z& T. X& U5 V! v- b
    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]:用于存储数据帧中数据段的数据。
    当我们需要发送报文时,就需要对此结构体进行初始化,然后调用该函数发送出去,例如:
  1. CanTxMsg TxMessage;4 c, @. k1 R8 ~( x- y, |( ?' _5 y
  2. TxMessage.StdId=0x12; // 标准标识符为 0( i' w3 g+ ^6 J7 W' g/ S+ u
  3. TxMessage.ExtId=0x12; // 设置扩展标示符(29 位)
    ( d2 t% V8 Z2 @# F
  4. TxMessage.IDE=0; // 使用扩展标识符: S8 T! D! \0 F# O5 k" `( [: Z
  5. TxMessage.RTR=0; // 消息类型为数据帧,一帧 8 位8 E9 y) u) j2 N0 i4 _' K
  6. TxMessage.DLC=8; // 发送两帧信息
    2 ~9 a& v0 Z. T8 m  U, L
  7. for(i=0;i<8;i++)
    2 |) Q8 H: c; @8 W8 y- r3 x
  8. TxMessage.Data[8]=msg[i]; // 第一帧信息( ]9 v- m' l2 @  D% y0 u: U
  9. CAN_Transmit(CAN1, &TxMessage);
复制代码

+ b; a' s' [, `' v: ~; K
    接收消息的函数是:
  1. void CAN_Receive(CAN_TypeDef* CANx, uint8_t FIFONumber, CanRxMsg*RxMessage);
复制代码
& B' y" @7 ]# E1 f" J
    前两个参数很好理解,选择 CAN 和 FIFO。第三个参数是一个结构体指针变量,结构体类型是 CanRxMsg,与 CanTxMsg 比较类似,其内包含了从邮箱接收的报文信息。下面我们简单介绍下它的成员:
  1. typedef struct+ r  `$ Z) `1 |: a7 E) M
  2. {
    8 X9 J7 `7 I4 W* v
  3. uint32_t StdId;* |( |, I* E  K1 P$ W
  4. uint32_t ExtId;( V9 V, Y, m" ?9 P' H  K- a
  5. uint8_t IDE;9 N. u3 [$ E2 u( S# r) q0 K: R
  6. uint8_t RTR;& p8 t! |. g: G/ v' ]
  7. uint8_t DLC;
    ( q9 k% l/ y; @; {
  8. uint8_t Data[8];: I9 Y6 W1 c( y  W3 r# k. s
  9. uint8_t FMI;- w6 a2 X) l! q. x6 r) l# L$ D, q8 k
  10. } CanRxMsg;
复制代码

. c9 e" \* W; t# B$ u
    前面几个成员和 CanTxMsg结构体内是一样的, 在 CanRxMsg 中多了一个成员FMI,它用于存储筛选器的编号,表示本报文是经过哪个筛选器存储进接收 FIFO的,可以用它简化软件处理。
(6)CAN 状态获取
    当使用 CAN 进行数据传输时, 我们会通过获取 CAN 状态标志来确认是否传输完成,比如说在接收报文时,通过检测标志位获知接收 FIFO 的状态,若收到报文,可调用库函数 CAN_Receive 把接收 FIFO 中的内容读取到预先定义的接收类型结构体中,然后再访问该结构体即可利用报文了。
    库函数中提供了很多获取 CAN 状态标志的函数,如 CAN_TransmitStatus()函数, CAN_MessagePending()函数, CAN_GetFlagStatus()函数等等,大家可以根据需要来调用。
    将以上几步全部配置好后,我们就可以使用 STM32F1 的 CAN收发数据了。

* b6 ~0 q. x4 B# V+ y4 H, @+ ]
赞 收藏 评论0 发布时间:2022-6-26 17:23

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版