RadioBanter

RadioBanter (https://www.radiobanter.com/)
-   Digital (https://www.radiobanter.com/digital/)
-   -   Bell 202 modulation (AFSK1200) (https://www.radiobanter.com/digital/7788-bell-202-modulation-afsk1200.html)

R C July 5th 03 07:21 AM

Bell 202 modulation (AFSK1200)
 
I'm working on a telemetry board and wish to include software FSK1200
(MX-614's are getting harder to find). The official Bell 202 spec is
out of print and hard to find as well, so I pieced this together from
what I can find. Multimon won't decode the output, so I'm likely doing
something wrong. This is supposed to be (once it works) my reference
implementation before I port it to my telemetry board, so I'm trying
to keep it as simple as possible.

The program outputs 22050 hz 8-bit signed data to stdout;
gcc fsk.c -o fsk -lm; ./fsk test; sox -s -b -r 22050 test -w
test.wav

Phase is coherent, and there are peaks at 1200 and 2200 in the
spectrum analysis.

Any help or suggestions would be appreciated.

Robert Cicconetti
KG4MVB

-- begin fsk.c --
#include math.h
#include stdio.h

// $#@$#@
//#define BAUDRATE 1200
// octet rate is 150 octet / sec. (1200 / 8). BIT period is
// .000666, or 1/1500 (10 bits per symbol, including start and stop).
// BAUDRATE is therefor used improperly here; fixme.
#define BAUDRATE 1500
#define MARK 1200
#define SPACE 2200

// For convenience when working with multimon
#define SAMPLERATE 22050

double sendbyte(char a, double angle);

int main (void)
{
// 8 bit output; encoder DAC is likely to be only 3 or 4 bit
short outbyte;
int i;
double angle = 0;
char test[]= "fm KG4MVB-0 to FFFFFF-0 SABM+";

for (i = 0 ; i 5000; i++) putchar(0);
//PREAMBLE
for (i = 0 ; i 24; i++) sendbyte(0, angle);

for (i = 0 ; i 29 ; i++)
{
angle = sendbyte(test[i], angle);
}

for (i = 0 ; i 2; i++) sendbyte(0, angle);
for (i = 0 ; i 50000; i++) putchar(0);
return 0;
}

double sendbyte(char a, double angle)
{
// LSB first. Start bit MARK end bit SPACE
// MARK is 1, SPACE is 0
//
double phase_offset = 0;
int freq, j = 0, i;
char bit;

for (i = 0; i 10; i++)
{
if (i == 0)
bit = 1;
else if (i == 9)
bit = 0;
else
bit =( a (i-1)) & 0x1;

if (bit == 1)
freq = MARK;
else
freq = SPACE;

phase_offset = angle -
(double)2*M_PI*freq*((double)(j-1)/SAMPLERATE);
while (j ((i+1)*SAMPLERATE)/BAUDRATE )
{
angle =
(double)2*M_PI*freq*((double)j/SAMPLERATE) + phase_offset;
putchar((short)(0.5*sin(angle)*128));
j++;
}
}
return angle;
}

R C July 5th 03 05:10 PM

Dana Myers K6JQ wrote in message ws.com...
R C wrote:
I'm working on a telemetry board and wish to include software FSK1200
(MX-614's are getting harder to find). The official Bell 202 spec is
out of print and hard to find as well, so I pieced this together from
what I can find. Multimon won't decode the output, so I'm likely doing
something wrong. This is supposed to be (once it works) my reference
implementation before I port it to my telemetry board, so I'm trying
to keep it as simple as possible.


Any help or suggestions would be appreciated.


First - is multimon expecting a properly formatted AX.25 frame?
(This code is clearly not creating an HDLC-encoded, AX.25-formatted
frame). It looks like you're sending conventional async-encoded
ASCII. You need to:


That would explain it. I hadn't planned on using AX-25 (TX is the
audio subcarrier of an FM ATV camera; no point in packetizing it since
no digipeaters would be able to hear it, and unit should never be out
of reception range in any case). I had planned on adding framing,
checksums and possibly FEC later.

I was also trying to keep it as simple as possible initially; encode
raw octets and make sure I can decode them.

It may be worth adding the HDLC/ax.25; initial basestation will likely
be a laptop running soundmodem.

Also - I'd avoid the use of transcendental functions (sin()) in the
synthesizer loop ; I'd create a lookup table and index it via an
integer phase accumulator. Off the top of my head, I'd guess a
16-bit phase accumulator and a 256-element sine array would be
plenty. You'd need to precalculate the phase increments for
1200 and 2200 Hz, likely as constants.


That is step 2 :) The target CPU will most likely be a Mega8535 AVR,
so conversion to integer is definately indicated. :)

Thanks,
R C
KG4MVB

R C July 5th 03 05:10 PM

Dana Myers K6JQ wrote in message ws.com...
R C wrote:
I'm working on a telemetry board and wish to include software FSK1200
(MX-614's are getting harder to find). The official Bell 202 spec is
out of print and hard to find as well, so I pieced this together from
what I can find. Multimon won't decode the output, so I'm likely doing
something wrong. This is supposed to be (once it works) my reference
implementation before I port it to my telemetry board, so I'm trying
to keep it as simple as possible.


Any help or suggestions would be appreciated.


First - is multimon expecting a properly formatted AX.25 frame?
(This code is clearly not creating an HDLC-encoded, AX.25-formatted
frame). It looks like you're sending conventional async-encoded
ASCII. You need to:


That would explain it. I hadn't planned on using AX-25 (TX is the
audio subcarrier of an FM ATV camera; no point in packetizing it since
no digipeaters would be able to hear it, and unit should never be out
of reception range in any case). I had planned on adding framing,
checksums and possibly FEC later.

I was also trying to keep it as simple as possible initially; encode
raw octets and make sure I can decode them.

It may be worth adding the HDLC/ax.25; initial basestation will likely
be a laptop running soundmodem.

Also - I'd avoid the use of transcendental functions (sin()) in the
synthesizer loop ; I'd create a lookup table and index it via an
integer phase accumulator. Off the top of my head, I'd guess a
16-bit phase accumulator and a 256-element sine array would be
plenty. You'd need to precalculate the phase increments for
1200 and 2200 Hz, likely as constants.


That is step 2 :) The target CPU will most likely be a Mega8535 AVR,
so conversion to integer is definately indicated. :)

