From 40e43b2f90290319343acf85811f0f27fa254686 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?V=C3=A1clav=20Jel=C3=ADnek?= <jelinva4@fel.cvut.cz>
Date: Thu, 17 Oct 2024 19:01:32 +0200
Subject: [PATCH] Add basic i2c master library, replace magic numbers with
 constants

---
 lib/cube/__init__.py          |  3 ++-
 lib/cube/esp_config.py        |  6 +++---
 lib/cube/i2c_master.py        | 35 +++++++++++++++++++++++++++++++++++
 lib/cube/sh1106.py            | 23 +++++++++++++++++++++++
 lib/menu_display.py           |  9 ++++++---
 lib/nxt/ultra.py              |  8 +++++---
 lib/robot.py                  | 14 +++++++++++++-
 lib/robot_consts.py           |  3 +++
 main/main.py                  |  2 +-
 menu_programs/cube_utility.py |  2 +-
 menu_programs/i2c_master.py   |  7 ++++---
 11 files changed, 96 insertions(+), 16 deletions(-)
 create mode 100644 lib/cube/i2c_master.py

diff --git a/lib/cube/__init__.py b/lib/cube/__init__.py
index 852e982..8d6a70a 100644
--- a/lib/cube/__init__.py
+++ b/lib/cube/__init__.py
@@ -1,4 +1,5 @@
 from .buzzer import Buzzer
 from .i2c_guard import I2CGuard
 from .sh1106 import SH1106_I2C
-from .esp_config import Esp
\ No newline at end of file
+from .esp_config import Esp
+from .i2c_master import I2C_master
\ No newline at end of file
diff --git a/lib/cube/esp_config.py b/lib/cube/esp_config.py
index 790b0ce..c87cafd 100644
--- a/lib/cube/esp_config.py
+++ b/lib/cube/esp_config.py
@@ -3,7 +3,7 @@ from machine import UART, Timer, Pin
 import utime
 import struct
 
-from lib.hw_defs.pins import INTERNAL_UART_HW_ID, INTERNAL_UART_TX_PIN, INTERNAL_UART_RX_PIN
+from lib.hw_defs.pins import INTERNAL_UART_HW_ID, INTERNAL_UART_TX_PIN, INTERNAL_UART_RX_PIN, BUTTON_POWER_RPIN_BIT
 
 class Esp:
     MAX_PAYLOAD = const(255)
@@ -298,7 +298,7 @@ class Esp:
             self.esp_mode_prev = self.esp_mode
             self.esp_mode_prev = self.esp_mode
             self.esp_mode = Esp.MODE_CMD
-            self.pcf_buttons.set_pin(8, False)
+            self.pcf_buttons.set_pin(BUTTON_POWER_RPIN_BIT, False)
             utime.sleep(0.01)
             self.flush()
        
@@ -307,7 +307,7 @@ class Esp:
             utime.sleep(0.01)
             self.flush()
             self.esp_mode = self.esp_mode_prev
-            self.pcf_buttons.set_pin(8, True)
+            self.pcf_buttons.set_pin(BUTTON_POWER_RPIN_BIT, True)
             utime.sleep(0.01)
 
     def timeout(self):
