Added source code, MZ6500 module still not complete

This commit is contained in:
Philip Smart
2022-09-04 14:51:38 +01:00
parent 8bba3b0e92
commit 96a69cc86d
74 changed files with 30544 additions and 69 deletions

View File

@@ -1 +0,0 @@
../../sharpkey/main/Mouse.cpp

729
main/Mouse.cpp Normal file
View File

@@ -0,0 +1,729 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Name: Mouse.cpp
// Created: Mar 2022
// Version: v1.0
// Author(s): Philip Smart
// Description: PS/2 Mouse to Sharp Host Interface logic.
// This source file contains the singleton class containing logic to obtain
// PS/2 mouse data (position, keys etc), map them into Sharp compatible codes and
// transmit the data to the connected host.
//
// The whole application of which this class is a member, uses the Espressif Development
// environment with Arduino components.
//
// Credits:
// Copyright: (c) 2022 Philip Smart <philip.smart@net2net.org>
//
// History: Mar 2022 - Initial write.
// v1.01 May 2022 - Initial release version.
// v1.02 Jun 2022 - Updates to reflect changes realised in other modules due to addition of
// bluetooth and suspend logic due to NVS issues using both cores.
// Updates to reflect moving functionality into the HID and to support
// Bluetooth as a primary mouse or secondary mouse.
//
// 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 <bitset>
#include <iostream>
#include <sstream>
#include <functional>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/uart.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "Arduino.h"
#include "soc/timer_group_struct.h"
#include "soc/timer_group_reg.h"
#include "driver/timer.h"
#include "sdkconfig.h"
#include "Mouse.h"
// Tag for ESP main application logging.
#define MAINTAG "Mouse"
// Mouse Protocol
// --------------
//
//
// The Sharp (X68000/X1/MZ-2500/MZ-2800) mouse uses an asynchronous serial protocol over two wires (MSDATA/MSCTRL).
// The MSCTRL signal is an enable signal, idle state = HIGH, it goes low prior to transmission of data by at least 1mS and goes high after
// transmission of last bit by ~2.56mS.
// The MSDATA signal is a standard asynchronous signal, idle = HIGH, 1 start bit, 8 data bits, 2 stop bits @ 4800baud.
//
// Protocol:
// Idle State (MSDATA/MSCTRL) = High.
// Transmission: MSCTRL -> LOW
// 1ms delay
// MSDATA -> low, first start bit.
// 3 bytes transmitted in a <1xStart><8xdata><2xstop> format.
// MSDATA -> high
// 2.56ms delay.
// MSCTRL -> HIGH
// Data bytes: <CTRL><POS X><POS Y>
// CTRL = [7] - Mouse rolling forward when high, backward when low.
// [6]
// [5] - Mouse rolling left, right when low.
// [4]
// [3]
// [2]
// [1] - Right button pressed = HIGH.
// [0] - Left button pressed = HIGH.
// POS X [7:0] - X Position data.
// POS Y [7:0] - Y Position data.
// Method to realise the Sharp host Mouse protocol.
// This method uses Core 1 and it will hold it in a spinlock as necessary to ensure accurate timing.
// Mouse data is passed into the method via a direct object, using the FreeRTOS Queue creates a time lag resulting in the mouse data being out of sync with hand movement.
IRAM_ATTR void Mouse::hostInterface( void * pvParameters )
{
// Locals.
//
Mouse* pThis = (Mouse*)pvParameters; // Retrieve pointer to object in order to access data.
bool msctrlEdge = false;
uint8_t txBuf[4];
uint32_t MSCTRL_MASK;
uint32_t MSDATA_MASK;
#ifdef CONFIG_HOST_BITBANG_UART
int txPos;
int txCnt;
uint32_t shiftReg;
uint64_t delayTimer = 0LL;
uint64_t curTime = 0LL;
uint32_t bitCount = 0;
enum HOSTXMITSTATE {
FSM_IDLE = 0,
FSM_STARTXMIT = 1,
FSM_STARTBIT = 2,
FSM_DATA = 3,
FSM_STOP = 4,
FSM_ENDXMIT = 5
} state = FSM_IDLE;
#endif
// Initialise the MUTEX which prevents this core from being released to other tasks.
pThis->x1Mutex = portMUX_INITIALIZER_UNLOCKED;
if(pThis->hostControl.secondaryIf == false)
{
MSCTRL_MASK = (1 << CONFIG_HOST_KDB0);
MSDATA_MASK = (1 << CONFIG_HOST_KDB1);
} else
{
MSCTRL_MASK = (1 << CONFIG_HOST_KDB0);
MSDATA_MASK = (1 << CONFIG_HOST_KDI4);
}
gpio_config_t ioConf;
ioConf.intr_type = GPIO_INTR_DISABLE;
ioConf.mode = GPIO_MODE_INPUT;
ioConf.pull_down_en = GPIO_PULLDOWN_DISABLE;
ioConf.pull_up_en = GPIO_PULLUP_ENABLE;
// Both Hardware UART and bitbang need MSCTRL setting as an input.
if(pThis->hostControl.secondaryIf == false)
{
ioConf.pin_bit_mask = (1ULL<<CONFIG_HOST_KDB0);
gpio_config(&ioConf);
}
// Bitbang mode also needs MSDATA setting as an output.
#ifdef CONFIG_HOST_BITBANG_UART
ioConf.pull_up_en = GPIO_PULLUP_DISABLE;
ioConf.mode = GPIO_MODE_OUTPUT;
if(pThis->hostControl.secondaryIf == false)
{
ioConf.pin_bit_mask = (1ULL<<CONFIG_HOST_KDB1);
} else
{
ioConf.pin_bit_mask = (1ULL<<CONFIG_HOST_KDI4);
}
gpio_config(&ioConf);
// Set MSDATA to default state which is high.
GPIO.out_w1ts = MSDATA_MASK;
#endif
// Configure a timer to be used for the host mouse asynchronous protocol spacing with 1uS resolution. The default clock source is the APB running at 80MHz.
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_0, &timerConfig));
ESP_ERROR_CHECK(timer_set_counter_value(TIMER_GROUP_0, TIMER_0, 0));
// Sign on.
ESP_LOGW(MAINTAG, "Starting Host side Mouse thread.");
// Permanent loop, wait for an incoming message on the key to send queue, read it then transmit to the host, repeat!
for(;;)
{
#ifdef CONFIG_HOST_BITBANG_UART
// Get the current timer value, only run the FSM when the timer is idle.
timer_get_counter_value(TIMER_GROUP_0, TIMER_0, &curTime);
if((state == FSM_IDLE && pThis->hostControl.secondaryIf == false) || curTime >= delayTimer)
{
// Ensure the timer is stopped.
timer_pause(TIMER_GROUP_0, TIMER_0);
delayTimer = 0LL;
// Finite state machine to retrieve a key for transmission then serialise it according to the X1 protocol.
switch(state)
{
case FSM_IDLE:
// Yield if the suspend flag is set.
pThis->yield(0);
// Check stack space, report if it is getting low.
if(uxTaskGetStackHighWaterMark(NULL) < 1024)
{
ESP_LOGW(MAINTAG, "THREAD STACK SPACE(%d)\n",uxTaskGetStackHighWaterMark(NULL));
}
if(pThis->hostControl.secondaryIf == false)
{
// Detect high to low edge. On mouse primary mode the MSCTRL signal forces the tempo. On mouse secondary mode (operating in tandem to keyboard),
// the timer forces the tempo.
//
msctrlEdge = (REG_READ(GPIO_IN_REG) & MSCTRL_MASK) != 0 ? true : msctrlEdge;
}
// Wait for a window when MSCTRL goes low.
if(pThis->hostControl.secondaryIf == true || (msctrlEdge == true && (REG_READ(GPIO_IN_REG) & MSCTRL_MASK) == 0))
{
// Wait for incoming mouse movement message.
if(pThis->xmitMsg.valid)
{
txBuf[0] = (uint8_t)pThis->xmitMsg.status;
txBuf[1] = (uint8_t)pThis->xmitMsg.xPos;
txBuf[2] = (uint8_t)pThis->xmitMsg.yPos;
pThis->xmitMsg.valid = false; // Shouldnt be a race state here but consider a mutex if mouse gets out of sync.
txBuf[3] = 0x00;
txPos = 0;
txCnt = 3;
} else
{
// Sharp host protocol requires us to send zero change messages on a regular period regardless of new data.
txBuf[0] = 0x00;
txBuf[1] = 0x00;
txBuf[2] = 0x00;
txBuf[3] = 0x00;
txPos = 0;
txCnt = 3;
}
// Advance to first start bit.
state = FSM_STARTXMIT;
// Clear edge detect for next loop.
msctrlEdge = false;
}
break;
case FSM_STARTXMIT:
// Ensure all variables and states correct before entering serialisation.
GPIO.out_w1ts = MSDATA_MASK;
state = FSM_STARTBIT;
bitCount = 8;
shiftReg = txBuf[txPos++];
txCnt--;
// Create, initialise and hold a spinlock so the current core is bound to this one method.
portENTER_CRITICAL(&pThis->x1Mutex);
break;
case FSM_STARTBIT:
// Send out the start bit by bringing MSDATA low for 208us (4800 baud 1bit time period).
GPIO.out_w1tc = MSDATA_MASK;
delayTimer = BITBANG_UART_BIT_TIME;
state = FSM_DATA;
break;
case FSM_DATA:
if(bitCount > 0)
{
// Setup the bit on MSDATA
if(shiftReg & 0x00000001)
{
GPIO.out_w1ts = MSDATA_MASK;
} else
{
GPIO.out_w1tc = MSDATA_MASK;
}
// Shift the data to the next bit for transmission.
shiftReg = shiftReg >> 1;
// 1 bit period.
delayTimer = BITBANG_UART_BIT_TIME;
// 1 Less bit in frame.
bitCount--;
} else
{
state = FSM_STOP;
}
break;
case FSM_STOP:
// Send out the stop bit, 2 are needed so just adjust the time delay.
GPIO.out_w1ts = MSDATA_MASK;
delayTimer = BITBANG_UART_BIT_TIME * 2;
state = FSM_ENDXMIT;
break;
case FSM_ENDXMIT:
// End of critical timing loop, release the core so other tasks can run whilst we load up the next byte.
portEXIT_CRITICAL(&pThis->x1Mutex);
// Any more bytes to transmit, loop and send if there are.
if(txCnt > 0)
{
state = FSM_STARTXMIT;
} else
{
// Reset timer for next loop.
delayTimer = 20000UL;
state = FSM_IDLE;
}
break;
}
// If a new delay is requested, set the value into the timer and start.
if(delayTimer > 0LL)
{
timer_set_counter_value(TIMER_GROUP_0, TIMER_0, 0LL);
timer_start(TIMER_GROUP_0, TIMER_0);
}
}
#endif
#ifdef CONFIG_HOST_HW_UART
// Get the current timer value, we need to wait 20ms between transmissions.
timer_get_counter_value(TIMER_GROUP_0, TIMER_0, &curTime);
if(curTime >= delayTimer)
{
// Wait for a window when MSCTRL goes low.
if(pThis->hostControl.secondaryIf == true || (REG_READ(GPIO_IN_REG) & MSCTRL_MASK) == 0)
{
// Ensure the timer is stopped, initialise to 0 and restart.
timer_pause(TIMER_GROUP_0, TIMER_0);
delayTimer = 20000LL;
timer_set_counter_value(TIMER_GROUP_0, TIMER_0, 0LL);
timer_start(TIMER_GROUP_0, TIMER_0);
// Wait for incoming mouse movement message.
if(pThis->xmitMsg.valid)
{
txBuf[0] = (uint8_t)pThis->xmitMsg.status;
txBuf[1] = (uint8_t)pThis->xmitMsg.xPos;
txBuf[2] = (uint8_t)pThis->xmitMsg.yPos;
pThis->xmitMsg.valid = false; // Shouldnt be a race state here but consider a mutex if mouse gets out of sync.
txBuf[3] = 0x00;
txPos = 0;
txCnt = 3;
} else
{
// Sharp host protocol requires us to send zero change messages on a regular period regardless of new data.
txBuf[0] = 0x00;
txBuf[1] = 0x00;
txBuf[2] = 0x00;
txBuf[3] = 0x00;
txPos = 0;
txCnt = 3;
}
// Send the bytes and wait.
uart_write_bytes(pThis->hostControl.uartNum, (const char *)txBuf, 3);
// This method doesnt actually return after the last byte is transmitted, it returns well before, so we tack on a 10ms delay which is the width for 3 bytes at 4800 baud.
uart_wait_tx_done(pThis->hostControl.uartNum, 25000);
vTaskDelay(10);
}
// Check stack space, report if it is getting low.
if(uxTaskGetStackHighWaterMark(NULL) < 1024)
{
ESP_LOGW(MAPKEYTAG, "THREAD STACK SPACE(%d)\n",uxTaskGetStackHighWaterMark(NULL));
}
// Yield if the suspend flag is set.
pThis->yield(0);
}
#endif
// Logic to feed the watchdog if needed. Watchdog disabled in menuconfig but if enabled this will need to be used.
//TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; // write enable
//TIMERG0.wdt_feed=1; // feed dog
//TIMERG0.wdt_wprotect=0; // write protect
//TIMERG1.wdt_wprotect=TIMG_WDT_WKEY_VALUE; // write enable
//TIMERG1.wdt_feed=1; // feed dog
//TIMERG1.wdt_wprotect=0; // write protect
}
}
// Primary HID routine.
// This method is responsible for receiving HID (PS/2 or BT) mouse scan data and mapping it into Sharp compatible mouse data.
// The HID mouse data once received is mapped and pushed onto a FIFO queue for transmission to the host.
//
void Mouse::mouseReceiveData(HID::t_mouseMessageElement mouseMessage)
{
// Locals.
uint8_t status;
// Invert Y as the Sharp host is inverted compared to a PS/2 on the Y axis.
mouseMessage.yPos = -mouseMessage.yPos;
// Initialise the status flag, on the Sharp host it is <Y Overflow><Y Underflow><X Overflow><X Underflow><1><0><Right Button><Left Button>
status = (((mouseMessage.xPos >> 8) & 0x01) << 4) | (mouseMessage.status & 0x0F );
// Check bounds and set flags accordingly.
if(mouseMessage.xPos > 127)
{
mouseMessage.xPos = 127; // Maximum resolution of Sharp host X movement.
status |= (1UL << 4); // Set overflow bit.
}
if(mouseMessage.xPos < -128)
{
mouseMessage.xPos = -128; // Minimum resolution of Sharp host X movement.
status |= (1UL << 5); // Set underflow bit.
}
if(mouseMessage.yPos > 127)
{
mouseMessage.yPos = 127; // Maximum resolution of Sharp host Y movement.
status |= (1UL << 6); // Set overflow bit.
}
if(mouseMessage.yPos < -128)
{
mouseMessage.yPos = -128; // Minimum resolution of Sharp host Y movement.
status |= (1UL << 7); // Set underflow bit.
}
// Convert back to 8bit 2's compliment and store in the host message to the host thread.
xmitMsg.xPos = (int8_t)mouseMessage.xPos;
xmitMsg.yPos = (int8_t)mouseMessage.yPos;
xmitMsg.status = status;
xmitMsg.wheel = mouseMessage.wheel;
xmitMsg.valid = true;
return;
}
// A method to return the Type of data for a given column in the KeyMap table.
//
void Mouse::getMouseConfigTypes(std::vector<std::string>& typeList)
{
// Add the types.
//
typeList.push_back(HID_MOUSE_HOST_SCALING_TYPE);
typeList.push_back(HID_MOUSE_SCALING_TYPE);
typeList.push_back(HID_MOUSE_RESOLUTION_TYPE);
typeList.push_back(HID_MOUSE_SAMPLING_TYPE);
return;
}
// Method to return a list of key:value entries for a given config category. This represents the
// feature which can be selected and the value it uses. Features can be combined by ORing the values
// together.
bool Mouse::getMouseSelectList(std::vector<std::pair<std::string, int>>& selectList, std::string option)
{
// Locals.
//
bool result = true;
// Build up a map, depending on the list required, of name to value. This list can then be used
// by a user front end to select an option based on a name and return its value.
if(option.compare(HID_MOUSE_HOST_SCALING_TYPE) == 0)
{
selectList.push_back(std::make_pair("ACTIVE", mouseConfig.host.scaling));
selectList.push_back(std::make_pair(HID_MOUSE_HOST_SCALING_1_1_NAME, HID::HID_MOUSE_HOST_SCALING_1_1));
selectList.push_back(std::make_pair(HID_MOUSE_HOST_SCALING_1_2_NAME, HID::HID_MOUSE_HOST_SCALING_1_2));
selectList.push_back(std::make_pair(HID_MOUSE_HOST_SCALING_1_3_NAME, HID::HID_MOUSE_HOST_SCALING_1_3));
selectList.push_back(std::make_pair(HID_MOUSE_HOST_SCALING_1_4_NAME, HID::HID_MOUSE_HOST_SCALING_1_4));
selectList.push_back(std::make_pair(HID_MOUSE_HOST_SCALING_1_5_NAME, HID::HID_MOUSE_HOST_SCALING_1_5));
}
else if(option.compare(HID_MOUSE_SCALING_TYPE) == 0)
{
selectList.push_back(std::make_pair("ACTIVE", mouseConfig.mouse.scaling));
selectList.push_back(std::make_pair(HID_MOUSE_SCALING_1_1_NAME, HID::HID_MOUSE_SCALING_1_1));
selectList.push_back(std::make_pair(HID_MOUSE_SCALING_2_1_NAME, HID::HID_MOUSE_SCALING_2_1));
}
else if(option.compare(HID_MOUSE_RESOLUTION_TYPE) == 0)
{
selectList.push_back(std::make_pair("ACTIVE", mouseConfig.mouse.resolution));
selectList.push_back(std::make_pair(HID_MOUSE_RESOLUTION_1_1_NAME, HID::HID_MOUSE_RESOLUTION_1_1));
selectList.push_back(std::make_pair(HID_MOUSE_RESOLUTION_1_2_NAME, HID::HID_MOUSE_RESOLUTION_1_2));
selectList.push_back(std::make_pair(HID_MOUSE_RESOLUTION_1_4_NAME, HID::HID_MOUSE_RESOLUTION_1_4));
selectList.push_back(std::make_pair(HID_MOUSE_RESOLUTION_1_8_NAME, HID::HID_MOUSE_RESOLUTION_1_8));
}
else if(option.compare(HID_MOUSE_SAMPLING_TYPE) == 0)
{
selectList.push_back(std::make_pair("ACTIVE", mouseConfig.mouse.sampleRate));
selectList.push_back(std::make_pair(HID_MOUSE_SAMPLE_RATE_10_NAME, HID::HID_MOUSE_SAMPLE_RATE_10));
selectList.push_back(std::make_pair(HID_MOUSE_SAMPLE_RATE_20_NAME, HID::HID_MOUSE_SAMPLE_RATE_20));
selectList.push_back(std::make_pair(HID_MOUSE_SAMPLE_RATE_40_NAME, HID::HID_MOUSE_SAMPLE_RATE_40));
selectList.push_back(std::make_pair(HID_MOUSE_SAMPLE_RATE_60_NAME, HID::HID_MOUSE_SAMPLE_RATE_60));
selectList.push_back(std::make_pair(HID_MOUSE_SAMPLE_RATE_80_NAME, HID::HID_MOUSE_SAMPLE_RATE_80));
selectList.push_back(std::make_pair(HID_MOUSE_SAMPLE_RATE_100_NAME, HID::HID_MOUSE_SAMPLE_RATE_100));
selectList.push_back(std::make_pair(HID_MOUSE_SAMPLE_RATE_200_NAME, HID::HID_MOUSE_SAMPLE_RATE_200));
} else
{
// Not found!
result = false;
}
// Return result, false if the option not found, true otherwise.
//
return(result);
}
// Public method to set the mouse configuration parameters.
//
bool Mouse::setMouseConfigValue(std::string paramName, std::string paramValue)
{
// Locals.
//
bool dataError = false;
int value(0);
std::stringstream testVal(paramValue);
// Match the parameter name to a known mouse parameter, type and data check the parameter value and assign to the config accordingly.
if(paramName.compare(HID_MOUSE_HOST_SCALING_TYPE) == 0)
{
// Exception handling is disabled, stringstream is used to catch bad input.
dataError = (static_cast<bool>(testVal >> value) ? false : true);
if(dataError == false)
{
if(value >= to_underlying(HID::HID_MOUSE_HOST_SCALING_1_1) && value <= to_underlying(HID::HID_MOUSE_HOST_SCALING_1_5))
{
mouseConfig.host.scaling = static_cast<HID::HID_MOUSE_HOST_SCALING>(value);
hid->setMouseHostScaling(mouseConfig.host.scaling);
} else
{
dataError = true;
}
}
}
if(paramName.compare(HID_MOUSE_SCALING_TYPE) == 0)
{
dataError = (static_cast<bool>(testVal >> value) ? false : true);
if(dataError == false)
{
if(value >= to_underlying(HID::HID_MOUSE_SCALING_1_1) && value <= to_underlying(HID::HID_MOUSE_SCALING_2_1))
{
mouseConfig.mouse.scaling = static_cast<HID::HID_MOUSE_SCALING>(value);
hid->setMouseScaling(mouseConfig.mouse.scaling);
} else
{
dataError = true;
}
}
}
if(paramName.compare(HID_MOUSE_RESOLUTION_TYPE) == 0)
{
dataError = (static_cast<bool>(testVal >> value) ? false : true);
if(dataError == false)
{
if(value >= to_underlying(HID::HID_MOUSE_RESOLUTION_1_1) && value <= to_underlying(HID::HID_MOUSE_RESOLUTION_1_8))
{
mouseConfig.mouse.resolution = static_cast<HID::HID_MOUSE_RESOLUTION>(value);
hid->setMouseResolution(mouseConfig.mouse.resolution);
} else
{
dataError = true;
}
}
}
if(paramName.compare(HID_MOUSE_SAMPLING_TYPE) == 0)
{
dataError = (static_cast<bool>(testVal >> value) ? false : true);
if(dataError == false)
{
if(value >= to_underlying(HID::HID_MOUSE_SAMPLE_RATE_10) && value <= to_underlying(HID::HID_MOUSE_SAMPLE_RATE_200))
{
mouseConfig.mouse.sampleRate = static_cast<HID::HID_MOUSE_SAMPLING>(value);
hid->setMouseSampleRate(mouseConfig.mouse.sampleRate);
} else
{
dataError = true;
}
}
}
// Error = true, success = false.
return(dataError);
}
// Method to save (persist) the configuration into NVS RAM.
bool Mouse::persistConfig(void)
{
// Locals.
bool result = true;
// Persist the data for next time.
if(nvs->persistData(getClassName(__PRETTY_FUNCTION__), &this->mouseConfig, sizeof(t_mouseConfig)) == false)
{
ESP_LOGW(MAINTAG, "Persisting Mouse configuration data failed, check NVS setup.\n");
result = false;
}
// Few other updates so make a commit here to ensure data is flushed and written.
else if(nvs->commitData() == false)
{
ESP_LOGW(MAINTAG, "NVS Commit writes operation failed, some previous writes may not persist in future power cycles.");
}
// Request persistence in the HID module.
result |= hid->persistConfig();
// Error = false, success = true.
return(result);
}
// Initialisation routine. Start two threads, one to handle the incoming PS/2 mouse data and map it, the second to handle the host interface.
void Mouse::init(uint32_t ifMode, NVS *hdlNVS, LED *hdlLED, HID *hdlHID)
{
// Initialise control variables.
#ifdef CONFIG_HOST_HW_UART
hostControl.uartNum = UART_NUM_2;
hostControl.uartBufferSize = 256;
hostControl.uartQueueSize = 10;
#endif
// Initialise the basic components.
init(hdlNVS, hdlHID);
// Invoke the prototype init which initialises common variables and devices shared by all subclass.
KeyInterface::init(getClassName(__PRETTY_FUNCTION__), hdlNVS, hdlLED, hdlHID, ifMode);
// There are two build possibilities, hardware UART and BITBANG. I initially coded using hardware but whilst trying to find a bug, wrote a bitbang
// technique and both are fit for purpose, so enabling either yields the same result.
#ifdef CONFIG_HOST_HW_UART
// Prepare the UART to be used for communications with the Sharp host.
// The Sharp host Mouse uses an Asynchronous protocol with 2 stop bits no parity 4800 baud.
//
uart_config_t uartConfig = {
.baud_rate = 4800,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_2,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.rx_flow_ctrl_thresh = 122,
.source_clk = UART_SCLK_APB,
};
// Configure UART parameters and pin assignments, software flow control, not RTS/CTS.
// The mouse only uses a Tx line, the MSCTRL line is used as a gate signal, so assign the Rx line to an unused pin.
ESP_ERROR_CHECK(uart_param_config(hostControl.uartNum, &uartConfig));
ESP_ERROR_CHECK(uart_set_pin(hostControl.uartNum, CONFIG_HOST_KDB1, CONFIG_HOST_KDB2, -1, -1));
// Install UART driver. Use RX/TX buffers without event queue.
ESP_ERROR_CHECK(uart_driver_install(hostControl.uartNum, hostControl.uartBufferSize, hostControl.uartBufferSize, 0, NULL, 0));
#endif
// Register the streaming callback for the mouse, this will receive data, process it and send to the hostInterface for transmission to the host.
hid->setDataCallback(&Mouse::mouseReceiveData, this);
// Create a task pinned to core 1 which will fulfill the Sharp Mouse host interface. This task has the highest priority
// and it will also hold spinlock and manipulate the watchdog to ensure a scan cycle timing can be met. This means
// all other tasks running on Core 1 will suspend as needed. The HID mouse controller will be serviced with core 0.
//
// Core 1 - Sharp Mouse Host Interface
ESP_LOGW(MAINTAG, "Starting mouseIf thread...");
::xTaskCreatePinnedToCore(&this->hostInterface, "mouseIf", 4096, this, 25, &this->TaskHostIF, 1);
vTaskDelay(500);
}
// Initialisation routine without hardware.
void Mouse::init(NVS *hdlNVS, HID *hdlHID)
{
// Invoke the prototype init which initialises common variables and devices shared by all subclass.
KeyInterface::init(getClassName(__PRETTY_FUNCTION__), hdlNVS, hdlHID);
// Retrieve configuration, if it doesnt exist, set defaults.
//
if(nvs->retrieveData(getClassName(__PRETTY_FUNCTION__), &this->mouseConfig, sizeof(t_mouseConfig)) == false)
{
ESP_LOGW(MAINTAG, "Mouse configuration set to default, no valid config in NVS found.");
mouseConfig.mouse.resolution= HID::HID_MOUSE_RESOLUTION_1_8;
mouseConfig.mouse.scaling = HID::HID_MOUSE_SCALING_1_1;
mouseConfig.mouse.sampleRate= HID::HID_MOUSE_SAMPLE_RATE_60;
mouseConfig.host.scaling = HID::HID_MOUSE_HOST_SCALING_1_2;
// Persist the data for next time.
if(nvs->persistData(getClassName(__PRETTY_FUNCTION__), &this->mouseConfig, sizeof(t_mouseConfig)) == false)
{
ESP_LOGW(MAINTAG, "Persisting Default Mouse configuration data failed, check NVS setup.\n");
}
// Few other updates so make a commit here to ensure data is flushed and written.
else if(nvs->commitData() == false)
{
ESP_LOGW(MAINTAG, "NVS Commit writes operation failed, some previous writes may not persist in future power cycles.");
}
}
}
// Constructor, basically initialise the Singleton interface and let the threads loose.
Mouse::Mouse(uint32_t ifMode, NVS *hdlNVS, LED *hdlLED, HID *hdlHID)
{
// Operating in uni-mode.
hostControl.secondaryIf = false;
// Initialise the interface
init(ifMode, hdlNVS, hdlLED, hdlHID);
}
// Constructor, basic initialisation without hardware.
Mouse::Mouse(NVS *hdlNVS, HID *hdlHID)
{
// Operating in uni-mode.
hostControl.secondaryIf = false;
// Initialise the interface
init(hdlNVS, hdlHID);
}
// Constructor for use when mouse operates in tandem with a keyboard.
Mouse::Mouse(uint32_t ifMode, NVS *hdlNVS, LED *hdlLED, HID *hdlHID, bool secondaryIf)
{
// The interface can act in primary mode, ie. sole interface or secondary mode where it acts in tandem to a keyboard host. Slight processing differences occur
// in secondary mode, for example, the pin used to output mouse data differs.
hostControl.secondaryIf = secondaryIf;
// Initialise the interface
init(ifMode, hdlNVS, hdlLED, hdlHID);
}
// Constructor, used for version reporting so no hardware is initialised.
Mouse::Mouse(void)
{
return;
}
// Destructor - only ever called when the class is used for version reporting.
Mouse::~Mouse(void)
{
return;
}