Thanks,
R C
KG4MVB

Dana Myers K6JQ July 6th 03 01:52 AM

R C wrote:

Dana Myers K6JQ wrote in message ws.com...



It may be worth adding the HDLC/ax.25; initial basestation will likely
be a laptop running soundmodem.


In which case, AX.25 isn't that hard at all to implement,
HDLC is fairly trivial, and the checksum code can be found
pretty readily (I have a Java port of something I sponged out
of an old RFC if you're looking for sample code).

You'll likely need it all for soundmodem recognize your frames, but
you don't need to do full-on connected-mode AX.25, UI datagrams are
likely enough.

Also - I'd avoid the use of transcendental functions (sin()) in the
synthesizer loop ; I'd create a lookup table and index it via an
integer phase accumulator. Off the top of my head, I'd guess a
16-bit phase accumulator and a 256-element sine array would be
plenty. You'd need to precalculate the phase increments for
1200 and 2200 Hz, likely as constants.



That is step 2 :) The target CPU will most likely be a Mega8535 AVR,
so conversion to integer is definately indicated. :)


Heh. You're tempting to cobble up the DDS code for giggles and
flash it into an atMega16 I have sitting here ;-)

Dana


Dana Myers K6JQ July 6th 03 01:52 AM

R C wrote:

Dana Myers K6JQ wrote in message ws.com...



It may be worth adding the HDLC/ax.25; initial basestation will likely
be a laptop running soundmodem.


In which case, AX.25 isn't that hard at all to implement,
HDLC is fairly trivial, and the checksum code can be found
pretty readily (I have a Java port of something I sponged out
of an old RFC if you're looking for sample code).

You'll likely need it all for soundmodem recognize your frames, but
you don't need to do full-on connected-mode AX.25, UI datagrams are
likely enough.

Also - I'd avoid the use of transcendental functions (sin()) in the
synthesizer loop ; I'd create a lookup table and index it via an
integer phase accumulator. Off the top of my head, I'd guess a
16-bit phase accumulator and a 256-element sine array would be
plenty. You'd need to precalculate the phase increments for
1200 and 2200 Hz, likely as constants.



That is step 2 :) The target CPU will most likely be a Mega8535 AVR,
so conversion to integer is definately indicated. :)


