import machine
import time
import framebuf
# --- PCD8544 Nokia 5110 LCD Driver ---
PCD8544_FUNCTIONSET = 0x20
PCD8544_DISPLAYCONTROL = 0x08
PCD8544_DISPLAYNORMAL = 0x0C
PCD8544_SETYADDR = 0x40
PCD8544_SETXADDR = 0x80
PCD8544_EXTENDEDINSTRUCTION = 0x21
PCD8544_SETVOP = 0x80
PCD8544_SETBIAS = 0x10
class PCD8544(framebuf.FrameBuffer):
def __init__(self, spi, dc_pin_num, rst_pin_num, cs_pin_num, bl_pin_num=None, contrast=0x3F, bias=0x03):
self.spi = spi
self.dc = machine.Pin(dc_pin_num, machine.Pin.OUT)
self.rst = machine.Pin(rst_pin_num, machine.Pin.OUT)
self.cs = machine.Pin(cs_pin_num, machine.Pin.OUT)
if bl_pin_num is not None:
self.bl = machine.Pin(bl_pin_num, machine.Pin.OUT)
self.bl.on()
else:
self.bl = None
self.height = 48
self.width = 84
self.buffer = bytearray(self.height // 8 * self.width)
super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB)
self.reset()
self.command(PCD8544_FUNCTIONSET | 0x01)
self.command(PCD8544_SETVOP | contrast)
self.command(PCD8544_SETBIAS | bias)
self.command(PCD8544_FUNCTIONSET)
self.command(PCD8544_DISPLAYCONTROL | PCD8544_DISPLAYNORMAL)
self.clear_screen()
def command(self, cmd):
self.dc.off()
self.cs.off()
self.spi.write(bytearray([cmd]))
self.cs.on()
def data(self, val):
self.dc.on()
self.cs.off()
self.spi.write(val)
self.cs.on()
def reset(self):
self.rst.on()
time.sleep_ms(10)
self.rst.off()
time.sleep_ms(10)
self.rst.on()
time.sleep_ms(10)
def show(self):
self.command(PCD8544_SETYADDR | 0)
self.command(PCD8544_SETXADDR | 0)
self.data(self.buffer)
def clear_screen(self):
self.fill(0)
self.show()
def display_text(self, text_lines):
self.fill(0)
y_offset = 0
for line in text_lines:
if y_offset < self.height - 8: # 8 pixel height, with some margin
# Trim lines to fit display width (max ~14 chars with standard font)
truncated_line = line[:14] if len(line) > 14 else line
self.text(truncated_line, 0, y_offset, 1) # (text, x, y, color=1:pixel on)
y_offset += 8 # Line spacing (reduced from 9 to fit more text)
self.show()
# --- Pin Definitions ---
# LCD
LCD_SPI_SCK_PIN_NUM = 18
LCD_SPI_MOSI_PIN_NUM = 19
LCD_DC_PIN_NUM = 16
LCD_RST_PIN_NUM = 17
LCD_CS_PIN_NUM = 20
LCD_BL_PIN_NUM = 21
# Shift Register (for LED)
SR_SER_PIN_NUM = 10 # SI/DS
SR_RCLK_PIN_NUM = 12 # RCK/STCP/Latch
SR_SRCLK_PIN_NUM = 11 # SCK/SHCP/Clock
# Multiplexer (for Buttons)
MUX_SIG_PIN_NUM = 26 # MUX Output/COM
MUX_S0_PIN_NUM = 13 # MUX Channel Select S0
# PIR Sensor
PIR_PIN_NUM = 27 # OUT
# --- Hardware Initialization ---
# SPI (for LCD)
spi = machine.SPI(0, baudrate=2000000, polarity=0, phase=0,
sck=machine.Pin(LCD_SPI_SCK_PIN_NUM),
mosi=machine.Pin(LCD_SPI_MOSI_PIN_NUM))
# LCD Object
lcd = PCD8544(spi, LCD_DC_PIN_NUM, LCD_RST_PIN_NUM, LCD_CS_PIN_NUM, LCD_BL_PIN_NUM, contrast=0x44)
# Shift Register Pins
ser_pin = machine.Pin(SR_SER_PIN_NUM, machine.Pin.OUT)
rclk_pin = machine.Pin(SR_RCLK_PIN_NUM, machine.Pin.OUT)
srclk_pin = machine.Pin(SR_SRCLK_PIN_NUM, machine.Pin.OUT)
# Multiplexer Pins - Adding an external pull-up (PULL_UP may not be working)
mux_sig = machine.Pin(MUX_SIG_PIN_NUM, machine.Pin.IN)
mux_s0 = machine.Pin(MUX_S0_PIN_NUM, machine.Pin.OUT)
# Setting initial state of MUX selection to avoid floating inputs
mux_s0.value(0)
# PIR Sensor Pin
pir = machine.Pin(PIR_PIN_NUM, machine.Pin.IN)
# --- Helper Functions ---
def set_led_state_sr(state):
"""Controls the LED state through the shift register."""
rclk_pin.value(0) # Lower latch
# Only control the first LED (Q0/QA)
byte_to_send = 0b00000001 if state else 0b00000000
for i in range(8): # Send 8 bits
srclk_pin.value(0) # Lower clock
# Send data to Q0 (least significant bit)
if i == 0:
ser_pin.value(1 if state else 0)
else: # For other Q pins (Q1-Q7) send 0
ser_pin.value(0)
srclk_pin.value(1) # Raise clock (data shifts)
rclk_pin.value(1) # Raise latch (transfer data to outputs)
# Debug output
print(f"LED State: {'ON' if state else 'OFF'}")
def select_mux_channel(channel):
"""Selects desired channel on multiplexer (0 or 1)."""
mux_s0.value(channel) # S0 = 0 for C0/I0, S0 = 1 for C1/I1
def read_button(button_index):
"""
Reads specified button through MUX.
Value: 0 if pressed, 1 if not pressed (due to internal pull-up).
IMPORTANT: Due to simulator limitations, we invert the logic here:
If MUX output is always 0, then consider button pressed only when
explicitly checking for that channel (active high logic).
"""
select_mux_channel(button_index)
time.sleep_ms(5) # Increased wait time for MUX to settle
value = mux_sig.value()
# Debug output
print(f"Reading Button Index: {button_index}, S0 Pin State: {mux_s0.value()}, MUX Output (GP26): {value}")
# For simulation: If currently selected channel matches the button we're checking
# and the output is 0, consider it pressed
is_pressed = (value == 0)
# Additional check for button state
# For Wokwi simulator's specific behavior with MUX
# 0 = pressed, 1 = not pressed
return 0 if is_pressed else 1
# --- System Variables ---
system_armed = False
motion_detected_flag = False
led_on_motion = False
led_blink_state = False
led_last_toggle_time = 0
BTN_ARM_INDEX = 0 # MUX C0/I0 channel (btn1)
BTN_ACK_INDEX = 1 # MUX C1/I1 channel (btn2)
# Debounce variables
btn_arm_last_state = 1 # 1: Not pressed, 0: Pressed
btn_ack_last_state = 1 # 1: Not pressed, 0: Pressed
btn_arm_last_change_time = 0 # Last time button state changed
btn_ack_last_change_time = 0 # Last time button state changed
DEBOUNCE_DELAY_MS = 200 # Debounce delay (milliseconds)
# Add blinking functionality
LED_BLINK_INTERVAL_MS = 500 # LED blink interval (milliseconds)
# --- Main Loop ---
lcd.display_text(["Sistem", "Basliyor", "Lutfen", "Bekleyin.."])
time.sleep(1) # Short initial wait
set_led_state_sr(False) # LED initially off
print("System Ready")
while True:
current_time = time.ticks_ms()
# Button 1 (Arm/Disarm) Reading and Debounce
raw_btn_arm_state = read_button(BTN_ARM_INDEX)
# Direct button check for simulation - kısa yol
if raw_btn_arm_state == 0: # Button is pressed
if btn_arm_last_state == 1: # And it was not pressed before (detect press event)
btn_arm_last_state = 0 # Update last state
# Toggle system state
system_armed = not system_armed
motion_detected_flag = False
led_on_motion = False
set_led_state_sr(False)
print(f"System Status Changed: {'Armed' if system_armed else 'Disarmed'}")
elif raw_btn_arm_state == 1: # Button is released
btn_arm_last_state = 1 # Update last state
# Button 2 (Acknowledge) Reading and Debounce
raw_btn_ack_state = read_button(BTN_ACK_INDEX)
# Direct button check for simulation
if raw_btn_ack_state == 0: # Button is pressed
if btn_ack_last_state == 1: # And it was not pressed before
btn_ack_last_state = 0 # Update state
# Process acknowledgment
if system_armed and motion_detected_flag:
motion_detected_flag = False
led_on_motion = False
set_led_state_sr(False) # Turn off LED
print("Motion Acknowledged!")
elif raw_btn_ack_state == 1: # Button is released
btn_ack_last_state = 1
# PIR Sensor Reading (HIGH = motion for Wokwi PIR)
current_pir_value = pir.value()
print(f"PIR Value: {current_pir_value} ({'Motion' if current_pir_value == 1 else 'No Motion'})")
if current_pir_value == 1:
if system_armed and not motion_detected_flag: # System armed and no motion detected yet
motion_detected_flag = True
led_on_motion = True
led_last_toggle_time = current_time # Initialize blink timer
print("HAREKET ALGILANDI! - MOTION DETECTED!")
# For testing without real PIR: Uncomment next line to simulate motion detection with Button 2
# if not system_armed and raw_btn_ack_state == 0: system_armed = True; motion_detected_flag = True; led_on_motion = True
# LED Control with Blinking
if system_armed and led_on_motion:
# Blink the LED when motion is detected
if time.ticks_diff(current_time, led_last_toggle_time) > LED_BLINK_INTERVAL_MS:
led_blink_state = not led_blink_state
set_led_state_sr(led_blink_state)
led_last_toggle_time = current_time
else:
set_led_state_sr(False) # LED off if system not armed or motion acknowledged
# LCD Update
display_lines = []
if system_armed:
display_lines.append("Durum: Kurulu")
if motion_detected_flag:
display_lines.append("HAREKET VAR!")
display_lines.append("ACK BTN Bas")
else:
display_lines.append("Gozetleniyor...")
else:
display_lines.append("Durum: Kapali")
display_lines.append("ARM BTN Bas")
# Show maximum 5 lines (approximate line capacity of LCD)
lcd.display_text(display_lines[:5])
time.sleep_ms(50) # Main loop delay, adjusts system responsivenessLoading
nokia-5110
nokia-5110