Storing Button State

I have written a simple piece of code to turn a white LED on if the button is pressed. I would like the button state to be remembered so the LED doesn't turn off till the button is pressed again. For some reason, this doesn't work and I'm not sure where I've gone wrong:

int whiteledPin = 8;
int buttonPin = 4;
int lastState = digitalRead(4);

void setup() {
  Serial.begin(9600);
  pinMode(buttonPin, INPUT_PULLUP);
  pinMode(whiteledPin, OUTPUT);

}

void loop() {
  int currState = digitalRead(buttonPin);
  if (currState == HIGH) {
    digitalWrite(whiteledPin, HIGH);
  }
  else {
    digitalWrite(whiteledPin, LOW);
  }
  lastState = currState;

}

you don't want to check the current state of the button, you want to capture the pulse, when it goes from HIGH to LOW (being pressed). when you get that then you inverse the state of the LED

careful, button do bounce...

You keep lastState, but never use it ?

1 Like

Demo-Code

/* explanation of the most important details:

  realising a functionality where using a momentary push-button
  acts as a toogle-switch
  push       => activated  push again => DE-activated
  push again => activated  push again => DE-activated
  etc. etc. ...
  This needs quite some code. This code is well organised in MULTIPLE functions
  where each function is a senseful SUB-program

  This is the reason why function loop looks super-short:

void loop () {
  activationMode = GetToggleSwitchState(); // must be executed all the time

  if (activationMode != last_activationMode) {
    Serial.println("button is pushed again switching LED");
    digitalWrite(whiteledPin,activationMode);
    last_activationMode = activationMode;
  }
}

  Huh that's all? No. Of course there is more (below function loop)
*/


#define ProjectName "Toggle Button demonstration"

// define IO-states for inputs with pull-up-resistors
// pull-up-resistors invert the logig
#define unPressed HIGH
#define pressed   LOW

const byte whiteledPin = 8;
const byte ToggleButtonPin = 4;

boolean activationMode;
boolean last_activationMode;


void setup() {
  Serial.begin(115200); // adjust baudrate in the serial monitor to match the number
  Serial.println( F("Setup-Start") );
  printFileNameDateTime();

  pinMode (whiteledPin, OUTPUT);  // used for indicating logging active or not
  digitalWrite(whiteledPin, LOW);
  // wire button between IO-pin and GND
  // Pull-up-resistor inverts the logic
  // unpressed: IO-pin detects HIGH
  // pressed:   IO-Pin detects LOW
  pinMode(ToggleButtonPin, INPUT_PULLUP);
}


void loop () {
  activationMode = GetToggleSwitchState(); // must be executed all the time

  if (activationMode != last_activationMode) {
    Serial.println("button is pushed again switching LED");
    digitalWrite(whiteledPin,activationMode);
    last_activationMode = activationMode;
  }
}


bool GetToggleSwitchState() {
  // "static" makes variables persistant over function calls
  static bool toggleState     = false;
  static bool lastToggleState = false;

  static byte buttonStateOld = unPressed;
  static unsigned long buttonScanStarted  =  0;
  unsigned long buttonDebounceTime = 50;
  static unsigned long buttonDebounceTimer;

  byte buttonStateNew;

  if ( TimePeriodIsOver(buttonDebounceTimer, buttonDebounceTime) ) {
    // if more time than buttonDebounceTime has passed by
    // this means let pass by some time until
    // bouncing of the button is over
    buttonStateNew = digitalRead(ToggleButtonPin);

    if (buttonStateNew != buttonStateOld) {
      // if button-state has changed
      buttonStateOld = buttonStateNew;
      if (buttonStateNew == unPressed) {
        // if button is released
        toggleState = !toggleState; // toggle state-variable
      } // the attention-mark is the NOT operator
    }   // which simply inverts the boolean state
  }     // !true  = false   NOT true  is false
  //       !false = true    NOT false is true
  return toggleState;
}




void printFileNameDateTime() {
  Serial.print( F("File   : ") );
  Serial.println( F(__FILE__) );
  Serial.print( F("Date   : ") );
  Serial.println( F(__DATE__) );
  Serial.print( F("Project: ") );
  Serial.println( F(ProjectName) );
}