Heh. You're tempting to cobble up the DDS code for giggles and
flash it into an atMega16 I have sitting here ;-)

Dana


Paul Keinanen July 6th 03 10:58 PM

On 4 Jul 2003 23:21:05 -0700, (R C) wrote:

I'm working on a telemetry board and wish to include software FSK1200
(MX-614's are getting harder to find). The official Bell 202 spec is
out of print and hard to find as well, so I pieced this together from
what I can find. Multimon won't decode the output, so I'm likely doing
something wrong.


What is this Multimon thing expecting ?

Is it expecting ordinary asynchronous characters ?

// LSB first. Start bit MARK end bit SPACE
// MARK is 1, SPACE is 0


for (i = 0; i 10; i++)
{
if (i == 0)
bit = 1;
else if (i == 9)
bit = 0;
else
bit =( a (i-1)) & 0x1;


In that case I assume you have mixed the polarity of the start and
stop bits.

In ordinary RS-232 asynchronous communication the start bit is "0"
SPACE, interrupting the idle MARK state, followed by the data bits
with LSB sent first. The stop bit is "1" or MARK, which then transfers
to a MARK idle state if no more characters are to be transmitted.

If that Multimon thing is expecting AX.25, don't forget to add the bit
stuffing in the last stage before transmission.

Paul OH3LWR



Paul Keinanen July 6th 03 10:58 PM

On 4 Jul 2003 23:21:05 -0700, (R C) wrote:

I'm working on a telemetry board and wish to include software FSK1200
(MX-614's are getting harder to find). The official Bell 202 spec is
out of print and hard to find as well, so I pieced this together from
what I can find. Multimon won't decode the output, so I'm likely doing
something wrong.


What is this Multimon thing expecting ?

Is it expecting ordinary asynchronous characters ?

// LSB first. Start bit MARK end bit SPACE
// MARK is 1, SPACE is 0


for (i = 0; i 10; i++)
{
if (i == 0)
bit = 1;
else if (i == 9)
bit = 0;
else
bit =( a (i-1)) & 0x1;


In that case I assume you have mixed the polarity of the start and
stop bits.

In ordinary RS-232 asynchronous communication the start bit is "0"
SPACE, interrupting the idle MARK state, followed by the data bits
with LSB sent first. The stop bit is "1" or MARK, which then transfers
to a MARK idle state if no more characters are to be transmitted.

If that Multimon thing is expecting AX.25, don't forget to add the bit
stuffing in the last stage before transmission.

Paul OH3LWR



R C July 7th 03 02:36 AM

Dana Myers K6JQ wrote in message ws.com...
R C wrote:
That is step 2 :) The target CPU will most likely be a Mega8535 AVR,
so conversion to integer is definately indicated. :)


Heh. You're tempting to cobble up the DDS code for giggles and
flash it into an atMega16 I have sitting here ;-)


Hmm.. shot one, for GCC, no ASM. Nominally targetted for 8515 at 8
mhz, easy
to redo.

Not quite finished implementing, and definately not tested, but
framework should be there.

R C
KG4MVB

-- begin fsk2.c --

#include avr/io.h
#include avr/pgmspace.h


// Generated via sine.c. Theory suggests you can use reflexive nature
of sine.
// but we'll keep it simple. Besides, we have 8K of flash.

// For avr use prog_uchar?
prog_uchar s_table[] = [
0x80,0x83,0x86,0x89,0x8C,0x90,0x93,0x96,0x99,0x9C, 0x9F,0xA2,0xA5,0xA8,0xAB,0xAE,
0xB1,0xB3,0xB6,0xB9,0xBC,0xBF,0xC1,0xC4,0xC7,0xC9, 0xCC,0xCE,0xD1,0xD3,0xD5,0xD8,
0xDA,0xDC,0xDE,0xE0,0xE2,0xE4,0xE6,0xE8,0xEA,0xEB, 0xED,0xEF,0xF0,0xF1,0xF3,0xF4,
0xF5,0xF6,0xF8,0xF9,0xFA,0xFA,0xFB,0xFC,0xFD,0xFD, 0xFE,0xFE,0xFE,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFE,0xFE,0xFE,0xFD,0xFD,0xFC, 0xFB,0xFA,0xFA,0xF9,0xF8,0xF6,
0xF5,0xF4,0xF3,0xF1,0xF0,0xEF,0xED,0xEB,0xEA,0xE8, 0xE6,0xE4,0xE2,0xE0,0xDE,0xDC,
0xDA,0xD8,0xD5,0xD3,0xD1,0xCE,0xCC,0xC9,0xC7,0xC4, 0xC1,0xBF,0xBC,0xB9,0xB6,0xB3,
0xB1,0xAE,0xAB,0xA8,0xA5,0xA2,0x9F,0x9C,0x99,0x96, 0x93,0x90,0x8C,0x89,0x86,0x83,
0x80,0x7D,0x7A,0x77,0x74,0x70,0x6D,0x6A,0x67,0x64, 0x61,0x5E,0x5B,0x58,0x55,0x52,
0x4F,0x4D,0x4A,0x47,0x44,0x41,0x3F,0x3C,0x39,0x37, 0x34,0x32,0x2F,0x2D,0x2B,0x28,
0x26,0x24,0x22,0x20,0x1E,0x1C,0x1A,0x18,0x16,0x15, 0x13,0x11,0x10,0x0F,0x0D,0x0C,
0x0B,0x0A,0x08,0x07,0x06,0x06,0x05,0x04,0x03,0x03, 0x02,0x02,0x02,0x01,0x01,0x01,
0x01,0x01,0x01,0x01,0x02,0x02,0x02,0x03,0x03,0x04, 0x05,0x06,0x06,0x07,0x08,0x0A,
0x0B,0x0C,0x0D,0x0F,0x10,0x11,0x13,0x15,0x16,0x18, 0x1A,0x1C,0x1E,0x20,0x22,0x24,
0x26,0x28,0x2B,0x2D,0x2F,0x32,0x34,0x37,0x39,0x3C, 0x3F,0x41,0x44,0x47,0x4A,0x4D,
0x4F,0x52,0x55,0x58,0x5B,0x5E,0x61,0x64,0x67,0x6A, 0x6D,0x70,0x74,0x77,0x7A,0x7D
];


// Hmm.. for now assume an 8515-class running at 8 mhz. I've got a few
// spares in the box, and haven't gotten to ordering the megas from
digikey
// yet.

// This will not be a dedicated DDS, and it only needs to produce two
// frequencies. Output methods include a DAC (R2R or separate), I2C
digipot
// (not sure if fast enough, but have some laying around) or PWM.
Assume DAC
// for now.


// Choose an apropriate sample rate. ~10 khz should be adequate, and
not put
// a large burden on the AVR. A low pass may be indicated.

// Keep things simple.. use 32-bit accumulator, so we can run on a
slow clock.
// This is overkill, but easier to do in GCC than 24-bit.
volatile register uint32_t accum, freq_offset;

/* May want to make a circular buffer */
volatile uint8_t fsk_out, fsk_bit, fsk_done_flag;


// Okay.. 8M clock

// Use /256 for TCNT0. 3 of the /256 cycles will give us a sample rate
of
// 10416 Hz
#define TCNT0_PRESCALE 4
#define TCNT0_PERIOD 3


// FCLK = 10416/2^32 = 2.43x10^-6 resolution.
// 1199.9999987893 hz
#define MARK 494780232
// 2200.0000002057 hz
#define SPACE 907097093

// Use /8 for TCNT1. This puts our nominal bitrate at 1199.4 bps,
prolly close
// enough. (0.05%)
#define TCNT1_PRESCALE 2
#define BIT_PERIOD 667

SIGNAL(SIG_OUTPUT_COMPARE0)
{
// Not sure how well this will be optimized, but as it's only run
// every 10khz..
accum += freq_offset;
PORTC = PRG_RDB(s_table + (accum 24));
0CR0 += TCNT0_PERIOD;
// is this needed?
sei();
}


/* This needs to be set pretty close to 1/1500 second. */
SIGNAL(SIG_OUTPUT_COMPARE1A)
{
switch (fsk_bit)
{
case 0 : freq_offset = MARK; break;
case 9 : freq_offset = SPACE; fsk_done = 1; break;
default : freq_offset = (fsk_out (fsk_bit-1)) ?
MARK : SPACE; break;
}
fsk_bit++;
// If only item on TCNT1;
//TCNT1 = 0;
// If sharing TCNT1
OCR1A += BIT_PERIOD;
// is this needed?
sei();
}

void main (void)
{
//setup everything, add ax.25 framing and HDLC encoding. optimized
//crc16 in avr-libc.
}

R C July 7th 03 02:36 AM

Dana Myers K6JQ wrote in message ws.com...
R C wrote:
That is step 2 :) The target CPU will most likely be a Mega8535 AVR,
so conversion to integer is definately indicated. :)


