Simultaneous servo control - arduino

Stumbling my way thru a problem where I'm trying to move two or more servos at the same time while iterating through multiple arrays. Think of a drum machine, where I may want to hit a snare, bass and high hat all the same time on some beats. Not on others.
byte bass[] = {1,0,1,0};
byte snare[] = {1,1,0,0};
byte hihat[] = {1,1,0,0};
for (int i = 0; i < sizeof(snare); i++)
{
if (i == 1)
{
snare.write(45);
delay(250);
snare.write(0);
dleay(250);
}
else if
{
snare.write(0);
delay(500);
}
}
And so on for each array, each instrument.
As you may see, this won't work because of the delay() and that the two or more loops wouldn't trigger at the same time as the procedural nature.
I understand I could pull this off with some wizardry using millis() instead of using delay().
Tho I'm curious if using a servo driver board would be easier to accomplish what I'm out to do or is millis() the only avenue for me?
Update
After tinkering with millis() for a while, I found myself overcomplicating the task and found a 'close enough' for the task I had in mind by going a simplier route.
#include <Servo.h>
Servo s1;
Servo s2;
Servo s3;
Servo s4;
byte bass[] = {45,0,0,0,45,0,0,0,0,0,0,0,0,0,0,0,45,0,45,0,0,0,0,0,0,0,0,0,45,0,0,0};
byte snare[] = {0,0,0,0,0,0,0,0,45,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,45,0,0,0,0,0,0,0};
byte hihat[] = {45,0,0,0,45,0,0,0,45,0,0,0,45,0,0,0,45,0,0,0,45,0,0,0,45,0,0,0,45,0,0,0};
byte cymb[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,45,0,0,0,0,0,0,0,0,0,0,0,0,0,45,0,0,0};
void setup() {
Serial.begin(9600);
s1.attach(6);
s2.attach(5);
s3.attach(4);
s4.attach(3);
s1.write(0);
s2.write(0);
s3.write(0);
s4.write(0);
}
void loop()
{
drummer();
}
void drummer()
{
for (int i = 0; i < sizeof(bass); i++)
{
s1.write(bass[i]);
s2.write(snare[i]);
s3.write(hihat[i]);
s4.write(cymb[i]);
delay(250);
}
}

Tho I'm curious if using a servo driver board would be easier to
accomplish what I'm out to do or is millis() the only avenue for me?
Millis() will work. I don't own a servo driver board so I can't say about it but I don't think that would help in running servos simultaneously.
Here is the code using millis(). I just wrote it based on your given example.
byte bass[] = {1,0,1,0};
byte snare[] = {1,1,0,0};
byte hihat[] = {1,1,0,0};
usigned long currentMillis = 0;
unsigned long previousMillis = 0;
const long interval = 250;
const long longInterval = 500;
for (int i = 0; i < sizeof(snare); i++)
{
if (i == 1)
{
snare.write(45);
currentMillis = millis();
if (currentMillis - previousMillis >= interval){
snare.write(0);
}
}
else if
{
if (currentMillis - previousMillis >= longInterval){
snare.write(0);
}
}
}

Related

Why does the ledstrip stop partway and then go all the way and can I make this more efficient