// easy to use helper-function for non-blocking timing
boolean TimePeriodIsOver (unsigned long &startOfPeriod, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - startOfPeriod >= TimePeriod ) {
    // more time than TimePeriod has elapsed since last time if-condition was true
    startOfPeriod = currentMillis; // a new period starts right here so set new starttime
    return true;
  }
  else return false;            // actual TimePeriod is NOT yet over
}

and as a WOKWI-simulation

best regards Stefan

I normally point ppl to the IDE Examples/02.Digital/StateChangeDetection sketch, and the article that goes with it.

Here is an adjustment and reduction of that example, which actually works. And somewhat less code to slog through to get the basic idea.

One of the original's flaws is that it does not handle

Try it here

Wokwi_badge State Change Detection


This code also follows the IPO model, which is a very useful and very used model for many the things we do with the Arduino.

IPO Programming Model


// https://wokwi.com/projects/380600410686023681
// https://forum.arduino.cc/t/storing-button-state/1185825

const int  buttonPin = 2;    // the pin that the pushbutton to ground
const int ledPin = 13;       // the pin where an LED and series 1K resistor to ground is attached

int buttonState = 0;         // current state of the button
int lastButtonState = 0;     // previous state of the button

int ledState = 0;            // LED starts off. non-zero will mean on

void setup() {
  // initialize serial communication:
  Serial.begin(115200);

  // initialize the button pin as a input, using the internal pull-up - pressed switch reads LOW:
  pinMode(buttonPin, INPUT_PULLUP);

  // initialize the LED as an output:
  pinMode(ledPin, OUTPUT);
}

void loop() {


// INPUT
  // read the pushbutton input pin:
  buttonState = !digitalRead(buttonPin);


// PROCESS
  // compare the buttonState to its previous state
  if (buttonState != lastButtonState) {
    // the state has changed, check what it is now
    if (buttonState == HIGH) {
      // if the current state is HIGH then the button went from off to on:
      Serial.println("switch is pressed.");

      ledState = !ledState;   // and toggle the LED state
    } 
    else {
      // if the current state is LOW then the button went from on to off:
      Serial.println("                 switch is released"); 
    }
  }
  // update the last state for next time through the loop
  lastButtonState = buttonState;


// OUTPUT
  digitalWrite(ledPin, ledState ? HIGH : LOW);

  delay(50);   // poor man's debounce - run the loop way slower than the button bounce.
}

Remove this line

  delay(50);   // poor man's debounce - run the loop way slower than the button bounce.

to see what contact bounce can do to you. You'll see several pressed/released messages for every single press.

HTH

a7

2 Likes

Hi Alto,

This is super useful thank you. I particularly appreciate you putting it into the simulator.

What is mean't by the term bounce? It wasn't something I was aware of until reading this thread

I see I should changed some of the comments from the original, they are were a bit out of whack. Sry, that's on me, I never read comments…

It's almost literal. When the switch contacts come crashing together, they don't instantly create a continuous path ("close"), rather the switch or pushbutton will appear to close and open many times, all taking a matter of a few milliseconds.

The Arduino is so fast that they can swing around the loop and look at the switch quick enough to be mislead.

And when the switch is opened or the pushbutton released, a similar period of indeterminate state ensues, again playing out in the eternity which is but a few milliseconds.

Crappy switches bounce longer. The simulator goes to the trouble of modeling this behaviour, otherwise more code would work better in simulation than in real life, making it way less valuable.

There are many techniques for debouncing switches, not all are software-based. Everyone has their favorite, and some ways are better for some situations.

Ganssle is one of those ppl that has written lotsa good stuff on embedded systems engineering, here's

Ganssle on debouncing

who will say things better than I every time.

I think it is important to understand, and to program around for yourself at least once using a known good algorithm. Many ppl move on to using (or just start right away with) one of the many debounce libraries that are available.

I've always done my own debouncing, or when it makes sense or I'm lazy, I just slow the entire loop down to a speed where the bounces are seen more like we see them, in other words, invisible.

If there's no need to run the loop at 1000s of times a second. Above the loop runs 20 times a second, and the bouncing is well done and finished by the 50 milliseconds between peeks.

a7

1 Like