Heh. You're tempting to cobble up the DDS code for giggles and
flash it into an atMega16 I have sitting here ;-)


Hmm.. shot one, for GCC, no ASM. Nominally targetted for 8515 at 8
mhz, easy
to redo.

Not quite finished implementing, and definately not tested, but
framework should be there.

R C
KG4MVB

-- begin fsk2.c --

#include avr/io.h
#include avr/pgmspace.h


// Generated via sine.c. Theory suggests you can use reflexive nature
of sine.
// but we'll keep it simple. Besides, we have 8K of flash.

// For avr use prog_uchar?
prog_uchar s_table[] = [
0x80,0x83,0x86,0x89,0x8C,0x90,0x93,0x96,0x99,0x9C, 0x9F,0xA2,0xA5,0xA8,0xAB,0xAE,
0xB1,0xB3,0xB6,0xB9,0xBC,0xBF,0xC1,0xC4,0xC7,0xC9, 0xCC,0xCE,0xD1,0xD3,0xD5,0xD8,
0xDA,0xDC,0xDE,0xE0,0xE2,0xE4,0xE6,0xE8,0xEA,0xEB, 0xED,0xEF,0xF0,0xF1,0xF3,0xF4,
0xF5,0xF6,0xF8,0xF9,0xFA,0xFA,0xFB,0xFC,0xFD,0xFD, 0xFE,0xFE,0xFE,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFE,0xFE,0xFE,0xFD,0xFD,0xFC, 0xFB,0xFA,0xFA,0xF9,0xF8,0xF6,
0xF5,0xF4,0xF3,0xF1,0xF0,0xEF,0xED,0xEB,0xEA,0xE8, 0xE6,0xE4,0xE2,0xE0,0xDE,0xDC,
0xDA,0xD8,0xD5,0xD3,0xD1,0xCE,0xCC,0xC9,0xC7,0xC4, 0xC1,0xBF,0xBC,0xB9,0xB6,0xB3,
0xB1,0xAE,0xAB,0xA8,0xA5,0xA2,0x9F,0x9C,0x99,0x96, 0x93,0x90,0x8C,0x89,0x86,0x83,
0x80,0x7D,0x7A,0x77,0x74,0x70,0x6D,0x6A,0x67,0x64, 0x61,0x5E,0x5B,0x58,0x55,0x52,
0x4F,0x4D,0x4A,0x47,0x44,0x41,0x3F,0x3C,0x39,0x37, 0x34,0x32,0x2F,0x2D,0x2B,0x28,
0x26,0x24,0x22,0x20,0x1E,0x1C,0x1A,0x18,0x16,0x15, 0x13,0x11,0x10,0x0F,0x0D,0x0C,
0x0B,0x0A,0x08,0x07,0x06,0x06,0x05,0x04,0x03,0x03, 0x02,0x02,0x02,0x01,0x01,0x01,
0x01,0x01,0x01,0x01,0x02,0x02,0x02,0x03,0x03,0x04, 0x05,0x06,0x06,0x07,0x08,0x0A,
0x0B,0x0C,0x0D,0x0F,0x10,0x11,0x13,0x15,0x16,0x18, 0x1A,0x1C,0x1E,0x20,0x22,0x24,
0x26,0x28,0x2B,0x2D,0x2F,0x32,0x34,0x37,0x39,0x3C, 0x3F,0x41,0x44,0x47,0x4A,0x4D,
0x4F,0x52,0x55,0x58,0x5B,0x5E,0x61,0x64,0x67,0x6A, 0x6D,0x70,0x74,0x77,0x7A,0x7D
];


