|
硬件设计 本实验使用到硬件资源如下: (1)D1 指示灯 (2)K_UP 和 K_DOWN按键 (3)串口 1 (4)CAN 控制器 (5)CAN 收发器:TJA1040 D1指示灯、K_UP 和 K_DOWN 按键、串口 1 电路在前面章节都介绍过,这里就不多说,至于 CAN 控制器它属于 STM32F1 芯片内部的资源,只要通过软件配置好即可使用,下面我们看下 STM32F1 的 CAN 与 TJA1040 的连接,如下图所示: 6 r% Q' @: H( P/ N" d8 f0 v
) G( O6 Y6 R R2 C; H
+ @) N/ X# n8 N7 H% r+ T 从电路图中可以看到,TJA1040 芯片的 CAN_TX 和 CAN_RX 管脚连接在P16 排针上,因为 PA11 和 PA12 是 USB 和 CAN1 共用的管脚,所以使用 P16排针来做切换,本章实验我们使用的是 CAN1,所以将 P16 排针短接到CAN 端,PCB 板上P16插针短接图如下图所示: ; }- T* @" C4 z7 K
' X# o0 v0 K9 W0 p+ g# D M
在TJA1040 的 CAN_H和CAN_L 管脚上连接了一个 120 欧的终端电阻, 如果我们的开发板不是作为 CAN 终端的话,需要把这个电阻去掉,以免影响通信。最后我们用两根导线将两块开发板上 CAN 收发器的 CAN_H 和 CAN_L 对应连接,不要交叉,否则通信错误。CAN_H 和 CAN_L 示意图如下图所示: # e' T) `+ t; s; J
6 o2 m1 L: I9 I1 q) V
连接好后, 我们 STM32F1 的 CAN 控制器就可以通过 CAN 收发器 TJA1040与外部 CAN总线进行通信了。 D1指示灯用来提示系统运行状态, K_UP 按键用来切换 CAN 工作模式, K_DOWN键用来发送数据,通过串口 1将切换的模式和发送与接收到的数据打印输出。 + o7 k' a" T$ p; m$ ?) b0 o
函数编写
+ U* y+ A9 B4 _# q3 u) X0 W/ O 本实例所要实现的功能是:通过 K_UP 键切换 CAN 通信模式,K_DOWN 键控制数据发送,将切换的 CAN 模式,发送和接收的数据通过串口打印输出,整个过程D1 指示灯不断闪烁,提示系统正常运行。本章我们使用的是 STM32F1 的 CAN1,程序框架如下: (1)初始化 CAN1,包括工作模式、波特率、筛选器设置等 (2)编写 CAN1 的发送和接收函数 (3)编写主函数 前面介绍 CAN 配置步骤时, 就已经讲解如何初始化 CAN。下面我们打开 “CAN 通信实验”工程,在 APP 工程组中添加can.c 文件(里面包含了 CAN 驱动程序),在 StdPeriph_Driver 工程组中添加stm32f10x_can.c 库文件。CAN 操作的库函数都放在 stm32f10x_can.c 和stm32f10x_can.h 文件中,所以使用到 CAN 就必须加入 stm32f10x_can.c文件,同时还要包含对应的头文件路径。 这里我们分析几个重要函数,其他部分程序大家可以打开工程查看。 # |" d( s! @8 z, D- ~+ M5 F$ Q) H
CAN1 初始化函数 要使用 STM32F1 的 CAN1,我们必须先对它进行配置。初始化代码如下: - //CAN 初始化
* S' J& v) g, T5 P - //tsjw:重新同步跳跃时间单元.范围:CAN_SJW_1tq~ CAN_SJW_4tq+ n5 J, q1 a! A9 c; k
- //tbs2:时间段 2 的时间单元. 范围:CAN_BS2_1tq~CAN_BS2_8tq;. A& N' @) R/ W& E1 |
- //tbs1:时间段 1 的时间单元. 范围:CAN_BS1_1tq ~CAN_BS1_16tq- S) E6 u; w. s! f
- //brp :波特率分频器.范围:1~1024; tq=(brp)*tpclk1) X# y2 n* P8 E8 L9 P' w
- //波特率=Fpclk1/((tbs1+tbs2+1)*brp);
5 W* {. \3 ?1 H8 R: w3 d. } - //mode:CAN_Mode_Normal,普通模式;CAN_Mode_LoopBack,回环模式;
8 b5 K j5 X8 n; k& ~9 }" S9 j - //Fpclk1 的 时 钟 在 初 始 化 的 时 候 设 置 为 36M, 如 果 设 置
- P4 t: ?9 e8 v8 ^7 M: l- C - CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,CAN_Mode_LoopBack);5 y' U) I0 G* d ^' ~& X
- //则波特率为:36M/((8+9+1)*4)=500Kbps
3 j( o0 n# x4 ^7 N8 ^ - //返回值:0,初始化 OK;
; g4 R0 h- D) C6 l4 ~8 E. L) l - // 其他,初始化失败;- E* E g; f- \9 c c* l, V1 b* x
- void CAN_Mode_Init(u8 tsjw,u8 tbs2,u8 tbs1,u16 brp,u8 mode)
+ }* u# }! L; M, P; e4 G- q - {3 s$ c5 i- ]( I L& L
- GPIO_InitTypeDef GPIO_InitStructure; z" a' b8 r# w( P' I8 l! L7 M
- CAN_InitTypeDef CAN_InitStructure;* C* x9 l* m( \' _/ `' T. h
- CAN_FilterInitTypeDef CAN_FilterInitStructure;
( a* ]' E/ U4 n$ M0 W% h! L& a - #if CAN_RX0_INT_ENABLE
" o+ j' k K7 G4 M' m# m( ] - NVIC_InitTypeDef NVIC_InitStructure;
0 {! ^7 P" D% N3 u3 n - #endif
& | `. R0 \6 R - RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE); // 打 开CAN1时钟1 w) Q J, @5 z# _7 v, d2 K( g' p
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //PA 端口时钟打开
& v/ @* i4 T* K& u$ G - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; //PA115 b0 P" g9 s% w% |# j& a# Y
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入模式& w/ b, G2 a8 ^, n
- GPIO_Init(GPIOA, &GPIO_InitStructure);& r4 H& S+ L! Z, |
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; //PA12
) J: L1 Y" Y4 X+ E1 Z% W - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出& j$ z8 j& [2 s+ N1 G
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO 口速度为 50MHz! u% O& a0 L a U3 A' x! C2 I
- GPIO_Init(GPIOA, &GPIO_InitStructure);
5 l4 f( @ k! W1 n- y) [$ u - //CAN 单元设置
* V7 b( A/ w _+ W1 j# [ - CAN_InitStructure.CAN_TTCM=DISABLE; //非时间触发通信模式) Q- H; |/ i6 T! `( }, v
- CAN_InitStructure.CAN_ABOM=DISABLE; //软件自动离线管理
8 a9 ? }. I$ W O0 h$ p - CAN_InitStructure.CAN_AWUM=DISABLE;//睡眠模式通过软件唤醒(清除CAN->MCR 的 SLEEP 位)/ J7 K+ L! j( |" X
- CAN_InitStructure.CAN_NART=ENABLE; //使用报文自动传送
3 N3 `: t5 r/ w% ~. }# A5 d; N3 d - CAN_InitStructure.CAN_RFLM=DISABLE; //报文不锁定,新的覆盖旧的
& l8 [2 G4 R4 e; ~; k) ^ - CAN_InitStructure.CAN_TXFP=DISABLE; //优先级由报文标识符决定
+ a5 T4 b# O5 O! [ - CAN_InitStructure.CAN_Mode= mode; //模式设置( E1 }, k- h6 }/ K4 R7 J! X- X' e
- CAN_InitStructure.CAN_SJW=tsjw; //重新同步跳跃宽度(Tsjw)为tsjw+1个时间单位 CAN_SJW_1tq~CAN_SJW_4tq. L' x" h" Q' F6 ]6 U
- CAN_InitStructure.CAN_BS1=tbs1; //Tbs1 范 围 CAN_BS1_1tq~CAN_BS1_16tq0 A& f) z) q b# Y; L0 Q$ g
- CAN_InitStructure.CAN_BS2=tbs2;//Tbs2 范 围 CAN_BS2_1tq ~CAN_BS2_8tq
" j+ {0 O6 h2 v: u+ a - CAN_InitStructure.CAN_Prescaler=brp; //分频系数(Fdiv)为 brp+1 {& P! L5 o/ j8 m
- CAN_Init(CAN1, &CAN_InitStructure); // 初始化 CAN1) n0 Y6 s' k: _) s3 @7 Q: L
- //配置过滤器$ R' D! E3 T* ~9 C4 ^, K8 h2 U8 j
- CAN_FilterInitStructure.CAN_FilterNumber=0; //过滤器0
) v: z6 W. s' P, }" D - CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask;# _1 J! x0 I; G5 ?( p3 y$ H8 J w
- CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit;
; `& ?) l* f8 L# Q8 V - //32位9 P u' f! f0 r
- CAN_FilterInitStructure.CAN_FilterIdHigh=0x0000;////32 位ID8 d1 n# W Q. G* b9 U8 k- G0 ^
- CAN_FilterInitStructure.CAN_FilterIdLow=0x0000;
% |2 I X) Y; c7 b' m; ^$ T - CAN_FilterInitStructure.CAN_FilterMaskIdHigh=0x0000;//32 位MASK/ G4 `" Y+ x% {/ T" p" t& p
- CAN_FilterInitStructure.CAN_FilterMaskIdLow=0x0000;
9 |$ ^5 M |1 l9 K9 \! X - CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0;% Z1 `! n8 p6 g9 E$ M- ^& ?4 d: V
- //过滤器 0 关联到 FIFO0
7 L6 U! `7 m% e/ } - CAN_FilterInitStructure.CAN_FilterActivation=ENABLE; //激活过滤器 0$ u8 W+ A) I0 p/ Z Q. t
- CAN_FilterInit(&CAN_FilterInitStructure);//滤波器初始化
: T& K4 {- ]0 [) S6 K7 m - #if CAN_RX0_INT_ENABLE
: k$ s: R5 b4 ? - CAN_ITConfig(CAN1,CAN_IT_FMP0,ENABLE); //FIFO0 消 息挂号中断允许.
9 K+ m1 X2 w5 T/ L5 W& Q# ^# s9 h - NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;) r; i1 Q- j J- T1 J) j& ?
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
' U1 n+ \) g" i& i4 c0 U - // 主优先级为 11 a; x8 L! F" J9 M& ^/ S% v6 f
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;& K6 \. D3 M9 D
- // 次优先级为 0
4 `. ~5 m6 {8 ?* y - NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; s4 a1 k) t: s/ ]
- NVIC_Init(&NVIC_InitStructure);. N, a) L9 _" Y
- #endif1 ]& n: ]" D9 t( I4 F
- }
复制代码
5 C q. L9 `" f3 @# }
在CAN1_Mode_Init()函数中,首先使能 CAN1 和 GPIOA 时钟,并将管脚配置为复用功能。然后配置 CAN的工作模式、波特率及筛选器等参数。这一过程在前面步骤介绍中已经提了。CAN1_Mode_Init()函数有 4个参数,用来设置 CAN1 的波特率和工作模式,方便大家修改 CAN 的波特率和工作模式。参数的具体意义在函数开始处已注释。 ; [" f) s# t8 a1 \2 g7 m# I- Y
CAN1 发送与接收函数 初始化了 CAN1 后,我们就可以编写它的发送和接收函数,具体代码如下: - //can 发送一组数据(固定格式:ID 为 0X12,标准帧,数据帧)
2 e b4 U; K, ^ - //len:数据长度(最大为8)
: u6 b. y( N# r3 ?4 G - //msg:数据指针,最大为8个字节.& x7 s- u0 r7 j q* K
- //返回值:0,成功;
, k/ l7 L: J% G5 B$ c; Z$ g! Q - // 其他,失败;
1 l! W$ v4 m) ~( `& P8 @5 h - u8 CAN1_Send_Msg(u8* msg,u8 len)
- C8 F% A- z3 G - {: O# c" i$ s2 S. e+ K# K8 @
- u8 mbox;% `. y k O6 t0 d6 Y, f
- u16 i=0;, F) K, x7 G4 R- `6 w9 b
- CanTxMsg TxMessage;( O7 B) s; {: k! q9 z6 o+ Q
- TxMessage.StdId=0x12; // 标准标识符为 0
5 u% E: e# q0 t6 u - TxMessage.ExtId=0x12; // 设置扩展标示符(29 位)& m2 ?1 c3 m' c* u" C4 W5 u4 k4 W
- TxMessage.IDE=0; // 使用扩展标识符; X3 V- o6 s! y% M/ R3 {# e4 g" N
- TxMessage.RTR=0; // 消息类型为数据帧,一帧 8位
: z* P5 R. d! Z - TxMessage.DLC=len; // 发送两帧信息1 y# M, ]" X* {: j& t1 q
- for(i=0;i<len;i++)7 Z" ~3 [! E1 G, O3 p$ v7 Y
- TxMessage.Data[i]=msg[i]; // 第一帧信息
+ u. q7 M4 e0 c+ B: D' D - mbox= CAN_Transmit(CAN1, &TxMessage);
3 X* I; _ _9 j# a. Z6 z c+ V - i=0;6 v2 I& | I5 d! F
- while((CAN_TransmitStatus(CAN1,
8 u( o) l' ^# ^& l3 L. }0 K# e - mbox)==CAN_TxStatus_Failed)&&(i<0XFFF))i++; //等待发送结束6 q) F0 ~- f7 r3 T V
- if(i>=0XFFF)return 1; l% a& v# R1 N' }
- return 0; X9 P: Z2 K& R m
- }
/ i8 V& n# d# q4 K+ j) e - //can 口接收数据查询: X/ q0 E" e/ n
- //buf:数据缓存区;5 p/ @2 x8 R1 H4 k5 v" y
- //返回值:0,无数据被收到;1 m: I9 G0 g; v$ I. S# S( ]
- // 其他,接收的数据长度;0 Y1 E+ b4 P, G) C9 ^
- u8 CAN1_Receive_Msg(u8 *buf)! Q K |. N( [ Y
- {
- R; |; a1 \) W; e7 ?+ g( m - u32 i;
5 o3 O- S, f; z# ?: r, v - CanRxMsg RxMessage;( \) e( w2 q& [: e, R* d
- if( CAN_MessagePending(CAN1,CAN_FIFO0)==0)return 0; //没有接收到数据,直接退出% x: ?; ~8 j% q1 U( A9 k3 }+ N* c+ m) b
- CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);//读取数据
% L- P# u! V x4 u) W - for(i=0;i<RxMessage.DLC;i++)$ ]( ?9 ]+ {5 g' I$ v
- buf[i]=RxMessage.Data[i]; p/ f# P- \4 P7 Q5 G* `
- return RxMessage.DLC;8 [2 E7 n0 c* S7 D3 N# S! D7 |
- }
复制代码
0 B2 m5 C0 g- y' S! q/ p
CAN1_Send_Msg 函数为发送函数,用于 CAN 报文的发送,主要是设置标识符 ID等信息,写入数据长度和数据,并请求发送,实现一次报文的发送。CAN1_Receive_Msg 函数为接收函数,用于接受报文数据并且将接受到的数据存放到 buf 数组内。
5 m6 a0 a! v4 y; r2 u
主函数 编写好 CAN1 初始化和发送接收函数后,接下来就可以编写主函数了,代码如下: - /****************************************************************" U; Y4 [' A4 Z9 D4 l$ H% p4 Z8 c
- * 函 数 名 : main
% k" s+ g- [+ { - * 函数功能 : 主函数
, C! q$ ^1 F+ ^ - * 输 入 : 无- C( [/ Z/ z; w- d* U
- * 输 出 : 无
3 z [" s T8 p - *****************************************************************/
: q) H0 P3 G4 m! ~8 Y* y3 [ - int main()# F# L6 M0 ]' |
- {( T! o* Z0 V+ [6 g
- u8 i=0,j=0;
7 V8 z+ r, w9 S. n - u8 key;! G# M$ i) R( k& f2 C, a7 L8 N4 M- S+ {
- u8 mode=0;# U3 g( ?& ~3 E. I( l; z
- u8 res;
8 h" \; z6 H- u6 T) K! k% _2 i - u8 tbuf[8],char_buf[8]; h/ g2 A {* R, R
- u8 rbuf[8];
6 E1 G9 |1 H* ~2 E0 A1 E - SysTick_Init(72);* ~& E2 O- S# F/ O; o$ P# M: F
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断优先级分组 分2 组
2 Y2 g3 y6 K. c% L4 Z" m - LED_Init();
* l: P. b9 p$ W( e - USART1_Init(9600);
& u% E" ]1 A! o6 J# x; M9 {& P a - KEY_Init();+ P! s* q9 W' }2 G2 P9 d- P
- CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,CAN_Mode_Normal);//500Kbps 波特率
2 i9 v; Y3 C+ V1 u - while(1)& d3 n0 ^$ z" A1 {
- {9 M: b" t' C- i8 M3 M0 c
- key=KEY_Scan(0);8 w$ {/ c" z' i% I9 _. F
- if(key==KEY_UP) //模式切换9 o0 F' r: K5 n% E; U8 s1 o0 R& E$ Z
- {
+ ~& y$ K8 |5 z9 Z$ m8 M4 p - mode=!mode;6 P2 J4 s/ }" Q+ R
- CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,mode);
6 E, X, T9 T% z, j2 s3 [; M) O - if(mode==0)
+ v Z; G2 S7 e - {0 K$ @9 Z2 ]1 N! U9 u' }+ `: s
- printf("Normal Mode\r\n");8 R1 K/ ?$ l# }- A
- }6 p( j: g- {1 W& Y' [
- else& d5 u4 |! \3 R* L
- {
1 z' m3 R# [6 O8 T - printf("LoopBack Mode\r\n");, w" x1 f3 T8 p. B8 V
- }
' K% L% N+ Y1 T+ y4 T; y2 |' B+ v - }
, }7 l: k# q" P - if(key==KEY_DOWN) //发送数据$ j+ `4 N9 z( j; ]
- {
& u& S d. Z+ x) g - for(j=0;j<8;j++)8 @- k) }0 T9 g- e/ f5 N) b
- {4 Y( f% y$ D+ O# D# U; [7 D
- tbuf[j]=j;' h: h# E" p# d" l% H
- char_buf[j]=tbuf[j]+0x30;
4 T" Z' s7 j x4 U - }
+ g3 ^" Q0 S4 e7 i' ^" x' b - res=CAN_Send_Msg(tbuf,8);
7 o# N) _" G- F8 t+ m - if(res)
/ x, m9 G* e8 t* R! \ - { g1 J( T( O' p& [( v0 c8 ~
- printf("Send Failed!\r\n");
: b: d% u, \8 M9 A( d" R: ]% K - }
* ?6 U" P8 k) [2 {$ _; n" _ - else6 }0 T8 \( q5 u* {; G
- {
) s, t7 {1 Z; ~) M8 H, d9 Q - printf("发送数据:%s\r\n",char_buf);
" N: A- Y+ F: l9 E; U C! L" M - }
C. F; J" Y( J' a - }' k% I+ T: |$ Z/ |$ L V
- res=CAN_Receive_Msg(rbuf);
3 T6 Q: U% s, Q: D' N - if(res)( X$ ]2 @. r4 v' Q8 u; {1 K/ G
- {
& w4 C) N2 T( j* E3 k/ f - for(j=0;j<res;j++)
/ ]; G. P0 F% P- b6 m6 y* p# W - {
0 V+ p1 i: y: E7 C! [7 B - char_buf[j]=rbuf[j]+0x30;
1 c# Z& c% ~1 j$ A+ q - }
& Y e/ J- L- E! j g& X - printf("接收数据:%s\r\n",char_buf);( W) V. S' R# i6 u# r0 {" J
- }3 O4 v& M, @& n1 x; o, T
- i++;
8 V- ~4 l+ b/ Q8 s1 h0 O( u2 l - if(i%20==0)' A3 [8 |4 `" `; r9 x1 C4 ~/ r/ g( ]
- {
5 t) l! y6 ]" l' Q+ | - led1=!led1;
1 K& _# Z# `9 H& n6 B& w( {6 X - }
% [* m" w& g l) N/ B: \1 p, b - delay_ms(10);
: K& e' C( \, z# |- L, q - }
' x: ]+ g$ S6 p1 v' w1 l - }
复制代码4 b& Y: P# _: H
主函数实现的功能很简单,首先调用之前编写好的硬件初始化函数,包括SysTick 系统时钟,中断分组,LED 初始化等。然后调用我们前面编写的CAN1_Mode_Init 函数,我们将 CAN1 波特率设置为 500Kbps(波特率计算可参考前面内容),工作模式为正常模式 CAN_Mode_Normal。最后进入 while 循环,循环检测K_UP 和 K_DOWN 按键是否按下。如果 K_UP 键按下,通过 mode 值变化实现CAN1的工作模式的切换(正常/环回模式),并将切换的工作模式提示信息打印输出。如果 K_DOWN 键按下,调用 CAN1_Send_Msg 函数将数据发送出去,并将发送的数据内容打印输出,同时调用 CAN1_Receive_Msg 函数将发送的数据内容保存在 rbuf 数组内,通过串口打印输出。整个过程 D1 指示灯会间隔200ms闪烁,提示系统正常运行。 将工程程序编译后下载到开发板内,可以看到 D1 指示灯不断闪烁,表示程序正常运行。默认我们设置的是正常模式,所以需要连接 2 块 STM32F1 开发板的CAN 接口才能互发数据(前提是两块开发板都下载了这个程序),当按下任意一块开发板上的 K_DOWN 键后,通过串口就可以查看它们的收发数据。如果想在串口调试助手上看到输出信息,可以打开“串口调试助手”,首先勾选下标号 1 DTR 框,然后再取消勾选。这是因为此串口助手启动时会把系统复位住,通过 DTR 状态切换下即可。然后设置好波特率等参数后,串口助手上即会收到串口发送过来的信息。(前提一定要用短接片将P16排针短接到 CAN 端,串口助手上先勾选下标号 1 DTR 框,然后再取消勾选)如下图所示:
0 u* Q! C! T1 W( H8 Z3 q |