Browse Source

first commit

Adrian 1 day ago
commit
d0cdfafbbe
14 changed files with 743 additions and 0 deletions
  1. 16 0
      .vscode/launch.json
  2. BIN
      __pycache__/config.cpython-38.pyc
  3. BIN
      __pycache__/player.cpython-38.pyc
  4. BIN
      __pycache__/pyopenmv.cpython-38.pyc
  5. 45 0
      cam.py
  6. 2 0
      config.py
  7. BIN
      icon.png
  8. 249 0
      main.py
  9. 83 0
      player.pyw
  10. 190 0
      pyopenmv.py
  11. BIN
      requiremnets.txt
  12. BIN
      run.lnk
  13. 157 0
      ssd1306.py
  14. 1 0
      wymagane_biblioteki.txt

+ 16 - 0
.vscode/launch.json

@@ -0,0 +1,16 @@
+{
+    // Use IntelliSense to learn about possible attributes.
+    // Hover to view descriptions of existing attributes.
+    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+    "version": "0.2.0",
+    "configurations": [
+        {
+            "name": "Python: Current File",
+            "type": "python",
+            "request": "launch",
+            "program": "${file}",
+            "console": "integratedTerminal",
+            "args": ["COM11"]
+        }
+    ]
+}

BIN
__pycache__/config.cpython-38.pyc


BIN
__pycache__/player.cpython-38.pyc


BIN
__pycache__/pyopenmv.cpython-38.pyc


+ 45 - 0
cam.py

@@ -0,0 +1,45 @@
+import sensor, image, time, math
+from pyb import Pin
+
+threshold_list = [(190, 255)]
+
+min_temp_in_celsius = 20.0
+max_temp_in_celsius = 40.0
+
+pin1 = Pin('P4', Pin.OUT_PP, Pin.PULL_NONE)
+
+sensor.reset()
+sensor.ioctl(sensor.IOCTL_LEPTON_SET_MODE, True, True)
+sensor.ioctl(sensor.IOCTL_LEPTON_SET_RANGE, min_temp_in_celsius, max_temp_in_celsius)
+sensor.ioctl(sensor.IOCTL_LEPTON_GET_WIDTH)
+sensor.ioctl(sensor.IOCTL_LEPTON_GET_HEIGHT)
+sensor.ioctl(sensor.IOCTL_LEPTON_GET_RADIOMETRY)
+
+sensor.set_pixformat(sensor.GRAYSCALE)
+sensor.set_framesize(sensor.QQVGA)
+sensor.skip_frames(time=5000)
+
+clock = time.clock()
+
+def map_g_to_temp(g):
+    return ((g * (max_temp_in_celsius - min_temp_in_celsius)) / 255.0) + min_temp_in_celsius
+
+while(True):
+    clock.tick()
+    img = sensor.snapshot()
+    blob_stats = []
+    blobs = img.find_blobs(threshold_list, pixels_threshold=200, area_threshold=200, merge=True)
+    # Collect stats into a list of tuples
+    for blob in blobs:
+        blob_stats.append((blob.x(), blob.y(), map_g_to_temp(img.get_statistics(thresholds=threshold_list,
+                                                                                roi=blob.rect()).mean())))
+    img.to_rainbow(color_palette=image.PALETTE_IRONBOW) # color it
+    for blob in blobs:
+        img.draw_rectangle(blob.rect(), color=(0,255,0))
+        img.draw_cross(blob.cx(), blob.cy(), color=(0,255,0))
+    for blob_stat in blob_stats:
+        if blob_stat[2] > 38.0:
+            pin1.value(1)
+        img.draw_string(blob_stat[0] + 2, blob_stat[1] + 1, "%.2f C" % blob_stat[2], mono_space=False, color=(0,255,0))
+    if not blob_stats:
+        pin1.value(0)

+ 2 - 0
config.py

@@ -0,0 +1,2 @@
+portname = 'COM24'
+scale = 5

BIN
icon.png


+ 249 - 0
main.py

