基于ARM-Cortex M3/4的GNU汇编的嵌入式程序设计之二——实现硬件I2C访问MS4525压力传感器

基于ARM-Cortex M3的GNU汇编的嵌入式程序设计之二——实现硬件I2C访问MS4525压力传感器

一、目标

根据前面对汇编的介绍,我们来实现一个stm32f103的硬件I2C从MS4525压力传感器读数据的驱动。笔者的硬件是103的I2C2连到了MS4525上面。

需求描述

需求描述
C函数封装要封装成C语言可以调用的函数,在其他的C函数中调用
传感器数值读取该函数执行一次就完成一次从传感器处读压力值。即每次用传感器读2个字节

有关的问题

很多人说stm32f103的硬件i2c有bug,其实主要就是两个

Bug解决方案
初始化时I2C的GPIO时钟使能不生效如果你是用的cube配置的引脚,那就是用hal库初始化。去HAL_I2C_MspInit()函数中优化。参见笔者的另一篇文章:《STM32F103与4525I压力传感器通讯中的硬件I2C的解决方案》
SR2_busy位锁定位1烧录一个不使用该I2C端口的程序,关机重启。修改你的代码再进行调试。只有硬件关机才能解锁。对调试不友好。

再有人说,总是调不通。这其实很有可能是用C语言不能严格执行这个端口要求的寄存器读取时序。
首先是使能CR1的PE,发送START。

发送START
置位CR1 - START位以后,轮询SR - SB位。如果置位了,则要读一次SR1再写入从机地址(即MS4525的地址),才能清SB位。

清ADDR
轮询SR1- ADDR位,如果该位置位,则需要通过读一次SR1再读一次SR2清除。之后,由于我们只是读2个字节,所以必须参考手册后面的文本。可以看到,如果只是读2个字节,要的操作是不一样的。这个非常重要。

在这里插入图片描述
所以说,如果读手册,一定要仔细点哈哈。

至于MS4525那边,其实比较简单,就是发送起始符,发送地址,读两个字节,发送NACK和终止符。就算是完成一次会话。

二、代码和解释

