在SensorTile上使用MicroPython(一)
在SensorTile上使用MicroPython(一)
shaoziyang 发表于10个月前
在SensorTile上使用MicroPython(一)
  • 发表于 10个月前
  • 阅读 148
  • 收藏 2
  • 点赞 1
  • 评论 1

腾讯云 技术升级10大核心产品年终让利>>>   

摘要: 介绍在SensorTile上编写传感器驱动的方法

前几天终于将SensorTile传感器部分的MicroPython驱动移植成功,因此就想将传感器的基本使用方法和大家讨论一下,顺便也写写MicroPython的移植方法。去年就有网友希望介绍移植方面的教程,因为各种原因一直没有时间写,正好借这个机会补上了。因为时间较少,一些地方没有仔细检查,如果发现文中有错误的地方,欢迎大家提出。

SensotTile简介

SensorTile核心板非常紧凑小巧,看起来就是一个可穿戴的原型(智能手表),因此它配置的传感器也是和运动相关的。SensorTile核心板上有4个传感器,它们分别是:

  • LPS22HB,气压+温度传感器
  • LSM6DSM,三轴加速度+三轴角速度传感器
  • LSM303AGR,三轴角速度+三轴磁场传感器
  • MP34DT04,MEMS麦克风传感器

我们先从最简单的LPS22HB开始,逐步介绍传感器的使用和移植方法。而MP34DT04传感器这次没有使用到,它的接口也和其他传感器不同,所以暂时先不看。


LPS22HB气压传感器


 

硬件接口

LPS22HB的原理图如上,从上面我们可以看到,传感器使用了SPI连接方式,但是只用了CS、SCL/SPC、SDA/SDI/SDO这几个脚,SDO脚没有使用,说明它没有使用标准的SPI接口方式。此外,还可以看到INT信号也没有连接,所以也就不能使用INT模式了。通常在INT模式下,可以预先设置一个门限参数,当传感器的输出超过这个门限时,就会自动产生一个INT信号,用来唤醒MCU,然后读取并处理参数,这有助于简化编程,降低系统功耗。

从传感器的用户手册中可以看到,传感器支持SPI/I2C两种接口。这两种接口方式是通过CS脚进行切换的,当CS为低电平时是SPI方式,CS是高电平时是I2C方式。SensorTile在硬件设计时,使用了半双工的SPI接口(又叫3-wire模式),这个模式下主机只使用MOSI做数据线,而从机使用MISO。它的好处在于可以节约一个数据线,缺点就是牺牲了速度。


 

因为MicroPython目前不支持半双工的SPI接口方式(硬件SPI和软件SPI都不支持这个方式),因此要用SPI方式驱动传感器就只能自己通过软件模拟这种SPI方式,这不但增加了软件的复杂性,同时速度也会比较慢,所以我采用了I2C接口方式。因为SPI_SDA(PB15)和SPI_CLK(PB13)引脚并不是硬件I2C接口,所以需要用软件I2C方式。好在micropython底层已经支持软件I2C,使用方法和硬件I2C一样,速度也不慢。

这里先介绍一下软件I2C。为了使用软件I2C,我们需要使用到micropython的machine库,而不能使用pyb库。大家可能也注意到了,CC3200、ESP8266、STM32等分支在硬件底层函数接口上有很多不同,这给我们编程和程序移植带来很多不便。从v1.8版开始,micropython开始增强了machine库的功能,这样有助于统一底层接口。软件I2C的使用方法如下:
 

import machine
i2c = machine.I2C(-1, sda=machine.Pin('PB15'), scl=machine.Pin('PB13'))

其中-1就代表使用了软件I2C,sda和scl就是使用的GPIO,可以使用任何GPIO,在SensorTile上就必须使用PB15和PB13。

直接这样定义后,大家会发现I2C还是不能工作,这是因为在SensorTile内部没有设置I2C的上拉电阻,这样I2C总线的状态无法确定,所以我们还需要使能GPIO内部的上拉电阻。注意这个步骤需要放在I2C初始化之后,因为在I2C初始化的时候,会重新设置GPIO状态和参数。直接在I2C定义中的sda=machine.Pin('PB15', pull=Pin.PULL_UP)加入上拉电阻定义也是不能工作的,因为在设置I2C时会忽略这个参数。在I2C定义后,再加入下面的定义,I2C就可以正常工作了,如果这时使用i2c.scan()函数,就可以搜索到4个设备。
 

