【转】 STM32的can总线实验心得

**(一) 工业现场总线 CAN 的基本介绍以及 STM32 的 CAN 模块简介
**
首先通读手册中关于CAN的文档,必须精读。
STM32F10xxx 参考手册Rev7V3.pdf
http://www.mystm32.com/bbs/redirect.php?tid=255&goto=lastpost#lastpost

需要精读的部分为 RCC 和 CAN 两个章节。
为什么需要精读 RCC 呢?因为我们将学习 CAN 的波特率的设置,将要使用到 RCC 部分的设置,因此推荐大家先复习下这部分中的几个时钟。

关于 STM32 的 can 总线简单介绍
bxCAN 是基本扩展 CAN (Basic Extended CAN) 的缩写,它支持 CAN 协议 2.0A 和 2.0B 。它的设计目标是,以最小的 CPU 负荷来高效处理大量收到的报文。它也支持报文发送的优先级要求(优先级特性可软件配置)。
对于安全紧要的应用,bxCAN 提供所有支持时间触发通信模式所需的硬件功能。

**主要特点
**· 支持 CAN 协议 2.0A 和 2.0B 主动模式
· 波特率最高可达 1 兆位 / 秒
· 支持时间触发通信功能

**发送
**· 3 个发送邮箱
· 发送报文的优先级特性可软件配置
· 记录发送 SOF 时刻的时间戳

**接收
**· 3 级深度的2个接收 FIFO
· 14 个位宽可变的过滤器组 - 由整个 CAN 共享
· 标识符列表
· FIFO 溢出处理方式可配置
· 记录接收 SOF 时刻的时间戳

**可支持时间触发通信模式
**· 禁止自动重传模式
· 16 位自由运行定时器
· 定时器分辨率可配置
· 可在最后 2 个数据字节发送时间戳

**管理
**· 中断可屏蔽
· 邮箱占用单独 1 块地址空间,便于提高软件效率

(二) STM32 CAN 模块工作模式

STM32 的 can 的工作模式分为:

/* CAN operating mode /
#define CAN_Mode_Normal ((u8)0x00) /
normal mode /
#define CAN_Mode_LoopBack ((u8)0x01) /
loopback mode /
#define CAN_Mode_Silent ((u8)0x02) /
silent mode /
#define CAN_Mode_Silent_LoopBack ((u8)0x03) /
loopback combined with silent mode */

在此章我们的 Mini-STM32 教程中我们将使用到 CAN_Mode_LoopBackCAN_Mode_Normal 两种模式。
我们第一步做的就是使用运行在 CAN_Mode_LoopBack 下进行自测试。

在参考手册中 CAN_Mode_LoopBack (环回模式) 的定义如下:
环回模式可用于自测试。为了避免外部的影响,在环回模式下 CAN 内核忽略确认错误 (在数据 / 远程帧的确认位时刻,不检测是否有显性位) 。在环回模式下,bxCAN 在内部把 Tx 输出回馈到 Rx 输入上,而完全忽略 CANRX 引脚的实际状态。发送的报文可以在 CANTX 引脚上检测到。
因此这种模式也特别适合大家做好硬件后自测程序。

**(三) CAN 接口端口映射
**

STM32 中的 CAN 物理引脚脚位可以设置成三种:

默认模式,重定义地址1模式,重定义地址2模式

在我们的 Mini-STM32 上面没有接出 CAN 的接口芯片, 所以我们可以利用

RealView MDK

的 CAN 软件

模拟

模块来做实验.


默认模式

/* Configure CAN pin: RX */

GPIO

_InitStructure.GPIO_Pin = GPIO_Pin_11;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;

GPIO_Init(GPIOA, &GPIO_InitStructure);

/* Configure CAN pin: TX */

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);


重定义地址1模式

/* Configure CAN pin: RX */

//GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;

//GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

//GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;

//GPIO_Init(GPIOB, &GPIO_InitStructure);

/* Configure CAN pin: TX */

//GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;

//GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

//GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

//GPIO_Init(GPIOB, &GPIO_InitStructure);

/* Configure CAN Remap 重影射 */

//GPIO_PinRemapConfig(GPIO_Remap1_CAN, ENABLE);


**重定义地址2模式
**

/* Configure CAN pin: RX */

//GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;

//GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

//GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;

//GPIO_Init(GPIOD, &GPIO_InitStructure);

/* Configure CAN pin: TX */

//GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;

//GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

//GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

//GPIO_Init(GPIOD, &GPIO_InitStructure);

/* Configure CAN Remap 重影射 */

//GPIO_PinRemapConfig(GPIO_Remap2_CAN, ENABLE);


设置完 CAN 的引脚之后还需要打开 CAN 的时钟:

/* CAN Periph clock enable */

RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN, ENABLE);

**(四) CAN 波特率设置
**

4、我们需要搞明白CAN波特率的设置,这个章节也是使用CAN的最重要的部分之一,因为这实际应用中我们需要根据我们实际的场合来选择 CAN 的波特率。

一般情况下面1M bps 的速率下可以最高可靠传输 40 米以内的距离。

在 50K 以下的波特率中一般可以可靠传输数公里远。

对于波特率的设置需要详细学习参考手册对应部分的解释。我们在调试软件的时候可以使用示波器来测试 CANTX 引脚上的波形的波特率,这样可以得到事半功倍的效果,大大的缩短调试学习的时间。

// ***************************************************************

// BaudRate = 1 / NominalBitTime

// NominalBitTime = 1tq + tBS1 + tBS2

// tq = (BRP[9:0] + 1) x tPCLK

// tPCLK = CAN’s clock = APB1’s clock

// ****************************************************************

也就是BaudRate = APB1 / ((BS1 + BS2 + 1) * Prescaler)

这里注意的是采用点的位置,也就时BS1,BS2的设置问题,这里我也找了一些资料,抄录下来给大家,是 CANopen 协议中推荐的设置。

1Mbps 速率下,采用点的位置在6tq位置处,BS1=5, BS2=2

500kbps 速率下,采用点的位置在8tq位置处,BS1=7, BS2=3

250kbps 速率下,采用点的位置在14tq位置处,BS1=13, BS2=2

125k, 100k, 50k, 20k, 10k 的采用点位置与 250K 相同。

因此我们需要重视的有软件中的这么几个部分:

// 设置 AHB 时钟(HCLK)

// RCC_SYSCLK_Div1 AHB 时钟 = 系统时钟

RCC_HCLKConfig(RCC_SYSCLK_Div8);

// 设置低速 AHB 时钟(PCLK1)

// RCC_HCLK_Div2 APB1 时钟 = HCLK / 2

RCC_PCLK1Config(RCC_HCLK_Div2);

// PLLCLK = 8MHz * 8 = 64 MHz

// 设置 PLL 时钟源及倍频系数

RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_8);

CAN 波特率设置中需要的就是PCLK1 的时钟。

CAN_InitStructure.CAN_Mode=CAN_Mode_LoopBack;

CAN_InitStructure.CAN_SJW=CAN_SJW_1tq;

CAN_InitStructure.CAN_BS1=CAN_BS1_8tq;

CAN_InitStructure.CAN_BS2=CAN_BS2_7tq;

CAN_InitStructure.CAN_Prescaler=5;

通过上面部分的时钟设置我们已经可以算出我们的波特率了

CAN_bps = PCLK1 / ((1 + 7 + 8) * 5) = 25K bps

大家也可以实际测试中修改时钟值来通过示波器测试我们需要的波特率是否正确例如将PLLCLK 设置降低一半:

// PLLCLK = 8MHz * 4 = 32 MHz

// 设置 PLL 时钟源及倍频系数

RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_4);

那么我们得到的CAN_bps也会降低一半。

接下来还可以修改 HCLK 和 PCLK1 ,其实最终这几个分频和倍频值最终影响的都是 PCLK1。

通过几次试验,相信大家应该很容易掌握波特率的设置了。

设置完波特率我们直接测试函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/* CAN transmit at 100Kb/s and receive by polling in loopback mode*/

TestRx = CAN_Polling();

if (TestRx == FAILED)

{

/* Turn on led connected to PA.00 pin (LD1) */

GPIO_SetBits(GPIOA, GPIO_Pin_0);

}

else

{

/* Turn off led connected to PA.00 pin (LD1) */

GPIO_ResetBits(GPIOA, GPIO_Pin_0);

}

/* CAN transmit at 500Kb/s and receive by interrupt in loopback mode*/

TestRx = CAN_Interrupt();

if (TestRx == FAILED)

{

/* Turn on led connected to PA.01 pin (LD2) */

GPIO_SetBits(GPIOA, GPIO_Pin_1);

}

else

{

/* Turn off led connected to PA.01 pin (LD2) */

GPIO_ResetBits(GPIOA, GPIO_Pin_1);

}

CAN 软件仿真模拟器调用出来.

大家可以仿真程序,当程序中 Test 等于 Passed 那么说明 Loopback 模式测试通过了。

