aboutsummaryrefslogtreecommitdiffstats
path: root/src/datenklo/uart.c
blob: 1521410bd06aec557806b74854c22c65fd946a2d (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
155
156
157
158
159
160
161
162
163
164
165
166
/* Software UART
 *
 * (C) 2019 by Andreas Eversberg <jolly@eversberg.eu>
 * All Rights Reserved
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include "../libdebug/debug.h"
#include "uart.h"

static uint32_t calc_parity(uint32_t data, uint8_t data_bits, enum uart_parity parity)
{
	int i;

	for (i = 0; i < data_bits; i++)
		parity |= (data >> i);
	parity &= 1;

	switch (parity) {
	case UART_PARITY_NONE:
	case UART_PARITY_SPACE:
		return 0;
	case UART_PARITY_MARK:
		return 1;
	case UART_PARITY_EVEN:
		return parity;
	case UART_PARITY_ODD:
		return parity ^ 1;
	}

	return 0; /* never reached */
}

int uart_init(uart_t *uart, void *inst, uint8_t data_bits, enum uart_parity parity, uint8_t stop_bits, int (*tx_cb)(void *inst), void (*rx_cb)(void *inst, int data, uint32_t flags))
{
	memset(uart, 0, sizeof(*uart));

	uart->inst = inst;
	uart->tx_cb = tx_cb;
	uart->rx_cb = rx_cb;
	uart->data_bits = data_bits;
	if (uart->data_bits > 9) {
		PDEBUG(DUART, DEBUG_ERROR, "Illegal number of data bits, please fix!\n");
		abort();
	}
	uart->parity = parity;
	uart->stop_bits = stop_bits;
	if (uart->stop_bits < 1 || uart->stop_bits > 2) {
		PDEBUG(DUART, DEBUG_ERROR, "Illegal number of stop bits, please fix!\n");
		abort();
	}
	uart->tx_pos = -1;
	uart->rx_pos = -1;
	uart->length = uart->stop_bits + !!uart->parity + uart->data_bits;

	return 0;
}

/* called by modulator to get next bit from uart */
int uart_tx_bit(uart_t *uart)
{
	uint32_t bit, parity;

	if (uart->tx_pos < 0) {
		/* no transmission, get data */
		uart->tx_data = uart->tx_cb(uart->inst);
		/* return 1, if no data has not be sent */
		if (uart->tx_data > 0x7fffffff)
			return 1;
		/* all bits after data are stop bits */
		uart->tx_data |= 0xffffffff << uart->data_bits;
		/* calculate parity */
		if (uart->parity)
			parity = calc_parity(uart->tx_data, uart->data_bits, uart->parity);
		/* add parity bit */
		if (uart->parity) {
			/* erase bit for parity */
			uart->tx_data ^= 1 << uart->data_bits;
			/* put parity bit */
			uart->tx_data |= parity << uart->data_bits;
		}
		/* start with the first bit */
		uart->tx_pos = 0;
		/* return start bit */
		return 0;
	}
	/* get bit to be send */
	bit = (uart->tx_data >> uart->tx_pos) & 1;
	/* go to next bit and set tx_pos to -1, if there is no more bit */
	if (++uart->tx_pos == uart->length)
		uart->tx_pos = -1;
	/* return bit */
	return bit;
}

int uart_is_tx(uart_t *uart)
{
	if (uart->tx_pos >= 0)
		return 1;
	return 0;
}

/* called by demodulator to indicate bit for uart */
void uart_rx_bit(uart_t *uart, int bit)
{
	uint32_t flags = 0;
	uint32_t parity;

	bit &= 1;

	/* if no data is receivd, check for start bit */
	if (uart->rx_pos < 0) {
		/* if no start bit */
		if (bit != 0 || uart->last_bit != 1)
			goto out;
		/* start bit */
		uart->rx_data = 0;
		uart->rx_pos = 0;
		return;
	}
	/* shift bit */
	uart->rx_data |= bit << (uart->rx_pos);
	/* end of transmission */
	if (++uart->rx_pos == uart->length) {
		/* turn off reception */
		uart->rx_pos = -1;
		/* check if parity is invalid */
		if (uart->parity) {
			parity = calc_parity(uart->rx_data, uart->data_bits, uart->parity);
			if (((uart->rx_data >> uart->data_bits) & 1) != parity)
				flags |= UART_PARITY_ERROR;
		}
		/* check if last stop bit is invalid */
		if (((uart->rx_data >> (uart->length - 1)) & 1) == 0) {
			flags |= UART_CODE_VIOLATION;
		}
		/* check if all bits are 0 */
		if (!uart->rx_data) {
			flags |= UART_BREAK;
		}
		/* clear all bits after data */
		uart->rx_data &= ~(0xffffffff << uart->data_bits);
		uart->rx_cb(uart->inst, uart->rx_data, flags);
	}

out:
	/* remember last bit for start bit detection (1 -> 0 transition) */
	uart->last_bit = bit;
}