Openmv平台的HMC5883l电子罗盘功能实现

478 阅读3分钟

21-05-22:

前言:最近课业十分紧张所以内容显得十分凌乱,注释也没来的及补充,还请多见谅。

博主现在在大三下学期,学校课设是一个很变态的智能小车项目,老师ban了树莓派,Arduino, K210这种算力比较高且发展比较完善的平台。所以不得不使用Openmv踩坑。

Openmv在二进制运算和二进制十进制转换方面做的让人十分想吐槽,大概本来也就不是用来干其他事情的?

先附上参考的代码链接,是树莓派上实现的链接,本人做了少许移植和后期参数校正。 1.bitbucket.org/thinkbowl/i… 2.bitbucket.org/thinkbowl/i…

代码由本人编辑的主要为class类中的getxes()和bin2int(),其余内容基本照搬,仅仅修改了I2C的库函数。后面对X和Y进行了offset修正以适应本地地理磁场环境。

HMC5883l参考手册强烈推荐这个版本(比大部分能找到的手册都要齐全): www.digikey.com/en/datashee…

另外HMC5883l在IIC上遇到的很奇怪的bug是只能使用0x1E作为设备地址,文档里的0x3C和0x3D似乎没什么用。

最终结果和实际手机指南针结果差距基本在10°以内,对于大部分角度都是1°的误差。

import math
from pyb import I2C, Pin, Timer
from time import *

class i2c_hmc5883l:
    ConfigurationRegisterA = 0x00
    ConfigurationRegisterB = 0x01
    ModeRegister = 0x02
    AxisXDataRegisterMSB = 0x03
    AxisXDataRegisterLSB = 0x04
    AxisZDataRegisterMSB = 0x05
    AxisZDataRegisterLSB = 0x06
    AxisYDataRegisterMSB = 0x07
    AxisYDataRegisterLSB = 0x08
    StatusRegister = 0x09
    IdentificationRegisterA = 0x10
    IdentificationRegisterB = 0x11
    IdentificationRegisterC = 0x12


    MeasurementContinuous = 0x00
    MeasurementSingleShot = 0x01
    MeasurementIdle = 0x03

    def __init__(self, gauss=1.3):
    #def __init__(self, port, addr=0x1e, gauss=1.3):
        #self.bus = i2c.i2c(port, addr)
        self.bus = I2C(2, I2C.MASTER)

        self.setScale(gauss)

    def __str__(self):
        ret_str = ""
        (x, y, z) = self.getAxes()
        ret_str += "Axis X: "+str(x)+"\n"
        ret_str += "Axis Y: "+str(y)+"\n"
        ret_str += "Axis Z: "+str(z)+"\n"

        ret_str += "Declination: "+self.getDeclinationString()+"\n"

        ret_str += "Heading: "+self.getHeadingString()+"\n"

        return ret_str



    def setContinuousMode(self):
        self.setOption(self.ModeRegister, self.MeasurementContinuous)

    def setScale(self, gauss):
        if gauss == 0.88:
            self.scale_reg = 0x00
            self.scale = 0.73
        elif gauss == 1.3:
            self.scale_reg = 0x01
            self.scale = 0.92
        elif gauss == 1.9:
            self.scale_reg = 0x02
            self.scale = 1.22
        elif gauss == 2.5:
            self.scale_reg = 0x03
            self.scale = 1.52
        elif gauss == 4.0:
            self.scale_reg = 0x04
            self.scale = 2.27
        elif gauss == 4.7:
            self.scale_reg = 0x05
            self.scale = 2.56
        elif gauss == 5.6:
            self.scale_reg = 0x06
            self.scale = 3.03
        elif gauss == 8.1:
            self.scale_reg = 0x07
            self.scale = 4.35

        self.scale_reg = self.scale_reg << 5
        self.setOption(self.ConfigurationRegisterB, self.scale_reg)

    def setDeclination(self, degree, min = 0):
        self.declinationDeg = degree
        self.declinationMin = min
        self.declination = (degree+min/60) * (math.pi/180)

    def setOption(self, register, *function_set):
        options = 0x00
        for function in function_set:
            options = options | function
        #self.bus.write_byte(register, options)
        self.bus.mem_write(addr = 0x1E, memaddr = register, data = options)

    def getDeclination(self):
        return (self.declinationDeg, self.declinationMin)

    def getDeclinationString(self):
        return str(self.declinationDeg)+"\u00b0 "+str(self.declinationMin)+"'"

    # Returns heading in degrees and minutes
    def getHeading(self):
        (scaled_x, scaled_y, scaled_z) = self.getAxes()

        headingRad = math.atan2(scaled_y, scaled_x)
        headingRad += self.declination

        # Correct for reversed heading
        if(headingRad < 0):
            headingRad += 2*math.pi

        # Check for wrap and compensate
        if(headingRad > 2*math.pi):
            headingRad -= 2*math.pi

        # Convert to degrees from radians
        headingDeg = headingRad * 180/math.pi
        degrees = math.floor(headingDeg)
        minutes = round(((headingDeg - degrees) * 60))
        return (degrees, minutes)

    def getHeadingString(self):
        (degrees, minutes) = self.getHeading()
        return str(degrees)+"\u00b0 "+str(minutes)+"'"

    def getAxes(self):
        (magno_x, magno_z,magno_y) = self.bus.mem_read(data=self.AxisXDataRegisterMSB, addr = 0x30)

        if (magno_x == -4096):
            magno_x = None
        else:
            magno_x = round(magno_x * self.scale, 4)

        if (magno_y == -4096):
            magno_y = None
        else:
            magno_y = round(magno_y * self.scale, 4)

        if (magno_z == -4096):
            magno_z = None
        else:
            magno_z = round(magno_z * self.scale, 4)

        return (magno_x, magno_y, magno_z)

    def getxes(self):
        m = self.bus.mem_read(memaddr=0x03, addr = 0x1E, data=6)
        #print(m[1]))
        #print(self.bus.mem_read(memaddr=0x03, addr = 0x1E, data=1))
        #print(self.bus.mem_read(memaddr=0x04, addr = 0x1E, data=1))
        rawDataArray=[]
        dataArray = []
        for item in m:
            rawDataArray.append(bin(item))
        #for item in m:
            #dataArray.append(int(item))
        for index in range(3):
            resultValue = self.bin2int(rawDataArray[index*2], rawDataArray[index*2+1])
            dataArray.append(resultValue)
        #print(len(rawDataArray[2]), '\t\t\t', rawDataArray[2])
        #print('x\t',dataArray[0]+104, 'y\t',dataArray[2]+151)
        #print('y\t',dataArray[2])


#此处的104和151是根据实际测试环境添加的offset
        heading = math.atan2(dataArray[2]+104,dataArray[0]+151)
        heading = heading/0.0174532925
        if(heading <0):
            heading = heading + 360
            heading= 360-heading

        print(heading)
        #print(self.bus.is_ready(0x1E))

#bin2int进行二进制补码转十进制操作,由于Openmv缺少移位等操作这个过程显得异常漫长。。
    def bin2int(self, bin1, bin2):
        dataBin1 = ''
        dataBin2 = ''
        dataList = []
        lenBin1 = len(bin1)
        lenBin2 = len(bin2)
        #print('bin1:', bin1, '\n', 'bin2:', bin2 )
        if lenBin1 <10:
            dataBin1 = dataBin1 + (10 - lenBin1)*'0'
        if lenBin2 <10:
            dataBin2 = dataBin2 + (10 - lenBin2)*'0'
        for i in range(2, lenBin1):
            dataBin1 = dataBin1 + bin1[i]
        for i in range(2, lenBin2):
            dataBin2 = dataBin2 + bin2[i]
        data = dataBin1 + dataBin2
        if data[0] == '0':
            data = '0b' + data
            #print('data:', data)
            output = int(data)
        else:
            for num in data:
                if num == '0':
                    dataList.append('1')
                else:
                    dataList.append('0')
            data = ''.join(dataList)
            #print('data:', data)
            output = 0 - int('0b' + data) - 1
        return output


a = i2c_hmc5883l()
a.setScale(gauss=0.88)
a.setContinuousMode()
while(1):
    a.getxes()