Skip to content

Implemented DMA support for PWM #443

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
This program demonstrates using the DMA controller to send data from
memory to a peripheral.

Specifically, it uses DMA to send a duty cycles values to the Timer (PWM).

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
with "../../../../../boards/stm32f4xx_m/stm32f4xx_m_full.gpr";

project Demo_PWM_DMA_Continuous extends "../../../../../examples/shared/common/common.gpr" is

for Main use ("demo_pwm_dma_continuous.adb");
for Languages use ("Ada");
for Source_Dirs use ("src");
for Object_Dir use "obj/" & STM32F4XX_M_Full.Build;
for Runtime ("Ada") use STM32F4XX_M_Full'Runtime("Ada");
for Create_Missing_Dirs use "true";

package Builder is
for Global_Configuration_Pragmas use "gnat.adc";
end Builder;

package Compiler renames STM32F4XX_M_Full.Compiler;

end Demo_PWM_DMA_Continuous;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pragma Partition_Elaboration_Policy (Sequential);

Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
------------------------------------------------------------------------------
-- --
-- Copyright (C) 2025, AdaCore --
-- --
-- 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 copyright holder nor the names of its --
-- contributors may be used to endorse or promote products derived --
-- from this software without specific prior written permission. --
-- --
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS --
-- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT --
-- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR --
-- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT --
-- HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, --
-- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT --
-- LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, --
-- DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY --
-- THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT --
-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE --
-- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --
-- --
------------------------------------------------------------------------------

with STM32.Board;
with STM32.Device;
with STM32.PWM;
with STM32.Timers;
with STM32.DMA;
with HAL;

procedure Demo_PWM_DMA_Continuous is
type Data is array (0 .. 200) of HAL.UInt32;
for Data'Component_Size use 32;
Bytes_To_Transfer : constant := Data'Length;
Source_Block : Data;

Controller : STM32.DMA.DMA_Controller renames STM32.Device.DMA_1;
Configuration : STM32.DMA.DMA_Stream_Configuration;

Tx_Channel : constant STM32.DMA.DMA_Channel_Selector := STM32.DMA.Channel_6;
Tx_Stream : constant STM32.DMA.DMA_Stream_Selector := STM32.DMA.Stream_0;

-- See RM0090, section 10.3.3, for the DMA channel request mapping tables
-- that say which controllers, and which channels and streams on those
-- controllers, can connect to which devices. For example, it is channel
-- six and stream null that connect DMA1 to the timer of TIM5_UP, so
-- we specify those values above.

Selected_Timer : STM32.Timers.Timer renames STM32.Device.Timer_5;
Timer_AF : constant STM32.GPIO_Alternate_Function :=
STM32.Device.GPIO_AF_TIM5_2;

Output_Channel : constant STM32.Timers.Timer_Channel :=
STM32.Timers.Channel_2;

Requested_Frequency : constant STM32.PWM.Hertz := 100;

LED_Control : STM32.PWM.PWM_Modulator;

begin

