/** * \file * * \brief SAM SERCOM USART Asynchronous Driver * * Copyright (c) 2012-2018 Microchip Technology Inc. and its subsidiaries. * * \asf_license_start * * \page License * * Subject to your compliance with these terms, you may use Microchip * software and any derivatives exclusively with Microchip products. * It is your responsibility to comply with third party license terms applicable * to your use of third party software (including open source software) that * may accompany Microchip software. * * THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES, * WHETHER EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE, * INCLUDING ANY IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY, * AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT WILL MICROCHIP BE * LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE, INCIDENTAL OR CONSEQUENTIAL * LOSS, DAMAGE, COST OR EXPENSE OF ANY KIND WHATSOEVER RELATED TO THE * SOFTWARE, HOWEVER CAUSED, EVEN IF MICROCHIP HAS BEEN ADVISED OF THE * POSSIBILITY OR THE DAMAGES ARE FORESEEABLE. TO THE FULLEST EXTENT * ALLOWED BY LAW, MICROCHIP'S TOTAL LIABILITY ON ALL CLAIMS IN ANY WAY * RELATED TO THIS SOFTWARE WILL NOT EXCEED THE AMOUNT OF FEES, IF ANY, * THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR THIS SOFTWARE. * * \asf_license_stop * */ /* * Support and FAQ: visit Microchip Support */ #include "usart_interrupt.h" /** * \internal * Asynchronous write of a buffer with a given length * * \param[in] module Pointer to USART software instance struct * \param[in] tx_data Pointer to data to be transmitted * \param[in] length Length of data buffer * */ enum status_code _usart_write_buffer( struct usart_module *const module, uint8_t *tx_data, uint16_t length) { /* Sanity check arguments */ Assert(module); Assert(module->hw); Assert(tx_data); /* Get a pointer to the hardware module instance */ SercomUsart *const usart_hw = &(module->hw->USART); system_interrupt_enter_critical_section(); /* Check if the USART transmitter is busy */ if (module->remaining_tx_buffer_length > 0) { system_interrupt_leave_critical_section(); return STATUS_BUSY; } /* Write parameters to the device instance */ module->remaining_tx_buffer_length = length; system_interrupt_leave_critical_section(); module->tx_buffer_ptr = tx_data; module->tx_status = STATUS_BUSY; /* Enable the Data Register Empty Interrupt */ usart_hw->INTENSET.reg = SERCOM_USART_INTFLAG_DRE; return STATUS_OK; } /** * \internal * Asynchronous read of a buffer with a given length * * \param[in] module Pointer to USART software instance struct * \param[in] rx_data Pointer to data to be received * \param[in] length Length of data buffer * */ enum status_code _usart_read_buffer( struct usart_module *const module, uint8_t *rx_data, uint16_t length) { /* Sanity check arguments */ Assert(module); Assert(module->hw); Assert(rx_data); /* Get a pointer to the hardware module instance */ SercomUsart *const usart_hw = &(module->hw->USART); system_interrupt_enter_critical_section(); /* Check if the USART receiver is busy */ if (module->remaining_rx_buffer_length > 0) { system_interrupt_leave_critical_section(); return STATUS_BUSY; } /* Set length for the buffer and the pointer, and let * the interrupt handler do the rest */ module->remaining_rx_buffer_length = length; system_interrupt_leave_critical_section(); module->rx_buffer_ptr = rx_data; module->rx_status = STATUS_BUSY; /* Enable the RX Complete Interrupt */ usart_hw->INTENSET.reg = SERCOM_USART_INTFLAG_RXC; #ifdef FEATURE_USART_LIN_SLAVE /* Enable the break character is received Interrupt */ if(module->lin_slave_enabled) { usart_hw->INTENSET.reg = SERCOM_USART_INTFLAG_RXBRK; } #endif #ifdef FEATURE_USART_START_FRAME_DECTION /* Enable a start condition is detected Interrupt */ if(module->start_frame_detection_enabled) { usart_hw->INTENSET.reg = SERCOM_USART_INTFLAG_RXS; } #endif return STATUS_OK; } /** * \brief Registers a callback * * Registers a callback function, which is implemented by the user. * * \note The callback must be enabled by \ref usart_enable_callback * in order for the interrupt handler to call it when the conditions for * the callback type are met. * * \param[in] module Pointer to USART software instance struct * \param[in] callback_func Pointer to callback function * \param[in] callback_type Callback type given by an enum * */ void usart_register_callback( struct usart_module *const module, usart_callback_t callback_func, enum usart_callback callback_type) { /* Sanity check arguments */ Assert(module); Assert(callback_func); /* Register callback function */ module->callback[callback_type] = callback_func; /* Set the bit corresponding to the callback_type */ module->callback_reg_mask |= (1 << callback_type); } /** * \brief Unregisters a callback * * Unregisters a callback function, which is implemented by the user. * * \param[in,out] module Pointer to USART software instance struct * \param[in] callback_type Callback type given by an enum * */ void usart_unregister_callback( struct usart_module *const module, enum usart_callback callback_type) { /* Sanity check arguments */ Assert(module); /* Unregister callback function */ module->callback[callback_type] = NULL; /* Clear the bit corresponding to the callback_type */ module->callback_reg_mask &= ~(1 << callback_type); } /** * \brief Asynchronous write a single char * * Sets up the driver to write the data given. If registered and enabled, * a callback function will be called when the transmit is completed. * * \param[in] module Pointer to USART software instance struct * \param[in] tx_data Data to transfer * * \returns Status of the operation. * \retval STATUS_OK If operation was completed * \retval STATUS_BUSY If operation was not completed, due to the * USART module being busy * \retval STATUS_ERR_DENIED If the transmitter is not enabled */ enum status_code usart_write_job( struct usart_module *const module, const uint16_t *tx_data) { /* Sanity check arguments */ Assert(module); Assert(tx_data); /* Check that the transmitter is enabled */ if (!(module->transmitter_enabled)) { return STATUS_ERR_DENIED; } /* Call internal write buffer function with length 1 */ return _usart_write_buffer(module, (uint8_t *)tx_data, 1); } /** * \brief Asynchronous read a single char * * Sets up the driver to read data from the USART module to the data * pointer given. If registered and enabled, a callback will be called * when the receiving is completed. * * \param[in] module Pointer to USART software instance struct * \param[out] rx_data Pointer to where received data should be put * * \returns Status of the operation. * \retval STATUS_OK If operation was completed * \retval STATUS_BUSY If operation was not completed */ enum status_code usart_read_job( struct usart_module *const module, uint16_t *const rx_data) { /* Sanity check arguments */ Assert(module); Assert(rx_data); /* Call internal read buffer function with length 1 */ return _usart_read_buffer(module, (uint8_t *)rx_data, 1); } /** * \brief Asynchronous buffer write * * Sets up the driver to write a given buffer over the USART. If registered and * enabled, a callback function will be called. * * \param[in] module Pointer to USART software instance struct * \param[in] tx_data Pointer do data buffer to transmit * \param[in] length Length of the data to transmit * * \note If using 9-bit data, the array that *tx_data point to should be defined * as uint16_t array and should be casted to uint8_t* pointer. Because it * is an address pointer, the highest byte is not discarded. For example: * \code #define TX_LEN 3 uint16_t tx_buf[TX_LEN] = {0x0111, 0x0022, 0x0133}; usart_write_buffer_job(&module, (uint8_t*)tx_buf, TX_LEN); \endcode * * \returns Status of the operation. * \retval STATUS_OK If operation was completed successfully. * \retval STATUS_BUSY If operation was not completed, due to the * USART module being busy * \retval STATUS_ERR_INVALID_ARG If operation was not completed, due to invalid * arguments * \retval STATUS_ERR_DENIED If the transmitter is not enabled */ enum status_code usart_write_buffer_job( struct usart_module *const module, uint8_t *tx_data, uint16_t length) { /* Sanity check arguments */ Assert(module); Assert(tx_data); if (length == 0) { return STATUS_ERR_INVALID_ARG; } /* Check that the transmitter is enabled */ if (!(module->transmitter_enabled)) { return STATUS_ERR_DENIED; } /* Issue internal asynchronous write */ return _usart_write_buffer(module, tx_data, length); } /** * \brief Asynchronous buffer read * * Sets up the driver to read from the USART to a given buffer. If registered * and enabled, a callback function will be called. * * \param[in] module Pointer to USART software instance struct * \param[out] rx_data Pointer to data buffer to receive * \param[in] length Data buffer length * * \note If using 9-bit data, the array that *rx_data point to should be defined * as uint16_t array and should be casted to uint8_t* pointer. Because it * is an address pointer, the highest byte is not discarded. For example: * \code #define RX_LEN 3 uint16_t rx_buf[RX_LEN] = {0x0,}; usart_read_buffer_job(&module, (uint8_t*)rx_buf, RX_LEN); \endcode * * \returns Status of the operation. * \retval STATUS_OK If operation was completed * \retval STATUS_BUSY If operation was not completed, due to the * USART module being busy * \retval STATUS_ERR_INVALID_ARG If operation was not completed, due to invalid * arguments * \retval STATUS_ERR_DENIED If the transmitter is not enabled */ enum status_code usart_read_buffer_job( struct usart_module *const module, uint8_t *rx_data, uint16_t length) { /* Sanity check arguments */ Assert(module); Assert(rx_data); if (length == 0) { return STATUS_ERR_INVALID_ARG; } /* Check that the receiver is enabled */ if (!(module->receiver_enabled)) { return STATUS_ERR_DENIED; } /* Issue internal asynchronous read */ return _usart_read_buffer(module, rx_data, length); } /** * \brief Cancels ongoing read/write operation * * Cancels the ongoing read/write operation modifying parameters in the * USART software struct. * * \param[in] module Pointer to USART software instance struct * \param[in] transceiver_type Transfer type to cancel */ void usart_abort_job( struct usart_module *const module, enum usart_transceiver_type transceiver_type) { /* Sanity check arguments */ Assert(module); Assert(module->hw); /* Get a pointer to the hardware module instance */ SercomUsart *const usart_hw = &(module->hw->USART); switch(transceiver_type) { case USART_TRANSCEIVER_RX: /* Clear the interrupt flag in order to prevent the receive * complete callback to fire */ usart_hw->INTFLAG.reg = SERCOM_USART_INTFLAG_RXC; /* Clear the software reception buffer */ module->remaining_rx_buffer_length = 0; break; case USART_TRANSCEIVER_TX: /* Clear the interrupt flag in order to prevent the receive * complete callback to fire */ usart_hw->INTFLAG.reg = SERCOM_USART_INTFLAG_TXC; /* Clear the software reception buffer */ module->remaining_tx_buffer_length = 0; break; } } /** * \brief Get status from the ongoing or last asynchronous transfer operation * * Returns the error from a given ongoing or last asynchronous transfer operation. * Either from a read or write transfer. * * \param[in] module Pointer to USART software instance struct * \param[in] transceiver_type Transfer type to check * * \return Status of the given job. * \retval STATUS_OK No error occurred during the last transfer * \retval STATUS_BUSY A transfer is ongoing * \retval STATUS_ERR_BAD_DATA The last operation was aborted due to a * parity error. The transfer could be affected * by external noise * \retval STATUS_ERR_BAD_FORMAT The last operation was aborted due to a * frame error * \retval STATUS_ERR_OVERFLOW The last operation was aborted due to a * buffer overflow * \retval STATUS_ERR_INVALID_ARG An invalid transceiver enum given */ enum status_code usart_get_job_status( struct usart_module *const module, enum usart_transceiver_type transceiver_type) { /* Sanity check arguments */ Assert(module); /* Variable for status code */ enum status_code status_code; switch(transceiver_type) { case USART_TRANSCEIVER_RX: status_code = module->rx_status; break; case USART_TRANSCEIVER_TX: status_code = module->tx_status; break; default: status_code = STATUS_ERR_INVALID_ARG; break; } return status_code; } /** * \internal * Handles interrupts as they occur, and it will run callback functions * which are registered and enabled. * * \param[in] instance ID of the SERCOM instance calling the interrupt * handler. */ void _usart_interrupt_handler( uint8_t instance) { /* Temporary variables */ uint16_t interrupt_status; uint16_t callback_status; uint8_t error_code; /* Get device instance from the look-up table */ struct usart_module *module = (struct usart_module *)_sercom_instances[instance]; /* Pointer to the hardware module instance */ SercomUsart *const usart_hw = &(module->hw->USART); /* Wait for the synchronization to complete */ _usart_wait_for_sync(module); /* Read and mask interrupt flag register */ interrupt_status = usart_hw->INTFLAG.reg; interrupt_status &= usart_hw->INTENSET.reg; callback_status = module->callback_reg_mask & module->callback_enable_mask; /* Check if a DATA READY interrupt has occurred, * and if there is more to transfer */ if (interrupt_status & SERCOM_USART_INTFLAG_DRE) { if (module->remaining_tx_buffer_length) { /* Write value will be at least 8-bits long */ uint16_t data_to_send = *(module->tx_buffer_ptr); /* Increment 8-bit pointer */ (module->tx_buffer_ptr)++; if (module->character_size == USART_CHARACTER_SIZE_9BIT) { data_to_send |= (*(module->tx_buffer_ptr) << 8); /* Increment 8-bit pointer */ (module->tx_buffer_ptr)++; } /* Write the data to send */ usart_hw->DATA.reg = (data_to_send & SERCOM_USART_DATA_MASK); if (--(module->remaining_tx_buffer_length) == 0) { /* Disable the Data Register Empty Interrupt */ usart_hw->INTENCLR.reg = SERCOM_USART_INTFLAG_DRE; /* Enable Transmission Complete interrupt */ usart_hw->INTENSET.reg = SERCOM_USART_INTFLAG_TXC; } } else { usart_hw->INTENCLR.reg = SERCOM_USART_INTFLAG_DRE; } } /* Check if the Transmission Complete interrupt has occurred and * that the transmit buffer is empty */ if (interrupt_status & SERCOM_USART_INTFLAG_TXC) { /* Disable TX Complete Interrupt, and set STATUS_OK */ usart_hw->INTENCLR.reg = SERCOM_USART_INTFLAG_TXC; module->tx_status = STATUS_OK; /* Run callback if registered and enabled */ if (callback_status & (1 << USART_CALLBACK_BUFFER_TRANSMITTED)) { (*(module->callback[USART_CALLBACK_BUFFER_TRANSMITTED]))(module); } } /* Check if the Receive Complete interrupt has occurred, and that * there's more data to receive */ if (interrupt_status & SERCOM_USART_INTFLAG_RXC) { if (module->remaining_rx_buffer_length) { /* Read out the status code and mask away all but the 4 LSBs*/ error_code = (uint8_t)(usart_hw->STATUS.reg & SERCOM_USART_STATUS_MASK); #if !SAMD20 /* CTS status should not be considered as an error */ if(error_code & SERCOM_USART_STATUS_CTS) { error_code &= ~SERCOM_USART_STATUS_CTS; } #endif #ifdef FEATURE_USART_LIN_MASTER /* TXE status should not be considered as an error */ if(error_code & SERCOM_USART_STATUS_TXE) { error_code &= ~SERCOM_USART_STATUS_TXE; } #endif /* Check if an error has occurred during the receiving */ if (error_code) { /* Check which error occurred */ if (error_code & SERCOM_USART_STATUS_FERR) { /* Store the error code and clear flag by writing 1 to it */ module->rx_status = STATUS_ERR_BAD_FORMAT; usart_hw->STATUS.reg = SERCOM_USART_STATUS_FERR; } else if (error_code & SERCOM_USART_STATUS_BUFOVF) { /* Store the error code and clear flag by writing 1 to it */ module->rx_status = STATUS_ERR_OVERFLOW; usart_hw->STATUS.reg = SERCOM_USART_STATUS_BUFOVF; } else if (error_code & SERCOM_USART_STATUS_PERR) { /* Store the error code and clear flag by writing 1 to it */ module->rx_status = STATUS_ERR_BAD_DATA; usart_hw->STATUS.reg = SERCOM_USART_STATUS_PERR; } #ifdef FEATURE_USART_LIN_SLAVE else if (error_code & SERCOM_USART_STATUS_ISF) { /* Store the error code and clear flag by writing 1 to it */ module->rx_status = STATUS_ERR_PROTOCOL; usart_hw->STATUS.reg = SERCOM_USART_STATUS_ISF; } #endif #ifdef FEATURE_USART_COLLISION_DECTION else if (error_code & SERCOM_USART_STATUS_COLL) { /* Store the error code and clear flag by writing 1 to it */ module->rx_status = STATUS_ERR_PACKET_COLLISION; usart_hw->STATUS.reg = SERCOM_USART_STATUS_COLL; } #endif /* Run callback if registered and enabled */ if (callback_status & (1 << USART_CALLBACK_ERROR)) { (*(module->callback[USART_CALLBACK_ERROR]))(module); } } else { /* Read current packet from DATA register, * increment buffer pointer and decrement buffer length */ uint16_t received_data = (usart_hw->DATA.reg & SERCOM_USART_DATA_MASK); /* Read value will be at least 8-bits long */ *(module->rx_buffer_ptr) = received_data; /* Increment 8-bit pointer */ module->rx_buffer_ptr += 1; if (module->character_size == USART_CHARACTER_SIZE_9BIT) { /* 9-bit data, write next received byte to the buffer */ *(module->rx_buffer_ptr) = (received_data >> 8); /* Increment 8-bit pointer */ module->rx_buffer_ptr += 1; } /* Check if the last character have been received */ if(--(module->remaining_rx_buffer_length) == 0) { /* Disable RX Complete Interrupt, * and set STATUS_OK */ usart_hw->INTENCLR.reg = SERCOM_USART_INTFLAG_RXC; module->rx_status = STATUS_OK; /* Run callback if registered and enabled */ if (callback_status & (1 << USART_CALLBACK_BUFFER_RECEIVED)) { (*(module->callback[USART_CALLBACK_BUFFER_RECEIVED]))(module); } } } } else { /* This should not happen. Disable Receive Complete interrupt. */ usart_hw->INTENCLR.reg = SERCOM_USART_INTFLAG_RXC; } } #ifdef FEATURE_USART_HARDWARE_FLOW_CONTROL if (interrupt_status & SERCOM_USART_INTFLAG_CTSIC) { /* Disable interrupts */ usart_hw->INTENCLR.reg = SERCOM_USART_INTENCLR_CTSIC; /* Clear interrupt flag */ usart_hw->INTFLAG.reg = SERCOM_USART_INTFLAG_CTSIC; /* Run callback if registered and enabled */ if (callback_status & (1 << USART_CALLBACK_CTS_INPUT_CHANGE)) { (*(module->callback[USART_CALLBACK_CTS_INPUT_CHANGE]))(module); } } #endif #ifdef FEATURE_USART_LIN_SLAVE if (interrupt_status & SERCOM_USART_INTFLAG_RXBRK) { /* Disable interrupts */ usart_hw->INTENCLR.reg = SERCOM_USART_INTENCLR_RXBRK; /* Clear interrupt flag */ usart_hw->INTFLAG.reg = SERCOM_USART_INTFLAG_RXBRK; /* Run callback if registered and enabled */ if (callback_status & (1 << USART_CALLBACK_BREAK_RECEIVED)) { (*(module->callback[USART_CALLBACK_BREAK_RECEIVED]))(module); } } #endif #ifdef FEATURE_USART_START_FRAME_DECTION if (interrupt_status & SERCOM_USART_INTFLAG_RXS) { /* Disable interrupts */ usart_hw->INTENCLR.reg = SERCOM_USART_INTENCLR_RXS; /* Clear interrupt flag */ usart_hw->INTFLAG.reg = SERCOM_USART_INTFLAG_RXS; /* Run callback if registered and enabled */ if (callback_status & (1 << USART_CALLBACK_START_RECEIVED)) { (*(module->callback[USART_CALLBACK_START_RECEIVED]))(module); } } #endif }