Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Sync Rotary encoder recording with external fiber photometry
#1
Hi Josh,

I am trying to Sync the rotary encoder data with my fiber photometry recordings. But I haven't figured it out. Could you give me some suggestions? Currently, I only have a Bpod State Machine r2, the rotary encoder module, and a rotary encoder. There is no trial structure during my recordings and the Fiber photometry system could send or record a TTL signal.

Thanks for your help.

Best,
Ke
Reply
#2
Hello Ke,

Here's my suggested approach:

-Capture rotary encoder data via USB instead of the microSD card. Relevant commands are: 
  • startUSBStream()
  • data = readUSBStream()
  • stopUSBStream()
as documented here.

This will allow you to use the '#' command passed from the state machine, to add an event timestamp to the rotary encoder data.


-Run a single trial on the state machine, for the duration of your experiment. Make sure the trial ends when your experiment ends, so your trial data will be saved.

-In the 'Output Actions' section of the first state, trigger two looping global timers. The first timer is attached to BNC1 --> Photometry System. The second timer (in sync with the first) sends the '#' command to the rotary encoder. Each timer loop is also timestamped in your Bpod behavior data, so this repeating event will be timestamped on all 3 systems. Both timers loop in sync at 10Hz, more than sufficient to account for any clock drift.


Code:
LoadSerialMessages('RotaryEncoder1', {['#' 1]}); % Set Message 1 as the REM command to sync
sma = NewStateMachine;
sma = SetGlobalTimer(sma, 'TimerID', 1, 'Duration', 0.05, 'OnsetDelay', 0,...
                    'Channel', 'BNC1', 'OnLevel', 1, 'OffLevel', 0,...
                    'Loop', 1, 'SendGlobalTimerEvents', 1, 'LoopInterval', 0.05);
sma = SetGlobalTimer(sma, 'TimerID', 2, 'Duration', 0.05, 'OnsetDelay', 0,...
                    'Channel', 'RotaryEncoder1', 'OnMessage', 1, 'OffMessage', 0,...
                    'Loop', 1, 'SendGlobalTimerEvents', 1, 'LoopInterval', 0.05);
sma = AddState(sma, 'Name', 'TimerTrig', ...
   'Timer', 0,...
   'StateChangeConditions', {'Tup', 'MyNextState'},...
   'OutputActions', {'GlobalTimerTrig', '11'}); % Binary strings can trigger multiple timers

Please let me know if this works for you!
-Josh
Reply
#3
Hi Josh,

Thanks so much for your suggestion. The Sync works.
But I have a problem to record enough new positions with readUSBStream. In many scenarios, I could only record the first ~1650 new positions, and all the events and new positions after that were missing.  Maybe I didn't use it correctly. I attached the script I use to test the recording for 60s.

Thanks for your help.

Best,
Ke





Code:
global BpodSystem
%% Resolve Rotary Module USB port
if (isfield(BpodSystem.ModuleUSB, 'RotaryEncoder1'))
   %% Create an instance of the Rotary module
   R = RotaryEncoderModule(BpodSystem.ModuleUSB.RotaryEncoder1);
else
   error('Error: To run this protocol, you must first pair the Rotary module with its USB port. Click the USB config button on the Bpod console.')
end
R.startUSBStream()
LoadSerialMessages('RotaryEncoder1', {['#' 1]}); % Set Message 1 as the REM command to sync
sma = NewStateMachine;
sma = SetGlobalTimer(sma, 'TimerID', 1, 'Duration', 0.05, 'OnsetDelay', 0,...
                   'Channel', 'BNC1', 'OnLevel', 1, 'OffLevel', 0,...
                   'Loop', 1, 'SendGlobalTimerEvents', 1, 'LoopInterval', 0.05);
sma = SetGlobalTimer(sma, 'TimerID', 2, 'Duration', 0.05, 'OnsetDelay', 0,...
                   'Channel', 'RotaryEncoder1', 'OnMessage', 1, 'OffMessage', 0,...
                   'Loop', 1, 'SendGlobalTimerEvents', 1, 'LoopInterval', 0.05);
sma = AddState(sma, 'Name', 'TimerTrig', ...
  'Timer', 0,...
  'StateChangeConditions', {'Tup', 'Recording'},...
  'OutputActions', {'GlobalTimerTrig', '11'}); % Binary strings can trigger multiple timers
sma = AddState(sma, 'Name', 'Recording', ...
   'Timer', 60,...
   'StateChangeConditions', {'Tup', '>exit'},...
   'OutputActions', {}); % Binary strings can trigger multiple timers
SendStateMatrix(sma);
RawEvents = RunStateMatrix;
if ~isempty(fieldnames(RawEvents)) % If trial data was returned
   BpodSystem.Data = AddTrialEvents(BpodSystem.Data,RawEvents); % Computes trial events from raw data
   BpodSystem.Data.Rotary = R.readUSBStream();
   R.stopUSBStream()
   SaveBpodSessionData; % Saves the field BpodSystem.Data to the current data file
end
Reply
#4
Hello Ke,

If your code doesn't call R.readUSBStream() often enough to avoid buffer overruns, you'll need to initialize the USB stream with:


Code:
R.startUSBStream('UseTimer');

This will start a MATLAB timer process in the background to read incoming data from the buffer to a private field of the R object every 100ms.
You can retrieve the data as you are already doing, with:
Code:
Data = R.readUSBStream();

-Josh
Reply
#5
(10-06-2021, 03:49 PM)Josh Wrote: Hello Ke,

If your code doesn't call R.readUSBStream() often enough to avoid buffer overruns, you'll need to initialize the USB stream with:


Code:
R.startUSBStream('UseTimer');

This will start a MATLAB timer process in the background to read incoming data from the buffer to a private field of the R object every 100ms.
You can retrieve the data as you are already doing, with:
Code:
Data = R.readUSBStream();

-Josh
Thanks, Josh. It is working now.  Smile
Reply


Forum Jump: