Accessing Registers with ctypes

General discussions and questions abound development of code with MicroPython that is not hardware specific.
Target audience: MicroPython Users.
Post Reply
Scottie
Posts: 2
Joined: Tue Jul 06, 2021 11:48 pm

Accessing Registers with ctypes

Post by Scottie » Wed Jul 07, 2021 12:17 am

Hello,

I am essentially brand new to MicroPython and am hoping to get some feedback on how to best implement a register interface.

I took a first pass attempt using bit-fields in ctypes. Here is some sample code for the ADC registers on the RPI Pico:

Code: Select all

import ctypes
from machine import mem32

ADC_BASE = 0x4004c000

# Struct layout for the ADC on the RP2040 IC
ADC_BITS = {
"CS": (0x00,{
    "EN": 0 << ctypes.BF_POS | 1 << ctypes.BF_LEN | ctypes.BFUINT32,
    "TS_EN": 1 << ctypes.BF_POS | 1 << ctypes.BF_LEN | ctypes.BFUINT32,
    "START_ONCE": 2 << ctypes.BF_POS | 1 << ctypes.BF_LEN | ctypes.BFUINT32,
    "START_MANY": 3 << ctypes.BF_POS | 1 << ctypes.BF_LEN | ctypes.BFUINT32,
    "READY": 8 << ctypes.BF_POS | 1 << ctypes.BF_LEN | ctypes.BFUINT32,
    "ERR": 9 << ctypes.BF_POS | 1 << ctypes.BF_LEN | ctypes.BFUINT32,
    "ERR_STICKY": 10 << ctypes.BF_POS | 1 << ctypes.BF_LEN | ctypes.BFUINT32,
    "AINSEL": 12 << ctypes.BF_POS | 3 << ctypes.BF_LEN | ctypes.BFUINT32,
    "RROBIN": 16 << ctypes.BF_POS | 5 << ctypes.BF_LEN | ctypes.BFUINT32,
    }),
"RESULT": (0x04,{ "VAL" : 12 << ctypes.BF_LEN | ctypes.BFUINT32, }),
"FCS": (0x08,{
    "EN": 0 << ctypes.BF_POS | 1 << ctypes.BF_LEN | ctypes.BFUINT32,
    "SHIFT": 1 << ctypes.BF_POS | 1 << ctypes.BF_LEN | ctypes.BFUINT32,
    "ERR": 2 << ctypes.BF_POS | 1 << ctypes.BF_LEN | ctypes.BFUINT32,
    "DREQ_EN": 3 << ctypes.BF_POS | 1 << ctypes.BF_LEN | ctypes.BFUINT32,    
    "EMPTY": 8 << ctypes.BF_POS | 1 << ctypes.BF_LEN | ctypes.BFUINT32,
    "FULL": 9 << ctypes.BF_POS | 1 << ctypes.BF_LEN | ctypes.BFUINT32,
    "UNDER": 10 << ctypes.BF_POS | 1 << ctypes.BF_LEN | ctypes.BFUINT32,
    "OVER": 11 << ctypes.BF_POS | 1 << ctypes.BF_LEN | ctypes.BFUINT32,
    "LEVEL": 16 << ctypes.BF_POS | 4 << ctypes.BF_LEN | ctypes.BFUINT32,
    "THRESH": 24 << ctypes.BF_POS | 4 << ctypes.BF_LEN | ctypes.BFUINT32,
    }),
"FIFO": (0x0C,{
    "VAL": 0 << ctypes.BF_POS | 12 << ctypes.BF_LEN | ctypes.BFUINT32,
    "ERR": 15 << ctypes.BF_POS | 1 << ctypes.BF_LEN | ctypes.BFUINT32,
    }),
"DIV": (0x10,{
    "FRAC": 0 << ctypes.BF_POS | 8 << ctypes.BF_LEN | ctypes.BFUINT32,
    "INT": 8 << ctypes.BF_POS | 16 << ctypes.BF_LEN | ctypes.BFUINT32,
    }),
}


ADC = ctypes.struct(ADC_BASE,ADC_BITS)


# Some basic bit field settings
print('Set ADC DIV Reg to 0')

ADC.DIV.INT = 0
ADC.DIV.FRAC = 0

print("ADC DIV REG: 0x{:08X}".format(mem32[ADC_BASE+0x10]))

print('The fraction bit field is bits 0:7')
print('Set DIV.FRAC to 0x35')

ADC.DIV.FRAC = 0x35

print("ADC DIV REG: 0x{:08X}".format(mem32[ADC_BASE+0x10]))

print('The integer bit field is bits 8:23')
print('Set DIV.INT to 0x4321')

ADC.DIV.INT = 0x4321

print("ADC DIV REG: 0x{:08X}".format(mem32[ADC_BASE+0x10]))
As I only have a handful of hours experience with MicroPython any critique or suggestions are welcome :).

