diff --git a/README.md b/README.md index e84b8dd43a24bc1ce41031694e43912d06b37e65..4e9206dd4294b356e5bf2b1b4e21e8293eeccb59 100644 --- a/README.md +++ b/README.md @@ -28,4 +28,5 @@ A valid configuration needs: - at least one appliance - at least one sensor -- every sensor to be associated with an appliance \ No newline at end of file +- every sensor to be associated with an appliance +- an I2C adress between 8 and 119 which is not dividable by 4 but by 2 diff --git a/configurations/production.json b/configurations/production.json index 56538063a7300e3e0b363aef2e2fe199b260f367..e402e57676923a424ad5807d321320aaea28069a 100644 --- a/configurations/production.json +++ b/configurations/production.json @@ -230,5 +230,6 @@ "brightness_5": 100 } } - } + }, + "addr": 62 } \ No newline at end of file diff --git a/configurations/test_with_addr.json b/configurations/test_with_addr.json new file mode 100644 index 0000000000000000000000000000000000000000..64fa5c1302401f0991c1bcaf6a5a9e2d24ffad86 --- /dev/null +++ b/configurations/test_with_addr.json @@ -0,0 +1,96 @@ +{ + "devices": { + "0": { + "name": "RGB Dimmer", + "type": 3, + "pins": { + "red": { + "assigned_pin": "1", + "active_low": false + }, + "green": { + "assigned_pin": "2", + "active_low": false + }, + "blue": { + "assigned_pin": "3", + "active_low": false + } + }, + "parameters": { + "delay": 10 + } + }, + "1": { + "name": "Shutter", + "type": 4, + "pins": { + "up": { + "assigned_pin": "97", + "active_low": true + }, + "down": { + "assigned_pin": "98", + "active_low": true + } + }, + "parameters": { + "tick_ms": 500, + "full_movement_ticks": 30 + } + }, + "2": { + "name": "Generic Binary", + "type": 1, + "pins": { + "signal": { + "assigned_pin": "105", + "active_low": false + } + }, + "parameters": {} + } + }, + "inputs": { + "0": { + "name": "Color cycle button", + "type": 4, + "associated_device": 0, + "pins": { + "signal": { + "assigned_pin": "34", + "active_low": false + } + }, + "parameters": { + "color_1": 0, + "color_2": 16711680, + "color_3": 16742144, + "color_4": 16776960, + "color_5": 65280, + "color_6": 30719, + "color_7": 255, + "color_8": 16711935 + } + }, + "1": { + "name": "Shutter control", + "type": 5, + "associated_device": 1, + "pins": { + "up": { + "assigned_pin": "35", + "active_low": false + }, + "down": { + "assigned_pin": "32", + "active_low": false + } + }, + "parameters": { + "full_press_time": 2000 + } + } + }, + "addr": 88 +} \ No newline at end of file diff --git a/fpga_device_manager/Config.py b/fpga_device_manager/Config.py index 50b9ee380c6e0ba91abf03d1219736f1084284d3..fa2b7d96213f73a1e4e2c826ffd161898d3331ab 100644 --- a/fpga_device_manager/Config.py +++ b/fpga_device_manager/Config.py @@ -1,5 +1,6 @@ """ -This module maintains a device configuration. It provides a device manager for output and input modules each. +This module maintains a device configuration. It provides a device manager for output and input modules each, +as well as the slave address. """ import json import os @@ -16,6 +17,25 @@ from fpga_device_manager.exceptions import DeviceInvalidError, InvalidConfigErro _output_mgr = DeviceManager(device_class=Output) _input_mgr = DeviceManager(device_class=Input) +_i2c_slave_address = 50 + + +def i2c_address(address=None): + """Acts as a property function for the i2c_address""" + global _i2c_slave_address + + if address: + if type(address) != int: + try: + address = int(address) + except ValueError: + pass + + if 8 < address < 119: + _i2c_slave_address = address + else: + return _i2c_slave_address + def clear() -> None: @@ -66,6 +86,8 @@ def load(cfg_filename: str, max_devices: Optional[int] = None) -> None: device = _output_mgr.get(associated_device_id) dev_input.associate(device) + i2c_address(device_file_data["addr"]) + def save(cfg_filename: str) -> None: """ @@ -74,7 +96,8 @@ def save(cfg_filename: str) -> None: """ data = { "devices": _output_mgr.save(), - "inputs": _input_mgr.save() + "inputs": _input_mgr.save(), + "addr": i2c_address() } with open(cfg_filename, "w") as file: @@ -104,6 +127,14 @@ def check() -> None: except DeviceInvalidError as e: errors.append(str(e)) + try: + temp = int(_i2c_slave_address) + if not temp or not(8 < temp < 119): + errors.append("The I2C-Address has to be between 8 and 119") + + except ValueError: + errors.append("The I2C-Address is not numeric") + if len(errors) > 0: raise InvalidConfigError(errors) @@ -135,6 +166,9 @@ def export(out_path: str) -> None: output_pin_indices = {pin.name: index for index, pin in enumerate(output_pins)} input_pin_indices = {pin.name: index for index, pin in enumerate(input_pins)} + # Get the I2C slave address + slave_addr = _i2c_slave_address + # Gather unused pins unused_pins = [pin for pin in Pins.all() if not pin.is_assigned()] @@ -143,6 +177,10 @@ def export(out_path: str) -> None: out_generator = f"{Constants.APP_NAME} {Constants.APP_VERSION}" out_timestamp = datetime.strftime(datetime.now(), "%Y/%m/%d %H:%M:%S") exports = { + "efb.v": tpl_loader.render("efb.v.tpl", + generator=out_generator, + timestamp=out_timestamp, + slave_addr=f"0b{slave_addr:07b}"), "devices_out.v": tpl_loader.render("devices_out.v.tpl", generator=out_generator, timestamp=out_timestamp, diff --git a/fpga_device_manager/res/ui/main.ui b/fpga_device_manager/res/ui/main.ui index 7bb352c643a9201f1fe2ade87497b0639ca34f8a..2193b2e512bb10c32aa267898f858b79b14e20cf 100755 --- a/fpga_device_manager/res/ui/main.ui +++ b/fpga_device_manager/res/ui/main.ui @@ -159,7 +159,7 @@ <enum>QFrame::Sunken</enum> </property> </widget> - </item># + </item> <item row="0" column="0"> <widget class="QFrame" name="fr_reserved"> <property name="minimumSize"> @@ -226,7 +226,7 @@ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> </property> </widget> - </item># + </item> <item row="2" column="4"> <widget class="QLabel" name="label_4"> <property name="text"> @@ -240,7 +240,7 @@ <string>Available (no PWM support)</string> </property> </widget> - </item># + </item> <item row="2" column="0"> <widget class="QFrame" name="fr_inputs"> <property name="minimumSize"> @@ -350,7 +350,7 @@ <item> <widget class="QTabWidget" name="tabs"> <property name="currentIndex"> - <number>0</number> + <number>2</number> </property> <widget class="DeviceListWidget" name="tab_devices"> <attribute name="title"> @@ -362,6 +362,62 @@ <string>Sensors</string> </attribute> </widget> + <widget class="QWidget" name="tab_addr"> + <attribute name="title"> + <string>Device Address</string> + </attribute> + <widget class="QWidget" name="verticalLayoutWidget"> + <property name="geometry"> + <rect> + <x>10</x> + <y>10</y> + <width>351</width> + <height>521</height> + </rect> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string>FPGA-Board I2C-Address</string> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="device_address"> + <property name="toolTip"> + <string>Please enter a valid number between 8 and 119.</string> + </property> + <property name="minimum"> + <number>8</number> + </property> + <property name="maximum"> + <number>119</number> + </property> + <property name="singleStep"> + <number>1</number> + </property> + <property name="value"> + <number>50</number> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </widget> </widget> </item> <item> diff --git a/fpga_device_manager/res/vtemplates/efb.v.tpl b/fpga_device_manager/res/vtemplates/efb.v.tpl new file mode 100644 index 0000000000000000000000000000000000000000..f43cd3ed42e3ac73d576479ff9f69cb000b7a2f4 --- /dev/null +++ b/fpga_device_manager/res/vtemplates/efb.v.tpl @@ -0,0 +1,123 @@ +{% extends "base.tpl" %} +{% block title %}Verilog netlist file{% endblock %} +{% block body %} +`timescale 1 ns / 1 ps +module efb (wb_clk_i, wb_rst_i, wb_cyc_i, wb_stb_i, wb_we_i, wb_adr_i, + wb_dat_i, wb_dat_o, wb_ack_o, i2c2_scl, i2c2_sda, i2c2_irqo)/* synthesis NGD_DRC_MASK=1 */; + input wire wb_clk_i; + input wire wb_rst_i; + input wire wb_cyc_i; + input wire wb_stb_i; + input wire wb_we_i; + input wire [7:0] wb_adr_i; + input wire [7:0] wb_dat_i; + output wire [7:0] wb_dat_o; + output wire wb_ack_o; + output wire i2c2_irqo; + inout wire i2c2_scl; + inout wire i2c2_sda; + + wire scuba_vhi; + wire i2c2_sdaoen; + wire i2c2_sdao; + wire i2c2_scloen; + wire i2c2_sclo; + wire i2c2_sdai; + wire i2c2_scli; + wire scuba_vlo; + + VHI scuba_vhi_inst (.Z(scuba_vhi)); + + BB BB2_sda (.I(i2c2_sdao), .T(i2c2_sdaoen), .O(i2c2_sdai), .B(i2c2_sda)); + + BB BB2_scl (.I(i2c2_sclo), .T(i2c2_scloen), .O(i2c2_scli), .B(i2c2_scl)); + + VLO scuba_vlo_inst (.Z(scuba_vlo)); + + defparam EFBInst_0.UFM_INIT_FILE_FORMAT = "HEX" ; + defparam EFBInst_0.UFM_INIT_FILE_NAME = "NONE" ; + defparam EFBInst_0.UFM_INIT_ALL_ZEROS = "ENABLED" ; + defparam EFBInst_0.UFM_INIT_START_PAGE = 0 ; + defparam EFBInst_0.UFM_INIT_PAGES = 0 ; + defparam EFBInst_0.DEV_DENSITY = "7000L" ; + defparam EFBInst_0.EFB_UFM = "DISABLED" ; + defparam EFBInst_0.TC_ICAPTURE = "DISABLED" ; + defparam EFBInst_0.TC_OVERFLOW = "DISABLED" ; + defparam EFBInst_0.TC_ICR_INT = "OFF" ; + defparam EFBInst_0.TC_OCR_INT = "OFF" ; + defparam EFBInst_0.TC_OV_INT = "OFF" ; + defparam EFBInst_0.TC_TOP_SEL = "OFF" ; + defparam EFBInst_0.TC_RESETN = "ENABLED" ; + defparam EFBInst_0.TC_OC_MODE = "TOGGLE" ; + defparam EFBInst_0.TC_OCR_SET = 32767 ; + defparam EFBInst_0.TC_TOP_SET = 65535 ; + defparam EFBInst_0.GSR = "ENABLED" ; + defparam EFBInst_0.TC_CCLK_SEL = 1 ; + defparam EFBInst_0.TC_MODE = "CTCM" ; + defparam EFBInst_0.TC_SCLK_SEL = "PCLOCK" ; + defparam EFBInst_0.EFB_TC_PORTMODE = "WB" ; + defparam EFBInst_0.EFB_TC = "DISABLED" ; + defparam EFBInst_0.SPI_WAKEUP = "DISABLED" ; + defparam EFBInst_0.SPI_INTR_RXOVR = "DISABLED" ; + defparam EFBInst_0.SPI_INTR_TXOVR = "DISABLED" ; + defparam EFBInst_0.SPI_INTR_RXRDY = "DISABLED" ; + defparam EFBInst_0.SPI_INTR_TXRDY = "DISABLED" ; + defparam EFBInst_0.SPI_SLAVE_HANDSHAKE = "DISABLED" ; + defparam EFBInst_0.SPI_PHASE_ADJ = "DISABLED" ; + defparam EFBInst_0.SPI_CLK_INV = "DISABLED" ; + defparam EFBInst_0.SPI_LSB_FIRST = "DISABLED" ; + defparam EFBInst_0.SPI_CLK_DIVIDER = 1 ; + defparam EFBInst_0.SPI_MODE = "MASTER" ; + defparam EFBInst_0.EFB_SPI = "DISABLED" ; + defparam EFBInst_0.I2C2_WAKEUP = "DISABLED" ; + defparam EFBInst_0.I2C2_GEN_CALL = "DISABLED" ; + defparam EFBInst_0.I2C2_CLK_DIVIDER = 125 ; + defparam EFBInst_0.I2C2_BUS_PERF = "100kHz" ; + defparam EFBInst_0.I2C2_SLAVE_ADDR = "{{ slave_addr }}" ; + defparam EFBInst_0.I2C2_ADDRESSING = "7BIT" ; + defparam EFBInst_0.EFB_I2C2 = "ENABLED" ; + defparam EFBInst_0.I2C1_WAKEUP = "DISABLED" ; + defparam EFBInst_0.I2C1_GEN_CALL = "DISABLED" ; + defparam EFBInst_0.I2C1_CLK_DIVIDER = 1 ; + defparam EFBInst_0.I2C1_BUS_PERF = "100kHz" ; + defparam EFBInst_0.I2C1_SLAVE_ADDR = "{{ slave_addr }}" ; + defparam EFBInst_0.I2C1_ADDRESSING = "7BIT" ; + defparam EFBInst_0.EFB_I2C1 = "DISABLED" ; + defparam EFBInst_0.EFB_WB_CLK_FREQ = "50.0" ; + EFB EFBInst_0 (.WBCLKI(wb_clk_i), .WBRSTI(wb_rst_i), .WBCYCI(wb_cyc_i), + .WBSTBI(wb_stb_i), .WBWEI(wb_we_i), .WBADRI7(wb_adr_i[7]), .WBADRI6(wb_adr_i[6]), + .WBADRI5(wb_adr_i[5]), .WBADRI4(wb_adr_i[4]), .WBADRI3(wb_adr_i[3]), + .WBADRI2(wb_adr_i[2]), .WBADRI1(wb_adr_i[1]), .WBADRI0(wb_adr_i[0]), + .WBDATI7(wb_dat_i[7]), .WBDATI6(wb_dat_i[6]), .WBDATI5(wb_dat_i[5]), + .WBDATI4(wb_dat_i[4]), .WBDATI3(wb_dat_i[3]), .WBDATI2(wb_dat_i[2]), + .WBDATI1(wb_dat_i[1]), .WBDATI0(wb_dat_i[0]), .PLL0DATI7(scuba_vlo), + .PLL0DATI6(scuba_vlo), .PLL0DATI5(scuba_vlo), .PLL0DATI4(scuba_vlo), + .PLL0DATI3(scuba_vlo), .PLL0DATI2(scuba_vlo), .PLL0DATI1(scuba_vlo), + .PLL0DATI0(scuba_vlo), .PLL0ACKI(scuba_vlo), .PLL1DATI7(scuba_vlo), + .PLL1DATI6(scuba_vlo), .PLL1DATI5(scuba_vlo), .PLL1DATI4(scuba_vlo), + .PLL1DATI3(scuba_vlo), .PLL1DATI2(scuba_vlo), .PLL1DATI1(scuba_vlo), + .PLL1DATI0(scuba_vlo), .PLL1ACKI(scuba_vlo), .I2C1SCLI(scuba_vlo), + .I2C1SDAI(scuba_vlo), .I2C2SCLI(i2c2_scli), .I2C2SDAI(i2c2_sdai), + .SPISCKI(scuba_vlo), .SPIMISOI(scuba_vlo), .SPIMOSII(scuba_vlo), + .SPISCSN(scuba_vlo), .TCCLKI(scuba_vlo), .TCRSTN(scuba_vlo), .TCIC(scuba_vlo), + .UFMSN(scuba_vhi), .WBDATO7(wb_dat_o[7]), .WBDATO6(wb_dat_o[6]), + .WBDATO5(wb_dat_o[5]), .WBDATO4(wb_dat_o[4]), .WBDATO3(wb_dat_o[3]), + .WBDATO2(wb_dat_o[2]), .WBDATO1(wb_dat_o[1]), .WBDATO0(wb_dat_o[0]), + .WBACKO(wb_ack_o), .PLLCLKO(), .PLLRSTO(), .PLL0STBO(), .PLL1STBO(), + .PLLWEO(), .PLLADRO4(), .PLLADRO3(), .PLLADRO2(), .PLLADRO1(), .PLLADRO0(), + .PLLDATO7(), .PLLDATO6(), .PLLDATO5(), .PLLDATO4(), .PLLDATO3(), + .PLLDATO2(), .PLLDATO1(), .PLLDATO0(), .I2C1SCLO(), .I2C1SCLOEN(), + .I2C1SDAO(), .I2C1SDAOEN(), .I2C2SCLO(i2c2_sclo), .I2C2SCLOEN(i2c2_scloen), + .I2C2SDAO(i2c2_sdao), .I2C2SDAOEN(i2c2_sdaoen), .I2C1IRQO(), .I2C2IRQO(i2c2_irqo), + .SPISCKO(), .SPISCKEN(), .SPIMISOO(), .SPIMISOEN(), .SPIMOSIO(), + .SPIMOSIEN(), .SPIMCSN7(), .SPIMCSN6(), .SPIMCSN5(), .SPIMCSN4(), + .SPIMCSN3(), .SPIMCSN2(), .SPIMCSN1(), .SPIMCSN0(), .SPICSNEN(), + .SPIIRQO(), .TCINT(), .TCOC(), .WBCUFMIRQ(), .CFGWAKE(), .CFGSTDBY()); + + + + // exemplar begin + // exemplar end + +endmodule +{% endblock %} \ No newline at end of file diff --git a/fpga_device_manager/windows/main_window.py b/fpga_device_manager/windows/main_window.py index 7854b8259e17b067262786f2f0760c45c9d81a70..7189f0b154abace3dcfa0ec2aa0baccebbc3e658 100755 --- a/fpga_device_manager/windows/main_window.py +++ b/fpga_device_manager/windows/main_window.py @@ -70,12 +70,18 @@ class MainWindow(BaseWindow): def refresh(self) -> None: """Updates all widgets.""" + self.update_address() self.update_labels() self.update_buttons() self.update_preview() self.update_widgets() self.update_title() + def update_address(self) -> None: + """Update the I2C-Address.""" + if self.device_address.value() != Config.i2c_address(): + Config.i2c_address(self.device_address.value()) + def update_labels(self) -> None: """Updates the window's labels.""" self.tabs.setTabText(0, "Appliances (%d)" % (len(Config.outputs().devices))) @@ -117,6 +123,7 @@ class MainWindow(BaseWindow): self.reset() Config.load(filename) + self.device_address.setValue(Config.i2c_address()) self.add_all_widgets() self.clear_dirty() self.update_title() @@ -131,6 +138,8 @@ class MainWindow(BaseWindow): Saves the current device configuration. :param filename: Name of file to save to """ + self.refresh() + try: Config.save(filename) self.clear_dirty() @@ -144,6 +153,7 @@ class MainWindow(BaseWindow): :param path: Path to output Verilog files to """ try: + self.refresh() Config.export(path) Popup.info(title="Successfully exported", message=f"Verilog code has been successfully written to {path}.")