add pwm base

This commit is contained in:
Kizarm 2024-03-01 14:14:21 +01:00
parent fdcc93ce96
commit e7f6d52a86
7 changed files with 345 additions and 0 deletions

53
pwm/Makefile Normal file
View file

@ -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

1
pwm/ch32v003 Symbolic link
View file

@ -0,0 +1 @@
../ch32v003/

150
pwm/dma_gpio.c Normal file
View file

@ -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 <stdio.h>
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" );
}
}

7
pwm/main.cpp Normal file
View file

@ -0,0 +1,7 @@
#include "pwmclass.h"
//////////////////////////////////////
static PwmClass pwm;
int main () {
for (;;);
return 0;
}

10
pwm/oneway.h Normal file
View file

@ -0,0 +1,10 @@
#ifndef ONEWAY_H
#define ONEWAY_H
#include <stdint.h>
class OneWay {
public:
virtual unsigned Send (uint16_t * const ptr, const unsigned len) = 0;
};
#endif // ONEWAY_H

102
pwm/pwmclass.cpp Normal file
View file

@ -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<uint32_t>(ptr);
DMA1.PADDR5.R = reinterpret_cast<uint32_t>(& 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; n<FULL_LEN; n+=1) buffer [n] = n << 1;
pInstance = this;
t1pwm_init ();
dma1ch5_init(buffer);
}

22
pwm/pwmclass.h Normal file
View file

@ -0,0 +1,22 @@
#ifndef PWMCLASS_H
#define PWMCLASS_H
#include "oneway.h"
static constexpr unsigned HALF_LEN = 64u;
static constexpr unsigned FULL_LEN = 2 * HALF_LEN;
class PwmClass {
uint16_t * const pL;
uint16_t * const pH;
uint16_t buffer [FULL_LEN];
OneWay * src;
public:
explicit PwmClass () noexcept;
void attach (OneWay & s) { src = & s; }
void send (const bool b) {
if (!src) return;
if (b) src->Send (pH, HALF_LEN);
else src->Send (pL, HALF_LEN);
}
};
#endif // PWMCLASS_H