aboutsummaryrefslogtreecommitdiffstats
path: root/examples/test-usart-dma.cpp
blob: 4a63044e30a75c81e3a4f1bec068d857df0f6510 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
/**
 * @file examples/test-usart-dma.cpp
 * @author Marti Bolivar <mbolivar@leaflabs.com>
 *
 * Simple test of DMA used with a USART receiver.
 *
 * Configures a USART receiver for use with DMA.  Received bytes are
 * placed into a buffer, with an interrupt firing when the buffer is
 * full.  At that point, the USART transmitter will print the contents
 * of the byte buffer.  The buffer is continually filled and refilled
 * in this manner.
 *
 * This example isn't very robust; don't use it in production.  In
 * particular, since the buffer keeps filling (DMA_CIRC_MODE is set),
 * if you keep sending characters after filling the buffer, you'll
 * overwrite earlier bytes; this may happen before those earlier bytes
 * are done printing.
 *
 * This code is released into the public domain.
 */

#include <libmaple/dma.h>
#include <libmaple/usart.h>
#include <libmaple/gpio.h>

#include <wirish/wirish.h>

/*
 * Configuration and state
 */

// Serial port and DMA configuration. You can change these to suit
// your purposes.
HardwareSerial *serial = &Serial2;
#define USART_DMA_DEV DMA1
#define USART_RX_DMA_CHANNEL DMA_CH6
#define BAUD 9600

// This will store received USART characters.
#define BUF_SIZE 20
char rx_buf[BUF_SIZE];

// The interrupt handler, rx_dma_irq(), sets this to 1.
volatile uint32 irq_fired = 0;
// Used to store DMA interrupt status register (ISR) bits inside
// rx_dma_irq(). This helps explain what's going on inside loop(); see
// comments below.
volatile uint32 isr = 0;

/*
 * Helper functions
 */

// This is our DMA interrupt handler.
void rx_dma_irq(void) {
    irq_fired = 1;
    isr = dma_get_isr_bits(USART_DMA_DEV, USART_RX_DMA_CHANNEL);
}

// Configure the USART receiver for use with DMA:
// 1. Turn it on.
// 2. Set the "DMA request on RX" bit in USART_CR3 (USART_CR3_DMAR).
void setup_usart(void) {
    serial->begin(BAUD);
    usart_dev *serial_dev = serial->c_dev();
    serial_dev->regs->CR3 = USART_CR3_DMAR;
}

// Configure the DMA controller to serve DMA requests from the USART.
void setup_dma_xfer(void) {
    dma_init(USART_DMA_DEV);
    dma_setup_transfer(USART_DMA_DEV, USART_RX_DMA_CHANNEL,
                       &serial->c_dev()->regs->DR, DMA_SIZE_8BITS,
                       rx_buf,                     DMA_SIZE_8BITS,
                       (DMA_MINC_MODE | DMA_CIRC_MODE | DMA_TRNS_CMPLT));
    dma_set_num_transfers(USART_DMA_DEV, USART_RX_DMA_CHANNEL,
                          BUF_SIZE - 1);
    dma_attach_interrupt(USART_DMA_DEV, USART_RX_DMA_CHANNEL, rx_dma_irq);
    dma_enable(USART_DMA_DEV, USART_RX_DMA_CHANNEL);
}

/*
 * setup() and loop()
 */

void setup(void) {
    pinMode(BOARD_LED_PIN, OUTPUT);
    setup_dma_xfer();
    setup_usart();
}

void loop(void) {
    toggleLED();
    delay(100);

    // See if the interrupt handler got called since the last time we
    // checked.
    if (irq_fired) {
        serial->println("** IRQ **");
        // Notice how the interrupt status register (ISR) bits show
        // transfer complete _and_ half-complete here, but the ISR
        // bits we print next will be zero. That's because the
        // variable "isr" gets set _inside_ rx_dma_irq(). After it
        // exits, libmaple cleans up by clearing the tube's ISR
        // bits. (If it didn't, and we forgot to, the interrupt would
        // repeatedly fire forever.)
        serial->print("ISR bits: 0x");
        serial->println(isr, HEX);
        irq_fired = 0;
    }

    // Print the ISR bits.
    //
    // Notice that the "transfer half-complete" ISR flag gets set when
    // we reach the rx_buf half-way point. This is true even though we
    // don't tell the DMA controller to interrupt us on a
    // half-complete transfer. That is, the ISR bits get set at the
    // right times no matter what; we just don't get interrupted
    // unless we asked. (If an error or other problem occurs, the
    // relevant ISR bits will get set in the same way).
    serial->print("[");
    serial->print(millis());
    serial->print("]\tISR bits: 0x");
    uint8 isr_bits = dma_get_isr_bits(USART_DMA_DEV, USART_RX_DMA_CHANNEL);
    serial->print(isr_bits, HEX);

    // Print the contents of rx_buf. If you keep typing after it fills
    // up, the new characters will overwrite the old ones, thanks to
    // DMA_CIRC_MODE.
    serial->print("\tCharacter buffer contents: '");
    serial->print(rx_buf);
    serial->println("'");
    if (isr_bits == 0x7) {
        serial->println("** Clearing ISR bits.");
        dma_clear_isr_bits(USART_DMA_DEV, USART_RX_DMA_CHANNEL);
    }
}

// ------- init() and main() --------------------------------------------------

// Force init to be called *first*, i.e. before static object allocation.
// Otherwise, statically allocated objects that need libmaple may fail.
__attribute__((constructor)) void premain() {
    init();
}

int main(void) {
    setup();

    while (true) {
        loop();
    }
    return 0;
}