diff --git a/.gitignore b/.gitignore
index 449fc15f746d0797925a51a746d8f7fa0a2b4227..b56264024e9f393f86ba06d2418e24078af823ac 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,5 @@
 *.lst
 *.bin
 *.map
+minichlink/minichlink
+minichlink/minichlink.so
diff --git a/README.md b/README.md
index b84e35b13a6cfe51a853562a0dad172d802448df..59599c9385d8b74709a27d39525e522ffdfe8512 100644
--- a/README.md
+++ b/README.md
@@ -55,14 +55,15 @@ You can use the pre-compiled minichlink or go to minichlink dir and `make` it.
 
 ```
 cd examples/blink
-make flash
+make
 ```
 
-Just use `make` if you want to compile but not flash.
+In Linux this will "just work"(TM) using `minichlink`.  
+In Windows, if you want to use minichlink, you will need to use Zadig to install WinUSB to the WCH-Link interface 0.  
+The generated .hex file is compatible with the official WCH flash tool.  
 
-In Linux this will "just work"(TM) using `minichlink`.
-In Windows, if you want to use minichlink, you will need to use Zadig to install WinUSB to the WCH-Link interface 0.
-The generated .hex file is compatible with the official WCH flash tool.
+text = code, data = constants and initialization values, bss = uninitialized values.  
+dec is the sum of the 3 and reflects the number of bytes in flash that will get taken up by the program.
 
 
 ## ESP32S2 Programming
@@ -75,7 +76,9 @@ It enumerates as 2 interfaces.
 
 If you want to mess with the programming code in Windows, you will have to install WinUSB to the interface 0.  Then you can uninstall it in Device Manager under USB Devices.
 
-On linux you find the serial port with `ls -l /dev/ttyUSB* /dev/ttyACM*` and connect to it with `screen /dev/ttyACM0 115200`
+On linux you find the serial port with `ls -l /dev/ttyUSB* /dev/ttyACM*` and connect to it with `screen /dev/ttyACM0 115200`  
+Disconnect with `CTRL+a` `:quit`.  
+
 Adding your user to these groups will remove the need to `sudo` for access to the serial port:
 debian-based
 	`sudo usermod -a -G dialout $USER`
@@ -106,8 +109,19 @@ To use the WCH-Link in WSL, it is required to "attach" the USB hardware on the W
 6. In powershell, use the command `usbipd wsl attach --busid=<BUSID>` to attach the device at the busid from previous step
 7. You will hear the windows sound for the USB device being removed (and silently attached to WSL instead)
 8. In WSL, you will now be able to run `lsusb` and see that the SCH-Link is attached
-9. For unknown reasons, you must run make under root access in order to connect to the programmer with minichlink.  Recommend running `sudo make flash` when building and programming projects using WSL
-Feel free to solve this issue and figure out a way to give the user hardware access to WCH-Link and modify these instructions.
+9. For unknown reasons, you must run make under root access in order to connect to the programmer with minichlink.  Recommend running `sudo make` when building and programming projects using WSL. This may work too (to be confirmed):
+
+### non-root access on linux
+Unlike serial interfaces, by default, the USB device is owned by root, has group set to root and everyone else may only read.
+1. Get the vendor:device ID of the WCH-Link from `lsusb`.
+2. Create a udev rule with `sudo nano /etc/udev/rules.d/80-USB_WCH-Link.rules`, paste (CTRL+SHIFT+V) `SUBSYSTEM=="usb", ATTR{idVendor}=="1a86", ATTR{idProduct}=="8010", MODE="0666"` and save, replacing the idVendor and idProduct if what you got previously was different.
+3. Reboot or reload the udev rules with `sudo udevadm control --reload-rules && sudo udevadm trigger` 
+4. ???
+5. profit
+Now anyone on your PC has access to the WCH-Link device, so you can stop using sudo for make.  
+I don't think there are any security risks here.
+You may also tie this to the WCH-Link serial number or some other attribute from `udevadm info -a -n /dev/bus/usb/busid/deviceid` with the bus id and device id you got from lsusb earlier.
+
 
 ## minichlink
 
@@ -119,6 +133,8 @@ Anyone who wants to write a good/nice utility should probably look at the code i
 
 ## VSCode + PlatformIO
 
+Note: This is genearlly used for CI on this repo.  However, note that this is **not** the path that allows for debugging on Windows.
+
 This project can also be built, uploaded and debugged with VSCode and the PlatformIO extension. Simply clone and open this project in VSCode and have the PlatformIO extension installed.
 
 See [here](https://github.com/Community-PIO-CH32V/platform-ch32v) for further details.
diff --git a/EVT/Makefile b/attic/EVT/Makefile
similarity index 100%
rename from EVT/Makefile
rename to attic/EVT/Makefile
diff --git a/EVT/README.md b/attic/EVT/README.md
similarity index 100%
rename from EVT/README.md
rename to attic/EVT/README.md
diff --git a/attic/cnlohr-notes.txt b/attic/cnlohr-notes.txt
new file mode 100644
index 0000000000000000000000000000000000000000..91e28ea9ff88f30c531b8d62a9c7773ceb227d68
--- /dev/null
+++ b/attic/cnlohr-notes.txt
@@ -0,0 +1,7 @@
+
+Overvolting notes:
+ * Tests done at 24MHz, no PLL.
+ * If running from flash, browns out at around 8V.
+ * If running from RAM, can go up to around 10V before it blows up.
+ * IN SPITE of PC5/PC6 being listed as FT inputs, Applying about >2.5V higher than VCC will blow up the chip.
+ * Trying to run off LDO - still blows up at 10.5V.
diff --git a/ch32v003fun/ch32v003fun.h b/ch32v003fun/ch32v003fun.h
index 4b33a1b1c8fb51cb8dc0c689c6e1ef68c40e0fc1..f75a27e42006f9456ba80395ef8cfc792cc6d9a7 100644
--- a/ch32v003fun/ch32v003fun.h
+++ b/ch32v003fun/ch32v003fun.h
@@ -4838,8 +4838,8 @@ void DefaultIRQHandler( void ) __attribute__((section(".text.vector_handler")))
 
 void DelaySysTick( uint32_t n );
 
-#define Delay_Us(n) DelaySysTick( n * DELAY_US_TIME )
-#define Delay_Ms(n) DelaySysTick( n * DELAY_MS_TIME )
+#define Delay_Us(n) DelaySysTick( (n) * DELAY_US_TIME )
+#define Delay_Ms(n) DelaySysTick( (n) * DELAY_MS_TIME )
 
 // Tricky: We need to make sure main and SystemInit() are preserved.
 int main() __attribute__((used));
diff --git a/ch32v003fun/ch32v003fun.mk b/ch32v003fun/ch32v003fun.mk
index 0f74ac4ea9e4da5b2e127c888196f21fdd7a493f..00ce809be1f30a78e8dc7cf1a8273883ffe6ec9e 100644
--- a/ch32v003fun/ch32v003fun.mk
+++ b/ch32v003fun/ch32v003fun.mk
@@ -14,7 +14,7 @@ CFLAGS+= \
 	-nostdlib \
 	-I. -Wall
 
-LDFLAGS+=-T $(CH32V003FUN)/ch32v003fun.ld -Wl,--gc-sections -L../../misc -lgcc
+LDFLAGS+=-T $(CH32V003FUN)/ch32v003fun.ld -Wl,--gc-sections -L$(CH32V003FUN)/../misc -lgcc
 
 SYSTEM_C:=$(CH32V003FUN)/ch32v003fun.c
 
@@ -23,7 +23,7 @@ $(TARGET).elf : $(SYSTEM_C) $(TARGET).c $(ADDITIONAL_C_FILES)
 
 $(TARGET).bin : $(TARGET).elf
 	$(PREFIX)-size $^
-	$(PREFIX)-objdump -SD $^ > $(TARGET).lst
+	$(PREFIX)-objdump -S $^ > $(TARGET).lst
 	$(PREFIX)-objdump -t $^ > $(TARGET).map
 	$(PREFIX)-objcopy -O binary $< $(TARGET).bin
 	$(PREFIX)-objcopy -O ihex $< $(TARGET).hex
diff --git a/examples/debugprintfdemo/.vscode/c_cpp_properties.json b/examples/debugprintfdemo/.vscode/c_cpp_properties.json
index 6bf3f6166958c1f8f449fc69ae3ce9cd9c1d46ce..b52c5f523093a4d5db08d9b49089335ef1749a04 100644
--- a/examples/debugprintfdemo/.vscode/c_cpp_properties.json
+++ b/examples/debugprintfdemo/.vscode/c_cpp_properties.json
@@ -12,7 +12,8 @@
             "intelliSenseMode": "linux-clang-x64",
             "compilerArgs": [
                 "-DCH32V003FUN_BASE"
-            ]
+            ],
+            "configurationProvider": "ms-vscode.makefile-tools"
         }
     ],
     "version": 4
diff --git a/examples/dma_gpio/Makefile b/examples/dma_gpio/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..0d1503639f76acba02886087a28e5b0dac8a0300
--- /dev/null
+++ b/examples/dma_gpio/Makefile
@@ -0,0 +1,10 @@
+all : flash
+
+TARGET:=dma_gpio
+
+include ../../ch32v003fun/ch32v003fun.mk
+
+flash : cv_flash
+clean : cv_clean
+
+
diff --git a/examples/dma_gpio/dma_gpio.c b/examples/dma_gpio/dma_gpio.c
new file mode 100644
index 0000000000000000000000000000000000000000..c57306afe1a5e92c9b0bd77e444b263cb1ef4107
--- /dev/null
+++ b/examples/dma_gpio/dma_gpio.c
@@ -0,0 +1,155 @@
+// DMA GPIO Output Example - this example shows
+// how you can output 8 pins all simultaneously
+// with a planned bit pattern at 4MSamples/s.
+//
+// It outputs a pattern of repeating 010101 and
+// 00000 alternating "frames".
+//
+// The interrupt fires once at the beginning and
+// once at the end.
+//
+#define SYSTEM_CORE_CLOCK 48000000
+
+#include "ch32v003fun.h"
+#include <stdio.h>
+
+#define APB_CLOCK SYSTEM_CORE_CLOCK
+
+volatile uint32_t count;
+
+#define MBSAMPS 1024
+uint8_t memory_buffer[1024];
+
+void DMA1_Channel2_IRQHandler( void ) __attribute__((interrupt)) __attribute__((section(".srodata")));
+void DMA1_Channel2_IRQHandler( void ) 
+{
+	int i;
+	static int frameno;
+	volatile int intfr = DMA1->INTFR;
+	do
+	{
+		DMA1->INTFCR = DMA1_IT_GL2;
+
+		// Gets called at the end-of-a frame.
+		if( intfr & DMA1_IT_TC2 )
+		{
+			uint32_t fv = (frameno&1)?0:0xaa55aa55;
+			uint32_t * mbb = (uint32_t*)( memory_buffer + MBSAMPS/2 );
+			for( i = 0; i < MBSAMPS/8; i++ )
+			{
+				mbb[i] = fv; // Fill in the frame data
+			}
+			frameno++;
+		}
+		
+		// Gets called halfway through the frame
+		if( intfr & DMA1_IT_HT2 )
+		{
+			uint32_t fv = (frameno&1)?0:0xaa55aa55;
+			uint32_t * mbb = (uint32_t*)( memory_buffer );
+			for( i = 0; i < MBSAMPS/8; i++ )
+			{
+				mbb[i] = fv; // Fill in the frame data.
+			}
+		}
+		intfr = DMA1->INTFR;
+	} while( intfr );
+}
+
+int main()
+{
+	int i;
+
+	SystemInit48HSI();
+
+	// Reset all the peripherals we care about.
+	RCC->APB2PRSTR = 0xffffffff;
+	RCC->APB2PRSTR = 0;
+	
+	SetupDebugPrintf();
+
+	// Enable DMA
+	RCC->AHBPCENR = RCC_AHBPeriph_SRAM | RCC_AHBPeriph_DMA1;
+
+	// Enable GPIO and Timer 1
+	RCC->APB2PCENR = RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOC | RCC_APB2Periph_TIM1 | RCC_APB2Periph_GPIOA;
+
+	// GPIO D0/D4 Push-Pull (LED)
+	GPIOD->CFGLR =
+		(GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*1) |    // PD1 = SWIO (so we don't go off-bus)
+		(GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*0) |
+		(GPIO_Speed_10MHz | GPIO_CNF_OUT_PP_AF)<<(4*2) | // PD2 = T1CH1
+		(GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*4);
+
+	// GPIO C All output.
+	GPIOC->CFGLR =
+		(GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*0) |
+		(GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*1) |
+		(GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*2) |
+		(GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*3) |
+		(GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*4) |
+		(GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*5) |
+		(GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*6) |
+		(GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*7);
+
+
+	// Fill in the plan of what we will be sending out.
+	for( i = 0; i < sizeof(memory_buffer) / sizeof(memory_buffer[0]); i++ )
+	{
+		memory_buffer[i] = (i&1)?0xaa:0x55;
+	}
+
+	// DMA2 can be configured to attach to T1CH1
+	// The system can only DMA out at ~2.2MSPS.  2MHz is stable.
+	DMA1_Channel2->CNTR = sizeof(memory_buffer) / sizeof(memory_buffer[0]);
+	DMA1_Channel2->MADDR = (uint32_t)memory_buffer;
+	DMA1_Channel2->PADDR = (uint32_t)&GPIOC->OUTDR;
+	DMA1_Channel2->CFGR = 
+		DMA_CFGR1_DIR |                      // MEM2PERIPHERAL
+		DMA_CFGR1_PL |                       // High priority.
+		0 |                                  // 8-bit memory
+		0 |                                  // 8-bit peripheral
+		DMA_CFGR1_MINC |                     // Increase memory.
+		DMA_CFGR1_CIRC |                     // Circular mode.
+		DMA_CFGR1_HTIE |                     // Half-trigger
+		DMA_CFGR1_TCIE |                     // Whole-trigger
+		DMA_CFGR1_EN;                        // Enable
+
+	NVIC_EnableIRQ( DMA1_Channel2_IRQn );
+	DMA1_Channel2->CFGR |= DMA_CFGR1_EN;
+
+	// NOTE: You can also hook up DMA1 Channel 3 to T1C2,
+	// if you want to output to multiple IO ports at
+	// at the same time.  Just be aware you have to offset
+	// the time they read at by at least 1/8Mth of a second.
+
+	// Setup Timer1.
+	RCC->APB2PRSTR = RCC_APB2Periph_TIM1;    // Reset Timer
+	RCC->APB2PRSTR = 0;
+
+	// Timer 1 setup.
+	TIM1->PSC = 0x0000;                      // Prescaler 
+	TIM1->ATRLR = 11;                        // Auto Reload - sets period (4MHz)
+	TIM1->SWEVGR = TIM_UG | TIM_TG;          // Reload immediately + Trigger DMA
+	TIM1->CCER = TIM_CC1E | TIM_CC1P;        // Enable CH1 output, positive pol
+	TIM1->CHCTLR1 = TIM_OC1M_2 | TIM_OC1M_1; // CH1 Mode is output, PWM1 (CC1S = 00, OC1M = 110)
+	TIM1->CH1CVR = 6;                        // Set the Capture Compare Register value to 50% initially
+	TIM1->BDTR = TIM_MOE;                    // Enable TIM1 outputs
+	TIM1->CTLR1 = TIM_CEN;                   // Enable TIM1
+	TIM1->DMAINTENR = TIM_UDE | TIM_CC1DE;   // Trigger DMA on TC match 1 (DMA Ch2) and TC match 2 (DMA Ch3)
+
+	// Just debug stuff.
+	printf( "Setup OK\n" );
+
+	while(1)
+	{
+		GPIOD->BSHR = 1 | (1<<4);	 // Turn on GPIOs
+		printf( "%d\n", GPIOD->OUTDR );
+		Delay_Ms( 250 );
+		GPIOD->BSHR = (1<<16) | (1<<(16+4)); // Turn off GPIODs
+		printf( "%d\n", GPIOD->OUTDR );
+		Delay_Ms( 250 );
+		printf( "Step\n" );
+	}
+}
+
diff --git a/examples/exti_pin_change_isr/Makefile b/examples/exti_pin_change_isr/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..68e79e2eb151024dd59f97786087e47685586ff6
--- /dev/null
+++ b/examples/exti_pin_change_isr/Makefile
@@ -0,0 +1,13 @@
+all : flash
+
+TARGET:=exti_pin_change_isr
+
+test : flash
+	../../minichlink/minichlink -X ECLK 1:0:0:1:63
+
+include ../../ch32v003fun/ch32v003fun.mk
+
+flash : cv_flash
+clean : cv_clean
+
+
diff --git a/examples/exti_pin_change_isr/exti_pin_change_isr.c b/examples/exti_pin_change_isr/exti_pin_change_isr.c
new file mode 100644
index 0000000000000000000000000000000000000000..1e937e782c87178a7868d9db9a3a97fec48af045
--- /dev/null
+++ b/examples/exti_pin_change_isr/exti_pin_change_isr.c
@@ -0,0 +1,77 @@
+// Could be defined here, or in the processor defines.
+#define SYSTEM_CORE_CLOCK 48000000
+
+#include "ch32v003fun.h"
+#include <stdio.h>
+
+#define APB_CLOCK SYSTEM_CORE_CLOCK
+
+volatile uint32_t count;
+
+// This code uses fast interrupts, but be warned, in this mode your hardware stack 
+// is only 2 interrupt calls deep!!!
+//
+// When you execute code here, from RAM, the latency is between 310 and 480ns.
+// From RAM is using  __attribute__((section(".srodata")));
+// Flash starts at 340ns but seems about the same otherwise, bar the fact it prevents
+// any other apps from running.
+void EXTI7_0_IRQHandler( void ) __attribute__((naked));
+void EXTI7_0_IRQHandler( void ) 
+{
+	// Flash just a little blip.
+	GPIOC->BSHR = 2;
+	GPIOC->BSHR = (2<<16);
+
+	// Acknowledge the interrupt
+	EXTI->INTFR = 1<<3;
+
+	// Return out of function.
+	asm volatile( "mret" );
+}
+
+int main()
+{
+	SystemInit48HSI();
+
+	// Enable GPIOs
+	RCC->APB2PCENR = RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOC | RCC_APB2Periph_AFIO;
+
+	// GPIO D3 for input pin change.
+	GPIOD->CFGLR =
+		(GPIO_CNF_IN_PUPD)<<(4*1) |  // Keep SWIO enabled.
+		(GPIO_SPEED_IN | GPIO_CNF_IN_PUPD)<<(4*3);  //PD4 = GPIOD IN
+
+	// GPIO C0 Push-Pull (our output) 
+	GPIOC->CFGLR =
+		(GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*0) |
+		(GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*1);
+
+	// Ugh this is tricky.
+	// This is how we set (INTSYSCR) to enable hardware interrupt nesting
+	// and hardware stack. BUT this means we can only stack intterrupts 2 deep.
+	//
+	// Note: If you don't do this, you will need to set your functions to be
+	// __attribute__((interrupt)) instead of  __attribute__((naked))  with an
+	// mret.
+	asm volatile( "addi t1,x0, 3\ncsrrw x0, 0x804, t1\n" : : :  "t1" );
+
+
+	// Configure the IO as an interrupt.
+	AFIO->EXTICR = 3<<(3*2); //PORTD.3 (3 out front says PORTD, 3 in back says 3)
+	EXTI->INTENR = 1<<3; // Enable EXT3
+	EXTI->RTENR = 1<<3;  // Rising edge trigger
+
+	// enable interrupt
+	NVIC_EnableIRQ( EXTI7_0_IRQn );
+
+	while(1)
+	{
+		// PC1 constantly toggles, but do realy cursed instructons ;)
+		// this is so we have something we can "break" from
+		GPIOC->BSHR = 1;
+		if( ((uint32_t*)count++) )
+			GPIOC->BSHR = (1<<16);
+		if( count == 100 ) count = 0;
+	}
+}
+
diff --git a/examples/i2c_oled/Makefile b/examples/i2c_oled/Makefile
index b9b2d1a02788c3f9d2bd9736a8e4a6004cf9253b..d5253892070f6b59e7b0d1016cc2077fdf5e6220 100644
--- a/examples/i2c_oled/Makefile
+++ b/examples/i2c_oled/Makefile
@@ -2,7 +2,7 @@ all : flash
 
 TARGET:=i2c_oled
 
-CFLAGS+=-DSTDOUT_UART
+#CFLAGS+=-DSTDOUT_UART
 
 include ../../ch32v003fun/ch32v003fun.mk
 
diff --git a/examples/i2c_oled/README.md b/examples/i2c_oled/README.md
index facc372c93fcf37bdaaaa47a73731e704bc6cd08..c8ea30e96730526aa06ebd16aa9fc43eada196d2 100644
--- a/examples/i2c_oled/README.md
+++ b/examples/i2c_oled/README.md
@@ -10,31 +10,38 @@ different graphic screens to test out the various drawing primitives.
 https://user-images.githubusercontent.com/1132011/230734071-dee305de-5aad-4ca0-a422-5fb31d2bb0e0.mp4
 
 ## Build options
-There are a few build-time options in the i2c.h source:
-* I2C_CLKRATE - defines the I2C bus clock rate. Both 100kHz and 400kHz are supported.
-800kHz has been seen to work when I2C_PRERATE is 1000000, but 1MHz did not. To
-use higher bus rates you must increase I2C_PRERATE at the expense of higher power
-consumption.
-* I2C_PRERATE - defines the I2C logic clock rate. Must be higher than I2C_CLKRATE.
-Keep this value as low as possible (but not lower than 1000000) to ensure low power
-operaton.
-* I2C_DUTY - for I2C_CLKRATE > 100kHz this specifies the duty cycle, either 33% or 36%.
-* TIMEOUT_MAX - the amount of tries in busy-wait loops before giving up. This value
-depends on the I2C_CLKRATE and should not affect normal operation.
-* I2C_IRQ - chooses IRQ-based operation instead of busy-wait polling. Useful to
-free up CPU resources but should be used carefully since it has more potential
-mysterious effects and less error checking.
+There are a few build-time options in the source - 
+
+In i2c_oled.c:
+* SSD1306_64X32, SSD1306_128X32, SSD1306_128X64 - choose only one of these
+depending on the type of OLED you've got.
+
+In ssd1306.h
+* SSD1306_PSZ - the number of bytes to send per I2C data packet. The default value
+of 32 seems to work well. Smaller values are allowed but may result in slower
+refresh rates.
+
+In ssd1306_i2c.h
+* SSD1306_I2C_ADDR - the I2C address of your OLED display. The default is 0x3c
+which should work for most devices. Use 0x3d if you've pulled the SA0 line high.
+* SSD1306_I2C_CLKRATE - defines the I2C bus clock rate. Both 100kHz and 400kHz
+are supported. 800kHz has been seen to work when I2C_PRERATE is 1000000, but 1MHz
+did not. To use higher bus rates you must increase I2C_PRERATE at the expense of
+higher power consumption.
+* SSD1306_I2C_PRERATE - defines the I2C logic clock rate. Must be higher than
+I2C_CLKRATE. Keep this value as low as possible (but not lower than 1000000) to
+ensure low power operaton.
+* SSD1306_I2C_DUTY - for I2C_CLKRATE > 100kHz this specifies the duty cycle,
+either 33% or 36%.
+* TIMEOUT_MAX - the amount of tries in busy-wait loops before giving up. This
+value depends on the I2C_CLKRATE and should not affect normal operation.
+* SSD1306_I2C_IRQ - chooses IRQ-based operation instead of busy-wait polling.
+Useful to free up CPU resources but should be used carefully since it has more
+potential mysterious effects and less error checking.
 * IRQ_DIAG - enables timing analysis via GPIO toggling. Don't enable this unless
 you know what you're doing.
 
 There are a few build-time options in the oled.h source:
-* OLED_ADDR - the I2C address of your OLED display. The default is 0x3c which
-should work for most devices. Use 0x3d if you've pulled the SA0 line high.
-* OLED_PSZ - the number of bytes to send per I2C data packet. The default value
-of 32 seems to work well. Smaller values are allowed but may result in slower
-refresh rates.
-* OLED_64X32, OLED_128X32, OLED_128X64 - choose only one of these depending on
-the type of OLED you've got.
 
 ## Use
 Connect an SSD1306-based OLED in I2C interface mode to pins PC1 (SDA) and PC2 (SCL)
diff --git a/examples/i2c_oled/i2c_oled.c b/examples/i2c_oled/i2c_oled.c
index f2f43ff5d9a8035ba87538ca63ec60f890f76b7a..94e6ce6390af94aa88c6d4020f01e71d1016b563 100644
--- a/examples/i2c_oled/i2c_oled.c
+++ b/examples/i2c_oled/i2c_oled.c
@@ -7,9 +7,15 @@
 #define SYSTEM_CORE_CLOCK 48000000
 #define APB_CLOCK SYSTEM_CORE_CLOCK
 
+// what type of OLED - uncomment just one
+//#define SSD1306_64X32
+#define SSD1306_128X32
+//#define SSD1306_128X64
+
 #include "ch32v003fun.h"
 #include <stdio.h>
