CYD Studio · MicroPython · ESP32 / ESP8266

Atölye Defteri

Her bölüm üç parça: Genel — her mikrodenetleyicide böyle · CYD Studio'da — bizim projelerden gerçek kod · Neden — kartta ölçüp öyle karar verdiğimiz şey. Yeşil kutulardaki sayılar tahmin değil, ölçüm (Eylül 2026).

1. Proje iskeleti

Bir mikrodenetleyici projesi her zaman aynı şekle sahiptir: kur → döngüde iş yap → çıkarken temizle. Arduino'da bunun adı setup()/loop(); MicroPython'da düz bir betiktir.

Genel
from machine import Pin
import time

led = Pin(2, Pin.OUT)      # kurulum
while True:                # sonsuz döngü
    led.value(1)
    time.sleep(0.5)
    led.value(0)
    time.sleep(0.5)

Durdurmanın yolu yok: fişi çekersin.

CYD Studio'da
projects/led_yakip_sondur.py
import cyd
n = 0
try:
    while not cyd.should_stop():
        n += 1
        cyd.led(g=True)
        if not cyd.sleep(0.5): break
        cyd.led_off()
        if not cyd.sleep(0.5): break
finally:
    cyd.led_off()   # çıkarken yanık kalmasın
print('durduruldu - %d kez' % n)
Neden

while True yerine while not cyd.should_stop(): paneldeki Durdur düğmesi bir bayrak kaldırır; döngü her turda ona bakar ve temiz çıkar.

cyd.sleep() beklerken de bayrağı dinler ve kesilirse False döner — 30 saniyelik bir uykunun ortasında bile Durdur 3 sn içinde tutar.

try/finally: proje nasıl biterse bitsin donanım temiz kalır. Röle açık kalmış bir projeyi düşün.

