Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
test-and-measurement:analog-discovery-pro-3x50:smart-lamp [2022/05/16 11:02] – [Software Setup] Álmos Veres-Vitályos | test-and-measurement:analog-discovery-pro-3x50:smart-lamp [2022/09/12 18:30] (current) – changed forum.digilentinc.com to forum.digilent.com Jeffrey | ||
---|---|---|---|
Line 1: | Line 1: | ||
+ | ====== Building a Battery Powered Smart Lamp with the Analog Discovery Pro (ADP3450/ | ||
+ | ~~TechArticle~~ | ||
+ | {{ : | ||
+ | |||
+ | <WRAP group> | ||
+ | In this guide, the processes of designing and building a battery-powered, | ||
+ | </ | ||
+ | ---- | ||
+ | |||
+ | ===== Planning ===== | ||
+ | <WRAP group> | ||
+ | Before starting designing the lamp, the goals of the project must be established. The central component of the lamp is an RGB LED which should be switched on/off via an application running on a phone. Communication with the phone can be resolved using Bluetooth or Bluetooth Low Energy, but the Low Energy variant (BLE) can be easier to use in some cases, due to the custom service characteristics (more on this later). To power the lamp, a battery can be used which can be charged from the Analog Discovery Pro (the lamp can't be directly powered from the ADP, as it doesn' | ||
+ | |||
+ | In this prototype, a 5 mm RGB LED will be used as the lamp, which is powered by an old phone battery, but the circuit can be scaled for higher power lamps if needed. Communication with the phone is resolved by the [[pmod: | ||
+ | </ | ||
+ | ---- | ||
+ | |||
+ | ===== Inventory ===== | ||
+ | <WRAP group>< | ||
+ | == Hardware == | ||
+ | * [[test-and-measurement: | ||
+ | * a smartphone with Android | ||
+ | * [[pmod: | ||
+ | * [[pmod: | ||
+ | * Lithium-Polymer (LiPo) battery cell | ||
+ | * RGB LED | ||
+ | * battery charger circuit | ||
+ | * [[https:// | ||
+ | * OP484 operational amplifier | ||
+ | * 3x 1KΩ resistor | ||
+ | * 3x 180Ω resistor | ||
+ | * 3x 47μF capacitor | ||
+ | * 3x 2N3904 NPN transistor | ||
+ | </ | ||
+ | == Software == | ||
+ | * [[software: | ||
+ | * [[https:// | ||
+ | * a web browser | ||
+ | * a terminal emulator | ||
+ | |||
+ | **Note:** //WaveForms can be installed by following the [[software: | ||
+ | </ | ||
+ | ---- | ||
+ | |||
+ | ===== Creating a Hardware Abstraction Layer (HAL) for the Analog Discovery Pro ===== | ||
+ | <WRAP group> | ||
+ | A Hardware Abstraction Layer (HAL) is a layer in programming that takes the functions controlling the hardware and makes them more abstract, by generalizing them. In this case, the hardware, the Analog Discovery Pro, is controlled using WaveForms SDK, from a Python script. WaveForms SDK requires a fair amount of stuff to happen to start using an instrument. The SDK uses the ctypes module and most instruments need several initialization functions (like dummy read/write functions in the case of some digital communication protocols) before using them. As the current project uses several instruments, | ||
+ | |||
+ | You can use the module presented in the [[test-and-measurement: | ||
+ | </ | ||
+ | |||
+ | /*<WRAP center round important 60%> | ||
+ | In the attached package, not all the functions were tested, so errors might appear in some cases. Use the package responsibly and feel free to modify it. | ||
+ | </ | ||
+ | |||
+ | |||
+ | <WRAP group> | ||
+ | Create a new folder and copy the **dwfconstants.py** file into it from WaveForms' | ||
+ | |||
+ | --> Wrapper: __init__.py # | ||
+ | <WRAP group>< | ||
+ | This file imports all the other modules and also gives a name to each one of them. Global initialization and cleanup functions are also implemented here. | ||
+ | </ | ||
+ | <code python> | ||
+ | """ | ||
+ | |||
+ | """ | ||
+ | |||
+ | # import every submodule | ||
+ | from WaveForms_HAL import WaveForms_HAL_Device as device | ||
+ | |||
+ | from WaveForms_HAL import WaveForms_HAL_Supply as supply | ||
+ | |||
+ | from WaveForms_HAL import WaveForms_HAL_Scope as scope | ||
+ | from WaveForms_HAL import WaveForms_HAL_Wavegen as wavegen | ||
+ | |||
+ | from WaveForms_HAL import WaveForms_HAL_Logic as logic | ||
+ | from WaveForms_HAL import WaveForms_HAL_Pattern as pattern | ||
+ | from WaveForms_HAL import WaveForms_HAL_Pattern_static as static_pattern | ||
+ | from WaveForms_HAL import WaveForms_HAL_Static as digital | ||
+ | |||
+ | from WaveForms_HAL import WaveForms_HAL_SPI as spi | ||
+ | from WaveForms_HAL import WaveForms_HAL_UART as uart | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | # synchronize submodules | ||
+ | def initialize(): | ||
+ | # connect to the device | ||
+ | if device.connected != True: | ||
+ | device.open() | ||
+ | # copy device handles | ||
+ | supply.hdwf = device.hdwf | ||
+ | scope.hdwf = device.hdwf | ||
+ | wavegen.hdwf = device.hdwf | ||
+ | logic.hdwf = device.hdwf | ||
+ | pattern.hdwf = device.hdwf | ||
+ | digital.hdwf = device.hdwf | ||
+ | spi.hdwf = device.hdwf | ||
+ | uart.hdwf = device.hdwf | ||
+ | return | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def close(): | ||
+ | # close the device | ||
+ | if device.connected: | ||
+ | # reset all instruments | ||
+ | supply.reset() | ||
+ | scope.reset() | ||
+ | wavegen.reset() | ||
+ | logic.reset() | ||
+ | pattern.reset() | ||
+ | static_pattern.reset() | ||
+ | digital.reset() | ||
+ | spi.reset() | ||
+ | uart.reset() | ||
+ | |||
+ | device.close() | ||
+ | return | ||
+ | |||
+ | |||
+ | """ | ||
+ | |||
+ | </ | ||
+ | </ | ||
+ | <-- | ||
+ | |||
+ | --> General: WaveForms_HAL_Device.py # | ||
+ | <WRAP group>< | ||
+ | This file contains functions used for opening and closing the device and error checking. A flag showing the current connection status is also included here. | ||
+ | </ | ||
+ | <code python> | ||
+ | """ | ||
+ | |||
+ | """ | ||
+ | |||
+ | # import necessary modules | ||
+ | from ctypes import * | ||
+ | from WaveForms_HAL.dwfconstants import * | ||
+ | import sys | ||
+ | if sys.platform.startswith(" | ||
+ | dwf = cdll.dwf | ||
+ | elif sys.platform.startswith(" | ||
+ | dwf = cdll.LoadLibrary("/ | ||
+ | else: | ||
+ | dwf = cdll.LoadLibrary(" | ||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | # global variables | ||
+ | hdwf = c_int() | ||
+ | connected = False # the current state of the device | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def check_error(): | ||
+ | # check for errors | ||
+ | errno = c_int() | ||
+ | dwf.FDwfGetLastError(errno) | ||
+ | |||
+ | # if there is an error | ||
+ | if errno != dwfercNoErc: | ||
+ | szerr = create_string_buffer(512) | ||
+ | dwf.FDwfGetLastErrorMsg(szerr) | ||
+ | print(str(szerr.value)) | ||
+ | quit() | ||
+ | return | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def open(): | ||
+ | # open the first connected device | ||
+ | dwf.FDwfDeviceOpen(c_int(-1), | ||
+ | # if there is a problem | ||
+ | if hdwf.value == hdwfNone.value: | ||
+ | # check for errors | ||
+ | check_error() | ||
+ | connected = True # set connection flag | ||
+ | return | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def close(): | ||
+ | dwf.FDwfDeviceClose(hdwf) | ||
+ | connected = False # set connection flag | ||
+ | return | ||
+ | |||
+ | |||
+ | """ | ||
+ | |||
+ | </ | ||
+ | </ | ||
+ | <-- | ||
+ | |||
+ | --> Logic Analyzer: WaveForms_HAL_Logic.py # | ||
+ | <WRAP group>< | ||
+ | This file contains functions controlling the logic analyzer. Besides the initialization and cleanup functions, the most important one is the receive_data() function, which reads the states of the digital I/O lines in a buffer according to the initialization parameters. The function accepts a parameter that specifies whether data specific to a digital I/O line should be selected from the dataset or not. | ||
+ | </ | ||
+ | <code python> | ||
+ | """ | ||
+ | |||
+ | """ | ||
+ | |||
+ | # import necessary modules | ||
+ | from ctypes import * | ||
+ | from WaveForms_HAL.dwfconstants import * | ||
+ | import numpy | ||
+ | import sys | ||
+ | if sys.platform.startswith(" | ||
+ | dwf = cdll.dwf | ||
+ | elif sys.platform.startswith(" | ||
+ | dwf = cdll.LoadLibrary("/ | ||
+ | else: | ||
+ | dwf = cdll.LoadLibrary(" | ||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | # global variables | ||
+ | hdwf = c_int() | ||
+ | data_count = 8192 # buffer size | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def initialize(base_frequency=100e06, | ||
+ | global data_count | ||
+ | data_count = buffer_size | ||
+ | # set internal clock frequency | ||
+ | internal_frequency = c_double() | ||
+ | dwf.FDwfDigitalInInternalClockInfo(hdwf, | ||
+ | dwf.FDwfDigitalInDividerSet(hdwf, | ||
+ | int(internal_frequency.value / base_frequency))) | ||
+ | # set 16-bit sample format | ||
+ | dwf.FDwfDigitalInSampleFormatSet(hdwf, | ||
+ | # set buffer size | ||
+ | dwf.FDwfDigitalInBufferSizeSet(hdwf, | ||
+ | return | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def reset(): | ||
+ | # reset the instrument | ||
+ | dwf.FDwfDigitalInReset(hdwf) | ||
+ | return | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def receive_data(channel=None): | ||
+ | # begin acquisition | ||
+ | dwf.FDwfDigitalInConfigure(hdwf, | ||
+ | while True: | ||
+ | status = c_byte() | ||
+ | dwf.FDwfDigitalInStatus(hdwf, | ||
+ | if status.value == stsDone.value: | ||
+ | # exit loop when finished | ||
+ | break | ||
+ | |||
+ | # get samples | ||
+ | buffer = (c_uint16 * data_count)() | ||
+ | dwf.FDwfDigitalInStatusData(hdwf, | ||
+ | buffer = numpy.fromiter(buffer, | ||
+ | buffer = buffer.tolist() | ||
+ | |||
+ | # break out for every pin | ||
+ | result = [[] for _ in range(16)] | ||
+ | for data in buffer: | ||
+ | for index in range(16): | ||
+ | result[index].append(data & (1 << index)) | ||
+ | |||
+ | # return only what is necessary | ||
+ | if channel != None: | ||
+ | return result[channel] | ||
+ | else: | ||
+ | return result | ||
+ | |||
+ | |||
+ | """ | ||
+ | |||
+ | </ | ||
+ | </ | ||
+ | <-- | ||
+ | |||
+ | --> Pattern Generator: WaveForms_HAL_Pattern.py # | ||
+ | <WRAP group>< | ||
+ | The most important part of this file is the function that generates the required signal on the required channel according to the input parameters. | ||
+ | </ | ||
+ | <code python> | ||
+ | """ | ||
+ | |||
+ | """ | ||
+ | |||
+ | # import necessary modules | ||
+ | from ctypes import * | ||
+ | from WaveForms_HAL.dwfconstants import * | ||
+ | import WaveForms_HAL.WaveForms_HAL_Static as digital | ||
+ | import math | ||
+ | import sys | ||
+ | if sys.platform.startswith(" | ||
+ | dwf = cdll.dwf | ||
+ | elif sys.platform.startswith(" | ||
+ | dwf = cdll.LoadLibrary("/ | ||
+ | else: | ||
+ | dwf = cdll.LoadLibrary(" | ||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | # global variables | ||
+ | hdwf = c_int() | ||
+ | |||
+ | # function types | ||
+ | |||
+ | |||
+ | class type: | ||
+ | pulse = DwfDigitalOutTypePulse | ||
+ | custom = DwfDigitalOutTypeCustom | ||
+ | random = DwfDigitalOutTypeRandom | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def reset(): | ||
+ | # reset the instrument | ||
+ | dwf.FDwfDigitalOutReset(hdwf) | ||
+ | return | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def generate(channel, | ||
+ | # reset pin | ||
+ | digital.stop(channel) | ||
+ | |||
+ | # get internal clock frequency | ||
+ | internal_frequency = c_double() | ||
+ | dwf.FDwfDigitalOutInternalClockInfo(hdwf, | ||
+ | # get counter value range | ||
+ | counter_limit = c_uint() | ||
+ | dwf.FDwfDigitalOutCounterInfo(hdwf, | ||
+ | # calculate the divider for the given frequency | ||
+ | divider = int(math.ceil(internal_frequency.value / | ||
+ | frequency / counter_limit.value)) | ||
+ | # calculate counter steps to get the required frequency | ||
+ | steps = int(round(internal_frequency.value / frequency / divider)) | ||
+ | # calculate steps for low and high parts of the period | ||
+ | high_steps = int(steps * duty_cycle / 100) | ||
+ | low_steps = int(steps - high_steps) | ||
+ | |||
+ | # enable the respective channel | ||
+ | dwf.FDwfDigitalOutEnableSet(hdwf, | ||
+ | # set output type | ||
+ | dwf.FDwfDigitalOutTypeSet(hdwf, | ||
+ | # set frequency | ||
+ | dwf.FDwfDigitalOutDividerSet(hdwf, | ||
+ | if function == type.pulse: | ||
+ | # set duty cycle | ||
+ | dwf.FDwfDigitalOutCounterSet(hdwf, | ||
+ | channel), c_int(low_steps), | ||
+ | elif function == type.custom: | ||
+ | # format data | ||
+ | buffer = (c_ubyte * ((len(data) + 7) >> 3))(0) | ||
+ | for index in range(len(data)): | ||
+ | if data[index] != 0: | ||
+ | buffer[index >> 3] |= 1 << (index & 7) | ||
+ | # load data | ||
+ | dwf.FDwfDigitalOutDataSet(hdwf, | ||
+ | channel), byref(buffer), | ||
+ | |||
+ | # start generating the signal | ||
+ | dwf.FDwfDigitalOutConfigure(hdwf, | ||
+ | return | ||
+ | |||
+ | |||
+ | """ | ||
+ | |||
+ | </ | ||
+ | </ | ||
+ | <-- | ||
+ | |||
+ | --> Static Pattern Generator: WaveForms_HAL_Pattern_static.py # | ||
+ | <WRAP group>< | ||
+ | Due to hardware limitations, | ||
+ | |||
+ | To make use of this possibility, | ||
+ | |||
+ | The current version of this module uses multithreading to realize several operations in parallel (actually the processor just switches very fast between the tasks). | ||
+ | </ | ||
+ | <code python> | ||
+ | """ | ||
+ | |||
+ | """ | ||
+ | |||
+ | # import necessary modules | ||
+ | import WaveForms_HAL.WaveForms_HAL_Static as digital | ||
+ | import time | ||
+ | import threading | ||
+ | import random | ||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | # list for processes | ||
+ | processes = [None, None, None, None, | ||
+ | None, None, None, None, | ||
+ | None, None, None, None, | ||
+ | None, None, None, None] | ||
+ | |||
+ | |||
+ | # class containing all channel parameters | ||
+ | class channel_data: | ||
+ | frequency = None | ||
+ | duty_cycle = None | ||
+ | signal_data = None | ||
+ | function_type = None | ||
+ | thread = None | ||
+ | stop = None | ||
+ | |||
+ | |||
+ | # function types | ||
+ | class type: | ||
+ | pulse = 0 | ||
+ | custom = 1 | ||
+ | random = 2 | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def reset(): | ||
+ | # stop all threads | ||
+ | for index in range(16): | ||
+ | stop(index) | ||
+ | |||
+ | # reset the instrument | ||
+ | digital.reset() | ||
+ | return | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def stop(channel): | ||
+ | global processes | ||
+ | |||
+ | # stop the generation | ||
+ | if processes[channel] != None: | ||
+ | processes[channel].stop = True | ||
+ | processes[channel].thread.join() | ||
+ | processes[channel] = None | ||
+ | |||
+ | # reset the pin | ||
+ | digital.stop(channel) | ||
+ | return | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def generate(channel, | ||
+ | global processes | ||
+ | |||
+ | # stop running thread | ||
+ | if processes[channel] != None: | ||
+ | stop(channel) | ||
+ | |||
+ | # set pin as output | ||
+ | digital.set_output(channel) | ||
+ | |||
+ | # save current channel data | ||
+ | current_ch = channel_data() | ||
+ | current_ch.frequency = frequency | ||
+ | current_ch.duty_cycle = duty_cycle | ||
+ | current_ch.signal_data = data | ||
+ | current_ch.function_type = function | ||
+ | current_ch.stop = False | ||
+ | current_ch.thread = threading.Thread(target=channel_handler, | ||
+ | |||
+ | # update data table | ||
+ | processes[channel] = current_ch | ||
+ | |||
+ | # start thread | ||
+ | processes[channel].thread.start() | ||
+ | |||
+ | return | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def channel_handler(channel): | ||
+ | global processes | ||
+ | |||
+ | loop_index = 0 | ||
+ | |||
+ | # start looping | ||
+ | while processes[channel].stop != True: | ||
+ | |||
+ | # measure start time | ||
+ | start_time = time.time() | ||
+ | |||
+ | if processes[channel].function_type == type.pulse: | ||
+ | # calculate waiting times | ||
+ | period = 1 / processes[channel].frequency | ||
+ | high_period = period * processes[channel].duty_cycle / 100 | ||
+ | low_period = period - high_period | ||
+ | # generate low cycle | ||
+ | digital.write(channel, | ||
+ | # wait | ||
+ | duration = time.time() - start_time | ||
+ | if duration < low_period: | ||
+ | time.sleep(low_period - duration) | ||
+ | # record starting time | ||
+ | start_time = time.time() | ||
+ | # generate high cycle | ||
+ | digital.write(channel, | ||
+ | # wait | ||
+ | duration = time.time() - start_time | ||
+ | if duration < high_period: | ||
+ | time.sleep(high_period - duration) | ||
+ | | ||
+ | elif processes[channel].function_type == type.custom: | ||
+ | # generate custom signal | ||
+ | data = processes[channel].signal_data | ||
+ | digital.write(channel, | ||
+ | # wait | ||
+ | duration = time.time() - start_time | ||
+ | period = 1 / processes[channel].frequency | ||
+ | if duration < period: | ||
+ | time.sleep(period - duration) | ||
+ | |||
+ | else: | ||
+ | # generate random signal | ||
+ | digital.write(channel, | ||
+ | # wait | ||
+ | duration = time.time() - start_time | ||
+ | period = 1 / processes[channel].frequency | ||
+ | if duration < period: | ||
+ | time.sleep(period - duration) | ||
+ | |||
+ | # increment loop index | ||
+ | loop_index += 1 | ||
+ | return | ||
+ | |||
+ | |||
+ | """ | ||
+ | |||
+ | </ | ||
+ | </ | ||
+ | <-- | ||
+ | |||
+ | --> Oscilloscope: | ||
+ | <WRAP group>< | ||
+ | This file contains functions that can command the oscilloscope to measure voltages on a specified channel or to fill a buffer with the recorded data point according to the settings with which the instrument was initialized. | ||
+ | </ | ||
+ | <code python> | ||
+ | """ | ||
+ | |||
+ | """ | ||
+ | |||
+ | # import necessary modules | ||
+ | from ctypes import * | ||
+ | from WaveForms_HAL.dwfconstants import * | ||
+ | import numpy | ||
+ | import sys | ||
+ | if sys.platform.startswith(" | ||
+ | dwf = cdll.dwf | ||
+ | elif sys.platform.startswith(" | ||
+ | dwf = cdll.LoadLibrary("/ | ||
+ | else: | ||
+ | dwf = cdll.LoadLibrary(" | ||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | # global variables | ||
+ | hdwf = c_int() | ||
+ | |||
+ | # triggering flag | ||
+ | trigger = False | ||
+ | # buffer size | ||
+ | data_count = 8192 | ||
+ | |||
+ | # possible trigger options | ||
+ | |||
+ | |||
+ | class trig: | ||
+ | # sources | ||
+ | class src: | ||
+ | no = (trigsrcNone, | ||
+ | scope_ch = [(trigsrcDetectorAnalogIn, | ||
+ | (trigsrcDetectorAnalogIn, | ||
+ | digital_ch = [(trigsrcDetectorDigitalIn, | ||
+ | (trigsrcDetectorDigitalIn, | ||
+ | (trigsrcDetectorDigitalIn, | ||
+ | (trigsrcDetectorDigitalIn, | ||
+ | (trigsrcDetectorDigitalIn, | ||
+ | (trigsrcDetectorDigitalIn, | ||
+ | (trigsrcDetectorDigitalIn, | ||
+ | (trigsrcDetectorDigitalIn, | ||
+ | external_ch = [(trigsrcExternal1, | ||
+ | | ||
+ | # types | ||
+ | |||
+ | class type: | ||
+ | edge = trigtypeEdge | ||
+ | pulse = trigtypePulse | ||
+ | transition = trigtypeTransition | ||
+ | # edges | ||
+ | |||
+ | class edge: | ||
+ | rising = trigcondRisingPositive | ||
+ | falling = trigcondFallingNegative | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def initialize(offset=0, | ||
+ | global data_count | ||
+ | data_count = buffer_size | ||
+ | # enable all channels | ||
+ | dwf.FDwfAnalogInChannelEnableSet(hdwf, | ||
+ | # set offset voltage | ||
+ | dwf.FDwfAnalogInChannelOffsetSet(hdwf, | ||
+ | # set range | ||
+ | dwf.FDwfAnalogInChannelRangeSet(hdwf, | ||
+ | # set the buffer size | ||
+ | dwf.FDwfAnalogInBufferSizeSet(hdwf, | ||
+ | # set the acquisition frequency | ||
+ | dwf.FDwfAnalogInFrequencySet(hdwf, | ||
+ | # disable averaging | ||
+ | dwf.FDwfAnalogInChannelFilterSet(hdwf, | ||
+ | |||
+ | # set up triggering | ||
+ | if trigger: | ||
+ | # enable/ | ||
+ | dwf.FDwfAnalogInTriggerAutoTimeoutSet(hdwf, | ||
+ | # set trigger source | ||
+ | dwf.FDwfAnalogInTriggerSourceSet(hdwf, | ||
+ | if trigger_src[1] != None: | ||
+ | dwf.FDwfAnalogInTriggerChannelSet(hdwf, | ||
+ | # set trigger type | ||
+ | dwf.FDwfAnalogInTriggerTypeSet(hdwf, | ||
+ | # set trigger level | ||
+ | dwf.FDwfAnalogInTriggerLevelSet(hdwf, | ||
+ | # set trigger edge | ||
+ | dwf.FDwfAnalogInTriggerConditionSet(hdwf, | ||
+ | return | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def reset(): | ||
+ | # reset the instrument | ||
+ | dwf.FDwfAnalogInReset(hdwf) | ||
+ | return | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def measure(channel): | ||
+ | # set up the instrument | ||
+ | dwf.FDwfAnalogInConfigure(hdwf, | ||
+ | # read data to buffer | ||
+ | dwf.FDwfAnalogInStatus(hdwf, | ||
+ | # extract data from buffer | ||
+ | voltage = c_double() | ||
+ | dwf.FDwfAnalogInStatusSample(hdwf, | ||
+ | return voltage.value | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def receive(channel): | ||
+ | # set up the instrument | ||
+ | dwf.FDwfAnalogInConfigure(hdwf, | ||
+ | # read data to buffer | ||
+ | while True: | ||
+ | status = c_byte() | ||
+ | dwf.FDwfAnalogInStatus(hdwf, | ||
+ | if status.value == DwfStateDone.value: | ||
+ | # exit loop when ready | ||
+ | break | ||
+ | |||
+ | # copy buffer | ||
+ | buffer = (c_double * data_count)() | ||
+ | dwf.FDwfAnalogInStatusData(hdwf, | ||
+ | |||
+ | # convert list | ||
+ | buffer = numpy.fromiter(buffer, | ||
+ | buffer = buffer.tolist() | ||
+ | return buffer | ||
+ | |||
+ | |||
+ | """ | ||
+ | |||
+ | </ | ||
+ | </ | ||
+ | <-- | ||
+ | |||
+ | --> Serial Peripheral Interface (SPI) Master: WaveForms_HAL_SPI.py # | ||
+ | <WRAP group>< | ||
+ | The Analog Discovery Pro can initiate SPI communication on any of the digital pins, after which it can exchange data with the connected slave device. | ||
+ | </ | ||
+ | <code python> | ||
+ | """ | ||
+ | |||
+ | """ | ||
+ | |||
+ | # import necessary modules | ||
+ | from ctypes import * | ||
+ | from WaveForms_HAL.dwfconstants import * | ||
+ | import sys | ||
+ | if sys.platform.startswith(" | ||
+ | dwf = cdll.dwf | ||
+ | elif sys.platform.startswith(" | ||
+ | dwf = cdll.LoadLibrary("/ | ||
+ | else: | ||
+ | dwf = cdll.LoadLibrary(" | ||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | # global variables | ||
+ | hdwf = c_int() | ||
+ | |||
+ | # endianness | ||
+ | |||
+ | |||
+ | class bit_order: | ||
+ | MSB_first = 1 | ||
+ | LSB_first = 0 | ||
+ | |||
+ | # pins | ||
+ | |||
+ | |||
+ | class pins: | ||
+ | cs = None | ||
+ | sck = None | ||
+ | mosi = None | ||
+ | miso = None | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def initialize(cs_pin, | ||
+ | # save pin numbers globally | ||
+ | pins.cs = cs_pin | ||
+ | pins.sck = sck_pin | ||
+ | pins.mosi = mosi_pin | ||
+ | pins.miso = miso_pin | ||
+ | |||
+ | dwf.FDwfDigitalSpiFrequencySet(hdwf, | ||
+ | frequency)) | ||
+ | dwf.FDwfDigitalSpiClockSet(hdwf, | ||
+ | |||
+ | if mosi_pin != None: | ||
+ | dwf.FDwfDigitalSpiDataSet(hdwf, | ||
+ | 0), c_int(mosi_pin)) | ||
+ | dwf.FDwfDigitalSpiIdleSet(hdwf, | ||
+ | 0), DwfDigitalOutIdleZet) | ||
+ | if miso_pin != None: | ||
+ | dwf.FDwfDigitalSpiDataSet(hdwf, | ||
+ | 1), c_int(miso_pin)) | ||
+ | dwf.FDwfDigitalSpiIdleSet(hdwf, | ||
+ | 1), DwfDigitalOutIdleZet) | ||
+ | |||
+ | dwf.FDwfDigitalSpiModeSet(hdwf, | ||
+ | dwf.FDwfDigitalSpiOrderSet(hdwf, | ||
+ | dwf.FDwfDigitalSpiSelect(hdwf, | ||
+ | cs_pin), c_int(1)) | ||
+ | |||
+ | dwf.FDwfDigitalSpiWriteOne(hdwf, | ||
+ | 1), c_int(0), c_int(0)) | ||
+ | return | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def reset(): | ||
+ | # reset the instrument | ||
+ | dwf.FDwfDigitalSpiReset(hdwf) | ||
+ | return | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def send(data): | ||
+ | # cast data | ||
+ | if type(data) == int: | ||
+ | data = "" | ||
+ | elif type(data) == list: | ||
+ | data = "" | ||
+ | |||
+ | # enable the chip select line | ||
+ | dwf.FDwfDigitalSpiSelect(hdwf, | ||
+ | |||
+ | # create buffer to write | ||
+ | data = (c_ubyte * len(data))(*[c_ubyte(ord(character)) for character in data]) | ||
+ | dwf.FDwfDigitalSpiWrite(hdwf, | ||
+ | len(data))) | ||
+ | |||
+ | # disable the chip select line | ||
+ | dwf.FDwfDigitalSpiSelect(hdwf, | ||
+ | return | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def receive(count): | ||
+ | # enable the chip select line | ||
+ | dwf.FDwfDigitalSpiSelect(hdwf, | ||
+ | |||
+ | # create buffer to store data | ||
+ | buffer = (c_ubyte*count)() | ||
+ | dwf.FDwfDigitalSpiRead(hdwf, | ||
+ | len(buffer))) | ||
+ | |||
+ | # disable the chip select line | ||
+ | dwf.FDwfDigitalSpiSelect(hdwf, | ||
+ | |||
+ | # decode data | ||
+ | data = [str(bin(element))[2: | ||
+ | data = [int(element, | ||
+ | data = "" | ||
+ | |||
+ | return data | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def exchange(data, | ||
+ | # cast data | ||
+ | if type(data) == int: | ||
+ | data = "" | ||
+ | elif type(data) == list: | ||
+ | data = "" | ||
+ | |||
+ | # enable the chip select line | ||
+ | dwf.FDwfDigitalSpiSelect(hdwf, | ||
+ | |||
+ | # create buffer to write | ||
+ | tx_buff = (c_ubyte * len(data)).from_buffer_copy(data) | ||
+ | # create buffer to store data | ||
+ | rx_buff = (c_ubyte*count)() | ||
+ | |||
+ | dwf.FDwfDigitalSpiWriteRead(hdwf, | ||
+ | len(tx_buff)), | ||
+ | |||
+ | # decode data | ||
+ | data = [str(bin(element))[2: | ||
+ | data = [int(element, | ||
+ | data = "" | ||
+ | |||
+ | # disable the chip select line | ||
+ | dwf.FDwfDigitalSpiSelect(hdwf, | ||
+ | return data | ||
+ | |||
+ | |||
+ | """ | ||
+ | |||
+ | </ | ||
+ | </ | ||
+ | <-- | ||
+ | |||
+ | --> Static I/O: WaveForms_HAL_Static.py # | ||
+ | <WRAP group>< | ||
+ | This module can read/write the state of a digital I/O line and can set the line as input, or output. | ||
+ | </ | ||
+ | <code python> | ||
+ | """ | ||
+ | |||
+ | """ | ||
+ | |||
+ | # import necessary modules | ||
+ | from ctypes import * | ||
+ | from WaveForms_HAL.dwfconstants import * | ||
+ | import sys | ||
+ | if sys.platform.startswith(" | ||
+ | dwf = cdll.dwf | ||
+ | elif sys.platform.startswith(" | ||
+ | dwf = cdll.LoadLibrary("/ | ||
+ | else: | ||
+ | dwf = cdll.LoadLibrary(" | ||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | # global variables | ||
+ | hdwf = c_int() | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def reset(): | ||
+ | # reset the instrument | ||
+ | dwf.FDwfDigitalIOReset(hdwf) | ||
+ | return | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def stop(pin): | ||
+ | # set pin to LOW | ||
+ | write(pin, False) | ||
+ | # disable static I/O | ||
+ | set_output(pin, | ||
+ | return | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def read(pin): | ||
+ | # read a digital pin | ||
+ | # load internal buffer with current state of the pins | ||
+ | dwf.FDwfDigitalIOStatus(hdwf) | ||
+ | data = c_uint32() | ||
+ | # get the current state of the pins | ||
+ | dwf.FDwfDigitalIOInputStatus(hdwf, | ||
+ | # convert the state to a 16 character binary string | ||
+ | data = list(bin(data.value)[2: | ||
+ | |||
+ | # check the required bit | ||
+ | if data[15 - pin] != " | ||
+ | # if it is one, return True (HIGH) | ||
+ | return True | ||
+ | else: | ||
+ | # else return False (LOW) | ||
+ | return False | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def set_output(pin, | ||
+ | # set a pin as output | ||
+ | # load current state of the output enable buffer | ||
+ | mask = c_uint16() | ||
+ | dwf.FDwfDigitalIOOutputEnableGet(hdwf, | ||
+ | |||
+ | # set bit mask | ||
+ | mask = set_mask(pin, | ||
+ | |||
+ | # set the pin to output | ||
+ | dwf.FDwfDigitalIOOutputEnableSet(hdwf, | ||
+ | return | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def write(pin, state): | ||
+ | # set a pin as output | ||
+ | # load current state of the output state buffer | ||
+ | mask = c_uint16() | ||
+ | dwf.FDwfDigitalIOOutputGet(hdwf, | ||
+ | |||
+ | # set bit mask | ||
+ | mask = set_mask(pin, | ||
+ | |||
+ | # set the pin state | ||
+ | dwf.FDwfDigitalIOOutputSet(hdwf, | ||
+ | return | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def set_mask(pin, | ||
+ | # convert to list | ||
+ | mask = list(bin(mask.value)[2: | ||
+ | |||
+ | # set bit | ||
+ | if value: | ||
+ | mask[15 - pin] = " | ||
+ | else: | ||
+ | mask[15 - pin] = " | ||
+ | |||
+ | # convert to number | ||
+ | mask = "" | ||
+ | mask = int(mask, 2) | ||
+ | return mask | ||
+ | |||
+ | |||
+ | """ | ||
+ | |||
+ | </ | ||
+ | </ | ||
+ | <-- | ||
+ | |||
+ | --> Supplies: WaveForms_HAL_Supply.py # | ||
+ | <WRAP group>< | ||
+ | This module can turn on/off the 3.3V power supply | ||
+ | </ | ||
+ | <code python> | ||
+ | """ | ||
+ | |||
+ | """ | ||
+ | |||
+ | # import necessary modules | ||
+ | from ctypes import * | ||
+ | from WaveForms_HAL.dwfconstants import * | ||
+ | import sys | ||
+ | if sys.platform.startswith(" | ||
+ | dwf = cdll.dwf | ||
+ | elif sys.platform.startswith(" | ||
+ | dwf = cdll.LoadLibrary("/ | ||
+ | else: | ||
+ | dwf = cdll.LoadLibrary(" | ||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | # global variables | ||
+ | hdwf = c_int() | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def reset(): | ||
+ | # reset the instrument | ||
+ | dwf.FDwfAnalogIOReset(hdwf) | ||
+ | return | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def switch(state): | ||
+ | # start/stop the power supplies | ||
+ | # set the output voltage to 3.3V | ||
+ | dwf.FDwfAnalogIOChannelNodeSet(hdwf, | ||
+ | # start/stop the supplies | ||
+ | dwf.FDwfAnalogIOEnableSet(hdwf, | ||
+ | return | ||
+ | |||
+ | |||
+ | """ | ||
+ | |||
+ | </ | ||
+ | </ | ||
+ | <-- | ||
+ | |||
+ | --> Universal Asynchronous Receiver-Transmitter (UART) Master: WaveForms_HAL_UART.py # | ||
+ | <WRAP group>< | ||
+ | This module can be used to initialize UART communication on any of the digital I/O lines, then send/ | ||
+ | </ | ||
+ | <code python> | ||
+ | """ | ||
+ | |||
+ | """ | ||
+ | |||
+ | # import necessary modules | ||
+ | from ctypes import * | ||
+ | from WaveForms_HAL.dwfconstants import * | ||
+ | import sys | ||
+ | if sys.platform.startswith(" | ||
+ | dwf = cdll.dwf | ||
+ | elif sys.platform.startswith(" | ||
+ | dwf = cdll.LoadLibrary("/ | ||
+ | else: | ||
+ | dwf = cdll.LoadLibrary(" | ||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | # global variables | ||
+ | hdwf = c_int() | ||
+ | |||
+ | # possible parity settings | ||
+ | |||
+ | |||
+ | class parity: | ||
+ | none = 0 | ||
+ | odd = 1 | ||
+ | even = 2 | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def initialize(rx_pin, | ||
+ | dwf.FDwfDigitalUartRateSet(hdwf, | ||
+ | dwf.FDwfDigitalUartTxSet(hdwf, | ||
+ | dwf.FDwfDigitalUartRxSet(hdwf, | ||
+ | dwf.FDwfDigitalUartBitsSet(hdwf, | ||
+ | # set parity bit requirements | ||
+ | dwf.FDwfDigitalUartParitySet(hdwf, | ||
+ | dwf.FDwfDigitalUartStopSet(hdwf, | ||
+ | |||
+ | # initialize tx with idle levels | ||
+ | reset_tx() | ||
+ | reset_rx() | ||
+ | return | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def reset(): | ||
+ | # reset the instrument | ||
+ | dwf.FDwfDigitalUartReset(hdwf) | ||
+ | return | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def reset_rx(): | ||
+ | # initialize rx (dummy receive) | ||
+ | dummy_buffer = c_int(0) | ||
+ | dummy_parity_flag = c_int(0) | ||
+ | dwf.FDwfDigitalUartRx(hdwf, | ||
+ | dummy_parity_flag)) | ||
+ | return | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def reset_tx(): | ||
+ | # initialize tx with idle level (dummy send) | ||
+ | dwf.FDwfDigitalUartTx(hdwf, | ||
+ | return | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def send(data): | ||
+ | # cast data | ||
+ | if type(data) == int: | ||
+ | data = "" | ||
+ | elif type(data) == list: | ||
+ | data = "" | ||
+ | |||
+ | # encode the string into a string buffer | ||
+ | data = create_string_buffer(data.encode(" | ||
+ | |||
+ | # send text, trim zero ending | ||
+ | dwf.FDwfDigitalUartTx(hdwf, | ||
+ | return | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def receive(chunk="", | ||
+ | data = create_string_buffer(8193) | ||
+ | count = c_int(0) | ||
+ | parity_flag = c_int(0) | ||
+ | dwf.FDwfDigitalUartRx(hdwf, | ||
+ | sizeof(data)-1), | ||
+ | |||
+ | if count.value > 0: | ||
+ | data[count.value] = 0 # add zero ending | ||
+ | # make a string from the string buffer | ||
+ | data = list(data.value) | ||
+ | data = "" | ||
+ | |||
+ | # attach previous data | ||
+ | data = chunk + data | ||
+ | |||
+ | # decode parity check results | ||
+ | if parity_flag.value == 0 and error == "no error": | ||
+ | parity_flag = "no error" | ||
+ | elif parity_flag.value < 0: | ||
+ | parity_flag = " | ||
+ | elif parity_flag.value > 0: | ||
+ | parity_flag = " | ||
+ | else: | ||
+ | parity_flag = error | ||
+ | |||
+ | # propagate previous results | ||
+ | data, parity_flag = receive(chunk=data, | ||
+ | |||
+ | else: | ||
+ | data = chunk | ||
+ | parity_flag = error | ||
+ | |||
+ | return data, parity_flag | ||
+ | |||
+ | |||
+ | """ | ||
+ | |||
+ | </ | ||
+ | </ | ||
+ | <-- | ||
+ | |||
+ | --> Waveform Generator: WaveForms_HAL_Wavegen.py # | ||
+ | <WRAP group>< | ||
+ | This module can be used to generate analog signals with the required parameters. Possible functions are also present in the module as class members. | ||
+ | </ | ||
+ | <code python> | ||
+ | """ | ||
+ | |||
+ | """ | ||
+ | |||
+ | # import necessary modules | ||
+ | from ctypes import * | ||
+ | from WaveForms_HAL.dwfconstants import * | ||
+ | import sys | ||
+ | if sys.platform.startswith(" | ||
+ | dwf = cdll.dwf | ||
+ | elif sys.platform.startswith(" | ||
+ | dwf = cdll.LoadLibrary("/ | ||
+ | else: | ||
+ | dwf = cdll.LoadLibrary(" | ||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | # global variables | ||
+ | hdwf = c_int() | ||
+ | |||
+ | # function types | ||
+ | |||
+ | |||
+ | class type: | ||
+ | custom = funcCustom | ||
+ | sine = funcSine | ||
+ | square = funcSquare | ||
+ | triangle = funcTriangle | ||
+ | noise = funcNoise | ||
+ | dc = funcDC | ||
+ | pulse = funcPulse | ||
+ | trapezium = funcTrapezium | ||
+ | sine_power = funcSinePower | ||
+ | |||
+ | class ramp: | ||
+ | up = funcRampUp | ||
+ | down = funcRampDown | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def reset(): | ||
+ | # reset the instrument | ||
+ | dwf.FDwfAnalogOutReset(hdwf) | ||
+ | return | ||
+ | |||
+ | |||
+ | """ | ||
+ | """ | ||
+ | """ | ||
+ | |||
+ | |||
+ | def generate(channel, | ||
+ | # enable channel | ||
+ | channel -= 1 | ||
+ | dwf.FDwfAnalogOutNodeEnableSet( | ||
+ | hdwf, channel, AnalogOutNodeCarrier, | ||
+ | # set function type | ||
+ | dwf.FDwfAnalogOutNodeFunctionSet( | ||
+ | hdwf, channel, AnalogOutNodeCarrier, | ||
+ | if function == type.custom: | ||
+ | dwf.FDwfAnalogOutNodeDataSet( | ||
+ | hdwf, channel, AnalogOutNodeCarrier, | ||
+ | # set frequency | ||
+ | dwf.FDwfAnalogOutNodeFrequencySet( | ||
+ | hdwf, channel, AnalogOutNodeCarrier, | ||
+ | # set amplitude | ||
+ | dwf.FDwfAnalogOutNodeAmplitudeSet( | ||
+ | hdwf, channel, AnalogOutNodeCarrier, | ||
+ | # set offset | ||
+ | if function == type.dc: | ||
+ | dwf.FDwfAnalogOutNodeOffsetSet( | ||
+ | hdwf, channel, AnalogOutNodeCarrier, | ||
+ | else: | ||
+ | dwf.FDwfAnalogOutNodeOffsetSet( | ||
+ | hdwf, channel, AnalogOutNodeCarrier, | ||
+ | # set symmetry | ||
+ | dwf.FDwfAnalogOutNodeSymmetrySet( | ||
+ | hdwf, channel, AnalogOutNodeCarrier, | ||
+ | |||
+ | # set running time limit | ||
+ | dwf.FDwfAnalogOutRunSet(hdwf, | ||
+ | # set wait time before start | ||
+ | dwf.FDwfAnalogOutWaitSet(hdwf, | ||
+ | # set number of repeating cycles | ||
+ | dwf.FDwfAnalogOutRepeatSet(hdwf, | ||
+ | |||
+ | # start | ||
+ | dwf.FDwfAnalogOutConfigure(hdwf, | ||
+ | return | ||
+ | |||
+ | """ | ||
+ | |||
+ | </ | ||
+ | </ | ||
+ | <-- | ||
+ | |||
+ | </ | ||
+ | |||
+ | ---- | ||
+ | |||
+ | ===== Creating a Module for the Pmods ===== | ||
+ | <WRAP group> | ||
+ | The Pmod drivers will be created in a similar way to the HAL, but will use it to control the hardware. Download the respective files, then continue at the next section. | ||
+ | |||
+ | As the Pmod BLE communicates via UART, the read and write functions will be implemented in two ways: one method uses the UART instrument to send and receive data, the other uses the logic analyzer and the pattern generator instruments to implement UART communication. This way the device will be able to use the "less smart" method not to interrupt PWM generation for the LEDs. The module can be downloaded {{ : | ||
+ | |||
+ | The Pmod ALS communicates via SPI, but along the read and write functions which use the protocol instrument, alternate functions using the static I/O instrument must be implemented for the same reasons. The module can be downloaded {{ : | ||
+ | </ | ||
+ | |||
+ | <WRAP center round important 60%> | ||
+ | In the attached packages, not all the functions were tested, so errors might appear in some cases. Use the package on your own responsibility and feel free to modify it. | ||
+ | </ | ||
+ | ---- | ||
+ | |||
+ | ==== Testing the Pmods ==== | ||
+ | <WRAP group> | ||
+ | To make sure that the previously created Python modules works (and to provide examples for later usage), they should be tested. | ||
+ | </ | ||
+ | |||
+ | <WRAP group> | ||
+ | --> Testing the Pmod BLE ^# | ||
+ | <WRAP group>< | ||
+ | To be able to test the Pmod, download the [[https:// | ||
+ | |||
+ | {{ : | ||
+ | |||
+ | <WRAP center round important 90%> | ||
+ | Note the long code (MAC address) below the name of the device. It will be important later. | ||
+ | </ | ||
+ | |||
+ | Connect to it, then open **Custom Service** and tap the **N** (notify) icon in the first custom characteristic. You should see system messages appearing in the output stream of the Python script. Also, the "got it" message should appear at the custom characteristic. To send data, tap the **W** icon and type in your message. | ||
+ | |||
+ | {{ : | ||
+ | |||
+ | <WRAP center round important 90%> | ||
+ | Note the long code (UUID) of the service and the characteristic you are using. It will be important later. | ||
+ | </ | ||
+ | |||
+ | </ | ||
+ | """ | ||
+ | |||
+ | # import modules | ||
+ | import Pmod_BLE as ble | ||
+ | import WF_SDK as wf # import WaveForms instruments | ||
+ | |||
+ | # define pins | ||
+ | ble.pins.tx = 4 | ||
+ | ble.pins.rx = 3 | ||
+ | ble.pins.rst = 5 | ||
+ | ble.pins.status = 6 | ||
+ | |||
+ | # turn on messages | ||
+ | ble.settings.DEBUG = True | ||
+ | |||
+ | try: | ||
+ | # initialize the interface | ||
+ | device_data = wf.device.open() | ||
+ | # check for connection errors | ||
+ | wf.device.check_error(device_data) | ||
+ | ble.open() | ||
+ | ble.reset(rx_mode=" | ||
+ | ble.reboot() | ||
+ | |||
+ | while True: | ||
+ | # check connection status | ||
+ | if ble.get_status(): | ||
+ | # receive data | ||
+ | data, sys_msg, error = ble.read(blocking=True, | ||
+ | # display data and system messages | ||
+ | if data != "": | ||
+ | print(" | ||
+ | ble.write_data(" | ||
+ | elif sys_msg != "": | ||
+ | print(" | ||
+ | elif error != "": | ||
+ | print(" | ||
+ | |||
+ | except KeyboardInterrupt: | ||
+ | pass | ||
+ | finally: | ||
+ | # close the device | ||
+ | ble.close(reset=True) | ||
+ | wf.device.close(device_data) | ||
+ | </ | ||
+ | <-- | ||
+ | </ | ||
+ | |||
+ | <WRAP group> | ||
+ | --> Testing the Pmod ALS ^# | ||
+ | <WRAP group>< | ||
+ | Use the flashlight of a phone to illuminate the sensor while the script is running. Use your hand to cover it to test it in dark. | ||
+ | </ | ||
+ | """ | ||
+ | |||
+ | # import modules | ||
+ | import Pmod_ALS as als | ||
+ | import WF_SDK as wf # import WaveForms instruments | ||
+ | from time import sleep | ||
+ | |||
+ | # define pins | ||
+ | als.pins.cs = 8 | ||
+ | als.pins.sdo = 9 | ||
+ | als.pins.sck = 10 | ||
+ | |||
+ | try: | ||
+ | # initialize the interface | ||
+ | device_data = wf.device.open() | ||
+ | # check for connection errors | ||
+ | wf.device.check_error(device_data) | ||
+ | als.open() | ||
+ | |||
+ | while True: | ||
+ | # display measurements | ||
+ | light = als.read_percent(rx_mode=" | ||
+ | print(" | ||
+ | light = als.read_percent(rx_mode=" | ||
+ | print(" | ||
+ | sleep(0.5) | ||
+ | |||
+ | except KeyboardInterrupt: | ||
+ | pass | ||
+ | finally: | ||
+ | # close the device | ||
+ | als.close(reset=True) | ||
+ | wf.device.close(device_data) | ||
+ | </ | ||
+ | <-- | ||
+ | </ | ||
+ | ---- | ||
+ | |||
+ | ===== Building the Application ===== | ||
+ | <WRAP group> | ||
+ | <WRAP group>< | ||
+ | To build an Android application, | ||
+ | |||
+ | --> QR Code for the Application # | ||
+ | <WRAP group>{{ : | ||
+ | <-- | ||
+ | |||
+ | For this application, | ||
+ | |||
+ | When you are ready, find the // | ||
+ | </ | ||
+ | {{ : | ||
+ | </ | ||
+ | |||
+ | <WRAP group>< | ||
+ | When you are finished with the user interface, enter Block view. Here use the puzzle pieces to create the logic backbone of your application. Define what happens when the user touches a slider/ | ||
+ | </ | ||
+ | {{ : | ||
+ | </ | ||
+ | |||
+ | <WRAP group> | ||
+ | --> Tips and Tricks # | ||
+ | <WRAP center round tip 80%> | ||
+ | Use comments on the pieces (small blue circles with a question mark) to make your " | ||
+ | </ | ||
+ | <WRAP center round tip 80%> | ||
+ | In this editor, every separate puzzle piece runs as an interrupt. Use this to your advantage. | ||
+ | </ | ||
+ | <WRAP center round tip 80%> | ||
+ | Hiding a component and invisible components can be useful. You can use an invisible error message, which is made visible only if an error appears, then hide it again using a timer interrupt, to display connection problems - this can be extremely useful during debugging. | ||
+ | </ | ||
+ | <WRAP center round tip 80%> | ||
+ | To easily access the BLE device, service and characteristic you need, use the **MAC address**, **Service UUID** and **Characteristic UUID** obtained previously. If you don't have those, you can find the addresses by following this step: [[test-and-measurement: | ||
+ | </ | ||
+ | <-- | ||
+ | </ | ||
+ | |||
+ | <WRAP group> | ||
+ | When you are ready, build the application and install it on your phone. To install it, you must enable installation from unknown sources. After installing the app, you might encounter warning messages from Google Play Protect, but just ignore them. | ||
+ | </ | ||
+ | </ | ||
+ | ---- | ||
+ | |||
+ | ===== Hardware Setup ===== | ||
+ | <WRAP group> | ||
+ | === Connecting the PMODs === | ||
+ | <WRAP group>< | ||
+ | Connect the Pmod ALS and the Pmod BLE to the digital lines of the Analog Discovery Pro. Note the connections as you will have to define them in the code. | ||
+ | </ | ||
+ | {{ : | ||
+ | </ | ||
+ | |||
+ | === Building the LED Driver === | ||
+ | <WRAP group> | ||
+ | To have a more linear control of brightness, not the voltage falling on a LED, but the current through it must be controlled. As the ADP3450 doesn' | ||
+ | |||
+ | You can use the quad op-amp to implement all three current sinks. | ||
+ | </ | ||
+ | <WRAP group>< | ||
+ | <iframe width=" | ||
+ | </ | ||
+ | |||
+ | === Connecting the Charger Circuit === | ||
+ | <WRAP group>< | ||
+ | Connect the charger circuit to one USB port in the back of the Analog Discovery Pro, then to the battery and the RGB LEDs. | ||
+ | |||
+ | To be able to measure the voltage of the battery cell, connect the first oscilloscope channel to the negative lead of the battery and the second channel to the positive lead. As the scope channels have a common reference, the difference between the two measurements will give the battery voltage. To check if the charger is connected, or not, connect one scope channel to the input of the charger. | ||
+ | </ | ||
+ | {{ : | ||
+ | </ | ||
+ | /* | ||
+ | === Connecting the RGB LED === | ||
+ | <WRAP group>< | ||
+ | Finally, connect the RGB LED (and the current limiting resistors calculated to your LED) between the positive lead of the battery and the remaining three MOSFETs of the Pmod OD1. In this circuit, only RGB LEDs with separate anodes and cathodes, or with a common anode and separate cathodes can be used. | ||
+ | </ | ||
+ | {{ : | ||
+ | </ | ||
+ | </ | ||
+ | ---- | ||
+ | |||
+ | ===== Software Setup ===== | ||
+ | <WRAP group> | ||
+ | In the following, the structure of the main program file will be detailed. You can follow this guide to write your own control program based on the instructions given here, or you can download the script {{ : | ||
+ | |||
+ | <WRAP group> | ||
+ | To be able to use all the previously created modules, you must import them into your script. | ||
+ | |||
+ | To make your code more readable and easier to debug, it is good practice to name your connections (so you don't have to keep in mind which digital, or analog channel is used for what). | ||
+ | |||
+ | Also define every important constant at the beginning of your script. If you initialize these values at the start of your code, it will be easier to modify them during tuning the finished project. | ||
+ | </ | ||
+ | |||
+ | <WRAP group> | ||
+ | --> Importing the Modules, Defining Connections and Constants # | ||
+ | <code python> | ||
+ | """ | ||
+ | |||
+ | # import modules | ||
+ | import Pmod_BLE as ble | ||
+ | import Pmod_ALS as als | ||
+ | import WF_SDK as wf | ||
+ | from time import time | ||
+ | |||
+ | # define connections | ||
+ | # PMOD BLE pins | ||
+ | ble.pins.rx = 3 | ||
+ | ble.pins.tx = 4 | ||
+ | ble.pins.rst = 5 | ||
+ | ble.pins.status = 6 | ||
+ | # PMOD ALS pins | ||
+ | als.pins.cs = 8 | ||
+ | als.pins.sdo = 9 | ||
+ | als.pins.sck = 10 | ||
+ | # scope channels | ||
+ | SC_BAT_P = 1 | ||
+ | SC_BAT_N = 2 | ||
+ | SC_CHARGE = 4 | ||
+ | # LED colors | ||
+ | LED_R = 0 | ||
+ | LED_G = 1 | ||
+ | LED_B = 2 | ||
+ | |||
+ | # other parameters | ||
+ | scope_average = 10 # how many measurements to average with the scope | ||
+ | light_average = 10 # how many measurements to average with the light sensor | ||
+ | led_pwm_frequency = 1e03 # in Hz | ||
+ | out_data_update = 10 # output data update time [s] | ||
+ | ble.settings.DEBUG = True # turn on messages from Pmod BLE | ||
+ | als.settings.DEBUG = True # turn on messages from Pmod ALS | ||
+ | DEBUG = True # turn on messages | ||
+ | |||
+ | # encoding prefixes | ||
+ | pre_red = 0b11 | ||
+ | pre_green = 0b10 | ||
+ | pre_blue = 0b01 | ||
+ | pre_bat = 0b11 | ||
+ | pre_charge = 0b10 | ||
+ | pre_light = 0b01 | ||
+ | |||
+ | class flags: | ||
+ | red = 0 | ||
+ | green = 0 | ||
+ | blue = 0 | ||
+ | last_red = -1 | ||
+ | last_green = -1 | ||
+ | last_blue = -1 | ||
+ | start_time = 0 | ||
+ | </ | ||
+ | <-- | ||
+ | </ | ||
+ | |||
+ | <WRAP group> | ||
+ | Some auxiliary functions might be needed as well, which will be used later in the script. Use these functions to make the script easier to read. | ||
+ | </ | ||
+ | |||
+ | <WRAP group> | ||
+ | --> Auxiliary Functions # | ||
+ | <code python> | ||
+ | def rgb_led(red, | ||
+ | """ | ||
+ | define the LED color in precentage | ||
+ | """ | ||
+ | wf.pattern.generate(device_data, | ||
+ | wf.pattern.generate(device_data, | ||
+ | wf.pattern.generate(device_data, | ||
+ | return | ||
+ | |||
+ | """ | ||
+ | |||
+ | def decode(data): | ||
+ | """ | ||
+ | decode incoming Bluetooth data | ||
+ | """ | ||
+ | # convert to list | ||
+ | for character in list(data): | ||
+ | # convert character to integer | ||
+ | character = ord(character) | ||
+ | # check prefixes and extract content | ||
+ | if character & 0xC0 == pre_red << 6: | ||
+ | flags.red = round((character & 0x3F) / 0x3F * 100) | ||
+ | elif character & 0xC0 == pre_green << 6: | ||
+ | flags.green = round((character & 0x3F) / 0x3F * 100) | ||
+ | elif character & 0xC0 == pre_blue << 6: | ||
+ | flags.blue = round((character & 0x3F) / 0x3F * 100) | ||
+ | else: | ||
+ | pass | ||
+ | return | ||
+ | |||
+ | """ | ||
+ | |||
+ | def encode(data, | ||
+ | """ | ||
+ | encode real numbers between lim_min and lim_max | ||
+ | """ | ||
+ | try: | ||
+ | # clamp between min and max limits | ||
+ | data = max(min(data, | ||
+ | # map between 0b000000 and 0b111111 | ||
+ | data = (data - lim_min) / (lim_max - lim_min) * 0x3F | ||
+ | # append prefix | ||
+ | data = (int(data) & 0x3F) | (prefix << 6) | ||
+ | return data | ||
+ | except: | ||
+ | return 0 | ||
+ | </ | ||
+ | <-- | ||
+ | </ | ||
+ | |||
+ | <WRAP group> | ||
+ | The " | ||
+ | |||
+ | To be able to use the instruments, | ||
+ | </ | ||
+ | |||
+ | <WRAP group> | ||
+ | --> Initialization # | ||
+ | <code python> | ||
+ | try: | ||
+ | # initialize the interface | ||
+ | device_data = wf.device.open() | ||
+ | # check for connection errors | ||
+ | wf.device.check_error(device_data) | ||
+ | if DEBUG: | ||
+ | print(device_data.name + " connected" | ||
+ | # start the power supplies | ||
+ | supplies_data = wf.supplies.data() | ||
+ | supplies_data.master_state = True | ||
+ | supplies_data.state = True | ||
+ | supplies_data.voltage = 3.3 | ||
+ | wf.supplies.switch(device_data, | ||
+ | if DEBUG: | ||
+ | print(" | ||
+ | # initialize the light sensor | ||
+ | als.open() | ||
+ | # initialize the Bluetooth module | ||
+ | ble.open() | ||
+ | ble.reboot() | ||
+ | | ||
+ | # turn off the lamp | ||
+ | rgb_led(0, 0, 0, device_data) | ||
+ | if DEBUG: | ||
+ | print(" | ||
+ | </ | ||
+ | <-- | ||
+ | </ | ||
+ | |||
+ | <WRAP group> | ||
+ | The main part of the script is run in an endless loop, which can be exited only by a keyboard interrupt. The main loop first handles the received data: the script decodes it, then sets the color and brightness of the lamp accordingly. The next section checks if the internal timer signals the end of the update period, reads the light intensity from the Pmod ALS, the battery voltage and the state of the charger (connected/ | ||
+ | </ | ||
+ | |||
+ | <WRAP group> | ||
+ | --> Main Loop: Receiving Data # | ||
+ | <code python> | ||
+ | while True: | ||
+ | if ble.get_status(): | ||
+ | # process the data and set lamp color | ||
+ | data, sys_msg, error = ble.read(blocking=True, | ||
+ | if len(data) > 0: | ||
+ | # decode incoming data | ||
+ | decode(data) | ||
+ | # set the color | ||
+ | if flags.red != flags.last_red: | ||
+ | flags.last_red = flags.red | ||
+ | rgb_led(flags.red, | ||
+ | if DEBUG: | ||
+ | print(" | ||
+ | elif flags.green != flags.last_green: | ||
+ | flags.last_green = flags.green | ||
+ | rgb_led(flags.red, | ||
+ | if DEBUG: | ||
+ | print(" | ||
+ | elif flags.last_blue != flags.blue: | ||
+ | flags.last_blue = flags.blue | ||
+ | rgb_led(flags.red, | ||
+ | if DEBUG: | ||
+ | print(" | ||
+ | </ | ||
+ | <-- | ||
+ | </ | ||
+ | |||
+ | <WRAP group> | ||
+ | --> Main Loop: Sending Data # | ||
+ | <code python> | ||
+ | if ble.get_status(): | ||
+ | # check timing | ||
+ | duration = time() - flags.start_time | ||
+ | if duration >= out_data_update: | ||
+ | # save current time | ||
+ | flags.start_time = time() | ||
+ | # measure the light intensity | ||
+ | light = 0 | ||
+ | for _ in range(light_average): | ||
+ | light += als.read_percent(rx_mode=" | ||
+ | light /= light_average | ||
+ | |||
+ | # encode and send the light intensity | ||
+ | light = encode(light, | ||
+ | ble.write_data(light, | ||
+ | |||
+ | # read battery voltage | ||
+ | batt_n = 0 | ||
+ | batt_p = 0 | ||
+ | for _ in range(scope_average): | ||
+ | batt_n += wf.scope.measure(device_data, | ||
+ | batt_n /= scope_average | ||
+ | for _ in range(scope_average): | ||
+ | batt_p += wf.scope.measure(device_data, | ||
+ | batt_p /= scope_average | ||
+ | battery_voltage = batt_p - batt_n | ||
+ | |||
+ | # encode and send voltage | ||
+ | battery_voltage = encode(battery_voltage, | ||
+ | ble.write_data(battery_voltage, | ||
+ | |||
+ | # read charger state | ||
+ | charger_voltage = 0 | ||
+ | for _ in range(scope_average): | ||
+ | charger_voltage += wf.scope.measure(device_data, | ||
+ | charger_voltage /= scope_average | ||
+ | |||
+ | # encode and send voltage | ||
+ | charger_voltage = encode(charger_voltage, | ||
+ | ble.write_data(charger_voltage, | ||
+ | else: | ||
+ | rgb_led(0, 0, 0, device_data) | ||
+ | </ | ||
+ | <-- | ||
+ | </ | ||
+ | |||
+ | <WRAP group> | ||
+ | When the script is finished, turn off the lamp, then close and reset the used instruments and disconnect from the device. | ||
+ | </ | ||
+ | |||
+ | <WRAP group> | ||
+ | --> Cleanup # | ||
+ | <code python> | ||
+ | except KeyboardInterrupt: | ||
+ | # exit on Ctrl+C | ||
+ | if DEBUG: | ||
+ | print(" | ||
+ | |||
+ | finally: | ||
+ | if DEBUG: | ||
+ | print(" | ||
+ | # turn off the lamp | ||
+ | rgb_led(0, 0, 0, device_data) | ||
+ | # close PMODs | ||
+ | ble.close(True) | ||
+ | als.close(True) | ||
+ | # stop and reset the power supplies | ||
+ | supplies_data = wf.supplies.data() | ||
+ | supplies_data.master_state = False | ||
+ | supplies_data.state = False | ||
+ | supplies_data.voltage = 0 | ||
+ | wf.supplies.switch(device_data, | ||
+ | wf.supplies.close(device_data) | ||
+ | if DEBUG: | ||
+ | print(" | ||
+ | # close device | ||
+ | wf.device.close(device_data) | ||
+ | if DEBUG: | ||
+ | print(" | ||
+ | </ | ||
+ | <-- | ||
+ | </ | ||
+ | </ | ||
+ | ---- | ||
+ | |||
+ | ===== Setting Up the Analog Discovery Pro 3450 (Linux Mode) ===== | ||
+ | <WRAP group> | ||
+ | To be able to run the script without a PC, you will have to boot up the Analog Discovery Pro in Linux Mode. Follow this guide to guide you through the boot process and to connect to the device with a terminal emulator: [[test-and-measurement: | ||
+ | |||
+ | The next step is to connect the ADP3450 to the internet. Follow this guide for the detailed steps: [[test-and-measurement: | ||
+ | |||
+ | Python 3 is already installed on the device, but there are some additional packages, which you will have to install. First install the Python package installer (pip) with the following command (use SSH to send commands to the ADP): | ||
+ | < | ||
+ | |||
+ | Don't forget to install the HAL created for the WaveForms SDK. You can copy the necessary files in the same directory where the project files are, or you can use the following command to install it from GitHub: | ||
+ | < | ||
+ | |||
+ | Run the script on boot, by entering these commands in the terminal: | ||
+ | < | ||
+ | sudo su | ||
+ | cd / | ||
+ | echo -n "" | ||
+ | nano lamp.service | ||
+ | </ | ||
+ | In the text editor, enter this snippet as the file's content (don't forget to change the path to the file): | ||
+ | < | ||
+ | [Unit] | ||
+ | Description=Smart Lamp Controller | ||
+ | |||
+ | [Service] | ||
+ | ExecStart=nohup python3 / | ||
+ | |||
+ | [Install] | ||
+ | WantedBy=multi-user.target | ||
+ | </ | ||
+ | Save the file with Ctrl+O and exit with Ctrl+X. Enable and try the service by: | ||
+ | < | ||
+ | systemctl start lamp | ||
+ | systemctl enable lamp | ||
+ | reboot | ||
+ | </ | ||
+ | </ | ||
+ | ---- | ||
+ | |||
+ | ===== Tuning and Testing ===== | ||
+ | <WRAP group> | ||
+ | Start the script, then start the application on your phone. Wait until the phone discovers the PMOD BLE, then connect to it. Set the lamp color and luminosity on the sliders of the application. | ||
+ | |||
+ | If you want to modify the parameters of the script, stop it with Ctrl+C, modify the parameters, then restart the script. Averaging more measurements leads to a more stable result, with a slower update time. | ||
+ | </ | ||
+ | ---- | ||
+ | |||
+ | ===== Next Steps ===== | ||
+ | <WRAP group> | ||
+ | For more information on WaveForms SDK, see its [[software: | ||
+ | |||
+ | For technical support, please visit the [[https:// | ||
+ | </ | ||