So i worked on the code again, i exchanged the buttons with Sensors i made Change Detection. the only problem now if i make the else Statement it always only counts to one and starts the else statement, even after i press other buttoons. The code only works when i delete the else if part. Is there any whay to make sure that the else if only starts, when the if statement isn`t true after an specific amount of time? I also exchanged the servo with an stepper motor and because my steppermotor is broken i put in an buzzer as feedback

Here the new code:

#include <Servo.h>
#include <Stepper.h>

int sensor1 = 13;
int sensor2 = 12;
int sensor3 = 11;
int sensor4 = 10;
int sensor5 = 9;
int buzzer= 4;
const int stepsPerRevolution = 2048; 
const int rolePerMinute = 25;  
Stepper myStepper(stepsPerRevolution, 5, 6, 7, 8);

int sensor1counter = 0;
int sensor2counter = 0;
int sensor3counter = 0;
int sensor4counter = 0;
int sensor5counter = 0; 

int sensor1state = 1; 
int sensor2state = 1;
int sensor3state = 1;
int sensor4state = 1;
int sensor5state = 1;

int lastsensorstate = 1; 


void setup() {
  pinMode(buzzer, OUTPUT);
  pinMode(sensor1, INPUT);
  pinMode(sensor2, INPUT);
  pinMode(sensor3, INPUT);
  pinMode(sensor4, INPUT);
  pinMode(sensor5, INPUT);
  Serial.begin(9600);
  myStepper.setSpeed(15);
}

void loop() {
  sensor1state= digitalRead(sensor1);
  sensor2state= digitalRead(sensor2);
  sensor3state= digitalRead(sensor3);
  sensor4state= digitalRead(sensor4);
  sensor5state= digitalRead(sensor5);

  if (sensor1state != lastsensorstate){
    if(sensor1state == 0){
     sensor1counter=1; 
    }
  }
  
  if (sensor2state != lastsensorstate){
    if(sensor2state == 0){
     sensor2counter=1; 
    }
  }
    
  if (sensor3state != lastsensorstate){
    if(sensor3state == 0){
     sensor3counter=1;
    }
  }
    
  if (sensor4state != lastsensorstate){
    if(sensor4state == 0){
     sensor4counter=1; 
    }
  }
    
  if (sensor5state != lastsensorstate){
    if(sensor5state == 0){
     sensor5counter=1; 
    }
  }
  
Serial.println(sensor1counter + sensor2counter + sensor3counter + sensor4counter + sensor5counter);

  if ((sensor1counter + sensor2counter + sensor3counter + sensor4counter + sensor5counter)==5)
  {
    myStepper.step(stepsPerRevolution);
    digitalWrite(buzzer,HIGH);
    delay(10);
    digitalWrite(buzzer,LOW);
    delay(100);
    myStepper.step(-stepsPerRevolution);
    sensor1counter =0;
    sensor2counter =0;
    sensor3counter =0;
    sensor4counter =0;
    sensor5counter =0;
  }
   else if ((sensor1counter + sensor2counter + sensor3counter + sensor4counter + sensor5counter)<5 && (sensor1counter + sensor2counter + sensor3counter + sensor4counter + sensor5counter)>=1){
    delay(60000);
    myStepper.step(stepsPerRevolution);
    digitalWrite(buzzer,HIGH);
    delay(10);
    digitalWrite(buzzer,LOW);
    delay(100);
    myStepper.step(-stepsPerRevolution);
    sensor1counter =0;
    sensor2counter =0;
    sensor3counter =0;
    sensor4counter =0;
    sensor5counter =0;
  }
}

  • i don't see where lastsensorstate is updated when there's a state change
  • there needs to be a separate last-state variable for each button
  • there doesn't need to be a global variable (i.e. sensor1state) for the current value of each button
  • using arrays will make the code more maintainable (easier to read, debug, expand) as well as easier to add more buttons
  • arrays would make computing the sum easier
  • don't see where sensor counts are be anything other than 0 or 1. did you want the counter to be incremented on each sensor event?
  • once computed, the sum can be used multiple times, no need to re-compute the sum
  • code can be put into functions instead of duplicated

look this over as an example of how to use arrays and to handle multiple buttons

// check multiple buttons and toggle LEDs

enum { Off = HIGH, On = LOW };

byte pinsLed [] = { 10, 11, 12 };
byte pinsBut [] = { A1, A2, A3 };
#define N_BUT   sizeof(pinsBut)

byte butState [N_BUT];

// -----------------------------------------------------------------------------
int
chkButtons ()
{
    for (unsigned n = 0; n < sizeof(pinsBut); n++)  {
        byte but = digitalRead (pinsBut [n]);

        if (butState [n] != but)  {
            butState [n] = but;

            delay (10);     // debounce

            if (On == but)
                return n;
        }
    }
    return -1;
}

// -----------------------------------------------------------------------------
void
loop ()
{
    switch (chkButtons ())  {
    case 2:
        digitalWrite (pinsLed [2], ! digitalRead (pinsLed [2]));
        break;

    case 1:
        digitalWrite (pinsLed [1], ! digitalRead (pinsLed [1]));
        break;

    case 0:
        digitalWrite (pinsLed [0], ! digitalRead (pinsLed [0]));
        break;
    }
}

// -----------------------------------------------------------------------------
void
setup ()
{
    Serial.begin (9600);

    for (unsigned n = 0; n < sizeof(pinsBut); n++)  {
        pinMode (pinsBut [n], INPUT_PULLUP);
        butState [n] = digitalRead (pinsBut [n]);
    }

    for (unsigned n = 0; n < sizeof(pinsLed); n++)  {
        digitalWrite (pinsLed [n], Off);
        pinMode      (pinsLed [n], OUTPUT);
    }
}
1 Like

In addition to @gcjr's bulletproof points, when you

we wonder what kind of sensors. If they have any bounce or brief period of indeterminate stable state, you need to put the delay in the loop which makes my example immune to that.

So tell us about the sensor. Or… test this with pushbuttons until it works, then switch to sensors. This is a good idea no matter who you are - always try to divide and conquer, solve issues with parts thy can be separate insofar as osiible, combine fully working and unserstood sections as late as possible.

While totally new code solving the entire matter in new ways with new things to ponder and learn is always lotsa fun, I encourage you to work with the code you wrote for now; address the issues that have been raised and get it working.

This would be better for your long term evolution. I can't look closely at your code, in transit looking through a tiny window, but it is probably close.

The big issue is that of the state storage… each sensor or button needs its own.

a7

1 Like

So the Sensors are standard limit switches.I Programmed the programm in an way wher my variables for each counter get set to 1 when the state changes. After that the counters get added. The total shows in my serial Monitor, and the if statement starts as soon as this counters add up to 5. Until that it works and i would prefer not to change that (I mean never touch a running system) but i need an second Statement which makes shure that there is an output, even if the counters dont count up to 5.

1 Like

it's good to explore state change detection. Once you've gotten what's going on, you might want to use one of the numerous button library such as Button in easyRun or OneButton or Toggle or EasyButton or Bounce2, ...

combine that with a state machine design (Here is a small introduction to the topic: Yet another Finite State Machine introduction) that will make your coding life easier.

thx for this fought, maybe im gonna ad them, but i first want the code to work, the most of it is working so never touch a running sysem, the online Problem is the priority of the statements. I only need the els if statement to start when the if statement isnt fullfilled

unless it's less than maintainable, which i believe is the case

i think it's better to start with something that works and make it better that try to make something that doesn't work, work (which it too often the case).

OK, those will def bounce, and you may be using a normally closed set of contacts, which is sometimes found in limit switch deployments. That may mean looking for digitalRead() to return the other value.

Therefore (bouncing problem, not switch sense) you will need to use the poor man's debouncing trick as in the state change example I offered, a delay() in the loop() which always gets executed.

At some point, that delay trick can be removed when you tackle real switch debouncing.

Or not. If everything in the loop can tolerate being executed only 50 times a second, then a 20 millisecond delay is adequate.

But… you will sooner later want your loop() to run freely, as it is intended to do. I recommend learning how to do the debouncing, even if you do use a library for it ever after.

For the switch sense, ppl sometimes use

# define PRESSED  LOW
# define NOT_PRESSED  HIGH

And use PRESSED and NOT_PRESSED everywhere else, which makes changing or fixing things available at just on place in you code.

a7

OK, we have begun to apply the advices to that code, but foe now

  if ((sensor1counter + sensor2counter + sensor3counter + sensor4counter + sensor5counter) == 5)
//
//...
// 
  if ((sensor1counter + sensor2counter + sensor3counter + sensor4counter + sensor5counter) < 5 && (sensor1counter + sensor2counter + sensor3counter + sensor4counter + sensor5counter) >= 1)  

Just describe what you intend for those two massive expressions in the if conditions are meant to accomplish.

Guesses can be made, but if you could just say like "I want one thing to happen if no limit switch is engaged, and another if only one one is engaged", maybe, or whatever is your truth, your goal.

Any such prosaic description can be coded - there's probably a clearer way to do.

Meanwhile we're here at the beach an hour early, or is it an hour late. The importnat part is the weather, spectacular warm just to the point of too hot, no wind. Plus: very few people. We like that.

a7

Inquiring minds want to know how you have wired the switches ("sensors").

It looks like a transition to a LOW reading is the thing you are looking for, so your a witch would be wired from the input pin to ground.

And… a resistor from each pin to 5 volts would be pulling up the input otherwise.

If you do not have those resistors, you could add the,, or some.y use INPUT_PULLUP and take advantage of the build-in internal pull-up capability of the Arduino input pins.


If you put your finger on the code and pretend to be the processor, you will quickly see why, unless you press two buttons at exactly the same time the ,logic will never see more than one during any lass through the loop.


The 60 second delay, during which nothing else happens, literally nothing, means your sensor input is not being read or handled by the logic.

For now, lose the stepper or servo code and just use serial print statements that announce your intention to put such code back in. Leave the short delays and the buzzer, lose the 60 seconds of brain freexe, again just for now.

Most of my sketches are very chatty, that is usually all they do at first, just tell me what should be and will be happening when the flesh is added to the bones, so to speak.

The variables with "count" might be better named with "flag" instead, as you only make use of 0 and 1, so it's no a count at all, but an indicator, in programming one of a few things referred to as flags. Is the flag up or down?

a7

OK here the simplest word i can thing of.
5 Sensors.
1 Output.
When Sensor pressed change variable to 1
Add variables together
When Veriables together =5 then do output

When Timer runs out start outpot too
here a code witch works completely i did`nt find any stopwatch or timer library that thits. I need to start the timer with an if statement and need to reset it in the first five if statements at the start of void loop. Would be cool if you could recommend some library for that i already tried simple Timer from Arduino Stopwatch and Timer three / Timer one none of them where working, atleast not in the way i needed them to work. Here the new code:

