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şuyor
Kesme (cyd.buton)
10 ms yoklama
hiçbir şey
50/50
50/50
2 küçük proje + ekrana çizen proje
50/50
34/50
+ CPU'yu bırakmayan bir thread
50/50
5/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.
Yol
Tel
Ne için
Nasıl bulunur
I2C
SDA, SCL + GND
sensörlerin çoğu, OLED, PCF8574; bir hatta çok cihaz, her birinin adresi var
i2c.scan() adresleri listeler
SPI
SCK, MOSI, MISO, CS
hızlı: ekran, SD kart, radyo
CS pini cihazı seçer
UART
TX↔RX çapraz + GND
GPS, GSM, başka bir kart
uart.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.
Mod
Nasıl koşar
Heap
Dahili RAM
Ne için
N Normal
tek ortak iş parçacığında asyncio görevi
2–15 KB
0
LED, sensör, küçük sayfa — çoğu şey
T Thread
kendi thread'i, GIL ile dilimli
aynı
10–15 KB
ekran çizen, sleep'siz döngü, ağır hesap
S Solo
panel kapalı, kart yeniden başlar, tek proje
hepsi
hepsi
BLE, 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
Senaryo
ortalama
en uzun
tek başına, N
12,9 ms
21 ms
+ 4 normal proje (LED/LDR)
15,9 ms
352 ms
+ ekrana çizen proje (N)
25,5 ms
973 ms
+ CPU bırakmayan thread
211 ms
3,4 sn
aynı yük, proje T'de
12,0 ms
37 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 MicroPython
CYD Studio firmware'i
Dahili RAM boş, panel açık
27,8 KB
55 KB
Python heap boş
80 / 137 (otomatik büyür)
75 / 94 (sabit 96)
Aynı anda proje
1
7 normal + 2–3 thread
NodeMCU heap boş
2,5–4,8 KB
24,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.
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
Pin
Ne
Kullan
GPIO22
Extended IO konnektörü
giriş/çıkış — buton, LED, I2C SDA; ana pinin
GPIO27
Sıcaklık/nem konnektörü
giriş/çıkış — ikinci pin, I2C SCL, ULN2803
GPIO35
Extended IO
sadece giriş, ADC var, pull-up yok
GPIO34
LDR (kart üstü)
cyd.light()
GPIO4 / 16 / 17
RGB LED
cyd.led(r,g,b) — ters mantık, sen düşünme
GPIO26
hoparlör
cyd.beep(freq, ms)
GPIO21
ekran arka ışığı
dokunma
GPIO1 / 3
USB konsol
dokunma ("TX/RX" etiketli konnektör de bu)
2, 12–15, 18, 19, 23, 25, 32, 33, 36, 39
ekran, dokunmatik, SD
dokunma
NodeMCU (ESP8266)
Etiket
GPIO
Kullan
D1, D2
5, 4
rahat giriş/çıkış; I2C için standart çift (SCL=D1, SDA=D2)
D5, D6, D7
14, 12, 13
rahat giriş/çıkış (SPI ile ortak)
D4
2
dahili mavi LED, ters mantık — cyd.led()
D3, D8, D0
0, 15, 16
açı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
Datasheet: besleme 3,3 mü 5 mi, çıkış dijital / analog / I2C / UART → hangi pin türü.
Bağla: VCC, GND, sinyal. I2C ise i2c_tara.py → adres görünmeli.
Önce ham değeri döngüde print et — anla, sonra karar kodu yaz.
Bekleme süresini sensörün hızına göre seç; eşiği sabit yazma, ölçüp oranla.
Komşusu olacaksa: ekran çiziyor ya da ağır hesap → T; giriş yakalıyor → cyd.buton.
Çalıştırmadan önce
while not cyd.should_stop() ve beklemeler cyd.sleep
try/finally içinde çıkışlar kapatılıyor
LED'de direnç, yükte ULN2803/transistör, GND ortak, girişe 5 V yok
Pin bu kartta var mı (getattr(cyd, 'D1', 22))
print her turda değil, olay olunca
Web sayfası gerekiyorsa cyd.rota, ikinci soket değil
BLE / HTTPS gerekiyorsa mod S
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), PCF8574A0x38–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.
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.
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.
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 it
IRQ (cyd.buton)
10 ms polling
nothing
50/50
50/50
2 small projects + a display-drawing project
50/50
34/50
+ a thread that never yields the CPU
50/50
5/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.
Bus
Wires
For
How to find
I2C
SDA, SCL + GND
most sensors, OLED, PCF8574; many devices on one bus, each with an address
i2c.scan() lists addresses
SPI
SCK, MOSI, MISO, CS
fast: display, SD card, radio
CS pin selects the device
UART
TX↔RX crossed + GND
GPS, GSM, another board
uart.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.
Mode
How it runs
Heap
Internal RAM
For
N Normal
asyncio task on one shared thread
2–15 KB
0
LEDs, sensors, small pages — most things
T Thread
own thread, sliced by the GIL
same
10–15 KB
display drawing, loops without sleep, heavy compute
S Solo
panel off, board reboots, single project
all
all
BLE, 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
Scenario
average
longest
alone, N
12.9 ms
21 ms
+ 4 normal projects (LED/LDR)
15.9 ms
352 ms
+ a display-drawing project (N)
25.5 ms
973 ms
+ a thread that never yields
211 ms
3.4 s
same load, project in T
12.0 ms
37 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 MicroPython
CYD Studio firmware
Internal RAM free, panel up
27.8 KB
55 KB
Python heap free
80 / 137 (auto-grows)
75 / 94 (fixed 96)
Concurrent projects
1
7 normal + 2–3 threads
NodeMCU heap free
2.5–4.8 KB
24.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
Pin
What
Use
GPIO22
Extended IO connector
in/out — button, LED, I2C SDA; your main pin
GPIO27
Temp/humidity connector
in/out — second pin, I2C SCL, ULN2803
GPIO35
Extended IO
input only, has ADC, no pull-up
GPIO34
LDR (on board)
cyd.light()
GPIO4 / 16 / 17
RGB LED
cyd.led(r,g,b) — inverted logic, handled for you
GPIO26
speaker
cyd.beep(freq, ms)
GPIO21
display backlight
leave alone
GPIO1 / 3
USB console
leave alone (the "TX/RX" connector is this too)
2, 12–15, 18, 19, 23, 25, 32, 33, 36, 39
display, touch, SD
leave alone
NodeMCU (ESP8266)
Label
GPIO
Use
D1, D2
5, 4
easy in/out; the standard I2C pair (SCL=D1, SDA=D2)
D5, D6, D7
14, 12, 13
easy in/out (shared with SPI)
D4
2
on-board blue LED, inverted — cyd.led()
D3, D8, D0
0, 15, 16
boot-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
Datasheet: supply 3.3 or 5 V, output digital / analog / I2C / UART → which kind of pin.
Wire VCC, GND, signal. If I2C, run i2c_tara.py → the address must appear.
print the raw value in a loop first — understand it, then write the decision code.
Pick the sleep from the sensor's speed; never hard-code a threshold, measure and use a ratio.
If it will have neighbours: draws on the display or computes heavily → T; captures input → cyd.buton.
Before you press Run
while not cyd.should_stop() and all waits are cyd.sleep
outputs switched off inside try/finally
resistor on the LED, ULN2803/transistor on the load, common GND, no 5 V into inputs
does the pin exist on this board (getattr(cyd, 'D1', 22))
print on events, not every turn
a web page → cyd.rota, not a second socket
BLE / HTTPS → mode S
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").
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.
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.
Module
Wiring
Core code
Trap
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+temp
VCC · 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 distance
VCC 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
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.