Elektor_Mag_-_January-February_2021

([email protected]) #1
lektor January & February 2021 25

SIN(X) is in the range of -1 to 1, if we want the
32-bit data to have a full-scale level the value
of SIN(X) must multiplied by (2^32)/2 - 1:

SINX = SINX * U
Where U = 2147483647

The conversion to Long can be done in just
one statement, a Long variable gets the value
of a Double variable. Now, the result of the
calculation is available as a signed 32-bit
value:

SINXlong = SINX

Splitting up this value into four bytes is just
a matter of shifting the bits of SINXlong and
store them in four variables of type Byte. A
subroutine called SampleX does all these
calculations for any value of X. But the calcula-
tion is only really accurate for X = -π/2 through
+π/2. So, first the sine wave is calculated from
-π/2 through +π/2, taking 97 samples (four
bytes per sample). The rest of the array is
completed by mirroring the elements of the
first array per group of four bytes, taking 95
samples. Sample 193 is the same as the first
of the array (four bytes). The Do-Loop restarts
there.

It would have been possible to create an array
with only samples of the sine wave from -π/2
through +π/2 and select the correct array
elements in the main loop to complete the
data for a full sine wave period at port D. But
instead of a sine wave a more complex signal
that is not symmetrical can be interesting for
other purposes. And if a complete array is
used, the main loop is easier to read.

With the aid of the shift register, the micro-
controller has eight clock cycles to process
each byte.

In BASCOM-AVR the instruction PORTD =
A(n), where A(n) is an element of a byte
array, only takes five clock cycles when used
in Do-Loop, surprisingly fast. To make each
byte take eight clock cycles three NOPs must

I2S signal generator, but also to make a
perfect 1 kHz sine wave test signal with 32-bit
accuracy. First the BASCOM-AVR instruc-
tion for SIN(x) was used in the calculations,
but that isn’t accurate enough, as following
examples show:

DIM Pi,A,X As Single
Pi = 3.1415926535897932384626433
X = Pi / 2
A = SIN(X)
Print "Sin(Pi/2) = " ; A

This piece of code results in:
Sin(Pi/2) = 0.99999332, whereas the result
should be exactly 1. And for X = Pi/6 the
result is 0.499993796, but should be exactly
0.5. So, for calculating an extremely accurate
sine wave, this is just not good enough. The
only option is to calculate the sine wave using
the Taylor polynomial for SIN(X) with suffi-
cient terms:

SIN(X) = X-(X^3)/3!+(X^5)/5!-
(X^7)/7!+(X^9)/9!-(X^11)/11!+(X^13)/13!
-(X^15)/15!

In BASCOM-AVR calculations can only be
performed on two operands; therefore, the
polynomial is calculated in many lines of
source code, as can be seen in the source
code of this project.

To speed things up, the factorial calculations
are avoided and constants are used instead
(F3 = 6 ... F15 = 1307674368000). All variables
in the calculations are of type Double, signed
64-bit binary numbers (8 bytes, 5 x 10^-324
to 3.4 x 10^308). With the Taylor polynomial
we get the following, more accurate results:

Sin(Pi/6) = 500E-3
Sin(Pi/2) = 999.999999993977E-3

To split up the result of the calculation
of SIN(X) into four bytes it must first be
converted to a variable of type Long, signed
32-bit binary numbers (-2147483648 to
2147483647). The result of the calculation of

The most significant bit of the serial data of
the I2S bus is located one clock period after
the change of word select. An extra D-type
flip-flop is needed. Selected is a 74HC74 (IC5),
a dual D-type flip-flop with set and reset and
positive edge-trigger. By doing so, the signal
for SDATA is delayed. To compensate for this
the inverted clock signal of IC4C is inverted
again by IC4D to make the serial clock line
up with the serial data.


Adjusting the output level
Instead of just producing a 1 kHz sine wave
with a fixed level, four inputs of port C (with
internal pull-ups) are connected to a hexadec-
imal coded rotary switch (SD-1010) to adjust
the output level. This feature can be used
to check linearity of the DAC. A four-way
DIP-switch could have been used instead
but changing levels is easier with the rotary
switch, much like an analog volume control.


There are ‘real code’ and ‘complementary
code’ type of rotary coded switches. The ‘real
code’ version is used in this project, but other
types can be used if the software is changed.
In position ‘0’ all four switches are open. To
change the output level for the digital audio
signal, the microcontroller must be reset to
recalculate all values in the sample array, hence
the presence of button S2 that must be pressed
to reset the microcontroller when the output
level is changed. A Select-Case statement is
used to set the scale factor U (type Double) to
the correct value for each level-setting.


The correct values for the scale factors for
each output level are defined as constant
values in the program not only to avoid extra
calculations, but also because calculations
using BASCOM’s LOG function in the program
are not accurate enough.


Calculating the sine wave
As mentioned earlier, the program calculates
the sine wave sample table immediately after
power up or reset, taking into account the
level setting of rotary switch S1. The purpose
of this project was not only to build “some”


[1] Audio DAC for Raspberry Pi: http://www.elektormagazine.com/labs/audio-dac-for-rpi-networked-audio-player-using-volumio
[2] Downloads gerber files and software: http://www.elektormagazine.com/200253-01
[3] This project’s Elektor Labs page: http://www.elektormagazine.com/labs/32-bit-i2s-sine-wave-generator-200253

WEB LINKS
Free download pdf