Would something like this mock code work, without the code stopping once one condition is met at 30 trials? At this point, I just need the four conditions to be shuffled, equal is trials (30), but not the same order for each time I run the code.
Min = 1
max = 120
threshold1 = 30
threshold2 = 60 // Not really needed but it can help to visualize
threshold3 = 90
loop 120 times
rand = generate a random integer between min and max
if rand > threshold3
Do action 1
max = max - 1
else if rand > threshold2
Do action 2
threshold3 = threshold3 - 1
max = max - 1
else if rand > threshold1
Do action 3
threshold1 = threshold1 + 1
min = min + 1
else
Do action 4
min = min + 1
Ok, this code makes sense. The array gets shuffled, stored in the same array, and then you have it so it can be printed. How would you implement this into my original code to pull each count of the array? I think my problem is seeing how you can 'walk' through the array to get a condition to apply for each of the 120 trials.
You walk through arrays by maintaining an index, just an integer variable that starts at 0, to which you add 1 to access the next card, and which signals the end of the deck when kncremting the index results in 120.
void loop()
{
static int cardIndex;
int nextCard = cardArray[cardIndex];
Serial.print("the next card is ");
Serail.println(nextCard);
cardIndex += 1; // next card next time unlessβ¦
if (cardIndex >= 120) {
Serial.println(" out of cards ");
Serial.flush();
while (1); // forever. die here.
}
}
So you see the loop runs freely, every time through you can handle the next card, and when you run out of cards you can decide what to do about that better than dying.
When I asked about who wrote the code, I was referring to that late obtuse fill and shuffle code, not you original code what was aiming for the real goal. I haven't even looked at that to see what the larger problem is, was focused laser like on the shuffling thing.
well instead of printing the "card" name just test the sequence value cardNames[sequence[i]]) it will be 0,1,2 or 3 if you have 4 "cards". so a switch() / case on this could let you do what you want
or you could use an array of action functions that you call as you traverse the sequence
// declare our action functions
void actionLL();
void actionLR();
void actionRL();
void actionRR();
// define an array of function pointers
void (*actionFunctions[])() = {actionLL, actionLR, actionRL, actionRR};
// count of actions
constexpr size_t actionCount = sizeof actionFunctions / sizeof * actionFunctions;
// how many draws for each action
constexpr size_t drawCount = 30;
// the array defining the sequence
byte sequence[drawCount * actionCount]; // holding the index of the actions
void fisherYatesShuffle(byte arr[], size_t size) {
randomSeed(analogRead(A0));
for (size_t i = size - 1; i > 0; --i) {
size_t j = random(i + 1);
byte temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
void actionLL() {
Serial.println("action LL");
}
void actionLR() {
Serial.println("action LR");
}
void actionRL() {
Serial.println("action RL");
}
void actionRR() {
Serial.println("action RR");
}
void action() {
Serial.println("Handling sequence:");
for (size_t actionIndex = 0; actionIndex < drawCount * actionCount; ++actionIndex) {
Serial.print(actionIndex); Serial.write('\t');
actionFunctions[sequence[actionIndex]](); // call the functions
}
}
void generateSequence() {
// initialize the array
for (size_t actionIndex = 0; actionIndex < actionCount; actionIndex++)
for (size_t i = 0; i < drawCount; i++)
sequence[actionIndex * drawCount + i] = actionIndex;
// shuffle the array
fisherYatesShuffle(sequence, drawCount * actionCount);
}
void setup() {
Serial.begin(115200);
generateSequence();
action();
}
void loop() {}
// the array defining the sequence
byte sequence[drawCount * actionCount]; // holding the index of the actions
The byte array sequence holds the indices of the actions, here it is just holding 0, 1, 2 or 3 which are enough for everyone to exploit as a selection of one of the four things.
Which in the later @J-M-L code is then used as an index into a table of functions to call, one for each of 0..3:
actionFunctions[ sequence[actionIndex] ](); // call the functions
I spaced it out so you can see the inside reference to an element of sequence.
A different function is available for each of the four kinds of things.
If you can exploit just the number 0..3 in your code, forget about the table of pointers to functions. That does qualify as not beginner-ish.
Just use the number you find at sequence[K] to do the Kth of the 120 available.
Yeah, I get that but if I am trying to pull from the sequence for this code below, what should the output sequence be called after shuffling? The code is giving me this error
Compilation error: expected primary-expression before ']' token
for the lines of code like this in the larger code I have at the end.
if (sequence[] == 0 || sequence[] == 1) // 0 is LL; 1 is LH
if (count < NUM_TRIALS)
{
delay(500);
int SIDE = random(lower_limit, upper_limit);
int LOHI = random(zero, max);
if (sequence[] == 0 || sequence[] == 1) // 0 is LL; 1 is LH
{
lower_limit++;
Serial.print("0,");
Serial.print(count + 1);
Serial.print(",");
Serial.print("Left");
Serial.print(",");
float input_delay = random(0, 5000);
delay(input_delay);
time = micros();
if (sequence[] == 0 || sequence[] == 2) { // 0 is LL; 2 is RL
zero++;
analogWrite(viboutL, 64);
Serial.print("Low");
Serial.print(",");
LL = LL + 1;
}
else if (sequence[] == 1 || sequence[] == 3) // 1 is LH; 3 is RH
{
max--;
analogWrite(viboutL, 255);
Serial.print("High");
Serial.print(",");
LH = LH + 1;
}
it would be really helpful if you would take a step back a describe in details the expected requirements for the code from a user perspective, what should happen ?
you said
The user is completing 120 trials that are a mix of the four conditions (LL, RL, LR, RR). They are responding to a high or low frequency. They respond with the left button when they receive a low frequency and respond with the right button for a high frequency. The frequencies can occur on either the left or right forearm (where the vibration motors are placed). I need the four conditions to be random but equal (for statistical analysis at the end of the experiment). Thus the total trials should be 120 and then 30 for each condition (divisible by 4). I think the issue is the code is running into a wall by choosing a random number that meets the conditions and then maxes out somehow.
β how LL LR RL RR match this? does one mean left or right arm and the other high or low freq?
β how long does the user have to press the answer button ?
Can do! I tried the putting the count for the index for the array sequence that was shuffled but it is not liking that.
Participants have a left and right vibration motor attached to their forearms. Participants have to press a button as fast as they can based on the frequency of the vibration. The vibration can be low or high and can appear on either the left or right forearm. Participant then press the left button when it is a low frequency, when it occurs on either the left OR right forearm. And participants press the right button when it is a high frequency, no matter if it occurs on either the left OR right forearm. I need reaction time in seconds and there to be a random delay between trials, as well as keep track of each trial number the response is made based on the four combinations. The output would look like this in excel from the data streamer.
Previously, we only had the vibration occur on either the left or right forearm with the same frequency but adding this second condition of low and high frequency is a pain to get it to be puedo-random. The four conditions need to be mixed between the 120 trials but have equal number of conditions which is why 30 was chosen.
You would get better help if you shared the code. Either make a small sketch that is not liked in the same way, or share the hole thing. Don't worry about using up a few thousand bytes. It really is best to analyze compiler errors or logic faults in a full context.
If it is indeed compiler errors, copy and paste the first few dozen lines.
I will be trying to build your project virtually, the additional or repeated explanation is good.
I wonder however about the importance of getting exactly 30 each every 120 trials. If subjects are going to be, um, subjected to multiple runs, it seems like just getting close enough to 0.25 each would suffice.
On the other hand, it's your trip, so.
This will be easy code, relatively, as it is the same process every time through the loop() for 120 times, then some reporting.
How short to how long a random delay are you planning for? Of course for now those should be expressed as adjustable limits - during testing you may find you'll want to turn down things that make testing take longer than they will when finally tested and deployed.
A random time will be waited out
A randomized vibration will be made
User input will be awaited for (what happens if too long goes by? wait forever?)
The trial #, stimulus, frequency, response, time, and delay will be (printed for now)
Rinse and repeat until 120
Is the delay printed on line N the time before line N + 1 was conducted, or is it the time that was delayed before line N?
There will be no neeed for any loops or delays within this pattern. This is where the finite state machine FSM will come in - every loop pass will be in some state, and decide based on the passge of time or the appearance of user input (or both) what to do and whether the state (step) needs to be bumped along to the next step, or if it is time to bump to the next of the 120.
I am compelled to mention also the IPO model for process control. A good fit to this goal and a good fit with FSMs.
here is an example on how you could deal with it. I'm using a finite state machine to walk through the different states. (Here is a small introduction to the topic: Yet another Finite State Machine introduction)
you can try the code here
you start a session by clicking one of the button, the number of steps per possibility is set in
constexpr size_t stepCountPerStimulus = 3; // change to 30 if you want 30 x 4 steps in the sequence
I used 3 to make it short to test (12 stimuli).
once you are done you get a report formatted for the forum, it looks like this
Trial
Location
Prompt
Answer
Reaction time(ms)
Delay(ms)
1
Right
High
Low
783
1534
2
Left
Low
Low
353
1847
3
Left
High
Low
297
668
4
Right
High
Low
281
790
5
Right
High
Low
292
752
6
Right
Low
Low
365
740
7
Left
Low
Low
360
855
8
Left
High
Low
318
1767
9
Left
High
Low
295
1250
10
Left
Low
Low
290
1980
11
Right
Low
Low
341
1779
12
Right
Low
Low
290
1225
(I kept pressing the same answer)
I replaced the motors by two buzzers but you can use your motors (just need to modify the startAction() and stopAction() functions)
click to see the code
/* ============================================
code is placed under the MIT license
Copyright (c) 2024 J-M-L
For the Arduino Forum : https://forum.arduino.cc/u/j-m-l
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.
===============================================
*/
#include <Toggle.h> // https://github.com/Dlloydev/Toggle
constexpr size_t stepCountPerStimulus = 3; // change to 30 if you want 30 x 4 steps in the sequence
constexpr unsigned int lowFrequency = 300;
constexpr unsigned int highFrequency = 1000;
constexpr unsigned int minDelay = 500;
constexpr unsigned int maxDelay = 2000;
constexpr byte rightArmBuzzerPin = 5;
constexpr byte leftArmBuzzerPin = 6;
constexpr byte highFreqButtonPin = 2;
constexpr byte lowFreqButtonPin = 3;
Toggle highFreqButton;
Toggle lowFreqButton;
enum : byte {LEFT_ARM, RIGHT_ARM};
enum : byte {LOW_FREQ, HIGH_FREQ};
enum : byte {LOW_FREQ_BUTTON, HIGH_FREQ_BUTTON, NO_RESPONSE};
enum : byte {IDLE, DELAY, ACTION, NEXT, SEQUENCE_TERMINATED,} state = IDLE;
struct __attribute__ ((packed)) Step { // 7 bytes
byte location: 1;
byte frequency: 1;
byte response: 2;
uint16_t initialDelay;
uint32_t reactionTime;
};
constexpr size_t sequenceLength = 4 * stepCountPerStimulus;
Step sequence[sequenceLength]; // 840 bytes
size_t sequencePosition;
uint32_t startTime;
void fisherYatesShuffle(Step arr[], size_t size) {
for (size_t i = size - 1; i > 0; --i) {
size_t j = random(i + 1);
Step temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
void generateReport() {
Serial.println(F("|Trial|Location|Prompt|Answer|Reaction time(ms)|Delay(ms)|"));
Serial.println(F("|-|-|-|-|-|-|"));
for (size_t i = 0; i < sequenceLength; i++) {
Serial.write('|'); Serial.print(i + 1);
Serial.write('|'); Serial.print(sequence[i].location == LEFT_ARM ? F("Left") : F("Right"));
Serial.write('|'); Serial.print(sequence[i].frequency == LOW_FREQ ? F("Low") : F("High"));
Serial.write('|'); Serial.print(sequence[i].response == LOW_FREQ_BUTTON ? F("Low") : F("High"));
Serial.write('|'); Serial.print(sequence[i].reactionTime);
Serial.write('|'); Serial.print(sequence[i].initialDelay);
Serial.println('|');
}
Serial.println();
}
void generateSequence() {
// initialize the array
randomSeed(analogRead(A0));
for (size_t i = 0; i < sequenceLength; i++) {
if (i >= 3 * stepCountPerStimulus) {
sequence[i].location = LEFT_ARM;
sequence[i].frequency = LOW_FREQ;
}
else if (i >= 2 * stepCountPerStimulus) {
sequence[i].location = LEFT_ARM;
sequence[i].frequency = HIGH_FREQ;
}
else if (i >= stepCountPerStimulus) {
sequence[i].location = RIGHT_ARM;
sequence[i].frequency = LOW_FREQ;
}
else {
sequence[i].location = RIGHT_ARM;
sequence[i].frequency = HIGH_FREQ;
}
sequence[i].initialDelay = random(minDelay, maxDelay + 1);
sequence[i].response = NO_RESPONSE;
sequence[i].reactionTime = 0;
}
// shuffle the array
fisherYatesShuffle(sequence, sequenceLength);
}
void startAction(byte loc, byte freq) {
byte buzzerPin = (loc == LEFT_ARM) ? leftArmBuzzerPin : rightArmBuzzerPin;
unsigned int frequency = (freq == LOW_FREQ) ? lowFrequency : highFrequency;
tone(buzzerPin, frequency);
}
void stopAction(byte loc, byte freq) {
byte buzzerPin = (loc == LEFT_ARM) ? leftArmBuzzerPin : rightArmBuzzerPin;
noTone(buzzerPin);
}
void setup() {
highFreqButton.begin(highFreqButtonPin);
lowFreqButton.begin(lowFreqButtonPin);
pinMode(rightArmBuzzerPin, OUTPUT);
pinMode(leftArmBuzzerPin, OUTPUT);
Serial.begin(115200);
Serial.println(F("β Press a button to get started."));
}
void loop() {
highFreqButton.poll();
lowFreqButton.poll();
switch (state) {
case IDLE:
if (highFreqButton.onPress() || lowFreqButton.onPress()) {
// one of the two button is pressed, we start
generateSequence();
sequencePosition = 0;
startTime = millis();
state = DELAY;
}
break;
case DELAY:
if (millis() - startTime >= sequence[sequencePosition].initialDelay) {
// we waited long enough, action
startAction(sequence[sequencePosition].location, sequence[sequencePosition].frequency);
startTime = millis();
state = ACTION;
}
break;
case ACTION:
if (highFreqButton.onPress()) {
sequence[sequencePosition].reactionTime = millis() - startTime;
sequence[sequencePosition].response = HIGH_FREQ_BUTTON;
state = NEXT;
}
else if (lowFreqButton.onPress()) {
sequence[sequencePosition].reactionTime = millis() - startTime;
sequence[sequencePosition].response = LOW_FREQ_BUTTON;
state = NEXT;
}
break;
case NEXT:
stopAction(sequence[sequencePosition].location, sequence[sequencePosition].frequency);
sequencePosition++;
if (sequencePosition >= sequenceLength) {
state = SEQUENCE_TERMINATED;
} else {
startTime = millis();
state = DELAY;
}
break;
case SEQUENCE_TERMINATED:
generateReport();
state = IDLE;
Serial.println(F("β Press a button to start a new session."));
break;
}
}