/*
**
******************************************************************************
* @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 "MESCinterface.h"
#include "main.h"
#include "Tasks/init.h"
#include "TTerm/Core/include/TTerm.h"
#include "Tasks/task_cli.h"
#include "Tasks/task_can.h"
#include "Tasks/task_overlay.h"
#include "Dash/MESCmotor_state.h"
#include
#include
#include
#include "TTerm/Core/include/TTerm_cwd.h"
#include "fatfs.h"
#include "tcp_serv.h"
#include "ssd1306.h"
#include "ssd1306_tests.h"
#include
const char TERM_startupText1[] = "\r\n";
const char TERM_startupText2[] = "\r\n[D][A][S][H] - Dashboard";
const char TERM_startupText3[] = "\r\n";
dash_data dash;
#define N_COLS 8
#define N_ROWS 1000
#ifdef STM32F407xx
__attribute__((section (".ccmram")))
#endif
float sample_data[N_COLS][N_ROWS];
#ifdef STM32F407xx
__attribute__((section (".ccmram")))
#endif
uint32_t n_rows;
void * TASK_CAN_allocate_node(TASK_CAN_handle * handle, TASK_CAN_node * node){
if(memcmp(node->short_name, "ESC", 3)==0){
node->type = NODE_TYPE_ESC;
node->data = pvPortMalloc(sizeof(esc_data));
((esc_data*)node->data)->node = node;
}
return NULL;
}
void * TASK_CAN_free_node(TASK_CAN_handle * handle, TASK_CAN_node * node){
vPortFree(node->data);
return NULL;
}
char fl_file_prefix[64] = "fl";
char sl_file_prefix[64] = "sl";
bool enable_display = false;
volatile bool enable_slow_log = false;
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(dash.id_speed , 1 , 254 , "id_speed" , "Obtain speed from ID" , VAR_ACCESS_RW , NULL , &TERM_varList);
TERM_addVar(dash.id_voltage , 1 , 254 , "id_voltage" , "Obtain bus voltage from ID" , VAR_ACCESS_RW , NULL , &TERM_varList);
TERM_addVar(fl_file_prefix , 0 , 0 , "fl_file" , "Fastloop file template" , VAR_ACCESS_RW , NULL , &TERM_varList);
TERM_addVar(sl_file_prefix , 0 , 0 , "sl_file" , "Slowloop file template" , VAR_ACCESS_RW , NULL , &TERM_varList);
TERM_addVar(enable_display , 0 , 1 , "ena_disp" , "Enable OLED" , VAR_ACCESS_RW , NULL , &TERM_varList);
TERM_addVar(enable_slow_log , 0 , 1 , "ena_log" , "Enable slow log" , VAR_ACCESS_RW , NULL , &TERM_varList);
}
typedef struct{
uint16_t rows;
uint16_t start;
esc_data * esc;
} save_arg;
volatile TaskHandle_t save_fastloop_handle;
void TASK_CAN_save_fastloop_data(void * argument){
save_arg * arg = argument;
TASK_CAN_node * node = arg->esc->node;
FIL out;
char timecode[90];
snprintf(timecode, sizeof(timecode), "%s_%u_%u.csv", fl_file_prefix, node->id, (uint32_t)xTaskGetTickCount());
FRESULT res = f_open(&out,timecode,FA_WRITE | FA_CREATE_ALWAYS);
if(res != FR_OK){
goto CLEANUP;
}
char buffer[200];
uint32_t len;
unsigned int written=0;
len = sprintf(buffer, "timestamp,Vbus,Iu,Iv,Iw,Vd,Vq,angle\r\n");
f_write(&out, buffer, len, &written);
for(uint8_t i=0;irows;i++){
len = snprintf(buffer, sizeof(buffer), "%f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.0f\r\n", sample_data[0][i], sample_data[1][i], sample_data[2][i], sample_data[3][i],
sample_data[4][i], sample_data[5][i], sample_data[6][i], sample_data[7][i]);
f_write(&out, buffer, len, &written);
}
f_close(&out);
CLEANUP:
vPortFree(arg);
save_fastloop_handle = NULL;
vTaskDelete(NULL);
vTaskDelay(portMAX_DELAY);
}
extern volatile TERMINAL_HANDLE * debug;
uint32_t count=0;
void TASK_CAN_packet_esc(esc_data * esc, uint32_t id, uint8_t* data, uint32_t len){
switch(id){
case CAN_ID_ADC1_2_REQ:
esc->adc1 = PACK_buf_to_float(data);
esc->adc2 = PACK_buf_to_float(data+4);
break;
case CAN_ID_SPEED:
esc->speed = PACK_buf_to_float(data);
break;
case CAN_ID_BUS_VOLT_CURR:
esc->bus_voltage = PACK_buf_to_float(data);
esc->bus_current = PACK_buf_to_float(data+4);
break;
case CAN_ID_TEMP_MOT_MOS1:
esc->temp_motor = PACK_buf_to_float(data);
esc->temp_mos1 = PACK_buf_to_float(data+4);
break;
case CAN_ID_TEMP_MOS2_MOS3:
esc->temp_mos2 = PACK_buf_to_float(data);
esc->temp_mos3 = PACK_buf_to_float(data+4);
break;
case CAN_ID_MOTOR_CURRENT:
esc->Iq = PACK_buf_to_float(data);
esc->Id = PACK_buf_to_float(data+4);
break;
case CAN_ID_MOTOR_VOLTAGE:
esc->Vq = PACK_buf_to_float(data);
esc->Vd = PACK_buf_to_float(data+4);
break;
case CAN_ID_STATUS:
esc->status = PACK_buf_to_u32(data);
break;
case CAN_ID_FOC_HYPER:
esc->cycles_fastloop = PACK_buf_to_u32(data);
esc->cycles_hyperloop = PACK_buf_to_u32(data+4);
break;
case CAN_ID_SAMPLE:{
uint16_t row = PACK_buf_to_u16(data);
uint8_t col = PACK_buf_to_u8(data+2);
uint8_t flags = PACK_buf_to_u8(data+3);
float val = PACK_buf_to_float(data+4);
if(flags==CAN_SAMPLE_FLAG_START){
count=0;
}else if(flags==CAN_SAMPLE_FLAG_END){
if(strcmp(fl_file_prefix, "") != 0){ //Only save if a filename is set
save_arg * arg = pvPortMalloc(sizeof(save_arg));
arg->start = 0;
arg->rows = n_rows;
arg->esc = esc;
if(arg){
xTaskCreate(TASK_CAN_save_fastloop_data, "task_save", 512, arg, osPriorityNormal, &save_fastloop_handle);
}
}
//Activate save function
count=0;
}else if(row < N_ROWS && col < N_COLS){
sample_data[col][row] = val;
count++;
if(row > n_rows){
n_rows = row + 1;
}
}
}
}
}
void TASK_CAN_packet_cb(TASK_CAN_handle * handle, uint32_t id, uint8_t sender, uint8_t receiver, uint8_t* data, uint32_t len){
TASK_CAN_node * node = TASK_CAN_get_node_from_id(sender);
if(node != NULL && node->type == NODE_TYPE_ESC && node->data != NULL){
esc_data * esc = node->data;
TASK_CAN_packet_esc(esc, id, data, len); //Process ESC data
return;
}
switch(id){
default:
break;
}
}
uint8_t CMD_sample(TERMINAL_HANDLE * handle, uint8_t argCount, char ** args){
uint32_t id=0;
for(int i=0;itype == NODE_TYPE_ESC && node->data != NULL){
uint32_t timeout = 1000;
while(save_fastloop_handle != NULL){
timeout--;
if(timeout==0){
ttprintf("Cannot free save task\r\n");
return TERM_CMD_EXIT_SUCCESS;
}
vTaskDelay(1);
}
TASK_CAN_add_uint32(&can1, CAN_ID_SAMPLE_SEND, id, 0, 0, 100);
timeout = 2000;
ttprintf("Saving fastloop data from ESC %u\r\n", id);
while(save_fastloop_handle == NULL && timeout){
timeout--;
vTaskDelay(1);
}
if(timeout==0){
ttprintf("Timeout on ESC %u\r\n", id);
continue;
}
}
}
}
}
return TERM_CMD_EXIT_SUCCESS;
}
void TASK_SLOW_LOG(void * argument){
char timecode[79];
FRESULT res;
uint32_t count=0;
uint32_t filesize=0;
FIL out;
do{
count++;
snprintf(timecode, sizeof(timecode), "%s_%u.csv", sl_file_prefix, count);
res = f_open(&out,timecode,FA_READ);
f_close(&out);
vTaskDelay(5);
}while(res ==FR_OK);
res = f_open(&out,timecode,FA_WRITE | FA_CREATE_ALWAYS);
if(res != FR_OK){
goto CLEANUP; //Terminate the task
}
char buffer[512];
uint32_t len;
unsigned int written=0;
len = sprintf(buffer, "id,ticks,speed,adc1,adc2,bus_voltage,bus_current,motor_current,temp_motor,temp_mos1,temp_mos2,temp_mos3,status,Iq,Id,Vq,Vd,cycles_fl,cycles_hl\r\n");
filesize +=len ;
f_write(&out, buffer, len, &written);
f_close(&out);
while(1){
for(uint32_t id=1;idtype == NODE_TYPE_ESC && node->data != NULL){
esc_data * esc = node->data;
len = sprintf(buffer, "%u,%u,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%u,%.3f,%.3f,%.3f,%.3f,%u,%u\r\n", id, xTaskGetTickCount() ,esc->speed, esc->adc1,
esc->adc2, esc->bus_voltage, esc->bus_current, esc->motor_current,
esc->temp_motor, esc->temp_mos1, esc->temp_mos2, esc->temp_mos3, esc->status, esc->Iq, esc->Id, esc->Vq, esc->Vd, esc->cycles_fastloop, esc->cycles_hyperloop);
res = f_open(&out,timecode,FA_WRITE | FA_OPEN_APPEND);
if(res == FR_OK){
f_write(&out, buffer, len, &written);
filesize += len;
f_close(&out);
}
}
}
vTaskDelay(100);
if(enable_slow_log==false){
goto CLEANUP; //Terminate the task
}
if(filesize > 1024 * 1024 * 200){ //200 Megabyte
count++;
snprintf(timecode, sizeof(timecode), "%s_%u.csv", sl_file_prefix, count);
res = f_open(&out,timecode,FA_WRITE | FA_CREATE_ALWAYS);
if(res == FR_OK){
filesize=0;
len = sprintf(buffer, "id,ticks,speed,adc1,adc2,bus_voltage,bus_current,motor_current,temp_motor,temp_mos1,temp_mos2,temp_mos3,status,Iq,Id,Vq,Vd,cycles_fl,cycles_hl\r\n");
filesize +=len ;
f_write(&out, buffer, len, &written);
f_close(&out);
}
}
}
CLEANUP:
vTaskDelete(NULL);
vTaskDelay(portMAX_DELAY);
}
float adc[4];
void task_ssd(void * argument){
ssd1306_Init();
char buffer[64];
TASK_CAN_node * node = NULL;
esc_data * esc;
float val;
while(1){
uint32_t y = 0;
ssd1306_Fill(Black);
ssd1306_SetCursor(2, y);
node = TASK_CAN_get_node_from_id(dash.id_speed);
if(node!=NULL && node->data!=NULL){
esc = node->data;
val = esc->speed;
}else{
val = NAN;
}
snprintf(buffer,sizeof(buffer),"Speed: %2.2f", val);
ssd1306_WriteString(buffer, Font_11x18, White);
y += 19;
ssd1306_SetCursor(2, y);
node = TASK_CAN_get_node_from_id(dash.id_voltage);
if(node!=NULL && node->data!=NULL){
esc = node->data;
val = esc->motor_current;
}else{
val = NAN;
}
snprintf(buffer,sizeof(buffer),"Curr : %2.2f", val);
ssd1306_WriteString(buffer, Font_11x18, White);
y += 19;
ssd1306_SetCursor(2, y);
node = TASK_CAN_get_node_from_id(dash.id_voltage);
if(node!=NULL && node->data!=NULL){
esc = node->data;
val = esc->bus_voltage;
}else{
val = NAN;
}
snprintf(buffer,sizeof(buffer),"Volt : %2.2f", val);
ssd1306_WriteString(buffer, Font_11x18, White);
y += 19;
ssd1306_UpdateScreen();
vTaskDelay(20);
}
}
extern ADC_HandleTypeDef hadc1;
void task_analog(void * argument){
while(1){
adc[0] = (float)ADC1->JDR1 / 4095.0f;
adc[1] = (float)ADC1->JDR2 / 4095.0f;
adc[2] = (float)ADC1->JDR3 / 4095.0f;
adc[3] = (float)ADC1->JDR4 / 4095.0f;
HAL_ADCEx_InjectedStart(&hadc1);
vTaskDelay(10);
}
}
void TASK_CAN_telemetry_fast(TASK_CAN_handle * handle){
TASK_CAN_add_float(handle , CAN_ID_ADC1_2_REQ , CAN_BROADCAST, adc[0] , adc[1] , 0);
}
void TASK_CAN_telemetry_slow(TASK_CAN_handle * handle){
}
uint8_t CMD_esc_info(TERMINAL_HANDLE * handle, uint8_t argCount, char ** args){
ttprintf("ESC detail info:\r\n");
for(uint32_t i=0;iid, node->short_name);
if(node->data && node->type == NODE_TYPE_ESC){
esc_data * esc = node->data;
ttprintf("\tSpeed: %f\r\n", esc->speed, esc->motor_current);
ttprintf("\tIq: %f \tId: %f \tVq: %f Vd: %f\t\r\n", esc->Iq, esc->Id, esc->Vq, esc->Vd);
ttprintf("\tADC1: %f \tADC2: %f\r\n", esc->adc1, esc->adc2);
ttprintf("\tTemp Motor: %f \tTemp Mos1: %f \tTemp Mos2: %f \tTemp Mos3: %f\r\n", esc->temp_motor, esc->temp_mos1, esc->temp_mos2, esc->temp_mos3);
ttprintf("\tBus voltage: %f \tBus current: %f\r\n", esc->bus_voltage, esc->bus_current);
ttprintf("\tCycles fastloop: %u \tCycles hyperloop: %u\r\n", esc->cycles_fastloop, esc->cycles_hyperloop);
ttprintf("\tMESC status: ");
switch(esc->status){
case MOTOR_STATE_INITIALISING:
ttprintf("INITIALISING");
break;
case MOTOR_STATE_DETECTING:
ttprintf("DETECTING");
break;
case MOTOR_STATE_MEASURING:
ttprintf("MEASURING");
break;
case MOTOR_STATE_ALIGN:
ttprintf("ALIGN");
break;
case MOTOR_STATE_OPEN_LOOP_STARTUP:
ttprintf("OL STARTUP");
break;
case MOTOR_STATE_OPEN_LOOP_TRANSITION:
ttprintf("OL TRANSITION");
break;
case MOTOR_STATE_TRACKING:
ttprintf("TRACKING");
break;
case MOTOR_STATE_RUN:
ttprintf("RUN");
break;
case MOTOR_STATE_GET_KV:
ttprintf("GET KV");
break;
case MOTOR_STATE_TEST:
ttprintf("TEST");
break;
case MOTOR_STATE_ERROR:
ttprintf("ERROR");
break;
case MOTOR_STATE_RECOVERING:
ttprintf("RECOVERING");
break;
case MOTOR_STATE_IDLE:
ttprintf("IDLE");
break;
case MOTOR_STATE_SLAMBRAKE:
ttprintf("SLAMBRAKE");
break;
}
ttprintf("\r\n");
}
}
}
return TERM_CMD_EXIT_SUCCESS;
}
void MESCinterface_init(TERMINAL_HANDLE * handle){
static bool is_init=false;
if(is_init) return;
is_init=true;
n_rows = 0;
populate_vars();
if(CMD_varLoad(&null_handle, 0, NULL) == TERM_CMD_EXIT_ERROR){
}
TERM_addCommand(CMD_ifconfig, "ifconfig", "ifconfig", 0, &TERM_defaultList);
TERM_addCommand(CMD_nodes, "nodes", "Node info", 0, &TERM_defaultList);
TERM_addCommand(CMD_can_send, "can_send", "Send CAN message", 0, &TERM_defaultList);
TERM_addCommand(CMD_esc_info, "esc", "ESC info", 0, &TERM_defaultList);
TERM_addCommand(CMD_sample, "sample", "Sample now", 0, &TERM_defaultList);
REGISTER_apps(&TERM_defaultList);
tcp_serv_init();
vTaskDelay(200);
f_mount(&SDFatFS, (TCHAR const*)SDPath, 1);
vTaskDelay(100);
if(enable_display){
xTaskCreate(task_ssd, "tskSSD", 1024, NULL, osPriorityNormal, NULL);
}
xTaskCreate(task_analog, "tskAnalog", 128, NULL, osPriorityAboveNormal, NULL);
if(enable_slow_log){
xTaskCreate(TASK_SLOW_LOG, "tskSLOW", 1024, NULL, osPriorityNormal, NULL);
}
}