AS5600 magnetic rotary sensor

General discussions and questions abound development of code with MicroPython that is not hardware specific.
Target audience: MicroPython Users.
Post Reply
sgall17
Posts: 8
Joined: Sun Jan 31, 2021 9:55 pm

AS5600 magnetic rotary sensor

Post by sgall17 » Fri Jul 16, 2021 11:48 pm

Hi,

I am new to github, and writing i2c drivers so I need some help.

I am writing a driver for the AS5600. I have made all the registers and bitfields in data sheet attributes of a class.
There is an early example of a higher level class that inherits from above, with a simple useage example.

I am using a Raspberry Pi Pico with
Version of micropython rp2-pico-20210704-unstable-v1.16-52-g0e11966ce.uf2
Programming on Thonny.
The board is a Seeedstudio board with Grove connnection.
Github:- https://github.com/sgall17a/AS5600/rele ... v0.1-alpha

There are some problems (relating to i2c I think ) that I need help with:

1. Reading ANGLE and RAWANGLE seem to work OK.
2. CONF (configure register) is writable but I dont understand its behaviour in my program.
If I write a value (any value) and read immediately it comes back zero.
If I power on and off the value written is there.
If I read and print the value and rewrite and reread without repowering it does not change.

4. I am using readto_mem and write_to mem I2C methods. Could these be the problem?

5. The data sheet sheet says that address autoincrement on reading (for most registers). Could this be the problem?

6. Problem 2.
The STATUS register does not seem to recognise a magnet even though it will read angles OK, so it must be seeing a magnet.

Is it OK to post blocks of code here or just refer to the github source?

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: AS5600 magnetic rotary sensor

Post by pythoncoder » Sat Jul 17, 2021 1:29 pm

I notice your code uses descriptors. In this official doc there is a caveat about using descriptors:
MicroPython design is not based around descriptor objects. So far, we were able to implement all native Python features (like properties) without explicit descriptors. Descriptors are considered "overdynamic" feature, and conflicts with the aim of being fast and efficient. However, basic support for descriptors in user-defined classes is now implemented.
This also extends to properties. These work, but are deprecated in applications requiring high performance.

However I don't think that is the cause of your problem and I'm afraid I don't have an answer to that one.

Your use of GitHub isn't optimal: it's best to post your source code directly rather than in a zip file, as then you get the benefit of Git version control.

You're welcome to post code or GitHub links on this forum.
Peter Hinch
Index to my micropython libraries.

sgall17
Posts: 8
Joined: Sun Jan 31, 2021 9:55 pm

Re: AS5600 magnetic rotary sensor

Post by sgall17 » Sat Jul 17, 2021 9:24 pm

Thanks Pythoncoder,

Why I liked descriptors in this setting:
1. I think Attributes are simple and intuitive use and avoid exposing separate getter and setter functions to the user.
2. I tried using properties but these (at least in my implementation) where much more verbose and actually harder to program.
3. Descriptors permit "boilerplate" like code which is easy to follow.
4. They may well be a bit slower but who cares for things like configuration?
5. For something like reading a whole lot of angles quickly you can still write a dedicated "fast read".
6. I dont really know about the internal implications of descriptors but wouldn't be less verbose also mean smaller and more space efficient?

My problem was easily fixed (after a short walk in the brisk winter air), I was not writing back to my cache properly.
I also realised that the cache should belong to the main class rather than the descriptor
so that different descriptors could share the value.

In hindsight I think nametuples were silly. I was just trying them out. I think just a series of micropython constants would be better.

Code: Select all


from machine import I2C,Pin
from micropython import const
from ustruct import unpack, pack
from collections import namedtuple
from time import sleep

AS5600_id = const(0x36)  #Device ID
m12 = const((1<<12)-1)  #0xFFF

