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.
  • 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 in loop() -- 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 and Sources 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!