aboutsummaryrefslogtreecommitdiffstats
path: root/os/hal/ports/MSP430X/hal_serial_lld.c
diff options
context:
space:
mode:
Diffstat (limited to 'os/hal/ports/MSP430X/hal_serial_lld.c')
-rw-r--r--os/hal/ports/MSP430X/hal_serial_lld.c657
1 files changed, 657 insertions, 0 deletions
diff --git a/os/hal/ports/MSP430X/hal_serial_lld.c b/os/hal/ports/MSP430X/hal_serial_lld.c
new file mode 100644
index 0000000..277f6d2
--- /dev/null
+++ b/os/hal/ports/MSP430X/hal_serial_lld.c
@@ -0,0 +1,657 @@
+/*
+ ChibiOS - Copyright (C) 2016 Andrew Wygle aka awygle
+
+ 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.
+*/
+
+/**
+ * @file MSP430X/hal_serial_lld.c
+ * @brief MSP430X serial subsystem low level driver source.
+ *
+ * @addtogroup SERIAL
+ * @{
+ */
+
+#include "hal.h"
+
+#if (HAL_USE_SERIAL == TRUE) || defined(__DOXYGEN__)
+
+/*===========================================================================*/
+/* Driver local definitions. */
+/*===========================================================================*/
+
+/*===========================================================================*/
+/* Driver exported variables. */
+/*===========================================================================*/
+
+/** @brief USART0 serial driver identifier.*/
+#if (MSP430X_SERIAL_USE_USART0 == TRUE) || defined(__DOXYGEN__)
+#ifndef __MSP430_HAS_EUSCI_A0__
+ #error "Cannot find USCI module to use for SD0"
+#endif
+SerialDriver SD0;
+#endif
+
+/** @brief USART1 serial driver identifier.*/
+#if (MSP430X_SERIAL_USE_USART1 == TRUE) || defined(__DOXYGEN__)
+#ifndef __MSP430_HAS_EUSCI_A1__
+ #error "Cannot find USCI module to use for SD1"
+#endif
+SerialDriver SD1;
+#endif
+
+/** @brief USART2 serial driver identifier.*/
+#if (MSP430X_SERIAL_USE_USART2 == TRUE) || defined(__DOXYGEN__)
+#ifndef __MSP430_HAS_EUSCI_A2__
+ #error "Cannot find USCI module to use for SD2"
+#endif
+SerialDriver SD2;
+#endif
+
+/** @brief USART3 serial driver identifier.*/
+#if (MSP430X_SERIAL_USE_USART3 == TRUE) || defined(__DOXYGEN__)
+#ifndef __MSP430_HAS_EUSCI_A3__
+ #error "Cannot find USCI module to use for SD3"
+#endif
+SerialDriver SD3;
+#endif
+
+/*===========================================================================*/
+/* Driver local variables and types. */
+/*===========================================================================*/
+
+/**
+ * @brief Driver default configuration.
+ */
+static const SerialConfig default_config = {
+ SERIAL_DEFAULT_BITRATE
+};
+
+/*===========================================================================*/
+/* Driver local functions. */
+/*===========================================================================*/
+
+/**
+ * @brief UCBRS calculation.
+ * @details This function calculates the UCBRS value for oversampled baud
+ * rates.
+ *
+ * @param[in] frac Fractional part of baud rate division, times 10000.
+ */
+static uint8_t UCBRS(uint16_t frac) {
+ /* TODO there must be a better way */
+ if (frac < 529)
+ return 0x00;
+ else if (frac < 715)
+ return 0x01;
+ else if (frac < 835)
+ return 0x02;
+ else if (frac < 1001)
+ return 0x04;
+ else if (frac < 1252)
+ return 0x08;
+ else if (frac < 1430)
+ return 0x10;
+ else if (frac < 1670)
+ return 0x20;
+ else if (frac < 2147)
+ return 0x11;
+ else if (frac < 2224)
+ return 0x21;
+ else if (frac < 2503)
+ return 0x22;
+ else if (frac < 3000)
+ return 0x44;
+ else if (frac < 3335)
+ return 0x25;
+ else if (frac < 3575)
+ return 0x49;
+ else if (frac < 3753)
+ return 0x4A;
+ else if (frac < 4003)
+ return 0x52;
+ else if (frac < 4286)
+ return 0x92;
+ else if (frac < 4378)
+ return 0x53;
+ else if (frac < 5002)
+ return 0x55;
+ else if (frac < 5715)
+ return 0xAA;
+ else if (frac < 6003)
+ return 0x6B;
+ else if (frac < 6254)
+ return 0xAD;
+ else if (frac < 6432)
+ return 0xB5;
+ else if (frac < 6667)
+ return 0xB6;
+ else if (frac < 7001)
+ return 0xD6;
+ else if (frac < 7147)
+ return 0xB7;
+ else if (frac < 7503)
+ return 0xBB;
+ else if (frac < 7861)
+ return 0xDD;
+ else if (frac < 8004)
+ return 0xED;
+ else if (frac < 8333)
+ return 0xEE;
+ else if (frac < 8464)
+ return 0xBF;
+ else if (frac < 8572)
+ return 0xDF;
+ else if (frac < 8751)
+ return 0xEF;
+ else if (frac < 9004)
+ return 0xF7;
+ else if (frac < 9170)
+ return 0xFB;
+ else if (frac < 9288)
+ return 0xFD;
+ else
+ return 0xFE;
+}
+
+/**
+ * @brief Modulation control word calculator.
+ * @details This function calculates the modulation control word from the
+ * input clock frequency and the requested baud rate.
+ *
+ * @param[in] baud Requested baud rate
+ * @param[in] freq Frequency of the clock driving the USCI module
+ */
+static uint16_t UCAxMCTLW(uint32_t baud, uint32_t freq) {
+
+ uint16_t n = freq/baud;
+ /*uint16_t frac = (freq * 10000 / baud) - ((freq / baud) * 10000);*/
+ uint16_t frac = (freq - (n * baud)) * 10000 / baud;
+ if (n > 16) {
+ while (n > 16) {
+ n -= 16;
+ }
+ return (UCBRS(frac) << 8) | (n << 4) | UCOS16;
+ }
+ return UCBRS(frac) << 8;
+}
+
+/**
+ * @brief UCBRW calculation.
+ * @details This function calculates the UCBRW value for all baud
+ * rates.
+ *
+ * @param[in] baud Requested baud rate
+ * @param[in] freq Frequency of the clock driving the USCI module
+ */
+static uint16_t UCAxBRW(uint32_t baud, uint32_t freq) {
+ uint16_t n = freq/baud;
+ if (n > 16) {
+ return n >> 4;
+ }
+ return n;
+}
+
+#if (MSP430X_SERIAL_USE_USART0 == TRUE) || defined(__DOXYGEN__)
+static void usart0_init(const SerialConfig *config) {
+ UCA0BRW = UCAxBRW(config->sc_bitrate, MSP430X_USART0_CLK_FREQ);
+ UCA0MCTLW = UCAxMCTLW(config->sc_bitrate, MSP430X_USART0_CLK_FREQ);
+ UCA0STATW = 0;
+ UCA0ABCTL = 0;
+ UCA0IRCTL = 0;
+ UCA0CTLW0 = (MSP430X_USART0_PARITY << 14) | (MSP430X_USART0_ORDER << 13) | \
+ (MSP430X_USART0_SIZE << 12) | (MSP430X_USART0_STOP << 11) | \
+ (MSP430X_USART0_UCSSEL);
+ UCA0IE = UCRXIE;
+}
+#endif
+
+#if (MSP430X_SERIAL_USE_USART1 == TRUE) || defined(__DOXYGEN__)
+static void usart1_init(const SerialConfig *config) {
+ UCA1BRW = UCAxBRW(config->sc_bitrate, MSP430X_USART1_CLK_FREQ);
+ UCA1MCTLW = UCAxMCTLW(config->sc_bitrate, MSP430X_USART1_CLK_FREQ);
+ UCA1STATW = 0;
+ UCA1ABCTL = 0;
+ UCA1IRCTL = 0;
+ UCA1IE = UCRXIE;
+ UCA1CTLW0 = (MSP430X_USART1_PARITY << 14) | (MSP430X_USART1_ORDER << 13) | \
+ (MSP430X_USART1_SIZE << 12) | (MSP430X_USART1_STOP << 11) | \
+ (MSP430X_USART1_UCSSEL);
+}
+#endif
+
+#if (MSP430X_SERIAL_USE_USART2 == TRUE) || defined(__DOXYGEN__)
+static void usart2_init(const SerialConfig *config) {
+ UCA2BRW = UCAxBRW(config->sc_bitrate, MSP430X_USART2_CLK_FREQ);
+ UCA2MCTLW = UCAxMCTLW(config->sc_bitrate);
+ UCA2STATW = 0;
+ UCA2ABCTL = 0;
+ UCA2IRCTL = 0;
+ UCA2IE = UCRXIE;
+ UCA2CTLW0 = (MSP430X_USART2_PARITY << 14) | (MSP430X_USART2_ORDER << 13) | \
+ (MSP430X_USART2_SIZE << 12) | (MSP430X_USART2_STOP << 11) | \
+ (MSP430X_USART2_UCSSEL);
+}
+#endif
+
+#if (MSP430X_SERIAL_USE_USART3 == TRUE) || defined(__DOXYGEN__)
+static void usart3_init(const SerialConfig *config) {
+ UCA3BRW = UCAxBRW(config->sc_bitrate, MSP430X_USART3_CLK_FREQ);
+ UCA3MCTLW = UCAxMCTLW(config->sc_bitrate, MSP430X_USART3_CLK_FREQ);
+ UCA3STATW = 0;
+ UCA3ABCTL = 0;
+ UCA3IRCTL = 0;
+ UCA3IE = UCRXIE;
+ UCA3CTLW0 = (MSP430X_USART3_PARITY << 14) | (MSP430X_USART3_ORDER << 13) | \
+ (MSP430X_USART3_SIZE << 12) | (MSP430X_USART3_STOP << 11) | \
+ (MSP430X_USART3_UCSSEL);
+}
+#endif
+
+#if (MSP430X_SERIAL_USE_USART0 == TRUE) || defined(__DOXYGEN__)
+static void notify0(io_queue_t *qp) {
+
+ (void)qp;
+ UCA0IE |= UCTXIE;
+}
+#endif
+
+#if (MSP430X_SERIAL_USE_USART1 == TRUE) || defined(__DOXYGEN__)
+static void notify1(io_queue_t *qp) {
+
+ (void)qp;
+ UCA1IE |= UCTXIE;
+}
+#endif
+
+#if (MSP430X_SERIAL_USE_USART2 == TRUE) || defined(__DOXYGEN__)
+static void notify2(io_queue_t *qp) {
+
+ (void)qp;
+ UCA2IE |= UCTXIE;
+}
+#endif
+
+#if (MSP430X_SERIAL_USE_USART3 == TRUE) || defined(__DOXYGEN__)
+static void notify3(io_queue_t *qp) {
+
+ (void)qp;
+ UCA3IE |= UCTXIE;
+}
+#endif
+
+/**
+ * @brief Error handling routine.
+ *
+ * @param[in] sra USCI status register containing errors
+ * @param[in] sdp pointer to a @p SerialDriver object
+ */
+static void set_error(uint16_t sra, SerialDriver *sdp) {
+ eventflags_t sts = 0;
+
+ if (sra & UCOE)
+ sts |= SD_OVERRUN_ERROR;
+ if (sra & UCPE)
+ sts |= SD_PARITY_ERROR;
+ if (sra & UCFE)
+ sts |= SD_FRAMING_ERROR;
+ osalSysLockFromISR();
+ chnAddFlagsI(sdp, sts);
+ osalSysUnlockFromISR();
+}
+
+
+/*===========================================================================*/
+/* Driver interrupt handlers. */
+/*===========================================================================*/
+
+#if MSP430X_SERIAL_USE_USART0 || defined(__DOXYGEN__)
+/**
+ * @brief USART0 interrupt handler.
+ *
+ * @isr
+ */
+PORT_IRQ_HANDLER(USCI_A0_VECTOR) {
+ msg_t b;
+
+ OSAL_IRQ_PROLOGUE();
+
+ switch (__even_in_range(UCA0IV,USCI_UART_UCTXCPTIFG)) {
+ case USCI_UART_UCRXIFG: /* RX interrupt */
+
+ /* Detect errors */
+ if (UCA0STATW & UCRXERR)
+ set_error(UCA0STATW, &SD0);
+
+ /* Data available */
+ osalSysLockFromISR();
+ sdIncomingDataI(&SD0, UCA0RXBUF);
+ osalSysUnlockFromISR();
+ break;
+
+ case USCI_UART_UCTXIFG: /* TX interrupt */
+
+ /* Transmission buffer empty */
+ osalSysLockFromISR();
+ b = sdRequestDataI(&SD0);
+ if (b < Q_OK) {
+ chnAddFlagsI(&SD0, CHN_OUTPUT_EMPTY);
+ UCA0IE = (UCA0IE & ~UCTXIE) | UCTXCPTIE;
+ UCA0IFG |= UCTXIFG; /* If we don't write to TXBUF, IFG won't get set */
+ }
+ else
+ UCA0TXBUF = b;
+ osalSysUnlockFromISR();
+ break;
+
+ case USCI_UART_UCTXCPTIFG: /* TX complete interrupt */
+
+ /* Physical transmission end */
+ osalSysLockFromISR();
+ if (oqIsEmptyI(&SD0.oqueue))
+ chnAddFlagsI(&SD0, CHN_TRANSMISSION_END);
+ UCA0IE &= ~UCTXCPTIE;
+ break;
+
+ default: /* other interrupts */
+ while (1);
+ break;
+ }
+
+ OSAL_IRQ_EPILOGUE();
+
+}
+#endif
+
+#if MSP430X_SERIAL_USE_USART1 || defined(__DOXYGEN__)
+/**
+ * @brief USART1 interrupt handler.
+ *
+ * @isr
+ */
+PORT_IRQ_HANDLER(USCI_A1_VECTOR) {
+ msg_t b;
+
+ OSAL_IRQ_PROLOGUE();
+
+ switch (__even_in_range(UCA1IV,USCI_UART_UCTXCPTIFG)) {
+ case USCI_UART_UCRXIFG: /* RX interrupt */
+
+ /* Detect errors */
+ if (UCA1STATW & UCRXERR)
+ set_error(UCA1STATW, &SD1);
+
+ /* Data available */
+ osalSysLockFromISR();
+ sdIncomingDataI(&SD1, UCA1RXBUF);
+ osalSysUnlockFromISR();
+ break;
+
+ case USCI_UART_UCTXIFG: /* TX interrupt */
+
+ /* Transmission buffer empty */
+ osalSysLockFromISR();
+ b = sdRequestDataI(&SD1);
+ if (b < Q_OK) {
+ chnAddFlagsI(&SD1, CHN_OUTPUT_EMPTY);
+ UCA1IE = (UCA1IE & ~UCTXIE) | UCTXCPTIE;
+ UCA1IFG |= UCTXIFG; /* If we don't write to TXBUF, IFG won't get set */
+ }
+ else
+ UCA1TXBUF = b;
+ osalSysUnlockFromISR();
+ break;
+
+ case USCI_UART_UCTXCPTIFG: /* TX complete interrupt */
+
+ /* Physical transmission end */
+ osalSysLockFromISR();
+ if (oqIsEmptyI(&SD1.oqueue))
+ chnAddFlagsI(&SD1, CHN_TRANSMISSION_END);
+ UCA1IE &= ~UCTXCPTIE;
+ break;
+
+ default: /* other interrupts */
+ while (1);
+ break;
+ }
+
+ OSAL_IRQ_EPILOGUE();
+
+}
+#endif
+
+#if MSP430X_SERIAL_USE_USART2 || defined(__DOXYGEN__)
+/**
+ * @brief USART2 interrupt handler.
+ *
+ * @isr
+ */
+PORT_IRQ_HANDLER(USCI_A2_VECTOR) {
+ msg_t b;
+
+ OSAL_IRQ_PROLOGUE();
+
+ switch (__even_in_range(UCA2IV,USCI_UART_UCTXCPTIFG)) {
+ case USCI_UART_UCRXIFG: /* RX interrupt */
+
+ /* Detect errors */
+ if (UCA2STATW & UCRXERR)
+ set_error(UCA2STATW, &SD2);
+
+ /* Data available */
+ osalSysLockFromISR();
+ sdIncomingDataI(&SD2, UCA2RXBUF);
+ osalSysUnlockFromISR();
+ break;
+
+ case USCI_UART_UCTXIFG: /* TX interrupt */
+
+ /* Transmission buffer empty */
+ osalSysLockFromISR();
+ b = sdRequestDataI(&SD2);
+ if (b < Q_OK) {
+ chnAddFlagsI(&SD2, CHN_OUTPUT_EMPTY);
+ UCA2IE = (UCA2IE & ~UCTXIE) | UCTXCPTIE;
+ UCA2IFG |= UCTXIFG; /* If we don't write to TXBUF, IFG won't get set */
+ }
+ else
+ UCA2TXBUF = b;
+ osalSysUnlockFromISR();
+ break;
+
+ case USCI_UART_UCTXCPTIFG: /* TX complete interrupt */
+
+ /* Physical transmission end */
+ osalSysLockFromISR();
+ if (oqIsEmptyI(&SD2.oqueue))
+ chnAddFlagsI(&SD2, CHN_TRANSMISSION_END);
+ UCA2IE &= ~UCTXCPTIE;
+ break;
+
+ default: /* other interrupts */
+ while (1);
+ break;
+ }
+
+ OSAL_IRQ_EPILOGUE();
+
+}
+#endif
+
+#if MSP430X_SERIAL_USE_USART3 || defined(__DOXYGEN__)
+/**
+ * @brief USART3 interrupt handler.
+ *
+ * @isr
+ */
+PORT_IRQ_HANDLER(USCI_A3_VECTOR) {
+ msg_t b;
+
+ OSAL_IRQ_PROLOGUE();
+
+ switch (__even_in_range(UCA3IV,USCI_UART_UCTXCPTIFG)) {
+ case USCI_UART_UCRXIFG: /* RX interrupt */
+
+ /* Detect errors */
+ if (UCA3STATW & UCRXERR)
+ set_error(UCA3STATW, &SD3);
+
+ /* Data available */
+ osalSysLockFromISR();
+ sdIncomingDataI(&SD3, UCA3RXBUF);
+ osalSysUnlockFromISR();
+ break;
+
+ case USCI_UART_UCTXIFG: /* TX interrupt */
+
+ /* Transmission buffer empty */
+ osalSysLockFromISR();
+ b = sdRequestDataI(&SD3);
+ if (b < Q_OK) {
+ chnAddFlagsI(&SD3, CHN_OUTPUT_EMPTY);
+ UCA3IE = (UCA3IE & ~UCTXIE) | UCTXCPTIE;
+ UCA3IFG |= UCTXIFG; /* If we don't write to TXBUF, IFG won't get set */
+ }
+ else
+ UCA3TXBUF = b;
+ osalSysUnlockFromISR();
+ break;
+
+ case USCI_UART_UCTXCPTIFG: /* TX complete interrupt */
+
+ /* Physical transmission end */
+ osalSysLockFromISR();
+ if (oqIsEmptyI(&SD3.oqueue))
+ chnAddFlagsI(&SD3, CHN_TRANSMISSION_END);
+ UCA3IE &= ~UCTXCPTIE;
+ break;
+
+ default: /* other interrupts */
+ while (1);
+ break;
+ }
+
+ OSAL_IRQ_EPILOGUE();
+
+}
+#endif
+
+
+/*===========================================================================*/
+/* Driver exported functions. */
+/*===========================================================================*/
+
+/**
+ * @brief Low level serial driver initialization.
+ *
+ * @notapi
+ */
+void sd_lld_init(void) {
+
+#if MSP430X_SERIAL_USE_USART0 == TRUE
+ sdObjectInit(&SD0, NULL, notify0);
+#endif
+
+#if MSP430X_SERIAL_USE_USART1 == TRUE
+ sdObjectInit(&SD1, NULL, notify1);
+#endif
+
+#if MSP430X_SERIAL_USE_USART2 == TRUE
+ sdObjectInit(&SD2, NULL, notify2);
+#endif
+
+#if MSP430X_SERIAL_USE_USART3 == TRUE
+ sdObjectInit(&SD3, NULL, notify3);
+#endif
+}
+
+/**
+ * @brief Low level serial driver configuration and (re)start.
+ *
+ * @param[in] sdp pointer to a @p SerialDriver object
+ * @param[in] config the architecture-dependent serial driver configuration.
+ * If this parameter is set to @p NULL then a default
+ * configuration is used.
+ *
+ * @notapi
+ */
+void sd_lld_start(SerialDriver *sdp, const SerialConfig *config) {
+
+ if (config == NULL) {
+ config = &default_config;
+ }
+
+
+ if (sdp->state == SD_STOP) {
+#if MSP430X_SERIAL_USE_USART0 == TRUE
+ if (&SD0 == sdp) {
+ usart0_init(config);
+ }
+#endif
+#if MSP430X_SERIAL_USE_USART1 == TRUE
+ if (&SD1 == sdp) {
+ usart1_init(config);
+ }
+#endif
+#if MSP430X_SERIAL_USE_USART2 == TRUE
+ if (&SD2 == sdp) {
+ usart2_init(config);
+ }
+#endif
+#if MSP430X_SERIAL_USE_USART3 == TRUE
+ if (&SD3 == sdp) {
+ usart3_init(config);
+ }
+#endif
+ }
+}
+
+/**
+ * @brief Low level serial driver stop.
+ * @details De-initializes the USART, stops the associated clock, resets the
+ * interrupt vector.
+ *
+ * @param[in] sdp pointer to a @p SerialDriver object
+ *
+ * @notapi
+ */
+void sd_lld_stop(SerialDriver *sdp) {
+
+ if (sdp->state == SD_READY) {
+#if MSP430X_SERIAL_USE_USART0 == TRUE
+ if (&SD0 == sdp) {
+ UCA0CTLW0 = UCSWRST;
+ }
+#endif
+#if MSP430X_SERIAL_USE_USART1 == TRUE
+ if (&SD1 == sdp) {
+ UCA1CTLW0 = UCSWRST;
+ }
+#endif
+#if MSP430X_SERIAL_USE_USART2 == TRUE
+ if (&SD2 == sdp) {
+ UCA2CTLW0 = UCSWRST;
+ }
+#endif
+#if MSP430X_SERIAL_USE_USART3 == TRUE
+ if (&SD3 == sdp) {
+ UCA3CTLW0 = UCSWRST;
+ }
+#endif
+ }
+}
+
+#endif /* HAL_USE_SERIAL == TRUE */
+
+/** @} */