@@ -0,0 +1,249 @@
+import sensor, image, time, math, socket, network
+from pyb import Pin
+
+SSID = "EkoSlupek"
+KEY = "1234567890"
+HOST = ""
+PORT = 8080
+
+threshold_list = [(190, 255)]
+min_temp_in_celsius = 20.0
+max_temp_in_celsius = 42.0
+treshold_temperature = 38.0
+# Korekta offsetu temperatury (dodaj/subtrahuj stopnie aby dopasować do referencyjnego termometru)
+temperature_offset = -2.0  # Przykład: jeśli kamera pokazuje 38°C a Flir 33°C, ustaw -5.0
+
+pin1 = Pin('P4', Pin.OUT_PP, Pin.PULL_NONE)
+
+# --- sensor setup ---
+sensor.reset()
+sensor.ioctl(sensor.IOCTL_LEPTON_SET_MODE, True, False)
+sensor.ioctl(sensor.IOCTL_LEPTON_SET_RANGE, min_temp_in_celsius, max_temp_in_celsius)
+sensor.set_pixformat(sensor.GRAYSCALE)
+sensor.set_framesize(sensor.QQVGA)
+sensor.skip_frames(time=2000)
+
+# --- WiFi jako Access Point ---
+try:
+    wlan = network.WLAN(network.AP_IF)
+    wlan.config(ssid=SSID, key=KEY, channel=2)
+    wlan.active(True)
+    print("AP started: {} IP: {}".format(SSID, wlan.ifconfig()[0]))
+except Exception as e:
+    print("WiFi error:", e)
+    wlan = None
+
+def map_g_to_temp(g):
+    temp = ((g * (max_temp_in_celsius - min_temp_in_celsius)) / 255.0) + min_temp_in_celsius
+    return temp + temperature_offset  # Dodaj korekcję offsetu
+
+def temp_to_g(temp):
+    """Konwertuj temperaturę na wartość grayscale"""
+    return int(((temp - min_temp_in_celsius) / (max_temp_in_celsius - min_temp_in_celsius)) * 255.0)
+
+# Próg temperatury - pokazuj tylko bloby powyżej tej temperatury
+min_display_temp = 35.0
+min_display_g = temp_to_g(min_display_temp)  # wartość grayscale dla 35°C
+
+# Próg dla liczenia średniej - liczymy średnią tylko z pikseli powyżej tej temperatury
+# To eliminuje problem z chłodnym otoczeniem w prostokącie bloba
+mean_threshold_temp = 36.5
+mean_threshold_g = temp_to_g(mean_threshold_temp)  # wartość grayscale dla 36.5°C
+
+# Opcja: użyj maksimum zamiast średniej (bardziej wrażliwe na gorączkę, ale może być podatne na zakłócenia)
+# False = użyj średniej z gorących pikseli (ZALECANE)
+# True = użyj maksimum (najgorętszy piksel w blobie)
+use_max_instead_of_mean = False
+
+# --- Funkcja restartująca serwer (TYLKO socket, bez restartu AP) ---
+def restart_server():
+    global server
+    try:
+        # close old server if exists
+        try:
+            if server:
+                server.close()
+        except:
+            pass
+
+        # create new server socket
+        server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
+        server.bind([HOST, PORT])
+        server.listen(1)  # jedno połączenie naraz
+        server.setblocking(False)
+        print("Server ready on port", PORT)
+    except Exception as e:
+        print("Server restart error:", e)
+        server = None
+
+# --- inicjalizacja ---
+server = None
+client = None
+last_client_activity = time.ticks_ms()  # aktualizacja przy każdym udanym send/accept
+restart_cooldown = time.ticks_ms()  # do ograniczenia częstotliwości restartów
+clock = time.clock()
+
+# Czas wykrycia gorączki - dla redukcji zakłóceń (opóźnienie 1 sekunda)
+fever_start_time = None
+fever_detection_delay_ms = 1000  # Czas opóźnienia w milisekundach (1 sekunda)
+
+restart_server()
+
+while True:
+    clock.tick()
+    img = sensor.snapshot()
+
+    # ---- analiza temperatury ----
+    blob_stats = []
+    blobs = img.find_blobs(threshold_list, pixels_threshold=200, area_threshold=200, merge=True)
+
+    for blob in blobs:
+        # Liczymy średnią tylko z pikseli powyżej 36.5°C w obszarze bloba
+        # To eliminuje problem z chłodnym otoczeniem w prostokącie bloba
+        hot_threshold = [(mean_threshold_g, 255)]
+        stats = img.get_statistics(thresholds=hot_threshold, roi=blob.rect())
+
+        # Sprawdź czy są jakieś piksele powyżej 36.5°C
+        if stats.mean() >= mean_threshold_g:
+            if use_max_instead_of_mean:
+                # Użyj maksimum (najgorętszy piksel) - bardziej wrażliwe
+                temp_g = stats.max()
+            else:
+                # Użyj średniej z gorących pikseli - bardziej stabilne (ZALECANE)
+                temp_g = stats.mean()
+
+            temp = map_g_to_temp(temp_g)
+
+            # Pokazuj tylko bloby z temperaturą > 35°C
+            if temp > min_display_temp:
+                blob_stats.append((blob.x(), blob.y(), temp, blob))
+
+    img.to_rainbow(color_palette=image.PALETTE_IRONBOW)
+
+    # Sprawdź czy którykolwiek blob ma przekroczoną temperaturę
+    has_fever = False
+
+    # Rysuj tylko bloby które mają mean > 35°C
+    for blob_stat in blob_stats:
+        blob = blob_stat[3]  # ostatni element to obiekt blob
+
+        # Rysuj elipsę zamiast prostokąta - lepiej pasuje do kształtu
+        try:
+            img.draw_ellipse(blob.cx(), blob.cy(), int(blob.w()//2), int(blob.h()//2), 0, color=(0,255,0))
+        except:
+            # Fallback: prostokąt jeśli elipsa nie działa
+            img.draw_rectangle(blob.rect(), color=(0,255,0))
+
+        img.draw_cross(blob.cx(), blob.cy(), color=(0,255,0))
+        img.draw_string(blob_stat[0]+2, blob_stat[1]+1, "%.2fC" % blob_stat[2], mono_space=False, color=(0,255,0))
+
+        if blob_stat[2] > treshold_temperature:
+            has_fever = True
+
+    # Sterowanie pinem z opóźnieniem 1 sekunda (redukcja zakłóceń)
+    current_time = time.ticks_ms()
+
+    if has_fever:
+        # Gorączka wykryta - sprawdź czy jest wykrywana wystarczająco długo
+        if fever_start_time is None:
+            # Pierwsze wykrycie - zapisz czas
+            fever_start_time = current_time
+        else:
+            # Sprawdź czy minęło wystarczająco dużo czasu (>1 sekunda)
+            if time.ticks_diff(current_time, fever_start_time) >= fever_detection_delay_ms:
+                # Gorączka wykrywana dłużej niż 1 sekunda - włącz pin
+                pin1.value(1)
+            # Jeśli jeszcze nie minęła sekunda, pin pozostaje w poprzednim stanie
+    else:
+        # Brak gorączki - resetuj timer i wyłącz pin
+        fever_start_time = None
+        pin1.value(0)
+
+    # ---- obsługa WiFi: jeśli interfejs się zawiesi -> wypisz i spróbuj ponownie (ale nie zamykamy AP bez potrzeby) ----
+    if wlan and not wlan.active():
+        print("WiFi down — trying to re-enable AP...")
+        try:
+            wlan.active(False)
+            time.sleep_ms(500)
+            wlan.active(True)
+            wlan.config(ssid=SSID, key=KEY, channel=2)
+            print("AP restarted: {} IP: {}".format(SSID, wlan.ifconfig()[0]))
+            # po restarcie interfejsu trzeba odtworzyć server socket
+            restart_server()
+        except Exception as e:
+            print("WiFi restart error:", e)
+        # przejdź dalej w pętli
+        continue
+
+    # ---- obsługa połączeń (server socket) ----
+    if server:
+        if not client:
+            # spróbuj acceptować (non-blocking)
+            try:
+                client, addr = server.accept()
+                print("New client:", addr)
+                client.settimeout(0.1)
+                client.send(
+                    "HTTP/1.1 200 OK\r\n"
+                    "Server: OpenMV\r\n"
+                    "Content-Type: multipart/x-mixed-replace;boundary=openmv\r\n\r\n"
+                )
+                # zaktualizuj timestamp aktywności przy udanym połączeniu
+                last_client_activity = time.ticks_ms()
+            except OSError:
+                # brak nowego klienta — nic nie rób
+                client = None
+
+        elif client:
+            # jeśli mamy klienta, wysyłaj ramki
+            try:
+                cframe = img.to_jpeg(quality=70, copy=True)
+                header = "\r\n--openmv\r\nContent-Type: image/jpeg\r\nContent-Length:{}\r\n\r\n".format(cframe.size())
+                client.send(header)
+                client.send(cframe)
+                # update ostatniej udanej wysyłki
+                last_client_activity = time.ticks_ms()
+            except OSError as e:
+                # klient rozłączył się lub socket padł -> zamykamy klienta i odtwarzamy server socket
+                print("⚠️ Client disconnected or socket error:", e)
+                try:
+                    client.close()
+                except:
+                    pass
+                client = None
+
+                # restartujemy TYLKO server socket (workaround WINC bug)
+                if time.ticks_diff(time.ticks_ms(), restart_cooldown) > 2000:
+                    restart_cooldown = time.ticks_ms()
+                    print("♻️ Recreating server socket (socket-only restart)...")
+                    try:
+                        if server:
+                            try:
+                                server.close()
+                            except:
+                                pass
+                        restart_server()
+                    except Exception as e2:
+                        print("Error recreating server socket:", e2)
+                    # krótka pauza, żeby nie próbować od razu accept w tej samej iteracji
+                    time.sleep_ms(200)
+
+    # ---- watchdog: jeśli przez >10 s brak udanej wysyłki/aktywności -> odtwórz server socket (bez restartu AP) ----
+    if time.ticks_diff(time.ticks_ms(), last_client_activity) > 10000:
+        # ochronna blokada przed zbyt częstymi restartami
+        if time.ticks_diff(time.ticks_ms(), restart_cooldown) > 2000:
+            print("⏰ No client activity for >10s — recreating server socket (watchdog).")
+            try:
+                if server:
+                    try:
+                        server.close()
+                    except:
+                        pass
+                restart_server()
+            except Exception as e:
+                print("Watchdog server recreate error:", e)
+            client = None
+            last_client_activity = time.ticks_ms()
+            restart_cooldown = time.ticks_ms()
+            time.sleep_ms(200)

+ 83 - 0
player.pyw

@@ -0,0 +1,83 @@
+#! python3
+# This file is part of the OpenMV project.
+
+
+import sys, os
+import numpy as np
+import pygame
+import pyopenmv
+from time import sleep
+
+import config
+
+dir_path = os.path.dirname(os.path.realpath(__file__))
+
+# cam code loader
+with open(dir_path + '/cam.py', 'r') as contentFile:
+    script = contentFile.read()
+
+# init pygame
+pygame.init()
+programIcon = pygame.image.load(dir_path + '/icon.png')
+pygame.display.set_icon(programIcon)
+pygame.display.set_caption('EkoSłupek')
+
+connected = False
+pyopenmv.disconnect()
+for i in range(10):
+    try:
+        # opens CDC port.
+        # Set small timeout when connecting
+        pyopenmv.init(config.portname, baudrate=921600, timeout=0.050)
+        connected = True
+        break
+    except Exception as e:
+        connected = False
+        sleep(0.100)
+
+if not connected:
+    print ( "Failed to connect to OpenMV's serial port.\n\n")
+    sys.exit(1)
+
+# Set higher timeout after connecting for lengthy transfers.
+pyopenmv.set_timeout(1*2) # SD Cards can cause big hicups.
+pyopenmv.stop_script()
+pyopenmv.enable_fb(True)
+pyopenmv.exec_script(script)
+
+# init screen
+running = True
+Clock = pygame.time.Clock()
+font = pygame.font.SysFont("monospace", 15)
+
+while running:
+    Clock.tick(60)
+
+    # read framebuffer
+    fb = pyopenmv.fb_dump()
+    if fb != None:
+        # scale
+        width = fb[0] * config.scale
+        height = fb[1] * config.scale
+        # create image from RGB888
+        image = pygame.image.frombuffer(fb[2].flat[0:], (fb[0], fb[1]), 'RGB')
+        image = pygame.transform.scale(image, (width, height))
+        # TODO check if res changed
+        screen = pygame.display.set_mode((width, height), pygame.DOUBLEBUF, 32)
+        # blit stuff
+        screen.blit(image, (0, 0))
+    
+        # update display
+        pygame.display.flip()
+
+    for event in pygame.event.get():
+        if event.type == pygame.QUIT:
+             running = False
+        elif event.type == pygame.KEYDOWN:
+            if event.key == pygame.K_ESCAPE:
+                running = False
+            if event.key == pygame.K_c:
+                pygame.image.save(image, dir_path + "/capture.png")
+
+pygame.quit()
+pyopenmv.stop_script()

+ 190 - 0
pyopenmv.py

@@ -0,0 +1,190 @@
+#!/usr/bin/env python2
+# This file is part of the OpenMV project.
+#
+# Copyright (c) 2013-2019 Ibrahim Abdelkader <iabdalkader@openmv.io>
+# Copyright (c) 2013-2019 Kwabena W. Agyeman <kwagyeman@openmv.io>
+#
+# This work is licensed under the MIT license, see the file LICENSE for details.
+#
+# Openmv module.
+
+import struct
+import sys,time
+import serial
+import platform
+import numpy as np
+from PIL import Image
+
+__serial = None
+__FB_HDR_SIZE   =12
+
+# USB Debug commands
+__USBDBG_CMD            = 48
+__USBDBG_FW_VERSION     = 0x80
+__USBDBG_FRAME_SIZE     = 0x81
+__USBDBG_FRAME_DUMP     = 0x82
+__USBDBG_ARCH_STR       = 0x83
+__USBDBG_SCRIPT_EXEC    = 0x05
+__USBDBG_SCRIPT_STOP    = 0x06
+__USBDBG_SCRIPT_SAVE    = 0x07
+__USBDBG_SCRIPT_RUNNING = 0x87
+__USBDBG_TEMPLATE_SAVE  = 0x08
+__USBDBG_DESCRIPTOR_SAVE= 0x09
+__USBDBG_ATTR_READ      = 0x8A
+__USBDBG_ATTR_WRITE     = 0x0B
+__USBDBG_SYS_RESET      = 0x0C
+__USBDBG_FB_ENABLE      = 0x0D
+__USBDBG_TX_BUF_LEN     = 0x8E
+__USBDBG_TX_BUF         = 0x8F
+
+ATTR_CONTRAST   =0
+ATTR_BRIGHTNESS =1
+ATTR_SATURATION =2
+ATTR_GAINCEILING=3
+
+__BOOTLDR_START         = 0xABCD0001
+__BOOTLDR_RESET         = 0xABCD0002
+__BOOTLDR_ERASE         = 0xABCD0004
+__BOOTLDR_WRITE         = 0xABCD0008
+
+def init(port, baudrate=921600, timeout=0.3):
+    global __serial
+    # open CDC port
+    __serial =  serial.Serial(port, baudrate=baudrate, timeout=timeout)
+
+def disconnect():
+    global __serial
+    try:
+        if (__serial):
+            __serial.close()
+            __serial = None
+    except:
+        pass
+
+def set_timeout(timeout):
+    __serial.timeout = timeout
+
+def fb_size():
+    # read fb header
+    __serial.write(struct.pack("<BBI", __USBDBG_CMD, __USBDBG_FRAME_SIZE, __FB_HDR_SIZE))
+    return struct.unpack("III", __serial.read(12))
+
+def fb_dump():
+    size = fb_size()
+
+    if (not size[0]):
+        # frame not ready
+        return None
+
+    if (size[2] > 2): #JPEG
+        num_bytes = size[2]
+    else:
+        num_bytes = size[0]*size[1]*size[2]
+
+    # read fb data
+    __serial.write(struct.pack("<BBI", __USBDBG_CMD, __USBDBG_FRAME_DUMP, num_bytes))
+    buff = __serial.read(num_bytes)
+
+    if size[2] == 1:  # Grayscale
+        y = np.fromstring(buff, dtype=np.uint8)
+        buff = np.column_stack((y, y, y))
+    elif size[2] == 2: # RGB565
+        arr = np.fromstring(buff, dtype=np.uint16).newbyteorder('S')
+        r = (((arr & 0xF800) >>11)*255.0/31.0).astype(np.uint8)
+        g = (((arr & 0x07E0) >>5) *255.0/63.0).astype(np.uint8)
+        b = (((arr & 0x001F) >>0) *255.0/31.0).astype(np.uint8)
+        buff = np.column_stack((r,g,b))
+    else: # JPEG
+        try:
+            buff = np.asarray(Image.frombuffer("RGB", size[0:2], buff, "jpeg", "RGB", ""))
+        except Exception as e:
+            print ("JPEG decode error (%s)"%(e))
+            return None
+
+    if (buff.size != (size[0]*size[1]*3)):
+        return None
+
+    return (size[0], size[1], buff.reshape((size[1], size[0], 3)))
+
+def exec_script(buf):
+    __serial.write(struct.pack("<BBI", __USBDBG_CMD, __USBDBG_SCRIPT_EXEC, len(buf)))
+    __serial.write(buf.encode())
+
+def stop_script():
+    __serial.write(struct.pack("<BBI", __USBDBG_CMD, __USBDBG_SCRIPT_STOP, 0))
+
+def script_running():
+    __serial.write(struct.pack("<BBI", __USBDBG_CMD, __USBDBG_SCRIPT_RUNNING, 4))
+    return struct.unpack("I", __serial.read(4))[0]
+
+def save_template(x, y, w, h, path):
+    buf = struct.pack("IIII", x, y, w, h) + path
+    __serial.write(struct.pack("<BBI", __USBDBG_CMD, __USBDBG_TEMPLATE_SAVE, len(buf)))
+    __serial.write(buf)
+
+def save_descriptor(x, y, w, h, path):
+    buf = struct.pack("HHHH", x, y, w, h) + path
+    __serial.write(struct.pack("<BBI", __USBDBG_CMD, __USBDBG_DESCRIPTOR_SAVE, len(buf)))
+    __serial.write(buf)
+
+def set_attr(attr, value):
+    __serial.write(struct.pack("<BBI", __USBDBG_CMD, __USBDBG_ATTR_WRITE, 8))
+    __serial.write(struct.pack("<II", attr, value))
+
+def get_attr(attr):
+    __serial.write(struct.pack("<BBIh", __USBDBG_CMD, __USBDBG_ATTR_READ, 1, attr))
+    return __serial.read(1)
+
+def reset():
+    __serial.write(struct.pack("<BBI", __USBDBG_CMD, __USBDBG_SYS_RESET, 0))
+
+def bootloader_start():
+    __serial.write(struct.pack("<I", __BOOTLDR_START))
+    return struct.unpack("I", __serial.read(4))[0] == __BOOTLDR_START
+
+def bootloader_reset():
+    __serial.write(struct.pack("<I", __BOOTLDR_RESET))
+
+def flash_erase(sector):
+    __serial.write(struct.pack("<II", __BOOTLDR_ERASE, sector))
+
+def flash_write(buf):
+    __serial.write(struct.pack("<I", __BOOTLDR_WRITE) + buf)
+
+def tx_buf_len():
+    __serial.write(struct.pack("<BBI", __USBDBG_CMD, __USBDBG_TX_BUF_LEN, 4))
+    return struct.unpack("I", __serial.read(4))[0]
+
+def tx_buf(bytes):
+    __serial.write(struct.pack("<BBI", __USBDBG_CMD, __USBDBG_TX_BUF, bytes))
+    return __serial.read(bytes)
+
+def fw_version():
+    __serial.write(struct.pack("<BBI", __USBDBG_CMD, __USBDBG_FW_VERSION, 12))
+    return struct.unpack("III", __serial.read(12))
+
+def enable_fb(enable):
+    __serial.write(struct.pack("<BBI", __USBDBG_CMD, __USBDBG_FB_ENABLE, 4))
+    __serial.write(struct.pack("<I", enable))
+
+def arch_str():
+    __serial.write(struct.pack("<BBI", __USBDBG_CMD, __USBDBG_ARCH_STR, 64))
+    return __serial.read(64).split('\0', 1)[0]
+
+if __name__ == '__main__':
+    if len(sys.argv)!= 3:
+        print ('usage: pyopenmv.py <port> <script>')
+        sys.exit(1)
+
+    with open(sys.argv[2], 'r') as fin:
+        buf = fin.read()
+
+    disconnect()
+    init(sys.argv[1])
+    stop_script()
+    exec_script(buf)
+    tx_len = tx_buf_len()
+    time.sleep(0.250)
+    if (tx_len):
+        print(tx_buf(tx_len).decode())
+    disconnect()

BIN
requiremnets.txt


BIN
run.lnk


+ 157 - 0
ssd1306.py

@@ -0,0 +1,157 @@
+import time
+import framebuf
+from pyb import SPI
+
+# register definitions
+SET_CONTRAST        = const(0x81)
+SET_ENTIRE_ON       = const(0xa4)
+SET_NORM_INV        = const(0xa6)
+SET_DISP            = const(0xae)
+SET_MEM_ADDR        = const(0x20)
+SET_COL_ADDR        = const(0x21)
+SET_PAGE_ADDR       = const(0x22)
+SET_DISP_START_LINE = const(0x40)
+SET_SEG_REMAP       = const(0xa0)
+SET_MUX_RATIO       = const(0xa8)
+SET_COM_OUT_DIR     = const(0xc0)
+SET_DISP_OFFSET     = const(0xd3)
+SET_COM_PIN_CFG     = const(0xda)
+SET_DISP_CLK_DIV    = const(0xd5)
+SET_PRECHARGE       = const(0xd9)
+SET_VCOM_DESEL      = const(0xdb)
+SET_CHARGE_PUMP     = const(0x8d)
+
+class SSD1306:
+    def __init__(self, width, height, external_vcc):
+        self.width = width
+        self.height = height
+        self.external_vcc = external_vcc
+        self.pages = self.height // 8
+        # Note the subclass must initialize self.framebuf to a framebuffer.
+        # This is necessary because the underlying data buffer is different
+        # between I2C and SPI implementations (I2C needs an extra byte).
+        self.poweron()
+        self.init_display()
+
+    def init_display(self):
+        for cmd in (
+            SET_DISP | 0x00, # off
+            # address setting
+            SET_MEM_ADDR, 0x00, # horizontal
+            # resolution and layout
+            SET_DISP_START_LINE | 0x00,
+            SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0
+            SET_MUX_RATIO, self.height - 1,
+            SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0
+            SET_DISP_OFFSET, 0x00,
+            SET_COM_PIN_CFG, 0x02 if self.height == 32 else 0x12,
+            # timing and driving scheme
+            SET_DISP_CLK_DIV, 0x80,
+            SET_PRECHARGE, 0x22 if self.external_vcc else 0xf1,
+            SET_VCOM_DESEL, 0x30, # 0.83*Vcc
+            # display
+            SET_CONTRAST, 0xff, # maximum
+            SET_ENTIRE_ON, # output follows RAM contents
+            SET_NORM_INV, # not inverted
+            # charge pump
+            SET_CHARGE_PUMP, 0x10 if self.external_vcc else 0x14,
+            SET_DISP | 0x01): # on
+            self.write_cmd(cmd)
+        self.fill(0)
+        self.show()
+
+    def poweroff(self):
+        self.write_cmd(SET_DISP | 0x00)
+
+    def contrast(self, contrast):
+        self.write_cmd(SET_CONTRAST)
+        self.write_cmd(contrast)
+
+    def invert(self, invert):
+        self.write_cmd(SET_NORM_INV | (invert & 1))
+
+    def show(self):
+        x0 = 0
+        x1 = self.width - 1
+        if self.width == 64:
+            # displays with width of 64 pixels are shifted by 32
+            x0 += 32
+            x1 += 32
+        self.write_cmd(SET_COL_ADDR)
+        self.write_cmd(x0)
+        self.write_cmd(x1)
+        self.write_cmd(SET_PAGE_ADDR)
+        self.write_cmd(0)
+        self.write_cmd(self.pages - 1)
+        self.write_framebuf()
+
+    def fill(self, col):
+        self.framebuf.fill(col)
+
+    def pixel(self, x, y, col):
+        self.framebuf.pixel(x, y, col)
+
+    def scroll(self, dx, dy):
+        self.framebuf.scroll(dx, dy)
+
+    def text(self, string, x, y, col=1):
+        self.framebuf.text(string, x, y, col)
+
+class SSD1306_I2C(SSD1306):
+    def __init__(self, width, height, i2c, addr=0x3c, external_vcc=False):
+        self.i2c = i2c
+        self.addr = addr
+        self.temp = bytearray(2)
+        super().__init__(width, height, external_vcc)
+
+    def write_cmd(self, cmd):
+        self.temp[0] = 0x80 # Co=1, D/C#=0
+        self.temp[1] = cmd
+        self.i2c.writeto(self.addr, self.temp)
+
+    def write_data(self, buf):
+        self.temp[0] = self.addr << 1
+        self.temp[1] = 0x40 # Co=0, D/C#=1
+        self.i2c.start()
+        self.i2c.write(self.temp)
+        self.i2c.write(buf)
+        self.i2c.stop()
+        
+        
+
+class SSD1306_SPI(SSD1306):
+    def __init__(self, width, height, spi, dc, res, cs, external_vcc=False):
+        self.rate = 10 * 1024 * 1024
+        dc.init(dc.OUT, value=0)
+        res.init(res.OUT, value=0)
+        cs.init(cs.OUT, value=1)
+        self.spi = spi
+        self.dc = dc
+        self.res = res
+        self.cs = cs
+        self.buffer = bytearray((height // 8) * width)
+        self.framebuf = framebuf.FrameBuffer1(self.buffer, width, height)
+        super().__init__(width, height, external_vcc)
+
+    def write_cmd(self, cmd):
+        self.spi.init(SPI.MASTER, baudrate=self.rate, polarity=0, phase=0)
+        self.cs.high()
+        self.dc.low()
+        self.cs.low()
+        self.spi.write(bytearray([cmd]))
+        self.cs.high()
+
+    def write_framebuf(self):
+        self.spi.init(SPI.MASTER,baudrate=self.rate, polarity=0, phase=0)
+        self.cs.high()
+        self.dc.high()
+        self.cs.low()
+        self.spi.write(self.buffer)
+        self.cs.high()
+
+    def poweron(self):
+        self.res.high()
+        time.sleep(1)
+        self.res.low()
+        time.sleep(10)
+        self.res.high()

+ 1 - 0
wymagane_biblioteki.txt

@@ -0,0 +1 @@
+pip install opencv-python pyserial Pillow pygame