1
0
mirror of https://github.com/preble/libpinproc synced 2026-02-24 18:25:23 +01:00
Files
libpinproc/examples/pinproctest/pinproctest.cpp

455 lines
15 KiB
C++

/*
* The MIT License
* Copyright (c) 2009 Gerry Stellenberg, Adam Preble
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* pinproctest.cpp
* libpinproc
*/
#include <string.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <cmath>
#include "../../include/pinproc.h" // Include libpinproc's header.
#include <fstream>
#include <yaml-cpp/yaml.h>
#define kFlippersSection "PRFlippers"
#define kBumpersSection "PRBumpers"
#define kCoilsSection "PRCoils"
#define kSwitchesSection "PRSwitches"
#define kFlipperPulseTime (34) // 34 ms
#define kBumperPulseTime (25) // 25 ms
#define kDMDColumns (128)
#define kDMDRows (32)
#define kDMDSubFrames (4) // For color depth of 16
/** Demonstration of the custom logging callback. */
void TestLogger(const char *text)
{
fprintf(stderr, "TEST: %s", text);
}
PRResult LoadConfiguration(YAML::Node& yamlDoc, const char *yamlFilePath)
{
try
{
std::ifstream fin(yamlFilePath);
if (fin.is_open() == false)
{
fprintf(stderr, "YAML file not found: %s\n", yamlFilePath);
return kPRFailure;
}
YAML::Parser parser(fin);
while(parser)
{
parser.GetNextDocument(yamlDoc);
}
}
catch (YAML::ParserException& ex)
{
fprintf(stderr, "YAML parse error at line=%d col=%d: %s\n", ex.line, ex.column, ex.msg.c_str());
return kPRFailure;
}
catch (YAML::RepresentationException& ex)
{
fprintf(stderr, "YAML representation error at line=%d col=%d: %s\n", ex.line, ex.column, ex.msg.c_str());
return kPRFailure;
}
catch (...)
{
fprintf(stderr, "Unexpected exception while parsing YAML config.\n");
return kPRFailure;
}
return kPRSuccess;
}
void ConfigureDrivers(PRHandle proc, PRMachineType machineType, YAML::Node& yamlDoc)
{
int i;
int mappedDriverGroupEnableIndex[kPRDriverGroupsMax];
int mappedWPCDriverGroupEnableIndex[] = {0, 0, 0, 0, 0, 4, 3, 2, 1, 5, 7, 7, 7, 7, 7, 7, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0};
int rowEnableIndex1;
int rowEnableIndex0;
bool tickleSternWatchdog;
bool globalPolarity;
bool activeLowMatrixRows;
int driverLoopTime;
int watchdogResetTime;
int slowGroupTime;
switch (machineType)
{
case kPRMachineWPC: {
memcpy(mappedDriverGroupEnableIndex,mappedWPCDriverGroupEnableIndex, sizeof(mappedDriverGroupEnableIndex));
rowEnableIndex1 = 6; // Unused in WPC
rowEnableIndex0 = 6;
tickleSternWatchdog = false;
globalPolarity = false;
activeLowMatrixRows = true;
driverLoopTime = 4; // milliseconds
watchdogResetTime = 1000; // milliseconds
slowGroupTime = driverLoopTime * 100; // microseconds
break;
}
}
PRDriverGlobalConfig globals;
globals.enableOutputs = false;
globals.globalPolarity = globalPolarity;
globals.useClear = false;
globals.strobeStartSelect = false;
globals.startStrobeTime = driverLoopTime; // milliseconds per output loop
globals.matrixRowEnableIndex1 = rowEnableIndex1;
globals.matrixRowEnableIndex0 = rowEnableIndex0;
globals.activeLowMatrixRows = activeLowMatrixRows;
globals.tickleSternWatchdog = tickleSternWatchdog;
globals.encodeEnables = false;
globals.watchdogExpired = false;
globals.watchdogEnable = true;
globals.watchdogResetTime = watchdogResetTime;
// We want to start up safely, so we'll update the global driver config twice.
// When we toggle enableOutputs like this P-ROC will reset the polarity:
// Enable now without the outputs enabled:
PRDriverUpdateGlobalConfig(proc, &globals);
// Now enable the outputs: (TODO: Why?)
globals.enableOutputs = true;
PRDriverUpdateGlobalConfig(proc, &globals);
// Configure the groups. Each group corresponds to 8 consecutive drivers, starting
// with driver #32. The following 6 groups are configured for coils/flashlamps.
PRDriverGroupConfig group;
for (i = 4; i < 10; i++)
{
PRDriverGetGroupConfig(proc, i, &group);
group.slowTime = 0;
group.enableIndex = mappedDriverGroupEnableIndex[i];
group.rowActivateIndex = 0;
group.rowEnableSelect = 0;
group.matrixed = false;
group.polarity = false;
group.active = 1;
group.disableStrobeAfter = false;
PRDriverUpdateGroupConfig(proc, &group);
}
// The following 8 groups are configured for the feature lamp matrix.
for (i = 10; i < 18; i++) {
PRDriverGetGroupConfig(proc, i, &group);
group.slowTime = slowGroupTime;
group.enableIndex = mappedDriverGroupEnableIndex[i];
group.rowActivateIndex = i - 10;
group.rowEnableSelect = 0;
group.matrixed = 1;
group.polarity = 0;
group.active = 1;
group.disableStrobeAfter = 1;
PRDriverUpdateGroupConfig(proc, &group);
}
}
void ConfigureSwitches(PRHandle proc, YAML::Node& yamlDoc)
{
int i;
// Configure switch controller registers (if the defaults aren't acceptable)
PRSwitchConfig switchConfig;
switchConfig.clear = false;
switchConfig.directMatrixScanLoopTime = 2; // milliseconds
switchConfig.pulsesBeforeCheckingRX = 10;
switchConfig.inactivePulsesAfterBurst = 12;
switchConfig.pulsesPerBurst = 6;
switchConfig.pulseHalfPeriodTime = 13; // milliseconds
PRSwitchUpdateConfig(proc, &switchConfig);
// Configures rules to notify the host for every debounced switch event.
for (i = 0; i <= kPRSwitchPhysicalLast; i++)
{
PRSwitchRule sw;
sw.notifyHost = true;
PRSwitchUpdateRule(proc, i, kPREventTypeSwitchClosedDebounced, &sw, NULL, 0);
PRSwitchUpdateRule(proc, i, kPREventTypeSwitchOpenDebounced, &sw, NULL, 0);
}
}
void ConfigureWPCFlipperSwitchRule (PRHandle proc, int swNum, int mainCoilNum, int holdCoilNum, int pulseTime)
{
const int numDriverRules = 2;
PRDriverState drivers[numDriverRules];
PRSwitchRule sw;
// Flipper on rules
PRDriverGetState(proc, mainCoilNum, &drivers[0]);
PRDriverStatePulse(&drivers[0],pulseTime); // Pulse coil for 34ms.
PRDriverGetState(proc, holdCoilNum, &drivers[1]);
PRDriverStatePulse(&drivers[1],0); // Turn on indefintely (set pulse for 0ms)
sw.notifyHost = false;
PRSwitchUpdateRule(proc,swNum, kPREventTypeSwitchClosedNondebounced, &sw, drivers, numDriverRules);
// Flipper off rules
PRDriverGetState(proc, mainCoilNum, &drivers[0]);
PRDriverStateDisable(&drivers[0]); // Disable main coil
PRDriverGetState(proc, holdCoilNum, &drivers[1]);
PRDriverStateDisable(&drivers[1]); // Disable hold coil
sw.notifyHost = false;
PRSwitchUpdateRule(proc,swNum, kPREventTypeSwitchOpenNondebounced, &sw, drivers, numDriverRules);
}
void ConfigureBumperRule (PRHandle proc, int swNum, int coilNum, int pulseTime)
{
const int numDriverRules = 1;
PRDriverState drivers[numDriverRules];
PRSwitchRule sw;
// Lower Right Flipper On
PRDriverGetState(proc, coilNum, &drivers[0]);
PRDriverStatePulse(&drivers[0],pulseTime); // Pulse coil for 34ms.
sw.notifyHost = false;
PRSwitchUpdateRule(proc,swNum, kPREventTypeSwitchClosedNondebounced, &sw, drivers, numDriverRules);
}
void ConfigureSwitchRules(PRHandle proc, YAML::Node& yamlDoc)
{
// WPC Flippers
const YAML::Node& flippers = yamlDoc[kFlippersSection];
for (YAML::Iterator flippersIt = flippers.begin(); flippersIt != flippers.end(); ++flippersIt)
{
int swNum, coilMain, coilHold;
std::string flipperName;
*flippersIt >> flipperName;
yamlDoc[kSwitchesSection][flipperName] >> swNum;
yamlDoc[kCoilsSection][flipperName + "Main"] >> coilMain;
yamlDoc[kCoilsSection][flipperName + "Hold"] >> coilHold;
ConfigureWPCFlipperSwitchRule (proc, swNum, coilMain, coilHold, kFlipperPulseTime);
}
const YAML::Node& bumpers = yamlDoc[kBumpersSection];
for (YAML::Iterator bumpersIt = bumpers.begin(); bumpersIt != bumpers.end(); ++bumpersIt)
{
int swNum, coilNum;
// WPC Slingshots
std::string bumperName;
*bumpersIt >> bumperName;
yamlDoc[kSwitchesSection][bumperName] >> swNum;
yamlDoc[kCoilsSection][bumperName] >> coilNum;
ConfigureBumperRule (proc, swNum, coilNum, kBumperPulseTime);
}
}
void ConfigureDMD(PRHandle proc)
{
int i;
// Create the structure that holds the DMD settings
PRDMDConfig dmdConfig;
memset(&dmdConfig, 0x0, sizeof(dmdConfig));
dmdConfig.numRows = kDMDRows;
dmdConfig.numColumns = kDMDColumns;
dmdConfig.numSubFrames = kDMDSubFrames;
for (i = 0; i < kDMDSubFrames; i++)
{
dmdConfig.rclkLowCycles[i] = 15;
dmdConfig.latchHighCycles[i] = 15;
dmdConfig.dotclkHalfPeriod[i] = 1;
}
dmdConfig.deHighCycles[0] = 250;
dmdConfig.deHighCycles[1] = 400;
dmdConfig.deHighCycles[2] = 180;
dmdConfig.deHighCycles[3] = 800;
PRDMDUpdateConfig(proc, &dmdConfig);
}
// Display a simple pattern to verify DMD functionality.
// 16 diagonal lines will rotate to the right. Every two rows will get brighter,
// starting with dim dots at the top.
void UpdateDots( unsigned char * dots, unsigned int dotOffset )
{
int i,j,k,color,mappedColor,loopCtr,byte_shifter;
const int rate_reduction_divisor = 1;
loopCtr = dotOffset/rate_reduction_divisor;
color = pow(2,kDMDSubFrames) - 1;
byte_shifter = 0x80;
// Slow it down just a tad
if (dotOffset%rate_reduction_divisor == 0)
{
// Set up byte_shifter to rotate pattern to the right.
byte_shifter = pow(2,(loopCtr%8));
// Clear the DMD dots every time the rotation occurs
memset(dots,0,((kDMDColumns*kDMDRows)/8)*kDMDSubFrames);
// Loop through all of the rows
for (i = kDMDRows - 1; i >= 0; i--)
{
// Map the color index to the DMD's physical color map
int mappedColors[] = {0, 2, 8, 10, 1, 3, 9, 11, 4, 6, 12, 14, 5, 7, 13, 15};
mappedColor = mappedColors[color];
// Loop through each of 16 bytes in a row
for (j = 0; j < kDMDColumns / 8; j++)
{
// Loop through each subframe
for (k = 0; k < kDMDSubFrames; k++)
{
// Turn on the byte in each sub-frame that's enabled
// active for the color code.
if ((mappedColor >> k) & 1 == 1)
dots[k*(kDMDColumns*kDMDRows/8)+((i%kDMDRows)*(kDMDColumns / 8))+j] = byte_shifter;
}
}
// Determine where to change the color in order to progress from row 0 = color 0
// to the last row being the last color.
if (i % (int)((kDMDRows/pow(2,kDMDSubFrames))) == 0) color--;
if (byte_shifter == 1) byte_shifter = 0x80;
else byte_shifter = byte_shifter >> 1;
}
}
}
bool runLoopRun = true;
void RunLoop(PRHandle proc)
{
const int maxEvents = 16;
PREvent events[maxEvents];
// Create dot array using an array of bytes. Each byte holds 8 dots. Need
// space for 4 sub-frames of 128/32 dots.
unsigned char dots[4*((128*32)/8)];
unsigned int dotOffset = 0;
while (runLoopRun)
{
PRDriverWatchdogTickle(proc);
// Create a dot pattern to test the DMD
UpdateDots(dots,dotOffset++);
PRDMDDraw(proc,dots);
int numEvents = PRGetEvents(proc, events, maxEvents);
for (int i = 0; i < numEvents; i++)
{
PREvent *event = &events[i];
const char *stateText = "Unknown";
switch (event->type)
{
case kPREventTypeSwitchOpenDebounced: stateText = "open"; break;
case kPREventTypeSwitchClosedDebounced: stateText = "closed"; break;
case kPREventTypeSwitchOpenNondebounced: stateText = "open(ndb)"; break;
case kPREventTypeSwitchClosedNondebounced: stateText = "closed(ndb)"; break;
}
printf("switch % 3d: %s\n", event->value, stateText);
}
usleep(10*1000); // Sleep for 10ms so we aren't pegging the CPU.
}
}
void sigint(int)
{
runLoopRun = false;
signal(SIGINT, SIG_DFL); // Re-install the default signal handler.
printf("Exiting...\n");
}
int main(int argc, const char **argv)
{
// Set a signal handler so that we can exit gracefully on Ctrl-C:
signal(SIGINT, sigint);
if (argc < 2)
{
fprintf(stderr, "Usage: %s <yaml machine description>\n", argv[0]);
return 1;
}
const char *yamlFilename = argv[1];
// Assign a custom logging callback to demonstrate capturing log information from P-ROC:
PRLogSetCallback(TestLogger);
YAML::Node yamlDoc;
LoadConfiguration(yamlDoc, yamlFilename);
PRMachineType machineType = kPRMachineInvalid;
std::string machineTypeString;
yamlDoc["PRGame"]["machineType"] >> machineTypeString;
if (machineTypeString == "wpc")
machineType = kPRMachineWPC;
else if(machineTypeString == "stern")
machineType = kPRMachineStern;
else
{
fprintf(stderr, "Unknown machine type: %s\n", machineTypeString.c_str());
return 1;
}
// Finally instantiate the P-ROC device:
PRHandle proc = PRCreate(machineType);
if (proc == kPRHandleInvalid)
return 1;
ConfigureDMD(proc);
ConfigureSwitches(proc, yamlDoc); // Notify host for all debounced switch events.
ConfigureSwitchRules(proc, yamlDoc); // Flippers, slingshots
// Make Drivers the last thing to configure so watchdog doesn't expire
// before the RunLoop begins.
ConfigureDrivers(proc, machineType, yamlDoc);
printf("Running. Hit Ctrl-C to exit.\n");
// Pulse a coil for testing purposes.
//PRDriverPulse(proc, 53, 100);
// Schedule a feature lamp for testing purposes.
//PRDriverSchedule(proc, 80, 0xFF00FF00, 0, 0);
// Pitter-patter a feature lamp for testing purposes.
//PRDriverPatter(proc, 84, 127, 127, 0);
RunLoop(proc);
// Clean up P-ROC.
printf("Disabling P-ROC drivers and switch rules...\n");
PRReset(proc, kPRResetFlagUpdateDevice); // Reset the device structs and write them into the device.
// Destroy the P-ROC device handle:
PRDelete(proc);
proc = kPRHandleInvalid;
return 0;
}