-#include "oled.h"
+#include "ssd1306_i2c.h"
+#include "ssd1306.h"
 
 int main()
 {
@@ -17,39 +23,44 @@ int main()
 	SystemInit48HSI();
 
 	// start serial @ default 115200bps
+#ifdef STDOUT_UART
 	SetupUART( UART_BRR );
 	Delay_Ms( 100 );
+#else
+	SetupDebugPrintf();
+#endif
 	printf("\r\r\n\ni2c_oled example\n\r");
 
 	// init i2c and oled
 	Delay_Ms( 100 );	// give OLED some more time
 	printf("initializing i2c oled...");
-	if(!oled_init())
+	if(!ssd1306_i2c_init())
 	{
+		ssd1306_init();
 		printf("done.\n\r");
 		
 		printf("Looping on test modes...");
 		while(1)
 		{
-			for(uint8_t mode=0;mode<(OLED_H>32?8:7);mode++)
+			for(uint8_t mode=0;mode<(SSD1306_H>32?8:7);mode++)
 			{
 				// clear buffer for next mode
-				oled_setbuf(0);
+				ssd1306_setbuf(0);
 
 				switch(mode)
 				{
 					case 0:
 						printf("buffer fill with binary\n\r");
-						for(int i=0;i<sizeof(oled_buffer);i++)
-							oled_buffer[i] = i;
+						for(int i=0;i<sizeof(ssd1306_buffer);i++)
+							ssd1306_buffer[i] = i;
 						break;
 					
 					case 1:
 						printf("pixel plots\n\r");
-						for(int i=0;i<OLED_W;i++)
+						for(int i=0;i<SSD1306_W;i++)
 						{
-							oled_drawPixel(i, i/(OLED_W/OLED_H), 1);
-							oled_drawPixel(i, OLED_H-1-(i/(OLED_W/OLED_H)), 1);
+							ssd1306_drawPixel(i, i/(SSD1306_W/SSD1306_H), 1);
+							ssd1306_drawPixel(i, SSD1306_H-1-(i/(SSD1306_W/SSD1306_H)), 1);
 						}
 						break;
 					
@@ -57,61 +68,61 @@ int main()
 						{
 							printf("Line plots\n\r");
 							uint8_t y= 0;
-							for(uint8_t x=0;x<OLED_W;x+=16)
+							for(uint8_t x=0;x<SSD1306_W;x+=16)
 							{
-								oled_drawLine(x, 0, OLED_W, y, 1);
-								oled_drawLine(OLED_W-x, OLED_H, 0, OLED_H-y, 1);
-								y+= OLED_H/8;
+								ssd1306_drawLine(x, 0, SSD1306_W, y, 1);
+								ssd1306_drawLine(SSD1306_W-x, SSD1306_H, 0, SSD1306_H-y, 1);
+								y+= SSD1306_H/8;
 							}
 						}
 						break;
 						
 					case 3:
 						printf("Circles empty and filled\n\r");
-						for(uint8_t x=0;x<OLED_W;x+=16)
+						for(uint8_t x=0;x<SSD1306_W;x+=16)
 							if(x<64)
-								oled_drawCircle(x, OLED_H/2, 15, 1);
+								ssd1306_drawCircle(x, SSD1306_H/2, 15, 1);
 							else
-								oled_fillCircle(x, OLED_H/2, 15, 1);
+								ssd1306_fillCircle(x, SSD1306_H/2, 15, 1);
 						break;
 					
 					case 4:
 						printf("Unscaled Text\n\r");
-						oled_drawstr(0,0, "This is a test", 1);
-						oled_drawstr(0,8, "of the emergency", 1);
-						oled_drawstr(0,16,"broadcasting", 1);
-						oled_drawstr(0,24,"system.",1);
-						if(OLED_H>32)
+						ssd1306_drawstr(0,0, "This is a test", 1);
+						ssd1306_drawstr(0,8, "of the emergency", 1);
+						ssd1306_drawstr(0,16,"broadcasting", 1);
+						ssd1306_drawstr(0,24,"system.",1);
+						if(SSD1306_H>32)
 						{
-							oled_drawstr(0,32, "Lorem ipsum", 1);
-							oled_drawstr(0,40, "dolor sit amet,", 1);
-							oled_drawstr(0,48,"consectetur", 1);
-							oled_drawstr(0,56,"adipiscing",1);
+							ssd1306_drawstr(0,32, "Lorem ipsum", 1);
+							ssd1306_drawstr(0,40, "dolor sit amet,", 1);
+							ssd1306_drawstr(0,48,"consectetur", 1);
+							ssd1306_drawstr(0,56,"adipiscing",1);
 						}
-						oled_xorrect(OLED_W/2, 0, OLED_W/2, OLED_W);
+						ssd1306_xorrect(SSD1306_W/2, 0, SSD1306_W/2, SSD1306_W);
 						break;
 						
 					case 5:
 						printf("Scaled Text 1, 2\n\r");
-						oled_drawstr_sz(0,0, "sz 8x8", 1, fontsize_8x8);
-						oled_drawstr_sz(0,16, "16x16", 1, fontsize_16x16);
+						ssd1306_drawstr_sz(0,0, "sz 8x8", 1, fontsize_8x8);
+						ssd1306_drawstr_sz(0,16, "16x16", 1, fontsize_16x16);
 						break;
 					
 					case 6:
 						printf("Scaled Text 4\n\r");
-						oled_drawstr_sz(0,0, "32x32", 1, fontsize_32x32);
+						ssd1306_drawstr_sz(0,0, "32x32", 1, fontsize_32x32);
 						break;
 					
 					
 					case 7:
 						printf("Scaled Text 8\n\r");
-						oled_drawstr_sz(0,0, "64", 1, fontsize_64x64);
+						ssd1306_drawstr_sz(0,0, "64", 1, fontsize_64x64);
 						break;
 
 					default:
 						break;
 				}
-				oled_refresh();
+				ssd1306_refresh();
 			
 				Delay_Ms(2000);
 			}
diff --git a/examples/i2c_oled/oled.h b/examples/i2c_oled/ssd1306.h
similarity index 62%
rename from examples/i2c_oled/oled.h
rename to examples/i2c_oled/ssd1306.h
index 378a27db98063cc95b6a60f0c6b81dfa59b389ea..19ea88db1053ad211038656b1dbe1452f8f6e2bb 100644
--- a/examples/i2c_oled/oled.h
+++ b/examples/i2c_oled/ssd1306.h
@@ -1,73 +1,59 @@
 /*
- * Single-File-Header for using I2C OLED
- * 03-29-2023 E. Brombaugh
+ * Single-File-Header for using SPI OLED
+ * 05-05-2023 E. Brombaugh
  */
 
-#ifndef _OLED_H
-#define _OLED_H
+#ifndef _SSD1306_H
+#define _SSD1306_H
 
 #include <stdint.h>
 #include <string.h>
-#include "i2c.h"
 #include "font_8x8.h"
 
-// OLED I2C address
-#define OLED_ADDR 0x3c
-
-// comfortable I2C packet size for this OLED
-#define OLED_PSZ 32
-
-// what type of OLED - uncomment just one
-//#define OLED_64X32
-#define OLED_128X32
-//#define OLED_128X64
+// comfortable packet size for this OLED
+#define SSD1306_PSZ 32
 
 // characteristics of each type
-#ifdef OLED_64X32
-#define OLED_W 64
-#define OLED_H 32
-#define OLED_FULLUSE
-#define OLED_OFFSET 32
+#if !defined (SSD1306_64X32) && !defined (SSD1306_128X32) && !defined (SSD1306_128X64)
+	#error "Please define the SSD1306_WXH resolution used in your application"
 #endif
 
-#ifdef OLED_128X32
-#define OLED_W 128
-#define OLED_H 32
-#define OLED_OFFSET 0
+#ifdef SSD1306_64X32
+#define SSD1306_W 64
+#define SSD1306_H 32
+#define SSD1306_FULLUSE
+#define SSD1306_OFFSET 32
 #endif
 
-#ifdef OLED_128X64
-#define OLED_W 128
-#define OLED_H 64
-#define OLED_FULLUSE
-#define OLED_OFFSET 0
+#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
 
 /*
  * send OLED command byte
  */
-uint8_t oled_cmd(uint8_t cmd)
+uint8_t ssd1306_cmd(uint8_t cmd)
 {
-	uint8_t pkt[2];
-	
-	pkt[0] = 0;
-	pkt[1] = cmd;
-	return i2c_send(OLED_ADDR, pkt, 2);
+	ssd1306_pkt_send(&cmd, 1, 1);
+	return 0;
 }
 
 /*
  * send OLED data packet (up to 32 bytes)
  */
-uint8_t oled_data(uint8_t *data, uint8_t sz)
+uint8_t ssd1306_data(uint8_t *data, uint8_t sz)
 {
-	uint8_t pkt[33];
-	
-	// max 32 bytes
-	sz = sz > 32 ? 32 : sz;
-	
-	pkt[0] = 0x40;
-	memcpy(&pkt[1], data, sz);
-	return i2c_send(OLED_ADDR, pkt, sz+1);
+	ssd1306_pkt_send(data, sz, 0);
+	return 0;
 }
 
 #define SSD1306_SETCONTRAST 0x81
@@ -104,13 +90,13 @@ uint8_t oled_data(uint8_t *data, uint8_t sz)
 #define vccstate SSD1306_SWITCHCAPVCC
 
 // OLED initialization commands for 128x32
-const uint8_t oled_init_array[] =
+const uint8_t ssd1306_init_array[] =
 {
     SSD1306_DISPLAYOFF,                    // 0xAE
     SSD1306_SETDISPLAYCLOCKDIV,            // 0xD5
     0x80,                                  // the suggested ratio 0x80
     SSD1306_SETMULTIPLEX,                  // 0xA8
-#ifdef OLED_64X32
+#ifdef SSD1306_64X32
 	0x1F,                                  // for 64-wide displays
 #else
 	0x3F,                                  // for 128-wide displays
@@ -139,17 +125,17 @@ const uint8_t oled_init_array[] =
 };
 
 // the display buffer
-uint8_t oled_buffer[OLED_W*OLED_H/8];
+uint8_t ssd1306_buffer[SSD1306_W*SSD1306_H/8];
 
 /*
  * set the buffer to a color
  */
-void oled_setbuf(uint8_t color)
+void ssd1306_setbuf(uint8_t color)
 {
-	memset(oled_buffer, color ? 0xFF : 0x00, sizeof(oled_buffer));
+	memset(ssd1306_buffer, color ? 0xFF : 0x00, sizeof(ssd1306_buffer));
 }
 
-#ifndef OLED_FULLUSE
+#ifndef SSD1306_FULLUSE
 /*
  * expansion array for OLED with every other row unused
  */
@@ -165,48 +151,48 @@ const uint8_t expand[16] =
 /*
  * Send the frame buffer
  */
-void oled_refresh(void)
+void ssd1306_refresh(void)
 {
 	uint16_t i;
 	
-	oled_cmd(SSD1306_COLUMNADDR);
-	oled_cmd(OLED_OFFSET);   // Column start address (0 = reset)
-	oled_cmd(OLED_OFFSET+OLED_W-1); // Column end address (127 = reset)
+	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)
 	
-	oled_cmd(SSD1306_PAGEADDR);
-	oled_cmd(0); // Page start address (0 = reset)
-	oled_cmd(7); // Page end address
+	ssd1306_cmd(SSD1306_PAGEADDR);
+	ssd1306_cmd(0); // Page start address (0 = reset)
+	ssd1306_cmd(7); // Page end address
 
-#ifdef OLED_FULLUSE
+#ifdef SSD1306_FULLUSE
 	/* for fully used rows just plow thru everything */
-    for(i=0;i<sizeof(oled_buffer);i+=OLED_PSZ)
+    for(i=0;i<sizeof(ssd1306_buffer);i+=SSD1306_PSZ)
 	{
 		/* send PSZ block of data */
-		oled_data(&oled_buffer[i], OLED_PSZ);
+		ssd1306_data(&ssd1306_buffer[i], SSD1306_PSZ);
 	}
 #else
 	/* for displays with odd rows unused expand bytes */
-	uint8_t tbuf[OLED_PSZ], j, k;
-    for(i=0;i<sizeof(oled_buffer);i+=128)
+	uint8_t tbuf[SSD1306_PSZ], j, k;
+    for(i=0;i<sizeof(ssd1306_buffer);i+=128)
 	{
 		/* low nybble */
-		for(j=0;j<128;j+=OLED_PSZ)
+		for(j=0;j<128;j+=SSD1306_PSZ)
 		{
-			for(k=0;k<OLED_PSZ;k++)
-				tbuf[k] = expand[oled_buffer[i+j+k]&0xf];
+			for(k=0;k<SSD1306_PSZ;k++)
+				tbuf[k] = expand[ssd1306_buffer[i+j+k]&0xf];
 			
 			/* send PSZ block of data */
-			oled_data(tbuf, OLED_PSZ);
+			ssd1306_data(tbuf, SSD1306_PSZ);
 		}
 		
 		/* high nybble */
-		for(j=0;j<128;j+=OLED_PSZ)
+		for(j=0;j<128;j+=SSD1306_PSZ)
 		{
-			for(k=0;k<OLED_PSZ;k++)
-				tbuf[k] = expand[(oled_buffer[i+j+k]>>4)&0xf];
+			for(k=0;k<SSD1306_PSZ;k++)
+				tbuf[k] = expand[(ssd1306_buffer[i+j+k]>>4)&0xf];
 			
 			/* send PSZ block of data */
-			oled_data(tbuf, OLED_PSZ);
+			ssd1306_data(tbuf, SSD1306_PSZ);
 		}
 	}
 #endif
@@ -215,71 +201,71 @@ void oled_refresh(void)
 /*
  * plot a pixel in the buffer
  */
-void oled_drawPixel(uint8_t x, uint8_t y, uint8_t color)
+void ssd1306_drawPixel(uint8_t x, uint8_t y, uint8_t color)
 {
 	uint16_t addr;
 	
 	/* clip */
-	if(x >= OLED_W)
+	if(x >= SSD1306_W)
 		return;
-	if(y >= OLED_H)
+	if(y >= SSD1306_H)
 		return;
 	
 	/* compute buffer address */
-	addr = x + OLED_W*(y/8);
+	addr = x + SSD1306_W*(y/8);
 	
 	/* set/clear bit in buffer */
 	if(color)
-		oled_buffer[addr] |= (1<<(y&7));
+		ssd1306_buffer[addr] |= (1<<(y&7));
 	else
-		oled_buffer[addr] &= ~(1<<(y&7));
+		ssd1306_buffer[addr] &= ~(1<<(y&7));
 }
 
 /*
  * plot a pixel in the buffer
  */
-void oled_xorPixel(uint8_t x, uint8_t y)
+void ssd1306_xorPixel(uint8_t x, uint8_t y)
 {
 	uint16_t addr;
 	
 	/* clip */
-	if(x >= OLED_W)
+	if(x >= SSD1306_W)
 		return;
-	if(y >= OLED_H)
+	if(y >= SSD1306_H)
 		return;
 	
 	/* compute buffer address */
-	addr = x + OLED_W*(y/8);
+	addr = x + SSD1306_W*(y/8);
 	
-	oled_buffer[addr] ^= (1<<(y&7));
+	ssd1306_buffer[addr] ^= (1<<(y&7));
 }
 
 /*
  *  fast vert line
  */
-void oled_drawFastVLine(uint8_t x, uint8_t y, uint8_t h, uint8_t color)
+void ssd1306_drawFastVLine(uint8_t x, uint8_t y, uint8_t h, uint8_t color)
 {
 	// clipping
-	if((x >= OLED_W) || (y >= OLED_H)) return;
-	if((y+h-1) >= OLED_H) h = OLED_H-y;
+	if((x >= SSD1306_W) || (y >= SSD1306_H)) return;
+	if((y+h-1) >= SSD1306_H) h = SSD1306_H-y;
 	while(h--)
 	{
-        oled_drawPixel(x, y++, color);
+        ssd1306_drawPixel(x, y++, color);
 	}
 }
 
 /*
  *  fast horiz line
  */
-void oled_drawFastHLine(uint8_t x, uint8_t y, uint8_t w, uint8_t color)
+void ssd1306_drawFastHLine(uint8_t x, uint8_t y, uint8_t w, uint8_t color)
 {
 	// clipping
-	if((x >= OLED_W) || (y >= OLED_H)) return;
-	if((x+w-1) >= OLED_W)  w = OLED_W-x;
+	if((x >= SSD1306_W) || (y >= SSD1306_H)) return;
+	if((x+w-1) >= SSD1306_W)  w = SSD1306_W-x;
 
 	while (w--)
 	{
-        oled_drawPixel(x++, y, color);
+        ssd1306_drawPixel(x++, y, color);
 	}
 }
 
@@ -304,7 +290,7 @@ void gfx_swap(uint16_t *z0, uint16_t *z1)
 /*
  * Bresenham line draw routine swiped from Wikipedia
  */
-void oled_drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint8_t color)
+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;
@@ -341,10 +327,10 @@ void oled_drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint8_t c
 		/* plot point */
 		if(steep)
 			/* flip point & plot */
-			oled_drawPixel(y, x, color);
+			ssd1306_drawPixel(y, x, color);
 		else
 			/* just plot */
-			oled_drawPixel(x, y, color);
+			ssd1306_drawPixel(x, y, color);
 
 		/* update error */
 		error = error - deltay;
@@ -361,7 +347,7 @@ void oled_drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint8_t c
 /*
  *  draws a circle
  */
-void oled_drawCircle(int16_t x, int16_t y, int16_t radius, int8_t color)
+void ssd1306_drawCircle(int16_t x, int16_t y, int16_t radius, int8_t color)
 {
     /* Bresenham algorithm */
     int16_t x_pos = -radius;
@@ -370,10 +356,10 @@ void oled_drawCircle(int16_t x, int16_t y, int16_t radius, int8_t color)
     int16_t e2;
 
     do {
-        oled_drawPixel(x - x_pos, y + y_pos, color);
-        oled_drawPixel(x + x_pos, y + y_pos, color);
-        oled_drawPixel(x + x_pos, y - y_pos, color);
-        oled_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_drawPixel(x - x_pos, y - y_pos, color);
         e2 = err;
         if (e2 <= y_pos) {
             err += ++y_pos * 2 + 1;
@@ -390,7 +376,7 @@ void oled_drawCircle(int16_t x, int16_t y, int16_t radius, int8_t color)
 /*
  *  draws a filled circle
  */
-void oled_fillCircle(int16_t x, int16_t y, int16_t radius, int8_t color)
+void ssd1306_fillCircle(int16_t x, int16_t y, int16_t radius, int8_t color)
 {
     /* Bresenham algorithm */
     int16_t x_pos = -radius;
@@ -399,12 +385,12 @@ void oled_fillCircle(int16_t x, int16_t y, int16_t radius, int8_t color)
     int16_t e2;
 
     do {
-        oled_drawPixel(x - x_pos, y + y_pos, color);
-        oled_drawPixel(x + x_pos, y + y_pos, color);
-        oled_drawPixel(x + x_pos, y - y_pos, color);
-        oled_drawPixel(x - x_pos, y - y_pos, color);
-        oled_drawFastHLine(x + x_pos, y + y_pos, 2 * (-x_pos) + 1, color);
-        oled_drawFastHLine(x + x_pos, y - y_pos, 2 * (-x_pos) + 1, 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_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;
@@ -421,18 +407,18 @@ void oled_fillCircle(int16_t x, int16_t y, int16_t radius, int8_t color)
 /*
  *  draw a rectangle
  */
-void oled_drawRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t color)
+void ssd1306_drawRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t color)
 {
-	oled_drawFastVLine(x, y, h, color);
-	oled_drawFastVLine(x+w-1, y, h, color);
-	oled_drawFastHLine(x, y, w, color);
-	oled_drawFastHLine(x, y+h-1, w, 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 oled_fillRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t color)
+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;
 	
@@ -445,7 +431,7 @@ void oled_fillRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t color)
 		while(w--)
 		{
 			/* invert pixels */
-			oled_drawPixel(m++, n, color);
+			ssd1306_drawPixel(m++, n, color);
 		}
 		n++;
 	}
@@ -454,7 +440,7 @@ void oled_fillRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t color)
 /*
  * invert a rectangle in the buffer
  */
-void oled_xorrect(uint8_t x, uint8_t y, uint8_t w, uint8_t h)
+void ssd1306_xorrect(uint8_t x, uint8_t y, uint8_t w, uint8_t h)
 {
 	uint8_t m, n=y, iw = w;
 	
@@ -467,7 +453,7 @@ void oled_xorrect(uint8_t x, uint8_t y, uint8_t w, uint8_t h)
 		while(w--)
 		{
 			/* invert pixels */
-			oled_xorPixel(m++, n);
+			ssd1306_xorPixel(m++, n);
 		}
 		n++;
 	}
@@ -476,7 +462,7 @@ void oled_xorrect(uint8_t x, uint8_t y, uint8_t w, uint8_t h)
 /*
  * Draw character to the display buffer
  */
-void oled_drawchar(uint8_t x, uint8_t y, uint8_t chr, uint8_t color)
+void ssd1306_drawchar(uint8_t x, uint8_t y, uint8_t chr, uint8_t color)
 {
 	uint16_t i, j, col;
 	uint8_t d;
@@ -491,7 +477,7 @@ void oled_drawchar(uint8_t x, uint8_t y, uint8_t chr, uint8_t color)
 			else
 				col = (~color)&1;
 			
-			oled_drawPixel(x+j, y+i, col);
+			ssd1306_drawPixel(x+j, y+i, col);
 			
 			// next bit
 			d <<= 1;
@@ -502,13 +488,13 @@ void oled_drawchar(uint8_t x, uint8_t y, uint8_t chr, uint8_t color)
 /*
  * draw a string to the display
  */
-void oled_drawstr(uint8_t x, uint8_t y, char *str, uint8_t color)
+void ssd1306_drawstr(uint8_t x, uint8_t y, char *str, uint8_t color)
 {
 	uint8_t c;
 	
 	while((c=*str++))
 	{
-		oled_drawchar(x, y, c, color);
+		ssd1306_drawchar(x, y, c, color);
 		x += 8;
 		if(x>120)
 			break;
@@ -528,7 +514,7 @@ typedef enum {
 /*
  * Draw character to the display buffer, scaled to size
  */
-void oled_drawchar_sz(uint8_t x, uint8_t y, uint8_t chr, uint8_t color, font_size_t font_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;
@@ -554,7 +540,7 @@ void oled_drawchar_sz(uint8_t x, uint8_t y, uint8_t chr, uint8_t color, font_siz
             // 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++) {
-                    oled_drawPixel(x + (j * font_scale) + k, y + (i * font_scale) + l, col);
+                    ssd1306_drawPixel(x + (j * font_scale) + k, y + (i * font_scale) + l, col);
                 }
             }
 
@@ -567,13 +553,13 @@ void oled_drawchar_sz(uint8_t x, uint8_t y, uint8_t chr, uint8_t color, font_siz
 /*
  * draw a string to the display buffer, scaled to size
  */
-void oled_drawstr_sz(uint8_t x, uint8_t y, char *str, uint8_t color, font_size_t font_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++))
 	{
-		oled_drawchar_sz(x, y, c, color, font_size);
+		ssd1306_drawchar_sz(x, y, c, color, font_size);
 		x += 8 * font_size;
 		if(x>128 - 8 * font_size)
 			break;
@@ -583,22 +569,22 @@ void oled_drawstr_sz(uint8_t x, uint8_t y, char *str, uint8_t color, font_size_t
 /*
  * initialize I2C and OLED
  */
-uint8_t oled_init(void)
+uint8_t ssd1306_init(void)
 {
-	// initialize port
-	i2c_init();
+	// pulse reset
+	ssd1306_rst();
 	
 	// initialize OLED
-	uint8_t *cmd_list = (uint8_t *)oled_init_array;
+	uint8_t *cmd_list = (uint8_t *)ssd1306_init_array;
 	while(*cmd_list != SSD1306_TERMINATE_CMDS)
 	{
-		if(oled_cmd(*cmd_list++))
+		if(ssd1306_cmd(*cmd_list++))
 			return 1;
 	}
 	
 	// clear display
-	oled_setbuf(0);
-	oled_refresh();
+	ssd1306_setbuf(0);
+	ssd1306_refresh();
 	
 	return 0;
 }
diff --git a/examples/i2c_oled/i2c.h b/examples/i2c_oled/ssd1306_i2c.h
similarity index 57%
rename from examples/i2c_oled/i2c.h
rename to examples/i2c_oled/ssd1306_i2c.h
index 335aafa969ffee617a09d8346f9ebc895f7ffb37..3ae7ce23b0277156cfb4571ed757eeb6c4b54c5f 100644
--- a/examples/i2c_oled/i2c.h
+++ b/examples/i2c_oled/ssd1306_i2c.h
@@ -1,29 +1,34 @@
 /*
- * Single-File-Header for I2C
- * 04-07-2023 E. Brombaugh
+ * Single-File-Header for SSD1306 I2C interface
+ * 05-07-2023 E. Brombaugh
  */
 
-#ifndef _I2C_H
-#define _I2C_H
+#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 I2C_CLKRATE 1000000
+#define SSD1306_I2C_CLKRATE 1000000
 
 // I2C Logic clock rate - must be higher than Bus clock rate
-#define I2C_PRERATE 2000000
+#define SSD1306_I2C_PRERATE 2000000
 
 // uncomment this for high-speed 36% duty cycle, otherwise 33%
-#define I2C_DUTY
+#define SSD1306_I2C_DUTY
 
 // I2C Timeout count
 #define TIMEOUT_MAX 100000
 
 // uncomment this to enable IRQ-driven operation
-#define I2C_IRQ
+//#define SSD1306_I2C_IRQ
 
-#ifdef I2C_IRQ
+#ifdef SSD1306_I2C_IRQ
 // some stuff that IRQ mode needs
-volatile uint8_t i2c_send_buffer[64], *i2c_send_ptr, i2c_send_sz, i2c_irq_state;
+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
@@ -32,7 +37,7 @@ volatile uint8_t i2c_send_buffer[64], *i2c_send_ptr, i2c_send_sz, i2c_irq_state;
 /*
  * init just I2C
  */
-void i2c_setup(void)
+void ssd1306_i2c_setup(void)
 {
 	uint16_t tempreg;
 	
@@ -43,34 +48,34 @@ void i2c_setup(void)
 	// set freq
 	tempreg = I2C1->CTLR2;
 	tempreg &= ~I2C_CTLR2_FREQ;
-	tempreg |= (APB_CLOCK/I2C_PRERATE)&I2C_CTLR2_FREQ;
+	tempreg |= (APB_CLOCK/SSD1306_I2C_PRERATE)&I2C_CTLR2_FREQ;
 	I2C1->CTLR2 = tempreg;
 	
 	// Set clock config
 	tempreg = 0;
-#if (I2C_CLKRATE <= 100000)
+#if (SSD1306_I2C_CLKRATE <= 100000)
 	// standard mode good to 100kHz
-	tempreg = (APB_CLOCK/(2*I2C_CLKRATE))&I2C_CKCFGR_CCR;
+	tempreg = (APB_CLOCK/(2*SSD1306_I2C_CLKRATE))&SSD1306_I2C_CKCFGR_CCR;
 #else
 	// fast mode over 100kHz
-#ifndef I2C_DUTY
+#ifndef SSD1306_I2C_DUTY
 	// 33% duty cycle
-	tempreg = (APB_CLOCK/(3*I2C_CLKRATE))&I2C_CKCFGR_CCR;
+	tempreg = (APB_CLOCK/(3*SSD1306_I2C_CLKRATE))&SSD1306_I2C_CKCFGR_CCR;
 #else
 	// 36% duty cycle
-	tempreg = (APB_CLOCK/(25*I2C_CLKRATE))&I2C_CKCFGR_CCR;
+	tempreg = (APB_CLOCK/(25*SSD1306_I2C_CLKRATE))&I2C_CKCFGR_CCR;
 	tempreg |= I2C_CKCFGR_DUTY;
 #endif
 	tempreg |= I2C_CKCFGR_FS;
 #endif
 	I2C1->CKCFGR = tempreg;
 
-#ifdef I2C_IRQ
+#ifdef SSD1306_I2C_IRQ
 	// enable IRQ driven operation
 	NVIC_EnableIRQ(I2C1_EV_IRQn);
 	
 	// initialize the state
-	i2c_irq_state = 0;
+	ssd1306_i2c_irq_state = 0;
 #endif
 	
 	// Enable I2C
@@ -80,37 +85,6 @@ void i2c_setup(void)
 	I2C1->CTLR1 |= I2C_CTLR1_ACK;
 }
 
-/*
- * init I2C and GPIO
- */
-void i2c_init(void)
-{
-	// Enable GPIOC and I2C
-	RCC->APB2PCENR |= RCC_APB2Periph_GPIOC;
-	RCC->APB1PCENR |= RCC_APB1Periph_I2C1;
-	
-	// 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);
-	
-#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
-	i2c_setup();
-}
-
 /*
  * error descriptions
  */
@@ -126,37 +100,37 @@ char *errstr[] =
 /*
  * error handler
  */
-uint8_t i2c_error(uint8_t err)
+uint8_t ssd1306_i2c_error(uint8_t err)
 {
 	// report error
-	printf("i2c_error - timeout waiting for %s\n\r", errstr[err]);
+	printf("ssd1306_i2c_error - timeout waiting for %s\n\r", errstr[err]);
 	
 	// reset & initialize I2C
-	i2c_setup();
+	ssd1306_i2c_setup();
 
 	return 1;
 }
 
 // event codes we use
-#define  I2C_EVENT_MASTER_MODE_SELECT ((uint32_t)0x00030001)  /* BUSY, MSL and SB flag */
-#define  I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED ((uint32_t)0x00070082)  /* BUSY, MSL, ADDR, TXE and TRA flags */
-#define  I2C_EVENT_MASTER_BYTE_TRANSMITTED ((uint32_t)0x00070084)  /* TRA, BUSY, MSL, TXE and BTF flags */
+#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 i2c_chk_evt(uint32_t event_mask)
+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 I2C_IRQ
+#ifdef SSD1306_I2C_IRQ
 /*
  * packet send for IRQ-driven operation
  */
-uint8_t i2c_send(uint8_t addr, uint8_t *data, uint8_t sz)
+uint8_t ssd1306_i2c_send(uint8_t addr, uint8_t *data, uint8_t sz)
 {
 	int32_t timeout;
 	
@@ -165,11 +139,11 @@ uint8_t i2c_send(uint8_t addr, uint8_t *data, uint8_t sz)
 #endif
 	
 	// error out if buffer under/overflow
-	if((sz > sizeof(i2c_send_buffer)) || !sz)
+	if((sz > sizeof(ssd1306_i2c_send_buffer)) || !sz)
 		return 2;
 	
 	// wait for previous packet to finish
-	while(i2c_irq_state);
+	while(ssd1306_i2c_irq_state);
 	
 #ifdef IRQ_DIAG
 	GPIOC->BSHR = (1<<(16+3));
@@ -177,37 +151,37 @@ uint8_t i2c_send(uint8_t addr, uint8_t *data, uint8_t sz)
 #endif
 	
 	// init buffer for sending
-	i2c_send_sz = sz;
-	i2c_send_ptr = i2c_send_buffer;
-	memcpy((uint8_t *)i2c_send_buffer, data, sz);
+	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 i2c_error(0);
+		return ssd1306_i2c_error(0);
 
 	// Set START condition
 	I2C1->CTLR1 |= I2C_CTLR1_START;
 
 	// wait for master mode select
 	timeout = TIMEOUT_MAX;
-	while((!i2c_chk_evt(I2C_EVENT_MASTER_MODE_SELECT)) && (timeout--));
+	while((!ssd1306_i2c_chk_evt(SSD1306_I2C_EVENT_MASTER_MODE_SELECT)) && (timeout--));
 	if(timeout==-1)
-		return i2c_error(1);
+		return ssd1306_i2c_error(1);
 	
 	// send 7-bit address + write flag
 	I2C1->DATAR = addr<<1;
 
 	// wait for transmit condition
 	timeout = TIMEOUT_MAX;
-	while((!i2c_chk_evt(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) && (timeout--));
+	while((!ssd1306_i2c_chk_evt(SSD1306_I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) && (timeout--));
 	if(timeout==-1)
-		return i2c_error(2);
+		return ssd1306_i2c_error(2);
 
 	// Enable TXE interrupt
 	I2C1->CTLR2 |= I2C_CTLR2_ITBUFEN | I2C_CTLR2_ITEVTEN;
-	i2c_irq_state = 1;
+	ssd1306_i2c_irq_state = 1;
 
 #ifdef IRQ_DIAG
 	GPIOC->BSHR = (1<<(16+4));
@@ -237,20 +211,20 @@ void I2C1_EV_IRQHandler(void)
 	if(STAR1 & I2C_STAR1_TXE)
 	{
 		/* check for remaining data */
-		if(i2c_send_sz--)
-			I2C1->DATAR = *i2c_send_ptr++;
+		if(ssd1306_i2c_send_sz--)
+			I2C1->DATAR = *ssd1306_i2c_send_ptr++;
 
 		/* was that the last byte? */
-		if(!i2c_send_sz)
+		if(!ssd1306_i2c_send_sz)
 		{
 			// disable TXE interrupt
 			I2C1->CTLR2 &= ~(I2C_CTLR2_ITBUFEN | I2C_CTLR2_ITEVTEN);
 			
 			// reset IRQ state
-			i2c_irq_state = 0;
+			ssd1306_i2c_irq_state = 0;
 			
 			// wait for tx complete
-			while(!i2c_chk_evt(I2C_EVENT_MASTER_BYTE_TRANSMITTED));
+			while(!ssd1306_i2c_chk_evt(SSD1306_I2C_EVENT_MASTER_BYTE_TRANSMITTED));
 
 			// set STOP condition
 			I2C1->CTLR1 |= I2C_CTLR1_STOP;
@@ -263,9 +237,9 @@ void I2C1_EV_IRQHandler(void)
 }
 #else
 /*
- * packet send for polled operation
+ * low-level packet send for blocking polled operation via i2c
  */
-uint8_t i2c_send(uint8_t addr, uint8_t *data, uint8_t sz)
+uint8_t ssd1306_i2c_send(uint8_t addr, uint8_t *data, uint8_t sz)
 {
 	int32_t timeout;
 	
@@ -273,25 +247,25 @@ uint8_t i2c_send(uint8_t addr, uint8_t *data, uint8_t sz)
 	timeout = TIMEOUT_MAX;
 	while((I2C1->STAR2 & I2C_STAR2_BUSY) && (timeout--));
 	if(timeout==-1)
-		return i2c_error(0);
+		return ssd1306_i2c_error(0);
 
 	// Set START condition
 	I2C1->CTLR1 |= I2C_CTLR1_START;
 	
 	// wait for master mode select
 	timeout = TIMEOUT_MAX;
-	while((!i2c_chk_evt(I2C_EVENT_MASTER_MODE_SELECT)) && (timeout--));
+	while((!ssd1306_i2c_chk_evt(SSD1306_I2C_EVENT_MASTER_MODE_SELECT)) && (timeout--));
 	if(timeout==-1)
-		return i2c_error(1);
+		return ssd1306_i2c_error(1);
 	
 	// send 7-bit address + write flag
 	I2C1->DATAR = addr<<1;
 
 	// wait for transmit condition
 	timeout = TIMEOUT_MAX;
-	while((!i2c_chk_evt(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) && (timeout--));
+	while((!ssd1306_i2c_chk_evt(SSD1306_I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) && (timeout--));
 	if(timeout==-1)
-		return i2c_error(2);
+		return ssd1306_i2c_error(2);
 
 	// send data one byte at a time
 	while(sz--)
@@ -300,7 +274,7 @@ uint8_t i2c_send(uint8_t addr, uint8_t *data, uint8_t sz)
 		timeout = TIMEOUT_MAX;
 		while(!(I2C1->STAR1 & I2C_STAR1_TXE) && (timeout--));
 		if(timeout==-1)
-			return i2c_error(3);
+			return ssd1306_i2c_error(3);
 		
 		// send command
 		I2C1->DATAR = *data++;
@@ -308,9 +282,9 @@ uint8_t i2c_send(uint8_t addr, uint8_t *data, uint8_t sz)
 
 	// wait for tx complete
 	timeout = TIMEOUT_MAX;
-	while((!i2c_chk_evt(I2C_EVENT_MASTER_BYTE_TRANSMITTED)) && (timeout--));
+	while((!ssd1306_i2c_chk_evt(SSD1306_I2C_EVENT_MASTER_BYTE_TRANSMITTED)) && (timeout--));
 	if(timeout==-1)
-		return i2c_error(4);
+		return ssd1306_i2c_error(4);
 
 	// set STOP condition
 	I2C1->CTLR1 |= I2C_CTLR1_STOP;
@@ -320,4 +294,70 @@ uint8_t i2c_send(uint8_t addr, uint8_t *data, uint8_t sz)
 }
 #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->APB2PCENR |= RCC_APB2Periph_GPIOC;
+	RCC->APB1PCENR |= RCC_APB1Periph_I2C1;
+	
+	// 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);
+	
+#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
diff --git a/examples/self_modify_code/self_modify_code.c b/examples/self_modify_code/self_modify_code.c
index 3e897f312eec2ef2a0a6ce02bcee91d104c4caf0..4b72d9988af3b17e76956a522fd5bc37ef238a31 100644
--- a/examples/self_modify_code/self_modify_code.c
+++ b/examples/self_modify_code/self_modify_code.c
@@ -10,10 +10,10 @@ uint32_t count;
 
 // This is a complicated way to do it from C land, as a demonstration
 
-// Tell the compiler to put this code in the .data section.  That
+// Tell the compiler to put this code in the .srodata section.  That
 // will cause the startup code to copy it from flash into RAM where
 // it can be easily modified at runtime.
-uint32_t ReadCSRSelfModify( uint16_t whichcsr ) __attribute__(( section(".data"))) __attribute__((noinline));
+uint32_t ReadCSRSelfModify( uint16_t whichcsr ) __attribute__(( section(".srodata"))) __attribute__((noinline));
 uint32_t ReadCSRSelfModify( uint16_t whichcsr )
 {
 	uint32_t ret;
diff --git a/examples/spi_24L01_rx/Makefile b/examples/spi_24L01_rx/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..5b54c64b56f41e500746d29d60f3dc740ad132dc
--- /dev/null
+++ b/examples/spi_24L01_rx/Makefile
@@ -0,0 +1,11 @@
+all : flash
+
+TARGET:=spi_24L01_rx
+ADDITIONAL_C_FILES+=nrf24l01_low_level.c nrf24l01.c 
+
+CFLAGS+=-DSTDOUT_UART
+
+include ../../ch32v003fun/ch32v003fun.mk
+
+flash : cv_flash
+clean : cv_clean
diff --git a/examples/spi_24L01_rx/README.md b/examples/spi_24L01_rx/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..f657c3215afd912691b1118de46dac20515f3770
--- /dev/null
+++ b/examples/spi_24L01_rx/README.md
@@ -0,0 +1,16 @@
+# SPI 24L01+ RX demo
+This is the receiving half of the spi_24L01 "example".
+Enjoy SPI and 2.4GHz:
+
+
+https://user-images.githubusercontent.com/104343143/235313119-4facb0b4-9f8d-4b79-b39e-647600fdaa26.mp4
+
+
+https://user-images.githubusercontent.com/104343143/235313123-14a2114d-388a-4c98-89b9-63e5e40de682.mp4
+
+## nRF24L01(+) library
+This is the doing of [Reza Ebrahimi](https://github.com/ebrezadev), I've just included a copy of [his library](https://github.com/ebrezadev/nRF24L01-C-Driver) here and made some modifications.
+
+## Arduino friend
+If, like me, you only have the one ch32v003 evaluation board, you can find a [friend for it here](https://github.com/recallmenot/ch32v003fun_friends/tree/main/Arduino/NRF24L01_TX).
+
diff --git a/examples/spi_24L01_rx/nrf24l01.c b/examples/spi_24L01_rx/nrf24l01.c
new file mode 100644
index 0000000000000000000000000000000000000000..75a2111beb5593bcad897f410cd03315d2fb06cf
--- /dev/null
+++ b/examples/spi_24L01_rx/nrf24l01.c
@@ -0,0 +1,616 @@
+#include "nrf24l01.h"
+
+/*nRF24L01+ features, enable / disable as needed*/
+static uint8_t NRF24_en_ack = ENABLE;
+static uint8_t NRF24_en_no_ack = ENABLE;
+static uint8_t NRF24_en_dynamic_payload = ENABLE;
+
+/*global variables related to this file*/
+static uint8_t SPI_command;                                       /*1 byte spi command*/
+static uint8_t register_current_value;                            /*in order to change some bits of internal registers or to check their content*/
+static uint8_t register_new_value;                                /*used to write new value to nrf24l01+ registers*/
+static uint8_t write_pointer;                                     /*used as an input for read and write functions (as a pointer)*/
+static uint8_t current_address_width;                             /*current address width for receiver pipe addresses (up to 6 pipes), from 3 to 5 bytes*/
+static uint8_t reset_flag = 0;                                    /*reset flag lets the software know if the nrf24l01+ has ever been reset or not*/
+static uint8_t current_mode = DEVICE_NOT_INITIALIZED;             /*current mode of operation: DEVICE_NOT_INITIALIZED, PRX, PTX, STANDBYI, STANDBYII, POWER_DOWN*/
+static uint8_t current_payload_width;                             /*payload width could be from 1 to 32 bytes, in either dynamic or static forms*/
+static uint8_t current_acknowledgement_state = NO_ACK_MODE;
+
+/*2 dimensional array of pipe addresses (5 byte address width) by default. you can change addresses using a new array later.
+  Pipe 1 address could be anything. pipe 3 to 6 addresses share the first 4 bytes with pipe 2 and only differ in byte 5*/
+uint8_t datapipe_address[MAXIMUM_NUMBER_OF_DATAPIPES][ADDRESS_WIDTH_DEFAULT] = {
+  {0X20, 0XC3, 0XC2, 0XC1, 0XA0},
+  {0X20, 0XC3, 0XC2, 0XC1, 0XA1},
+  {0X20, 0XC3, 0XC2, 0XC1, 0XA2},
+  {0X20, 0XC3, 0XC2, 0XC1, 0XA3},
+  {0X20, 0XC3, 0XC2, 0XC1, 0XA4},
+  {0X20, 0XC3, 0XC2, 0XC1, 0XA5}
+};
+
+/*function for PTX device to transmit 1 to 32 bytes of data, used for both dynamic payload length
+   and static payload length methods. acknowledgemet state could be NO_ACK_MODE or ACK_MODE*/
+uint8_t nrf24_transmit(uint8_t *payload, uint8_t payload_width, uint8_t acknowledgement_state)
+{
+  nrf24_read(STATUS_ADDRESS, &register_current_value, 1, CLOSE);         /*in order to check TX_FIFO status*/
+  if ((!(register_current_value & (1 << TX_FULL))) && (current_mode == PTX))
+  {
+    current_acknowledgement_state = acknowledgement_state;      /*setting the acknowledgement state to either NO_ACK or ACK, based on input*/
+    if (NRF24_en_dynamic_payload == ENABLE)
+      current_payload_width = payload_width;
+    nrf24_send_payload(payload, payload_width);          /*the actual function to send data*/
+    return (TRANSMIT_BEGIN);                                     /*TX FIFO is not full and nrf24l01+ mode is standby ii or ptx*/
+  }
+  else
+  {
+    return (TRANSMIT_FAIL);            /*TX FIFO full or wrong mode*/
+  }
+}
+
+/*used by nrf24_transmit function to send the actual data*/
+void nrf24_send_payload(uint8_t *payload, uint8_t payload_width)
+{
+  nrf24_SPI(SPI_ON);
+  if (current_acknowledgement_state == NO_ACK_MODE)
+    SPI_command = W_TX_PAYLOAD_NOACK;
+  else
+    SPI_command = W_TX_PAYLOAD;
+  SPI_send_command(SPI_command);
+  for (; payload_width; payload_width--)
+  {
+    SPI_command = *payload;
+    SPI_send_command(SPI_command);
+    payload++;
+  }
+  nrf24_SPI(SPI_OFF);
+}
+
+/*reports back transmit status: TRANSMIT_DONE, TRANSMIT_FAILED (in case of reaching maximum number of retransmits in auto acknowledgement mode)
+  and TRANSMIT_IN_PROGRESS, if neither flags are set. automatically resets the '1' flags.*/
+uint8_t nrf24_transmit_status()
+{
+  nrf24_read(STATUS_ADDRESS, &register_current_value, 1, CLOSE);      /*status register is read to check TX_DS flag*/
+  if (register_current_value & (1 << TX_DS))                          /*if the TX_DS == 1, */
+  {
+    nrf24_write(STATUS_ADDRESS, &register_current_value, 1, CLOSE);   /*reseting the TX_DS flag. as mentioned by datasheet, writing '1' to a flag resets that flag*/
+    return TRANSMIT_DONE;
+  }
+  else if (register_current_value & (1 << MAX_RT))
+  {
+    nrf24_write(STATUS_ADDRESS, &register_current_value, 1, CLOSE);   /*reseting the MAX_RT flag. as mentioned by datasheet, writing '1' to a flag resets that flag*/
+    return TRANSMIT_FAILED;
+  }
+  else
+    return TRANSMIT_IN_PROGRESS;
+}
+
+/*the receive function output is used as a polling method to check the received data inside RX FIFOs. 
+If there is any data available, it will be loaded inside payload array*/
+uint8_t nrf24_receive(uint8_t *payload, uint8_t payload_width)
+{
+  if (current_mode == PRX)
+  {
+    nrf24_read(STATUS_ADDRESS, &register_current_value, 1, CLOSE);
+    if (register_current_value & (1 << RX_DR))                         /*if received data is ready inside RX FIFO*/
+    {
+      if(NRF24_en_dynamic_payload == DISABLE)                                    /*if dynamif payload width is disabled, use the static payload width and ignore the input*/
+        payload_width = current_payload_width;
+        
+      nrf24_SPI(SPI_ON);                                                /*sending the read payload command to nrf24l01+*/                          
+      SPI_command = R_RX_PAYLOAD;
+      SPI_send_command(SPI_command);
+       
+      for (; payload_width; payload_width--)
+      {
+        SPI_command = NOP_CMD;
+        *payload = SPI_send_command(SPI_command);
+        payload++;
+      }
+      nrf24_SPI(SPI_OFF); 
+      nrf24_read(FIFO_STATUS_ADDRESS, &register_current_value, 1, CLOSE);   /*in order to check the RX_EMPTY flag*/
+      if(register_current_value & (1 << RX_EMPTY))                        /*if the RX FIFO is empty, reset the RX_DR flag inside STATUS register*/
+      {
+        nrf24_read(STATUS_ADDRESS, &register_current_value, 1, CLOSE);
+        register_new_value = register_current_value | (1 << RX_DR);
+        nrf24_write(STATUS_ADDRESS, &register_new_value, 1, CLOSE); 
+      }      
+      return OPERATION_DONE;
+    }
+    else
+    {
+      return RECEIVE_FIFO_EMPTY;
+    }
+  }
+  else
+    return OPERATION_ERROR;
+}
+
+/*function which uses TX_FLUSH or RX_FLUSH command to flush the fifo buffers. if successful, output is OPERATION_DONE.
+   if not successful (wrong input or wrong mode of operation) output will be OPERATION_ERROR*/
+uint8_t nrf24_flush(uint8_t fifo_select)
+{
+  switch (fifo_select)
+  {
+    case TX_BUFFER:
+      if (current_mode == PTX)
+      {
+        nrf24_SPI(SPI_ON);
+        SPI_command = FLUSH_TX;
+        SPI_send_command(SPI_command);
+        nrf24_SPI(SPI_OFF);
+        return OPERATION_DONE;
+      }
+      else
+        return OPERATION_ERROR;
+    case RX_BUFFER:
+      if (current_mode == PRX)
+      {
+        nrf24_SPI(SPI_ON);
+        SPI_command = FLUSH_RX;
+        SPI_send_command(SPI_command);
+        nrf24_SPI(SPI_OFF);
+        return OPERATION_DONE;
+      }
+      else
+        return OPERATION_ERROR;
+    default:
+      return OPERATION_ERROR;
+  }
+}
+
+/*must be called atleast once, which happens with calling nrf24_device function*/
+void nrf24_reset()
+{
+  reset_flag = RESET;
+  nrf24_CE(CE_OFF);
+  register_new_value = CONFIG_REGISTER_DEFAULT;
+  nrf24_write(CONFIG_ADDRESS, &register_new_value, 1, CLOSE);
+  register_new_value = EN_AA_REGISTER_DEFAULT;
+  nrf24_write(EN_AA_ADDRESS, &register_new_value, 1, CLOSE);
+  register_new_value = EN_RXADDR_REGISTER_DEFAULT;
+  nrf24_write(EN_RXADDR_ADDRESS, &register_new_value, 1, CLOSE);
+  register_new_value = SETUP_AW_REGISTER_DEFAULT;
+  nrf24_write(SETUP_AW_ADDRESS, &register_new_value, 1, CLOSE);
+  register_new_value = RF_CH_REGISTER_DEFAULT;
+  nrf24_write(RF_CH_ADDRESS, &register_new_value, 1, CLOSE);
+  register_new_value = RF_SETUP_REGISTER_DEFAULT;
+  nrf24_write(RF_SETUP_ADDRESS, &register_new_value, 1, CLOSE);
+  register_new_value = STATUS_REGISTER_DEFAULT;
+  nrf24_write(STATUS_ADDRESS, &register_new_value, 1, CLOSE);
+
+  nrf24_mode(PTX);
+  nrf24_flush(TX_BUFFER);
+  nrf24_mode(PRX);
+  nrf24_flush(RX_BUFFER);
+
+  nrf24_read(STATUS_ADDRESS, &register_current_value, 1, CLOSE);
+  register_new_value = register_current_value | (1 << RX_DR) | (1 << TX_DS) | (1 << MAX_RT);
+  nrf24_write(STATUS_ADDRESS, &register_new_value, 1, CLOSE);
+  
+  nrf24_interrupt_mask(ENABLE, ENABLE, ENABLE);
+  nrf24_crc_configuration(ENABLE, 1);
+  nrf24_address_width(ADDRESS_WIDTH_DEFAULT);
+  nrf24_rf_datarate(RF_DATARATE_DEFAULT);
+  nrf24_rf_power(RF_PWR_DEFAULT);
+  nrf24_rf_channel(RF_CHANNEL_DEFAULT);
+  nrf24_datapipe_enable(NUMBER_OF_DP_DEFAULT);
+  /*nrf24_datapipe_address_configuration();*/
+  /*nrf24_datapipe_ptx(1);*/
+  nrf24_prx_static_payload_width(STATIC_PAYLOAD_WIDTH_DEFAULT, NUMBER_OF_DP_DEFAULT);
+  nrf24_automatic_retransmit_setup(RETRANSMIT_DELAY_DEFAULT, RETRANSMIT_COUNT_DEFAULT);
+  nrf24_auto_acknowledge_datapipe(NUMBER_OF_DP_DEFAULT);
+  nrf24_auto_acknowledge_datapipe(0);
+  nrf24_dynamic_payload(NRF24_en_dynamic_payload, NUMBER_OF_DP_DEFAULT);
+  nrf24_payload_without_ack(NRF24_en_no_ack);
+  nrf24_payload_with_ack(NRF24_en_ack);
+}
+
+/*used by firmware to set the nrf24 mode in TRANSMITTER, RECEIVER, POWER_SAVING or TURN_OFF states, and reseting the device
+  if it has not been done yet. This is the initializer, and everything starts by calling nrf24_device first.It has a higher
+  level of abstraction than nrf24_mode and must be used by user*/
+void nrf24_device(uint8_t device_mode, uint8_t reset_state)
+{
+  SPI_Initializer();
+  pinout_Initializer();
+  delay_function(STARTUP_DELAY);
+
+  if ((reset_state == RESET) || (reset_flag == 0))
+  {
+    nrf24_reset();
+  }
+
+  switch (device_mode)
+  {
+    case TRANSMITTER:
+      nrf24_mode(POWER_DOWN);
+      nrf24_interrupt_mask(ENABLE, DISABLE, DISABLE);                /*disabling tx interrupt mask*/
+      nrf24_mode(PTX);
+      break;
+    case RECEIVER:
+      nrf24_mode(POWER_DOWN);
+      nrf24_interrupt_mask(DISABLE, ENABLE, ENABLE);                /*disabling rx interrupt mask*/
+      nrf24_mode(PRX);
+      delay_function(PRX_MODE_DELAY);                              /*100ms for PRX mode*/
+      break;
+    case POWER_SAVING:
+      nrf24_mode(POWER_DOWN);
+      nrf24_interrupt_mask(ENABLE, ENABLE, ENABLE);
+      nrf24_mode(STANDBYI);
+      break;
+    case TURN_OFF:
+      nrf24_mode(POWER_DOWN);
+      nrf24_interrupt_mask(ENABLE, ENABLE, ENABLE);
+      break;
+    default:
+      nrf24_mode(POWER_DOWN);
+      nrf24_interrupt_mask(ENABLE, ENABLE, ENABLE);
+      break;
+  }
+}
+
+/*setting automatic retransmit delay time and maximum number of retransmits*/
+void nrf24_automatic_retransmit_setup(uint16_t delay_time, uint8_t retransmit_count)
+{
+  register_new_value = 0x00;
+  for (; (delay_time > 250) && (register_new_value < 0X0F); delay_time -= 250)
+    register_new_value++;
+  register_new_value <<= ARD_0;
+  if ((retransmit_count > 0) && (retransmit_count < 16))
+    register_new_value |= retransmit_count;
+  else
+    register_new_value |= 0;
+  nrf24_write(SETUP_RETR_ADDRESS, &register_new_value, 1, CLOSE);
+}
+
+/*setting auto acknoledgement on datapipes*/
+void nrf24_auto_acknowledge_datapipe(uint8_t datapipe)
+{
+  if (datapipe < 7)
+    register_new_value = (1 << datapipe);
+  nrf24_write(EN_AA_ADDRESS, &register_new_value, 1, CLOSE);
+}
+
+/*turns on or off the dynamic payload width capability*/
+void nrf24_dynamic_payload(uint8_t state, uint8_t datapipe)
+{
+  nrf24_auto_acknowledge_datapipe(datapipe);                        /*setting auto acknowledgment before setting dynamic payload*/
+  nrf24_auto_acknowledge_datapipe(0);
+  nrf24_read(FEATURE_ADDRESS, &register_current_value, 1, CLOSE);
+  if (state == ENABLE)
+  {
+    register_new_value = register_current_value | (1 << EN_DPL);    /*EN_DPL bit turns dynamic payload width on or off on all datapipes*/
+    nrf24_write(FEATURE_ADDRESS, &register_new_value, 1, CLOSE);
+    if (datapipe < 7)
+      register_new_value = (1 << datapipe) - 1;                       /*turning on dynamic payload width on chosen datapipes, using DYNPD register*/
+    nrf24_write(DYNPD_ADDRESS, &register_new_value, 1, CLOSE);
+    NRF24_en_dynamic_payload = ENABLE;
+  }
+  else
+  {
+    register_new_value = register_current_value & (~(1 << EN_DPL));
+    nrf24_write(FEATURE_ADDRESS, &register_new_value, 1, CLOSE);
+    NRF24_en_dynamic_payload = DISABLE;
+  }
+}
+
+/*function to enable or disable sending without acknowledge.
+   if disabled, TX must send a payload with ACK-request and receiver must be able to answer it.
+   manipulates EN_DYN_ACK inside FEATURE*/
+void nrf24_payload_without_ack(uint8_t state)
+{
+  if (state == ENABLE)
+  {
+    nrf24_read(FEATURE_ADDRESS, &register_current_value, 1, CLOSE);
+    register_new_value = register_current_value | (1 << EN_DYN_ACK);
+    nrf24_write(FEATURE_ADDRESS, &register_new_value, 1, CLOSE);
+  }
+  else
+  {
+    nrf24_read(FEATURE_ADDRESS, &register_current_value, 1, CLOSE);
+    register_new_value = register_current_value & (~(1 << EN_DYN_ACK));
+    nrf24_write(FEATURE_ADDRESS, &register_new_value, 1, CLOSE);
+  }
+}
+
+/*function to enable or disable sending with acknowledge.
+   if disabled, the payload can be sent only without ACK-request.
+   manipulates EN_ACK_PAY and EN_DPL inside FEATURE as Dynamic Payload Length is required.*/
+void nrf24_payload_with_ack(uint8_t state)
+{
+  if (state == ENABLE)
+  {
+    nrf24_read(FEATURE_ADDRESS, &register_current_value, 1, CLOSE);
+    register_new_value = register_current_value | (1 << EN_ACK_PAY) | (1 << EN_DPL);
+    nrf24_write(FEATURE_ADDRESS, &register_new_value, 1, CLOSE);
+    nrf24_read(DYNPD_ADDRESS, &register_current_value, 1, CLOSE);
+	// enable dynamic payload for all pipes
+    register_new_value = register_current_value | 0b111111;
+    nrf24_write(DYNPD_ADDRESS, &register_new_value, 1, CLOSE);
+  }
+  else
+  {
+    nrf24_read(FEATURE_ADDRESS, &register_current_value, 1, CLOSE);
+    register_new_value = register_current_value & (~((1 << EN_ACK_PAY) | (1 << EN_DPL)));
+    nrf24_write(FEATURE_ADDRESS, &register_new_value, 1, CLOSE);
+  }
+}
+
+/*on nrf24l01+ there is only one address for PTX device which must be the same as PRX data pipe address 0*/
+void nrf24_datapipe_ptx(uint8_t datapipe_number)
+{
+  nrf24_write(TX_ADDR_ADDRESS, &datapipe_address[datapipe_number - 1][0], current_address_width, CLOSE);
+}
+
+/*setting the 6 datapipe addresses using the datapipe_address[][]*/
+void nrf24_datapipe_address_configuration()
+{
+  uint8_t address = RX_ADDR_P0_ADDRESS;
+  for (uint8_t counter = 0; counter < 6; counter++)
+  {
+    nrf24_write(address, &datapipe_address[counter][0], current_address_width, CLOSE);
+    address++;
+  }
+}
+
+/*function to change static payload width, from 1 to 32 bytes in each payload*/
+void nrf24_prx_static_payload_width(uint8_t static_payload_width, uint8_t number_of_datapipes)
+{
+  for (uint8_t address = RX_PW_P0_ADDRESS; number_of_datapipes; number_of_datapipes--)
+  {
+    nrf24_write(address, &static_payload_width, 1, CLOSE);
+    address++;
+  }
+  current_payload_width = static_payload_width;
+}
+
+/*datapipes are turned on and off using EN_RXADDR register, PRX datapipe addresses are located in RX_ADDR_Pn, TX address is located inside TX_ADDR*/
+void nrf24_datapipe_enable(uint8_t datapipe)
+{
+  nrf24_read(EN_RXADDR_ADDRESS, &register_current_value, 1, CLOSE);
+  if (NRF24_en_ack) {
+    register_current_value |= (1 << ERX_P0);
+  }
+  register_new_value = register_current_value | (1 << datapipe);
+  nrf24_write(EN_RXADDR_ADDRESS, &register_new_value, 1, CLOSE);
+}
+
+/*function to set the nrf24l01+ address width, from 3 to 5 bytes*/
+void nrf24_address_width(uint8_t address_width)
+{
+  if ((address_width <= 5) && (address_width >= 3))
+  {
+    write_pointer = address_width - 2;
+  }
+  else
+  {
+    write_pointer = 3;
+  }
+  nrf24_write(SETUP_AW_ADDRESS, &write_pointer, 1, CLOSE);                    /*5 bytes is the maximum address width available*/
+  current_address_width = address_width;
+}
+
+/*datarate settings, you can choose between 2mbps, 1mbps, 250kbps*/
+void nrf24_rf_datarate(uint16_t rf_datarate)
+{
+  nrf24_read(RF_SETUP_ADDRESS, &register_current_value, 1, CLOSE);
+  register_current_value &= ~((1 << RF_DR_LOW) | (1 << RF_DR_HIGH));
+  switch (rf_datarate)
+  {
+    case 2000:
+      register_new_value = register_current_value | (1 << RF_DR_HIGH);
+      break;
+    case 1000:
+      register_new_value = register_current_value;
+      break;
+    case 250:
+      register_new_value = register_current_value | (1 << RF_DR_LOW);
+      break;
+    default:
+      register_new_value = register_current_value;
+      break;
+  }
+  nrf24_write(RF_SETUP_ADDRESS, &register_new_value, 1, CLOSE);
+}
+
+/*nrf24l01+ RF power settings. 0dbm, -6dbm, -12dbm, -18dbm*/
+void nrf24_rf_power(uint8_t rf_power)
+{
+  nrf24_read(RF_SETUP_ADDRESS, &register_current_value, 1, CLOSE);
+  register_current_value &= ~((1 << RF_PWR_1) | (1 << RF_PWR_0));
+  switch (rf_power)
+  {
+    case 0:
+      register_new_value = register_current_value | ((1 << RF_PWR_1) | (1 << RF_PWR_0));
+      break;
+    case 6:
+      register_new_value = register_current_value | (1 << RF_PWR_1);
+      break;
+    case 12:
+      register_new_value = register_current_value | (1 << RF_PWR_0);
+      break;
+    case 18:
+      register_new_value = register_current_value;
+      break;
+    default:
+      register_new_value = register_current_value | (1 << RF_PWR_1);
+      break;
+  }
+  nrf24_write(RF_SETUP_ADDRESS, &register_new_value, 1, CLOSE);
+}
+
+/*read whether the current channel is busy (has traffic), needs to be called from RX mode*/
+uint8_t nrf24_rf_channel_read_busy(uint8_t rf_channel)
+{
+  uint8_t signals_detected;
+  nrf24_read(RPD_REG_ADDRESS, &signals_detected, 1, CLOSE);
+  if (signals_detected) {
+    return CHANNEL_BUSY;
+  }
+  else {
+    return CHANNEL_CLEAR;
+  }
+}
+
+/*test whether a channel is busy (has traffic), waiting for ms_to_test*/
+uint8_t nrf24_rf_channel_test_busy(uint8_t rf_channel, uint16_t ms_to_test)
+{
+  if ((rf_channel <= 125) && (rf_channel >= 1))
+  {
+    // back up old channel
+    uint8_t previous_channel;
+    nrf24_read(RF_CH_ADDRESS, &previous_channel, 1, CLOSE);
+    // back up old mode
+    uint8_t previous_mode = current_mode;
+    // switch to new channel
+    nrf24_rf_channel(rf_channel);
+    // switch to RX, Received Power Detector is set to 0 and begins sampling
+    if (previous_mode != PRX) {
+      nrf24_mode(PRX);
+    }
+    // wait at least 1 ms before declaring channel clear
+    delay_function(1 > ms_to_test ? 1 : ms_to_test);
+    // Received Power Detector latches to 1 if there was a signal >-64dBm for at least 40 uS consecutively since RX mode was enabled
+    uint8_t signals_detected = nrf24_rf_channel_read_busy(rf_channel);
+    // switch back to old channel
+    nrf24_rf_channel(previous_channel);
+    // switch back to old mode
+    if (previous_mode != PRX) {
+      nrf24_mode(previous_mode);
+    }
+    if (signals_detected) {
+      return CHANNEL_BUSY;
+    }
+    else {
+      return CHANNEL_CLEAR;
+    }
+  }
+  else
+  {
+		return CHANNEL_BUSY;
+  }
+}
+
+/*nrf24l01+ RF channel selection, from 1 to 125*/
+void nrf24_rf_channel(uint8_t rf_channel)
+{
+  if ((rf_channel <= 125) && (rf_channel >= 1))
+  {
+    uint8_t write_pointer = rf_channel;
+    nrf24_write(RF_CH_ADDRESS, &write_pointer, 1, CLOSE);
+  }
+  else
+  {
+    uint8_t write_pointer = 1;
+    nrf24_write(RF_CH_ADDRESS, &write_pointer, 1, CLOSE);
+  }
+}
+
+/*interrupt mask settings. 3 seperate masks for RX, TX, and RT (maximum numbers of retransmission reached*/
+void nrf24_interrupt_mask(uint8_t rx_mask, uint8_t tx_mask, uint8_t max_rt_mask)
+{
+  nrf24_read(CONFIG_ADDRESS, &register_current_value, 1, CLOSE);
+  if (rx_mask)
+    register_new_value = (register_current_value) | (1 << MASK_RX_DR);
+  else
+    register_new_value &= (~(1 << MASK_RX_DR));
+  if (tx_mask)
+    register_new_value |= (1 << MASK_TX_DS);
+  else
+    register_new_value &= (~(1 << MASK_TX_DS));
+  if (max_rt_mask)
+    register_new_value |= (1 << MASK_MAX_RT);
+  else
+    register_new_value &= (~(1 << MASK_MAX_RT));
+
+  nrf24_write(CONFIG_ADDRESS, &register_new_value, 1, CLOSE);
+}
+
+/*enabling or disabling crc in payload; setting crc encoding scheme between 1 or 2 bytes*/
+void nrf24_crc_configuration(uint8_t crc_enable, uint8_t crc_encoding_scheme)
+{
+  nrf24_read(CONFIG_ADDRESS, &register_current_value, 1, CLOSE);
+  if (crc_enable)
+    register_new_value = (register_current_value) | (1 << EN_CRC);
+  else
+    register_new_value &= (~(1 << EN_CRC));
+  if (crc_encoding_scheme == 2)
+    register_new_value |= (1 << CRCO);
+  else
+    register_new_value &= (~(1 << CRCO));
+
+  nrf24_write(CONFIG_ADDRESS, &register_new_value, 1, CLOSE);
+}
+
+/*mode selector: power down, standby i, standby ii, ptx, prx. used by nrf24_device function*/
+void nrf24_mode(uint8_t mode)
+{
+  nrf24_read(CONFIG_ADDRESS, &register_current_value, 1, CLOSE);
+  switch (mode)
+  {
+    case POWER_DOWN:
+      nrf24_CE(CE_OFF);
+      register_new_value = (register_current_value) & (~(1 << PWR_UP));
+      delay_function(POWER_DOWN_DELAY);
+      break;
+    case STANDBYI:                                 /*standby I is defined by 'PWR_UP = 1' and 'CE pin LOW'*/
+      nrf24_CE(CE_OFF);
+      register_new_value = (register_current_value) | (1 << PWR_UP);
+      delay_function(STANDBYI_DELAY);
+      break;
+    case STANDBYII:                                 /*standby ii is related to a ptx device*/
+      nrf24_CE(CE_ON);
+      register_new_value = ((register_current_value) | (1 << PWR_UP)) & (~(1 << PRIM_RX));
+      delay_function(STANDBYI_DELAY);
+      break;
+    case PTX:
+      nrf24_CE(CE_ON);
+      register_new_value = ((register_current_value) | (1 << PWR_UP)) & (~(1 << PRIM_RX));
+      delay_function(STANDBYI_DELAY);
+      break;
+    case PRX:
+      nrf24_CE(CE_ON);
+      register_new_value = (register_current_value) | (1 << PWR_UP) | (1 << PRIM_RX);
+      delay_function(STANDBYI_DELAY);
+      break;
+    default:
+      nrf24_CE(CE_OFF);
+      register_new_value = (register_current_value) & (~(1 << PWR_UP));
+      delay_function(POWER_DOWN_DELAY);
+      break;
+  }
+  nrf24_write(CONFIG_ADDRESS, &register_new_value, 1, CLOSE);
+  current_mode = mode;
+}
+
+/*reads the number of bytes (data_length) from the register in nrf24l01+ (address) and stores them inside an array (value),
+  then closes the spi connection (spi_state = CLOSE) or leaves it open (spi_state = OPEN)*/
+void nrf24_read(uint8_t address, uint8_t *value, uint8_t data_length, uint8_t spi_state)
+{
+  nrf24_SPI(SPI_ON);
+  SPI_command = R_REGISTER | address;    /*in order to read CONFIG, then change one bit*/
+  SPI_send_command(SPI_command);
+  SPI_command = NOP_CMD;
+  for (; data_length ; data_length--)
+  {
+    *value = SPI_send_command(SPI_command);
+    value++;
+  }
+  if (spi_state == CLOSE)
+    nrf24_SPI(SPI_OFF);
+}
+
+/*writes the number of bytes (data_length) from an array (value) inside registers in nrf24l01+ (address),
+  then closes the spi connection (spi_state = CLOSE) or leaves it open (spi_state = OPEN)*/
+void nrf24_write(uint8_t address, uint8_t *value, uint8_t data_length, uint8_t spi_state)
+{
+  nrf24_SPI(SPI_ON);
+  SPI_command = W_REGISTER | address;    /*in order to read CONFIG, then change one bit*/
+  SPI_send_command(SPI_command);
+  for (; data_length ; data_length--)
+  {
+    SPI_command = *value;
+    value++;
+    SPI_send_command(SPI_command);
+  }
+  if (spi_state == CLOSE)
+    nrf24_SPI(SPI_OFF);
+}
diff --git a/examples/spi_24L01_rx/nrf24l01.h b/examples/spi_24L01_rx/nrf24l01.h
new file mode 100644
index 0000000000000000000000000000000000000000..f3552406a2500078ccae4c6495cf957161efc398
--- /dev/null
+++ b/examples/spi_24L01_rx/nrf24l01.h
@@ -0,0 +1,219 @@
+#ifndef NRF24L01_H
+#define NRF24L01_H
+/*nrf24l01: MSbit to LSbit, LSbyte to MSbyte*/
+#include <stdio.h>
+#include <stdint.h>
+
+#define STARTUP_DELAY                 150             /*in milliseconds*/
+#define POWER_DOWN_DELAY              2
+#define STANDBYI_DELAY                2
+#define PRX_MODE_DELAY                100
+#define ADDRESS_WIDTH_DEFAULT         5               /*address width in bytes, for default value*/
+#define RF_CHANNEL_DEFAULT            32        
+#define RF_DATARATE_DEFAULT           1000            /*250, 1000, 2000*/
+#define RF_PWR_DEFAULT                6               /*0, -6, -12, -18*/
+#define STATIC_PAYLOAD_WIDTH_DEFAULT  1               /*for static payload mode, configurable between 1 and 32 bytes for PRX device ONLY (RX_PW_Pn, n for data pipe n)(no register for payload length in PTX device)*/
+#define NUMBER_OF_DP_DEFAULT          1               /*number of datapipes, 1 to 6*/ 
+#define RETRANSMIT_DELAY_DEFAULT      500             /*in uS*/
+#define RETRANSMIT_COUNT_DEFAULT      3
+ 
+#define OPEN                          1
+#define CLOSE                         0
+#define ENABLE                        1
+#define DISABLE                       0
+#define SPI_OFF                       1
+#define SPI_ON                        0
+#define CE_OFF                        0
+#define CE_ON                         1
+
+#define CONFIG_REGISTER_DEFAULT       0X08
+#define EN_AA_REGISTER_DEFAULT        0X3F
+#define EN_RXADDR_REGISTER_DEFAULT    0X00
+#define SETUP_AW_REGISTER_DEFAULT     0X03
+#define SETUP_RETR_REGISTER_DEFAULT   0X03
+#define RF_CH_REGISTER_DEFAULT        0X02
+#define RF_SETUP_REGISTER_DEFAULT     0X0E
+#define STATUS_REGISTER_DEFAULT       0X0E
+#define MAXIMUM_NUMBER_OF_DATAPIPES   6
+
+#define POWER_DOWN                    0X00
+#define STANDBYI                      0X01
+#define STANDBYII                     0X02
+#define PTX                           0X03
+#define PRX                           0X04
+#define DEVICE_NOT_INITIALIZED        0X05
+
+#define TRANSMITTER                   0X00
+#define RECEIVER                      0X01
+#define POWER_SAVING                  0X02
+#define TURN_OFF                      0X03
+
+#define RESET                         1
+#define NO_RESET                      0
+#define NO_ACK_MODE                   1
+#define ACK_MODE                      0
+#define TRANSMIT_BEGIN                1
+#define TRANSMIT_FAIL                 0
+#define TRANSMIT_IN_PROGRESS          0
+#define TRANSMIT_DONE                 1
+#define TRANSMIT_FAILED               0XFF
+#define OPERATION_DONE                1
+#define OPERATION_ERROR               0
+#define RECEIVE_FIFO_EMPTY            2
+#define TX_BUFFER                     1
+#define RX_BUFFER                     0
+// return states for nrf24_rf_channel_test_busy
+#define CHANNEL_CLEAR                 0
+#define CHANNEL_BUSY                  1
+
+/*bits definition section*/
+#define MASK_RX_DR          6               /*mask interrupt caused by RX_DR: 1 interrupt not reflected on IRQ pin (IRQ is active low), inside CONFIG register*/
+#define MASK_TX_DS          5               /*mask interrupt caused by TX_DS: 1 interrupt not reflected on IRQ pin (IRQ is active low), inside CONFIG register*/
+#define MASK_MAX_RT         4               /*mask interrupt caused by MAX_RT means maximum number of retransmissions reached: 1 interrupt not reflected on IRQ pin (IRQ is active low), inside CONFIG register*/
+#define EN_CRC              3               /*enale CRC, forced high if one of the bits in EN_AA is high, inside CONFIG register*/
+#define CRCO                2               /*CRC encoding scheme, 0 is 1 byte, 1 is 2 bytes, inside CONFIG register*/
+#define PWR_UP              1               /*1 is power up, inside CONFIG register*/
+#define PRIM_RX             0               /*RX/TX control, 1: PRX, inside CONFIG register*/
+#define ENAA_P5             5               /*enable auto acknowledgement data pipe 5*/
+#define ENAA_P4             4
+#define ENAA_P3             3
+#define ENAA_P2             2
+#define ENAA_P1             1
+#define ENAA_P0             0
+#define ERX_P5              5               /*part of EN_RXADDR, enable data pipe 5*/
+#define ERX_P4              4
+#define ERX_P3              3
+#define ERX_P2              2
+#define ERX_P1              1
+#define ERX_P0              0
+#define AW_1                1               /*RX/TX address field width, 00 illegal, 01 3 bytes, 10 4 bytes, 11 5 bytes*/
+#define AW_0                0
+#define ARD_3               7               /*auto retransmit delay, 0000 250us, 0001 500us ...> 1111 4000us*/
+#define ARD_2               6
+#define ARD_1               5
+#define ARD_0               4
+#define ARC_3               3               /*auto retransmit count, 0000 retransmit deisabled, 1111 up to 15 retransmit on failure of AA. (inside SETUP_RETR register)*/
+#define ARC_2               2
+#define ARC_1               1
+#define ARC_0               0
+#define RF_CH_6             6               /*sets the frequencvy channel nRF24L01+ operates on*/
+#define RF_CH_5             5
+#define RF_CH_4             4
+#define RF_CH_3             3
+#define RF_CH_2             2
+#define RF_CH_1             1
+#define RF_CH_0             0
+#define CONT_WAVE           7               /*enables continuous carrier transmit when high*/
+#define RF_DR_LOW           5               /*sets the RF data rate to 250kbps*/
+#define PLL_LOCK            4               /*force PLL lock signal. used for testing ONLY*/
+#define RF_DR_HIGH          3               /*select between high speed data rates and works ONLY when RF_DR_LOW is 0. 0 for 1Mbps, 1 for 2Mbps*/
+#define RF_PWR_1            2
+#define RF_PWR_0            1
+#define RX_DR               6               /*IRQ for new packet in RX FIFO (newly received)*/
+#define TX_DS               5               /*IRQ for ACK received in TX mode*/
+#define MAX_RT              4 
+#define RX_P_NO_2           3
+#define RX_P_NO_1           2
+#define RX_P_NO_0           1
+#define PLOS_CNT_3          7               /*inside OBSERVE_TX register, counts the total number of retransmissions since last channel change. reset by writing to RF_CH*/
+#define PLOS_CNT_2          6
+#define PLOS_CNT_1          5
+#define PLOS_CNT_0          4
+#define ARC_CNT_3           3               /*inside OBSERVE_TX register, counts the number of retransmissions for current transaction. reset by initiating new transaction*/
+#define ARC_CNT_2           2
+#define ARC_CNT_1           1
+#define ARC_CNT_0           0
+#define RPD                 0               /*received power detector, if received power is less than -64dbm, RPD = 0*/
+#define TX_REUSE            6
+#define TX_FULL             5
+#define TX_EMPTY            4
+#define RX_FULL             1
+#define RX_EMPTY            0
+#define DPL_P5              5
+#define DPL_P4              4
+#define DPL_P3              3
+#define DPL_P2              2
+#define DPL_P1              1
+#define DPL_P0              0                 /*must be set on PTX in dynamic payload length mode*/
+#define EN_DPL              2                 /*set to enable dynamic payload length*/
+#define EN_ACK_PAY          1                 /*used to enable auto acknowledgement with payload in PRX (inside FEATURE register)*/
+#define EN_DYN_ACK          0                 /**/
+
+/*registers definition section*/
+#define CONFIG_ADDRESS              0X00
+#define EN_AA_ADDRESS               0X01              /*enable auto acknowledgement feature*/
+#define EN_RXADDR_ADDRESS           0X02              /*register containing bits to enable 6 data pipes individually*/
+#define SETUP_AW_ADDRESS            0X03              /*address field length is configured in here to be 3, 4 or 5 bytes long*/
+#define SETUP_RETR_ADDRESS          0X04              /*setup ARC bits to configure auto retransmission count*/
+#define RF_CH_ADDRESS               0X05
+#define RF_SETUP_ADDRESS            0X06
+#define STATUS_ADDRESS              0X07              /*contains RX_DR, TX_DS, MAX_RT, RX_P_NO, TX_FULL, send R_REGISTER then NOP to read*/ 
+#define OBSERVE_TX_ADDRESS          0X08              /*contains ARC_CNT and PLOS_CNT, two counters for retransmission. these counters could be used to assess the network quality*/
+#define RPD_REG_ADDRESS             0X09
+#define RX_ADDR_P0_ADDRESS          0X0A              /*the address for PRX device. if a packet contains this address, enhanced shockburst starts validating the packet*/
+#define RX_ADDR_P1_ADDRESS          0X0B              /*a total of 6 unique addresses could be assigned to a PRX device (Multiceiver feature)*/
+#define RX_ADDR_P2_ADDRESS          0X0C              /*these addresses must NOT be the same*/
+#define RX_ADDR_P3_ADDRESS          0X0D
+#define RX_ADDR_P4_ADDRESS          0X0E
+#define RX_ADDR_P5_ADDRESS          0X0F
+#define TX_ADDR_ADDRESS             0X10              /*40 bits long register, transmit address, used for a PTX device only. configure address legth in SETUP_AW register. set RX_ADDR_P0 equal to this address to handle automatic acknowledge*/
+#define RX_PW_P0_ADDRESS            0X11              /*these registers are for setting the static payload length in static payload length mode (receiver side)*/
+#define RX_PW_P1_ADDRESS            0X12
+#define RX_PW_P2_ADDRESS            0X13
+#define RX_PW_P3_ADDRESS            0X14
+#define RX_PW_P4_ADDRESS            0X15
+#define RX_PW_P5_ADDRESS            0X16
+#define FIFO_STATUS_ADDRESS         0X17
+#define DYNPD_ADDRESS               0X1C              /*on receiver side (RX mode), this register must be set to enable dynamic payload length. a PTX in dynamic mode, must have the DYNPD_P0 set*/
+#define FEATURE_ADDRESS             0X1D              /*contains the EN_DPL bit to enable dynamic payload length*/
+
+/*commands definition section*/
+#define R_REGISTER          0X00              /*read commmand and STATUS registers, 5 bit register map address*/
+#define W_REGISTER          0X20              /*write commmand and STATUS registers, 5 bit register map address, executable in POWER DOWN or STANDBY modes only*/
+#define R_RX_PAYLOAD        0X61              /*read RX payload, 1-32 bytes. read operation starts at byte 0. payload is deleted from FIFO after its read*/
+#define W_TX_PAYLOAD        0XA0              /*write TX payload, starts at byte 0, 1-32 bytes*/
+#define FLUSH_TX            0XE1              /*flush TX FIFO, used in TX mode*/
+#define FLUSH_RX            0XE2              /*flush RX FIFO, used in RX mode*/
+#define REUSE_TX_PL         0XE3              /*used for a PTX device, reuse last transmitted payload for an exact number. alternative to auto retransmission*/
+#define R_RX_PL_WID         0X60              /*command for receiver side, in order to read the payload length in dynamic payload length mode*/
+#define W_ACK_PAYLOAD       0XA0              /*used in RX mode, to write payload in TX FIFO and later transmit the payloads along with ACK packet to PTX, if DPL is enabled*/
+#define W_TX_PAYLOAD_NOACK  0XB0              /*used in TX mode, disables AUTOACK on this specific packet. must be first enabled in FEATURE register by setting the EN_DYN_ACK bit. if used, PTX will not wait for ACK and goes directly to standby I*/
+#define NOP_CMD             0XFF              /*might be used to read the status register*/
+
+void nrf24_reset();                            
+void nrf24_device(uint8_t device_mode, uint8_t reset_state);
+uint8_t SPI_send_command(uint8_t command);          
+void pinout_Initializer();         
+void SPI_Initializer();
+void nrf24_mode(uint8_t mode);
+void nrf24_SPI(uint8_t input);
+void nrf24_CE(uint8_t input);
+void nrf24_address_width(uint8_t address_width);
+uint8_t nrf24_rf_channel_read_busy(uint8_t rf_channel);
+uint8_t nrf24_rf_channel_test_busy(uint8_t rf_channel, uint16_t ms_to_test);
+void nrf24_rf_channel(uint8_t rf_channel);
+void nrf24_rf_power(uint8_t rf_power);
+void nrf24_rf_datarate(uint16_t rf_datarate);
+void nrf24_read(uint8_t address, uint8_t *value, uint8_t data_length, uint8_t spi_state);
+void nrf24_write(uint8_t address, uint8_t *value, uint8_t data_length, uint8_t spi_state);
+void delay_function(uint32_t duration_ms);
+void nrf24_crc_configuration(uint8_t crc_enable, uint8_t crc_encoding_scheme);
+void nrf24_interrupt_mask(uint8_t rx_mask, uint8_t tx_mask, uint8_t max_rt_mask);
+void nrf24_datapipe_enable(uint8_t number_of_datapipes);
+void nrf24_prx_static_payload_width(uint8_t static_payload_width, uint8_t number_of_datapipes);
+void nrf24_datapipe_address_configuration();
+void nrf24_datapipe_ptx(uint8_t datapipe_number);
+void nrf24_automatic_retransmit_setup(uint16_t delay_time, uint8_t retransmit_count);
+void nrf24_auto_acknowledge_datapipe(uint8_t datapipe);
+void nrf24_payload_without_ack(uint8_t state);
+void nrf24_payload_with_ack(uint8_t state);
+void nrf24_dynamic_payload(uint8_t state, uint8_t datapipe);
+void nrf24_device(uint8_t device_mode, uint8_t reset_state);
+void nrf24_send_payload(uint8_t *payload, uint8_t payload_width);
+uint8_t nrf24_receive(uint8_t *payload, uint8_t payload_width);
+uint8_t nrf24_transmit(uint8_t *payload, uint8_t payload_width, uint8_t acknowledgement_state);
+uint8_t nrf24_transmit_status();
+void nrf24_dynamic_ack(uint8_t state);
+uint8_t nrf24_flush(uint8_t fifo_select);
+
+#endif
diff --git a/examples/spi_24L01_rx/nrf24l01_low_level.c b/examples/spi_24L01_rx/nrf24l01_low_level.c
new file mode 100644
index 0000000000000000000000000000000000000000..3d5c21b9e2aa09a314612d864a04c82127f18384
--- /dev/null
+++ b/examples/spi_24L01_rx/nrf24l01_low_level.c
@@ -0,0 +1,71 @@
+#define SYSTEM_CORE_CLOCK 48000000
+#define APB_CLOCK SYSTEM_CORE_CLOCK
+#include "../../ch32v003fun/ch32v003fun.h"
+
+
+#define CH32V003_SPI_SPEED_HZ 1000000
+#define CH32V003_SPI_DIRECTION_2LINE_TXRX
+#define CH32V003_SPI_CLK_MODE_POL0_PHA0			//leading = rising		trailing = falling		sample on leading		default if you're unsure
+#define CH32V003_SPI_NSS_SOFTWARE_ANY_MANUAL	// toggle manually!
+#define CH32V003_SPI_IMPLEMENTATION
+#include "../../extralibs/ch32v003_SPI.h"
+#include "nrf24l01.h"
+
+/*start of low level functions, specific to the mcu and compiler*/
+
+/*delay in miliseconds*/
+void delay_function(uint32_t duration_ms)
+{
+	Delay_Ms(duration_ms);
+}
+
+/*contains all SPI configuations, such as pins and control registers*/
+/*SPI control: master, interrupts disabled, clock polarity low when idle, clock phase falling edge, clock up tp 1 MHz*/
+void SPI_Initializer()
+{
+	SPI_init();
+	SPI_begin_8();
+}
+
+/*contains all CSN and CE pins gpio configurations, including setting them as gpio outputs and turning SPI off and CE '1'*/
+void pinout_Initializer()
+{
+	// CSN on PC0
+	GPIOC->CFGLR &= ~(0xf<<(4*0));
+	GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*0);
+	// CSN high
+	GPIOC->BSHR = (1<<0);
+	// CE on PC4
+	GPIOC->CFGLR &= ~(0xf<<(4*4));
+	GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*4);
+	// CE HIGH
+	GPIOC->BSHR = (1<<4);
+}
+
+/*CSN pin manipulation to high or low (SPI on or off)*/
+void nrf24_SPI(uint8_t input)
+{
+	if (input > 0) {
+		GPIOC->BSHR = (1<<(0+0));
+	}
+	else {
+		GPIOC->BSHR = (1<<(16+0));
+	}
+}
+
+/*1 byte SPI shift register send and receive routine*/
+uint8_t SPI_send_command(uint8_t command)
+{
+	return SPI_transfer_8(command);
+}
+
+/*CE pin maniplation to high or low*/
+void nrf24_CE(uint8_t input)
+{
+	if (input > 0) {
+		GPIOC->BSHR = (1<<(0+4));
+	}
+	else {
+		GPIOC->BSHR = (1<<(16+4));
+	}
+}
diff --git a/examples/spi_24L01_rx/spi_24L01_rx.c b/examples/spi_24L01_rx/spi_24L01_rx.c
new file mode 100644
index 0000000000000000000000000000000000000000..0b20192b71d7272843f9ab316fbfed82b1f9d9d7
--- /dev/null
+++ b/examples/spi_24L01_rx/spi_24L01_rx.c
@@ -0,0 +1,136 @@
+/*
+ * Example for 24L01+ over SPI, using https://github.com/ebrezadev/nRF24L01-C-Driver
+ * 04-26-2023 recallmenot 
+ */
+
+#define SYSTEM_CORE_CLOCK 48000000
+#define APB_CLOCK SYSTEM_CORE_CLOCK
+
+#include "../../ch32v003fun/ch32v003fun.h"
+#include <stdio.h>
+#include "nrf24l01.h"
+
+
+
+#define TIME_GAP 300
+uint8_t ascending_number = 0;
+char txt[16];
+
+
+
+//######### debug fn
+
+void uint8_to_binary_string(uint8_t value, char* output, int len) {
+		for (int i = 0; i < len; i++) {
+				output[len - i - 1] = (value & 1) ? '1' : '0';
+				value >>= 1;
+		}
+		output[len] = '\0';
+}
+
+
+void print_reg(char* name, uint8_t addr) {
+	char str[9];
+	uint8_t REG;
+	nrf24_read(addr, &REG, 1, CLOSE);
+	uint8_to_binary_string(REG, str, 8);
+	printf("				 %s register: %s\n\r", name, str);
+}
+
+
+void print_debug() {
+	print_reg("FEATURE      ", FEATURE_ADDRESS);
+	print_reg("TX OBSERVE   ", OBSERVE_TX_ADDRESS);
+	print_reg("STATUS       ", STATUS_ADDRESS);
+	print_reg("RX_PW_P0 ADDR", RX_ADDR_P0_ADDRESS);
+	print_reg("TX ADDR      ", TX_ADDR_ADDRESS);
+	print_reg("EN_AA        ", EN_AA_ADDRESS);
+	print_reg("EN_RXADDR    ", EN_RXADDR_ADDRESS);
+}
+
+
+
+//######### LED fn
+
+// wire PD4 to LED1 on the dev board (-)
+inline void led_on() {
+	GPIOD->BSHR = 1<<(16+4);
+}
+
+inline void led_off() {
+	GPIOD->BSHR = 1<<4;
+}
+
+
+
+//######### RX fn
+
+uint8_t recvnumber() {
+  return nrf24_receive(&ascending_number, 1);
+}
+
+uint8_t recvstr() {
+  return nrf24_receive((uint8_t*)&txt, 16);
+}
+
+void receive() {
+	// to switch between sending an uint8_t and a 16-byte-char-array, just uncomment one of these two:
+	//uint8_t result = recvnumber();
+	uint8_t result = recvstr();
+	// also uncomment the corresponding one for case OPERATION_DONE
+
+	//print_debug();
+	switch(result) {
+		case OPERATION_ERROR:
+			printf("EEE   RX operation error\n\r");
+			break;
+		case RECEIVE_FIFO_EMPTY:
+			printf("      RX empty\n\r");
+			//printf("      RX empty, last received: %u", ascending_number);
+			break;
+		case OPERATION_DONE:
+			led_on();
+			// pick one of these two:
+			//printf("***   RX success, received: %u\n\r", ascending_number);
+			printf("***   RX success, received: %s\n\r", txt);
+			led_off();
+			break;
+	}
+	Delay_Ms(TIME_GAP);
+}
+
+
+
+//######### MAIN
+
+int main()
+{
+	SystemInit48HSI();
+
+	// start serial @ default 115200bps
+	SetupUART( UART_BRR );
+	Delay_Ms( 100 );
+
+	printf("\r\r\n\nspi_24L01_RX\n\r");
+
+	printf("initializing radio as RX...");
+	nrf24_device(RECEIVER, RESET);
+	nrf24_rf_power(18);						//default TX power is -6dB, pretty strong, reduce to -18dBm for one room (ACK = TX)
+	//nrf24_automatic_retransmit_setup(RETRANSMIT_DELAY_DEFAULT, 0);
+	printf("done.\n\r");
+
+	print_debug();
+
+	// GPIO D0 Push-Pull for RX notification
+	RCC->APB2PCENR |= RCC_APB2Periph_GPIOD;
+	GPIOD->CFGLR &= ~(0xf<<(4*4));
+	GPIOD->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*4);
+
+	Delay_Ms(1000);
+
+	printf("looping...\n\r");
+	while(1)
+	{
+		receive();
+	}
+}
diff --git a/examples/spi_24L01_tx/Makefile b/examples/spi_24L01_tx/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..6ad3d51350ca002205f5ad25ed107cfdf4086b2a
--- /dev/null
+++ b/examples/spi_24L01_tx/Makefile
@@ -0,0 +1,11 @@
+all : flash
+
+TARGET:=spi_24L01_tx
+ADDITIONAL_C_FILES+=nrf24l01_low_level.c nrf24l01.c 
+
+CFLAGS+=-DSTDOUT_UART
+
+include ../../ch32v003fun/ch32v003fun.mk
+
+flash : cv_flash
+clean : cv_clean
diff --git a/examples/spi_24L01_tx/README.md b/examples/spi_24L01_tx/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..7f6026b3542ccd3174f7a3cf2337bdff491ca2e7
--- /dev/null
+++ b/examples/spi_24L01_tx/README.md
@@ -0,0 +1,49 @@
+# SPI nRF24L01+ demo
+This "example" consists of three parts:
+A SPI Library for the ch32v003, a platform-agnostic library for the nRF24L01(+) and the actual example.
+
+## SPI library
+The SPI library helps you configure the SPI registers.
+Initially the idea was to make it Arduino-compatible but GPL.
+This is free-er.
+Currently, only master-mode is supported, either R+W or W-only.
+Then you can start talking on the SPI bus through SPI_read_x(), SPI_write_x() and, most importantly SPI_transfer_x(), where the x can be a 8 or 16 bit word (this is C, so no overloading).
+When you're done you can even shut down the SPI peripheral to reduce current consumption.
+
+I welcome any improvements you may choose to make, it's far from complete or good.
+CRC, for example, is not implemented yet and I'm incapable of making DMA easy, as the spi dac example showed the processor can be mostly "braindead" and the tiny chip can still output loads of data.
+
+## nRF24L01(+) library
+This is the doing of [Reza Ebrahimi](https://github.com/ebrezadev), I've just included a copy of [his library](https://github.com/ebrezadev/nRF24L01-C-Driver) here and made some modifications.
+
+## example
+The example shows how to send either a uint8_t or a char[16].
+To choose, move the comments.
+The receiver-part is spi_24L01_rx, split in two because I don't know how to have the unified makefile recurse any differently.
+You can enable / disable acknowledge mode by using "ACK_MODE" or "NO_ACK_MODE" in the call to nrf24_transmit().
+
+### nRF module
+Never connect the nRF module to 5V, it operates at 1.9-3.3V, much past that it will probably die!
+Also, I've become convinced the briliant engineers at nordic are evil!!
+Thanks to nordics specifications in the datasheet, the chinese module manufacturers don't even pack 100nF onto the board as bypass caps!
+Four hours of trouble-shooting later I stumbled upon the magic of adding a capacitor!!
+For you see, as the IC transmits its packets, it draws huge amounts.. wrong!
+Even in RX-mode, additional capacitance is essential for basic operation, let alone the acknowledge mode where they need to switch roles within 500uS to exchange a "roger roger".
+To the VCC and GND pins I added a 33uF 16V each. Though a 10uF ceramic capacitor would likely have been better due to lower ESR. The blue polyester caps are 220nF, straight onto the 10nF(!!) bypass cap, but I doubt they are strictly necessary.
+Can you imagine they saved 0.006$ and now we have to get out ye olde soldering iron so these work properly!!
+
+#### pinout for ch32v003
+Please check whether your module looks like [THIS](https://www.circuitspecialists.com/content/552219/NRF24L01-RF-2.jpg) before using this pinout!
+perspective: looking at the underside of the module, pin header in the top right corner
+
+nRF		  | ch32		|	nRF		| ch32
+--------|---------|-------|------
+VCC     | 3V3     | GND   | GND
+CSN/SS  |	C0			|	CE		| C4
+MOSI	  | C6			|	SCK		| C5
+IRQ		  | NC			|	MISO	| C7
+
+And then wire D4 to LED1 on the evaluation board.
+
+#### Arduino friend
+If, like me, you only have the one ch32v003 evaluation board, you can find a [friend for it here](https://github.com/recallmenot/ch32v003fun_friends/tree/main/Arduino/NRF24L01_RX).
diff --git a/examples/spi_24L01_tx/nrf24l01.c b/examples/spi_24L01_tx/nrf24l01.c
new file mode 100644
index 0000000000000000000000000000000000000000..75a2111beb5593bcad897f410cd03315d2fb06cf
--- /dev/null
+++ b/examples/spi_24L01_tx/nrf24l01.c
@@ -0,0 +1,616 @@
+#include "nrf24l01.h"
+
+/*nRF24L01+ features, enable / disable as needed*/
+static uint8_t NRF24_en_ack = ENABLE;
+static uint8_t NRF24_en_no_ack = ENABLE;
+static uint8_t NRF24_en_dynamic_payload = ENABLE;
+
+/*global variables related to this file*/
+static uint8_t SPI_command;                                       /*1 byte spi command*/
+static uint8_t register_current_value;                            /*in order to change some bits of internal registers or to check their content*/
+static uint8_t register_new_value;                                /*used to write new value to nrf24l01+ registers*/
+static uint8_t write_pointer;                                     /*used as an input for read and write functions (as a pointer)*/
+static uint8_t current_address_width;                             /*current address width for receiver pipe addresses (up to 6 pipes), from 3 to 5 bytes*/
+static uint8_t reset_flag = 0;                                    /*reset flag lets the software know if the nrf24l01+ has ever been reset or not*/
+static uint8_t current_mode = DEVICE_NOT_INITIALIZED;             /*current mode of operation: DEVICE_NOT_INITIALIZED, PRX, PTX, STANDBYI, STANDBYII, POWER_DOWN*/
+static uint8_t current_payload_width;                             /*payload width could be from 1 to 32 bytes, in either dynamic or static forms*/
+static uint8_t current_acknowledgement_state = NO_ACK_MODE;
+
+/*2 dimensional array of pipe addresses (5 byte address width) by default. you can change addresses using a new array later.
+  Pipe 1 address could be anything. pipe 3 to 6 addresses share the first 4 bytes with pipe 2 and only differ in byte 5*/
+uint8_t datapipe_address[MAXIMUM_NUMBER_OF_DATAPIPES][ADDRESS_WIDTH_DEFAULT] = {
+  {0X20, 0XC3, 0XC2, 0XC1, 0XA0},
+  {0X20, 0XC3, 0XC2, 0XC1, 0XA1},
+  {0X20, 0XC3, 0XC2, 0XC1, 0XA2},
+  {0X20, 0XC3, 0XC2, 0XC1, 0XA3},
+  {0X20, 0XC3, 0XC2, 0XC1, 0XA4},
+  {0X20, 0XC3, 0XC2, 0XC1, 0XA5}
+};
+
+/*function for PTX device to transmit 1 to 32 bytes of data, used for both dynamic payload length
+   and static payload length methods. acknowledgemet state could be NO_ACK_MODE or ACK_MODE*/
+uint8_t nrf24_transmit(uint8_t *payload, uint8_t payload_width, uint8_t acknowledgement_state)
+{
+  nrf24_read(STATUS_ADDRESS, &register_current_value, 1, CLOSE);         /*in order to check TX_FIFO status*/
+  if ((!(register_current_value & (1 << TX_FULL))) && (current_mode == PTX))
+  {
+    current_acknowledgement_state = acknowledgement_state;      /*setting the acknowledgement state to either NO_ACK or ACK, based on input*/
+    if (NRF24_en_dynamic_payload == ENABLE)
+      current_payload_width = payload_width;
+    nrf24_send_payload(payload, payload_width);          /*the actual function to send data*/
+    return (TRANSMIT_BEGIN);                                     /*TX FIFO is not full and nrf24l01+ mode is standby ii or ptx*/
+  }
+  else
+  {
+    return (TRANSMIT_FAIL);            /*TX FIFO full or wrong mode*/
+  }
+}
+
+/*used by nrf24_transmit function to send the actual data*/
+void nrf24_send_payload(uint8_t *payload, uint8_t payload_width)
+{
+  nrf24_SPI(SPI_ON);
+  if (current_acknowledgement_state == NO_ACK_MODE)
+    SPI_command = W_TX_PAYLOAD_NOACK;
+  else
+    SPI_command = W_TX_PAYLOAD;
+  SPI_send_command(SPI_command);
+  for (; payload_width; payload_width--)
+  {
+    SPI_command = *payload;
+    SPI_send_command(SPI_command);
+    payload++;
+  }
+  nrf24_SPI(SPI_OFF);
+}
+
+/*reports back transmit status: TRANSMIT_DONE, TRANSMIT_FAILED (in case of reaching maximum number of retransmits in auto acknowledgement mode)
+  and TRANSMIT_IN_PROGRESS, if neither flags are set. automatically resets the '1' flags.*/
+uint8_t nrf24_transmit_status()
+{
+  nrf24_read(STATUS_ADDRESS, &register_current_value, 1, CLOSE);      /*status register is read to check TX_DS flag*/
+  if (register_current_value & (1 << TX_DS))                          /*if the TX_DS == 1, */
+  {
+    nrf24_write(STATUS_ADDRESS, &register_current_value, 1, CLOSE);   /*reseting the TX_DS flag. as mentioned by datasheet, writing '1' to a flag resets that flag*/
+    return TRANSMIT_DONE;
+  }
+  else if (register_current_value & (1 << MAX_RT))
+  {
+    nrf24_write(STATUS_ADDRESS, &register_current_value, 1, CLOSE);   /*reseting the MAX_RT flag. as mentioned by datasheet, writing '1' to a flag resets that flag*/
+    return TRANSMIT_FAILED;
+  }
+  else
+    return TRANSMIT_IN_PROGRESS;
+}
+
+/*the receive function output is used as a polling method to check the received data inside RX FIFOs. 
+If there is any data available, it will be loaded inside payload array*/
+uint8_t nrf24_receive(uint8_t *payload, uint8_t payload_width)
+{
+  if (current_mode == PRX)
+  {
+    nrf24_read(STATUS_ADDRESS, &register_current_value, 1, CLOSE);
+    if (register_current_value & (1 << RX_DR))                         /*if received data is ready inside RX FIFO*/
+    {
+      if(NRF24_en_dynamic_payload == DISABLE)                                    /*if dynamif payload width is disabled, use the static payload width and ignore the input*/
+        payload_width = current_payload_width;
+        
+      nrf24_SPI(SPI_ON);                                                /*sending the read payload command to nrf24l01+*/                          
+      SPI_command = R_RX_PAYLOAD;
+      SPI_send_command(SPI_command);
+       
+      for (; payload_width; payload_width--)
+      {
+        SPI_command = NOP_CMD;
+        *payload = SPI_send_command(SPI_command);
+        payload++;
+      }
+      nrf24_SPI(SPI_OFF); 
+      nrf24_read(FIFO_STATUS_ADDRESS, &register_current_value, 1, CLOSE);   /*in order to check the RX_EMPTY flag*/
+      if(register_current_value & (1 << RX_EMPTY))                        /*if the RX FIFO is empty, reset the RX_DR flag inside STATUS register*/
+      {
+        nrf24_read(STATUS_ADDRESS, &register_current_value, 1, CLOSE);
+        register_new_value = register_current_value | (1 << RX_DR);
+        nrf24_write(STATUS_ADDRESS, &register_new_value, 1, CLOSE); 
+      }      
+      return OPERATION_DONE;
+    }
+    else
+    {
+      return RECEIVE_FIFO_EMPTY;
+    }
+  }
+  else
+    return OPERATION_ERROR;
+}
+
+/*function which uses TX_FLUSH or RX_FLUSH command to flush the fifo buffers. if successful, output is OPERATION_DONE.
+   if not successful (wrong input or wrong mode of operation) output will be OPERATION_ERROR*/
+uint8_t nrf24_flush(uint8_t fifo_select)
+{
+  switch (fifo_select)
+  {
+    case TX_BUFFER:
+      if (current_mode == PTX)
+      {
+        nrf24_SPI(SPI_ON);
+        SPI_command = FLUSH_TX;
+        SPI_send_command(SPI_command);
+        nrf24_SPI(SPI_OFF);
+        return OPERATION_DONE;
+      }
+      else
+        return OPERATION_ERROR;
+    case RX_BUFFER:
+      if (current_mode == PRX)
+      {
+        nrf24_SPI(SPI_ON);
+        SPI_command = FLUSH_RX;
+        SPI_send_command(SPI_command);
+        nrf24_SPI(SPI_OFF);
+        return OPERATION_DONE;
+      }
+      else
+        return OPERATION_ERROR;
+    default:
+      return OPERATION_ERROR;
+  }
+}
+
+/*must be called atleast once, which happens with calling nrf24_device function*/
+void nrf24_reset()
+{
+  reset_flag = RESET;
+  nrf24_CE(CE_OFF);
+  register_new_value = CONFIG_REGISTER_DEFAULT;
+  nrf24_write(CONFIG_ADDRESS, &register_new_value, 1, CLOSE);
+  register_new_value = EN_AA_REGISTER_DEFAULT;
+  nrf24_write(EN_AA_ADDRESS, &register_new_value, 1, CLOSE);
+  register_new_value = EN_RXADDR_REGISTER_DEFAULT;
+  nrf24_write(EN_RXADDR_ADDRESS, &register_new_value, 1, CLOSE);
+  register_new_value = SETUP_AW_REGISTER_DEFAULT;
+  nrf24_write(SETUP_AW_ADDRESS, &register_new_value, 1, CLOSE);
+  register_new_value = RF_CH_REGISTER_DEFAULT;
+  nrf24_write(RF_CH_ADDRESS, &register_new_value, 1, CLOSE);
+  register_new_value = RF_SETUP_REGISTER_DEFAULT;
+  nrf24_write(RF_SETUP_ADDRESS, &register_new_value, 1, CLOSE);
+  register_new_value = STATUS_REGISTER_DEFAULT;
+  nrf24_write(STATUS_ADDRESS, &register_new_value, 1, CLOSE);
+
+  nrf24_mode(PTX);
+  nrf24_flush(TX_BUFFER);
+  nrf24_mode(PRX);
+  nrf24_flush(RX_BUFFER);
+
+  nrf24_read(STATUS_ADDRESS, &register_current_value, 1, CLOSE);
+  register_new_value = register_current_value | (1 << RX_DR) | (1 << TX_DS) | (1 << MAX_RT);
+  nrf24_write(STATUS_ADDRESS, &register_new_value, 1, CLOSE);
+  
+  nrf24_interrupt_mask(ENABLE, ENABLE, ENABLE);
+  nrf24_crc_configuration(ENABLE, 1);
+  nrf24_address_width(ADDRESS_WIDTH_DEFAULT);
+  nrf24_rf_datarate(RF_DATARATE_DEFAULT);
+  nrf24_rf_power(RF_PWR_DEFAULT);
+  nrf24_rf_channel(RF_CHANNEL_DEFAULT);
+  nrf24_datapipe_enable(NUMBER_OF_DP_DEFAULT);
+  /*nrf24_datapipe_address_configuration();*/
+  /*nrf24_datapipe_ptx(1);*/
+  nrf24_prx_static_payload_width(STATIC_PAYLOAD_WIDTH_DEFAULT, NUMBER_OF_DP_DEFAULT);
+  nrf24_automatic_retransmit_setup(RETRANSMIT_DELAY_DEFAULT, RETRANSMIT_COUNT_DEFAULT);
+  nrf24_auto_acknowledge_datapipe(NUMBER_OF_DP_DEFAULT);
+  nrf24_auto_acknowledge_datapipe(0);
+  nrf24_dynamic_payload(NRF24_en_dynamic_payload, NUMBER_OF_DP_DEFAULT);
+  nrf24_payload_without_ack(NRF24_en_no_ack);
+  nrf24_payload_with_ack(NRF24_en_ack);
+}
+
+/*used by firmware to set the nrf24 mode in TRANSMITTER, RECEIVER, POWER_SAVING or TURN_OFF states, and reseting the device
+  if it has not been done yet. This is the initializer, and everything starts by calling nrf24_device first.It has a higher
+  level of abstraction than nrf24_mode and must be used by user*/
+void nrf24_device(uint8_t device_mode, uint8_t reset_state)
+{
+  SPI_Initializer();
+  pinout_Initializer();
+  delay_function(STARTUP_DELAY);
+
+  if ((reset_state == RESET) || (reset_flag == 0))
+  {
+    nrf24_reset();
+  }
+
+  switch (device_mode)
+  {
+    case TRANSMITTER:
+      nrf24_mode(POWER_DOWN);
+      nrf24_interrupt_mask(ENABLE, DISABLE, DISABLE);                /*disabling tx interrupt mask*/
+      nrf24_mode(PTX);
+      break;
+    case RECEIVER:
+      nrf24_mode(POWER_DOWN);
+      nrf24_interrupt_mask(DISABLE, ENABLE, ENABLE);                /*disabling rx interrupt mask*/
+      nrf24_mode(PRX);
+      delay_function(PRX_MODE_DELAY);                              /*100ms for PRX mode*/
+      break;
+    case POWER_SAVING:
+      nrf24_mode(POWER_DOWN);
+      nrf24_interrupt_mask(ENABLE, ENABLE, ENABLE);
+      nrf24_mode(STANDBYI);
+      break;
+    case TURN_OFF:
+      nrf24_mode(POWER_DOWN);
+      nrf24_interrupt_mask(ENABLE, ENABLE, ENABLE);
+      break;
+    default:
+      nrf24_mode(POWER_DOWN);
+      nrf24_interrupt_mask(ENABLE, ENABLE, ENABLE);
+      break;
+  }
+}
+
+/*setting automatic retransmit delay time and maximum number of retransmits*/
+void nrf24_automatic_retransmit_setup(uint16_t delay_time, uint8_t retransmit_count)
+{
+  register_new_value = 0x00;
+  for (; (delay_time > 250) && (register_new_value < 0X0F); delay_time -= 250)
+    register_new_value++;
+  register_new_value <<= ARD_0;
+  if ((retransmit_count > 0) && (retransmit_count < 16))
+    register_new_value |= retransmit_count;
+  else
+    register_new_value |= 0;
+  nrf24_write(SETUP_RETR_ADDRESS, &register_new_value, 1, CLOSE);
+}
+
+/*setting auto acknoledgement on datapipes*/
+void nrf24_auto_acknowledge_datapipe(uint8_t datapipe)
+{
+  if (datapipe < 7)
+    register_new_value = (1 << datapipe);
+  nrf24_write(EN_AA_ADDRESS, &register_new_value, 1, CLOSE);
+}
+
+/*turns on or off the dynamic payload width capability*/
+void nrf24_dynamic_payload(uint8_t state, uint8_t datapipe)
+{
+  nrf24_auto_acknowledge_datapipe(datapipe);                        /*setting auto acknowledgment before setting dynamic payload*/
+  nrf24_auto_acknowledge_datapipe(0);
+  nrf24_read(FEATURE_ADDRESS, &register_current_value, 1, CLOSE);
+  if (state == ENABLE)
+  {
+    register_new_value = register_current_value | (1 << EN_DPL);    /*EN_DPL bit turns dynamic payload width on or off on all datapipes*/
+    nrf24_write(FEATURE_ADDRESS, &register_new_value, 1, CLOSE);
+    if (datapipe < 7)
+      register_new_value = (1 << datapipe) - 1;                       /*turning on dynamic payload width on chosen datapipes, using DYNPD register*/
+    nrf24_write(DYNPD_ADDRESS, &register_new_value, 1, CLOSE);
+    NRF24_en_dynamic_payload = ENABLE;
+  }
+  else
+  {
+    register_new_value = register_current_value & (~(1 << EN_DPL));
+    nrf24_write(FEATURE_ADDRESS, &register_new_value, 1, CLOSE);
+    NRF24_en_dynamic_payload = DISABLE;
+  }
+}
+
+/*function to enable or disable sending without acknowledge.
+   if disabled, TX must send a payload with ACK-request and receiver must be able to answer it.
+   manipulates EN_DYN_ACK inside FEATURE*/
+void nrf24_payload_without_ack(uint8_t state)
+{
+  if (state == ENABLE)
+  {
+    nrf24_read(FEATURE_ADDRESS, &register_current_value, 1, CLOSE);
+    register_new_value = register_current_value | (1 << EN_DYN_ACK);
+    nrf24_write(FEATURE_ADDRESS, &register_new_value, 1, CLOSE);
+  }
+  else
+  {
+    nrf24_read(FEATURE_ADDRESS, &register_current_value, 1, CLOSE);
+    register_new_value = register_current_value & (~(1 << EN_DYN_ACK));
+    nrf24_write(FEATURE_ADDRESS, &register_new_value, 1, CLOSE);
+  }
+}
+
+/*function to enable or disable sending with acknowledge.
+   if disabled, the payload can be sent only without ACK-request.
+   manipulates EN_ACK_PAY and EN_DPL inside FEATURE as Dynamic Payload Length is required.*/
+void nrf24_payload_with_ack(uint8_t state)
+{
+  if (state == ENABLE)
+  {
+    nrf24_read(FEATURE_ADDRESS, &register_current_value, 1, CLOSE);
+    register_new_value = register_current_value | (1 << EN_ACK_PAY) | (1 << EN_DPL);
+    nrf24_write(FEATURE_ADDRESS, &register_new_value, 1, CLOSE);
+    nrf24_read(DYNPD_ADDRESS, &register_current_value, 1, CLOSE);
+	// enable dynamic payload for all pipes
+    register_new_value = register_current_value | 0b111111;
+    nrf24_write(DYNPD_ADDRESS, &register_new_value, 1, CLOSE);
+  }
+  else
+  {
+    nrf24_read(FEATURE_ADDRESS, &register_current_value, 1, CLOSE);
+    register_new_value = register_current_value & (~((1 << EN_ACK_PAY) | (1 << EN_DPL)));
+    nrf24_write(FEATURE_ADDRESS, &register_new_value, 1, CLOSE);
+  }
+}
+
+/*on nrf24l01+ there is only one address for PTX device which must be the same as PRX data pipe address 0*/
+void nrf24_datapipe_ptx(uint8_t datapipe_number)
+{
+  nrf24_write(TX_ADDR_ADDRESS, &datapipe_address[datapipe_number - 1][0], current_address_width, CLOSE);
+}
+
+/*setting the 6 datapipe addresses using the datapipe_address[][]*/
+void nrf24_datapipe_address_configuration()
+{
+  uint8_t address = RX_ADDR_P0_ADDRESS;
+  for (uint8_t counter = 0; counter < 6; counter++)
+  {
+    nrf24_write(address, &datapipe_address[counter][0], current_address_width, CLOSE);
+    address++;
+  }
+}
+
+/*function to change static payload width, from 1 to 32 bytes in each payload*/
+void nrf24_prx_static_payload_width(uint8_t static_payload_width, uint8_t number_of_datapipes)
+{
+  for (uint8_t address = RX_PW_P0_ADDRESS; number_of_datapipes; number_of_datapipes--)
+  {
+    nrf24_write(address, &static_payload_width, 1, CLOSE);
+    address++;
+  }
+  current_payload_width = static_payload_width;
+}
+
+/*datapipes are turned on and off using EN_RXADDR register, PRX datapipe addresses are located in RX_ADDR_Pn, TX address is located inside TX_ADDR*/
+void nrf24_datapipe_enable(uint8_t datapipe)
+{
+  nrf24_read(EN_RXADDR_ADDRESS, &register_current_value, 1, CLOSE);
+  if (NRF24_en_ack) {
+    register_current_value |= (1 << ERX_P0);
+  }
+  register_new_value = register_current_value | (1 << datapipe);
+  nrf24_write(EN_RXADDR_ADDRESS, &register_new_value, 1, CLOSE);
+}
+
+/*function to set the nrf24l01+ address width, from 3 to 5 bytes*/
+void nrf24_address_width(uint8_t address_width)
+{
+  if ((address_width <= 5) && (address_width >= 3))
+  {
+    write_pointer = address_width - 2;
+  }
+  else
+  {
+    write_pointer = 3;
+  }
+  nrf24_write(SETUP_AW_ADDRESS, &write_pointer, 1, CLOSE);                    /*5 bytes is the maximum address width available*/
+  current_address_width = address_width;
+}
+
+/*datarate settings, you can choose between 2mbps, 1mbps, 250kbps*/
+void nrf24_rf_datarate(uint16_t rf_datarate)
+{
+  nrf24_read(RF_SETUP_ADDRESS, &register_current_value, 1, CLOSE);
+  register_current_value &= ~((1 << RF_DR_LOW) | (1 << RF_DR_HIGH));
+  switch (rf_datarate)
+  {
+    case 2000:
+      register_new_value = register_current_value | (1 << RF_DR_HIGH);
+      break;
+    case 1000:
+      register_new_value = register_current_value;
+      break;
+    case 250:
+      register_new_value = register_current_value | (1 << RF_DR_LOW);
+      break;
+    default:
+      register_new_value = register_current_value;
+      break;
+  }
+  nrf24_write(RF_SETUP_ADDRESS, &register_new_value, 1, CLOSE);
+}
+
+/*nrf24l01+ RF power settings. 0dbm, -6dbm, -12dbm, -18dbm*/
+void nrf24_rf_power(uint8_t rf_power)
+{
+  nrf24_read(RF_SETUP_ADDRESS, &register_current_value, 1, CLOSE);
+  register_current_value &= ~((1 << RF_PWR_1) | (1 << RF_PWR_0));
+  switch (rf_power)
+  {
+    case 0:
+      register_new_value = register_current_value | ((1 << RF_PWR_1) | (1 << RF_PWR_0));
+      break;
+    case 6:
+      register_new_value = register_current_value | (1 << RF_PWR_1);
+      break;
+    case 12:
+      register_new_value = register_current_value | (1 << RF_PWR_0);
+      break;
+    case 18:
+      register_new_value = register_current_value;
+      break;
+    default:
+      register_new_value = register_current_value | (1 << RF_PWR_1);
+      break;
+  }
+  nrf24_write(RF_SETUP_ADDRESS, &register_new_value, 1, CLOSE);
+}
+
+/*read whether the current channel is busy (has traffic), needs to be called from RX mode*/
+uint8_t nrf24_rf_channel_read_busy(uint8_t rf_channel)
+{
+  uint8_t signals_detected;
+  nrf24_read(RPD_REG_ADDRESS, &signals_detected, 1, CLOSE);
+  if (signals_detected) {
+    return CHANNEL_BUSY;
+  }
+  else {
+    return CHANNEL_CLEAR;
+  }
+}
+
+/*test whether a channel is busy (has traffic), waiting for ms_to_test*/
+uint8_t nrf24_rf_channel_test_busy(uint8_t rf_channel, uint16_t ms_to_test)
+{
+  if ((rf_channel <= 125) && (rf_channel >= 1))
+  {
+    // back up old channel
+    uint8_t previous_channel;
+    nrf24_read(RF_CH_ADDRESS, &previous_channel, 1, CLOSE);
+    // back up old mode
+    uint8_t previous_mode = current_mode;
+    // switch to new channel
+    nrf24_rf_channel(rf_channel);
+    // switch to RX, Received Power Detector is set to 0 and begins sampling
+    if (previous_mode != PRX) {
+      nrf24_mode(PRX);
+    }
+    // wait at least 1 ms before declaring channel clear
+    delay_function(1 > ms_to_test ? 1 : ms_to_test);
+    // Received Power Detector latches to 1 if there was a signal >-64dBm for at least 40 uS consecutively since RX mode was enabled
+    uint8_t signals_detected = nrf24_rf_channel_read_busy(rf_channel);
+    // switch back to old channel
+    nrf24_rf_channel(previous_channel);
+    // switch back to old mode
+    if (previous_mode != PRX) {
+      nrf24_mode(previous_mode);
+    }
+    if (signals_detected) {
+      return CHANNEL_BUSY;
+    }
+    else {
+      return CHANNEL_CLEAR;
+    }
+  }
+  else
+  {
+		return CHANNEL_BUSY;
+  }
+}
+
+/*nrf24l01+ RF channel selection, from 1 to 125*/
+void nrf24_rf_channel(uint8_t rf_channel)
+{
+  if ((rf_channel <= 125) && (rf_channel >= 1))
+  {
+    uint8_t write_pointer = rf_channel;
+    nrf24_write(RF_CH_ADDRESS, &write_pointer, 1, CLOSE);
+  }
+  else
+  {
+    uint8_t write_pointer = 1;
+    nrf24_write(RF_CH_ADDRESS, &write_pointer, 1, CLOSE);
+  }
+}
+
+/*interrupt mask settings. 3 seperate masks for RX, TX, and RT (maximum numbers of retransmission reached*/
+void nrf24_interrupt_mask(uint8_t rx_mask, uint8_t tx_mask, uint8_t max_rt_mask)
+{
+  nrf24_read(CONFIG_ADDRESS, &register_current_value, 1, CLOSE);
+  if (rx_mask)
+    register_new_value = (register_current_value) | (1 << MASK_RX_DR);
+  else
+    register_new_value &= (~(1 << MASK_RX_DR));
+  if (tx_mask)
+    register_new_value |= (1 << MASK_TX_DS);
+  else
+    register_new_value &= (~(1 << MASK_TX_DS));
+  if (max_rt_mask)
+    register_new_value |= (1 << MASK_MAX_RT);
+  else
+    register_new_value &= (~(1 << MASK_MAX_RT));
+
+  nrf24_write(CONFIG_ADDRESS, &register_new_value, 1, CLOSE);
+}
+
+/*enabling or disabling crc in payload; setting crc encoding scheme between 1 or 2 bytes*/
+void nrf24_crc_configuration(uint8_t crc_enable, uint8_t crc_encoding_scheme)
+{
+  nrf24_read(CONFIG_ADDRESS, &register_current_value, 1, CLOSE);
+  if (crc_enable)
+    register_new_value = (register_current_value) | (1 << EN_CRC);
+  else
+    register_new_value &= (~(1 << EN_CRC));
+  if (crc_encoding_scheme == 2)
+    register_new_value |= (1 << CRCO);
+  else
+    register_new_value &= (~(1 << CRCO));
+
+  nrf24_write(CONFIG_ADDRESS, &register_new_value, 1, CLOSE);
+}
+
+/*mode selector: power down, standby i, standby ii, ptx, prx. used by nrf24_device function*/
+void nrf24_mode(uint8_t mode)
+{
+  nrf24_read(CONFIG_ADDRESS, &register_current_value, 1, CLOSE);
+  switch (mode)
+  {
+    case POWER_DOWN:
+      nrf24_CE(CE_OFF);
+      register_new_value = (register_current_value) & (~(1 << PWR_UP));
+      delay_function(POWER_DOWN_DELAY);
+      break;
+    case STANDBYI:                                 /*standby I is defined by 'PWR_UP = 1' and 'CE pin LOW'*/
+      nrf24_CE(CE_OFF);
+      register_new_value = (register_current_value) | (1 << PWR_UP);
+      delay_function(STANDBYI_DELAY);
+      break;
+    case STANDBYII:                                 /*standby ii is related to a ptx device*/
+      nrf24_CE(CE_ON);
+      register_new_value = ((register_current_value) | (1 << PWR_UP)) & (~(1 << PRIM_RX));
+      delay_function(STANDBYI_DELAY);
+      break;
+    case PTX:
+      nrf24_CE(CE_ON);
+      register_new_value = ((register_current_value) | (1 << PWR_UP)) & (~(1 << PRIM_RX));
+      delay_function(STANDBYI_DELAY);
+      break;
+    case PRX:
+      nrf24_CE(CE_ON);
+      register_new_value = (register_current_value) | (1 << PWR_UP) | (1 << PRIM_RX);
+      delay_function(STANDBYI_DELAY);
+      break;
+    default:
+      nrf24_CE(CE_OFF);
+      register_new_value = (register_current_value) & (~(1 << PWR_UP));
+      delay_function(POWER_DOWN_DELAY);
+      break;
+  }
+  nrf24_write(CONFIG_ADDRESS, &register_new_value, 1, CLOSE);
+  current_mode = mode;
+}
+
+/*reads the number of bytes (data_length) from the register in nrf24l01+ (address) and stores them inside an array (value),
+  then closes the spi connection (spi_state = CLOSE) or leaves it open (spi_state = OPEN)*/
+void nrf24_read(uint8_t address, uint8_t *value, uint8_t data_length, uint8_t spi_state)
+{
+  nrf24_SPI(SPI_ON);
+  SPI_command = R_REGISTER | address;    /*in order to read CONFIG, then change one bit*/
+  SPI_send_command(SPI_command);
+  SPI_command = NOP_CMD;
+  for (; data_length ; data_length--)
+  {
+    *value = SPI_send_command(SPI_command);
+    value++;
+  }
+  if (spi_state == CLOSE)
+    nrf24_SPI(SPI_OFF);
+}
+
+/*writes the number of bytes (data_length) from an array (value) inside registers in nrf24l01+ (address),
+  then closes the spi connection (spi_state = CLOSE) or leaves it open (spi_state = OPEN)*/
+void nrf24_write(uint8_t address, uint8_t *value, uint8_t data_length, uint8_t spi_state)
+{
+  nrf24_SPI(SPI_ON);
+  SPI_command = W_REGISTER | address;    /*in order to read CONFIG, then change one bit*/
+  SPI_send_command(SPI_command);
+  for (; data_length ; data_length--)
+  {
+    SPI_command = *value;
+    value++;
+    SPI_send_command(SPI_command);
+  }
+  if (spi_state == CLOSE)
+    nrf24_SPI(SPI_OFF);
+}
diff --git a/examples/spi_24L01_tx/nrf24l01.h b/examples/spi_24L01_tx/nrf24l01.h
new file mode 100644
index 0000000000000000000000000000000000000000..f3552406a2500078ccae4c6495cf957161efc398
--- /dev/null
+++ b/examples/spi_24L01_tx/nrf24l01.h
@@ -0,0 +1,219 @@
+#ifndef NRF24L01_H
+#define NRF24L01_H
+/*nrf24l01: MSbit to LSbit, LSbyte to MSbyte*/
+#include <stdio.h>
+#include <stdint.h>
+
+#define STARTUP_DELAY                 150             /*in milliseconds*/
+#define POWER_DOWN_DELAY              2
+#define STANDBYI_DELAY                2
+#define PRX_MODE_DELAY                100
+#define ADDRESS_WIDTH_DEFAULT         5               /*address width in bytes, for default value*/
+#define RF_CHANNEL_DEFAULT            32        
+#define RF_DATARATE_DEFAULT           1000            /*250, 1000, 2000*/
+#define RF_PWR_DEFAULT                6               /*0, -6, -12, -18*/
+#define STATIC_PAYLOAD_WIDTH_DEFAULT  1               /*for static payload mode, configurable between 1 and 32 bytes for PRX device ONLY (RX_PW_Pn, n for data pipe n)(no register for payload length in PTX device)*/
+#define NUMBER_OF_DP_DEFAULT          1               /*number of datapipes, 1 to 6*/ 
+#define RETRANSMIT_DELAY_DEFAULT      500             /*in uS*/
+#define RETRANSMIT_COUNT_DEFAULT      3
+ 
+#define OPEN                          1
+#define CLOSE                         0
+#define ENABLE                        1
+#define DISABLE                       0
+#define SPI_OFF                       1
+#define SPI_ON                        0
+#define CE_OFF                        0
+#define CE_ON                         1
+
+#define CONFIG_REGISTER_DEFAULT       0X08
+#define EN_AA_REGISTER_DEFAULT        0X3F
+#define EN_RXADDR_REGISTER_DEFAULT    0X00
+#define SETUP_AW_REGISTER_DEFAULT     0X03
+#define SETUP_RETR_REGISTER_DEFAULT   0X03
+#define RF_CH_REGISTER_DEFAULT        0X02
+#define RF_SETUP_REGISTER_DEFAULT     0X0E
+#define STATUS_REGISTER_DEFAULT       0X0E
+#define MAXIMUM_NUMBER_OF_DATAPIPES   6
+
+#define POWER_DOWN                    0X00
+#define STANDBYI                      0X01
+#define STANDBYII                     0X02
+#define PTX                           0X03
+#define PRX                           0X04
+#define DEVICE_NOT_INITIALIZED        0X05
+
+#define TRANSMITTER                   0X00
+#define RECEIVER                      0X01
+#define POWER_SAVING                  0X02
+#define TURN_OFF                      0X03
+
+#define RESET                         1
+#define NO_RESET                      0
+#define NO_ACK_MODE                   1
+#define ACK_MODE                      0
+#define TRANSMIT_BEGIN                1
+#define TRANSMIT_FAIL                 0
+#define TRANSMIT_IN_PROGRESS          0
+#define TRANSMIT_DONE                 1
+#define TRANSMIT_FAILED               0XFF
+#define OPERATION_DONE                1
+#define OPERATION_ERROR               0
+#define RECEIVE_FIFO_EMPTY            2
+#define TX_BUFFER                     1
+#define RX_BUFFER                     0
+// return states for nrf24_rf_channel_test_busy
+#define CHANNEL_CLEAR                 0
+#define CHANNEL_BUSY                  1
+
+/*bits definition section*/
+#define MASK_RX_DR          6               /*mask interrupt caused by RX_DR: 1 interrupt not reflected on IRQ pin (IRQ is active low), inside CONFIG register*/
+#define MASK_TX_DS          5               /*mask interrupt caused by TX_DS: 1 interrupt not reflected on IRQ pin (IRQ is active low), inside CONFIG register*/
+#define MASK_MAX_RT         4               /*mask interrupt caused by MAX_RT means maximum number of retransmissions reached: 1 interrupt not reflected on IRQ pin (IRQ is active low), inside CONFIG register*/
+#define EN_CRC              3               /*enale CRC, forced high if one of the bits in EN_AA is high, inside CONFIG register*/
+#define CRCO                2               /*CRC encoding scheme, 0 is 1 byte, 1 is 2 bytes, inside CONFIG register*/
+#define PWR_UP              1               /*1 is power up, inside CONFIG register*/
+#define PRIM_RX             0               /*RX/TX control, 1: PRX, inside CONFIG register*/
+#define ENAA_P5             5               /*enable auto acknowledgement data pipe 5*/
+#define ENAA_P4             4
+#define ENAA_P3             3
+#define ENAA_P2             2
+#define ENAA_P1             1
+#define ENAA_P0             0
+#define ERX_P5              5               /*part of EN_RXADDR, enable data pipe 5*/
+#define ERX_P4              4
+#define ERX_P3              3
+#define ERX_P2              2
+#define ERX_P1              1
+#define ERX_P0              0
+#define AW_1                1               /*RX/TX address field width, 00 illegal, 01 3 bytes, 10 4 bytes, 11 5 bytes*/
+#define AW_0                0
+#define ARD_3               7               /*auto retransmit delay, 0000 250us, 0001 500us ...> 1111 4000us*/
+#define ARD_2               6
+#define ARD_1               5
+#define ARD_0               4
+#define ARC_3               3               /*auto retransmit count, 0000 retransmit deisabled, 1111 up to 15 retransmit on failure of AA. (inside SETUP_RETR register)*/
+#define ARC_2               2
+#define ARC_1               1
+#define ARC_0               0
+#define RF_CH_6             6               /*sets the frequencvy channel nRF24L01+ operates on*/
+#define RF_CH_5             5
+#define RF_CH_4             4
+#define RF_CH_3             3
+#define RF_CH_2             2
+#define RF_CH_1             1
+#define RF_CH_0             0
+#define CONT_WAVE           7               /*enables continuous carrier transmit when high*/
+#define RF_DR_LOW           5               /*sets the RF data rate to 250kbps*/
+#define PLL_LOCK            4               /*force PLL lock signal. used for testing ONLY*/
+#define RF_DR_HIGH          3               /*select between high speed data rates and works ONLY when RF_DR_LOW is 0. 0 for 1Mbps, 1 for 2Mbps*/
+#define RF_PWR_1            2
+#define RF_PWR_0            1
+#define RX_DR               6               /*IRQ for new packet in RX FIFO (newly received)*/
+#define TX_DS               5               /*IRQ for ACK received in TX mode*/
+#define MAX_RT              4 
+#define RX_P_NO_2           3
+#define RX_P_NO_1           2
+#define RX_P_NO_0           1
+#define PLOS_CNT_3          7               /*inside OBSERVE_TX register, counts the total number of retransmissions since last channel change. reset by writing to RF_CH*/
+#define PLOS_CNT_2          6
+#define PLOS_CNT_1          5
+#define PLOS_CNT_0          4
+#define ARC_CNT_3           3               /*inside OBSERVE_TX register, counts the number of retransmissions for current transaction. reset by initiating new transaction*/
+#define ARC_CNT_2           2
+#define ARC_CNT_1           1
+#define ARC_CNT_0           0
+#define RPD                 0               /*received power detector, if received power is less than -64dbm, RPD = 0*/
+#define TX_REUSE            6
+#define TX_FULL             5
+#define TX_EMPTY            4
+#define RX_FULL             1
+#define RX_EMPTY            0
+#define DPL_P5              5
+#define DPL_P4              4
+#define DPL_P3              3
+#define DPL_P2              2
+#define DPL_P1              1
+#define DPL_P0              0                 /*must be set on PTX in dynamic payload length mode*/
+#define EN_DPL              2                 /*set to enable dynamic payload length*/
+#define EN_ACK_PAY          1                 /*used to enable auto acknowledgement with payload in PRX (inside FEATURE register)*/
+#define EN_DYN_ACK          0                 /**/
+
+/*registers definition section*/
+#define CONFIG_ADDRESS              0X00
+#define EN_AA_ADDRESS               0X01              /*enable auto acknowledgement feature*/
+#define EN_RXADDR_ADDRESS           0X02              /*register containing bits to enable 6 data pipes individually*/
+#define SETUP_AW_ADDRESS            0X03              /*address field length is configured in here to be 3, 4 or 5 bytes long*/
+#define SETUP_RETR_ADDRESS          0X04              /*setup ARC bits to configure auto retransmission count*/
+#define RF_CH_ADDRESS               0X05
+#define RF_SETUP_ADDRESS            0X06
+#define STATUS_ADDRESS              0X07              /*contains RX_DR, TX_DS, MAX_RT, RX_P_NO, TX_FULL, send R_REGISTER then NOP to read*/ 
+#define OBSERVE_TX_ADDRESS          0X08              /*contains ARC_CNT and PLOS_CNT, two counters for retransmission. these counters could be used to assess the network quality*/
+#define RPD_REG_ADDRESS             0X09
+#define RX_ADDR_P0_ADDRESS          0X0A              /*the address for PRX device. if a packet contains this address, enhanced shockburst starts validating the packet*/
+#define RX_ADDR_P1_ADDRESS          0X0B              /*a total of 6 unique addresses could be assigned to a PRX device (Multiceiver feature)*/
+#define RX_ADDR_P2_ADDRESS          0X0C              /*these addresses must NOT be the same*/
+#define RX_ADDR_P3_ADDRESS          0X0D
+#define RX_ADDR_P4_ADDRESS          0X0E
+#define RX_ADDR_P5_ADDRESS          0X0F
+#define TX_ADDR_ADDRESS             0X10              /*40 bits long register, transmit address, used for a PTX device only. configure address legth in SETUP_AW register. set RX_ADDR_P0 equal to this address to handle automatic acknowledge*/
+#define RX_PW_P0_ADDRESS            0X11              /*these registers are for setting the static payload length in static payload length mode (receiver side)*/
+#define RX_PW_P1_ADDRESS            0X12
+#define RX_PW_P2_ADDRESS            0X13
+#define RX_PW_P3_ADDRESS            0X14
+#define RX_PW_P4_ADDRESS            0X15
+#define RX_PW_P5_ADDRESS            0X16
+#define FIFO_STATUS_ADDRESS         0X17
+#define DYNPD_ADDRESS               0X1C              /*on receiver side (RX mode), this register must be set to enable dynamic payload length. a PTX in dynamic mode, must have the DYNPD_P0 set*/
+#define FEATURE_ADDRESS             0X1D              /*contains the EN_DPL bit to enable dynamic payload length*/
+
+/*commands definition section*/
+#define R_REGISTER          0X00              /*read commmand and STATUS registers, 5 bit register map address*/
+#define W_REGISTER          0X20              /*write commmand and STATUS registers, 5 bit register map address, executable in POWER DOWN or STANDBY modes only*/
+#define R_RX_PAYLOAD        0X61              /*read RX payload, 1-32 bytes. read operation starts at byte 0. payload is deleted from FIFO after its read*/
+#define W_TX_PAYLOAD        0XA0              /*write TX payload, starts at byte 0, 1-32 bytes*/
+#define FLUSH_TX            0XE1              /*flush TX FIFO, used in TX mode*/
+#define FLUSH_RX            0XE2              /*flush RX FIFO, used in RX mode*/
+#define REUSE_TX_PL         0XE3              /*used for a PTX device, reuse last transmitted payload for an exact number. alternative to auto retransmission*/
+#define R_RX_PL_WID         0X60              /*command for receiver side, in order to read the payload length in dynamic payload length mode*/
+#define W_ACK_PAYLOAD       0XA0              /*used in RX mode, to write payload in TX FIFO and later transmit the payloads along with ACK packet to PTX, if DPL is enabled*/
+#define W_TX_PAYLOAD_NOACK  0XB0              /*used in TX mode, disables AUTOACK on this specific packet. must be first enabled in FEATURE register by setting the EN_DYN_ACK bit. if used, PTX will not wait for ACK and goes directly to standby I*/
+#define NOP_CMD             0XFF              /*might be used to read the status register*/
+
+void nrf24_reset();                            
+void nrf24_device(uint8_t device_mode, uint8_t reset_state);
+uint8_t SPI_send_command(uint8_t command);          
+void pinout_Initializer();         
+void SPI_Initializer();
+void nrf24_mode(uint8_t mode);
+void nrf24_SPI(uint8_t input);
+void nrf24_CE(uint8_t input);
+void nrf24_address_width(uint8_t address_width);
+uint8_t nrf24_rf_channel_read_busy(uint8_t rf_channel);
+uint8_t nrf24_rf_channel_test_busy(uint8_t rf_channel, uint16_t ms_to_test);
+void nrf24_rf_channel(uint8_t rf_channel);
+void nrf24_rf_power(uint8_t rf_power);
+void nrf24_rf_datarate(uint16_t rf_datarate);
+void nrf24_read(uint8_t address, uint8_t *value, uint8_t data_length, uint8_t spi_state);
+void nrf24_write(uint8_t address, uint8_t *value, uint8_t data_length, uint8_t spi_state);
+void delay_function(uint32_t duration_ms);
+void nrf24_crc_configuration(uint8_t crc_enable, uint8_t crc_encoding_scheme);
+void nrf24_interrupt_mask(uint8_t rx_mask, uint8_t tx_mask, uint8_t max_rt_mask);
+void nrf24_datapipe_enable(uint8_t number_of_datapipes);
+void nrf24_prx_static_payload_width(uint8_t static_payload_width, uint8_t number_of_datapipes);
+void nrf24_datapipe_address_configuration();
+void nrf24_datapipe_ptx(uint8_t datapipe_number);
+void nrf24_automatic_retransmit_setup(uint16_t delay_time, uint8_t retransmit_count);
+void nrf24_auto_acknowledge_datapipe(uint8_t datapipe);
+void nrf24_payload_without_ack(uint8_t state);
+void nrf24_payload_with_ack(uint8_t state);
+void nrf24_dynamic_payload(uint8_t state, uint8_t datapipe);
+void nrf24_device(uint8_t device_mode, uint8_t reset_state);
+void nrf24_send_payload(uint8_t *payload, uint8_t payload_width);
+uint8_t nrf24_receive(uint8_t *payload, uint8_t payload_width);
+uint8_t nrf24_transmit(uint8_t *payload, uint8_t payload_width, uint8_t acknowledgement_state);
+uint8_t nrf24_transmit_status();
+void nrf24_dynamic_ack(uint8_t state);
+uint8_t nrf24_flush(uint8_t fifo_select);
+
+#endif
diff --git a/examples/spi_24L01_tx/nrf24l01_low_level.c b/examples/spi_24L01_tx/nrf24l01_low_level.c
new file mode 100644
index 0000000000000000000000000000000000000000..3d5c21b9e2aa09a314612d864a04c82127f18384
--- /dev/null
+++ b/examples/spi_24L01_tx/nrf24l01_low_level.c
@@ -0,0 +1,71 @@
+#define SYSTEM_CORE_CLOCK 48000000
+#define APB_CLOCK SYSTEM_CORE_CLOCK
+#include "../../ch32v003fun/ch32v003fun.h"
+
+
+#define CH32V003_SPI_SPEED_HZ 1000000
+#define CH32V003_SPI_DIRECTION_2LINE_TXRX
+#define CH32V003_SPI_CLK_MODE_POL0_PHA0			//leading = rising		trailing = falling		sample on leading		default if you're unsure
+#define CH32V003_SPI_NSS_SOFTWARE_ANY_MANUAL	// toggle manually!
+#define CH32V003_SPI_IMPLEMENTATION
+#include "../../extralibs/ch32v003_SPI.h"
+#include "nrf24l01.h"
+
+/*start of low level functions, specific to the mcu and compiler*/
+
+/*delay in miliseconds*/
+void delay_function(uint32_t duration_ms)
+{
+	Delay_Ms(duration_ms);
+}
+
+/*contains all SPI configuations, such as pins and control registers*/
+/*SPI control: master, interrupts disabled, clock polarity low when idle, clock phase falling edge, clock up tp 1 MHz*/
+void SPI_Initializer()
+{
+	SPI_init();
+	SPI_begin_8();
+}
+
+/*contains all CSN and CE pins gpio configurations, including setting them as gpio outputs and turning SPI off and CE '1'*/
+void pinout_Initializer()
+{
+	// CSN on PC0
+	GPIOC->CFGLR &= ~(0xf<<(4*0));
+	GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*0);
+	// CSN high
+	GPIOC->BSHR = (1<<0);
+	// CE on PC4
+	GPIOC->CFGLR &= ~(0xf<<(4*4));
+	GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*4);
+	// CE HIGH
+	GPIOC->BSHR = (1<<4);
+}
+
+/*CSN pin manipulation to high or low (SPI on or off)*/
+void nrf24_SPI(uint8_t input)
+{
+	if (input > 0) {
+		GPIOC->BSHR = (1<<(0+0));
+	}
+	else {
+		GPIOC->BSHR = (1<<(16+0));
+	}
+}
+
+/*1 byte SPI shift register send and receive routine*/
+uint8_t SPI_send_command(uint8_t command)
+{
+	return SPI_transfer_8(command);
+}
+
+/*CE pin maniplation to high or low*/
+void nrf24_CE(uint8_t input)
+{
+	if (input > 0) {
+		GPIOC->BSHR = (1<<(0+4));
+	}
+	else {
+		GPIOC->BSHR = (1<<(16+4));
+	}
+}
diff --git a/examples/spi_24L01_tx/spi_24L01_tx.c b/examples/spi_24L01_tx/spi_24L01_tx.c
new file mode 100644
index 0000000000000000000000000000000000000000..46c5f519431ebfe6d690b4a313e15088dd8b8ecf
--- /dev/null
+++ b/examples/spi_24L01_tx/spi_24L01_tx.c
@@ -0,0 +1,150 @@
+/*
+ * Example for 24L01+ over SPI, using https://github.com/ebrezadev/nRF24L01-C-Driver
+ * 04-26-2023 recallmenot 
+ */
+
+#define SYSTEM_CORE_CLOCK 48000000
+#define APB_CLOCK SYSTEM_CORE_CLOCK
+
+#include "../../ch32v003fun/ch32v003fun.h"
+#include <stdio.h>
+#include "nrf24l01.h"
+
+
+
+#define TIME_GAP 1000
+uint8_t ascending_number = 0x00;
+char txt[16];
+
+
+
+//######### debug fn
+
+void uint8_to_binary_string(uint8_t value, char* output, int len) {
+		for (int i = 0; i < len; i++) {
+				output[len - i - 1] = (value & 1) ? '1' : '0';
+				value >>= 1;
+		}
+		output[len] = '\0';
+}
+
+
+void print_reg(char* name, uint8_t addr) {
+	char str[9];
+	uint8_t REG;
+	nrf24_read(addr, &REG, 1, CLOSE);
+	uint8_to_binary_string(REG, str, 8);
+	printf("				 %s register: %s\n\r", name, str);
+}
+
+
+void print_debug() {
+	print_reg("FEATURE      ", FEATURE_ADDRESS);
+	print_reg("TX OBSERVE   ", OBSERVE_TX_ADDRESS);
+	print_reg("STATUS       ", STATUS_ADDRESS);
+	print_reg("RX_PW_P0 ADDR", RX_ADDR_P0_ADDRESS);
+	print_reg("TX ADDR      ", TX_ADDR_ADDRESS);
+	print_reg("EN_AA        ", EN_AA_ADDRESS);
+	print_reg("EN_RXADDR    ", EN_RXADDR_ADDRESS);
+}
+
+
+
+//######### LED fn
+
+// led is PD4 to LED1 on board, which is (-)
+inline void led_on() {
+	GPIOD->BSHR = 1<<(16+4);
+}
+
+inline void led_off() {
+	GPIOD->BSHR = 1<<4;
+}
+
+
+
+//######### TX fn
+
+uint8_t sendnumber() {
+	return nrf24_transmit(&ascending_number, 1, ACK_MODE);
+}
+
+// function prototype (declaration), definition in "ch32v003fun.c"
+int mini_snprintf(char* buffer, unsigned int buffer_len, const char *fmt, ...);
+
+uint8_t sendstr() {
+	mini_snprintf(txt, sizeof(txt), "Hello, %u", ascending_number);
+	printf("\n\rsending %s\n\r", txt);
+	return nrf24_transmit((uint8_t*)txt, 16, ACK_MODE);
+}
+
+void send() {
+	// to switch between sending an uint8_t and a 16-byte-char-array, just uncomment one of these two:
+	//uint8_t tx_cmd_status = sendnumber();
+	uint8_t tx_cmd_status = sendstr();
+	switch (tx_cmd_status) {
+		case TRANSMIT_BEGIN:
+		led_on();
+			printf("***		sending package\n\r");
+			break;
+		case TRANSMIT_FAIL:
+			printf("EEE		unable to send package\n\r");
+			break;
+	}
+
+	Delay_Ms(50);					// give the nRF some time to send
+	print_debug();
+
+	switch (nrf24_transmit_status()) {
+		case TRANSMIT_DONE:
+			printf("*OK		sent: %u\n\r", ascending_number);
+		led_off();
+			break;
+		case TRANSMIT_FAILED:
+			printf("EEE		no ACK received!!\n\r");
+			break;
+		case TRANSMIT_IN_PROGRESS:
+			printf("EEE		still transmitting???\n\r");
+			break;
+	}
+}
+
+
+
+//######### MAIN
+
+
+int main()
+{
+	SystemInit48HSI();
+
+	// start serial @ default 115200bps
+	SetupUART( UART_BRR );
+
+	printf("\r\r\n\nspi_24L01_TX\n\r");
+
+	printf("initializing radio as TX...");
+	nrf24_device(TRANSMITTER, RESET);
+	nrf24_rf_power(18);						//default TX power is -6dB, pretty strong, reduce to -18dBm for one room
+	printf("done.\n\r");
+
+	print_debug();
+
+	// GPIO D4 Push-Pull for foreground blink
+	RCC->APB2PCENR |= RCC_APB2Periph_GPIOD;
+	GPIOD->CFGLR &= ~(0xf<<(4*4));
+	GPIOD->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*4);
+
+	Delay_Ms(1000);
+
+	printf("looping...\n\r");
+	while(1)
+	{
+		Delay_Ms( TIME_GAP );
+		send();
+
+		ascending_number++;
+
+		printf("***		next number: %u\n\r", ascending_number);
+	}
+}
diff --git a/examples/spi_oled/Makefile b/examples/spi_oled/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..77b7065cb887fa2af72cdfcb440dcd92c528fee0
--- /dev/null
+++ b/examples/spi_oled/Makefile
@@ -0,0 +1,12 @@
+all : flash
+
+TARGET:=spi_oled
+
+CFLAGS+=-I../i2c_oled
+#CFLAGS+=-DSTDOUT_UART
+
+include ../../ch32v003fun/ch32v003fun.mk
+
+flash : cv_flash
+clean : cv_clean
+
diff --git a/examples/spi_oled/README.md b/examples/spi_oled/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..36eb8b3fcacda15cd7710bf74a02d315e5e6da20
--- /dev/null
+++ b/examples/spi_oled/README.md
@@ -0,0 +1,36 @@
+# SPI OLED demonstration
+This example code demonstrates use of the CH32V003 SPI peripheral with an SSD1306
+OLED. Three different OLED sizes are supported - 64x32, 128x32, and 128x64.
+It provides a generic SPI port initialization and transmit (write-only) low level
+interface and a high-level graphics driver with pixels, lines, circles, rectangles
+and 8x8 character font rendering. Out of the box this demo runs Conway's Life.
+
+
+
+https://user-images.githubusercontent.com/1132011/236727389-7b778260-87c0-43a8-89e1-8a5459719fdd.mp4
+
+
+
+## Build options
+There are two build-time options in the source-
+
+In spi_oled.c:
+* SSD1306_64X32, SSD1306_128X32, SSD1306_128X64 - choose only one of these
+depending on the type of OLED you've got.
+
+in ssd1306.h:
+* SSD1306_PSZ - the number of bytes to send per SPI data packet. The default value
+of 32 seems to work well. Smaller values are allowed but may result in slower
+refresh rates.
+
+## Use
+Connect an SSD1306-based OLED in SPI interface mode as follows:
+* PC2 - RST
+* PC3 - CS
+* PC4 - DC
+* PC5 - SCK / D0
+* PC6 - MOSI / D1
+
+Observe Conway's Life starting with random pixels, running for 500 iterations and
+then restarting with new random pixels.
+
diff --git a/examples/spi_oled/debug.sh b/examples/spi_oled/debug.sh
new file mode 100755
index 0000000000000000000000000000000000000000..bb05a9465959cb153fad95bba4b2175667830528
--- /dev/null
+++ b/examples/spi_oled/debug.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+# before running this you should start OOCD server
+#../../../MRS_Toolchain_Linux_x64_V1.70/OpenOCD/bin/openocd -f ../../../MRS_Toolchain_Linux_x64_V1.70/OpenOCD/bin/wch-riscv.cfg
+ 
+../../../MRS_Toolchain_Linux_x64_V1.70/RISC-V\ Embedded\ GCC/bin/riscv-none-embed-gdb
diff --git a/examples/spi_oled/spi_oled.c b/examples/spi_oled/spi_oled.c
new file mode 100644
index 0000000000000000000000000000000000000000..f5231e8833bc00c821be9c87fb8749363ed3125e
--- /dev/null
+++ b/examples/spi_oled/spi_oled.c
@@ -0,0 +1,277 @@
+/*
+ * Example for using SPI with 128x32 graphic OLED
+ * 03-29-2023 E. Brombaugh
+ */
+
+// Could be defined here, or in the processor defines.
+#define SYSTEM_CORE_CLOCK 48000000
+#define APB_CLOCK SYSTEM_CORE_CLOCK
+
+// what type of OLED - uncomment just one
+//#define SSD1306_64X32
+//#define SSD1306_128X32
+#define SSD1306_128X64
+
+#include "ch32v003fun.h"
+#include <stdio.h>
+#include "ssd1306_spi.h"
+#include "ssd1306.h"
+
+/* White Noise Generator State */
+#define NOISE_BITS 8
+#define NOISE_MASK ((1<<NOISE_BITS)-1)
+#define NOISE_POLY_TAP0 31
+#define NOISE_POLY_TAP1 21
+#define NOISE_POLY_TAP2 1
+#define NOISE_POLY_TAP3 0
+uint32_t lfsr = 1;
+
+/*
+ * random byte generator
+ */
+uint8_t rand8(void)
+{
+	uint8_t bit;
+	uint32_t new_data;
+	
+	for(bit=0;bit<NOISE_BITS;bit++)
+	{
+		new_data = ((lfsr>>NOISE_POLY_TAP0) ^
+					(lfsr>>NOISE_POLY_TAP1) ^
+					(lfsr>>NOISE_POLY_TAP2) ^
+					(lfsr>>NOISE_POLY_TAP3));
+		lfsr = (lfsr<<1) | (new_data&1);
+	}
+	
+	return lfsr&NOISE_MASK;
+}
+
+/*
+ * return pixel value in buffer
+ */
+uint8_t getpix(uint8_t *buf, int16_t x, int16_t y)
+{
+	if((x<0) || (x>=SSD1306_W))
+		return 0;
+	if((y<0) || (y>=SSD1306_H))
+		return 0;
+	return buf[x+SSD1306_W*(y>>3)] & (1<<(y&7)) ? 1 : 0;
+}
+
+/*
+ * conway's life on B/W OLED
+ */
+void conway(uint8_t *buf)
+{
+	uint8_t col[2][(SSD1306_H>>3)];
+	int16_t x, y, B, b, sum, d, colidx = 0;
+	
+	/* iterate over columns */
+	for(x=0;x<SSD1306_W;x++)
+	{
+		/* iterate bytes in column */
+		for(B=0;B<(SSD1306_H>>3);B++)
+		{
+			/* init byte accum */
+			d = 0;
+			
+			/* iterate over bits in byte */
+			for(b=0;b<8;b++)
+			{
+				/* which row */
+				y = B*8+b;
+				
+				/* prep byte accum for this bit */
+				d >>= 1;
+				
+				/* count live neighbors */
+				sum = getpix(buf, x-1, y-1)+getpix(buf, x, y-1)+getpix(buf, x+1, y-1)+
+					getpix(buf, x-1, y)+getpix(buf, x+1, y)+
+					getpix(buf, x-1, y+1)+getpix(buf, x, y+1)+getpix(buf, x+1, y+1);
+				
+				/* check life for next cycle */
+				if(getpix(buf, x, y))
+				{
+					/* live cell */
+					if((sum==2)||(sum==3))
+						d |= 128;
+				}
+				else
+				{
+					/* dead cell */
+					if(sum == 3)
+						d |= 128;
+				}
+				
+				//printf("x=%3d, B=%1d, b=%1d, y=%2d\n\r", x, B, b, y);
+			}
+
+			col[colidx][B] = d;
+		}
+		
+		colidx ^= 1;
+		
+		if(x>0)
+		{
+			/* update previous column */
+			for(y=0;y<(SSD1306_H>>3);y++)
+				buf[(x-1)+SSD1306_W*y] = col[colidx][y];
+		}
+	}
+	
+	colidx ^= 1;
+
+	/* update final column */
+	for(y=0;y<(SSD1306_H>>3);y++)
+		buf[127+SSD1306_W*y] = col[colidx][y];
+}
+
+int count = 0;
+
+int main()
+{
+	// 48MHz internal clock
+	SystemInit48HSI();
+
+	// start serial @ default 115200bps
+#ifdef STDOUT_UART
+	SetupUART( UART_BRR );
+	Delay_Ms( 100 );
+#else
+	SetupDebugPrintf();
+#endif
+	printf("\r\r\n\nspi_oled example\n\r");
+
+	// init spi and oled
+	Delay_Ms( 100 );	// give OLED some more time
+	printf("initializing spi oled...");
+	if(!ssd1306_spi_init())
+	{
+		ssd1306_init();
+		printf("done.\n\r");
+		
+#if 0
+		printf("Looping on test modes...");
+		while(1)
+		{
+			for(uint8_t mode=0;mode<(SSD1306_H>32?8:7);mode++)
+			{
+				// clear buffer for next mode
+				ssd1306_setbuf(0);
+
+				switch(mode)
+				{
+					case 0:
+						printf("buffer fill with binary\n\r");
+						for(int i=0;i<sizeof(ssd1306_buffer);i++)
+							ssd1306_buffer[i] = i;
+						break;
+					
+					case 1:
+						printf("pixel plots\n\r");
+						for(int i=0;i<SSD1306_W;i++)
+						{
+							ssd1306_drawPixel(i, i/(SSD1306_W/SSD1306_H), 1);
+							ssd1306_drawPixel(i, SSD1306_H-1-(i/(SSD1306_W/SSD1306_H)), 1);
+						}
+						break;
+					
+					case 2:
+						{
+							printf("Line plots\n\r");
+							uint8_t y= 0;
+							for(uint8_t x=0;x<SSD1306_W;x+=16)
+							{
+								ssd1306_drawLine(x, 0, SSD1306_W, y, 1);
+								ssd1306_drawLine(SSD1306_W-x, SSD1306_H, 0, SSD1306_H-y, 1);
+								y+= SSD1306_H/8;
+							}
+						}
+						break;
+						
+					case 3:
+						printf("Circles empty and filled\n\r");
+						for(uint8_t x=0;x<SSD1306_W;x+=16)
+							if(x<64)
+								ssd1306_drawCircle(x, SSD1306_H/2, 15, 1);
+							else
+								ssd1306_fillCircle(x, SSD1306_H/2, 15, 1);
+						break;
+					
+					case 4:
+						printf("Unscaled Text\n\r");
+						ssd1306_drawstr(0,0, "This is a test", 1);
+						ssd1306_drawstr(0,8, "of the emergency", 1);
+						ssd1306_drawstr(0,16,"broadcasting", 1);
+						ssd1306_drawstr(0,24,"system.",1);
+						if(SSD1306_H>32)
+						{
+							ssd1306_drawstr(0,32, "Lorem ipsum", 1);
+							ssd1306_drawstr(0,40, "dolor sit amet,", 1);
+							ssd1306_drawstr(0,48,"consectetur", 1);
+							ssd1306_drawstr(0,56,"adipiscing",1);
+						}
+						ssd1306_xorrect(SSD1306_W/2, 0, SSD1306_W/2, SSD1306_W);
+						break;
+						
+					case 5:
+						printf("Scaled Text 1, 2\n\r");
+						ssd1306_drawstr_sz(0,0, "sz 8x8", 1, fontsize_8x8);
+						ssd1306_drawstr_sz(0,16, "16x16", 1, fontsize_16x16);
+						break;
+					
+					case 6:
+						printf("Scaled Text 4\n\r");
+						ssd1306_drawstr_sz(0,0, "32x32", 1, fontsize_32x32);
+						break;
+					
+					
+					case 7:
+						printf("Scaled Text 8\n\r");
+						ssd1306_drawstr_sz(0,0, "64", 1, fontsize_64x64);
+						break;
+
+					default:
+						break;
+				}
+				ssd1306_refresh();
+			
+				printf("count = %d\n\r", count++);
+				
+				Delay_Ms(2000);
+			}
+		}
+#else
+		printf("Looping...\n\r");
+		while(1)
+		{
+			int i;
+			
+			/* fill with random */
+			for(i=0;i<sizeof(ssd1306_buffer);i++)
+			{
+				ssd1306_buffer[i] = rand8();
+			}
+			ssd1306_refresh();
+
+			/* run conway iterations */
+			for(i=0;i<500;i++)
+			{
+				conway(ssd1306_buffer);
+				
+				/* refresh */
+				ssd1306_refresh();
+			}
+			
+			printf("count = %d\n\r", count++);
+		
+			Delay_Ms(2000);
+		}
+#endif
+	}
+	else
+		printf("failed.\n\r");
+	
+	printf("Stuck here forever...\n\r");
+	while(1);
+}
diff --git a/examples/spi_oled/ssd1306_spi.h b/examples/spi_oled/ssd1306_spi.h
new file mode 100644
index 0000000000000000000000000000000000000000..471e616b2d5aa4c0887b486901c275678b81f553
--- /dev/null
+++ b/examples/spi_oled/ssd1306_spi.h
@@ -0,0 +1,103 @@
+/*
+ * Single-File-Header for SSD1306 SPI interface
+ * 05-05-2023 E. Brombaugh
+ */
+
+#ifndef _SSD1306_SPI_H
+#define _SSD1306_SPI_H
+
+// control pins
+#define SSD1306_RST_PORT GPIOC
+#define SSD1306_RST_PIN 2
+#define SSD1306_RST_HIGH() SSD1306_RST_PORT->BSHR = (1<<(SSD1306_RST_PIN))
+#define SSD1306_RST_LOW() SSD1306_RST_PORT->BSHR = (1<<(16+SSD1306_RST_PIN))
+#define SSD1306_CS_PORT GPIOC
+#define SSD1306_CS_PIN 3
+#define SSD1306_CS_HIGH() SSD1306_CS_PORT->BSHR = (1<<(SSD1306_CS_PIN))
+#define SSD1306_CS_LOW() SSD1306_CS_PORT->BSHR = (1<<(16+SSD1306_CS_PIN))
+#define SSD1306_DC_PIN 4
+#define SSD1306_DC_PORT GPIOC
+#define SSD1306_DC_HIGH() SSD1306_DC_PORT->BSHR = (1<<(SSD1306_DC_PIN))
+#define SSD1306_DC_LOW() SSD1306_DC_PORT->BSHR = (1<<(16+SSD1306_DC_PIN))
+
+/*
+ * init SPI and GPIO for SSD1306 OLED
+ */
+uint8_t ssd1306_spi_init(void)
+{
+	// Enable GPIOC and SPI
+	RCC->APB2PCENR |= RCC_APB2Periph_GPIOC | RCC_APB2Periph_SPI1;
+	
+	// setup GPIO for reset, chip select and data/cmd
+	SSD1306_RST_PORT->CFGLR &= ~(0xf<<(4*SSD1306_RST_PIN));
+	SSD1306_RST_PORT->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*SSD1306_RST_PIN);
+	SSD1306_RST_HIGH();
+	SSD1306_CS_PORT->CFGLR &= ~(0xf<<(4*SSD1306_CS_PIN));
+	SSD1306_CS_PORT->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*SSD1306_CS_PIN);
+	SSD1306_CS_HIGH();
+	SSD1306_DC_PORT->CFGLR &= ~(0xf<<(4*SSD1306_DC_PIN));
+	SSD1306_DC_PORT->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*SSD1306_DC_PIN);
+	SSD1306_DC_LOW();
+
+	// PC5 is SCK, 10MHz Output, alt func, p-p
+	GPIOC->CFGLR &= ~(0xf<<(4*5));
+	GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP_AF)<<(4*5);
+	
+	// PC6 is MOSI, 10MHz Output, alt func, p-p
+	GPIOC->CFGLR &= ~(0xf<<(4*6));
+	GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP_AF)<<(4*6);
+	
+	// Configure SPI 
+	SPI1->CTLR1 = 
+		SPI_NSS_Soft | SPI_CPHA_1Edge | SPI_CPOL_Low | SPI_DataSize_8b |
+		SPI_Mode_Master | SPI_Direction_1Line_Tx |
+		SPI_BaudRatePrescaler_2;
+
+	// enable SPI port
+	SPI1->CTLR1 |= CTLR1_SPE_Set;
+	
+	// always succeed
+	return 0;
+}
+
+/*
+ * toggle reset line
+ */
+void ssd1306_rst(void)
+{
+	SSD1306_RST_LOW();
+	Delay_Ms(10);
+	SSD1306_RST_HIGH();
+}
+
+/*
+ * packet send for blocking polled operation via spi
+ */
+uint8_t ssd1306_pkt_send(uint8_t *data, uint8_t sz, uint8_t cmd)
+{
+	if(cmd)
+		SSD1306_DC_LOW();
+	else
+		SSD1306_DC_HIGH();
+	SSD1306_CS_LOW();
+	
+	// send data
+	while(sz--)
+	{
+		// wait for TXE
+		while(!(SPI1->STATR & SPI_STATR_TXE));
+		
+		// Send byte
+		SPI1->DATAR = *data++;
+	}
+	
+	// wait for not busy before exiting
+	while(SPI1->STATR & SPI_STATR_BSY);
+	
+	SSD1306_CS_HIGH();
+	
+	// we're happy
+	return 0;
+}
+
+#endif
diff --git a/extralibs/ch32v003_SPI.h b/extralibs/ch32v003_SPI.h
new file mode 100644
index 0000000000000000000000000000000000000000..f7263fa934003f734f5767c2254a75677780cfda
--- /dev/null
+++ b/extralibs/ch32v003_SPI.h
@@ -0,0 +1,339 @@
+//######## necessities
+
+// include guards
+#ifndef CH32V003_SPI_H
+#define CH32V003_SPI_H
+
+// includes
+#include<stdint.h>								//uintN_t support
+#include"../ch32v003fun/ch32v003fun.h"
+
+
+
+/*######## library usage and configuration
+
+in the .c files that use this library, you'll need to #define some configuration options _before_ the #include "ch32v003_SPI.h"
+
+SYSTEM_CORE_CLOCK and APB_CLOCK should be defined already as APB_CLOCK is used by this library
+
+to enable using the functions of this library:
+#define CH32V003_SPI_IMPLEMENTATION
+
+to configure the settings of the SPI bus, first, declare the desired bus speed
+
+#define CH32V003_SPI_SPEED_HZ 1000000
+
+then pick the desired setting of each group:
+
+#define CH32V003_SPI_DIRECTION_2LINE_TXRX
+#define CH32V003_SPI_DIRECTION_1LINE_TX
+
+#define CH32V003_SPI_CLK_MODE_POL0_PHA0			//leading = rising		trailing = falling		sample on leading		default if you're unsure
+#define CH32V003_SPI_CLK_MODE_POL0_PHA1			//leading = rising		trailing = falling		sample on trailing
+#define CH32V003_SPI_CLK_MODE_POL1_PHA0			//leading = falling		trailing = rising		sample on leading
+#define CH32V003_SPI_CLK_MODE_POL1_PHA1			//leading = falling		trailing = rising		sample on trailing
+
+#define CH32V003_SPI_NSS_HARDWARE_PC0			// _NSS	toggled by hardware, automatic
+#define CH32V003_SPI_NSS_HADRWARE_PC1			// NSS	toggled by hardware, automatic
+#define CH32V003_SPI_NSS_SOFTWARE_PC3			// PC3	toggled by software, automatic, manual setters available
+#define CH32V003_SPI_NSS_SOFTWARE_PC4			// PC4	toggled by software, automatic, manual setters available
+#define CH32V003_SPI_NSS_SOFTWARE_ANY_MANUAL	// toggle manually!
+*/
+
+
+
+//######## function overview (declarations): use these!
+// initialize and configure the SPI peripheral
+static inline void SPI_init();
+
+// establish / end a connection to the SPI device
+static inline void SPI_begin_8();
+static inline void SPI_begin_16();
+static inline void SPI_end();
+
+// manually set the NSS (chip select) pin high / low
+// "SPI_NSS_HIGH_FN" and "SPI_NSS_LOW_FN" only become available functions if the selected NSS is software PC3 or PC4
+#if defined(CH32V003_SPI_NSS_SOFTWARE_PC3) || defined(CH32V003_SPI_NSS_SOFTWARE_PC4)
+static inline void SPI_NSS_software_low();
+static inline void SPI_NSS_software_high();
+#endif
+
+// read / write the SPI device
+// these commands are raw, you'll have to consider all other steps in SPI_transfer!
+static inline uint8_t SPI_read_8();
+static inline uint16_t SPI_read_16();
+static inline void SPI_write_8(uint8_t data);
+static inline void SPI_write_16(uint16_t data);
+
+// send a command and get a response from the SPI device
+// you'll use this for most devices
+static inline uint8_t SPI_transfer_8(uint8_t data);
+static inline uint8_t SPI_transfer_16(uint16_t data);
+
+// SPI peripheral power enable / disable (default off, init() automatically enables)
+// send SPI peripheral to sleep
+static inline void SPI_poweroff();
+// wake SPI peripheral from sleep
+static inline void SPI_poweron();
+
+// helper: kill / restore all interrupts on the CH32V003
+static inline void kill_interrrupts();
+static inline void restore_interrupts();
+
+
+
+//######## internal function declarations
+static inline void SPI_wait_TX_complete();
+static inline uint8_t SPI_is_RX_empty();
+static inline void SPI_wait_RX_available();
+
+
+
+//######## internal variables
+static uint16_t EXT1_INTENR_backup;
+
+
+
+//######## preprocessor macros
+// min and max helper macros
+#define MIN(a,b) (((a)<(b))?(a):(b))
+#define MAX(a,b) (((a)>(b))?(a):(b))
+
+// stringify for displaying what #defines evaluated to at preprocessor stage
+#define VALUE_TO_STRING(x) #x
+#define VALUE(x) VALUE_TO_STRING(x)
+#define VAR_NAME_VALUE(var) #var "="  VALUE(var)
+
+//compile-time log2
+#define LOG2(x) ((x) == 0 ? -1 : __builtin_ctz(x))
+
+// compile-time clock prescaler calculation: log2(APB_CLOCK/SPEED_BUS)
+#define SPI_CLK_RATIO (APB_CLOCK / CH32V003_SPI_SPEED_HZ)
+#define SPI_CLK_PRESCALER LOG2(SPI_CLK_RATIO)
+
+// ensure that CLOCK_PRESCALER_VALUE is within the range of 0..7
+_Static_assert(SPI_CLK_PRESCALER >= 0 && SPI_CLK_PRESCALER <= 7, "SPI_CLK_PRESCALER is out of range (0..7). Please set a different SPI bus speed. prescaler = log2(f_CPU/f_SPI)");
+//#pragma message(VAR_NAME_VALUE(SPI_CLK_PRESCALER))
+
+
+
+//######## preprocessor #define requirements
+
+#if !defined(CH32V003_SPI_DIRECTION_2LINE_TXRX) && !defined(CH32V003_SPI_DIRECTION_1LINE_TX)
+	#warning "none of the CH32V003_SPI_DIRECTION_ options were defined!"
+#endif
+#if defined(CH32V003_SPI_DIRECTION_2LINE_TXRX) && defined(CH32V003_SPI_DIRECTION_1LINE_TX)
+	#warning "both CH32V003_SPI_DIRECTION_ options were defined!"
+#endif
+
+#if ((defined(CH32V003_SPI_CLK_MODE_POL0_PHA0) ? 1 : 0) + \
+	(defined(CH32V003_SPI_CLK_MODE_POL0_PHA1) ? 1 : 0) + \
+	(defined(CH32V003_SPI_CLK_MODE_POL1_PHA0) ? 1 : 0) + \
+	(defined(CH32V003_SPI_CLK_MODE_POL1_PHA1) ? 1 : 0)) > 1
+	#warning "more than one of the CH32V003_SPI_CLK_MODE_ options were defined!"
+#endif
+#if ((defined(CH32V003_SPI_CLK_MODE_POL0_PHA0) ? 1 : 0) + \
+	(defined(CH32V003_SPI_CLK_MODE_POL0_PHA1) ? 1 : 0) + \
+	(defined(CH32V003_SPI_CLK_MODE_POL1_PHA0) ? 1 : 0) + \
+	(defined(CH32V003_SPI_CLK_MODE_POL1_PHA1) ? 1 : 0)) == 0
+	#warning "none of the CH32V003_SPI_CLK_MODE_ options were defined!"
+#endif
+
+#if ((defined(CH32V003_SPI_NSS_HARDWARE_PC0) ? 1 : 0) + \
+	(defined(CH32V003_SPI_NSS_HARDWARE_PC1) ? 1 : 0) + \
+	(defined(CH32V003_SPI_NSS_SOFTWARE_PC3) ? 1 : 0) + \
+	(defined(CH32V003_SPI_NSS_SOFTWARE_PC4) ? 1 : 0) + \
+	(defined(CH32V003_SPI_NSS_SOFTWARE_ANY_MANUAL) ? 1 : 0)) > 1
+	#warning "more than one of the CH32V003_SPI_NSS_ options were defined!"
+#endif
+#if ((defined(CH32V003_SPI_NSS_HARDWARE_PC0) ? 1 : 0) + \
+	(defined(CH32V003_SPI_NSS_HARDWARE_PC1) ? 1 : 0) + \
+	(defined(CH32V003_SPI_NSS_SOFTWARE_PC3) ? 1 : 0) + \
+	(defined(CH32V003_SPI_NSS_SOFTWARE_PC4) ? 1 : 0) + \
+	(defined(CH32V003_SPI_NSS_SOFTWARE_ANY_MANUAL) ? 1 : 0)) == 0
+	#warning "none of the CH32V003_SPI_NSS_ options were defined!"
+#endif
+
+
+
+//########  small function definitions, static inline
+static inline void SPI_init() {
+	SPI_poweron();
+	
+	// reset control register
+	SPI1->CTLR1 = 0;
+
+	// set prescaler
+	SPI1->CTLR1 |= SPI_CTLR1_BR & (SPI_CLK_PRESCALER<<3);
+
+	// set clock polarity and phase 
+	#if defined(CH32V003_SPI_CLK_MODE_POL0_PHA0)
+		SPI1->CTLR1 |= (SPI_CPOL_Low | SPI_CPHA_1Edge);
+	#elif defined (CH32V003_SPI_CLK_MODE_POL0_PHA1)
+		SPI1->CTLR1 |= (SPI_CPOL_Low | SPI_CPHA_2Edge);
+	#elif defined (CH32V003_SPI_CLK_MODE_POL1_PHA0)
+		SPI1->CTLR1 |= (SPI_CPOL_High | SPI_CPHA_1Edge);
+	#elif defined (CH32V003_SPI_CLK_MODE_POL1_PHA1)
+		SPI1->CTLR1 |= (SPI_CPOL_High | SPI_CPHA_2Edge);
+	#endif
+	
+	// configure NSS pin, master mode
+	#if defined(CH32V003_SPI_NSS_HARDWARE_PC0)
+		// _NSS (negative slave select) on PC0, 10MHz Output, alt func, push-pull1
+		SPI1->CTLR1 |= SPI_NSS_Hard;					// NSS hardware control mode
+		GPIOC->CFGLR &= ~(0xf<<(4*0));
+		GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP_AF)<<(4*0);
+		AFIO->PCFR1 |= GPIO_Remap_SPI1;					// remap NSS (C1) to _NSS (C0)
+		SPI1->CTLR2 |= SPI_CTLR2_SSOE;					// pull _NSS high
+	#elif defined(CH32V003_SPI_NSS_HADRWARE_PC1)
+		// NSS (negative slave select) on PC1, 10MHz Output, alt func, push-pull1
+		SPI1->CTLR1 |= SPI_NSS_Hard;					// NSS hardware control mode
+		GPIOC->CFGLR &= ~(0xf<<(4*1));
+		GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP_AF)<<(4*1);
+		SPI1->CTLR2 |= SPI_CTLR2_SSOE;					// pull _NSS high
+	#elif defined(CH32V003_SPI_NSS_SOFTWARE_PC3)
+		SPI1->CTLR1 |= SPI_NSS_Soft;					// SSM NSS software control mode
+		GPIOC->CFGLR &= ~(0xf<<(4*3));
+		GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP_AF)<<(4*3);
+	#elif defined(CH32V003_SPI_NSS_SOFTWARE_PC4)
+		SPI1->CTLR1 |= SPI_NSS_Soft;					// SSM NSS software control mode
+		GPIOC->CFGLR &= ~(0xf<<(4*4));
+		GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP_AF)<<(4*4);
+	#elif defined(CH32V003_SPI_NSS_SOFTWARE_ANY_MANUAL)
+		SPI1->CTLR1 |= SPI_NSS_Soft;					// SSM NSS software control mode
+	#endif
+
+	// SCK on PC5, 10MHz Output, alt func, push-pull
+	GPIOC->CFGLR &= ~(0xf<<(4*5));
+	GPIOC->CFGLR |= (GPIO_Speed_50MHz | GPIO_CNF_OUT_PP_AF)<<(4*5);
+
+	// CH32V003 is master
+	SPI1->CTLR1 |= SPI_Mode_Master;
+	
+	// set data direction and configure data pins
+	#if defined(CH32V003_SPI_DIRECTION_2LINE_TXRX)
+		SPI1->CTLR1 |= SPI_Direction_2Lines_FullDuplex;
+
+		// MOSI on PC6, 10MHz Output, alt func, push-pull
+		GPIOC->CFGLR &= ~(0xf<<(4*6));
+		GPIOC->CFGLR |= (GPIO_Speed_50MHz | GPIO_CNF_OUT_PP_AF)<<(4*6);
+		
+		// MISO on PC7, 10MHz input, floating
+		GPIOC->CFGLR &= ~(0xf<<(4*7));
+		GPIOC->CFGLR |= GPIO_CNF_IN_FLOATING<<(4*7);
+	#elif defined(CH32V003_SPI_DIRECTION_1LINE_TX)
+		SPI1->CTLR1 |= SPI_Direction_1Line_Tx;
+
+		// MOSI on PC6, 10MHz Output, alt func, push-pull
+		GPIOC->CFGLR &= ~(0xf<<(4*6));
+		GPIOC->CFGLR |= (GPIO_Speed_50MHz | GPIO_CNF_OUT_PP_AF)<<(4*6);
+	#endif
+}
+
+static inline void SPI_begin_8() {
+	SPI1->CTLR1 |= SPI_DataSize_8b;					// DFF 16bit data-length enable, writable only when SPE is 0
+	SPI1->CTLR1 |= CTLR1_SPE_Set;
+}
+static inline void SPI_begin_16() {
+	SPI1->CTLR1 |= SPI_DataSize_16b;				// DFF 16bit data-length enable, writable only when SPE is 0
+	SPI1->CTLR1 |= CTLR1_SPE_Set;
+}
+static inline void SPI_end() {
+	SPI1->CTLR1 &= CTLR1_SPE_Reset;
+}
+
+#if defined(CH32V003_SPI_NSS_SOFTWARE_PC3)
+static inline void SPI_NSS_software_high() {
+	GPIOC->BSHR = (1<<3);
+}
+static inline void SPI_NSS_software_low() {
+	GPIOC->BSHR = (1<<(16+3));
+}
+#elif defined(CH32V003_SPI_NSS_SOFTWARE_PC4) 
+static inline void SPI_NSS_software_high() {
+	GPIOC->BSHR = (1<<4);
+}
+static inline void SPI_NSS_software_low() {
+	GPIOC->BSHR = (1<<(16+4));
+}
+#endif
+
+static inline uint8_t SPI_read_8() {
+	return SPI1->DATAR;
+}
+static inline uint16_t SPI_read_16() {
+	return SPI1->DATAR;
+}
+static inline void SPI_write_8(uint8_t data) {
+	SPI1->DATAR = data;
+}
+static inline void SPI_write_16(uint16_t data) {
+	SPI1->DATAR = data;
+}
+static inline uint8_t SPI_transfer_8(uint8_t data) {
+	#if defined(CH32V003_SPI_NSS_SOFTWARE_PC3) || defined(CH32V003_SPI_NSS_SOFTWARE_PC4) 
+		SPI_NSS_software_high();
+	#endif
+	SPI_write_8(data);
+	SPI_wait_TX_complete();
+	asm volatile("nop");
+	SPI_wait_RX_available();
+	#if defined(CH32V003_SPI_NSS_SOFTWARE_PC3) || defined(CH32V003_SPI_NSS_SOFTWARE_PC4) 
+		SPI_NSS_software_low();
+	#endif
+	return SPI_read_8();
+}
+static inline uint8_t SPI_transfer_16(uint16_t data) {
+	#if defined(CH32V003_SPI_NSS_SOFTWARE_PC3) || defined(CH32V003_SPI_NSS_SOFTWARE_PC4) 
+		SPI_NSS_software_high();
+	#endif
+	SPI_write_16(data);
+	SPI_wait_TX_complete();
+	asm volatile("nop");
+	SPI_wait_RX_available();
+	#if defined(CH32V003_SPI_NSS_SOFTWARE_PC3) || defined(CH32V003_SPI_NSS_SOFTWARE_PC4) 
+		SPI_NSS_software_low();
+	#endif
+	return SPI_read_16();
+}
+
+static inline void SPI_poweroff() {
+	SPI_end();
+	RCC->APB2PCENR &= ~RCC_APB2Periph_SPI1;
+}
+static inline void SPI_poweron() {
+	RCC->APB2PCENR |= RCC_APB2Periph_GPIOC | RCC_APB2Periph_SPI1;
+}
+
+static inline void kill_interrrupts() {
+	EXT1_INTENR_backup = EXTI->INTENR;
+	// zero the interrupt enable register to disable all interrupts
+	EXTI->INTENR = 0;
+}
+static inline void restore_interrupts() {
+	EXTI->INTENR = EXT1_INTENR_backup;
+}
+
+
+
+//########  small internal function definitions, static inline
+static inline void SPI_wait_TX_complete() {
+	while(!(SPI1->STATR & SPI_STATR_TXE)) {}
+}
+static inline uint8_t SPI_is_RX_empty() {
+	return SPI1->STATR & SPI_STATR_RXNE;
+}
+static inline void SPI_wait_RX_available() {
+	while(!(SPI1->STATR & SPI_STATR_RXNE)) {}
+}
+
+
+
+//######## implementation block
+//#define CH32V003_SPI_IMPLEMENTATION //enable so LSP can give you text colors while working on the implementation block, disable for normal use of the library
+#if defined(CH32V003_SPI_IMPLEMENTATION)
+
+//no functions here because I think all of the functions are small enough to static inline
+
+#endif // CH32V003_SPI_IMPLEMENTATION
+#endif // CH32V003_SPI_H
diff --git a/minichlink/Makefile b/minichlink/Makefile
index e90cca96cf0b361db14d32b2c54619de2e217ac6..e3f1b3809bbbcbadb4b6b83e8c44e6a7a18052d6 100644
--- a/minichlink/Makefile
+++ b/minichlink/Makefile
@@ -10,7 +10,7 @@ C_S:=minichlink.c pgm-wch-linke.c pgm-esp32s2-ch32xx.c nhc-link042.c minichgdb.c
 ifeq ($(OS),Windows_NT)
 	LDFLAGS:=-L. -lpthread -lusb-1.0 -lsetupapi -lws2_32
 	CFLAGS:=-Os -s -Wall
-	TOOLS:=minichlink.exe minichlink.dll
+	TOOLS:=minichlink.exe
 else
 	OS_NAME := $(shell uname -s | tr A-Z a-z)
 	ifeq ($(OS_NAME),linux)
@@ -38,7 +38,7 @@ minichlink.so : $(C_S)
 	gcc -o $@ $^ $(LDFLAGS) $(CFLAGS) $(INCS) -shared -fPIC
 
 minichlink.dll : $(C_S)
-	x86_64-w64-mingw32-gcc -o $@ $^ $(LDFLAGS) $(CFLAGS) $(INCS) -shared
+	x86_64-w64-mingw32-gcc -o $@ $^ $(LDFLAGS) $(CFLAGS) $(INCS) -shared -DMINICHLINK_AS_LIBRARY
 
 install_udev_rules :
 	cp 99-WCH-LinkE.rules /etc/udev/rules.d/
diff --git a/minichlink/microgdbstub.h b/minichlink/microgdbstub.h
index 7b6647b333d40ac8bb1b0e53f4af1ba9b348c8af..3d802f55bdfb690121ef300dcf0b0d0b2a465a66 100644
--- a/minichlink/microgdbstub.h
+++ b/minichlink/microgdbstub.h
@@ -69,6 +69,7 @@ int WSAAPI WSAPoll(struct pollfd * fdArray, ULONG       fds, INT         timeout
 #define MSG_NOSIGNAL 0
 #else
 #define closesocket close
+#include <arpa/inet.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <linux/in.h>
@@ -525,7 +526,7 @@ int MicroGDBPollServer( void * dev )
 			struct   sockaddr_in tin;
 			socklen_t addrlen  = sizeof(tin);
 			memset( &tin, 0, addrlen );
-			int tsocket = accept( serverSocket, (struct sockaddr *)&tin, (int*)&addrlen );
+			int tsocket = accept( serverSocket, (struct sockaddr *)&tin, (void*)&addrlen );
 			closesocket( serverSocket );
 			serverSocket = tsocket;
 			listenMode = 2;
diff --git a/minichlink/minichlink.c b/minichlink/minichlink.c
index d64b844c9295bb38c07cff46acc6bdd8368caea9..1c5c335be26b2e028f39f90a9a64bd08794a73ce 100644
--- a/minichlink/minichlink.c
+++ b/minichlink/minichlink.c
@@ -11,35 +11,55 @@
 #include "minichlink.h"
 #include "../ch32v003fun/ch32v003fun.h"
 
-static int64_t StringToMemoryAddress( const char * number );
-static void StaticUpdatePROGBUFRegs( void * dev );
-static int InternalUnlockBootloader( void * dev );
+static int64_t StringToMemoryAddress( const char * number ) __attribute__((used));
+static void StaticUpdatePROGBUFRegs( void * dev ) __attribute__((used));
+static int InternalUnlockBootloader( void * dev ) __attribute__((used));
 
 void TestFunction(void * v );
 struct MiniChlinkFunctions MCF;
 
-int main( int argc, char ** argv )
+void * MiniCHLinkInitAsDLL( struct MiniChlinkFunctions ** MCFO )
 {
 	void * dev = 0;
 	if( (dev = TryInit_WCHLinkE()) )
 	{
-		fprintf( stderr, "Found WCH LinkE\n" );
+		fprintf( stderr, "Found WCH Link\n" );
 	}
 	else if( (dev = TryInit_ESP32S2CHFUN()) )
 	{
 		fprintf( stderr, "Found ESP32S2 Programmer\n" );
 	}
-    else if ((dev = TryInit_NHCLink042()))
-    {
-        fprintf( stderr, "Found NHC-Link042 Programmer\n" );
-    }
+	else if ((dev = TryInit_NHCLink042()))
+	{
+		fprintf( stderr, "Found NHC-Link042 Programmer\n" );
+	}
 	else
 	{
 		fprintf( stderr, "Error: Could not initialize any supported programmers\n" );
-		return -32;
+		return 0;
 	}
-	
+
 	SetupAutomaticHighLevelFunctions( dev );
+	if( MCFO )
+	{
+		*MCFO = &MCF;
+	}
+	return dev;
+}
+
+#if !defined( MINICHLINK_AS_LIBRARY ) && !defined( MINICHLINK_IMPORT )
+int main( int argc, char ** argv )
+{
+	if( argc > 1 && argv[1][0] == '-' && argv[1][1] == 'h' )
+	{
+		goto help;
+	}
+	void * dev = MiniCHLinkInitAsDLL( 0 );
+	if( !dev )
+	{
+		fprintf( stderr, "Error: Could not initialize any supported programmers\n" );
+		return -32;
+	}
 
 	int status;
 	int must_be_end = 0;
@@ -175,6 +195,13 @@ keep_going:
 				{
 					printf( "GDBServer Running\n" );
 				}
+				else
+				{
+					// In case we aren't running already.
+					MCF.HaltMode( dev, 2 );
+
+					//XXX TODO: Why do some programmers start automatically, and others don't? 
+				}
 
 				do
 				{
@@ -345,6 +372,8 @@ keep_going:
 					goto unimplemented;
 				}
 
+				printf( "Read %d bytes\n", (int)amount );
+
 				if( hex )
 				{
 					int i;
@@ -516,7 +545,7 @@ unimplemented:
 	fprintf( stderr, "Error: Command '%s' unimplemented on this programmer.\n", lastcommand );
 	return -1;
 }
-
+#endif
 
 #if defined(WINDOWS) || defined(WIN32) || defined(_WIN32)
 #define strtoll _strtoi64
@@ -601,7 +630,24 @@ static int DefaultWaitForDoneOp( void * dev, int ignore )
 	while( rrv & (1<<12) );
 	if( (rrv >> 8 ) & 7 )
 	{
-		if( !ignore ) fprintf( stderr, "Fault writing memory (DMABSTRACTS = %08x)\n", rrv );
+		if( !ignore )
+		{
+			const char * errortext = 0;
+			switch( (rrv>>8)&7 )
+			{
+			case 1: errortext = "Command in execution"; break;
+			case 2: errortext = "Abstract Command Unsupported"; break;
+			case 3: errortext = "Execption executing Abstract Command"; break;
+			case 4: errortext = "Processor not halted."; break;
+			case 5: errortext = "Bus Error"; break;
+			case 6: errortext = "Parity Bit"; break;
+			default: errortext = "Other Error"; break;
+			}
+
+			uint32_t temp;
+			MCF.ReadReg32( dev, DMSTATUS, &temp );
+			fprintf( stderr, "Fault writing memory (DMABSTRACTS = %08x) (%s) DMSTATUS: %08x\n", rrv, errortext, temp );
+		}
 		MCF.WriteReg32( dev, DMABSTRACTCS, 0x00000700 );
 		return -9;
 	}
@@ -642,14 +688,21 @@ int DefaultSetupInterface( void * dev )
 
 static void StaticUpdatePROGBUFRegs( void * dev )
 {
-	MCF.WriteReg32( dev, DMDATA0, 0xe00000f4 );   // DATA0's location in memory.
-	MCF.WriteReg32( dev, DMCOMMAND, 0x0023100a ); // Copy data to x10
-	MCF.WriteReg32( dev, DMDATA0, 0xe00000f8 );   // DATA1's location in memory.
-	MCF.WriteReg32( dev, DMCOMMAND, 0x0023100b ); // Copy data to x11
-	MCF.WriteReg32( dev, DMDATA0, 0x40022010 ); //FLASH->CTLR
-	MCF.WriteReg32( dev, DMCOMMAND, 0x0023100c ); // Copy data to x12
+	uint32_t rr;
+	if( MCF.ReadReg32( dev, DMHARTINFO, &rr ) )
+	{
+		fprintf( stderr, "Error: Could not get hart info.\n" );
+		return;
+	}
+	uint32_t data0offset = 0xe0000000 | ( rr & 0x7ff );
+	MCF.WriteReg32( dev, DMDATA0, data0offset );       // DATA0's location in memory.
+	MCF.WriteReg32( dev, DMCOMMAND, 0x0023100a );      // Copy data to x10
+	MCF.WriteReg32( dev, DMDATA0, data0offset + 4 );   // DATA1's location in memory.
+	MCF.WriteReg32( dev, DMCOMMAND, 0x0023100b );      // Copy data to x11
+	MCF.WriteReg32( dev, DMDATA0, 0x40022010 );        // FLASH->CTLR
+	MCF.WriteReg32( dev, DMCOMMAND, 0x0023100c );      // Copy data to x12
 	MCF.WriteReg32( dev, DMDATA0, CR_PAGE_PG|CR_BUF_LOAD);
-	MCF.WriteReg32( dev, DMCOMMAND, 0x0023100d ); // Copy data to x13
+	MCF.WriteReg32( dev, DMCOMMAND, 0x0023100d );      // Copy data to x13
 }
 
 static int InternalUnlockBootloader( void * dev )
@@ -700,7 +753,7 @@ static int DefaultWriteHalfWord( void * dev, uint32_t address_to_write, uint16_t
 	ret |= MCF.WaitForDoneOp( dev, 0 );
 	iss->currentstateval = -1;
 
-
+	if( ret ) fprintf( stderr, "Fault on DefaultWriteHalfWord\n" );
 	return ret;
 }
 
@@ -726,6 +779,8 @@ static int DefaultReadHalfWord( void * dev, uint32_t address_to_write, uint16_t
 	ret |= MCF.WaitForDoneOp( dev, 0 );
 	iss->currentstateval = -1;
 
+	if( ret ) fprintf( stderr, "Fault on DefaultReadHalfWord\n" );
+
 	uint32_t rr;
 	ret |= MCF.ReadReg32( dev, DMDATA0, &rr );
 	*data = rr;
@@ -754,6 +809,7 @@ static int DefaultWriteByte( void * dev, uint32_t address_to_write, uint8_t data
 	MCF.WriteReg32( dev, DMCOMMAND, 0x00271008 ); // Copy data to x8, and execute program.
 
 	ret |= MCF.WaitForDoneOp( dev, 0 );
+	if( ret ) fprintf( stderr, "Fault on DefaultWriteByte\n" );
 	iss->currentstateval = -1;
 	return ret;
 }
@@ -778,6 +834,7 @@ static int DefaultReadByte( void * dev, uint32_t address_to_write, uint8_t * dat
 	MCF.WriteReg32( dev, DMCOMMAND, 0x00221008 ); // Read x8 into DATA0.
 
 	ret |= MCF.WaitForDoneOp( dev, 0 );
+	if( ret ) fprintf( stderr, "Fault on DefaultReadByte\n" );
 	iss->currentstateval = -1;
 
 	uint32_t rr;
@@ -850,7 +907,10 @@ static int DefaultWriteWord( void * dev, uint32_t address_to_write, uint32_t dat
 		iss->currentstateval = address_to_write;
 
 		if( is_flash )
+		{
 			ret |= MCF.WaitForDoneOp( dev, 0 );
+			if( ret ) fprintf( stderr, "Fault on DefaultWriteWord Part 1\n" );
+		}
 	}
 	else
 	{
@@ -866,10 +926,12 @@ static int DefaultWriteWord( void * dev, uint32_t address_to_write, uint32_t dat
 			// XXX TODO: This likely can be a very short delay.
 			// XXX POSSIBLE OPTIMIZATION REINVESTIGATE.
 			ret |= MCF.WaitForDoneOp( dev, 0 );
+			if( ret ) fprintf( stderr, "Fault on DefaultWriteWord Part 2\n" );
 		}
 		else
 		{
 			ret |= MCF.WaitForDoneOp( dev, 0 );
+			if( ret ) fprintf( stderr, "Fault on DefaultWriteWord Part 3\n" );
 		}
 	}
 
@@ -1102,12 +1164,14 @@ static int DefaultReadWord( void * dev, uint32_t address_to_read, uint32_t * dat
 		iss->currentstateval = address_to_read;
 
 		r |= MCF.WaitForDoneOp( dev, 0 );
+		if( r ) fprintf( stderr, "Fault on DefaultReadWord Part 1\n" );
 	}
 
 	if( iss->autoincrement )
 		iss->currentstateval += 4;
 
 	r |= MCF.ReadReg32( dev, DMDATA0, data );
+
 	if( iss->currentstateval == iss->ram_base + iss->ram_size )
 		MCF.WaitForDoneOp( dev, 1 ); // Ignore any post-errors. 
 	return r;
@@ -1147,6 +1211,7 @@ int DefaultErase( void * dev, uint32_t address, uint32_t length, int type )
 	{
 		if( ( rw = StaticUnlockFlash( dev, iss ) ) )
 			return rw;
+		printf( "Flash unlocked\n" );
 	}
 
 	if( type == 1 )
@@ -1157,8 +1222,12 @@ int DefaultErase( void * dev, uint32_t address, uint32_t length, int type )
 		MCF.WriteWord( dev, (intptr_t)&FLASH->CTLR, 0 );
 		MCF.WriteWord( dev, (intptr_t)&FLASH->CTLR, FLASH_CTLR_MER  );
 		MCF.WriteWord( dev, (intptr_t)&FLASH->CTLR, CR_STRT_Set|FLASH_CTLR_MER );
-		if( MCF.WaitForFlash && MCF.WaitForFlash( dev ) ) return -11;		
+		rw = MCF.WaitForDoneOp( dev, 0 );
+		if( MCF.WaitForFlash && MCF.WaitForFlash( dev ) ) { fprintf( stderr, "Error: Wait for flash error.\n" ); return -11; }
+		rw = MCF.WaitForDoneOp( dev, 0 );
 		MCF.WriteWord( dev, (intptr_t)&FLASH->CTLR, 0 );
+		rw = MCF.WaitForDoneOp( dev, 0 );
+		fprintf( stderr, "Whole Chip Erase Code: %d\n", rw );
 	}
 	else
 	{
@@ -1197,8 +1266,11 @@ int DefaultReadBinaryBlob( void * dev, uint32_t address_to_read_from, uint32_t r
 		{
 			uint32_t rw;
 			r = MCF.ReadWord( dev, rpos, &rw );
+			//printf( "RW: %d %08x %08x\n", r, rpos, rw );
 			if( r ) return r;
-			memcpy( blob, &rw, remain );
+			int rem = remain;
+			if( rem > 4 ) rem = 4;
+			memcpy( blob, &rw, rem);
 			blob += 4;
 			rpos += 4;
 		}
@@ -1236,8 +1308,9 @@ int DefaultReadBinaryBlob( void * dev, uint32_t address_to_read_from, uint32_t r
 			}
 		}
 	}
-	MCF.WaitForDoneOp( dev, 0 );
-	return 0;
+	int r = MCF.WaitForDoneOp( dev, 0 );
+	if( r ) fprintf( stderr, "Fault on DefaultReadBinaryBlob\n" );
+	return r;
 }
 
 int DefaultReadCPURegister( void * dev, uint32_t regno, uint32_t * regret )
@@ -1346,9 +1419,14 @@ static int DefaultHaltMode( void * dev, int mode )
 	switch ( mode )
 	{
 	case 0:
+		MCF.WriteReg32( dev, DMSHDWCFGR, 0x5aa50000 | (1<<10) ); // Shadow Config Reg
+		MCF.WriteReg32( dev, DMCFGR, 0x5aa50000 | (1<<10) ); // CFGR (1<<10 == Allow output from slave)
+		MCF.WriteReg32( dev, DMCFGR, 0x5aa50000 | (1<<10) ); // Bug in silicon?  If coming out of cold boot, and we don't do our little "song and dance" this has to be called.
+
 		MCF.WriteReg32( dev, DMCONTROL, 0x80000001 ); // Make the debug module work properly.
 		MCF.WriteReg32( dev, DMCONTROL, 0x80000001 ); // Initiate a halt request.
-		MCF.WriteReg32( dev, DMCONTROL, 0x00000001 ); // Clear Halt Request.
+//		MCF.WriteReg32( dev, DMCONTROL, 0x00000001 ); // Clear Halt Request.  This is recommended, but not doing it seems more stable.
+		// Sometimes, even if the processor is halted but the MSB is clear, it will spuriously start?
 		MCF.FlushLLCommands( dev );
 		break;
 	case 1:
@@ -1359,6 +1437,10 @@ static int DefaultHaltMode( void * dev, int mode )
 		MCF.FlushLLCommands( dev );
 		break;
 	case 2:
+		MCF.WriteReg32( dev, DMSHDWCFGR, 0x5aa50000 | (1<<10) ); // Shadow Config Reg
+		MCF.WriteReg32( dev, DMCFGR, 0x5aa50000 | (1<<10) ); // CFGR (1<<10 == Allow output from slave)
+		MCF.WriteReg32( dev, DMCFGR, 0x5aa50000 | (1<<10) ); // Bug in silicon?  If coming out of cold boot, and we don't do our little "song and dance" this has to be called.
+
 		MCF.WriteReg32( dev, DMCONTROL, 0x40000001 ); // resumereq
 		MCF.FlushLLCommands( dev );
 		break;
@@ -1378,6 +1460,17 @@ static int DefaultHaltMode( void * dev, int mode )
 		MCF.FlushLLCommands( dev );
 		break;
 	}
+#if 0
+	int i;
+	for( i = 0; i < 100; i++ )
+	{
+		uint32_t temp = 0;
+		MCF.ReadReg32( dev, DMSTATUS, &temp );
+		fprintf( stderr, "DMSTATUS: %08x\n", temp );
+		usleep( 20000);
+	}
+#endif
+
 	iss->processor_in_mode = mode;
 	return 0;
 }
diff --git a/minichlink/minichlink.exe b/minichlink/minichlink.exe
index eda0a5a992c0971a374653f4e5e505ddabd6859f..706e73954c033747773fed2780e10ef8bc99b40d 100644
Binary files a/minichlink/minichlink.exe and b/minichlink/minichlink.exe differ
diff --git a/minichlink/minichlink.h b/minichlink/minichlink.h
index 4bbd18aa65b91d659d4e633e72a3fd296acdc293..0462984284cef827728f2e2631ce0745fef5280e 100644
--- a/minichlink/minichlink.h
+++ b/minichlink/minichlink.h
@@ -130,8 +130,22 @@ struct InternalState
 #define DMCFGR       0x7D
 #define DMSHDWCFGR   0x7E
 
+#if defined( WIN32 ) || defined( _WIN32 )
+#if defined( MINICHLINK_AS_LIBRARY )
+	#define DLLDECORATE __declspec(dllexport)
+#elif defined( MINICHLINK_IMPORT )
+	#define DLLDECORATE __declspec(dllimport)
+#else
+	#define DLLDECORATE
+#endif
+#else
+	#define DLLDECORATE
+#endif
+
+void * MiniCHLinkInitAsDLL(struct MiniChlinkFunctions ** MCFO) DLLDECORATE;
 extern struct MiniChlinkFunctions MCF;
 
+
 // Returns 'dev' on success, else 0.
 void * TryInit_WCHLinkE();
 void * TryInit_ESP32S2CHFUN();
diff --git a/minichlink/pgm-esp32s2-ch32xx.c b/minichlink/pgm-esp32s2-ch32xx.c
index bfb6ea1d87f2856b5e1ab9f96e7700852b278f26..11d0048daee958797d8e837009331b330fe63365 100644
--- a/minichlink/pgm-esp32s2-ch32xx.c
+++ b/minichlink/pgm-esp32s2-ch32xx.c
@@ -236,6 +236,10 @@ int ESPBlockWrite64( void * dev, uint32_t address_to_write, uint8_t * data )
 	{
 		ESPFlushLLCommands( dev );
 	} while( eps->replylen < 2 );
+	
+	// Not sure why this is needed.
+	ESPWaitForDoneOp( dev, 0 );
+
 	return eps->reply[1];
 }
 
@@ -357,7 +361,6 @@ int ESPPollTerminal( void * dev, uint8_t * buffer, int maxlen, uint32_t leavefla
 
 	memcpy( buffer, eps->reply + 2, rlen - 1 );
 
-
 	return rlen - 1;
 }
 
diff --git a/minichlink/pgm-wch-linke.c b/minichlink/pgm-wch-linke.c
index 832007df9bff8603f5359431a9c477198360a498..ca91ed2890de47cdf6ca8ce4acd30de39f9599a7 100644
--- a/minichlink/pgm-wch-linke.c
+++ b/minichlink/pgm-wch-linke.c
@@ -13,9 +13,16 @@ struct LinkEProgrammerStruct
 {
 	void * internal;
 	libusb_device_handle * devh;
-	int lasthaltmode;
+	int lasthaltmode; // For non-003 chips
 };
 
+// For non-ch32v003 chips.
+#if 0
+static int LEReadBinaryBlob( void * d, uint32_t offset, uint32_t amount, uint8_t * readbuff );
+static int InternalLinkEHaltMode( void * d, int mode );
+static int LEWriteBinaryBlob( void * d, uint32_t address_to_write, uint32_t len, uint8_t * blob );
+#endif
+
 #define WCHTIMEOUT 5000
 #define WCHCHECK(x) if( (status = x) ) { fprintf( stderr, "Bad USB Operation on " __FILE__ ":%d (%d)\n", __LINE__, status ); exit( status ); }
 
@@ -34,8 +41,13 @@ void wch_link_command( libusb_device_handle * devh, const void * command_v, int
 	{
 		reply = buffer; replymax = sizeof( buffer );
 	}
-	
+
+//	printf("wch_link_command send (%d)", commandlen); for(int i = 0; i< commandlen; printf(" %02x",command[i++])); printf("\n");
+
 	status = libusb_bulk_transfer( devh, 0x81, reply, replymax, transferred, WCHTIMEOUT );
+
+//	printf("wch_link_command reply (%d)", *transferred); for(int i = 0; i< *transferred; printf(" %02x",reply[i++])); printf("\n"); 
+
 	if( status ) goto sendfail;
 	return;
 sendfail:
@@ -120,7 +132,20 @@ int LEWriteReg32( void * dev, uint8_t reg_7_bit, uint32_t command )
 			(command >> 0) & 0xff,
 			iOP };
 
-	wch_link_command( devh, req, sizeof(req), 0, 0, 0 );
+	uint8_t resp[128];
+	int resplen;
+	wch_link_command( devh, req, sizeof(req), &resplen, resp, sizeof(resp) );
+	if( resplen != 9 || resp[3] != reg_7_bit )
+	{
+		fprintf( stderr, "Error setting write reg. Tell cnlohr. Maybe we should allow retries here?\n" );
+		fprintf( stderr, "RR: %d :", resplen );
+		int i;
+		for( i = 0; i < resplen; i++ )
+		{
+			fprintf( stderr, "%02x ", resp[i] );
+		}
+		fprintf( stderr, "\n" );
+	}
 	return 0;
 }
 
@@ -136,6 +161,27 @@ int LEReadReg32( void * dev, uint8_t reg_7_bit, uint32_t * commandresp )
 			iOP };
 	wch_link_command( devh, req, sizeof( req ), (int*)&transferred, rbuff, sizeof( rbuff ) );
 	*commandresp = ( rbuff[4]<<24 ) | (rbuff[5]<<16) | (rbuff[6]<<8) | (rbuff[7]<<0);
+	if( transferred != 9 || rbuff[3] != reg_7_bit )
+	{
+		fprintf( stderr, "Error setting write reg. Tell cnlohr. Maybe we should allow retries here?\n" );
+		fprintf( stderr, "RR: %d :", transferred );
+		int i;
+		for( i = 0; i < transferred; i++ )
+		{
+			fprintf( stderr, "%02x ", rbuff[i] );
+		}
+		fprintf( stderr, "\n" );
+	}
+	/*
+	printf( "RR: %d :", transferred );
+	int i;
+	for( i = 0; i < transferred; i++ )
+	{
+		printf( "%02x ", rbuff[i] );
+	}
+	printf( "\n" );
+	*/
+
 	return 0;
 }
 