#include <Servo.h>
#include <Stepper.h>
#include <TimerOne.h>

int sensor1 = 13;
int sensor2 = 12;
int sensor3 = 11;
int sensor4 = 10;
int sensor5 = 9;
int buzzer= 4;
const int stepsPerRevolution = 2048; 
const int rolePerMinute = 25;  
Stepper myStepper(stepsPerRevolution, 5, 6, 7, 8);


int sensor1counter = 0;
int sensor2counter = 0;
int sensor3counter = 0;
int sensor4counter = 0;
int sensor5counter = 0; 

int sensor1state = 1; 
int sensor2state = 1;
int sensor3state = 1;
int sensor4state = 1;
int sensor5state = 1;

int lastsensorstate = 1;
int summe;


void setup() {
  pinMode(buzzer, OUTPUT);
  pinMode(sensor1, INPUT);
  pinMode(sensor2, INPUT);
  pinMode(sensor3, INPUT);
  pinMode(sensor4, INPUT);
  pinMode(sensor5, INPUT);
  Serial.begin(9600);
  myStepper.setSpeed(15);
}

void loop() {
  sensor1state= digitalRead(sensor1);
  sensor2state= digitalRead(sensor2);
  sensor3state= digitalRead(sensor3);
  sensor4state= digitalRead(sensor4);
  sensor5state= digitalRead(sensor5);


  if (sensor1state != lastsensorstate){
    if(sensor1state == 0){
     sensor1counter=1;
    }
  }
  
  if (sensor2state != lastsensorstate){
    if(sensor2state == 0){
     sensor2counter=1; 
    }
  }
    
  if (sensor3state != lastsensorstate){
    if(sensor3state == 0){
     sensor3counter=1;
    }
  }
    
  if (sensor4state != lastsensorstate){
    if(sensor4state == 0){
     sensor4counter=1; 
    }
  }
    
  if (sensor5state != lastsensorstate){
    if(sensor5state == 0){
     sensor5counter=1; 
    }
  }

  summe= (sensor1counter + sensor2counter + sensor3counter + sensor4counter + sensor5counter);
  Serial.println(summe);
  if (summe==5)
  {
    myStepper.step(stepsPerRevolution);
    digitalWrite(buzzer,HIGH);
    delay(10);
    digitalWrite(buzzer,LOW);
    delay(100);
    myStepper.step(-stepsPerRevolution);
    sensor1counter =0;
    sensor2counter =0;
    sensor3counter =0;
    sensor4counter =0;
    sensor5counter =0;
  }
}