diff --git a/lib/cube/i2c_master.py b/lib/cube/i2c_master.py
new file mode 100644
index 0000000..10cafa1
--- /dev/null
+++ b/lib/cube/i2c_master.py
@@ -0,0 +1,35 @@
+from machine import I2C, Pin
+
+from lib.hw_defs.pins import UTZ_I2C_SDA_PIN, UTZ_I2C_SCK_PIN, I2C_STRONG_PULL_RPIN 
+from lib.robot_consts import I2C_MULTICUBE_FREQ
+
+class I2C_master:
+    def __init__(self, pcf_buttons, general_add=0x41):
+        self.general_add = general_add
+        self.pcf_buttons = pcf_buttons
+        self.data_received = None
+        self.data_sending = None
+        self.start()
+
+    def start(self):
+        self.pcf_buttons.set_pin(I2C_STRONG_PULL_RPIN, False)
+        self.i2c = I2C(id=0,scl=Pin(UTZ_I2C_SCK_PIN), 
+                       sda=Pin(UTZ_I2C_SDA_PIN), freq=I2C_MULTICUBE_FREQ)
+
+    def read(self, add, len=10):
+        self.data_received = self.i2c.readfrom(add, len)
+        return self.data_received
+
+    def write(self, add, message):
+        self.data_sending = message
+        self.i2c.writeto(add, message)
+
+    def write_general(self, message):
+        self.i2c.writeto(self.general_add, message)
+
+    def get_general_add(self):
+        return self.general_add
+    
+    def deinit(self):
+        self.i2c = None
+        self.pcf_buttons.set_pin(I2C_STRONG_PULL_RPIN, True)
\ No newline at end of file
diff --git a/lib/cube/sh1106.py b/lib/cube/sh1106.py
index 65ab805..12c6ca4 100644
--- a/lib/cube/sh1106.py
+++ b/lib/cube/sh1106.py
@@ -403,6 +403,29 @@ class SH1106(framebuf.FrameBuffer):
             y += 1
         self.show()
 
