תכנון, ניהול והערכת פרויקטים בעידן הבינה המלאכותית

✦ בנוי על ידי פהד גאנם ✦

S = w1·s1 + w2·s2 + w3·s3 + w4·s4w1 + w2 + w3 + w4
2
3
3
2
—
—
—
—

SBME280 = w1·s1 + w2·s2 + w3·s3 + w4·s4w1 + w2 + w3 + w4 = 2·3 + 3·4 + 3·5 + 2·52 + 3 + 3 + 2 = 4310 = 4.30
SDHT22 = w1·s1 + w2·s2 + w3·s3 + w4·s4w1 + w2 + w3 + w4 = 2·5 + 3·5 + 3·2 + 2·32 + 3 + 3 + 2 = 3710 = 3.70
SDS18B20 = 2·5 + 3·1 + 3·3 + 2·32 + 3 + 3 + 2 = 2810 = 2.80
SDHT11 = 2·1 + 3·2 + 3·2 + 2·32 + 3 + 3 + 2 = 2010 = 2.00
SDHT22 = 5·5 + 3·5 + 1·2 + 1·35 + 3 + 1 + 1 = 4510 = 4.50
SBME280 = 5·3 + 3·4 + 1·5 + 1·55 + 3 + 1 + 1 = 3710 = 3.70

Rp(min) = VDD − VOLIOL
Rp(max) = tr(max)0.8473 · Cb
tr = 0.8473 · Rp · Cb
—
—
—
—
—
—
—
—
—
—
—
—
3.3 V
200 pF
100 kHz
4.7 kΩ
0 / 6
—
0 / 6
—
—
—
—
—


Rp(min) = VDD − VOLIOL = 3.3 V − 0.4 V3 mA = 967 Ω

Rp(max) = tr(max)0.8473 · Cb = 1000 ns0.8473 · 200 pF = 5.90 kΩ

tr = 0.8473 · 10 kΩ · 200 pF = 1695 ns
1695 ns > 1000 ns

967 Ω < 4.7 kΩ < 5.90 kΩ
tr = 0.8473 · 4.7 kΩ · 200 pF = 796 ns
IOL = VDD − VOLRp = 3.3 V − 0.4 V4.7 kΩ = 0.617 mA

Rp(max) = tr(max)0.8473 · Cb = 300 ns0.8473 · 200 pF = 1.77 kΩ
4.7 kΩ > 1.77 kΩ

Pmiss = D − dD   (d < D)
M = N · s
Mfree = MRAM − M
2.0 s
0.30 s
720
0
0
0
—
—
—
—
—
—
—
—
—
—

N = TΔt = 1440 min2 min = 720
M = N · s = 720 · 4 = 2880 B
2880 B > 2048 B
2880 − 2048 = 832 B
M = N · s = 720 · 2 = 1440 B
Mfree = MRAM − M = 2048 − 1440 = 608 B
MMRAM = 1440 B2048 B = 0.703
23.44 °C · 10 = 234.4
round(234.4) = 234
ESP32 · Arduino C++
// 8.3  Greenhouse climate controller - the modular version, written for review.
// Three layers: drivers (classes that own the hardware), logic (pure functions
// that know nothing about pins), and glue (setup and loop).
// Nothing in loop() blocks: a millis() scheduler runs the 2 s job.

#include <Wire.h>
#include <SPI.h>

// ---------- pins and constants: one place, no magic numbers further down ----------
const uint8_t  PIN_SDA  = 21;
const uint8_t  PIN_SCL  = 22;
const uint8_t  PIN_SCK  = 18;
const uint8_t  PIN_MOSI = 23;
const uint8_t  PIN_CS   = 5;       // MAX7219 chip select
const uint8_t  PIN_FAN  = 26;      // MOSFET gate, never the fan itself
const uint8_t  PIN_POT  = 34;      // set-point potentiometer, ADC1, input only

const uint32_t SAMPLE_MS = 2000;   // sampling period
const float    HYST      = 1.0f;   // fan on above set+1, off below set-1
const uint16_t LOG_N     = 720;    // 24 h of history, one entry every 2 min
const uint8_t  LOG_EVERY = 60;     // 60 samples x 2 s = 2 min

// ======================= DRIVER LAYER =======================

