Current section

Files

Jump to
grisp src pmod_dio.erl
Raw

src/pmod_dio.erl

-module(pmod_dio).
-behaviour(gen_server).
% FIXME: remove!
-export([crc5/1]).
-export([read/2]).
-export([read_burst/2]).
-export([write/3]).
% API
-export([start_link/2]).
% Callbacks
-export([init/1]).
-export([handle_call/3]).
-export([handle_cast/2]).
% TODO: Verify!
-define(SPI_MODE, #{clock => {low, leading}}).
-define(CRC5_START, 16#1F).
-define(CRC5_POLY, 16#15).
-include("grisp.hrl").
%--- API -----------------------------------------------------------------------
start_link(Connector, _Opts) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, Connector, []).
read(Chip, Reg) ->
Value = <<0>>,
call({request, {Chip, single, read, Reg, Value}}).
read_burst(Chip, 'DoiLevel') ->
Value = <<0, 0, 0, 0, 0, 0>>,
call({request, {Chip, burst, read, 'DoiLevel', Value}});
read_burst(_Chip, Reg) ->
error({invalid_burst_register, Reg}).
write(Chip, Reg, Value) ->
call({request, {Chip, single, write, Reg, Value}}).
%--- Callbacks -----------------------------------------------------------------
init(Slot) ->
grisp_devices:register(Slot, ?MODULE),
Bus = grisp_spi:open(Slot),
{ok, #{bus => Bus}}.
handle_call({request, Request}, _From, #{bus := Bus} = State) ->
{reply, send_request(Bus, Request), State};
handle_call(Request, From, _State) ->
error({unknown_call, Request, From}).
handle_cast(Request, _State) -> error({unknown_cast, Request}).
%--- Internal ------------------------------------------------------------------
call(Call) ->
Dev = grisp_devices:default(?MODULE),
gen_server:call(Dev#device.pid, Call).
send_request(Bus, {Chip, Type, Op, Reg, Value}) ->
Addr = Chip - 1,
Encoded = encode_request(Reg, Value),
Request = request(Type, Op, Addr, Reg, Encoded),
[Response] = grisp_spi:transfer(Bus, [{?SPI_MODE, Request}]),
decode_response(Op, Addr, Reg, bit_size(Encoded), Response).
request(Type, Op, Addr, Reg, Value) ->
Req =
<<Addr:2, (type(Type)):1, (reg(Reg)):4, (rw(Op)):1, Value/binary,
2#000:3>>,
CRC = crc5(Req),
<<Req/bitstring, CRC:5>>.
type(single) -> 0;
type(burst) -> 1.
rw(read) -> 0;
rw(write) -> 1.
crc5(Bitstring) ->
crc5(Bitstring, ?CRC5_START).
crc5(<<Bit:1, Bin/bitstring>>, R) ->
R2 =
case (Bit band 16#01) bxor ((R band 16#10) bsr 4) of
Val when Val > 0 -> ?CRC5_POLY bxor (R bsl 1) band 16#1f;
_Else -> (R bsl 1) band 16#1f
end,
crc5(Bin, R2);
crc5(<<>>, R) ->
R.
encode_request(_Reg, Value) when is_binary(Value) ->
Value;
encode_request(Reg, Value) ->
reg(encode, Reg, Value).
decode_response(Op, Addr, Reg, Len, <<_:2, Response/bitstring>>) ->
0 = crc5(Response),
<<
SHTVDD:1,
AbvVDD:1,
OWOffF:1,
OvrCurr:1,
OvlDf:1,
GLOBLF:1,
Result:Len/bitstring,
Addr:2,
ThrErr:1,
_CRC:5
>> = Response,
maps:merge(
#{
'SHTVDD' => boolean(SHTVDD),
'AbvVDD' => boolean(AbvVDD),
'OWOffF' => boolean(OWOffF),
'OvrCurr' => boolean(OvrCurr),
'OvlDf' => boolean(OvlDf),
'GLOBLF' => boolean(GLOBLF),
'ThrErr' => boolean(ThrErr)
},
decode_result(Op, Reg, Result)
).
decode_result(read, Reg, Result) ->
#{
result => decode_registers(Reg, Result)
};
decode_result(write, _Reg, Result) ->
<<
DiLvl4:1,
DiLvl3:1,
DiLvl2:1,
DiLvl1:1,
F4:1,
F3:1,
F2:1,
F1:1
>> = Result,
#{
result => #{
'DiLvl4' => boolean(DiLvl4),
'DiLvl3' => boolean(DiLvl3),
'DiLvl2' => boolean(DiLvl2),
'DiLvl1' => boolean(DiLvl1),
'F4' => boolean(F4),
'F3' => boolean(F3),
'F2' => boolean(F2),
'F1' => boolean(F1)
}
}.
decode_registers(StartReg, Response) ->
decode_registers(reg(StartReg), Response, #{}).
decode_registers(Reg, <<Resp:8/bitstring, Response/binary>>, Result) ->
Name = reg(Reg),
decode_registers(Reg + 1, Response, Result#{
Name => reg(decode, Name, Resp)
});
decode_registers(_Reg, <<>>, Result) ->
Result.
-define(MAP_FIELD(Name),
list_to_atom(??Name) => field(decode, list_to_atom(??Name), Name)
).
-define(BIN_FIELD(Name),
(field(encode, list_to_atom(??Name), Name))
).
reg('SetOUT') -> 16#00;
reg('SetLED') -> 16#01;
reg('DoiLevel') -> 16#02;
reg('Interrupt') -> 16#03;
reg('OvrLdChF') -> 16#04;
reg('OpnWirChF') -> 16#05;
reg('ShtVDDChF') -> 16#06;
reg('GlobalErr') -> 16#07;
reg('OpnWrEn') -> 16#08;
reg('ShtVDDEn') -> 16#09;
reg('Config1') -> 16#0A;
reg('Config2') -> 16#0B;
reg('ConfigDI') -> 16#0C;
reg('ConfigDO') -> 16#0D;
reg('CurrLim') -> 16#0E;
reg('Mask') -> 16#0F;
reg(16#00) -> 'SetOUT';
reg(16#01) -> 'SetLED';
reg(16#02) -> 'DoiLevel';
reg(16#03) -> 'Interrupt';
reg(16#04) -> 'OvrLdChF';
reg(16#05) -> 'OpnWirChF';
reg(16#06) -> 'ShtVDDChF';
reg(16#07) -> 'GlobalErr';
reg(16#08) -> 'OpnWrEn';
reg(16#09) -> 'ShtVDDEn';
reg(16#0A) -> 'Config1';
reg(16#0B) -> 'Config2';
reg(16#0C) -> 'ConfigDI';
reg(16#0D) -> 'ConfigDO';
reg(16#0E) -> 'CurrLim';
reg(16#0F) -> 'Mask';
reg(Reg) -> error({invalid_register, Reg}).
reg(decode, 'SetOUT', Content) when is_binary(Content) ->
<<
SetDi4:1,
SetDi3:1,
SetDi2:1,
SetDi1:1,
HighO4:1,
HighO3:1,
HighO2:1,
HighO1:1
>> = Content,
#{
?MAP_FIELD(SetDi4),
?MAP_FIELD(SetDi3),
?MAP_FIELD(SetDi2),
?MAP_FIELD(SetDi1),
?MAP_FIELD(HighO4),
?MAP_FIELD(HighO3),
?MAP_FIELD(HighO2),
?MAP_FIELD(HighO1)
};
reg(encode, 'SetOUT', Content) when is_map(Content) ->
#{
'SetDi4' := SetDi4,
'SetDi3' := SetDi3,
'SetDi2' := SetDi2,
'SetDi1' := SetDi1,
'HighO4' := HighO4,
'HighO3' := HighO3,
'HighO2' := HighO2,
'HighO1' := HighO1
} = Content,
<<
?BIN_FIELD(SetDi4):1,
?BIN_FIELD(SetDi3):1,
?BIN_FIELD(SetDi2):1,
?BIN_FIELD(SetDi1):1,
?BIN_FIELD(HighO4):1,
?BIN_FIELD(HighO3):1,
?BIN_FIELD(HighO2):1,
?BIN_FIELD(HighO1):1
>>;
reg(decode, 'SetLED', Content) when is_binary(Content) ->
<<
SLED4:1,
SLED3:1,
SLED2:1,
SLED1:1,
FLED4:1,
FLED3:1,
FLED2:1,
FLED1:1
>> = Content,
#{
?MAP_FIELD(SLED4),
?MAP_FIELD(SLED3),
?MAP_FIELD(SLED2),
?MAP_FIELD(SLED1),
?MAP_FIELD(FLED4),
?MAP_FIELD(FLED3),
?MAP_FIELD(FLED2),
?MAP_FIELD(FLED1)
};
reg(encode, 'SetLED', Content) when is_map(Content) ->
#{
'SLED4' := SLED4,
'SLED3' := SLED3,
'SLED2' := SLED2,
'SLED1' := SLED1,
'FLED4' := FLED4,
'FLED3' := FLED3,
'FLED2' := FLED2,
'FLED1' := FLED1
} = Content,
<<
?BIN_FIELD(SLED4):1,
?BIN_FIELD(SLED3):1,
?BIN_FIELD(SLED2):1,
?BIN_FIELD(SLED1):1,
?BIN_FIELD(FLED4):1,
?BIN_FIELD(FLED3):1,
?BIN_FIELD(FLED2):1,
?BIN_FIELD(FLED1):1
>>;
reg(decode, 'DoiLevel', Content) when is_binary(Content) ->
<<
SafeDemagF4:1,
SafeDemagF3:1,
SafeDemagF2:1,
SafeDemagF1:1,
DoiLevel4_VDDOKFault:1,
DoiLevel3_VDDOKFault:1,
DoiLevel2_VDDOKFault:1,
DoiLevel1_VDDOKFault:1
>> = Content,
#{
?MAP_FIELD(SafeDemagF4),
?MAP_FIELD(SafeDemagF3),
?MAP_FIELD(SafeDemagF2),
?MAP_FIELD(SafeDemagF1),
?MAP_FIELD(DoiLevel4_VDDOKFault),
?MAP_FIELD(DoiLevel3_VDDOKFault),
?MAP_FIELD(DoiLevel2_VDDOKFault),
?MAP_FIELD(DoiLevel1_VDDOKFault)
};
reg(decode, 'Interrupt', Content) ->
<<ComErr:1, SupplyErr:1, DeMagFault:1, ShtVDDFault:1, AboveVDDFault:1,
OWOffFault:1, CurrLim:1, OverLdFault:1>> = Content,
#{
?MAP_FIELD(ComErr),
?MAP_FIELD(SupplyErr),
?MAP_FIELD(DeMagFault),
?MAP_FIELD(ShtVDDFault),
?MAP_FIELD(AboveVDDFault),
?MAP_FIELD(OWOffFault),
?MAP_FIELD(CurrLim),
?MAP_FIELD(OverLdFault)
};
reg(decode, 'OvrLdChF', Content) ->
<<
CL4:1,
CL3:1,
CL2:1,
CL1:1,
OVL4:1,
OVL3:1,
OVL2:1,
OVL1:1
>> = Content,
#{
?MAP_FIELD(CL4),
?MAP_FIELD(CL3),
?MAP_FIELD(CL2),
?MAP_FIELD(CL1),
?MAP_FIELD(OVL4),
?MAP_FIELD(OVL3),
?MAP_FIELD(OVL2),
?MAP_FIELD(OVL1)
};
reg(decode, 'OpnWirChF', Content) ->
<<
AboveVDD4:1,
AboveVDD3:1,
AboveVDD2:1,
AboveVDD1:1,
OWOff4:1,
OWOff3:1,
OWOff2:1,
OWOff1:1
>> = Content,
#{
?MAP_FIELD(AboveVDD4),
?MAP_FIELD(AboveVDD3),
?MAP_FIELD(AboveVDD2),
?MAP_FIELD(AboveVDD1),
?MAP_FIELD(OWOff4),
?MAP_FIELD(OWOff3),
?MAP_FIELD(OWOff2),
?MAP_FIELD(OWOff1)
};
reg(decode, 'ShtVDDChF', Content) ->
<<
VDDOV4:1,
VDDOV3:1,
VDDOV2:1,
VDDOV1:1,
SHVDD4:1,
SHVDD3:1,
SHVDD2:1,
SHVDD1:1
>> = Content,
#{
?MAP_FIELD(VDDOV4),
?MAP_FIELD(VDDOV3),
?MAP_FIELD(VDDOV2),
?MAP_FIELD(VDDOV1),
?MAP_FIELD(SHVDD4),
?MAP_FIELD(SHVDD3),
?MAP_FIELD(SHVDD2),
?MAP_FIELD(SHVDD1)
};
reg(decode, 'GlobalErr', Content) ->
<<
WDogErr:1,
LossGND:1,
ThrmShutd:1,
VDD_UVLO:1,
VDD_Warn:1,
VDD_Low:1,
V5_UVLO:1,
VINT_UV:1
>> = Content,
#{
?MAP_FIELD(WDogErr),
?MAP_FIELD(LossGND),
?MAP_FIELD(ThrmShutd),
?MAP_FIELD(VDD_UVLO),
?MAP_FIELD(VDD_Warn),
?MAP_FIELD(VDD_Low),
?MAP_FIELD(V5_UVLO),
?MAP_FIELD(VINT_UV)
};
reg(decode, 'Config1', Content) when is_binary(Content) ->
<<
LedCurrLim:1,
FLatchEn:1,
FilterLong:1,
FFilterEn:1,
FLEDStretch:2,
SLEDSet:1,
FLEDSet:1
>> = Content,
#{
?MAP_FIELD(LedCurrLim),
?MAP_FIELD(FLatchEn),
?MAP_FIELD(FilterLong),
?MAP_FIELD(FFilterEn),
?MAP_FIELD(FLEDStretch),
?MAP_FIELD(SLEDSet),
?MAP_FIELD(FLEDSet)
};
reg(encode, 'Config1', Content) when is_map(Content) ->
#{
'LedCurrLim' := LedCurrLim,
'FLatchEn' := FLatchEn,
'FilterLong' := FilterLong,
'FFilterEn' := FFilterEn,
'FLEDStretch' := FLEDStretch,
'SLEDSet' := SLEDSet,
'FLEDSet' := FLEDSet
} = Content,
<<
?BIN_FIELD(LedCurrLim):1,
?BIN_FIELD(FLatchEn):1,
?BIN_FIELD(FilterLong):1,
?BIN_FIELD(FFilterEn):1,
?BIN_FIELD(FLEDStretch):2,
?BIN_FIELD(SLEDSet):1,
?BIN_FIELD(FLEDSet):1
>>;
reg(decode, 'Config2', Content) when is_binary(Content) ->
<<
WDTo:2,
OWOffCs:2,
ShtVddThr:2,
SynchWDEn:1,
VDDOnThr:1
>> = Content,
#{
?MAP_FIELD(WDTo),
?MAP_FIELD(OWOffCs),
?MAP_FIELD(ShtVddThr),
?MAP_FIELD(SynchWDEn),
?MAP_FIELD(VDDOnThr)
};
reg(encode, 'Config2', Content) when is_map(Content) ->
#{
'WDTo' := WDTo,
'OWOffCs' := OWOffCs,
'ShtVddThr' := ShtVddThr,
'SynchWDEn' := SynchWDEn,
'VDDOnThr' := VDDOnThr
} = Content,
<<
?BIN_FIELD(WDTo):2,
?BIN_FIELD(OWOffCs):2,
?BIN_FIELD(ShtVddThr):2,
?BIN_FIELD(SynchWDEn):1,
?BIN_FIELD(VDDOnThr):1
>>;
reg(decode, 'ConfigDI', Content) when is_binary(Content) ->
<<
Typ2Di:1,
% Reserved
0:1,
VDDFaultDis:1,
VDDFaultSel:1,
AboveVDDProtEn:1,
OVLStretchEn:1,
OVLBlank:2
>> = Content,
#{
?MAP_FIELD(Typ2Di),
?MAP_FIELD(VDDFaultDis),
?MAP_FIELD(VDDFaultSel),
?MAP_FIELD(AboveVDDProtEn),
?MAP_FIELD(OVLStretchEn),
?MAP_FIELD(OVLBlank)
};
reg(encode, 'ConfigDI', Content) when is_map(Content) ->
#{
'Typ2Di' := Typ2Di,
'VDDFaultDis' := VDDFaultDis,
'VDDFaultSel' := VDDFaultSel,
'AboveVDDProtEn' := AboveVDDProtEn,
'OVLStretchEn' := OVLStretchEn,
'OVLBlank' := OVLBlank
} = Content,
<<
?BIN_FIELD(Typ2Di):1,
% Reserved
0:1,
?BIN_FIELD(VDDFaultDis):1,
?BIN_FIELD(VDDFaultSel):1,
?BIN_FIELD(AboveVDDProtEn):1,
?BIN_FIELD(OVLStretchEn):1,
?BIN_FIELD(OVLBlank):2
>>;
reg(decode, 'ConfigDO', Content) when is_binary(Content) ->
<<
DoMode4:2,
DoMode3:2,
DoMode2:2,
DoMode1:2
>> = Content,
#{
?MAP_FIELD(DoMode4),
?MAP_FIELD(DoMode3),
?MAP_FIELD(DoMode2),
?MAP_FIELD(DoMode1)
};
reg(encode, 'ConfigDO', Content) when is_map(Content) ->
#{
'DoMode4' := DoMode4,
'DoMode3' := DoMode3,
'DoMode2' := DoMode2,
'DoMode1' := DoMode1
} = Content,
<<
?BIN_FIELD(DoMode4):2,
?BIN_FIELD(DoMode3):2,
?BIN_FIELD(DoMode2):2,
?BIN_FIELD(DoMode1):2
>>;
reg(decode, 'CurrLim', Content) when is_binary(Content) ->
<<
CL4:2,
CL3:2,
CL2:2,
CL1:2
>> = Content,
#{
?MAP_FIELD(CL4),
?MAP_FIELD(CL3),
?MAP_FIELD(CL2),
?MAP_FIELD(CL1)
};
reg(encode, 'CurrLim', Content) when is_map(Content) ->
#{
'CL4' := CL4,
'CL3' := CL3,
'CL2' := CL2,
'CL1' := CL1
} = Content,
<<
?BIN_FIELD(CL4):2,
?BIN_FIELD(CL3):2,
?BIN_FIELD(CL2):2,
?BIN_FIELD(CL1):2
>>;
reg(decode, 'Mask', Content) when is_binary(Content) ->
<<
CommErrM:1,
SupplyErrM:1,
VddOKM:1,
ShtVddM:1,
AboveVDDM:1,
OWOffM:1,
CurrLimM:1,
OverLdM:1
>> = Content,
#{
?MAP_FIELD(CommErrM),
?MAP_FIELD(SupplyErrM),
?MAP_FIELD(VddOKM),
?MAP_FIELD(ShtVddM),
?MAP_FIELD(AboveVDDM),
?MAP_FIELD(OWOffM),
?MAP_FIELD(CurrLimM),
?MAP_FIELD(OverLdM)
};
reg(encode, 'Mask', Content) when is_map(Content) ->
#{
'CommErrM' := CommErrM,
'SupplyErrM' := SupplyErrM,
'VddOKM' := VddOKM,
'ShtVddM' := ShtVddM,
'AboveVDDM' := AboveVDDM,
'OWOffM' := OWOffM,
'CurrLimM' := CurrLimM,
'OverLdM' := OverLdM
} = Content,
<<
?BIN_FIELD(CommErrM):1,
?BIN_FIELD(SupplyErrM):1,
?BIN_FIELD(VddOKM):1,
?BIN_FIELD(ShtVddM):1,
?BIN_FIELD(AboveVDDM):1,
?BIN_FIELD(OWOffM):1,
?BIN_FIELD(CurrLimM):1,
?BIN_FIELD(OverLdM):1
>>;
reg(decode, _Reg, Content) ->
Content.
field(decode, 'FLEDStretch', Value) ->
pick(Value, {
false,
{millisecond, 1_000},
{millisecond, 2_000},
{millisecond, 3_000}
});
field(encode, 'FLEDStretch', false) ->
0;
field(encode, 'FLEDStretch', {millisecond, 1_000}) ->
1;
field(encode, 'FLEDStretch', {millisecond, 2_000}) ->
2;
field(encode, 'FLEDStretch', {millisecond, 3_000}) ->
3;
field(decode, 'WDTo', Value) ->
pick(Value, {
false,
{millisecond, 200},
{millisecond, 600},
{millisecond, 1_200}
});
field(encode, 'WDTo', false) ->
0;
field(encode, 'WDTo', {millisecond, 200}) ->
1;
field(encode, 'WDTo', {millisecond, 600}) ->
2;
field(encode, 'WDTo', {millisecond, 1_200}) ->
3;
field(decode, 'OWOffCs', Value) ->
{microampere, pick(Value, {60, 100, 300, 600})};
field(encode, 'OWOffCs', {microampere, 60}) ->
0;
field(encode, 'OWOffCs', {microampere, 100}) ->
1;
field(encode, 'OWOffCs', {microampere, 300}) ->
2;
field(encode, 'OWOffCs', {micro_apmere, 600}) ->
3;
field(decode, 'ShtVDDThr', Value) ->
{volt, pick(Value, {9, 10, 12, 14})};
field(encode, 'ShtVDDThr', {volt, 9}) ->
0;
field(encode, 'ShtVDDThr', {volt, 10}) ->
1;
field(encode, 'ShtVDDThr', {volt, 12}) ->
2;
field(encode, 'ShtVDDThr', {volt, 14}) ->
3;
field(decode, 'Typ2Di', 0) ->
type_1_3;
field(decode, 'Typ2Di', 1) ->
type_2;
field(encode, 'Typ2Di', type_1_3) ->
0;
field(encode, 'Typ2Di', type_2) ->
1;
field(decode, 'OVLBlack', Value) ->
pick(Value, {
false,
{millisecond, 8},
{millisecond, 50},
{millisecond, 300}
});
field(encode, 'OVLBlack', false) ->
0;
field(encode, 'OVLBlack', {millisecond, 8}) ->
1;
field(encode, 'OVLBlack', {millisecond, 50}) ->
2;
field(encode, 'OVLBlack', {millisecond, 300}) ->
3;
field(decode, Reg, Value) when
Reg == 'DoMode4'; Reg == 'DoMode3'; Reg == 'DoMode2'; Reg == 'DoMode1'
->
pick(Value, {
high_side,
high_side_2x,
active_clamp_push_pull,
simple_push_pull
});
field(encode, Reg, high_side) when
Reg == 'DoMode4'; Reg == 'DoMode3'; Reg == 'DoMode2'; Reg == 'DoMode1'
->
16#00;
field(encode, Reg, high_side_2x) when
Reg == 'DoMode4'; Reg == 'DoMode3'; Reg == 'DoMode2'; Reg == 'DoMode1'
->
16#01;
field(encode, Reg, active_clamp_push_pull) when
Reg == 'DoMode4'; Reg == 'DoMode3'; Reg == 'DoMode2'; Reg == 'DoMode1'
->
16#10;
field(encode, Reg, simple_push_pull) when
Reg == 'DoMode4'; Reg == 'DoMode3'; Reg == 'DoMode2'; Reg == 'DoMode1'
->
16#11;
field(decode, Reg, Value) when
Reg == 'CL4'; Reg == 'CL3'; Reg == 'CL2'; Reg == 'CL1'
->
pick(
Value,
{
#{current_limit => {milliampere, 600}, inrush => {millisecond, 20}},
#{current_limit => {milliampere, 130}, inrush => {millisecond, 50}},
#{current_limit => {milliampere, 300}, inrush => {millisecond, 40}},
#{current_limit => {milliampere, 1200}, inrush => {millisecond, 10}}
}
);
field(encode, Reg, #{
current_limit := {milliampere, 600}, inrush := {millisecond, 20}
}) when
Reg == 'CL4'; Reg == 'CL3'; Reg == 'CL2'; Reg == 'CL1'
->
16#00;
field(encode, Reg, #{
current_limit := {milliampere, 130}, inrush := {millisecond, 50}
}) when
Reg == 'CL4'; Reg == 'CL3'; Reg == 'CL2'; Reg == 'CL1'
->
16#01;
field(encode, Reg, #{
current_limit := {milliampere, 300}, inrush := {millisecond, 40}
}) when
Reg == 'CL4'; Reg == 'CL3'; Reg == 'CL2'; Reg == 'CL1'
->
16#10;
field(encode, Reg, #{
current_limit := {milliampere, 1200}, inrush := {millisecond, 10}
}) when
Reg == 'CL4'; Reg == 'CL3'; Reg == 'CL2'; Reg == 'CL1'
->
16#11;
field(decode, _Name, 0) ->
false;
field(decode, _Name, 1) ->
true;
field(encode, _Name, false) ->
0;
field(encode, _Name, true) ->
1;
field(encode, Name, Value) ->
error({invalid_field_value, Name, Value}).
boolean(0) -> false;
boolean(1) -> true.
pick(Index, Values) -> element(Index + 1, Values).