Currently you are shifting in the button states and directly shifting them out again to the LEDs. Thus the state of the LEDs follows exactly the state of the buttons.
As jsotola explained in the comments, you need to seperate what you get as input (buttons and serial) from what you set as output. For this you need 1 or more variables to hold the state of the LEDs (your output) and the state of each input (for example each button). The last is what you have in the copy
variable. It resembles the state of the buttons input. Now create a new variable byte ledstates;
. The shift_out()
function has to be called with this variable instead of with copy
.
Now that input and output are seperated, you can look at the desired behavior for the input. As 1 button press (pressing and releasing) should toggle the state of the corresponding LED, you have to look for a LOW-to-HIGH transition. A common way is to save the current state of the buttons in a variable. Then, in the next execution of shift_in()
you can check, if the previous state (as saved in the variable) and the new measured state do a LOW-to-HIGH transition (previously LOW, now HIGH). This is a button press, that you can use to toggle the corresponding LED bit in the newly created ledstates
variable. Save the new measured state in the copy
variable. (Btw: You should rename the copy
variable to something more describing)
Also you need to consider a mechanism, that you haven't had to bother with the previous code: Button bouncing. A mechanical switch or button will not make one single clean transition, when being pressed. The mechanical contact will bounce a bit, resulting in very fast HIGH-LOW transitions, until the button is settled. If you don't consider that in your program, you will get many transitions and thus toggling the LED many times with only one button press. There are many ways to tackle this (and all of them can be easily googled). I would go with the "don't sense any further transitions after the first for a fixed time" way. This can be done by a simple delay()
call at the end of your shift_in()
function (not so elegant with some caveats) or by using millis()
to create a non-blocking timeout (in the best case for each button individually) (Look at the BlinkWithoutDelay
example, that comes with the Arduino IDE. It is worth learning this principle).
So you have something like this (not the full code, just some hints):
byte copy;
byte ledstates;
void loop(){
if (Serial.available()>0){
message();
}
shift_In();
shift_out(ledstates);
}
void shift_in(){
digitalWrite(CLKI,HIGH);
digitalWrite(LD,HIGH);
digitalWrite(LD,LOW);
digitalWrite(LD,HIGH);
digitalWrite(CLK,HIGH);
digitalWrite(CLKI,LOW);
byte buttonstates = shiftIn(SO,CLK,MSBFIRST); //read button states in a local variable
digitalWrite(CLKI,HIGH);
for(int i=0;i<8;i++){ // loop through all 8 bits of the buttonstates
if(!(copy & (1<<i)) && (buttonstates & (1<<i))){ // check if old button state (in copy) is zero and new buttonstate is 1 (for the currently check bit)
ledstates ^= 1<<i; // toggle the corresponding bit in the ledstates variable (will be shifted out later in the loop() function)
}
}
copy = buttonstates;
delay(10); // delay for debouncing
}
As mentioned above we have seperated input and output. Now it is easy to also implement a fitting bluetooth control. You want the corresponding light go on on serial input (through bluetooth) and go out after a specific time automatically. For such a thing using millis()
with a timestamp is the best option. First we take the message()
function. We define 2 variables at the global scope (outside of any function at the start of the program) to hold the timestamp of our last serial communication and a byte to hold the LEDs, that where activated via serial. In the message()
function we now set the corresponding bit in this last variable. If the serial data was not valid, we simply return from the function (this makes the code shorter). At the end of the function we ensure, that every LED that was set in our variable gets also set in our ledstates
variable, which then will be shifted out. And lastly we save a timestamp with the millis()
function, which returns the number of milliseconds since the startup.
unsigned long bt_leds_timestamp=0;
byte bt_leds_activated=0;
void message(){
if(Serial.available()){ // Send data only when you receive data:
char data = Serial.read(); //Read the incoming data & store into data
Serial.print(data); //Print Value inside data in Serial monitor
switch (data){
case 'A' :
bt_leds_activated |= 1;
break;
case 'B' :
bt_leds_activated |= 2;
break;
case 'C' :
bt_leds_activated |= 4;
break;
case 'D' :
bt_leds_activated |= 8;
break;
case 'E' :
bt_leds_activated |= 16;
break;
case 'F' :
bt_leds_activated |= 32 ;
break;
case 'G' :
bt_leds_activated |= 64;
break;
case 'H' :
bt_leds_activated |= 128;
break;
default : return; }
}
ledstates |= bt_leds_activated;
shift_out(ledstates);
bt_leds_timestamp = millis();
}
Now the corresponding LEDs get turned on, but not off. So we need to put the following code in the main loop()
function:
if(bt_leds_activated && millis()-bt_leds_timestamp > 2000){
ledstates &= ~bt_leds_activated;
shift_out(ledstates);
bt_leds_activated = 0;
}
This code checks, if any LED was activated via bluetooth (the first part of the if condition evaluates to true, if bt_leds_activated
is different than zero) and if a certain time (here 2s aka 2000ms) has passed since the timestamp was taken. When the time has come, then the LED bits from bt_leds_activated
, which we set earlierare cleared in
ledstatesto turn the LEDs off. Then this is shifted out to set the actual state of the LEDs for us to see. Finally we reset
bt_leds_activated` to zero, since we don't have any more LEDs to turn of automatically, until new data arrives.
This has 1 caveat: When you send new data over bluetooth within these 2s, the timeout for the LEDs previously activated via bluetooth will also be resetted, so that they will light longer. The way around that is to use 1 timestamp for each LED, but I thought this as to complicated for now. Don't want this, you can implement that part yourself just like I did with all of them together.
Note: I haven't tested this code in any way.
toggle
mean?