MicroPython:Библиотеки/uctypes: различия между версиями
Myagkij (обсуждение | вклад) Нет описания правки |
Нет описания правки |
||
(не показано 19 промежуточных версий 1 участника) | |||
Строка 5: | Строка 5: | ||
=Модуль uctypes – доступ к двоичным данным при помощи структур данных<ref>[http://docs.micropython.org/en/latest/library/uctypes.html docs.micropython.org - uctypes – access binary data in a structured way]</ref>= | =Модуль uctypes – доступ к двоичным данным при помощи структур данных<ref>[http://docs.micropython.org/en/latest/library/uctypes.html docs.micropython.org - uctypes – access binary data in a structured way]</ref>= | ||
В этом модуле реализован «интерфейс для внешних данных» для MicroPython. Его идея близка | В этом модуле реализован ''«интерфейс для внешних данных»'' для [[MicroPython]]. Его идея близка [[CPython]]’овскому модулю ctypes, но сам [[API]] отличается, упрощен и оптимизирован под маленькие ресурсы. Базовая идея модуля uctypes в том, чтобы возможность задать макет структуры данных была не хуже, чем в языке [[C]], а также в том, чтобы потом получить доступ к этой структуре и ее подполям при помощи знакомого точечного синтаксиса. | ||
Внимание | {{Блок/Инфо4 | ||
|1=Внимание | |||
|2=Модуль ctypes позволяет получить доступ к любым адресам памяти на устройстве (включая регистры чтения/записи и управляющие регистры). Неосторожное использование может привести к вылетам, потере данных и даже неисправности устройства.}} | |||
Смотрите также | {{Блок/Инфо1 | ||
|1=Смотрите также | |||
|2=''Модуль ustruct.'' В нем реализован стандартный [[Python]]’овский способ доступа к двоичным структурам данных (но он плохо масштабируется для больших и сложных структур).}} | |||
Примеры использования: | Примеры использования: | ||
<syntaxhighlight lang="python | <syntaxhighlight lang="python"> | ||
import uctypes | import uctypes | ||
Строка 69: | Строка 73: | ||
WWDG.WWDG_CR.WDGA = 1 | WWDG.WWDG_CR.WDGA = 1 | ||
print("Текущее значение счетчика:", WWDG.WWDG_CR.T) | print("Текущее значение счетчика:", WWDG.WWDG_CR.T) | ||
Макет структуры данных | </syntaxhighlight> | ||
Макет структуры задается в «дескрипторе» – Python-словаре, в котором есть поля, которые служат ключами, а также другие свойства, через которые можно получить доступ к привязанным к ним значениям. | |||
== Макет структуры данных == | |||
Макет структуры задается в ''«дескрипторе»'' – Python-словаре, в котором есть поля, которые служат ключами, а также другие свойства, через которые можно получить доступ к привязанным к ним значениям. | |||
<syntaxhighlight lang="python"> | |||
{ | { | ||
"field1": <properties>, | "field1": <properties>, | ||
Строка 82: | Строка 91: | ||
Ниже – несколько примеров разных типов полей. | Ниже – несколько примеров разных типов полей. | ||
* Скалярные типы: | * '''Скалярные типы:''' | ||
<syntaxhighlight lang="python"> | |||
"field_name": offset | uctypes.UINT32 | "field_name": offset | uctypes.UINT32 | ||
Другими словами, значение – это идентификатор скалярного типа данных, рядом с которым по принципу логического ИЛИ размещено смещение поля (в байтах) от начала структуры. | </syntaxhighlight> | ||
* Рекурсивные структуры: | |||
Другими словами, значение – это идентификатор скалярного типа данных, рядом с которым по принципу логического ''ИЛИ'' размещено смещение поля (в байтах) от начала структуры. | |||
* '''Рекурсивные структуры:''' | |||
<syntaxhighlight lang="python"> | |||
"sub": (offset, { | "sub": (offset, { | ||
"b0": 0 | uctypes.UINT8, | "b0": 0 | uctypes.UINT8, | ||
"b1": 1 | uctypes.UINT8, | "b1": 1 | uctypes.UINT8, | ||
}) | }) | ||
</syntaxhighlight> | |||
То есть значение – это 2-элементный кортеж. Его первый элемент – это смещение, а второй – это словарь с дескриптором структуры (примечание: смещения в рекурсивных дескрипторах связаны со структурой, которую они задают). Разумеется, рекурсивную структуру можно задать не только самим словарем, но и указанием на словарь с дескриптором структуры (который был задан ранее) по его названию. | То есть значение – это 2-элементный кортеж. Его первый элемент – это смещение, а второй – это словарь с дескриптором структуры (примечание: смещения в рекурсивных дескрипторах связаны со структурой, которую они задают). Разумеется, рекурсивную структуру можно задать не только самим словарем, но и указанием на словарь с дескриптором структуры (который был задан ранее) по его названию. | ||
* Массивы примитивных типов данных: | |||
* '''Массивы примитивных типов данных:''' | |||
<syntaxhighlight lang="python"> | |||
"arr": (offset | uctypes.ARRAY, size | uctypes.UINT8), | "arr": (offset | uctypes.ARRAY, size | uctypes.UINT8), | ||
То есть значение – это 2-элементный кортеж. Его первый элемент – это флаг ARRAY, рядом с которым по принципу логического ИЛИ размещено смещение, а второй – это идентификатор скалярного типа данных, рядом с которым по принципу логического ИЛИ размещено количество элементов в массиве. | </syntaxhighlight> | ||
* Массивы агрегатных типов данных: | |||
То есть значение – это 2-элементный кортеж. Его первый элемент – это флаг ''ARRAY'', рядом с которым по принципу логического ИЛИ размещено смещение, а второй – это идентификатор скалярного типа данных, рядом с которым по принципу логического ''ИЛИ'' размещено количество элементов в массиве. | |||
* '''Массивы агрегатных типов данных:''' | |||
<syntaxhighlight lang="python"> | |||
"arr2": (offset | uctypes.ARRAY, size, {"b": 0 | uctypes.UINT8}), | "arr2": (offset | uctypes.ARRAY, size, {"b": 0 | uctypes.UINT8}), | ||
То есть значение – это 3-элементный кортеж. Его первый элемент – это флаг ARRAY, рядом с которым по принципу логического ИЛИ размещено смещение, второй – это количество элементов в массиве, а третий – это дескриптор заданного типа. | </syntaxhighlight> | ||
* Указатель на примитивный тип данных: | |||
То есть значение – это 3-элементный кортеж. Его первый элемент – это флаг ''ARRAY'', рядом с которым по принципу логического ''ИЛИ'' размещено смещение, второй – это количество элементов в массиве, а третий – это дескриптор заданного типа. | |||
* '''Указатель на примитивный тип данных:''' | |||
<syntaxhighlight lang="python"> | |||
"ptr": (offset | uctypes.PTR, uctypes.UINT8), | "ptr": (offset | uctypes.PTR, uctypes.UINT8), | ||
То есть значение – это 2-элементный кортеж. Его первый элемент – это флаг PTR, рядом с которым по принципу логического ИЛИ размещено смещение, а второй – это идентификатор скалярного типа данных. | </syntaxhighlight> | ||
* Указатель на агрегатный тип данных: | |||
То есть значение – это 2-элементный кортеж. Его первый элемент – это флаг ''PTR'', рядом с которым по принципу логического ''ИЛИ'' размещено смещение, а второй – это идентификатор скалярного типа данных. | |||
* '''Указатель на агрегатный тип данных:''' | |||
<syntaxhighlight lang="python"> | |||
"ptr2": (offset | uctypes.PTR, {"b": 0 | uctypes.UINT8}), | "ptr2": (offset | uctypes.PTR, {"b": 0 | uctypes.UINT8}), | ||
То есть значение – это 2-элементный кортеж. Его первый элемент – это флаг PTR, рядом с которым по принципу логического ИЛИ размещено смещение, а второй – это тип дескриптора, на который ссылается указатель. | </syntaxhighlight> | ||
* Битовые поля: | |||
То есть значение – это 2-элементный кортеж. Его первый элемент – это флаг ''PTR'', рядом с которым по принципу логического ''ИЛИ'' размещено смещение, а второй – это тип дескриптора, на который ссылается указатель. | |||
* '''Битовые поля:''' | |||
<syntaxhighlight lang="python"> | |||
"bitf0": offset | uctypes.BFUINT16 | lsbit << uctypes.BF_POS | bitsize << uctypes.BF_LEN, | "bitf0": offset | uctypes.BFUINT16 | lsbit << uctypes.BF_POS | bitsize << uctypes.BF_LEN, | ||
То есть значение – это идентификатор скалярного типа данных, содержащего заданное битовое поле (названия типов похожи на названия скалярных типов, но с префиксом BF), рядом с которым по принципу логического ИЛИ размещено смещение для скалярного значения, содержащего битовое поле, а также по принципу логического ИЛИ объединенное со значением для позиции бита и длины битового поля (в битах), смещенные битами BF_POS и BF_LEN соответственно. Стартовая позиция битового поля начинается с самого младшего бита скалярного значения (это позиция «0»), и это номер самого правого бита в битовом поле (другими словами, это количество битов, на которое нужно сместить скалярное значение вправо, чтобы извлечь битовое поле). | </syntaxhighlight> | ||
В примере выше сначала будет извлечено UINT16-значение на смещении «0» (эта деталь может быть важна при доступе к аппаратным регистрам, для доступа к которым требуются длина и выравнивание), а затем будут извлечены само битовое поле, чей самый правый бит – это самый младший бит (lsbit) этого UINT16-значения, и его длина (bitsize). Например, если lsbit – это «0», а bitsize – это «8», то фактически это даст доступ к самому младшему байту UINT16-значения. | |||
Помните, что операции с битовыми полями не зависят от порядка байтов. В частности, в примере выше доступ к самому младшему байту UINT16-значения будет получен и если порядок в структуре будет «от старшего к младшему», и если он будет «от младшего к старшему». Но важно, чтобы у самого младшего бита был номер «0». У разных значений может быть разная нумерация в их нативных ABI, но в uctypes всегда используется описанная выше нормализированная нумерация. | То есть значение – это идентификатор скалярного типа данных, содержащего заданное битовое поле (названия типов похожи на названия скалярных типов, но с префиксом ''BF''), рядом с которым по принципу логического ''ИЛИ'' размещено смещение для скалярного значения, содержащего битовое поле, а также по принципу логического ''ИЛИ'' объединенное со значением для позиции бита и длины битового поля (в битах), смещенные битами ''BF_POS'' и ''BF_LEN'' соответственно. Стартовая позиция битового поля начинается с самого младшего бита скалярного значения (это позиция ''«0»''), и это номер самого правого бита в битовом поле (другими словами, это количество битов, на которое нужно сместить скалярное значение вправо, чтобы извлечь битовое поле). | ||
Содержимое модуля | |||
* | В примере выше сначала будет извлечено UINT16-значение на смещении ''«0»'' (эта деталь может быть важна при доступе к аппаратным регистрам, для доступа к которым требуются длина и выравнивание), а затем будут извлечены само битовое поле, чей самый правый бит – это самый младший бит (lsbit) этого UINT16-значения, и его длина (bitsize). Например, если lsbit – это «0», а bitsize – это «8», то фактически это даст доступ к самому младшему байту UINT16-значения. | ||
* uctypes.LITTLE_ENDIAN | |||
* uctypes.BIG_ENDIAN | Помните, что операции с битовыми полями не зависят от порядка байтов. В частности, в примере выше доступ к самому младшему байту UINT16-значения будет получен и если порядок в структуре будет ''«от старшего к младшему»'', и если он будет ''«от младшего к старшему»''. Но важно, чтобы у самого младшего бита был номер ''«0»''. У разных значений может быть разная нумерация в их нативных ''ABI'', но в uctypes всегда используется описанная выше нормализированная нумерация. | ||
* uctypes.NATIVE | |||
* uctypes.sizeof( | == Содержимое модуля == | ||
* uctypes.addressof( | |||
* uctypes.bytes_at( | * [[MicroPython:Библиотеки/uctypes/uctypes.struct()|uctypes.struct()]] | ||
* uctypes.bytearray_at( | * [[MicroPython:Библиотеки/uctypes/uctypes.LITTLE_ENDIAN|uctypes.LITTLE_ENDIAN]] | ||
* uctypes. | * [[MicroPython:Библиотеки/uctypes/uctypes.BIG_ENDIAN|uctypes.BIG_ENDIAN]] | ||
* uctypes.FLOAT32 | * [[MicroPython:Библиотеки/uctypes/uctypes.NATIVE|uctypes.NATIVE]] | ||
* uctypes.VOID | * [[MicroPython:Библиотеки/uctypes/uctypes.sizeof()|uctypes.sizeof()]] | ||
* uctypes | * [[MicroPython:Библиотеки/uctypes/uctypes.addressof()|uctypes.addressof()]] | ||
Дескрипторы структуры данных и инстанцинирование объекта структуры | * [[MicroPython:Библиотеки/uctypes/uctypes.bytes_at()|uctypes.bytes_at()]] | ||
* [[MicroPython:Библиотеки/uctypes/uctypes.bytearray_at()|uctypes.bytearray_at()]] | |||
* [[MicroPython:Библиотеки/uctypes/uctypes.UINT8|uctypes.UINT8]] | |||
* [[MicroPython:Библиотеки/uctypes/uctypes.INT8|uctypes.INT8]] | |||
* [[MicroPython:Библиотеки/uctypes/uctypes.UINT16|uctypes.UINT16]] | |||
* [[MicroPython:Библиотеки/uctypes/uctypes.INT16|uctypes.INT16]] | |||
* [[MicroPython:Библиотеки/uctypes/uctypes.UINT32|uctypes.UINT32]] | |||
* [[MicroPython:Библиотеки/uctypes/uctypes.INT32|uctypes.INT32]] | |||
* [[MicroPython:Библиотеки/uctypes/uctypes.UINT64|uctypes.UINT64]] | |||
* [[MicroPython:Библиотеки/uctypes/uctypes.INT64|uctypes.INT64]] | |||
* [[MicroPython:Библиотеки/uctypes/uctypes.FLOAT32|uctypes.FLOAT32]] | |||
* [[MicroPython:Библиотеки/uctypes/uctypes.FLOAT64|uctypes.FLOAT64]] | |||
* [[MicroPython:Библиотеки/uctypes/uctypes.VOID|uctypes.VOID]] | |||
* [[MicroPython:Библиотеки/uctypes/uctypes.PTR|uctypes.PTR]] | |||
* [[MicroPython:Библиотеки/uctypes/uctypes.ARRAY|uctypes.ARRAY]] | |||
== Дескрипторы структуры данных и инстанцинирование объекта структуры == | |||
Задав в конструкторе uctypes.struct() словарь с дескриптором структуры и тип макета, вы можете инстанцинировать экземпляр нужной вам структуры в нужном адресе памяти. Адрес в памяти обычно можно узнать следующим образом: | Задав в конструкторе uctypes.struct() словарь с дескриптором структуры и тип макета, вы можете инстанцинировать экземпляр нужной вам структуры в нужном адресе памяти. Адрес в памяти обычно можно узнать следующим образом: | ||
* Это может быть адрес, заданный заранее – если речь об аппаратных регистрах на «голой» системе. Ищите эти адреса в документации к своему микроконтроллеру/SoC. | * Это может быть адрес, заданный заранее – если речь об аппаратных регистрах на ''«голой»'' системе. Ищите эти адреса в документации к своему микроконтроллеру/[[SoC]]. | ||
* При помощи некоторых FFI-функций (от англ. «foreign function interface», т.е. речь о функциях, служащих интерфейсом для внешних функций). | * При помощи некоторых [[FFI-функций]] (от англ. ''«foreign function interface»'', т.е. речь о функциях, служащих интерфейсом для внешних функций). | ||
* При помощи uctypes.addressof() – когда вам нужно передать аргументы FFI-функции или получить доступ к некоторым данным для I/O-операций (к примеру, для чтения данных из файла или сетевого сокета). | * При помощи uctypes.addressof() – когда вам нужно передать аргументы [[FFI-функции]] или получить доступ к некоторым данным для I/O-операций (к примеру, для чтения данных из файла или [[сетевого сокета]]). | ||
Объекты структуры данных | |||
Объекты структуры данных позволяют получить доступ к отдельным полям при помощи стандартного точечного синтаксиса: my_struct.substruct1.field1. Если это поле скалярного типа, то извлечение данных из него сгенерирует примитивное значение ( | == Объекты структуры данных == | ||
Если поле – это массив, доступ к его отдельным элементам можно получить при помощи стандартного оператора индексирования [] – и для чтения, и для присвоения. | |||
Если поле – это указатель, то его можно разыменовать при помощи синтаксиса [0] (это соответствует C- | Объекты структуры данных позволяют получить доступ к отдельным полям при помощи стандартного точечного синтаксиса: ''my_struct.substruct1.field1''. Если это поле скалярного типа, то извлечение данных из него сгенерирует примитивное значение ([[Python]]’овское целое число или число с плавающей точкой), соответствующее значению, содержащемуся в поле. Скалярное поле также можно присвоить. | ||
Подытоживая, доступ к полям структуры данных в целом повторяет синтаксис языка C, за исключением разыменовывания указателей, где нужно использовать оператор [0] вместо *. | |||
Ограничения | Если поле – это массив, доступ к его отдельным элементам можно получить при помощи стандартного оператора индексирования '''[]''' – и для чтения, и для присвоения. | ||
* Избегайте доступа к вложенным структурам. Например, вместо mcu_registers.peripheral_a.register1 задайте отдельные дескрипторы для каждого периферийного устройства – например, peripheral_a.register1. Или можно закэшировать нужное периферийное устройство: peripheral_a = mcu_registers.peripheral_a. Если регистр состоит из нескольких битовых полей, вам нужно будет закэшировать указатели к нужному регистру: reg_a = mcu_registers.peripheral_a.reg_a. | Если поле – это указатель, то его можно разыменовать при помощи синтаксиса '''[0]''' (это соответствует [[C-оператор]]у '''*''', но '''[0]''' работает и в [[C]] тоже). Индексирование указателей при помощи целого числа (кроме [[«0»]]) тоже поддерживается – при помощи той же семантики, что и в [[C]]. | ||
* Избегайте других нескалярных данных вроде массивов. Например, вместо peripheral_a.register[0] используйте peripheral_a.register0. Опять же, в качестве альтернативы можно закэшировать промежуточные значения, например, register0 = peripheral_a.register[0]. | |||
Подытоживая, доступ к полям структуры данных в целом повторяет синтаксис языка [[C]], за исключением разыменовывания указателей, где нужно использовать оператор '''[0]''' вместо '''*'''. | |||
== Ограничения == | |||
* Доступ к нескалярным полям ведет к немедленному выделению памяти под объекты для них. Это значит, что нужно быть особенно осторожным при работе с макетом структуры, доступ к которой нужно получить в момент, когда выделение памяти отключено (например, из прерывания). Рекомендации: | |||
** Избегайте доступа к вложенным структурам. Например, вместо ''mcu_registers.peripheral_a.register1'' задайте отдельные дескрипторы для каждого периферийного устройства – например, ''peripheral_a.register1''. Или можно закэшировать нужное периферийное устройство: ''peripheral_a = mcu_registers.peripheral_a''. Если регистр состоит из нескольких битовых полей, вам нужно будет закэшировать указатели к нужному регистру: ''reg_a = mcu_registers.peripheral_a.reg_a''. | |||
** Избегайте других нескалярных данных вроде массивов. Например, вместо ''peripheral_a.register[0]'' используйте ''peripheral_a.register0''. Опять же, в качестве альтернативы можно закэшировать промежуточные значения, например, ''register0 = peripheral_a.register[0]''. | |||
* Диапазон смещений, поддерживаемых модулем uctypes, ограничен. В принципе, этот диапазон зависит от используемой реализации, но в целом можно посоветовать разбивать структуры таким образом, чтобы уложиться в диапазон между несколькими килобайтами и несколькими десятками килобайт. В принципе, в большинстве случаев это нормальная ситуация. Например, вам нет смысла задавать в одной структуре все регистры микроконтроллера (простирающиеся по всему 32-битному адресному пространству) – обычно вам нужны лишь регистры используемых периферийных компонентов. В крайнем случае вам может понадобиться специально разбить структуру на несколько частей (например, чтобы получить доступ к нативной структуре данных с много-мегабайтным массивом посередине, но это очень гипотетическая ситуация). | |||
<syntaxhighlight lang="python | <syntaxhighlight lang="python"> | ||
=См.также= | =См.также= |
Текущая версия от 18:17, 14 мая 2023
Модуль uctypes – доступ к двоичным данным при помощи структур данных[1]
В этом модуле реализован «интерфейс для внешних данных» для MicroPython. Его идея близка CPython’овскому модулю ctypes, но сам API отличается, упрощен и оптимизирован под маленькие ресурсы. Базовая идея модуля uctypes в том, чтобы возможность задать макет структуры данных была не хуже, чем в языке C, а также в том, чтобы потом получить доступ к этой структуре и ее подполям при помощи знакомого точечного синтаксиса.
Примеры использования:
import uctypes
# Пример 1: часть заголовка ELF-файла.
# https://wikipedia.org/wiki/Executable_and_Linkable_Format#File_header
ELF_HEADER = {
"EI_MAG": (0x0 | uctypes.ARRAY, 4 | uctypes.UINT8),
"EI_DATA": 0x5 | uctypes.UINT8,
"e_machine": 0x12 | uctypes.UINT16,
}
# "f" – это ELF-файл, открытый в двоичном режиме.
buf = f.read(uctypes.sizeof(ELF_HEADER, uctypes.LITTLE_ENDIAN))
header = uctypes.struct(uctypes.addressof(buf), ELF_HEADER, uctypes.LITTLE_ENDIAN)
assert header.EI_MAG == b"\x7fELF"
assert header.EI_DATA == 1, "Упс, неправильный порядок следования байтов. Можно попробовать снова с uctypes.BIG_ENDIAN."
print("machine:", hex(header.e_machine))
# Пример 2: Резидентная структура данных, с указателями.
COORD = {
"x": 0 | uctypes.FLOAT32,
"y": 4 | uctypes.FLOAT32,
}
STRUCT1 = {
"data1": 0 | uctypes.UINT8,
"data2": 4 | uctypes.UINT32,
"ptr": (8 | uctypes.PTR, COORD),
}
# Предположим, у вас есть адрес структуры типа STRUCT1 в «addr».
# Аргумент uctypes.NATIVE опционален (по умолчанию используется).
struct1 = uctypes.struct(addr, STRUCT1, uctypes.NATIVE)
print("x:", struct1.ptr[0].x)
# Пример 3: доступ к регистрам CPU. Часть WWDG-блока STM32F4xx.
WWDG_LAYOUT = {
"WWDG_CR": (0, {
# «BFUINT32» означает размер регистра WWDG_CR.
"WDGA": 7 << uctypes.BF_POS | 1 << uctypes.BF_LEN | uctypes.BFUINT32,
"T": 0 << uctypes.BF_POS | 7 << uctypes.BF_LEN | uctypes.BFUINT32,
}),
"WWDG_CFR": (4, {
"EWI": 9 << uctypes.BF_POS | 1 << uctypes.BF_LEN | uctypes.BFUINT32,
"WDGTB": 7 << uctypes.BF_POS | 2 << uctypes.BF_LEN | uctypes.BFUINT32,
"W": 0 << uctypes.BF_POS | 7 << uctypes.BF_LEN | uctypes.BFUINT32,
}),
}
WWDG = uctypes.struct(0x40002c00, WWDG_LAYOUT)
WWDG.WWDG_CFR.WDGTB = 0b10
WWDG.WWDG_CR.WDGA = 1
print("Текущее значение счетчика:", WWDG.WWDG_CR.T)
Макет структуры данных
Макет структуры задается в «дескрипторе» – Python-словаре, в котором есть поля, которые служат ключами, а также другие свойства, через которые можно получить доступ к привязанным к ним значениям.
{
"field1": <properties>,
"field2": <properties>,
...
}
В данный момент при использовании uctypes требуется явно указывать смещение для каждого поля. Это смещение задается в байтах от начала структуры.
Ниже – несколько примеров разных типов полей.
- Скалярные типы:
"field_name": offset | uctypes.UINT32
Другими словами, значение – это идентификатор скалярного типа данных, рядом с которым по принципу логического ИЛИ размещено смещение поля (в байтах) от начала структуры.
- Рекурсивные структуры:
"sub": (offset, {
"b0": 0 | uctypes.UINT8,
"b1": 1 | uctypes.UINT8,
})
То есть значение – это 2-элементный кортеж. Его первый элемент – это смещение, а второй – это словарь с дескриптором структуры (примечание: смещения в рекурсивных дескрипторах связаны со структурой, которую они задают). Разумеется, рекурсивную структуру можно задать не только самим словарем, но и указанием на словарь с дескриптором структуры (который был задан ранее) по его названию.
- Массивы примитивных типов данных:
"arr": (offset | uctypes.ARRAY, size | uctypes.UINT8),
То есть значение – это 2-элементный кортеж. Его первый элемент – это флаг ARRAY, рядом с которым по принципу логического ИЛИ размещено смещение, а второй – это идентификатор скалярного типа данных, рядом с которым по принципу логического ИЛИ размещено количество элементов в массиве.
- Массивы агрегатных типов данных:
"arr2": (offset | uctypes.ARRAY, size, {"b": 0 | uctypes.UINT8}),
То есть значение – это 3-элементный кортеж. Его первый элемент – это флаг ARRAY, рядом с которым по принципу логического ИЛИ размещено смещение, второй – это количество элементов в массиве, а третий – это дескриптор заданного типа.
- Указатель на примитивный тип данных:
"ptr": (offset | uctypes.PTR, uctypes.UINT8),
То есть значение – это 2-элементный кортеж. Его первый элемент – это флаг PTR, рядом с которым по принципу логического ИЛИ размещено смещение, а второй – это идентификатор скалярного типа данных.
- Указатель на агрегатный тип данных:
"ptr2": (offset | uctypes.PTR, {"b": 0 | uctypes.UINT8}),
То есть значение – это 2-элементный кортеж. Его первый элемент – это флаг PTR, рядом с которым по принципу логического ИЛИ размещено смещение, а второй – это тип дескриптора, на который ссылается указатель.
- Битовые поля:
"bitf0": offset | uctypes.BFUINT16 | lsbit << uctypes.BF_POS | bitsize << uctypes.BF_LEN,
То есть значение – это идентификатор скалярного типа данных, содержащего заданное битовое поле (названия типов похожи на названия скалярных типов, но с префиксом BF), рядом с которым по принципу логического ИЛИ размещено смещение для скалярного значения, содержащего битовое поле, а также по принципу логического ИЛИ объединенное со значением для позиции бита и длины битового поля (в битах), смещенные битами BF_POS и BF_LEN соответственно. Стартовая позиция битового поля начинается с самого младшего бита скалярного значения (это позиция «0»), и это номер самого правого бита в битовом поле (другими словами, это количество битов, на которое нужно сместить скалярное значение вправо, чтобы извлечь битовое поле).
В примере выше сначала будет извлечено UINT16-значение на смещении «0» (эта деталь может быть важна при доступе к аппаратным регистрам, для доступа к которым требуются длина и выравнивание), а затем будут извлечены само битовое поле, чей самый правый бит – это самый младший бит (lsbit) этого UINT16-значения, и его длина (bitsize). Например, если lsbit – это «0», а bitsize – это «8», то фактически это даст доступ к самому младшему байту UINT16-значения.
Помните, что операции с битовыми полями не зависят от порядка байтов. В частности, в примере выше доступ к самому младшему байту UINT16-значения будет получен и если порядок в структуре будет «от старшего к младшему», и если он будет «от младшего к старшему». Но важно, чтобы у самого младшего бита был номер «0». У разных значений может быть разная нумерация в их нативных ABI, но в uctypes всегда используется описанная выше нормализированная нумерация.
Содержимое модуля
- uctypes.struct()
- uctypes.LITTLE_ENDIAN
- uctypes.BIG_ENDIAN
- uctypes.NATIVE
- uctypes.sizeof()
- uctypes.addressof()
- uctypes.bytes_at()
- uctypes.bytearray_at()
- uctypes.UINT8
- uctypes.INT8
- uctypes.UINT16
- uctypes.INT16
- uctypes.UINT32
- uctypes.INT32
- uctypes.UINT64
- uctypes.INT64
- uctypes.FLOAT32
- uctypes.FLOAT64
- uctypes.VOID
- uctypes.PTR
- uctypes.ARRAY
Дескрипторы структуры данных и инстанцинирование объекта структуры
Задав в конструкторе uctypes.struct() словарь с дескриптором структуры и тип макета, вы можете инстанцинировать экземпляр нужной вам структуры в нужном адресе памяти. Адрес в памяти обычно можно узнать следующим образом:
- Это может быть адрес, заданный заранее – если речь об аппаратных регистрах на «голой» системе. Ищите эти адреса в документации к своему микроконтроллеру/SoC.
- При помощи некоторых FFI-функций (от англ. «foreign function interface», т.е. речь о функциях, служащих интерфейсом для внешних функций).
- При помощи uctypes.addressof() – когда вам нужно передать аргументы FFI-функции или получить доступ к некоторым данным для I/O-операций (к примеру, для чтения данных из файла или сетевого сокета).
Объекты структуры данных
Объекты структуры данных позволяют получить доступ к отдельным полям при помощи стандартного точечного синтаксиса: my_struct.substruct1.field1. Если это поле скалярного типа, то извлечение данных из него сгенерирует примитивное значение (Python’овское целое число или число с плавающей точкой), соответствующее значению, содержащемуся в поле. Скалярное поле также можно присвоить.
Если поле – это массив, доступ к его отдельным элементам можно получить при помощи стандартного оператора индексирования [] – и для чтения, и для присвоения.
Если поле – это указатель, то его можно разыменовать при помощи синтаксиса [0] (это соответствует C-оператору *, но [0] работает и в C тоже). Индексирование указателей при помощи целого числа (кроме «0») тоже поддерживается – при помощи той же семантики, что и в C.
Подытоживая, доступ к полям структуры данных в целом повторяет синтаксис языка C, за исключением разыменовывания указателей, где нужно использовать оператор [0] вместо *.
Ограничения
- Доступ к нескалярным полям ведет к немедленному выделению памяти под объекты для них. Это значит, что нужно быть особенно осторожным при работе с макетом структуры, доступ к которой нужно получить в момент, когда выделение памяти отключено (например, из прерывания). Рекомендации:
- Избегайте доступа к вложенным структурам. Например, вместо mcu_registers.peripheral_a.register1 задайте отдельные дескрипторы для каждого периферийного устройства – например, peripheral_a.register1. Или можно закэшировать нужное периферийное устройство: peripheral_a = mcu_registers.peripheral_a. Если регистр состоит из нескольких битовых полей, вам нужно будет закэшировать указатели к нужному регистру: reg_a = mcu_registers.peripheral_a.reg_a.
- Избегайте других нескалярных данных вроде массивов. Например, вместо peripheral_a.register[0] используйте peripheral_a.register0. Опять же, в качестве альтернативы можно закэшировать промежуточные значения, например, register0 = peripheral_a.register[0].
- Диапазон смещений, поддерживаемых модулем uctypes, ограничен. В принципе, этот диапазон зависит от используемой реализации, но в целом можно посоветовать разбивать структуры таким образом, чтобы уложиться в диапазон между несколькими килобайтами и несколькими десятками килобайт. В принципе, в большинстве случаев это нормальная ситуация. Например, вам нет смысла задавать в одной структуре все регистры микроконтроллера (простирающиеся по всему 32-битному адресному пространству) – обычно вам нужны лишь регистры используемых периферийных компонентов. В крайнем случае вам может понадобиться специально разбить структуру на несколько частей (например, чтобы получить доступ к нативной структуре данных с много-мегабайтным массивом посередине, но это очень гипотетическая ситуация).
<syntaxhighlight lang="python">