+    def draw_arrow(self, x: int, y: int, dir: int, c: int = 1):
+        if dir == 0: #left
+            for i in range(4):
+                for j in range(2):
+                    self.pixel(x+i+j, y+i, c)
+                    self.pixel(x+i+j, y-i, c)
+        elif dir == 1: #up
+            for i in range(4):
+                for j in range(2):
+                    self.pixel(x+i, y+i+j, c)
+                    self.pixel(x-i, y+i+j, c)
+        elif dir == 2: #right
+            for i in range(4):
+                for j in range(2):
+                    self.pixel(x-i-j, y+i, c)
+                    self.pixel(x-i-j, y-i, c)
+        elif dir == 3: #down
+            for i in range(4):
+                for j in range(2):
+                    self.pixel(x+i, y-i-j, c)
+                    self.pixel(x-i, y-i-j, c)
+            
+
     def centered_text(self, s: str, y: int, c: int = 1):
         x = max((self.width - 8 * len(s)) // 2, 0)
         self.text(s, x, y, c)
diff --git a/lib/menu_display.py b/lib/menu_display.py
index b302f05..b97187c 100644
--- a/lib/menu_display.py
+++ b/lib/menu_display.py
@@ -4,7 +4,7 @@ from lib.robot_consts import BAT_VOLTAGE_MAX, BAT_VOLTAGE_MIN
 
 DISPLAY_TITLE_POS = 18
 DISPLAY_PROG_POS = 42
-DISPLAY_ARROW_GAP = 10
+DISPLAY_ARROW_GAP = 9
 ESP_MAX_RESET_FAILED = 5
 
 class Menu:
@@ -93,8 +93,11 @@ def display_fill_cube_test(robot, current_cube_test, cube_test_menu_name):
     display_fill_arrows(robot)
 
 def display_fill_arrows(robot):
-    robot.display.centered_text('^', DISPLAY_PROG_POS - DISPLAY_ARROW_GAP, 1)
-    robot.display.centered_text('v', DISPLAY_PROG_POS + DISPLAY_ARROW_GAP, 1)
+    y_pos = DISPLAY_PROG_POS - DISPLAY_ARROW_GAP
+    x_pos = 64
+    robot.display.draw_arrow(x_pos, y_pos, 1, 1)
+    y_pos = DISPLAY_PROG_POS + DISPLAY_ARROW_GAP + 7
+    robot.display.draw_arrow(x_pos, y_pos, 3, 1)
 
 def display_fill_fw_version(robot, fw_version):
     robot.display.fill(0)
diff --git a/lib/nxt/ultra.py b/lib/nxt/ultra.py
index 27aef9a..70be3e1 100644
--- a/lib/nxt/ultra.py
+++ b/lib/nxt/ultra.py
@@ -2,6 +2,8 @@ from machine import Timer, I2C, Pin
 import utime
 import rp2
 
+from lib.hw_defs.pins import UTZ_I2C_SCK_PIN, UTZ_I2C_SDA_PIN
+
 # I2C Nonblocking via PIO
 @rp2.asm_pio(autopush=False, autopull=False, out_shiftdir=rp2.PIO.SHIFT_RIGHT, sideset_init=rp2.PIO.OUT_HIGH, out_init=rp2.PIO.OUT_HIGH)
 def I2C_ReadSensor():
@@ -70,8 +72,8 @@ class UltrasonicSensor:
         
         # Inicialization of I2C, recommended baud rate is 9600, capable of 30000
         if(self.use_nonblocking == True):
-            SDA = Pin(16,mode=Pin.OPEN_DRAIN, pull=Pin.PULL_UP)
-            SCL = Pin(17,mode=Pin.OPEN_DRAIN, pull=Pin.PULL_UP)
+            SDA = Pin(UTZ_I2C_SDA_PIN,mode=Pin.OPEN_DRAIN, pull=Pin.PULL_UP)
+            SCL = Pin(UTZ_I2C_SCK_PIN,mode=Pin.OPEN_DRAIN, pull=Pin.PULL_UP)
             self.sm0 = rp2.StateMachine(0, I2C_ReadSensor, out_base=SDA, sideset_base=SCL, freq=30000*8)
             self.sm1 = rp2.StateMachine(1, I2C_ReadSensor2, in_base=SDA, out_base=SDA, sideset_base=SCL, freq=30000*8)
             
@@ -80,7 +82,7 @@ class UltrasonicSensor:
             self.sm0.irq(self.handler)
             self.sm0.active(1)
         else:    
-            self.i2c = I2C(id=0,scl=Pin(17), sda=Pin(16), freq=(30000))
+            self.i2c = I2C(id=0,scl=Pin(UTZ_I2C_SCK_PIN), sda=Pin(UTZ_I2C_SDA_PIN), freq=(30000))
         # Initialize return buffer    
         if only_first_target == True:
             self.read_buffer = [255]
diff --git a/lib/robot.py b/lib/robot.py
index d4aebaf..200c395 100644
--- a/lib/robot.py
+++ b/lib/robot.py
@@ -1,7 +1,7 @@
 from machine import Pin, I2C, Timer
 from time import sleep, ticks_ms
 
-from lib.cube import Buzzer, I2CGuard, SH1106_I2C, Esp
+from lib.cube import Buzzer, I2CGuard, SH1106_I2C, Esp, I2C_master
 from lib import nxt, ev3, OC
 
 from lib.hw_defs.ports import SENSOR_PORT_PINS, MOTOR_PORT_PINS
@@ -30,6 +30,8 @@ class Robot:
         self.sensors = Sensors()
         self.motors = [EmptyObject() for i in range(len(MOTOR_PORT_PINS))]
 
+        self.i2c_master = EmptyObject()
+
         # Initialize brick components
         self.battery = brick_ll.Battery()
         self.buzzer = Buzzer()
@@ -117,11 +119,21 @@ class Robot:
         gyro_irq_pin = Pin(ICM_IRQ_PIN)
         gyro_irq_pin.irq(handler=None)
 
+    def init_i2c_master(self):
+        if isinstance(self.i2c_master, EmptyObject):
+            self.i2c_master = I2C_master(self.buttons)
+
+    def deinit_i2c_master(self):
+        if not isinstance(self.i2c_master, EmptyObject):
+            self.i2c_master.deinit()
+            self.i2c_master = EmptyObject()
+
     # Deinitialize all hardware components initialized by the user program
     def deinit_all(self):
         self.led.off()
         self.buzzer.off()
         self.deinit_irq()
+        self.deinit_i2c_master()
         self.deinit_motors()
         self.deinit_sensors()
         self.delete_mutex()
diff --git a/lib/robot_consts.py b/lib/robot_consts.py
index ae4fdea..27e439c 100644
--- a/lib/robot_consts.py
+++ b/lib/robot_consts.py
@@ -81,6 +81,9 @@ BAT_VOLTAGE_TURNOFF = (BAT_VOLTAGE_MIN+0.1)			# Volts
 PCF_CHECK_PERIOD = const(50)		# ms
 
 I2C_FREQ = const(400000)			# Hz
+I2C_MULTICUBE_FREQ = const(100000)
+
 ESP_BAUDRATE = const(115200)		# bps
+ESP32_COMMAND_PIN = const(8)
 
 FW_VERSION = "17.10.2024"		# Firmware version
\ No newline at end of file
diff --git a/main/main.py b/main/main.py
index c0aadea..580948d 100644
--- a/main/main.py
+++ b/main/main.py
@@ -39,7 +39,7 @@ def main():
     MENU_PROGRAM_NXT_UTZ_IDX = 3
     menu_programs_functions_size = len(menu_programs_functions)
 
-    menu_cube_programs_functions = (('Gyro Acc', menu_programs.cube_gyro_acc_run), ('Utility', menu_programs.cube_utility_run), 
+    menu_cube_programs_functions = (('Gyro Acc', menu_programs.cube_gyro_acc_run), ('LED & buzzer', menu_programs.cube_utility_run), 
                                     ('Wifi webserver', menu_programs.esp_wifi_run), ('I2C Master', menu_programs.i2c_master_run),
                                     ('I2C Slave', menu_programs.i2c_slave_run))
     menu_cube_programs_functions_size = len(menu_cube_programs_functions)
diff --git a/menu_programs/cube_utility.py b/menu_programs/cube_utility.py
index f8eadb9..9f09c9d 100644
--- a/menu_programs/cube_utility.py
+++ b/menu_programs/cube_utility.py
@@ -24,7 +24,7 @@ def cube_utility_run(robot):
 
         # Show info on the display
         robot.display.fill(0)
-        robot.display.centered_text("Utility", 0, 1)
+        robot.display.centered_text("LED & buzzer", 0, 1)
         robot.display.text('Bat: {:.2f}V'.format(bat_voltage), 0, 16, 1)
         robot.display.text('Counter: {}'.format(counter), 0, 24, 1)
         robot.display.text('LED state: {}'.format(counter % 2 == 0), 0, 32, 1)
diff --git a/menu_programs/i2c_master.py b/menu_programs/i2c_master.py
index c23412a..5197fd4 100644
--- a/menu_programs/i2c_master.py
+++ b/menu_programs/i2c_master.py
@@ -7,7 +7,7 @@ ASCII_a = 97
 
 
 def i2c_master_run(robot):
-    i2c = I2C(id=0,scl=Pin(17), sda=Pin(16), freq=100000)
+    robot.init_i2c_master()
     data_received = 0x5F
     data_sending = 0
     debounce = False
@@ -27,8 +27,9 @@ def i2c_master_run(robot):
         robot.display.text('<', 0, 54, 1)
         robot.display.show()
 
-        i2c.writeto(0x41, (data_sending + ASCII_a).to_bytes(1, 'big'))
-        data_received = int.from_bytes(i2c.readfrom(0x41, 1), 'big')
+        print(robot.i2c_master.i2c.scan())
+        robot.i2c_master.write(0x41, (data_sending + ASCII_a).to_bytes(1, 'big'))
+        data_received = int.from_bytes(robot.i2c_master.read(0x41, 1), 'big')
 
         buttons = robot.buttons.pressed()
         if buttons[Button.LEFT]:
-- 
GitLab