STM32.PWM.Configure_PWM_Timer (Selected_Timer'Access, Requested_Frequency);

LED_Control.Attach_PWM_Channel
(Selected_Timer'Access,
Output_Channel,
STM32.Board.Green_LED,
Timer_AF);
LED_Control.Enable_Output;

-- Initialize of values
for Ind in 0 .. 100 loop
Source_Block (Ind) := LED_Control.Calculate_Compare_Value (Ind);
Source_Block (Data'Last - Ind) := Source_Block (Ind);
end loop;

STM32.Timers.Set_Output_Preload_Enable
(Selected_Timer, Output_Channel, True);

STM32.Timers.Configure_DMA (Selected_Timer,
STM32.Timers.DMA_Base_CCR2,
STM32.Timers.DMA_Burst_Length_1);

STM32.Timers.Enable_DMA_Source
(Selected_Timer, STM32.Timers.Timer_DMA_Update);

STM32.Device.Enable_Clock (Controller);

STM32.DMA.Reset (Controller, Tx_Stream);

Configuration.Channel := Tx_Channel;
Configuration.Direction := STM32.DMA.Memory_To_Peripheral;
Configuration.Increment_Peripheral_Address := False;
Configuration.Increment_Memory_Address := True;
Configuration.Peripheral_Data_Format := STM32.DMA.Words;
Configuration.Memory_Data_Format := STM32.DMA.Words;
Configuration.Operation_Mode := STM32.DMA.Circular_Mode;
Configuration.Priority := STM32.DMA.Priority_Very_High;
Configuration.FIFO_Enabled := False;

STM32.DMA.Configure (Controller, Tx_Stream, Configuration);

STM32.DMA.Start_Transfer
(Controller,
Tx_Stream,
Source => Source_Block'Address,
Destination => STM32.PWM.Data_Register_Address (LED_Control),
Data_Count => Bytes_To_Transfer);

STM32.Timers.Enable_Capture_Compare_DMA (Selected_Timer);

loop
null;
end loop;
end Demo_PWM_DMA_Continuous;



47 changes: 32 additions & 15 deletions arch/ARM/STM32/drivers/stm32-pwm.adb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
-- --
------------------------------------------------------------------------------

with System; use System;
with STM32_SVD; use STM32_SVD;
with STM32.Device; use STM32.Device;

Expand Down Expand Up @@ -60,6 +59,23 @@ package body STM32.PWM is
function Has_APB1_Frequency (This : Timer) return Boolean;
-- timers 3, 4, 6, 7, 12, 13, 14

-----------------------------
-- Calculate_Compare_Value --
-----------------------------

function Calculate_Compare_Value
(This : in out PWM_Modulator; Value : Percentage) return UInt32 is
Period : constant UInt32 := Timer_Period (This);
begin
if Value = 0 then
return 0;
elsif Period < 42949672 then
return ((Period + 1) * UInt32 (Value) / 100) - 1;
else
return ((Period + 1) / 100 * UInt32 (Value)) - 1;
end if;
end Calculate_Compare_Value;

--------------------
-- Set_Duty_Cycle --
--------------------
Expand All @@ -68,25 +84,16 @@ package body STM32.PWM is
(This : in out PWM_Modulator;
Value : Percentage)
is
Pulse16 : UInt16;
Pulse32 : UInt32;
Pulse32 : constant UInt32 := Calculate_Compare_Value (This, Value);
begin
This.Duty_Cycle := Value;

if Value = 0 then
Set_Compare_Value (This.Generator.all, This.Channel, UInt16'(0));
if Has_32bit_CC_Values (This.Generator.all) then
Set_Compare_Value (This.Generator.all, This.Channel, Pulse32);
else
-- for a Value of 0, the computation of Pulse wraps around, so we
-- only compute it when not zero

if Has_32bit_CC_Values (This.Generator.all) then
Pulse32 := UInt32 ((Timer_Period (This) + 1) * UInt32 (Value) / 100) - 1;
Set_Compare_Value (This.Generator.all, This.Channel, Pulse32);
else
Pulse16 := UInt16 ((Timer_Period (This) + 1) * UInt32 (Value) / 100) - 1;
Set_Compare_Value (This.Generator.all, This.Channel, Pulse16);
end if;
Set_Compare_Value (This.Generator.all, This.Channel, UInt16 (Pulse32));
end if;

end Set_Duty_Cycle;

-------------------
Expand Down Expand Up @@ -438,4 +445,14 @@ package body STM32.PWM is
This'Address = STM32_SVD.TIM13_Base or
This'Address = STM32_SVD.TIM14_Base);

---------------------------
-- Data_Register_Address --
---------------------------

function Data_Register_Address
(This : PWM_Modulator) return Address is
begin
return Data_Register_Address (This.Generator.all);
end Data_Register_Address;

end STM32.PWM;
8 changes: 8 additions & 0 deletions arch/ARM/STM32/drivers/stm32-pwm.ads
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@

with STM32.GPIO; use STM32.GPIO;
with STM32.Timers; use STM32.Timers;
with System; use System;

package STM32.PWM is
pragma Elaborate_Body;
Expand Down Expand Up @@ -148,6 +149,9 @@ package STM32.PWM is

subtype Percentage is Integer range 0 .. 100;

function Calculate_Compare_Value
(This : in out PWM_Modulator; Value : Percentage) return UInt32;

procedure Set_Duty_Cycle
(This : in out PWM_Modulator;
Value : Percentage)
Expand Down Expand Up @@ -190,6 +194,10 @@ package STM32.PWM is
Polarity : in Timer_Output_Compare_Polarity);
-- Set the polarity of the complimentary output of This modulator.

function Data_Register_Address
(This : PWM_Modulator) return Address with Inline;
-- Returns the address of the Timer DMAR Register.

Invalid_Request : exception;
-- Raised when the requested frequency is too high or too low for the given
-- timer and system clocks when calling Configure_PWM_Timer, or when
Expand Down
10 changes: 10 additions & 0 deletions arch/ARM/STM32/drivers/stm32-timers.adb
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,16 @@ package body STM32.Timers is
This.CR2.Capture_Compare_DMA_Selection := False;
end Disable_Capture_Compare_DMA;

---------------------------
-- Data_Register_Address --
---------------------------

function Data_Register_Address
(This : Timer) return Address is
begin
return This.DMAR'Address;
end Data_Register_Address;

-----------------------
-- Current_Prescaler --
-----------------------
Expand Down
3 changes: 3 additions & 0 deletions arch/ARM/STM32/drivers/stm32-timers.ads
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,9 @@ package STM32.Timers is
procedure Disable_Capture_Compare_DMA
(This : in out Timer);

function Data_Register_Address
(This : Timer) return Address with Inline;

----------------------------------------------------------------------------

-- Output Compare Management ----------------------------------------------
Expand Down
Loading