mirror of
https://github.com/cnlohr/lolra.git
synced 2026-06-15 07:19:25 +00:00
Add test fft
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user