So I need some help reviewing and editing the code below. Is there a way to make it more efficient instead of what I did? Also, are there better ways to do some of the methods I did? Answers are appreciated greatly (I'm fairly new to Arduino).
The code below is for a corridor ledstrip that lights up when the distance sensor on the Arduino detects anything below a certain distancenear it (yes, I thought of using a motion sensor but I only had distance sensors on hand).
const int trigPin = 9;
const int echoPin = 10;
float duration, distance;
#include <FastLED.h>
#define LED_PIN 6
#define NUM_LEDS 60
int timingnum = 0;
//#define BRIGHTNESS bright
int bright = 100;///the brightness of the leds
int lednum = 60;///the number of leds available
int timer;
CRGB leds[NUM_LEDS];
void setup() {
pinMode(trigPin, OUTPUT);
pinMode(echoPin, INPUT);
Serial.begin(9600);
FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, NUM_LEDS);
// FastLED.setBrightness( BRIGHTNESS );
}
void loop() {
lights();
}
void sensor() { ///THIS IS CLEAN
digitalWrite(trigPin, LOW);
delayMicroseconds(timingnum);
digitalWrite(trigPin, HIGH);
delayMicroseconds(timingnum);
digitalWrite(trigPin, LOW);
duration = pulseIn(echoPin, HIGH);
distance = (duration*.0343)/2;
Serial.print("Distance: ");
Serial.println(distance);
}
// in the lights the reason why it wouldnt output the data for the
// represented while loops is because the while loop continued running
// without the data from the distance and thus it needs to constantly
// be updated in the while loop conditional statments
void lights() {
sensor(); //data update
while (distance <= 10) {
for (int x = 0; x <= 60; x++) {
leds[x] = CRGB(255,255,255);
FastLED.show();
sensor(); //sensor update
}
for (timer = 0; timer <=800; timer++) {
sensor();
}
sensor(); //replaces the data updates above
}
sensor(); //sensor update
while (distance >= 11) {
for (int x = 0; x <= 60; x++) {
leds[x] = CRGB(0,0,0);
FastLED.show();
}
sensor(); //data update
}
}
I would be tempted to make a little function to set the RGB colors of your LED strip: void fillup(byte r, byte g, byte b). Then you would call it in two places: fillup(0,0,0) and fillup(127,127,127) (yeah, not 100% on).
Also, I'm confused why you have so many sensor() calls in the first while loop. Seems like you only need to call it once when you need updated values for distance.
Also, the comments on the sensor calls are confusing... after reading them I have no idea what sensor is supposed to do. I tend to put a comment before functions that describes what they do, and put a stand-alone comment in the code to explain what the next "paragraph" does. And I avoid banners - they get in the way.
Do you want FastLED.show() inside the loop that updates the colors? Or just after the loop, to update the hardware after the array is changed? IDK, just asking.
I usually do not mind globals, but in this case you would be better off letting sensor return the distance. Then you could while ( sensor() <= 10 ). Or use a do.. while loop with one call to sensor at the bottom if you want to keep the global.
I'd try to get rid of the floating point, too... just calculate what your thresholds are in the raw echo values, and use those. Do we really care what the internal units of measurement are?
Sorry for the long unload... There's nothing wrong with what you have.

lights not flashing off in loop function

So for a little background I am both new to Arduino and as well as to C++. I mostly deal with Javascript, so its likely I made some assumptions while programming this.
The goal of my code is to have a number of lights all flashing in sequence.
the function should turned the light on for each pin, waiting a second or so, then turning off all the pin, and then this is called again in the loop. However, in both the arduino and the online IDE, the LED flashed on but remained on.
My (incredibly generic) question is what is going wrong with my loop?
int NumOfOutputs = 1;
void setup() {
// put your setup code here, to run once:
if(NumOfOutputs >= 1){
for(int i = 2; i < NumOfOutputs + 2; i++){
pinMode(i, OUTPUT);
}
}
}
void flashSequence1(float baseRate){
for(int i = 2; i < NumOfOutputs + 2; i++){
digitalWrite(i,HIGH);
}
size_t: print(delay);
delay(baseRate * 1.00);
for(int i = 2; i < NumOfOutputs + 2; i++){
digitalWrite(i,LOW);
}
}
void flashSequence2(float baseRate, unsigned int repeat){
}
void flashSequence3(float baseRate, unsigned int repeat){
}
void loop() {
float baseRate = 1.0;
flashSequence1(baseRate);
//flashSequence2();
//flashSequence3();
}
Your delay is so short. It's simple: In order to make the human eye detect that a led is flashing, the delay between the led is on and the led is off must be greater than 10 msec. This came from my experiment with leds on Arduino.
Also, you should put a delay after led is on and a delay after led is off too.

Cannot receive data more than once with 433Mhz on Arduino

