May 1st brought us an awesome episode of the In Beta podcast on the 5by5 network.
Basically, Gina Trapani explains how it feels to a developer when her project is forked, and that forks take many different forms. Ultimately, it looks to me like good stuff is coming out.
Previously, I described how we measure the transfer of the AC bias.
To recap, we assume the user has characterized the DC biasing of the SQUID(s). When we measure the transfer of a single Array-SQUID or a dual-SQUID (i.e. pre-SQUID plus array-SQUID), we need to take the following steps:
Why do we put a test AC signal on the feedback coil, instead of sending it straight through a pixel on the sensor? Because each pixel has a hardwired LC filter, we could say. But that feedback coil is there, because the SQUIDs hardly have a dynamic range.
Let me put this in other words, because it's extremely important to understand the whole setup.
The SQUID is a very sensitive magnetometer. Thus the smallest change of flux will cause it to change its output. The output looks like a sine wave, thus it has an extremely small dynamic range (namely, the flank of a sinus). This means that we need a feedback loop that holds the SQUID locked to an output range. Because we have more than one pixel, we would run the SQUID out of its dynamic range. Thus the feedback loop ensures that, within its dynamic range, the SQUID can add all the pixel carrier waves.
To make this measurement, we use a set of registers that allow us to configure the Demux board to start spewing data over the second optical network link. It's very flexible and can be viewed as a number of digital data taps located on several points of the Demux board. You can set the data source, the amount of data, the trigger source, and finally whether you want a specified number of data blocks or just continuous data.
The data source can be configured to the ADC, the DACs, or the demodulated output of pixel 1 to 16. The ADC is of course what we need currently, but you can also read out the DACs. This is useful to check that the FPGA algorithms (more on that later) are correctly working. Finally to read the demodulated output of pixels is hugely useful, because (1) (see below).
The trigger source is worth a different topic, which involves explaining what the sensor actually measures (X-rays or infrared signals). The options currently are: testpulse generator (2), level triggering of selected pixels, both of these options, or just auto (get all the data).
For our purpose, we need to configure it as following:
Footnotes:
(1) Demodulation saves us bandwidth as well as processing on the PC.
(2) The testpulse generator is used to generate fake detector signals, to test the complete signal chain. You can test demodulation and/or test the electronical system. It generates the ideal detector signal. You can send the testpulse generator signal to the feedback coil of the SQUID and measure the PPM drift of the signal chain.
Here's my version of a state machine in Objective-C, useful for your iOS or Mac OS X projects. There are many like it, but this one is mine. I've modeled an alarm clock here.
What I like about it: it's pretty small and light on the objects. The state transitions are the only thing that's done with an object, and even these could be replaced with a struct, but that causes problems with ARC (which doesn't like function pointers in structs).
The code below is WTFPL-licensed. Just so you know.
The header file:
/* Enums that we need throughout this class to maintain state */
enum WSAlarmState {WSAlarmStateInactive,
WSAlarmStateActive, WSAlarmStatePlaying};
enum WSAlarmAction {WSAlarmActionStart, WSAlarmActionPlay,
WSAlarmActionSnooze, WSAlarmActionStop}; /* Describes how one state moves to the other */
@interface WSAlarmStateTransition : NSObject @property enum WSAlarmState srcState;
@property enum WSAlarmAction result;
@property enum WSAlarmState dstState;@end
/* Singleton that maintains state for an alarm */
@interface WSAlarm : WSNotification@property enum WSAlarmState currentState;
@end
The header file contains the enums for the states and the actions. Note that these actions are both used as a return value and as an input value.
The implementation file starts with the init method, which sets up the state transition table. Basically, this table says: given a state and a resulting action, what is the next state?
Furthermore, it contains a function that does all transitions, a function that looks up the next state, and the state methods.
#import "WSAlarm.h"
@implementation WSAlarmStateTransition
- (id)init:(enum WSAlarmState)srcState :(enum WSAlarmAction)action :(enum WSAlarmState)dstState
{
if (self = [super init]) {
// Do initialization here
DLog(@"init");
self.srcState = srcState;
self.result = action;
self.dstState = dstState;
}
return self;
}@end
#pragma mark -
#pragma mark WSAlarm class @implementation WSAlarm {
NSArray *stateMethods;
NSArray *stateTransitions;
} - (id)init
{
if (self = [super init]) {
// Do initialization here
DLog(@"init");
self.currentState = WSAlarmStateInactive;
/* This array and enum WSAlarmStates must be in sync! */
stateMethods = @[
[NSValue valueWithPointer:@selector(noAlarmActiveState)],
[NSValue valueWithPointer:@selector(alarmActiveState)],
[NSValue valueWithPointer:@selector(playingAlarm)]
];
stateTransitions = @[
[[WSAlarmStateTransition alloc] init:WSAlarmStateInactive
:WSAlarmActionStart
:WSAlarmStateActive],
[[WSAlarmStateTransition alloc] init:WSAlarmStateActive
:WSAlarmActionPlay
:WSAlarmStatePlaying],
[[WSAlarmStateTransition alloc] init:WSAlarmStateActive
:WSAlarmActionStop
:WSAlarmStateInactive],
[[WSAlarmStateTransition alloc] init:WSAlarmStateActive
:WSAlarmActionForegrounded
:WSAlarmStatePlayedInBackground],
[[WSAlarmStateTransition alloc] init:WSAlarmStatePlaying
:WSAlarmActionStart
:WSAlarmStateActive],
[[WSAlarmStateTransition alloc] init:WSAlarmStatePlaying
:WSAlarmActionStop
:WSAlarmStateInactive],
[[WSAlarmStateTransition alloc] init:WSAlarmStatePlayedInBackground
:WSAlarmActionStart
:WSAlarmStateActive],
[[WSAlarmStateTransition alloc] init:WSAlarmStatePlayedInBackground
:WSAlarmActionStop
:WSAlarmStateInactive],
}
return self;
} - (void)registerDefaults
{
DLog(@"entry");
} #pragma mark -
#pragma mark Convenience methods - (void)start:(NSDate*)notifDate
{
self.notificationDate = notifDate;
[self transitionToState:WSAlarmStateActive withAction:WSAlarmActionStart];
} - (void)stop
{
[self transitionToState:WSAlarmStateInactive withAction:WSAlarmActionStop];
} - (void)play
{
[self transitionToState:WSAlarmStatePlaying withAction:WSAlarmActionPlay];
} - (void)snooze
{
[self transitionToState:WSAlarmStateActive withAction:WSAlarmActionSnooze];
} #pragma mark -
#pragma mark State machine // Walk through table of transitions, and return new state
- (enum WSAlarmState)lookupTransitionForState:(enum WSAlarmState)state
withResult:(enum WSAlarmAction)action
{
enum WSAlarmState newState = -1;
for(WSAlarmStateTransition *t in stateTransitions) {
if(t.srcState == state && t.result == action) {
// We found the new state.
newState = t.dstState;
break;
}
}
if(newState == -1) {
NSString *msg = [NSString stringWithFormat:
@"Can't transition from state %@ with return code %@",
alarmStateString[state], alarmActionString[action]];
@throw [NSException exceptionWithName:@"TransitionException"
reason:msg
userInfo:nil]; }
return newState;
} - (void)transitionToState:(enum WSAlarmState)newState withAction:(enum WSAlarmAction)result
{
NSValue *stateMethodValue = (NSValue*) stateMethods[newState];
SEL stateMethod = [stateMethodValue pointerValue];
// We need these because otherwise we get a warning
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
NSNumber *param = [NSNumber numberWithInt:result];
enum WSAlarmAction nextAction = [self performSelector:stateMethod withObject:param];
#pragma clang diagnostic pop
self.currentState = [self lookupTransitionForState:self.currentState withResult:nextAction];
} #pragma mark -
#pragma mark States - (enum WSAlarmAction)noAlarmActiveState:(enum WSAlarmAction)action
{
// Some code to stop the alarm
return WSAlarmActionStop;
} - (enum WSAlarmAction)alarmActiveState:(enum WSAlarmAction)action
{
if(action == WSAlarmActionSnooze) {
// User tapped "snooze", stop the sound
} else if(action == WSAlarmActionStart) {
// No alarm active, user starts alarm
} else {
// We reached state alarm active with a weird action
}
return WSAlarmActionStart;
} - (enum WSAlarmAction)playingAlarmState:(enum WSAlarmAction)result
{
// Some code to play a sound
return WSAlarmActionPlay;
}@end
Another routine in the system is the AC gain curve measurement.
This routine is different from the previous one, 2013-04-09 Characterizing the transfer of a voltage biased SQUID. In that routine, we just looked at the DC part of the amplifier chain.
In this measurement, we assume those measurements were successful, and that the amplifier chain was set according to optimal parameters.
And since our instrument basically uses AC biased TESes (as explained here), we want to measure the transfer of the AC bias, after the SQUID has been configured optimally. This is very interesting, because that means you see the instrument as a whole, instead of characterizing a small part.
So what we're going to do, is sweep the AC bias and then measure back the result.
To do this, we have to send the FPGA the appropriate commands. First, some schematics:
On the Demux board, there are two DACs that can drive the SQUID in the cryostat. There is also an ADC to measure back the result. The FEE board is in between here, but for now we can ignore it. The cryostat is where the above sensor is located. It has an array of TESes, each with its own LC filter.
If we want to feed in an AC bias, we don't want to use DAC2, because we don't want it filtered through the LC filters. Instead, the FPGA exposes a register where we can switch the input line of both DACs, so you can feed the AC bias signal to DAC1. Note that this AC bias signal is still digitized, otherwise we couldn't feed it into a DAC :-)
The user needs to be able to adjust the AC bias signal. The FPGA exposes a number of parameters for this, but we just need the frequency and amplitude, it's a really small signal: 2 MHz at 1 uA. Both are adjustable, and I'll add a setting for 1 and 5 MHz as well.
We retrieve the data asynchronously. This is rather different from our usual way of getting data; i.e. normally we'd just polling a particular register.
In this case, the hardware has a special function called the "science switch". A possible analogy could be that this science switch acts as a number of water faucets, where you'd just let the raw data stream out of the board, from specific points. Since water and electronics don't mix much, it's actually a number of registers. I'll write more on that later.
Currently, I'm finishing a software routine to characterize the transfer (see below) of a voltage based SQUID. Simplified, our electronics could schematically be shown as follows:
Firstly, about this schema.
On the left, you see a single SQUID. This is the pre-SQUID. In the middle, you see an array of SQUIDs. We functionally treat that last one as a single squid, calling it "the" array-SQUID. These two types of SQUIDs are biased differently; the pre-SQUID is voltage-biased, and the array-SQUID is biased with a current.
You can view this combination as one "dual-stage SQUID".
So what we do, is: we set a current over the shunt on the left. This will put a voltage on the pre-SQUID and a magnetic field will form on the inductor next to the array-SQUID. We'll read out an output voltage on the right.
Now normally, the amount of voltage we put on the pre-SQUID would overdrive the array-SQUID. Thus, we put it in flux-locked loop. This is a mode where the array-SQUID simply passes current that results from the changes in flux.
Because the array-SQUID is in flux-locked-loop (FLL), we can measure the output current of the pre-SQUID without a dynamic range limitation (+/- 500 uA). This flux-locked-loop is nothing more than a feedback circuit which forces the bias of the array-SQUID down to zero.
Note that this is all DC-biased. The SQUIDs are part of the amplifier chain, and the whole amplifier chain is DC-biased. After we've characterized the amplifier chain, our primary purpose is to read out the sensor, consisting of a TES array that's AC-biased, see also 2012-07-02 Minimizing amplifier chain offset.
Secondly, about the routine itself.
I said "characterize the transfer". The transfer means: what is the effect of the input on the output. If you draw this function, you'd ideally see a sinus. We want to know in which voltage range our pre-SQUID behaves the best (i.e. where it has the highest dynamic range. In other words, you want to pick the voltage that's right in the middle on a flank. Because that way, with a little bit of input, you get a nice stretch of usable output.
Compare it to a radio that can receive a lot of stations.
Articles, chronologically (latest first):
Not yet finished. Maybe will never be finished. Maybe they'll get deleted. Who knows?
Programming:
System administration:
Others:
Files: