116 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			116 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /**
 | |
|  * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
 | |
|  *
 | |
|  * SPDX-License-Identifier: BSD-3-Clause
 | |
|  */
 | |
| 
 | |
| // Use two DMA channels to make a programmed sequence of data transfers to the
 | |
| // UART (a data gather operation). One channel is responsible for transferring
 | |
| // the actual data, the other repeatedly reprograms that channel.
 | |
| 
 | |
| #include <stdio.h>
 | |
| #include "pico/stdlib.h"
 | |
| #include "hardware/dma.h"
 | |
| #include "hardware/structs/uart.h"
 | |
| 
 | |
| // These buffers will be DMA'd to the UART, one after the other.
 | |
| 
 | |
| const char word0[] = "Transferring ";
 | |
| const char word1[] = "one ";
 | |
| const char word2[] = "word ";
 | |
| const char word3[] = "at ";
 | |
| const char word4[] = "a ";
 | |
| const char word5[] = "time.\n";
 | |
| 
 | |
| // Note the order of the fields here: it's important that the length is before
 | |
| // the read address, because the control channel is going to write to the last
 | |
| // two registers in alias 3 on the data channel:
 | |
| //           +0x0        +0x4          +0x8          +0xC (Trigger)
 | |
| // Alias 0:  READ_ADDR   WRITE_ADDR    TRANS_COUNT   CTRL
 | |
| // Alias 1:  CTRL        READ_ADDR     WRITE_ADDR    TRANS_COUNT
 | |
| // Alias 2:  CTRL        TRANS_COUNT   READ_ADDR     WRITE_ADDR
 | |
| // Alias 3:  CTRL        WRITE_ADDR    TRANS_COUNT   READ_ADDR
 | |
| //
 | |
| // This will program the transfer count and read address of the data channel,
 | |
| // and trigger it. Once the data channel completes, it will restart the
 | |
| // control channel (via CHAIN_TO) to load the next two words into its control
 | |
| // registers.
 | |
| 
 | |
| const struct {uint32_t len; const char *data;} control_blocks[] = {
 | |
|     {count_of(word0) - 1, word0}, // Skip null terminator
 | |
|     {count_of(word1) - 1, word1},
 | |
|     {count_of(word2) - 1, word2},
 | |
|     {count_of(word3) - 1, word3},
 | |
|     {count_of(word4) - 1, word4},
 | |
|     {count_of(word5) - 1, word5},
 | |
|     {0, NULL}                     // Null trigger to end chain.
 | |
| };
 | |
| 
 | |
| int main() {
 | |
| #ifndef uart_default
 | |
| #warning dma/control_blocks example requires a UART
 | |
| #else
 | |
|     stdio_init_all();
 | |
|     puts("DMA control block example:");
 | |
| 
 | |
|     // ctrl_chan loads control blocks into data_chan, which executes them.
 | |
|     int ctrl_chan = dma_claim_unused_channel(true);
 | |
|     int data_chan = dma_claim_unused_channel(true);
 | |
| 
 | |
|     // The control channel transfers two words into the data channel's control
 | |
|     // registers, then halts. The write address wraps on a two-word
 | |
|     // (eight-byte) boundary, so that the control channel writes the same two
 | |
|     // registers when it is next triggered.
 | |
| 
 | |
|     dma_channel_config c = dma_channel_get_default_config(ctrl_chan);
 | |
|     channel_config_set_transfer_data_size(&c, DMA_SIZE_32);
 | |
|     channel_config_set_read_increment(&c, true);
 | |
|     channel_config_set_write_increment(&c, true);
 | |
|     channel_config_set_ring(&c, true, 3); // 1 << 3 byte boundary on write ptr
 | |
| 
 | |
|     dma_channel_configure(
 | |
|         ctrl_chan,
 | |
|         &c,
 | |
|         &dma_hw->ch[data_chan].al3_transfer_count, // Initial write address
 | |
|         &control_blocks[0],                        // Initial read address
 | |
|         2,                                         // Halt after each control block
 | |
|         false                                      // Don't start yet
 | |
|     );
 | |
| 
 | |
|     // The data channel is set up to write to the UART FIFO (paced by the
 | |
|     // UART's TX data request signal) and then chain to the control channel
 | |
|     // once it completes. The control channel programs a new read address and
 | |
|     // data length, and retriggers the data channel.
 | |
| 
 | |
|     c = dma_channel_get_default_config(data_chan);
 | |
|     channel_config_set_transfer_data_size(&c, DMA_SIZE_8);
 | |
|     channel_config_set_dreq(&c, DREQ_UART0_TX + 2 * uart_get_index(uart_default));
 | |
|     // Trigger ctrl_chan when data_chan completes
 | |
|     channel_config_set_chain_to(&c, ctrl_chan);
 | |
|     // Raise the IRQ flag when 0 is written to a trigger register (end of chain):
 | |
|     channel_config_set_irq_quiet(&c, true);
 | |
| 
 | |
|     dma_channel_configure(
 | |
|         data_chan,
 | |
|         &c,
 | |
|         &uart_get_hw(uart_default)->dr,
 | |
|         NULL,           // Initial read address and transfer count are unimportant;
 | |
|         0,              // the control channel will reprogram them each time.
 | |
|         false           // Don't start yet.
 | |
|     );
 | |
| 
 | |
|     // Everything is ready to go. Tell the control channel to load the first
 | |
|     // control block. Everything is automatic from here.
 | |
|     dma_start_channel_mask(1u << ctrl_chan);
 | |
| 
 | |
|     // The data channel will assert its IRQ flag when it gets a null trigger,
 | |
|     // indicating the end of the control block list. We're just going to wait
 | |
|     // for the IRQ flag instead of setting up an interrupt handler.
 | |
|     while (!(dma_hw->intr & 1u << data_chan))
 | |
|         tight_loop_contents();
 | |
|     dma_hw->ints0 = 1u << data_chan;
 | |
| 
 | |
|     puts("DMA finished.");
 | |
| #endif
 | |
| }
 |