I am currently building myself some lighting for my apartment using Arduinos and strips of WS2812b LEDs. My plan was to control them with an app from my smartphone and a Raspberry Pi. The phone sends data (like color, mode etc.) to the Pi and the Pi transmits that via a 433MHz transmitter to three Arduino Nanos I wanted to place across the room.
Sending data from my phone through the Pi to the Arduinos works without a problem, the data is received correctly and the LEDs light up, but as soon as I do this once no more data can be received. There is no issue when I just receive and don't do anything else.
Here's my code:
#include <RCSwitch.h>
#include <Adafruit_NeoPixel.h>
#define LEDPIN 9
#define NUMPIXELS 75
#define STARTBIT 4194305
#define STOPBIT 8388609
#define SETBIT 2097153
RCSwitch rc = RCSwitch();
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUMPIXELS, LEDPIN, NEO_GRB + NEO_KHZ800);
uint32_t data[] = {0,0,0};
bool written[] = {false, false, false};
bool led_set = false;
uint32_t wait = 0;
uint16_t j = 0;
int index = 0;
bool recording = false;
int led = 0;
unsigned long time;
static unsigned long received = 0;
void setup() {
Serial.begin(9600);
time = millis();
pinMode(LEDPIN, INPUT);
rc.enableReceive(0); // Receiver on interrupt 0 => that is pin #2
strip.begin();
for (int i = 0; i < 75; i++) {
strip.setPixelColor(i, strip.Color(255,255,255));
}
strip.show();
}
void loop() {
if (millis()-time >= 1000) {
Serial.println(*((unsigned int*)rc.nReceivedValue));
Serial.println(rc.nReceivedValue);
Serial.println(millis());
time = millis();
}
if (rc.available()) {
received = rc.getReceivedValue();
rc.resetAvailable();
Serial.print("received: ");
Serial.print(received);
Serial.println();
}
if ((received == STOPBIT) && recording) {
Serial.println("stopped");
recording = false;
}
else if ((received == STARTBIT) && not recording) {
recording = true;
led_set = false;
Serial.println("started");
}
if (recording) {
if(received == SETBIT && written[index] && index < 3) {
index++;
Serial.println("setbit");
}
else if(received != STARTBIT && received != STOPBIT && received != SETBIT && received != 0 && not written[index] && index < 3) {
data[index] = received & 0xFFFFFFFF;
written[index] = true;
//Serial.println(received);
Serial.println(data[index]);
Serial.println("written");
}
}
else if (data[0] != 0 && data[1] != 0 && data[2] != 0 && not recording && not led_set) {
Serial.println("setting LED");
for (int i = 0; i < NUMPIXELS; i++) {
strip.setPixelColor(i, data[2]);
}
strip.show();
rc.resetAvailable();
}
if (not recording) {
for (int k = 0; k < 3; k++) {
written[k] = false;
}
index = 0;
}
}
(I pretty much ripped this code to pieces and this is what's left.)
These are the transmitter/receivers and I use RC-Switch and Adafruit NeoPixel as libraries, other LED-libraries (FastLED, light_ws2812) had the same error though.
What I found out so far is that it works with less than 5 LEDs and sometimes with more if I set the colors to low values. Then it's more or less chance if I get another receive or not. If I do not set the LEDs here
else if (data[0] != 0 && data[1] != 0 && data[2] != 0 && not recording && not led_set) {
Serial.println("setting LED");
for (int i = 0; i < NUMPIXELS; i++) {
strip.setPixelColor(i, data[2]);
}
strip.show();
I can receive more data, if I hardcode the values for the colors it works only for bright white (so every color to 255) or for color values below 14.
If I comment out the last four lines in setup() the problem can't even be solved by the restart button (unplugging helps though). Switching the receiver does not work either.
All three of the Arduinos I use have the same issue.
I use a 6A power supply (at least for the strip with 75 LEDs, the other two have 30 LEDs each with a 2.4A supply) so power should be no issue.
When I hooked up my Arduino Uno to the second data pin of the receiver it couldn't receive anything either, using the Uno with another receiver next to it was fine. Using the Uno as oscilloscope and displaying the voltage on the data pin I noticed a lot of noise after first transmitting. It looked like it increased with the color values. The noise was gone after I restarted the Arduino with the LED-setting in setup() and it doesn't occur if I don't do anything with the LEDs at all. There is a 5 MOhm pull-down resistor between data and GND on the receiver module. I don't actually know if this noise has anything to do with this, because receiving was sometimes fine even with noise.
I tried disabling the receive pin before setting the LEDs and enabling it afterwards but that didn't help. Enabling interrupts manually doesn't do anything either. I changed the rc-switch interrupt handling so it prints something every time an interrupt occurs. That worked even after receiving the first data, The Arduino somehow just doesn't recognize what's been sent.
I really would like to know if there's anything I could try to solve this, besides switching to WiFi-modules or actual wire.

How to add a time delay when reading a sensor - arduino

I am making a project that has to sense ambient light with a LDR. The idea is, that when the value of the LDR is low for 3 seconds, the led turns on. Also when the value of that LDR gets higher again, and stays high for 3 seconds, the led should turn of. This is so I can avoid that just a cloud or somebody waving over the sensor turns the led on immediately.
I know that I can use the mills() function here like in the blink without delay tutorial. But it doesn't seem to work....
this is my code so far:
#define ledPin 2
#define ldrPin A0
int daylight = 430;
int night = 150;
int ledState = 0;
int lightState = 0;
const long timeOut = 2000;
unsigned long previousMillis = 0;
unsigned long previousMillis2 = 0;
unsigned long tNow = 0;
void setup() {
// put your setup code here, to run once:
pinMode(ledPin, OUTPUT);
pinMode(ldrPin, INPUT);
Serial.begin(9600);
}
void loop() {
// put your main code here, to run repeatedly:
tNow = millis();
int value = analogRead(ldrPin);
switch (lightState) {
case 0:
ledState = 0;
if (value <= 200 && (tNow - previousMillis) >= timeOut)
{
previousMillis = tNow;
lightState = 1;
}
break;
case 1:
ledState = 1;
if (value >= 300 && (tNow - previousMillis2) >= timeOut)
{
previousMillis2 = tNow;
lightState = 0;
}
break;
}
switch (ledState) {
case 0:
digitalWrite(ledPin, LOW);
break;
case 1:
digitalWrite(ledPin, HIGH);
break;
}
Serial.println(value);
Serial.println(ledState);
}
You could try using smoothing to read a running average from the sensor. That way you'll have a smoothed average instead of an immediate value, so a short spike (like a hand) won't change the value if you keep the window long enough.
There is a tutorial on the arduino website explaining how to do this. Basically you store multiple previous values and keep track of the average.

arduino interrupts with servo motor

currently am working on project to open a door with access code using arduino UNO and a servo motor. Normal operation requires entering access code using keypad which is working fine. Another option requires pressing a button that causes an interrupt to rotate the servo motor. My problem is my interrupt only works once and never works again. Plus how do i put the for-loop to rotate the servo motor inside the interrupt function with a delay. I know that is not possible but am calling another function that has the delayMicroseconds but all this is not working. Below is my implementation please help
#include <Keypad.h>
#include <LiquidCrystal.h>
#include <Servo.h>
Servo servo;
const int openButtonPin = 2;
void setup() {
// put your setup code here, to run once:
servo.attach(5);
pinMode(openButtonPin, INPUT); //Pin 2 is input
attachInterrupt(0, enforceOpenAccess, HIGH); // PIN 2
}
void(* resetFunc)(void) = 0;
void loop()
{
//My other keypad implementations go here
}
void myDelay(int x) // function to cause delay in the interrupt
{
for(int i = 0; i<x; i++)
{
delayMicroseconds(1000);
}
}
void enforceOpenAccess() // ISR
{
for(int k =0; k<=180; k+=2)
{
servo.write(k); //rotate the servo
myDelay(30); //delay the rotation of the servo
}
}
The code above is run on arduino UNO being simulated in proteus and the interrupt button is a push button. Please if there is other ways of implementing that but with the same behaviour as I have described above help out. Thanks a lot
There are a couple of problems in the slice of code you posted. Just for completeness, you should post the loop function, since we can't guess what you wrote inside.
Just one comment: did you put a pullup? Otherwise use INPUT_PULLUP instead of INPUT for the button pinmode.
The main one is that you attached the interrupt for the HIGH mode, which will trigger the interrupt any time the pin is up, not on the rising edge. And please use the macro digitalPinToInterrupt to map to the correct pin:
attachInterrupt(digitalPinToInterrupt(openButtonPin), enforceOpenAccess, RISING);
Then.. Let's improve the code. You really should use the interrupts only when strictly necessary when you have to respond IMMEDIATELY (= less than a couple of milliseconds) to an input. Here you don't have to, so it's MUCH better to check for the button in the loop (more on turning the motor following)
uint8_t lastState;
void setup()
{
...
lastState = LOW;
}
void loop()
{
uint8_t currentState = digitalRead(openButtonPin);
if ((currentState != lastState) && (currentState == HIGH))
{
// Start turning the motor
}
lastState = currentState;
...
}
This will enable you to properly debounce the button too:
#include <Bounce2.h>
Bounce debouncer = Bounce();
void setup()
{
...
pinMode(openButtonPin, INPUT); //Pin 2 is input
debouncer.attach(openButtonPin);
debouncer.interval(5); // interval in ms
}
void loop()
{
debouncer.update();
if (debouncer.rose())
{
// Start turning the motor
}
...
}
If, on the other way, you REALLY want to use the interrupts (because waiting for a couple of milliseconds is too much for you), you should do something like this:
#include <Bounce2.h>
Bounce debouncer = Bounce();
void setup()
{
...
pinMode(openButtonPin, INPUT);
attachInterrupt(digitalPinToInterrupt(openButtonPin), enforceOpenAccess, RISING);
}
void loop()
{
...
}
void enforceOpenAccess() // ISR
{
// Start turning the motor
}
It looks like your code? No, because now we'll speak about turning the motor
You should NOT use delays to make steps, because otherwise you will wait for 30ms * 180 steps = 5.4s before being able to do anything else.
You can, however, make a sort of reduced state machine. You want your servo to move from 0 to 180 in steps of 1. So let's code the "don't move" state with any value greater than 180, and consequently we can do something like this in the loop:
unsigned long lastServoTime;
uint8_t servoPosition = 255;
const int timeBetweenSteps_in_ms = 30;
void loop()
{
...
if (servoPosition <= 180)
{ // servo should move
if ((millis() - lastServoTime) >= timeBetweenSteps_in_ms)
{
lastServoTime += timeBetweenSteps_in_ms;
servoPosition++;
if (servoPosition <= 180)
servo.write(servoPosition);
}
}
}
Then, using any of the previous examples, instead of // Start turning the motor write
lastServoTime = millis();
servoPosition = 0;
servo.write(servoPosition);
This way you won't block the main loop even when the button is pressed
This is what is in my loop()
char key = keypad.getKey();
if(key)
{
if(j < 10)
{
studentNumber[j] = key;
//holdMaskedNumber[j] = '*';
lcd.setCursor(0,2);
lcd.print(String(studentNumber));
if(j == 9)
{
studentNumber[9] = '\0';
//holdMaskedNumber[9] = 0;
lcd.clear();
//String number = String(studentNumber);
//lcd.print(number);
//delay(1000);
//lcd.clear();
lcd.print("Access Code");
}
j++;
}
else
{
if(i < 5)
{
accessCode[i] = key;
holdMaskedCode[i] = '*';
lcd.setCursor(1,2);
lcd.print(String(holdMaskedCode));
if(i == 4)
{
holdMaskedCode[5] = '\0';
accessCode[5] = '\0';
//lcd.clear();
//lcd.setCursor(0,0);
//accessCodeString = String(accessCode);
//lcd.print(accessCodeString);
//delay(1000);
lcd.clear();
for(int i =0; i<6; i++)
{
lcd.print("Please wait.");
delay(500);
lcd.clear();
lcd.print("Please wait..");
delay(500);
lcd.clear();
lcd.print("Please wait...");
delay(500);
lcd.clear();
}
digitalWrite(4, HIGH);
lcd.print("Access Granted");
for(int k =0; k<=180; k+=2)
{
servo.write(k);
delay(30);
}
resetFunc();
}
i++;
}
}
}

Resources