栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

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

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

基于ARM-Cortex M3的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。


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


轮询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段开辟就是了。


.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
	.type asm_Func_presSen_getVal,%function



asm_Func_presSen_getVal:
	push	{r4-r7, lr}


	ldr	 	r4, =I2C2_BaseAddr; 
	ldr  	r3, [r4, #CR1];

Enable_the_I2C2:
	orr 	r3, #I2C_CR1_PE	
	str		r3, [r4, CR1];
Send_a_Start:
	orr		r3, #I2C_CR1_START  
	str		r3, [r4, CR1];

Check_if_the_i2c_start_isSent:
	ldr 	r5, [r4, #SR1];	
	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	
	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里创建了一个测试线程,循环读取传感器的值。

#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结构体下。并且定义了

#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 

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


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





static void init(void);


extern uint16_t asm_Func_presSen_getVal(void);

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

void init(void){
}


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

四、总结

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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/983036.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号