sda=machine.Pin('PB15', Pin.OPEN_DRAIN, pull=Pin.PULL_UP) 
scl=machine.Pin('PB13', Pin.OPEN_DRAIN, pull=Pin.PULL_UP)



传感器寄存器

ST公司为SensorTile kit提供了多个例程,例程中包含了传感器的底层驱动函数。如果使用C++编程,可以使用这些驱动函数。而我们要使用micropython进行编程,所以无法直接使用ST的底层函数,需要自己对寄存器进行操作(其实ST的底层驱动函数也是对这些寄存器进行操作,只是它已经封装好了,不用再看寄存器说明了)。为了使用LPS22HB,需要对传感器的寄存器有初步的了解。


 

上图是LPS22HB的寄存器列表,它的寄存器不是太多,除去保留的寄存器(Reserved)外,一共大约有二十几个。每个寄存器都有一个地址,寄存器输出是8位的,大部分寄存器可以读写(RW),少量寄存器是只读的(R)。如果按照功能进行划分,传感器的寄存器大致可以分为下面几类:

  • 功能设置
  • 传感器状态
  • 参数输出

功能设置寄存器可以设置传感器的工作模式、参数输出频率、参数范围、中断等参数,只有设置了正确的参数后,传感器才能工作。在上电/复位后,我们也需要先设置功能寄存器(初始化),否则传感器是没有输出的,因为默认情况下传感器是处于掉电模式(Power down)。

状态寄存器通常是只读的,可以通过它查询传感器当前的状态或者某种标志位。特别在中断工作模式下,需要通过状态寄存器查询发生的中断。

参数输出就是传感器的输出,如气压、温度等参数。很多参数使用了双字节甚至更多字节,需要读取后在组合起来。
因为寄存器较多,所以我们只介绍主要使用到的传感器,其他传感器大家可以慢慢研究(寄存器说明请见LPS22HB数据手册的第9节:Register description)。


  • 设备识别寄存器:WHO_AM_I (0Fh)

可以用来识别芯片的型号。这个寄存器是只读的,输出是 0xB1,也就是十进制的177。


  • 控制寄存器:CTRL_REG1 (10h)

这是最重要的一个寄存器,主要参数都在这里设置

7
        
6
        
5
        
4
        
3
        
2
        
1
        
0
        
0
        
ODR2
        
ODR1
        
ODR0
        
EN_LPFP
        
LPFP_CFG
        
BDU
        
SIM
        


ODR代表采样频率,当ODR=0时,传感器进入掉电模式,设置成其它参数时,传感器就按照指定频率开始采样。
 

EN_LPFP代表使用内部低通滤波器,默认是关闭的。
LPFP_CFG是低通滤波器带宽设置,它需要和EN_LPFP配合使用。
BDU是Block data update的缩小,它代表只有读取输出数据后才更新寄存器
SIM是选择3线/4线SPI方式。


  • 气压寄存器

气压参数由三个寄存器组成,分别是PRESS_OUT_XL (28h)、PRESS_OUT_L (29h)、PRESS_OUT_H (2Ah)

 

气压的计算方法是

    气压 = PRESS_OUT_H·PRESS_OUT_L·PRESS_OUT_XL / 4096

就是将三个寄存器的值组合起来,然后除以4096。如果精度要求不高,也可以只取PRESS_OUT_H和PRESS_OUT_L,然后除以16。气压传感器的精度是±0.1hPa,所以保留一位小数就可以了。

如果希望通过气压计算高度,通常是用查表计算。不过因为气压容易受到温度、湿度、风力等多个条件影响,通过气压计算绝对高度的误差较大,所以通常是测量相对高度(高度变化)。


  • 温度寄存器

温度参数由两个寄存器组成:
 

温度的计算方法是:

    温度 = TEMP_OUT_H·TEMP_OUT_L / 100

考虑到低于0°时是负数,所以需要将这个参数做为有符号数处理。温度传感器的精度是±1.5℃。

如果没有特殊要求,使用上述几个寄存器就可以实现基本的数据采集功能。如果希望进一步降低功耗、改变模式、使用中断、使用FIFO、使用参考值等功能,还需要进一步研究其它寄存器才行。


LPS22HB 的 Micropython 程序移植

前面介绍了传感器的接口、主要寄存器、参数计算等方面的内容,下面就介绍用MicroPython驱动LPS22HB的方法。

为了让程序具有通用性,以及系统模块化的要求,我们将为 LPS22HB 单独建立一个 Module,这样也方便其它程序使用。python语言中,一个module和C++的子程序差不多,里面可以包含多个对象(class),每个对象提供一系列函数或方法。但是python语言没有C++那么复杂,也不是面向对象的语言,使用起来简单得多。