@@ -150,23 +196,50 @@ static int LESetupInterface( void * d )
 	uint8_t rbuff[1024];
 	uint32_t transferred = 0;
 
+	// This puts the processor on hold to allow the debugger to run.
+	wch_link_command( dev, "\x81\x0d\x01\x03", 4, (int*)&transferred, rbuff, 1024 ); // Reply: Ignored, 820d050900300500
+
 	// Place part into reset.
 	wch_link_command( dev, "\x81\x0d\x01\x01", 4, (int*)&transferred, rbuff, 1024 );	// Reply is: "\x82\x0d\x04\x02\x08\x02\x00"
+	switch(rbuff[5]) {
+		case 1:
+			fprintf(stderr, "WCH Programmer is CH549 version %d.%d\n",rbuff[3], rbuff[4]);
+			break;
+		case 2:
+			fprintf(stderr, "WCH Programmer is CH32V307 version %d.%d\n",rbuff[3], rbuff[4]);
+			break;
+		case 3:
+			fprintf(stderr, "WCH Programmer is CH32V203 version %d.%d\n",rbuff[3], rbuff[4]);
+			break;
+		case 4:
+			fprintf(stderr, "WCH Programmer is LinkB version %d.%d\n",rbuff[3], rbuff[4]);
+			break;
+		case 18:
+			fprintf(stderr, "WCH Programmer is LinkE version %d.%d\n",rbuff[3], rbuff[4]);
+			break;
+		default:
+			fprintf(stderr, "Unknown WCH Programmer %02x (Ver %d.%d)\n", rbuff[5], rbuff[3], rbuff[4]);
+			break;
+	}
 
 	// TODO: What in the world is this?  It doesn't appear to be needed.
 	wch_link_command( dev, "\x81\x0c\x02\x09\x01", 5, 0, 0, 0 ); //Reply is: 820c0101
 
 	// This puts the processor on hold to allow the debugger to run.
