/** * \file * \brief Implements line following of the robot * \author Erich Styger, erich.styger@hslu.ch * \license SPDX-License-Identifier: BSD-3-Clause * This is the implementation of the line following. */ #include "platform.h" #if PL_CONFIG_APP_LINE_FOLLOWING || PL_CONFIG_APP_LINE_MAZE #include "LineFollow.h" #include "McuRTOS.h" #include "McuShell.h" #include "Shell.h" #include "Motor.h" #include "Reflectance.h" #include "McuWait.h" #include "McuUtility.h" #include "Pid.h" #include "Turn.h" #include "Maze.h" #include "Shell.h" #if PL_CONFIG_USE_BUZZER #include "Buzzer.h" #endif #if PL_CONFIG_USE_DRIVE #include "Drive.h" #endif #define LINE_FOLLOW_FW (1) /* test setting to do forward line following */ #define LINE_DO_DEBUG_WAIT (0) /* if set to one, it will add some debug wait to stop the engines */ #if LINE_DO_DEBUG_WAIT #define LINE_DO_DEBUG_WAIT_MS (500) /* waiting time in milliseconds */ #endif #if LINE_DO_DEBUG_WAIT /* debug mode */ static void DebugStopAndWait(void) { static volatile bool waitHere = FALSE; TURN_Turn(TURN_STOP, NULL); vTaskDelay(LINE_DO_DEBUG_WAIT_MS); waitHere = TRUE; } #endif #define PL_TURN_ON_FINISH (0) /* temporary only */ typedef enum { LINE_SPEED_LOW, LINE_SPEED_MEDIUM, LINE_SPEED_HIGH } LINE_SpeedMode; static LINE_SpeedMode lineFollowSpeed = LINE_SPEED_LOW; /* set value in LineTask()! */ typedef enum { STATE_IDLE, /* idle, not doing anything */ STATE_FOLLOW_SEGMENT, /* line following segment, going forward */ #if PL_CONFIG_APP_LINE_MAZE STATE_TURN, /* reached an intersection, turning around */ STATE_FINISHED, /* reached finish area */ #endif STATE_STOP /* stop the engines */ } StateType; /* task notification bits */ #define LF_START_FOLLOWING (1<<0) /* start line following */ #define LF_STOP_FOLLOWING (1<<1) /* stop line following */ static TaskHandle_t LFTaskHandle; static volatile StateType LF_currState = STATE_IDLE; #if PL_CONFIG_APP_LINE_MAZE static uint8_t LF_solvedIdx = 0; /* index to iterate through the solution, zero is the solution start index */ #endif void LF_StartFollowing(void) { (void)xTaskNotify(LFTaskHandle, LF_START_FOLLOWING, eSetBits); } void LF_StopFollowing(void) { (void)xTaskNotify(LFTaskHandle, LF_STOP_FOLLOWING, eSetBits); } void LF_StartStopFollowing(void) { if (LF_IsFollowing()) { (void)xTaskNotify(LFTaskHandle, LF_STOP_FOLLOWING, eSetBits); } else { (void)xTaskNotify(LFTaskHandle, LF_START_FOLLOWING, eSetBits); } } /*! * \brief follows a line segment. * \return Returns TRUE if still on line segment */ bool LF_FollowSegment(REF_LineKindMode mode, bool forward) { uint16_t currLine; bool onLine; REF_LineKind currLineKind; REF_GetLineValue(&onLine); currLineKind = REF_GetLineKind(mode); if (currLineKind!=REF_LINE_STRAIGHT) { SHELL_SendString((unsigned char*)"LF_FollowSegment: "); SHELL_SendString(REF_LineKindStr(currLineKind)); SHELL_SendString((unsigned char*)"\r\n"); } #if PL_CONFIG_APP_LINE_FOLLOWING if (currLineKind==REF_LINE_STRAIGHT || currLineKind!=REF_LINE_NONE) { /* as long we have something: follow line */ #elif PL_CONFIG_APP_LINE_MAZE if (currLineKind==REF_LINE_STRAIGHT) { /* as long we have a line: follow line */ #endif currLine = REF_GetLineValue(&onLine); PID_Line(currLine, REF_MIDDLE_LINE_VALUE, REF_LineWidth(), forward); /* continue move along the line */ return TRUE; } else { /* stop motors */ SHELL_SendString((unsigned char*)"LF_FollowSegment stop: "); SHELL_SendString(REF_LineKindStr(currLineKind)); SHELL_SendString((unsigned char*)"\r\n"); MOT_SetSpeedPercent(MOT_GetMotorHandle(MOT_MOTOR_LEFT), 0); MOT_SetSpeedPercent(MOT_GetMotorHandle(MOT_MOTOR_RIGHT), 0); vTaskDelay(pdMS_TO_TICKS(20)); /* give time for PWM to change */ return FALSE; /* intersection/change of direction or not on line any more */ } } /*! * \brief Move from outside onto a line/segment to follow it. * \return Returns TRUE if still on line segment */ bool LF_MoveOnSegment(bool turningLeft) { uint16_t currLine, targetLine; int tmp; bool onLine; REF_LineKind currLineKind; uint16_t lineWidth; currLine = REF_GetLineValue(&onLine); currLineKind = REF_GetLineKind(REF_LINE_KIND_MODE_ALL); lineWidth = REF_LineWidth(); if (lineWidth>2000) { if (turningLeft) { tmp = REF_MIDDLE_LINE_VALUE+lineWidth; } else { tmp = REF_MIDDLE_LINE_VALUE-lineWidth; } if (tmp<0) { tmp = 0; } if (tmp>REF_MAX_LINE_VALUE) { tmp = REF_MAX_LINE_VALUE; } targetLine = tmp; } else { targetLine = REF_MIDDLE_LINE_VALUE; } if (currLineKind==REF_LINE_STRAIGHT || currLineKind==REF_LINE_LEFT || currLineKind==REF_LINE_RIGHT || currLineKind==REF_LINE_FULL) { PID_Line(currLine, targetLine, REF_LineWidth(), TRUE); /* move along the line */ return TRUE; } else { return FALSE; /* intersection/change of direction or not on line any more */ } } bool LF_FollowSegmentLinePos(REF_LineKindMode mode, uint16_t setLinePos) { uint16_t currLine; bool onLine; REF_LineKind currLineKind; currLine = REF_GetLineValue(&onLine); currLineKind = REF_GetLineKind(mode); if (currLineKind==REF_LINE_STRAIGHT) { PID_Line(currLine, setLinePos, REF_LineWidth(), TRUE); /* move along the line */ return TRUE; } else { return FALSE; /* intersection/change of direction or not on line any more */ } } static void StateMachine(void) { for(;;) { switch (LF_currState) { case STATE_IDLE: break; case STATE_FOLLOW_SEGMENT: #if PL_CONFIG_APP_LINE_MAZE if (!LF_FollowSegment(REF_LINE_KIND_MODE_MAZE, LINE_FOLLOW_FW)) { TURN_Turn(TURN_STOP, NULL); #if LINE_DO_DEBUG_WAIT /* debug mode */ DebugStopAndWait(); #endif LF_currState = STATE_TURN; /* make turn */ continue; /* process next step immediately */ } #else { REF_LineKind currLineKind; if (!LF_FollowSegment(REF_LINE_KIND_MODE_LINE_FOLLOW, LINE_FOLLOW_FW)) { SHELL_SendString((unsigned char*)"No segment any more!\r\n"); currLineKind = REF_GetLineKind(REF_LINE_KIND_MODE_LINE_FOLLOW); SHELL_SendString((unsigned char*)"Line: "); SHELL_SendString(REF_LineKindStr(currLineKind)); SHELL_SendString((unsigned char*)"\r\n"); SHELL_SendString((unsigned char*)"stop!\r\n"); LF_currState = STATE_STOP; /* stop if we do not have a line any more */ } } #endif break; #if PL_CONFIG_APP_LINE_MAZE case STATE_TURN: if (MAZE_IsSolved()) { TURN_Kind turn; turn = MAZE_GetSolvedTurn(&LF_solvedIdx); if (turn==TURN_STOP) { /* last turn reached */ TURN_Turn(turn, NULL); #if PL_CONFIG_USE_DRIVE (void)DRV_SetMode(DRV_MODE_NONE); /* disable any drive mode */ #endif LF_currState = STATE_STOP; SHELL_SendString((unsigned char *)"MAZE: I'm back to the start of maze!\r\n"); #if PL_CONFIG_USE_BUZZER BUZ_PlayTune(1); #endif MAZE_ClearSolution(); /* clear solution */ #if PL_TURN_ON_START /* turn the robot on start so it is ready to run the maze again */ #if PL_CONFIG_USE_QUADRATURE TURN_Turn(TURN_LEFT180, NULL); #else /* not accurate enough without position sensor */ TURN_Turn(TURN_LEFT90, NULL); /* do turn in two steps */ TURN_Turn(TURN_LEFT90, NULL); #endif #endif TURN_Turn(TURN_STOP, NULL); #if PL_CONFIG_USE_DRIVE (void)DRV_SetMode(DRV_MODE_NONE); /* disable any drive mode */ #endif /* now ready to solve maze again */ SHELL_SendString((unsigned char *)"MAZE: ready to start again!\r\n"); } else { /* perform turning */ TURN_Turn(TURN_STEP_LINE_FW_AND_POST_LINE, NULL); /* Step over line */ TURN_Turn(turn, NULL); #if LINE_DO_DEBUG_WAIT /* debug mode */ DebugStopAndWait(); #endif #if PL_CONFIG_USE_DRIVE (void)DRV_SetMode(DRV_MODE_NONE); /* disable any drive mode, so we can do line following (line following is PWM) */ #endif LF_currState = STATE_FOLLOW_SEGMENT; continue; /* process next state immediately */ } } else { /* still evaluating maze */ bool deadEndGoBw = FALSE; bool finished = FALSE; if (MAZE_EvaluteTurn(&finished, &deadEndGoBw)==ERR_OK) { /* finished turning */ #if LINE_DO_DEBUG_WAIT /* debug mode */ DebugStopAndWait(); #endif #if PL_CONFIG_USE_DRIVE (void)DRV_SetMode(DRV_MODE_NONE); /* disable any drive mode */ #endif if (finished) { LF_currState = STATE_FINISHED; MAZE_SetSolved(); LF_solvedIdx = 0; /* set index to start of solution */ #if PL_CONFIG_HAS_BUZZER BUZ_PlayTune(BUZ_TUNE_MAZE_DESTINATION); #endif #if PL_TURN_ON_FINISH /* turn the robot */ #if PL_CONFIG_USE_QUADRATURE TURN_Turn(TURN_LEFT180, NULL); #else /* not accurate enough without position sensor */ TURN_Turn(TURN_LEFT90, NULL); /* do turn in two steps */ TURN_Turn(TURN_LEFT90, NULL); #endif #endif //TURN_Turn(TURN_STOP, NULL); #if LINE_DO_DEBUG_WAIT /* debug mode */ DebugStopAndWait(); #endif #if PL_CONFIG_USE_DRIVE (void)DRV_SetMode(DRV_MODE_NONE); /* disable any drive mode */ #endif /* now ready to do line following */ } else { LF_currState = STATE_FOLLOW_SEGMENT; } } else { /* error case */ LF_currState = STATE_STOP; } } break; #endif #if PL_CONFIG_APP_LINE_MAZE case STATE_FINISHED: SHELL_SendString((unsigned char *)"MAZE: Found maze destination!\r\n"); #if PL_CONFIG_USE_BUZZER BUZ_PlayTune(BUZ_TUNE_MAZE_DESTINATION); #endif //SHELL_SendString((unsigned char *)"MAZE: Going back to start...\r\n"); //LF_currState = STATE_FOLLOW_SEGMENT; /* go back to start */ MAZE_ClearSolution(); /* clear solution */ LF_currState = STATE_STOP; break; #endif case STATE_STOP: SHELL_SendString((unsigned char *)"Stopped!\r\n"); (void)DRV_SetMode(DRV_MODE_NONE); /* disable any drive mode */ TURN_Turn(TURN_STOP, NULL); LF_currState = STATE_IDLE; break; } /* switch */ break; /* get out of for loop */ } /* for */ } bool LF_IsFollowing(void) { return LF_currState!=STATE_IDLE; } static const unsigned char *LINE_GetSpeedModeString(LINE_SpeedMode mode) { switch(mode) { case LINE_SPEED_LOW: return (const unsigned char*)"LOW"; case LINE_SPEED_MEDIUM: return (const unsigned char*)"MEDIUM"; case LINE_SPEED_HIGH: return (const unsigned char*)"HIGH"; default: break; } return (const unsigned char*)"UNKNOWN"; } static uint8_t LINE_SetSpeed(LINE_SpeedMode speed) { uint8_t res; PID_Config *lineFwPid, *posLeftPid, *posRightPid; res = PID_GetPIDConfig(PID_CONFIG_LINE_FW, &lineFwPid); if (res!=ERR_OK || lineFwPid==NULL) { return ERR_FAILED; } res = PID_GetPIDConfig(PID_CONFIG_POS_LEFT, &posLeftPid); if (res!=ERR_OK || posLeftPid==NULL) { return ERR_FAILED; } res = PID_GetPIDConfig(PID_CONFIG_POS_RIGHT, &posRightPid); if (res!=ERR_OK || posRightPid==NULL) { return ERR_FAILED; } switch(speed) { case LINE_SPEED_LOW: lineFwPid->maxSpeedPercent = 15; lineFwPid->pFactor100 = 4000; lineFwPid->dFactor100 = 500; TURN_SetSteps(680, 170, 180); posLeftPid->maxSpeedPercent = 30; posLeftPid->pFactor100 = 2000; posRightPid->pFactor100 = posLeftPid->pFactor100; posRightPid->maxSpeedPercent = posLeftPid->maxSpeedPercent; break; case LINE_SPEED_MEDIUM: lineFwPid->maxSpeedPercent = 30; /* 15 => 30 */ lineFwPid->pFactor100 = 5500; lineFwPid->iAntiWindup = 100000; TURN_SetSteps(700, 190, 100); posLeftPid->maxSpeedPercent = 80; posLeftPid->pFactor100 = 2000; posRightPid->pFactor100 = posLeftPid->pFactor100; posRightPid->maxSpeedPercent = posLeftPid->maxSpeedPercent; break; case LINE_SPEED_HIGH: /* to be verified */ lineFwPid->maxSpeedPercent = 40; lineFwPid->pFactor100 = 2000; lineFwPid->iAntiWindup = 30000; TURN_SetSteps(700, 150, 80); posLeftPid->maxSpeedPercent = 100; posLeftPid->pFactor100 = 2000; posRightPid->pFactor100 = posLeftPid->pFactor100; posRightPid->maxSpeedPercent = posLeftPid->maxSpeedPercent; break; default: return ERR_FAILED; } /* switch */ lineFollowSpeed = speed; SHELL_SendString((unsigned char*)"Changed parameters for speed!\n"); return ERR_OK; } static void LineTask (void *pvParameters) { uint32_t notifcationValue; BaseType_t notified; (void)pvParameters; /* not used */ #if PL_SLOWER_SPEED (void)LINE_SetSpeed(LINE_SPEED_LOW); #else /* check! */ (void)LINE_SetSpeed(LINE_SPEED_MEDIUM); #endif for(;;) { notified = xTaskNotifyWait(0UL, LF_START_FOLLOWING|LF_STOP_FOLLOWING, ¬ifcationValue, 1); /* check flags, need to wait for one tick */ if (notified==pdTRUE) { /* received notification */ if (notifcationValue&LF_START_FOLLOWING) { if (REF_CanUseSensor()) { PID_Start(); LF_currState = STATE_FOLLOW_SEGMENT; #if PL_CONFIG_USE_DRIVE (void)DRV_SetMode(DRV_MODE_NONE); /* disable any drive mode */ #endif } else { SHELL_SendString((unsigned char*)"Sensors not ready!\r\n"); #if PL_CONFIG_USE_BUZZER (void)BUZ_Beep(500, 500); #endif } } if (notifcationValue&LF_STOP_FOLLOWING) { LF_currState = STATE_STOP; } } StateMachine(); if (LF_IsFollowing()) { vTaskDelay(5/portTICK_PERIOD_MS); } else { vTaskDelay(100/portTICK_PERIOD_MS); } } } static void LF_PrintHelp(const McuShell_StdIOType *io) { McuShell_SendHelpStr((unsigned char*)"line", (unsigned char*)"Group of line following commands\r\n", io->stdOut); McuShell_SendHelpStr((unsigned char*)" help|status", (unsigned char*)"Shows line help or status\r\n", io->stdOut); McuShell_SendHelpStr((unsigned char*)" speed ", (unsigned char*)"Line following speed, either low, medium or high\r\n", io->stdOut); McuShell_SendHelpStr((unsigned char*)" start|stop", (unsigned char*)"Starts or stops line following\r\n", io->stdOut); } static void LF_PrintStatus(const McuShell_StdIOType *io) { uint8_t buf[32]; McuShell_SendStatusStr((unsigned char*)"line follow", (unsigned char*)"Line following status\r\n", io->stdOut); switch (LF_currState) { case STATE_IDLE: McuShell_SendStatusStr((unsigned char*)" state", (unsigned char*)"IDLE\r\n", io->stdOut); break; case STATE_FOLLOW_SEGMENT: McuShell_SendStatusStr((unsigned char*)" state", (unsigned char*)"FOLLOW_SEGMENT\r\n", io->stdOut); break; case STATE_STOP: McuShell_SendStatusStr((unsigned char*)" state", (unsigned char*)"STOP\r\n", io->stdOut); break; #if PL_CONFIG_APP_LINE_MAZE case STATE_TURN: McuShell_SendStatusStr((unsigned char*)" state", (unsigned char*)"TURN\r\n", io->stdOut); break; case STATE_FINISHED: McuShell_SendStatusStr((unsigned char*)" state", (unsigned char*)"FINISHED\r\n", io->stdOut); break; #endif default: McuShell_SendStatusStr((unsigned char*)" state", (unsigned char*)"UNKNOWN\r\n", io->stdOut); break; } /* switch */ McuUtility_strcpy(buf, sizeof(buf), LINE_GetSpeedModeString(lineFollowSpeed)); McuUtility_strcat(buf, sizeof(buf), (unsigned char*)"\r\n"); McuShell_SendStatusStr((unsigned char*)" speed", buf, io->stdOut); } uint8_t LF_ParseCommand(const unsigned char *cmd, bool *handled, const McuShell_StdIOType *io) { uint8_t res = ERR_OK; if (McuUtility_strcmp((char*)cmd, (char*)McuShell_CMD_HELP)==0 || McuUtility_strcmp((char*)cmd, (char*)"line help")==0) { LF_PrintHelp(io); *handled = TRUE; } else if (McuUtility_strcmp((char*)cmd, (char*)McuShell_CMD_STATUS)==0 || McuUtility_strcmp((char*)cmd, (char*)"line status")==0) { LF_PrintStatus(io); *handled = TRUE; } else if (McuUtility_strcmp((char*)cmd, (char*)"line start")==0) { LF_StartFollowing(); *handled = TRUE; } else if (McuUtility_strcmp((char*)cmd, (char*)"line stop")==0) { LF_StopFollowing(); *handled = TRUE; } else if (McuUtility_strcmp((char*)cmd, (char*)"line speed low")==0) { (void)LINE_SetSpeed(LINE_SPEED_LOW); *handled = TRUE; } else if (McuUtility_strcmp((char*)cmd, (char*)"line speed medium")==0) { (void)LINE_SetSpeed(LINE_SPEED_MEDIUM); *handled = TRUE; } else if (McuUtility_strcmp((char*)cmd, (char*)"line speed high")==0) { (void)LINE_SetSpeed(LINE_SPEED_HIGH); *handled = TRUE; } return res; } void LF_Deinit(void) { /* nothing needed */ } void LF_Init(void) { LF_currState = STATE_IDLE; if (xTaskCreate(LineTask, "Line", 600/sizeof(StackType_t), NULL, tskIDLE_PRIORITY+2, &LFTaskHandle) != pdPASS) { for(;;){} /* error */ } } #endif /* PL_CONFIG_APP_LINE_FOLLOWING || PL_CONFIG_APP_LINE_MAZE */