Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interfacing with the GBFans Sound Board #15

Closed
ajquick opened this issue Jul 12, 2022 · 10 comments · Fixed by #16
Closed

Interfacing with the GBFans Sound Board #15

ajquick opened this issue Jul 12, 2022 · 10 comments · Fixed by #16

Comments

@ajquick
Copy link
Contributor

ajquick commented Jul 12, 2022

I was not anticipating this, but it appears the signals sent from the GBFans sound board are not as straight forward as I had anticipated. I was originally operating under this assumption:

IDC Pin 7: Pack Change (Gnd = Change), has a weak 10K to 100K pullup to +5VDC
IDC Pin 8: Vent (Gnd = Vent), has a weak 10K to 100K pullup to +5VDC
IDC Pin 9: Fire (Gnd = Fire), has a weak 10K to 100K pullup to +5VDC
IDC Pin 10: PowerUp/Down (Gnd = PowerDown), has a weak 10K to 100K pullup to +5VDC

These are the inputs on the Powercell light kit for standalone operation.

However when a GBFans sound board is used, there is a much different signaling scheme used!

Start up: Pin 10 is high, all others low.
Idle: Pin 7 is high, all others low.
Firing: Pin 7, 8 and 10 are high, Pin 9 is low.
Venting: Pin 7, 9 and 10 are high, Pin 8 is low.
Shutdown: Pin 7, 8 and 9 are high. Pin 10 is low.
Change state: Pin 7 and 10 are high, Pin 8 and 9 is low. (I am assuming this one).
Off: Pins 7-10 are low.

This is actually quite useful as it helps with the timings. Venting is actually signaled when we previously thought it wasn't. Overheating however is not.

I believe it would be good to allow for both GBFans Sound board connections and the standalone mode to be supported. This can be done by seeing how long the enable pin is held high for. If it is held for more than ~3 seconds we know we're in standalone mode.

I believe a class handling the inputs can get created now.

@ajquick
Copy link
Contributor Author

ajquick commented Jul 12, 2022

Spongeface, the creator of the GBFans Sound Board was generous to explain exactly how the signal schema works from the sound board to the light kit. As it turns out, it is much more in-depth than I had expected.

Each of the 4 wires is a 4 bit (0-15) signal that expresses each state the sound board is in and forwards that data to the light kit.

Pin 7 = MSB
Pin 10 = LSB

Dec Pin 7 Pin 8 Pin 9 Pin 10 Description
0 0 0 0 0 Off
1 0 0 0 1 Power Up
2 0 0 1 0 Red Cyclotron Color
3 0 0 1 1 Green Cyclotron Color
4 0 1 0 0 Blue Cyclotron Color
5 0 1 0 1 Orange Cyclotron Color
6 0 1 1 0 Automatic Venting Mode
7 0 1 1 1 Change Cyclotron Colors*
8 1 0 0 0 Normal Operation (Idle)**
9 1 0 0 1 Venting Strobe Only
10 1 0 1 0 Test Mode
11 1 0 1 1 Venting Action with Interaction**
12 1 1 0 0 Fire (movie version)
13 1 1 0 1 Fire (TVG, overheat version)**
14 1 1 1 0 Power Down**
15 1 1 1 1 Delay -> Power Up -> Idle*

* Only available in standalone mode
**Available in both standalone and controlled mode.

Upon boot, if all pins are pulled low, it is assumed that the board is under the control of the sound board.

If all pins are pulled high (through pull up resistors) it is assumed the board is in standalone mode.

@ajquick
Copy link
Contributor Author

ajquick commented Jul 17, 2022

Updated with a basic control scheme in this branch: https://github.com/gbfans/afterlife-lightkit/tree/feature/effects-with-ramping-colors-controls

@prodestrian
Copy link
Collaborator

Well, that looks more complex than I was expecting. Your example Control code looks similar to what I'd think we could do.
I don't know if the GBFans soundboard already handles debouncing of the buttons/switches, so maybe we don't need to include the Bounce2 library anymore?