-	wch_link_command( dev, "\x81\x0d\x01\x02", 4, 0, 0, 0 ); // Reply: Ignored, 820d050900300500
+	wch_link_command( dev, "\x81\x0d\x01\x02", 4, (int*)&transferred, rbuff, 1024 ); // Reply: Ignored, 820d050900300500
+	if (rbuff[0] == 0x81 && rbuff[1] == 0x55 && rbuff[2] == 0x01 && rbuff[3] == 0x01)
+	{
+		fprintf(stderr, "link error, nothing connected to linker\n");
+		return -1;
+	}
 
 	// For some reason, if we don't do this sometimes the programmer starts in a hosey mode.
 	MCF.WriteReg32( d, DMCONTROL, 0x80000001 ); // Make the debug module work properly.
 	MCF.WriteReg32( d, DMCONTROL, 0x80000001 ); // Initiate a halt request.
 	MCF.WriteReg32( d, DMCONTROL, 0x80000001 ); // No, really make sure.
 	MCF.WriteReg32( d, DMABSTRACTCS, 0x00000700 ); // Ignore any pending errors.
-	MCF.WriteReg32( d, DMPROGBUF0, 0x00100073 );
 	MCF.WriteReg32( d, DMABSTRACTAUTO, 0 );
-	MCF.WriteReg32( d, DMCOMMAND, 0x00261000 ); // Read x0
+	MCF.WriteReg32( d, DMCOMMAND, 0x00261000 ); // Read x0 (Null command)
 
 	// This puts the processor on hold to allow the debugger to run.
 	wch_link_command( dev, "\x81\x11\x01\x09", 4, (int*)&transferred, rbuff, 1024 ); // Reply: Chip ID + Other data (see below)
