![]() |
PCI-interfacing ADDI-DATA APCI-1516 - Printable Version +- Python Forum (https://python-forum.io) +-- Forum: Python Coding (https://python-forum.io/forum-7.html) +--- Forum: General Coding Help (https://python-forum.io/forum-8.html) +--- Thread: PCI-interfacing ADDI-DATA APCI-1516 (/thread-43856.html) |
PCI-interfacing ADDI-DATA APCI-1516 - Lux_Aitherros - Jan-09-2025 Hello there. I've been searching kinda every place I know of and I am completely exhausted. Thus, I want to ask other developers for their expertise. I have an ADDI-DATA APCI-1516 which I want to talk to using python. Specifically, I want to toggle outputs based on variable frequencies. I've developed a user interface using tkinter and everything works fine so far. I instantiated the driver functions using ctypes. However, upon compiling the script using auto-py-to-exe and running the executable, everything works fine up until I want to toggle the outputs. The program opens the card's communication, but I don't see any LEDs flickering on my PX901-DG breakout board. And yes, a 24V power supply is connected to it, with the jumpers correctly positioned. The drivers are installed, the device manager sees the card and the program is able to talk to it. Btw, I am running Win10 64bit, Python 3.11.4 Now here is my code (idk if it will display as code or as text): import ctypes.wintypes from tkinter import * from tkinter import ttk from tkinter import messagebox from PIL import Image, ImageTk from math import floor import time import threading import ctypes import sys import os class GlobalVariables: pass class PCICommunication: def __init__(self): self.returnValue = -1 self.dll = ctypes.CDLL('C:/Windows/System32/PCI1516.dll') #Acquiring no of boards self.numberOfBoards = ctypes.wintypes.BYTE() self.dll.i_PCI1516_GetNumberOfBoards.argtypes = [ctypes.POINTER(ctypes.wintypes.BYTE)] self.dll.i_PCI1516_GetNumberOfBoards.restype = ctypes.c_int #Acquiring board data self.boardIdentifierSize = 256 self.boardIdentifier = ctypes.create_string_buffer(self.boardIdentifierSize) self.uiNumber = ctypes.wintypes.DWORD() self.deviceNumber = ctypes.wintypes.DWORD() self.busNumber = ctypes.wintypes.DWORD() self.baseAddress0 = ctypes.wintypes.DWORD() self.baseAddress1 = ctypes.wintypes.DWORD() self.interrupt = ctypes.wintypes.BYTE() self.dll.i_PCI1516_GetBoardInformation.argtypes = [ ctypes.wintypes.BYTE, # boardIndex ctypes.c_char_p, # boardIdentifier ctypes.wintypes.DWORD, # boardIdentifierSize ctypes.POINTER(ctypes.wintypes.DWORD), # uiNumber ctypes.POINTER(ctypes.wintypes.DWORD), # deviceNumber ctypes.POINTER(ctypes.wintypes.DWORD), # busNumber ctypes.POINTER(ctypes.wintypes.DWORD), # baseAddress0 ctypes.POINTER(ctypes.wintypes.DWORD), # baseAddress1 ctypes.POINTER(ctypes.wintypes.BYTE)] # interrupt self.dll.i_PCI1516_GetBoardInformation.restype = ctypes.c_int #Open communication self.deviceHandle = ctypes.wintypes.HANDLE() self.dll.i_PCI1516_OpenBoardViaIdentifier.argtypes = [ ctypes.c_char_p, ctypes.POINTER(ctypes.wintypes.HANDLE)] self.dll.i_PCI1516_OpenBoardViaIdentifier.restype = ctypes.c_int #Close communication self.dll.i_PCI1516_CloseBoard.argtypes = (ctypes.wintypes.HANDLE,) self.dll.i_PCI1516_CloseBoard.restype = ctypes.c_int #Activate digital memory - necessary to write to channels self.dll.i_PCI1516_SetDigitalOutputMemoryOn.argtypes = (ctypes.wintypes.HANDLE,) self.dll.i_PCI1516_SetDigitalOutputMemoryOn.restype = ctypes.c_int #Set output channel self.dll.i_PCI1516_Set1DigitalOutputOn.argtypes = [ ctypes.wintypes.HANDLE, ctypes.wintypes.BYTE] self.dll.i_PCI1516_Set1DigitalOutputOn.restype = ctypes.c_int #Reset output channel self.dll.i_PCI1516_Set1DigitalOutputOff.argtypes = [ ctypes.wintypes.HANDLE, ctypes.wintypes.BYTE] self.dll.i_PCI1516_Set1DigitalOutputOff.restype = ctypes.c_int def OpenCommunication(self): self.returnValue = self.dll.i_PCI1516_GetNumberOfBoards(ctypes.byref(self.numberOfBoards)) if self.numberOfBoards.value == 0: noDevicesFoundWindow = messagebox.showerror('No device found', 'No xPCI1516 device found') if noDevicesFoundWindow: quit() #Right now this method is only capable of communicating with ONE card, indexed at 0 if self.numberOfBoards.value > 0: for i in range(self.numberOfBoards.value): self.returnValue = self.dll.i_PCI1516_GetBoardInformation(i, self.boardIdentifier, self.boardIdentifierSize, ctypes.byref(self.uiNumber), ctypes.byref(self.deviceNumber), ctypes.byref(self.busNumber), ctypes.byref(self.baseAddress0), ctypes.byref(self.baseAddress1), ctypes.byref(self.interrupt)) print(f'Index: {i}') print(f'Identifier: {self.boardIdentifier.value}') print(f'Device no: {self.deviceNumber.value}') print(f'Bus no: {self.busNumber.value}') print(f'Base addr 0: {self.baseAddress0.value}') print(f'Base addr 1: {self.baseAddress1.value}') deviceToOpen = input('Which board should be opened (input identifier)?') self.returnValue = self.dll.i_PCI1516_OpenBoardViaIdentifier(ctypes.c_char_p(deviceToOpen.encode('utf-8')), ctypes.byref(self.deviceHandle)) print(f'Opening: {self.returnValue}') if self.returnValue != 0: quittingWindow = messagebox.askretrycancel('Failed setup', f'Could not establish communication with PCI board.\n\n Error code: {self.returnValue}') if quittingWindow: self.OpenCommunication() else: quit() self.returnValue = self.dll.i_PCI1516_SetDigitalOutputMemoryOn(self.deviceHandle) if self.returnValue != 0: quittingWindow = messagebox.askretrycancel('Failed setup', f'Could not activate digital memory.\n\n Error code: {self.returnValue}') if quittingWindow: self.OpenCommunication() else: quit() def CloseCommunication(self): '''Close PCI communication''' self.returnValue = self.dll.i_PCI1516_CloseBoard(self.deviceHandle) print(self.returnValue) def SetOutputChannel(self, channel): '''Set PCI output channel''' self.returnValue = self.dll.i_PCI1516_Set1DigitalOutputOn(self.deviceHandle, channel) print(f'Turning on channel: {channel}. Return: {self.returnValue}') def ResetOutputChannel(self, channel): '''Reset PCI output channel''' self.returnValue = self.dll.i_PCI1516_Set1DigitalOutputOff(self.deviceHandle, channel) print(f'Turning off channel: {channel}. Return: {self.returnValue}') class UserInterface: frames = [] def __init__(self): self.mainWindow = Tk() self.mainWindow.title('ADDI-DATA Communication') self.mainWindow.geometry('372x212') self.mainWindow.resizable(False, False) #Create other widgets self.outputAreaFrame = Frame(self.mainWindow, relief = 'sunken', borderwidth = 1, width = 362, height = 182) self.bottomBar = Frame(self.mainWindow, relief = 'raised', borderwidth = 1) self.versionLabel = Label(self.bottomBar, text = 'V0.1') self.creatorLabel = Label(self.bottomBar, text = 'Developed by Lux A.') #Pack widgets self.outputAreaFrame.pack(padx = 5, pady = 5, fill = 'both') self.outputAreaFrame.pack_propagate(False) self.outputAreaFrame.grid_propagate(False) self.bottomBar.pack(fill = 'x') self.versionLabel.pack(side = 'left') self.creatorLabel.pack(side = 'right') for i in range(8): self.CreateNewOutputArea() self.mainWindow.lift() self.mainWindow.mainloop() def CreateNewOutputArea(self): '''This function is used to create a new area including a toggle switch and frequency input as well as a close button. It appends it to the frames array. The OutputAreas will be arranged in a WxH 8x4 grid''' index = len(self.frames) outputArea = self.OutputArea(master = self.outputAreaFrame, index = index) self.frames.append([index, outputArea]) class OutputArea: def __init__(self, master, index): self.state = 'off' self.index = index #Import images and create ressources try: basePath = sys._MEIPASS #Path where pyInstaller unpacks additional files, used in .exe except Exception: basePath = os.path.abspath('.') #Path of current working directory, used when developing self.offButtonPNG = ImageTk.PhotoImage(Image.open(os.path.join(basePath, 'images/Toggle_Switch_Off.png')).resize((40, 20), Image.LANCZOS)) self.onButtonPNG = ImageTk.PhotoImage(Image.open(os.path.join(basePath, 'images/Toggle_Switch_On.png')).resize((40, 20), Image.LANCZOS)) #Create widgets self.areaFrame = Frame(master, width = 88, height = 88, relief = 'sunken', borderwidth = 1) self.indexLabel = Label(self.areaFrame, text = self.index + 1) self.horizontalSeparator = ttk.Separator(self.areaFrame, orient = 'horizontal') self.toggleOnOffButton = Button(self.areaFrame, image = self.offButtonPNG, relief = 'flat', command = self.ToggleState) self.frequencyInput = Spinbox(self.areaFrame, from_ = 0, to = 1000, width = 10, state = 'readonly') #Pack widgets self.areaFrame.grid_propagate(False) self.indexLabel.grid(row = 0, columnspan = 2, sticky = 'NEWS') self.horizontalSeparator.grid(row = 1, column = 0, columnspan = 2, sticky = 'WE') self.toggleOnOffButton.grid(row = 2, column = 0, sticky = 'NWS') self.frequencyInput.grid(row = 3, column = 0, columnspan = 2, sticky = 'NEWS', pady = 12, padx = 5) #Create area self.areaFrame.grid(row = floor(self.index / 4), column = self.index % 4, padx = 1, pady = 1) #Create the thread to output the frequency self.CreateThread() def ToggleState(self): if self.state == 'off': self.state = 'on' self.toggleOnOffButton.configure(image = self.onButtonPNG) self.CreateThread() else: self.state = 'off' self.toggleOnOffButton.configure(image = self.offButtonPNG) def ToggleOutput(self): while self.state == 'on': frequency = int(self.frequencyInput.get()) if frequency > 0: period = 1 / frequency #Frequented output COM.SetOutputChannel(channel = self.index) time.sleep(period / 2) COM.ResetOutputChannel(channel = self.index) time.sleep(period / 2) else: #permanent output COM.SetOutputChannel(channel = self.index) def CreateThread(self): thread = threading.Thread(target = self.ToggleOutput) thread.daemon = True #Daemonized threads exit when the main program exits thread.start() if __name__ == '__main__': COM = PCICommunication() COM.OpenCommunication() UI = UserInterface() RE: PCI-interfacing ADDI-DATA APCI-1516 - deanhystad - Jan-10-2025 I think your logic is wrong. I would expect that turning an LED ON or OFF would be a single message, but your program sends a continuous stream of messages. I think the only time you need to create a thread is when state == on and frequency > 0, otherwise ToggleOutput should just send a Set or Reset output channel command. If you do create a thread to periodically blink the output, the thread should terminate when the blinking stops (frequency set to 0 or state to off). You could create a thread for each output, but I think it makes more sense to make 1 thread that handles all the outputs. Better yet, use a periodic event instead of a bunch of threads. You really don't want any part of your program to sleep. I would get rid of all the threads and create a timer "event" using the .after() command. This code creates a list of blinking lamps that is updated every millisecond. The lamps use a counter to decide when it is time to toggle the lamp. Since I don't have hardware I simulate the lamps with a label that blinks on and off. import tkinter as tk from tkinter import ttk class Lamp(tk.Frame): """Controls for a lamp.""" def __init__(self, parent, index, command=None): """Initialize lamp. Arguments: parent: My parent widget index: index of lamp I control. command: Command to execute when state or frequency changes. """ super().__init__(parent, borderwidth=1) self.state = 'off' self.index = index self.value = 0 self.frequency = 0 self._command = command self._freq_count = 0 self._counter = 0 label = tk.Label(self, text=f"Lamp {index}") self.toggle_button = tk.Button( self, text="Off", width=3, command=self._toggle_state ) self.display = tk.Label(self, text=" ", width=1) self.freq_input = tk.StringVar(self, "0") spinbox = ttk.Spinbox( self, textvariable=self.freq_input, from_=0, to=500, command=self._frequency_changed ) # Callback for entering text in the spinbox. spinbox.bind('<Return>', self._frequency_changed) spinbox.bind('<FocusOut>', self._frequency_changed) label.grid(row=0, column=0, sticky='news') self.display.grid(row=0, column=1, sticky="news") self.toggle_button.grid(row=1, column=0, columnspan=2, sticky='news') spinbox.grid(row=2, column=0, columnspan=2, sticky='news') def blinking(self): """Return True if this lamp is blinking.""" return self.state == "on" and self.frequency > 0 def blink(self): """Update the lamp.""" self._counter = self._counter - 1 if self._counter <= 0: self._set_value(int(not self.value)) self._counter = self._freq_count def __str__(self): return f"Lamp {self.index}, state={self.state}, frequency={self.frequency}" def __repr__(self): return f"<{self}>" def _set_value(self, value): """Turn lamp on/off.""" self.value = value self.display["text"] = "0" if value else " " # Replace with call to Set or Reset output channel. def _toggle_state(self): """Toggle the lamp state between on and off.""" if self.state == 'off': self.state = 'on' self.toggle_button.configure(text="On") self._counter = self.frequency self._set_value(1) else: self.state = 'off' self.toggle_button.configure(text="Off") self._set_value(0) if self._command is not None: self._command(self) def _frequency_changed(self, *_): """Called when new value entered for frequency.""" try: self.frequency = max(0, min(500, int(self.freq_input.get()))) except (ValueError): self.frequency = 0 self.freq_input.set(self.frequency) self._freq_count = 0 if self.frequency == 0 else 1000 // self.frequency self._counter = self._freq_count if self.frequency == 0: self._set_value(int(self.state == "on")) if self._command is not None: self._command(self) class UserInterface(tk.Tk): """Gui for controlling lamps.""" def __init__(self): super().__init__() self._lamps = [] for i in range(8): lamp = Lamp(self, i, command=self._lamp_changed) lamp.grid(row=i // 4, column=i % 4, sticky='news') self._lamps.append(lamp) self._blinking_lamps = [] def _lamp_changed(self, lamp): """Called when lamp state or frequency changed.""" print(lamp) if lamp.blinking(): if lamp not in self._blinking_lamps: self._blinking_lamps.append(lamp) if len(self._blinking_lamps) == 1: self._blink() elif lamp in self._blinking_lamps: self._blinking_lamps.remove(lamp) print(self._blinking_lamps) def _blink(self): """Update the blinking lamps.""" if self._blinking_lamps: for lamp in self._blinking_lamps: lamp.blink() self.after(1, self._blink) UserInterface().mainloop() RE: PCI-interfacing ADDI-DATA APCI-1516 - Lux_Aitherros - Jan-13-2025 (Jan-10-2025, 03:20 PM)deanhystad Wrote: I think your logic is wrong. I would expect that turning an LED ON or OFF would be a single message, but your program sends a continuous stream of messages. I think the only time you need to create a thread is when state == on and frequency > 0, otherwise ToggleOutput should just send a Set or Reset output channel command. If you do create a thread to periodically blink the output, the thread should terminate when the blinking stops (frequency set to 0 or state to off). Heyo and thanks for your answer. I don't think this is a problem. It could be a problem to continuosly set the output if the desired frequency is 0Hz, but it didn't look like it yet. You might've seen that I even print out each response, the output is always 0, so the card accepts the command and responds with 0 (no error). Another point that takes my focus off of my code is that even the example executables that come with the driver pack can't get the card to set or reset any lights. We've tested the driver examples on a different setup (Win 7, 32 bit, APCI1500) and it worked. I do NOT think it has to do with the card as both of my APCI1516s don't work, rather than it being the operating system. The driver pack I download is the multiarchitecture driver pack for 64 and 32 it systems, but as of now I am at a point where I think it has to do with bit-depth. Originally the card was intended for operation in 32bit systems, could it be that it simply isn't made for 64bit operation? |