REGS=namedtuple('REGS','ZMCO ZPOS MPOS MANG CONF RAWANGLE ANGLE  STATUS AGC MAGNITUDE BURN')
r = REGS(0,1,3,5,7,0xc,0xe,0x0b,0x1a,0xb,0xff)
    
#You cant overwrite __attribute__ in micropython but you can use Descriptors

class RegDescriptor:
    "Read and write a bit field from a register"
    
    def __init__(self,reg,shift,mask,buffsize=2):
        "initialise with specific identifiers for the bit field"

        self.reg = reg
        self.shift = shift
        self.mask = mask
        self.buffsize = buffsize
        self.writeable = (r.ZMCO,r.ZPOS,r.MPOS,r.MANG,r.CONF,r.BURN)
        #NB the I2c object and the device name come from the main class via an object
        
    def get_register(self,obj):
        "Read an I2C register"
        #cache those registers with values that will not change.
        #Dont bother caching bit fields.
        if self.reg in obj.cache:  
             return obj.cache[self.reg]
            
        #print ('reading now the actual device now')
            
        buff = obj.i2c.readfrom_mem(obj.device,self.reg,self.buffsize)
  

        if self.buffsize == 2:
            v = unpack(">H",buff)[0]  #2 bytes big endian
        else:
            v = unpack(">B",buff)[0]
        
            
        #cache writeable values since they are the ones that will not change in useage    
        if self.reg in self.writeable:
            obj.cache[self.reg] = v
            
        return v
        
    def __get__(self,obj,objtype):
        "Get the register then extract the bit field"
        v = self.get_register(obj)
        v >>= self.shift
        v &= self.mask
        return v
    
    def __set__(self,obj,value):
        "Insert a new value into the bit field of the old value then write it back"
        if not self.reg in self.writeable:
            raise AttributeError('Register is not writable')
        oldvalue = self.get_register(obj)
        #oldvalue <<= self.shift # get_register() does a shift, so we have to shift it back
        insertmask = 0xffff - (self.mask << self.shift) #make a mask for a hole
        oldvalue &= insertmask # AND a hole in the old value
        value &= self.mask # mask our new value in case it is too big
        value <<= self.shift
        oldvalue |= value  # OR the new value back into the hole
        if self.buffsize == 2:
            buff = pack(">H",oldvalue)
        else:
            buff = pack(">B",oldvalue)
            
        obj.i2c.writeto_mem(obj.device,self.reg,buff) # write result back to the AS5600
        
        #must write the new value into the cache
        self.cache[self.reg] = oldvalue
        
  

