引言 CAN 是 Controller Area Network 的缩写(以下称为 CAN),是 ISO 国际标准化的串行通信协议。CAN 总线是一种应用广泛的现场总线,是近20年发展起来的新技术。在当前的汽车产业中,出于对安全性、舒适性、方便性、低公害、低成本的要求,各种各样的电子控制系统被开发了出来。由于这些系统之间通信所用的数据类型及对可靠性的要求不尽相同,由多条总线构成的情况很多,线束的数量也随之增加。为适应“减少线束的数量”、“通过多个 LAN,进行大量数据的高速通信”的需要,1986 年德国电气商博世公司开发出面向汽车的 CAN 通信协议。此后,CAN 通过 ISO11898 及 ISO11519 进行了标准化,现在在欧洲已是汽车网络的标准协议。3 {% k* p# v/ v+ D9 }, P" r) s0 }
现在,CAN 的高性能和可靠性已被认同,并被广泛地应用于工业自动化、船舶、医疗设备、工业设备等方面。现场总线是当今自动化领域技术发展的热点之一,被誉为自动化领域的计算机局域网。它的出现为分布式控制系统实现各节点之间实时、可靠的数据通信提供了强有力的技术支持。近年来,其所具有的高可靠性和良好的错误检测能力受到重视,被广泛应用于汽车计算机控制系统和环境温度恶劣、电磁辐射强和振动大的工业环境。 $ O' M9 X1 `4 Q% R3 F- \# c# u
介绍CAN协议的特点CAN属于总线式串行通信网络。由于采用了许多新技术和独特的设计思想 ,与同类产品相比 , CAN 总线在数据通信方面具有可靠、实时和灵活的优点。* A' `( Y) @& a6 X. F$ t4 s
CAN 总线上用“显性”(Dominant)和“隐性” (Recessive)两个互补的逻辑值表示“0”和“1”。CAN-H和CAN-L为CAN总线收发器 与总线之间的两接口引脚,信号是以两线之间的“差 分”电压形式出现。在隐性状态,CNA-H和CANL被固定在平均电压电平附近,Vdiff近似于0。显性位以大于最小阀值的差分电压表示。CAN 总线的通信距离最远可达10Km(位速率为5 kbps) ,通信速率最快可达 1Mbps(此时最长通信距离为40m)。 多主控制。在总线空闲时,所有单元都可以发送消息(多主控制),而两个以上的单元同时开始发送消息时,根据标识符(Identifier 以下称为 ID)决定优先级。ID 并不是表示发送的目的地址,而是表示访问总线的消息的优先级。两个以上的单元同时开始发送消息时,对各消息 ID 的每个位进行逐个仲裁比较。仲裁获胜(被判定为优先级最高)的单元可继续发送消息,仲裁失利的单元则立刻停止发送而进行接收工作。 系统的柔软性。与总线相连的单元没有类似于“地址”的信息。因此在总线上增加单元时,连接在总线上的其它单元的软硬件及应用层都不需要改变。 通信速度较快,通信距离远。最高 1Mbps(距离小于 40M),最远可达10KM(速率低于 5Kbps)。 具有错误检测、错误通知和错误恢复功能。所有单元都可以检测错误(错误检测功能),检测出错误的单元会立即同时通知其他所有单元(错误通知功能),正在发送消息的单元一旦检测出错误,会强制结束当前的发送。强制结束发送的单元会不断反复地重新发送此消息直到成功发送为止(错误恢复功能)。 故障封闭功能。CAN 可以判断出错误的类型是总线上暂时的数据错误(如外部噪声等)还是持续的数据错误(如单元内部故障、驱动器故障、断线等)。由此功能,当总线上发生持续数据错误时,可将引起此故障的单元从总线上隔离出去。 连接节点多。CAN 总线是可同时连接多个单元的总线。可连接的单元总数理论上是没有限制的。但实际上可连接的单元数受总线上的时间延迟及电气负载的限制。降低通信速度,可连接的单元数增加;提高通信速度,则可连接的单元数减少。
# q, K8 t, c" b( x4 ]
( p5 S( @& K3 Q! a2 @
正是因为 CAN 协议的这些特点,使得 CAN 特别适合工业过程监控设备的互连,因此,越来越受到工业界的重视,并已公认为最有前途的现场总线之一。
3 b6 b0 Y2 o) W! f/ y) G5 w CAN的帧- E- i3 q+ d/ f) A! S
CAN的帧的类型CAN 协议是通过以下 5 种类型的帧进行的: 数据帧 遥控帧 错误帧 过载帧 间隔帧
. O1 P" _6 P- P% t ]! d# B
' c7 p! a8 g, [ D" t
另外,数据帧和遥控帧有标准格式和扩展格式两种格式。标准格式有 11 个位的标识符(ID),扩展格式有 29 个位的 ID。 / Z9 E, E, R8 f+ Y! A* m% A
帧类型 | 帧用途 | 数据帧 | 用于发送单元向接收单元传送数据的帧 | 遥控帧 | 用于接收单元向具有相同 ID 的发送单元请求数据的帧 | 错误帧 | 用于当检测出错误时向其它单元通知错误的帧 | 过载帧 | 用于接收单元通知其尚未做好接收准备的帧 | 间隔帧 | 用于将数据帧及遥控帧与前面的帧分离开来的帧 |
) o- K, C1 E# @0 |3 [& o
" P1 L7 k! k4 Z数据帧的构成STM32的CANSTM32F1 自带的是 bxCAN,即基本扩展 CAN。它支持 CAN 协议 2.0A 和 2.0B。它的设计目标是,以最小的 CPU 负荷来高效处理大量收到的报文。它也支持报文发送的优先级要求(优先级特性可软件配置)。) b: e. Q) Q" D! T
STM32F1 的 bxCAN 的主要特点有: 低速CAN总线为开环,高速CAN总线为闭环,总线由CAN_H和CAN_L两根线组成,总线上可以挂多个节点设备。每个节点设备由CAN控制器和CAN收发器组成,CAN控制器通常作为外设集成在MPU/MCU上,而CAN收发器则需要外围添加芯片电路。 4 p2 i5 a4 s; q2 U9 v
CAN的发送接收CAN的发送流程CAN 发送流程为:程序选择 1 个空置的邮箱(TME=1)→设置标识符(ID),数据长度和发送数据→设置 CAN_TIxR 的 TXRQ 位为 1,请求发送→邮箱挂号(等待成为最高优先级)→预定发送(等待总线空闲)→发送→邮箱空置。 CAN的接收流程CAN 接收到的有效报文,被存储在 3 级邮箱深度的 FIFO 中。FIFO 完全由硬件来管理,从而节省了 CPU 的处理负荷,简化了软件并保证了数据的一致性。应用程序只能通过读取FIFO输出邮箱,来读取 FIFO 中最先收到的报文。这里的有效报文是指那些正确被接收的(直到 EOF都没有错误)且通过了标识符过滤的报文。前面我们知道 CAN 的接收有 2 个 FIFO,我们每个滤波器组都可以设置其关联的 FIFO,通过 CAN_FFA1R 的设置,可以将滤波器组关联到FIFO0/FIFO1。
6 `! d8 f# q( F: p- ICAN 接收流程为:FIFO 空 → 收到有效报文 → 挂号_1(存入 FIFO 的一个邮箱,这个由硬件控制,我们不需要理会)→ 收到有效报文 → 挂号_2 → 收到有效报文→挂号_3→收到有效报文→溢出。# w# `! [! {8 A, ?3 N
这个流程里面,我们没有考虑从 FIFO 读出报文的情况,实际情况是:我们必须在 FIFO 溢出之前,读出至少 1 个报文,否则下个报文到来,将导致 FIFO 溢出,从而出现报文丢失。每读出 1 个报文,相应的挂号就减 1,直到 FIFO 空。- Z+ X2 x; g/ F2 y9 M$ v- ^6 n
FIFO 接收到的报文数,我们可以通过查询 CAN_RFxR 的 FMP 寄存器来得到,只要 FMP不为 0,我们就可以从 FIFO 读出收到的报文。
7 t0 S' Q( K c$ ^, d% \1 N CAN控制和状态寄存器
5 K6 M& C: v6 L9 o# y& lCAN主控制寄存器 (CAN_MCR)
' n* Y; U' w0 J j8 o
, ^) l/ `6 D$ W+ b+ y6 U
对于我们正常的开发来说,INRQ 位是最重要的一位,该位用来控制初始化请求。1 V# ^# |4 y/ a: c& _7 k
软件对该位清 0,可使 CAN 从初始化模式进入正常工作模式:当 CAN 在接收引脚检测到连续的 11 个隐性位后,CAN 就达到同步,并为接收和发送数据作好准备了。为此,硬件相应地对 CAN_MSR 寄存器的 INAK 位清’0’。% O1 ?. r3 U2 g. A) z' A! x3 Y
软件对该位置 1 可使 CAN 从正常工作模式进入初始化模式:一旦当前的 CAN 活动(发送或接收)结束,CAN 就进入初始化模式。相应地,硬件对 CAN_MSR 寄存器的 INAK 位置’1’。# e7 }5 a6 I) }+ F+ w" Z4 E+ ~
所以我们在 CAN 初始化的时候,先要设置该位为 1,然后进行初始化(尤其是 CAN_BTR的设置,该寄存器,必须在 CAN 正常工作之前设置),之后再设置该位为 0,让 CAN 进入正常工作模式。
0 w5 C1 `6 ?+ Z( q* S9 H0 @7 f7 } CAN 位时序寄存器(CAN_BTR)当CAN处于初始化模式时,该寄存器只能由软件访问。
5 }% g- M" [4 a9 S' Y# O
% U3 b8 n5 Q$ ?- l7 h; _
该寄存器用于设置分频、Tbs1、Tbs2以及 Tsjw 等非常重要的参数,直接决定了 CAN 的波特率。另外该寄存器还可以设置 CAN 的工作模式。
# S, U. h1 n5 j
2 w, y& N7 N7 S9 l0 _STM32 提供了两种测试模式,环回模式和静默模式,当然他们组合还可以组合成环回静默模式。1 Z7 X* U$ F/ K3 Z6 S; n
在环回模式下,bxCAN 把发送的报文当作接收的报文并保存(如果可以通过接收过滤)在接收邮箱里。也就是环回模式是一个自发自收的模式。) a' _! J: s% k. o$ A2 v* O- \$ _
环回模式可用于自测试。为了避免外部的影响,在环回模式下 CAN 内核忽略确认错误(在数据/远程帧的确认位时刻,不检测是否有显性位)。在环回模式下,bxCAN 在内部把 Tx 输出回馈到 Rx 输入上,而完全忽略 CANRX 引脚的实际状态。发送的报文可以在CANTX 引脚上检测到。
, y: o9 N$ \( K' n: B6 b( Z CAN 发送邮箱标识符寄存器(CAN_TIxR)
: b1 S2 _+ d1 r N" s2 c
该寄存器主要用来设置标识符(包括扩展标识符),另外还可以设置帧类型,通过 TXRQ值 1,来请求邮箱发送。因为有 3 个发送邮箱,所以寄存器CAN_TIxR 有 3 个。 ) @1 a% D. L8 E' r: J
CAN 发送邮箱数据长度和时间戳寄存器 (CAN_TDTxR)当邮箱不在空置状态时,该寄存器的所有位为写保护。 ; H. a! U( w- n' ]' c
]% g* n% a& |% p' t* S2 M
该寄存器我们本章仅用来设置数据长度,即最低 4 个位。 ; \: {; p5 q* `* g$ G* D. S
CAN 发送邮箱低字节数据寄存器 (CAN_TDLxR)
+ {5 J/ E7 d6 v1 i7 _
; ]3 b& X, z$ f$ [/ O该寄存器用来存储将要发送的数据,这里只能存储低 4 个字节,另外还有一个寄存器CAN_TDHxR,该寄存器用来存储高 4 个字节,这样总共就可以存储 8 个字节。CAN_TDHxR的各位描述同 CAN_TDLxR 类似。
9 v6 ~4 R" n4 ?1 ]" A2 O CAN 接收 FIFO 邮箱标识符寄存器 (CAN_RIxR)2 f7 d$ H" [; k: U
/ w, G. E7 B6 y' w1 G* g
该寄存器各位描述同 CAN_TIxR 寄存器几乎一模一样,只是最低位为保留位,该寄存器用于保存接收到的报文标识符等信息,我们可以通过读该寄存器获取相关信息。 同样的,CAN 接收 FIFO 邮箱数据长度和时间戳寄存器 (CAN_RDTxR) 、CAN 接收 FIFO邮 箱 低 字 节 数 据 寄 存 器 (CAN_RDLxR) 和 CAN 接 收 FIFO 邮 箱 高 字 节 数 据 寄 存 器(CAN_RDHxR) 分别和发送邮箱的:CAN_TDTxR、CAN_TDLxR 以及 CAN_TDHxR 类似。 3 J g8 G* o! A: O c8 p& x) |' x
软件实现HAL 库 中 CAN 相 关 的 函 数 在 文 件 stm32f1xx_hal_can.c 和 对 应 的 头 文 件stm32f1xx_hal_can.h 中。
1 e5 K( i% `, `' h! e6 Y& w# R8 o& ^5 f9 p6 b% `
CAN 的初始化配置步骤
' r, b3 @& x* u. e3 B2 T, Q- GPIO_InitTypeDef GPIO_Initure;, z8 V; v1 L$ L% m4 c6 P4 ^
- # o0 k" n! X& C
- __HAL_RCC_CAN1_CLK_ENABLE(); //使能CAN1时钟6 A' C% g; Q1 T
- __HAL_RCC_GPIOA_CLK_ENABLE(); //开启GPIOA时钟: o1 j$ o4 b1 n- D- D( s; \; k
- & a# f% H0 I* Y% h3 \+ s* ~6 z$ k
- GPIO_Initure.Pin=GPIO_PIN_12; //PA120 R% I+ p: s8 ?( W \5 l* O1 C7 d, a
- GPIO_Initure.Mode=GPIO_MODE_AF_PP; //推挽复用# I$ Z3 f; ~; |; `# O" e
- GPIO_Initure.Pull=GPIO_PULLUP; //上拉
! M- a& X4 o# }9 }' t( m - GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH; //高速
8 s, M% s4 n5 p$ ]( d3 s - HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化 ) O0 O6 V+ V" p' _ d1 d# ^+ l& a
- GPIO_Initure.Pin=GPIO_PIN_11; //PA11( }) T g6 O; P+ p1 U
- GPIO_Initure.Mode=GPIO_MODE_AF_INPUT; //推挽复用
% j8 u* Z, h3 Z. e; \) P+ M: c - HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化
复制代码 7 {4 v; r# [7 t: \7 I1 i- l* V# J C
' p7 ^$ w n* L4 i" K
2.设置 CAN 工作模式及波特率等。 这一步通过先设置 CAN_MCR 寄存器的 INRQ 位,让 CAN 进入初始化模式,然后设置CAN_MCR 的其他相关控制位。再通过 CAN_BTR 设置波特率和工作模式(正常模式/环回模式)等信息。最后设置 INRQ 为 0,退出初始化模式。 在库函数中,提供了函数 HAL_CAN_Init 用来初始化 CAN 的工作模式以及波特率,HAL_CAN_Init 函数体中,在初始化之前,会设置 CAN_MCR 寄存器的 INRQ 为 1 让其进入初始化模式,然后初始化 CAN_MCR 寄存器和 CRN_BTR 寄存器之后,会设置 CAN_MCR 寄存器的 INRQ 为 0 让其退出初始化模式。所以我们在调用这个函数的前后不需要再进行初始化模式设置。
( _) j7 e8 X" C' [$ O( n
初始化实例为: - CAN_HandleTypeDef CAN1_Handler; //CAN1句柄
& i+ `- v7 D$ R. G0 \5 p - CAN_InitTypeDef CAN1_InitConf;
& J% R9 `' S ] - CAN1_Handler.Instance=CAN1;$ d3 J5 q$ P& l: D- ?) A
- CAN1_Handler.Init = CAN1_InitConf;
* g% D" E- t6 _* \ - CAN1_Handler.Init.Prescaler=brp; //分频系数(Fdiv)为brp+1
, W+ N! H5 n$ e - CAN1_Handler.Init.Mode=mode; //模式设置 , H {6 X7 N, B) a. I0 N
- CAN1_Handler.Init.SyncJumpWidth=tsjw; //重新同步跳跃宽度(Tsjw)为tsjw+1个时间单位 CAN_SJW_1TQ~CAN_SJW_4TQ! W3 c% I) {8 O. Q
- CAN1_Handler.Init.TimeSeg1=tbs1; //tbs1范围CAN_BS1_1TQ~CAN_BS1_16TQ8 {7 e9 D7 ~! w' R5 \, }, a
- CAN1_Handler.Init.TimeSeg2=tbs2; //tbs2范围CAN_BS2_1TQ~CAN_BS2_8TQ: ?( \5 Z. v/ j2 x
- CAN1_Handler.Init.TimeTriggeredMode=DISABLE; //非时间触发通信模式
. m3 O5 x5 |0 q - CAN1_Handler.Init.AutoBusOff=DISABLE; //软件自动离线管理
t9 M6 [7 l3 ^2 n - CAN1_Handler.Init.AutoWakeUp=DISABLE; //睡眠模式通过软件唤醒(清除CAN->MCR的SLEEP位). p9 v( `3 F* n
- CAN1_Handler.Init.AutoRetransmission=ENABLE; //禁止报文自动传送
x1 W; Q7 q% m9 s) P" E& A, M0 P - CAN1_Handler.Init.ReceiveFifoLocked=DISABLE; //报文不锁定,新的覆盖旧的
: r! E* G2 }; E! i. j+ B, ] - CAN1_Handler.Init.TransmitFifoPriority=DISABLE; //优先级由报文标识符决定 # [, ?6 _. \" a8 z3 S t* H: k5 W
- if(HAL_CAN_Init(&CAN1_Handler)!=HAL_OK) //初始化
) J" i6 W4 e! i1 o, R - return 1;. o- |2 i5 `" c3 |5 ] r: c
- return 0;
复制代码 1 @$ C e; t" a& C
HAL 库通用提供了 MSP 初始化回调函数,CAN 回调函数为: - void HAL_CAN_MspInit(CAN_HandleTypeDef* hcan);
复制代码 . X5 @7 L0 U6 I7 F/ j/ S8 F" U
该回调函数一般用来编写时钟使能,IO 初始化以及 NVIC 等配置。 - CAN_FilterTypeDef sFilterConfig;
: _# q+ D3 `5 H+ `, d% ^3 n+ ?! g - /*配置CAN过滤器*/
: P& `& ~- W% E - sFilterConfig.FilterBank = 0; //过滤器0
3 u5 x I: q, r* c. Q6 ] - sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;2 n6 _) d/ A& U. S, z
- sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
- M) r! b! o D0 F: l - sFilterConfig.FilterIdHigh = 0x0000; //32位ID+ K. u/ y" A2 ]8 t
- sFilterConfig.FilterIdLow = 0x0000;$ @5 a, {! p% |& u
- sFilterConfig.FilterMaskIdHigh = 0x0000; //32位MASK
$ q( s! f5 Y) w* N5 ? - sFilterConfig.FilterMaskIdLow = 0x0000;
, z3 A! t1 [- Q8 z. c( y - sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;//过滤器0关联到FIFO0
4 P2 k8 i! f% |* ?. L7 M8 N- M - sFilterConfig.FilterActivation = ENABLE; //激活滤波器0" O I1 _* _& [
- sFilterConfig.SlaveStartFilterBank = 14;1 a+ m& r/ k! C8 N
- //过滤器配置' P6 T; I2 z& u6 v- Q
- if (HAL_CAN_ConfigFilter(&CAN1_Handler, &sFilterConfig) != HAL_OK)
) R* p2 ]7 _' S: @. d4 Z; L - {
4 P) `9 l( Y0 | - while(1){}7 m" x3 Y9 u6 Z7 V8 W/ y4 L+ L7 Z
- }
复制代码 4 E0 }4 b( u- B* B2 T6 I0 m7 a
3. 设置滤波器。
$ W, ?1 c! ~8 `我们将使用滤波器组 0,并工作在 32 位标识符屏蔽位模式下。先设置 CAN_FMR的 FINIT 位,让过滤器组工作在初始化模式下,然后设置滤波器组 0 的工作模式以及标识符 ID和屏蔽位。最后激活滤波器,并退出滤波器初始化模式。3 N0 Q- ^5 T! X# L9 C+ e
在 HAL 库中,提供了函数 HAL_CAN_ConfigFilter 用来初始化 CAN 的滤波器相关参数。( W6 B* [. t4 G+ }/ j- `) |! y
HAL_CAN_ConfigFilter 函数体中,在初始化滤波器之前,会设置 CAN_FMR 寄存器的 FINIT位为 1 让其进入初始化模式,然后初始化 CAN 滤波器相关的寄存器之后,会设置 CAN_FMR寄存器的 FINIT 位为 0 让其退出初始化模式。所以我们在调用这个函数的前后不需要再进行初始化模式设置。 - CAN_FilterTypeDef sFilterConfig;+ s# b q, C2 H* h+ R; _" C1 V2 x) F
- /*配置CAN过滤器*/$ y/ ^$ e2 {2 }' ~) o) u% o
- sFilterConfig.FilterBank = 0; //过滤器0, m. ^& f# I7 Z
- sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;/ z1 \& @! r7 @, \& }
- sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;$ C$ |0 r. l2 w
- sFilterConfig.FilterIdHigh = 0x0000; //32位ID
6 j" V Y# a( L8 D - sFilterConfig.FilterIdLow = 0x0000;
9 k: s4 ^/ [8 m6 c: W - sFilterConfig.FilterMaskIdHigh = 0x0000; //32位MASK1 V, d' V# h) K2 g2 b+ O
- sFilterConfig.FilterMaskIdLow = 0x0000;
1 r$ B& \. a$ v - sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;//过滤器0关联到FIFO0
5 Y0 z, Y, ^6 i& L - sFilterConfig.FilterActivation = ENABLE; //激活滤波器0; Z* ^) D! C9 J4 P# [1 n" {
- sFilterConfig.SlaveStartFilterBank = 14;
1 @; z C0 Y( \' G1 j6 }, V" r - //过滤器配置% v2 x# q p# C6 p
- if (HAL_CAN_ConfigFilter(&CAN1_Handler, &sFilterConfig) != HAL_OK), Q( {9 a% C: t& \; ]
- {- ]2 s. }3 W# E# `" n3 G
- while(1){}( r1 |" ~2 _- t: N# `1 s* R1 r
- }
复制代码
- I7 e# L9 `* R! m4 K
9 i# r- e$ t5 s6 x+ ^4.发送接收消息
/ x9 C1 o; `& Z+ j在初始化 CAN 相关参数以及过滤器之后,接下来就是发送和接收消息了。HAL 库中提供了发送和接受消息的函数。+ q1 g/ k) o/ r: C# V, {
发送消息的函数是: r `) U5 ^$ Q, H, h4 c
- HAL_StatusTypeDef HAL_CAN_AddTxMessage(CAN_HandleTypeDef *hcan, CAN_TxHeaderTypeDef *pHeader, uint8_t aData[], uint32_t *pTxMailbox)
复制代码 6 j9 P/ c2 m5 p, ]2 P% X+ m3 E
- HAL_StatusTypeDef HAL_CAN_GetRxMessage(CAN_HandleTypeDef *hcan, uint32_t RxFifo, CAN_RxHeaderTypeDef *pHeader, uint8_t aData[])
复制代码
# D) G0 K5 [; M5 h c第一个入口参数为 CAN 句柄,第二个为 FIFO 号,然后是接收指针及数据存放的地址。我们接受之后,只需要读取 pHeader 便可获取接收数据和相关信息。
2 z% A9 s b: I. @2 ^2 E至此,CAN 就可以开始正常工作了。如果用到中断,就还需要进行中断相关的配置。 * e" t d, o$ Q7 \9 z8 s
功能设计CAN 的初始化 - ////CAN初始化
4 p5 y `/ o5 f {: P - //tsjw:重新同步跳跃时间单元.范围:CAN_SJW_1TQ~CAN_SJW_4TQ2 D" z+ }; r! S6 ?
- //tbs2:时间段2的时间单元. 范围:CAN_BS2_1TQ~CAN_BS2_8TQ;
+ t( |( e( m$ a5 p# q, B, ~* Y - //tbs1:时间段1的时间单元. 范围:CAN_BS1_1TQ~CAN_BS1_16TQ8 K) W- m" x& f& ]8 U+ O
- //brp :波特率分频器.范围:1~1024; tq=(brp)*tpclk1
" ]3 T% d2 C& @: Q - //波特率=Fpclk1/((tbs1+tbs2+1)*brp); 其中tbs1和tbs2我们只用关注标识符上标志的序号,例如CAN_BS2_1TQ,我们就认为tbs2=1来计算即可。; f2 H* ~1 K: a q
- //mode:CAN_MODE_NORMAL,普通模式;CAN_MODE_LOOPBACK,回环模式;
. E6 a6 U5 G* T8 T+ D. S. I - //Fpclk1的时钟在初始化的时候设置为36M,如果设置CAN1_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,CAN_MODE_LOOPBACK);8 F, h, {0 \( p8 \% x
- //则波特率为:36M/((8+9+1)*4)=500Kbps/ q6 \$ q& y! `% ?4 q
- //返回值:0,初始化OK;5 \- d, c" c+ H' L* b
- // 其他,初始化失败; 7 ]; J. C& f* Q+ E
- u8 CAN1_Mode_Init(u32 tsjw,u32 tbs2,u32 tbs1,u16 brp,u32 mode)$ i! s! b6 Y5 d, B
- {
" ^" j/ D T1 g$ u, n$ c0 i - CAN_InitTypeDef CAN1_InitConf;
1 Q- ]- [0 U+ y1 Z1 Q# V, Y - CAN1_Handler.Instance=CAN1;
v- z# i+ F3 ] - CAN1_Handler.Init = CAN1_InitConf;
; R, O4 V' S, H- }- h -
/ Y! z' A9 `1 J8 \: f$ {9 ] - CAN1_Handler.Init.Prescaler=brp; //分频系数(Fdiv)为brp+14 k$ s+ ^2 w7 K2 l
- CAN1_Handler.Init.Mode=mode; //模式设置
- \0 O4 V u% n. u1 ^; s - CAN1_Handler.Init.SyncJumpWidth=tsjw; //重新同步跳跃宽度(Tsjw)为tsjw+1个时间单位 CAN_SJW_1TQ~CAN_SJW_4TQ" m! J7 W( Y4 E5 A* d& m
- CAN1_Handler.Init.TimeSeg1=tbs1; //tbs1范围CAN_BS1_1TQ~CAN_BS1_16TQ
1 d# C- r6 r: q2 L3 g$ W5 `4 Z - CAN1_Handler.Init.TimeSeg2=tbs2; //tbs2范围CAN_BS2_1TQ~CAN_BS2_8TQ
5 q# R$ o# a4 z4 V* h. e - CAN1_Handler.Init.TimeTriggeredMode=DISABLE; //非时间触发通信模式
. R. H1 Q0 B. X; F1 U - CAN1_Handler.Init.AutoBusOff=DISABLE; //软件自动离线管理
1 p" K% M! @- q1 x1 G. v; q; o: r - CAN1_Handler.Init.AutoWakeUp=DISABLE; //睡眠模式通过软件唤醒(清除CAN->MCR的SLEEP位)
4 q& f$ j' U3 s/ n) z4 N* N4 k' i5 H - CAN1_Handler.Init.AutoRetransmission=ENABLE; //禁止报文自动传送
! K8 {0 P# e3 h. ~) j9 Q - CAN1_Handler.Init.ReceiveFifoLocked=DISABLE; //报文不锁定,新的覆盖旧的
- C( {# V$ X8 n - CAN1_Handler.Init.TransmitFifoPriority=DISABLE; //优先级由报文标识符决定 7 F3 B4 W: C/ M4 i. W
- if(HAL_CAN_Init(&CAN1_Handler)!=HAL_OK) //初始化
7 p. Y+ ^# x# |- r$ g% B - return 1;
; i/ z: o: S, ` - return 0;
" l/ j1 C& @+ H; `2 l5 x3 k* h - }
复制代码 ) K: o% a3 X K9 d; q! Y1 n
/ n" K1 t* h9 T+ F. x# |
该函数带有 5 个参数,可以设置 CAN 通信的波特率和工作模式等. & c3 ?7 k5 ?& d% F, D; A
2. CAN 报文的发送/ i3 d( c/ R0 [8 y6 e) X
主要是设置标识符 ID等信息,写入数据长度和数据,并请求发送,实现一次报文的发送。 - //can发送一组数据(固定格式:ID为0X12,标准帧,数据帧) : u1 [ z ^2 b
- //len:数据长度(最大为8)
4 q7 f* ^8 ~7 K3 ] - //msg:数据指针,最大为8个字节.. I& H% ~' j. R- k( {8 {% A
- //返回值:0,成功;
/ k5 Y, v& T4 J: q& z- y# Z - // 其他,失败;# J8 Z& }* x' p {, s6 |& n9 S0 I
- u8 CAN1_Send_Msg(u8* msg,u8 len)
3 J+ t1 D/ k( h5 X - {
i" y6 D# Y' M4 `3 ^8 b - u8 i=0;
$ s) s7 [& M0 e+ S# u( j" ~# Q - u32 TxMailbox;
( l. ]) Z1 T. ]! U- ^' [ - u8 message[8];
: K) {, G9 o2 P3 A J8 k+ ^" U - TxHeader.StdId=0X12; //标准标识符" g; ?% k( b6 m% z R. |8 l
- TxHeader.ExtId=0x12; //扩展标识符(29位)5 l2 p1 W7 a+ w
- TxHeader.IDE=CAN_ID_STD; //使用标准帧
; C& O+ d: V' s' Q: @ - TxHeader.RTR=CAN_RTR_DATA; //数据帧
1 n9 p# {7 @" N( Q" V; V* f' y+ i - TxHeader.DLC=len;
$ [0 }0 h" r- ?! V, {2 Q - for(i=0;i<len;i++)
$ }, e: b s/ m+ Z - {
" b& I" @: n0 C$ Z- k, {, l - message[i]=msg[i];
/ y0 x* Y/ K2 J }0 D - }
& e$ N1 J! N3 t3 Z5 c: l - if(HAL_CAN_AddTxMessage(&CAN1_Handler, &TxHeader, message, &TxMailbox) != HAL_OK)//发送! V" y2 b0 B9 P. w; }+ l! |
- {. r3 J3 ]* e ]2 U
- return 1;
]5 _: v8 X4 }+ h& L - }
3 I7 [: A+ z+ Q/ J5 o0 x7 b - while(HAL_CAN_GetTxMailboxesFreeLevel(&CAN1_Handler) != 3) {}/ _1 q: A1 j3 ^0 J% q
- return 0;
( j+ d' p ]1 j" T( ]9 F* {& ? - }
复制代码
; B' |7 F) Q+ o# ^& V+ I w: h
0 u7 y) I* e4 S% G6 U& v我们通过按键选择 CAN 的工作模式(正常模式/环回模式),然后通过 KEY0 控制数据发送,并通过查询的办法,将接收到的数据显示在串口上(选择非CAN占用的口)。如果是环回模式,我们不需要 2 个开发板。如果是正常模式,我们就需要 2 个开发板,并且将他们的 CAN 接口对接起来,然后一个开发板发送数据,另外一个开发板将接收到的数据显示在串口上。 - int main(void)
4 R/ j3 |" H0 [) k5 G9 [4 r - { * k% G( D8 P$ M5 R7 q/ l5 @
- u8 key;9 X) E; j& l4 d# U9 K2 O
- u8 i=0,t=0;
; T9 H# b; w: k/ s( Y - u8 cnt=0;
7 b3 [3 Q/ M" n. U - u8 canbuf[8];8 ^% d7 i9 s4 |/ v1 I& i6 G
- u8 res;
8 g* `9 F+ t6 O7 E" ^& H - u8 mode=1; $ }& J6 P1 T0 X6 E" x$ \* @3 X
- HAL_Init(); //初始化HAL库 * x. S" d( T" g; W2 i$ ~" m
- Stm32_Clock_Init(RCC_PLL_MUL9); //设置时钟,72M7 e W2 k& s" |/ z+ q
- delay_init(72); //初始化延时函数2 N' H7 N* X4 r1 W8 O
- uart_init(115200); //初始化串口
7 f- L4 j- U2 Z7 M$ |. O4 N - usmart_dev.init(84); //初始化USMART
; w- H- n. y* \/ K0 C2 ^) h/ z - LED_Init(); //初始化LED $ t, B: Y# J$ T/ O1 u
- KEY_Init(); //初始化按键
: s& W; T3 f% u* N8 d U - CAN1_Mode_Init(CAN_SJW_1TQ,CAN_BS2_8TQ,CAN_BS1_9TQ,4,CAN_MODE_LOOPBACK); //CAN初始化,波特率500Kbps * L, H% ~' [2 n% j, H
- CAN_Config(); " F4 l, t$ i# s' y' n# l ]
- printf("LoopBack Mode"); ; u6 B' `7 n3 ^) ]* }+ ^
- printf("KEY0:Send WK_UP:Mode");//显示提示信息 ; Y+ F4 X: `( y; d( C3 M* k
- printf("Count:"); //显示当前计数值 ) t( `- k: C% q6 U
- printf("Send Data:"); //提示发送的数据
, s/ O+ {) ~- _& v - printf("Receive Data:"); //提示接收到的数据 - |+ M" S8 \ K) B; r
- while(1)3 [$ A8 U% j5 ^) z
- {
% ]$ E6 Z2 K D; j$ w u2 y. N7 r - key=KEY_Scan(0);: T( @2 Q( j( I6 a; @ w$ a
- if(key==KEY0_PRES)//KEY0按下,发送一次数据
) f# z% Q0 g, z8 [& m - {$ s7 w ]' r; {, h: i$ g
- for(i=0;i<8;i++)' y1 K- F s0 d3 R' B7 m
- {9 T8 ]3 i, O3 ~
- canbuf[i]=cnt+i;//填充发送缓冲区$ N9 o4 F# c; n! w
- if(i<4)printf("%d\r\n",canbuf[i]); //显示数据' `6 T0 t) O7 p h9 ]0 y/ Q
- else printf("%d\r\n",canbuf[i]); //显示数据
: W: s2 q; ]% o, t$ V3 ?2 ^$ X' l - }) o, C, e$ u3 l! n1 k
- res=CAN1_Send_Msg(canbuf,8);//发送8个字节
2 Q2 E3 M% E3 H6 i0 h - if(res)printf("Failed"); //提示发送失败! Q/ u3 P) x; @7 b7 S9 `
- else printf("OK "); //提示发送成功 % l0 r/ k/ r% a* }# q4 q A: W% l3 v
- }else if(key==WKUP_PRES)//WK_UP按下,改变CAN的工作模式
0 U' k9 J5 T& J3 s J - {
* _2 p: g0 a) {. A: R0 z) \ - mode=!mode;8 k5 x8 ^; s5 ]- a+ i) j
- if(mode==0) CAN1_Mode_Init(CAN_SJW_1TQ,CAN_BS2_8TQ,CAN_BS1_9TQ,4,CAN_MODE_NORMAL); //回环模式,波特率500Kbps3 X" s" _& V4 J; Y
- else if(mode==1) CAN1_Mode_Init(CAN_SJW_1TQ,CAN_BS2_8TQ,CAN_BS1_9TQ,4,CAN_MODE_LOOPBACK); //回环模式,波特率500Kbps& }6 g4 |' z" l
- CAN_Config();- _4 f7 Y+ M* y' k( I! P! h' \
- if(mode==0)//普通模式,需要2个开发板
4 M. Z4 o6 o: {. l7 D8 C - {
$ e" |# o5 v+ Y - printf("Nnormal Mode ");
: `: I4 \2 g+ l0 P% Z - }else //回环模式,一个开发板就可以测试了.
7 ^' f* i8 N4 v - {
0 J( d# b) R/ p - printf("LoopBack Mode");
1 A7 R* j, t/ J3 K; C9 L) E$ w - }3 x+ A1 E- B y1 ~& J$ T0 P1 o/ e9 {
- }
0 p4 G* T* e* g3 { - key=CAN1_Receive_Msg(canbuf);
% d- r Q* q+ ]/ N$ s- _ - if(key < 9)//接收到有数据, ^4 X" K- F+ V9 {# j. d2 K
- {
" E, l$ n7 f3 Y& B# L4 _
9 h5 D$ N' x# [( j0 t$ q3 o/ }$ c- for(i=0;i<key;i++)
! {6 w# {! J7 P8 K2 K - {
8 _0 d4 Y( ~+ f* O# V3 [6 K - if(i<4)printf("%d\r\n",canbuf[i]); //显示数据
- ?! W/ \8 I6 ]4 k - else printf("%d\r\n",canbuf[i]); //显示数据
1 W# ]1 h1 N% E( {! D - }
, z) D) W, N0 [ k - }
; E/ B; y# y: a- S9 { - t++; 2 [9 ^9 R! G6 ^' V
- delay_ms(10);
* n7 Y! A" G7 \2 }$ \ - if(t==20)
! e: @! h1 k4 R - {& B) S* o( y+ J1 ^
- LED0=!LED0;//提示系统正在运行 & `% ]/ N: x( e+ a5 o [
- t=0;
! a% ^6 N4 q! y. f8 S - cnt++;
4 @, v$ ?! Z8 |. ~; i2 l( w - printf("%d\r\n",cnt); //显示数据
$ W, ^/ K# ]4 S4 p - }
- p+ h9 U/ U) p; u O - } 5 O9 M8 [5 l4 l# K" h3 g
- }0 @& a; @, _& M3 }8 Y
- }
" |9 u% u" _* b! P" b9 J" D - key=CAN1_Receive_Msg(canbuf);/ W5 q5 s- T" Z
- if(key < 9)//接收到有数据
4 `# {) {* B4 o% D - {
9 A1 v6 L0 d- {4 `5 }
+ t$ C5 T; ~2 W* u- for(i=0;i<key;i++)
6 a6 [% ?4 F! r3 o- Q5 w - { : d l& P7 E: m$ n% D+ \. X' h
- if(i<4)printf("%d\r\n",canbuf[i]); //显示数据
$ ^2 p8 i9 }2 T; ^' m - else printf("%d\r\n",canbuf[i]); //显示数据
; P% c1 a/ Y' Z' g/ _7 y - }
3 t$ `. i g' `, o B$ W! l - }
* K7 a3 B) Q6 m/ D. f2 O4 X/ P - t++;
$ a4 E* J t) R7 U" J- N* [ - delay_ms(10);
; h& s) E7 _8 h; I, L# e3 u' ` - if(t==20)& Z, k8 K, p3 Y# b
- { L8 o' w1 U6 V* [0 {1 X1 w7 U
- LED0=!LED0;//提示系统正在运行 + H1 R. K+ m# _: w% [
- t=0;
3 \- q8 v3 D$ i" R: X! A - cnt++;: \% ?# P1 a) G) `2 Y6 \/ x
- printf("%d\r\n",cnt); //显示数据: J& I+ N D* z5 f6 e
- }
' Z; p; Y# M1 I2 B, j7 V% _7 S - } # s8 s. E- a; c; X3 G* A8 K2 \/ O! W
- }
复制代码 5 O. \2 f) q; q1 F0 Z) q
转载自: 跋扈洋 8 a: _+ C2 j8 `1 ~1 j
|