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 [2021/10/18 07:22] – [Building a Battery Powered Smart Lamp with the Analog Discovery Pro (ADP3450/ADP3250)] Á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 10: | Line 10: | ||
===== Planning ===== | ===== Planning ===== | ||
<WRAP group> | <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). The application should also display the ambient light conditions to notify the user to turn off the lamp if it isn't needed (automatic switching may be implemented if needed). 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' | + | 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: | + | 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: |
</ | </ | ||
---- | ---- | ||
Line 23: | Line 23: | ||
* [[pmod: | * [[pmod: | ||
* [[pmod: | * [[pmod: | ||
- | * [[pmod: | ||
- | * [[pmod: | ||
* Lithium-Polymer (LiPo) battery cell | * Lithium-Polymer (LiPo) battery cell | ||
* RGB LED | * RGB LED | ||
- | * USB-A to Micro-USB cable | + | * battery charger circuit |
* [[https:// | * [[https:// | ||
- | * USB connector | ||
- | * LT3092 programmable current source | ||
* OP484 operational amplifier | * OP484 operational amplifier | ||
- | * 5mm LED | + | * 3x 1KΩ resistor |
- | * 470Ω resistor | + | * 3x 180Ω resistor |
- | * 3x 100Ω resistor | + | * 3x 47μF capacitor |
- | * 47Ω resistor | + | * 3x 2N3904 NPN transistor |
- | * 10Ω resistor | + | |
</ | </ | ||
== Software == | == Software == | ||
Line 42: | Line 37: | ||
* [[https:// | * [[https:// | ||
* a web browser | * a web browser | ||
+ | * a terminal emulator | ||
**Note:** //WaveForms can be installed by following the [[software: | **Note:** //WaveForms can be installed by following the [[software: | ||
Line 49: | Line 45: | ||
===== Creating a Hardware Abstraction Layer (HAL) for the Analog Discovery Pro ===== | ===== Creating a Hardware Abstraction Layer (HAL) for the Analog Discovery Pro ===== | ||
<WRAP group> | <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, | + | 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 | ||
</ | </ | ||
- | <WRAP center round important 60%> | + | /*<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. | 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. | ||
</ | </ | ||
Line 1422: | Line 1420: | ||
<-- | <-- | ||
- | </ | + | </ |
---- | ---- | ||
- | ===== Creating a Library | + | ===== Creating a Module |
<WRAP group> | <WRAP group> | ||
- | Now a library should be created to control the Pmods easily. These Pmod drivers will be created in a similar way to the HAL, but will use it to control the hardware. | + | The Pmod drivers will be created in a similar way to the HAL, but will use it to control the hardware. |
+ | |||
+ | 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 | ||
</ | </ | ||
<WRAP center round important 60%> | <WRAP center round important 60%> | ||
- | In the attached | + | In the attached |
- | </ | + | |
- | + | ||
- | + | ||
- | <WRAP group> | + | |
- | Create a new folder and inside create an initializer file and separate files for every Pmod you want to use. | + | |
- | + | ||
- | --> Wrapper: __init__.py # | + | |
- | <WRAP group>< | + | |
- | This module imports all submodules and names them | + | |
- | </ | + | |
- | <code python> | + | |
- | """ | + | |
- | + | ||
- | """ | + | |
- | + | ||
- | from PMOD import PMOD_ALS as ALS | + | |
- | from PMOD import PMOD_BLE as BLE | + | |
- | from PMOD import PMOD_DA1 as DA1 | + | |
- | from PMOD import PMOD_KYPD as KYPD | + | |
- | from PMOD import PMOD_OD1 as OD1 | + | |
- | + | ||
- | </ | + | |
- | </ | + | |
- | <-- | + | |
- | + | ||
- | --> Pmod ALS: PMOD_ALS.py # | + | |
- | <WRAP group>< | + | |
- | The ambient light sensor uses the SPI interface for communication, | + | |
- | </ | + | |
- | <code python> | + | |
- | """ | + | |
- | + | ||
- | """ | + | |
- | """ | + | |
- | """ | + | |
- | + | ||
- | + | ||
- | class pins: | + | |
- | cs = 0 # CS pin of the Pmod | + | |
- | sdo = 1 # SDO pin of the Pmod | + | |
- | sck = 2 # SCK pin of the Pmod | + | |
- | + | ||
- | + | ||
- | # WaveForms SDK HAL object | + | |
- | WF = None | + | |
- | + | ||
- | + | ||
- | """ | + | |
- | """ | + | |
- | """ | + | |
- | + | ||
- | + | ||
- | def initialize(WaveForms_HAL): | + | |
- | global WF | + | |
- | WF = WaveForms_HAL | + | |
- | + | ||
- | # start the power supply | + | |
- | WF.supply.switch(True) | + | |
- | + | ||
- | # initialize the SPI interface | + | |
- | WF.spi.initialize(cs_pin=pins.cs, | + | |
- | miso_pin=pins.sdo, | + | |
- | return | + | |
- | + | ||
- | + | ||
- | """ | + | |
- | """ | + | |
- | """ | + | |
- | + | ||
- | + | ||
- | def close(): | + | |
- | pass | + | |
- | return | + | |
- | + | ||
- | + | ||
- | """ | + | |
- | """ | + | |
- | """ | + | |
- | + | ||
- | + | ||
- | def receive_data(): | + | |
- | data = WF.spi.receive(2) | + | |
- | msb = ord(data[0]) & 0xFF | + | |
- | lsb = ord(data[1]) & 0xFF | + | |
- | # concatenate bytes without trailing and leading zeros | + | |
- | result = ((msb << 3) | (lsb >> 4)) & 0xFF | + | |
- | return result | + | |
- | + | ||
- | + | ||
- | """ | + | |
- | """ | + | |
- | """ | + | |
- | + | ||
- | + | ||
- | def receive_percent(): | + | |
- | # receive and convert raw data | + | |
- | data = receive_data() * 100 / 127 | + | |
- | return round(data, 2) | + | |
- | + | ||
- | + | ||
- | """ | + | |
- | + | ||
- | </ | + | |
- | </ | + | |
- | <-- | + | |
- | + | ||
- | --> Pmod BLE: PMOD_BLE.py # | + | |
- | <WRAP group>< | + | |
- | The Pmod BLE is a Bluetooth Low Energy module, which communicates on UART interface, using a 115200bps baud rate. The controlling module contains functions to factory reset the device by putting it into command mode and sending the necessary commands. Besides that, it also can send and receive data, decoding the received bytes and separating the buffer into a list which contains only data and one which contains only system messages (starting and ending with " | + | |
- | </ | + | |
- | <code python> | + | |
- | """ | + | |
- | + | ||
- | import time | + | |
- | + | ||
- | """ | + | |
- | """ | + | |
- | """ | + | |
- | + | ||
- | + | ||
- | class pins: | + | |
- | rx = 0 # TX pin of the Pmod | + | |
- | tx = 1 # RX pin of the Pmod | + | |
- | rst = 2 # RESET pin of the Pmod | + | |
- | + | ||
- | + | ||
- | class commands: | + | |
- | command_mode = " | + | |
- | data_mode = " | + | |
- | rename = " | + | |
- | factory_reset = " | + | |
- | high_power = " | + | |
- | code = " | + | |
- | mode = " | + | |
- | reboot = " | + | |
- | + | ||
- | + | ||
- | # WaveForms SDK HAL object | + | |
- | WF = None | + | |
- | + | ||
- | # variables for detecting system messages | + | |
- | currently_sys = False | + | |
- | previous_msg = "" | + | |
- | + | ||
- | + | ||
- | """ | + | |
- | """ | + | |
- | """ | + | |
- | + | ||
- | + | ||
- | def initialize(WaveForms_HAL, | + | |
- | global WF | + | |
- | WF = WaveForms_HAL | + | |
- | + | ||
- | # start the power supply | + | |
- | WF.supply.switch(True) | + | |
- | + | ||
- | # initialize the UART interface | + | |
- | WF.uart.initialize(rx_pin=pins.tx, | + | |
- | + | ||
- | # factory reset the Pmod | + | |
- | if first_run: | + | |
- | reset() | + | |
- | return | + | |
- | + | ||
- | + | ||
- | """ | + | |
- | """ | + | |
- | """ | + | |
- | + | ||
- | + | ||
- | def reboot(): | + | |
- | # hard reset the device | + | |
- | # pull down the reset line | + | |
- | WF.digital.write(pins.rst, | + | |
- | # wait | + | |
- | time.sleep(1) | + | |
- | # pull up the reset line | + | |
- | WF.digital.write(pins.rst, | + | |
- | return | + | |
- | + | ||
- | + | ||
- | """ | + | |
- | """ | + | |
- | """ | + | |
- | + | ||
- | + | ||
- | def reset(): | + | |
- | # enter command mode | + | |
- | send_command(commands.command_mode) | + | |
- | time.sleep(3) | + | |
- | + | ||
- | # factory reset the Pmod | + | |
- | success = send_command(commands.factory_reset) | + | |
- | while success != True: | + | |
- | success = send_command(commands.factory_reset) | + | |
- | time.sleep(1) | + | |
- | + | ||
- | # enter command mode | + | |
- | send_command(commands.command_mode) | + | |
- | time.sleep(3) | + | |
- | + | ||
- | # rename device | + | |
- | send_command(commands.rename) | + | |
- | time.sleep(1) | + | |
- | + | ||
- | # set high power mode | + | |
- | send_command(commands.high_power) | + | |
- | time.sleep(1) | + | |
- | + | ||
- | # set communication mode | + | |
- | send_command(commands.mode) | + | |
- | time.sleep(1) | + | |
- | + | ||
- | # exit command mode | + | |
- | send_command(commands.data_mode) | + | |
- | time.sleep(3) | + | |
- | return | + | |
- | + | ||
- | + | ||
- | """ | + | |
- | """ | + | |
- | """ | + | |
- | + | ||
- | + | ||
- | def close(): | + | |
- | # restart the module | + | |
- | reboot() | + | |
- | return | + | |
- | + | ||
- | + | ||
- | """ | + | |
- | """ | + | |
- | """ | + | |
- | + | ||
- | + | ||
- | def send_command(command): | + | |
- | # send the command | + | |
- | send_data(command) | + | |
- | # record response | + | |
- | response, _, error = receive_data | + | |
- | # analyze response | + | |
- | response = response[0: | + | |
- | if response == " | + | |
- | return False | + | |
- | return True | + | |
- | + | ||
- | + | ||
- | """ | + | |
- | """ | + | |
- | """ | + | |
- | + | ||
- | + | ||
- | def send_data(data): | + | |
- | # send data | + | |
- | WF.uart.send(data) | + | |
- | return | + | |
- | + | ||
- | + | ||
- | """ | + | |
- | """ | + | |
- | """ | + | |
- | + | ||
- | + | ||
- | def receive_data(): | + | |
- | global previous_msg, | + | |
- | + | ||
- | # record incoming message | + | |
- | data, error = WF.uart.receive() | + | |
- | sys_msg = "" | + | |
- | + | ||
- | # check for system messages | + | |
- | special = data.count(" | + | |
- | + | ||
- | """ | + | |
- | + | ||
- | if special == 0: | + | |
- | if currently_sys: | + | |
- | # the middle of a system message | + | |
- | previous_msg = previous_msg + data | + | |
- | data = "" | + | |
- | else: | + | |
- | # not a system message | + | |
- | pass | + | |
- | + | ||
- | """ | + | |
- | + | ||
- | elif special == 2: | + | |
- | # clear system message | + | |
- | sys_msg = data | + | |
- | data = "" | + | |
- | currently_sys = False | + | |
- | previous_msg = "" | + | |
- | + | ||
- | """ | + | |
- | + | ||
- | else: | + | |
- | # fragmented system message | + | |
- | data_list = data.split(" | + | |
- | + | ||
- | if currently_sys: | + | |
- | # the end of the message | + | |
- | sys_msg = previous_msg + data_list[0] + " | + | |
- | currently_sys = False | + | |
- | previous_msg = "" | + | |
- | data = data_list[1] | + | |
- | + | ||
- | else: | + | |
- | # the start of the message | + | |
- | currently_sys = True | + | |
- | previous_msg = " | + | |
- | data = data_list[0] | + | |
- | + | ||
- | return data, sys_msg, error | + | |
- | + | ||
- | + | ||
- | """ | + | |
- | + | ||
- | </ | + | |
- | </ | + | |
- | <-- | + | |
- | + | ||
- | --> Pmod DA1: PMOD_DA1.py # | + | |
- | <WRAP group>< | + | |
- | The digital to analog converter also communicates via the SPI interface. The Pmod DA1 includes two DAC chips that have common chip select and serial clock pins. Both chips contain two 8-bit DAC channels, so in total, the Pmod features four different conversion channels. The program computes the 8-bit data word from the voltage, then appends it to the command word specific to the DAC channel. To use chip 1, or chip 2, the command and data words must be sent on the data 0, or data 1 lines. | + | |
- | </ | + | |
- | <code python> | + | |
- | """ | + | |
- | + | ||
- | """ | + | |
- | """ | + | |
- | """ | + | |
- | + | ||
- | + | ||
- | class pins: | + | |
- | sync = 0 # CS pin of the Pmod | + | |
- | d0 = 1 # data 0 pin of the Pmod | + | |
- | d1 = 2 # data 0 pin of the Pmod | + | |
- | sck = 3 # SCK pin of the Pmod | + | |
- | + | ||
- | + | ||
- | class output_channel: | + | |
- | # channels of the digital to analog converter | + | |
- | A1 = 0 | + | |
- | B1 = 1 | + | |
- | A2 = 2 | + | |
- | B2 = 3 | + | |
- | + | ||
- | + | ||
- | # voltage limits in Volts | + | |
- | max_voltage = 3.3 | + | |
- | min_voltage = 0 | + | |
- | + | ||
- | # WaveForms SDK HAL object | + | |
- | WF = None | + | |
- | + | ||
- | + | ||
- | """ | + | |
- | """ | + | |
- | """ | + | |
- | + | ||
- | + | ||
- | def initialize(WaveForms_HAL): | + | |
- | global WF | + | |
- | WF = WaveForms_HAL | + | |
- | + | ||
- | # start the power supply | + | |
- | WF.supply.switch(True) | + | |
- | + | ||
- | return | + | |
- | + | ||
- | + | ||
- | """ | + | |
- | """ | + | |
- | """ | + | |
- | + | ||
- | + | ||
- | def close(): | + | |
- | pass | + | |
- | return | + | |
- | + | ||
- | + | ||
- | """ | + | |
- | """ | + | |
- | """ | + | |
- | + | ||
- | + | ||
- | def output_voltage(channel, | + | |
- | # select DAC chip | + | |
- | mosi = pins.d0 | + | |
- | if channel == output_channel.A2 or channel == output_channel.B2: | + | |
- | mosi = pins.d1 | + | |
- | + | ||
- | # initialize the SPI interface | + | |
- | WF.spi.initialize(cs_pin=pins.sync, | + | |
- | mosi_pin=mosi, | + | |
- | + | ||
- | # limit and encode voltage | + | |
- | voltage = max(min(voltage, | + | |
- | data = round(voltage * 255 / 3.3) & 0xFF | + | |
- | + | ||
- | # select channel | + | |
- | command = 3 | + | |
- | if channel == output_channel.B1 or channel == output_channel.B2: | + | |
- | command = 7 | + | |
- | + | ||
- | # send both words | + | |
- | WF.spi.send([command, | + | |
- | return | + | |
- | + | ||
- | + | ||
- | """ | + | |
- | + | ||
- | </ | + | |
- | </ | + | |
- | <-- | + | |
- | + | ||
- | --> Pmod KYPD: PMOD_KYPD.py # | + | |
- | <WRAP group>< | + | |
- | This module uses the static I/O instrument to read the current state of the keypad and returns the pressed keys. | + | |
- | </ | + | |
- | <code python> | + | |
- | """ | + | |
- | + | ||
- | """ | + | |
- | """ | + | |
- | """ | + | |
- | + | ||
- | + | ||
- | class pins: | + | |
- | rows = [7, 6, 5, 4] # pins connected to keypad rows | + | |
- | columns = [3, 2, 1, 0] # pins connected to keypad columns | + | |
- | + | ||
- | + | ||
- | # the layout of the keypad | + | |
- | keymap = [[" | + | |
- | [" | + | |
- | [" | + | |
- | [" | + | |
- | + | ||
- | + | ||
- | # WaveForms SDK HAL object | + | |
- | WF = None | + | |
- | + | ||
- | + | ||
- | """ | + | |
- | """ | + | |
- | """ | + | |
- | + | ||
- | + | ||
- | def initialize(WaveForms_HAL): | + | |
- | global WF | + | |
- | WF = WaveForms_HAL | + | |
- | + | ||
- | # enable the power supply | + | |
- | WF.supply.switch(True) | + | |
- | + | ||
- | # for every pin connected to a row | + | |
- | for pin in pins.rows: | + | |
- | WF.digital.set_output(pin) | + | |
- | WF.digital.write(pin, | + | |
- | return | + | |
- | + | ||
- | + | ||
- | """ | + | |
- | """ | + | |
- | """ | + | |
- | + | ||
- | + | ||
- | def close(): | + | |
- | pass | + | |
- | return | + | |
- | + | ||
- | + | ||
- | """ | + | |
- | """ | + | |
- | """ | + | |
- | + | ||
- | + | ||
- | def receive_data(): | + | |
- | # get the current state of the Pmod KYPD | + | |
- | result = [] # buffer to store the results | + | |
- | + | ||
- | # go through all the rows | + | |
- | for index_1 in range(len(pins.rows)): | + | |
- | # set the state of every row | + | |
- | for index_2 in range(len(pins.rows)): | + | |
- | # only one row is HIGH at every moment | + | |
- | if index_1 == index_2: | + | |
- | # set the current row HIGH | + | |
- | WF.digital.write(pins.rows[index_2], | + | |
- | else: | + | |
- | # set the current row LOW | + | |
- | WF.digital.write(pins.rows[index_2], | + | |
- | + | ||
- | # check every column | + | |
- | for index_2 in range(len(pins.columns)): | + | |
- | # if the current column is HIGH | + | |
- | if WF.digital.read(pins.columns[index_2]): | + | |
- | # append the respective key to the results | + | |
- | result.append(keymap[index_1][index_2]) | + | |
- | + | ||
- | # return the results | + | |
- | return result | + | |
- | + | ||
- | + | ||
- | """ | + | |
- | + | ||
- | </ | + | |
- | </ | + | |
- | <-- | + | |
- | + | ||
- | --> Pmod OD1: PMOD_OD1.py # | + | |
- | <WRAP group>< | + | |
- | The Pmod OD1 contains four open drain MOSFETs, which act like switches: if a gate pin is set HIGH (True), the respective MOSFET grounds the connected line. If the gate pin is set to LOW (False), the MOSFET input (the drain) has high impedance. The module realizes functions for turning the switches on/off, toggling and driving them with a pulse width modulated (PWM) signal. When driving the MOSFETs with the PWM signal, the type of signal generation (static, or pattern generator) can be defined as a parameter. | + | |
- | </ | + | |
- | <code python> | + | |
- | """ | + | |
- | + | ||
- | """ | + | |
- | """ | + | |
- | """ | + | |
- | + | ||
- | + | ||
- | class pins: | + | |
- | g1 = 0 # G1 pin of the Pmod | + | |
- | g2 = 1 # G2 pin of the Pmod | + | |
- | g3 = 2 # G3 pin of the Pmod | + | |
- | g4 = 3 # G4 pin of the Pmod | + | |
- | + | ||
- | + | ||
- | # WaveForms SDK HAL object | + | |
- | WF = None | + | |
- | + | ||
- | + | ||
- | """ | + | |
- | """ | + | |
- | """ | + | |
- | + | ||
- | + | ||
- | def initialize(WaveForms_HAL): | + | |
- | global WF | + | |
- | WF = WaveForms_HAL | + | |
- | + | ||
- | # start the power supply | + | |
- | WF.supply.switch(True) | + | |
- | return | + | |
- | + | ||
- | + | ||
- | """ | + | |
- | """ | + | |
- | """ | + | |
- | + | ||
- | + | ||
- | def close(): | + | |
- | WF.static_pattern.reset() | + | |
- | return | + | |
- | + | ||
- | + | ||
- | """ | + | |
- | """ | + | |
- | """ | + | |
- | + | ||
- | + | ||
- | def on(channel): | + | |
- | pin = None | + | |
- | if channel == 1: | + | |
- | pin = pins.g1 | + | |
- | elif channel == 2: | + | |
- | pin = pins.g2 | + | |
- | elif channel == 3: | + | |
- | pin = pins.g3 | + | |
- | elif channel == 4: | + | |
- | pin = pins.g4 | + | |
- | + | ||
- | if pin != None: | + | |
- | # initialize the pins | + | |
- | WF.static_pattern.stop(pin) | + | |
- | WF.digital.set_output(pin) | + | |
- | # switch on MOSFET | + | |
- | WF.digital.write(pin, | + | |
- | return | + | |
- | + | ||
- | + | ||
- | """ | + | |
- | """ | + | |
- | """ | + | |
- | + | ||
- | + | ||
- | def off(channel): | + | |
- | pin = None | + | |
- | if channel == 1: | + | |
- | pin = pins.g1 | + | |
- | elif channel == 2: | + | |
- | pin = pins.g2 | + | |
- | elif channel == 3: | + | |
- | pin = pins.g3 | + | |
- | elif channel == 4: | + | |
- | pin = pins.g4 | + | |
- | + | ||
- | if pin != None: | + | |
- | # initialize the pins | + | |
- | WF.static_pattern.stop(pin) | + | |
- | WF.digital.set_output(pin) | + | |
- | # switch off MOSFET 4 | + | |
- | WF.digital.write(pin, | + | |
- | return | + | |
- | + | ||
- | + | ||
- | """ | + | |
- | """ | + | |
- | """ | + | |
- | + | ||
- | + | ||
- | def switch(channel): | + | |
- | pin = None | + | |
- | if channel == 1: | + | |
- | pin = pins.g1 | + | |
- | elif channel == 2: | + | |
- | pin = pins.g2 | + | |
- | elif channel == 3: | + | |
- | pin = pins.g3 | + | |
- | elif channel == 4: | + | |
- | pin = pins.g4 | + | |
- | + | ||
- | if pin != None: | + | |
- | # initialize the pins | + | |
- | WF.static_pattern.stop(pin) | + | |
- | WF.digital.set_output(pin) | + | |
- | # switch off MOSFET 4 | + | |
- | state = WF.digital.read(pin) | + | |
- | WF.digital.write(pin, | + | |
- | return | + | |
- | + | ||
- | + | ||
- | """ | + | |
- | """ | + | |
- | """ | + | |
- | + | ||
- | + | ||
- | def drive(channel, | + | |
- | # enable/ | + | |
- | instrument = WF.pattern | + | |
- | if static: | + | |
- | instrument = WF.static_pattern | + | |
- | + | ||
- | if channel == 1: | + | |
- | # drive MOSFET 1 | + | |
- | instrument.generate(pins.g1, | + | |
- | instrument.type.pulse, | + | |
- | elif channel == 2: | + | |
- | # drive MOSFET 2 | + | |
- | instrument.generate(pins.g2, | + | |
- | instrument.type.pulse, | + | |
- | elif channel == 3: | + | |
- | # drive MOSFET 3 | + | |
- | instrument.generate(pins.g3, | + | |
- | instrument.type.pulse, | + | |
- | elif channel == 4: | + | |
- | # drive MOSFET 4 | + | |
- | instrument.generate(pins.g4, | + | |
- | instrument.type.pulse, | + | |
- | return | + | |
- | + | ||
- | + | ||
- | """ | + | |
- | + | ||
- | </ | + | |
- | </ | + | |
- | <-- | + | |
</ | </ | ||
---- | ---- | ||
- | ===== Testing the Modules ===== | + | ==== Testing the Pmods ==== |
<WRAP group> | <WRAP group> | ||
- | To make sure that the previously created Python modules | + | To make sure that the previously created Python modules |
</ | </ | ||
<WRAP group> | <WRAP group> | ||
- | Create test files for all Pmods and the Analog and Digital input-output instruments in the same folder, where the // | + | --> Testing the Pmod BLE ^# |
- | + | ||
- | --> Testing the Oscilloscope and the Waveform Generator: Test_HAL_Analog.py | + | |
<WRAP group>< | <WRAP group>< | ||
- | To test the functionality of the analog input-output modules, first connect an oscilloscope channel to one of the waveform generator' | ||
- | |||
- | {{ : | ||
- | </ | ||
- | <code python> | ||
- | # import modules | ||
- | import WaveForms_HAL as WF | ||
- | import matplotlib.pyplot as plt | ||
- | |||
- | # define used channels | ||
- | scope_ch = 1 | ||
- | wavegen_ch = 1 | ||
- | |||
- | # define signal parameters | ||
- | dc_voltage = 2.25 | ||
- | sin_amplitude = 2.0 | ||
- | sin_frequency = 50e03 | ||
- | |||
- | try: | ||
- | # initialize the interface | ||
- | WF.initialize() | ||
- | |||
- | # initialize the scope with default settings | ||
- | WF.scope.initialize() | ||
- | |||
- | # output a DC voltage | ||
- | WF.wavegen.generate(wavegen_ch, | ||
- | # display measured voltage level | ||
- | print(WF.scope.measure(scope_ch)) | ||
- | |||
- | # output a sine signal | ||
- | WF.wavegen.generate(wavegen_ch, | ||
- | amplitude=sin_amplitude, | ||
- | |||
- | while True: | ||
- | plt.plot(WF.scope.receive(scope_ch)) | ||
- | plt.show() | ||
- | |||
- | except KeyboardInterrupt: | ||
- | pass | ||
- | finally: | ||
- | # close device | ||
- | WF.close() | ||
- | |||
- | </ | ||
- | </ | ||
- | <-- | ||
- | |||
- | --> Testing the Logic Analyzer and the Pattern Generator: Test_HAL_Digital.py # | ||
- | <WRAP group>< | ||
- | To test the functionality of the digital input-output modules, a PWM signal is generated on one of the digital input-output channels. The signal is read back on the same pin, then plotted using the matplotlib module. | ||
- | |||
- | {{ : | ||
- | </ | ||
- | <code python> | ||
- | # import modules | ||
- | import WaveForms_HAL as WF | ||
- | import matplotlib.pyplot as plt | ||
- | |||
- | # define used digital pin | ||
- | pin = 1 | ||
- | |||
- | # define signal parameters | ||
- | frequency = 50e03 | ||
- | duty_cycle = 30 | ||
- | |||
- | try: | ||
- | # initialize the interface | ||
- | WF.initialize() | ||
- | |||
- | # initialize the logic analyzer with default settings | ||
- | WF.logic.initialize() | ||
- | |||
- | # generate a signal | ||
- | WF.pattern.generate( | ||
- | pin, frequency, WF.pattern.type.pulse, | ||
- | |||
- | while True: | ||
- | plt.plot(WF.logic.receive_data(pin)) | ||
- | plt.show() | ||
- | |||
- | except KeyboardInterrupt: | ||
- | pass | ||
- | finally: | ||
- | # close the device | ||
- | WF.close() | ||
- | |||
- | </ | ||
- | </ | ||
- | <-- | ||
- | |||
- | --> Testing the Pmod ALS: Test_PMOD_ALS.py # | ||
- | <WRAP group>< | ||
- | To test the Pmod ALS, the ambient light intensity is read and displayed continuously. | ||
- | </ | ||
- | <code python> | ||
- | # import modules | ||
- | from PMOD import ALS | ||
- | import WaveForms_HAL as WF | ||
- | |||
- | # define pins | ||
- | ALS.pins.cs = 10 | ||
- | ALS.pins.sdo = 9 | ||
- | ALS.pins.sck = 8 | ||
- | |||
- | try: | ||
- | # initialize the interface | ||
- | WF.initialize() | ||
- | ALS.initialize(WF) | ||
- | |||
- | while True: | ||
- | # display measurements | ||
- | print(ALS.receive_percent()) | ||
- | |||
- | except KeyboardInterrupt: | ||
- | pass | ||
- | finally: | ||
- | # close the device | ||
- | ALS.close() | ||
- | WF.close() | ||
- | |||
- | </ | ||
- | </ | ||
- | <-- | ||
- | |||
- | --> Testing the Pmod BLE: Test_PMOD_BLE.py # | ||
- | <WRAP group>< | ||
- | To test the Pmod BLE, all messages received on Bluetooth are displayed, the string "got it" is sent as a response. | ||
- | |||
To be able to test the Pmod, download the [[https:// | To be able to test the Pmod, download the [[https:// | ||
Line 2262: | Line 1462: | ||
</ | </ | ||
- | </ | + | </ |
- | <code python> | + | """ |
+ | |||
# import modules | # import modules | ||
- | from PMOD import | + | import |
- | import | + | import |
+ | |||
# define pins | # define pins | ||
- | BLE.pins.tx = 6 | + | ble.pins.tx = 4 |
- | BLE.pins.rx = 5 | + | ble.pins.rx = 3 |
- | BLE.pins.rst = 4 | + | ble.pins.rst = 5 |
+ | ble.pins.status = 6 | ||
+ | # turn on messages | ||
+ | ble.settings.DEBUG = True | ||
+ | |||
try: | try: | ||
# initialize the interface | # initialize the interface | ||
- | | + | |
- | | + | # check for connection errors |
- | | + | wf.device.check_error(device_data) |
+ | ble.open() | ||
+ | | ||
+ | | ||
+ | |||
while True: | while True: | ||
- | # receive data | + | |
- | data, sys_msg, error = BLE.receive_data() | + | if ble.get_status(): |
- | + | | |
- | | + | data, sys_msg, error = ble.read(blocking=True, |
- | | + | # display |
- | if len(data) > 0: | + | if data != "" |
- | buf = data | + | print("data: " + data) # display it |
- | elif len(sys_msg) > 0: | + | ble.write_data("ok", tx_mode=" |
- | buf = sys_msg | + | elif sys_msg != "": |
- | + | | |
- | # if the data is valid | + | elif error != "": |
- | if len(buf) > 0: | + | print(" |
- | print(buf) # display it | + | |
- | BLE.send_data(" | + | |
- | + | ||
- | # if the data isn't valid | + | |
- | elif error != "no error": | + | |
- | print(error) | + | |
except KeyboardInterrupt: | except KeyboardInterrupt: | ||
pass | pass | ||
finally: | finally: | ||
# close the device | # close the device | ||
- | | + | |
- | BLE.close() | + | |
- | | + | </ |
- | + | ||
- | </ | + | |
- | </ | + | |
<-- | <-- | ||
+ | </ | ||
- | --> Testing the Pmod DA1: Test_PMOD_DA1.py | + | <WRAP group> |
+ | --> Testing the Pmod ALS ^# | ||
<WRAP group>< | <WRAP group>< | ||
- | Connect | + | Use the flashlight |
- | </ | + | </ |
- | <code python> | + | """ |
- | # import modules | + | |
- | from PMOD import DA1 | + | |
- | import WaveForms_HAL as WF | + | |
- | # define used pins | ||
- | DA1.pins.sync = 3 | ||
- | DA1.pins.d0 = 2 | ||
- | DA1.pins.d1 = 1 | ||
- | DA1.pins.sck = 0 | ||
- | |||
- | try: | ||
- | # initialize the interface | ||
- | WF.initialize() | ||
- | DA1.initialize(WF) | ||
- | |||
- | # initialize the scope with default settings | ||
- | WF.scope.initialize() | ||
- | |||
- | while True: | ||
- | # read voltage from keyboard | ||
- | voltage = input(" | ||
- | # check exit condition | ||
- | if voltage == " | ||
- | break | ||
- | # set voltage | ||
- | DA1.output_voltage(DA1.output_channel.A1, | ||
- | # display measured voltage levels | ||
- | print([WF.scope.measure(1), | ||
- | |||
- | # read voltage from keyboard | ||
- | voltage = input(" | ||
- | # check exit condition | ||
- | if voltage == " | ||
- | break | ||
- | # set voltage | ||
- | DA1.output_voltage(DA1.output_channel.B1, | ||
- | # display measured voltage levels | ||
- | print([WF.scope.measure(1), | ||
- | |||
- | # read voltage from keyboard | ||
- | voltage = input(" | ||
- | # check exit condition | ||
- | if voltage == " | ||
- | break | ||
- | # set voltage | ||
- | DA1.output_voltage(DA1.output_channel.A2, | ||
- | # display measured voltage levels | ||
- | print([WF.scope.measure(1), | ||
- | |||
- | # read voltage from keyboard | ||
- | voltage = input(" | ||
- | # check exit condition | ||
- | if voltage == " | ||
- | break | ||
- | # set voltage | ||
- | DA1.output_voltage(DA1.output_channel.B2, | ||
- | # display measured voltage levels | ||
- | print([WF.scope.measure(1), | ||
- | |||
- | except KeyboardInterrupt: | ||
- | pass | ||
- | finally: | ||
- | # close the device | ||
- | DA1.close() | ||
- | WF.close() | ||
- | |||
- | </ | ||
- | </ | ||
- | <-- | ||
- | |||
- | --> Testing the Pmod KYPD: Test_PMOD_KYPD.py # | ||
- | <WRAP group>< | ||
- | To test the keypad, read and display the pressed keys continuously. | ||
- | </ | ||
- | <code python> | ||
# import modules | # import modules | ||
- | from PMOD import | + | import |
- | import | + | import |
+ | from time import sleep | ||
# define pins | # define pins | ||
- | KYPD.pins.rows = [7, 6, 5, 4] | + | als.pins.cs = 8 |
- | KYPD.pins.columns | + | als.pins.sdo = 9 |
+ | als.pins.sck = 10 | ||
try: | try: | ||
# initialize the interface | # initialize the interface | ||
- | | + | |
- | | + | # check for connection errors |
+ | wf.device.check_error(device_data) | ||
+ | | ||
while True: | while True: | ||
- | # display | + | # display |
- | print(KYPD.receive_data()) | + | light = als.read_percent(rx_mode=" |
+ | print(" | ||
+ | light = als.read_percent(rx_mode=" | ||
+ | print(" | ||
+ | sleep(0.5) | ||
except KeyboardInterrupt: | except KeyboardInterrupt: | ||
Line 2412: | Line 1547: | ||
finally: | finally: | ||
# close the device | # close the device | ||
- | | + | |
- | | + | |
- | + | </ | |
- | </ | + | |
- | </ | + | |
<-- | <-- | ||
- | |||
- | --> Testing the Pmod OD1: Test_PMOD_OD1.py # | ||
- | <WRAP group>< | ||
- | To test the open drain MOSFETs, connect the cathode (shorter lead) of some LEDs to the MOSFET drains. Connect one and of 470Ω resistors to the anodes (longer lead) of the LEDs. Connect the other lead of the resistors to the power supply output (red cable) on ADP. | ||
- | |||
- | The script turns on and off every LED one after the other, then repeats this step, but with the toggling function. When finished, enter duty cycles for every PWM signal commanding the MOSFETs to set the brightness of the LEDs. Finish the script by entering " | ||
- | </ | ||
- | <code python> | ||
- | # import modules | ||
- | from PMOD import OD1 | ||
- | import WaveForms_HAL as WF | ||
- | import time | ||
- | |||
- | # define used pins | ||
- | OD1.pins.g1 = 3 | ||
- | OD1.pins.g2 = 2 | ||
- | OD1.pins.g3 = 1 | ||
- | OD1.pins.g4 = 0 | ||
- | |||
- | # define signal frequency | ||
- | frequency = 1 | ||
- | |||
- | # define PWM type | ||
- | static_pwm = True | ||
- | |||
- | # require static testing | ||
- | static_required = False | ||
- | |||
- | # define delay after operations | ||
- | delay = 1 # in seconds | ||
- | |||
- | try: | ||
- | # initialize the interface | ||
- | WF.initialize() | ||
- | OD1.initialize(WF) | ||
- | |||
- | if static_required: | ||
- | # try the on/off functions | ||
- | for index in range(1, 5): | ||
- | OD1.on(index) | ||
- | print(" | ||
- | time.sleep(delay) | ||
- | OD1.off(index) | ||
- | print(" | ||
- | time.sleep(delay) | ||
- | |||
- | # try the toggling function | ||
- | for index in range(1, 5): | ||
- | OD1.switch(index) | ||
- | print(" | ||
- | time.sleep(delay) | ||
- | OD1.switch(index) | ||
- | print(" | ||
- | time.sleep(delay) | ||
- | |||
- | while True: | ||
- | # read duty cycles from keyboard | ||
- | duty = input(" | ||
- | # check exit condition | ||
- | if duty == " | ||
- | break | ||
- | # test the pwm drive functions | ||
- | OD1.drive(1, | ||
- | |||
- | duty = input(" | ||
- | if duty == " | ||
- | break | ||
- | OD1.drive(2, | ||
- | |||
- | duty = input(" | ||
- | if duty == " | ||
- | break | ||
- | OD1.drive(3, | ||
- | |||
- | duty = input(" | ||
- | if duty == " | ||
- | break | ||
- | OD1.drive(4, | ||
- | |||
- | except KeyboardInterrupt: | ||
- | pass | ||
- | finally: | ||
- | # turn off everything | ||
- | OD1.off(1) | ||
- | OD1.off(2) | ||
- | OD1.off(3) | ||
- | OD1.off(4) | ||
- | |||
- | # close the device | ||
- | OD1.close() | ||
- | WF.close() | ||
- | |||
- | </ | ||
- | </ | ||
- | <-- | ||
- | |||
</ | </ | ||
---- | ---- | ||
Line 2520: | Line 1557: | ||
<WRAP group> | <WRAP group> | ||
<WRAP group>< | <WRAP group>< | ||
- | To build an Android application, | + | To build an Android application, |
+ | |||
+ | --> QR Code for the Application # | ||
+ | <WRAP group>{{ : | ||
+ | <-- | ||
For this application, | For this application, | ||
Line 2531: | Line 1572: | ||
<WRAP group>< | <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/ | 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 center round tip> | + | </ |
+ | {{ : | ||
+ | </ | ||
+ | |||
+ | <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 " | Use comments on the pieces (small blue circles with a question mark) to make your " | ||
</ | </ | ||
- | <WRAP center round tip> | + | <WRAP center round tip 80%> |
In this editor, every separate puzzle piece runs as an interrupt. Use this to your advantage. | In this editor, every separate puzzle piece runs as an interrupt. Use this to your advantage. | ||
</ | </ | ||
- | <WRAP center round tip> | + | <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. | 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> | + | <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: | 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> | <WRAP group> | ||
Line 2557: | Line 1603: | ||
=== Connecting the PMODs === | === Connecting the PMODs === | ||
<WRAP group>< | <WRAP group>< | ||
- | Connect the Pmod ALS, the Pmod BLE, the Pmod DA1, and one channel | + | 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 PWM Generator | + | === Building the LED Driver |
- | <WRAP group>< | + | <WRAP group> |
- | Connect three output channels | + | To have a more linear control |
- | Connect | + | You can use the quad op-amp |
- | </ | + | </ |
- | {{ : | + | < |
- | </WRAP></ | + | <iframe width=" |
+ | </html></ | ||
+ | === Connecting the Charger Circuit === | ||
<WRAP group>< | <WRAP group>< | ||
- | To test the PWM generator | + | Connect |
- | </ | + | |
- | {{ : | + | |
- | </ | + | |
- | === Building the Charger Circuit === | + | 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. |
- | <WRAP group>< | + | |
- | Use the Micro-USB plug to provide power to the battery charger. You can connect the charger to the back panel of the ADP. The charger consists of a programmable current sink, realized with the LT3092. The reference voltage will be provided by the first waveform generator channel of the ADP. The current sink can charge up the Li-Po cell until it reaches 4.2V. From here the cell would need a constant voltage source with decreasing current to get the topping charge, but to keep the circuit simple, this part won't be implemented. | + | |
- | + | ||
- | To prevent the negative lead of the battery from becoming floating, the first MOSFET is used in the Pmod OD1 to couple it to the ground, when the charger isn't plugged in. A charge signaling LED is also connected to the ADP to provide feedback. | + | |
- | + | ||
- | 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. | + | |
</ | </ | ||
- | {{ : | + | {{ : |
</ | </ | ||
+ | /* | ||
=== Connecting the RGB LED === | === Connecting the RGB LED === | ||
<WRAP group>< | <WRAP group>< | ||
Line 2593: | Line 1632: | ||
</ | </ | ||
{{ : | {{ : | ||
- | </ | + | </ |
</ | </ | ||
---- | ---- | ||
Line 2599: | Line 1638: | ||
===== Software Setup ===== | ===== Software Setup ===== | ||
<WRAP group> | <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 source code {{ : | + | 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 |
- | === Importing the Modules and Defining Connections === | + | <WRAP group> |
- | <WRAP group>< | + | |
To be able to use all the previously created modules, you must import them into your script. | 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). | 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. | ||
+ | </ | ||
+ | |||
+ | < | ||
+ | --> Importing the Modules, Defining Connections and Constants # | ||
<code python> | <code python> | ||
+ | """ | ||
+ | |||
# import modules | # import modules | ||
- | from PMOD import | + | import |
- | import | + | import |
- | import | + | import |
- | + | from time import time | |
- | """ | + | |
# define connections | # define connections | ||
- | |||
- | # PMOD DA1 pins | ||
- | DA1.pins.sync = 3 | ||
- | DA1.pins.d0 = 2 | ||
- | DA1.pins.d1 = 1 | ||
- | DA1.pins.sck = 0 | ||
- | |||
- | # Pmod OD1 pins | ||
- | OD1.pins.g1 = 7 | ||
- | |||
# PMOD BLE pins | # PMOD BLE pins | ||
- | BLE.pins.rst = 4 | + | ble.pins.rx = 3 |
- | BLE.pins.rx = 5 | + | ble.pins.tx |
- | BLE.pins.tx = 6 | + | ble.pins.rst = 5 |
+ | ble.pins.status | ||
# PMOD ALS pins | # PMOD ALS pins | ||
- | ALS.pins.cs = 10 | + | als.pins.cs = 8 |
- | ALS.pins.sdo = 9 | + | als.pins.sdo = 9 |
- | ALS.pins.sck = 8 | + | als.pins.sck = 10 |
- | + | ||
- | # digital I/O | + | |
- | signal_LED = 11 | + | |
# scope channels | # scope channels | ||
- | battery_neg_ch | + | SC_BAT_P |
- | battery_pos_ch | + | SC_BAT_N = 2 |
+ | SC_CHARGE = 4 | ||
+ | # LED colors | ||
+ | LED_R = 0 | ||
+ | LED_G = 1 | ||
+ | LED_B = 2 | ||
- | # wavegen channels | + | # other parameters |
- | current_reference | + | scope_average |
- | pwm_reference | + | light_average |
- | + | led_pwm_frequency = 1e03 | |
- | # lamp color references | + | out_data_update |
- | lamp_red | + | ble.settings.DEBUG = True # turn on messages from Pmod BLE |
- | lamp_green | + | als.settings.DEBUG = True # turn on messages from Pmod ALS |
- | lamp_blue | + | DEBUG = True # turn on messages |
- | # define low side switch (OD1 channel) | + | # encoding prefixes |
- | low_switch | + | pre_red |
+ | 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></ | + | <-- |
+ | </ | ||
- | === Global Variables and Auxiliary Functions === | + | <WRAP group> |
- | <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 |
- | Define | + | </ |
- | Some auxiliary functions might be needed as well, which will be used later in the script. | + | < |
- | </WRAP><WRAP half column> | + | --> Auxiliary Functions # |
<code python> | <code python> | ||
- | # other parameters | + | def rgb_led(red, green, blue, device_data): |
- | light_referesh = 2 # delay between refreshing the light intensity | + | """ |
- | scope_average = 10 # how many measurements to average with the scope | + | |
- | light_average = 10 # how many measurements to average with the light sensor | + | |
- | battery_charged = 4.2 # battery charge level in volts | + | |
- | battery_discharged = 3.7 # battery discharge level in volts | + | wf.pattern.generate(device_data, |
- | max_charge_current | + | 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 scale(value, | + | """ |
- | # scale a number from a range to another | + | |
- | return ((value | + | |
+ | 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></ | + | <-- |
+ | </ | ||
+ | <WRAP group> | ||
The " | The " | ||
- | === Initialization === | + | To be able to use the instruments, |
- | <WRAP group>< | + | </ |
- | To be able to use the instruments, | + | |
- | </ | + | < |
+ | --> Initialization # | ||
<code python> | <code python> | ||
+ | try: | ||
# initialize the interface | # initialize the interface | ||
- | | + | |
- | + | # check for connection errors | |
- | # initialize the scope with default settings | + | |
- | | + | if DEBUG: |
- | + | print(device_data.name + " connected" | |
- | # initialize | + | # start the power supplies |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | + | if DEBUG: | |
- | """ | + | |
- | + | # initialize | |
- | # start generating | + | |
- | | + | |
- | | + | ble.open() |
- | 0.5, frequency=1e03, | + | ble.reboot() |
- | + | | |
- | | + | |
# turn off the lamp | # turn off the lamp | ||
- | | + | |
- | DA1.output_voltage(lamp_green, 1) | + | |
- | DA1.output_voltage(lamp_blue, 1) | + | print(" |
- | + | ||
- | | + | |
- | | + | |
- | WF.digital.write(signal_LED, | + | |
- | OD1.off(low_switch) | + | |
</ | </ | ||
- | </WRAP></ | + | <-- |
+ | </ | ||
- | === Endless Loop === | + | <WRAP group> |
- | <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 part of the script is run in an endless loop, which can be exited only by a keyboard interrupt. | + | </ |
- | </WRAP>< | + | |
- | <code python> | + | |
- | # variables | + | |
- | start_time = time.time() | + | |
- | | + | |
- | off_flag = True | + | |
- | charger_connected = False | + | |
- | </ | + | |
- | </ | + | |
- | <WRAP group>< | + | <WRAP group> |
- | All other variables are temporal, they change in every iterations, so they can be defined in the loop. | + | --> Main Loop: Receiving Data # |
- | </ | + | |
<code python> | <code python> | ||
- | # temporal variables | + | while True: |
- | | + | |
- | | + | # 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: | ||
+ | | ||
+ | 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></ | + | <-- |
+ | </ | ||
- | === Receive Data on Bluetooth Low Energy === | + | <WRAP group> |
- | <WRAP group>< | + | --> Main Loop: Sending Data # |
- | As a first step, read the messages from the PMOD BLE and save the connection status (also turn off the lamp if the module is disconnected). If the data is received without error, convert the received bytes to voltages, then set the lamp color, by setting the output values of the DAC channels. | + | |
- | </ | + | |
<code python> | <code python> | ||
- | # read data from the Bluetooth module | + | |
- | data, sys_msg, error = BLE.receive_data() | + | |
- | | + | |
+ | 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 | ||
- | | + | |
- | if sys_msg.startswith(" | + | light = encode(light, pre_light, 100) |
- | | + | |
- | elif sys_msg == "%DISCONNECT%": | + | |
- | lamp_on | + | |
- | | + | |
- | if error == "no error" and len(data) >= 3: | + | |
- | | + | batt_p |
- | | + | for _ in range(scope_average): |
- | duty_red = scale(data[-3], [1, 255], [1, 0]) | + | |
- | | + | |
- | | + | |
+ | | ||
+ | | ||
+ | battery_voltage = batt_p | ||
- | | + | |
- | if lamp_on: | + | |
- | if BT_flag: | + | |
- | | + | |
- | off_flag = False | + | |
- | DA1.output_voltage(lamp_red, duty_red) | + | |
- | DA1.output_voltage(lamp_green, duty_green) | + | |
- | | + | |
- | else: | + | |
- | if not off_flag: | + | |
- | off_flag | + | |
- | DA1.output_voltage(lamp_red, 1) | + | |
- | DA1.output_voltage(lamp_green, | + | |
- | DA1.output_voltage(lamp_blue, | + | |
+ | # 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></ | + | <-- |
+ | </ | ||
- | === Measuring the Light Intensity === | + | <WRAP group> |
- | <WRAP group>< | + | When the script |
- | Light intensity is measured only after a predefined time interval. | + | </ |
- | </ | + | |
+ | < | ||
+ | --> Cleanup # | ||
<code python> | <code python> | ||
- | # if the light intensity has to be measured | + | except KeyboardInterrupt: |
- | if time.time() - start_time > light_referesh: | + | # exit on Ctrl+C |
- | # reinitialize the SPI interface | + | if DEBUG: |
- | | + | print(" |
- | + | ||
- | # measure the light intensity | + | |
- | for _ in range(light_average): | + | |
- | light += ALS.receive_percent() | + | |
- | light /= light_average | + | |
- | + | ||
- | # encode the light intensity | + | |
- | encoded_light = round(light * 2.55) & 0xFF | + | |
- | + | ||
- | # reinitialize the UART interface | + | |
- | BLE.initialize(WF) | + | |
- | + | ||
- | # send light intensity | + | |
- | BLE.send_data(encoded_light) | + | |
- | + | ||
- | start_time = time.time() | + | |
+ | 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(" | ||
</ | </ | ||
- | </ | + | <-- |
+ | </ | ||
+ | </ | ||
+ | ---- | ||
- | === Charging | + | ===== Setting Up the Analog Discovery Pro 3450 (Linux Mode) ===== |
- | <WRAP group>< | + | <WRAP group> |
- | In every iteration of the main loop, measure the battery voltage. Also check, whether the charger is connected, or not and turn on the low-side switch, if the charger is disconnected, | + | 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 |
- | </ | + | |
- | <code python> | + | |
- | # read battery voltage | + | |
- | for _ in range(scope_average): | + | |
- | battery_negative += WF.scope.measure(battery_neg_ch) | + | |
- | battery_negative /= scope_average | + | |
- | for _ in range(scope_average): | + | |
- | battery_positive += WF.scope.measure(battery_pos_ch) | + | |
- | battery_positive /= scope_average | + | |
- | battery_voltage = battery_positive - battery_negative | + | |
- | # decide if the charger | + | The next step is to connect the ADP3450 to the internet. Follow this guide for the detailed steps: [[test-and-measurement:analog-discovery-pro-3x50: |
- | if battery_positive >= 4.3: | + | |
- | charger_connected = True | + | |
- | OD1.off(low_switch) | + | |
- | else: | + | |
- | charger_connected = False | + | |
- | OD1.on(low_switch) | + | |
- | # calculate charge current reference | + | 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 |
- | charge_current = max_charge_current * scale(battery_voltage, [battery_discharged, battery_charged], | + | < |
- | charge_current_ref = scale(charge_current, | + | |
- | # start/stop charging | + | 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: |
- | if charger_connected and battery_voltage < battery_charged: | + | < |
- | WF.wavegen.generate(current_reference, WF.wavegen.type.dc, | + | |
- | | + | |
- | else: | + | |
- | WF.wavegen.generate(current_reference, | + | |
- | WF.digital.write(signal_LED, | + | |
+ | 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): |
+ | <code> | ||
+ | [Unit] | ||
+ | Description=Smart Lamp Controller | ||
- | === Cleanup === | + | [Service] |
- | <WRAP group>< | + | ExecStart=nohup python3 |
- | When the script is finished, turn off the lamp and the charger, then close reset the used instruments and disconnect from the device. | + | |
- | </WRAP>< | + | |
- | <code python> | + | |
- | # turn off the lamp | + | |
- | DA1.output_voltage(lamp_red, | + | |
- | DA1.output_voltage(lamp_green, | + | |
- | DA1.output_voltage(lamp_blue, | + | |
- | + | ||
- | # turn off the LED and the switch | + | |
- | WF.digital.write(signal_LED, | + | |
- | OD1.off(low_switch) | + | |
- | + | ||
- | # close PMODs | + | |
- | ALS.close() | + | |
- | BLE.close() | + | |
- | DA1.close() | + | |
- | OD1.close() | + | |
- | + | ||
- | # close device | + | |
- | WF.close() | + | |
+ | [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 | ||
</ | </ | ||
- | </ | ||
- | |||
</ | </ | ||
---- | ---- | ||
Line 2891: | Line 1968: | ||
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. | 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. | ||
- | |||
- | Take care not to overcharge, or over-discharge the battery, or overload the power supply. | ||
</ | </ | ||
---- | ---- | ||
Line 2900: | Line 1975: | ||
For more information on WaveForms SDK, see its [[software: | For more information on WaveForms SDK, see its [[software: | ||
- | For technical support, please visit the [[https:// | + | For technical support, please visit the [[https:// |
</ | </ | ||