// Hmm.. for now assume an 8515-class running at 8 mhz. I've got a few
// spares in the box, and haven't gotten to ordering the megas from
digikey
// yet.

// This will not be a dedicated DDS, and it only needs to produce two
// frequencies. Output methods include a DAC (R2R or separate), I2C
digipot
// (not sure if fast enough, but have some laying around) or PWM.
Assume DAC
// for now.


// Choose an apropriate sample rate. ~10 khz should be adequate, and
not put
// a large burden on the AVR. A low pass may be indicated.

// Keep things simple.. use 32-bit accumulator, so we can run on a
slow clock.
// This is overkill, but easier to do in GCC than 24-bit.
volatile register uint32_t accum, freq_offset;

/* May want to make a circular buffer */
volatile uint8_t fsk_out, fsk_bit, fsk_done_flag;


// Okay.. 8M clock

// Use /256 for TCNT0. 3 of the /256 cycles will give us a sample rate
of
// 10416 Hz
#define TCNT0_PRESCALE 4
#define TCNT0_PERIOD 3


// FCLK = 10416/2^32 = 2.43x10^-6 resolution.
// 1199.9999987893 hz
#define MARK 494780232
// 2200.0000002057 hz
#define SPACE 907097093

// Use /8 for TCNT1. This puts our nominal bitrate at 1199.4 bps,
prolly close
// enough. (0.05%)
#define TCNT1_PRESCALE 2
#define BIT_PERIOD 667

