summaryrefslogtreecommitdiff
path: root/src/usart_helpers.c
blob: 41b553341184f88a820309fc76b0a8ba410fa825 (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
/*
 * This file is part of the libusbhost library
 * hosted at http://github.com/libusbhost/libusbhost
 *
 * Copyright (C) 2015 Amir Hammad <amir.hammad@hotmail.com>
 *
 *
 * libusbhost is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library.  If not, see <http://www.gnu.org/licenses/>.
 *
 */


#include "usart_helpers.h"
#define TINYPRINTF_OVERRIDE_LIBC 0
#define TINYPRINTF_DEFINE_TFP_SPRINTF 0
#include "tinyprintf.h"

#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <libopencm3/stm32/usart.h>
#include <libopencm3/stm32/dma.h>
#include <libopencm3/cm3/nvic.h>
#include <libopencmsis/core_cm3.h>


void usart_fprintf(struct dma_usart_file *f, const char *str, ...) {
	va_list va;
	va_start(va, str);
	tfp_format(f, putf, str, va);
	va_end(va);
}

void usart_init(uint32_t arg_usart, uint32_t baudrate) {
	usart_set_baudrate(arg_usart, baudrate);
	usart_set_databits(arg_usart, 8);
	usart_set_flow_control(arg_usart, USART_FLOWCONTROL_NONE);
	usart_set_mode(arg_usart, USART_MODE_TX | USART_MODE_RX);
	usart_set_parity(arg_usart, USART_PARITY_NONE);
	usart_set_stopbits(arg_usart, USART_STOPBITS_1);
	usart_enable(arg_usart);
}

void usart_dma_init(uint32_t usart, uint32_t baudrate, uint32_t dma, uint8_t stream, uint8_t channel) {
    usart_init(usart, baudrate);

    dma_stream_reset(dma, stream);
    dma_channel_select(dma, stream, DMA_SxCR_CHSEL(channel));
	dma_set_peripheral_address(dma, stream, (uint32_t)&USART_DR(usart));
	dma_set_transfer_mode(dma, stream, DMA_SxCR_DIR_MEM_TO_PERIPHERAL);
	dma_enable_memory_increment_mode(dma, stream);
	dma_set_peripheral_size(dma, stream, DMA_SxCR_PSIZE_8BIT);
	dma_set_memory_size(dma, stream, DMA_SxCR_MSIZE_8BIT);
	dma_set_priority(dma, stream, DMA_SxCR_PL_VERY_HIGH);
	dma_enable_transfer_complete_interrupt(dma, stream);
    usart_enable_tx_dma(usart);
}

void usart_kickoff_dma(uint32_t dma, uint8_t stream, uint8_t *buf, size_t len) {
    /* initiate transmission of new buffer */
	dma_set_memory_address(dma, stream, (uint32_t)buf); /* select active buffer address */
	dma_set_number_of_data(dma, stream, len);
	dma_enable_stream(dma, stream);
}

void schedule_dma(volatile struct dma_usart_file *f) {
    struct dma_buf *buf = f->buf;

    uint32_t xfr_len, xfr_start = buf->xfr_end;
    if (buf->wr_pos > xfr_start) /* no wraparound */
        xfr_len = buf->wr_pos - xfr_start;
    else /* wraparound */
        xfr_len = buf->len - xfr_start; /* schedule transfer until end of buffer */

    buf->xfr_start = xfr_start;
    buf->xfr_end = (xfr_start + xfr_len) % buf->len; /* handle wraparound */
    usart_kickoff_dma(f->dma, f->stream, buf->data + xfr_start, xfr_len);
}

int dma_fifo_push(volatile struct dma_buf *buf, char c) {
    if (buf->wr_pos == buf->xfr_start)
        return -EBUSY;

    buf->data[buf->wr_pos] = c;
    buf->wr_pos = (buf->wr_pos + 1) % buf->len;
    return 0;
}

void putf(void *file, char c) {
    volatile struct dma_usart_file *f = (struct dma_usart_file *)file;

    nvic_disable_irq(f->irqn);
    {
        /* push char to fifo, busy-loop if stalled to wait for USART to empty fifo via DMA */
        while (dma_fifo_push(f->buf, c) == -EBUSY) {
            nvic_enable_irq(f->irqn);
            nvic_disable_irq(f->irqn);
        }

        /* If the DMA stream is idle right now, schedule a transfer */
        if (!(DMA_SCR(f->dma, f->stream) & DMA_SxCR_EN) /* DMA is not running */
                && !dma_get_interrupt_flag(f->dma, f->stream, DMA_TCIF)/* DMA interrupt is clear */) {
            schedule_dma(f);
        }
    }
    nvic_enable_irq(f->irqn);
}