// BME280 over raw I2C registers. Temperature only: the humidity and pressure
// compensation formulas are NOT implemented here, and the class does not
// pretend otherwise.
class Bme280 {
 public:
  explicit Bme280(uint8_t addr) : addr_(addr) {}

  bool begin() {
    if (read8(0xD0) != 0x60) return false;   // chip-ID register must read 0x60
    t1_ = read16(0x88);                      // factory calibration, LSB first
    t2_ = (int16_t)read16(0x8A);
    t3_ = (int16_t)read16(0x8C);
    write8(0xF2, 0x01);                      // humidity oversampling x1
    write8(0xF4, 0x27);                      // temp x1, pressure x1, normal mode
    return true;
  }

  // Returns degrees Celsius, or NAN when the bus transaction fails.
  float readTempC() {
    Wire.beginTransmission(addr_);
    Wire.write(0xFA);
    if (Wire.endTransmission() != 0) return NAN;
    if (Wire.requestFrom(addr_, (uint8_t)3) != 3) return NAN;
    int32_t msb  = Wire.read();              // three separate statements:
    int32_t lsb  = Wire.read();              // the order of the reads matters
    int32_t xlsb = Wire.read();
    int32_t adc  = (msb << 12) | (lsb << 4) | (xlsb >> 4);

    // Integer compensation from the datasheet; the result is in 0.01 degC.
    int32_t v1 = (((adc >> 3) - ((int32_t)t1_ << 1)) * (int32_t)t2_) >> 11;
    int32_t d  = (adc >> 4) - (int32_t)t1_;
    int32_t v2 = (((d * d) >> 12) * (int32_t)t3_) >> 14;
    int32_t centi = ((v1 + v2) * 5 + 128) >> 8;
    return centi / 100.0f;
  }

 private:
  uint8_t  addr_;
  uint16_t t1_ = 0;
  int16_t  t2_ = 0, t3_ = 0;

  void write8(uint8_t reg, uint8_t val) {
    Wire.beginTransmission(addr_);
    Wire.write(reg);
    Wire.write(val);
    Wire.endTransmission();
  }
  uint8_t read8(uint8_t reg) {
    Wire.beginTransmission(addr_);
    Wire.write(reg);
    if (Wire.endTransmission() != 0) return 0;
    if (Wire.requestFrom(addr_, (uint8_t)1) != 1) return 0;
    return Wire.read();
  }
  uint16_t read16(uint8_t reg) {
    uint8_t lo = read8(reg);
    uint8_t hi = read8((uint8_t)(reg + 1));
    return ((uint16_t)hi << 8) | lo;
  }
};

// MAX7219 eight-digit display over SPI. Its inputs need 3.5 V for a logic
// high, so the three lines pass through a level shifter on the way there.
class Max7219 {
 public:
  explicit Max7219(uint8_t cs) : cs_(cs) {}

  void begin() {
    pinMode(cs_, OUTPUT);
    digitalWrite(cs_, HIGH);
    SPI.begin(PIN_SCK, -1, PIN_MOSI, cs_);
    send(0x0F, 0x00);                        // display test off
    send(0x09, 0xFF);                        // BCD decode on all digits
    send(0x0B, 0x07);                        // scan all eight digits
    send(0x0A, 0x04);                        // brightness
    for (uint8_t d = 1; d <= 8; d++) send(d, BLANK);
    send(0x0C, 0x01);                        // leave shutdown mode
  }

  // Shows a value given in tenths: 234 is displayed as 23.4
  void showTenths(int16_t v) {
    bool neg = v < 0;
    uint16_t a = neg ? -v : v;
    if (a > 999) a = 999;
    uint8_t tens = a / 100;
    send(1, a % 10);                         // digit 1 is the rightmost
    send(2, ((a / 10) % 10) | 0x80);         // bit 7 lights the decimal point
    send(3, tens ? tens : (neg ? MINUS : BLANK));
    send(4, (tens && neg) ? MINUS : BLANK);
  }

 private:
  static const uint8_t MINUS = 0x0A, BLANK = 0x0F;
  uint8_t cs_;

  void send(uint8_t reg, uint8_t val) {
    SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
    digitalWrite(cs_, LOW);
    SPI.transfer(reg);
    SPI.transfer(val);
    digitalWrite(cs_, HIGH);                 // data is latched on this edge
    SPI.endTransaction();
  }
};