I'm still a little fuzzy on bit operations but I did see some example code for it. This is entirely untested obviously, but maybe it's something like this?

const int CONTROL_PINS[4] = {7,8,9,10};
byte result;
for ( int count = 0, count <= 3, count++ ) {
    bitWrite(result, count, digitalRead( CONTROL_PINS[count] ));
}
# Do something with result

Because if we're interacting with the GBFans board, we don't really need to name the input pins anymore, they're just Pins 7, 8, 9, 10. So we just need to scan all four of them and work out the current state.

The difficult part is that I don't know how the above states behave. For example, I assume at some point the "Red Cyclotron Color" state will be set, but how is this different to "Normal Operation (Idle)"? Does it quickly flash "Red Cyclotron" then change to "Normal Operation"? If you change to "Green Cyclotron", does it flash that once to change modes then stay on "Normal Operation" too? Or how many milliseconds does it pulse the Cyclotron color change signal?

We probably need to write a little test script which dumps the State to the Serial console so we can record exactly what the Soundboard is doing during all modes. Then we can document this in the code to ensure consistent behaviour.

@ajquick
Copy link
Contributor Author

ajquick commented Jul 17, 2022

That method of reading the states looks like it may work. I was originally trying to use bytes as well but switched to an INT when I saw that you can't use bytes in Switch / Case. Though I could have made it a large If / Else ladder.

That being said, I think we do want the aspects of the Bounce2 library for two reasons:

  1. We mainly want to look for the change in state and Bounce2 makes that easy, yes we can do what Bounce2 does and look for the change in the byte data, but it's already a working library and fairly compact. Is it worth copying code from Bounce2 to only have exactly what we need it to do?

  2. We do want to consider the possibility that this kit can work in standalone mode as well. Standalone mode would expect that the end user is using switches to command the lights on and off. Now it is entirely possible that we could use byte mode for controlled mode and bounce2 for standalone mode, but perhaps its better to reuse the variables between each control mode?

The difficult part is that I don't know how the above states behave. For example, I assume at some point the "Red Cyclotron Color" state will be set, but how is this different to "Normal Operation (Idle)"? Does it quickly flash "Red Cyclotron" then change to "Normal Operation"? If you change to "Green Cyclotron", does it flash that once to change modes then stay on "Normal Operation" too? Or how many milliseconds does it pulse the Cyclotron color change signal?

I think we will need to brainstorm that part a bit more.

There are the following other modes:

  • Pack Mode (Red Cyclotron)
  • Slime Mode (Green Cyclotron)
  • Stasis Mode (Blue Cyclotron)
  • Meson Mode (Orange Cyclotron)

Each of these modes will need have their own Idle, Firing, Venting, Shutdown.. etc. configurable animations, see my example here: #10. So I believe by sending the Byte command to trigger Green Cyclotron mode for example, it would be a temporary command sent and the pack would continue in whatever mode it was already in (idle, firing, venting.. etc).

It isn't going to be a priority for the main code, but as long as everything is kept as customizable as possible, it should be no problem to expand later on. The most basic implementation will simply do a Color::changeColor(Lights.Cyclotron(),CRGB::Green) or whatever the command may be.

@ajquick
Copy link
Contributor Author

ajquick commented Jul 28, 2022

Added a branch called "Button-Testing".

This is just a simplified version of the Control.cpp code I am working on in my branch.

I have it connected to the GBFans kit and ran it through a few things, which produced an output like this at the serial port:

Ready...
Power Up
Idle Operation
Firing with change in speed.
Idle Operation
Fire in TVG Mode with Overheating
Idle Operation
Firing with no effects.
Fire in TVG Mode with Overheating
Orange Cyclotron
Firing with Automatic Venting
Test Mode
Firing with change in speed.
Idle Operation
Power Down
Firing with Automatic Venting
Pack Off
Power Up
Vent Strobe
Idle Operation
Firing with change in speed.
Idle Operation
Firing with change in speed.
Idle Operation
Fire in TVG Mode with Overheating
Idle Operation
Power Down
Red Cyclotron
Pack Off