class AS5600:
    def __init__(self,i2c,device):
        self.i2c = i2c
        self.device = device
        self.writeable =(r.ZMCO,r.ZPOS,r.MPOS,r.MANG,r.CONF,r.BURN)
        self.cache = {} #cache register values
        
    #Use descriptors to read and write a bit field from a register
    #1. we read one or two bytes from i2c
    #2. We shift the value so that the least significant bit is bit zero
    #3. We mask off the bits required  (most values are 12 bits hence m12)
    ZMCO=      RegDescriptor(r.ZMCO,shift=0,mask=3,buffsize=1) #1 bit
    ZPOS=      RegDescriptor(r.ZPOS,0,m12) #zero position
    MPOS=      RegDescriptor(r.MPOS,0,m12) #maximum position
    MANG=      RegDescriptor(r.MANG,0,m12) #maximum angle (alternative to above)
    #Dummy example how how to make friendlier duplicate names if you want to
    #max_angle = RegDescriptor(r.MANG,0,m12) #maximum angle (alternative to above)
    CONF=      RegDescriptor(r.CONF,0,(1<<14)-1) # this register has 14 bits (see below)
    RAWANGLE=  RegDescriptor(r.RAWANGLE,0,m12) 
    ANGLE   =  RegDescriptor(r.ANGLE,0,m12) #angle with various adjustments (see datasheet)
    STATUS=    RegDescriptor(r.STATUS,0,m12) #basically strength of magnet
    AGC=       RegDescriptor(r.AGC,0,0xF,1) #automatic gain control
    MAGNITUDE= RegDescriptor(r.MAGNITUDE,0,m12) #? something to do with the CORDIC for atan RTFM
    BURN=      RegDescriptor(r.BURN,0,0xF,1)

    #Configuration bit fields
    PM =      RegDescriptor(r.CONF,0,0x3) #2bits Power mode
    HYST =    RegDescriptor(r.CONF,2,0x3) # hysteresis for smoothing out zero crossing
    OUTS =    RegDescriptor(r.CONF,4,0x3) # HARDWARE output stage ie analog (low,high)  or PWM
    PWMF =    RegDescriptor(r.CONF,6,0x3) #pwm frequency
    SF =      RegDescriptor(r.CONF,8,0x3) #slow filter (?filters glitches harder) RTFM
    FTH =     RegDescriptor(r.CONF,10,0x7) #3 bits fast filter threshold. RTFM
    WD =      RegDescriptor(r.CONF,13,0x1) #1 bit watch dog - Kicks into low power mode if nothing changes
    
    #status bit fields. ?having problems getting these to make sense
    MH =      RegDescriptor(r.STATUS,3,0x1) #2bits  Magnet too strong (high)
    ML =      RegDescriptor(r.STATUS,4,0x1) #2bits  Magnet too weak (low)
    MD =      RegDescriptor(r.STATUS,5,0x1) #2bits  Magnet detected
    
    #Higher level stuff follows
    
    def scan(self):
        "Debug utility function to check your i2c bus"
        devices = self.i2c.scan()
        print(devices)
        if AS5600_id in devices:
            print('Found AS5600 (id =',hex(AS5600_id),')')
        print(self.CONF)
        
    def burn_angle(self):
        "Burn ZPOS and MPOS -(can only do this 3 times)"
        self.BURN = 0x80
        
    def burn_setting(self):
        "Burn config and mang- (can only do this once)"
        self.BURN = 0x40
    
    def magnet_status(self):
        s = "Magnet "
       # print(self.MD)
        return
        if self.MD == 1:
            s += " detected"
        else:
            s += " not detected"
        
            
        if self.ML == 1:
            s+ " (magnet too weak)"
            
        if self.MH == 1:
            s+ " (magnet too strong)"
        return s
    

sgall17
Posts: 8
Joined: Sun Jan 31, 2021 9:55 pm

Re: AS5600 magnetic rotary sensor

Post by sgall17 » Sun Jul 18, 2021 1:05 am

From the cPython documentation.

"Descriptors are used throughout the language. It is how functions turn into bound methods. Common tools like classmethod(), staticmethod(), property(), and functools.cached_property() are all implemented as descriptors."

I got the impression from this that descriptors is a fairly standard way of doing things. Is this not the case? Are things different in Micropython?

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: AS5600 magnetic rotary sensor

Post by pythoncoder » Sun Jul 18, 2021 6:23 am

I am no expert on the internal implementation of MicroPython. The quote I posted above seems to state that MicroPython does not use descriptors internally. As you point out, CPython does, extensively.

I take your point about performance. I just thought it worth pointing out the MicroPython convention of using bound methods rather than properties or descriptors. If you look at the official libraries you will see that this is adopted throughout. However Adafruit take the opposite approach and use descriptors extensively. It is entirely your choice. ;)

Constants are a great way to improve performance. You can also reduce RAM consumption by prepending names with an underscore.

Code: Select all

_MY_LOCAL_VALUE = const(42)  # Save 4 bytes
Peter Hinch
Index to my micropython libraries.

sgall17
Posts: 8
Joined: Sun Jan 31, 2021 9:55 pm

Re: AS5600 magnetic rotary sensor

Post by sgall17 » Sun Jul 18, 2021 10:47 am

Thanks - I will go through some other drivers and revise mine.

Post Reply