// The 12 V fan behind a logic-level MOSFET.
class Fan {
 public:
  explicit Fan(uint8_t pin) : pin_(pin) {}

  bool begin() {
    bool ok = ledcAttach(pin_, 25000, 8);    // 25 kHz, 8-bit duty
    ledcWrite(pin_, 0);
    return ok;
  }
  void set(bool on) {
    on_ = on;
    ledcWrite(pin_, on ? 255 : 0);
  }
  bool isOn() const { return on_; }

 private:
  uint8_t pin_;
  bool    on_ = false;
};

// Set-point potentiometer: the full turn covers 15 to 35 degC.
class SetPot {
 public:
  explicit SetPot(uint8_t pin) : pin_(pin) {}

  void  begin() { analogReadResolution(12); }
  float readC() { return 15.0f + analogRead(pin_) * 20.0f / 4095.0f; }

 private:
  uint8_t pin_;
};

// ======================= LOGIC LAYER =======================
// No pins, no Wire, no SPI below this line - which is why it can be tested
// on the bench with nothing connected.

// Hysteresis rule. Inside the band the fan keeps its present state.
bool fanWanted(float t, float tSet, bool on) {
  if (t > tSet + HYST) return true;
  if (t < tSet - HYST) return false;
  return on;
}

// 23.44 degC becomes 234: two bytes instead of four, exact to 0.1 degC.
int16_t toTenths(float t) {
  return (int16_t)lroundf(t * 10.0f);
}

int16_t  logBuf[LOG_N];                      // 720 x 2 = 1440 bytes
uint16_t logHead = 0, logCount = 0;

// Ring buffer: when it is full the oldest entry is overwritten.
void logPush(int16_t tenths) {
  logBuf[logHead] = tenths;
  logHead = (logHead + 1) % LOG_N;
  if (logCount < LOG_N) logCount++;
}

// Unit test of the logic layer. It needs no sensor, no display and no fan.
bool selfTest() {
  struct Case { float t, tSet; bool on, want; };
  static const Case cases[] = {
    {26.1f, 25.0f, false, true },            // above the upper threshold
    {23.9f, 25.0f, true,  false},            // below the lower threshold
    {25.5f, 25.0f, false, false},            // inside the band, was off
    {25.5f, 25.0f, true,  true },            // inside the band, was on
    {26.0f, 25.0f, false, false},            // exactly on the upper threshold
    {24.0f, 25.0f, true,  true },            // exactly on the lower threshold
  };
  uint8_t fails = 0;
  for (const Case &c : cases) {
    bool got = fanWanted(c.t, c.tSet, c.on);
    if (got != c.want) {
      fails++;
      Serial.printf("FAIL fanWanted(%.1f, %.1f, %d) = %d\n", c.t, c.tSet, c.on, got);
    }
  }
  if (toTenths(23.44f) != 234) { fails++; Serial.println("FAIL toTenths(23.44)"); }
  if (toTenths(-5.04f) != -50) { fails++; Serial.println("FAIL toTenths(-5.04)"); }

  Serial.println(fails ? "selfTest: FAIL" : "selfTest: PASS");
  return fails == 0;
}

// ======================= GLUE =======================
Bme280  sensor(0x76);
Max7219 disp(PIN_CS);
Fan     fan(PIN_FAN);
SetPot  pot(PIN_POT);

void setup() {
  Serial.begin(115200);
  Wire.begin(PIN_SDA, PIN_SCL);

  selfTest();                                // the logic is checked first
  if (!sensor.begin()) Serial.println("BME280 not found: chip ID is not 0x60");
  if (!fan.begin())    Serial.println("fan PWM could not be attached");
  disp.begin();
  pot.begin();
}