@@ -180,6 +253,17 @@ static int LESetupInterface( void * d )
 	fprintf( stderr, "PFlags       : %02x-%02x-%02x-%02x\n", rbuff[12], rbuff[13], rbuff[14], rbuff[15] );
 	fprintf( stderr, "Part Type (B): %02x-%02x-%02x-%02x\n", rbuff[16], rbuff[17], rbuff[18], rbuff[19] );
 	
+	if( rbuff[2] == 0x05 && rbuff[3] == 0x06 )
+	{
+//		fprintf( stderr, "CH32V307 Detected.  Allowing old-flash-mode for operation.\n" );
+//		MCF.WriteBinaryBlob = LEWriteBinaryBlob;
+//		MCF.ReadBinaryBlob = LEReadBinaryBlob;
+	}
+	else
+	{
+		// No need to use these and the propreitary blob.
+	}
+	
 	return 0;
 }
 
@@ -244,7 +328,6 @@ void * TryInit_WCHLinkE()
 	wch_linke_devh = wch_link_base_setup(0);
 	if( !wch_linke_devh ) return 0;
 
-
 	struct LinkEProgrammerStruct * ret = malloc( sizeof( struct LinkEProgrammerStruct ) );
 	memset( ret, 0, sizeof( *ret ) );
 	ret->devh = wch_linke_devh;