Ölçüldü
Bayrağı dinlemeyen proje 3 sn sonra "takıldı" sayılır; tek çalışan oysa kart kendini resetler (~8 sn'de panel geri gelir). En kötü ihtimalde bile kart elinde kalmaz.

2. Pin: dijital giriş / çıkış

GPIO pini tek bir teldir; ya sürer (çıkış: 0 V ya da 3,3 V) ya dinler (giriş: dışarıdaki voltajı 0/1 okur). Eşik kabaca 0,8 V altı = 0, 2 V üstü = 1; arası belirsiz.

Genel
p = Pin(22, Pin.OUT)
p.value(1)          # 3,3 V
p.value(0)          # 0 V

g = Pin(27, Pin.IN, Pin.PULL_UP)
g.value()           # 0 ya da 1

Havada kalan giriş pini anten gibidir, rastgele okur. PULL_UP/PULL_DOWN "boşta ne olsun" sorusunun cevabıdır.

CYD Studio'da
projects/harici_led.py
from machine import Pin
import cyd

# CYD: GPIO22. NodeMCU: D1 (GPIO5).
PIN = getattr(cyd, 'D1', 22)
led = Pin(PIN, Pin.OUT)
led.value(0)

while not cyd.should_stop():
    led.value(1)
    if not cyd.sleep(0.5): break
    led.value(0)
    if not cyd.sleep(0.5): break

Kart üstü RGB LED için pin bilmene gerek yok: cyd.led(r,g,b).

Neden

getattr(cyd, 'D1', 22): NodeMCU'da 22 numaralı pin yok — ValueError: invalid pin. Aynı dosya iki kartta çalışsın diye kartın verdiği etiketi soruyoruz. (Bu hata gerçekten yaşandı: proje "1 saniye çalışıp düştü" gibi göründü.)

CYD'de boş pin çok az: ekran, dokunmatik, SD ve LED çoğunu almış. Sana kalan 22, 27 ve sadece-giriş 35. Pin haritası §12'de.

Elektrik
Pin 3,3 V verir ve en fazla ~12 mA taşır. LED'e 220 Ω direnç şart; motor, röle, lamba pinden sürülmez (§6). 5 V çıkışlı bir şeyi giriş pinine doğrudan bağlama. Her şeyin GND'si ortak olmalı — ortak toprak yoksa hiçbir sinyal anlamlı değil.

3. Buton: yoklama mı, kesme mi

İki yol var. Yoklama (polling): döngüde pini okursun. Kesme (IRQ): donanım, pin değiştiği anda senin fonksiyonunu çağırır; sen döngüde sadece "kaç basış oldu" diye sorarsın.

Genel
# yoklama
b = Pin(22, Pin.IN, Pin.PULL_UP)
onceki = 1
while True:
    v = b.value()
    if onceki == 1 and v == 0:
        print('basildi')
    onceki = v
    time.sleep_ms(20)

# kesme
def isr(pin): sayac[0] += 1
b.irq(trigger=Pin.IRQ_FALLING, handler=isr)

Kablolama: buton bir ucu pine, diğeri GND'ye. Direnç yok, PULL_UP içeride; basılı = 0.

CYD Studio'da
projects/buton_ornek.py
import cyd

def basinca(b):
    print('basis #%d' % b.sayac)   # kısa tut

b = cyd.buton(22, fn=basinca)      # pin ↔ GND
acik = False
while not cyd.should_stop():
    if b.basildi():                # son sorudan beri
        acik = not acik
        cyd.led(False, acik, False)
    cyd.sleep(0.05)

b.sayac toplam, b.basili() şu an, sekme_ms=40 titreşim süzme. Proje bitince kesme kendiliğinden kapanır.

Neden

Yoklama, döngün başka bir şeyle meşgulken basışı kaçırır. Kesme donanımdan sayar; sen 1 saniye sonra sorsan da basış orada durur.

Kesme fonksiyonunun içinde print, bellek ayırma, uzun iş yapılmaz; cyd.buton senin fn'ini kesme dışına (micropython.schedule) taşıdığı için orada print serbest, ama yine kısa tut.

Buton "sekme" yapar: bir basışta 5–10 titreşim. 40 ms içindeki tekrarlar sayılmaz.

Ölçüldü — 50 taklit basış, 60 ms
Yanında ne koşuyorKesme (cyd.buton)10 ms yoklama
hiçbir şey50/5050/50
2 küçük proje + ekrana çizen proje50/5034/50
+ CPU'yu bırakmayan bir thread50/505/50
Tek proje çalışırken standart yoklama da 50/50 yakalar — sıradan yazılmış kod tek başına mağdur olmaz; komşu proje gelince fark açılır.

4. Analog okuma (ADC)

Potansiyometre, LDR, NTC, pil seviyesi 0/1 değil, sürekli voltaj verir. ADC bunu sayıya çevirir. Her pin ADC yapamaz; giriş asla 3,3 V'u geçmemeli.

Genel
from machine import ADC, Pin
a = ADC(Pin(35))
a.atten(ADC.ATTN_11DB)   # ESP32: 0–3,3 V
ham = a.read()            # 0–4095
volt = ham / 4095 * 3.3

# NodeMCU: ADC(0).read() → 0–1023
LDR gibi direnç sensörü tek başına okunmaz, gerilim bölücü kurulur: 3,3V ──[ LDR ]──●──[ 10k ]── GND │ ADC pini
CYD Studio'da
projects/gece_lambasi.py (öz)
dusuk = yuksek = None
yanik = False
while not cyd.should_stop():
    ham = cyd.light()           # CYD: GPIO34, NodeMCU: A0
    dusuk  = ham if dusuk  is None else min(dusuk, ham)
    yuksek = ham if yuksek is None else max(yuksek, ham)
    if n <= 8 or yuksek - dusuk < 20:
        cyd.sleep(0.3); continue   # hâlâ öğreniyor
    oran = (ham - dusuk) / (yuksek - dusuk)
    if not yanik and oran < 0.5 - 0.12:
        yanik = True;  cyd.led(True, True, True)
    elif yanik and oran > 0.5 + 0.12:
        yanik = False; cyd.led_off()
    cyd.sleep(0.3)
Neden

Sabit eşik yazma. "ham < 2000 ise karanlık" her odada, her kartta farklı sonuç verir (CYD 0–4095, NodeMCU 0–1023 döner). Proje ilk 8 ölçümde en düşük/en yüksek değeri öğrenip eşiği oran olarak kurar.

Histerezis (%12 ölü bant): eşiğin tam üstünde gezen değer LED'i titretir. Yanmak için 0,38'in altına, sönmek için 0,62'nin üstüne çıkması gerekir.

Gürültülü analogda ayrıca 5–10 ölçümün ortalamasını almak yaygındır.

Ölçüldü
CYD'nin kart üstü LDR'si (GPIO34) çalışıyor ama aralığı dar: oda ışığında ~146/4095, elle kapatınca ~80. Bu yüzden proje ölçeği kendisi öğreniyor.

5. Zaman ve bekleme

Üç ayrı ihtiyaç var: beklemek, süre ölçmek, periyodik iş yapmak. Hepsini sleep ile çözmeye çalışmak klasik hata.

Genel
import time
time.sleep(1)          # beklemek (bloke)
time.sleep_ms(20)

t0 = time.ticks_ms()   # süre ölçmek
...
gecen = time.ticks_diff(time.ticks_ms(), t0)

# periyodik: sleep ile DEĞİL, saatle
son = time.ticks_ms()
while True:
    if time.ticks_diff(time.ticks_ms(), son) >= 1000:
        son = time.ticks_ms()
        olc()
    kisa_isler()

ticks_ms() taşar (sarar); farkı her zaman ticks_diff ile al, çıkarma yapma.

CYD Studio'da
projects/hareket_led.py (ısınma bölümü)
ISINMA_SN = 45
basla = time.ticks_ms()
while not cyd.should_stop():
    if time.ticks_diff(time.ticks_ms(), basla) >= ISINMA_SN * 1000:
        break                       # 45 sn doldu
    led(True)
    if not cyd.sleep(0.12): break   # bu arada LED yanıp söner
    led(False)
    if not cyd.sleep(0.12): break

PIR sensörü ilk 45 sn'de saçmalar; proje o süreyi bekler ama kilitlenmez: LED yanıp söner, Durdur çalışır.

Neden

time.sleep(45) yazsaydık: 45 sn boyunca Durdur işlemez, panel o projeyi "takıldı" sanır. Uzun süreyi kısa uykular + saat ile geçiriyoruz.

Bekleme süresi ne olmalı? Buton 20–50 ms, sensör 0,5–2 sn, ekran yenileme 100–500 ms. Daha sık okumak kazandırmaz, sadece komşu projelerden zaman çalar.

Normal modda (§8) cyd.sleep aynı zamanda diğer projelere sıra verdiğin andır — hiç sleep'siz döngü komşuları bekletir.

6. Güç: LED, röle, ULN2803, PWM

Pin bir sinyaldir, güç kaynağı değil. Kural: pin sadece tetikler; yükü başka bir şey sürer.

Genel
LED (≤ 12 mA): pin ──[220Ω]──▶|── GND Röle / motor / 12 V lamba — ULN2803A: pin ──▶ ULN giriş (1B) ULN COM ── +12 V ← flyback diyotları için ULN çıkış (1C) ── yükün (−) ucu yükün (+) ucu ── +12 V ULN GND ── kart GND ── adaptör (−) ← ortak toprak
# "analog çıkış" yok: PWM var
from machine import PWM
pwm = PWM(Pin(22), freq=1000)
pwm.duty(512)      # %50 parlaklık (ESP32 0–1023)
CYD Studio'da
projects/uln2803_yuk.py (öz)
KANALLAR = (27,)
pinler = [Pin(p, Pin.OUT, value=0) for p in KANALLAR]

def hepsi(deger):
    for p in pinler: p.value(deger)

try:
    while not cyd.should_stop():
        hepsi(1); ekran(True, tur)
        if not cyd.sleep(PERIYOT): break
        hepsi(0); ekran(False, tur)
        if not cyd.sleep(PERIYOT): break
finally:
    hepsi(0)          # 12 V yük açık kalmasın

value=0 ile kurmak: pin açıldığı an yük kapalı. Röleyi "bir an tık" diye açmamak için.

Neden

Dirençsiz LED 100 mA+ çeker; ESP32 pini 12 mA içindir — pin ya da çip gider.

ULN2803 sekiz Darlington transistör + flyback diyot: motor/röle bobini kapanırken oluşan ters gerilimi yutar. Transistörü tek tek kurmaktansa tek çip.

Kartın 3,3 V regülatörü küçük: sensörleri besler, motoru beslemez. Motorun kendi kaynağı olur, toprak ortaklanır.

ESP8266'da PWM 0–1023, Pico'da duty_u16 0–65535 — sayı kartta değişir, fikir değişmez.

7. Veri yolları: I2C, SPI, UART

Sıcaklık sensörü, OLED, GPS gibi cihazlar 0/1 değil byte konuşur. Üç standart dil var; datasheet hangisini söylüyorsa o.

YolTelNe içinNasıl bulunur
I2CSDA, SCL + GNDsensörlerin çoğu, OLED, PCF8574; bir hatta çok cihaz, her birinin adresi vari2c.scan() adresleri listeler
SPISCK, MOSI, MISO, CShızlı: ekran, SD kart, radyoCS pini cihazı seçer
UARTTX↔RX çapraz + GNDGPS, GSM, başka bir kartuart.read() ham byte
Genel
from machine import SoftI2C, Pin
i2c = SoftI2C(scl=Pin(27), sda=Pin(22))
print(i2c.scan())        # [118] → 0x76

# ham okuma: adres, kayıt, kaç byte
i2c.readfrom_mem(0x76, 0xD0, 1)

# pratikte sürücü kullanılır
import bme280
s = bme280.BME280(i2c)
s.oku()

SoftI2C herhangi iki pinde çalışır; hız gerekmiyorsa (sensörler için gerekmez) donanım I2C'ye ihtiyaç yok.

CYD Studio'da
projects/ornek_bme280.py (öz)
SDA, SCL = 22, 27       # CYD'nin iki boş pini

def sensor_bagla():
    while not cyd.should_stop():
        try:
            i2c = SoftI2C(sda=Pin(SDA), scl=Pin(SCL), freq=100000)
            s = bme280.BME280(i2c)
            print('sensor bagli: 0x%02X' % s.adres)
            return s
        except Exception as e:
            print('sensor yok (%s) - 2 sn sonra tekrar' % e)
            if not cyd.sleep(2): return None

Önce i2c_tara.py'yi çalıştır: hangi pin çiftinde hangi adres var, loga yazar.

Neden

Sensör bağlanmadan proje düşerse tek gördüğün bir traceback. "Bulana kadar dene, her 2 sn'de bir söyle" kalıbı hem kabloyu düzeltme fırsatı verir hem logu okunur tutar.

bme280.py projeler klasöründe duran sıradan bir modül: proje bitince sys.modules'ten silinir, düzenlersen bir sonraki çalıştırmada yeni sürüm koşar.

UART örneği: kopru_uart.py CYD'yi USB'si bozuk bir kart için programlayıcıya çevirir — GPIO27 TX, GPIO22 RX.

8. Çoklu proje: Normal / Thread / Solo

Buraya kadar olan her şey tek proje içindi. CYD Studio'nun asıl işi birden fazla projeyi yan yana koşturmak; her satırdaki N / T / S cipi bunun seçimidir.

ModNasıl koşarHeapDahili RAMNe için
N Normaltek ortak iş parçacığında asyncio görevi2–15 KB0LED, sensör, küçük sayfa — çoğu şey
T Threadkendi thread'i, GIL ile dilimliaynı10–15 KBekran çizen, sleep'siz döngü, ağır hesap
S Solopanel kapalı, kart yeniden başlar, tek projehepsihepsiBLE, HTTPS
Genel

Bir kartta iki işi aynı anda yapmanın iki klasik yolu: thread (işletim sistemi sırayla koşturur, her birinin kendi yığını) ve işbirlikçi görev (asyncio: her görev await dediğinde sırayı bırakır, tek yığın).

import asyncio
async def led():
    while True:
        ...; await asyncio.sleep(0.5)
async def sensor():
    while True:
        ...; await asyncio.sleep(2)
asyncio.run(asyncio.gather(led(), sensor()))
CYD Studio'da

Sen düz yazarsın:

while not cyd.should_stop():
    ...
    cyd.sleep(0.5)

Normal modda sistem bunu satır satır async def içine sarar, cyd.sleep( → await yapar, geçici dosyaya yazıp oradan derler. Sen async görmezsin.

Bekleme çağrısı bir def içindeyse dönüşüm uymaz → proje kendiliğinden T'de açılır, log nedenini yazar.

İstersen kendin de yazabilirsin: dosyada async def main(): varsa dokunulmaz.

Neden thread değil

Thread'in yığını dahili DRAM'den çıkar: proje başına 10–15 KB. Panel açıkken 55 KB var → en fazla 3–4 proje. Normal modda görev başına dahili RAM 0.

Ölçüldü: 7 küçük proje yan yana, dahili RAM 37 KB'de sabit kaldı; sınır heap oldu. ESP8266'da (thread hiç yok) 3 proje.

Bedeli: tek iş parçacığı olduğu için sleep'siz proje komşularını bekletir. O yüzden T var.

Ölçüldü — 10 ms'de tur atan proje, turlar arası en uzun boşluk
Senaryoortalamaen uzun
tek başına, N12,9 ms21 ms
+ 4 normal proje (LED/LDR)15,9 ms352 ms
+ ekrana çizen proje (N)25,5 ms973 ms
+ CPU bırakmayan thread211 ms3,4 sn
aynı yük, proje T'de12,0 ms37 ms
Kural: ekrana çizen ve ağır hesap yapan → T; buton gibi girişler → cyd.buton (kesme, zamanlamadan bağımsız). Gerisi N.

9. Projenin web sayfası

Bir projenin telefondan kontrol edilmesi istenir: "yükü aç/kapat", "sıcaklığı göster". Klasik yol projenin kendi HTTP sunucusunu açmasıdır. Bizde yasak — ve daha kolayı var.

Genel
import socket
s = socket.socket()
s.bind(('0.0.0.0', 8080)); s.listen(1)
while True:
    c, a = s.accept()
    istek = c.recv(512)
    c.send(b'HTTP/1.1 200 OK\r\n\r\n<h1>selam</h1>')
    c.close()

Her projede aynı 15 satır, ikinci port, isteği elle ayrıştırma.

CYD Studio'da
projects/yuk_kontrol.py (öz)
mod = 'kapali'

def r_sayfa(istek):
    return SAYFA            # str → html

def r_ayar(istek):
    global mod
    mod = istek['sorgu'].get('mod', mod)
    return {'mod': mod}     # dict → json

cyd.rota('/yuk', r_sayfa)
cyd.rota('/yuk/ayar', r_ayar)

try:
    while not cyd.should_stop():
        yuk.value(1 if mod == 'acik' else 0)
        cyd.sleep(0.02)
finally:
    cyd.rota_sil()

Sayfa http://<kart>/yuk'ta, panelde "Uygulamalar:" satırında link olarak görünür.

Neden

İkinci dinleyici soket lwIP tamponlarıyla dahili RAM'i yiyor: ölçüldü, panel kilitlendi. Tek soket, tek port, aynı sunucu.

İşleyici sunucunun iş parçacığında koşar: içinde bekleme yapma, sadece değişken oku/yaz. Asıl işi (pin sürme) ana döngü yapar — sayfa ile proje bir değişken üzerinden konuşur (mod).

Proje bitince rotalar silinir; başka bir projenin /yuk'u kalmaz.

10. Log ve hata ayıklama

Genel

Mikrodenetleyicide ekran yok; hata ayıklamanın tek aracı seri porta print. Kart resetlenince her şey uçar; USB takılı değilse hiçbir şey görmezsin.

try:
    ...
except Exception as e:
    print('hata:', e)     # seri porta
CYD Studio'da

print() üç yere gider: panelin Konsol sekmesi (canlı), /logs/<proje>.log (kalıcı, 8 KB/proje, resetten sonra da durur), seri port.

if n % 10 == 0:                # her turda değil
    print('%d kez yanip sondu' % n)

try:
    ham = cyd.light()
except OSError as e:
    print('sensor okunamadi:', e)
    break                      # düşme, açıkla, çık

Panelde projeyi seç → Kayıtlı log: Solo modda ya da reset sonrası bile ne olduğunu görürsün.

Neden

Saniyede 12 satırdan fazla yazan proje kısılır (fazlası sayılır, "N satır atlandı" der): sıkı döngüde print paneli boğuyordu.

Flash ömrü için satırlar toplu yazılır; reset öncesi tampon diske indirilir — "yanıt vermiyor, resetleniyor" satırının kaybolmaması için bunu bizzat düzelttik.

Hatayı yakalayıp açıklayıp çıkmak, traceback'le düşmekten iyidir: log "sensör yok, 2 sn sonra tekrar" der, sen kabloya bakarsın.

11. Bellek gerçeği

PC'de bellek düşünmezsin; burada her KB'nin sahibi var. İki cep: Python heap (projenin verisi) ve dahili DRAM (WiFi, soketler, thread yığınları, BLE/TLS).

CYD (ESP32)Stok MicroPythonCYD Studio firmware'i
Dahili RAM boş, panel açık27,8 KB55 KB
Python heap boş80 / 137 (otomatik büyür)75 / 94 (sabit 96)
Aynı anda proje17 normal + 2–3 thread
NodeMCU heap boş2,5–4,8 KB24,7 KB
Genel

Yaygın kurallar: büyük string üretme (birleştirme kopya yapar), listeyi büyütmek yerine sabit bytearray kullan, ara ara gc.collect(), kaç KB kaldığına gc.mem_free() ile bak.

import gc
gc.collect()
print(gc.mem_free())
CYD Studio'da

Panelin sistem kutusu iki cebi ayrı gösterir; projede cyd.dram() dahili RAM'i verir. BLE/HTTPS 33–45 KB bitişik dahili RAM ister → o projeler S modunda.

SAYFA = """<html>...</html>"""   # 3 KB string: tamam
veri = bytearray(20000)            # 20 KB: heap'ten, ölçüldü
foto = requests.get(url).content   # 200 KB: SIĞMAZ

Büyük veri parça parça: ornek_sunucuya_yukle.py 512 baytlık dilimlerle gönderir.

Neden firmware'i kendimiz derledik

Stokta heap kendini ikiye katlayarak büyüyor ve dahili RAM'i yutuyordu (76 → 12 KB); çekirdek modüller RAM'e yükleniyordu. Çekirdeği flash'a dondurduk, heap'i sabitledik, WiFi tamponlarını küçülttük. Sen yine .py yazarsın; derleme bizim işimiz.

Canlı log halkası 250 satırken 20,7 KB tutup heap'i parçalıyordu → 100 satır. Ekrana çizen proje 25–33 KB heap alır (durunca geri gelir).

12. Kart farkları ve pin haritası

Aynı proje iki kartta çalışsın diye kart farkları koda değil veriye konuldu: her kartın cyd.py'si bir OZELLIKLER sözlüğü bildirir, olmayan donanım açık hata fırlatır (sessizce yanlış çalışmasın).

Genel

Kartlar arasında değişen: pin numaraları, ADC aralığı (4095 / 1023), PWM aralığı, thread var mı, ekran var mı, kaç KB RAM. Değişmeyen: §1–§7'deki her fikir.

CYD Studio'da
PIN = getattr(cyd, 'D1', 22)       # pin etiketi

d = None
try:
    d = cyd.display()             # ekran yoksa hata
except Exception as e:
    print('ekran yok:', e)

BUYUK = d.height >= 160           # 320×240 mı 128×64 mü
if BUYUK: OLCEK = 3
else:     OLCEK = 1
Neden

ornek_bme280.py CYD'de büyük yazı, OLED kartta tek satır çizer — aynı dosya. Ekran yoksa proje düşmez, sadece loga yazar. "Bu kartta yok" bilgisi /api/caps ile panele de gider; panel olmayan düğmeyi gizler.

CYD (ESP32-2432S028R) — sana kalan pinler

PinNeKullan
GPIO22Extended IO konnektörügiriş/çıkış — buton, LED, I2C SDA; ana pinin
GPIO27Sıcaklık/nem konnektörügiriş/çıkış — ikinci pin, I2C SCL, ULN2803
GPIO35Extended IOsadece giriş, ADC var, pull-up yok
GPIO34LDR (kart üstü)cyd.light()
GPIO4 / 16 / 17RGB LEDcyd.led(r,g,b) — ters mantık, sen düşünme
GPIO26hoparlörcyd.beep(freq, ms)
GPIO21ekran arka ışığıdokunma
GPIO1 / 3USB konsoldokunma ("TX/RX" etiketli konnektör de bu)
2, 12–15, 18, 19, 23, 25, 32, 33, 36, 39ekran, dokunmatik, SDdokunma

NodeMCU (ESP8266)

EtiketGPIOKullan
D1, D25, 4rahat giriş/çıkış; I2C için standart çift (SCL=D1, SDA=D2)
D5, D6, D714, 12, 13rahat giriş/çıkış (SPI ile ortak)
D42dahili mavi LED, ters mantık — cyd.led()
D3, D8, D00, 15, 16açılışta özel görev; kaçın
A0—tek analog, 0–1023, en fazla 3,3 V (kart üstünde bölücü var)

Kodda cyd.D1 gibi yazarsın; sayıyı ezberlemene gerek yok.

13. Şablon ve kontrol listesi

yeni_proje.py
# NE YAPAR: tek cümle
# KABLOLAMA: pin ↔ ne
import cyd
from machine import Pin

PIN = getattr(cyd, 'D1', 22)
ARALIK = 0.5

# --- kurulum ---
cikis = Pin(PIN, Pin.OUT, value=0)
d = None
try:
    d = cyd.display()
except Exception:
    pass

print('basladi: GPIO%d, %.1f sn' % (PIN, ARALIK))

# --- döngü ---
try:
    while not cyd.should_stop():
        ...                          # iş
        if not cyd.sleep(ARALIK):
            break
finally:
    cikis.value(0)                   # temizlik: her koşulda
    print('bitti')

Yeni bir sensörü bağlarken

  1. Datasheet: besleme 3,3 mü 5 mi, çıkış dijital / analog / I2C / UART → hangi pin türü.
  2. Bağla: VCC, GND, sinyal. I2C ise i2c_tara.py → adres görünmeli.
  3. Önce ham değeri döngüde print et — anla, sonra karar kodu yaz.
  4. Bekleme süresini sensörün hızına göre seç; eşiği sabit yazma, ölçüp oranla.
  5. Komşusu olacaksa: ekran çiziyor ya da ağır hesap → T; giriş yakalıyor → cyd.buton.

Çalıştırmadan önce

14. Yeni modül: datasheet'ten sürücüye

Elinde hiç görmediğin bir modül var; internetteki kütüphane kartına uymuyor ya da yok. Sürücü yazmak sanıldığından kısa: datasheet'ten üç bilgi alırsın, gerisi §7'deki I2C çağrılarıdır. Örnek: PCF8574 I2C giriş/çıkış çoklayıcı (8 ek pin, LCD1602 "backpack"lerin altındaki çip).

Datasheet'ten alınacak üç şey

1. Adres: PCF8574 0x20–0x27 (A0–A2 pinleri), PCF8574A 0x38–0x3F. i2c.scan() hangisi olduğunu söyler.

2. Protokol: kayıt yok. Bir byte yazarsan 8 çıkış o olur; bir byte okursan 8 pinin durumu gelir. Bit 0 = P0 … bit 7 = P7.

3. Tuhaflık: "yarı-çift yönlü" pinler. Giriş olarak kullanacağın pine önce 1 yaz (zayıf pull-up), sonra oku. Çıkış olarak 0 yazınca güçlü çeker (LED'i GND'ye değil VCC'ye bağla, 0 = yanık), 1 yazınca sadece zayıf 100 µA verir. INT pini herhangi bir giriş değişince LOW'a düşer.

Sürücü: 20 satır
projects/pcf8574.py
class PCF8574:
    def __init__(self, i2c, adres=0x20):
        self.i2c = i2c
        self.adres = adres
        self.cikis = 0xFF          # hepsi 1: giriş/boşta
        self._yaz()

    def _yaz(self):
        self.i2c.writeto(self.adres, bytes([self.cikis]))

    def pin_yaz(self, p, deger):   # p: 0–7
        if deger: self.cikis |= (1 << p)
        else:     self.cikis &= ~(1 << p) & 0xFF
        self._yaz()

    def oku(self):                 # 8 pin, byte
        return self.i2c.readfrom(self.adres, 1)[0]

    def pin_oku(self, p):
        return (self.oku() >> p) & 1
Projede kullanımı ve neden
projects/pcf_ornek.py
import cyd
from machine import SoftI2C, Pin
from pcf8574 import PCF8574

i2c = SoftI2C(scl=Pin(27), sda=Pin(22))
print('i2c:', [hex(a) for a in i2c.scan()])
x = PCF8574(i2c, 0x20)
x.pin_yaz(0, 1)              # P0 giriş: buton P0↔GND
while not cyd.should_stop():
    basili = x.pin_oku(0) == 0
    x.pin_yaz(7, 0 if basili else 1)   # P7 LED (VCC'ye)
    cyd.sleep(0.05)

Sınıfı ayrı dosyaya koymak: proje bitince modül boşaltılır, düzeltirsen yeni sürüm koşar. Yoklama 50 ms: I2C üzerinden kesme yok (INT pinini kartın bir pinine bağlayıp cyd.buton(pin) ile yakalarsan var). PCF8574 hız için değil, pin sayısı için: CYD'nin 2 boş pinine 8 ekler.

Sürücü yazarken genel sıra
1) scan() ile adresi gör. 2) Datasheet'in "register map" tablosundan kimlik kaydını oku (varsa, ör. BME280 0xD0 → 0x60): doğru çipe ulaştığını kanıtlar. 3) Tek bir ölçümü ham byte olarak yazdır. 4) Datasheet'teki dönüşüm formülünü uygula. 5) Sınıfa sar, oku() ver. Her adımda print — bir adım çalışmadan sonrakine geçme.

