mirror of
https://github.com/cnlohr/lolra.git
synced 2026-06-17 00:09:31 +00:00
Progress on goertzel's work in conjunction with web.
This commit is contained in:
+1
-1
Submodule ch32v/ch32v003fun updated: 3f6f5f7db5...aa91922a06
@@ -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
|
||||||
@@ -62,7 +62,7 @@ SOFTWARE.
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#define SH1107_128x128
|
#define SH1107_128x128
|
||||||
|
#define SSD1306_REMAP_I2C
|
||||||
#include "ssd1306_i2c.h"
|
#include "ssd1306_i2c.h"
|
||||||
#include "ssd1306.h"
|
#include "ssd1306.h"
|
||||||
|
|
||||||
@@ -86,7 +86,7 @@ void SetupADC()
|
|||||||
// XXX TODO -look into PGA
|
// XXX TODO -look into PGA
|
||||||
// XXX TODO - Look into tag-teaming the ADCs
|
// XXX TODO - Look into tag-teaming the ADCs
|
||||||
|
|
||||||
// PDA is analog input chl 7
|
// PA7 is analog input chl 7
|
||||||
GPIOA->CFGLR &= ~(0xf<<(4*7)); // CNF = 00: Analog, MODE = 00: Input
|
GPIOA->CFGLR &= ~(0xf<<(4*7)); // CNF = 00: Analog, MODE = 00: Input
|
||||||
|
|
||||||
// ADC CLK is chained off of APB2.
|
// ADC CLK is chained off of APB2.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ TARGET_MCU:=CH32V203G6U6
|
|||||||
TARGET_MCU_PACKAGE:=CH32V203G6U6
|
TARGET_MCU_PACKAGE:=CH32V203G6U6
|
||||||
CH32V003FUN:=../ch32v003fun/ch32v003fun
|
CH32V003FUN:=../ch32v003fun/ch32v003fun
|
||||||
|
|
||||||
EXTRA_CFLAGS:=-Wno-unused-function -I../../lib -I../lib
|
EXTRA_CFLAGS:=-Wno-unused-function -I../../lib -I../lib -I../ch32v003fun/examples_v20x/otg_device -I.
|
||||||
|
|
||||||
include ../ch32v003fun/ch32v003fun/ch32v003fun.mk
|
include ../ch32v003fun/ch32v003fun/ch32v003fun.mk
|
||||||
|
|
||||||
|
|||||||
@@ -63,73 +63,72 @@ SOFTWARE.
|
|||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
#define SH1107_128x128
|
#define SH1107_128x128
|
||||||
|
#define SSD1306_REMAP_I2C
|
||||||
//#define PWM_OUTPUT
|
#define PWM_OUTPUT
|
||||||
#define ENABLE_OLED
|
#define ENABLE_OLED
|
||||||
#include "ssd1306_i2c.h"
|
#include "ssd1306_i2c.h"
|
||||||
#include "ssd1306.h"
|
#include "ssd1306.h"
|
||||||
|
#include "./usb_config.h"
|
||||||
|
#include "../ch32v003fun/examples_v20x/otg_device/otgusb.h"
|
||||||
|
|
||||||
|
|
||||||
#define ADC_BUFFSIZE 1024
|
#define ADC_BUFFSIZE 1024
|
||||||
|
|
||||||
volatile uint16_t adc_buffer[ADC_BUFFSIZE];
|
volatile uint16_t adc_buffer[ADC_BUFFSIZE];
|
||||||
|
|
||||||
//#define PWM_PERIOD (31-1)
|
int g_volume_pwm = 127; // 0 - 127 (100%) (but you can go over 100)
|
||||||
//const int32_t g_goertzel_omega_per_sample = 1238618695; // 47/256 -> 27.01920 MHz
|
|
||||||
//const int32_t g_goertzel_coefficient = 870249096;
|
|
||||||
//const int32_t g_goertzel_coefficient_s = 1963250500;
|
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
#define PWM_PERIOD (30-1)
|
int g_pwm_period = (30-1);
|
||||||
#define GOERTZEL_BUFFER (752)
|
int g_goertzel_buffer = (752);
|
||||||
const int32_t g_goertzel_omega_per_sample = 2485087396; // 0.368351 of whole per step / 27.031915MHz
|
int32_t g_goertzel_omega_per_sample = 2485087396; // 0.368351 of whole per step / 27.031915MHz
|
||||||
const int32_t g_goertzel_coefficient = -1453756170;
|
int32_t g_goertzel_coefficient = -1453756170;
|
||||||
const int32_t g_goertzel_coefficient_s = 1580594514;
|
int32_t g_goertzel_coefficient_s = 1580594514;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if 1
|
||||||
|
int g_pwm_period = (30-1);
|
||||||
|
int g_goertzel_buffer = (180);
|
||||||
|
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
|
#endif
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
#define PWM_PERIOD (30-1)
|
int g_pwm_period = (31-1);
|
||||||
#define GOERTZEL_BUFFER (180)
|
int g_goertzel_buffer = (412);
|
||||||
const int32_t g_goertzel_omega_per_sample = 5509657063; // 0.816667 of whole per step / 0.880000MHz
|
|
||||||
const int32_t g_goertzel_coefficient = 873460290;
|
|
||||||
const int32_t g_goertzel_coefficient_s = -1961823932;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
#define PWM_PERIOD (31-1)
|
|
||||||
#define GOERTZEL_BUFFER (412)
|
|
||||||
const int32_t g_goertzel_omega_per_sample = 1670254667; // 0.247573 of whole per step / 1.150016MHz
|
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 = 32748822;
|
||||||
const int32_t g_goertzel_coefficient_s = 2147233926;
|
const int32_t g_goertzel_coefficient_s = 2147233926;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
#define PWM_PERIOD (30-1)
|
int g_pwm_period = (30-1);
|
||||||
#define GOERTZEL_BUFFER (576)
|
int g_goertzel_buffer = (576);
|
||||||
const int32_t g_goertzel_omega_per_sample = 1264972285; // 0.187500 of whole per step / 90.300000MHz
|
const int32_t g_goertzel_omega_per_sample = 1264972285; // 0.187500 of whole per step / 90.300000MHz
|
||||||
const int32_t g_goertzel_coefficient = 821806413;
|
const int32_t g_goertzel_coefficient = 821806413;
|
||||||
const int32_t g_goertzel_coefficient_s = 1984016189;
|
const int32_t g_goertzel_coefficient_s = 1984016189;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
#define PWM_PERIOD (30-1)
|
int g_pwm_period = (30-1);
|
||||||
#define GOERTZEL_BUFFER (320)
|
int g_goertzel_buffer = (320);
|
||||||
const int32_t g_goertzel_omega_per_sample = 990894956; // 0.146875 of whole per step / 101.505000MHz
|
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 = 1296126516;
|
||||||
const int32_t g_goertzel_coefficient_s = 1712233066;
|
const int32_t g_goertzel_coefficient_s = 1712233066;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
#define PWM_PERIOD (30-1)
|
int g_pwm_period = (30-1);
|
||||||
#define GOERTZEL_BUFFER (384)
|
int g_goertzel_buffer = (384);
|
||||||
const int32_t g_goertzel_omega_per_sample = 4251712402; // 0.630208 of whole per step / 27.025000MHz
|
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 = -1468003291;
|
||||||
const int32_t g_goertzel_coefficient_s = -1567371161;
|
const int32_t g_goertzel_coefficient_s = -1567371161;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if 1
|
#if 0
|
||||||
#define PWM_PERIOD (30-1)
|
int g_pwm_period = (30-1);
|
||||||
#define GOERTZEL_BUFFER (336)
|
int g_goertzel_buffer = (336);
|
||||||
const int32_t g_goertzel_omega_per_sample = 1827182189; // 0.270833 of whole per step / 89.900000MHz
|
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 = -280302863;
|
||||||
const int32_t g_goertzel_coefficient_s = 2129111628;
|
const int32_t g_goertzel_coefficient_s = 2129111628;
|
||||||
@@ -228,16 +227,15 @@ static void SetupTimer1()
|
|||||||
RCC->APB2PRSTR &= ~RCC_APB2Periph_TIM1;
|
RCC->APB2PRSTR &= ~RCC_APB2Periph_TIM1;
|
||||||
|
|
||||||
TIM1->PSC = 0; // Prescalar to 0x0000 (so, 48MHz base clock)
|
TIM1->PSC = 0; // Prescalar to 0x0000 (so, 48MHz base clock)
|
||||||
TIM1->ATRLR = PWM_PERIOD;
|
TIM1->ATRLR = g_pwm_period;
|
||||||
|
|
||||||
#ifdef PWM_OUTPUT
|
#ifdef PWM_OUTPUT
|
||||||
// PA9 = T1CH2.
|
// PA9 = T1CH2.
|
||||||
GPIOA->CFGHR &= ~(0xf<<(4*1));
|
funPinMode( PA9, GPIO_CFGLR_OUT_2Mhz_AF_PP );
|
||||||
GPIOA->CFGHR |= (GPIO_Speed_2MHz | GPIO_CNF_OUT_PP_AF)<<(4*1);
|
|
||||||
|
|
||||||
TIM1->CCER = TIM_CC2E | TIM_CC2P;
|
TIM1->CCER = TIM_CC2E | TIM_CC2P;
|
||||||
TIM1->CHCTLR1 |= TIM_OC2M_2 | TIM_OC2M_1 | TIM_OC2FE;
|
TIM1->CHCTLR1 |= TIM_OC2M_2 | TIM_OC2M_1 | TIM_OC2FE;
|
||||||
TIM1->CH2CVR = 5; // Actual duty cycle (Off to begin with)
|
TIM1->CH2CVR = 5; // Set duty cycle somewhere random.
|
||||||
|
|
||||||
// Enable TIM1 outputs
|
// Enable TIM1 outputs
|
||||||
TIM1->BDTR |= 0xc000;//TIM_MOE;
|
TIM1->BDTR |= 0xc000;//TIM_MOE;
|
||||||
@@ -303,7 +301,6 @@ void DMA1_Channel1_IRQHandler( void )
|
|||||||
// Add a tiny bias to the ADC to help keep goertz in range.
|
// Add a tiny bias to the ADC to help keep goertz in range.
|
||||||
const int adc_offset = (-2048) << INFADC;
|
const int adc_offset = (-2048) << INFADC;
|
||||||
|
|
||||||
|
|
||||||
while( adc_tail != adc_buffer_end )
|
while( adc_tail != adc_buffer_end )
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -356,7 +353,7 @@ void DMA1_Channel1_IRQHandler( void )
|
|||||||
adc_tail+=4;
|
adc_tail+=4;
|
||||||
goertzel_samples+=4;
|
goertzel_samples+=4;
|
||||||
if( adc_tail == adc_buffer_top ) adc_tail = adc_buffer;
|
if( adc_tail == adc_buffer_top ) adc_tail = adc_buffer;
|
||||||
if( goertzel_samples == GOERTZEL_BUFFER )
|
if( goertzel_samples == g_goertzel_buffer )
|
||||||
{
|
{
|
||||||
g_goertzelp_store = goertzel - (g_goertzel_omega_per_sample>>(29-16));
|
g_goertzelp_store = goertzel - (g_goertzel_omega_per_sample>>(29-16));
|
||||||
g_goertzelp2_store = goertzelp;
|
g_goertzelp2_store = goertzelp;
|
||||||
@@ -382,12 +379,9 @@ void DMA1_Channel1_IRQHandler( void )
|
|||||||
|
|
||||||
|
|
||||||
#ifdef PWM_OUTPUT
|
#ifdef PWM_OUTPUT
|
||||||
|
intensity = intensity * g_volume_pwm * g_pwm_period / (intensity_max>>(7-7));
|
||||||
intensity = intensity * PWM_PERIOD / (intensity_max>>7);
|
if( intensity >= g_pwm_period-1 ) intensity = g_pwm_period-2;
|
||||||
|
|
||||||
if( intensity >= PWM_PERIOD-1 ) intensity = PWM_PERIOD-2;
|
|
||||||
if( intensity < 1 ) intensity = 1;
|
if( intensity < 1 ) intensity = 1;
|
||||||
|
|
||||||
TIM1->CH2CVR = intensity; // Actual duty cycle (Off to begin with)
|
TIM1->CH2CVR = intensity; // Actual duty cycle (Off to begin with)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -410,13 +404,17 @@ void DMA1_Channel1_IRQHandler( void )
|
|||||||
//GPIOD->BSHR = 1<<16; // Turn off GPIOD0 for profiling
|
//GPIOD->BSHR = 1<<16; // Turn off GPIOD0 for profiling
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline uint32_t gets2()
|
||||||
|
{
|
||||||
|
uint32_t ret;
|
||||||
|
asm volatile( "mv %[ret], s2" : [ret]"=&r"(ret) );
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void InnerLoop()
|
void InnerLoop()
|
||||||
{
|
{
|
||||||
while(1){
|
while(1){
|
||||||
|
|
||||||
int k;
|
|
||||||
#if 0
|
#if 0
|
||||||
int adcz = adc_buffer[0];
|
int adcz = adc_buffer[0];
|
||||||
for( k = 0; k < 128; k++ )
|
for( k = 0; k < 128; k++ )
|
||||||
@@ -470,13 +468,17 @@ void InnerLoop()
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef ENABLE_OLED
|
#ifdef ENABLE_OLED
|
||||||
ssd1306_refresh();
|
|
||||||
ssd1306_setbuf(0);
|
|
||||||
|
|
||||||
char cts[32];
|
char cts[32];
|
||||||
snprintf( cts, 32, "%d %d", intensity_max, intensity );
|
snprintf( cts, 32, "%d %d", intensity_max, intensity );
|
||||||
|
|
||||||
ssd1306_drawstr( 0, 0, cts, 1 );
|
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
|
#else
|
||||||
Delay_Ms(17);
|
Delay_Ms(17);
|
||||||
#endif
|
#endif
|
||||||
@@ -484,6 +486,8 @@ void InnerLoop()
|
|||||||
// printf( "%6d %8d %8d - %8d %8d - %8d\n", g_goertzel_outs,g_goertzelp2_store, g_goertzelp_store, rr, ri, x );
|
// printf( "%6d %8d %8d - %8d %8d - %8d\n", g_goertzel_outs,g_goertzelp2_store, g_goertzelp_store, rr, ri, x );
|
||||||
|
|
||||||
// Delay_Ms(940);
|
// Delay_Ms(940);
|
||||||
|
//printf( "!!!!\n ");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -530,7 +534,7 @@ int main()
|
|||||||
|
|
||||||
#ifdef ENABLE_OLED
|
#ifdef ENABLE_OLED
|
||||||
ssd1306_i2c_setup();
|
ssd1306_i2c_setup();
|
||||||
uint8_t ret = ssd1306_i2c_init();
|
ssd1306_i2c_init();
|
||||||
ssd1306_init();
|
ssd1306_init();
|
||||||
ssd1306_setbuf(0);
|
ssd1306_setbuf(0);
|
||||||
#endif
|
#endif
|
||||||
@@ -565,5 +569,63 @@ int main()
|
|||||||
|
|
||||||
SetupTimer1();
|
SetupTimer1();
|
||||||
|
|
||||||
|
|
||||||
|
funPinMode( PA0, GPIO_CFGLR_OUT_10Mhz_PP );
|
||||||
|
|
||||||
|
USBOTGSetup();
|
||||||
InnerLoop();
|
InnerLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
uint8_t scratchpad[256];
|
||||||
|
|
||||||
|
|
||||||
|
int HandleHidUserSetReportSetup( struct _USBState * ctx, tusb_control_request_t * req )
|
||||||
|
{
|
||||||
|
int id = req->wValue & 0xff;
|
||||||
|
if( id == 0xaa )
|
||||||
|
{
|
||||||
|
// memset( scratchpad, 0x55, sizeof(scratchpad) );
|
||||||
|
//printf( "SET REPORT! %d [%02x]\n", req->wLength, scratchpad[200] );
|
||||||
|
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 )
|
||||||
|
{
|
||||||
|
//printf( "GET REPORT! %d\n", req->wLength );
|
||||||
|
ctx->pCtrlPayloadPtr = scratchpad;
|
||||||
|
return 255;
|
||||||
|
}
|
||||||
|
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 )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#define FUNCONF_USE_UARTPRINTF 0
|
#define FUNCONF_USE_UARTPRINTF 0
|
||||||
#define FUNCONF_USE_HSE 1
|
#define FUNCONF_USE_HSE 1
|
||||||
#define FUNCONF_SYSTICK_USE_HCLK 1
|
#define FUNCONF_SYSTICK_USE_HCLK 1
|
||||||
|
#define FUNCONF_ENABLE_HPE 0
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
+28
-10
@@ -1,14 +1,20 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<HTML>
|
<HTML>
|
||||||
<HEAD>
|
<HEAD>
|
||||||
|
<LINK rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon">
|
||||||
|
<META charset="UTF-8">
|
||||||
<STYLE>
|
<STYLE>
|
||||||
body {
|
body {
|
||||||
background-color: Canvas;
|
background-color: Canvas;
|
||||||
color: CanvasText;
|
color: CanvasText;
|
||||||
color-scheme: light dark;
|
|
||||||
}
|
}
|
||||||
|
:root {
|
||||||
|
color-scheme: dark;
|
||||||
|
}
|
||||||
</STYLE>
|
</STYLE>
|
||||||
|
|
||||||
|
<SCRIPT src=webhidcontrol.js></SCRIPT>
|
||||||
|
|
||||||
<SCRIPT>
|
<SCRIPT>
|
||||||
|
|
||||||
|
|
||||||
@@ -28,11 +34,11 @@ function Goertz( n, mhz, fr, brf )
|
|||||||
let omega = fr * 3.1415926535*2.0;
|
let omega = fr * 3.1415926535*2.0;
|
||||||
var textarea = document.getElementById("goertzeloutput");
|
var textarea = document.getElementById("goertzeloutput");
|
||||||
textarea.value =
|
textarea.value =
|
||||||
"#define PWM_PERIOD ("+n+"-1)\n" +
|
"int g_pwm_period = ("+n+"-1);\n" +
|
||||||
"#define GOERTZEL_BUFFER ("+brf+")\n" +
|
"int g_goertzel_buffer = ("+brf+");\n" +
|
||||||
"const int32_t g_goertzel_omega_per_sample = " + ( omega*2*(1<<29)).toFixed(0) + "; // " + ( omega / (3.1415926535*2.0)).toFixed(6) + " of whole per step / " + mhz.toFixed(6) + "MHz\n" +
|
"int32_t g_goertzel_omega_per_sample = " + ( omega*2*(1<<29)).toFixed(0) + "; // " + ( omega / (3.1415926535*2.0)).toFixed(6) + " of whole per step / " + mhz.toFixed(6) + "MHz\n" +
|
||||||
"const int32_t g_goertzel_coefficient = " + (2 * Math.cos( omega ) * (1<<30)).toFixed(0) + ";\n" +
|
"int32_t g_goertzel_coefficient = " + (2 * Math.cos( omega ) * (1<<30)).toFixed(0) + ";\n" +
|
||||||
"const int32_t g_goertzel_coefficient_s = "+ (2 * Math.sin( omega ) * (1<<30)).toFixed(0) + ";\n";
|
"int32_t g_goertzel_coefficient_s = "+ (2 * Math.sin( omega ) * (1<<30)).toFixed(0) + ";\n";
|
||||||
|
|
||||||
// Highlight its content
|
// Highlight its content
|
||||||
textarea.select();
|
textarea.select();
|
||||||
@@ -184,13 +190,19 @@ function computeTable()
|
|||||||
document.getElementById( "TABLE" ).innerHTML = contents;
|
document.getElementById( "TABLE" ).innerHTML = contents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onLoad()
|
||||||
|
{
|
||||||
|
onLoadWebHidControl();
|
||||||
|
}
|
||||||
</SCRIPT>
|
</SCRIPT>
|
||||||
</HEAD>
|
</HEAD>
|
||||||
<BODY>
|
<BODY onLoad="onLoad()">
|
||||||
|
|
||||||
<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>
|
<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>
|
<TABLE>
|
||||||
|
<TR>
|
||||||
|
<TD VALIGN=TOP>
|
||||||
|
<TABLE>
|
||||||
<TR><TD>Crystal MHz</TD><TD><INPUT ID=crystalmhz VALUE=144></TD></TR>
|
<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>Target MHz</TD><TD><INPUT ID=targetmhz VALUE=27.019360></TD></TR>
|
||||||
<TR><TD>Quanta</TD><TD><INPUT ID=quanta VALUE=1024> (Goertzel's Algorithm Only)</TD></TR>
|
<TR><TD>Quanta</TD><TD><INPUT ID=quanta VALUE=1024> (Goertzel's Algorithm Only)</TD></TR>
|
||||||
@@ -198,7 +210,13 @@ function computeTable()
|
|||||||
<TR><TD>Table Type</TD><TD><INPUT TYPE=RADIO ID=QUADRATURE NAME=computetype checked>Quadrature</INPUT><INPUT TYPE=RADIO ID=GOERTZELS NAME=computetype>Goertzels</INPUT></TD></TR>
|
<TR><TD>Table Type</TD><TD><INPUT TYPE=RADIO ID=QUADRATURE NAME=computetype checked>Quadrature</INPUT><INPUT TYPE=RADIO ID=GOERTZELS NAME=computetype>Goertzels</INPUT></TD></TR>
|
||||||
<TR><TD COLSPAN=2><INPUT TYPE=SUBMIT VALUE="Compute" ONCLICK="computeTable()"></TD></TR>
|
<TR><TD COLSPAN=2><INPUT TYPE=SUBMIT VALUE="Compute" ONCLICK="computeTable()"></TD></TR>
|
||||||
</TABLE>
|
</TABLE>
|
||||||
|
</TD>
|
||||||
|
<TD VALIGN=TOP>
|
||||||
|
Live Control:
|
||||||
|
<TABLE><TR><TD><INPUT TYPE=SUBMIT onClick="reqConnect()" VALUE="Open Device" ID=connectButton></TD><TD><DIV ID=STATUS></DIV></TD></TR></TABLE>
|
||||||
|
<DIV ID="StatusPerf"></DIV>
|
||||||
|
</TR>
|
||||||
|
</TABLE>
|
||||||
|
|
||||||
<DIV ID=TABLE></DIV>
|
<DIV ID=TABLE></DIV>
|
||||||
|
|
||||||
|
|||||||
@@ -1,713 +0,0 @@
|
|||||||
/*
|
|
||||||
* Single-File-Header for using SPI OLED
|
|
||||||
* 05-05-2023 E. Brombaugh
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef _SSD1306_H
|
|
||||||
#define _SSD1306_H
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include "font_8x8.h"
|
|
||||||
|
|
||||||
// comfortable packet size for this OLED
|
|
||||||
#define SSD1306_PSZ 32
|
|
||||||
|
|
||||||
// characteristics of each type
|
|
||||||
#if !defined (SSD1306_64X32) && !defined (SSD1306_128X32) && !defined (SSD1306_128X64) && !defined (SH1107_128x128)
|
|
||||||
#error "Please define the SSD1306_WXH resolution used in your application"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef SSD1306_64X32
|
|
||||||
#define SSD1306_W 64
|
|
||||||
#define SSD1306_H 32
|
|
||||||
#define SSD1306_FULLUSE
|
|
||||||
#define SSD1306_OFFSET 32
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef SSD1306_128X32
|
|
||||||
#define SSD1306_W 128
|
|
||||||
#define SSD1306_H 32
|
|
||||||
#define SSD1306_OFFSET 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef SSD1306_128X64
|
|
||||||
#define SSD1306_W 128
|
|
||||||
#define SSD1306_H 64
|
|
||||||
#define SSD1306_FULLUSE
|
|
||||||
#define SSD1306_OFFSET 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef SH1107_128x128
|
|
||||||
#define SH1107
|
|
||||||
#define SSD1306_FULLUSE
|
|
||||||
#define SSD1306_W 128
|
|
||||||
#define SSD1306_H 128
|
|
||||||
#define SSD1306_FULLUSE
|
|
||||||
#define SSD1306_OFFSET 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
|
||||||
* send OLED command byte
|
|
||||||
*/
|
|
||||||
uint8_t ssd1306_cmd(uint8_t cmd)
|
|
||||||
{
|
|
||||||
ssd1306_pkt_send(&cmd, 1, 1);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* send OLED data packet (up to 32 bytes)
|
|
||||||
*/
|
|
||||||
uint8_t ssd1306_data(uint8_t *data, uint8_t sz)
|
|
||||||
{
|
|
||||||
ssd1306_pkt_send(data, sz, 0);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define SSD1306_SETCONTRAST 0x81
|
|
||||||
#define SSD1306_SEGREMAP 0xA0
|
|
||||||
#define SSD1306_DISPLAYALLON_RESUME 0xA4
|
|
||||||
#define SSD1306_DISPLAYALLON 0xA5
|
|
||||||
#define SSD1306_NORMALDISPLAY 0xA6
|
|
||||||
#define SSD1306_INVERTDISPLAY 0xA7
|
|
||||||
#define SSD1306_DISPLAYOFF 0xAE
|
|
||||||
#define SSD1306_DISPLAYON 0xAF
|
|
||||||
#define SSD1306_SETDISPLAYOFFSET 0xD3
|
|
||||||
#define SSD1306_SETCOMPINS 0xDA
|
|
||||||
#define SSD1306_SETVCOMDETECT 0xDB
|
|
||||||
#define SSD1306_SETDISPLAYCLOCKDIV 0xD5
|
|
||||||
#define SSD1306_SETPRECHARGE 0xD9
|
|
||||||
#define SSD1306_SETMULTIPLEX 0xA8
|
|
||||||
#define SSD1306_SETLOWCOLUMN 0x00
|
|
||||||
#define SSD1306_SETHIGHCOLUMN 0x10
|
|
||||||
#define SSD1306_SETSTARTLINE 0x40
|
|
||||||
#define SSD1306_MEMORYMODE 0x20
|
|
||||||
#define SSD1306_COLUMNADDR 0x21
|
|
||||||
#define SSD1306_PAGEADDR 0x22
|
|
||||||
#define SSD1306_COMSCANINC 0xC0
|
|
||||||
#define SSD1306_COMSCANDEC 0xC8
|
|
||||||
#define SSD1306_CHARGEPUMP 0x8D
|
|
||||||
#define SSD1306_EXTERNALVCC 0x1
|
|
||||||
#define SSD1306_SWITCHCAPVCC 0x2
|
|
||||||
#define SSD1306_TERMINATE_CMDS 0xFF
|
|
||||||
|
|
||||||
/* choose VCC mode */
|
|
||||||
#define SSD1306_EXTERNALVCC 0x1
|
|
||||||
#define SSD1306_SWITCHCAPVCC 0x2
|
|
||||||
//#define vccstate SSD1306_EXTERNALVCC
|
|
||||||
#define vccstate SSD1306_SWITCHCAPVCC
|
|
||||||
|
|
||||||
// OLED initialization commands for 128x32
|
|
||||||
const uint8_t ssd1306_init_array[] =
|
|
||||||
{
|
|
||||||
#ifdef SH1107
|
|
||||||
SSD1306_DISPLAYOFF, // Turn OLED off
|
|
||||||
0x00, // Low column
|
|
||||||
0x10, // High column
|
|
||||||
0xb0, // Page address
|
|
||||||
0xdc, 0x00, // Set Display Start Line (Where in memory it reads from)
|
|
||||||
SSD1306_SETCONTRAST, 0x6f, // Set constrast
|
|
||||||
SSD1306_COLUMNADDR, // Set memory addressing mode
|
|
||||||
SSD1306_DISPLAYALLON_RESUME, // normal (as opposed to invert colors, always on or off.)
|
|
||||||
SSD1306_SETMULTIPLEX, (SSD1306_H-1), // Iterate over all 128 rows (Multiplex Ratio)
|
|
||||||
SSD1306_SETDISPLAYOFFSET, 0x00, // Set display offset // Where this appears on-screen (Some displays will be different)
|
|
||||||
SSD1306_SETDISPLAYCLOCKDIV, 0xf0, // Set precharge properties. THIS IS A LIE This has todo with timing. <<< This makes it go brrrrrrrrr
|
|
||||||
SSD1306_SETPRECHARGE, 0x1d, // Set pre-charge period (This controls brightness)
|
|
||||||
SSD1306_SETVCOMDETECT, 0x35, // Set vcomh
|
|
||||||
SSD1306_SETSTARTLINE | 0x0, // 0x40 | line
|
|
||||||
0xad, 0x80, // Set Charge pump
|
|
||||||
SSD1306_SEGREMAP, 0x01, // Default mapping
|
|
||||||
SSD1306_SETPRECHARGE, 0x06, // ???? No idea what this does, but this looks best.
|
|
||||||
SSD1306_SETCONTRAST, 0xfe, // Set constrast
|
|
||||||
SSD1306_SETVCOMDETECT, 0xfe, // Set vcomh
|
|
||||||
SSD1306_SETMULTIPLEX, (SSD1306_H-1), // 128-wide.
|
|
||||||
SSD1306_DISPLAYON, // Display on.
|
|
||||||
#else
|
|
||||||
SSD1306_DISPLAYOFF, // 0xAE
|
|
||||||
SSD1306_SETDISPLAYCLOCKDIV, // 0xD5
|
|
||||||
0x80, // the suggested ratio 0x80
|
|
||||||
SSD1306_SETMULTIPLEX, // 0xA8
|
|
||||||
#ifdef SSD1306_64X32
|
|
||||||
0x1F, // for 64-wide displays
|
|
||||||
#else
|
|
||||||
0x3F, // for 128-wide displays
|
|
||||||
#endif
|
|
||||||
SSD1306_SETDISPLAYOFFSET, // 0xD3
|
|
||||||
0x00, // no offset
|
|
||||||
SSD1306_SETSTARTLINE | 0x0, // 0x40 | line
|
|
||||||
SSD1306_CHARGEPUMP, // 0x8D
|
|
||||||
0x14, // enable?
|
|
||||||
SSD1306_MEMORYMODE, // 0x20
|
|
||||||
0x00, // 0x0 act like ks0108
|
|
||||||
SSD1306_SEGREMAP | 0x1, // 0xA0 | bit
|
|
||||||
SSD1306_COMSCANDEC,
|
|
||||||
SSD1306_SETCOMPINS, // 0xDA
|
|
||||||
0x12, //
|
|
||||||
SSD1306_SETCONTRAST, // 0x81
|
|
||||||
0x8F,
|
|
||||||
SSD1306_SETPRECHARGE, // 0xd9
|
|
||||||
0xF1,
|
|
||||||
SSD1306_SETVCOMDETECT, // 0xDB
|
|
||||||
0x40,
|
|
||||||
SSD1306_DISPLAYALLON_RESUME, // 0xA4
|
|
||||||
SSD1306_NORMALDISPLAY, // 0xA6
|
|
||||||
SSD1306_DISPLAYON, // 0xAF --turn on oled panel
|
|
||||||
#endif
|
|
||||||
SSD1306_TERMINATE_CMDS // 0xFF --fake command to mark end
|
|
||||||
};
|
|
||||||
|
|
||||||
// the display buffer
|
|
||||||
uint8_t ssd1306_buffer[SSD1306_W*SSD1306_H/8];
|
|
||||||
|
|
||||||
/*
|
|
||||||
* set the buffer to a color
|
|
||||||
*/
|
|
||||||
void ssd1306_setbuf(uint8_t color)
|
|
||||||
{
|
|
||||||
memset(ssd1306_buffer, color ? 0xFF : 0x00, sizeof(ssd1306_buffer));
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef SSD1306_FULLUSE
|
|
||||||
/*
|
|
||||||
* expansion array for OLED with every other row unused
|
|
||||||
*/
|
|
||||||
const uint8_t expand[16] =
|
|
||||||
{
|
|
||||||
0x00,0x02,0x08,0x0a,
|
|
||||||
0x20,0x22,0x28,0x2a,
|
|
||||||
0x80,0x82,0x88,0x8a,
|
|
||||||
0xa0,0xa2,0xa8,0xaa,
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Send the frame buffer
|
|
||||||
*/
|
|
||||||
void ssd1306_refresh(void)
|
|
||||||
{
|
|
||||||
uint16_t i;
|
|
||||||
|
|
||||||
#ifdef SH1107
|
|
||||||
|
|
||||||
ssd1306_cmd(SSD1306_MEMORYMODE); // vertical addressing mode.
|
|
||||||
|
|
||||||
for(i=0;i<SSD1306_H/8;i++)
|
|
||||||
{
|
|
||||||
ssd1306_cmd(0xb0 | i);
|
|
||||||
ssd1306_cmd( 0x00 | (0&0xf) );
|
|
||||||
ssd1306_cmd( 0x10 | (0>>4) );
|
|
||||||
ssd1306_data(&ssd1306_buffer[i*4*SSD1306_PSZ+0*SSD1306_PSZ], SSD1306_PSZ);
|
|
||||||
ssd1306_data(&ssd1306_buffer[i*4*SSD1306_PSZ+1*SSD1306_PSZ], SSD1306_PSZ);
|
|
||||||
ssd1306_data(&ssd1306_buffer[i*4*SSD1306_PSZ+2*SSD1306_PSZ], SSD1306_PSZ);
|
|
||||||
ssd1306_data(&ssd1306_buffer[i*4*SSD1306_PSZ+3*SSD1306_PSZ], SSD1306_PSZ);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
ssd1306_cmd(SSD1306_COLUMNADDR);
|
|
||||||
ssd1306_cmd(SSD1306_OFFSET); // Column start address (0 = reset)
|
|
||||||
ssd1306_cmd(SSD1306_OFFSET+SSD1306_W-1); // Column end address (127 = reset)
|
|
||||||
|
|
||||||
ssd1306_cmd(SSD1306_PAGEADDR);
|
|
||||||
ssd1306_cmd(0); // Page start address (0 = reset)
|
|
||||||
ssd1306_cmd(7); // Page end address
|
|
||||||
|
|
||||||
#ifdef SSD1306_FULLUSE
|
|
||||||
/* for fully used rows just plow thru everything */
|
|
||||||
for(i=0;i<sizeof(ssd1306_buffer);i+=SSD1306_PSZ)
|
|
||||||
{
|
|
||||||
/* send PSZ block of data */
|
|
||||||
ssd1306_data(&ssd1306_buffer[i], SSD1306_PSZ);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
/* for displays with odd rows unused expand bytes */
|
|
||||||
uint8_t tbuf[SSD1306_PSZ], j, k;
|
|
||||||
for(i=0;i<sizeof(ssd1306_buffer);i+=128)
|
|
||||||
{
|
|
||||||
/* low nybble */
|
|
||||||
for(j=0;j<128;j+=SSD1306_PSZ)
|
|
||||||
{
|
|
||||||
for(k=0;k<SSD1306_PSZ;k++)
|
|
||||||
tbuf[k] = expand[ssd1306_buffer[i+j+k]&0xf];
|
|
||||||
|
|
||||||
/* send PSZ block of data */
|
|
||||||
ssd1306_data(tbuf, SSD1306_PSZ);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* high nybble */
|
|
||||||
for(j=0;j<128;j+=SSD1306_PSZ)
|
|
||||||
{
|
|
||||||
for(k=0;k<SSD1306_PSZ;k++)
|
|
||||||
tbuf[k] = expand[(ssd1306_buffer[i+j+k]>>4)&0xf];
|
|
||||||
|
|
||||||
/* send PSZ block of data */
|
|
||||||
ssd1306_data(tbuf, SSD1306_PSZ);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* plot a pixel in the buffer
|
|
||||||
*/
|
|
||||||
void ssd1306_drawPixel(uint8_t x, uint8_t y, uint8_t color)
|
|
||||||
{
|
|
||||||
uint16_t addr;
|
|
||||||
|
|
||||||
/* clip */
|
|
||||||
if(x >= SSD1306_W)
|
|
||||||
return;
|
|
||||||
if(y >= SSD1306_H)
|
|
||||||
return;
|
|
||||||
|
|
||||||
/* compute buffer address */
|
|
||||||
addr = x + SSD1306_W*(y/8);
|
|
||||||
|
|
||||||
/* set/clear bit in buffer */
|
|
||||||
if(color)
|
|
||||||
ssd1306_buffer[addr] |= (1<<(y&7));
|
|
||||||
else
|
|
||||||
ssd1306_buffer[addr] &= ~(1<<(y&7));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* plot a pixel in the buffer
|
|
||||||
*/
|
|
||||||
void ssd1306_xorPixel(uint8_t x, uint8_t y)
|
|
||||||
{
|
|
||||||
uint16_t addr;
|
|
||||||
|
|
||||||
/* clip */
|
|
||||||
if(x >= SSD1306_W)
|
|
||||||
return;
|
|
||||||
if(y >= SSD1306_H)
|
|
||||||
return;
|
|
||||||
|
|
||||||
/* compute buffer address */
|
|
||||||
addr = x + SSD1306_W*(y/8);
|
|
||||||
|
|
||||||
ssd1306_buffer[addr] ^= (1<<(y&7));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* draw a an image from an array, directly into to the display buffer
|
|
||||||
* the color modes allow for overwriting and even layering (sprites!)
|
|
||||||
*/
|
|
||||||
void ssd1306_drawImage(uint8_t x, uint8_t y, const unsigned char* input, uint8_t width, uint8_t height, uint8_t color_mode) {
|
|
||||||
uint8_t x_absolute;
|
|
||||||
uint8_t y_absolute;
|
|
||||||
uint8_t pixel;
|
|
||||||
uint8_t bytes_to_draw = width / 8;
|
|
||||||
uint16_t buffer_addr;
|
|
||||||
|
|
||||||
for (uint8_t line = 0; line < height; line++) {
|
|
||||||
y_absolute = y + line;
|
|
||||||
if (y_absolute >= SSD1306_H) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// SSD1306 is in vertical mode, yet we want to draw horizontally, which necessitates assembling the output bytes from the input data
|
|
||||||
// bitmask for current pixel in vertical (output) byte
|
|
||||||
uint8_t v_mask = 1 << (y_absolute & 7);
|
|
||||||
|
|
||||||
for (uint8_t byte = 0; byte < bytes_to_draw; byte++) {
|
|
||||||
uint8_t input_byte = input[byte + line * bytes_to_draw];
|
|
||||||
|
|
||||||
for (pixel = 0; pixel < 8; pixel++) {
|
|
||||||
x_absolute = x + 8 * (bytes_to_draw - byte) + pixel;
|
|
||||||
if (x_absolute >= SSD1306_W) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// looking at the horizontal display, we're drawing bytes bottom to top, not left to right, hence y / 8
|
|
||||||
buffer_addr = x_absolute + SSD1306_W * (y_absolute / 8);
|
|
||||||
// state of current pixel
|
|
||||||
uint8_t input_pixel = input_byte & (1 << pixel);
|
|
||||||
|
|
||||||
switch (color_mode) {
|
|
||||||
case 0:
|
|
||||||
// write pixels as they are
|
|
||||||
ssd1306_buffer[buffer_addr] = (ssd1306_buffer[buffer_addr] & ~v_mask) | (input_pixel ? v_mask : 0);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
// write pixels after inversion
|
|
||||||
ssd1306_buffer[buffer_addr] = (ssd1306_buffer[buffer_addr] & ~v_mask) | (!input_pixel ? v_mask : 0);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
// 0 clears pixel
|
|
||||||
ssd1306_buffer[buffer_addr] &= input_pixel ? 0xFF : ~v_mask;
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
// 1 sets pixel
|
|
||||||
ssd1306_buffer[buffer_addr] |= input_pixel ? v_mask : 0;
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
// 0 sets pixel
|
|
||||||
ssd1306_buffer[buffer_addr] |= !input_pixel ? v_mask : 0;
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
// 1 clears pixel
|
|
||||||
ssd1306_buffer[buffer_addr] &= input_pixel ? ~v_mask : 0xFF;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#if SSD1306_LOG_IMAGE == 1
|
|
||||||
printf("%02x ", input_byte);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#if SSD1306_LOG_IMAGE == 1
|
|
||||||
printf("\n\r");
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* fast vert line
|
|
||||||
*/
|
|
||||||
void ssd1306_drawFastVLine(uint8_t x, uint8_t y, uint8_t h, uint8_t color)
|
|
||||||
{
|
|
||||||
// clipping
|
|
||||||
if((x >= SSD1306_W) || (y >= SSD1306_H)) return;
|
|
||||||
if((y+h-1) >= SSD1306_H) h = SSD1306_H-y;
|
|
||||||
while(h--)
|
|
||||||
{
|
|
||||||
ssd1306_drawPixel(x, y++, color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* fast horiz line
|
|
||||||
*/
|
|
||||||
void ssd1306_drawFastHLine(uint8_t x, uint8_t y, uint8_t w, uint8_t color)
|
|
||||||
{
|
|
||||||
// clipping
|
|
||||||
if((x >= SSD1306_W) || (y >= SSD1306_H)) return;
|
|
||||||
if((x+w-1) >= SSD1306_W) w = SSD1306_W-x;
|
|
||||||
|
|
||||||
while (w--)
|
|
||||||
{
|
|
||||||
ssd1306_drawPixel(x++, y, color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* abs() helper function for line drawing
|
|
||||||
*/
|
|
||||||
int16_t gfx_abs(int16_t x)
|
|
||||||
{
|
|
||||||
return (x<0) ? -x : x;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* swap() helper function for line drawing
|
|
||||||
*/
|
|
||||||
void gfx_swap(uint16_t *z0, uint16_t *z1)
|
|
||||||
{
|
|
||||||
uint16_t temp = *z0;
|
|
||||||
*z0 = *z1;
|
|
||||||
*z1 = temp;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Bresenham line draw routine swiped from Wikipedia
|
|
||||||
*/
|
|
||||||
void ssd1306_drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint8_t color)
|
|
||||||
{
|
|
||||||
int16_t steep;
|
|
||||||
int16_t deltax, deltay, error, ystep, x, y;
|
|
||||||
|
|
||||||
/* flip sense 45deg to keep error calc in range */
|
|
||||||
steep = (gfx_abs(y1 - y0) > gfx_abs(x1 - x0));
|
|
||||||
|
|
||||||
if(steep)
|
|
||||||
{
|
|
||||||
gfx_swap(&x0, &y0);
|
|
||||||
gfx_swap(&x1, &y1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* run low->high */
|
|
||||||
if(x0 > x1)
|
|
||||||
{
|
|
||||||
gfx_swap(&x0, &x1);
|
|
||||||
gfx_swap(&y0, &y1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* set up loop initial conditions */
|
|
||||||
deltax = x1 - x0;
|
|
||||||
deltay = gfx_abs(y1 - y0);
|
|
||||||
error = deltax/2;
|
|
||||||
y = y0;
|
|
||||||
if(y0 < y1)
|
|
||||||
ystep = 1;
|
|
||||||
else
|
|
||||||
ystep = -1;
|
|
||||||
|
|
||||||
/* loop x */
|
|
||||||
for(x=x0;x<=x1;x++)
|
|
||||||
{
|
|
||||||
/* plot point */
|
|
||||||
if(steep)
|
|
||||||
/* flip point & plot */
|
|
||||||
ssd1306_drawPixel(y, x, color);
|
|
||||||
else
|
|
||||||
/* just plot */
|
|
||||||
ssd1306_drawPixel(x, y, color);
|
|
||||||
|
|
||||||
/* update error */
|
|
||||||
error = error - deltay;
|
|
||||||
|
|
||||||
/* update y */
|
|
||||||
if(error < 0)
|
|
||||||
{
|
|
||||||
y = y + ystep;
|
|
||||||
error = error + deltax;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* draws a circle
|
|
||||||
*/
|
|
||||||
void ssd1306_drawCircle(int16_t x, int16_t y, int16_t radius, int8_t color)
|
|
||||||
{
|
|
||||||
/* Bresenham algorithm */
|
|
||||||
int16_t x_pos = -radius;
|
|
||||||
int16_t y_pos = 0;
|
|
||||||
int16_t err = 2 - 2 * radius;
|
|
||||||
int16_t e2;
|
|
||||||
|
|
||||||
do {
|
|
||||||
ssd1306_drawPixel(x - x_pos, y + y_pos, color);
|
|
||||||
ssd1306_drawPixel(x + x_pos, y + y_pos, color);
|
|
||||||
ssd1306_drawPixel(x + x_pos, y - y_pos, color);
|
|
||||||
ssd1306_drawPixel(x - x_pos, y - y_pos, color);
|
|
||||||
e2 = err;
|
|
||||||
if (e2 <= y_pos) {
|
|
||||||
err += ++y_pos * 2 + 1;
|
|
||||||
if(-x_pos == y_pos && e2 <= x_pos) {
|
|
||||||
e2 = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (e2 > x_pos) {
|
|
||||||
err += ++x_pos * 2 + 1;
|
|
||||||
}
|
|
||||||
} while (x_pos <= 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* draws a filled circle
|
|
||||||
*/
|
|
||||||
void ssd1306_fillCircle(int16_t x, int16_t y, int16_t radius, int8_t color)
|
|
||||||
{
|
|
||||||
/* Bresenham algorithm */
|
|
||||||
int16_t x_pos = -radius;
|
|
||||||
int16_t y_pos = 0;
|
|
||||||
int16_t err = 2 - 2 * radius;
|
|
||||||
int16_t e2;
|
|
||||||
|
|
||||||
do {
|
|
||||||
ssd1306_drawPixel(x - x_pos, y + y_pos, color);
|
|
||||||
ssd1306_drawPixel(x + x_pos, y + y_pos, color);
|
|
||||||
ssd1306_drawPixel(x + x_pos, y - y_pos, color);
|
|
||||||
ssd1306_drawPixel(x - x_pos, y - y_pos, color);
|
|
||||||
ssd1306_drawFastHLine(x + x_pos, y + y_pos, 2 * (-x_pos) + 1, color);
|
|
||||||
ssd1306_drawFastHLine(x + x_pos, y - y_pos, 2 * (-x_pos) + 1, color);
|
|
||||||
e2 = err;
|
|
||||||
if (e2 <= y_pos) {
|
|
||||||
err += ++y_pos * 2 + 1;
|
|
||||||
if(-x_pos == y_pos && e2 <= x_pos) {
|
|
||||||
e2 = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(e2 > x_pos) {
|
|
||||||
err += ++x_pos * 2 + 1;
|
|
||||||
}
|
|
||||||
} while(x_pos <= 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* draw a rectangle
|
|
||||||
*/
|
|
||||||
void ssd1306_drawRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t color)
|
|
||||||
{
|
|
||||||
ssd1306_drawFastVLine(x, y, h, color);
|
|
||||||
ssd1306_drawFastVLine(x+w-1, y, h, color);
|
|
||||||
ssd1306_drawFastHLine(x, y, w, color);
|
|
||||||
ssd1306_drawFastHLine(x, y+h-1, w, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* fill a rectangle
|
|
||||||
*/
|
|
||||||
void ssd1306_fillRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t color)
|
|
||||||
{
|
|
||||||
uint8_t m, n=y, iw = w;
|
|
||||||
|
|
||||||
/* scan vertical */
|
|
||||||
while(h--)
|
|
||||||
{
|
|
||||||
m=x;
|
|
||||||
w=iw;
|
|
||||||
/* scan horizontal */
|
|
||||||
while(w--)
|
|
||||||
{
|
|
||||||
/* invert pixels */
|
|
||||||
ssd1306_drawPixel(m++, n, color);
|
|
||||||
}
|
|
||||||
n++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* invert a rectangle in the buffer
|
|
||||||
*/
|
|
||||||
void ssd1306_xorrect(uint8_t x, uint8_t y, uint8_t w, uint8_t h)
|
|
||||||
{
|
|
||||||
uint8_t m, n=y, iw = w;
|
|
||||||
|
|
||||||
/* scan vertical */
|
|
||||||
while(h--)
|
|
||||||
{
|
|
||||||
m=x;
|
|
||||||
w=iw;
|
|
||||||
/* scan horizontal */
|
|
||||||
while(w--)
|
|
||||||
{
|
|
||||||
/* invert pixels */
|
|
||||||
ssd1306_xorPixel(m++, n);
|
|
||||||
}
|
|
||||||
n++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Draw character to the display buffer
|
|
||||||
*/
|
|
||||||
void ssd1306_drawchar(uint8_t x, uint8_t y, uint8_t chr, uint8_t color)
|
|
||||||
{
|
|
||||||
uint16_t i, j, col;
|
|
||||||
uint8_t d;
|
|
||||||
|
|
||||||
for(i=0;i<8;i++)
|
|
||||||
{
|
|
||||||
d = fontdata[(chr<<3)+i];
|
|
||||||
for(j=0;j<8;j++)
|
|
||||||
{
|
|
||||||
if(d&0x80)
|
|
||||||
col = color;
|
|
||||||
else
|
|
||||||
col = (~color)&1;
|
|
||||||
|
|
||||||
ssd1306_drawPixel(x+j, y+i, col);
|
|
||||||
|
|
||||||
// next bit
|
|
||||||
d <<= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* draw a string to the display
|
|
||||||
*/
|
|
||||||
void ssd1306_drawstr(uint8_t x, uint8_t y, char *str, uint8_t color)
|
|
||||||
{
|
|
||||||
uint8_t c;
|
|
||||||
|
|
||||||
while((c=*str++))
|
|
||||||
{
|
|
||||||
ssd1306_drawchar(x, y, c, color);
|
|
||||||
x += 8;
|
|
||||||
if(x>120)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* enum for font size
|
|
||||||
*/
|
|
||||||
typedef enum {
|
|
||||||
fontsize_8x8 = 1,
|
|
||||||
fontsize_16x16 = 2,
|
|
||||||
fontsize_32x32 = 4,
|
|
||||||
fontsize_64x64 = 8,
|
|
||||||
} font_size_t;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Draw character to the display buffer, scaled to size
|
|
||||||
*/
|
|
||||||
void ssd1306_drawchar_sz(uint8_t x, uint8_t y, uint8_t chr, uint8_t color, font_size_t font_size)
|
|
||||||
{
|
|
||||||
uint16_t i, j, col;
|
|
||||||
uint8_t d;
|
|
||||||
|
|
||||||
// Determine the font scale factor based on the font_size parameter
|
|
||||||
uint8_t font_scale = (uint8_t)font_size;
|
|
||||||
|
|
||||||
// Loop through each row of the font data
|
|
||||||
for (i = 0; i < 8; i++)
|
|
||||||
{
|
|
||||||
// Retrieve the font data for the current row
|
|
||||||
d = fontdata[(chr << 3) + i];
|
|
||||||
|
|
||||||
// Loop through each column of the font data
|
|
||||||
for (j = 0; j < 8; j++)
|
|
||||||
{
|
|
||||||
// Determine the color to draw based on the current bit in the font data
|
|
||||||
if (d & 0x80)
|
|
||||||
col = color;
|
|
||||||
else
|
|
||||||
col = (~color) & 1;
|
|
||||||
|
|
||||||
// Draw the pixel at the original size and scaled size using nested for-loops
|
|
||||||
for (uint8_t k = 0; k < font_scale; k++) {
|
|
||||||
for (uint8_t l = 0; l < font_scale; l++) {
|
|
||||||
ssd1306_drawPixel(x + (j * font_scale) + k, y + (i * font_scale) + l, col);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move to the next bit in the font data
|
|
||||||
d <<= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* draw a string to the display buffer, scaled to size
|
|
||||||
*/
|
|
||||||
void ssd1306_drawstr_sz(uint8_t x, uint8_t y, char *str, uint8_t color, font_size_t font_size)
|
|
||||||
{
|
|
||||||
uint8_t c;
|
|
||||||
|
|
||||||
while((c=*str++))
|
|
||||||
{
|
|
||||||
ssd1306_drawchar_sz(x, y, c, color, font_size);
|
|
||||||
x += 8 * font_size;
|
|
||||||
if(x>128 - 8 * font_size)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* initialize I2C and OLED
|
|
||||||
*/
|
|
||||||
uint8_t ssd1306_init(void)
|
|
||||||
{
|
|
||||||
// pulse reset
|
|
||||||
ssd1306_rst();
|
|
||||||
|
|
||||||
// initialize OLED
|
|
||||||
uint8_t *cmd_list = (uint8_t *)ssd1306_init_array;
|
|
||||||
while(*cmd_list != SSD1306_TERMINATE_CMDS)
|
|
||||||
{
|
|
||||||
if(ssd1306_cmd(*cmd_list++))
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear display
|
|
||||||
ssd1306_setbuf(0);
|
|
||||||
ssd1306_refresh();
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,374 +0,0 @@
|
|||||||
/*
|
|
||||||
* Single-File-Header for SSD1306 I2C interface
|
|
||||||
* 05-07-2023 E. Brombaugh
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef _SSD1306_I2C_H
|
|
||||||
#define _SSD1306_I2C_H
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
// SSD1306 I2C address
|
|
||||||
#define SSD1306_I2C_ADDR 0x3c
|
|
||||||
|
|
||||||
// I2C Bus clock rate - must be lower the Logic clock rate
|
|
||||||
#define SSD1306_I2C_CLKRATE 1000000
|
|
||||||
|
|
||||||
// I2C Logic clock rate - must be higher than Bus clock rate
|
|
||||||
#define SSD1306_I2C_PRERATE 2000000
|
|
||||||
|
|
||||||
// uncomment this for high-speed 36% duty cycle, otherwise 33%
|
|
||||||
#define SSD1306_I2C_DUTY
|
|
||||||
|
|
||||||
// I2C Timeout count
|
|
||||||
#define TIMEOUT_MAX 100000
|
|
||||||
|
|
||||||
// uncomment this to enable IRQ-driven operation
|
|
||||||
//#define SSD1306_I2C_IRQ
|
|
||||||
|
|
||||||
#ifdef SSD1306_I2C_IRQ
|
|
||||||
// some stuff that IRQ mode needs
|
|
||||||
volatile uint8_t ssd1306_i2c_send_buffer[64], *ssd1306_i2c_send_ptr, ssd1306_i2c_send_sz, ssd1306_i2c_irq_state;
|
|
||||||
|
|
||||||
// uncomment this to enable time diags in IRQ
|
|
||||||
//#define IRQ_DIAG
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
|
||||||
* init just I2C
|
|
||||||
*/
|
|
||||||
void ssd1306_i2c_setup(void)
|
|
||||||
{
|
|
||||||
uint16_t tempreg;
|
|
||||||
|
|
||||||
// Reset I2C1 to init all regs
|
|
||||||
RCC->APB1PRSTR |= RCC_APB1Periph_I2C1;
|
|
||||||
RCC->APB1PRSTR &= ~RCC_APB1Periph_I2C1;
|
|
||||||
|
|
||||||
// set freq
|
|
||||||
tempreg = I2C1->CTLR2;
|
|
||||||
tempreg &= ~I2C_CTLR2_FREQ;
|
|
||||||
tempreg |= (FUNCONF_SYSTEM_CORE_CLOCK/SSD1306_I2C_PRERATE)&I2C_CTLR2_FREQ;
|
|
||||||
I2C1->CTLR2 = tempreg;
|
|
||||||
|
|
||||||
// Set clock config
|
|
||||||
tempreg = 0;
|
|
||||||
#if (SSD1306_I2C_CLKRATE <= 100000)
|
|
||||||
// standard mode good to 100kHz
|
|
||||||
tempreg = (FUNCONF_SYSTEM_CORE_CLOCK/(2*SSD1306_I2C_CLKRATE))&SSD1306_I2C_CKCFGR_CCR;
|
|
||||||
#else
|
|
||||||
// fast mode over 100kHz
|
|
||||||
#ifndef SSD1306_I2C_DUTY
|
|
||||||
// 33% duty cycle
|
|
||||||
tempreg = (FUNCONF_SYSTEM_CORE_CLOCK/(3*SSD1306_I2C_CLKRATE))&SSD1306_I2C_CKCFGR_CCR;
|
|
||||||
#else
|
|
||||||
// 36% duty cycle
|
|
||||||
tempreg = (FUNCONF_SYSTEM_CORE_CLOCK/(25*SSD1306_I2C_CLKRATE))&I2C_CKCFGR_CCR;
|
|
||||||
tempreg |= I2C_CKCFGR_DUTY;
|
|
||||||
#endif
|
|
||||||
tempreg |= I2C_CKCFGR_FS;
|
|
||||||
#endif
|
|
||||||
I2C1->CKCFGR = tempreg;
|
|
||||||
|
|
||||||
#ifdef SSD1306_I2C_IRQ
|
|
||||||
// enable IRQ driven operation
|
|
||||||
NVIC_EnableIRQ(I2C1_EV_IRQn);
|
|
||||||
|
|
||||||
// initialize the state
|
|
||||||
ssd1306_i2c_irq_state = 0;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Enable I2C
|
|
||||||
I2C1->CTLR1 |= I2C_CTLR1_PE;
|
|
||||||
|
|
||||||
// set ACK mode
|
|
||||||
I2C1->CTLR1 |= I2C_CTLR1_ACK;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* error descriptions
|
|
||||||
*/
|
|
||||||
char *errstr[] =
|
|
||||||
{
|
|
||||||
"not busy",
|
|
||||||
"master mode",
|
|
||||||
"transmit mode",
|
|
||||||
"tx empty",
|
|
||||||
"transmit complete",
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* error handler
|
|
||||||
*/
|
|
||||||
uint8_t ssd1306_i2c_error(uint8_t err)
|
|
||||||
{
|
|
||||||
// report error
|
|
||||||
printf("ssd1306_i2c_error - timeout waiting for %s\n\r", errstr[err]);
|
|
||||||
|
|
||||||
// reset & initialize I2C
|
|
||||||
ssd1306_i2c_setup();
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// event codes we use
|
|
||||||
#define SSD1306_I2C_EVENT_MASTER_MODE_SELECT ((uint32_t)0x00030001) /* BUSY, MSL and SB flag */
|
|
||||||
#define SSD1306_I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED ((uint32_t)0x00070082) /* BUSY, MSL, ADDR, TXE and TRA flags */
|
|
||||||
#define SSD1306_I2C_EVENT_MASTER_BYTE_TRANSMITTED ((uint32_t)0x00070084) /* TRA, BUSY, MSL, TXE and BTF flags */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* check for 32-bit event codes
|
|
||||||
*/
|
|
||||||
uint8_t ssd1306_i2c_chk_evt(uint32_t event_mask)
|
|
||||||
{
|
|
||||||
/* read order matters here! STAR1 before STAR2!! */
|
|
||||||
uint32_t status = I2C1->STAR1 | (I2C1->STAR2<<16);
|
|
||||||
return (status & event_mask) == event_mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef SSD1306_I2C_IRQ
|
|
||||||
/*
|
|
||||||
* packet send for IRQ-driven operation
|
|
||||||
*/
|
|
||||||
uint8_t ssd1306_i2c_send(uint8_t addr, uint8_t *data, uint8_t sz)
|
|
||||||
{
|
|
||||||
int32_t timeout;
|
|
||||||
|
|
||||||
#ifdef IRQ_DIAG
|
|
||||||
GPIOC->BSHR = (1<<(3));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// error out if buffer under/overflow
|
|
||||||
if((sz > sizeof(ssd1306_i2c_send_buffer)) || !sz)
|
|
||||||
return 2;
|
|
||||||
|
|
||||||
// wait for previous packet to finish
|
|
||||||
while(ssd1306_i2c_irq_state);
|
|
||||||
|
|
||||||
#ifdef IRQ_DIAG
|
|
||||||
GPIOC->BSHR = (1<<(16+3));
|
|
||||||
GPIOC->BSHR = (1<<(4));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// init buffer for sending
|
|
||||||
ssd1306_i2c_send_sz = sz;
|
|
||||||
ssd1306_i2c_send_ptr = ssd1306_i2c_send_buffer;
|
|
||||||
memcpy((uint8_t *)ssd1306_i2c_send_buffer, data, sz);
|
|
||||||
|
|
||||||
// wait for not busy
|
|
||||||
timeout = TIMEOUT_MAX;
|
|
||||||
while((I2C1->STAR2 & I2C_STAR2_BUSY) && (timeout--));
|
|
||||||
if(timeout==-1)
|
|
||||||
return ssd1306_i2c_error(0);
|
|
||||||
|
|
||||||
// Set START condition
|
|
||||||
I2C1->CTLR1 |= I2C_CTLR1_START;
|
|
||||||
|
|
||||||
// wait for master mode select
|
|
||||||
timeout = TIMEOUT_MAX;
|
|
||||||
while((!ssd1306_i2c_chk_evt(SSD1306_I2C_EVENT_MASTER_MODE_SELECT)) && (timeout--));
|
|
||||||
if(timeout==-1)
|
|
||||||
return ssd1306_i2c_error(1);
|
|
||||||
|
|
||||||
// send 7-bit address + write flag
|
|
||||||
I2C1->DATAR = addr<<1;
|
|
||||||
|
|
||||||
// wait for transmit condition
|
|
||||||
timeout = TIMEOUT_MAX;
|
|
||||||
while((!ssd1306_i2c_chk_evt(SSD1306_I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) && (timeout--));
|
|
||||||
if(timeout==-1)
|
|
||||||
return ssd1306_i2c_error(2);
|
|
||||||
|
|
||||||
// Enable TXE interrupt
|
|
||||||
I2C1->CTLR2 |= I2C_CTLR2_ITBUFEN | I2C_CTLR2_ITEVTEN;
|
|
||||||
ssd1306_i2c_irq_state = 1;
|
|
||||||
|
|
||||||
#ifdef IRQ_DIAG
|
|
||||||
GPIOC->BSHR = (1<<(16+4));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// exit
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* IRQ handler for I2C events
|
|
||||||
*/
|
|
||||||
void I2C1_EV_IRQHandler(void) __attribute__((interrupt));
|
|
||||||
void I2C1_EV_IRQHandler(void)
|
|
||||||
{
|
|
||||||
uint16_t STAR1, STAR2 __attribute__((unused));
|
|
||||||
|
|
||||||
#ifdef IRQ_DIAG
|
|
||||||
GPIOC->BSHR = (1<<(4));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// read status, clear any events
|
|
||||||
STAR1 = I2C1->STAR1;
|
|
||||||
STAR2 = I2C1->STAR2;
|
|
||||||
|
|
||||||
/* check for TXE */
|
|
||||||
if(STAR1 & I2C_STAR1_TXE)
|
|
||||||
{
|
|
||||||
/* check for remaining data */
|
|
||||||
if(ssd1306_i2c_send_sz--)
|
|
||||||
I2C1->DATAR = *ssd1306_i2c_send_ptr++;
|
|
||||||
|
|
||||||
/* was that the last byte? */
|
|
||||||
if(!ssd1306_i2c_send_sz)
|
|
||||||
{
|
|
||||||
// disable TXE interrupt
|
|
||||||
I2C1->CTLR2 &= ~(I2C_CTLR2_ITBUFEN | I2C_CTLR2_ITEVTEN);
|
|
||||||
|
|
||||||
// reset IRQ state
|
|
||||||
ssd1306_i2c_irq_state = 0;
|
|
||||||
|
|
||||||
// wait for tx complete
|
|
||||||
while(!ssd1306_i2c_chk_evt(SSD1306_I2C_EVENT_MASTER_BYTE_TRANSMITTED));
|
|
||||||
|
|
||||||
// set STOP condition
|
|
||||||
I2C1->CTLR1 |= I2C_CTLR1_STOP;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef IRQ_DIAG
|
|
||||||
GPIOC->BSHR = (1<<(16+4));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
/*
|
|
||||||
* low-level packet send for blocking polled operation via i2c
|
|
||||||
*/
|
|
||||||
uint8_t ssd1306_i2c_send(uint8_t addr, uint8_t *data, uint8_t sz)
|
|
||||||
{
|
|
||||||
int32_t timeout;
|
|
||||||
|
|
||||||
// wait for not busy
|
|
||||||
timeout = TIMEOUT_MAX;
|
|
||||||
while((I2C1->STAR2 & I2C_STAR2_BUSY) && (timeout--));
|
|
||||||
if(timeout==-1)
|
|
||||||
return ssd1306_i2c_error(0);
|
|
||||||
|
|
||||||
// Set START condition
|
|
||||||
I2C1->CTLR1 |= I2C_CTLR1_START;
|
|
||||||
|
|
||||||
// wait for master mode select
|
|
||||||
timeout = TIMEOUT_MAX;
|
|
||||||
while((!ssd1306_i2c_chk_evt(SSD1306_I2C_EVENT_MASTER_MODE_SELECT)) && (timeout--));
|
|
||||||
if(timeout==-1)
|
|
||||||
return ssd1306_i2c_error(1);
|
|
||||||
|
|
||||||
// send 7-bit address + write flag
|
|
||||||
I2C1->DATAR = addr<<1;
|
|
||||||
|
|
||||||
// wait for transmit condition
|
|
||||||
timeout = TIMEOUT_MAX;
|
|
||||||
while((!ssd1306_i2c_chk_evt(SSD1306_I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) && (timeout--));
|
|
||||||
if(timeout==-1)
|
|
||||||
return ssd1306_i2c_error(2);
|
|
||||||
|
|
||||||
// send data one byte at a time
|
|
||||||
while(sz--)
|
|
||||||
{
|
|
||||||
// wait for TX Empty
|
|
||||||
timeout = TIMEOUT_MAX;
|
|
||||||
while(!(I2C1->STAR1 & I2C_STAR1_TXE) && (timeout--));
|
|
||||||
if(timeout==-1)
|
|
||||||
return ssd1306_i2c_error(3);
|
|
||||||
|
|
||||||
// send command
|
|
||||||
I2C1->DATAR = *data++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// wait for tx complete
|
|
||||||
timeout = TIMEOUT_MAX;
|
|
||||||
while((!ssd1306_i2c_chk_evt(SSD1306_I2C_EVENT_MASTER_BYTE_TRANSMITTED)) && (timeout--));
|
|
||||||
if(timeout==-1)
|
|
||||||
return ssd1306_i2c_error(4);
|
|
||||||
|
|
||||||
// set STOP condition
|
|
||||||
I2C1->CTLR1 |= I2C_CTLR1_STOP;
|
|
||||||
|
|
||||||
// we're happy
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
|
||||||
* high-level packet send for I2C
|
|
||||||
*/
|
|
||||||
uint8_t ssd1306_pkt_send(uint8_t *data, uint8_t sz, uint8_t cmd)
|
|
||||||
{
|
|
||||||
uint8_t pkt[33];
|
|
||||||
|
|
||||||
/* build command or data packets */
|
|
||||||
if(cmd)
|
|
||||||
{
|
|
||||||
pkt[0] = 0;
|
|
||||||
pkt[1] = *data;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
pkt[0] = 0x40;
|
|
||||||
memcpy(&pkt[1], data, sz);
|
|
||||||
}
|
|
||||||
return ssd1306_i2c_send(SSD1306_I2C_ADDR, pkt, sz+1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* init I2C and GPIO
|
|
||||||
*/
|
|
||||||
uint8_t ssd1306_i2c_init(void)
|
|
||||||
{
|
|
||||||
// Enable GPIOC and I2C
|
|
||||||
RCC->APB1PCENR |= RCC_APB1Periph_I2C1;
|
|
||||||
|
|
||||||
#ifdef CH32V20x
|
|
||||||
RCC->APB2PCENR |= RCC_APB2Periph_GPIOB;
|
|
||||||
// PB7 is SDA, 10MHz Output, alt func, open-drain
|
|
||||||
GPIOB->CFGLR &= ~(0xf<<(4*7));
|
|
||||||
GPIOB->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_OD_AF)<<(4*7);
|
|
||||||
|
|
||||||
// PB6 is SCL, 10MHz Output, alt func, open-drain
|
|
||||||
GPIOB->CFGLR &= ~(0xf<<(4*6));
|
|
||||||
GPIOB->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_OD_AF)<<(4*6);
|
|
||||||
#else
|
|
||||||
RCC->APB2PCENR |= RCC_APB2Periph_GPIOC;
|
|
||||||
// PC1 is SDA, 10MHz Output, alt func, open-drain
|
|
||||||
GPIOC->CFGLR &= ~(0xf<<(4*1));
|
|
||||||
GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_OD_AF)<<(4*1);
|
|
||||||
|
|
||||||
// PC2 is SCL, 10MHz Output, alt func, open-drain
|
|
||||||
GPIOC->CFGLR &= ~(0xf<<(4*2));
|
|
||||||
GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_OD_AF)<<(4*2);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef IRQ_DIAG
|
|
||||||
// GPIO diags on PC3/PC4
|
|
||||||
GPIOC->CFGLR &= ~(0xf<<(4*3));
|
|
||||||
GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*3);
|
|
||||||
GPIOC->BSHR = (1<<(16+3));
|
|
||||||
GPIOC->CFGLR &= ~(0xf<<(4*4));
|
|
||||||
GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*4);
|
|
||||||
GPIOC->BSHR = (1<<(16+4));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// load I2C regs
|
|
||||||
ssd1306_i2c_setup();
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
// test if SSD1306 is on the bus by sending display off command
|
|
||||||
uint8_t command = 0xAF;
|
|
||||||
return ssd1306_pkt_send(&command, 1, 1);
|
|
||||||
#else
|
|
||||||
return 0;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* reset is not used for SSD1306 I2C interface
|
|
||||||
*/
|
|
||||||
void ssd1306_rst(void)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
const expectedProductName = "CNLohr lolra ch32v203 goertzel test";
|
||||||
|
const filter = { vendorId : 0x1209, productId : 0xd035 };
|
||||||
|
let dev = null;
|
||||||
|
let loopAbort = false;
|
||||||
|
|
||||||
|
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" );
|
||||||
|
}
|
||||||
|
|
||||||
|
function onLoadWebHidControl()
|
||||||
|
{
|
||||||
|
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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendLoop()
|
||||||
|
{
|
||||||
|
const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||||
|
var arraySend = new Uint8Array(255);
|
||||||
|
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 )
|
||||||
|
{
|
||||||
|
var i = 0|0;
|
||||||
|
for( var i = 0|0; i < 255|0; i++ )
|
||||||
|
arraySend[i] = (Math.random()*256)|0;
|
||||||
|
|
||||||
|
sendReport = dev.sendFeatureReport( 0xAA, arraySend ).catch( sendLoopError );
|
||||||
|
if( !sendReport ) sendLoopError( "error creating sendFeatureReport" );
|
||||||
|
|
||||||
|
receiveReport = dev.receiveFeatureReport( 0xAA ).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 (Split between send and receive)<br>" +
|
||||||
|
(xActionSecAvg).toFixed(2) + "transactions/sec<br>" +
|
||||||
|
"Good Count: " + goodCount + "<BR>Bad Count: " + badCount;
|
||||||
|
lastTime = thisTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( sendReport )
|
||||||
|
{
|
||||||
|
await sendReport;
|
||||||
|
}
|
||||||
|
if( receiveReport )
|
||||||
|
{
|
||||||
|
// Validate Data.
|
||||||
|
let receiveData = await receiveReport;
|
||||||
|
if( receiveData && receiveData.buffer )
|
||||||
|
{
|
||||||
|
let data = new Uint8Array( receiveData.buffer );
|
||||||
|
|
||||||
|
// Tricky: Data goes:
|
||||||
|
// reportID -> Payload...
|
||||||
|
let failed = false;
|
||||||
|
for( var i = 0|0; i < 254|0; i++ )
|
||||||
|
{
|
||||||
|
if( data[i+1] != arraySend[i] )
|
||||||
|
{
|
||||||
|
console.log( "Disagreement at index " + i );
|
||||||
|
console.log( data );
|
||||||
|
console.log( arraySend );
|
||||||
|
badCount++;
|
||||||
|
failed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if( !failed ) 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user