@@ -259,23 +342,16 @@ void * TryInit_WCHLinkE()
 	MCF.Control5v = LEControl5v;
 	MCF.Unbrick = LEUnbrick;
 	MCF.ConfigureNRSTAsGPIO = LEConfigureNRSTAsGPIO;
-	//MCF.WriteBinaryBlob = LEWriteBinaryBlob;
-	//MCF.ReadBinaryBlob = LEReadBinaryBlob;
+
 	MCF.Exit = LEExit;
 	return ret;
 };
 
 
-
-
-
-
-
-
-// Graveyard.
-
 #if 0
 
+// In case you are using a non-CH32V003 board.
+
 
 const uint8_t * bootloader = (const uint8_t*)
 "\x21\x11\x22\xca\x26\xc8\x93\x77\x15\x00\x99\xcf\xb7\x06\x67\x45" \
@@ -461,4 +537,6 @@ static int LEWriteBinaryBlob( void * d, uint32_t address_to_write, uint32_t len,
 	}
 	return 0;
 }
+
+
 #endif
diff --git a/misc/LIBGCC_LICENSE b/misc/LIBGCC_LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..293b2efc5964c0b6c7906a9997686e55c952b341
--- /dev/null
+++ b/misc/LIBGCC_LICENSE
@@ -0,0 +1,773 @@
+The software in the directory newlib and the files configure.ac, Makefile.in,
+and patches/newlib, is licensed as follows:
+
+Copyright (c) 2016, The Regents of the University of California (Regents).
+All Rights Reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+3. Neither the name of the Regents nor the
+   names of its contributors may be used to endorse or promote products
+   derived from this software without specific prior written permission.
+
+IN NO EVENT SHALL REGENTS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
+SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING
+OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF REGENTS HAS
+BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE SOFTWARE AND ACCOMPANYING DOCUMENTATION, IF ANY, PROVIDED
+HEREUNDER IS PROVIDED "AS IS". REGENTS HAS NO OBLIGATION TO PROVIDE
+MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+
+The software in the directories binutils, gcc, and linux-headers, and the
+files patches/binutils and patches/gcc, is licensed as follows:
+
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+     51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+The software in the directory glibc and the file patches/glibc is licensed as
+follows:
+
+                  GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+                  GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+                            NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
diff --git a/misc/README.md b/misc/README.md
index db7028fecc36eef059a6a3687743960d2ca28cb2..4c5bcc6104c89a55e9ca3b92dc49e2271500b273 100644
--- a/misc/README.md
+++ b/misc/README.md
@@ -1,6 +1,6 @@
 # Supplementary non-official material
 
