Analog input refactoring and improvements

* Lazily update readout circuit simulation as soon as the pin
      changes
    * Always assume that "max resistance" means "connected to ground"
    * Minor accuracy improvements
This commit is contained in:
Christian Speckner 2017-07-22 00:35:55 +02:00
parent 706755ad7f
commit 0d5d3de420
22 changed files with 148 additions and 67 deletions

View File

@ -36,9 +36,6 @@ AmigaMouse::AmigaMouse(Jack jack, const Event& event, const System& system)
myTrakBallLeft = myTrakBallDown = myScanCountV = myScanCountH =
myCountV = myCountH = 0;
// Analog pins are never used by the trakball controller
myAnalogPinValue[Five] = myAnalogPinValue[Nine] = maximumResistance;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -36,9 +36,6 @@ AtariMouse::AtariMouse(Jack jack, const Event& event, const System& system)
myTrakBallLeft = myTrakBallDown = myScanCountV = myScanCountH =
myCountV = myCountH = 0;
// Analog pins are never used by the Atari ST mouse controller
myAnalogPinValue[Five] = myAnalogPinValue[Nine] = maximumResistance;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -39,8 +39,6 @@ AtariVox::AtariVox(Jack jack, const Event& event, const System& system,
myDigitalPinState[One] = myDigitalPinState[Two] =
myDigitalPinState[Three] = myDigitalPinState[Four] = true;
myAnalogPinValue[Five] = myAnalogPinValue[Nine] = maximumResistance;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -61,10 +61,14 @@ void BoosterGrip::update()
// The CBS Booster-grip has two more buttons on it. These buttons are
// connected to the inputs usually used by paddles.
myAnalogPinValue[Five] = (myEvent.get(myTriggerEvent) != 0) ?
minimumResistance : maximumResistance;
myAnalogPinValue[Nine] = (myEvent.get(myBoosterEvent) != 0) ?
minimumResistance : maximumResistance;
updateAnalogPin(
Five,
(myEvent.get(myTriggerEvent) != 0) ? minimumResistance : maximumResistance
);
updateAnalogPin(
Nine,
(myEvent.get(myBoosterEvent) != 0) ? minimumResistance : maximumResistance
);
// Axis events (usually generated by the Stelladaptor)
int xaxis = myEvent.get(myXAxisValue);
@ -117,7 +121,7 @@ void BoosterGrip::update()
if(myEvent.get(Event::MouseButtonLeftValue))
myDigitalPinState[Six] = false;
if(myEvent.get(Event::MouseButtonRightValue))
myAnalogPinValue[Nine] = minimumResistance;
updateAnalogPin(Nine, minimumResistance);
}
}

View File

@ -32,10 +32,10 @@ CompuMate::CompuMate(const Console& console, const Event& event,
myLeftController = make_ptr<CMControl>(*this, Controller::Left, event, system);
myRightController = make_ptr<CMControl>(*this, Controller::Right, event, system);
myLeftController->myAnalogPinValue[Controller::Nine] = Controller::maximumResistance;
myLeftController->myAnalogPinValue[Controller::Five] = Controller::minimumResistance;
myRightController->myAnalogPinValue[Controller::Nine] = Controller::minimumResistance;
myRightController->myAnalogPinValue[Controller::Five] = Controller::maximumResistance;
myLeftController->updateAnalogPin(Controller::Nine, Controller::maximumResistance);
myLeftController->updateAnalogPin(Controller::Five, Controller::minimumResistance);
myRightController->updateAnalogPin(Controller::Nine, Controller::minimumResistance);
myRightController->updateAnalogPin(Controller::Five, Controller::maximumResistance);
enableKeyHandling(false);
}
@ -61,6 +61,7 @@ void CompuMate::update()
Controller& lp = myConsole.leftController();
Controller& rp = myConsole.rightController();
lp.myAnalogPinValue[Controller::Nine] = Controller::maximumResistance;
lp.myAnalogPinValue[Controller::Five] = Controller::minimumResistance;
lp.myDigitalPinState[Controller::Six] = true;
@ -196,4 +197,14 @@ void CompuMate::update()
default:
break;
}
if (lp.myOnAnalogPinUpdateCallback) {
lp.myOnAnalogPinUpdateCallback(Controller::Five);
lp.myOnAnalogPinUpdateCallback(Controller::Nine);
}
if (rp.myOnAnalogPinUpdateCallback) {
rp.myOnAnalogPinUpdateCallback(Controller::Five);
rp.myOnAnalogPinUpdateCallback(Controller::Nine);
}
}

View File

