General Program Structure
Let us take an example Cyclops program and perform a walkthrough. For details on usage, design, implementation please refer the developer documentation.
Example: rpc_loop
The rpc_loop
example is included in the /src/teensy/
directory of the repository.
File: MySources.h
1. #ifndef CL_MY_SOURCES_H
3. #define CL_MY_SOURCES_H
4. //
5. // This file contains the globals definitions of the Source objects Just include
6. // this file into your main `.ino` script, and you'll get access to the objects
7. // here.
8. //
9.
10. uint16_t triangle_wave(uint8_t seq){
11. return (abs(20 - seq)/40.0)*2048;
12. }
13.
14. uint32_t triangle_update_period(uint8_t seq){
15. return 30000 + seq;
16. }
17.
18. uint16_t vd_1[] = {1, 2048};
19. uint32_t ht_1[] = {70000, 70000};
20.
21. cyclops::generatedSource gen ( triangle_wave
23. , triangle_update_period
24. , 40
25. , cyclops::source::LOOPBACK);
26.
27. cyclops::storedSource sto_1 ( vd_1
28. , ht_1
29. , 2
30. , cyclops::source::LOOPBACK);
31.
33. cyclops::squareSource square (cyclops::source::LOOPBACK);
34.
35. /* You must register these sources with the library, by:
36. *
37. * 1. making a globally scoped array of pointers to the objects.
38. * 2. assign the global array to the ``globalSourceList_ptr``
39. *
40. * Only the registered sources are guaranteed to work when using the RPC,
41. * especially when using the OE GUI.
43. */
44. cyclops::Source* globalSourceList[] = { &gen
45. , &sto_1
46. , &square
47. };
48. // Assigning this array to the special pointer.
49. REGISTER_SOURCE_LIST(globalSourceList, 3);
50. #endif
Source
List
As mentioned earlier, you must start with the definition of Source
objects. Three kinds of Source
objects can be defined:
storedSource
- The one-size fit-all format, where you specify the complete signal data in the form of an
array
.
- The one-size fit-all format, where you specify the complete signal data in the form of an
squareSource
- Provides special methods to configure all parameters of a square signal.
generatedSource
beta
- An experimental and extremely compact format, where you define a "Generator Function", that computes the signal voltage as a function of time.
- These are most friendly to use, but come with associated (high) cost.
The example defines 3 Sources
: gen
, sto_1
and square
(in a searate file, to keep length definitions away from the main logic).
# 44-49
You must also register the Sources
by adding them to a global list.
Waveform
and Board
Continuing the example,
File: rpc_loop.ino
1. #include <Cyclops.h>
2. #include <CLTask.h>
3. #include "MySources.h"
4.
5. cyclops::Board myb(cyclops::board::CH0);
6. cyclops::Waveform waveform(&myb, &gen);
7. cyclops::Queue processQueue;
8.
This program has just 1 Board
object, that means it will control just one LED channel
, namely channel 0
. It also has just one Waveform
object for the same reason.
You must define as many Board
and Waveform
objects (up to 4) as the number of LEDs that you wish to control.
The Queue
processQueue
is used to hold any tasks that have been recieved over USB but are pending.
These objects must have global scope. If you define them in
setup()
, you won't be able to use them inloop()
-- simple.
setup()
Here, you must setup peripherals, and any configurtions of the globals that you might have created.
File: rpc_loop.ino
9. void setup()
10. {
11. pinMode(30, INPUT_PULLUP);
12. pinMode(31, INPUT_PULLUP);
13. pinMode(32, INPUT_PULLUP);
14. pinMode(33, INPUT_PULLUP);
15.
16. Serial.begin(57600);
17. SPI_fifo.begin(SPI_CLOCK_16MHz); // 16MHz SPI clock, using pin 10 as CS
18. double delta = cyclops::Waveform::initAll();
19. Timer1.initialize(delta);
20. Timer1.attachInterrupt(cyclops::cyclops_timer_isr); // Defined in Waveform_t.h
21. }
22.
The pins [30-33] are used to find out the number and "addresses" of the connected Cyclops Boards.
Run program without any Board!
You can run Cyclops programs on the Teensy, even if no board is connected! You can define more (
Waveforms
= )Boards
andSources
than required, for testing, (or whatever) and the Teensy will NOT optimize away the unnecessary overhead.
# 16
Start the hardware SPI
bus and controller. SPI is used to send the Signal data from Sources
to the DAC
on Cyclops (similar to Cyclops rev 3.5).
# 17
Start the USB Serial interface for RPC.
Baud Rate is always fixed!
This is an interesting Teensy feature. All USB Serial communication happens at the highest possible Baud rates (depending on your PC/Workstation USB controller and the Teensy USB controller).
# 19-20
Start the Timer1
-- which is responsible for the precise updates of the Signals via an interrupt.
loop()
23. void loop()
24. {
25. cyclops::Waveform::processAll();
26. cyclops::readSerialAndPush(&processQueue);
27. if (processQueue.size > 0){
28. cyclops::Task* t = processQueue.peek();
29. t->compute();
30. processQueue.pop();
31. }
32. }
# 25
The most important call, it is responsible for performing the SPI
transfers.
# 26
Reads from the Serial interface and parses the recieved bytes into Task
objects. These are pushed into the processQueue
.
# 27-31
Process one Task
from the processQueue
(if any). Most Tasks
are one-liners, but it's important that only 1 Task is processed at a time (related to Interrupts)
Running the Examples
Open the example using Arduino IDE. Compile the program and flash it to the Teensy. Some examples (like rpc_loop
) use the Serial Interface.
To interact with the Cyclops, look for a python
Tkinter application in /src/cyclops-ctrl-gui/gui_main.py
. Usage instructions can be found in packet_gen.py
and by passing a command-line flag to invoke, like so:
python gui_main.py -h
Quickly defining Source
objects
We provide a python
script, the SignalEditor
(in /plugin-GUI/Resources/Python
) which makes it super easy to edit and view signals live from an IPython shell. This script can save the signals in various formats.
Choose the Cyclops Objects
format which would show the plain-text C++
array and Object definition code which you can copy and paste into mySources.h
.
More info can be found here.
Further Reading
At this point, you know how the Cyclops Library should (and can) be used.
If you wish to know all about the inner working and design, we lay it bare for you in Design. With that knowledge you can start tweaking the Cyclops Library and work in baremetal mode with the Teensy. You could add more RPC for your custom script, or add cool modules to use other features of the Teensy like ADC, PWM generation, etc.
Previous: Overview
Next Up: "Using Cyclops with OE GUI"
If you are going to use Cyclops only with the Open-Ephys GUI, you need not worry about the Library internals -- just get familiar with the CyclopsPlugin
API. That's the next section, so read on!