I don't know if I am doing something wrong or if this is the expected behavior, but I get an error trying to dir(ADC)
>>> type(ADC)
<class 'struct'>
>>> print(ADC)
<struct STRUCT 4004c000>
>>> dir(ADC)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: __dir__
>>>
I suppose because there is no dir function for the struct the autocomplete fails in Thonny?
>>> ADC.**press tab**
NB! Thonny could not execute 'shell_autocomplete'.


SCRIPT:
__thonny_helper.print_mgmt_value(dir(ADC) if 'ADC' in locals() or 'ADC' in globals() else [])

STDOUT:


STDERR:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: __dir__


You may need to reconnect, Stop/Restart or hard-reset your device.
Thank you for your help

User avatar
jimmo
Posts: 2754
Joined: Tue Aug 08, 2017 1:57 am
Location: Sydney, Australia
Contact:

Re: Accessing Registers with ctypes

Post by jimmo » Wed Jul 07, 2021 12:49 am

Scottie wrote:
Wed Jul 07, 2021 12:17 am
I don't know if I am doing something wrong or if this is the expected behavior, but I get an error trying to dir(ADC)
This is a bit of an interesting edge case... I think __dir__ for builtins (that don't explicitly implement __dir__) works by iterating the locals dict... and a uctypes struct won't have a locals dict. And (probably for space reasons) it doesn't implement __dir__ either.
Scottie wrote:
Wed Jul 07, 2021 12:17 am
I suppose because there is no dir function for the struct the autocomplete fails in Thonny?
I don't know much about Thonny, but it sounds like Thonny is doing it's own auto-completion outside of the MicroPython repl?? (interesting! how does this work??). I vaguely remember that at some stage Thonny was line buffered, perhaps this is why they had to do it this way.

(Fun fact, the way tab completion works in MicroPython is that rather than asking the object what properties/attributes it has, it attempts to query all known strings on that object... this works because MicroPython does string interning, so there's a big pool of them)

FWIW, at the "regular" REPL (e.g. using mpremote or any other serial program), tab completion works fine on the ADC object.

Code: Select all

>>> rpi_ctypes.ADC.**tab**
CS              RESULT          FCS             FIFO
DIV
I haven't seen anyone do this approach for register access (although I haven't looked in that much detail). It's a neat trick and much less errorprone. That said, I generally just use machine.mem32 for everything.

Scottie
Posts: 2
Joined: Tue Jul 06, 2021 11:48 pm

Re: Accessing Registers with ctypes

Post by Scottie » Wed Jul 07, 2021 2:09 am

FWIW, at the "regular" REPL (e.g. using mpremote or any other serial program), tab completion works fine on the ADC object.
This is very exciting! Thank you for suggesting a basic serial terminal, I got tab-complete to work so I am one step closer:)

I can't say much about Thonny as it is new to me too. But, it sounds like you are right about Thonny doing its own thing. As Thonny will also display local variables and let you inspect them, but in the case of this struct I get the same errors in the shell.

The struct & bit-field access is not my own idea. Microchip has been providing register structs in their MCUs headers and I have grown accustomed to using them :). Just a snippet from one of their headers:

Code: Select all

extern volatile uint16_t AD1CON1 __attribute__((__sfr__));
__extension__ typedef struct tagAD1CON1BITS {
  union {
    struct {
      uint16_t DONE:1;
      uint16_t SAMP:1;
      uint16_t ASAM:1;
      uint16_t :2;
      uint16_t SSRC:3;
      uint16_t FORM:2;
      uint16_t :3;
      uint16_t ADSIDL:1;
      uint16_t :1;
      uint16_t ADON:1;
    };
    struct {
      uint16_t :5;
      uint16_t SSRC0:1;
      uint16_t SSRC1:1;
      uint16_t SSRC2:1;
      uint16_t FORM0:1;
      uint16_t FORM1:1;
    };
  };
} AD1CON1BITS;
extern volatile AD1CON1BITS AD1CON1bits __attribute__((__sfr__));
So you could directly write to the AD1CON1 register or set/test registers bit fields with:

Code: Select all

AD1CON1bits.ADON = 1; //Just an example 
That said, I generally just use machine.mem32 for everything.
As you say its not too difficult just to use mem32, that's where I started trying port the DMA & ADC sample c code to MicroPython. For example just a snippet:

Code: Select all

    def run(self):
        mem32[CH0_TRANS_COUNT] = self.N
        mem32[CH0_READ_ADDR] = ADC_FIFO
        mem32[CH0_WRITE_ADDR] = self.pbuf
        mem32[CH0_CTRL_TRIG] |= 1 #enable DMA
        mem32[ADC_CS] |= 0b1001
Thanks again for the help.

Post Reply