@ -855,6 +855,8 @@ void Console::setControllers(const string& rommd5)
myLeftControl = std::move(rightC);
myRightControl = std::move(leftC);
}
myTIA->bindToControllers();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -26,7 +26,8 @@ Controller::Controller(Jack jack, const Event& event, const System& system,
: myJack(jack),
myEvent(event),
mySystem(system),
myType(type)
myType(type),
myOnAnalogPinUpdateCallback(0)
{
myDigitalPinState[One] =
myDigitalPinState[Two] =
@ -115,8 +116,18 @@ void Controller::set(DigitalPin pin, bool value)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Controller::set(AnalogPin pin, Int32 value)
{
updateAnalogPin(pin, value);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Controller::updateAnalogPin(AnalogPin pin, Int32 value)
{
myAnalogPinValue[pin] = value;
if (myOnAnalogPinUpdateCallback) {
myOnAnalogPinUpdateCallback(pin);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -22,6 +22,8 @@ class Controller;
class Event;
class System;
#include <functional>
#include "Serializable.hxx"
#include "bspf.hxx"
@ -92,6 +94,11 @@ class Controller : public Serializable
Paddles, SaveKey, TrakBall
};
/**
Callback type for analog pin updates
*/
using onAnalogPinUpdateCallback = std::function<void(AnalogPin)>;
public:
/**
Create a new controller plugged into the specified jack
@ -237,6 +244,13 @@ class Controller : public Serializable
*/
string name() const override { return myName; }
/**
Inject a callback to be notified on analog pin updates.
*/
void setOnAnalogPinUpdateCallback(onAnalogPinUpdateCallback callback) {
myOnAnalogPinUpdateCallback = callback;
}
public:
/// Constant which represents maximum resistance for analog pins
static constexpr Int32 maximumResistance = 0x7FFFFFFF;
@ -244,6 +258,9 @@ class Controller : public Serializable
/// Constant which represents minimum resistance for analog pins
static constexpr Int32 minimumResistance = 0x00000000;
protected:
void updateAnalogPin(AnalogPin, Int32 value);
protected:
/// Specifies which jack the controller is plugged in
const Jack myJack;
@ -263,6 +280,10 @@ class Controller : public Serializable
/// The boolean value on each digital pin
bool myDigitalPinState[5];
/// The callback that is dispatched whenver an analog pin has changed
onAnalogPinUpdateCallback myOnAnalogPinUpdateCallback;
private:
/// The analog value on each analog pin
Int32 myAnalogPinValue[2];

View File

@ -49,9 +49,6 @@ Driving::Driving(Jack jack, const Event& event, const System& system)
// Digital pins 3 and 4 are not connected
myDigitalPinState[Three] = myDigitalPinState[Four] = true;
// Analog pins are not connected
myAnalogPinValue[Five] = myAnalogPinValue[Nine] = maximumResistance;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -41,11 +41,6 @@ Genesis::Genesis(Jack jack, const Event& event, const System& system)
myFire1Event = Event::JoystickOneFire;
myFire2Event = Event::JoystickOneFire5;
}
// Analog pin 9 is not connected to this controller at all
// Analog pin 5 corresponds to button 'C' on the gamepad, and corresponds
// to the 'booster' button on a BoosterGrip controller
myAnalogPinValue[Five] = myAnalogPinValue[Nine] = maximumResistance;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -61,8 +56,10 @@ void Genesis::update()
// The Genesis has one more button (C) that can be read by the 2600
// However, it seems to work opposite to the BoosterGrip controller,
// in that the logic is inverted
myAnalogPinValue[Five] = (myEvent.get(myFire2Event) == 0) ?
minimumResistance : maximumResistance;
updateAnalogPin(
Five,
(myEvent.get(myFire2Event) == 0) ? minimumResistance : maximumResistance
);
// Mouse motion and button events
if(myControlID > -1)
@ -93,7 +90,7 @@ void Genesis::update()
if(myEvent.get(Event::MouseButtonLeftValue))
myDigitalPinState[Six] = false;
if(myEvent.get(Event::MouseButtonRightValue))
myAnalogPinValue[Five] = maximumResistance;
updateAnalogPin(Five, maximumResistance);
}
}

View File

@ -43,9 +43,6 @@ Joystick::Joystick(Jack jack, const Event& event, const System& system)
myXAxisValue = Event::SARightAxis0Value;
myYAxisValue = Event::SARightAxis1Value;
}
// Analog pins are never used by the joystick
myAnalogPinValue[Five] = myAnalogPinValue[Nine] = maximumResistance;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -61,32 +61,39 @@ void Keyboard::write(DigitalPin pin, bool value)
// Set defaults
myDigitalPinState[Six] = true;
myAnalogPinValue[Five] = minimumResistance;
myAnalogPinValue[Nine] = minimumResistance;
Int32 resistanceFive = MIN_RESISTANCE;
Int32 resistanceNine = MIN_RESISTANCE;
// Now scan the rows and columns
if(!myDigitalPinState[Four])
{
myDigitalPinState[Six] = (myEvent.get(myPoundEvent) == 0);
if(myEvent.get(myZeroEvent) != 0) myAnalogPinValue[Five] = maximumResistance;
if(myEvent.get(myStarEvent) != 0) myAnalogPinValue[Nine] = maximumResistance;
if(myEvent.get(myZeroEvent) != 0) resistanceFive = maximumResistance;
if(myEvent.get(myStarEvent) != 0) resistanceNine = maximumResistance;
}
if(!myDigitalPinState[Three])
{
myDigitalPinState[Six] = (myEvent.get(myNineEvent) == 0);
if(myEvent.get(myEightEvent) != 0) myAnalogPinValue[Five] = maximumResistance;
if(myEvent.get(mySevenEvent) != 0) myAnalogPinValue[Nine] = maximumResistance;
if(myEvent.get(myEightEvent) != 0) resistanceFive = maximumResistance;
if(myEvent.get(mySevenEvent) != 0) resistanceNine = maximumResistance;
}
if(!myDigitalPinState[Two])
{
myDigitalPinState[Six] = (myEvent.get(mySixEvent) == 0);
if(myEvent.get(myFiveEvent) != 0) myAnalogPinValue[Five] = maximumResistance;
if(myEvent.get(myFourEvent) != 0) myAnalogPinValue[Nine] = maximumResistance;
if(myEvent.get(myFiveEvent) != 0) resistanceFive = maximumResistance;
if(myEvent.get(myFourEvent) != 0) resistanceNine = maximumResistance;
}
if(!myDigitalPinState[One])
{
myDigitalPinState[Six] = (myEvent.get(myThreeEvent) == 0);
if(myEvent.get(myTwoEvent) != 0) myAnalogPinValue[Five] = maximumResistance;
if(myEvent.get(myOneEvent) != 0) myAnalogPinValue[Nine] = maximumResistance;
if(myEvent.get(myTwoEvent) != 0) resistanceFive = maximumResistance;
if(myEvent.get(myOneEvent) != 0) resistanceNine = maximumResistance;
}
if (resistanceFive != read(Five)) {
updateAnalogPin(Five, resistanceFive);
}
if (resistanceNine != read(Nine))
updateAnalogPin(Nine, resistanceNine);
}