SIGNAL(SIG_OUTPUT_COMPARE0)
{
// Not sure how well this will be optimized, but as it's only run
// every 10khz..
accum += freq_offset;
PORTC = PRG_RDB(s_table + (accum 24));
0CR0 += TCNT0_PERIOD;
// is this needed?
sei();
}


/* This needs to be set pretty close to 1/1500 second. */
SIGNAL(SIG_OUTPUT_COMPARE1A)
{
switch (fsk_bit)
{
case 0 : freq_offset = MARK; break;
case 9 : freq_offset = SPACE; fsk_done = 1; break;
default : freq_offset = (fsk_out (fsk_bit-1)) ?
MARK : SPACE; break;
}
fsk_bit++;
// If only item on TCNT1;
//TCNT1 = 0;
// If sharing TCNT1
OCR1A += BIT_PERIOD;
// is this needed?
sei();
}

void main (void)
{
//setup everything, add ax.25 framing and HDLC encoding. optimized
//crc16 in avr-libc.
}

R C July 7th 03 03:46 AM

Paul Keinanen wrote in message . ..
On 4 Jul 2003 23:21:05 -0700, (R C) wrote:

I'm working on a telemetry board and wish to include software FSK1200
(MX-614's are getting harder to find). The official Bell 202 spec is
out of print and hard to find as well, so I pieced this together from
what I can find. Multimon won't decode the output, so I'm likely doing
something wrong.


What is this Multimon thing expecting ?


Okay, I've figured some things out since I first posted. Multimon was
written by Thomas Sailer, who also wrote the soundmodem program.

It's available at
http://www.baycom.org/~tom/ham/linux/multimon.html.

Documentation is lacking, but reviewing the source (don't try and
debug at 4 AM; I should've figured this out originally) the afsk1200
decoder expects hdlc encoding, and possibly AX.25 framing (didn't
trace that far). However, an undocumented option -v 10 yields the raw
bit stream.

In that case I assume you have mixed the polarity of the start and
stop bits.

In ordinary RS-232 asynchronous communication the start bit is "0"
SPACE, interrupting the idle MARK state, followed by the data bits
with LSB sent first. The stop bit is "1" or MARK, which then transfers
to a MARK idle state if no more characters are to be transmitted.


That part I figured out from APRS packets recorded off the air. Start
is definately 1200 hz, stop is 2200 hz.

Thanks for the suggestions,
R C
KG4MVB


All times are GMT +1. The time now is 08:23 PM.

Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
RadioBanter.com