There are some strange abnormalities occurring. For example it was switching between the different firing modes when I believe it shouldn't have been. It also triggered test mode momentarily. I believe the debounce time of 5ms (or whatever it is set to) may actually be too low. It appears to be catching a few signals as it is changing to another one.

@prodestrian
Copy link
Collaborator

I was playing with my own prototype here and I was getting unreliable readings too.
First thing is I think we need to look at is:

Upon boot, if all pins are pulled low, it is assumed that the board is under the control of the sound board.
If all pins are pulled high (through pull up resistors) it is assumed the board is in standalone mode.

So, we should probably enable the INPUT_PULLUP resistors on those pins.
With nothing connected the board should go into Standalone mode, this would do it.
If I tie all 4 pins to GND I should get Controlled Mode.

Secondly, I think Bounce2 is potentially causing us problems here, it's possible that there's an issue with the millisecond timers that are being used (these are separate instances, after all). You got a random Test Mode (1-0-1-0) and then it was immediately followed by Firing with change in speed (1-0-1-1). So it's possible the final bit (Pin 10) hadn't yet debounced, so for up to 5ms you were seeing 10 instead of 11.

We should ditch Bounce2 and handle the debouncing ourselves. We can use a single timer and just digitalRead() all 4 pins at once to determine the current value, and compare it to the last value. If more than the debounce interval has passed, we proceed.

I think I can write up a test sketch for this by building on what you have already.

I also think we should keep this test sketch (and any other useful tests we have) as part of the main repository, so that we can continue using them for troubleshooting in the future. It's definitely easier than having to run multiple working copies of the code, every time I need to troubleshoot my wiring.

@ajquick
Copy link
Contributor Author

ajquick commented Aug 21, 2022

You are correct the inputs need to be INPUT_PULLUP on everything but the FIRE_BTN_PIN, which does not have an internal pullup.

On V3 of the board, the pullups are built onto the board, all others need to have INPUT_PULLUP.. and having it activated on V3 will not be an issue, it will just draw a little bit more power.

When INPUT_PULLUP is enabled, you will be able to successfully see controlled mode or standalone mode at boot.