still not thinking about using arrays ?

Please. When you track the state of something, each thing you track needs its own "lastsesnorstate" to keep track of that button's, um, last state.

Furthermore, you need to actually set the last sensor state, so that the next time it is in fact the old sensor state and can be compared to the new sensor state.

You did not add the poor man's loop delay. It may not matter - did your own analysis lead you to conclude it was unnecessary?

It's almost as if you aren't reading our responses and actually thinking about how state change detection works.

You didn't say how your buttons are wired. You didn't say you are using a pullup or pulldown resistor. At the very least, those should have been something you could answer or ask about or google.

Someone here followed this thread and produced this:

int sensor1 = 13;
int sensor2 = 12;

int buzzer = 4;

# define NSENSORS 2

int sensor1counter = 0;
int sensor2counter = 0;

int sensor1state = 1; 
int sensor2state = 1;

int lastsensorstate1 = 1; 
int lastsensorstate2 = 1; 

int summe;

void setup() {
  Serial.begin(9600);
  Serial.println("SERIAL PRINTING!!!\n");
  
  pinMode(buzzer, OUTPUT);

  pinMode(sensor1, INPUT_PULLUP);
  pinMode(sensor2, INPUT_PULLUP);
}

void loop() {
  sensor1state = digitalRead(sensor1);
  sensor2state = digitalRead(sensor2);

  if (sensor1state != lastsensorstate1){
    if(sensor1state == 0){
     sensor1counter = 1; 
    }

    lastsensorstate1 = sensor1state;
  }
  
  if (sensor2state != lastsensorstate2){
    if(sensor2state == 0){
     sensor2counter = 1; 
    }

    lastsensorstate2 = sensor2state;
  }
 
  summe = sensor1counter + sensor2counter;
  Serial.println(summe);
  if (summe == NSENSORS)
  {
    digitalWrite(buzzer,HIGH);
 //   delay(10); buzz for 0.01 seconds? I don't hear that fast
    delay(1000);
    digitalWrite(buzzer,LOW);
    delay(100);

    sensor1counter = 0; 
    sensor2counter = 0; 
  }
}

