mirror of https://github.com/ARMmbed/mbed-os.git
820 lines
24 KiB
C
820 lines
24 KiB
C
/* mbed Microcontroller Library
|
|
* Copyright (c) 2016 u-blox
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
/* The serial driver connects UART HW to mbed and also associates the UART
|
|
* HW with physical pins. Any physical pin can be linked to any UART,
|
|
* however the mbed serial port initialisation API makes no mention of
|
|
* which UART HW is to be used (only the pins) and hence the driver needs
|
|
* to make some decisions for itself.
|
|
*
|
|
* There are two and a half UARTs on the chip: UART0, UART1 and a
|
|
* lower-power, receive-only UART that is clocked from 32 kHz and can
|
|
* therefore be awake while the rest of the chip is sleeping peacefully.
|
|
* This provides maximal power saving, however the LP UART can only run
|
|
* at 9600 bits/s (which is quite sufficient for all NB-IoT needs).
|
|
*
|
|
* So, if the baud rate is 9600 the driver code configures the LP UART
|
|
* for Rx and UART0 for Tx. If the baud rate is not 9600 then it configures
|
|
* UART0 for both Rx and Tx. Unless... the Tx pin is the pin UART1_TX (it
|
|
* is an mbed convention to use the Tx pin), which is p6, in which case UART1
|
|
* is configured instead. This latter is not the normal case as this pin
|
|
* is intended to be used as a GPIO.
|
|
*
|
|
* If the baud rate is changed the driver reconfigures to match.
|
|
*
|
|
* TODO: implement asynchronous and flow control APIs.
|
|
*/
|
|
|
|
#include "mbed_assert.h"
|
|
#include "serial_api.h"
|
|
#include "pinmap.h"
|
|
|
|
#include "cmsis.h"
|
|
|
|
/* ----------------------------------------------------------------
|
|
* MACROS
|
|
* ----------------------------------------------------------------*/
|
|
|
|
/* Registers banks for the standard UARTs */
|
|
#define UART0_REG (*(volatile uart_ctrl_t *) UART0_BASE)
|
|
#define UART1_REG (*(volatile uart_ctrl_t *) UART1_BASE)
|
|
|
|
/* Masks for the UART control bits in the reset and clock enable registers */
|
|
#define UART0_CTRL (1 << 3)
|
|
#define UART1_CTRL (1 << 4)
|
|
#define UARTLP_CTRL (1 << 6)
|
|
|
|
/* Convert number of data bits to register values */
|
|
#define MIN_NUM_UART_DATA_BITS 5
|
|
#define MAX_NUM_UART_DATA_BITS 8
|
|
#define REGISTER_DATA_BITS(x) ((x) - MIN_NUM_UART_DATA_BITS)
|
|
|
|
/* Number of stop bits */
|
|
#define NUM_UART_STOP_BITS_1 1
|
|
#define NUM_UART_STOP_BITS_2 2
|
|
|
|
/* ----------------------------------------------------------------
|
|
* TYPES
|
|
* ----------------------------------------------------------------*/
|
|
|
|
/* Enum to identify the interrupt to the UART handler */
|
|
typedef enum {
|
|
IRQ_UART_ID_0_AND_LP,
|
|
IRQ_UART_ID_1,
|
|
NUM_IRQ_IDS
|
|
} irq_uart_id_t;
|
|
|
|
/* ----------------------------------------------------------------
|
|
* GLOBAL VARIABLES
|
|
* ----------------------------------------------------------------*/
|
|
|
|
/* The IRQ configuration variables, set up and named by mbed */
|
|
static uint32_t serial_irq_ids[NUM_IRQ_IDS] = {0};
|
|
static uart_irq_handler irq_handler = NULL;
|
|
|
|
/* RTX needs these */
|
|
int stdio_uart_inited = 0;
|
|
serial_t stdio_uart;
|
|
|
|
/* ----------------------------------------------------------------
|
|
* FUNCTION PROTOTYPES
|
|
* ----------------------------------------------------------------*/
|
|
|
|
static void init_config(serial_t *obj);
|
|
static void deinit_config(serial_t *obj);
|
|
static void set_baud(serial_t *obj, uint32_t baud_rate);
|
|
static void irq_enable(serial_t *obj);
|
|
static void irq_disable(serial_t *obj);
|
|
|
|
/* ----------------------------------------------------------------
|
|
* NON-API FUNCTIONS
|
|
* ----------------------------------------------------------------*/
|
|
|
|
/* Initialise the given serial config by setting the pin functions
|
|
* and then resetting the relevant HW */
|
|
static void init_config(serial_t *obj)
|
|
{
|
|
uint32_t x;
|
|
|
|
switch (obj->config) {
|
|
case SERIAL_CONFIG_UARTLP_RX_UART0_TX:
|
|
{
|
|
pin_function(obj->rx_pin, PIN_FUNCTION_LP_UART);
|
|
pin_function(obj->tx_pin, PIN_FUNCTION_UART0_TXD);
|
|
CLKEN_REG_BITSET = UARTLP_CTRL | UART0_CTRL;
|
|
obj->reg_base = &UART0_REG;
|
|
obj->index = IRQ_UART_ID_0_AND_LP;
|
|
/* Reset the LPUART and UART0 HW */
|
|
/* NOTE: RESET_REG_BITTOG doesn't have the desired
|
|
* effect, need to use BITSET and then BITCLR */
|
|
RESET_REG_BITSET |= 1ul << 6;
|
|
RESET_REG_BITCLR |= 1ul << 6;
|
|
RESET_REG_BITSET |= 1ul << 3;
|
|
RESET_REG_BITCLR |= 1ul << 3;
|
|
}
|
|
break;
|
|
case SERIAL_CONFIG_UART0_RX_UART0_TX:
|
|
{
|
|
pin_function(obj->rx_pin, PIN_FUNCTION_UART0_RXD);
|
|
pin_function(obj->tx_pin, PIN_FUNCTION_UART0_TXD);
|
|
CLKEN_REG_BITSET = UART0_CTRL;
|
|
obj->reg_base = &UART0_REG;
|
|
obj->index = IRQ_UART_ID_0_AND_LP;
|
|
/* Reset the UART0 HW */
|
|
RESET_REG_BITSET |= 1ul << 3;
|
|
RESET_REG_BITCLR |= 1ul << 3;
|
|
}
|
|
break;
|
|
case SERIAL_CONFIG_UART1_RX_UART1_TX:
|
|
{
|
|
pin_function(obj->rx_pin, PIN_FUNCTION_UART1_RXD);
|
|
pin_function(obj->tx_pin, PIN_FUNCTION_UART1_TXD);
|
|
CLKEN_REG_BITSET = UART1_CTRL;
|
|
obj->reg_base = &UART1_REG;
|
|
obj->index = IRQ_UART_ID_1;
|
|
/* Reset the UART1 HW */
|
|
RESET_REG_BITSET |= 1ul << 4;
|
|
RESET_REG_BITCLR |= 1ul << 4;
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
MBED_ASSERT(false);
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* Tickle the UART control register to make sure it is updated */
|
|
x = obj->reg_base->UARTLCR_H;
|
|
obj->reg_base->UARTLCR_H = x;
|
|
|
|
/* Set the FIFO. The meaning of the three FIFO interrupt-level
|
|
* bits are as follows:
|
|
*
|
|
* 0 = 1/8 full
|
|
* 1 = 1/4 full
|
|
* 2 = 1/2 full
|
|
* 3 = 3/4 full
|
|
* 4 = 7/8 full
|
|
*
|
|
* Set up the Rx FIFO to be used fully (but we will also set
|
|
* a timeout to get immediate notice) and also the Tx FIFO
|
|
* to be fully used. */
|
|
obj->reg_base->UARTIFLS = (obj->reg_base->UARTIFLS & ~(0x07 << 0)) | (4 << 0);
|
|
obj->reg_base->UARTIFLS = (obj->reg_base->UARTIFLS & ~(0x07 << 3)) | (4 << 3);
|
|
obj->reg_base->UARTLCR_H |= 1 << 4;
|
|
|
|
/* Enable for Tx and Rx (TODO: add CTS when we add flow control) */
|
|
obj->reg_base->UARTCR |= (1 << 8) | (1 << 9);
|
|
|
|
/* Now enable it */
|
|
obj->reg_base->UARTCR |= 1 << 0;
|
|
|
|
obj->format_set = false;
|
|
obj->baud_rate = 0;
|
|
obj->irq_rx_setting = IRQ_NOT_SET;
|
|
obj->irq_tx_setting = IRQ_NOT_SET;
|
|
}
|
|
|
|
/* Release a serial port */
|
|
static void deinit_config(serial_t *obj)
|
|
{
|
|
pin_function(obj->rx_pin, PIN_FUNCTION_UNCLAIMED);
|
|
pin_function(obj->tx_pin, PIN_FUNCTION_UNCLAIMED);
|
|
|
|
/* Disable UART */
|
|
obj->reg_base->UARTCR &= ~(1 << 0);
|
|
|
|
/* Flush transmit FIFO */
|
|
obj->reg_base->UARTLCR_H = 0;
|
|
|
|
/* Disable everything */
|
|
obj->reg_base->UARTCR = 0;
|
|
|
|
switch (obj->config) {
|
|
case SERIAL_CONFIG_UARTLP_RX_UART0_TX:
|
|
{
|
|
CLKEN_REG_BITCLR = UARTLP_CTRL | UART0_CTRL;
|
|
LP_UART_CTRL &= ~(0xF << 20); /* Disable all LP interrupts */
|
|
}
|
|
break;
|
|
case SERIAL_CONFIG_UART0_RX_UART0_TX:
|
|
{
|
|
CLKEN_REG_BITCLR = UART0_CTRL;
|
|
}
|
|
break;
|
|
case SERIAL_CONFIG_UART1_RX_UART1_TX:
|
|
{
|
|
CLKEN_REG_BITCLR = UART1_CTRL;
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
MBED_ASSERT(false);
|
|
}
|
|
break;
|
|
}
|
|
|
|
obj->config = MAX_NUM_SERIAL_CONFIGS;
|
|
obj->reg_base = NULL;
|
|
}
|
|
|
|
/* Set the baud rate for either of the two (non-LP) UARTS */
|
|
static void set_baud(serial_t *obj, uint32_t baud_rate)
|
|
{
|
|
uint32_t baud_rate_divider_i;
|
|
uint32_t baud_rate_divider_f;
|
|
uint32_t remainder;
|
|
uint32_t core_clock;
|
|
uint32_t x;
|
|
|
|
/* Baud rate divider calculation:
|
|
*
|
|
* The integer part is: clock / (16 * baud)
|
|
*
|
|
* The fractional part is: round (decimal_part * 64),
|
|
* ...where decimal part is, for example, 0.085
|
|
*
|
|
* decimal_part is: remainder / (16 * baud),
|
|
* ...where: remainder = core_clock % (baud * 16),
|
|
*
|
|
* So the fractional part becomes:
|
|
* round (decimal_part * 64) = round (remainder * 64 / (16 * baud)) = round (remainder * 4 / baud)
|
|
*/
|
|
|
|
/* Get the mean clock frequency */
|
|
core_clock = (CLK_FREQ_HIGHTARGET >> 1) + (CLK_FREQ_LOWTARGET >> 1);
|
|
/* Work out the actual clock frequency */
|
|
core_clock = (core_clock * CLOCKS_REFERENCE_CLOCK_FREQ) / (((CLK_FREQ_NREFCLKS + 1) & 0xFFFF) * (CLK_GATE_SYS & 0xFF));
|
|
baud_rate_divider_i = core_clock / (baud_rate << 4);
|
|
remainder = core_clock % (baud_rate << 4);
|
|
baud_rate_divider_f = ((remainder << 3) / baud_rate) >> 1;
|
|
/* Round it */
|
|
baud_rate_divider_f += ((remainder << 3) / baud_rate) & 1;
|
|
|
|
/* Disable UART while writing to control registers */
|
|
obj->reg_base->UARTCR &= ~(1 << 0);
|
|
|
|
obj->reg_base->UARTIBRD = baud_rate_divider_i;
|
|
obj->reg_base->UARTFBRD = baud_rate_divider_f;
|
|
|
|
/* Make IBRD and FBRD update */
|
|
x = obj->reg_base->UARTLCR_H;
|
|
obj->reg_base->UARTLCR_H = x;
|
|
|
|
/* Now enable the UART again */
|
|
obj->reg_base->UARTCR |= 1 << 0;
|
|
}
|
|
|
|
/* Set the NVIC bits */
|
|
static void irq_enable(serial_t *obj)
|
|
{
|
|
switch (obj->config) {
|
|
case SERIAL_CONFIG_UARTLP_RX_UART0_TX:
|
|
{
|
|
NVIC_EnableIRQ(UART0_IRQn);
|
|
NVIC_EnableIRQ(LPUART_IRQn);
|
|
}
|
|
break;
|
|
case SERIAL_CONFIG_UART0_RX_UART0_TX:
|
|
{
|
|
NVIC_EnableIRQ(UART0_IRQn);
|
|
}
|
|
break;
|
|
case SERIAL_CONFIG_UART1_RX_UART1_TX:
|
|
{
|
|
NVIC_EnableIRQ(UART1_IRQn);
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
MBED_ASSERT(false);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Unset the NVIC bits */
|
|
static void irq_disable(serial_t *obj)
|
|
{
|
|
switch (obj->config) {
|
|
case SERIAL_CONFIG_UARTLP_RX_UART0_TX:
|
|
{
|
|
NVIC_DisableIRQ(UART0_IRQn);
|
|
NVIC_DisableIRQ(LPUART_IRQn);
|
|
}
|
|
break;
|
|
case SERIAL_CONFIG_UART0_RX_UART0_TX:
|
|
{
|
|
NVIC_DisableIRQ(UART0_IRQn);
|
|
}
|
|
break;
|
|
case SERIAL_CONFIG_UART1_RX_UART1_TX:
|
|
{
|
|
NVIC_DisableIRQ(UART1_IRQn);
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
MBED_ASSERT(false);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* UART0 IRQ */
|
|
void IRQ7_UART0_Handler()
|
|
{
|
|
uint32_t id = serial_irq_ids[IRQ_UART_ID_0_AND_LP];
|
|
|
|
/* Check Rx and Rx Timeout bit */
|
|
if (UART0_REG.UARTMIS & ((1 << 4) | (1 << 6))) {
|
|
if (id != 0) {
|
|
irq_handler(id, RxIrq);
|
|
/* Reading the character clears the interrupt,
|
|
* no way to protect against another arriving
|
|
* while processing one */
|
|
}
|
|
}
|
|
/* Check Tx bit */
|
|
if (UART0_REG.UARTMIS & (1 << 5)) {
|
|
if (id != 0) {
|
|
irq_handler(id, TxIrq);
|
|
}
|
|
/* Not sure what clears the interrupt so clear it explicitly */
|
|
NVIC_ClearPendingIRQ(UART1_IRQn);
|
|
}
|
|
}
|
|
|
|
/* UART1 IRQ */
|
|
void IRQ8_UART1_Handler()
|
|
{
|
|
uint32_t id = serial_irq_ids[IRQ_UART_ID_1];
|
|
|
|
/* Check Rx and Rx Timeout bit */
|
|
if (UART1_REG.UARTMIS & ((1 << 4) | (1 << 6))) {
|
|
if (id != 0) {
|
|
irq_handler(id, RxIrq);
|
|
}
|
|
/* Reading the character clears the interrupt,
|
|
* no way to protect against another arriving
|
|
* while processing one */
|
|
}
|
|
/* Check Tx bit */
|
|
if (UART1_REG.UARTMIS & (1 << 5)) {
|
|
if (id != 0) {
|
|
irq_handler(id, TxIrq);
|
|
}
|
|
/* Not sure what clears the interrupt so clear it explicitly */
|
|
NVIC_ClearPendingIRQ(UART1_IRQn);
|
|
}
|
|
}
|
|
|
|
/* LP UART IRQ, receive only */
|
|
void IRQ16_LPUART_Handler()
|
|
{
|
|
uint32_t id = serial_irq_ids[IRQ_UART_ID_0_AND_LP];
|
|
|
|
if (id != 0) {
|
|
irq_handler(id, RxIrq);
|
|
|
|
/* Another character might have arrived while
|
|
* we are processing the last, so
|
|
* check status bits 8 to 10 again and pend
|
|
* interrupt if there's something there */
|
|
if (((LP_UART_STATUS >> 8) & 0x07) != 0) {
|
|
NVIC_SetPendingIRQ(LPUART_IRQn);
|
|
} else {
|
|
LP_UART_CTRL |= 1 << 27; /* Clear the interrupt */
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ----------------------------------------------------------------
|
|
* MBED API CALLS: SETUP FUNCTIONS
|
|
* ----------------------------------------------------------------*/
|
|
|
|
void serial_init(serial_t *obj, PinName tx, PinName rx)
|
|
{
|
|
uint32_t clock = CLK_FREQ_DIG_CLKS;
|
|
|
|
/* There are two and a half UARTs on the chip. The normal
|
|
* configuration is to use the LP_UART for Rx and UART0 for
|
|
* Tx. This gives maximal power saving in that the chip can
|
|
* wake up on receipt of data. However, this only works if the
|
|
* data rate is 9600 because that's the only data rate that
|
|
* the 32 kHz (i.e. RTC) clock driving the LP UART can support.
|
|
*
|
|
* So, if the data rate is 9600, use the LP_UART/UART0
|
|
* combination, otherwise use UART0 for both Rx and Tx. However,
|
|
* we don't know the data rate at this point so assume LP_UART
|
|
* (as this works at the default baud rate) and we can change
|
|
* our minds later.
|
|
*
|
|
* There is another serial port, UART1, which is normally used
|
|
* by the modem processor to send out debug. We only initialise
|
|
* that here if the Tx pin is UART1_TX. */
|
|
|
|
/* Wait for the clock to be stable */
|
|
while ((clock < CLK_FREQ_LOWTARGET) || (clock > CLK_FREQ_HIGHTARGET)) {
|
|
clock = CLK_FREQ_DIG_CLKS;
|
|
}
|
|
|
|
if (tx == UART1_TX) {
|
|
/* Use UART1 for Rx and Tx */
|
|
obj->config = SERIAL_CONFIG_UART1_RX_UART1_TX;
|
|
} else {
|
|
/* Use LP_UART for Rx, UART0 for Tx */
|
|
obj->config = SERIAL_CONFIG_UARTLP_RX_UART0_TX;
|
|
}
|
|
|
|
obj->rx_pin = rx;
|
|
obj->tx_pin = tx;
|
|
init_config(obj);
|
|
|
|
/* TODO: set rx pin Pull mode ? */
|
|
|
|
/* set default baud rate and format */
|
|
serial_baud(obj, 9600);
|
|
serial_format(obj, 8, ParityNone, 1);
|
|
|
|
if (tx == UART0_TX) {
|
|
/* The UART0 pins are the stdio pins */
|
|
stdio_uart_inited = 1;
|
|
stdio_uart = *obj;
|
|
}
|
|
}
|
|
|
|
void serial_free(serial_t *obj)
|
|
{
|
|
if (obj->tx_pin == UART0_TX) {
|
|
stdio_uart_inited = 0;
|
|
}
|
|
|
|
serial_irq_ids[obj->index] = 0;
|
|
|
|
/* Release the port HW */
|
|
deinit_config(obj);
|
|
}
|
|
|
|
void serial_baud(serial_t *obj, int baudrate)
|
|
{
|
|
bool switch_port_config = false;
|
|
bool format_set = obj->format_set;
|
|
uint8_t stop_bits = obj->format.stop_bits;
|
|
uint8_t data_bits = obj->format.data_bits;
|
|
SerialParity parity = (SerialParity) obj->format.parity;
|
|
irq_setting_t irq_rx_setting = obj->irq_rx_setting;
|
|
irq_setting_t irq_tx_setting = obj->irq_tx_setting;
|
|
|
|
if ((obj->config == SERIAL_CONFIG_UARTLP_RX_UART0_TX) && (baudrate != 9600)) {
|
|
/* If we were on LP UART but the baud rate is not 9600 then
|
|
* switch to the standard UART (as the LP UART can't go any higher
|
|
* because it's clocked from 32 kHz) */
|
|
deinit_config(obj);
|
|
obj->config = SERIAL_CONFIG_UART0_RX_UART0_TX;
|
|
init_config(obj);
|
|
switch_port_config = true;
|
|
} else if ((obj->config == SERIAL_CONFIG_UART0_RX_UART0_TX) && (baudrate == 9600)) {
|
|
/* If we were on UART0 but the baud rate is 9600 then switch to the
|
|
* LP UART for receive */
|
|
deinit_config(obj);
|
|
obj->config = SERIAL_CONFIG_UARTLP_RX_UART0_TX;
|
|
init_config(obj);
|
|
switch_port_config = true;
|
|
}
|
|
|
|
/* Disable UART while writing to control registers */
|
|
obj->reg_base->UARTCR &= ~(1 << 0);
|
|
|
|
if (switch_port_config) {
|
|
/* If the port was switched, switch the port configuration also */
|
|
if (format_set) {
|
|
/* This serial port has been previously set up so switch the
|
|
* settings across to this new configuration */
|
|
serial_format(obj, data_bits, parity, stop_bits);
|
|
}
|
|
if (irq_rx_setting != IRQ_NOT_SET) {
|
|
/* Do the same for Rx interrupts, if they were set */
|
|
serial_irq_set(obj, RxIrq, (irq_rx_setting == IRQ_ON));
|
|
}
|
|
if (irq_tx_setting != IRQ_NOT_SET) {
|
|
/* Do the same for Tx interrupts, if they were set */
|
|
serial_irq_set(obj, TxIrq, (irq_tx_setting == IRQ_ON));
|
|
}
|
|
}
|
|
|
|
switch (obj->config) {
|
|
case SERIAL_CONFIG_UARTLP_RX_UART0_TX:
|
|
{
|
|
/* Set LP UART to 9600 (numerator 75 (0x4B), denominator 256 (00 == 256)) */
|
|
LP_UART_CTRL = (LP_UART_CTRL & ~0xFFFF) | 0x004B;
|
|
set_baud(obj, baudrate);
|
|
}
|
|
break;
|
|
case SERIAL_CONFIG_UART0_RX_UART0_TX:
|
|
case SERIAL_CONFIG_UART1_RX_UART1_TX:
|
|
{
|
|
set_baud(obj, baudrate);
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
MBED_ASSERT(false);
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* Enable the UART again */
|
|
obj->reg_base->UARTCR |= 1 << 0;
|
|
|
|
obj->baud_rate = baudrate;
|
|
}
|
|
|
|
void serial_format(serial_t *obj, int data_bits, SerialParity parity, int stop_bits)
|
|
{
|
|
bool lp_also = false;
|
|
|
|
MBED_ASSERT(data_bits >= MIN_NUM_UART_DATA_BITS);
|
|
MBED_ASSERT(data_bits <= MAX_NUM_UART_DATA_BITS);
|
|
MBED_ASSERT(stop_bits >= NUM_UART_STOP_BITS_1);
|
|
MBED_ASSERT(stop_bits <= NUM_UART_STOP_BITS_2);
|
|
|
|
/* The LP UART is different to UARTs 0 and 1 so deal with it
|
|
* explicitly when required */
|
|
if (obj->config == SERIAL_CONFIG_UARTLP_RX_UART0_TX) {
|
|
lp_also = true;
|
|
}
|
|
|
|
/* Disable UART while writing to control registers */
|
|
obj->reg_base->UARTCR &= ~(1 << 0);
|
|
|
|
/* Set data bits (bits 5 and 6 of the UART0/1 register, bits 18 and 19 of the LP UART register) */
|
|
obj->reg_base->UARTLCR_H = (obj->reg_base->UARTLCR_H & ~(0x03 << 5)) | (REGISTER_DATA_BITS(data_bits) << 5);
|
|
if (lp_also) {
|
|
LP_UART_CTRL = (LP_UART_CTRL & ~(0x03 << 18)) | (REGISTER_DATA_BITS(data_bits) << 18);
|
|
}
|
|
obj->format.data_bits = (uint8_t) data_bits;
|
|
|
|
/* Set stop bits (bit 7 of the UART0/1 register) (there is no such setting for the LP UART) */
|
|
if (stop_bits == NUM_UART_STOP_BITS_1) {
|
|
/* Clear 2-stop-bits bit */
|
|
obj->reg_base->UARTLCR_H &= ~(1 << 7);
|
|
} else {
|
|
/* Set 2-stop-bits bit */
|
|
obj->reg_base->UARTLCR_H |= 1 << 7;
|
|
}
|
|
obj->format.stop_bits = (uint8_t) stop_bits;
|
|
|
|
/* Set parity */
|
|
switch (parity) {
|
|
case ParityNone:
|
|
{
|
|
/* Disable parity */
|
|
obj->reg_base->UARTLCR_H &= ~0x02;
|
|
if (lp_also)
|
|
{
|
|
LP_UART_CTRL &= ~(1 << 24);
|
|
}
|
|
}
|
|
break;
|
|
case ParityOdd:
|
|
{
|
|
/* Set even bit and enable parity */
|
|
obj->reg_base->UARTLCR_H = (obj->reg_base->UARTLCR_H | (1 << 3)) | (1 << 2);
|
|
if (lp_also)
|
|
{
|
|
LP_UART_CTRL |= (1 << 24) | (1 << 25);
|
|
}
|
|
}
|
|
break;
|
|
case ParityEven:
|
|
{
|
|
/* Clear even bit and enable parity */
|
|
obj->reg_base->UARTLCR_H = (obj->reg_base->UARTLCR_H & ~(1 << 3)) | (1 << 2);
|
|
if (lp_also)
|
|
{
|
|
LP_UART_CTRL &= ~(1 << 25);
|
|
LP_UART_CTRL |= 1 << 24;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
MBED_ASSERT(false);
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* Enable the UART again */
|
|
obj->reg_base->UARTCR |= 1 << 0;
|
|
|
|
obj->format.parity = (uint8_t) parity;
|
|
obj->format_set = true;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------
|
|
* MBED API CALLS: INTERRUPT FUNCTIONS
|
|
* ----------------------------------------------------------------*/
|
|
|
|
void serial_irq_handler(serial_t *obj, uart_irq_handler handler, uint32_t id)
|
|
{
|
|
irq_handler = handler;
|
|
serial_irq_ids[obj->index] = id;
|
|
}
|
|
|
|
void serial_irq_set(serial_t *obj, SerialIrq irq, uint32_t enable)
|
|
{
|
|
bool lp_also = false;
|
|
|
|
if (obj->config == SERIAL_CONFIG_UARTLP_RX_UART0_TX) {
|
|
lp_also = true;
|
|
}
|
|
|
|
/* Disable UART while writing to control registers */
|
|
obj->reg_base->UARTCR &= ~(1 << 0);
|
|
|
|
if (enable) {
|
|
switch (irq) {
|
|
case RxIrq:
|
|
{
|
|
/* Bit 4 for Rx and bit 6 for Rx Timeout */
|
|
obj->reg_base->UARTIMSC |= (1 << 4) | (1 << 6);
|
|
if (lp_also)
|
|
{
|
|
/* "Word Received" IRQ */
|
|
LP_UART_CTRL |= 1 << 23;
|
|
}
|
|
obj->irq_rx_setting = IRQ_ON;
|
|
irq_enable(obj);
|
|
}
|
|
break;
|
|
case TxIrq:
|
|
{
|
|
/* Bit 5 */
|
|
obj->reg_base->UARTIMSC |= 1 << 5;
|
|
obj->irq_tx_setting = IRQ_ON;
|
|
irq_enable(obj);
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
MBED_ASSERT(false);
|
|
}
|
|
break;
|
|
}
|
|
} else {
|
|
switch (irq) {
|
|
case RxIrq:
|
|
{
|
|
/* Bit 4 for Rx and bit 6 for Rx Timeout */
|
|
obj->reg_base->UARTIMSC &= ~((1 << 4) | (1 << 6));
|
|
if (lp_also)
|
|
{
|
|
/* "Word Received" IRQ */
|
|
LP_UART_CTRL &= ~(1 << 23);
|
|
}
|
|
obj->irq_rx_setting = IRQ_OFF;
|
|
}
|
|
break;
|
|
case TxIrq:
|
|
{
|
|
/* Bit 5 */
|
|
obj->reg_base->UARTIMSC &= ~(1 << 5);
|
|
obj->irq_tx_setting = IRQ_OFF;
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
MBED_ASSERT(false);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if ((obj->irq_rx_setting == IRQ_OFF) && (obj->irq_tx_setting == IRQ_OFF)) {
|
|
irq_disable(obj);
|
|
}
|
|
}
|
|
|
|
/* Enable the UART again */
|
|
obj->reg_base->UARTCR |= 1 << 0;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------
|
|
* MBED API CALLS: TRANSMIT AND RECEIVE FUNCTIONS
|
|
* ----------------------------------------------------------------*/
|
|
|
|
int serial_getc(serial_t *obj)
|
|
{
|
|
uint8_t data = 0;
|
|
|
|
/* Block until there is data to read */
|
|
while (!serial_readable(obj)) {}
|
|
|
|
/* Read the data */
|
|
switch (obj->config) {
|
|
case SERIAL_CONFIG_UARTLP_RX_UART0_TX:
|
|
{
|
|
data = (uint8_t) LP_UART_DATA;
|
|
}
|
|
break;
|
|
case SERIAL_CONFIG_UART0_RX_UART0_TX:
|
|
case SERIAL_CONFIG_UART1_RX_UART1_TX:
|
|
{
|
|
data = (uint8_t) obj->reg_base->UARTDR;
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
MBED_ASSERT(false);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return (int) data;
|
|
}
|
|
|
|
void serial_putc(serial_t *obj, int c)
|
|
{
|
|
/* Block until there is room to write */
|
|
while (!serial_writable(obj)) {}
|
|
|
|
/* Write the data */
|
|
obj->reg_base->UARTDR = (uint8_t) c;
|
|
}
|
|
|
|
int serial_readable(serial_t *obj)
|
|
{
|
|
bool readable = false;
|
|
|
|
switch (obj->config) {
|
|
case SERIAL_CONFIG_UARTLP_RX_UART0_TX:
|
|
{
|
|
/* Check the status register, bits 8 to 10 indicate
|
|
* the number of Rx bytes (make sure it's the status
|
|
* register not the data register as a read from that
|
|
* register would clear the Rx interrupt) */
|
|
readable = (((LP_UART_STATUS >> 8) & 0x07) != 0);
|
|
}
|
|
break;
|
|
case SERIAL_CONFIG_UART0_RX_UART0_TX:
|
|
case SERIAL_CONFIG_UART1_RX_UART1_TX:
|
|
{
|
|
/* Check the Rx FIFO Empty bit */
|
|
readable = ((obj->reg_base->UARTFR & (1 << 4)) != (1 << 4));
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
MBED_ASSERT(false);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return (int) readable;
|
|
}
|
|
|
|
int serial_writable(serial_t *obj)
|
|
{
|
|
/* Check the "UART TX FIFO full" bit:
|
|
* only if this is 0 can we transmit */
|
|
return (obj->reg_base->UARTFR & (1 << 5)) != (1 << 5);
|
|
}
|
|
|
|
void serial_break_set(serial_t *obj)
|
|
{
|
|
/* Disable UART while writing to control registers */
|
|
obj->reg_base->UARTCR &= ~(1 << 0);
|
|
|
|
/* Set bit 1 of the line control register */
|
|
obj->reg_base->UARTLCR_H |= 1 << 0;
|
|
|
|
/* Enable the UART again */
|
|
obj->reg_base->UARTCR |= 1 << 0;
|
|
}
|
|
|
|
void serial_break_clear(serial_t *obj)
|
|
{
|
|
/* Disable UART while writing to control registers */
|
|
obj->reg_base->UARTCR &= ~(1 << 0);
|
|
|
|
/* Clear bit 1 of the line control register */
|
|
obj->reg_base->UARTLCR_H &= ~(1 << 0);
|
|
|
|
/* Enable the UART again */
|
|
obj->reg_base->UARTCR |= 1 << 0;
|
|
}
|