Compare commits

...

30 Commits

Author SHA1 Message Date
cnlohr 84b7b478c0 Almos thtere maybe? 2024-07-13 01:49:39 -07:00
cnlohr 64c8253055 Working with arbitrary goertzel's 2024-07-13 01:19:46 -07:00
cnlohr 7f3ebcc732 eh different printf 2024-07-12 02:02:41 -07:00
cnlohr e9f97f7757 CB FM working 2024-07-12 01:54:24 -07:00
cnlohr 8cf6fa53a9 Add note for future work 2024-07-11 02:07:41 -07:00
cnlohr 0379e7cb30 Fixup audio - now working with javascript. 2024-07-11 02:04:28 -07:00
cnlohr 5028657d02 Add shim to add sound 2024-07-10 02:29:02 -07:00
cnlohr 21e6aab95c Tweak HTML 2024-07-09 03:36:03 -07:00
cnlohr 6c7adbdcc2 Making good progress. 2024-07-09 03:18:12 -07:00
cnlohr edede9d517 Lots of general updates. closing in on a solution 2024-07-09 02:17:18 -07:00
cnlohr e0d41ca056 Fix a multitude of bugs. Some sillier than others. 2024-07-08 23:52:42 -07:00
cnlohr d206128b38 Cleanup - only store I/Q samples 2024-07-07 04:00:49 -07:00
cnlohr 0a6e76034f Progress on goertzel's work in conjunction with web. 2024-07-07 03:44:51 -07:00
cnlohr 4fe9638062 Let's try to get iir goertzel working 2024-07-02 00:38:25 -07:00
cnlohr 0f71fdafe0 Progress so far - giving up for tonight 2024-06-26 02:08:31 -07:00
cnlohr a8ab17013f Switch to FM transmitter sweep 2024-06-25 03:14:43 -07:00
cnlohr 41ec379ad6 Update with 27.025MHz receiver too 2024-06-25 02:55:45 -07:00
cnlohr e0db86d513 update calculator notes 2024-06-25 02:40:43 -07:00
cnlohr 26d8facf2c Update all files + calculator. Working with FM Stations 2024-06-25 02:39:10 -07:00
cnlohr 25dc0234c0 Update table working with goertzel's 2024-06-24 03:32:34 -07:00
cnlohr 2b75c51cd9 Update calculator to handle goertzel 2024-06-24 02:48:18 -07:00
cnlohr 9f0cc2c50d Add print for intensity 2024-06-24 01:38:14 -07:00
cnlohr 5067970ec3 Goertzel's works! 2024-06-24 01:20:02 -07:00
cnlohr 64335ae653 Goertzels is working 2024-06-23 23:32:35 -07:00
cnlohr 7052e8c111 Why no math good 2024-06-23 20:58:33 -07:00
cnlohr 70caf8b76a update with goertzel 2024-06-23 01:11:18 -04:00
cnlohr 17b7d3fdcb Working FFT 2024-06-23 01:10:57 -04:00
cnlohr 9bbca912a0 Add test fft 2024-06-22 04:58:22 -07:00
cnlohr 696701ffb7 Ok, done for 27MHz 2024-06-22 02:30:55 -07:00
cnlohr f31cb4595d Functioning w/ speaker 2024-06-22 02:23:53 -07:00
20 changed files with 5525 additions and 106 deletions
+12 -13
View File
@@ -91,17 +91,13 @@ SOFTWARE.
Calculated to use the 19.75th harmonic @ 27.08571429MHz, but ideal found at 27.08643MHz
*/
#define Q 1800
#define Q 1000
// For Quadrature - use 30
// For nonquadrature use 40.
#define PWM_PERIOD (40-1) //For 27MHz
#define PWM_PERIOD (28-1) //For 27.000500MHz
//#define QUADRATURE
//#define TIGHT_OUT
//#define DUMPBUFF
#define PWM_OUTPUT 1
#define PWM_OUTPUT 7
//#define PWM_PERIOD (32-1)
//#define QUADRATURE
@@ -124,12 +120,13 @@ void SetupADC()
// Set up single conversion on chl 7
ADC1->RSQR1 = 0;
ADC1->RSQR2 = 0;
ADC1->RSQR3 = 7; // 0-9 for 8 ext inputs and two internals
ADC1->RSQR3 = 6; // 0-9 for 8 ext inputs and two internals /// 7 or 6 means one of the ADC inputs.
// Not using injection group.
// PD4 is analog input chl 7
// PD4 is analog input chl 7 + 6
GPIOD->CFGLR &= ~(0xf<<(4*4)); // CNF = 00: Analog, MODE = 00: Input
GPIOD->CFGLR &= ~(0xf<<(4*6)); // CNF = 00: Analog, MODE = 00: Input
// Sampling time for channels. Careful: This has PID tuning implications.
// Note that with 3 and 3,the full loop (and injection) runs at 138kHz.
@@ -281,14 +278,16 @@ void InnerLoop()
int s = 1<<( ( 32 - __builtin_clz(is) )/2);
s = (s + is/s)/2;
#ifdef TIGHT_OUT
printf( "%d\n", s );
printf( "%d\n", is );
#elif defined( PWM_OUTPUT )
int tv = (s>>PWM_OUTPUT) + (PWM_PERIOD/2);
int tv = (i>>PWM_OUTPUT) + (PWM_PERIOD/2);
if( tv < 0 ) tv = 0;
if( tv >= PWM_PERIOD ) tv = PWM_PERIOD-1;
TIM1->CH3CVR = tv;
TIM1->CH4CVR = tv;
#else
printf( "%8d I:%7d Q:%7d [%d %d %d %d] / %d\n",s, i ,q, adc_buffer[0], adc_buffer[1], adc_buffer[2], adc_buffer[3], (int)(SysTick->CNT - tstart) );
-92
View File
@@ -1,92 +0,0 @@
<!DOCTYPE html>
<HTML>
<HEAD>
<SCRIPT>
function DrawSpan( colspan, freq, target, docolor )
{
var fdist = Math.abs( freq - target );
fdist = Math.pow( fdist, 0.5 ) * 500;
// if( fdist > 255 ) fdist = 255;
let ret = "<TD COLSPAN=" + colspan + ' ';
if( docolor ) ret += 'STYLE="background-color:rgb(' + fdist + ',' + (511-fdist) + ',0)";';
ret += '>' + freq.toFixed(6) + "</TD>";
return ret;
}
function computeTable()
{
const max_harmonics = 28|0;
const min_harmonics = 1|0;
let xtal = Number(document.getElementById("crystalmhz").value );
let target = Number(document.getElementById("targetmhz").value );
let contents = "<TABLE BORDER=1>";
contents += '<TR><TH>d\\h</div></TH>';
for( let h = 0|min_harmonics; h <= max_harmonics; h++ )
{
contents += "<TH COLSPAN=2>" + h + "</TH>";
}
contents += "</TR>";
for( let n = 0|28; n <= 66; n++ )
{
for( let mode = 0; mode < 4; mode++ )
{
contents += "<TR>";
if( mode == 0 )
contents += "<TD ROWSPAN=4>" + n + "</TD>";
for( let h = 0|min_harmonics; h <= max_harmonics; h++ )
{
let freq = ( xtal / n );
if( mode == 0 )
contents += DrawSpan( 2, freq * h, target, false );
else if( mode == 1 )
contents += DrawSpan( 2, freq * (h-.25), target, true );
else if( mode == 2 )
contents += DrawSpan( 2, freq * (h+.25), target, true );
else if( mode == 3 )
contents += DrawSpan( 2, freq * (h+0.5), target, true );
}
contents += "</TD>";
}
}
contents += "</TABLE>";
document.getElementById( "TABLE" ).innerHTML = contents;
}
</SCRIPT>
</HEAD>
<BODY>
<p>Tool for computing tuning to specific frequencies by use of direct ADC reading at specific timer-controlled rate to "tune" to specific frequencies either by quadrature or differential.</p>
<TABLE>
<TR><TD>Crystal MHz</TD><TD><INPUT ID=crystalmhz VALUE=48></TD></TR>
<TR><TD>Target MHz</TD><TD><INPUT ID=targetmhz VALUE=27.1></TD></TR>
<TR><TD COLSPAN=2><INPUT TYPE=SUBMIT ONCLICK="computeTable()"></TD></TR>
</TABLE>
<TABLE>
<TR><TD>Quadrature:</TD></TR>
<TR><TD>I = + + - -</TD></TR>
<TR><TD>Q = + - - +</TD></TR>
<TR><TD>Differntial:</TD></TR>
<TR><TD>V = + - + -</TD></TR>
<TR><TD>You choose the mode you operate in, either Quadrature or differential</TD></TR>
</TABLE>
<p> Table shows: </P>
<TABLE BORDER=1>
<TR><TD>Sample Frequency Harmonic</TD></TR>
<TR><TD>Lower Quadrature Frequency</TD></TR>
<TR><TD>Upper Quadrature Frequency</TD></TR>
<TR><TD>Differential Frequency</TD></TR>
</TABLE>
<DIV ID=TABLE></DIV>
</BODY>
</HTML>
+551
View File
@@ -0,0 +1,551 @@
/**
MIT-like-non-ai-license
Copyright (c) 2024 Charles Lohr "CNLohr"
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the two following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
In addition the following restrictions apply:
1. The Software and any modifications made to it may not be used for the
purpose of training or improving machine learning algorithms, including but not
limited to artificial intelligence, natural language processing, or data
mining. This condition applies to any derivatives, modifications, or updates
based on the Software code. Any usage of the Software in an AI-training dataset
is considered a breach of this License.
2. The Software may not be included in any dataset used for training or
improving machine learning algorithms, including but not limited to artificial
intelligence, natural language processing, or data mining.
3. Any person or organization found to be in violation of these restrictions
will be subject to legal action and may be held liable for any damages
resulting from such use.
If any term is unenforcable, other terms remain in-force.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
**/
// NOT LORA!!!
// Transmit a sweep on 97.7MHz
#define FM_TRANSMITTER_SWEEP
// Nothing = Transmit a 315MHz signal.
// XXX WARNING: Something is wrong with this -
// The output isn't perfectly time aligned
// And as such there are extra images in weird places.
// TODO: Investigate the DMA+SPI Port jankyness.
#include "ch32v003fun.h"
#include <stdio.h>
#include <string.h>
#include "LoRa-SDR-Code.h"
#ifdef LORAWAN
#include "lorawan_simple.h"
#endif
#define DMA_SIZE_WORDS 128
#define SENDBUFF_WORDS (DMA_SIZE_WORDS*2)
uint8_t sendbuff[SENDBUFF_WORDS];
// Bits are shifted out MSBit first, then to LSBit
#define MAX_BYTES 25
#define MAX_SYMBOLS (MAX_BYTES*2+16)
// Our table is bespoke for the specific SF.
#define CHIPSSPREAD CHIRPLENGTH_WORDS// QUARTER_CHIRP_LENGTH_WORDS (TODO: Use the quater value elsewhere in the code)
#define MARK_FROM_SF0 (1<<SF_NUMBER) // SF7
#define PREAMBLE_CHIRPS 10
#define CODEWORD_LENGTH 2
uint32_t quadsetcount;
int16_t quadsets[MAX_SYMBOLS*4+PREAMBLE_CHIRPS*4+9+CODEWORD_LENGTH*4];
int runningcount_bits = 0;
volatile int fxcycle;
volatile int quadsetplace = -1;
volatile uint32_t temp;
#if 0
int16_t * AddChirp( int16_t * qso, int offset, int verneer )
{
offset = offset * CHIPSSPREAD / (MARK_FROM_SF0);
offset += verneer;
*(qso++) = (CHIPSSPREAD * 0 / 4 + offset + CHIPSSPREAD ) % CHIPSSPREAD;
*(qso++) = (CHIPSSPREAD * 1 / 4 + offset + CHIPSSPREAD ) % CHIPSSPREAD;
*(qso++) = (CHIPSSPREAD * 2 / 4 + offset + CHIPSSPREAD ) % CHIPSSPREAD;
*(qso++) = (CHIPSSPREAD * 3 / 4 + offset + CHIPSSPREAD ) % CHIPSSPREAD;
return qso;
}
// This IRQ is called periodically to fill the output buffer that is shifted to SPI
void DMA1_Channel3_IRQHandler( void ) __attribute__((interrupt)) __attribute__((section(".srodata")));
void DMA1_Channel3_IRQHandler( void )
{
//GPIOD->BSHR = 1; // Turn on GPIOD0 for profiling
// Backup flags.
volatile int intfr = DMA1->INTFR;
do
{
// Clear all possible flags.
DMA1->INTFCR = DMA1_IT_GL3;
//int place = DMA1_Channel3->CNTR;
// CNTR says that there are THIS MANY bytes left in the transfer.
// So a high CNTR value indicates a very early place in the buffer.
uint16_t * sb = 0;
temp++;
if( intfr & DMA1_IT_HT3 )
{
sb = sendbuff;
}
else if( intfr & DMA1_IT_TC3 )
{
sb = sendbuff + SENDBUFF_WORDS/2;
}
if( sb )
{
if( quadsetplace < 0 )
{
// Abort Send
memset( sb, 0, SENDBUFF_WORDS*2/2 );
DMA1_Channel3->CFGR &= ~DMA_CFGR1_EN;
goto complete;
}
fxcycle += DMA_SIZE_WORDS;
if( fxcycle == NUM_DMAS_PER_QUARTER_CHIRP*DMA_SIZE_WORDS )
{
fxcycle = 0;
// Advance to next quarter word.
quadsetplace++;
if( quadsetplace > quadsetcount )
{
#ifdef TEST_TONE
quadsetplace = 0;
#else
quadsetplace = -1;
#endif
memset( sb, 0, SENDBUFF_WORDS*2/2 );
goto complete;
}
}
int symbol = quadsets[quadsetplace]; // Actually 0...CHIRPLENGTHWORDS
const uint16_t * tsb = 0;
// Select down- or up-chirp.
if( symbol < 0 )
{
int word = fxcycle - symbol - 1;
if( word >= CHIRPLENGTH_WORDS ) word -= CHIRPLENGTH_WORDS;
word++;
tsb = (&chirpbuff[word+REVERSE_START_OFFSET_BYTES/4]);
}
else
{
int word = fxcycle + symbol;
if( word >= CHIRPLENGTH_WORDS ) word -= CHIRPLENGTH_WORDS;
tsb = (&chirpbuff[word]);
}
// I tried using the DMA to do the copy - but that didn't work for some reason.
//while( DMA1_Channel5->CFGR & 1 );
// DMA1_Channel5->CNTR = DMA_SIZE_WORDS/2;
// DMA1_Channel5->MADDR = (uint32_t)tsb;
// DMA1_Channel5->PADDR = (uint32_t)sb;
// DMA1_Channel5->CFGR |= DMA_CFGR1_EN;
int cpy = DMA_SIZE_WORDS/2;
#ifdef TEST_TONE
// Test tone
memset( sb, 0xaa, cpy*2 );
#else
#if DMA_SIZE_WORDS_DIVISIBLE_BY_FOUR == 0
#error need divisibiltiy by 2.
#else
//while( cpy-- ) { *(sb++) = wordo; }
// Guarantee aligned access.
uint32_t * sbw = (uint32_t*)(((uint32_t)sb)&~3);
uint32_t * tsbw = (uint32_t*)(((uint32_t)tsb)&~3);
//while( cpy-- ) { *(sbw++) = *(tsbw++); }
// Align the data copy if needed
if( cpy & 1 )
{
*(sbw++) = *(tsbw++);
}
cpy /= 2; // Doubled up per loop
asm volatile("\
1:\
c.lw a3, 0(%[from])\n\
c.lw a4, 4(%[from])\n\
c.addi %[from], 8\n\
c.addi %[cpy], -1\n\
c.sw a3, 0(%[to])\n\
c.sw a4, 4(%[to])\n\
c.addi %[to], 8\n\
c.bnez %[cpy], 1b\n\
" : [cpy]"+r"(cpy), [from]"+r"(tsbw), [to]"+r"(sbw) : : "a3", "a4", "memory");
#endif
#endif
}
complete:
intfr = DMA1->INTFR;
} while( intfr );
//GPIOD->BSHR = 1<<16; // Turn off GPIOD0 for profiling
}
#endif
void LoopFunction() __attribute__((section(".srodata")));
void LoopFunction()
{
uint8_t * start = (uint8_t*)DMA1_Channel5->MADDR;
uint8_t * end = (uint8_t*)((uint32_t)DMA1_Channel5->MADDR + SENDBUFF_WORDS);
uint8_t * here = start+ 8;
uint32_t targ = 2000;
uint32_t running = 0;
uint8_t * tail = end - DMA1_Channel5->CNTR;
uint32_t * cntr = DMA1_Channel5->CNTR;
uint32_t temp = 0;
uint32_t temp2 = 0;
asm volatile("\n\
li %[targ], 2000\n\
genloop:\n\
lw %[temp], 0(%[cntr])\n\
sub %[tail], %[end], %[temp]\n\
beq %[here], %[tail], genloop\n\
innerloop:\
li %[temp], 17\n\
blt %[running], %[targ], noskip\n\
li %[temp], 18\n\
sub %[running], %[running], %[targ]\n\
noskip:\n\
sb %[temp], 0(%[here])\n\
addi %[here], %[here], 1\n\
bne %[here], %[end], skipreset\n\
add %[here], x0, %[start]\n\
skipreset:\n\
bne %[here], %[tail], innerloop\n\
j genloop\n\
" : [here]"+r"(here) :
[start]"r"(start),
[end]"r"(end),
[targ]"r"(targ),
[running]"r"(running),
[tail]"r"(tail),
[cntr]"r"(cntr),
[temp]"r"(temp),
[temp2]"r"(temp2) );
/*
while(1)
{
int targ_f = 2000; //(frameno & 511)*9 + 1700;
int run_f = 0;
uint8_t * tail = end - DMA1_Channel5->CNTR;
while( here != tail )
{
int setf = 17;
if( run_f > targ_f )
{
setf = 18;
run_f -= targ_f;
}
run_f += setf*32;
*here = setf;
here++;
}
*/
/*
for( j = 0; j < sizeof( sendbuff ); j++ )
{
int setf = 10;
if( run_f > targ_f )
{
setf = 9;
run_f -= targ_f;
}
run_f += setf*32;
sendbuff[j] = setf;
}
*/
}
void LoopFunction2() __attribute__((aligned(256))) __attribute__((section(".srodata"))) __attribute__ ((noinline));
__attribute__((section(".sdata"))) __attribute__((aligned(256))) const uint32_t tablef[] = {
0x06060606,
0x06060607,
0x06070607,
0x07070706,
0x07070707,
0x07070708,
0x07080708,
0x08080807,
0x08080808,
0x08080809,
0x08090809,
0x09090908,
0x09090909,
0x0909090a,
0x090a090a,
0x0a0a0a09,
0x0a0a0a0a, // Below this line is unstable - i.e. sometimes there are missing DMA transfers.
0x0a0a0a0b,
0x0a0b0a0b,
0x0b0b0b0a,
0x0b0b0b0b,
0x0b0b0b0c,
0x0b0c0b0c,
0x0c0c0c0b,
0x0c0c0c0c,
0x0c0c0c0d,
0x0c0d0c0d,
0x0d0d0d0c,
0x0d0d0d0d,
0x0d0d0d0e,
0x0d0e0d0e,
0x0e0e0e0d,
0x0e0e0e0e,
0x0e0e0e0f,
0x0e0f0e0f,
0x0f0f0f0e,
0x0f0f0f0f,
0x0f0f0f10,
0x0f100f10,
0x1010100f,
0x10101010,
0x10101011,
0x10111011,
0x11111110,
};
void LoopFunction2()
{
uint32_t * start = (uint8_t*)DMA1_Channel5->MADDR;
uint32_t * end = (uint8_t*)((uint32_t)DMA1_Channel5->MADDR + SENDBUFF_WORDS);
uint32_t * here = start;
int run_f = 0;
volatile uint32_t * cntrptr = &DMA1_Channel5->CNTR;
while(1)
{
//uint32_t * tail = 0xfffffffc & (uintptr_t)(((uint8_t*)end) - *cntrptr);
//if( tail == end ) tail--;
uint32_t * tail = ((SENDBUFF_WORDS-1) & (0xfffffffc)) & (uintptr_t)(((uint8_t*)start) + SENDBUFF_WORDS - *cntrptr);
while( here != tail )
{
#ifdef FM_TRANSMITTER_SWEEP
// 97.7MHz FM Station
int notein = (SysTick->CNT>>3)&0x1fff;
if( notein > 0xfff ) notein = 0x2000-notein;
uint32_t cp = (notein)+0x20000;
#else
// 315MHz
uint32_t cp = 0x1bfc3;
#endif
*(here++) = tablef[run_f>>12];
run_f &= (1<<12)-1;
run_f += cp;
if( here == end )
here = start;
}
}
}
int main()
{
SystemInit();
funGpioInitAll();
// Set a wait state (1 = normal <= 48MHz)
FLASH->ACTLR = 1;
// MCO for testing.
// funPinMode( PA8, GPIO_CFGLR_OUT_50Mhz_AF_PP ); RCC->CFGR0 |= RCC_CFGR0_MCO_PLL;
// printf( "Switching to HSE\n" );
Delay_Ms( 10 );
// Disable clock security system.
RCC->CTLR &= ~RCC_CSSON;
// Enable external crystal
RCC->CTLR |= RCC_HSEON;
// XXX NOTE: This is only used if you have a clock, not an oscillator.
// RCC->CTLR |= RCC_HSEBYP;
// Set System Clock Source to be 0.
RCC->CFGR0 = (RCC->CFGR0 & ~RCC_SW) | 0;
// Disable PLL
RCC->CTLR &= ~RCC_PLLON;
// Switch to HSE
RCC->CFGR0 |= RCC_PLLSRC;
// Enable PLL
RCC->CTLR |= RCC_PLLON;
// Wait for HSE to become ready.
while( !( RCC->CTLR & RCC_HSERDY) );
while( !( RCC->CTLR & RCC_PLLRDY) );
RCC->CFGR0 |= RCC_SW_1; // Switch system clock to PLL
// printf( "HSE Switched\n" );
Delay_Ms( 10 );
RCC->CTLR &= ~RCC_HSION;
Delay_Ms( 10 );
//printf( "HSI Off [%08lx %08lx]\n", RCC->CTLR, RCC->CFGR0 ); HSI Off [03035180 0001000a]
RCC->APB2PCENR |= RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOC |
RCC_APB2Periph_TIM1;
RCC->AHBPCENR |= RCC_AHBPeriph_DMA1;
funPinMode( PC3, GPIO_CFGLR_OUT_50Mhz_AF_PP ); // T1C3 on PC3
RCC->APB2PRSTR |= RCC_APB2Periph_TIM1;
RCC->APB2PRSTR &= ~RCC_APB2Periph_TIM1;
// Prescaler
TIM1->PSC = 0x0000;
// Auto Reload - sets period
TIM1->ATRLR = 17;
// Reload immediately
TIM1->SWEVGR |= TIM_UG;
// Enable CH1N output, positive pol
TIM1->CCER |= TIM_CC3E;
TIM1->CCER |= TIM_CC1E;
// Compare 3 = for output
// Modes:
// 0, 1, 2: Nothing
// 3: Flip
// 4, 5: Nothing
// 6: "Fast PWM mode 1"
// 7: Flipping (Further out)
#ifdef FM_TRANSMITTER_SWEEP
TIM1->CHCTLR2 = TIM_OC3M_0 | TIM_OC3M_1 | TIM_OC3PE | TIM_OC3FE;
#else
TIM1->CHCTLR2 = TIM_OC3M_2 | TIM_OC3M_1 | TIM_OC3PE | TIM_OC3FE;
#endif
// Compare 1 = for triggering
TIM1->CHCTLR1 = TIM_OC1M_2 | TIM_OC1M_1 | TIM_OC1FE;
// Set the Capture Compare Register value to 50% initially
TIM1->CH3CVR = 4; // ACTUALLY Ignored typically it seems.
TIM1->CH1CVR = 0; // This triggers DMA.
// Enable TIM1 outputs
TIM1->BDTR |= TIM_MOE;
// Enable TIM1
TIM1->CTLR1 |= TIM_CEN;
TIM1->DMAINTENR = TIM_TDE | TIM_UDE; // Outputs DMA1_Channel5
#if 0
// Another try - use TIM2 to trigger DMA?
RCC->APB1PCENR |= RCC_APB1Periph_TIM2;
RCC->APB1PRSTR |= RCC_APB1Periph_TIM2;
RCC->APB1PRSTR &= ~RCC_APB1Periph_TIM2;
TIM2->PSC = 0x0000;
TIM2->ATRLR = 37;
TIM2->SWEVGR |= TIM_UG;
TIM2->BDTR |= TIM_MOE;
TIM2->CHCTLR1 = TIM_OC1M_2 | TIM_OC1M_1 | TIM_OC1FE;
TIM2->DMAINTENR |= TIM_UDE | TIM_TDE | TIM_TDE | TIM_CC1DE;
TIM2->CTLR1 |= TIM_CEN;
// TIM2_UP = DMA Channel 2.
#endif
DMA1_Channel5->PADDR = (uint32_t)&TIM1->CH3CVR;
DMA1_Channel5->MADDR = (uint32_t)sendbuff;
DMA1_Channel5->CNTR = 0;// sizeof( bufferset )/2; // Number of unique copies. (Don't start, yet!)
DMA1_Channel5->CFGR =
DMA_M2M_Disable |
DMA_Priority_VeryHigh |
DMA_PeripheralDataSize_HalfWord |
DMA_MemoryDataSize_Byte |
DMA_MemoryInc_Enable |
DMA_Mode_Circular | // OR DMA_Mode_Circular or DMA_Mode_Normal
DMA_DIR_PeripheralDST |
0;
//DMA_IT_TC | DMA_IT_HT; // Transmission Complete + Half Empty Interrupts.
// NVIC_EnableIRQ( DMA1_Channel3_IRQn );
int j;
for( j = 0; j < sizeof( sendbuff ); j++ )
{
sendbuff[j] = 12;
}
// Enter critical section.
DMA1_Channel5->MADDR = (uint32_t)sendbuff;
DMA1_Channel5->CNTR = SENDBUFF_WORDS; // Number of unique uint16_t entries.
DMA1_Channel5->CFGR |= DMA_CFGR1_EN;
LoopFunction2();
}
+15
View File
@@ -0,0 +1,15 @@
all : flash
TARGET:=adcfft
TARGET_MCU:=CH32V203G6U6
TARGET_MCU_PACKAGE:=CH32V203G6U6
CH32V003FUN:=../ch32v003fun/ch32v003fun
EXTRA_CFLAGS:=-Wno-unused-function -I../../lib -I../lib
include ../ch32v003fun/ch32v003fun/ch32v003fun.mk
flash : cv_flash
clean : cv_clean
rm -rf rf_data_gen chirpbuff.dat chirpbuff.h chirpbuffinfo.h
+4
View File
@@ -0,0 +1,4 @@
# Example with an FFT for quickly figuring out where on the spectrum to look.
* Display on PB8/PB9 for SCL/SDA
* Analog is on PA7
+486
View File
@@ -0,0 +1,486 @@
/**
MIT-like-non-ai-license
Copyright (c) 2024 Charles Lohr "CNLohr"
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the two following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
In addition the following restrictions apply:
1. The Software and any modifications made to it may not be used for the
purpose of training or improving machine learning algorithms, including but not
limited to artificial intelligence, natural language processing, or data
mining. This condition applies to any derivatives, modifications, or updates
based on the Software code. Any usage of the Software in an AI-training dataset
is considered a breach of this License.
2. The Software may not be included in any dataset used for training or
improving machine learning algorithms, including but not limited to artificial
intelligence, natural language processing, or data mining.
3. Any person or organization found to be in violation of these restrictions
will be subject to legal action and may be held liable for any damages
resulting from such use.
If any term is unenforcable, other terms remain in-force.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
**/
// NOT LORA!!! -- but experimenting with the possibility of rx.
// SETUP INSTRUCTIONS:
// (1) `make` in the optionbytes folder to configure `RESET` correctly.
// (2) Create a tone (if using the funprog, ../ch32v003fun/minichlink/minichlink -X ECLK 1:235:189:9:3 for 27.48387097MHz
// (2) or, for 24.387096762MHz - ../ch32v003fun/minichlink/minichlink -X ECLK 1:150:49:8:3
/* More notes
* Minimum sample time with DMA = fCPU / 28 (5.14MHz)
*/
#include "ch32v003fun.h"
#include <stdio.h>
#define SH1107_128x128
#define SSD1306_REMAP_I2C
#include "ssd1306_i2c.h"
#include "ssd1306.h"
#define FIX_FFT_IMPLEMENTATION
#include "fix_fft.h"
/* General note:
*/
#define Q 256
#define TARGET_BIN 51
#define PWM_PERIOD (31-1) //For 27.0MHz, use 36MHz if quadrature -- It appears to be good for *244 in the table? WHY 26MHz???!?!!?
#define ADC_BUFFSIZE 512
volatile uint16_t adc_buffer[ADC_BUFFSIZE];
void SetupADC()
{
// XXX TODO -look into PGA
// XXX TODO - Look into tag-teaming the ADCs
// PA7 is analog input chl 7
GPIOA->CFGLR &= ~(0xf<<(4*7)); // CNF = 00: Analog, MODE = 00: Input
// ADC CLK is chained off of APB2.
// Reset the ADC to init all regs
RCC->APB2PRSTR |= RCC_APB2Periph_ADC1;
RCC->APB2PRSTR &= ~RCC_APB2Periph_ADC1;
// ADCCLK = 12 MHz => RCC_ADCPRE divide by 4
RCC->CFGR0 &= ~RCC_ADCPRE; // Clear out the bis in case they were set
RCC->CFGR0 |= RCC_ADCPRE_DIV2; // Fastest possible (divide-by-2) NOTE: This is OUTSIDE the specified value in the datasheet.
// Set up single conversion on chl 7
ADC1->RSQR1 = 0;
ADC1->RSQR2 = 0;
ADC1->RSQR3 = 7; // 0-9 for 8 ext inputs and two internals
// Not using injection group.
// Sampling time for channels. Careful: This has PID tuning implications.
// Note that with 3 and 3,the full loop (and injection) runs at 138kHz.
ADC1->SAMPTR2 = (0<<(3*7));
// Turn on ADC and set rule group to sw trig
// 0 = Use TRGO event for Timer 1 to fire ADC rule.
ADC1->CTLR2 = ADC_ADON | ADC_EXTTRIG | ADC_DMA;
// Reset calibration
ADC1->CTLR2 |= ADC_RSTCAL;
while(ADC1->CTLR2 & ADC_RSTCAL);
// Calibrate ADC
ADC1->CTLR2 |= ADC_CAL;
while(ADC1->CTLR2 & ADC_CAL);
// ADC_SCAN: Allow scanning.
ADC1->CTLR1 = /*ADC_Pga_64 | */ADC_SCAN;
// Turn on DMA
RCC->AHBPCENR |= RCC_AHBPeriph_DMA1;
//DMA1_Channel1 is for ADC
DMA1_Channel1->PADDR = (uint32_t)&ADC1->RDATAR;
DMA1_Channel1->MADDR = (uint32_t)adc_buffer;
DMA1_Channel1->CNTR = ADC_BUFFSIZE;
DMA1_Channel1->CFGR =
DMA_M2M_Disable |
DMA_Priority_VeryHigh |
DMA_MemoryDataSize_HalfWord |
DMA_PeripheralDataSize_HalfWord |
DMA_MemoryInc_Enable |
DMA_Mode_Circular |
DMA_DIR_PeripheralSRC;
// Turn on DMA channel 1
DMA1_Channel1->CFGR |= DMA_CFGR1_EN;
// Enable continuous conversion and DMA
ADC1->CTLR2 |= ADC_DMA; // | ADC_CONT;
// start conversion
ADC1->CTLR2 |= ADC_SWSTART;// | ADC_CONT;
}
static void SetupTimer1()
{
// Enable Timer 1
RCC->APB2PRSTR |= RCC_APB2Periph_TIM1;
RCC->APB2PRSTR &= ~RCC_APB2Periph_TIM1;
TIM1->PSC = 0; // Prescalar to 0x0000 (so, 48MHz base clock)
TIM1->ATRLR = PWM_PERIOD;
#ifdef PWM_OUTPUT
// PA10 = T1CH3.
GPIOA->CFGHR &= ~(0xf<<(4*2));
GPIOA->CFGHR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP_AF)<<(4*2);
TIM1->CCER = TIM_CC3E | TIM_CC3P;
TIM1->CHCTLR2 = TIM_OC3M_2 | TIM_OC3M_1;
TIM1->CH3CVR = 5; // Actual duty cycle (Off to begin with)
#endif
TIM1->CCER = TIM_CC1E;
TIM1->CHCTLR1 = TIM_OC1M_2 | TIM_OC1M_1;
TIM1->CH1CVR = 1;
// Setup TRGO to trigger for ADC (NOTE: Not on the 203! TIM1_TRGO is only connected to injection)
//TIM1->CTLR2 = TIM_MMS_1;
// Enable TIM1 outputs
TIM1->BDTR = TIM_MOE;
TIM1->CTLR1 = TIM_CEN;
}
void InnerLoop() __attribute__((noreturn));
void InnerLoop()
{
//int i = 0;
//int q = 0;
int tpl = 0;
// Timer goes backwards when we are moving forwards.
volatile uint16_t * adc_buffer_end = 0;
volatile uint16_t * adc_buffer_top = adc_buffer + ADC_BUFFSIZE;
volatile uint16_t * adc = adc_buffer;
int frcnt = 0;
int tstart = 0;
int16_t shadowbuff[Q+16];
int shadowplace = 0;
#define SCALEUP 6
#define SHADOWSTORE(X) shadowbuff[frcnt+X] = t;
while( 1 )
{
tpl = ADC_BUFFSIZE - DMA1_Channel1->CNTR; // Warning, sometimes this is == to the base, or == 0 (i.e. might be 256, if top is 255)
tpl += ADC_BUFFSIZE;
tpl = (tpl & (ADC_BUFFSIZE-1));
if( tpl == ADC_BUFFSIZE ) tpl = 0;
adc_buffer_end = adc_buffer + ( ( tpl / 4) * 4 );
//printf( "%3d %4d %d %04x\n", DMA1_Channel1->CNTR, TIM1->CNT, ADC1->RDATAR, ADC1->STATR );
while( adc != adc_buffer_end )
{
int32_t t = adc[0]; SHADOWSTORE(0);
//i += t; q += t;
t = adc[1]; SHADOWSTORE(1);
//i -= t; q += t;
t = adc[2]; SHADOWSTORE(2);
//i -= t; q -= t;
t = adc[3]; SHADOWSTORE(3);
//i += t; q -= t;
adc += 4;
frcnt += 4;
if( adc == adc_buffer_top ) adc = adc_buffer;
if( frcnt >= Q ) break;
}
if( frcnt >= Q )
{
#if 0
#ifdef DUMPBUFF
int j;
for( j = 0; j < Q; j++ )
printf( "%d,%d\n", j, shadowbuff[j] );
#endif
#ifdef QUADRATURE
int ti = i>>3;
int tq = q>>3;
int is = (ti*ti + tq*tq)>>8;
#else
int is = i>>2;
#endif
int s = 1<<( ( 32 - __builtin_clz(is) )/2);
s = (s + is/s)/2;
#ifdef TIGHT_OUT
printf( "%d\n", s );
#elif defined( PWM_OUTPUT )
int tv = (s>>PWM_OUTPUT) + (PWM_PERIOD/2);
if( tv < 0 ) tv = 0;
if( tv >= PWM_PERIOD ) tv = PWM_PERIOD-1;
TIM1->CH3CVR = tv;
#else
printf( "%8d I:%7d Q:%7d [%d %d %d %d] / %d\n",s, i ,q, adc_buffer[0], adc_buffer[1], adc_buffer[2], adc_buffer[3], (int)(SysTick->CNT - tstart) );
#endif
//printf( "%d\n", s );
#endif
int k;
// printf( "Data: " );
for( k = 0; k < Q; k++ )
{
// printf( "%d, ",shadowbuff[k] );
shadowbuff[k] = (shadowbuff[k]<<SCALEUP)-32768;
}
//printf( "\n" );
int osb = shadowbuff[0];
int16_t FSQ[Q] = { 0 };
for( k = 0; k < 128; k++ )
{
ssd1306_drawPixel( k, ((shadowbuff[k+128]-osb)>>(SCALEUP+1))+40, 1 );
}
int16_t imag[Q] = { 0 };
int r =
fix_fft(shadowbuff, imag, 8, 0);
//fix_fftr(shadowbuff, 7 /*1<<7 = 128 bins wide*/, 0);
//fix_fft(shadowbuff, FSQ, 8 /*1<<7 = 128 bins wide*/, 0);
// printf( "FFT:
// for( k = 0; k < 128; k++ )
// {
// }
int targbin = TARGET_BIN;
int targv = 0;
int maxbin = 0;
int maxbinv = 0;
for( k = 0; k < 128; k++ )
{
/*
int s = shadowbuff[k] * shadowbuff[k] + FSQ[k]*FSQ[k];
//if( s == 0 ) continue;
int x = 1<<( ( 32 - __builtin_clz(s) )/2);
x = (x + i/x)/2;
x = (x + i/x)/2; //Not really needed.
*/
// for real
// int x = shadowbuff[(k>>1) | ((k&64)>>6)];
// For faked imag
#if 0
int s = shadowbuff[k] * shadowbuff[k] + shadowbuff[255-k]*shadowbuff[255-k];
//if( s == 0 ) continue;
int x = 1<<( ( 32 - __builtin_clz(s) )/2);
x = (x + s/x)/2;
x = (x + s/x)/2; //Not really needed.
x = (x + s/x)/2; //Not really needed.
//x = shadowbuff[ k ];
//x = s >> 8;
x = x >> 2;
#endif
int s = shadowbuff[k] * shadowbuff[k] + imag[k]*imag[k];
//if( s == 0 ) continue;
int x = 1<<( ( 32 - __builtin_clz(s) )/2);
x = (x + s/x)/2;
x = (x + s/x)/2; //Not really needed.
x = (x + s/x)/2; //Not really needed.
//x = shadowbuff[ k ];
//x = s >> 8;
//x = x >> 2;
if( x > maxbinv && k != 0) { maxbinv = x; maxbin = k; }
if( k == targbin ) targv = x;
x++;
if( x < 0 ) x = 0;
if( x > 127 ) x = 127;
if( x != 0 )
ssd1306_drawFastVLine( k, 127-x, x, 1 );
}
static int tbhist[128];
static int tbhead = 0;
tbhist[tbhead++] = targv;
if( tbhead == 128 ) tbhead = 0;
char cts[32];
snprintf( cts, sizeof(cts), "%5d%5d@%d", osb, targv, targbin );
ssd1306_drawstr( 0, 0, cts, 1 );
snprintf( cts, sizeof(cts), "P:%d B%3d/%4d", PWM_PERIOD, maxbin, maxbinv );
ssd1306_drawstr( 0, 8, cts, 1 );
for( k = 0; k < 128; k++ )
{
ssd1306_drawPixel( k, 105-tbhist[(tbhead-k+256)%128]/2, 1);
}
memset( shadowbuff, 0, sizeof( shadowbuff ) );
ssd1306_refresh();
ssd1306_setbuf(0);
frcnt = 0;
// i = 0;
// q = 0;
tpl = ADC_BUFFSIZE - DMA1_Channel1->CNTR;
adc = adc_buffer + ( ( tpl / 4) * 4 );
tstart = SysTick->CNT;
}
/*
Delay_Us( 100 );
int end = DMA1_Channel1->CNTR;
int v0 = adc_buffer[0];
int v1 = adc_buffer[1];
int v2 = adc_buffer[2];
int v3 = adc_buffer[3];
printf( "%d %d %d %d %d\n", (uint8_t)(start-end), v0, v1, v2, v3 );
*/
}
}
int main()
{
SystemInit();
SysTick->CTLR = (1<<2) | 1; // HCLK
Delay_Ms(100);
printf( "System On\n" );
RCC->CTLR |= RCC_HSEON;
while( ! ( RCC->CTLR & RCC_HSERDY ) );
RCC->CFGR0 = (RCC->CFGR0 & ~RCC_SW) | RCC_SW_HSE;
RCC->CTLR &= ~RCC_PLLON;
// Switch PLL to HSE.
RCC->CFGR0 |= RCC_PLLSRC;
// x18; 8MHz x 18 = 144 MHz
RCC->CFGR0 &= ~RCC_PPRE2; // No divisor on APB1/2
RCC->CFGR0 &= ~RCC_PPRE1;
RCC->CFGR0 |= RCC_PLLMULL_0 | RCC_PLLMULL_1 | RCC_PLLMULL_2 | RCC_PLLMULL_3;
RCC->CTLR |= RCC_PLLON;
// Switch to PLL
RCC->CFGR0 = (RCC->CFGR0 & ~RCC_SW) | RCC_SW_PLL;
// Disable HSI
RCC->CTLR &= ~(RCC_HSION);
Delay_Ms(10);
printf( "CTLR: %08x / CFGR0: %08x\n", (RCC->CTLR), (RCC->CFGR0) );
RCC->AHBPCENR |= 3; //DMA2EN | DMA1EN
RCC->APB2PCENR |= RCC_APB2Periph_TIM1 | RCC_APB2Periph_ADC1 | RCC_APB2Periph_ADC2 | 0x07; // Enable all GPIO
RCC->APB1PCENR |= RCC_APB1Periph_TIM2;
SetupADC();
printf( "Setting up OLED.\n" );
ssd1306_i2c_setup();
uint8_t ret = ssd1306_i2c_init();
ssd1306_init();
ssd1306_setbuf(0);
#if 0
int i = 0;
int k = 0;
int frame = 0;
while( 1)
{
// ssd1306_drawLine( (frame)%128, (0)%128, (0)%128, (127-frame)%128, 1 );
ssd1306_drawstr( frame%128, frame%128, "hello", 1 );
ssd1306_refresh();
ssd1306_setbuf(0);
frame++;
}
while(1);
#endif
#if 0
// turn on the op-amp
EXTEN->EXTEN_CTR |= EXTEN_OPA_EN;
// select op-amp pos pin: 0 = PA2, 1 = PD7
EXTEN->EXTEN_CTR |= EXTEN_OPA_PSEL;
// select op-amp neg pin: 0 = PA1, 1 = PD0
EXTEN->EXTEN_CTR |= EXTEN_OPA_NSEL;
#endif
printf( "ADC Setup\n" );
SetupTimer1();
printf( "Timer 1 setup\n" );
InnerLoop();
}
+10
View File
@@ -0,0 +1,10 @@
#ifndef _FUNCONFIG_H
#define _FUNCONFIG_H
#define FUNCONF_USE_DEBUGPRINTF 1
#define FUNCONF_USE_UARTPRINTF 0
#define FUNCONF_USE_HSE 1
#define FUNCONF_SYSTICK_USE_HCLK 1
#endif
+15
View File
@@ -0,0 +1,15 @@
all : flash
TARGET:=adcgoertzel
TARGET_MCU:=CH32V203G6U6
TARGET_MCU_PACKAGE:=CH32V203G6U6
CH32V003FUN:=../ch32v003fun/ch32v003fun
EXTRA_CFLAGS:=-Wno-unused-function -I../../lib -I../lib -I../ch32v003fun/examples_v20x/otg_device -I.
include ../ch32v003fun/ch32v003fun/ch32v003fun.mk
flash : cv_flash
clean : cv_clean
rm -rf rf_data_gen chirpbuff.dat chirpbuff.h chirpbuffinfo.h
+29
View File
@@ -0,0 +1,29 @@
# How to use this
* Display on PB8/PB9 for SCL/SDA
* Analog is on PA7
First, use FFT, then look in the middle for the highest bin @. This is the # of pixels from the left side of the screen you are.
Then edit ttest.c,
```
g_goertzel_omega_per_sample = (47.0/256) * 3.1415926535*2.0*65536;
```
where 47.0 in this case is the bin # from the FFT.
Change this, `make test`
Then copy the values:
const int32_t g_goertzel_omega_per_sample = 75599;
const int32_t g_goertzel_coefficient = 870257651;
const int32_t g_goertzel_coefficient_s = 1963246708;
into the top of adcgoertzel.c
TODO: TODO: Test HTML values.
TODO: TODO: g_goertzel_omega_per_sample lokos WRONG.
+723
View File
@@ -0,0 +1,723 @@
/**
MIT-like-non-ai-license
Copyright (c) 2024 Charles Lohr "CNLohr"
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the two following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
In addition the following restrictions apply:
1. The Software and any modifications made to it may not be used for the
purpose of training or improving machine learning algorithms, including but not
limited to artificial intelligence, natural language processing, or data
mining. This condition applies to any derivatives, modifications, or updates
based on the Software code. Any usage of the Software in an AI-training dataset
is considered a breach of this License.
2. The Software may not be included in any dataset used for training or
improving machine learning algorithms, including but not limited to artificial
intelligence, natural language processing, or data mining.
3. Any person or organization found to be in violation of these restrictions
will be subject to legal action and may be held liable for any damages
resulting from such use.
If any term is unenforcable, other terms remain in-force.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
**/
// NOT LORA!!! -- but experimenting with the possibility of rx.
// SETUP INSTRUCTIONS:
// (1) `make` in the optionbytes folder to configure `RESET` correctly.
// (2) Create a tone (if using the funprog, ../ch32v003fun/minichlink/minichlink -X ECLK 1:235:189:9:3 for 27.48387097MHz
// (2) or, for 24.387096762MHz - ../ch32v003fun/minichlink/minichlink -X ECLK 1:150:49:8:3
/* More notes
* Minimum sample time with DMA = fCPU / 28 (5.14MHz)
*/
#include "ch32v003fun.h"
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define SH1107_128x128
#define SSD1306_REMAP_I2C
//#define PWM_OUTPUT
#define ENABLE_OLED
#define PROFILING_PIN PA0
#include "ssd1306_i2c.h"
#include "ssd1306.h"
#include "./usb_config.h"
#include "../ch32v003fun/examples_v20x/otg_device/otgusb.h"
// Bigger buffer decreases chance of fall-through, but increases the size of each operation.
#define ADC_BUFFSIZE 512
volatile uint16_t adc_buffer[ADC_BUFFSIZE];
int g_volume_pwm = 127; // 0 - 127 (100%) (but you can go over 100)
#if 0
int g_pwm_period = (30-1);
int g_goertzel_buffer = (752);
int g_exactcompute = (0);
int32_t g_goertzel_omega_per_sample = 2485087396; // 0.368351 of whole per step / 27.031915MHz
int32_t g_goertzel_coefficient = -1453756170;
int32_t g_goertzel_coefficient_s = 1580594514;
#endif
#if 1
int g_pwm_period = (30-1);
int g_goertzel_buffer = (180);
int g_exactcompute = (0);
int32_t g_goertzel_omega_per_sample = 5509657063; // 0.816667 of whole per step / 0.880000MHz
int32_t g_goertzel_coefficient = 873460290;
int32_t g_goertzel_coefficient_s = -1961823932;
#endif
#if 0
int g_pwm_period = (31-1);
int g_goertzel_buffer = (412);
int g_exactcompute = (0);
const int32_t g_goertzel_omega_per_sample = 1670254667; // 0.247573 of whole per step / 1.150016MHz
const int32_t g_goertzel_coefficient = 32748822;
const int32_t g_goertzel_coefficient_s = 2147233926;
#endif
#if 0
int g_pwm_period = (30-1);
int g_goertzel_buffer = (576);
int g_exactcompute = (0);
int32_t g_goertzel_omega_per_sample = 1264972285; // 0.187500 of whole per step / 90.300000MHz
int32_t g_goertzel_coefficient = 821806413;
int32_t g_goertzel_coefficient_s = 1984016189;
#endif
#if 0
int g_pwm_period = (30-1);
int g_goertzel_buffer = (320);
int g_exactcompute = (0);
const int32_t g_goertzel_omega_per_sample = 990894956; // 0.146875 of whole per step / 101.505000MHz
const int32_t g_goertzel_coefficient = 1296126516;
const int32_t g_goertzel_coefficient_s = 1712233066;
#endif
#if 0
int g_pwm_period = (30-1);
int g_goertzel_buffer = (384);
int g_exactcompute = (0);
const int32_t g_goertzel_omega_per_sample = 4251712402; // 0.630208 of whole per step / 27.025000MHz
const int32_t g_goertzel_coefficient = -1468003291;
const int32_t g_goertzel_coefficient_s = -1567371161;
#endif
#if 0
int g_pwm_period = (30-1);
int g_goertzel_buffer = (336);
int g_exactcompute = (0);
const int32_t g_goertzel_omega_per_sample = 1827182189; // 0.270833 of whole per step / 89.900000MHz
const int32_t g_goertzel_coefficient = -280302863;
const int32_t g_goertzel_coefficient_s = 2129111628;
#endif
int intensity_average = 1;
#define LOG_GOERTZEL_LIST 512
int32_t qibaselogs[LOG_GOERTZEL_LIST];
volatile int qibaselogs_head;
void SetupADC()
{
// XXX TODO -look into PGA
// XXX TODO - Look into tag-teaming the ADCs
// PDA is analog input chl 7
GPIOA->CFGLR &= ~(0xf<<(4*7)); // CNF = 00: Analog, MODE = 00: Input
// ADC CLK is chained off of APB2.
// Reset the ADC to init all regs
RCC->APB2PRSTR |= RCC_APB2Periph_ADC1;
RCC->APB2PRSTR &= ~RCC_APB2Periph_ADC1;
// ADCCLK = 12 MHz => RCC_ADCPRE divide by 4
RCC->CFGR0 &= ~RCC_ADCPRE; // Clear out the bis in case they were set
RCC->CFGR0 |= RCC_ADCPRE_DIV2; // Fastest possible (divide-by-2) NOTE: This is OUTSIDE the specified value in the datasheet.
// Set up single conversion on chl 7
ADC1->RSQR1 = 0;
ADC1->RSQR2 = 0;
ADC1->RSQR3 = 7; // 0-9 for 8 ext inputs and two internals
// Not using injection group.
// Sampling time for channels. Careful: This has PID tuning implications.
// Note that with 3 and 3,the full loop (and injection) runs at 138kHz.
ADC1->SAMPTR2 = (0<<(3*7));
// Turn on ADC and set rule group to sw trig
// 0 = Use TRGO event for Timer 1 to fire ADC rule.
ADC1->CTLR2 = ADC_ADON | ADC_EXTTRIG | ADC_DMA;
// Reset calibration
ADC1->CTLR2 |= ADC_RSTCAL;
while(ADC1->CTLR2 & ADC_RSTCAL);
// Calibrate ADC
ADC1->CTLR2 |= ADC_CAL;
while(ADC1->CTLR2 & ADC_CAL);
// ADC_SCAN: Allow scanning.
ADC1->CTLR1 = ADC_Pga_64 | ADC_SCAN;
// Turn on DMA
RCC->AHBPCENR |= RCC_AHBPeriph_DMA1;
//DMA1_Channel1 is for ADC
DMA1_Channel1->PADDR = (uint32_t)&ADC1->RDATAR;
DMA1_Channel1->MADDR = (uint32_t)adc_buffer;
DMA1_Channel1->CNTR = ADC_BUFFSIZE;
DMA1_Channel1->CFGR =
DMA_M2M_Disable |
DMA_Priority_VeryHigh |
DMA_MemoryDataSize_HalfWord |
DMA_PeripheralDataSize_HalfWord |
DMA_MemoryInc_Enable |
DMA_Mode_Circular |
DMA_DIR_PeripheralSRC;
// NVIC_SetPriority( DMA1_Channel1_IRQn, 0<<4 ); //We don't need to tweak priority.
NVIC_EnableIRQ( DMA1_Channel1_IRQn );
DMA1_Channel1->CFGR |= DMA_CFGR1_EN | DMA_IT_TC | DMA_IT_HT; // Transmission Complete + Half Empty Interrupts.
// Turn on DMA channel 1
DMA1_Channel1->CFGR |= DMA_CFGR1_EN;
// Enable continuous conversion and DMA
ADC1->CTLR2 |= ADC_DMA; // | ADC_CONT;
// start conversion
ADC1->CTLR2 |= ADC_SWSTART;// | ADC_CONT;
}
static void SetupTimer1()
{
// Enable Timer 1
RCC->APB2PRSTR |= RCC_APB2Periph_TIM1;
RCC->APB2PRSTR &= ~RCC_APB2Periph_TIM1;
TIM1->PSC = 0; // Prescalar to 0x0000 (so, 48MHz base clock)
TIM1->ATRLR = g_pwm_period;
#ifdef PWM_OUTPUT
// PA9 = T1CH2.
funPinMode( PA9, GPIO_CFGLR_OUT_2Mhz_AF_PP );
TIM1->CCER = TIM_CC2E | TIM_CC2P;
TIM1->CHCTLR1 |= TIM_OC2M_2 | TIM_OC2M_1 | TIM_OC2FE;
TIM1->CH2CVR = 5; // Set duty cycle somewhere random.
// Enable TIM1 outputs
TIM1->BDTR |= 0xc000;//TIM_MOE;
#endif
TIM1->CCER |= TIM_CC1E;
TIM1->CHCTLR1 |= TIM_OC1M_2 | TIM_OC1M_1;
TIM1->CH1CVR = 1;
// Setup TRGO to trigger for ADC (NOTE: Not on the 203! TIM1_TRGO is only connected to injection)
//TIM1->CTLR2 = TIM_MMS_1;
// Enable TIM1 outputs
TIM1->BDTR = TIM_MOE;
TIM1->CTLR1 = TIM_CEN;
}
void InnerLoop() __attribute__((noreturn));
uint32_t tc;
volatile uint16_t * adc_tail = adc_buffer;
uint32_t g_goertzel_samples;
uint32_t g_goertzel_outs;
int32_t g_goertzel, g_goertzelp, g_goertzelp2;
int32_t g_goertzelp_store, g_goertzelp2_store;
int32_t g_laststart = 0;
int32_t g_lastper;
int32_t g_lastlen;
uint32_t g_accumulate_over_window;
void DMA1_Channel1_IRQHandler( void ) __attribute__((interrupt));
void DMA1_Channel1_IRQHandler( void )
{
int32_t start = SysTick->CNT;
#ifdef PROFILING_PIN
funDigitalWrite( PROFILING_PIN, 1 );
#endif
// Timer goes backwards when we are moving forwards.
volatile uint16_t * adc_buffer_end = 0;
volatile uint16_t * adc_buffer_top = adc_buffer + ADC_BUFFSIZE;
int32_t goertzel_coefficient = g_goertzel_coefficient;
int32_t goertzelp2 = g_goertzelp2;
int32_t goertzelp = g_goertzelp;
int32_t goertzel = g_goertzel;
int32_t accumulate_over_window = g_accumulate_over_window;
uint32_t goertzel_samples = g_goertzel_samples;
// Backup flags.
volatile int intfr = DMA1->INTFR;
do
{
// Clear all possible flags.
DMA1->INTFCR = DMA1_IT_GL1;
int tpl = ADC_BUFFSIZE - DMA1_Channel1->CNTR; // Warning, sometimes this is == to the base, or == 0 (i.e. might be 256, if top is 255)
tpl += ADC_BUFFSIZE;
tpl = (tpl & (ADC_BUFFSIZE-1));
if( tpl == ADC_BUFFSIZE ) tpl = 0;
adc_buffer_end = adc_buffer + ( ( tpl / 4) * 4 );
#define INFADC 2
// Add a tiny bias to the ADC to help keep goertz in range.
static int adc_offset = (-2048) << INFADC;
while( adc_tail != adc_buffer_end )
{
uint32_t t; // 1/2 of 4096, to try to keep our numbers reasonable.
// Here is where the magic happens.
#if 1
#define XSTR(x) #x
#define GOERTZELLOOP(idx) \
asm volatile("\n\
lhu %[adcin]," XSTR(idx) "(%[adc_tail]) \n\
slli %[adcin],%[adcin],%[iadc] /*INFADC = 2*/ \n\
add %[adcin],%[adcin],%[adcoffset] /*adcin += adcoffset*/ \n\
add %[accumulate_over_window], %[adcin], %[accumulate_over_window]\n \
addi %[goertzelp2],%[goertzelp],0 /*goertzelp2 = goertzelp*/ \n\
addi %[goertzelp], %[goertzel],0 /*goertzelp = goertzel*/ \n\
slli %[goertzel], %[goertzelp], 2 /*prescaling up goertzelp*/\n\
mulh %[goertzel], %[goertzel_coefficient], %[goertzel]\n\
sub %[adcin],%[adcin],%[goertzelp2] /*adcin -= goertzelp2*/ \n\
add %[goertzel], %[goertzel], %[adcin] /* mulh = signed * signed + adc */ \n"\
: [goertzel]"+r"(goertzel), [goertzelp]"+r"(goertzelp), [goertzelp2]"+r"(goertzelp2), [adcin]"+r"(t), [accumulate_over_window]"+r"(accumulate_over_window) : \
[adc_tail]"r"(adc_tail), [adcoffset]"r"(adc_offset), [goertzel_coefficient]"r"(goertzel_coefficient), [iadc]"i"(INFADC) );
GOERTZELLOOP(0);
GOERTZELLOOP(2);
GOERTZELLOOP(4);
GOERTZELLOOP(6);
#else
t = ((adc_tail[0])<<INFADC)+adc_offset;
goertzelp2 = goertzelp;
goertzelp = goertzel;
goertzel = t + ( ( (((int32_t)(goertzel_coefficient))) * ((((int64_t)goertzelp)<<2)) ) >> 32 ) - goertzelp2;
t = ((adc_tail[1])<<INFADC)+adc_offset;
goertzelp2 = goertzelp;
goertzelp = goertzel;
goertzel = t + ( ( (((int32_t)(goertzel_coefficient))) * ((((int64_t)goertzelp)<<2)) ) >> 32 ) - goertzelp2;
t = ((adc_tail[2])<<INFADC)+adc_offset;
goertzelp2 = goertzelp;
goertzelp = goertzel;
goertzel = t + ( ( (((int32_t)(goertzel_coefficient))) * ((((int64_t)goertzelp)<<2)) ) >> 32 ) - goertzelp2;
t = ((adc_tail[3])<<INFADC)+adc_offset;
goertzelp2 = goertzelp;
goertzelp = goertzel;
goertzel = t + ( ( (((int32_t)(goertzel_coefficient))) * ((((int64_t)goertzelp)<<2)) ) >> 32 ) - goertzelp2;
#endif
adc_tail+=4;
goertzel_samples+=4;
if( adc_tail == adc_buffer_top ) adc_tail = adc_buffer;
if( goertzel_samples == g_goertzel_buffer )
{
#ifdef PROFILING_PIN
funDigitalWrite( PROFILING_PIN, 0 );
#endif
g_goertzelp_store = goertzel;
g_goertzelp2_store = goertzelp;
int32_t zp = g_goertzelp_store;
int32_t zp2 = g_goertzelp2_store;
int32_t rr = (((int64_t)(g_goertzel_coefficient ) * (int64_t)zp<<1)>>32) - (zp2);
int32_t ri = (((int64_t)(g_goertzel_coefficient_s) * (int64_t)zp<<1)>>32);
qibaselogs[qibaselogs_head] = ((uint16_t)rr) | (((uint16_t)ri)<<16);
qibaselogs_head = ( qibaselogs_head + 1 ) & ((LOG_GOERTZEL_LIST)-1);
rr>>=2;
ri>>=2;
int s = rr * rr + ri * ri;
//int intensity = 1<<( ( 32 - __builtin_clz(s) )/2);
#define ABS(x) (((x)<0)?-(x):(x))
int intensity = (ABS(rr) + ABS(ri)) * 26100 / 32768; // Found experimentally (Also try to avoid divide-by-zero.
if( intensity == 0 )
intensity = 1;
intensity = (intensity + s/intensity)/2;
intensity = (intensity + s/intensity)/2;
intensity_average = intensity_average - (intensity_average>>12) + (intensity>>6);
#ifdef PWM_OUTPUT
intensity = intensity * g_volume_pwm * g_pwm_period / (intensity_average>>(10-12));
if( intensity >= g_pwm_period-1 ) intensity = g_pwm_period-2;
if( intensity < 1 ) intensity = 1;
TIM1->CH2CVR = intensity; // Actual duty cycle (Off to begin with)
#endif
g_goertzel_outs++;
goertzel = 0;
goertzelp = 0;
goertzel_samples = 0;
// Try to improve bias.
adc_offset -= accumulate_over_window / g_goertzel_buffer;
accumulate_over_window = 0;
#ifdef PROFILING_PIN
funDigitalWrite( PROFILING_PIN, 1 );
#endif
}
}
intfr = DMA1->INTFR;
} while( intfr & DMA1_IT_GL1 );
g_goertzelp2 = goertzelp2;
g_goertzelp = goertzelp;
g_goertzel = goertzel;
g_goertzel_samples = goertzel_samples;
g_accumulate_over_window = accumulate_over_window;
#ifdef PROFILING_PIN
funDigitalWrite( PROFILING_PIN, 0 ); // For profiling
#endif
int32_t end = SysTick->CNT;
g_lastper = start - g_laststart;
g_laststart = start;
g_lastlen = end - start;
}
static inline uint32_t gets2()
{
uint32_t ret;
asm volatile( "mv %[ret], s2" : [ret]"=&r"(ret) );
return ret;
}
void InnerLoop()
{
while(1){
#if 0
int adcz = adc_buffer[0];
for( k = 0; k < 128; k++ )
{
int y = adc_buffer[k]-adcz + 64;
if( y < 0 ) y = 0;
if( y > 127 ) y = 127;
ssd1306_drawPixel( k, y, 1 );
}
#endif
int pxa = 0;
// Only display half of the list so the other half could
// be updated by the ISR.
int glread = qibaselogs_head;
int intensity = 0;
for( pxa = 0; pxa < LOG_GOERTZEL_LIST; pxa++ )
{
uint32_t combiq = qibaselogs[glread];
glread = ( glread + 1 ) & ( LOG_GOERTZEL_LIST -1 );
int16_t rr = combiq & 0xffff;
int16_t ri = combiq >> 16;
rr = rr * 512 / (intensity_average);
ri = ri * 512 / (intensity_average);
rr += 64;
ri += 64;
if( rr < 0 ) rr = 0;
if( ri < 0 ) ri = 0;
if( rr > 127 ) rr = 127;
if( ri > 127 ) ri = 127;
#ifdef ENABLE_OLED
ssd1306_drawPixel( rr, ri, 1 );
#endif
}
#ifdef ENABLE_OLED
//char cts[32];
//snprintf( cts, 32, "%d", intensity_average );
//ssd1306_drawstr( 0, 0, cts, 1 );
ssd1306_refresh();
//static int ik = 0;
//printf( "%d %08x\n", ik, ssd1306_buffer[ik++] );
//if( ik == sizeof(ssd1306_buffer) ) ik = 0;
ssd1306_setbuf(0);
#else
Delay_Ms(17);
#endif
// printf( "%6d %8d %8d - %8d %8d - %8d\n", g_goertzel_outs,g_goertzelp2_store, g_goertzelp_store, rr, ri, x );
// Delay_Ms(940);
//printf( "!!!!\n ");
}
}
int main()
{
SystemInit();
SysTick->CTLR = (1<<2) | 1; // HCLK
Delay_Ms(100);
RCC->CTLR |= RCC_HSEON;
while( ! ( RCC->CTLR & RCC_HSERDY ) );
RCC->CFGR0 = (RCC->CFGR0 & ~RCC_SW) | RCC_SW_HSE;
RCC->CTLR &= ~RCC_PLLON;
// Switch PLL to HSE.
RCC->CFGR0 |= RCC_PLLSRC;
// x18; 8MHz x 18 = 144 MHz
RCC->CFGR0 &= ~RCC_PPRE2; // No divisor on APB1/2
RCC->CFGR0 &= ~RCC_PPRE1;
RCC->CFGR0 |= RCC_PLLMULL_0 | RCC_PLLMULL_1 | RCC_PLLMULL_2 | RCC_PLLMULL_3;
RCC->CTLR |= RCC_PLLON;
// Switch to PLL
RCC->CFGR0 = (RCC->CFGR0 & ~RCC_SW) | RCC_SW_PLL;
// Disable HSI
RCC->CTLR &= ~(RCC_HSION);
Delay_Ms(10);
//printf( "CTLR: %08x / CFGR0: %08x\n", (RCC->CTLR), (RCC->CFGR0) );
RCC->AHBPCENR |= 3; //DMA2EN | DMA1EN
RCC->APB2PCENR |= RCC_APB2Periph_TIM1 | RCC_APB2Periph_ADC1 | RCC_APB2Periph_ADC2 | 0x07 | RCC_APB2Periph_GPIOA; // Enable all GPIO
RCC->APB1PCENR |= RCC_APB1Periph_TIM2;
SetupADC();
#ifdef ENABLE_OLED
ssd1306_i2c_setup();
ssd1306_i2c_init();
if( ssd1306_init() )
printf( "Failed to initialize OLED\n" );
else
printf( "Initialized OLED\n" );
ssd1306_setbuf(1);
ssd1306_refresh();
#endif
#if 0
int i = 0;
int k = 0;
int frame = 0;
while( 1)
{
// ssd1306_drawLine( (frame)%128, (0)%128, (0)%128, (127-frame)%128, 1 );
ssd1306_drawstr( frame%128, frame%128, "hello", 1 );
ssd1306_refresh();
ssd1306_setbuf(0);
frame++;
}
while(1);
#endif
#if 0
// turn on the op-amp
EXTEN->EXTEN_CTR |= EXTEN_OPA_EN;
// select op-amp pos pin: 0 = PA2, 1 = PD7
EXTEN->EXTEN_CTR |= EXTEN_OPA_PSEL;
// select op-amp neg pin: 0 = PA1, 1 = PD0
EXTEN->EXTEN_CTR |= EXTEN_OPA_NSEL;
#endif
#ifdef PROFILING_PIN
funPinMode( PROFILING_PIN, GPIO_CFGLR_OUT_50Mhz_PP );
#endif
SetupTimer1();
USBOTGSetup();
InnerLoop();
}
uint8_t scratchpad[512];
int g_isConfigurePacket = 0;
int HandleHidUserSetReportSetup( struct _USBState * ctx, tusb_control_request_t * req )
{
int id = req->wValue & 0xff;
g_isConfigurePacket = 0;
if( id == 0xaa && req->wLength <= sizeof( scratchpad ) )
{
ctx->pCtrlPayloadPtr = scratchpad;
return req->wLength;
}
else if( id == 0xac && req->wLength <= sizeof( scratchpad ) )
{
g_isConfigurePacket = 1;
ctx->pCtrlPayloadPtr = scratchpad;
return req->wLength;
}
return 0;
}
int HandleHidUserGetReportSetup( struct _USBState * ctx, tusb_control_request_t * req )
{
int id = req->wValue & 0xff;
if( id == 0xaa )
{
ctx->pCtrlPayloadPtr = scratchpad;
return 255;
}
else if( id == 0xac )
{
g_isConfigurePacket = 1;
ctx->pCtrlPayloadPtr = scratchpad;
return 63;
}
else if( id == 0xad )
{
static int last_baselog;
int samps_to_send = (qibaselogs_head - last_baselog + LOG_GOERTZEL_LIST * 2 - 1) & (LOG_GOERTZEL_LIST-1);
if( samps_to_send > 120 ) samps_to_send = 120;
((uint32_t*)scratchpad)[0] = (intensity_average<<12) | samps_to_send;
((uint32_t*)scratchpad)[1] = (g_lastper<<16) | g_lastlen;
((uint32_t*)scratchpad)[2] = (0<<16) | (((g_pwm_period+1)*g_goertzel_buffer)); //LSW = 144MHz / X
int i;
for( i = 3; i < samps_to_send + 3; i++ )
{
last_baselog = (last_baselog+1)&(LOG_GOERTZEL_LIST-1);
((uint32_t*)(scratchpad))[i] = ((int32_t*)qibaselogs)[last_baselog];
}
for( ; i < 128; i++ )
((uint32_t*)(scratchpad))[i] = 0;
ctx->pCtrlPayloadPtr = scratchpad;
return 510;
}
return 0;
}
void HandleHidUserReportDataOut( struct _USBState * ctx, uint8_t * data, int len )
{
}
int HandleHidUserReportDataIn( struct _USBState * ctx, uint8_t * data, int len )
{
// printf( "IN %d %d %08x %08x\n", len, ctx->USBFS_SetupReqLen, data, FSUSBCTX.ENDPOINTS[0] );
// memset( data, 0xcc, len );
return len;
}
void HandleHidUserReportOutComplete( struct _USBState * ctx )
{
if( g_isConfigurePacket )
{
uint32_t * configs = (uint32_t*)scratchpad;
// Note: configs[0] == 0xac (command type)
printf( "Is Configure Packet %08x\n", configs[1] );
int numconfigs = configs[1];
if( numconfigs > 0) g_pwm_period = configs[2];
if( numconfigs > 1) g_goertzel_buffer = configs[3];
if( numconfigs > 2) g_goertzel_omega_per_sample = configs[4]; // 0.816667 of whole per step / 0.880000MHz
if( numconfigs > 3) g_goertzel_coefficient = configs[5];
if( numconfigs > 4) g_goertzel_coefficient_s = configs[6];
if( numconfigs > 5) g_exactcompute = configs[7];
// Need to reset so we don't blast by.
g_goertzel_samples = 0;
TIM1->ATRLR = g_pwm_period;
g_isConfigurePacket = 0;
}
return;
}
+11
View File
@@ -0,0 +1,11 @@
#ifndef _FUNCONFIG_H
#define _FUNCONFIG_H
#define FUNCONF_USE_DEBUGPRINTF 1
#define FUNCONF_USE_UARTPRINTF 0
#define FUNCONF_USE_HSE 1
#define FUNCONF_SYSTICK_USE_HCLK 1
#define FUNCONF_ENABLE_HPE 0
#endif
+17
View File
@@ -0,0 +1,17 @@
all : test floattest
floattest : floattestt
./floattestt
floattestt : floattestt.c
gcc -o $@ $^ -lm -g
test : ttest
./ttest
ttest : ttest.c
gcc -o $@ $^ -lm -g
clean :
rm -rf *.o ttest *~ floattestt
Binary file not shown.
+112
View File
@@ -0,0 +1,112 @@
#include <stdio.h>
#include <math.h>
#include <stdint.h>
uint32_t g_goertzel_omega_per_sample;
int32_t g_goertzel_coefficient;
int32_t g_goertzel_coefficient_s;
uint32_t g_goertzel_samples;
uint32_t g_goertzel_outs;
int32_t g_goertzelp, g_goertzelp2;
int32_t g_goertzelp_store, g_goertzelp2_store;
int main()
{
//XXX XXX NOTE If you are computing the coefficients, you can plug the value in here.
// You can get this number from calculator.html. I've had better luck with "inverse goertzel" numbers.
g_goertzel_omega_per_sample = (47.0/256) * 3.1415926535*2.0*(1<<29);
g_goertzel_coefficient = 2 * cos( g_goertzel_omega_per_sample / (double)(1<<29) ) * (1<<30);
g_goertzel_coefficient_s = 2 * sin( g_goertzel_omega_per_sample / (double)(1<<29) ) * (1<<30);
const double AomegaPerSample = g_goertzel_omega_per_sample/(double)(1<<29);
const int AnumSamples = 256; // enough to go from 0 to 2pi
double Acoeff = 2 * cos( AomegaPerSample ) * 65536;
double Acoeff_s = 2 * sin( AomegaPerSample ) * 65536;
double Asprev = AomegaPerSample * 65536;
double Asprev2 = 0;
printf( "%d / %d / %d %f / %f / %f\n", g_goertzel_omega_per_sample, g_goertzel_coefficient, g_goertzel_coefficient_s, Acoeff, Acoeff_s, AomegaPerSample );
g_goertzelp = g_goertzel_omega_per_sample>>(29-16);
int32_t goertzel_coefficient = g_goertzel_coefficient;
int32_t goertzelp2 = g_goertzelp2;
int32_t goertzelp = g_goertzelp;
uint32_t goertzel_samples = g_goertzel_samples;
uint32_t adc_tail;
double As;
int js = 0;
for( js = 0; js < 260/4; js++ )
{
int32_t t; // 1/2 of 4096, to try to keep our numbers reasonable.
// Here is where the magic happens.
int32_t goertzel;
#define ITERATION(x) \
t = sin( AomegaPerSample * (x+js*4) ) * 16384*0; \
\
/* Fixed */ \
goertzelp2 = goertzelp; \
goertzelp = goertzel; \
goertzel = t + ( ( (((int32_t)(goertzel_coefficient))) * ((((int64_t)goertzelp)<<2)) ) >> 32 ) - goertzelp2; \
\
/* Float */ \
Asprev2 = Asprev; \
Asprev = As; \
As = t + ( Acoeff * Asprev ) / 65536.0 - Asprev2; \
\
/*printf( "%d,%d,%d,%d\n", ( ( (int64_t)(goertzel_coefficient) * (int64_t)goertzelp ) >> 32 ) , goertzel_coefficient, goertzelp, goertzelp2 ); */ \
\
{\
int32_t rr = (((int64_t)(g_goertzel_coefficient ) * (int64_t)goertzelp<<1)>>32) - (goertzelp2); \
int32_t ri = (((int64_t)(g_goertzel_coefficient_s) * (int64_t)goertzelp<<1)>>32); \
/*printf( "%3d %10d %10d %10d %10d / %9d %9d %9d * ", js*4+x, rr, ri, goertzelp, goertzelp2, goertzel_coefficient, g_goertzel_omega_per_sample, t );*/ \
/*printf( "%4d %10d %10d (%10d %10d) ", js*4+x, goertzelp, goertzelp2, goertzel_coefficient, g_goertzel_coefficient_s );*/ \
printf( "%4d %10d %10d (%10d %10d) ", js*4+x, goertzelp, goertzelp2, rr, ri ); \
\
float Apower = Asprev*Asprev + Asprev2*Asprev2 - (Acoeff * Asprev * Asprev2); \
double ArR = 0.5 * Acoeff * Asprev / 65536 - Asprev2; \
double ArI = 0.5 * Acoeff_s * Asprev / 65536; \
/*printf( "%14.3f, %14.3f (%14.3f %14.3f)\n", Asprev, Asprev2, Acoeff,Acoeff_s );*/ \
printf( "%14.3f, %14.3f (%14.3f %14.3f %f)\n", Asprev, Asprev2, ArR, ArI, sqrt( ArR*ArR +ArI*ArI ) ); \
}
ITERATION( 0 );
ITERATION( 1 );
ITERATION( 2 );
ITERATION( 3 );
adc_tail+=4;
goertzel_samples+=4;
// if( adc_tail == adc_buffer_top ) adc_tail = adc_buffer;
/* if( goertzel_samples == 128 )
{
g_goertzelp_store = goertzelp;
g_goertzelp2_store = goertzelp2;
goertzelp = 0;
goertzelp2 = 0;
g_goertzel_outs++;
goertzel_samples = 0;
}
*/
}
g_goertzelp2 = goertzelp2;
g_goertzelp = goertzelp;
g_goertzel_samples = goertzel_samples;
printf( "Constants:\nconst int32_t g_goertzel_omega_per_sample = %d;\nconst int32_t g_goertzel_coefficient = %d;\nconst int32_t g_goertzel_coefficient_s = %d;\n", g_goertzel_omega_per_sample, g_goertzel_coefficient, g_goertzel_coefficient_s );
}
+157
View File
@@ -0,0 +1,157 @@
#ifndef _USB_CONFIG_H
#define _USB_CONFIG_H
#include "funconfig.h"
#include "ch32v003fun.h"
#define FUSB_CONFIG_EPS 2 // Include EP0 in this count
#define FUSB_SUPPORTS_SLEEP 0
#define FUSB_HID_INTERFACES 1
#define FUSB_CURSED_TURBO_DMA 0 // Hacky, but seems fine, shaves 2.5us off filling 64-byte buffers.
#define FUSB_HID_USER_REPORTS 1
#define FUSB_IO_PROFILE 1
#define FUSB_USE_HPE FUNCONF_ENABLE_HPE
#include "usb_defines.h"
//Taken from http://www.usbmadesimple.co.uk/ums_ms_desc_dev.htm
static const uint8_t device_descriptor[] = {
18, //Length
1, //Type (Device)
0x00, 0x02, //Spec
0x0, //Device Class
0x0, //Device Subclass
0x0, //Device Protocol (000 = use config descriptor)
64, //Max packet size for EP0 (This has to be 8 because of the USB Low-Speed Standard)
0x09, 0x12, //ID Vendor
0x35, 0xd0, //ID Product
0x03, 0x00, //ID Rev
1, //Manufacturer string
2, //Product string
3, //Serial string
1, //Max number of configurations
};
static const uint8_t HIDAPIRepDesc[ ] =
{
HID_USAGE_PAGE ( 0xff ), // Vendor-defined page.
HID_USAGE ( 0x00 ),
HID_REPORT_SIZE ( 8 ),
HID_COLLECTION ( HID_COLLECTION_LOGICAL ),
HID_REPORT_COUNT ( 254 ),
HID_REPORT_ID ( 0xaa )
HID_USAGE ( 0x01 ),
HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,
HID_REPORT_COUNT ( 63 ), // For use with `hidapitester --vidpid 1209/D003 --open --read-feature 171`
HID_REPORT_ID ( 0xab )
HID_USAGE ( 0x01 ),
HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,
HID_REPORT_COUNT ( 63 ), // For configuring the setup.
HID_REPORT_ID ( 0xac )
HID_USAGE ( 0x01 ),
HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,
HID_REPORT_COUNT_N ( 510,2 ), // For receiving IQ data on host.
HID_REPORT_ID ( 0xad )
HID_USAGE ( 0x01 ),
HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,
HID_COLLECTION_END,
};
/* Configuration Descriptor Set */
static const uint8_t config_descriptor[ ] =
{
/* Configuration Descriptor */
0x09, // bLength
0x02, // bDescriptorType
0x22, 0x00, // wTotalLength
0x01, // bNumInterfaces (3)
0x01, // bConfigurationValue
0x00, // iConfiguration
0xA0, // bmAttributes: Bus Powered; Remote Wakeup
0x32, // MaxPower: 100mA
/* Interface Descriptor (Special) */
0x09, // bLength
0x04, // bDescriptorType
0x00, // bInterfaceNumber
0x01, // bAlternateSetting
0x01, // bNumEndpoints
0x03, // bInterfaceClass
0x00, // bInterfaceSubClass
0xff, // bInterfaceProtocol: OTher
0x00, // iInterface
/* HID Descriptor (Special) */
0x09, // bLength
0x21, // bDescriptorType
0x10, 0x01, // bcdHID
0x00, // bCountryCode
0x01, // bNumDescriptors
0x22, // bDescriptorType
sizeof(HIDAPIRepDesc), 0x00, // wDescriptorLength
/* Endpoint Descriptor (Special) */
0x07, // bLength
0x05, // bDescriptorType
0x81, // bEndpointAddress: IN Endpoint 1
0x03, // bmAttributes
0x08, 0x00, // wMaxPacketSize
0xff, // bInterval: slow.
};
#define STR_MANUFACTURER u"CNLohr"
#define STR_PRODUCT u"lolra ch32v203 goertzel test"
#define STR_SERIAL u"CUSTOMDEVICE000"
struct usb_string_descriptor_struct {
uint8_t bLength;
uint8_t bDescriptorType;
uint16_t wString[];
};
const static struct usb_string_descriptor_struct string0 __attribute__((section(".rodata"))) = {
4,
3,
{0x0409}
};
const static struct usb_string_descriptor_struct string1 __attribute__((section(".rodata"))) = {
sizeof(STR_MANUFACTURER),
3,
STR_MANUFACTURER
};
const static struct usb_string_descriptor_struct string2 __attribute__((section(".rodata"))) = {
sizeof(STR_PRODUCT),
3,
STR_PRODUCT
};
const static struct usb_string_descriptor_struct string3 __attribute__((section(".rodata"))) = {
sizeof(STR_SERIAL),
3,
STR_SERIAL
};
// This table defines which descriptor data is sent for each specific
// request from the host (in wValue and wIndex).
const static struct descriptor_list_struct {
uint32_t lIndexValue;
const uint8_t *addr;
uint8_t length;
} descriptor_list[] = {
{0x00000100, device_descriptor, sizeof(device_descriptor)},
{0x00000200, config_descriptor, sizeof(config_descriptor)},
// interface number // 2200 for hid descriptors.
{0x00002200, HIDAPIRepDesc, sizeof(HIDAPIRepDesc)},
{0x00002100, config_descriptor + 18, 9 }, // Not sure why, this seems to be useful for Windows + Android.
{0x00000300, (const uint8_t *)&string0, 4},
{0x04090301, (const uint8_t *)&string1, sizeof(STR_MANUFACTURER)},
{0x04090302, (const uint8_t *)&string2, sizeof(STR_PRODUCT)},
{0x04090303, (const uint8_t *)&string3, sizeof(STR_SERIAL)}
};
#define DESCRIPTOR_LIST_ENTRIES ((sizeof(descriptor_list))/(sizeof(struct descriptor_list_struct)) )
#endif
Binary file not shown.
+315
View File
@@ -0,0 +1,315 @@
<!DOCTYPE html>
<HTML>
<HEAD>
<LINK rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon">
<META charset="UTF-8">
<STYLE>
body { background-color: Canvas; color: CanvasText; }
:root { color-scheme: dark; }
</STYLE>
<SCRIPT src=webhidcontrol.js></SCRIPT>
<SCRIPT>
function DrawSpan( rowspan, colspan, freq, target, docolor, extrastr = "" )
{
var fdist = Math.abs( freq - target );
fdist = Math.pow( fdist, 0.5 ) * 500;
// if( fdist > 255 ) fdist = 255;
let ret = "<TD COLSPAN=" + colspan + ' ROWSPAN' + rowspan + ' ';
if( docolor ) ret += 'STYLE="color:black;background-color:rgb(' + fdist + ',' + (511-fdist) + ',0)";';
ret += '>' + extrastr + freq.toFixed(6) + "</TD>";
return ret;
}
function Goertz( n, mhz, fr, brf, exact_compute )
{
let omega = fr * 3.1415926535*2.0;
var textarea = document.getElementById("goertzeloutput");
var g_goertzel_omega_per_sample = Math.round( ( omega*2*(1<<29)) );
var g_goertzel_coefficient = Math.round( (2 * Math.cos( omega ) * (1<<30)) );
var g_goertzel_coefficient_s = Math.round( (2 * Math.sin( omega ) * (1<<30)) );
var g_exactcompute = exact_compute;
textarea.value =
"int g_pwm_period = ("+n+"-1);\n" +
"int g_exactcompute = ("+exact_compute+");\n" +
"int g_goertzel_buffer = ("+brf+");\n" +
"int32_t g_goertzel_omega_per_sample = " + g_goertzel_coefficient.toFixed(0) + "; // " + ( omega / (3.1415926535*2.0)).toFixed(6) + " of whole per step / " + mhz.toFixed(6) + "MHz\n" +
"int32_t g_goertzel_coefficient = " + g_goertzel_coefficient.toFixed(0) + ";\n" +
"int32_t g_goertzel_coefficient_s = " + g_goertzel_coefficient_s.toFixed(0) + ";\n";
// Highlight its content
textarea.select();
// Copy the highlighted text
document.execCommand("copy");
updateWebHidDeviceWithParameters( [ (n-1)|0, brf|0, g_goertzel_omega_per_sample|0, g_goertzel_coefficient|0, g_goertzel_coefficient_s|0, exact_compute|0 ] );
}
function computeTable()
{
let xtal = Number(document.getElementById("crystalmhz").value );
let target = Number(document.getElementById("targetmhz").value );
let quadrature = document.getElementById("QUADRATURE").checked;
let goertzels = document.getElementById("GOERTZELS").checked;
let goertzel2 = document.getElementById("GOERTZEL2").checked;
let quanta = Math.round(Number(document.getElementById("quanta").value));
let quantasearch = Math.round(Number(document.getElementById("quantasearch").value));
const max_harmonics = 28|0;
const min_harmonics = (quadrature?1:0)|0;
let contents = "";
if( quadrature )
{
contents += "<TABLE>" +
"<TR><TD>Quadrature:</TD></TR>" +
"<TR><TD>I = + + - -</TD></TR>" +
"<TR><TD>Q = + - - +</TD></TR>" +
"<TR><TD>Differntial:</TD></TR>" +
"<TR><TD>V = + - + -</TD></TR>" +
"<TR><TD>You choose the mode you operate in, either Quadrature or differential</TD></TR>" +
"</TABLE>" +
"<p> Table shows: </P>" +
"<TABLE BORDER=1>" +
"<TR><TD>Sample Frequency Harmonic</TD></TR>" +
"<TR><TD>Lower Quadrature Frequency</TD></TR>" +
"<TR><TD>Upper Quadrature Frequency</TD></TR>" +
"<TR><TD>Differential Frequency</TD></TR>" +
"</TABLE>";
}
else if( goertzels )
{
contents +=
"<TABLE BORDER=1>" +
"<TR><TD>Goertzel</TD></TR>" +
"<TR><TD>Goertzel (Inverse)</TD></TR>" +
"</TABLE><TEXTAREA ROWS=6 COLS=120 ID=goertzeloutput></TEXTAREA>" +
"<P>Click on a ordinal offset to create the C code needed for that tuning parameter. Clicking will copy-to-clipboard.</P>" +
"<P>N Divisor #30 (row 3) is usually pretty good. And, try to select things near 0.25 / 0.75, and avoid 0.0, 0.5, and 1.0.</P>" +
"<P>Goertzel's mode is for the ch32v203</P>";
}
if( goertzels || quadrature )
{
contents += "<TABLE BORDER=1>";
contents += '<TR><TH>d\\h</div></TH>';
for( let h = 0|min_harmonics; h <= max_harmonics; h++ )
{
contents += "<TH COLSPAN=2>" + h + "</TH>";
}
for( let n = 0|28; n <= 66; n++ )
{
let freq = ( xtal / n );
let goertzelpoint = 0;
let goertzelpointinv = 0;
let tgoertzelp = 0;
let tgoertzelpi = 0;
let quantaA = 0;
let quantaINV = 0;
for( let h = 0|min_harmonics; h <= max_harmonics; h++ )
{
let base = freq * h;
let next = freq * (h+1);
// Round quanta down to next order-of-4-even
for( let tquanta = (quanta&0xffffc) - (quantasearch&0xffffc); tquanta < (quanta&0xffffc) + (quantasearch&0xffffc); tquanta+=4 )
{
if( target <= next && target >= base )
{
var t;
let tgoertzelpoint = ( target - base ) / ( next - base );
tgoertzelpoint = Math.round(tquanta * tgoertzelpoint)/tquanta;
if( Math.abs( freq*(h+tgoertzelpoint) - target ) < Math.abs( freq*(h+goertzelpoint) - target ) )
{
goertzelpoint = tgoertzelpoint;
tgoertzelp = h;
quantaA = tquanta;
}
let tgoertzelpointinv = (1.0 - ( target - base ) / ( next - base ));
tgoertzelpointinv = Math.round(tquanta * tgoertzelpointinv)/tquanta;
if( Math.abs( freq*(h-tgoertzelpointinv+1) - target ) < Math.abs( freq*(h-goertzelpointinv+1) - target ) )
{
goertzelpointinv = tgoertzelpointinv;
tgoertzelpi = h;
quantaINV = tquanta;
}
}
}
}
contents += "</TR>";
for( let mode = 0; mode < 4; mode++ )
{
contents += "<TR>";
if( mode == 0 )
contents += "<TD ROWSPAN=" + 4 + ">" + n + "</TD>";
for( let h = 0|min_harmonics; h <= max_harmonics; h++ )
{
if( quadrature )
{
if( mode == 0 )
contents += DrawSpan( 1, 2, freq * h, target, false );
else if( mode == 1 )
contents += DrawSpan( 1, 2, freq * (h-.25), target, true );
else if( mode == 2 )
contents += DrawSpan( 1, 2, freq * (h+.25), target, true );
else if( mode == 3 )
contents += DrawSpan( 1, 2, freq * (h+0.5), target, true );
}
else
{
if( mode == 0 )
{
contents += "<TD COLSPAN=2>"
if( tgoertzelp == h ) contents += "<SPAN ONCLICK='Goertz(" + n + ", " + freq * (h+goertzelpoint) + ", " + (goertzelpoint) + ", " + quantaA + ", 0)'>↑" + (goertzelpoint).toFixed(6) + "</SPAN>";
contents += "</TD>";
}
else if( mode == 1 )
{
contents += DrawSpan( 1, 2, freq * (h+goertzelpoint), target, true );
}
else if( mode == 2 )
{
contents += "<TD COLSPAN=2>"
if( tgoertzelpi == h-1 ) contents += "<SPAN ONCLICK='Goertz(" + n + ", " + freq * (h-goertzelpointinv) + ", " + goertzelpointinv + ", " + quantaINV + ", 0)'>↓" + goertzelpointinv.toFixed(6) + "</SPAN>";
contents += "</TD>";
}
else if( mode == 3 )
{
contents += DrawSpan( 1, 2, freq * (h-goertzelpointinv), target, true );
}
}
}
contents += "</TD>";
}
}
contents += "</TABLE>";
}
else if( goertzel2 )
{
contents += "</TABLE><TEXTAREA ROWS=6 COLS=120 ID=goertzeloutput></TEXTAREA><BR>";
contents += "<TABLE BORDER=1>";
contents += '<TR><TH>d\\h</div></TH>';
for( let h = 0|min_harmonics; h <= max_harmonics+1; h++ )
{
contents += "<TH COLSPAN=1>" + h + "</TH>";
}
for( let n = 0|28; n <= 96; n++ )
{
let freq = ( xtal / n );
let goertzelpoint = 0;
let goertzelpointinv = 0;
let tgoertzelp = 0;
let tgoertzelpi = 0;
let quantaA = 0;
let quantaINV = 0;
for( let rid = 0|0; rid < 2|0; rid++ )
{
contents += "<TR>";
for( let h = -2|min_harmonics; h <= max_harmonics; h++ )
{
let tquanta = (quanta&0xffffc);
let base = freq * h;
let next = freq * (h+1);
let tgoertzelpoint = tquanta;
tgoertzelpoint = ( target - base ) / ( next - base );
tgoertzelpoint = ((tgoertzelpoint%1)+1)%1;
quantaA = tquanta;
tgoertzelp = h;
if( h == -2 )
{
if( rid == 0 )
{
contents += "<TD COLSPAN=1 ROWSPAN=2>"
contents += "<SPAN ONCLICK='Goertz(" + n + ", " + freq * (h+tgoertzelpoint) + ", " + (tgoertzelpoint) + ", " + quantaA + ", 1)'>↑" + n + "</SPAN>";
contents += "</TD>"
}
}
else
{
if( rid == 0 )
{
contents += DrawSpan( 1, 1, freq * (h+tgoertzelpoint), target, true );
}
else
{
contents += DrawSpan( 1, 1, freq * (h-tgoertzelpoint), target, true );
}
}
}
contents += "</TR>";
}
}
contents += "</TABLE>";
}
document.getElementById( "TABLE" ).innerHTML = contents;
}
function onLoad()
{
onLoadWebHidControl();
}
</SCRIPT>
</HEAD>
<BODY onLoad="onLoad()">
<TABLE WIDTH=100%>
<TR>
<TD COLSPAN=3>
<p>Tool for computing tuning to specific frequencies by use of direct ADC reading at specific timer-controlled rate to "tune" to specific frequencies either by quadrature or differential.</p>
</TD>
<TD ROWSPAN=2 VALIGN=TOP WIDTH=100% ID=LiveGraphContainer>
<CANVAS WIDTH=100% HEIGHT=200 ID=LiveGraph> </CANVAS>
</TD>
</TR>
<TR>
<TD VALIGN=TOP>
<TABLE WIDTH=480>
<TR><TD>Crystal MHz</TD><TD><INPUT ID=crystalmhz VALUE=144></TD></TR>
<TR><TD>Target MHz</TD><TD><INPUT ID=targetmhz VALUE=27.019360></TD></TR>
<TR><TD>Quanta</TD><TD><INPUT ID=quanta VALUE=1024> (Goertzel's Only)</TD></TR>
<TR><TD>Quanta Search Range</TD><TD><INPUT ID=quantasearch VALUE=64> (Goertzel's Only)</TD></TR>
<TR><TD>Table Type</TD><TD>
<INPUT TYPE=RADIO ID=QUADRATURE NAME=computetype>Quadrature</INPUT>
<INPUT TYPE=RADIO ID=GOERTZELS NAME=computetype>Goertzels</INPUT>
<INPUT TYPE=RADIO ID=GOERTZEL2 NAME=computetype checked>Goertzel (unalign)</INPUT>
</TD></TR>
<TR><TD COLSPAN=2><INPUT TYPE=SUBMIT VALUE="Compute" ONCLICK="computeTable()"></TD></TR>
</TABLE>
</TD>
<TD VALIGN=TOP>
Live Control:
<TABLE><TR>
<TD><INPUT TYPE=SUBMIT onClick="reqConnect()" VALUE="Open Device" ID=connectButton>
<INPUT TYPE=SUBMIT onClick="toggleAudio()" VALUE="Play Audio" ID="ToggleAudioButton">
<INPUT TYPE=SUBMIT onClick="toggleAudioModulation()" VALUE="modulation" ID="ToggleAudioModulationButton">
<DIV ID=STATUS></DIV></TD>
</TR></TABLE>
<DIV ID="StatusPerf"></DIV>
</TD>
</TD>
</TR>
</TABLE>
<DIV ID=TABLE></DIV>
</BODY>
</HTML>
+2569
View File
File diff suppressed because it is too large Load Diff
+498
View File
@@ -0,0 +1,498 @@
const expectedProductName = "CNLohr lolra ch32v203 goertzel test";
const filter = { vendorId : 0x1209, productId : 0xd035 };
let dev = null;
let loopAbort = false;
const IQHistoryLen = 4096;
var IQHistoryArray = new Uint32Array(IQHistoryLen);
var MPHistoryArray = new Float32Array(IQHistoryLen*2);
var IQHistoryHead = 0|0;
var lastIntensity = 1.0;
var lastNumQ = 0;
var lastTotalTime = 1;
var lastTimeUsed = 1;
var graphIsClicked = false;
function graphClick( e )
{
if( e.type == "mousedown" ) graphIsClicked = true;
if( e.type == "mouseup" ) graphIsClicked = false;
return true;
}
function setStatus( msg )
{
document.getElementById( "STATUS" ).innerHTML = msg;
console.log( msg );
}
function setStatusError( msg )
{
setStatus( "<FONT COLOR=RED>" + msg + "</FONT>" );
console.log( msg );
console.trace();
}
function tryConnect()
{
if( !navigator.hid )
{
return;
}
if( !dev )
{
navigator.hid.getDevices().then( (devices) =>
{
if( devices.length == 0 )
setStatusError( "No devices found. Open a device." );
else
devices.forEach( tryOpen );
});
}
}
async function closeDeviceTool()
{
loopAbort = false;
setStatusError( "Disconnected" );
}
var playingAudioProcessor = null;
var audioContext = null;
var targetModulation = 0;
var targetGain = 0.0;
function UpdateButtonNames()
{
document.getElementById( "ToggleAudioButton" ).value = ( targetGain > 0.0 ) ? "Stop Audio" : "Start Audio";
document.getElementById( "ToggleAudioModulationButton" ).value = ( targetModulation > 0.5 ) ? "FM" : "AM";
}
async function toggleAudioModulation()
{
if( playingAudioProcessor != null )
{
var newVal = 1 - targetModulation;
targetModulation = newVal;
}
UpdateButtonNames();
}
async function toggleAudio()
{
if( playingAudioProcessor == null )
{
var bypass = '\
class PlayingAudioProcessor extends AudioWorkletProcessor {\
static get parameterDescriptors() {\
return [\
{ name: "gain", defaultValue: 0, },\
{ name: "sampleAdvance", defaultValue: 0.5, },\
]\
};\
constructor() {\
super();\
this.rbuffer = new Float32Array(8192); \
this.rbufferhead = 0|0; \
this.rbuffertail = 0|0; \
this.sampleplace = 0.0; \
this.dcoffset = 0.0; \
this.totalsampcount = 0|0; \
\
this.port.onmessage = (e) => { \
for( var i = 0|0; i < e.data.length|0; i++ ) \
{ \
let n = (this.rbufferhead + (1|0))%(8192|0); \
if( n == this.rbuffertail ) \
{ \
this.rbuffertail = (this.rbuffertail + (1|0))%(8192|0); \
console.log( `Overflow` ); \
} \
var vv = e.data[i]; \
this.dcoffset = this.dcoffset * 0.995 + vv * 0.005; \
this.rbuffer[this.rbufferhead] = vv - this.dcoffset; \
this.rbufferhead = n; \
} \
}; \
}\
\
process(inputs, outputs, parameters) {\
/*console.log( parameters.gain[0] );*/ \
/*console.log( this.ingestData );*/ \
let len = outputs[0][0].length; \
const sa = Math.fround( parameters.sampleAdvance[0] ); /*float*/ \
var s = Math.fround( this.sampleplace ); /*float*/ \
var tail = this.rbuffertail | 0; /* int*/ \
var tailnext = this.rbuffertail | 0; /* int*/ \
if( tail == this.rbufferhead ) { console.log( "Underflow " ); return true; }\
var tsamp = Math.fround( this.rbuffer[tail] ); \
var nsamp = Math.fround( this.rbuffer[tailnext] ); \
this.totalsampcount += len|0; \
for (let b = 0|0; b < len|0; b++) { \
s += sa; \
var excess = Math.floor( s ) | 0; \
if( excess > 0 ) \
{ \
s -= excess; \
tail = ( tail + (excess|0) ) % (8192|0); \
tailnext = ( tail + (1|0) ) % (8192|0); \
if( tail == this.rbufferhead ) { console.log( "Underflow" ); break; } \
tsamp = Math.fround( this.rbuffer[tail] ); \
nsamp = Math.fround( this.rbuffer[tailnext] ); \
} \
var valv = tsamp * (1.0-s) + nsamp * s; \
outputs[0][0][b] = valv*parameters.gain[0]; \
} \
/*console.log( tail + " " + this.rbuffertail + " " + tsamp + " " + nsamp );*/ \
this.rbuffertail = tail; \
this.sampleplace = s; \
return true; \
} \
} \
\
registerProcessor("playing-audio-processor", PlayingAudioProcessor);';
// The following mechanism does not work on Chrome.
// const dataURI = URL.createObjectURL( new Blob([bypass], { type: 'text/javascript', } ) );
// Extremely tricky trick to side-step local file:// CORS issues.
// https://stackoverflow.com/a/67125196/2926815
// https://stackoverflow.com/a/72180421/2926815
let blob = new Blob([bypass], {type: 'application/javascript'});
let reader = new FileReader();
await reader.readAsDataURL(blob);
let dataURI = await new Promise((res) => {
reader.onloadend = function () {
res(reader.result);
}
});
audioContext = new AudioContext();
await audioContext.audioWorklet.addModule(dataURI);
playingAudioProcessor = new AudioWorkletNode(
audioContext,
"playing-audio-processor"
);
playingAudioProcessor.connect(audioContext.destination);
audioContext.resume();
let gainParam = playingAudioProcessor.parameters.get( "gain" );
gainParam.setValueAtTime( 0, audioContext.currentTime );
}
var newVal = 0.1 - targetGain;
console.log( "Setting gain to: " + newVal );
let gainParam = playingAudioProcessor.parameters.get("gain");
gainParam.setValueAtTime( newVal, audioContext.currentTime);
targetGain = newVal;
document.getElementById( "ToggleAudioButton" ).value = ( newVal > 0.5 ) ? "Stop Audio" : "Start Audio";
UpdateButtonNames();
}
function onLoadWebHidControl()
{
liveGraph = document.getElementById( "LiveGraph" );
liveGraph.addEventListener( "mousedown", graphClick );
liveGraph.addEventListener( "mouseup", graphClick );
UpdateButtonNames();
setTimeout( sendLoop, 1 );
if( !navigator.hid )
{
setStatusError( "Browser does not support HID." );
document.getElementById( "connectButton" ).hidden = true;
}
else
{
navigator.hid.addEventListener("disconnect", (event) => { if( event.device.productName == expectedProductName ) closeDeviceTool(); } );
}
setTimeout( () => { elapsedOK = true; }, 3000 );
}
function reqConnect()
{
loopAbort = true;
const initialization = navigator.hid.requestDevice( { filters: [ filter ] } );
initialization.then( gotUSBDevice );
initialization.catch( setStatusError );
}
function gotUSBDevice(result)
{
if( result.length < 1 )
{
setStatusError( "Error: No devices found" );
return;
}
if( result[0].productName != expectedProductName )
{
setStatusError( "Error: Wrong device name. Got " + result[0].productName + " Expected " + expectedProductName );
return;
}
const thisDev = result[0];
tryOpen( thisDev );
}
function tryOpen( thisDev )
{
thisDev.open().then( ( result ) => {
if( result === undefined )
{
if( dev ) dev.close();
loopAbort = false;
dev = thisDev;
setStatus( "Connected." );
}
else
{
setStatusError( "Error: Could not open; " + result );
}
} ).catch( (e) => setStatusError( "Error: Could not open; " + e ) );
}
let sendReport = null;
let receiveReport = null;
async function sendLoopError( e )
{
sendReport = null;
receiveReport = null;
if( dev ) await dev.close();
dev = null;
setStatusError( e );
}
function updateWebHidDeviceWithParameters( paramlist )
{
var i = 0|0;
var arraySend = new Uint8Array(63);
for( var i = 0|0; i < paramlist.length|0; i++ )
{
var vv = paramlist[i] | 0;
arraySend[i*4+7] = (vv>>0)&0xff;
arraySend[i*4+8] = (vv>>8)&0xff;
arraySend[i*4+9] = (vv>>16)&0xff;
arraySend[i*4+10] = (vv>>24)&0xff;
}
arraySend[3] = paramlist.length | 0;
sendReport = dev.sendFeatureReport( 0xAC, arraySend ).catch( sendLoopError );
if( !sendReport ) sendLoopError( "error creating sendFeatureReport" );
}
FMiirphase = 0.0; /* for FM */
FMlastphase = 0.0; /* for FM */
FMphaseout = 0.0; /* for FM */
async function sendLoop()
{
const sleep = ms => new Promise(r => setTimeout(r, ms));
var frameNo = 0|0;
var lastTime = performance.now();
let goodCount = 0;
let badCount = 0;
let kBsecAvg = 0;
let xActionSecAvg = 0;
while( true )
{
if( dev && !loopAbort )
{
receiveReport = dev.receiveFeatureReport( 0xAD ).catch( sendLoopError );
if( !receiveReport ) sendLoopError( "error creating receiveReport" );
frameNo++
const updateStatsPerfPer = 4;
if( frameNo % updateStatsPerfPer == 0 )
{
let thisTime = performance.now();
let deltaTime = thisTime - lastTime;
let kBsec = (255*1000/1024*updateStatsPerfPer)/(deltaTime);
let xActionSec = (2*updateStatsPerfPer*1000)/(deltaTime);
kBsecAvg = kBsecAvg * 0.9 + kBsec * 0.1;
xActionSecAvg = xActionSecAvg * 0.9 + xActionSec * 0.1;
document.getElementById( "StatusPerf" ).innerHTML =
(kBsecAvg).toFixed(2) + " kBytes/s<br>" +
(xActionSecAvg).toFixed(2) + "transactions/sec<br>" +
"Count: " + goodCount + " / " + badCount;
lastTime = thisTime;
}
else if( frameNo % updateStatsPerfPer == 2 )
{
const ctx = liveGraph.getContext("2d");
if( !graphIsClicked )
{
let liveGraphContainer = document.getElementById( "LiveGraphContainer" );
liveGraph.width = liveGraphContainer.clientWidth;
liveGraph.style.position = 'absolute';
ctx.clearRect(0, 0, liveGraph.width, liveGraph.height);
var filledness = lastNumQ * 198 / 120;
ctx.fillStyle = "rgb( 240 240 240 )";
ctx.fillRect( 2, 2 + 198 - filledness, 18, filledness - 2);
filledness = ( lastTimeUsed * 1.0 / lastTotalTime ) * 198;
ctx.fillStyle = "rgb( 240 240 240 )";
ctx.fillRect( 26, 2 + 198 - filledness, 18, filledness - 2 );
ctx.fillStyle = `rgb( 255 255 255 )`;
let mulcoeff = 10000.0 / lastIntensity;
var lot = 1.2;
var x = 253;
for( var i = (IQHistoryHead-1) & (IQHistoryLen-1); i != IQHistoryHead|0; i = (i - 1 + IQHistoryLen) & (IQHistoryLen-1) )
{
let power = MPHistoryArray[i*2+0]; //Math.sqrt( real * real + imag * imag ) * mulcoeff;
let phase = MPHistoryArray[i*2+1]; //Math.atan2( real, imag ) * 0.159155078*0.5;
ctx.fillRect(x,power*120+10,2,2);
ctx.fillRect(x,phase*80+110,2,2);
let v = IQHistoryArray[i];
let real = (v >> 16);
let imag = (v & 0xffff);
if( real > 32767 ) real -= 65536;
if( imag > 32767 ) imag -= 65536;
real = real * mulcoeff + 100;
imag = imag * mulcoeff + 100;
if( real < 0 ) real = 0; if( real > 255 ) real = 255;
if( imag < 0 ) imag = 0; if( imag > 255 ) imag = 255;
x++;
if( lot > 0 )
{
ctx.globalAlpha = lot;
ctx.fillRect(real+50,imag,2,2);
}
ctx.globalAlpha = 1.0;
lot -= 0.0015;
}
ctx.strokeStyle = "rgb( 128 128 128 )";
ctx.fillStyle = "rgb( 128 0 0 )";
ctx.strokeRect( 1, 1, 20, 198 );
ctx.strokeRect( 25, 1, 20, 198 );
ctx.strokeRect( 49, 1, 200, 198 );
ctx.strokeRect( 253, 1, liveGraph.width, 198 );
}
}
if( sendReport )
{
await sendReport;
}
if( receiveReport )
{
let receiveData = await receiveReport;
if( receiveData && receiveData.buffer )
{
let data = new Uint32Array( receiveData.buffer.slice( 0, 508 ) );
let intensity = data[0]>>8;
let numq = data[0] & 0xff;
let time_total = data[1]>>16;
let time_used = data[1]&0xffff;
let sample_divisor = data[2]&0xffff;
let demodbuffer = new Float32Array(numq);
let mulcoeff = 100.0 / lastIntensity;
for( var i = 0|0; i < numq; i++ )
{
let vv = IQHistoryArray[IQHistoryHead] = data[i+3];
let vi = vv >> 16;
let vq = vv & 0xffff;
if( vi >= 32768 ) vi = vi-65535;
if( vq >= 32768 ) vq = vq-65535;
let power = Math.sqrt( vi * vi + vq * vq ) * mulcoeff;
let phase = Math.atan2( vi, vq ) * 0.159155078 + 0.5;
MPHistoryArray[IQHistoryHead*2+0] = power; //Math.sqrt( real * real + imag * imag ) * mulcoeff;
MPHistoryArray[IQHistoryHead*2+1] = phase; //Math.atan2( real, imag ) * 0.159155078*0.5;
if( targetModulation == 0 )
{ /* AM */
demodbuffer[i] = power;
}
else if( targetModulation == 1 )
{ /* FM */
var diffphase = phase - FMlastphase;
FMlastphase = phase;
if( diffphase - FMiirphase < 0.0 ) diffphase += 1.0;
if( diffphase - FMiirphase > 1.0 ) diffphase -= 1.0;
FMiirphase = FMiirphase * 0.999 + diffphase * 0.001;
diffphase -= FMiirphase;
if( diffphase > 0.5 ) diffphase -= 1;
if( diffphase <-0.5 ) diffphase += 1;
var po = FMphaseout = FMphaseout * 0.993 + diffphase;
if( po < 0.0 ) po += 1.0;
if( po > 1.0 ) po -= 1.0;
demodbuffer[i] = po;
}
IQHistoryHead = (IQHistoryHead+1)%IQHistoryLen;
}
lastIntensity = intensity;
lastNumQ = numq;
lastTotalTime = time_total;
lastTimeUsed = time_used;
if( audioContext != null && playingAudioProcessor != null )
{
// TODO: Use crystalmhz
let sampleAdvance = (144000000.0/sample_divisor) / audioContext.sampleRate;
let sampleAdvanceParam = playingAudioProcessor.parameters.get("sampleAdvance");
sampleAdvanceParam.setValueAtTime( sampleAdvance, audioContext.currentTime);
playingAudioProcessor.port.postMessage( demodbuffer );
}
goodCount++;
}
else
{
badCount++;
}
}
}
else if( loopAbort )
{
if( dev )
{
console.log( "Loop Aborting, Dev Closing" );
await dev.close();
console.log( "Loop Aborting, Dev Closed." );
dev = null;
}
await sleep(100);
}
else
{
// Try opening dev.
console.log( "Attempting reconnect." );
tryConnect();
goodCount = 0;
badCount = 0;
let i = 0|0;
for( i = 0; i < 10; i++ )
{
await sleep(100);
if( dev )
{
//break;
}
}
}
}
}