-
Play
11/02/2020 at 06:51 • 0 commentsInstruction to retrieve data from the tape:
- select baudrate and serial mode on Mercury baseboard switches 7 - 2, for example 000110XX will set 300bps, 8 bits, odd parity, 1 stop bits (Note: 1200 bps will work with high quality recorder/tape and finely tuned volume and tone. 600 and 300 is much less demanding)
- use same setting on your Terminal software on the PC (for example TeraTerm)
- establish all wire connections (AUDIO_OUT > MIC, AUDIO IN < SPKR, PMOD USB to PC USB)
- rewind the tape to required position (as marked in your catalog)
- press PLAY
- watch data from the tape on the terminal console - if these were valid ASCII, it will be text, otherwise bytes beyond ASCII range will be printed according to current character set mapping (and the BELL character 0x07 will sound too!) HEX file or similar format can encode binary while still be human readable and are good candidates to use as format due to wide support and basic checksum integrity checking.
- press STOP when UART blinking stops (otherwise junk beyond last good written position can be picked up)
Principle of operation:
(refer to https://github.com/zpekic/Sys_cas/blob/main/Mercury/tapeuart.vhd )
Remember, recording was done using simple "binary frequency shift keying" with following parameters:
bps mark ("1") frequency (Hz) space ("0") frequency (Hz) fmark
----
bpsfspace
----
bps300 4800 1200 8 4 600 9600 4800 8 4 1200 19200 9600 8 4 Result is a distorted waveform on the tape, but which still holds the primary mark and space frequencies. The task is to figure out those frequencies in real time, and based on which one is detected, output 0 or 1 which will be the UART output (TXD).
This is of course a classic FFT or Goertzel algorithm problem, but neither of those is used here. The brute force of FPGA and high bandwidth of modern A/D adapters is used instead, with some simple hacky tricks. Here are the steps:
1. Connect the available A/D converter and drive it to sample tape data
The Mercury baseboard uses the A/D converter described here. I used the driver code provided as a sample. The clock is 25MHz, and the sampling frequency 0.75MHz - both are clearly overkill for the purpose, but they are easily obtainable by simple clock dividers. The channel is either of the audio left or right.
-- Mercury ADC component ADC : entity work.MercuryADC port map( clock => adc_clk, trigger => adc_trigger, diffn => '0', channel => "000", -- channel 0 = left audio Dout => adc_dout, OutVal => adc_done, adc_miso => ADC_MISO, adc_mosi => ADC_MOSI, adc_cs => ADC_CSN, adc_clk => ADC_SCK );
2. Process each A/D conversion
adc_done signal will pulse when the conversion is ready, at which point the data reading (strength of the signal from tape in 10-bit resolution should be analyzed) is presented at Dout port. We don't really care about the value, but the point where the value is very close to zero, hoping that moment indicates the period of the waveform that captures the main recorded mark/space frequency.
Picking this threshold value can be tricky and depends on the characteristics of the signal path between audio input and A/D converter. In this case hex value 0x12 is selected, but to make it easier to figure it out, the min and max value (wave amplitude) is captured and can be displayed through debug port.
When it is detected that previous value was close to zero but is now greater then 0x12 then 1 output is generated, otherwise 0. Note that there is a certain level of hysteresis in this as the previous value of f_in_audio is taken into account.
-- ADC sampling process on_adc_done : process (adc_done, f_in_audio) begin if (rising_edge(adc_done)) then if (f_in_audio = '0') then if (unsigned(adc_dout) > "00" & X"12") then -- 24 f_in_audio <= '1'; end if; else if (unsigned(adc_dout) < "00" & X"12") then -- 24 f_in_audio <= '0'; end if; end if; if (unsigned(adc_dout) > max) then max <= unsigned(adc_dout); end if; if (unsigned(adc_dout) < min) then min <= unsigned(adc_dout); end if; end if; end process;
The result here is a binary signal f_in_audio, which has the maximum frequency determined by sample rate of A/D converter (roughly 75kHz in this case == 750kHz trigger frequency / 10 or so clock cycles needed per 1 conversion). This signal will be mostly 1, but will have streams of 0s where the analog signal "crossed zero".
3. Establishing time interval baselines
If the tape had recorded all 1 at 600bps, meaning mark frequency 9600, one would expect these sequences of 0 in f_in_audio to occur every 1/9600 s, at about 10.4 ms intervals, and if 0 was recorded at 20.8ms. So figuring out if 0 or 1 was recorded becomes a problem of time interval measurement.
Both mark and space frequencies are available in the component, as they were needed to record onto the tape. They can now be used to establish the time interval against some convenient baseline, for example A/D trigger frequency ("tick" value):
on_freq_space: process(freq_space, tick, prev0) begin if (rising_edge(freq_space)) then limit0 <= tick - prev0; prev0 <= tick; end if; end process; on_freq_mark: process(freq_mark, tick, prev1) begin if (rising_edge(freq_mark)) then limit1 <= tick - prev1; prev1 <= tick; end if; end process; on_adc_samplefreq: process(adc_samplefreq, adc_done) begin if (adc_done = '1') then adc_trigger <= '0'; else if (rising_edge(adc_samplefreq)) then adc_trigger <= not adc_done; tick <= tick + 1; end if; end if; end process;
This will result in registers limit0 and limit1 to have two "stopwatch" values - their absolute value is not important, but at all times limit1 ~= 2 * limit0.4. Measure current input signal intervals
Similar to the baselines, we can "stopwatch" the incoming f_in (== f_in_audio) signal against the same "tick" frequency:
on_f_in: process(f_in, tick, prev) begin if (rising_edge(f_in)) then delta <= tick - prev; prev <= tick; end if; end process;
the value in register "delta" measures the time since last zeros were detected in the input stream, so that now we have 3 registers with 3 values - 2 for known frequencies, and 1 for unknown one.
5. Decide if input time interval is closer to either known ones
At this point, we just have to decide if measured value is closer to either and use them as inputs to R/S flip-flop (again, hysteresis as simplest way of filtering the noise)
detect0 <= '1' when (delta > (limit0 - 15)) else '0'; detect1 <= '1' when (delta < (limit1 + 15)) else '0'; ntxd <= not (detect0 or txd); txd <= not (detect1 or ntxd);
The value 15 is just some "slack" to allow for various errors in sampling, tape speed etc.
More sophisticated circuit could also detect neither frequency ("no data") state and report to higher level design component if needed, to drive something like a DCD signal.
6. Output the UART signal
serout <= not (txd);
-
Record
11/02/2020 at 06:50 • 0 commentsInstruction to record data onto the tape:
- select baudrate and serial mode on Mercury baseboard switches 7 - 2, for example 001111XX will set 600bps, 8 bits, no parity, 2 stop bits (Note: 1200 bps will work with high quality recorder/tape and finely tuned volume and tone. 600 and 300 is much less demanding)
- use same setting on your Terminal software on the PC (for example TeraTerm)
- establish all wire connections (AUDIO_OUT > MIC, AUDIO IN < SPKR, PMOD USB to PC USB)
- rewind the tape to required position (and mark to catalog position)
- press RECORD
- send file (or simply type) in terminal console window - all bytes outgoing will be recorded onto the tape. In order to later see the recorded data, the simplest way is to send text. For example any HEX file or similar format can encode binary while still be human readable.
- press STOP when UART blinking stops (and mark to catalog position)
Principle of operation:
(refer to https://github.com/zpekic/Sys_cas/blob/main/Mercury/tapeuart.vhd )
The recording is simple "binary frequency shift keying" with following parameters:
bps mark ("1") frequency (Hz) space ("0") frequency (Hz) fmark
----
bpsfspace
----
bps300 4800 1200 8 4 600 9600 4800 8 4 1200 19200 9600 8 4 All of this is accomplished with simple multiplexer that transfers one of two incoming frequencies above to audio_left and audio_right pins (no stereo signal, but mono replicated on both channels if tape recorder uses only one):
-- output path f_out <= freq_space when (serin = '0') else freq_mark; -- always output to audio audio_left <= f_out; audio_right <= f_out;
Given that frequencies are square waves which have infinite number of harmonics (sine waves), first of which is of the base frequency, and others rapidly attenuating odd multiples, and that these cannot be all stored on the tape, the signal that is actually stored is a distorted square/sine wave (see screenshot from oscilloscope). The D/A conversion happens on the path from FPGA output pin towards the tape. For other FPGA, a real D/A converter could be used which could easily generate very close approximation of a smooth sine wave of given mark/space frequency. But in the end it doesn't matter, as long as the receiver side can pick up the main harmonic component somehow from the analog signal recorded on the tape.It is important to note that regardless of baudrate, each "1" will always result in 8 cycles and each "0" of 4 cycles within the bit time. This big 2/1 difference allows differentiation between 1 and 0 on the receiving side, but presents a problem - mark frequency of 19.2KHz is close to the upper reliable frequency reproduction limit of a cassette tape.