搞清楚了状况,我们就着手写程序。
第一步,创建presSen_asm.s文件。 注意,很多时候我们可能系统中有个presSen.c的文件,不可以直接创建presSen.s,因为这两个文件一旦编译都是生成presSen.o,会冲突。
第二步,设计程序。
函数定义笔者输出一个函数叫`uint16_t asm_Func_presSen_getVal(void);

那四句套话 把开篇那四句从startup_stm32f103xe.s里抄过来。解锁32位Thumb-2指令。

符号定义 考虑到程序中会用到I2C2有关的寄存器CR1、DR、SR1、SR2, 用到的位主要是CR1的START、POS、ACK、STOP和PE,SR1的SB、RXNE、ADDR、BTF。因为这些都是local的,所以可以直接使用该符号而没有必要提前用.local声明。我们给I2C2_BaseAddr赋值为这个口的寄存器的起始地址,剩下的相关寄存器只要定义个偏移量就可以了。如果你不记得这些寄存器的偏移量,去手册里这个位置查看。基地址也可以从手册里看,也可以从C的头文件里找。这样用LDR和STR就可以通过基地址+偏移量直接访问了。

那些.section .text.asm_Func_presSen_getVal后面的那些设置选项,参考一些关于汇编的伪指令
在这里插入图片描述

规划寄存器和内存 数数我们用多少存储。就像用C语言上来看看用几个变量。如果小于8个,就只要寄存器就可以了。本文就这么1个函数,来来回回就操作那么3个外设寄存器,再加上1个寄存器记录基地址,4个寄存器开局,就先不去开辟内存了,写了不够再去BSS段开辟就是了。

/*
 * presSen_asm.s
 *
 *  Created on: 2022年6月17日
 *      Author: SystemUser
 */

.syntax unified
.cpu cortex-m3
.fpu softvfp
.thumb

.global asm_Func_presSen_getVal

.set 	I2C2_BaseAddr,	 	0x40005800
.set 	CR1,				0x00
.set 	DR,					0x10
.set 	SR1,				0x14
.set 	SR2,				0x18

.set 	I2C_CR1_START,		0x100
.set 	I2C_CR1_STOP,		0x200
.set 	I2C_CR1_ACK,		0x400
.set	I2C_CR1_POS,		0x800
.set	I2C_CR1_PE,			0x01
.set	I2C_CR1_POS_ACK,	I2C_CR1_POS | I2C_CR1_ACK
.set 	I2C_SR1_ADDR, 		0x02
.set 	I2C_SR1_SB, 		0x01
.set 	I2C_SR1_BTF, 		0x04

.set 	sensor_addr,		0x51

	.section .text.asm_Func_presSen_getVal/*,"ax",%progbits*/
	.type asm_Func_presSen_getVal,%function
/*
*	Accroding to the manual, Case of two bytes to be received:
*	– Set POS and ACK
*	– Wait for the ADDR flag to be set
*	– Clear ADDR
*	– Clear ACK
*	– Wait for BTF to be set
*	– Program STOP
*	– Read DR twice
*/

/*r3 is used to hold CR1, r4 for I2C2_BaseAddr, r5 for SR1, R6 for SR2*/
asm_Func_presSen_getVal:
	push	{r4-r7, lr}
/*for test*/
/*test over*/
	ldr	 	r4, =I2C2_BaseAddr; /* load the address of the I2C2 base register*/
	ldr  	r3, [r4, #CR1];

Enable_the_I2C2:
	orr 	r3, #I2C_CR1_PE	/*Set the PE*/
	str		r3, [r4, CR1];
Send_a_Start:
	orr		r3, #I2C_CR1_START  /*Send a start*/
	str		r3, [r4, CR1];

Check_if_the_i2c_start_isSent:
	ldr 	r5, [r4, #SR1];	/*Keep reading the sr1 register*/
	and		r5, #I2C_SR1_SB
	cmp		r5, #I2C_SR1_SB
	bne		Check_if_the_i2c_start_isSent;
Send_the_Addr:
	ldr 	r5, [r4, #SR1];
	mov		r3, #sensor_addr;
	str		r3, [r4, DR];

Set_POS_and_ACK:
	ldr  	r3, [r4, #CR1];
	orr 	r3, #I2C_CR1_POS_ACK	/*Set the ACK and POS*/
	str		r3, [r4, CR1];

Wait_for_the_ADDR_flag_to_be_set:
	ldr  	r5, [r4, #SR1];
	and		r5, #I2C_SR1_ADDR
	cmp		r5, #I2C_SR1_ADDR
	bne		Wait_for_the_ADDR_flag_to_be_set;

Clear_Addr:
	ldr		r5, [r4, #SR1];
	ldr		r6, [r4, #SR2];

Clear_ACK:
	ldr 	r3, [r4, #CR1];
	bic 	r3, I2C_CR1_ACK;
	str		r3, [r4, CR1];

Wait_for_BTF_to_be_set:
	ldr  	r5, [r4, #SR1];
	and		r5, #I2C_SR1_BTF
	cmp		r5, #I2C_SR1_BTF
	bne		Wait_for_BTF_to_be_set;

Program_STOP:
	ldr 	r3, [r4, #CR1];
	orr 	r3, I2C_CR1_STOP;
	str		r3, [r4, CR1];

Read_DR_twice:
	ldrb	r2, [r4, #DR]
	ldrb	r1, [r4, #DR]
Form_the_return_value:
	add 	r0, r1, r2, lsl 8;
Disable_I2C2:
	mov		r3, #0;
	str		r3, [r4, #CR1];
End_the_function:
	pop	 	{r4-r7, lr}
	bx 	 	lr
.size asm_Func_presSen_getVal, .-asm_Func_presSen_getVal

定义函数并实现 用前文说的方法定义函数asm_Func_presSen_getVal()。进入函数以后先把r4 - r7压入栈里。因为这次个寄存器是被调用函数保护寄存器。剩下的每一段的语句块的含义都跟语句标号一样。最后由于传感器两次传来的值是两个8位的,分别存于r2和r1的低8位。用一句add r0, r1, r2, lsl 8;将传感器的16位值算出并存入r0,也就是返回值。
告诉调试器函数尺寸 最后一句.size asm_Func_presSen_getVal, .-asm_Func_presSen_getVal告诉调试器这个函数的大小。

三、运行情况

编译通过以后,从内存分布上检查一下。本函数占104个字节,除了4个压栈,没有额外的内存消耗。

在这里插入图片描述
测试线程的C代码如下所示。在tskTest.c里创建了一个测试线程,循环读取传感器的值。

/*
*	tskTest.c
*/
#include "tskTest.h"
#include "FreeRTOS.h"
#include "task.h"
#include "stdbool.h"
#include "presSen.h"


static void init(void);

const TskTest_Def tskTest = {
		.init	=	init,
};

#define STACK_SIZE 128
static StaticTask_t TCB_tskTest;
static StackType_t stack_tskTest[STACK_SIZE];
static TaskHandle_t tskTest_handle;
static void tskTest_Entry(void*);

void init(void){
	tskTest_handle = xTaskCreateStatic(
			tskTest_Entry,
			"tskTest",
			STACK_SIZE,
			(void*)0,
			4,
			stack_tskTest,
			&TCB_tskTest
			);
}

void tskTest_Entry(void* p){
	static __USED uint16_t sensorVal;
	while(1){
		sensorVal = presSen.get_SenVal(unit_cmH2O);
		vTaskDelay(pdMS_TO_TICKS(500));
		sensorVal = 0;
	}
}

目前还是把跟这个I2C端口相关的函数封装到一个C结构体下。并且定义了

/*
* 	* presSen.h
*/
#ifndef PRESSEN_INC_PRESSEN_H_
#define PRESSEN_INC_PRESSEN_H_

#include "stdint.h"

typedef struct _PresSen_Def{
	void (*init)(void);
	uint16_t (*get_SenVal)(void);

}PresSen_Def;

extern const PresSen_Def presSen;
#endif /* PRESSEN_INC_PRESSEN_H_ */

在C的源程序文件中,暂时先保留init()函数的C语言实现,虽然为空函数。实例化presSen这个块。用extern关键字将汇编函数引进本头文件,并赋给presSen的get_val成员。

/*
 * presSen.c
 *
 *  Created on: 2022年6月11日
 *      Author: SystemUser
 */

#include "presSen.h"
#include "main.h"
#include "stdbool.h"

/* The address of the sensor is actually 0x28. But the address used in the
 * program */



static void init(void);

/*Using get_SenVal() to call get_SenVal_unit() to make sure the structure preesSen
 * is placed in the .text section by the linker.*/
extern uint16_t asm_Func_presSen_getVal(void);

const PresSen_Def presSen = {
	.init	=	init,
	.get_SenVal	=	asm_Func_presSen_getVal,
};

void init(void){
}

函数运行情况述
可以看出,每次运行sensorVal都能正确的获得传感器的读数。该驱动程序工作正常。

四、总结

可以看出,用汇编可以严格实现手册上规定的寄存器访问时序,节省内存开支,实现高效的驱动。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值