15. Sık modüller: hızlı referans

Her satır: bağlantı, kütüphane (firmware'de gömülü olanlar işaretli), 5 satırlık çekirdek kod, tuzak. Hepsi §13 şablonunun içine, döngüye girer.

ModülBağlantıÇekirdek kodTuzak
DS18B20 sıcaklık (1-Wire)VCC 3,3 · GND · DATA→pin, DATA–VCC arası 4,7 kΩ
import onewire, ds18x20   # gömülü
ow = ds18x20.DS18X20(onewire.OneWire(Pin(22)))
roms = ow.scan()
ow.convert_temp(); cyd.sleep(0.75)
print(ow.read_temp(roms[0]))
convert → 750 ms bekle → oku; aynı hatta çok sensör olur, her birinin ROM kimliği var
DHT11 / DHT22 nem+sıcaklıkVCC · GND · DATA→pin (modülde pull-up var)
import dht                # gömülü
s = dht.DHT22(Pin(22))
s.measure()
print(s.temperature(), s.humidity())
en sık 2 sn'de bir oku; ilk ölçüm hata verebilir, try ile geç
HC-SR04 mesafeVCC 5 V · GND · TRIG→pin · ECHO→pin bölücüyle (5 V çıkış!)
from machine import time_pulse_us
trig = Pin(22, Pin.OUT); echo = Pin(35, Pin.IN)
trig.value(1); time.sleep_us(10); trig.value(0)
us = time_pulse_us(echo, 1, 30000)
cm = us / 58
ECHO 5 V'tur: 1 kΩ + 2 kΩ bölücü; −1/−2 dönerse zaman aşımı
Servo SG90kırmızı 5 V · kahve GND · turuncu→pin
pwm = PWM(Pin(22), freq=50)
def aci(a):              # 0–180
    pwm.duty(int(26 + a / 180 * 102))   # ESP32 0–1023
servoyu kartın 3,3 V'undan besleme; 50 Hz şart; darbe 0,5–2,5 ms
Röle modülüVCC 5 V · GND · IN→pin
r = Pin(22, Pin.OUT, value=1)   # çoğu modül LOW=çeker
r.value(0)   # aç
r.value(1)   # kapat
çoğu modül ters mantık; 230 V tarafı: sadece bilen dokunur
Döner enkoderCLK→pin · DT→pin · SW→pin · GND (pull-up'lar içeride)
clk = Pin(22, Pin.IN, Pin.PULL_UP); dt = Pin(27, Pin.IN, Pin.PULL_UP)
sayac = [0]
def don(p): sayac[0] += -1 if dt.value() else 1
clk.irq(trigger=Pin.IRQ_FALLING, handler=don)
yoklama adım kaçırır → kesme; sekmeye karşı 2 ms'den sık sayma
NeoPixel WS28125 V · GND · DIN→pin (330 Ω seri)
import neopixel          # gömülü
np = neopixel.NeoPixel(Pin(22), 8)
np[0] = (255, 0, 0); np.write()
çok LED çok akım (60 mA/LED beyazda): ayrı 5 V; veri 3,3 V genelde yeter
LCD1602 I2C (PCF8574 backpack)VCC 5 V · GND · SDA · SCL
i2c.scan()   # 0x27 ya da 0x3F
# lcd_api.py + i2c_lcd.py (projects/)
lcd = I2cLcd(i2c, 0x27, 2, 16)
lcd.putstr('CYD Studio')
arka kontrast potu; 5 V modül SDA/SCL'de 5 V'a çekebilir — çoğu kart tolere eder, emin değilsen 3,3 V modül al
Potansiyometreuçlar 3,3 V ve GND · orta→ADC
a = ADC(Pin(35)); a.atten(ADC.ATTN_11DB)
yuzde = a.read() * 100 // 4095
5 V'a bağlama; uçlarda titreme için 5 ölçüm ortalaması
MQTT (sunucuya veri)WiFi
from umqtt.simple import MQTTClient   # gömülü
c = MQTTClient('cyd', '192.168.0.33'); c.connect()
c.publish(b'ev/sicaklik', b'23.5')
normal modda düz MQTT olur, TLS'li (8883) için S modu

16. Kablodan panel (USB köprüsü)

WiFi yoksa ya da PC internetten kopmasın istiyorsan panel kablodan gelir: kartta usbkopru.py, PC'de tools/usb_panel.py.

python tools\usb_panel.py COM4 --board nodemcu     # CYD için: COM3 --board cyd
# tarayıcı: http://127.0.0.1:8080/   (localhost DEĞİL - Windows IPv6'yı önce dener, 2 sn gecikir)
Nasıl çalışıyor

Panel sayfası PC'deki dosyadan anında yüklenir; sadece /api/… istekleri seri porttan gider. İstek tek satırlık base64 çerçeve; kart aynı sunucu kodunu (server._route) sahte bir bağlantıyla çalıştırıp cevabı geri akıtır. Kartın konsol çıktısı da aynı pencerede akar.

Ölçüldü (ESP8266)

durum 0,6–1 sn · kaydet/çalıştır/log/durdur 0,2–0,9 sn · 11 KB doküman 1,5 sn. 384 bayttan büyük gövdeler 192 baytlık onaylı parçalarla gider: onaysız akışta kartın seri tamponu taşıp parça bozuluyordu (incorrect padding).

Sınırlar

Köprü açıkken COM portu meşguldür: deploy için köprüyü Ctrl+C ile kapat. Aynı anda tek sekme. WiFi'li panel yanında çalışmaya devam eder.

Kaynaklar: kart üstü belge device/docs.md (ölçümler), kartın Doküman düğmesi (Shift+tık: karttaki kısa belge), /projects altındaki örnekler. Sayılar CYD ve NodeMCU üzerinde Eylül 2026'da ölçüldü; kart ya da firmware değişirse yeniden ölçülmeli.
CYD Studio · MicroPython · ESP32 / ESP8266

Workshop Notebook

Every chapter has three parts: General — how it works on any microcontroller · In CYD Studio — real code from our projects · Why — what we measured on the board and decided from it. Numbers in green boxes are measurements, not estimates (September 2026).

1. Project skeleton

A microcontroller project always has the same shape: set up → do work in a loop → clean up on exit. Arduino calls it setup()/loop(); in MicroPython it is a plain script.

General
from machine import Pin
import time

led = Pin(2, Pin.OUT)      # setup
while True:                # forever
    led.value(1)
    time.sleep(0.5)
    led.value(0)
    time.sleep(0.5)

No way to stop it: you pull the plug.

In CYD Studio
projects/led_yakip_sondur.py
import cyd
n = 0
try:
    while not cyd.should_stop():
        n += 1
        cyd.led(g=True)
        if not cyd.sleep(0.5): break
        cyd.led_off()
        if not cyd.sleep(0.5): break
finally:
    cyd.led_off()   # never leave it lit
print('stopped - %d blinks' % n)
Why

while not cyd.should_stop() instead of while True: the panel's Stop button raises a flag; the loop checks it every turn and exits cleanly.

cyd.sleep() watches the flag while waiting and returns False when interrupted — Stop lands within 3 s even in the middle of a 30-second sleep.

try/finally: however the project ends, the hardware is left clean. Think of a relay left switched on.

Measured
A project that ignores the flag is marked "stuck" after 3 s; if it is the only one running the board resets itself (panel back in ~8 s). Even in the worst case the board is never lost.

2. Pins: digital in / out

A GPIO pin is one wire; it either drives (output: 0 V or 3.3 V) or listens (input: reads the outside voltage as 0/1). Threshold roughly: below 0.8 V = 0, above 2 V = 1; in between is undefined.

General
p = Pin(22, Pin.OUT)
p.value(1)          # 3.3 V
p.value(0)          # 0 V

g = Pin(27, Pin.IN, Pin.PULL_UP)
g.value()           # 0 or 1

A floating input is an antenna and reads noise. PULL_UP/PULL_DOWN answers "what is it when nothing is connected".

In CYD Studio
projects/harici_led.py
from machine import Pin
import cyd

# CYD: GPIO22. NodeMCU: D1 (GPIO5).
PIN = getattr(cyd, 'D1', 22)
led = Pin(PIN, Pin.OUT)
led.value(0)

while not cyd.should_stop():
    led.value(1)
    if not cyd.sleep(0.5): break
    led.value(0)
    if not cyd.sleep(0.5): break

For the on-board RGB LED you need no pin number: cyd.led(r,g,b).

Why

getattr(cyd, 'D1', 22): the NodeMCU has no pin 22 — ValueError: invalid pin. We ask the board for its label so the same file runs on both. (This really happened: the project looked like it "ran one second and died".)

The CYD has very few free pins: display, touch, SD and LED take most. Yours are 22, 27 and input-only 35. Pin map in §12.

Electrical
A pin gives 3.3 V and carries at most ~12 mA. An LED needs a 220 Ω resistor; motors, relays, lamps are never driven from a pin (§6). Never wire a 5 V output straight into an input pin. Everything shares one GND — without a common ground no signal means anything.

3. Buttons: polling or interrupt

Two ways. Polling: read the pin in the loop. Interrupt (IRQ): the hardware calls your function the moment the pin changes; in the loop you only ask "how many presses so far".

General
# polling
b = Pin(22, Pin.IN, Pin.PULL_UP)
prev = 1
while True:
    v = b.value()
    if prev == 1 and v == 0:
        print('pressed')
    prev = v
    time.sleep_ms(20)

# interrupt
def isr(pin): count[0] += 1
b.irq(trigger=Pin.IRQ_FALLING, handler=isr)

Wiring: one leg to the pin, the other to GND. No resistor, PULL_UP is internal; pressed = 0.

In CYD Studio
projects/buton_ornek.py
import cyd

def on_press(b):
    print('press #%d' % b.sayac)   # keep it short

b = cyd.buton(22, fn=on_press)     # pin ↔ GND
on = False
while not cyd.should_stop():
    if b.basildi():                # presses since last ask
        on = not on
        cyd.led(False, on, False)
    cyd.sleep(0.05)

b.sayac total, b.basili() held right now, sekme_ms=40 debounce. The IRQ is released when the project ends.

Why

Polling misses a press while your loop is busy elsewhere. The interrupt counts in hardware; ask a second later and the press is still there.

Inside an IRQ handler: no print, no allocation, nothing long. cyd.buton moves your fn out of the IRQ (micropython.schedule), so print is allowed there — still keep it short.

Buttons bounce: one press is 5–10 edges. Repeats within 40 ms are not counted.

Measured — 50 simulated presses, 60 ms each
Running next to itIRQ (cyd.buton)10 ms polling
nothing50/5050/50
2 small projects + a display-drawing project50/5034/50
+ a thread that never yields the CPU50/505/50
With a single project plain polling also scores 50/50 — ordinary code is fine alone; the gap opens when neighbours arrive.

4. Analog reading (ADC)

Potentiometers, LDRs, NTCs, battery level give a continuous voltage, not 0/1. The ADC turns it into a number. Not every pin has an ADC; the input must never exceed 3.3 V.

General
from machine import ADC, Pin
a = ADC(Pin(35))
a.atten(ADC.ATTN_11DB)   # ESP32: 0–3.3 V
raw = a.read()            # 0–4095
volt = raw / 4095 * 3.3

# NodeMCU: ADC(0).read() → 0–1023
A resistive sensor (LDR) is not read on its own; build a voltage divider: 3.3V ──[ LDR ]──●──[ 10k ]── GND │ ADC pin
In CYD Studio
projects/gece_lambasi.py (core)
low = high = None
lit = False
while not cyd.should_stop():
    raw = cyd.light()           # CYD: GPIO34, NodeMCU: A0
    low  = raw if low  is None else min(low, raw)
    high = raw if high is None else max(high, raw)
    if n <= 8 or high - low < 20:
        cyd.sleep(0.3); continue   # still learning
    ratio = (raw - low) / (high - low)
    if not lit and ratio < 0.5 - 0.12:
        lit = True;  cyd.led(True, True, True)
    elif lit and ratio > 0.5 + 0.12:
        lit = False; cyd.led_off()
    cyd.sleep(0.3)
Why

Never hard-code a threshold. "raw < 2000 means dark" differs per room and per board (CYD returns 0–4095, NodeMCU 0–1023). The project learns min/max over the first 8 readings and sets the threshold as a ratio.

Hysteresis (12% dead band): a value hovering at the threshold would flicker the LED. It must drop below 0.38 to switch on and rise above 0.62 to switch off.

For noisy analog inputs averaging 5–10 samples is common too.

Measured
The CYD's on-board LDR (GPIO34) works but its range is narrow: ~146/4095 in room light, ~80 when covered. That is why the project learns its own scale.

5. Time and waiting

Three separate needs: waiting, measuring a duration, doing something periodically. Solving all three with sleep is the classic mistake.

General
import time
time.sleep(1)          # wait (blocking)
time.sleep_ms(20)

t0 = time.ticks_ms()   # measure
...
elapsed = time.ticks_diff(time.ticks_ms(), t0)

# periodic: by the clock, NOT by sleep
last = time.ticks_ms()
while True:
    if time.ticks_diff(time.ticks_ms(), last) >= 1000:
        last = time.ticks_ms()
        measure()
    short_work()

ticks_ms() wraps around; always use ticks_diff, never plain subtraction.

In CYD Studio
projects/hareket_led.py (warm-up part)
WARMUP_S = 45
start = time.ticks_ms()
while not cyd.should_stop():
    if time.ticks_diff(time.ticks_ms(), start) >= WARMUP_S * 1000:
        break                       # 45 s over
    led(True)
    if not cyd.sleep(0.12): break   # blink meanwhile
    led(False)
    if not cyd.sleep(0.12): break

A PIR sensor is erratic for its first 45 s; the project waits without locking up: the LED blinks, Stop works.

Why

With time.sleep(45) Stop would be ignored for 45 s and the panel would call the project stuck. Long waits are done as short sleeps + the clock.

How long to sleep? Buttons 20–50 ms, sensors 0.5–2 s, screen refresh 100–500 ms. Reading more often gains nothing; it only steals time from neighbour projects.

In normal mode (§8) cyd.sleep is also the moment you hand the turn to other projects — a loop with no sleep stalls its neighbours.

6. Power: LEDs, relays, ULN2803, PWM

A pin is a signal, not a power supply. Rule: the pin only triggers; something else drives the load.

General
LED (≤ 12 mA): pin ──[220Ω]──▶|── GND Relay / motor / 12 V lamp — ULN2803A: pin ──▶ ULN input (1B) ULN COM ── +12 V ← for the flyback diodes ULN output (1C) ── load (−) load (+) ── +12 V ULN GND ── board GND ── adapter (−) ← common ground
# there is no "analog out": PWM instead
from machine import PWM
pwm = PWM(Pin(22), freq=1000)
pwm.duty(512)      # 50% brightness (ESP32 0–1023)
In CYD Studio
projects/uln2803_yuk.py (core)
CHANNELS = (27,)
pins = [Pin(p, Pin.OUT, value=0) for p in CHANNELS]

def all_(v):
    for p in pins: p.value(v)

try:
    while not cyd.should_stop():
        all_(1); screen(True, turn)
        if not cyd.sleep(PERIOD): break
        all_(0); screen(False, turn)
        if not cyd.sleep(PERIOD): break
finally:
    all_(0)          # never leave the 12 V load on

Creating with value=0: the load is off the instant the pin exists — no relay "click" at start.

Why

An LED without a resistor draws 100 mA+; an ESP32 pin is rated for 12 mA — the pin or the chip dies.

The ULN2803 is eight Darlington transistors plus flyback diodes: it absorbs the reverse spike a motor/relay coil produces when switched off. One chip instead of building transistor stages.

The board's 3.3 V regulator is small: it feeds sensors, not motors. The motor gets its own supply; grounds are joined.

ESP8266 PWM is 0–1023, the Pico uses duty_u16 0–65535 — the number changes per board, the idea does not.

7. Buses: I2C, SPI, UART

Temperature sensors, OLEDs, GPS modules speak bytes, not 0/1. Three standard languages; the datasheet tells you which.

BusWiresForHow to find
I2CSDA, SCL + GNDmost sensors, OLED, PCF8574; many devices on one bus, each with an addressi2c.scan() lists addresses
SPISCK, MOSI, MISO, CSfast: display, SD card, radioCS pin selects the device
UARTTX↔RX crossed + GNDGPS, GSM, another boarduart.read() raw bytes
General
from machine import SoftI2C, Pin
i2c = SoftI2C(scl=Pin(27), sda=Pin(22))
print(i2c.scan())        # [118] → 0x76

# raw read: address, register, count
i2c.readfrom_mem(0x76, 0xD0, 1)

# in practice you use a driver
import bme280
s = bme280.BME280(i2c)
s.oku()

SoftI2C works on any two pins; unless you need speed (sensors don't) hardware I2C is unnecessary.

In CYD Studio
projects/ornek_bme280.py (core)
SDA, SCL = 22, 27       # the CYD's two free pins

def connect():
    while not cyd.should_stop():
        try:
            i2c = SoftI2C(sda=Pin(SDA), scl=Pin(SCL), freq=100000)
            s = bme280.BME280(i2c)
            print('sensor at 0x%02X' % s.adres)
            return s
        except Exception as e:
            print('no sensor (%s) - retry in 2 s' % e)
            if not cyd.sleep(2): return None

Run i2c_tara.py first: it logs which address answers on which pin pair.

Why

If the project crashes when the sensor is unplugged, all you see is a traceback. "Retry until found, say so every 2 s" lets you fix the wire and keeps the log readable.

bme280.py is an ordinary module in the projects folder: it is unloaded from sys.modules when the project ends, so an edit runs on the next start.

UART example: kopru_uart.py turns the CYD into a programmer for a board with a broken USB port — GPIO27 TX, GPIO22 RX.

8. Many projects: Normal / Thread / Solo

Everything so far was one project. CYD Studio's real job is running several side by side; the N / T / S chip on each row is that choice.

ModeHow it runsHeapInternal RAMFor
N Normalasyncio task on one shared thread2–15 KB0LEDs, sensors, small pages — most things
T Threadown thread, sliced by the GILsame10–15 KBdisplay drawing, loops without sleep, heavy compute
S Solopanel off, board reboots, single projectallallBLE, HTTPS
General

Two classic ways to do two things at once on one board: threads (the OS alternates them, each with its own stack) and cooperative tasks (asyncio: each task yields at await, one stack).

import asyncio
async def led():
    while True:
        ...; await asyncio.sleep(0.5)
async def sensor():
    while True:
        ...; await asyncio.sleep(2)
asyncio.run(asyncio.gather(led(), sensor()))
In CYD Studio

You write it plainly:

while not cyd.should_stop():
    ...
    cyd.sleep(0.5)

In normal mode the system wraps it line by line into an async def, turns cyd.sleep( into await, writes a temp file and compiles from it. You never see async.

If a sleep call sits inside a def the transform does not apply → the project opens as T by itself and the log says why.

You may also write it yourself: a file containing async def main(): is left untouched.

Why not threads

A thread's stack comes out of internal DRAM: 10–15 KB per project. With the panel up there is 55 KB → 3–4 projects at most. In normal mode a task costs 0 internal RAM.

Measured: 7 small projects side by side, internal RAM flat at 37 KB; the heap became the limit. On the ESP8266 (no threads at all) 3 projects.

The price: one thread, so a project without sleep stalls its neighbours. That is what T is for.

Measured — a project looping every 10 ms, longest gap between turns
Scenarioaveragelongest
alone, N12.9 ms21 ms
+ 4 normal projects (LED/LDR)15.9 ms352 ms
+ a display-drawing project (N)25.5 ms973 ms
+ a thread that never yields211 ms3.4 s
same load, project in T12.0 ms37 ms
Rule: display drawing and heavy compute → T; inputs like buttons → cyd.buton (IRQ, independent of timing). Everything else N.

9. A project's web page

You want to control a project from a phone: "switch the load", "show the temperature". The classic way is a project opening its own HTTP server. Here it is forbidden — and there is an easier way.

General
import socket
s = socket.socket()
s.bind(('0.0.0.0', 8080)); s.listen(1)
while True:
    c, a = s.accept()
    req = c.recv(512)
    c.send(b'HTTP/1.1 200 OK\r\n\r\n<h1>hello</h1>')
    c.close()

The same 15 lines in every project, a second port, parsing requests by hand.

In CYD Studio
projects/yuk_kontrol.py (core)
mode = 'off'

def r_page(req):
    return PAGE             # str → html

def r_set(req):
    global mode
    mode = req['sorgu'].get('mod', mode)
    return {'mod': mode}    # dict → json

cyd.rota('/yuk', r_page)
cyd.rota('/yuk/ayar', r_set)

try:
    while not cyd.should_stop():
        load.value(1 if mode == 'acik' else 0)
        cyd.sleep(0.02)
finally:
    cyd.rota_sil()

The page lives at http://<board>/yuk and shows as a link in the panel's "Apps:" row.

Why

A second listening socket eats internal RAM through lwIP buffers: measured, the panel froze. One socket, one port, the same server.

The handler runs on the server's thread: no waiting inside, only read/write variables. The real work (driving the pin) is done by the main loop — page and project talk through one variable (mode).

Routes are removed when the project ends; no other project inherits /yuk.

10. Logs and debugging

General

A microcontroller has no screen; the only debugging tool is print to the serial port. A reset wipes everything; with USB unplugged you see nothing.

try:
    ...
except Exception as e:
    print('error:', e)    # to serial
In CYD Studio

print() goes to three places: the panel's Console tab (live), /logs/<project>.log (persistent, 8 KB per project, survives resets), and the serial port.

if n % 10 == 0:                # not every turn
    print('%d blinks' % n)

try:
    raw = cyd.light()
except OSError as e:
    print('sensor read failed:', e)
    break                      # don't crash: explain, exit

Select the project in the panel → Saved log: you see what happened even in Solo mode or after a reset.

Why

A project printing more than 12 lines per second is throttled (the rest are counted, "N lines skipped"): print in a tight loop used to choke the panel.

Lines are written in batches to spare the flash; the buffer is flushed before every reset — we fixed this ourselves so the "not responding, resetting" line is never lost.

Catch, explain, exit beats dying with a traceback: the log says "no sensor, retrying in 2 s" and you look at the wire.

11. The memory reality

On a PC you never think about memory; here every KB has an owner. Two pockets: the Python heap (project data) and internal DRAM (Wi-Fi, sockets, thread stacks, BLE/TLS).

CYD (ESP32)Stock MicroPythonCYD Studio firmware
Internal RAM free, panel up27.8 KB55 KB
Python heap free80 / 137 (auto-grows)75 / 94 (fixed 96)
Concurrent projects17 normal + 2–3 threads
NodeMCU heap free2.5–4.8 KB24.7 KB
General

Common rules: don't build big strings (concatenation copies), use a fixed bytearray instead of a growing list, call gc.collect() now and then, check what is left with gc.mem_free().

import gc
gc.collect()
print(gc.mem_free())
In CYD Studio

The panel's system box shows both pockets; in a project cyd.dram() returns internal RAM. BLE/HTTPS need 33–45 KB of contiguous internal RAM → those projects run in S.

PAGE = """<html>...</html>"""    # 3 KB string: fine
data = bytearray(20000)            # 20 KB: from the heap, measured
photo = requests.get(url).content  # 200 KB: WON'T FIT

Large data goes in pieces: ornek_sunucuya_yukle.py sends 512-byte slices.

Why we build our own firmware

Stock MicroPython grows its heap by doubling and swallowed internal RAM (76 → 12 KB); the core modules were loaded into RAM. We froze the core into flash, fixed the heap size and shrank the Wi-Fi buffers. You still write .py; compiling is our job.

The live log ring at 250 lines held 20.7 KB and fragmented the heap → 100 lines. A display-drawing project takes 25–33 KB of heap (returned on stop).

12. Board differences and pin map

So the same project runs on both boards, board differences live in data, not code: each board's cyd.py declares an OZELLIKLER dict, and missing hardware raises an explicit error (never silently misbehaves).

General

What changes between boards: pin numbers, ADC range (4095 / 1023), PWM range, whether threads exist, whether there is a display, how many KB of RAM. What doesn't: every idea in §1–§7.

In CYD Studio
PIN = getattr(cyd, 'D1', 22)       # pin label

d = None
try:
    d = cyd.display()             # raises if no display
except Exception as e:
    print('no display:', e)

BIG = d.height >= 160             # 320×240 or 128×64
if BIG: SCALE = 3
else:   SCALE = 1
Why

ornek_bme280.py draws large text on the CYD and a single line on the OLED board — same file. Without a display the project doesn't crash, it only logs. "Not on this board" also reaches the panel via /api/caps; the panel hides the missing button.

CYD (ESP32-2432S028R) — the pins left for you

PinWhatUse
GPIO22Extended IO connectorin/out — button, LED, I2C SDA; your main pin
GPIO27Temp/humidity connectorin/out — second pin, I2C SCL, ULN2803
GPIO35Extended IOinput only, has ADC, no pull-up
GPIO34LDR (on board)cyd.light()
GPIO4 / 16 / 17RGB LEDcyd.led(r,g,b) — inverted logic, handled for you
GPIO26speakercyd.beep(freq, ms)
GPIO21display backlightleave alone
GPIO1 / 3USB consoleleave alone (the "TX/RX" connector is this too)
2, 12–15, 18, 19, 23, 25, 32, 33, 36, 39display, touch, SDleave alone

NodeMCU (ESP8266)

LabelGPIOUse
D1, D25, 4easy in/out; the standard I2C pair (SCL=D1, SDA=D2)
D5, D6, D714, 12, 13easy in/out (shared with SPI)
D42on-board blue LED, inverted — cyd.led()
D3, D8, D00, 15, 16boot-time duties; avoid
A0—the only analog, 0–1023, max 3.3 V (divider on board)

In code you write cyd.D1; no need to memorise numbers.

13. Template and checklist

new_project.py
# WHAT IT DOES: one sentence
# WIRING: pin ↔ what
import cyd
from machine import Pin

PIN = getattr(cyd, 'D1', 22)
INTERVAL = 0.5

# --- setup ---
out = Pin(PIN, Pin.OUT, value=0)
d = None
try:
    d = cyd.display()
except Exception:
    pass

print('started: GPIO%d, %.1f s' % (PIN, INTERVAL))

# --- loop ---
try:
    while not cyd.should_stop():
        ...                          # work
        if not cyd.sleep(INTERVAL):
            break
finally:
    out.value(0)                     # cleanup: always
    print('done')

Wiring a new sensor

  1. Datasheet: supply 3.3 or 5 V, output digital / analog / I2C / UART → which kind of pin.
  2. Wire VCC, GND, signal. If I2C, run i2c_tara.py → the address must appear.
  3. print the raw value in a loop first — understand it, then write the decision code.
  4. Pick the sleep from the sensor's speed; never hard-code a threshold, measure and use a ratio.
  5. If it will have neighbours: draws on the display or computes heavily → T; captures input → cyd.buton.

Before you press Run

14. A new module: from datasheet to driver

You hold a module you have never seen; the library online doesn't fit your board or doesn't exist. Writing a driver is shorter than it sounds: take three facts from the datasheet, the rest is the I2C calls from §7. Example: the PCF8574 I2C I/O expander (8 extra pins; the chip under every LCD1602 "backpack").

Three things from the datasheet

1. Address: PCF8574 0x20–0x27 (A0–A2 pins), PCF8574A 0x38–0x3F. i2c.scan() tells you which.

2. Protocol: no registers. Write one byte and the 8 outputs become it; read one byte and you get the 8 pin states. Bit 0 = P0 … bit 7 = P7.

3. The quirk: "quasi-bidirectional" pins. A pin you want as an input must be written 1 first (weak pull-up), then read. As an output, writing 0 sinks strongly (wire the LED to VCC, 0 = lit); writing 1 sources only a weak 100 µA. The INT pin goes LOW whenever any input changes.

The driver: 20 lines
projects/pcf8574.py
class PCF8574:
    def __init__(self, i2c, addr=0x20):
        self.i2c = i2c
        self.addr = addr
        self.out = 0xFF            # all 1: inputs / idle
        self._write()

    def _write(self):
        self.i2c.writeto(self.addr, bytes([self.out]))

    def pin_write(self, p, v):     # p: 0–7
        if v: self.out |= (1 << p)
        else: self.out &= ~(1 << p) & 0xFF
        self._write()

    def read(self):                # 8 pins, one byte
        return self.i2c.readfrom(self.addr, 1)[0]

    def pin_read(self, p):
        return (self.read() >> p) & 1
In a project, and why
projects/pcf_example.py
import cyd
from machine import SoftI2C, Pin
from pcf8574 import PCF8574

i2c = SoftI2C(scl=Pin(27), sda=Pin(22))
print('i2c:', [hex(a) for a in i2c.scan()])
x = PCF8574(i2c, 0x20)
x.pin_write(0, 1)            # P0 input: button P0↔GND
while not cyd.should_stop():
    pressed = x.pin_read(0) == 0
    x.pin_write(7, 0 if pressed else 1)   # P7 LED (to VCC)
    cyd.sleep(0.05)

Keeping the class in its own file: the module is unloaded when the project ends, so a fix runs next time. Polling at 50 ms: there is no interrupt over I2C (unless you wire INT to a board pin and catch it with cyd.buton(pin)). The PCF8574 is about pin count, not speed: it adds 8 to the CYD's 2 free pins.

The general order when writing a driver
1) See the address with scan(). 2) Read the ID register from the datasheet's register map (if there is one, e.g. BME280 0xD0 → 0x60): it proves you reached the right chip. 3) Print one measurement as raw bytes. 4) Apply the datasheet's conversion formula. 5) Wrap it in a class with a read(). print at every step — never move on before the previous step works.

15. Common modules: quick reference

Each row: wiring, library (built into the firmware where marked), the 5-line core, the trap. All of it goes inside the §13 template loop.

ModuleWiringCore codeTrap
DS18B20 temperature (1-Wire)VCC 3.3 · GND · DATA→pin, 4.7 kΩ DATA–VCC
import onewire, ds18x20   # built in
ow = ds18x20.DS18X20(onewire.OneWire(Pin(22)))
roms = ow.scan()
ow.convert_temp(); cyd.sleep(0.75)
print(ow.read_temp(roms[0]))
convert → wait 750 ms → read; many sensors on one wire, each with a ROM id
DHT11 / DHT22 humidity+tempVCC · GND · DATA→pin (module has pull-up)
import dht                # built in
s = dht.DHT22(Pin(22))
s.measure()
print(s.temperature(), s.humidity())
read at most every 2 s; the first read may fail, skip it with try
HC-SR04 distanceVCC 5 V · GND · TRIG→pin · ECHO→pin through a divider (5 V output!)
from machine import time_pulse_us
trig = Pin(22, Pin.OUT); echo = Pin(35, Pin.IN)
trig.value(1); time.sleep_us(10); trig.value(0)
us = time_pulse_us(echo, 1, 30000)
cm = us / 58
ECHO is 5 V: 1 kΩ + 2 kΩ divider; −1/−2 means timeout
Servo SG90red 5 V · brown GND · orange→pin
pwm = PWM(Pin(22), freq=50)
def angle(a):            # 0–180
    pwm.duty(int(26 + a / 180 * 102))   # ESP32 0–1023
don't power it from the board's 3.3 V; 50 Hz is mandatory; pulse 0.5–2.5 ms
Relay moduleVCC 5 V · GND · IN→pin
r = Pin(22, Pin.OUT, value=1)   # most modules: LOW = on
r.value(0)   # on
r.value(1)   # off
most modules are active-low; the mains side is for people who know what they're doing
Rotary encoderCLK→pin · DT→pin · SW→pin · GND (pull-ups internal)
clk = Pin(22, Pin.IN, Pin.PULL_UP); dt = Pin(27, Pin.IN, Pin.PULL_UP)
count = [0]
def turn(p): count[0] += -1 if dt.value() else 1
clk.irq(trigger=Pin.IRQ_FALLING, handler=turn)
polling misses steps → interrupt; ignore edges closer than 2 ms (bounce)
NeoPixel WS28125 V · GND · DIN→pin (330 Ω in series)
import neopixel          # built in
np = neopixel.NeoPixel(Pin(22), 8)
np[0] = (255, 0, 0); np.write()
many LEDs = much current (60 mA/LED at white): separate 5 V; 3.3 V data usually works
LCD1602 I2C (PCF8574 backpack)VCC 5 V · GND · SDA · SCL
i2c.scan()   # 0x27 or 0x3F
# lcd_api.py + i2c_lcd.py (projects/)
lcd = I2cLcd(i2c, 0x27, 2, 16)
lcd.putstr('CYD Studio')
contrast pot on the back; a 5 V module can pull SDA/SCL to 5 V — most boards tolerate it, buy a 3.3 V one if unsure
Potentiometerends to 3.3 V and GND · wiper→ADC
a = ADC(Pin(35)); a.atten(ADC.ATTN_11DB)
percent = a.read() * 100 // 4095
never to 5 V; average 5 readings against jitter at the ends
MQTT (data to a server)Wi-Fi
from umqtt.simple import MQTTClient   # built in
c = MQTTClient('cyd', '192.168.0.33'); c.connect()
c.publish(b'home/temp', b'23.5')
plain MQTT works in normal mode; TLS (8883) needs mode S

16. Panel over the cable (USB bridge)

No Wi-Fi, or you want the PC to stay on the internet: the panel comes over the cable — usbkopru.py on the board, tools/usb_panel.py on the PC.

python tools\usb_panel.py COM4 --board nodemcu     # for the CYD: COM3 --board cyd
# browser: http://127.0.0.1:8080/   (NOT localhost - Windows tries IPv6 first, 2 s per request)
How it works

The panel page loads instantly from the PC's disk; only /api/… calls travel over serial. A request is one base64 line; the board runs the same server code (server._route) with a fake connection and streams the reply back. The board's console output flows in the same window.

Measured (ESP8266)

status 0.6–1 s · save/run/log/stop 0.2–0.9 s · 11 KB docs 1.5 s. Bodies over 384 bytes go in 192-byte acknowledged chunks: without acks the board's serial buffer overflowed and chunks arrived corrupt (incorrect padding).

Limits

While the bridge runs the COM port is busy: close it with Ctrl+C before a deploy. One tab at a time. The Wi-Fi panel keeps working alongside.