/** * \file * \brief Module to drive the robot. * \author Erich Styger, erich.styger@hslu.ch * \license SPDX-License-Identifier: BSD-3-Clause * This module allows to drive the robot and to perform turns. */ #include "platform.h" #if PL_CONFIG_USE_DRIVE #include "Drive.h" #include "McuRTOS.h" #include "FreeRTOS.h" #include "McuUtility.h" #if PL_CONFIG_USE_TACHO #include "Tacho.h" #endif #include "Pid.h" #include "Motor.h" #if PL_CONFIG_USE_SHELL #include "McuShell.h" #endif #if configUSE_TRACE_HOOKS #include "McuPercepio.h" #endif #if PL_CONFIG_USE_QUADRATURE #include "QuadCounter.h" #endif #include "Shell.h" #include "McuWait.h" #define PRINT_DRIVE_INFO 0 /* if we print debug info */ struct { DRV_Mode mode; struct { int32_t left, right; } speed; #if PL_CONFIG_USE_QUADRATURE struct { int32_t left, right; } pos; #endif } DRV_Status; typedef enum { DRV_SET_MODE, DRV_SET_SPEED, #if PL_CONFIG_USE_POS_PID DRV_SET_POS, #endif } DRV_Commands; typedef struct { DRV_Commands cmd; union { DRV_Mode mode; /* DRV_SET_MODE */ struct { int32_t left, right; } speed; /* DRV_SET_SPEED */ #if PL_CONFIG_USE_POS_PID struct { int32_t left, right; } pos; /* DRV_SET_POS */ #endif } u; } DRV_Command; #define QUEUE_LENGTH 4 /* number of items in queue, that's my buffer size */ #define QUEUE_ITEM_SIZE sizeof(DRV_Command) /* each item is a single drive command */ static QueueHandle_t DRV_Queue; bool DRV_IsStopped(void) { #if PL_CONFIG_USE_TACHO QuadCounter_QuadCntrType leftPos; QuadCounter_QuadCntrType rightPos; #endif if (uxQueueMessagesWaiting(DRV_Queue)>0) { return FALSE; /* still messages in command queue, so there is something pending */ } #if PL_CONFIG_USE_TACHO /* do *not* use/calculate speed: too slow! Use position encoder instead */ leftPos = QuadCounter_GetPosLeft(); rightPos = QuadCounter_GetPosRight(); if (DRV_Status.mode==DRV_MODE_POS) { #if PL_CONFIG_HAS_HIGH_RES_ENCODER if (DRV_Status.pos.left!=(int32_t)leftPos) { return FALSE; } if (DRV_Status.pos.right!=(int32_t)rightPos) { return FALSE; } #else if (DRV_Status.pos.left!=(int32_t)leftPos) { return FALSE; } if (DRV_Status.pos.right!=(int32_t)rightPos) { return FALSE; } #endif return TRUE; } if (DRV_Status.mode==DRV_MODE_STOP) { return TRUE; } else { /* ???? what to do otherwise ???? */ return FALSE; } #else #warning "NYI" return TRUE; #endif } uint8_t DRV_Stop(int32_t timeoutMs) { (void)DRV_SetMode(DRV_MODE_STOP); /* stop it */ do { if (DRV_IsStopped()) { break; } McuWait_WaitOSms(5); timeoutMs -= 5; } while (timeoutMs>0); if (timeoutMs<0) { return ERR_BUSY; /* timeout */ } return ERR_OK; } bool DRV_IsDrivingBackward(void) { return DRV_Status.mode==DRV_MODE_SPEED && DRV_Status.speed.left<0 && DRV_Status.speed.right<0; } #if PL_CONFIG_USE_POS_PID static bool match(int16_t pos, int16_t target) { #if PL_CONFIG_HAS_HIGH_RES_ENCODER #define MATCH_MARGIN 50 #else #define MATCH_MARGIN 0 #endif #if MATCH_MARGIN>0 return (pos>=target-MATCH_MARGIN && pos<=target+MATCH_MARGIN); #else return pos==target; #endif } #endif bool DRV_HasTurned(void) { #if PL_CONFIG_USE_POS_PID int16_t pos; if (uxQueueMessagesWaiting(DRV_Queue)>0) { return FALSE; /* still messages in command queue, so there is something pending */ } if (DRV_Status.mode==DRV_MODE_POS) { #if PL_CONFIG_USE_TACHO #if PL_CONFIG_HAS_HIGH_RES_ENCODER #define DRV_TURN_SPEED_LOW 50 #else #define DRV_TURN_SPEED_LOW 5 #endif int32_t speedL, speedR; speedL = TACHO_GetSpeed(TRUE); speedR = TACHO_GetSpeed(FALSE); if (speedL>-DRV_TURN_SPEED_LOW && speedL-DRV_TURN_SPEED_LOW && speedRstdOut); McuShell_SendHelpStr((unsigned char*)" help|status", (unsigned char*)"Shows drive help or status\r\n", io->stdOut); #if PL_CONFIG_USE_QUADRATURE McuShell_SendHelpStr((unsigned char*)" mode ", (unsigned char*)"Set driving mode (none|stop|speed|pos)\r\n", io->stdOut); #else McuShell_SendHelpStr((unsigned char*)" mode ", (unsigned char*)"Set driving mode (none|stop|speed)\r\n", io->stdOut); #endif McuShell_SendHelpStr((unsigned char*)" speed ", (unsigned char*)"Move left and right motors with given speed\r\n", io->stdOut); #if PL_CONFIG_USE_POS_PID McuShell_SendHelpStr((unsigned char*)" pos ", (unsigned char*)"Move left and right wheels to given position\r\n", io->stdOut); McuShell_SendHelpStr((unsigned char*)" pos reset", (unsigned char*)"Reset drive and wheel position\r\n", io->stdOut); #endif } static void DRV_PrintStatus(const McuShell_StdIOType *io) { uint8_t buf[24]; McuShell_SendStatusStr((unsigned char*)"drive", (unsigned char*)"Drive status\r\n", io->stdOut); McuShell_SendStatusStr((unsigned char*)" mode", DRV_GetModeStr(DRV_Status.mode), io->stdOut); McuShell_SendStr((unsigned char*)"\r\n", io->stdOut); McuUtility_Num32sToStr(buf, sizeof(buf), DRV_Status.speed.left); #if PL_CONFIG_USE_TACHO McuUtility_strcat(buf, sizeof(buf), (unsigned char*)" steps/sec\r\n"); #else McuUtility_strcat(buf, sizeof(buf), (unsigned char*)" % PWM\r\n"); #endif McuShell_SendStatusStr((unsigned char*)" speed left", buf, io->stdOut); McuUtility_Num32sToStr(buf, sizeof(buf), DRV_Status.speed.right); #if PL_CONFIG_USE_TACHO McuUtility_strcat(buf, sizeof(buf), (unsigned char*)" steps/sec\r\n"); #else McuUtility_strcat(buf, sizeof(buf), (unsigned char*)" % PWM\r\n"); #endif McuShell_SendStatusStr((unsigned char*)" speed right", buf, io->stdOut); #if PL_CONFIG_USE_POS_PID McuUtility_Num32sToStr(buf, sizeof(buf), DRV_Status.pos.left); McuUtility_strcat(buf, sizeof(buf), (unsigned char*)" (curr: "); McuUtility_strcatNum32s(buf, sizeof(buf), (int32_t)QuadCounter_GetPosLeft()); McuUtility_strcat(buf, sizeof(buf), (unsigned char*)")\r\n"); McuShell_SendStatusStr((unsigned char*)" pos left", buf, io->stdOut); McuUtility_Num32sToStr(buf, sizeof(buf), DRV_Status.pos.right); McuUtility_strcat(buf, sizeof(buf), (unsigned char*)" (curr: "); McuUtility_strcatNum32s(buf, sizeof(buf), (int32_t)QuadCounter_GetPosRight()); McuUtility_strcat(buf, sizeof(buf), (unsigned char*)")\r\n"); McuShell_SendStatusStr((unsigned char*)" pos right", buf, io->stdOut); #endif } uint8_t DRV_ParseCommand(const unsigned char *cmd, bool *handled, const McuShell_StdIOType *io) { uint8_t res = ERR_OK; const unsigned char *p; int32_t val1, val2; if (McuUtility_strcmp((char*)cmd, (char*)McuShell_CMD_HELP)==0 || McuUtility_strcmp((char*)cmd, (char*)"drive help")==0) { DRV_PrintHelp(io); *handled = TRUE; } else if (McuUtility_strcmp((char*)cmd, (char*)McuShell_CMD_STATUS)==0 || McuUtility_strcmp((char*)cmd, (char*)"drive status")==0) { DRV_PrintStatus(io); *handled = TRUE; } else if (McuUtility_strncmp((char*)cmd, (char*)"drive speed ", sizeof("drive speed ")-1)==0) { p = cmd+sizeof("drive speed"); if (McuUtility_xatoi(&p, &val1)==ERR_OK) { if (McuUtility_xatoi(&p, &val2)==ERR_OK) { if (DRV_SetSpeed(val1, val2)!=ERR_OK) { McuShell_SendStr((unsigned char*)"failed\r\n", io->stdErr); } *handled = TRUE; } else { McuShell_SendStr((unsigned char*)"failed\r\n", io->stdErr); } } else { McuShell_SendStr((unsigned char*)"Wrong argument(s)\r\n", io->stdErr); res = ERR_FAILED; } #if PL_CONFIG_USE_POS_PID } else if (McuUtility_strncmp((char*)cmd, (char*)"drive pos reset", sizeof("drive pos reset")-1)==0) { QuadCounter_SetPosLeft(0); QuadCounter_SetPosRight(0); if (DRV_SetPos(0, 0)!=ERR_OK) { McuShell_SendStr((unsigned char*)"failed\r\n", io->stdErr); } *handled = TRUE; } else if (McuUtility_strncmp((char*)cmd, (char*)"drive pos ", sizeof("drive pos ")-1)==0) { p = cmd+sizeof("drive pos"); if (McuUtility_xatoi(&p, &val1)==ERR_OK) { if (McuUtility_xatoi(&p, &val2)==ERR_OK) { if (DRV_SetPos(val1, val2)!=ERR_OK) { McuShell_SendStr((unsigned char*)"failed\r\n", io->stdErr); } *handled = TRUE; } else { McuShell_SendStr((unsigned char*)"failed\r\n", io->stdErr); } } else { McuShell_SendStr((unsigned char*)"Wrong argument(s)\r\n", io->stdErr); res = ERR_FAILED; } #endif } else if (McuUtility_strncmp((char*)cmd, (char*)"drive mode ", sizeof("drive mode ")-1)==0) { p = cmd+sizeof("drive mode"); if (McuUtility_strcmp((char*)p, (char*)"none")==0) { if (DRV_SetMode(DRV_MODE_NONE)!=ERR_OK) { res = ERR_FAILED; } } else if (McuUtility_strcmp((char*)p, (char*)"stop")==0) { if (DRV_SetMode(DRV_MODE_STOP)!=ERR_OK) { res = ERR_FAILED; } } else if (McuUtility_strcmp((char*)p, (char*)"speed")==0) { if (DRV_SetMode(DRV_MODE_SPEED)!=ERR_OK) { res = ERR_FAILED; } #if PL_CONFIG_USE_QUADRATURE } else if (McuUtility_strcmp((char*)p, (char*)"pos")==0) { if (DRV_SetMode(DRV_MODE_POS)!=ERR_OK) { res = ERR_FAILED; } #endif } else { res = ERR_FAILED; } if (res!=ERR_OK) { McuShell_SendStr((unsigned char*)"failed\r\n", io->stdErr); } *handled = TRUE; } return res; } #endif /* PL_CONFIG_USE_SHELL */ static uint8_t GetCmd(void) { DRV_Command cmd; portBASE_TYPE res; res = xQueueReceive(DRV_Queue, &cmd, 0); if (res==errQUEUE_EMPTY) { return ERR_RXEMPTY; /* no command */ } /* process command */ taskENTER_CRITICAL(); if (cmd.cmd==DRV_SET_MODE) { #if PL_CONFIG_USE_PID PID_Start(); /* reset PID, especially integral counters */ #endif DRV_Status.mode = cmd.u.mode; } else if (cmd.cmd==DRV_SET_SPEED) { DRV_Status.speed.left = cmd.u.speed.left; DRV_Status.speed.right = cmd.u.speed.right; #if PL_CONFIG_USE_POS_PID } else if (cmd.cmd==DRV_SET_POS) { DRV_Status.pos.left = cmd.u.pos.left; DRV_Status.pos.right = cmd.u.pos.right; #endif } taskEXIT_CRITICAL(); #if PRINT_DRIVE_INFO { uint8_t buf[32]; if (cmd.cmd==DRV_SET_MODE) { McuUtility_strcpy(buf, sizeof(buf), "SETMODE: "); McuUtility_strcat(buf, sizeof(buf), DRV_GetModeStr(DRV_Status.mode)); } else if (cmd.cmd==DRV_SET_SPEED) { McuUtility_strcpy(buf, sizeof(buf), "SETSPEED: "); McuUtility_strcatNum32s(buf, sizeof(buf), DRV_Status.speed.left); McuUtility_strcat(buf, sizeof(buf), ", "); McuUtility_strcatNum32s(buf, sizeof(buf), DRV_Status.speed.right); } else if (cmd.cmd==DRV_SET_POS) { McuUtility_strcpy(buf, sizeof(buf), "SETPOS: "); McuUtility_strcatNum32s(buf, sizeof(buf), DRV_Status.pos.left); McuUtility_strcat(buf, sizeof(buf), ", "); McuUtility_strcatNum32s(buf, sizeof(buf), DRV_Status.pos.right); } else { McuUtility_strcpy(buf, sizeof(buf), "ERROR!"); } McuUtility_strcat(buf, sizeof(buf), "\r\n"); SHELL_SendString(buf); } #endif return ERR_OK; } static void DriveTask(void *pvParameters) { TickType_t xLastWakeTime; #if !PL_CONFIG_USE_TACHO DRV_Mode prevMode; #endif (void)pvParameters; xLastWakeTime = xTaskGetTickCount(); #if !PL_CONFIG_USE_TACHO prevMode = DRV_MODE_NONE; #endif for(;;) { while (GetCmd()==ERR_OK) { /* returns ERR_RXEMPTY if queue is empty */ /* process incoming commands */ } #if PL_CONFIG_USE_TACHO TACHO_CalcSpeed(); #endif if (DRV_Status.mode==DRV_MODE_SPEED) { #if PL_CONFIG_USE_SPEED_PID PID_Speed(TACHO_GetSpeed(TRUE), DRV_Status.speed.left, TRUE); PID_Speed(TACHO_GetSpeed(FALSE), DRV_Status.speed.right, FALSE); #else { MOT_SpeedPercent speedL, speedR; if (DRV_Status.speed.left<-100) { speedL = -60; /* limit to avoid battery drop! */ } else if (DRV_Status.speed.left>100) { speedL = 60; /* limit to avoid battery drop! */ } else { speedL = DRV_Status.speed.left; } if (DRV_Status.speed.right<-100) { speedR = -60; } else if (DRV_Status.speed.right>100) { speedR = 60; } else { speedR = DRV_Status.speed.right; } MOT_SetSpeedPercent(MOT_GetMotorHandle(MOT_MOTOR_LEFT), speedL); MOT_SetSpeedPercent(MOT_GetMotorHandle(MOT_MOTOR_RIGHT), speedR); } #endif } else if (DRV_Status.mode==DRV_MODE_STOP) { #if PL_CONFIG_USE_SPEED_PID PID_Speed(TACHO_GetSpeed(TRUE), 0, TRUE); PID_Speed(TACHO_GetSpeed(FALSE), 0, FALSE); #elif !PL_CONFIG_USE_TACHO if (prevMode!=DRV_MODE_STOP) { /* stop motors */ MOT_SetSpeedPercent(MOT_GetMotorHandle(MOT_MOTOR_LEFT), 0); MOT_SetSpeedPercent(MOT_GetMotorHandle(MOT_MOTOR_RIGHT), 0); } #endif #if PL_CONFIG_USE_POS_PID } else if (DRV_Status.mode==DRV_MODE_POS) { PID_Pos(QuadCounter_GetPosLeft(), DRV_Status.pos.left, TRUE); PID_Pos(QuadCounter_GetPosRight(), DRV_Status.pos.right, FALSE); #endif } else if (DRV_Status.mode==DRV_MODE_NONE) { /* do nothing */ } #if !PL_CONFIG_USE_TACHO prevMode = DRV_Status.mode; #endif vTaskDelayUntil(&xLastWakeTime, 5/portTICK_PERIOD_MS); } /* for */ } void DRV_Deinit(void) { vQueueDelete(DRV_Queue); } void DRV_Init(void) { #if PL_CONFIG_USE_REMOTE DRV_Status.mode = DRV_MODE_SPEED; #else DRV_Status.mode = DRV_MODE_NONE; #endif DRV_Status.speed.left = 0; DRV_Status.speed.right = 0; #if PL_CONFIG_USE_QUADRATURE DRV_Status.pos.left = 0; DRV_Status.pos.right = 0; #endif DRV_Queue = xQueueCreate(QUEUE_LENGTH, QUEUE_ITEM_SIZE); if (DRV_Queue==NULL) { for(;;){} /* out of memory? */ } vQueueAddToRegistry(DRV_Queue, "Drive"); #if configUSE_TRACE_HOOKS PTRC1_vTraceSetQueueName(DRV_Queue, "Drive"); #endif if (xTaskCreate(DriveTask, "Drive", 500/sizeof(StackType_t), NULL, tskIDLE_PRIORITY+3, NULL) != pdPASS) { for(;;){} /* error */ } } #endif /* PL_CONFIG_USE_DRIVE */