void loop() {
  static uint32_t last = 0;
  static uint8_t  sinceLog = 0;

  uint32_t now = millis();
  if (now - last >= SAMPLE_MS) {             // unsigned math survives rollover
    last += SAMPLE_MS;                       // fixed step: the period cannot drift

    float t = sensor.readTempC();
    if (isnan(t)) {
      fan.set(true);                         // blind controller: ventilate
      Serial.println("sensor error - fan forced on");
      return;
    }
    float tSet = pot.readC();
    fan.set(fanWanted(t, tSet, fan.isOn()));

    int16_t tenths = toTenths(t);
    disp.showTenths(tenths);
    if (++sinceLog >= LOG_EVERY) {
      sinceLog = 0;
      logPush(tenths);
    }
    Serial.printf("T=%.2f  set=%.1f  fan=%d  log=%u\n", t, tSet, fan.isOn(), logCount);
  }
  // Execution falls through here thousands of times a second, so a button,
  // a serial command or a second timer can be served without waiting.
}

tbit = 1baud
Nall = n + n·(n − 1)2
115200 bit/s
9600 bit/s
8
—
—
—
—

baud = 1tbit = 152·10−6 s = 19231 bit/s
tTX = 1baud = 119200 bit/s = 52.08 µs
tRX = 1baud = 19600 bit/s = 104.17 µs
tRXtTX = 104.17 µs52.08 µs = 2
TTX = 10baud = 1019200 bit/s = 520.8 µs
TRX = 10baud = 109600 bit/s = 1041.7 µs
ESP32 · Arduino C++
// 8.4  I2C bus scanner - the first tool to run before blaming code or wiring.
// It asks every legal address whether anyone is there, prints each answer,
// and then names the two devices the greenhouse controller expects.

#include <Wire.h>

const int PIN_SDA = 21;
const int PIN_SCL = 22;

struct Expected {
  uint8_t     addr;
  const char *name;
  bool        found;
};

Expected expected[] = {
  { 0x68, "DS1307", false },
  { 0x76, "BME280", false },
};
const int N_EXPECTED = sizeof(expected) / sizeof(expected[0]);

void scanBus() {
  int count = 0;
  for (int i = 0; i < N_EXPECTED; i++) expected[i].found = false;

  Serial.println("I2C scan:");
  // 0x00-0x07 and 0x78-0x7F are reserved by the I2C specification.
  for (uint8_t addr = 0x08; addr <= 0x77; addr++) {
    Wire.beginTransmission(addr);
    uint8_t err = Wire.endTransmission();   // 0 means the address was ACKed
    if (err != 0) continue;

    Serial.printf("  device at 0x%02X\n", addr);
    count++;
    for (int i = 0; i < N_EXPECTED; i++) {
      if (expected[i].addr == addr) expected[i].found = true;
    }
  }

  Serial.printf("%d device(s) answered\n", count);
  for (int i = 0; i < N_EXPECTED; i++) {
    Serial.printf("  0x%02X %-7s %s\n", expected[i].addr, expected[i].name,
                  expected[i].found ? "found" : "MISSING");
  }
}

void setup() {
  Serial.begin(115200);                   // the monitor must be set to 115200 too
  Wire.begin(PIN_SDA, PIN_SCL, 100000);   // 100 kHz: the DS1307 is no faster
  delay(200);
}

void loop() {
  scanBus();
  delay(3000);   // repeat, so a wire can be moved while watching the result
}

Δt = tact − tplan
Δ% = Δttplan × 100 %
pAI = nAIn × 100 %
—
—
—
—
—
—
—
5 h
8 h
6 h
—
—
—
—


Δt = tact − tplan = 30 − 24 = 6 h

Δ% = Δttplan = 6 h24 h = 0.25 = 25 %

pAI = nAIn = 58 = 0.625 = 62.5 %

pfix = nfixnAI = 35 = 0.6 = 60 %
nmiss = nAI − nfix = 5 − 3 = 2

T = t1 + t2 + t3 + t4 + t5
pi = tiT
c = nN
1.5 min
2.5 min
2.5 min
2.0 min
1.5 min
—
—
—
—
—
—
—
—

T = 1.5 + 2.5 + 2.5 + 2 + 1.5 = 10 min
p1 = t1T = 1.5 min10 min = 0.15 = 15 %
p2 = t2T = 2.5 min10 min = 0.25 = 25 %
p4 = t4T = 2 min10 min = 0.20 = 20 %
15 + 25 + 25 + 20 + 15 = 100 %
t4 = 2 min ≥ 2 min ✓
c1 = n1N = 14 = 0.25 = 25 %
c4 = n4N = 24 = 0.50 = 50 %
const float BAND_C = 2.0;
ESP32 · Arduino C++
// 8.6  Greenhouse climate controller - the logic layer of the project.
// Written so that every requirement change from the oral drill has ONE
// obvious place. The six places are marked "change 1" ... "change 6".

