You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
208 lines
6.5 KiB
208 lines
6.5 KiB
2 years ago
|
/*
|
||
|
* radioCWModulator_F32.cpp
|
||
|
*
|
||
|
* Created: Bob larkin W7PUA March 2023
|
||
|
*
|
||
|
* License: MIT License. Use at your own risk.
|
||
|
*/
|
||
|
|
||
|
#include "radioCWModulator_F32.h"
|
||
|
#include "sinTable512_f32.h"
|
||
|
|
||
|
void radioCWModulator_F32::update(void) {
|
||
|
float32_t keyData[128]; // CW key down and up, 0.0f and 1.0f
|
||
|
float32_t modulateCW[128]; // Storage for data to modulate sine wave
|
||
|
uint16_t index, i;
|
||
|
float32_t a, b;
|
||
|
audio_block_f32_t *blockOut;
|
||
|
|
||
|
blockOut = AudioStream_F32::allocate_f32(); // Output block
|
||
|
if (!blockOut) return;
|
||
|
|
||
|
// A new character cannot enter sendBuffer during an interrupt, but
|
||
|
// the state IDLE_CW can be created by some other state ending.
|
||
|
// So it needs to be in the audio sample loop.
|
||
|
|
||
|
// We always generate CW at 12 ksps. The number of data points in this
|
||
|
// generation varies to provide 128 output output points after
|
||
|
// interpolation to 48 or 96 ksps.
|
||
|
for(i=0; i<nSamplesPerUpdate; i++)
|
||
|
{
|
||
|
timeMsF += timeSamplesMs;
|
||
|
timeMsI = (uint32_t)(0.5 + timeMsF);
|
||
|
|
||
|
if(!enableXmit) // Just leave the key up and no new characters
|
||
|
{
|
||
|
levelCW = 0.0f;
|
||
|
goto noXmit;
|
||
|
}
|
||
|
|
||
|
switch(stateCW)
|
||
|
{
|
||
|
case IDLE_CW:
|
||
|
timeMsF = 0.0f;
|
||
|
timeMsI = 0;
|
||
|
if(((indexW-indexR)&0X1FF) > 0) // Get next char, if available
|
||
|
{
|
||
|
c = sendBuffer[indexR];
|
||
|
if (c>95)
|
||
|
c -= 64; // Convert lc to caps
|
||
|
else
|
||
|
c -= 32; // Move subscript to (0, 63)
|
||
|
ic = mc[(int)c]; // Ch to morse code lookup
|
||
|
if (c==27) // Long Dash from ';'
|
||
|
{
|
||
|
stateCW = LONG_DASH;
|
||
|
levelCW = 1.0f;
|
||
|
timeMsF = 0.0f;
|
||
|
timeMsI = 0;
|
||
|
}
|
||
|
else if (ic==0X17) // A space character
|
||
|
{
|
||
|
stateCW = WORD_SPACE;
|
||
|
levelCW = 0.0f;
|
||
|
timeMsF = 0.0f;
|
||
|
timeMsI = 0;
|
||
|
}
|
||
|
else if(ic>1 && (ic & 1)==0x01)
|
||
|
{
|
||
|
stateCW = DASH_CW;
|
||
|
levelCW = 1.0f;
|
||
|
timeMsF = 0.0f;
|
||
|
timeMsI = 0;
|
||
|
}
|
||
|
else if(ic>1 && (ic & 1)==0X00)
|
||
|
{
|
||
|
stateCW = DOT_CW;
|
||
|
levelCW = 1.0f;
|
||
|
timeMsF = 0.0f;
|
||
|
timeMsI = 0;
|
||
|
}
|
||
|
else if(ic==0X01)
|
||
|
{
|
||
|
stateCW = IDLE_CW;
|
||
|
levelCW = 0.0f;
|
||
|
}
|
||
|
} // end, if new character
|
||
|
break;
|
||
|
case DASH_CW:
|
||
|
if(timeMsI > dashCW) // Finished dash
|
||
|
{
|
||
|
levelCW = 0.0f;
|
||
|
if(ic>1) ic >>= 1; // Shift right 1
|
||
|
if(ic==1)
|
||
|
stateCW = CHAR_SPACE;
|
||
|
else
|
||
|
stateCW = DOT_DASH_SPACE;
|
||
|
timeMsF = 0.0f;
|
||
|
timeMsI = 0;
|
||
|
}
|
||
|
break;
|
||
|
case DOT_CW:
|
||
|
if(timeMsI > dotCW)
|
||
|
{
|
||
|
levelCW = 0.0f;
|
||
|
if(ic>1) ic >>= 1; // Shift right 1
|
||
|
if(ic==1)
|
||
|
stateCW = CHAR_SPACE;
|
||
|
else
|
||
|
stateCW = DOT_DASH_SPACE;
|
||
|
timeMsF = 0.0f;
|
||
|
timeMsI = 0;
|
||
|
}
|
||
|
break;
|
||
|
case DOT_DASH_SPACE:
|
||
|
if(timeMsI > ddCW) // Just finished
|
||
|
{
|
||
|
timeMsF = 0.0f;
|
||
|
timeMsI = 0;
|
||
|
if(ic>1 && (ic & 1)==0x01)
|
||
|
{
|
||
|
stateCW = DASH_CW;
|
||
|
levelCW = 1.0f;
|
||
|
}
|
||
|
else if(ic>1 && (ic & 1)==0X00)
|
||
|
{
|
||
|
stateCW = DOT_CW;
|
||
|
levelCW = 1.0f;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
stateCW = IDLE_CW;
|
||
|
levelCW = 0.0;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case CHAR_SPACE:
|
||
|
if(timeMsI > chCW+ddCW) // Just finished sending a character
|
||
|
// chCW+ddCW sounds better than chCW to me.
|
||
|
{
|
||
|
indexR++; // Sending is ended, bump the read index
|
||
|
indexR = indexR & 0X1FF; // Confine to (0, 511)
|
||
|
timeMsF = 0.0f;
|
||
|
timeMsI = 0;
|
||
|
stateCW = IDLE_CW;
|
||
|
break;
|
||
|
}
|
||
|
case WORD_SPACE:
|
||
|
if(timeMsI > spCW) // Just finished sending a space
|
||
|
{
|
||
|
indexR++; // Sending is ended, bump the read index
|
||
|
indexR = indexR & 0X1FF; // Confine to (0, 511)
|
||
|
timeMsF = 0.0f;
|
||
|
timeMsI = 0;
|
||
|
stateCW = IDLE_CW;
|
||
|
break;
|
||
|
}
|
||
|
case LONG_DASH:
|
||
|
if(timeMsI > longDashCW) // Just finished sending a long dash
|
||
|
{
|
||
|
levelCW = 0.0f;
|
||
|
stateCW = CHAR_SPACE;
|
||
|
timeMsF = 0.0f;
|
||
|
timeMsI = 0;
|
||
|
break;
|
||
|
}
|
||
|
} // end switch
|
||
|
noXmit:
|
||
|
keyData[i] = levelCW;
|
||
|
} // end, over all 128 times
|
||
|
|
||
|
arm_fir_f32(&GaussLPFInst, keyData, keyData, nSamplesPerUpdate);
|
||
|
|
||
|
// INTERPOLATE - Interpolate here to support higher sample rates,
|
||
|
// while using the same spectral LPF. To this point we have 128, 32
|
||
|
// or 16 "active" data points for 12, 48, or 96ksps.
|
||
|
//
|
||
|
// 0 1 2 3 4 5 6 7 8 9 i
|
||
|
// 0 0 0 0 1 1 1 1 2 2 i/4
|
||
|
// t 0 0 0 t 0 0 0 t 0 i==4*(i/4)
|
||
|
//
|
||
|
if(nSample > 1) // Only needs interpolation if >1
|
||
|
{
|
||
|
for(i=0; i<128; i++)
|
||
|
{
|
||
|
if( i==(nSample*(1/nSample)) )
|
||
|
modulateCW[i]= keyData[i/nSample];
|
||
|
else
|
||
|
modulateCW[i] = 0.0f;
|
||
|
}
|
||
|
arm_fir_f32(&interpolateLPFInst, modulateCW, keyData, 128);
|
||
|
}
|
||
|
|
||
|
// Interpolation is done, now amplitude modulate CW onto a sine wave.
|
||
|
for (i=0; i < 128; i++)
|
||
|
{
|
||
|
phaseS += phaseIncrement;
|
||
|
if (phaseS > 512.0f) phaseS -= 512.0f;
|
||
|
index = (uint16_t) phaseS;
|
||
|
float32_t deltaPhase = phaseS - (float32_t)index;
|
||
|
// Read two nearest values of input value from the sine table
|
||
|
a = sinTable512_f32[index];
|
||
|
b = sinTable512_f32[index+1];
|
||
|
blockOut->data[i] = magnitude*keyData[i]*(a+(b-a)*deltaPhase);
|
||
|
}
|
||
|
AudioStream_F32::transmit(blockOut);
|
||
|
AudioStream_F32::release (blockOut);
|
||
|
}
|