classe DMA – acesso ao controlador DMA do RP2040¶
A classe DMA oferece acesso ao controlador de Acesso Direto à Memória (DMA) do RP2040, fornecendo a capacidade de mover dados entre blocos de memória e/ou registradores de IO. O controlador DMA tem suas próprias conexões de barramento mestre de leitura e escrita, separadas, na malha do barramento, e cada canal DMA pode independentemente ler dados de um endereço e escrevê-los de volta em outro endereço, opcionalmente incrementando um ou ambos os ponteiros, permitindo que ele realize transferências em nome do processador enquanto o processador executa outras tarefas ou entra em um estado de baixo consumo. O controlador DMA do RP2040 tem 12 canais DMA independentes que podem ser executados concorrentemente. Para detalhes completos do sistema DMA do RP2040 veja a seção 2.5 do RP2040 Datasheet.
Exemplos¶
O uso mais simples do controlador DMA é mover dados de um bloco de memória para outro. Isto pode ser realizado 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
Observe que, embora este exemplo permaneça em um laço ocioso enquanto espera a transferência ser concluída, o programa poderia muito bem realizar algum trabalho útil nesse tempo.
Outro uso, talvez mais comum, do controlador DMA é transferir entre a memória e um periférico de IO. Nesta situação o endereço do registrador de IO não muda a cada transferência, mas o endereço de memória precisa ser incrementado. Também é necessário controlar o ritmo da transferência para não escrever dados antes que possam ser aceitos por um periférico ou lê-los antes que os dados estejam prontos, e isto pode ser controlado com o campo treq_sel do registrador de controle do canal DMA. Os vários campos do registrador de controle de cada canal DMA podem ser empacotados usando o método DMA.pack_ctrl() e desempacotados usando o método estático DMA.unpack_ctrl(). O código para transferir dados de um array de bytes para a FIFO de TX de uma máquina de estados PIO, um byte por vez, é assim:
# 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
)
Observe que neste exemplo o valor fornecido para o endereço de escrita é apenas a máquina de estados PIO para a qual estamos enviando os dados. Isto funciona porque as máquinas de estados PIO apresentam o protocolo de buffer, permitindo acesso direto a seus registradores de FIFO de dados.
Construtor¶
- class rp2.DMA¶
Reivindica 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 registradores do 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 os dados ou um objeto que fornecerá os dados a serem lidos. 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 deste canal parar. Observe que este é o número de transferências, não o número de bytes. Se as transferências tiverem 2 ou 4 bytes de largura, então a quantidade total de dados movidos (e, portanto, o tamanho do buffer necessário) precisa ser multiplicada de acordo.
ctrl: O valor para o registrador de controle do DMA. Este é um valor inteiro que normalmente é empacotado usando o
DMA.pack_ctrl().trigger: Opcionalmente, inicia a transferência imediatamente.
- irq(handler: Callable[[DMA], None] | None = None, hard: bool = False) Callable¶
Retorna o objeto IRQ para este canal DMA e, opcionalmente, o configura.
- close() None¶
Libera a reivindicação sobre o canal DMA subjacente e libera o manipulador de interrupção. O objeto
DMAnão pode ser usado após esta operação.
- pack_ctrl(default: int | None = None, **kwargs) int¶
Empacota os valores fornecidos nos argumentos nomeados nos campos nomeados de um novo valor de registrador de controle. Qualquer campo que não for fornecido será definido com um valor padrão. O padrão será obtido do valor
defaultfornecido ou, se este não for dado, um padrão adequado para o canal atual; definir isto para o valor atual do atributoDMA.ctrlfornece uma maneira fácil de sobrescrever um subconjunto dos campos.As chaves para os argumentos nomeados podem ser qualquer chave retornada pelo método
DMA.unpack_ctrl(). Os valores graváveis são:enable:
boolDefina para habilitar o canal (padrão:True).high_pri:
boolTorna o tráfego de barramento deste canal de alta prioridade (padrão:False).size:
intTamanho da transferência: 0=byte, 1=meia palavra, 2=palavra (padrão: 2).inc_read:
boolIncrementa o endereço de leitura após cada transferência (padrão:True).inc_write:
boolIncrementa o endereço de escrita após cada transferência (padrão:True).ring_size:
intSe for diferente de zero, apenas osring_sizebits inferiores de um dos registradores de endereço mudarão quando um endereço for incrementado, fazendo com que o endereço dê a volta no próximo limite de1 << ring_sizebytes. Qual endereço dá a volta é controlado pela flagring_sel. Um valor zero desabilita o retorno do endereço.ring_sel:
boolDefina comoFalsepara que oring_sizeseja aplicado ao endereço de leitura ouTruepara aplicá-lo ao endereço de escrita.chain_to:
intO número do canal de um canal a ser disparado após a conclusão desta transferência. Definir este valor para o número do próprio canal deste objeto DMA desabilita o encadeamento (este é o padrão).treq_sel:
intSeleciona um sinal de Requisição de Transferência. Veja a seção 2.5.3 no datasheet do RP2040 para detalhes.irq_quiet:
boolNão gera interrupção ao final de cada transferência. As interrupções serão geradas, em vez disso, quando um valor zero for escrito no registrador de disparo, o que interromperá uma sequência de transferências encadeadas (padrão:True).bswap:
boolSe definido como verdadeiro, os bytes em palavras ou meias palavras serão invertidos antes da escrita (padrão:True).sniff_en:
boolDefina comoTruepara permitir que os dados sejam acessados pelo hardware de sniff do chip (padrão:False).write_err:
boolDefinir isto comoTruelimpará um erro de escrita reportado anteriormente.read_err:
boolDefinir isto comoTruelimpará um erro de leitura reportado anteriormente.
Veja a descrição do registrador
CH0_CTRL_TRIGna seção 2.5.7 do datasheet do RP2040 para detalhes de todos esses campos.
- unpack_ctrl(value: int) dict¶
Desempacota um valor de um registrador de controle de canal DMA em um dicionário com pares chave/valor para cada um dos campos do registrador de controle. value é o valor do registrador
ctrla ser desempacotado.Este método retornará valores para todas as chaves que podem ser passadas a
DMA.pack_ctrl. Além disso, também retornará as flags somente leitura do registrador de controle:busy, que vai para alto quando uma transferência começa e para baixo quando ela termina, eahb_err, que é o OU lógico das flagsread_errewrite_err. Esses valores serão ignorados ao empacotar, de modo que o dicionário criado ao desempacotar um registrador de controle pode ser usado diretamente como os argumentos nomeados para o empacotamento.
- active(value: bool | None = None, /) bool¶
Obtém ou define se o canal DMA está em execução no momento.
>>> 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á. Ele pode ser escrito com um inteiro ou com um objeto que suporte o protocolo de buffer, e fazer isso tem efeito imediato.
- write: int¶
Este atributo reflete o endereço para o qual a próxima transferência de barramento escreverá. Ele pode ser escrito com um inteiro ou com um objeto que suporte o protocolo de buffer, e fazer isso tem efeito imediato.
- count: int¶
Ler este atributo retornará o número de transferências de barramento restantes na sequência de transferência atual. Escrever este atributo define o número total de transferências para a próxima sequência de transferência.
- ctrl: int¶
Este atributo reflete o registrador de controle do canal DMA. Ele normalmente é escrito com um inteiro empacotado usando o método
DMA.pack_ctrl(). O valor de registrador retornado pode ser desempacotado usando o métodoDMA.unpack_ctrl().
- channel: int¶
O número do canal do canal DMA. Este pode ser passado no argumento
chain_todeDMA.pack_ctrl()em outro canal para permitir o encadeamento de DMA.
- registers: 'memoryview'¶
Este atributo é um objeto semelhante a um array que permite acesso direto aos registradores do canal DMA. O índice é por palavra, em vez de por byte, de modo que os índices de registrador são os deslocamentos de endereço de registrador divididos por 4. Veja o datasheet do RP2040 para detalhes dos registradores.
Acesso a encadeamento e registrador de disparo¶
O controlador DMA no RP2040 oferece alguns recursos avançados para permitir que um canal DMA inicie uma transferência em outro canal. Um é o uso do valor chain_to no registrador de controle e o outro é a escrita em um dos registradores do canal DMA que tem um efeito de disparo. Quando combinado com a capacidade de ter um canal DMA escrevendo diretamente nos DMA.registers de outro canal, isto permite que transações complexas sejam realizadas sem qualquer intervenção da CPU.
Abaixo está um exemplo do uso tanto de encadeamento quanto de disparo por registrador para implementar a coleta de múltiplos blocos de dados em um único destino. Detalhes completos desses recursos podem ser encontrados na seção 2.5 do datasheet do RP2040, e o código abaixo é uma versão Pythonic do exemplo da subseçã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 permanece ocioso enquanto espera a transferência ser concluída; alternativamente, ele poderia definir um manipulador de interrupção e retornar imediatamente.