Disclaimer
I've created this post not only for the code review, but rather because I think it would be helpful for the users of the CAEN V2718 module.
Problem description
I have a VME board (V2718) which may be used as a square form generator. To set the frequency and duty cycle user must specify three parameters :
The period N (in terms of the number of time-steps) : $$ 1 \leq N \leq 255 \tag{1a}\label{1a} $$
The duty cycle D (in terms of the number of time-steps) : $$ 1 \leq D \leq N-1 \tag{1b}\label{1b} $$
The time-step d (used to express the period and duty cycle) which may be either of the following : $$ d_1 = 25n\text{s},\, d_2 = 1600n\text{s},\, d_3 = 410\mu\text{s},\, d_4 = 104m\text{s} $$
For example, to generate the 100 Hz square (50% d.c.) one can choose the following configuration: $$ d = d_3,\,N = 24,\, D=N/2\quad \Rightarrow\quad T = d N\approx 0.01\text{ s} $$
Usually the primary quantities are frequency and duty cycle --- not period and duty cycle. And, of course, people don't want to specify some strange parameters like N, D, and d --- they want frequencies and duties! So the problem is to write a function that chooses the raw parameters to generate the desirable square waveform.
Algorithm
To find the closest possible frequency to the desirable frequency f_set we need to find the closest possible period corresponding to the f_set. Given f_set the algorithm is the following
- Find two T- and T+ which can be obtained by the module such that : $$ T_{set} \in [T_{-}, T_{+}) $$
- If either of T's is absent then let the absent one be equal to the present one
- If both of T's are absent then return
false
Calculate the errors : $$ e_{+} := |f_{\text{set}} - f_{+}|,\quad e_{-} := |f_{\text{set}} - f_{-}| $$
Choose the corresponding T based on the errors' values.
The picture shows the idea (although see the Useful notes):
Useful notes:
- N cannot be 1. See(\ref{1b})
- T+ and T- can be expressed by using different time-steps (in other words, the red triangles on the picture may be the ticks of the different time scales)
Code
bool V2718Pulser::SetSquare( uint32_t freq, uint8_t duty )
{
struct { double expo; double num; CVTimeUnits unit; } ss[4] = { { 1000000000., 25., cvUnit25ns },
{ 10000000., 16., cvUnit1600ns },
{ 100000., 41., cvUnit410us },
{ 1000., 104., cvUnit104ms } };
const uint32_t MAX_PERIOD = 0xff; // MAX_N
const uint32_t MIN_PERIOD = 0x02; // MIN_N
struct { uint32_t n; int u; } sPlus, sMinus; // T+, T-
uint32_t n0 = 0;// The reference point
if( freq > 0 )
{
for( int i = 0; i < 4; ++i )
{
n0 = std::floor( ss[i].expo / ss[i].num / freq );
if( (n0 >= MIN_PERIOD) && (n0 < MAX_PERIOD) )
{
// T_set falls between the ticks of the same time-scale
sMinus = { n0, i };
sPlus = { n0 + 1, i };
break;
}
else if( n0 == 1 )
{
// Bad value because of the duty cycle --- should be changed
if( i > 0 )
{
sMinus = { MAX_PERIOD, i - 1 }; // the last tick of the finer time-scale
sPlus = { n0 + 1, i }; // use the current time-scale, but the next tick
}
else
{
// There is no finer time-scale
sPlus = sMinus = { n0 + 1, i };
}
break;
}
else if( n0 == MAX_PERIOD )
{
if( i < 3 )
{
sMinus = { n0, i };
sPlus = { MIN_PERIOD, i + 1 }; // use the 2-th tick of the coarser time-scale
}
else
{
// The is no coarser time-scale
sPlus = sMinus = { n0, i };
}
break;
}
}
if( n0 != 0 )
{
double errorPlus = std::fabs( freq - ss[sPlus.u].expo / ss[sPlus.u].num / sPlus.n );
double errorMinus = std::fabs( freq - ss[sMinus.u].expo / ss[sMinus.u].num / sMinus.n );
if( errorPlus < errorMinus )
{
n0 = sPlus.n;
fTimeUnit = ss[sPlus.u].unit; // d
}
else
{
n0 = sMinus.n;
fTimeUnit = ss[sMinus.u].unit; // d
}
duty = ((duty > 0) ? ((duty < 100) ? duty : 99) : 1);
uint32_t width = n0 * duty / 100;// < MAX_PERIOD
width = (width > 0) ? width : 1;
if( width < n0 )
{
fPeriod = n0; // N
fWidth = width; // D
return true;
}
}
}
return false;
}
Result
I created the "frequency error graph" of this module for the frequencies from 1 to 10000000 Hz:
Bad frequencies
You can see that there are frequencies with a very big error (~30%). One of such frequency is 1836 Hz. Indeed,
$$ T_{\text{bad}} = \frac{1}{1836} = 0.000544662 = 544.662 \times 10^{-6}\text{s} = 544.662 \mu\text{s} $$
which means the closest times are $$ T_{-} = 255 \times 1600n\text{s} = 408\mu\text{s},\quad T_{+} = 2 \times 410\mu\text{s} = 820\mu\text{s} $$
so
$$ f_{-} = 2450.98\text{ Hz},\quad f_{+} = 1219.51\text{ Hz} $$
Very bad frequencies
There are 2 frequencies (2440, 2441) that even "don't exist" (the function returns false
), i.e. neither T+ nor T- were found. Indeed,
$$ T_{\text{very bad}} = \frac{1}{2440} = 0.000409836 = 409.836\mu\text{s} $$
and
$$ 255 \times 1600n\text{s} < T_{\text{very bad}} < 410\mu\text{s} $$
so
$$ \left\lfloor \frac{T_{\text{very bad}}}{d_{i}} \right\rfloor \text{is either 0 or } >255 $$
and the algorithm fails to find T's.