KEIL 5.38的ARM-CM3/4 ARM汇编设计学习笔记6 - STM32的CAN学习与测试1

一、介绍

CAN总线是STM32的一个很重要的接口。在很多与伺服控制器、工控上位机等设备通讯的时候,CAN都是必须要支持的接口。然而奇怪的是,很多的嵌入式工程师似乎并不会使用。所以研究了相应的说明文件以后,我利用代码和调试来学习并测试了CAN接口。这里对学习和测试的过程进行说明。

有关CAN的协议这里就不赘述了。网上有很多讲的。我这里使用的开发与测试相关的软硬件如下表所示。

设备与配件型号与参数
IDEKeil MDK-Community v5.38
开发板HX32F4
系统Windows 10
示波器SIGLENT SDS3104X HD

二、目标

  1. 编写CAN的配置代码
  2. 发送CAN的数据帧或远程帧
  3. 在示波器上捕捉到CANH和CANL波形
  4. 接收到该帧

三、 实验设计

研究了一下STM32F4和F1的bxCAN,它们能发送远程/数据、标准/扩展这样一共4种帧。通过配置RTR和IDE位来实现。在本案例中,CAN1的GPIO口是PB8和PB9,功能配置都是AF9。那么一共要有下面的流程来实现从配置到应用。

首先和其他的接口或外设一样,整个设置过程有3步:

  1. 激活CAN和GPIO的时钟
  2. 设置GPIO
  3. 设置CAN

就设置CAN而言,一共有5步:

  1. 切换到Initialisation模式
  2. 设置波特率、回环与静默模式、中断
  3. 设置过滤器
  4. 启动过滤器
  5. 切换到Normal模式

接下来,就可以填写发送邮箱,就像填报表那样,发送数据了。

这里注意一下,CAN是没有DMA的。

这里如果只是为了学习,有个非常简单有效的办法。就是启动调试以后,在调试窗口填写邮箱,并发送。如果你还不是很熟悉有关的寄存器的话。

四、代码设计

4.1 创建CAN1_Drv.h和CAN1_Drv.s

创建这样一个C头文件,代码如下所示

#ifndef _CAN1_DRV_H_
#define _CAN1_DRV_H_

#include "stdint.h"

typedef struct {
	void (*init)(uint32_t baudrate_Hz);
}CAN1_Def;
extern CAN1_Def can1;

#endif

再创建CAN1_Drv.s源文件,准备在这个文件中实现can1结构体。


	get peripherials.s
rRCC	rn 	r8
rGPIOB	rn 	r9
rCAN1	rn	r10

	import SystemCoreClockUpdate
	
	area text, code	
	align 4
	preserve8 
init	proc
	push {r4 - r11, lr}
	
	; Enable the clocks. 
	; APB1 for CAN1, AHB1 for GPIOB
	ldr rRCC, =RCC_BaseAddr
	ldr  r4, [rRCC, #RCC_AHB1ENR]
	orr  r4, #RCC_AHB1ENR_GPIOBEN
	str  r4, [rRCC, #RCC_AHB1ENR]
	ldr  r4, [rRCC, #RCC_APB1ENR]
	orr  r4, #RCC_APB1ENR_CAN1EN
	str  r4, [rRCC, #RCC_APB1ENR]
	
	; Configure the pins for CAN1
	; CAN_TX: PB8, AF9
	; CAN_RX: PB9, AF9
	ldr  rGPIOB, =GPIOB_BaseAddr
	ldr  r4, [rGPIOB, #GPIOx_MODER]
	bfc  r4, #16, #4
	orr  r4, #(GPIOx_MODER_AFIO:shl:16):or:(GPIOx_MODER_AFIO:shl:18)
	str  r4, [rGPIOB, #GPIOx_MODER]
	ldr  r4, [rGPIOB, #GPIOx_OSPEEDR]
	bfc  r4, #16, #4
	orr  r4, #(GPIOx_OSPEEDR_VERYHIGH:shl:16):or:(GPIOx_OSPEEDR_VERYHIGH:shl:18)
	str  r4, [rGPIOB, #GPIOx_OSPEEDR]
	ldr  r4, [rGPIOB, #GPIOx_PUPDR]
	bfc  r4, #16, #4
	orr  r4, #(GPIOx_PUPDR_PU:shl:16):or:(GPIOx_PUPDR_PU:shl:18)
	str  r4, [rGPIOB, #GPIOx_PUPDR]
	ldr  r4, [rGPIOB, #GPIOx_AFRH]
	bfc  r4, #0, #4
	orr  r4, #(9:shl:0) :or: (9:shl:4)
	str	 r4, [rGPIOB, #GPIOx_AFRH]
	
	; Setup the CAN1
	ldr rCAN1, =CAN1_BaseAddr
	
	; Trans to Initialisation Mode
	ldr  r4, [rCAN1, #CAN_MCR]
	bic  r4, #CAN_MCR_SLEEP
	orr	 r4, #CAN_MCR_INRQ
	str	 r4, [rCAN1, #CAN_MCR]
	
	; Set the Baudrate
	; CAN1 is at APB1. 
	push {r0}
	ldr  r4, =SystemCoreClockUpdate
	blx  r4
	lsr  r4, r0, #2
	; Now f_apb1 is r4.
	pop  {r1}
	
	; TS1 and TS2 are 3, BRP + 1 is r4.
	; Enable the loop-back mode
	udiv r4, r4, r1
	mov  r5, #9
	udiv r4, r4 ,r5
	sub  r4, #1
	mov  r5, #3
	ldr  r6, [rCAN1, #CAN_BTR]
	bfc  r6, #16, #6
	bfi	 r6, r4, #0, #10
	bfi	 r6, r5, #16, #4
	bfi	 r6, r5, #20, #3
	orr  r6, #1:shl:30
	str  r6, [rCAN1, #CAN_BTR]
	
	; Set the filter
	ldr  r4, [rCAN1, #CAN_FM1R]
	orr	 r4, #1:shl:0
	str  r4, [rCAN1, #CAN_FM1R]
	
	; The filtered STID is 11, data frame
	; Enable the filter F0R1
	mov  r4, #11:shl:21
	str  r4, [rCAN1, #CAN_F0R1]
	ldr  r4, [rCAN1, #CAN_FA1R]
	orr  r4, #1:shl:0
	str  r4, [rCAN1, #CAN_FA1R]
	ldr  r4, [rCAN1, #CAN_FMR]
	bic  r4, #1:shl:0
	str  r4, [rCAN1, #CAN_FMR]
	
	; Switch CAN1 Peripherial from Initialisation to Normal State
	ldr  r4, [rCAN1, #CAN_MCR]
	bic  r4, #1:shl:0
	str  r4, [rCAN1, #CAN_MCR]
	
	
	; Make a test
	ldr  r4, =Test_String
	ldr  r4, [r4]
	str  r4, [rCAN1, #CAN_TDL0R]
	mov  r4, #4
	ldr  r5, [rCAN1, #CAN_TDT0R]
	bfi  r5, r4, #0, #3
	str  r5, [rCAN1, #CAN_TDT0R]
	ldr  r4, [rCAN1, #CAN_TI0R]
	
	mov  r4, #11:shl:21
	str  r4, [rCAN1, #CAN_TI0R]
	
	
	pop	 {r4 - r11, lr}
	bx	 lr
	endp
Test_String	dcb	"Andy"	
	align 4	
can1 dcd	init
	export can1
	
		
	end

通过这样源文件,就成功配置好了bxCAN1。下面对这段代码进行解释。

4.2 源文件配置

	get peripherials.s
rRCC	rn 	r8
rGPIOB	rn 	r9
rCAN1	rn	r10

	import SystemCoreClockUpdate
	
	area text, code	
	align 4
	preserve8

peripherials.s是另一个汇编源文件,里面是我定义的有关的寄存器地址。类似于stm32f4xx.h那种文件。代码如下所示。

	if :def: _PERIPHERIALS_H_
	else
		gbla _PERIPHERIALS_H_
RCC_BaseAddr 	equ		0x40023800	

RCC_CR			equ		0x00
RCC_PLLCFGR		equ		0x04
RCC_CFGR		equ		0x08
RCC_CIR			equ		0x0C
RCC_AHB1ENR		equ		0x30
RCC_AHB2ENR		equ		0x34
RCC_AHB3ENR		equ		0x38
RCC_APB1ENR		equ		0x40
RCC_APB2ENR		equ		0x44

RCC_CR_HSEON		equ		1:shl:16
RCC_CR_PLLON		equ		1:shl:24	
RCC_PLLCFGR_PLLSRC	equ		1:shl:22
RCC_PLLCFGR_PLLP_DIV_2		equ	 	2_00:shl:16
RCC_PLLCFGR_PLLP_DIV_4		equ	 	2_01:shl:16
RCC_PLLCFGR_PLLP_DIV_6		equ	 	2_10:shl:16
RCC_PLLCFGR_PLLP_DIV_8		equ	 	2_11:shl:16

RCC_CFGR_PPRE2_APB2_DIV_2	equ		2_100:shl:13
RCC_CFGR_PPRE2_APB2_DIV_4	equ		2_101:shl:13	
RCC_CFGR_PPRE2_APB2_DIV_8	equ		2_110:shl:13		
RCC_CFGR_PPRE2_APB2_DIV_16	equ		2_111:shl:13			
RCC_CFGR_PPRE1_APB1_DIV_2	equ		2_100:shl:10
RCC_CFGR_PPRE1_APB1_DIV_4	equ		2_101:shl:10	
RCC_CFGR_PPRE1_APB1_DIV_8	equ		2_110:shl:10		
RCC_CFGR_PPRE1_APB1_DIV_16	equ		2_111:shl:10
RCC_CFGR_SW_HSE		equ		2_01
RCC_CFGR_SW_PLL		equ		2_10
RCC_AHB1ENR_GPIOBEN	equ		1:shl:1
RCC_APB1ENR_CAN1EN	equ		1:shl:25



FLASH_BaseAddr	equ		0x40023C00 
FLASH_ACR		equ		0x00	
FLASH_ACR_DCEN		equ		1:shl:10
FLASH_ACR_ICEN		equ		1:shl:9
FLASH_ACR_PRFTEN	equ		1:shl:8
	
GPIOB_BaseAddr	equ		0x40020400
GPIOx_MODER		equ		0x00
GPIOx_OTYPER	equ		0x04
GPIOx_OSPEEDR	equ		0x08
GPIOx_PUPDR		equ		0x0C	
GPIOx_IDR		equ		0x10
GPIOx_ODR		equ		0x14
GPIOx_BSRR		equ		0x18	
GPIOx_LCKR		equ		0x1C
GPIOx_AFRL		equ		0x20
GPIOx_AFRH		equ		0x24	

GPIOx_MODER_AFIO			equ		2_10	
GPIOx_OSPEEDR_LOW			equ		2_00
GPIOx_OSPEEDR_MEDIATE		equ		2_01
GPIOx_OSPEEDR_HIGH			equ		2_00
GPIOx_OSPEEDR_VERYHIGH		equ		2_11
GPIOx_PUPDR_NONE			equ		2_00
GPIOx_PUPDR_PU				equ		2_01
GPIOx_PUPDR_PD				equ		2_10


CAN1_BaseAddr	equ		0x40006400
; CAN control and status registers
CAN_MCR			equ		0x00
CAN_MSR			equ		0x04
CAN_TSR			equ		0x08
CAN_RF0R		equ		0x0C
CAN_RF1R		equ		0x10
CAN_IER			equ		0x14
CAN_ESR			equ		0x18
CAN_BTR			equ		0x1C 	
	
; CAN mailbox registers
CAN_TI0R		equ		0x180
CAN_TDT0R		equ		0x184
CAN_TDL0R		equ		0x188		
CAN_TDH0R		equ		0x18C
CAN_TI1R		equ		0x190
CAN_TDT1R		equ		0x194
CAN_TDL1R		equ		0x198		
CAN_TDH1R		equ		0x19C
CAN_TI2R		equ		0x1A0
CAN_TDT2R		equ		0x1A4
CAN_TDL2R		equ		0x1A8		
CAN_TDH2R		equ		0x1AC
CAN_RI0R 		equ		0x1B0		
CAN_RDT0R		equ		0x1B4
CAN_RDL0R		equ		0x1B8
CAN_RDH0R		equ		0x1BC
CAN_RI1R 		equ		0x1C0		
CAN_RDT1R		equ		0x1C4
CAN_RDL1R		equ		0x1C8
CAN_RDH1R		equ		0x1CC

; CAN filter registers
CAN_FMR			equ		0x200
CAN_FM1R		equ		0x204
CAN_FS1R		equ		0x20C
CAN_FFA1R		equ		0x214
CAN_FA1R		equ		0x21C	
CAN_F0R1		equ		0x240		
CAN_F27R2		equ		0x31C
	
CAN_MCR_SLEEP	equ		1:shl:1
CAN_MCR_INRQ	equ		1:shl:0	
CAN_TIxR_TXRQ	equ		1:shl:0
CAN_TIxR_RTR	equ		1:shl:1
CAN_TIxR_IDE	equ		1:shl:2


			endif
			end

将C函数void SystemCoreClockUpdate()从外部引入。但是由于这个函数要求栈8字节地址预留,所以必须加入preserve8伪指令,否则报警告。
接下来创建void init((uint32_t baudrate_Hz)函数。

4.3 启动时钟

在Keil环境中,STM32F407的启动代码只给加载了内部RC震荡器作为AHB源。但是我们想切换到PLL。参考我前面的《KEIL 5.38的ARM-CM3/4 ARM汇编设计学习笔记2——设置PLL》,我将时钟设置为144MHz。之后就是使能AHB1的GPIOB时钟。

	; Enable the clocks. 
	; APB1 for CAN1, AHB1 for GPIOB
	ldr rRCC, =RCC_BaseAddr
	ldr  r4, [rRCC, #RCC_AHB1ENR]
	orr  r4, #RCC_AHB1ENR_GPIOBEN
	str  r4, [rRCC, #RCC_AHB1ENR]
	ldr  r4, [rRCC, #RCC_APB1ENR]
	orr  r4, #RCC_APB1ENR_CAN1EN
	str  r4, [rRCC, #RCC_APB1ENR]

4.4 配置引脚

如果是STM32F407,则需要将CAN的这两个引脚都设置为AFIO,根据芯片手册《STM32F405xx STM32F407xx》,CAN1的两个引脚是PB8和PB9,AFIO号是AF9。但是如果是STM32F103,那么CAN_RX配置为Input,CAN_TX配置为AFIO。

; Configure the pins for CAN1
	; CAN_TX: PB8, AF9
	; CAN_RX: PB9, AF9
	ldr  rGPIOB, =GPIOB_BaseAddr
	ldr  r4, [rGPIOB, #GPIOx_MODER]
	bfc  r4, #16, #4
	orr  r4, #(GPIOx_MODER_AFIO:shl:16):or:(GPIOx_MODER_AFIO:shl:18)
	str  r4, [rGPIOB, #GPIOx_MODER]
	ldr  r4, [rGPIOB, #GPIOx_OSPEEDR]
	bfc  r4, #16, #4
	orr  r4, #(GPIOx_OSPEEDR_VERYHIGH:shl:16):or:(GPIOx_OSPEEDR_VERYHIGH:shl:18)
	str  r4, [rGPIOB, #GPIOx_OSPEEDR]
	ldr  r4, [rGPIOB, #GPIOx_PUPDR]
	bfc  r4, #16, #4
	orr  r4, #(GPIOx_PUPDR_PU:shl:16):or:(GPIOx_PUPDR_PU:shl:18)
	str  r4, [rGPIOB, #GPIOx_PUPDR]
	ldr  r4, [rGPIOB, #GPIOx_AFRH]
	bfc  r4, #0, #4
	orr  r4, #(9:shl:0) :or: (9:shl:4)
	str	 r4, [rGPIOB, #GPIOx_AFRH]

4.5 配置bxCAN1

使能MCR的INAK,清除SLEEP位。将bxCAN1唤醒,进入初始化状态。在初始化状态中涉及到MCR和BTR两个寄存器。

; Trans to Initialisation Mode
	ldr  r4, [rCAN1, #CAN_MCR]
	bic  r4, #CAN_MCR_SLEEP
	orr	 r4, #CAN_MCR_INRQ
	str	 r4, [rCAN1, #CAN_MCR]

设置波特率。这里我为了实现1个形参表示波特率,我调用void SystemCoreClockUpdate(void)函数获得当前的HCLK的频率。由于这个函数的最后一句其实就是吧HCLK的值放到了r0,所以调用了这个函数以后,r0中就是HCLK的频率值。由于我的APB1是HCLK的4分频(我在RCC那边就是这么写的),我因此就获得了APB1的频率。

根据这些数值,算出了要填写的TS0、TS1和BRP的值。由于我们测试要使用回环模式,不然发的邮件一定是回不来的。这样填写一下BTR。如果不使用中断且只是发送邮件,CAN1的初始化就是完成了。

; Set the Baudrate
	; CAN1 is at APB1. 
	push {r0}
	ldr  r4, =SystemCoreClockUpdate
	blx  r4
	lsr  r4, r0, #2
	; Now f_apb1 is r4.
	pop  {r1}
	
	; TS1 and TS2 are 3, BRP + 1 is r4.
	; Enable the loop-back mode
	udiv r4, r4, r1
	mov  r5, #9
	udiv r4, r4 ,r5
	sub  r4, #1
	mov  r5, #3
	ldr  r6, [rCAN1, #CAN_BTR]
	bfc  r6, #16, #6
	bfi	 r6, r4, #0, #10
	bfi	 r6, r5, #16, #4
	bfi	 r6, r5, #20, #3
	orr  r6, #1:shl:30
	str  r6, [rCAN1, #CAN_BTR]

4.6 接收和过滤器设置

但是我们还是要接受数据的,所以我们要配置接收。跟接收相关的其实就是个过滤器,涉及到那几个接收过滤器以及相关的配置寄存器。这里注意,如果没有任何一个过滤器是激活的,肯定是不会有任何信息被送到邮箱里的。过滤器配置寄存器的设置要在FMR的FINIT位为1的时候进行配置。配完了记得清除。

; Set the filter
	ldr  r4, [rCAN1, #CAN_FM1R]
	orr	 r4, #1:shl:0
	str  r4, [rCAN1, #CAN_FM1R]
	
	; The filtered STID is 11, data frame
	; Enable the filter F0R1
	mov  r4, #11:shl:21
	str  r4, [rCAN1, #CAN_F0R1]
	ldr  r4, [rCAN1, #CAN_FA1R]
	orr  r4, #1:shl:0
	str  r4, [rCAN1, #CAN_FA1R]
	ldr  r4, [rCAN1, #CAN_FMR]
	bic  r4, #1:shl:0
	str  r4, [rCAN1, #CAN_FMR]

我这里设置的是使能过滤器0,设置为16位过滤器,List Mode模式, 导入邮箱0。通过的STID是11(0x0B)的标准数据帧。

4.7 转入Normal状态

清除MCR-INAK位将CAN1转入Normal状态。

	; Switch CAN1 Peripherial from Initialisation to Normal State
	ldr  r4, [rCAN1, #CAN_MCR]
	bic  r4, #1:shl:0
	str  r4, [rCAN1, #CAN_MCR]

4.8 做个测试

我放了4个字节"Andy“在函数结束之后的位置。

; Make a test
	ldr  r4, =Test_String
	ldr  r4, [r4]
	str  r4, [rCAN1, #CAN_TDL0R]
	mov  r4, #4
	ldr  r5, [rCAN1, #CAN_TDT0R]
	bfi  r5, r4, #0, #3
	str  r5, [rCAN1, #CAN_TDT0R]
	ldr  r4, [rCAN1, #CAN_TI0R]
	
	mov  r4, #11:shl:21
	str  r4, [rCAN1, #CAN_TI0R]
	
	
	pop	 {r4 - r11, lr}
	bx	 lr
Test_String	dcb	"Andy"	
	endp
	align 4	
can1 dcd	init
	export can1

编译,调试。调试窗口如下所示。
在这里插入图片描述

在CAN1-TI0R-TXRQ那里,点击一下。会看到2个现象:

  1. RF0R-FMP0那里变成了1。这时候会发现RI0R,RDT0R,RDL0R也都有相应的更新。就是说,收到了数据。
  2. 示波器上也会有波形显示。

在这里插入图片描述
这里看到,示波器的CAN协议分析可以抓到这个包,数据也正确。但是唯一一点要说的是,由于我们采用的是回环模式自发自收,所以ACK那里显示no。如果用Lecroy的示波器则会提示ACK Error。这里暂时认为是回环模式的原因造成的。

关于鼎阳的这个示波器的CAN协议分析软件,根据和官方的确认,目前存在一个bug。那就是,必须要把波形拖拽到靠近屏幕的右边协议分析软件才能生效。这个要自己调整。大概就是让ACK应该出现的那个位置卡在屏幕右边缘。鼎阳官方确认这是个一个bug,可能在后面的固件升级的时候修正。那就让我们拭目以待吧。

五 结论

根据上面所述的实验,我们可以通过操作bxCAN相关寄存器的方法很简单地实现CAN的收发。如果要实现一个可靠的完整的CAN接口的通讯驱动,还需要实现环形队列和接收中断。就像其他的MCU通讯接口一样,CAN的配置与应用也是大致下面所述的个步骤。

  1. 使能外设和相关GPIO的时钟
  2. 配置管脚
  3. 使能该外设或进入该外设的初始化状态
  4. 配置波特率、中断和接收过滤器
  5. 进入正常状态

可以看出,通过以上的配置,就可以实现数据的收发。非常高效。

  • 15
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值