并且在 CAN 通讯框中我们可以看到发送和接收到的数据:

回循模式下的源代码, 基于 MDK3.5:

(493.79 KB)

到此时说明如果大家只有一块CAN模块的时候学习可以告一个段落了,不过这个并不代表大家就已经掌握了 CAN 了,正真要掌握它,大家还是需要看大量的 CAN 部分的资料,参考手册部分的也是不够的,市面上有几本专门介绍现场总线和CAN总线的书,推荐大家买来经常翻翻看看,这样到需要实际应用的时候才可以做到 如鱼得水。

**(五) 正常模式
**

完成了 loopback 模式的测试之后接下来我们需要学习的就是多机通讯了,当然由于我们的 Mini-STM32 没有将 CAN 接口引出来, 所以我们没有办法在板子上面做这部分的试验了,只能在 RealView MDK 的软件中进行模拟。

如果您拥有两块带 CAN 硬件的 STM32 的板子,您需要自己构建硬件的物理层的连接, 使用三根线将 CANH,CANL,GND 三根线直连,当然你要接好终端电阻才能保证通讯的正常通讯,当两块板子都跳好后我们使用万用表测量下 CANH和CANL之间的电阻是否为 60 欧姆。多块板子多机通讯的是否你只需要在总线的主机端和最后一端接上终端电阻就可以了.

在初始化完成后,软件应该让硬件进入正常模式,以便正常接收和发送报文。软件可以通过对 CAN_MCR 寄存器的INRQ位清 ‘0’,来请求从初始化模式进入正常模式,然后要等待硬件对 CAN_MSR 寄存器的 INAK 位置 ‘1’ 的确认。在跟 CAN 总线取得同步,即在 CANRX 引脚上监测到 11 个连续的隐性位 (等效于总线空闲) 后,bxCAN 才能正常接收和发送报文。

不需要在初始化模式下进行过滤器初值的设置,但必须在它处在非激活状态下完成 (相应的 FACT 位为 ‘0’ ) 。而过滤器的位宽和模式的设置,则必须在初始化模式中进入正常模式前完成。

准备工作做完我们需要设置 CAN 通讯部份软件。

我们把 TestStatus CAN_Polling(void) 函数和 TestStatus CAN_Interrupt(void) 函数中的 LoopBack 模式修改为 Normal 模式.

//CAN_InitStructure.CAN_Mode=CAN_Mode_LoopBack;

CAN_InitStructure.CAN_Mode=CAN_Mode_Normal;

接下来我们就可以做实验了. 但是由于 RealView MDK 的 CAN 模块没有办法接收, 所以我们只做发送的例子.

我们的例子中分别发送两帧数据:

(1) TestStatus CAN_Polling(void) 查询发送

第一帧数据为: ID 为 0x11, 数据为 8 个字节的一个数据包.

TxMessage.StdId=0x11;

TxMessage.RTR=CAN_RTR_DATA;

TxMessage.IDE=CAN_ID_STD;

TxMessage.DLC=8;

TxMessage.Data[0]=0x01;

TxMessage.Data[1]=0x02;

TxMessage.Data[2]=0x03;

TxMessage.Data[3]=0x04;

TxMessage.Data[4]=0x05;

TxMessage.Data[5]=0x06;

TxMessage.Data[6]=0x07;

TxMessage.Data[7]=0x08;

**(2) TestStatus CAN_Interrupt(void) 中断发送
**

第二帧数据为:ID 为 0x1234, 数据为 8 个字节的一个数据包.

TxMessage.StdId=0x12;

TxMessage.ExtId=0x34;

TxMessage.IDE=CAN_ID_EXT;

TxMessage.RTR=CAN_RTR_DATA;

TxMessage.DLC=8;

TxMessage.Data[0]=0x11;

TxMessage.Data[1]=0x22;

TxMessage.Data[2]=0x33;

TxMessage.Data[3]=0x44;

TxMessage.Data[4]=0x55;

TxMessage.Data[5]=0x66;

TxMessage.Data[6]=0x77;

TxMessage.Data[7]=0x88;

CAN_Transmit(&TxMessage);

在主函数中初始化之后加上这两句发送函数:

/* CAN transmit at 100Kb/s and receive by polling in Normal mode*/

CAN_Polling();

while(i++ < 1000);

/* CAN transmit at 500Kb/s and receive by interrupt in Normal mode*/

CAN_Interrupt();

程序改完了, 我们需要编译通过后, 点软件仿真.

CAN 软件仿真模拟器

调用出来.

接下来我们全速运行到 while(1) 就可以看到结果了.