-This is a copy of libgcc.a that I know works well with the part.  If you have a better one, please share!
+This is a copy of libgcc.a that I know works well with the part. Below are the instructions on how to get the source code and how to build it.
 
 # Extra totally non-official info
 
@@ -10,3 +10,29 @@ At 3.3v, it seems that for all speed configurations, the short drive current is
 
 At 5v the peak current power application is 90-100mA for emitter and collector.
 
+## Building libgcc.a from source.
+
+1. Install prerequisites (for Debian-based systems; similar steps for other systems):
+
+```
+sudo apt-get install build-essential autoconf automake autotools-dev curl \
+libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo \
+gperf libtool patchutils bc zlib1g-dev git
+```
+
+2. Checkout sources:
+
+```
+git clone --recursive https://github.com/riscv/riscv-gnu-toolchain.git
+cd riscv-gnu-toolchain
+```
+
+3. Configure and build:
+
+```
+./configure --prefix $(pwd)/build-ch32v003 --with-arch=rv32ec --with-abi=ilp32e
+make -j8
+```
+
+4. Enjoy the built libgcc.a at ./build-ch32v003/lib/gcc/riscv32-unknown-elf/12.2.0/libgcc.a
+
diff --git a/misc/libgcc.a b/misc/libgcc.a
index 7e3b7303b0bfcec86e4beccab77ede2ca3e753f6..117e412d1e5323edea1dfee86b2108459c743fe7 100644
Binary files a/misc/libgcc.a and b/misc/libgcc.a differ