/* Copyright 2016 - 2022 Benjamin Vedder benjamin@vedder.se Copyright 2022 Zach O'Brien This file is part of the VESC firmware. The VESC firmware 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. The VESC firmware 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 . */ #include "enc_as5x47u.h" #include "ch.h" #include "hal.h" #include "stm32f4xx_conf.h" #include "hw.h" #include "mc_interface.h" #include "utils_math.h" #include "spi_bb.h" #include "timer.h" #include #include #define AS5x47U_SPI_READ_BIT 0x4000 #define AS5x47U_SPI_WRITE_BIT 0x0000 #define AS5x47U_SPI_DIAG_FUSA_ERROR_BIT_POS 10 #define AS5x47U_SPI_DIAG_COF_BIT_POS 2 #define AS5x47U_SPI_DIAG_COMP_LOW_BIT_POS 3 #define AS5x47U_SPI_DIAG_COMP_HIGH_BIT_POS 4 #define AS5x47U_SPI_ERRFL_WDTST_BIT_POS 7 #define AS5x47U_SPI_ERRFL_CRC_ERROR_BIT_POS 6 #define AS5x47U_SPI_ERRFL_MAG_HALF_BIT_POS 1 #define AS5x47U_SPI_EXCLUDE_PARITY_AND_ERROR_BITMASK 0x3FFF #define AS5x47U_SPI_AGC_MASK 0xFF #define AS5x47U_SPI_WARN_FLAG_MASK 0x8000 #define AS5x47U_SPI_ERROR_FLAG_MASK 0x4000 #define AS5x47U_SPI_ERRFL_ADR 0x0001 #define AS5x47U_SPI_DIAG_ADR 0x3FF5 #define AS5x47U_SPI_MAGN_ADR 0x3FFD #define AS5x47U_SPI_AGC_ADR 0x3FF9 #define AS5x47U_SPI_POS_ADR 0x3FFF #define AS5x47U_SPI_READ_ERRFL_MSG (AS5x47U_SPI_ERRFL_ADR | AS5x47U_SPI_READ_BIT) #define AS5x47U_SPI_READ_DIAG_MSG (AS5x47U_SPI_DIAG_ADR | AS5x47U_SPI_READ_BIT) #define AS5x47U_SPI_READ_MAGN_MSG (AS5x47U_SPI_MAGN_ADR | AS5x47U_SPI_READ_BIT) #define AS5x47U_SPI_READ_AGC_MSG (AS5x47U_SPI_AGC_ADR | AS5x47U_SPI_READ_BIT) #define AS5x47U_SPI_READ_POS_MSG (AS5x47U_SPI_POS_ADR | AS5x47U_SPI_READ_BIT) #define AS5x47U_SPI_READ_ERRFL_CRC (0x06) #define AS5x47U_SPI_READ_DIAG_CRC (0x6F) #define AS5x47U_SPI_READ_MAGN_CRC (0x87) #define AS5x47U_SPI_READ_AGC_CRC (0xF3) #define AS5x47U_SPI_READ_POS_CRC (0xBD) #define AS5x47U_CONNECTION_DETERMINATOR_ERROR_THRESHOLD 5 enum { SPI_SEQ_TX_MAG_RX_POS, SPI_SEQ_TX_POS_RX_MAG, SPI_SEQ_TX_AGC_RX_POS, SPI_SEQ_TX_POS_RX_AGC, SPI_SEQ_TX_DIAG_RX_POS, SPI_SEQ_TX_POS_RX_DIAG, SPI_SEQ_TX_ERRFL_RX_POS, SPI_SEQ_TX_POS_RX_ERRFL, SPI_SEQ_PREV_ERR, }; // Private functions static void AS5x47U_determinate_if_connected(AS5x47U_config_t *cfg, bool was_last_valid); static void AS5x47U_start_spi_exchange_precalc_crc(AS5x47U_config_t *cfg, uint16_t tx_data, uint8_t tx_crc); static void AS5x47U_process_pos(AS5x47U_config_t *cfg, uint16_t posData); static uint8_t enc_as5x47u_crc8(const uint8_t *data, size_t len, const uint8_t inital) { // Lookup table generated using poly 0x1D static const uint8_t crc8_lookup[] = { 0x00, 0x1D, 0x3A, 0x27, 0x74, 0x69, 0x4E, 0x53, 0xE8, 0xF5, 0xD2, 0xCF, 0x9C, 0x81, 0xA6, 0xBB, 0xCD, 0xD0, 0xF7, 0xEA, 0xB9, 0xA4, 0x83, 0x9E, 0x25, 0x38, 0x1F, 0x02, 0x51, 0x4C, 0x6B, 0x76, 0x87, 0x9A, 0xBD, 0xA0, 0xF3, 0xEE, 0xC9, 0xD4, 0x6F, 0x72, 0x55, 0x48, 0x1B, 0x06, 0x21, 0x3C, 0x4A, 0x57, 0x70, 0x6D, 0x3E, 0x23, 0x04, 0x19, 0xA2, 0xBF, 0x98, 0x85, 0xD6, 0xCB, 0xEC, 0xF1, 0x13, 0x0E, 0x29, 0x34, 0x67, 0x7A, 0x5D, 0x40, 0xFB, 0xE6, 0xC1, 0xDC, 0x8F, 0x92, 0xB5, 0xA8, 0xDE, 0xC3, 0xE4, 0xF9, 0xAA, 0xB7, 0x90, 0x8D, 0x36, 0x2B, 0x0C, 0x11, 0x42, 0x5F, 0x78, 0x65, 0x94, 0x89, 0xAE, 0xB3, 0xE0, 0xFD, 0xDA, 0xC7, 0x7C, 0x61, 0x46, 0x5B, 0x08, 0x15, 0x32, 0x2F, 0x59, 0x44, 0x63, 0x7E, 0x2D, 0x30, 0x17, 0x0A, 0xB1, 0xAC, 0x8B, 0x96, 0xC5, 0xD8, 0xFF, 0xE2, 0x26, 0x3B, 0x1C, 0x01, 0x52, 0x4F, 0x68, 0x75, 0xCE, 0xD3, 0xF4, 0xE9, 0xBA, 0xA7, 0x80, 0x9D, 0xEB, 0xF6, 0xD1, 0xCC, 0x9F, 0x82, 0xA5, 0xB8, 0x03, 0x1E, 0x39, 0x24, 0x77, 0x6A, 0x4D, 0x50, 0xA1, 0xBC, 0x9B, 0x86, 0xD5, 0xC8, 0xEF, 0xF2, 0x49, 0x54, 0x73, 0x6E, 0x3D, 0x20, 0x07, 0x1A, 0x6C, 0x71, 0x56, 0x4B, 0x18, 0x05, 0x22, 0x3F, 0x84, 0x99, 0xBE, 0xA3, 0xF0, 0xED, 0xCA, 0xD7, 0x35, 0x28, 0x0F, 0x12, 0x41, 0x5C, 0x7B, 0x66, 0xDD, 0xC0, 0xE7, 0xFA, 0xA9, 0xB4, 0x93, 0x8E, 0xF8, 0xE5, 0xC2, 0xDF, 0x8C, 0x91, 0xB6, 0xAB, 0x10, 0x0D, 0x2A, 0x37, 0x64, 0x79, 0x5E, 0x43, 0xB2, 0xAF, 0x88, 0x95, 0xC6, 0xDB, 0xFC, 0xE1, 0x5A, 0x47, 0x60, 0x7D, 0x2E, 0x33, 0x14, 0x09, 0x7F, 0x62, 0x45, 0x58, 0x0B, 0x16, 0x31, 0x2C, 0x97, 0x8A, 0xAD, 0xB0, 0xE3, 0xFE, 0xD9, 0xC4, }; uint8_t cksum = inital; size_t i; for (i = 0; i < len; i++) { cksum ^= data[i]; cksum = crc8_lookup[cksum]; } return cksum; } void enc_as5x47u_spi_callback(SPIDriver *pspi) { if (pspi != NULL && pspi->app_arg != NULL) { AS5x47U_config_t *cfg = (AS5x47U_config_t*)pspi->app_arg; spiUnselectI(cfg->spi_dev); // Determine time step for error rate calculation float timestep = timer_seconds_elapsed_since(cfg->state.last_update_time); if (timestep > 1.0) { timestep = 1.0; } cfg->state.last_update_time = timer_time_now(); uint8_t rx_crc = cfg->state.rx_buf[2]; uint8_t calc_crc = enc_as5x47u_crc8(cfg->state.rx_buf, 2, 0xC4) ^ 0xFF; uint16_t rx_data = cfg->state.rx_buf[0] << 8 | cfg->state.rx_buf[1]; if (calc_crc == rx_crc) { AS5x47U_determinate_if_connected(cfg, true); cfg->state.sensor_diag.is_error = (uint8_t)((rx_data & AS5x47U_SPI_ERROR_FLAG_MASK) != 0); if (!cfg->state.sensor_diag.is_error) { UTILS_LP_FAST(cfg->state.spi_error_rate, 0.0, timestep); switch(cfg->state.spi_seq) { case SPI_SEQ_TX_MAG_RX_POS: // Receive position, then request position while receiving magnitude AS5x47U_process_pos(cfg, rx_data); cfg->state.spi_seq = SPI_SEQ_TX_POS_RX_MAG; AS5x47U_start_spi_exchange_precalc_crc( cfg, AS5x47U_SPI_READ_POS_MSG, AS5x47U_SPI_READ_POS_CRC); break; case SPI_SEQ_TX_POS_RX_MAG: // Receive magnitude cfg->state.sensor_diag.serial_magnitude = rx_data; cfg->state.sensor_diag.magnitude = rx_data & AS5x47U_SPI_EXCLUDE_PARITY_AND_ERROR_BITMASK; cfg->state.spi_seq = SPI_SEQ_TX_AGC_RX_POS; break; case SPI_SEQ_TX_AGC_RX_POS: // Receive position, then request position while receiving AGC AS5x47U_process_pos(cfg, rx_data); cfg->state.spi_seq = SPI_SEQ_TX_POS_RX_AGC; AS5x47U_start_spi_exchange_precalc_crc( cfg, AS5x47U_SPI_READ_POS_MSG, AS5x47U_SPI_READ_POS_CRC); break; case SPI_SEQ_TX_POS_RX_AGC: // Receive AGC cfg->state.sensor_diag.serial_AGC_value = rx_data; cfg->state.sensor_diag.AGC_value = (uint8_t)rx_data; cfg->state.spi_seq = SPI_SEQ_TX_DIAG_RX_POS; break; case SPI_SEQ_TX_DIAG_RX_POS: // Receive position, then request position while requesting diagnostic flags AS5x47U_process_pos(cfg, rx_data); cfg->state.spi_seq = SPI_SEQ_TX_POS_RX_DIAG; AS5x47U_start_spi_exchange_precalc_crc( cfg, AS5x47U_SPI_READ_POS_MSG, AS5x47U_SPI_READ_POS_CRC); break; case SPI_SEQ_TX_POS_RX_DIAG: // Receive diagnostic flags cfg->state.sensor_diag.serial_diag_flgs = rx_data; cfg->state.sensor_diag.is_broken_hall = (rx_data >> AS5x47U_SPI_DIAG_FUSA_ERROR_BIT_POS) & 1; cfg->state.sensor_diag.is_COF = (rx_data >> AS5x47U_SPI_DIAG_COF_BIT_POS) & 1; cfg->state.sensor_diag.is_Comp_low = (rx_data >> AS5x47U_SPI_DIAG_COMP_LOW_BIT_POS) & 1; cfg->state.sensor_diag.is_Comp_high = (rx_data >> AS5x47U_SPI_DIAG_COMP_HIGH_BIT_POS) & 1; cfg->state.spi_seq = SPI_SEQ_TX_ERRFL_RX_POS; break; case SPI_SEQ_TX_ERRFL_RX_POS: // Receive position, then request pos while receiving error flags AS5x47U_process_pos(cfg, rx_data); cfg->state.spi_seq = SPI_SEQ_TX_POS_RX_ERRFL; AS5x47U_start_spi_exchange_precalc_crc( cfg, AS5x47U_SPI_READ_POS_MSG, AS5x47U_SPI_READ_POS_CRC); break; case SPI_SEQ_TX_POS_RX_ERRFL: // Receive error flags cfg->state.spi_seq = 0; cfg->state.sensor_diag.serial_error_flgs = rx_data; cfg->state.sensor_diag.is_wdtst = (rx_data >> AS5x47U_SPI_ERRFL_WDTST_BIT_POS) & 1; cfg->state.sensor_diag.is_crc_error = (rx_data >> AS5x47U_SPI_ERRFL_CRC_ERROR_BIT_POS) & 1; cfg->state.sensor_diag.is_mag_half = (rx_data >> AS5x47U_SPI_ERRFL_MAG_HALF_BIT_POS) & 1; cfg->state.spi_seq = SPI_SEQ_TX_MAG_RX_POS; break; case SPI_SEQ_PREV_ERR: // Not sure what was just received, but just requested ERRFL, so ignore rx // data and prepare to receive ERRFL next exchange cfg->state.spi_seq = SPI_SEQ_TX_POS_RX_ERRFL; break; default: // Something went wrong, just start the sequence over. cfg->state.spi_seq = SPI_SEQ_TX_MAG_RX_POS; break; } } else { // Error flag is set AS5x47U_determinate_if_connected(cfg, true); // Encoder is connected... UTILS_LP_FAST(cfg->state.spi_error_rate, 1.0, timestep); // But there is an error ++cfg->state.spi_error_cnt; // Error flag is set so need to read error register next cfg->state.spi_seq = SPI_SEQ_PREV_ERR; } } else { // CRC error AS5x47U_determinate_if_connected(cfg, false); UTILS_LP_FAST(cfg->state.spi_error_rate, 1.0, timestep); ++cfg->state.spi_error_cnt; cfg->state.spi_seq = SPI_SEQ_PREV_ERR; } } } static void as5x47u_spi_err_callback(SPIDriver *pspi) { if(pspi != NULL && pspi->app_arg != NULL) { AS5x47U_config_t *cfg = (AS5x47U_config_t*)pspi->app_arg; // Make sure we won't process the data memset(cfg->state.rx_buf, 0, sizeof(cfg->state.rx_buf)); } } bool enc_as5x47u_init(AS5x47U_config_t *cfg) { if (cfg->spi_dev == NULL) { return false; } memset(&cfg->state, 0, sizeof(AS5x47U_state)); palSetPadMode(cfg->sck_gpio, cfg->sck_pin, PAL_MODE_ALTERNATE(cfg->spi_af) | PAL_STM32_OSPEED_HIGHEST); palSetPadMode(cfg->miso_gpio, cfg->miso_pin, PAL_MODE_ALTERNATE(cfg->spi_af) | PAL_STM32_OSPEED_HIGHEST); palSetPadMode(cfg->nss_gpio, cfg->nss_pin, PAL_MODE_OUTPUT_PUSHPULL | PAL_STM32_OSPEED_HIGHEST); palSetPadMode(cfg->mosi_gpio, cfg->mosi_pin, PAL_MODE_ALTERNATE(cfg->spi_af) | PAL_STM32_OSPEED_HIGHEST); cfg->spi_dev->app_arg = (void*)cfg; cfg->spi_dev->err_cb = as5x47u_spi_err_callback; // Set in encoder_cfg.c, kept here for visability // cfg->spi_dev->config->end_cb = enc_as5x47u_spi_callback; spiStart(cfg->spi_dev, &(cfg->hw_spi_cfg)); cfg->state.spi_error_rate = 0.0; return true; } void enc_as5x47u_deinit(AS5x47U_config_t *cfg) { if (cfg->spi_dev != NULL) { palSetPadMode(cfg->miso_gpio, cfg->miso_pin, PAL_MODE_INPUT_PULLUP); palSetPadMode(cfg->sck_gpio, cfg->sck_pin, PAL_MODE_INPUT_PULLUP); palSetPadMode(cfg->nss_gpio, cfg->nss_pin, PAL_MODE_INPUT_PULLUP); palSetPadMode(cfg->mosi_gpio, cfg->mosi_pin, PAL_MODE_INPUT_PULLUP); spiStop(cfg->spi_dev); cfg->state.last_enc_angle = 0.0; cfg->state.spi_error_rate = 0.0; } } void enc_as5x47u_routine(AS5x47U_config_t *cfg) { switch(cfg->state.spi_seq) { case SPI_SEQ_TX_MAG_RX_POS: // Request magnitude AS5x47U_start_spi_exchange_precalc_crc( cfg, AS5x47U_SPI_READ_MAGN_MSG, AS5x47U_SPI_READ_MAGN_CRC); break; case SPI_SEQ_TX_AGC_RX_POS: // Request AGC AS5x47U_start_spi_exchange_precalc_crc( cfg, AS5x47U_SPI_READ_AGC_MSG, AS5x47U_SPI_READ_AGC_CRC); break; case SPI_SEQ_TX_DIAG_RX_POS: // Request diagnostic flags AS5x47U_start_spi_exchange_precalc_crc( cfg, AS5x47U_SPI_READ_DIAG_MSG, AS5x47U_SPI_READ_DIAG_CRC); break; case SPI_SEQ_TX_ERRFL_RX_POS: // Request error flags AS5x47U_start_spi_exchange_precalc_crc(cfg, AS5x47U_SPI_READ_ERRFL_MSG, AS5x47U_SPI_READ_ERRFL_CRC); break; default: case SPI_SEQ_PREV_ERR: // There was a problem of some sort, request ERRFL AS5x47U_start_spi_exchange_precalc_crc(cfg, AS5x47U_SPI_READ_ERRFL_MSG, AS5x47U_SPI_READ_ERRFL_CRC); break; } } static void AS5x47U_process_pos(AS5x47U_config_t *cfg, uint16_t posData) { cfg->state.spi_val = posData; posData &= AS5x47U_SPI_EXCLUDE_PARITY_AND_ERROR_BITMASK; cfg->state.last_enc_angle = (float)(posData * 360) / (float)(1 << 14); } static void AS5x47U_determinate_if_connected(AS5x47U_config_t *cfg, bool was_last_valid) { if (!was_last_valid) { cfg->state.spi_communication_error_count++; if (cfg->state.spi_communication_error_count >= AS5x47U_CONNECTION_DETERMINATOR_ERROR_THRESHOLD) { cfg->state.spi_communication_error_count = AS5x47U_CONNECTION_DETERMINATOR_ERROR_THRESHOLD; cfg->state.sensor_diag.is_connected = 0; } } else { if (cfg->state.spi_communication_error_count) { cfg->state.spi_communication_error_count--; } else { cfg->state.sensor_diag.is_connected = 1; } } } /* TODO: uncomment if needed static void AS5x47U_start_spi_exchange(AS5x47U_config_t *cfg, uint16_t tx_data) { uint8_t tx_to_crc[] = {(tx_data >> 8) & 0xFF, (tx_data & 0xFF)}; uint8_t tx_crc = enc_as5x47u_crc8(tx_to_crc, 2, 0xC4) ^ 0xFF; AS5x47U_start_spi_exchange_precalc_crc(cfg, tx_data, tx_crc); } */ static void AS5x47U_start_spi_exchange_precalc_crc(AS5x47U_config_t *cfg, uint16_t tx_data, uint8_t tx_crc) { cfg->state.tx_buf[0] = (tx_data >> 8) & 0xFF; cfg->state.tx_buf[1] = tx_data & 0xFF; cfg->state.tx_buf[2] = tx_crc; memset(cfg->state.rx_buf, 0, sizeof(cfg->state.rx_buf)); // There is a weird corner case where the DMA may not read all of the Rx data. This // causes the RXNE flag to be set when an exchange starts, causing the first byte of // data received to be from the previous exchange. This is corrected by reading the // SPI data register, clearing the RXNE flag. volatile uint32_t test = cfg->spi_dev->spi->DR; (void)test; // get rid of unused warning spiSelectI(cfg->spi_dev); spiStartExchangeI(cfg->spi_dev, 3, cfg->state.tx_buf, cfg->state.rx_buf); }