classe DMA – accès au contrôleur DMA du RP2040¶
La classe DMA offre l’accès au contrôleur d’accès direct à la mémoire (DMA) du RP2040, permettant de déplacer des données entre des blocs de mémoire et/ou des registres d’E/S. Le contrôleur DMA possède ses propres connexions maîtres de bus de lecture et d’écriture séparées sur la structure du bus, et chaque canal DMA peut indépendamment lire des données depuis une adresse et les réécrire vers une autre adresse, en incrémentant éventuellement l’un des pointeurs ou les deux, ce qui lui permet d’effectuer des transferts pour le compte du processeur pendant que celui-ci exécute d’autres tâches ou entre dans un état de faible consommation. Le contrôleur DMA du RP2040 dispose de 12 canaux DMA indépendants pouvant s’exécuter simultanément. Pour tous les détails sur le système DMA du RP2040, consultez la section 2.5 de la fiche technique du RP2040.
Exemples¶
L’utilisation la plus simple du contrôleur DMA consiste à déplacer des données d’un bloc de mémoire vers un autre. Cela peut être réalisé avec le code suivant
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
Notez que, bien que cet exemple reste dans une boucle d’attente pendant qu’il attend la fin du transfert, le programme pourrait tout aussi bien effectuer un travail utile pendant ce temps.
Une autre utilisation du contrôleur DMA, peut-être plus courante, consiste à transférer des données entre la mémoire et un périphérique d’E/S. Dans cette situation, l’adresse du registre d’E/S ne change pas à chaque transfert, mais l’adresse mémoire doit être incrémentée. Il est également nécessaire de contrôler le rythme du transfert afin de ne pas écrire de données avant qu’elles puissent être acceptées par un périphérique, ni les lire avant qu’elles soient prêtes, ce qui peut être contrôlé avec le champ treq_sel du registre de contrôle du canal DMA. Les différents champs du registre de contrôle de chaque canal DMA peuvent être empaquetés à l’aide de la méthode DMA.pack_ctrl() et dépaquetés à l’aide de la méthode statique DMA.unpack_ctrl(). Le code permettant de transférer des données d’un tableau d’octets vers la FIFO TX d’une machine d’état PIO, un octet à la fois, ressemble à ceci
# 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
)
Notez que dans cet exemple, la valeur donnée pour l’adresse d’écriture est simplement la machine d’état PIO à laquelle nous envoyons les données. Cela fonctionne parce que les machines d’état PIO présentent le protocole tampon, permettant un accès direct à leurs registres de FIFO de données.
Constructeur¶
- class rp2.DMA¶
Réserve l’un des canaux du contrôleur DMA pour un usage exclusif.
- config(read: 'int | _AnyReadableBuf | None' = None, write: 'int | _AnyWritableBuf | None' = None, count: int | None = None, ctrl: int | None = None, trigger: bool = False) None¶
Configure les registres DMA du canal et démarre éventuellement le transfert. Les paramètres sont :
read : L’adresse à partir de laquelle le contrôleur DMA commencera à lire les données, ou un objet qui fournira les données à lire. Il peut s’agir d’un entier ou de tout objet qui prend en charge le protocole tampon.
write : L’adresse vers laquelle le contrôleur DMA commencera à écrire, ou un objet dans lequel les données seront écrites. Il peut s’agir d’un entier ou de tout objet qui prend en charge le protocole tampon.
count : Le nombre de transferts de bus qui s’exécuteront avant que ce canal ne s’arrête. Notez qu’il s’agit du nombre de transferts, et non du nombre d’octets. Si les transferts font 2 ou 4 octets de large, la quantité totale de données déplacées (et donc la taille du tampon requis) doit être multipliée en conséquence.
ctrl : La valeur du registre de contrôle DMA. Il s’agit d’une valeur entière qui est généralement empaquetée à l’aide de
DMA.pack_ctrl().trigger : Démarre éventuellement le transfert immédiatement.
- irq(handler: Callable[[DMA], None] | None = None, hard: bool = False) Callable¶
Renvoie l’objet IRQ de ce canal DMA et le configure éventuellement.
- close() None¶
Libère la réservation du canal DMA sous-jacent et libère le gestionnaire d’interruption. L’objet
DMAne peut pas être utilisé après cette opération.
- pack_ctrl(default: int | None = None, **kwargs) int¶
Empaquette les valeurs fournies dans les arguments nommés dans les champs nommés d’une nouvelle valeur de registre de contrôle. Tout champ non fourni sera défini sur une valeur par défaut. La valeur par défaut sera soit tirée de la valeur
defaultfournie, soit, si celle-ci n’est pas donnée, une valeur par défaut adaptée au canal actuel ; définir ceci sur la valeur actuelle de l’attributDMA.ctrloffre un moyen simple de remplacer un sous-ensemble des champs.Les clés des arguments nommés peuvent être n’importe quelle clé renvoyée par la méthode
DMA.unpack_ctrl(). Les valeurs modifiables sont :enable :
boolÀ définir pour activer le canal (par défaut :True).high_pri :
boolRend le trafic de bus de ce canal hautement prioritaire (par défaut :False).size :
intTaille de transfert : 0=octet, 1=demi-mot, 2=mot (par défaut : 2).inc_read :
boolIncrémente l’adresse de lecture après chaque transfert (par défaut :True).inc_write :
boolIncrémente l’adresse d’écriture après chaque transfert (par défaut :True).ring_size :
intSi non nul, seuls lesring_sizebits de poids faible de l’un des registres d’adresse changeront lorsqu’une adresse est incrémentée, faisant boucler l’adresse à la prochaine limite de1 << ring_sizeoctets. L’adresse qui est bouclée est contrôlée par l’indicateurring_sel. Une valeur de zéro désactive le bouclage d’adresse.ring_sel :
boolÀ définir surFalsepour quering_sizes’applique à l’adresse de lecture, ou surTruepour qu’il s’applique à l’adresse d’écriture.chain_to :
intLe numéro de canal d’un canal à déclencher une fois ce transfert terminé. Définir cette valeur sur le propre numéro de canal de cet objet DMA désactive le chaînage (c’est le comportement par défaut).treq_sel :
intSélectionne un signal de requête de transfert (Transfer Request). Voir la section 2.5.3 de la fiche technique du RP2040 pour plus de détails.irq_quiet :
boolNe génère pas d’interruption à la fin de chaque transfert. Les interruptions seront générées à la place lorsqu’une valeur nulle est écrite dans le registre de déclenchement, ce qui arrêtera une séquence de transferts chaînés (par défaut :True).bswap :
boolSi défini sur true, les octets dans les mots ou demi-mots seront inversés avant l’écriture (par défaut :True).sniff_en :
boolÀ définir surTruepour permettre l’accès aux données par le matériel de surveillance (sniff) de la puce (par défaut :False).write_err :
boolDéfinir ceci surTrueeffacera une erreur d’écriture précédemment signalée.read_err :
boolDéfinir ceci surTrueeffacera une erreur de lecture précédemment signalée.
Voir la description du registre
CH0_CTRL_TRIGdans la section 2.5.7 de la fiche technique du RP2040 pour plus de détails sur tous ces champs.
- unpack_ctrl(value: int) dict¶
Dépaquette la valeur d’un registre de contrôle de canal DMA en un dictionnaire avec des paires clé/valeur pour chacun des champs du registre de contrôle. value est la valeur du registre
ctrlà dépaqueter.Cette méthode renverra les valeurs de toutes les clés qui peuvent être passées à
DMA.pack_ctrl. De plus, elle renverra également les indicateurs en lecture seule du registre de contrôle :busy, qui passe à l’état haut lorsqu’un transfert démarre et à l’état bas lorsqu’il se termine, etahb_err, qui est le OU logique des indicateursread_erretwrite_err. Ces valeurs seront ignorées lors de l’empaquetage, de sorte que le dictionnaire créé en dépaquetant un registre de contrôle peut être utilisé directement comme arguments nommés pour l’empaquetage.
- active(value: bool | None = None, /) bool¶
Obtient ou définit si le canal DMA est actuellement en cours d’exécution.
>>> sm.active() 0 >>> sm.active(1) >>> while sm.active(): ... pass
- read: int¶
Cet attribut reflète l’adresse à partir de laquelle le prochain transfert de bus lira. Il peut être écrit soit avec un entier, soit avec un objet qui prend en charge le protocole tampon, et cela a un effet immédiat.
- write: int¶
Cet attribut reflète l’adresse vers laquelle le prochain transfert de bus écrira. Il peut être écrit soit avec un entier, soit avec un objet qui prend en charge le protocole tampon, et cela a un effet immédiat.
- count: int¶
La lecture de cet attribut renverra le nombre de transferts de bus restants dans la séquence de transfert actuelle. L’écriture de cet attribut définit le nombre total de transferts de la prochaine séquence de transfert.
- ctrl: int¶
Cet attribut reflète le registre de contrôle du canal DMA. Il est généralement écrit avec un entier empaqueté à l’aide de la méthode
DMA.pack_ctrl(). La valeur de registre renvoyée peut être dépaquetée à l’aide de la méthodeDMA.unpack_ctrl().
- channel: int¶
Le numéro de canal du canal DMA. Il peut être passé dans l’argument
chain_todeDMA.pack_ctrl()sur un autre canal pour permettre le chaînage DMA.
- registers: 'memoryview'¶
Cet attribut est un objet de type tableau qui permet un accès direct aux registres du canal DMA. L’index se fait par mot, plutôt que par octet, de sorte que les index de registre correspondent aux décalages d’adresse de registre divisés par 4. Voir la fiche technique du RP2040 pour les détails des registres.
Chaînage et accès au registre de déclenchement¶
Le contrôleur DMA du RP2040 offre quelques fonctionnalités avancées permettant à un canal DMA d’initier un transfert sur un autre canal. L’une est l’utilisation de la valeur chain_to dans le registre de contrôle et l’autre est l’écriture dans l’un des registres du canal DMA qui a un effet de déclenchement. Couplée à la capacité d’un canal DMA à écrire directement dans les DMA.registers d’un autre canal, cela permet d’effectuer des transactions complexes sans aucune intervention du processeur.
Vous trouverez ci-dessous un exemple utilisant à la fois le chaînage et le déclenchement par registre pour implémenter le rassemblement de plusieurs blocs de données vers une seule destination. Tous les détails de ces fonctionnalités se trouvent dans la section 2.5 de la fiche technique du RP2040, et le code ci-dessous est une version pythonique de l’exemple de la sous-section 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)
Cet exemple reste en attente pendant qu’il attend la fin du transfert ; il pourrait également définir un gestionnaire d’interruption et revenir immédiatement.