一个典型的mudule的结构如下,它由若干class组成,每个class下又由多个函数组成。其中比较特殊的是__init__()函数,它类似C++的构造函数初始化,在定义class变量后就会自动调用__init__()函数,默认需要进行初始化的内容都放在这个函数中。此外,class下的每个函数在定义时的默认第一个参数都是self,但是调用时并不需要使用它,self参数由python系统内部使用。更多关于python语法部分的内容,请大家参考python教程或者参考书,这里就不重复了。

 

对于LPS22HB传感器,我们先定义一个基本的LPS22HB类:
 

class LPS22HB(object): 
    def __init__(self): 
        xxxx 
         
    def func1(): 
        xxxx 
         
    def func2(): 
        xxxx 



然后再将初始化、其它功能函数逐步添加进去,最后就是一个完整的驱动了。

首先需要添加的就是初始化部分,在__init__()函数中,先添加GPIO部分,将CS的GPIO设置为输出,并设置为高电平,这样I2C才能正常工作:

        # set CS high 
        CS_LPS22HB = Pin(LPS22HB_CS_PIN, Pin.OUT) 
        CS_LPS22HB(1) 
        CS_AG = Pin(LSM6DSM_CS_PIN, Pin.OUT) 
        CS_AG(1) 
        CS_A = Pin(LSM303AGR_CS_A_PIN, Pin.OUT) 
        CS_A(1) 
        CS_M = Pin(LSM303AGR_CS_M_PIN, Pin.OUT) 
        CS_M(1)


然后再添加I2C初始化部分的代码:

        # soft I2C 
        self.i2c = machine.I2C(-1, sda=machine.Pin('PB15'), scl=machine.Pin('PB13')) 
        # set open drain and pull up 
        sda=machine.Pin('PB15', Pin.OPEN_DRAIN, pull=Pin.PULL_UP) 
        scl=machine.Pin('PB13', Pin.OPEN_DRAIN, pull=Pin.PULL_UP)


再设置LPS22HB的CTRL1_REG寄存器,让LPS22HB默认处于工作模式:

        # start LPS22HB 
        self.setreg(0x18, LPS22HB_CTRL_REG1, LPS22HB_ADDRESS) 
        self.temp0 = 0 
        self.press = 0 
        self.LPS22HB_ON = True


self.temp0、self.press、self.LPS22HB_ON是内部变量,用于后面的参数计算和状态设置。它们不是必须的,这里定义它们主要是为了方便同一模块下其它函数调用。

初始化部分完成后,就是添加其它功能函数了。大家可以发现在上面的初始化部分我们使用了一个设置寄存器的函数,因为设置和读取寄存器是一个通用性的操作,所以我们将寄存器的操作也设置成函数,这样也方便将底层和应用层分离。为了方便读取参数,我们还设置了一个读取两个相邻寄存器的函数get2reg,这个函数没有使用传感器自动递增寄存器地址的功能,是因为在传感器的BUD模式下,地址自动递增的功能是无效的,为了让程序有更好的兼容性,所以稍微牺牲了一点性能。

    def setreg(self, dat, reg, addr): 
        buf = bytearray(2) 
        buf[0] = reg 
        buf[1] = dat 
        self.i2c.writeto(addr, buf) 

    def getreg(self, reg, addr): 
        buf = bytearray(1) 
        buf[0] = reg 
        self.i2c.writeto(addr, buf) 
        t = self.i2c.readfrom(addr, 1) 
        return t[0] 

    def get2reg(self, reg, addr): 
        l = self.getreg(reg, addr) 
        h = self.getreg(reg+1, addr) 
        return l+h*256


为了增加程序的可读性和可维护下,我们将寄存器的名称定义为常量,并且将它放在class的前面,这类似于C语言中的#define。寄存器名称前面还加上LPS22HB前缀,这样可以在一个Module中存在多个芯片定义时防止和其它芯片的定义相冲突。

