Add test fft

This commit is contained in:
cnlohr
2024-06-22 04:58:22 -07:00
parent 696701ffb7
commit 9bbca912a0
6 changed files with 4082 additions and 0 deletions
+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 -Ilib
include ../ch32v003fun/ch32v003fun/ch32v003fun.mk
flash : cv_flash
clean : cv_clean
rm -rf rf_data_gen chirpbuff.dat chirpbuff.h chirpbuffinfo.h
+401
View File
@@ -0,0 +1,401 @@
/**
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
#include "ssd1306_i2c.h"
#include "ssd1306.h"
#define FIX_FFT_IMPLEMENTATION
#include "fix_fft.h"
/* General note:
*/
#define Q 128
#define PWM_PERIOD (36-1) //For 27.0MHz -- It appears to be good for *244 in the table? WHY 26MHz???!?!!?
#define ADC_BUFFSIZE 256
volatile uint16_t adc_buffer[ADC_BUFFSIZE];
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;
// 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 SHADOWSTORE(X) shadowbuff[frcnt+X] = t<<3;
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)
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;
int osb = shadowbuff[0];
int16_t FSQ[Q] = { 0 };
for( k = 0; k < 128; k++ )
{
ssd1306_drawPixel( k, ((shadowbuff[k]-osb)>>3)+32, 1 );
}
int r =
//fix_fftr(shadowbuff, 7 /*1<<7 = 128 bins wide*/, 0);
fix_fft(shadowbuff, FSQ, 7 /*1<<7 = 128 bins wide*/, 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
int x = shadowbuff[ k ];
x++;
if( x < 0 ) x = 0;
if( x > 127 ) x = 127;
if( x != 0 )
ssd1306_drawFastVLine( k, 127-x, x, 1 );
if( k== 0 )
{
char cts[16];
snprintf( cts, sizeof(cts), "%7d%5d", x, osb );
ssd1306_drawstr( 0, 0, cts, 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" );
// 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;
Delay_Ms(50);
// Disable HSI
RCC->CTLR &= ~(RCC_HSION);
// printf( "RCC: %08x\n", (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
File diff suppressed because it is too large Load Diff
+713
View File
@@ -0,0 +1,713 @@
/*
* 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
+374
View File
@@ -0,0 +1,374 @@
/*
* 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