клас DMA – доступ до контролера DMA мікроконтролера RP2040¶
Клас DMA надає доступ до контролера прямого доступу до пам’яті (DMA) мікроконтролера RP2040, забезпечуючи можливість переміщення даних між блоками пам’яті та/або регістрами введення-виведення. Контролер DMA має власні, окремі з’єднання шини для читання та запису, і кожен канал DMA може незалежно зчитувати дані з однієї адреси та записувати їх за іншою адресою, за необхідності збільшуючи один або обидва вказівники. Це дозволяє здійснювати передачу даних від імені процесора, поки сам процесор виконує інші завдання або переходить у режим зниженого енергоспоживання. Контролер DMA RP2040 має 12 незалежних каналів DMA, які можуть працювати одночасно. Повний опис системи DMA RP2040 наведено в розділі 2.5 RP2040 Datasheet.
Приклади¶
Найпростіше використання контролера DMA — переміщення даних з одного блоку пам’яті до іншого. Це можна здійснити за допомогою наступного коду:
a = bytearray(32*1024)
b = bytearray(32*1024)
d = rp2.DMA()
c = d.pack_ctrl() # Just use the default control value.
# The count is in 'transfers', which defaults to four-byte words, so divide length by 4
d.config(read=a, write=b, count=len(a)//4, ctrl=c, trigger=True)
# Wait for completion
while d.active():
pass
Зверніть увагу: хоча в цьому прикладі програма виконує цикл очікування завершення передачі, вона могла б замість цього виконувати корисну роботу.
Ще одне, можливо, більш поширене застосування контролера DMA — передача даних між пам’яттю та периферійним пристроєм введення-виведення. У цьому випадку адреса регістра введення-виведення не змінюється для кожної передачі, але адреса пам’яті повинна збільшуватися. Також необхідно керувати темпом передачі, щоб не записувати дані до того, як їх зможе прийняти периферійний пристрій, і не зчитувати їх до того, як вони будуть готові; це можна контролювати за допомогою поля treq_sel регістра керування каналу DMA. Різні поля регістра керування кожного каналу DMA можна упакувати за допомогою методу DMA.pack_ctrl() та розпакувати за допомогою статичного методу DMA.unpack_ctrl(). Код для передачі даних з масиву байтів до TX FIFO стат-машини PIO по одному байту виглядає так:
# pio_num is index of the PIO block being used, sm_num is the state machine in that block.
# my_state_machine is an rp2.PIO() instance.
DATA_REQUEST_INDEX = (pio_num << 3) + sm_num
src_data = bytearray(1024)
d = rp2.DMA()
# Transfer bytes, rather than words, don't increment the write address and pace the transfer.
c = d.pack_ctrl(size=0, inc_write=False, treq_sel=DATA_REQUEST_INDEX)
d.config(
read=src_data,
write=my_state_machine,
count=len(src_data),
ctrl=c,
trigger=True
)
Зверніть увагу, що в цьому прикладі значення адреси запису — це просто стат-машина PIO, до якої надсилаються дані. Це працює тому, що стат-машини PIO підтримують буферний протокол, що дозволяє безпосередній доступ до регістрів FIFO їхніх даних.
Конструктор¶
- class rp2.DMA¶
Резервує один із каналів контролера DMA для виключного використання.
- config(read: 'int | _AnyReadableBuf | None' = None, write: 'int | _AnyWritableBuf | None' = None, count: int | None = None, ctrl: int | None = None, trigger: bool = False) None¶
Налаштовує регістри DMA для каналу і за необхідності розпочинає передачу. Параметри:
read: Адреса, з якої контролер DMA почне зчитувати дані, або об’єкт, що надаватиме дані для читання. Може бути цілим числом або будь-яким об’єктом, що підтримує буферний протокол.
write: Адреса, за якою контролер DMA почне записувати дані, або об’єкт, у який будуть записані дані. Може бути цілим числом або будь-яким об’єктом, що підтримує буферний протокол.
count: Кількість передач по шині, які виконаються до зупинки каналу. Зверніть увагу, що це кількість передач, а не байтів. Якщо передачі мають ширину 2 або 4 байти, загальний обсяг переміщених даних (і відповідно розмір необхідного буфера) потрібно помножити відповідно.
ctrl: Значення регістра керування DMA. Це ціле число, яке зазвичай упаковується за допомогою
DMA.pack_ctrl().trigger: За необхідності розпочати передачу негайно.
- irq(handler: Callable[[DMA], None] | None = None, hard: bool = False) Callable¶
Повертає об’єкт IRQ для цього каналу DMA і за необхідності налаштовує його.
- close() None¶
Звільняє резервування базового каналу DMA та вивільняє обробник переривань. Об’єкт
DMAне можна використовувати після цієї операції.
- pack_ctrl(default: int | None = None, **kwargs) int¶
Упаковує значення, надані в іменованих аргументах, у відповідні поля нового значення регістра керування. Будь-яке поле, яке не задано, отримає значення за замовчуванням. Значення за замовчуванням береться або із заданого аргументу
default, або, якщо він не вказаний, з відповідного значення для поточного каналу; встановлення цього значення рівним поточному значенню атрибутаDMA.ctrlзабезпечує зручний спосіб перевизначити підмножину полів.Ключами іменованих аргументів можуть бути будь-які ключі, що повертаються методом
DMA.unpack_ctrl(). Доступні для запису значення:enable:
boolВстановіть, щоб увімкнути канал (за замовчуванням:True).high_pri:
boolЗробити трафік шини цього каналу пріоритетним (за замовчуванням:False).size:
intРозмір передачі: 0=байт, 1=напівслово, 2=слово (за замовчуванням: 2).inc_read:
boolЗбільшувати адресу читання після кожної передачі (за замовчуванням:True).inc_write:
boolЗбільшувати адресу запису після кожної передачі (за замовчуванням:True).ring_size:
intЯкщо не дорівнює нулю, лише нижніring_sizeбіт одного з регістрів адреси будуть змінюватися при збільшенні адреси, що призводить до її обгортання на межі1 << ring_sizeбайтів. Який саме регістр адреси обгортається, визначається прапоромring_sel. Нульове значення вимикає обгортання адреси.ring_sel:
boolВстановітьFalse, щоб застосуватиring_sizeдо адреси читання, абоTrue— до адреси запису.chain_to:
intНомер каналу, який буде запущено після завершення цієї передачі. Встановлення цього значення рівним номеру власного каналу цього об’єкта DMA вимикає ланцюжок (це значення за замовчуванням).treq_sel:
intВибір сигналу запиту на передачу. Подробиці дивіться в розділі 2.5.3 технічного опису RP2040.irq_quiet:
boolНе генерувати переривання після кожної передачі. Переривання генеруватимуться натомість, коли до регістра тригера записується нульове значення, що зупинить послідовність ланцюгових передач (за замовчуванням:True).bswap:
boolЯкщо встановлено true, байти в словах або напівсловах будуть переставлені перед записом (за замовчуванням:True).sniff_en:
boolВстановітьTrue, щоб дозволити апаратному модулю перехоплення мікросхеми отримувати доступ до даних (за замовчуванням:False).write_err:
boolВстановлення значенняTrueочистить раніше зафіксовану помилку запису.read_err:
boolВстановлення значенняTrueочистить раніше зафіксовану помилку читання.
Опис усіх цих полів наведено в описі регістра
CH0_CTRL_TRIGу розділі 2.5.7 технічного опису RP2040.
- unpack_ctrl(value: int) dict¶
Розпаковує значення регістра керування каналу DMA у словник із парами ключ/значення для кожного з полів регістра. value — це значення регістра
ctrlдля розпакування.Цей метод поверне значення для всіх ключів, які можна передати до
DMA.pack_ctrl. Крім того, він також поверне прапори регістра керування, доступні лише для читання:busy(встановлюється на початку передачі і знімається після її завершення) таahb_err(логічне АБО прапорівread_errіwrite_err). Ці значення ігноруватимуться при упакуванні, тому словник, отриманий розпакуванням регістра керування, можна безпосередньо використовувати як іменовані аргументи для упакування.
- active(value: bool | None = None, /) bool¶
Отримує або встановлює, чи виконує канал DMA передачу даних у даний момент.
>>> sm.active() 0 >>> sm.active(1) >>> while sm.active(): ... pass
- read: int¶
Цей атрибут відображає адресу, з якої відбудеться наступна передача по шині для читання. Він може бути записаний цілим числом або об’єктом, що підтримує буферний протокол, і таке записування набирає чинності негайно.
- write: int¶
Цей атрибут відображає адресу, за якою відбудеться наступна передача по шині для запису. Він може бути записаний цілим числом або об’єктом, що підтримує буферний протокол, і таке записування набирає чинності негайно.
- count: int¶
Читання цього атрибута повертає кількість передач по шині, що залишилися в поточній послідовності передачі. Запис до цього атрибута встановлює загальну кількість передач для наступної послідовності передачі.
- ctrl: int¶
Цей атрибут відображає регістр керування каналу DMA. Зазвичай до нього записується ціле число, упаковане за допомогою методу
DMA.pack_ctrl(). Повернуте значення регістра можна розпакувати за допомогою методуDMA.unpack_ctrl().
- channel: int¶
Номер каналу DMA. Його можна передати в аргументі
chain_toфункціїDMA.pack_ctrl()іншого каналу для увімкнення ланцюгового DMA.
- registers: 'memoryview'¶
Цей атрибут є масивоподібним об’єктом, що дозволяє безпосередній доступ до регістрів каналу DMA. Індексування ведеться за словами, а не за байтами, тому індекси регістрів — це зміщення адрес регістрів, поділені на 4. Опис регістрів дивіться в технічному описі RP2040.
Ланцюгування та доступ до регістра тригера¶
Контролер DMA RP2040 пропонує кілька розширених функцій, що дозволяють одному каналу DMA ініціювати передачу на іншому каналі. Одна з них — використання значення chain_to в регістрі керування, інша — запис до одного з регістрів каналу DMA, що має ефект тригера. У поєднанні з можливістю одного каналу DMA записувати безпосередньо до DMA.registers іншого каналу, це дозволяє виконувати складні транзакції без будь-якого втручання CPU.
Нижче наведено приклад використання як ланцюгування, так і тригерування регістрів для реалізації збирання кількох блоків даних в єдине призначення. Повний опис цих функцій наведено в розділі 2.5 технічного опису RP2040, а наведений нижче код є «пітонічною» версією прикладу з підрозділу 2.5.6.2.
from rp2 import DMA
from uctypes import addressof
from array import array
def gather_strings(string_list, buf):
# We use two DMA channels. The first sends lengths and source addresses from the gather
# list to the registers of the second. The second copies the data itself.
gather_dma = DMA()
buffer_dma = DMA()
# Pack up length/address pairs to be sent to the registers.
gather_list = array("I")
for s in string_list:
gather_list.append(len(s))
gather_list.append(addressof(s))
gather_list.append(0)
gather_list.append(0)
# When writing to the registers of the second DMA channel, we need to wrap the
# write address on an 8-byte (1<<3 bytes) boundary. We write to the ``TRANS_COUNT``
# and ``READ_ADD_TRIG`` registers in the last register alias (registers 14 and 15).
gather_ctrl = gather_dma.pack_ctrl(ring_size=3, ring_sel=True)
gather_dma.config(
read=gather_list, write=buffer_dma.registers[14:16],
count=2, ctrl=gather_ctrl
)
# When copying the data, the transfer size is single bytes, and when completed we need
# to chain back to the start another gather DMA transaction.
buffer_ctrl = buffer_dma.pack_ctrl(size=0, chain_to=gather_dma.channel)
# The read and count values will be set by the other DMA channel.
buffer_dma.config(write=buf, ctrl=buffer_ctrl)
# Set the transfer in motion.
gather_dma.active(1)
# Wait until all the register values have been sent
end_address = addressof(gather_list) + 4 * len(gather_list)
while gather_dma.read != end_address:
pass
input = ["This is ", "a ", "test", " of the scatter", " gather", " process"]
output = bytearray(64)
print(output)
gather_strings(input, output)
print(output)
Цей приклад виконує цикл очікування завершення передачі; альтернативно можна налаштувати обробник переривань і повернутися негайно.