millis() در هر لحظه دقیقاً یک مقدار مشخص برمیگرداند و ممکن است مقدار مورد انتظار دقیقاً دیده نشود.
مثلاً اگر بنویسیم:
if (millis() == previous + interval)
ممکن است مقدار millis() از مقدار موردنظر عبور کند و شرط هیچوقت اجرا نشود.
unsigned long previous = 0;
const unsigned long interval = 1000;
void loop() {
unsigned long current = millis();
if (current - previous >= interval) {
Serial.println("EVENT");
previous = current;
}
}
millis() بعد از رسیدن به حداکثر مقدار قابل نمایش نوع داده، سرریز میشود و از مقدار بسیار بزرگ دوباره به مقدار نزدیک صفر برمیگردد. بنابراین نباید منطق برنامه را بر اساس مقایسه مستقیم زمانها طراحی کنیم.
current - previous >= interval به شکلی طراحی میشود که حتی هنگام سرریز millis() نیز بتواند اختلاف زمانی را بهدرستی بررسی کند.
previous = millis();
در روش previous += interval;
کد:
uint8_t i = 10;
while (i >= 0) {
Serial.println(i);
i--;
}
uint8_t فقط اعداد بدون علامت را نگه میدارد و بازه آن 0 تا 255 است.
بنابراین وقتی i = 0; و سپس i--; انجام شود، مقدار به 255 برمیگردد.
در نتیجه شرط i >= 0 برای uint8_t همیشه درست است و حلقه میتواند بینهایت ادامه پیدا کند.
استفاده از int:
int i = 10;
while (i >= 0) {
Serial.println(i);
i--;
}
استفاده از شرط مناسب برای unsigned:
uint8_t i = 10;
while (i > 0) {
Serial.println(i);
i--;
}
Serial.println(0);
کد:
for (int i = 0; i < 10; i++) {
if (digitalRead(BUTTON) == LOW) {
continue;
}
if (counter >= 5) {
break;
}
counter++;
}
continue; باعث میشود دستورات بعد از آن در همان دور اجرا نشوند.
if (counter >= 5) { break; } باعث خروج از for میشود.
counter >= 5 درست شده و break اجرا میشود؛ بنابراین حلقه همانجا تمام میشود.
"break":
if (condition) {
break;
}
برای خروج زودهنگام از حلقه.
"continue":
if (invalid) {
continue;
}
برای رد کردن iteration فعلی.
continue میتواند برای نادیده گرفتن داده نامعتبر و break برای خروج از جستوجو یا پردازش یک حلقه استفاده شود.
کد:
void loop() {
int value = analogRead(A0);
EEPROM.write(10, value);
delay(100);
}
analogRead() در Arduino Uno مقداری بین 0 تا 1023 میدهد.EEPROM.write() فقط یک byte مینویسد؛ بنابراین نمیتوان مقدار 1023 را به شکل کامل در یک byte ذخیره کرد.delay(100) اجرای سایر بخشهای برنامه را متوقف میکند.EEPROM.update(address, value);
update() فقط زمانی مینویسد که مقدار جدید با مقدار قبلی متفاوت باشد.
میتوان از:
EEPROM.put(address, value);
استفاده کرد. و برای خواندن:
EEPROM.get(address, value);
EEPROM.update() مشکل فرسایش را کاملاً حل نمیکند؛ فقط تعداد نوشتنهای غیرضروری را کاهش میدهد.
شرایط:
millis()delay()enum AlarmState {
NORMAL,
WARNING,
DANGER
};
AlarmState state = NORMAL;
unsigned long previousMillis = 0;
const unsigned long interval = 100;
#define LED_PIN 9
#define BUZZER_PIN 3
void updateAlarm(int distance) {
if (distance > 100) {
state = NORMAL;
}
else if (distance >= 50) {
state = WARNING;
}
else {
state = DANGER;
}
}
void handleNormal() {
digitalWrite(LED_PIN, LOW);
noTone(BUZZER_PIN);
}
void handleWarning() {
digitalWrite(LED_PIN, HIGH);
tone(BUZZER_PIN, 500);
}
void handleDanger() {
noTone(BUZZER_PIN);
int pwm = map(analogRead(A0), 0, 1023, 0, 255);
analogWrite(LED_PIN, pwm);
tone(BUZZER_PIN, 1000);
}
void loop() {
unsigned long current = millis();
if (current - previousMillis >= interval) {
previousMillis = current;
// خواندن سنسور
// updateAlarm(distance);
}
switch (state) {
case NORMAL:
handleNormal();
break;
case WARNING:
handleWarning();
break;
case DANGER:
handleDanger();
break;
}
// در این قسمت HC-05 نیز میتواند
// بدون delay پردازش شود.
}
delay() متوقف شود و بخشهای مختلف باید بتوانند بهصورت غیرمسدودکننده اجرا شوند.
پایهها:
#define ENA 5
#define IN1 7
#define IN2 8
تابع باید سرعت بین -255 تا 255 بگیرد.
void setMotor(int speed) {
speed = constrain(speed, -255, 255);
if (speed > 0) {
digitalWrite(IN1, HIGH);
digitalWrite(IN2, LOW);
analogWrite(ENA, speed);
}
else if (speed < 0) {
digitalWrite(IN1, LOW);
digitalWrite(IN2, HIGH);
analogWrite(ENA, -speed);
}
else {
digitalWrite(IN1, LOW);
digitalWrite(IN2, LOW);
analogWrite(ENA, 0);
}
}
analogWrite(ENA, -100); مفهوم درستی ندارد. عدد منفی برای تعیین جهت موتور استفاده میشود.
+150 → حرکت در جهت اول-150 → حرکت در جهت مخالف0 → توقفENA برای کنترل توان/PWM و IN1 و IN2 برای تعیین جهت استفاده میشوند.
هدف:
ADC: 120 تا 900
↓
PWM: 40 تا 220
تابع: uint8_t calculatePWM(int sensorValue)
uint8_t calculatePWM(int sensorValue) {
sensorValue = constrain(sensorValue, 120, 900);
int pwm = map(
sensorValue,
120, 900,
40, 220
);
pwm = constrain(pwm, 40, 220);
return (uint8_t)pwm;
}
map() بهتنهایی مقدار ورودی را محدود نمیکند. مثلاً اگر sensorValue = 1000; باشد، map() میتواند مقداری خارج از محدوده موردنظر تولید کند. پس برای اطمینان از constrain استفاده میکنیم.
تابع باید سه عدد بگیرد و موارد زیر را برگرداند:
بدون استفاده از متغیر global.
void analyzeValues(
int a,
int b,
int c,
int &minimum,
int &maximum,
float &average,
int &range
) {
minimum = a;
if (b < minimum)
minimum = b;
if (c < minimum)
minimum = c;
maximum = a;
if (b > maximum)
maximum = b;
if (c > maximum)
maximum = c;
average = (a + b + c) / 3.0;
range = maximum - minimum;
}
مثلاً:
int minValue;
int maxValue;
float avg;
int range;
analyzeValues(
10,
25,
17,
minValue,
maxValue,
avg,
range
);
نتیجه:
minimum = 10
maximum = 25
average = 17.33
range = 15
3.0 مهم است تا تقسیم به صورت اعشاری انجام شود.
دستورات ورودی:
AUTO
MANUAL
MOTOR:150
MOTOR:-100
LED:80
BUZZER:1000
یک ساختار مناسب:
enum Mode {
AUTO_MODE,
MANUAL_MODE
};
Mode mode = AUTO_MODE;
char buffer[32];
byte index = 0;
void receiveBluetooth() {
while (Serial.available()) {
char c = Serial.read();
// نادیده گرفتن کاراکترهای نامعتبر
if (c == '\r') {
continue;
}
// پایان دستور
if (c == '\n') {
buffer[index] = '\0';
processCommand(buffer);
index = 0;
break;
}
if (index < sizeof(buffer) - 1) {
buffer[index++] = c;
}
}
}
void processCommand(char *cmd) {
if (strcmp(cmd, "AUTO") == 0) {
mode = AUTO_MODE;
return;
}
if (strcmp(cmd, "MANUAL") == 0) {
mode = MANUAL_MODE;
return;
}
if (strncmp(cmd, "MOTOR:", 6) == 0) {
int value = atoi(cmd + 6);
value = constrain(value, -255, 255);
setMotor(value);
return;
}
if (strncmp(cmd, "LED:", 4) == 0) {
int value = atoi(cmd + 4);
value = constrain(value, 0, 255);
analogWrite(LED_PIN, value);
return;
}
if (strncmp(cmd, "BUZZER:", 7) == 0) {
int frequency = atoi(cmd + 7);
if (frequency > 0) {
tone(BUZZER_PIN, frequency);
}
else {
noTone(BUZZER_PIN);
}
return;
}
}
Receive
↓
Buffer
↓
تشخیص کامل شدن دستور
↓
Parse
↓
Validate
↓
Execute
continue میتواند برای رد کردن کاراکترهای غیرضروری استفاده شود و break برای خروج از حلقه دریافت پس از کامل شدن دستور.
چهار حالت:
IDLE
WARNING
DANGER
ERROR
شرایط:
distance > 100 → IDLE
50 <= distance <=100 → WARNING
distance < 50 → DANGER
distance < 0 → ERROR
enum SystemState {
IDLE,
WARNING,
DANGER,
ERROR_STATE
};
SystemState currentState = IDLE;
SystemState calculateState(int distance) {
if (distance < 0)
return ERROR_STATE;
if (distance < 50)
return DANGER;
if (distance <= 100)
return WARNING;
return IDLE;
}
void updateState(int distance) {
SystemState newState = calculateState(distance);
if (newState != currentState) {
currentState = newState;
executeStateChange(currentState);
}
}
void executeStateChange(SystemState state) {
switch (state) {
case IDLE:
noTone(BUZZER_PIN);
// LCD: IDLE
break;
case WARNING:
tone(BUZZER_PIN, 500);
// LCD: WARNING
break;
case DANGER:
tone(BUZZER_PIN, 1000);
// LCD: DANGER
break;
case ERROR_STATE:
noTone(BUZZER_PIN);
// LCD: ERROR
break;
}
}
if (newState != currentState) و از delay() استفاده نشود تا سایر قسمتهای برنامه بتوانند همزمان اجرا شوند.
millis() → برای زمانبندی غیرمسدودکنندهdelay() → اجرای برنامه را متوقف میکندuint8_t → محدوده 0 تا 255map() → تبدیل بازه، ولی بهتنهایی محدودسازی نمیکندconstrain() → محدود کردن مقدار به یک بازهbreak → خروج از حلقهcontinue → رد کردن iteration فعلیenum → مناسب برای State Machine&) → امکان تغییر متغیر ارسالشده از داخل تابع