/* Copyright 2026 Igor Gorniak gorniak.igor@gmail.com Copyright 2016 - 2026 Benjamin Vedder benjamin@vedder.se 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_ma782.h" #include #include #include "ch.h" #include "hal.h" #include "mc_interface.h" #include "utils_math.h" #include "spi_bb.h" #include "timer.h" #include "commands.h" #include "hw.h" #define BIT_MASK(l, h) (((1U << ((h) + 1)) - 1) & ~((1U << (l)) - 1)) #define MA782_READ_REG_CMD 0x40 #define MA782_READ_REG_ADDR(x) ((x) & BIT_MASK(0, 4)) #define MA782_WRITE_REG_CMD 0x80 #define MA782_WRITE_REG_ADDR(x) ((x) & BIT_MASK(0, 4)) #define MA782_REG_FW 0x0E #define MA782_FILTER_WINDOW 6 #define MA782_RESOLUTION_BITS 9 #define MA782_MAX_RESOLUTION_BITS 12 static void ma782_error(ma782_state_t *state, unsigned err) { state->error |= err; state->error_count++; } static uint16_t ma782_resolution_mask(ma782_state_t *state) { (void)(state); return BIT_MASK(4 + (MA782_MAX_RESOLUTION_BITS - MA782_RESOLUTION_BITS), 15); } static uint8_t ma782_read_reg(ma782_config_t *cfg, uint8_t reg_addr) { ma782_state_t *state = &cfg->state; if (state->substate != MA782_IDLE) { ma782_error(state, MA782_READ_NOT_IDLE); return false; } state->tx_data = (MA782_READ_REG_CMD | MA782_READ_REG_ADDR(reg_addr)) << 8; spiSelectI(cfg->spi_dev); __NOP(); spiPolledExchange(cfg->spi_dev, state->tx_data); spiUnselectI(cfg->spi_dev); __NOP(); spiSelectI(cfg->spi_dev); __NOP(); state->rx_data = spiPolledExchange(cfg->spi_dev, 0); spiUnselectI(cfg->spi_dev); return state->rx_data & BIT_MASK(0, 7); } static bool ma782_write_reg(ma782_config_t *cfg, uint8_t reg_addr, uint8_t reg_val) { ma782_state_t *state = &cfg->state; if (state->substate != MA782_IDLE) { ma782_error(state, MA782_WRITE_NOT_IDLE); return false; } state->tx_data = ((MA782_WRITE_REG_CMD |\ MA782_WRITE_REG_ADDR(reg_addr)) << 8) | reg_val; spiSelectI(cfg->spi_dev); spiPolledExchange(cfg->spi_dev, state->tx_data); spiUnselectI(cfg->spi_dev); __NOP(); spiSelectI(cfg->spi_dev); state->rx_data = spiPolledExchange(cfg->spi_dev, 0); spiUnselectI(cfg->spi_dev); if ((state->rx_data & BIT_MASK(0, 7)) != reg_val) { ma782_error(state, MA782_WRITE_REG_FAIL); return false; } if (ma782_read_reg(cfg, reg_addr) != reg_val) { ma782_error(state, MA782_WRITE_READOUT_FAIL); return false; } return true; } static bool ma782_read_angle(ma782_config_t *cfg) { ma782_state_t *state = &cfg->state; if (state->substate != MA782_IDLE) { ma782_error(state, MA782_ANGLE_NOT_IDLE); return false; } state->tx_data = 0; // 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 state->substate = MA782_READ_ANGLE_REQ; spiSelectI(cfg->spi_dev); __NOP(); spiStartExchangeI(cfg->spi_dev, 1, &state->tx_data, &state->rx_data); return true; } static bool ma782_read_angle_finish(ma782_config_t *cfg) { ma782_state_t *state = &cfg->state; uint16_t angle = (uint16_t)state->rx_data; angle &= ma782_resolution_mask(state); float ret = (float)angle * (360.0f / 65535.0f); state->last_enc_angle = ret; state->substate = MA782_IDLE; return true; } static void ma782_error_cb(SPIDriver *pspi) { if(pspi != NULL && pspi->app_arg != NULL) { ma782_config_t *cfg = (ma782_config_t*)pspi->app_arg; ma782_state_t *state = &cfg->state; ma782_error(state, MA782_SPI_ERROR); } } static void ma782_init_config(ma782_config_t *cfg) { memset(&cfg->state, 0, sizeof(ma782_state_t)); } void enc_ma782_routine(ma782_config_t *cfg) { ma782_state_t *state = &cfg->state; if (cfg->spi_dev->state == SPI_READY) { if(state->start > 0) { ma782_read_angle(cfg); } UTILS_LP_FAST(state->spi_comm_error_rate, 0.0, 0.0001); } else { ++state->spi_comm_error_cnt; ma782_error(state, MA782_SPI_NOT_READY); // compute rate with factor 0.0001 for 10000hz UTILS_LP_FAST(state->spi_comm_error_rate, 1.0, 0.0001); } } void compute_ma782_callback(SPIDriver *pspi) { if (pspi != NULL && pspi->app_arg != NULL) { ma782_config_t *cfg = (ma782_config_t*)pspi->app_arg; ma782_state_t *state = &cfg->state; spiUnselectI(cfg->spi_dev); state->spi_cnt++; switch (state->substate) { case MA782_IDLE: ma782_error(state, MA782_CALLBACK_IN_IDLE); break; case MA782_READ_ANGLE_REQ: ma782_read_angle_finish(cfg); break; default: ma782_error(state, MA782_UNKNOWN_STATE); break; } } } static uint8_t ma782_read_fw(ma782_config_t *cfg) { return ma782_read_reg(cfg, MA782_REG_FW) >> 4; } static bool ma782_write_fw(ma782_config_t *cfg, uint8_t value) { return ma782_write_reg(cfg, MA782_REG_FW, (value & 0x0F) << 4); } static bool ma782_set_filter_window(ma782_config_t *cfg, uint8_t value) { if (ma782_read_fw(cfg) != value) { return ma782_write_fw(cfg, value); } return true; } bool enc_ma782_init(ma782_config_t *cfg) { ma782_state_t *state = &cfg->state; if (cfg->spi_dev == NULL) { return false; } ma782_init_config(cfg); cfg->spi_dev->app_arg = (void*)cfg; cfg->spi_dev->err_cb = ma782_error_cb; 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 | PAL_STM32_PUDR_FLOATING); 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); palSetPadMode(cfg->en_gpio, cfg->en_pin, PAL_MODE_OUTPUT_PUSHPULL | PAL_STM32_OSPEED_HIGHEST); chThdSleepMilliseconds(1); palSetPad(cfg->en_gpio, cfg->en_pin); chThdSleepMilliseconds(1); spiStart(cfg->spi_dev, &(cfg->hw_spi_cfg)); chThdSleepMilliseconds(1); bool ret = true; if (!ma782_set_filter_window(cfg, MA782_FILTER_WINDOW)) { ret = false; } if (ret == true) { state->start = 1; } return ret; } void enc_ma782_deinit(ma782_config_t *cfg) { if (cfg->spi_dev != NULL) { palClearPad(cfg->en_gpio, cfg->en_pin); 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); palSetPadMode(cfg->en_gpio, cfg->en_pin, PAL_MODE_INPUT_PULLDOWN); spiStop(cfg->spi_dev); cfg->state.last_enc_angle = 0.0f; cfg->state.spi_error_cnt = 0; cfg->state.spi_error_rate = 0.0f; } } void enc_ma782_print_status(ma782_config_t *cfg) { ma782_state_t *state = &cfg->state; commands_printf("MA782 STATUS:\n" "Last angle : %.3f\n" "Error : 0x%04x\n" "Error count : %u\n" "SPI cnt : %u\n" "Last TX : %04x\n" "Last RX : %04x\n" "Substate : %u\n" "Start : %u\n" , (double)state->last_enc_angle, (unsigned)state->error, (unsigned)state->error_count, (unsigned)state->spi_cnt, (unsigned)state->tx_data, (unsigned)state->rx_data, (unsigned)state->substate, (unsigned)state->start ); }