// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD // // 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. /******************************************************************************* * NOTICE * The ll is not public api, don't use in application code. * See readme.md in soc/include/hal/readme.md ******************************************************************************/ // The Lowlevel layer for TWAI #pragma once #ifdef __cplusplus extern "C" { #endif #include #include #include "hal/twai_types.h" #include "soc/twai_periph.h" /* ------------------------- Defines and Typedefs --------------------------- */ #define TWAI_LL_STATUS_RBS (0x1 << 0) #define TWAI_LL_STATUS_DOS (0x1 << 1) #define TWAI_LL_STATUS_TBS (0x1 << 2) #define TWAI_LL_STATUS_TCS (0x1 << 3) #define TWAI_LL_STATUS_RS (0x1 << 4) #define TWAI_LL_STATUS_TS (0x1 << 5) #define TWAI_LL_STATUS_ES (0x1 << 6) #define TWAI_LL_STATUS_BS (0x1 << 7) //Todo: Add Miss status support #define TWAI_LL_INTR_RI (0x1 << 0) #define TWAI_LL_INTR_TI (0x1 << 1) #define TWAI_LL_INTR_EI (0x1 << 2) //Data overrun interrupt not supported in SW due to HW peculiarities #define TWAI_LL_INTR_EPI (0x1 << 5) #define TWAI_LL_INTR_ALI (0x1 << 6) #define TWAI_LL_INTR_BEI (0x1 << 7) /* * The following frame structure has an NEARLY identical bit field layout to * each byte of the TX buffer. This allows for formatting and parsing frames to * be done outside of time critical regions (i.e., ISRs). All the ISR needs to * do is to copy byte by byte to/from the TX/RX buffer. The two reserved bits in * TX buffer are used in the frame structure to store the self_reception and * single_shot flags which in turn indicate the type of transmission to execute. */ typedef union { struct { struct { uint8_t dlc: 4; //Data length code (0 to 8) of the frame uint8_t self_reception: 1; //This frame should be transmitted using self reception command uint8_t single_shot: 1; //This frame should be transmitted using single shot command uint8_t rtr: 1; //This frame is a remote transmission request uint8_t frame_format: 1; //Format of the frame (1 = extended, 0 = standard) }; union { struct { uint8_t id[2]; //11 bit standard frame identifier uint8_t data[8]; //Data bytes (0 to 8) uint8_t reserved8[2]; } standard; struct { uint8_t id[4]; //29 bit extended frame identifier uint8_t data[8]; //Data bytes (0 to 8) } extended; }; }; uint8_t bytes[13]; } __attribute__((packed)) twai_ll_frame_buffer_t; _Static_assert(sizeof(twai_ll_frame_buffer_t) == 13, "TX/RX buffer type should be 13 bytes"); /* ---------------------------- Mode Register ------------------------------- */ /** * @brief Enter reset mode * * When in reset mode, the TWAI controller is effectively disconnected from the * TWAI bus and will not participate in any bus activates. Reset mode is required * in order to write the majority of configuration registers. * * @param hw Start address of the TWAI registers * @return true if reset mode was entered successfully * * @note Reset mode is automatically entered on BUS OFF condition */ static inline bool twai_ll_enter_reset_mode(twai_dev_t *hw) { hw->mode_reg.rm = 1; return hw->mode_reg.rm; } /** * @brief Exit reset mode * * When not in reset mode, the TWAI controller will take part in bus activities * (e.g., send/receive/acknowledge messages and error frames) depending on the * operating mode. * * @param hw Start address of the TWAI registers * @return true if reset mode was exit successfully * * @note Reset mode must be exit to initiate BUS OFF recovery */ static inline bool twai_ll_exit_reset_mode(twai_dev_t *hw) { hw->mode_reg.rm = 0; return !(hw->mode_reg.rm); } /** * @brief Check if in reset mode * @param hw Start address of the TWAI registers * @return true if in reset mode */ static inline bool twai_ll_is_in_reset_mode(twai_dev_t *hw) { return hw->mode_reg.rm; } /** * @brief Set operating mode of TWAI controller * * @param hw Start address of the TWAI registers * @param mode Operating mode * * @note Must be called in reset mode */ static inline void twai_ll_set_mode(twai_dev_t *hw, twai_mode_t mode) { if (mode == TWAI_MODE_NORMAL) { //Normal Operating mode hw->mode_reg.lom = 0; hw->mode_reg.stm = 0; } else if (mode == TWAI_MODE_NO_ACK) { //Self Test Mode (No Ack) hw->mode_reg.lom = 0; hw->mode_reg.stm = 1; } else if (mode == TWAI_MODE_LISTEN_ONLY) { //Listen Only Mode hw->mode_reg.lom = 1; hw->mode_reg.stm = 0; } } /* --------------------------- Command Register ----------------------------- */ /** * @brief Set TX command * * Setting the TX command will cause the TWAI controller to attempt to transmit * the frame stored in the TX buffer. The TX buffer will be occupied (i.e., * locked) until TX completes. * * @param hw Start address of the TWAI registers * * @note Transmit commands should be called last (i.e., after handling buffer * release and clear data overrun) in order to prevent the other commands * overwriting this latched TX bit with 0. */ static inline void twai_ll_set_cmd_tx(twai_dev_t *hw) { hw->command_reg.tr = 1; } /** * @brief Set single shot TX command * * Similar to setting TX command, but the TWAI controller will not automatically * retry transmission upon an error (e.g., due to an acknowledgement error). * * @param hw Start address of the TWAI registers * * @note Transmit commands should be called last (i.e., after handling buffer * release and clear data overrun) in order to prevent the other commands * overwriting this latched TX bit with 0. */ static inline void twai_ll_set_cmd_tx_single_shot(twai_dev_t *hw) { hw->command_reg.val = 0x03; //Set command_reg.tr and command_reg.at simultaneously for single shot transmittion request } /** * @brief Aborts TX * * Frames awaiting TX will be aborted. Frames already being TX are not aborted. * Transmission Complete Status bit is automatically set to 1. * Similar to setting TX command, but the TWAI controller will not automatically * retry transmission upon an error (e.g., due to acknowledge error). * * @param hw Start address of the TWAI registers * * @note Transmit commands should be called last (i.e., after handling buffer * release and clear data overrun) in order to prevent the other commands * overwriting this latched TX bit with 0. */ static inline void twai_ll_set_cmd_abort_tx(twai_dev_t *hw) { hw->command_reg.at = 1; } /** * @brief Release RX buffer * * Rotates RX buffer to the next frame in the RX FIFO. * * @param hw Start address of the TWAI registers */ static inline void twai_ll_set_cmd_release_rx_buffer(twai_dev_t *hw) { hw->command_reg.rrb = 1; } /** * @brief Clear data overrun * * Clears the data overrun status bit * * @param hw Start address of the TWAI registers */ static inline void twai_ll_set_cmd_clear_data_overrun(twai_dev_t *hw) { hw->command_reg.cdo = 1; } /** * @brief Set self reception single shot command * * Similar to setting TX command, but the TWAI controller also simultaneously * receive the transmitted frame and is generally used for self testing * purposes. The TWAI controller will not ACK the received message, so consider * using the NO_ACK operating mode. * * @param hw Start address of the TWAI registers * * @note Transmit commands should be called last (i.e., after handling buffer * release and clear data overrun) in order to prevent the other commands * overwriting this latched TX bit with 0. */ static inline void twai_ll_set_cmd_self_rx_request(twai_dev_t *hw) { hw->command_reg.srr = 1; } /** * @brief Set self reception request command * * Similar to setting the self reception request, but the TWAI controller will * not automatically retry transmission upon an error (e.g., due to and * acknowledgement error). * * @param hw Start address of the TWAI registers * * @note Transmit commands should be called last (i.e., after handling buffer * release and clear data overrun) in order to prevent the other commands * overwriting this latched TX bit with 0. */ static inline void twai_ll_set_cmd_self_rx_single_shot(twai_dev_t *hw) { hw->command_reg.val = 0x12; //Set command_reg.srr and command_reg.at simultaneously for single shot self reception request } /* --------------------------- Status Register ------------------------------ */ /** * @brief Get all status bits * * @param hw Start address of the TWAI registers * @return Status bits */ static inline uint32_t twai_ll_get_status(twai_dev_t *hw) { return hw->status_reg.val; } /** * @brief Check if RX FIFO overrun status bit is set * * @param hw Start address of the TWAI registers * @return Overrun status bit */ static inline bool twai_ll_is_fifo_overrun(twai_dev_t *hw) { return hw->status_reg.dos; } /** * @brief Check if previously TX was successful * * @param hw Start address of the TWAI registers * @return Whether previous TX was successful */ static inline bool twai_ll_is_last_tx_successful(twai_dev_t *hw) { return hw->status_reg.tcs; } //Todo: Add stand alone status bit check functions when necessary /* -------------------------- Interrupt Register ---------------------------- */ /** * @brief Get currently set interrupts * * Reading the interrupt registers will automatically clear all interrupts * except for the Receive Interrupt. * * @param hw Start address of the TWAI registers * @return Bit mask of set interrupts */ static inline uint32_t twai_ll_get_and_clear_intrs(twai_dev_t *hw) { return hw->interrupt_reg.val; } /* ----------------------- Interrupt Enable Register ------------------------ */ /** * @brief Set which interrupts are enabled * * @param hw Start address of the TWAI registers * @param Bit mask of interrupts to enable * * @note Must be called in reset mode */ static inline void twai_ll_set_enabled_intrs(twai_dev_t *hw, uint32_t intr_mask) { hw->interrupt_enable_reg.val = intr_mask; } /* ------------------------ Bus Timing Registers --------------------------- */ /** * @brief Set bus timing * * @param hw Start address of the TWAI registers * @param brp Baud Rate Prescaler * @param sjw Synchronization Jump Width * @param tseg1 Timing Segment 1 * @param tseg2 Timing Segment 2 * @param triple_sampling Triple Sampling enable/disable * * @note Must be called in reset mode * @note ESP32S2 brp can be any even number between 2 to 32768 */ static inline void twai_ll_set_bus_timing(twai_dev_t *hw, uint32_t brp, uint32_t sjw, uint32_t tseg1, uint32_t tseg2, bool triple_sampling) { hw->bus_timing_0_reg.brp = (brp / 2) - 1; hw->bus_timing_0_reg.sjw = sjw - 1; hw->bus_timing_1_reg.tseg1 = tseg1 - 1; hw->bus_timing_1_reg.tseg2 = tseg2 - 1; hw->bus_timing_1_reg.sam = triple_sampling; } /* ----------------------------- ALC Register ------------------------------- */ /** * @brief Clear Arbitration Lost Capture Register * * Reading the ALC register rearms the Arbitration Lost Interrupt * * @param hw Start address of the TWAI registers */ static inline void twai_ll_clear_arb_lost_cap(twai_dev_t *hw) { (void)hw->arbitration_lost_captue_reg.val; //Todo: Decode ALC register } /* ----------------------------- ECC Register ------------------------------- */ /** * @brief Clear Error Code Capture register * * Reading the ECC register rearms the Bus Error Interrupt * * @param hw Start address of the TWAI registers */ static inline void twai_ll_clear_err_code_cap(twai_dev_t *hw) { (void)hw->error_code_capture_reg.val; //Todo: Decode error code capture } /* ----------------------------- EWL Register ------------------------------- */ /** * @brief Set Error Warning Limit * * @param hw Start address of the TWAI registers * @param ewl Error Warning Limit * * @note Must be called in reset mode */ static inline void twai_ll_set_err_warn_lim(twai_dev_t *hw, uint32_t ewl) { hw->error_warning_limit_reg.ewl = ewl; } /** * @brief Get Error Warning Limit * * @param hw Start address of the TWAI registers * @return Error Warning Limit */ static inline uint32_t twai_ll_get_err_warn_lim(twai_dev_t *hw) { return hw->error_warning_limit_reg.val; } /* ------------------------ RX Error Count Register ------------------------- */ /** * @brief Get RX Error Counter * * @param hw Start address of the TWAI registers * @return REC value * * @note REC is not frozen in reset mode. Listen only mode will freeze it. A BUS * OFF condition automatically sets the REC to 0. */ static inline uint32_t twai_ll_get_rec(twai_dev_t *hw) { return hw->rx_error_counter_reg.val; } /** * @brief Set RX Error Counter * * @param hw Start address of the TWAI registers * @param rec REC value * * @note Must be called in reset mode */ static inline void twai_ll_set_rec(twai_dev_t *hw, uint32_t rec) { hw->rx_error_counter_reg.rxerr = rec; } /* ------------------------ TX Error Count Register ------------------------- */ /** * @brief Get TX Error Counter * * @param hw Start address of the TWAI registers * @return TEC value * * @note A BUS OFF condition will automatically set this to 128 */ static inline uint32_t twai_ll_get_tec(twai_dev_t *hw) { return hw->tx_error_counter_reg.val; } /** * @brief Set TX Error Counter * * @param hw Start address of the TWAI registers * @param tec TEC value * * @note Must be called in reset mode */ static inline void twai_ll_set_tec(twai_dev_t *hw, uint32_t tec) { hw->tx_error_counter_reg.txerr = tec; } /* ---------------------- Acceptance Filter Registers ----------------------- */ /** * @brief Set Acceptance Filter * @param hw Start address of the TWAI registers * @param code Acceptance Code * @param mask Acceptance Mask * @param single_filter Whether to enable single filter mode * * @note Must be called in reset mode */ static inline void twai_ll_set_acc_filter(twai_dev_t* hw, uint32_t code, uint32_t mask, bool single_filter) { uint32_t code_swapped = __builtin_bswap32(code); uint32_t mask_swapped = __builtin_bswap32(mask); for (int i = 0; i < 4; i++) { hw->acceptance_filter.acr[i].byte = ((code_swapped >> (i * 8)) & 0xFF); hw->acceptance_filter.amr[i].byte = ((mask_swapped >> (i * 8)) & 0xFF); } hw->mode_reg.afm = single_filter; } /* ------------------------- TX/RX Buffer Registers ------------------------- */ /** * @brief Copy a formatted TWAI frame into TX buffer for transmission * * @param hw Start address of the TWAI registers * @param tx_frame Pointer to formatted frame * * @note Call twai_ll_format_frame_buffer() to format a frame */ static inline void twai_ll_set_tx_buffer(twai_dev_t *hw, twai_ll_frame_buffer_t *tx_frame) { //Copy formatted frame into TX buffer for (int i = 0; i < 13; i++) { hw->tx_rx_buffer[i].val = tx_frame->bytes[i]; } } /** * @brief Copy a received frame from the RX buffer for parsing * * @param hw Start address of the TWAI registers * @param rx_frame Pointer to store formatted frame * * @note Call twai_ll_prase_frame_buffer() to parse the formatted frame */ static inline void twai_ll_get_rx_buffer(twai_dev_t *hw, twai_ll_frame_buffer_t *rx_frame) { //Copy RX buffer registers into frame for (int i = 0; i < 13; i++) { rx_frame->bytes[i] = hw->tx_rx_buffer[i].byte; } } /** * @brief Format contents of a TWAI frame into layout of TX Buffer * * This function encodes a message into a frame structure. The frame structure * has an identical layout to the TX buffer, allowing the frame structure to be * directly copied into TX buffer. * * @param[in] 11bit or 29bit ID * @param[in] dlc Data length code * @param[in] data Pointer to an 8 byte array containing data. NULL if no data * @param[in] format Type of TWAI frame * @param[in] single_shot Frame will not be retransmitted on failure * @param[in] self_rx Frame will also be simultaneously received * @param[out] tx_frame Pointer to store formatted frame */ static inline void twai_ll_format_frame_buffer(uint32_t id, uint8_t dlc, const uint8_t *data, uint32_t flags, twai_ll_frame_buffer_t *tx_frame) { bool is_extd = flags & TWAI_MSG_FLAG_EXTD; bool is_rtr = flags & TWAI_MSG_FLAG_RTR; //Set frame information tx_frame->dlc = dlc; tx_frame->frame_format = is_extd; tx_frame->rtr = is_rtr; tx_frame->self_reception = (flags & TWAI_MSG_FLAG_SELF) ? 1 : 0; tx_frame->single_shot = (flags & TWAI_MSG_FLAG_SS) ? 1 : 0; //Set ID. The ID registers are big endian and left aligned, therefore a bswap will be required if (is_extd) { uint32_t id_temp = __builtin_bswap32((id & TWAI_EXTD_ID_MASK) << 3); //((id << 3) >> 8*(3-i)) for (int i = 0; i < 4; i++) { tx_frame->extended.id[i] = (id_temp >> (8 * i)) & 0xFF; } } else { uint32_t id_temp = __builtin_bswap16((id & TWAI_STD_ID_MASK) << 5); //((id << 5) >> 8*(1-i)) for (int i = 0; i < 2; i++) { tx_frame->standard.id[i] = (id_temp >> (8 * i)) & 0xFF; } } uint8_t *data_buffer = (is_extd) ? tx_frame->extended.data : tx_frame->standard.data; if (!is_rtr) { //Only copy data if the frame is a data frame (i.e not a remote frame) for (int i = 0; (i < dlc) && (i < TWAI_FRAME_MAX_DLC); i++) { data_buffer[i] = data[i]; } } } /** * @brief Parse formatted TWAI frame (RX Buffer Layout) into its constituent contents * * @param[in] rx_frame Pointer to formatted frame * @param[out] id 11 or 29bit ID * @param[out] dlc Data length code * @param[out] data Data. Left over bytes set to 0. * @param[out] format Type of TWAI frame */ static inline void twai_ll_prase_frame_buffer(twai_ll_frame_buffer_t *rx_frame, uint32_t *id, uint8_t *dlc, uint8_t *data, uint32_t *flags) { //Copy frame information *dlc = rx_frame->dlc; uint32_t flags_temp = 0; flags_temp |= (rx_frame->frame_format) ? TWAI_MSG_FLAG_EXTD : 0; flags_temp |= (rx_frame->rtr) ? TWAI_MSG_FLAG_RTR : 0; flags_temp |= (rx_frame->dlc > TWAI_FRAME_MAX_DLC) ? TWAI_MSG_FLAG_DLC_NON_COMP : 0; *flags = flags_temp; //Copy ID. The ID registers are big endian and left aligned, therefore a bswap will be required if (rx_frame->frame_format) { uint32_t id_temp = 0; for (int i = 0; i < 4; i++) { id_temp |= rx_frame->extended.id[i] << (8 * i); } id_temp = __builtin_bswap32(id_temp) >> 3; //((byte[i] << 8*(3-i)) >> 3) *id = id_temp & TWAI_EXTD_ID_MASK; } else { uint32_t id_temp = 0; for (int i = 0; i < 2; i++) { id_temp |= rx_frame->standard.id[i] << (8 * i); } id_temp = __builtin_bswap16(id_temp) >> 5; //((byte[i] << 8*(1-i)) >> 5) *id = id_temp & TWAI_STD_ID_MASK; } uint8_t *data_buffer = (rx_frame->frame_format) ? rx_frame->extended.data : rx_frame->standard.data; //Only copy data if the frame is a data frame (i.e. not a remote frame) int data_length = (rx_frame->rtr) ? 0 : ((rx_frame->dlc > TWAI_FRAME_MAX_DLC) ? TWAI_FRAME_MAX_DLC : rx_frame->dlc); for (int i = 0; i < data_length; i++) { data[i] = data_buffer[i]; } //Set remaining bytes of data to 0 for (int i = data_length; i < TWAI_FRAME_MAX_DLC; i++) { data[i] = 0; } } /* ----------------------- RX Message Count Register ------------------------ */ /** * @brief Get RX Message Counter * * @param hw Start address of the TWAI registers * @return RX Message Counter */ static inline uint32_t twai_ll_get_rx_msg_count(twai_dev_t *hw) { return hw->rx_message_counter_reg.val; } /* ------------------------- Clock Divider Register ------------------------- */ /** * @brief Set CLKOUT Divider and enable/disable * * Configure CLKOUT. CLKOUT is a pre-scaled version of APB CLK. Divider can be * 1, or any even number from 2 to 490. Set the divider to 0 to disable CLKOUT. * * @param hw Start address of the TWAI registers * @param divider Divider for CLKOUT (any even number from 2 to 490). Set to 0 to disable CLKOUT */ static inline void twai_ll_set_clkout(twai_dev_t *hw, uint32_t divider) { if (divider >= 2 && divider <= 490) { hw->clock_divider_reg.co = 0; hw->clock_divider_reg.cd = (divider / 2) - 1; } else if (divider == 1) { //Setting the divider reg to max value (255) means a divider of 1 hw->clock_divider_reg.co = 0; hw->clock_divider_reg.cd = 255; } else { hw->clock_divider_reg.co = 1; hw->clock_divider_reg.cd = 0; } } #ifdef __cplusplus } #endif