View File

@ -65,6 +65,8 @@ class Keyboard : public Controller
mySevenEvent, myEightEvent, myNineEvent,
myStarEvent, myZeroEvent, myPoundEvent;
static constexpr Int32 MIN_RESISTANCE = 5600;
private:
// Following constructors and assignment operators not supported
Keyboard() = delete;

View File

@ -46,10 +46,6 @@ KidVid::KidVid(Jack jack, const Event& event, const System& system,
myGame = KVSMURFS; // Smurfs Save the Day
else
myEnabled = false;
// Analog pins are never used by the KidVid controller
// (at least not in this implementation)
myAnalogPinValue[Five] = myAnalogPinValue[Nine] = maximumResistance;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -17,6 +17,7 @@
#include <cassert>
#include <iostream>
#include <bitset>
#include "Console.hxx"
#include "Settings.hxx"

View File

@ -29,9 +29,6 @@ MindLink::MindLink(Jack jack, const Event& event, const System& system)
myDigitalPinState[Two] = true;
myDigitalPinState[Three] = true;
myDigitalPinState[Four] = true;
// Analog pins are never used by the MindLink
myAnalogPinValue[Five] = myAnalogPinValue[Nine] = maximumResistance;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -28,7 +28,8 @@ Paddles::Paddles(Jack jack, const Event& event, const System& system,
{
// We must start with minimum resistance; see commit
// 38b452e1a047a0dca38c5bcce7c271d40f76736e for more information
myAnalogPinValue[Nine] = myAnalogPinValue[Five] = minimumResistance;
updateAnalogPin(Five, minimumResistance);
updateAnalogPin(Nine, minimumResistance);
// The following logic reflects that mapping paddles to different
// devices can be extremely complex
@ -260,12 +261,12 @@ void Paddles::update()
int sa_yaxis = myEvent.get(myP1AxisValue);
if(abs(myLastAxisX - sa_xaxis) > 10)
{
myAnalogPinValue[Nine] = Int32(MAX_RESISTANCE * ((32767 - Int16(sa_xaxis)) / 65536.0));
updateAnalogPin(Nine, Int32(MAX_RESISTANCE * ((32767 - Int16(sa_xaxis)) / 65536.0)));
sa_changed = true;
}
if(abs(myLastAxisY - sa_yaxis) > 10)
{
myAnalogPinValue[Five] = Int32(MAX_RESISTANCE * ((32767 - Int16(sa_yaxis)) / 65536.0));
updateAnalogPin(Five, Int32(MAX_RESISTANCE * ((32767 - Int16(sa_yaxis)) / 65536.0)));
sa_changed = true;
}
myLastAxisX = sa_xaxis;
@ -352,11 +353,9 @@ void Paddles::update()
// Only change state if the charge has actually changed
if(myCharge[1] != myLastCharge[1])
myAnalogPinValue[Five] =
Int32(MAX_RESISTANCE * (myCharge[1] / float(TRIGMAX)));
updateAnalogPin(Five, Int32(MAX_RESISTANCE * (myCharge[1] / float(TRIGMAX))));
if(myCharge[0] != myLastCharge[0])
myAnalogPinValue[Nine] =
Int32(MAX_RESISTANCE * (myCharge[0] / float(TRIGMAX)));
updateAnalogPin(Nine, Int32(MAX_RESISTANCE * (myCharge[0] / float(TRIGMAX))));
myLastCharge[1] = myCharge[1];
myLastCharge[0] = myCharge[0];

View File

@ -27,7 +27,6 @@ SaveKey::SaveKey(Jack jack, const Event& event, const System& system,
myEEPROM = make_ptr<MT24LC256>(eepromfile, system);
myDigitalPinState[One] = myDigitalPinState[Two] = true;
myAnalogPinValue[Five] = myAnalogPinValue[Nine] = maximumResistance;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -38,9 +38,6 @@ TrakBall::TrakBall(Jack jack, const Event& event, const System& system)
myTrakBallLeft = myTrakBallDown = myScanCountV = myScanCountH =
myCountV = myCountH = 0;
// Analog pins are never used by the trakball controller
myAnalogPinValue[Five] = myAnalogPinValue[Nine] = maximumResistance;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -22,10 +22,10 @@
static constexpr double
C = 68e-9,
RPOT = 1e6,
R0 = 1.8e3,
R0 = 1.5e3,
USUPP = 5;
static constexpr double TRIPPOINT_LINES = 380;
static constexpr double TRIPPOINT_LINES = 379;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PaddleReader::PaddleReader()
@ -79,7 +79,16 @@ void PaddleReader::update(double value, double timestamp, ConsoleTiming consoleT
if (value != myValue) {
myValue = value;
updateCharge(timestamp);
if (myValue < 0) {
// value < 0 signifies either maximum resistance OR analog input connected to
// ground (keyboard controllers). As we have no way to tell these apart we just
// assume ground and discharge.
myU = 0;
myTimestamp = timestamp;
} else {
updateCharge(timestamp);
}
}
}

View File

@ -334,6 +334,45 @@ bool TIA::load(Serializer& in)
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void TIA::bindToControllers()
{
myConsole.leftController().setOnAnalogPinUpdateCallback(
[this] (Controller::AnalogPin pin) {
TIA& tia = mySystem->tia();
switch (pin) {
case Controller::AnalogPin::Five:
tia.updatePaddle(1);
break;
case Controller::AnalogPin::Nine:
tia.updatePaddle(0);
break;
}
}
);
myConsole.rightController().setOnAnalogPinUpdateCallback(
[this] (Controller::AnalogPin pin) {
TIA& tia = mySystem->tia();
switch (pin) {
case Controller::AnalogPin::Five:
tia.updatePaddle(3);
break;
case Controller::AnalogPin::Nine:
tia.updatePaddle(2);
break;
}
}
);
for (uInt8 i = 0; i < 4; i++)
updatePaddle(i);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt8 TIA::peek(uInt16 address)
{
@ -1032,9 +1071,6 @@ void TIA::onFrameStart()
{
myXAtRenderingStart = 0;
for (uInt8 i = 0; i < 4; i++)
updatePaddle(i);
// Check for colour-loss emulation
if (myColorLossEnabled)
{
@ -1452,7 +1488,7 @@ void TIA::updatePaddle(uInt8 idx)
}
myPaddleReaders[idx].update(
(resistance == Controller::maximumResistance ? -1 : double(resistance)) / Paddles::MAX_RESISTANCE,
(resistance == Controller::maximumResistance) ? -1 : (double(resistance) / Paddles::MAX_RESISTANCE),
myTimestamp,
consoleTiming()
);

View File

@ -37,6 +37,7 @@
#include "LatchedInput.hxx"
#include "PaddleReader.hxx"
#include "DelayQueueIterator.hxx"
#include "Control.hxx"
/**
This class is a device that emulates the Television Interface Adaptor
@ -146,6 +147,11 @@ class TIA : public Device
*/
void installDelegate(System& system, Device& device);
/**
Bind to controllers.
*/
void bindToControllers();
/**
The following are very similar to save() and load(), except they
do a 'deeper' save of the display data itself.