Bounce 2 may need to go. I will let you look into doing that as it is a little bit beyond my understanding at the moment. That being said, since these are only lights having a few seconds entering TEST_MODE (which isn't supported anyways) won't be an issue. There would just be a flash of different color lights before continuing on to the next correct mode.

I just pushed a bit of code I've been changing a little bit here: https://github.com/gbfans/afterlife-lightkit/tree/feature/effects-with-ramping-colors-controls/SOFTWARE/AfterlifeLightKit

I was just starting to implement the JSON config and switching between modes and states.

States = Classic, Slime, Stasis, Meson, Party(?)
Modes = Inactive, Startup, Idle, Firing, Overheating, Venting, Restart, Shutdown

I would like to have the ability to switch from Inactive -> Start Up -> Idle and have it read the configuration and change patterns each step of the way.

@prodestrian
Copy link
Collaborator

prodestrian commented Aug 21, 2022

@ajquick OK, I've rewritten the button-testing control class completely and I'm getting much more useful data out of it now.
I haven't had time to build a fake GBFans soundboard using an Arduino, and I can't find four pushbuttons in my workshop, so right now I'm literally just moving wires from GND to 5V on my breadboard to control whether the inputs are pressed or not.

Here's my code: 042397c

EDIT: I've added another commit to streamline this and remove a few unnecessary variables, the latest version has been pushed to the button-testing branch:
https://github.com/gbfans/afterlife-lightkit/tree/button-testing/SOFTWARE/Buttontest

Uploading this sketch should spit out some useful Serial data.

On boot, it should tell you which mode you're running (Standalone or Controlled). If I leave all 4 pins HIGH, I get Standalone mode. Anything else, I get Controlled mode. So this part seems to work now that we've got rid of Bounce2.
Note that it doesn't do any debouncing on first load, I'm setting the current input State to 99 at boot and then overwriting this when Controls::init() is first called. This saves us from having to add any delays to work around debouncing. You can set your debouncing milliseconds to 500ms if you like and it will still give you the correct state at first boot.

I removed the references to different pin functions (ENABLE, FIRE, VENT, CHANGE) because these don't seem to apply anymore? Instead I'm just listing the pins in a constant (corresponding to IDC pins 7,8,9,10; or MSB=>LSB):
const int INPUT_PINS[4] = {D5,D6,D0,D7};

I was wasting too much time trying to get bitWrite working so for now I'm just adding up the pin values from 0-15 manually.

  int inputState = 0;
  if (digitalRead( INPUT_PINS[0] ) == HIGH) {
    inputState += 1;
  }
  if (digitalRead( INPUT_PINS[1] ) == HIGH) {
    inputState += 2;
  }
  if (digitalRead( INPUT_PINS[2] ) == HIGH) {
    inputState += 4;
  }
  if (digitalRead( INPUT_PINS[3] ) == HIGH) {
    inputState += 8;
  }

But after all that it does work. If I set the third pin HIGH, I get a value of 4 (STATE_BLUE_CYCLOTRON). If I also set the second pin HIGH, I get a value of 6 (STATE_AUTOMATIC_VENTING). I've tried various combinations and they seem to work, so I believe when you upload my sketch and connect a GBFans board, you should get the correct States written to the Serial monitor.

We won't be doing any of the "business logic" inside the Controls class, so I've added some more methods to help us with this. Here's an example:

void loop() {
  controls.update();
  if (controls.changed()) {
    Serial.print("CHANGED - Old: ");
    Serial.print(controls.getPreviousState());
    Serial.print(", New: ");
    Serial.println(controls.getCurrentState());
  }

So we can tell:

  • Have any of the controls changed (ie did we get a new signal from the soundboard?)
  • What is the current State?
  • What was the previous State?

I'm not sure if there's anything else we need here if the sketch works for you. Feel free to make adjustments if you need to (eg INPUT_DEBOUNCE_DELAY), I don't have the hardware so I'm basically just working from Spongeface's info posted above, you might have to make the final changes. If you're still seeing weird behaviour due to TEST_MODE we can add an exclusion for that.

Once it all seems to be working, we'll merge the branch, close this ticket, and move onto the next objective.

@ajquick
Copy link
Contributor Author

ajquick commented Dec 12, 2022

Can confirm. It works and there are no errant signals detected that I can see. Here is an example of turning on the power, firing, hitting the vent a few times:

Boot Mode: Controlled
CHANGED - Old: 1, New: 7
CHANGED - Old: 7, New: 0
CHANGED - Old: 0, New: 8
CHANGED - Old: 8, New: 1
CHANGED - Old: 1, New: 11
CHANGED - Old: 11, New: 1
CHANGED - Old: 1, New: 9
CHANGED - Old: 9, New: 1
CHANGED - Old: 1, New: 9
CHANGED - Old: 9, New: 1
CHANGED - Old: 1, New: 9
CHANGED - Old: 9, New: 1
CHANGED - Old: 1, New: 11
CHANGED - Old: 11, New: 1
CHANGED - Old: 1, New: 7
CHANGED - Old: 7, New: 0

I removed the references to different pin functions (ENABLE, FIRE, VENT, CHANGE) because these don't seem to apply anymore?

As long as the inputs are correctly mapped while in "Standalone" mode, you should be okay. That mode would have you connect your switches directly to the header. (Which basically no one will do.)

@prodestrian
Copy link
Collaborator

I have completed this via #16 and merged it in so I can proceed with the remaining functionality.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants