Files
SharpKey/main/LED.cpp
2022-09-04 14:51:38 +01:00

342 lines
15 KiB
C++

/////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Name: LED.cpp
// Created: Mar 2022
// Version: v1.0
// Author(s): Philip Smart
// Description: Base class for the encapsulation and control methods of an LED used primarily to
// indicate to users the status of the application.
// Credits:
// Copyright: (c) 2019-2022 Philip Smart <philip.smart@net2net.org>
//
// History: Mar 2022 - Initial write.
// v1.01 May 2022 - Initial release version.
//
// Notes: See Makefile to enable/disable conditional components
//
/////////////////////////////////////////////////////////////////////////////////////////////////////////
// This source file is free software: you can redistribute it and#or modify
// it under the terms of the GNU General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This source file is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
/////////////////////////////////////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_system.h"
#include "driver/gpio.h"
#include "soc/timer_group_struct.h"
#include "soc/timer_group_reg.h"
#include "driver/timer.h"
#include "PS2KeyAdvanced.h"
#include "PS2Mouse.h"
#include "sdkconfig.h"
#include "LED.h"
// Method to set the LED mode, duty cycle and duty period. Once the current LED cycle has come to an end, the control
// thread will replace the working configuration with the new configuration set here.
//
bool LED::setLEDMode(enum LED_MODE mode, enum LED_DUTY_CYCLE dutyCycle, uint32_t maxBlinks, uint64_t usDutyPeriod, uint64_t msInterPeriod)
{
// Locals.
//
bool result = true;
// If a setup is already waiting to be processed, exit with fail. This could be stacked into a vector but not really beneficial.
if(ledCtrl.newConfig.updated == false)
{
// Ensure we have exclusive access, the LED can be controlled by numerous threads, so ensure only one can access and setup at a time.
if(xSemaphoreTake(ledCtrl.mutexInternal, (TickType_t)1000) == pdTRUE)
{
ledCtrl.newConfig.mode = mode;
ledCtrl.newConfig.dutyCycle = dutyCycle;
ledCtrl.newConfig.maxBlinks = maxBlinks;
ledCtrl.newConfig.dutyPeriod = usDutyPeriod;
ledCtrl.newConfig.interPeriod = msInterPeriod;
ledCtrl.newConfig.updated = true;
// Release mutex so other threads can set the LED.
xSemaphoreGive(ledCtrl.mutexInternal);
} else
{
result = false;
}
} else
{
result = false;
}
return(result);
}
// Thread method to provide LED control.
IRAM_ATTR void LED::ledInterface(void *pvParameters)
{
// Locals.
//
uint32_t LED_MASK;
uint64_t delayTimer = 0LL;
uint64_t curTime = 0LL;
enum LEDSTATE {
LEDSTATE_IDLE = 0,
LEDSTATE_BLINK_MARK = 1,
LEDSTATE_BLINK_SPACE = 2,
LEDSTATE_BLINK_INTER = 3,
} fsmState = LEDSTATE_IDLE;
#define LEDIFTAG "ledInterface"
// Map the instantiating object so we can access its methods and data.
LED* pThis = (LED*)pvParameters;
// Set the LED GPIO mask according to the defined pin. This code needs updating if GPIO1 pins are used.
LED_MASK = (1 << pThis->ledCtrl.ledPin);
// Sign on.
ESP_LOGW("ledInterface", "Starting LED control thread.");
// Turn off LED.
GPIO.out_w1tc = LED_MASK;
// Loops forever.
for(;;)
{
// Check stack space, report if it is getting low.
if(uxTaskGetStackHighWaterMark(NULL) < 1024)
{
ESP_LOGW(LEDIFTAG, "THREAD STACK SPACE(%d)\n",uxTaskGetStackHighWaterMark(NULL));
}
// If a new configuration is set, then once the running FSM has returned to idle, update the configuration prior to the next FSM run.
//
if(pThis->ledCtrl.newConfig.updated)
{
// Take control of the Mutex so we are able to take on the data without a new setup clashing. If the Mutex is taken then continue on with the state machine logic till next loop.
if(xSemaphoreTake(pThis->ledCtrl.mutexInternal, (TickType_t)1) == pdTRUE)
{
pThis->ledCtrl.currentConfig = pThis->ledCtrl.newConfig;
pThis->ledCtrl.currentConfig.valid = true;
pThis->ledCtrl.newConfig.updated = false;
pThis->ledCtrl.blinkCnt = 0;
// Got new setup so release mutex.
xSemaphoreGive(pThis->ledCtrl.mutexInternal);
}
}
// Only run if we have a valid configuration.
if(pThis->ledCtrl.currentConfig.valid)
{
do {
// Get the current timer value, only run the FSM when the timer is idle.
timer_get_counter_value(TIMER_GROUP_0, TIMER_1, &curTime);
if(curTime >= delayTimer)
{
// Ensure the timer is stopped.
timer_pause(TIMER_GROUP_0, TIMER_1);
delayTimer = 0LL;
// Mini finite state machine for LED control.
switch(fsmState)
{
case LEDSTATE_IDLE:
// For on/off, no need for the FSM, just apply setting to LED and loop.
switch(pThis->ledCtrl.currentConfig.mode)
{
case LED_MODE_ON:
// Turn on LED.
GPIO.out_w1ts = LED_MASK;
delayTimer = 1000UL;
break;
case LED_MODE_BLINK_ONESHOT:
// If the number of blinks is not 0 then on reaching the count, switch to LED off mode.
if(pThis->ledCtrl.currentConfig.maxBlinks > 0 && pThis->ledCtrl.blinkCnt++ >= pThis->ledCtrl.currentConfig.maxBlinks)
{
pThis->ledCtrl.currentConfig.mode = LED_MODE_OFF;
} else
{
fsmState = LEDSTATE_BLINK_MARK;
}
break;
case LED_MODE_BLINK:
// Normal blink mode increments the count which is used for determining inter blink period.
pThis->ledCtrl.blinkCnt++;
fsmState = LEDSTATE_BLINK_MARK;
break;
case LED_MODE_OFF:
default:
// Turn off LED.
GPIO.out_w1tc = LED_MASK;
delayTimer = 1000UL;
break;
}
break;
case LEDSTATE_BLINK_MARK:
// Turn on LED.
GPIO.out_w1ts = LED_MASK;
// Next state, SPACE.
fsmState = LEDSTATE_BLINK_SPACE;
// Calculate time to SPACE.
switch(pThis->ledCtrl.currentConfig.dutyCycle)
{
case LED_DUTY_CYCLE_10:
delayTimer = (pThis->ledCtrl.currentConfig.dutyPeriod / 10);
break;
case LED_DUTY_CYCLE_20:
delayTimer = ((pThis->ledCtrl.currentConfig.dutyPeriod / 10) * 2);
break;
case LED_DUTY_CYCLE_30:
delayTimer = ((pThis->ledCtrl.currentConfig.dutyPeriod / 10) * 3);
break;
case LED_DUTY_CYCLE_40:
delayTimer = ((pThis->ledCtrl.currentConfig.dutyPeriod / 10) * 4);
break;
case LED_DUTY_CYCLE_50:
delayTimer = ((pThis->ledCtrl.currentConfig.dutyPeriod / 10) * 5);
break;
case LED_DUTY_CYCLE_60:
delayTimer = ((pThis->ledCtrl.currentConfig.dutyPeriod / 10) * 6);
break;
case LED_DUTY_CYCLE_70:
delayTimer = ((pThis->ledCtrl.currentConfig.dutyPeriod / 10) * 7);
break;
case LED_DUTY_CYCLE_80:
delayTimer = ((pThis->ledCtrl.currentConfig.dutyPeriod / 10) * 8);
break;
case LED_DUTY_CYCLE_90:
delayTimer = ((pThis->ledCtrl.currentConfig.dutyPeriod / 10) * 9);
break;
// We shouldnt be here if duty cycle is off, so back to idle.
case LED_DUTY_CYCLE_OFF:
default:
GPIO.out_w1tc = LED_MASK;
delayTimer = 0;
fsmState = LEDSTATE_IDLE;
break;
}
break;
case LEDSTATE_BLINK_SPACE:
// Turn off LED.
GPIO.out_w1tc = LED_MASK;
// Calculate time to next MARK.
delayTimer = pThis->ledCtrl.currentConfig.dutyPeriod - delayTimer;
// Now add an interblink delay prior to next blink.
fsmState = LEDSTATE_BLINK_INTER;
break;
case LEDSTATE_BLINK_INTER:
// If we are in normal mode with a blink limit set and limit reached or in limited mode, then add an interblink delay as configured.
if((pThis->ledCtrl.currentConfig.mode == LED_MODE_BLINK && pThis->ledCtrl.currentConfig.maxBlinks > 0 && pThis->ledCtrl.blinkCnt >= pThis->ledCtrl.currentConfig.maxBlinks) ||
(pThis->ledCtrl.currentConfig.mode == LED_MODE_BLINK_ONESHOT))
{
// Interblink delay is given in milli-seconds, so multiply up and set delay.
delayTimer = pThis->ledCtrl.currentConfig.interPeriod * 1000;
// Reset blink counter to trigger next interperiod delay.
if(pThis->ledCtrl.currentConfig.mode == LED_MODE_BLINK)
pThis->ledCtrl.blinkCnt = 0;
}
// We return to IDLE to allow time for reconfiguration if requested.
fsmState = LEDSTATE_IDLE;
break;
// Unknown or not programmed state, return to IDLE.
default:
fsmState = LEDSTATE_IDLE;
break;
}
// If a new delay is requested, reset the value in the timer and start.
if(delayTimer > 0LL)
{
timer_set_counter_value(TIMER_GROUP_0, TIMER_1, 0LL);
timer_start(TIMER_GROUP_0, TIMER_1);
}
}
// Give the OS some time...
taskYIELD();
} while(fsmState != LEDSTATE_IDLE);
}
}
}
// Method to set the GPIO pin to be used for LED output.
void LED::ledInit(uint8_t ledPin)
{
// Initialise variables.
this->ledCtrl.currentConfig.valid = false;
this->ledCtrl.currentConfig.updated = false;
this->ledCtrl.currentConfig.mode = LED_MODE_OFF;
this->ledCtrl.currentConfig.dutyCycle = LED_DUTY_CYCLE_OFF;
this->ledCtrl.currentConfig.dutyPeriod = 0LL;
this->ledCtrl.currentConfig.interPeriod = 0LL;
this->ledCtrl.newConfig = this->ledCtrl.currentConfig;
// Store GPIO pin to which LED is connected.
this->ledCtrl.ledPin = ledPin;
// Configure a timer to be used for the LED blink rate.
timer_config_t timerConfig = {
.alarm_en = TIMER_ALARM_DIS, // No alarm, were not using interrupts as we are in a dedicated thread.
.counter_en = TIMER_PAUSE, // Timer paused until required.
.intr_type = TIMER_INTR_LEVEL, // No interrupts used.
.counter_dir = TIMER_COUNT_UP, // Timing a fixed period.
.auto_reload = TIMER_AUTORELOAD_DIS, // No need for auto reload, fixed time period.
.divider = 80 // 1Mhz operation giving 1uS resolution.
};
ESP_ERROR_CHECK(timer_init(TIMER_GROUP_0, TIMER_1, &timerConfig));
ESP_ERROR_CHECK(timer_set_counter_value(TIMER_GROUP_0, TIMER_1, 0));
// Setup mutex's.
ledCtrl.mutexInternal = xSemaphoreCreateMutex();
// Core 0 - Application
// LED control thread - dedicated thread to control the LED according to set mode.
ESP_LOGW("ledInit", "Starting LEDif thread...");
::xTaskCreatePinnedToCore(&this->ledInterface, "ledif", 4096, this, 0, &this->TaskLEDIF, 0);
}
// Constructor, basically initialise the Singleton interface and let the control thread loose.
LED::LED(uint32_t hwPin)
{
// Store the class name for later use, ie. NVS key access.
this->className = getClassName(__PRETTY_FUNCTION__);
// Configure the Power LED used for activity and user interaction. Initial state is ON until a keyboard is detected when it turns off and only blinks on keyboard activity.
ledInit(hwPin);
// Initial state, turn on LED to indicate LED control is working.
setLEDMode(LED::LED_MODE_ON, LED::LED_DUTY_CYCLE_OFF, 0, 0L, 0L);
}
// Basic constructor, do nothing!
LED::LED(void)
{
// Store the class name for later use, ie. NVS key access.
this->className = getClassName(__PRETTY_FUNCTION__);
}