/* ** ****************************************************************************** * @file : MESCinterface.c * @brief : Initializing RTOS system and parameters ****************************************************************************** * @attention * *

© Copyright (c) 2022 Jens Kerrinnes. * All rights reserved.

* * This software component is licensed under BSD 3-Clause license, * the "License"; You may not use this file except in compliance with the * License. You may obtain a copy of the License at: * opensource.org/licenses/BSD-3-Clause ****************************************************************************** *In addition to the usual 3 BSD clauses, it is explicitly noted that you *do NOT have the right to take sections of this code for other projects *without attribution and credit to the source. Specifically, if you copy into *copyleft licenced code without attribution and retention of the permissive BSD *3 clause licence, you grant a perpetual licence to do the same regarding turning sections of your code *permissive, and lose any rights to use of this code previously granted or assumed. * *This code is intended to remain permissively licensed wherever it goes, *maintaining the freedom to distribute compiled binaries WITHOUT a requirement to supply source. * *This is to ensure this code can at any point be used commercially, on products that may require *such restriction to meet regulatory requirements, or to avoid damage to hardware, or to ensure *warranties can reasonably be honoured. ******************************************************************************/ #include "cmsis_os2.h" #include "MESCinterface.h" #include "main.h" #include "Tasks/init.h" #include "TTerm/Core/include/TTerm.h" #include "AXIS/MESCmotor_state.h" #include "Tasks/task_cli.h" #include "Tasks/task_can.h" #include "Tasks/task_overlay.h" #include #include #include #include "TTerm/Core/include/TTerm_cwd.h" #include axis_vars_t axis_vars; const char TERM_startupText1[] = "\r\n"; const char TERM_startupText2[] = "\r\n[A][X][I][S] - Throttle"; const char TERM_startupText3[] = "\r\n"; void callback(TermVariableDescriptor * var){ axis_vars.error_count = 0; } void populate_vars(){ can1.node_id = 255; // | Variable | MIN | MAX | NAME | DESCRIPTION | RW | CALLBACK | VAR LIST HANDLE TERM_addVar(can1.node_id , 1 , 254 , "node_id" , "Node ID" , VAR_ACCESS_RW , NULL , &TERM_varList); TERM_addVar(axis_vars.encoder_error_limit , 0 , 1000 , "enc_err_lim" , "Encoder error limit" , VAR_ACCESS_RW , callback , &TERM_varList); //TERM_addVar(axis_vars.check_pwm_vs_spi , 0 , 1 , "sanitycheck" , "Check PWM vs SPI" , VAR_ACCESS_RW , NULL , &TERM_varList); //TERM_addVar(axis_vars.use_spi , 0 , 1 , "use_spi" , "Retrieve angle via SPI" , VAR_ACCESS_RW , NULL , &TERM_varList); //TERM_addVar(axis_vars.use_pwm , 0 , 1 , "use_pwm" , "Retrieve angle via PWM" , VAR_ACCESS_RW , NULL , &TERM_varList); TERM_addVar(axis_vars.throttle_start , 0.0f , 1.0f , "zero" , "Throttle zero" , VAR_ACCESS_RW , NULL , &TERM_varList); TERM_addVar(axis_vars.throttle_end , 0.0f , 1.0f , "span" , "Throttle span" , VAR_ACCESS_RW , NULL , &TERM_varList); TERM_addVar(axis_vars.throttle_offset , 0.0f , 1.0f , "offset" , "Throttle offset" , VAR_ACCESS_RW , NULL , &TERM_varList); TERM_addVar(axis_vars.throttle_threshold , 0.0f , 1.0f , "threshold" , "Throttle threshold" , VAR_ACCESS_RW , NULL , &TERM_varList); } extern volatile TERMINAL_HANDLE * debug; uint32_t count=0; void TASK_CAN_packet_cb(TASK_CAN_handle * handle, uint32_t id, uint8_t sender, uint8_t receiver, uint8_t* data, uint32_t len){ switch(id){ default: break; } } float pi = 3.1415; extern SPI_HandleTypeDef hspi1; extern TIM_HandleTypeDef htim1; extern TIM_HandleTypeDef htim2; extern TIM_HandleTypeDef htim15; HAL_StatusTypeDef SpiStatus; uint8_t has_even_parity(uint16_t x){ uint32_t count=0; for(int i=0; i<16; i++){ if(x & (1 << i)){ count ++; } } if(count % 2){ return 0; }else{ return 1; } } mt6816_t read_encoder(){ mt6816_t ret; uint16_t SPITxData; uint16_t SPIRxData; ret.error = MT6816_OK; //Generate an NSS HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_RESET); //falling edge signals start of transmission SPITxData = 0x8300; SPIRxData = 0xa5a5; SpiStatus = HAL_SPI_TransmitReceive(&hspi1, (uint8_t*)&SPITxData, (uint8_t*)&SPIRxData, 1, 2); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_SET);//Rising edge ends the transmission ret.angle = 0; ret.angle = (SPIRxData&0x00FF)<<8; HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_RESET); SPITxData = 0x8400; SPIRxData = 0xa5a5; SpiStatus = HAL_SPI_TransmitReceive(&hspi1, (uint8_t*)&SPITxData, (uint8_t*)&SPIRxData, 1, 2); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_SET); ret.angle |= (SPIRxData & 0x00FF); ret.angle = ret.angle>>2; uint8_t parity_bit = SPIRxData & 0b1; ret.status = 0; if(has_even_parity(ret.angle) == parity_bit){ ret.angle = 0; ret.error = MT6816_ERROR; ret.status |= MT6816_PARITY_ERROR; } if(SPIRxData & 0b10){ //Check no magnet bit ret.angle = 0; ret.error = MT6816_ERROR; ret.status |= MT6816_NO_MAG; } return ret; } float utils_map(float x, float in_min, float in_max, float out_min, float out_max) { return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; } float utils_truncate_number(float number, float min, float max, float deadband) { if (number > max) { if(number > (max + deadband)){ return 0.0f; }else{ return max; } } else if (number < min) { if(number < (min - deadband)){ return 0.0f; }else{ return min; } } return number; } void task_encoder(void * argument){ //Init SPI to the encoder HAL_SPI_Init(&hspi1); //Init the PW in HAL_TIM_IC_Start(&htim2, TIM_CHANNEL_1); HAL_TIM_IC_Start(&htim2, TIM_CHANNEL_2); //Start differential analog output timer HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_1); //Set the output analog to 0V htim1.Instance->CCR1 = 0; //Start PWM out HAL_TIM_PWM_Start(&htim15, TIM_CHANNEL_1); htim15.Instance->CCR1 = 0; while(1){ bool error_state = false; if(axis_vars.use_spi){ axis_vars.mt6816 = read_encoder(); //Process the data axis_vars.ratioSPI = (float)axis_vars.mt6816.angle/16384.0f; if(axis_vars.mt6816.error){ error_state=true; } } if(axis_vars.use_pwm){ axis_vars.ratioPWM = (float)htim2.Instance->CCR2/(float)(htim2.Instance->CCR1); } if(axis_vars.check_pwm_vs_spi){ if (fabsf(axis_vars.ratioPWM-axis_vars.ratioSPI)>0.05){ error_state = true; } } bool kill = false; //Use second source to verify accuracy if(error_state){ //Check the PWM and the SPI agree and that the magnet present bit is low axis_vars.accumulated_errors++; if(axis_vars.encoder_error_limit && axis_vars.error_count >= axis_vars.encoder_error_limit){ kill = true; axis_vars.throttle_raw = 0.0f; htim1.Instance->CCR1 = 0; htim15.Instance->CCR1 = 0; }else{ axis_vars.error_count++; } } else { if(axis_vars.encoder_error_limit == 0){ axis_vars.error_count=0; } if(axis_vars.error_count){ axis_vars.error_count--; } if(axis_vars.error_count == 0){ htim1.Instance->CCR1 = (uint16_t)(axis_vars.ratioSPI*(float)htim1.Instance->ARR); htim15.Instance->CCR1 = (uint16_t)(2000.0f+2000.0f*axis_vars.ratioSPI); if(axis_vars.use_spi && axis_vars.use_pwm){ axis_vars.throttle_raw = axis_vars.ratioSPI; }else if(axis_vars.use_pwm == true && axis_vars.use_spi == false){ axis_vars.throttle_raw = axis_vars.ratioPWM; }else if(axis_vars.use_pwm == false && axis_vars.use_spi == true){ axis_vars.throttle_raw = axis_vars.ratioSPI; } }else{ kill = true; axis_vars.throttle_raw = 0.0f; htim1.Instance->CCR1 = 0; htim15.Instance->CCR1 = 0; } } float cal = axis_vars.throttle_raw - axis_vars.throttle_start; if(cal < 0.0f){ cal += 1.0f; } axis_vars.throttle_z_calib = cal; if(cal < axis_vars.throttle_threshold){ cal = 0.0f; } axis_vars.throttle_calibrated = utils_truncate_number(utils_map(cal, 0.0f, axis_vars.throttle_end, 0.0f, 1.0f), 0.0f, 1.0f, 0.1f); if(kill==false){ if (axis_vars.throttle_calibrated < axis_vars.throttle_offset) { axis_vars.throttle_mapped = utils_map(axis_vars.throttle_calibrated, 0.0f, axis_vars.throttle_offset, -1.0f, 0.0f); } else { axis_vars.throttle_mapped = utils_map(axis_vars.throttle_calibrated, axis_vars.throttle_offset, 1.0f, 0.0f, 1.0f); } }else{ axis_vars.throttle_mapped = 0.0f; } vTaskDelay(10); } } void TASK_CAN_telemetry_fast(TASK_CAN_handle * handle){ TASK_CAN_add_float(handle , CAN_ID_ADC1_2_REQ , CAN_BROADCAST, axis_vars.throttle_mapped , 0.0f , 0); } void TASK_CAN_telemetry_slow(TASK_CAN_handle * handle){ } void MESCinterface_init(TERMINAL_HANDLE * handle){ static bool is_init=false; if(is_init) return; is_init=true; populate_vars(); axis_vars.check_pwm_vs_spi = true; axis_vars.use_pwm = true; axis_vars.use_spi = true; axis_vars.error_count = 0; axis_vars.throttle_raw = 0.0f; axis_vars.throttle_start = 0.0f; axis_vars.throttle_end = 1.0f; axis_vars.throttle_offset = 0.0f; axis_vars.throttle_threshold = 0.1f; if(CMD_varLoad(&null_handle, 0, NULL) == TERM_CMD_EXIT_ERROR){ } TERM_addCommand(CMD_nodes, "nodes", "Node info", 0, &TERM_defaultList); TERM_addCommand(CMD_can_send, "can_send", "Send CAN message", 0, &TERM_defaultList); REGISTER_apps(&TERM_defaultList); xTaskCreate(task_encoder, "tskEncoder", 128, NULL, osPriorityAboveNormal, NULL); }