which works. For timing, she used delay. If you want to do timing this the "right" way, you need to familiarize youtself with the millis() function. Start with "blink without delay", google it and see the example in the IDE for the idiomatic pattern.

The sketch above is for two sensors. You have a choice: continue in ignorance of the power that array variables will bring to coding things like this, in terms of shorter code what will be easier to write, perfect, modify and one day enhance.

Or copy/paste/edit like you know how to do, and live with the trouble that makes for all the same reasons.

The sketch above also will not mind at all if you press and release the buttons, it fires off after you've pressed (and even if you've released) both buttons at least once. I am quite sure this would not be what you are trying to do.

Try again on your words. If English isn't one of your strengths, perhaps expressing yourself in a language you are more familiar with and using google translate would help.

As far as I can tell, something will start after all five sensors have been pressed, and go on until either some time period has elapsed or a sensor button gets pressed again.

Which just seems odd. Again, guessing, I'd say you meant to say "start a timed motion when all five sensors hare closed (or is it open? You never answered that question). Stop after that time has elapsed, or if any of the five sensors change stateback to the opposite."

This if statment that sets the counter when the switch goes LOW

    if (sensor1state == 0){
     sensor1counter = 1; 
    }

has no else clause. But if sensor1state isn't LOW, it means the sensor has become HIGH, so

    if (sensor1state == 0){
     sensor1counter = 1; 
    }
    else {
// right here it means the sensor has switched the other way.
    }

there's a place for reacting to a dropped sensor.

I'm tuning out for awhile. My recommendation is that pursue knowledge in lieu of making progress on your project: array variables.

And basic program logic. You might start working through the IDE examples, or hunt up some kind of organized exposure to basic programming logic.

There is no need to, and good reasons not to, rely on copy/paste/editing to grow your code.

a7