uctypes — ikili verilere yapılandırılmış bir şekilde erişim¶
Bu modül, MicroPython için “yabancı veri arayüzünü” (foreign data interface) gerçekler. Arkasındaki fikir CPython’ın ctypes modülüne benzer, ancak gerçek API farklıdır; küçük boyut için akıcı hale getirilmiş ve eniyilenmiştir. Modülün temel fikri, veri yapısı düzenini yaklaşık olarak C dilinin izin verdiği güçte tanımlamak ve ardından alt alanlara başvurmak için tanıdık nokta sözdizimini kullanarak ona erişmektir.
Uyarı
uctypes modülü, makinenin keyfi bellek adreslerine (I/O ve kontrol yazmaçları dahil) erişime izin verir. Dikkatsiz kullanımı çökmelere, veri kaybına ve hatta donanım arızasına yol açabilir.
Ayrıca bakınız
structModülüİkili verileri paketlemek ve açmak için standart Python modülü.
structher seferinde tüm arabellekler üzerinde, kompakt bir biçim dizesi (örneğin'<HBB4sI') kullanarak çalışır; bu, birkaç sabit alan için iyi çalışır ancak büyük veya derin iç içe geçmiş yapılara kötü ölçeklenir: her okuma veya yazma biçim dizesini yeniden ayrıştırır, birleşimler (union) ve bit alanları desteklenmez ve mevcut bir arabelleğe türlü bir görünüm elde etmenin yolu yoktur.uctypes, düzeni bir kez tanımlamanıza, onu bir bellek bölgesine (RAM, çevre birimi yazmaçları, birbytearray) iliştirmenize ve ardından tek tek alanlara adlandırılmış öznitelikler olarak erişmenize olanak tanıyarakstructmodülünü tamamlar; böylece tekrarlanan ayrıştırma ve kopyalamadan kaçınır ve iç içe yapılar, diziler, birleşimler ve bit alanları için destek ekler.
Kullanım örnekleri:
import uctypes
# Example 1: Subset of ELF file header
# 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" is an ELF file opened in binary mode
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, "Oops, wrong endianness. Could retry with uctypes.BIG_ENDIAN."
print("machine:", hex(header.e_machine))
# Example 2: In-memory data structure, with pointers
COORD = {
"x": 0 | uctypes.FLOAT32,
"y": 4 | uctypes.FLOAT32,
}
STRUCT1 = {
"data1": 0 | uctypes.UINT8,
"data2": 4 | uctypes.UINT32,
"ptr": (8 | uctypes.PTR, COORD),
}
# Suppose you have address of a structure of type STRUCT1 in "addr"
# uctypes.NATIVE is optional (used by default)
struct1 = uctypes.struct(addr, STRUCT1, uctypes.NATIVE)
print("x:", struct1.ptr[0].x)
# Example 3: Access to CPU registers. Subset of STM32F4xx WWDG block
WWDG_LAYOUT = {
"WWDG_CR": (0, {
# BFUINT32 here means size of the WWDG_CR register
"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("Current counter:", WWDG.WWDG_CR.T)
Yapı düzenini tanımlama¶
Yapı düzeni, bir “tanımlayıcı” (descriptor) tarafından tanımlanır - alan adlarını anahtar olarak ve onlara erişmek için gereken diğer özellikleri ilişkili değerler olarak kodlayan bir Python sözlüğü:
{
"field1": <properties>,
"field2": <properties>,
...
}
Şu anda uctypes, her alan için ofsetlerin açık biçimde belirtilmesini gerektirir. Ofsetler, yapının başlangıcından itibaren bayt cinsinden verilir.
Aşağıda çeşitli alan türleri için kodlama örnekleri verilmiştir:
Skaler türler:
"field_name": offset | uctypes.UINT32diğer bir deyişle, değer, yapının başlangıcından itibaren bir alan ofseti (bayt cinsinden) ile VEYA’lanmış (ORed) bir skaler tür tanımlayıcısıdır.
Özyinelemeli yapılar:
"sub": (offset, { "b0": 0 | uctypes.UINT8, "b1": 1 | uctypes.UINT8, })
yani değer 2 elemanlı bir demettir (2-tuple); ilk elemanı bir ofset, ikincisi ise bir yapı tanımlayıcısı sözlüğüdür (not: özyinelemeli tanımlayıcılardaki ofsetler, tanımladıkları yapıya görelidir). Elbette, özyinelemeli yapılar yalnızca düz bir sözlükle değil, daha önce tanımlanmış bir yapı tanımlayıcısı sözlüğüne adıyla başvurarak da belirtilebilir.
İlkel türlerden oluşan diziler:
"arr": (offset | uctypes.ARRAY, size | uctypes.UINT8),yani değer 2 elemanlı bir demettir (2-tuple); ilk elemanı ofsetle VEYA’lanmış (ORed) ARRAY bayrağı, ikincisi ise dizideki eleman sayısıyla VEYA’lanmış skaler eleman türüdür.
Bileşik türlerden oluşan diziler:
"arr2": (offset | uctypes.ARRAY, size, {"b": 0 | uctypes.UINT8}),yani değer 3 elemanlı bir demettir (3-tuple); ilk elemanı ofsetle VEYA’lanmış (ORed) ARRAY bayrağı, ikincisi dizideki eleman sayısı, üçüncüsü ise eleman türünün bir tanımlayıcısıdır.
İlkel bir türe işaretçi:
"ptr": (offset | uctypes.PTR, uctypes.UINT8),yani değer 2 elemanlı bir demettir (2-tuple); ilk elemanı ofsetle VEYA’lanmış (ORed) PTR bayrağı, ikincisi ise bir skaler eleman türüdür.
Bileşik bir türe işaretçi:
"ptr2": (offset | uctypes.PTR, {"b": 0 | uctypes.UINT8}),yani değer 2 elemanlı bir demettir (2-tuple); ilk elemanı ofsetle VEYA’lanmış (ORed) PTR bayrağı, ikincisi ise işaret edilen türün bir tanımlayıcısıdır.
Bit alanları:
"bitf0": offset | uctypes.BFUINT16 | lsbit << uctypes.BF_POS | bitsize << uctypes.BF_LEN,yani değer, verilen bit alanını içeren bir skaler değer türüdür (tür adları skaler türlere benzer ancak
BFön ekiyle başlar) ve bit alanını içeren skaler değer için ofset ile VEYA’lanmış (ORed), ayrıca skaler değer içindeki bit alanının bit konumu ve bit uzunluğu değerleriyle - sırasıyla BF_POS ve BF_LEN bit kadar kaydırılmış olarak - tekrar VEYA’lanmıştır. Bir bit alanı konumu, skalerin en az anlamlı bitinden itibaren sayılır (konum 0’a sahiptir) ve bir alanın en sağdaki bitinin numarasıdır (diğer bir deyişle, bit alanını çıkarmak için bir skalerin sağa kaydırılması gereken bit sayısıdır).Yukarıdaki örnekte, önce 0 ofsetinde bir UINT16 değeri çıkarılacaktır (bu ayrıntı, belirli erişim boyutu ve hizalamasının gerektiği donanım yazmaçlarına erişirken önemli olabilir) ve ardından en sağdaki biti bu UINT16’nın lsbit biti olan ve uzunluğu bitsize bit olan bit alanı çıkarılacaktır. Örneğin, lsbit 0 ve bitsize 8 ise, etkin biçimde UINT16’nın en az anlamlı baytına erişilecektir.
Bit alanı işlemlerinin hedef bayt sıralama düzeninden (endianness) bağımsız olduğunu unutmayın; özellikle yukarıdaki örnek, hem little-endian hem de big-endian yapılarda UINT16’nın en az anlamlı baytına erişecektir. Ancak bu, en az anlamlı bitin 0 olarak numaralandırılmasına bağlıdır. Bazı hedefler, yerel ABI’lerinde farklı bir numaralandırma kullanabilir, ancak
uctypesher zaman yukarıda açıklanan normalleştirilmiş numaralandırmayı kullanır.
Modül içeriği¶
- class uctypes.struct(addr: int, descriptor: dict, layout_type: int = NATIVE, /)¶
Bellekteki yapı adresine, (bir sözlük olarak kodlanmış) tanımlayıcıya ve düzen türüne (aşağıya bakın) dayalı olarak bir “yabancı veri yapısı” nesnesi örneklendirir.
- uctypes.LITTLE_ENDIAN: int¶
Little-endian paketlenmiş bir yapı için düzen türü. (Paketlenmiş, her alanın tanımlayıcıda tanımlandığı kadar bayt kapladığı, yani hizalamanın 1 olduğu anlamına gelir).
- uctypes.NATIVE: int¶
Yerel (native) bir yapı için düzen türü - veri bayt sıralama düzeni ve hizalaması, MicroPython’ın üzerinde çalıştığı sistemin ABI’sine uygun olur.
- uctypes.sizeof(struct: dict | Any, layout_type: int = NATIVE, /) int¶
Veri yapısının boyutunu bayt cinsinden döndürür. struct argümanı, bir yapı sınıfı veya örneklendirilmiş belirli bir yapı nesnesi (ya da onun bileşik alanı) olabilir.
- uctypes.addressof(obj: Any) int¶
Bir nesnenin adresini döndürür. Argüman bytes, bytearray veya arabellek protokolünü destekleyen başka bir nesne olmalıdır (ve gerçekte döndürülen şey bu arabelleğin adresidir).
- uctypes.bytes_at(addr: int, size: int) bytes¶
Verilen adres ve boyuttaki belleği bir bytes nesnesi olarak yakalar. bytes nesnesi değiştirilemez olduğundan, bellek aslında çoğaltılır ve bytes nesnesine kopyalanır; bu nedenle bellek içeriği daha sonra değişirse, oluşturulan nesne özgün değerini korur.
- uctypes.bytearray_at(addr: int, size: int) bytearray¶
Verilen adres ve boyuttaki belleği bir bytearray nesnesi olarak yakalar. Yukarıdaki bytes_at() fonksiyonunun aksine, bellek başvuru ile yakalanır; böylece hem yazılabilir hem de verilen bellek adresindeki güncel değere erişebilirsiniz.
Skaler tamsayı türleri. Her biri bariz sayıda bayt (1, 2, 4 veya 8) kaplar ve yapının düzen türünün bayt sıralama düzeni (NATIVE, LITTLE_ENDIAN veya BIG_ENDIAN‘den biri) kullanılarak okunur/yazılır.
- uctypes.FLOAT32: int¶
IEEE 754 tek duyarlıklı kayan noktalı sayı (4 bayt). Okumalar ve yazmalar bir Python
floatdeğerine/değerinden dönüştürülür.
- uctypes.FLOAT64: int¶
IEEE 754 çift duyarlıklı kayan noktalı sayı (8 bayt). Okumalar ve yazmalar bir Python
floatdeğerine/değerinden dönüştürülür.
- uctypes.VOID: int¶
UINT8için takma ad. C tarzıvoid *alanlarının deyimsel olarak(uctypes.PTR, uctypes.VOID)şeklinde tanımlanabilmesi için sağlanmıştır.
- uctypes.PTR: int¶
Bir tanımlayıcı alanını başka bir türe işaretçi olarak işaretler. Bir işaretçi alanı, iki elemanlı bir demet
(offset | PTR, target_type_or_descriptor)olarak yazılır. İşaretçinin başvurusunu çözmek (dereference), tuttuğu adrese türlü bir görünüm verir.
- uctypes.ARRAY: int¶
Bir tanımlayıcı alanını başka bir türden sabit uzunluklu bir dizi olarak işaretler. Bir dizi alanı, skalerlerden oluşan diziler için
(offset | ARRAY, count | element_type)veya yapılardan oluşan diziler için(offset | ARRAY, count, element_descriptor)şeklindedir. Eleman sayısı tanımlayıcı tanımlandığı anda sabitlenir.
Yapılar için açık bir sabit yoktur: ne PTR ne de ARRAY kullanan bir bileşik tanımlayıcı, bir yapı olarak ele alınır.
Yapı tanımlayıcıları ve yapı nesnelerini örneklendirme¶
Bir yapı tanımlayıcısı sözlüğü ve düzen türü verildiğinde, uctypes.struct() yapıcısını kullanarak belirli bir bellek adresinde belirli bir yapı örneği oluşturabilirsiniz. Bellek adresi genellikle aşağıdaki kaynaklardan gelir:
Baremetal bir sistemde donanım yazmaçlarına erişirken önceden tanımlanmış adres. Bu adresleri, belirli bir MCU/SoC için veri sayfasında (datasheet) arayın.
Bir FFI (Foreign Function Interface) fonksiyonuna yapılan bir çağrının dönüş değeri olarak.
uctypes.addressof()fonksiyonundan; bir FFI fonksiyonuna argüman geçirmek istediğinizde veya alternatif olarak I/O için bazı verilere (örneğin bir dosyadan veya ağ soketinden okunan veriler) erişmek için.
Yapı nesneleri¶
Yapı nesneleri, standart nokta gösterimini kullanarak tek tek alanlara erişime izin verir: my_struct.substruct1.field1. Bir alan skaler türdeyse, onu okumak, alanda bulunan değere karşılık gelen ilkel bir değer (Python tamsayı veya kayan noktalı sayı) üretir. Bir skaler alana atama da yapılabilir.
Bir alan diziyse, tek tek elemanlarına standart altsimge işleci [] ile erişilebilir - hem okuma hem de atama.
Bir alan işaretçiyse, [0] sözdizimi kullanılarak başvurusu çözülebilir (C’deki * işlecine karşılık gelir, ancak [0] C’de de çalışır). Bir işaretçiyi 0 dışındaki tamsayı değerleriyle altsimgelemek de, C’dekiyle aynı anlamla desteklenir.
Özetle, yapı alanlarına erişim, işaretçi başvuru çözümü dışında genel olarak C sözdizimini izler; işaretçi başvuru çözümünde ise * yerine [0] işlecini kullanmanız gerekir.
Kısıtlamalar¶
1. Accessing non-scalar fields leads to allocation of intermediate objects to represent them. This means that special care should be taken to layout a structure which needs to be accessed when memory allocation is disabled (e.g. from an interrupt). The recommendations are:
İç içe geçmiş yapılara erişmekten kaçının. Örneğin,
mcu_registers.peripheral_a.register1yerine, her çevre birimi içinperipheral_a.register1şeklinde erişilecek ayrı düzen tanımlayıcıları tanımlayın. Veya yalnızca belirli bir çevre birimini önbelleğe alın:peripheral_a = mcu_registers.peripheral_a. Bir yazmaç birden çok bit alanından oluşuyorsa, belirli bir yazmaca olan başvuruları önbelleğe almanız gerekir:reg_a = mcu_registers.peripheral_a.reg_a.Diziler gibi diğer skaler olmayan verilerden kaçının. Örneğin,
peripheral_a.register[0]yerineperipheral_a.register0kullanın. Yine, bir alternatif ara değerleri önbelleğe almaktır, örneğinregister0 = peripheral_a.register[0].
2. Range of offsets supported by the uctypes module is limited.
The exact range supported is considered an implementation detail,
and the general suggestion is to split structure definitions to
cover from a few kilobytes to a few dozen of kilobytes maximum.
In most cases, this is a natural situation anyway, e.g. it doesn’t make
sense to define all registers of an MCU (spread over 32-bit address
space) in one structure, but rather a peripheral block by peripheral
block. In some extreme cases, you may need to split a structure in
several parts artificially (e.g. if accessing native data structure
with multi-megabyte array in the middle, though that would be a very
synthetic case).