diff --git a/pwm/Makefile b/pwm/Makefile new file mode 100644 index 0000000..8941f82 --- /dev/null +++ b/pwm/Makefile @@ -0,0 +1,53 @@ +# ch32v003 +TARGET?= ch32v003 +TOOL ?= gcc +#TOOL ?= clang + + +PRJ = example + +VPATH = . ./$(TARGET) +BLD = ./build/ +DFLAGS = -d +LFLAGS = -g +LDLIBS = +BFLAGS = --strip-unneeded + +CFLAGS = -MMD -Wall -ggdb -fno-exceptions -ffunction-sections -fdata-sections +CFLAGS+= -I. -I./$(TARGET) -I/usr/include/newlib +DEL = rm -f + +# zdrojaky +OBJS = main.o pwmclass.o + +include $(TARGET)/$(TOOL).mk +BOBJS = $(addprefix $(BLD),$(OBJS)) + +all: $(BLD) $(PRJ).elf +# ... atd. +-include $(BLD)*.d +# linker +$(PRJ).elf: $(BOBJS) + -@echo [LD $(TOOL),$(TARGET)] $@ + @$(LD) $(LFLAGS) -o $(PRJ).elf $(BOBJS) $(LDLIBS) + -@echo "size:" + @$(SIZE) $(PRJ).elf + -@echo "listing:" + $(DUMP) $(DFLAGS) $(PRJ).elf > $(PRJ).lst + -@echo "OK." + $(COPY) $(BFLAGS) -O binary $(PRJ).elf $(PRJ).bin +# preloz co je potreba +$(BLD)%.o: %.c + -@echo [CC $(TOOL),$(TARGET)] $@ + @$(CC) -c $(CFLAGS) $< -o $@ +$(BLD)%.o: %.cpp + -@echo [CX $(TOOL),$(TARGET)] $@ + @$(CXX) -std=c++17 -fno-rtti -c $(CFLAGS) $< -o $@ +$(BLD): + mkdir $(BLD) +flash: $(PRJ).elf + minichlink -w $(PRJ).bin flash -b +# vycisti +clean: + $(DEL) $(BLD)* *.lst *.bin *.elf *.map *~ +.PHONY: all clean diff --git a/pwm/ch32v003 b/pwm/ch32v003 new file mode 120000 index 0000000..0bcf9a1 --- /dev/null +++ b/pwm/ch32v003 @@ -0,0 +1 @@ +../ch32v003/ \ No newline at end of file diff --git a/pwm/dma_gpio.c b/pwm/dma_gpio.c new file mode 100644 index 0000000..07fcd23 --- /dev/null +++ b/pwm/dma_gpio.c @@ -0,0 +1,150 @@ +// 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. +// + +#include "ch32v003fun.h" +#include + +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; + + SystemInit(); + + // Reset all the peripherals we care about. + RCC->APB2PRSTR = 0xffffffff; + RCC->APB2PRSTR = 0; + + // 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 (48MHz / (11+1) = 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( "%lu\n", GPIOD->OUTDR ); + Delay_Ms( 250 ); + GPIOD->BSHR = (1<<16) | (1<<(16+4)); // Turn off GPIODs + printf( "%lu\n", GPIOD->OUTDR ); + Delay_Ms( 250 ); + printf( "Step\n" ); + } +} + diff --git a/pwm/main.cpp b/pwm/main.cpp new file mode 100644 index 0000000..2893b65 --- /dev/null +++ b/pwm/main.cpp @@ -0,0 +1,7 @@ +#include "pwmclass.h" +////////////////////////////////////// +static PwmClass pwm; +int main () { + for (;;); + return 0; +} diff --git a/pwm/oneway.h b/pwm/oneway.h new file mode 100644 index 0000000..7dbb5b2 --- /dev/null +++ b/pwm/oneway.h @@ -0,0 +1,10 @@ +#ifndef ONEWAY_H +#define ONEWAY_H +#include + +class OneWay { + public: + virtual unsigned Send (uint16_t * const ptr, const unsigned len) = 0; +}; + +#endif // ONEWAY_H diff --git a/pwm/pwmclass.cpp b/pwm/pwmclass.cpp new file mode 100644 index 0000000..43b510f --- /dev/null +++ b/pwm/pwmclass.cpp @@ -0,0 +1,102 @@ +#include "system.h" +#include "pwmclass.h" +static PwmClass * pInstance = nullptr; +extern "C" void DMA1_Channel2_IRQHandler( void ) __attribute__((interrupt)); +void DMA1_Channel2_IRQHandler( void ) { + DMA1_Type::INTFR_DEF state (DMA1.INTFR); + DMA1.INTFCR.R = state.R; // clear all + if (!pInstance) return; + if (state.B.HTIF2) pInstance->send(false); + if (state.B.TCIF2) pInstance->send(true); +} + +/* + * initialize TIM1 for PWM + */ +static inline void t1pwm_init () noexcept { + // Enable GPIOC, GPIOD and TIM1 + RCC.APB2PCENR.modify([] (RCC_Type::APB2PCENR_DEF & r) -> auto { + r.B.IOPCEN = SET; + r.B.IOPDEN = SET; + r.B.TIM1EN = SET; + return r.R; + }); + // PD0 is T1CH1N, PD2 is T1CH1, 10MHz Output alt func, push-pull + GPIOD.CFGLR.modify([](GPIOA_Type::CFGLR_DEF & r) -> auto { + r.B.CNF0 = 2u; + r.B.MODE0 = 1u; + r.B.CNF2 = 2u; + r.B.MODE2 = 1u; + return r.R; + }); + // Reset TIM1 to init all regs + RCC.APB2PRSTR.B.TIM1RST = SET; + RCC.APB2PRSTR.B.TIM1RST = RESET; + // CTLR1: default is up, events generated, edge align + // SMCFGR: default clk input is CK_INT + // Prescaler + TIM1.PSC.R = 24000u; + // Auto Reload - sets period + TIM1.ATRLR.R = 255u; + + TIM1.CCER.modify([](TIM1_Type::CCER_DEF & r) -> auto { + // Enable CH1N output, positive pol + r.B.CC1NE = SET; + r.B.CC1E = SET; + /* + r.B.CC1NP = SET; // active Low + r.B.CC1P = SET; + */ + return r.R; + }); + // CH1 Mode is output, PWM1 (CC1S = 00, OC1M = 110) + TIM1.CHCTLR1_Output.modify([](TIM1_Type::CHCTLR1_Output_DEF & r) -> auto { + r.B.OC1M = 0x6u; + return r.R; + }); + // Enable TIM1 outputs + TIM1.BDTR.modify([](TIM1_Type::BDTR_DEF & r) -> auto { + r.B.MOE = SET; + r.B.DTG = 48u; // Dead time 1us + return r.R; + }); + + // Reload immediately + Trigger DMA + TIM1.SWEVGR.B.UG = SET; + TIM1.DMAINTENR.B.UDE = SET; + // Enable TIM1 + TIM1.CTLR1.B.CEN = SET; +} +static void dma1ch5_init (void * ptr) { + // Enable DMA + RCC.AHBPCENR.modify([](RCC_Type::AHBPCENR_DEF & r) -> auto { + r.B.SRAMEN = SET; + r.B.DMA1EN = SET; + return r.R; + }); + // DMA5 can be configured to attach to T1UP + // The system can only DMA out at ~2.2MSPS. 2MHz is stable. + DMA1.CNTR5.R = FULL_LEN; + DMA1.MADDR5.R = reinterpret_cast(ptr); + DMA1.PADDR5.R = reinterpret_cast(& TIM1.CH1CVR); + DMA1.CFGR5.modify([](DMA1_Type::CFGR5_DEF & r) -> auto { + r.B.DIR = SET; // MEM2PERIPHERAL + r.B.PL = 2u; // High priority. + r.B.PSIZE = 1u; // 16-bit peripheral + r.B.MSIZE = 1u; // 16-bit memory + r.B.MINC = SET; // Increase memory. + r.B.CIRC = SET; // Circular mode. + r.B.HTIE = SET; // Half-trigger + r.B.TCIE = SET; // Whole-trigger + return r.R; + }); + NVIC.EnableIRQ (DMA1_Channel5_IRQn); + DMA1.CFGR5.B.EN = SET; +} + +PwmClass::PwmClass() noexcept : pL(buffer), pH(buffer + HALF_LEN), src(nullptr) { + for (unsigned n=0; nSend (pH, HALF_LEN); + else src->Send (pL, HALF_LEN); + } +}; + +#endif // PWMCLASS_H