#include 

// ---- hardware layer: the wiring, as numbers ----
const int PIN_SDA = 21;                   // I2C data  (BME280, DS1307)
const int PIN_SCL = 22;                   // I2C clock
const int PIN_FAN = 26;                   // gate of the logic-level MOSFET
const int PIN_SET = 34;                   // set-point potentiometer, input only

// ---- logic layer: the numbers the examiner may ask to change ----
const float         BAND_C    = 1.0;      // change 1: half-width of the band, degC
const unsigned long SAMPLE_MS = 2000;     // change 2: sampling period, ms
const float         SET_MIN_C = 15.0;     // potentiometer at one end
const float         SET_MAX_C = 35.0;     // potentiometer at the other end

const int FAN_FREQ = 25000;               // PWM frequency, Hz
const int FAN_BITS = 8;                   // duty runs 0..255
const int FAN_FULL = 255;

const int BME_ADDR    = 0x76;
const int BME_REG_ID  = 0xD0;
const int BME_CHIP_ID = 0x60;

bool          fanOn      = false;
unsigned long lastSample = 0;

// ---- driver layer (placeholder) ----
// Returns false when the sensor does not answer - change 5 relies on it.
// The presence check is real: it reads the chip-ID register over I2C.
// The two values below are FIXED DEMO VALUES, not measurements. The raw
// register read and the compensation maths live in the driver from 8.3,
// and they replace the two marked lines. Change 4 rewrites this body only.
bool readClimate(float &t, float &h) {
  Wire.beginTransmission(BME_ADDR);
  Wire.write(BME_REG_ID);
  if (Wire.endTransmission(false) != 0) return false;   // no acknowledge
  if (Wire.requestFrom(BME_ADDR, 1) != 1) return false;
  if (Wire.read() != BME_CHIP_ID) return false;         // another chip answered
  t = 26.4;                               // DEMO VALUE - replace with 8.3 driver
  h = 55.0;                               // DEMO VALUE - replace with 8.3 driver
  return true;
}

// ---- display layer (placeholder) ----
// The MAX7219 driver from 8.3 belongs here. Until it is pasted in, the
// value goes to the serial monitor, clearly tagged as display output.
void showValue(char tag, float v) {
  Serial.printf("[display] %c %.1f\n", tag, v);
}

// ---- actuator: the only place that touches the fan pin ----
void setFan(int duty) {
  ledcWrite(PIN_FAN, duty);
}

// ---- pure logic: no pins, no buses, easy to test on paper ----
// Change 6 replaces this function with one that returns a duty.
bool fanWanted(float t, float tSet, bool on) {
  if (!on && t > tSet + BAND_C) return true;        // too warm: start
  if (on  && t < tSet - BAND_C) return false;       // cool enough: stop
  return on;                                            // inside the band: keep
}

float readSetPoint() {
  int raw = analogRead(PIN_SET);                        // 0..4095
  return SET_MIN_C + (SET_MAX_C - SET_MIN_C) * raw / 4095.0;
}

void controlStep() {
  float t = 0, h = 0;
  if (!readClimate(t, h)) {
    // change 5: the alarm and the safe fan state are decided in this branch
    Serial.println("sensor: no answer, step skipped");
    return;
  }
  float tSet = readSetPoint();
  fanOn = fanWanted(t, tSet, fanOn);
  setFan(fanOn ? FAN_FULL : 0);           // change 6: one duty instead of two
  showValue('t', t);                      // change 3: choose the page here
  Serial.printf("t=%.1f C  h=%.0f %%  set=%.1f C  fan=%d\n", t, h, tSet, fanOn);
}

void setup() {
  Serial.begin(115200);
  Wire.begin(PIN_SDA, PIN_SCL);
  analogReadResolution(12);
  ledcAttach(PIN_FAN, FAN_FREQ, FAN_BITS);
  setFan(0);
}

void loop() {
  unsigned long now = millis();
  if (now - lastSample >= SAMPLE_MS) {              // no delay() anywhere
    lastSample = now;
    controlStep();
  }
}
🏠 ♿