diff --git a/.gitignore b/.gitignore index ee4cfb75b..e0386f705 100644 --- a/.gitignore +++ b/.gitignore @@ -130,5 +130,5 @@ vs/*.log tests/libDaisy_gtest tests/build/bin/ -examples/*/build/ +examples/**/build/ diff --git a/.vscode/launch.json b/.vscode/launch.json index 5b38355cb..bc98c51de 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -21,6 +21,37 @@ "windows": { "MIMode": "gdb", } + }, + { + "name": "Debug", + "configFiles": [ + "interface/stlink.cfg", + "target/stm32h7x.cfg" + ], + "cwd": "${workspaceFolder}", + "debuggerArgs": [ + "-d", + "${workspaceRoot}" + ], + // Here's where you can put the path to the program you want to debug: + "executable": "${workspaceRoot}/examples/PWM/AddressableLED/build/addressable-led.elf", + "interface": "swd", + "openOCDLaunchCommands": [ + "init", + "reset init", + "gdb_breakpoint_override hard" + ], + "preRestartCommands": [ + "load", + "enable breakpoint", + "monitor reset" + ], + "request": "launch", + "runToMain": true, + "servertype": "openocd", + "showDevDebugOutput": true, + "svdFile": "${workspaceRoot}/.vscode/STM32H750x.svd", + "type": "cortex-debug" } ] } \ No newline at end of file diff --git a/Makefile b/Makefile index 812d0bcc6..8d915f4a2 100644 --- a/Makefile +++ b/Makefile @@ -58,6 +58,7 @@ per/qspi \ per/spi \ per/spiMultislave \ per/tim \ +per/tim_channel \ per/uart \ ui/UI \ ui/AbstractMenu \ @@ -69,8 +70,8 @@ util/WaveTableLoader \ ###################################### # building variables ###################################### -DEBUG = 0 -OPT = -O3 +DEBUG = 1 +OPT = -O0 ####################################### # paths diff --git a/examples/PWM/AddressableLED/Makefile b/examples/PWM/AddressableLED/Makefile new file mode 100644 index 000000000..6b0c64748 --- /dev/null +++ b/examples/PWM/AddressableLED/Makefile @@ -0,0 +1,17 @@ +# Project Name +TARGET = addressable-led + +# Sources +CPP_SOURCES = addressable-led.cpp + +DEBUG=1 +OPT=-O0 + +C_DEFS += -DDEBUG_DEFAULT_INTERRUPT_HANDLERS + +# Library Locations +LIBDAISY_DIR = ../../.. + +# Core location, and generic Makefile. +SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core +include $(SYSTEM_FILES_DIR)/Makefile diff --git a/examples/PWM/AddressableLED/addressable-led.cpp b/examples/PWM/AddressableLED/addressable-led.cpp new file mode 100644 index 000000000..e3c87218a --- /dev/null +++ b/examples/PWM/AddressableLED/addressable-led.cpp @@ -0,0 +1,183 @@ +/** DMA PWM + * + * In this example we will use the DMA to generate a PWM sequence + * This can be common for things like Digital LEDs (WS8212), motor control, etc. + * + * For this we're going to use Daisy Seed pin D17 as TIM3 Channel 4 (unless it has issues because of the LED attached..) + * + * TODO: This won't work until this stuff is resolved: + * * Hardcoding for TIM3Ch4 is in the libDaisy stuff. Needs to get updated so we can use TIM4 Ch1 for the devboard + * * Some sort of stop/reset between transactions to prep LEDs for next signal + */ +#include "daisy_seed.h" + +using namespace daisy; + +/** Hardware object for communicating with Daisy */ +DaisySeed hw; +const size_t kOutBufferSize = 512; +uint32_t DMA_BUFFER_MEM_SECTION outbuffer[kOutBufferSize]; + +// const size_t kTickPeriod = 36; +// const int kOneTime = kTickPeriod - 20; +// const int kZeroTime = kTickPeriod - 8; +const size_t kTickPeriod = 29; +// const size_t kTickPeriod = 59; +const int kOneTime = 20; +const int kZeroTime = 8; + + +/** both the same for now*/ +// const int kZeroTime = 14; +// const int kOneTime = 21; +// const int kZeroTime = 2 * (kTickPeriod / 3); +// const int kOneTime = kTickPeriod / 3; + +const int kNumLeds = 4; +uint8_t led_data[kNumLeds][3]; /**< RGB data */ + +const size_t kOutDataSize = kNumLeds * 3 * 8; +uint32_t DMA_BUFFER_MEM_SECTION + output_data[kNumLeds * 3 + * 8]; /**< PWM lengths data, one "duration" per bit */ + +/** buff expects that 8 elements are available for the one 8-bit color val*/ +static void populate_bits(uint8_t color_val, uint32_t* buff) +{ + for(int i = 0; i < 8; i++) + { + buff[i] = (color_val & (1 << (7 - i))) > 0 ? kOneTime : kZeroTime; + } +} + +static void fill_led_data() +{ + /** TODO fix these to be accurate for necessary timing */ + for(int i = 0; i < kNumLeds; i++) + { + /** Grab G, R, B for filling bytes */ + uint8_t g = led_data[i][1]; + uint8_t r = led_data[i][0]; + uint8_t b = led_data[i][2]; + auto data_index = i * 3 * 8; + populate_bits(g, &output_data[data_index]); + populate_bits(r, &output_data[data_index + 8]); + populate_bits(b, &output_data[data_index + 16]); + } +} + +static void set_led(int index, uint8_t r, uint8_t g, uint8_t b) +{ + led_data[index][0] = r; + led_data[index][1] = g; + led_data[index][2] = b; +} + +static void set_led_f(int index, float r, float g, float b) +{ + set_led(index, r * 255, g * 255, b * 255); +} + +void EndOfLeds(void* context) +{ + TimChannel* pwm = (TimChannel*)context; + pwm->SetPwm(0); + // pwm->Stop(); +} + +int main(void) +{ + /** Initialize hardware */ + hw.Init(); + + /** Initialize timer */ + TimerHandle::Config tim_cfg; + TimerHandle timer; + tim_cfg.periph = TimerHandle::Config::Peripheral::TIM_4; + tim_cfg.dir = TimerHandle::Config::CounterDir::UP; + timer.Init(tim_cfg); + + /** Generate period for timer + * This is a marvelously useful little tidbit that should be put into a TimerHandle function or something. + */ + uint32_t prescaler = 8; + uint32_t tickspeed = (System::GetPClk2Freq() * 2) / prescaler; + uint32_t target_pulse_freq = 833333; /**< 1.2 microsecond symbol length */ + uint32_t period = (tickspeed / target_pulse_freq) - 1; + timer.SetPrescaler(prescaler - 1); /**< ps=0 is divide by 1 and so on.*/ + timer.SetPeriod(period); + + TimChannel::Config chn_cfg; + chn_cfg.tim = &timer; + chn_cfg.chn = TimChannel::Config::Channel::ONE; + chn_cfg.mode = TimChannel::Config::Mode::PWM; + chn_cfg.polarity = TimChannel::Config::Polarity::LOW; + chn_cfg.pin = seed::D13; + TimChannel pwm; + /** Fill Buffer */ + for(size_t i = 0; i < kOutBufferSize; i++) + { + float t = (float)i / (float)(kOutBufferSize - 1); /**< 0.0->1.0 */ + float ts = 0.5f + (cos(t * 6.28) * 0.5f); + outbuffer[i] = (uint32_t)(ts * period); + } + /** Initialize PWM */ + pwm.Init(chn_cfg); + pwm.SetPwm(0); + pwm.Start(); + timer.Start(); + + uint32_t now, tled; + now = tled = System::GetNow(); + + float gbright = 0; + + while(1) + { + now = System::GetNow(); + //if(now - tled > 33) + if(now - tled > 33) + { + tled = now; + // gbright += 0.0005f; + // if(gbright > 0.5f) + // { + // gbright = 0.f; + // } + gbright += 0.01; + if(gbright > 0.5f) + { + gbright = 0.f; + } + + // gbright = 0.03f; + // gbright = 1; + + /* Lets set some LED stuff */ + for(int i = 0; i < kNumLeds; i++) + { + // float bright = (float)(now & 1023) / 1023.f; + // float bright = (now & 1023) > 511 ? 0.33f : 0.f; + switch(i) + { + case 0: set_led_f(i, gbright, 0.f, 0.f); break; + case 1: set_led_f(i, 0.f, 0.f, gbright); break; + case 2: set_led_f(i, 0.f, gbright, 0.f); break; + default: set_led_f(i, gbright, gbright, gbright); break; + } + // switch(i) + // { + // case 0: set_led(i, gbright, 0, 0); break; + // case 1: set_led(i, 0, gbright, 0); break; + // case 2: set_led(i, 0, 0, gbright); break; + // default: set_led(i, gbright, gbright, gbright); break; + // } + } + fill_led_data(); + + /** And transmit */ + pwm.Start(); + pwm.StartDma(output_data, kOutDataSize, EndOfLeds, (void*)&pwm); + } + } +} diff --git a/examples/PWM/Basic-PWM/Makefile b/examples/PWM/Basic-PWM/Makefile new file mode 100644 index 000000000..760c2f75d --- /dev/null +++ b/examples/PWM/Basic-PWM/Makefile @@ -0,0 +1,15 @@ +# Project Name +TARGET = basic-pwm + +# Sources +CPP_SOURCES = basic-pwm.cpp + +DEBUG=1 +OPT=-O0 + +# Library Locations +LIBDAISY_DIR = ../../.. + +# Core location, and generic Makefile. +SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core +include $(SYSTEM_FILES_DIR)/Makefile diff --git a/examples/PWM/Basic-PWM/basic-pwm.cpp b/examples/PWM/Basic-PWM/basic-pwm.cpp new file mode 100644 index 000000000..84fb865be --- /dev/null +++ b/examples/PWM/Basic-PWM/basic-pwm.cpp @@ -0,0 +1,55 @@ +/** Basic PWM + * + * In this example, we will configure a single GPIO + * to output PWM using the TIM hardware built into the daisy. + * + * For this we're going to use Daisy Seed pin D17 as TIM3 Channel 4 + */ +#include "daisy_seed.h" + +using namespace daisy; + +/** Hardware object for communicating with Daisy */ +DaisySeed hw; + +int main(void) +{ + /** Initialize hardware */ + hw.Init(); + + /** Configure frequency (12kHz) */ + auto tim_target_freq = 12000; + auto tim_base_freq = System::GetPClk2Freq(); + + TimerHandle::Config tim_cfg; + TimerHandle timer; + tim_cfg.periph = TimerHandle::Config::Peripheral::TIM_3; + tim_cfg.period = tim_base_freq / tim_target_freq; + timer.Init(tim_cfg); + + TimChannel::Config chn_cfg; + chn_cfg.tim = &timer; + chn_cfg.chn = TimChannel::Config::Channel::FOUR; + chn_cfg.mode = TimChannel::Config::Mode::PWM; + chn_cfg.pin = seed::D17; + TimChannel pwm; + /** Initialize PWM */ + pwm.Init(chn_cfg); + timer.Start(); + pwm.Start(); + + /** Step through some brightness values */ + int vals[32]; + for(int i = 0; i < 32; i++) + vals[i] = i * (tim_cfg.period / 32); + + while(1) + { + /* ~30Hz animation of our little 32-frame ramp wave. */ + for(int i = 0; i < 32; i++) + { + pwm.SetPwm(vals[i]); + System::Delay(33); + } + } +} diff --git a/examples/PWM/DMA-PWM/Makefile b/examples/PWM/DMA-PWM/Makefile new file mode 100644 index 000000000..49e593b6a --- /dev/null +++ b/examples/PWM/DMA-PWM/Makefile @@ -0,0 +1,17 @@ +# Project Name +TARGET = dma-pwm + +# Sources +CPP_SOURCES = dma-pwm.cpp + +DEBUG=1 +OPT=-O0 + +C_DEFS += -DDEBUG_DEFAULT_INTERRUPT_HANDLERS + +# Library Locations +LIBDAISY_DIR = ../../.. + +# Core location, and generic Makefile. +SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core +include $(SYSTEM_FILES_DIR)/Makefile diff --git a/examples/PWM/DMA-PWM/dma-pwm.cpp b/examples/PWM/DMA-PWM/dma-pwm.cpp new file mode 100644 index 000000000..6c2cf3d5f --- /dev/null +++ b/examples/PWM/DMA-PWM/dma-pwm.cpp @@ -0,0 +1,69 @@ +/** DMA PWM + * + * In this example we will use the DMA to generate a PWM sequence + * This can be common for things like Digital LEDs (WS8212), motor control, etc. + * + * For this we're going to use Daisy Seed pin D17 as TIM3 Channel 4 (unless it has issues because of the LED attached..) + */ +#include "daisy_seed.h" + +using namespace daisy; + +/** Hardware object for communicating with Daisy */ +DaisySeed hw; +const size_t kOutBufferSize = 512; +uint32_t DMA_BUFFER_MEM_SECTION outbuffer[kOutBufferSize]; + +int main(void) +{ + /** Initialize hardware */ + hw.Init(); + + /** Initialize timer */ + TimerHandle::Config tim_cfg; + TimerHandle timer; + tim_cfg.periph = TimerHandle::Config::Peripheral::TIM_3; + timer.Init(tim_cfg); + + /** Generate period for timer + * This is a marvelously useful little tidbit that should be put into a TimerHandle function or something. + */ + uint32_t prescaler = 8; + uint32_t tickspeed = (System::GetPClk2Freq() * 2) / prescaler; + uint32_t target_pulse_freq = 833333; /**< 1.2 microsecond symbol length */ + uint32_t period = (tickspeed / target_pulse_freq) - 1; + timer.SetPrescaler(prescaler - 1); /**< ps=0 is divide by 1 and so on.*/ + timer.SetPeriod(period); + + TimChannel::Config chn_cfg; + chn_cfg.tim = &timer; + chn_cfg.chn = TimChannel::Config::Channel::FOUR; + chn_cfg.mode = TimChannel::Config::Mode::PWM; + chn_cfg.pin = seed::D17; + TimChannel pwm; + /** Fill Buffer */ + for(size_t i = 0; i < kOutBufferSize; i++) + { + float t = (float)i / (float)(kOutBufferSize - 1); /**< 0.0->1.0 */ + float ts = 0.5f + (cos(t * 6.28) * 0.5f); + outbuffer[i] = (uint32_t)(ts * period); + } + /** Initialize PWM */ + pwm.Init(chn_cfg); + timer.Start(); + pwm.Start(); + System::Delay(1000); + pwm.StartDma(outbuffer, kOutBufferSize, nullptr); + bool led_state = true; + + while(1) + { + System::Delay(2000); + hw.SetLed(led_state); + if(led_state) + led_state = false; + else + led_state = true; + pwm.StartDma(outbuffer, kOutBufferSize, nullptr); + } +} diff --git a/src/daisy.h b/src/daisy.h index 38023cce6..cff8ee5a2 100644 --- a/src/daisy.h +++ b/src/daisy.h @@ -34,6 +34,7 @@ #include "per/sdmmc.h" #include "per/spi.h" #include "per/spiMultislave.h" +#include "per/tim_channel.h" #include "per/rng.h" #include "hid/disp/display.h" #include "hid/disp/oled_display.h" diff --git a/src/per/tim.h b/src/per/tim.h index c37b25e0c..388b45c56 100644 --- a/src/per/tim.h +++ b/src/per/tim.h @@ -123,7 +123,7 @@ class TimerHandle ** This will adjust the rate of ticks: ** Calculated as APBN_Freq / prescalar per tick ** where APBN is APB1 for Most general purpose timers, - ** and APB2 for HRTIM,a nd the advanced timers. + ** and APB2 for HRTIM, and the advanced timers. ** This can be changed "on-the-fly" ** */ Result SetPrescaler(uint32_t val); diff --git a/src/per/tim_channel.cpp b/src/per/tim_channel.cpp new file mode 100644 index 000000000..27a3b6f77 --- /dev/null +++ b/src/per/tim_channel.cpp @@ -0,0 +1,273 @@ +#include "tim_channel.h" +#include "util/hal_map.h" + +namespace daisy +{ +static DMA_HandleTypeDef timhdma; + +/** Pin Mappings: + * TODO: Make a map + * + * TIM2 CH1 - PA0 (AF1), PA5 (AF1) + * TIM2 CH2 - PA1 (AF1), PB3 (AF1) + * TIM2 CH3 - PA2 (AF1), PB10 (AF1) + * TIM2 CH4 - PA3 (AF1), PB11 (AF1) + * TIM3 CH1 - PA6 (AF2), PB4 (AF2), PC6 (AF2) + * TIM3 CH2 - PA7 (AF2), PB5 (AF2), PC7 (AF2) + * TIM3 CH3 - PB0 (AF2), PC8 (AF2) + * TIM3 CH4 - PB1 (AF2), PC9 (AF2) + * TIM4 CH1 - PD12 (AF2), PB6 (AF2) + * TIM4 CH2 - PD13 (AF2), PB7 (AF2) + * TIM4 CH3 - PD14 (AF2), PB8 (AF2) + * TIM4 CH4 - PD15 (AF2), PB9 (AF2) + * TIM5 CH1 - PA0 (AF2), PH10 (AF2) + * TIM5 CH2 - PA1 (AF2), PH11 (AF2) + * TIM5 CH3 - PA2 (AF2), PH12 (AF2) + * TIM5 CH4 - PA3 (AF2), PI0 (AF2) + * + * And without a map: + * TIM2 ChN (AF1) + * TIM3 ChN (AF2) + * TIM4 ChN (AF2) + * TIM5 ChN (AF2) + */ + +/** Sets the instance of the HAL TIM Handle based on the values in the Daisy struct + * This also returns the AF value for a given timer (can be used for GPIO init). + */ +static uint32_t SetInstance(TIM_HandleTypeDef* tim, + TimerHandle::Config::Peripheral dsy_periph) +{ + uint32_t af_value; + switch(dsy_periph) + { + case TimerHandle::Config::Peripheral::TIM_2: + tim->Instance = TIM2; + af_value = GPIO_AF1_TIM2; + break; + case TimerHandle::Config::Peripheral::TIM_3: + tim->Instance = TIM3; + af_value = GPIO_AF2_TIM3; + break; + case TimerHandle::Config::Peripheral::TIM_4: + tim->Instance = TIM4; + af_value = GPIO_AF2_TIM4; + break; + case TimerHandle::Config::Peripheral::TIM_5: + tim->Instance = TIM5; + af_value = GPIO_AF2_TIM5; + break; + } + return af_value; +} + +static uint32_t GetHalChannel(TimChannel::Config::Channel chn) +{ + auto hal_chn = chn == TimChannel::Config::Channel::ONE ? TIM_CHANNEL_1 + : chn == TimChannel::Config::Channel::TWO ? TIM_CHANNEL_2 + : chn == TimChannel::Config::Channel::THREE ? TIM_CHANNEL_3 + : chn == TimChannel::Config::Channel::FOUR ? TIM_CHANNEL_4 + : TIM_CHANNEL_1; + return hal_chn; +} + +void TimChannel::Init(const TimChannel::Config& cfg) +{ + cfg_ = cfg; + /** Configure Channel */ + TIM_OC_InitTypeDef sConfigOC = {0}; + sConfigOC.OCMode = TIM_OCMODE_PWM1; + sConfigOC.Pulse = 0; + sConfigOC.OCPolarity = cfg.polarity == Config::Polarity::HIGH + ? TIM_OCPOLARITY_HIGH + : TIM_OCPOLARITY_LOW; + sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; + auto chn = GetHalChannel(cfg.chn); + TIM_HandleTypeDef tim; + auto af_value = SetInstance(&tim, cfg.tim->GetConfig().periph); + HAL_TIM_PWM_ConfigChannel(&tim, &sConfigOC, chn); + + /** TODO: remove conversion to old pin, and add hal map for new Pin type */ + dsy_gpio_pin tpin = cfg.pin; + GPIO_TypeDef* port = dsy_hal_map_get_port(&tpin); + uint16_t pin = dsy_hal_map_get_pin(&tpin); + /** Start Clock for port (if necessary) */ + dsy_hal_map_gpio_clk_enable(tpin.port); + /** Intilize the actual pin */ + GPIO_InitTypeDef gpio_init = {0}; + gpio_init.Pin = pin; + gpio_init.Mode = GPIO_MODE_AF_PP; + gpio_init.Pull = GPIO_NOPULL; + gpio_init.Speed = GPIO_SPEED_LOW; + gpio_init.Alternate = af_value; + HAL_GPIO_Init(port, &gpio_init); +} + +const TimChannel::Config& TimChannel::GetConfig() const +{ + return cfg_; +} + +void TimChannel::Start() +{ + TIM_HandleTypeDef tim; + SetInstance(&tim, cfg_.tim->GetConfig().periph); + HAL_TIM_PWM_Start(&tim, GetHalChannel(cfg_.chn)); +} +void TimChannel::Stop() +{ + TIM_HandleTypeDef tim; + SetInstance(&tim, cfg_.tim->GetConfig().periph); + HAL_TIM_PWM_Stop(&tim, GetHalChannel(cfg_.chn)); +} + +void TimChannel::SetPwm(uint32_t val) +{ + TIM_HandleTypeDef tim; + SetInstance(&tim, cfg_.tim->GetConfig().periph); + __HAL_TIM_SET_COMPARE(&tim, GetHalChannel(cfg_.chn), val); +} +static TIM_HandleTypeDef globaltim; +TimChannel::EndTransmissionFunctionPtr globalcb; +void* globalcb_context; + +void TimChannel::StartDma(void* data, + size_t size, + TimChannel::EndTransmissionFunctionPtr callback, + void* cb_context) +{ + timhdma.Instance = DMA2_Stream5; + + globalcb = callback; + globalcb_context = cb_context; + + /** kind of nuts to set this.... */ + switch(cfg_.tim->GetConfig().periph) + { + case TimerHandle::Config::Peripheral::TIM_2: + switch(cfg_.chn) + { + case TimChannel::Config::Channel::ONE: + timhdma.Init.Request = DMA_REQUEST_TIM2_CH1; + break; + case TimChannel::Config::Channel::TWO: + timhdma.Init.Request = DMA_REQUEST_TIM2_CH2; + break; + case TimChannel::Config::Channel::THREE: + timhdma.Init.Request = DMA_REQUEST_TIM2_CH3; + break; + case TimChannel::Config::Channel::FOUR: + timhdma.Init.Request = DMA_REQUEST_TIM2_CH4; + break; + } + break; + case TimerHandle::Config::Peripheral::TIM_3: + switch(cfg_.chn) + { + case TimChannel::Config::Channel::ONE: + timhdma.Init.Request = DMA_REQUEST_TIM3_CH1; + break; + case TimChannel::Config::Channel::TWO: + timhdma.Init.Request = DMA_REQUEST_TIM3_CH2; + break; + case TimChannel::Config::Channel::THREE: + timhdma.Init.Request = DMA_REQUEST_TIM3_CH3; + break; + case TimChannel::Config::Channel::FOUR: + timhdma.Init.Request = DMA_REQUEST_TIM3_CH4; + break; + } + break; + case TimerHandle::Config::Peripheral::TIM_4: + switch(cfg_.chn) + { + case TimChannel::Config::Channel::ONE: + timhdma.Init.Request = DMA_REQUEST_TIM4_CH1; + break; + case TimChannel::Config::Channel::TWO: + timhdma.Init.Request = DMA_REQUEST_TIM4_CH2; + break; + case TimChannel::Config::Channel::THREE: + timhdma.Init.Request = DMA_REQUEST_TIM4_CH3; + break; + case TimChannel::Config::Channel::FOUR: + timhdma.Init.Request + = DMA_REQUEST_TIM4_UP; /**< TIM4_CH4 DMA Rq doesn't exist?*/ + break; + } + break; + case TimerHandle::Config::Peripheral::TIM_5: + switch(cfg_.chn) + { + case TimChannel::Config::Channel::ONE: + timhdma.Init.Request = DMA_REQUEST_TIM5_CH1; + break; + case TimChannel::Config::Channel::TWO: + timhdma.Init.Request = DMA_REQUEST_TIM5_CH2; + break; + case TimChannel::Config::Channel::THREE: + timhdma.Init.Request = DMA_REQUEST_TIM5_CH3; + break; + case TimChannel::Config::Channel::FOUR: + timhdma.Init.Request = DMA_REQUEST_TIM5_CH4; + break; + } + break; + } + + timhdma.Init.Direction = DMA_MEMORY_TO_PERIPH; + + timhdma.Init.PeriphInc = DMA_PINC_DISABLE; + timhdma.Init.MemInc = DMA_MINC_ENABLE; + timhdma.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD; + timhdma.Init.MemDataAlignment = DMA_MDATAALIGN_WORD; + timhdma.Init.Mode = DMA_NORMAL; + timhdma.Init.Priority = DMA_PRIORITY_LOW; + timhdma.Init.FIFOMode = DMA_FIFOMODE_DISABLE; + timhdma.Init.MemBurst = DMA_MBURST_SINGLE; + timhdma.Init.PeriphBurst = DMA_PBURST_SINGLE; + if(HAL_DMA_Init(&timhdma) != HAL_OK) + { + // something bad + } + SetInstance(&globaltim, cfg_.tim->GetConfig().periph); + switch(cfg_.chn) + { + case TimChannel::Config::Channel::ONE: + __HAL_LINKDMA(&globaltim, hdma[TIM_DMA_ID_CC1], timhdma); + break; + case TimChannel::Config::Channel::TWO: + __HAL_LINKDMA(&globaltim, hdma[TIM_DMA_ID_CC2], timhdma); + break; + case TimChannel::Config::Channel::THREE: + __HAL_LINKDMA(&globaltim, hdma[TIM_DMA_ID_CC3], timhdma); + break; + case TimChannel::Config::Channel::FOUR: + __HAL_LINKDMA(&globaltim, hdma[TIM_DMA_ID_CC4], timhdma); + break; + } + HAL_TIM_PWM_Start_DMA( + &globaltim, GetHalChannel(cfg_.chn), (uint32_t*)data, size); +} + + +extern "C" void DMA2_Stream5_IRQHandler(void) +{ + // DMA_HandleTypeDef timhdma; + // timhdma.Instance = DMA2_Stream5; + HAL_DMA_IRQHandler(&timhdma); +} + + +extern "C" void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef* htim) +{ + if(globalcb) + { + globalcb(globalcb_context); + } +} + + +extern "C" void DMAMUX1_OVR_IRQHandler(void) {} + +} // namespace daisy \ No newline at end of file diff --git a/src/per/tim_channel.h b/src/per/tim_channel.h new file mode 100644 index 000000000..3b9076403 --- /dev/null +++ b/src/per/tim_channel.h @@ -0,0 +1,96 @@ +#include "daisy_core.h" +#include "tim.h" + +/** Some notes as I set stuff up: + * + * The HAL_TIM_Base_Init() seems to cover everything we need, + * everything for the "channel" I/O happens via the MspInit HAL callbacks + * Since we'll be handling these post-init, on channel-by-channel option + * we can probably avoid doing the {function}Init stuff, and populating any MspInit + * + * Also, I'm including all of the modes here for now, but I'm really focused on + * implementing PWM (via DMA). So I will do all of the implementation for that, + * and then see what doing IC, OC, and ONEPULSE would look like. + * + * also we'll _probably_ have to do a pimpl, but we'll see what we can do without one for the moment. + * + */ + +namespace daisy +{ +class TimChannel +{ + public: + struct Config + { + /** Specifies the Channel to use */ + enum class Channel + { + ONE, + TWO, + THREE, + FOUR, + }; + + enum class Mode + { + INPUT_CAPTURE, + OUTPUT_COMPARE, + PWM, + ONE_PULSE, + }; + + enum class Polarity + { + HIGH, + LOW + }; + + TimerHandle* tim; + Channel chn; + Mode mode; + Polarity polarity; + Pin pin; + Config() + : tim(nullptr), + chn(Channel::ONE), + mode(Mode::PWM), + polarity(Polarity::LOW) + { + } + }; + + TimChannel() {} + ~TimChannel() {} + + /** Initializes the GPIO Pin and sets up PWM for the given channel */ + void Init(const Config& cfg); + + /** Starts the PWM output on the given channel's pin */ + void Start(); + + /** Stops the PWM output on the given channel's pin */ + void Stop(); + + /** Sets the immediate PWM value, based on the current TIM period. + * For example, if period is 256, then a val of 128 will be 50% pulsewidth + */ + void SetPwm(uint32_t val); + + typedef void (*EndTransmissionFunctionPtr)(void* context); + + + /** Starts the DMA for the given buffer, calling the callback + * when the transmission is complete. + */ + void StartDma(void* data, + size_t size, + EndTransmissionFunctionPtr callback = nullptr, + void* cb_context = nullptr); + + const Config& GetConfig() const; + + private: + Config cfg_; +}; +} // namespace daisy \ No newline at end of file diff --git a/src/sys/dma.c b/src/sys/dma.c index 73c0a1ed7..78a28a393 100644 --- a/src/sys/dma.c +++ b/src/sys/dma.c @@ -48,6 +48,10 @@ extern "C" HAL_NVIC_EnableIRQ(DMA2_Stream2_IRQn); HAL_NVIC_SetPriority(DMA2_Stream3_IRQn, 0, 0); HAL_NVIC_EnableIRQ(DMA2_Stream3_IRQn); + + // DMA2_Stream5 used for TIM Channel operation + HAL_NVIC_SetPriority(DMA2_Stream5_IRQn, 0, 0); + HAL_NVIC_EnableIRQ(DMA2_Stream5_IRQn); } void dsy_dma_deinit(void) diff --git a/src/util/hal_map.h b/src/util/hal_map.h index acb7d23e4..9d280198a 100644 --- a/src/util/hal_map.h +++ b/src/util/hal_map.h @@ -1,6 +1,9 @@ #pragma once #ifndef DSY_HAL_MAP_H #define DSY_HAL_MAP_H +#ifdef __cplusplus +extern "C" { +#endif #include "stm32h7xx_hal.h" #include "daisy_core.h" @@ -30,5 +33,9 @@ uint16_t dsy_hal_map_get_pin(const dsy_gpio_pin *p); */ void dsy_hal_map_gpio_clk_enable(dsy_gpio_port port); +#ifdef __cplusplus +} +#endif + #endif /** @} */