/** * \file * \brief Reflectance sensor driver implementation. * \author Erich Styger, erich.styger@hslu.ch * \license SPDX-License-Identifier: BSD-3-Clause * This module implements the driver for the bot front sensor. */ #include "platform.h" #if PL_CONFIG_USE_LINE_SENSOR #include "Reflectance.h" #include "McuRTOS.h" #include "McuGPIO.h" #include "McuUtility.h" #include "McuWait.h" #if PL_CONFIG_USE_BUZZER #include "Buzzer.h" #endif #include "Application.h" #if PL_CONFIG_HAS_NVM_CONFIG #include "NVM_Config.h" #endif #include "Shell.h" #include "Timer.h" typedef enum { REF_STATE_INIT, REF_STATE_NOT_CALIBRATED, REF_STATE_START_CALIBRATION, REF_STATE_CALIBRATING, REF_STATE_STOP_CALIBRATION, REF_STATE_SAVE_CALIBRATION, REF_STATE_READY } RefStateType; static volatile RefStateType refState = REF_STATE_INIT; static struct { bool isEnabled; /* if sensor is enabled or not. If not enabled, it does not do any measurements */ bool savePower; /* if true, the IR LEDs are turned off between measurements */ McuGPIO_Handle_t irOn; /* HIGH active, pin to turn on/off power to the IR LEDs */ McuGPIO_Handle_t sensor[REF_NOF_SENSORS]; /* pins for the IR sensor */ } REF_Sensor; #define REF_USE_WHITE_LINE 0 /* if set to 1, then the robot is using a white (on black) line, otherwise a black (on white) line */ #define REF_START_STOP_CALIB 1 /* start/stop calibration commands */ #if REF_START_STOP_CALIB static SemaphoreHandle_t REF_StartStopSem = NULL; #endif #define REF_TIMEOUT_TICKS (((TMR_GetRefelectanceTimerFrequencyHz()/1000)*REF_SENSOR_TIMEOUT_US)/1000) /* REF_SENSOR_TIMEOUT_US translated into timeout ticks */ typedef uint16_t SensorTimeType; #define MAX_SENSOR_VALUE ((SensorTimeType)-1) /* type of NVM Configuration data (in FLASH) */ typedef struct SensorCalibT_ { SensorTimeType minVal[REF_NOF_SENSORS]; SensorTimeType maxVal[REF_NOF_SENSORS]; } SensorCalibT; static SensorCalibT *SensorCalibMinMaxPtr; /* pointer to calibrated data in FLASH, NULL if not calibrated */ static SensorCalibT *SensorCalibMinMaxTmpPtr=NULL; /* temporary calibration data */ static SensorTimeType SensorRaw[REF_NOF_SENSORS]; /* raw sensor values */ static SensorTimeType SensorCalibrated[REF_NOF_SENSORS]; /* 0 means white/min value, 1000 means black/max value */ static bool REF_LedOn = TRUE; static int16_t refCenterLineVal=0; /* 0 means no line, >0 means line is below sensor 0, 1000 below sensor 1 and so on */ static REF_LineKind refLineKind = REF_LINE_NONE; static uint16_t refLineWidth = 0; void REF_GetSensorValues(uint16_t *values, int nofValues) { int i; for(i=0;itimeoutCntVal) { break; /* get out of while loop */ } for(i=0;i max[i]) { max[i] = raw[i]; } } } } static void ReadCalibrated(SensorTimeType calib[REF_NOF_SENSORS], SensorTimeType raw[REF_NOF_SENSORS]) { int i; int32_t x, denominator; uint32_t max; max = 0; /* determine maximum timeout value */ for(i=0;imaxVal[i]>max) { max = SensorCalibMinMaxPtr->maxVal[i]; } } if (max > REF_TIMEOUT_TICKS) { /* limit to timeout value */ max = REF_TIMEOUT_TICKS; } (void)REF_MeasureRaw(raw, max); if (SensorCalibMinMaxPtr==NULL) { /* no calibration data? */ return; } for(i=0;imaxVal[i]-SensorCalibMinMaxPtr->minVal[i]; if (denominator!=0) { x = (((int32_t)raw[i]-SensorCalibMinMaxPtr->minVal[i])*1000)/denominator; } if (x<0) { x = 0; } else if (x>1000) { x = 1000; } calib[i] = x; } } /* * Operates the same as read calibrated, but also returns an * estimated position of the robot with respect to a line. The * estimate is made using a weighted average of the sensor indices * multiplied by 1000, so that a return value of 1000 indicates that * the line is directly below sensor 0, a return value of 2000 * indicates that the line is directly below sensor 1, 2000 * indicates that it's below sensor 2000, etc. Intermediate * values indicate that the line is between two sensors. The * formula is: * * 1000*value0 + 2000*value1 + 3000*value2 + ... * -------------------------------------------- * value0 + value1 + value2 + ... * * By default, this function assumes a dark line (high values) * surrounded by white (low values). If your line is light on * black, set the optional second argument white_line to true. In * this case, each sensor value will be replaced by (1000-value) * before the averaging. */ #define RETURN_LAST_VALUE 0 static int ReadLine(SensorTimeType calib[REF_NOF_SENSORS], SensorTimeType raw[REF_NOF_SENSORS], bool white_line) { int i; unsigned long avg; /* this is for the weighted total, which is long */ /* before division */ unsigned int sum; /* this is for the denominator which is <= 64000 */ unsigned int mul; /* multiplication factor, 0, 1000, 2000, 3000 ... */ int value; #if RETURN_LAST_VALUE bool on_line = FALSE; static int last_value=0; /* assume initially that the line is left. */ #endif (void)raw; /* unused */ avg = 0; sum = 0; mul = 1000; #if REF_SENSOR1_IS_LEFT for(i=0;i=0;i--) { #endif value = calib[i]; if(white_line) { value = 1000-value; } /* only average in values that are above a noise threshold */ if(value > REF_MIN_NOISE_VAL) { avg += ((long)value)*mul; sum += value; } mul += 1000; } #if RETURN_LAST_VALUE /* keep track of whether we see the line at all */ if(value > REF_MIN_LINE_VAL) { on_line = TRUE; } if(!on_line) { /* If it last read to the left of center, return 0. */ if(last_value < endIdx*1000/2) { return 0; } else { /* If it last read to the right of center, return the max. */ return endIdx*1000; } } last_value = avg/sum; return last_value; #else if (sum>0) { return avg/sum; } else { return avg; } #endif } unsigned char *REF_LineKindStr(REF_LineKind line) { switch(line) { case REF_LINE_NONE: return (unsigned char *)"NONE"; case REF_LINE_STRAIGHT: return (unsigned char *)"STRAIGHT"; case REF_LINE_LEFT: return (unsigned char *)"LEFT"; case REF_LINE_RIGHT: return (unsigned char *)"RIGHT"; case REF_LINE_FULL: return (unsigned char *)"FULL"; case REF_LINE_AIR: return (unsigned char *)"AIR"; default: return (unsigned char *)"unknown"; } /* switch */ } REF_LineKind REF_GetLineKind(REF_LineKindMode mode) { if (mode==REF_LINE_KIND_MODE_ALL || mode==REF_LINE_KIND_MODE_MAZE || mode==REF_LINE_KIND_MODE_SUMO) { return refLineKind; } else if (mode==REF_LINE_KIND_MODE_LINE_FOLLOW) { switch(refLineKind) { case REF_LINE_NONE: return REF_LINE_NONE; case REF_LINE_LEFT: case REF_LINE_RIGHT: case REF_LINE_STRAIGHT: return REF_LINE_STRAIGHT; case REF_LINE_AIR: case REF_LINE_FULL: return REF_LINE_NONE; default: return REF_LINE_NONE; } } return refLineKind; } static bool SensorsSaturated(void) { #if 0 int i, cnt; /* check if robot is in the air or does see something */ cnt = 0; for(i=0;iREF_MIN_LINE_VAL) { /* count only line values */ nofLines++; sum += val[i]; if (i0) { /* both leftmost and rightmost sensors are seeing white, middle is seeing at least one black line */ return REF_LINE_STRAIGHT; /* straight line forward */ } if (outerLeft>=REF_MIN_LINE_VAL && outerRightMIN_LEFT_RIGHT_SUM && sumRight=REF_MIN_LINE_VAL && sumRight>MIN_LEFT_RIGHT_SUM && sumLeft=REF_MIN_LINE_VAL && outerRight>=REF_MIN_LINE_VAL && sumRight>MIN_LEFT_RIGHT_SUM && sumLeft>MIN_LEFT_RIGHT_SUM) { return REF_LINE_FULL; /* full line */ } else if (sumRight==0 && sumLeft==0 && sum == 0) { return REF_LINE_NONE; /* no line */ } else { return REF_LINE_STRAIGHT; /* straight line forward */ } } uint16_t REF_LineWidth(void) { return refLineWidth; } static uint16_t CalculateRefLineWidth(SensorTimeType calib[REF_NOF_SENSORS]) { int32_t val; int i; val = 0; for(i=0;i=REF_MIN_NOISE_VAL) { /* sensor not seeing anything? */ val += calib[i]; } } return (uint16_t)val; } static void REF_Measure(void) { ReadCalibrated(SensorCalibrated, SensorRaw); refCenterLineVal = ReadLine(SensorCalibrated, SensorRaw, REF_USE_WHITE_LINE); refLineWidth = CalculateRefLineWidth(SensorCalibrated); if (refState==REF_STATE_READY) { refLineKind = ReadLineKind(SensorCalibrated); } } #if REF_START_STOP_CALIB void REF_CalibrateStartStop(void) { REF_Sensor.isEnabled = TRUE; if (refState==REF_STATE_NOT_CALIBRATED || refState==REF_STATE_CALIBRATING || refState==REF_STATE_READY) { (void)xSemaphoreGive(REF_StartStopSem); } } #endif bool REF_CanUseSensor(void) { return refState==REF_STATE_READY; } uint16_t REF_GetLineValue(bool *onLine) { *onLine = refCenterLineVal>0 && refCenterLineValstdOut); McuShell_SendHelpStr((unsigned char*)" help|status", (unsigned char*)"Print help or status information\r\n", io->stdOut); McuShell_SendHelpStr((unsigned char*)" enable|disable", (unsigned char*)"Enables or disables the reflectance measurement\r\n", io->stdOut); McuShell_SendHelpStr((unsigned char*)" calib (start|stop)", (unsigned char*)"Start/Stop calibrating while moving sensor over line\r\n", io->stdOut); McuShell_SendHelpStr((unsigned char*)" save power (on|off)", (unsigned char*)"Saves power with turning off IR between measurements\r\n", io->stdOut); return ERR_OK; } static unsigned char*REF_GetStateString(void) { switch (refState) { case REF_STATE_INIT: return (unsigned char*)"INIT"; case REF_STATE_NOT_CALIBRATED: return (unsigned char*)"NOT CALIBRATED"; case REF_STATE_START_CALIBRATION: return (unsigned char*)"START CALIBRATION"; case REF_STATE_CALIBRATING: return (unsigned char*)"CALIBRATING"; case REF_STATE_STOP_CALIBRATION: return (unsigned char*)"STOP CALIBRATION"; case REF_STATE_SAVE_CALIBRATION: return (unsigned char*)"SAVE CALIBRATION"; case REF_STATE_READY: return (unsigned char*)"READY"; default: break; } /* switch */ return (unsigned char*)"UNKNOWN"; } static uint8_t PrintStatus(const McuShell_StdIOType *io) { unsigned char buf[32]; int i; McuShell_SendStatusStr((unsigned char*)"reflectance", (unsigned char*)"IR line sensor status\r\n", io->stdOut); McuShell_SendStatusStr((unsigned char*)" enabled", REF_Sensor.isEnabled?(unsigned char*)"yes\r\n":(unsigned char*)"no\r\n", io->stdOut); #if REF_USE_WHITE_LINE McuShell_SendStatusStr((unsigned char*)" line", (unsigned char*)"white\r\n", io->stdOut); #else McuShell_SendStatusStr((unsigned char*)" line", (unsigned char*)"black\r\n", io->stdOut); #endif McuShell_SendStatusStr((unsigned char*)" state", REF_GetStateString(), io->stdOut); McuShell_SendStr((unsigned char*)"\r\n", io->stdOut); McuShell_SendStatusStr((unsigned char*)" save power", REF_Sensor.savePower?(unsigned char*)"yes\r\n":(unsigned char*)"no\r\n", io->stdOut); McuUtility_strcpy(buf, sizeof(buf), (unsigned char*)"0x"); McuUtility_strcatNum16Hex(buf, sizeof(buf), REF_MIN_NOISE_VAL); McuUtility_strcat(buf, sizeof(buf), (unsigned char*)"\r\n"); McuShell_SendStatusStr((unsigned char*)" min noise", buf, io->stdOut); McuUtility_strcpy(buf, sizeof(buf), (unsigned char*)"0x"); McuUtility_strcatNum16Hex(buf, sizeof(buf), REF_MIN_LINE_VAL); McuUtility_strcat(buf, sizeof(buf), (unsigned char*)"\r\n"); McuShell_SendStatusStr((unsigned char*)" min line", buf, io->stdOut); McuUtility_Num16uToStr(buf, sizeof(buf), REF_SENSOR_TIMEOUT_US); McuUtility_strcat(buf, sizeof(buf), (unsigned char*)" us, 0x"); McuUtility_strcatNum16Hex(buf, sizeof(buf), REF_TIMEOUT_TICKS); McuUtility_strcat(buf, sizeof(buf), (unsigned char*)" ticks\r\n"); McuShell_SendStatusStr((unsigned char*)" timeout", buf, io->stdOut); McuShell_SendStatusStr((unsigned char*)" raw val", (unsigned char*)"", io->stdOut); #if REF_SENSOR1_IS_LEFT for (i=0;i=0;i--) { if (i==REF_NOF_SENSORS-1) { #endif McuShell_SendStr((unsigned char*)"0x", io->stdOut); } else { McuShell_SendStr((unsigned char*)" 0x", io->stdOut); } buf[0] = '\0'; McuUtility_strcatNum16Hex(buf, sizeof(buf), SensorRaw[i]); McuShell_SendStr(buf, io->stdOut); } McuShell_SendStr((unsigned char*)"\r\n", io->stdOut); if (SensorCalibMinMaxPtr!=NULL) { /* have calibration data */ McuShell_SendStatusStr((unsigned char*)" min val", (unsigned char*)"", io->stdOut); #if REF_SENSOR1_IS_LEFT for (i=0;i=0;i--) { if (i==REF_NOF_SENSORS-1) { #endif McuShell_SendStr((unsigned char*)"0x", io->stdOut); } else { McuShell_SendStr((unsigned char*)" 0x", io->stdOut); } buf[0] = '\0'; McuUtility_strcatNum16Hex(buf, sizeof(buf), SensorCalibMinMaxPtr->minVal[i]); McuShell_SendStr(buf, io->stdOut); } McuShell_SendStr((unsigned char*)"\r\n", io->stdOut); } if (SensorCalibMinMaxPtr!=NULL) { McuShell_SendStatusStr((unsigned char*)" max val", (unsigned char*)"", io->stdOut); #if REF_SENSOR1_IS_LEFT for (i=0;i=0;i--) { if (i==REF_NOF_SENSORS-1) { #endif McuShell_SendStr((unsigned char*)"0x", io->stdOut); } else { McuShell_SendStr((unsigned char*)" 0x", io->stdOut); } buf[0] = '\0'; McuUtility_strcatNum16Hex(buf, sizeof(buf), SensorCalibMinMaxPtr->maxVal[i]); McuShell_SendStr(buf, io->stdOut); } McuShell_SendStr((unsigned char*)"\r\n", io->stdOut); } if (SensorCalibMinMaxPtr!=NULL) { /* have calibration data */ McuShell_SendStatusStr((unsigned char*)" calib val", (unsigned char*)"", io->stdOut); #if REF_SENSOR1_IS_LEFT for (i=0;i=0;i--) { if (i==REF_NOF_SENSORS-1) { #endif McuShell_SendStr((unsigned char*)"0x", io->stdOut); } else { McuShell_SendStr((unsigned char*)" 0x", io->stdOut); } buf[0] = '\0'; McuUtility_strcatNum16Hex(buf, sizeof(buf), SensorCalibrated[i]); McuShell_SendStr(buf, io->stdOut); } McuShell_SendStr((unsigned char*)"\r\n", io->stdOut); } McuShell_SendStatusStr((unsigned char*)" line pos", (unsigned char*)"", io->stdOut); buf[0] = '\0'; McuUtility_strcatNum16s(buf, sizeof(buf), refCenterLineVal); McuShell_SendStr(buf, io->stdOut); McuShell_SendStr((unsigned char*)"\r\n", io->stdOut); McuShell_SendStatusStr((unsigned char*)" line width", (unsigned char*)"", io->stdOut); buf[0] = '\0'; McuUtility_strcatNum16s(buf, sizeof(buf), refLineWidth); McuShell_SendStr(buf, io->stdOut); McuShell_SendStr((unsigned char*)"\r\n", io->stdOut); McuShell_SendStatusStr((unsigned char*)" line kind", REF_LineKindStr(refLineKind), io->stdOut); McuShell_SendStr((unsigned char*)"\r\n", io->stdOut); return ERR_OK; } uint8_t REF_ParseCommand(const unsigned char *cmd, bool *handled, const McuShell_StdIOType *io) { if (McuUtility_strcmp((char*)cmd, McuShell_CMD_HELP)==0 || McuUtility_strcmp((char*)cmd, "ref help")==0) { *handled = TRUE; return PrintHelp(io); } else if ((McuUtility_strcmp((char*)cmd, McuShell_CMD_STATUS)==0) || (McuUtility_strcmp((char*)cmd, "ref status")==0)) { *handled = TRUE; return PrintStatus(io); } else if (McuUtility_strcmp((char*)cmd, "ref enable")==0) { REF_Sensor.isEnabled = TRUE; *handled = TRUE; return ERR_OK; } else if (McuUtility_strcmp((char*)cmd, "ref disable")==0) { REF_Sensor.isEnabled = FALSE; *handled = TRUE; return ERR_OK; } else if (McuUtility_strcmp((char*)cmd, "ref calib start")==0) { if (refState==REF_STATE_NOT_CALIBRATED || refState==REF_STATE_READY) { APP_StateStartCalibrate(); } else { McuShell_SendStr((unsigned char*)"ERROR: cannot start calibration, must not be calibrating or be ready.\r\n", io->stdErr); return ERR_FAILED; } *handled = TRUE; return ERR_OK; } else if (McuUtility_strcmp((char*)cmd, "ref calib stop")==0) { if (refState==REF_STATE_CALIBRATING) { APP_StateStopCalibrate(); } else { McuShell_SendStr((unsigned char*)"ERROR: can only stop if calibrating.\r\n", io->stdErr); return ERR_FAILED; } *handled = TRUE; return ERR_OK; } else if (McuUtility_strcmp((char*)cmd, "ref save power on")==0) { REF_Sensor.savePower = true; McuGPIO_SetLow(REF_Sensor.irOn);; /* turn IR LED's off */ *handled = TRUE; return ERR_OK; } else if (McuUtility_strcmp((char*)cmd, "ref save power off")==0) { REF_Sensor.savePower = false; *handled = TRUE; return ERR_OK; } return ERR_OK; } #endif /* PL_CONFIG_USE_SHELL */ static void REF_StateMachine(void) { int i; switch (refState) { case REF_STATE_INIT: #if PL_CONFIG_HAS_NVM_CONFIG SensorCalibMinMaxPtr = NVMC_GetReflectanceData(); if (SensorCalibMinMaxPtr!=NULL) { /* use calibration data from FLASH */ refState = REF_STATE_READY; } else { #if PL_CONFIG_USE_SHELL SHELL_SendString((unsigned char*)"no calibration data present.\r\n"); #endif refState = REF_STATE_NOT_CALIBRATED; } #else refState = REF_STATE_NOT_CALIBRATED; #endif break; case REF_STATE_NOT_CALIBRATED: vTaskDelay(pdMS_TO_TICKS(80)); /* no need to sample that fast: this gives 80+20=100 ms */ (void)REF_MeasureRaw(SensorRaw, REF_TIMEOUT_TICKS); #if REF_START_STOP_CALIB if (xSemaphoreTake(REF_StartStopSem, 0)==pdTRUE) { refState = REF_STATE_START_CALIBRATION; } #endif break; case REF_STATE_START_CALIBRATION: #if PL_CONFIG_USE_SHELL SHELL_SendString((unsigned char*)"start calibration...\r\n"); #endif if (SensorCalibMinMaxTmpPtr!=NULL) { refState = REF_STATE_INIT; /* error case */ break; } SensorCalibMinMaxTmpPtr = pvPortMalloc(sizeof(SensorCalibT)); if (SensorCalibMinMaxTmpPtr!=NULL) { /* success */ for(i=0;iminVal[i] = MAX_SENSOR_VALUE; SensorCalibMinMaxTmpPtr->maxVal[i] = 0; SensorCalibrated[i] = 0; } refState = REF_STATE_CALIBRATING; } else { refState = REF_STATE_INIT; /* error case */ } break; case REF_STATE_CALIBRATING: vTaskDelay(pdMS_TO_TICKS(80)); /* no need to sample that fast: this gives 80+20=100 ms */ if (SensorCalibMinMaxTmpPtr!=NULL) { /* safety check */ REF_CalibrateMinMax(SensorCalibMinMaxTmpPtr->minVal, SensorCalibMinMaxTmpPtr->maxVal, SensorRaw); } else { refState = REF_STATE_INIT; /* error case */ break; } #if PL_CONFIG_USE_BUZZER (void)BUZ_Beep(300, 50); #endif #if REF_START_STOP_CALIB if (xSemaphoreTake(REF_StartStopSem, 0)==pdTRUE) { refState = REF_STATE_STOP_CALIBRATION; } #endif break; case REF_STATE_STOP_CALIBRATION: #if PL_CONFIG_USE_SHELL SHELL_SendString((unsigned char*)"...stopping calibration.\r\n"); #endif refState = REF_STATE_SAVE_CALIBRATION; break; case REF_STATE_SAVE_CALIBRATION: #if PL_CONFIG_HAS_NVM_CONFIG if (NVMC_SaveReflectanceData((void*)SensorCalibMinMaxTmpPtr, sizeof(SensorCalibT))!=ERR_OK) { #if PL_CONFIG_USE_SHELL SHELL_SendString((unsigned char*)"FAILED storing to FLASH!?!\r\n"); #endif } /* free memory */ vPortFree(SensorCalibMinMaxTmpPtr); SensorCalibMinMaxTmpPtr = NULL; SensorCalibMinMaxPtr = NVMC_GetReflectanceData(); if (SensorCalibMinMaxPtr!=NULL) { /* use calibration data from FLASH */ #if PL_CONFIG_USE_SHELL SHELL_SendString((unsigned char*)"calibration data saved.\r\n"); #endif refState = REF_STATE_READY; break; } else { refState = REF_STATE_INIT; /* error case */ } #else refState = REF_STATE_READY; #endif break; case REF_STATE_READY: REF_Measure(); #if REF_START_STOP_CALIB if (xSemaphoreTake(REF_StartStopSem, 0)==pdTRUE) { refState = REF_STATE_START_CALIBRATION; } #endif break; } /* switch */ } static void ReflTask(void *pvParameters) { (void)pvParameters; /* not used */ for(;;) { if (REF_Sensor.isEnabled) { REF_StateMachine(); vTaskDelay(pdMS_TO_TICKS(5)); } else { vTaskDelay(pdMS_TO_TICKS(100)); } } } void REF_Init(void) { McuGPIO_Config_t gpioConfig; McuGPIO_HwPin_t hw[REF_NOF_SENSORS] = { {.gpio=GPIOD, .port=PORTD, .pin=2}, {.gpio=GPIOD, .port=PORTD, .pin=3}, {.gpio=GPIOD, .port=PORTD, .pin=4}, {.gpio=GPIOD, .port=PORTD, .pin=5}, {.gpio=GPIOD, .port=PORTD, .pin=6}, {.gpio=GPIOD, .port=PORTD, .pin=7}, }; REF_Sensor.isEnabled = true; REF_Sensor.savePower = false; /* turn off, as this extends the ref sensor task time by around 200 us */ McuGPIO_GetDefaultConfig(&gpioConfig); /* IR on/off: PTD1, high active */ gpioConfig.hw.gpio = GPIOD; gpioConfig.hw.port = PORTD; gpioConfig.hw.pin = 1; gpioConfig.isInput = false; gpioConfig.isHighOnInit = false; REF_Sensor.irOn = McuGPIO_InitGPIO(&gpioConfig); REF_LedOn = false; /* IR sensors */ gpioConfig.isInput = true; gpioConfig.isHighOnInit = false; for(int i=0; i