class DMA – acesso ao controlador DMA do RP2040¶
A classe DMA oferece acesso ao controlador de Acesso Direto à Memória (DMA) do RP2040, disponibilizando a capacidade de mover dados entre blocos de memória e/ou registos de E/S. O controlador DMA tem as suas próprias ligações de bus master de leitura e escrita independentes ao barramento, e cada canal DMA pode ler dados de um endereço e escrevê-los noutro de forma independente, incrementando opcionalmente um ou ambos os ponteiros, permitindo realizar transferências em nome do processador enquanto este executa outras tarefas ou entra num estado de baixo consumo. O controlador DMA do RP2040 tem 12 canais DMA independentes que podem ser executados em simultâneo. Para detalhes completos sobre o sistema DMA do RP2040, consulte a secção 2.5 do RP2040 Datasheet.
Exemplos¶
A utilização mais simples do controlador DMA é mover dados de um bloco de memória para outro. Isto pode ser feito com o seguinte código:
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
Note que, embora este exemplo permaneça num ciclo de espera enquanto aguarda a conclusão da transferência, o programa poderia igualmente realizar trabalho útil durante esse tempo.
Outro uso, porventura mais comum do controlador DMA, é a transferência entre memória e um periférico de E/S. Nesta situação, o endereço do registo de E/S não muda em cada transferência, mas o endereço de memória precisa de ser incrementado. É também necessário controlar o ritmo da transferência para não escrever dados antes de o periférico os poder aceitar ou lê-los antes de estarem prontos; isso pode ser controlado com o campo treq_sel do registo de controlo do canal DMA. Os vários campos do registo de controlo de cada canal DMA podem ser agrupados usando o método DMA.pack_ctrl() e desagrupados usando o método estático DMA.unpack_ctrl(). O código para transferir dados de um array de bytes para o TX FIFO de uma máquina de estado PIO, um byte de cada vez, tem o seguinte aspeto:
# 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
)
Note que neste exemplo o valor dado para o endereço de escrita é simplesmente a máquina de estado PIO para a qual estamos a enviar os dados. Isto funciona porque as máquinas de estado PIO implementam o protocolo de buffer, permitindo acesso direto aos seus registos FIFO de dados.
Construtor¶
- class rp2.DMA¶
Reserva um dos canais do controlador DMA para uso exclusivo.
- config(read: 'int | _AnyReadableBuf | None' = None, write: 'int | _AnyWritableBuf | None' = None, count: int | None = None, ctrl: int | None = None, trigger: bool = False) None¶
Configura os registos DMA para o canal e opcionalmente inicia a transferência. Os parâmetros são:
read: O endereço a partir do qual o controlador DMA começará a ler dados, ou um objeto que fornecerá os dados a ler. Pode ser um inteiro ou qualquer objeto que suporte o protocolo de buffer.
write: O endereço para o qual o controlador DMA começará a escrever, ou um objeto no qual os dados serão escritos. Pode ser um inteiro ou qualquer objeto que suporte o protocolo de buffer.
count: O número de transferências de barramento que serão executadas antes de o canal parar. Note que este é o número de transferências, não o número de bytes. Se as transferências forem de 2 ou 4 bytes de largura, a quantidade total de dados movidos (e, portanto, o tamanho do buffer necessário) deverá ser multiplicada em conformidade.
ctrl: O valor para o registo de controlo DMA. É um valor inteiro tipicamente agrupado usando
DMA.pack_ctrl().trigger: Opcionalmente, inicia a transferência de imediato.
- irq(handler: Callable[[DMA], None] | None = None, hard: bool = False) Callable¶
Devolve o objeto IRQ deste canal DMA e opcionalmente configura-o.
- close() None¶
Liberta a reserva sobre o canal DMA subjacente e liberta o gestor de interrupções. O objeto
DMAnão pode ser utilizado após esta operação.
- pack_ctrl(default: int | None = None, **kwargs) int¶
Agrupa os valores fornecidos nos argumentos de palavra-chave nos campos nomeados de um novo valor de registo de controlo. Qualquer campo não fornecido será definido com um valor predefinido. O valor predefinido será retirado do valor
defaultfornecido ou, caso não seja fornecido, um valor predefinido adequado para o canal atual; definir este como o valor atual do atributoDMA.ctrloferece uma forma fácil de substituir um subconjunto dos campos.As chaves dos argumentos de palavra-chave podem ser qualquer chave devolvida pelo método
DMA.unpack_ctrl(). Os valores graváveis são:enable:
boolDefinir para ativar o canal (predefinição:True).high_pri:
boolTorna o tráfego de barramento deste canal de alta prioridade (predefinição:False).size:
intTamanho da transferência: 0=byte, 1=meia palavra, 2=palavra (predefinição: 2).inc_read:
boolIncrementar o endereço de leitura após cada transferência (predefinição:True).inc_write:
boolIncrementar o endereço de escrita após cada transferência (predefinição:True).ring_size:
intSe não-zero, apenas osring_sizebits inferiores de um registo de endereço serão alterados quando um endereço é incrementado, fazendo o endereço retornar ao início no próximo limite de1 << ring_sizebytes. O endereço que é envolvido é controlado pelo sinalizadorring_sel. Um valor zero desativa o envolvimento de endereço.ring_sel:
boolDefinir comoFalsepara aplicarring_sizeao endereço de leitura ouTruepara aplicar ao endereço de escrita.chain_to:
intO número do canal a acionar após a conclusão desta transferência. Definir este valor como o número do próprio canal deste objeto DMA desativa o encadeamento (este é o valor predefinido).treq_sel:
intSeleciona um sinal de Pedido de Transferência. Consulte a secção 2.5.3 do datasheet do RP2040 para detalhes.irq_quiet:
boolNão gera interrupção no final de cada transferência. As interrupções serão geradas quando um valor zero for escrito no registo de acionamento, o que irá parar uma sequência de transferências encadeadas (predefinição:True).bswap:
boolSe definido como verdadeiro, os bytes em palavras ou meias-palavras serão invertidos antes de escrever (predefinição:True).sniff_en:
boolDefinir comoTruepara permitir que os dados sejam acedidos pelo hardware de sniff do chip (predefinição:False).write_err:
boolDefinir comoTrueirá limpar um erro de escrita previamente reportado.read_err:
boolDefinir comoTrueirá limpar um erro de leitura previamente reportado.
Consulte a descrição do registo
CH0_CTRL_TRIGna secção 2.5.7 do datasheet do RP2040 para detalhes de todos estes campos.
- unpack_ctrl(value: int) dict¶
Desagrupa um valor para um registo de controlo de canal DMA num dicionário com pares chave/valor para cada campo do registo de controlo. value é o valor do registo
ctrla desagrupar.Este método devolverá valores para todas as chaves que podem ser passadas a
DMA.pack_ctrl. Além disso, devolverá também os sinalizadores somente de leitura no registo de controlo:busy, que fica alto quando uma transferência começa e baixo quando termina, eahb_err, que é o OR lógico dos sinalizadoresread_errewrite_err. Estes valores serão ignorados durante o agrupamento, de modo que o dicionário criado pelo desagrupamento de um registo de controlo pode ser usado diretamente como argumentos de palavra-chave para o agrupamento.
- active(value: bool | None = None, /) bool¶
Obtém ou define se o canal DMA está atualmente em execução.
>>> sm.active() 0 >>> sm.active(1) >>> while sm.active(): ... pass
- read: int¶
Este atributo reflete o endereço a partir do qual a próxima transferência de barramento lerá. Pode ser escrito com um inteiro ou com um objeto que suporte o protocolo de buffer, sendo o efeito imediato.
- write: int¶
Este atributo reflete o endereço para o qual a próxima transferência de barramento escreverá. Pode ser escrito com um inteiro ou com um objeto que suporte o protocolo de buffer, sendo o efeito imediato.
- count: int¶
Ler este atributo devolverá o número de transferências de barramento restantes na sequência de transferência atual. Escrever neste atributo define o número total de transferências para a próxima sequência de transferência.
- ctrl: int¶
Este atributo reflete o registo de controlo do canal DMA. Tipicamente é escrito com um inteiro agrupado usando o método
DMA.pack_ctrl(). O valor de registo devolvido pode ser desagrupado usando o métodoDMA.unpack_ctrl().
- channel: int¶
O número do canal DMA. Este pode ser passado no argumento
chain_todeDMA.pack_ctrl()noutro canal para permitir o encadeamento de DMA.
- registers: 'memoryview'¶
Este atributo é um objeto semelhante a um array que permite acesso direto aos registos do canal DMA. O índice é por palavra, não por byte, pelo que os índices de registo são os deslocamentos de endereço de registo divididos por 4. Consulte o datasheet do RP2040 para detalhes dos registos.
Encadeamento e acesso ao registo de acionamento¶
O controlador DMA do RP2040 oferece algumas funcionalidades avançadas para permitir que um canal DMA inicie uma transferência noutro canal. Uma delas é o uso do valor chain_to no registo de controlo e a outra é escrever num dos registos do canal DMA que tem efeito de acionamento. Quando combinado com a capacidade de um canal DMA escrever diretamente nos DMA.registers de outro canal, isto permite que transações complexas sejam realizadas sem qualquer intervenção da CPU.
Em seguida, apresenta-se um exemplo de uso de encadeamento e de acionamento por registo para implementar a recolha de múltiplos blocos de dados num único destino. Os detalhes completos destas funcionalidades podem ser encontrados na secção 2.5 do datasheet do RP2040, e o código seguinte é uma versão Pythónica do exemplo da subsecção 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)
Este exemplo fica em espera enquanto aguarda a conclusão da transferência; em alternativa, poderia definir um gestor de interrupções e retornar imediatamente.