# LPS22HB register 
LPS22HB_INTERRUPT_CFG= const(0x0B) 
LPS22HB_THS_P_L      = const(0x0C) 
LPS22HB_THS_P_H      = const(0x0D) 
LPS22HB_WHO_AM_I     = const(0x0F) 
LPS22HB_CTRL_REG1    = const(0x10) 
LPS22HB_CTRL_REG2    = const(0x11) 
LPS22HB_CTRL_REG3    = const(0x12) 
LPS22HB_FIFO_CTRL    = const(0x14) 
LPS22HB_REF_P_XL     = const(0x15) 
LPS22HB_REF_P_L      = const(0x16) 
LPS22HB_REF_P_H      = const(0x17) 
LPS22HB_RPDS_L       = const(0x18) 
LPS22HB_RPDS_H       = const(0x19) 
LPS22HB_RES_CONF     = const(0x1A) 
LPS22HB_INT_SOURCE   = const(0x25) 
LPS22HB_FIFO_STATUS  = const(0x26) 
LPS22HB_STATUS       = const(0x27) 
LPS22HB_PRESS_OUT_XL = const(0x28) 
LPS22HB_PRESS_OUT_L  = const(0x29) 
LPS22HB_PRESS_OUT_H  = const(0x2A) 
LPS22HB_TEMP_OUT_L   = const(0x2B) 
LPS22HB_TEMP_OUT_H   = const(0x2C) 
LPS22HB_LPFP_RES     = const(0x33)


前面的寄存器操作、初始化等可以看成是准备工作,准备工作完成了,就是具体传感器的操作了。我们使用传感器最重要的目的就是需要获得传感器的参数,因此再定义两个函数,一个用于获取气压,一个获取温度。

    def LPS22HB_temp(self): 
        self.temp0 = self.get2reg(LPS22HB_TEMP_OUT_L, LPS22HB_ADDRESS) 
        if(self.temp0 > 0x7FFF): 
            self.temp0 -= 65536 
        return self.temp0/100 

    def LPS22HB_press(self): 
        self.press = self.getreg(LPS22HB_PRESS_OUT_XL, LPS22HB_ADDRESS) 
        self.press += self.get2reg(LPS22HB_PRESS_OUT_L, LPS22HB_ADDRESS) * 256 
        return self.press/4096



气压函数是先读取三个寄存器的参数,然后将结果除以4096,这就是按照前面介绍的计算方法进行换算的。而温度函数稍微麻烦一点,因为存在负数的问题。在python语言中不像C语言那样可以自动进行类型转换,寄存器的参数不能直接转换为负数,所以需要自己判断和转换。因为这里是一个双字节的数据,最高位就是符号位,因此如果数据大于0x7FFF或者最高位是1,那么就认为它是负数。

另外在一些情况下,需要同时读取温度和气压两个数据,所以我们可以将两个参数放到一个函数中,通过一个列表返回。这里可以直接将前面两个函数放在return的列表中。

    def LPS22HB(self): 
        return [self.LPS22HB_temp(), self.LPS22HB_press()]


最后,为了降低功耗,还需要让传感器可以进入掉电模式,因此我们还需要增加两个功耗管理函数:

    def LPS22HB_poweron(self): 
        t = self.getreg(LPS22HB_CTRL_REG1, LPS22HB_ADDRESS) & 0x0F 
        self.setreg(t|0x10, LPS22HB_CTRL_REG1, LPS22HB_ADDRESS) 
        self.LPS22HB_ON = True 

    def LPS22HB_poweroff(self): 
        t = self.getreg(LPS22HB_CTRL_REG1, LPS22HB_ADDRESS) & 0x0F 
        self.setreg(t, LPS22HB_CTRL_REG1, LPS22HB_ADDRESS) 
        self.LPS22HB_ON = False



在掉电模式下(Power down),传感器的最低功耗是1uA。其实LPS22HB的功耗也不高,在ODR和LC_EN都是1时也只有3uA。

完成上面的工作后,我们就实现了一个最基本的LPS22HB驱动。我们可以把它保存到一个LPS22HB.py文件中,然后用下面的方法使用它:

>>> from LPS22HB import LPS22HB
>>> lp=LPS22HB()
>>> lp.LPS22HB_temp()
16.17
>>> lp.LPS22HB_press()
1025.827
>>> lp.LPS22HB()
[16.14, 1025.839]


如果用dir(LPS22HB),可以查看全部的函数

>>> dir(LPS22HB)
['__qualname__', 'LPS22HB_poweron', '__module__', 'LPS22HB_press', 'LPS22HB_temp', 'LPS22HB', 'getreg', 'setreg', 'get2reg', 'LPS22HB_poweroff', '__init__']

如果想进一步完善LPS22HB驱动,增加功能,使用中断等,大家可以在此基础上进行改进。


附:

共有 人打赏支持
粉丝 12
博文 75
码字总数 25881
评论 (1)
clouddyy
牛了!
×
shaoziyang
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: